Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ npm run gen-metrics # regenerate scripts/metrics.json
For specific workflows, load the matching skill under `ai-skills/` rather than improvising:

- `wm-ai-release-notes` — add or edit entries in a versioned release notes file under `docs/release-notes/`.
- `wm-ai-release-notes-draft` — draft a whole release notes file from a source sheet of shipped tickets (CSV/XLSX export, GitLab branch-compare report, or pasted developer notes).
- `wm-feature-announcements` — new post under `blogs/feature-announcements/`.
- `wm-ai-blog` — new narrative or thought-leadership post under `blogs/blog/`.
- `wm-ai-create-guide` — create a how-to or tutorial page under `docs/guide/`.
Expand Down
169 changes: 169 additions & 0 deletions ai-skills/wm-ai-release-notes-draft/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
name: wm-ai-release-notes-draft
description: >
Use this skill when drafting a whole release notes file from a source list of shipped work — a CSV or
XLSX export of Jira tickets, a GitLab branch-compare HTML report, or a block of notes pasted by a
developer. Activate when the user hands over a sheet or report and asks to turn it into release notes,
fill in a version's notes, or process a release's tickets in bulk. For adding or editing one entry at a
time in an existing file, use `wm-ai-release-notes` instead.
license: MIT
metadata:
version: 0.1.0
surface: docs/release-notes
docusaurus: ^3.9.0
---

# Drafting Release Notes From a Source Sheet

Turn a list of shipped tickets into a complete draft of a versioned release notes file. The source is written by and for engineers and QA — your job is to decide what customers should be told, and say it in their language.

This skill covers **triage, enrichment, and reporting**. It does not restate entry format. Titles, bodies, pills, links, tabs, accordians, and the release overview line are defined in `../wm-ai-release-notes/references/conventions.md` — read it before writing entries.

## When to use

- The user provides a sheet, report, or pasted list covering a release and wants the notes drafted.
- The user wants a source list triaged into Features / Enhancements / Bug Fixes.
- The user wants to know what in a source list is worth announcing at all.

## When NOT to use

- Adding or editing one entry in an existing file → use `wm-ai-release-notes`.
- A public-facing post celebrating a feature → use `wm-ai-feature-announcements`.

## Relationship to `wm-ai-release-notes`

That skill confirms every entry with the user before writing. **Bulk drafting replaces per-entry confirmation** — otherwise a 50-ticket sheet becomes 50 round trips. Confirm these two things up front instead, then write the whole draft and report:

1. **Target file and version** — resolve the path and say which file you will write.
2. **Whether to enrich from Jira** — the user has answered both ways on different releases. Ask; do not assume.

Everything else is reported after the draft is written, not asked before.

## Procedure

### Step 1 — Parse the source

Read the sheet into a ticket list: id, title, description, and any repo or branch column. See `references/source-formats.md` for CSV, XLSX, GitLab-HTML, and pasted-notes recipes.

**Always de-duplicate by ticket id.** These sheets repeat rows — one 60-row export covered 49 unique tickets. Report both counts.

### Step 2 — Follow the sources

**Every reference in the sheet is a lead, and every lead gets followed or raised. Never silently skip one.**

A ticket's real content is often not in the sheet. It sits behind a link. An entry written without opening that link is a guess dressed as a finding.

This applies to **any** outbound reference, whatever the host. Jira, Basecamp, GitLab, GitHub, and Jenkins are the common ones, but the rule is not a list of approved platforms — a wiki page, a dashboard, a spreadsheet, a recording, or a one-off internal URL all count. If a ticket points somewhere, go there.

#### Jira

Fetch every ticket by key and pull `summary`, `description`, `issuetype`, `status`, `resolution`, and `fixVersions`. The recipe, including the large-output workaround, is in `references/source-formats.md`.

Jira earns its keep in three ways:

