@misterhuydo/cairn-mcp 1.33.0 → 1.33.1

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
@@ -3,15 +3,15 @@ import h from"fs";import ee from"os";import w from"path";import{execSync as es}f
3
3
  `).filter(t=>t.trim().length>0).join(`
4
4
  `)}function kt(e){return e.split(`
5
5
  `).map(t=>t.trimEnd()).join(`
6
- `)}function Et(e){return e.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function Fe(e,t={}){let{noStyle:n=!1,noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=t,a=[],c=e.match(/<template>([\s\S]*?)<\/template>/i);if(c){let i=c[1];s&&(i=i.replace(/<!--[\s\S]*?-->/g,"")),o&&(i=i.split(`
6
+ `)}function Et(e){return e.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function Le(e,t={}){let{noStyle:n=!1,noComments:s=!0,noEmptyLines:o=!0,aggressive:r=!1}=t,a=[],c=e.match(/<template>([\s\S]*?)<\/template>/i);if(c){let i=c[1];s&&(i=i.replace(/<!--[\s\S]*?-->/g,"")),o&&(i=i.split(`
7
7
  `).filter(u=>u.trim()).join(`
8
8
  `)),i=i.replace(/\s+/g," ").trim(),i&&a.push(`<template>${i}</template>`)}let l=e.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(l){let i=l[1],u=de(l[2],"javascript",{noComments:s,noEmptyLines:o,aggressive:r}),d=/setup/i.test(i)?"<script setup>":"<script>";u&&a.push(`${d}${u}</script>`)}if(!n){let i=e.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(i){let u=i[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();u&&a.push(`<style${i[1]}>${u}</style>`)}}return a.join(`
9
9
 
