Files
gstack/test/plan-count-history.test.ts
T
garrytan 689ef3094b test: start the plan-count history PTY on its readiness marker (H)
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.
2026-09-29 06:37:56 +00:00

52 lines
4.0 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.
/** 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);