- **Umbrella tickets.** A ticket whose description is just links to other tickets is unwritable as-is. Fetch the linked ids and you get the real list.
- **`issuetype`.** A Jira `Improvement` is an Enhancement; a `Bug` is a Bug Fix. Use it to check your own classification.
- **`fixVersions`.** Evidence about which release an item belongs to — see Step 3.

#### Everything else a ticket points to

Open it if any connector or fetch tool can reach it, whatever the platform. A ticket whose entire description is a link is the strongest signal that the link holds the content.

#### When you cannot resolve a reference

Four cases, one rule — **ask the user, and say exactly what you could not reach.**

| Situation | What to do |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No connector, or it needs authorizing | Name the platform and ask the user to authorize it. OAuth cannot run in a non-interactive session. Offer to continue meanwhile, with the affected entries flagged |
| The link is dead, private, or returns nothing | Say which ticket and which link, and ask how they want it handled |
| Only an id, no URL (`WMS-12345`, `#4821`) | Ask for the base URL or project rather than guessing a host |
| Opened it and it is still unclear | Say what you read and what remains ambiguous. Ask rather than inventing a plausible reading |

Never let an unreachable reference quietly become an exclusion or a confident-sounding sentence. Carry it into the report under **Unchecked sources**.

### Step 3 — Check `fixVersions` against the target release

`fixVersions` is **evidence, not a verdict**. Sheet filenames are often wrong, and the user may deliberately place items elsewhere — for one release they chose to put everything not-yet-documented into the open version regardless of what Jira said.

So: compare, and if a material number of tickets disagree with the target release, **report the mismatch and let the user decide placement.** Never silently re-target.

### Step 4 — Check what is already published

Read the other release notes files in the same version series. Source reports routinely include work already announced — a cherry-picked fix appears in two branches, or a feature's first commit lands one release before its polish.

If an item is already documented in an earlier file, do not announce it again. Report it as already covered.

### Step 5 — Triage

Apply `references/triage-rules.md`. In order:

1. **Drop** what no customer can observe.
2. **Merge** tickets describing one defect, and umbrella tickets into one entry with sub-bullets.
3. **Split** a ticket that contains both a fix and an enhancement.
4. **Classify** into tab and accordian.

### Step 6 — Write entries

Rewrite each kept item in customer language per `references/triage-rules.md`, then format it per `conventions.md`.

Write the release overview line **last**, from the entries that ended up in the file.

### Step 7 — Validate

AGENTS.md makes this a hard gate:

```bash
npm run lint
npm run build
```

Both must pass. Run the build in the background; it is slow.

### Step 8 — Report

See **Reporting** below. Do not skip it — it is the part the user actually reviews.

## Repo to accordian mapping

For GitLab compare reports spanning repos:

| Repo | Accordian | Pill |
| -------------------------- | ------------------------------------------------------------------------ | -------- |
| `wavemaker-react-codegen` | User Interface | web |
| `wavemaker-react-runtime` | User Interface | web |
| `wavemaker-foundation-css` | User Interface | per item |
| `wavemaker-ng-studio` | Platform, except canvas / visual-editor / theming items → User Interface | per item |
| `projects-hub` | Platform | none |
| `wm-agent-server` | Platform | none |

## Reporting

The user's standing instruction is **do not skip anything without telling them.** Every source row ends up in exactly one bucket, and the report accounts for all of them:

- **Added** — counts per tab and accordian.
- **Excluded** — every dropped ticket with its reason. List them individually; a total is not enough.
- **Already covered** — items found in an earlier release file, naming that file.
- **Merged or split** — which tickets were combined or divided, and why.
- **Low confidence** — entries where the ticket was too vague to be sure. Say plainly that the wording is your best reading, not established fact.
- **Unchecked sources** — every link or reference you could not open, with the ticket it belongs to and why it failed. This is the bucket that is easiest to drop and most damaging to drop, because a skipped link looks identical to a ticket that had nothing behind it.
- **Open gaps** — a new capability with no doc to link, an unresolved placeholder, a missing pill you could not determine.

## Common mistakes to avoid

