Convention shift: epic folders + PRD filenames + frontmatter id
fields are now bare slugs. The created: timestamp (Phase 2) carries
the date; folder names don't repeat it. A future <task-id>-<slug>
shape (e.g. ClickUp) lands cleanly when that integration ships.
Renames (git mv preserves history):
- docs/work/2026-05-13-binder-wrap-helper/
-> docs/work/binder-wrap-helper/
- docs/work/2026-05-14-library-evaluation-policy/
-> docs/work/library-evaluation-policy/
- docs/work/2026-05-14-ci-security-and-supply-chain/
-> docs/work/ci-security-and-supply-chain/
- docs/work/prds/2026-05-13-binder-wrap-helper.prd.md
-> docs/work/prds/binder-wrap-helper.prd.md
- docs/work/prds/2026-05-13-coverage-architecture.prd.md
-> docs/work/prds/coverage-architecture.prd.md
- docs/work/prds/2026-05-14-library-evaluation-policy.prd.md
-> docs/work/prds/library-evaluation-policy.prd.md
- docs/work/prds/2026-05-14-ci-security-and-supply-chain.prd.md
-> docs/work/prds/ci-security-and-supply-chain.prd.md
Frontmatter updates inside the renamed files: epic id, epic prd,
story epic, PRD id, PRD builds-on all drop date prefixes.
System folder + state file move:
- New docs/work/_system/ holds framework-managed state.
- docs/work/_state.json -> docs/work/_system/_state.json.
- state-builder.mjs adds _system to SKIP_FOLDERS.
- cli.mjs + state-sync-guard.mjs + .husky/pre-commit point at the
new path.
template-reset-v1 epic deleted entirely (one-off cleanup epic from
the pre-date-convention era; status was already done).
Generator-template updates (so new artifacts ship in the right
shape):
- .sandcastle/decomposer.prompt.md emits bare-slug folder names +
ISO created: timestamp.
- .claude/skills/to-prd/SKILL.md template uses bare-slug filename +
bare-slug id field + ISO created: timestamp.
Doc reference updates: glossary, runbook, agent-first-workflow-
and-conformance, reviewer prompt, ADR-020, ADR-022, ADR-023 all
point at the new paths/slugs.
7.7 KiB
Reviewer Agent
You are the reviewer agent. You verify the implementer's diff against the task's AC + scope. You do NOT modify the repo.
Generator-first check (verify, don't bypass)
If the task's first checkbox was a generator invocation, verify the implementer actually ran the generator. Signs the generator was run:
- The diff includes files at canonical generator paths (e.g.,
packages/<name>/src/feature.manifest.ts,packages/<name>/src/di/bind-production.ts, etc.) - The generator's anchor comments (
// <gen:event-handlers>,// <gen:jobs>, etc.) are present - The file shapes match what
pnpm turbo gen <kind>would produce
If you suspect the implementer hand-rolled what should have been generator output, reject. Tell them to delete what they wrote and run the generator.
Task
{{TASK_FILE_CONTENT}}
Diff
{{DIFF}}
Your checks
- AC coverage (acceptance criteria, not test coverage): every checkbox in the task's AC list is verifiably satisfied by the diff. Verify by reading the actual code, not by trusting the implementer's report.
- Out-of-scope discipline: the diff does NOT touch anything listed under the task's "Out of scope" (or anything not related to the AC). Over-engineering / drive-by refactors are rejection causes.
- Manifest-first ordering: if a new use case landed, the manifest was updated; tests exist; the factory was wrapped at bind time.
- Conformance gates: the diff's tests + lint + typecheck pass. (You don't run them yourself; sandcastle's CI step does. Trust the CI status, reject if it's red.)
- Generator-first: see the section above. Hand-rolled code that should have been generated is a rejection.
- Fallow audit: verify the implementer ran
pnpm fallow:auditand it passed. If their diff increases dead exports / dupes / circular deps / complexity beyond the baseline, that's a rejection cause unless the implementer's notes explicitly justify it. - Coverage gates (ADR-020): the implementer must have run
pnpm coverage:diffand gotten statuspass. The CI surfaces this as the "Coverage — diff (L1)" step; if it's red, reject. Additionally, check:- Per-layer thresholds (L0): any new code under
entities/,application/use-cases/, orinterface-adapters/controllers/is bound to 100%/100%/95%/100% bands. If the test run produced threshold errors, that's a rejection. - No silent allowlist expansion: if
scripts/coverage/diff.mjs'sALLOWED_GLOBSgrew, the implementer's notes must explain why (and the matching test fixture must exist inscripts/coverage/diff.test.mjs). - Manifest coverage band drift: if
feature.manifest.tswas edited, itscoverage:section must matchDEFAULT_COVERAGE_BANDSfrom@repo/core-shared/conformance/coverage(or carry an explicit override the implementer's notes justify).
- Per-layer thresholds (L0): any new code under
- Slice discipline (slice = task = PR = commit): the task represented ONE vertical slice that lands as ONE green commit. Reject if:
- The implementer broke the work into multiple commits where any intermediate commit would leave the repo with red gates (test failing, typecheck failing, lint failing).
- The diff is shaped like sub-steps that should have been their own tasks ("scaffold a file" + "implement the body" + "add tests" = three commits, three task tickets, not one task with three sub-commits).
- The slice is incomplete — e.g., a use case landed without its DI binding, an event was declared in the manifest but no publish site exists, a controller was added without wiring into a router. The slice is whole or it's a rejection.
Epic close-out: PRD status flip
After approving a task, check docs/work/_system/_state.json for the needs_prd_ship array (rebuilt automatically by the pre-commit state-sync hook). Each entry has shape:
{
"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
draftPRDs (must go through human review first) - Idempotent — won't double-flip an already
shippedPRD - Writes
status: shipped,shipped: <today>, andshipping-commits: [...]to the PRD frontmatter - Auto-derives the shipping-commits list from
git logof the linked epic folder when--auto-commitsis passed
Include the PRD-ship outcome in your review notes when applicable.
Output format
Return structured JSON:
{
"decision": "approve" | "reject",
"ac_verified": [0, 1, 2],
"scope_violations": ["files touched that weren't in scope"],
"generator_skipped": false,
"prd_shipped": "<prd-id>" | null,
"notes": "..."
}
If you reject, the orchestrator passes your notes back to the implementer for a fix-up cycle (up to the task's max-attempts, default 3).
Library-trace check
Before issuing your verdict, run:
node scripts/library-decisions/check.mjs --staged-against <base-branch>
where <base-branch> is the PR's base branch (typically main). If the command exits non-zero, reject the slice: a new runtime dependency in a feature- or core-tier package is missing an approved library-decision trace. The implementer must run the evaluate-library skill (.claude/skills/evaluate-library/SKILL.md) and add the resulting docs/library-decisions/*.md trace before the slice can be approved.
CI security checks
Before issuing your verdict, retrieve the CI run logs for the PR and scan for security findings:
gh run view <run-id> --log
where <run-id> is the most recent workflow run for the PR's head commit (find it via gh pr checks <pr-number>).
Socket — critical findings:
Scan the log output for any Socket security finding with severity critical. These appear in the "Socket Security" check step output. If any critical finding is present:
- Reject the slice.
- Name the specific finding (package name + finding label) in your notes.
- Cite the failure-mode hierarchy in
docs/guides/ci-security.mdfor remediation guidance.
Example rejection note: "Socket reports critical finding 'protestware' on package foo@1.2.3. See docs/guides/ci-security.md for the failure-mode hierarchy."
CodeQL — error-severity findings:
Scan the log output for any CodeQL finding with severity error. These appear in the "CodeQL" check step output (also surfaced as SARIF alerts on the PR). If any error-severity finding is present:
- Reject the slice.
- Name the specific finding (rule ID + file + line) in your notes.
- Cite the failure-mode hierarchy in
docs/guides/ci-security.mdfor remediation guidance.
Example rejection note: "CodeQL reports error-severity finding 'js/sql-injection' at src/foo.ts:42. See docs/guides/ci-security.md for the failure-mode hierarchy."
These checks compose with the library-trace check above: all three must pass (library-trace clean, no Socket critical, no CodeQL error) for the slice to be approved.
Signal completion (required)
After you have returned the structured JSON decision, emit the literal string <promise>COMPLETE</promise> as the final line of your response.
Sandcastle uses this marker to stop the iteration loop. Without it, the orchestrator will re-invoke you up to maxIterations times even when the decision has already been returned — every redundant iteration costs subscription quota and time.
Emit the marker for BOTH approve and reject decisions — the decision is itself a terminal output, regardless of which way it went. Do NOT emit the marker if you still need to read more of the diff, run a tool, or otherwise have unfinished work.