Files
Garry Tanandcapy-ai[bot] 148d28ea81 feat(ship): /ship and /autoplan size their review to the diff (GSTA-131) (#3527)
* wip(GSTA-131): review depth, fix-check transitions, dispatch counters, carry pregate, autoplan round policy

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* wip(GSTA-131): reviewers stay at full effort; audits light

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ship): Step 9 dispatches what gstack-review-plan plans; fix-check after below-floor fixes; cap two full passes plus one fix-check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: review-dispatch-compare has a free lane; review-depth fixtures registered free; resumed max_rounds case off the old default

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(GSTA-131): ship review pass-1 repairs: fix-check binds under its own skill, Step 11.5 accepts a fix-check or a planned adversarial skip, delta carry with a pregate, carry --pregate pinned to the checkout, quiet-after-cap revise regates, skill-dir markdown and rule docs classed code, gate signals from tests class up, plan fails closed on git errors, per-class compare ratios

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(GSTA-131): ship review pass-2 repairs: Step 11 follows the last full-pass plan, Important Rules restored, ship goldens regenerated, true per-class medians, review-policy transitions and once-per-invocation voices, docs/** assets stay docs-class, CLI and fix-check binding tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): GSTA-131 review depth, fix-check, delta /ship and autoplan rounds

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: ship-policy cap and counters, review-policy /review cycle cap, unattended passes (docs audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): 400-word findings cap fits the /review size pins (five host pins lowered by 2); CHANGELOG max_rounds wording

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): gate integrity names the size-ledger entry and the lowered /review pins

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: the round-budget case asserts the default through ROUNDS output only (pregate literals heuristic)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: curate ship-receipt-carry-pregate out of the Windows lane like its --pregate siblings

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): end the Windows curation sentence instead of a mid-sentence colon

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): one decision each for the review-loop exits, the planned adversarial skip and Step 16's TODOS rerun (screen judge repair)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): fix-check repairs: pin the renamed Step 11 heading, keep the below-floor exit at the cap, name the plan run a repeat reuses, print the native skip on outside-only plans

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 05:45:54 +00:00

240 lines
19 KiB
TypeScript

/**
* Review dashboard and plan-file review report resolvers.
*
* Moved from scripts/resolvers/review.ts.
*/
import { type TemplateContext } from './types';
export function generateReviewDashboard(ctx: TemplateContext): string {
const result = `## Review Readiness Dashboard
${ctx.skillName === 'ship' ? 'In pre-flight, read the review log and config to show readiness; Step 9 runs the new pre-landing review.' : 'After completing the review, read the review log and config to display the dashboard.'}
\`\`\`bash
~/.claude/skills/gstack/bin/gstack-review-read
\`\`\`
**1. Choose the records to display.** Use the latest record for each row below.
Do not use a record older than 7 days to clear a row, and never substitute an older
success for a newer failure. Ship metrics are not review records.
| Row | Choose the latest of | Status suffix |
|---|---|---|
| Eng Review | \`review\` or \`plan-eng-review\` | (DIFF) or (PLAN) |
| CEO Review | \`plan-ceo-review\` | — |
| Design Review | \`plan-design-review\` or \`design-review-lite\` | (FULL) or (LITE) |
| Adversarial | \`adversarial-review\` or legacy \`codex-review\` | — |
| Outside Voice | \`codex-plan-review\` from CEO or Eng review | — |
Keep each record's host, source, outside_provider, outside_status and phase.
Historical source "claude" is a native subagent; "claude-code" is the external CLI.
Do not infer old providers or unknown models from today's harness. A native result
does not fill missing, disabled or skipped outside coverage.
**Source attribution:** Append a recorded \`via\` to the suffix ("CLEAR (PLAN via
/autoplan)"); without one, keep "CLEAR (PLAN)" or "CLEAR (DIFF)". Below the dashboard, group \`autoplan-voices\`
and \`design-outside-voices\` by workflow run and phase. Show each phase's provider
and outside_status; retain partial coverage. These details do not clear Eng Review.
**2. Check freshness before choosing a verdict.**
- **Content-first rule:** For \`review\`, \`adversarial-review\`, \`codex-review\`,
ship-stage reviews and \`design-review-lite\`, use \`review_freshness.status\`
and show its \`reason\`. CURRENT means a completed clean review whose start and
end content fingerprints equal the current \`---WTREE---\` fingerprint.
STALE or UNVERIFIED cannot clear Eng Review. Missing \`review_freshness\`,
including legacy log-only records, means UNVERIFIED. Never fall back to HEAD
equality or commit distance for diff evidence, even at zero commits.
Show recorded cycles, completed/converged fields and missing source/phase
coverage. Unknown coverage is not a pass.
- **Plan records** (plan-ceo-review, plan-eng-review, plan-design-review and
codex-plan-review) use the 7-day window, not the working-tree fingerprint.
If \`plan_sha256\` is present, you may compare the plan file and report a mismatch.
Compare the recorded commit with \`---HEAD---\`; if different, run
\`git rev-list --count STORED_COMMIT..HEAD\` and report
"Note: {skill} review from {date} may be stale — {N} commits since review".
A failed command means UNKNOWN, treated as stale; without commit tracking,
suggest a re-run. Omit notes when all reviews are current.
**3. Choose the historical verdict.** CLEARED requires the selected Eng Review
to be \`clean\`, within 7 days and fresh under step 2. Otherwise report NOT CLEARED
and its missing, stale or open-issue reason.${ctx.skillName === 'ship' ? ` With no Eng Review record, say "no saved Eng Review: run the \`PERSIST:\` line a read-only /plan-eng-review printed, or rerun it" (docs/troubleshooting.md#plan-eng-review-not-persisted).` : ''} If \`skip_eng_review\` is true, show
"SKIPPED (global)" for Eng Review and CLEARED for this dashboard.
${ctx.skillName === 'ship' ? 'This verdict never skips Step 9 or its finding, approval and convergence gates. Continue Step 1 even when history is NOT CLEARED.' : 'Eng Review is required by default; `gstack-config set skip_eng_review true` disables that requirement.'}
Other rows provide context, not a substitute for Eng Review:
- Recommend CEO Review for product/business or scope decisions, not routine fixes or cleanup.
- Recommend Design Review for UI/UX work, not backend, infrastructure or prompt-only work.
${ctx.skillName === 'ship' ? '- Step 9\'s review plan decides the adversarial passes (Step 11).\n' : '- Adversarial review always includes a native pass. Available, enabled outside\n challenges supplement it; diffs of 200+ lines also get the structured P1 gate.\n'}- Outside Voice is the default-on plan review after CEO/Eng review. \`codex_reviews\`
disables that extra step. Provider failure uses native fallback and records
missing outside coverage; this dashboard row never gates shipping.
**4. Display the dashboard.** Show missing, stale, disabled or unavailable results
explicitly, never as CLEAR. Display a fresh \`clean\` result as CLEAR and
\`issues_open\` as ISSUES OPEN without changing the stored status.
${ctx.skillName === 'ship' ? `**REVIEW READINESS DASHBOARD**
Use one row for each entry in step 1. Only Eng Review is marked required.
| Review | Runs | Last run | Status | Required |
|---|---:|---|---|---|
| {row and suffix} | {count} | {timestamp or —} | {actual status and reason} | {yes/no} |
VERDICT: {CLEARED or NOT CLEARED} — {reason}` : `\`\`\`
+====================================================================+
| REVIEW READINESS DASHBOARD |
+====================================================================+
| Review | Runs | Last Run | Status | Required |
|-----------------|------|---------------------|-----------|----------|
| Eng Review | 1 | 2026-03-16 15:00 | CLEAR | YES |
| CEO Review | 0 | — | — | no |
| Design Review | 0 | — | — | no |
| Adversarial | 0 | — | — | no |
| Outside Voice | 0 | — | — | no |
+--------------------------------------------------------------------+
| VERDICT: CLEARED — Eng Review passed |
+====================================================================+
\`\`\``}`;
return ctx.skillName === 'plan-eng-review' ? result.replaceAll('\\`', '`') : result;
}
export function generatePlanFileReviewReport(ctx: TemplateContext): string {
const beforeLog = ['plan-ceo-review', 'plan-eng-review', 'plan-design-review', 'plan-devex-review'].includes(ctx.skillName);
const ceo = ctx.skillName === 'plan-ceo-review';
const eng = ctx.skillName === 'plan-eng-review';
const reviewFile = eng ? 'report file' : 'plan file';
const conditionalWrites = ceo || ctx.skillName === 'plan-eng-review';
const storagePolicy = ceo ? 'Step 0 storage policy' : 'Review record and write policy';
const result = `## Plan File Review Report
${beforeLog ? (conditionalWrites ? (eng ? 'After Required outputs are prepared, save the working plan and complete review body with the terminal report below. Apply **Review record and write policy**.' : `Produce the complete accepted plan and review output, including this report, under the ${storagePolicy} before announcing completion.`) : 'Save the accepted plan changes and full review output, including the report below, before logging or announcing completion.') : `After displaying the Review Readiness Dashboard in conversation output, also update the
**plan file** itself so review status is visible to anyone reading the plan.`}
### ${ctx.skillName === 'plan-eng-review' ? 'Use the selected report file' : 'Detect the plan file'}
${ctx.skillName === 'plan-eng-review' ? 'Use the report file already selected under **Review record and write policy**. Do not choose another destination here.' : beforeLog ? `Use an explicitly requested output/report file first. Otherwise use the reviewed plan named by the user, then the host active plan. ${conditionalWrites ? `Apply the ${storagePolicy}. Without a permitted file, produce the complete reviewed plan and report in chat, labeled not persisted; do not skip report generation.` : 'If no file is in scope, skip this section; ordinary no-file review logging still applies.'}` : `1. Check if there is an active plan file in this conversation (the host provides plan file
paths in system messages — look for plan file references in the conversation context).
2. If not found, skip this section silently — not every review runs in plan mode.`}
### Generate the report
${beforeLog ? `Run \`~/.claude/skills/gstack/bin/gstack-review-read\` for prior review entries.
Use the current ${conditionalWrites ? 'Completion Summary' : 'Completion Summary or DX Scorecard'} for this review's status and findings;
apply the Review Log field rules below and add exactly one to its prior run count.
Do not pre-log this run to populate the report.
Use prior entries for other reviews, retaining their status, attribution and freshness.` : `Read the review log output you already have from the Review Readiness Dashboard step above.`}
Parse each JSONL entry using recorded provenance. Historical source "claude" is a native Claude subagent; "claude-code" is the external CLI. Keep historical codex identifiers and never relabel old records from the current harness. Unknown model identity remains unknown. For new records, show host, outside_provider, outside_status, and phase. Only completed external records establish outside coverage; native fallbacks do not.
Each skill logs different fields:
- **plan-ceo-review**: \\\`status\\\`, \\\`unresolved\\\`, \\\`critical_gaps\\\`, \\\`mode\\\`, \\\`scope_proposed\\\`, \\\`scope_accepted\\\`, \\\`scope_deferred\\\`, \\\`commit\\\`
→ Findings: "{scope_proposed} proposals, {scope_accepted} accepted, {scope_deferred} deferred"
→ If scope fields are 0 or missing (HOLD/REDUCTION mode): "mode: {mode}, {critical_gaps} critical gaps"
- **plan-eng-review**: \\\`status\\\`, \\\`unresolved\\\`, \\\`critical_gaps\\\`, \\\`issues_found\\\`, \\\`mode\\\`, \\\`commit\\\`
→ Findings: "{issues_found} issues, {critical_gaps} critical gaps"
- **plan-design-review**: \\\`status\\\`, \\\`initial_score\\\`, \\\`overall_score\\\`, \\\`unresolved\\\`, \\\`decisions_made\\\`, \\\`commit\\\`
→ Findings: "score: {initial_score}/10 → {overall_score}/10, {decisions_made} decisions"
- **plan-devex-review**: \\\`status\\\`, \\\`initial_score\\\`, \\\`overall_score\\\`, \\\`product_type\\\`, \\\`tthw_current\\\`, \\\`tthw_target\\\`, \\\`mode\\\`, \\\`persona\\\`, \\\`competitive_tier\\\`, \\\`unresolved\\\`, \\\`commit\\\`
→ Findings: "score: {initial_score}/10 → {overall_score}/10, TTHW: {tthw_current} → {tthw_target}"
- **devex-review**: \\\`status\\\`, \\\`overall_score\\\`, \\\`product_type\\\`, \\\`tthw_measured\\\`, \\\`dimensions_tested\\\`, \\\`dimensions_inferred\\\`, \\\`boomerang\\\`, \\\`commit\\\`
→ Findings: "score: {overall_score}/10, TTHW: {tthw_measured}, {dimensions_tested} tested/{dimensions_inferred} inferred"
- **codex-review**: \\\`status\\\`, \\\`gate\\\`, \\\`findings\\\`, \\\`findings_fixed\\\`
→ Findings: "{findings} findings, {findings_fixed}/{findings} fixed"
${ceo ? `For **Outside Review**, use this run's completed reviewer output and finding
dispositions: "N findings; R resolved; U unresolved". With no findings, write
"0 findings — completed review". Label native fallback findings as native and
keep external coverage unavailable. For disabled or unavailable attempts, write
the actual reason and "no completed external review"; never imply zero findings.
If prior history lacks counts, say "finding count not recorded". Preserve each
attempt's provider and outcome in OUTSIDE COVERAGE.
` : ''}${beforeLog ? (conditionalWrites ? 'The current row describes this actual review. Mark an unlogged current run as not persisted; do not present it as a saved dashboard entry.' : 'The current row and its later log must describe the same saved review.') : `All fields needed for the Findings column are now present in the JSONL entries.
For the review you just completed, you may use richer details from your own Completion
Summary. For prior reviews, use the JSONL fields directly — they contain all required data.`}
${conditionalWrites ? 'Display `clean` as CLEAR and `issues_open` as ISSUES OPEN, retaining freshness and not-persisted labels. Other statuses keep their recorded meaning.\n\n' : ''}Produce this markdown table:
\\\`\\\`\\\`markdown
## GSTACK REVIEW REPORT
| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| CEO Review | \\\`/plan-ceo-review\\\` | Scope & strategy | {runs} | {status} | {findings} |
| Outside Review | {recorded provider and trigger} | Independent 2nd opinion | {runs} | {outside_status} | {findings} |
| Eng Review | \\\`/plan-eng-review\\\` | Architecture & tests (required) | {runs} | {status} | {findings} |
| Design Review | \\\`/plan-design-review\\\` | UI/UX gaps | {runs} | {status} | {findings} |
| DX Review | \\\`/plan-devex-review\\\` | Developer experience gaps | {runs} | {status} | {findings} |
\\\`\\\`\\\`
Below the table, add these lines. **OUTSIDE COVERAGE** and **CROSS-MODEL** are conditional:
include them when the phase ran, was disabled/skipped/unavailable, or has findings;
omit them only when no such phase applies. **VERDICT** is always present:
- **OUTSIDE COVERAGE:** provider, phase, completion state, and findings. Include unavailable, disabled, and skipped phases; never infer completion from another phase.
- **CROSS-MODEL:** only when native and completed external reviews exist — overlap analysis with recorded providers and known model identity. Do not infer distinct model families from harness names.
- **VERDICT:** list reviews that are CLEAR (e.g., "CEO + ENG CLEARED — ready to implement").
If Eng Review is not CLEAR and not skipped globally, append "eng review required".
${ceo ? `**Unresolved-decisions status (MANDATORY):** This is the report's final content,
after VERDICT. Count this review's open items from its ledger. For prior reviews,
sum \`unresolved\` over the latest fresh row per skill (the dashboard's seven-day
window), excluding the current skill so it is not counted twice.
- If both counts are zero, end with the exact unbolded line \`NO UNRESOLVED DECISIONS\`.
- Otherwise use the bold label \`**UNRESOLVED DECISIONS:**\` (not a new heading),
then one bullet per current open item. When the prior count N is positive, add
a final bullet \`- + N unresolved from prior reviews\`, even if there are no
current items. The last bullet is the final non-whitespace line; append no
separate count line or trailing prose. Never omit this status.
` : `**Unresolved-decisions status (MANDATORY — never omitted; the report's final non-whitespace
line).** After VERDICT, end the report (content under the \\\`## GSTACK REVIEW REPORT\\\`
heading — a bold label, never a new \\\`## \\\` heading; exempt from the "omit when empty"
rule) with exactly one: the exact unbolded line \\\`NO UNRESOLVED DECISIONS\\\` (a bolded one
does NOT count), OR a \\\`**UNRESOLVED DECISIONS:**\\\` header + one bullet per open item
(last bullet = final line; add \\\`+ N unresolved from prior reviews\\\` only when N > 0).
This avoids double-counting: list THIS review's open items from context; for prior reviews
sum \\\`unresolved\\\` over the latest fresh row per skill (dashboard 7-day window) after you
DROP the current skill's row; emit the sentinel only when both are zero.`}
### Write to the ${reviewFile}
${beforeLog ? (conditionalWrites ? `${ceo ? 'If no destination is selected' : 'If the report destination is absent'} or writing is forbidden, assemble the same complete ${eng ? 'working plan' : 'plan'}, review output and terminal report in chat, labeled not persisted. Do not run the file-writing steps below or claim their Read-back gate passed.${ctx.skillName === 'plan-eng-review' ? ' Then use **Report-back outcome**.' : " Follow Stage 3's blocked chat return; no completed-review log or handoff."} Otherwise save only accepted changes, keeping unresolved choices pending:` : '**PLAN MODE EXCEPTION — ALWAYS RUN:** Save the complete reviewed plan/report with only accepted changes applied; keep unresolved choices pending.') : `**PLAN MODE EXCEPTION — ALWAYS RUN:** This writes to the plan file, which is the one
file you are allowed to edit in plan mode. The plan file review report is part of the
plan's living status.`}
The report must always be the LAST section of the ${reviewFile} — never mid-file.
Use a single delete-then-append flow:
${beforeLog ? `1. Read the existing ${eng ? 'report file' : 'plan/report'}, if present. Preserve its content and apply only
accepted changes; include the full review output. Locate any existing
\`## GSTACK REVIEW REPORT\` section.` : `1. Read the plan file (Read tool) to see its full current content. Search the read
output for a \\\`## GSTACK REVIEW REPORT\\\` heading anywhere in the file.`}
2. If found, use the Edit tool to DELETE the entire existing section. Match from
\\\`## GSTACK REVIEW REPORT\\\` through either the next \\\`## \\\` heading or end of
file, whichever comes first. Replace with the empty string. This applies
regardless of where the section currently lives — mid-file deletion is
intentional, not a special case. ${ceo ? 'If the Edit fails, report the error and stop before Review Log or decision logging.' : `If the Edit fails (e.g., concurrent edit\n changed the content), re-read the ${reviewFile} and retry once.`}
${ceo ? `3. Save the complete updated plan and review body with the new
\`## GSTACK REVIEW REPORT\` at EOF:
- If the destination file exists, Read it now, whether or not step 2 deleted
a report. Use Edit with the suffix from this Read, or Write the complete file.
- If the destination file does not exist, use Write to create the complete file.
In both cases, keep the report last and continue to the Read-back gate.` : `3. If a report was deleted, Read the updated file. Append the new
\\\`## GSTACK REVIEW REPORT\\\` at EOF. Use Edit to match the suffix
confirmed by the latest Read, or Write the full file with the report last.${beforeLog ? ' Append whether or not a prior report existed.' : ''}
"Unresolved Decisions" is not an EOF anchor when other sections follow it.`}
${beforeLog ? `4. **Read-back gate:** Read the saved file. Verify the accepted changes, full review
output, current review row, verdict and final unresolved-decisions status, with
\`## GSTACK REVIEW REPORT\` as the last section. If writing or verification fails,
${ctx.skillName === 'plan-eng-review' ? 'report the error and follow **Blocked outcome** before Review Log or decision logging.' : 'report the error and stop before Review Log or decision logging.'}` : `4. Verify with the Read tool that \\\`## GSTACK REVIEW REPORT\\\` is the last
\\\`## \\\` heading in the file before continuing. If it isn't, repeat steps
2-3 once.`}
${ceo || ctx.skillName === 'plan-eng-review' ? 'Do NOT replace the section in place; delete it and append the new report at EOF.' : `Do NOT replace the section in place; delete it and append the new report at EOF,
so the review report is always the plan's last section.`}`;
return conditionalWrites ? result.replaceAll('\\`', '`') : result;
}