mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
The fake CLI prints a startup marker and the runner waits for it instead of the fixed 8 s startup sleep (8.6 s -> 0.9 s locally). eng-semantic-terminal's sleeping registration cases went with C; plan-count-timeout keeps the fixed wait because it asserts deadline behavior.
52 lines
4.0 KiB
TypeScript
52 lines
4.0 KiB
TypeScript
/** Exercise preceding-call context through the real PTY/native capture loop, without a provider. */
|
||
import {expect,test} from 'bun:test';
|
||
import fs from 'node:fs';
|
||
import os from 'node:os';
|
||
import path from 'node:path';
|
||
import {pathToFileURL} from 'node:url';
|
||
|
||
test.skipIf(process.platform === 'win32')('native issue callbacks receive earlier calls, never the current or future call', async()=>{
|
||
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'plan-count-history-'));
|
||
const fake=path.join(dir,'fake-claude'), worker=path.join(dir,'worker.ts');
|
||
const report=path.join(dir,'report.md'), resultFile=path.join(dir,'result.json'), seen=path.join(dir,'history.jsonl');
|
||
const runner=pathToFileURL(path.join(import.meta.dir,'helpers/claude-pty-runner.ts')).href;
|
||
fs.writeFileSync(fake, `#!${process.execPath}\n`+String.raw`
|
||
import fs from 'node:fs'; import path from 'node:path';
|
||
const sessionId='history-fixture';
|
||
const dir=path.join(process.env.CLAUDE_CONFIG_DIR,'projects',sessionId);
|
||
fs.mkdirSync(dir,{recursive:true}); const file=path.join(dir,sessionId+'.jsonl');
|
||
const native=(role,content,timestamp,extra={})=>fs.appendFileSync(file,JSON.stringify({
|
||
cwd:process.cwd(),sessionId,isSidechain:false,timestamp,message:{role,content},...extra,
|
||
})+'\n');
|
||
let sent=false;process.stdin.setRawMode?.(true);
|
||
process.stdin.on('data',()=>{
|
||
if(sent)return;sent=true;const now=Date.now();
|
||
for(const [i,id] of ['first','second'].entries()){
|
||
const q={header:'Finding',question:'Fix issue '+id+'?',options:[{label:'Fix it'},{label:'Keep it'}],multiSelect:false};
|
||
native('assistant',[{type:'tool_use',id,name:'AskUserQuestion',input:{questions:[q]}}],new Date(now-1000+i*100).toISOString());
|
||
native('user',[{type:'tool_result',tool_use_id:id,content:'Answered.'}],new Date(now-950+i*100).toISOString(),{toolUseResult:{answers:{[q.question]:q.options[0].label}}});
|
||
}
|
||
fs.writeFileSync(process.env.PROBE_PLAN,'# Reviewed plan\n\n## GSTACK REVIEW REPORT\n\n| Review | Status | Findings |\n|---|---|---|\n| DX Review | clean | two resolved |\n\nVERDICT: DX CLEARED — eng review required\n\nNO UNRESOLVED DECISIONS\n');
|
||
const text='DX review complete. Two findings resolved interactively.\n\nPlan written to: '+String.fromCharCode(96)+process.env.PROBE_PLAN+String.fromCharCode(96);
|
||
native('assistant',[{type:'text',text}],new Date(Date.now()+5).toISOString());
|
||
process.stdout.write('●'+text.replace(/ /g,'')+'\nCrunched ·done\n❯ ');
|
||
});process.stdin.resume();
|
||
process.stdout.write('PTY_READY:history\x1b[2J\x1b[H');
|
||
`);
|
||
fs.chmodSync(fake,0o755);
|
||
fs.writeFileSync(worker,`import fs from 'node:fs';\nimport {runPlanSkillCounting} from ${JSON.stringify(runner)};\n`+
|
||
`const result=await runPlanSkillCounting({skillName:'plan-devex-review',slashCommand:'/plan-devex-review',followUpPrompt:'# History fixture',startupReadyMarker:'PTY_READY:history',expectedPlanPath:${JSON.stringify(report)},isLastStep0AUQ:()=>false,isReviewAUQ:(fp,prior)=>{fs.appendFileSync(${JSON.stringify(seen)},JSON.stringify({current:fp.nativeCall.toolUseId,prior:prior?.map(c=>c.toolUseId)??null})+'\\n');return true;},reviewCountCeiling:8,timeoutMs:33000,env:{PROBE_PLAN:${JSON.stringify(report)}}});\n`+
|
||
`fs.writeFileSync(${JSON.stringify(resultFile)},JSON.stringify(result));\n`);
|
||
const child=Bun.spawn([process.execPath,worker],{env:{...process.env,EVALS_HERMETIC:'1',EVALS_RUN_ID:'',BROWSE_TERMINAL_BINARY:fake},stdout:'pipe',stderr:'pipe'});
|
||
const timer=setTimeout(()=>child.kill('SIGKILL'),35000);
|
||
try{
|
||
const [code,stdout,stderr]=await Promise.all([child.exited,new Response(child.stdout).text(),new Response(child.stderr).text()]);
|
||
expect(code,stdout+stderr).toBe(0);
|
||
const obs=JSON.parse(fs.readFileSync(resultFile,'utf8'));
|
||
expect(obs.outcome).toBe('completion_summary');expect(obs.reviewCount).toBe(2);
|
||
expect(fs.readFileSync(seen,'utf8').trim().split('\n').map(line=>JSON.parse(line))).toEqual([
|
||
{current:'first',prior:[]},{current:'second',prior:['first']},
|
||
]);
|
||
} finally {clearTimeout(timer);child.kill('SIGKILL');fs.rmSync(dir,{recursive:true,force:true});}
|
||
},40000);
|