wyrm-mcp 9.0.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,21 @@
1
- import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyFileSync as q,chmodSync as j}from"fs";import{homedir as m,platform as C}from"os";import{join as c,dirname as w,resolve as V}from"path";import{fileURLToPath as T}from"url";import{spawnSync as v}from"child_process";function Q(){const e=m(),t=C(),o=t==="darwin"?c(e,"Library","Application Support","Code","User"):t==="win32"?c(e,"AppData","Roaming","Code","User"):c(e,".config","Code","User"),n=t==="darwin"?c(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?c(e,"AppData","Roaming","Code - Insiders","User"):c(e,".config","Code - Insiders","User"),s=t==="darwin"?c(e,"Library","Application Support","Claude"):t==="win32"?c(e,"AppData","Roaming","Claude"):c(e,".config","claude"),r=t==="darwin"?c(e,".cursor"):t==="win32"?c(e,".cursor"):c(e,".cursor"),i=t==="darwin"?c(e,".codeium","windsurf"):t==="win32"?c(e,".codeium","windsurf"):c(e,".codeium","windsurf"),u=t==="darwin"?c(e,".config","zed"):t==="win32"?c(e,"AppData","Roaming","Zed"):c(e,".config","zed"),a=c(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:c(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:c(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:c(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:c(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:c(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:c(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:c(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:c(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function Z(e){try{const t=C()==="win32"?"where":"which",o=v(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function S(){const e=Q();for(const t of e){if(t.id==="claude-code")t.detected=d(t.configPath)||Z("claude");else{const o=w(t.configPath);t.detected=d(o)}if(t.detected&&d(t.configPath))try{const o=p(t.configPath,"utf-8"),n=$(o);t.configured=ee(n,t)}catch{t.configured=!1}t.detected&&(t.version=X(t))}return e}function X(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=v(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
1
+ import{existsSync as d,readFileSync as g,writeFileSync as p,mkdirSync as P,copyFileSync as X,chmodSync as C}from"fs";import{homedir as h,platform as b}from"os";import{join as a,dirname as w,resolve as ee}from"path";import{fileURLToPath as _}from"url";import{spawnSync as v}from"child_process";function te(){const e=h(),t=b(),o=t==="darwin"?a(e,"Library","Application Support","Code","User"):t==="win32"?a(e,"AppData","Roaming","Code","User"):a(e,".config","Code","User"),n=t==="darwin"?a(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?a(e,"AppData","Roaming","Code - Insiders","User"):a(e,".config","Code - Insiders","User"),s=t==="darwin"?a(e,"Library","Application Support","Claude"):t==="win32"?a(e,"AppData","Roaming","Claude"):a(e,".config","claude"),r=t==="darwin"?a(e,".cursor"):t==="win32"?a(e,".cursor"):a(e,".cursor"),i=t==="darwin"?a(e,".codeium","windsurf"):t==="win32"?a(e,".codeium","windsurf"):a(e,".codeium","windsurf"),u=t==="darwin"?a(e,".config","zed"):t==="win32"?a(e,"AppData","Roaming","Zed"):a(e,".config","zed"),c=a(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:a(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:a(n,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:a(s,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:a(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:a(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:a(i,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:a(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:a(c,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function oe(e){try{const t=b()==="win32"?"where":"which",o=v(t,[e],{encoding:"utf-8",timeout:5e3});return o.status===0&&!!o.stdout&&o.stdout.trim().length>0}catch{return!1}}function A(){const e=te();for(const t of e){if(t.id==="claude-code")t.detected=d(t.configPath)||oe("claude");else{const o=w(t.configPath);t.detected=d(o)}if(t.detected&&d(t.configPath))try{const o=g(t.configPath,"utf-8"),n=E(o);t.configured=re(n,t)}catch{t.configured=!1}t.detected&&(t.version=ne(t))}return e}function ne(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=v(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
2
  `)[0];break}case"cursor":{const t=v("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
3
3
  `)[0];break}case"claude-code":{const t=v("claude",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.trim().split(`
4
- `)[0];break}}}catch{}}function ee(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function N(){try{const n=v("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(n.stdout?.trim())return n.stdout.trim()}catch{}try{const s=v("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(s){const r=c(s,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=c(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[c(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),c(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),c(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(d(n))return n;return"wyrm-mcp"}function D(){return c(m(),".wyrm","wyrm.db")}function we(e="mcp-json",t){const o=t?.serverPath??N(),n=t?.dbPath??D(),s=o==="wyrm-mcp",r={command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:n}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function te(e,t){const o=t.serverPath,n=o==="wyrm-mcp",s=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function M(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=w(e.configPath);d(o)||P(o,{recursive:!0});let n={},s=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");n=$(a),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath),{mode:384});try{j(r,384)}catch{}}const i=te(e,t);switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const a=n.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=i;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const a=n[e.mcpKey];a.wyrm=i;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const a=n[e.mcpKey];a.wyrm=i;break}}l(e.configPath,JSON.stringify(n,null,2)+`
5
- `,"utf-8");const u=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:u,message:`${e.name} ${u==="configured"?"configured":"updated"} successfully`,backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function ve(e,t){try{if(!d(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=p(e.configPath,"utf-8");let n;try{n=$(o)}catch{return{client:e,action:"failed",message:"Config file is not valid JSON; refusing to touch it"}}const r=(e.configFormat==="vscode"?n.mcp?.servers:n[e.mcpKey])?.wyrm;if(!r)return{client:e,action:"skipped",message:"Wyrm not configured in this client"};const i=`${e.configPath}.wyrm-backup`;l(i,o,{encoding:"utf-8",mode:384});try{j(i,384)}catch{}return r.env={...r.env,...t},l(e.configPath,JSON.stringify(n,null,2)+`
6
- `,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(o){return{client:e,action:"failed",message:String(o)}}}function oe(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!d(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=p(e.configPath,"utf-8"),o=$(t),n=`${e.configPath}.wyrm-backup`;l(n,t,{mode:384});try{j(n,384)}catch{}switch(e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const s=o[e.mcpKey];s&&delete s.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
7
- `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:n}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function _(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:c(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const R=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]},{script:"wyrm-citation-audit.mjs",events:["Stop"]}];function ne(){const e=c(m(),".claude");if(!d(e))return null;const t=_();try{const o=c(e,"hooks");d(o)||P(o,{recursive:!0});const n=w(T(import.meta.url)),s=c(n,"..","scripts","hooks"),r=t.configPath;let i={};if(d(r))try{i=JSON.parse(p(r,"utf-8"))}catch{i={}}const u=i.hooks??{};let a=!1;const f=[];for(const h of R){const g=c(s,h.script);if(!d(g)){f.push(h.script);continue}const W=c(o,h.script);q(g,W);try{j(W,493)}catch{}const O=`node ${W}`;for(const k of h.events){const x=typeof k=="string"?k:k.event,F=typeof k=="string"?void 0:k.matcher,E=Array.isArray(u[x])?u[x]:[];if(!E.some(b=>Array.isArray(b.hooks)&&b.hooks.some(Y=>Y.command===O))){const b=F?{matcher:F,hooks:[{type:"command",command:O,timeout:30}]}:{hooks:[{type:"command",command:O,timeout:30}]};E.push(b),a=!0}u[x]=E}}i.hooks=u,l(r,JSON.stringify(i,null,2)+`
8
- `,"utf-8"),t.configured=!0;const y=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:a?"configured":"skipped",message:(a?"auto-memory hooks installed":"already installed")+y}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ke(){const e=c(m(),".claude");if(!d(e))return null;const t=_();try{const o=w(T(import.meta.url)),n=`node ${c(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(d(s))try{r=JSON.parse(p(s,"utf-8"))}catch{r={}}const i=r.statusLine;if(i?.command&&!/wyrm-statusline/.test(i.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${i.command}); not overwriting`};const u=i?.command===n;return r.statusLine={type:"command",command:n,padding:0},l(s,JSON.stringify(r,null,2)+`
9
- `,"utf-8"),{client:t,action:u?"skipped":"configured",message:u?"buddy statusline already installed":"Wyrm buddy statusline installed \u2014 persistent in the Claude Code TUI"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Pe(){const e=c(m(),".claude","settings.json");if(!d(e))return null;const t=_();try{const o=JSON.parse(p(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
10
- `,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function re(){const e=c(m(),".claude","settings.json");if(!d(e))return null;const t=_();try{const o=JSON.parse(p(e,"utf-8")),n=c(m(),".claude","hooks"),s=new Set(R.map(u=>`node ${c(n,u.script)}`)),r=new Set(R.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),i=o.hooks;if(i){for(const u of r)Array.isArray(i[u])&&(i[u]=i[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!s.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),i[u].length===0&&delete i[u]);Object.keys(i).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
11
- `,"utf-8"),{client:t,action:"configured",message:"auto-memory hooks removed"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}const L=/wyrm-guard/,se=5,ie=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function A(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??c(m(),".claude","settings.json")}function J(e){return{id:"claude-code-guard",name:"Claude Code (failure firewall)",icon:"\u{1F6E1}\uFE0F",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function B(e){return C()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ce(){const e=w(T(import.meta.url)),t=c(e,"wyrm-guard.js");if(d(t))return`node ${B(t)}`;try{const o=C()==="win32"?"where":"which",s=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
12
- `)[0]?.trim();if(s)return B(s)}catch{}return null}function I(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const n of Object.keys(t)){const s=t[n];if(!Array.isArray(s))continue;let r=!1;const i=[];for(const u of s){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){i.push(u);continue}const f=a.hooks.filter(y=>!L.test(y?.command??""));if(f.length===a.hooks.length){i.push(u);continue}o=!0,r=!0,f.length>0&&i.push({...a,hooks:f})}r&&(i.length===0?delete t[n]:t[n]=i)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ae(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(c(m(),".claude")))return{client:J(A(e)),action:"skipped",message:"failure firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm guard`"};const o=A(e),n=J(o);try{const s=e.guardCommand??ce();if(!s)return{client:n,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};P(w(o),{recursive:!0});let r={};if(d(o))try{const g=JSON.parse(p(o,"utf-8"));if(typeof g!="object"||g===null||Array.isArray(g))throw new Error("not an object");r=g}catch{return{client:n,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const i=r.hooks;if(i!==void 0&&(typeof i!="object"||i===null||Array.isArray(i)))return{client:n,action:"failed",message:`${o} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify (fix the file, then re-run)`};const u=i?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:n,action:"failed",message:`${o} hooks.PreToolUse has an unexpected shape (not an array) \u2014 refusing to modify (fix the file, then re-run)`};const a=JSON.stringify(r);I(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of ie)y.push({matcher:g,hooks:[{type:"command",command:s,timeout:se}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
13
- `,"utf-8"),n.configured=!0,{client:n,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(s){return{client:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function ue(e={}){const t=A(e);if(!d(t))return null;const o=J(t);try{let n;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");n=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return I(n)?(l(t,JSON.stringify(n,null,2)+`
14
- `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Se(e={}){const t=A(e),o=[];try{const s=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const u of i?.hooks??[]){const a=u?.command;a&&L.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function de(e){const t={serverPath:e?.serverPath||N(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=c(m(),".wyrm");d(o)||P(o,{recursive:!0}),H(t);const n=S(),s=[];for(const u of n)s.push(M(u,t));const r=ne();r&&s.push(r);const i=ae();return i&&s.push(i),s}function be(){const e=S(),t=[];for(const s of e)t.push(oe(s));const o=re();o&&t.push(o);const n=ue();return n&&t.push(n),t}function fe(e,t){const o={serverPath:t?.serverPath||N(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};H(o);const n=S(),s=[];for(const r of n)e.includes(r.id)&&s.push(M(r,o));return s}function H(e){const t=c(m(),".wyrm","wyrm-config.json"),o=S().filter(s=>s.configured).map(s=>s.id),n={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};l(t,JSON.stringify(n,null,2)+`
15
- `,"utf-8")}function G(){const e=c(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function je(){const e=G();return e?fe(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):de()}const z="<!-- wyrm:start -->",K="<!-- wyrm:end -->",U=`${z}
4
+ `)[0];break}}}catch{}}function re(e,t){switch(t.configFormat){case"vscode":return e.mcp?.servers?.wyrm!==void 0;case"mcp-json":return e[t.mcpKey]?.wyrm!==void 0;case"zed":return e[t.mcpKey]?.wyrm!==void 0;default:return!1}}function M(){try{const n=v("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(n.stdout?.trim())return n.stdout.trim()}catch{}try{const s=v("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(s){const r=a(s,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=ee(__dirname,"index.js");if(d(e))return e;const t=a(h(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[a(h(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"projects","Wyrm","packages","mcp-server","dist","index.js"),a(h(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const n of o)if(d(n))return n;return"wyrm-mcp"}function D(){return a(h(),".wyrm","wyrm.db")}function Ae(e="mcp-json",t){const o=t?.serverPath??M(),n=t?.dbPath??D(),s=o==="wyrm-mcp",r={command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:n}};switch(e){case"stdio":return r;case"vscode":return{mcp:{servers:{wyrm:r}}};default:return{mcpServers:{wyrm:r}}}}function se(e,t){const o=t.serverPath,n=o==="wyrm-mcp",s=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function H(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=w(e.configPath);d(o)||P(o,{recursive:!0});let n={},s=!1;if(d(e.configPath))try{const c=g(e.configPath,"utf-8");n=E(c),s=!0}catch{n={}}let r;if(s){r=`${e.configPath}.wyrm-backup`,p(r,g(e.configPath),{mode:384});try{C(r,384)}catch{}}const i=se(e,t);switch(e.configFormat){case"vscode":{n.mcp||(n.mcp={});const c=n.mcp;c.servers||(c.servers={});const f=c.servers;f.wyrm=i;break}case"mcp-json":{n[e.mcpKey]||(n[e.mcpKey]={});const c=n[e.mcpKey];c.wyrm=i;break}case"zed":{n[e.mcpKey]||(n[e.mcpKey]={});const c=n[e.mcpKey];c.wyrm=i;break}}p(e.configPath,JSON.stringify(n,null,2)+`
5
+ `,"utf-8");const u=e.configured?"updated":"configured";return e.configured=!0,{client:e,action:u,message:`${e.name} ${u==="configured"?"configured":"updated"} successfully`,backup:r}}catch(o){return{client:e,action:"failed",message:`Failed to configure ${e.name}: ${o}`}}}function je(e,t){try{if(!d(e.configPath))return{client:e,action:"skipped",message:"No config file"};const o=g(e.configPath,"utf-8");let n;try{n=E(o)}catch{return{client:e,action:"failed",message:"Config file is not valid JSON; refusing to touch it"}}const r=(e.configFormat==="vscode"?n.mcp?.servers:n[e.mcpKey])?.wyrm;if(!r)return{client:e,action:"skipped",message:"Wyrm not configured in this client"};const i=`${e.configPath}.wyrm-backup`;p(i,o,{encoding:"utf-8",mode:384});try{C(i,384)}catch{}return r.env={...r.env,...t},p(e.configPath,JSON.stringify(n,null,2)+`
6
+ `,"utf-8"),{client:e,action:"updated",message:`env: ${Object.keys(t).join(", ")}`,backup:i}}catch(o){return{client:e,action:"failed",message:String(o)}}}function ie(e){if(!e.detected||!e.configured)return{client:e,action:"skipped",message:`${e.name} not configured`};try{if(!d(e.configPath))return{client:e,action:"skipped",message:`${e.name} config not found`};const t=g(e.configPath,"utf-8"),o=E(t),n=`${e.configPath}.wyrm-backup`;p(n,t,{mode:384});try{C(n,384)}catch{}switch(e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const s=o[e.mcpKey];s&&delete s.wyrm;break}}return p(e.configPath,JSON.stringify(o,null,2)+`
7
+ `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:n}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function O(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:a(h(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const J=[{script:"wyrm-session-capture.mjs",events:["SessionEnd","PreCompact"]},{script:"wyrm-session-rehydrate.mjs",events:["SessionStart"]},{script:"wyrm-session-prune.mjs",events:["SessionEnd"]},{script:"wyrm-tool-call-trace.mjs",events:["PostToolUse"]},{script:"wyrm-run-auto.mjs",events:["SessionStart",{event:"PreToolUse",matcher:"Task"},"SessionEnd"]},{script:"wyrm-citation-audit.mjs",events:["Stop"]}];function ce(){const e=a(h(),".claude");if(!d(e))return null;const t=O();try{const o=a(e,"hooks");d(o)||P(o,{recursive:!0});const n=w(_(import.meta.url)),s=a(n,"..","scripts","hooks"),r=t.configPath;let i={};if(d(r))try{i=JSON.parse(g(r,"utf-8"))}catch{i={}}const u=i.hooks??{};let c=!1;const f=[];for(const l of J){const y=a(s,l.script);if(!d(y)){f.push(l.script);continue}const T=a(o,l.script);X(y,T);try{C(T,493)}catch{}const W=`node ${T}`;for(const S of l.events){const N=typeof S=="string"?S:S.event,L=typeof S=="string"?void 0:S.matcher,x=Array.isArray(u[N])?u[N]:[];if(!x.some(j=>Array.isArray(j.hooks)&&j.hooks.some(Z=>Z.command===W))){const j=L?{matcher:L,hooks:[{type:"command",command:W,timeout:30}]}:{hooks:[{type:"command",command:W,timeout:30}]};x.push(j),c=!0}u[N]=x}}i.hooks=u,p(r,JSON.stringify(i,null,2)+`
8
+ `,"utf-8"),t.configured=!0;const m=f.length?` (missing from package: ${f.join(", ")})`:"";return{client:t,action:c?"configured":"skipped",message:(c?"auto-memory hooks installed":"already installed")+m}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function Ce(){const e=a(h(),".claude");if(!d(e))return null;const t=O();try{const o=w(_(import.meta.url)),n=`node ${a(o,"wyrm-statusline.js")}`,s=t.configPath;let r={};if(d(s))try{r=JSON.parse(g(s,"utf-8"))}catch{r={}}const i=r.statusLine;if(i?.command&&!/wyrm-statusline/.test(i.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${i.command}); not overwriting`};const u=i?.command===n;return r.statusLine={type:"command",command:n,padding:0},p(s,JSON.stringify(r,null,2)+`
9
+ `,"utf-8"),{client:t,action:u?"skipped":"configured",message:u?"buddy statusline already installed":"Wyrm buddy statusline installed \u2014 persistent in the Claude Code TUI"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function _e(){const e=a(h(),".claude","settings.json");if(!d(e))return null;const t=O();try{const o=JSON.parse(g(e,"utf-8")),n=o.statusLine;return n?.command&&/wyrm-statusline/.test(n.command)?(delete o.statusLine,p(e,JSON.stringify(o,null,2)+`
10
+ `,"utf-8"),{client:t,action:"configured",message:"Wyrm statusline removed"}):{client:t,action:"skipped",message:"no Wyrm statusline set"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}function ae(){const e=a(h(),".claude","settings.json");if(!d(e))return null;const t=O();try{const o=JSON.parse(g(e,"utf-8")),n=a(h(),".claude","hooks"),s=new Set(J.map(u=>`node ${a(n,u.script)}`)),r=new Set(J.flatMap(u=>u.events.map(c=>typeof c=="string"?c:c.event))),i=o.hooks;if(i){for(const u of r)Array.isArray(i[u])&&(i[u]=i[u].map(c=>({...c,hooks:Array.isArray(c.hooks)?c.hooks.filter(f=>!s.has(f.command??"")):c.hooks})).filter(c=>!Array.isArray(c.hooks)||c.hooks.length>0),i[u].length===0&&delete i[u]);Object.keys(i).length===0&&delete o.hooks}return p(e,JSON.stringify(o,null,2)+`
11
+ `,"utf-8"),{client:t,action:"configured",message:"auto-memory hooks removed"}}catch(o){return{client:t,action:"failed",message:o instanceof Error?o.message:String(o)}}}const I=/wyrm-guard/,G=5,ue=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function k(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??a(h(),".claude","settings.json")}function R(e){return{id:"claude-code-guard",name:"Claude Code (failure firewall)",icon:"\u{1F6E1}\uFE0F",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function $(e){return b()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function de(){const e=w(_(import.meta.url)),t=a(e,"wyrm-guard.js");if(d(t))return`node ${$(t)}`;try{const o=b()==="win32"?"where":"which",s=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
12
+ `)[0]?.trim();if(s)return $(s)}catch{}return null}function U(e,t){const o=e.hooks;if(!o||typeof o!="object"||Array.isArray(o))return!1;let n=!1;for(const s of Object.keys(o)){const r=o[s];if(!Array.isArray(r))continue;let i=!1;const u=[];for(const c of r){const f=c;if(!f||typeof f!="object"||!Array.isArray(f.hooks)){u.push(c);continue}const m=f.hooks.filter(l=>!t.test(l?.command??""));if(m.length===f.hooks.length){u.push(c);continue}n=!0,i=!0,m.length>0&&u.push({...f,hooks:m})}i&&(u.length===0?delete o[s]:o[s]=u)}return n&&Object.keys(o).length===0&&delete e.hooks,n}function z(e){return U(e,I)}function fe(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(a(h(),".claude")))return{client:R(k(e)),action:"skipped",message:"failure firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm guard`"};const o=k(e),n=R(o);try{const s=e.guardCommand??de();if(!s)return{client:n,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};P(w(o),{recursive:!0});let r={};if(d(o))try{const y=JSON.parse(g(o,"utf-8"));if(typeof y!="object"||y===null||Array.isArray(y))throw new Error("not an object");r=y}catch{return{client:n,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const i=r.hooks;if(i!==void 0&&(typeof i!="object"||i===null||Array.isArray(i)))return{client:n,action:"failed",message:`${o} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify (fix the file, then re-run)`};const u=i?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:n,action:"failed",message:`${o} hooks.PreToolUse has an unexpected shape (not an array) \u2014 refusing to modify (fix the file, then re-run)`};const c=JSON.stringify(r);z(r);const f=r.hooks??{},m=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const y of ue)m.push({matcher:y,hooks:[{type:"command",command:s,timeout:G}]});f.PreToolUse=m,r.hooks=f;const l=JSON.stringify(r)!==c;return l&&p(o,JSON.stringify(r,null,2)+`
13
+ `,"utf-8"),n.configured=!0,{client:n,action:l?"configured":"skipped",message:l?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(s){return{client:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function me(e={}){const t=k(e);if(!d(t))return null;const o=R(t);try{let n;try{const r=JSON.parse(g(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");n=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return z(n)?(p(t,JSON.stringify(n,null,2)+`
14
+ `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Oe(e={}){const t=k(e),o=[];try{const s=JSON.parse(g(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const u of i?.hooks??[]){const c=u?.command;c&&I.test(c)&&o.push(c)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}const K=/wyrm-memo/,le=[{event:"PreToolUse",matcher:"Read"},{event:"PreToolUse",matcher:"Bash"},{event:"PostToolUse",matcher:"Read"},{event:"PostToolUse",matcher:"Bash"}];function Y(e){return{id:"claude-code-memo",name:"Claude Code (token firewall)",icon:"",configPath:e,configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}function pe(){const e=w(_(import.meta.url)),t=a(e,"wyrm-memo.js");if(d(t))return`node ${$(t)}`;try{const o=b()==="win32"?"where":"which",s=v(o,["wyrm-memo"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
15
+ `)[0]?.trim();if(s)return $(s)}catch{}return null}function he(e={}){const t=k(e),o=!!(e.settingsPath??process.env.CLAUDE_SETTINGS),n=Y(t);if(!o&&!d(a(h(),".claude")))return{client:n,action:"skipped",message:"token firewall: ~/.claude not found on this machine (needs the Claude Code CLI). Not armed. Install Claude Code then re-run `wyrm memo`"};try{const s=e.memoCommand??pe();if(!s)return{client:n,action:"skipped",message:"wyrm-memo bin not found (npm i -g wyrm-mcp, or build dist/). Token-firewall hooks not installed"};P(w(t),{recursive:!0});let r={};if(d(t))try{const m=JSON.parse(g(t,"utf-8"));if(typeof m!="object"||m===null||Array.isArray(m))throw new Error("not an object");r=m}catch{return{client:n,action:"failed",message:`${t} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const i=r.hooks;if(i!==void 0&&(typeof i!="object"||i===null||Array.isArray(i)))return{client:n,action:"failed",message:`${t} "hooks" has an unexpected shape (not a plain object) \u2014 refusing to modify`};for(const m of["PreToolUse","PostToolUse"]){const l=i?.[m];if(l!==void 0&&!Array.isArray(l))return{client:n,action:"failed",message:`${t} hooks.${m} has an unexpected shape (not an array) \u2014 refusing to modify`}}const u=JSON.stringify(r);U(r,K);const c=r.hooks??{};for(const{event:m,matcher:l}of le){const y=Array.isArray(c[m])?c[m]:[];y.push({matcher:l,hooks:[{type:"command",command:s,timeout:G}]}),c[m]=y}r.hooks=c;const f=JSON.stringify(r)!==u;return f&&p(t,JSON.stringify(r,null,2)+`
16
+ `,"utf-8"),n.configured=!0,{client:n,action:f?"configured":"skipped",message:f?"token-firewall hooks installed (PreToolUse + PostToolUse: Read, Bash). Mode: observe (set WYRM_MEMO_MODE=warn to hand maps back)":"token-firewall hooks already installed"}}catch(s){return{client:n,action:"failed",message:s instanceof Error?s.message:String(s)}}}function $e(e={}){const t=k(e);if(!d(t))return null;const o=Y(t);try{let n;try{const r=JSON.parse(g(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");n=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return U(n,K)?(p(t,JSON.stringify(n,null,2)+`
17
+ `,"utf-8"),{client:o,action:"configured",message:"token-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-memo hooks installed"}}catch(n){return{client:o,action:"failed",message:n instanceof Error?n.message:String(n)}}}function Ee(e={}){const t=k(e),o=[];try{const s=JSON.parse(g(t,"utf-8")).hooks??{};for(const r of Object.values(s))if(Array.isArray(r))for(const i of r)for(const u of i?.hooks??[]){const c=u?.command;c&&K.test(c)&&o.push(c)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ge(e){const t={serverPath:e?.serverPath||M(),dbPath:e?.dbPath||D(),httpPort:e?.httpPort},o=a(h(),".wyrm");d(o)||P(o,{recursive:!0}),q(t);const n=A(),s=[];for(const c of n)s.push(H(c,t));const r=ce();r&&s.push(r);const i=fe();i&&s.push(i);const u=he();return u&&s.push(u),s}function Te(){const e=A(),t=[];for(const s of e)t.push(ie(s));const o=ae();o&&t.push(o);const n=me();return n&&t.push(n),t}function ye(e,t){const o={serverPath:t?.serverPath||M(),dbPath:t?.dbPath||D(),httpPort:t?.httpPort};q(o);const n=A(),s=[];for(const r of n)e.includes(r.id)&&s.push(H(r,o));return s}function q(e){const t=a(h(),".wyrm","wyrm-config.json"),o=A().filter(s=>s.configured).map(s=>s.id),n={version:"3.0.0",serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort,configuredClients:o,lastSetup:new Date().toISOString(),autoUpdate:!0};p(t,JSON.stringify(n,null,2)+`
18
+ `,"utf-8")}function V(){const e=a(h(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(g(e,"utf-8"))}catch{return null}}function We(){const e=V();return e?ye(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ge()}const Q="<!-- wyrm:start -->",F="<!-- wyrm:end -->",B=`${Q}
16
19
  ## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
17
20
 
18
21
  **Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
@@ -36,23 +39,23 @@ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyF
36
39
  **Keep wyrm-mcp current** \u2014 \`wyrm_check_update\` returns whether a newer version is on npm (cached 24h). When one is, surface it to the operator and offer \`wyrm_self_update\` (with \`confirm:true\`) to upgrade.
37
40
 
38
41
  **Buddy companion** \u2014 call \`wyrm_buddy\` for warm, data-grounded check-ins. At session start (after \`wyrm_session_prime\`) for long-running work, and when the operator completes a quest or hits a milestone. The buddy speaks in a chosen persona but every claim sources a real DB row \u2014 speak it verbatim, never invent additional encouragement. Auto-federates with other registered MCPs that expose \`*_buddy\` tools.
39
- ${K}`;function Ce(e,t){const o={injected:[],skipped:[],errors:[]},n=(r,i)=>{try{const u=w(r);if(d(u)||P(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(z),y=a.indexOf(K);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+K.length);l(r,`${h}${U}${g}`,"utf-8")}else{const h=a.endsWith(`
42
+ ${F}`;function Ne(e,t){const o={injected:[],skipped:[],errors:[]},n=(r,i)=>{try{const u=w(r);if(d(u)||P(u,{recursive:!0}),d(r)){const c=g(r,"utf-8"),f=c.indexOf(Q),m=c.indexOf(F);if(f!==-1&&m!==-1&&m>f){const l=c.slice(0,f),y=c.slice(m+F.length);p(r,`${l}${B}${y}`,"utf-8")}else{const l=c.endsWith(`
40
43
  `)?`
41
44
  `:`
42
45
 
43
- `;l(r,`${a}${h}${U}
44
- `,"utf-8")}}else l(r,`${U}
45
- `,"utf-8");o.injected.push(i)}catch(u){o.errors.push(`${i}: ${u}`)}},s=t.length===0?["copilot","cursor"]:t;for(const r of s){const i=r.toLowerCase();if(i==="copilot"||i==="vscode-copilot"){const u=c(e,".github","copilot-instructions.md");n(u,".github/copilot-instructions.md")}else if(i==="cursor"){const u=c(e,".cursor","rules");n(u,".cursor/rules")}else o.skipped.push(r)}return o}function $(e){let t="",o=!1;for(let n=0;n<e.length;n++){const s=e[n];if(o){if(t+=s,s==="\\"){t+=e[++n]??"";continue}s==='"'&&(o=!1);continue}if(s==='"'){o=!0,t+=s;continue}if(s==="/"&&e[n+1]==="/"){for(;n<e.length&&e[n]!==`
46
+ `;p(r,`${c}${l}${B}
47
+ `,"utf-8")}}else p(r,`${B}
48
+ `,"utf-8");o.injected.push(i)}catch(u){o.errors.push(`${i}: ${u}`)}},s=t.length===0?["copilot","cursor"]:t;for(const r of s){const i=r.toLowerCase();if(i==="copilot"||i==="vscode-copilot"){const u=a(e,".github","copilot-instructions.md");n(u,".github/copilot-instructions.md")}else if(i==="cursor"){const u=a(e,".cursor","rules");n(u,".cursor/rules")}else o.skipped.push(r)}return o}function E(e){let t="",o=!1;for(let n=0;n<e.length;n++){const s=e[n];if(o){if(t+=s,s==="\\"){t+=e[++n]??"";continue}s==='"'&&(o=!1);continue}if(s==='"'){o=!0,t+=s;continue}if(s==="/"&&e[n+1]==="/"){for(;n<e.length&&e[n]!==`
46
49
  `;)n++;t+=`
47
50
  `;continue}if(s==="/"&&e[n+1]==="*"){for(n+=2;n<e.length&&!(e[n]==="*"&&e[n+1]==="/");)n++;n+=1;continue}if(s===","){let r=n+1;for(;;){for(;r<e.length&&/\s/.test(e[r]);)r++;if(e[r]==="/"&&e[r+1]==="/"){for(;r<e.length&&e[r]!==`
48
- `;)r++;continue}if(e[r]==="/"&&e[r+1]==="*"){for(r+=2;r<e.length&&!(e[r]==="*"&&e[r+1]==="/");)r++;r+=2;continue}break}if(e[r]==="}"||e[r]==="]")continue}t+=s}return JSON.parse(t)}function _e(){const e=S(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let n=`\u{F115D} Wyrm Auto-Configure Status
51
+ `;)r++;continue}if(e[r]==="/"&&e[r+1]==="*"){for(r+=2;r<e.length&&!(e[r]==="*"&&e[r+1]==="/");)r++;r+=2;continue}break}if(e[r]==="}"||e[r]==="]")continue}t+=s}return JSON.parse(t)}function xe(){const e=A(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let n=`\u{F115D} Wyrm Auto-Configure Status
49
52
 
50
53
  `;n+=` Detected: ${t.length}/${e.length} AI clients
51
54
  `,n+=` Configured: ${o.length}/${t.length} clients
52
55
 
53
- `;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",u=r.version?` (${r.version})`:"",a=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";n+=`${i} ${r.icon} ${r.name}${u}: ${a}
54
- `}const s=G();return s&&(n+=`
56
+ `;for(const r of e){const i=r.detected?r.configured?" \u25CF":" \u25D0":" \u25CB",u=r.version?` (${r.version})`:"",c=r.detected?r.configured?"configured \u2713":"detected \u2014 not configured":"not found";n+=`${i} ${r.icon} ${r.name}${u}: ${c}
57
+ `}const s=V();return s&&(n+=`
55
58
  Server: ${s.serverPath}
56
59
  `,n+=` DB: ${s.dbPath}
57
60
  `,n+=` Last: ${new Date(s.lastSetup).toLocaleString()}
58
- `),n}export{se as GUARD_HOOK_TIMEOUT_SEC,U as WYRM_INJECT_BLOCK,ve as applyWyrmEnvToClient,de as autoConfigureAll,M as configureClient,fe as configureSpecific,S as detectClients,we as exportServerConfig,N as findWyrmServerPath,D as getDefaultDbPath,_e as getStatusSummary,Ce as injectSystemPrompt,ne as installClaudeCodeHooks,ke as installClaudeStatusline,ae as installWyrmGuardHooks,G as loadWyrmMeta,$ as parseJsonWithComments,B as quoteHookPath,je as reconfAll,re as removeClaudeCodeHooks,Pe as removeClaudeStatusline,be as removeFromAll,oe as removeFromClient,ue as removeWyrmGuardHooks,ce as resolveWyrmGuardCommand,Se as wyrmGuardHookStatus};
61
+ `),n}export{G as GUARD_HOOK_TIMEOUT_SEC,le as MEMO_HOOK_EVENTS,B as WYRM_INJECT_BLOCK,je as applyWyrmEnvToClient,ge as autoConfigureAll,H as configureClient,ye as configureSpecific,A as detectClients,Ae as exportServerConfig,M as findWyrmServerPath,D as getDefaultDbPath,xe as getStatusSummary,Ne as injectSystemPrompt,ce as installClaudeCodeHooks,Ce as installClaudeStatusline,fe as installWyrmGuardHooks,he as installWyrmMemoHooks,V as loadWyrmMeta,E as parseJsonWithComments,$ as quoteHookPath,We as reconfAll,ae as removeClaudeCodeHooks,_e as removeClaudeStatusline,Te as removeFromAll,ie as removeFromClient,me as removeWyrmGuardHooks,$e as removeWyrmMemoHooks,de as resolveWyrmGuardCommand,pe as resolveWyrmMemoCommand,Oe as wyrmGuardHookStatus,Ee as wyrmMemoHookStatus};
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{Server as fe}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as de}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as ge,GetPromptRequestSchema as he,ListPromptsRequestSchema as we,ListResourcesRequestSchema as Se,ListResourceTemplatesRequestSchema as Re,ListToolsRequestSchema as ve,ReadResourceRequestSchema as ke}from"@modelcontextprotocol/sdk/types.js";import{WyrmDB as be,resolveDbPath as Ee}from"./database.js";import{ValidationError as U}from"./validate.js";import{validationErrorResponse as q,cacheKeyFor as De}from"./render.js";import{handlerRegistry as h,registryToolDefs as Ce,applyDeclaredOutputSchemas as Te,buildStableToolList as Oe}from"./handlers/registry.js";import{resolveActorEnvelope as V,runWithActor as J,getActor as xe,runWithReadCacheKey as Me}from"./handlers/boundary.js";import{resolveAliasCall as We}from"./handlers/aliases.js";import{resolveShimCall as Pe,shimToolDefs as Ae}from"./handlers/shims.js";import{getPromptResult as Ie,wyrmPromptList as Ne}from"./handlers/prompts.js";import{listWyrmResources as Le,listWyrmResourceTemplates as Ye,readWyrmResource as je,ResourceNotFoundError as Fe}from"./handlers/resources.js";import{TaskStore as Be,resolveTaskGate as Ke,runLongOp as Ge}from"./tasks.js";import{registerTaskHandlers as He,finalizeEarly as Ue,unwrapOutcome as qe,applyAdminGate as Ve}from"./tasks-dispatch.js";import{ReplicationManager as Je}from"./replication-daemon.js";import{WYRM_SERVER_INSTRUCTIONS as $e}from"./priority-embed.js";import{WyrmSync as ze}from"./sync.js";import{cache as w,estimateTokens as W}from"./performance.js";import{readFileSync as $,unlinkSync as Qe,existsSync as z}from"fs";import{join as Xe}from"path";import{initializeOrchestrator as Ze}from"./auto-orchestrator.js";import{getCrypto as et,initializeCrypto as tt}from"./crypto.js";import{initVectorSearch as rt}from"./vector-init.js";import{KnowledgeGraph as ot}from"./knowledge-graph.js";import{MemoryArtifacts as st}from"./memory-artifacts.js";import{GroundTruths as nt,ReasoningScaffolds as at}from"./intelligence.js";import{classifyCapture as $o}from"./capture.js";import{initializeLicense as it,getLicenseInfo as ct}from"./license.js";import{refreshRevocations as Q}from"./revocations.js";import{refreshLicenseIfNeeded as X}from"./license-refresh.js";import{activationSatisfied as mt,activationGateResult as lt}from"./activation.js";import{WyrmAnalytics as pt}from"./analytics.js";import{WyrmCloudBackup as _t}from"./cloud-backup.js";import{CloudSyncManager as ut}from"./cloud-sync.js";import{FailurePatterns as yt}from"./failure-patterns.js";import{AgentPresence as ft}from"./presence.js";import{Causality as dt}from"./causality.js";import{wireTruthAutoCascade as gt}from"./truth-cascade.js";import{SymbolGraph as ht}from"./symbols.js";import{HourLedger as wt}from"./hours.js";import{ToolAnalytics as St}from"./tool-analytics.js";import{Rehydration as Rt}from"./rehydration.js";import{AuditLog as vt,loadAuditSignerFromEnv as kt}from"./audit.js";import{retryableWriteCause as bt,busyErrorResponse as Et}from"./sqlite-busy.js";import{SubAgent as Dt}from"./sub-agent.js";import{Federation as Ct}from"./federation.js";import{Goals as Tt}from"./goals.js";import{OutboundMcpClient as Ot}from"./mcp-client.js";import{AgentLoop as xt}from"./agent-loop.js";import{makeInternalDispatch as Mt}from"./internal-dispatch.js";import{createUsageTracker as Wt}from"./usage-tracker.js";import{AgentDaemon as Pt}from"./agent-daemon.js";import{emitStartupVersionBanner as At}from"./version-check.js";import{resolveProfile as It,filterByProfile as Nt}from"./tool-profiles.js";import{annotateTools as Lt,elideDefaultAnnotations as Yt}from"./tool-annotations.js";import{applyDeprecationNotices as jt,noteDeprecatedToolInvocation as Ft,isHardCliExile as Bt,cliExileRedirectResponse as Kt}from"./deprecations.js";import{goldenCapture as f}from"./golden.js";import{makeEstimator as Gt}from"./token-budget.js";import{makeRunBudgetedContextBuild as Ht}from"./context-build-budgeted.js";import{assembleThread as Ut}from"./thread-assemble.js";import{getThread as qt}from"./thread-store.js";import{renderThreadForContext as Z}from"./handlers/thread.js";import{makeRunCompanionBuddy as Vt}from"./buddy-runner.js";import{loadWeightsFromEnv as Jt}from"./context-ranking.js";import{SessionSeen as $t}from"./session-seen.js";import{recordServed as zt}from"./served-records.js";import{DesignSystem as Qt}from"./design.js";import{fileURLToPath as Xt}from"url";import{dirname as Zt,join as ee}from"path";import{readFileSync as er,existsSync as tr}from"fs";const rr=137;let P=null;function or(){return P||(P=new Qt(r.getDatabase())),P}const sr=Gt(),nr=Jt();let A=null;function te(){return A||(A=new $t(r.getDatabase())),A}const C=(()=>{try{const e=Zt(Xt(import.meta.url));for(const t of[ee(e,"..","package.json"),ee(e,"..","..","package.json")])if(tr(t)){const s=JSON.parse(er(t,"utf-8")).version;if(typeof s=="string")return s}}catch{}return"unknown"})(),r=new be(Ee()),ar=new ze(r),re=new ot(r.getDatabase()),R=new st(r.getDatabase()),v=new nt(r.getDatabase()),I=new at(r.getDatabase()),N=new yt(r.getDatabase()),L=new ft(r.getDatabase()),Y=new dt(r.getDatabase());gt(v,Y);const j=new ht(r.getDatabase()),ir=new wt(r.getDatabase()),k=new St(r.getDatabase()),cr=Ht({groundTruths:v,scaffoldLib:I,memory:R,toolAnalytics:k,sessionSeen:te,tokenEstimator:sr,rankWeights:nr,threadText:e=>{try{return Z(Ut(r.getDatabase(),e))}catch{try{const t=qt(r.getDatabase(),e);return t?Z(t):""}catch{return""}}}}),oe=new Rt(r.getDatabase()),mr=new vt(r.getDatabase(),kt()),lr=new Dt(r.getDatabase()),se=new Ct(r.getDatabase()),ne=new Tt(r.getDatabase()),ae={projects:r,goals:ne,getActor:xe,store:r,raw:()=>r.getDatabase(),memory:R,truths:v,scaffolds:I,failures:N,presence:L,rehydration:oe,graph:re,symbols:j,vectors:()=>T,indexing:()=>K,cache:w,decrypt:e=>B.maybeDecrypt(e),clientSupportsResources:()=>{const e=c.getClientCapabilities()?.experimental;return!!e&&"resources"in e}},F=new Ot(r.getDatabase()),pr=Vt({db:r,mcpClient:F,version:C}),_r=new Pt(r.getDatabase()),ur=Mt({db:r,failures:N,symbols:j,rehydration:oe,causality:Y,presence:L,federation:se,memory:R,groundTruths:v}),yr=new xt(r.getDatabase(),F,ur);if(process.env.WYRM_ENCRYPTION_KEY)try{tt(process.env.WYRM_ENCRYPTION_KEY)}catch(e){console.error("Wyrm: Failed to initialize encryption:",e)}const B=et();let T=null,K=null;{const e=rt(r,R,B);T=e.vectorStore,K=e.indexingPipeline}const fr=Ze({autoOrchestrateEnabled:!0,minConfidenceThreshold:65,maxParallelAgents:6,defaultHaikuBoosting:!0,trackMetrics:!0});it();const O=ct();O.tier!=="free"&&console.error(`Wyrm: Licensed \u2014 ${O.tier} tier (expires: ${O.expiresAt||"never"})`);const dr=!!O.key&&!/^(0|false|no|off)$/i.test(process.env.WYRM_LICENSE_REFRESH??"");dr&&(Promise.all([Q(),X()]).catch(()=>{}),setInterval(()=>{Promise.all([Q(),X()]).catch(()=>{})},1440*60*1e3).unref?.());try{const e=process.env.HOME||process.env.USERPROFILE||"",t=Xe(e,".wyrm","bundled-skills.json");if(e&&z(t)){const s=JSON.parse($(t,"utf-8"));let _=0;for(const i of s.skills)try{if(!z(i.path))continue;const m=$(i.path,"utf-8").match(/^---\s*\n([\s\S]*?)\n---/);let a=i.name,u;if(m){const g=m[1].match(/description:\s*\|?\s*\n([\s\S]*?)(?:\nname:|\nmetadata:|\ncategory:|\ntype:|$)/);g&&(a=g[1].split(`
3
- `).map(E=>E.trim()).filter(Boolean).join(" ").slice(0,500));const S=m[1].match(/category:\s*([^\n]+)/);S&&(u=S[1].trim())}r.registerSkill(i.name,a,i.path,u,"Ghost Protocol",void 0,"bundled"),_++}catch{}_>0&&console.error(`Wyrm: Registered ${_} bundled skill(s) on first start.`);try{Qe(t)}catch{}}}catch{}const G=new pt(r.getDatabase()),ie=new _t;process.env.WYRM_R2_ENDPOINT&&process.env.WYRM_R2_ACCESS_KEY&&process.env.WYRM_R2_SECRET_KEY&&ie.configure({endpoint:process.env.WYRM_R2_ENDPOINT,bucket:process.env.WYRM_R2_BUCKET||"wyrm-backups",accessKeyId:process.env.WYRM_R2_ACCESS_KEY,secretAccessKey:process.env.WYRM_R2_SECRET_KEY},process.env.WYRM_ENCRYPTION_KEY);import{fileURLToPath as gr}from"url";import{dirname as hr,join as ce}from"path";const wr=gr(import.meta.url),me=hr(wr),Sr=new ut(ce(me,"cloud-sync-entrypoint.js")),Rr=new Je(ce(me,"replication-daemon-entrypoint.js")),{trackUsage:d,getUsageStats:vr,usageLog:kr}=Wt(G),br=new Map;function Er(e,t=!1){return{content:[{type:"text",text:e}]}}const Dr=new Set(["wyrm_list_projects","wyrm_project_context","wyrm_global_context","wyrm_all_quests","wyrm_data_query","wyrm_data_categories","wyrm_search","wyrm_stats","wyrm_skill_list","wyrm_skill_get","wyrm_skill_search","wyrm_skill_stats","wyrm_orchestration_config","wyrm_orchestration_stats","wyrm_license","wyrm_analytics_dashboard","wyrm_cost_report","wyrm_entity_search","wyrm_entity_graph","wyrm_entity_path","wyrm_truth_get","wyrm_scaffold_get","wyrm_session_prime"]);function Cr(e,t){return!(!Dr.has(e)||e==="wyrm_session_prime"&&(t?.run_id===void 0||t?.run_id===null))}const le=new Set(["wyrm_session_start","wyrm_session_update","wyrm_quest_add","wyrm_quest_complete","wyrm_data_insert","wyrm_data_batch_insert","wyrm_set_global","wyrm_scan_projects","wyrm_sync","wyrm_maintenance","wyrm_vector_setup","wyrm_reindex","wyrm_skill_register","wyrm_skill_delete","wyrm_skill_activate","wyrm_skill_deactivate","wyrm_spec_register","wyrm_activate","wyrm_cloud_backup","wyrm_cloud_sync","wyrm_encrypt_setup","wyrm_entity_add","wyrm_entity_link","wyrm_entity_merge","wyrm_remember","wyrm_recall","wyrm_feedback","wyrm_truth_set","wyrm_scaffold_save","wyrm_distill","wyrm_review","wyrm_capture","wyrm_import_git","wyrm_import_pr","wyrm_import_rules","wyrm_inject_prompt","wyrm_prune","wyrm_sync_export","wyrm_sync_import"]),b=new Be,c=new fe({name:"wyrm",version:C},{capabilities:{tools:{},prompts:{},resources:{},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}},instructions:$e}),pe={...ae,db:r,sync:ar,graph:re,memory:R,groundTruths:v,scaffoldLib:I,failures:N,presence:L,causality:Y,symbols:j,hours:ir,toolAnalytics:k,auditLog:mr,subAgent:lr,federation:se,goals:ne,mcpClient:F,agentDaemon:_r,agentLoop:yr,analytics:G,cloudBackup:ie,cloudSyncManager:Sr,replicationManager:Rr,orchestrator:fr,server:c,wyrmCrypto:B,get vectorStore(){return T},get indexingPipeline(){return K},setVectorStore:e=>{T=e},WYRM_PACKAGE_VERSION:C,WYRM_TOOL_COUNT:rr,WRITE_TOOLS:le,usageLog:kr,responseFingerprints:br,designSystem:or,sessionSeen:te,cachedResponse:Er,getUsageStats:vr,runCompanionBuddy:pr,runBudgetedContextBuild:cr};c.setRequestHandler(ve,async()=>{const e=[...Ce(),...Ae()],t=It();return{tools:Oe(Te(jt(Yt(Lt(Nt(e,t))))))}}),c.setRequestHandler(we,async()=>({prompts:[...Ne()]})),c.setRequestHandler(he,async e=>{const t=e.params.arguments??{},s=V({args:t,meta:e.params._meta,clientName:c.getClientVersion()?.name});return J(s,()=>Ie(e.params.name,t,ae))}),c.setRequestHandler(Se,async()=>({resources:Le()})),c.setRequestHandler(Re,async()=>({resourceTemplates:Ye()})),c.setRequestHandler(ke,async e=>{const t=e.params.uri;try{const s=je(t,pe),{nextCursor:_,...i}=s;return _?{contents:[i],_meta:{"wyrm/nextCursor":_}}:{contents:[i]}}catch(s){throw s instanceof Fe?new Error(s.message):s}}),He(c,b),c.setRequestHandler(ge,async e=>{const{name:t,arguments:s}=e.params;if(!mt())return lt();const _=V({args:s??{},meta:e.params._meta,clientName:c.getClientVersion()?.name});Ft(t);const i=performance.now(),x=JSON.stringify(s||{}),m=W(x);let a,u,g=null;try{const o=We(t,s??{});o.via?.kind==="cli-exile"&&(g=o.via),{tool:a,args:u}=Pe(o.tool,o.args)}catch(o){if(o instanceof U){const n=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()});const l=q(t,o.field,o.message);return f({tool:t,args:s,meta:e.params._meta,response:l,outcome:"validation_error",ms:n,dispatch:"switch"}),l}throw o}if(g&&Bt(g.name)){const o=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:o,timestamp:new Date().toISOString()});const n=Kt(g.name);return f({tool:t,args:s,meta:e.params._meta,response:n,outcome:"soft_error",ms:o,dispatch:"switch"}),n}const S=Ke(c.getClientCapabilities()?.tasks,e.params),E=Ve(b,S,a,o=>{const n=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),f({tool:t,args:s,meta:e.params._meta,response:o,outcome:"soft_error",ms:n,dispatch:h.has(a)?"registry":"switch"})});if(E!==null)return E;const _e=a===t?x:JSON.stringify(u),M=Cr(a,u??{})?De(a,_e):null;if(M){const o=w.get(M);if(o){const n=Math.round(performance.now()-i),l=W(JSON.stringify(o));d({tool:t,tokens_in:m,tokens_out:l,cached:!0,ms:n,timestamp:new Date().toISOString()});const p={...o};return f({tool:t,args:s,meta:e.params._meta,response:p,cached:!0,ms:n,dispatch:h.has(a)?"registry":"switch"}),Ue(b,S,a,p)}}if(le.has(a)){const o=u?.projectPath;o&&w.invalidate(o),w.invalidate("wyrm_list_projects"),w.invalidate("wyrm_global_context"),w.invalidate("wyrm_all_quests"),w.invalidate("wyrm_stats")}let D;const ue=u||{};try{const o=await J(_,async()=>{const H=h.get(a);return H?await Ge(a,S,b,()=>Me(M,()=>H(ue,pe))):{kind:"inline",result:{content:[{type:"text",text:`Unknown tool: ${a}`}],isError:!0}}}),{wireValue:n,payload:l}=qe(b,o);D=l;const p=Math.round(performance.now()-i),y=JSON.stringify(D),ye=W(y);return zt(r.getDatabase(),y),d({tool:t,tokens_in:m,tokens_out:ye,cached:!1,ms:p,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!D?.isError,latency_ms:p}),f({tool:t,args:s,meta:e.params._meta,response:D,ms:p,dispatch:h.has(a)?"registry":"switch"}),n}catch(o){const n=Math.round(performance.now()-i);if(o instanceof U){d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()});const y=q(t,o.field,o.message);return f({tool:t,args:s,meta:e.params._meta,response:y,outcome:"validation_error",ms:n,dispatch:h.has(a)?"registry":"switch"}),y}const l=bt(o);if(l!==null){d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!1,error_message:`WYRM_BUSY[${l}]: ${String(o).slice(0,400)}`,latency_ms:n});const y=Et(t,l);return f({tool:t,args:s,meta:e.params._meta,response:y,outcome:"error",ms:n,dispatch:h.has(a)?"registry":"switch"}),y}d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!1,error_message:String(o).slice(0,500),latency_ms:n});const p={content:[{type:"text",text:`Error: ${o}`}],isError:!0};return f({tool:t,args:s,meta:e.params._meta,response:p,outcome:"error",ms:n,dispatch:h.has(a)?"registry":"switch"}),p}}),process.on("uncaughtException",e=>{try{console.error("Wyrm[uncaughtException]:",e instanceof Error?e.stack:e)}catch{}}),process.on("unhandledRejection",e=>{try{console.error("Wyrm[unhandledRejection]:",e instanceof Error?e.stack:e)}catch{}});async function Tr(){const e=new de;await c.connect(e),At(r.getDatabase(),C).catch(()=>{});const t=()=>{try{G.shutdown()}catch{}process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t)}Tr().catch(console.error);export{$o as classifyCapture};
2
+ import{Server as fe}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as de}from"@modelcontextprotocol/sdk/server/stdio.js";import{scheduleMaintenanceSweep as ge}from"./self-maintenance.js";import{CallToolRequestSchema as he,GetPromptRequestSchema as we,ListPromptsRequestSchema as Se,ListResourcesRequestSchema as Re,ListResourceTemplatesRequestSchema as ve,ListToolsRequestSchema as ke,ReadResourceRequestSchema as be}from"@modelcontextprotocol/sdk/types.js";import{WyrmDB as Ee,resolveDbPath as De}from"./database.js";import{ValidationError as U}from"./validate.js";import{validationErrorResponse as q,cacheKeyFor as Ce}from"./render.js";import{handlerRegistry as h,registryToolDefs as Te,applyDeclaredOutputSchemas as Oe,buildStableToolList as xe}from"./handlers/registry.js";import{resolveActorEnvelope as V,runWithActor as J,getActor as Me,runWithReadCacheKey as We}from"./handlers/boundary.js";import{resolveAliasCall as Pe}from"./handlers/aliases.js";import{resolveShimCall as Ae,shimToolDefs as Ie}from"./handlers/shims.js";import{getPromptResult as Ne,wyrmPromptList as Le}from"./handlers/prompts.js";import{listWyrmResources as Ye,listWyrmResourceTemplates as je,readWyrmResource as Fe,ResourceNotFoundError as Be}from"./handlers/resources.js";import{TaskStore as Ke,resolveTaskGate as Ge,runLongOp as He}from"./tasks.js";import{registerTaskHandlers as Ue,finalizeEarly as qe,unwrapOutcome as Ve,applyAdminGate as Je}from"./tasks-dispatch.js";import{ReplicationManager as $e}from"./replication-daemon.js";import{WYRM_SERVER_INSTRUCTIONS as ze}from"./priority-embed.js";import{WyrmSync as Qe}from"./sync.js";import{cache as w,estimateTokens as W}from"./performance.js";import{readFileSync as $,unlinkSync as Xe,existsSync as z}from"fs";import{join as Ze}from"path";import{initializeOrchestrator as et}from"./auto-orchestrator.js";import{getCrypto as tt,initializeCrypto as rt}from"./crypto.js";import{initVectorSearch as ot}from"./vector-init.js";import{KnowledgeGraph as st}from"./knowledge-graph.js";import{MemoryArtifacts as nt}from"./memory-artifacts.js";import{GroundTruths as at,ReasoningScaffolds as it}from"./intelligence.js";import{classifyCapture as Qo}from"./capture.js";import{initializeLicense as ct,getLicenseInfo as mt}from"./license.js";import{refreshRevocations as Q}from"./revocations.js";import{refreshLicenseIfNeeded as X}from"./license-refresh.js";import{activationSatisfied as lt,activationGateResult as pt}from"./activation.js";import{WyrmAnalytics as _t}from"./analytics.js";import{WyrmCloudBackup as ut}from"./cloud-backup.js";import{CloudSyncManager as yt}from"./cloud-sync.js";import{FailurePatterns as ft}from"./failure-patterns.js";import{AgentPresence as dt}from"./presence.js";import{Causality as gt}from"./causality.js";import{wireTruthAutoCascade as ht}from"./truth-cascade.js";import{SymbolGraph as wt}from"./symbols.js";import{HourLedger as St}from"./hours.js";import{ToolAnalytics as Rt}from"./tool-analytics.js";import{Rehydration as vt}from"./rehydration.js";import{AuditLog as kt,loadAuditSignerFromEnv as bt}from"./audit.js";import{retryableWriteCause as Et,busyErrorResponse as Dt}from"./sqlite-busy.js";import{SubAgent as Ct}from"./sub-agent.js";import{Federation as Tt}from"./federation.js";import{Goals as Ot}from"./goals.js";import{OutboundMcpClient as xt}from"./mcp-client.js";import{AgentLoop as Mt}from"./agent-loop.js";import{makeInternalDispatch as Wt}from"./internal-dispatch.js";import{createUsageTracker as Pt}from"./usage-tracker.js";import{AgentDaemon as At}from"./agent-daemon.js";import{emitStartupVersionBanner as It}from"./version-check.js";import{resolveProfile as Nt,filterByProfile as Lt}from"./tool-profiles.js";import{annotateTools as Yt,elideDefaultAnnotations as jt}from"./tool-annotations.js";import{applyDeprecationNotices as Ft,noteDeprecatedToolInvocation as Bt,isHardCliExile as Kt,cliExileRedirectResponse as Gt}from"./deprecations.js";import{goldenCapture as f}from"./golden.js";import{makeEstimator as Ht}from"./token-budget.js";import{makeRunBudgetedContextBuild as Ut}from"./context-build-budgeted.js";import{assembleThread as qt}from"./thread-assemble.js";import{getThread as Vt}from"./thread-store.js";import{renderThreadForContext as Z}from"./handlers/thread.js";import{makeRunCompanionBuddy as Jt}from"./buddy-runner.js";import{loadWeightsFromEnv as $t}from"./context-ranking.js";import{SessionSeen as zt}from"./session-seen.js";import{recordServed as Qt}from"./served-records.js";import{DesignSystem as Xt}from"./design.js";import{fileURLToPath as Zt}from"url";import{dirname as er,join as ee}from"path";import{readFileSync as tr,existsSync as rr}from"fs";const or=137;let P=null;function sr(){return P||(P=new Xt(r.getDatabase())),P}const nr=Ht(),ar=$t();let A=null;function te(){return A||(A=new zt(r.getDatabase())),A}const C=(()=>{try{const e=er(Zt(import.meta.url));for(const t of[ee(e,"..","package.json"),ee(e,"..","..","package.json")])if(rr(t)){const s=JSON.parse(tr(t,"utf-8")).version;if(typeof s=="string")return s}}catch{}return"unknown"})(),r=new Ee(De()),ir=new Qe(r),re=new st(r.getDatabase()),R=new nt(r.getDatabase()),v=new at(r.getDatabase()),I=new it(r.getDatabase()),N=new ft(r.getDatabase()),L=new dt(r.getDatabase()),Y=new gt(r.getDatabase());ht(v,Y);const j=new wt(r.getDatabase()),cr=new St(r.getDatabase()),k=new Rt(r.getDatabase()),mr=Ut({groundTruths:v,scaffoldLib:I,memory:R,toolAnalytics:k,sessionSeen:te,tokenEstimator:nr,rankWeights:ar,threadText:e=>{try{return Z(qt(r.getDatabase(),e))}catch{try{const t=Vt(r.getDatabase(),e);return t?Z(t):""}catch{return""}}}}),oe=new vt(r.getDatabase()),lr=new kt(r.getDatabase(),bt()),pr=new Ct(r.getDatabase()),se=new Tt(r.getDatabase()),ne=new Ot(r.getDatabase()),ae={projects:r,goals:ne,getActor:Me,store:r,raw:()=>r.getDatabase(),memory:R,truths:v,scaffolds:I,failures:N,presence:L,rehydration:oe,graph:re,symbols:j,vectors:()=>T,indexing:()=>K,cache:w,decrypt:e=>B.maybeDecrypt(e),clientSupportsResources:()=>{const e=c.getClientCapabilities()?.experimental;return!!e&&"resources"in e}},F=new xt(r.getDatabase()),_r=Jt({db:r,mcpClient:F,version:C}),ur=new At(r.getDatabase()),yr=Wt({db:r,failures:N,symbols:j,rehydration:oe,causality:Y,presence:L,federation:se,memory:R,groundTruths:v}),fr=new Mt(r.getDatabase(),F,yr);if(process.env.WYRM_ENCRYPTION_KEY)try{rt(process.env.WYRM_ENCRYPTION_KEY)}catch(e){console.error("Wyrm: Failed to initialize encryption:",e)}const B=tt();let T=null,K=null;{const e=ot(r,R,B);T=e.vectorStore,K=e.indexingPipeline}const dr=et({autoOrchestrateEnabled:!0,minConfidenceThreshold:65,maxParallelAgents:6,defaultHaikuBoosting:!0,trackMetrics:!0});ct();const O=mt();O.tier!=="free"&&console.error(`Wyrm: Licensed \u2014 ${O.tier} tier (expires: ${O.expiresAt||"never"})`);const gr=!!O.key&&!/^(0|false|no|off)$/i.test(process.env.WYRM_LICENSE_REFRESH??"");gr&&(Promise.all([Q(),X()]).catch(()=>{}),setInterval(()=>{Promise.all([Q(),X()]).catch(()=>{})},1440*60*1e3).unref?.());try{const e=process.env.HOME||process.env.USERPROFILE||"",t=Ze(e,".wyrm","bundled-skills.json");if(e&&z(t)){const s=JSON.parse($(t,"utf-8"));let _=0;for(const i of s.skills)try{if(!z(i.path))continue;const m=$(i.path,"utf-8").match(/^---\s*\n([\s\S]*?)\n---/);let a=i.name,u;if(m){const g=m[1].match(/description:\s*\|?\s*\n([\s\S]*?)(?:\nname:|\nmetadata:|\ncategory:|\ntype:|$)/);g&&(a=g[1].split(`
3
+ `).map(E=>E.trim()).filter(Boolean).join(" ").slice(0,500));const S=m[1].match(/category:\s*([^\n]+)/);S&&(u=S[1].trim())}r.registerSkill(i.name,a,i.path,u,"Ghost Protocol",void 0,"bundled"),_++}catch{}_>0&&console.error(`Wyrm: Registered ${_} bundled skill(s) on first start.`);try{Xe(t)}catch{}}}catch{}const G=new _t(r.getDatabase()),ie=new ut;process.env.WYRM_R2_ENDPOINT&&process.env.WYRM_R2_ACCESS_KEY&&process.env.WYRM_R2_SECRET_KEY&&ie.configure({endpoint:process.env.WYRM_R2_ENDPOINT,bucket:process.env.WYRM_R2_BUCKET||"wyrm-backups",accessKeyId:process.env.WYRM_R2_ACCESS_KEY,secretAccessKey:process.env.WYRM_R2_SECRET_KEY},process.env.WYRM_ENCRYPTION_KEY);import{fileURLToPath as hr}from"url";import{dirname as wr,join as ce}from"path";const Sr=hr(import.meta.url),me=wr(Sr),Rr=new yt(ce(me,"cloud-sync-entrypoint.js")),vr=new $e(ce(me,"replication-daemon-entrypoint.js")),{trackUsage:d,getUsageStats:kr,usageLog:br}=Pt(G),Er=new Map;function Dr(e,t=!1){return{content:[{type:"text",text:e}]}}const Cr=new Set(["wyrm_list_projects","wyrm_project_context","wyrm_global_context","wyrm_all_quests","wyrm_data_query","wyrm_data_categories","wyrm_search","wyrm_stats","wyrm_skill_list","wyrm_skill_get","wyrm_skill_search","wyrm_skill_stats","wyrm_orchestration_config","wyrm_orchestration_stats","wyrm_license","wyrm_analytics_dashboard","wyrm_cost_report","wyrm_entity_search","wyrm_entity_graph","wyrm_entity_path","wyrm_truth_get","wyrm_scaffold_get","wyrm_session_prime"]);function Tr(e,t){return!(!Cr.has(e)||e==="wyrm_session_prime"&&(t?.run_id===void 0||t?.run_id===null))}const le=new Set(["wyrm_session_start","wyrm_session_update","wyrm_quest_add","wyrm_quest_complete","wyrm_data_insert","wyrm_data_batch_insert","wyrm_set_global","wyrm_scan_projects","wyrm_sync","wyrm_maintenance","wyrm_vector_setup","wyrm_reindex","wyrm_skill_register","wyrm_skill_delete","wyrm_skill_activate","wyrm_skill_deactivate","wyrm_spec_register","wyrm_activate","wyrm_cloud_backup","wyrm_cloud_sync","wyrm_encrypt_setup","wyrm_entity_add","wyrm_entity_link","wyrm_entity_merge","wyrm_remember","wyrm_recall","wyrm_feedback","wyrm_truth_set","wyrm_scaffold_save","wyrm_distill","wyrm_review","wyrm_capture","wyrm_import_git","wyrm_import_pr","wyrm_import_rules","wyrm_inject_prompt","wyrm_prune","wyrm_sync_export","wyrm_sync_import"]),b=new Ke,c=new fe({name:"wyrm",version:C},{capabilities:{tools:{},prompts:{},resources:{},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}},instructions:ze}),pe={...ae,db:r,sync:ir,graph:re,memory:R,groundTruths:v,scaffoldLib:I,failures:N,presence:L,causality:Y,symbols:j,hours:cr,toolAnalytics:k,auditLog:lr,subAgent:pr,federation:se,goals:ne,mcpClient:F,agentDaemon:ur,agentLoop:fr,analytics:G,cloudBackup:ie,cloudSyncManager:Rr,replicationManager:vr,orchestrator:dr,server:c,wyrmCrypto:B,get vectorStore(){return T},get indexingPipeline(){return K},setVectorStore:e=>{T=e},WYRM_PACKAGE_VERSION:C,WYRM_TOOL_COUNT:or,WRITE_TOOLS:le,usageLog:br,responseFingerprints:Er,designSystem:sr,sessionSeen:te,cachedResponse:Dr,getUsageStats:kr,runCompanionBuddy:_r,runBudgetedContextBuild:mr};c.setRequestHandler(ke,async()=>{const e=[...Te(),...Ie()],t=Nt();return{tools:xe(Oe(Ft(jt(Yt(Lt(e,t))))))}}),c.setRequestHandler(Se,async()=>({prompts:[...Le()]})),c.setRequestHandler(we,async e=>{const t=e.params.arguments??{},s=V({args:t,meta:e.params._meta,clientName:c.getClientVersion()?.name});return J(s,()=>Ne(e.params.name,t,ae))}),c.setRequestHandler(Re,async()=>({resources:Ye()})),c.setRequestHandler(ve,async()=>({resourceTemplates:je()})),c.setRequestHandler(be,async e=>{const t=e.params.uri;try{const s=Fe(t,pe),{nextCursor:_,...i}=s;return _?{contents:[i],_meta:{"wyrm/nextCursor":_}}:{contents:[i]}}catch(s){throw s instanceof Be?new Error(s.message):s}}),Ue(c,b),c.setRequestHandler(he,async e=>{const{name:t,arguments:s}=e.params;if(!lt())return pt();const _=V({args:s??{},meta:e.params._meta,clientName:c.getClientVersion()?.name});Bt(t);const i=performance.now(),x=JSON.stringify(s||{}),m=W(x);let a,u,g=null;try{const o=Pe(t,s??{});o.via?.kind==="cli-exile"&&(g=o.via),{tool:a,args:u}=Ae(o.tool,o.args)}catch(o){if(o instanceof U){const n=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()});const l=q(t,o.field,o.message);return f({tool:t,args:s,meta:e.params._meta,response:l,outcome:"validation_error",ms:n,dispatch:"switch"}),l}throw o}if(g&&Kt(g.name)){const o=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:o,timestamp:new Date().toISOString()});const n=Gt(g.name);return f({tool:t,args:s,meta:e.params._meta,response:n,outcome:"soft_error",ms:o,dispatch:"switch"}),n}const S=Ge(c.getClientCapabilities()?.tasks,e.params),E=Je(b,S,a,o=>{const n=Math.round(performance.now()-i);d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),f({tool:t,args:s,meta:e.params._meta,response:o,outcome:"soft_error",ms:n,dispatch:h.has(a)?"registry":"switch"})});if(E!==null)return E;const _e=a===t?x:JSON.stringify(u),M=Tr(a,u??{})?Ce(a,_e):null;if(M){const o=w.get(M);if(o){const n=Math.round(performance.now()-i),l=W(JSON.stringify(o));d({tool:t,tokens_in:m,tokens_out:l,cached:!0,ms:n,timestamp:new Date().toISOString()});const p={...o};return f({tool:t,args:s,meta:e.params._meta,response:p,cached:!0,ms:n,dispatch:h.has(a)?"registry":"switch"}),qe(b,S,a,p)}}if(le.has(a)){const o=u?.projectPath;o&&w.invalidate(o),w.invalidate("wyrm_list_projects"),w.invalidate("wyrm_global_context"),w.invalidate("wyrm_all_quests"),w.invalidate("wyrm_stats")}let D;const ue=u||{};try{const o=await J(_,async()=>{const H=h.get(a);return H?await He(a,S,b,()=>We(M,()=>H(ue,pe))):{kind:"inline",result:{content:[{type:"text",text:`Unknown tool: ${a}`}],isError:!0}}}),{wireValue:n,payload:l}=Ve(b,o);D=l;const p=Math.round(performance.now()-i),y=JSON.stringify(D),ye=W(y);return Qt(r.getDatabase(),y),d({tool:t,tokens_in:m,tokens_out:ye,cached:!1,ms:p,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!D?.isError,latency_ms:p}),f({tool:t,args:s,meta:e.params._meta,response:D,ms:p,dispatch:h.has(a)?"registry":"switch"}),n}catch(o){const n=Math.round(performance.now()-i);if(o instanceof U){d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()});const y=q(t,o.field,o.message);return f({tool:t,args:s,meta:e.params._meta,response:y,outcome:"validation_error",ms:n,dispatch:h.has(a)?"registry":"switch"}),y}const l=Et(o);if(l!==null){d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!1,error_message:`WYRM_BUSY[${l}]: ${String(o).slice(0,400)}`,latency_ms:n});const y=Dt(t,l);return f({tool:t,args:s,meta:e.params._meta,response:y,outcome:"error",ms:n,dispatch:h.has(a)?"registry":"switch"}),y}d({tool:t,tokens_in:m,tokens_out:0,cached:!1,ms:n,timestamp:new Date().toISOString()}),k.log({tool_name:t,args:s,success:!1,error_message:String(o).slice(0,500),latency_ms:n});const p={content:[{type:"text",text:`Error: ${o}`}],isError:!0};return f({tool:t,args:s,meta:e.params._meta,response:p,outcome:"error",ms:n,dispatch:h.has(a)?"registry":"switch"}),p}}),process.on("uncaughtException",e=>{try{console.error("Wyrm[uncaughtException]:",e instanceof Error?e.stack:e)}catch{}}),process.on("unhandledRejection",e=>{try{console.error("Wyrm[unhandledRejection]:",e instanceof Error?e.stack:e)}catch{}});async function Or(){const e=new de;await c.connect(e),ge(r),It(r.getDatabase(),C).catch(()=>{});const t=()=>{try{G.shutdown()}catch{}process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t)}Or().catch(console.error);export{Qo as classifyCapture};
@@ -1,4 +1,4 @@
1
- import{pruneWriteLedger as _}from"./receipts.js";import{metabolizeEnabled as E,metabolizeDefaultEnabled as h,runMetabolize as f}from"./metabolize.js";import{ServedRecords as l}from"./served-records.js";function y(c,o={}){const{db:n,sessionSeen:d,failures:a,presence:u}=c,{vacuum:p,archiveDays:i}=o,t=[];if(i){const e=n.getAllProjects(1e3);let r=0;for(const s of e)r+=n.archiveOldSessions(s.id,i);t.push(`Archived ${r} old sessions`)}p&&(n.vacuum(),t.push("Vacuumed database"));try{const e=parseInt(process.env.WYRM_SEEN_TTL_DAYS??"7",10),r=d.prune(Number.isFinite(e)&&e>0?e:7);r>0&&t.push(`Pruned ${r} session_seen_artifacts rows older than ${e}d`)}catch{}try{const e=parseInt(process.env.WYRM_SEEN_TTL_DAYS??"7",10),r=Number.isFinite(e)&&e>0?e:7,s=new l(n.getDatabase()).prune(r);s>0&&t.push(`Pruned ${s} served_records rows older than ${r}d`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),r=parseInt(process.env.WYRM_EVENT_MAX_PER_PROJECT??"5000",10),{deleted:s}=n.pruneEvents({olderThanDays:Number.isFinite(e)?e:90,maxPerProject:Number.isFinite(r)?r:5e3});s>0&&t.push(`Pruned ${s} Live Memory events (retention)`)}catch{}try{if(h()){const e=f(n.getDatabase(),{near:E()});e.exactMerged+e.nearCandidatesQueued+e.decayed>0&&t.push(`Metabolize: ${e.exactMerged} exact dupes superseded, ${e.nearCandidatesQueued} near-dup candidates queued, ${e.decayed} stale auto-captures decayed`)}}catch{}try{const e=_(n.getDatabase());e>0&&t.push(`Pruned ${e} write_ledger receipts (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),r=Number.isFinite(e)&&e>0?e:90,s=a.pruneBlocks(r);s>0&&t.push(`Pruned ${s} failure_blocks rows older than ${r}d (retention)`)}catch{}try{const e=u.reap();(e.presence>0||e.claims>0)&&t.push(`Reaped ${e.presence} stale presence row(s) and ${e.claims} expired quest claim(s)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),r=Number.isFinite(e)&&e>0?e:90,s=n.getDatabase().prepare("DELETE FROM run_briefs WHERE created_at < datetime('now', ?)").run(`-${r} days`).changes;s>0&&t.push(`Pruned ${s} run_briefs rows older than ${r}d (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_BRIEFS_MAX??"512",10),r=Number.isFinite(e)&&e>0?e:512,s=n.getDatabase().prepare(`
1
+ import{pruneWriteLedger as _}from"./receipts.js";import{metabolizeEnabled as h,metabolizeDefaultEnabled as E,runMetabolize as f}from"./metabolize.js";import{ServedRecords as l}from"./served-records.js";import{ReadMemos as m}from"./read-memo.js";function M(o,c={}){const{db:n,sessionSeen:d,failures:a,presence:u}=o,{vacuum:p,archiveDays:i}=c,t=[];if(i){const e=n.getAllProjects(1e3);let s=0;for(const r of e)s+=n.archiveOldSessions(r.id,i);t.push(`Archived ${s} old sessions`)}p&&(n.vacuum(),t.push("Vacuumed database"));try{const e=parseInt(process.env.WYRM_SEEN_TTL_DAYS??"7",10),s=d.prune(Number.isFinite(e)&&e>0?e:7);s>0&&t.push(`Pruned ${s} session_seen_artifacts rows older than ${e}d`)}catch{}try{const e=parseInt(process.env.WYRM_SEEN_TTL_DAYS??"7",10),s=Number.isFinite(e)&&e>0?e:7,r=new l(n.getDatabase()).prune(s);r>0&&t.push(`Pruned ${r} served_records rows older than ${s}d`)}catch{}try{const e=parseInt(process.env.WYRM_MEMO_TTL_DAYS??"7",10),s=Number.isFinite(e)&&e>0?e:7,r=new m(n.getDatabase()).prune(s);r>0&&t.push(`Pruned ${r} read_memos rows older than ${s}d`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=parseInt(process.env.WYRM_EVENT_MAX_PER_PROJECT??"5000",10),{deleted:r}=n.pruneEvents({olderThanDays:Number.isFinite(e)?e:90,maxPerProject:Number.isFinite(s)?s:5e3});r>0&&t.push(`Pruned ${r} Live Memory events (retention)`)}catch{}try{if(E()){const e=f(n.getDatabase(),{near:h()});e.exactMerged+e.nearCandidatesQueued+e.decayed>0&&t.push(`Metabolize: ${e.exactMerged} exact dupes superseded, ${e.nearCandidatesQueued} near-dup candidates queued, ${e.decayed} stale auto-captures decayed`)}}catch{}try{const e=_(n.getDatabase());e>0&&t.push(`Pruned ${e} write_ledger receipts (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=Number.isFinite(e)&&e>0?e:90,r=a.pruneBlocks(s);r>0&&t.push(`Pruned ${r} failure_blocks rows older than ${s}d (retention)`)}catch{}try{const e=u.reap();(e.presence>0||e.claims>0)&&t.push(`Reaped ${e.presence} stale presence row(s) and ${e.claims} expired quest claim(s)`)}catch{}try{const e=parseInt(process.env.WYRM_EVENT_RETAIN_DAYS??"90",10),s=Number.isFinite(e)&&e>0?e:90,r=n.getDatabase().prepare("DELETE FROM run_briefs WHERE created_at < datetime('now', ?)").run(`-${s} days`).changes;r>0&&t.push(`Pruned ${r} run_briefs rows older than ${s}d (retention)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_BRIEFS_MAX??"512",10),s=Number.isFinite(e)&&e>0?e:512,r=n.getDatabase().prepare(`
2
2
  DELETE FROM run_briefs WHERE rowid IN (
3
3
  SELECT rowid FROM run_briefs ORDER BY created_at DESC, rowid DESC LIMIT -1 OFFSET ?
4
- )`).run(r).changes;s>0&&t.push(`Pruned ${s} run_briefs rows over the ${r}-row cap (WYRM_RUN_BRIEFS_MAX)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_INACTIVE_HOURS??"24",10),r=a.sweepRunQuarantine({runInactiveHours:Number.isFinite(e)&&e>0?e:24});(r.promoted>0||r.expired>0||r.runs_abandoned>0)&&t.push(`Run-quarantine sweep: promoted ${r.promoted} failure(s), expired ${r.expired} abandoned-run failure(s), TTL-abandoned ${r.runs_abandoned} stalled run(s)`),r.degraded&&t.push(`Run-quarantine sweep: ${r.degraded}`)}catch{}return n.checkpoint(),t.push("Checkpointed WAL"),{lines:t,dbSize:n.getStats().dbSize}}export{y as runMaintenance};
4
+ )`).run(s).changes;r>0&&t.push(`Pruned ${r} run_briefs rows over the ${s}-row cap (WYRM_RUN_BRIEFS_MAX)`)}catch{}try{const e=parseInt(process.env.WYRM_RUN_INACTIVE_HOURS??"24",10),s=a.sweepRunQuarantine({runInactiveHours:Number.isFinite(e)&&e>0?e:24});(s.promoted>0||s.expired>0||s.runs_abandoned>0)&&t.push(`Run-quarantine sweep: promoted ${s.promoted} failure(s), expired ${s.expired} abandoned-run failure(s), TTL-abandoned ${s.runs_abandoned} stalled run(s)`),s.degraded&&t.push(`Run-quarantine sweep: ${s.degraded}`)}catch{}return n.checkpoint(),t.push("Checkpointed WAL"),{lines:t,dbSize:n.getStats().dbSize}}export{M as runMaintenance};
@@ -210,7 +210,7 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
210
210
  timestamp TEXT DEFAULT (datetime('now'))
211
211
  );
212
212
  CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage_events(timestamp);
213
- `)}},{version:3,description:"Add visibility and created_by columns for future multi-user support",up:e=>{const s=["sessions","quests","context","data_lake"];for(const t of s){const E=e.prepare(`PRAGMA table_info(${t})`).all().map(T=>T.name);E.includes("visibility")||e.exec(`ALTER TABLE ${t} ADD COLUMN visibility TEXT DEFAULT 'private'`),E.includes("created_by")||e.exec(`ALTER TABLE ${t} ADD COLUMN created_by TEXT DEFAULT 'local'`)}}},{version:4,description:"Knowledge graph \u2014 entities, aliases, relationships with FTS5",up:e=>{e.exec(`
213
+ `)}},{version:3,description:"Add visibility and created_by columns for future multi-user support",up:e=>{const s=["sessions","quests","context","data_lake"];for(const t of s){const i=e.prepare(`PRAGMA table_info(${t})`).all().map(T=>T.name);i.includes("visibility")||e.exec(`ALTER TABLE ${t} ADD COLUMN visibility TEXT DEFAULT 'private'`),i.includes("created_by")||e.exec(`ALTER TABLE ${t} ADD COLUMN created_by TEXT DEFAULT 'local'`)}}},{version:4,description:"Knowledge graph \u2014 entities, aliases, relationships with FTS5",up:e=>{e.exec(`
214
214
  -- Entities: named things that can be linked together
215
215
  CREATE TABLE IF NOT EXISTS entities (
216
216
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -844,7 +844,7 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
844
844
  ON token_savings_log(session_id, timestamp DESC);
845
845
  CREATE INDEX IF NOT EXISTS idx_savings_category
846
846
  ON token_savings_log(category, timestamp DESC);
847
- `);const s=["ground_truths","memory_artifacts","quests","decision_edges","design_references","design_tokens"];for(const t of s)try{if(e.prepare(`PRAGMA table_info(${t})`).all().some(E=>E.name==="cross_project_visibility"))continue;e.exec(`
847
+ `);const s=["ground_truths","memory_artifacts","quests","decision_edges","design_references","design_tokens"];for(const t of s)try{if(e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name==="cross_project_visibility"))continue;e.exec(`
848
848
  ALTER TABLE ${t} ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
849
849
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
850
850
  `)}catch{}}},{version:15,description:"Live Memory v6.4 \u2014 append-only events log + wyrm_meta (device identity)",up:e=>{e.exec(`
@@ -908,7 +908,7 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
908
908
  `)}},{version:17,description:'Backfill cross_project_visibility on decision_edges (migration 14 named a non-existent "decisions" table)',up:e=>{try{e.prepare("PRAGMA table_info(decision_edges)").all().some(t=>t.name==="cross_project_visibility")||e.exec(`
909
909
  ALTER TABLE decision_edges ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
910
910
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
911
- `)}catch{}}},{version:18,description:"GOD-SKILL SPEC v2 \u2014 skill tier/governs/composes + spec-kit registry (specs table)",up:e=>{const s=e.prepare("PRAGMA table_info(skills)").all(),t=i=>s.some(E=>E.name===i);t("tier")||e.exec(`
911
+ `)}catch{}}},{version:18,description:"GOD-SKILL SPEC v2 \u2014 skill tier/governs/composes + spec-kit registry (specs table)",up:e=>{const s=e.prepare("PRAGMA table_info(skills)").all(),t=E=>s.some(i=>i.name===E);t("tier")||e.exec(`
912
912
  ALTER TABLE skills ADD COLUMN tier TEXT NOT NULL DEFAULT 'atomic'
913
913
  CHECK (tier IN ('atomic', 'mega', 'god'));
914
914
  `),t("governs")||e.exec("ALTER TABLE skills ADD COLUMN governs TEXT NOT NULL DEFAULT '[]';"),t("composes")||e.exec("ALTER TABLE skills ADD COLUMN composes TEXT NOT NULL DEFAULT '[]';"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_tier ON skills(tier);"),e.exec(`
@@ -929,8 +929,8 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
929
929
  `)}},{version:19,description:"Grove sync policy: projects.sync_policy gate (private by default) for cloud + federation isolation",up:e=>{e.prepare("PRAGMA table_info(projects)").all().some(a=>a.name==="sync_policy")||e.exec(`
930
930
  ALTER TABLE projects ADD COLUMN sync_policy TEXT NOT NULL DEFAULT 'private'
931
931
  CHECK (sync_policy IN ('private', 'cloud', 'team'));
932
- `);const t=(a,o)=>{try{return e.prepare(`PRAGMA table_info(${a})`).all().some(r=>r.name===o)}catch{return!1}},i=a=>{try{return e.prepare(a).all().map(o=>o.pid)}catch{return[]}},E=new Set;for(const a of["ground_truths","memory_artifacts","quests","design_tokens","design_references"])if(!(!t(a,"cross_project_visibility")||!t(a,"project_id")))for(const o of i(`SELECT DISTINCT project_id AS pid FROM ${a}
933
- WHERE cross_project_visibility IN ('org', 'public') AND project_id IS NOT NULL`))E.add(o);const T=e.prepare("UPDATE projects SET sync_policy = 'cloud' WHERE id = ? AND sync_policy = 'private'");for(const a of E)T.run(a)}},{version:20,description:"v7 F2 (T008) \u2014 run-native provenance: runs + run_agents tables, agent_id/run_id attribution on 9 tables, failure_patterns.quarantine_scope",up:e=>{e.exec(`
932
+ `);const t=(a,o)=>{try{return e.prepare(`PRAGMA table_info(${a})`).all().some(r=>r.name===o)}catch{return!1}},E=a=>{try{return e.prepare(a).all().map(o=>o.pid)}catch{return[]}},i=new Set;for(const a of["ground_truths","memory_artifacts","quests","design_tokens","design_references"])if(!(!t(a,"cross_project_visibility")||!t(a,"project_id")))for(const o of E(`SELECT DISTINCT project_id AS pid FROM ${a}
933
+ WHERE cross_project_visibility IN ('org', 'public') AND project_id IS NOT NULL`))i.add(o);const T=e.prepare("UPDATE projects SET sync_policy = 'cloud' WHERE id = ? AND sync_policy = 'private'");for(const a of i)T.run(a)}},{version:20,description:"v7 F2 (T008) \u2014 run-native provenance: runs + run_agents tables, agent_id/run_id attribution on 9 tables, failure_patterns.quarantine_scope",up:e=>{e.exec(`
934
934
  CREATE TABLE IF NOT EXISTS runs (
935
935
  run_id TEXT PRIMARY KEY,
936
936
  parent_run_id TEXT,
@@ -955,7 +955,7 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
955
955
  FOREIGN KEY (run_id) REFERENCES runs(run_id) ON DELETE CASCADE
956
956
  );
957
957
  CREATE INDEX IF NOT EXISTS idx_run_agents_agent ON run_agents(agent_id);
958
- `);const s=(i,E)=>{try{return e.prepare(`PRAGMA table_info(${i})`).all().some(T=>T.name===E)}catch{return!1}},t=["memory_artifacts","failure_patterns","decision_edges","ground_truths","quests","sessions","quest_claims","events","audit_log"];for(const i of t)s(i,"agent_id")||e.exec(`ALTER TABLE ${i} ADD COLUMN agent_id TEXT;`),s(i,"run_id")||e.exec(`ALTER TABLE ${i} ADD COLUMN run_id TEXT;`);e.exec(`
958
+ `);const s=(E,i)=>{try{return e.prepare(`PRAGMA table_info(${E})`).all().some(T=>T.name===i)}catch{return!1}},t=["memory_artifacts","failure_patterns","decision_edges","ground_truths","quests","sessions","quest_claims","events","audit_log"];for(const E of t)s(E,"agent_id")||e.exec(`ALTER TABLE ${E} ADD COLUMN agent_id TEXT;`),s(E,"run_id")||e.exec(`ALTER TABLE ${E} ADD COLUMN run_id TEXT;`);e.exec(`
959
959
  CREATE INDEX IF NOT EXISTS idx_failure_run ON failure_patterns(run_id) WHERE run_id IS NOT NULL;
960
960
  CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id) WHERE run_id IS NOT NULL;
961
961
  CREATE INDEX IF NOT EXISTS idx_artifacts_run ON memory_artifacts(run_id) WHERE run_id IS NOT NULL;
@@ -990,10 +990,10 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
990
990
  created_at TEXT DEFAULT (datetime('now')),
991
991
  PRIMARY KEY (run_id, role)
992
992
  );
993
- `)}},{version:24,description:"v7 F3 (T029) \u2014 claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes",up:e=>{const s=(t,i)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(E=>E.name===i)}catch{return!1}};s("quest_claims","role")||e.exec("ALTER TABLE quest_claims ADD COLUMN role TEXT;"),s("agent_presence","run_id")||e.exec("ALTER TABLE agent_presence ADD COLUMN run_id TEXT;"),s("agent_presence","role")||e.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT;"),e.exec(`
993
+ `)}},{version:24,description:"v7 F3 (T029) \u2014 claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes",up:e=>{const s=(t,E)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E)}catch{return!1}};s("quest_claims","role")||e.exec("ALTER TABLE quest_claims ADD COLUMN role TEXT;"),s("agent_presence","run_id")||e.exec("ALTER TABLE agent_presence ADD COLUMN run_id TEXT;"),s("agent_presence","role")||e.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT;"),e.exec(`
994
994
  CREATE INDEX IF NOT EXISTS idx_quest_claims_run ON quest_claims(run_id) WHERE run_id IS NOT NULL;
995
995
  CREATE INDEX IF NOT EXISTS idx_presence_run ON agent_presence(run_id) WHERE run_id IS NOT NULL;
996
- `)}},{version:25,description:"Skills portability \u2014 store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate",up:e=>{const s=(t,i)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(E=>E.name===i)}catch{return!1}};s("skills","content")||e.exec("ALTER TABLE skills ADD COLUMN content TEXT;"),s("skills","content_sha256")||e.exec("ALTER TABLE skills ADD COLUMN content_sha256 TEXT;"),s("skills","content_updated_at")||e.exec("ALTER TABLE skills ADD COLUMN content_updated_at TEXT;"),s("skills","cross_project_visibility")||e.exec("ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';"),s("skills","is_shared")||e.exec("ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';")}},{version:26,description:"Reverse-bridge rejection tombstones \u2014 remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted",up:e=>{e.exec(`
996
+ `)}},{version:25,description:"Skills portability \u2014 store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate",up:e=>{const s=(t,E)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E)}catch{return!1}};s("skills","content")||e.exec("ALTER TABLE skills ADD COLUMN content TEXT;"),s("skills","content_sha256")||e.exec("ALTER TABLE skills ADD COLUMN content_sha256 TEXT;"),s("skills","content_updated_at")||e.exec("ALTER TABLE skills ADD COLUMN content_updated_at TEXT;"),s("skills","cross_project_visibility")||e.exec("ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';"),s("skills","is_shared")||e.exec("ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';")}},{version:26,description:"Reverse-bridge rejection tombstones \u2014 remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted",up:e=>{e.exec(`
997
997
  CREATE TABLE IF NOT EXISTS reverse_bridge_tombstones (
998
998
  project_id INTEGER NOT NULL,
999
999
  sig TEXT NOT NULL,
@@ -1130,20 +1130,20 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
1130
1130
  UNIQUE(kind, row_id)
1131
1131
  );
1132
1132
  CREATE INDEX IF NOT EXISTS idx_sync_tombstones_pending ON sync_tombstones(pushed_at_ms);
1133
- `);const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,i]of s)e.exec(`
1133
+ `);const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,E]of s)e.exec(`
1134
1134
  CREATE TRIGGER IF NOT EXISTS trg_tombstone_${t} AFTER DELETE ON ${t}
1135
1135
  BEGIN
1136
1136
  INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
1137
- VALUES ('${i}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1137
+ VALUES ('${E}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1138
1138
  END;
1139
- `)}},{version:35,description:"Grove-gate the tombstone triggers (quest #113): a tombstone must record ONLY when the deleted row would have SYNCED, matching the upsert gate exactly (cross_project_visibility IN org/public AND grove sync_policy IN cloud/team). Migration 34 recorded a tombstone for EVERY delete, so a PRIVATE grove's (or unshared skill's) deletion leaked content-free deletion metadata to the cloud as noise. Recreates all 6 triggers gated.",up:e=>{const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,i]of s){e.exec(`DROP TRIGGER IF EXISTS trg_tombstone_${t};`);const E=t==="skills"?"OLD.cross_project_visibility IN ('org','public')":"OLD.cross_project_visibility IN ('org','public') AND (OLD.project_id IS NULL OR (SELECT sync_policy FROM projects WHERE id = OLD.project_id) IN ('cloud','team'))";e.exec(`
1139
+ `)}},{version:35,description:"Grove-gate the tombstone triggers (quest #113): a tombstone must record ONLY when the deleted row would have SYNCED, matching the upsert gate exactly (cross_project_visibility IN org/public AND grove sync_policy IN cloud/team). Migration 34 recorded a tombstone for EVERY delete, so a PRIVATE grove's (or unshared skill's) deletion leaked content-free deletion metadata to the cloud as noise. Recreates all 6 triggers gated.",up:e=>{const s=[["ground_truths","truth"],["memory_artifacts","memory_artifact"],["quests","quest"],["design_tokens","design_token"],["design_references","design_reference"],["skills","skill"]];for(const[t,E]of s){e.exec(`DROP TRIGGER IF EXISTS trg_tombstone_${t};`);const i=t==="skills"?"OLD.cross_project_visibility IN ('org','public')":"OLD.cross_project_visibility IN ('org','public') AND (OLD.project_id IS NULL OR (SELECT sync_policy FROM projects WHERE id = OLD.project_id) IN ('cloud','team'))";e.exec(`
1140
1140
  CREATE TRIGGER trg_tombstone_${t} AFTER DELETE ON ${t}
1141
- WHEN ${E}
1141
+ WHEN ${i}
1142
1142
  BEGIN
1143
1143
  INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
1144
- VALUES ('${i}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1144
+ VALUES ('${E}', CAST(OLD.id AS TEXT), CAST(strftime('%s','now') AS INTEGER) * 1000);
1145
1145
  END;
1146
- `)}}},{version:36,description:`Add updated_at to quests + design_references so cloud sync can watermark-filter them. Root cause of "sync never settles" (2026-07-17): collectChangedRows skips its incremental time filter for any synced table WITHOUT an updated_at column, so it re-collected ALL org/public quests (101) + design_references every sync, stamping each with Date.now() and re-pushing forever (~1s/row server-side). The 4 other synced tables already have updated_at. This migration adds the column + backfills from the row's real last-change time (normalized via datetime() to the clean "YYYY-MM-DD HH:MM:SS" format strftime expects). No triggers: both tables are external-content FTS5, where a self-UPDATE inside a trigger corrupts the FTS index when an indexed column changes ("database disk image is malformed"). updated_at is stamped in app code on every insert/update instead (database.ts addQuest/updateQuest/dynamic-update, design.ts) \u2014 the same convention the other synced tables use.`,up:e=>{const s=(t,i)=>e.prepare(`PRAGMA table_info(${t})`).all().some(E=>E.name===i);s("quests","updated_at")||(e.exec("ALTER TABLE quests ADD COLUMN updated_at TEXT"),e.exec("UPDATE quests SET updated_at = datetime(COALESCE(completed_at, created_at, 'now'))")),s("design_references","updated_at")||(e.exec("ALTER TABLE design_references ADD COLUMN updated_at TEXT"),e.exec("UPDATE design_references SET updated_at = datetime('now')"))}},{version:37,description:"Add metrics_snapshots \u2014 the longitudinal flight recorder for Wyrm effectiveness + quality over time. wyrm doctor / stats / benches all measure a MOMENT; nothing timestamped a trend, so a slow degradation (recall drifting as the corpus outgrows the index, vectors quietly off for days, a crucible-caught defect class recurring) was invisible until someone re-ran a tool by hand. One row per (kind, capture): kind=health (corpus/coverage/queue/failures/doctor checks), kind=effectiveness (recall hit-rate, failure-block rate, rehydrate non-empty rate, token savings), kind=retrieval (recall@k from the eval bench). metrics_json keeps the schema flexible so new metrics need no migration. Written nightly (health+effectiveness) + on-demand/per-release (retrieval); read by `wyrm metrics trend`.",up:e=>{e.exec(`
1146
+ `)}}},{version:36,description:`Add updated_at to quests + design_references so cloud sync can watermark-filter them. Root cause of "sync never settles" (2026-07-17): collectChangedRows skips its incremental time filter for any synced table WITHOUT an updated_at column, so it re-collected ALL org/public quests (101) + design_references every sync, stamping each with Date.now() and re-pushing forever (~1s/row server-side). The 4 other synced tables already have updated_at. This migration adds the column + backfills from the row's real last-change time (normalized via datetime() to the clean "YYYY-MM-DD HH:MM:SS" format strftime expects). No triggers: both tables are external-content FTS5, where a self-UPDATE inside a trigger corrupts the FTS index when an indexed column changes ("database disk image is malformed"). updated_at is stamped in app code on every insert/update instead (database.ts addQuest/updateQuest/dynamic-update, design.ts) \u2014 the same convention the other synced tables use.`,up:e=>{const s=(t,E)=>e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===E);s("quests","updated_at")||(e.exec("ALTER TABLE quests ADD COLUMN updated_at TEXT"),e.exec("UPDATE quests SET updated_at = datetime(COALESCE(completed_at, created_at, 'now'))")),s("design_references","updated_at")||(e.exec("ALTER TABLE design_references ADD COLUMN updated_at TEXT"),e.exec("UPDATE design_references SET updated_at = datetime('now')"))}},{version:37,description:"Add metrics_snapshots \u2014 the longitudinal flight recorder for Wyrm effectiveness + quality over time. wyrm doctor / stats / benches all measure a MOMENT; nothing timestamped a trend, so a slow degradation (recall drifting as the corpus outgrows the index, vectors quietly off for days, a crucible-caught defect class recurring) was invisible until someone re-ran a tool by hand. One row per (kind, capture): kind=health (corpus/coverage/queue/failures/doctor checks), kind=effectiveness (recall hit-rate, failure-block rate, rehydrate non-empty rate, token savings), kind=retrieval (recall@k from the eval bench). metrics_json keeps the schema flexible so new metrics need no migration. Written nightly (health+effectiveness) + on-demand/per-release (retrieval); read by `wyrm metrics trend`.",up:e=>{e.exec(`
1147
1147
  CREATE TABLE IF NOT EXISTS metrics_snapshots (
1148
1148
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1149
1149
  captured_at TEXT NOT NULL DEFAULT (datetime('now')),
@@ -1164,10 +1164,10 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
1164
1164
  source_refs TEXT DEFAULT '{}',
1165
1165
  updated_at INTEGER NOT NULL DEFAULT 0
1166
1166
  );
1167
- `)}},{version:39,description:"Firewall target normalization (quest #1033) \u2014 add canonical_target + command_head to failure_patterns so the guard can catch flag-reordered / =-form command repeats (Tier A hard block) and warn on added-flag variants (Tier B advisory). Backfills existing command rows via canonicalizeCommand/commandHead; indexes both keys for range-scan lookups.",up:e=>{const s=e.prepare("PRAGMA table_info(failure_patterns)").all().map(E=>E.name);s.includes("canonical_target")||e.exec("ALTER TABLE failure_patterns ADD COLUMN canonical_target TEXT;"),s.includes("command_head")||e.exec("ALTER TABLE failure_patterns ADD COLUMN command_head TEXT;"),e.exec(`
1167
+ `)}},{version:39,description:"Firewall target normalization (quest #1033) \u2014 add canonical_target + command_head to failure_patterns so the guard can catch flag-reordered / =-form command repeats (Tier A hard block) and warn on added-flag variants (Tier B advisory). Backfills existing command rows via canonicalizeCommand/commandHead; indexes both keys for range-scan lookups.",up:e=>{const s=e.prepare("PRAGMA table_info(failure_patterns)").all().map(i=>i.name);s.includes("canonical_target")||e.exec("ALTER TABLE failure_patterns ADD COLUMN canonical_target TEXT;"),s.includes("command_head")||e.exec("ALTER TABLE failure_patterns ADD COLUMN command_head TEXT;"),e.exec(`
1168
1168
  CREATE INDEX IF NOT EXISTS idx_failure_canonical ON failure_patterns(canonical_target);
1169
1169
  CREATE INDEX IF NOT EXISTS idx_failure_cmdhead ON failure_patterns(command_head);
1170
- `);const t=e.prepare("SELECT id, target FROM failure_patterns WHERE scope = 'command'").all(),i=e.prepare("UPDATE failure_patterns SET canonical_target = ?, command_head = ? WHERE id = ?");for(const E of t)i.run(n(E.target)||null,d(E.target)||null,E.id)}},{version:40,description:"Negotiated Recall NR-1 (specs/negotiated-recall) \u2014 recall_policy: one row per run_id holding the session's recall consent state. mode=full is today's behavior (no filtering); mode=clean mutes the MUTE set (memory_artifacts kind IN (pattern, heuristic) OR outcome=positive) while the firewall (anti_pattern/negative/failure_patterns) and ground_truths stay unaffected by construction, never by an exclusion list; mode=custom governs by muted_ids/challenged_truth_ids alone. muted_ids/challenged_truth_ids are JSON arrays applied in every mode. Table only \u2014 no enforcement here (that lands in NR-3 at the MemoryArtifacts chokepoint); a DB with zero recall_policy rows recalls identically to pre-NR-1.",up:e=>{e.exec(`
1170
+ `);const t=e.prepare("SELECT id, target FROM failure_patterns WHERE scope = 'command'").all(),E=e.prepare("UPDATE failure_patterns SET canonical_target = ?, command_head = ? WHERE id = ?");for(const i of t)E.run(n(i.target)||null,d(i.target)||null,i.id)}},{version:40,description:"Negotiated Recall NR-1 (specs/negotiated-recall) \u2014 recall_policy: one row per run_id holding the session's recall consent state. mode=full is today's behavior (no filtering); mode=clean mutes the MUTE set (memory_artifacts kind IN (pattern, heuristic) OR outcome=positive) while the firewall (anti_pattern/negative/failure_patterns) and ground_truths stay unaffected by construction, never by an exclusion list; mode=custom governs by muted_ids/challenged_truth_ids alone. muted_ids/challenged_truth_ids are JSON arrays applied in every mode. Table only \u2014 no enforcement here (that lands in NR-3 at the MemoryArtifacts chokepoint); a DB with zero recall_policy rows recalls identically to pre-NR-1.",up:e=>{e.exec(`
1171
1171
  CREATE TABLE IF NOT EXISTS recall_policy (
1172
1172
  run_id TEXT PRIMARY KEY,
1173
1173
  mode TEXT NOT NULL DEFAULT 'full'
@@ -1185,10 +1185,27 @@ import{canonicalizeCommand as n,commandHead as d}from"./failure-patterns.js";con
1185
1185
  served_at TEXT NOT NULL DEFAULT (datetime('now')),
1186
1186
  PRIMARY KEY (session_key, artifact_id, artifact_kind)
1187
1187
  );
1188
+ `)}},{version:42,description:"Token firewall phase 1 \u2014 read_memos: one row per file read in full (path, sha256, a landmark map, counters), so the wyrm-memo PreToolUse hook can recognise a repeat whole-file read of an unchanged file and count or hand back the map instead. Raw file text is never stored.",up:e=>{e.exec(`
1189
+ CREATE TABLE IF NOT EXISTS read_memos (
1190
+ path TEXT PRIMARY KEY,
1191
+ sha256 TEXT NOT NULL,
1192
+ bytes INTEGER NOT NULL,
1193
+ lines INTEGER NOT NULL,
1194
+ distilled TEXT NOT NULL,
1195
+ tool TEXT NOT NULL,
1196
+ project_id INTEGER,
1197
+ session_id TEXT,
1198
+ first_seen TEXT NOT NULL DEFAULT (datetime('now')),
1199
+ last_seen TEXT NOT NULL DEFAULT (datetime('now')),
1200
+ seen_count INTEGER NOT NULL DEFAULT 1,
1201
+ hits INTEGER NOT NULL DEFAULT 0,
1202
+ tokens_avoided INTEGER NOT NULL DEFAULT 0
1203
+ );
1204
+ CREATE INDEX IF NOT EXISTS idx_read_memos_last_seen ON read_memos(last_seen);
1188
1205
  `)}}];function l(e){e.exec(`
1189
1206
  CREATE TABLE IF NOT EXISTS schema_versions (
1190
1207
  version INTEGER PRIMARY KEY,
1191
1208
  description TEXT NOT NULL,
1192
1209
  applied_at TEXT DEFAULT (datetime('now'))
1193
1210
  );
1194
- `);const s=e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v,t=N.filter(T=>T.version>s).slice().sort((T,a)=>T.version-a.version);if(t.length===0)return[];const i=[],E=e.prepare("INSERT INTO schema_versions (version, description) VALUES (?, ?)");for(const T of t)e.transaction(()=>{T.up(e),E.run(T.version,T.description)})(),i.push(T);return i}function _(e){try{return e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v}catch{return 0}}export{_ as getSchemaVersion,N as migrations,l as runMigrations};
1211
+ `);const s=e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v,t=N.filter(T=>T.version>s).slice().sort((T,a)=>T.version-a.version);if(t.length===0)return[];const E=[],i=e.prepare("INSERT INTO schema_versions (version, description) VALUES (?, ?)");for(const T of t)e.transaction(()=>{T.up(e),i.run(T.version,T.description)})(),E.push(T);return E}function _(e){try{return e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v}catch{return 0}}export{_ as getSchemaVersion,N as migrations,l as runMigrations};
@@ -0,0 +1,23 @@
1
+ import{createHash as A}from"node:crypto";import{basename as g,extname as b,isAbsolute as f,resolve as p}from"node:path";import{commandSegments as y}from"./wyrm-guard.js";const S=1400,N=40,E=90,T=12,_="Whole-file read already done this week and unchanged. Prefer Read with offset/limit on the lines you need, or grep. (wyrm-memo)";function L(e){return A("sha256").update(e).digest("hex")}function H(e){return e<=0?0:Math.ceil(e/4)}function c(e,t){return e.replace(/[\x00-\x1f\x7f]/g," ").replace(/\s+/g," ").trim().slice(0,t)}const M=[{exts:["md","markdown","txt"],test:e=>/^#{1,4}\s+\S/.test(e)},{exts:["ts","js","mjs","cjs","tsx","jsx","mts","cts"],test:e=>/^(export\s+(default\s+)?(async\s+)?(function|class|const|let|var|interface|type|enum)\b|(async\s+)?function\s+[A-Za-z_$]|class\s+[A-Za-z_$]|(interface|type|enum)\s+[A-Za-z_$])/.test(e)},{exts:["py"],test:e=>/^(async\s+def|def|class)\s+[A-Za-z_]/.test(e)},{exts:["rs"],test:e=>/^(pub(\([^)]*\))?\s+)?(fn|struct|enum|impl|trait|mod)\b/.test(e)},{exts:["go"],test:e=>/^(func|type)\s+[A-Za-z_]/.test(e)},{exts:["css","scss","less"],test:e=>/^\/\*\s*[-=]{3,}/.test(e)||/^(\.|#|@|:root|[a-z][a-z0-9-]*)[^{;]*\{\s*$/.test(e)},{exts:["astro","html","htm","vue","svelte"],test:e=>/^\s*<(section|main|header|footer|nav|article|aside|h1|h2|h3)\b/.test(e)||/^\s*\{\/\*/.test(e)||/^---\s*$/.test(e)},{exts:["json"],test:e=>/^\s{0,2}"[^"]+"\s*:/.test(e)},{exts:["yaml","yml","toml"],test:e=>/^[A-Za-z_"[][^:=]{0,60}[:=]/.test(e)}];function j(e,t){const s=e.split(`
2
+ `),o=b(t).replace(/^\./,"").toLowerCase(),r=M.find(n=>n.exts.includes(o)),a=Buffer.byteLength(e,"utf8"),l=`${c(g(t),80)} | ${s.length} lines | ${a} bytes | sha ${L(e).slice(0,12)}`,i=[];if(r)for(let n=0;n<s.length&&i.length<N;n++)r.test(s[n])&&i.push(`L${n+1}: ${c(s[n],E)}`);if(i.length===0)for(let n=0;n<s.length&&i.length<T;n++)s[n].trim().length>0&&i.push(`L${n+1}: ${c(s[n],E)}`);const u=S-l.length-_.length-2,d=[];let h=0;for(const n of i){if(h+n.length+1>u)break;d.push(n),h+=n.length+1}return[l,...d,_].join(`
3
+ `)}const R=8e3;function O(e){const t=Math.min(e.length,R);if(t===0)return!0;let s=0;for(let o=0;o<t;o++){const r=e[o];if(r===0)return!1;(r<32&&r!==9&&r!==10&&r!==13||r===127)&&s++}return s/t<=.1}function m(e){return typeof e=="string"&&e.length>0?e:null}function x(e,t){return f(e)?p(e):t&&f(t)?p(t,e):null}const C=/^cat(?:\s+-[A-Za-z]+)*\s+(?:'([^']+)'|"([^"]+)"|([^\s'"|<>&;]+))\s*$/;function v(e){const t=e.tool_name,s=e.tool_input;if(typeof t!="string"||typeof s!="object"||s===null)return null;const o=s,r=m(e.cwd);if(t==="Read"){const a=m(o.file_path);if(!a||typeof o.limit=="number"||typeof o.offset=="number"&&o.offset>1)return null;const l=x(a,r);return l?{absPath:l,tool:"Read",display:c(a,120)}:null}if(t==="Bash"){const a=m(o.command)?.trim();if(!a)return null;const l=y(a);if(l.length!==1)return null;const i=l[0].match(C);if(!i)return null;const u=i[1]??i[2]??i[3];if(!u)return null;const d=x(u,r);return d?{absPath:d,tool:"Bash",display:c(l[0],120)}:null}return null}function D(e,t,s=Date.now()){const o=Date.parse(e.replace(" ","T")+"Z");return Number.isFinite(o)?o<s-t*864e5:!0}class w{db;constructor(t){this.db=t}get(t){return this.db.prepare("SELECT * FROM read_memos WHERE path = ?").get(t)??null}upsert(t){return this.db.prepare(`
4
+ INSERT INTO read_memos (path, sha256, bytes, lines, distilled, tool, project_id, session_id)
5
+ VALUES (@path, @sha256, @bytes, @lines, @distilled, @tool, @projectId, @sessionId)
6
+ ON CONFLICT(path) DO UPDATE SET
7
+ bytes = excluded.bytes,
8
+ lines = excluded.lines,
9
+ distilled = excluded.distilled,
10
+ tool = excluded.tool,
11
+ project_id = excluded.project_id,
12
+ session_id = excluded.session_id,
13
+ last_seen = datetime('now'),
14
+ seen_count = CASE WHEN read_memos.sha256 = excluded.sha256 THEN read_memos.seen_count + 1 ELSE 1 END,
15
+ hits = CASE WHEN read_memos.sha256 = excluded.sha256 THEN read_memos.hits ELSE 0 END,
16
+ tokens_avoided = CASE WHEN read_memos.sha256 = excluded.sha256 THEN read_memos.tokens_avoided ELSE 0 END,
17
+ sha256 = excluded.sha256
18
+ RETURNING *
19
+ `).get({path:t.path,sha256:t.sha256,bytes:t.bytes,lines:t.lines,distilled:t.distilled,tool:t.tool,projectId:t.projectId??null,sessionId:t.sessionId??null})}recordHit(t,s){this.db.prepare(`
20
+ UPDATE read_memos SET hits = hits + 1, tokens_avoided = tokens_avoided + ? WHERE path = ?
21
+ `).run(Math.max(0,Math.floor(s)),t)}prune(t){const s=Math.max(0,Math.floor(t));return this.db.prepare("DELETE FROM read_memos WHERE last_seen < datetime('now', ?)").run(`-${s} days`).changes}summary(){const t=this.db.prepare(`
22
+ SELECT COUNT(*) AS memos, COALESCE(SUM(hits), 0) AS hits, COALESCE(SUM(tokens_avoided), 0) AS tokensAvoided FROM read_memos
23
+ `).get();return{memos:t.memos,hits:t.hits,tokensAvoided:t.tokensAvoided}}}export{w as ReadMemos,j as distill,H as estimateTokens,D as isExpired,O as isProbablyText,v as readTarget,L as sha256Hex};
@@ -0,0 +1,7 @@
1
+ import{SessionSeen as s}from"./session-seen.js";import{FailurePatterns as c}from"./failure-patterns.js";import{AgentPresence as u}from"./presence.js";import{runMaintenance as m}from"./maintenance.js";const i="last_maintenance_at",l=24;function E(t){const e=Number(t.WYRM_MAINTENANCE_INTERVAL_HOURS);return Number.isFinite(e)&&e>0?e:l}function N(t,e,a){const r=new Date(e).toISOString(),n=new Date(e-a*36e5).toISOString();return t.prepare(`
2
+ INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
3
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
4
+ WHERE wyrm_meta.value < ?
5
+ `).run(i,r,n).changes===1}function S(t){try{return t.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(i)?.value??null}catch{return null}}function p(t,e=process.env,a=Date.now()){try{if((e.WYRM_AUTO_MAINTENANCE??"1")==="0")return{outcome:"disabled",lines:[]};const r=t.getDatabase();if(!N(r,a,E(e)))return{outcome:"not-due",lines:[]};try{const n=m({db:t,sessionSeen:new s(r),failures:new c(r),presence:new u(r)});return e.WYRM_MAINTENANCE_DEBUG==="1"&&process.stderr.write(`wyrm: automatic maintenance ran, ${n.lines.length} step(s)
6
+ `),{outcome:"ran",lines:n.lines}}catch(n){if(e.WYRM_MAINTENANCE_DEBUG==="1"){const o=n instanceof Error?n.message:String(n);process.stderr.write(`wyrm: automatic maintenance failed (retries next interval): ${o.slice(0,200)}
7
+ `)}return{outcome:"failed",lines:[]}}}catch{return{outcome:"failed",lines:[]}}}function T(t,e=process.env){setImmediate(()=>{try{p(t,e)}catch{}})}export{i as LAST_MAINTENANCE_KEY,N as claimMaintenanceSlot,S as lastMaintenanceAt,p as maybeRunMaintenance,T as scheduleMaintenanceSweep};