@misterhuydo/cairn-mcp 1.22.0 → 1.23.0

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/dist/cairn-cli.js CHANGED
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env node
2
- import g from"fs";import G from"os";import S from"path";import{execSync as Ln}from"child_process";import Hn from"fast-glob";var je={".java":"java",".ts":"typescript",".tsx":"typescript",".js":"javascript",".jsx":"javascript",".mjs":"javascript",".vue":"vue",".py":"python",".sql":"sql",".yml":"config",".yaml":"config",".properties":"config",".env":"config",".xml":"xml",".html":"html",".md":"markdown"};function te(e,t,n={}){let{noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=n,i=e;return s&&(i=ft(i,t)),o&&(i=mt(i)),i=ht(i),r&&(i=yt(i)),i.trim()}function ft(e,t){switch(t){case"java":case"typescript":case"javascript":case"vue":return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(n,s)=>s||!n.startsWith("//")?n:"");case"python":return e.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return e.replace(/<!--[\s\S]*?-->/g,"");case"config":return e.replace(/#.*$/gm,"");default:return e}}function mt(e){return e.split(`
2
+ import h from"fs";import G from"os";import S from"path";import{execSync as Hn}from"child_process";import Kn from"fast-glob";var Pe={".java":"java",".ts":"typescript",".tsx":"typescript",".js":"javascript",".jsx":"javascript",".mjs":"javascript",".vue":"vue",".py":"python",".sql":"sql",".yml":"config",".yaml":"config",".properties":"config",".env":"config",".xml":"xml",".html":"html",".md":"markdown"};function te(e,t,n={}){let{noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=n,i=e;return s&&(i=yt(i,t)),o&&(i=gt(i)),i=_t(i),r&&(i=St(i)),i.trim()}function yt(e,t){switch(t){case"java":case"typescript":case"javascript":case"vue":return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(n,s)=>s||!n.startsWith("//")?n:"");case"python":return e.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return e.replace(/<!--[\s\S]*?-->/g,"");case"config":return e.replace(/#.*$/gm,"");default:return e}}function gt(e){return e.split(`
3
3
  `).filter(t=>t.trim().length>0).join(`
4
- `)}function ht(e){return e.split(`
4
+ `)}function _t(e){return e.split(`
5
5
  `).map(t=>t.trimEnd()).join(`
6
- `)}function yt(e){return e.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function Pe(e,t={}){let{noStyle:n=!1,noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=t,i=[],u=e.match(/<template>([\s\S]*?)<\/template>/i);if(u){let c=u[1];s&&(c=c.replace(/<!--[\s\S]*?-->/g,"")),o&&(c=c.split(`
7
- `).filter(a=>a.trim()).join(`
8
- `)),c=c.replace(/\s+/g," ").trim(),c&&i.push(`<template>${c}</template>`)}let f=e.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(f){let c=f[1],a=te(f[2],"javascript",{noComments:s,noEmptyLines:o,aggressive:r}),p=/setup/i.test(c)?"<script setup>":"<script>";a&&i.push(`${p}${a}</script>`)}if(!n){let c=e.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(c){let a=c[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();a&&i.push(`<style${c[1]}>${a}</style>`)}}return i.join(`
6
+ `)}function St(e){return e.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function je(e,t={}){let{noStyle:n=!1,noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=t,i=[],l=e.match(/<template>([\s\S]*?)<\/template>/i);if(l){let a=l[1];s&&(a=a.replace(/<!--[\s\S]*?-->/g,"")),o&&(a=a.split(`
7
+ `).filter(c=>c.trim()).join(`
8
+ `)),a=a.replace(/\s+/g," ").trim(),a&&i.push(`<template>${a}</template>`)}let p=e.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(p){let a=p[1],c=te(p[2],"javascript",{noComments:s,noEmptyLines:o,aggressive:r}),d=/setup/i.test(a)?"<script setup>":"<script>";c&&i.push(`${d}${c}</script>`)}if(!n){let a=e.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(a){let c=a[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();c&&i.push(`<style${a[1]}>${c}</style>`)}}return i.join(`
9
9
 
10
- `)}import A from"fs";import K from"path";import ne from"fs";import gt from"os";import Q from"path";var _t=null;function H(){return _t||process.cwd()}function q(){let e=Q.join(H(),".cairn");ne.mkdirSync(Q.join(e,"bundles"),{recursive:!0});let t=Q.join(e,".cairn-project");return ne.existsSync(t)||ne.writeFileSync(t,"","utf8"),e}function pe(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Te(){let e=Q.join(gt.homedir(),".claude","projects",pe(H()));return ne.existsSync(e)?Q.join(e,"memory"):null}import I from"fs";import fe from"path";import{execSync as St}from"child_process";var $="would_be_lost";var Ne=2*1024*1024,xt=12e4,wt=[256*1024,1024*1024],Me=".handoff-hint",bt=220;function de(e,t=bt){let n=String(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t-1)+"\u2026"}function kt(e,t){try{let n=St(`git log --oneline --after="${t}"`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim();return n?n.split(`
11
- `).length:0}catch{return null}}function se(e,t){let n=typeof t?.[$]=="string"?t[$].trim():"";if(!n)return{recorded:!1,text:"",at:null,commits:null,stale:!1};let s=t[`${$}_at`]||t.checkpoint_at||null,o=s?kt(e,s):null;return{recorded:!0,text:n,at:s,commits:o,stale:(o??0)>0}}function me(e){if(!e.recorded)return"No handoff recorded \u2014 nothing was written down about what the last session would lose.";let t=e.stale?` (written before the ${e.commits} commit(s) since \u2014 may be stale)`:"";return`Carried forward: ${de(e.text)}${t}`}function Et(){let e=Number(process.env.CAIRN_CONTEXT_HINT_TOKENS);return Number.isFinite(e)&&e>0?e:xt}function $t(e,t){let n=I.openSync(e,"r");try{let s=I.fstatSync(n).size,o=Math.min(s,t),r=Buffer.alloc(o);return I.readSync(n,r,0,o,s-o),{text:r.toString("utf8"),truncated:o<s}}finally{I.closeSync(n)}}function Rt(e,t){let n=e.split(`
12
- `),s=t?1:0;for(let o=n.length-1;o>=s;o--){let r=n[o].trim();if(!r.startsWith("{"))continue;let i;try{i=JSON.parse(r)}catch{continue}if(i?.isSidechain===!0)continue;let u=i?.message?.usage;if(!u)continue;let f=(u.input_tokens||0)+(u.cache_read_input_tokens||0)+(u.cache_creation_input_tokens||0)+(u.output_tokens||0);if(f>0)return f}return null}function vt(e){if(!e)return{tokens:0,source:"none",bytes:0};let t;try{t=I.statSync(e).size}catch{return{tokens:0,source:"none",bytes:0}}for(let n of wt){let s;try{s=$t(e,n)}catch{break}let o=Rt(s.text,s.truncated);if(o!=null)return{tokens:o,source:"transcript",bytes:t};if(!s.truncated)break}return{tokens:0,source:"bytes",bytes:t}}function Ot(e){return e.recorded&&!e.stale?"clear":"compact"}function jt(e,t){return e.recorded?e.stale?`${t}, and the recorded handoff predates the ${e.commits} commit(s) since, so it no longer describes the work in flight`:`${t}, and the handoff is current as of the last commit, so a reset costs only what is not already in that line`:`${t}, and nothing is written down about what this session would lose, so a hard reset would drop it with no way to get it back`}function Ae({cwd:e,session:t,transcriptPath:n,bytes:s,usage:o}){let r=o??vt(n);r.source==="none"&&typeof s=="number"&&(r={tokens:0,source:"bytes",bytes:s});let i=s??r.bytes,u=Et();if(r.source==="none")return null;let f,c;if(r.source==="transcript"){if(r.tokens<u)return null;f=Math.floor(r.tokens/u),c=`this session is carrying about ${r.tokens.toLocaleString()} tokens of context`}else{if(i<Ne)return null;f=Math.floor(i/Ne),c=`this session's transcript is ${(i/1024/1024).toFixed(1)} MB (token count unreadable, so this is a rough size signal only)`}let a=se(e,t),p=Ot(a),m=jt(a,c),h;a.recorded?a.stale?h=`[cairn] Stopping point: ${c}. Clearing now would lose: ${de(a.text)} \u2014 written before the ${a.commits} commit(s) since.`:h=`[cairn] Stopping point: ${c}. The handoff is current: ${de(a.text)} \u2014 clearing costs only what is not in that line.`:h=`[cairn] Stopping point: ${c}. Nothing is recorded about what clearing would lose \u2014 ask the user first.`;let d=R=>R===p?" (Recommended)":"",l=[` - "Checkpoint + /clear${d("clear")}" \u2014 I write the handoff now, then you type /clear.`," Full reset and maximum headroom; everything not written down is gone."],y=[` - "Checkpoint + /compact${d("compact")}" \u2014 I write the handoff now, then you type /compact.`," Nothing has to be retyped, but the summary is lossy and the window stays partly full."],_=["STOPPING POINT \u2014 this is the user's call, not yours. Ask them before doing anything else.","",`Use AskUserQuestion. Label the options as ACTIONS, put "(Recommended)" on the ${p}`,`option and list it FIRST, and give them cairn's reason in your own words: ${m}.`,"","Offer exactly these:",...p==="clear"?[...l,...y]:[...y,...l],' - "Keep going" \u2014 decline. Cairn will not raise this again until the situation changes.',"",`Whichever they pick, call cairn_checkpoint FIRST and answer ${$} \u2014 including for`,"/clear, which needs it most, not least. That field is the only part of this session that","survives either option.","","You CANNOT run /clear or /compact yourself: no tool or hook can trigger a slash command.","Say so plainly and ask the user to type it.","","After they type it, cairn's SessionStart hook re-injects the checkpoint automatically, so the","next session starts already knowing where it is. If that does not happen the hook is not",'installed (`cairn install-hooks`) \u2014 in that case tell them to type "resume".',"",`Never write ${$} on their behalf, and never tell them there is nothing outstanding:`,"cairn cannot see what was never written down, so it can never issue an all-clear."].join(`
13
- `),x=[f,r.source,a.recorded?a.stale?"stale":"current":"missing",a.commits??"?",p,n?fe.basename(n):"-"].join("|");return{line:h,guidance:_,signature:x,state:a,recommend:p,reason:m,usage:r,bytes:i}}function Fe(e,t){let n=fe.join(e,Me);try{if(I.readFileSync(n,"utf8").trim()===t)return!1}catch{}try{I.writeFileSync(n,t,"utf8")}catch{}return!0}function Le(e){try{I.unlinkSync(fe.join(e,Me))}catch{}}var Pt=1e3,Tt=100*1024;function Nt(e,t,n,s){if(e&&typeof e=="object"&&e.ref)return e;let o=typeof e=="string"?e:String(e),r=/^\[\d{4}-\d{2}-\d{2}\]/.test(o)?o:`[${n}] ${o}`;if(r.length<=Pt)return r;let i=K.join(t,"notes");A.mkdirSync(i,{recursive:!0});let f=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${s}.md`,c=K.join(i,f);return A.writeFileSync(c,r,"utf8"),{description:r.split(`
14
- `)[0].slice(0,120),ref:K.join(".cairn","notes",f)}}function he(e,{message:t,active_files:n=[],notes:s=[],auto:o=!1,...r}){if(typeof s=="string")try{s=JSON.parse(s)}catch{s=s?[s]:[]}Array.isArray(s)||(s=[]);let i=q(),u=K.join(i,"session.json"),f=new Date().toISOString().slice(0,10),c=new Date().toISOString(),a={};try{a=JSON.parse(A.readFileSync(u,"utf8"))||{}}catch{}let p=typeof r[$]=="string"&&r[$].trim()?r[$].trim():null,m=p??(typeof a[$]=="string"?a[$]:null),h=p?c:a[`${$}_at`]??null,d={};try{let _=e.prepare("SELECT path FROM main.files").all();for(let{path:x}of _)try{d[x]=A.statSync(x).mtimeMs}catch{}}catch{}for(let _ of n)try{d[_]=A.statSync(_).mtimeMs}catch{}let l=s.map((_,x)=>Nt(_,i,f,x));if(A.existsSync(u))try{if(A.statSync(u).size>Tt){let x=1;for(;A.existsSync(K.join(i,`session.${x}.json`));)x++;A.renameSync(u,K.join(i,`session.${x}.json`))}}catch{}let y={message:t,checkpoint_at:c,last_manual_checkpoint_at:o?a.last_manual_checkpoint_at??null:c,last_manual_message:o?a.last_manual_message??null:t,active_files:n,notes:l,[$]:m,[`${$}_at`]:h,mtime_snapshot:d};return A.writeFileSync(u,JSON.stringify(y,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:y.checkpoint_at,active_files_tracked:n.length,notes_saved:l.length,[$]:m?p?"recorded":"carried forward from the previous checkpoint":`not recorded \u2014 nothing will tell the next session what this one would lose (pass ${$})`},null,2)}]}}import D from"fs";import N from"path";import Mt from"crypto";var De=80;function ye(e){let t=N.dirname(N.resolve(e)),n=N.parse(t).root;for(;;){let s=N.join(t,".cairn");try{if(D.statSync(s).isDirectory()&&D.existsSync(N.join(s,".cairn-project"))&&s.split(N.sep).filter(r=>r===".cairn").length===1)return s}catch{}if(t===n)return null;t=N.dirname(t)}}function Ce(e,t){let n=Mt.createHash("sha256").update(N.resolve(t)).digest("hex").slice(0,6);return N.join(e,"views",`${n}_${N.basename(t)}`)}function J(e){try{return JSON.parse(D.readFileSync(N.join(e,"minify-map.json"),"utf8"))}catch{return{}}}function M(e,t){D.mkdirSync(e,{recursive:!0}),D.writeFileSync(N.join(e,"minify-map.json"),JSON.stringify(t,null,2),"utf8")}function oe(e,t){let n=0;for(let[s,o]of Object.entries(t)){let r=!1;if(!D.existsSync(s))r=!0;else if(!D.existsSync(o.tempPath))r=!0;else try{D.statSync(s).mtimeMs>o.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(o.tempPath)try{D.unlinkSync(o.tempPath)}catch{}delete t[s],n++}}return n}function Ie(e,t){switch(t){case"javascript":case"typescript":return ge(e);case"java":return ge(e,{annotations:!0});case"python":return Dt(e);case"vue":return Ct(e);case"sql":return It(e);default:return e.split(`
10
+ `)}import L from"fs";import q from"path";import ne from"fs";import xt from"os";import Q from"path";var wt=null;function H(){return wt||process.cwd()}function K(){let e=Q.join(H(),".cairn");ne.mkdirSync(Q.join(e,"bundles"),{recursive:!0});let t=Q.join(e,".cairn-project");return ne.existsSync(t)||ne.writeFileSync(t,"","utf8"),e}function ue(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Te(){let e=Q.join(xt.homedir(),".claude","projects",ue(H()));return ne.existsSync(e)?Q.join(e,"memory"):null}import W from"fs";import de from"path";import{execSync as bt}from"child_process";var O="would_be_lost";var Ne=2*1024*1024,Et=12e4,kt=[256*1024,1024*1024],Me=".handoff-hint",$t=220;function pe(e,t=$t){let n=String(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t-1)+"\u2026"}function Rt(e,t){try{let n=bt(`git log --oneline --after="${t}"`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim();return n?n.split(`
11
+ `).length:0}catch{return null}}function se(e,t){let n=typeof t?.[O]=="string"?t[O].trim():"";if(!n)return{recorded:!1,text:"",at:null,commits:null,stale:!1};let s=t[`${O}_at`]||t.checkpoint_at||null,o=s?Rt(e,s):null;return{recorded:!0,text:n,at:s,commits:o,stale:(o??0)>0}}function fe(e){if(!e.recorded)return"No handoff recorded \u2014 nothing was written down about what the last session would lose.";let t=e.stale?` (written before the ${e.commits} commit(s) since \u2014 may be stale)`:"";return`Carried forward: ${pe(e.text)}${t}`}function vt(){let e=Number(process.env.CAIRN_CONTEXT_HINT_TOKENS);return Number.isFinite(e)&&e>0?e:Et}function Ot(e,t){let n=W.openSync(e,"r");try{let s=W.fstatSync(n).size,o=Math.min(s,t),r=Buffer.alloc(o);return W.readSync(n,r,0,o,s-o),{text:r.toString("utf8"),truncated:o<s}}finally{W.closeSync(n)}}function Pt(e,t){let n=e.split(`
12
+ `),s=t?1:0;for(let o=n.length-1;o>=s;o--){let r=n[o].trim();if(!r.startsWith("{"))continue;let i;try{i=JSON.parse(r)}catch{continue}if(i?.isSidechain===!0)continue;let l=i?.message?.usage;if(!l)continue;let p=(l.input_tokens||0)+(l.cache_read_input_tokens||0)+(l.cache_creation_input_tokens||0)+(l.output_tokens||0);if(p>0)return p}return null}function jt(e){if(!e)return{tokens:0,source:"none",bytes:0};let t;try{t=W.statSync(e).size}catch{return{tokens:0,source:"none",bytes:0}}for(let n of kt){let s;try{s=Ot(e,n)}catch{break}let o=Pt(s.text,s.truncated);if(o!=null)return{tokens:o,source:"transcript",bytes:t};if(!s.truncated)break}return{tokens:0,source:"bytes",bytes:t}}function Tt(e){return e.recorded&&!e.stale?"clear":"compact"}function Nt(e,t){return e.recorded?e.stale?`${t}, and the recorded handoff predates the ${e.commits} commit(s) since, so it no longer describes the work in flight`:`${t}, and the handoff is current as of the last commit, so a reset costs only what is not already in that line`:`${t}, and nothing is written down about what this session would lose, so a hard reset would drop it with no way to get it back`}function Ae({cwd:e,session:t,transcriptPath:n,bytes:s,usage:o}){let r=o??jt(n);r.source==="none"&&typeof s=="number"&&(r={tokens:0,source:"bytes",bytes:s});let i=s??r.bytes,l=vt();if(r.source==="none")return null;let p,a;if(r.source==="transcript"){if(r.tokens<l)return null;p=Math.floor(r.tokens/l),a=`this session is carrying about ${r.tokens.toLocaleString()} tokens of context`}else{if(i<Ne)return null;p=Math.floor(i/Ne),a=`this session's transcript is ${(i/1024/1024).toFixed(1)} MB (token count unreadable, so this is a rough size signal only)`}let c=se(e,t),d=Tt(c),f=Nt(c,a),y;c.recorded?c.stale?y=`[cairn] Stopping point: ${a}. Clearing now would lose: ${pe(c.text)} \u2014 written before the ${c.commits} commit(s) since.`:y=`[cairn] Stopping point: ${a}. The handoff is current: ${pe(c.text)} \u2014 clearing costs only what is not in that line.`:y=`[cairn] Stopping point: ${a}. Nothing is recorded about what clearing would lose \u2014 ask the user first.`;let m=b=>b===d?" (Recommended)":"",u=[` - "Checkpoint + /clear${m("clear")}" \u2014 I write the handoff now, then you type /clear.`," Full reset and maximum headroom; everything not written down is gone."],g=[` - "Checkpoint + /compact${m("compact")}" \u2014 I write the handoff now, then you type /compact.`," Nothing has to be retyped, but the summary is lossy and the window stays partly full."],_=["STOPPING POINT \u2014 this is the user's call, not yours. Ask them before doing anything else.","",`Use AskUserQuestion. Label the options as ACTIONS, put "(Recommended)" on the ${d}`,`option and list it FIRST, and give them cairn's reason in your own words: ${f}.`,"","Offer exactly these:",...d==="clear"?[...u,...g]:[...g,...u],' - "Keep going" \u2014 decline. Cairn will not raise this again until the situation changes.',"",`Whichever they pick, call cairn_checkpoint FIRST and answer ${O} \u2014 including for`,"/clear, which needs it most, not least. That field is the only part of this session that","survives either option.","","You CANNOT run /clear or /compact yourself: no tool or hook can trigger a slash command.","Say so plainly and ask the user to type it.","","After they type it, cairn's SessionStart hook re-injects the checkpoint automatically, so the","next session starts already knowing where it is. If that does not happen the hook is not",'installed (`cairn install-hooks`) \u2014 in that case tell them to type "resume".',"",`Never write ${O} on their behalf, and never tell them there is nothing outstanding:`,"cairn cannot see what was never written down, so it can never issue an all-clear."].join(`
13
+ `),w=[p,r.source,c.recorded?c.stale?"stale":"current":"missing",c.commits??"?",d,n?de.basename(n):"-"].join("|");return{line:y,guidance:_,signature:w,state:c,recommend:d,reason:f,usage:r,bytes:i}}function Fe(e,t){let n=de.join(e,Me);try{if(W.readFileSync(n,"utf8").trim()===t)return!1}catch{}try{W.writeFileSync(n,t,"utf8")}catch{}return!0}function Le(e){try{W.unlinkSync(de.join(e,Me))}catch{}}var Mt=1e3,At=100*1024;function Ft(e,t,n,s){if(e&&typeof e=="object"&&e.ref)return e;let o=typeof e=="string"?e:String(e),r=/^\[\d{4}-\d{2}-\d{2}\]/.test(o)?o:`[${n}] ${o}`;if(r.length<=Mt)return r;let i=q.join(t,"notes");L.mkdirSync(i,{recursive:!0});let p=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${s}.md`,a=q.join(i,p);return L.writeFileSync(a,r,"utf8"),{description:r.split(`
14
+ `)[0].slice(0,120),ref:q.join(".cairn","notes",p)}}function me(e,{message:t,active_files:n=[],notes:s=[],auto:o=!1,...r}){if(typeof s=="string")try{s=JSON.parse(s)}catch{s=s?[s]:[]}Array.isArray(s)||(s=[]);let i=K(),l=q.join(i,"session.json"),p=new Date().toISOString().slice(0,10),a=new Date().toISOString(),c={};try{c=JSON.parse(L.readFileSync(l,"utf8"))||{}}catch{}let d=typeof r[O]=="string"&&r[O].trim()?r[O].trim():null,f=d??(typeof c[O]=="string"?c[O]:null),y=d?a:c[`${O}_at`]??null,m={};try{let _=e.prepare("SELECT path FROM main.files").all();for(let{path:w}of _)try{m[w]=L.statSync(w).mtimeMs}catch{}}catch{}for(let _ of n)try{m[_]=L.statSync(_).mtimeMs}catch{}let u=s.map((_,w)=>Ft(_,i,p,w));if(L.existsSync(l))try{if(L.statSync(l).size>At){let w=1;for(;L.existsSync(q.join(i,`session.${w}.json`));)w++;L.renameSync(l,q.join(i,`session.${w}.json`))}}catch{}let g={message:t,checkpoint_at:a,last_manual_checkpoint_at:o?c.last_manual_checkpoint_at??null:a,last_manual_message:o?c.last_manual_message??null:t,active_files:n,notes:u,[O]:f,[`${O}_at`]:y,mtime_snapshot:m};return L.writeFileSync(l,JSON.stringify(g,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:g.checkpoint_at,active_files_tracked:n.length,notes_saved:u.length,[O]:f?d?"recorded":"carried forward from the previous checkpoint":`not recorded \u2014 nothing will tell the next session what this one would lose (pass ${O})`},null,2)}]}}import I from"fs";import M from"path";import Lt from"crypto";var De=80;function he(e){let t=M.dirname(M.resolve(e)),n=M.parse(t).root;for(;;){let s=M.join(t,".cairn");try{if(I.statSync(s).isDirectory()&&I.existsSync(M.join(s,".cairn-project"))&&s.split(M.sep).filter(r=>r===".cairn").length===1)return s}catch{}if(t===n)return null;t=M.dirname(t)}}function Ce(e,t){let n=Lt.createHash("sha256").update(M.resolve(t)).digest("hex").slice(0,6);return M.join(e,"views",`${n}_${M.basename(t)}`)}function J(e){try{return JSON.parse(I.readFileSync(M.join(e,"minify-map.json"),"utf8"))}catch{return{}}}function F(e,t){I.mkdirSync(e,{recursive:!0}),I.writeFileSync(M.join(e,"minify-map.json"),JSON.stringify(t,null,2),"utf8")}function oe(e,t){let n=0;for(let[s,o]of Object.entries(t)){let r=!1;if(!I.existsSync(s))r=!0;else if(!I.existsSync(o.tempPath))r=!0;else try{I.statSync(s).mtimeMs>o.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(o.tempPath)try{I.unlinkSync(o.tempPath)}catch{}delete t[s],n++}}return n}function Ie(e,t){switch(t){case"javascript":case"typescript":return ye(e);case"java":return ye(e,{annotations:!0});case"python":return Wt(e);case"vue":return Ut(e);case"sql":return Ht(e);default:return e.split(`
15
15
  `).slice(0,30).join(`
16
16
  `)+`
17
- ...`}}function ge(e,{annotations:t=!1}={}){let n=e.split(`
18
- `),s=[],o=0;for(let r=0;r<n.length;r++){let i=n[r].trim();if(!i)continue;let{opens:u,closes:f}=Lt(i),c=u-f;if(o===0){let a=At(i);a!==null?s.push(`${r+1}: ${a}`):i==="}"&&s.push("}")}else if(o===1){let a=Ft(i,t);a!==null&&s.push(` ${r+1}: ${a}`)}o=Math.max(0,o+c)}return s.join(`
19
- `)}function At(e){return/^import\s/.test(e)||/^@\w+/.test(e)||/^package\s/.test(e)?e:/^export\s/.test(e)?/^export\s+(default\s+)?(async\s+)?function[\s*]|^export\s+(default\s+)?(abstract\s+)?class\s/.test(e)?F(e):e:/^(async\s+)?function[\s*]/.test(e)||/^(abstract\s+)?class\s/.test(e)||/^(public\s+|private\s+|protected\s+)?(interface|enum|type|record)\s/.test(e)||/^(const|let|var)\s+\w+/.test(e)||/^(public|private|protected|abstract|final)\s+(class|interface|enum)\s/.test(e)?F(e):e==="}"?"}":null}function Ft(e,t){return t&&/^@\w+/.test(e)?e:e==="}"?"}":/^(public|private|protected|static|final|abstract|async|override|readonly|synchronized|native)\s/.test(e)||/^constructor\s*\(/.test(e)||/^(get|set)\s+\w+\s*\(/.test(e)||/^\w[\w$]*\s*[<(]/.test(e)&&!e.startsWith("return")&&!e.startsWith("throw")&&!e.startsWith("if")&&!e.startsWith("for")&&!e.startsWith("while")&&!e.startsWith("switch")&&!e.startsWith("const")&&!e.startsWith("let")&&!e.startsWith("var")?F(e):null}function F(e){let t=0,n=!1,s="";for(let o=0;o<e.length;o++){let r=e[o];if(n)r===s&&e[o-1]!=="\\"&&(n=!1);else if(r==='"'||r==="'"||r==="`")n=!0,s=r;else if(r==="{"){if(t===0)return e.slice(0,o).trimEnd();t++}else r==="}"&&t--}return e}function Lt(e){let t=0,n=0,s=!1,o="";for(let r=0;r<e.length;r++){let i=e[r];s?i===o&&e[r-1]!=="\\"&&(s=!1):i==='"'||i==="'"||i==="`"?(s=!0,o=i):i==="{"?t++:i==="}"&&n++}return{opens:t,closes:n}}function Dt(e){let t=[],n=e.split(`
17
+ ...`}}function ye(e,{annotations:t=!1}={}){let n=e.split(`
18
+ `),s=[],o=0;for(let r=0;r<n.length;r++){let i=n[r].trim();if(!i)continue;let{opens:l,closes:p}=It(i),a=l-p;if(o===0){let c=Dt(i);c!==null?s.push(`${r+1}: ${c}`):i==="}"&&s.push("}")}else if(o===1){let c=Ct(i,t);c!==null&&s.push(` ${r+1}: ${c}`)}o=Math.max(0,o+a)}return s.join(`
19
+ `)}function Dt(e){return/^import\s/.test(e)||/^@\w+/.test(e)||/^package\s/.test(e)?e:/^export\s/.test(e)?/^export\s+(default\s+)?(async\s+)?function[\s*]|^export\s+(default\s+)?(abstract\s+)?class\s/.test(e)?D(e):e:/^(async\s+)?function[\s*]/.test(e)||/^(abstract\s+)?class\s/.test(e)||/^(public\s+|private\s+|protected\s+)?(interface|enum|type|record)\s/.test(e)||/^(const|let|var)\s+\w+/.test(e)||/^(public|private|protected|abstract|final)\s+(class|interface|enum)\s/.test(e)?D(e):e==="}"?"}":null}function Ct(e,t){return t&&/^@\w+/.test(e)?e:e==="}"?"}":/^(public|private|protected|static|final|abstract|async|override|readonly|synchronized|native)\s/.test(e)||/^constructor\s*\(/.test(e)||/^(get|set)\s+\w+\s*\(/.test(e)||/^\w[\w$]*\s*[<(]/.test(e)&&!e.startsWith("return")&&!e.startsWith("throw")&&!e.startsWith("if")&&!e.startsWith("for")&&!e.startsWith("while")&&!e.startsWith("switch")&&!e.startsWith("const")&&!e.startsWith("let")&&!e.startsWith("var")?D(e):null}function D(e){let t=0,n=!1,s="";for(let o=0;o<e.length;o++){let r=e[o];if(n)r===s&&e[o-1]!=="\\"&&(n=!1);else if(r==='"'||r==="'"||r==="`")n=!0,s=r;else if(r==="{"){if(t===0)return e.slice(0,o).trimEnd();t++}else r==="}"&&t--}return e}function It(e){let t=0,n=0,s=!1,o="";for(let r=0;r<e.length;r++){let i=e[r];s?i===o&&e[r-1]!=="\\"&&(s=!1):i==='"'||i==="'"||i==="`"?(s=!0,o=i):i==="{"?t++:i==="}"&&n++}return{opens:t,closes:n}}function Wt(e){let t=[],n=e.split(`
20
20
  `);for(let s=0;s<n.length;s++){let o=n[s];if(!o.trim())continue;let r=o.trimStart();r.startsWith("import ")||r.startsWith("from ")?t.push(o):(r.startsWith("class ")||r.startsWith("def ")||r.startsWith("async def "))&&t.push(`${s+1}: ${o.trim().replace(/:(\s*)$/,":")}`)}return t.join(`
21
- `)}function Ct(e){let t=[];for(let s of e.matchAll(/<(template|script|style)(\s[^>]*)?\s*>/g))t.push(s[0]);let n=e.match(/<script(?:\s[^>]*)?\s*>([\s\S]*?)<\/script>/);if(n){let s=ge(n[1]);s.trim()&&(t.push("// script:"),t.push(s))}return t.join(`
22
- `)}function It(e){let t=[],n=e.split(`
21
+ `)}function Ut(e){let t=[];for(let s of e.matchAll(/<(template|script|style)(\s[^>]*)?\s*>/g))t.push(s[0]);let n=e.match(/<script(?:\s[^>]*)?\s*>([\s\S]*?)<\/script>/);if(n){let s=ye(n[1]);s.trim()&&(t.push("// script:"),t.push(s))}return t.join(`
22
+ `)}function Ht(e){let t=[],n=e.split(`
23
23
  `);for(let s=0;s<n.length;s++){let o=n[s].trim();/^(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE|SELECT|WITH|GRANT|REVOKE|BEGIN|COMMIT)\b/i.test(o)&&t.push(`${s+1}: ${o.slice(0,120)}${o.length>120?"...":""}`)}return t.join(`
24
- `)}import E from"fs";import V from"path";function ee(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),n=!1,s=null,o=[],r=new Set;for(let i of t){if(/^##\s+Phases\s*$/i.test(i)){n=!0;continue}if(/^##\s+/.test(i)&&!/^###/.test(i)){s&&(o.push(s),s=null),n=!1;continue}if(!n)continue;let u=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(i);if(u){s&&o.push(s);let f=parseInt(u[1],10),c=u[2];if(r.has(f)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${f} ignored
25
- `),s=null;continue}r.add(f),s={phase_number:f,text:c,bodyLines:[]};continue}s&&s.bodyLines.push(i)}return s&&o.push(s),o.map(i=>({phase_number:i.phase_number,text:i.text,body:i.bodyLines.join(`
26
- `).trim()||null}))}function We(e,t,n){let s=e.prepare("SELECT id, phase_number, status, text FROM main.roadmap_phases WHERE decision_name = ? AND phase_number IS NOT NULL ORDER BY phase_number").all(t),o=new Map(s.map(h=>[h.phase_number,h])),r=new Set,i=e.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),u=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),f=e.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),c=e.prepare("UPDATE main.roadmap_phases SET phase_number = NULL WHERE id = ?"),a=[],p=[],m=[];for(let h=0;h<n.length;h++){let d=n[h];r.add(d.phase_number);let l=o.get(d.phase_number);if(l&&l.status==="done"&&l.text!==d.text){c.run(l.id);let y=u.run(t,d.phase_number,d.text,d.body,"open",h);p.push({id:Number(y.lastInsertRowid),phase_number:d.phase_number,text:d.text,replaced_shipped:l.id})}else if(l)i.run(d.text,d.body,"stale","open",l.id),a.push({id:l.id,phase_number:d.phase_number,text:d.text,reactivated:l.status==="stale"});else{let y=u.run(t,d.phase_number,d.text,d.body,"open",h);p.push({id:Number(y.lastInsertRowid),phase_number:d.phase_number,text:d.text})}}for(let[h,d]of o)!r.has(h)&&d.status!=="stale"&&d.status!=="done"&&(f.run(d.id),m.push({id:d.id,phase_number:h}));return{updated:a.length,inserted:p.length,staled:m.length,updated_phases:a,inserted_phases:p,staled_phases:m}}var O="roadmap",Wt=`# Roadmap
24
+ `)}import v from"fs";import V from"path";function ee(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),n=!1,s=null,o=[],r=new Set;for(let i of t){if(/^##\s+Phases\s*$/i.test(i)){n=!0;continue}if(/^##\s+/.test(i)&&!/^###/.test(i)){s&&(o.push(s),s=null),n=!1;continue}if(!n)continue;let l=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(i);if(l){s&&o.push(s);let p=parseInt(l[1],10),a=l[2];if(r.has(p)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${p} ignored
25
+ `),s=null;continue}r.add(p),s={phase_number:p,text:a,bodyLines:[]};continue}s&&s.bodyLines.push(i)}return s&&o.push(s),o.map(i=>({phase_number:i.phase_number,text:i.text,body:i.bodyLines.join(`
26
+ `).trim()||null}))}var We=(e,t)=>e+" :: "+t;function Ue(e,t,n,s={}){let o=e.prepare("SELECT id, phase_number, status, text FROM main.roadmap_phases WHERE decision_name = ? AND phase_number IS NOT NULL ORDER BY phase_number").all(t),r=new Map(o.map(b=>[b.phase_number,b])),i=new Set,l=e.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),p=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),a=e.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),c=e.prepare("UPDATE main.roadmap_phases SET phase_number = NULL WHERE id = ?"),d=e.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = 'open', resolved_at = NULL WHERE id = ?"),f=s.completed??null,y=f===null?null:new Set(f.map(b=>We(b.number,b.title))),m=[],u=[],g=[],_=[],w=[];for(let b=0;b<n.length;b++){let x=n[b];i.add(x.phase_number);let E=r.get(x.phase_number);if(E&&E.status==="done"&&E.text!==x.text){c.run(E.id);let A=p.run(t,x.phase_number,x.text,x.body,"open",b);_.push({id:Number(A.lastInsertRowid),phase_number:x.phase_number,text:x.text,replaced_shipped:E.id})}else if(E&&E.status==="done"&&y!==null&&!y.has(We(x.phase_number,x.text)))d.run(x.text,x.body,E.id),m.push({id:E.id,phase_number:x.phase_number,text:x.text});else if(E)l.run(x.text,x.body,"stale","open",E.id),g.push({id:E.id,phase_number:x.phase_number,text:x.text,reactivated:E.status==="stale"});else{let A=p.run(t,x.phase_number,x.text,x.body,"open",b);_.push({id:Number(A.lastInsertRowid),phase_number:x.phase_number,text:x.text})}}for(let[b,x]of r)!i.has(b)&&x.status!=="stale"&&x.status!=="done"&&(a.run(x.id),w.push({id:x.id,phase_number:b}));if(m.length>0){let b=n.reduce((x,E)=>Math.max(x,E.phase_number),0);for(let[x,E]of r)E.status==="done"&&x>b&&(c.run(E.id),u.push({id:E.id,phase_number:x,text:E.text}))}return{updated:g.length,inserted:_.length,staled:w.length,repaired:m.length,retired:u.length,updated_phases:g,inserted_phases:_,staled_phases:w,repaired_phases:m,retired_phases:u}}var j="roadmap",Jt=`# Roadmap
27
27
 
28
28
  The forward-looking plan: only upcoming and in-progress phases live here. When a
29
29
  phase is marked done (cairn_roadmap set_status <id> done) it is removed from this
@@ -34,72 +34,72 @@ the plan \u2014 Cairn syncs it into the live tracker on the next cairn_roadmap r
34
34
 
35
35
  ### Phase 1: First phase title
36
36
  What this phase delivers.
37
- `;function B(){return V.join(q(),"roadmap.md")}function Ut(){return V.join(q(),"roadmap_completed.md")}function re(){let e=B();return E.existsSync(e)?E.readFileSync(e,"utf8"):""}function Se(){let e=B();return E.existsSync(e)?!1:(E.writeFileSync(e,Wt,"utf8"),!0)}function Ht(e){let t=[];for(let n of e){let s=(n.text||"").trim();s&&(!Number.isInteger(n.phase_number)||n.phase_number<1||t.push({phase_number:n.phase_number,text:s,body:n.body??null}))}return t}var _e="decision_production_roadmap.md";function Jt(e){let t=e.match(/^---\n[\s\S]*?\n---\n?/);return t?e.slice(t[0].length):e}function Bt(e,t){let n=V.join(e,"MEMORY.md");if(!E.existsSync(n))return;let s=E.readFileSync(n,"utf8").split(/\r?\n/).filter(o=>!o.includes(`](${t})`));E.writeFileSync(n,s.join(`
38
- `),"utf8")}function Yt(){let e=B();if(E.existsSync(e))return null;let t=[],n=Te();n&&t.push(n),t.push(V.join(q(),"memory"));let s=null,o=null;for(let i of t){let u=V.join(i,_e);s==null&&E.existsSync(u)&&(s=Jt(E.readFileSync(u,"utf8")).trim(),o=u)}if(s==null)return null;let r=/^#\s/m.test(s)?"":`# Roadmap
37
+ `;function B(){return V.join(K(),"roadmap.md")}function Bt(){return V.join(K(),"roadmap_completed.md")}function re(){let e=B();return v.existsSync(e)?v.readFileSync(e,"utf8"):""}function _e(){let e=B();return v.existsSync(e)?!1:(v.writeFileSync(e,Jt,"utf8"),!0)}function Yt(e){let t=[];for(let n of e){let s=(n.text||"").trim();s&&(!Number.isInteger(n.phase_number)||n.phase_number<1||t.push({phase_number:n.phase_number,text:s,body:n.body??null}))}return t}var ge="decision_production_roadmap.md";function zt(e){let t=e.match(/^---\n[\s\S]*?\n---\n?/);return t?e.slice(t[0].length):e}function Gt(e,t){let n=V.join(e,"MEMORY.md");if(!v.existsSync(n))return;let s=v.readFileSync(n,"utf8").split(/\r?\n/).filter(o=>!o.includes(`](${t})`));v.writeFileSync(n,s.join(`
38
+ `),"utf8")}function Kt(){let e=B();if(v.existsSync(e))return null;let t=[],n=Te();n&&t.push(n),t.push(V.join(K(),"memory"));let s=null,o=null;for(let i of t){let l=V.join(i,ge);s==null&&v.existsSync(l)&&(s=zt(v.readFileSync(l,"utf8")).trim(),o=l)}if(s==null)return null;let r=/^#\s/m.test(s)?"":`# Roadmap
39
39
 
