feat(work): pnpm work prd-ship + auto-flip integration in sandcastle
Closes the PRD-lifecycle gap surfaced by the user: when sandcastle
finishes an epic's last task, the seed PRD should auto-flip from
approved -> shipped. Builds the mechanism, wires it into the work
CLI + state index + reviewer prompt + docs.
scripts/work/prd-ship.mjs (new):
- parseFrontmatter / serializeFrontmatter — minimal YAML-ish parser
sufficient for PRD frontmatter (scalar + list shapes)
- flipPrdStatus — pure function: takes PRD text, returns new text
with status=shipped + shipped=<date> + optional shipping-commits.
Refuses to flip draft, idempotent fail-soft on already-shipped,
rejects unexpected statuses
- deriveShippingCommits — best-effort git log of the linked epic
folder for the --auto-commits flag
- findPrdPath — id -> path lookup under docs/work/prds/
- runCli — wiring for `pnpm work prd-ship <id> [--commits|--auto-commits]`
scripts/work/prd-ship.test.mjs (new, 17 tests):
- Frontmatter parser handles scalars + lists + missing frontmatter
- flipPrdStatus covers all transitions + refusals + body/key preservation
- findPrdPath + serializeFrontmatter coverage
scripts/work/state-builder.mjs:
- Epic entries gain a `prd` field
- New computeNeedsPrdShip surfaces epics done with PRD status not yet
shipped: state.needs_prd_ship[] with action commands
scripts/work/cli.mjs:
- New subcommand `pnpm work prd-ship <id>`
.sandcastle/reviewer.prompt.md:
- "Epic close-out: PRD status flip" section instructing reviewer to
check _state.json.needs_prd_ship and run the suggested action
- JSON output extends with prd_shipped: "<id>" | null
docs/work/README.md:
- "PRD lifecycle" section documenting the 4 statuses + auto-flip
Future PRDs follow the lifecycle automatically: decomposer refuses
draft, human flips to approved, sandcastle ships the epic, reviewer
runs prd-ship on the final task, PRD lands as shipped with its
commit trail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,28 @@ If you suspect the implementer hand-rolled what should have been generator outpu
|
||||
- **No silent allowlist expansion**: if `scripts/coverage/diff.mjs`'s `ALLOWED_GLOBS` grew, the implementer's notes must explain why (and the matching test fixture must exist in `scripts/coverage/diff.test.mjs`).
|
||||
- **Manifest coverage band drift**: if `feature.manifest.ts` was edited, its `coverage:` section must match `DEFAULT_COVERAGE_BANDS` from `@repo/core-shared/conformance/coverage` (or carry an explicit override the implementer's notes justify).
|
||||
|
||||
## Epic close-out: PRD status flip
|
||||
|
||||
After approving a task, check `docs/work/_state.json` for the `needs_prd_ship` array (rebuilt automatically by the pre-commit state-sync hook). Each entry has shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"epic": "<epic-slug>",
|
||||
"prd": "<prd-id>",
|
||||
"prd_status": "approved",
|
||||
"action": "pnpm work prd-ship <prd-id> --auto-commits"
|
||||
}
|
||||
```
|
||||
|
||||
If the task you just approved was the FINAL task of an epic (i.e., the epic transitioned to `status: done`) and that epic appears in `needs_prd_ship`, the orchestrator must run the suggested `action` command before declaring the epic closed. The `prd-ship` command:
|
||||
|
||||
- Refuses to flip `draft` PRDs (must go through human review first)
|
||||
- Idempotent — won't double-flip an already `shipped` PRD
|
||||
- Writes `status: shipped`, `shipped: <today>`, and `shipping-commits: [...]` to the PRD frontmatter
|
||||
- Auto-derives the shipping-commits list from `git log` of the linked epic folder when `--auto-commits` is passed
|
||||
|
||||
Include the PRD-ship outcome in your review notes when applicable.
|
||||
|
||||
## Output format
|
||||
|
||||
Return structured JSON:
|
||||
@@ -47,6 +69,7 @@ Return structured JSON:
|
||||
"ac_verified": [0, 1, 2],
|
||||
"scope_violations": ["files touched that weren't in scope"],
|
||||
"generator_skipped": false,
|
||||
"prd_shipped": "<prd-id>" | null,
|
||||
"notes": "..."
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user