---
name: apt:release-notes
description: "Persona-aware user-facing release notes — draft, compile, publish"
apt-skill-version: {{APT_VERSION}}
stage: ops
intent: capture
when_to_use: "The user wants to draft, compile, or publish persona-aware user-facing release notes for a project that opted in via /apt:setup."
user_invocable: true
internal: false
spawns_agent: false
agent_name: null
task_context: none
default_track: DEEP
default_execution_mode: auto
execution_modes:
  - auto
  - step
allowed-tools: "Bash, Read, Write, Edit, Grep, Glob"
argument-hint: "apt:release-notes [--draft --pr <n> | --compile <tag> | --publish <tag>]"
gates: []
---

<objective>
Produce user-facing release notes for a project that opted in via
`/apt:setup` → Batch 9 → "Publish user-facing release notes? Yes". This
skill is separate from `/apt:docs changelog` — the dev-flavored
`CHANGELOG.md` surface stays its responsibility; this skill owns the
persona-voice-tuned narrative that ships to end users.

Three modes:

- `--draft --pr <n>` — write a per-PR fragment file (towncrier-style).
  Usually called automatically by `/apt:close-task` on merge; rarely
  invoked by hand.
- `--compile <tag>` — at release time, read every accumulated fragment,
  filter by the configured persona tier, write a single per-tag
  narrative file, and clear the unreleased dir.
- `--publish <tag>` — shell `gh release create/edit` to push the
  compiled body to the GitHub Release.

Rules of the road (locked):

- **Per-project opt-in (spec ID-7).** Presence of `.aperant/personas.json`
  does NOT auto-enable this skill. The toggle is
  `config.changelog.release_notes.enabled` — opt-in is explicit via
  `/apt:setup` Batch 9.
- **No LLM calls in the CLI (spec ID-3).** The deterministic verbs
  (`apt-tools release-notes draft|compile|publish`) handle file I/O only.
  Persona-voice tuning is authored by you — the host LLM — using the
  `appendices/persona-voice.md` loader.
- **Triage bucket is non-negotiable (spec ID-6).** Fragments without a
  recognized persona tag MUST surface in a `## Needs Triage` section at
  compile time. Never silent-drop. This is the human curator's safety
  net against trust collapse over time.
- **Fast Path Guarantee (CLAUDE.md).** This skill is DEEP-track-only
  surface area. `/apt:quick` never enters this skill; the
  `apt-release-notes-quick-exempt.test.ts` regression test pins the
  exemption.
</objective>

<your_environment>
- **Working directory:** The project root
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary.
- **gh CLI dependency:** required only for `--publish`. The `publish` verb returns a graceful `{status: 'skipped', reason: 'gh-not-found'}` envelope when `gh` is unavailable — the skill never crashes the host on a missing binary.
- **Output:** `docs/releases/_unreleased/PR-<n>.md` (fragments) + `docs/releases/<tag>.md` (compiled) + GitHub Release body (when published).
</your_environment>

<state_files>
## State Files

**Reads:**
- `.aperant/config.json` — `changelog.release_notes.{enabled, output_dir, persona_filter, require_pr_field}` and the stage blocks.
- `.aperant/personas.json` — persona roster + tier; used by `compile` to filter fragments.
- `docs/releases/_unreleased/PR-*.md` — fragments accumulated since the last tag.
- `PROJECT.md` (when present) — Core Value section feeds the hero narrative the host LLM authors at compile time.

**Writes:**
- `docs/releases/_unreleased/PR-<n>.md` — towncrier-style fragments (draft mode).
- `docs/releases/<tag>.md` — per-tag compiled narrative (compile mode).
- GitHub Release body via `gh release create/edit` (publish mode).
</state_files>

<process>

## 0. Hard-Gate + Opt-in Check

No hard gates (`gates: []`). Before running ANY mode, confirm the project opted in:

```bash
rn_enabled=$(jq -r '.changelog.release_notes.enabled // false' .aperant/config.json)
if [ "$rn_enabled" != "true" ]; then
  echo "[apt:release-notes] This project has not opted into user-facing release notes."
  echo "                    Run /apt:setup → 'Changelog & Release Notes' to enable."
  exit 0
fi
```

