mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-03 01:46:55 +02:00
* 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
100 lines
7.6 KiB
TypeScript
100 lines
7.6 KiB
TypeScript
|
||
import * as fs from 'node:fs';
|
||
import * as path from 'node:path';
|
||
const dir=process.env.CLAUDE_CONFIG_DIR, scenario=process.env.SEED_CASE;
|
||
const cliArgs=process.argv.slice(2), sessionIndex=cliArgs.indexOf('--session-id');
|
||
const sid=sessionIndex>=0?cliArgs[sessionIndex+1]:'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb', cwd=process.cwd();
|
||
const file=path.join(dir,'projects','fixture',sid+'.jsonl');
|
||
const events=path.join(dir,'events.jsonl'), statusFile=path.join(dir,'sessions',process.pid+'.json');
|
||
fs.mkdirSync(path.dirname(file),{recursive:true});fs.mkdirSync(path.dirname(statusFile),{recursive:true});
|
||
const status={pid:process.pid,sessionId:sid,cwd,startedAt:Date.now(),kind:'interactive',entrypoint:'cli',version:'fixture',
|
||
procStart:process.platform==='linux'?fs.readFileSync('/proc/self/stat','utf8').split(') ').pop().split(' ')[19]:'opaque-test-start',
|
||
pidDomain:process.platform==='linux'?'linux:'+fs.readFileSync('/etc/machine-id','utf8').trim()+':'+fs.readlinkSync('/proc/self/ns/pid'):'test-domain'};
|
||
if(scenario==='wrong-pid')status.pid++;
|
||
if(scenario==='wrong-start')status.procStart+='0';
|
||
if(scenario==='wrong-domain')status.pidDomain+='-different';
|
||
if(scenario==='startup-waiting')status.waitingFor='permission prompt';
|
||
fs.writeFileSync(statusFile,JSON.stringify(status));
|
||
fs.writeFileSync(path.join(dir,'launch.json'),JSON.stringify({argv:process.argv.slice(2),planModeHint:process.env.GSTACK_PLAN_MODE??null,planModeForce:process.env.GSTACK_PLAN_MODE_FORCE??null,term:process.env.TERM??null,forceColor:process.env.FORCE_COLOR??null,
|
||
terminalEnv:Object.fromEntries(['CI','TERM','COLORTERM','FORCE_COLOR','NO_COLOR'].map(key=>[key,process.env[key]??null]))}));
|
||
const event=(kind,value)=>fs.appendFileSync(events,JSON.stringify({kind,value,at:Date.now()})+'\n');
|
||
const row=(type,content,stop)=>JSON.stringify({type,sessionId:sid,cwd,message:{role:type,content,stop_reason:stop}})+'\n';
|
||
const text=s=>[{type:'text',text:s}];
|
||
const append=(type,content,stop)=>fs.appendFileSync(file,row(type,content,stop));
|
||
const rule='─'.repeat(120);
|
||
const frame=(s,history='',top=rule,bottom=rule)=>{
|
||
const inputRows=1+(s.match(/\r\n/g)||[]).length;
|
||
const topRow=(process.stdout.rows||40)-inputRows-2;
|
||
process.stdout.write('\x1b[2J\x1b[H'+history+'\x1b['+topRow+';1H'+top+'\r\n❯ '+s+'\r\n'+bottom+'\r\npaste again to expand');
|
||
};
|
||
let input='',seed='',submitted=false;
|
||
process.stdin.setRawMode(true);process.stdin.resume();
|
||
const hint='Try "refactor <filepath>"';
|
||
if(scenario==='startup-prior-conversation')append('user',text('An earlier request'));
|
||
if(scenario==='startup-terminal-placeholder-cursor'){
|
||
const styled=process.env.TERM&&process.env.TERM!=='dumb'&&process.env.FORCE_COLOR!=='0'
|
||
&&(process.env.FORCE_COLOR==='1'||!process.env.CI&&!process.env.NO_COLOR);
|
||
frame(styled?'\x1b[7mT\x1b[27m\x1b[2m'+hint.slice(1)+'\x1b[22m':hint);
|
||
}
|
||
else if(scenario==='startup-ci-placeholder')frame(process.env.CI==='true'&&process.env.FORCE_COLOR!=='1'?hint:'\x1b[2m'+hint+'\x1b[22m');
|
||
else if(scenario==='startup-ci-typed-hint')frame(hint);
|
||
else if(scenario==='startup-placeholder-cursor')frame('\x1b[7mT\x1b[27m\x1b[2m'+hint.slice(1)+'\x1b[22m');
|
||
else if(scenario==='startup-placeholder-unicode')frame('\x1b[2mTry "refactor src/設定.ts"\x1b[22m');
|
||
else if(['startup-placeholder','startup-prior-conversation','startup-missing-styles','startup-waiting','startup-prose-question','startup-permission','startup-fresh-waiting'].includes(scenario))frame('\x1b[2m'+hint+'\x1b[22m');
|
||
else if(scenario==='startup-typed-hint')frame(hint);
|
||
else if(scenario==='startup-partial-dim')frame('\x1b[2mTry \x1b[22m"refactor <filepath>"');
|
||
else frame('');
|
||
if(scenario==='startup-prose-question')process.stdout.write('Which option do you prefer?\r\nA) Full review (recommended)\r\nB) Skip review\r\n');
|
||
if(scenario==='startup-permission')process.stdout.write('Bash command run checks requires permission\r\n');
|
||
process.stdin.on('data',chunk=>{
|
||
input+=chunk.toString();
|
||
if(input.startsWith('\x1b[200~')&&input.endsWith('\x1b[201~')){
|
||
seed=input.slice(6,-6);input='';event('paste',seed);
|
||
frame('[Pasted text #1 +'+(seed.match(/\n/g)||[]).length+' lines]');return;
|
||
}
|
||
if(input==='\r'&&!submitted){
|
||
submitted=true;input='';event('enter',seed);frame('');
|
||
if(scenario==='no-ack')return;
|
||
if(scenario.startsWith('native-paste')){
|
||
const body=scenario==='native-paste-changed'?seed.replace('Keep','Alter'):scenario==='native-paste-fused'?seed+'\n/plan-eng-review':seed;
|
||
let native='\n\n<pasted_content id="1aab">\n'+body+'</pasted_content id="'+(scenario==='native-paste-mismatched'?'1aac':'1aab')+'">\n';
|
||
if(scenario==='native-paste-duplicate')native+=native;
|
||
if(scenario==='native-paste-appended')native+='/plan-eng-review';
|
||
append('user',scenario==='native-paste-block'?text(native):scenario==='native-paste-multiple-blocks'?[...text(native),...text('extra request')]:native);
|
||
}else append('user',text(scenario==='fused'?seed+'\n/plan-eng-review':seed));
|
||
if(scenario==='duplicate')append('user',text(seed));
|
||
if(scenario==='session-switch'){status.sessionId='bbbbbbbb-1111-2222-3333-aaaaaaaaaaaa';fs.writeFileSync(statusFile,JSON.stringify(status));return;}
|
||
if(scenario==='foreign-cwd'){fs.writeFileSync(file,row('user',text(seed)).replace(cwd,cwd+'-other'));return;}
|
||
if(scenario==='pending-tool'||scenario==='completed-tool'||scenario==='question'){
|
||
append('assistant',[{type:'tool_use',id:'call1',name:scenario==='question'?'AskUserQuestion':'Read',input:{}}],'tool_use');
|
||
}
|
||
if(scenario==='status-updating'){fs.writeFileSync(statusFile,'{\"pid\":');setTimeout(()=>fs.writeFileSync(statusFile,JSON.stringify(status)),120);}
|
||
if(scenario==='permission'){status.waitingFor='permission prompt';fs.writeFileSync(statusFile,JSON.stringify(status));}
|
||
setTimeout(()=>{
|
||
if(scenario==='no-end-turn')return;
|
||
if(scenario==='completed-tool')append('user',[{type:'tool_result',tool_use_id:'call1',content:'Read complete'}]);
|
||
append('assistant',text('Draft received; waiting for your skill command.'),'end_turn');event('end_turn',seed);
|
||
if(scenario==='partial')fs.appendFileSync(file,'{"type":');
|
||
// Keep the submitted message visible, as the native CLI does. An older
|
||
// boxed example must never supply the current input's empty state.
|
||
const olderBox=scenario.startsWith('history-')||scenario==='unframed-current'?rule+'\r\n❯ \r\n'+rule+'\r\n':'';
|
||
const history='❯ '+seed.replace(/\n/g,'\r\n')+'\r\n● Draft received; waiting for your skill command.\r\n'+olderBox;
|
||
if(scenario==='history-no-current'){
|
||
process.stdout.write('\x1b[2J\x1b[H'+history+'This was only an example.\r\n');return;
|
||
}
|
||
const current=scenario==='prose-question'?'\r\nWhich option do you prefer?\r\nA) Full review (recommended)\r\nB) Skip review\r\n❯ '
|
||
:scenario.endsWith('multiline-current')?'\r\n keep this draft'
|
||
:scenario.endsWith('typed-current')?'keep this draft':'';
|
||
frame(current,history,scenario.endsWith('missing-current-top')||scenario==='unframed-current'?'':rule,
|
||
scenario.endsWith('missing-current-bottom')||scenario==='unframed-current'?'':scenario==='mismatched-current-rules'?rule.slice(1):rule);
|
||
if(scenario==='stray-prompt-after-current')process.stdout.write('\r❯ keep this later draft');
|
||
},180);return;
|
||
}
|
||
if(/^\/plan-[a-z-]+\r$/.test(input)){
|
||
const command=input;event('slash',command);input='';
|
||
if(scenario==='native-replay')process.emit('gstack-seeded-slash',command);
|
||
if(scenario==='observation-scope-hint')process.stdout.write('\r\n❯ 1. Review changes\r\n 2. Keep current plan\r\nEnter to select\r\n');
|
||
}
|
||
});
|
||
setTimeout(()=>process.exit(0),scenario==='native-replay'?40000:scenario==='observation-scope-hint'?15000:scenario==='wrong-pid'?12000:5000);
|