feat(ship): batched specialists per shard, audit skip at zero code paths, gate-scan summary for the parent (GSTA-167)

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-29 21:39:03 +00:00
1 parent 1a558ded87
commit bde74d6f33
17 files changed
+334 -101

No files matched your search

+8 -8
View File
@@ -6,29 +6,29 @@
"agy/gstack-plan-design-review": 110044,
"agy/gstack-plan-devex-review": 116496,
"agy/gstack-plan-eng-review": 144907,
"agy/gstack-review": 103743,
"agy/gstack-review": 103737,
"codex/gstack-autoplan": 108553,
"codex/gstack-land-and-deploy": 100284,
"codex/gstack-plan-design-review": 111116,
"codex/gstack-plan-devex-review": 118004,
"codex/gstack-plan-eng-review": 146072,
"codex/gstack-review": 103266,
"codex/gstack-review": 103260,
"copilot/gstack-autoplan": 109253,
"copilot/gstack-plan-design-review": 110355,
"copilot/gstack-plan-devex-review": 115958,
"copilot/gstack-plan-eng-review": 145072,
"copilot/gstack-review": 103324,
"copilot/gstack-review": 103318,
"cursor/gstack-autoplan": 108485,
"cursor/gstack-plan-design-review": 108900,
"cursor/gstack-plan-devex-review": 115228,
"cursor/gstack-plan-eng-review": 143662,
"cursor/gstack-review": 121614,
"cursor/gstack-review": 121608,
"factory/gstack-autoplan": 110763,
"factory/gstack-land-and-deploy": 100105,
"factory/gstack-plan-design-review": 111675,
"factory/gstack-plan-devex-review": 118591,
"factory/gstack-plan-eng-review": 146770,
"factory/gstack-review": 125387,
"factory/gstack-review": 125381,
"gbrain/gstack-autoplan": 108892,
"gbrain/gstack-plan-eng-review": 126289,
"hermes/gstack-autoplan": 108891,
@@ -37,18 +37,18 @@
"kiro/gstack-plan-design-review": 108752,
"kiro/gstack-plan-devex-review": 115080,
"kiro/gstack-plan-eng-review": 143524,
"kiro/gstack-review": 121438,
"kiro/gstack-review": 121432,
"openclaw/gstack-autoplan": 108715,
"openclaw/gstack-plan-eng-review": 124804,
"opencode/gstack-autoplan": 109234,
"opencode/gstack-plan-design-review": 110187,
"opencode/gstack-plan-devex-review": 115836,
"opencode/gstack-plan-eng-review": 144931,
"opencode/gstack-review": 122236,
"opencode/gstack-review": 122230,
"slate/gstack-autoplan": 108372,
"slate/gstack-plan-design-review": 108826,
"slate/gstack-plan-devex-review": 115154,
"slate/gstack-plan-eng-review": 143593,
"slate/gstack-review": 121526
"slate/gstack-review": 121520
}
}
+47
View File
@@ -0,0 +1,47 @@
/**
* The shared gate-scan block, run: the parent gets the GATE_SUMMARY line and one index
* line per read-level hunk, never the hunk text (the reviewer packets and the diff carry
* that; the artifact holds the full listing). The fixture repository lives under the OS
* temp directory, outside this checkout, and every seed commit is asserted.
*/
import { afterEach, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { generateGateScanBlock } from '../scripts/resolvers/gate-scan';
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
import { between } from './helpers/prompt-structure';
const ROOT = path.join(import.meta.dir, '..');
const dirs: string[] = [];
afterEach(() => { while (dirs.length) fs.rmSync(dirs.pop()!, { recursive: true, force: true }); });
const tmp = (p: string) => { const d = fs.mkdtempSync(path.join(os.tmpdir(), p)); dirs.push(d); return d; };
test('the block prints the summary and the hunk index; the hunk text stays out of the parent', () => {
const dir = tmp('gate-scan-block-');
expect(path.relative(ROOT, dir).startsWith('..')).toBe(true);
const git = (...a: string[]) => spawnSync('git', a, { cwd: dir, encoding: 'utf8', timeout: 10_000 });
const write = (text: string) => { fs.mkdirSync(path.join(dir, 'test'), { recursive: true }); fs.writeFileSync(path.join(dir, 'test', 'a.test.ts'), text); };
git('init', '-q', '-b', 'main'); git('config', 'user.email', 'fixture@example.com'); git('config', 'user.name', 'Fixture'); git('config', 'commit.gpgsign', 'false');
write("test('a', () => {\n expect(HUNK_BODY_MARKER).toBe(1);\n expect(2).toBe(2);\n});\n");
git('add', '.');
expect(git('commit', '-qm', 'base').status).toBe(0);
git('checkout', '-qb', 'feat');
write("test.skip('a', () => {\n expect(2).toBe(2);\n});\n");
git('add', '.');
expect(git('commit', '-qm', 'feat').status).toBe(0);
const ctx: TemplateContext = { skillName: 'ship', tmplPath: 'ship/SKILL.md.tmpl', host: 'claude', paths: HOST_PATHS.claude };
const bash = between(generateGateScanBlock(ctx), '```bash', '\n```').slice('```bash'.length).replaceAll(HOST_PATHS.claude.binDir, path.join(ROOT, 'bin')).replaceAll('<base>', 'main');
const r = spawnSync('bash', ['-ec', bash], { cwd: dir, encoding: 'utf8', timeout: 60_000, env: { ...process.env, GSTACK_STATE_ROOT: tmp('gate-scan-state-') } });
expect(r.status).toBe(0);
const out = r.stdout.trimEnd().split('\n');
expect(out[0]).toMatch(/^GATE_SUMMARY: listed=\d+ eligible=[1-9]/);
expect(out.slice(1).length).toBeGreaterThan(0);
for (const l of out.slice(1)) expect(l).toMatch(/^\[[^\]]+\] \S+ test\/a\.test\.ts @@/);
expect(r.stdout).not.toContain('HUNK_BODY_MARKER');
expect(r.stdout).not.toContain('citation:');
const artifact = /artifact=(\S+)/.exec(out[0]!)![1]!;
expect(fs.readFileSync(artifact, 'utf8')).toContain('HUNK_BODY_MARKER');
});
+109 -5
View File
@@ -9,7 +9,7 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import * as P from '../lib/review-prompts';
import { shardChunks } from '../lib/review-packet';
import { shardChunks, SPECIALIST_PACKING } from '../lib/review-packet';
const ROOT = path.join(import.meta.dir, '..');
const BIN = path.join(ROOT, 'bin', 'gstack-review-packet');
@@ -43,7 +43,7 @@ const checklist = (name: string) => fs.readFileSync(path.join(ROOT, 'review', 's
describe('gstack-review-packet build', () => {
test('a packet holds its instructions, contract, checklist, the diff and read budget, and no other repository content', () => {
const r = repo({ 'src/unrelated.ts': 'export const UNRELATED_MARKER = 1;\n' }, { 'src/a.ts': lines(80, 'alpha') });
const b = build(r);
const b = build(r, ['--fanout', 'parallel']);
expect(b.status).toBe(0);
const all = packets(b.stdout);
const testing = all.find(p => p.name === 'testing')!;
@@ -61,7 +61,7 @@ describe('gstack-review-packet build', () => {
test('the defensive framing reaches the security and adversarial packets with fixtures as a stat; testing reads fixtures in full', () => {
const r = repo({}, { 'src/auth/login.ts': lines(120, 'auth'), 'test/login.test.ts': 'PAYLOAD_MARKER\n' });
const all = packets(build(r, ['--force', 'security']).stdout);
const all = packets(build(r, ['--force', 'security', '--fanout', 'parallel']).stdout);
for (const name of ['security', 'adversarial']) {
const p = all.find(x => x.name === name)!;
expect(p.text).toContain(P.DEFENSIVE_REVIEW_FRAMING);
@@ -89,7 +89,7 @@ describe('gstack-review-packet build', () => {
test('exclusions are honored: maintainability leaves out docs and tests; testing keeps tests; untracked files are in', () => {
const r = repo({}, { 'src/a.ts': lines(80, 'gamma'), 'docs/guide.md': '# Guide\nDOCS_MARKER\n', 'test/a.test.ts': 'TEST_MARKER\n' }, { 'src/new.ts': 'UNTRACKED_MARKER\n' });
const all = packets(build(r).stdout);
const all = packets(build(r, ['--fanout', 'parallel']).stdout);
const m = all.find(p => p.name === 'maintainability')!;
const t = all.find(p => p.name === 'testing')!;
expect(m.text).not.toContain('+DOCS_MARKER');
@@ -128,7 +128,7 @@ describe('gstack-review-packet build', () => {
test('the Red Team is deferred until collect; with --findings it reads them, the framing and fixtures in summary mode', () => {
const r = repo({}, { 'src/a.ts': lines(260, 'delta'), 'test/fixture.test.ts': 'PAYLOAD_MARKER\n' });
expect(build(r).stdout).toContain('PACKET_DEFERRED: red-team');
expect(build(r, ['--fanout', 'parallel']).stdout).toContain('PACKET_DEFERRED: red-team');
const merged = path.join(r.out, 'merged.jsonl');
fs.writeFileSync(merged, JSON.stringify({ severity: 'CRITICAL', path: 'src/a.ts', line: 3, summary: 'KNOWN_FINDING', sources: ['testing'] }) + '\n');
const red = packets(build(r, ['--only', 'red-team', '--findings', merged]).stdout)[0]!;
@@ -166,6 +166,81 @@ describe('gstack-review-packet build', () => {
expect(a.text).toContain('+export const zeta19 = 19;');
expect(build(r, ['--audit', 'plan-completion']).status).toBe(2);
});
const planDispatches = (r: Repo, extra: string[] = []) => {
const plan = spawnSync(path.join(ROOT, 'bin', 'gstack-review-plan'), ['--base', 'main', '--gate-tagged', '0', ...extra], { cwd: r.dir, encoding: 'utf8', timeout: 60_000, env: { ...process.env, GSTACK_STATE_ROOT: r.state } });
return [...plan.stdout.matchAll(/^DISPATCH: (\S+) (\S+)/gm)].map(m => ({ role: m[1]!, name: m[2]! }));
};
test('standard depth: the pass\'s specialist lines are one batched packet inlining every selected checklist; red-team and adversarial stay separate', () => {
const r = repo({}, { 'src/a.ts': lines(260, 'kappa'), 'test/a.test.ts': 'TEST_MARKER\n', 'docs/guide.md': '# Guide\nDOCS_MARKER\n' });
const selected = planDispatches(r).filter(d => d.role === 'specialist').map(d => d.name);
expect(selected.length).toBeGreaterThan(1);
const b = build(r);
expect(b.status).toBe(0);
const all = packets(b.stdout);
const specialists = all.filter(p => p.role === 'specialist');
expect(specialists.map(p => p.name)).toEqual(['batched']);
const batched = specialists[0]!;
for (const c of selected) { expect(batched.text).toContain(`## Checklist: ${c}`); expect(batched.text).toContain(checklist(c)); }
expect(batched.text).not.toContain('## Checklist: red-team');
expect(batched.text).toContain('Tag each finding with the checklist that found it');
expect(batched.text).toContain(`COVERED: ${selected.join(',')}`);
expect(batched.text).toContain('+TEST_MARKER');
expect(batched.text).not.toContain('+DOCS_MARKER');
expect(batched.text).toContain('the parent runs `gstack-state-matrix check` on it');
expect(b.stdout).toContain(`PACKED: specialist batched checklists=${selected.join(',')} shards=1`);
expect(b.stdout).toContain('PACKET_DEFERRED: red-team');
expect(all.some(p => p.role === 'adversarial-native')).toBe(true);
const parallel = packets(build(r, ['--fanout', 'parallel']).stdout).filter(p => p.role === 'specialist');
expect(parallel.map(p => p.name)).toEqual(selected);
expect(build(r, ['--fanout', 'serial']).status).toBe(2);
});
test('the default packing is the one switch SPECIALIST_PACKING names', () => {
expect(SPECIALIST_PACKING).toBe('batched');
});
test('full depth: every specialist batched, the diff unexcluded; the Red Team counts the batched checklists', () => {
const r = repo({}, { 'src/a.ts': lines(260, 'lambda'), 'docs/guide.md': '# Guide\nDOCS_MARKER\n' });
const selected = planDispatches(r, ['--depth-floor', 'full']).filter(d => d.role === 'specialist').map(d => d.name);
const b = build(r, ['--depth-floor', 'full']);
const specialists = packets(b.stdout).filter(p => p.role === 'specialist');
expect(specialists.map(p => p.name)).toEqual(['batched']);
expect(specialists[0]!.text).toContain('+DOCS_MARKER');
const merged = path.join(r.out, 'merged.jsonl');
fs.writeFileSync(merged, '');
const red = packets(build(r, ['--depth-floor', 'full', '--only', 'red-team', '--findings', merged]).stdout)[0]!;
expect(red.text).toContain(`reviewed by ${selected.length} specialists`);
});
test('a batched diff past the cap is one batched packet per shard, each with every checklist', () => {
const files: Record<string, string> = {};
for (let i = 0; i < 30; i++) files[`src/f${i}.ts`] = lines(400, `w${i}_`);
const r = repo({}, files);
const shards = packets(build(r, ['--only', 'specialist']).stdout);
expect(shards.length).toBeGreaterThan(1);
expect(shards.map(s => s.name)).toEqual(shards.map((_, i) => `batched.${i + 1}of${shards.length}`));
for (const s of shards) { expect(s.bytes).toBeLessThanOrEqual(200_000); expect(s.text).toContain(checklist('testing')); expect(s.text).toContain(checklist('maintainability')); }
});
test('Step 7: a diff with no code path (docs and data only) builds no audit packet and says why', () => {
const r = repo({}, { 'docs/guide.md': '# Guide\n', 'data/scores.json': '{"a":1}\n' });
const b = build(r, ['--audit', 'test-coverage']);
expect(b.status).toBe(0);
expect(packets(b.stdout)).toEqual([]);
expect(b.stdout).toMatch(/^AUDIT_SKIPPED: test-coverage code_paths=0 \(.*docs\/guide\.md.*\)/m);
expect(fs.existsSync(path.join(r.out, 'test-coverage.packet.md'))).toBe(false);
const code = repo({}, { 'src/a.ts': lines(5, 'mu'), 'data/scores.json': '{"a":1}\n' });
expect(packets(build(code, ['--audit', 'test-coverage']).stdout).length).toBe(1);
});
test('the coverage audit packet is read-only: no edits, no test runs, three calls, whatever generation the parent has', () => {
const r = repo({}, { 'src/a.ts': lines(20, 'nu') });
const a = packets(build(r, ['--audit', 'test-coverage', '--generation', 'allowed', '--passes-used', '0']).stdout)[0]!;
expect(a.text).toContain('Generation: audit-only; passes used: 0 of 2.');
expect(a.text).toContain(P.AUDIT_READ_ONLY);
});
});
describe('the packet prose and the generated SKILL.md prose are one text', () => {
@@ -180,6 +255,17 @@ describe('the packet prose and the generated SKILL.md prose are one text', () =>
expect(read('ship/sections/review-army.md')).toContain(P.packetBrief('<file>', '<out>'));
expect(read('ship/sections/adversarial.md')).toContain(P.packetBrief('<file>', '<out>'));
});
test('/ship Step 9 dispatches the batched packet at every depth and logs its checklists; Step 7 skips a no-code audit and writes tests itself', () => {
const army = read('ship/sections/review-army.md');
expect(army).toContain('--fanout parallel');
expect(army).toContain('"checklists":[');
expect(army).toContain('PACKED:');
expect(army).toMatch(/checklists include state-machines/);
const coverage = read('ship/sections/test-coverage.md');
expect(coverage).toContain('AUDIT_SKIPPED:');
expect(coverage).not.toContain('--generation <allowed|audit-only>');
expect(coverage).toMatch(/the parent writes and runs the tests/);
});
test('the outside voice boundary the safety-rule registry pins in the resolver is the packet\'s boundary', () => {
const line = read('scripts/resolvers/outside-voice-steps.ts').split('\n').find(l => l.startsWith('const CODEX_BOUNDARY = '))!;
expect(line).toBe(`const CODEX_BOUNDARY = '${P.CODEX_BOUNDARY_TEXT}\\\\n\\\\n';`);
@@ -235,6 +321,24 @@ describe('gstack-review-packet collect', () => {
expect(prose.stdout).toContain('REVIEWER: security status=invalid');
});
test('a batched packet must end with COVERED naming every checklist it inlined; a missing one is incomplete coverage', () => {
const d = (out: string) => {
const x = dir([{ role: 'specialist', name: 'batched' }], { batched: out });
const m = JSON.parse(fs.readFileSync(path.join(x, 'manifest.json'), 'utf8'));
m.packets[0].checklists = ['testing', 'security'];
fs.writeFileSync(path.join(x, 'manifest.json'), JSON.stringify(m));
return x;
};
const ok = collect(d('NO FINDINGS\nCOVERED: testing, security'));
expect(ok.status).toBe(0);
expect(ok.stdout).toContain('REVIEWER: batched status=clean crit=0 info=0 covered=testing,security');
const partial = collect(d(finding({ specialist: 'testing' }) + '\nCOVERED: testing'));
expect(partial.status).toBe(3);
expect(partial.stdout).toContain('REVIEWER: batched status=findings crit=0 info=1 covered=testing uncovered=security');
expect(partial.stdout).toMatch(/batched=uncovered:security.*\(REVIEW_COVERAGE_INCOMPLETE\)$/m);
expect(collect(d('NO FINDINGS')).status).toBe(3);
});
test('INFORMATIONAL findings are counts in the record and lines in informational.md', () => {
const d = dir(specialists, { testing: finding({ summary: 'INFO_ONLY_MARKER', fix: 'do it' }), security: 'NO FINDINGS' });
const r = collect(d);
@@ -1,4 +1,4 @@
{
"bytes": -3324,
"why": "GSTA-167 reviewer packets: Step 9.1 dispatches each reviewer by one packet file (gstack-review-packet build/collect) in place of the inlined specialist prompt; Red Team, fix-check, Step 11 and the Step 7/8 audits read packets too"
"bytes": -2687,
"why": "GSTA-167 reviewer packets: Step 9.1 dispatches each reviewer by one packet file (gstack-review-packet build/collect) in place of the inlined specialist prompt; Red Team, fix-check, Step 11 and the Step 7/8 audits read packets too. PR 3b: a pass's specialists are one batched packet per shard with a COVERED line, Step 7 skips the audit on a diff with no code path and its packet is read-only, the gate scan prints the summary and hunk index only"
}