@misterhuydo/cairn-mcp 1.28.0 → 1.28.2

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
@@ -60,7 +60,7 @@ ${o}`,"utf8");return}let r=n.length;for(let i=s+1;i<n.length;i++)if(/^##\s+/.tes
60
60
  `).replace(/^\n+/,""),u=`
61
61
 
62
62
  ${o}${l.trim()?`
63
- `+l:""}`;ne.writeFileSync(q(),a+u,"utf8")}function st(e){let t=yn(e);if(t.length===0)return{added:[],skipped:[],total:0};let s=Ee()?[]:oe(ce()).map(u=>({text:u.text,body:u.body})),o=new Set(s.map(u=>Y(u.text))),r=[...s],a=[],l=[];for(let u of t){let i=Y(u.text);if(o.has(i)){l.push(u.text);continue}o.add(i),r.push(u),a.push(u.text)}return a.length>0&&nt(r),{added:a,skipped:l,total:r.length}}function _n(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function Sn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function ot(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let n=t.map(m=>({text:Oe(m.content||m.activeForm||""),status:_n(m.status)})).filter(m=>m.text);if(n.length===0)return{changed:!1};let s=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(T).filter(m=>m.status!=="stale");if(s.length===0){let d=Ee()?[]:oe(ce()).map(w=>({text:w.text,body:w.body})),h=new Set(d.map(w=>Y(w.text))),f=[...d],g=0;for(let w of n){let b=Y(w.text);h.has(b)||(h.add(b),f.push({text:w.text,body:null}),g++)}if(g===0)return{changed:!1};nt(f);let _=te(e);return{changed:!0,seeded:g,diff:_}}let o=Sn(e);o==null&&(o=s.find(m=>m.status==="in_progress")?.id??s.find(m=>m.status==="open")?.id??s[0].id);let r=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),a=new Map(r.map(m=>[Y(m.text),m])),l=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),u=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,c=0,p=0;for(let m of n){let d=a.get(Y(m.text));if(d){if(d.status==="done"&&m.status!=="done")continue;d.status!==m.status&&(u.run(m.status,d.id),p++)}else l.run(o,m.text,m.status,i++),c++}return{changed:c>0||p>0,parentId:o,inserted:c,updated:p}}var xn=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 wn(e){return[...new Set(Y(e).split(" ").filter(t=>t.length>=4&&!xn.has(t)))]}function bn(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),l=t.find(u=>u.phase_number===a);if(l)return{...l,confidence:"high"}}let o=new Set(Y(n).split(" ").filter(Boolean)),r=null;for(let a of t){let l=wn(a.text);if(l.length<2)continue;let u=l.filter(c=>o.has(c)),i=u.length/l.length;u.length>=2&&i>=.6&&(!r||i>r.ratio)&&(r={...a,confidence:"medium",ratio:i})}return r?(delete r.ratio,r):null}function ve(e,t){return hn(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3}).trim()}function rt(e,t){let n=Re.join(t,".cairn"),s=Re.join(n,".last-commit"),o=Re.join(n,"roadmap-pending.json"),r;try{r=ve(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let a=null;try{a=ne.readFileSync(s,"utf8").trim()||null}catch{}let l=[];if(a&&a!==r)try{l=ve(t,`rev-list --no-merges --reverse ${a}..HEAD`).split(`
63
+ `+l:""}`;ne.writeFileSync(q(),a+u,"utf8")}function st(e){let t=yn(e);if(t.length===0)return{added:[],skipped:[],total:0};let s=Ee()?[]:oe(ce()).map(u=>({text:u.text,body:u.body})),o=new Set(s.map(u=>Y(u.text))),r=[...s],a=[],l=[];for(let u of t){let i=Y(u.text);if(o.has(i)){l.push(u.text);continue}o.add(i),r.push(u),a.push(u.text)}return a.length>0&&nt(r),{added:a,skipped:l,total:r.length}}function _n(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function Sn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function ot(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let n=t.map(m=>({text:Oe(m.content||m.activeForm||""),status:_n(m.status)})).filter(m=>m.text);if(n.length===0)return{changed:!1};let s=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(T).filter(m=>m.status!=="stale");if(s.length===0){let d=Ee()?[]:oe(ce()).map(w=>({text:w.text,body:w.body})),h=new Set(d.map(w=>Y(w.text))),f=[...d],g=0;for(let w of n){let b=Y(w.text);h.has(b)||(h.add(b),f.push({text:w.text,body:null}),g++)}if(g===0)return{changed:!1};nt(f);let _=te(e);return{changed:!0,seeded:g,diff:_}}let o=Sn(e);o==null&&(o=s.find(m=>m.status==="in_progress")?.id??s.find(m=>m.status==="open")?.id??s[0].id);let r=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),a=new Map(r.map(m=>[Y(m.text),m])),l=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),u=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,c=0,p=0;for(let m of n){let d=a.get(Y(m.text));if(d){if(d.status==="done"&&m.status!=="done")continue;d.status!==m.status&&(u.run(m.status,d.id),p++)}else l.run(o,m.text,m.status,i++),c++}return{changed:c>0||p>0,parentId:o,inserted:c,updated:p}}var xn=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 wn(e){return[...new Set(Y(e).split(" ").filter(t=>t.length>=4&&!xn.has(t)))]}function bn(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),l=t.find(u=>u.phase_number===a);if(l)return{...l,confidence:"high"}}let o=new Set(Y(n).split(" ").filter(Boolean)),r=null;for(let a of t){let l=wn(a.text);if(l.length<2)continue;let u=l.filter(c=>o.has(c)),i=u.length/l.length;u.length>=2&&i>=.6&&(!r||i>r.ratio)&&(r={...a,confidence:"medium",ratio:i})}return r?(delete r.ratio,r):null}function ve(e,t){return hn(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3,stdio:["ignore","pipe","ignore"]}).trim()}function rt(e,t){let n=Re.join(t,".cairn"),s=Re.join(n,".last-commit"),o=Re.join(n,"roadmap-pending.json"),r;try{r=ve(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let a=null;try{a=ne.readFileSync(s,"utf8").trim()||null}catch{}let l=[];if(a&&a!==r)try{l=ve(t,`rev-list --no-merges --reverse ${a}..HEAD`).split(`
64
64
  `).filter(Boolean)}catch{l=[]}try{ne.writeFileSync(s,r,"utf8")}catch{}let u=[];try{let i=JSON.parse(ne.readFileSync(o,"utf8"));Array.isArray(i)&&(u=i)}catch{}if(l.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(T);for(let c of l){let p="";try{p=ve(t,`show -s --format=%s%n%b ${c}`)}catch{}let m=bn(p,i);m&&!u.some(d=>d.phase_id===m.id)&&u.push({phase_id:m.id,phase_number:m.phase_number,text:m.text,commit:c.slice(0,7),confidence:m.confidence})}}if(u.length){let i=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(c=>c.id));u=u.filter(c=>i.has(c.phase_id))}try{ne.writeFileSync(o,JSON.stringify(u),"utf8")}catch{}return{candidates:u,newCommits:l.length}}import U from"fs";import P from"path";var le="cairn",X="roadmap/1",kn="<!-- slot:roadmap";function En(e){return`${kn} format=${X} provider=${le} generated=${e} -->`}var $n=`<!-- ${le}:roadmap -->`,Rn=/^<!--\s*slot:roadmap\b([^>]*?)-->/,vn=/^<!--\s*([A-Za-z0-9_.-]+)\s*:\s*roadmap\s*-->/,On=/([A-Za-z_][A-Za-z0-9_-]*)=("[^"]*"|\S+)/g;function jn(e){if(typeof e!="string")return null;let t=e.indexOf(`
65
65
  `);return(t===-1?e:e.slice(0,t)).trim()}function Tn(e){let t=jn(e);if(!t)return null;let n=Rn.exec(t);if(n){let o={};for(let r of n[1].matchAll(On))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=vn.exec(t);return s?{provider:s[1].toLowerCase(),format:null,generated:null}:null}var Pn=/^```roadmap-json[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/m;function ct(e){if(typeof e!="string")return null;let t=Pn.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 Nn=new Set(["format","provider","generated","title","cursor","phases","migrated_from","migrated"]),Mn=new Set(["id","number","title","status","goal","blocked_by","slices","detail","links"]);function lt(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 ut(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),ut(n.slices,t));return t}function An(e){if(typeof e!="string")return null;let t=ct(e)?.migrated_from;if(typeof t=="string"&&t)return t;let n=e.indexOf(`
66
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 Fn=/\[\[([^\]|#\n]+?)(?:[|#][^\]\n]*)?\]\]/g,Ln=["decision","knowledge","experience","preference"];function Dn(e){let t=P.relative(z(),e).split(P.sep).join("/");return!t||t.startsWith("..")||P.isAbsolute(t)?null:t}function Cn(e){let t=P.join(z(),".cairn","memory");return[P.join(t,`${e}.md`),...Ln.map(n=>P.join(t,`${n}_${e}.md`))]}function In(e){if(!e)return[];let t=[],n=new Set;for(let s of String(e).matchAll(Fn)){let o=s[1].trim();if(!(!o||n.has(o))){n.add(o);for(let r of Cn(o)){if(!U.existsSync(r))continue;let a=Dn(r);a&&t.push({kind:"memory",title:o,path:a});break}}}return t}var Wn={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"},Un=new Set(["open","in_progress","done","blocked","stale"]);function Hn(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function pt(){return P.join(z(),"ROADMAP.md")}function dt(){return new Date().toISOString().replace(/\.\d{3}Z$/,"Z")}function Jn(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Bn(e){let t=new Map,n;try{n=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let s of n)t.has(s.blocked_id)||t.set(s.blocked_id,new Set),t.get(s.blocked_id).add(s.blocker_id);return t}function Yn(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 zn(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(f=>f.status!=="stale").length===0)return null;let n=Jn(e),s=Bn(e),o=new Map(t.map(f=>[f.id,f.status])),r=new Map(t.map(f=>[f.id,f.phase_number!=null?`Phase ${f.phase_number}`:f.text])),a=new Map(t.map(f=>[f.id,f])),l=new Map;for(let f of t){let g=f.parent_phase_id??"root";l.has(g)||l.set(g,[]),l.get(g).push(f)}for(let f of l.values())f.sort(Yn);let u=new Map;for(let f of l.get("root")||[])f.decision_name!=null&&(f.decision_name===T&&f.phase_number==null||(u.has(f.decision_name)||u.set(f.decision_name,[]),u.get(f.decision_name).push(f)));let i=[...u.keys()].sort((f,g)=>f===T?-1:g===T?1:f<g?-1:f>g?1:0),c=new Map,p=!1;for(let f of i){let g=u.get(f).filter(w=>w.status!=="stale"),_=f===T?g.filter(w=>w.status!=="done"):g;_.length!==0&&(p=!0,c.set(f,{visible:_,doneCount:g.filter(w=>w.status==="done").length,total:g.length}))}return p?{cursor:n,decisionNames:i,visibleRoots:c,children:f=>(l.get(f)||[]).filter(g=>g.status!=="stale"),unfinishedBlockers:f=>{let g=s.get(f);return g?[...g].filter(_=>o.get(_)!=="done").map(_=>r.get(_)||`#${_}`):[]},blockerIds:f=>{let g=s.get(f);return g?[...g].filter(_=>a.has(_)).sort((_,w)=>_-w).map(_=>`phase-${_}`):[]}}:null}var je=120;function Gn(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 ft(e){if(!e)return null;let t=[];for(let a of e.split(`
@@ -113,6 +113,6 @@ Index already lists every memory file. Nothing to do.`),process.exit(0)}else if(
113
113
  `)+`
114
114
  `),y.writeFileSync(s,new Date().toISOString(),"utf8"),process.exit(0)}else if(N==="session-start"){let e={};if(!process.stdin.isTTY)try{e=JSON.parse(y.readFileSync(0,"utf8"))||{}}catch{}let t=typeof e.cwd=="string"&&e.cwd?e.cwd:process.cwd(),n=S.join(t,".cairn"),s=S.join(n,"session.json");y.existsSync(s)||process.exit(0);let o=typeof e.source=="string"?e.source:"",r;try{r=JSON.parse(y.readFileSync(s,"utf8"))}catch{process.exit(0)}Ye(n);let a=/^Auto-checkpoint\b/.test(r.message||""),l=a&&r.last_manual_message||r.message||"(no message recorded)",u=o==="compact"?"a /compact":o==="clear"?"a /clear":o==="fork"?"a fork":o==="resume"?"a resume":"a restart",i=[`[cairn] Session restored automatically after ${u} \u2014 the user did not have to ask:`,..._t(t,r),"","This IS the prior session state. Do not re-derive it from git log or file reads, and do not","ask the user what they were doing \u2014 it is above. If their first message continues this work,",'carry on from would_lose without making them say "resume" first.',"","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(`
115
115
  `),c=(w,b)=>{let x=String(w).replace(/\s+/g," ").trim();return x.length<=b?x:x.slice(0,b-1)+"\u2026"},p=se(t,r),m=a&&r.last_manual_checkpoint_at||r.checkpoint_at,d=m?new Date(m).toLocaleString():"time unknown",h=[`[cairn] Checkpoint restored after ${u} \u2014 saved ${d}`];h.push(` Working on: ${c(l,140)}`),h.push(p.recorded?` Would be lost: ${c(p.text,200)}`+(p.stale?` (written before the ${p.commits} commit(s) since \u2014 may be stale)`:""):" Would be lost: NOT RECORDED \u2014 nothing says what the last session would lose.");let f=[];r.active_files?.length&&f.push(`${r.active_files.length} active file(s)`),r.notes?.length&&f.push(`${r.notes.length} note(s)`),f.push("full detail"),h.push(` Type "resume" for: ${f.join(", ")}.`);let g=h.join(`
116
- `),_=["[cairn] Session restored from checkpoint.","Before anything else, open with ONE short sentence saying what the previous session was","working on and what it left unfinished, then ask whether to continue it.","Just that sentence and the question \u2014 no summary, no plan, no tool calls.","The details are already in the context you were given."].join(" ");process.stdout.write(JSON.stringify({systemMessage:g,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:i,initialUserMessage:_}})),process.exit(0)}else if(N==="roadmap-hint"){let e=S.join(process.cwd(),".cairn"),t=await St(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(`
116
+ `),_=["[cairn] Session restored from checkpoint.","Lead your next reply with ONE short sentence naming what the previous session left","unfinished. If the user has already asked for something, answer it as normal immediately","after \u2014 this must never delay, replace or override their request. If they have not asked","for anything yet, end that sentence by asking whether to continue the unfinished work.","The details are already in the context you were given; do not re-derive them."].join(" ");process.stdout.write(JSON.stringify({systemMessage:g,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:i,initialUserMessage:_}})),process.exit(0)}else if(N==="roadmap-hint"){let e=S.join(process.cwd(),".cairn"),t=await St(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(`
117
117
  `)+`
118
118
  `),process.exit(0)}else if(N==="install"){let e=process.platform,t=S.join(Z.homedir(),".claude.json"),n={};if(y.existsSync(t))try{n=JSON.parse(y.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:wt},y.writeFileSync(t,JSON.stringify(n,null,2),"utf8");let o=S.join(Z.homedir(),".claude");xt(o,!0),console.log("Cairn installed successfully!"),console.log(""),console.log(` Platform : ${e}`),console.log(` MCP : cairn-mcp registered in ${t}${s?" (updated)":""}`),console.log(` Hooks : installed in ${S.join(o,"settings.json")}`),console.log(""),console.log("Active hooks:"),console.log(" PreToolUse[Read] -> cairn minify (compress source files for reading)"),console.log(" PreToolUse[Edit] -> cairn edit-guard (block Edit until re-read with full content)"),console.log(" PreToolUse[Write] -> cairn edit-guard (clear minify state before full file overwrite)"),console.log(" PostToolUse[ExitPlanMode] -> cairn roadmap-capture (approved plan -> roadmap phases)"),console.log(" PostToolUse[TodoWrite] -> cairn roadmap-capture (todos -> roadmap sub-tasks)"),console.log(" Stop -> cairn session-tick (checkpoint + memory backup + roadmap, surfaced)"),console.log(" UserPromptSubmit -> cairn resume-hint (remind Claude of prior session)"),console.log(" UserPromptSubmit -> cairn memory-restore (restore .cairn/memory -> Claude store if empty)"),console.log(" SessionStart -> 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(N==="install-hooks"){let e=process.argv.includes("--global"),t=e?S.join(Z.homedir(),".claude"):S.join(process.cwd(),".claude");xt(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 xt(e,t){let n=S.join(e,"settings.json"),s={};if(y.existsSync(n))try{s=JSON.parse(y.readFileSync(n,"utf8"))}catch{}s.hooks=s.hooks||{};let o=s.hooks.PreToolUse||[];o.some(d=>d.matcher==="Read"&&d.hooks?.some(h=>h.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let r=o.filter(d=>!(d.matcher==="Edit"&&d.hooks?.some(h=>h.command==="cairn set-mode edit")));r.some(d=>d.matcher==="Edit"&&d.hooks?.some(h=>h.command==="cairn edit-guard"))||r.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),r.some(d=>d.matcher==="Write"&&d.hooks?.some(h=>h.command==="cairn edit-guard"))||r.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),s.hooks.PreToolUse=r;let a=(d,h)=>(d||[]).map(f=>({...f,hooks:(f.hooks||[]).filter(g=>!h(g.command))})).filter(f=>(f.hooks||[]).length>0),l=d=>typeof d=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(d),u=a(s.hooks.Stop,d=>d==="cairn checkpoint --auto"||d==="cairn roadmap-hint");u.some(d=>d.hooks?.some(h=>h.command==="cairn session-tick"))||u.push({hooks:[{type:"command",command:"cairn session-tick"}]}),s.hooks.Stop=u;let i=a(s.hooks.UserPromptSubmit,l);i.some(d=>d.hooks?.some(h=>h.command==="cairn resume-hint"))||i.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),i.some(d=>d.hooks?.some(h=>h.command==="cairn memory-restore"))||i.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),s.hooks.UserPromptSubmit=i;let c=a(s.hooks.SessionStart,d=>d==="cairn session-start");c.push({hooks:[{type:"command",command:"cairn session-start"}]}),s.hooks.SessionStart=c;let p=a(s.hooks.PostToolUse,l),m=d=>{p.some(h=>h.matcher===d&&h.hooks?.some(f=>f.command==="cairn roadmap-capture"))||p.push({matcher:d,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(m("ExitPlanMode"),m("TodoWrite"),s.hooks.PostToolUse=p,t){let d=S.join(e,"scripts");for(let h of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let f=S.join(d,h);if(y.existsSync(f))try{y.unlinkSync(f)}catch{}}}if(!t){let d=S.join(process.cwd(),".cairn");y.mkdirSync(d,{recursive:!0});let h=S.join(d,".cairn-project");y.existsSync(h)||y.writeFileSync(h,"","utf8")}y.mkdirSync(e,{recursive:!0}),y.writeFileSync(n,JSON.stringify(s,null,2),"utf8")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/cairn-mcp",
3
- "version": "1.28.0",
3
+ "version": "1.28.2",
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",