40
- `;E.writeFileSync(e,r+s+`
41
- `,"utf8");for(let i of t){let u=V.join(i,_e);try{E.existsSync(u)&&E.unlinkSync(u)}catch{}try{Bt(i,_e)}catch{}}return{migrated:!0,from:o,to:e}}function X(e){Yt();let t=B();if(!E.existsSync(t))return null;let n=E.readFileSync(t,"utf8");if(!/^##\s+Phases\s*$/im.test(n))return null;let s=Ht(ee(n));return We(e,O,s)}function ie(){let e=Ut();return E.existsSync(e)?E.readFileSync(e,"utf8").split(/\r?\n/).filter(t=>/^-\s+\d{4}-\d{2}-\d{2}\s+—\s+Phase\s/.test(t)).length:0}import Z from"fs";import xe from"path";import{execSync as zt}from"child_process";var Ue=120;function be(e){if(!e)return"";let t=String(e).replace(/`([^`]*)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/\[([^\]]+)\]\([^)]*\)/g,"$1").replace(/^[-*+\s]+/,"").replace(/^#{1,6}\s*/,"").replace(/[*~]/g,"").replace(/\s+/g," ").trim().replace(/[::.\s]+$/,"");return t.length>Ue&&(t=t.slice(0,Ue-1).trimEnd()+"\u2026"),t}function W(e){return String(e||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim()}function Gt(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),n=c=>c.map(a=>({text:be(a.titleRaw),body:a.bodyLines.join(`
42
- `).trim()||null})).filter(a=>a.text),s=[],o=null;for(let c of t){let a=/^(#{1,6})\s+(.+?)\s*$/.exec(c);if(a&&a[1].length>=2&&a[1].length<=4){o&&s.push(o),o={titleRaw:a[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&s.push(o);let r=[];o=null;for(let c of t){let a=/^(\d+)[.)]\s+(.+?)\s*$/.exec(c);if(a){o&&r.push(o),o={titleRaw:a[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&r.push(o);let i=[n(s),n(r)].filter(c=>c.length>=2);if(i.length)return i.sort((c,a)=>a.length-c.length),i[0];let u=t.find(c=>c.trim());return[{text:be(u||"Plan")||"Plan",body:e.trim()||null}]}function qt(e){let t=[];return e.forEach((n,s)=>{t.push(`### Phase ${s+1}: ${n.text}`),n.body&&n.body.trim()&&t.push(n.body.trim()),t.push("")}),t.join(`
40
+ `;v.writeFileSync(e,r+s+`
41
+ `,"utf8");for(let i of t){let l=V.join(i,ge);try{v.existsSync(l)&&v.unlinkSync(l)}catch{}try{Gt(i,ge)}catch{}}return{migrated:!0,from:o,to:e}}function X(e){Kt();let t=B();if(!v.existsSync(t))return null;let n=v.readFileSync(t,"utf8");if(!/^##\s+Phases\s*$/im.test(n))return null;let s=Yt(ee(n));return Ue(e,j,s,{completed:Se()})}var qt=/^-\s+(\d{4}-\d{2}-\d{2})\s+—\s+Phase\s+(\d+):\s*(.+?)(?:\s+\(([^)]*)\))?\s*$/,He=/^phase-(\d+)$/,Vt=/^[0-9a-f]{7,40}$/;function Se(){let e=Bt();if(!v.existsSync(e))return null;let t=[];for(let n of v.readFileSync(e,"utf8").split(/\r?\n/)){let s=qt.exec(n);if(!s)continue;let o=s[3],r=null,i=null;if(s[4]!=null){let l=s[4].split(/\s*,\s*/).filter(Boolean);if(l.length>0&&l.every(a=>He.test(a)||Vt.test(a)))for(let a of l){let c=He.exec(a);c?r=parseInt(c[1],10):i=a}else o=`${o} (${s[4]})`}t.push({date:s[1],number:parseInt(s[2],10),title:o,id:r,commit:i})}return t}import Z from"fs";import xe from"path";import{execSync as Xt}from"child_process";var Je=120;function be(e){if(!e)return"";let t=String(e).replace(/`([^`]*)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/\[([^\]]+)\]\([^)]*\)/g,"$1").replace(/^[-*+\s]+/,"").replace(/^#{1,6}\s*/,"").replace(/[*~]/g,"").replace(/\s+/g," ").trim().replace(/[::.\s]+$/,"");return t.length>Je&&(t=t.slice(0,Je-1).trimEnd()+"\u2026"),t}function U(e){return String(e||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim()}function Zt(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),n=a=>a.map(c=>({text:be(c.titleRaw),body:c.bodyLines.join(`
42
+ `).trim()||null})).filter(c=>c.text),s=[],o=null;for(let a of t){let c=/^(#{1,6})\s+(.+?)\s*$/.exec(a);if(c&&c[1].length>=2&&c[1].length<=4){o&&s.push(o),o={titleRaw:c[2],bodyLines:[]};continue}o&&o.bodyLines.push(a)}o&&s.push(o);let r=[];o=null;for(let a of t){let c=/^(\d+)[.)]\s+(.+?)\s*$/.exec(a);if(c){o&&r.push(o),o={titleRaw:c[2],bodyLines:[]};continue}o&&o.bodyLines.push(a)}o&&r.push(o);let i=[n(s),n(r)].filter(a=>a.length>=2);if(i.length)return i.sort((a,c)=>c.length-a.length),i[0];let l=t.find(a=>a.trim());return[{text:be(l||"Plan")||"Plan",body:e.trim()||null}]}function Qt(e){let t=[];return e.forEach((n,s)=>{t.push(`### Phase ${s+1}: ${n.text}`),n.body&&n.body.trim()&&t.push(n.body.trim()),t.push("")}),t.join(`
43
43
  `).replace(/\n{3,}/g,`
44
44
 
45
45
  `).trimEnd()+`
46
- `}function He(e){let t=re(),n=t.split(/\r?\n/),s=n.findIndex(c=>/^##\s+Phases\s*$/i.test(c)),o=qt(e);if(s===-1){let c=t.replace(/\s*$/,"");Z.writeFileSync(B(),`${c}
46
+ `}function Be(e){let t=re(),n=t.split(/\r?\n/),s=n.findIndex(a=>/^##\s+Phases\s*$/i.test(a)),o=Qt(e);if(s===-1){let a=t.replace(/\s*$/,"");Z.writeFileSync(B(),`${a}
47
47
 
48
48
  ## Phases
49
49
 
50
- ${o}`,"utf8");return}let r=n.length;for(let c=s+1;c<n.length;c++)if(/^##\s+/.test(n[c])&&!/^###/.test(n[c])){r=c;break}let i=n.slice(0,s+1).join(`
51
- `),u=n.slice(r).join(`
52
- `).replace(/^\n+/,""),f=`
50
+ ${o}`,"utf8");return}let r=n.length;for(let a=s+1;a<n.length;a++)if(/^##\s+/.test(n[a])&&!/^###/.test(n[a])){r=a;break}let i=n.slice(0,s+1).join(`
51
+ `),l=n.slice(r).join(`
52
+ `).replace(/^\n+/,""),p=`
53
53
 
54
- ${o}${u.trim()?`
55
- `+u:""}`;Z.writeFileSync(B(),i+f,"utf8")}function Je(e){let t=Gt(e);if(t.length===0)return{added:[],skipped:[],total:0};let s=Se()?[]:ee(re()).map(f=>({text:f.text,body:f.body})),o=new Set(s.map(f=>W(f.text))),r=[...s],i=[],u=[];for(let f of t){let c=W(f.text);if(o.has(c)){u.push(f.text);continue}o.add(c),r.push(f),i.push(f.text)}return i.length>0&&He(r),{added:i,skipped:u,total:r.length}}function Kt(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function Vt(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Be(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let n=t.map(m=>({text:be(m.content||m.activeForm||""),status:Kt(m.status)})).filter(m=>m.text);if(n.length===0)return{changed:!1};let s=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(O).filter(m=>m.status!=="stale");if(s.length===0){let h=Se()?[]:ee(re()).map(x=>({text:x.text,body:x.body})),d=new Set(h.map(x=>W(x.text))),l=[...h],y=0;for(let x of n){let R=W(x.text);d.has(R)||(d.add(R),l.push({text:x.text,body:null}),y++)}if(y===0)return{changed:!1};He(l);let _=X(e);return{changed:!0,seeded:y,diff:_}}let o=Vt(e);o==null&&(o=s.find(m=>m.status==="in_progress")?.id??s.find(m=>m.status==="open")?.id??s[0].id);let r=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),i=new Map(r.map(m=>[W(m.text),m])),u=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),f=e.prepare("UPDATE main.roadmap_phases SET status = ? WHERE id = ?"),c=e.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(o).n,a=0,p=0;for(let m of n){let h=i.get(W(m.text));if(h){if(h.status==="done"&&m.status!=="done")continue;h.status!==m.status&&(f.run(m.status,h.id),p++)}else u.run(o,m.text,m.status,c++),a++}return{changed:a>0||p>0,parentId:o,inserted:a,updated:p}}var Xt=new Set(["the","and","for","with","from","into","that","this","then","your","add","fix","update","make","phase","support","implement","feature","refactor","wire","wires","should","will","also","use","using","via"]);function Zt(e){return[...new Set(W(e).split(" ").filter(t=>t.length>=4&&!Xt.has(t)))]}function Qt(e,t){if(!e||!t?.length)return null;let n=String(e),s=/\bphase\s*#?\s*(\d+)\b/i.exec(n);if(s){let i=parseInt(s[1],10),u=t.find(f=>f.phase_number===i);if(u)return{...u,confidence:"high"}}let o=new Set(W(n).split(" ").filter(Boolean)),r=null;for(let i of t){let u=Zt(i.text);if(u.length<2)continue;let f=u.filter(a=>o.has(a)),c=f.length/u.length;f.length>=2&&c>=.6&&(!r||c>r.ratio)&&(r={...i,confidence:"medium",ratio:c})}return r?(delete r.ratio,r):null}function we(e,t){return zt(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3}).trim()}function Ye(e,t){let n=xe.join(t,".cairn"),s=xe.join(n,".last-commit"),o=xe.join(n,"roadmap-pending.json"),r;try{r=we(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let i=null;try{i=Z.readFileSync(s,"utf8").trim()||null}catch{}let u=[];if(i&&i!==r)try{u=we(t,`rev-list --no-merges --reverse ${i}..HEAD`).split(`
56
- `).filter(Boolean)}catch{u=[]}try{Z.writeFileSync(s,r,"utf8")}catch{}let f=[];try{let c=JSON.parse(Z.readFileSync(o,"utf8"));Array.isArray(c)&&(f=c)}catch{}if(u.length){let c=e.prepare("SELECT id, phase_number, text FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL AND status IN ('open','in_progress') AND phase_number IS NOT NULL").all(O);for(let a of u){let p="";try{p=we(t,`show -s --format=%s%n%b ${a}`)}catch{}let m=Qt(p,c);m&&!f.some(h=>h.phase_id===m.id)&&f.push({phase_id:m.id,phase_number:m.phase_number,text:m.text,commit:a.slice(0,7),confidence:m.confidence})}}if(f.length){let c=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(a=>a.id));f=f.filter(a=>c.has(a.phase_id))}try{Z.writeFileSync(o,JSON.stringify(f),"utf8")}catch{}return{candidates:f,newCommits:u.length}}import L from"fs";import j from"path";var ae="cairn",z="roadmap/1",en="<!-- slot:roadmap";function tn(e){return`${en} format=${z} provider=${ae} generated=${e} -->`}var nn=`<!-- ${ae}:roadmap -->`,sn=/^<!--\s*slot:roadmap\b([^>]*?)-->/,on=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,rn=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function an(e){if(typeof e!="string")return null;let t=e.indexOf(`
57
- `);return(t===-1?e:e.slice(0,t)).trim()}function cn(e){let t=an(e);if(!t)return null;let n=sn.exec(t);if(n){let o={};for(let r of n[1].matchAll(rn))o[r[1]]=r[2].replace(/^"|"$/g,"");return o.provider?{provider:o.provider.toLowerCase(),format:o.format||null,generated:o.generated||null}:null}let s=on.exec(t);return s?{provider:s[1].toLowerCase(),format:null,generated:null}:null}var ln=/^```roadmap-json[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/m;function Ge(e){if(typeof e!="string")return null;let t=ln.exec(e);if(!t)return null;try{let n=JSON.parse(t[1]);return n&&typeof n=="object"&&!Array.isArray(n)?n:null}catch{return null}}var un=new Set(["format","provider","generated","title","cursor","phases","migrated_from","migrated"]),pn=new Set(["id","number","title","status","goal","blocked_by","slices","detail","links"]);function qe(e,t){let n={};if(!e||typeof e!="object")return n;for(let s of Object.keys(e).sort())t.has(s)||(n[s]=e[s]);return n}function Ke(e,t=new Map){if(!Array.isArray(e))return t;for(let n of e)n&&typeof n=="object"&&typeof n.id=="string"&&(t.set(n.id,n),Ke(n.slices,t));return t}function dn(e){if(typeof e!="string")return null;let t=Ge(e)?.migrated_from;if(typeof t=="string"&&t)return t;let n=e.indexOf(`
58
- `);if(n===-1)return null;let s=e.indexOf("-->",n),o=s===-1?e.slice(0,512):e.slice(n,s),r=/^\s*migrated-from=(.+?)\s*$/m.exec(o);return r?r[1]:null}var fn=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,mn=["decision","knowledge","experience","preference"];function hn(e){let t=j.relative(H(),e).split(j.sep).join("/");return!t||t.startsWith("..")||j.isAbsolute(t)?null:t}function yn(e){let t=j.join(H(),".cairn","memory");return[j.join(t,`${e}.md`),...mn.map(n=>j.join(t,`${n}_${e}.md`))]}function gn(e){if(!e)return[];let t=[],n=new Set;for(let s of String(e).matchAll(fn)){let o=s[1].trim();if(!(!o||n.has(o))){n.add(o);for(let r of yn(o)){if(!L.existsSync(r))continue;let i=hn(r);i&&t.push({kind:"memory",title:o,path:i});break}}}return t}var _n={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Sn=new Set(["open","in_progress","done","blocked","stale"]);function xn(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function Ve(){return j.join(H(),"ROADMAP.md")}function Xe(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function wn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function bn(e){let t=new Map,n;try{n=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let s of n)t.has(s.blocked_id)||t.set(s.blocked_id,new Set),t.get(s.blocked_id).add(s.blocker_id);return t}function kn(e,t){let n=e.decision_name??"",s=t.decision_name??"";return n!==s?n<s?-1:1:e.phase_number!=null&&t.phase_number!=null?e.phase_number-t.phase_number:e.position-t.position||e.id-t.id}function En(e){let t;try{t=e.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, body, status, position FROM main.roadmap_phases ORDER BY decision_name, phase_number, position, id").all()}catch{return null}if(t.filter(l=>l.status!=="stale").length===0)return null;let n=wn(e),s=bn(e),o=new Map(t.map(l=>[l.id,l.status])),r=new Map(t.map(l=>[l.id,l.phase_number!=null?`Phase ${l.phase_number}`:l.text])),i=new Map(t.map(l=>[l.id,l])),u=new Map;for(let l of t){let y=l.parent_phase_id??"root";u.has(y)||u.set(y,[]),u.get(y).push(l)}for(let l of u.values())l.sort(kn);let f=new Map;for(let l of u.get("root")||[])l.decision_name!=null&&(l.decision_name===O&&l.phase_number==null||(f.has(l.decision_name)||f.set(l.decision_name,[]),f.get(l.decision_name).push(l)));let c=[...f.keys()].sort((l,y)=>l===O?-1:y===O?1:l<y?-1:l>y?1:0),a=new Map,p=!1;for(let l of c){let y=f.get(l).filter(x=>x.status!=="stale"),_=l===O?y.filter(x=>x.status!=="done"):y;_.length!==0&&(p=!0,a.set(l,{visible:_,doneCount:y.filter(x=>x.status==="done").length,total:y.length}))}return p?{cursor:n,decisionNames:c,visibleRoots:a,children:l=>(u.get(l)||[]).filter(y=>y.status!=="stale"),unfinishedBlockers:l=>{let y=s.get(l);return y?[...y].filter(_=>o.get(_)!=="done").map(_=>r.get(_)||`#${_}`):[]},blockerIds:l=>{let y=s.get(l);return y?[...y].filter(_=>i.has(_)).sort((_,x)=>_-x).map(_=>`phase-${_}`):[]}}:null}var ke=120;function $n(e){return e.replace(/\s*\((?:see\s+)?\[\[[^\]]+\]\](?:\s*(?:,|and)\s*\[\[[^\]]+\]\])*\s*\)/gi,"").replace(/\[\[([^\]]+)\]\]/g,(t,n)=>n.replace(/_/g," ").trim()).replace(/\s{2,}/g," ").replace(/\s+([.,;:!?])/g,"$1").trim()}function Ze(e){if(!e)return null;let t=[];for(let i of e.split(`
59
- `)){let u=i.trim();if(!u){if(t.length)break;continue}t.push(u)}if(!t.length)return null;let n=$n(t.join(" ").replace(/\s+/g," ")),s=null,o=/[.?!](?=\s|$)/g,r;for(;(r=o.exec(n))!==null&&(s=n.slice(0,r.index+1),!(s.length>=40)););if(s||(s=n),s.length>ke){let i=s.lastIndexOf(" ",ke-1);s=s.slice(0,i>60?i:ke-1).trimEnd()+"\u2026"}return s}function ze(e){let t=e.doneCount,n=e.total;if(!n)return"_No phases yet._";let s=Math.round(t/n*10);return`**${t} of ${n} done** \`${"\u2588".repeat(s)}${"\u2591".repeat(10-s)}\``}function Rn(e,t){let n=[tn(t.generated),"<!--"," Generated by cairn \u2014 do NOT edit by hand."," The plan lives in .cairn/roadmap.md; status and cursor come from cairn_roadmap."," This file is regenerated whenever the roadmap changes; manual edits are overwritten."," Per-phase detail and links are in the roadmap-json block at the end of this file."];if(t.migratedFrom&&(n.push(" The ROADMAP.md that was here before cairn adopted this file is kept at:"),n.push(` migrated-from=${t.migratedFrom}`)),n.push("-->","","# Roadmap",""),e==null){n.push("_No active plan right now._");let a=ie();return a>0&&n.push("","---","",`**Shipped so far:** ${a} phase${a===1?"":"s"}.`),n}let{cursor:s,decisionNames:o,visibleRoots:r,children:i,unfinishedBlockers:u}=e;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let f=(a,p)=>{let m=" ".repeat(p),h=a.phase_number!=null?`Phase ${a.phase_number}: ${a.text}`:a.text,d=_n[a.status]||a.status,l=u(a.id);l.length&&(d+=` \xB7 waiting on ${l.join(", ")}`),s===a.id&&(d+=" \xB7 \u2190 current"),n.push(`${m}- [${xn(a.status)}] **${h}** \u2014 ${d}`);let y=Ze(a.body);y&&n.push(`${m} ${y}`);for(let _ of i(a.id))f(_,p+1)};for(let a of o){let p=r.get(a);if(p){n.push(""),a===O?n.push("## Active plan","",ze(p)):n.push(`## ${a}`,"",ze(p)),n.push("");for(let m of p.visible)f(m,0)}}let c=ie();return c>0&&n.push("","---","",`**Shipped so far:** ${c} phase${c===1?"":"s"}.`),n}function Qe(e,t,n,s){let o=n.get(`phase-${e.id}`),r=Ze(e.body),i=gn(e.body),u=t.blockerIds(e.id),f={id:`phase-${e.id}`};if(e.phase_number!=null&&(f.number=e.phase_number),f.title=e.text,f.status=Sn.has(e.status)?e.status:"open",r&&(f.goal=r),u.length&&(f.blocked_by=u),!s){let c=[],a=p=>{for(let m of t.children(p))c.push(Qe(m,t,n,!0)),a(m.id)};a(e.id),c.length&&(f.slices=c)}return e.body&&e.body.trim()&&(f.detail=e.body.trim()),i.length&&(f.links=i),{...f,...qe(o,pn)}}function vn(e,t){let n=Ke(t.previous?.phases),s=[];for(let i of e?.decisionNames??[]){let u=e.visibleRoots.get(i);if(u)for(let f of u.visible)s.push(Qe(f,e,n,!1))}let o={format:z,provider:ae,generated:t.generated,title:`${j.basename(H())} roadmap`,cursor:e?.cursor!=null?`phase-${e.cursor}`:null};t.migratedFrom&&(o.migrated_from=t.migratedFrom,t.migrated&&(o.migrated=t.migrated)),o.phases=s;let r={...o,...qe(t.previous,un)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(r,null,2),"```","","</details>"]}function Ee(e,t={}){let n={generated:t.generated||Xe(),migratedFrom:t.migratedFrom||null,migrated:t.migrated||null,previous:t.previous&&typeof t.previous=="object"?t.previous:null},s=En(e);return s==null&&!t.allowEmpty?null:[...Rn(s,n),...vn(s,n)].join(`
54
+ ${o}${l.trim()?`
55
+ `+l:""}`;Z.writeFileSync(B(),i+p,"utf8")}function Ye(e){let t=Zt(e);if(t.length===0)return{added:[],skipped:[],total:0};let s=_e()?[]:ee(re()).map(p=>({text:p.text,body:p.body})),o=new Set(s.map(p=>U(p.text))),r=[...s],i=[],l=[];for(let p of t){let a=U(p.text);if(o.has(a)){l.push(p.text);continue}o.add(a),r.push(p),i.push(p.text)}return i.length>0&&Be(r),{added:i,skipped:l,total:r.length}}function en(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function tn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function ze(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let n=t.map(f=>({text:be(f.content||f.activeForm||""),status:en(f.status)})).filter(f=>f.text);if(n.length===0)return{changed:!1};let s=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(j).filter(f=>f.status!=="stale");if(s.length===0){let y=_e()?[]:ee(re()).map(w=>({text:w.text,body:w.body})),m=new Set(y.map(w=>U(w.text))),u=[...y],g=0;for(let w of n){let b=U(w.text);m.has(b)||(m.add(b),u.push({text:w.text,body:null}),g++)}if(g===0)return{changed:!1};Be(u);let _=X(e);return{changed:!0,seeded:g,diff:_}}let o=tn(e);o==null&&(o=s.find(f=>f.status==="in_progress")?.id??s.find(f=>f.status==="open")?.id??s[0].id);let r=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),i=new Map(r.map(f=>[U(f.text),f])),l=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),p=e.prepare("UPDATE main.roadmap_phases SET status = ? WHERE id = ?"),a=e.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(o).n,c=0,d=0;for(let f of n){let y=i.get(U(f.text));if(y){if(y.status==="done"&&f.status!=="done")continue;y.status!==f.status&&(p.run(f.status,y.id),d++)}else l.run(o,f.text,f.status,a++),c++}return{changed:c>0||d>0,parentId:o,inserted:c,updated:d}}var nn=new Set(["the","and","for","with","from","into","that","this","then","your","add","fix","update","make","phase","support","implement","feature","refactor","wire","wires","should","will","also","use","using","via"]);function sn(e){return[...new Set(U(e).split(" ").filter(t=>t.length>=4&&!nn.has(t)))]}function on(e,t){if(!e||!t?.length)return null;let n=String(e),s=/\bphase\s*#?\s*(\d+)\b/i.exec(n);if(s){let i=parseInt(s[1],10),l=t.find(p=>p.phase_number===i);if(l)return{...l,confidence:"high"}}let o=new Set(U(n).split(" ").filter(Boolean)),r=null;for(let i of t){let l=sn(i.text);if(l.length<2)continue;let p=l.filter(c=>o.has(c)),a=p.length/l.length;p.length>=2&&a>=.6&&(!r||a>r.ratio)&&(r={...i,confidence:"medium",ratio:a})}return r?(delete r.ratio,r):null}function we(e,t){return Xt(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3}).trim()}function Ge(e,t){let n=xe.join(t,".cairn"),s=xe.join(n,".last-commit"),o=xe.join(n,"roadmap-pending.json"),r;try{r=we(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let i=null;try{i=Z.readFileSync(s,"utf8").trim()||null}catch{}let l=[];if(i&&i!==r)try{l=we(t,`rev-list --no-merges --reverse ${i}..HEAD`).split(`
56
+ `).filter(Boolean)}catch{l=[]}try{Z.writeFileSync(s,r,"utf8")}catch{}let p=[];try{let a=JSON.parse(Z.readFileSync(o,"utf8"));Array.isArray(a)&&(p=a)}catch{}if(l.length){let a=e.prepare("SELECT id, phase_number, text FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL AND status IN ('open','in_progress') AND phase_number IS NOT NULL").all(j);for(let c of l){let d="";try{d=we(t,`show -s --format=%s%n%b ${c}`)}catch{}let f=on(d,a);f&&!p.some(y=>y.phase_id===f.id)&&p.push({phase_id:f.id,phase_number:f.phase_number,text:f.text,commit:c.slice(0,7),confidence:f.confidence})}}if(p.length){let a=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(c=>c.id));p=p.filter(c=>a.has(c.phase_id))}try{Z.writeFileSync(o,JSON.stringify(p),"utf8")}catch{}return{candidates:p,newCommits:l.length}}import C from"fs";import T from"path";var ie="cairn",z="roadmap/1",rn="<!-- slot:roadmap";function an(e){return`${rn} format=${z} provider=${ie} generated=${e} -->`}var cn=`<!-- ${ie}:roadmap -->`,ln=/^<!--\s*slot:roadmap\b([^>]*?)-->/,un=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,pn=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function dn(e){if(typeof e!="string")return null;let t=e.indexOf(`
57
+ `);return(t===-1?e:e.slice(0,t)).trim()}function fn(e){let t=dn(e);if(!t)return null;let n=ln.exec(t);if(n){let o={};for(let r of n[1].matchAll(pn))o[r[1]]=r[2].replace(/^"|"$/g,"");return o.provider?{provider:o.provider.toLowerCase(),format:o.format||null,generated:o.generated||null}:null}let s=un.exec(t);return s?{provider:s[1].toLowerCase(),format:null,generated:null}:null}var mn=/^```roadmap-json[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/m;function Ve(e){if(typeof e!="string")return null;let t=mn.exec(e);if(!t)return null;try{let n=JSON.parse(t[1]);return n&&typeof n=="object"&&!Array.isArray(n)?n:null}catch{return null}}var hn=new Set(["format","provider","generated","title","cursor","phases","migrated_from","migrated"]),yn=new Set(["id","number","title","status","goal","blocked_by","slices","detail","links"]);function Xe(e,t){let n={};if(!e||typeof e!="object")return n;for(let s of Object.keys(e).sort())t.has(s)||(n[s]=e[s]);return n}function Ze(e,t=new Map){if(!Array.isArray(e))return t;for(let n of e)n&&typeof n=="object"&&typeof n.id=="string"&&(t.set(n.id,n),Ze(n.slices,t));return t}function gn(e){if(typeof e!="string")return null;let t=Ve(e)?.migrated_from;if(typeof t=="string"&&t)return t;let n=e.indexOf(`
58
+ `);if(n===-1)return null;let s=e.indexOf("-->",n),o=s===-1?e.slice(0,512):e.slice(n,s),r=/^\s*migrated-from=(.+?)\s*$/m.exec(o);return r?r[1]:null}var _n=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,Sn=["decision","knowledge","experience","preference"];function xn(e){let t=T.relative(H(),e).split(T.sep).join("/");return!t||t.startsWith("..")||T.isAbsolute(t)?null:t}function wn(e){let t=T.join(H(),".cairn","memory");return[T.join(t,`${e}.md`),...Sn.map(n=>T.join(t,`${n}_${e}.md`))]}function bn(e){if(!e)return[];let t=[],n=new Set;for(let s of String(e).matchAll(_n)){let o=s[1].trim();if(!(!o||n.has(o))){n.add(o);for(let r of wn(o)){if(!C.existsSync(r))continue;let i=xn(r);i&&t.push({kind:"memory",title:o,path:i});break}}}return t}var En={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},kn=new Set(["open","in_progress","done","blocked","stale"]);function $n(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function Qe(){return T.join(H(),"ROADMAP.md")}function et(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function Rn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function vn(e){let t=new Map,n;try{n=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let s of n)t.has(s.blocked_id)||t.set(s.blocked_id,new Set),t.get(s.blocked_id).add(s.blocker_id);return t}function On(e,t){let n=e.decision_name??"",s=t.decision_name??"";return n!==s?n<s?-1:1:e.phase_number!=null&&t.phase_number!=null?e.phase_number-t.phase_number:e.position-t.position||e.id-t.id}function Pn(e){let t;try{t=e.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, body, status, position FROM main.roadmap_phases ORDER BY decision_name, phase_number, position, id").all()}catch{return null}if(t.filter(u=>u.status!=="stale").length===0)return null;let n=Rn(e),s=vn(e),o=new Map(t.map(u=>[u.id,u.status])),r=new Map(t.map(u=>[u.id,u.phase_number!=null?`Phase ${u.phase_number}`:u.text])),i=new Map(t.map(u=>[u.id,u])),l=new Map;for(let u of t){let g=u.parent_phase_id??"root";l.has(g)||l.set(g,[]),l.get(g).push(u)}for(let u of l.values())u.sort(On);let p=new Map;for(let u of l.get("root")||[])u.decision_name!=null&&(u.decision_name===j&&u.phase_number==null||(p.has(u.decision_name)||p.set(u.decision_name,[]),p.get(u.decision_name).push(u)));let a=[...p.keys()].sort((u,g)=>u===j?-1:g===j?1:u<g?-1:u>g?1:0),c=new Map,d=!1;for(let u of a){let g=p.get(u).filter(w=>w.status!=="stale"),_=u===j?g.filter(w=>w.status!=="done"):g;_.length!==0&&(d=!0,c.set(u,{visible:_,doneCount:g.filter(w=>w.status==="done").length,total:g.length}))}return d?{cursor:n,decisionNames:a,visibleRoots:c,children:u=>(l.get(u)||[]).filter(g=>g.status!=="stale"),unfinishedBlockers:u=>{let g=s.get(u);return g?[...g].filter(_=>o.get(_)!=="done").map(_=>r.get(_)||`#${_}`):[]},blockerIds:u=>{let g=s.get(u);return g?[...g].filter(_=>i.has(_)).sort((_,w)=>_-w).map(_=>`phase-${_}`):[]}}:null}var Ee=120;function jn(e){return e.replace(/\s*\((?:see\s+)?\[\[[^\]]+\]\](?:\s*(?:,|and)\s*\[\[[^\]]+\]\])*\s*\)/gi,"").replace(/\[\[([^\]]+)\]\]/g,(t,n)=>n.replace(/_/g," ").trim()).replace(/\s{2,}/g," ").replace(/\s+([.,;:!?])/g,"$1").trim()}function tt(e){if(!e)return null;let t=[];for(let i of e.split(`
59
+ `)){let l=i.trim();if(!l){if(t.length)break;continue}t.push(l)}if(!t.length)return null;let n=jn(t.join(" ").replace(/\s+/g," ")),s=null,o=/[.?!](?=\s|$)/g,r;for(;(r=o.exec(n))!==null&&(s=n.slice(0,r.index+1),!(s.length>=40)););if(s||(s=n),s.length>Ee){let i=s.lastIndexOf(" ",Ee-1);s=s.slice(0,i>60?i:Ee-1).trimEnd()+"\u2026"}return s}var Tn=8;function Ke(){let e=Se();if(!e||e.length===0)return[];let t=e.slice(-Tn).reverse(),n=["","---","","## Recently shipped",""];for(let o of t)n.push(`- ${o.date} \u2014 ${o.title}${o.commit?` \`${o.commit}\``:""}`);let s=e.length-t.length;return n.push("",s>0?`_${e.length} phases shipped in total; ${s} earlier one${s===1?"":"s"} not listed._`:`_${e.length} phase${e.length===1?"":"s"} shipped in total._`),n}function qe(e){let t=e.doneCount,n=e.total;if(!n)return"_No phases yet._";let s=Math.round(t/n*10);return`**${t} of ${n} done** \`${"\u2588".repeat(s)}${"\u2591".repeat(10-s)}\``}function Nn(e,t){let n=[an(t.generated),"<!--"," Generated by cairn \u2014 do NOT edit by hand."," The plan lives in .cairn/roadmap.md; status and cursor come from cairn_roadmap."," This file is regenerated whenever the roadmap changes; manual edits are overwritten."," Per-phase detail and links are in the roadmap-json block at the end of this file."];if(t.migratedFrom&&(n.push(" The ROADMAP.md that was here before cairn adopted this file is kept at:"),n.push(` migrated-from=${t.migratedFrom}`)),n.push("-->","","# Roadmap",""),e==null)return n.push("_No active plan right now._"),n.push(...Ke()),n;let{cursor:s,decisionNames:o,visibleRoots:r,children:i,unfinishedBlockers:l}=e;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let p=(a,c)=>{let d=" ".repeat(c),f=a.phase_number!=null?`Phase ${a.phase_number}: ${a.text}`:a.text,y=En[a.status]||a.status,m=l(a.id);m.length&&(y+=` \xB7 waiting on ${m.join(", ")}`),s===a.id&&(y+=" \xB7 \u2190 current"),n.push(`${d}- [${$n(a.status)}] **${f}** \u2014 ${y}`);let u=tt(a.body);u&&n.push(`${d} ${u}`);for(let g of i(a.id))p(g,c+1)};for(let a of o){let c=r.get(a);if(c){n.push(""),a===j?n.push("## Active plan","",qe(c)):n.push(`## ${a}`,"",qe(c)),n.push("");for(let d of c.visible)p(d,0)}}return n.push(...Ke()),n}function nt(e,t,n,s){let o=n.get(`phase-${e.id}`),r=tt(e.body),i=bn(e.body),l=t.blockerIds(e.id),p={id:`phase-${e.id}`};if(e.phase_number!=null&&(p.number=e.phase_number),p.title=e.text,p.status=kn.has(e.status)?e.status:"open",r&&(p.goal=r),l.length&&(p.blocked_by=l),!s){let a=[],c=d=>{for(let f of t.children(d))a.push(nt(f,t,n,!0)),c(f.id)};c(e.id),a.length&&(p.slices=a)}return e.body&&e.body.trim()&&(p.detail=e.body.trim()),i.length&&(p.links=i),{...p,...Xe(o,yn)}}function Mn(e,t){let n=Ze(t.previous?.phases),s=[];for(let i of e?.decisionNames??[]){let l=e.visibleRoots.get(i);if(l)for(let p of l.visible)s.push(nt(p,e,n,!1))}let o={format:z,provider:ie,generated:t.generated,title:`${T.basename(H())} roadmap`,cursor:e?.cursor!=null?`phase-${e.cursor}`:null};t.migratedFrom&&(o.migrated_from=t.migratedFrom,t.migrated&&(o.migrated=t.migrated)),o.phases=s;let r={...o,...Xe(t.previous,hn)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(r,null,2),"```","","</details>"]}function ke(e,t={}){let n={generated:t.generated||et(),migratedFrom:t.migratedFrom||null,migrated:t.migrated||null,previous:t.previous&&typeof t.previous=="object"?t.previous:null},s=Pn(e);return s==null&&!t.allowEmpty?null:[...Nn(s,n),...Mn(s,n)].join(`
60
60
  `)+`
61
- `}function On(e){let t=j.join(e,"ROADMAP.bak.md");if(!L.existsSync(t))return t;for(let n=2;n<=1e3;n++){let s=j.join(e,`ROADMAP.bak.${n}.md`);if(!L.existsSync(s))return s}return null}function jn(e,t){let n=j.join(j.dirname(e),`.${j.basename(e)}.${process.pid}.tmp`);try{L.writeFileSync(n,t,"utf8"),L.renameSync(n,e)}catch(s){try{L.unlinkSync(n)}catch{}throw s}}function Y(e,t,n={}){return{written:!1,reason:e,warning:`[cairn] ROADMAP.md was not updated \u2014 ${e}`,path:t,...n}}function et(e){let t=Ve(),n=null;if(L.existsSync(t))try{n=L.readFileSync(t,"utf8")}catch(l){return Y(`the existing file could not be read (${l.message})`,t)}let s=cn(n),o=n!=null&&(s?.provider===ae||n.startsWith(nn));if(n!=null&&s&&!o)return{written:!1,reason:`ROADMAP.md is generated by another provider (${s.provider}) \u2014 refusing to overwrite`,conflict_provider:s.provider,warning:`[cairn] ROADMAP.md is published by "${s.provider}", so cairn left it untouched and the file may not reflect cairn's plan. Two roadmap providers are claiming the same file \u2014 decide which one owns it.`,path:t};let r=o?Ge(n):null,i=o?dn(n):null,u=o&&typeof r?.migrated=="string"?r.migrated:null,f=typeof r?.generated=="string"?r.generated:null,c=!1,a=Ee(e,{generated:f||"1970-01-01T00:00:00Z",migratedFrom:i,migrated:u,previous:r});if(a==null){if(!o)return{written:!1,reason:"empty roadmap \u2014 no file written",path:t};c=!0,a=Ee(e,{generated:f||"1970-01-01T00:00:00Z",migratedFrom:i,migrated:u,previous:r,allowEmpty:!0})}if(o&&n===a)return{written:!1,unchanged:!0,path:t,bytes:Buffer.byteLength(a)};let p=Xe(),m=null,h=null;if(n!=null&&!o){let l=On(j.dirname(t));if(l==null)return Y("no free ROADMAP.bak name was available to preserve the existing file",t);try{L.renameSync(t,l)}catch(y){return Y(`the existing file could not be preserved (${y.message})`,t)}m=j.basename(l),h=l,i=m,u=p}a=Ee(e,{generated:p,migratedFrom:i,migrated:u,previous:r,allowEmpty:c});try{jn(t,a)}catch(l){if(h)try{return L.renameSync(h,t),Y(`write failed (${l.message}); the original ROADMAP.md was restored`,t)}catch{return Y(`write failed (${l.message}); the original ROADMAP.md is at ${m} and must be restored by hand`,t,{backed_up_to:m})}return Y(`write failed (${l.message})`,t)}let d={written:!0,path:t,bytes:Buffer.byteLength(a),format:z,generated:p};return m&&(d.backed_up_to=m,d.warning=`[cairn] The existing ROADMAP.md was not cairn-generated. It has been preserved as ${m}, and ROADMAP.md is now generated from cairn's roadmap.`),d}function Pn(e){try{return et(e)}catch(t){process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${t.message}
62
- `);let n=null;try{n=Ve()}catch{}return Y(`publish failed (${t.message})`,n)}}function ce(e){let t=Pn(e);return t&&t.warning?t.warning:null}var Tn=new Set(["open","in_progress"]);function Nn(e,t){e.prepare("INSERT INTO main.roadmap_cursor (singleton, phase_id) VALUES (1, ?) ON CONFLICT(singleton) DO UPDATE SET phase_id = excluded.phase_id").run(t)}function Mn(e,t){let n=e.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),s=e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),o=n.get(t);for(;o&&o.parent_phase_id!=null;)s.run(o.parent_phase_id),o=n.get(o.parent_phase_id)}function tt(e,t){Nn(e,t),e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(t),Mn(e,t)}function An(e){let t=new Map,n;try{n=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let s of n)t.has(s.blocked_id)||t.set(s.blocked_id,new Set),t.get(s.blocked_id).add(s.blocker_id);return t}function Fn(e,t,n){let s=t.get(e);if(!s||s.size===0)return[];let o=[];for(let r of s)n.get(r)!=="done"&&o.push(r);return o}function nt(e,t){let n=e.prepare("SELECT id, decision_name, phase_number, parent_phase_id, status, position, text FROM main.roadmap_phases ORDER BY decision_name, phase_number, position, id").all();if(n.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let s=new Map(n.map(p=>[p.id,p.status])),o=An(e),r=new Map;for(let p of n){let m=p.parent_phase_id??`root:${p.decision_name}`;r.has(m)||r.set(m,[]),r.get(m).push(p)}for(let p of r.values())p.sort((m,h)=>m.phase_number!=null&&h.phase_number!=null?m.phase_number-h.phase_number:m.position-h.position||m.id-h.id);let i=[],u=new Set;for(let p of n){if(p.parent_phase_id!=null||u.has(p.decision_name))continue;u.add(p.decision_name);let m=[],h=r.get(`root:${p.decision_name}`)||[];for(let d=h.length-1;d>=0;d--)m.push(h[d]);for(;m.length;){let d=m.pop();i.push(d);let l=r.get(d.id)||[];for(let y=l.length-1;y>=0;y--)m.push(l[y])}}let f=p=>Tn.has(p.status),c=[],a=0;if(t!=null){let p=i.findIndex(m=>m.id===t);a=p===-1?0:p+1}for(let p=a;p<i.length;p++){let m=i[p];if(!f(m))continue;let h=Fn(m.id,o,s);if(h.length>0){c.push({id:m.id,blockers:h});continue}let d=t!=null?n.find(_=>_.id===t):null,l=d&&d.decision_name!==m.decision_name,y;return t==null?y=`First actionable phase: ${m.decision_name??"(sub-task)"} / Phase ${m.phase_number??"\xB7"}.`:l?y=`Previous decision "${d.decision_name}" is complete \u2014 advanced to "${m.decision_name}".`:y=`Next open phase in "${m.decision_name??"(sub-task)"}" after id ${t}.`,c.length>0&&(y+=` Skipped ${c.length} dep-blocked phase(s): ${c.map(_=>`id ${_.id} (blocked by ${_.blockers.join(",")})`).join("; ")}.`),{id:m.id,reason:y,skipped_blocked:c}}return c.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${c.map(p=>`id ${p.id} (blocked by ${p.blockers.join(",")})`).join("; ")}.`,skipped_blocked:c}:{id:null,reason:"No actionable phases remaining."}}var Dn=new Set(["java","typescript","javascript","vue","python","sql"]);function $e(e,t){let n={hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e};t&&(n.additionalContext=t),process.stdout.write(JSON.stringify({decision:"block",reason:e,hookSpecificOutput:n})),process.exit(0)}var it={name:"cairn",contract:1,title:"cairn",slots:["roadmap"],provides:{roadmap:{path:"ROADMAP.md",format:z}},spec:"Project memory and roadmap. Publishes the plan as ROADMAP.md in the roadmap/1 slot format."};function Cn(){let e=S.join(G.homedir(),".claude.json");if(!g.existsSync(e))return!1;let t;try{t=JSON.parse(g.readFileSync(e,"utf8"))}catch{return!1}let n=t?.mcpServers?.cairn;if(!n||typeof n!="object")return!1;let s=n.terminalConnect,o=s?.provides?.roadmap?.format;if(s&&o===z)return!1;n.terminalConnect=it;try{return g.writeFileSync(e,JSON.stringify(t,null,2),"utf8"),!0}catch{return!1}}function at(e){let t=S.join(G.homedir(),".claude","projects",pe(e));return g.existsSync(t)?S.join(t,"memory"):null}function ct(e){try{return g.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}}function In(e){let t=at(e);if(!t||!g.existsSync(t))return 0;let n=S.join(e,".cairn","memory");g.mkdirSync(n,{recursive:!0});let s=0;for(let o of ct(t)){let r=S.join(t,o),i;try{i=g.statSync(r)}catch{continue}if(!i.isFile())continue;let u=S.join(n,o),f=!g.existsSync(u);if(!f)try{f=i.mtimeMs>g.statSync(u).mtimeMs}catch{f=!0}if(f){g.copyFileSync(r,u);try{g.utimesSync(u,i.atime,i.mtime)}catch{}s++}}return s}function Wn(e){let t=at(e);if(!t||(g.mkdirSync(t,{recursive:!0}),ct(t).some(o=>o!=="MEMORY.md")))return 0;let n=S.join(e,".cairn","memory");if(!g.existsSync(n))return 0;let s=0;for(let o of g.readdirSync(n)){let r=S.join(n,o),i;try{i=g.statSync(r)}catch{continue}if(i.isFile()){g.copyFileSync(r,S.join(t,o));try{g.utimesSync(S.join(t,o),i.atime,i.mtime)}catch{}s++}}return s}function st(e,t){let n=new Date(t.checkpoint_at).toLocaleString(),r=[` message: ${/^Auto-checkpoint\b/.test(t.message||"")&&t.last_manual_message||t.message}`];r.push(` would_lose: ${me(se(e,t))}`),r.push(` checkpoint: ${n}`),t.active_files?.length>0&&r.push(` active_files: ${t.active_files.join(", ")}`),t.notes?.length>0&&r.push(` notes: ${t.notes.join(" | ")}`);let i=c=>{try{return Ln(c,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}};try{let c=new Date(t.checkpoint_at).toISOString(),a=i(`git log --oneline --after="${c}"`);a!==null&&r.push(` commits_since: ${a?a.split(`
63
- `).join("; "):"(none)"}`)}catch{}let u=i("git diff --name-only HEAD");u&&r.push(` uncommitted: ${u.split(`
64
- `).join(", ")}`);let f=i("git status --short");return f&&r.push(` git_status:
65
- ${f.split(`
66
- `).map(c=>" "+c).join(`
67
- `)}`),r.push(" \u2192 Call cairn_resume for full index status and open todos."),r}async function ot(e){let t={active:!1},n=S.join(e,"index.db");if(!g.existsSync(n))return t;let s;try{({DatabaseSync:s}=await import("node:sqlite"))}catch{return t}let o;try{o=new s(n,{readonly:!0})}catch{return t}let r=u=>u.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");function i(u,f){if(!g.existsSync(u))return null;let c=g.readFileSync(u,"utf8"),a=!1,p=!1,m=[];for(let h of c.split(/\r?\n/)){if(/^##\s+Phases\s*$/i.test(h)){a=!0;continue}if(a&&/^##\s+/.test(h)&&!/^###/.test(h))break;if(!a)continue;let d=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(h);if(d){if(p)break;parseInt(d[1],10)===f&&(m.push(`### Phase ${d[1]}: ${d[2]}`),p=!0);continue}p&&m.push(h)}return m.join(`
68
- `).trim()||null}try{let f=o.prepare("SELECT phase_id FROM roadmap_cursor WHERE singleton = 1").get()?.phase_id??null;if(f==null)return o.close(),t;let c=o.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, status, position FROM roadmap_phases ORDER BY decision_name, phase_number, position, id").all(),a={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},p=new Map;for(let k of c){let w=k.parent_phase_id??"root";p.has(w)||p.set(w,[]),p.get(w).push(k)}for(let k of p.values())k.sort((w,b)=>w.decision_name&&b.decision_name&&w.decision_name!==b.decision_name?w.decision_name.localeCompare(b.decision_name):w.phase_number!=null&&b.phase_number!=null?w.phase_number-b.phase_number:w.position-b.position||w.id-b.id);let m=p.get("root")||[],h=new Map;for(let k of m)k.decision_name!=null&&(h.has(k.decision_name)||h.set(k.decision_name,[]),h.get(k.decision_name).push(k));let d=[],l=(k,w)=>{for(let b of p.get(k)||[]){if(b.status==="stale")continue;let le=f===b.id?" \u2192":"",ue=b.phase_number!=null?`Phase ${b.phase_number}: ${b.text}`:b.text;d.push(`${" ".repeat(w)}${a[b.status]||"[?]"}${le} ${ue}`),l(b.id,w+1)}};for(let[k,w]of h){let b=w.filter(v=>v.status!=="stale");if(b.length===0)continue;let le=b.every(v=>v.status==="done"),ue=b.some(v=>v.status==="in_progress"),lt=le?"done":ue?"in_progress":"open",ut=b.filter(v=>v.status==="done").length,pt=k===O?"Roadmap":`Decision: ${k}`;d.push(`${a[lt]} ${pt} (${ut}/${b.length} done)`);for(let v of w){if(v.status==="stale"||k===O&&v.status==="done")continue;let dt=f===v.id?" \u2192":"";d.push(` ${a[v.status]||"[?]"}${dt} Phase ${v.phase_number}: ${v.text}`),l(v.id,2)}}let y=k=>c.find(w=>w.id===k),_=y(f),x=_?.decision_name??null,R=_?.phase_number??null,T=_;for(;T&&T.parent_phase_id!=null&&(T=y(T.parent_phase_id),!!T);)x=T.decision_name??x,R=T.phase_number??R;o.close();let C=_?_.phase_number!=null?`${_.decision_name} / Phase ${_.phase_number}: ${_.text}`:`${x} / Phase ${R} \u2192 ${_.text}`:"(unknown)",U=S.join(e,".last-roadmap-cursor"),Re=null;try{Re=parseInt(g.readFileSync(U,"utf8").trim(),10)}catch{}let ve=Re!==f;try{g.writeFileSync(U,String(f),"utf8")}catch{}let Oe=null;if(ve&&x!=null&&R!=null){let k=x===O?S.join(e,"roadmap.md"):S.join(e,"memory",`decision_${r(x)}.md`),w=i(k,R);w&&(Oe=w.split(`
69
- `))}return{active:!0,currentLabel:C,treeLines:d,advanced:ve,memoLines:Oe}}catch{try{o?.close()}catch{}return t}}process.on("uncaughtException",e=>{process.stderr.write(`cairn: ${e.message}
70
- `),process.exit(0)});var P=process.argv[2];if(P==="--version"||P==="-v"){let e=JSON.parse(g.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(P==="minify"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t,n;try{n=JSON.parse(e)?.tool_input||{},t=n.file_path}catch{process.exit(0)}t||process.exit(0),(n.offset!=null||n.limit!=null)&&process.exit(0),t=S.resolve(t);let s=ye(t);s||process.exit(0);let o=S.join(s,"views"),r=S.relative(o,t);!r.startsWith("..")&&!S.isAbsolute(r)&&process.exit(0);let i=J(s),f=oe(s,i)>0,c=i[t];c?.state==="edit-ready"&&(f&&M(s,i),process.exit(0)),c?.state==="compressed"&&(c.state="edit-ready",M(s,i),process.exit(0));let a=S.extname(t).toLowerCase(),p=je[a];(!p||!Dn.has(p))&&(f&&M(s,i),process.exit(0));let m;try{m=g.readFileSync(t,"utf8")}catch{f&&M(s,i),process.exit(0)}let h=m.split(`
71
- `).length,d=p==="vue"?Pe(m):te(m,p),l=d.split(`
72
- `).length,y=h>0?Math.round((1-l/h)*100):0,_=Ce(s,t);g.mkdirSync(S.dirname(_),{recursive:!0}),g.writeFileSync(_,d,"utf8");let x=c?.readCount??0;i[t]={tempPath:_,state:"compressed",minifiedAt:g.statSync(t).mtimeMs,readCount:x+1},M(s,i);let R=l>De,T=x>0;if(T||R){let C=Ie(d,p),U=T?`[cairn: ${h} \u2192 ${l} lines | seen \xD7${x+1} this session]`:`[cairn: ${h} \u2192 outline (${l} minified lines)]`;process.stderr.write(`${U}
73
- ${C}
61
+ `}function An(e){let t=T.join(e,"ROADMAP.bak.md");if(!C.existsSync(t))return t;for(let n=2;n<=1e3;n++){let s=T.join(e,`ROADMAP.bak.${n}.md`);if(!C.existsSync(s))return s}return null}function Fn(e,t){let n=T.join(T.dirname(e),`.${T.basename(e)}.${process.pid}.tmp`);try{C.writeFileSync(n,t,"utf8"),C.renameSync(n,e)}catch(s){try{C.unlinkSync(n)}catch{}throw s}}function Y(e,t,n={}){return{written:!1,reason:e,warning:`[cairn] ROADMAP.md was not updated \u2014 ${e}`,path:t,...n}}function st(e){let t=Qe(),n=null;if(C.existsSync(t))try{n=C.readFileSync(t,"utf8")}catch(u){return Y(`the existing file could not be read (${u.message})`,t)}let s=fn(n),o=n!=null&&(s?.provider===ie||n.startsWith(cn));if(n!=null&&s&&!o)return{written:!1,reason:`ROADMAP.md is generated by another provider (${s.provider}) \u2014 refusing to overwrite`,conflict_provider:s.provider,warning:`[cairn] ROADMAP.md is published by "${s.provider}", so cairn left it untouched and the file may not reflect cairn's plan. Two roadmap providers are claiming the same file \u2014 decide which one owns it.`,path:t};let r=o?Ve(n):null,i=o?gn(n):null,l=o&&typeof r?.migrated=="string"?r.migrated:null,p=typeof r?.generated=="string"?r.generated:null,a=!1,c=ke(e,{generated:p||"1970-01-01T00:00:00Z",migratedFrom:i,migrated:l,previous:r});if(c==null){if(!o)return{written:!1,reason:"empty roadmap \u2014 no file written",path:t};a=!0,c=ke(e,{generated:p||"1970-01-01T00:00:00Z",migratedFrom:i,migrated:l,previous:r,allowEmpty:!0})}if(o&&n===c)return{written:!1,unchanged:!0,path:t,bytes:Buffer.byteLength(c)};let d=et(),f=null,y=null;if(n!=null&&!o){let u=An(T.dirname(t));if(u==null)return Y("no free ROADMAP.bak name was available to preserve the existing file",t);try{C.renameSync(t,u)}catch(g){return Y(`the existing file could not be preserved (${g.message})`,t)}f=T.basename(u),y=u,i=f,l=d}c=ke(e,{generated:d,migratedFrom:i,migrated:l,previous:r,allowEmpty:a});try{Fn(t,c)}catch(u){if(y)try{return C.renameSync(y,t),Y(`write failed (${u.message}); the original ROADMAP.md was restored`,t)}catch{return Y(`write failed (${u.message}); the original ROADMAP.md is at ${f} and must be restored by hand`,t,{backed_up_to:f})}return Y(`write failed (${u.message})`,t)}let m={written:!0,path:t,bytes:Buffer.byteLength(c),format:z,generated:d};return f&&(m.backed_up_to=f,m.warning=`[cairn] The existing ROADMAP.md was not cairn-generated. It has been preserved as ${f}, and ROADMAP.md is now generated from cairn's roadmap.`),m}function Ln(e){try{return st(e)}catch(t){process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${t.message}
62
+ `);let n=null;try{n=Qe()}catch{}return Y(`publish failed (${t.message})`,n)}}function ae(e){let t=Ln(e);return t&&t.warning?t.warning:null}var Dn=new Set(["open","in_progress"]);function Cn(e,t){e.prepare("INSERT INTO main.roadmap_cursor (singleton, phase_id) VALUES (1, ?) ON CONFLICT(singleton) DO UPDATE SET phase_id = excluded.phase_id").run(t)}function In(e,t){let n=e.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),s=e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),o=n.get(t);for(;o&&o.parent_phase_id!=null;)s.run(o.parent_phase_id),o=n.get(o.parent_phase_id)}function ot(e,t){Cn(e,t),e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(t),In(e,t)}function Wn(e){let t=new Map,n;try{n=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let s of n)t.has(s.blocked_id)||t.set(s.blocked_id,new Set),t.get(s.blocked_id).add(s.blocker_id);return t}function Un(e,t,n){let s=t.get(e);if(!s||s.size===0)return[];let o=[];for(let r of s)n.get(r)!=="done"&&o.push(r);return o}function rt(e,t){let n=e.prepare("SELECT id, decision_name, phase_number, parent_phase_id, status, position, text FROM main.roadmap_phases ORDER BY decision_name, phase_number, position, id").all();if(n.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let s=new Map(n.map(d=>[d.id,d.status])),o=Wn(e),r=new Map;for(let d of n){let f=d.parent_phase_id??`root:${d.decision_name}`;r.has(f)||r.set(f,[]),r.get(f).push(d)}for(let d of r.values())d.sort((f,y)=>f.phase_number!=null&&y.phase_number!=null?f.phase_number-y.phase_number:f.position-y.position||f.id-y.id);let i=[],l=new Set;for(let d of n){if(d.parent_phase_id!=null||l.has(d.decision_name))continue;l.add(d.decision_name);let f=[],y=r.get(`root:${d.decision_name}`)||[];for(let m=y.length-1;m>=0;m--)f.push(y[m]);for(;f.length;){let m=f.pop();i.push(m);let u=r.get(m.id)||[];for(let g=u.length-1;g>=0;g--)f.push(u[g])}}let p=d=>Dn.has(d.status),a=[],c=0;if(t!=null){let d=i.findIndex(f=>f.id===t);c=d===-1?0:d+1}for(let d=c;d<i.length;d++){let f=i[d];if(!p(f))continue;let y=Un(f.id,o,s);if(y.length>0){a.push({id:f.id,blockers:y});continue}let m=t!=null?n.find(_=>_.id===t):null,u=m&&m.decision_name!==f.decision_name,g;return t==null?g=`First actionable phase: ${f.decision_name??"(sub-task)"} / Phase ${f.phase_number??"\xB7"}.`:u?g=`Previous decision "${m.decision_name}" is complete \u2014 advanced to "${f.decision_name}".`:g=`Next open phase in "${f.decision_name??"(sub-task)"}" after id ${t}.`,a.length>0&&(g+=` Skipped ${a.length} dep-blocked phase(s): ${a.map(_=>`id ${_.id} (blocked by ${_.blockers.join(",")})`).join("; ")}.`),{id:f.id,reason:g,skipped_blocked:a}}return a.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${a.map(d=>`id ${d.id} (blocked by ${d.blockers.join(",")})`).join("; ")}.`,skipped_blocked:a}:{id:null,reason:"No actionable phases remaining."}}var Jn=new Set(["java","typescript","javascript","vue","python","sql"]);function $e(e,t){let n={hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e};t&&(n.additionalContext=t),process.stdout.write(JSON.stringify({decision:"block",reason:e,hookSpecificOutput:n})),process.exit(0)}var lt={name:"cairn",contract:1,title:"cairn",slots:["roadmap"],provides:{roadmap:{path:"ROADMAP.md",format:z}},spec:"Project memory and roadmap. Publishes the plan as ROADMAP.md in the roadmap/1 slot format."};function Bn(){let e=S.join(G.homedir(),".claude.json");if(!h.existsSync(e))return!1;let t;try{t=JSON.parse(h.readFileSync(e,"utf8"))}catch{return!1}let n=t?.mcpServers?.cairn;if(!n||typeof n!="object")return!1;let s=n.terminalConnect,o=s?.provides?.roadmap?.format;if(s&&o===z)return!1;n.terminalConnect=lt;try{return h.writeFileSync(e,JSON.stringify(t,null,2),"utf8"),!0}catch{return!1}}function ut(e){let t=S.join(G.homedir(),".claude","projects",ue(e));return h.existsSync(t)?S.join(t,"memory"):null}function pt(e){try{return h.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}}function Yn(e){let t=ut(e);if(!t||!h.existsSync(t))return 0;let n=S.join(e,".cairn","memory");h.mkdirSync(n,{recursive:!0});let s=0;for(let o of pt(t)){let r=S.join(t,o),i;try{i=h.statSync(r)}catch{continue}if(!i.isFile())continue;let l=S.join(n,o),p=!h.existsSync(l);if(!p)try{p=i.mtimeMs>h.statSync(l).mtimeMs}catch{p=!0}if(p){h.copyFileSync(r,l);try{h.utimesSync(l,i.atime,i.mtime)}catch{}s++}}return s}function zn(e){let t=ut(e);if(!t||(h.mkdirSync(t,{recursive:!0}),pt(t).some(o=>o!=="MEMORY.md")))return 0;let n=S.join(e,".cairn","memory");if(!h.existsSync(n))return 0;let s=0;for(let o of h.readdirSync(n)){let r=S.join(n,o),i;try{i=h.statSync(r)}catch{continue}if(i.isFile()){h.copyFileSync(r,S.join(t,o));try{h.utimesSync(S.join(t,o),i.atime,i.mtime)}catch{}s++}}return s}function it(e,t){let n=new Date(t.checkpoint_at).toLocaleString(),r=[` message: ${/^Auto-checkpoint\b/.test(t.message||"")&&t.last_manual_message||t.message}`];r.push(` would_lose: ${fe(se(e,t))}`),r.push(` checkpoint: ${n}`),t.active_files?.length>0&&r.push(` active_files: ${t.active_files.join(", ")}`),t.notes?.length>0&&r.push(` notes: ${t.notes.join(" | ")}`);let i=a=>{try{return Hn(a,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}};try{let a=new Date(t.checkpoint_at).toISOString(),c=i(`git log --oneline --after="${a}"`);c!==null&&r.push(` commits_since: ${c?c.split(`
63
+ `).join("; "):"(none)"}`)}catch{}let l=i("git diff --name-only HEAD");l&&r.push(` uncommitted: ${l.split(`
64
+ `).join(", ")}`);let p=i("git status --short");return p&&r.push(` git_status:
65
+ ${p.split(`
66
+ `).map(a=>" "+a).join(`
67
+ `)}`),r.push(" \u2192 Call cairn_resume for full index status and open todos."),r}async function at(e){let t={active:!1},n=S.join(e,"index.db");if(!h.existsSync(n))return t;let s;try{({DatabaseSync:s}=await import("node:sqlite"))}catch{return t}let o;try{o=new s(n,{readonly:!0})}catch{return t}let r=l=>l.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");function i(l,p){if(!h.existsSync(l))return null;let a=h.readFileSync(l,"utf8"),c=!1,d=!1,f=[];for(let y of a.split(/\r?\n/)){if(/^##\s+Phases\s*$/i.test(y)){c=!0;continue}if(c&&/^##\s+/.test(y)&&!/^###/.test(y))break;if(!c)continue;let m=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(y);if(m){if(d)break;parseInt(m[1],10)===p&&(f.push(`### Phase ${m[1]}: ${m[2]}`),d=!0);continue}d&&f.push(y)}return f.join(`
68
+ `).trim()||null}try{let p=o.prepare("SELECT phase_id FROM roadmap_cursor WHERE singleton = 1").get()?.phase_id??null;if(p==null)return o.close(),t;let a=o.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, status, position FROM roadmap_phases ORDER BY decision_name, phase_number, position, id").all(),c={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},d=new Map;for(let R of a){let k=R.parent_phase_id??"root";d.has(k)||d.set(k,[]),d.get(k).push(R)}for(let R of d.values())R.sort((k,$)=>k.decision_name&&$.decision_name&&k.decision_name!==$.decision_name?k.decision_name.localeCompare($.decision_name):k.phase_number!=null&&$.phase_number!=null?k.phase_number-$.phase_number:k.position-$.position||k.id-$.id);let f=d.get("root")||[],y=new Map;for(let R of f)R.decision_name!=null&&(y.has(R.decision_name)||y.set(R.decision_name,[]),y.get(R.decision_name).push(R));let m=[],u=(R,k)=>{for(let $ of d.get(R)||[]){if($.status==="stale")continue;let ce=p===$.id?" \u2192":"",le=$.phase_number!=null?`Phase ${$.phase_number}: ${$.text}`:$.text;m.push(`${" ".repeat(k)}${c[$.status]||"[?]"}${ce} ${le}`),u($.id,k+1)}};for(let[R,k]of y){let $=k.filter(P=>P.status!=="stale");if($.length===0)continue;let ce=$.every(P=>P.status==="done"),le=$.some(P=>P.status==="in_progress"),dt=ce?"done":le?"in_progress":"open",ft=$.filter(P=>P.status==="done").length,mt=R===j?"Roadmap":`Decision: ${R}`;m.push(`${c[dt]} ${mt} (${ft}/${$.length} done)`);for(let P of k){if(P.status==="stale"||R===j&&P.status==="done")continue;let ht=p===P.id?" \u2192":"";m.push(` ${c[P.status]||"[?]"}${ht} Phase ${P.phase_number}: ${P.text}`),u(P.id,2)}}let g=R=>a.find(k=>k.id===R),_=g(p),w=_?.decision_name??null,b=_?.phase_number??null,x=_;for(;x&&x.parent_phase_id!=null&&(x=g(x.parent_phase_id),!!x);)w=x.decision_name??w,b=x.phase_number??b;o.close();let E=_?_.phase_number!=null?`${_.decision_name} / Phase ${_.phase_number}: ${_.text}`:`${w} / Phase ${b} \u2192 ${_.text}`:"(unknown)",A=S.join(e,".last-roadmap-cursor"),Re=null;try{Re=parseInt(h.readFileSync(A,"utf8").trim(),10)}catch{}let ve=Re!==p;try{h.writeFileSync(A,String(p),"utf8")}catch{}let Oe=null;if(ve&&w!=null&&b!=null){let R=w===j?S.join(e,"roadmap.md"):S.join(e,"memory",`decision_${r(w)}.md`),k=i(R,b);k&&(Oe=k.split(`
69
+ `))}return{active:!0,currentLabel:E,treeLines:m,advanced:ve,memoLines:Oe}}catch{try{o?.close()}catch{}return t}}process.on("uncaughtException",e=>{process.stderr.write(`cairn: ${e.message}
70
+ `),process.exit(0)});var N=process.argv[2];if(N==="--version"||N==="-v"){let e=JSON.parse(h.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(N==="minify"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t,n;try{n=JSON.parse(e)?.tool_input||{},t=n.file_path}catch{process.exit(0)}t||process.exit(0),(n.offset!=null||n.limit!=null)&&process.exit(0),t=S.resolve(t);let s=he(t);s||process.exit(0);let o=S.join(s,"views"),r=S.relative(o,t);!r.startsWith("..")&&!S.isAbsolute(r)&&process.exit(0);let i=J(s),p=oe(s,i)>0,a=i[t];a?.state==="edit-ready"&&(p&&F(s,i),process.exit(0)),a?.state==="compressed"&&(a.state="edit-ready",F(s,i),process.exit(0));let c=S.extname(t).toLowerCase(),d=Pe[c];(!d||!Jn.has(d))&&(p&&F(s,i),process.exit(0));let f;try{f=h.readFileSync(t,"utf8")}catch{p&&F(s,i),process.exit(0)}let y=f.split(`
71
+ `).length,m=d==="vue"?je(f):te(f,d),u=m.split(`
72
+ `).length,g=y>0?Math.round((1-u/y)*100):0,_=Ce(s,t);h.mkdirSync(S.dirname(_),{recursive:!0}),h.writeFileSync(_,m,"utf8");let w=a?.readCount??0;i[t]={tempPath:_,state:"compressed",minifiedAt:h.statSync(t).mtimeMs,readCount:w+1},F(s,i);let b=u>De,x=w>0;if(x||b){let E=Ie(m,d),A=x?`[cairn: ${y} \u2192 ${u} lines | seen \xD7${w+1} this session]`:`[cairn: ${y} \u2192 outline (${u} minified lines)]`;process.stderr.write(`${A}
73
+ ${E}
74
74
  Full minified view: Read("${_}")
75
- `)}else process.stderr.write(`[cairn: ${h} \u2192 ${l} lines (-${y}%)]
76
- ${d}
75
+ `)}else process.stderr.write(`[cairn: ${y} \u2192 ${u} lines (-${g}%)]
76
+ ${m}
77
77
  Cached view: Read("${_}")
78
- `);process.exit(2)})}else if(P==="edit-guard"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t="(unknown)",n="Edit";try{let p=JSON.parse(e);t=p?.tool_input?.file_path||"(unknown)",n=p?.tool_name||"Edit"}catch{}let s=S.resolve(t),o=ye(s);o||process.exit(0);let r=S.join(o,"views"),i=S.relative(r,s);if(!i.startsWith("..")&&!S.isAbsolute(i)){let p=J(o),m=Object.entries(p).find(([,d])=>d.tempPath===s),h=m?`Edit the source file instead: Edit("${m[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";$e(`[cairn] That path is a cached view, not the source \u2014 it is read-only and is overwritten on the next read. ${h}`)}let u=J(o),f=oe(o,u),c=u[s];c||(f>0&&M(o,u),process.exit(0)),n==="Write"&&(delete u[s],M(o,u),process.exit(0)),c.state==="edit-ready"&&(delete u[s],M(o,u),process.exit(0)),c.state="edit-ready",M(o,u);let a=null;try{let p=g.readFileSync(s,"utf8");p.split(`
79
- `).length<=300&&(a=p)}catch{}a!==null?$e(`[cairn] Expected step, not a failure: this file was shown compressed, so the edit was built from partial text. The full source has been provided in context \u2014 re-apply the same edit, or Read("${t}") first if you do not see it.`,`[cairn] Full source of ${t} (the compressed view you saw is not editable text \u2014 use the exact content below, then re-apply your Edit; the guard is now cleared):
78
+ `);process.exit(2)})}else if(N==="edit-guard"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t="(unknown)",n="Edit";try{let d=JSON.parse(e);t=d?.tool_input?.file_path||"(unknown)",n=d?.tool_name||"Edit"}catch{}let s=S.resolve(t),o=he(s);o||process.exit(0);let r=S.join(o,"views"),i=S.relative(r,s);if(!i.startsWith("..")&&!S.isAbsolute(i)){let d=J(o),f=Object.entries(d).find(([,m])=>m.tempPath===s),y=f?`Edit the source file instead: Edit("${f[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";$e(`[cairn] That path is a cached view, not the source \u2014 it is read-only and is overwritten on the next read. ${y}`)}let l=J(o),p=oe(o,l),a=l[s];a||(p>0&&F(o,l),process.exit(0)),n==="Write"&&(delete l[s],F(o,l),process.exit(0)),a.state==="edit-ready"&&(delete l[s],F(o,l),process.exit(0)),a.state="edit-ready",F(o,l);let c=null;try{let d=h.readFileSync(s,"utf8");d.split(`
79
+ `).length<=300&&(c=d)}catch{}c!==null?$e(`[cairn] Expected step, not a failure: this file was shown compressed, so the edit was built from partial text. The full source has been provided in context \u2014 re-apply the same edit, or Read("${t}") first if you do not see it.`,`[cairn] Full source of ${t} (the compressed view you saw is not editable text \u2014 use the exact content below, then re-apply your Edit; the guard is now cleared):
80
80
 
81
- ${a}`):$e(`[cairn] Expected step, not a failure: this file was shown compressed. Read("${t}") to load the real source (it comes through in full now), then re-apply the same edit.`)})}else if(P==="roadmap-capture"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",async()=>{let t;try{t=JSON.parse(e)}catch{process.exit(0)}let n=t?.tool_name||"",s=t?.tool_input||{},o=process.cwd(),r=S.join(o,".cairn");g.existsSync(r)||process.exit(0);let i=S.join(r,"index.db");g.existsSync(i)||process.exit(0);let u;try{({DatabaseSync:u}=await import("node:sqlite"))}catch{process.exit(0)}let f=/exit.?plan.?mode/i.test(n),c=/todo.?write/i.test(n);!f&&!c&&process.exit(0);let a=null,p;try{if(f){let h=s.plan;(typeof h!="string"||!h.trim())&&process.exit(0);let d=Je(h);if(p=new u(i),X(p),(p.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let y=nt(p,null);y.id!=null&&tt(p,y.id)}d.added.length>0&&(a=`[cairn] roadmap: captured ${d.added.length} phase(s) from plan${d.skipped.length?` (${d.skipped.length} already present)`:""}.`)}else if(c){p=new u(i);let h=Be(p,s.todos);h.seeded?a=`[cairn] roadmap: seeded ${h.seeded} phase(s) from todos.`:h.inserted>0&&(a=`[cairn] roadmap: linked ${h.inserted} new sub-task(s) to the current phase.`)}}catch(h){process.stderr.write(`[cairn] roadmap-capture skipped: ${h.message}
82
- `);try{p?.close()}catch{}process.exit(0)}let m=p?ce(p):null;try{p?.close()}catch{}m&&(a=a?`${a} ${m}`:m),a&&process.stdout.write(JSON.stringify({systemMessage:a})),process.exit(0)})}else if(P==="validate-map"){let e=S.join(process.cwd(),".cairn");g.existsSync(e)||process.exit(0);let t=J(e),n=oe(e,t);for(let s of Object.values(t))s.readCount=0;M(e,t),n>0&&process.stdout.write(`[cairn] Cleaned minify map: ${n} stale entr${n===1?"y":"ies"} removed
83
- `),process.exit(0)}else if(P==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],n=[];try{let s=S.join(process.cwd(),".cairn"),o=S.join(s,"session.json");g.existsSync(o)&&(t=JSON.parse(g.readFileSync(o,"utf8")).notes||[]);let r=J(s);n=Object.entries(r).filter(([,i])=>(i.readCount??0)>0).map(([i])=>i)}catch{}he(null,{message:e,active_files:n,notes:t,auto:!0}),process.exit(0)}else if(P==="session-tick"){let e=process.cwd(),t=S.join(e,".cairn");g.existsSync(t)||process.exit(0);let n={};if(!process.stdin.isTTY)try{n=JSON.parse(g.readFileSync(0,"utf8"))||{}}catch{}let s=[];try{let c=`Auto-checkpoint at ${new Date().toISOString()}`,a=[],p=S.join(t,"session.json");if(g.existsSync(p))try{a=JSON.parse(g.readFileSync(p,"utf8")).notes||[]}catch{}let m=J(t),h=Object.entries(m).filter(([,d])=>(d.readCount??0)>0).map(([d])=>d);he(null,{message:c,active_files:h,notes:a,auto:!0}),s.push("\u2713 checkpoint saved")}catch{}try{let c=In(e);c>0&&s.push(`memory backed up (${c})`)}catch{}let o=[],r=null;try{let c=S.join(t,"index.db");if(g.existsSync(c)){let{DatabaseSync:a}=await import("node:sqlite"),p=new a(c);try{X(p),r=ce(p);try{o=Ye(p,e).candidates}catch{}}finally{p.close()}}}catch{}let i="";try{let c=await ot(t);if(c.active){s.push(`roadmap ${c.advanced?"updated":"on track"}: ${c.currentLabel}`);let a=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...c.treeLines.map(p=>" "+p)];c.memoLines&&a.push("","\u2500\u2500 Current phase detail (from roadmap.md) \u2500\u2500",...c.memoLines,"\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500"),a.push("","Keep this current via cairn_roadmap on MATERIAL change (phase done / blocked / new) \u2014 not every turn."),i=a.join(`
81
+ ${c}`):$e(`[cairn] Expected step, not a failure: this file was shown compressed. Read("${t}") to load the real source (it comes through in full now), then re-apply the same edit.`)})}else if(N==="roadmap-capture"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",async()=>{let t;try{t=JSON.parse(e)}catch{process.exit(0)}let n=t?.tool_name||"",s=t?.tool_input||{},o=process.cwd(),r=S.join(o,".cairn");h.existsSync(r)||process.exit(0);let i=S.join(r,"index.db");h.existsSync(i)||process.exit(0);let l;try{({DatabaseSync:l}=await import("node:sqlite"))}catch{process.exit(0)}let p=/exit.?plan.?mode/i.test(n),a=/todo.?write/i.test(n);!p&&!a&&process.exit(0);let c=null,d;try{if(p){let y=s.plan;(typeof y!="string"||!y.trim())&&process.exit(0);let m=Ye(y);if(d=new l(i),X(d),(d.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let g=rt(d,null);g.id!=null&&ot(d,g.id)}m.added.length>0&&(c=`[cairn] roadmap: captured ${m.added.length} phase(s) from plan${m.skipped.length?` (${m.skipped.length} already present)`:""}.`)}else if(a){d=new l(i);let y=ze(d,s.todos);y.seeded?c=`[cairn] roadmap: seeded ${y.seeded} phase(s) from todos.`:y.inserted>0&&(c=`[cairn] roadmap: linked ${y.inserted} new sub-task(s) to the current phase.`)}}catch(y){process.stderr.write(`[cairn] roadmap-capture skipped: ${y.message}
82
+ `);try{d?.close()}catch{}process.exit(0)}let f=d?ae(d):null;try{d?.close()}catch{}f&&(c=c?`${c} ${f}`:f),c&&process.stdout.write(JSON.stringify({systemMessage:c})),process.exit(0)})}else if(N==="validate-map"){let e=S.join(process.cwd(),".cairn");h.existsSync(e)||process.exit(0);let t=J(e),n=oe(e,t);for(let s of Object.values(t))s.readCount=0;F(e,t),n>0&&process.stdout.write(`[cairn] Cleaned minify map: ${n} stale entr${n===1?"y":"ies"} removed
83
+ `),process.exit(0)}else if(N==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],n=[];try{let s=S.join(process.cwd(),".cairn"),o=S.join(s,"session.json");h.existsSync(o)&&(t=JSON.parse(h.readFileSync(o,"utf8")).notes||[]);let r=J(s);n=Object.entries(r).filter(([,i])=>(i.readCount??0)>0).map(([i])=>i)}catch{}me(null,{message:e,active_files:n,notes:t,auto:!0}),process.exit(0)}else if(N==="session-tick"){let e=process.cwd(),t=S.join(e,".cairn");h.existsSync(t)||process.exit(0);let n={};if(!process.stdin.isTTY)try{n=JSON.parse(h.readFileSync(0,"utf8"))||{}}catch{}let s=[];try{let a=`Auto-checkpoint at ${new Date().toISOString()}`,c=[],d=S.join(t,"session.json");if(h.existsSync(d))try{c=JSON.parse(h.readFileSync(d,"utf8")).notes||[]}catch{}let f=J(t),y=Object.entries(f).filter(([,m])=>(m.readCount??0)>0).map(([m])=>m);me(null,{message:a,active_files:y,notes:c,auto:!0}),s.push("\u2713 checkpoint saved")}catch{}try{let a=Yn(e);a>0&&s.push(`memory backed up (${a})`)}catch{}let o=[],r=null;try{let a=S.join(t,"index.db");if(h.existsSync(a)){let{DatabaseSync:c}=await import("node:sqlite"),d=new c(a);try{X(d),r=ae(d);try{o=Ge(d,e).candidates}catch{}}finally{d.close()}}}catch{}let i="";try{let a=await at(t);if(a.active){s.push(`roadmap ${a.advanced?"updated":"on track"}: ${a.currentLabel}`);let c=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...a.treeLines.map(d=>" "+d)];a.memoLines&&c.push("","\u2500\u2500 Current phase detail (from roadmap.md) \u2500\u2500",...a.memoLines,"\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500"),c.push("","Keep this current via cairn_roadmap on MATERIAL change (phase done / blocked / new) \u2014 not every turn."),i=c.join(`
84
84
  `)}}catch{}if(r&&(s.push("ROADMAP.md not updated \u2014 see warning"),i=i?`${i}
85
85
 
86
- ${r}`:r),o.length>0){s.push(`${o.length} phase(s) may be done \u2014 confirm with user`);let c=["","[cairn] Possible COMPLETED phase(s) detected from recent commits \u2014","CONFIRM WITH THE USER before marking done; do NOT mark done unilaterally:"];for(let a of o)c.push(` \u2022 Phase ${a.phase_number}: ${a.text} (commit ${a.commit}, ${a.confidence}) \u2192 if confirmed: cairn_roadmap set_status ${a.phase_id} done`);i=i?i+`
87
- `+c.join(`
88
- `):c.join(`
89
- `).trimStart()}let u=null;try{let c=JSON.parse(g.readFileSync(S.join(t,"session.json"),"utf8")),a=Ae({cwd:e,session:c,transcriptPath:n.transcript_path});if(a&&Fe(t,a.signature)){u=a;let p=`
90
- ${a.line}
91
- ${a.guidance}`;i=i?i+`
92
- `+p:p.trimStart()}}catch{}s.length===0&&!u&&process.exit(0);let f={systemMessage:s.length>0?`[cairn] ${s.join(" \xB7 ")}${u?`
93
- ${u.line}`:""}`:u.line};i&&(f.additionalContext=i),process.stdout.write(JSON.stringify(f)),process.exit(0)}else if(P==="memory-restore"){let e=process.cwd();g.existsSync(S.join(e,".cairn"))||process.exit(0);try{let t=Wn(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
94
- `)}catch{}process.exit(0)}else if(P==="resume-hint"){let e=S.join(process.cwd(),".cairn"),t=S.join(e,"session.json"),n=S.join(e,"index.db"),s=S.join(e,".hint-lock"),o=1800*1e3,r=process.cwd()===G.homedir();if(!r&&g.existsSync(e)&&(g.existsSync(n)||g.existsSync(t))){let l=S.join(e,".cairn-project");if(!g.existsSync(l))try{g.writeFileSync(l,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
95
- `)}catch{}}if(!r&&g.existsSync(e))try{Cn()&&process.stdout.write(`[cairn] Repaired: declared the roadmap slot (${z}) in ~/.claude.json.
96
- `)}catch{}if(!r&&g.existsSync(n))try{let{DatabaseSync:l}=await import("node:sqlite"),y=new l(n,{readonly:!0}),_=null;try{_=ce(y)}finally{y.close()}_&&process.stdout.write(`${_}
97
- `)}catch{}{let l=[S.join(process.cwd(),".claude","settings.json"),S.join(G.homedir(),".claude","settings.json")];for(let y of l)try{if(!g.existsSync(y))continue;let _=g.readFileSync(y,"utf8"),x=JSON.parse(_),R=x?.hooks?.PreToolUse;if(!Array.isArray(R))continue;let T=!1;for(let C of R)if(!(C.matcher!=="Edit"||!Array.isArray(C.hooks)))for(let U of C.hooks)U.command==="cairn set-mode edit"&&(U.command="cairn edit-guard",T=!0);T&&(g.writeFileSync(y,JSON.stringify(x,null,2),"utf8"),process.stdout.write(`[cairn] Repaired: migrated cairn set-mode \u2192 cairn edit-guard in ${y}
98
- `))}catch{}}let i="";if(!process.stdin.isTTY)try{i=(JSON.parse(g.readFileSync(0,"utf8"))?.prompt||"").toLowerCase()}catch{}let f=/\b(resume|continue|pick up|where (were|did) (we|i)|last session|carry on|what('s| is) next|what (were|was) (we|i)|start|begin)\b/.test(i);g.existsSync(t)||(g.existsSync(n)||(g.existsSync(s)&&Date.now()-g.statSync(s).mtimeMs<o&&process.exit(0),g.mkdirSync(e,{recursive:!0}),g.writeFileSync(s,new Date().toISOString(),"utf8"),process.stdout.write(`[cairn] No index found. Run cairn_maintain to index this project.
99
- `)),process.exit(0)),g.existsSync(s)&&Date.now()-g.statSync(s).mtimeMs<o&&process.exit(0);let c=JSON.parse(g.readFileSync(t,"utf8")),a=new Date(c.checkpoint_at).toLocaleString(),p=/^Auto-checkpoint\b/.test(c.message||""),m=p&&c.last_manual_message||c.message,h=p&&c.last_manual_checkpoint_at?new Date(c.last_manual_checkpoint_at).toLocaleString():a,d=[`[cairn] Prior session: "${m}" (${h}).`,"[cairn] SESSION: ALWAYS call cairn_resume FIRST when asked to resume, continue, or recall prior work \u2014 do NOT substitute git log or file reads. cairn_resume includes commits_since_checkpoint so you can cross-reference checkpoint notes against what was shipped. At end of session, and before any /clear or /compact, call cairn_checkpoint with message + active_files + notes + would_be_lost (what a fresh session would NOT know).","[cairn] TOOLS: NEVER use Bash to run cat, grep, head, tail, find, or ls for file inspection \u2014 always use the Read, Grep, or Glob tools instead. Prefer cairn_search / cairn_bundle / cairn_describe over manual file browsing. Use cairn_outline for structural overview, cairn_todos for backlog, cairn_security before PRs.","[cairn] MEMORY: IMPORTANT \u2014 call cairn_memo IMMEDIATELY and SILENTLY (do NOT ask the user, do NOT list what you are about to save, do NOT confirm afterward) when you observe \u2014 preference: user corrects/confirms your behaviour; experience: non-trivial problem solved; decision: architectural choice made; knowledge: non-obvious codebase fact discovered. cairn_employ_memory is explicit only \u2014 never call it unless asked."];if(!f){let l=se(process.cwd(),c);l.recorded&&d.push(`[cairn] ${me(l)}`)}f&&d.push(["[cairn] RESUME \u2014 checkpoint loaded inline:",...st(process.cwd(),c)].join(`
100
- `)),process.stdout.write(d.join(`
86
+ ${r}`:r),o.length>0){s.push(`${o.length} phase(s) may be done \u2014 confirm with user`);let a=["","[cairn] Possible COMPLETED phase(s) detected from recent commits \u2014","CONFIRM WITH THE USER before marking done; do NOT mark done unilaterally:"];for(let c of o)a.push(` \u2022 Phase ${c.phase_number}: ${c.text} (commit ${c.commit}, ${c.confidence}) \u2192 if confirmed: cairn_roadmap set_status ${c.phase_id} done`);i=i?i+`
87
+ `+a.join(`
88
+ `):a.join(`
89
+ `).trimStart()}let l=null;try{let a=JSON.parse(h.readFileSync(S.join(t,"session.json"),"utf8")),c=Ae({cwd:e,session:a,transcriptPath:n.transcript_path});if(c&&Fe(t,c.signature)){l=c;let d=`
90
+ ${c.line}
91
+ ${c.guidance}`;i=i?i+`
92
+ `+d:d.trimStart()}}catch{}s.length===0&&!l&&process.exit(0);let p={systemMessage:s.length>0?`[cairn] ${s.join(" \xB7 ")}${l?`
93
+ ${l.line}`:""}`:l.line};i&&(p.additionalContext=i),process.stdout.write(JSON.stringify(p)),process.exit(0)}else if(N==="memory-restore"){let e=process.cwd();h.existsSync(S.join(e,".cairn"))||process.exit(0);try{let t=zn(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
94
+ `)}catch{}process.exit(0)}else if(N==="resume-hint"){let e=S.join(process.cwd(),".cairn"),t=S.join(e,"session.json"),n=S.join(e,"index.db"),s=S.join(e,".hint-lock"),o=1800*1e3,r=process.cwd()===G.homedir();if(!r&&h.existsSync(e)&&(h.existsSync(n)||h.existsSync(t))){let u=S.join(e,".cairn-project");if(!h.existsSync(u))try{h.writeFileSync(u,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
95
+ `)}catch{}}if(!r&&h.existsSync(e))try{Bn()&&process.stdout.write(`[cairn] Repaired: declared the roadmap slot (${z}) in ~/.claude.json.
96
+ `)}catch{}if(!r&&h.existsSync(n))try{let{DatabaseSync:u}=await import("node:sqlite"),g=new u(n,{readonly:!0}),_=null;try{_=ae(g)}finally{g.close()}_&&process.stdout.write(`${_}
97
+ `)}catch{}{let u=[S.join(process.cwd(),".claude","settings.json"),S.join(G.homedir(),".claude","settings.json")];for(let g of u)try{if(!h.existsSync(g))continue;let _=h.readFileSync(g,"utf8"),w=JSON.parse(_),b=w?.hooks?.PreToolUse;if(!Array.isArray(b))continue;let x=!1;for(let E of b)if(!(E.matcher!=="Edit"||!Array.isArray(E.hooks)))for(let A of E.hooks)A.command==="cairn set-mode edit"&&(A.command="cairn edit-guard",x=!0);x&&(h.writeFileSync(g,JSON.stringify(w,null,2),"utf8"),process.stdout.write(`[cairn] Repaired: migrated cairn set-mode \u2192 cairn edit-guard in ${g}
98
+ `))}catch{}}let i="";if(!process.stdin.isTTY)try{i=(JSON.parse(h.readFileSync(0,"utf8"))?.prompt||"").toLowerCase()}catch{}let p=/\b(resume|continue|pick up|where (were|did) (we|i)|last session|carry on|what('s| is) next|what (were|was) (we|i)|start|begin)\b/.test(i);h.existsSync(t)||(h.existsSync(n)||(h.existsSync(s)&&Date.now()-h.statSync(s).mtimeMs<o&&process.exit(0),h.mkdirSync(e,{recursive:!0}),h.writeFileSync(s,new Date().toISOString(),"utf8"),process.stdout.write(`[cairn] No index found. Run cairn_maintain to index this project.
99
+ `)),process.exit(0)),h.existsSync(s)&&Date.now()-h.statSync(s).mtimeMs<o&&process.exit(0);let a=JSON.parse(h.readFileSync(t,"utf8")),c=new Date(a.checkpoint_at).toLocaleString(),d=/^Auto-checkpoint\b/.test(a.message||""),f=d&&a.last_manual_message||a.message,y=d&&a.last_manual_checkpoint_at?new Date(a.last_manual_checkpoint_at).toLocaleString():c,m=[`[cairn] Prior session: "${f}" (${y}).`,"[cairn] SESSION: ALWAYS call cairn_resume FIRST when asked to resume, continue, or recall prior work \u2014 do NOT substitute git log or file reads. cairn_resume includes commits_since_checkpoint so you can cross-reference checkpoint notes against what was shipped. At end of session, and before any /clear or /compact, call cairn_checkpoint with message + active_files + notes + would_be_lost (what a fresh session would NOT know).","[cairn] TOOLS: NEVER use Bash to run cat, grep, head, tail, find, or ls for file inspection \u2014 always use the Read, Grep, or Glob tools instead. Prefer cairn_search / cairn_bundle / cairn_describe over manual file browsing. Use cairn_outline for structural overview, cairn_todos for backlog, cairn_security before PRs.","[cairn] MEMORY: IMPORTANT \u2014 call cairn_memo IMMEDIATELY and SILENTLY (do NOT ask the user, do NOT list what you are about to save, do NOT confirm afterward) when you observe \u2014 preference: user corrects/confirms your behaviour; experience: non-trivial problem solved; decision: architectural choice made; knowledge: non-obvious codebase fact discovered. cairn_employ_memory is explicit only \u2014 never call it unless asked."];if(!p){let u=se(process.cwd(),a);u.recorded&&m.push(`[cairn] ${fe(u)}`)}p&&m.push(["[cairn] RESUME \u2014 checkpoint loaded inline:",...it(process.cwd(),a)].join(`
100
+ `)),process.stdout.write(m.join(`
101
101
  `)+`
102
- `),g.writeFileSync(s,new Date().toISOString(),"utf8"),process.exit(0)}else if(P==="session-start"){let e=process.cwd(),t=S.join(e,".cairn"),n=S.join(t,"session.json");g.existsSync(n)||process.exit(0);let s={};if(!process.stdin.isTTY)try{s=JSON.parse(g.readFileSync(0,"utf8"))||{}}catch{}let o=typeof s.source=="string"?s.source:"";o&&o!=="clear"&&o!=="compact"&&process.exit(0);let r;try{r=JSON.parse(g.readFileSync(n,"utf8"))}catch{process.exit(0)}Le(t);let u=[`[cairn] Session restored automatically after ${o==="compact"?"a /compact":o==="clear"?"a /clear":"a session restart"} \u2014 the user did not have to ask:`,...st(e,r),"","Pick the thread back up from would_lose above. If it says nothing was recorded, treat that as","a gap, not an all-clear: cairn cannot see what the previous session never wrote down."].join(`
103
- `);process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:u}})),process.exit(0)}else if(P==="roadmap-hint"){let e=S.join(process.cwd(),".cairn"),t=await ot(e);t.active||process.exit(0);let n=["[cairn] ROADMAP \u2014 cursor is active.",...t.treeLines.map(s=>"[cairn] "+s),`[cairn] Current: ${t.currentLabel}`];if(t.memoLines){n.push("[cairn] \u2500\u2500\u2500\u2500\u2500 Decision memo (current phase) \u2500\u2500\u2500\u2500\u2500");for(let s of t.memoLines)n.push("[cairn] "+s);n.push("[cairn] \u2500\u2500\u2500\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500\u2500\u2500\u2500")}n.push("[cairn] Use cairn_roadmap set_status <id> done (auto-advances), focus <id>, or next to move cursor."),process.stdout.write(n.join(`
102
+ `),h.writeFileSync(s,new Date().toISOString(),"utf8"),process.exit(0)}else if(N==="session-start"){let e=process.cwd(),t=S.join(e,".cairn"),n=S.join(t,"session.json");h.existsSync(n)||process.exit(0);let s={};if(!process.stdin.isTTY)try{s=JSON.parse(h.readFileSync(0,"utf8"))||{}}catch{}let o=typeof s.source=="string"?s.source:"";o&&o!=="clear"&&o!=="compact"&&process.exit(0);let r;try{r=JSON.parse(h.readFileSync(n,"utf8"))}catch{process.exit(0)}Le(t);let l=[`[cairn] Session restored automatically after ${o==="compact"?"a /compact":o==="clear"?"a /clear":"a session restart"} \u2014 the user did not have to ask:`,...it(e,r),"","Pick the thread back up from would_lose above. If it says nothing was recorded, treat that as","a gap, not an all-clear: cairn cannot see what the previous session never wrote down."].join(`
103
+ `);process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:l}})),process.exit(0)}else if(N==="roadmap-hint"){let e=S.join(process.cwd(),".cairn"),t=await at(e);t.active||process.exit(0);let n=["[cairn] ROADMAP \u2014 cursor is active.",...t.treeLines.map(s=>"[cairn] "+s),`[cairn] Current: ${t.currentLabel}`];if(t.memoLines){n.push("[cairn] \u2500\u2500\u2500\u2500\u2500 Decision memo (current phase) \u2500\u2500\u2500\u2500\u2500");for(let s of t.memoLines)n.push("[cairn] "+s);n.push("[cairn] \u2500\u2500\u2500\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500\u2500\u2500\u2500")}n.push("[cairn] Use cairn_roadmap set_status <id> done (auto-advances), focus <id>, or next to move cursor."),process.stdout.write(n.join(`
104
104
  `)+`
105
- `),process.exit(0)}else if(P==="install"){let e=process.platform,t=S.join(G.homedir(),".claude.json"),n={};if(g.existsSync(t))try{n=JSON.parse(g.readFileSync(t,"utf8"))}catch{}n.mcpServers=n.mcpServers||{};let s=!!n.mcpServers.cairn;n.mcpServers.cairn={...s&&typeof n.mcpServers.cairn=="object"?n.mcpServers.cairn:{},command:"cairn-mcp",terminalConnect:it},g.writeFileSync(t,JSON.stringify(n,null,2),"utf8");let o=S.join(G.homedir(),".claude");rt(o,!0),console.log("Cairn installed successfully!"),console.log(""),console.log(` Platform : ${e}`),console.log(` MCP : cairn-mcp registered in ${t}${s?" (updated)":""}`),console.log(` Hooks : installed in ${S.join(o,"settings.json")}`),console.log(""),console.log("Active hooks:"),console.log(" PreToolUse[Read] -> cairn minify (compress source files for reading)"),console.log(" PreToolUse[Edit] -> cairn edit-guard (block Edit until re-read with full content)"),console.log(" PreToolUse[Write] -> cairn edit-guard (clear minify state before full file overwrite)"),console.log(" PostToolUse[ExitPlanMode] -> cairn roadmap-capture (approved plan -> roadmap phases)"),console.log(" PostToolUse[TodoWrite] -> cairn roadmap-capture (todos -> roadmap sub-tasks)"),console.log(" Stop -> cairn session-tick (checkpoint + memory backup + roadmap, surfaced)"),console.log(" UserPromptSubmit -> cairn resume-hint (remind Claude of prior session)"),console.log(" UserPromptSubmit -> cairn memory-restore (restore .cairn/memory -> Claude store if empty)"),console.log(" SessionStart[clear] -> cairn session-start (re-inject checkpoint after /clear)"),console.log(" SessionStart[compact]-> cairn session-start (re-inject checkpoint after /compact)"),console.log(""),console.log("Restart Claude Code to activate the MCP server, then open any project and start working."),console.log("Upgrade any time with: npm install -g @misterhuydo/cairn-mcp"),process.exit(0)}else if(P==="install-hooks"){let e=process.argv.includes("--global"),t=e?S.join(G.homedir(),".claude"):S.join(process.cwd(),".claude");rt(t,e),console.log(`Cairn hooks installed in ${e?"global (~/.claude/settings.json)":"project (.claude/settings.json)"}`),console.log(""),console.log("Active hooks:"),console.log(" PreToolUse[Read] -> cairn minify (compress source files for reading)"),console.log(" PreToolUse[Edit] -> cairn edit-guard (block Edit until re-read with full content)"),console.log(" PreToolUse[Write] -> cairn edit-guard (clear minify state before full file overwrite)"),console.log(" PostToolUse[ExitPlanMode] -> cairn roadmap-capture (approved plan -> roadmap phases)"),console.log(" PostToolUse[TodoWrite] -> cairn roadmap-capture (todos -> roadmap sub-tasks)"),console.log(" Stop -> cairn session-tick (checkpoint + memory backup + roadmap, surfaced)"),console.log(" UserPromptSubmit -> cairn resume-hint (remind Claude of prior session)"),console.log(" UserPromptSubmit -> cairn memory-restore (restore .cairn/memory -> Claude store if empty)"),console.log(" SessionStart[clear] -> cairn session-start (re-inject checkpoint after /clear)"),console.log(" SessionStart[compact]-> cairn session-start (re-inject checkpoint after /compact)"),process.exit(0)}else console.error("Usage: cairn <--version | install | install-hooks [--global] | minify | edit-guard | validate-map | checkpoint --auto | session-tick | session-start | resume-hint | roadmap-hint | roadmap-capture | memory-restore>"),process.exit(1);function rt(e,t){let n=S.join(e,"settings.json"),s={};if(g.existsSync(n))try{s=JSON.parse(g.readFileSync(n,"utf8"))}catch{}s.hooks=s.hooks||{};let o=s.hooks.PreToolUse||[];o.some(d=>d.matcher==="Read"&&d.hooks?.some(l=>l.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let r=o.filter(d=>!(d.matcher==="Edit"&&d.hooks?.some(l=>l.command==="cairn set-mode edit")));r.some(d=>d.matcher==="Edit"&&d.hooks?.some(l=>l.command==="cairn edit-guard"))||r.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),r.some(d=>d.matcher==="Write"&&d.hooks?.some(l=>l.command==="cairn edit-guard"))||r.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),s.hooks.PreToolUse=r;let i=(d,l)=>(d||[]).map(y=>({...y,hooks:(y.hooks||[]).filter(_=>!l(_.command))})).filter(y=>(y.hooks||[]).length>0),u=d=>typeof d=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(d),f=i(s.hooks.Stop,d=>d==="cairn checkpoint --auto"||d==="cairn roadmap-hint");f.some(d=>d.hooks?.some(l=>l.command==="cairn session-tick"))||f.push({hooks:[{type:"command",command:"cairn session-tick"}]}),s.hooks.Stop=f;let c=i(s.hooks.UserPromptSubmit,u);c.some(d=>d.hooks?.some(l=>l.command==="cairn resume-hint"))||c.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),c.some(d=>d.hooks?.some(l=>l.command==="cairn memory-restore"))||c.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),s.hooks.UserPromptSubmit=c;let a=s.hooks.SessionStart||[],p=d=>{a.some(l=>l.matcher===d&&l.hooks?.some(y=>y.command==="cairn session-start"))||a.push({matcher:d,hooks:[{type:"command",command:"cairn session-start"}]})};p("clear"),p("compact"),s.hooks.SessionStart=a;let m=i(s.hooks.PostToolUse,u),h=d=>{m.some(l=>l.matcher===d&&l.hooks?.some(y=>y.command==="cairn roadmap-capture"))||m.push({matcher:d,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(h("ExitPlanMode"),h("TodoWrite"),s.hooks.PostToolUse=m,t){let d=S.join(e,"scripts");for(let l of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let y=S.join(d,l);if(g.existsSync(y))try{g.unlinkSync(y)}catch{}}}if(!t){let d=S.join(process.cwd(),".cairn");g.mkdirSync(d,{recursive:!0});let l=S.join(d,".cairn-project");g.existsSync(l)||g.writeFileSync(l,"","utf8")}g.mkdirSync(e,{recursive:!0}),g.writeFileSync(n,JSON.stringify(s,null,2),"utf8")}
105
+ `),process.exit(0)}else if(N==="install"){let e=process.platform,t=S.join(G.homedir(),".claude.json"),n={};if(h.existsSync(t))try{n=JSON.parse(h.readFileSync(t,"utf8"))}catch{}n.mcpServers=n.mcpServers||{};let s=!!n.mcpServers.cairn;n.mcpServers.cairn={...s&&typeof n.mcpServers.cairn=="object"?n.mcpServers.cairn:{},command:"cairn-mcp",terminalConnect:lt},h.writeFileSync(t,JSON.stringify(n,null,2),"utf8");let o=S.join(G.homedir(),".claude");ct(o,!0),console.log("Cairn installed successfully!"),console.log(""),console.log(` Platform : ${e}`),console.log(` MCP : cairn-mcp registered in ${t}${s?" (updated)":""}`),console.log(` Hooks : installed in ${S.join(o,"settings.json")}`),console.log(""),console.log("Active hooks:"),console.log(" PreToolUse[Read] -> cairn minify (compress source files for reading)"),console.log(" PreToolUse[Edit] -> cairn edit-guard (block Edit until re-read with full content)"),console.log(" PreToolUse[Write] -> cairn edit-guard (clear minify state before full file overwrite)"),console.log(" PostToolUse[ExitPlanMode] -> cairn roadmap-capture (approved plan -> roadmap phases)"),console.log(" PostToolUse[TodoWrite] -> cairn roadmap-capture (todos -> roadmap sub-tasks)"),console.log(" Stop -> cairn session-tick (checkpoint + memory backup + roadmap, surfaced)"),console.log(" UserPromptSubmit -> cairn resume-hint (remind Claude of prior session)"),console.log(" UserPromptSubmit -> cairn memory-restore (restore .cairn/memory -> Claude store if empty)"),console.log(" SessionStart[clear] -> cairn session-start (re-inject checkpoint after /clear)"),console.log(" SessionStart[compact]-> cairn session-start (re-inject checkpoint after /compact)"),console.log(""),console.log("Restart Claude Code to activate the MCP server, then open any project and start working."),console.log("Upgrade any time with: npm install -g @misterhuydo/cairn-mcp"),process.exit(0)}else if(N==="install-hooks"){let e=process.argv.includes("--global"),t=e?S.join(G.homedir(),".claude"):S.join(process.cwd(),".claude");ct(t,e),console.log(`Cairn hooks installed in ${e?"global (~/.claude/settings.json)":"project (.claude/settings.json)"}`),console.log(""),console.log("Active hooks:"),console.log(" PreToolUse[Read] -> cairn minify (compress source files for reading)"),console.log(" PreToolUse[Edit] -> cairn edit-guard (block Edit until re-read with full content)"),console.log(" PreToolUse[Write] -> cairn edit-guard (clear minify state before full file overwrite)"),console.log(" PostToolUse[ExitPlanMode] -> cairn roadmap-capture (approved plan -> roadmap phases)"),console.log(" PostToolUse[TodoWrite] -> cairn roadmap-capture (todos -> roadmap sub-tasks)"),console.log(" Stop -> cairn session-tick (checkpoint + memory backup + roadmap, surfaced)"),console.log(" UserPromptSubmit -> cairn resume-hint (remind Claude of prior session)"),console.log(" UserPromptSubmit -> cairn memory-restore (restore .cairn/memory -> Claude store if empty)"),console.log(" SessionStart[clear] -> cairn session-start (re-inject checkpoint after /clear)"),console.log(" SessionStart[compact]-> cairn session-start (re-inject checkpoint after /compact)"),process.exit(0)}else console.error("Usage: cairn <--version | install | install-hooks [--global] | minify | edit-guard | validate-map | checkpoint --auto | session-tick | session-start | resume-hint | roadmap-hint | roadmap-capture | memory-restore>"),process.exit(1);function ct(e,t){let n=S.join(e,"settings.json"),s={};if(h.existsSync(n))try{s=JSON.parse(h.readFileSync(n,"utf8"))}catch{}s.hooks=s.hooks||{};let o=s.hooks.PreToolUse||[];o.some(m=>m.matcher==="Read"&&m.hooks?.some(u=>u.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let r=o.filter(m=>!(m.matcher==="Edit"&&m.hooks?.some(u=>u.command==="cairn set-mode edit")));r.some(m=>m.matcher==="Edit"&&m.hooks?.some(u=>u.command==="cairn edit-guard"))||r.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),r.some(m=>m.matcher==="Write"&&m.hooks?.some(u=>u.command==="cairn edit-guard"))||r.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),s.hooks.PreToolUse=r;let i=(m,u)=>(m||[]).map(g=>({...g,hooks:(g.hooks||[]).filter(_=>!u(_.command))})).filter(g=>(g.hooks||[]).length>0),l=m=>typeof m=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(m),p=i(s.hooks.Stop,m=>m==="cairn checkpoint --auto"||m==="cairn roadmap-hint");p.some(m=>m.hooks?.some(u=>u.command==="cairn session-tick"))||p.push({hooks:[{type:"command",command:"cairn session-tick"}]}),s.hooks.Stop=p;let a=i(s.hooks.UserPromptSubmit,l);a.some(m=>m.hooks?.some(u=>u.command==="cairn resume-hint"))||a.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),a.some(m=>m.hooks?.some(u=>u.command==="cairn memory-restore"))||a.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),s.hooks.UserPromptSubmit=a;let c=s.hooks.SessionStart||[],d=m=>{c.some(u=>u.matcher===m&&u.hooks?.some(g=>g.command==="cairn session-start"))||c.push({matcher:m,hooks:[{type:"command",command:"cairn session-start"}]})};d("clear"),d("compact"),s.hooks.SessionStart=c;let f=i(s.hooks.PostToolUse,l),y=m=>{f.some(u=>u.matcher===m&&u.hooks?.some(g=>g.command==="cairn roadmap-capture"))||f.push({matcher:m,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(y("ExitPlanMode"),y("TodoWrite"),s.hooks.PostToolUse=f,t){let m=S.join(e,"scripts");for(let u of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let g=S.join(m,u);if(h.existsSync(g))try{h.unlinkSync(g)}catch{}}}if(!t){let m=S.join(process.cwd(),".cairn");h.mkdirSync(m,{recursive:!0});let u=S.join(m,".cairn-project");h.existsSync(u)||h.writeFileSync(u,"","utf8")}h.mkdirSync(e,{recursive:!0}),h.writeFileSync(n,JSON.stringify(s,null,2),"utf8")}