Files
gstack/test/ceo-mode-preference-al.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

141 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {afterAll, beforeAll, expect, test} from 'bun:test';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {spawnSync} from 'node:child_process';
import {getQuestion} from '../scripts/question-registry';
import {E2E_TOUCHFILES} from './helpers/touchfiles-data';
import {CARVE_GUARDS} from './helpers/carve-guards';
const root=path.resolve(import.meta.dir,'..');
const temp=fs.mkdtempSync(path.join(os.tmpdir(),'gstack-ceo-mode-preference-'));
function section(s:string){
const match=s.match(/^### 0[A-Z]\. Mode Selection\n([\s\S]*?)(?=^### |$(?![\s\S]))/m);
if(!match)throw new Error('CEO Mode Selection section missing');
return match[1]!;
}
const rendered=new Map<string,string>();
const env=(state:string)=>({...process.env,GSTACK_HOME:state,GSTACK_STATE_ROOT:state});
beforeAll(()=>{
for(const host of ['claude','codex']){
const out=path.join(temp,host),state=path.join(temp,'render-state');
const result=spawnSync(process.execPath,['run','scripts/gen-skill-docs.ts','--host',host,'--out-dir',out],{cwd:root,env:env(state),encoding:'utf8',timeout:120_000});
if(result.status!==0)throw new Error(result.stderr||result.stdout);
rendered.set(host,fs.readFileSync(path.join(out,host==='claude'?'plan-ceo-review':'.agents/skills/gstack-plan-ceo-review','SKILL.md'),'utf8'));
}
},120_000);
afterAll(()=>fs.rmSync(temp,{recursive:true,force:true}));
function modeId(document:string){
const ids=[...section(document).matchAll(/`question_id=([^`]+)`/g)].map(m=>m[1]!);
expect(ids).toHaveLength(1);return ids[0]!;
}
function tuning(document:string){
return document.split('## Question Tuning (skip entirely if')[1]!.split('\n## ')[0]!;
}
function renderedCheck(host:string,id:string){
const match=tuning(rendered.get(host)!).match(/`(printf '%s' "<question summary>" \| ([^`]+)\/gstack-question-preference --check "<id>" --summary-stdin)`/)!;
expect(match).not.toBeNull();
expect(match[2]).toBe(host==='claude'?'~/.claude/skills/gstack/bin':'$GSTACK_BIN');
const quote=(value:string)=>"'"+value.replace(/'/g,"'\\''")+"'";
// Run the rendered command, substituting its documented fields and mapping
// the host's installed executable location to this isolated checkout.
return match[1]!.replace('<question summary>','Select the CEO review mode for the current plan.')
.replace('"<id>"',quote(id))
.replace(match[2]!+'/gstack-question-preference',quote(path.join(root,'bin/gstack-question-preference')));
}
function checkWithPreference(host:string,preference?:string,writeId?:string){
const id=modeId(rendered.get(host)!),state=fs.mkdtempSync(path.join(temp,'state-'));
const run=(args:string[],input?:string)=>spawnSync(path.join(root,'bin/gstack-question-preference'),args,{cwd:root,env:env(state),input,encoding:'utf8',timeout:30_000});
if(preference){const written=run(['--write',JSON.stringify({question_id:writeId??id,preference,source:'plan-tune'})]);expect(written.status).toBe(0);}
const result=spawnSync('bash',['-c',renderedCheck(host,id)],{cwd:root,env:env(state),encoding:'utf8',timeout:30_000});
expect(result.status).toBe(0);return {id,result,run};
}
test('source and both isolated host renders bind the shared check, marker and log to the registered mode identity',()=>{
const source=fs.readFileSync(path.join(root,'plan-ceo-review/SKILL.md.tmpl'),'utf8');
for(const document of [source,...rendered.values()]){
const id=modeId(document),s=section(document);
expect(getQuestion(id)).toMatchObject({id:'plan-ceo-review-mode',skill:'plan-ceo-review',category:'routing',door_type:'two-way'});
const routing=s.slice(s.indexOf('3. Resolve that recommendation'),s.indexOf('4. **Mode handoff:**')).replace(/\s+/g,' ');
expect(routing).toContain('check `question_id=plan-ceo-review-mode` through the preamble');
expect(routing).toContain('A check that exits 0 with `AUTO_DECIDE` selects the recommendation');
expect(routing).toContain('go to the automatic handoff in step 4');
expect(routing).toContain('When tuning is false, omit the lookup');
const handoff=s.split('**Mode handoff:**')[1]!;
expect(handoff).toContain('`'+id+': AUTO_DECIDE`');
expect(handoff).toContain('Auto-decided review mode → <selected mode> (your preference)');
const asked=routing.split('Without that successful check,')[1]!;
expect(asked).toContain('offer all four modes in one AskUserQuestion');
expect(asked).toContain('**STOP for the answer**');
expect(asked).toContain('When `QUESTION_TUNING: true`');
expect(asked).toContain('`<gstack-qid:'+id+'>`');
const logging=handoff.split('Record mode provenance after the handoff')[1]!.split('If 0D')[0]!.replace(/\s+/g,' ');
expect(logging).toContain('no question log because none was asked');
expect(logging).toContain('`'+id+'`, `auto_decided: true`');
expect(logging).toContain('`auto_decided: false`, including the question ID only when `QUESTION_TUNING: true`');
expect(s.indexOf('3. Resolve that recommendation')).toBeGreaterThanOrEqual(0);
expect(s.indexOf('4. **Mode handoff:**')).toBeGreaterThan(s.indexOf('3. Resolve that recommendation'));
expect(handoff).toContain('After selection');
expect(handoff).toContain('send brief chat before tools or further questions');
expect(s.slice(0,s.indexOf('4. **Mode handoff:**'))).not.toMatch(/\blog (?:with|that ID)\b/);
expect(handoff.indexOf('Record mode provenance after the handoff')).toBeGreaterThan(handoff.indexOf('- Other selections:'));
expect(handoff.indexOf("Follow the selected mode's route:")).toBeGreaterThan(handoff.indexOf('Record mode provenance after the handoff'));
expect(s).not.toContain('plan-ceo-review-mode-selection');
}
for(const document of rendered.values()){
expect(tuning(document)).toContain('<gstack-qid:{question_id}>');
expect(tuning(document)).toContain('"question_id":"<id>"');
}
});
test('both rendered canonical checks actually honor a stored never-ask preference',()=>{
for(const host of rendered.keys()){
const {result,run}=checkWithPreference(host,'never-ask');
expect(result.stdout.trim()).toBe('AUTO_DECIDE');
const wrong=run(['--check','plan-ceo-review-mode-selection','--summary-stdin'],'Select the CEO review mode.');
expect(wrong.status).toBe(0);expect(wrong.stdout.trim()).toBe('ASK_NORMALLY');
}
});
test('absent, always-ask and foreign preferences do not authorize either host to select a mode',()=>{
for(const host of rendered.keys()){
for(const [preference,id] of [[undefined,undefined],['always-ask',undefined],['never-ask','plan-design-review-mode']] as const){
expect(checkWithPreference(host,preference,id).result.stdout.trim()).toBe('ASK_NORMALLY');
}
}
});
test('only an explicit user selection or enabled successful mode check bypasses asking',()=>{
for(const document of rendered.values()){
const s=section(document),q=tuning(document);
expect(s).toContain('An explicit choice skips steps 2–3');
const routing=s.slice(s.indexOf('3. Resolve that recommendation'),s.indexOf('4. **Mode handoff:**')).replace(/\s+/g,' ');
expect(routing).toContain('When `QUESTION_TUNING: true`, first check `question_id=plan-ceo-review-mode` through the preamble');
expect(routing).toContain('When tuning is false, omit the lookup');
expect(routing).toContain('A check that exits 0 with `AUTO_DECIDE` selects the recommendation');
expect(s).toContain('**STOP for the answer**');
expect(document).toContain('Question Tuning (skip entirely if `QUESTION_TUNING: false`)');
expect(q).toContain('`AUTO_DECIDE` means choose the recommended option');
expect(q).toContain('Auto-decided [summary] → [option] (your preference). Change with /plan-tune.');
expect(q).toContain('`ASK_NORMALLY` means ask.');
expect(s).toContain('Selecting a mode does not approve changes');
expect(document.replace(/\s+/g,' ')).toContain('With no required choice, or after those choices settle, go to 0E');
expect(s.replace(/\s+/g,' ')).toContain('Preserve 0D approvals and ask about each proposed addition or cut');
expect(s).toContain('offer all four modes in one AskUserQuestion');
expect(s).toContain("using step 2's recommendation");
expect(s).toContain('For >15 planned changed files, recommend SCOPE REDUCTION');
expect(document).toContain('more than 8 files or more than 2 new classes/services');
expect(s.replace(/\s+/g,' ')).toContain('ask about each proposed addition or cut, including those prompted by file-count thresholds');
expect(s.replace(/\s+/g,' ')).toContain('Count distinct planned file additions, edits and deletions');
expect(s.replace(/\s+/g,' ')).toContain('mark estimated counts as estimates');
expect(s).toContain('These modes differ in kind, not coverage; do NOT score completeness');
expect(document).toContain('Note: options differ in kind, not coverage — no completeness score.');
}
});
test('the new render/runtime regression belongs to the existing auto-decide owner',()=>{
expect(Object.entries(E2E_TOUCHFILES).filter(([,v])=>v.includes('test/ceo-mode-preference-al.test.ts')).map(([k])=>k)).toEqual(['auto-decide-preserved']);
});
test('rendered mode contract stays within the existing canonical skeleton cap',()=>{
// --out-dir changes section-link roots only. Undo that output-location
// substitution before measuring the same canonical bytes as parity-suite.
const out=path.join(temp,'claude').replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
const canonical=rendered.get('claude')!.replace(new RegExp(out+'/([^\\s)`"\'*]+/sections/)','g'),(_m,p1)=>'~/.claude/skills/gstack/'+p1);
const cap=Object.values(CARVE_GUARDS).find(g=>g.skill==='plan-ceo-review')!.maxSkeletonBytes;
expect(Buffer.byteLength(canonical)).toBeLessThanOrEqual(cap);
});