|
| 1 | +# Complete Release Summary Coverage Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Prevent SDL-MCP releases from publishing concise grouped summaries that fail to account for work in the complete previous-tag-to-release-tag commit range. |
| 6 | + |
| 7 | +**Architecture:** A dependency-free release-notes module owns the canonical Git inventory, strict hidden coverage-marker parser, validation, and final body rendering. `prepare-release` invokes validation before expensive gates; the repository release skills require final-tag rendering before `gh release create --notes-file`. |
| 8 | + |
| 9 | +**Tech Stack:** Node.js 24 ESM, Node standard library, Git CLI, `node:test`, existing SDL-MCP release scripts. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Chunk 1: Validator and renderer |
| 14 | + |
| 15 | +### Task 1: Establish assertion-level RED |
| 16 | + |
| 17 | +**Files:** |
| 18 | +- Create: `tests/unit/build-release-notes.test.ts` |
| 19 | +- Create bootstrap only: `scripts/build-release-notes.mjs` |
| 20 | +- Reference: `devdocs/superpowers/specs/2026-08-25-release-summary-coverage-design.md` |
| 21 | + |
| 22 | +- [ ] **Step 1: Write the initial import test** |
| 23 | + |
| 24 | +Import the wished-for exports: |
| 25 | + |
| 26 | +```typescript |
| 27 | +import { |
| 28 | + buildReleaseNotes, |
| 29 | + extractVersionSection, |
| 30 | + readReleaseInventory, |
| 31 | + validateReleaseNoteCoverage, |
| 32 | +} from "../../scripts/build-release-notes.mjs"; |
| 33 | +``` |
| 34 | + |
| 35 | +- [ ] **Step 2: Run once and capture bootstrap RED** |
| 36 | + |
| 37 | +```bash |
| 38 | +node --experimental-strip-types --test-concurrency=1 --test tests/unit/build-release-notes.test.ts |
| 39 | +``` |
| 40 | + |
| 41 | +Expected: FAIL because `scripts/build-release-notes.mjs` does not exist. |
| 42 | + |
| 43 | +- [ ] **Step 3: Add skeletal exports only** |
| 44 | + |
| 45 | +Create exports that throw `new Error("not implemented")`. Do not add parsing, Git, validation, or rendering behavior. |
| 46 | + |
| 47 | +- [ ] **Step 4: Write behavior tests** |
| 48 | + |
| 49 | +Use full synthetic 40-hex object IDs. Assert: |
| 50 | + |
| 51 | +- exact version-section extraction; |
| 52 | +- one visible summary bullet may cover multiple commits; |
| 53 | +- the renderer emits one `## Commits since <baseTag>` appendix; |
| 54 | +- missing, duplicate, unknown, abbreviated, include-plus-omit, malformed, orphaned, and out-of-section markers fail; |
| 55 | +- only `merge-only` is accepted, and only for a commit with multiple parents; |
| 56 | +- the final tagged release commit is implicitly allowed only when its sole parent equals `preReleaseTargetOid`, its subject is `chore: release v<version>`, and all changed paths are in the exact allowlist. |
| 57 | + |
| 58 | +Create disposable repositories with `mkdtempSync`, `git init`, commits, and an annotated tag. Assert: |
| 59 | + |
| 60 | +- refs resolve through `^{commit}`; |
| 61 | +- a missing ref or non-ancestor base tag fails; |
| 62 | +- shallow repositories fail; |
| 63 | +- inventory includes merge commits; |
| 64 | +- bounded buffer overflow fails rather than returning partial data. |
| 65 | + |
| 66 | +- [ ] **Step 5: Run and capture assertion-level RED** |
| 67 | + |
| 68 | +Run the focused test. |
| 69 | + |
| 70 | +Expected: parser, inventory, renderer, and final-release-commit assertions fail with `not implemented`, proving each behavior is exercised. |
| 71 | + |
| 72 | +### Task 2: Implement the minimum dependency-free module |
| 73 | + |
| 74 | +**Files:** |
| 75 | +- Modify: `scripts/build-release-notes.mjs` |
| 76 | +- Test: `tests/unit/build-release-notes.test.ts` |
| 77 | + |
| 78 | +- [ ] **Step 1: Implement strict section and marker parsing** |
| 79 | + |
| 80 | +Export: |
| 81 | + |
| 82 | +```javascript |
| 83 | +export function extractVersionSection(markdown, version) {} |
| 84 | +export function validateReleaseNoteCoverage(options) {} |
| 85 | +``` |
| 86 | + |
| 87 | +Use line-based parsing only. Accept exactly: |
| 88 | + |
| 89 | +```text |
| 90 | + <!-- release-note-commits: <40hex>[ <40hex>...] --> |
| 91 | +<!-- release-note-omit: <40hex> merge-only --> |
| 92 | +``` |
| 93 | + |
| 94 | +Bind include markers only to the immediately preceding summary bullet inside the exact version section. Reject every malformed or duplicate assignment. |
| 95 | + |
| 96 | +- [ ] **Step 2: Implement canonical Git inventory** |
| 97 | + |
| 98 | +Export: |
| 99 | + |
| 100 | +```javascript |
| 101 | +export function readReleaseInventory({ cwd, baseTag, target }) {} |
| 102 | +``` |
| 103 | + |
| 104 | +Use `execFileSync("git", args, { maxBuffer: 16 * 1024 * 1024 })`; never invoke a shell. Resolve `baseTag^{commit}` and `target^{commit}`, reject shallow repositories and non-ancestor ranges, and collect full object ID, parents, subject, and changed paths for every commit in `baseTag..target`. |
| 105 | + |
| 106 | +- [ ] **Step 3: Implement final body rendering** |
| 107 | + |
| 108 | +Export: |
| 109 | + |
| 110 | +```javascript |
| 111 | +export function buildReleaseNotes(options) {} |
| 112 | +``` |
| 113 | + |
| 114 | +In build mode, read the changelog from the immutable target commit with `git show <target>^{commit}:CHANGELOG.md`, not from the working tree. Validate the grouped section, allow only the mechanically valid final release commit exception, then append exactly one deterministic commit appendix from the same inventory. |
| 115 | + |
| 116 | +- [ ] **Step 4: Add the CLI** |
| 117 | + |
| 118 | +Support: |
| 119 | + |
| 120 | +```text |
| 121 | +node scripts/build-release-notes.mjs validate --version <version> --base-tag <tag> --target <ref> |
| 122 | +node scripts/build-release-notes.mjs build --version <version> --base-tag <tag> --target <tag> --pre-release-target <oid> --output <path> |
| 123 | +``` |
| 124 | + |
| 125 | +Validation prints machine-readable JSON containing `preReleaseTargetOid`. Build mode writes the validated body only after every check passes. |
| 126 | + |
| 127 | +- [ ] **Step 5: Run focused tests and capture GREEN** |
| 128 | + |
| 129 | +Run the focused test command. |
| 130 | + |
| 131 | +Expected: all tests pass with zero failures. |
| 132 | + |
| 133 | +- [ ] **Step 6: Run syntax and diff checks** |
| 134 | + |
| 135 | +```bash |
| 136 | +git diff --check |
| 137 | +node --check scripts/build-release-notes.mjs |
| 138 | +``` |
| 139 | + |
| 140 | +Expected: both exit 0. |
| 141 | + |
| 142 | +## Chunk 2: Release workflow integration and documentation |
| 143 | + |
| 144 | +### Task 3: Gate release preparation |
| 145 | + |
| 146 | +**Files:** |
| 147 | +- Modify: `scripts/prepare-release.mjs` |
| 148 | +- Test: `tests/unit/build-release-notes.test.ts` |
| 149 | + |
| 150 | +- [ ] **Step 1: Add a failing prepare-release integration assertion** |
| 151 | + |
| 152 | +Assert the release script imports or invokes the canonical validator before its existing expensive build/test commands and requires an explicit `--base-tag`. |
| 153 | + |
| 154 | +- [ ] **Step 2: Run focused test and capture RED** |
| 155 | + |
| 156 | +Expected: FAIL because `prepare-release` does not invoke coverage validation. |
| 157 | + |
| 158 | +- [ ] **Step 3: Add the minimal integration** |
| 159 | + |
| 160 | +Parse `--base-tag <tag>`, invoke validation with package version and `HEAD` before expensive gates, and print the returned full `preReleaseTargetOid`. Preserve every existing release check and exit-code contract. |
| 161 | + |
| 162 | +- [ ] **Step 4: Run focused tests and capture GREEN** |
| 163 | + |
| 164 | +Expected: all focused tests pass. |
| 165 | + |
| 166 | +### Task 4: Synchronize required release instructions |
| 167 | + |
| 168 | +**Files:** |
| 169 | +- Modify: `.codex/skills/release-notes/SKILL.md` |
| 170 | +- Modify: `.agents/skills/release-notes/SKILL.md` |
| 171 | +- Test: `tests/unit/build-release-notes.test.ts` |
| 172 | + |
| 173 | +- [ ] **Step 1: Add failing guidance assertions** |
| 174 | + |
| 175 | +For each skill file independently, assert it requires: |
| 176 | + |
| 177 | +- the complete unbounded range; |
| 178 | +- concise grouped summaries; |
| 179 | +- hidden coverage markers; |
| 180 | +- no second visible commit list; |
| 181 | +- preservation of `preReleaseTargetOid`; |
| 182 | +- final body generation from the annotated tag; |
| 183 | +- `gh release create --notes-file`. |
| 184 | + |
| 185 | +Do not require unrelated bytes in the two skill files to remain identical. |
| 186 | + |
| 187 | +- [ ] **Step 2: Run focused test and capture RED** |
| 188 | + |
| 189 | +Expected: FAIL because current skills lack the coverage-ledger and final-tag steps. |
| 190 | + |
| 191 | +- [ ] **Step 3: Update both skill copies** |
| 192 | + |
| 193 | +Keep the existing categories and concise grouped-summary rules. Add the explicit ledger/build workflow. Do not expose hashes twice in rendered output. |
| 194 | + |
| 195 | +- [ ] **Step 4: Run focused test and capture GREEN** |
| 196 | + |
| 197 | +Expected: all focused tests pass. |
| 198 | + |
| 199 | +## Chunk 3: Verification and handoff |
| 200 | + |
| 201 | +### Task 5: Verify the implementation |
| 202 | + |
| 203 | +**Files:** |
| 204 | +- Verify all files above. |
| 205 | + |
| 206 | +- [ ] **Step 1: Run the focused regression** |
| 207 | + |
| 208 | +```bash |
| 209 | +node --experimental-strip-types --test-concurrency=1 --test tests/unit/build-release-notes.test.ts |
| 210 | +``` |
| 211 | + |
| 212 | +Expected: zero failures. |
| 213 | + |
| 214 | +- [ ] **Step 2: Run affected release/tooling checks** |
| 215 | + |
| 216 | +```bash |
| 217 | +npm run typecheck |
| 218 | +npm run lint |
| 219 | +git diff --check |
| 220 | +``` |
| 221 | + |
| 222 | +Expected: typecheck and diff check exit 0; lint has zero errors, with only known warnings if present. |
| 223 | + |
| 224 | +- [ ] **Step 3: Exercise final-tag rendering in a synthetic repository** |
| 225 | + |
| 226 | +Create a disposable tagged fixture containing grouped summaries and hidden coverage markers, then run build mode against its immutable annotated tag. |
| 227 | + |
| 228 | +Expected: validation succeeds, grouped prose is concise, and one appendix contains every fixture commit exactly once. |
| 229 | + |
| 230 | +- [ ] **Step 4: Verify historical inventory separately** |
| 231 | + |
| 232 | +Call `readReleaseInventory({ cwd: process.cwd(), baseTag: "v0.13.4", target: "v0.13.5" })` from a one-line Node ESM command. |
| 233 | + |
| 234 | +Expected: 35 commits, with no attempt to validate pre-marker historical changelog content and no external mutation. |
| 235 | + |
| 236 | +- [ ] **Step 5: Review the final diff** |
| 237 | + |
| 238 | +Confirm every changed line traces to full-range grouped-summary enforcement. Confirm no package, lockfile, workflow, tag, or release mutation. |
| 239 | + |
| 240 | +- [ ] **Step 6: Commit only if explicitly authorized** |
| 241 | + |
| 242 | +If commit authorization is confirmed, create one implementation commit separate from the approved design and plan commits. Otherwise leave the verified implementation uncommitted and report that state. |
0 commit comments