import { expect, test } from 'bun:test'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { findNativeAutoDecision } from './helpers/native-auto-decide'; import { classifyVisible } from './helpers/claude-pty-runner'; import { readPlanCountTranscript, type NativePublicToolEvent } from './helpers/plan-count-transcript'; import { E2E_TOUCHFILES, selectTests } from './helpers/touchfiles'; const captured = JSON.parse(fs.readFileSync(path.join(import.meta.dir,'fixtures/native-auto-decide-ag.json'),'utf8')); const clone = (i=0) => structuredClone(captured.attempts[i]); const verdict = (f=clone()) => findNativeAutoDecision(f.transcript,f.tools,f.options); const ownedMessage = (f:any) => f.transcript.assistantMessages.find((m:any)=>m.sessionId===f.options.sessionId&&m.text.includes('Auto-decided')); const literal = 'Auto-decided review mode → **HOLD SCOPE** (your preference). Change with /plan-tune.'; test('both exact native attempts preserve the asserted preference annotation despite terminal damage',()=>{ for(const [i,attempt] of captured.attempts.entries()){ const actual=verdict(attempt);expect(actual).not.toBeNull();expect(actual!.sessionId).toBe(attempt.options.sessionId); expect(actual!.option).toBe('HOLD SCOPE');expect(actual!.annotation).toContain('(your preference). Change with /plan-tune.'); expect(ownedMessage(attempt).text).toContain(actual!.annotation); if(i===1)expect(attempt.transcript.assistantMessages.some((m:any)=>m.sessionId!==attempt.options.sessionId&&m.text.includes('Auto-decided'))).toBe(true); } expect(classifyVisible('Auto-dcided review mode: HOLD SCOPE. DONE.')).toBeNull(); }); test('native annotation needs one successful post-command load in the owned session',()=>{ const mutations:Array<(f:any)=>void>=[ f=>{f.transcript.status='missing';},f=>{f.transcript.status='error';},f=>{f.options.sessionId='foreign';}, f=>{f.options.commandStartedAt=f.options.now;},f=>{f.options.commandStartedAt=NaN;},f=>{f.options.now=Infinity;}, f=>{f.options.now=f.options.commandStartedAt-1;},f=>{f.options.skillName='other-skill';}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');use.name='Read';}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');f.tools.push({...use});}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');f.tools=f.tools.filter((e:any)=>e.kind!=='result'||e.toolUseId!==use.toolUseId);}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');f.tools.find((e:any)=>e.kind==='result'&&e.toolUseId===use.toolUseId).isError=true;}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');const result=f.tools.find((e:any)=>e.kind==='result'&&e.toolUseId===use.toolUseId);f.tools.push({...result});}, f=>{const use=f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill');f.tools.find((e:any)=>e.kind==='result'&&e.toolUseId===use.toolUseId).timestamp=new Date(f.options.commandStartedAt-1).toISOString();}, f=>{ownedMessage(f).timestamp='invalid';},f=>{ownedMessage(f).timestamp=new Date(f.options.now+1).toISOString();}, f=>{ownedMessage(f).timestamp=new Date(f.options.commandStartedAt-1).toISOString();}, f=>{f.transcript.calls.push({sessionId:f.options.sessionId,toolUseId:'asked',questions:[{header:'Mode',question:'Choose mode?',options:[{label:'A'},{label:'B'}]}],answered:false});}, ]; for(const mutate of mutations){const f=clone();mutate(f);expect(verdict(f)).toBeNull();} const f=clone();f.tools.find((e:any)=>e.kind==='use'&&e.name==='Skill').input.skill='gstack:plan-ceo-review';expect(verdict(f)).not.toBeNull(); }); test('quotes, source introductions, conditions and incomplete template prose are not annotations',()=>{ for(const text of [ `> ${literal}`,` ${literal}`,`\t${literal}`,`"${literal}"`,`\`\`\`text\n${literal}\n\`\`\``, `Example:\n\n${literal}`,`An unproven hypothesis.\n\n${literal}`,`Previous transcript:\n\n${literal}`, `If approved, ${literal}`,literal.replace('(your preference)','(if you approve)'),literal.replace('Auto-decided','Auto-dcided'), literal.replace('Change with /plan-tune.',''),`**Review mode: EXPANSION.**\n\n${literal}`, `Example:\n\n**Review mode: HOLD SCOPE.**\n\n${literal}`, 'Auto-decided