Files
gstack/scripts/resolvers/review-army.ts
Garry Tanandcapy-ai[bot] 8966f3620c v1.91.350.0 feat(review): GSTA-167 variant G reviewer stack: blocking floor in both voices, one batched specialist, data-file classifier (#3660)
* feat(review): variant G part 4: data files no code names are docs in classifyDiff, untested state-matrix cells map below the floor per cell (GSTA-167 PR-a)

Part 4a: lib/review-depth.ts owns DATA_PATH (JSON, JSONL, NDJSON, CSV, TSV, raster
images; YAML, TOML and SVG stay code) and applies the data rule only in classifyDiff,
to candidates lib/review-collect.ts finds no non-docs consumer for (git grep of
basenames at head, merge base and, live, the working tree; data-to-data chains four
deep; over 2,000 candidates keeps all code; a grep failure fails closed). classifyPath
and every path-only caller are unchanged; the receipt gate scan decides through
gateScanNeeded (path-only), so it cannot be skipped. gstack-ship-preflight computes
RUNTIME_LINES with classifyDiff over gstack-review-plan --json (now with consumed).

Part 4b: lib/review-scope.ts mapStateMatrixOutput maps an "untested cell" finding to
INFORMATIONAL when its cell has test null in the same output's matrix and it is the
only finding on that cell; gstack-state-matrix findings prints it for Step 9.2 before
the merge. state-machines.md is byte-identical; check still reads the raw output.

Reviewer-brief pins repinned under no-loss replay PR-a (Arm 4: plan identity at the 14
cohort heads; free exact re-score of saved C, D and A' findings).

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

* feat(review): variant G parts 1 and 3: blocking floor in both prose voices, specialists at medium (GSTA-167)

Part 1: the native adversarial and outside-voice prompts end with the blocking
floor ("would fail a test, break production, or lose data or secrets") and ask
each finding for Severity and Blocks: yes|no. Additive rule: a finding is
CRITICAL when its label rule says so or it says Blocks: yes; Blocks: no never
lowers one (proseVoiceSeverity, BLOCKING_FLOOR in lib/review-scope.ts).
Part 3: ROLE_EFFORT puts specialists at medium; Red Team, native adversarial
and fix-check stay full; light's batched specialist (carries the Red Team
prompt) stays full; --no-tiers sends specialists to host.

Repinned reviewer briefs with no-loss replay G1 (live Arm G1, not yet run):
ship/sections/review-army.md, ship/sections/adversarial.md,
review/sections/adversarial.md, lib/review-depth.ts. Merge only on G1's pass.
Review-skill parity ratio 1.3136 -> 1.3180 and the gstack-review size pins
+537 for the approved +0.3k tokens per voice.

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

* feat(review): variant G part 2: lean ensemble, one batched specialist at every depth (GSTA-167)

planReview dispatches one 'specialist batched' at standard and full depth (and
light, which adds the Red Team checklist): Testing and Security always on code
and gate class, plus State-machines, Data-migration, API-contract and Design
when scope asks; a forced checklist always joins. Maintainability, Performance
and Simplification are read only when forced (SKIP names them). LIGHT_FIXED is
[testing, security]; BATCH_SCOPED joins DEPTH_RULES so old receipts re-plan.
The Red Team rule and prompt are unchanged; it reads the batched reader's
merged findings. specialistsFor stays as the legacy selection the dispatch
compare script models.

Repinned reviewer briefs with no-loss replay L (live Arm L, scored L∘G1 vs G1;
not yet run): ship/sections/review-army.md, lib/review-depth.ts. Merge only on
L's pass.

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

* feat(review): variant G on #3648's dispatch: Blocks: yes collects as CRITICAL, batched checklists by name (GSTA-167)

Integrates parts 1 and 2 with gstack-review-dispatch (#3648):
- lib/review-ledger.ts parseFindings: a prose voice's "Blocks: yes" makes its
  own line, or the finding on the line just before it, CRITICAL in
  merged.jsonl (normalizer v2, certified by round 5's G1), so judge refuses a
  lead that keeps it below the floor; with no finding before it the Blocks
  line is its own CRITICAL finding. "Blocks: no" never lowers a label.
- lib/review-brief.ts: the outside-voice brief no longer appends "Give each
  finding a severity"; the section's prompt now asks for Severity and Blocks,
  so the brief is the certified section prompt plus the branch diff.
- bin/gstack-review-dispatch render --checklists takes the plan's
  checklists= names (review/specialists/<name>.md) as well as paths and
  refuses an unknown one, since the batched reader is now every depth's
  specialist dispatch.

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

* chore(changelog): GSTA-167 variant G reviewer stack fragment

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

* fix(review): consumer scan survives grep.lineNumber/grep.column; collect reads separate-line Severity/Blocks

/ship pass 1 (PR #3660): the Red Team CRITICAL at lib/review-collect.ts:120 and the
informational findings in the same batch. proseVoiceSeverity had no caller and is
removed; the normalizer v2 table now runs through parseFindings, the path collect uses.

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

* fix(review): collect keeps every prose blocker and one id per finding

/ship pass 2 (PR #3660): an unlabeled line carrying an inline Blocks: yes collects as
CRITICAL again; Severity/Blocks continuation needs a colon, stops at a list item, heading,
bold paragraph opener or a repeated field, and never swallows a top-level bullet or its
section label; the native voice's FIXABLE collects as CRITICAL under its role with or
without a dispatch name; the consumer scan adds --no-color.

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

* fix(review): a later finding's fields never land on the one before it; explicit Severity wins its line

/ship pass 2 Red Team (PR #3660): prose after a finding's Severity/Blocks field, a
post-blank paragraph or a line in an indented block starts the next finding, and a
line's explicit Severity: label decides it over other label words on the line.

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

* fix(review): FIXABLE outranks a Severity label on its line; a blocker keeps its own id and subject

/ship fix-check (PR #3660): labels are named before the strongest is taken, so a one-line
native FIXABLE ... Severity: Medium collects as CRITICAL; a later finding starts at a
file:line after the open finding's fields, field bullets stay on their finding, a field
line may omit the colon, and continuation prose never replaces a list item's subject.

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

* docs(changelog): GSTA-167 fragment adds round 6 and the collect and consumer-scan fixes

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-31 05:06:03 +00:00

354 lines
21 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Review Army resolver — parallel specialist reviewers for /review
*
* Generates template prose that instructs Claude to:
* 1. Detect stack and scope (via gstack-diff-scope)
* 2. Select and dispatch specialist subagents in parallel
* 3. Collect, parse, merge, and deduplicate JSON findings
* 4. Feed merged findings into the existing Fix-First pipeline
*
* Shipped as Release 2 of the self-learning roadmap (SELF_LEARNING_V0.md).
*/
import type { TemplateContext } from './types';
import { CC_BACKGROUND_DEFAULT_SINCE, FOREGROUND_IF_AVAILABLE, BACKGROUND_RECOVERY } from './constants';
import { learningsCapture, LEARNINGS_VERDICT } from './learnings';
import { DEFENSIVE_REVIEW_FRAMING, FIXTURE_SUMMARY_MODE } from './defensive-review';
function generateSpecialistSelection(ctx: TemplateContext): string {
const isShip = ctx.skillName === 'ship';
const stepSel = isShip ? '9.1' : '4.5';
const stepMerge = isShip ? '9.2' : '4.6';
const nextStep = isShip ? 'Step 9.3 (cross-review dedup)' : 'Step 4.8 (adversarial review), then Step 5';
return `## Step ${stepSel}: Review Army — Specialist Dispatch
### Detect stack and scope
\`\`\`bash
_SM=$(${ctx.paths.binDir}/gstack-state-matrix detect --base <base>) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM")
source <(${ctx.paths.binDir}/gstack-diff-scope <base> 2>/dev/null) || true
set | grep '^SCOPE_'
STACK=""
[ -f Gemfile ] && STACK="\${STACK}ruby "
[ -f package.json ] && STACK="\${STACK}node "
[ -f requirements.txt ] || [ -f pyproject.toml ] && STACK="\${STACK}python "
[ -f go.mod ] && STACK="\${STACK}go "
[ -f Cargo.toml ] && STACK="\${STACK}rust "
[ -f pubspec.yaml ] && STACK="\${STACK}dart "
echo "STACK: \${STACK:-unknown}"
DIFF_BASE=$(git merge-base origin/<base> HEAD)
DIFF_LINES=$(git diff "$DIFF_BASE" --numstat | awk '{n+=$1+$2} END {print n+0}')
echo "DIFF_LINES: $DIFF_LINES"
# Detect test framework
TEST_FW=""
{ [ -f jest.config.ts ] || [ -f jest.config.js ]; } && TEST_FW="jest"
[ -f vitest.config.ts ] && TEST_FW="vitest"
{ [ -f spec/spec_helper.rb ] || [ -f .rspec ]; } && TEST_FW="rspec"
{ [ -f pytest.ini ] || [ -f conftest.py ]; } && TEST_FW="pytest"
[ -f go.mod ] && TEST_FW="go-test"
[ -f pubspec.yaml ] && TEST_FW="dart-test" && grep -qsE '^ +sdk: *.?flutter' pubspec.yaml && TEST_FW="flutter-test"
echo "TEST_FW: \${TEST_FW:-unknown}"
\`\`\`
### Read specialist hit rates (adaptive gating)
\`\`\`bash
${ctx.paths.binDir}/gstack-specialist-stats 2>/dev/null || true
\`\`\`
### Select specialists
Select specialists from the scope signals above.
**Always-on (every review with 50+ changed lines):**
1. **Testing** — read \`${ctx.paths.skillRoot}/review/specialists/testing.md\`
2. **Maintainability** — read \`${ctx.paths.skillRoot}/review/specialists/maintainability.md\`
**If DIFF_LINES < 50:** Skip all specialists except State Machines (item 9 decides it). Print: "Small diff ($DIFF_LINES lines) — specialists skipped." Continue to Step ${stepMerge} with the ${isShip ? 'core/design-lite' : 'core'} findings and an empty or State Machines-only specialist list, then the parent's Exploratory QA step and ${nextStep}. Small diffs skip fan-out, never the parent-owned smoke probes or core shared-code checks.
**Conditional (dispatch when its scope signal is true):**
3. **Security** — if SCOPE_AUTH=true, OR if SCOPE_BACKEND=true AND DIFF_LINES > 100. Read \`${ctx.paths.skillRoot}/review/specialists/security.md\`
4. **Performance** — if SCOPE_BACKEND=true OR SCOPE_FRONTEND=true. Read \`${ctx.paths.skillRoot}/review/specialists/performance.md\`
5. **Data Migration** — if SCOPE_MIGRATIONS=true. Read \`${ctx.paths.skillRoot}/review/specialists/data-migration.md\`
6. **API Contract** — if SCOPE_API=true. Read \`${ctx.paths.skillRoot}/review/specialists/api-contract.md\`
7. **Design** — if SCOPE_FRONTEND=true. Use the existing design review checklist at \`${ctx.paths.skillRoot}/review/design-checklist.md\` and run the mechanical pass at the top of that checklist (the user-installed design detector, when present) before the LLM items
8. **Simplification** — if DIFF_LINES > 100. Read \`${ctx.paths.skillRoot}/review/specialists/simplification.md\`. Advisory-only lens: hunts unrequested structure (hand-rolled stdlib, one-implementation abstractions, dependencies duplicating platform features), never coverage.
9. **State Machines** — if SCOPE_STATE_MACHINE is true or unknown: read \`${ctx.paths.skillRoot}/review/specialists/state-machines.md\`, run \`${ctx.paths.binDir}/gstack-state-matrix outfile\`, Read the file it prints, write the full output there, then run \`${ctx.paths.binDir}/gstack-state-matrix check <file>\` on it.
### Adaptive gating
After scope-based selection, apply adaptive gating based on specialist hit rates:
For each conditional specialist that passed scope gating, read the \`gstack-specialist-stats\` output:
- If tagged \`[GATE_CANDIDATE]\` (0 findings in 10+ dispatches): skip it. Print: "[specialist] auto-gated (0 findings in N reviews)."
- If tagged \`[NEVER_GATE]\`: always dispatch regardless of hit rate.
**Force flags:** \`--<specialist>\` (e.g. \`--simplification\`) or \`--all-specialists\` in the prompt force-includes it.
Print the selection:
"Dispatching N specialists: [names]. Skipped: [names] (scope not detected). Gated: [names] (0 findings in N+ reviews)."`;
}
/**
* The diff command every dispatched reviewer runs. In /ship the parent writes the
* base sha into the prompt (the merge base on pass 1, RANGE_BASE from pass 2 on):
* a reviewer is its own process and never sees the parent's shell variables, so a
* `$RANGE_BASE` reference there would silently fall back to the whole branch.
*/
function diffBase(ctx: TemplateContext): string {
return ctx.skillName === 'ship' ? 'DIFF_BASE=<diff-base>' : 'DIFF_BASE=$(git merge-base origin/<base> HEAD)';
}
function generateSpecialistDispatch(ctx: TemplateContext): string {
return `### Dispatch specialists in parallel
For each selected specialist, launch an independent subagent via the Agent tool.
**Launch ALL selected specialists in a single message** (multiple Agent tool calls)
so they run in parallel. Each subagent has fresh context — no prior review bias.
${ctx.skillName === 'ship' ? `
**Fan-out (\`--review-fanout auto|serial|parallel\` in the prompt; default \`auto\`).**
Under \`auto\`, when the host reports fewer free subagent slots than selected
specialists (Capy caps a thread at 8 running subagents, and waiting or idle ones hold
a slot), dispatch in batches of the free-slot count, at least one per batch, each
batch in one message, and start the next batch only after every reader of the
previous one has finished or is confirmed stopped; print \`Fan-out: N specialists in
M batches of K\`. A host that reports no slot count runs one parallel batch.
\`serial\` is batches of one; \`parallel\` is one batch regardless of slots (a refused
launch is a failed reader, never silently dropped coverage). Batching changes the
order, never the set: every selected specialist still runs before Step 9.2 merges.
` : ''}
**Each specialist subagent prompt:**
Each specialist prompt includes:
1. The specialist's checklist path from the selection above (the subagent reads it; never paste its content)
2. Stack context: "This is a {STACK} project."
3. Past learnings for this domain (if any exist):
\`\`\`bash
${learningsCapture(`${ctx.paths.binDir}/gstack-learnings-search --type pitfall --query "{specialist domain}" --limit 5`)}
${LEARNINGS_VERDICT}
\`\`\`
If learnings are found, include them: "Past learnings for this domain: {learnings}"
4. **Defensive framing (Security specialist and the Red Team below only).** Begin the prompt with:
"${DEFENSIVE_REVIEW_FRAMING}"
and replace its full-diff command with: "${FIXTURE_SUMMARY_MODE}"
Every other specialist, including Testing, reads the full diff with fixtures.
5. Instructions:
"You are a specialist code reviewer. Read the checklist at {checklist path}, then run
\`${diffBase(ctx)} && git diff "$DIFF_BASE"${ctx.skillName === 'ship' ? ' <exclude>' : ''}\` to get the ${ctx.skillName === 'ship' ? 'diff (`<diff-base>`: write the sha in before dispatch, the merge base on pass 1 and `RANGE_BASE` from pass 2 on; never a shell variable; `<exclude>`: everything after `<diff-base>` on this reviewer\'s `DIFF_CMD:` line, verbatim; empty on `DIFF_EXCLUDE: none`)' : 'full diff'}. Apply the checklist against the diff.
\`NO FINDINGS\` alone is the expected, complete result for a clean diff; never invent an item. Otherwise output one JSON object per finding per line, 400 words at most:
{\\"severity\\":\\"CRITICAL|INFORMATIONAL\\",\\"confidence\\":N,\\"path\\":\\"file\\",\\"line\\":N,\\"category\\":\\"category\\",\\"summary\\":\\"description\\",\\"fix\\":\\"recommended fix\\",\\"fingerprint\\":\\"path:line:category\\",\\"specialist\\":\\"name\\"}
Required fields: severity, confidence, path, category, summary, specialist.
Optional: line, fix, fingerprint, evidence, test_stub, advisory, evidence_paths, helper_target, cause.
CRITICAL means a test that would fail, a production break, or a security or data-loss defect; everything else is INFORMATIONAL.
A defect the diff causes but that shows elsewhere (a caller broken by a changed signature, a consumer of a removed export) keeps the victim as \`path\` and names the changed line in \`cause\`: {\\"cause\\":{\\"path\\":\\"file\\",\\"line\\":N}}. Without it the finding reads as pre-existing code.
Leave extraction advice to the core shared-code check; report real defects in duplicated code independently, keep advisory metadata on structural advice, and never call a demonstrated defect advisory because a shared helper could fix it.
If you can write a test that would catch this issue, include it in the \`test_stub\` field.
Use the detected test framework ({TEST_FW}). Write a minimal skeleton — describe/it/test
blocks with clear intent. Skip test_stub for architectural or design-only findings.
Output nothing else: no preamble, no summary or commentary.
Stack context: {STACK}
Past learnings: {learnings or 'none'}"
**Subagent configuration:**
- Use \`subagent_type: "general-purpose"\`
- Pass ${FOREGROUND_IF_AVAILABLE} on every specialist Agent call — background is the default since ${CC_BACKGROUND_DEFAULT_SINCE}; omitting an available flag is not foreground. ${BACKGROUND_RECOVERY}
**Wait for readers before editing:**
- Confirm that each task has finished or is stopped. A timeout alone does not prove termination. If a reader or writer is still active, wait; if its state is unknown, inspect its task/process status. If you cannot confirm it stopped, use the parent's Fix-First stop path without edits.
- A failed task may be stopped without having completed its review. Record the failure and retain usable partial findings.
- Continue independent evidence collection after a terminal failure. Missing dispatched coverage remains incomplete, never completed or clean; successful peers cannot replace it.`;
}
function generateFindingsMerge(ctx: TemplateContext): string {
const isShip = ctx.skillName === 'ship';
const stepMerge = isShip ? '9.2' : '4.6';
const fixFirstRef = isShip ? 'Step 9.3 dedup, then Step 9.4 Fix-First' : 'Step 5 Fix-First';
const critPassRef = isShip ? 'the checklist pass (Step 9)' : 'the CRITICAL pass findings from Step 4';
const persistRef = isShip ? 'the review-log persist' : 'the review-log entry in Step 5.8';
return `### Step ${stepMerge}: Collect and merge findings
Follow these stages in order. Validate core and specialist findings alike, but keep
their source labels: specialist scoring is not the final review's defect count.
#### 1. Parse outputs
After specialist attempts settle, collect their outputs, tagged by actual source.
\`NO FINDINGS\` is a completed, clean result, never a failed reviewer. Otherwise parse
each JSON line and skip invalid lines. Missing or unusable output is incomplete
coverage, not an empty success. Keep each specialist's returned findings for activity stats.${isShip ? `
An output that carries a state-machine matrix record (State Machines, or a batched
reader that read its checklist) is parsed from \`${ctx.paths.binDir}/gstack-state-matrix findings <file>\`,
after \`check\` ran on the raw file: an untested cell that is the only finding on its
cell comes back INFORMATIONAL (\`"state_matrix":"untested-cell"\`), the test-gap ruling
in \`docs/review-policy.md\`; a wrong row or collapsed kinds stays CRITICAL.` : ''}
#### 2. Validate severity
For core and specialist findings with \`"severity":"CRITICAL"\` and \`"advisory":true\`,
remove \`advisory\` and retain its \`CRITICAL\` severity. Treat these as defects before
identity, merging, counting, scoring or Fix-First. Never downgrade severity to make
advisory metadata consistent. Valid INFORMATIONAL advisories remain advisory in
every category, including simplification.
#### 3. Identify and merge
Partition defects and advisories BEFORE grouping by fingerprint. Never merge a
defect with advice, even on a supplied-hash collision. Neither higher-confidence
advice nor a prior skipped extraction may replace, downgrade or suppress a defect.
Compute identities for both core and specialist findings:
- Shared-code advice (category \`shared-libs\` or fingerprint prefix \`shared-libs:\`):
call installed \`sharedLibsFingerprint\` from \`${ctx.paths.skillRoot}/lib/review-evidence.ts\`
with \`evidence_paths\` and \`helper_target\` as literal JSON on stdin, as in the core pass;
never trust a supplied hash or generate one yourself. Missing/malformed metadata
cannot deduplicate or reuse a saved decision.
- Other findings: use supplied \`fingerprint\`, else \`{path}:{line}:{category}\`
or \`{path}:{category}\` when no line exists.
Within the specialist list, merge matching identities in the same partition: keep
the highest confidence and all source names. Confirmation by distinct specialists adds +1 (cap at 10) and \`MULTI-SPECIALIST CONFIRMED ({specialist1} + {specialist2})\`.
Core findings never earn a specialist confidence boost. Preserve \`advisory\`,
\`evidence_paths\` and \`helper_target\` through every merge.
#### 4. Lead judgment
Reviewers fill the space they are given. Before a merged finding counts, the
parent reads it and drops or downgrades it on these grounds, saying so:
- **Nit gravity.** A reviewer whose whole return is style, naming and preference
found nothing; say so and count no defect.
- **Hypothetical, not actual.** A defect names the call site or input that reaches
it; one that does not is a note until someone traces it.
- **Structure for a case that does not exist.** Advice to abstract, generalize or
guard a path nothing takes is advisory.
- **A different design is not a defect.** "I would have done it differently" with
no failing case is dropped.
- **Missing context.** A finding that assumes a rule the repository documents
otherwise is dropped with the pointer.
- **On record.** A class \`docs/review-policy.md\` rules below the floor is a note,
named by its ruling.
Independent agreement between reviewers is the strongest signal (the
\`MULTI-SPECIALIST CONFIRMED\` boost records it). A reviewer that
returned \`NO FINDINGS\` was right until a traced defect says otherwise.
#### 5. Apply specialist confidence gates
- Confidence 7+: show normally in the findings output
- Confidence 5-6: show with caveat "Medium confidence — verify this is actually an issue"
- Confidence 3-4: move to appendix (suppress from main findings)
- Confidence 1-2: suppress entirely
Core findings keep the core Confidence Calibration gates.
#### 6. Score and present specialists
Only specialist findings enter this header and \`quality_score\`; core findings do not.
Use the merged NON-advisory specialist findings for both counts and score;
the header's N is X + Y, so advisory findings never add to it:
\`quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5))\`
Cap at 10 and retain for ${persistRef}. These are not final unresolved-defect totals.
Print only this block: the stage 7 activity object and \`test_stub\` bodies are log and Fix-First data.
Validated \`"advisory": true\` findings from any source are excluded from score,
header, unresolved-defect totals and clean-status blockers. Show them separately;
they remain ASK-only, never auto-applied. Real defects follow normal Fix-First.
\`\`\`
SPECIALIST REVIEW: N findings (X critical, Y informational) from Z specialists
[For each finding, in order: CRITICAL first, then INFORMATIONAL, sorted by confidence descending;
advisory findings last, each rendered with an [ADVISORY] label in place of the severity]
[SEVERITY] (confidence: N/10, specialist: name) path:line — summary
Fix: recommended fix
[If MULTI-SPECIALIST CONFIRMED: show confirmation note]
PR Quality Score: X/10
\`\`\`
**Simplification footer (after the score line):**
- If the simplification specialist was dispatched and returned findings, sum
their \`lines_removable\` values and print: \`net: -N lines possible\` (omit
findings without the field from the sum).
- If it was dispatched and returned NO FINDINGS, print:
\`Simplification: lean already — nothing to cut.\`
- If it was not dispatched, print neither line.
Core shared-code savings never enter this footer; explain any overlap once in the core proposal.
#### 7. Save specialist activity
Compile a \`specialists\` object for ${persistRef}.
${isShip ? 'For each specialist' : 'For DIFF_LINES < 50, keep `specialists: {}` (or `state-machines` alone when it dispatched); do not manufacture per-specialist scope records. Otherwise record each considered specialist'} (testing, maintainability, security, performance, data-migration, api-contract, design, simplification, state-machines, red-team):
- If dispatched: \`{"dispatched": true, "findings": N, "critical": N, "informational": N}\`
- If skipped by scope: \`{"dispatched": false, "reason": "scope"}\`
- If skipped by gating: \`{"dispatched": false, "reason": "gated"}\`
- If not applicable (e.g., red-team not activated): omit from the object
Count only findings that specialist actually returned, before deduplication.
Advisory findings COUNT in the stats \`findings\` field, not its defect counts.
Include Design despite its different checklist. Preserve dispatch/failure status:
zero returned findings from a failed attempt is not a clean review.
#### 8. Hand off to Fix-First
Send these findings to ${fixFirstRef} alongside ${critPassRef}.
Consolidate equivalent shared-code advice under the core proposal, retaining all
sources and counting overlapping savings once. Keep actual specialist stats;
core-only advice must not create a specialist dispatch or finding.
Normal AUTO-FIX/ASK rules apply, with advice ASK-only. Missing coverage still blocks
completion. Advice never permits edits while readers are active or replaces a required review.`;
}
function generateRedTeam(ctx: TemplateContext): string {
const isShip = ctx.skillName === 'ship';
const stepMerge = isShip ? '9.2' : '4.6';
const fixFirstRef = isShip ? 'Step 9.3 dedup, then Step 9.4 Fix-First' : 'Step 5 Fix-First';
return `### Red Team dispatch (conditional)
**Activation:** Only if DIFF_LINES > 200 OR any specialist produced a CRITICAL finding.
If activated, dispatch one more subagent via the Agent tool (pass ${FOREGROUND_IF_AVAILABLE} — foreground; subagents default to background since ${CC_BACKGROUND_DEFAULT_SINCE}; ${BACKGROUND_RECOVERY})
The Red Team subagent receives:
1. The red-team checklist path \`${ctx.paths.skillRoot}/review/specialists/red-team.md\` (it reads the file)
2. The merged specialist findings from Step ${stepMerge}, one line each (so it knows what was already caught)
3. The git diff command
Prompt, after the defensive framing and fixture handling from item 4 of the specialist dispatch: "You are a red team reviewer. The code has already been reviewed by N specialists
who found the following issues: {merged findings summary}. Your job is to find what they
MISSED. Read the checklist at {red-team checklist path}, list changed files with \`${diffBase(ctx)} && git diff --name-status "$DIFF_BASE"\`, read them as that fixture handling says, and look for gaps.
Output findings as JSON objects (same schema as the specialists), or \`NO FINDINGS\` alone, the expected result when they missed nothing. Focus on cross-cutting
concerns, integration boundary issues, and failure modes that specialist checklists
don't cover."
Tag Red Team findings \`"specialist":"red-team"\`.
Add them to the original specialist outputs and rerun stages 1–8 of Step ${stepMerge}
before ${fixFirstRef}; do not boost or count the earlier findings twice.
If the Red Team returns NO FINDINGS, note: "Red Team review: no additional issues found."
If the Red Team fails or times out, confirm it stopped and record its review as incomplete, just as for other specialists. ${isShip ? "Return to the parent's Exploratory QA step, then dedup and persistence; Step 9.4 cannot certify missing dispatched coverage as completed or clean." : 'Continue independent Step 4.7 QA and Step 4.8 adversarial review; Step 5.8 cannot certify missing dispatched coverage as completed or clean.'}`;
}
export function generateReviewArmy(ctx: TemplateContext): string {
// Codex host: strip entirely — Codex should not run Review Army
if (ctx.host === 'codex') return '';
const sections = [
generateSpecialistSelection(ctx),
generateSpecialistDispatch(ctx),
generateFindingsMerge(ctx),
generateRedTeam(ctx),
];
return sections.join('\n\n---\n\n');
}