@misterhuydo/cairn-mcp 1.14.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -166,8 +166,18 @@ cairn_roadmap { action: "focus", id: 3 } → make phase 3 the current f
166
166
  cairn_roadmap { action: "set_status", id: 3, status: "done" } → ship it
167
167
  cairn_roadmap { action: "add", parent: 3, text: "..." } → ad-hoc sub-task under a phase
168
168
  cairn_roadmap { action: "deps_add", from: 2, to: 4 } → phase 2 blocks phase 4
169
+ cairn_roadmap { action: "publish" } → (re)write the human-readable ROADMAP.md at the repo root
169
170
  ```
170
171
 
172
+ The live plan is also projected into a legible **`ROADMAP.md` at the repo root** — a
173
+ human-readable view with checkboxes, per-item status, and a `← current` marker on the
174
+ cursor phase. It refreshes automatically on session activity (the end-of-turn hook and
175
+ the first prompt of a session) as well as on every `cairn_roadmap` call, so a workspace
176
+ that has a roadmap but hasn't touched the roadmap tool this session still shows a
177
+ populated file. It is deterministic (no timestamps, stable ordering, so an unchanged
178
+ plan re-writes byte-identically and leaves clean git diffs), never written as an empty
179
+ placeholder, and never clobbers a hand-authored `ROADMAP.md`.
180
+
171
181
  Editing `roadmap.md` *is* editing the plan — read actions re-seed from it, and the
172
182
  end-of-turn hook folds in any hand-edits. Marking a phase **done** prunes it from
173
183
  `roadmap.md` and appends it to the append-only `.cairn/roadmap_completed.md`, so the
package/dist/cairn-cli.js CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import f from"fs";import U from"os";import y from"path";import{execSync as pe}from"child_process";import xt from"fast-glob";var me={".java":"java",".ts":"typescript",".tsx":"typescript",".js":"javascript",".jsx":"javascript",".mjs":"javascript",".vue":"vue",".py":"python",".sql":"sql",".yml":"config",".yaml":"config",".properties":"config",".env":"config",".xml":"xml",".html":"html",".md":"markdown"};function V(e,t,s={}){let{noComments:n=!0,noEmptyLines:o=!0,aggressive:a=!1}=s,i=e;return n&&(i=Ce(i,t)),o&&(i=De(i)),i=Ae(i),a&&(i=Ie(i)),i.trim()}function Ce(e,t){switch(t){case"java":case"typescript":case"javascript":case"vue":return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(s,n)=>n||!s.startsWith("//")?s:"");case"python":return e.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return e.replace(/<!--[\s\S]*?-->/g,"");case"config":return e.replace(/#.*$/gm,"");default:return e}}function De(e){return e.split(`
2
+ import h from"fs";import B from"os";import y from"path";import{execSync as me}from"child_process";import Ft from"fast-glob";var ge={".java":"java",".ts":"typescript",".tsx":"typescript",".js":"javascript",".jsx":"javascript",".mjs":"javascript",".vue":"vue",".py":"python",".sql":"sql",".yml":"config",".yaml":"config",".properties":"config",".env":"config",".xml":"xml",".html":"html",".md":"markdown"};function V(e,t,s={}){let{noComments:n=!0,noEmptyLines:o=!0,aggressive:a=!1}=s,i=e;return n&&(i=Ue(i,t)),o&&(i=He(i)),i=Je(i),a&&(i=Be(i)),i.trim()}function Ue(e,t){switch(t){case"java":case"typescript":case"javascript":case"vue":return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(s,n)=>n||!s.startsWith("//")?s:"");case"python":return e.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return e.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return e.replace(/<!--[\s\S]*?-->/g,"");case"config":return e.replace(/#.*$/gm,"");default:return e}}function He(e){return e.split(`
3
3
  `).filter(t=>t.trim().length>0).join(`
4
- `)}function Ae(e){return e.split(`
4
+ `)}function Je(e){return e.split(`
5
5
  `).map(t=>t.trimEnd()).join(`
6
- `)}function Ie(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:s=!1,noComments:n=!0,noEmptyLines:o=!0,aggressive:a=!1}=t,i=[],l=e.match(/<template>([\s\S]*?)<\/template>/i);if(l){let c=l[1];n&&(c=c.replace(/<!--[\s\S]*?-->/g,"")),o&&(c=c.split(`
6
+ `)}function Be(e){return e.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function _e(e,t={}){let{noStyle:s=!1,noComments:n=!0,noEmptyLines:o=!0,aggressive:a=!1}=t,i=[],p=e.match(/<template>([\s\S]*?)<\/template>/i);if(p){let c=p[1];n&&(c=c.replace(/<!--[\s\S]*?-->/g,"")),o&&(c=c.split(`
7
7
  `).filter(m=>m.trim()).join(`
8
- `)),c=c.replace(/\s+/g," ").trim(),c&&i.push(`<template>${c}</template>`)}let u=e.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(u){let c=u[1],m=V(u[2],"javascript",{noComments:n,noEmptyLines:o,aggressive:a}),d=/setup/i.test(c)?"<script setup>":"<script>";m&&i.push(`${d}${m}</script>`)}if(!s){let c=e.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(c){let m=c[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();m&&i.push(`<style${c[1]}>${m}</style>`)}}return i.join(`
8
+ `)),c=c.replace(/\s+/g," ").trim(),c&&i.push(`<template>${c}</template>`)}let f=e.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(f){let c=f[1],m=V(f[2],"javascript",{noComments:n,noEmptyLines:o,aggressive:a}),u=/setup/i.test(c)?"<script setup>":"<script>";m&&i.push(`${u}${m}</script>`)}if(!s){let c=e.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(c){let m=c[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();m&&i.push(`<style${c[1]}>${m}</style>`)}}return i.join(`
9
9
 
10
- `)}import L from"fs";import A from"path";import z from"fs";import We from"os";import H from"path";var Ue=null;function he(){return Ue||process.cwd()}function J(){let e=H.join(he(),".cairn");z.mkdirSync(H.join(e,"bundles"),{recursive:!0});let t=H.join(e,".cairn-project");return z.existsSync(t)||z.writeFileSync(t,"","utf8"),e}function ee(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function ye(){let e=H.join(We.homedir(),".claude","projects",ee(he()));return z.existsSync(e)?H.join(e,"memory"):null}var He=1e3,Je=100*1024;function Be(e,t,s,n){if(e&&typeof e=="object"&&e.ref)return e;let o=typeof e=="string"?e:String(e),a=/^\[\d{4}-\d{2}-\d{2}\]/.test(o)?o:`[${s}] ${o}`;if(a.length<=He)return a;let i=A.join(t,"notes");L.mkdirSync(i,{recursive:!0});let u=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${n}.md`,c=A.join(i,u);return L.writeFileSync(c,a,"utf8"),{description:a.split(`
11
- `)[0].slice(0,120),ref:A.join(".cairn","notes",u)}}function te(e,{message:t,active_files:s=[],notes:n=[]}){if(typeof n=="string")try{n=JSON.parse(n)}catch{n=n?[n]:[]}Array.isArray(n)||(n=[]);let o=J(),a=A.join(o,"session.json"),i=new Date().toISOString().slice(0,10),l={};try{let m=e.prepare("SELECT path FROM main.files").all();for(let{path:d}of m)try{l[d]=L.statSync(d).mtimeMs}catch{}}catch{}for(let m of s)try{l[m]=L.statSync(m).mtimeMs}catch{}let u=n.map((m,d)=>Be(m,o,i,d));if(L.existsSync(a))try{if(L.statSync(a).size>Je){let d=1;for(;L.existsSync(A.join(o,`session.${d}.json`));)d++;L.renameSync(a,A.join(o,`session.${d}.json`))}}catch{}let c={message:t,checkpoint_at:new Date().toISOString(),active_files:s,notes:u,mtime_snapshot:l};return L.writeFileSync(a,JSON.stringify(c,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:c.checkpoint_at,active_files_tracked:s.length,notes_saved:u.length},null,2)}]}}import M from"fs";import j from"path";import Ye from"crypto";var ge=80;function ne(e){let t=j.dirname(j.resolve(e)),s=j.parse(t).root;for(;;){let n=j.join(t,".cairn");try{if(M.statSync(n).isDirectory()&&M.existsSync(j.join(n,".cairn-project"))&&n.split(j.sep).filter(a=>a===".cairn").length===1)return n}catch{}if(t===s)return null;t=j.dirname(t)}}function _e(e,t){let s=Ye.createHash("sha256").update(j.resolve(t)).digest("hex").slice(0,6);return j.join(e,"views",`${s}_${j.basename(t)}`)}function C(e){try{return JSON.parse(M.readFileSync(j.join(e,"minify-map.json"),"utf8"))}catch{return{}}}function P(e,t){M.mkdirSync(e,{recursive:!0}),M.writeFileSync(j.join(e,"minify-map.json"),JSON.stringify(t,null,2),"utf8")}function K(e,t){let s=0;for(let[n,o]of Object.entries(t)){let a=!1;if(!M.existsSync(n))a=!0;else if(!M.existsSync(o.tempPath))a=!0;else try{M.statSync(n).mtimeMs>o.minifiedAt&&(a=!0)}catch{a=!0}if(a){if(o.tempPath)try{M.unlinkSync(o.tempPath)}catch{}delete t[n],s++}}return s}function Se(e,t){switch(t){case"javascript":case"typescript":return se(e);case"java":return se(e,{annotations:!0});case"python":return ze(e);case"vue":return Ke(e);case"sql":return Xe(e);default:return e.split(`
10
+ `)}import L from"fs";import W from"path";import z from"fs";import Ye from"os";import Y from"path";var Ge=null;function K(){return Ge||process.cwd()}function I(){let e=Y.join(K(),".cairn");z.mkdirSync(Y.join(e,"bundles"),{recursive:!0});let t=Y.join(e,".cairn-project");return z.existsSync(t)||z.writeFileSync(t,"","utf8"),e}function se(e){return e.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Se(){let e=Y.join(Ye.homedir(),".claude","projects",se(K()));return z.existsSync(e)?Y.join(e,"memory"):null}var qe=1e3,Ve=100*1024;function ze(e,t,s,n){if(e&&typeof e=="object"&&e.ref)return e;let o=typeof e=="string"?e:String(e),a=/^\[\d{4}-\d{2}-\d{2}\]/.test(o)?o:`[${s}] ${o}`;if(a.length<=qe)return a;let i=W.join(t,"notes");L.mkdirSync(i,{recursive:!0});let f=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${n}.md`,c=W.join(i,f);return L.writeFileSync(c,a,"utf8"),{description:a.split(`
11
+ `)[0].slice(0,120),ref:W.join(".cairn","notes",f)}}function oe(e,{message:t,active_files:s=[],notes:n=[]}){if(typeof n=="string")try{n=JSON.parse(n)}catch{n=n?[n]:[]}Array.isArray(n)||(n=[]);let o=I(),a=W.join(o,"session.json"),i=new Date().toISOString().slice(0,10),p={};try{let m=e.prepare("SELECT path FROM main.files").all();for(let{path:u}of m)try{p[u]=L.statSync(u).mtimeMs}catch{}}catch{}for(let m of s)try{p[m]=L.statSync(m).mtimeMs}catch{}let f=n.map((m,u)=>ze(m,o,i,u));if(L.existsSync(a))try{if(L.statSync(a).size>Ve){let u=1;for(;L.existsSync(W.join(o,`session.${u}.json`));)u++;L.renameSync(a,W.join(o,`session.${u}.json`))}}catch{}let c={message:t,checkpoint_at:new Date().toISOString(),active_files:s,notes:f,mtime_snapshot:p};return L.writeFileSync(a,JSON.stringify(c,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:c.checkpoint_at,active_files_tracked:s.length,notes_saved:f.length},null,2)}]}}import F from"fs";import O from"path";import Ke from"crypto";var xe=80;function re(e){let t=O.dirname(O.resolve(e)),s=O.parse(t).root;for(;;){let n=O.join(t,".cairn");try{if(F.statSync(n).isDirectory()&&F.existsSync(O.join(n,".cairn-project"))&&n.split(O.sep).filter(a=>a===".cairn").length===1)return n}catch{}if(t===s)return null;t=O.dirname(t)}}function we(e,t){let s=Ke.createHash("sha256").update(O.resolve(t)).digest("hex").slice(0,6);return O.join(e,"views",`${s}_${O.basename(t)}`)}function D(e){try{return JSON.parse(F.readFileSync(O.join(e,"minify-map.json"),"utf8"))}catch{return{}}}function T(e,t){F.mkdirSync(e,{recursive:!0}),F.writeFileSync(O.join(e,"minify-map.json"),JSON.stringify(t,null,2),"utf8")}function X(e,t){let s=0;for(let[n,o]of Object.entries(t)){let a=!1;if(!F.existsSync(n))a=!0;else if(!F.existsSync(o.tempPath))a=!0;else try{F.statSync(n).mtimeMs>o.minifiedAt&&(a=!0)}catch{a=!0}if(a){if(o.tempPath)try{F.unlinkSync(o.tempPath)}catch{}delete t[n],s++}}return s}function Ee(e,t){switch(t){case"javascript":case"typescript":return ie(e);case"java":return ie(e,{annotations:!0});case"python":return et(e);case"vue":return tt(e);case"sql":return nt(e);default:return e.split(`
12
12
  `).slice(0,30).join(`
13
13
  `)+`
14
- ...`}}function se(e,{annotations:t=!1}={}){let s=e.split(`
15
- `),n=[],o=0;for(let a=0;a<s.length;a++){let i=s[a].trim();if(!i)continue;let{opens:l,closes:u}=Ve(i),c=l-u;if(o===0){let m=Ge(i);m!==null?n.push(`${a+1}: ${m}`):i==="}"&&n.push("}")}else if(o===1){let m=qe(i,t);m!==null&&n.push(` ${a+1}: ${m}`)}o=Math.max(0,o+c)}return n.join(`
16
- `)}function Ge(e){return/^import\s/.test(e)||/^@\w+/.test(e)||/^package\s/.test(e)?e:/^export\s/.test(e)?/^export\s+(default\s+)?(async\s+)?function[\s*]|^export\s+(default\s+)?(abstract\s+)?class\s/.test(e)?T(e):e:/^(async\s+)?function[\s*]/.test(e)||/^(abstract\s+)?class\s/.test(e)||/^(public\s+|private\s+|protected\s+)?(interface|enum|type|record)\s/.test(e)||/^(const|let|var)\s+\w+/.test(e)||/^(public|private|protected|abstract|final)\s+(class|interface|enum)\s/.test(e)?T(e):e==="}"?"}":null}function qe(e,t){return t&&/^@\w+/.test(e)?e:e==="}"?"}":/^(public|private|protected|static|final|abstract|async|override|readonly|synchronized|native)\s/.test(e)||/^constructor\s*\(/.test(e)||/^(get|set)\s+\w+\s*\(/.test(e)||/^\w[\w$]*\s*[<(]/.test(e)&&!e.startsWith("return")&&!e.startsWith("throw")&&!e.startsWith("if")&&!e.startsWith("for")&&!e.startsWith("while")&&!e.startsWith("switch")&&!e.startsWith("const")&&!e.startsWith("let")&&!e.startsWith("var")?T(e):null}function T(e){let t=0,s=!1,n="";for(let o=0;o<e.length;o++){let a=e[o];if(s)a===n&&e[o-1]!=="\\"&&(s=!1);else if(a==='"'||a==="'"||a==="`")s=!0,n=a;else if(a==="{"){if(t===0)return e.slice(0,o).trimEnd();t++}else a==="}"&&t--}return e}function Ve(e){let t=0,s=0,n=!1,o="";for(let a=0;a<e.length;a++){let i=e[a];n?i===o&&e[a-1]!=="\\"&&(n=!1):i==='"'||i==="'"||i==="`"?(n=!0,o=i):i==="{"?t++:i==="}"&&s++}return{opens:t,closes:s}}function ze(e){let t=[],s=e.split(`
14
+ ...`}}function ie(e,{annotations:t=!1}={}){let s=e.split(`
15
+ `),n=[],o=0;for(let a=0;a<s.length;a++){let i=s[a].trim();if(!i)continue;let{opens:p,closes:f}=Ze(i),c=p-f;if(o===0){let m=Xe(i);m!==null?n.push(`${a+1}: ${m}`):i==="}"&&n.push("}")}else if(o===1){let m=Qe(i,t);m!==null&&n.push(` ${a+1}: ${m}`)}o=Math.max(0,o+c)}return n.join(`
16
+ `)}function Xe(e){return/^import\s/.test(e)||/^@\w+/.test(e)||/^package\s/.test(e)?e:/^export\s/.test(e)?/^export\s+(default\s+)?(async\s+)?function[\s*]|^export\s+(default\s+)?(abstract\s+)?class\s/.test(e)?M(e):e:/^(async\s+)?function[\s*]/.test(e)||/^(abstract\s+)?class\s/.test(e)||/^(public\s+|private\s+|protected\s+)?(interface|enum|type|record)\s/.test(e)||/^(const|let|var)\s+\w+/.test(e)||/^(public|private|protected|abstract|final)\s+(class|interface|enum)\s/.test(e)?M(e):e==="}"?"}":null}function Qe(e,t){return t&&/^@\w+/.test(e)?e:e==="}"?"}":/^(public|private|protected|static|final|abstract|async|override|readonly|synchronized|native)\s/.test(e)||/^constructor\s*\(/.test(e)||/^(get|set)\s+\w+\s*\(/.test(e)||/^\w[\w$]*\s*[<(]/.test(e)&&!e.startsWith("return")&&!e.startsWith("throw")&&!e.startsWith("if")&&!e.startsWith("for")&&!e.startsWith("while")&&!e.startsWith("switch")&&!e.startsWith("const")&&!e.startsWith("let")&&!e.startsWith("var")?M(e):null}function M(e){let t=0,s=!1,n="";for(let o=0;o<e.length;o++){let a=e[o];if(s)a===n&&e[o-1]!=="\\"&&(s=!1);else if(a==='"'||a==="'"||a==="`")s=!0,n=a;else if(a==="{"){if(t===0)return e.slice(0,o).trimEnd();t++}else a==="}"&&t--}return e}function Ze(e){let t=0,s=0,n=!1,o="";for(let a=0;a<e.length;a++){let i=e[a];n?i===o&&e[a-1]!=="\\"&&(n=!1):i==='"'||i==="'"||i==="`"?(n=!0,o=i):i==="{"?t++:i==="}"&&s++}return{opens:t,closes:s}}function et(e){let t=[],s=e.split(`
17
17
  `);for(let n=0;n<s.length;n++){let o=s[n];if(!o.trim())continue;let a=o.trimStart();a.startsWith("import ")||a.startsWith("from ")?t.push(o):(a.startsWith("class ")||a.startsWith("def ")||a.startsWith("async def "))&&t.push(`${n+1}: ${o.trim().replace(/:(\s*)$/,":")}`)}return t.join(`
18
- `)}function Ke(e){let t=[];for(let n of e.matchAll(/<(template|script|style)(\s[^>]*)?\s*>/g))t.push(n[0]);let s=e.match(/<script(?:\s[^>]*)?\s*>([\s\S]*?)<\/script>/);if(s){let n=se(s[1]);n.trim()&&(t.push("// script:"),t.push(n))}return t.join(`
19
- `)}function Xe(e){let t=[],s=e.split(`
18
+ `)}function tt(e){let t=[];for(let n of e.matchAll(/<(template|script|style)(\s[^>]*)?\s*>/g))t.push(n[0]);let s=e.match(/<script(?:\s[^>]*)?\s*>([\s\S]*?)<\/script>/);if(s){let n=ie(s[1]);n.trim()&&(t.push("// script:"),t.push(n))}return t.join(`
19
+ `)}function nt(e){let t=[],s=e.split(`
20
20
  `);for(let n=0;n<s.length;n++){let o=s[n].trim();/^(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE|SELECT|WITH|GRANT|REVOKE|BEGIN|COMMIT)\b/i.test(o)&&t.push(`${n+1}: ${o.slice(0,120)}${o.length>120?"...":""}`)}return t.join(`
21
- `)}import b from"fs";import Y from"path";function B(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),s=!1,n=null,o=[],a=new Set;for(let i of t){if(/^##\s+Phases\s*$/i.test(i)){s=!0;continue}if(/^##\s+/.test(i)&&!/^###/.test(i)){n&&(o.push(n),n=null),s=!1;continue}if(!s)continue;let l=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(i);if(l){n&&o.push(n);let u=parseInt(l[1],10),c=l[2];if(a.has(u)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${u} ignored
22
- `),n=null;continue}a.add(u),n={phase_number:u,text:c,bodyLines:[]};continue}n&&n.bodyLines.push(i)}return n&&o.push(n),o.map(i=>({phase_number:i.phase_number,text:i.text,body:i.bodyLines.join(`
23
- `).trim()||null}))}function xe(e,t,s){let n=e.prepare("SELECT id, phase_number, status FROM main.roadmap_phases WHERE decision_name = ? ORDER BY phase_number").all(t),o=new Map(n.map(r=>[r.phase_number,r])),a=new Set,i=e.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),l=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),u=e.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),c=[],m=[],d=[];for(let r=0;r<s.length;r++){let p=s[r];a.add(p.phase_number);let h=o.get(p.phase_number);if(h)i.run(p.text,p.body,"stale","open",h.id),c.push({id:h.id,phase_number:p.phase_number,text:p.text,reactivated:h.status==="stale"});else{let g=l.run(t,p.phase_number,p.text,p.body,"open",r);m.push({id:Number(g.lastInsertRowid),phase_number:p.phase_number,text:p.text})}}for(let[r,p]of o)!a.has(r)&&p.status!=="stale"&&p.status!=="done"&&(u.run(p.id),d.push({id:p.id,phase_number:r}));return{updated:c.length,inserted:m.length,staled:d.length,updated_phases:c,inserted_phases:m,staled_phases:d}}var N="roadmap",Qe=`# Roadmap
21
+ `)}import k from"fs";import U from"path";function G(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),s=!1,n=null,o=[],a=new Set;for(let i of t){if(/^##\s+Phases\s*$/i.test(i)){s=!0;continue}if(/^##\s+/.test(i)&&!/^###/.test(i)){n&&(o.push(n),n=null),s=!1;continue}if(!s)continue;let p=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(i);if(p){n&&o.push(n);let f=parseInt(p[1],10),c=p[2];if(a.has(f)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${f} ignored
22
+ `),n=null;continue}a.add(f),n={phase_number:f,text:c,bodyLines:[]};continue}n&&n.bodyLines.push(i)}return n&&o.push(n),o.map(i=>({phase_number:i.phase_number,text:i.text,body:i.bodyLines.join(`
23
+ `).trim()||null}))}function be(e,t,s){let n=e.prepare("SELECT id, phase_number, status FROM main.roadmap_phases WHERE decision_name = ? ORDER BY phase_number").all(t),o=new Map(n.map(r=>[r.phase_number,r])),a=new Set,i=e.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),p=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),f=e.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),c=[],m=[],u=[];for(let r=0;r<s.length;r++){let l=s[r];a.add(l.phase_number);let d=o.get(l.phase_number);if(d)i.run(l.text,l.body,"stale","open",d.id),c.push({id:d.id,phase_number:l.phase_number,text:l.text,reactivated:d.status==="stale"});else{let g=p.run(t,l.phase_number,l.text,l.body,"open",r);m.push({id:Number(g.lastInsertRowid),phase_number:l.phase_number,text:l.text})}}for(let[r,l]of o)!a.has(r)&&l.status!=="stale"&&l.status!=="done"&&(f.run(l.id),u.push({id:l.id,phase_number:r}));return{updated:c.length,inserted:m.length,staled:u.length,updated_phases:c,inserted_phases:m,staled_phases:u}}var P="roadmap",st=`# Roadmap
24
24
 
25
25
  The forward-looking plan: only upcoming and in-progress phases live here. When a
26
26
  phase is marked done (cairn_roadmap set_status <id> done) it is removed from this
@@ -31,63 +31,67 @@ the plan \u2014 Cairn syncs it into the live tracker on the next cairn_roadmap r
31
31
 
32
32
  ### Phase 1: First phase title
33
33
  What this phase delivers.
34
- `;function D(){return Y.join(J(),"roadmap.md")}function X(){let e=D();return b.existsSync(e)?b.readFileSync(e,"utf8"):""}function re(){let e=D();return b.existsSync(e)?!1:(b.writeFileSync(e,Qe,"utf8"),!0)}function Ze(e){let t=[];for(let s of e){let n=(s.text||"").trim();n&&(!Number.isInteger(s.phase_number)||s.phase_number<1||t.push({phase_number:s.phase_number,text:n,body:s.body??null}))}return t}var oe="decision_production_roadmap.md";function et(e){let t=e.match(/^---\n[\s\S]*?\n---\n?/);return t?e.slice(t[0].length):e}function tt(e,t){let s=Y.join(e,"MEMORY.md");if(!b.existsSync(s))return;let n=b.readFileSync(s,"utf8").split(/\r?\n/).filter(o=>!o.includes(`](${t})`));b.writeFileSync(s,n.join(`
35
- `),"utf8")}function nt(){let e=D();if(b.existsSync(e))return null;let t=[],s=ye();s&&t.push(s),t.push(Y.join(J(),"memory"));let n=null,o=null;for(let i of t){let l=Y.join(i,oe);n==null&&b.existsSync(l)&&(n=et(b.readFileSync(l,"utf8")).trim(),o=l)}if(n==null)return null;let a=/^#\s/m.test(n)?"":`# Roadmap
34
+ `;function C(){return U.join(I(),"roadmap.md")}function ot(){return U.join(I(),"roadmap_completed.md")}function Q(){let e=C();return k.existsSync(e)?k.readFileSync(e,"utf8"):""}function ce(){let e=C();return k.existsSync(e)?!1:(k.writeFileSync(e,st,"utf8"),!0)}function rt(e){let t=[];for(let s of e){let n=(s.text||"").trim();n&&(!Number.isInteger(s.phase_number)||s.phase_number<1||t.push({phase_number:s.phase_number,text:n,body:s.body??null}))}return t}var ae="decision_production_roadmap.md";function it(e){let t=e.match(/^---\n[\s\S]*?\n---\n?/);return t?e.slice(t[0].length):e}function at(e,t){let s=U.join(e,"MEMORY.md");if(!k.existsSync(s))return;let n=k.readFileSync(s,"utf8").split(/\r?\n/).filter(o=>!o.includes(`](${t})`));k.writeFileSync(s,n.join(`
35
+ `),"utf8")}function ct(){let e=C();if(k.existsSync(e))return null;let t=[],s=Se();s&&t.push(s),t.push(U.join(I(),"memory"));let n=null,o=null;for(let i of t){let p=U.join(i,ae);n==null&&k.existsSync(p)&&(n=it(k.readFileSync(p,"utf8")).trim(),o=p)}if(n==null)return null;let a=/^#\s/m.test(n)?"":`# Roadmap
36
36
 