- **Trusting the sheet's filename for the version.** Check `fixVersions`, and ask.
- **Per-entry confirmation on a bulk draft.** Confirm the file and the Jira question, then draft.
- **Announcing something twice.** Check earlier files in the series first.
- **Keeping QA phrasing.** See the de-jargon test in `references/triage-rules.md`.
- **Skipping a link you could not open.** Follow every reference, or raise it. An unreachable link is never a silent exclusion.
- **Guessing a host from a bare id.** Ask for the base URL instead.
- **Inventing a cause.** State only what the ticket states.
- **Inventing a doc link.** Grep `docs/` first; if nothing covers it, add no link and report the gap.
- **Counts in the overview line.** They go stale on the next edit.
- **Naming a customer or their app.** This repo is public.

## Reference files

- Triage, exclusion, and rewriting rules: `references/triage-rules.md`
- Parsing and Jira recipes: `references/source-formats.md`
- Entry format and style: `../wm-ai-release-notes/references/conventions.md`
155 changes: 155 additions & 0 deletions ai-skills/wm-ai-release-notes-draft/references/source-formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Source Formats and Recipes

Parsing the inputs these drafts come from, and enriching them from Jira.

Write intermediate files to the session scratchpad, never into the repo.

## XLSX

`openpyxl` is available; `pandas` is not.

```python
import openpyxl
wb = openpyxl.load_workbook('<path>.xlsx', data_only=True)
ws = wb[wb.sheetnames[0]]
rows = list(ws.iter_rows(values_only=True))
header, body = rows[0], rows[1:]
```

Columns are typically: `S.No`, `Jira ID`, `Title`, `Description`, `Developer (Assignee)`, `Jira Link`.

Dumping every row at once can blow the context budget. Probe the shape first, then pull the columns you need.

## CSV

Standard CSV with quoted multi-line descriptions. Use `csv.DictReader`, not line splitting — descriptions contain commas, newlines, and code blocks.

## GitLab branch-compare HTML

Reports titled "Listing Differences Between Branches". Structure:

- An `h4` per repo, each followed by a table of commits.
- Columns: `S.NO`, `Commit Message`, `WMS ID(s)`, `Title`, `Description`, `Author`, `URL`, `Date & Time`, `Branch`.
- A "Nothing to compare in:" list naming repos with no changes — that is a real finding, not an omission.

Notes specific to this format:

- The `Title` and `Description` columns are the **Jira** title and description, so several commits repeat the same text. Group by ticket id, not by row.
- The `Branch` column can name a different release branch than the report's own comparison, usually a cherry-pick. Treat it as a hint to check whether the item is already published.
- One ticket commonly spans several repos.

## Pasted developer notes

Sometimes a developer pastes prose already grouped into Features / Improvements / Bug Fixes. Treat the grouping as a proposal, not a decision:

- Their "Improvements" usually map to Enhancements.
- Check for an item appearing as both a feature and a bug fix — adding a capability and fixing that same capability within one release is churn. Keep the capability, and raise the duplicate with the user rather than dropping it silently.
- Ask which accordian it belongs to if they have not said.

## De-duplicating

Source sheets repeat rows. Always collapse by ticket id and report both numbers:

```python
seen, unique = set(), []
for r in body:
tid = r[1]
if tid in seen:
continue
seen.add(tid)
unique.append(r)
print(len(body), "rows ->", len(unique), "tickets")
```

## Jira enrichment

Fetch every ticket in one query rather than one call per ticket.

```yaml
searchJiraIssuesUsingJql
cloudId: wavemaker.atlassian.net
jql: key in (WMS-1111,WMS-2222,...)
fields: ["summary","description","status","issuetype","resolution","fixVersions"]
maxResults: 50
responseContentFormat: markdown
```

Include any ids linked from an umbrella ticket in the same query.

### Handling the oversized result

The response exceeds the tool's token cap and is written to a file instead. Extract with `jq`, putting the filter in a file — escaping a quoted `join(", ")` inline fails under the shell:

