Files
gstack/test/outside-voice-routing.test.ts
T
Garry Tan dcaea52800 v1.91.7.0 feat: add functional QA and pre-publication docs checks (#2983)
* feat: add surface-aware exploratory QA and ship documentation gates

* test: preserve delegated QA setup authority after main integration

* fix(qa): clarify exploration order and preserve report artifacts

* test(qa): follow the shared setup reference directly

* refactor(ship): make verification and recovery routes explicit

* test(ship): align evidence and review guards with explicit routes

* fix(workflows): clarify ship recovery and functional QA evidence

* fix(workflows): clarify approval recovery and full QA coverage

* refactor(workflows): order review transactions and clarify ship state

* fix(ship): clarify final verification and fail closed at publication

* fix(evals): attribute native atomic documentation writes

* fix(ship): clarify recovery and documentation lifecycle guidance

* fix(test): preserve observed native placeholder styling in CI

* fix(codex): report watchdog timeouts without a process-exit race

* Checkpoint functional QA implementation and workflow validation repairs

* Fix documentation and shared-review fixture contracts

* docs: clarify judge reuse and evaluation supervision

* test: align review evidence and selected case contracts

* test: verify append-only documentation checkpoints and recovery

* fix: qualify QA workflows and CI validation repairs

* fix: launch shared-libs fixture scripts on Windows

* fix: qualify QA deadlines, fixture isolation, and shard cleanup

* fix: preserve qualified QA and cancellation repairs

* fix: enforce functional fixture authority and share strict event decoding

* fix: retain free-test evidence and explain recovery

* fix: reject malformed native evidence after decoder consolidation

* test: use reliable capture for telemetry privacy filters

* test: refresh measured quick coverage and document validation costs

* Fix native fixture receipts and preserve VM validation evidence

* Align negative judge controls with upstream clarity policy

* Fix report-only QA preparation and public evidence handling

* Clarify QA-only preparation and current-report preservation

* Stream Ship quality judgments with an explicit 64k response contract

* Validate compact judge reasoning locally with supported wire schema

* Align functional QA fixture instructions with evidence acceptance

* Bind native browser diagnostics to execution evidence and align review verdicts

* Preserve native diagnostic line boundaries

* Serialize functional QA evidence from native captures

* Keep large QA evidence fixture payload out of Windows argv
2026-09-29 06:07:35 -07:00

150 lines
7.6 KiB
TypeScript

/** Outside reviews follow harness identity, including lazily loaded workflows. */
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import { mkdtempSync, readFileSync, readdirSync, existsSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { ALL_HOST_CONFIGS } from '../hosts';
import { ALL_MODEL_NAMES } from '../scripts/models';
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
import { outsideVoiceFor, outsideVoiceCommand } from '../scripts/resolvers/outside-voice';
import { RESOLVERS } from '../scripts/resolvers';
import { installOutsideReviewFixture } from './helpers/outside-voice-fixture';
const ROOT = resolve(import.meta.dir, '..');
let output: string;
function skillDir(host: typeof ALL_HOST_CONFIGS[number], skill: string): string {
return host.name === 'claude'
? join(output, skill === 'gstack' ? '' : skill)
: join(output, host.hostSubdir, 'skills', skill === 'gstack' ? 'gstack' : `gstack-${skill}`);
}
function readUnion(dir: string): string {
const sectionDir = join(dir, 'sections');
return readFileSync(join(dir, 'SKILL.md'), 'utf8')
+ (existsSync(sectionDir) ? readdirSync(sectionDir).sort()
.filter(file => file.endsWith('.md'))
.map(file => '\n' + readFileSync(join(sectionDir, file), 'utf8')).join('') : '');
}
beforeAll(() => {
output = mkdtempSync(join(tmpdir(), 'gstack-outside-routing-'));
const generated = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--out-dir', output], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
});
if (generated.exitCode !== 0) throw new Error(generated.stderr.toString());
});
afterAll(() => { if (output) rmSync(output, { recursive: true, force: true }); });
describe('outside-review host and model matrix', () => {
for (const host of ALL_HOST_CONFIGS) {
test(`${host.name}: only its own wrapper is excluded`, () => {
expect(existsSync(join(skillDir(host, 'codex'), 'SKILL.md'))).toBe(host.name !== 'codex');
expect(existsSync(join(skillDir(host, 'claude-code'), 'SKILL.md'))).toBe(host.name !== 'claude');
expect(existsSync(join(skillDir(host, 'claude'), 'SKILL.md'))).toBe(false);
for (const wrapper of ['codex', 'claude-code']) {
if (!existsSync(join(skillDir(host, wrapper), 'SKILL.md'))) continue;
const text = readUnion(skillDir(host, wrapper));
// Keep all three modes reachable, including carved section files.
expect(text).toMatch(/\breview\b/i);
expect(text).toMatch(/\bchallenge\b/i);
expect(text).toMatch(/\bconsult\b/i);
}
});
test(`${host.name}: every model overlay retains the same provider and routing`, () => {
const providers = ALL_MODEL_NAMES.map(model => {
const ctx: TemplateContext = { host: host.name, model, skillName: 'review', tmplPath: 'review/SKILL.md.tmpl', paths: HOST_PATHS[host.name] };
const voice = outsideVoiceFor(ctx);
expect(voice.id).toBe(host.name === 'codex' ? 'claude-code' : 'codex');
expect(voice.skillName).toBe(host.name === 'codex' ? 'claude-code' : 'codex');
const command = outsideVoiceCommand(ctx, { promptFile: '"$PROMPT_FILE"', timeoutMs: 120_000 });
expect(command).toContain(host.name === 'codex' ? 'gstack-claude-code' : 'codex');
if (host.name === 'codex') expect(command).not.toMatch(/\bcodex\s+(?:exec|review)\s/);
return RESOLVERS.OUTSIDE_VOICE_ROUTING(ctx);
});
expect(new Set(providers).size).toBe(1);
});
test(`${host.name}: generic and explicit second-opinion routing is installed`, () => {
const router = readUnion(skillDir(host, 'gstack'));
expect(router).toMatch(/second opinion/i);
expect(router).toContain(host.name === 'codex' ? 'claude-code' : 'codex');
expect(router).toMatch(/explicit/i);
expect(router).toMatch(/(?:do not|never|without).*?(?:substitut|switch|replac)|(?:keep|honor|respect).*?(?:provider|request)/i);
});
}
});
describe('generated automatic review coverage', () => {
const skills = ['office-hours', 'plan-ceo-review', 'plan-eng-review', 'plan-devex-review', 'plan-design-review', 'design-review', 'design-consultation', 'review', 'ship', 'document-release', 'autoplan', 'spec'];
for (const skill of skills) {
test(`Codex /${skill} invokes Claude Code, including lazy sections`, () => {
const host = ALL_HOST_CONFIGS.find(h => h.name === 'codex')!;
const text = readUnion(skillDir(host, skill));
expect(text).toContain('gstack-claude-code');
// Prose about the CLI is permitted; executable codex review commands are not.
expect(shellLines(text).filter(line => /\bcodex\s+(?:exec|review)\b/.test(line))).toEqual([]);
expect(text).toContain('Claude Code');
});
}
test('Codex still omits Review Army specialist dispatch', () => {
const host = ALL_HOST_CONFIGS.find(h => h.name === 'codex')!;
for (const skill of ['review', 'ship']) {
expect(readUnion(skillDir(host, skill))).not.toContain('Step 4.5: Review Army — Specialist Dispatch');
}
});
});
/** Only shell fences count as invocation sites: explanatory prose cannot spawn. */
function shellLines(text: string): string[] {
let inShell = false;
return text.split('\n').filter(line => {
if (/^```(?:bash|sh|shell)\s*$/.test(line)) { inShell = true; return false; }
if (/^```\s*$/.test(line)) { inShell = false; return false; }
return inShell && !/^\s*#/.test(line);
});
}
function templateFiles(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const full = join(dir, entry.name);
if (entry.isDirectory() && entry.name === 'sections') return templateFiles(full);
return entry.isFile() && entry.name.endsWith('.tmpl') ? [full] : [];
});
}
test('automatic workflow templates delegate reviewer commands to shared resolvers', () => {
const skills = ['office-hours', 'plan-ceo-review', 'plan-eng-review', 'plan-devex-review', 'plan-design-review', 'design-review', 'design-consultation', 'design-shotgun', 'review', 'ship', 'document-release', 'autoplan', 'spec'];
const violations: string[] = [];
for (const skill of skills) {
for (const file of templateFiles(join(ROOT, skill))) {
for (const line of shellLines(readFileSync(file, 'utf8'))) {
// /spec's --execute worker implements the filed spec; it is not a reviewer.
if (file === join(ROOT, 'spec', 'sections', 'gate-and-file.md.tmpl') && line.trim() === 'cat "$ARCHIVE_PATH" | (cd "$SPAWN_PATH" && claude -p 2>&1) &') continue;
if (/\bcodex\s+(?:exec|review)\b|\bclaude\s+(?:-p|--print)\b|\bgstack-claude-code\s+--/.test(line)) {
violations.push(`${file.slice(ROOT.length + 1)}: ${line.trim()}`);
}
}
}
}
expect(violations).toEqual([]);
});
for (const host of ['claude', 'codex'] as const) {
test(`${host}: live E2E installs an executable extracted workflow from the actual host render`, () => {
const repo = mkdtempSync(join(tmpdir(), 'gstack-outside-fixture-'));
try {
const dir = installOutsideReviewFixture(output, host, repo, ROOT);
const text = readFileSync(join(dir, 'SKILL.md'), 'utf8');
expect(text).toContain('Step 0: Detect platform and base branch');
expect(text).toContain('Step 3: Get the diff');
expect(text).toContain('Step 4.8: Adversarial review (always-on)');
expect(text).toContain(host === 'codex' ? 'gstack-claude-code' : 'codex exec');
expect(text).not.toContain('## Preamble (run first)');
expect(text.match(/^name:/gm)).toHaveLength(1);
} finally { rmSync(repo, { recursive: true, force: true }); }
});
}