37
- `;b.writeFileSync(e,a+n+`
38
- `,"utf8");for(let i of t){let l=Y.join(i,oe);try{b.existsSync(l)&&b.unlinkSync(l)}catch{}try{tt(i,oe)}catch{}}return{migrated:!0,from:o,to:e}}function I(e){nt();let t=D();if(!b.existsSync(t))return null;let s=b.readFileSync(t,"utf8");if(!/^##\s+Phases\s*$/im.test(s))return null;let n=Ze(B(s));return xe(e,N,n)}import W from"fs";import ie from"path";import{execSync as st}from"child_process";var Ee=120;function ce(e){if(!e)return"";let t=String(e).replace(/`([^`]*)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/\[([^\]]+)\]\([^)]*\)/g,"$1").replace(/^[-*+\s]+/,"").replace(/^#{1,6}\s*/,"").replace(/[*~]/g,"").replace(/\s+/g," ").trim().replace(/[::.\s]+$/,"");return t.length>Ee&&(t=t.slice(0,Ee-1).trimEnd()+"\u2026"),t}function F(e){return String(e||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim()}function ot(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),s=c=>c.map(m=>({text:ce(m.titleRaw),body:m.bodyLines.join(`
39
- `).trim()||null})).filter(m=>m.text),n=[],o=null;for(let c of t){let m=/^(#{1,6})\s+(.+?)\s*$/.exec(c);if(m&&m[1].length>=2&&m[1].length<=4){o&&n.push(o),o={titleRaw:m[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&n.push(o);let a=[];o=null;for(let c of t){let m=/^(\d+)[.)]\s+(.+?)\s*$/.exec(c);if(m){o&&a.push(o),o={titleRaw:m[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&a.push(o);let i=[s(n),s(a)].filter(c=>c.length>=2);if(i.length)return i.sort((c,m)=>m.length-c.length),i[0];let l=t.find(c=>c.trim());return[{text:ce(l||"Plan")||"Plan",body:e.trim()||null}]}function rt(e){let t=[];return e.forEach((s,n)=>{t.push(`### Phase ${n+1}: ${s.text}`),s.body&&s.body.trim()&&t.push(s.body.trim()),t.push("")}),t.join(`
37
+ `;k.writeFileSync(e,a+n+`
38
+ `,"utf8");for(let i of t){let p=U.join(i,ae);try{k.existsSync(p)&&k.unlinkSync(p)}catch{}try{at(i,ae)}catch{}}return{migrated:!0,from:o,to:e}}function H(e){ct();let t=C();if(!k.existsSync(t))return null;let s=k.readFileSync(t,"utf8");if(!/^##\s+Phases\s*$/im.test(s))return null;let n=rt(G(s));return be(e,P,n)}function le(){let e=ot();return k.existsSync(e)?k.readFileSync(e,"utf8").split(/\r?\n/).filter(t=>/^-\s+\d{4}-\d{2}-\d{2}\s+—\s+Phase\s/.test(t)).length:0}import J from"fs";import pe from"path";import{execSync as lt}from"child_process";var ke=120;function ue(e){if(!e)return"";let t=String(e).replace(/`([^`]*)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/\[([^\]]+)\]\([^)]*\)/g,"$1").replace(/^[-*+\s]+/,"").replace(/^#{1,6}\s*/,"").replace(/[*~]/g,"").replace(/\s+/g," ").trim().replace(/[::.\s]+$/,"");return t.length>ke&&(t=t.slice(0,ke-1).trimEnd()+"\u2026"),t}function N(e){return String(e||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim()}function pt(e){if(!e||typeof e!="string")return[];let t=e.split(/\r?\n/),s=c=>c.map(m=>({text:ue(m.titleRaw),body:m.bodyLines.join(`
39
+ `).trim()||null})).filter(m=>m.text),n=[],o=null;for(let c of t){let m=/^(#{1,6})\s+(.+?)\s*$/.exec(c);if(m&&m[1].length>=2&&m[1].length<=4){o&&n.push(o),o={titleRaw:m[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&n.push(o);let a=[];o=null;for(let c of t){let m=/^(\d+)[.)]\s+(.+?)\s*$/.exec(c);if(m){o&&a.push(o),o={titleRaw:m[2],bodyLines:[]};continue}o&&o.bodyLines.push(c)}o&&a.push(o);let i=[s(n),s(a)].filter(c=>c.length>=2);if(i.length)return i.sort((c,m)=>m.length-c.length),i[0];let p=t.find(c=>c.trim());return[{text:ue(p||"Plan")||"Plan",body:e.trim()||null}]}function dt(e){let t=[];return e.forEach((s,n)=>{t.push(`### Phase ${n+1}: ${s.text}`),s.body&&s.body.trim()&&t.push(s.body.trim()),t.push("")}),t.join(`
40
40
  `).replace(/\n{3,}/g,`
41
41
 
42
42
  `).trimEnd()+`
43
- `}function we(e){let t=X(),s=t.split(/\r?\n/),n=s.findIndex(c=>/^##\s+Phases\s*$/i.test(c)),o=rt(e);if(n===-1){let c=t.replace(/\s*$/,"");W.writeFileSync(D(),`${c}
43
+ `}function $e(e){let t=Q(),s=t.split(/\r?\n/),n=s.findIndex(c=>/^##\s+Phases\s*$/i.test(c)),o=dt(e);if(n===-1){let c=t.replace(/\s*$/,"");J.writeFileSync(C(),`${c}
44
44
 
45
45
  ## Phases
46
46
 
47
47
  ${o}`,"utf8");return}let a=s.length;for(let c=n+1;c<s.length;c++)if(/^##\s+/.test(s[c])&&!/^###/.test(s[c])){a=c;break}let i=s.slice(0,n+1).join(`
48
- `),l=s.slice(a).join(`
49
- `).replace(/^\n+/,""),u=`
48
+ `),p=s.slice(a).join(`
49
+ `).replace(/^\n+/,""),f=`
50
50
 
51
- ${o}${l.trim()?`
52
- `+l:""}`;W.writeFileSync(D(),i+u,"utf8")}function ke(e){let t=ot(e);if(t.length===0)return{added:[],skipped:[],total:0};let n=re()?[]:B(X()).map(u=>({text:u.text,body:u.body})),o=new Set(n.map(u=>F(u.text))),a=[...n],i=[],l=[];for(let u of t){let c=F(u.text);if(o.has(c)){l.push(u.text);continue}o.add(c),a.push(u),i.push(u.text)}return i.length>0&&we(a),{added:i,skipped:l,total:a.length}}function it(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function at(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function $e(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let s=t.map(r=>({text:ce(r.content||r.activeForm||""),status:it(r.status)})).filter(r=>r.text);if(s.length===0)return{changed:!1};let n=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(N).filter(r=>r.status!=="stale");if(n.length===0){let p=re()?[]:B(X()).map(S=>({text:S.text,body:S.body})),h=new Set(p.map(S=>F(S.text))),g=[...p],w=0;for(let S of s){let v=F(S.text);h.has(v)||(h.add(v),g.push({text:S.text,body:null}),w++)}if(w===0)return{changed:!1};we(g);let _=I(e);return{changed:!0,seeded:w,diff:_}}let o=at(e);o==null&&(o=n.find(r=>r.status==="in_progress")?.id??n.find(r=>r.status==="open")?.id??n[0].id);let a=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),i=new Map(a.map(r=>[F(r.text),r])),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 = ?"),c=e.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(o).n,m=0,d=0;for(let r of s){let p=i.get(F(r.text));if(p){if(p.status==="done"&&r.status!=="done")continue;p.status!==r.status&&(u.run(r.status,p.id),d++)}else l.run(o,r.text,r.status,c++),m++}return{changed:m>0||d>0,parentId:o,inserted:m,updated:d}}var ct=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 pt(e){return[...new Set(F(e).split(" ").filter(t=>t.length>=4&&!ct.has(t)))]}function lt(e,t){if(!e||!t?.length)return null;let s=String(e),n=/\bphase\s*#?\s*(\d+)\b/i.exec(s);if(n){let i=parseInt(n[1],10),l=t.find(u=>u.phase_number===i);if(l)return{...l,confidence:"high"}}let o=new Set(F(s).split(" ").filter(Boolean)),a=null;for(let i of t){let l=pt(i.text);if(l.length<2)continue;let u=l.filter(m=>o.has(m)),c=u.length/l.length;u.length>=2&&c>=.6&&(!a||c>a.ratio)&&(a={...i,confidence:"medium",ratio:c})}return a?(delete a.ratio,a):null}function ae(e,t){return st(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3}).trim()}function be(e,t){let s=ie.join(t,".cairn"),n=ie.join(s,".last-commit"),o=ie.join(s,"roadmap-pending.json"),a;try{a=ae(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let i=null;try{i=W.readFileSync(n,"utf8").trim()||null}catch{}let l=[];if(i&&i!==a)try{l=ae(t,`rev-list --no-merges --reverse ${i}..HEAD`).split(`
53
- `).filter(Boolean)}catch{l=[]}try{W.writeFileSync(n,a,"utf8")}catch{}let u=[];try{let c=JSON.parse(W.readFileSync(o,"utf8"));Array.isArray(c)&&(u=c)}catch{}if(l.length){let c=e.prepare("SELECT id, phase_number, text FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL AND status IN ('open','in_progress') AND phase_number IS NOT NULL").all(N);for(let m of l){let d="";try{d=ae(t,`show -s --format=%s%n%b ${m}`)}catch{}let r=lt(d,c);r&&!u.some(p=>p.phase_id===r.id)&&u.push({phase_id:r.id,phase_number:r.phase_number,text:r.text,commit:m.slice(0,7),confidence:r.confidence})}}if(u.length){let c=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(m=>m.id));u=u.filter(m=>c.has(m.phase_id))}try{W.writeFileSync(o,JSON.stringify(u),"utf8")}catch{}return{candidates:u,newCommits:l.length}}var dt=new Set(["open","in_progress"]);function ut(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 mt(e,t){let s=e.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),n=e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),o=s.get(t);for(;o&&o.parent_phase_id!=null;)n.run(o.parent_phase_id),o=s.get(o.parent_phase_id)}function ve(e,t){ut(e,t),e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(t),mt(e,t)}function ft(e){let t=new Map,s;try{s=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let n of s)t.has(n.blocked_id)||t.set(n.blocked_id,new Set),t.get(n.blocked_id).add(n.blocker_id);return t}function ht(e,t,s){let n=t.get(e);if(!n||n.size===0)return[];let o=[];for(let a of n)s.get(a)!=="done"&&o.push(a);return o}function Re(e,t){let s=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(s.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let n=new Map(s.map(d=>[d.id,d.status])),o=ft(e),a=new Map;for(let d of s){let r=d.parent_phase_id??`root:${d.decision_name}`;a.has(r)||a.set(r,[]),a.get(r).push(d)}for(let d of a.values())d.sort((r,p)=>r.phase_number!=null&&p.phase_number!=null?r.phase_number-p.phase_number:r.position-p.position||r.id-p.id);let i=[],l=new Set;for(let d of s){if(d.parent_phase_id!=null||l.has(d.decision_name))continue;l.add(d.decision_name);let r=[],p=a.get(`root:${d.decision_name}`)||[];for(let h=p.length-1;h>=0;h--)r.push(p[h]);for(;r.length;){let h=r.pop();i.push(h);let g=a.get(h.id)||[];for(let w=g.length-1;w>=0;w--)r.push(g[w])}}let u=d=>dt.has(d.status),c=[],m=0;if(t!=null){let d=i.findIndex(r=>r.id===t);m=d===-1?0:d+1}for(let d=m;d<i.length;d++){let r=i[d];if(!u(r))continue;let p=ht(r.id,o,n);if(p.length>0){c.push({id:r.id,blockers:p});continue}let h=t!=null?s.find(_=>_.id===t):null,g=h&&h.decision_name!==r.decision_name,w;return t==null?w=`First actionable phase: ${r.decision_name??"(sub-task)"} / Phase ${r.phase_number??"\xB7"}.`:g?w=`Previous decision "${h.decision_name}" is complete \u2014 advanced to "${r.decision_name}".`:w=`Next open phase in "${r.decision_name??"(sub-task)"}" after id ${t}.`,c.length>0&&(w+=` Skipped ${c.length} dep-blocked phase(s): ${c.map(_=>`id ${_.id} (blocked by ${_.blockers.join(",")})`).join("; ")}.`),{id:r.id,reason:w,skipped_blocked:c}}return c.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${c.map(d=>`id ${d.id} (blocked by ${d.blockers.join(",")})`).join("; ")}.`,skipped_blocked:c}:{id:null,reason:"No actionable phases remaining."}}var yt=new Set(["java","typescript","javascript","vue","python","sql"]);function Oe(e){let t=y.join(U.homedir(),".claude","projects",ee(e));return f.existsSync(t)?y.join(t,"memory"):null}function Te(e){try{return f.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}}function gt(e){let t=Oe(e);if(!t||!f.existsSync(t))return 0;let s=y.join(e,".cairn","memory");f.mkdirSync(s,{recursive:!0});let n=0;for(let o of Te(t)){let a=y.join(t,o),i;try{i=f.statSync(a)}catch{continue}if(!i.isFile())continue;let l=y.join(s,o),u=!f.existsSync(l);if(!u)try{u=i.mtimeMs>f.statSync(l).mtimeMs}catch{u=!0}if(u){f.copyFileSync(a,l);try{f.utimesSync(l,i.atime,i.mtime)}catch{}n++}}return n}function _t(e){let t=Oe(e);if(!t||(f.mkdirSync(t,{recursive:!0}),Te(t).some(o=>o!=="MEMORY.md")))return 0;let s=y.join(e,".cairn","memory");if(!f.existsSync(s))return 0;let n=0;for(let o of f.readdirSync(s)){let a=y.join(s,o),i;try{i=f.statSync(a)}catch{continue}if(i.isFile()){f.copyFileSync(a,y.join(t,o));try{f.utimesSync(y.join(t,o),i.atime,i.mtime)}catch{}n++}}return n}async function je(e){let t={active:!1},s=y.join(e,"index.db");if(!f.existsSync(s))return t;let n;try{({DatabaseSync:n}=await import("node:sqlite"))}catch{return t}let o;try{o=new n(s,{readonly:!0})}catch{return t}let a=l=>l.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");function i(l,u){if(!f.existsSync(l))return null;let c=f.readFileSync(l,"utf8"),m=!1,d=!1,r=[];for(let p of c.split(/\r?\n/)){if(/^##\s+Phases\s*$/i.test(p)){m=!0;continue}if(m&&/^##\s+/.test(p)&&!/^###/.test(p))break;if(!m)continue;let h=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(p);if(h){if(d)break;parseInt(h[1],10)===u&&(r.push(`### Phase ${h[1]}: ${h[2]}`),d=!0);continue}d&&r.push(p)}return r.join(`
54
- `).trim()||null}try{let u=o.prepare("SELECT phase_id FROM roadmap_cursor WHERE singleton = 1").get()?.phase_id??null;if(u==null)return o.close(),t;let c=o.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, status, position FROM roadmap_phases ORDER BY decision_name, phase_number, position, id").all(),m={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},d=new Map;for(let k of c){let x=k.parent_phase_id??"root";d.has(x)||d.set(x,[]),d.get(x).push(k)}for(let k of d.values())k.sort((x,E)=>x.decision_name&&E.decision_name&&x.decision_name!==E.decision_name?x.decision_name.localeCompare(E.decision_name):x.phase_number!=null&&E.phase_number!=null?x.phase_number-E.phase_number:x.position-E.position||x.id-E.id);let r=d.get("root")||[],p=new Map;for(let k of r)k.decision_name!=null&&(p.has(k.decision_name)||p.set(k.decision_name,[]),p.get(k.decision_name).push(k));let h=[],g=(k,x)=>{for(let E of d.get(k)||[]){if(E.status==="stale")continue;let Q=u===E.id?" \u2192":"",Z=E.phase_number!=null?`Phase ${E.phase_number}: ${E.text}`:E.text;h.push(`${" ".repeat(x)}${m[E.status]||"[?]"}${Q} ${Z}`),g(E.id,x+1)}};for(let[k,x]of p){let E=x.filter($=>$.status!=="stale");if(E.length===0)continue;let Q=E.every($=>$.status==="done"),Z=E.some($=>$.status==="in_progress"),Le=Q?"done":Z?"in_progress":"open",Me=E.filter($=>$.status==="done").length,Ne=k===N?"Roadmap":`Decision: ${k}`;h.push(`${m[Le]} ${Ne} (${Me}/${E.length} done)`);for(let $ of x){if($.status==="stale"||k===N&&$.status==="done")continue;let Fe=u===$.id?" \u2192":"";h.push(` ${m[$.status]||"[?]"}${Fe} Phase ${$.phase_number}: ${$.text}`),g($.id,2)}}let w=k=>c.find(x=>x.id===k),_=w(u),S=_?.decision_name??null,v=_?.phase_number??null,O=_;for(;O&&O.parent_phase_id!=null&&(O=w(O.parent_phase_id),!!O);)S=O.decision_name??S,v=O.phase_number??v;o.close();let G=_?_.phase_number!=null?`${_.decision_name} / Phase ${_.phase_number}: ${_.text}`:`${S} / Phase ${v} \u2192 ${_.text}`:"(unknown)",q=y.join(e,".last-roadmap-cursor"),le=null;try{le=parseInt(f.readFileSync(q,"utf8").trim(),10)}catch{}let de=le!==u;try{f.writeFileSync(q,String(u),"utf8")}catch{}let ue=null;if(de&&S!=null&&v!=null){let k=S===N?y.join(e,"roadmap.md"):y.join(e,"memory",`decision_${a(S)}.md`),x=i(k,v);x&&(ue=x.split(`
55
- `))}return{active:!0,currentLabel:G,treeLines:h,advanced:de,memoLines:ue}}catch{try{o?.close()}catch{}return t}}process.on("uncaughtException",e=>{process.stderr.write(`cairn: ${e.message}
56
- `),process.exit(0)});var R=process.argv[2];if(R==="--version"||R==="-v"){let e=JSON.parse(f.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(R==="minify"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t,s;try{s=JSON.parse(e)?.tool_input||{},t=s.file_path}catch{process.exit(0)}t||process.exit(0),(s.offset!=null||s.limit!=null)&&process.exit(0),t=y.resolve(t);let n=ne(t);n||process.exit(0);let o=y.join(n,"views"),a=y.relative(o,t);!a.startsWith("..")&&!y.isAbsolute(a)&&process.exit(0);let i=C(n),u=K(n,i)>0,c=i[t];c?.state==="edit-ready"&&(u&&P(n,i),process.exit(0)),c?.state==="compressed"&&(c.state="edit-ready",P(n,i),process.exit(0));let m=y.extname(t).toLowerCase(),d=me[m];(!d||!yt.has(d))&&(u&&P(n,i),process.exit(0));let r;try{r=f.readFileSync(t,"utf8")}catch{u&&P(n,i),process.exit(0)}let p=r.split(`
57
- `).length,h=d==="vue"?fe(r):V(r,d),g=h.split(`
58
- `).length,w=p>0?Math.round((1-g/p)*100):0,_=_e(n,t);f.mkdirSync(y.dirname(_),{recursive:!0}),f.writeFileSync(_,h,"utf8");let S=c?.readCount??0;i[t]={tempPath:_,state:"compressed",minifiedAt:f.statSync(t).mtimeMs,readCount:S+1},P(n,i);let v=g>ge,O=S>0;if(O||v){let G=Se(h,d),q=O?`[cairn: ${p} \u2192 ${g} lines | seen \xD7${S+1} this session]`:`[cairn: ${p} \u2192 outline (${g} minified lines)]`;process.stderr.write(`${q}
59
- ${G}
51
+ ${o}${p.trim()?`
52
+ `+p:""}`;J.writeFileSync(C(),i+f,"utf8")}function Re(e){let t=pt(e);if(t.length===0)return{added:[],skipped:[],total:0};let n=ce()?[]:G(Q()).map(f=>({text:f.text,body:f.body})),o=new Set(n.map(f=>N(f.text))),a=[...n],i=[],p=[];for(let f of t){let c=N(f.text);if(o.has(c)){p.push(f.text);continue}o.add(c),a.push(f),i.push(f.text)}return i.length>0&&$e(a),{added:i,skipped:p,total:a.length}}function ut(e){return e==="completed"?"done":e==="in_progress"?"in_progress":"open"}function mt(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function ve(e,t){if(!Array.isArray(t)||t.length===0)return{changed:!1};let s=t.map(r=>({text:ue(r.content||r.activeForm||""),status:ut(r.status)})).filter(r=>r.text);if(s.length===0)return{changed:!1};let n=e.prepare("SELECT id, status FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL").all(P).filter(r=>r.status!=="stale");if(n.length===0){let l=ce()?[]:G(Q()).map(x=>({text:x.text,body:x.body})),d=new Set(l.map(x=>N(x.text))),g=[...l],S=0;for(let x of s){let $=N(x.text);d.has($)||(d.add($),g.push({text:x.text,body:null}),S++)}if(S===0)return{changed:!1};$e(g);let _=H(e);return{changed:!0,seeded:S,diff:_}}let o=mt(e);o==null&&(o=n.find(r=>r.status==="in_progress")?.id??n.find(r=>r.status==="open")?.id??n[0].id);let a=e.prepare("SELECT id, text, status FROM main.roadmap_phases WHERE parent_phase_id = ?").all(o),i=new Map(a.map(r=>[N(r.text),r])),p=e.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, status, position) VALUES (NULL, NULL, ?, ?, ?, ?)"),f=e.prepare("UPDATE main.roadmap_phases SET status = ? WHERE id = ?"),c=e.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS n FROM main.roadmap_phases WHERE parent_phase_id = ?").get(o).n,m=0,u=0;for(let r of s){let l=i.get(N(r.text));if(l){if(l.status==="done"&&r.status!=="done")continue;l.status!==r.status&&(f.run(r.status,l.id),u++)}else p.run(o,r.text,r.status,c++),m++}return{changed:m>0||u>0,parentId:o,inserted:m,updated:u}}var ft=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 ht(e){return[...new Set(N(e).split(" ").filter(t=>t.length>=4&&!ft.has(t)))]}function yt(e,t){if(!e||!t?.length)return null;let s=String(e),n=/\bphase\s*#?\s*(\d+)\b/i.exec(s);if(n){let i=parseInt(n[1],10),p=t.find(f=>f.phase_number===i);if(p)return{...p,confidence:"high"}}let o=new Set(N(s).split(" ").filter(Boolean)),a=null;for(let i of t){let p=ht(i.text);if(p.length<2)continue;let f=p.filter(m=>o.has(m)),c=f.length/p.length;f.length>=2&&c>=.6&&(!a||c>a.ratio)&&(a={...i,confidence:"medium",ratio:c})}return a?(delete a.ratio,a):null}function de(e,t){return lt(`git ${t}`,{cwd:e,encoding:"utf8",timeout:5e3}).trim()}function Pe(e,t){let s=pe.join(t,".cairn"),n=pe.join(s,".last-commit"),o=pe.join(s,"roadmap-pending.json"),a;try{a=de(t,"rev-parse HEAD")}catch{return{candidates:[],newCommits:0}}let i=null;try{i=J.readFileSync(n,"utf8").trim()||null}catch{}let p=[];if(i&&i!==a)try{p=de(t,`rev-list --no-merges --reverse ${i}..HEAD`).split(`
53
+ `).filter(Boolean)}catch{p=[]}try{J.writeFileSync(n,a,"utf8")}catch{}let f=[];try{let c=JSON.parse(J.readFileSync(o,"utf8"));Array.isArray(c)&&(f=c)}catch{}if(p.length){let c=e.prepare("SELECT id, phase_number, text FROM main.roadmap_phases WHERE decision_name = ? AND parent_phase_id IS NULL AND status IN ('open','in_progress') AND phase_number IS NOT NULL").all(P);for(let m of p){let u="";try{u=de(t,`show -s --format=%s%n%b ${m}`)}catch{}let r=yt(u,c);r&&!f.some(l=>l.phase_id===r.id)&&f.push({phase_id:r.id,phase_number:r.phase_number,text:r.text,commit:m.slice(0,7),confidence:r.confidence})}}if(f.length){let c=new Set(e.prepare("SELECT id FROM main.roadmap_phases WHERE status IN ('open','in_progress')").all().map(m=>m.id));f=f.filter(m=>c.has(m.phase_id))}try{J.writeFileSync(o,JSON.stringify(f),"utf8")}catch{}return{candidates:f,newCommits:p.length}}import Z from"fs";import gt from"path";var je="<!-- cairn:roadmap -->",_t={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"};function St(e){return e==="done"?"x":e==="in_progress"?"~":e==="blocked"?"!":" "}function xt(){return gt.join(K(),"ROADMAP.md")}function wt(e){try{return e.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Et(e){let t=new Map,s;try{s=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let n of s)t.has(n.blocked_id)||t.set(n.blocked_id,new Set),t.get(n.blocked_id).add(n.blocker_id);return t}function bt(e,t){let s=e.decision_name??"",n=t.decision_name??"";return s!==n?s<n?-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 kt(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(d=>d.status!=="stale").length===0)return null;let s=wt(e),n=Et(e),o=new Map(t.map(d=>[d.id,d.status])),a=new Map(t.map(d=>[d.id,d.phase_number!=null?`Phase ${d.phase_number}`:d.text])),i=d=>{let g=n.get(d);return g?[...g].filter(S=>o.get(S)!=="done").map(S=>a.get(S)||`#${S}`):[]},p=new Map;for(let d of t){let g=d.parent_phase_id??"root";p.has(g)||p.set(g,[]),p.get(g).push(d)}for(let d of p.values())d.sort(bt);let f=new Map;for(let d of p.get("root")||[])d.decision_name!=null&&(f.has(d.decision_name)||f.set(d.decision_name,[]),f.get(d.decision_name).push(d));let c=[...f.keys()].sort((d,g)=>d===P?-1:g===P?1:d<g?-1:d>g?1:0),m=[je,"<!--"," 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.","-->","","# Roadmap","","Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked."],u=(d,g)=>{let S=" ".repeat(g),_=d.phase_number!=null?`Phase ${d.phase_number}: ${d.text}`:d.text,x=_t[d.status]||d.status,$=i(d.id);if($.length&&(x+=` \xB7 waiting on ${$.join(", ")}`),s===d.id&&(x+=" \xB7 \u2190 current"),m.push(`${S}- [${St(d.status)}] **${_}** \u2014 ${x}`),d.body){let R=d.body.split(`
54
+ `).map(A=>A.trim()).find(Boolean);R&&m.push(`${S} ${R}`)}for(let R of p.get(d.id)||[])R.status!=="stale"&&u(R,g+1)},r=!1;for(let d of c){let g=f.get(d).filter(_=>_.status!=="stale"),S=d===P?g.filter(_=>_.status!=="done"):g;if(S.length!==0){if(r=!0,m.push(""),d===P)m.push("## Active plan");else{let _=g.filter(x=>x.status==="done").length;m.push(`## ${d} \u2014 ${_}/${g.length} done`)}m.push("");for(let _ of S)u(_,0)}}if(!r)return null;let l=le();return l>0&&m.push("","---","",`**Shipped:** ${l} phase${l===1?"":"s"} \u2014 see \`.cairn/roadmap_completed.md\`.`),m.join(`
55
+ `)+`
56
+ `}function Oe(e){let t=xt(),s=kt(e),n=null;if(Z.existsSync(t))try{n=Z.readFileSync(t,"utf8")}catch{n=null}if(n!=null&&!n.startsWith(je))return{written:!1,reason:"ROADMAP.md exists and is not cairn-generated \u2014 refusing to overwrite",path:t};if(s==null){if(n!=null)try{return Z.unlinkSync(t),{written:!1,removed:!0,path:t}}catch(o){return{written:!1,reason:`remove failed: ${o.message}`,path:t}}return{written:!1,reason:"empty roadmap \u2014 no file written",path:t}}return n===s?{written:!1,unchanged:!0,path:t,bytes:Buffer.byteLength(s)}:(Z.writeFileSync(t,s,"utf8"),{written:!0,path:t,bytes:Buffer.byteLength(s)})}function ee(e){try{return Oe(e)}catch(t){return process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${t.message}
57
+ `),null}}var $t=new Set(["open","in_progress"]);function Rt(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 vt(e,t){let s=e.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),n=e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),o=s.get(t);for(;o&&o.parent_phase_id!=null;)n.run(o.parent_phase_id),o=s.get(o.parent_phase_id)}function Te(e,t){Rt(e,t),e.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(t),vt(e,t)}function Pt(e){let t=new Map,s;try{s=e.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return t}for(let n of s)t.has(n.blocked_id)||t.set(n.blocked_id,new Set),t.get(n.blocked_id).add(n.blocker_id);return t}function jt(e,t,s){let n=t.get(e);if(!n||n.size===0)return[];let o=[];for(let a of n)s.get(a)!=="done"&&o.push(a);return o}function Me(e,t){let s=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(s.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let n=new Map(s.map(u=>[u.id,u.status])),o=Pt(e),a=new Map;for(let u of s){let r=u.parent_phase_id??`root:${u.decision_name}`;a.has(r)||a.set(r,[]),a.get(r).push(u)}for(let u of a.values())u.sort((r,l)=>r.phase_number!=null&&l.phase_number!=null?r.phase_number-l.phase_number:r.position-l.position||r.id-l.id);let i=[],p=new Set;for(let u of s){if(u.parent_phase_id!=null||p.has(u.decision_name))continue;p.add(u.decision_name);let r=[],l=a.get(`root:${u.decision_name}`)||[];for(let d=l.length-1;d>=0;d--)r.push(l[d]);for(;r.length;){let d=r.pop();i.push(d);let g=a.get(d.id)||[];for(let S=g.length-1;S>=0;S--)r.push(g[S])}}let f=u=>$t.has(u.status),c=[],m=0;if(t!=null){let u=i.findIndex(r=>r.id===t);m=u===-1?0:u+1}for(let u=m;u<i.length;u++){let r=i[u];if(!f(r))continue;let l=jt(r.id,o,n);if(l.length>0){c.push({id:r.id,blockers:l});continue}let d=t!=null?s.find(_=>_.id===t):null,g=d&&d.decision_name!==r.decision_name,S;return t==null?S=`First actionable phase: ${r.decision_name??"(sub-task)"} / Phase ${r.phase_number??"\xB7"}.`:g?S=`Previous decision "${d.decision_name}" is complete \u2014 advanced to "${r.decision_name}".`:S=`Next open phase in "${r.decision_name??"(sub-task)"}" after id ${t}.`,c.length>0&&(S+=` Skipped ${c.length} dep-blocked phase(s): ${c.map(_=>`id ${_.id} (blocked by ${_.blockers.join(",")})`).join("; ")}.`),{id:r.id,reason:S,skipped_blocked:c}}return c.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${c.map(u=>`id ${u.id} (blocked by ${u.blockers.join(",")})`).join("; ")}.`,skipped_blocked:c}:{id:null,reason:"No actionable phases remaining."}}var Ot=new Set(["java","typescript","javascript","vue","python","sql"]);function Ne(e){let t=y.join(B.homedir(),".claude","projects",se(e));return h.existsSync(t)?y.join(t,"memory"):null}function De(e){try{return h.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}}function Tt(e){let t=Ne(e);if(!t||!h.existsSync(t))return 0;let s=y.join(e,".cairn","memory");h.mkdirSync(s,{recursive:!0});let n=0;for(let o of De(t)){let a=y.join(t,o),i;try{i=h.statSync(a)}catch{continue}if(!i.isFile())continue;let p=y.join(s,o),f=!h.existsSync(p);if(!f)try{f=i.mtimeMs>h.statSync(p).mtimeMs}catch{f=!0}if(f){h.copyFileSync(a,p);try{h.utimesSync(p,i.atime,i.mtime)}catch{}n++}}return n}function Mt(e){let t=Ne(e);if(!t||(h.mkdirSync(t,{recursive:!0}),De(t).some(o=>o!=="MEMORY.md")))return 0;let s=y.join(e,".cairn","memory");if(!h.existsSync(s))return 0;let n=0;for(let o of h.readdirSync(s)){let a=y.join(s,o),i;try{i=h.statSync(a)}catch{continue}if(i.isFile()){h.copyFileSync(a,y.join(t,o));try{h.utimesSync(y.join(t,o),i.atime,i.mtime)}catch{}n++}}return n}async function Le(e){let t={active:!1},s=y.join(e,"index.db");if(!h.existsSync(s))return t;let n;try{({DatabaseSync:n}=await import("node:sqlite"))}catch{return t}let o;try{o=new n(s,{readonly:!0})}catch{return t}let a=p=>p.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");function i(p,f){if(!h.existsSync(p))return null;let c=h.readFileSync(p,"utf8"),m=!1,u=!1,r=[];for(let l of c.split(/\r?\n/)){if(/^##\s+Phases\s*$/i.test(l)){m=!0;continue}if(m&&/^##\s+/.test(l)&&!/^###/.test(l))break;if(!m)continue;let d=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(l);if(d){if(u)break;parseInt(d[1],10)===f&&(r.push(`### Phase ${d[1]}: ${d[2]}`),u=!0);continue}u&&r.push(l)}return r.join(`
58
+ `).trim()||null}try{let f=o.prepare("SELECT phase_id FROM roadmap_cursor WHERE singleton = 1").get()?.phase_id??null;if(f==null)return o.close(),t;let c=o.prepare("SELECT id, decision_name, phase_number, parent_phase_id, text, status, position FROM roadmap_phases ORDER BY decision_name, phase_number, position, id").all(),m={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},u=new Map;for(let b of c){let w=b.parent_phase_id??"root";u.has(w)||u.set(w,[]),u.get(w).push(b)}for(let b of u.values())b.sort((w,E)=>w.decision_name&&E.decision_name&&w.decision_name!==E.decision_name?w.decision_name.localeCompare(E.decision_name):w.phase_number!=null&&E.phase_number!=null?w.phase_number-E.phase_number:w.position-E.position||w.id-E.id);let r=u.get("root")||[],l=new Map;for(let b of r)b.decision_name!=null&&(l.has(b.decision_name)||l.set(b.decision_name,[]),l.get(b.decision_name).push(b));let d=[],g=(b,w)=>{for(let E of u.get(b)||[]){if(E.status==="stale")continue;let te=f===E.id?" \u2192":"",ne=E.phase_number!=null?`Phase ${E.phase_number}: ${E.text}`:E.text;d.push(`${" ".repeat(w)}${m[E.status]||"[?]"}${te} ${ne}`),g(E.id,w+1)}};for(let[b,w]of l){let E=w.filter(v=>v.status!=="stale");if(E.length===0)continue;let te=E.every(v=>v.status==="done"),ne=E.some(v=>v.status==="in_progress"),Ce=te?"done":ne?"in_progress":"open",Ae=E.filter(v=>v.status==="done").length,Ie=b===P?"Roadmap":`Decision: ${b}`;d.push(`${m[Ce]} ${Ie} (${Ae}/${E.length} done)`);for(let v of w){if(v.status==="stale"||b===P&&v.status==="done")continue;let We=f===v.id?" \u2192":"";d.push(` ${m[v.status]||"[?]"}${We} Phase ${v.phase_number}: ${v.text}`),g(v.id,2)}}let S=b=>c.find(w=>w.id===b),_=S(f),x=_?.decision_name??null,$=_?.phase_number??null,R=_;for(;R&&R.parent_phase_id!=null&&(R=S(R.parent_phase_id),!!R);)x=R.decision_name??x,$=R.phase_number??$;o.close();let A=_?_.phase_number!=null?`${_.decision_name} / Phase ${_.phase_number}: ${_.text}`:`${x} / Phase ${$} \u2192 ${_.text}`:"(unknown)",q=y.join(e,".last-roadmap-cursor"),fe=null;try{fe=parseInt(h.readFileSync(q,"utf8").trim(),10)}catch{}let he=fe!==f;try{h.writeFileSync(q,String(f),"utf8")}catch{}let ye=null;if(he&&x!=null&&$!=null){let b=x===P?y.join(e,"roadmap.md"):y.join(e,"memory",`decision_${a(x)}.md`),w=i(b,$);w&&(ye=w.split(`
59
+ `))}return{active:!0,currentLabel:A,treeLines:d,advanced:he,memoLines:ye}}catch{try{o?.close()}catch{}return t}}process.on("uncaughtException",e=>{process.stderr.write(`cairn: ${e.message}
60
+ `),process.exit(0)});var j=process.argv[2];if(j==="--version"||j==="-v"){let e=JSON.parse(h.readFileSync(new URL("../package.json",import.meta.url),"utf8"));console.log(e.version),process.exit(0)}else if(j==="minify"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t,s;try{s=JSON.parse(e)?.tool_input||{},t=s.file_path}catch{process.exit(0)}t||process.exit(0),(s.offset!=null||s.limit!=null)&&process.exit(0),t=y.resolve(t);let n=re(t);n||process.exit(0);let o=y.join(n,"views"),a=y.relative(o,t);!a.startsWith("..")&&!y.isAbsolute(a)&&process.exit(0);let i=D(n),f=X(n,i)>0,c=i[t];c?.state==="edit-ready"&&(f&&T(n,i),process.exit(0)),c?.state==="compressed"&&(c.state="edit-ready",T(n,i),process.exit(0));let m=y.extname(t).toLowerCase(),u=ge[m];(!u||!Ot.has(u))&&(f&&T(n,i),process.exit(0));let r;try{r=h.readFileSync(t,"utf8")}catch{f&&T(n,i),process.exit(0)}let l=r.split(`
61
+ `).length,d=u==="vue"?_e(r):V(r,u),g=d.split(`
62
+ `).length,S=l>0?Math.round((1-g/l)*100):0,_=we(n,t);h.mkdirSync(y.dirname(_),{recursive:!0}),h.writeFileSync(_,d,"utf8");let x=c?.readCount??0;i[t]={tempPath:_,state:"compressed",minifiedAt:h.statSync(t).mtimeMs,readCount:x+1},T(n,i);let $=g>xe,R=x>0;if(R||$){let A=Ee(d,u),q=R?`[cairn: ${l} \u2192 ${g} lines | seen \xD7${x+1} this session]`:`[cairn: ${l} \u2192 outline (${g} minified lines)]`;process.stderr.write(`${q}
63
+ ${A}
60
64
  Full minified view: Read("${_}")
61
- `)}else process.stderr.write(`[cairn: ${p} \u2192 ${g} lines (-${w}%)]
62
- ${h}
65
+ `)}else process.stderr.write(`[cairn: ${l} \u2192 ${g} lines (-${S}%)]
66
+ ${d}
63
67
  Cached view: Read("${_}")
64
- `);process.exit(2)})}else if(R==="edit-guard"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t="(unknown)",s="Edit";try{let d=JSON.parse(e);t=d?.tool_input?.file_path||"(unknown)",s=d?.tool_name||"Edit"}catch{}let n=y.resolve(t),o=ne(n);o||process.exit(0);let a=y.join(o,"views"),i=y.relative(a,n);if(!i.startsWith("..")&&!y.isAbsolute(i)){let d=C(o),r=Object.entries(d).find(([,h])=>h.tempPath===n),p=r?`Edit the source file instead: Edit("${r[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";process.stderr.write(`[cairn] Cannot edit a cached view file \u2014 it is read-only and will be overwritten on next read.
65
- ${p}
66
- `),process.exit(2)}let l=C(o),u=K(o,l),c=l[n];c||(u>0&&P(o,l),process.exit(0)),s==="Write"&&(delete l[n],P(o,l),process.exit(0)),c.state==="edit-ready"&&(delete l[n],P(o,l),process.exit(0)),c.state="edit-ready",P(o,l);let m=null;try{let d=f.readFileSync(n,"utf8");d.split(`
67
- `).length<=300&&(m=d)}catch{}m!==null?process.stderr.write(`[cairn] File was compressed in context. Full source content:
68
+ `);process.exit(2)})}else if(j==="edit-guard"){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>{let t="(unknown)",s="Edit";try{let u=JSON.parse(e);t=u?.tool_input?.file_path||"(unknown)",s=u?.tool_name||"Edit"}catch{}let n=y.resolve(t),o=re(n);o||process.exit(0);let a=y.join(o,"views"),i=y.relative(a,n);if(!i.startsWith("..")&&!y.isAbsolute(i)){let u=D(o),r=Object.entries(u).find(([,d])=>d.tempPath===n),l=r?`Edit the source file instead: Edit("${r[0]}")`:"Edit the original source file, not the .cairn/views/ cache.";process.stderr.write(`[cairn] Cannot edit a cached view file \u2014 it is read-only and will be overwritten on next read.
69
+ ${l}
70
+ `),process.exit(2)}let p=D(o),f=X(o,p),c=p[n];c||(f>0&&T(o,p),process.exit(0)),s==="Write"&&(delete p[n],T(o,p),process.exit(0)),c.state==="edit-ready"&&(delete p[n],T(o,p),process.exit(0)),c.state="edit-ready",T(o,p);let m=null;try{let u=h.readFileSync(n,"utf8");u.split(`
71
+ `).length<=300&&(m=u)}catch{}m!==null?process.stderr.write(`[cairn] File was compressed in context. Full source content:
68
72
 
69
73
  ${m}
70
74
 
71
75
  Re-apply your Edit using the exact content above (state is now edit-ready).
72
76
  `):process.stderr.write(`[cairn] File compressed. Steps: (1) Read("${c.tempPath}") \u2014 loads full content into context. (2) Edit("${t}") \u2014 edit the original source, not the view.
73
- `),process.exit(2)})}else if(R==="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 s=t?.tool_name||"",n=t?.tool_input||{},o=process.cwd(),a=y.join(o,".cairn");f.existsSync(a)||process.exit(0);let i=y.join(a,"index.db");f.existsSync(i)||process.exit(0);let l;try{({DatabaseSync:l}=await import("node:sqlite"))}catch{process.exit(0)}let u=/exit.?plan.?mode/i.test(s),c=/todo.?write/i.test(s);!u&&!c&&process.exit(0);let m=null,d;try{if(u){let r=n.plan;(typeof r!="string"||!r.trim())&&process.exit(0);let p=ke(r);if(d=new l(i),I(d),(d.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let g=Re(d,null);g.id!=null&&ve(d,g.id)}p.added.length>0&&(m=`[cairn] roadmap: captured ${p.added.length} phase(s) from plan${p.skipped.length?` (${p.skipped.length} already present)`:""}.`)}else if(c){d=new l(i);let r=$e(d,n.todos);r.seeded?m=`[cairn] roadmap: seeded ${r.seeded} phase(s) from todos.`:r.inserted>0&&(m=`[cairn] roadmap: linked ${r.inserted} new sub-task(s) to the current phase.`)}}catch(r){process.stderr.write(`[cairn] roadmap-capture skipped: ${r.message}
74
- `);try{d?.close()}catch{}process.exit(0)}try{d?.close()}catch{}m&&process.stdout.write(JSON.stringify({systemMessage:m})),process.exit(0)})}else if(R==="validate-map"){let e=y.join(process.cwd(),".cairn");f.existsSync(e)||process.exit(0);let t=C(e),s=K(e,t);for(let n of Object.values(t))n.readCount=0;P(e,t),s>0&&process.stdout.write(`[cairn] Cleaned minify map: ${s} stale entr${s===1?"y":"ies"} removed
75
- `),process.exit(0)}else if(R==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],s=[];try{let n=y.join(process.cwd(),".cairn"),o=y.join(n,"session.json");f.existsSync(o)&&(t=JSON.parse(f.readFileSync(o,"utf8")).notes||[]);let a=C(n);s=Object.entries(a).filter(([,i])=>(i.readCount??0)>0).map(([i])=>i)}catch{}te(null,{message:e,active_files:s,notes:t}),process.exit(0)}else if(R==="session-tick"){let e=process.cwd(),t=y.join(e,".cairn");f.existsSync(t)||process.exit(0);let s=[];try{let i=`Auto-checkpoint at ${new Date().toISOString()}`,l=[],u=y.join(t,"session.json");if(f.existsSync(u))try{l=JSON.parse(f.readFileSync(u,"utf8")).notes||[]}catch{}let c=C(t),m=Object.entries(c).filter(([,d])=>(d.readCount??0)>0).map(([d])=>d);te(null,{message:i,active_files:m,notes:l}),s.push("\u2713 checkpoint saved")}catch{}try{let i=gt(e);i>0&&s.push(`memory backed up (${i})`)}catch{}let n=[];try{let i=y.join(t,"index.db");if(f.existsSync(i)){let{DatabaseSync:l}=await import("node:sqlite"),u=new l(i);try{I(u);try{n=be(u,e).candidates}catch{}}finally{u.close()}}}catch{}let o="";try{let i=await je(t);if(i.active){s.push(`roadmap ${i.advanced?"updated":"on track"}: ${i.currentLabel}`);let l=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...i.treeLines.map(u=>" "+u)];i.memoLines&&l.push("","\u2500\u2500 Current phase detail (from roadmap.md) \u2500\u2500",...i.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."),o=l.join(`
76
- `)}}catch{}if(n.length>0){s.push(`${n.length} phase(s) may be done \u2014 confirm with user`);let i=["","[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 n)i.push(` \u2022 Phase ${l.phase_number}: ${l.text} (commit ${l.commit}, ${l.confidence}) \u2192 if confirmed: cairn_roadmap set_status ${l.phase_id} done`);o=o?o+`
77
+ `),process.exit(2)})}else if(j==="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 s=t?.tool_name||"",n=t?.tool_input||{},o=process.cwd(),a=y.join(o,".cairn");h.existsSync(a)||process.exit(0);let i=y.join(a,"index.db");h.existsSync(i)||process.exit(0);let p;try{({DatabaseSync:p}=await import("node:sqlite"))}catch{process.exit(0)}let f=/exit.?plan.?mode/i.test(s),c=/todo.?write/i.test(s);!f&&!c&&process.exit(0);let m=null,u;try{if(f){let r=n.plan;(typeof r!="string"||!r.trim())&&process.exit(0);let l=Re(r);if(u=new p(i),H(u),(u.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null)==null){let g=Me(u,null);g.id!=null&&Te(u,g.id)}l.added.length>0&&(m=`[cairn] roadmap: captured ${l.added.length} phase(s) from plan${l.skipped.length?` (${l.skipped.length} already present)`:""}.`)}else if(c){u=new p(i);let r=ve(u,n.todos);r.seeded?m=`[cairn] roadmap: seeded ${r.seeded} phase(s) from todos.`:r.inserted>0&&(m=`[cairn] roadmap: linked ${r.inserted} new sub-task(s) to the current phase.`)}}catch(r){process.stderr.write(`[cairn] roadmap-capture skipped: ${r.message}
78
+ `);try{u?.close()}catch{}process.exit(0)}u&&ee(u);try{u?.close()}catch{}m&&process.stdout.write(JSON.stringify({systemMessage:m})),process.exit(0)})}else if(j==="validate-map"){let e=y.join(process.cwd(),".cairn");h.existsSync(e)||process.exit(0);let t=D(e),s=X(e,t);for(let n of Object.values(t))n.readCount=0;T(e,t),s>0&&process.stdout.write(`[cairn] Cleaned minify map: ${s} stale entr${s===1?"y":"ies"} removed
79
+ `),process.exit(0)}else if(j==="checkpoint"&&process.argv[3]==="--auto"){let e=`Auto-checkpoint at ${new Date().toISOString()}`,t=[],s=[];try{let n=y.join(process.cwd(),".cairn"),o=y.join(n,"session.json");h.existsSync(o)&&(t=JSON.parse(h.readFileSync(o,"utf8")).notes||[]);let a=D(n);s=Object.entries(a).filter(([,i])=>(i.readCount??0)>0).map(([i])=>i)}catch{}oe(null,{message:e,active_files:s,notes:t}),process.exit(0)}else if(j==="session-tick"){let e=process.cwd(),t=y.join(e,".cairn");h.existsSync(t)||process.exit(0);let s=[];try{let i=`Auto-checkpoint at ${new Date().toISOString()}`,p=[],f=y.join(t,"session.json");if(h.existsSync(f))try{p=JSON.parse(h.readFileSync(f,"utf8")).notes||[]}catch{}let c=D(t),m=Object.entries(c).filter(([,u])=>(u.readCount??0)>0).map(([u])=>u);oe(null,{message:i,active_files:m,notes:p}),s.push("\u2713 checkpoint saved")}catch{}try{let i=Tt(e);i>0&&s.push(`memory backed up (${i})`)}catch{}let n=[];try{let i=y.join(t,"index.db");if(h.existsSync(i)){let{DatabaseSync:p}=await import("node:sqlite"),f=new p(i);try{H(f),ee(f);try{n=Pe(f,e).candidates}catch{}}finally{f.close()}}}catch{}let o="";try{let i=await Le(t);if(i.active){s.push(`roadmap ${i.advanced?"updated":"on track"}: ${i.currentLabel}`);let p=["[cairn] Production roadmap (\u2192 = cursor / current phase):",...i.treeLines.map(f=>" "+f)];i.memoLines&&p.push("","\u2500\u2500 Current phase detail (from roadmap.md) \u2500\u2500",...i.memoLines,"\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500"),p.push("","Keep this current via cairn_roadmap on MATERIAL change (phase done / blocked / new) \u2014 not every turn."),o=p.join(`
80
+ `)}}catch{}if(n.length>0){s.push(`${n.length} phase(s) may be done \u2014 confirm with user`);let i=["","[cairn] Possible COMPLETED phase(s) detected from recent commits \u2014","CONFIRM WITH THE USER before marking done; do NOT mark done unilaterally:"];for(let p of n)i.push(` \u2022 Phase ${p.phase_number}: ${p.text} (commit ${p.commit}, ${p.confidence}) \u2192 if confirmed: cairn_roadmap set_status ${p.phase_id} done`);o=o?o+`
77
81
  `+i.join(`
78
82
  `):i.join(`
79
- `).trimStart()}s.length===0&&process.exit(0);let a={systemMessage:`[cairn] ${s.join(" \xB7 ")}`};o&&(a.additionalContext=o),process.stdout.write(JSON.stringify(a)),process.exit(0)}else if(R==="memory-restore"){let e=process.cwd();f.existsSync(y.join(e,".cairn"))||process.exit(0);try{let t=_t(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
80
- `)}catch{}process.exit(0)}else if(R==="resume-hint"){let e=y.join(process.cwd(),".cairn"),t=y.join(e,"session.json"),s=y.join(e,"index.db"),n=y.join(e,".hint-lock"),o=1800*1e3;if(!(process.cwd()===U.homedir())&&f.existsSync(e)&&(f.existsSync(s)||f.existsSync(t))){let r=y.join(e,".cairn-project");if(!f.existsSync(r))try{f.writeFileSync(r,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
81
- `)}catch{}}{let r=[y.join(process.cwd(),".claude","settings.json"),y.join(U.homedir(),".claude","settings.json")];for(let p of r)try{if(!f.existsSync(p))continue;let h=f.readFileSync(p,"utf8"),g=JSON.parse(h),w=g?.hooks?.PreToolUse;if(!Array.isArray(w))continue;let _=!1;for(let S of w)if(!(S.matcher!=="Edit"||!Array.isArray(S.hooks)))for(let v of S.hooks)v.command==="cairn set-mode edit"&&(v.command="cairn edit-guard",_=!0);_&&(f.writeFileSync(p,JSON.stringify(g,null,2),"utf8"),process.stdout.write(`[cairn] Repaired: migrated cairn set-mode \u2192 cairn edit-guard in ${p}
82
- `))}catch{}}let i="";try{i=(JSON.parse(f.readFileSync("/dev/stdin","utf8"))?.prompt||"").toLowerCase()}catch{}let u=/\b(resume|continue|pick up|where (were|did) (we|i)|last session|carry on|what('s| is) next|what (were|was) (we|i)|start|begin)\b/.test(i);f.existsSync(t)||(f.existsSync(s)||(f.existsSync(n)&&Date.now()-f.statSync(n).mtimeMs<o&&process.exit(0),f.mkdirSync(e,{recursive:!0}),f.writeFileSync(n,new Date().toISOString(),"utf8"),process.stdout.write(`[cairn] No index found. Run cairn_maintain to index this project.
83
- `)),process.exit(0)),f.existsSync(n)&&Date.now()-f.statSync(n).mtimeMs<o&&process.exit(0);let c=JSON.parse(f.readFileSync(t,"utf8")),m=new Date(c.checkpoint_at).toLocaleString(),d=[`[cairn] Prior session: "${c.message}" (${m}).`,"[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 call cairn_checkpoint with message + active_files + notes.","[cairn] TOOLS: NEVER use Bash to run cat, grep, head, tail, find, or ls for file inspection \u2014 always use the Read, Grep, or Glob tools instead. Prefer cairn_search / cairn_bundle / cairn_describe over manual file browsing. Use cairn_outline for structural overview, cairn_todos for backlog, cairn_security before PRs.","[cairn] MEMORY: IMPORTANT \u2014 call cairn_memo IMMEDIATELY and SILENTLY (do NOT ask the user, do NOT list what you are about to save, do NOT confirm afterward) when you observe \u2014 preference: user corrects/confirms your behaviour; experience: non-trivial problem solved; decision: architectural choice made; knowledge: non-obvious codebase fact discovered. cairn_employ_memory is explicit only \u2014 never call it unless asked."];if(u){let r=["[cairn] RESUME \u2014 checkpoint loaded inline:"];r.push(` message: ${c.message}`),r.push(` checkpoint: ${m}`),c.active_files?.length>0&&r.push(` active_files: ${c.active_files.join(", ")}`),c.notes?.length>0&&r.push(` notes: ${c.notes.join(" | ")}`);try{let p=new Date(c.checkpoint_at).toISOString(),h=pe(`git log --oneline --after="${p}"`,{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();r.push(` commits_since: ${h?h.split(`
84
- `).join("; "):"(none)"}`)}catch{}try{let p=pe("git diff --name-only HEAD",{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();p&&r.push(` uncommitted: ${p.split(`
85
- `).join(", ")}`)}catch{}try{let p=pe("git status --short",{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();p&&r.push(` git_status:
86
- ${p.split(`
87
- `).map(h=>" "+h).join(`
88
- `)}`)}catch{}r.push(" \u2192 Call cairn_resume for full index status and open todos."),d.push(r.join(`
89
- `))}process.stdout.write(d.join(`
83
+ `).trimStart()}s.length===0&&process.exit(0);let a={systemMessage:`[cairn] ${s.join(" \xB7 ")}`};o&&(a.additionalContext=o),process.stdout.write(JSON.stringify(a)),process.exit(0)}else if(j==="memory-restore"){let e=process.cwd();h.existsSync(y.join(e,".cairn"))||process.exit(0);try{let t=Mt(e);t>0&&process.stdout.write(`[cairn] Restored ${t} memory file(s) from .cairn/memory backup (Claude store was empty)
84
+ `)}catch{}process.exit(0)}else if(j==="resume-hint"){let e=y.join(process.cwd(),".cairn"),t=y.join(e,"session.json"),s=y.join(e,"index.db"),n=y.join(e,".hint-lock"),o=1800*1e3,a=process.cwd()===B.homedir();if(!a&&h.existsSync(e)&&(h.existsSync(s)||h.existsSync(t))){let r=y.join(e,".cairn-project");if(!h.existsSync(r))try{h.writeFileSync(r,"","utf8"),process.stdout.write(`[cairn] Repaired: wrote .cairn-project sentinel for hook compatibility.
85
+ `)}catch{}}if(!a&&h.existsSync(s))try{let{DatabaseSync:r}=await import("node:sqlite"),l=new r(s,{readonly:!0});try{ee(l)}finally{l.close()}}catch{}{let r=[y.join(process.cwd(),".claude","settings.json"),y.join(B.homedir(),".claude","settings.json")];for(let l of r)try{if(!h.existsSync(l))continue;let d=h.readFileSync(l,"utf8"),g=JSON.parse(d),S=g?.hooks?.PreToolUse;if(!Array.isArray(S))continue;let _=!1;for(let x of S)if(!(x.matcher!=="Edit"||!Array.isArray(x.hooks)))for(let $ of x.hooks)$.command==="cairn set-mode edit"&&($.command="cairn edit-guard",_=!0);_&&(h.writeFileSync(l,JSON.stringify(g,null,2),"utf8"),process.stdout.write(`[cairn] Repaired: migrated cairn set-mode \u2192 cairn edit-guard in ${l}
86
+ `))}catch{}}let i="";try{i=(JSON.parse(h.readFileSync("/dev/stdin","utf8"))?.prompt||"").toLowerCase()}catch{}let f=/\b(resume|continue|pick up|where (were|did) (we|i)|last session|carry on|what('s| is) next|what (were|was) (we|i)|start|begin)\b/.test(i);h.existsSync(t)||(h.existsSync(s)||(h.existsSync(n)&&Date.now()-h.statSync(n).mtimeMs<o&&process.exit(0),h.mkdirSync(e,{recursive:!0}),h.writeFileSync(n,new Date().toISOString(),"utf8"),process.stdout.write(`[cairn] No index found. Run cairn_maintain to index this project.
87
+ `)),process.exit(0)),h.existsSync(n)&&Date.now()-h.statSync(n).mtimeMs<o&&process.exit(0);let c=JSON.parse(h.readFileSync(t,"utf8")),m=new Date(c.checkpoint_at).toLocaleString(),u=[`[cairn] Prior session: "${c.message}" (${m}).`,"[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 call cairn_checkpoint with message + active_files + notes.","[cairn] TOOLS: NEVER use Bash to run cat, grep, head, tail, find, or ls for file inspection \u2014 always use the Read, Grep, or Glob tools instead. Prefer cairn_search / cairn_bundle / cairn_describe over manual file browsing. Use cairn_outline for structural overview, cairn_todos for backlog, cairn_security before PRs.","[cairn] MEMORY: IMPORTANT \u2014 call cairn_memo IMMEDIATELY and SILENTLY (do NOT ask the user, do NOT list what you are about to save, do NOT confirm afterward) when you observe \u2014 preference: user corrects/confirms your behaviour; experience: non-trivial problem solved; decision: architectural choice made; knowledge: non-obvious codebase fact discovered. cairn_employ_memory is explicit only \u2014 never call it unless asked."];if(f){let r=["[cairn] RESUME \u2014 checkpoint loaded inline:"];r.push(` message: ${c.message}`),r.push(` checkpoint: ${m}`),c.active_files?.length>0&&r.push(` active_files: ${c.active_files.join(", ")}`),c.notes?.length>0&&r.push(` notes: ${c.notes.join(" | ")}`);try{let l=new Date(c.checkpoint_at).toISOString(),d=me(`git log --oneline --after="${l}"`,{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();r.push(` commits_since: ${d?d.split(`
88
+ `).join("; "):"(none)"}`)}catch{}try{let l=me("git diff --name-only HEAD",{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();l&&r.push(` uncommitted: ${l.split(`
89
+ `).join(", ")}`)}catch{}try{let l=me("git status --short",{cwd:process.cwd(),encoding:"utf8",timeout:5e3}).trim();l&&r.push(` git_status:
90
+ ${l.split(`
91
+ `).map(d=>" "+d).join(`
92
+ `)}`)}catch{}r.push(" \u2192 Call cairn_resume for full index status and open todos."),u.push(r.join(`
93
+ `))}process.stdout.write(u.join(`
90
94
  `)+`
91
- `),f.writeFileSync(n,new Date().toISOString(),"utf8"),process.exit(0)}else if(R==="roadmap-hint"){let e=y.join(process.cwd(),".cairn"),t=await je(e);t.active||process.exit(0);let s=["[cairn] ROADMAP \u2014 cursor is active.",...t.treeLines.map(n=>"[cairn] "+n),`[cairn] Current: ${t.currentLabel}`];if(t.memoLines){s.push("[cairn] \u2500\u2500\u2500\u2500\u2500 Decision memo (current phase) \u2500\u2500\u2500\u2500\u2500");for(let n of t.memoLines)s.push("[cairn] "+n);s.push("[cairn] \u2500\u2500\u2500\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500\u2500\u2500\u2500")}s.push("[cairn] Use cairn_roadmap set_status <id> done (auto-advances), focus <id>, or next to move cursor."),process.stdout.write(s.join(`
95
+ `),h.writeFileSync(n,new Date().toISOString(),"utf8"),process.exit(0)}else if(j==="roadmap-hint"){let e=y.join(process.cwd(),".cairn"),t=await Le(e);t.active||process.exit(0);let s=["[cairn] ROADMAP \u2014 cursor is active.",...t.treeLines.map(n=>"[cairn] "+n),`[cairn] Current: ${t.currentLabel}`];if(t.memoLines){s.push("[cairn] \u2500\u2500\u2500\u2500\u2500 Decision memo (current phase) \u2500\u2500\u2500\u2500\u2500");for(let n of t.memoLines)s.push("[cairn] "+n);s.push("[cairn] \u2500\u2500\u2500\u2500\u2500 (re-read after each phase to confirm direction) \u2500\u2500\u2500\u2500\u2500")}s.push("[cairn] Use cairn_roadmap set_status <id> done (auto-advances), focus <id>, or next to move cursor."),process.stdout.write(s.join(`
92
96
  `)+`
93
- `),process.exit(0)}else if(R==="install"){let e=process.platform,t=y.join(U.homedir(),".claude.json"),s={};if(f.existsSync(t))try{s=JSON.parse(f.readFileSync(t,"utf8"))}catch{}s.mcpServers=s.mcpServers||{};let n=!!s.mcpServers.cairn;s.mcpServers.cairn={command:"cairn-mcp"},f.writeFileSync(t,JSON.stringify(s,null,2),"utf8");let o=y.join(U.homedir(),".claude");Pe(o,!0),console.log("Cairn installed successfully!"),console.log(""),console.log(` Platform : ${e}`),console.log(` MCP : cairn-mcp registered in ${t}${n?" (updated)":""}`),console.log(` Hooks : installed in ${y.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(""),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(R==="install-hooks"){let e=process.argv.includes("--global"),t=e?y.join(U.homedir(),".claude"):y.join(process.cwd(),".claude");Pe(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)"),process.exit(0)}else console.error("Usage: cairn <--version | install | install-hooks [--global] | minify | edit-guard | validate-map | checkpoint --auto | session-tick | resume-hint | roadmap-hint | roadmap-capture | memory-restore>"),process.exit(1);function Pe(e,t){let s=y.join(e,"settings.json"),n={};if(f.existsSync(s))try{n=JSON.parse(f.readFileSync(s,"utf8"))}catch{}n.hooks=n.hooks||{};let o=n.hooks.PreToolUse||[];o.some(r=>r.matcher==="Read"&&r.hooks?.some(p=>p.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let a=o.filter(r=>!(r.matcher==="Edit"&&r.hooks?.some(p=>p.command==="cairn set-mode edit")));a.some(r=>r.matcher==="Edit"&&r.hooks?.some(p=>p.command==="cairn edit-guard"))||a.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),a.some(r=>r.matcher==="Write"&&r.hooks?.some(p=>p.command==="cairn edit-guard"))||a.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),n.hooks.PreToolUse=a;let i=(r,p)=>(r||[]).map(h=>({...h,hooks:(h.hooks||[]).filter(g=>!p(g.command))})).filter(h=>(h.hooks||[]).length>0),l=r=>typeof r=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(r),u=i(n.hooks.Stop,r=>r==="cairn checkpoint --auto"||r==="cairn roadmap-hint");u.some(r=>r.hooks?.some(p=>p.command==="cairn session-tick"))||u.push({hooks:[{type:"command",command:"cairn session-tick"}]}),n.hooks.Stop=u;let c=i(n.hooks.UserPromptSubmit,l);c.some(r=>r.hooks?.some(p=>p.command==="cairn resume-hint"))||c.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),c.some(r=>r.hooks?.some(p=>p.command==="cairn memory-restore"))||c.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),n.hooks.UserPromptSubmit=c;let m=i(n.hooks.PostToolUse,l),d=r=>{m.some(p=>p.matcher===r&&p.hooks?.some(h=>h.command==="cairn roadmap-capture"))||m.push({matcher:r,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(d("ExitPlanMode"),d("TodoWrite"),n.hooks.PostToolUse=m,t){let r=y.join(e,"scripts");for(let p of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let h=y.join(r,p);if(f.existsSync(h))try{f.unlinkSync(h)}catch{}}}if(!t){let r=y.join(process.cwd(),".cairn");f.mkdirSync(r,{recursive:!0});let p=y.join(r,".cairn-project");f.existsSync(p)||f.writeFileSync(p,"","utf8")}f.mkdirSync(e,{recursive:!0}),f.writeFileSync(s,JSON.stringify(n,null,2),"utf8")}
97
+ `),process.exit(0)}else if(j==="install"){let e=process.platform,t=y.join(B.homedir(),".claude.json"),s={};if(h.existsSync(t))try{s=JSON.parse(h.readFileSync(t,"utf8"))}catch{}s.mcpServers=s.mcpServers||{};let n=!!s.mcpServers.cairn;s.mcpServers.cairn={command:"cairn-mcp"},h.writeFileSync(t,JSON.stringify(s,null,2),"utf8");let o=y.join(B.homedir(),".claude");Fe(o,!0),console.log("Cairn installed successfully!"),console.log(""),console.log(` Platform : ${e}`),console.log(` MCP : cairn-mcp registered in ${t}${n?" (updated)":""}`),console.log(` Hooks : installed in ${y.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(""),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(j==="install-hooks"){let e=process.argv.includes("--global"),t=e?y.join(B.homedir(),".claude"):y.join(process.cwd(),".claude");Fe(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)"),process.exit(0)}else console.error("Usage: cairn <--version | install | install-hooks [--global] | minify | edit-guard | validate-map | checkpoint --auto | session-tick | resume-hint | roadmap-hint | roadmap-capture | memory-restore>"),process.exit(1);function Fe(e,t){let s=y.join(e,"settings.json"),n={};if(h.existsSync(s))try{n=JSON.parse(h.readFileSync(s,"utf8"))}catch{}n.hooks=n.hooks||{};let o=n.hooks.PreToolUse||[];o.some(r=>r.matcher==="Read"&&r.hooks?.some(l=>l.command==="cairn minify"))||o.push({matcher:"Read",hooks:[{type:"command",command:"cairn minify"}]});let a=o.filter(r=>!(r.matcher==="Edit"&&r.hooks?.some(l=>l.command==="cairn set-mode edit")));a.some(r=>r.matcher==="Edit"&&r.hooks?.some(l=>l.command==="cairn edit-guard"))||a.push({matcher:"Edit",hooks:[{type:"command",command:"cairn edit-guard"}]}),a.some(r=>r.matcher==="Write"&&r.hooks?.some(l=>l.command==="cairn edit-guard"))||a.push({matcher:"Write",hooks:[{type:"command",command:"cairn edit-guard"}]}),n.hooks.PreToolUse=a;let i=(r,l)=>(r||[]).map(d=>({...d,hooks:(d.hooks||[]).filter(g=>!l(g.command))})).filter(d=>(d.hooks||[]).length>0),p=r=>typeof r=="string"&&/sync-memory-(forward|restore)\.(mjs|py)/.test(r),f=i(n.hooks.Stop,r=>r==="cairn checkpoint --auto"||r==="cairn roadmap-hint");f.some(r=>r.hooks?.some(l=>l.command==="cairn session-tick"))||f.push({hooks:[{type:"command",command:"cairn session-tick"}]}),n.hooks.Stop=f;let c=i(n.hooks.UserPromptSubmit,p);c.some(r=>r.hooks?.some(l=>l.command==="cairn resume-hint"))||c.push({hooks:[{type:"command",command:"cairn resume-hint"}]}),c.some(r=>r.hooks?.some(l=>l.command==="cairn memory-restore"))||c.push({hooks:[{type:"command",command:"cairn memory-restore"}]}),n.hooks.UserPromptSubmit=c;let m=i(n.hooks.PostToolUse,p),u=r=>{m.some(l=>l.matcher===r&&l.hooks?.some(d=>d.command==="cairn roadmap-capture"))||m.push({matcher:r,hooks:[{type:"command",command:"cairn roadmap-capture"}]})};if(u("ExitPlanMode"),u("TodoWrite"),n.hooks.PostToolUse=m,t){let r=y.join(e,"scripts");for(let l of["sync-memory-forward.mjs","sync-memory-restore.mjs","sync-memory-forward.py","sync-memory-restore.py"]){let d=y.join(r,l);if(h.existsSync(d))try{h.unlinkSync(d)}catch{}}}if(!t){let r=y.join(process.cwd(),".cairn");h.mkdirSync(r,{recursive:!0});let l=y.join(r,".cairn-project");h.existsSync(l)||h.writeFileSync(l,"","utf8")}h.mkdirSync(e,{recursive:!0}),h.writeFileSync(s,JSON.stringify(n,null,2),"utf8")}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import{Server as ts}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as ns}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as ss,ListToolsRequestSchema as is}from"@modelcontextprotocol/sdk/types.js";import{DatabaseSync as be}from"node:sqlite";import de from"fs";import k from"path";import $ from"fs";import sn from"os";import D from"path";var Se=null;function A(){return Se||process.cwd()}function xe(t){let e=Se;return Se=t,e}function M(){let t=D.join(A(),".cairn");$.mkdirSync(D.join(t,"bundles"),{recursive:!0});let e=D.join(t,".cairn-project");return $.existsSync(e)||$.writeFileSync(e,"","utf8"),t}function rn(t){return t.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function Te(){let t=D.join(sn.homedir(),".claude","projects",rn(A()));return $.existsSync(t)?D.join(t,"memory"):null}function Be(t){let e=D.join(t,"MEMORY.md");$.existsSync(e)||$.writeFileSync(e,`# Memory Index
2
- `,"utf8")}function V(){let t=D.join(M(),"memory");$.mkdirSync(t,{recursive:!0}),Be(t);let e=Te();if(!e)return t;$.mkdirSync(e,{recursive:!0}),Be(e);let s=n=>$.readdirSync(n).some(i=>i.endsWith(".md")&&i!=="MEMORY.md");if(!s(e)&&s(t))for(let n of $.readdirSync(t)){let i=D.join(t,n);try{if(!$.statSync(i).isFile())continue;let r=$.statSync(i);$.copyFileSync(i,D.join(e,n));try{$.utimesSync(D.join(e,n),r.atime,r.mtime)}catch{}}catch{}}return e}var on=`
1
+ import{Server as ms}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as fs}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as hs,ListToolsRequestSchema as gs}from"@modelcontextprotocol/sdk/types.js";import{DatabaseSync as Oe}from"node:sqlite";import de from"fs";import v from"path";import M from"fs";import cn from"os";import D from"path";var be=null;function $(){return be||process.cwd()}function Re(t){let e=be;return be=t,e}function A(){let t=D.join($(),".cairn");M.mkdirSync(D.join(t,"bundles"),{recursive:!0});let e=D.join(t,".cairn-project");return M.existsSync(e)||M.writeFileSync(e,"","utf8"),t}function pn(t){return t.replace(/[:/\\]/g,"-").replace(/^-+/,"")}function we(){let t=D.join(cn.homedir(),".claude","projects",pn($()));return M.existsSync(t)?D.join(t,"memory"):null}function Je(t){let e=D.join(t,"MEMORY.md");M.existsSync(e)||M.writeFileSync(e,`# Memory Index
2
+ `,"utf8")}function z(){let t=D.join(A(),"memory");M.mkdirSync(t,{recursive:!0}),Je(t);let e=we();if(!e)return t;M.mkdirSync(e,{recursive:!0}),Je(e);let s=n=>M.readdirSync(n).some(i=>i.endsWith(".md")&&i!=="MEMORY.md");if(!s(e)&&s(t))for(let n of M.readdirSync(t)){let i=D.join(t,n);try{if(!M.statSync(i).isFile())continue;let r=M.statSync(i);M.copyFileSync(i,D.join(e,n));try{M.utimesSync(D.join(e,n),r.atime,r.mtime)}catch{}}catch{}}return e}var ln=`
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 ts}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
- `,an=1e6;function cn(t){let e=k.dirname(k.resolve(t));for(;;){let s=k.join(e,".cairn","index.db");if(de.existsSync(s))return s;let n=k.dirname(e);if(n===e)return null;e=n}}function pn(t){let e=k.resolve(A()),s=k.resolve(k.join(M(),"index.db")),n=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)"),i=cn(e);if(i){try{k.resolve(i)!==s&&n.run(k.resolve(i));let r=new be(i,{readonly:!0}),o=r.prepare("SELECT path FROM sub_indexes").all();r.close();for(let{path:a}of o){let c=k.resolve(a);c!==s&&n.run(c)}}catch{}return}try{let r=k.dirname(e),o=de.readdirSync(r,{withFileTypes:!0});for(let a of o){if(!a.isDirectory())continue;let c=k.resolve(k.join(r,a.name,".cairn","index.db"));c!==s&&de.existsSync(c)&&n.run(c)}}catch{}}var ln=["files","symbols","dependencies","security_findings"];function Ge(t,e,s){let n=`"${t}"`;switch(e){case"files":return`SELECT id+${s} AS id, repo, path, language, file_type, last_indexed FROM ${n}.files`;case"symbols":return`SELECT id+${s} AS id, file_id+${s} AS file_id, fqn, name, kind, language, exported, description, line FROM ${n}.symbols`;case"dependencies":return`SELECT from_file_id+${s} AS from_file_id, to_fqn, dep_type FROM ${n}.dependencies`;case"build_deps":return`SELECT repo, manager, group_id, artifact, version, scope FROM ${n}.build_deps`;case"security_findings":return`SELECT id+${s} AS id, file_id+${s} AS file_id, line, severity, cwe, rule_name, description, code_snippet FROM ${n}.security_findings`;default:return`SELECT * FROM ${n}.${e}`}}function dn(t){if(!de.existsSync(t))return{valid:!1,reason:"file not found"};try{let e=new be(t,{readonly:!0}),s=e.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(i=>i.name);e.close();let n=ln.filter(i=>!s.includes(i));return n.length>0?{valid:!1,reason:`missing tables: ${n.join(", ")}`}:{valid:!0}}catch(e){return{valid:!1,reason:e.message}}}function Xe(){let t=k.join(M(),"index.db"),e=new be(t);e.exec("PRAGMA journal_mode = WAL"),e.exec("PRAGMA synchronous = NORMAL"),e.exec(on);try{e.exec("ALTER TABLE main.symbols ADD COLUMN line INTEGER")}catch{}let s=!1;try{e.exec("ALTER TABLE main.todos ADD COLUMN parent_id INTEGER REFERENCES todos(id) ON DELETE SET NULL"),s=!0}catch{}try{e.exec("ALTER TABLE main.todos ADD COLUMN position INTEGER NOT NULL DEFAULT 0"),s=!0}catch{}s&&e.exec(`
101
+ `,dn=1e6;function un(t){let e=v.dirname(v.resolve(t));for(;;){let s=v.join(e,".cairn","index.db");if(de.existsSync(s))return s;let n=v.dirname(e);if(n===e)return null;e=n}}function mn(t){let e=v.resolve($()),s=v.resolve(v.join(A(),"index.db")),n=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)"),i=un(e);if(i){try{v.resolve(i)!==s&&n.run(v.resolve(i));let r=new Oe(i,{readonly:!0}),o=r.prepare("SELECT path FROM sub_indexes").all();r.close();for(let{path:a}of o){let c=v.resolve(a);c!==s&&n.run(c)}}catch{}return}try{let r=v.dirname(e),o=de.readdirSync(r,{withFileTypes:!0});for(let a of o){if(!a.isDirectory())continue;let c=v.resolve(v.join(r,a.name,".cairn","index.db"));c!==s&&de.existsSync(c)&&n.run(c)}}catch{}}var fn=["files","symbols","dependencies","security_findings"];function Ye(t,e,s){let n=`"${t}"`;switch(e){case"files":return`SELECT id+${s} AS id, repo, path, language, file_type, last_indexed FROM ${n}.files`;case"symbols":return`SELECT id+${s} AS id, file_id+${s} AS file_id, fqn, name, kind, language, exported, description, line FROM ${n}.symbols`;case"dependencies":return`SELECT from_file_id+${s} AS from_file_id, to_fqn, dep_type FROM ${n}.dependencies`;case"build_deps":return`SELECT repo, manager, group_id, artifact, version, scope FROM ${n}.build_deps`;case"security_findings":return`SELECT id+${s} AS id, file_id+${s} AS file_id, line, severity, cwe, rule_name, description, code_snippet FROM ${n}.security_findings`;default:return`SELECT * FROM ${n}.${e}`}}function hn(t){if(!de.existsSync(t))return{valid:!1,reason:"file not found"};try{let e=new Oe(t,{readonly:!0}),s=e.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(i=>i.name);e.close();let n=fn.filter(i=>!s.includes(i));return n.length>0?{valid:!1,reason:`missing tables: ${n.join(", ")}`}:{valid:!0}}catch(e){return{valid:!1,reason:e.message}}}function Ve(){let t=v.join(A(),"index.db"),e=new Oe(t);e.exec("PRAGMA journal_mode = WAL"),e.exec("PRAGMA synchronous = NORMAL"),e.exec(ln);try{e.exec("ALTER TABLE main.symbols ADD COLUMN line INTEGER")}catch{}let s=!1;try{e.exec("ALTER TABLE main.todos ADD COLUMN parent_id INTEGER REFERENCES todos(id) ON DELETE SET NULL"),s=!0}catch{}try{e.exec("ALTER TABLE main.todos ADD COLUMN position INTEGER NOT NULL DEFAULT 0"),s=!0}catch{}s&&e.exec(`
102
102
  UPDATE main.todos AS t
103
103
  SET position = (
104
104
  SELECT rn - 1 FROM (
@@ -107,25 +107,25 @@ import{Server as ts}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
107
107
  ) AS rn FROM main.todos
108
108
  ) ord WHERE ord.id = t.id
109
109
  )
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 l of i){let m=r.run("legacy_v1_9",a++,null,l.text,l.status,l.position);o.set(l.id,m.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 l of c){let m=o.get(l.parent_id);m!=null&&r.run(null,null,m,l.text,l.status,l.position)}let p=e.prepare("SELECT todo_id FROM main.roadmap_cursor WHERE singleton = 1").get();p?.todo_id!=null&&o.has(p.todo_id)&&e.prepare("UPDATE main.roadmap_cursor SET phase_id = ? WHERE singleton = 1").run(o.get(p.todo_id))}}}catch(n){process.stderr.write(`[cairn] v1.9.0\u2192v1.10.0 roadmap migration skipped: ${n.message}
111
- `)}return pn(e),Re(e),e}function Je(t,e){let s=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)");for(let n of e)s.run(k.resolve(n))}function ue(t){return t.prepare("PRAGMA database_list").all().filter(e=>e.name!=="temp"&&e.name!=="main").map(e=>e.name)}function Re(t){let e=t.prepare("PRAGMA database_list").all(),s=new Map(e.filter(l=>l.name!=="temp"&&l.name!=="main"&&l.file).map(l=>[k.resolve(l.file),l.name])),n=[...s.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=n.length,c=[...n];for(let{path:l}of r){let m=k.resolve(l);if(s.has(m)){c.includes(s.get(m))||c.push(s.get(m));continue}let u=dn(m);if(!u.valid){o.run(l),i.removed.push(`${l} (${u.reason})`);continue}let d=`sub${a++}`;try{t.exec(`ATTACH DATABASE '${m.replace(/\\/g,"/").replace(/'/g,"''")}' AS "${d}"`),c.push(d),s.set(m,d),i.attached.push(l)}catch(g){i.skipped.push(`${l} (${g.message})`)}}if(c.length===0)return i;let p=["files","symbols","dependencies","build_deps","security_findings"];for(let l of p){t.exec(`DROP VIEW IF EXISTS temp.${l}`);let m=[Ge("main",l,0),...c.map((u,d)=>Ge(u,l,(d+1)*an))];t.exec(`CREATE TEMP VIEW ${l} AS ${m.join(" UNION ALL ")}`)}return i}import Le from"fs/promises";import re from"path";import Ye from"fast-glob";import we from"path";var X={".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"},Ve=["pom.xml","package.json","build.gradle"],un=["**/node_modules/**","**/.git/**","**/dist/**","**/build/**","**/target/**","**/.next/**","**/__pycache__/**","**/*.min.js","**/.cairn/**"];async function ze(t){return(await Ye(["**/.cairn/index.db"],{cwd:t,absolute:!0,dot:!0,ignore:["**/node_modules/**"]})).map(s=>we.dirname(we.dirname(s)))}async function P(t,e=[]){let s=Object.keys(X).map(r=>`**/*${r}`),n=e.map(r=>`${we.relative(t,r).replace(/\\/g,"/")}/**`);return await Ye(s,{cwd:t,ignore:[...un,...n],absolute:!0})}import fn from"path";function Ke(t,e){return t.substring(0,e).split(`
112
- `).length}function Qe(t,e,s){let n=e.match(/^package\s+([\w.]+);/m)?.[1]||"",i=[...e.matchAll(/^import\s+([\w.]+);/gm)].map(h=>h[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",p=n?`${n}.${a}`:a,l=o?Ke(e,o.index):null,m=[...e.matchAll(/(?:public|protected|private)[^{;]*\s+(\w+)\s*\([^)]*\)\s*(?:throws[^{]*)?\{/gm)],u=e.match(/extends\s+([\w.]+)/)?.[1],d=e.match(/implements\s+([\w.,\s]+)/)?.[1]?.split(",").map(h=>h.trim()),g=e.match(/\/\*\*([\s\S]*?)\*\//)?.[1]?.replace(/\s*\*\s?/g," ").trim(),f=p?m.map(h=>({name:h[1],fqn:`${p}.${h[1]}`,kind:"method",exported:!1,description:"",line:Ke(e,h.index)})):[];return{language:"java",symbols:[{name:a,fqn:p,kind:c,exported:!0,description:g||"",line:l},...f],imports:i,extends:u?[u]:[],implements:d||[]}}function se(t,e){return t.substring(0,e).split(`
113
- `).length}function ie(t,e,s,n="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=Ze(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"class",exported:a,description:c,line:se(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:se(e,o.index)});for(let o of e.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)){let a=Ze(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:a,line:se(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:se(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:se(e,o.index)});return{language:n,symbols:i,imports:r}}function Ze(t,e){let n=t.substring(0,e).match(/\/\*\*([\s\S]*?)\*\/\s*$/);return n?n[1].replace(/\s*\*\s?/g," ").trim():""}function et(t,e,s){let n=t.split("/").pop().replace(".vue",""),r=e.match(/<script(?:\s+setup)?(?:\s+lang=["']ts["'])?>([\s\S]*?)<\/script>/i)?.[1]||"",o=ie(t,r,s,"vue"),c=[...e.match(/<template>([\s\S]*?)<\/template>/i)?.[1]?.matchAll(/<([A-Z][A-Za-z]+)/g)||[]].map(p=>p[1]);return o.symbols.unshift({name:n,fqn:`${t}::${n}`,kind:"component",exported:!0,description:`Vue component: ${n}`}),o.childComponents=[...new Set(c)],o}function Oe(t,e){return t.substring(0,e).split(`
114
- `).length}function tt(t,e,s){let n=[],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();n.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"class",exported:!0,description:o||"",line:Oe(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();n.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"function",exported:!0,description:o||"",line:Oe(e,r.index)})}for(let r of e.matchAll(/^@([\w.]+)(?:\(([^)]*)\))?/gm))n.push({name:r[1],fqn:`${t}::@${r[1]}`,kind:"decorator",exported:!1,description:"",line:Oe(e,r.index)});return{language:"python",symbols:n,imports:i}}function nt(t,e,s){let n=[];for(let i of e.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"\w.]+)/gi))n.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))n.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))n.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"view",exported:!0,description:""});return{language:"sql",symbols:n,imports:[]}}function st(t,e,s){let n=[];for(let i of e.matchAll(/^([\w.-]+)\s*[:=]\s*(.+)/gm)){let r=i[1].trim();r.startsWith("#")||n.push({name:r,fqn:`${t}::${r}`,kind:"config-key",exported:!1,description:i[2].trim().substring(0,80)})}return{language:"config",symbols:n,imports:[]}}var mn=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 Ne(t,e,s){let n=[];for(let r of e.matchAll(/\bid\s*=\s*["']([^"']+)["']/g))n.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();mn.has(o)||i.has(o)||(i.add(o),n.push({name:o,fqn:`${t}::${o}`,kind:"component",exported:!0,description:""}))}return{language:"xml",symbols:n,imports:[]}}function it(t,e,s){let n=[];for(let i of e.matchAll(/^(#{1,3})\s+(.+)/gm)){let r=i[1].length,o=i[2].trim();n.push({name:o,fqn:`${t}::${o.replace(/\s+/g,"-").toLowerCase()}`,kind:r===1?"doc-title":"doc-section",exported:!0,description:""})}return{language:"markdown",symbols:n,imports:[]}}var hn={java:Qe,typescript:ie,javascript:ie,vue:et,python:tt,sql:nt,config:st,xml:Ne,html:Ne,markdown:it};function gn(t){let e=fn.extname(t).toLowerCase(),s=X[e];return{language:s,parser:hn[s]||null}}async function rt(t,e,s){let{language:n,parser:i}=gn(t);return i?i(t,e,s,n):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 ot(t,e,s){let n=[],i=e.split(`
115
- `);for(let r of yn)r.lang&&!r.lang.includes(s)||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
- `);if(r.negPattern.test(c))return}n.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 n}function at(t,e,s){let n=[];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]||"",p=r.match(/<scope>([^<]+)<\/scope>/)?.[1]||"compile";o&&a&&n.push({manager:"maven",group_id:o,artifact:a,version:c,scope:p})}return n}function ct(t,e,s){let n=[];try{let i=JSON.parse(e);for(let[r,o]of Object.entries(i.dependencies||{}))n.push({manager:"npm",group_id:"dependencies",artifact:r,version:o,scope:"runtime"});for(let[r,o]of Object.entries(i.devDependencies||{}))n.push({manager:"npm",group_id:"devDependencies",artifact:r,version:o,scope:"dev"});for(let[r,o]of Object.entries(i.peerDependencies||{}))n.push({manager:"npm",group_id:"peerDependencies",artifact:r,version:o,scope:"peer"})}catch{}return n}function pt(t,e,s){let n=[];for(let i of e.matchAll(/(\w+)\s+['"]([^:'"]+):([^:'"]+):([^'"]+)['"]/g)){let r=i[1],o=i[2],a=i[3],c=i[4];n.push({manager:"gradle",group_id:o,artifact:a,version:c,scope:r})}return n}function lt(t,{repo:e,path:s,language:n,file_type:i}){let r=t.prepare("SELECT id FROM main.files WHERE path = ?").get(s);return r?(t.prepare("UPDATE main.files SET language = ?, file_type = ?, last_indexed = ? WHERE id = ?").run(n,i,Date.now(),r.id),r.id):t.prepare("INSERT INTO main.files (repo, path, language, file_type, last_indexed) VALUES (?, ?, ?, ?, ?)").run(e,s,n,i,Date.now()).lastInsertRowid}function dt(t,{file_id:e,fqn:s,name:n,kind:i,language:r,exported:o,description:a,line:c}){let p=t.prepare("SELECT id FROM main.symbols WHERE fqn = ?").get(s);p?t.prepare("UPDATE main.symbols SET file_id=?, name=?, kind=?, language=?, exported=?, description=?, line=? WHERE id=?").run(e,n,i,r,o?1:0,a||"",c??null,p.id):t.prepare("INSERT INTO main.symbols (file_id, fqn, name, kind, language, exported, description, line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e,s,n,i,r,o?1:0,a||"",c??null)}function ut(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 mt(t,{from_file_id:e,to_fqn:s,dep_type:n}){t.prepare("INSERT INTO main.dependencies (from_file_id, to_fqn, dep_type) VALUES (?, ?, ?)").run(e,s,n)}function ft(t,{repo:e,manager:s,group_id:n,artifact:i,version:r,scope:o}){t.prepare("SELECT rowid FROM main.build_deps WHERE repo=? AND manager=? AND artifact=?").get(e,s,i)||t.prepare("INSERT INTO main.build_deps (repo, manager, group_id, artifact, version, scope) VALUES (?, ?, ?, ?, ?, ?)").run(e,s,n,i,r,o)}function ht(t,{file_id:e,line:s,severity:n,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,s,n,i,r,o,a)}import En from"fs/promises";var _n=/(?:\/\/|#|\/\*|\*|<!--)\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s]+(.*)/i;async function me(t,e){t.prepare("DELETE FROM main.todos WHERE source = ?").run("scan");let s=t.prepare("INSERT INTO main.todos (source, file, line, kind, text) VALUES (?, ?, ?, ?, ?)"),n=0;for(let i of e)try{let o=(await En.readFile(i,"utf-8")).split(`
117
- `);for(let a=0;a<o.length;a++){let c=_n.exec(o[a]);if(c){let p=c[1].toUpperCase(),l=c[2].trim();l&&(s.run("scan",i,a+1,p,l),n++)}}}catch{}return n}function Sn(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 gt(t,e,s,n,i){try{let r=await Le.readFile(e,"utf-8"),o=await rt(e,r,s);if(!o)return;let{language:a}=o;if(n&&!n.includes(a))return;i.files_by_language[a]=(i.files_by_language[a]||0)+1;let c=lt(t,{repo:s,path:e,language:a,file_type:Sn(e)});ut(t,c);for(let l of o.symbols||[])dt(t,{...l,file_id:c,language:a}),i.symbols_total++;for(let l of o.imports||[])mt(t,{from_file_id:c,to_fqn:l,dep_type:"imports"}),i.dependencies_mapped++;let p=ot(e,r,a);for(let l of p)ht(t,{file_id:c,...l}),i.security_findings++}catch{}}function yt(t){t.exec(`
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 h=r.run("legacy_v1_9",a++,null,p.text,p.status,p.position);o.set(p.id,h.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 h=o.get(p.parent_id);h!=null&&r.run(null,null,h,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(n){process.stderr.write(`[cairn] v1.9.0\u2192v1.10.0 roadmap migration skipped: ${n.message}
111
+ `)}return mn(e),Ne(e),e}function ze(t,e){let s=t.prepare("INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)");for(let n of e)s.run(v.resolve(n))}function ue(t){return t.prepare("PRAGMA database_list").all().filter(e=>e.name!=="temp"&&e.name!=="main").map(e=>e.name)}function Ne(t){let e=t.prepare("PRAGMA database_list").all(),s=new Map(e.filter(p=>p.name!=="temp"&&p.name!=="main"&&p.file).map(p=>[v.resolve(p.file),p.name])),n=[...s.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=n.length,c=[...n];for(let{path:p}of r){let h=v.resolve(p);if(s.has(h)){c.includes(s.get(h))||c.push(s.get(h));continue}let m=hn(h);if(!m.valid){o.run(p),i.removed.push(`${p} (${m.reason})`);continue}let d=`sub${a++}`;try{t.exec(`ATTACH DATABASE '${h.replace(/\\/g,"/").replace(/'/g,"''")}' AS "${d}"`),c.push(d),s.set(h,d),i.attached.push(p)}catch(u){i.skipped.push(`${p} (${u.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 h=[Ye("main",p,0),...c.map((m,d)=>Ye(m,p,(d+1)*dn))];t.exec(`CREATE TEMP VIEW ${p} AS ${h.join(" UNION ALL ")}`)}return i}import Ie from"fs/promises";import oe from"path";import Ke from"fast-glob";import Le from"path";var J={".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"},Qe=["pom.xml","package.json","build.gradle"],gn=["**/node_modules/**","**/.git/**","**/dist/**","**/build/**","**/target/**","**/.next/**","**/__pycache__/**","**/*.min.js","**/.cairn/**"];async function Ze(t){return(await Ke(["**/.cairn/index.db"],{cwd:t,absolute:!0,dot:!0,ignore:["**/node_modules/**"]})).map(s=>Le.dirname(Le.dirname(s)))}async function P(t,e=[]){let s=Object.keys(J).map(r=>`**/*${r}`),n=e.map(r=>`${Le.relative(t,r).replace(/\\/g,"/")}/**`);return await Ke(s,{cwd:t,ignore:[...gn,...n],absolute:!0})}import _n from"path";function et(t,e){return t.substring(0,e).split(`
112
+ `).length}function tt(t,e,s){let n=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=n?`${n}.${a}`:a,p=o?et(e,o.index):null,h=[...e.matchAll(/(?:public|protected|private)[^{;]*\s+(\w+)\s*\([^)]*\)\s*(?:throws[^{]*)?\{/gm)],m=e.match(/extends\s+([\w.]+)/)?.[1],d=e.match(/implements\s+([\w.,\s]+)/)?.[1]?.split(",").map(g=>g.trim()),u=e.match(/\/\*\*([\s\S]*?)\*\//)?.[1]?.replace(/\s*\*\s?/g," ").trim(),f=l?h.map(g=>({name:g[1],fqn:`${l}.${g[1]}`,kind:"method",exported:!1,description:"",line:et(e,g.index)})):[];return{language:"java",symbols:[{name:a,fqn:l,kind:c,exported:!0,description:u||"",line:p},...f],imports:i,extends:m?[m]:[],implements:d||[]}}function ie(t,e){return t.substring(0,e).split(`
113
+ `).length}function re(t,e,s,n="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=nt(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"class",exported:a,description:c,line:ie(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:ie(e,o.index)});for(let o of e.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)){let a=nt(e,o.index);i.push({name:o[1],fqn:`${t}::${o[1]}`,kind:"function",exported:!0,description:a,line:ie(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:ie(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:ie(e,o.index)});return{language:n,symbols:i,imports:r}}function nt(t,e){let n=t.substring(0,e).match(/\/\*\*([\s\S]*?)\*\/\s*$/);return n?n[1].replace(/\s*\*\s?/g," ").trim():""}function st(t,e,s){let n=t.split("/").pop().replace(".vue",""),r=e.match(/<script(?:\s+setup)?(?:\s+lang=["']ts["'])?>([\s\S]*?)<\/script>/i)?.[1]||"",o=re(t,r,s,"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:n,fqn:`${t}::${n}`,kind:"component",exported:!0,description:`Vue component: ${n}`}),o.childComponents=[...new Set(c)],o}function ke(t,e){return t.substring(0,e).split(`
114
+ `).length}function it(t,e,s){let n=[],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();n.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"class",exported:!0,description:o||"",line:ke(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();n.push({name:r[1],fqn:`${t}::${r[1]}`,kind:"function",exported:!0,description:o||"",line:ke(e,r.index)})}for(let r of e.matchAll(/^@([\w.]+)(?:\(([^)]*)\))?/gm))n.push({name:r[1],fqn:`${t}::@${r[1]}`,kind:"decorator",exported:!1,description:"",line:ke(e,r.index)});return{language:"python",symbols:n,imports:i}}function rt(t,e,s){let n=[];for(let i of e.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"\w.]+)/gi))n.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))n.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))n.push({name:i[1],fqn:`${t}::${i[1]}`,kind:"view",exported:!0,description:""});return{language:"sql",symbols:n,imports:[]}}function ot(t,e,s){let n=[];for(let i of e.matchAll(/^([\w.-]+)\s*[:=]\s*(.+)/gm)){let r=i[1].trim();r.startsWith("#")||n.push({name:r,fqn:`${t}::${r}`,kind:"config-key",exported:!1,description:i[2].trim().substring(0,80)})}return{language:"config",symbols:n,imports:[]}}var yn=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 ve(t,e,s){let n=[];for(let r of e.matchAll(/\bid\s*=\s*["']([^"']+)["']/g))n.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();yn.has(o)||i.has(o)||(i.add(o),n.push({name:o,fqn:`${t}::${o}`,kind:"component",exported:!0,description:""}))}return{language:"xml",symbols:n,imports:[]}}function at(t,e,s){let n=[];for(let i of e.matchAll(/^(#{1,3})\s+(.+)/gm)){let r=i[1].length,o=i[2].trim();n.push({name:o,fqn:`${t}::${o.replace(/\s+/g,"-").toLowerCase()}`,kind:r===1?"doc-title":"doc-section",exported:!0,description:""})}return{language:"markdown",symbols:n,imports:[]}}var En={java:tt,typescript:re,javascript:re,vue:st,python:it,sql:rt,config:ot,xml:ve,html:ve,markdown:at};function Sn(t){let e=_n.extname(t).toLowerCase(),s=J[e];return{language:s,parser:En[s]||null}}async function ct(t,e,s){let{language:n,parser:i}=Sn(t);return i?i(t,e,s,n):null}var xn=[{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 pt(t,e,s){let n=[],i=e.split(`
115
+ `);for(let r of xn)r.lang&&!r.lang.includes(s)||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
+ `);if(r.negPattern.test(c))return}n.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 n}function lt(t,e,s){let n=[];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&&n.push({manager:"maven",group_id:o,artifact:a,version:c,scope:l})}return n}function dt(t,e,s){let n=[];try{let i=JSON.parse(e);for(let[r,o]of Object.entries(i.dependencies||{}))n.push({manager:"npm",group_id:"dependencies",artifact:r,version:o,scope:"runtime"});for(let[r,o]of Object.entries(i.devDependencies||{}))n.push({manager:"npm",group_id:"devDependencies",artifact:r,version:o,scope:"dev"});for(let[r,o]of Object.entries(i.peerDependencies||{}))n.push({manager:"npm",group_id:"peerDependencies",artifact:r,version:o,scope:"peer"})}catch{}return n}function ut(t,e,s){let n=[];for(let i of e.matchAll(/(\w+)\s+['"]([^:'"]+):([^:'"]+):([^'"]+)['"]/g)){let r=i[1],o=i[2],a=i[3],c=i[4];n.push({manager:"gradle",group_id:o,artifact:a,version:c,scope:r})}return n}function mt(t,{repo:e,path:s,language:n,file_type:i}){let r=t.prepare("SELECT id FROM main.files WHERE path = ?").get(s);return r?(t.prepare("UPDATE main.files SET language = ?, file_type = ?, last_indexed = ? WHERE id = ?").run(n,i,Date.now(),r.id),r.id):t.prepare("INSERT INTO main.files (repo, path, language, file_type, last_indexed) VALUES (?, ?, ?, ?, ?)").run(e,s,n,i,Date.now()).lastInsertRowid}function ft(t,{file_id:e,fqn:s,name:n,kind:i,language:r,exported:o,description:a,line:c}){let l=t.prepare("SELECT id FROM main.symbols WHERE fqn = ?").get(s);l?t.prepare("UPDATE main.symbols SET file_id=?, name=?, kind=?, language=?, exported=?, description=?, line=? WHERE id=?").run(e,n,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,s,n,i,r,o?1:0,a||"",c??null)}function ht(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 gt(t,{from_file_id:e,to_fqn:s,dep_type:n}){t.prepare("INSERT INTO main.dependencies (from_file_id, to_fqn, dep_type) VALUES (?, ?, ?)").run(e,s,n)}function yt(t,{repo:e,manager:s,group_id:n,artifact:i,version:r,scope:o}){t.prepare("SELECT rowid FROM main.build_deps WHERE repo=? AND manager=? AND artifact=?").get(e,s,i)||t.prepare("INSERT INTO main.build_deps (repo, manager, group_id, artifact, version, scope) VALUES (?, ?, ?, ?, ?, ?)").run(e,s,n,i,r,o)}function _t(t,{file_id:e,line:s,severity:n,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,s,n,i,r,o,a)}import Tn from"fs/promises";var bn=/(?:\/\/|#|\/\*|\*|<!--)\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s]+(.*)/i;async function me(t,e){t.prepare("DELETE FROM main.todos WHERE source = ?").run("scan");let s=t.prepare("INSERT INTO main.todos (source, file, line, kind, text) VALUES (?, ?, ?, ?, ?)"),n=0;for(let i of e)try{let o=(await Tn.readFile(i,"utf-8")).split(`
117
+ `);for(let a=0;a<o.length;a++){let c=bn.exec(o[a]);if(c){let l=c[1].toUpperCase(),p=c[2].trim();p&&(s.run("scan",i,a+1,l,p),n++)}}}catch{}return n}function Rn(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 Et(t,e,s,n,i){try{let r=await Ie.readFile(e,"utf-8"),o=await ct(e,r,s);if(!o)return;let{language:a}=o;if(n&&!n.includes(a))return;i.files_by_language[a]=(i.files_by_language[a]||0)+1;let c=mt(t,{repo:s,path:e,language:a,file_type:Rn(e)});ht(t,c);for(let p of o.symbols||[])ft(t,{...p,file_id:c,language:a}),i.symbols_total++;for(let p of o.imports||[])gt(t,{from_file_id:c,to_fqn:p,dep_type:"imports"}),i.dependencies_mapped++;let l=pt(e,r,a);for(let p of l)_t(t,{file_id:c,...p}),i.security_findings++}catch{}}function St(t){t.exec(`
118
118
  DELETE FROM main.fts_index;
119
119
  INSERT INTO main.fts_index(fqn, name, description, path, repo, language, kind)
120
120
  SELECT s.fqn, s.name, COALESCE(s.description,''), f.path, f.repo, s.language, s.kind
121
121
  FROM main.symbols s JOIN main.files f ON s.file_id = f.id;
122
- `)}async function Et(t,{languages:e}={}){let s=Date.now(),n=process.cwd(),i=n.replace(/\\/g,"/");if(i.includes("/.cairn/")||i.endsWith("/.cairn"))return{content:[{type:"text",text:JSON.stringify({error:"Cannot run cairn_maintain from inside a .cairn/ directory. Please run from the project root."})}]};let r=re.basename(n),o=await ze(n),a=o.map(u=>re.join(u,".cairn","index.db")),c={repos_indexed:1,sub_indexes_found:o.length,files_by_language:{},symbols_total:0,dependencies_mapped:0,security_findings:0,todos_found:0},p=await P(n,o);for(let u of p)await gt(t,u,r,e,c);for(let u of Ve){let d=re.join(n,u);try{let g=await Le.readFile(d,"utf-8"),f=[];u==="pom.xml"?f=at(d,g,r):u==="package.json"?f=ct(d,g,r):u==="build.gradle"&&(f=pt(d,g,r));for(let h of f)ft(t,{repo:r,...h})}catch{}}c.todos_found=await me(t,p),yt(t),a.length>0&&(Je(t,a),Re(t));let l=null;try{let u=re.join(n,".gitignore");(await Le.readFile(u,"utf-8").catch(()=>"")).split(`
123
- `).map(h=>h.trim()).some(h=>h===".cairn"||h===".cairn/"||h==="/.cairn"||h==="/.cairn/")||(l="Add .cairn/ to your .gitignore to avoid committing the index and session cache.")}catch{}c.duration_ms=Date.now()-s;let m={...c};return l&&(m.gitignore_hint=l),{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}async function fe(t,e){let s=process.cwd(),n=re.basename(s),i={files_by_language:{},symbols_total:0,dependencies_mapped:0,security_findings:0};for(let r of e)await gt(t,r,n,null,i);return yt(t),i}function _t(t,{query:e,limit:s=10,language:n,kind:i}){if(!e?.trim())return{content:[{type:"text",text:"[]"}]};let r=["main",...ue(t)],o=[];for(let u of r){let d=`
122
+ `)}async function xt(t,{languages:e}={}){let s=Date.now(),n=process.cwd(),i=n.replace(/\\/g,"/");if(i.includes("/.cairn/")||i.endsWith("/.cairn"))return{content:[{type:"text",text:JSON.stringify({error:"Cannot run cairn_maintain from inside a .cairn/ directory. Please run from the project root."})}]};let r=oe.basename(n),o=await Ze(n),a=o.map(m=>oe.join(m,".cairn","index.db")),c={repos_indexed:1,sub_indexes_found:o.length,files_by_language:{},symbols_total:0,dependencies_mapped:0,security_findings:0,todos_found:0},l=await P(n,o);for(let m of l)await Et(t,m,r,e,c);for(let m of Qe){let d=oe.join(n,m);try{let u=await Ie.readFile(d,"utf-8"),f=[];m==="pom.xml"?f=lt(d,u,r):m==="package.json"?f=dt(d,u,r):m==="build.gradle"&&(f=ut(d,u,r));for(let g of f)yt(t,{repo:r,...g})}catch{}}c.todos_found=await me(t,l),St(t),a.length>0&&(ze(t,a),Ne(t));let p=null;try{let m=oe.join(n,".gitignore");(await Ie.readFile(m,"utf-8").catch(()=>"")).split(`
123
+ `).map(g=>g.trim()).some(g=>g===".cairn"||g===".cairn/"||g==="/.cairn"||g==="/.cairn/")||(p="Add .cairn/ to your .gitignore to avoid committing the index and session cache.")}catch{}c.duration_ms=Date.now()-s;let h={...c};return p&&(h.gitignore_hint=p),{content:[{type:"text",text:JSON.stringify(h,null,2)}]}}async function fe(t,e){let s=process.cwd(),n=oe.basename(s),i={files_by_language:{},symbols_total:0,dependencies_mapped:0,security_findings:0};for(let r of e)await Et(t,r,n,null,i);return St(t),i}function Tt(t,{query:e,limit:s=10,language:n,kind:i}){if(!e?.trim())return{content:[{type:"text",text:"[]"}]};let r=["main",...ue(t)],o=[];for(let m of r){let d=`
124
124
  SELECT fqn, name, kind, language, description, path, repo,
125
125
  bm25(fts_index) as score
126
- FROM ${u}.fts_index
127
- WHERE ${u}.fts_index MATCH ?
128
- `,g=[e];n&&(d+=" AND language = ?",g.push(n)),i&&(d+=" AND kind = ?",g.push(i)),d+=" ORDER BY bm25(fts_index)",d+=` LIMIT ${s*2}`;try{let f=t.prepare(d).all(...g);o.push(...f)}catch{}}o.sort((u,d)=>u.score-d.score);let a=new Set,p=o.filter(u=>a.has(u.fqn)?!1:(a.add(u.fqn),!0)).slice(0,s),l=new Map;for(let u of r){let d=p.filter(g=>!l.has(g.fqn)).map(g=>g.fqn);if(d.length===0)break;try{let g=d.map(()=>"?").join(","),f=t.prepare(`SELECT fqn, line FROM ${u}.symbols WHERE fqn IN (${g})`).all(...d);for(let h of f)h.line!=null&&l.set(h.fqn,h.line)}catch{}}let m=p.map(u=>({...u,line:l.get(u.fqn)??null}));return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}function St(t,{path:e}){let s=t.prepare("SELECT * FROM files WHERE path LIKE ?").all(`${e}%`);if(s.length===0)return{content:[{type:"text",text:JSON.stringify({path:e,error:"No files found"})}]};let n=s.map(d=>d.id),i=[...new Set(s.map(d=>d.language).filter(Boolean))],r=n.map(()=>"?").join(","),o=t.prepare(`SELECT * FROM symbols WHERE file_id IN (${r})`).all(...n),a={};for(let d of o){let g=d.kind||"other";a[g]||(a[g]=[]),a[g].push(d.name)}let c=t.prepare(`SELECT DISTINCT to_fqn FROM dependencies WHERE from_file_id IN (${r}) AND dep_type = 'imports'`).all(...n).map(d=>d.to_fqn),p=[];if(o.length>0){let d=o.map(f=>f.fqn),g=d.map(()=>"?").join(",");p=t.prepare(`SELECT DISTINCT f.path FROM dependencies d JOIN files f ON d.from_file_id = f.id WHERE d.to_fqn IN (${g})`).all(...d).map(f=>f.path)}let l=new Set(t.prepare("SELECT fqn FROM symbols").all().map(d=>d.fqn)),m=[...new Set(c.filter(d=>!l.has(d)))].slice(0,20),u={path:e,languages:i,symbols:a,imports_from:c.slice(0,20),imported_by:p.slice(0,20),external_deps:m};return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}function xt(t,{module:e,mode:s}){if(s==="health"){let n=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:n,symbols:i,dependencies:r,security_findings:o,by_language:a,last_indexed:c?new Date(c).toISOString():null},null,2)}]}}if(s==="instability"){let n=t.prepare("SELECT id, path, language FROM files").all(),i={};for(let a of n){let p=a.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/")||"/";e&&!p.includes(e)||(i[p]||(i[p]={name:p,language:a.language,fileIds:[]}),i[p].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(","),p=t.prepare(`SELECT COUNT(DISTINCT to_fqn) as c FROM dependencies WHERE from_file_id IN (${c})`).get(...a.fileIds)?.c||0,l=t.prepare(`SELECT fqn FROM symbols WHERE file_id IN (${c})`).all(...a.fileIds).map(g=>g.fqn),m=0;if(l.length>0){let g=l.map(()=>"?").join(",");m=t.prepare(`SELECT COUNT(DISTINCT from_file_id) as c FROM dependencies WHERE to_fqn IN (${g})`).get(...l)?.c||0}let u=m+p===0?0:p/(m+p),d=u>=.8?"safe_to_refactor":u>=.3?"review_before_change":"load_bearing";r.push({name:a.name,language:a.language,instability:Math.round(u*100)/100,status:d})}r.sort((a,c)=>c.instability-a.instability);let o=t.prepare(`
126
+ FROM ${m}.fts_index
127
+ WHERE ${m}.fts_index MATCH ?
128
+ `,u=[e];n&&(d+=" AND language = ?",u.push(n)),i&&(d+=" AND kind = ?",u.push(i)),d+=" ORDER BY bm25(fts_index)",d+=` LIMIT ${s*2}`;try{let f=t.prepare(d).all(...u);o.push(...f)}catch{}}o.sort((m,d)=>m.score-d.score);let a=new Set,l=o.filter(m=>a.has(m.fqn)?!1:(a.add(m.fqn),!0)).slice(0,s),p=new Map;for(let m of r){let d=l.filter(u=>!p.has(u.fqn)).map(u=>u.fqn);if(d.length===0)break;try{let u=d.map(()=>"?").join(","),f=t.prepare(`SELECT fqn, line FROM ${m}.symbols WHERE fqn IN (${u})`).all(...d);for(let g of f)g.line!=null&&p.set(g.fqn,g.line)}catch{}}let h=l.map(m=>({...m,line:p.get(m.fqn)??null}));return{content:[{type:"text",text:JSON.stringify(h,null,2)}]}}function bt(t,{path:e}){let s=t.prepare("SELECT * FROM files WHERE path LIKE ?").all(`${e}%`);if(s.length===0)return{content:[{type:"text",text:JSON.stringify({path:e,error:"No files found"})}]};let n=s.map(d=>d.id),i=[...new Set(s.map(d=>d.language).filter(Boolean))],r=n.map(()=>"?").join(","),o=t.prepare(`SELECT * FROM symbols WHERE file_id IN (${r})`).all(...n),a={};for(let d of o){let u=d.kind||"other";a[u]||(a[u]=[]),a[u].push(d.name)}let c=t.prepare(`SELECT DISTINCT to_fqn FROM dependencies WHERE from_file_id IN (${r}) AND dep_type = 'imports'`).all(...n).map(d=>d.to_fqn),l=[];if(o.length>0){let d=o.map(f=>f.fqn),u=d.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 (${u})`).all(...d).map(f=>f.path)}let p=new Set(t.prepare("SELECT fqn FROM symbols").all().map(d=>d.fqn)),h=[...new Set(c.filter(d=>!p.has(d)))].slice(0,20),m={path:e,languages:i,symbols:a,imports_from:c.slice(0,20),imported_by:l.slice(0,20),external_deps:h};return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}function Rt(t,{module:e,mode:s}){if(s==="health"){let n=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:n,symbols:i,dependencies:r,security_findings:o,by_language:a,last_indexed:c?new Date(c).toISOString():null},null,2)}]}}if(s==="instability"){let n=t.prepare("SELECT id, path, language FROM files").all(),i={};for(let a of n){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(u=>u.fqn),h=0;if(p.length>0){let u=p.map(()=>"?").join(",");h=t.prepare(`SELECT COUNT(DISTINCT from_file_id) as c FROM dependencies WHERE to_fqn IN (${u})`).get(...p)?.c||0}let m=h+l===0?0:l/(h+l),d=m>=.8?"safe_to_refactor":m>=.3?"review_before_change":"load_bearing";r.push({name:a.name,language:a.language,instability:Math.round(m*100)/100,status:d})}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
@@ -139,59 +139,63 @@ import{Server as ts}from"@modelcontextprotocol/sdk/server/index.js";import{Stdio
139
139
  JOIN symbols s2 ON d2.to_fqn = s2.fqn
140
140
  WHERE s2.file_id = d1.from_file_id
141
141
  LIMIT 20
142
- `).all();return{content:[{type:"text",text:JSON.stringify({cycles_detected:n},null,2)}]}}return{content:[{type:"text",text:JSON.stringify({error:`Unknown mode: ${s}`})}]}}function Tt(t,{paths:e,severity:s="HIGH",language:n}){let i=`
142
+ `).all();return{content:[{type:"text",text:JSON.stringify({cycles_detected:n},null,2)}]}}return{content:[{type:"text",text:JSON.stringify({error:`Unknown mode: ${s}`})}]}}function wt(t,{paths:e,severity:s="HIGH",language:n}){let i=`
143
143
  SELECT sf.severity, sf.cwe, sf.rule_name as rule, sf.line,
144
144
  sf.code_snippet as snippet, sf.description as fix,
145
145
  f.path as file, f.language as lang
146
146
  FROM security_findings sf
147
147
  JOIN files f ON sf.file_id = f.id
148
148
  WHERE 1=1
149
- `,r=[];s==="HIGH"?i+=" AND sf.severity = 'HIGH'":s==="MEDIUM"?i+=" AND sf.severity IN ('HIGH','MEDIUM')":s!=="ALL"&&(i+=" AND sf.severity = ?",r.push(s)),n&&(i+=" AND f.language = ?",r.push(n)),e&&e.length>0&&(i+=` AND (${e.map(()=>"f.path LIKE ?").join(" OR ")})`,r.push(...e.map(p=>`${p}%`))),i+=" ORDER BY sf.severity, f.path";let o=t.prepare(i).all(...r),a={},c={};for(let p of o)a[p.lang]=(a[p.lang]||0)+1,c[p.severity]=(c[p.severity]||0)+1;return{content:[{type:"text",text:JSON.stringify({total_findings:o.length,by_language:a,by_severity:c,findings:o.slice(0,100)},null,2)}]}}import K from"fs";import Q from"path";function z(t,e,s={}){let{noComments:n=!0,noEmptyLines:i=!0,aggressive:r=!1}=s,o=t;return n&&(o=xn(o,e)),i&&(o=Tn(o)),o=bn(o),r&&(o=Rn(o)),o.trim()}function xn(t,e){switch(e){case"java":case"typescript":case"javascript":case"vue":return t.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(s,n)=>n||!s.startsWith("//")?s:"");case"python":return t.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return t.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return t.replace(/<!--[\s\S]*?-->/g,"");case"config":return t.replace(/#.*$/gm,"");default:return t}}function Tn(t){return t.split(`
149
+ `,r=[];s==="HIGH"?i+=" AND sf.severity = 'HIGH'":s==="MEDIUM"?i+=" AND sf.severity IN ('HIGH','MEDIUM')":s!=="ALL"&&(i+=" AND sf.severity = ?",r.push(s)),n&&(i+=" AND f.language = ?",r.push(n)),e&&e.length>0&&(i+=` AND (${e.map(()=>"f.path LIKE ?").join(" OR ")})`,r.push(...e.map(l=>`${l}%`))),i+=" ORDER BY sf.severity, f.path";let o=t.prepare(i).all(...r),a={},c={};for(let l of o)a[l.lang]=(a[l.lang]||0)+1,c[l.severity]=(c[l.severity]||0)+1;return{content:[{type:"text",text:JSON.stringify({total_findings:o.length,by_language:a,by_severity:c,findings:o.slice(0,100)},null,2)}]}}import Q from"fs";import Z from"path";function K(t,e,s={}){let{noComments:n=!0,noEmptyLines:i=!0,aggressive:r=!1}=s,o=t;return n&&(o=wn(o,e)),i&&(o=On(o)),o=Nn(o),r&&(o=Ln(o)),o.trim()}function wn(t,e){switch(e){case"java":case"typescript":case"javascript":case"vue":return t.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1|\/(?![/*])(?:[^/\\\n]|\\.)*\/[gimsuy]*|\/\/[^\n]*/g,(s,n)=>n||!s.startsWith("//")?s:"");case"python":return t.replace(/#.*$/gm,"").replace(/'''[\s\S]*?'''/g,"").replace(/"""[\s\S]*?"""/g,"");case"sql":return t.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"");case"xml":case"html":return t.replace(/<!--[\s\S]*?-->/g,"");case"config":return t.replace(/#.*$/gm,"");default:return t}}function On(t){return t.split(`
150
150
  `).filter(e=>e.trim().length>0).join(`
151
- `)}function bn(t){return t.split(`
151
+ `)}function Nn(t){return t.split(`
152
152
  `).map(e=>e.trimEnd()).join(`
153
- `)}function Rn(t){return t.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function he(t,e={}){let{noStyle:s=!1,noComments:n=!0,noEmptyLines:i=!0,aggressive:r=!1}=e,o=[],a=t.match(/<template>([\s\S]*?)<\/template>/i);if(a){let p=a[1];n&&(p=p.replace(/<!--[\s\S]*?-->/g,"")),i&&(p=p.split(`
154
- `).filter(l=>l.trim()).join(`
155
- `)),p=p.replace(/\s+/g," ").trim(),p&&o.push(`<template>${p}</template>`)}let c=t.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);if(c){let p=c[1],l=z(c[2],"javascript",{noComments:n,noEmptyLines:i,aggressive:r}),m=/setup/i.test(p)?"<script setup>":"<script>";l&&o.push(`${m}${l}</script>`)}if(!s){let p=t.match(/<style([^>]*)>([\s\S]*?)<\/style>/i);if(p){let l=p[2].replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim();l&&o.push(`<style${p[1]}>${l}</style>`)}}return o.join(`
156
-
157
- `)}async function bt(t={}){let{bundleName:e="default",noComments:s=!0,noEmptyLines:n=!0,noStyle:i=!1,aggressive:r=!1,onlyChanged:o=!1,filterLang:a=null,filterPaths:c=null,maxSizeKB:p=800}=t,l=Q.join(M(),"bundles"),m=process.cwd(),u=Q.join(l,`${e}.txt`),d=Q.join(l,`${e}.mtime.json`),g=o&&K.existsSync(d)?JSON.parse(K.readFileSync(d,"utf8")):{},f={},h=[],y={processed:0,skipped_unchanged:0,skipped_size:0,original_bytes:0,compressed_bytes:0,by_language:{}},_=a?Array.isArray(a)?a:[a]:null,T=await P(m);for(let E of T){let R=Q.extname(E).toLowerCase(),x=X[R];if(!x||_&&!_.includes(x))continue;if(c){let S=Q.relative(m,E).replace(/\\/g,"/");if(!c.some(I=>S.startsWith(I)))continue}let O=K.statSync(E).mtimeMs;if(f[E]=O,o&&g[E]===O){y.skipped_unchanged++;continue}if(Buffer.byteLength(h.join(`
158
- `),"utf8")/1024>p){y.skipped_size++;continue}let L;try{L=K.readFileSync(E,"utf8")}catch{continue}y.original_bytes+=Buffer.byteLength(L,"utf8");let w=x==="vue"?he(L,{noStyle:i,noComments:s,noEmptyLines:n,aggressive:r}):z(L,x,{noComments:s,noEmptyLines:n,aggressive:r});if(!w.trim())continue;let le=Q.relative(m,E).replace(/\\/g,"/");h.push(`### File: ${le}`),h.push(w.trim()),h.push(""),y.compressed_bytes+=Buffer.byteLength(w,"utf8"),y.processed++,y.by_language[x]=(y.by_language[x]||0)+1}let b=h.join(`
159
- `);K.writeFileSync(u,b,"utf8"),o&&K.writeFileSync(d,JSON.stringify(f),"utf8");let C=y.original_bytes>0?Math.round((1-y.compressed_bytes/y.original_bytes)*100):0;return{bundle_path:u,processed:y.processed,skipped_unchanged:y.skipped_unchanged,skipped_size_cap:y.skipped_size,original_kb:Math.round(y.original_bytes/1024),compressed_kb:Math.round(y.compressed_bytes/1024),reduction_pct:C,by_language:y.by_language}}async function Rt(t,e){let{bundle_name:s="default",filter_paths:n=null,filter_language:i=null,no_comments:r=!0,no_empty_lines:o=!0,no_style:a=!1,aggressive:c=!1,only_changed:p=!1,max_size_kb:l=800}=e,m=await bt({bundleName:s,noComments:r,noEmptyLines:o,noStyle:a,aggressive:c,onlyChanged:p,filterLang:i,filterPaths:n,maxSizeKB:l});return m.next_step=`Read the bundle: Read("${m.bundle_path}")`,{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}import W from"fs";import Z from"path";var wn=1e3,On=100*1024;function Nn(t,e,s,n){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:`[${s}] ${i}`;if(r.length<=wn)return r;let o=Z.join(e,"notes");W.mkdirSync(o,{recursive:!0});let c=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${n}.md`,p=Z.join(o,c);return W.writeFileSync(p,r,"utf8"),{description:r.split(`
160
- `)[0].slice(0,120),ref:Z.join(".cairn","notes",c)}}function wt(t,{message:e,active_files:s=[],notes:n=[]}){if(typeof n=="string")try{n=JSON.parse(n)}catch{n=n?[n]:[]}Array.isArray(n)||(n=[]);let i=M(),r=Z.join(i,"session.json"),o=new Date().toISOString().slice(0,10),a={};try{let l=t.prepare("SELECT path FROM main.files").all();for(let{path:m}of l)try{a[m]=W.statSync(m).mtimeMs}catch{}}catch{}for(let l of s)try{a[l]=W.statSync(l).mtimeMs}catch{}let c=n.map((l,m)=>Nn(l,i,o,m));if(W.existsSync(r))try{if(W.statSync(r).size>On){let m=1;for(;W.existsSync(Z.join(i,`session.${m}.json`));)m++;W.renameSync(r,Z.join(i,`session.${m}.json`))}}catch{}let p={message:e,checkpoint_at:new Date().toISOString(),active_files:s,notes:c,mtime_snapshot:a};return W.writeFileSync(r,JSON.stringify(p,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:p.checkpoint_at,active_files_tracked:s.length,notes_saved:c.length},null,2)}]}}import B from"fs";import Ee from"path";import{exec as Bn}from"child_process";import{promisify as Gn}from"util";import J from"fs";import Ot from"path";function Nt(t){try{return JSON.parse(J.readFileSync(Ot.join(t,"minify-map.json"),"utf8"))}catch{return{}}}function ke(t,e){J.mkdirSync(t,{recursive:!0}),J.writeFileSync(Ot.join(t,"minify-map.json"),JSON.stringify(e,null,2),"utf8")}function Lt(t,e){let s=0;for(let[n,i]of Object.entries(e)){let r=!1;if(!J.existsSync(n))r=!0;else if(!J.existsSync(i.tempPath))r=!0;else try{J.statSync(n).mtimeMs>i.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(i.tempPath)try{J.unlinkSync(i.tempPath)}catch{}delete e[n],s++}}return s}import F from"fs";import ee from"path";var It=["preference","experience"],kt=["preference","experience","decision","knowledge"];function Ct(t){if(!t||typeof t!="string")return[];let e=t.split(/\r?\n/),s=!1,n=null,i=[],r=new Set;for(let o of e){if(/^##\s+Phases\s*$/i.test(o)){s=!0;continue}if(/^##\s+/.test(o)&&!/^###/.test(o)){n&&(i.push(n),n=null),s=!1;continue}if(!s)continue;let a=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(o);if(a){n&&i.push(n);let c=parseInt(a[1],10),p=a[2];if(r.has(c)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${c} ignored
161
- `),n=null;continue}r.add(c),n={phase_number:c,text:p,bodyLines:[]};continue}n&&n.bodyLines.push(o)}return n&&i.push(n),i.map(o=>({phase_number:o.phase_number,text:o.text,body:o.bodyLines.join(`
162
- `).trim()||null}))}function $t(t,e,s){let n=t.prepare("SELECT id, phase_number, status FROM main.roadmap_phases WHERE decision_name = ? ORDER BY phase_number").all(e),i=new Map(n.map(u=>[u.phase_number,u])),r=new Set,o=t.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),a=t.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),c=t.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),p=[],l=[],m=[];for(let u=0;u<s.length;u++){let d=s[u];r.add(d.phase_number);let g=i.get(d.phase_number);if(g)o.run(d.text,d.body,"stale","open",g.id),p.push({id:g.id,phase_number:d.phase_number,text:d.text,reactivated:g.status==="stale"});else{let f=a.run(e,d.phase_number,d.text,d.body,"open",u);l.push({id:Number(f.lastInsertRowid),phase_number:d.phase_number,text:d.text})}}for(let[u,d]of i)!r.has(u)&&d.status!=="stale"&&d.status!=="done"&&(c.run(d.id),m.push({id:d.id,phase_number:u}));return{updated:p.length,inserted:l.length,staled:m.length,updated_phases:p,inserted_phases:l,staled_phases:m}}function oe(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function Ln(t,e){return`${t}_${oe(e)}.md`}function kn(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return{};let s={};for(let n of e[1].split(`
163
- `)){let i=n.indexOf(":");i!==-1&&(s[n.slice(0,i).trim()]=n.slice(i+1).trim())}return s}function At(t){let e=ee.join(t,"MEMORY.md");return F.existsSync(e)?F.readFileSync(e,"utf8"):""}function vt(t,e){let s=["# Memory Index",""];for(let n of e)s.push(`- [${n.name}](${n.file}) \u2014 ${n.description} \`[${n.type}]\``);F.writeFileSync(ee.join(t,"MEMORY.md"),s.join(`
153
+ `)}function Ln(t){return t.replace(/\s+/g," ").replace(/\s*([=+\-*/%<>!&|^~?:;,{}()[\]])\s*/g,"$1").replace(/;\s*}/g,"}").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").trim()}function he(t,e={}){let{noStyle:s=!1,noComments:n=!0,noEmptyLines:i=!0,aggressive:r=!1}=e,o=[],a=t.match(/<template>([\s\S]*?)<\/template>/i);if(a){let l=a[1];n&&(l=l.replace(/<!--[\s\S]*?-->/g,"")),i&&(l=l.split(`
154
+ `).filter(p=>p.trim()).join(`
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=K(c[2],"javascript",{noComments:n,noEmptyLines:i,aggressive:r}),h=/setup/i.test(l)?"<script setup>":"<script>";p&&o.push(`${h}${p}</script>`)}if(!s){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
+
157
+ `)}async function Ot(t={}){let{bundleName:e="default",noComments:s=!0,noEmptyLines:n=!0,noStyle:i=!1,aggressive:r=!1,onlyChanged:o=!1,filterLang:a=null,filterPaths:c=null,maxSizeKB:l=800}=t,p=Z.join(A(),"bundles"),h=process.cwd(),m=Z.join(p,`${e}.txt`),d=Z.join(p,`${e}.mtime.json`),u=o&&Q.existsSync(d)?JSON.parse(Q.readFileSync(d,"utf8")):{},f={},g=[],y={processed:0,skipped_unchanged:0,skipped_size:0,original_bytes:0,compressed_bytes:0,by_language:{}},_=a?Array.isArray(a)?a:[a]:null,T=await P(h);for(let E of T){let R=Z.extname(E).toLowerCase(),b=J[R];if(!b||_&&!_.includes(b))continue;if(c){let S=Z.relative(h,E).replace(/\\/g,"/");if(!c.some(C=>S.startsWith(C)))continue}let O=Q.statSync(E).mtimeMs;if(f[E]=O,o&&u[E]===O){y.skipped_unchanged++;continue}if(Buffer.byteLength(g.join(`
158
+ `),"utf8")/1024>l){y.skipped_size++;continue}let k;try{k=Q.readFileSync(E,"utf8")}catch{continue}y.original_bytes+=Buffer.byteLength(k,"utf8");let w=b==="vue"?he(k,{noStyle:i,noComments:s,noEmptyLines:n,aggressive:r}):K(k,b,{noComments:s,noEmptyLines:n,aggressive:r});if(!w.trim())continue;let le=Z.relative(h,E).replace(/\\/g,"/");g.push(`### File: ${le}`),g.push(w.trim()),g.push(""),y.compressed_bytes+=Buffer.byteLength(w,"utf8"),y.processed++,y.by_language[b]=(y.by_language[b]||0)+1}let x=g.join(`
159
+ `);Q.writeFileSync(m,x,"utf8"),o&&Q.writeFileSync(d,JSON.stringify(f),"utf8");let I=y.original_bytes>0?Math.round((1-y.compressed_bytes/y.original_bytes)*100):0;return{bundle_path:m,processed:y.processed,skipped_unchanged:y.skipped_unchanged,skipped_size_cap:y.skipped_size,original_kb:Math.round(y.original_bytes/1024),compressed_kb:Math.round(y.compressed_bytes/1024),reduction_pct:I,by_language:y.by_language}}async function Nt(t,e){let{bundle_name:s="default",filter_paths:n=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,h=await Ot({bundleName:s,noComments:r,noEmptyLines:o,noStyle:a,aggressive:c,onlyChanged:l,filterLang:i,filterPaths:n,maxSizeKB:p});return h.next_step=`Read the bundle: Read("${h.bundle_path}")`,{content:[{type:"text",text:JSON.stringify(h,null,2)}]}}import q from"fs";import ee from"path";var kn=1e3,vn=100*1024;function In(t,e,s,n){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:`[${s}] ${i}`;if(r.length<=kn)return r;let o=ee.join(e,"notes");q.mkdirSync(o,{recursive:!0});let c=`note-${new Date().toISOString().replace(/[:.]/g,"-")}-${n}.md`,l=ee.join(o,c);return q.writeFileSync(l,r,"utf8"),{description:r.split(`
160
+ `)[0].slice(0,120),ref:ee.join(".cairn","notes",c)}}function Lt(t,{message:e,active_files:s=[],notes:n=[]}){if(typeof n=="string")try{n=JSON.parse(n)}catch{n=n?[n]:[]}Array.isArray(n)||(n=[]);let i=A(),r=ee.join(i,"session.json"),o=new Date().toISOString().slice(0,10),a={};try{let p=t.prepare("SELECT path FROM main.files").all();for(let{path:h}of p)try{a[h]=q.statSync(h).mtimeMs}catch{}}catch{}for(let p of s)try{a[p]=q.statSync(p).mtimeMs}catch{}let c=n.map((p,h)=>In(p,i,o,h));if(q.existsSync(r))try{if(q.statSync(r).size>vn){let h=1;for(;q.existsSync(ee.join(i,`session.${h}.json`));)h++;q.renameSync(r,ee.join(i,`session.${h}.json`))}}catch{}let l={message:e,checkpoint_at:new Date().toISOString(),active_files:s,notes:c,mtime_snapshot:a};return q.writeFileSync(r,JSON.stringify(l,null,2),"utf8"),{content:[{type:"text",text:JSON.stringify({saved:!0,checkpoint_at:l.checkpoint_at,active_files_tracked:s.length,notes_saved:c.length},null,2)}]}}import G from"fs";import xe from"path";import{exec as ns}from"child_process";import{promisify as ss}from"util";import Y from"fs";import kt from"path";function vt(t){try{return JSON.parse(Y.readFileSync(kt.join(t,"minify-map.json"),"utf8"))}catch{return{}}}function Ce(t,e){Y.mkdirSync(t,{recursive:!0}),Y.writeFileSync(kt.join(t,"minify-map.json"),JSON.stringify(e,null,2),"utf8")}function It(t,e){let s=0;for(let[n,i]of Object.entries(e)){let r=!1;if(!Y.existsSync(n))r=!0;else if(!Y.existsSync(i.tempPath))r=!0;else try{Y.statSync(n).mtimeMs>i.minifiedAt&&(r=!0)}catch{r=!0}if(r){if(i.tempPath)try{Y.unlinkSync(i.tempPath)}catch{}delete e[n],s++}}return s}import F from"fs";import te from"path";var Mt=["preference","experience"],Ct=["preference","experience","decision","knowledge"];function At(t){if(!t||typeof t!="string")return[];let e=t.split(/\r?\n/),s=!1,n=null,i=[],r=new Set;for(let o of e){if(/^##\s+Phases\s*$/i.test(o)){s=!0;continue}if(/^##\s+/.test(o)&&!/^###/.test(o)){n&&(i.push(n),n=null),s=!1;continue}if(!s)continue;let a=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(o);if(a){n&&i.push(n);let c=parseInt(a[1],10),l=a[2];if(r.has(c)){process.stderr.write(`[cairn] memo phase parser: duplicate Phase ${c} ignored
161
+ `),n=null;continue}r.add(c),n={phase_number:c,text:l,bodyLines:[]};continue}n&&n.bodyLines.push(o)}return n&&i.push(n),i.map(o=>({phase_number:o.phase_number,text:o.text,body:o.bodyLines.join(`
162
+ `).trim()||null}))}function jt(t,e,s){let n=t.prepare("SELECT id, phase_number, status FROM main.roadmap_phases WHERE decision_name = ? ORDER BY phase_number").all(e),i=new Map(n.map(m=>[m.phase_number,m])),r=new Set,o=t.prepare("UPDATE main.roadmap_phases SET text = ?, body = ?, status = CASE WHEN status = ? THEN ? ELSE status END WHERE id = ?"),a=t.prepare("INSERT INTO main.roadmap_phases (decision_name, phase_number, parent_phase_id, text, body, status, position) VALUES (?, ?, NULL, ?, ?, ?, ?)"),c=t.prepare("UPDATE main.roadmap_phases SET status = 'stale' WHERE id = ?"),l=[],p=[],h=[];for(let m=0;m<s.length;m++){let d=s[m];r.add(d.phase_number);let u=i.get(d.phase_number);if(u)o.run(d.text,d.body,"stale","open",u.id),l.push({id:u.id,phase_number:d.phase_number,text:d.text,reactivated:u.status==="stale"});else{let f=a.run(e,d.phase_number,d.text,d.body,"open",m);p.push({id:Number(f.lastInsertRowid),phase_number:d.phase_number,text:d.text})}}for(let[m,d]of i)!r.has(m)&&d.status!=="stale"&&d.status!=="done"&&(c.run(d.id),h.push({id:d.id,phase_number:m}));return{updated:l.length,inserted:p.length,staled:h.length,updated_phases:l,inserted_phases:p,staled_phases:h}}function ae(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function Cn(t,e){return`${t}_${ae(e)}.md`}function $n(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return{};let s={};for(let n of e[1].split(`
163
+ `)){let i=n.indexOf(":");i!==-1&&(s[n.slice(0,i).trim()]=n.slice(i+1).trim())}return s}function Ft(t){let e=te.join(t,"MEMORY.md");return F.existsSync(e)?F.readFileSync(e,"utf8"):""}function $t(t,e){let s=["# Memory Index",""];for(let n of e)s.push(`- [${n.name}](${n.file}) \u2014 ${n.description} \`[${n.type}]\``);F.writeFileSync(te.join(t,"MEMORY.md"),s.join(`
164
164
  `)+`
165
- `,"utf8")}function U(t){let e=At(t),s=[];for(let n of e.split(`
166
- `)){let i=n.match(/^- \[(.+?)\]\((.+?)\) — (.*?) `\[(.+?)\]`$/);i&&s.push({name:i[1],file:i[2],description:i[3],type:i[4]})}return s}function Mt(t){return U(t).filter(s=>s.type==="preference").map(s=>{let n=ee.join(t,s.file);return F.existsSync(n)?{name:s.name,description:s.description,file:s.file,content:F.readFileSync(n,"utf8")}:null}).filter(Boolean)}function jt(t,{action:e,name:s,type:n,content:i,description:r="",origin:o}){let a=V();if(e==="list"){let y=U(a);return{content:[{type:"text",text:JSON.stringify({total:y.length,index:At(a)||"(no memories yet)",entries:y},null,2)}]}}if(!s)throw new Error(`name is required for action "${e}". Pass: name (memory identifier), type (preference|experience|decision|knowledge), content (what to remember), and optionally description.`);if(e==="read"){let y=U(a).find(T=>T.name===s||oe(T.name)===oe(s));if(!y)return{content:[{type:"text",text:JSON.stringify({found:!1,name:s},null,2)}]};let _=ee.join(a,y.file);return F.existsSync(_)?{content:[{type:"text",text:JSON.stringify({found:!0,name:y.name,type:y.type,description:y.description,content:F.readFileSync(_,"utf8")},null,2)}]}:{content:[{type:"text",text:JSON.stringify({found:!1,name:s,reason:"file missing"},null,2)}]}}if(e==="delete"){let y=U(a),_=y.findIndex(C=>C.name===s||oe(C.name)===oe(s));if(_===-1)return{content:[{type:"text",text:JSON.stringify({deleted:!1,reason:"not found"},null,2)}]};let[T]=y.splice(_,1),b=ee.join(a,T.file);return F.existsSync(b)&&F.unlinkSync(b),vt(a,y),{content:[{type:"text",text:JSON.stringify({deleted:!0,name:s},null,2)}]}}if(!n)throw new Error("type is required for write");if(!i)throw new Error("content is required for write");if(!kt.includes(n))throw new Error(`type must be one of: ${kt.join(", ")}`);let c=Ln(n,s),p=ee.join(a,c),l=F.existsSync(p),m=new Date().toISOString(),u=m;if(l){let y=kn(F.readFileSync(p,"utf8"));y.created_at&&(u=y.created_at)}let d=["---",`name: ${s}`,`description: ${r||s}`,`type: ${n}`];o&&d.push(`origin: ${o}`),d.push(`created_at: ${u}`,`updated_at: ${m}`,"---"),F.writeFileSync(p,d.join(`
165
+ `,"utf8")}function U(t){let e=Ft(t),s=[];for(let n of e.split(`
166
+ `)){let i=n.match(/^- \[(.+?)\]\((.+?)\) — (.*?) `\[(.+?)\]`$/);i&&s.push({name:i[1],file:i[2],description:i[3],type:i[4]})}return s}function Dt(t){return U(t).filter(s=>s.type==="preference").map(s=>{let n=te.join(t,s.file);return F.existsSync(n)?{name:s.name,description:s.description,file:s.file,content:F.readFileSync(n,"utf8")}:null}).filter(Boolean)}function Pt(t,{action:e,name:s,type:n,content:i,description:r="",origin:o}){let a=z();if(e==="list"){let y=U(a);return{content:[{type:"text",text:JSON.stringify({total:y.length,index:Ft(a)||"(no memories yet)",entries:y},null,2)}]}}if(!s)throw new Error(`name is required for action "${e}". Pass: name (memory identifier), type (preference|experience|decision|knowledge), content (what to remember), and optionally description.`);if(e==="read"){let y=U(a).find(T=>T.name===s||ae(T.name)===ae(s));if(!y)return{content:[{type:"text",text:JSON.stringify({found:!1,name:s},null,2)}]};let _=te.join(a,y.file);return F.existsSync(_)?{content:[{type:"text",text:JSON.stringify({found:!0,name:y.name,type:y.type,description:y.description,content:F.readFileSync(_,"utf8")},null,2)}]}:{content:[{type:"text",text:JSON.stringify({found:!1,name:s,reason:"file missing"},null,2)}]}}if(e==="delete"){let y=U(a),_=y.findIndex(I=>I.name===s||ae(I.name)===ae(s));if(_===-1)return{content:[{type:"text",text:JSON.stringify({deleted:!1,reason:"not found"},null,2)}]};let[T]=y.splice(_,1),x=te.join(a,T.file);return F.existsSync(x)&&F.unlinkSync(x),$t(a,y),{content:[{type:"text",text:JSON.stringify({deleted:!0,name:s},null,2)}]}}if(!n)throw new Error("type is required for write");if(!i)throw new Error("content is required for write");if(!Ct.includes(n))throw new Error(`type must be one of: ${Ct.join(", ")}`);let c=Cn(n,s),l=te.join(a,c),p=F.existsSync(l),h=new Date().toISOString(),m=h;if(p){let y=$n(F.readFileSync(l,"utf8"));y.created_at&&(m=y.created_at)}let d=["---",`name: ${s}`,`description: ${r||s}`,`type: ${n}`];o&&d.push(`origin: ${o}`),d.push(`created_at: ${m}`,`updated_at: ${h}`,"---"),F.writeFileSync(l,d.join(`
167
167
  `)+`
168
168
 
169
169
  `+i+`
170
- `,"utf8");let g=U(a),f=g.findIndex(y=>y.file===c),h={name:s,file:c,description:r||s,type:n};return f>=0?g[f]=h:g.push(h),vt(a,g),{content:[{type:"text",text:JSON.stringify({saved:!0,name:s,type:n,file:p,action:l?"updated":"created"},null,2)}]}}import ve from"fs";import vn from"path";function In(t){if(t.length===0)return null;let e=t.map(i=>i.replace(/\\/g,"/")),s=e[0].split("/"),n="";for(let i=1;i<=s.length;i++){let r=s.slice(0,i).join("/");if(e.every(o=>o.startsWith(r+"/")))n=r;else break}return n||null}function Ft(t,e){let s=Object.keys(t);if(s.length===0||s.filter(p=>{try{return ve.statSync(p),!1}catch{return!0}}).length<Math.ceil(s.length*.5))return null;let i=In(s);if(!i)return null;let r=e.replace(/\\/g,"/"),o=i.replace(/\\/g,"/");if(r===o)return null;let a=s.slice(0,Math.min(8,s.length));return a.filter(p=>{let l=p.replace(/\\/g,"/").replace(o+"/","");try{return ve.statSync(r+"/"+l),!0}catch{return!1}}).length<Math.ceil(a.length*.6)?null:{oldRoot:o,newRoot:r}}function ge(t,e,s){let n=t.replace(/\\/g,"/");if(n.startsWith(e+"/"))return s+n.slice(e.length);let i=e.replace(/\//g,"\\");return t.startsWith(i+"\\")?s+"/"+t.slice(i.length+1).replace(/\\/g,"/"):t}function Dt(t,e,s,n,i,r){let o={};for(let[l,m]of Object.entries(s.mtime_snapshot||{}))o[ge(l,i,r)]=m;s.mtime_snapshot=o,s.active_files=(s.active_files||[]).map(l=>ge(l,i,r)),ve.writeFileSync(vn.join(e,"session.json"),JSON.stringify(s,null,2),"utf8");let a=i.replace(/\//g,"\\");function c(l,m){try{t.prepare(`UPDATE ${l} SET ${m} = replace(${m}, ?, ?)`).run(i,r),t.prepare(`UPDATE ${l} SET ${m} = replace(${m}, ?, ?)`).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 p={};for(let[l,m]of Object.entries(n)){let u={...m};u.tempPath&&(u.tempPath=ge(u.tempPath,i,r)),p[ge(l,i,r)]=u}for(let l of Object.keys(n))delete n[l];Object.assign(n,p)}import N from"fs";import te from"path";var ae="roadmap";function ne(){return te.join(M(),"roadmap.md")}function Pt(){return te.join(M(),"roadmap_completed.md")}function Cn(t){let e=[];for(let s of t){let n=(s.text||"").trim();n&&(!Number.isInteger(s.phase_number)||s.phase_number<1||e.push({phase_number:s.phase_number,text:n,body:s.body??null}))}return e}var Ie="decision_production_roadmap.md";function $n(t){let e=t.match(/^---\n[\s\S]*?\n---\n?/);return e?t.slice(e[0].length):t}function An(t,e){let s=te.join(t,"MEMORY.md");if(!N.existsSync(s))return;let n=N.readFileSync(s,"utf8").split(/\r?\n/).filter(i=>!i.includes(`](${e})`));N.writeFileSync(s,n.join(`
171
- `),"utf8")}function Mn(){let t=ne();if(N.existsSync(t))return null;let e=[],s=Te();s&&e.push(s),e.push(te.join(M(),"memory"));let n=null,i=null;for(let o of e){let a=te.join(o,Ie);n==null&&N.existsSync(a)&&(n=$n(N.readFileSync(a,"utf8")).trim(),i=a)}if(n==null)return null;let r=/^#\s/m.test(n)?"":`# Roadmap
170
+ `,"utf8");let u=U(a),f=u.findIndex(y=>y.file===c),g={name:s,file:c,description:r||s,type:n};return f>=0?u[f]=g:u.push(g),$t(a,u),{content:[{type:"text",text:JSON.stringify({saved:!0,name:s,type:n,file:l,action:p?"updated":"created"},null,2)}]}}import $e from"fs";import Mn from"path";function An(t){if(t.length===0)return null;let e=t.map(i=>i.replace(/\\/g,"/")),s=e[0].split("/"),n="";for(let i=1;i<=s.length;i++){let r=s.slice(0,i).join("/");if(e.every(o=>o.startsWith(r+"/")))n=r;else break}return n||null}function Ut(t,e){let s=Object.keys(t);if(s.length===0||s.filter(l=>{try{return $e.statSync(l),!1}catch{return!0}}).length<Math.ceil(s.length*.5))return null;let i=An(s);if(!i)return null;let r=e.replace(/\\/g,"/"),o=i.replace(/\\/g,"/");if(r===o)return null;let a=s.slice(0,Math.min(8,s.length));return a.filter(l=>{let p=l.replace(/\\/g,"/").replace(o+"/","");try{return $e.statSync(r+"/"+p),!0}catch{return!1}}).length<Math.ceil(a.length*.6)?null:{oldRoot:o,newRoot:r}}function ge(t,e,s){let n=t.replace(/\\/g,"/");if(n.startsWith(e+"/"))return s+n.slice(e.length);let i=e.replace(/\//g,"\\");return t.startsWith(i+"\\")?s+"/"+t.slice(i.length+1).replace(/\\/g,"/"):t}function Ht(t,e,s,n,i,r){let o={};for(let[p,h]of Object.entries(s.mtime_snapshot||{}))o[ge(p,i,r)]=h;s.mtime_snapshot=o,s.active_files=(s.active_files||[]).map(p=>ge(p,i,r)),$e.writeFileSync(Mn.join(e,"session.json"),JSON.stringify(s,null,2),"utf8");let a=i.replace(/\//g,"\\");function c(p,h){try{t.prepare(`UPDATE ${p} SET ${h} = replace(${h}, ?, ?)`).run(i,r),t.prepare(`UPDATE ${p} SET ${h} = replace(${h}, ?, ?)`).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,h]of Object.entries(n)){let m={...h};m.tempPath&&(m.tempPath=ge(m.tempPath,i,r)),l[ge(p,i,r)]=m}for(let p of Object.keys(n))delete n[p];Object.assign(n,l)}import N from"fs";import ne from"path";var H="roadmap";function se(){return ne.join(A(),"roadmap.md")}function Wt(){return ne.join(A(),"roadmap_completed.md")}function jn(t){let e=[];for(let s of t){let n=(s.text||"").trim();n&&(!Number.isInteger(s.phase_number)||s.phase_number<1||e.push({phase_number:s.phase_number,text:n,body:s.body??null}))}return e}var Me="decision_production_roadmap.md";function Fn(t){let e=t.match(/^---\n[\s\S]*?\n---\n?/);return e?t.slice(e[0].length):t}function Dn(t,e){let s=ne.join(t,"MEMORY.md");if(!N.existsSync(s))return;let n=N.readFileSync(s,"utf8").split(/\r?\n/).filter(i=>!i.includes(`](${e})`));N.writeFileSync(s,n.join(`
171
+ `),"utf8")}function Pn(){let t=se();if(N.existsSync(t))return null;let e=[],s=we();s&&e.push(s),e.push(ne.join(A(),"memory"));let n=null,i=null;for(let o of e){let a=ne.join(o,Me);n==null&&N.existsSync(a)&&(n=Fn(N.readFileSync(a,"utf8")).trim(),i=a)}if(n==null)return null;let r=/^#\s/m.test(n)?"":`# Roadmap
172
172
 
173
173
  `;N.writeFileSync(t,r+n+`
174
- `,"utf8");for(let o of e){let a=te.join(o,Ie);try{N.existsSync(a)&&N.unlinkSync(a)}catch{}try{An(o,Ie)}catch{}}return{migrated:!0,from:i,to:t}}function Ut(t){Mn();let e=ne();if(!N.existsSync(e))return null;let s=N.readFileSync(e,"utf8");if(!/^##\s+Phases\s*$/im.test(s))return null;let n=Cn(Ct(s));return $t(t,ae,n)}function Ht(t){let e=ne();if(!N.existsSync(e))return{removed:!1,title:null};let s=N.readFileSync(e,"utf8").split(/\r?\n/),n=[],i=!1,r=!1,o=!1,a=null;for(let c of s){if(/^##\s+Phases\s*$/i.test(c)){i=!0,r=!1,n.push(c);continue}if(/^##\s+/.test(c)&&!/^###/.test(c)){i=!1,r=!1,n.push(c);continue}if(i){let p=/^###\s+Phase\s+(\d+)\s*:\s*(.+?)\s*$/.exec(c);if(p){if(parseInt(p[1],10)===t){r=!0,o=!0,a=p[2];continue}r=!1,n.push(c);continue}if(r)continue;n.push(c);continue}n.push(c)}if(o){let c=n.join(`
174
+ `,"utf8");for(let o of e){let a=ne.join(o,Me);try{N.existsSync(a)&&N.unlinkSync(a)}catch{}try{Dn(o,Me)}catch{}}return{migrated:!0,from:i,to:t}}function qt(t){Pn();let e=se();if(!N.existsSync(e))return null;let s=N.readFileSync(e,"utf8");if(!/^##\s+Phases\s*$/im.test(s))return null;let n=jn(At(s));return jt(t,H,n)}function Bt(t){let e=se();if(!N.existsSync(e))return{removed:!1,title:null};let s=N.readFileSync(e,"utf8").split(/\r?\n/),n=[],i=!1,r=!1,o=!1,a=null;for(let c of s){if(/^##\s+Phases\s*$/i.test(c)){i=!0,r=!1,n.push(c);continue}if(/^##\s+/.test(c)&&!/^###/.test(c)){i=!1,r=!1,n.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,n.push(c);continue}if(r)continue;n.push(c);continue}n.push(c)}if(o){let c=n.join(`
175
175
  `).replace(/\n{3,}/g,`
176
176
 
177
- `);N.writeFileSync(e,c,"utf8")}return{removed:o,title:a}}function Wt(t,e,s={}){let n=Pt(),i=N.existsSync(n)?N.readFileSync(n,"utf8"):`# Completed phases
177
+ `);N.writeFileSync(e,c,"utf8")}return{removed:o,title:a}}function Gt(t,e,s={}){let n=Wt(),i=N.existsSync(n)?N.readFileSync(n,"utf8"):`# Completed phases
178
178
 
179
179
  Shipped roadmap phases, newest last.
180
180
  `,r=s.date||new Date().toISOString().slice(0,10),o=s.commit?` (${s.commit})`:"",a=`- ${r} \u2014 Phase ${t}: ${e}${o}`;return i.endsWith(`
181
181
  `)||(i+=`
182
182
  `),i+=a+`
183
- `,N.writeFileSync(n,i,"utf8"),{logged:a}}function qt(){let t=Pt();return N.existsSync(t)?N.readFileSync(t,"utf8").split(/\r?\n/).filter(e=>/^-\s+\d{4}-\d{2}-\d{2}\s+—\s+Phase\s/.test(e)).length:0}var Ce={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},Bt=new Set(["open","in_progress","done","blocked","stale"]),jn=new Set(["open","in_progress"]);function ye(t){return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}function Fn(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 Gt(t){t.prepare("DELETE FROM main.roadmap_cursor WHERE singleton = 1").run()}function Dn(t,e){let s=t.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),n=t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),i=s.get(e);for(;i&&i.parent_phase_id!=null;)n.run(i.parent_phase_id),i=s.get(i.parent_phase_id)}function $e(t,e){Fn(t,e),t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(e),Dn(t,e)}function Yt(t){let e=t.filter(s=>s.status!=="stale");return e.length===0?"stale":e.every(s=>s.status==="done")?"done":e.some(s=>s.status==="in_progress")?"in_progress":e.some(s=>s.status==="blocked")&&!e.some(s=>s.status==="in_progress")?"blocked":"open"}function Me(t){let e=new Map,s;try{s=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let n of s)e.has(n.blocked_id)||e.set(n.blocked_id,new Set),e.get(n.blocked_id).add(n.blocker_id);return e}function je(t,e,s){let n=e.get(t);if(!n||n.size===0)return[];let i=[];for(let r of n)s.get(r)!=="done"&&i.push(r);return i}function Xt(t,e){let s=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(s.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let n=new Map(s.map(m=>[m.id,m.status])),i=Me(t),r=new Map;for(let m of s){let u=m.parent_phase_id??`root:${m.decision_name}`;r.has(u)||r.set(u,[]),r.get(u).push(m)}for(let m of r.values())m.sort((u,d)=>u.phase_number!=null&&d.phase_number!=null?u.phase_number-d.phase_number:u.position-d.position||u.id-d.id);let o=[],a=new Set;for(let m of s){if(m.parent_phase_id!=null||a.has(m.decision_name))continue;a.add(m.decision_name);let u=[],d=r.get(`root:${m.decision_name}`)||[];for(let g=d.length-1;g>=0;g--)u.push(d[g]);for(;u.length;){let g=u.pop();o.push(g);let f=r.get(g.id)||[];for(let h=f.length-1;h>=0;h--)u.push(f[h])}}let c=m=>jn.has(m.status),p=[],l=0;if(e!=null){let m=o.findIndex(u=>u.id===e);l=m===-1?0:m+1}for(let m=l;m<o.length;m++){let u=o[m];if(!c(u))continue;let d=je(u.id,i,n);if(d.length>0){p.push({id:u.id,blockers:d});continue}let g=e!=null?s.find(y=>y.id===e):null,f=g&&g.decision_name!==u.decision_name,h;return e==null?h=`First actionable phase: ${u.decision_name??"(sub-task)"} / Phase ${u.phase_number??"\xB7"}.`:f?h=`Previous decision "${g.decision_name}" is complete \u2014 advanced to "${u.decision_name}".`:h=`Next open phase in "${u.decision_name??"(sub-task)"}" after id ${e}.`,p.length>0&&(h+=` Skipped ${p.length} dep-blocked phase(s): ${p.map(y=>`id ${y.id} (blocked by ${y.blockers.join(",")})`).join("; ")}.`),{id:u.id,reason:h,skipped_blocked:p}}return p.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${p.map(m=>`id ${m.id} (blocked by ${m.blockers.join(",")})`).join("; ")}.`,skipped_blocked:p}:{id:null,reason:"No actionable phases remaining."}}function Pn(t){let e=t.filter(n=>n.status!=="stale");return{done:e.filter(n=>n.status==="done").length,total:e.length}}function Un(t,{compact:e=!1,showStale:s=!1,decisionFilter:n=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=ye(t),o=Me(t),a=new Map(i.map(f=>[f.id,f.status])),c=new Map;for(let f of i){let h=f.parent_phase_id??"root";c.has(h)||c.set(h,[]),c.get(h).push(f)}for(let f of c.values())f.sort((h,y)=>h.decision_name&&y.decision_name&&h.decision_name!==y.decision_name?h.decision_name.localeCompare(y.decision_name):h.phase_number!=null&&y.phase_number!=null?h.phase_number-y.phase_number:h.position-y.position||h.id-y.id);let p=c.get("root")||[],l=new Map;for(let f of p)f.decision_name!=null&&(n!=null&&f.decision_name!==n||(l.has(f.decision_name)||l.set(f.decision_name,[]),l.get(f.decision_name).push(f)));if(n!=null&&l.size===0)return`(no decision named "${n}" \u2014 try cairn_roadmap action=tree without filter to list all)`;let m=0,u=[],d=f=>{let h=je(f.id,o,a);return h.length>0?` blocked-by:${h.join(",")}`:""},g=(f,h)=>{let y=c.get(f)||[];for(let _ of y){if(!s&&_.status==="stale"){m++;continue}let T=Ce[_.status]||"[?]",b=r===_.id?" \u2192":"",C=_.phase_number!=null?`Phase ${_.phase_number}: ${_.text}`:_.text,E=e?"":` (id:${_.id})`;u.push(`${" ".repeat(h)}${T}${b} ${C}${E}${d(_)}`),g(_.id,h+1)}};for(let[f,h]of l){if(!s&&h.every(E=>E.status==="stale")){m+=h.length;continue}let y=Yt(h),{done:_,total:T}=Pn(h),b=T>0?` (${_}/${T} done)`:"",C=f===ae?"Roadmap":`Decision: ${f}`;u.push(`${Ce[y]} ${C}${b}`);for(let E of h){if(!s&&E.status==="stale"){m++;continue}if(f===ae&&E.status==="done")continue;let R=Ce[E.status]||"[?]",x=r===E.id?" \u2192":"",O=`Phase ${E.phase_number}: ${E.text}`,j=e?"":` (id:${E.id})`;u.push(` ${R}${x} ${O}${j}${d(E)}`),g(E.id,2)}}return m>0&&u.push(`(${m} stale phase${m===1?"":"s"} hidden \u2014 pass show_stale: true to view)`),u.join(`
184
- `)}function Jt(t){let e=t.prepare("SELECT decision_name, status FROM main.roadmap_phases WHERE parent_phase_id IS NULL").all(),s=new Map;for(let o of e)s.has(o.decision_name)||s.set(o.decision_name,[]),s.get(o.decision_name).push(o);let n=0;for(let o of s.values()){let a=Yt(o);(a==="in_progress"||a==="open")&&n++}let i=ye(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,p=o;for(;p.parent_phase_id!=null&&(p=t.prepare("SELECT id, decision_name, phase_number, parent_phase_id FROM main.roadmap_phases WHERE id = ?").get(p.parent_phase_id),!!p);)a=p.decision_name??a,c=p.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:s.size,decisions_in_flight:n,cursor_phase_id:i,current:r}}function Hn(t,e,s){if(e===s)throw new Error("A phase cannot block itself");if(t.prepare("SELECT id FROM main.roadmap_phases WHERE id IN (?, ?)").all(e,s).length!==2)throw new Error(`One or both phases not found (from=${e}, to=${s})`);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===s)throw new Error(`Cycle: phase ${s} already (transitively) blocks phase ${e}`);if(!a.has(c)){a.add(c);for(let p of r.get(c)||[])o.push(p)}}return t.prepare("INSERT OR IGNORE INTO main.roadmap_phase_deps (blocker_id, blocked_id) VALUES (?, ?)").run(e,s),{added:{blocker_id:e,blocked_id:s}}}function Wn(t,e,s){if(t.prepare("DELETE FROM main.roadmap_phase_deps WHERE blocker_id = ? AND blocked_id = ?").run(e,s).changes===0)throw new Error(`No dep edge ${e} \u2192 ${s}`);return{removed:{blocker_id:e,blocked_id:s}}}function qn(t,e){if(e!=null){let n=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:n,blocks:i}}return{edges:t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps ORDER BY blocker_id, blocked_id").all()}}function Ae(t){try{return Ut(t)}catch(e){return process.stderr.write(`[cairn] roadmap.md sync skipped: ${e.message}
185
- `),null}}function ce(t,e={}){let s=e.action??"tree",{id:n,status:i,parent:r,text:o}=e;switch(s){case"tree":{Ae(t);let a=e.show_stale===!0||e.hide_stale===!1,c=Un(t,{compact:e.compact===!0,showStale:a,decisionFilter:e.decision??null}),p=Jt(t),l=[c,"",p.current?`Current: ${p.current}`:"(no cursor set \u2014 call cairn_roadmap focus <id>)"];p.decisions_in_flight>1&&l.push(`In flight: ${p.decisions_in_flight} decisions`);let m=qt();return m>0&&l.push(`Shipped: ${m} (see .cairn/roadmap_completed.md)`),{content:[{type:"text",text:l.join(`
186
- `)}]}}case"status":return Ae(t),{content:[{type:"text",text:JSON.stringify(Jt(t),null,2)}]};case"sync":{let a=Ae(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"focus":{if(!n)throw new Error("id is required for focus");let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);if(!a)throw new Error(`No phase with id ${n}`);if(a.status==="stale")throw new Error(`Phase ${n} is stale \u2014 restore by re-adding it to .cairn/roadmap.md`);let c=Me(t),p=new Map(t.prepare("SELECT id, status FROM main.roadmap_phases").all().map(d=>[d.id,d.status])),l=je(n,c,p);$e(t,n);let m=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n),u=l.length>0?`Focused phase ${n} despite unfinished blockers: ${l.join(", ")}.`:`Focused phase ${n}.`;return{content:[{type:"text",text:JSON.stringify({focused:m,reason:u,blocked_by:l},null,2)}]}}case"next":{let a=ye(t),c=Xt(t,a);if(c.id==null)return Gt(t),{content:[{type:"text",text:JSON.stringify({cursor:null,reason:c.reason,skipped_blocked:c.skipped_blocked??[]},null,2)}]};$e(t,c.id);let p=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(c.id);return{content:[{type:"text",text:JSON.stringify({cursor:c.id,phase:p,reason:c.reason,skipped_blocked:c.skipped_blocked??[]},null,2)}]}}case"set_status":{if(!n)throw new Error("id is required for set_status");if(!i)throw new Error("status is required for set_status");if(!Bt.has(i))throw new Error(`Invalid status "${i}". Use: ${[...Bt].join(", ")}`);let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);if(!a)throw new Error(`No phase with id ${n}`);let c=i==="done"?"datetime('now')":"NULL";t.prepare(`UPDATE main.roadmap_phases SET status = ?, resolved_at = ${c} WHERE id = ?`).run(i,n);let p=null,l=null;if(i==="done"&&ye(t)===n){let g=Xt(t,n);g.id!=null?($e(t,g.id),p=g.id,l=g.reason):(Gt(t),l=g.reason)}let m=null,u=a.decision_name===ae&&a.phase_number!=null;if(i==="done"&&u)try{let g=Wt(a.phase_number,a.text),f=Ht(a.phase_number).removed;m={logged:g.logged,pruned_from_roadmap_md:f}}catch(g){process.stderr.write(`[cairn] ship bookkeeping failed for phase ${n}: ${g.message}
187
- `)}let d=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);return{content:[{type:"text",text:JSON.stringify({updated:d,cursor_advanced_to:p,reason:l,completed:m},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,p=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),l=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(p.lastInsertRowid);return{content:[{type:"text",text:JSON.stringify({added:l,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(Hn(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(Wn(t,a,c),null,2)}]}}case"deps_list":return{content:[{type:"text",text:JSON.stringify(qn(t,e.id??null),null,2)}]};default:throw new Error(`Unknown action: "${s}". Use tree, status, sync, focus, next, set_status, add, deps_add, deps_remove, or deps_list.`)}}var Fe=Gn(Bn);async function Vt(t){let e=M(),s=Nt(e);Lt(e,s)>0&&ke(e,s);let i=Ee.join(e,"session.json");if(!B.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(B.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(S=>typeof S=="string"&&S.includes("git-snapshot:"));if(o.length>0){let S=Ee.join(e,"session.bak.json");B.existsSync(S)||B.copyFileSync(i,S),r.notes=r.notes.filter(I=>!(typeof I=="string"&&I.includes("git-snapshot:"))),B.writeFileSync(i,JSON.stringify(r,null,2),"utf8")}let a=null,c=r.mtime_snapshot||{};Object.keys(c).length>0&&(a=Ft(c,A()),a&&(Dt(t,e,r,s,a.oldRoot,a.newRoot),ke(e,s)));let p=r.mtime_snapshot||{},l=[];for(let[S,I]of Object.entries(p))try{B.statSync(S).mtimeMs!==I&&l.push({file:S,change:"modified"})}catch{l.push({file:S,change:"deleted"})}try{let S=await P(A());for(let I of S)I in p||l.push({file:I,change:"new"})}catch{}let m=l.filter(S=>S.change==="deleted").map(S=>S.file);if(m.length>0){let S=t.prepare("DELETE FROM main.files WHERE path = ?");for(let I of m)S.run(I)}let u=l.filter(S=>S.change!=="deleted").map(S=>S.file),d={files_reindexed:0,files_unchanged:0};u.length>0&&(await fe(t,u),d.files_reindexed=u.length),d.files_unchanged=Object.keys(p).length-l.filter(S=>S.change!=="new").length;let g=V(),f=Mt(g),h=U(g),y=h.length>0?h.map(S=>`[${S.type}] ${S.name} \u2014 ${S.description}`).join(`
188
- `):null,_=ue(t),T=0;try{T=t.prepare("SELECT COUNT(*) AS n FROM main.todos WHERE status IN ('open', 'in_progress')").get().n}catch{}let b=null;try{let S=B.existsSync(ne()),I=JSON.parse(ce(t,{action:"status"}).content[0].text);if(S||I.decisions_total>0){let H=ce(t,{action:"tree",compact:!0}).content[0].text;b={file:Ee.relative(A(),ne()).replace(/\\/g,"/"),current:I.current,decisions_in_flight:I.decisions_in_flight,tree:H}}}catch{}let C=b?b.current?`Roadmap (${b.file}) \u2014 current: ${b.current}`:`Roadmap (${b.file}) \u2014 no cursor set (call cairn_roadmap next)`:"",E=[`Last session: ${r.message}`,a?`Project relocated from ${a.oldRoot} \u2192 repaired automatically.`:"",C,l.length>0?`${l.length} file(s) changed since checkpoint`:"No files changed since checkpoint",T>0?`${T} open todo(s)`:"",_.length>0?`${_.length} sub-index(es)`:""].filter(Boolean).join(" \xB7 "),R=null;try{B.readFileSync(Ee.join(A(),".gitignore"),"utf8").split(`
189
- `).map(H=>H.trim()).some(H=>H===".cairn"||H===".cairn/"||H==="/.cairn"||H==="/.cairn/")||(R="Add .cairn/ to your .gitignore to avoid committing the index and session cache.")}catch{}let x={last_checkpoint:r.checkpoint_at,message:r.message,index_status:u.length>0||m.length>0?"incremental":"up_to_date",files_reindexed:d.files_reindexed,files_deleted:m.length,files_unchanged:Math.max(0,d.files_unchanged),sub_indexes_active:_.length,open_todos:T,changed_since_checkpoint:l.slice(0,20),active_files:r.active_files||[],notes:r.notes||[],resume_summary:E};R&&(x.gitignore_hint=R),b&&(x.roadmap=b),o.length>0&&(x.migrated=`Removed ${o.length} legacy git-snapshot note(s). Original session backed up to session.bak.json.`);let O=A(),j=new Date(r.checkpoint_at).toISOString(),[L,w,le]=await Promise.allSettled([Fe(`git log --oneline --after="${j}"`,{cwd:O,timeout:5e3}),Fe("git diff --name-only HEAD",{cwd:O,timeout:5e3}),Fe("git status --short",{cwd:O,timeout:5e3})]);if(L.status==="fulfilled"){let S=L.value.stdout.trim();S&&(x.commits_since_checkpoint=S.split(`
190
- `))}if(w.status==="fulfilled"){let S=w.value.stdout.trim();S&&(x.uncommitted_changes=S.split(`
191
- `))}if(le.status==="fulfilled"){let S=le.value.stdout.trim();S&&(x.git_status=S)}return a&&(x.relocated=a),f.length>0&&(x.preferences=f.map(S=>({name:S.name,description:S.description,file:S.file}))),y&&(x.memory_index=y),{content:[{type:"text",text:JSON.stringify(x,null,2)}]}}import Xn from"fs";import Jn from"path";var Yn=new Set(["java","typescript","javascript","vue","python","sql"]);function zt(t,{file_path:e}){if(!e)throw new Error("file_path is required");let s=Jn.extname(e).toLowerCase(),n=X[s];if(!n||!Yn.has(n))return{content:[{type:"text",text:JSON.stringify({skipped:!0,reason:`language '${n??s}' is not minified`})}]};let i=Xn.readFileSync(e,"utf8"),r=i.split(`
192
- `).length,o=n==="vue"?he(i):z(i,n),a=o.split(`
183
+ `,N.writeFileSync(n,i,"utf8"),{logged:a}}function ye(){let t=Wt();return N.existsSync(t)?N.readFileSync(t,"utf8").split(/\r?\n/).filter(e=>/^-\s+\d{4}-\d{2}-\d{2}\s+—\s+Phase\s/.test(e)).length:0}import _e from"fs";import Un from"path";var Xt="<!-- cairn:roadmap -->",Hn={open:"open",in_progress:"in progress",done:"done",blocked:"blocked",stale:"stale"};function Wn(t){return t==="done"?"x":t==="in_progress"?"~":t==="blocked"?"!":" "}function qn(){return Un.join($(),"ROADMAP.md")}function Bn(t){try{return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}catch{return null}}function Gn(t){let e=new Map,s;try{s=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let n of s)e.has(n.blocked_id)||e.set(n.blocked_id,new Set),e.get(n.blocked_id).add(n.blocker_id);return e}function Xn(t,e){let s=t.decision_name??"",n=e.decision_name??"";return s!==n?s<n?-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 Jn(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(u=>u.status!=="stale").length===0)return null;let s=Bn(t),n=Gn(t),i=new Map(e.map(u=>[u.id,u.status])),r=new Map(e.map(u=>[u.id,u.phase_number!=null?`Phase ${u.phase_number}`:u.text])),o=u=>{let f=n.get(u);return f?[...f].filter(g=>i.get(g)!=="done").map(g=>r.get(g)||`#${g}`):[]},a=new Map;for(let u of e){let f=u.parent_phase_id??"root";a.has(f)||a.set(f,[]),a.get(f).push(u)}for(let u of a.values())u.sort(Xn);let c=new Map;for(let u of a.get("root")||[])u.decision_name!=null&&(c.has(u.decision_name)||c.set(u.decision_name,[]),c.get(u.decision_name).push(u));let l=[...c.keys()].sort((u,f)=>u===H?-1:f===H?1:u<f?-1:u>f?1:0),p=[Xt,"<!--"," 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.","-->","","# Roadmap","","Legend: `[x]` done \xB7 `[~]` in progress \xB7 `[ ]` open \xB7 `[!]` blocked."],h=(u,f)=>{let g=" ".repeat(f),y=u.phase_number!=null?`Phase ${u.phase_number}: ${u.text}`:u.text,_=Hn[u.status]||u.status,T=o(u.id);if(T.length&&(_+=` \xB7 waiting on ${T.join(", ")}`),s===u.id&&(_+=" \xB7 \u2190 current"),p.push(`${g}- [${Wn(u.status)}] **${y}** \u2014 ${_}`),u.body){let x=u.body.split(`
184
+ `).map(I=>I.trim()).find(Boolean);x&&p.push(`${g} ${x}`)}for(let x of a.get(u.id)||[])x.status!=="stale"&&h(x,f+1)},m=!1;for(let u of l){let f=c.get(u).filter(y=>y.status!=="stale"),g=u===H?f.filter(y=>y.status!=="done"):f;if(g.length!==0){if(m=!0,p.push(""),u===H)p.push("## Active plan");else{let y=f.filter(_=>_.status==="done").length;p.push(`## ${u} \u2014 ${y}/${f.length} done`)}p.push("");for(let y of g)h(y,0)}}if(!m)return null;let d=ye();return d>0&&p.push("","---","",`**Shipped:** ${d} phase${d===1?"":"s"} \u2014 see \`.cairn/roadmap_completed.md\`.`),p.join(`
185
+ `)+`
186
+ `}function Ae(t){let e=qn(),s=Jn(t),n=null;if(_e.existsSync(e))try{n=_e.readFileSync(e,"utf8")}catch{n=null}if(n!=null&&!n.startsWith(Xt))return{written:!1,reason:"ROADMAP.md exists and is not cairn-generated \u2014 refusing to overwrite",path:e};if(s==null){if(n!=null)try{return _e.unlinkSync(e),{written:!1,removed:!0,path:e}}catch(i){return{written:!1,reason:`remove failed: ${i.message}`,path:e}}return{written:!1,reason:"empty roadmap \u2014 no file written",path:e}}return n===s?{written:!1,unchanged:!0,path:e,bytes:Buffer.byteLength(s)}:(_e.writeFileSync(e,s,"utf8"),{written:!0,path:e,bytes:Buffer.byteLength(s)})}function Jt(t){try{return Ae(t)}catch(e){return process.stderr.write(`[cairn] ROADMAP.md publish skipped: ${e.message}
187
+ `),null}}var je={open:"[ ]",in_progress:"[~]",done:"[x]",blocked:"[!]",stale:"[s]"},Yt=new Set(["open","in_progress","done","blocked","stale"]),Yn=new Set(["open","in_progress"]);function Se(t){return t.prepare("SELECT phase_id FROM main.roadmap_cursor WHERE singleton = 1").get()?.phase_id??null}function Vn(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 Vt(t){t.prepare("DELETE FROM main.roadmap_cursor WHERE singleton = 1").run()}function zn(t,e){let s=t.prepare("SELECT parent_phase_id, status FROM main.roadmap_phases WHERE id = ?"),n=t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'"),i=s.get(e);for(;i&&i.parent_phase_id!=null;)n.run(i.parent_phase_id),i=s.get(i.parent_phase_id)}function Fe(t,e){Vn(t,e),t.prepare("UPDATE main.roadmap_phases SET status = 'in_progress' WHERE id = ? AND status = 'open'").run(e),zn(t,e)}function Qt(t){let e=t.filter(s=>s.status!=="stale");return e.length===0?"stale":e.every(s=>s.status==="done")?"done":e.some(s=>s.status==="in_progress")?"in_progress":e.some(s=>s.status==="blocked")&&!e.some(s=>s.status==="in_progress")?"blocked":"open"}function De(t){let e=new Map,s;try{s=t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps").all()}catch{return e}for(let n of s)e.has(n.blocked_id)||e.set(n.blocked_id,new Set),e.get(n.blocked_id).add(n.blocker_id);return e}function Pe(t,e,s){let n=e.get(t);if(!n||n.size===0)return[];let i=[];for(let r of n)s.get(r)!=="done"&&i.push(r);return i}function zt(t,e){let s=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(s.length===0)return{id:null,reason:"No phases \u2014 add a ## Phases section to .cairn/roadmap.md."};let n=new Map(s.map(h=>[h.id,h.status])),i=De(t),r=new Map;for(let h of s){let m=h.parent_phase_id??`root:${h.decision_name}`;r.has(m)||r.set(m,[]),r.get(m).push(h)}for(let h of r.values())h.sort((m,d)=>m.phase_number!=null&&d.phase_number!=null?m.phase_number-d.phase_number:m.position-d.position||m.id-d.id);let o=[],a=new Set;for(let h of s){if(h.parent_phase_id!=null||a.has(h.decision_name))continue;a.add(h.decision_name);let m=[],d=r.get(`root:${h.decision_name}`)||[];for(let u=d.length-1;u>=0;u--)m.push(d[u]);for(;m.length;){let u=m.pop();o.push(u);let f=r.get(u.id)||[];for(let g=f.length-1;g>=0;g--)m.push(f[g])}}let c=h=>Yn.has(h.status),l=[],p=0;if(e!=null){let h=o.findIndex(m=>m.id===e);p=h===-1?0:h+1}for(let h=p;h<o.length;h++){let m=o[h];if(!c(m))continue;let d=Pe(m.id,i,n);if(d.length>0){l.push({id:m.id,blockers:d});continue}let u=e!=null?s.find(y=>y.id===e):null,f=u&&u.decision_name!==m.decision_name,g;return e==null?g=`First actionable phase: ${m.decision_name??"(sub-task)"} / Phase ${m.phase_number??"\xB7"}.`:f?g=`Previous decision "${u.decision_name}" is complete \u2014 advanced to "${m.decision_name}".`:g=`Next open phase in "${m.decision_name??"(sub-task)"}" after id ${e}.`,l.length>0&&(g+=` Skipped ${l.length} dep-blocked phase(s): ${l.map(y=>`id ${y.id} (blocked by ${y.blockers.join(",")})`).join("; ")}.`),{id:m.id,reason:g,skipped_blocked:l}}return l.length>0?{id:null,reason:`All remaining open phases are blocked by unfinished dependencies: ${l.map(h=>`id ${h.id} (blocked by ${h.blockers.join(",")})`).join("; ")}.`,skipped_blocked:l}:{id:null,reason:"No actionable phases remaining."}}function Kn(t){let e=t.filter(n=>n.status!=="stale");return{done:e.filter(n=>n.status==="done").length,total:e.length}}function Qn(t,{compact:e=!1,showStale:s=!1,decisionFilter:n=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=Se(t),o=De(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,y)=>g.decision_name&&y.decision_name&&g.decision_name!==y.decision_name?g.decision_name.localeCompare(y.decision_name):g.phase_number!=null&&y.phase_number!=null?g.phase_number-y.phase_number:g.position-y.position||g.id-y.id);let l=c.get("root")||[],p=new Map;for(let f of l)f.decision_name!=null&&(n!=null&&f.decision_name!==n||(p.has(f.decision_name)||p.set(f.decision_name,[]),p.get(f.decision_name).push(f)));if(n!=null&&p.size===0)return`(no decision named "${n}" \u2014 try cairn_roadmap action=tree without filter to list all)`;let h=0,m=[],d=f=>{let g=Pe(f.id,o,a);return g.length>0?` blocked-by:${g.join(",")}`:""},u=(f,g)=>{let y=c.get(f)||[];for(let _ of y){if(!s&&_.status==="stale"){h++;continue}let T=je[_.status]||"[?]",x=r===_.id?" \u2192":"",I=_.phase_number!=null?`Phase ${_.phase_number}: ${_.text}`:_.text,E=e?"":` (id:${_.id})`;m.push(`${" ".repeat(g)}${T}${x} ${I}${E}${d(_)}`),u(_.id,g+1)}};for(let[f,g]of p){if(!s&&g.every(E=>E.status==="stale")){h+=g.length;continue}let y=Qt(g),{done:_,total:T}=Kn(g),x=T>0?` (${_}/${T} done)`:"",I=f===H?"Roadmap":`Decision: ${f}`;m.push(`${je[y]} ${I}${x}`);for(let E of g){if(!s&&E.status==="stale"){h++;continue}if(f===H&&E.status==="done")continue;let R=je[E.status]||"[?]",b=r===E.id?" \u2192":"",O=`Phase ${E.phase_number}: ${E.text}`,j=e?"":` (id:${E.id})`;m.push(` ${R}${b} ${O}${j}${d(E)}`),u(E.id,2)}}return h>0&&m.push(`(${h} stale phase${h===1?"":"s"} hidden \u2014 pass show_stale: true to view)`),m.join(`
188
+ `)}function Kt(t){let e=t.prepare("SELECT decision_name, status FROM main.roadmap_phases WHERE parent_phase_id IS NULL").all(),s=new Map;for(let o of e)s.has(o.decision_name)||s.set(o.decision_name,[]),s.get(o.decision_name).push(o);let n=0;for(let o of s.values()){let a=Qt(o);(a==="in_progress"||a==="open")&&n++}let i=Se(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:s.size,decisions_in_flight:n,cursor_phase_id:i,current:r}}function Zn(t,e,s){if(e===s)throw new Error("A phase cannot block itself");if(t.prepare("SELECT id FROM main.roadmap_phases WHERE id IN (?, ?)").all(e,s).length!==2)throw new Error(`One or both phases not found (from=${e}, to=${s})`);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===s)throw new Error(`Cycle: phase ${s} 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,s),{added:{blocker_id:e,blocked_id:s}}}function es(t,e,s){if(t.prepare("DELETE FROM main.roadmap_phase_deps WHERE blocker_id = ? AND blocked_id = ?").run(e,s).changes===0)throw new Error(`No dep edge ${e} \u2192 ${s}`);return{removed:{blocker_id:e,blocked_id:s}}}function ts(t,e){if(e!=null){let n=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:n,blocks:i}}return{edges:t.prepare("SELECT blocker_id, blocked_id FROM main.roadmap_phase_deps ORDER BY blocker_id, blocked_id").all()}}function Ee(t){try{return qt(t)}catch(e){return process.stderr.write(`[cairn] roadmap.md sync skipped: ${e.message}
189
+ `),null}}function ce(t,e={}){let s=e.action??"tree",{id:n,status:i,parent:r,text:o}=e;switch(s){case"tree":{Ee(t);let a=e.show_stale===!0||e.hide_stale===!1,c=Qn(t,{compact:e.compact===!0,showStale:a,decisionFilter:e.decision??null}),l=Kt(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 h=ye();return h>0&&p.push(`Shipped: ${h} (see .cairn/roadmap_completed.md)`),{content:[{type:"text",text:p.join(`
190
+ `)}]}}case"status":return Ee(t),{content:[{type:"text",text:JSON.stringify(Kt(t),null,2)}]};case"sync":{let a=Ee(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 Ee(t),{content:[{type:"text",text:JSON.stringify(Ae(t),null,2)}]};case"focus":{if(!n)throw new Error("id is required for focus");let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);if(!a)throw new Error(`No phase with id ${n}`);if(a.status==="stale")throw new Error(`Phase ${n} is stale \u2014 restore by re-adding it to .cairn/roadmap.md`);let c=De(t),l=new Map(t.prepare("SELECT id, status FROM main.roadmap_phases").all().map(d=>[d.id,d.status])),p=Pe(n,c,l);Fe(t,n);let h=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n),m=p.length>0?`Focused phase ${n} despite unfinished blockers: ${p.join(", ")}.`:`Focused phase ${n}.`;return{content:[{type:"text",text:JSON.stringify({focused:h,reason:m,blocked_by:p},null,2)}]}}case"next":{let a=Se(t),c=zt(t,a);if(c.id==null)return Vt(t),{content:[{type:"text",text:JSON.stringify({cursor:null,reason:c.reason,skipped_blocked:c.skipped_blocked??[]},null,2)}]};Fe(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(!n)throw new Error("id is required for set_status");if(!i)throw new Error("status is required for set_status");if(!Yt.has(i))throw new Error(`Invalid status "${i}". Use: ${[...Yt].join(", ")}`);let a=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);if(!a)throw new Error(`No phase with id ${n}`);let c=i==="done"?"datetime('now')":"NULL";t.prepare(`UPDATE main.roadmap_phases SET status = ?, resolved_at = ${c} WHERE id = ?`).run(i,n);let l=null,p=null;if(i==="done"&&Se(t)===n){let u=zt(t,n);u.id!=null?(Fe(t,u.id),l=u.id,p=u.reason):(Vt(t),p=u.reason)}let h=null,m=a.decision_name===H&&a.phase_number!=null;if(i==="done"&&m)try{let u=Gt(a.phase_number,a.text),f=Bt(a.phase_number).removed;h={logged:u.logged,pruned_from_roadmap_md:f}}catch(u){process.stderr.write(`[cairn] ship bookkeeping failed for phase ${n}: ${u.message}
191
+ `)}let d=t.prepare("SELECT * FROM main.roadmap_phases WHERE id = ?").get(n);return{content:[{type:"text",text:JSON.stringify({updated:d,cursor_advanced_to:l,reason:p,completed:h},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(Zn(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(es(t,a,c),null,2)}]}}case"deps_list":return{content:[{type:"text",text:JSON.stringify(ts(t,e.id??null),null,2)}]};default:throw new Error(`Unknown action: "${s}". Use tree, status, sync, publish, focus, next, set_status, add, deps_add, deps_remove, or deps_list.`)}}var Ue=ss(ns);async function Zt(t){let e=A(),s=vt(e);It(e,s)>0&&Ce(e,s);let i=xe.join(e,"session.json");if(!G.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(G.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(S=>typeof S=="string"&&S.includes("git-snapshot:"));if(o.length>0){let S=xe.join(e,"session.bak.json");G.existsSync(S)||G.copyFileSync(i,S),r.notes=r.notes.filter(C=>!(typeof C=="string"&&C.includes("git-snapshot:"))),G.writeFileSync(i,JSON.stringify(r,null,2),"utf8")}let a=null,c=r.mtime_snapshot||{};Object.keys(c).length>0&&(a=Ut(c,$()),a&&(Ht(t,e,r,s,a.oldRoot,a.newRoot),Ce(e,s)));let l=r.mtime_snapshot||{},p=[];for(let[S,C]of Object.entries(l))try{G.statSync(S).mtimeMs!==C&&p.push({file:S,change:"modified"})}catch{p.push({file:S,change:"deleted"})}try{let S=await P($());for(let C of S)C in l||p.push({file:C,change:"new"})}catch{}let h=p.filter(S=>S.change==="deleted").map(S=>S.file);if(h.length>0){let S=t.prepare("DELETE FROM main.files WHERE path = ?");for(let C of h)S.run(C)}let m=p.filter(S=>S.change!=="deleted").map(S=>S.file),d={files_reindexed:0,files_unchanged:0};m.length>0&&(await fe(t,m),d.files_reindexed=m.length),d.files_unchanged=Object.keys(l).length-p.filter(S=>S.change!=="new").length;let u=z(),f=Dt(u),g=U(u),y=g.length>0?g.map(S=>`[${S.type}] ${S.name} \u2014 ${S.description}`).join(`
192
+ `):null,_=ue(t),T=0;try{T=t.prepare("SELECT COUNT(*) AS n FROM main.todos WHERE status IN ('open', 'in_progress')").get().n}catch{}let x=null;try{let S=G.existsSync(se()),C=JSON.parse(ce(t,{action:"status"}).content[0].text);if(S||C.decisions_total>0){let W=ce(t,{action:"tree",compact:!0}).content[0].text;x={file:xe.relative($(),se()).replace(/\\/g,"/"),current:C.current,decisions_in_flight:C.decisions_in_flight,tree:W}}}catch{}let I=x?x.current?`Roadmap (${x.file}) \u2014 current: ${x.current}`:`Roadmap (${x.file}) \u2014 no cursor set (call cairn_roadmap next)`:"",E=[`Last session: ${r.message}`,a?`Project relocated from ${a.oldRoot} \u2192 repaired automatically.`:"",I,p.length>0?`${p.length} file(s) changed since checkpoint`:"No files changed since checkpoint",T>0?`${T} open todo(s)`:"",_.length>0?`${_.length} sub-index(es)`:""].filter(Boolean).join(" \xB7 "),R=null;try{G.readFileSync(xe.join($(),".gitignore"),"utf8").split(`
193
+ `).map(W=>W.trim()).some(W=>W===".cairn"||W===".cairn/"||W==="/.cairn"||W==="/.cairn/")||(R="Add .cairn/ to your .gitignore to avoid committing the index and session cache.")}catch{}let b={last_checkpoint:r.checkpoint_at,message:r.message,index_status:m.length>0||h.length>0?"incremental":"up_to_date",files_reindexed:d.files_reindexed,files_deleted:h.length,files_unchanged:Math.max(0,d.files_unchanged),sub_indexes_active:_.length,open_todos:T,changed_since_checkpoint:p.slice(0,20),active_files:r.active_files||[],notes:r.notes||[],resume_summary:E};R&&(b.gitignore_hint=R),x&&(b.roadmap=x),o.length>0&&(b.migrated=`Removed ${o.length} legacy git-snapshot note(s). Original session backed up to session.bak.json.`);let O=$(),j=new Date(r.checkpoint_at).toISOString(),[k,w,le]=await Promise.allSettled([Ue(`git log --oneline --after="${j}"`,{cwd:O,timeout:5e3}),Ue("git diff --name-only HEAD",{cwd:O,timeout:5e3}),Ue("git status --short",{cwd:O,timeout:5e3})]);if(k.status==="fulfilled"){let S=k.value.stdout.trim();S&&(b.commits_since_checkpoint=S.split(`
194
+ `))}if(w.status==="fulfilled"){let S=w.value.stdout.trim();S&&(b.uncommitted_changes=S.split(`
195
+ `))}if(le.status==="fulfilled"){let S=le.value.stdout.trim();S&&(b.git_status=S)}return a&&(b.relocated=a),f.length>0&&(b.preferences=f.map(S=>({name:S.name,description:S.description,file:S.file}))),y&&(b.memory_index=y),{content:[{type:"text",text:JSON.stringify(b,null,2)}]}}import is from"fs";import rs from"path";var os=new Set(["java","typescript","javascript","vue","python","sql"]);function en(t,{file_path:e}){if(!e)throw new Error("file_path is required");let s=rs.extname(e).toLowerCase(),n=J[s];if(!n||!os.has(n))return{content:[{type:"text",text:JSON.stringify({skipped:!0,reason:`language '${n??s}' is not minified`})}]};let i=is.readFileSync(e,"utf8"),r=i.split(`
196
+ `).length,o=n==="vue"?he(i):K(i,n),a=o.split(`
193
197
  `).length,c=r>0?Math.round((1-a/r)*100):0;return{content:[{type:"text",text:`[cairn: ${r} \u2192 ${a} lines (-${c}%)]
194
- ${o}`}]}}import _e from"path";var Vn=[{group:"read",verbs:["get","fetch","find","load","retrieve","read","query","lookup","select"]},{group:"write",verbs:["create","add","insert","save","persist","store","put","post"]},{group:"update",verbs:["update","modify","edit","change","patch","set","replace","upsert"]},{group:"delete",verbs:["delete","remove","destroy","clear","purge","drop","erase","reset"]}],zn=[["open","close"],["connect","disconnect"],["start","stop"],["init","destroy"],["init","cleanup"],["create","delete"],["enable","disable"],["lock","unlock"],["encode","decode"],["encrypt","decrypt"],["serialize","deserialize"],["subscribe","unsubscribe"],["register","unregister"],["login","logout"],["mount","unmount"],["acquire","release"],["push","pop"],["enqueue","dequeue"]],Kn=5e3;function Qn(t){let e=t.prepare(`SELECT f.repo,
198
+ ${o}`}]}}import Te from"path";var as=[{group:"read",verbs:["get","fetch","find","load","retrieve","read","query","lookup","select"]},{group:"write",verbs:["create","add","insert","save","persist","store","put","post"]},{group:"update",verbs:["update","modify","edit","change","patch","set","replace","upsert"]},{group:"delete",verbs:["delete","remove","destroy","clear","purge","drop","erase","reset"]}],cs=[["open","close"],["connect","disconnect"],["start","stop"],["init","destroy"],["init","cleanup"],["create","delete"],["enable","disable"],["lock","unlock"],["encode","decode"],["encrypt","decrypt"],["serialize","deserialize"],["subscribe","unsubscribe"],["register","unregister"],["login","logout"],["mount","unmount"],["acquire","release"],["push","pop"],["enqueue","dequeue"]],ps=5e3;function ls(t){let e=t.prepare(`SELECT f.repo,
195
199
  COUNT(DISTINCT f.id) AS file_count,
196
200
  COUNT(DISTINCT s.id) AS symbol_count,
197
201
  GROUP_CONCAT(DISTINCT f.language) AS languages
@@ -199,19 +203,19 @@ ${o}`}]}}import _e from"path";var Vn=[{group:"read",verbs:["get","fetch","find",
199
203
  LEFT JOIN symbols s ON s.file_id = f.id
200
204
  GROUP BY f.repo
201
205
  ORDER BY f.repo`).all(),s=e.map(i=>` ${i.repo.padEnd(40)} ${String(i.file_count).padStart(5)} files ${String(i.symbol_count).padStart(6)} symbols [${i.languages}]`),n=e.reduce((i,r)=>({files:i.files+r.file_count,symbols:i.symbols+r.symbol_count}),{files:0,symbols:0});return{content:[{type:"text",text:JSON.stringify({mode:"federation_summary",note:"Index is too large for a full outline. Use repo parameter to drill into a specific service.",total_repos:e.length,total_files:n.files,total_symbols:n.symbols,repos:e,usage:'Call cairn_outline with { repo: "<repo-name>" } to get the full outline for one service.',outline:s.join(`
202
- `)},null,2)}]}}function Kt(t,{paths:e,repo:s,checks:n}={}){let i=process.cwd();if(!e?.length&&!s&&t.prepare("SELECT COUNT(*) AS n FROM symbols").get().n>Kn)return Qn(t);let r;if(s)r=t.prepare("SELECT id, path, language FROM files WHERE repo = ? ORDER BY path").all(s);else if(e&&e.length>0){let f=e.map(()=>"f.path LIKE ?").join(" OR ");r=t.prepare(`SELECT id, path, language FROM files f WHERE (${f}) ORDER BY path`).all(...e.map(h=>`%${h}%`))}else r=t.prepare("SELECT id, path, language FROM files ORDER BY path").all();let o=new Map,a=new Set;for(let f of r){let h=_e.relative(i,f.path).replace(/\\/g,"/");a.has(h)||(a.add(h),o.set(f.id,{id:f.id,path:f.path,relPath:h,language:f.language,symbols:[]}))}let c=t.prepare("SELECT file_id, fqn, name, kind, exported, line FROM symbols ORDER BY file_id, kind, name").all();for(let f of c)o.has(f.file_id)&&o.get(f.file_id).symbols.push(f);let p=[];for(let f of o.values()){if(f.symbols.length===0)continue;p.push(`
203
- ${f.relPath} [${f.language}]`);let h=new Map,y=[];for(let _ of f.symbols)if(_.kind==="class"||_.kind==="interface"||_.kind==="enum")h.has(_.name)||h.set(_.name,{meta:_,members:[]});else if(_.fqn&&_.fqn.includes(".")){let T=_.fqn.split("."),b=T[T.length-2];h.has(b)?h.get(b).members.push(_):y.push(_)}else y.push(_);for(let[_,{meta:T,members:b}]of h){let C=T.exported?"+":"-",E=T.line?`:${T.line}`:"";p.push(` ${C} ${T.kind} ${_}${E}`);for(let R of b){let x=R.exported?"+":"-",O=R.line?`:${R.line}`:"";p.push(` ${x} ${R.name}${O} (${R.kind})`)}}for(let _ of y){if(_.kind==="class")continue;let T=_.exported?"+":"-",b=_.line?`:${_.line}`:"";p.push(` ${T} ${_.name}${b} (${_.kind})`)}}let l=[],m=new Set;for(let f of o.values())/[/\\](test|spec|__tests__)[/\\]|\.test\.|\.spec\./.test(f.path)&&m.add(_e.basename(f.path).replace(/\.(test|spec)\.[a-z]+$/,"").toLowerCase());for(let f of o.values()){let h=/[/\\](test|spec|__tests__)[/\\]|\.test\.|\.spec\./.test(f.path),y=f.symbols.filter(E=>E.kind==="method"||E.kind==="function"),_=y.map(E=>E.name),T=_.map(E=>E.toLowerCase());y.length>20&&l.push({file:f.relPath,issue:"god_class",detail:`${y.length} methods \u2014 likely violates single responsibility`,confidence:y.length>35?"HIGH":"MEDIUM",jump_to:f.symbols.find(E=>E.kind==="class")?.name});let b=new Set;for(let[E,R]of zn){let x=`${E}/${R}`;if(b.has(x))continue;let O=T.some(w=>w===E||w.startsWith(E+"_")||w.startsWith(E[0].toUpperCase()+E.slice(1))),j=T.some(w=>w===R||w.startsWith(R+"_")||w.startsWith(R[0].toUpperCase()+R.slice(1))),L=_.find(w=>w.toLowerCase()===E||w.toLowerCase().startsWith(E));O&&!j&&L&&(b.add(x),l.push({file:f.relPath,issue:"symmetry_gap",detail:`has \`${L}\` but no \`${R}*\` \u2014 possible incomplete lifecycle`,confidence:"MEDIUM",jump_to:L}))}for(let{group:E,verbs:R}of Vn){let x=new Set,O=new Map;for(let j of _){let L=j.toLowerCase();for(let w of R)if(L===w||L.startsWith(w)&&(L.length===w.length||/[A-Z_]/.test(j[w.length]))){x.add(w),O.has(w)||O.set(w,j);break}}if(x.size>=3){let j=[...x].map(L=>`\`${O.get(L)}\``).join(", ");l.push({file:f.relPath,issue:"naming_inconsistency",detail:`Mixed "${E}" verbs: ${j} \u2014 pick one convention`,confidence:"LOW",jump_to:O.get([...x][0])})}}if(!h&&f.symbols.some(E=>E.kind==="class")){let E=_e.basename(f.path).replace(/\.[^.]+$/,"").toLowerCase();m.has(E)||l.push({file:f.relPath,issue:"missing_tests",detail:`No test file found for ${_e.basename(f.path)}`,confidence:"LOW",jump_to:f.symbols.find(R=>R.kind==="class")?.name})}let C=f.symbols.filter(E=>E.kind==="class");for(let E of C){let R=E.fqn?E.fqn+".":E.name+".";!f.symbols.some(O=>O.fqn&&O.fqn.startsWith(R))&&!h&&l.push({file:f.relPath,issue:"empty_class",detail:`\`${E.name}\` has no indexed members \u2014 may be a stub or anemic model`,confidence:"LOW",jump_to:E.name})}}let u=new Map;for(let f of o.values())for(let h of f.symbols.filter(y=>y.kind==="class"))u.has(h.name)||u.set(h.name,new Set),u.get(h.name).add(f.relPath);for(let[f,h]of u){let y=[...h];y.length>1&&l.push({file:y.join(", "),issue:"duplicate_class_name",detail:`\`${f}\` defined in ${y.length} files \u2014 risk of confusion or shadowing`,confidence:"MEDIUM",jump_to:f})}let d={HIGH:0,MEDIUM:1,LOW:2};l.sort((f,h)=>(d[f.confidence]??3)-(d[h.confidence]??3));let g=Zn(l);return{content:[{type:"text",text:JSON.stringify({file_count:o.size,symbol_count:c.length,suspect_count:l.length,summary:g,suspects:l,outline:p.join(`
204
- `).trim()},null,2)}]}}function Zn(t){let e={};for(let o of t)e[o.issue]=(e[o.issue]||0)+1;let s=t.filter(o=>o.confidence==="HIGH").length,n=t.filter(o=>o.confidence==="MEDIUM").length,i=[];s&&i.push(`${s} HIGH`),n&&i.push(`${n} MEDIUM`);let r=t.length-s-n;return r&&i.push(`${r} LOW`),i.length?`${t.length} suspects (${i.join(", ")}): ${Object.entries(e).map(([o,a])=>`${a} ${o}`).join(", ")}`:"No structural suspects detected"}async function Qt(t,e={}){let{text:s,id:n,status:i,source:r}=e,o=e.action??"list",a=e.kind;switch(o){case"list":{let c="SELECT * FROM main.todos WHERE 1=1",p=[];i&&(c+=" AND status = ?",p.push(i)),r&&(c+=" AND source = ?",p.push(r)),a&&(c+=" AND kind = ?",p.push(a.toUpperCase())),c+=" ORDER BY CASE status WHEN 'open' THEN 0 ELSE 1 END, source, file, line";let l=t.prepare(c).all(...p),m=l.filter(d=>d.status==="open").length,u=l.filter(d=>d.status==="done").length;return{content:[{type:"text",text:JSON.stringify({total:l.length,open:m,done:u,todos:l},null,2)}]}}case"add":{if(!s)throw new Error("text is required for add action");let c=(a||"TODO").toUpperCase(),p=t.prepare("INSERT INTO main.todos (source, kind, text) VALUES (?, ?, ?)").run("manual",c,s),l=t.prepare("SELECT * FROM main.todos WHERE id = ?").get(p.lastInsertRowid);return{content:[{type:"text",text:JSON.stringify({added:l},null,2)}]}}case"resolve":{if(!n)throw new Error("id is required for resolve action");if(t.prepare("UPDATE main.todos SET status = ?, resolved_at = datetime('now') WHERE id = ?").run("done",n).changes===0)throw new Error(`No todo found with id ${n}`);let p=t.prepare("SELECT * FROM main.todos WHERE id = ?").get(n);return{content:[{type:"text",text:JSON.stringify({resolved:p},null,2)}]}}case"scan":{let c=await P(process.cwd()),p=await me(t,c),l=t.prepare("SELECT COUNT(*) AS n FROM main.todos WHERE status = 'open'").get().n;return{content:[{type:"text",text:JSON.stringify({scanned_files:c.length,todos_found:p,open_todos:l},null,2)}]}}default:throw new Error(`Unknown action: "${o}". Use list, add, resolve, or scan.`)}}import G from"fs";import q from"path";var Zt=["package.json","pom.xml","Cargo.toml","go.mod","pyproject.toml","build.gradle","build.gradle.kts","composer.json","Gemfile","CMakeLists.txt"];function De(t){let e;try{e=G.statSync(t).isDirectory()?t:q.dirname(t)}catch{return null}for(;;){if(G.existsSync(q.join(e,".cairn",".cairn-project"))||G.existsSync(q.join(e,".git")))return e;for(let n of Zt)if(G.existsSync(q.join(e,n)))return e;let s=q.dirname(e);if(s===e)return null;e=s}}function en(t,{project_path:e}={}){let s=process.cwd(),n=A();if(!e)return xe(null),{content:[{type:"text",text:JSON.stringify({switched:!0,project_root:s,previous_root:n,action:"restored_to_session_root"},null,2)}]};let i=De(e);if(!i)return{content:[{type:"text",text:JSON.stringify({switched:!1,reason:"Could not detect a project root from the given path. No .git, package.json, or other project markers found.",path_given:e},null,2)}]};let r=G.existsSync(q.join(i,".cairn","index.db")),o=[];G.existsSync(q.join(i,".cairn",".cairn-project"))&&o.push("cairn project"),G.existsSync(q.join(i,".git"))&&o.push("git repo");for(let a of Zt)if(G.existsSync(q.join(i,a))){o.push(a);break}return xe(i),{content:[{type:"text",text:JSON.stringify({switched:!0,project_root:i,previous_root:n,detected_by:o,has_cairn_index:r,tip:r?"Call cairn_resume to restore this project's session, or cairn_search to query its index.":"No cairn index found. Call cairn_maintain to index this project first."},null,2)}]}}import pe from"fs";import Y from"path";function tn(t,{source_path:e,types:s,overwrite:n=!1}){if(!e)throw new Error("source_path is required");let i,r=e.replace(/\\/g,"/");if(r.endsWith("/.cairn/memory")||r.endsWith("\\.cairn\\memory"))i=e;else if(r.endsWith("/.cairn")||r.endsWith("\\.cairn"))i=Y.join(e,"memory");else{let d=De(e);if(!d)return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:"Could not detect a project root from source_path.",source_path:e},null,2)}]};i=Y.join(d,".cairn","memory")}if(!pe.existsSync(i))return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:"Source project has no .cairn/memory directory. Run cairn_maintain or cairn_resume there first.",source_memory_dir:i},null,2)}]};let o=s&&s.length>0?s:It,a=U(i).filter(d=>o.includes(d.type));if(a.length===0)return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:`No memories of types [${o.join(", ")}] found in source project.`,source_memory_dir:i},null,2)}]};let c=V(),p=Y.dirname(Y.dirname(i)),l=U(c),m=new Set(l.map(d=>d.file)),u={copied:[],skipped:[],errors:[]};for(let d of a){let g=Y.join(i,d.file);if(!pe.existsSync(g)){u.errors.push({name:d.name,reason:"source file missing"});continue}let f=Y.join(c,d.file),h=m.has(d.file);if(h&&!n){u.skipped.push({name:d.name,reason:"already exists (use overwrite: true to replace)"});continue}let y=pe.readFileSync(g,"utf8");y.includes(`
206
+ `)},null,2)}]}}function tn(t,{paths:e,repo:s,checks:n}={}){let i=process.cwd();if(!e?.length&&!s&&t.prepare("SELECT COUNT(*) AS n FROM symbols").get().n>ps)return ls(t);let r;if(s)r=t.prepare("SELECT id, path, language FROM files WHERE repo = ? ORDER BY path").all(s);else if(e&&e.length>0){let f=e.map(()=>"f.path LIKE ?").join(" OR ");r=t.prepare(`SELECT id, path, language FROM files f WHERE (${f}) ORDER BY path`).all(...e.map(g=>`%${g}%`))}else r=t.prepare("SELECT id, path, language FROM files ORDER BY path").all();let o=new Map,a=new Set;for(let f of r){let g=Te.relative(i,f.path).replace(/\\/g,"/");a.has(g)||(a.add(g),o.set(f.id,{id:f.id,path:f.path,relPath:g,language:f.language,symbols:[]}))}let c=t.prepare("SELECT file_id, fqn, name, kind, exported, line FROM symbols ORDER BY file_id, kind, name").all();for(let f of c)o.has(f.file_id)&&o.get(f.file_id).symbols.push(f);let l=[];for(let f of o.values()){if(f.symbols.length===0)continue;l.push(`
207
+ ${f.relPath} [${f.language}]`);let g=new Map,y=[];for(let _ of f.symbols)if(_.kind==="class"||_.kind==="interface"||_.kind==="enum")g.has(_.name)||g.set(_.name,{meta:_,members:[]});else if(_.fqn&&_.fqn.includes(".")){let T=_.fqn.split("."),x=T[T.length-2];g.has(x)?g.get(x).members.push(_):y.push(_)}else y.push(_);for(let[_,{meta:T,members:x}]of g){let I=T.exported?"+":"-",E=T.line?`:${T.line}`:"";l.push(` ${I} ${T.kind} ${_}${E}`);for(let R of x){let b=R.exported?"+":"-",O=R.line?`:${R.line}`:"";l.push(` ${b} ${R.name}${O} (${R.kind})`)}}for(let _ of y){if(_.kind==="class")continue;let T=_.exported?"+":"-",x=_.line?`:${_.line}`:"";l.push(` ${T} ${_.name}${x} (${_.kind})`)}}let p=[],h=new Set;for(let f of o.values())/[/\\](test|spec|__tests__)[/\\]|\.test\.|\.spec\./.test(f.path)&&h.add(Te.basename(f.path).replace(/\.(test|spec)\.[a-z]+$/,"").toLowerCase());for(let f of o.values()){let g=/[/\\](test|spec|__tests__)[/\\]|\.test\.|\.spec\./.test(f.path),y=f.symbols.filter(E=>E.kind==="method"||E.kind==="function"),_=y.map(E=>E.name),T=_.map(E=>E.toLowerCase());y.length>20&&p.push({file:f.relPath,issue:"god_class",detail:`${y.length} methods \u2014 likely violates single responsibility`,confidence:y.length>35?"HIGH":"MEDIUM",jump_to:f.symbols.find(E=>E.kind==="class")?.name});let x=new Set;for(let[E,R]of cs){let b=`${E}/${R}`;if(x.has(b))continue;let O=T.some(w=>w===E||w.startsWith(E+"_")||w.startsWith(E[0].toUpperCase()+E.slice(1))),j=T.some(w=>w===R||w.startsWith(R+"_")||w.startsWith(R[0].toUpperCase()+R.slice(1))),k=_.find(w=>w.toLowerCase()===E||w.toLowerCase().startsWith(E));O&&!j&&k&&(x.add(b),p.push({file:f.relPath,issue:"symmetry_gap",detail:`has \`${k}\` but no \`${R}*\` \u2014 possible incomplete lifecycle`,confidence:"MEDIUM",jump_to:k}))}for(let{group:E,verbs:R}of as){let b=new Set,O=new Map;for(let j of _){let k=j.toLowerCase();for(let w of R)if(k===w||k.startsWith(w)&&(k.length===w.length||/[A-Z_]/.test(j[w.length]))){b.add(w),O.has(w)||O.set(w,j);break}}if(b.size>=3){let j=[...b].map(k=>`\`${O.get(k)}\``).join(", ");p.push({file:f.relPath,issue:"naming_inconsistency",detail:`Mixed "${E}" verbs: ${j} \u2014 pick one convention`,confidence:"LOW",jump_to:O.get([...b][0])})}}if(!g&&f.symbols.some(E=>E.kind==="class")){let E=Te.basename(f.path).replace(/\.[^.]+$/,"").toLowerCase();h.has(E)||p.push({file:f.relPath,issue:"missing_tests",detail:`No test file found for ${Te.basename(f.path)}`,confidence:"LOW",jump_to:f.symbols.find(R=>R.kind==="class")?.name})}let I=f.symbols.filter(E=>E.kind==="class");for(let E of I){let R=E.fqn?E.fqn+".":E.name+".";!f.symbols.some(O=>O.fqn&&O.fqn.startsWith(R))&&!g&&p.push({file:f.relPath,issue:"empty_class",detail:`\`${E.name}\` has no indexed members \u2014 may be a stub or anemic model`,confidence:"LOW",jump_to:E.name})}}let m=new Map;for(let f of o.values())for(let g of f.symbols.filter(y=>y.kind==="class"))m.has(g.name)||m.set(g.name,new Set),m.get(g.name).add(f.relPath);for(let[f,g]of m){let y=[...g];y.length>1&&p.push({file:y.join(", "),issue:"duplicate_class_name",detail:`\`${f}\` defined in ${y.length} files \u2014 risk of confusion or shadowing`,confidence:"MEDIUM",jump_to:f})}let d={HIGH:0,MEDIUM:1,LOW:2};p.sort((f,g)=>(d[f.confidence]??3)-(d[g.confidence]??3));let u=ds(p);return{content:[{type:"text",text:JSON.stringify({file_count:o.size,symbol_count:c.length,suspect_count:p.length,summary:u,suspects:p,outline:l.join(`
208
+ `).trim()},null,2)}]}}function ds(t){let e={};for(let o of t)e[o.issue]=(e[o.issue]||0)+1;let s=t.filter(o=>o.confidence==="HIGH").length,n=t.filter(o=>o.confidence==="MEDIUM").length,i=[];s&&i.push(`${s} HIGH`),n&&i.push(`${n} MEDIUM`);let r=t.length-s-n;return r&&i.push(`${r} LOW`),i.length?`${t.length} suspects (${i.join(", ")}): ${Object.entries(e).map(([o,a])=>`${a} ${o}`).join(", ")}`:"No structural suspects detected"}async function nn(t,e={}){let{text:s,id:n,status:i,source:r}=e,o=e.action??"list",a=e.kind;switch(o){case"list":{let c="SELECT * FROM main.todos WHERE 1=1",l=[];i&&(c+=" AND status = ?",l.push(i)),r&&(c+=" AND source = ?",l.push(r)),a&&(c+=" AND kind = ?",l.push(a.toUpperCase())),c+=" ORDER BY CASE status WHEN 'open' THEN 0 ELSE 1 END, source, file, line";let p=t.prepare(c).all(...l),h=p.filter(d=>d.status==="open").length,m=p.filter(d=>d.status==="done").length;return{content:[{type:"text",text:JSON.stringify({total:p.length,open:h,done:m,todos:p},null,2)}]}}case"add":{if(!s)throw new Error("text is required for add action");let c=(a||"TODO").toUpperCase(),l=t.prepare("INSERT INTO main.todos (source, kind, text) VALUES (?, ?, ?)").run("manual",c,s),p=t.prepare("SELECT * FROM main.todos WHERE id = ?").get(l.lastInsertRowid);return{content:[{type:"text",text:JSON.stringify({added:p},null,2)}]}}case"resolve":{if(!n)throw new Error("id is required for resolve action");if(t.prepare("UPDATE main.todos SET status = ?, resolved_at = datetime('now') WHERE id = ?").run("done",n).changes===0)throw new Error(`No todo found with id ${n}`);let l=t.prepare("SELECT * FROM main.todos WHERE id = ?").get(n);return{content:[{type:"text",text:JSON.stringify({resolved:l},null,2)}]}}case"scan":{let c=await P(process.cwd()),l=await me(t,c),p=t.prepare("SELECT COUNT(*) AS n FROM main.todos WHERE status = 'open'").get().n;return{content:[{type:"text",text:JSON.stringify({scanned_files:c.length,todos_found:l,open_todos:p},null,2)}]}}default:throw new Error(`Unknown action: "${o}". Use list, add, resolve, or scan.`)}}import X from"fs";import B from"path";var sn=["package.json","pom.xml","Cargo.toml","go.mod","pyproject.toml","build.gradle","build.gradle.kts","composer.json","Gemfile","CMakeLists.txt"];function He(t){let e;try{e=X.statSync(t).isDirectory()?t:B.dirname(t)}catch{return null}for(;;){if(X.existsSync(B.join(e,".cairn",".cairn-project"))||X.existsSync(B.join(e,".git")))return e;for(let n of sn)if(X.existsSync(B.join(e,n)))return e;let s=B.dirname(e);if(s===e)return null;e=s}}function rn(t,{project_path:e}={}){let s=process.cwd(),n=$();if(!e)return Re(null),{content:[{type:"text",text:JSON.stringify({switched:!0,project_root:s,previous_root:n,action:"restored_to_session_root"},null,2)}]};let i=He(e);if(!i)return{content:[{type:"text",text:JSON.stringify({switched:!1,reason:"Could not detect a project root from the given path. No .git, package.json, or other project markers found.",path_given:e},null,2)}]};let r=X.existsSync(B.join(i,".cairn","index.db")),o=[];X.existsSync(B.join(i,".cairn",".cairn-project"))&&o.push("cairn project"),X.existsSync(B.join(i,".git"))&&o.push("git repo");for(let a of sn)if(X.existsSync(B.join(i,a))){o.push(a);break}return Re(i),{content:[{type:"text",text:JSON.stringify({switched:!0,project_root:i,previous_root:n,detected_by:o,has_cairn_index:r,tip:r?"Call cairn_resume to restore this project's session, or cairn_search to query its index.":"No cairn index found. Call cairn_maintain to index this project first."},null,2)}]}}import pe from"fs";import V from"path";function on(t,{source_path:e,types:s,overwrite:n=!1}){if(!e)throw new Error("source_path is required");let i,r=e.replace(/\\/g,"/");if(r.endsWith("/.cairn/memory")||r.endsWith("\\.cairn\\memory"))i=e;else if(r.endsWith("/.cairn")||r.endsWith("\\.cairn"))i=V.join(e,"memory");else{let d=He(e);if(!d)return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:"Could not detect a project root from source_path.",source_path:e},null,2)}]};i=V.join(d,".cairn","memory")}if(!pe.existsSync(i))return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:"Source project has no .cairn/memory directory. Run cairn_maintain or cairn_resume there first.",source_memory_dir:i},null,2)}]};let o=s&&s.length>0?s:Mt,a=U(i).filter(d=>o.includes(d.type));if(a.length===0)return{content:[{type:"text",text:JSON.stringify({migrated:!1,reason:`No memories of types [${o.join(", ")}] found in source project.`,source_memory_dir:i},null,2)}]};let c=z(),l=V.dirname(V.dirname(i)),p=U(c),h=new Set(p.map(d=>d.file)),m={copied:[],skipped:[],errors:[]};for(let d of a){let u=V.join(i,d.file);if(!pe.existsSync(u)){m.errors.push({name:d.name,reason:"source file missing"});continue}let f=V.join(c,d.file),g=h.has(d.file);if(g&&!n){m.skipped.push({name:d.name,reason:"already exists (use overwrite: true to replace)"});continue}let y=pe.readFileSync(u,"utf8");y.includes(`
205
209
  origin:`)||(y=y.replace(`---
206
210
 
207
211
  `,`---
208
- origin: ${p}
212
+ origin: ${l}
209
213
 
210
- `),y.includes(`origin: ${p}`)||(y=y.replace(/^---\n/,`---
211
- origin: ${p}
212
- `)));try{pe.writeFileSync(f,y,"utf8"),h||(l.push({...d}),m.add(d.file)),u.copied.push({name:d.name,type:d.type,origin:p})}catch(_){u.errors.push({name:d.name,reason:_.message})}}if(u.copied.length>0){let d=["# Memory Index",""];for(let g of l)d.push(`- [${g.name}](${g.file}) \u2014 ${g.description} \`[${g.type}]\``);pe.writeFileSync(Y.join(c,"MEMORY.md"),d.join(`
214
+ `),y.includes(`origin: ${l}`)||(y=y.replace(/^---\n/,`---
215
+ origin: ${l}
216
+ `)));try{pe.writeFileSync(f,y,"utf8"),g||(p.push({...d}),h.add(d.file)),m.copied.push({name:d.name,type:d.type,origin:l})}catch(_){m.errors.push({name:d.name,reason:_.message})}}if(m.copied.length>0){let d=["# Memory Index",""];for(let u of p)d.push(`- [${u.name}](${u.file}) \u2014 ${u.description} \`[${u.type}]\``);pe.writeFileSync(V.join(c,"MEMORY.md"),d.join(`
213
217
  `)+`
214
- `,"utf8")}return{content:[{type:"text",text:JSON.stringify({migrated:u.copied.length>0,source:p,types_migrated:o,copied:u.copied.length,skipped:u.skipped.length,errors:u.errors.length,details:u},null,2)}]}}import es from"fs";var Ue=Date.now(),Pe=!1;function He(){Ue=Date.now()}async function We(t){if(Pe)return;Pe=!0;let e=Ue;Ue=Date.now();try{let s=await P(A()),n=[];for(let i of s)try{es.statSync(i).mtimeMs>e&&n.push(i)}catch{}n.length>0&&await fe(t,n)}finally{Pe=!1}}function nn(t,e=3e4){let s=setInterval(()=>We(t),e);s.unref&&s.unref()}var v=Xe();nn(v);var qe=new ts({name:"cairn",version:"1.0.0"},{capabilities:{tools:{}}});qe.setRequestHandler(is,async()=>({tools:[{name:"cairn_maintain",description:"Index the current project into Cairn's polyglot knowledge graph. Supports Java, TypeScript, JavaScript, Vue, Python, SQL, config, and Markdown. Indexes from the current working directory. Run at session start or after major changes.",inputSchema:{type:"object",properties:{languages:{type:"array",items:{type:"string"},description:'Optional: limit to specific languages e.g. ["java","typescript"]'}}}},{name:"cairn_search",description:"Search the codebase by concept across all languages. Returns ranked symbols with scores.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10},language:{type:"string",description:"Optional: filter by language"},kind:{type:"string",description:"Optional: class/function/component/table/..."}},required:["query"]}},{name:"cairn_describe",description:"Describe what a directory/module does: symbols, responsibilities, upstream/downstream deps.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Directory or file path to describe"}},required:["path"]}},{name:"cairn_code_graph",description:"Analyze module dependencies and instability metrics across all languages. Use before refactoring.",inputSchema:{type:"object",properties:{module:{type:"string",description:"Optional: scope to a specific module/package"},mode:{type:"string",enum:["instability","health","cycles"]}},required:["mode"]}},{name:"cairn_security",description:"Scan for security vulnerabilities across all languages (XSS, XXE, SQLi, weak crypto, hardcoded secrets, etc.)",inputSchema:{type:"object",properties:{paths:{type:"array",items:{type:"string"},description:"Optional: scope to specific paths"},severity:{type:"string",enum:["HIGH","MEDIUM","LOW","ALL"],default:"HIGH"},language:{type:"string",description:"Optional: scope to a specific language"}}}},{name:"cairn_bundle",description:`Produce a minified single-file snapshot of source code for Claude to read.
218
+ `,"utf8")}return{content:[{type:"text",text:JSON.stringify({migrated:m.copied.length>0,source:l,types_migrated:o,copied:m.copied.length,skipped:m.skipped.length,errors:m.errors.length,details:m},null,2)}]}}import us from"fs";var qe=Date.now(),We=!1;function Be(){qe=Date.now()}async function Ge(t){if(We)return;We=!0;let e=qe;qe=Date.now();try{let s=await P($()),n=[];for(let i of s)try{us.statSync(i).mtimeMs>e&&n.push(i)}catch{}n.length>0&&await fe(t,n)}finally{We=!1}}function an(t,e=3e4){let s=setInterval(()=>Ge(t),e);s.unref&&s.unref()}var L=Ve();an(L);var Xe=new ms({name:"cairn",version:"1.0.0"},{capabilities:{tools:{}}});Xe.setRequestHandler(gs,async()=>({tools:[{name:"cairn_maintain",description:"Index the current project into Cairn's polyglot knowledge graph. Supports Java, TypeScript, JavaScript, Vue, Python, SQL, config, and Markdown. Indexes from the current working directory. Run at session start or after major changes.",inputSchema:{type:"object",properties:{languages:{type:"array",items:{type:"string"},description:'Optional: limit to specific languages e.g. ["java","typescript"]'}}}},{name:"cairn_search",description:"Search the codebase by concept across all languages. Returns ranked symbols with scores.",inputSchema:{type:"object",properties:{query:{type:"string"},limit:{type:"number",default:10},language:{type:"string",description:"Optional: filter by language"},kind:{type:"string",description:"Optional: class/function/component/table/..."}},required:["query"]}},{name:"cairn_describe",description:"Describe what a directory/module does: symbols, responsibilities, upstream/downstream deps.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Directory or file path to describe"}},required:["path"]}},{name:"cairn_code_graph",description:"Analyze module dependencies and instability metrics across all languages. Use before refactoring.",inputSchema:{type:"object",properties:{module:{type:"string",description:"Optional: scope to a specific module/package"},mode:{type:"string",enum:["instability","health","cycles"]}},required:["mode"]}},{name:"cairn_security",description:"Scan for security vulnerabilities across all languages (XSS, XXE, SQLi, weak crypto, hardcoded secrets, etc.)",inputSchema:{type:"object",properties:{paths:{type:"array",items:{type:"string"},description:"Optional: scope to specific paths"},severity:{type:"string",enum:["HIGH","MEDIUM","LOW","ALL"],default:"HIGH"},language:{type:"string",description:"Optional: scope to a specific language"}}}},{name:"cairn_bundle",description:`Produce a minified single-file snapshot of source code for Claude to read.
215
219
  Strips comments, empty lines, and optionally styles. Returns the bundle file path and compression stats.
216
220
  Use AFTER cairn_search to get a readable version of the files Claude needs to work with.
217
221
  Typical workflow: cairn_search \u2192 find files \u2192 cairn_bundle those paths \u2192 Claude reads bundle.`,inputSchema:{type:"object",properties:{bundle_name:{type:"string",default:"default",description:"Name for the bundle file (saved to .cairn/bundles/<name>.txt)"},filter_paths:{type:"array",items:{type:"string"},description:"Optional: only include files under these relative paths"},filter_language:{type:"string",description:"Optional: only bundle this language (java/typescript/vue/python/...)"},no_comments:{type:"boolean",default:!0,description:"Strip code comments"},no_empty_lines:{type:"boolean",default:!0,description:"Remove empty lines"},no_style:{type:"boolean",default:!1,description:"Skip <style> blocks in Vue files"},aggressive:{type:"boolean",default:!1,description:"Aggressive whitespace/punctuation minification"},only_changed:{type:"boolean",default:!1,description:"Incremental: only re-bundle files changed since last run"},max_size_kb:{type:"number",default:800,description:"Safety cap in KB to avoid context window overflow"}}}},{name:"cairn_checkpoint",description:"Save current session state to .cairn/session.json so it can be restored next session with cairn_resume.",inputSchema:{type:"object",properties:{message:{type:"string",description:"What you were working on \u2014 will be shown on resume"},active_files:{type:"array",items:{type:"string"},description:"Optional: absolute paths to files actively being worked on"},notes:{type:"array",items:{type:"string"},description:"Optional: things Claude should remember next session"}},required:["message"]}},{name:"cairn_resume",description:"Restore the last saved session state. Detects which files changed since the checkpoint and incrementally re-indexes only those files. Also loads preference memories and surfaces the memory index. Call at the start of a session instead of cairn_maintain when resuming work. If the project was moved to a different drive or directory, paths are repaired automatically.",inputSchema:{type:"object",properties:{}}},{name:"cairn_outline",description:`Generate a structural outline of the project (or a scoped subset) and run heuristic analysis to detect likely issues.
@@ -230,4 +234,4 @@ Detects:
230
234
  - empty_class: class with no indexed members (stub or anemic model)
231
235
  - duplicate_class_name: same class name defined in multiple files
232
236
 
233
- 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"],description:"write: save/update a memory | read: load a memory by name | list: show all memories with index | delete: remove a memory"},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"}},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","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) | 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 rs=new Set(["cairn_search","cairn_bundle","cairn_describe","cairn_outline","cairn_code_graph","cairn_security"]);qe.setRequestHandler(ss,async t=>{let{name:e,arguments:s}=t.params;switch(rs.has(e)&&await We(v),e){case"cairn_maintain":{let n=await Et(v,s);return He(),n}case"cairn_search":return _t(v,s);case"cairn_describe":return St(v,s);case"cairn_code_graph":return xt(v,s);case"cairn_security":return Tt(v,s);case"cairn_bundle":return await Rt(v,s);case"cairn_checkpoint":return wt(v,s);case"cairn_resume":{let n=await Vt(v);return He(),n}case"cairn_outline":return Kt(v,s);case"cairn_minify":return zt(v,s);case"cairn_todos":return await Qt(v,s);case"cairn_roadmap":return ce(v,s);case"cairn_memo":return jt(v,s);case"cairn_switch":return en(v,s);case"cairn_employ_memory":return tn(v,s);default:throw new Error(`Unknown tool: ${e}`)}});var os=new ns;await qe.connect(os);
237
+ 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"],description:"write: save/update a memory | read: load a memory by name | list: show all memories with index | delete: remove a memory"},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"}},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 ys=new Set(["cairn_search","cairn_bundle","cairn_describe","cairn_outline","cairn_code_graph","cairn_security"]);Xe.setRequestHandler(hs,async t=>{let{name:e,arguments:s}=t.params;switch(ys.has(e)&&await Ge(L),e){case"cairn_maintain":{let n=await xt(L,s);return Be(),n}case"cairn_search":return Tt(L,s);case"cairn_describe":return bt(L,s);case"cairn_code_graph":return Rt(L,s);case"cairn_security":return wt(L,s);case"cairn_bundle":return await Nt(L,s);case"cairn_checkpoint":return Lt(L,s);case"cairn_resume":{let n=await Zt(L);return Be(),n}case"cairn_outline":return tn(L,s);case"cairn_minify":return en(L,s);case"cairn_todos":return await nn(L,s);case"cairn_roadmap":{let n=ce(L,s);return Jt(L),n}case"cairn_memo":return Pt(L,s);case"cairn_switch":return rn(L,s);case"cairn_employ_memory":return on(L,s);default:throw new Error(`Unknown tool: ${e}`)}});var _s=new fs;await Xe.connect(_s);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/cairn-mcp",
3
- "version": "1.14.1",
3
+ "version": "1.16.0",
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",