```bash
cat > "$SP/extract.jq" << 'EOF'
.issues.nodes[] | "===== \(.key) =====\nSUMMARY: \(.fields.summary)\nTYPE: \(.fields.issuetype.name)\nSTATUS: \(.fields.status.name)\nRESOLUTION: \(.fields.resolution.name // "null")\nFIXVERSIONS: \([.fields.fixVersions[]?.name] | join(", "))\nDESCRIPTION:\n\(.fields.description // "null")\n"
EOF
jq -r -f "$SP/extract.jq" "$RESULT_FILE" > "$SP/jira.txt"
```

Then scan the one-line fields across all tickets before reading any full description:

```bash
grep -E "^=====|^SUMMARY:|^TYPE:|^FIXVERSIONS:" "$SP/jira.txt"
```

Read full descriptions only for the tickets that are ambiguous.

### When the connector is unauthorized

OAuth cannot run in a non-interactive session. Tell the user to re-authorize it in their connector settings, and offer to proceed from the sheet alone with uncertain entries flagged.

## Links inside tickets

Descriptions in these sheets frequently carry the real content behind a link. Treat **every** outbound reference as a lead to follow, and report any you could not reach.

The table below is what turns up most often — it is **not** an allowlist. Anything a ticket points to counts, including internal URLs, dashboards, wikis, spreadsheets, and recordings on hosts not named here. If you do not recognise the host, still try it, and raise it if you cannot reach it.

| Source | Typically holds | Notes |
| ------------------------------------------------------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Jira (`browse/WMS-nnnnn`) | The ticket itself, or the children of an umbrella | Fetch with the connector; include linked ids in the same JQL query |
| Basecamp (`app.basecamp.com/.../todos/...`, `/messages/...`) | The entire specification for a feature whose Jira description is one line | A description that is *only* a Basecamp link means the ticket text is not the source of truth |
| GitLab / GitHub (merge request, PR, commit) | What actually changed, when the ticket is vague | Useful for deciding fix vs enhancement |
| Jenkins or any other CI | Build and job history behind a build-related ticket | |
| Confluence or any wiki | Specs and design docs | |
| Google Drive | Screen recordings and screenshots | Often the only evidence for a QA ticket; usually not machine-readable |
| Figma | Design specs for a UI ticket | |
| Anything else | Whatever the author thought was worth linking | Same rule: open it, or raise it |

Two shapes to watch for in exported descriptions:

- Atlassian smart links appear as a `custom` element wrapping the URL rather than as plain markdown.
- Inline images appear as `blob:` URLs that resolve to nothing outside the browser session. An image-only description carries no text you can use — treat it as unreachable and say so.

### A bare id with no URL

A sheet may reference `WMS-12345`, `#4821`, or `PROJ-99` with no host. Do not guess the instance. Ask the user for the base URL or the project, then fetch.

### Still unclear after opening it

Opening a link does not guarantee an answer — a Drive recording, a screenshot-only ticket, or a thread that never states the outcome can all leave the behaviour ambiguous. Say what you read, say what is still missing, and ask. A plausible-sounding sentence built on an unresolved reference is the failure this rule exists to prevent.

## Checking what is already published

Before writing, pull the headings from the other files in the version series to catch both duplicate announcements and near-identical titles:

```bash
grep -h "^ - ###" docs/release-notes/<series>/*.mdx
```

Make a new title distinct from an existing one even when the underlying defects differ.

## Verifying the result

```bash
python3 -c "
c = open('<file>.mdx').read()
print('entries:', c.count('- ###'))
print('tabs:', c.count('<ReleaseNotesTabs>'), c.count('</ReleaseNotesTabs>'))
print('placeholders left:', c.count('{/* Content */}'))
"
```

A balanced-tag check will not catch leftover QA phrasing — a keyword scan only finds names you already thought of. Re-read each body against the de-jargon test in `triage-rules.md`.

Then run the `npm run lint` and `npm run build` gate from AGENTS.md.
Loading