import { expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { generateReviewArmy } from '../scripts/resolvers/review-army';
import { generateCrossReviewDedup, generateSharedCodeReuse, generateScopeDrift } from '../scripts/resolvers/review-scope';
import { generatePlanCompletionAuditReview, generatePlanCompletionAuditShip, generatePlanCompletionGateShip, PLAN_AUDIT_NOT_RUN } from '../scripts/resolvers/plan-gates';
import { generateQAReview } from '../scripts/resolvers/qa';
import { generateConfidenceCalibration } from '../scripts/resolvers/confidence';
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
import { between, compact, expectAbsent, expectMentions, expectOrdered, expectTokens } from './helpers/prompt-structure';
const root = join(import.meta.dir, '..');
const skill = readFileSync(join(root, 'review/SKILL.md.tmpl'), 'utf8');
const adversarial = readFileSync(join(root, 'review/sections/adversarial.md.tmpl'), 'utf8');
test('review audits deliverables before deferring behavioral plan checks to the QA preflight', () => {
const ctx: TemplateContext = { skillName: 'review', tmplPath: '', host: 'claude', paths: HOST_PATHS.claude };
const audit = generatePlanCompletionAuditReview(ctx).replace(/\s+/g, ' ');
for (const rule of [
/separate static audit evidence from behavioral checks/i,
/never DONE from a diff/,
/zero audited deliverables do not waive these checks/i,
/for each audited deliverable/i,
/leave the item UNVERIFIABLE and defer the command to Step 4\.7/,
/do not start applications, exercise APIs or mutate state during this audit/i,
]) expect(audit).toMatch(rule);
const inspect = audit.search(/inspect the validator and its hooks before running it/i);
expect(inspect).toBeGreaterThan(-1);
expect(inspect).toBeLessThan(audit.search(/if found and verified safe above, invoke it/i));
});
test('review prior-Skip matching includes adversarial and Greptile findings without relaxing eligibility', () => {
const dedup = generateCrossReviewDedup({ skillName: 'review', tmplPath: '', host: 'claude', paths: HOST_PATHS.claude });
expectMentions(dedup, [['only', 'conditions', 'unchanged']], 'dedup');
expectMentions(dedup, [['never', 'advisory', 'suppress']], 'dedup');
expect(dedup).toMatch(/only suppress `skipped` findings — never `fixed` or `auto-fixed`/i);
expect(skill).toContain('Step 5.0 severity/prior-skip dedup');
});
test('Review audit and prior-Skip clarifications do not route Ship through Review steps', () => {
for (const host of Object.keys(HOST_PATHS) as TemplateContext['host'][]) {
const ctx: TemplateContext = { skillName: 'ship', tmplPath: '', host, paths: HOST_PATHS[host] };
const audit = generatePlanCompletionAuditShip(ctx);
expect(audit).toContain('Step 8.1/9');
expect(audit).not.toContain('Step 4.7');
const dedup = generateCrossReviewDedup(ctx);
expect(dedup).toContain('Step 9.3: Cross-review finding dedup');
expect(dedup).not.toContain('Step 5.0');
expect(dedup).not.toContain('For every combined finding');
}
});
test('review collects every source before its single parent fix phase', () => {
const markers = [
'## Step 4: Critical pass', '### TODOS cross-reference',
'### Documentation staleness check', '{{SECTION:review-army}}',
'{{QA_REVIEW}}', '{{SECTION:adversarial}}',
'## Step 5: Fix-First Review', '{{CROSS_REVIEW_DEDUP}}',
'### Step 5a:', '### Step 5b:', '### Step 5c:', '### Step 5d:',
'## Step 5.8: Persist Eng Review result',
];
const positions = markers.map(marker => skill.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
expect(skill.match(/## Step 5: Fix-First Review/g)).toHaveLength(1);
expectMentions(skill.replace(/\s+/g, ' '), [['do not', 'reviewed', 'source']], 'skill.replace(/\s+/g, )');
});
test('review settles adversarial attempts before fixing and has one full-pass back edge', () => {
const generated = readFileSync(join(root, 'review/sections/adversarial.md'), 'utf8');
const decision = skill.slice(skill.indexOf('## Step 5.8: Persist Eng Review result')).replace(/\s+/g, ' ');
expect(generated).toContain('## Step 4.8: Adversarial review');
expectMentions(generated, [['do not', 'during', 'edit']], 'generated');
expect(decision).toMatch(/a pass covers Steps 3–5/i);
expect(decision).toContain('Below 3, repeat Steps 3–5 with a new REVIEW_START');
expect(decision).not.toContain('Route Step');
expect(decision).not.toContain('Steps 5.0–5d');
expectMentions(decision, [['without', 'summary', 'fourth']], 'decision');
});
test('review small-diff and failed-reader paths retain QA and the required adversarial pass', () => {
const army = generateReviewArmy({ skillName: 'review', tmplPath: 'review/SKILL.md.tmpl',
host: 'claude', paths: HOST_PATHS.claude });
expect(army).not.toContain('design-lite');
expectMentions(army.replace(/\s+/g, ' '), [['never', 'dispatched', 'incomplete']], 'review army');
expect(army).not.toContain('skip silently');
});
test('review defines QA confidence, severity, impact selection and numeric version comparison', () => {
const flat = skill.replace(/\s+/g, ' ');
expect(flat).toContain('confidence (1–10)');
expect(flat).toMatch(/retain Step 4\.7's severity/i);
});
test('review emits scope check after the plan audit and before the checklist', () => {
const markers = [
'{{SCOPE_DRIFT}}', '{{SECTION:plan-completion}}',
'## Step 2: Read the checklist',
];
const positions = markers.map(marker => skill.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
const audit = readFileSync(join(root, 'review/sections/plan-completion.md'), 'utf8').replace(/\s+/g, ' ');
expect(skill).not.toContain('Finish Step 1.5 here');
});
test('review composes confidence-tagged findings into one final report with explicit incomplete coverage', () => {
const flat = skill.replace(/\s+/g, ' ');
expect(flat).toContain('CRITICAL/INFORMATIONAL');
const report = flat.slice(flat.indexOf('### Report the final review'), flat.indexOf('{{LEARNINGS_LOG}}'));
expect(report).toMatch(/one final report/i);
expect(report).toMatch(/unresolved non-advisory defects/i);
expect(report).toMatch(/[Ss]tate INCOMPLETE if `COMPLETED` is false/);
expect(report).toContain('`## Exploratory QA and Verification Results`');
});
test('small-diff persistence uses an empty specialist map without manufacturing skipped coverage', () => {
const army = generateReviewArmy({ skillName: 'review', tmplPath: 'review/SKILL.md.tmpl',
host: 'claude', paths: HOST_PATHS.claude });
expect(army).toContain('DIFF_LINES < 50, keep `specialists: {}`');
expect(skill.replace(/\s+/g, ' ')).toMatch(/Step 4\.6's `specialists` object unchanged/i);
const ship = readFileSync(join(root, 'ship/sections/review-army.md.tmpl'), 'utf8');
expect(ship).toContain('`specialists`: `{}` for a small-diff skip');
expect(ship.replace(/\s+/g, ' ')).toMatch(/all required probes pass/i);
});
test('caller QA runs charter and setup after resource loading and has a severity for unmatched functional failures', () => {
for (const skillName of ['review', 'ship']) {
const body = generateQAReview({ skillName, tmplPath: '', host: 'claude', paths: HOST_PATHS.claude });
const preparation = body.indexOf(skillName === 'review'
? '**1. Set the charter and isolation.**'
: 'Run the shared preflight;');
const probes = body.indexOf('**3. Run smoke and plan checks.**');
expect(preparation).toBeGreaterThan(-1);
if (skillName === 'review') {
const readiness = body.indexOf('**2. Check readiness and list required checks.**');
expect(readiness).toBeGreaterThan(preparation);
expect(probes).toBeGreaterThan(readiness);
expect(body.slice(preparation, readiness).replace(/\s+/g, ' ')).toMatch(/isolation\/permission preflight before setup/i);
} else expect(preparation).toBeGreaterThan(body.indexOf('**2. List required checks.**'));
expect(probes).toBeGreaterThan(preparation);
expect(body.replace(/\s+/g, ' ')).toContain('`functional-contract`, `CRITICAL`');
expectMentions(body, [['not', 'setup/permission', 'blockers']], 'body');
expectMentions(body, [['approval', 'creation', 'needs']], 'body');
expect(body).not.toContain('for parent approval');
}
});
test('review prepares context and deduplicates before classifying findings', () => {
const positions = [
'## Step 3.5: Diff scans', '## Step 3.6: Gather review context',
'{{LEARNINGS_SEARCH}}', '{{ASIDE_RESEARCH}}', '## Step 4: Critical pass',
'## Step 5: Fix-First Review', '{{CROSS_REVIEW_DEDUP}}',
'**Keep decisions through fix cycles:**', '### Step 5a: Classify each finding',
].map(marker => skill.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
expect(skill).toMatch(/before Step 5a classification/i);
});
test('review owns the complete persistence contract after the adversarial read', () => {
const step = skill.slice(skill.indexOf('## Step 5.8: Persist Eng Review result'));
expect(skill.indexOf('{{SECTION:adversarial}}')).toBeLessThan(skill.indexOf('## Step 5.8: Persist Eng Review result'));
expect(adversarial).not.toContain('### Before persisting Eng Review (Step 5.8)');
for (const token of [
'repeat Steps 3–5', 'at most 3 fix cycles', 'original `evidence_paths`/`helper_target`',
'snapshot_covered_paths', 'REVIEW_START', 'COMPLETED', 'CONVERGED', 'CYCLES', 'Step 4.7',
'native Step 4.8 adversarial pass', 'named-risk', 'zero counts', '`completed:false`',
'`specialists`', '`findings`', 'explicit Skip', 'sharedLibsFingerprint', '`review_binding`',
]) expect(step.toLowerCase().replace(/\s+/g, ' ')).toContain(token.toLowerCase());
expect(step.indexOf('### 1. Re-review after edits'))
.toBeLessThan(step.indexOf('### 2. Fill the record'));
expect(step.indexOf('### 2. Fill the record'))
.toBeLessThan(step.indexOf('~/.claude/skills/gstack/bin/gstack-review-log'));
expect(step).toContain('`quality_score`');
expect(step.indexOf('Pre-Landing Review: N issues (X critical, Y informational)'))
.toBeGreaterThan(step.indexOf('~/.claude/skills/gstack/bin/gstack-review-log'));
expect(step).toContain('`## Exploratory QA and Verification Results`');
});
test('review distinguishes required native coverage from optional outside coverage', () => {
const section = readFileSync(join(root, 'review/sections/adversarial.md'), 'utf8');
expectMentions(section, [['only', 'non-blocking', 'adversarial']], 'section');
expect(section).not.toContain('All errors are non-blocking');
});
test('review identifies probe selection, report assets and the detected diff base', () => {
const generated = generateQAReview({ skillName: 'review', tmplPath: 'review/SKILL.md.tmpl',
host: 'claude', paths: HOST_PATHS.claude });
const checklist = readFileSync(join(root, 'review/checklist.md'), 'utf8');
expect(generated).toContain('5 minutes/12 probes');
expect(generated).toContain("Read QA\'s `templates/functional-report-template.md`");
expect(generated).toMatch(/link every checkpoint/i);
expect(generated).toMatch(/no second report/i);
expect(checklist).not.toContain('git diff origin/main');
});
test('caller QA defines execution, evidence ownership and report adaptation before handoff', () => {
for (const skillName of ['review', 'ship']) {
const generated = generateQAReview({ skillName, tmplPath: `${skillName}/SKILL.md.tmpl`,
host: 'claude', paths: HOST_PATHS.claude }).replace(/\s+/g, ' ');
expect(generated).toMatch(/never overwrite another run/i);
expectMentions(generated, [['without', 'current', 'updates']], 'generated');
expect(generated).toContain('Use checklist severity');
if (skillName === 'review') {
expect(generated).toContain('`## Exploratory QA and Verification Results`');
expect(generated).toContain('`### Browser results`');
} else {
expect(generated).toContain('`## Exploratory QA`');
}
}
});
test('review section index follows the actual pre-fix execution order', () => {
const manifest = JSON.parse(readFileSync(join(root, 'review/sections/manifest.json'), 'utf8'));
expect(manifest.sections.map((section: { id: string }) => section.id)).toEqual([
'plan-completion', 'review-army', 'adversarial', 'shared-code-reuse',
]);
});
test('review finalization ownership: initialize invocation state and capture the core token before reading', () => {
const start = skill.slice(skill.indexOf('## Step 3: Get the diff'), skill.indexOf('## Step 3.4:'));
expect(start).toContain('CYCLES=0');
expect(skill.match(/CYCLES=0/g)).toHaveLength(1);
expect(start).toContain('gstack-review-log --start review\ngit diff "$DIFF_BASE"');
const flat = start.replace(/\s+/g, ' ');
expect(flat).toMatch(/REVIEW_START for this core candidate before reading its diff/i);
expectMentions(flat, [['never', 'reading', 'before']], 'flat');
expect(flat).toMatch(/separate PASS_START tokens, not REVIEW_START/);
expectMentions(flat, [['only', 'finishes', 'final']], 'flat');
});
test('review finalization ownership: late findings use Fix-First before the bounded parent transition', () => {
const flat = skill.replace(/\s+/g, ' ');
const markers = [
'{{SECTION:adversarial}}',
'## Step 5: Fix-First Review',
'Structured approval does not waive advisory/test_stub ASK gates',
'## Step 5.8: Persist Eng Review result',
'Edited: increment CYCLES once',
'No edits: fill the record below',
'### 2. Fill the record',
'--finish REVIEW_START',
];
const positions = markers.map(marker => flat.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
expect(flat).toContain('at most 3 fix cycles');
expect(flat).toContain('At 3, persist `converged:false`');
const limit = flat.slice(flat.indexOf('At 3,'), flat.indexOf('No edits:'));
expect(limit).toMatch(/then stop this invocation/i);
expectMentions(limit, [['without', 'summary', 'fourth']], 'limit');
});
test('review finalization ownership: affected QA reuse cannot replace a full review or erase decisions', () => {
const step = skill.slice(skill.indexOf('## Step 5.8: Persist Eng Review result'));
const flat = step.replace(/\s+/g, ' ');
expect(flat).toContain('execute Steps 3–5 in order');
expectMentions(flat, [['only', 'unchanged-input', 'evidence']], 'flat');
expectMentions(flat, [['never', 'reusing', 'review']], 'flat');
expectMentions(flat, [['do not', 'suppress', 'earlier']], 'flat');
expect(flat).toContain('logger computes `snapshot_covered_paths`');
expectMentions(flat, [['never', 'prior-record', 'prior-cycle']], 'flat');
});
test('review finalization ownership: required native completion and optional outside records stay separate', () => {
const step = skill.slice(skill.indexOf('### 2. Fill the record'));
const flat = step.replace(/\s+/g, ' ');
expectMentions(flat, [['not', 'inconclusive', 'required']], 'flat');
expect(flat).toMatch(/`\/ship` named-risk acceptance cannot complete `\/review`/i);
expectMentions(flat, [['cannot', 'substitute', 'native']], 'flat');
expect(flat).toMatch(/structured-review gate still applies/i);
expect(flat).toContain('zero counts and `completed:false`');
expect(flat).toContain('`CONVERGED`: true only for a completed zero-edit pass');
});
test('review finalization ownership: finish only the final core token without log-time capture', () => {
const step = skill.slice(skill.indexOf('## Step 5.8: Persist Eng Review result'));
const flat = step.replace(/\s+/g, ' ');
expect(step.match(/--finish REVIEW_START/g)).toHaveLength(1);
expect(step).not.toContain('--start review');
expect(step).not.toContain('--finish PASS_START');
expect(flat).toMatch(/never invent a binding or replace REVIEW_START at log time/i);
expectMentions(flat, [['only', 'finish', 'final']], 'flat');
expect(step).toContain('"completed":COMPLETED,"converged":CONVERGED,"cycles":CYCLES');
});
test('review finalization ownership: the plan audit retains its high-impact gate before the final scope check', () => {
const plan = readFileSync(join(root, 'review/sections/plan-completion.md.tmpl'), 'utf8');
expect(plan).toContain('HIGH-impact discrepancy question');
expectMentions(plan, [['before', 'resolve', 'final']], 'plan');
expect(plan).not.toContain('never blocks the review');
expect(plan).toContain('{{PLAN_COMPLETION_AUDIT_REVIEW}}');
const audit = readFileSync(join(root, 'review/sections/plan-completion.md'), 'utf8');
const gate = audit.indexOf('**HIGH-impact plan-file discrepancies** trigger AskUserQuestion');
expect(gate).toBeGreaterThan(-1);
expect(gate).toBeLessThan(audit.indexOf('When continuing after the audit (no HIGH-impact gate, or option B/C)'));
const flatAudit = audit.replace(/\s+/g, ' ');
expectMentions(flatAudit, [['before', 'implementation', 'invocation']], 'flatAudit');
expect(flatAudit).toMatch(/not this read-only audit/i);
// C2: a spawned or non-interactive review cannot answer the question; its default writes and drops nothing.
expect(flatAudit).toContain('Spawned or non-interactive: report REQUIREMENTS MISSING, continue; record nothing.');
expect(flatAudit.indexOf('Spawned or non-interactive: report')).toBeLessThan(flatAudit.indexOf('When continuing after the audit'));
});
test('review scope notes remain provisional until the plan section emits the only final scope check', () => {
const scope = generateScopeDrift({ skillName: 'review', tmplPath: 'review/SKILL.md.tmpl',
host: 'claude', paths: HOST_PATHS.claude }).replace(/\s+/g, ' ');
expect(scope).toMatch(/keep these notes provisional/i);
expectMentions(scope, [['before', 'single', 'final']], 'scope');
expect(scope).not.toContain('Scope Check: [CLEAN');
expect(scope).not.toContain('available plan-audit results');
});
test('review confidence uses its severity labels without an undefined P0 exception', () => {
const ctx: TemplateContext = { skillName: 'review', tmplPath: 'review/SKILL.md.tmpl',
host: 'claude', paths: HOST_PATHS.claude };
const confidence = generateConfidenceCalibration(ctx);
expect(confidence).toContain('[CRITICAL]');
expect(confidence).toContain('[CRITICAL|INFORMATIONAL]');
expect(confidence).not.toMatch(/\bP[012]\b/);
const flat = confidence.replace(/\s+/g, ' ');
for (const row of [
/\| 9-10 \|[^|]*\| Show normally \|/, /\| 7-8 \|[^|]*\| Show normally \|/,
/\| 5-6 \|[^|]*\| Show with caveat/, /\| 3-4 \|[^|]*\| Suppress from main report/,
/\| 1-2 \|[^|]*\| Only report a suspected release-blocking catastrophe/,
]) expect(flat).toMatch(row);
expect(flat).toMatch(/explicitly speculative/i);
expect(flat).toMatch(/the finding is unverified/i);
expect(flat).toContain('file:line');
expect(flat).toMatch(/force its confidence to 4-5/i);
expect(flat).toMatch(/never invent speculative confidence 7\+/i);
expect(confidence.indexOf('Pre-emit verification gate')).toBeLessThan(confidence.indexOf('| Score |'));
expect(confidence).not.toContain('FP classes the gate kills');
expect(confidence).not.toContain('1539-framework-aware-review.md');
expectMentions(generateConfidenceCalibration({ ...ctx, skillName: 'ship' }), [['only', 'severity', 'report']], 'section');
});
test('review names the lifecycle and record owners before using their persistence rules', () => {
const start = skill.slice(skill.indexOf('## Step 3: Get the diff'), skill.indexOf('## Step 3.4:')).replace(/\s+/g, ' ');
expect(start).toContain('REVIEW_START / PASS_START |');
expect(start).toContain('`review_binding` |');
expect(start).toContain('`snapshot_covered_paths` |');
expectMentions(start, [['never', 'matching', 'reusable']], 'start');
expectMentions(start, [['never', 'supplied', 'reviewer']], 'start');
});
test('review invocation-local advice reuse keeps raw-source and changed-decision gates', () => {
const decisions = skill.slice(skill.indexOf('**Keep decisions through fix cycles:**'),
skill.indexOf('### Step 5a:')).replace(/\s+/g, ' ');
expect(decisions).toMatch(/save completed AUTO-FIX\/fix and explicit Skip actions/i);
expect(decisions).toContain("`advisory`, `evidence_paths` and `helper_target`");
expect(decisions).toMatch(/require a new question/i);
expect(decisions).toMatch(/cannot suppress new\/recurring defects/i);
});
for (const skillName of ['review', 'ship']) {
const ctx: TemplateContext = { skillName, tmplPath: `${skillName}/SKILL.md.tmpl`,
host: 'claude', paths: HOST_PATHS.claude };
const army = generateReviewArmy(ctx);
const flat = army.replace(/\s+/g, ' ');
// PR gate run 38981523623 (ship-exploratory-plan-checks): the block sourced the scope signals
// without printing them, so the agent appended its own echo to see SCOPE_FRONTEND.
test(`${skillName} clarity: the scope block prints every signal specialist selection reads`, () => {
const block = between(army, '### Detect stack and scope', '### Read specialist hit rates');
expect(block).toContain("/gstack-diff-scope 2>/dev/null) || true\nset | grep '^SCOPE_'\n");
const selection = between(army, '### Select specialists', '### Adaptive gating');
const used = [...new Set(selection.match(/SCOPE_[A-Z_]+/g))];
expect(used.length).toBeGreaterThan(0);
const flags = between(readFileSync(join(root, 'bin/gstack-diff-scope'), 'utf8'), '_print_flags() {', '}');
for (const signal of used) {
if (flags.includes(`echo "${signal}=`)) continue;
// PR gate run 39002269281: a signal gstack-diff-scope does not print comes from a detect helper the scope
// block itself runs, never from a command named only in selection prose.
const helper = /bin\/(gstack-[a-z-]+) detect\b/.exec(block.split('\n').find(line => line.includes(signal))!)?.[1];
expect(helper, signal).toBeDefined();
expect(readFileSync(join(root, 'bin', helper!), 'utf8'), signal).toContain(`${signal}=\${`);
expect(selection, signal).not.toMatch(/\bdetect\b/);
}
});
test(`${skillName} clarity: the state-machine signal checks the detector's exit status and fails toward dispatch`, () => {
const block = between(army, '### Detect stack and scope', '### Read specialist hit rates');
expect(block).toContain(`_SM=$(${HOST_PATHS.claude.binDir}/gstack-state-matrix detect --base ) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM")\n`);
expect(block.indexOf('SCOPE_STATE_MACHINE=unknown')).toBeLessThan(block.indexOf("set | grep '^SCOPE_'"));
const selection = between(army, '### Select specialists', '### Adaptive gating').replace(/\s+/g, ' ');
expect(selection).toContain('9. **State Machines** — if SCOPE_STATE_MACHINE is true or unknown: read');
// /review delta on 51f1e02: a fixed .gstack/tmp/state-machines.md let a second review check the previous run's
// output (Write refuses an unread existing file) and commit it; each run now checks the new file outfile prints.
expect(selection).toContain('/gstack-state-matrix outfile`, Read the file it prints, write the full output there, then run `');
expect(selection).toContain('/gstack-state-matrix check ` on it.');
expect(selection).not.toContain('.gstack/tmp/state-machines.md');
expect(selection).not.toContain('<<');
expect(selection).not.toContain('pipe output');
});
test(`${skillName} clarity: the scope block run against a failing detector prints unknown and keeps its stderr`, () => {
const stubs = mkdtempSync(join(realpathSync(tmpdir()), 'scope-block-'));
try {
const block = /\`\`\`bash\n([\s\S]*?)\n\`\`\`/.exec(between(generateReviewArmy({ ...ctx, paths: { ...ctx.paths, binDir: stubs } }),
'### Detect stack and scope', '### Read specialist hit rates'))![1].replaceAll('', 'main');
writeFileSync(join(stubs, 'gstack-diff-scope'), '#!/bin/sh\necho SCOPE_FRONTEND=false\n', { mode: 0o755 });
const run = (detector: string) => {
writeFileSync(join(stubs, 'gstack-state-matrix'), `#!/bin/sh\n${detector}\n`, { mode: 0o755 });
return spawnSync('bash', ['-c', block], { cwd: stubs, encoding: 'utf8', timeout: 10_000 });
};
const failed = run("echo SCOPE_STATE_MACHINE=false; echo '# no merge base with origin/main; run: git fetch origin main'; echo 'detector crashed' >&2; exit 2");
expect(failed.stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m);
expect(failed.stdout).not.toMatch(/^SCOPE_STATE_MACHINE=false$/m);
expect(failed.stderr).toContain('detector crashed');
expect(run('exit 127').stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m);
const silent = run("echo 'detector found no git' >&2; exit 3");
expect(silent.stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m);
expect(silent.stderr).toContain('detector found no git');
const lateFail = run("echo SCOPE_STATE_MACHINE=true; echo SCOPE_LEAK=1; exit 1");
expect(lateFail.stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m);
expect(lateFail.stdout).not.toContain('SCOPE_LEAK');
const detected = run("echo SCOPE_STATE_MACHINE=true; echo '# state-machine: src/takes.ts:4 proposal,accept'");
expect(detected.stdout).toMatch(/^SCOPE_STATE_MACHINE=true$/m);
expect(detected.stdout).toMatch(/^SCOPE_FRONTEND=false$/m);
expect(run('echo SCOPE_STATE_MACHINE=false').stdout).toMatch(/^SCOPE_STATE_MACHINE=false$/m);
expect(block).toContain("DIFF_LINES=$(git diff \"$DIFF_BASE\" --numstat | awk '{n+=$1+$2} END {print n+0}')");
} finally { rmSync(stubs, { recursive: true, force: true }); }
});
test(`${skillName} clarity: terminal failure permits independent work but never certifies coverage`, () => {
expectMentions(flat, [['confirm', 'finished', 'stopped']], 'flat');
expectMentions(flat, [['does not', 'termination', 'timeout']], 'flat');
expect(flat).toMatch(/stop path without edits/i);
expectMentions(flat, [['never', 'dispatched', 'incomplete']], 'flat');
const redTeam = flat.slice(flat.indexOf('### Red Team dispatch'));
expectMentions(redTeam, [['confirm', 'incomplete', 'stopped']], 'redTeam');
expect(redTeam).toContain('stages 1–7');
});
test(`${skillName} clarity: ordered specialist merge separates validation from scoring and provenance`, () => {
const markers = ['#### 1. Parse outputs', '#### 2. Validate severity',
'#### 3. Identify and merge', '#### 4. Apply specialist confidence gates',
'#### 5. Score and present specialists', '#### 6. Save specialist activity',
'#### 7. Hand off to Fix-First'];
const positions = markers.map(marker => army.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
const validation = flat.slice(flat.indexOf('#### 2.'), flat.indexOf('#### 3.'));
expect(validation).toContain('remove `advisory` and retain its `CRITICAL` severity');
expect(validation).toMatch(/never downgrade severity/i);
const merge = flat.slice(flat.indexOf('#### 3.'), flat.indexOf('#### 4.'));
expect(merge.indexOf('Partition defects and advisories')).toBeLessThan(merge.indexOf('grouping by fingerprint'));
for (const token of ['sharedLibsFingerprint', 'literal JSON on stdin', '+1 (cap at 10)']) expect(merge).toContain(token);
for (const rule of [/never trust a supplied hash/i, /missing\/malformed metadata cannot deduplicate/i,
/core findings never earn a specialist confidence boost/i]) expect(merge).toMatch(rule);
for (const gate of ['Confidence 7+', 'Confidence 5-6', 'Confidence 3-4', 'Confidence 1-2']) expect(army).toContain(gate);
const scoring = flat.slice(flat.indexOf('#### 5.'), flat.indexOf('#### 6.'));
expect(scoring).toMatch(/only specialist findings enter this header and `quality_score`/i);
expect(scoring).toMatch(/non-advisory/i);
expect(scoring).toContain('quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5))');
expect(flat).toMatch(/advisory findings count in the stats `findings` field/i);
expectMentions(flat, [['only', 'specialist', 'findings']], 'flat');
expectMentions(flat, [['not', 'specialist', 'core-only']], 'flat');
expect(flat).toContain('ASK-only');
});
test(`${skillName} clarity: shared-code reuse gives executable decisions and retains checker safeguards`, () => {
const reuse = generateSharedCodeReuse(ctx).replace(/\s+/g, ' ');
const markers = ['1. **Read the evidence.**', '2. **Run the checker.**',
'3. **Act on its result.**', '4. **Persist through the logger.**'];
const positions = markers.map(marker => reuse.indexOf(marker));
expect(positions.every(position => position >= 0)).toBe(true);
expect(positions).toEqual([...positions].sort((a, b) => a - b));
for (const token of ['--check-shared-libs REVIEW_START', "<<'GSTACK_SHARED_LIBS_REUSE_JSON'", 'literal JSON on stdin',
'`reusable: true`', 'sharedLibsFingerprint', 'explicit Skip', 'logger-versioned `snapshot_covered_paths`',
'canReuseSharedLibsAdvisory']) expect(reuse).toContain(token);
for (const rule of [/false, command failure or unreadable output[^.]*never suppression/i,
/do not supply your own snapshot, prior record or coverage/i, /unknown evidence fails closed/i,
/real defects retain normal Fix-First handling/i]) expect(reuse).toMatch(rule);
});
}
test('review clarity: settlement gates edits separately from incomplete required coverage', () => {
const fix = skill.slice(skill.indexOf('## Step 5: Fix-First Review'), skill.indexOf('{{CROSS_REVIEW_DEDUP}}')).replace(/\s+/g, ' ');
expectMentions(fix, [['stop', 'without', 'edits']], 'fix');
});
test('review clarity: Greptile reply choices never substitute for Fix-First approval', () => {
const fix = skill.slice(skill.indexOf('## Step 5: Fix-First Review'), skill.indexOf('{{CROSS_REVIEW_DEDUP}}'));
expect(fix).toContain('VALID & ACTIONABLE Greptile findings');
const greptile = skill.slice(skill.indexOf('### Greptile comment resolution'), skill.indexOf('## Step 5.8:'));
const flat = greptile.replace(/\s+/g, ' ');
expectMentions(flat, [['not', 'decisions', 'approval']], 'flat');
expect(flat).toContain('B) Propose a code change');
expect(flat).toContain('Steps 5c–5d');
expectMentions(flat, [['wait', 'approval', 'editing']], 'flat');
expect(flat).toMatch(/no new fix permission/i);
});
test('ship review clarity: parent settlement gate precedes classification and cannot waive coverage', () => {
const ship = readFileSync(join(root, 'ship/sections/review-army.md.tmpl'), 'utf8');
const gate = ship.slice(ship.indexOf('## Step 9.4:'), ship.indexOf('1. **Classify')).replace(/\s+/g, ' ');
expectMentions(gate, [['wait', 'termination', 'confirm']], 'gate');
expectMentions(gate, [['stop', 'incomplete', 'through']], 'gate');
expectMentions(gate, [['blocks', 'continuation', 'dispatched']], 'gate');
});
test('review keeps its smoke-clock, setup-authority, plan-gate and findings-source rules', () => {
const ctx = { skillName: 'review', tmplPath: 'review/SKILL.md.tmpl', host: 'claude', paths: HOST_PATHS.claude } as TemplateContext;
const qa = generateQAReview(ctx).replace(/\s+/g, ' ');
expect(qa).toMatch(/\/review sets none; only an invoker-supplied EARLIER_UTC counts/);
expectMentions(qa, [['never', 'report-only', 'installs']], 'qa');
expect(qa).toMatch(/otherwise they stay blocked/i);
expect(qa).not.toContain('Ask for setup/permission');
expect(generateQAReview({ ...ctx, skillName: 'ship', tmplPath: 'ship/SKILL.md.tmpl' })).not.toContain('/review sets none');
const audit = generatePlanCompletionAuditReview(ctx).replace(/\s+/g, ' ');
// F1 (#2768): no bound plan prints the exact not-run line instead of the old notice.
expect(audit).toContain(PLAN_AUDIT_NOT_RUN);
expect(audit).not.toMatch(/skip dispatch/);
expect(audit).toContain('**HIGH-impact plan-file discrepancies** trigger AskUserQuestion');
expectMentions(audit, [['never', 'fallback', 'question']], 'audit');
const shipCtx = { ...ctx, skillName: 'ship', tmplPath: 'ship/SKILL.md.tmpl' };
expectMentions(generatePlanCompletionAuditShip(shipCtx), [['do not', 'another', 'search']], 'generatePlanCompletionAuditShip(shipCtx)');
const persist = skill.slice(skill.indexOf('### 2. Fill the record')).replace(/\s+/g, ' ');
});
// INV-1 consumers: an outside review that reports `unverified` or
// `unavailable` is missing coverage in /ship's readiness note, its PR body
// and /review's completion, never a pass.
test('ship and review count unverified or unavailable outside reviews as missing coverage', () => {
const read = (rel: string) => readFileSync(join(import.meta.dir, '..', rel), 'utf-8').replace(/\s+/g, ' ');
for (const rel of ['ship/SKILL.md', 'ship/sections/pr-body.md', 'review/SKILL.md']) {
expect(read(rel)).toMatch(/`unverified` or `unavailable` is (listed as )?missing coverage[^.]*never (as )?(a )?pass/i);
}
});
test('testing specialist treats negative assertions as decisions and catches ones a rename emptied', () => {
const specialist = between(readFileSync(join(root, 'review/specialists/testing.md'), 'utf8'), '### Negative Assertions', '\n### ');
expectTokens(specialist, ['not.toContain', 'assertNotIn'], 'testing specialist');
expectMentions(compact(specialist), [
['new', 'absent', 'removal', 'not coverage'],
['authorizes', 'removal'],
['rename', 'negative assertion', 'passes forever'],
['grep', 'negative assertions', 'old value'],
], 'testing specialist');
});
test('security specialist checks authorization paths a route-guard read misses', () => {
const auth = compact(between(readFileSync(join(root, 'review/specialists/security.md'), 'utf8'), '### Auth & Authorization Bypass', '\n### '));
expectMentions(auth, [
['error', 'renderer', 'outside', 'guard'],
['ownership', 'role', 'downgrade'],
['list', 'detail', 'download', 'separately'],
['session', 'before', 'validating', 'callback'],
], 'security specialist');
});
// Gate Integrity (docs/designs/HONEST_WORK_GATE_INTEGRITY.md W2): the category
// sits in the core CRITICAL pass every diff size gets, its findings reach a
// human through one four-option question, and the record carries identity
// fields a later reader can act on. Tokens and order only; the prose is free.
test('Gate Integrity is a Pass 1 category that is ASK-only and keeps the three levels apart', () => {
const checklist = readFileSync(join(root, 'review/checklist.md'), 'utf8');
const pass1 = between(checklist, '### Pass 1 — CRITICAL', '### Pass 2 — INFORMATIONAL');
expect(pass1).toContain('#### Gate Integrity');
expect(between(checklist, '**Two-pass review:**', '**Output format:**')).toMatch(/Pass 1 \(CRITICAL\):[^\n]*Gate Integrity/);
const category = compact(between(pass1, '#### Gate Integrity'));
expectTokens(category, ['GATE_SUMMARY:', 'RH-1', 'RH-3', 'RH-4', 'RH-12', 'RH-13', 'RH-14', 'RH-15', 'RH-16', 'RH-2', 'RH-5',
'**Listed**', '**Read**', '**Finding**', 'gstack-decision-search --query ""'], 'Gate Integrity category');
expectMentions(category, [
['ask', 'never', 'auto-fix'],
['groups', 'one decision'],
['confidence', 'identification', 'not justification'],
['citations', 'never', 'remove', 'finding'],
['data', 'never', 'instructions', 'actor', 'reason'],
], 'Gate Integrity category');
const heuristic = between(checklist, '## Fix-First Heuristic', '## Suppressions');
const askColumn = heuristic.slice(heuristic.indexOf('ASK (needs human judgment):'));
expect(askColumn).toContain('Gate Integrity');
expect(between(checklist, '## Severity Classification', '## Fix-First Heuristic')).toMatch(/└─ Gate Integrity/);
});
test('the eval-threshold suppression no longer removes a relaxed gate from the human question', () => {
const suppressions = compact(between(readFileSync(join(root, 'review/checklist.md'), 'utf8'), '## Suppressions'));
expect(suppressions).toContain('Eval threshold changes');
const line = suppressions.slice(suppressions.indexOf('Eval threshold changes'), suppressions.indexOf('Harmless no-ops'));
expectMentions(line, [['not', 'suppress', 'gate integrity'], ['citation', 'never', 'removes', 'finding']], 'threshold suppression');
expectTokens(line, ['verified', 'unverified'], 'threshold suppression');
});
test('review runs the gate scan before slop, carries the listing into Step 4 and asks the four gate options in order', () => {
expectOrdered(skill, ['## Step 3.5: Diff scans', '{{GATE_SCAN_BLOCK}}', 'bun run slop:diff', '## Step 3.6:',
'## Step 4: Critical pass', '### Step 5c:', 'review-gate-disposition',
'A) Restore the gate', 'B) Keep — justified', 'C) Keep — other reason', 'D) Leave open for a later human',
'### Step 5d:', '## Step 5.8:'], 'review skill');
expect(skill.match(/\{\{GATE_SCAN_BLOCK\}\}/g)).toHaveLength(1);
expectTokens(between(skill, '## Step 4: Critical pass', '### Shared-code opportunities'), ['GATE_SUMMARY:', '`gate`', '`gate_id`'], 'Step 4 gate carry');
const ask = compact(between(skill, '### Step 5c:', '### Step 5d:'));
expect(ask).toContain('');
expectMentions(ask, [
['spawned', 'headless', 'd', 'recommended'],
['auto-chosen', 'never', 'disposition'],
], 'Step 5c gate question');
for (const action of ['`restored`', '`kept`', '`open`']) expect(ask).toContain(action);
});
test('review persists gate identity fields, counts open findings and reports one Gate edits line', () => {
const record = compact(between(skill, '### 2. Fill the record', '### Report the final review'));
expectTokens(record, ['`gate`', '`gate_id`', '`reason`', '`kept`', '`restored`', '`open`', '`actor`', 'gstack-session-kind'], 'review record');
expectMentions(record, [
['never', 'hunk text'],
['`open`', 'count', '`issues_found`'],
['logger', 'stamps', '`actor`'],
], 'review record');
const report = compact(between(skill, '### Report the final review', '{{LEARNINGS_LOG}}'));
expectOrdered(report, ['`Gate edits: none detected (', '`Gate edits: N listed, M read, K findings —', '`Gate edits: partial (', '`Gate edits: UNAVAILABLE —'], 'final report');
expectMentions(report, [['unavailable', 'never', 'none detected']], 'final report');
});
test('review suppressions allow a clean result and skip common false positives but never suppress missing validation or a missing await', () => {
const suppressions = between(readFileSync(join(root, 'review/checklist.md'), 'utf8'), '## Suppressions');
expectMentions(compact(suppressions), [['zero findings', 'valid']], 'suppressions');
expectTokens(suppressions, ['"Function too long"', '"Possible null dereference"', '"N+1 query"', '"Hardcoded value"', '`Math.random()`'], 'suppressions');
expectAbsent(suppressions, [/missing input validation/i, /missing await/i], 'suppressions');
});