When opted out, exit cleanly — never crash, never write any file.

## 1. Parse Flags

The skill's three modes are mutually exclusive. Parse `$ARGUMENTS`:

- `--draft --pr <n> --note "<text>" [--persona <name>] [--commits <sha,sha,...>] [--date <YYYY-MM-DD>]` → Draft mode (§2).
- `--compile <tag>` → Compile mode (§3).
- `--publish <tag> [--dry-run]` → Publish mode (§4).

If no mode flag is passed, display a short usage message and exit.

## 2. Draft Mode

Used to write a per-PR fragment. Usually this is called automatically by `/apt:close-task` on merge (see `apt-close-task/SKILL.md` §2.6), but it can be invoked by hand for backfills or for manual fragments.

Hand the inputs to the deterministic CLI:

```bash
node packages/framework/bin/apt-tools.mjs release-notes draft . \
  --pr {pr_number} \
  --note "{one-sentence user-impact note}" \
  [--persona {persona-name}] \
  [--date {ISO-date}]
```

The CLI is idempotent — re-running with identical inputs returns `{status: 'ok', written: false, reason: 'no-change'}`. Trust the envelope; do NOT manually inspect the file before running.

When you (the host LLM) compose the note, consult `appendices/persona-voice.md` for the persona-voice posture loader. The fragment goes into the user-facing pipeline; voice-tune accordingly.

## 3. Compile Mode

Used at release time to fold every accumulated fragment into a single per-tag narrative. The CLI does the deterministic work — your job is the narrative pass on the compiled body if the user wants polish beyond the structured per-PR list.

```bash
node packages/framework/bin/apt-tools.mjs release-notes compile . --tag {tag}
```

The envelope reports:

```json
{
  "status": "ok",
  "command": "release-notes-compile",
  "tag": "{tag}",
  "path": "docs/releases/{tag}.md",
  "fragments_total": N,
  "fragments_in_body": M,
  "fragments_in_triage": K,
  "fragments_filtered": F,
  "cleared_unreleased": true
}
```

If `fragments_in_triage > 0`, ALERT the user before proceeding to publish — those fragments need persona curation. Offer two options:

1. Edit the compiled file directly to move triage entries into the right tier section (and re-tag the persona in the original PR if appropriate).
2. Proceed to publish with the triage section intact — sometimes the right call when the unmapped change is genuinely cross-cutting.

If `fragments_total === 0`, gently note that there's nothing to compile yet (likely the user hasn't merged any PRs since the last release).

## 4. Publish Mode

Push the compiled body to the GitHub Release:

```bash
node packages/framework/bin/apt-tools.mjs release-notes publish . --tag {tag}
```

The CLI handles both `gh release create` (new release) and `gh release edit` (existing release). When `gh` is not on PATH, the envelope returns `{status: 'skipped', reason: 'gh-not-found'}` — surface the message to the user and suggest installing the GitHub CLI.

For local-only verification, pass `--dry-run` to skip the network call and just confirm the body file exists.

## 5. Re-invocation

Each mode is safe to re-run:

- `draft` — idempotent per spec AC2.
- `compile` — overwrites `<tag>.md` and re-clears `_unreleased/`. Running compile twice in a row with no new fragments produces an empty body — careful.
- `publish` — `gh release edit` is the second call's branch, so the GitHub Release body is replaced cleanly.

</process>

<appendices>
- `appendices/persona-voice.md` — Aperant-authored posture loader for voice-tuning the note text per persona archetype.
</appendices>

<integration>
| Skill | How it interacts |
|-------|------------------|
| `/apt:setup` Batch 9 | Writes the opt-in toggle + audience + persona-filter that this skill reads. |
| `/apt:ship` §2.6 | Auto-drafts the `Release note: …` line in the PR body BEFORE this skill runs; ship blocks if `require_pr_field=true` AND no signal. |
| `/apt:close-task` §2.6 | Invokes `apt-tools release-notes draft` on merge — drops the per-PR fragment automatically. |
| `/apt:docs changelog` | The dev-flavored sibling. Stays its own surface; this skill does NOT touch `CHANGELOG.md`. |
| `/apt:personas` | Produces the `.aperant/personas.json` roster that compile mode filters against. |
</integration>
