Merge origin/main into garrytan/gsta92-phase2-review (after #3481 landed)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 20:50:36 +00:00
commit 3b6642984f
131 files changed
+7843 -337

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@
"files": {
"scripts/lib/paid-cases.ts": {"blob":"f862cf5241580c7593b9ee62eacf5ef046248d26","since":6,"nonBehavioral":"Claude-host verdicts are unchanged: the Codex host (EVALS_HOST=codex, codex-host-runner) runs only when selected and records its own per-model series; the Codex runner refactor (execCodex extraction, onSpawn hook) keeps its events and verdicts and adds token cost accounting; host fields on the plan/report are absent for claude"},
"scripts/lib/paid-plan.ts": {"blob":"e8ce3cb98474dded38c5128a631d283b0ca35b63","since":6,"nonBehavioral":"F1 rule-16 flaky rerun: report aggregation, rerun planning and the gate slice exit code; no case's execution, trial outcome, trial record or panel verdict changes, so no pass-rate series resets"},
"scripts/lib/paid-report.ts": {"blob":"19a87cb02bb3cd0f57da7719a5c30b7a4df81b2b","since":6,"nonBehavioral":"credential rejections are classed infra instead of assertion: no case's pass or fail changes; only failed trials whose provider rejected the run's key move class, so no rerun spends on a rejected key"},
"scripts/lib/paid-report.ts": {"blob":"224dc5e8ba23a3589a37bad94db72ed1ca3c7c77","since":6,"nonBehavioral":"a case shard's result records are attributed to its one case: the trial's cost_usd and cost_known come from them, and a failed trial's failure_class, exit_reason and error come from its record instead of a bare assertion with the JUnit message, so a red whose record says infra or timeout is classed by that record (INFRA instead of VERDICT: rerun and bisect eligibility, not a pass rate); no case's outcome or selection changes and pass rates count outcomes only, so no pass-rate series resets"},
"scripts/lib/published-text.ts": {"blob":"3f2e4d616b5dd0204035710c66b64d49055c5714","since":6},
"scripts/lib/shard-engine.ts": {"blob":"dc714d7d897aed185c0ec3d2c92ad22d954039e5","since":6,"nonBehavioral":"createShardSandbox drops the Capy launch markers (GSTACK_SESSION_KIND, GSTACK_CAPY, GSTACK_EPHEMERAL, GSTACK_INSTALL_VERDICT) from shard environments; CI never sets them, so no case's input or verdict changes there, and on a Capy machine fixtures now see the session kind they set themselves instead of the installer's"},
"scripts/test-paid-shards.ts": {"blob":"5439d0a8f6e1569fa7c924af20d67c30c81d5d30","since":6,"nonBehavioral":"credential rejections are classed infra instead of assertion: no case's pass or fail changes; only failed trials whose provider rejected the run's key move class, so no rerun spends on a rejected key"},
+3 -1
View File
@@ -275,7 +275,9 @@ export function junitCensus(manifest: PaidRunManifest, artifacts: ReportArtifact
const kind = (E2E_KINDS[id] ?? 'rule') as EvalCaseKind;
const failed = cases.find(tc => tc.outcome === 'failed');
const caseOutcome: TrialOutcome = failed ? 'failed' : cases.some(tc => tc.outcome === 'passed') ? 'passed' : 'skipped';
const mine = records.filter((r: any) => r?.name === id || r?.case_id === id);
// A record is named by its case id or carries case_id; an E2E record named by its display label ("/qa fix loop") is
// the shard's only case's when the shard ran one case, else it stays unattributed and the trial's cost unknown.
const mine = records.filter((r: any) => r?.name === id || r?.case_id === id || (byCase.size === 1 && !r?.case_id));
const failedRecord = mine.find((r: any) => r.passed === false);
const recorded: TrialFailureClass | undefined = !failed ? undefined
: failed.failureType === 'TimeoutError' ? 'timeout' : failedRecord ? failureClassOf(failedRecord) : 'assertion';
+8
View File
@@ -66,6 +66,10 @@ export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }>
{ file: 'test/pregate-invalidation.test.ts', reason: 'expects the Linux-host lanes verdict (the windows lane as the requires-remote obligation); on Windows the Linux lane is the obligation instead; gstack-pregate itself launches through lib/script-invocation.ts' },
{ file: 'test/pregate-selection.test.ts', reason: 'its gstack-pregate (bin) cases expect the Linux-host lanes verdict (a local lane run); on Windows the fixture\'s Linux lane is a requires-remote obligation' },
{ file: 'test/pregate-stamp-only.test.ts', reason: 'its gstack-pregate (bin) cases expect the Linux-host lanes verdict for the mapped-file and test-reads-manifest fixtures, and its mode-change case needs a POSIX exec bit and symlink; the stamp rule it drives is POSIX-run like the other pregate fixtures' },
{ file: 'test/pregate-starter.test.ts', reason: 'expects the Linux-host lanes verdict (the fixture\'s Linux lane runs locally and the no-policy row is warn); on Windows that lane is a requires-remote obligation; gstack-pregate itself launches through lib/script-invocation.ts' },
{ file: 'test/pregate-allowlist.test.ts', reason: 'spawns bin/gstack-pregate through test/helpers/pregate-fixture.ts runBin and gstack-redact through the bin shebang; the allowlist rules it drives are POSIX-run like the other pregate fixtures' },
{ file: 'test/pregate-environment.test.ts', reason: 'drives exit-127 and spawn-failure classification through a POSIX shell (bash\'s command-not-found exit) and expects the Linux-host lanes verdict; on Windows the fixture\'s Linux lane is a requires-remote obligation' },
{ file: 'test/pregate-scratch.test.ts', reason: 'asserts the 0700 mode of the per-run scratch directory and the Linux-host lanes verdict (lane and guard logs written locally); Windows fakes mode bits and makes the Linux lane a requires-remote obligation' },
{ file: 'test/pregate-bats-nested.test.ts', reason: 'expects the bats lane to run locally, which it does on a Linux host; on Windows the fixture\'s Linux lane is a requires-remote obligation' },
{ file: 'test/pregate-docs-lane-review.test.ts', reason: 'spawns bin/gstack-pregate through test/helpers/pregate-fixture.ts runBin (shebang via CreateProcess); the docs-lane selection rules it drives are POSIX-run like the other pregate fixtures' },
{ file: 'test/pregate-dart.test.ts', reason: 'its dart, flutter and fvm stand-ins are bash scripts on a colon-joined PATH, which Bun.which and CreateProcess cannot use on Windows, and its containment case needs a POSIX symlink; the Dart lane code it drives is POSIX-run like the other pregate fixtures' },
@@ -287,6 +291,10 @@ export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }>
file: 'test/ship-queue-train.test.ts',
reason: 'drives bin/gstack-ship-queue against the stateful #!/bin/sh gh shim in test/helpers/ship-queue-train-fixture.ts; on windows-latest spawnSync(\'gh\') resolves the runner\'s gh.exe instead of the shim (windows-free-shard run 39152702056)',
},
{
file: 'test/ship-queue-train-cost.test.ts',
reason: 'drives bin/gstack-ship-queue train verdict against the same #!/bin/sh gh shim in test/helpers/ship-queue-train-fixture.ts; on windows-latest spawnSync(\'gh\') resolves the runner\'s gh.exe instead of the shim (windows-free-shard 2 on #3478 at d5e6a7d)',
},
{
file: 'test/ship-policy-compat.test.ts',
reason: 'spawns bin/gstack-ship-policy, bin/gstack-restamp and bin/gstack-tree-receipt (shebangs) through test/helpers/restamp-fixture.ts',
+1 -1
View File
@@ -38,7 +38,7 @@ export const AUTOPLAN_DRIVER_COMMANDS: Array<{ cmd: string; does: string }> = [
/** The reply grammar `gstack-gate` parses, one form per line. */
export const GATE_REPLY_FORMS: Array<{ reply: string; means: string }> = [
{ reply: 'rec', means: 'every item takes its recommendation, plan changes included (each prints APPLY:); `all` is an alias' },
{ reply: 'rec', means: 'every item takes its recommendation, plan changes included (each prints APPLY:); `all` and `do it` are aliases' },
{ reply: 'as-is', means: 'keep every direction; no plan change' },
{ reply: 'd3b uc1a', means: 'one <id><option> token per item you set; unanswered auto items take their default' },
{ reply: 'rec except d3b', means: 'recommendations, with the listed overrides' },
+4 -2
View File
@@ -131,7 +131,7 @@ Dispatch via the Agent tool with ${FOREGROUND_IF_AVAILABLE} (background is the d
Subagent prompt:
"${DEFENSIVE_REVIEW_FRAMING}
Read the diff for this branch. First list changed files: \`DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff --name-status "$DIFF_BASE"\`. ${FIXTURE_SUMMARY_MODE}
Read the diff for this branch. First list changed files: \`DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff --name-status "$DIFF_BASE"\`. ${FIXTURE_SUMMARY_MODE}${isShip ? ' From Step 9\'s pass 2 on, \`DIFF_BASE=$RANGE_BASE\` applies here too; \`NO FINDINGS\` is the expected result for a clean range.' : ''}
Think like an attacker and a chaos engineer. Your job is to find ways this code will fail in production. Look for: edge cases, race conditions, security holes, resource leaks, failure modes, silent data corruption, logic errors that produce wrong results silently, error handling that swallows failures, and trust boundary violations. No compliments — just the problems. For each finding, classify as FIXABLE (you know how to fix it) or INVESTIGATE (needs human judgment). After listing findings, end your output with ONE line in the canonical format \`Recommendation: <action> because <one-line reason naming the most exploitable finding>\` — examples: \`Recommendation: Fix the unbounded retry at queue.ts:78 because it'll DoS the worker pool under sustained 429s\` or \`Recommendation: Ship as-is because the strongest finding is a theoretical race that requires conditions we can't trigger in production\`. The reason must point to a specific finding (or no-fix rationale). Generic reasons like 'because it's safer' do not qualify."
@@ -303,7 +303,9 @@ in order before leaving Step 11:
new attempt separately, then reconsider these decisions. Without that correction,
or if the recovery fails, ask for repair and remain blocked.
2. **Fixes queued after native completion:** Keep the findings and their approvals.
Insert Steps 9, 10 and 11 before the pending Step 11.5 in the work list.
Under \`review_scope frozen\`, scope the queue first as Step 9.4 item 0 does; when
nothing is \`scope: fix\`, run item 3.5 (defer and render) and take decision 3.
Otherwise insert Steps 9, 10 and 11 before the pending Step 11.5 in the work list.
Step 9 completes full review before fixes; any further repair inserts its checks
ahead of the remaining items. These fresh reviews after code edits are not recovery retries.
Returning here never resets Step 9's three-cycle fix limit.
+41 -23
View File
@@ -57,7 +57,7 @@ ${ctx.paths.binDir}/gstack-specialist-stats 2>/dev/null || true
### Select specialists
Based on the scope signals above, select which specialists to dispatch.
Select specialists from the scope signals above.
**Always-on (dispatch on every review with 50+ changed lines):**
1. **Testing** — read \`${ctx.paths.skillRoot}/review/specialists/testing.md\`
@@ -78,13 +78,13 @@ Based on the scope signals above, select which specialists to dispatch.
After scope-based selection, apply adaptive gating based on specialist hit rates:
For each conditional specialist that passed scope gating, check the \`gstack-specialist-stats\` output above:
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.
Note which specialists were selected, gated, and skipped. Print the selection:
Print the selection:
"Dispatching N specialists: [names]. Skipped: [names] (scope not detected). Gated: [names] (0 findings in N+ reviews)."`;
}
@@ -97,7 +97,7 @@ so they run in parallel. Each subagent has fresh context — no prior review bia
**Each specialist subagent prompt:**
Construct the prompt for each specialist. The prompt includes:
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."
@@ -120,20 +120,21 @@ Every other specialist, including Testing, reads the full diff with fixtures.
"You are a specialist code reviewer. Read the checklist at {checklist path}, then run
\`DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE"\` to get the full diff. Apply the checklist against the diff.
For each finding, output a JSON object on its own line:
\`NO FINDINGS\` alone is the expected, complete result for a clean diff; never invent an item to show work. Otherwise output one JSON object per finding, on its own line:
{\\"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.
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.
Optional extraction advice belongs to the core shared-code check; do not duplicate its proposals. Report real defects in duplicated code independently. Preserve advisory metadata when returning structural advice, and never label a demonstrated defect advisory merely because sharing a helper could fix it.
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.
If no findings: output \`NO FINDINGS\` and nothing else.
Do not output anything else — no preamble, no summary, no commentary.
Output nothing else: no preamble, no summary or commentary.
Stack context: {STACK}
Past learnings: {learnings or 'none'}"
@@ -162,9 +163,9 @@ 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.
Successful \`NO FINDINGS\` is a completed empty result. Otherwise parse each JSON line and
skip invalid lines. Missing or unusable output is incomplete coverage, not an
empty success. Retain each specialist's returned findings for activity stats.
\`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.
#### 2. Validate severity
@@ -190,12 +191,29 @@ Compute identities for both core and specialist findings:
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})\`.
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. Apply specialist confidence gates
#### 4. Lead judgment
Reviewers fill the space they are given. Before any merged finding counts, the
parent reads each one and drops or downgrades it on these grounds, saying so:
- **Nit gravity.** When a reviewer's whole return is style, naming and preference,
the code is probably fine; write that down and count nothing from it as a 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, never a defect.
- **A different design is not a defect.** "I would have done it differently" with
no failing case behind it is dropped.
- **Missing context.** A finding that assumes a rule the repository documents
otherwise is dropped with the pointer.
Agreement between reviewers that did not see each other's output is the strongest
signal; the \`MULTI-SPECIALIST CONFIRMED\` boost above 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"
@@ -204,14 +222,14 @@ Core findings never earn a specialist confidence boost. Preserve \`advisory\`,
Core findings keep the core Confidence Calibration gates.
#### 5. Score and present specialists
#### 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 6 activity object and \`test_stub\` bodies are log and Fix-First data.
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.
@@ -236,9 +254,9 @@ PR Quality Score: X/10
\`Simplification: lean already — nothing to cut.\`
- If it was not dispatched, print neither line.
Do not add core shared-code savings to this specialist footer. Explain any overlap once in the core proposal instead of presenting duplicate savings.
Core shared-code savings never enter this footer; explain any overlap once in the core proposal.
#### 6. Save specialist activity
#### 7. Save specialist activity
Compile a \`specialists\` object for ${persistRef}.
${isShip ? 'For each specialist' : 'For DIFF_LINES < 50, keep `specialists: {}`; 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):
@@ -252,7 +270,7 @@ 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.
#### 7. Hand off to Fix-First
#### 8. Hand off to Fix-First
Send these findings to ${fixFirstRef} alongside ${critPassRef}.
Consolidate equivalent shared-code advice under the core proposal, retaining all
@@ -280,12 +298,12 @@ The Red Team subagent receives:
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 \`DIFF_BASE=$(git merge-base origin/<base> HEAD) && 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). Focus on cross-cutting
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."
If the Red Team finds additional issues, tag them \`"specialist":"red-team"\`.
Add them to the original specialist outputs and rerun stages 1–7 of Step ${stepMerge}
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."