10
- `)}import U from"fs";import ne from"path";import L from"fs";import $t from"os";import W from"path";var ge=null;function K(){return ge||process.cwd()}function Ce(e){let t=ge;return ge=e,t}function q(){let e=W.join(K(),".cairn");L.mkdirSync(W.join(e,"bundles"),{recursive:!0});let t=W.join(e,".cairn-project");return L.existsSync(t)||L.writeFileSync(t,"","utf8"),e}function _e(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Se(){let e=W.join($t.homedir(),".claude","projects",_e(K()));return L.existsSync(e)?W.join(e,"memory"):null}function Le(e){let t=W.join(e,"MEMORY.md");L.existsSync(t)||L.writeFileSync(t,`# Memory Index
11
- `,"utf8")}function xe(){let e=W.join(q(),"memory");L.mkdirSync(e,{recursive:!0}),Le(e);let t=Se();if(!t)return e;L.mkdirSync(t,{recursive:!0}),Le(t);let n=s=>L.readdirSync(s).some(o=>o.endsWith(".md")&&o!=="MEMORY.md");if(!n(t)&&n(e))for(let s of L.readdirSync(e)){let o=W.join(e,s);try{if(!L.statSync(o).isFile())continue;let r=L.statSync(o);L.copyFileSync(o,W.join(t,s));try{L.utimesSync(W.join(t,s),r.atime,r.mtime)}catch{}}catch{}}return t}import te from"fs";import Rt from"path";import{execSync as vt}from"child_process";var P="would_be_lost";var _s=2*1024*1024,Ot=[256*1024,1024*1024],Tt=".handoff-hint",jt=220;function we(e,t=jt){let n=String(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t-1)+"\u2026"}function Pt(e,t){try{let n=vt(`git log --oneline --after="${t}"`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim();return n?n.split(`
10
+ `)}import U from"fs";import ne from"path";import F from"fs";import $t from"os";import W from"path";var ge=null;function K(){return ge||process.cwd()}function Ce(e){let t=ge;return ge=e,t}function q(){let e=W.join(K(),".cairn");F.mkdirSync(W.join(e,"bundles"),{recursive:!0});let t=W.join(e,".cairn-project");return F.existsSync(t)||F.writeFileSync(t,"","utf8"),e}function _e(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Se(){let e=W.join($t.homedir(),".claude","projects",_e(K()));return F.existsSync(e)?W.join(e,"memory"):null}function Fe(e){let t=W.join(e,"MEMORY.md");F.existsSync(t)||F.writeFileSync(t,`# Memory Index
11
+ `,"utf8")}function xe(){let e=W.join(q(),"memory");F.mkdirSync(e,{recursive:!0}),Fe(e);let t=Se();if(!t)return e;F.mkdirSync(t,{recursive:!0}),Fe(t);let n=s=>F.readdirSync(s).some(o=>o.endsWith(".md")&&o!=="MEMORY.md");if(!n(t)&&n(e))for(let s of F.readdirSync(e)){let o=W.join(e,s);try{if(!F.statSync(o).isFile())continue;let r=F.statSync(o);F.copyFileSync(o,W.join(t,s));try{F.utimesSync(W.join(t,s),r.atime,r.mtime)}catch{}}catch{}}return t}import te from"fs";import Rt from"path";import{execSync as vt}from"child_process";var P="would_be_lost";var _s=2*1024*1024,Ot=[256*1024,1024*1024],Tt=".handoff-hint",jt=220;function we(e,t=jt){let n=String(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t-1)+"\u2026"}function Pt(e,t){try{let n=vt(`git log --oneline --after="${t}"`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim();return n?n.split(`
12
12
  `).length:0}catch{return null}}function ce(e,t){let n=typeof t?.[P]=="string"?t[P].trim():"";if(!n)return{recorded:!1,text:"",at:null,commits:null,stale:!1};let s=t[`${P}_at`]||t.checkpoint_at||null,o=s?Pt(e,s):null;return{recorded:!0,text:n,at:s,commits:o,stale:(o??0)>0}}function be(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: ${we(e.text)}${t}`}function Nt(){let e=Number(process.env.CAIRN_CONTEXT_HINT_TOKENS);return Number.isFinite(e)&&e>0?e:null}function Mt(e,t){let n=te.openSync(e,"r");try{let s=te.fstatSync(n).size,o=Math.min(s,t),r=Buffer.alloc(o);return te.readSync(n,r,0,o,s-o),{text:r.toString("utf8"),truncated:o<s}}finally{te.closeSync(n)}}function At(e,t){let n=e.split(`
13
- `),s=t?1:0;for(let o=n.length-1;o>=s;o--){let r=n[o].trim();if(!r.startsWith("{"))continue;let a;try{a=JSON.parse(r)}catch{continue}if(a?.isSidechain===!0)continue;let c=a?.message?.usage;if(!c)continue;let l=(c.input_tokens||0)+(c.cache_read_input_tokens||0)+(c.cache_creation_input_tokens||0)+(c.output_tokens||0);if(l>0)return l}return null}function Ft(e){if(!e)return{tokens:0,source:"none",bytes:0};let t;try{t=te.statSync(e).size}catch{return{tokens:0,source:"none",bytes:0}}for(let n of Ot){let s;try{s=Mt(e,n)}catch{break}let o=At(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 Lt(e){return e.recorded&&!e.stale?"clear":"compact"}var Ct=["Cairn measures the count above but CANNOT see your context window \u2014 no hook input carries","it and the transcript never records a limit. You know your own window; cairn does not. So","the call is yours, and cairn will never make it for you: before starting what the user just","asked for, estimate what it will cost (files to read, tools to run, output to write) and","weigh that against what is left.","","If it plausibly does NOT fit, raise it BEFORE starting rather than stalling halfway:",` 1. Call cairn_checkpoint FIRST and answer ${P} \u2014 it is the only part of this`," session that survives either option.",' 2. Then AskUserQuestion with "Checkpoint + /clear", "Checkpoint + /compact" and "Keep',' going". State plainly that you CANNOT type the command \u2014 no tool or hook can trigger a'," slash command \u2014 so they have to type it themselves.",` 3. Never write ${P} for them, and never say there is nothing outstanding:`," cairn cannot see what was never written down, so it can never issue an all-clear.","","If it fits, say nothing and get on with it. Do not narrate the check, do not report a","percentage as if cairn had given you one, and do not re-raise what the user already declined."].join(`
14
- `);function De({cwd:e,session:t,transcriptPath:n,bytes:s,usage:o}){let r=o??Ft(n);r.source==="none"&&typeof s=="number"&&(r={tokens:0,source:"bytes",bytes:s});let a=s??r.bytes;if(r.source==="none")return null;let c=r.source==="transcript"?`${r.tokens.toLocaleString()} tokens used so far`:`transcript is ${(a/1024/1024).toFixed(1)} MB, token count unreadable (size signal only, not a token count)`,l=ce(e,t),i=Lt(l),u;l.recorded?l.stale?u=`Handoff is STALE, written before the ${l.commits} commit(s) since: `+we(l.text):u=`Handoff is current: ${we(l.text)}`:u="Nothing is recorded about what a reset would lose.";let d=Nt(),p=d!==null&&r.source==="transcript"&&r.tokens>=d?` This session declared CAIRN_CONTEXT_HINT_TOKENS=${d.toLocaleString()} and is past it.`:"";return{line:`[cairn] Context: ${c}. ${u}${p}`,protocol:Ct,state:l,recommend:i,usage:r,bytes:a}}function Ie(e){try{te.unlinkSync(Rt.join(e,Tt))}catch{}}var Dt=1e3,It=100*1024;function Wt(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<=Dt)return r;let a=ne.join(t,"notes");U.mkdirSync(a,{recursive:!0});let l=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${s}.md`,i=ne.join(a,l);return U.writeFileSync(i,r,"utf8"),{description:r.split(`
13
+ `),s=t?1:0;for(let o=n.length-1;o>=s;o--){let r=n[o].trim();if(!r.startsWith("{"))continue;let a;try{a=JSON.parse(r)}catch{continue}if(a?.isSidechain===!0)continue;let c=a?.message?.usage;if(!c)continue;let l=(c.input_tokens||0)+(c.cache_read_input_tokens||0)+(c.cache_creation_input_tokens||0)+(c.output_tokens||0);if(l>0)return l}return null}function Lt(e){if(!e)return{tokens:0,source:"none",bytes:0};let t;try{t=te.statSync(e).size}catch{return{tokens:0,source:"none",bytes:0}}for(let n of Ot){let s;try{s=Mt(e,n)}catch{break}let o=At(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 Ft(e){return e.recorded&&!e.stale?"clear":"compact"}var Ct=["Cairn CANNOT see your context window; you can, so the call is yours: judge whether what was","just asked fits in what is left. If it does, say nothing and get on with it, and do not",`re-raise a decline. If it does not, call cairn_checkpoint FIRST (answer ${P} \u2014 it`,'is all of this session that survives), then AskUserQuestion offering "Checkpoint + /clear",','"Checkpoint + /compact" or "Keep going", saying plainly they must type it: you CANNOT type the',`command. Never write ${P} for them and never issue an all-clear.`].join(`
14
+ `);function De({cwd:e,session:t,transcriptPath:n,bytes:s,usage:o}){let r=o??Lt(n);r.source==="none"&&typeof s=="number"&&(r={tokens:0,source:"bytes",bytes:s});let a=s??r.bytes;if(r.source==="none")return null;let c=r.source==="transcript"?`${r.tokens.toLocaleString()} tokens used so far`:`transcript is ${(a/1024/1024).toFixed(1)} MB, token count unreadable (size signal only, not a token count)`,l=ce(e,t),i=Ft(l),u;l.recorded?l.stale?u=`Handoff is STALE, written before the ${l.commits} commit(s) since: `+we(l.text):u=`Handoff is current: ${we(l.text)}`:u="Nothing is recorded about what a reset would lose.";let d=Nt(),p=d!==null&&r.source==="transcript"&&r.tokens>=d?` This session declared CAIRN_CONTEXT_HINT_TOKENS=${d.toLocaleString()} and is past it.`:"";return{line:`[cairn] Context: ${c}. ${u}${p}`,protocol:Ct,state:l,recommend:i,usage:r,bytes:a}}function Ie(e){try{te.unlinkSync(Rt.join(e,Tt))}catch{}}var Dt=1e3,It=100*1024;function Wt(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<=Dt)return r;let a=ne.join(t,"notes");U.mkdirSync(a,{recursive:!0});let l=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${s}.md`,i=ne.join(a,l);return U.writeFileSync(i,r,"utf8"),{description:r.split(`
15
15
  `)[0].slice(0,120),ref:ne.join(".cairn","notes",l)}}function ke(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 a=q(),c=ne.join(a,"session.json"),l=new Date().toISOString().slice(0,10),i=new Date().toISOString(),u={};try{u=JSON.parse(U.readFileSync(c,"utf8"))||{}}catch{}let d=typeof r[P]=="string"&&r[P].trim()?r[P].trim():null,p=d??(typeof u[P]=="string"?u[P]:null),f=d?i:u[`${P}_at`]??null,m={};try{let _=e.prepare("SELECT path FROM main.files").all();for(let{path:b}of _)try{m[b]=U.statSync(b).mtimeMs}catch{}}catch{}for(let _ of n)try{m[_]=U.statSync(_).mtimeMs}catch{}let x=s.map((_,b)=>Wt(_,a,l,b));if(U.existsSync(c))try{if(U.statSync(c).size>It){let b=1;for(;U.existsSync(ne.join(a,`session.${b}.json`));)b++;U.renameSync(c,ne.join(a,`session.${b}.json`))}}catch{}let k={message:t,checkpoint_at:i,last_manual_checkpoint_at:o?u.last_manual_checkpoint_at??null:i,last_manual_message:o?u.last_manual_message??null:t,active_files:n,notes:x,[P]:p,[`${P}_at`]:f,mtime_snapshot:m};return U.writeFileSync(c,JSON.stringify(k,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:k.checkpoint_at,active_files_tracked:n.length,notes_saved:x.length,[P]:p?d?"recorded":"carried forward from the previous checkpoint":`not recorded \u2014 nothing will tell the next session what this one would lose (pass ${P})`},null,2)}]}}import B from"fs";import C from"path";import Ut from"crypto";var We=80;function Ee(e){let t=C.dirname(C.resolve(e)),n=C.parse(t).root;for(;;){let s=C.join(t,".cairn");try{if(B.statSync(s).isDirectory()&&B.existsSync(C.join(s,".cairn-project"))&&s.split(C.sep).filter(r=>r===".cairn").length===1)return s}catch{}if(t===n)return null;t=C.dirname(t)}}function Ue(e,t){let n=Ut.createHash("sha256").update(C.resolve(t)).digest("hex").slice(0,6);return C.join(e,"views",`${n}_${C.basename(t)}`)}function V(e){try{return JSON.parse(B.readFileSync(C.join(e,"minify-map.json"),"utf8"))}catch{return{}}}function I(e,t){B.mkdirSync(e,{recursive:!0}),B.writeFileSync(C.join(e,"minify-map.json"),JSON.stringify(t,null,2),"utf8")}function pe(e,t){let n=0;for(let[s,o]of Object.entries(t)){let r=!1;if(!B.existsSync(s))r=!0;else if(!B.existsSync(o.tempPath))r=!0;else try{B.statSync(s).mtimeMs>o.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(o.tempPath)try{B.unlinkSync(o.tempPath)}catch{}delete t[s],n++}}return n}function He(e,t){switch(t){case"javascript":case"typescript":return $e(e);case"java":return $e(e,{annotations:!0});case"python":return Yt(e);case"vue":return zt(e);case"sql":return Gt(e);default:return e.split(`
16
16
  `).slice(0,30).join(`
17
17
  `)+`
@@ -63,8 +63,8 @@ ${o}${c.trim()?`
63
63
  `+c:""}`;re.writeFileSync(X(),a+l,"utf8")}function Ve(e){let t=dn(e);if(t.length===0)return{added:[],skipped:[],total:0};let s=Oe()?[]:le(fe()).map(l=>({text:l.text,body:l.body})),o=new Set(s.map(l=>z(l.text))),r=[...s],a=[],c=[];for(let l of t){let i=z(l.text);if(o.has(i)){c.push(l.text);continue}o.add(i),r.push(l),a.push(l.text)}return a.length>0&&qe(r),{added:a,skipped:c,total:r.length}}function fn(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function mn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Xe(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let n=t.map(p=>({text:Pe(p.content||p.activeForm||""),status:fn(p.status)})).filter(p=>p.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(M).filter(p=>p.status!=="stale");if(s.length===0){let f=Oe()?[]:le(fe()).map(b=>({text:b.text,body:b.body})),m=new Set(f.map(b=>z(b.text))),x=[...f],k=0;for(let b of n){let $=z(b.text);m.has($)||(m.add($),x.push({text:b.text,body:null}),k++)}if(k===0)return{changed:!1};qe(x);let _=oe(e);return{changed:!0,seeded:k,diff:_}}let o=mn(e);o==null&&(o=s.find(p=>p.status==="in_progress")?.id??s.find(p=>p.status==="open")?.id??s[0].id);let r=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),a=new Map(r.map(p=>[z(p.text),p])),c=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),l=e.prepare("UPDATE main.roadmap_phases SET status = ? WHERE id = ?"),i=e.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(o).n,u=0,d=0;for(let p of n){let f=a.get(z(p.text));if(f){if(f.status==="done"&&p.status!=="done")continue;f.status!==p.status&&(l.run(p.status,f.id),d++)}else c.run(o,p.text,p.status,i++),u++}return{changed:u>0||d>0,parentId:o,inserted:u,updated:d}}var hn=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 yn(e){return[...new Set(z(e).split(" ").filter(t=>t.length>=4&&!hn.has(t)))]}function gn(e,t){if(!e||!t?.length)return null;let n=String(e),s=/\bphase\s*#?\s*(\d+)\b/i.exec(n);if(s){let a=parseInt(s[1],10),c=t.find(l=>l.phase_number===a);if(c)return{...c,confidence:"high"}}let o=new Set(z(n).split(" ").filter(Boolean)),r=null;for(let a of t){let c=yn(a.text);if(c.length<2)continue;let l=c.filter(u=>o.has(u)),i=l.length/c.length;l.length>=2&&i>=.6&&(!r||i>r.ratio)&&(r={...a,confidence:"medium",ratio:i})}return r?(delete r.ratio,r):null}function je(e,t){return un(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()}function Ze(e,t){let n=Te.join(t,".cairn"),s=Te.join(n,".last-commit"),o=Te.join(n,"roadmap-pending.json"),r;try{r=je(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let a=null;try{a=re.readFileSync(s,"utf8").trim()||null}catch{}let c=[];if(a&&a!==r)try{c=je(t,`rev-list --no-merges --reverse ${a}..HEAD`).split(`
64
64
  `).filter(Boolean)}catch{c=[]}try{re.writeFileSync(s,r,"utf8")}catch{}let l=[];try{let i=JSON.parse(re.readFileSync(o,"utf8"));Array.isArray(i)&&(l=i)}catch{}if(c.length){let i=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(M);for(let u of c){let d="";try{d=je(t,`show -s --format=%s%n%b ${u}`)}catch{}let p=gn(d,i);p&&!l.some(f=>f.phase_id===p.id)&&l.push({phase_id:p.id,phase_number:p.phase_number,text:p.text,commit:u.slice(0,7),confidence:p.confidence})}}if(l.length){let i=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(u=>u.id));l=l.filter(u=>i.has(u.phase_id))}try{re.writeFileSync(o,JSON.stringify(l),"utf8")}catch{}return{candidates:l,newCommits:c.length}}import J from"fs";import A from"path";var he="cairn",Q="roadmap/1",_n="<!-- slot:roadmap";function Sn(e){return`${_n} format=${Q} provider=${he} generated=${e} -->`}var xn=`<!-- ${he}:roadmap -->`,wn=/^<!--\s*slot:roadmap\b([^>]*?)-->/,bn=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,kn=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function En(e){if(typeof e!="string")return null;let t=e.indexOf(`
65
65
  `);return(t===-1?e:e.slice(0,t)).trim()}function $n(e){let t=En(e);if(!t)return null;let n=wn.exec(t);if(n){let o={};for(let r of n[1].matchAll(kn))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=bn.exec(t);return s?{provider:s[1].toLowerCase(),format:null,generated:null}:null}var Rn=/^```roadmap-json[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/m;function tt(e){if(typeof e!="string")return null;let t=Rn.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 vn=new Set(["format","provider","generated","title","cursor","cursor_phase","cursor_path","phases","shipped_phases","migrated_from","migrated"]),On=new Set(["id","number","title","status","shipped","commit","goal","blocked_by","slices","detail","links"]);function nt(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 st(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),st(n.slices,t));return t}function Tn(e){if(typeof e!="string")return null;let t=tt(e)?.migrated_from;if(typeof t=="string"&&t)return t;let n=e.indexOf(`
66
- `);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 jn=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,Pn=["decision","knowledge","experience","preference"];function Nn(e){let t=A.relative(K(),e).split(A.sep).join("/");return!t||t.startsWith("..")||A.isAbsolute(t)?null:t}function Mn(e){let t=A.join(K(),".cairn","memory");return[A.join(t,`${e}.md`),...Pn.map(n=>A.join(t,`${n}_${e}.md`))]}function An(e){if(!e)return[];let t=[],n=new Set;for(let s of String(e).matchAll(jn)){let o=s[1].trim();if(!(!o||n.has(o))){n.add(o);for(let r of Mn(o)){if(!J.existsSync(r))continue;let a=Nn(r);a&&t.push({kind:"memory",title:o,path:a});break}}}return t}var Fn={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Ln=new Set(["open","in_progress","done","blocked","stale"]);function Cn(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function ot(){return A.join(K(),"ROADMAP.md")}function rt(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function Dn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function In(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 Wn(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 Un(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(y=>y.status!=="stale").length===0)return null;let n=Dn(e),s=In(e),o=new Map(t.map(y=>[y.id,y.status])),r=new Map(t.map(y=>[y.id,y.phase_number!=null?`Phase ${y.phase_number}`:y.text])),a=new Map(t.map(y=>[y.id,y])),c=new Map;for(let y of t){let S=y.parent_phase_id??"root";c.has(S)||c.set(S,[]),c.get(S).push(y)}for(let y of c.values())y.sort(Wn);let l=new Map,i=[];for(let y of c.get("root")||[])if(y.decision_name!=null){if(y.decision_name===M&&y.phase_number==null){y.status==="done"&&i.push(y);continue}l.has(y.decision_name)||l.set(y.decision_name,[]),l.get(y.decision_name).push(y)}let u=[...l.keys()].sort((y,S)=>y===M?-1:S===M?1:y<S?-1:y>S?1:0),d=new Map,p=!1;for(let y of u){let S=l.get(y).filter(E=>E.status!=="stale"),g=y===M?S.filter(E=>E.status!=="done"):S;g.length!==0&&(p=!0,d.set(y,{visible:g,doneCount:S.filter(E=>E.status==="done").length,total:S.length}))}if(!p)return null;let f=y=>{let S=s.get(y);return S?[...S].filter(g=>o.get(g)!=="done").map(g=>r.get(g)||`#${g}`):[]},m=y=>{let S=s.get(y);return S?[...S].filter(g=>a.has(g)).sort((g,E)=>g-E).map(g=>`phase-${g}`):[]},x=y=>(c.get(y)||[]).filter(S=>S.status!=="stale"),k=y=>c.get(y)||[],_=new Map(u.map(y=>[y,l.get(y)])),b=new Map;for(let y of me()||[])y.id!=null&&b.set(y.id,{shipped:y.date,commit:y.commit||null});return{cursor:n,decisionNames:u,visibleRoots:d,children:x,unfinishedBlockers:f,blockerIds:m,ancestry:y=>{let S=[],g=new Set,E=y;for(;E!=null&&a.has(E)&&!g.has(E);)g.add(E),S.unshift(E),E=a.get(E).parent_phase_id;return S},allChildren:k,jsonRoots:_,archivedRoots:i,shipped:y=>b.get(y)||null}}var Ne=120;function Hn(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 it(e){if(!e)return null;let t=[];for(let a of e.split(`
67
- `)){let c=a.trim();if(!c){if(t.length)break;continue}t.push(c)}if(!t.length)return null;let n=Hn(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>Ne){let a=s.lastIndexOf(" ",Ne-1);s=s.slice(0,a>60?a:Ne-1).trimEnd()+"\u2026"}return s}var Jn=8;function Qe(){let e=me();if(!e||e.length===0)return[];let t=e.slice(-Jn).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 et(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 Bn(e,t){let n=[Sn(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(...Qe()),n;let{cursor:s,decisionNames:o,visibleRoots:r,children:a,unfinishedBlockers:c}=e;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let l=(i,u)=>{let d=" ".repeat(u),p=i.phase_number!=null?`Phase ${i.phase_number}: ${i.text}`:i.text,f=Fn[i.status]||i.status,m=c(i.id);m.length&&(f+=` \xB7 waiting on ${m.join(", ")}`),s===i.id&&(f+=" \xB7 \u2190 current"),n.push(`${d}- [${Cn(i.status)}] **${p}** \u2014 ${f}`);let x=it(i.body);x&&n.push(`${d} ${x}`);for(let k of a(i.id))l(k,u+1)};for(let i of o){let u=r.get(i);if(u){n.push(""),i===M?n.push("## Active plan","",et(u)):n.push(`## ${i}`,"",et(u)),n.push("");for(let d of u.visible)l(d,0)}}return n.push(...Qe()),n}function at(e,t,n,s){let o=n.get(`phase-${e.id}`),r=it(e.body),a=An(e.body),c=t.blockerIds(e.id),l={id:`phase-${e.id}`};e.phase_number!=null&&(l.number=e.phase_number),l.title=e.text,l.status=Ln.has(e.status)?e.status:"open";let i=e.status==="done"?t.shipped(e.id):null;if(i&&(l.shipped=i.shipped,i.commit&&(l.commit=i.commit)),r&&(l.goal=r),c.length&&(l.blocked_by=c),!s){let u=[],d=p=>{for(let f of t.allChildren(p))u.push(at(f,t,n,!0)),d(f.id)};d(e.id),u.length&&(l.slices=u)}return e.body&&e.body.trim()&&(l.detail=e.body.trim()),a.length&&(l.links=a),{...l,...nt(o,On)}}function Yn(e,t){let n=st(t.previous?.phases),s=[];for(let u of e?.decisionNames??[]){let d=e.jsonRoots.get(u);if(d)for(let p of d)s.push(at(p,e,n,!1))}let o=(e?.archivedRoots??[]).map(u=>{let d=e.shipped(u.id),p={id:`phase-${u.id}`,title:u.text,status:"done"};return d&&(p.shipped=d.shipped,d.commit&&(p.commit=d.commit)),p}),r=new Set(s.map(u=>u.id)),a=e?.cursor!=null?e.ancestry(e.cursor):[],c=a.length?`phase-${a[0]}`:null,l={format:Q,provider:he,generated:t.generated,title:`${A.basename(K())} roadmap`,cursor:e?.cursor!=null?`phase-${e.cursor}`:null,cursor_phase:c!==null&&r.has(c)?c:null,cursor_path:a.length?a.map(u=>`phase-${u}`):null};t.migratedFrom&&(l.migrated_from=t.migratedFrom,t.migrated&&(l.migrated=t.migrated)),l.phases=s,l.shipped_phases=o;let i={...l,...nt(t.previous,vn)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(i,null,2),"```","","</details>"]}function Me(e,t={}){let n={generated:t.generated||rt(),migratedFrom:t.migratedFrom||null,migrated:t.migrated||null,previous:t.previous&&typeof t.previous=="object"?t.previous:null},s=Un(e);return s==null&&!t.allowEmpty?null:[...Bn(s,n),...Yn(s,n)].join(`
66
+ `);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 jn=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,Pn=["decision","knowledge","experience","preference"];function Nn(e){let t=A.relative(K(),e).split(A.sep).join("/");return!t||t.startsWith("..")||A.isAbsolute(t)?null:t}function Mn(e){let t=A.join(K(),".cairn","memory");return[A.join(t,`${e}.md`),...Pn.map(n=>A.join(t,`${n}_${e}.md`))]}function An(e){if(!e)return[];let t=[],n=new Set;for(let s of String(e).matchAll(jn)){let o=s[1].trim();if(!(!o||n.has(o))){n.add(o);for(let r of Mn(o)){if(!J.existsSync(r))continue;let a=Nn(r);a&&t.push({kind:"memory",title:o,path:a});break}}}return t}var Ln={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Fn=new Set(["open","in_progress","done","blocked","stale"]);function Cn(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function ot(){return A.join(K(),"ROADMAP.md")}function rt(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function Dn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function In(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 Wn(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 Un(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(y=>y.status!=="stale").length===0)return null;let n=Dn(e),s=In(e),o=new Map(t.map(y=>[y.id,y.status])),r=new Map(t.map(y=>[y.id,y.phase_number!=null?`Phase ${y.phase_number}`:y.text])),a=new Map(t.map(y=>[y.id,y])),c=new Map;for(let y of t){let S=y.parent_phase_id??"root";c.has(S)||c.set(S,[]),c.get(S).push(y)}for(let y of c.values())y.sort(Wn);let l=new Map,i=[];for(let y of c.get("root")||[])if(y.decision_name!=null){if(y.decision_name===M&&y.phase_number==null){y.status==="done"&&i.push(y);continue}l.has(y.decision_name)||l.set(y.decision_name,[]),l.get(y.decision_name).push(y)}let u=[...l.keys()].sort((y,S)=>y===M?-1:S===M?1:y<S?-1:y>S?1:0),d=new Map,p=!1;for(let y of u){let S=l.get(y).filter(E=>E.status!=="stale"),g=y===M?S.filter(E=>E.status!=="done"):S;g.length!==0&&(p=!0,d.set(y,{visible:g,doneCount:S.filter(E=>E.status==="done").length,total:S.length}))}if(!p)return null;let f=y=>{let S=s.get(y);return S?[...S].filter(g=>o.get(g)!=="done").map(g=>r.get(g)||`#${g}`):[]},m=y=>{let S=s.get(y);return S?[...S].filter(g=>a.has(g)).sort((g,E)=>g-E).map(g=>`phase-${g}`):[]},x=y=>(c.get(y)||[]).filter(S=>S.status!=="stale"),k=y=>c.get(y)||[],_=new Map(u.map(y=>[y,l.get(y)])),b=new Map;for(let y of me()||[])y.id!=null&&b.set(y.id,{shipped:y.date,commit:y.commit||null});return{cursor:n,decisionNames:u,visibleRoots:d,children:x,unfinishedBlockers:f,blockerIds:m,ancestry:y=>{let S=[],g=new Set,E=y;for(;E!=null&&a.has(E)&&!g.has(E);)g.add(E),S.unshift(E),E=a.get(E).parent_phase_id;return S},allChildren:k,jsonRoots:_,archivedRoots:i,shipped:y=>b.get(y)||null}}var Ne=120;function Hn(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 it(e){if(!e)return null;let t=[];for(let a of e.split(`
67
+ `)){let c=a.trim();if(!c){if(t.length)break;continue}t.push(c)}if(!t.length)return null;let n=Hn(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>Ne){let a=s.lastIndexOf(" ",Ne-1);s=s.slice(0,a>60?a:Ne-1).trimEnd()+"\u2026"}return s}var Jn=8;function Qe(){let e=me();if(!e||e.length===0)return[];let t=e.slice(-Jn).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 et(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 Bn(e,t){let n=[Sn(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(...Qe()),n;let{cursor:s,decisionNames:o,visibleRoots:r,children:a,unfinishedBlockers:c}=e;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let l=(i,u)=>{let d=" ".repeat(u),p=i.phase_number!=null?`Phase ${i.phase_number}: ${i.text}`:i.text,f=Ln[i.status]||i.status,m=c(i.id);m.length&&(f+=` \xB7 waiting on ${m.join(", ")}`),s===i.id&&(f+=" \xB7 \u2190 current"),n.push(`${d}- [${Cn(i.status)}] **${p}** \u2014 ${f}`);let x=it(i.body);x&&n.push(`${d} ${x}`);for(let k of a(i.id))l(k,u+1)};for(let i of o){let u=r.get(i);if(u){n.push(""),i===M?n.push("## Active plan","",et(u)):n.push(`## ${i}`,"",et(u)),n.push("");for(let d of u.visible)l(d,0)}}return n.push(...Qe()),n}function at(e,t,n,s){let o=n.get(`phase-${e.id}`),r=it(e.body),a=An(e.body),c=t.blockerIds(e.id),l={id:`phase-${e.id}`};e.phase_number!=null&&(l.number=e.phase_number),l.title=e.text,l.status=Fn.has(e.status)?e.status:"open";let i=e.status==="done"?t.shipped(e.id):null;if(i&&(l.shipped=i.shipped,i.commit&&(l.commit=i.commit)),r&&(l.goal=r),c.length&&(l.blocked_by=c),!s){let u=[],d=p=>{for(let f of t.allChildren(p))u.push(at(f,t,n,!0)),d(f.id)};d(e.id),u.length&&(l.slices=u)}return e.body&&e.body.trim()&&(l.detail=e.body.trim()),a.length&&(l.links=a),{...l,...nt(o,On)}}function Yn(e,t){let n=st(t.previous?.phases),s=[];for(let u of e?.decisionNames??[]){let d=e.jsonRoots.get(u);if(d)for(let p of d)s.push(at(p,e,n,!1))}let o=(e?.archivedRoots??[]).map(u=>{let d=e.shipped(u.id),p={id:`phase-${u.id}`,title:u.text,status:"done"};return d&&(p.shipped=d.shipped,d.commit&&(p.commit=d.commit)),p}),r=new Set(s.map(u=>u.id)),a=e?.cursor!=null?e.ancestry(e.cursor):[],c=a.length?`phase-${a[0]}`:null,l={format:Q,provider:he,generated:t.generated,title:`${A.basename(K())} roadmap`,cursor:e?.cursor!=null?`phase-${e.cursor}`:null,cursor_phase:c!==null&&r.has(c)?c:null,cursor_path:a.length?a.map(u=>`phase-${u}`):null};t.migratedFrom&&(l.migrated_from=t.migratedFrom,t.migrated&&(l.migrated=t.migrated)),l.phases=s,l.shipped_phases=o;let i={...l,...nt(t.previous,vn)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(i,null,2),"```","","</details>"]}function Me(e,t={}){let n={generated:t.generated||rt(),migratedFrom:t.migratedFrom||null,migrated:t.migrated||null,previous:t.previous&&typeof t.previous=="object"?t.previous:null},s=Un(e);return s==null&&!t.allowEmpty?null:[...Bn(s,n),...Yn(s,n)].join(`
68
68
  `)+`
69
69
  `}function zn(e){let t=A.join(e,"ROADMAP.bak.md");if(!J.existsSync(t))return t;for(let n=2;n<=1e3;n++){let s=A.join(e,`ROADMAP.bak.${n}.md`);if(!J.existsSync(s))return s}return null}function Gn(e,t){let n=A.join(A.dirname(e),`.${A.basename(e)}.${process.pid}.tmp`);try{J.writeFileSync(n,t,"utf8"),J.renameSync(n,e)}catch(s){try{J.unlinkSync(n)}catch{}throw s}}function Z(e,t,n={}){return{written:!1,reason:e,warning:`[cairn] ROADMAP.md was not updated \u2014 ${e}`,path:t,...n}}function ct(e){let t=ot(),n=null;if(J.existsSync(t))try{n=J.readFileSync(t,"utf8")}catch(x){return Z(`the existing file could not be read (${x.message})`,t)}let s=$n(n),o=n!=null&&(s?.provider===he||n.startsWith(xn));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?tt(n):null,a=o?Tn(n):null,c=o&&typeof r?.migrated=="string"?r.migrated:null,l=typeof r?.generated=="string"?r.generated:null,i=!1,u=Me(e,{generated:l||"1970-01-01T00:00:00Z",migratedFrom:a,migrated:c,previous:r});if(u==null){if(!o)return{written:!1,reason:"empty roadmap \u2014 no file written",path:t};i=!0,u=Me(e,{generated:l||"1970-01-01T00:00:00Z",migratedFrom:a,migrated:c,previous:r,allowEmpty:!0})}if(o&&n===u)return{written:!1,unchanged:!0,path:t,bytes:Buffer.byteLength(u)};let d=rt(),p=null,f=null;if(n!=null&&!o){let x=zn(A.dirname(t));if(x==null)return Z("no free ROADMAP.bak name was available to preserve the existing file",t);try{J.renameSync(t,x)}catch(k){return Z(`the existing file could not be preserved (${k.message})`,t)}p=A.basename(x),f=x,a=p,c=d}u=Me(e,{generated:d,migratedFrom:a,migrated:c,previous:r,allowEmpty:i});try{Gn(t,u)}catch(x){if(f)try{return J.renameSync(f,t),Z(`write failed (${x.message}); the original ROADMAP.md was restored`,t)}catch{return Z(`write failed (${x.message}); the original ROADMAP.md is at ${p} and must be restored by hand`,t,{backed_up_to:p})}return Z(`write failed (${x.message})`,t)}let m={written:!0,path:t,bytes:Buffer.byteLength(u),format:Q,generated:d};return p&&(m.backed_up_to=p,m.warning=`[cairn] The existing ROADMAP.md was not cairn-generated. It has been preserved as ${p}, and ROADMAP.md is now generated from cairn's roadmap.`),m}function Kn(e){try{return ct(e)}catch(t){process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${t.message}
70
70
  `);let n=null;try{n=ot()}catch{}return Z(`publish failed (${t.message})`,n)}}function ye(e){let t=Kn(e);return t&&t.warning?t.warning:null}var qn=new Set(["open","in_progress"]);function Vn(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 Xn(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 lt(e,t){Vn(e,t),e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(t),Xn(e,t)}function Zn(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 Qn(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 ut(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=Zn(e),r=new Map;for(let d of n){let p=d.parent_phase_id??`root:${d.decision_name}`;r.has(p)||r.set(p,[]),r.get(p).push(d)}for(let d of r.values())d.sort((p,f)=>p.phase_number!=null&&f.phase_number!=null?p.phase_number-f.phase_number:p.position-f.position||p.id-f.id);let a=[],c=new Set;for(let d of n){if(d.parent_phase_id!=null||c.has(d.decision_name))continue;c.add(d.decision_name);let p=[],f=r.get(`root:${d.decision_name}`)||[];for(let m=f.length-1;m>=0;m--)p.push(f[m]);for(;p.length;){let m=p.pop();a.push(m);let x=r.get(m.id)||[];for(let k=x.length-1;k>=0;k--)p.push(x[k])}}let l=d=>qn.has(d.status),i=[],u=0;if(t!=null){let d=a.findIndex(p=>p.id===t);u=d===-1?0:d+1}for(let d=u;d<a.length;d++){let p=a[d];if(!l(p))continue;let f=Qn(p.id,o,s);if(f.length>0){i.push({id:p.id,blockers:f});continue}let m=t!=null?n.find(_=>_.id===t):null,x=m&&m.decision_name!==p.decision_name,k;return t==null?k=`First actionable phase: ${p.decision_name??"(sub-task)"} / Phase ${p.phase_number??"\xB7"}.`:x?k=`Previous decision "${m.decision_name}" is complete \u2014 advanced to "${p.decision_name}".`:k=`Next open phase in "${p.decision_name??"(sub-task)"}" after id ${t}.`,i.length>0&&(k+=` Skipped ${i.length} dep-blocked phase(s): ${i.map(_=>`id ${_.id} (blocked by ${_.blockers.join(",")})`).join("; ")}.`),{id:p.id,reason:k,skipped_blocked:i}}return i.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${i.map(d=>`id ${d.id} (blocked by ${d.blockers.join(",")})`).join("; ")}.`,skipped_blocked:i}:{id:null,reason:"No actionable phases remaining."}}var ts=new Set(["java","typescript","javascript","vue","python","sql"]);function ue(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 mt={name:"cairn",contract:1,title:"cairn",slots:["roadmap"],provides:{roadmap:{path:"ROADMAP.md",format:Q}},spec:"Project memory and roadmap. Publishes the plan as ROADMAP.md in the roadmap/1 slot format."};function ns(){let e=w.join(ee.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===Q)return!1;n.terminalConnect=mt;try{return h.writeFileSync(e,JSON.stringify(t,null,2),"utf8"),!0}catch{return!1}}function ht(e){let t=w.join(ee.homedir(),".claude","projects",_e(e));return h.existsSync(t)?w.join(t,"memory"):null}function yt(e){try{return h.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}}function ss(e){let t=ht(e);if(!t||!h.existsSync(t))return 0;let n=w.join(e,".cairn","memory");h.mkdirSync(n,{recursive:!0});let s=0;for(let o of yt(t)){let r=w.join(t,o),a;try{a=h.statSync(r)}catch{continue}if(!a.isFile())continue;let c=w.join(n,o),l=!h.existsSync(c);if(!l)try{l=a.mtimeMs>h.statSync(c).mtimeMs}catch{l=!0}if(l){h.copyFileSync(r,c);try{h.utimesSync(c,a.atime,a.mtime)}catch{}s++}}return s}function os(e){let t=ht(e);if(!t||(h.mkdirSync(t,{recursive:!0}),yt(t).some(o=>o!=="MEMORY.md")))return 0;let n=w.join(e,".cairn","memory");if(!h.existsSync(n))return 0;let s=0;for(let o of h.readdirSync(n)){let r=w.join(n,o),a;try{a=h.statSync(r)}catch{continue}if(a.isFile()){h.copyFileSync(r,w.join(t,o));try{h.utimesSync(w.join(t,o),a.atime,a.mtime)}catch{}s++}}return s}function dt(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: ${be(ce(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 a=i=>{try{return es(i,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}};try{let i=new Date(t.checkpoint_at).toISOString(),u=a(`git log --oneline --after="${i}"`);u!==null&&r.push(` commits_since: ${u?u.split(`
@@ -75,29 +75,29 @@ ${l.split(`
75
75
  `)}`),r.push(" \u2192 Call cairn_resume for full index status and open todos."),r}async function pt(e){let t={active:!1},n=w.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=c=>c.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");function a(c,l){if(!h.existsSync(c))return null;let i=h.readFileSync(c,"utf8"),u=!1,d=!1,p=[];for(let f of i.split(/\r?\n/)){if(/^##\s+Phases\s*$/i.test(f)){u=!0;continue}if(u&&/^##\s+/.test(f)&&!/^###/.test(f))break;if(!u)continue;let m=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(f);if(m){if(d)break;parseInt(m[1],10)===l&&(p.push(`### Phase ${m[1]}: ${m[2]}`),d=!0);continue}d&&p.push(f)}return p.join(`
76
76
  `).trim()||null}try{let l=o.prepare("SELECT phase_id FROM roadmap_cursor WHERE singleton = 1").get()?.phase_id??null;if(l==null)return o.close(),t;let i=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(),u={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},d=new Map;for(let v of i){let R=v.parent_phase_id??"root";d.has(R)||d.set(R,[]),d.get(R).push(v)}for(let v of d.values())v.sort((R,O)=>R.decision_name&&O.decision_name&&R.decision_name!==O.decision_name?R.decision_name.localeCompare(O.decision_name):R.phase_number!=null&&O.phase_number!=null?R.phase_number-O.phase_number:R.position-O.position||R.id-O.id);let p=d.get("root")||[],f=new Map;for(let v of p)v.decision_name!=null&&(f.has(v.decision_name)||f.set(v.decision_name,[]),f.get(v.decision_name).push(v));let m=[],x=(v,R)=>{for(let O of d.get(v)||[]){if(O.status==="stale")continue;let ae=l===O.id?" \u2192":"",G=O.phase_number!=null?`Phase ${O.phase_number}: ${O.text}`:O.text;m.push(`${" ".repeat(R)}${u[O.status]||"[?]"}${ae} ${G}`),x(O.id,R+1)}};for(let[v,R]of f){let O=R.filter(N=>N.status!=="stale");if(O.length===0)continue;let ae=O.every(N=>N.status==="done"),G=O.some(N=>N.status==="in_progress"),gt=ae?"done":G?"in_progress":"open",_t=O.filter(N=>N.status==="done").length,St=v===M?"Roadmap":`Decision: ${v}`;m.push(`${u[gt]} ${St} (${_t}/${O.length} done)`);for(let N of R){if(N.status==="stale"||v===M&&N.status==="done")continue;let xt=l===N.id?" \u2192":"";m.push(` ${u[N.status]||"[?]"}${xt} Phase ${N.phase_number}: ${N.text}`),x(N.id,2)}}let k=v=>i.find(R=>R.id===v),_=k(l),b=_?.decision_name??null,$=_?.phase_number??null,T=_;for(;T&&T.parent_phase_id!=null&&(T=k(T.parent_phase_id),!!T);)b=T.decision_name??b,$=T.phase_number??$;o.close();let y=_?_.phase_number!=null?`${_.decision_name} / Phase ${_.phase_number}: ${_.text}`:`${b} / Phase ${$} \u2192 ${_.text}`:"(unknown)",S=w.join(e,".last-roadmap-cursor"),g=null;try{g=parseInt(h.readFileSync(S,"utf8").trim(),10)}catch{}let E=g!==l;try{h.writeFileSync(S,String(l),"utf8")}catch{}let ie=null;if(E&&b!=null&&$!=null){let v=b===M?w.join(e,"roadmap.md"):w.join(e,"memory",`decision_${r(b)}.md`),R=a(v,$);R&&(ie=R.split(`
77
77
  `))}return{active:!0,currentLabel:y,treeLines:m,advanced:E,memoLines:ie}}catch{try{o?.close()}catch{}return t}}process.on("uncaughtException",e=>{process.stderr.write(`cairn: ${e.message}
78
- `),process.exit(0)});var F=process.argv[2];if(F==="--version"||F==="-v"){let e=JSON.parse(h.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(F==="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=w.resolve(t);let s=Ee(t);s||process.exit(0);let o=w.join(s,"views"),r=w.relative(o,t);!r.startsWith("..")&&!w.isAbsolute(r)&&process.exit(0);let a=V(s),l=pe(s,a)>0,i=a[t];i?.state==="edit-ready"&&(l&&I(s,a),process.exit(0)),i?.state==="compressed"&&(i.state="edit-ready",I(s,a),process.exit(0));let u=w.extname(t).toLowerCase(),d=Ae[u];(!d||!ts.has(d))&&(l&&I(s,a),process.exit(0));let p;try{p=h.readFileSync(t,"utf8")}catch{l&&I(s,a),process.exit(0)}let f=p.split(`
79
- `).length,m=d==="vue"?Fe(p):de(p,d),x=m.split(`
78
+ `),process.exit(0)});var L=process.argv[2];if(L==="--version"||L==="-v"){let e=JSON.parse(h.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(L==="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=w.resolve(t);let s=Ee(t);s||process.exit(0);let o=w.join(s,"views"),r=w.relative(o,t);!r.startsWith("..")&&!w.isAbsolute(r)&&process.exit(0);let a=V(s),l=pe(s,a)>0,i=a[t];i?.state==="edit-ready"&&(l&&I(s,a),process.exit(0)),i?.state==="compressed"&&(i.state="edit-ready",I(s,a),process.exit(0));let u=w.extname(t).toLowerCase(),d=Ae[u];(!d||!ts.has(d))&&(l&&I(s,a),process.exit(0));let p;try{p=h.readFileSync(t,"utf8")}catch{l&&I(s,a),process.exit(0)}let f=p.split(`
79
+ `).length,m=d==="vue"?Le(p):de(p,d),x=m.split(`
80
80
  `).length,k=f>0?Math.round((1-x/f)*100):0,_=Ue(s,t);h.mkdirSync(w.dirname(_),{recursive:!0}),h.writeFileSync(_,m,"utf8");let b=i?.readCount??0;a[t]={tempPath:_,state:"compressed",minifiedAt:h.statSync(t).mtimeMs,readCount:b+1},I(s,a);let $=x>We,T=b>0;if(T||$){let y=He(m,d),S=T?`[cairn: ${f} \u2192 ${x} lines | seen \xD7${b+1} this session]`:`[cairn: ${f} \u2192 outline (${x} minified lines)]`;ue(`[cairn] Expected step, not a failure: ${w.basename(t)} was served as an outline (${f} lines \u2192 ${x} minified). The outline is in context; Read("${_}") for the full minified view.`,`${S}
81
81
  ${y}
82
82
  Full minified view: Read("${_}")`)}else ue(`[cairn] Expected step, not a failure: ${w.basename(t)} was served minified (${f} \u2192 ${x} lines, -${k}%). The content is in context; Read("${_}") for the cached view.`,`[cairn: ${f} \u2192 ${x} lines (-${k}%)]
83
83
  ${m}
84
- Cached view: Read("${_}")`)})}else if(F==="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=w.resolve(t),o=Ee(s);o||process.exit(0);let r=w.join(o,"views"),a=w.relative(r,s);if(!a.startsWith("..")&&!w.isAbsolute(a)){let d=V(o),p=Object.entries(d).find(([,m])=>m.tempPath===s),f=p?`Edit the source file instead: Edit("${p[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";ue(`[cairn] That path is a cached view, not the source \u2014 it is read-only and is overwritten on the next read. ${f}`)}let c=V(o),l=pe(o,c),i=c[s];i||(l>0&&I(o,c),process.exit(0)),n==="Write"&&(delete c[s],I(o,c),process.exit(0)),i.state==="edit-ready"&&(delete c[s],I(o,c),process.exit(0)),i.state="edit-ready",I(o,c);let u=null;try{let d=h.readFileSync(s,"utf8");d.split(`
84
+ Cached view: Read("${_}")`)})}else if(L==="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=w.resolve(t),o=Ee(s);o||process.exit(0);let r=w.join(o,"views"),a=w.relative(r,s);if(!a.startsWith("..")&&!w.isAbsolute(a)){let d=V(o),p=Object.entries(d).find(([,m])=>m.tempPath===s),f=p?`Edit the source file instead: Edit("${p[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";ue(`[cairn] That path is a cached view, not the source \u2014 it is read-only and is overwritten on the next read. ${f}`)}let c=V(o),l=pe(o,c),i=c[s];i||(l>0&&I(o,c),process.exit(0)),n==="Write"&&(delete c[s],I(o,c),process.exit(0)),i.state==="edit-ready"&&(delete c[s],I(o,c),process.exit(0)),i.state="edit-ready",I(o,c);let u=null;try{let d=h.readFileSync(s,"utf8");d.split(`
85
85
  `).length<=300&&(u=d)}catch{}u!==null?ue(`[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):
86
86
 
87
- ${u}`):ue(`[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(F==="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=w.join(o,".cairn");h.existsSync(r)||process.exit(0);let a=w.join(r,"index.db");h.existsSync(a)||process.exit(0);let c;try{({DatabaseSync:c}=await import("node:sqlite"))}catch{process.exit(0)}let l=/exit.?plan.?mode/i.test(n),i=/todo.?write/i.test(n);!l&&!i&&process.exit(0);let u=null,d;try{if(l){let f=s.plan;(typeof f!="string"||!f.trim())&&process.exit(0);let m=Ve(f);if(d=new c(a),oe(d),(d.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let k=ut(d,null);k.id!=null&&lt(d,k.id)}m.added.length>0&&(u=`[cairn] roadmap: captured ${m.added.length} phase(s) from plan${m.skipped.length?` (${m.skipped.length} already present)`:""}.`)}else if(i){d=new c(a);let f=Xe(d,s.todos);f.seeded?u=`[cairn] roadmap: seeded ${f.seeded} phase(s) from todos.`:f.inserted>0&&(u=`[cairn] roadmap: linked ${f.inserted} new sub-task(s) to the current phase.`)}}catch(f){process.stderr.write(`[cairn] roadmap-capture skipped: ${f.message}
88
- `);try{d?.close()}catch{}process.exit(0)}let p=d?ye(d):null;try{d?.close()}catch{}p&&(u=u?`${u} ${p}`:p),u&&process.stdout.write(JSON.stringify({systemMessage:u})),process.exit(0)})}else if(F==="validate-map"){let e=w.join(process.cwd(),".cairn");h.existsSync(e)||process.exit(0);let t=V(e),n=pe(e,t);for(let s of Object.values(t))s.readCount=0;I(e,t),n>0&&process.stdout.write(`[cairn] Cleaned minify map: ${n} stale entr${n===1?"y":"ies"} removed
89
- `),process.exit(0)}else if(F==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],n=[];try{let s=w.join(process.cwd(),".cairn"),o=w.join(s,"session.json");h.existsSync(o)&&(t=JSON.parse(h.readFileSync(o,"utf8")).notes||[]);let r=V(s);n=Object.entries(r).filter(([,a])=>(a.readCount??0)>0).map(([a])=>a)}catch{}ke(null,{message:e,active_files:n,notes:t,auto:!0}),process.exit(0)}else if(F==="session-tick"){let e=process.cwd(),t=w.join(e,".cairn");if(h.existsSync(t)||process.exit(0),!process.stdin.isTTY)try{h.readFileSync(0,"utf8")}catch{}let n=[];try{let c=`Auto-checkpoint at ${new Date().toISOString()}`,l=[],i=w.join(t,"session.json");if(h.existsSync(i))try{l=JSON.parse(h.readFileSync(i,"utf8")).notes||[]}catch{}let u=V(t),d=Object.entries(u).filter(([,p])=>(p.readCount??0)>0).map(([p])=>p);ke(null,{message:c,active_files:d,notes:l,auto:!0}),n.push("\u2713 checkpoint saved")}catch{}try{let c=ss(e);c>0&&n.push(`memory backed up (${c})`)}catch{}let s=[],o=null;try{let c=w.join(t,"index.db");if(h.existsSync(c)){let{DatabaseSync:l}=await import("node:sqlite"),i=new l(c);try{oe(i),o=ye(i);try{s=Ze(i,e).candidates}catch{}}finally{i.close()}}}catch{}let r="";try{let c=await pt(t);if(c.active){n.push(`roadmap ${c.advanced?"updated":"on track"}: ${c.currentLabel}`);let l=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...c.treeLines.map(i=>" "+i)];c.memoLines&&l.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"),l.push("","Keep this current via cairn_roadmap on MATERIAL change (phase done / blocked / new) \u2014 not every turn."),r=l.join(`
87
+ ${u}`):ue(`[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(L==="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=w.join(o,".cairn");h.existsSync(r)||process.exit(0);let a=w.join(r,"index.db");h.existsSync(a)||process.exit(0);let c;try{({DatabaseSync:c}=await import("node:sqlite"))}catch{process.exit(0)}let l=/exit.?plan.?mode/i.test(n),i=/todo.?write/i.test(n);!l&&!i&&process.exit(0);let u=null,d;try{if(l){let f=s.plan;(typeof f!="string"||!f.trim())&&process.exit(0);let m=Ve(f);if(d=new c(a),oe(d),(d.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let k=ut(d,null);k.id!=null&&lt(d,k.id)}m.added.length>0&&(u=`[cairn] roadmap: captured ${m.added.length} phase(s) from plan${m.skipped.length?` (${m.skipped.length} already present)`:""}.`)}else if(i){d=new c(a);let f=Xe(d,s.todos);f.seeded?u=`[cairn] roadmap: seeded ${f.seeded} phase(s) from todos.`:f.inserted>0&&(u=`[cairn] roadmap: linked ${f.inserted} new sub-task(s) to the current phase.`)}}catch(f){process.stderr.write(`[cairn] roadmap-capture skipped: ${f.message}
88
+ `);try{d?.close()}catch{}process.exit(0)}let p=d?ye(d):null;try{d?.close()}catch{}p&&(u=u?`${u} ${p}`:p),u&&process.stdout.write(JSON.stringify({systemMessage:u})),process.exit(0)})}else if(L==="validate-map"){let e=w.join(process.cwd(),".cairn");h.existsSync(e)||process.exit(0);let t=V(e),n=pe(e,t);for(let s of Object.values(t))s.readCount=0;I(e,t),n>0&&process.stdout.write(`[cairn] Cleaned minify map: ${n} stale entr${n===1?"y":"ies"} removed
89
+ `),process.exit(0)}else if(L==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],n=[];try{let s=w.join(process.cwd(),".cairn"),o=w.join(s,"session.json");h.existsSync(o)&&(t=JSON.parse(h.readFileSync(o,"utf8")).notes||[]);let r=V(s);n=Object.entries(r).filter(([,a])=>(a.readCount??0)>0).map(([a])=>a)}catch{}ke(null,{message:e,active_files:n,notes:t,auto:!0}),process.exit(0)}else if(L==="session-tick"){let e=process.cwd(),t=w.join(e,".cairn");if(h.existsSync(t)||process.exit(0),!process.stdin.isTTY)try{h.readFileSync(0,"utf8")}catch{}let n=[];try{let c=`Auto-checkpoint at ${new Date().toISOString()}`,l=[],i=w.join(t,"session.json");if(h.existsSync(i))try{l=JSON.parse(h.readFileSync(i,"utf8")).notes||[]}catch{}let u=V(t),d=Object.entries(u).filter(([,p])=>(p.readCount??0)>0).map(([p])=>p);ke(null,{message:c,active_files:d,notes:l,auto:!0}),n.push("\u2713 checkpoint saved")}catch{}try{let c=ss(e);c>0&&n.push(`memory backed up (${c})`)}catch{}let s=[],o=null;try{let c=w.join(t,"index.db");if(h.existsSync(c)){let{DatabaseSync:l}=await import("node:sqlite"),i=new l(c);try{oe(i),o=ye(i);try{s=Ze(i,e).candidates}catch{}}finally{i.close()}}}catch{}let r="";try{let c=await pt(t);if(c.active){n.push(`roadmap ${c.advanced?"updated":"on track"}: ${c.currentLabel}`);let l=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...c.treeLines.map(i=>" "+i)];c.memoLines&&l.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"),l.push("","Keep this current via cairn_roadmap on MATERIAL change (phase done / blocked / new) \u2014 not every turn."),r=l.join(`
90
90
  `)}}catch{}if(o&&(n.push("ROADMAP.md not updated \u2014 see warning"),r=r?`${r}
91
91
 
92
92
  ${o}`:o),s.length>0){n.push(`${s.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 l of s)c.push(` \u2022 Phase ${l.phase_number}: ${l.text} (commit ${l.commit}, ${l.confidence}) \u2192 if confirmed: cairn_roadmap set_status ${l.phase_id} done`);r=r?r+`
93
93
  `+c.join(`
94
94
  `):c.join(`
95
- `).trimStart()}n.length===0&&process.exit(0);let a={systemMessage:`[cairn] ${n.join(" \xB7 ")}`};r&&(a.additionalContext=r),process.stdout.write(JSON.stringify(a)),process.exit(0)}else if(F==="repair-memory"){let e=process.argv.includes("--dry-run"),t=process.cwd();h.existsSync(w.join(t,".cairn"))||(console.error("[cairn] Not a cairn project (no .cairn/ here). Run this from the project root."),process.exit(1)),Ce(t);let n=ze(xe(),{dryRun:e});if(n.reason&&(console.error(`[cairn] ${n.reason}: ${n.dir}`),process.exit(1)),console.log(`Memory index: ${n.dir}`),console.log(` files on disk : ${n.files_on_disk}`),console.log(` already listed: ${n.already_indexed}`),console.log(` missing : ${n.restored}`),n.restored>0){for(let s of n.restored_entries.slice(0,20))console.log(` + ${s.name}${s.type?" ["+s.type+"]":""} (${s.file})`);n.restored_entries.length>20&&console.log(` \u2026 and ${n.restored_entries.length-20} more`)}if(n.dangling.length>0){console.log(` dangling : ${n.dangling.length} pointer(s) whose file is gone (left alone)`);for(let s of n.dangling.slice(0,10))console.log(` ? ${s.name} (${s.file})`)}n.unreadable.length>0&&console.log(` unreadable : ${n.unreadable.length} file(s) skipped`),e?console.log(n.restored>0?`
95
+ `).trimStart()}n.length===0&&process.exit(0);let a={systemMessage:`[cairn] ${n.join(" \xB7 ")}`};r&&(a.additionalContext=r),process.stdout.write(JSON.stringify(a)),process.exit(0)}else if(L==="repair-memory"){let e=process.argv.includes("--dry-run"),t=process.cwd();h.existsSync(w.join(t,".cairn"))||(console.error("[cairn] Not a cairn project (no .cairn/ here). Run this from the project root."),process.exit(1)),Ce(t);let n=ze(xe(),{dryRun:e});if(n.reason&&(console.error(`[cairn] ${n.reason}: ${n.dir}`),process.exit(1)),console.log(`Memory index: ${n.dir}`),console.log(` files on disk : ${n.files_on_disk}`),console.log(` already listed: ${n.already_indexed}`),console.log(` missing : ${n.restored}`),n.restored>0){for(let s of n.restored_entries.slice(0,20))console.log(` + ${s.name}${s.type?" ["+s.type+"]":""} (${s.file})`);n.restored_entries.length>20&&console.log(` \u2026 and ${n.restored_entries.length-20} more`)}if(n.dangling.length>0){console.log(` dangling : ${n.dangling.length} pointer(s) whose file is gone (left alone)`);for(let s of n.dangling.slice(0,10))console.log(` ? ${s.name} (${s.file})`)}n.unreadable.length>0&&console.log(` unreadable : ${n.unreadable.length} file(s) skipped`),e?console.log(n.restored>0?`
96
96
  Dry run \u2014 nothing written. Re-run without --dry-run to restore them.`:`
97
97
  Dry run \u2014 nothing to restore.`):n.repaired?console.log(`
98
98
  Restored ${n.restored} pointer(s). Previous index kept as ${n.backup}.`):console.log(`
99
- Index already lists every memory file. Nothing to do.`),process.exit(0)}else if(F==="memory-restore"){let e=process.cwd();h.existsSync(w.join(e,".cairn"))||process.exit(0);try{let t=os(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
100
- `)}catch{}process.exit(0)}else if(F==="resume-hint"){let e=w.join(process.cwd(),".cairn"),t=w.join(e,"session.json"),n=w.join(e,"index.db"),s=w.join(e,".hint-lock"),o=1800*1e3,r=process.cwd()===ee.homedir();if(!r&&h.existsSync(e)&&(h.existsSync(n)||h.existsSync(t))){let _=w.join(e,".cairn-project");if(!h.existsSync(_))try{h.writeFileSync(_,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
99
+ Index already lists every memory file. Nothing to do.`),process.exit(0)}else if(L==="memory-restore"){let e=process.cwd();h.existsSync(w.join(e,".cairn"))||process.exit(0);try{let t=os(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
100
+ `)}catch{}process.exit(0)}else if(L==="resume-hint"){let e=w.join(process.cwd(),".cairn"),t=w.join(e,"session.json"),n=w.join(e,"index.db"),s=w.join(e,".hint-lock"),o=1800*1e3,r=process.cwd()===ee.homedir();if(!r&&h.existsSync(e)&&(h.existsSync(n)||h.existsSync(t))){let _=w.join(e,".cairn-project");if(!h.existsSync(_))try{h.writeFileSync(_,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
101
101
  `)}catch{}}if(!r&&h.existsSync(e))try{ns()&&process.stdout.write(`[cairn] Repaired: declared the roadmap slot (${Q}) in ~/.claude.json.
102
102
  `)}catch{}if(!r&&h.existsSync(n))try{let{DatabaseSync:_}=await import("node:sqlite"),b=new _(n,{readonly:!0}),$=null;try{$=ye(b)}finally{b.close()}$&&process.stdout.write(`${$}
103
103
  `)}catch{}{let _=[w.join(process.cwd(),".claude","settings.json"),w.join(ee.homedir(),".claude","settings.json")];for(let b of _)try{if(!h.existsSync(b))continue;let $=h.readFileSync(b,"utf8"),T=JSON.parse($),y=T?.hooks?.PreToolUse;if(!Array.isArray(y))continue;let S=!1;for(let g of y)if(!(g.matcher!=="Edit"||!Array.isArray(g.hooks)))for(let E of g.hooks)E.command==="cairn set-mode edit"&&(E.command="cairn edit-guard",S=!0);S&&(h.writeFileSync(b,JSON.stringify(T,null,2),"utf8"),process.stdout.write(`[cairn] Repaired: migrated cairn set-mode \u2192 cairn edit-guard in ${b}
@@ -107,8 +107,8 @@ ${b.protocol}`)}catch{}h.existsSync(s)&&Date.now()-h.statSync(s).mtimeMs<o&&(u&&
107
107
  `),process.exit(0));let d=JSON.parse(h.readFileSync(t,"utf8")),p=new Date(d.checkpoint_at).toLocaleString(),f=/^Auto-checkpoint\b/.test(d.message||""),m=f&&d.last_manual_message||d.message,x=f&&d.last_manual_checkpoint_at?new Date(d.last_manual_checkpoint_at).toLocaleString():p,k=[`[cairn] Prior session: "${m}" (${x}).`,"[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.",`[cairn] CONTEXT: you cannot see your own token usage, but cairn can and does \u2014 it measures this session's real usage from the transcript on EVERY prompt and reports it in the "[cairn] Context:" line below. It reports; it does NOT decide, because it cannot see your context window and you can. There is no threshold and no automatic warning: whether to stop is your judgement, made against the size of the work you are about to start. If the user asks about context, give them the measured token count and your own read on whether the next task fits \u2014 do NOT say cairn has no such feature, and do NOT quote a percentage as if cairn supplied one.`];if(!i){let _=ce(process.cwd(),d);_.recorded&&k.push(`[cairn] ${be(_)}`)}i&&k.push(["[cairn] RESUME \u2014 checkpoint loaded inline:",...dt(process.cwd(),d)].join(`
108
108
  `)),u&&k.push(u),process.stdout.write(k.join(`
109
109
  `)+`
110
- `),h.writeFileSync(s,new Date().toISOString(),"utf8"),process.exit(0)}else if(F==="session-start"){let e={};if(!process.stdin.isTTY)try{e=JSON.parse(h.readFileSync(0,"utf8"))||{}}catch{}let t=typeof e.cwd=="string"&&e.cwd?e.cwd:process.cwd(),n=w.join(t,".cairn"),s=w.join(n,"session.json");h.existsSync(s)||process.exit(0);let o=typeof e.source=="string"?e.source:"",r;try{r=JSON.parse(h.readFileSync(s,"utf8"))}catch{process.exit(0)}Ie(n);let a=/^Auto-checkpoint\b/.test(r.message||""),c=a&&r.last_manual_message||r.message||"(no message recorded)",l=o==="compact"?"a /compact":o==="clear"?"a /clear":o==="fork"?"a fork":o==="resume"?"a resume":"a restart",i=[`[cairn] Session restored automatically after ${l} \u2014 the user did not have to ask:`,...dt(t,r),"","This IS the prior session state. Do not re-derive it from git log or file reads, do not ask","the user what they were doing, and do not ask whether to continue \u2014 it is above, and the",'client that opened this session already said "resume" on their behalf. If their first',"message continues this work, carry on from would_lose without making them ask twice.","","If would_lose says nothing was recorded, treat that as a gap rather than an all-clear: cairn","cannot see what the previous session never wrote down."].join(`
110
+ `),h.writeFileSync(s,new Date().toISOString(),"utf8"),process.exit(0)}else if(L==="session-start"){let e={};if(!process.stdin.isTTY)try{e=JSON.parse(h.readFileSync(0,"utf8"))||{}}catch{}let t=typeof e.cwd=="string"&&e.cwd?e.cwd:process.cwd(),n=w.join(t,".cairn"),s=w.join(n,"session.json");h.existsSync(s)||process.exit(0);let o=typeof e.source=="string"?e.source:"",r;try{r=JSON.parse(h.readFileSync(s,"utf8"))}catch{process.exit(0)}Ie(n);let a=/^Auto-checkpoint\b/.test(r.message||""),c=a&&r.last_manual_message||r.message||"(no message recorded)",l=o==="compact"?"a /compact":o==="clear"?"a /clear":o==="fork"?"a fork":o==="resume"?"a resume":"a restart",i=[`[cairn] Session restored automatically after ${l} \u2014 the user did not have to ask:`,...dt(t,r),"","This IS the prior session state. Do not re-derive it from git log or file reads, do not ask","the user what they were doing, and do not ask whether to continue \u2014 it is above, and the",'client that opened this session already said "resume" on their behalf. If their first',"message continues this work, carry on from would_lose without making them ask twice.","","If would_lose says nothing was recorded, treat that as a gap rather than an all-clear: cairn","cannot see what the previous session never wrote down."].join(`
111
111
  `),u=(b,$)=>{let T=String(b).replace(/\s+/g," ").trim();return T.length<=$?T:T.slice(0,$-1)+"\u2026"},d=ce(t,r),p=a&&r.last_manual_checkpoint_at||r.checkpoint_at,f=p?new Date(p).toLocaleString():"time unknown",m=[`[cairn] Checkpoint restored after ${l} \u2014 saved ${f}`];m.push(` Working on: ${u(c,140)}`),m.push(d.recorded?` Would be lost: ${u(d.text,200)}`+(d.stale?` (written before the ${d.commits} commit(s) since \u2014 may be stale)`:""):" Would be lost: NOT RECORDED \u2014 nothing says what the last session would lose.");let x=[];r.active_files?.length&&x.push(`${r.active_files.length} active file(s)`),r.notes?.length&&x.push(`${r.notes.length} note(s)`),x.push("full detail"),m.push(` Type "resume" for: ${x.join(", ")}.`);let k=m.join(`
112
- `),_=["[cairn] Session restored from checkpoint.","Lead your next reply with ONE short sentence naming what the previous session left","unfinished, then carry straight on with it. Do NOT ask whether to continue, do NOT ask","what they were working on, and do NOT offer a menu \u2014 the answer is in the context you","were given, so use it rather than re-deriving it. If the user has asked for something,","answer that instead, immediately after the sentence: this must never delay, replace or","override their request."].join(" ");process.stdout.write(JSON.stringify({systemMessage:k,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:i,initialUserMessage:_}})),process.exit(0)}else if(F==="roadmap-hint"){let e=w.join(process.cwd(),".cairn"),t=await pt(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(`
112
+ `),_=["[cairn] Session restored from checkpoint.","Lead your next reply with ONE short sentence naming what the previous session left","unfinished, then carry straight on with it. Do NOT ask whether to continue, do NOT ask","what they were working on, and do NOT offer a menu \u2014 the answer is in the context you","were given, so use it rather than re-deriving it. If the user has asked for something,","answer that instead, immediately after the sentence: this must never delay, replace or","override their request."].join(" ");process.stdout.write(JSON.stringify({systemMessage:k,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:i,initialUserMessage:_}})),process.exit(0)}else if(L==="roadmap-hint"){let e=w.join(process.cwd(),".cairn"),t=await pt(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(`
113
113
  `)+`
114
- `),process.exit(0)}else if(F==="install"){let e=process.platform,t=w.join(ee.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:mt},h.writeFileSync(t,JSON.stringify(n,null,2),"utf8");let o=w.join(ee.homedir(),".claude");ft(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 ${w.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 -> cairn session-start (re-inject checkpoint on restart / clear / compact / fork)"),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(F==="install-hooks"){let e=process.argv.includes("--global"),t=e?w.join(ee.homedir(),".claude"):w.join(process.cwd(),".claude");ft(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 -> cairn session-start (re-inject checkpoint on restart / clear / compact / fork)"),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 | repair-memory [--dry-run]>"),process.exit(1);function ft(e,t){let n=w.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(f=>f.matcher==="Read"&&f.hooks?.some(m=>m.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let r=o.filter(f=>!(f.matcher==="Edit"&&f.hooks?.some(m=>m.command==="cairn set-mode edit")));r.some(f=>f.matcher==="Edit"&&f.hooks?.some(m=>m.command==="cairn edit-guard"))||r.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),r.some(f=>f.matcher==="Write"&&f.hooks?.some(m=>m.command==="cairn edit-guard"))||r.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),s.hooks.PreToolUse=r;let a=(f,m)=>(f||[]).map(x=>({...x,hooks:(x.hooks||[]).filter(k=>!m(k.command))})).filter(x=>(x.hooks||[]).length>0),c=f=>typeof f=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(f),l=a(s.hooks.Stop,f=>f==="cairn checkpoint --auto"||f==="cairn roadmap-hint");l.some(f=>f.hooks?.some(m=>m.command==="cairn session-tick"))||l.push({hooks:[{type:"command",command:"cairn session-tick"}]}),s.hooks.Stop=l;let i=a(s.hooks.UserPromptSubmit,c);i.some(f=>f.hooks?.some(m=>m.command==="cairn resume-hint"))||i.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),i.some(f=>f.hooks?.some(m=>m.command==="cairn memory-restore"))||i.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),s.hooks.UserPromptSubmit=i;let u=a(s.hooks.SessionStart,f=>f==="cairn session-start");u.push({hooks:[{type:"command",command:"cairn session-start"}]}),s.hooks.SessionStart=u;let d=a(s.hooks.PostToolUse,c),p=f=>{d.some(m=>m.matcher===f&&m.hooks?.some(x=>x.command==="cairn roadmap-capture"))||d.push({matcher:f,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(p("ExitPlanMode"),p("TodoWrite"),s.hooks.PostToolUse=d,t){let f=w.join(e,"scripts");for(let m of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let x=w.join(f,m);if(h.existsSync(x))try{h.unlinkSync(x)}catch{}}}if(!t){let f=w.join(process.cwd(),".cairn");h.mkdirSync(f,{recursive:!0});let m=w.join(f,".cairn-project");h.existsSync(m)||h.writeFileSync(m,"","utf8")}h.mkdirSync(e,{recursive:!0}),h.writeFileSync(n,JSON.stringify(s,null,2),"utf8")}
114
+ `),process.exit(0)}else if(L==="install"){let e=process.platform,t=w.join(ee.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:mt},h.writeFileSync(t,JSON.stringify(n,null,2),"utf8");let o=w.join(ee.homedir(),".claude");ft(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 ${w.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 -> cairn session-start (re-inject checkpoint on restart / clear / compact / fork)"),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(L==="install-hooks"){let e=process.argv.includes("--global"),t=e?w.join(ee.homedir(),".claude"):w.join(process.cwd(),".claude");ft(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 -> cairn session-start (re-inject checkpoint on restart / clear / compact / fork)"),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 | repair-memory [--dry-run]>"),process.exit(1);function ft(e,t){let n=w.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(f=>f.matcher==="Read"&&f.hooks?.some(m=>m.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let r=o.filter(f=>!(f.matcher==="Edit"&&f.hooks?.some(m=>m.command==="cairn set-mode edit")));r.some(f=>f.matcher==="Edit"&&f.hooks?.some(m=>m.command==="cairn edit-guard"))||r.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),r.some(f=>f.matcher==="Write"&&f.hooks?.some(m=>m.command==="cairn edit-guard"))||r.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),s.hooks.PreToolUse=r;let a=(f,m)=>(f||[]).map(x=>({...x,hooks:(x.hooks||[]).filter(k=>!m(k.command))})).filter(x=>(x.hooks||[]).length>0),c=f=>typeof f=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(f),l=a(s.hooks.Stop,f=>f==="cairn checkpoint --auto"||f==="cairn roadmap-hint");l.some(f=>f.hooks?.some(m=>m.command==="cairn session-tick"))||l.push({hooks:[{type:"command",command:"cairn session-tick"}]}),s.hooks.Stop=l;let i=a(s.hooks.UserPromptSubmit,c);i.some(f=>f.hooks?.some(m=>m.command==="cairn resume-hint"))||i.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),i.some(f=>f.hooks?.some(m=>m.command==="cairn memory-restore"))||i.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),s.hooks.UserPromptSubmit=i;let u=a(s.hooks.SessionStart,f=>f==="cairn session-start");u.push({hooks:[{type:"command",command:"cairn session-start"}]}),s.hooks.SessionStart=u;let d=a(s.hooks.PostToolUse,c),p=f=>{d.some(m=>m.matcher===f&&m.hooks?.some(x=>x.command==="cairn roadmap-capture"))||d.push({matcher:f,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(p("ExitPlanMode"),p("TodoWrite"),s.hooks.PostToolUse=d,t){let f=w.join(e,"scripts");for(let m of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let x=w.join(f,m);if(h.existsSync(x))try{h.unlinkSync(x)}catch{}}}if(!t){let f=w.join(process.cwd(),".cairn");h.mkdirSync(f,{recursive:!0});let m=w.join(f,".cairn-project");h.existsSync(m)||h.writeFileSync(m,"","utf8")}h.mkdirSync(e,{recursive:!0}),h.writeFileSync(n,JSON.stringify(s,null,2),"utf8")}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Si}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as xi,ListToolsRequestSchema as bi}from"@modelcontextprotocol/sdk/types.js";import{DatabaseSync as Ae}from"node:sqlite";import _e from"fs";import C from"path";import P from"fs";import Cn from"os";import q from"path";var Le=null;function $(){return Le||process.cwd()}function $e(t){let e=Le;return Le=t,e}function F(){let t=q.join($(),".cairn");P.mkdirSync(q.join(t,"bundles"),{recursive:!0});let e=q.join(t,".cairn-project");return P.existsSync(e)||P.writeFileSync(e,"","utf8"),t}function Fn(t){return t.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Ie(){let t=q.join(Cn.homedir(),".claude","projects",Fn($()));return P.existsSync(t)?q.join(t,"memory"):null}function ot(t){let e=q.join(t,"MEMORY.md");P.existsSync(e)||P.writeFileSync(e,`# Memory Index
2
- `,"utf8")}function ne(){let t=q.join(F(),"memory");P.mkdirSync(t,{recursive:!0}),ot(t);let e=Ie();if(!e)return t;P.mkdirSync(e,{recursive:!0}),ot(e);let n=s=>P.readdirSync(s).some(i=>i.endsWith(".md")&&i!=="MEMORY.md");if(!n(e)&&n(t))for(let s of P.readdirSync(t)){let i=q.join(t,s);try{if(!P.statSync(i).isFile())continue;let r=P.statSync(i);P.copyFileSync(i,q.join(e,s));try{P.utimesSync(q.join(e,s),r.atime,r.mtime)}catch{}}catch{}}return e}var jn=`
1
+ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Si}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as xi,ListToolsRequestSchema as bi}from"@modelcontextprotocol/sdk/types.js";import{DatabaseSync as Ae}from"node:sqlite";import _e from"fs";import C from"path";import P from"fs";import Cn from"os";import q from"path";var Le=null;function $(){return Le||process.cwd()}function $e(t){let e=Le;return Le=t,e}function j(){let t=q.join($(),".cairn");P.mkdirSync(q.join(t,"bundles"),{recursive:!0});let e=q.join(t,".cairn-project");return P.existsSync(e)||P.writeFileSync(e,"","utf8"),t}function jn(t){return t.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Ie(){let t=q.join(Cn.homedir(),".claude","projects",jn($()));return P.existsSync(t)?q.join(t,"memory"):null}function ot(t){let e=q.join(t,"MEMORY.md");P.existsSync(e)||P.writeFileSync(e,`# Memory Index
2
+ `,"utf8")}function ne(){let t=q.join(j(),"memory");P.mkdirSync(t,{recursive:!0}),ot(t);let e=Ie();if(!e)return t;P.mkdirSync(e,{recursive:!0}),ot(e);let n=s=>P.readdirSync(s).some(i=>i.endsWith(".md")&&i!=="MEMORY.md");if(!n(e)&&n(t))for(let s of P.readdirSync(t)){let i=q.join(t,s);try{if(!P.statSync(i).isFile())continue;let r=P.statSync(i);P.copyFileSync(i,q.join(e,s));try{P.utimesSync(q.join(e,s),r.atime,r.mtime)}catch{}}catch{}}return e}var Fn=`
3
3
  CREATE TABLE IF NOT EXISTS files (
4
4
  id INTEGER PRIMARY KEY,
5
5
  repo TEXT NOT NULL,
@@ -98,7 +98,7 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
98
98
  );
99
99
  CREATE INDEX IF NOT EXISTS idx_roadmap_phase_deps_blocked
100
100
  ON roadmap_phase_deps(blocked_id);
101
- `,Dn=1e6;function Pn(t){let e=C.dirname(C.resolve(t));for(;;){let n=C.join(e,".cairn","index.db");if(_e.existsSync(n))return n;let s=C.dirname(e);if(s===e)return null;e=s}}function Un(t){let e=C.resolve($()),n=C.resolve(C.join(F(),"index.db")),s=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)"),i=Pn(e);if(i){try{C.resolve(i)!==n&&s.run(C.resolve(i));let r=new Ae(i,{readonly:!0}),o=r.prepare("SELECT path FROM sub_indexes").all();r.close();for(let{path:a}of o){let c=C.resolve(a);c!==n&&s.run(c)}}catch{}return}try{let r=C.dirname(e),o=_e.readdirSync(r,{withFileTypes:!0});for(let a of o){if(!a.isDirectory())continue;let c=C.resolve(C.join(r,a.name,".cairn","index.db"));c!==n&&_e.existsSync(c)&&s.run(c)}}catch{}}var Hn=["files","symbols","dependencies","security_findings"];function at(t,e,n){let s=`"${t}"`;switch(e){case"files":return`SELECT id+${n} AS id, repo, path, language, file_type, last_indexed FROM ${s}.files`;case"symbols":return`SELECT id+${n} AS id, file_id+${n} AS file_id, fqn, name, kind, language, exported, description, line FROM ${s}.symbols`;case"dependencies":return`SELECT from_file_id+${n} AS from_file_id, to_fqn, dep_type FROM ${s}.dependencies`;case"build_deps":return`SELECT repo, manager, group_id, artifact, version, scope FROM ${s}.build_deps`;case"security_findings":return`SELECT id+${n} AS id, file_id+${n} AS file_id, line, severity, cwe, rule_name, description, code_snippet FROM ${s}.security_findings`;default:return`SELECT * FROM ${s}.${e}`}}function Wn(t){if(!_e.existsSync(t))return{valid:!1,reason:"file not found"};try{let e=new Ae(t,{readonly:!0}),n=e.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(i=>i.name);e.close();let s=Hn.filter(i=>!n.includes(i));return s.length>0?{valid:!1,reason:`missing tables: ${s.join(", ")}`}:{valid:!0}}catch(e){return{valid:!1,reason:e.message}}}function ct(){let t=C.join(F(),"index.db"),e=new Ae(t);e.exec("PRAGMA journal_mode = WAL"),e.exec("PRAGMA synchronous = NORMAL"),e.exec(jn);try{e.exec("ALTER TABLE main.symbols ADD COLUMN line INTEGER")}catch{}let n=!1;try{e.exec("ALTER TABLE main.todos ADD COLUMN parent_id INTEGER REFERENCES todos(id) ON DELETE SET NULL"),n=!0}catch{}try{e.exec("ALTER TABLE main.todos ADD COLUMN position INTEGER NOT NULL DEFAULT 0"),n=!0}catch{}n&&e.exec(`
101
+ `,Dn=1e6;function Pn(t){let e=C.dirname(C.resolve(t));for(;;){let n=C.join(e,".cairn","index.db");if(_e.existsSync(n))return n;let s=C.dirname(e);if(s===e)return null;e=s}}function Un(t){let e=C.resolve($()),n=C.resolve(C.join(j(),"index.db")),s=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)"),i=Pn(e);if(i){try{C.resolve(i)!==n&&s.run(C.resolve(i));let r=new Ae(i,{readonly:!0}),o=r.prepare("SELECT path FROM sub_indexes").all();r.close();for(let{path:a}of o){let c=C.resolve(a);c!==n&&s.run(c)}}catch{}return}try{let r=C.dirname(e),o=_e.readdirSync(r,{withFileTypes:!0});for(let a of o){if(!a.isDirectory())continue;let c=C.resolve(C.join(r,a.name,".cairn","index.db"));c!==n&&_e.existsSync(c)&&s.run(c)}}catch{}}var Hn=["files","symbols","dependencies","security_findings"];function at(t,e,n){let s=`"${t}"`;switch(e){case"files":return`SELECT id+${n} AS id, repo, path, language, file_type, last_indexed FROM ${s}.files`;case"symbols":return`SELECT id+${n} AS id, file_id+${n} AS file_id, fqn, name, kind, language, exported, description, line FROM ${s}.symbols`;case"dependencies":return`SELECT from_file_id+${n} AS from_file_id, to_fqn, dep_type FROM ${s}.dependencies`;case"build_deps":return`SELECT repo, manager, group_id, artifact, version, scope FROM ${s}.build_deps`;case"security_findings":return`SELECT id+${n} AS id, file_id+${n} AS file_id, line, severity, cwe, rule_name, description, code_snippet FROM ${s}.security_findings`;default:return`SELECT * FROM ${s}.${e}`}}function Wn(t){if(!_e.existsSync(t))return{valid:!1,reason:"file not found"};try{let e=new Ae(t,{readonly:!0}),n=e.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(i=>i.name);e.close();let s=Hn.filter(i=>!n.includes(i));return s.length>0?{valid:!1,reason:`missing tables: ${s.join(", ")}`}:{valid:!0}}catch(e){return{valid:!1,reason:e.message}}}function ct(){let t=C.join(j(),"index.db"),e=new Ae(t);e.exec("PRAGMA journal_mode = WAL"),e.exec("PRAGMA synchronous = NORMAL"),e.exec(Fn);try{e.exec("ALTER TABLE main.symbols ADD COLUMN line INTEGER")}catch{}let n=!1;try{e.exec("ALTER TABLE main.todos ADD COLUMN parent_id INTEGER REFERENCES todos(id) ON DELETE SET NULL"),n=!0}catch{}try{e.exec("ALTER TABLE main.todos ADD COLUMN position INTEGER NOT NULL DEFAULT 0"),n=!0}catch{}n&&e.exec(`
102
102
  UPDATE main.todos AS t
103
103
  SET position = (
104
104
  SELECT rn - 1 FROM (
@@ -110,8 +110,8 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
110
110
  `);try{e.exec("ALTER TABLE main.roadmap_cursor ADD COLUMN phase_id INTEGER REFERENCES roadmap_phases(id) ON DELETE SET NULL")}catch{}try{if(!e.prepare("SELECT 1 FROM main.roadmap_phases WHERE decision_name = 'legacy_v1_9' LIMIT 1").get()){let i=e.prepare("SELECT id, text, status, position FROM main.todos WHERE parent_id IS NULL AND id IN (SELECT DISTINCT parent_id FROM main.todos WHERE parent_id IS NOT NULL) ORDER BY position, id").all();if(i.length>0){let r=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (?, ?, ?, ?, ?, ?)"),o=new Map,a=1;for(let p of i){let u=r.run("legacy_v1_9",a++,null,p.text,p.status,p.position);o.set(p.id,u.lastInsertRowid)}let c=e.prepare("SELECT id, parent_id, text, status, position FROM main.todos WHERE parent_id IS NOT NULL ORDER BY position, id").all();for(let p of c){let u=o.get(p.parent_id);u!=null&&r.run(null,null,u,p.text,p.status,p.position)}let l=e.prepare("SELECT todo_id FROM main.roadmap_cursor WHERE singleton = 1").get();l?.todo_id!=null&&o.has(l.todo_id)&&e.prepare("UPDATE main.roadmap_cursor SET phase_id = ? WHERE singleton = 1").run(o.get(l.todo_id))}}}catch(s){process.stderr.write(`[cairn] v1.9.0\u2192v1.10.0 roadmap migration skipped: ${s.message}
111
111
  `)}return Un(e),Me(e),e}function lt(t,e){let n=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)");for(let s of e)n.run(C.resolve(s))}function Ee(t){return t.prepare("PRAGMA database_list").all().filter(e=>e.name!=="temp"&&e.name!=="main").map(e=>e.name)}function Me(t){let e=t.prepare("PRAGMA database_list").all(),n=new Map(e.filter(p=>p.name!=="temp"&&p.name!=="main"&&p.file).map(p=>[C.resolve(p.file),p.name])),s=[...n.values()],i={attached:[],removed:[],skipped:[]},r=t.prepare("SELECT path FROM main.sub_indexes").all(),o=t.prepare("DELETE FROM main.sub_indexes WHERE path = ?"),a=s.length,c=[...s];for(let{path:p}of r){let u=C.resolve(p);if(n.has(u)){c.includes(n.get(u))||c.push(n.get(u));continue}let d=Wn(u);if(!d.valid){o.run(p),i.removed.push(`${p} (${d.reason})`);continue}let m=`sub${a++}`;try{t.exec(`ATTACH DATABASE '${u.replace(/\\/g,"/").replace(/'/g,"''")}' AS "${m}"`),c.push(m),n.set(u,m),i.attached.push(p)}catch(y){i.skipped.push(`${p} (${y.message})`)}}if(c.length===0)return i;let l=["files","symbols","dependencies","build_deps","security_findings"];for(let p of l){t.exec(`DROP VIEW IF EXISTS temp.${p}`);let u=[at("main",p,0),...c.map((d,m)=>at(d,p,(m+1)*Dn))];t.exec(`CREATE TEMP VIEW ${p} AS ${u.join(" UNION ALL ")}`)}return i}import De from"fs/promises";import ue from"path";import pt from"fast-glob";import Ce from"path";var Q={".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"},dt=["pom.xml","package.json","build.gradle"],qn=["**/node_modules/**","**/.git/**","**/dist/**","**/build/**","**/target/**","**/.next/**","**/__pycache__/**","**/*.min.js","**/.cairn/**"];async function ut(t){return(await pt(["**/.cairn/index.db"],{cwd:t,absolute:!0,dot:!0,ignore:["**/node_modules/**"]})).map(n=>Ce.dirname(Ce.dirname(n)))}async function B(t,e=[]){let n=Object.keys(Q).map(r=>`**/*${r}`),s=e.map(r=>`${Ce.relative(t,r).replace(/\\/g,"/")}/**`);return await pt(n,{cwd:t,ignore:[...qn,...s],absolute:!0})}import Gn from"path";function ft(t,e){return t.substring(0,e).split(`
112
112
  `).length}function mt(t,e,n){let s=e.match(/^package\s+([\w.]+);/m)?.[1]||"",i=[...e.matchAll(/^import\s+([\w.]+);/gm)].map(g=>g[1]),o=/(?:public|protected)?\s+(?:abstract\s+)?(?:class|interface|enum|record)\s+(\w+)/.exec(e),a=o?.[1]||"",c=o?.[0]?.match(/class|interface|enum|record/)?.[0]||"class",l=s?`${s}.${a}`:a,p=o?ft(e,o.index):null,u=[...e.matchAll(/(?:public|protected|private)[^{;]*\s+(\w+)\s*\([^)]*\)\s*(?:throws[^{]*)?\{/gm)],d=e.match(/extends\s+([\w.]+)/)?.[1],m=e.match(/implements\s+([\w.,\s]+)/)?.[1]?.split(",").map(g=>g.trim()),y=e.match(/\/\*\*([\s\S]*?)\*\//)?.[1]?.replace(/\s*\*\s?/g," ").trim(),f=l?u.map(g=>({name:g[1],fqn:`${l}.${g[1]}`,kind:"method",exported:!1,description:"",line:ft(e,g.index)})):[];return{language:"java",symbols:[{name:a,fqn:l,kind:c,exported:!0,description:y||"",line:p},...f],imports:i,extends:d?[d]:[],implements:m||[]}}function pe(t,e){return t.substring(0,e).split(`
113
- `).length}function de(t,e,n,s="typescript"){let i=[],r=[];for(let o of e.matchAll(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g))r.push(...o[1].split(",").map(a=>a.trim().split(" as ")[0]).filter(Boolean));for(let o of e.matchAll(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g))r.push(o[1]);for(let o of e.matchAll(/(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g)){let a=o[0].startsWith("export"),c=ht(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"class",exported:a,description:c,line:pe(e,o.index)})}for(let o of e.matchAll(/export\s+(?:interface|type)\s+(\w+)/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"interface",exported:!0,description:"",line:pe(e,o.index)});for(let o of e.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)){let a=ht(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:a,line:pe(e,o.index)})}for(let o of e.matchAll(/export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:"",line:pe(e,o.index)});for(let o of e.matchAll(/export\s+(?:const\s+)?enum\s+(\w+)/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"enum",exported:!0,description:"",line:pe(e,o.index)});return{language:s,symbols:i,imports:r}}function ht(t,e){let s=t.substring(0,e).match(/\/\*\*([\s\S]*?)\*\/\s*$/);return s?s[1].replace(/\s*\*\s?/g," ").trim():""}function gt(t,e,n){let s=t.split("/").pop().replace(".vue",""),r=e.match(/<script(?:\s+setup)?(?:\s+lang=["']ts["'])?>([\s\S]*?)<\/script>/i)?.[1]||"",o=de(t,r,n,"vue"),c=[...e.match(/<template>([\s\S]*?)<\/template>/i)?.[1]?.matchAll(/<([A-Z][A-Za-z]+)/g)||[]].map(l=>l[1]);return o.symbols.unshift({name:s,fqn:`${t}::${s}`,kind:"component",exported:!0,description:`Vue component: ${s}`}),o.childComponents=[...new Set(c)],o}function Fe(t,e){return t.substring(0,e).split(`
114
- `).length}function yt(t,e,n){let s=[],i=[];for(let r of e.matchAll(/^from\s+([\w.]+)\s+import\s+([\w,\s*]+)/gm))i.push(...r[2].split(",").map(o=>o.trim()).filter(Boolean));for(let r of e.matchAll(/^import\s+([\w.,\s]+)/gm))i.push(...r[1].split(",").map(o=>o.trim()).filter(Boolean));for(let r of e.matchAll(/^class\s+(\w+)(?:\(([^)]*)\))?:/gm)){let o=e.slice(r.index).match(/:\s*\n\s+"""([\s\S]*?)"""/)?.[1]?.trim();s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"class",exported:!0,description:o||"",line:Fe(e,r.index)})}for(let r of e.matchAll(/^(?:async\s+)?def\s+(\w+)\s*\(/gm)){if(r[1].startsWith("_"))continue;let o=e.slice(r.index).match(/\).*?:\s*\n\s+"""([\s\S]*?)"""/)?.[1]?.trim();s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"function",exported:!0,description:o||"",line:Fe(e,r.index)})}for(let r of e.matchAll(/^@([\w.]+)(?:\(([^)]*)\))?/gm))s.push({name:r[1],fqn:`${t}::@${r[1]}`,kind:"decorator",exported:!1,description:"",line:Fe(e,r.index)});return{language:"python",symbols:s,imports:i}}function _t(t,e,n){let s=[];for(let i of e.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"\w.]+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"table",exported:!0,description:""});for(let i of e.matchAll(/CREATE\s+(?:OR\s+REPLACE\s+)?(?:PROCEDURE|FUNCTION)\s+(\w+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"function",exported:!0,description:""});for(let i of e.matchAll(/CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(\w+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"view",exported:!0,description:""});return{language:"sql",symbols:s,imports:[]}}function Et(t,e,n){let s=[];for(let i of e.matchAll(/^([\w.-]+)\s*[:=]\s*(.+)/gm)){let r=i[1].trim();r.startsWith("#")||s.push({name:r,fqn:`${t}::${r}`,kind:"config-key",exported:!1,description:i[2].trim().substring(0,80)})}return{language:"config",symbols:s,imports:[]}}var Bn=new Set(["div","span","p","a","ul","ol","li","table","tr","td","th","thead","tbody","form","input","button","select","option","textarea","label","img","br","hr","h1","h2","h3","h4","h5","h6","head","body","html","script","style","link","meta","nav","header","footer","main","section","article","aside","figure"]);function je(t,e,n){let s=[];for(let r of e.matchAll(/\bid\s*=\s*["']([^"']+)["']/g))s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"bean",exported:!0,description:""});let i=new Set;for(let r of e.matchAll(/<([a-z][a-z0-9-]*)(?:\s|\/?>)/gi)){let o=r[1].toLowerCase();Bn.has(o)||i.has(o)||(i.add(o),s.push({name:o,fqn:`${t}::${o}`,kind:"component",exported:!0,description:""}))}return{language:"xml",symbols:s,imports:[]}}function St(t,e,n){let s=[];for(let i of e.matchAll(/^(#{1,3})\s+(.+)/gm)){let r=i[1].length,o=i[2].trim();s.push({name:o,fqn:`${t}::${o.replace(/\s+/g,"-").toLowerCase()}`,kind:r===1?"doc-title":"doc-section",exported:!0,description:""})}return{language:"markdown",symbols:s,imports:[]}}var Jn={java:mt,typescript:de,javascript:de,vue:gt,python:yt,sql:_t,config:Et,xml:je,html:je,markdown:St};function Xn(t){let e=Gn.extname(t).toLowerCase(),n=Q[e];return{language:n,parser:Jn[n]||null}}async function xt(t,e,n){let{language:s,parser:i}=Xn(t);return i?i(t,e,n,s):null}var Yn=[{id:"CWE-611",lang:["java"],severity:"HIGH",name:"XXE (XML External Entity)",pattern:/builder\.parse\(/,negPattern:/setFeature.*FEATURE_SECURE_PROCESSING/,fix:"Add dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) before parse()"},{id:"CWE-89",lang:["java"],severity:"HIGH",name:"SQL Injection",pattern:/"(SELECT|INSERT|UPDATE|DELETE)\b.*"\s*\+/i,fix:"Use PreparedStatement with parameterized queries"},{id:"CWE-326",lang:["java"],severity:"HIGH",name:"Weak Cryptography",pattern:/getInstance\("(MD5|SHA1|DES|RC4)"\)/,fix:"Use SHA-256 or AES-256"},{id:"CWE-502",lang:["java"],severity:"HIGH",name:"Unsafe Deserialization",pattern:/new ObjectInputStream|\.readObject\(\)/,fix:"Validate input before deserialization; use safe deserialization libraries"},{id:"CWE-078",lang:["java"],severity:"HIGH",name:"Command Injection",pattern:/Runtime\.getRuntime\(\)\.exec\(/,fix:"Use ProcessBuilder with argument list instead of string concatenation"},{id:"CWE-79",lang:["javascript","typescript","vue"],severity:"HIGH",name:"XSS via innerHTML / dangerouslySetInnerHTML",pattern:/\.innerHTML\s*=|dangerouslySetInnerHTML/,fix:"Use textContent or sanitize with DOMPurify"},{id:"CWE-89",lang:["javascript","typescript"],severity:"HIGH",name:"SQL Injection (JS)",pattern:/`\s*(SELECT|INSERT|UPDATE|DELETE)\b.*\$\{/i,fix:"Use parameterized queries or an ORM"},{id:"CWE-798",lang:["javascript","typescript","vue"],severity:"HIGH",name:"Hardcoded Secret",pattern:/(api_key|apikey|secret|password|token)\s*[:=]\s*['"][^'"]{8,}['"]/i,fix:"Move secrets to environment variables or a secrets manager"},{id:"CWE-327",lang:["javascript","typescript"],severity:"MEDIUM",name:"Weak Crypto (Node)",pattern:/createHash\(['"]md5['"]\)|createHash\(['"]sha1['"]\)/,fix:"Use SHA-256 or stronger"},{id:"CWE-601",lang:["javascript","typescript","vue"],severity:"MEDIUM",name:"Open Redirect",pattern:/res\.redirect\([^)]*req\.(query|params|body)/,fix:"Validate redirect URL against an allowlist"},{id:"CWE-089",lang:["python"],severity:"HIGH",name:"SQL Injection (Python)",pattern:/execute\([f"'].*%(s|d)|execute\(.*format\(/,fix:"Use parameterized queries: cursor.execute(sql, params)"},{id:"CWE-078",lang:["python"],severity:"HIGH",name:"Command Injection (Python)",pattern:/os\.system\(|subprocess\.call\(.*shell=True/,fix:"Use subprocess with a list of args and shell=False"},{id:"CWE-798",lang:["python"],severity:"HIGH",name:"Hardcoded Secret (Python)",pattern:/(api_key|secret|password|token)\s*=\s*['"][^'"]{8,}['"]/i,fix:"Use environment variables or a secrets manager like Vault"},{id:"CWE-312",lang:null,severity:"MEDIUM",name:"Cleartext Sensitive Data in Comments",pattern:/\/\/.*?(password|secret|token)\s*[:=]\s*\S+/i,fix:"Remove sensitive data from comments"}];function bt(t,e,n){let s=[],i=e.split(`
113
+ `).length}function de(t,e,n,s="typescript"){let i=[],r=[];for(let o of e.matchAll(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g))r.push(...o[1].split(",").map(a=>a.trim().split(" as ")[0]).filter(Boolean));for(let o of e.matchAll(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g))r.push(o[1]);for(let o of e.matchAll(/(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g)){let a=o[0].startsWith("export"),c=ht(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"class",exported:a,description:c,line:pe(e,o.index)})}for(let o of e.matchAll(/export\s+(?:interface|type)\s+(\w+)/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"interface",exported:!0,description:"",line:pe(e,o.index)});for(let o of e.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)){let a=ht(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:a,line:pe(e,o.index)})}for(let o of e.matchAll(/export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:"",line:pe(e,o.index)});for(let o of e.matchAll(/export\s+(?:const\s+)?enum\s+(\w+)/g))i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"enum",exported:!0,description:"",line:pe(e,o.index)});return{language:s,symbols:i,imports:r}}function ht(t,e){let s=t.substring(0,e).match(/\/\*\*([\s\S]*?)\*\/\s*$/);return s?s[1].replace(/\s*\*\s?/g," ").trim():""}function gt(t,e,n){let s=t.split("/").pop().replace(".vue",""),r=e.match(/<script(?:\s+setup)?(?:\s+lang=["']ts["'])?>([\s\S]*?)<\/script>/i)?.[1]||"",o=de(t,r,n,"vue"),c=[...e.match(/<template>([\s\S]*?)<\/template>/i)?.[1]?.matchAll(/<([A-Z][A-Za-z]+)/g)||[]].map(l=>l[1]);return o.symbols.unshift({name:s,fqn:`${t}::${s}`,kind:"component",exported:!0,description:`Vue component: ${s}`}),o.childComponents=[...new Set(c)],o}function je(t,e){return t.substring(0,e).split(`
114
+ `).length}function yt(t,e,n){let s=[],i=[];for(let r of e.matchAll(/^from\s+([\w.]+)\s+import\s+([\w,\s*]+)/gm))i.push(...r[2].split(",").map(o=>o.trim()).filter(Boolean));for(let r of e.matchAll(/^import\s+([\w.,\s]+)/gm))i.push(...r[1].split(",").map(o=>o.trim()).filter(Boolean));for(let r of e.matchAll(/^class\s+(\w+)(?:\(([^)]*)\))?:/gm)){let o=e.slice(r.index).match(/:\s*\n\s+"""([\s\S]*?)"""/)?.[1]?.trim();s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"class",exported:!0,description:o||"",line:je(e,r.index)})}for(let r of e.matchAll(/^(?:async\s+)?def\s+(\w+)\s*\(/gm)){if(r[1].startsWith("_"))continue;let o=e.slice(r.index).match(/\).*?:\s*\n\s+"""([\s\S]*?)"""/)?.[1]?.trim();s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"function",exported:!0,description:o||"",line:je(e,r.index)})}for(let r of e.matchAll(/^@([\w.]+)(?:\(([^)]*)\))?/gm))s.push({name:r[1],fqn:`${t}::@${r[1]}`,kind:"decorator",exported:!1,description:"",line:je(e,r.index)});return{language:"python",symbols:s,imports:i}}function _t(t,e,n){let s=[];for(let i of e.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"\w.]+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"table",exported:!0,description:""});for(let i of e.matchAll(/CREATE\s+(?:OR\s+REPLACE\s+)?(?:PROCEDURE|FUNCTION)\s+(\w+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"function",exported:!0,description:""});for(let i of e.matchAll(/CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(\w+)/gi))s.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"view",exported:!0,description:""});return{language:"sql",symbols:s,imports:[]}}function Et(t,e,n){let s=[];for(let i of e.matchAll(/^([\w.-]+)\s*[:=]\s*(.+)/gm)){let r=i[1].trim();r.startsWith("#")||s.push({name:r,fqn:`${t}::${r}`,kind:"config-key",exported:!1,description:i[2].trim().substring(0,80)})}return{language:"config",symbols:s,imports:[]}}var Bn=new Set(["div","span","p","a","ul","ol","li","table","tr","td","th","thead","tbody","form","input","button","select","option","textarea","label","img","br","hr","h1","h2","h3","h4","h5","h6","head","body","html","script","style","link","meta","nav","header","footer","main","section","article","aside","figure"]);function Fe(t,e,n){let s=[];for(let r of e.matchAll(/\bid\s*=\s*["']([^"']+)["']/g))s.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"bean",exported:!0,description:""});let i=new Set;for(let r of e.matchAll(/<([a-z][a-z0-9-]*)(?:\s|\/?>)/gi)){let o=r[1].toLowerCase();Bn.has(o)||i.has(o)||(i.add(o),s.push({name:o,fqn:`${t}::${o}`,kind:"component",exported:!0,description:""}))}return{language:"xml",symbols:s,imports:[]}}function St(t,e,n){let s=[];for(let i of e.matchAll(/^(#{1,3})\s+(.+)/gm)){let r=i[1].length,o=i[2].trim();s.push({name:o,fqn:`${t}::${o.replace(/\s+/g,"-").toLowerCase()}`,kind:r===1?"doc-title":"doc-section",exported:!0,description:""})}return{language:"markdown",symbols:s,imports:[]}}var Jn={java:mt,typescript:de,javascript:de,vue:gt,python:yt,sql:_t,config:Et,xml:Fe,html:Fe,markdown:St};function Xn(t){let e=Gn.extname(t).toLowerCase(),n=Q[e];return{language:n,parser:Jn[n]||null}}async function xt(t,e,n){let{language:s,parser:i}=Xn(t);return i?i(t,e,n,s):null}var Yn=[{id:"CWE-611",lang:["java"],severity:"HIGH",name:"XXE (XML External Entity)",pattern:/builder\.parse\(/,negPattern:/setFeature.*FEATURE_SECURE_PROCESSING/,fix:"Add dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) before parse()"},{id:"CWE-89",lang:["java"],severity:"HIGH",name:"SQL Injection",pattern:/"(SELECT|INSERT|UPDATE|DELETE)\b.*"\s*\+/i,fix:"Use PreparedStatement with parameterized queries"},{id:"CWE-326",lang:["java"],severity:"HIGH",name:"Weak Cryptography",pattern:/getInstance\("(MD5|SHA1|DES|RC4)"\)/,fix:"Use SHA-256 or AES-256"},{id:"CWE-502",lang:["java"],severity:"HIGH",name:"Unsafe Deserialization",pattern:/new ObjectInputStream|\.readObject\(\)/,fix:"Validate input before deserialization; use safe deserialization libraries"},{id:"CWE-078",lang:["java"],severity:"HIGH",name:"Command Injection",pattern:/Runtime\.getRuntime\(\)\.exec\(/,fix:"Use ProcessBuilder with argument list instead of string concatenation"},{id:"CWE-79",lang:["javascript","typescript","vue"],severity:"HIGH",name:"XSS via innerHTML / dangerouslySetInnerHTML",pattern:/\.innerHTML\s*=|dangerouslySetInnerHTML/,fix:"Use textContent or sanitize with DOMPurify"},{id:"CWE-89",lang:["javascript","typescript"],severity:"HIGH",name:"SQL Injection (JS)",pattern:/`\s*(SELECT|INSERT|UPDATE|DELETE)\b.*\$\{/i,fix:"Use parameterized queries or an ORM"},{id:"CWE-798",lang:["javascript","typescript","vue"],severity:"HIGH",name:"Hardcoded Secret",pattern:/(api_key|apikey|secret|password|token)\s*[:=]\s*['"][^'"]{8,}['"]/i,fix:"Move secrets to environment variables or a secrets manager"},{id:"CWE-327",lang:["javascript","typescript"],severity:"MEDIUM",name:"Weak Crypto (Node)",pattern:/createHash\(['"]md5['"]\)|createHash\(['"]sha1['"]\)/,fix:"Use SHA-256 or stronger"},{id:"CWE-601",lang:["javascript","typescript","vue"],severity:"MEDIUM",name:"Open Redirect",pattern:/res\.redirect\([^)]*req\.(query|params|body)/,fix:"Validate redirect URL against an allowlist"},{id:"CWE-089",lang:["python"],severity:"HIGH",name:"SQL Injection (Python)",pattern:/execute\([f"'].*%(s|d)|execute\(.*format\(/,fix:"Use parameterized queries: cursor.execute(sql, params)"},{id:"CWE-078",lang:["python"],severity:"HIGH",name:"Command Injection (Python)",pattern:/os\.system\(|subprocess\.call\(.*shell=True/,fix:"Use subprocess with a list of args and shell=False"},{id:"CWE-798",lang:["python"],severity:"HIGH",name:"Hardcoded Secret (Python)",pattern:/(api_key|secret|password|token)\s*=\s*['"][^'"]{8,}['"]/i,fix:"Use environment variables or a secrets manager like Vault"},{id:"CWE-312",lang:null,severity:"MEDIUM",name:"Cleartext Sensitive Data in Comments",pattern:/\/\/.*?(password|secret|token)\s*[:=]\s*\S+/i,fix:"Remove sensitive data from comments"}];function bt(t,e,n){let s=[],i=e.split(`
115
115
  `);for(let r of Yn)r.lang&&!r.lang.includes(n)||i.forEach((o,a)=>{if(r.pattern.test(o)){if(r.negPattern){let c=i.slice(Math.max(0,a-5),a+5).join(`
116
116
  `);if(r.negPattern.test(c))return}s.push({severity:r.severity,cwe:r.id,rule_name:r.name,line:a+1,code_snippet:o.trim().substring(0,120),description:r.fix})}});return s}function Tt(t,e,n){let s=[];for(let i of e.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g)){let r=i[1],o=r.match(/<groupId>([^<]+)<\/groupId>/)?.[1]||"",a=r.match(/<artifactId>([^<]+)<\/artifactId>/)?.[1]||"",c=r.match(/<version>([^<]+)<\/version>/)?.[1]||"",l=r.match(/<scope>([^<]+)<\/scope>/)?.[1]||"compile";o&&a&&s.push({manager:"maven",group_id:o,artifact:a,version:c,scope:l})}return s}function wt(t,e,n){let s=[];try{let i=JSON.parse(e);for(let[r,o]of Object.entries(i.dependencies||{}))s.push({manager:"npm",group_id:"dependencies",artifact:r,version:o,scope:"runtime"});for(let[r,o]of Object.entries(i.devDependencies||{}))s.push({manager:"npm",group_id:"devDependencies",artifact:r,version:o,scope:"dev"});for(let[r,o]of Object.entries(i.peerDependencies||{}))s.push({manager:"npm",group_id:"peerDependencies",artifact:r,version:o,scope:"peer"})}catch{}return s}function Rt(t,e,n){let s=[];for(let i of e.matchAll(/(\w+)\s+['"]([^:'"]+):([^:'"]+):([^'"]+)['"]/g)){let r=i[1],o=i[2],a=i[3],c=i[4];s.push({manager:"gradle",group_id:o,artifact:a,version:c,scope:r})}return s}function Ot(t,{repo:e,path:n,language:s,file_type:i}){let r=t.prepare("SELECT id FROM main.files WHERE path = ?").get(n);return r?(t.prepare("UPDATE main.files SET language = ?, file_type = ?, last_indexed = ? WHERE id = ?").run(s,i,Date.now(),r.id),r.id):t.prepare("INSERT INTO main.files (repo, path, language, file_type, last_indexed) VALUES (?, ?, ?, ?, ?)").run(e,n,s,i,Date.now()).lastInsertRowid}function Nt(t,{file_id:e,fqn:n,name:s,kind:i,language:r,exported:o,description:a,line:c}){let l=t.prepare("SELECT id FROM main.symbols WHERE fqn = ?").get(n);l?t.prepare("UPDATE main.symbols SET file_id=?, name=?, kind=?, language=?, exported=?, description=?, line=? WHERE id=?").run(e,s,i,r,o?1:0,a||"",c??null,l.id):t.prepare("INSERT INTO main.symbols (file_id, fqn, name, kind, language, exported, description, line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,n,s,i,r,o?1:0,a||"",c??null)}function kt(t,e){t.prepare("DELETE FROM main.dependencies WHERE from_file_id = ?").run(e),t.prepare("DELETE FROM main.security_findings WHERE file_id = ?").run(e)}function vt(t,{from_file_id:e,to_fqn:n,dep_type:s}){t.prepare("INSERT INTO main.dependencies (from_file_id, to_fqn, dep_type) VALUES (?, ?, ?)").run(e,n,s)}function Lt(t,{repo:e,manager:n,group_id:s,artifact:i,version:r,scope:o}){t.prepare("SELECT rowid FROM main.build_deps WHERE repo=? AND manager=? AND artifact=?").get(e,n,i)||t.prepare("INSERT INTO main.build_deps (repo, manager, group_id, artifact, version, scope) VALUES (?, ?, ?, ?, ?, ?)").run(e,n,s,i,r,o)}function $t(t,{file_id:e,line:n,severity:s,cwe:i,rule_name:r,description:o,code_snippet:a}){t.prepare("INSERT INTO main.security_findings (file_id, line, severity, cwe, rule_name, description, code_snippet) VALUES (?, ?, ?, ?, ?, ?, ?)").run(e,n,s,i,r,o,a)}import zn from"fs/promises";var Kn=/(?:\/\/|#|\/\*|\*|<!--)\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s]+(.*)/i;async function Se(t,e){t.prepare("DELETE FROM main.todos WHERE source = ?").run("scan");let n=t.prepare("INSERT INTO main.todos (source, file, line, kind, text) VALUES (?, ?, ?, ?, ?)"),s=0;for(let i of e)try{let o=(await zn.readFile(i,"utf-8")).split(`
117
117
  `);for(let a=0;a<o.length;a++){let c=Kn.exec(o[a]);if(c){let l=c[1].toUpperCase(),p=c[2].trim();p&&(n.run("scan",i,a+1,l,p),s++)}}}catch{}return s}function Vn(t){let e=t.toLowerCase();return e.includes("test")||e.includes("spec")?"test":e.endsWith("pom.xml")||e.endsWith("package.json")||e.endsWith("build.gradle")?"build":e.endsWith(".yml")||e.endsWith(".yaml")||e.endsWith(".properties")||e.endsWith(".env")?"config":e.endsWith(".md")||e.endsWith(".html")?"doc":"source"}async function It(t,e,n,s,i){try{let r=await De.readFile(e,"utf-8"),o=await xt(e,r,n);if(!o)return;let{language:a}=o;if(s&&!s.includes(a))return;i.files_by_language[a]=(i.files_by_language[a]||0)+1;let c=Ot(t,{repo:n,path:e,language:a,file_type:Vn(e)});kt(t,c);for(let p of o.symbols||[])Nt(t,{...p,file_id:c,language:a}),i.symbols_total++;for(let p of o.imports||[])vt(t,{from_file_id:c,to_fqn:p,dep_type:"imports"}),i.dependencies_mapped++;let l=bt(e,r,a);for(let p of l)$t(t,{file_id:c,...p}),i.security_findings++}catch{}}function At(t){t.exec(`
@@ -125,7 +125,7 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
125
125
  bm25(fts_index) as score
126
126
  FROM ${d}.fts_index
127
127
  WHERE ${d}.fts_index MATCH ?
128
- `,y=[e];s&&(m+=" AND language = ?",y.push(s)),i&&(m+=" AND kind = ?",y.push(i)),m+=" ORDER BY bm25(fts_index)",m+=` LIMIT ${n*2}`;try{let f=t.prepare(m).all(...y);o.push(...f)}catch{}}o.sort((d,m)=>d.score-m.score);let a=new Set,l=o.filter(d=>a.has(d.fqn)?!1:(a.add(d.fqn),!0)).slice(0,n),p=new Map;for(let d of r){let m=l.filter(y=>!p.has(y.fqn)).map(y=>y.fqn);if(m.length===0)break;try{let y=m.map(()=>"?").join(","),f=t.prepare(`SELECT fqn, line FROM ${d}.symbols WHERE fqn IN (${y})`).all(...m);for(let g of f)g.line!=null&&p.set(g.fqn,g.line)}catch{}}let u=l.map(d=>({...d,line:p.get(d.fqn)??null}));return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}function Ft(t,{path:e}){let n=t.prepare("SELECT * FROM files WHERE path LIKE ?").all(`${e}%`);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({path:e,error:"No files found"})}]};let s=n.map(m=>m.id),i=[...new Set(n.map(m=>m.language).filter(Boolean))],r=s.map(()=>"?").join(","),o=t.prepare(`SELECT * FROM symbols WHERE file_id IN (${r})`).all(...s),a={};for(let m of o){let y=m.kind||"other";a[y]||(a[y]=[]),a[y].push(m.name)}let c=t.prepare(`SELECT DISTINCT to_fqn FROM dependencies WHERE from_file_id IN (${r}) AND dep_type = 'imports'`).all(...s).map(m=>m.to_fqn),l=[];if(o.length>0){let m=o.map(f=>f.fqn),y=m.map(()=>"?").join(",");l=t.prepare(`SELECT DISTINCT f.path FROM dependencies d JOIN files f ON d.from_file_id = f.id WHERE d.to_fqn IN (${y})`).all(...m).map(f=>f.path)}let p=new Set(t.prepare("SELECT fqn FROM symbols").all().map(m=>m.fqn)),u=[...new Set(c.filter(m=>!p.has(m)))].slice(0,20),d={path:e,languages:i,symbols:a,imports_from:c.slice(0,20),imported_by:l.slice(0,20),external_deps:u};return{content:[{type:"text",text:JSON.stringify(d,null,2)}]}}function jt(t,{module:e,mode:n}){if(n==="health"){let s=t.prepare("SELECT COUNT(*) as c FROM files").get()?.c||0,i=t.prepare("SELECT COUNT(*) as c FROM symbols").get()?.c||0,r=t.prepare("SELECT COUNT(*) as c FROM dependencies").get()?.c||0,o=t.prepare("SELECT COUNT(*) as c FROM security_findings").get()?.c||0,a=t.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all(),c=t.prepare("SELECT MAX(last_indexed) as t FROM files").get()?.t;return{content:[{type:"text",text:JSON.stringify({files:s,symbols:i,dependencies:r,security_findings:o,by_language:a,last_indexed:c?new Date(c).toISOString():null},null,2)}]}}if(n==="instability"){let s=t.prepare("SELECT id, path, language FROM files").all(),i={};for(let a of s){let l=a.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/")||"/";e&&!l.includes(e)||(i[l]||(i[l]={name:l,language:a.language,fileIds:[]}),i[l].fileIds.push(a.id))}let r=[];for(let[,a]of Object.entries(i)){if(a.fileIds.length===0)continue;let c=a.fileIds.map(()=>"?").join(","),l=t.prepare(`SELECT COUNT(DISTINCT to_fqn) as c FROM dependencies WHERE from_file_id IN (${c})`).get(...a.fileIds)?.c||0,p=t.prepare(`SELECT fqn FROM symbols WHERE file_id IN (${c})`).all(...a.fileIds).map(y=>y.fqn),u=0;if(p.length>0){let y=p.map(()=>"?").join(",");u=t.prepare(`SELECT COUNT(DISTINCT from_file_id) as c FROM dependencies WHERE to_fqn IN (${y})`).get(...p)?.c||0}let d=u+l===0?0:l/(u+l),m=d>=.8?"safe_to_refactor":d>=.3?"review_before_change":"load_bearing";r.push({name:a.name,language:a.language,instability:Math.round(d*100)/100,status:m})}r.sort((a,c)=>c.instability-a.instability);let o=t.prepare(`
128
+ `,y=[e];s&&(m+=" AND language = ?",y.push(s)),i&&(m+=" AND kind = ?",y.push(i)),m+=" ORDER BY bm25(fts_index)",m+=` LIMIT ${n*2}`;try{let f=t.prepare(m).all(...y);o.push(...f)}catch{}}o.sort((d,m)=>d.score-m.score);let a=new Set,l=o.filter(d=>a.has(d.fqn)?!1:(a.add(d.fqn),!0)).slice(0,n),p=new Map;for(let d of r){let m=l.filter(y=>!p.has(y.fqn)).map(y=>y.fqn);if(m.length===0)break;try{let y=m.map(()=>"?").join(","),f=t.prepare(`SELECT fqn, line FROM ${d}.symbols WHERE fqn IN (${y})`).all(...m);for(let g of f)g.line!=null&&p.set(g.fqn,g.line)}catch{}}let u=l.map(d=>({...d,line:p.get(d.fqn)??null}));return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}function jt(t,{path:e}){let n=t.prepare("SELECT * FROM files WHERE path LIKE ?").all(`${e}%`);if(n.length===0)return{content:[{type:"text",text:JSON.stringify({path:e,error:"No files found"})}]};let s=n.map(m=>m.id),i=[...new Set(n.map(m=>m.language).filter(Boolean))],r=s.map(()=>"?").join(","),o=t.prepare(`SELECT * FROM symbols WHERE file_id IN (${r})`).all(...s),a={};for(let m of o){let y=m.kind||"other";a[y]||(a[y]=[]),a[y].push(m.name)}let c=t.prepare(`SELECT DISTINCT to_fqn FROM dependencies WHERE from_file_id IN (${r}) AND dep_type = 'imports'`).all(...s).map(m=>m.to_fqn),l=[];if(o.length>0){let m=o.map(f=>f.fqn),y=m.map(()=>"?").join(",");l=t.prepare(`SELECT DISTINCT f.path FROM dependencies d JOIN files f ON d.from_file_id = f.id WHERE d.to_fqn IN (${y})`).all(...m).map(f=>f.path)}let p=new Set(t.prepare("SELECT fqn FROM symbols").all().map(m=>m.fqn)),u=[...new Set(c.filter(m=>!p.has(m)))].slice(0,20),d={path:e,languages:i,symbols:a,imports_from:c.slice(0,20),imported_by:l.slice(0,20),external_deps:u};return{content:[{type:"text",text:JSON.stringify(d,null,2)}]}}function Ft(t,{module:e,mode:n}){if(n==="health"){let s=t.prepare("SELECT COUNT(*) as c FROM files").get()?.c||0,i=t.prepare("SELECT COUNT(*) as c FROM symbols").get()?.c||0,r=t.prepare("SELECT COUNT(*) as c FROM dependencies").get()?.c||0,o=t.prepare("SELECT COUNT(*) as c FROM security_findings").get()?.c||0,a=t.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all(),c=t.prepare("SELECT MAX(last_indexed) as t FROM files").get()?.t;return{content:[{type:"text",text:JSON.stringify({files:s,symbols:i,dependencies:r,security_findings:o,by_language:a,last_indexed:c?new Date(c).toISOString():null},null,2)}]}}if(n==="instability"){let s=t.prepare("SELECT id, path, language FROM files").all(),i={};for(let a of s){let l=a.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/")||"/";e&&!l.includes(e)||(i[l]||(i[l]={name:l,language:a.language,fileIds:[]}),i[l].fileIds.push(a.id))}let r=[];for(let[,a]of Object.entries(i)){if(a.fileIds.length===0)continue;let c=a.fileIds.map(()=>"?").join(","),l=t.prepare(`SELECT COUNT(DISTINCT to_fqn) as c FROM dependencies WHERE from_file_id IN (${c})`).get(...a.fileIds)?.c||0,p=t.prepare(`SELECT fqn FROM symbols WHERE file_id IN (${c})`).all(...a.fileIds).map(y=>y.fqn),u=0;if(p.length>0){let y=p.map(()=>"?").join(",");u=t.prepare(`SELECT COUNT(DISTINCT from_file_id) as c FROM dependencies WHERE to_fqn IN (${y})`).get(...p)?.c||0}let d=u+l===0?0:l/(u+l),m=d>=.8?"safe_to_refactor":d>=.3?"review_before_change":"load_bearing";r.push({name:a.name,language:a.language,instability:Math.round(d*100)/100,status:m})}r.sort((a,c)=>c.instability-a.instability);let o=t.prepare(`
129
129
  SELECT s.name || ' (' || COALESCE(s.language,'?') || ')' as label, COUNT(*) as dep_count
130
130
  FROM dependencies d JOIN symbols s ON d.to_fqn = s.fqn
131
131
  GROUP BY d.to_fqn ORDER BY dep_count DESC LIMIT 5
@@ -154,12 +154,12 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
154
154
  `).filter(p=>p.trim()).join(`
155
155
  `)),l=l.replace(/\s+/g," ").trim(),l&&o.push(`<template>${l}</template>`)}let c=t.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(c){let l=c[1],p=se(c[2],"javascript",{noComments:s,noEmptyLines:i,aggressive:r}),u=/setup/i.test(l)?"<script setup>":"<script>";p&&o.push(`${u}${p}</script>`)}if(!n){let l=t.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(l){let p=l[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();p&&o.push(`<style${l[1]}>${p}</style>`)}}return o.join(`
156
156
 
157
- `)}async function Pt(t={}){let{bundleName:e="default",noComments:n=!0,noEmptyLines:s=!0,noStyle:i=!1,aggressive:r=!1,onlyChanged:o=!1,filterLang:a=null,filterPaths:c=null,maxSizeKB:l=800}=t,p=re.join(F(),"bundles"),u=process.cwd(),d=re.join(p,`${e}.txt`),m=re.join(p,`${e}.mtime.json`),y=o&&ie.existsSync(m)?JSON.parse(ie.readFileSync(m,"utf8")):{},f={},g=[],S={processed:0,skipped_unchanged:0,skipped_size:0,original_bytes:0,compressed_bytes:0,by_language:{}},_=a?Array.isArray(a)?a:[a]:null,w=await B(u);for(let h of w){let E=re.extname(h).toLowerCase(),b=Q[E];if(!b||_&&!_.includes(b))continue;if(c){let z=re.relative(u,h).replace(/\\/g,"/");if(!c.some(ye=>z.startsWith(ye)))continue}let k=ie.statSync(h).mtimeMs;if(f[h]=k,o&&y[h]===k){S.skipped_unchanged++;continue}if(Buffer.byteLength(g.join(`
157
+ `)}async function Pt(t={}){let{bundleName:e="default",noComments:n=!0,noEmptyLines:s=!0,noStyle:i=!1,aggressive:r=!1,onlyChanged:o=!1,filterLang:a=null,filterPaths:c=null,maxSizeKB:l=800}=t,p=re.join(j(),"bundles"),u=process.cwd(),d=re.join(p,`${e}.txt`),m=re.join(p,`${e}.mtime.json`),y=o&&ie.existsSync(m)?JSON.parse(ie.readFileSync(m,"utf8")):{},f={},g=[],S={processed:0,skipped_unchanged:0,skipped_size:0,original_bytes:0,compressed_bytes:0,by_language:{}},_=a?Array.isArray(a)?a:[a]:null,w=await B(u);for(let h of w){let E=re.extname(h).toLowerCase(),b=Q[E];if(!b||_&&!_.includes(b))continue;if(c){let z=re.relative(u,h).replace(/\\/g,"/");if(!c.some(ye=>z.startsWith(ye)))continue}let k=ie.statSync(h).mtimeMs;if(f[h]=k,o&&y[h]===k){S.skipped_unchanged++;continue}if(Buffer.byteLength(g.join(`
158
158
  `),"utf8")/1024>l){S.skipped_size++;continue}let O;try{O=ie.readFileSync(h,"utf8")}catch{continue}S.original_bytes+=Buffer.byteLength(O,"utf8");let N=b==="vue"?be(O,{noStyle:i,noComments:n,noEmptyLines:s,aggressive:r}):se(O,b,{noComments:n,noEmptyLines:s,aggressive:r});if(!N.trim())continue;let le=re.relative(u,h).replace(/\\/g,"/");g.push(`### File: ${le}`),g.push(N.trim()),g.push(""),S.compressed_bytes+=Buffer.byteLength(N,"utf8"),S.processed++,S.by_language[b]=(S.by_language[b]||0)+1}let R=g.join(`
159
159
  `);ie.writeFileSync(d,R,"utf8"),o&&ie.writeFileSync(m,JSON.stringify(f),"utf8");let x=S.original_bytes>0?Math.round((1-S.compressed_bytes/S.original_bytes)*100):0;return{bundle_path:d,processed:S.processed,skipped_unchanged:S.skipped_unchanged,skipped_size_cap:S.skipped_size,original_kb:Math.round(S.original_bytes/1024),compressed_kb:Math.round(S.compressed_bytes/1024),reduction_pct:x,by_language:S.by_language}}async function Ut(t,e){let{bundle_name:n="default",filter_paths:s=null,filter_language:i=null,no_comments:r=!0,no_empty_lines:o=!0,no_style:a=!1,aggressive:c=!1,only_changed:l=!1,max_size_kb:p=800}=e,u=await Pt({bundleName:n,noComments:r,noEmptyLines:o,noStyle:a,aggressive:c,onlyChanged:l,filterLang:i,filterPaths:s,maxSizeKB:p});return u.next_step=`Read the bundle: Read("${u.bundle_path}")`,{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}import G from"fs";import oe from"path";import{execSync as ns}from"child_process";var v="would_be_lost",Ht="What would a fresh session NOT know? Name what lives only in this conversation and nowhere else: work you finished but never verified, a thread you are in the middle of, a decision made for a reason that is not in the commit message or the code, a thing you tried that did not work. Write it for someone who never saw this session. Omit only when there is genuinely nothing to carry \u2014 this is the one part of a checkpoint that cannot be recovered from git or the index.",Wr=2*1024*1024,qr=[256*1024,1024*1024];var ss=220;function is(t,e=ss){let n=String(t).replace(/\s+/g," ").trim();return n.length<=e?n:n.slice(0,e-1)+"\u2026"}function rs(t,e){try{let n=ns(`git log --oneline --after="${e}"`,{cwd:t,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim();return n?n.split(`
160
- `).length:0}catch{return null}}function Wt(t,e){let n=typeof e?.[v]=="string"?e[v].trim():"";if(!n)return{recorded:!1,text:"",at:null,commits:null,stale:!1};let s=e[`${v}_at`]||e.checkpoint_at||null,i=s?rs(t,s):null;return{recorded:!0,text:n,at:s,commits:i,stale:(i??0)>0}}function qt(t){if(!t.recorded)return"No handoff recorded \u2014 nothing was written down about what the last session would lose.";let e=t.stale?` (written before the ${t.commits} commit(s) since \u2014 may be stale)`:"";return`Carried forward: ${is(t.text)}${e}`}var Br=["Cairn measures the count above but CANNOT see your context window \u2014 no hook input carries","it and the transcript never records a limit. You know your own window; cairn does not. So","the call is yours, and cairn will never make it for you: before starting what the user just","asked for, estimate what it will cost (files to read, tools to run, output to write) and","weigh that against what is left.","","If it plausibly does NOT fit, raise it BEFORE starting rather than stalling halfway:",` 1. Call cairn_checkpoint FIRST and answer ${v} \u2014 it is the only part of this`," session that survives either option.",' 2. Then AskUserQuestion with "Checkpoint + /clear", "Checkpoint + /compact" and "Keep',' going". State plainly that you CANNOT type the command \u2014 no tool or hook can trigger a'," slash command \u2014 so they have to type it themselves.",` 3. Never write ${v} for them, and never say there is nothing outstanding:`," cairn cannot see what was never written down, so it can never issue an all-clear.","","If it fits, say nothing and get on with it. Do not narrate the check, do not report a","percentage as if cairn had given you one, and do not re-raise what the user already declined."].join(`
160
+ `).length:0}catch{return null}}function Wt(t,e){let n=typeof e?.[v]=="string"?e[v].trim():"";if(!n)return{recorded:!1,text:"",at:null,commits:null,stale:!1};let s=e[`${v}_at`]||e.checkpoint_at||null,i=s?rs(t,s):null;return{recorded:!0,text:n,at:s,commits:i,stale:(i??0)>0}}function qt(t){if(!t.recorded)return"No handoff recorded \u2014 nothing was written down about what the last session would lose.";let e=t.stale?` (written before the ${t.commits} commit(s) since \u2014 may be stale)`:"";return`Carried forward: ${is(t.text)}${e}`}var Br=["Cairn CANNOT see your context window; you can, so the call is yours: judge whether what was","just asked fits in what is left. If it does, say nothing and get on with it, and do not",`re-raise a decline. If it does not, call cairn_checkpoint FIRST (answer ${v} \u2014 it`,'is all of this session that survives), then AskUserQuestion offering "Checkpoint + /clear",','"Checkpoint + /compact" or "Keep going", saying plainly they must type it: you CANNOT type the',`command. Never write ${v} for them and never issue an all-clear.`].join(`
161
161
  `);var os=1e3,as=100*1024;function cs(t,e,n,s){if(t&&typeof t=="object"&&t.ref)return t;let i=typeof t=="string"?t:String(t),r=/^\[\d{4}-\d{2}-\d{2}\]/.test(i)?i:`[${n}] ${i}`;if(r.length<=os)return r;let o=oe.join(e,"notes");G.mkdirSync(o,{recursive:!0});let c=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${s}.md`,l=oe.join(o,c);return G.writeFileSync(l,r,"utf8"),{description:r.split(`
162
- `)[0].slice(0,120),ref:oe.join(".cairn","notes",c)}}function Bt(t,{message:e,active_files:n=[],notes:s=[],auto:i=!1,...r}){if(typeof s=="string")try{s=JSON.parse(s)}catch{s=s?[s]:[]}Array.isArray(s)||(s=[]);let o=F(),a=oe.join(o,"session.json"),c=new Date().toISOString().slice(0,10),l=new Date().toISOString(),p={};try{p=JSON.parse(G.readFileSync(a,"utf8"))||{}}catch{}let u=typeof r[v]=="string"&&r[v].trim()?r[v].trim():null,d=u??(typeof p[v]=="string"?p[v]:null),m=u?l:p[`${v}_at`]??null,y={};try{let S=t.prepare("SELECT path FROM main.files").all();for(let{path:_}of S)try{y[_]=G.statSync(_).mtimeMs}catch{}}catch{}for(let S of n)try{y[S]=G.statSync(S).mtimeMs}catch{}let f=s.map((S,_)=>cs(S,o,c,_));if(G.existsSync(a))try{if(G.statSync(a).size>as){let _=1;for(;G.existsSync(oe.join(o,`session.${_}.json`));)_++;G.renameSync(a,oe.join(o,`session.${_}.json`))}}catch{}let g={message:e,checkpoint_at:l,last_manual_checkpoint_at:i?p.last_manual_checkpoint_at??null:l,last_manual_message:i?p.last_manual_message??null:e,active_files:n,notes:f,[v]:d,[`${v}_at`]:m,mtime_snapshot:y};return G.writeFileSync(a,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:f.length,[v]:d?u?"recorded":"carried forward from the previous checkpoint":`not recorded \u2014 nothing will tell the next session what this one would lose (pass ${v})`},null,2)}]}}import K from"fs";import Ne from"path";import{exec as ci}from"child_process";import{promisify as li}from"util";import Z from"fs";import Gt from"path";function Jt(t){try{return JSON.parse(Z.readFileSync(Gt.join(t,"minify-map.json"),"utf8"))}catch{return{}}}function Pe(t,e){Z.mkdirSync(t,{recursive:!0}),Z.writeFileSync(Gt.join(t,"minify-map.json"),JSON.stringify(e,null,2),"utf8")}function Xt(t,e){let n=0;for(let[s,i]of Object.entries(e)){let r=!1;if(!Z.existsSync(s))r=!0;else if(!Z.existsSync(i.tempPath))r=!0;else try{Z.statSync(s).mtimeMs>i.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(i.tempPath)try{Z.unlinkSync(i.tempPath)}catch{}delete e[s],n++}}return n}import L from"fs";import U from"path";var zt=["preference","experience"],He=["preference","experience","decision","knowledge"];function Kt(t){if(!t||typeof t!="string")return[];let e=t.split(/\r?\n/),n=!1,s=null,i=[],r=new Set;for(let o of e){if(/^##\s+Phases\s*$/i.test(o)){n=!0;continue}if(/^##\s+/.test(o)&&!/^###/.test(o)){s&&(i.push(s),s=null),n=!1;continue}if(!n)continue;let a=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(o);if(a){s&&i.push(s);let c=parseInt(a[1],10),l=a[2];if(r.has(c)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${c} ignored
162
+ `)[0].slice(0,120),ref:oe.join(".cairn","notes",c)}}function Bt(t,{message:e,active_files:n=[],notes:s=[],auto:i=!1,...r}){if(typeof s=="string")try{s=JSON.parse(s)}catch{s=s?[s]:[]}Array.isArray(s)||(s=[]);let o=j(),a=oe.join(o,"session.json"),c=new Date().toISOString().slice(0,10),l=new Date().toISOString(),p={};try{p=JSON.parse(G.readFileSync(a,"utf8"))||{}}catch{}let u=typeof r[v]=="string"&&r[v].trim()?r[v].trim():null,d=u??(typeof p[v]=="string"?p[v]:null),m=u?l:p[`${v}_at`]??null,y={};try{let S=t.prepare("SELECT path FROM main.files").all();for(let{path:_}of S)try{y[_]=G.statSync(_).mtimeMs}catch{}}catch{}for(let S of n)try{y[S]=G.statSync(S).mtimeMs}catch{}let f=s.map((S,_)=>cs(S,o,c,_));if(G.existsSync(a))try{if(G.statSync(a).size>as){let _=1;for(;G.existsSync(oe.join(o,`session.${_}.json`));)_++;G.renameSync(a,oe.join(o,`session.${_}.json`))}}catch{}let g={message:e,checkpoint_at:l,last_manual_checkpoint_at:i?p.last_manual_checkpoint_at??null:l,last_manual_message:i?p.last_manual_message??null:e,active_files:n,notes:f,[v]:d,[`${v}_at`]:m,mtime_snapshot:y};return G.writeFileSync(a,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:f.length,[v]:d?u?"recorded":"carried forward from the previous checkpoint":`not recorded \u2014 nothing will tell the next session what this one would lose (pass ${v})`},null,2)}]}}import K from"fs";import Ne from"path";import{exec as ci}from"child_process";import{promisify as li}from"util";import Z from"fs";import Gt from"path";function Jt(t){try{return JSON.parse(Z.readFileSync(Gt.join(t,"minify-map.json"),"utf8"))}catch{return{}}}function Pe(t,e){Z.mkdirSync(t,{recursive:!0}),Z.writeFileSync(Gt.join(t,"minify-map.json"),JSON.stringify(e,null,2),"utf8")}function Xt(t,e){let n=0;for(let[s,i]of Object.entries(e)){let r=!1;if(!Z.existsSync(s))r=!0;else if(!Z.existsSync(i.tempPath))r=!0;else try{Z.statSync(s).mtimeMs>i.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(i.tempPath)try{Z.unlinkSync(i.tempPath)}catch{}delete e[s],n++}}return n}import L from"fs";import U from"path";var zt=["preference","experience"],He=["preference","experience","decision","knowledge"];function Kt(t){if(!t||typeof t!="string")return[];let e=t.split(/\r?\n/),n=!1,s=null,i=[],r=new Set;for(let o of e){if(/^##\s+Phases\s*$/i.test(o)){n=!0;continue}if(/^##\s+/.test(o)&&!/^###/.test(o)){s&&(i.push(s),s=null),n=!1;continue}if(!n)continue;let a=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(o);if(a){s&&i.push(s);let c=parseInt(a[1],10),l=a[2];if(r.has(c)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${c} ignored
163
163
  `),s=null;continue}r.add(c),s={phase_number:c,text:l,bodyLines:[]};continue}s&&s.bodyLines.push(o)}return s&&i.push(s),i.map(o=>({phase_number:o.phase_number,text:o.text,body:o.bodyLines.join(`
164
164
  `).trim()||null}))}var Ue=(t,e)=>t+" :: "+e;function Vt(t,e,n,s={}){let i=t.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(e),r=new Map(i.map(h=>[h.phase_number,h])),o=new Set,a=t.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),c=t.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),l=t.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),p=t.prepare("UPDATE main.roadmap_phases SET phase_number = NULL WHERE id = ?"),u=t.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = 'open', resolved_at = NULL WHERE id = ?"),d=s.completed??null,m=d===null?null:new Set(d.map(h=>Ue(h.number,h.title))),y=new Map;for(let h of d??[])h.id!=null&&y.set(Ue(h.number,h.title),h.id);let f=n.some(h=>{let E=r.get(h.phase_number);return E&&E.status==="done"&&E.text!==h.text}),g=[],S=[],_=[],w=[],R=[];for(let h=0;h<n.length;h++){let E=n[h];o.add(E.phase_number);let b=r.get(E.phase_number),k=!!b&&b.status==="done",A=Ue(E.phase_number,E.text),O=m!==null&&m.has(A),N=O&&y.has(A)&&y.get(A)!==b.id,le=O&&(f||N);if(k&&b.text===E.text&&m!==null&&!O)u.run(E.text,E.body,b.id),g.push({id:b.id,phase_number:E.phase_number,text:E.text});else if(k&&(b.text!==E.text||le)){p.run(b.id);let z=c.run(e,E.phase_number,E.text,E.body,"open",h);w.push({id:Number(z.lastInsertRowid),phase_number:E.phase_number,text:E.text,replaced_shipped:b.id})}else if(b)a.run(E.text,E.body,"stale","open",b.id),_.push({id:b.id,phase_number:E.phase_number,text:E.text,reactivated:b.status==="stale"});else{let z=c.run(e,E.phase_number,E.text,E.body,"open",h);w.push({id:Number(z.lastInsertRowid),phase_number:E.phase_number,text:E.text})}}for(let[h,E]of r)!o.has(h)&&E.status!=="stale"&&E.status!=="done"&&(l.run(E.id),R.push({id:E.id,phase_number:h}));if(g.length>0||w.some(h=>h.replaced_shipped!=null)){let h=n.reduce((E,b)=>Math.max(E,b.phase_number),0);for(let[E,b]of r)b.status==="done"&&E>h&&(p.run(b.id),S.push({id:b.id,phase_number:E,text:b.text}))}return{updated:_.length,inserted:w.length,staled:R.length,repaired:g.length,retired:S.length,updated_phases:_,inserted_phases:w,staled_phases:R,repaired_phases:g,retired_phases:S}}function fe(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function ls(t,e){return`${t}_${fe(e)}.md`}function Qt(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return{};let n={};for(let s of e[1].split(`
165
165
  `)){let i=s.indexOf(":");i!==-1&&(n[s.slice(0,i).trim()]=s.slice(i+1).trim())}return n}function Zt(t){let e=U.join(t,"MEMORY.md");return L.existsSync(e)?L.readFileSync(e,"utf8"):""}var ps=/^- \[(.+?)\]\((.+?)\)\s+—\s+(.*?)(?:\s+`\[(.+?)\]`)?\s*$/;function en(t){let e=ps.exec(t);return e?{name:e[1],file:e[2],description:e[3],type:e[4]||null}:null}function Yt(t){let e="- ["+t.name+"]("+t.file+") \u2014 "+t.description;return t.type?e+" `["+t.type+"]`":e}function me(t,e){let n=U.join(t,"MEMORY.md"),s=new Map(e.map(a=>[a.file,a])),i=new Set,r=L.existsSync(n)?L.readFileSync(n,"utf8").split(`
@@ -172,8 +172,8 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
172
172
  `)+`
173
173
 
174
174
  `+i+`
175
- `,"utf8");let f=W(c),g=f.findIndex(_=>_.file===l),S={name:n,file:l,description:r||n,type:s};return g>=0?f[g]=S:f.push(S),me(c,f),{content:[{type:"text",text:JSON.stringify({saved:!0,name:n,type:s,file:p,action:u?"updated":"created"},null,2)}]}}import We from"fs";import fs from"path";function ms(t){if(t.length===0)return null;let e=t.map(i=>i.replace(/\\/g,"/")),n=e[0].split("/"),s="";for(let i=1;i<=n.length;i++){let r=n.slice(0,i).join("/");if(e.every(o=>o.startsWith(r+"/")))s=r;else break}return s||null}function sn(t,e){let n=Object.keys(t);if(n.length===0||n.filter(l=>{try{return We.statSync(l),!1}catch{return!0}}).length<Math.ceil(n.length*.5))return null;let i=ms(n);if(!i)return null;let r=e.replace(/\\/g,"/"),o=i.replace(/\\/g,"/");if(r===o)return null;let a=n.slice(0,Math.min(8,n.length));return a.filter(l=>{let p=l.replace(/\\/g,"/").replace(o+"/","");try{return We.statSync(r+"/"+p),!0}catch{return!1}}).length<Math.ceil(a.length*.6)?null:{oldRoot:o,newRoot:r}}function Te(t,e,n){let s=t.replace(/\\/g,"/");if(s.startsWith(e+"/"))return n+s.slice(e.length);let i=e.replace(/\//g,"\\");return t.startsWith(i+"\\")?n+"/"+t.slice(i.length+1).replace(/\\/g,"/"):t}function rn(t,e,n,s,i,r){let o={};for(let[p,u]of Object.entries(n.mtime_snapshot||{}))o[Te(p,i,r)]=u;n.mtime_snapshot=o,n.active_files=(n.active_files||[]).map(p=>Te(p,i,r)),We.writeFileSync(fs.join(e,"session.json"),JSON.stringify(n,null,2),"utf8");let a=i.replace(/\//g,"\\");function c(p,u){try{t.prepare(`UPDATE ${p} SET ${u} = replace(${u}, ?, ?)`).run(i,r),t.prepare(`UPDATE ${p} SET ${u} = replace(${u}, ?, ?)`).run(a,r)}catch{}}c("files","path"),c("symbols","fqn"),c("todos","file");try{t.prepare("DELETE FROM fts_index_content WHERE c0 LIKE ? OR c0 LIKE ? OR c3 LIKE ? OR c3 LIKE ?").run(i+"%",a+"%",i+"%",a+"%"),t.prepare("INSERT INTO fts_index(fts_index) VALUES('rebuild')").run()}catch{}let l={};for(let[p,u]of Object.entries(s)){let d={...u};d.tempPath&&(d.tempPath=Te(d.tempPath,i,r)),l[Te(p,i,r)]=d}for(let p of Object.keys(s))delete s[p];Object.assign(s,l)}import I from"fs";import ee from"path";import{execSync as hs}from"child_process";var H="roadmap";function ae(){return ee.join(F(),"roadmap.md")}function an(){return ee.join(F(),"roadmap_completed.md")}function gs(t){let e=[];for(let n of t){let s=(n.text||"").trim();s&&(!Number.isInteger(n.phase_number)||n.phase_number<1||e.push({phase_number:n.phase_number,text:s,body:n.body??null}))}return e}var qe="decision_production_roadmap.md";function ys(t){let e=t.match(/^---\n[\s\S]*?\n---\n?/);return e?t.slice(e[0].length):t}function _s(t,e){let n=ee.join(t,"MEMORY.md");if(!I.existsSync(n))return;let s=I.readFileSync(n,"utf8").split(/\r?\n/).filter(i=>!i.includes(`](${e})`));I.writeFileSync(n,s.join(`
176
- `),"utf8")}function Es(){let t=ae();if(I.existsSync(t))return null;let e=[],n=Ie();n&&e.push(n),e.push(ee.join(F(),"memory"));let s=null,i=null;for(let o of e){let a=ee.join(o,qe);s==null&&I.existsSync(a)&&(s=ys(I.readFileSync(a,"utf8")).trim(),i=a)}if(s==null)return null;let r=/^#\s/m.test(s)?"":`# Roadmap
175
+ `,"utf8");let f=W(c),g=f.findIndex(_=>_.file===l),S={name:n,file:l,description:r||n,type:s};return g>=0?f[g]=S:f.push(S),me(c,f),{content:[{type:"text",text:JSON.stringify({saved:!0,name:n,type:s,file:p,action:u?"updated":"created"},null,2)}]}}import We from"fs";import fs from"path";function ms(t){if(t.length===0)return null;let e=t.map(i=>i.replace(/\\/g,"/")),n=e[0].split("/"),s="";for(let i=1;i<=n.length;i++){let r=n.slice(0,i).join("/");if(e.every(o=>o.startsWith(r+"/")))s=r;else break}return s||null}function sn(t,e){let n=Object.keys(t);if(n.length===0||n.filter(l=>{try{return We.statSync(l),!1}catch{return!0}}).length<Math.ceil(n.length*.5))return null;let i=ms(n);if(!i)return null;let r=e.replace(/\\/g,"/"),o=i.replace(/\\/g,"/");if(r===o)return null;let a=n.slice(0,Math.min(8,n.length));return a.filter(l=>{let p=l.replace(/\\/g,"/").replace(o+"/","");try{return We.statSync(r+"/"+p),!0}catch{return!1}}).length<Math.ceil(a.length*.6)?null:{oldRoot:o,newRoot:r}}function Te(t,e,n){let s=t.replace(/\\/g,"/");if(s.startsWith(e+"/"))return n+s.slice(e.length);let i=e.replace(/\//g,"\\");return t.startsWith(i+"\\")?n+"/"+t.slice(i.length+1).replace(/\\/g,"/"):t}function rn(t,e,n,s,i,r){let o={};for(let[p,u]of Object.entries(n.mtime_snapshot||{}))o[Te(p,i,r)]=u;n.mtime_snapshot=o,n.active_files=(n.active_files||[]).map(p=>Te(p,i,r)),We.writeFileSync(fs.join(e,"session.json"),JSON.stringify(n,null,2),"utf8");let a=i.replace(/\//g,"\\");function c(p,u){try{t.prepare(`UPDATE ${p} SET ${u} = replace(${u}, ?, ?)`).run(i,r),t.prepare(`UPDATE ${p} SET ${u} = replace(${u}, ?, ?)`).run(a,r)}catch{}}c("files","path"),c("symbols","fqn"),c("todos","file");try{t.prepare("DELETE FROM fts_index_content WHERE c0 LIKE ? OR c0 LIKE ? OR c3 LIKE ? OR c3 LIKE ?").run(i+"%",a+"%",i+"%",a+"%"),t.prepare("INSERT INTO fts_index(fts_index) VALUES('rebuild')").run()}catch{}let l={};for(let[p,u]of Object.entries(s)){let d={...u};d.tempPath&&(d.tempPath=Te(d.tempPath,i,r)),l[Te(p,i,r)]=d}for(let p of Object.keys(s))delete s[p];Object.assign(s,l)}import I from"fs";import ee from"path";import{execSync as hs}from"child_process";var H="roadmap";function ae(){return ee.join(j(),"roadmap.md")}function an(){return ee.join(j(),"roadmap_completed.md")}function gs(t){let e=[];for(let n of t){let s=(n.text||"").trim();s&&(!Number.isInteger(n.phase_number)||n.phase_number<1||e.push({phase_number:n.phase_number,text:s,body:n.body??null}))}return e}var qe="decision_production_roadmap.md";function ys(t){let e=t.match(/^---\n[\s\S]*?\n---\n?/);return e?t.slice(e[0].length):t}function _s(t,e){let n=ee.join(t,"MEMORY.md");if(!I.existsSync(n))return;let s=I.readFileSync(n,"utf8").split(/\r?\n/).filter(i=>!i.includes(`](${e})`));I.writeFileSync(n,s.join(`
176
+ `),"utf8")}function Es(){let t=ae();if(I.existsSync(t))return null;let e=[],n=Ie();n&&e.push(n),e.push(ee.join(j(),"memory"));let s=null,i=null;for(let o of e){let a=ee.join(o,qe);s==null&&I.existsSync(a)&&(s=ys(I.readFileSync(a,"utf8")).trim(),i=a)}if(s==null)return null;let r=/^#\s/m.test(s)?"":`# Roadmap
177
177
 
178
178
  `;I.writeFileSync(t,r+s+`
179
179
  `,"utf8");for(let o of e){let a=ee.join(o,qe);try{I.existsSync(a)&&I.unlinkSync(a)}catch{}try{_s(o,qe)}catch{}}return{migrated:!0,from:i,to:t}}function cn(t){Es();let e=ae();if(!I.existsSync(e))return null;let n=I.readFileSync(e,"utf8");if(!/^##\s+Phases\s*$/im.test(n))return null;let s=gs(Kt(n));return Vt(t,H,s,{completed:he()})}function ln(t){let e=ae();if(!I.existsSync(e))return{removed:!1,title:null};let n=I.readFileSync(e,"utf8").split(/\r?\n/),s=[],i=!1,r=!1,o=!1,a=null;for(let c of n){if(/^##\s+Phases\s*$/i.test(c)){i=!0,r=!1,s.push(c);continue}if(/^##\s+/.test(c)&&!/^###/.test(c)){i=!1,r=!1,s.push(c);continue}if(i){let l=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(c);if(l){if(parseInt(l[1],10)===t){r=!0,o=!0,a=l[2];continue}r=!1,s.push(c);continue}if(r)continue;s.push(c);continue}s.push(c)}if(o){let c=s.join(`
@@ -182,20 +182,20 @@ import{Server as Ei}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
182
182
  `);I.writeFileSync(e,c,"utf8")}return{removed:o,title:a}}function pn(t,e,n={}){let s=an(),i=I.existsSync(s)?I.readFileSync(s,"utf8"):`# Completed phases
183
183
 
184
184
  Shipped roadmap phases, newest last.
185
- `,r=n.date||new Date().toISOString().slice(0,10),o=n.commit===void 0?bs(ee.dirname(F())):n.commit,a=[n.phaseId!=null?`phase-${n.phaseId}`:null,o||null].filter(Boolean),c=a.length?` (${a.join(", ")})`:"",l=`- ${r} \u2014 Phase ${t}: ${e}${c}`;return i.endsWith(`
185
+ `,r=n.date||new Date().toISOString().slice(0,10),o=n.commit===void 0?bs(ee.dirname(j())):n.commit,a=[n.phaseId!=null?`phase-${n.phaseId}`:null,o||null].filter(Boolean),c=a.length?` (${a.join(", ")})`:"",l=`- ${r} \u2014 Phase ${t}: ${e}${c}`;return i.endsWith(`
186
186
  `)||(i+=`
187
187
  `),i+=l+`
188
- `,I.writeFileSync(s,i,"utf8"),{logged:l}}var Ss=/^-\s+(\d{4}-\d{2}-\d{2})\s+—\s+Phase\s+(\d+):\s*(.+?)(?:\s+\(([^)]*)\))?\s*$/,on=/^phase-(\d+)$/,xs=/^[0-9a-f]{7,40}$/;function he(){let t=an();if(!I.existsSync(t))return null;let e=[];for(let n of I.readFileSync(t,"utf8").split(/\r?\n/)){let s=Ss.exec(n);if(!s)continue;let i=s[3],r=null,o=null;if(s[4]!=null){let a=s[4].split(/\s*,\s*/).filter(Boolean);if(a.length>0&&a.every(l=>on.test(l)||xs.test(l)))for(let l of a){let p=on.exec(l);p?r=parseInt(p[1],10):o=l}else i=`${i} (${s[4]})`}e.push({date:s[1],number:parseInt(s[2],10),title:i,id:r,commit:o})}return e}function bs(t){try{return hs("git rev-parse --short HEAD",{cwd:t,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}function dn(){return he()?.length??0}import J from"fs";import j from"path";var we="cairn",Je="roadmap/1",Ts="<!-- slot:roadmap";function ws(t){return`${Ts} format=${Je} provider=${we} generated=${t} -->`}var Rs=`<!-- ${we}:roadmap -->`,Os=/^<!--\s*slot:roadmap\b([^>]*?)-->/,Ns=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,ks=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function vs(t){if(typeof t!="string")return null;let e=t.indexOf(`
188
+ `,I.writeFileSync(s,i,"utf8"),{logged:l}}var Ss=/^-\s+(\d{4}-\d{2}-\d{2})\s+—\s+Phase\s+(\d+):\s*(.+?)(?:\s+\(([^)]*)\))?\s*$/,on=/^phase-(\d+)$/,xs=/^[0-9a-f]{7,40}$/;function he(){let t=an();if(!I.existsSync(t))return null;let e=[];for(let n of I.readFileSync(t,"utf8").split(/\r?\n/)){let s=Ss.exec(n);if(!s)continue;let i=s[3],r=null,o=null;if(s[4]!=null){let a=s[4].split(/\s*,\s*/).filter(Boolean);if(a.length>0&&a.every(l=>on.test(l)||xs.test(l)))for(let l of a){let p=on.exec(l);p?r=parseInt(p[1],10):o=l}else i=`${i} (${s[4]})`}e.push({date:s[1],number:parseInt(s[2],10),title:i,id:r,commit:o})}return e}function bs(t){try{return hs("git rev-parse --short HEAD",{cwd:t,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}function dn(){return he()?.length??0}import J from"fs";import F from"path";var we="cairn",Je="roadmap/1",Ts="<!-- slot:roadmap";function ws(t){return`${Ts} format=${Je} provider=${we} generated=${t} -->`}var Rs=`<!-- ${we}:roadmap -->`,Os=/^<!--\s*slot:roadmap\b([^>]*?)-->/,Ns=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,ks=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function vs(t){if(typeof t!="string")return null;let e=t.indexOf(`
189
189
  `);return(e===-1?t:t.slice(0,e)).trim()}function Ls(t){let e=vs(t);if(!e)return null;let n=Os.exec(e);if(n){let i={};for(let r of n[1].matchAll(ks))i[r[1]]=r[2].replace(/^"|"$/g,"");return i.provider?{provider:i.provider.toLowerCase(),format:i.format||null,generated:i.generated||null}:null}let s=Ns.exec(e);return s?{provider:s[1].toLowerCase(),format:null,generated:null}:null}var $s=/^```roadmap-json[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/m;function mn(t){if(typeof t!="string")return null;let e=$s.exec(t);if(!e)return null;try{let n=JSON.parse(e[1]);return n&&typeof n=="object"&&!Array.isArray(n)?n:null}catch{return null}}var Is=new Set(["format","provider","generated","title","cursor","cursor_phase","cursor_path","phases","shipped_phases","migrated_from","migrated"]),As=new Set(["id","number","title","status","shipped","commit","goal","blocked_by","slices","detail","links"]);function hn(t,e){let n={};if(!t||typeof t!="object")return n;for(let s of Object.keys(t).sort())e.has(s)||(n[s]=t[s]);return n}function gn(t,e=new Map){if(!Array.isArray(t))return e;for(let n of t)n&&typeof n=="object"&&typeof n.id=="string"&&(e.set(n.id,n),gn(n.slices,e));return e}function Ms(t){if(typeof t!="string")return null;let e=mn(t)?.migrated_from;if(typeof e=="string"&&e)return e;let n=t.indexOf(`
190
- `);if(n===-1)return null;let s=t.indexOf("-->",n),i=s===-1?t.slice(0,512):t.slice(n,s),r=/^\s*migrated-from=(.+?)\s*$/m.exec(i);return r?r[1]:null}var Cs=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,Fs=["decision","knowledge","experience","preference"];function js(t){let e=j.relative($(),t).split(j.sep).join("/");return!e||e.startsWith("..")||j.isAbsolute(e)?null:e}function Ds(t){let e=j.join($(),".cairn","memory");return[j.join(e,`${t}.md`),...Fs.map(n=>j.join(e,`${n}_${t}.md`))]}function Ps(t){if(!t)return[];let e=[],n=new Set;for(let s of String(t).matchAll(Cs)){let i=s[1].trim();if(!(!i||n.has(i))){n.add(i);for(let r of Ds(i)){if(!J.existsSync(r))continue;let o=js(r);o&&e.push({kind:"memory",title:i,path:o});break}}}return e}var Us={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Hs=new Set(["open","in_progress","done","blocked","stale"]);function Ws(t){return t==="done"?"x":t==="in_progress"?"~":t==="blocked"?"!":" "}function yn(){return j.join($(),"ROADMAP.md")}function _n(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function qs(t){try{return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Bs(t){let e=new Map,n;try{n=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let s of n)e.has(s.blocked_id)||e.set(s.blocked_id,new Set),e.get(s.blocked_id).add(s.blocker_id);return e}function Gs(t,e){let n=t.decision_name??"",s=e.decision_name??"";return n!==s?n<s?-1:1:t.phase_number!=null&&e.phase_number!=null?t.phase_number-e.phase_number:t.position-e.position||t.id-e.id}function Js(t){let e;try{e=t.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(e.filter(x=>x.status!=="stale").length===0)return null;let n=qs(t),s=Bs(t),i=new Map(e.map(x=>[x.id,x.status])),r=new Map(e.map(x=>[x.id,x.phase_number!=null?`Phase ${x.phase_number}`:x.text])),o=new Map(e.map(x=>[x.id,x])),a=new Map;for(let x of e){let h=x.parent_phase_id??"root";a.has(h)||a.set(h,[]),a.get(h).push(x)}for(let x of a.values())x.sort(Gs);let c=new Map,l=[];for(let x of a.get("root")||[])if(x.decision_name!=null){if(x.decision_name===H&&x.phase_number==null){x.status==="done"&&l.push(x);continue}c.has(x.decision_name)||c.set(x.decision_name,[]),c.get(x.decision_name).push(x)}let p=[...c.keys()].sort((x,h)=>x===H?-1:h===H?1:x<h?-1:x>h?1:0),u=new Map,d=!1;for(let x of p){let h=c.get(x).filter(b=>b.status!=="stale"),E=x===H?h.filter(b=>b.status!=="done"):h;E.length!==0&&(d=!0,u.set(x,{visible:E,doneCount:h.filter(b=>b.status==="done").length,total:h.length}))}if(!d)return null;let m=x=>{let h=s.get(x);return h?[...h].filter(E=>i.get(E)!=="done").map(E=>r.get(E)||`#${E}`):[]},y=x=>{let h=s.get(x);return h?[...h].filter(E=>o.has(E)).sort((E,b)=>E-b).map(E=>`phase-${E}`):[]},f=x=>(a.get(x)||[]).filter(h=>h.status!=="stale"),g=x=>a.get(x)||[],S=new Map(p.map(x=>[x,c.get(x)])),_=new Map;for(let x of he()||[])x.id!=null&&_.set(x.id,{shipped:x.date,commit:x.commit||null});return{cursor:n,decisionNames:p,visibleRoots:u,children:f,unfinishedBlockers:m,blockerIds:y,ancestry:x=>{let h=[],E=new Set,b=x;for(;b!=null&&o.has(b)&&!E.has(b);)E.add(b),h.unshift(b),b=o.get(b).parent_phase_id;return h},allChildren:g,jsonRoots:S,archivedRoots:l,shipped:x=>_.get(x)||null}}var Be=120;function Xs(t){return t.replace(/\s*\((?:see\s+)?\[\[[^\]]+\]\](?:\s*(?:,|and)\s*\[\[[^\]]+\]\])*\s*\)/gi,"").replace(/\[\[([^\]]+)\]\]/g,(e,n)=>n.replace(/_/g," ").trim()).replace(/\s{2,}/g," ").replace(/\s+([.,;:!?])/g,"$1").trim()}function En(t){if(!t)return null;let e=[];for(let o of t.split(`
191
- `)){let a=o.trim();if(!a){if(e.length)break;continue}e.push(a)}if(!e.length)return null;let n=Xs(e.join(" ").replace(/\s+/g," ")),s=null,i=/[.?!](?=\s|$)/g,r;for(;(r=i.exec(n))!==null&&(s=n.slice(0,r.index+1),!(s.length>=40)););if(s||(s=n),s.length>Be){let o=s.lastIndexOf(" ",Be-1);s=s.slice(0,o>60?o:Be-1).trimEnd()+"\u2026"}return s}var Ys=8;function un(){let t=he();if(!t||t.length===0)return[];let e=t.slice(-Ys).reverse(),n=["","---","","## Recently shipped",""];for(let i of e)n.push(`- ${i.date} \u2014 ${i.title}${i.commit?` \`${i.commit}\``:""}`);let s=t.length-e.length;return n.push("",s>0?`_${t.length} phases shipped in total; ${s} earlier one${s===1?"":"s"} not listed._`:`_${t.length} phase${t.length===1?"":"s"} shipped in total._`),n}function fn(t){let e=t.doneCount,n=t.total;if(!n)return"_No phases yet._";let s=Math.round(e/n*10);return`**${e} of ${n} done** \`${"\u2588".repeat(s)}${"\u2591".repeat(10-s)}\``}function zs(t,e){let n=[ws(e.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(e.migratedFrom&&(n.push(" The ROADMAP.md that was here before cairn adopted this file is kept at:"),n.push(` migrated-from=${e.migratedFrom}`)),n.push("-->","","# Roadmap",""),t==null)return n.push("_No active plan right now._"),n.push(...un()),n;let{cursor:s,decisionNames:i,visibleRoots:r,children:o,unfinishedBlockers:a}=t;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let c=(l,p)=>{let u=" ".repeat(p),d=l.phase_number!=null?`Phase ${l.phase_number}: ${l.text}`:l.text,m=Us[l.status]||l.status,y=a(l.id);y.length&&(m+=` \xB7 waiting on ${y.join(", ")}`),s===l.id&&(m+=" \xB7 \u2190 current"),n.push(`${u}- [${Ws(l.status)}] **${d}** \u2014 ${m}`);let f=En(l.body);f&&n.push(`${u} ${f}`);for(let g of o(l.id))c(g,p+1)};for(let l of i){let p=r.get(l);if(p){n.push(""),l===H?n.push("## Active plan","",fn(p)):n.push(`## ${l}`,"",fn(p)),n.push("");for(let u of p.visible)c(u,0)}}return n.push(...un()),n}function Sn(t,e,n,s){let i=n.get(`phase-${t.id}`),r=En(t.body),o=Ps(t.body),a=e.blockerIds(t.id),c={id:`phase-${t.id}`};t.phase_number!=null&&(c.number=t.phase_number),c.title=t.text,c.status=Hs.has(t.status)?t.status:"open";let l=t.status==="done"?e.shipped(t.id):null;if(l&&(c.shipped=l.shipped,l.commit&&(c.commit=l.commit)),r&&(c.goal=r),a.length&&(c.blocked_by=a),!s){let p=[],u=d=>{for(let m of e.allChildren(d))p.push(Sn(m,e,n,!0)),u(m.id)};u(t.id),p.length&&(c.slices=p)}return t.body&&t.body.trim()&&(c.detail=t.body.trim()),o.length&&(c.links=o),{...c,...hn(i,As)}}function Ks(t,e){let n=gn(e.previous?.phases),s=[];for(let p of t?.decisionNames??[]){let u=t.jsonRoots.get(p);if(u)for(let d of u)s.push(Sn(d,t,n,!1))}let i=(t?.archivedRoots??[]).map(p=>{let u=t.shipped(p.id),d={id:`phase-${p.id}`,title:p.text,status:"done"};return u&&(d.shipped=u.shipped,u.commit&&(d.commit=u.commit)),d}),r=new Set(s.map(p=>p.id)),o=t?.cursor!=null?t.ancestry(t.cursor):[],a=o.length?`phase-${o[0]}`:null,c={format:Je,provider:we,generated:e.generated,title:`${j.basename($())} roadmap`,cursor:t?.cursor!=null?`phase-${t.cursor}`:null,cursor_phase:a!==null&&r.has(a)?a:null,cursor_path:o.length?o.map(p=>`phase-${p}`):null};e.migratedFrom&&(c.migrated_from=e.migratedFrom,e.migrated&&(c.migrated=e.migrated)),c.phases=s,c.shipped_phases=i;let l={...c,...hn(e.previous,Is)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(l,null,2),"```","","</details>"]}function Ge(t,e={}){let n={generated:e.generated||_n(),migratedFrom:e.migratedFrom||null,migrated:e.migrated||null,previous:e.previous&&typeof e.previous=="object"?e.previous:null},s=Js(t);return s==null&&!e.allowEmpty?null:[...zs(s,n),...Ks(s,n)].join(`
190
+ `);if(n===-1)return null;let s=t.indexOf("-->",n),i=s===-1?t.slice(0,512):t.slice(n,s),r=/^\s*migrated-from=(.+?)\s*$/m.exec(i);return r?r[1]:null}var Cs=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,js=["decision","knowledge","experience","preference"];function Fs(t){let e=F.relative($(),t).split(F.sep).join("/");return!e||e.startsWith("..")||F.isAbsolute(e)?null:e}function Ds(t){let e=F.join($(),".cairn","memory");return[F.join(e,`${t}.md`),...js.map(n=>F.join(e,`${n}_${t}.md`))]}function Ps(t){if(!t)return[];let e=[],n=new Set;for(let s of String(t).matchAll(Cs)){let i=s[1].trim();if(!(!i||n.has(i))){n.add(i);for(let r of Ds(i)){if(!J.existsSync(r))continue;let o=Fs(r);o&&e.push({kind:"memory",title:i,path:o});break}}}return e}var Us={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Hs=new Set(["open","in_progress","done","blocked","stale"]);function Ws(t){return t==="done"?"x":t==="in_progress"?"~":t==="blocked"?"!":" "}function yn(){return F.join($(),"ROADMAP.md")}function _n(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function qs(t){try{return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Bs(t){let e=new Map,n;try{n=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let s of n)e.has(s.blocked_id)||e.set(s.blocked_id,new Set),e.get(s.blocked_id).add(s.blocker_id);return e}function Gs(t,e){let n=t.decision_name??"",s=e.decision_name??"";return n!==s?n<s?-1:1:t.phase_number!=null&&e.phase_number!=null?t.phase_number-e.phase_number:t.position-e.position||t.id-e.id}function Js(t){let e;try{e=t.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(e.filter(x=>x.status!=="stale").length===0)return null;let n=qs(t),s=Bs(t),i=new Map(e.map(x=>[x.id,x.status])),r=new Map(e.map(x=>[x.id,x.phase_number!=null?`Phase ${x.phase_number}`:x.text])),o=new Map(e.map(x=>[x.id,x])),a=new Map;for(let x of e){let h=x.parent_phase_id??"root";a.has(h)||a.set(h,[]),a.get(h).push(x)}for(let x of a.values())x.sort(Gs);let c=new Map,l=[];for(let x of a.get("root")||[])if(x.decision_name!=null){if(x.decision_name===H&&x.phase_number==null){x.status==="done"&&l.push(x);continue}c.has(x.decision_name)||c.set(x.decision_name,[]),c.get(x.decision_name).push(x)}let p=[...c.keys()].sort((x,h)=>x===H?-1:h===H?1:x<h?-1:x>h?1:0),u=new Map,d=!1;for(let x of p){let h=c.get(x).filter(b=>b.status!=="stale"),E=x===H?h.filter(b=>b.status!=="done"):h;E.length!==0&&(d=!0,u.set(x,{visible:E,doneCount:h.filter(b=>b.status==="done").length,total:h.length}))}if(!d)return null;let m=x=>{let h=s.get(x);return h?[...h].filter(E=>i.get(E)!=="done").map(E=>r.get(E)||`#${E}`):[]},y=x=>{let h=s.get(x);return h?[...h].filter(E=>o.has(E)).sort((E,b)=>E-b).map(E=>`phase-${E}`):[]},f=x=>(a.get(x)||[]).filter(h=>h.status!=="stale"),g=x=>a.get(x)||[],S=new Map(p.map(x=>[x,c.get(x)])),_=new Map;for(let x of he()||[])x.id!=null&&_.set(x.id,{shipped:x.date,commit:x.commit||null});return{cursor:n,decisionNames:p,visibleRoots:u,children:f,unfinishedBlockers:m,blockerIds:y,ancestry:x=>{let h=[],E=new Set,b=x;for(;b!=null&&o.has(b)&&!E.has(b);)E.add(b),h.unshift(b),b=o.get(b).parent_phase_id;return h},allChildren:g,jsonRoots:S,archivedRoots:l,shipped:x=>_.get(x)||null}}var Be=120;function Xs(t){return t.replace(/\s*\((?:see\s+)?\[\[[^\]]+\]\](?:\s*(?:,|and)\s*\[\[[^\]]+\]\])*\s*\)/gi,"").replace(/\[\[([^\]]+)\]\]/g,(e,n)=>n.replace(/_/g," ").trim()).replace(/\s{2,}/g," ").replace(/\s+([.,;:!?])/g,"$1").trim()}function En(t){if(!t)return null;let e=[];for(let o of t.split(`
191
+ `)){let a=o.trim();if(!a){if(e.length)break;continue}e.push(a)}if(!e.length)return null;let n=Xs(e.join(" ").replace(/\s+/g," ")),s=null,i=/[.?!](?=\s|$)/g,r;for(;(r=i.exec(n))!==null&&(s=n.slice(0,r.index+1),!(s.length>=40)););if(s||(s=n),s.length>Be){let o=s.lastIndexOf(" ",Be-1);s=s.slice(0,o>60?o:Be-1).trimEnd()+"\u2026"}return s}var Ys=8;function un(){let t=he();if(!t||t.length===0)return[];let e=t.slice(-Ys).reverse(),n=["","---","","## Recently shipped",""];for(let i of e)n.push(`- ${i.date} \u2014 ${i.title}${i.commit?` \`${i.commit}\``:""}`);let s=t.length-e.length;return n.push("",s>0?`_${t.length} phases shipped in total; ${s} earlier one${s===1?"":"s"} not listed._`:`_${t.length} phase${t.length===1?"":"s"} shipped in total._`),n}function fn(t){let e=t.doneCount,n=t.total;if(!n)return"_No phases yet._";let s=Math.round(e/n*10);return`**${e} of ${n} done** \`${"\u2588".repeat(s)}${"\u2591".repeat(10-s)}\``}function zs(t,e){let n=[ws(e.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(e.migratedFrom&&(n.push(" The ROADMAP.md that was here before cairn adopted this file is kept at:"),n.push(` migrated-from=${e.migratedFrom}`)),n.push("-->","","# Roadmap",""),t==null)return n.push("_No active plan right now._"),n.push(...un()),n;let{cursor:s,decisionNames:i,visibleRoots:r,children:o,unfinishedBlockers:a}=t;n.push("Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked.");let c=(l,p)=>{let u=" ".repeat(p),d=l.phase_number!=null?`Phase ${l.phase_number}: ${l.text}`:l.text,m=Us[l.status]||l.status,y=a(l.id);y.length&&(m+=` \xB7 waiting on ${y.join(", ")}`),s===l.id&&(m+=" \xB7 \u2190 current"),n.push(`${u}- [${Ws(l.status)}] **${d}** \u2014 ${m}`);let f=En(l.body);f&&n.push(`${u} ${f}`);for(let g of o(l.id))c(g,p+1)};for(let l of i){let p=r.get(l);if(p){n.push(""),l===H?n.push("## Active plan","",fn(p)):n.push(`## ${l}`,"",fn(p)),n.push("");for(let u of p.visible)c(u,0)}}return n.push(...un()),n}function Sn(t,e,n,s){let i=n.get(`phase-${t.id}`),r=En(t.body),o=Ps(t.body),a=e.blockerIds(t.id),c={id:`phase-${t.id}`};t.phase_number!=null&&(c.number=t.phase_number),c.title=t.text,c.status=Hs.has(t.status)?t.status:"open";let l=t.status==="done"?e.shipped(t.id):null;if(l&&(c.shipped=l.shipped,l.commit&&(c.commit=l.commit)),r&&(c.goal=r),a.length&&(c.blocked_by=a),!s){let p=[],u=d=>{for(let m of e.allChildren(d))p.push(Sn(m,e,n,!0)),u(m.id)};u(t.id),p.length&&(c.slices=p)}return t.body&&t.body.trim()&&(c.detail=t.body.trim()),o.length&&(c.links=o),{...c,...hn(i,As)}}function Ks(t,e){let n=gn(e.previous?.phases),s=[];for(let p of t?.decisionNames??[]){let u=t.jsonRoots.get(p);if(u)for(let d of u)s.push(Sn(d,t,n,!1))}let i=(t?.archivedRoots??[]).map(p=>{let u=t.shipped(p.id),d={id:`phase-${p.id}`,title:p.text,status:"done"};return u&&(d.shipped=u.shipped,u.commit&&(d.commit=u.commit)),d}),r=new Set(s.map(p=>p.id)),o=t?.cursor!=null?t.ancestry(t.cursor):[],a=o.length?`phase-${o[0]}`:null,c={format:Je,provider:we,generated:e.generated,title:`${F.basename($())} roadmap`,cursor:t?.cursor!=null?`phase-${t.cursor}`:null,cursor_phase:a!==null&&r.has(a)?a:null,cursor_path:o.length?o.map(p=>`phase-${p}`):null};e.migratedFrom&&(c.migrated_from=e.migratedFrom,e.migrated&&(c.migrated=e.migrated)),c.phases=s,c.shipped_phases=i;let l={...c,...hn(e.previous,Is)};return["","<details>","<summary>Machine-readable plan (roadmap/1)</summary>","","```roadmap-json",JSON.stringify(l,null,2),"```","","</details>"]}function Ge(t,e={}){let n={generated:e.generated||_n(),migratedFrom:e.migratedFrom||null,migrated:e.migrated||null,previous:e.previous&&typeof e.previous=="object"?e.previous:null},s=Js(t);return s==null&&!e.allowEmpty?null:[...zs(s,n),...Ks(s,n)].join(`
192
192
  `)+`
193
- `}function Vs(t){let e=j.join(t,"ROADMAP.bak.md");if(!J.existsSync(e))return e;for(let n=2;n<=1e3;n++){let s=j.join(t,`ROADMAP.bak.${n}.md`);if(!J.existsSync(s))return s}return null}function Qs(t,e){let n=j.join(j.dirname(t),`.${j.basename(t)}.${process.pid}.tmp`);try{J.writeFileSync(n,e,"utf8"),J.renameSync(n,t)}catch(s){try{J.unlinkSync(n)}catch{}throw s}}function te(t,e,n={}){return{written:!1,reason:t,warning:`[cairn] ROADMAP.md was not updated \u2014 ${t}`,path:e,...n}}function Xe(t){let e=yn(),n=null;if(J.existsSync(e))try{n=J.readFileSync(e,"utf8")}catch(f){return te(`the existing file could not be read (${f.message})`,e)}let s=Ls(n),i=n!=null&&(s?.provider===we||n.startsWith(Rs));if(n!=null&&s&&!i)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:e};let r=i?mn(n):null,o=i?Ms(n):null,a=i&&typeof r?.migrated=="string"?r.migrated:null,c=typeof r?.generated=="string"?r.generated:null,l=!1,p=Ge(t,{generated:c||"1970-01-01T00:00:00Z",migratedFrom:o,migrated:a,previous:r});if(p==null){if(!i)return{written:!1,reason:"empty roadmap \u2014 no file written",path:e};l=!0,p=Ge(t,{generated:c||"1970-01-01T00:00:00Z",migratedFrom:o,migrated:a,previous:r,allowEmpty:!0})}if(i&&n===p)return{written:!1,unchanged:!0,path:e,bytes:Buffer.byteLength(p)};let u=_n(),d=null,m=null;if(n!=null&&!i){let f=Vs(j.dirname(e));if(f==null)return te("no free ROADMAP.bak name was available to preserve the existing file",e);try{J.renameSync(e,f)}catch(g){return te(`the existing file could not be preserved (${g.message})`,e)}d=j.basename(f),m=f,o=d,a=u}p=Ge(t,{generated:u,migratedFrom:o,migrated:a,previous:r,allowEmpty:l});try{Qs(e,p)}catch(f){if(m)try{return J.renameSync(m,e),te(`write failed (${f.message}); the original ROADMAP.md was restored`,e)}catch{return te(`write failed (${f.message}); the original ROADMAP.md is at ${d} and must be restored by hand`,e,{backed_up_to:d})}return te(`write failed (${f.message})`,e)}let y={written:!0,path:e,bytes:Buffer.byteLength(p),format:Je,generated:u};return d&&(y.backed_up_to=d,y.warning=`[cairn] The existing ROADMAP.md was not cairn-generated. It has been preserved as ${d}, and ROADMAP.md is now generated from cairn's roadmap.`),y}function Zs(t){try{return Xe(t)}catch(e){process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${e.message}
193
+ `}function Vs(t){let e=F.join(t,"ROADMAP.bak.md");if(!J.existsSync(e))return e;for(let n=2;n<=1e3;n++){let s=F.join(t,`ROADMAP.bak.${n}.md`);if(!J.existsSync(s))return s}return null}function Qs(t,e){let n=F.join(F.dirname(t),`.${F.basename(t)}.${process.pid}.tmp`);try{J.writeFileSync(n,e,"utf8"),J.renameSync(n,t)}catch(s){try{J.unlinkSync(n)}catch{}throw s}}function te(t,e,n={}){return{written:!1,reason:t,warning:`[cairn] ROADMAP.md was not updated \u2014 ${t}`,path:e,...n}}function Xe(t){let e=yn(),n=null;if(J.existsSync(e))try{n=J.readFileSync(e,"utf8")}catch(f){return te(`the existing file could not be read (${f.message})`,e)}let s=Ls(n),i=n!=null&&(s?.provider===we||n.startsWith(Rs));if(n!=null&&s&&!i)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:e};let r=i?mn(n):null,o=i?Ms(n):null,a=i&&typeof r?.migrated=="string"?r.migrated:null,c=typeof r?.generated=="string"?r.generated:null,l=!1,p=Ge(t,{generated:c||"1970-01-01T00:00:00Z",migratedFrom:o,migrated:a,previous:r});if(p==null){if(!i)return{written:!1,reason:"empty roadmap \u2014 no file written",path:e};l=!0,p=Ge(t,{generated:c||"1970-01-01T00:00:00Z",migratedFrom:o,migrated:a,previous:r,allowEmpty:!0})}if(i&&n===p)return{written:!1,unchanged:!0,path:e,bytes:Buffer.byteLength(p)};let u=_n(),d=null,m=null;if(n!=null&&!i){let f=Vs(F.dirname(e));if(f==null)return te("no free ROADMAP.bak name was available to preserve the existing file",e);try{J.renameSync(e,f)}catch(g){return te(`the existing file could not be preserved (${g.message})`,e)}d=F.basename(f),m=f,o=d,a=u}p=Ge(t,{generated:u,migratedFrom:o,migrated:a,previous:r,allowEmpty:l});try{Qs(e,p)}catch(f){if(m)try{return J.renameSync(m,e),te(`write failed (${f.message}); the original ROADMAP.md was restored`,e)}catch{return te(`write failed (${f.message}); the original ROADMAP.md is at ${d} and must be restored by hand`,e,{backed_up_to:d})}return te(`write failed (${f.message})`,e)}let y={written:!0,path:e,bytes:Buffer.byteLength(p),format:Je,generated:u};return d&&(y.backed_up_to=d,y.warning=`[cairn] The existing ROADMAP.md was not cairn-generated. It has been preserved as ${d}, and ROADMAP.md is now generated from cairn's roadmap.`),y}function Zs(t){try{return Xe(t)}catch(e){process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${e.message}
194
194
  `);let n=null;try{n=yn()}catch{}return te(`publish failed (${e.message})`,n)}}function xn(t){let e=Zs(t);return e&&e.warning?e.warning:null}var Ye={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},bn=new Set(["open","in_progress","done","blocked","stale"]),ei=new Set(["open","in_progress"]);function Oe(t){return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}function ti(t,e){t.prepare("INSERT INTO main.roadmap_cursor (singleton, phase_id) VALUES (1, ?) ON CONFLICT(singleton) DO UPDATE SET phase_id = excluded.phase_id").run(e)}function Tn(t){t.prepare("DELETE FROM main.roadmap_cursor WHERE singleton = 1").run()}function ni(t,e){let n=t.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),s=t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),i=n.get(e);for(;i&&i.parent_phase_id!=null;)s.run(i.parent_phase_id),i=n.get(i.parent_phase_id)}function ze(t,e){ti(t,e),t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(e),ni(t,e)}function On(t){let e=t.filter(n=>n.status!=="stale");return e.length===0?"stale":e.every(n=>n.status==="done")?"done":e.some(n=>n.status==="in_progress")?"in_progress":e.some(n=>n.status==="blocked")&&!e.some(n=>n.status==="in_progress")?"blocked":"open"}function Ke(t){let e=new Map,n;try{n=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let s of n)e.has(s.blocked_id)||e.set(s.blocked_id,new Set),e.get(s.blocked_id).add(s.blocker_id);return e}function Ve(t,e,n){let s=e.get(t);if(!s||s.size===0)return[];let i=[];for(let r of s)n.get(r)!=="done"&&i.push(r);return i}function wn(t,e){let n=t.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(u=>[u.id,u.status])),i=Ke(t),r=new Map;for(let u of n){let d=u.parent_phase_id??`root:${u.decision_name}`;r.has(d)||r.set(d,[]),r.get(d).push(u)}for(let u of r.values())u.sort((d,m)=>d.phase_number!=null&&m.phase_number!=null?d.phase_number-m.phase_number:d.position-m.position||d.id-m.id);let o=[],a=new Set;for(let u of n){if(u.parent_phase_id!=null||a.has(u.decision_name))continue;a.add(u.decision_name);let d=[],m=r.get(`root:${u.decision_name}`)||[];for(let y=m.length-1;y>=0;y--)d.push(m[y]);for(;d.length;){let y=d.pop();o.push(y);let f=r.get(y.id)||[];for(let g=f.length-1;g>=0;g--)d.push(f[g])}}let c=u=>ei.has(u.status),l=[],p=0;if(e!=null){let u=o.findIndex(d=>d.id===e);p=u===-1?0:u+1}for(let u=p;u<o.length;u++){let d=o[u];if(!c(d))continue;let m=Ve(d.id,i,s);if(m.length>0){l.push({id:d.id,blockers:m});continue}let y=e!=null?n.find(S=>S.id===e):null,f=y&&y.decision_name!==d.decision_name,g;return e==null?g=`First actionable phase: ${d.decision_name??"(sub-task)"} / Phase ${d.phase_number??"\xB7"}.`:f?g=`Previous decision "${y.decision_name}" is complete \u2014 advanced to "${d.decision_name}".`:g=`Next open phase in "${d.decision_name??"(sub-task)"}" after id ${e}.`,l.length>0&&(g+=` Skipped ${l.length} dep-blocked phase(s): ${l.map(S=>`id ${S.id} (blocked by ${S.blockers.join(",")})`).join("; ")}.`),{id:d.id,reason:g,skipped_blocked:l}}return l.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${l.map(u=>`id ${u.id} (blocked by ${u.blockers.join(",")})`).join("; ")}.`,skipped_blocked:l}:{id:null,reason:"No actionable phases remaining."}}function si(t){let e=t.filter(s=>s.status!=="stale");return{done:e.filter(s=>s.status==="done").length,total:e.length}}function ii(t,{compact:e=!1,showStale:n=!1,decisionFilter:s=null}={}){let i=t.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, status, position FROM main.roadmap_phases ORDER BY decision_name, phase_number, position, id").all();if(i.length===0)return"(no roadmap \u2014 add a `## Phases` section to .cairn/roadmap.md)";let r=Oe(t),o=Ke(t),a=new Map(i.map(f=>[f.id,f.status])),c=new Map;for(let f of i){let g=f.parent_phase_id??"root";c.has(g)||c.set(g,[]),c.get(g).push(f)}for(let f of c.values())f.sort((g,S)=>g.decision_name&&S.decision_name&&g.decision_name!==S.decision_name?g.decision_name.localeCompare(S.decision_name):g.phase_number!=null&&S.phase_number!=null?g.phase_number-S.phase_number:g.position-S.position||g.id-S.id);let l=c.get("root")||[],p=new Map;for(let f of l)f.decision_name!=null&&(f.decision_name===H&&f.phase_number==null||s!=null&&f.decision_name!==s||(p.has(f.decision_name)||p.set(f.decision_name,[]),p.get(f.decision_name).push(f)));if(s!=null&&p.size===0)return`(no decision named "${s}" \u2014 try cairn_roadmap action=tree without filter to list all)`;let u=0,d=[],m=f=>{let g=Ve(f.id,o,a);return g.length>0?` blocked-by:${g.join(",")}`:""},y=(f,g)=>{let S=c.get(f)||[];for(let _ of S){if(!n&&_.status==="stale"){u++;continue}let w=Ye[_.status]||"[?]",R=r===_.id?" \u2192":"",x=_.phase_number!=null?`Phase ${_.phase_number}: ${_.text}`:_.text,h=e?"":` (id:${_.id})`;d.push(`${" ".repeat(g)}${w}${R} ${x}${h}${m(_)}`),y(_.id,g+1)}};for(let[f,g]of p){if(!n&&g.every(h=>h.status==="stale")){u+=g.length;continue}let S=On(g),{done:_,total:w}=si(g),R=w>0?` (${_}/${w} done)`:"",x=f===H?"Roadmap":`Decision: ${f}`;d.push(`${Ye[S]} ${x}${R}`);for(let h of g){if(!n&&h.status==="stale"){u++;continue}if(f===H&&h.status==="done")continue;let E=Ye[h.status]||"[?]",b=r===h.id?" \u2192":"",k=`Phase ${h.phase_number}: ${h.text}`,A=e?"":` (id:${h.id})`;d.push(` ${E}${b} ${k}${A}${m(h)}`),y(h.id,2)}}return u>0&&d.push(`(${u} stale phase${u===1?"":"s"} hidden \u2014 pass show_stale: true to view)`),d.join(`
195
195
  `)}function Rn(t){let e=t.prepare("SELECT decision_name, status FROM main.roadmap_phases WHERE parent_phase_id IS NULL").all(),n=new Map;for(let o of e)n.has(o.decision_name)||n.set(o.decision_name,[]),n.get(o.decision_name).push(o);let s=0;for(let o of n.values()){let a=On(o);(a==="in_progress"||a==="open")&&s++}let i=Oe(t),r=null;if(i!=null){let o=t.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text FROM main.roadmap_phases WHERE id = ?").get(i);if(o){let a=o.decision_name,c=o.phase_number,l=o;for(;l.parent_phase_id!=null&&(l=t.prepare("SELECT id, decision_name, phase_number, parent_phase_id FROM main.roadmap_phases WHERE id = ?").get(l.parent_phase_id),!!l);)a=l.decision_name??a,c=l.phase_number??c;r=o.phase_number!=null?`${o.decision_name} / Phase ${o.phase_number} (${o.text})`:`${a} / Phase ${c} \u2192 sub: ${o.text}`}}return{decisions_total:n.size,decisions_in_flight:s,cursor_phase_id:i,current:r}}function ri(t,e,n){if(e===n)throw new Error("A phase cannot block itself");if(t.prepare("SELECT id FROM main.roadmap_phases WHERE id IN (?, ?)").all(e,n).length!==2)throw new Error(`One or both phases not found (from=${e}, to=${n})`);let i=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all(),r=new Map;for(let c of i)r.has(c.blocked_id)||r.set(c.blocked_id,[]),r.get(c.blocked_id).push(c.blocker_id);let o=[e],a=new Set;for(;o.length;){let c=o.pop();if(c===n)throw new Error(`Cycle: phase ${n} already (transitively) blocks phase ${e}`);if(!a.has(c)){a.add(c);for(let l of r.get(c)||[])o.push(l)}}return t.prepare("INSERT OR IGNORE INTO main.roadmap_phase_deps (blocker_id, blocked_id) VALUES (?, ?)").run(e,n),{added:{blocker_id:e,blocked_id:n}}}function oi(t,e,n){if(t.prepare("DELETE FROM main.roadmap_phase_deps WHERE blocker_id = ? AND blocked_id = ?").run(e,n).changes===0)throw new Error(`No dep edge ${e} \u2192 ${n}`);return{removed:{blocker_id:e,blocked_id:n}}}function ai(t,e){if(e!=null){let s=t.prepare("SELECT blocker_id FROM main.roadmap_phase_deps WHERE blocked_id = ?").all(e).map(r=>r.blocker_id),i=t.prepare("SELECT blocked_id FROM main.roadmap_phase_deps WHERE blocker_id = ?").all(e).map(r=>r.blocked_id);return{phase_id:e,blocked_by:s,blocks:i}}return{edges:t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps ORDER BY blocker_id, blocked_id").all()}}function Re(t){try{return cn(t)}catch(e){return process.stderr.write(`[cairn] roadmap.md sync skipped: ${e.message}
196
196
  `),null}}function ge(t,e={}){let n=e.action??"tree",{id:s,status:i,parent:r,text:o}=e;switch(n){case"tree":{Re(t);let a=e.show_stale===!0||e.hide_stale===!1,c=ii(t,{compact:e.compact===!0,showStale:a,decisionFilter:e.decision??null}),l=Rn(t),p=[c,"",l.current?`Current: ${l.current}`:"(no cursor set \u2014 call cairn_roadmap focus <id>)"];l.decisions_in_flight>1&&p.push(`In flight: ${l.decisions_in_flight} decisions`);let u=dn();return u>0&&p.push(`Shipped: ${u} (see .cairn/roadmap_completed.md)`),{content:[{type:"text",text:p.join(`
197
197
  `)}]}}case"status":return Re(t),{content:[{type:"text",text:JSON.stringify(Rn(t),null,2)}]};case"sync":{let a=Re(t);return{content:[{type:"text",text:JSON.stringify(a==null?{synced:!1,reason:"No .cairn/roadmap.md with a `## Phases` section."}:{synced:!0,...a},null,2)}]}}case"publish":return Re(t),{content:[{type:"text",text:JSON.stringify(Xe(t),null,2)}]};case"focus":{if(!s)throw new Error("id is required for focus");let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(s);if(!a)throw new Error(`No phase with id ${s}`);if(a.status==="stale")throw new Error(`Phase ${s} is stale \u2014 restore by re-adding it to .cairn/roadmap.md`);let c=Ke(t),l=new Map(t.prepare("SELECT id, status FROM main.roadmap_phases").all().map(m=>[m.id,m.status])),p=Ve(s,c,l);ze(t,s);let u=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(s),d=p.length>0?`Focused phase ${s} despite unfinished blockers: ${p.join(", ")}.`:`Focused phase ${s}.`;return{content:[{type:"text",text:JSON.stringify({focused:u,reason:d,blocked_by:p},null,2)}]}}case"next":{let a=Oe(t),c=wn(t,a);if(c.id==null)return Tn(t),{content:[{type:"text",text:JSON.stringify({cursor:null,reason:c.reason,skipped_blocked:c.skipped_blocked??[]},null,2)}]};ze(t,c.id);let l=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(c.id);return{content:[{type:"text",text:JSON.stringify({cursor:c.id,phase:l,reason:c.reason,skipped_blocked:c.skipped_blocked??[]},null,2)}]}}case"set_status":{if(!s)throw new Error("id is required for set_status");if(!i)throw new Error("status is required for set_status");if(!bn.has(i))throw new Error(`Invalid status "${i}". Use: ${[...bn].join(", ")}`);let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(s);if(!a)throw new Error(`No phase with id ${s}`);let c=i==="done"?"datetime('now')":"NULL";t.prepare(`UPDATE main.roadmap_phases SET status = ?, resolved_at = ${c} WHERE id = ?`).run(i,s);let l=null,p=null;if(i==="done"&&Oe(t)===s){let y=wn(t,s);y.id!=null?(ze(t,y.id),l=y.id,p=y.reason):(Tn(t),p=y.reason)}let u=null,d=a.decision_name===H&&a.phase_number!=null;if(i==="done"&&d)try{let y=pn(a.phase_number,a.text,{phaseId:a.id}),f=ln(a.phase_number).removed;u={logged:y.logged,pruned_from_roadmap_md:f}}catch(y){process.stderr.write(`[cairn] ship bookkeeping failed for phase ${s}: ${y.message}
198
- `)}let m=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(s);return{content:[{type:"text",text:JSON.stringify({updated:m,cursor_advanced_to:l,reason:p,completed:u},null,2)}]}}case"add":{if(!o)throw new Error("text is required for add");if(r==null)throw new Error("parent (phase_id) is required \u2014 ad-hoc sub-tasks must hang under an existing phase. For freestanding work use cairn_todos add.");let a=t.prepare("SELECT id, decision_name FROM main.roadmap_phases WHERE id = ?").get(r);if(!a)throw new Error(`Parent phase ${r} not found`);let c=t.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(r).n,l=t.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)").run(r,o,"open",c),p=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(l.lastInsertRowid);return{content:[{type:"text",text:JSON.stringify({added:p,inherits_decision_from:a.decision_name},null,2)}]}}case"deps_add":{let a=e.from,c=e.to;if(a==null||c==null)throw new Error("deps_add requires from (blocker phase id) and to (blocked phase id)");return{content:[{type:"text",text:JSON.stringify(ri(t,a,c),null,2)}]}}case"deps_remove":{let a=e.from,c=e.to;if(a==null||c==null)throw new Error("deps_remove requires from and to");return{content:[{type:"text",text:JSON.stringify(oi(t,a,c),null,2)}]}}case"deps_list":return{content:[{type:"text",text:JSON.stringify(ai(t,e.id??null),null,2)}]};default:throw new Error(`Unknown action: "${n}". Use tree, status, sync, publish, focus, next, set_status, add, deps_add, deps_remove, or deps_list.`)}}var Qe=li(ci);async function Nn(t){let e=F(),n=Jt(e);Xt(e,n)>0&&Pe(e,n);let i=Ne.join(e,"session.json");if(!K.existsSync(i))return{content:[{type:"text",text:JSON.stringify({error:"No checkpoint found. Run cairn_maintain to index the project, then cairn_checkpoint to save session state."},null,2)}]};let r=JSON.parse(K.readFileSync(i,"utf8"));if(typeof r.notes=="string")try{r.notes=JSON.parse(r.notes)}catch{r.notes=[]}Array.isArray(r.notes)||(r.notes=[]);let o=r.notes.filter(T=>typeof T=="string"&&T.includes("git-snapshot:"));if(o.length>0){let T=Ne.join(e,"session.bak.json");K.existsSync(T)||K.copyFileSync(i,T),r.notes=r.notes.filter(D=>!(typeof D=="string"&&D.includes("git-snapshot:"))),K.writeFileSync(i,JSON.stringify(r,null,2),"utf8")}let a=null,c=r.mtime_snapshot||{};Object.keys(c).length>0&&(a=sn(c,$()),a&&(rn(t,e,r,n,a.oldRoot,a.newRoot),Pe(e,n)));let l=r.mtime_snapshot||{},p=[];for(let[T,D]of Object.entries(l))try{K.statSync(T).mtimeMs!==D&&p.push({file:T,change:"modified"})}catch{p.push({file:T,change:"deleted"})}try{let T=await B($());for(let D of T)D in l||p.push({file:D,change:"new"})}catch{}let u=p.filter(T=>T.change==="deleted").map(T=>T.file);if(u.length>0){let T=t.prepare("DELETE FROM main.files WHERE path = ?");for(let D of u)T.run(D)}let d=p.filter(T=>T.change!=="deleted").map(T=>T.file),m={files_reindexed:0,files_unchanged:0};d.length>0&&(await xe(t,d),m.files_reindexed=d.length),m.files_unchanged=Object.keys(l).length-p.filter(T=>T.change!=="new").length;let y=ne(),f=tn(y),g=W(y),S=g.length>0?g.map(T=>`[${T.type}] ${T.name} \u2014 ${T.description}`).join(`
198
+ `)}let m=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(s);return{content:[{type:"text",text:JSON.stringify({updated:m,cursor_advanced_to:l,reason:p,completed:u},null,2)}]}}case"add":{if(!o)throw new Error("text is required for add");if(r==null)throw new Error("parent (phase_id) is required \u2014 ad-hoc sub-tasks must hang under an existing phase. For freestanding work use cairn_todos add.");let a=t.prepare("SELECT id, decision_name FROM main.roadmap_phases WHERE id = ?").get(r);if(!a)throw new Error(`Parent phase ${r} not found`);let c=t.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(r).n,l=t.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)").run(r,o,"open",c),p=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(l.lastInsertRowid);return{content:[{type:"text",text:JSON.stringify({added:p,inherits_decision_from:a.decision_name},null,2)}]}}case"deps_add":{let a=e.from,c=e.to;if(a==null||c==null)throw new Error("deps_add requires from (blocker phase id) and to (blocked phase id)");return{content:[{type:"text",text:JSON.stringify(ri(t,a,c),null,2)}]}}case"deps_remove":{let a=e.from,c=e.to;if(a==null||c==null)throw new Error("deps_remove requires from and to");return{content:[{type:"text",text:JSON.stringify(oi(t,a,c),null,2)}]}}case"deps_list":return{content:[{type:"text",text:JSON.stringify(ai(t,e.id??null),null,2)}]};default:throw new Error(`Unknown action: "${n}". Use tree, status, sync, publish, focus, next, set_status, add, deps_add, deps_remove, or deps_list.`)}}var Qe=li(ci);async function Nn(t){let e=j(),n=Jt(e);Xt(e,n)>0&&Pe(e,n);let i=Ne.join(e,"session.json");if(!K.existsSync(i))return{content:[{type:"text",text:JSON.stringify({error:"No checkpoint found. Run cairn_maintain to index the project, then cairn_checkpoint to save session state."},null,2)}]};let r=JSON.parse(K.readFileSync(i,"utf8"));if(typeof r.notes=="string")try{r.notes=JSON.parse(r.notes)}catch{r.notes=[]}Array.isArray(r.notes)||(r.notes=[]);let o=r.notes.filter(T=>typeof T=="string"&&T.includes("git-snapshot:"));if(o.length>0){let T=Ne.join(e,"session.bak.json");K.existsSync(T)||K.copyFileSync(i,T),r.notes=r.notes.filter(D=>!(typeof D=="string"&&D.includes("git-snapshot:"))),K.writeFileSync(i,JSON.stringify(r,null,2),"utf8")}let a=null,c=r.mtime_snapshot||{};Object.keys(c).length>0&&(a=sn(c,$()),a&&(rn(t,e,r,n,a.oldRoot,a.newRoot),Pe(e,n)));let l=r.mtime_snapshot||{},p=[];for(let[T,D]of Object.entries(l))try{K.statSync(T).mtimeMs!==D&&p.push({file:T,change:"modified"})}catch{p.push({file:T,change:"deleted"})}try{let T=await B($());for(let D of T)D in l||p.push({file:D,change:"new"})}catch{}let u=p.filter(T=>T.change==="deleted").map(T=>T.file);if(u.length>0){let T=t.prepare("DELETE FROM main.files WHERE path = ?");for(let D of u)T.run(D)}let d=p.filter(T=>T.change!=="deleted").map(T=>T.file),m={files_reindexed:0,files_unchanged:0};d.length>0&&(await xe(t,d),m.files_reindexed=d.length),m.files_unchanged=Object.keys(l).length-p.filter(T=>T.change!=="new").length;let y=ne(),f=tn(y),g=W(y),S=g.length>0?g.map(T=>`[${T.type}] ${T.name} \u2014 ${T.description}`).join(`
199
199
  `):null,_=Ee(t),w=0;try{w=t.prepare("SELECT COUNT(*) AS n FROM main.todos WHERE status IN ('open', 'in_progress')").get().n}catch{}let R=null;try{let T=K.existsSync(ae()),D=JSON.parse(ge(t,{action:"status"}).content[0].text);if(T||D.decisions_total>0){let X=ge(t,{action:"tree",compact:!0}).content[0].text;R={file:Ne.relative($(),ae()).replace(/\\/g,"/"),current:D.current,decisions_in_flight:D.decisions_in_flight,tree:X}}}catch{}let x=R?R.current?`Roadmap (${R.file}) \u2014 current: ${R.current}`:`Roadmap (${R.file}) \u2014 no cursor set (call cairn_roadmap next)`:"",h=Wt($(),r),b=/^Auto-checkpoint\b/.test(r.message||"")&&r.last_manual_message||r.message,k=[`Last session: ${b}`,qt(h),a?`Project relocated from ${a.oldRoot} \u2192 repaired automatically.`:"",x,p.length>0?`${p.length} file(s) changed since checkpoint`:"No files changed since checkpoint",w>0?`${w} open todo(s)`:"",_.length>0?`${_.length} sub-index(es)`:""].filter(Boolean).join(" \xB7 "),A=null;try{K.readFileSync(Ne.join($(),".gitignore"),"utf8").split(`
200
200
  `).map(X=>X.trim()).some(X=>X===".cairn"||X===".cairn/"||X==="/.cairn"||X==="/.cairn/")||(A="Add .cairn/ to your .gitignore to avoid committing the index and session cache.")}catch{}let O={last_checkpoint:r.checkpoint_at,message:b,[v]:h.recorded?h.text:null,[`${v}_note`]:h.recorded?h.stale?`Written ${h.at} \u2014 ${h.commits} commit(s) have landed since, so treat it as possibly stale and verify before relying on it.`:`Written ${h.at}, current as of the last commit.`:"Nothing was recorded about what the last session would lose. This is not an all-clear: cairn cannot see what was never written down.",last_manual_checkpoint:r.last_manual_checkpoint_at??null,index_status:d.length>0||u.length>0?"incremental":"up_to_date",files_reindexed:m.files_reindexed,files_deleted:u.length,files_unchanged:Math.max(0,m.files_unchanged),sub_indexes_active:_.length,open_todos:w,changed_since_checkpoint:p.slice(0,20),active_files:r.active_files||[],notes:r.notes||[],resume_summary:k};A&&(O.gitignore_hint=A),R&&(O.roadmap=R),o.length>0&&(O.migrated=`Removed ${o.length} legacy git-snapshot note(s). Original session backed up to session.bak.json.`);let N=$(),le=new Date(r.checkpoint_at).toISOString(),[z,ye,rt]=await Promise.allSettled([Qe(`git log --oneline --after="${le}"`,{cwd:N,timeout:5e3}),Qe("git diff --name-only HEAD",{cwd:N,timeout:5e3}),Qe("git status --short",{cwd:N,timeout:5e3})]);if(z.status==="fulfilled"){let T=z.value.stdout.trim();T&&(O.commits_since_checkpoint=T.split(`
201
201
  `))}if(ye.status==="fulfilled"){let T=ye.value.stdout.trim();T&&(O.uncommitted_changes=T.split(`
@@ -239,4 +239,4 @@ Detects:
239
239
  - empty_class: class with no indexed members (stub or anemic model)
240
240
  - duplicate_class_name: same class name defined in multiple files
241
241
 
242
- Use to: get a project-wide bird's-eye view, triage code quality, find where to look first.`,inputSchema:{type:"object",properties:{repo:{type:"string",description:'Optional: scope to a specific repo/service by name (e.g. "elprint-component-service")'},paths:{type:"array",items:{type:"string"},description:"Optional: scope to specific file paths or directories (substring match)"},checks:{type:"array",items:{type:"string"},description:'Optional: limit to specific checks e.g. ["god_class","symmetry_gap"]'}}}},{name:"cairn_minify",description:"Minify a single source file and return compressed content. Strips comments and empty lines. Useful when passive hooks are not installed. Supports java, typescript, javascript, vue, python, sql.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to the file to minify"}},required:["file_path"]}},{name:"cairn_memo",description:'Persist and retrieve typed memories in .cairn/memory/. Use this \u2014 not the auto-memory system \u2014 for all project-level memories. Triggered by: "save cairn memory", "cairn remember", "build cairn memories", "save experience to cairn". Four types: preference (user working style \u2014 always loaded on resume), experience (transferable lessons learned \u2014 migrated between projects), decision (project-specific architectural choices), knowledge (project-specific codebase facts). Memories survive context loss and accumulate across sessions.',inputSchema:{type:"object",properties:{action:{type:"string",enum:["write","read","list","delete","reindex"],description:"write: save/update a memory | read: load a memory by name | list: show all memories with index | delete: remove a memory | reindex: repair MEMORY.md by restoring pointers to memory files it has lost (additive \u2014 never rewrites or removes an existing line; pass dry_run to preview)"},name:{type:"string",description:"Memory identifier (required for write/read/delete)"},type:{type:"string",enum:["preference","experience","decision","knowledge"],description:"Memory type (required for write)"},content:{type:"string",description:"Memory body \u2014 what should be remembered (required for write)"},description:{type:"string",description:"One-line summary shown in the memory index"},origin:{type:"string",description:"Optional: source project path, for traceability of migrated memories"},dry_run:{type:"boolean",description:"reindex only: report what would be restored without writing"}},required:["action"]}},{name:"cairn_switch",description:"Switch the active Cairn project context to a different directory. Detects the project root automatically from any path within the project (via .git, package.json, .cairn, etc.). Call cairn_resume or cairn_maintain after switching. Call with no arguments to return to the original session's project.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Any path inside the target project (file or directory). Omit to restore the original session root."}}}},{name:"cairn_employ_memory",description:"Migrate memories from another project's .cairn/memory/ into the current project. By default copies only transferable types (preference + experience) \u2014 skips project-specific decision and knowledge. Imported memories are tagged with their origin project path for traceability.",inputSchema:{type:"object",properties:{source_path:{type:"string",description:"Path to the source project (any file/directory inside it, or its .cairn directory)"},types:{type:"array",items:{type:"string",enum:["preference","experience","decision","knowledge"]},description:'Optional: memory types to migrate (default: ["preference", "experience"])'},overwrite:{type:"boolean",default:!1,description:"Overwrite existing memories with the same filename (default: false)"}},required:["source_path"]}},{name:"cairn_todos",description:"Long-tail backlog: scanned TODO/FIXME/HACK/XXX comments + manual notes. Stats are included in cairn_maintain and cairn_resume reports. For the active multi-phase plan (authored in .cairn/roadmap.md, with a cursor + deps) use cairn_roadmap instead.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","add","resolve","scan"],description:"list: show todos | add: create a manual todo | resolve: mark done by id | scan: re-scan codebase"},text:{type:"string",description:"Text for the new todo (add action)"},kind:{type:"string",enum:["TODO","FIXME","HACK","XXX","NOTE"],description:"Kind for new todo (default: TODO)"},id:{type:"number",description:"Todo ID to resolve (resolve action)"},status:{type:"string",enum:["open","done"],description:"Filter by status (list action)"},source:{type:"string",enum:["scan","manual"],description:"Filter by source (list action)"}},required:["action"]}},{name:"cairn_roadmap",description:"Active project plan: phases with status + cursor + dep graph. Phases are AUTHORED in the first-class document .cairn/roadmap.md (NOT in memory) via a `## Phases` section (### Phase 1: title, ### Phase 2: ...). Read actions (tree/status/next/sync) re-seed from that file, so editing roadmap.md IS editing the plan (existing phases preserved, removed marked stale, added appended). Marking a phase done prunes it from roadmap.md and appends it to .cairn/roadmap_completed.md (append-only log); the done row stays in the DB but is hidden from the tree, so the plan always shows what is next. Sub-tasks under a phase are ad-hoc via add --parent. Inter-phase deps via deps_add \u2014 `next` will skip dep-blocked phases and explain in the reason. The Stop hook re-prints this tree after every assistant turn while a cursor is set.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["tree","status","sync","publish","focus","next","set_status","add","deps_add","deps_remove","deps_list"],description:"tree: render the roadmap (M/N done rollup; stale + shipped-done hidden by default \u2014 pass show_stale: true for stale) | status: one-line summary | sync: re-seed from .cairn/roadmap.md (also runs automatically on tree/status/next) | publish: (re)generate the human-readable ROADMAP.md at the repo root from the live plan (also runs automatically after every action; write-if-changed, never a placeholder) | focus <id>: move cursor, mark in_progress, propagate up (warns if dep-blocked but does not refuse) | next: DFS-advance cursor to next actionable phase (skips stale + dep-blocked, returns reason) | set_status <id> <status>: open|in_progress|done|blocked|stale (done auto-advances cursor; a done roadmap phase is pruned from roadmap.md + logged to roadmap_completed.md) | add <parent> <text>: ad-hoc sub-task under an existing phase | deps_add <from> <to>: declare phase <from> blocks phase <to> (cycle-checked) | deps_remove <from> <to> | deps_list [<id>]: list edges (or just edges touching <id>)"},id:{type:"number",description:"Phase ID (focus, set_status, deps_list)"},status:{type:"string",enum:["open","in_progress","done","blocked","stale"],description:"New status (set_status)"},parent:{type:"number",description:"Parent phase ID (add \u2014 required, ad-hoc sub-tasks must hang under an existing phase)"},text:{type:"string",description:"Sub-task text (add action)"},compact:{type:"boolean",description:"Compact tree render \u2014 strips id tags (tree action)"},show_stale:{type:"boolean",description:"Include stale phases in tree (default: hidden)"},hide_stale:{type:"boolean",description:"Deprecated \u2014 stale is hidden by default. Pass hide_stale: false to show, or use show_stale: true."},decision:{type:"string",description:"Filter tree to a single decision (tree action)"},from:{type:"number",description:"Blocker phase ID (deps_add, deps_remove)"},to:{type:"number",description:"Blocked phase ID (deps_add, deps_remove)"}},required:["action"]}}]}));var Ti=new Set(["cairn_search","cairn_bundle","cairn_describe","cairn_outline","cairn_code_graph","cairn_security"]);it.setRequestHandler(xi,async t=>{let{name:e,arguments:n}=t.params;switch(Ti.has(e)&&await st(M),e){case"cairn_maintain":{let s=await Mt(M,n);return nt(),s}case"cairn_search":return Ct(M,n);case"cairn_describe":return Ft(M,n);case"cairn_code_graph":return jt(M,n);case"cairn_security":return Dt(M,n);case"cairn_bundle":return await Ut(M,n);case"cairn_checkpoint":return Bt(M,{...n,auto:!1});case"cairn_resume":{let s=await Nn(M);return nt(),s}case"cairn_outline":return vn(M,n);case"cairn_minify":return kn(M,n);case"cairn_todos":return await Ln(M,n);case"cairn_roadmap":{let s=ge(M,n),i=xn(M);return i&&Array.isArray(s?.content)&&s.content.push({type:"text",text:i}),s}case"cairn_memo":return nn(M,n);case"cairn_switch":return In(M,n);case"cairn_employ_memory":return An(M,n);default:throw new Error(`Unknown tool: ${e}`)}});var wi=new Si;await it.connect(wi);
242
+ Use to: get a project-wide bird's-eye view, triage code quality, find where to look first.`,inputSchema:{type:"object",properties:{repo:{type:"string",description:'Optional: scope to a specific repo/service by name (e.g. "elprint-component-service")'},paths:{type:"array",items:{type:"string"},description:"Optional: scope to specific file paths or directories (substring match)"},checks:{type:"array",items:{type:"string"},description:'Optional: limit to specific checks e.g. ["god_class","symmetry_gap"]'}}}},{name:"cairn_minify",description:"Minify a single source file and return compressed content. Strips comments and empty lines. Useful when passive hooks are not installed. Supports java, typescript, javascript, vue, python, sql.",inputSchema:{type:"object",properties:{file_path:{type:"string",description:"Absolute path to the file to minify"}},required:["file_path"]}},{name:"cairn_memo",description:'Persist and retrieve typed memories in .cairn/memory/. Use this \u2014 not the auto-memory system \u2014 for all project-level memories. Triggered by: "save cairn memory", "cairn remember", "build cairn memories", "save experience to cairn". Four types: preference (user working style \u2014 always loaded on resume), experience (transferable lessons learned \u2014 migrated between projects), decision (project-specific architectural choices), knowledge (project-specific codebase facts). Memories survive context loss and accumulate across sessions.',inputSchema:{type:"object",properties:{action:{type:"string",enum:["write","read","list","delete","reindex"],description:"write: save/update a memory | read: load a memory by name | list: show all memories with index | delete: remove a memory | reindex: repair MEMORY.md by restoring pointers to memory files it has lost (additive \u2014 never rewrites or removes an existing line; pass dry_run to preview)"},name:{type:"string",description:"Memory identifier (required for write/read/delete)"},type:{type:"string",enum:["preference","experience","decision","knowledge"],description:"Memory type (required for write)"},content:{type:"string",description:"Memory body \u2014 what should be remembered (required for write)"},description:{type:"string",description:"One-line summary shown in the memory index"},origin:{type:"string",description:"Optional: source project path, for traceability of migrated memories"},dry_run:{type:"boolean",description:"reindex only: report what would be restored without writing"}},required:["action"]}},{name:"cairn_switch",description:"Switch the active Cairn project context to a different directory. Detects the project root automatically from any path within the project (via .git, package.json, .cairn, etc.). Call cairn_resume or cairn_maintain after switching. Call with no arguments to return to the original session's project.",inputSchema:{type:"object",properties:{project_path:{type:"string",description:"Any path inside the target project (file or directory). Omit to restore the original session root."}}}},{name:"cairn_employ_memory",description:"Migrate memories from another project's .cairn/memory/ into the current project. By default copies only transferable types (preference + experience) \u2014 skips project-specific decision and knowledge. Imported memories are tagged with their origin project path for traceability.",inputSchema:{type:"object",properties:{source_path:{type:"string",description:"Path to the source project (any file/directory inside it, or its .cairn directory)"},types:{type:"array",items:{type:"string",enum:["preference","experience","decision","knowledge"]},description:'Optional: memory types to migrate (default: ["preference", "experience"])'},overwrite:{type:"boolean",default:!1,description:"Overwrite existing memories with the same filename (default: false)"}},required:["source_path"]}},{name:"cairn_todos",description:"Long-tail backlog: scanned TODO/FIXME/HACK/XXX comments + manual notes. Stats are included in cairn_maintain and cairn_resume reports. For the active multi-phase plan (authored in .cairn/roadmap.md, with a cursor + deps) use cairn_roadmap instead.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","add","resolve","scan"],description:"list: show todos | add: create a manual todo | resolve: mark done by id | scan: re-scan codebase"},text:{type:"string",description:"Text for the new todo (add action)"},kind:{type:"string",enum:["TODO","FIXME","HACK","XXX","NOTE"],description:"Kind for new todo (default: TODO)"},id:{type:"number",description:"Todo ID to resolve (resolve action)"},status:{type:"string",enum:["open","done"],description:"Filter by status (list action)"},source:{type:"string",enum:["scan","manual"],description:"Filter by source (list action)"}},required:["action"]}},{name:"cairn_roadmap",description:"Active project plan: phases with status + cursor + dep graph. Phases are AUTHORED in the first-class document .cairn/roadmap.md (NOT in memory) via a `## Phases` section (### Phase 1: title, ### Phase 2: ...). Read actions (tree/status/next/sync) re-seed from that file, so editing roadmap.md IS editing the plan (existing phases preserved, removed marked stale, added appended). Marking a phase done prunes it from roadmap.md and appends it to .cairn/roadmap_completed.md (append-only log); the done row stays in the DB but is hidden from the tree, so the plan always shows what is next. Sub-tasks under a phase are ad-hoc via add --parent. Inter-phase deps via deps_add \u2014 `next` will skip dep-blocked phases and explain in the reason. The Stop hook re-prints this tree after every assistant turn while a cursor is set.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["tree","status","sync","publish","focus","next","set_status","add","deps_add","deps_remove","deps_list"],description:"tree: render the roadmap (M/N done rollup; stale + shipped-done hidden by default \u2014 pass show_stale: true for stale) | status: one-line summary | sync: re-seed from .cairn/roadmap.md (also runs automatically on tree/status/next) | publish: (re)generate the human-readable ROADMAP.md at the repo root from the live plan (also runs automatically after every action; write-if-changed, never a placeholder) | focus <id>: move cursor, mark in_progress, propagate up (warns if dep-blocked but does not refuse) | next: DFS-advance cursor to next actionable phase (skips stale + dep-blocked, returns reason) | set_status <id> <status>: open|in_progress|done|blocked|stale (done auto-advances cursor; a done roadmap phase is pruned from roadmap.md + logged to roadmap_completed.md) | add <parent> <text>: ad-hoc sub-task under an existing phase | deps_add <from> <to>: declare phase <from> blocks phase <to> (cycle-checked) | deps_remove <from> <to> | deps_list [<id>]: list edges (or just edges touching <id>)"},id:{type:"number",description:"Phase ID (focus, set_status, deps_list)"},status:{type:"string",enum:["open","in_progress","done","blocked","stale"],description:"New status (set_status)"},parent:{type:"number",description:"Parent phase ID (add \u2014 required, ad-hoc sub-tasks must hang under an existing phase)"},text:{type:"string",description:"Sub-task text (add action)"},compact:{type:"boolean",description:"Compact tree render \u2014 strips id tags (tree action)"},show_stale:{type:"boolean",description:"Include stale phases in tree (default: hidden)"},hide_stale:{type:"boolean",description:"Deprecated \u2014 stale is hidden by default. Pass hide_stale: false to show, or use show_stale: true."},decision:{type:"string",description:"Filter tree to a single decision (tree action)"},from:{type:"number",description:"Blocker phase ID (deps_add, deps_remove)"},to:{type:"number",description:"Blocked phase ID (deps_add, deps_remove)"}},required:["action"]}}]}));var Ti=new Set(["cairn_search","cairn_bundle","cairn_describe","cairn_outline","cairn_code_graph","cairn_security"]);it.setRequestHandler(xi,async t=>{let{name:e,arguments:n}=t.params;switch(Ti.has(e)&&await st(M),e){case"cairn_maintain":{let s=await Mt(M,n);return nt(),s}case"cairn_search":return Ct(M,n);case"cairn_describe":return jt(M,n);case"cairn_code_graph":return Ft(M,n);case"cairn_security":return Dt(M,n);case"cairn_bundle":return await Ut(M,n);case"cairn_checkpoint":return Bt(M,{...n,auto:!1});case"cairn_resume":{let s=await Nn(M);return nt(),s}case"cairn_outline":return vn(M,n);case"cairn_minify":return kn(M,n);case"cairn_todos":return await Ln(M,n);case"cairn_roadmap":{let s=ge(M,n),i=xn(M);return i&&Array.isArray(s?.content)&&s.content.push({type:"text",text:i}),s}case"cairn_memo":return nn(M,n);case"cairn_switch":return In(M,n);case"cairn_employ_memory":return An(M,n);default:throw new Error(`Unknown tool: ${e}`)}});var wi=new Si;await it.connect(wi);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/cairn-mcp",
3
- "version": "1.33.0",
3
+ "version": "1.33.1",
4
4
  "description": "MCP server that gives Claude Code persistent memory across sessions. Index your codebase once, search symbols, bundle source, scan for vulnerabilities, and checkpoint/resume work — across Java, TypeScript, Vue, Python, SQL and more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",