bostrat-node 1.16.3 → 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bostrat-audit CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import L from"node:fs/promises";import Ze from"node:os";import v from"node:path";var ke=(e,o)=>{if(e==null||e==="")return o;let t=Number(e);return Number.isFinite(t)&&t>=0?t:o},ot=ke(process.env.BOSTRAT_HOOK_FOLD_MS,250);var we=1e3;function U(e){let o=Number(e?.count);return Number.isInteger(o)&&o>1&&e?.tools&&typeof e.tools=="object"?Math.min(o,we):1}function q(e){let o=e||{},t=U(o);if(t===1)return[o];let a=(Array.isArray(o.calls)?o.calls:[]).filter(i=>i&&typeof i=="object").slice(0,t),r={};for(let i of a)i.tool&&(r[i.tool]=(r[i.tool]||0)+1);for(let[i,c]of Object.entries(o.tools))for(let p=r[i]||0;p<Number(c)&&a.length<t;p++)a.push({tool:i});for(;a.length<t;)a.push({tool:null});return a}import{createHash as Ie}from"node:crypto";var xe=new Set(["userId"]);function K(e){return e===null||typeof e!="object"?e===void 0||typeof e=="number"&&!Number.isFinite(e)?"null":JSON.stringify(e):Array.isArray(e)?`[${e.map(t=>K(t===void 0?null:t)).join(",")}]`:`{${Object.keys(e).filter(t=>e[t]!==void 0).sort().map(t=>`${JSON.stringify(t)}:${K(e[t])}`).join(",")}}`}function se(e){let o={};for(let t of Object.keys(e))xe.has(t)||(o[t]=e[t]);return Ie("sha256").update(K(o)).digest("hex")}var re=e=>Number.isFinite(e?.seq);function ie(e){let o={members:0,chained:0,preChain:0,intactFrom:null,intactTo:null,genesis:!1,breaks:[]},t=null,a=!1,r=(i,c)=>{t={seq:i.seq,hash:c},o.intactFrom=i.seq,o.genesis=i.seq===1&&i.prev===null};for(let i of e||[]){if(!re(i))continue;if(o.members++,i.prev===void 0){t&&!a?(o.breaks.push({kind:"unchained",seq:i.seq,eventId:i.id??null}),a=!0):t||o.preChain++;continue}o.chained++;let c=se(i);if(!t||a){a=!1,r(i,c);continue}if(i.seq===t.seq+1){if(i.prev===t.hash){t={seq:i.seq,hash:c};continue}o.breaks.push({kind:"prev-mismatch",seq:i.seq,eventId:i.id??null,expected:t.hash,got:i.prev})}else i.seq>t.seq+1?o.breaks.push({kind:"seq-gap",seq:i.seq,eventId:i.id??null,from:t.seq+1,to:i.seq-1}):o.breaks.push({kind:"seq-repeat",seq:i.seq,eventId:i.id??null,headSeq:t.seq});r(i,c)}return o.intactTo=t?t.seq:null,t||(o.intactFrom=null),o}var A=6e4,Se=120*A,ae=2e3,ce=5e3,$e=2*A,Ee=5e3,_e=2*A,C=5e3,Me={Workflow:360*A,Agent:60*A},Ae=5*A,Oe=10*A,J=80,T=3,qe=["Checkpoint: write a work-in-progress handoff","Resume from .bostrat/handoff/latest.md","Beat: write a","You are a SUBTASK agent working in an isolated","A subtask box you delegated has finished"],X={operator:"strong",orchestrator:"strong","card-answer":"strong",loop:"strong","background-job":"strong","task-continuation":"strong","operator-terminal":"weak","orchestrator-unlogged":"weak","background-launch":"weak","task-continuation-unlogged":"weak",peer:"flagged","nested-session":"flagged","unknown-prompt":"flagged",unrooted:"flagged"},M=e=>String(e??"").replace(/\s+/g," ").trim(),u=e=>e?.ts??0,V=(e,o)=>!!e&&!!o&&(e.startsWith(o.slice(0,J))||o.startsWith(e.slice(0,J))),Te=6,ve=(e,o)=>V(e,o)||e.length>=Te&&o.includes(e.slice(0,J)),je=/^<pasted_content\b[^>]*>\s*/i,G=e=>M(e.payload?.promptHead??e.payload?.text).replace(je,""),Ne=/^<cross-session-message\b/i,Fe=/^<task-notification>/i,Ce=/<tool-use-id>\s*([^<\s]+)\s*<\/tool-use-id>/i;function de(e){let o=e.payload||{},t=U(o),a=q(o)[0],r=a.head||a.path||(o.command?M(o.command):""),i=e.type==="tool.failed"?" [failed]":e.type==="tool.started"?" [never completed]":"";return`${a.tool||"tool"}${r?`: ${r.slice(0,100)}`:""}${i}${t>1?` (+${t-1} more)`:""}`}var De=/\.answered$/,Re=new Set(["loop.created","followup.created"]),Ue=16,ue=new Set(["tool.started","tool.called","tool.failed"]);function Pe(){return{byId:new Map,byDelivery:new Map,byPrompt:new Map,byToolUse:new Map,jobs:new Map}}function Le(e,o){if(!o||typeof o.type!="string")return;o.id&&e.byId.set(o.id,o);let t=o.payload||{};if(o.type==="turn.user")o.source==="orchestrator"&&t.deliveryId&&e.byDelivery.set(t.deliveryId,o),o.source==="hook"&&t.promptId&&!e.byPrompt.has(`${o.boxId}\0${t.promptId}`)&&e.byPrompt.set(`${o.boxId}\0${t.promptId}`,o);else if(o.type==="job.started"&&t.id)e.jobs.set(t.id,o);else if(ue.has(o.type)&&o.source==="hook")for(let a of q(t))a.toolUseId&&!e.byToolUse.has(a.toolUseId)&&e.byToolUse.set(a.toolUseId,o)}function He(e){let o=Pe();for(let t of e)Le(o,t);return o}function le(e,o){let t=e;for(let a=0;t&&a<Ue;a++){let r=t.payload||{},i=t.type;if(t.causationId&&o.byId.has(t.causationId)&&t.causationId!==t.id){t=o.byId.get(t.causationId);continue}if(ue.has(i)){let c=r.promptId?o.byPrompt.get(`${t.boxId}\0${r.promptId}`):null;if(!c)return null;t=c;continue}if(i==="turn.user"){if(t.source==="hook"){if(r.kind==="task-notification"&&r.jobId){let p=o.jobs.get(r.jobId);if(!p)return null;t=p;continue}let c=r.deliveryId?o.byDelivery.get(r.deliveryId):null;if(!c)return null;t=c;continue}return t.source==="orchestrator"?r.loopId||r.producer==="loop"?{origin:"loop",root:t}:{origin:t.actor==="orchestrator"?"orchestrator":"operator",root:t}:t.source==="transcript"?{origin:"operator-terminal",root:t}:null}if(i==="job.started"){let c=r.id?o.byToolUse.get(r.id):null;if(!c)return null;t=c;continue}return De.test(i)||Re.has(i)?{origin:"card-answer",root:t}:i==="peer.message"?{origin:"peer",root:t}:null}return null}function We(e){let o={events:e.length,firstSeq:null,lastSeq:null,headMissing:!1,gaps:0,repeats:0},t=null;for(let r of e)Number.isFinite(r?.seq)&&(t===null?o.firstSeq=r.seq:r.seq>t+1?o.gaps++:r.seq<=t&&o.repeats++,t=r.seq);o.lastSeq=t,o.headMissing=o.firstSeq!==null&&o.firstSeq>1;let a=ie(e);return o.chain={state:a.chained===0?"pre-chain":a.breaks.length?"broken":a.genesis?"intact":"intact-from-mid-log",chained:a.chained,preChain:a.preChain,intactFrom:a.intactFrom,intactTo:a.intactTo,breaks:a.breaks},o}var Be=e=>{switch(e.kind){case"prev-mismatch":return`seq ${e.seq} states a predecessor that is not the line before it \u2014 the line before it changed, or was replaced`;case"seq-gap":return`seq ${e.from}${e.to!==e.from?`\u2013${e.to}`:""} missing between chained lines`;case"seq-repeat":return`seq ${e.seq} appears again after the chain passed it (a line was inserted, or the log was rewound)`;case"unchained":return`seq ${e.seq} carries no chain link after chained lines (an older node resumed this log)`;default:return`${e.kind} at seq ${e.seq}`}};function Ke(e){let o=new Map,t=new Map,a=new Map,r=new Map,i=(s,g,I)=>{s.has(g)||s.set(g,[]),s.get(g).push(I)};for(let s of e)if((s.type==="box.state.changed"||s.type.startsWith("handoff."))&&i(r,s.payload?.box||s.boxId||null,u(s)),s.type==="session.ready"&&([...r.get(s.boxId)||[],...r.get(null)||[]].some(I=>I<=u(s)&&u(s)-I<=_e)||i(a,s.boxId,u(s))),s.type==="turn.user")if(s.source==="orchestrator"){let g=(s.payload?.attachments||[]).map(I=>I?.id).filter(Boolean);i(o,s.boxId,{ts:u(s),head:M(s.payload?.text),files:g,actor:s.actor,usedAt:null})}else s.source==="transcript"&&i(t,s.boxId,{ts:u(s),head:M(s.payload?.text),note:s.payload?.note?.kind||null});let c=new Map,p=new Map,f=new Map;for(let s of e){if(s.type==="turn.user"&&s.source==="orchestrator"&&p.set(s.boxId,!0),s.source!=="hook")continue;if(s.type==="turn.agent"){let x=f.get(s.boxId)||0;x>0?f.set(s.boxId,x-1):p.set(s.boxId,!1);continue}if(s.type!=="turn.user")continue;let g=G(s),I=(()=>{if(s.actor==="peer"||Ne.test(g))return{origin:"peer"};if(Fe.test(g))return{origin:"task-continuation",jobId:g.match(Ce)?.[1]||null};let x=(o.get(s.boxId)||[]).filter(m=>(m.usedAt===null||u(s)-m.usedAt<=ce)&&m.ts>=u(s)-Se&&m.ts<=u(s)+ae&&(ve(m.head,g)||!m.head&&m.files.length>0&&(m.files.some(E=>g.includes(`.bostrat/uploads/${E}`))||u(s)-m.ts<=ce))),$=x[x.length-1];if($)return $.usedAt=u(s),{origin:$.actor==="orchestrator"?"orchestrator":"operator"};if(qe.some(m=>g.startsWith(m)))return{origin:"orchestrator-unlogged"};if((t.get(s.boxId)||[]).find(m=>!m.note&&m.ts>=u(s)-ae&&m.ts<=u(s)+$e&&V(m.head,g)))return{origin:"operator-terminal"};let D=(a.get(s.boxId)||[]).some(m=>m<=u(s)&&u(s)-m<=Ee);return{origin:p.get(s.boxId)||D?"nested-session":"unknown-prompt"}})();c.set(s,I),I.origin==="nested-session"?f.set(s.boxId,(f.get(s.boxId)||0)+1):p.set(s.boxId,!0)}return c}function Ye(e){let o=new Map,t=new Map;for(let a of e){let r=a.payload?.id;if(r)if(a.type==="job.started"){let i={from:(a.payload?.ts??u(a))-C,to:1/0,loggedAt:u(a)};t.set(r,i),o.has(a.boxId)||o.set(a.boxId,[]),o.get(a.boxId).push(i)}else a.type==="job.stopped"&&t.has(r)&&(t.get(r).to=u(a)+C)}return{byBox:o,byId:t}}function Je(e,{trackId:o=null,name:t=null,since:a=0}={}){let r=Ke(e),i=Ye(e),c=new Set;for(let n of e)n.type==="tool.called"&&n.source==="hook"&&c.add(n.boxId);let p=n=>n.type==="tool.called"&&(n.source==="hook"||!(n.provider==="claude"&&c.has(n.boxId)))||n.type==="tool.failed"&&n.source==="hook",f=new Set,s=new Map;for(let n of e)if(n.source==="hook")if(n.type==="tool.called"||n.type==="tool.failed")for(let l of q(n.payload))l.toolUseId&&f.add(l.toolUseId);else(n.type==="turn.agent"||n.type==="session.ready"||n.type==="turn.aborted"||n.type==="turn.interrupted")&&(s.has(n.boxId)||s.set(n.boxId,[]),s.get(n.boxId).push(u(n)));let g=e.length?u(e[e.length-1]):0,I=n=>{if(n.type!=="tool.started"||n.source!=="hook")return null;let l=q(n.payload).filter(b=>b.toolUseId&&!f.has(b.toolUseId));return l.length&&((s.get(n.boxId)||[]).some(b=>b>u(n))||g-u(n)>Oe)?l:null},x={},$={},W={keyed:0,timing:0},D=He(e),m=new Map,E=(n,l,d,b,k,h=1)=>{let j=`${n}\0${d.boxId}\0${b}`,w=m.get(j);return(!w||u(d)-w.to>Ae)&&(w={rule:n,severity:l,trackId:o,track:t,boxId:d.boxId,from:u(d),to:u(d),count:0,note:k,samples:[]},m.set(j,w),R.push(w)),w.to=u(d),w.count+=h,w},R=[],B=new Map,be=n=>(B.has(n)||B.set(n,{open:!1,origin:null,nested:0,soft:null,lastSendHead:null,launch:null}),B.get(n));for(let n of e){let l=be(n.boxId),d=u(n)>=a;if(n.type==="session.foreign"){if(n.payload?.phase==="prompted"&&d){let y=E("nested-session","medium",n,"","a session that is not the box's own was prompted through this box \u2014 typically an agent-launched `claude -p`");y.samples.length<T&&y.samples.push(M(n.payload?.promptHead).slice(0,100))}continue}if(n.type==="turn.user"&&n.source==="hook"){let y=r.get(n);if(y.origin==="nested-session"){if(l.nested++,d){let O=E("nested-session","medium",n,"","a prompt no recorded send accounts for arrived mid-turn or right after a SessionStart \u2014 typically an agent-launched `claude -p`");O.samples.length<T&&O.samples.push(G(n).slice(0,100))}continue}if(l.open=!0,l.origin=y.origin,y.origin==="task-continuation"&&!i.byId.has(y.jobId)&&(l.origin="task-continuation-unlogged"),l.origin==="unknown-prompt"&&d){let O=E("unknown-prompt","medium",n,"","a turn opened by a prompt with no operator, machinery or job record behind it");O.samples.length<T&&O.samples.push(G(n).slice(0,100))}}else if(n.type==="turn.agent"&&n.source==="hook")l.nested>0?l.nested--:l.open=!1;else if(n.type==="turn.aborted"||n.type==="turn.interrupted")l.open=!1;else if(n.type==="turn.user"&&n.source==="orchestrator")l.lastSendHead=M(n.payload?.text),l.soft=n.actor==="orchestrator"?"orchestrator":"operator",l.open=!0,l.origin=l.soft;else if(n.type==="turn.user"&&n.source==="transcript"){let y=M(n.payload?.text);!n.payload?.note&&!V(y,l.lastSendHead)&&(l.soft="operator-terminal")}else n.type==="peer.message"&&(l.soft="peer");let b=I(n);if(!b&&!p(n))continue;let k=b?b.length:n.source==="hook"?U(n.payload):1,h;if(n.payload?.foreignSession)h="nested-session";else if(n.source==="hook"){let y=(i.byBox.get(n.boxId)||[]).some(_=>u(n)>=_.from&&u(n)<=_.to);l.open?h=l.origin:y?h="background-job":l.nested>0?h="nested-session":l.launch&&u(n)<=l.launch?h="background-launch":h="unrooted";let O=(i.byBox.get(n.boxId)||[]).some(_=>_.loggedAt<=u(n)+C);for(let _ of new Set(q(n.payload).map(ne=>ne.tool)))(_==="Workflow"||_==="Agent"&&!O)&&l.open&&X[l.origin]!=="flagged"&&(l.launch=Math.max(l.launch||0,u(n)+Me[_]))}else h=l.soft||"unrooted";let j=n.source==="hook"&&!n.payload?.foreignSession?le(n,D):null;if(j&&(h=j.origin),!d)continue;W[j?"keyed":"timing"]+=k,x[h]=(x[h]||0)+k;let w=n.provider||"unknown";if($[w]||={actions:0,fidelity:n.source==="hook"?"hook":"transcript",identified:!1},$[w].actions+=k,n.source==="hook"&&($[w].fidelity="hook"),n.source==="hook"&&q(n.payload).some(y=>y.toolUseId)&&($[w].identified=!0),b){let y=E("unfinished-calls","low",n,"","calls the CLI began (PreToolUse) and never reported complete or failed \u2014 killed, crashed or restarted mid-call; the tool may have run",k);y.samples.length<T&&y.samples.push(de(n))}if(h==="unrooted"){let y=E("unrooted-actions","high",n,"","tool calls with no open turn, no recorded job and no launch that accounts for them",k);y.samples.length<T&&y.samples.push(de(n))}else if(h==="peer")E("peer-driven-actions","low",n,"","tool calls in a turn another session's message opened",k);else if(h==="nested-session"||h==="unknown-prompt"){let y=m.get(`${h}\0${n.boxId}\0`);y&&(y.actions=(y.actions||0)+k)}}let F={total:0,keyed:0,timing:0,unrooted:0};{let n=new Map,l=new Map;for(let d of e)d.source==="hook"&&(d.type==="turn.user"?l.set(d.boxId,u(d)):(d.type==="turn.agent"||d.type==="turn.aborted"||d.type==="turn.interrupted")&&l.has(d.boxId)&&((n.get(d.boxId)||n.set(d.boxId,[]).get(d.boxId)).push([l.get(d.boxId),u(d)]),l.delete(d.boxId)));for(let[d,b]of l)(n.get(d)||n.set(d,[]).get(d)).push([b,1/0]);for(let d of e){if(d.type!=="code.committed"||d.source!=="hook"||u(d)<a)continue;F.total++;let b=Number(d.payload?.ts??d.ts)||u(d);if(le(d,D))F.keyed++;else if((n.get(d.boxId)||[]).some(([k,h])=>b>=k-C&&b<=h+C))F.timing++;else{F.unrooted++;let k=E("unrooted-commit","medium",d,"","a commit landed in the box's workspace with no turn open and no recorded cause");k.samples.length<T&&k.samples.push(`${String(d.payload?.sha||"").slice(0,7)} ${M(d.payload?.subject).slice(0,80)}`)}}}let S=We(e),oe=Object.values(x).reduce((n,l)=>n+l,0);if(S.headMissing&&oe&&R.push({rule:"log-head-missing",severity:"medium",trackId:o,track:t,boxId:null,from:u(e[0]),to:u(e[0]),count:1,samples:[],note:`the log starts at seq ${S.firstSeq} \u2014 its earlier history is not on this node`}),S.chain.breaks.length){let n=new Map;for(let h of e)Number.isFinite(h?.seq)&&!n.has(h.seq)&&n.set(h.seq,h);let l=S.chain.breaks.filter(h=>h.kind==="prev-mismatch"||h.kind==="seq-repeat"),d=h=>u(n.get(h.seq))||u(e.at(-1)),b=S.chain.breaks[0],k=S.chain.breaks.at(-1);R.push({rule:"chain-broken",severity:l.length?"high":"medium",trackId:o,track:t,boxId:null,from:d(b),to:d(k),count:S.chain.breaks.length,samples:S.chain.breaks.slice(0,T).map(Be),note:`the log's hash chain breaks ${S.chain.breaks.length} time(s) on this node \u2014 advisory: the plane's verified head is the anchor (GET /plane/admin/chain/${o??"<track>"})`})}return{trackId:o,track:t,actions:oe,byOrigin:x,byEvidence:W,byProvider:$,commits:F,findings:R,integrity:S}}var pe={high:0,medium:1,low:2};function fe(e,{days:o=14,now:t=Date.now()}={}){let a=o?t-o*864e5:0,r={days:o||null,tracks:0,actions:0,byOrigin:{},byStrength:{strong:0,weak:0,flagged:0},byEvidence:{keyed:0,timing:0},commits:{total:0,keyed:0,timing:0,unrooted:0},byProvider:{},findings:[],integrity:{logs:0,headMissing:0,withGaps:0,withRepeats:0,chain:{intact:0,intactFromMidLog:0,broken:0,preChain:0}}};for(let i of e||[]){let c=Je(i.events||[],{trackId:i.trackId,name:i.name,since:a});r.integrity.logs++,c.integrity.headMissing&&r.integrity.headMissing++,c.integrity.gaps&&r.integrity.withGaps++,c.integrity.repeats&&r.integrity.withRepeats++;let p=c.integrity.chain?.state;if(p==="intact"?r.integrity.chain.intact++:p==="intact-from-mid-log"?r.integrity.chain.intactFromMidLog++:p==="broken"?r.integrity.chain.broken++:r.integrity.chain.preChain++,!(!c.actions&&!c.findings.length)){r.tracks++,r.actions+=c.actions;for(let f of Object.keys(r.byEvidence))r.byEvidence[f]+=c.byEvidence?.[f]||0;for(let f of Object.keys(r.commits))r.commits[f]+=c.commits?.[f]||0;for(let[f,s]of Object.entries(c.byOrigin))r.byOrigin[f]=(r.byOrigin[f]||0)+s,r.byStrength[X[f]||"flagged"]+=s;for(let[f,s]of Object.entries(c.byProvider))r.byProvider[f]||={actions:0,fidelity:s.fidelity,identified:!1},r.byProvider[f].actions+=s.actions,s.fidelity==="hook"&&(r.byProvider[f].fidelity="hook"),s.identified&&(r.byProvider[f].identified=!0);r.findings.push(...c.findings)}}return r.findings.sort((i,c)=>pe[i.severity]-pe[c.severity]||c.to-i.to),r.ok=!r.findings.some(i=>i.severity==="high"),r}var P=(e,o)=>o?`${(e/o*100).toFixed(e&&e/o<.001?2:1)}%`:"0%",Y=e=>new Date(e).toISOString().slice(0,16).replace("T"," ");function he(e,{limit:o=20}={}){let t=[];t.push(`AGENT ACTION AUDIT \xB7 ${e.days?`last ${e.days}d`:"all history"} \xB7 ${e.actions} action(s) across ${e.tracks} Track(s)`),t.push(""),t.push("Who asked for it");for(let p of["strong","weak","flagged"]){let f=Object.entries(e.byOrigin).filter(([s])=>(X[s]||"flagged")===p).sort((s,g)=>g[1]-s[1]);if(f.length){t.push(` ${p.toUpperCase()} \xB7 ${e.byStrength[p]} (${P(e.byStrength[p],e.actions)})`);for(let[s,g]of f)t.push(` ${s.padEnd(27)} ${String(g).padStart(7)} ${P(g,e.actions)}`)}}t.push("");let a=e.byEvidence||{keyed:0,timing:0};t.push(`Rooted by recorded key \xB7 ${a.keyed} (${P(a.keyed,e.actions)}) \xB7 by timing \xB7 ${a.timing} (${P(a.timing,e.actions)})`);let r=e.commits||{total:0,keyed:0,timing:0,unrooted:0};r.total&&t.push(`Commits \xB7 ${r.total}: ${r.keyed} cite their turn, ${r.timing} inside an open turn, ${r.unrooted} unrooted`),t.push(""),t.push("Evidence by provider");for(let[p,f]of Object.entries(e.byProvider).sort((s,g)=>g[1].actions-s[1].actions))t.push(` ${p.padEnd(10)} ${String(f.actions).padStart(7)} `+(f.fidelity==="hook"?`hook-recorded (turn boundaries known${f.identified?"; calls identified, intent and outcome paired":""})`:"transcript only (agent-written; no turn boundaries)"));t.push("");let i=e.integrity,c=i.chain||{intact:0,intactFromMidLog:0,broken:0,preChain:0};if(t.push(`Log integrity \xB7 ${i.logs} log(s): hash chain intact on ${c.intact} from seq 1 and ${c.intactFromMidLog} from mid-log, broken on ${c.broken}, ${c.preChain} pre-chain (written before the chain); ${i.headMissing} missing their head, ${i.withGaps} with seq gaps, ${i.withRepeats} with repeated seq`),t.push(" (the chain is walked over this node's copy \u2014 advisory; the plane's verified head is the anchor)"),t.push(""),!e.findings.length)t.push("Findings: none.");else{t.push(`Findings \xB7 ${e.findings.length}`);for(let p of e.findings.slice(0,o)){let f=p.to-p.from>A?`${Y(p.from)} \u2192 ${Y(p.to).slice(11)}`:Y(p.from),s=p.actions?`${p.count} prompt(s), ${p.actions} action(s)`:`${p.count}`;t.push(` [${p.severity}] ${p.rule} \xB7 ${p.track||p.trackId}${p.boxId?` \xB7 ${p.boxId}`:""} \xB7 ${f} \xB7 ${s}`),t.push(` ${p.note}`);for(let g of p.samples)t.push(` - ${g}`)}e.findings.length>o&&t.push(` \u2026 ${e.findings.length-o} more (--json for all)`)}return t.push(""),t.push(e.ok?"No action without an origin. The log is agent-writable, so this is evidence, not proof.":"Actions with NO origin were found \u2014 read the [high] findings above."),t.join(`
3
- `)}import Ge from"node:path";import Xe from"node:os";import{EventEmitter as Ve}from"node:events";var ze=new Ve;ze.setMaxListeners(0);var ft=process.env.BOSTRAT_STATE_DIR||Ge.join(process.env.HOME||Xe.tmpdir(),".bostrat");var Qe=/(?<!\\)((?:\\\\)*)\\u0000/g,ge=e=>e.includes("\\u0000"),me=e=>e.replace(Qe,"$1\\ufffd");var H=process.env.BOSTRAT_STATE_DIR||v.join(process.env.HOME||Ze.tmpdir(),".bostrat"),N=process.argv.slice(2),Z=e=>{let o=N.indexOf(e);return o>=0?N[o+1]:null};(N.includes("--help")||N.includes("-h"))&&(console.log("usage: bostrat-audit [--days N | --all] [--track <id-or-name>] [--limit N] [--json]"),process.exit(0));var ee=[];try{ee=JSON.parse(await L.readFile(v.join(H,"tracks.json"),"utf8")).tracks||[]}catch{}var et=new Map(ee.map(e=>[e.id,e.name])),te=new Set([v.join(H,"events")]);for(let e of ee)e.cwd&&te.add(v.join(e.cwd,".bostrat","events"));try{for(let e of await L.readdir(v.join(H,"workspaces")))te.add(v.join(H,"workspaces",e,".bostrat","events"))}catch{}var z=Z("--track"),ye=[];for(let e of te){let o=[];try{o=(await L.readdir(e)).filter(t=>t.endsWith(".ndjson"))}catch{continue}for(let t of o){let a=t.slice(0,-7),r=et.get(a)||null;if(z&&a!==z&&!(r||"").toLowerCase().includes(z.toLowerCase()))continue;let i=[];for(let c of(await L.readFile(v.join(e,t),"utf8")).split(`
4
- `))if(c.trim())try{i.push(JSON.parse(ge(c)?me(c):c))}catch{}i.length&&ye.push({trackId:a,name:r,events:i})}}var tt=N.includes("--all")?0:Number(Z("--days"))||14,Q=fe(ye,{days:tt});console.log(N.includes("--json")?JSON.stringify(Q,null,2):he(Q,{limit:Number(Z("--limit"))||20}));process.exit(Q.ok?0:2);
2
+ import Y from"node:fs/promises";import zt from"node:os";import C from"node:path";var He=(t,o)=>{if(t==null||t==="")return o;let e=Number(t);return Number.isFinite(e)&&e>=0?e:o},Gt=He(process.env.BOSTRAT_HOOK_FOLD_MS,250);var Be=1e3;function q(t){let o=Number(t?.count);return Number.isInteger(o)&&o>1&&t?.tools&&typeof t.tools=="object"?Math.min(o,Be):1}function O(t){let o=t||{},e=q(o);if(e===1)return[o];let i=(Array.isArray(o.calls)?o.calls:[]).filter(n=>n&&typeof n=="object").slice(0,e),r={};for(let n of i)n.tool&&(r[n.tool]=(r[n.tool]||0)+1);for(let[n,c]of Object.entries(o.tools))for(let l=r[n]||0;l<Number(c)&&i.length<e;l++)i.push({tool:n});for(;i.length<e;)i.push({tool:null});return i}import{createHash as Ke}from"node:crypto";var ze=new Set(["userId"]);function X(t){return t===null||typeof t!="object"?t===void 0||typeof t=="number"&&!Number.isFinite(t)?"null":JSON.stringify(t):Array.isArray(t)?`[${t.map(e=>X(e===void 0?null:e)).join(",")}]`:`{${Object.keys(t).filter(e=>t[e]!==void 0).sort().map(e=>`${JSON.stringify(e)}:${X(t[e])}`).join(",")}}`}function he(t){let o={};for(let e of Object.keys(t))ze.has(e)||(o[e]=t[e]);return Ke("sha256").update(X(o)).digest("hex")}var fe=t=>Number.isFinite(t?.seq);function me(t){let o={members:0,chained:0,preChain:0,intactFrom:null,intactTo:null,genesis:!1,breaks:[]},e=null,i=!1,r=(n,c)=>{e={seq:n.seq,hash:c},o.intactFrom=n.seq,o.genesis=n.seq===1&&n.prev===null};for(let n of t||[]){if(!fe(n))continue;if(o.members++,n.prev===void 0){e&&!i?(o.breaks.push({kind:"unchained",seq:n.seq,eventId:n.id??null}),i=!0):e||o.preChain++;continue}o.chained++;let c=he(n);if(!e||i){i=!1,r(n,c);continue}if(n.seq===e.seq+1){if(n.prev===e.hash){e={seq:n.seq,hash:c};continue}o.breaks.push({kind:"prev-mismatch",seq:n.seq,eventId:n.id??null,expected:e.hash,got:n.prev})}else n.seq>e.seq+1?o.breaks.push({kind:"seq-gap",seq:n.seq,eventId:n.id??null,from:e.seq+1,to:n.seq-1}):o.breaks.push({kind:"seq-repeat",seq:n.seq,eventId:n.id??null,headSeq:e.seq});r(n,c)}return o.intactTo=e?e.seq:null,e||(o.intactFrom=null),o}var j=6e4,Ye=120*j,ge=2e3,ye=5e3,Ge=2*j,Je=5e3,Ve=2*j,H=5e3,Xe={Workflow:360*j,Agent:60*j},Ze=5*j,Qe=10*j,Q=80,D=3,et=["Checkpoint: write a work-in-progress handoff","Resume from .bostrat/handoff/latest.md","Beat: write a","You are a SUBTASK agent working in an isolated","A subtask box you delegated has finished"],te={operator:"strong",orchestrator:"strong","card-answer":"strong",loop:"strong","background-job":"strong","task-continuation":"strong","operator-terminal":"weak","orchestrator-unlogged":"weak","background-launch":"weak","task-continuation-unlogged":"weak",peer:"flagged","nested-session":"flagged","unknown-prompt":"flagged",unrooted:"flagged"},N=t=>String(t??"").replace(/\s+/g," ").trim(),f=t=>t?.ts??0,oe=(t,o)=>!!t&&!!o&&(t.startsWith(o.slice(0,Q))||o.startsWith(t.slice(0,Q))),tt=6,ot=(t,o)=>oe(t,o)||t.length>=tt&&o.includes(t.slice(0,Q)),nt=/^<pasted_content\b[^>]*>\s*/i,ee=t=>N(t.payload?.promptHead??t.payload?.text).replace(nt,""),st=/^<cross-session-message\b/i,rt=/^<task-notification>/i,it=/<tool-use-id>\s*([^<\s]+)\s*<\/tool-use-id>/i;function be(t){let o=t.payload||{},e=q(o),i=O(o)[0],r=i.head||i.path||(o.command?N(o.command):""),n=t.type==="tool.failed"?" [failed]":t.type==="tool.started"?" [never completed]":"";return`${i.tool||"tool"}${r?`: ${r.slice(0,100)}`:""}${n}${e>1?` (+${e-1} more)`:""}`}var at=/\.answered$/,ct=new Set(["loop.created","followup.created"]),lt=16,$e=new Set(["tool.started","tool.called","tool.failed"]);function dt(){return{byId:new Map,byDelivery:new Map,byPrompt:new Map,byToolUse:new Map,jobs:new Map}}function ut(t,o){if(!o||typeof o.type!="string")return;o.id&&t.byId.set(o.id,o);let e=o.payload||{};if(o.type==="turn.user")o.source==="orchestrator"&&e.deliveryId&&t.byDelivery.set(e.deliveryId,o),o.source==="hook"&&e.promptId&&!t.byPrompt.has(`${o.boxId}\0${e.promptId}`)&&t.byPrompt.set(`${o.boxId}\0${e.promptId}`,o);else if(o.type==="job.started"&&e.id)t.jobs.set(e.id,o);else if($e.has(o.type)&&o.source==="hook")for(let i of O(e))i.toolUseId&&!t.byToolUse.has(i.toolUseId)&&t.byToolUse.set(i.toolUseId,o)}function pt(t){let o=dt();for(let e of t)ut(o,e);return o}function we(t,o){let e=t;for(let i=0;e&&i<lt;i++){let r=e.payload||{},n=e.type;if(e.causationId&&o.byId.has(e.causationId)&&e.causationId!==e.id){e=o.byId.get(e.causationId);continue}if($e.has(n)){let c=r.promptId?o.byPrompt.get(`${e.boxId}\0${r.promptId}`):null;if(!c)return null;e=c;continue}if(n==="turn.user"){if(e.source==="hook"){if(r.kind==="task-notification"&&r.jobId){let l=o.jobs.get(r.jobId);if(!l)return null;e=l;continue}let c=r.deliveryId?o.byDelivery.get(r.deliveryId):null;if(!c)return null;e=c;continue}return e.source==="orchestrator"?r.loopId||r.producer==="loop"?{origin:"loop",root:e}:{origin:e.actor==="orchestrator"?"orchestrator":"operator",root:e}:e.source==="transcript"?{origin:"operator-terminal",root:e}:null}if(n==="job.started"){let c=r.id?o.byToolUse.get(r.id):null;if(!c)return null;e=c;continue}return at.test(n)||ct.has(n)?{origin:"card-answer",root:e}:n==="peer.message"?{origin:"peer",root:e}:null}return null}function ht(t){let o={events:t.length,firstSeq:null,lastSeq:null,headMissing:!1,gaps:0,repeats:0},e=null;for(let r of t)Number.isFinite(r?.seq)&&(e===null?o.firstSeq=r.seq:r.seq>e+1?o.gaps++:r.seq<=e&&o.repeats++,e=r.seq);o.lastSeq=e,o.headMissing=o.firstSeq!==null&&o.firstSeq>1;let i=me(t);return o.chain={state:i.chained===0?"pre-chain":i.breaks.length?"broken":i.genesis?"intact":"intact-from-mid-log",chained:i.chained,preChain:i.preChain,intactFrom:i.intactFrom,intactTo:i.intactTo,breaks:i.breaks},o}var ft=t=>{switch(t.kind){case"prev-mismatch":return`seq ${t.seq} states a predecessor that is not the line before it \u2014 the line before it changed, or was replaced`;case"seq-gap":return`seq ${t.from}${t.to!==t.from?`\u2013${t.to}`:""} missing between chained lines`;case"seq-repeat":return`seq ${t.seq} appears again after the chain passed it (a line was inserted, or the log was rewound)`;case"unchained":return`seq ${t.seq} carries no chain link after chained lines (an older node resumed this log)`;default:return`${t.kind} at seq ${t.seq}`}};function mt(t){let o=new Map,e=new Map,i=new Map,r=new Map,n=(s,d,m)=>{s.has(d)||s.set(d,[]),s.get(d).push(m)};for(let s of t)if((s.type==="box.state.changed"||s.type.startsWith("handoff."))&&n(r,s.payload?.box||s.boxId||null,f(s)),s.type==="session.ready"&&([...r.get(s.boxId)||[],...r.get(null)||[]].some(m=>m<=f(s)&&f(s)-m<=Ve)||n(i,s.boxId,f(s))),s.type==="turn.user")if(s.source==="orchestrator"){let d=(s.payload?.attachments||[]).map(m=>m?.id).filter(Boolean);n(o,s.boxId,{ts:f(s),head:N(s.payload?.text),files:d,actor:s.actor,usedAt:null})}else s.source==="transcript"&&n(e,s.boxId,{ts:f(s),head:N(s.payload?.text),note:s.payload?.note?.kind||null});let c=new Map,l=new Map,h=new Map;for(let s of t){if(s.type==="turn.user"&&s.source==="orchestrator"&&l.set(s.boxId,!0),s.source!=="hook")continue;if(s.type==="turn.agent"){let k=h.get(s.boxId)||0;k>0?h.set(s.boxId,k-1):l.set(s.boxId,!1);continue}if(s.type!=="turn.user")continue;let d=ee(s),m=(()=>{if(s.actor==="peer"||st.test(d))return{origin:"peer"};if(rt.test(d))return{origin:"task-continuation",jobId:d.match(it)?.[1]||null};let k=(o.get(s.boxId)||[]).filter(b=>(b.usedAt===null||f(s)-b.usedAt<=ye)&&b.ts>=f(s)-Ye&&b.ts<=f(s)+ge&&(ot(b.head,d)||!b.head&&b.files.length>0&&(b.files.some(T=>d.includes(`.bostrat/uploads/${T}`))||f(s)-b.ts<=ye))),y=k[k.length-1];if(y)return y.usedAt=f(s),{origin:y.actor==="orchestrator"?"orchestrator":"operator"};if(et.some(b=>d.startsWith(b)))return{origin:"orchestrator-unlogged"};if((e.get(s.boxId)||[]).find(b=>!b.note&&b.ts>=f(s)-ge&&b.ts<=f(s)+Ge&&oe(b.head,d)))return{origin:"operator-terminal"};let M=(i.get(s.boxId)||[]).some(b=>b<=f(s)&&f(s)-b<=Je);return{origin:l.get(s.boxId)||M?"nested-session":"unknown-prompt"}})();c.set(s,m),m.origin==="nested-session"?h.set(s.boxId,(h.get(s.boxId)||0)+1):l.set(s.boxId,!0)}return c}function gt(t){let o=new Map,e=new Map;for(let i of t){let r=i.payload?.id;if(r)if(i.type==="job.started"){let n={from:(i.payload?.ts??f(i))-H,to:1/0,loggedAt:f(i)};e.set(r,n),o.has(i.boxId)||o.set(i.boxId,[]),o.get(i.boxId).push(n)}else i.type==="job.stopped"&&e.has(r)&&(e.get(r).to=f(i)+H)}return{byBox:o,byId:e}}function yt(t,{trackId:o=null,name:e=null,since:i=0}={}){let r=mt(t),n=gt(t),c=new Set;for(let a of t)a.type==="tool.called"&&a.source==="hook"&&c.add(a.boxId);let l=a=>a.type==="tool.called"&&(a.source==="hook"||!(a.provider==="claude"&&c.has(a.boxId)))||a.type==="tool.failed"&&a.source==="hook",h=new Set,s=new Map;for(let a of t)if(a.source==="hook")if(a.type==="tool.called"||a.type==="tool.failed")for(let p of O(a.payload))p.toolUseId&&h.add(p.toolUseId);else(a.type==="turn.agent"||a.type==="session.ready"||a.type==="turn.aborted"||a.type==="turn.interrupted")&&(s.has(a.boxId)||s.set(a.boxId,[]),s.get(a.boxId).push(f(a)));let d=t.length?f(t[t.length-1]):0,m=a=>{if(a.type!=="tool.started"||a.source!=="hook")return null;let p=O(a.payload).filter($=>$.toolUseId&&!h.has($.toolUseId));return p.length&&((s.get(a.boxId)||[]).some($=>$>f(a))||d-f(a)>Qe)?p:null},k={},y={},v={keyed:0,timing:0},M=pt(t),b=new Map,T=(a,p,u,$,x,g=1)=>{let P=`${a}\0${u.boxId}\0${$}`,I=b.get(P);return(!I||f(u)-I.to>Ze)&&(I={rule:a,severity:p,trackId:o,track:e,boxId:u.boxId,from:f(u),to:f(u),count:0,note:x,samples:[]},b.set(P,I),B.push(I)),I.to=f(u),I.count+=g,I},B=[],V=new Map,Ue=a=>(V.has(a)||V.set(a,{open:!1,origin:null,nested:0,soft:null,lastSendHead:null,launch:null}),V.get(a));for(let a of t){let p=Ue(a.boxId),u=f(a)>=i;if(a.type==="session.foreign"){if(a.payload?.phase==="prompted"&&u){let w=T("nested-session","medium",a,"","a session that is not the box's own was prompted through this box \u2014 typically an agent-launched `claude -p`");w.samples.length<D&&w.samples.push(N(a.payload?.promptHead).slice(0,100))}continue}if(a.type==="turn.user"&&a.source==="hook"){let w=r.get(a);if(w.origin==="nested-session"){if(p.nested++,u){let R=T("nested-session","medium",a,"","a prompt no recorded send accounts for arrived mid-turn or right after a SessionStart \u2014 typically an agent-launched `claude -p`");R.samples.length<D&&R.samples.push(ee(a).slice(0,100))}continue}if(p.open=!0,p.origin=w.origin,w.origin==="task-continuation"&&!n.byId.has(w.jobId)&&(p.origin="task-continuation-unlogged"),p.origin==="unknown-prompt"&&u){let R=T("unknown-prompt","medium",a,"","a turn opened by a prompt with no operator, machinery or job record behind it");R.samples.length<D&&R.samples.push(ee(a).slice(0,100))}}else if(a.type==="turn.agent"&&a.source==="hook")p.nested>0?p.nested--:p.open=!1;else if(a.type==="turn.aborted"||a.type==="turn.interrupted")p.open=!1;else if(a.type==="turn.user"&&a.source==="orchestrator")p.lastSendHead=N(a.payload?.text),p.soft=a.actor==="orchestrator"?"orchestrator":"operator",p.open=!0,p.origin=p.soft;else if(a.type==="turn.user"&&a.source==="transcript"){let w=N(a.payload?.text);!a.payload?.note&&!oe(w,p.lastSendHead)&&(p.soft="operator-terminal")}else a.type==="peer.message"&&(p.soft="peer");let $=m(a);if(!$&&!l(a))continue;let x=$?$.length:a.source==="hook"?q(a.payload):1,g;if(a.payload?.foreignSession)g="nested-session";else if(a.source==="hook"){let w=(n.byBox.get(a.boxId)||[]).some(A=>f(a)>=A.from&&f(a)<=A.to);p.open?g=p.origin:w?g="background-job":p.nested>0?g="nested-session":p.launch&&f(a)<=p.launch?g="background-launch":g="unrooted";let R=(n.byBox.get(a.boxId)||[]).some(A=>A.loggedAt<=f(a)+H);for(let A of new Set(O(a.payload).map(pe=>pe.tool)))(A==="Workflow"||A==="Agent"&&!R)&&p.open&&te[p.origin]!=="flagged"&&(p.launch=Math.max(p.launch||0,f(a)+Xe[A]))}else g=p.soft||"unrooted";let P=a.source==="hook"&&!a.payload?.foreignSession?we(a,M):null;if(P&&(g=P.origin),!u)continue;v[P?"keyed":"timing"]+=x,k[g]=(k[g]||0)+x;let I=a.provider||"unknown";if(y[I]||={actions:0,fidelity:a.source==="hook"?"hook":"transcript",identified:!1},y[I].actions+=x,a.source==="hook"&&(y[I].fidelity="hook"),a.source==="hook"&&O(a.payload).some(w=>w.toolUseId)&&(y[I].identified=!0),$){let w=T("unfinished-calls","low",a,"","calls the CLI began (PreToolUse) and never reported complete or failed \u2014 killed, crashed or restarted mid-call; the tool may have run",x);w.samples.length<D&&w.samples.push(be(a))}if(g==="unrooted"){let w=T("unrooted-actions","high",a,"","tool calls with no open turn, no recorded job and no launch that accounts for them",x);w.samples.length<D&&w.samples.push(be(a))}else if(g==="peer")T("peer-driven-actions","low",a,"","tool calls in a turn another session's message opened",x);else if(g==="nested-session"||g==="unknown-prompt"){let w=b.get(`${g}\0${a.boxId}\0`);w&&(w.actions=(w.actions||0)+x)}}let U={total:0,keyed:0,timing:0,unrooted:0};{let a=new Map,p=new Map;for(let u of t)u.source==="hook"&&(u.type==="turn.user"?p.set(u.boxId,f(u)):(u.type==="turn.agent"||u.type==="turn.aborted"||u.type==="turn.interrupted")&&p.has(u.boxId)&&((a.get(u.boxId)||a.set(u.boxId,[]).get(u.boxId)).push([p.get(u.boxId),f(u)]),p.delete(u.boxId)));for(let[u,$]of p)(a.get(u)||a.set(u,[]).get(u)).push([$,1/0]);for(let u of t){if(u.type!=="code.committed"||u.source!=="hook"||f(u)<i)continue;U.total++;let $=Number(u.payload?.ts??u.ts)||f(u);if(we(u,M))U.keyed++;else if((a.get(u.boxId)||[]).some(([x,g])=>$>=x-H&&$<=g+H))U.timing++;else{U.unrooted++;let x=T("unrooted-commit","medium",u,"","a commit landed in the box's workspace with no turn open and no recorded cause");x.samples.length<D&&x.samples.push(`${String(u.payload?.sha||"").slice(0,7)} ${N(u.payload?.subject).slice(0,80)}`)}}}let _=ht(t),ue=Object.values(k).reduce((a,p)=>a+p,0);if(_.headMissing&&ue&&B.push({rule:"log-head-missing",severity:"medium",trackId:o,track:e,boxId:null,from:f(t[0]),to:f(t[0]),count:1,samples:[],note:`the log starts at seq ${_.firstSeq} \u2014 its earlier history is not on this node`}),_.chain.breaks.length){let a=new Map;for(let g of t)Number.isFinite(g?.seq)&&!a.has(g.seq)&&a.set(g.seq,g);let p=_.chain.breaks.filter(g=>g.kind==="prev-mismatch"||g.kind==="seq-repeat"),u=g=>f(a.get(g.seq))||f(t.at(-1)),$=_.chain.breaks[0],x=_.chain.breaks.at(-1);B.push({rule:"chain-broken",severity:p.length?"high":"medium",trackId:o,track:e,boxId:null,from:u($),to:u(x),count:_.chain.breaks.length,samples:_.chain.breaks.slice(0,D).map(ft),note:`the log's hash chain breaks ${_.chain.breaks.length} time(s) on this node \u2014 advisory: the plane's verified head is the anchor (GET /plane/admin/chain/${o??"<track>"})`})}return{trackId:o,track:e,actions:ue,byOrigin:k,byEvidence:v,byProvider:y,commits:U,findings:B,integrity:_}}var ke={high:0,medium:1,low:2};function xe(t,{days:o=14,now:e=Date.now()}={}){let i=o?e-o*864e5:0,r={days:o||null,tracks:0,actions:0,byOrigin:{},byStrength:{strong:0,weak:0,flagged:0},byEvidence:{keyed:0,timing:0},commits:{total:0,keyed:0,timing:0,unrooted:0},byProvider:{},findings:[],integrity:{logs:0,headMissing:0,withGaps:0,withRepeats:0,chain:{intact:0,intactFromMidLog:0,broken:0,preChain:0}}};for(let n of t||[]){let c=yt(n.events||[],{trackId:n.trackId,name:n.name,since:i});r.integrity.logs++,c.integrity.headMissing&&r.integrity.headMissing++,c.integrity.gaps&&r.integrity.withGaps++,c.integrity.repeats&&r.integrity.withRepeats++;let l=c.integrity.chain?.state;if(l==="intact"?r.integrity.chain.intact++:l==="intact-from-mid-log"?r.integrity.chain.intactFromMidLog++:l==="broken"?r.integrity.chain.broken++:r.integrity.chain.preChain++,!(!c.actions&&!c.findings.length)){r.tracks++,r.actions+=c.actions;for(let h of Object.keys(r.byEvidence))r.byEvidence[h]+=c.byEvidence?.[h]||0;for(let h of Object.keys(r.commits))r.commits[h]+=c.commits?.[h]||0;for(let[h,s]of Object.entries(c.byOrigin))r.byOrigin[h]=(r.byOrigin[h]||0)+s,r.byStrength[te[h]||"flagged"]+=s;for(let[h,s]of Object.entries(c.byProvider))r.byProvider[h]||={actions:0,fidelity:s.fidelity,identified:!1},r.byProvider[h].actions+=s.actions,s.fidelity==="hook"&&(r.byProvider[h].fidelity="hook"),s.identified&&(r.byProvider[h].identified=!0);r.findings.push(...c.findings)}}return r.findings.sort((n,c)=>ke[n.severity]-ke[c.severity]||c.to-n.to),r.ok=!r.findings.some(n=>n.severity==="high"),r}var K=(t,o)=>o?`${(t/o*100).toFixed(t&&t/o<.001?2:1)}%`:"0%",Z=t=>new Date(t).toISOString().slice(0,16).replace("T"," ");function Ie(t,{limit:o=20}={}){let e=[];e.push(`AGENT ACTION AUDIT \xB7 ${t.days?`last ${t.days}d`:"all history"} \xB7 ${t.actions} action(s) across ${t.tracks} Track(s)`),e.push(""),e.push("Who asked for it");for(let l of["strong","weak","flagged"]){let h=Object.entries(t.byOrigin).filter(([s])=>(te[s]||"flagged")===l).sort((s,d)=>d[1]-s[1]);if(h.length){e.push(` ${l.toUpperCase()} \xB7 ${t.byStrength[l]} (${K(t.byStrength[l],t.actions)})`);for(let[s,d]of h)e.push(` ${s.padEnd(27)} ${String(d).padStart(7)} ${K(d,t.actions)}`)}}e.push("");let i=t.byEvidence||{keyed:0,timing:0};e.push(`Rooted by recorded key \xB7 ${i.keyed} (${K(i.keyed,t.actions)}) \xB7 by timing \xB7 ${i.timing} (${K(i.timing,t.actions)})`);let r=t.commits||{total:0,keyed:0,timing:0,unrooted:0};r.total&&e.push(`Commits \xB7 ${r.total}: ${r.keyed} cite their turn, ${r.timing} inside an open turn, ${r.unrooted} unrooted`),e.push(""),e.push("Evidence by provider");for(let[l,h]of Object.entries(t.byProvider).sort((s,d)=>d[1].actions-s[1].actions))e.push(` ${l.padEnd(10)} ${String(h.actions).padStart(7)} `+(h.fidelity==="hook"?`hook-recorded (turn boundaries known${h.identified?"; calls identified, intent and outcome paired":""})`:"transcript only (agent-written; no turn boundaries)"));e.push("");let n=t.integrity,c=n.chain||{intact:0,intactFromMidLog:0,broken:0,preChain:0};if(e.push(`Log integrity \xB7 ${n.logs} log(s): hash chain intact on ${c.intact} from seq 1 and ${c.intactFromMidLog} from mid-log, broken on ${c.broken}, ${c.preChain} pre-chain (written before the chain); ${n.headMissing} missing their head, ${n.withGaps} with seq gaps, ${n.withRepeats} with repeated seq`),e.push(" (the chain is walked over this node's copy \u2014 advisory; the plane's verified head is the anchor)"),e.push(""),!t.findings.length)e.push("Findings: none.");else{e.push(`Findings \xB7 ${t.findings.length}`);for(let l of t.findings.slice(0,o)){let h=l.to-l.from>j?`${Z(l.from)} \u2192 ${Z(l.to).slice(11)}`:Z(l.from),s=l.actions?`${l.count} prompt(s), ${l.actions} action(s)`:`${l.count}`;e.push(` [${l.severity}] ${l.rule} \xB7 ${l.track||l.trackId}${l.boxId?` \xB7 ${l.boxId}`:""} \xB7 ${h} \xB7 ${s}`),e.push(` ${l.note}`);for(let d of l.samples)e.push(` - ${d}`)}t.findings.length>o&&e.push(` \u2026 ${t.findings.length-o} more (--json for all)`)}return e.push(""),e.push(t.ok?"No action without an origin. The log is agent-writable, so this is evidence, not proof.":"Actions with NO origin were found \u2014 read the [high] findings above."),e.join(`
3
+ `)}var bt="BP-AU-R4",wt=160,Se={high:0,medium:1,low:2},Ee=200,ve=5,_e=8,Te=5,Oe=10*6e4,kt=Object.freeze(["localhost","127.0.0.1","0.0.0.0","github.com","api.github.com","raw.githubusercontent.com","objects.githubusercontent.com","registry.npmjs.org","pypi.org","files.pythonhosted.org"]),$t=5e3,se=new Set(["tool.called","tool.failed"]),Ae=new Set(["Write","Edit","NotebookEdit"]),xt=t=>String(t??"").replace(/\s+/g," ").trim(),W=(t,o=120)=>xt(t).slice(0,o);function It(t){let o=String(t??""),e=o.split(/\s*(?:;|&&|\|\||\||\n)\s*/).map(i=>i.trim()).filter(Boolean);return o.length>=wt&&e.length&&e.pop(),e}function re(t){return String(t??"").replace(/\d?>>?\s*\S+|<\s*\S+|2>&1/g," ").trim().split(/\s+/).filter(Boolean)}function Re(t){let o=0;for(;o<t.length&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(t[o]);)o++;return{cmd:t[o]??null,rest:t.slice(o+1),at:o}}function Ne(t){let o=re(t),e=o.findIndex(s=>s==="git"||s.endsWith("/git"));if(e<0)return null;for(e++;e<o.length&&o[e].startsWith("-");)(o[e]==="-C"||o[e]==="-c")&&e++,e++;if(o[e]!=="push")return null;let i=[],r=[];for(let s=e+1;s<o.length;s++){let d=o[s];d.startsWith("-")?(i.push(d),(d==="-o"||d==="--push-option"||d==="--repo"||d==="--receive-pack"||d==="--exec")&&s++):r.push(d)}let n=i.some(s=>s==="--force"||/^-[a-zA-Z]+$/.test(s)&&s.includes("f"))||r.some(s=>s.startsWith("+")),c=i.some(s=>s.startsWith("--force-with-lease")||s.startsWith("--force-if-includes")),l=r[0]??null,h=r.slice(1).map(s=>s.replace(/^["']|["']$/g,"").replace(/^\+/,"")).map(s=>s.includes(":")?s.slice(s.indexOf(":")+1):s).filter(s=>s&&!s.startsWith("$"));return{force:n,lease:c,remote:l,dsts:h}}var qe=String.raw`(?:~|\$HOME|\$\{HOME\}|/home/[^\s/"']+|/root|/Users/[^\s/"']+)`,St=new RegExp(`${qe}/\\.ssh(?:/|\\b)`),Et=new RegExp(`${qe}/(?:\\.claude\\.json\\b|\\.claude/\\.credentials\\.json|\\.codex/auth\\.json|\\.cursor(?:/|\\b))`),vt=/(?:^|[\s/"'=(])(\.env(?:\.[A-Za-z0-9_.-]+)?)(?=$|[\s"'|;&)])/g,_t=/^\.env\.(?:example|sample|template|dist)$/,De=/(\.claude\/settings(?:\.local)?\.json|\.claude\/hooks\/[^\s"']*|\.bostrat\/events(?:\/[^\s"']*)?)/,Tt=/^\/tmp\/|\/\.bostrat\/tmp\//,Ot=/^(?:refs\/heads\/)?(?:main|master|develop|development|trunk|production|prod|staging|release(?:\/|$)|v\d)/,At=/(?:^|\/)track\/|^HEAD$|^refs\/bostrat\//,Nt=/https?:\/\/([a-z0-9.-]+\.[a-z]{2,}|localhost|\d+\.\d+\.\d+\.\d+)(?::\d+)?/gi,je=t=>St.test(t)||Et.test(t),Me=t=>[...String(t??"").matchAll(vt)].some(o=>!_t.test(o[1])),jt=t=>De.test(t),Mt=t=>(t.startsWith("/")||t.startsWith("~"))&&!Tt.test(t);function Rt(t){let o=String(t??"").match(De);if(!o)return null;let e=o[1],i=e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");if(new RegExp(`>{1,2}\\s*["']?[^\\s"']*${i}`).test(t))return"redirect";let r=re(t),{cmd:n,at:c}=Re(r);if(n==="sudo"){for(c++;c<r.length&&r[c].startsWith("-");)c++;n=r[c]??null}return!n||!r.some(l=>l.includes(e))?null:/^(?:rm|mv|truncate|chmod|chown|tee|install|shred|unlink)$/.test(n)?n:n==="sed"&&r.some(l=>/^-[a-zA-Z]*i/.test(l))?"sed -i":n==="cp"&&r[r.length-1].includes(e)?"cp":/^(?:python3?|perl|node|ruby)$/.test(n)&&/open\([^)]*['"][wa]|writeFile|write_text|appendFile|fs\.write/.test(t)?`${n} write`:null}function qt(t){let o=new Set;for(let e of String(t??"").matchAll(Nt))o.add(e[1].toLowerCase());return[...o]}function Dt({knownHosts:t=[]}={}){return{known:new Set([...kt,...[...t].map(o=>String(o).toLowerCase()).filter(Boolean)]),hosts:new Set,tracks:new Map}}function Ct(t,o){let e=t.tracks.get(o??"");return e||t.tracks.set(o??"",e={costs:[],peer:new Map}),e}var S=(t,o,e,i)=>Object.freeze({severity:t,requirement:bt,note:o,measured:e,detect:i}),z=(t,o)=>{let e=0,i=null;for(let r of t.calls)if(r.head)for(let n of It(r.head)){let c=o(n,r);c&&(e++,i??=W(c))}return e?{count:e,sample:i}:null},ne=(t,o)=>{let e=0,i=null;for(let r of t.calls){let n=o(r);n&&(e++,i??=W(n))}return e?{count:e,sample:i}:null},L=Object.freeze({"force-push":S("high","git push --force / -f / +refspec in a command head (--force-with-lease, the Track's own rebase flow, is not one)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 1,080 logs 2026-08\u219210 \xB7 58 --force-with-lease pushes excluded by design \u2192 high",(t,o)=>z(o,e=>{let i=Ne(e);return i&&i.force&&!i.lease?e:null})),"protected-config-write":S("high","a write to the box's own harness: .claude/settings*.json, a .claude/hooks/ script or the Track's .bostrat/events/ log \u2014 by a file tool, a redirect, rm/mv/tee/sed -i, or a scripted open-for-write (reads are not hits)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 165 reads of those paths (cat, grep, python json.loads) excluded by design \u2192 high",(t,o)=>{let e=0,i=null;for(let n of o.calls)Ae.has(n.tool)&&n.path&&jt(n.path)&&(e++,i??=W(`${n.tool} ${n.path}`));let r=z(o,n=>{let c=Rt(n);return c?`${c}: ${n}`:null});return r&&(e+=r.count,i??=r.sample),e?{count:e,sample:i}:null}),"push-outside-track":S("medium","git push to a protected branch (main/master/develop/release\u2026), to a branch that is not a Track branch, or to a remote other than origin/upstream \u2014 a Track's own code leaves on its track/ branch or the WIP shadow ref","2026-10-21 fleet replay \xB7 7 hits / 98,690 hook calls \xB7 4 Tracks \xB7 0 in the last 14 d \xB7 every hit operator-instructed work on another repo (two dep/ branches, a fork's multiplayer branch, one HEAD:main) \u2192 medium, not a page",(t,o)=>z(o,e=>{let i=Ne(e);return i&&(i.remote&&!/^(?:origin|upstream)$/.test(i.remote)&&!i.remote.startsWith("$")&&!i.remote.startsWith("-")||i.dsts.some(r=>Ot.test(r)||!At.test(r)))?e:null})),"credential-read":S("medium","a command head or file path naming SSH material (~/.ssh) or a vendor auth file (~/.claude.json, ~/.claude/.credentials.json, ~/.codex/auth.json, ~/.cursor) \u2014 the box-auth policy's denylist shapes; ~/.claude/projects (transcripts) is not one","2026-10-21 fleet replay \xB7 12 hits / 98,690 hook calls \xB7 8 Tracks \xB7 3 in the last 14 d \xB7 every hit an operator-asked inspection on the operator's own node (ssh config for a host alias, stat/keys of ~/.claude.json) \u2192 medium until a 14-day window reads 0",(t,o)=>ne(o,e=>e.head&&je(e.head)?e.head:e.path&&je(e.path)?`${e.tool} ${e.path}`:null)),"dotenv-read":S("low","a command head or file path naming a .env file (.env, .env.local, .env.production \u2026; the committed .env.example / .sample / .template are not ones)","2026-10-21 fleet replay \xB7 309 hits / 98,690 hook calls \xB7 44 Tracks \xB7 0 in the last 14 d \xB7 all of them an instructed `source \u2026/almanac/.env` on the operator's other repos \u2192 low (report only)",(t,o)=>ne(o,e=>e.head&&Me(e.head)?e.head:e.path&&Me(e.path)?`${e.tool} ${e.path}`:null)),"privileged-command":S("medium","a shell segment whose command is sudo or docker (after any VAR=value prefix) \u2014 host-level reach from inside a box","2026-10-21 fleet replay \xB7 220 hits (183 events) / 98,690 hook calls \xB7 57 Tracks \xB7 3 in the last 14 d \xB7 sudo = the operator's own journalctl/systemctl asks on the Pi, docker = instructed compose/psql work on the operator's other services \u2192 medium (a hosted node's first hit is the number to watch)",(t,o)=>z(o,e=>{let{cmd:i}=Re(re(e));return i==="sudo"||i==="docker"?e:null})),"write-outside-workspace":S("low","a file tool (Write / Edit / NotebookEdit) on an absolute path \u2014 the hook strips the box's workspace prefix, so an absolute path is outside it \u2014 that is not the box's scratch dir (/tmp/\u2026, \u2026/.bostrat/tmp/\u2026)","2026-10-21 fleet replay \xB7 106 hits / 98,690 hook calls \xB7 10 Tracks \xB7 44 in the last 14 d \xB7 the operator's other repos and Agent-tool worktrees under .bostrat/repos on the operator's own node \u2192 low (report only)",(t,o)=>ne(o,e=>Ae.has(e.tool)&&e.path&&Mt(e.path)?`${e.tool} ${e.path}`:null)),"egress-new-host":S("low","a command head reaching, by URL, a host no box in the fleet has reached before (this projection's lifetime, seeded from the warm tails; KNOWN_HOSTS + PLANE_AUDIT_KNOWN_HOSTS never count) \u2014 a baseline-growth signal, so each host is reported once","2026-10-21 fleet replay \xB7 140 first sightings / 98,690 hook calls \xB7 69 Tracks \xB7 8 in the last 14 d \xB7 198 if the baseline were per Track \xB7 every host a vendor API, a docs site or the operator's own services \u2192 low (report only)",(t,o)=>{let e=0,i=null;for(let r of o.calls)if(r.head)for(let n of qt(r.head))o.state.known.has(n)||o.state.hosts.has(n)||(o.state.hosts.add(n),o.state.hosts.size>$t&&o.state.hosts.delete(o.state.hosts.values().next().value),e++,i??=W(`${n} \xB7 ${r.head}`));return e?{count:e,sample:i}:null}),"spend-spike":S("low",`a cost.recorded turn over ${_e}\xD7 the Track's running median (its last ${Ee} cost events, at least ${ve} of them) and over $${Te} \u2014 a turn that cost far more than this Track's turns do`,"2026-10-21 fleet replay \xB7 114 hits / 8,421 cost events \xB7 64 Tracks \xB7 2 in the last 14 d \xB7 every hit a long build turn after short prompts (a $25\u201378 turn on a $0.37 median) \u2014 real spend, no one's attack \u2192 low (report only)",(t,o)=>{if(t.type!=="cost.recorded")return null;let e=o.track.costs,i=Number(t.payload?.usd);if(!Number.isFinite(i)||i<0)return null;let r=null;if(e.length>=ve&&i>=Te){let n=[...e].sort((l,h)=>l-h),c=n[Math.floor(n.length/2)];i>_e*c&&(r={count:1,sample:`$${i.toFixed(2)} vs median $${c.toFixed(2)} over ${e.length} turns${t.payload?.model?` \xB7 ${t.payload.model}`:""}`})}return e.push(i),e.length>Ee&&e.shift(),r}),"peer-injected-call":S("medium",`a tool call within ${Oe/6e4} min of a peer.message on the same box with no operator or node-typed turn in between \u2014 the cross-session injection vector (peer-message.js); a peer is never a root (BP-AU-D1)`,"2026-10-21 fleet replay \xB7 0 peer.message events in 288,395 \xB7 the replay cannot bound this rule's rate, so it stays medium (rail) until a window with peer traffic has been measured; the shape itself is BP-AU-D1's stated R4 case",(t,o)=>{if(!o.calls.length)return null;let e=o.track.peer.get(o.boxKey);if(e==null||o.ts-e>Oe||o.ts<e)return null;let i=o.calls[0];return{count:o.count,sample:W(`${Math.round((o.ts-e)/1e3)}s after a peer message \xB7 ${i.tool}${i.head?`: ${i.head}`:i.path?` ${i.path}`:""}`)}}),"foreign-session-call":S("medium","a hook-recorded tool call from a claude session that is not the box's own (payload.foreignSession, BP-AU-I12): a nested or stray CLI driving the box's hooks \u2014 R3 never roots it, this names it","2026-10-21 fleet replay \xB7 14 hits / 98,744 hook calls \xB7 3 Tracks \xB7 4 in the last 14 d \xB7 all the xscout loop's nested claude prompts, I12's own acceptance case \u2192 medium (rail), not a page",(t,o)=>{if(!o.calls.length||!t.payload?.foreignSession)return null;let e=o.calls[0];return{count:o.count,sample:W(`session ${String(t.payload.sessionId||"?").slice(0,8)} \xB7 ${e.tool}${e.head?`: ${e.head}`:e.path?` ${e.path}`:""}`)}})}),F=Object.freeze(Object.keys(L)),Pt=new Set([...se,"cost.recorded","peer.message","turn.user"]);function Wt(t,o,{rules:e=null}={}){if(!o||!Pt.has(o.type)||o.source==="plane")return[];let i=Ct(t,o.trackId),r=o.payload||{},n=o.boxId??"",c=Number(o.ts)||0;if(o.type==="turn.user")return(o.actor==="user"||o.actor==="orchestrator")&&i.peer.delete(n),[];if(o.type==="peer.message")return i.peer.set(n,c),[];let l=se.has(o.type)&&o.source==="hook",s={calls:l?O(r):[],count:l?q(r):0,track:i,state:t,ts:c,boxKey:n},d=[];for(let m of e||F){let k=L[m];if(!k)continue;let y=null;try{y=k.detect(o,s)}catch{y=null}y&&y.count>0&&d.push({rule:m,count:y.count,sample:y.sample??null})}return d}function Ce(t,{days:o=14,now:e=Date.now(),rules:i=null}={}){let r=o?e-o*864e5:0,n=Dt(),c=i||F,l={days:o||null,logs:0,events:0,calls:0,callsInWindow:0,rules:Object.fromEntries(c.map(s=>[s,{severity:L[s].severity,note:L[s].note,measured:L[s].measured,hits:0,hitsInWindow:0,events:0,tracks:0,tracksInWindow:0,perTenK:0,samples:[]}]))},h=Object.fromEntries(c.map(s=>[s,{all:new Set,win:new Set}]));for(let s of t||[]){let d=Array.isArray(s?.events)?[...s.events].sort((m,k)=>(Number(m.ts)||0)-(Number(k.ts)||0)):[];if(d.length){l.logs++;for(let m of d){l.events++;let k=(Number(m.ts)||0)>=r;if(se.has(m.type)&&m.source==="hook"){let y=q(m.payload||{});l.calls+=y,k&&(l.callsInWindow+=y)}for(let y of Wt(n,m,{rules:c})){let v=l.rules[y.rule];if(v.hits+=y.count,v.events++,h[y.rule].all.add(m.trackId??s.trackId),k&&(v.hitsInWindow+=y.count,h[y.rule].win.add(m.trackId??s.trackId)),y.sample){let M=v.samples.find(b=>b.text===y.sample);M?M.n++:v.samples.length<200&&v.samples.push({text:y.sample,n:1,ts:Number(m.ts)||0,trackId:m.trackId??s.trackId,name:s.name??null})}}}}}for(let s of c){let d=l.rules[s];d.tracks=h[s].all.size,d.tracksInWindow=h[s].win.size,d.perTenK=l.calls?Number((d.hits/l.calls*1e4).toFixed(2)):0,d.samples.sort((m,k)=>k.n-m.n||k.ts-m.ts),d.samples=d.samples.slice(0,50)}return l.ok=!c.some(s=>L[s].severity==="high"&&l.rules[s].hitsInWindow>0),l}var Lt=t=>new Date(t).toISOString().slice(0,10);function Pe(t,{samples:o=5}={}){let e=[];e.push(`R4 DETECTOR REPLAY \xB7 ${t.days?`window last ${t.days}d`:"all history"} \xB7 ${t.logs} logs \xB7 ${t.events.toLocaleString()} events \xB7 ${t.calls.toLocaleString()} hook calls (${t.callsInWindow.toLocaleString()} in window)`),e.push("A rule's severity is what its measured rate supports; `high` pages (D7). Hits are what the plane fold would have flagged.");let i=Object.keys(t.rules).sort((r,n)=>Se[t.rules[r].severity]-Se[t.rules[n].severity]||r.localeCompare(n));for(let r of i){let n=t.rules[r];e.push(""),e.push(`${r} [${n.severity}] \xB7 ${n.hits} hit(s) all-time (${n.events} events, ${n.tracks} Tracks, ${n.perTenK}/10k calls) \xB7 ${n.hitsInWindow} in window (${n.tracksInWindow} Tracks)`),e.push(` ${n.note}`),n.measured&&e.push(` measured: ${n.measured}`);for(let c of n.samples.slice(0,o))e.push(` ${c.n}\xD7 ${Lt(c.ts)} ${c.name?`${c.name} `:""}(${c.trackId}) ${c.text}`)}return e.push(""),e.push(t.ok?"ok: no high-severity detector hit in the window":"ATTENTION: a high-severity detector hit inside the window (exit 2)"),e.join(`
4
+ `)}import Ft from"node:path";import Ut from"node:os";import{EventEmitter as Ht}from"node:events";var Bt=new Ht;Bt.setMaxListeners(0);var ao=process.env.BOSTRAT_STATE_DIR||Ft.join(process.env.HOME||Ut.tmpdir(),".bostrat");var Kt=/(?<!\\)((?:\\\\)*)\\u0000/g,We=t=>t.includes("\\u0000"),Le=t=>t.replace(Kt,"$1\\ufffd");var G=process.env.BOSTRAT_STATE_DIR||C.join(process.env.HOME||zt.tmpdir(),".bostrat"),E=process.argv.slice(2),J=t=>{let o=E.indexOf(t);return o>=0?E[o+1]:null};(E.includes("--help")||E.includes("-h"))&&(console.log("usage: bostrat-audit [--days N | --all] [--track <id-or-name>] [--limit N] [--json]"),console.log(" bostrat-audit --detectors [--days N | --all] [--track <id-or-name>] [--rule <name>]... [--limit N] [--json]"),console.log(` rules: ${F.join(", ")}`),process.exit(0));var ce=[];try{ce=JSON.parse(await Y.readFile(C.join(G,"tracks.json"),"utf8")).tracks||[]}catch{}var Yt=new Map(ce.map(t=>[t.id,t.name])),le=new Set([C.join(G,"events")]);for(let t of ce)t.cwd&&le.add(C.join(t.cwd,".bostrat","events"));try{for(let t of await Y.readdir(C.join(G,"workspaces")))le.add(C.join(G,"workspaces",t,".bostrat","events"))}catch{}var ie=J("--track"),de=[];for(let t of le){let o=[];try{o=(await Y.readdir(t)).filter(e=>e.endsWith(".ndjson"))}catch{continue}for(let e of o){let i=e.slice(0,-7),r=Yt.get(i)||null;if(ie&&i!==ie&&!(r||"").toLowerCase().includes(ie.toLowerCase()))continue;let n=[];for(let c of(await Y.readFile(C.join(t,e),"utf8")).split(`
5
+ `))if(c.trim())try{n.push(JSON.parse(We(c)?Le(c):c))}catch{}n.length&&de.push({trackId:i,name:r,events:n})}}var Fe=E.includes("--all")?0:Number(J("--days"))||14;if(E.includes("--detectors")){let t=E.flatMap((i,r)=>i==="--rule"&&E[r+1]?[E[r+1]]:[]),o=t.filter(i=>!F.includes(i));o.length&&(console.error(`unknown rule(s): ${o.join(", ")} \u2014 known: ${F.join(", ")}`),process.exit(1));let e=Ce(de,{days:Fe,rules:t.length?t:null});console.log(E.includes("--json")?JSON.stringify(e,null,2):Pe(e,{samples:Number(J("--limit"))||5})),process.exit(e.ok?0:2)}var ae=xe(de,{days:Fe});console.log(E.includes("--json")?JSON.stringify(ae,null,2):Ie(ae,{limit:Number(J("--limit"))||20}));process.exit(ae.ok?0:2);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ var g=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`;async function m(n,c,o){let t=await fetch(`${g}${c}`,{method:n,headers:o?{"content-type":"application/json"}:void 0,body:o?JSON.stringify(o):void 0}),s=await t.json().catch(()=>({}));if(!t.ok)throw new Error(s.error||`HTTP ${t.status}`);return s}function l(n){console.error(`[bostrat-estimate] ${n}`),process.exit(1)}function d(n){let c=Number(n)||0;if(c<60)return`${c}m`;let o=c/60;if(o<8)return`${Number.isInteger(o)?o:o.toFixed(1)}h`;let t=o/8;return`${Number.isInteger(t)?t:t.toFixed(1)}d`}var $=n=>n?new Date(n).toISOString().slice(0,10):"?",a=process.argv.slice(2),f=a.shift(),h='usage: bostrat-estimate record --human <dur> --agent <dur> --scope "<one line, \u2264160 chars>" [--bp BP-XX-I3 | --ticket KEY-123]',p='usage: bostrat-estimate close <estimate-id> [--note "<what landed>"] (or: close --latest)';if(f==="record"){let n=null,c=null,o=null,t=null,s=null,i=[];for(let e=0;e<a.length;e++)a[e]==="--human"?n=a[++e]:a[e]==="--agent"?c=a[++e]:a[e]==="--scope"?o=a[++e]:a[e]==="--bp"?t=a[++e]:a[e]==="--ticket"?s=a[++e]:i.push(a[e]);!o&&i.length&&(o=i.join(" ").trim()),(!n||!c||!o)&&l(h);try{let e=await m("POST","/api/estimates/record",{cwd:process.cwd(),human:n,agent:c,scope:o,bp:t,ticket:s}),u=e.bp?` for ${e.bp}`:e.ticket?` for ${e.ticket}`:"",r=[e.provider,e.model].filter(Boolean).join(" \xB7 ");console.log(`[bostrat-estimate] recorded ${e.estimateId}${u}: human ${d(e.human.minutes)} \xB7 agent-active ${d(e.agent.minutes)}${r?` (${r})`:""}`),console.log(` \u201C${e.scope}\u201D \u2014 close it when the work lands: bostrat-estimate close ${e.estimateId}`)}catch(e){l(`${e.message}
3
+ ${h}`)}}else if(f==="close"){let n=null,c=!1,o="";for(let t=0;t<a.length;t++)a[t]==="--latest"?c=!0:a[t]==="--note"?o=a[++t]:!n&&!a[t].startsWith("--")?n=a[t]:l(p);!n&&!c&&l(p);try{let t=await m("POST","/api/estimates/close",{cwd:process.cwd(),estimateId:n,latest:c,note:o});console.log(`[bostrat-estimate] closed ${t.estimateId} \u2014 \u201C${t.scope}\u201D`),console.log(" Its actuals are folded from this Track's own log and held against the estimate.")}catch(t){l(`${t.message}
4
+ ${p}`)}}else if(f==="ls"){let n=a.includes("--open");try{let{estimates:c,counts:o}=await m("GET",`/api/estimates?cwd=${encodeURIComponent(process.cwd())}`),t=n?c.filter(s=>s.status==="open"):c;t.length||console.log(`[bostrat-estimate] ${n?"no open estimates":"nothing recorded"} on this Track yet`);for(let s of t){let i=s.status==="open"?"o":"x",e=s.bp||s.ticket||"",u=[s.provider,s.model].filter(Boolean).join(" \xB7 ");console.log(`${i} ${s.estimateId.padEnd(13)} ${$(s.recordedAt)} human ${d(s.human?.minutes).padEnd(6)} agent ${d(s.agent?.minutes).padEnd(6)} ${e.padEnd(12)} ${s.scope}`);let r=[u,s.status==="closed"?`closed ${$(s.closedAt)} (${s.closeReason})${s.closeNote?` \u2014 ${s.closeNote}`:""}`:null].filter(Boolean).join(" \xB7 ");r&&console.log(` ${r}`)}o&&t.length&&console.log(` (${o.open} open \xB7 ${o.closed} closed)`)}catch(c){l(c.message)}}else l("usage: bostrat-estimate <record|close|ls> \u2026");