mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
Move every function from scripts/resolvers/review.ts, unchanged, into: - review-dashboard.ts: review dashboard, plan-file review report - plan-gates.ts: approval check, exit-plan-mode gate, plan-file discovery, plan-completion audit/gate (ship + review), plan verification exec - spec-review.ts: both spec review loops, benefits-from, anti-shortcut clause - outside-voice-steps.ts: Codex second opinion, adversarial step, Codex plan review, Codex doc review, disabled-outside record - review-scope.ts: scope drift, cross-review dedup, shared-code reuse review.ts is deleted; index.ts imports the new modules. gen-skill-docs output is byte-identical for every host (--host all). Test imports and source-path references are re-pointed; the two source-text report/gate tests in gen-skill-docs.test.ts become behavioral renders across every consuming skill and host. All 46 touchfile entries that named review.ts now name all five modules, guarded by a recorded selection golden.
77 lines
5.0 KiB
TypeScript
77 lines
5.0 KiB
TypeScript
import { expect, test } from 'bun:test';
|
||
import { readFileSync } from 'node:fs';
|
||
import { generatePlanCompletionGateShip } from '../scripts/resolvers/plan-gates';
|
||
import { generateTestBootstrap } from '../scripts/resolvers/testing';
|
||
import { HOST_PATHS } from '../scripts/resolvers/types';
|
||
|
||
const read = (file: string) => readFileSync(new URL(`../${file}`, import.meta.url), 'utf8');
|
||
const compact = (text: string) => text.replace(/\s+/g, ' ');
|
||
const ctx = { host: 'claude' as const, skillName: 'ship', tmplPath: '', paths: HOST_PATHS.claude };
|
||
|
||
test('ship STOP blocks advancement while retaining the stated repair route', () => {
|
||
const entry = compact(read('ship/SKILL.md.tmpl'));
|
||
expect(entry).toContain('STOP blocks advancement until the stated repair/resume route clears; without one, end this attempt');
|
||
expect(entry).toContain('Answer each AskUserQuestion before continuing');
|
||
const review = compact(read('ship/sections/review-army.md.tmpl'));
|
||
expect(review).toContain('**Dispatched reviewer output missing:** STOP');
|
||
expect(review).toContain('Retain queued fixes and restore coverage');
|
||
expect(review).toContain('**Third fixing cycle reached (`CYCLES >= 3`):** STOP');
|
||
expect(entry).toContain('Routine authorization never waives those gates or their required user decisions');
|
||
});
|
||
|
||
test('a new ship bootstrap choice overrides only the saved decline, not framework selection', () => {
|
||
const ship = generateTestBootstrap(ctx);
|
||
const decline = ship.slice(ship.indexOf('**If BOOTSTRAP_DECLINED**'), ship.indexOf('**If NO ecosystem marker matched:**'));
|
||
expect(compact(decline)).toContain("Step 5's explicit Add tests choice overrides that marker for this invocation only");
|
||
expect(compact(decline)).toContain('continue to runtime detection and B2–B3, including framework approval');
|
||
expect(decline).not.toContain('rm ');
|
||
expect(ship.indexOf('**If ANY existing-test evidence appears**')).toBeLessThan(ship.indexOf('**If BOOTSTRAP_DECLINED**'));
|
||
const qa = generateTestBootstrap({ ...ctx, skillName: 'qa' });
|
||
expect(qa).toContain('**If BOOTSTRAP_DECLINED** appears: Print "Test bootstrap previously declined — skipping." **Skip the rest of bootstrap.**');
|
||
expect(qa).not.toContain("Step 5's explicit Add tests choice");
|
||
});
|
||
|
||
test('an unverified item answered not done enters the existing decision before continuing', () => {
|
||
const gate = generatePlanCompletionGateShip(ctx);
|
||
const exits = compact(gate.slice(gate.indexOf('**Exit conditions:**'), gate.indexOf('**Cap.**')));
|
||
expect(exits).toContain('Any N: STOP and report that item as NOT DONE');
|
||
expect(exits).toContain('Resume only after its required work is verified');
|
||
expect(exits).toContain('no second deferral choice');
|
||
expect(exits).not.toContain('re-running /ship');
|
||
expect(gate).toContain('Per-item confirmation is mandatory');
|
||
expect(gate).toContain('with the user\'s free-text evidence');
|
||
});
|
||
|
||
test('docs reentry distinguishes the initial audit from same-invocation accepted evidence', () => {
|
||
const docs = compact(read('ship/sections/documentation.md.tmpl'));
|
||
const entry = docs.slice(0, docs.indexOf('## Prepare the candidate'));
|
||
expect(entry).toContain('First entry always launches the initial audit');
|
||
expect(entry).toContain('On reentry, reuse only this invocation\'s validated audit or named-risk decision');
|
||
expect(entry).toContain('Reentry never resets the count or authorizes a launch');
|
||
expect(entry).toContain("this invocation's validated audit or named-risk decision");
|
||
expect(entry).toContain('base/input hashes still match');
|
||
expect(entry).toContain('retain its actual status and scope');
|
||
expect(entry).toContain('Otherwise use Blocked recovery, not an unconditional launch');
|
||
expect(docs).toContain('never a third attempt, even after Step 16 changes');
|
||
expect(docs).toContain('never reuse an audit across invocations');
|
||
expect(docs).toContain('Unconfirmed writers, ownership violations, unauthorized Git mutation and redaction/security gates cannot be waived');
|
||
});
|
||
|
||
test('late behavioral repairs rebuild before the docs decision and commit only remaining changes', () => {
|
||
const entry = compact(read('ship/SKILL.md.tmpl'));
|
||
expect(entry).toContain('A range ending at Step 14 does not enter Step 14.5');
|
||
expect(entry).toContain('rebuild and compare again before stage 3 decides documentation freshness');
|
||
const commit = entry.slice(entry.indexOf('### 5. Report, then push'), entry.indexOf('## Step 17:'));
|
||
expect(commit).toContain('left uncommitted after Step 15');
|
||
expect(commit).toContain('never create an empty commit');
|
||
expect(commit).toContain('Preserve unrelated user files');
|
||
});
|
||
|
||
test('the title is prefixed once before the exact scanned value is published', () => {
|
||
const body = compact(read('ship/sections/pr-body.md.tmpl'));
|
||
expect(body).toContain("Use Step 18's `NEW_TITLE` unchanged; its version prefix is already present");
|
||
expect(body).not.toContain('`NEW_TITLE`, prefixed with');
|
||
expect(body).toContain('--title "$NEW_TITLE"');
|
||
expect(body).toContain('the same scanned `NEW_TITLE`');
|
||
});
|