Files
gstack/test/sideways-review.test.ts
Garry Tanandcapy-ai[bot] ab79e55119 v1.91.78.0 feat(review): plan reality rows, sideways review and cso sweep, investigate recipes (multi-agent wave PR D) (#3119)
* v1.91.70.0 feat(unattended): artifact contract, unattended session kind, findings-derived review log, gate list

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

* v1.91.72.0 feat(autoplan): unattended runner next|submit, pluggable outside voice, review checklists, spec-loop cap

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

* fix(b-runner): atomic lock creation in spend-ledger, judge bundle skips runner-only sections, fixture env spelling

- lib/spend-ledger.ts: create the lock by linking a complete temp file so a
  contender never reads an empty lock and breaks a live owner (CI admitted 20
  of a cap of 8 under contention); unreadable locks break only by mtime age.
- test/helpers/workflow-judge-input.ts: exclude manifest runner_only sections
  (the B4 checklists) from the judge bundle; the judge rates the interactive
  workflow. test/workflow-judge-input.test.ts expects the same set.
- test/helpers/autoplan-run-fixture.ts: spell the runner's session kind
  without the literal the free-suite environment scan rejects.

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

* v1.91.74.0 feat(review): plan reality rows, sideways review and cso sweep, investigate recipes

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

* test: register the investigate carve case and map Reality rows to real headings in the review checklists

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

* test: pin the corrected Reality rows obligation rows

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

* fix(investigate): use allowlisted placeholders in the flake and polluter fences; select the investigate carve case on PTY changes

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-11 02:14:23 +00:00

98 lines
5.5 KiB
TypeScript

/**
* Plan D2: review and cso look sideways. Pins the tokens, not the prose: the
* sideways sweep and the forced-CRITICAL list reach review Step 4 and the
* checklist, the flake-fix number reaches review and investigate from one
* resolver, the review-mode Scope Check carries `Diff read` and `Undeclared
* behavior changes`, /cso adopts the sibling sweep and nothing else, and ship's
* byte-pinned plan-completion section is untouched.
*/
import { describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { FORCED_CRITICAL, generateFlakeFixNumber, generateSidewaysSweep, undeclaredBehaviorLines } from '../scripts/resolvers/sideways';
import type { TemplateContext } from '../scripts/resolvers/types';
const ROOT = path.resolve(import.meta.dir, '..');
const read = (rel: string) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
const ctx = { skillName: 'review', host: 'claude' } as unknown as TemplateContext;
const between = (text: string, from: string, to: string) => {
const a = text.indexOf(from);
expect(a, `missing ${from}`).toBeGreaterThanOrEqual(0);
const b = text.indexOf(to, a + from.length);
return text.slice(a, b < 0 ? undefined : b);
};
describe('sideways resolver', () => {
test('the forced-CRITICAL classes are the three the plan names and the review variant carries them', () => {
expect([...FORCED_CRITICAL]).toEqual(['data loss', 'trust-boundary bypass', 'uncapped spend']);
const review = generateSidewaysSweep(ctx);
expect(review).toContain('worst sibling');
expect(review).toContain('forced CRITICAL');
for (const cls of FORCED_CRITICAL) expect(review.toLowerCase()).toContain(cls);
});
test('the cso variant is the sibling sweep and nothing else', () => {
const cso = generateSidewaysSweep(ctx, ['cso']);
expect(cso).toContain('worst sibling');
expect(cso).not.toMatch(/CRITICAL|data loss|uncapped/);
expect(cso.split('\n').length).toBe(1);
});
test('the flake-fix number is one rule in two sizes', () => {
for (const variant of [['review'], ['investigate']]) {
const text = generateFlakeFixNumber(ctx, variant);
expect(text).toContain('P(fail | regression)');
expect(text).toContain('RH-15');
expect(text).toMatch(/before and after/);
}
});
test('undeclared behavior lines: two Scope Check fields and a rule that ties approval to a full-diff read', () => {
const { fields, rule } = undeclaredBehaviorLines();
expect(fields.split('\n').map(l => l.split(':')[0])).toEqual(['Diff read', 'Undeclared behavior changes']);
expect(rule).toContain('`Diff read: full`');
expect(rule).toContain('`Scope Check: CLEAN`');
});
});
describe('rendered review', () => {
const skill = read('review/SKILL.md');
const step4 = between(skill, '## Step 4: Critical pass (core review)', '### Shared-code opportunities (core pass)');
test('Step 4 carries the sideways sweep with the forced-CRITICAL list and the flake-fix number; the enum-only paragraph is gone', () => {
expect(step4).toContain('**Sideways sweep (every finding; reads code OUTSIDE the diff).**');
expect(step4).toContain('forced CRITICAL');
expect(step4).toContain('P(fail | regression)');
expect(step4).not.toContain('**Enum & Value Completeness requires reading code OUTSIDE the diff.**');
expect(step4).toMatch(/enum value, status, tier or type constant/);
});
test('the checklist has a Sideways sweep section with every shape the plan names and the forced-CRITICAL note in the severity table', () => {
const checklist = read('review/checklist.md');
const sweep = between(checklist, '## Sideways sweep (every finding)', '## Severity Classification');
for (const bullet of ['**Siblings.**', '**Forced-CRITICAL.**', '**A new cache in front of a guarded or metered call.**', '**Imports moved from static to dynamic.**', '**A changed function with parity claims.**', "**Pool queries inside a caller's transaction.**", '**Test-only flake fix.**']) {
expect(sweep).toContain(bullet);
}
for (const cls of FORCED_CRITICAL) expect(sweep.toLowerCase()).toContain(cls);
expect(sweep).toContain('P(fail | regression)');
const severity = between(checklist, '## Severity Classification', '## Fix-First Heuristic');
expect(severity).toContain('Forced-CRITICAL (Sideways sweep)');
expect(severity).toMatch(/└─ Gate Integrity/);
});
test('the review-mode Scope Check carries Diff read and Undeclared behavior changes; ship stays byte-identical', () => {
const section = read('review/sections/plan-completion.md');
const fence = between(section, 'Scope Check: [CLEAN / DRIFT DETECTED / REQUIREMENTS MISSING]', '```');
expect(fence).toContain('Diff read: full (<n> files, <m> hunks) | partial (<unread paths>)');
expect(fence).toContain('Undeclared behavior changes:');
expect(section).toContain('count as approved only with `Diff read: full` recorded');
for (const rel of ['ship/sections/plan-completion.md', 'ship/SKILL.md']) {
expect(read(rel)).not.toContain('Undeclared behavior changes');
}
});
});
describe('rendered cso', () => {
test('Phase 12 adopts the sibling sweep and keeps its own severity vocabulary', () => {
const skill = read('cso/SKILL.md');
const phase12 = between(skill, '### Phase 12', '### Phase 13');
expect(phase12).toContain('severity follows the worst sibling');
expect(phase12).not.toContain('root-cause variants');
expect(phase12).not.toMatch(/forced CRITICAL|Forced-CRITICAL/);
});
});