wyrm-mcp 7.5.3 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{getLicenseInfo as r,selfTestVerifier as n}from"./license.js";import{OFFICIAL_BUILD as e}from"./build-flags.js";const i=e||/^(1|true|yes|on)$/i.test(process.env.WYRM_REQUIRE_ACTIVATION??""),t=e?!n().ok:!1;if(t)try{console.error("Wyrm: integrity self-test FAILED \u2014 license verifier tampered; activation gate locked.")}catch{}function s(){return t?!1:!i||r().valid}function c(){return{content:[{type:"text",text:"\u{1F512} Wyrm requires a free account on this build.\n\nRun `wyrm login` in your terminal to sign in and activate (free). Your memory stays local \u2014 login just ties usage to an account so it can be managed.\n\nAlready logged in? Your activation may have expired \u2014 run `wyrm login` again to refresh."}],isError:!0}}export{i as REQUIRE_ACTIVATION,c as activationGateResult,s as activationSatisfied};
1
+ import{getLicenseInfo as t}from"./license.js";const e=/^(1|true|yes|on)$/i.test(process.env.WYRM_REQUIRE_ACTIVATION??"");function o(){return!e||t().valid}function i(){return{content:[{type:"text",text:"\u{1F512} This deployment requires an account (WYRM_REQUIRE_ACTIVATION is set).\n\nRun `wyrm login` in your terminal to sign in and activate. Your memory stays local \u2014 login just ties usage to an account so it can be managed.\n\nAlready logged in? Your activation may have expired \u2014 run `wyrm login` again to refresh."}],isError:!0}}export{e as REQUIRE_ACTIVATION,i as activationGateResult,o as activationSatisfied};
@@ -1,16 +1,17 @@
1
- import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as k,copyFileSync as Y,chmodSync as q}from"fs";import{homedir as m,platform as W}from"os";import{join as i,dirname as w,resolve as V}from"path";import{fileURLToPath as O}from"url";import{spawnSync as P}from"child_process";function Q(){const e=m(),t=W(),o=t==="darwin"?i(e,"Library","Application Support","Code","User"):t==="win32"?i(e,"AppData","Roaming","Code","User"):i(e,".config","Code","User"),s=t==="darwin"?i(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?i(e,"AppData","Roaming","Code - Insiders","User"):i(e,".config","Code - Insiders","User"),n=t==="darwin"?i(e,"Library","Application Support","Claude"):t==="win32"?i(e,"AppData","Roaming","Claude"):i(e,".config","claude"),r=t==="darwin"?i(e,".cursor"):t==="win32"?i(e,".cursor"):i(e,".cursor"),c=t==="darwin"?i(e,".codeium","windsurf"):t==="win32"?i(e,".codeium","windsurf"):i(e,".codeium","windsurf"),u=t==="darwin"?i(e,".config","zed"):t==="win32"?i(e,"AppData","Roaming","Zed"):i(e,".config","zed"),a=i(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:i(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:i(s,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:i(n,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:i(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:i(c,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:i(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:i(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function S(){const e=Q();for(const t of e){const o=w(t.configPath);if(t.detected=d(o),t.detected&&d(t.configPath))try{const s=p(t.configPath,"utf-8"),n=R(s);t.configured=X(n,t)}catch{t.configured=!1}t.detected&&(t.version=Z(t))}return e}function Z(e){try{switch(e.id){case"vscode-copilot":case"vscode-insiders":{const t=e.id==="vscode-insiders"?"code-insiders":"code",o=P(t,["--version"],{encoding:"utf-8",timeout:5e3});if(o.stdout)return o.stdout.split(`
2
- `)[0];break}case"cursor":{const t=P("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
3
- `)[0];break}}}catch{}}function X(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 K(){try{const s=P("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(s.stdout?.trim())return s.stdout.trim()}catch{}try{const n=P("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(n){const r=i(n,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=i(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[i(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const s of o)if(d(s))return s;return"wyrm-mcp"}function U(){return i(m(),".wyrm","wyrm.db")}function ee(e,t){const o=t.serverPath,s=o==="wyrm-mcp",n=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};default:return{}}}function F(e,t){if(!e.detected)return{client:e,action:"skipped",message:`${e.name} not detected`};try{const o=w(e.configPath);d(o)||k(o,{recursive:!0});let s={},n=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");s=R(a),n=!0}catch{s={}}let r;n&&(r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath)));const c=ee(e,t);switch(e.configFormat){case"vscode":{s.mcp||(s.mcp={});const a=s.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=c;break}case"mcp-json":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}case"zed":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}}l(e.configPath,JSON.stringify(s,null,2)+`
4
- `,"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 te(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=R(t),s=`${e.configPath}.wyrm-backup`;switch(l(s,t),e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const n=o[e.mcpKey];n&&delete n.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
5
- `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:s}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function A(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:i(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const E=[{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"]}];function oe(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=i(e,"hooks");d(o)||k(o,{recursive:!0});const s=w(O(import.meta.url)),n=i(s,"..","scripts","hooks"),r=t.configPath;let c={};if(d(r))try{c=JSON.parse(p(r,"utf-8"))}catch{c={}}const u=c.hooks??{};let a=!1;const f=[];for(const h of E){const g=i(n,h.script);if(!d(g)){f.push(h.script);continue}const _=i(o,h.script);Y(g,_);try{q(_,493)}catch{}const C=`node ${_}`;for(const v of h.events){const x=typeof v=="string"?v:v.event,J=typeof v=="string"?void 0:v.matcher,$=Array.isArray(u[x])?u[x]:[];if(!$.some(b=>Array.isArray(b.hooks)&&b.hooks.some(z=>z.command===C))){const b=J?{matcher:J,hooks:[{type:"command",command:C,timeout:30}]}:{hooks:[{type:"command",command:C,timeout:30}]};$.push(b),a=!0}u[x]=$}}c.hooks=u,l(r,JSON.stringify(c,null,2)+`
6
- `,"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 ge(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=w(O(import.meta.url)),s=`node ${i(o,"wyrm-statusline.js")}`,n=t.configPath;let r={};if(d(n))try{r=JSON.parse(p(n,"utf-8"))}catch{r={}}const c=r.statusLine;if(c?.command&&!/wyrm-statusline/.test(c.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${c.command}); not overwriting`};const u=c?.command===s;return r.statusLine={type:"command",command:s,padding:0},l(n,JSON.stringify(r,null,2)+`
7
- `,"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 ye(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=o.statusLine;return s?.command&&/wyrm-statusline/.test(s.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
8
- `,"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 se(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=i(m(),".claude","hooks"),n=new Set(E.map(u=>`node ${i(s,u.script)}`)),r=new Set(E.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),c=o.hooks;if(c){for(const u of r)Array.isArray(c[u])&&(c[u]=c[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!n.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),c[u].length===0&&delete c[u]);Object.keys(c).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
9
- `,"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 M=/wyrm-guard/,re=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function j(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??i(m(),".claude","settings.json")}function T(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 L(e){return W()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ne(){const e=w(O(import.meta.url)),t=i(e,"wyrm-guard.js");if(d(t))return`node ${L(t)}`;try{const o=W()==="win32"?"where":"which",n=P(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
10
- `)[0]?.trim();if(n)return L(n)}catch{}return null}function B(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const s of Object.keys(t)){const n=t[s];if(!Array.isArray(n))continue;let r=!1;const c=[];for(const u of n){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){c.push(u);continue}const f=a.hooks.filter(y=>!M.test(y?.command??""));if(f.length===a.hooks.length){c.push(u);continue}o=!0,r=!0,f.length>0&&c.push({...a,hooks:f})}r&&(c.length===0?delete t[s]:t[s]=c)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ie(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(i(m(),".claude")))return{client:T(j(e)),action:"skipped",message:"Claude Code not detected (~/.claude missing) \u2014 the failure firewall is NOT enforced on this machine; install Claude Code then re-run `wyrm guard`"};const o=j(e),s=T(o);try{const n=e.guardCommand??ne();if(!n)return{client:s,action:"skipped",message:"wyrm-guard bin not found (npm i -g wyrm-mcp, or build dist/) \u2014 firewall hooks not installed"};k(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:s,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const c=r.hooks;if(c!==void 0&&(typeof c!="object"||c===null||Array.isArray(c)))return{client:s,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=c?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:s,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);B(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of re)y.push({matcher:g,hooks:[{type:"command",command:n,timeout:1}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
11
- `,"utf-8"),s.configured=!0,{client:s,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(n){return{client:s,action:"failed",message:n instanceof Error?n.message:String(n)}}}function ce(e={}){const t=j(e);if(!d(t))return null;const o=T(t);try{let s;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");s=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return B(s)?(l(t,JSON.stringify(s,null,2)+`
12
- `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function we(e={}){const t=j(e),o=[];try{const n=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(n))if(Array.isArray(r))for(const c of r)for(const u of c?.hooks??[]){const a=u?.command;a&&M.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ae(e){const t={serverPath:e?.serverPath||K(),dbPath:e?.dbPath||U(),httpPort:e?.httpPort},o=i(m(),".wyrm");d(o)||k(o,{recursive:!0}),I(t);const s=S(),n=[];for(const u of s)n.push(F(u,t));const r=oe();r&&n.push(r);const c=ie();return c&&n.push(c),n}function ve(){const e=S(),t=[];for(const n of e)t.push(te(n));const o=se();o&&t.push(o);const s=ce();return s&&t.push(s),t}function ue(e,t){const o={serverPath:t?.serverPath||K(),dbPath:t?.dbPath||U(),httpPort:t?.httpPort};I(o);const s=S(),n=[];for(const r of s)e.includes(r.id)&&n.push(F(r,o));return n}function I(e){const t=i(m(),".wyrm","wyrm-config.json"),o=S().filter(n=>n.configured).map(n=>n.id),s={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(s,null,2)+`
13
- `,"utf-8")}function H(){const e=i(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function ke(){const e=H();return e?ue(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ae()}const G="<!-- wyrm:start -->",D="<!-- wyrm:end -->",N=`${G}
1
+ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as P,copyFileSync as Y,chmodSync as q}from"fs";import{homedir as m,platform as j}from"os";import{join as i,dirname as w,resolve as V}from"path";import{fileURLToPath as O}from"url";import{spawnSync as v}from"child_process";function Q(){const e=m(),t=j(),o=t==="darwin"?i(e,"Library","Application Support","Code","User"):t==="win32"?i(e,"AppData","Roaming","Code","User"):i(e,".config","Code","User"),s=t==="darwin"?i(e,"Library","Application Support","Code - Insiders","User"):t==="win32"?i(e,"AppData","Roaming","Code - Insiders","User"):i(e,".config","Code - Insiders","User"),n=t==="darwin"?i(e,"Library","Application Support","Claude"):t==="win32"?i(e,"AppData","Roaming","Claude"):i(e,".config","claude"),r=t==="darwin"?i(e,".cursor"):t==="win32"?i(e,".cursor"):i(e,".cursor"),c=t==="darwin"?i(e,".codeium","windsurf"):t==="win32"?i(e,".codeium","windsurf"):i(e,".codeium","windsurf"),u=t==="darwin"?i(e,".config","zed"):t==="win32"?i(e,"AppData","Roaming","Zed"):i(e,".config","zed"),a=i(e,".continue");return[{id:"vscode-copilot",name:"VS Code (Copilot)",icon:"\u{1F4BB}",configPath:i(o,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"vscode-insiders",name:"VS Code Insiders",icon:"\u{1F7E2}",configPath:i(s,"settings.json"),configFormat:"vscode",mcpKey:"mcp.servers",detected:!1,configured:!1},{id:"claude-desktop",name:"Claude Desktop",icon:"\u{1F916}",configPath:i(n,"claude_desktop_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"claude-code",name:"Claude Code",icon:"\u2733\uFE0F",configPath:i(e,".claude.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"cursor",name:"Cursor",icon:"\u{1F4D0}",configPath:i(r,"mcp.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"windsurf",name:"Windsurf",icon:"\u{1F3C4}",configPath:i(c,"mcp_config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1},{id:"zed",name:"Zed",icon:"\u26A1",configPath:i(u,"settings.json"),configFormat:"zed",mcpKey:"context_servers",detected:!1,configured:!1},{id:"continue",name:"Continue",icon:"\u{1F504}",configPath:i(a,"config.json"),configFormat:"mcp-json",mcpKey:"mcpServers",detected:!1,configured:!1}]}function Z(e){try{const t=j()==="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"),s=K(o);t.configured=ee(s,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(`
2
+ `)[0];break}case"cursor":{const t=v("cursor",["--version"],{encoding:"utf-8",timeout:5e3});if(t.stdout)return t.stdout.split(`
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 E(){try{const s=v("which",["wyrm-mcp"],{encoding:"utf-8",timeout:5e3});if(s.stdout?.trim())return s.stdout.trim()}catch{}try{const n=v("npm",["root","-g"],{encoding:"utf-8",timeout:5e3}).stdout?.trim();if(n){const r=i(n,"wyrm-mcp","dist","index.js");if(d(r))return r}}catch{}const e=V(__dirname,"index.js");if(d(e))return e;const t=i(m(),".wyrm","node_modules","wyrm-mcp","dist","index.js");if(d(t))return t;const o=[i(m(),"Git Projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"projects","Wyrm","packages","mcp-server","dist","index.js"),i(m(),"dev","Wyrm","packages","mcp-server","dist","index.js")];for(const s of o)if(d(s))return s;return"wyrm-mcp"}function T(){return i(m(),".wyrm","wyrm.db")}function ye(e="mcp-json",t){const o=t?.serverPath??E(),s=t?.dbPath??T(),n=o==="wyrm-mcp",r={command:n?"npx":"node",args:n?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:s}};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,s=o==="wyrm-mcp",n=o.endsWith(".js");switch(e.configFormat){case"vscode":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"mcp-json":return{command:s?"npx":"node",args:s?["wyrm-mcp"]:[o],env:{WYRM_DB_PATH:t.dbPath}};case"zed":return{command:s?"npx":"node",args:s?["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 s={},n=!1;if(d(e.configPath))try{const a=p(e.configPath,"utf-8");s=K(a),n=!0}catch{s={}}let r;n&&(r=`${e.configPath}.wyrm-backup`,l(r,p(e.configPath)));const c=te(e,t);switch(e.configFormat){case"vscode":{s.mcp||(s.mcp={});const a=s.mcp;a.servers||(a.servers={});const f=a.servers;f.wyrm=c;break}case"mcp-json":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}case"zed":{s[e.mcpKey]||(s[e.mcpKey]={});const a=s[e.mcpKey];a.wyrm=c;break}}l(e.configPath,JSON.stringify(s,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 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=K(t),s=`${e.configPath}.wyrm-backup`;switch(l(s,t),e.configFormat){case"vscode":{const r=o.mcp?.servers;r&&delete r.wyrm;break}case"mcp-json":case"zed":{const n=o[e.mcpKey];n&&delete n.wyrm;break}}return l(e.configPath,JSON.stringify(o,null,2)+`
6
+ `,"utf-8"),e.configured=!1,{client:e,action:"configured",message:`Removed Wyrm from ${e.name}`,backup:s}}catch(t){return{client:e,action:"failed",message:`Failed to remove from ${e.name}: ${t}`}}}function A(){return{id:"claude-code-hooks",name:"Claude Code (auto-memory)",icon:"\u{1FA9D}",configPath:i(m(),".claude","settings.json"),configFormat:"mcp-json",mcpKey:"",detected:!0,configured:!1}}const D=[{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"]}];function se(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=i(e,"hooks");d(o)||P(o,{recursive:!0});const s=w(O(import.meta.url)),n=i(s,"..","scripts","hooks"),r=t.configPath;let c={};if(d(r))try{c=JSON.parse(p(r,"utf-8"))}catch{c={}}const u=c.hooks??{};let a=!1;const f=[];for(const h of D){const g=i(n,h.script);if(!d(g)){f.push(h.script);continue}const _=i(o,h.script);Y(g,_);try{q(_,493)}catch{}const C=`node ${_}`;for(const k of h.events){const $=typeof k=="string"?k:k.event,F=typeof k=="string"?void 0:k.matcher,W=Array.isArray(u[$])?u[$]:[];if(!W.some(b=>Array.isArray(b.hooks)&&b.hooks.some(z=>z.command===C))){const b=F?{matcher:F,hooks:[{type:"command",command:C,timeout:30}]}:{hooks:[{type:"command",command:C,timeout:30}]};W.push(b),a=!0}u[$]=W}}c.hooks=u,l(r,JSON.stringify(c,null,2)+`
7
+ `,"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 we(){const e=i(m(),".claude");if(!d(e))return null;const t=A();try{const o=w(O(import.meta.url)),s=`node ${i(o,"wyrm-statusline.js")}`,n=t.configPath;let r={};if(d(n))try{r=JSON.parse(p(n,"utf-8"))}catch{r={}}const c=r.statusLine;if(c?.command&&!/wyrm-statusline/.test(c.command))return{client:t,action:"skipped",message:`a non-Wyrm statusLine is already set (${c.command}); not overwriting`};const u=c?.command===s;return r.statusLine={type:"command",command:s,padding:0},l(n,JSON.stringify(r,null,2)+`
8
+ `,"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 ve(){const e=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=o.statusLine;return s?.command&&/wyrm-statusline/.test(s.command)?(delete o.statusLine,l(e,JSON.stringify(o,null,2)+`
9
+ `,"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=i(m(),".claude","settings.json");if(!d(e))return null;const t=A();try{const o=JSON.parse(p(e,"utf-8")),s=i(m(),".claude","hooks"),n=new Set(D.map(u=>`node ${i(s,u.script)}`)),r=new Set(D.flatMap(u=>u.events.map(a=>typeof a=="string"?a:a.event))),c=o.hooks;if(c){for(const u of r)Array.isArray(c[u])&&(c[u]=c[u].map(a=>({...a,hooks:Array.isArray(a.hooks)?a.hooks.filter(f=>!n.has(f.command??"")):a.hooks})).filter(a=>!Array.isArray(a.hooks)||a.hooks.length>0),c[u].length===0&&delete c[u]);Object.keys(c).length===0&&delete o.hooks}return l(e,JSON.stringify(o,null,2)+`
10
+ `,"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 U=/wyrm-guard/,ne=["Bash","Edit|Write|MultiEdit|NotebookEdit"];function x(e){return e.settingsPath??process.env.CLAUDE_SETTINGS??i(m(),".claude","settings.json")}function N(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 L(e){return j()==="win32"?`"${e.replace(/"/g,'""')}"`:`'${e.replace(/'/g,"'\\''")}'`}function ie(){const e=w(O(import.meta.url)),t=i(e,"wyrm-guard.js");if(d(t))return`node ${L(t)}`;try{const o=j()==="win32"?"where":"which",n=v(o,["wyrm-guard"],{encoding:"utf-8",timeout:5e3}).stdout?.trim().split(`
11
+ `)[0]?.trim();if(n)return L(n)}catch{}return null}function B(e){const t=e.hooks;if(!t||typeof t!="object"||Array.isArray(t))return!1;let o=!1;for(const s of Object.keys(t)){const n=t[s];if(!Array.isArray(n))continue;let r=!1;const c=[];for(const u of n){const a=u;if(!a||typeof a!="object"||!Array.isArray(a.hooks)){c.push(u);continue}const f=a.hooks.filter(y=>!U.test(y?.command??""));if(f.length===a.hooks.length){c.push(u);continue}o=!0,r=!0,f.length>0&&c.push({...a,hooks:f})}r&&(c.length===0?delete t[s]:t[s]=c)}return o&&Object.keys(t).length===0&&delete e.hooks,o}function ce(e={}){if(!!!(e.settingsPath??process.env.CLAUDE_SETTINGS)&&!d(i(m(),".claude")))return{client:N(x(e)),action:"skipped",message:"Claude Code not detected (~/.claude missing) \u2014 the failure firewall is NOT enforced on this machine; install Claude Code then re-run `wyrm guard`"};const o=x(e),s=N(o);try{const n=e.guardCommand??ie();if(!n)return{client:s,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:s,action:"failed",message:`${o} is not valid JSON \u2014 refusing to overwrite it (fix or remove the file, then re-run)`}}const c=r.hooks;if(c!==void 0&&(typeof c!="object"||c===null||Array.isArray(c)))return{client:s,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=c?.PreToolUse;if(u!==void 0&&!Array.isArray(u))return{client:s,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);B(r);const f=r.hooks??{},y=Array.isArray(f.PreToolUse)?f.PreToolUse:[];for(const g of ne)y.push({matcher:g,hooks:[{type:"command",command:n,timeout:1}]});f.PreToolUse=y,r.hooks=f;const h=JSON.stringify(r)!==a;return h&&l(o,JSON.stringify(r,null,2)+`
12
+ `,"utf-8"),s.configured=!0,{client:s,action:h?"configured":"skipped",message:h?"failure-firewall hooks installed (PreToolUse: Bash + Edit|Write|MultiEdit|NotebookEdit)":"failure-firewall hooks already installed"}}catch(n){return{client:s,action:"failed",message:n instanceof Error?n.message:String(n)}}}function ae(e={}){const t=x(e);if(!d(t))return null;const o=N(t);try{let s;try{const r=JSON.parse(p(t,"utf-8"));if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");s=r}catch{return{client:o,action:"failed",message:`${t} is not valid JSON \u2014 not touching it`}}return B(s)?(l(t,JSON.stringify(s,null,2)+`
13
+ `,"utf-8"),{client:o,action:"configured",message:"failure-firewall hooks removed"}):{client:o,action:"skipped",message:"no wyrm-guard hooks installed"}}catch(s){return{client:o,action:"failed",message:s instanceof Error?s.message:String(s)}}}function ke(e={}){const t=x(e),o=[];try{const n=JSON.parse(p(t,"utf-8")).hooks??{};for(const r of Object.values(n))if(Array.isArray(r))for(const c of r)for(const u of c?.hooks??[]){const a=u?.command;a&&U.test(a)&&o.push(a)}}catch{}return{settingsPath:t,installed:o.length>0,commands:o}}function ue(e){const t={serverPath:e?.serverPath||E(),dbPath:e?.dbPath||T(),httpPort:e?.httpPort},o=i(m(),".wyrm");d(o)||P(o,{recursive:!0}),H(t);const s=S(),n=[];for(const u of s)n.push(M(u,t));const r=se();r&&n.push(r);const c=ce();return c&&n.push(c),n}function Pe(){const e=S(),t=[];for(const n of e)t.push(oe(n));const o=re();o&&t.push(o);const s=ae();return s&&t.push(s),t}function de(e,t){const o={serverPath:t?.serverPath||E(),dbPath:t?.dbPath||T(),httpPort:t?.httpPort};H(o);const s=S(),n=[];for(const r of s)e.includes(r.id)&&n.push(M(r,o));return n}function H(e){const t=i(m(),".wyrm","wyrm-config.json"),o=S().filter(n=>n.configured).map(n=>n.id),s={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(s,null,2)+`
14
+ `,"utf-8")}function I(){const e=i(m(),".wyrm","wyrm-config.json");if(!d(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function Se(){const e=I();return e?de(e.configuredClients,{serverPath:e.serverPath,dbPath:e.dbPath,httpPort:e.httpPort}):ue()}const G="<!-- wyrm:start -->",R="<!-- wyrm:end -->",J=`${G}
14
15
  ## Wyrm Memory + Agent (auto-managed \u2014 do not edit this section)
15
16
 
16
17
  **Session start** \u2014 call \`wyrm_session_prime\` first to load project context (ground truths, open quests, recent sessions, scaffolds) before responding.
@@ -34,20 +35,20 @@ import{existsSync as d,readFileSync as p,writeFileSync as l,mkdirSync as k,copyF
34
35
  **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.
35
36
 
36
37
  **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.
37
- ${D}`;function Pe(e,t){const o={injected:[],skipped:[],errors:[]},s=(r,c)=>{try{const u=w(r);if(d(u)||k(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(G),y=a.indexOf(D);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+D.length);l(r,`${h}${N}${g}`,"utf-8")}else{const h=a.endsWith(`
38
+ ${R}`;function be(e,t){const o={injected:[],skipped:[],errors:[]},s=(r,c)=>{try{const u=w(r);if(d(u)||P(u,{recursive:!0}),d(r)){const a=p(r,"utf-8"),f=a.indexOf(G),y=a.indexOf(R);if(f!==-1&&y!==-1&&y>f){const h=a.slice(0,f),g=a.slice(y+R.length);l(r,`${h}${J}${g}`,"utf-8")}else{const h=a.endsWith(`
38
39
  `)?`
39
40
  `:`
40
41
 
41
- `;l(r,`${a}${h}${N}
42
- `,"utf-8")}}else l(r,`${N}
43
- `,"utf-8");o.injected.push(c)}catch(u){o.errors.push(`${c}: ${u}`)}},n=t.length===0?["copilot","cursor"]:t;for(const r of n){const c=r.toLowerCase();if(c==="copilot"||c==="vscode-copilot"){const u=i(e,".github","copilot-instructions.md");s(u,".github/copilot-instructions.md")}else if(c==="cursor"){const u=i(e,".cursor","rules");s(u,".cursor/rules")}else o.skipped.push(r)}return o}function R(e){let t=e.replace(/\/\/.*$/gm,"");return t=t.replace(/\/\*[\s\S]*?\*\//g,""),t=t.replace(/,\s*([\]}])/g,"$1"),JSON.parse(t)}function Se(){const e=S(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let s=`\u{F115D} Wyrm Auto-Configure Status
42
+ `;l(r,`${a}${h}${J}
43
+ `,"utf-8")}}else l(r,`${J}
44
+ `,"utf-8");o.injected.push(c)}catch(u){o.errors.push(`${c}: ${u}`)}},n=t.length===0?["copilot","cursor"]:t;for(const r of n){const c=r.toLowerCase();if(c==="copilot"||c==="vscode-copilot"){const u=i(e,".github","copilot-instructions.md");s(u,".github/copilot-instructions.md")}else if(c==="cursor"){const u=i(e,".cursor","rules");s(u,".cursor/rules")}else o.skipped.push(r)}return o}function K(e){let t=e.replace(/\/\/.*$/gm,"");return t=t.replace(/\/\*[\s\S]*?\*\//g,""),t=t.replace(/,\s*([\]}])/g,"$1"),JSON.parse(t)}function je(){const e=S(),t=e.filter(r=>r.detected),o=e.filter(r=>r.configured);let s=`\u{F115D} Wyrm Auto-Configure Status
44
45
 
45
46
  `;s+=` Detected: ${t.length}/${e.length} AI clients
46
47
  `,s+=` Configured: ${o.length}/${t.length} clients
47
48
 
48
49
  `;for(const r of e){const c=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";s+=`${c} ${r.icon} ${r.name}${u}: ${a}
49
- `}const n=H();return n&&(s+=`
50
+ `}const n=I();return n&&(s+=`
50
51
  Server: ${n.serverPath}
51
52
  `,s+=` DB: ${n.dbPath}
52
53
  `,s+=` Last: ${new Date(n.lastSetup).toLocaleString()}
53
- `),s}export{N as WYRM_INJECT_BLOCK,ae as autoConfigureAll,F as configureClient,ue as configureSpecific,S as detectClients,K as findWyrmServerPath,U as getDefaultDbPath,Se as getStatusSummary,Pe as injectSystemPrompt,oe as installClaudeCodeHooks,ge as installClaudeStatusline,ie as installWyrmGuardHooks,H as loadWyrmMeta,L as quoteHookPath,ke as reconfAll,se as removeClaudeCodeHooks,ye as removeClaudeStatusline,ve as removeFromAll,te as removeFromClient,ce as removeWyrmGuardHooks,ne as resolveWyrmGuardCommand,we as wyrmGuardHookStatus};
54
+ `),s}export{J as WYRM_INJECT_BLOCK,ue as autoConfigureAll,M as configureClient,de as configureSpecific,S as detectClients,ye as exportServerConfig,E as findWyrmServerPath,T as getDefaultDbPath,je as getStatusSummary,be as injectSystemPrompt,se as installClaudeCodeHooks,we as installClaudeStatusline,ce as installWyrmGuardHooks,I as loadWyrmMeta,L as quoteHookPath,Se as reconfAll,re as removeClaudeCodeHooks,ve as removeClaudeStatusline,Pe as removeFromAll,oe as removeFromClient,ae as removeWyrmGuardHooks,ie as resolveWyrmGuardCommand,ke as wyrmGuardHookStatus};
@@ -1,4 +1,4 @@
1
- import{applyBudget as w,resolveBudget as v}from"./token-budget.js";import{score as p}from"./context-ranking.js";function S(g){const{groundTruths:b,scaffoldLib:h,memory:y,toolAnalytics:$,sessionSeen:u,tokenEstimator:d,rankWeights:f}=g;return function(t){const o=v({callArg:t.maxTokens,envValue:process.env.WYRM_CONTEXT_TOKEN_BUDGET,clientName:null}),x=t.sessionId?u().getSeen(t.sessionId):new Set,a=b.formatForContext(t.project.id),r=d.count(a??"");let m=a??"",k=r;r>o*.5&&!t.strictBudget&&process.stderr.write(`[wyrm_context_build] ground truths consume ${r} of ${o}-token budget \u2014 body will be heavily elided. Pass strict_budget:true to elide truths too.
2
- `),t.strictBudget&&r>o*.5&&(m=(a??"").slice(0,o*2),k=d.count(m));const l=[],c=h.findBest(t.task,t.project.id);if(c){const e=h.formatForContext(c),i=c.scaffold.id;l.push({item:{kind:"scaffold",id:i,title:c.scaffold.problem_type,body:e},key:`scaffold:${i}`,inlineCost:d.count(e),stubCost:20,score:p({confidence:.9,relevance:.9,updatedAt:new Date().toISOString()},f)})}const B=y.recall(t.project.id,t.task,{limit:20,minConfidence:t.minConfidence??.2});for(const e of B){const i=e.artifact;if(t.kinds&&t.kinds.length>0&&!t.kinds.includes(i.kind))continue;const _=[`**${i.kind.toUpperCase()}** \xB7 ${i.problem}`,i.constraints?`Constraints: ${i.constraints}`:"",i.validated_fix?`Fix: ${i.validated_fix}`:"",i.why_it_worked?`Why: ${i.why_it_worked}`:""].filter(Boolean).join(`
3
- `);l.push({item:{kind:"memory",id:i.id,title:i.problem.slice(0,60),body:_},key:`memory:${i.id}`,inlineCost:d.count(_),stubCost:25,score:p({confidence:i.confidence,relevance:e.relevance_score,updatedAt:i.updated_at},f)})}const s=w(l,{budget:o,alreadySeen:x,reserved:t.strictBudget?0:k});t.sessionId&&(u().markBulk(t.sessionId,s.inline.map(e=>({id:e.id,kind:e.kind,mode:"inline"}))),u().markBulk(t.sessionId,s.elided.map(e=>({id:e.item.id,kind:e.item.kind,mode:"stub"}))));const n=[];n.push(`\u{F115D} **Context Brief** \u2014 "${t.task}"`),n.push(""),n.push("## Ground truths (stable preamble)"),n.push(""),n.push(m||"_no ground truths set for this project yet_"),n.push(""),n.push("## Task-relevant memory"),n.push("");for(const e of s.inline)n.push(`### [${e.kind}:${e.id}] ${e.title}`),n.push(e.body),n.push("");if(s.elided.length>0){n.push("## Elided to stubs"),n.push(""),n.push("_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_"),n.push("");for(const e of s.elided){const i=e.reason==="seen"?"shown earlier in session":`~${e.stubCost} tokens`;n.push(`- [${e.item.kind}:${e.item.id}] ${e.item.title} \xB7 ${i}`)}n.push("")}n.push("---"),n.push(`_Budget: ${s.tokensInline}/${o} tokens inline \xB7 ${s.elided.length} stub${s.elided.length===1?"":"s"} \xB7 estimator: ${d.source}_`);const C=n.join(`
4
- `);try{$.log({tool_name:"wyrm_context_build",project_id:t.project.id,args:{budget:o,items_total:l.length,items_inline:s.inline.length,items_elided:s.elided.length,tokens_inline:s.tokensInline,tokens_stubs:s.tokensStubs},success:!0,latency_ms:0})}catch{}return{content:[{type:"text",text:C}]}}}export{S as makeRunBudgetedContextBuild};
1
+ import{applyBudget as w,resolveBudget as v}from"./token-budget.js";import{autoSummaryPenalty as T,score as p}from"./context-ranking.js";function E(g){const{groundTruths:y,scaffoldLib:h,memory:b,toolAnalytics:$,sessionSeen:u,tokenEstimator:d,rankWeights:f}=g;return function(t){const o=v({callArg:t.maxTokens,envValue:process.env.WYRM_CONTEXT_TOKEN_BUDGET,clientName:null}),x=t.sessionId?u().getSeen(t.sessionId):new Set,a=y.formatForContext(t.project.id),r=d.count(a??"");let m=a??"",k=r;r>o*.5&&!t.strictBudget&&process.stderr.write(`[wyrm_context_build] ground truths consume ${r} of ${o}-token budget \u2014 body will be heavily elided. Pass strict_budget:true to elide truths too.
2
+ `),t.strictBudget&&r>o*.5&&(m=(a??"").slice(0,o*2),k=d.count(m));const l=[],c=h.findBest(t.task,t.project.id);if(c){const e=h.formatForContext(c),s=c.scaffold.id;l.push({item:{kind:"scaffold",id:s,title:c.scaffold.problem_type,body:e},key:`scaffold:${s}`,inlineCost:d.count(e),stubCost:20,score:p({confidence:.9,relevance:.9,updatedAt:new Date().toISOString()},f)})}const B=b.recall(t.project.id,t.task,{limit:20,minConfidence:t.minConfidence??.2});for(const e of B){const s=e.artifact;if(t.kinds&&t.kinds.length>0&&!t.kinds.includes(s.kind))continue;const _=[`**${s.kind.toUpperCase()}** \xB7 ${s.problem}`,s.constraints?`Constraints: ${s.constraints}`:"",s.validated_fix?`Fix: ${s.validated_fix}`:"",s.why_it_worked?`Why: ${s.why_it_worked}`:""].filter(Boolean).join(`
3
+ `);l.push({item:{kind:"memory",id:s.id,title:s.problem.slice(0,60),body:_},key:`memory:${s.id}`,inlineCost:d.count(_),stubCost:25,score:p({confidence:s.confidence,relevance:e.relevance_score,updatedAt:s.updated_at,sourcePenalty:T(s.problem)},f)})}const i=w(l,{budget:o,alreadySeen:x,reserved:t.strictBudget?0:k});t.sessionId&&(u().markBulk(t.sessionId,i.inline.map(e=>({id:e.id,kind:e.kind,mode:"inline"}))),u().markBulk(t.sessionId,i.elided.map(e=>({id:e.item.id,kind:e.item.kind,mode:"stub"}))));const n=[];n.push(`\u{F115D} **Context Brief** \u2014 "${t.task}"`),n.push(""),n.push("## Ground truths (stable preamble)"),n.push(""),n.push(m||"_no ground truths set for this project yet_"),n.push(""),n.push("## Task-relevant memory"),n.push("");for(const e of i.inline)n.push(`### [${e.kind}:${e.id}] ${e.title}`),n.push(e.body),n.push("");if(i.elided.length>0){n.push("## Elided to stubs"),n.push(""),n.push("_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_"),n.push("");for(const e of i.elided){const s=e.reason==="seen"?"shown earlier in session":`~${e.stubCost} tokens`;n.push(`- [${e.item.kind}:${e.item.id}] ${e.item.title} \xB7 ${s}`)}n.push("")}n.push("---"),n.push(`_Budget: ${i.tokensInline}/${o} tokens inline \xB7 ${i.elided.length} stub${i.elided.length===1?"":"s"} \xB7 estimator: ${d.source}_`);const C=n.join(`
4
+ `);try{$.log({tool_name:"wyrm_context_build",project_id:t.project.id,args:{budget:o,items_total:l.length,items_inline:i.inline.length,items_elided:i.elided.length,tokens_inline:i.tokensInline,tokens_stubs:i.tokensStubs},success:!0,latency_ms:0})}catch{}return{content:[{type:"text",text:C}]}}}export{E as makeRunBudgetedContextBuild};
@@ -1 +1 @@
1
- import{logger as l}from"./logger.js";const s={confidence:.4,recency:.2,relevance:.3,usefulness:.1};function p(e=process.env){const r=e.WYRM_RANK_WEIGHTS;if(!r)return{...s};try{const n=JSON.parse(r);return{confidence:c(n.confidence??s.confidence),recency:c(n.recency??s.recency),relevance:c(n.relevance??s.relevance),usefulness:c(n.usefulness??s.usefulness)}}catch(n){return l.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${n.message}`),{...s}}}function c(e){return Number.isFinite(e)?Math.max(0,Math.min(1,e)):0}function v(e,r,n=new Date){const t=c(e.confidence??.5),a=c(e.usefulness??.5),u=c(e.relevance??.5),o=e.updatedAt?Math.max(0,(n.getTime()-new Date(e.updatedAt).getTime())/(1440*60*1e3)):1/0,f=Number.isFinite(o)?Math.exp(-o/30):0,i=t*r.confidence+f*r.recency+u*r.relevance+a*r.usefulness;return c(i)}export{s as DEFAULT_WEIGHTS,p as loadWeightsFromEnv,v as score};
1
+ import{logger as d}from"./logger.js";const o={confidence:.4,recency:.2,relevance:.3,usefulness:.1};function v(e=process.env){const r=e.WYRM_RANK_WEIGHTS;if(!r)return{...o};try{const s=JSON.parse(r),n={confidence:c(s.confidence??o.confidence),recency:c(s.recency??o.recency),relevance:c(s.relevance??o.relevance),usefulness:c(s.usefulness??o.usefulness)},t=n.confidence+n.recency+n.relevance+n.usefulness;return t<=0?{...o}:(n.confidence/=t,n.recency/=t,n.relevance/=t,n.usefulness/=t,n)}catch(s){return d.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${s.message}`),{...o}}}function c(e){return Number.isFinite(e)?Math.max(0,Math.min(1,e)):0}const p=.5,y=/^\s*\[auto:(precompact|sessionend|sessionstart)/i;function A(e){return e&&y.test(e)?p:0}function M(e,r,s=new Date){const n=c(e.confidence??.5),t=c(e.usefulness??.5),a=c(e.relevance??.5),u=e.updatedAt?Math.max(0,(s.getTime()-new Date(e.updatedAt).getTime())/(1440*60*1e3)):1/0,f=Number.isFinite(u)?Math.exp(-u/30):0,i=n*r.confidence+f*r.recency+a*r.relevance+t*r.usefulness,l=c(e.sourcePenalty??0);return c(i*(1-l))}export{p as AUTO_SUMMARY_PENALTY,o as DEFAULT_WEIGHTS,A as autoSummaryPenalty,v as loadWeightsFromEnv,M as score};
package/dist/database.js CHANGED
@@ -1,4 +1,4 @@
1
- import k from"better-sqlite3";import{existsSync as u,mkdirSync as b,readdirSync as D,statSync as L,writeFileSync as I}from"fs";import{homedir as O}from"os";import{join as d,basename as A,resolve as M,normalize as w}from"path";import{spawnSync as R}from"child_process";import{getResilienceManager as v}from"./resilience.js";import{WyrmLogger as j}from"./logger.js";import{runMigrations as H,getSchemaVersion as F}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h}from"./security.js";import{emitEvent as m,eventsSince as W,subscribeEvents as U,isLiveMemoryEnabled as x,ingestRemoteEvent as P,eventsForPush as B,pruneEvents as q,getMeta as Y,setMeta as $}from"./events.js";import{getActor as C}from"./handlers/boundary.js";import{readSkillContent as y,slugify as G,skillContentSha as V}from"./skill-author.js";import{resolveProjectForPath as J}from"./project-resolve.js";function le(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(O(),".wyrm","wyrm.db")}class de{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(O(),".wyrm");u(e)||b(e,{recursive:!0}),this.dbPath=t||d(e,"wyrm.db"),this.logger=new j,this.resilience=v(),this.db=this.initializeDatabase(this.dbPath),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON");const s=H(this.db);s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${F(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>new k(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
1
+ import{openDatabase as k}from"./storage/index.js";import{existsSync as u,mkdirSync as b,readdirSync as D,statSync as L,writeFileSync as I}from"fs";import{homedir as O}from"os";import{join as d,basename as A,resolve as M,normalize as w}from"path";import{spawnSync as R}from"child_process";import{getResilienceManager as v}from"./resilience.js";import{WyrmLogger as j}from"./logger.js";import{runMigrations as H,getSchemaVersion as F}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h}from"./security.js";import{emitEvent as m,eventsSince as W,subscribeEvents as U,isLiveMemoryEnabled as x,ingestRemoteEvent as P,eventsForPush as B,pruneEvents as q,getMeta as Y,setMeta as $}from"./events.js";import{getActor as C}from"./handlers/boundary.js";import{readSkillContent as y,slugify as G,skillContentSha as V}from"./skill-author.js";import{resolveProjectForPath as J}from"./project-resolve.js";function le(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(O(),".wyrm","wyrm.db")}class de{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(O(),".wyrm");u(e)||b(e,{recursive:!0}),this.dbPath=t||d(e,"wyrm.db"),this.logger=new j,this.resilience=v(),this.db=this.initializeDatabase(this.dbPath),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON");const s=H(this.db);s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${F(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>k(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
2
2
  INSERT INTO watch_dirs (path, recursive)
3
3
  VALUES (?, ?)
4
4
  ON CONFLICT(path) DO UPDATE SET recursive = excluded.recursive
@@ -1,77 +1,77 @@
1
- import{join as H}from"path";import{TOOL_ANNOTATIONS as E}from"../tool-annotations.js";import{renderResult as q,withGlyph as I,renderErrorResponse as C,validationErrorBody as R}from"../render.js";import{ValidationError as B,asEnum as K}from"../validate.js";import{daemonOr as D}from"../daemon-writer.js";import{classifyCapture as J}from"../capture.js";import{extractCandidates as W,candidateToArtifact as O,escapeLikePattern as L}from"../auto-capture.js";import{parseTrace as Y,segmentsToText as Q}from"../trace-harvest.js";import{getActor as X,runWithActor as G}from"./boundary.js";import{harvestProjects as V}from"../harvest.js";import{readFileSync as F,readdirSync as z,statSync as M}from"fs";import{join as N}from"path";import{homedir as Z}from"os";import{readSpecDir as ee,specTaskSignature as te}from"../spec-kit.js";const ge=[{name:"wyrm_capture",description:"Use to save anything worth keeping - the single write funnel. Auto-classifies notes, decisions, and lessons into quest, ground truth, or memory; risky writes queue for wyrm_review. mode=direct (store a proven pattern or lesson learned so future agents reuse it), extract (pull memories from a conversation transcript or freeform text), trace (harvest a session JSONL / tool-call log into run-tagged review candidates), session (close out a work session into reusable knowledge), artifacts (harvest a repo's READMEs and recent commits into durable facts), import source=git|pr|rules, spec (turn a tasks.md into tracked work items), checklist. Keywords: remember this, jot down, write down, future reference.",inputSchema:{type:"object",properties:{content:{type:"string",description:"classify/rules"},project_id:{type:"number",description:"classify/import/spec"},tags:{type:"array",items:{type:"string"}},mode:{type:"string",enum:["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist"],description:"quest|truth|memory force the type"},source:{type:"string",enum:["git","pr","rules"],description:"import"},projectPath:{type:"string",description:"non-classify"},kind:{type:"string",description:"direct"},problem:{type:"string",description:"direct"},validatedFix:{type:"string",description:"direct"},text:{type:"string",description:"extract"},sessionId:{type:"string",description:"session"},candidates:{type:"array",items:{type:"object"},description:"session"},dryRun:{type:"boolean",description:"artifacts"},commits:{type:"array",items:{type:"object"},description:"import git"},title:{type:"string",description:"import pr"},body:{type:"string",description:"import pr"},specDir:{type:"string",description:"spec"},problemType:{type:"string",description:"checklist"},whenToUse:{type:"string",description:"checklist"},steps:{type:"array",items:{type:"string"},description:"checklist"}},required:[]},outputSchema:{type:"object",properties:{status:{type:"string",enum:["captured","queued_for_review"]},type:{type:"string"},subtype:{type:"string"},confidence:{type:"number"},reasoning:{type:"string"},id:{type:"integer"},ref:{type:"string"},needs_review:{type:"boolean"},reason:{type:"string"},artifact_id:{type:"integer"},conflicts_with:{type:"array"},advisory_conflicts:{type:"array"}},required:["status"]},annotations:E.wyrm_capture,aliases:[],handler:async(j,{store:v,raw:b,memory:u,truths:f,cache:_})=>{const{content:a,project_id:m,projectPath:o,tags:y,mode:i}=j;let d=J(a);i&&i!=="auto"&&(d={type:i,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[i]??i,confidence:100,reasoning:`Mode override: ${i}`});const{type:p,subtype:c,confidence:g,reasoning:r}=d,e=m??(o?v.getProject(o)?.id??null:null);let n=0,s="",h=!1,t;if(p==="quest"){if(e===null)return C(R("wyrm_capture","projectPath","is required for quest capture"));n=(await D("quest_add",e,{title:a.slice(0,200),description:"",priority:"medium",tags:y?.join(",")},()=>v.addQuest(e,a.slice(0,200),"","medium",y?.join(",")))).id,s="quest",_.invalidate("wyrm_all_quests"),_.invalidate("wyrm_stats")}else if(p==="truth"){if(e===null)return C(R("wyrm_capture","projectPath","is required for truth capture"));if(g>=80){const w=c??"other",S=f.getCurrent(e).filter(P=>P.category===w);if(S.length>0){const P={kind:"pattern",problem:`Potential conflict with ${S.length} existing truth(s) in category "${w}"`,validatedFix:a,whyItWorked:"Pending review \u2014 possible supersession",tags:y??[],confidence:g/100,needsReview:1},l=await D("artifact_add",e,P,()=>u.add(e,P));return q({status:"queued_for_review",reason:"conflict_check",artifact_id:l.id,conflicts_with:S.map(k=>({id:k.id,content:k.value.slice(0,80)}))})}}if(i!=="truth"&&g<100){const w={kind:"pattern",problem:a,validatedFix:"",whyItWorked:"",tags:y??[],confidence:g/100,needsReview:1};n=(await D("artifact_add",e,w,()=>u.add(e,w))).id,s="mem",h=!0}else{const w={category:"decision",key:a.slice(0,60),value:a};n=(await D("truth_set",e,w,()=>f.set(e,w))).id,s="truth",_.invalidate("wyrm_truth_get"),_.invalidate("wyrm_context_build")}}else{if(e===null)return C(R("wyrm_capture","projectPath","is required for memory capture"));const w=g>=75,x={kind:c,problem:a,validatedFix:"",whyItWorked:"",tags:y??[],confidence:g/100,needsReview:w?0:1};if(n=(await D("artifact_add",e,x,()=>u.add(e,x))).id,s="mem",h=!w,c==="heuristic"&&g>=75&&e!==null)try{const P=a.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),l=b().prepare(`
1
+ import{join as H}from"path";import{TOOL_ANNOTATIONS as A}from"../tool-annotations.js";import{renderResult as S,withGlyph as I,renderErrorResponse as E,validationErrorBody as T}from"../render.js";import{ValidationError as B,asEnum as K}from"../validate.js";import{daemonOr as D}from"../daemon-writer.js";import{classifyCapture as J}from"../capture.js";import{extractCandidates as W,candidateToArtifact as O,escapeLikePattern as L}from"../auto-capture.js";import{parseTrace as Y,segmentsToText as Q}from"../trace-harvest.js";import{getActor as X,runWithActor as G}from"./boundary.js";import{harvestProjects as V}from"../harvest.js";import{readFileSync as F,readdirSync as z,statSync as M}from"fs";import{join as N}from"path";import{homedir as Z}from"os";import{readSpecDir as ee,specTaskSignature as te}from"../spec-kit.js";const he=[{name:"wyrm_capture",description:"Use to save anything worth keeping - the single write funnel. Auto-classifies notes, decisions, and lessons into quest, ground truth, or memory; risky writes queue for wyrm_review. mode=direct (store a proven pattern or lesson learned so future agents reuse it), extract (pull memories from a conversation transcript or freeform text), trace (harvest a session JSONL / tool-call log into run-tagged review candidates), session (close out a work session into reusable knowledge), artifacts (harvest a repo's READMEs and recent commits into durable facts), import source=git|pr|rules, spec (turn a tasks.md into tracked work items), checklist. Keywords: remember this, jot down, write down, future reference.",inputSchema:{type:"object",properties:{content:{type:"string",description:"classify/rules"},project_id:{type:"number",description:"classify/import/spec"},tags:{type:"array",items:{type:"string"}},mode:{type:"string",enum:["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist"],description:"quest|truth|memory force the type"},source:{type:"string",enum:["git","pr","rules"],description:"import"},projectPath:{type:"string",description:"non-classify"},kind:{type:"string",description:"direct"},problem:{type:"string",description:"direct"},validatedFix:{type:"string",description:"direct"},text:{type:"string",description:"extract"},sessionId:{type:"string",description:"session"},candidates:{type:"array",items:{type:"object"},description:"session"},dryRun:{type:"boolean",description:"artifacts"},commits:{type:"array",items:{type:"object"},description:"import git"},title:{type:"string",description:"import pr"},body:{type:"string",description:"import pr"},specDir:{type:"string",description:"spec"},problemType:{type:"string",description:"checklist"},whenToUse:{type:"string",description:"checklist"},steps:{type:"array",items:{type:"string"},description:"checklist"}},required:[]},outputSchema:{type:"object",properties:{status:{type:"string",enum:["captured","queued_for_review"]},type:{type:"string"},subtype:{type:"string"},confidence:{type:"number"},reasoning:{type:"string"},id:{type:"integer"},ref:{type:"string"},needs_review:{type:"boolean"},reason:{type:"string"},artifact_id:{type:"integer"},conflicts_with:{type:"array"},advisory_conflicts:{type:"array"}},required:["status"]},annotations:A.wyrm_capture,aliases:[],handler:async(j,{store:v,raw:b,memory:u,truths:h,cache:w})=>{const{content:n,project_id:m,projectPath:o,tags:y,mode:i}=j;if(typeof n!="string"||!n.trim()){const f=j;return f.problem||f.validated_fix||f.validatedFix||f.kind?E(T("wyrm_capture","mode","you passed pattern/lesson fields (kind/problem/validated_fix) \u2014 set mode:'direct' to store them as a distilled artifact.")):E(T("wyrm_capture","content","content is required for auto capture (or use mode:'direct' with problem + validated_fix)."))}let d=J(n);i&&i!=="auto"&&(d={type:i,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[i]??i,confidence:100,reasoning:`Mode override: ${i}`});const{type:p,subtype:c,confidence:g,reasoning:r}=d,e=m??(o?v.getProject(o)?.id??null:null);let a=0,s="",_=!1,t;if(p==="quest"){if(e===null)return E(T("wyrm_capture","projectPath","is required for quest capture"));a=(await D("quest_add",e,{title:n.slice(0,200),description:"",priority:"medium",tags:y?.join(",")},()=>v.addQuest(e,n.slice(0,200),"","medium",y?.join(",")))).id,s="quest",w.invalidate("wyrm_all_quests"),w.invalidate("wyrm_stats")}else if(p==="truth"){if(e===null)return E(T("wyrm_capture","projectPath","is required for truth capture"));if(g>=80){const f=c??"other",q=h.getCurrent(e).filter(x=>x.category===f);if(q.length>0){const x={kind:"pattern",problem:`Potential conflict with ${q.length} existing truth(s) in category "${f}"`,validatedFix:n,whyItWorked:"Pending review \u2014 possible supersession",tags:y??[],confidence:g/100,needsReview:1},l=await D("artifact_add",e,x,()=>u.add(e,x));return S({status:"queued_for_review",reason:"conflict_check",artifact_id:l.id,conflicts_with:q.map(k=>({id:k.id,content:k.value.slice(0,80)}))})}}if(i!=="truth"&&g<100){const f={kind:"pattern",problem:n,validatedFix:"",whyItWorked:"",tags:y??[],confidence:g/100,needsReview:1};a=(await D("artifact_add",e,f,()=>u.add(e,f))).id,s="mem",_=!0}else{const f={category:"decision",key:n.slice(0,60),value:n};a=(await D("truth_set",e,f,()=>h.set(e,f))).id,s="truth",w.invalidate("wyrm_truth_get"),w.invalidate("wyrm_context_build")}}else{if(e===null)return E(T("wyrm_capture","projectPath","is required for memory capture"));const f=g>=75,P={kind:c,problem:n,validatedFix:"",whyItWorked:"",tags:y??[],confidence:g/100,needsReview:f?0:1};if(a=(await D("artifact_add",e,P,()=>u.add(e,P))).id,s="mem",_=!f,c==="heuristic"&&g>=75&&e!==null)try{const x=n.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),l=b().prepare(`
2
2
  SELECT a.id, a.problem FROM memory_artifacts a
3
3
  JOIN memory_artifacts_fts fts ON a.id = fts.rowid
4
4
  WHERE fts MATCH ? AND a.project_id = ? AND a.kind = 'heuristic' AND a.needs_review = 0
5
5
  LIMIT 3
6
- `).all(P,e);l.length>0&&(t=l.map(k=>({id:k.id,content:k.problem.slice(0,80)})))}catch{}}const $={status:"captured",type:p,subtype:c,confidence:g,reasoning:r,id:n,ref:`${s}:${n}`,needs_review:h,...t&&t.length>0?{advisory_conflicts:t}:{}};return q($,(w,x)=>{let S=`Captured as ${w.type}: ${w.subtype}
7
- Confidence: ${w.confidence}% | ${w.reasoning}
8
- ID: ${w.ref}`;w.needs_review&&(S+=`
9
- `+I(x.warn,"Stored for review -- run `wyrm_review` to activate"));const P=w.advisory_conflicts;return P&&P.length>0&&(S+=`
10
-
11
- `+I(x.warn,`Advisory: ${P.length} similar heuristic(s) found:`)+`
12
- `+P.map(l=>` ${x.bullet} [${l.id}] ${l.content}`).join(`
13
- `)),S})}},{name:"wyrm_remember",description:"Store a distilled knowledge artifact \u2014 proven patterns, lessons learned, anti-patterns, and reasoning traces. These are recalled automatically by wyrm_context_build to help AI models apply past knowledge to new tasks. Example: wyrm_remember({ projectPath: '/home/user/api', kind: 'anti_pattern', problem: 'Jest suite hung after adding the SSE server', validatedFix: 'close() the server in afterAll \u2014 its keep-alive timer held the event loop open', whyItWorked: 'open handles block jest exit', outcome: 'positive', tags: ['jest', 'sse'] })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project this knowledge belongs to"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"],description:"reasoning_trace: a solved problem with steps; lesson: a general insight; pattern: a proven approach; anti_pattern: what NOT to do; heuristic: a rule of thumb"},problem:{type:"string",description:"What was being solved or observed (be specific and searchable)"},constraints:{type:"string",description:"What conditions or constraints applied"},validatedFix:{type:"string",description:"What actually worked \u2014 the validated solution or approach"},whyItWorked:{type:"string",description:"The insight behind WHY this worked (key for transfer learning)"},outcome:{type:"string",enum:["positive","negative","neutral"],description:"Was this approach successful?"},tags:{type:"array",items:{type:"string"},description:"Searchable tags (e.g. ['auth', 'rate-limit', 'typescript'])"},confidence:{type:"number",description:"Confidence level 0.0\u20131.0 (default: 1.0)"},sourceSessionId:{type:"number",description:"Session ID where this was discovered (optional)"}},required:["projectPath","kind","problem"]},outputSchema:{type:"object",properties:{id:{type:"integer"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},problem:{type:"string"},validated_fix:{type:["string","null"]},why_it_worked:{type:["string","null"]},confidence:{type:"number"},tags:{type:["string","null"]}},required:["id","kind","problem","validated_fix","why_it_worked","confidence","tags"]},annotations:E.wyrm_remember,aliases:[],handler:async(j,{store:v,memory:b})=>{const{projectPath:u,kind:f,problem:_,constraints:a,validatedFix:m,whyItWorked:o,outcome:y,tags:i,confidence:d,sourceSessionId:p}=j,c=K("kind",f,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(c===void 0)throw new B("kind","is required");const g=v.getProject(u);if(!g)return C(R("wyrm_remember","projectPath","project not found"));const r={kind:c,problem:_,constraints:a,validatedFix:m,whyItWorked:o,outcome:y,tags:i,confidence:d,sourceSessionId:p},e=await D("artifact_add",g.id,r,()=>b.add(g.id,r)),n={id:e.id,kind:e.kind,problem:e.problem,validated_fix:e.validated_fix??null,why_it_worked:e.why_it_worked??null,confidence:e.confidence,tags:e.tags??null};return q(n,(s,h)=>{let t=I(h.brand,"**Memory Stored**")+`
14
-
15
- `;return t+=`${h.bullet} **Kind:** ${s.kind}
16
- `,t+=`${h.bullet} **ID:** ${s.id}
17
- `,t+=`${h.bullet} **Problem:** ${s.problem}
18
- `,s.validated_fix&&(t+=`${h.bullet} **Solution:** ${s.validated_fix}
19
- `),s.why_it_worked&&(t+=`${h.bullet} **Why it worked:** ${s.why_it_worked}
20
- `),t+=`${h.bullet} **Confidence:** ${(s.confidence*100).toFixed(0)}%
21
- `,s.tags&&(t+=`${h.bullet} **Tags:** ${s.tags}
22
- `),t+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",t})}},{name:"wyrm_auto_capture",description:"Auto-extract durable memories from freeform text (a session transcript, notes, a chat) into the REVIEW QUEUE. A LOCAL extractor \u2014 a configurable Ollama model (WYRM_EXTRACT_MODEL / the `model` arg) if set, else a deterministic heuristic \u2014 pulls out truths, failures, decisions, patterns, and lessons as needs_review candidates; approve/reject with wyrm_review (nothing auto-trusted). Closes the 'just talk, it remembers' gap without a cloud LLM. Deduped + idempotent. Example: wyrm_auto_capture({ projectPath: '/home/user/api', text: 'Deploy failed twice because wrangler.toml pointed at the old KV id; switched to the new binding and decided to pin binding names in CI.' })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the extracted candidates under"},text:{type:"string",description:"Freeform text to extract durable memories from"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic extraction"}},required:["projectPath","text"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","added","skipped","candidates"]},annotations:E.wyrm_auto_capture,aliases:[],handler:async(j,{store:v,raw:b,memory:u,cache:f})=>{const{projectPath:_,text:a,model:m}=j,o=v.getProject(_);if(!o)return C(R("wyrm_auto_capture","projectPath","project not found"));if(!a||a.trim().length<20)return C(R("wyrm_auto_capture","text","must be at least 20 characters"));const{candidates:y,method:i,model:d}=await W(a,{model:m}),p=b();let c=0,g=0;for(const e of y){const n=O(e),s=n.tags[n.tags.length-1];if(p.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(o.id,"%"+L(s)+"%")){g++;continue}u.add(o.id,{kind:n.kind,problem:n.problem,tags:n.tags,confidence:n.confidence,needsReview:1,createdBy:"auto-extract"}),c++}c>0&&f.invalidate();const r={method:i,model:d??null,added:c,skipped:g,candidates:y.slice(0,8).map(e=>({kind:e.kind,text:e.text.slice(0,80)}))};return q(r,(e,n)=>{const s=e.candidates.map(h=>` ${n.bullet} [${h.kind}] ${h.text}`).join(`
23
- `);return I(n.brand,`Auto-capture (${e.method}${e.model?` - ${e.model}`:""}) -- ${e.added} candidate(s) -> review queue, ${e.skipped} already present.`)+`
6
+ `).all(x,e);l.length>0&&(t=l.map(k=>({id:k.id,content:k.problem.slice(0,80)})))}catch{}}const $={status:"captured",type:p,subtype:c,confidence:g,reasoning:r,id:a,ref:`${s}:${a}`,needs_review:_,...t&&t.length>0?{advisory_conflicts:t}:{}};return S($,(f,P)=>{let q=`Captured as ${f.type}: ${f.subtype}
7
+ Confidence: ${f.confidence}% | ${f.reasoning}
8
+ ID: ${f.ref}`;f.needs_review&&(q+=`
9
+ `+I(P.warn,"Stored for review -- run `wyrm_review` to activate"));const x=f.advisory_conflicts;return x&&x.length>0&&(q+=`
10
+
11
+ `+I(P.warn,`Advisory: ${x.length} similar heuristic(s) found:`)+`
12
+ `+x.map(l=>` ${P.bullet} [${l.id}] ${l.content}`).join(`
13
+ `)),q})}},{name:"wyrm_remember",description:"Store a distilled knowledge artifact \u2014 proven patterns, lessons learned, anti-patterns, and reasoning traces. These are recalled automatically by wyrm_context_build to help AI models apply past knowledge to new tasks. Example: wyrm_remember({ projectPath: '/home/user/api', kind: 'anti_pattern', problem: 'Jest suite hung after adding the SSE server', validatedFix: 'close() the server in afterAll \u2014 its keep-alive timer held the event loop open', whyItWorked: 'open handles block jest exit', outcome: 'positive', tags: ['jest', 'sse'] })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project this knowledge belongs to"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"],description:"reasoning_trace: a solved problem with steps; lesson: a general insight; pattern: a proven approach; anti_pattern: what NOT to do; heuristic: a rule of thumb"},problem:{type:"string",description:"What was being solved or observed (be specific and searchable)"},constraints:{type:"string",description:"What conditions or constraints applied"},validatedFix:{type:"string",description:"What actually worked \u2014 the validated solution or approach"},whyItWorked:{type:"string",description:"The insight behind WHY this worked (key for transfer learning)"},outcome:{type:"string",enum:["positive","negative","neutral"],description:"Was this approach successful?"},tags:{type:"array",items:{type:"string"},description:"Searchable tags (e.g. ['auth', 'rate-limit', 'typescript'])"},confidence:{type:"number",description:"Confidence level 0.0\u20131.0 (default: 1.0)"},sourceSessionId:{type:"number",description:"Session ID where this was discovered (optional)"}},required:["projectPath","kind","problem"]},outputSchema:{type:"object",properties:{id:{type:"integer"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},problem:{type:"string"},validated_fix:{type:["string","null"]},why_it_worked:{type:["string","null"]},confidence:{type:"number"},tags:{type:["string","null"]}},required:["id","kind","problem","validated_fix","why_it_worked","confidence","tags"]},annotations:A.wyrm_remember,aliases:[],handler:async(j,{store:v,memory:b})=>{const{projectPath:u,kind:h,problem:w,constraints:n,validatedFix:m,whyItWorked:o,outcome:y,tags:i,confidence:d,sourceSessionId:p}=j,c=K("kind",h,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(c===void 0)throw new B("kind","is required");const g=v.getProject(u);if(!g)return E(T("wyrm_remember","projectPath","project not found"));const r={kind:c,problem:w,constraints:n,validatedFix:m,whyItWorked:o,outcome:y,tags:i,confidence:d,sourceSessionId:p},e=await D("artifact_add",g.id,r,()=>b.add(g.id,r)),a={id:e.id,kind:e.kind,problem:e.problem,validated_fix:e.validated_fix??null,why_it_worked:e.why_it_worked??null,confidence:e.confidence,tags:e.tags??null};return S(a,(s,_)=>{let t=I(_.brand,"**Memory Stored**")+`
14
+
15
+ `;return t+=`${_.bullet} **Kind:** ${s.kind}
16
+ `,t+=`${_.bullet} **ID:** ${s.id}
17
+ `,t+=`${_.bullet} **Problem:** ${s.problem}
18
+ `,s.validated_fix&&(t+=`${_.bullet} **Solution:** ${s.validated_fix}
19
+ `),s.why_it_worked&&(t+=`${_.bullet} **Why it worked:** ${s.why_it_worked}
20
+ `),t+=`${_.bullet} **Confidence:** ${(s.confidence*100).toFixed(0)}%
21
+ `,s.tags&&(t+=`${_.bullet} **Tags:** ${s.tags}
22
+ `),t+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",t})}},{name:"wyrm_auto_capture",description:"Auto-extract durable memories from freeform text (a session transcript, notes, a chat) into the REVIEW QUEUE. A LOCAL extractor \u2014 a configurable Ollama model (WYRM_EXTRACT_MODEL / the `model` arg) if set, else a deterministic heuristic \u2014 pulls out truths, failures, decisions, patterns, and lessons as needs_review candidates; approve/reject with wyrm_review (nothing auto-trusted). Closes the 'just talk, it remembers' gap without a cloud LLM. Deduped + idempotent. Example: wyrm_auto_capture({ projectPath: '/home/user/api', text: 'Deploy failed twice because wrangler.toml pointed at the old KV id; switched to the new binding and decided to pin binding names in CI.' })",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the extracted candidates under"},text:{type:"string",description:"Freeform text to extract durable memories from"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic extraction"}},required:["projectPath","text"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","added","skipped","candidates"]},annotations:A.wyrm_auto_capture,aliases:[],handler:async(j,{store:v,raw:b,memory:u,cache:h})=>{const{projectPath:w,text:n,model:m}=j,o=v.getProject(w);if(!o)return E(T("wyrm_auto_capture","projectPath","project not found"));if(!n||n.trim().length<20)return E(T("wyrm_auto_capture","text","must be at least 20 characters"));const{candidates:y,method:i,model:d}=await W(n,{model:m}),p=b();let c=0,g=0;for(const e of y){const a=O(e),s=a.tags[a.tags.length-1];if(p.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(o.id,"%"+L(s)+"%")){g++;continue}u.add(o.id,{kind:a.kind,problem:a.problem,tags:a.tags,confidence:a.confidence,needsReview:1,createdBy:"auto-extract"}),c++}c>0&&h.invalidate();const r={method:i,model:d??null,added:c,skipped:g,candidates:y.slice(0,8).map(e=>({kind:e.kind,text:e.text.slice(0,80)}))};return S(r,(e,a)=>{const s=e.candidates.map(_=>` ${a.bullet} [${_.kind}] ${_.text}`).join(`
23
+ `);return I(a.brand,`Auto-capture (${e.method}${e.model?` - ${e.model}`:""}) -- ${e.added} candidate(s) -> review queue, ${e.skipped} already present.`)+`
24
24
  ${s}
25
25
 
26
- Approve/reject with wyrm_review.`})}},{name:"wyrm_capture_trace",description:"Harvest a harness's working trace into run-tagged review-queue candidates \u2014 OFFLINE. Ingests a Claude Code session transcript (JSONL), a ~/.dragon/traces file/dir, or WYRM_TRACE_TOOL_CALLS event output. The SAME local extractor as auto-capture (Ollama WYRM_EXTRACT_MODEL if set, else deterministic \u2014 never a cloud LLM) pulls durable truths/failures/decisions/patterns/lessons; secrets (API keys, tokens, passwords) are REDACTED before anything is stored; candidates land needs_review=1 stamped with the run_id (arg or ambient envelope). Approve/reject with wyrm_review; deduped + idempotent. Pass `trace` (inline text) OR `path` (a trace file or the ~/.dragon/traces dir). Reached via wyrm_capture({ mode: 'trace' }).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the candidates under"},trace:{type:"string",description:"Inline trace text (JSONL transcript / dragon records / tool-call events)"},path:{type:"string",description:"Path to a trace file, or the ~/.dragon/traces directory (newest file is read); omit ~ for absolute"},format:{type:"string",enum:["auto","claude-jsonl","dragon","tool-calls"],description:"Trace format (default auto-detect)"},run_id:{type:"string",description:"Tag candidates with this run (default: ambient envelope run_id)"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic"}},required:["projectPath"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},format:{type:"string"},source:{type:"string"},run_id:{type:["string","null"]},segments:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","format","source","run_id","segments","added","skipped","candidates"]},annotations:E.wyrm_capture_trace,aliases:[],handler:async(j,{store:v,raw:b,memory:u,cache:f})=>{const{projectPath:_,trace:a,path:m,format:o,run_id:y,model:i}=j,d=v.getProject(_);if(!d)return C(R("wyrm_capture_trace","projectPath","project not found"));const p=X(),c=typeof y=="string"&&y.trim()?y.trim().slice(0,64):p.run_id;let g="",r="inline";if(typeof a=="string"&&a.trim())g=a,r="inline";else if(typeof m=="string"&&m.trim()){let l=m.trim();(l==="~"||l.startsWith("~/"))&&(l=N(Z(),l.slice(1)));try{if(M(l).isDirectory()){const T=z(l).map(A=>N(l,A)).filter(A=>{try{return M(A).isFile()}catch{return!1}}).sort((A,U)=>M(U).mtimeMs-M(A).mtimeMs);if(T.length===0)return C(R("wyrm_capture_trace","path","no trace files in directory"));g=F(T[0],"utf-8"),r=T[0]}else g=F(l,"utf-8"),r=l}catch(k){return C(R("wyrm_capture_trace","path",`could not read trace path: ${k.message}`))}}else return C(R("wyrm_capture_trace","trace","provide inline trace or path to trace file"));const{segments:e,format:n}=Y(g,o??"auto"),s=Q(e);if(s.trim().length<20){const l={method:"deterministic",model:null,format:n,source:r,run_id:c,segments:e.length,added:0,skipped:0,candidates:[]};return q(l,(k,T)=>I(T.brand,`Trace harvest (${k.format}) -- no durable content extracted from ${k.segments} segment(s).`))}const{candidates:h,method:t,model:$}=await W(s,{model:i}),w=b();let x=0,S=0;for(const l of h){const k=O(l),T=k.tags[k.tags.length-1];if(w.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(d.id,"%"+L(T)+"%")){S++;continue}const A=c?[...k.tags,"trace",`run:${c}`]:[...k.tags,"trace"];G({agent_id:p.agent_id,run_id:c,source:c?"param":p.source},()=>u.add(d.id,{kind:k.kind,problem:k.problem,tags:A,confidence:k.confidence,needsReview:1,createdBy:"trace-harvest"})),x++}x>0&&f.invalidate();const P={method:t,model:$??null,format:n,source:r,run_id:c,segments:e.length,added:x,skipped:S,candidates:h.slice(0,8).map(l=>({kind:l.kind,text:l.text.slice(0,80)}))};return q(P,(l,k)=>{const T=l.candidates.map(A=>` ${k.bullet} [${A.kind}] ${A.text}`).join(`
27
- `);return I(k.brand,`Trace harvest (${l.format}, ${l.method}${l.model?` - ${l.model}`:""}) -- ${l.segments} segment(s) -> ${l.added} candidate(s) to review queue, ${l.skipped} already present${l.run_id?` [run:${l.run_id}]`:""}.`)+(T?`
28
- ${T}`:"")+`
26
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_capture_trace",description:"Harvest a harness's working trace into run-tagged review-queue candidates \u2014 OFFLINE. Ingests a Claude Code session transcript (JSONL), a ~/.dragon/traces file/dir, or WYRM_TRACE_TOOL_CALLS event output. The SAME local extractor as auto-capture (Ollama WYRM_EXTRACT_MODEL if set, else deterministic \u2014 never a cloud LLM) pulls durable truths/failures/decisions/patterns/lessons; secrets (API keys, tokens, passwords) are REDACTED before anything is stored; candidates land needs_review=1 stamped with the run_id (arg or ambient envelope). Approve/reject with wyrm_review; deduped + idempotent. Pass `trace` (inline text) OR `path` (a trace file or the ~/.dragon/traces dir). Reached via wyrm_capture({ mode: 'trace' }).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to file the candidates under"},trace:{type:"string",description:"Inline trace text (JSONL transcript / dragon records / tool-call events)"},path:{type:"string",description:"Path to a trace file, or the ~/.dragon/traces directory (newest file is read); omit ~ for absolute"},format:{type:"string",enum:["auto","claude-jsonl","dragon","tool-calls"],description:"Trace format (default auto-detect)"},run_id:{type:"string",description:"Tag candidates with this run (default: ambient envelope run_id)"},model:{type:"string",description:"Optional Ollama model for extraction (overrides WYRM_EXTRACT_MODEL); omit for deterministic"}},required:["projectPath"]},outputSchema:{type:"object",properties:{method:{type:"string"},model:{type:["string","null"]},format:{type:"string"},source:{type:"string"},run_id:{type:["string","null"]},segments:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},candidates:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["method","model","format","source","run_id","segments","added","skipped","candidates"]},annotations:A.wyrm_capture_trace,aliases:[],handler:async(j,{store:v,raw:b,memory:u,cache:h})=>{const{projectPath:w,trace:n,path:m,format:o,run_id:y,model:i}=j,d=v.getProject(w);if(!d)return E(T("wyrm_capture_trace","projectPath","project not found"));const p=X(),c=typeof y=="string"&&y.trim()?y.trim().slice(0,64):p.run_id;let g="",r="inline";if(typeof n=="string"&&n.trim())g=n,r="inline";else if(typeof m=="string"&&m.trim()){let l=m.trim();(l==="~"||l.startsWith("~/"))&&(l=N(Z(),l.slice(1)));try{if(M(l).isDirectory()){const C=z(l).map(R=>N(l,R)).filter(R=>{try{return M(R).isFile()}catch{return!1}}).sort((R,U)=>M(U).mtimeMs-M(R).mtimeMs);if(C.length===0)return E(T("wyrm_capture_trace","path","no trace files in directory"));g=F(C[0],"utf-8"),r=C[0]}else g=F(l,"utf-8"),r=l}catch(k){return E(T("wyrm_capture_trace","path",`could not read trace path: ${k.message}`))}}else return E(T("wyrm_capture_trace","trace","provide inline trace or path to trace file"));const{segments:e,format:a}=Y(g,o??"auto"),s=Q(e);if(s.trim().length<20){const l={method:"deterministic",model:null,format:a,source:r,run_id:c,segments:e.length,added:0,skipped:0,candidates:[]};return S(l,(k,C)=>I(C.brand,`Trace harvest (${k.format}) -- no durable content extracted from ${k.segments} segment(s).`))}const{candidates:_,method:t,model:$}=await W(s,{model:i}),f=b();let P=0,q=0;for(const l of _){const k=O(l),C=k.tags[k.tags.length-1];if(f.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(d.id,"%"+L(C)+"%")){q++;continue}const R=c?[...k.tags,"trace",`run:${c}`]:[...k.tags,"trace"];G({agent_id:p.agent_id,run_id:c,source:c?"param":p.source},()=>u.add(d.id,{kind:k.kind,problem:k.problem,tags:R,confidence:k.confidence,needsReview:1,createdBy:"trace-harvest"})),P++}P>0&&h.invalidate();const x={method:t,model:$??null,format:a,source:r,run_id:c,segments:e.length,added:P,skipped:q,candidates:_.slice(0,8).map(l=>({kind:l.kind,text:l.text.slice(0,80)}))};return S(x,(l,k)=>{const C=l.candidates.map(R=>` ${k.bullet} [${R.kind}] ${R.text}`).join(`
27
+ `);return I(k.brand,`Trace harvest (${l.format}, ${l.method}${l.model?` - ${l.model}`:""}) -- ${l.segments} segment(s) -> ${l.added} candidate(s) to review queue, ${l.skipped} already present${l.run_id?` [run:${l.run_id}]`:""}.`)+(C?`
28
+ ${C}`:"")+`
29
29
 
30
- Approve/reject with wyrm_review.`})}},{name:"wyrm_distill",description:"Distill a work session into candidate memory artifacts for review. Parses the session's objectives, decisions, and outcomes into structured artifacts (lessons, patterns, anti-patterns) and queues them for approval via wyrm_review. Use at session end to extract institutional knowledge.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path"},sessionId:{type:"string",description:"Session ID to distill"},candidates:{type:"array",description:"Pre-parsed candidate artifacts to store for review",items:{type:"object",properties:{kind:{type:"string",enum:["lesson","pattern","anti_pattern","heuristic","reasoning_trace"]},title:{type:"string"},content:{type:"string"},tags:{type:"array",items:{type:"string"}},confidence:{type:"number"}},required:["kind","title","content"]}}},required:["projectPath","candidates"]},outputSchema:{type:"object",properties:{count:{type:"integer"},artifacts:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},title:{type:"string"}},required:["id","kind","title"]}}},required:["count","artifacts"]},annotations:E.wyrm_distill,aliases:[],handler:(j,{store:v,memory:b})=>{const{projectPath:u,candidates:f}=j,_=v.getProject(u);if(!_)return{content:[{type:"text",text:`Project not found: ${u}`}],isError:!0};if(!f||f.length===0)return{content:[{type:"text",text:`**Distill**
30
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_distill",description:"Distill a work session into candidate memory artifacts for review. Parses the session's objectives, decisions, and outcomes into structured artifacts (lessons, patterns, anti-patterns) and queues them for approval via wyrm_review. Use at session end to extract institutional knowledge.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root path"},sessionId:{type:"string",description:"Session ID to distill"},candidates:{type:"array",description:"Pre-parsed candidate artifacts to store for review",items:{type:"object",properties:{kind:{type:"string",enum:["lesson","pattern","anti_pattern","heuristic","reasoning_trace"]},title:{type:"string"},content:{type:"string"},tags:{type:"array",items:{type:"string"}},confidence:{type:"number"}},required:["kind","title","content"]}}},required:["projectPath","candidates"]},outputSchema:{type:"object",properties:{count:{type:"integer"},artifacts:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},title:{type:"string"}},required:["id","kind","title"]}}},required:["count","artifacts"]},annotations:A.wyrm_distill,aliases:[],handler:(j,{store:v,memory:b})=>{const{projectPath:u,candidates:h}=j,w=v.getProject(u);if(!w)return{content:[{type:"text",text:`Project not found: ${u}`}],isError:!0};if(!h||h.length===0)return{content:[{type:"text",text:`**Distill**
31
31
 
32
- No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const a=[];for(const o of f){const y=b.add(_.id,{kind:o.kind,problem:o.title,validatedFix:o.content,tags:o.tags??[],confidence:o.confidence??.7,needsReview:1});a.push({id:y.id,kind:o.kind,title:o.title})}const m={count:a.length,artifacts:a};return q(m,(o,y)=>{let i=I(y.brand,`**Distillation Complete** -- ${o.count} artifact${o.count!==1?"s":""} queued for review`)+`
32
+ No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const n=[];for(const o of h){const y=b.add(w.id,{kind:o.kind,problem:o.title,validatedFix:o.content,tags:o.tags??[],confidence:o.confidence??.7,needsReview:1});n.push({id:y.id,kind:o.kind,title:o.title})}const m={count:n.length,artifacts:n};return S(m,(o,y)=>{let i=I(y.brand,`**Distillation Complete** -- ${o.count} artifact${o.count!==1?"s":""} queued for review`)+`
33
33
 
34
34
  `;return i+=o.artifacts.map(d=>`${y.bullet} [#${d.id}] **${d.kind}**: ${d.title}`).join(`
35
- `),i+="\n\nUse `wyrm_review` with each ID to approve or reject.",i})}},{name:"wyrm_harvest",description:"Auto-populate memory from artifacts you already produce. Walks a project (or ALL registered projects) and harvests durable facts from its docs (README/CLAUDE/AGENTS/ARCHITECTURE) plus recent commit subjects (git log) into the REVIEW QUEUE \u2014 approve/reject with wyrm_review, nothing is auto-trusted. Idempotent (re-runs skip what's already harvested). Use dryRun to preview. The fix for a thin corpus.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Harvest one project; omit to harvest ALL registered projects"},dryRun:{type:"boolean",description:"Preview what would be harvested without writing anything"},gitLimit:{type:"number",description:"How many recent commits to scan per project (default 30, max 200)"},includeCode:{type:"boolean",description:"Also harvest CODE: tech-stack facts from manifests (package.json/Cargo.toml/\u2026) + TODO/FIXME markers into the review queue, AND index code symbols (functions/classes/types) for search. Heavier \u2014 scope to one project for the full code pass."}}},outputSchema:{type:"object",properties:{dry_run:{type:"boolean"},projects_scanned:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},top:{type:"array",items:{type:"object",properties:{project:{type:"string"},added:{type:"integer"},skipped:{type:"integer"}},required:["project","added","skipped"]}},sample:{type:"array",items:{type:"string"}},code_symbols:{type:["object","null"],properties:{symbols:{type:"integer"},projects:{type:"integer"},files:{type:"integer"}},required:["symbols","projects","files"]}},required:["dry_run","projects_scanned","added","skipped","top","sample","code_symbols"]},annotations:E.wyrm_harvest,aliases:[],handler:(j,{store:v,raw:b,memory:u,symbols:f,cache:_})=>{const{projectPath:a,dryRun:m,gitLimit:o,includeCode:y}=j,i=b(),d={existsBySig:(t,$)=>!!i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(t,"%"+L($)+"%"),addCandidate:(t,$)=>{u.add(t,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})}};let p;if(a){const t=v.getProject(a);if(!t)return{content:[{type:"text",text:"Project not found"}],isError:!0};p=[{id:t.id,name:t.name,path:t.path}]}else p=v.getAllProjects(500).map(t=>({id:t.id,name:t.name,path:t.path}));const{reports:c,totalAdded:g,totalSkipped:r}=V(d,p,{dryRun:m,gitLimit:o,includeCode:y});!m&&g>0&&_.invalidate();let e=null;if(y&&!m){let t=0,$=0,w=0;for(const x of p)try{const S=f.indexProject(x.id,x.path);t+=S.files,$+=S.symbols,w++}catch{}e={symbols:$,projects:w,files:t}}const n=c.filter(t=>t.added>0).sort((t,$)=>$.added-t.added).slice(0,15).map(t=>({project:t.project,added:t.added,skipped:t.skipped})),s=c.flatMap(t=>t.sample??[]).slice(0,6),h={dry_run:!!m,projects_scanned:p.length,added:g,skipped:r,top:n,sample:s,code_symbols:e};return q(h,(t,$)=>{const w=t.code_symbols?`
36
- Indexed ${t.code_symbols.symbols} code symbols across ${t.code_symbols.projects} project(s) (${t.code_symbols.files} files) -> searchable via wyrm_symbol_search.`:"",x=t.top.map(P=>` +${String(P.added).padStart(3)} (skip ${P.skipped}) ${P.project}`).join(`
37
- `),S=t.sample.map(P=>` ${$.bullet} ${P}`).join(`
38
- `);return I($.brand,`Harvest ${t.dry_run?"(dry run) ":""}-- ${t.added} candidate(s) -> review queue across ${t.projects_scanned} project(s); ${t.skipped} already present.`)+`${w}
39
- ${x}`+(S?`
35
+ `),i+="\n\nUse `wyrm_review` with each ID to approve or reject.",i})}},{name:"wyrm_harvest",description:"Auto-populate memory from artifacts you already produce. Walks a project (or ALL registered projects) and harvests durable facts from its docs (README/CLAUDE/AGENTS/ARCHITECTURE) plus recent commit subjects (git log) into the REVIEW QUEUE \u2014 approve/reject with wyrm_review, nothing is auto-trusted. Idempotent (re-runs skip what's already harvested). Use dryRun to preview. The fix for a thin corpus.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Harvest one project; omit to harvest ALL registered projects"},dryRun:{type:"boolean",description:"Preview what would be harvested without writing anything"},gitLimit:{type:"number",description:"How many recent commits to scan per project (default 30, max 200)"},includeCode:{type:"boolean",description:"Also harvest CODE: tech-stack facts from manifests (package.json/Cargo.toml/\u2026) + TODO/FIXME markers into the review queue, AND index code symbols (functions/classes/types) for search. Heavier \u2014 scope to one project for the full code pass."}}},outputSchema:{type:"object",properties:{dry_run:{type:"boolean"},projects_scanned:{type:"integer"},added:{type:"integer"},skipped:{type:"integer"},top:{type:"array",items:{type:"object",properties:{project:{type:"string"},added:{type:"integer"},skipped:{type:"integer"}},required:["project","added","skipped"]}},sample:{type:"array",items:{type:"string"}},code_symbols:{type:["object","null"],properties:{symbols:{type:"integer"},projects:{type:"integer"},files:{type:"integer"}},required:["symbols","projects","files"]}},required:["dry_run","projects_scanned","added","skipped","top","sample","code_symbols"]},annotations:A.wyrm_harvest,aliases:[],handler:(j,{store:v,raw:b,memory:u,symbols:h,cache:w})=>{const{projectPath:n,dryRun:m,gitLimit:o,includeCode:y}=j,i=b(),d={existsBySig:(t,$)=>!!i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(t,"%"+L($)+"%"),addCandidate:(t,$)=>{u.add(t,{kind:$.kind,problem:$.text,tags:[...$.tags,$.sig],confidence:$.confidence,needsReview:1,createdBy:"harvest"})}};let p;if(n){const t=v.getProject(n);if(!t)return{content:[{type:"text",text:"Project not found"}],isError:!0};p=[{id:t.id,name:t.name,path:t.path}]}else p=v.getAllProjects(500).map(t=>({id:t.id,name:t.name,path:t.path}));const{reports:c,totalAdded:g,totalSkipped:r}=V(d,p,{dryRun:m,gitLimit:o,includeCode:y});!m&&g>0&&w.invalidate();let e=null;if(y&&!m){let t=0,$=0,f=0;for(const P of p)try{const q=h.indexProject(P.id,P.path);t+=q.files,$+=q.symbols,f++}catch{}e={symbols:$,projects:f,files:t}}const a=c.filter(t=>t.added>0).sort((t,$)=>$.added-t.added).slice(0,15).map(t=>({project:t.project,added:t.added,skipped:t.skipped})),s=c.flatMap(t=>t.sample??[]).slice(0,6),_={dry_run:!!m,projects_scanned:p.length,added:g,skipped:r,top:a,sample:s,code_symbols:e};return S(_,(t,$)=>{const f=t.code_symbols?`
36
+ Indexed ${t.code_symbols.symbols} code symbols across ${t.code_symbols.projects} project(s) (${t.code_symbols.files} files) -> searchable via wyrm_symbol_search.`:"",P=t.top.map(x=>` +${String(x.added).padStart(3)} (skip ${x.skipped}) ${x.project}`).join(`
37
+ `),q=t.sample.map(x=>` ${$.bullet} ${x}`).join(`
38
+ `);return I($.brand,`Harvest ${t.dry_run?"(dry run) ":""}-- ${t.added} candidate(s) -> review queue across ${t.projects_scanned} project(s); ${t.skipped} already present.`)+`${f}
39
+ ${P}`+(q?`
40
40
 
41
41
  Sample:
42
- ${S}`:"")+`
42
+ ${q}`:"")+`
43
43
 
44
- Approve/reject with wyrm_review.`})}},{name:"wyrm_import_git",description:"Import a list of commits into the Wyrm memory review queue. Useful for onboarding a project's history or capturing recent work.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},commits:{type:"array",description:"Array of commit objects",items:{type:"object",properties:{message:{type:"string"},diff_summary:{type:"string"},author:{type:"string"},date:{type:"string"}},required:["message"]}},auto_approve:{type:"boolean",description:"Skip review queue and activate immediately (default: false)"}},required:["project_id","commits"]},outputSchema:{type:"object",properties:{captured:{type:"integer"},skipped:{type:"integer"},auto_approved:{type:"boolean"}},required:["captured","skipped","auto_approved"]},annotations:E.wyrm_import_git,aliases:[],handler:(j,{store:v,memory:b})=>{const{project_id:u,commits:f,auto_approve:_}=j;if(!v.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let m=0,o=0;for(const i of f){const d=i.message??"";if(/^Merge /i.test(d)){o++;continue}if(/^(chore|bump|release|version)/i.test(d)){o++;continue}let p="pattern";/^fix(\(.+\))?:/i.test(d)?p="lesson":/^feat(\(.+\))?:/i.test(d)?p="pattern":/^refactor(\(.+\))?:/i.test(d)&&(p="heuristic");const c=d.split(":")[0]??"commit";b.add(u,{kind:p,problem:d,validatedFix:i.diff_summary??"",whyItWorked:`Committed by ${i.author??"unknown"} on ${i.date??"unknown"}`,tags:["git","commit",c.toLowerCase()],confidence:.6,needsReview:_?0:1}),m++}return q({captured:m,skipped:o,auto_approved:!!_},(i,d)=>I(d.brand,"**Git Import Complete**")+`
44
+ Approve/reject with wyrm_review.`})}},{name:"wyrm_import_git",description:"Import a list of commits into the Wyrm memory review queue. Useful for onboarding a project's history or capturing recent work.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},commits:{type:"array",description:"Array of commit objects",items:{type:"object",properties:{message:{type:"string"},diff_summary:{type:"string"},author:{type:"string"},date:{type:"string"}},required:["message"]}},auto_approve:{type:"boolean",description:"Skip review queue and activate immediately (default: false)"}},required:["project_id","commits"]},outputSchema:{type:"object",properties:{captured:{type:"integer"},skipped:{type:"integer"},auto_approved:{type:"boolean"}},required:["captured","skipped","auto_approved"]},annotations:A.wyrm_import_git,aliases:[],handler:(j,{store:v,memory:b})=>{const{project_id:u,commits:h,auto_approve:w}=j;if(!v.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let m=0,o=0;for(const i of h){const d=i.message??"";if(/^Merge /i.test(d)){o++;continue}if(/^(chore|bump|release|version)/i.test(d)){o++;continue}let p="pattern";/^fix(\(.+\))?:/i.test(d)?p="lesson":/^feat(\(.+\))?:/i.test(d)?p="pattern":/^refactor(\(.+\))?:/i.test(d)&&(p="heuristic");const c=d.split(":")[0]??"commit";b.add(u,{kind:p,problem:d,validatedFix:i.diff_summary??"",whyItWorked:`Committed by ${i.author??"unknown"} on ${i.date??"unknown"}`,tags:["git","commit",c.toLowerCase()],confidence:.6,needsReview:w?0:1}),m++}return S({captured:m,skipped:o,auto_approved:!!w},(i,d)=>I(d.brand,"**Git Import Complete**")+`
45
45
 
46
46
  ${d.bullet} **Captured:** ${i.captured}
47
47
  ${d.bullet} **Skipped:** ${i.skipped} (merges/chores)
48
- ${d.bullet} **Review needed:** ${i.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${i.captured} artifact${i.captured!==1?"s":""}`}`)}},{name:"wyrm_import_pr",description:"Import a pull request (title, body, review comments) into the Wyrm memory review queue to extract patterns and heuristics.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},title:{type:"string",description:"PR title"},body:{type:"string",description:"PR description / body"},review_comments:{type:"array",items:{type:"string"},description:"Review comment strings"},auto_approve:{type:"boolean",description:"Skip review queue (default: false)"}},required:["project_id","title","body"]},outputSchema:{type:"object",properties:{created:{type:"integer"},auto_approved:{type:"boolean"}},required:["created","auto_approved"]},annotations:E.wyrm_import_pr,aliases:[],handler:(j,{store:v,memory:b})=>{const{project_id:u,title:f,body:_,review_comments:a,auto_approve:m}=j;if(!v.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let y=0;const i=m?0:1;b.add(u,{kind:"pattern",problem:`${f}
48
+ ${d.bullet} **Review needed:** ${i.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${i.captured} artifact${i.captured!==1?"s":""}`}`)}},{name:"wyrm_import_pr",description:"Import a pull request (title, body, review comments) into the Wyrm memory review queue to extract patterns and heuristics.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},title:{type:"string",description:"PR title"},body:{type:"string",description:"PR description / body"},review_comments:{type:"array",items:{type:"string"},description:"Review comment strings"},auto_approve:{type:"boolean",description:"Skip review queue (default: false)"}},required:["project_id","title","body"]},outputSchema:{type:"object",properties:{created:{type:"integer"},auto_approved:{type:"boolean"}},required:["created","auto_approved"]},annotations:A.wyrm_import_pr,aliases:[],handler:(j,{store:v,memory:b})=>{const{project_id:u,title:h,body:w,review_comments:n,auto_approve:m}=j;if(!v.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let y=0;const i=m?0:1;b.add(u,{kind:"pattern",problem:`${h}
49
49
 
50
- ${_}`,validatedFix:"",whyItWorked:"",tags:["git","pr"],confidence:.65,needsReview:i}),y++;for(const p of a??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(p)&&(b.add(u,{kind:"heuristic",problem:p,validatedFix:"",whyItWorked:"",tags:["git","pr","review"],confidence:.65,needsReview:i}),y++);return q({created:y,auto_approved:!!m},(p,c)=>I(c.brand,"**PR Import Complete**")+`
50
+ ${w}`,validatedFix:"",whyItWorked:"",tags:["git","pr"],confidence:.65,needsReview:i}),y++;for(const p of n??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(p)&&(b.add(u,{kind:"heuristic",problem:p,validatedFix:"",whyItWorked:"",tags:["git","pr","review"],confidence:.65,needsReview:i}),y++);return S({created:y,auto_approved:!!m},(p,c)=>I(c.brand,"**PR Import Complete**")+`
51
51
 
52
52
  ${c.bullet} **Artifacts created:** ${p.created}
53
- ${c.bullet} **Review needed:** ${p.auto_approved?"No (auto-approved)":"Yes -- run `wyrm_review` to activate"}`)}},{name:"wyrm_import_rules",description:"Import a .cursorrules / copilot-instructions file into Wyrm. Parses by section and creates ground truths (for constraint/rule language) or memory artifacts (for general guidelines).",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},content:{type:"string",description:"Full text of the rules file"},format:{type:"string",enum:["cursorrules","copilot","plain"],description:"Source format (default: plain)"},source_file:{type:"string",description:"Filename for provenance (optional)"}},required:["project_id","content"]},outputSchema:{type:"object",properties:{format:{type:"string"},source:{type:"string"},truths_created:{type:"integer"},artifacts_created:{type:"integer"}},required:["format","source","truths_created","artifacts_created"]},annotations:E.wyrm_import_rules,aliases:[],handler:(j,{store:v,memory:b,truths:u})=>{const{project_id:f,content:_,format:a,source_file:m}=j;if(!v.getProjectById(f))return{content:[{type:"text",text:`Project ID ${f} not found.`}],isError:!0};const y=a??"plain",i=m??"rules",d=["imported",y,i],p=_.split(/\n(?=#)/),c=_.split(/\n\n+/),g=(p.length>=c.length?p:c).map(s=>s.trim()).filter(s=>s.length>=15);let r=0,e=0;for(const s of g)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(s)?(u.set(f,{category:"constraint",key:s.slice(0,50).replace(/\n/g," "),value:s,source:i}),r++):(b.add(f,{kind:"heuristic",problem:s,validatedFix:"",whyItWorked:"",tags:d,confidence:.7,needsReview:1}),e++);return q({format:y,source:i,truths_created:r,artifacts_created:e},(s,h)=>I(h.brand,`**Rules Import Complete** (${s.format})`)+`
53
+ ${c.bullet} **Review needed:** ${p.auto_approved?"No (auto-approved)":"Yes -- run `wyrm_review` to activate"}`)}},{name:"wyrm_import_rules",description:"Import a .cursorrules / copilot-instructions file into Wyrm. Parses by section and creates ground truths (for constraint/rule language) or memory artifacts (for general guidelines).",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID"},content:{type:"string",description:"Full text of the rules file"},format:{type:"string",enum:["cursorrules","copilot","plain"],description:"Source format (default: plain)"},source_file:{type:"string",description:"Filename for provenance (optional)"}},required:["project_id","content"]},outputSchema:{type:"object",properties:{format:{type:"string"},source:{type:"string"},truths_created:{type:"integer"},artifacts_created:{type:"integer"}},required:["format","source","truths_created","artifacts_created"]},annotations:A.wyrm_import_rules,aliases:[],handler:(j,{store:v,memory:b,truths:u})=>{const{project_id:h,content:w,format:n,source_file:m}=j;if(!v.getProjectById(h))return{content:[{type:"text",text:`Project ID ${h} not found.`}],isError:!0};const y=n??"plain",i=m??"rules",d=["imported",y,i],p=w.split(/\n(?=#)/),c=w.split(/\n\n+/),g=(p.length>=c.length?p:c).map(s=>s.trim()).filter(s=>s.length>=15);let r=0,e=0;for(const s of g)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(s)?(u.set(h,{category:"constraint",key:s.slice(0,50).replace(/\n/g," "),value:s,source:i}),r++):(b.add(h,{kind:"heuristic",problem:s,validatedFix:"",whyItWorked:"",tags:d,confidence:.7,needsReview:1}),e++);return S({format:y,source:i,truths_created:r,artifacts_created:e},(s,_)=>I(_.brand,`**Rules Import Complete** (${s.format})`)+`
54
54
 
55
- ${h.bullet} **Ground truths created:** ${s.truths_created} (active immediately)
56
- ${h.bullet} **Memory artifacts created:** ${s.artifacts_created} (pending review)
57
- ${h.bullet} **Source:** ${s.source}
55
+ ${_.bullet} **Ground truths created:** ${s.truths_created} (active immediately)
56
+ ${_.bullet} **Memory artifacts created:** ${s.artifacts_created} (pending review)
57
+ ${_.bullet} **Source:** ${s.source}
58
58
 
59
- Run \`wyrm_review\` to approve the memory artifacts.`)}},{name:"wyrm_spec_register",description:"Make spec-kit Wyrm-native. Reads a spec directory (spec.md title/summary + tasks.md task list) and creates one Wyrm quest per task, linked to a project, with a stored spec\u2192project link. Idempotent \u2014 re-running updates the same quests (deduped by a per-spec-task signature) instead of duplicating.",inputSchema:{type:"object",properties:{specDir:{type:"string",description:"Absolute path to the spec directory (e.g. '.../dragon-platform/specs/001-ghostmesh')"},projectPath:{type:"string",description:"Path of the Wyrm project to attach the spec + quests to (must already be registered)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority for the generated quests (default 'medium')"}},required:["specDir","projectPath"]},outputSchema:{type:"object",properties:{spec_dir:{type:"string"},title:{type:["string","null"]},project:{type:"string"},tasks_parsed:{type:"integer"},quests_created:{type:"integer"},quests_updated:{type:"integer"},quest_ids:{type:"array",items:{type:"integer"}}},required:["spec_dir","title","project","tasks_parsed","quests_created","quests_updated","quest_ids"]},annotations:E.wyrm_spec_register,aliases:[],handler:(j,{store:v,indexing:b})=>{const{specDir:u,projectPath:f,priority:_}=j,a=["specDir","projectPath"].filter(r=>{const e=j[r];return!e&&e!==0&&e!==!1});if(a.length>0)return{content:[{type:"text",text:`Missing required arguments: ${a.join(", ")}`}],isError:!0};const m=v.getProject(f);if(!m)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${f}". Register it first (wyrm_scan_projects / wyrm_session_start).`}],isError:!0};const o=ee(u);if(o.tasks.length===0){const r={spec_dir:u,title:o.title??null,project:m.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return q(r,(e,n)=>I(n.brand,`**Spec Register**: No tasks found in ${H(e.spec_dir,"tasks.md")}. Nothing to create.`))}const y=_||"medium";let i=0,d=0;const p=[];for(const r of o.tasks){const e=te(u,r.id),n=`${r.id}: ${r.title}`,s=e,h=v.findQuestBySpecSignature(m.id,e);if(h)v.updateQuestFields(h.id,{title:n,tags:s}),p.push(h.id),d++;else{const t=v.addQuest(m.id,n,`spec-kit task from ${u}`,y,s);b()?.enqueue("quest",t.id,m.id),p.push(t.id),i++}}const c=v.upsertSpec(m.id,u,o.title,o.summary,o.tasks.length),g={spec_dir:c.spec_dir,title:c.title??null,project:m.name,tasks_parsed:o.tasks.length,quests_created:i,quests_updated:d,quest_ids:p};return q(g,(r,e)=>{let n=I(e.brand,"**Spec Registered**")+`
59
+ Run \`wyrm_review\` to approve the memory artifacts.`)}},{name:"wyrm_spec_register",description:"Make spec-kit Wyrm-native. Reads a spec directory (spec.md title/summary + tasks.md task list) and creates one Wyrm quest per task, linked to a project, with a stored spec\u2192project link. Idempotent \u2014 re-running updates the same quests (deduped by a per-spec-task signature) instead of duplicating.",inputSchema:{type:"object",properties:{specDir:{type:"string",description:"Absolute path to the spec directory (e.g. '.../dragon-platform/specs/001-ghostmesh')"},projectPath:{type:"string",description:"Path of the Wyrm project to attach the spec + quests to (must already be registered)"},priority:{type:"string",enum:["critical","high","medium","low"],description:"Priority for the generated quests (default 'medium')"}},required:["specDir","projectPath"]},outputSchema:{type:"object",properties:{spec_dir:{type:"string"},title:{type:["string","null"]},project:{type:"string"},tasks_parsed:{type:"integer"},quests_created:{type:"integer"},quests_updated:{type:"integer"},quest_ids:{type:"array",items:{type:"integer"}}},required:["spec_dir","title","project","tasks_parsed","quests_created","quests_updated","quest_ids"]},annotations:A.wyrm_spec_register,aliases:[],handler:(j,{store:v,indexing:b})=>{const{specDir:u,projectPath:h,priority:w}=j,n=["specDir","projectPath"].filter(r=>{const e=j[r];return!e&&e!==0&&e!==!1});if(n.length>0)return{content:[{type:"text",text:`Missing required arguments: ${n.join(", ")}`}],isError:!0};const m=v.getProject(h);if(!m)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${h}". Register it first (wyrm_scan_projects / wyrm_session_start).`}],isError:!0};const o=ee(u);if(o.tasks.length===0){const r={spec_dir:u,title:o.title??null,project:m.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return S(r,(e,a)=>I(a.brand,`**Spec Register**: No tasks found in ${H(e.spec_dir,"tasks.md")}. Nothing to create.`))}const y=w||"medium";let i=0,d=0;const p=[];for(const r of o.tasks){const e=te(u,r.id),a=`${r.id}: ${r.title}`,s=e,_=v.findQuestBySpecSignature(m.id,e);if(_)v.updateQuestFields(_.id,{title:a,tags:s}),p.push(_.id),d++;else{const t=v.addQuest(m.id,a,`spec-kit task from ${u}`,y,s);b()?.enqueue("quest",t.id,m.id),p.push(t.id),i++}}const c=v.upsertSpec(m.id,u,o.title,o.summary,o.tasks.length),g={spec_dir:c.spec_dir,title:c.title??null,project:m.name,tasks_parsed:o.tasks.length,quests_created:i,quests_updated:d,quest_ids:p};return S(g,(r,e)=>{let a=I(e.brand,"**Spec Registered**")+`
60
60
 
61
- `;return n+=`Spec: ${r.title||"(untitled)"}
62
- `,c.summary&&(n+=`Summary: ${c.summary}
63
- `),n+=`Dir: ${r.spec_dir}
64
- `,n+=`Project: ${r.project}
65
- `,n+=`Tasks parsed: ${r.tasks_parsed}
66
- `,n+=`Quests created: ${r.quests_created} - updated: ${r.quests_updated}
67
- `,n+=`
61
+ `;return a+=`Spec: ${r.title||"(untitled)"}
62
+ `,c.summary&&(a+=`Summary: ${c.summary}
63
+ `),a+=`Dir: ${r.spec_dir}
64
+ `,a+=`Project: ${r.project}
65
+ `,a+=`Tasks parsed: ${r.tasks_parsed}
66
+ `,a+=`Quests created: ${r.quests_created} - updated: ${r.quests_updated}
67
+ `,a+=`
68
68
  \`\`\`json
69
69
  ${JSON.stringify({spec:r.spec_dir,title:r.title,project:r.project,tasksParsed:r.tasks_parsed,questsCreated:r.quests_created,questsUpdated:r.quests_updated,questIds:r.quest_ids},null,2)}
70
- \`\`\``,n})}},{name:"wyrm_scaffold_save",description:"Save a reasoning scaffold \u2014 a structured checklist that guides step-by-step thinking for a specific problem type. Scaffolds are automatically surfaced by wyrm_context_build when the task matches, helping AI models apply proven reasoning patterns.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional, null for global scaffolds)"},problemType:{type:"string",description:"Short slug for the problem type (e.g. 'api-design', 'security-review', 'db-migration')"},description:{type:"string",description:"What kind of task this scaffold helps with"},whenToUse:{type:"string",description:"Conditions that indicate this scaffold should be applied"},steps:{type:"array",items:{type:"string"},description:"Ordered checklist steps to follow"},verificationSteps:{type:"array",items:{type:"string"},description:"Steps to verify the work is correct"},doNotApplyIf:{type:"string",description:"Conditions where this scaffold does NOT apply"},tags:{type:"array",items:{type:"string"},description:"Searchable tags"}},required:["problemType","description","whenToUse","steps"]},outputSchema:{type:"object",properties:{id:{type:"integer"},problem_type:{type:"string"},description:{type:"string"},steps:{type:"integer"}},required:["id","problem_type","description","steps"]},annotations:E.wyrm_scaffold_save,aliases:[],handler:(j,{store:v,scaffolds:b,cache:u})=>{const{projectPath:f,problemType:_,description:a,whenToUse:m,steps:o,verificationSteps:y,doNotApplyIf:i,tags:d}=j,p=f?v.getProject(f)?.id??void 0:void 0,c=b.save({projectId:p,problemType:_,description:a,whenToUse:m,steps:o,verificationSteps:y??void 0,doNotApplyIf:i?[i]:void 0,tags:d??[]});u.invalidate("wyrm_scaffold_get"),u.invalidate("wyrm_context_build");const g={id:c.id,problem_type:_,description:a,steps:o.length};return q(g,(r,e)=>I(e.brand,"**Reasoning Scaffold Saved**")+`
70
+ \`\`\``,a})}},{name:"wyrm_scaffold_save",description:"Save a reasoning scaffold \u2014 a structured checklist that guides step-by-step thinking for a specific problem type. Scaffolds are automatically surfaced by wyrm_context_build when the task matches, helping AI models apply proven reasoning patterns.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional, null for global scaffolds)"},problemType:{type:"string",description:"Short slug for the problem type (e.g. 'api-design', 'security-review', 'db-migration')"},description:{type:"string",description:"What kind of task this scaffold helps with"},whenToUse:{type:"string",description:"Conditions that indicate this scaffold should be applied"},steps:{type:"array",items:{type:"string"},description:"Ordered checklist steps to follow"},verificationSteps:{type:"array",items:{type:"string"},description:"Steps to verify the work is correct"},doNotApplyIf:{type:"string",description:"Conditions where this scaffold does NOT apply"},tags:{type:"array",items:{type:"string"},description:"Searchable tags"}},required:["problemType","description","whenToUse","steps"]},outputSchema:{type:"object",properties:{id:{type:"integer"},problem_type:{type:"string"},description:{type:"string"},steps:{type:"integer"}},required:["id","problem_type","description","steps"]},annotations:A.wyrm_scaffold_save,aliases:[],handler:(j,{store:v,scaffolds:b,cache:u})=>{const{projectPath:h,problemType:w,description:n,whenToUse:m,steps:o,verificationSteps:y,doNotApplyIf:i,tags:d}=j,p=h?v.getProject(h)?.id??void 0:void 0,c=b.save({projectId:p,problemType:w,description:n,whenToUse:m,steps:o,verificationSteps:y??void 0,doNotApplyIf:i?[i]:void 0,tags:d??[]});u.invalidate("wyrm_scaffold_get"),u.invalidate("wyrm_context_build");const g={id:c.id,problem_type:w,description:n,steps:o.length};return S(g,(r,e)=>I(e.brand,"**Reasoning Scaffold Saved**")+`
71
71
 
72
72
  ${e.bullet} **Problem Type:** ${r.problem_type}
73
73
  ${e.bullet} **Description:** ${r.description}
74
74
  ${e.bullet} **Steps:** ${r.steps} checklist items
75
75
  ${e.bullet} **ID:** ${r.id}
76
76
 
77
- This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${r.problem_type}".`)}}];export{ge as captureToolSpecs};
77
+ This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${r.problem_type}".`)}}];export{he as captureToolSpecs};