wyrm-mcp 9.2.1 → 9.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{randomBytes as y}from"node:crypto";import{closeSync as p,existsSync as S,mkdirSync as h,openSync as A,readFileSync as f,renameSync as x,statSync as O,unlinkSync as m,writeFileSync as w,writeSync as k}from"node:fs";import{homedir as g}from"node:os";import{dirname as N,join as M}from"node:path";const $=7,_=6e3,E=5e3;function C(){return M(g(),".wyrm","auto-runs.json")}const R="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function T(e){if(typeof e!="string"||!/^[0-9A-HJKMNP-TV-Z]{26}$/.test(e))return null;let r=0;for(let t=0;t<10;t++)r=r*32+R.indexOf(e[t]);return r}function d(e,r){const t={},c=[];for(const[o,u]of Object.entries(e)){const n=u;if(!n||typeof n!="object"||typeof n.run_id!="string"||n.run_id===""){c.push({session:o,runId:null,reason:"invalid"});continue}const i=n.run_id,a=r.runStatus?.(i);if(a!==void 0&&a!=="running"){c.push({session:o,runId:i,reason:"ended"});continue}const s=typeof n.t=="number"&&Number.isFinite(n.t)?n.t:T(i);if(s!==null&&r.now-s>r.maxAgeMs){c.push({session:o,runId:i,reason:"idle"});continue}t[o]=s===null?{...n,t:r.now}:n}return{next:t,removed:c}}function b(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}function j(e,r){const t=`${e}.lock`,c=`${process.pid}:${y(8).toString("hex")}`,o=Date.now()+_;for(h(N(e),{recursive:!0});;){try{const n=A(t,"wx",384);try{k(n,c)}finally{p(n)}break}catch(n){if(n.code!=="EEXIST")return!1}let u=!1;try{u=Date.now()-O(t).mtimeMs>E}catch(n){if(n.code==="ENOENT")continue}if(u)try{m(t);continue}catch{}if(Date.now()>=o)return!1;b(10+Math.floor(Math.random()*30))}try{return r(),!0}finally{try{f(t,"utf8")===c&&m(t)}catch{}}}function l(e){let r;try{r=f(e,"utf8")}catch(t){if(t.code==="ENOENT")return{outcome:"missing"};throw t}try{const t=JSON.parse(r);return!t||typeof t!="object"||Array.isArray(t)?{outcome:"corrupt"}:{store:t}}catch{return{outcome:"corrupt"}}}function I(e,r){if(!S(e))return{outcome:"missing"};if(r.dryRun){const o=l(e);return"outcome"in o?o:{outcome:"ok",total:Object.keys(o.store).length,removed:d(o.store,r).removed}}let t={outcome:"locked"};return j(e,()=>{const o=l(e);if("outcome"in o){t=o;return}const{next:u,removed:n}=d(o.store,r);if(JSON.stringify(u)!==JSON.stringify(o.store)){const i=`${e}.tmp.${process.pid}`;w(i,JSON.stringify(u),{mode:384}),x(i,e)}t={outcome:"ok",total:Object.keys(o.store).length,removed:n}})?t:{outcome:"locked"}}export{$ as AUTO_RUNS_MAX_AGE_DAYS,E as LOCK_STALE_MS,_ as LOCK_WAIT_MS,C as autoRunsPath,d as planAutoRunsPrune,I as pruneAutoRunsFile,T as ulidTimeMs,j as withAutoRunsLock};
@@ -1,4 +1,4 @@
1
- import{homedir as l}from"os";import{join as a,dirname as c}from"path";import{readFileSync as m,existsSync as y}from"fs";import{resolveEmbeddingState as h}from"./providers/embedding-provider.js";import{fileURLToPath as d}from"url";const u=d(import.meta.url),n=c(u),w=`Wyrm is not "AI memory" in the file-write sense. It's a layered intelligence
1
+ import{homedir as c}from"os";import{join as n,dirname as m}from"path";import{readFileSync as h,existsSync as d}from"fs";import{resolveEmbeddingState as u}from"./providers/embedding-provider.js";import{fileURLToPath as y}from"url";const p=y(import.meta.url),l=m(p),w=`Wyrm is not "AI memory" in the file-write sense. It's a layered intelligence
2
2
  substrate that solves the four specific problems AI assistants share:
3
3
 
4
4
  1. Amnesia. Sessions inherit prior state via lossless rehydration.
@@ -11,5 +11,5 @@ substrate that solves the four specific problems AI assistants share:
11
11
  Every Wyrm session is local-first SQLite with FTS5 + hybrid semantic search.
12
12
  Encryption is operator-controlled. Federation is opt-in per row. The audit
13
13
  log is hash-chained and Ed25519-signable for SOC2/HIPAA scenarios. None of
14
- this requires you to leave your laptop.`,p=[{name:"Counter-pattern memory",what:"Records failed approaches with (scope, target, description) and blocks the same suggestion next time.",why:"Other memory tools learn only from success. Wyrm refuses to repeat mistakes \u2014 saves the most expensive class of tokens (the regenerated wrong answer).",tools:["wyrm_failure_record","wyrm_failure_check","wyrm_failure_list","wyrm_failure_resolve"]},{name:"Ground truths (versioned, cascade-aware)",what:"Validated project facts (architecture, constraints, decisions) \u2014 each truth carries a version, last-verified timestamp, and outbound decision links.",why:"Truths are injected first into every wyrm_context_build call so the AI never works from contradicted assumptions. When a truth changes, dependent decisions auto-invalidate.",tools:["wyrm_truth_set","wyrm_truth_get","wyrm_decided_because","wyrm_decision_upstream","wyrm_decision_downstream","wyrm_decision_invalidate"]},{name:"Reasoning scaffolds",what:'Saved step-by-step checklists for problem types ("debug a slow query", "ship a migration") that surface automatically when a task description matches.',why:"Replaces ad-hoc rediscovery of process knowledge with structured prompts. Cheaper than re-explaining the playbook every session.",tools:["wyrm_scaffold_save","wyrm_scaffold_get"]},{name:"Lossless session rehydration",what:"wyrm_session_rehydrate produces a complete briefing markdown a fresh AI ingests to inherit prior state \u2014 decisions, open quests, failures, recent commits, files touched.",why:"Cross-session continuity without re-exploration. The single biggest token-saver in the toolkit.",tools:["wyrm_session_rehydrate","wyrm_session_prime","wyrm_session_start","wyrm_session_update"]},{name:"Distillation queue",what:"Candidate memory artifacts extracted from sessions land in a review queue; the operator approves or rejects each.",why:"Bayesian confidence updates after each review \u2014 Wyrm gets sharper with use without auto-trusting noise.",tools:["wyrm_distill","wyrm_review","wyrm_remember","wyrm_recall","wyrm_feedback"]},{name:"Knowledge graph",what:"Named entities + typed directional relationships, neighborhood traversal, shortest-path queries.",why:'When the AI asks "how does X relate to Y?", Wyrm answers in O(log n) instead of grepping the corpus.',tools:["wyrm_entity_add","wyrm_entity_link","wyrm_entity_search","wyrm_entity_graph","wyrm_entity_path","wyrm_entity_merge"]},{name:"Cross-repo symbol index",what:"Workspace-wide function/class/type index across TS/JS/Python/Rust/Go/PHP/Ruby with caller resolution.",why:`Symbol lookups don't require re-grepping every project. "Who calls handleAuth?" answers immediately.`,tools:["wyrm_symbol_index","wyrm_symbol_search","wyrm_symbol_callers","wyrm_symbol_stats"]},{name:"OODA agent loop (autonomous goals)",what:"wyrm_goal_set persists an objective with success criteria. The wyrm-loop daemon runs Observe\u2192Orient\u2192Decide\u2192Act every interval, dispatching whitelisted tools. Replayable, capped, auditable.",why:"Lets you hand the AI a multi-step objective and walk away. No data-exfil tools accessible from inside the loop.",tools:["wyrm_goal_set","wyrm_goal_list","wyrm_goal_pause","wyrm_goal_resume","wyrm_goal_complete","wyrm_goal_abandon","wyrm_goal_iterations","wyrm_act","wyrm_agent_init","wyrm_agent_status","wyrm_agent_stop","wyrm_agent_restart"]},{name:"Outbound MCP client",what:"Wyrm calls other MCP servers (GitHub, Slack, Linear, \u2026) over stdio. The agent loop dispatches them like internal tools.",why:"Composability: Wyrm becomes the orchestration layer over your entire MCP stack instead of a sibling.",tools:["wyrm_mcp_register","wyrm_mcp_list","wyrm_mcp_disable","wyrm_mcp_tools","wyrm_call_external"]},{name:"Hybrid semantic search",what:"wyrm_search fuses FTS5 keyword candidates with vector candidates (Ollama nomic-embed-text by default; OpenAI optional) via Reciprocal Rank Fusion.",why:`Catches both "exact term" and "semantically similar" hits in one call. Silently falls back to FTS5-only if Ollama isn't running.`,tools:["wyrm_search","wyrm_vector_setup"]},{name:"Multi-agent presence + work-stealing",what:"Claude, Copilot, Cursor, and Codex announce themselves via TTL heartbeats. Quests can be claimed exclusively.",why:"No two agents step on the same quest. The work-stealing semantics scale to teams.",tools:["wyrm_presence_announce","wyrm_presence_list","wyrm_presence_release","wyrm_quest_claim","wyrm_quest_release"]},{name:"Federated team sync",what:"Per-row is_shared flag, /sync/push endpoint, conflict-as-quest semantics. PII never leaves the box unless explicitly shared.",why:"Team memory without a SaaS. No silent overwrites \u2014 every conflict becomes a quest the operator resolves.",tools:["wyrm_share","wyrm_unshare","wyrm_sync","wyrm_sync_export","wyrm_sync_import","wyrm_sync_conflicts","wyrm_sync_resolve"]},{name:"Compliance audit trail",what:"Hash-chained audit log with optional Ed25519 signatures. wyrm_audit_export produces tamper-evident bundles.",why:"SOC2/HIPAA reviewers get a real artifact. The hash chain detects any tampering after the fact.",tools:["wyrm_audit_log","wyrm_audit_export","wyrm_audit_verify"]},{name:"Hour ledger + invoice generation",what:"Derives billable hours from session content and emits markdown invoices.",why:"For solo operators and small teams: Wyrm is also the time tracker. No second tool to keep in sync.",tools:["wyrm_hours_report","wyrm_invoice_generate","wyrm_cost_report"]},{name:"Self-improvement analytics",what:"Per-tool error rate, p50/p95 latency, cost-per-tool. Wyrm watches Wyrm.",why:"Lets you spot which tools are hot, which are slow, which fail \u2014 input for both ops tuning and product decisions.",tools:["wyrm_analytics_dashboard","wyrm_tool_analytics","wyrm_usage","wyrm_stats"]}];function _(){try{const t=[a(n,"..","package.json"),a(n,"..","..","package.json")];for(const e of t)if(y(e))return JSON.parse(m(e,"utf-8")).version??"unknown"}catch{}return"unknown"}function x(t,e,s){const o=process.env.WYRM_DB_PATH??a(l(),".wyrm","wyrm.db"),i=s.isAgentRunning(),r=h();return{product:"Wyrm",version:_(),pitch:w,install:{dbPath:o,nodeVersion:process.version,platform:`${process.platform}-${process.arch}`},runtime:{vectorProvider:s.detectVectorProvider?s.detectVectorProvider():r.resolved,vectorReason:r.reason,vectorFix:r.fix,encryption:s.hasEncryption(),federation:s.isFederationEnabled(),agentLoop:i===null?"unknown":i?"running":"stopped"},features:p,toolCount:e}}function S(t){const e=[];e.push(`# ${t.product} ${t.version} \u2014 Capability Briefing`),e.push(""),e.push(t.pitch),e.push("");const s=t.runtime.vectorProvider==="none"?"vectors OFF (FTS-only recall)":`${t.runtime.vectorProvider} embeddings`;e.push(`**Runtime:** ${t.toolCount} MCP tools \xB7 ${s} \xB7 ${t.runtime.encryption?"encryption ON":"encryption off"} \xB7 agent ${t.runtime.agentLoop}`),t.runtime.vectorReason&&(e.push(""),t.runtime.vectorProvider==="none"?e.push(`> WARNING: semantic recall degraded (${t.runtime.vectorReason}) - recall is FTS5 keyword-only. Fix: ${t.runtime.vectorFix}`):e.push(`> NOTE: embedding provider is advisory (${t.runtime.vectorReason}) - semantic recall is running. Fix: ${t.runtime.vectorFix}`)),e.push(`**Install:** ${t.install.platform} \xB7 Node ${t.install.nodeVersion} \xB7 DB ${t.install.dbPath}`),e.push(""),e.push('## What Wyrm gives you beyond "AI memory"'),e.push("");for(const o of t.features)e.push(`### ${o.name}`),e.push(`- **What:** ${o.what}`),e.push(`- **Why:** ${o.why}`),e.push(`- **Tools:** \`${o.tools.join("`, `")}\``),e.push("");return e.join(`
15
- `)}export{x as getCapabilities,S as renderCapabilityBriefing};
14
+ this requires you to leave your laptop.`,f=[{name:"Counter-pattern memory",what:"Records failed approaches with (scope, target, description) and blocks the same suggestion next time.",why:"Other memory tools learn only from success. Wyrm refuses to repeat mistakes \u2014 saves the most expensive class of tokens (the regenerated wrong answer).",tools:["wyrm_failure_record","wyrm_failure_check"],cli:["wyrm failure list","wyrm failure resolve <id>"]},{name:"Ground truths (versioned, cascade-aware)",what:"Validated project facts (architecture, constraints, decisions) \u2014 each truth carries a version, last-verified timestamp, and outbound decision links.",why:"Truths are injected first into every wyrm_context_build call so the AI never works from contradicted assumptions. When a truth changes, dependent decisions auto-invalidate.",tools:["wyrm_truth_set","wyrm_truth_get","wyrm_decided_because","wyrm_decision_trace action=upstream|downstream|invalidate"]},{name:"Reasoning scaffolds",what:'Saved step-by-step checklists for problem types ("debug a slow query", "ship a migration") that surface automatically when a task description matches.',why:"Replaces ad-hoc rediscovery of process knowledge with structured prompts. Cheaper than re-explaining the playbook every session.",tools:["wyrm_capture mode=checklist","wyrm_context_build"]},{name:"Lossless session rehydration",what:"wyrm_session action=rehydrate produces a complete briefing markdown a fresh AI ingests to inherit prior state: decisions, open quests, failures, recent commits, files touched.",why:"Cross-session continuity without re-exploration. The single biggest token-saver in the toolkit.",tools:["wyrm_session action=rehydrate","wyrm_session_prime","wyrm_session action=start|update"]},{name:"Distillation queue",what:"Candidate memory artifacts extracted from sessions land in a review queue; the operator approves or rejects each.",why:"Bayesian confidence updates after each review \u2014 Wyrm gets sharper with use without auto-trusting noise.",tools:["wyrm_capture mode=session","wyrm_review","wyrm_capture mode=direct","wyrm_recall","wyrm_capture mode=feedback"]},{name:"Knowledge graph",what:"Named entities + typed directional relationships, neighborhood traversal, shortest-path queries.",why:'When the AI asks "how does X relate to Y?", Wyrm answers in O(log n) instead of grepping the corpus.',tools:["wyrm_entity action=add|link|search|graph|path|merge"]},{name:"Cross-repo symbol index",what:"Workspace-wide function/class/type index across TS/JS/Python/Rust/Go/PHP/Ruby with caller resolution.",why:`Symbol lookups don't require re-grepping every project. "Who calls handleAuth?" answers immediately.`,tools:["wyrm_search type=symbols","wyrm_capture mode=artifacts includeCode=true"]},{name:"OODA agent loop (autonomous goals)",what:"wyrm_goal action=set persists an objective with success criteria. The wyrm-loop daemon runs Observe\u2192Orient\u2192Decide\u2192Act every interval, dispatching whitelisted tools. Replayable, capped, auditable.",why:"Lets you hand the AI a multi-step objective and walk away. No data-exfil tools accessible from inside the loop.",tools:["wyrm_goal action=set|list|pause|resume|complete|abandon|iterations","wyrm_act"],cli:["wyrm agent init","wyrm agent status","wyrm agent stop","wyrm agent restart"]},{name:"Outbound MCP client",what:"Wyrm calls other MCP servers (GitHub, Slack, Linear, \u2026) over stdio. The agent loop dispatches them like internal tools.",why:"Composability: Wyrm becomes the orchestration layer over your entire MCP stack instead of a sibling.",tools:["wyrm_mcp action=register|list|disable|tools","wyrm_call_external"]},{name:"Hybrid semantic search",what:"wyrm_search fuses FTS5 keyword candidates with vector candidates (Ollama nomic-embed-text by default; OpenAI optional) via Reciprocal Rank Fusion.",why:`Catches both "exact term" and "semantically similar" hits in one call. Silently falls back to FTS5-only if Ollama isn't running.`,tools:["wyrm_search"],cli:["wyrm index setup"]},{name:"Multi-agent presence + work-stealing",what:"Claude, Copilot, Cursor, and Codex announce themselves via TTL heartbeats. Quests can be claimed exclusively.",why:"No two agents step on the same quest. The work-stealing semantics scale to teams.",tools:["wyrm_presence action=announce|list|release","wyrm_quest action=claim|release"]},{name:"Federated team sync",what:"Per-row is_shared flag, /sync/push endpoint, conflict-as-quest semantics. PII never leaves the box unless explicitly shared.",why:"Team memory without a SaaS. No silent overwrites \u2014 every conflict becomes a quest the operator resolves.",tools:["wyrm_share","wyrm_replication action=unshare|conflicts|resolve","wyrm_project action=sync"],cli:["wyrm sync export --out <path>","wyrm sync import --from <path>"]},{name:"Compliance audit trail",what:"Hash-chained audit log with optional Ed25519 signatures. wyrm_audit action=export produces tamper-evident bundles.",why:"SOC2/HIPAA reviewers get a real artifact. The hash chain detects any tampering after the fact.",tools:["wyrm_audit action=log|export|verify"]},{name:"Hour ledger + invoice generation",what:"Derives billable hours from session content and emits markdown invoices.",why:"For solo operators and small teams: Wyrm is also the time tracker. No second tool to keep in sync.",tools:[],cli:["wyrm hours report","wyrm invoice generate"]},{name:"Self-improvement analytics",what:"Per-tool error rate, p50/p95 latency, cost-per-tool. Wyrm watches Wyrm.",why:"Lets you spot which tools are hot, which are slow, which fail \u2014 input for both ops tuning and product decisions.",tools:["wyrm_stats"]}];function g(){try{const e=[n(l,"..","package.json"),n(l,"..","..","package.json")];for(const t of e)if(d(t))return JSON.parse(h(t,"utf-8")).version??"unknown"}catch{}return"unknown"}function C(e,t,o,a){const s=process.env.WYRM_DB_PATH??n(c(),".wyrm","wyrm.db"),r=o.isAgentRunning(),i=u();return{product:"Wyrm",version:g(),pitch:w,install:{dbPath:s,nodeVersion:process.version,platform:`${process.platform}-${process.arch}`},runtime:{vectorProvider:o.detectVectorProvider?o.detectVectorProvider():i.resolved,vectorReason:i.reason,vectorFix:i.fix,encryption:o.hasEncryption(),federation:o.isFederationEnabled(),agentLoop:r===null?"unknown":r?"running":"stopped"},features:f,toolCount:t,...a!==void 0?{fullToolCount:a}:{}}}function T(e){const t=[];t.push(`# ${e.product} ${e.version} \u2014 Capability Briefing`),t.push(""),t.push(e.pitch),t.push("");const o=e.runtime.vectorProvider==="none"?"vectors OFF (FTS-only recall)":`${e.runtime.vectorProvider} embeddings`,a=e.fullToolCount!==void 0&&e.fullToolCount!==e.toolCount?`${e.toolCount} MCP tools (${e.fullToolCount} in the full profile)`:`${e.toolCount} MCP tools`;t.push(`**Runtime:** ${a} \xB7 ${o} \xB7 ${e.runtime.encryption?"encryption ON":"encryption off"} \xB7 agent ${e.runtime.agentLoop}`),e.runtime.vectorReason&&(t.push(""),e.runtime.vectorProvider==="none"?t.push(`> WARNING: semantic recall degraded (${e.runtime.vectorReason}) - recall is FTS5 keyword-only. Fix: ${e.runtime.vectorFix}`):t.push(`> NOTE: embedding provider is advisory (${e.runtime.vectorReason}) - semantic recall is running. Fix: ${e.runtime.vectorFix}`)),t.push(`**Install:** ${e.install.platform} \xB7 Node ${e.install.nodeVersion} \xB7 DB ${e.install.dbPath}`),t.push(""),t.push('## What Wyrm gives you beyond "AI memory"'),t.push("");for(const s of e.features)t.push(`### ${s.name}`),t.push(`- **What:** ${s.what}`),t.push(`- **Why:** ${s.why}`),s.tools.length>0&&t.push(`- **Tools:** \`${s.tools.join("`, `")}\``),s.cli&&s.cli.length>0&&t.push(`- **CLI:** \`${s.cli.join("`, `")}\``),t.push("");return t.join(`
15
+ `)}export{C as getCapabilities,T as renderCapabilityBriefing};
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import{readFileSync as g}from"fs";import{dirname as x,join as b}from"path";import{fileURLToPath as w}from"url";function f(){try{const t=x(w(import.meta.url));return JSON.parse(g(b(t,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}function M(t,n){const o=t.FORCE_COLOR;return o!==void 0?!(o==="0"||o.toLowerCase()==="false"):t.NO_COLOR!==void 0&&t.NO_COLOR!==""?!1:n===!0}const y=M(process.env,process.stdout.isTTY),C=t=>y?t:"",R={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},e=Object.fromEntries(Object.entries(R).map(([t,n])=>[t,C(n)])),$={bold:t=>`${e.bold}${t}${e.reset}`,dim:t=>`${e.dim}${t}${e.reset}`,red:t=>`${e.red}${t}${e.reset}`,green:t=>`${e.green}${t}${e.reset}`,yellow:t=>`${e.yellow}${t}${e.reset}`,blue:t=>`${e.blue}${t}${e.reset}`,magenta:t=>`${e.magenta}${t}${e.reset}`,cyan:t=>`${e.cyan}${t}${e.reset}`,primary:t=>`${e.brightMagenta}${t}${e.reset}`,success:t=>`${e.brightGreen}${t}${e.reset}`,warning:t=>`${e.brightYellow}${t}${e.reset}`,error:t=>`${e.brightRed}${t}${e.reset}`},p={dragon:"\u{F115D}",fire:"\u{1F525}",scroll:"\u{1F4DC}",sword:"\u2694\uFE0F",shield:"\u{1F6E1}\uFE0F",treasure:"\u{1F48E}",quest:"\u{1F3AF}",check:"\u2713",cross:"\u2717",arrow:"\u2192",bullet:"\u2022",star:"\u2605",warning:"\u26A0\uFE0F",error:"\u274C",success:"\u2705",info:"\u2139\uFE0F",clock:"\u{1F550}",folder:"\u{1F4C1}",file:"\u{1F4C4}",lock:"\u{1F512}",unlock:"\u{1F513}",sync:"\u{1F504}",search:"\u{1F50D}",sparkle:"\u2728"},v={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function P(){return`
1
+ import{readFileSync as x}from"fs";import{dirname as b,join as w}from"path";import{fileURLToPath as M}from"url";import{shouldUseColor as g}from"./color.js";function f(){try{const t=b(M(import.meta.url));return JSON.parse(x(w(t,"..","package.json"),"utf-8")).version??"dev"}catch{return"dev"}}const y=g(process.env,process.stdout.isTTY),S=t=>y?t:"",R={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",italic:"\x1B[3m",underline:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",brightRed:"\x1B[91m",brightGreen:"\x1B[92m",brightYellow:"\x1B[93m",brightBlue:"\x1B[94m",brightMagenta:"\x1B[95m",brightCyan:"\x1B[96m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m"},e=Object.fromEntries(Object.entries(R).map(([t,n])=>[t,S(n)])),$={bold:t=>`${e.bold}${t}${e.reset}`,dim:t=>`${e.dim}${t}${e.reset}`,red:t=>`${e.red}${t}${e.reset}`,green:t=>`${e.green}${t}${e.reset}`,yellow:t=>`${e.yellow}${t}${e.reset}`,blue:t=>`${e.blue}${t}${e.reset}`,magenta:t=>`${e.magenta}${t}${e.reset}`,cyan:t=>`${e.cyan}${t}${e.reset}`,primary:t=>`${e.brightMagenta}${t}${e.reset}`,success:t=>`${e.brightGreen}${t}${e.reset}`,warning:t=>`${e.brightYellow}${t}${e.reset}`,error:t=>`${e.brightRed}${t}${e.reset}`},u={dragon:"\u{F115D}",fire:"\u{1F525}",scroll:"\u{1F4DC}",sword:"\u2694\uFE0F",shield:"\u{1F6E1}\uFE0F",treasure:"\u{1F48E}",quest:"\u{1F3AF}",check:"\u2713",cross:"\u2717",arrow:"\u2192",bullet:"\u2022",star:"\u2605",warning:"\u26A0\uFE0F",error:"\u274C",success:"\u2705",info:"\u2139\uFE0F",clock:"\u{1F550}",folder:"\u{1F4C1}",file:"\u{1F4C4}",lock:"\u{1F512}",unlock:"\u{1F513}",sync:"\u{1F504}",search:"\u{1F50D}",sparkle:"\u2728"},L={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};function I(){return`
2
2
  ${e.brightMagenta}\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557${e.reset}
3
3
  ${e.brightMagenta}\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551${e.reset}
4
4
  ${e.brightMagenta}\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551${e.reset}
@@ -7,19 +7,19 @@ ${e.brightMagenta}\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D
7
7
  ${e.brightMagenta} \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D${e.reset}
8
8
  ${e.dim} Persistent AI Memory System v${f()}${e.reset}
9
9
  ${e.dim} ghosts.lk${e.reset}
10
- `}function E(){return`${e.brightMagenta}\u{F115D} Wyrm v${f()}${e.reset}`}class N{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(n="Loading..."){this.text=n}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${e.cyan}${this.frames[this.current]}${e.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(n){this.text=n}succeed(n){this.stop(),console.log(`\r${$.success("\u2713")} ${n||this.text}`)}fail(n){this.stop(),console.log(`\r${$.error("\u2717")} ${n||this.text}`)}warn(n){this.stop(),console.log(`\r${$.warning("!")} ${n||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class _{total;current=0;width;label;constructor(n,o="Progress",r=30){this.total=n,this.width=r,this.label=o}update(n,o){this.current=n,o&&(this.label=o),this.render()}increment(n){this.update(this.current+1,n)}render(){const n=Math.min(100,Math.floor(this.current/this.total*100)),o=Math.floor(this.current/this.total*this.width),r=this.width-o,s=`${e.brightMagenta}${"\u2588".repeat(o)}${e.dim}${"\u2591".repeat(r)}${e.reset}`,i=`${n}%`.padStart(4);process.stdout.write(`\r${this.label} ${s} ${i} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function I(t,n,o={}){const{padding:r=2,border:s=!0}=o,i=t.map((c,l)=>{const m=Math.max(...n.map(u=>(u[l]||"").length));return Math.max(c.length,m)}),a=(c,l)=>c.padEnd(l),h=i.map(c=>"\u2500".repeat(c+r)).join(s?"\u253C":" ");let d="";s&&(d+=`${e.dim}\u250C${i.map(c=>"\u2500".repeat(c+r)).join("\u252C")}\u2510${e.reset}
10
+ `}function N(){return`${e.brightMagenta}\u{F115D} Wyrm v${f()}${e.reset}`}class T{frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];current=0;interval=null;text;constructor(n="Loading..."){this.text=n}start(){this.interval=setInterval(()=>{process.stdout.write(`\r${e.cyan}${this.frames[this.current]}${e.reset} ${this.text}`),this.current=(this.current+1)%this.frames.length},80)}update(n){this.text=n}succeed(n){this.stop(),console.log(`\r${$.success("\u2713")} ${n||this.text}`)}fail(n){this.stop(),console.log(`\r${$.error("\u2717")} ${n||this.text}`)}warn(n){this.stop(),console.log(`\r${$.warning("!")} ${n||this.text}`)}stop(){this.interval&&(clearInterval(this.interval),this.interval=null,process.stdout.write("\r\x1B[K"))}}class A{total;current=0;width;label;constructor(n,o="Progress",r=30){this.total=n,this.width=r,this.label=o}update(n,o){this.current=n,o&&(this.label=o),this.render()}increment(n){this.update(this.current+1,n)}render(){const n=Math.min(100,Math.floor(this.current/this.total*100)),o=Math.floor(this.current/this.total*this.width),r=this.width-o,s=`${e.brightMagenta}${"\u2588".repeat(o)}${e.dim}${"\u2591".repeat(r)}${e.reset}`,i=`${n}%`.padStart(4);process.stdout.write(`\r${this.label} ${s} ${i} (${this.current}/${this.total})`)}complete(){this.update(this.total),console.log()}}function _(t,n,o={}){const{padding:r=2,border:s=!0}=o,i=t.map((c,l)=>{const p=Math.max(...n.map(m=>(m[l]||"").length));return Math.max(c.length,p)}),a=(c,l)=>c.padEnd(l),h=i.map(c=>"\u2500".repeat(c+r)).join(s?"\u253C":" ");let d="";s&&(d+=`${e.dim}\u250C${i.map(c=>"\u2500".repeat(c+r)).join("\u252C")}\u2510${e.reset}
11
11
  `),d+=(s?`${e.dim}\u2502${e.reset}`:"")+t.map((c,l)=>` ${$.bold(a(c,i[l]))} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
12
12
  `,s&&(d+=`${e.dim}\u251C${h}\u2524${e.reset}
13
- `);for(const c of n)d+=(s?`${e.dim}\u2502${e.reset}`:"")+c.map((l,m)=>` ${a(l||"",i[m])} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
14
- `;return s&&(d+=`${e.dim}\u2514${i.map(c=>"\u2500".repeat(c+r)).join("\u2534")}\u2518${e.reset}`),d}function T(t,n,o=50){const r=n.split(`
13
+ `);for(const c of n)d+=(s?`${e.dim}\u2502${e.reset}`:"")+c.map((l,p)=>` ${a(l||"",i[p])} `).join(s?`${e.dim}\u2502${e.reset}`:"")+(s?`${e.dim}\u2502${e.reset}`:"")+`
14
+ `;return s&&(d+=`${e.dim}\u2514${i.map(c=>"\u2500".repeat(c+r)).join("\u2534")}\u2518${e.reset}`),d}function G(t,n,o=50){const r=n.split(`
15
15
  `),s=Math.max(t.length,...r.map(a=>a.length),o);let i=`${e.dim}\u256D${"\u2500".repeat(s+2)}\u256E${e.reset}
16
16
  `;i+=`${e.dim}\u2502${e.reset} ${$.bold(t.padEnd(s))} ${e.dim}\u2502${e.reset}
17
17
  `,i+=`${e.dim}\u251C${"\u2500".repeat(s+2)}\u2524${e.reset}
18
18
  `;for(const a of r)i+=`${e.dim}\u2502${e.reset} ${a.padEnd(s)} ${e.dim}\u2502${e.reset}
19
- `;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function S(t){if(t===0)return"0 B";const n=1024,o=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(t)/Math.log(n));return`${(t/Math.pow(n,r)).toFixed(2)} ${o[r]}`}function A(t){return t<1e3?`${t}ms`:t<6e4?`${(t/1e3).toFixed(1)}s`:t<36e5?`${Math.floor(t/6e4)}m ${Math.floor(t%6e4/1e3)}s`:`${Math.floor(t/36e5)}h ${Math.floor(t%36e5/6e4)}m`}function G(t){console.log(`
20
- ${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function Y(t){console.log(`${p.success} ${$.success(t)}`)}function D(t){console.error(`${p.error} ${$.error(t)}`)}function F(t){console.log(`${p.warning} ${$.warning(t)}`)}function W(t){console.log(`${p.info} ${t}`)}function O(t,n){const o=Array.from({length:t.length+1},(r,s)=>[s,...Array(n.length).fill(0)]);for(let r=1;r<=n.length;r++)o[0][r]=r;for(let r=1;r<=t.length;r++)for(let s=1;s<=n.length;s++){const i=t[r-1]===n[s-1]?0:1;o[r][s]=Math.min(o[r-1][s]+1,o[r][s-1]+1,o[r-1][s-1]+i),r>1&&s>1&&t[r-1]===n[s-2]&&t[r-2]===n[s-1]&&(o[r][s]=Math.min(o[r][s],o[r-2][s-2]+1))}return o[t.length][n.length]}function K(t,n,o=3){const r=t.toLowerCase(),s=r.length<=4?1:2;return[...new Set(n)].map(i=>({cand:i,dist:O(r,i.toLowerCase())})).filter(({cand:i,dist:a})=>a<=s||r.length>=3&&i.toLowerCase().startsWith(r)).sort((i,a)=>i.dist-a.dist||i.cand.localeCompare(a.cand)).slice(0,o).map(({cand:i})=>i)}function U(t,n=process.platform){const o=n==="win32"?/^(.*?)[\\/]node_modules[\\/]wyrm-mcp[\\/]/i.exec(t):/^(.*)\/lib\/node_modules\/wyrm-mcp\//.exec(t);return!o||!o[1]||/[\\/](_npx|node_modules)([\\/]|$)/.test(o[1])?null:o[1]}function q(t){if(t.isTTY){let r=-1;return{onProgress:(s,i,a)=>{const h=Math.floor(i/a*100);h!==r&&(r=h,t.write(`\r ${s}: ${h}% `))},finish:()=>{t.write(`
21
- `)}}}const n=new Set,o=new Set;return{onProgress:(r,s,i)=>{n.has(r)||(n.add(r),t.write(` ${r}: downloading (${S(i)})
19
+ `;return i+=`${e.dim}\u2570${"\u2500".repeat(s+2)}\u256F${e.reset}`,i}function v(t){if(t===0)return"0 B";const n=1024,o=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(t)/Math.log(n));return`${(t/Math.pow(n,r)).toFixed(2)} ${o[r]}`}function O(t){return t<1e3?`${t}ms`:t<6e4?`${(t/1e3).toFixed(1)}s`:t<36e5?`${Math.floor(t/6e4)}m ${Math.floor(t%6e4/1e3)}s`:`${Math.floor(t/36e5)}h ${Math.floor(t%36e5/6e4)}m`}function Y(t){console.log(`
20
+ ${$.bold($.primary(`\u25B6 ${t}`))}`),console.log(`${e.dim}${"\u2500".repeat(t.length+4)}${e.reset}`)}function D(t){console.log(`${u.success} ${$.success(t)}`)}function F(t){console.error(`${u.error} ${$.error(t)}`)}function W(t){console.log(`${u.warning} ${$.warning(t)}`)}function K(t){console.log(`${u.info} ${t}`)}function B(t,n){const o=Array.from({length:t.length+1},(r,s)=>[s,...Array(n.length).fill(0)]);for(let r=1;r<=n.length;r++)o[0][r]=r;for(let r=1;r<=t.length;r++)for(let s=1;s<=n.length;s++){const i=t[r-1]===n[s-1]?0:1;o[r][s]=Math.min(o[r-1][s]+1,o[r][s-1]+1,o[r-1][s-1]+i),r>1&&s>1&&t[r-1]===n[s-2]&&t[r-2]===n[s-1]&&(o[r][s]=Math.min(o[r][s],o[r-2][s-2]+1))}return o[t.length][n.length]}function U(t,n,o=3){const r=t.toLowerCase(),s=r.length<=4?1:2;return[...new Set(n)].map(i=>({cand:i,dist:B(r,i.toLowerCase())})).filter(({cand:i,dist:a})=>a<=s||r.length>=3&&i.toLowerCase().startsWith(r)).sort((i,a)=>i.dist-a.dist||i.cand.localeCompare(a.cand)).slice(0,o).map(({cand:i})=>i)}function q(t,n=process.platform){const o=n==="win32"?/^(.*?)[\\/]node_modules[\\/]wyrm-mcp[\\/]/i.exec(t):/^(.*)\/lib\/node_modules\/wyrm-mcp\//.exec(t);return!o||!o[1]||/[\\/](_npx|node_modules)([\\/]|$)/.test(o[1])?null:o[1]}function J(t){if(t.isTTY){let r=-1;return{onProgress:(s,i,a)=>{const h=Math.floor(i/a*100);h!==r&&(r=h,t.write(`\r ${s}: ${h}% `))},finish:()=>{t.write(`
21
+ `)}}}const n=new Set,o=new Set;return{onProgress:(r,s,i)=>{n.has(r)||(n.add(r),t.write(` ${r}: downloading (${v(i)})
22
22
  `)),s>=i&&!o.has(r)&&(o.add(r),t.write(` ${r}: done
23
- `))},finish:()=>{}}}class B extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function J(t,n={}){const o=n.input??process.stdin,r=n.output??process.stdout,s=typeof o.setRawMode=="function";r.write(t),s&&o.setRawMode(!0),o.resume();let i="";return new Promise((a,h)=>{const d=m=>{for(const u of m){if(u===13||u===10){l(),r.write(`
24
- `),a(i);return}if(u===3){l(),h(new B);return}if(u===4){l(),r.write(`
25
- `),a(i);return}if(u===127||u===8){i.length>0&&(i=i.slice(0,-1),r.write("\b \b"));continue}i+=String.fromCharCode(u),r.write("*")}},c=()=>{l(),a(i)};function l(){s&&o.setRawMode(!1),o.pause(),o.removeListener("data",d),o.removeListener("end",c)}o.on("data",d),o.on("end",c)})}export{P as BANNER,E as MINI_BANNER,_ as ProgressBar,B as ReadSecretCanceled,N as Spinner,$ as c,e as colors,q as createDownloadProgress,O as editDistance,T as formatBox,S as formatBytes,A as formatDuration,I as formatTable,p as icons,U as npmGlobalPrefixFromPath,D as printError,W as printInfo,G as printSection,Y as printSuccess,F as printWarning,v as priorityIcons,J as readSecret,M as shouldUseColor,K as suggestClosest};
23
+ `))},finish:()=>{}}}class C extends Error{constructor(){super("readSecret canceled"),this.name="ReadSecretCanceled"}}async function V(t,n={}){const o=n.input??process.stdin,r=n.output??process.stdout,s=typeof o.setRawMode=="function";r.write(t),s&&o.setRawMode(!0),o.resume();let i="";return new Promise((a,h)=>{const d=p=>{for(const m of p){if(m===13||m===10){l(),r.write(`
24
+ `),a(i);return}if(m===3){l(),h(new C);return}if(m===4){l(),r.write(`
25
+ `),a(i);return}if(m===127||m===8){i.length>0&&(i=i.slice(0,-1),r.write("\b \b"));continue}i+=String.fromCharCode(m),r.write("*")}},c=()=>{l(),a(i)};function l(){s&&o.setRawMode(!1),o.pause(),o.removeListener("data",d),o.removeListener("end",c)}o.on("data",d),o.on("end",c)})}export{I as BANNER,N as MINI_BANNER,A as ProgressBar,C as ReadSecretCanceled,T as Spinner,$ as c,e as colors,J as createDownloadProgress,B as editDistance,G as formatBox,v as formatBytes,O as formatDuration,_ as formatTable,u as icons,q as npmGlobalPrefixFromPath,F as printError,K as printInfo,Y as printSection,D as printSuccess,W as printWarning,L as priorityIcons,V as readSecret,g as shouldUseColor,U as suggestClosest};
package/dist/color.js ADDED
@@ -0,0 +1 @@
1
+ function O(e,o){const r=e.FORCE_COLOR;return r!==void 0?!(r==="0"||r.toLowerCase()==="false"):e.NO_COLOR!==void 0&&e.NO_COLOR!==""?!1:o===!0}export{O as shouldUseColor};
package/dist/database.js CHANGED
@@ -30,7 +30,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
30
30
  INSERT INTO sessions (project_id, date, objectives, completed, issues, commits, files_changed, notes, tokens_estimate, agent_id, run_id)
31
31
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
32
  RETURNING *
33
- `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return m(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
33
+ `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return m(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&o!==void 0&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
34
34
  UPDATE sessions SET ${s.join(", ")} WHERE id = ? RETURNING *
35
35
  `).get(...r),"updateSession");if(!n.success)throw n.error||new Error("Failed to update session");return m(this.db,{projectId:n.data.project_id,kind:"session_update",refTable:"sessions",refId:t}),n.data}getSession(t){return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(t)}updateContinuity(t,e,s){const r=e.trim();if(!r)return;const i=12e3,n=this.db.prepare("SELECT thread, turn_count FROM project_continuity WHERE project_id = ?").get(t),a=new Date().toISOString(),o=`[${a}${s?" \xB7 "+s:""}] ${r}`;let c=n?.thread?n.thread+`
36
36
 
@@ -1,2 +1,2 @@
1
- import{renderErrorResponse as c}from"./render.js";const i={wyrm_setup:{cli:"wyrm setup"},wyrm_embed:{cli:"wyrm embed"},wyrm_inject_prompt:{cli:"wyrm prompt inject"},wyrm_migrate_prompt:{cli:"wyrm prompt migrate"},wyrm_check_update:{cli:"wyrm update --check"},wyrm_self_update:{cli:"wyrm update"},wyrm_vector_setup:{cli:"wyrm index setup"},wyrm_reindex:{cli:"wyrm index rebuild"},wyrm_encrypt_setup:{cli:"wyrm setup --encrypt"},wyrm_license:{cli:"wyrm license"},wyrm_activate:{cli:"wyrm activate <key>"},wyrm_hours_report:{cli:"wyrm hours report"},wyrm_invoice_generate:{cli:"wyrm invoice generate"},wyrm_agent_init:{cli:"wyrm agent init"},wyrm_agent_status:{cli:"wyrm agent status"},wyrm_agent_stop:{cli:"wyrm agent stop"},wyrm_agent_restart:{cli:"wyrm agent restart"},wyrm_intro:{cli:"wyrm intro"},wyrm_sync_export:{cli:"wyrm sync export --out <path>",functionalThrough:"7.0.x"},wyrm_sync_import:{cli:"wyrm sync import --from <path>"},wyrm_cloud_backup:{cli:"wyrm cloud backup",functionalThrough:"7.0.x"},wyrm_cloud_sync:{cli:"wyrm cloud sync"}};function o(r){const e=i[r];if(!e)return"";const t=e.functionalThrough?` \u2014 remains functional through ${e.functionalThrough}`:"";return` [Deprecated in v7 \u2192 use the wyrm CLI: ${e.cli}${t}]`}function y(r){return r.map(e=>{const t=o(e.name);return t?{...e,description:`${e.description??""}${t}`}:e})}const n=new Set;function m(){n.clear()}function a(r){try{const e=i[r];if(!e||n.has(r))return;n.add(r);const t=e.functionalThrough?` This name stays functional through ${e.functionalThrough} (published sunset) so scheduled jobs never silently stop.`:"";process.stderr.write(`[wyrm] DEPRECATION: '${r}' moves off MCP to the wyrm CLI in 7.0 \u2014 use: ${e.cli}. Behavior is unchanged in 6.18.${t}
2
- `)}catch{}}const s="WYRM_CLI_EXILE";function p(r){const e=i[r];return e!==void 0&&e.functionalThrough===void 0}function l(r){const e=i[r];if(!e)throw new Error(`cliExileRedirectBody: '${r}' is not a CLI-exile tool`);return{error:{code:s,message:`${r} moved off MCP in 7.0 \u2014 run: ${e.cli}`,retryable:!1},cli:e.cli,tool:r,expected:`This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. Run \`${e.cli}\` from a shell instead \u2014 do not retry this MCP call (the redirect is deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`}}function w(r){return c(l(r))}export{i as CLI_EXILE_TOOLS,s as WYRM_CLI_EXILE_CODE,m as _resetDeprecationWarningsForTests,y as applyDeprecationNotices,l as cliExileRedirectBody,w as cliExileRedirectResponse,o as deprecationSuffix,p as isHardCliExile,a as noteDeprecatedToolInvocation};
1
+ import{renderErrorResponse as c}from"./render.js";const i={wyrm_setup:{cli:"wyrm setup"},wyrm_embed:{cli:"wyrm embed"},wyrm_inject_prompt:{cli:"wyrm prompt inject"},wyrm_migrate_prompt:{cli:"wyrm prompt migrate"},wyrm_check_update:{cli:"wyrm update --check"},wyrm_self_update:{cli:"wyrm update"},wyrm_vector_setup:{cli:"wyrm index setup"},wyrm_reindex:{cli:"wyrm index rebuild"},wyrm_encrypt_setup:{cli:"wyrm setup --encrypt"},wyrm_license:{cli:"wyrm license"},wyrm_activate:{cli:"wyrm activate <key>"},wyrm_hours_report:{cli:"wyrm hours report"},wyrm_invoice_generate:{cli:"wyrm invoice generate"},wyrm_agent_init:{cli:"wyrm agent init"},wyrm_agent_status:{cli:"wyrm agent status"},wyrm_agent_stop:{cli:"wyrm agent stop"},wyrm_agent_restart:{cli:"wyrm agent restart"},wyrm_intro:{cli:"wyrm intro"},wyrm_sync_export:{cli:"wyrm sync export --out <path>",functionalThrough:"7.0.x"},wyrm_sync_import:{cli:"wyrm sync import --from <path>"},wyrm_cloud_backup:{cli:"wyrm cloud backup",functionalThrough:"7.0.x"},wyrm_cloud_sync:{cli:"wyrm cloud sync"}};function o(r){const e=i[r];if(!e)return"";const t=e.functionalThrough?`; remains functional through ${e.functionalThrough}`:"";return` [Deprecated in v7 \u2192 use the wyrm CLI: ${e.cli}${t}]`}function y(r){return r.map(e=>{const t=o(e.name);return t?{...e,description:`${e.description??""}${t}`}:e})}const n=new Set;function a(){n.clear()}function m(r){try{const e=i[r];if(!e||n.has(r))return;n.add(r);const t=e.functionalThrough?` This name stays functional through ${e.functionalThrough} (published sunset) so scheduled jobs never silently stop.`:"";process.stderr.write(`[wyrm] DEPRECATION: '${r}' moves off MCP to the wyrm CLI in 7.0; use: ${e.cli}. Behavior is unchanged in 6.18.${t}
2
+ `)}catch{}}const s="WYRM_CLI_EXILE";function p(r){const e=i[r];return e!==void 0&&e.functionalThrough===void 0}function l(r){const e=i[r];if(!e)throw new Error(`cliExileRedirectBody: '${r}' is not a CLI-exile tool`);return{error:{code:s,message:`${r} moved off MCP in 7.0; run: ${e.cli}`,retryable:!1},cli:e.cli,tool:r,expected:`This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. Run \`${e.cli}\` from a shell instead, and do not retry this MCP call (the redirect is deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`}}function w(r){return c(l(r))}export{i as CLI_EXILE_TOOLS,s as WYRM_CLI_EXILE_CODE,a as _resetDeprecationWarningsForTests,y as applyDeprecationNotices,l as cliExileRedirectBody,w as cliExileRedirectResponse,o as deprecationSuffix,p as isHardCliExile,m as noteDeprecatedToolInvocation};
@@ -0,0 +1 @@
1
+ import{unknownKeysError as f}from"../validate.js";import{registryToolDefs as w}from"./registry.js";import{shimToolDefs as y}from"./shims.js";const b=["actor","run_id"],_={wyrm_quest_complete:{quest_id:"spelling of questId; wyrm_quest publishes quest_id",id:"spelling of questId; a rendered quest:N ref binds here too"},wyrm_quest_claim:{questId:"spelling of quest_id; wyrm_quest_complete publishes questId",id:"spelling of quest_id; a rendered quest:N ref binds here too"},wyrm_quest_release:{questId:"spelling of quest_id; wyrm_quest_complete publishes questId",id:"spelling of quest_id; a rendered quest:N ref binds here too"},wyrm_all_quests:{limit:"result cap; published on wyrm_quest for action=list, read the same when called by this name"},wyrm_goal_complete:{goal_id:"spelling of id; wyrm_goal_iterations publishes goal_id"},wyrm_goal_pause:{goal_id:"spelling of id; wyrm_goal_iterations publishes goal_id"},wyrm_goal_resume:{goal_id:"spelling of id; wyrm_goal_iterations publishes goal_id"},wyrm_goal_abandon:{goal_id:"spelling of id; wyrm_goal_iterations publishes goal_id"},wyrm_goal_iterations:{id:"spelling of goal_id; wyrm_goal publishes id"},wyrm_remember:{validated_fix:"spelling of validatedFix, the column and outputSchema name",why_it_worked:"spelling of whyItWorked, the column and outputSchema name"},wyrm_capture:{validated_fix:"spelling of validatedFix, read by the guard that sends a direct-capture payload to mode=direct"},wyrm_failure_record:{files:"source files the failure is about, anchored for the staleness check; undeclared on the lean default surface"}};let u=null;function c(e){return u??=new Map([...w(),...y()].map(t=>[t.name,Object.keys(t.inputSchema.properties??{})])),u.get(e)}const m=new Set;function S(e,t,o){const r=c(e);if(r===void 0)return;const p=o===e?[]:c(o)??[],s=[...new Set([...r,...p])],l=[...Object.keys(_[e]??{}),...o===e?[]:Object.keys(_[o]??{})],h=new Set([...s,...l,...b]),i=Object.keys(t).filter(g=>!h.has(g));if(i.length===0)return;const n=f(i,[...s,...l],s),a=process.env.WYRM_ALLOW_UNKNOWN_ARGS;if(a!=="1"&&a!=="true")throw n;const d=`${e}\0${i.join("\0")}`;m.has(d)||(m.add(d),console.error(`[wyrm] ${e}: ${n.message}. Passed through because WYRM_ALLOW_UNKNOWN_ARGS=1 (the tool ignores keys it does not read).`))}export{_ as ARG_ALIASES,b as UNIVERSAL_ARG_NAMES,c as publishedArgNames,S as rejectUnknownArgs};
@@ -1,78 +1,78 @@
1
- import{join as te}from"path";import{createHash as K}from"crypto";import{TOOL_ANNOTATIONS as D}from"../tool-annotations.js";import{renderResult as E,withGlyph as T,renderErrorResponse as R,validationErrorBody as C}from"../render.js";import{ValidationError as J,asEnum as re,asTags as Y}from"../validate.js";import{daemonOr as M}from"../daemon-writer.js";import{classifyCapture as ie}from"../capture.js";import{recordWrite as A}from"../receipts.js";import{extractCandidates as Q,candidateToArtifact as z,escapeLikePattern as O}from"../auto-capture.js";import{parseTrace as se,segmentsToText as oe}from"../trace-harvest.js";import{getActor as ne,runWithActor as ae}from"./boundary.js";import{harvestProjects as de}from"../harvest.js";import{readFileSync as X,readdirSync as ce,statSync as F,lstatSync as pe}from"fs";import{join as N}from"path";import{homedir as G}from"os";import{resolveUserPath as U,userPathRoots as H,SecurityError as V}from"../security.js";import{autoEnrollProject as ue}from"../project-resolve.js";import{readSpecDir as le,specTaskSignature as me}from"../spec-kit.js";function B(_,w){return(_?.needs_review??w)===1}const Z=["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist"],ye=["auto","classify","quest","truth","memory"];function ee(_){return new Set(_.toLowerCase().split(/[^a-z0-9]+/).filter(w=>w.length>2))}function fe(_,w,x){if(_.value.trim()===x.trim())return!1;if(_.key.trim().toLowerCase()===w.trim().toLowerCase())return!0;const v=ee(_.value),c=ee(x);if(v.size===0||c.size===0)return!1;let f=0;for(const s of v)c.has(s)&&f++;return f/(v.size+c.size-f)>=.6}const Ae=[{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:[...Z],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",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},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","skipped"]},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:D.wyrm_capture,aliases:[],handler:async(_,{store:w,raw:x,memory:v,truths:c,cache:f})=>{const{content:s,project_id:h,projectPath:n,mode:a}=_,g=Y("tags",_.tags);if(a!==void 0&&!ye.includes(a))throw new J("mode",`must be one of: ${Z.join(", ")}`);if(typeof s!="string"||!s.trim()){const u=_;return u.problem||u.validated_fix||u.validatedFix||u.kind?R(C("wyrm_capture","mode","you passed pattern/lesson fields (kind/problem/validated_fix) \u2014 set mode:'direct' to store them as a distilled artifact.")):R(C("wyrm_capture","content","content is required for auto capture (or use mode:'direct' with problem + validated_fix)."))}let $=ie(s);a&&a!=="auto"&&a!=="classify"&&($={type:a,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[a]??a,confidence:100,reasoning:`Mode override: ${a}`});const{type:d,subtype:i,confidence:p,reasoning:k}=$,e=w,t=h??(n?w.getProject(n)?.id??ue(e,n)?.id:void 0)??null;let o=0,P="",r=!1,l;if(d==="quest"){if(t===null)return R(C("wyrm_capture","projectPath","is required for quest capture"));o=(await M("quest_add",t,{title:s.slice(0,200),description:"",priority:"medium",tags:g?.join(",")},()=>w.addQuest(t,s.slice(0,200),"","medium",g?.join(",")))).id,P="quest",f.invalidate("wyrm_all_quests"),f.invalidate("wyrm_stats")}else if(d==="truth"){if(t===null)return R(C("wyrm_capture","projectPath","is required for truth capture"));if(a!=="truth"&&p>=80){const u=i??"other",m=c.getCurrent(t).filter(j=>j.category===u&&fe(j,s.slice(0,60),s));if(m.length>0){const j={kind:"pattern",problem:`Potential conflict with ${m.length} existing truth(s) in category "${u}" (same key or near-identical statement)`,validatedFix:s,whyItWorked:"Pending review \u2014 possible supersession",tags:g??[],confidence:p/100,needsReview:1},q=await M("artifact_add",t,j,()=>v.add(t,j)),S=B(q,1),L=A(x(),{tool:"wyrm_capture",outcome:S?"queued":"stored",refTable:"mem",refId:q.id,reason:S?`conflict_check \u2014 possible supersession of ${m.length} truth(s); review to activate`:`conflict_check \u2014 possible supersession of ${m.length} truth(s); auto-approved, live in recall`,projectId:t});return E({status:S?"queued_for_review":"captured",reason:"conflict_check",artifact_id:q.id,conflicts_with:m.map(W=>({id:W.id,content:W.value.slice(0,80)})),receipt:L})}}if(a!=="truth"&&p<100){const u={kind:"pattern",problem:s,validatedFix:"",whyItWorked:"",tags:g??[],confidence:p/100,needsReview:1},I=await M("artifact_add",t,u,()=>v.add(t,u));o=I.id,P="mem",r=B(I,1)}else{const u={category:"decision",key:s.slice(0,60),value:s};o=(await M("truth_set",t,u,()=>c.set(t,u))).id,P="truth",f.invalidate("wyrm_truth_get"),f.invalidate("wyrm_context_build")}}else{if(t===null)return R(C("wyrm_capture","projectPath","is required for memory capture"));const u=p>=75,I={kind:i,problem:s,validatedFix:"",whyItWorked:"",tags:g??[],confidence:p/100,needsReview:u?0:1},m=await M("artifact_add",t,I,()=>v.add(t,I));if(o=m.id,P="mem",r=B(m,u?0:1),i==="heuristic"&&p>=75&&t!==null)try{const j=s.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),q=x().prepare(`
1
+ import{join as re}from"path";import{createHash as K}from"crypto";import{TOOL_ANNOTATIONS as D}from"../tool-annotations.js";import{renderResult as E,withGlyph as T,validationErrorResponse as R}from"../render.js";import{ValidationError as J,asEnum as ie,asTags as Y}from"../validate.js";import{daemonOr as O}from"../daemon-writer.js";import{classifyCapture as se}from"../capture.js";import{recordWrite as A}from"../receipts.js";import{extractCandidates as Q,candidateToArtifact as z,escapeLikePattern as W}from"../auto-capture.js";import{parseTrace as oe,segmentsToText as ne}from"../trace-harvest.js";import{getActor as ae,runWithActor as de}from"./boundary.js";import{harvestProjects as ce}from"../harvest.js";import{readFileSync as X,readdirSync as pe,statSync as F,lstatSync as ue}from"fs";import{join as N}from"path";import{homedir as G}from"os";import{resolveUserPath as U,userPathRoots as H,SecurityError as V}from"../security.js";import{autoEnrollProject as le}from"../project-resolve.js";import{readSpecDir as me,specTaskSignature as ye}from"../spec-kit.js";function B(m,y){return(m?.needs_review??y)===1}const Z=["auto","quest","truth","memory","classify","direct","extract","trace","session","artifacts","import","spec","checklist","feedback"],M=(m,y)=>y.map($=>`${m}:${$}`),fe=["auto","classify","quest","truth","memory"];function ee(m){return new Set(m.toLowerCase().split(/[^a-z0-9]+/).filter(y=>y.length>2))}function he(m,y,$){if(m.value.trim()===$.trim())return!1;if(m.key.trim().toLowerCase()===y.trim().toLowerCase())return!0;const _=ee(m.value),u=ee($);if(_.size===0||u.size===0)return!1;let f=0;for(const n of _)u.has(n)&&f++;return f/(_.size+u.size-f)>=.6}const De=[{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, feedback (artifactId + success: mark what you recalled as useful, or not). 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"},tags:{type:"array",items:{type:"string"}},mode:{type:"string",enum:[...Z],description:"quest|truth|memory force the type"},source:{type:"string",enum:["git","pr","rules"]},projectPath:{type:"string"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},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","skipped"]},mode:{type:"string"},refs:{type:"array",items:{type:"string"}},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","mode","refs"]},annotations:D.wyrm_capture,aliases:[],handler:async(m,{store:y,raw:$,memory:_,truths:u,cache:f})=>{const{content:n,project_id:h,projectPath:d,mode:a}=m,g=Y("tags",m.tags);if(a!==void 0&&!fe.includes(a))throw new J("mode",`must be one of: ${Z.join(", ")}`);if(typeof n!="string"||!n.trim()){const c=m;return c.problem||c.validated_fix||c.validatedFix||c.kind?R("wyrm_capture","mode","you passed pattern/lesson fields (kind/problem/validated_fix); set mode:'direct' to store them as a distilled artifact."):R("wyrm_capture","content","content is required for auto capture (or use mode:'direct' with problem + validated_fix).")}let w=se(n);a&&a!=="auto"&&a!=="classify"&&(w={type:a,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[a]??a,confidence:100,reasoning:`Mode override: ${a}`});const{type:b,subtype:s,confidence:o,reasoning:v}=w,i=y,t=h??(d?y.getProject(d)?.id??le(i,d)?.id:void 0)??null;let r=0,x="",I=!1,e;if(b==="quest"){if(t===null)return R("wyrm_capture","projectPath","is required for quest capture");r=(await O("quest_add",t,{title:n.slice(0,200),description:"",priority:"medium",tags:g?.join(",")},()=>y.addQuest(t,n.slice(0,200),"","medium",g?.join(",")))).id,x="quest",f.invalidate("wyrm_all_quests"),f.invalidate("wyrm_stats")}else if(b==="truth"){if(t===null)return R("wyrm_capture","projectPath","is required for truth capture");if(a!=="truth"&&o>=80){const c=s??"other",q=u.getCurrent(t).filter(l=>l.category===c&&he(l,n.slice(0,60),n));if(q.length>0){const l={kind:"pattern",problem:`Potential conflict with ${q.length} existing truth(s) in category "${c}" (same key or near-identical statement)`,validatedFix:n,whyItWorked:"Pending review \u2014 possible supersession",tags:g??[],confidence:o/100,needsReview:1},j=await O("artifact_add",t,l,()=>_.add(t,l)),S=B(j,1),L=A($(),{tool:"wyrm_capture",outcome:S?"queued":"stored",refTable:"mem",refId:j.id,reason:S?`conflict_check \u2014 possible supersession of ${q.length} truth(s); review to activate`:`conflict_check \u2014 possible supersession of ${q.length} truth(s); auto-approved, live in recall`,projectId:t});return E({status:S?"queued_for_review":"captured",mode:a??"auto",refs:M("mem",[j.id]),reason:"conflict_check",artifact_id:j.id,conflicts_with:q.map(C=>({id:C.id,content:C.value.slice(0,80)})),receipt:L})}}if(a!=="truth"&&o<100){const c={kind:"pattern",problem:n,validatedFix:"",whyItWorked:"",tags:g??[],confidence:o/100,needsReview:1},P=await O("artifact_add",t,c,()=>_.add(t,c));r=P.id,x="mem",I=B(P,1)}else{const c={category:"decision",key:n.slice(0,60),value:n};r=(await O("truth_set",t,c,()=>u.set(t,c))).id,x="truth",f.invalidate("wyrm_truth_get"),f.invalidate("wyrm_context_build")}}else{if(t===null)return R("wyrm_capture","projectPath","is required for memory capture");const c=o>=75,P={kind:s,problem:n,validatedFix:"",whyItWorked:"",tags:g??[],confidence:o/100,needsReview:c?0:1},q=await O("artifact_add",t,P,()=>_.add(t,P));if(r=q.id,x="mem",I=B(q,c?0:1),s==="heuristic"&&o>=75&&t!==null)try{const l=n.replace(/['"*]/g," ").trim().split(/\s+/).slice(0,5).join(" "),j=$().prepare(`
2
2
  SELECT a.id, a.problem FROM memory_artifacts a
3
3
  JOIN memory_artifacts_fts ON a.id = memory_artifacts_fts.rowid
4
4
  WHERE memory_artifacts_fts MATCH ? AND a.project_id = ? AND a.kind = 'heuristic' AND a.needs_review = 0
5
5
  LIMIT 3
6
- `).all(j,t);q.length>0&&(l=q.map(S=>({id:S.id,content:S.problem.slice(0,80)})))}catch{}}const y=A(x(),{tool:"wyrm_capture",outcome:r?"queued":"stored",refTable:P,refId:o,reason:r?`needs_review (confidence ${p}%) \u2014 review to activate`:void 0,projectId:t??void 0}),b={status:"captured",type:d,subtype:i,confidence:p,reasoning:k,id:o,ref:`${P}:${o}`,needs_review:r,receipt:y,...l&&l.length>0?{advisory_conflicts:l}:{}};return E(b,(u,I)=>{let m=`Captured as ${u.type}: ${u.subtype}
7
- Confidence: ${u.confidence}% | ${u.reasoning}
8
- ID: ${u.ref}`;u.needs_review&&(m+=`
9
- `+T(I.warn,"Stored for review -- run `wyrm_review` to activate"));const j=u.advisory_conflicts;return j&&j.length>0&&(m+=`
10
-
11
- `+T(I.warn,`Advisory: ${j.length} similar heuristic(s) found:`)+`
12
- `+j.map(q=>` ${I.bullet} [${q.id}] ${q.content}`).join(`
13
- `)),m})}},{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:D.wyrm_remember,aliases:[],handler:async(_,{store:w,memory:x,raw:v})=>{const{projectPath:c,kind:f,problem:s,constraints:h,outcome:n,confidence:a,sourceSessionId:g}=_,$=Y("tags",_.tags),d=_,i=d.validatedFix??d.validated_fix,p=d.whyItWorked??d.why_it_worked,k=re("kind",f,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(k===void 0)throw new J("kind","is required");const e=w.getProject(c);if(!e)return R(C("wyrm_remember","projectPath","project not found"));const t={kind:k,problem:s,constraints:h,validatedFix:i,whyItWorked:p,outcome:n,tags:$,confidence:a,sourceSessionId:g},o=await M("artifact_add",e.id,t,()=>x.add(e.id,t)),P=A(v(),{tool:"wyrm_remember",outcome:o.needs_review===1?"queued":"stored",refTable:"mem",refId:o.id,reason:o.needs_review===1?"needs_review \u2014 hidden until approved":void 0,source:o.created_by,projectId:e.id}),r={status:o.needs_review===1?"queued_for_review":"captured",id:o.id,kind:o.kind,problem:o.problem,validated_fix:o.validated_fix??null,why_it_worked:o.why_it_worked??null,confidence:o.confidence,tags:o.tags??null,receipt:P};return E(r,(l,y)=>{let b=T(y.brand,"**Memory Stored**")+`
14
-
15
- `;return b+=`${y.bullet} **Kind:** ${l.kind}
16
- `,b+=`${y.bullet} **ID:** ${l.id}
17
- `,b+=`${y.bullet} **Problem:** ${l.problem}
18
- `,l.validated_fix&&(b+=`${y.bullet} **Solution:** ${l.validated_fix}
19
- `),l.why_it_worked&&(b+=`${y.bullet} **Why it worked:** ${l.why_it_worked}
20
- `),b+=`${y.bullet} **Confidence:** ${(l.confidence*100).toFixed(0)}%
21
- `,l.tags&&(b+=`${y.bullet} **Tags:** ${l.tags}
22
- `),b+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",b})}},{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:D.wyrm_auto_capture,aliases:[],handler:async(_,{store:w,raw:x,memory:v,cache:c})=>{const{projectPath:f,text:s,model:h}=_,n=w.getProject(f);if(!n)return R(C("wyrm_auto_capture","projectPath","project not found"));if(!s||s.trim().length<20)return R(C("wyrm_auto_capture","text","must be at least 20 characters"));const{candidates:a,method:g,model:$}=await Q(s,{model:h}),d=x();let i=0,p=0;for(const e of a){const t=z(e),o=t.tags[t.tags.length-1];if(d.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(n.id,"%"+O(o)+"%")){p++,A(d,{tool:"wyrm_auto_capture",outcome:"dropped",reason:"dedup \u2014 already captured",source:"auto-extract",projectId:n.id});continue}const P=v.add(n.id,{kind:t.kind,problem:t.problem,tags:t.tags,confidence:t.confidence,needsReview:1,createdBy:"auto-extract"});A(d,{tool:"wyrm_auto_capture",outcome:"queued",refTable:"mem",refId:P.id,reason:"needs_review \u2014 auto-extracted; review to activate",source:"auto-extract",projectId:n.id}),i++}i>0&&c.invalidate();const k={method:g,model:$??null,added:i,skipped:p,status:i>0?"queued_for_review":"skipped",receipt:{outcome:i>0?"queued":"dropped",reason:`${i} queued for review, ${p} dropped as duplicates`},candidates:a.slice(0,8).map(e=>({kind:e.kind,text:e.text.slice(0,80)}))};return E(k,(e,t)=>{const o=e.candidates.map(P=>` ${t.bullet} [${P.kind}] ${P.text}`).join(`
23
- `);return T(t.brand,`Auto-capture (${e.method}${e.model?` - ${e.model}`:""}) -- ${e.added} candidate(s) -> review queue, ${e.skipped} already present.`)+`
24
- ${o}
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:D.wyrm_capture_trace,aliases:[],handler:async(_,{store:w,raw:x,memory:v,cache:c})=>{const{projectPath:f,trace:s,path:h,format:n,run_id:a,model:g}=_,$=w.getProject(f);if(!$)return R(C("wyrm_capture_trace","projectPath","project not found"));const d=ne(),i=typeof a=="string"&&a.trim()?a.trim().slice(0,64):d.run_id;let p="",k="inline";if(typeof s=="string"&&s.trim())p=s,k="inline";else if(typeof h=="string"&&h.trim()){let m;try{m=U(h,H([N(G(),".dragon","traces")]),"wyrm_capture_trace.path")}catch(j){if(j instanceof V)return R(C("wyrm_capture_trace","path",j.message));throw j}try{if(F(m).isDirectory()){const q=H([N(G(),".dragon","traces")]),S=ce(m).map(L=>N(m,L)).filter(L=>{try{return pe(L).isFile()?(U(L,q,"wyrm_capture_trace.path"),!0):!1}catch{return!1}}).sort((L,W)=>F(W).mtimeMs-F(L).mtimeMs);if(S.length===0)return R(C("wyrm_capture_trace","path","no trace files in directory"));p=X(S[0],"utf-8"),k=S[0]}else p=X(m,"utf-8"),k=m}catch(j){return R(C("wyrm_capture_trace","path",`could not read trace path: ${j.message}`))}}else return R(C("wyrm_capture_trace","trace","provide inline trace or path to trace file"));const{segments:e,format:t}=se(p,n??"auto"),o=oe(e);if(o.trim().length<20){const m={method:"deterministic",model:null,format:t,source:k,run_id:i,segments:e.length,added:0,skipped:0,status:"skipped",candidates:[]};return E(m,(j,q)=>T(q.brand,`Trace harvest (${j.format}) -- no durable content extracted from ${j.segments} segment(s).`))}const{candidates:P,method:r,model:l}=await Q(o,{model:g}),y=x();let b=0,u=0;for(const m of P){const j=z(m),q=j.tags[j.tags.length-1];if(y.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get($.id,"%"+O(q)+"%")){u++;continue}const S=i?[...j.tags,"trace",`run:${i}`]:[...j.tags,"trace"];ae({agent_id:d.agent_id,run_id:i,source:i?"param":d.source},()=>v.add($.id,{kind:j.kind,problem:j.problem,tags:S,confidence:j.confidence,needsReview:1,createdBy:"trace-harvest"})),b++}b>0&&c.invalidate();const I={method:r,model:l??null,format:t,source:k,run_id:i,segments:e.length,added:b,skipped:u,status:b>0?"queued_for_review":"skipped",candidates:P.slice(0,8).map(m=>({kind:m.kind,text:m.text.slice(0,80)}))};return E(I,(m,j)=>{const q=m.candidates.map(S=>` ${j.bullet} [${S.kind}] ${S.text}`).join(`
27
- `);return T(j.brand,`Trace harvest (${m.format}, ${m.method}${m.model?` - ${m.model}`:""}) -- ${m.segments} segment(s) -> ${m.added} candidate(s) to review queue, ${m.skipped} already present${m.run_id?` [run:${m.run_id}]`:""}.`)+(q?`
28
- ${q}`:"")+`
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:D.wyrm_distill,aliases:[],handler:(_,{store:w,memory:x})=>{const{projectPath:v,candidates:c}=_,f=w.getProject(v);if(!f)return{content:[{type:"text",text:`Project not found: ${v}`}],isError:!0};if(!c||c.length===0)return{content:[{type:"text",text:`**Distill**
31
-
32
- No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const s=[];for(const n of c){const a=x.add(f.id,{kind:n.kind,problem:n.title,validatedFix:n.content,tags:n.tags??[],confidence:n.confidence??.7,needsReview:1});s.push({id:a.id,kind:n.kind,title:n.title})}const h={status:"queued_for_review",count:s.length,artifacts:s};return E(h,(n,a)=>{let g=T(a.brand,`**Distillation Complete** -- ${n.count} artifact${n.count!==1?"s":""} queued for review`)+`
33
-
34
- `;return g+=n.artifacts.map($=>`${a.bullet} [#${$.id}] **${$.kind}**: ${$.title}`).join(`
35
- `),g+="\n\nUse `wyrm_review` with each ID to approve or reject.",g})}},{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:D.wyrm_harvest,aliases:[],handler:(_,{store:w,raw:x,memory:v,symbols:c,cache:f})=>{const{projectPath:s,dryRun:h,gitLimit:n,includeCode:a}=_,g=x(),$={existsBySig:(r,l)=>!!g.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(r,"%"+O(l)+"%"),addCandidate:(r,l)=>{const y=v.add(r,{kind:l.kind,problem:l.text,tags:[...l.tags,l.sig],confidence:l.confidence,needsReview:1,createdBy:"harvest"});A(g,{tool:"wyrm_harvest",outcome:"queued",refTable:"mem",refId:y.id,reason:"needs_review \u2014 harvested; review to activate",source:"harvest",projectId:r})},onSkip:r=>{A(g,{tool:"wyrm_harvest",outcome:"dropped",reason:"dedup \u2014 signature already harvested",source:"harvest",projectId:r})}};let d;if(s){const r=w.getProject(s);if(!r)return{content:[{type:"text",text:"Project not found"}],isError:!0};d=[{id:r.id,name:r.name,path:r.path}]}else d=w.getAllProjects(500).map(r=>({id:r.id,name:r.name,path:r.path}));const{reports:i,totalAdded:p,totalSkipped:k}=de($,d,{dryRun:h,gitLimit:n,includeCode:a});!h&&p>0&&f.invalidate();let e=null;if(a&&!h){let r=0,l=0,y=0;for(const b of d)try{const u=c.indexProject(b.id,b.path);r+=u.files,l+=u.symbols,y++}catch{}e={symbols:l,projects:y,files:r}}const t=i.filter(r=>r.added>0).sort((r,l)=>l.added-r.added).slice(0,15).map(r=>({project:r.project,added:r.added,skipped:r.skipped})),o=i.flatMap(r=>r.sample??[]).slice(0,6),P={status:!h&&p>0?"queued_for_review":"skipped",dry_run:!!h,projects_scanned:d.length,added:p,skipped:k,top:t,sample:o,code_symbols:e};return E(P,(r,l)=>{const y=r.code_symbols?`
36
- Indexed ${r.code_symbols.symbols} code symbols across ${r.code_symbols.projects} project(s) (${r.code_symbols.files} files).`:"",b=r.top.map(I=>` +${String(I.added).padStart(3)} (skip ${I.skipped}) ${I.project}`).join(`
37
- `),u=r.sample.map(I=>` ${l.bullet} ${I}`).join(`
38
- `);return T(l.brand,`Harvest ${r.dry_run?"(dry run) ":""}-- ${r.added} candidate(s) -> review queue across ${r.projects_scanned} project(s); ${r.skipped} already present.`)+`${y}
39
- ${b}`+(u?`
6
+ `).all(l,t);j.length>0&&(e=j.map(S=>({id:S.id,content:S.problem.slice(0,80)})))}catch{}}const k=A($(),{tool:"wyrm_capture",outcome:I?"queued":"stored",refTable:x,refId:r,reason:I?`needs_review (confidence ${o}%) \u2014 review to activate`:void 0,projectId:t??void 0}),p={status:"captured",mode:a==="classify"||a===void 0?"auto":a,refs:[`${x}:${r}`],type:b,subtype:s,confidence:o,reasoning:v,id:r,ref:`${x}:${r}`,needs_review:I,receipt:k,...e&&e.length>0?{advisory_conflicts:e}:{}};return E(p,(c,P)=>{let q=`Captured as ${c.type}: ${c.subtype}
7
+ Confidence: ${c.confidence}% | ${c.reasoning}
8
+ ID: ${c.ref}`;c.needs_review&&(q+=`
9
+ `+T(P.warn,"Stored for review -- run `wyrm_review` to activate"));const l=c.advisory_conflicts;return l&&l.length>0&&(q+=`
10
+
11
+ `+T(P.warn,`Advisory: ${l.length} similar heuristic(s) found:`)+`
12
+ `+l.map(j=>` ${P.bullet} [${j.id}] ${j.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:D.wyrm_remember,aliases:[],handler:async(m,{store:y,memory:$,raw:_})=>{const{projectPath:u,kind:f,problem:n,constraints:h,outcome:d,confidence:a,sourceSessionId:g}=m,w=Y("tags",m.tags),b=m,s=b.validatedFix??b.validated_fix,o=b.whyItWorked??b.why_it_worked,v=ie("kind",f,["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]);if(v===void 0)throw new J("kind","is required");const i=y.getProject(u);if(!i)return R("wyrm_remember","projectPath","project not found");const t={kind:v,problem:n,constraints:h,validatedFix:s,whyItWorked:o,outcome:d,tags:w,confidence:a,sourceSessionId:g},r=await O("artifact_add",i.id,t,()=>$.add(i.id,t)),x=A(_(),{tool:"wyrm_remember",outcome:r.needs_review===1?"queued":"stored",refTable:"mem",refId:r.id,reason:r.needs_review===1?"needs_review \u2014 hidden until approved":void 0,source:r.created_by,projectId:i.id}),I={status:r.needs_review===1?"queued_for_review":"captured",mode:"direct",refs:M("mem",[r.id]),id:r.id,kind:r.kind,problem:r.problem,validated_fix:r.validated_fix??null,why_it_worked:r.why_it_worked??null,confidence:r.confidence,tags:r.tags??null,receipt:x};return E(I,(e,k)=>{let p=T(k.brand,"**Memory Stored**")+`
14
+
15
+ `;return p+=`${k.bullet} **Kind:** ${e.kind}
16
+ `,p+=`${k.bullet} **ID:** ${e.id}
17
+ `,p+=`${k.bullet} **Problem:** ${e.problem}
18
+ `,e.validated_fix&&(p+=`${k.bullet} **Solution:** ${e.validated_fix}
19
+ `),e.why_it_worked&&(p+=`${k.bullet} **Why it worked:** ${e.why_it_worked}
20
+ `),p+=`${k.bullet} **Confidence:** ${(e.confidence*100).toFixed(0)}%
21
+ `,e.tags&&(p+=`${k.bullet} **Tags:** ${e.tags}
22
+ `),p+="\n_Use `wyrm_recall` to retrieve similar memories, or `wyrm_context_build` to assemble a task brief._",p})}},{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:D.wyrm_auto_capture,aliases:[],handler:async(m,{store:y,raw:$,memory:_,cache:u})=>{const{projectPath:f,text:n,model:h}=m,d=y.getProject(f);if(!d)return R("wyrm_auto_capture","projectPath","project not found");if(!n||n.trim().length<20)return R("wyrm_auto_capture","text","must be at least 20 characters");const{candidates:a,method:g,model:w}=await Q(n,{model:h}),b=$();let s=0,o=0;const v=[];for(const t of a){const r=z(t),x=r.tags[r.tags.length-1];if(b.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(d.id,"%"+W(x)+"%")){o++,A(b,{tool:"wyrm_auto_capture",outcome:"dropped",reason:"dedup \u2014 already captured",source:"auto-extract",projectId:d.id});continue}const I=_.add(d.id,{kind:r.kind,problem:r.problem,tags:r.tags,confidence:r.confidence,needsReview:1,createdBy:"auto-extract"});v.push(I.id),A(b,{tool:"wyrm_auto_capture",outcome:"queued",refTable:"mem",refId:I.id,reason:"needs_review \u2014 auto-extracted; review to activate",source:"auto-extract",projectId:d.id}),s++}s>0&&u.invalidate();const i={method:g,model:w??null,added:s,skipped:o,status:s>0?"queued_for_review":"skipped",mode:"extract",refs:M("mem",v),receipt:{outcome:s>0?"queued":"dropped",reason:`${s} queued for review, ${o} dropped as duplicates`},candidates:a.slice(0,8).map(t=>({kind:t.kind,text:t.text.slice(0,80)}))};return E(i,(t,r)=>{const x=t.candidates.map(I=>` ${r.bullet} [${I.kind}] ${I.text}`).join(`
23
+ `);return T(r.brand,`Auto-capture (${t.method}${t.model?` - ${t.model}`:""}) -- ${t.added} candidate(s) -> review queue, ${t.skipped} already present.`)+`
24
+ ${x}
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:D.wyrm_capture_trace,aliases:[],handler:async(m,{store:y,raw:$,memory:_,cache:u})=>{const{projectPath:f,trace:n,path:h,format:d,run_id:a,model:g}=m,w=y.getProject(f);if(!w)return R("wyrm_capture_trace","projectPath","project not found");const b=ae(),s=typeof a=="string"&&a.trim()?a.trim().slice(0,64):b.run_id;let o="",v="inline";if(typeof n=="string"&&n.trim())o=n,v="inline";else if(typeof h=="string"&&h.trim()){let l;try{l=U(h,H([N(G(),".dragon","traces")]),"wyrm_capture_trace.path")}catch(j){if(j instanceof V)return R("wyrm_capture_trace","path",j.message);throw j}try{if(F(l).isDirectory()){const S=H([N(G(),".dragon","traces")]),L=pe(l).map(C=>N(l,C)).filter(C=>{try{return ue(C).isFile()?(U(C,S,"wyrm_capture_trace.path"),!0):!1}catch{return!1}}).sort((C,te)=>F(te).mtimeMs-F(C).mtimeMs);if(L.length===0)return R("wyrm_capture_trace","path","no trace files in directory");o=X(L[0],"utf-8"),v=L[0]}else o=X(l,"utf-8"),v=l}catch(j){return R("wyrm_capture_trace","path",`could not read trace path: ${j.message}`)}}else return R("wyrm_capture_trace","trace","provide inline trace or path to trace file");const{segments:i,format:t}=oe(o,d??"auto"),r=ne(i);if(r.trim().length<20){const l={method:"deterministic",model:null,format:t,source:v,run_id:s,segments:i.length,added:0,skipped:0,status:"skipped",mode:"trace",refs:[],candidates:[]};return E(l,(j,S)=>T(S.brand,`Trace harvest (${j.format}) -- no durable content extracted from ${j.segments} segment(s).`))}const{candidates:x,method:I,model:e}=await Q(r,{model:g}),k=$();let p=0,c=0;const P=[];for(const l of x){const j=z(l),S=j.tags[j.tags.length-1];if(k.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w.id,"%"+W(S)+"%")){c++;continue}const L=s?[...j.tags,"trace",`run:${s}`]:[...j.tags,"trace"],C=de({agent_id:b.agent_id,run_id:s,source:s?"param":b.source},()=>_.add(w.id,{kind:j.kind,problem:j.problem,tags:L,confidence:j.confidence,needsReview:1,createdBy:"trace-harvest"}));P.push(C.id),p++}p>0&&u.invalidate();const q={method:I,model:e??null,format:t,source:v,run_id:s,segments:i.length,added:p,skipped:c,status:p>0?"queued_for_review":"skipped",mode:"trace",refs:M("mem",P),candidates:x.slice(0,8).map(l=>({kind:l.kind,text:l.text.slice(0,80)}))};return E(q,(l,j)=>{const S=l.candidates.map(L=>` ${j.bullet} [${L.kind}] ${L.text}`).join(`
27
+ `);return T(j.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}]`:""}.`)+(S?`
28
+ ${S}`:"")+`
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:D.wyrm_distill,aliases:[],handler:(m,{store:y,memory:$})=>{const{projectPath:_,candidates:u}=m,f=y.getProject(_);if(!f)return{content:[{type:"text",text:`Project not found: ${_}`}],isError:!0};if(!u||u.length===0)return{content:[{type:"text",text:`**Distill**
31
+
32
+ No candidates provided. Provide an array of candidate artifacts to distill.`}],isError:!0};const n=[];for(const d of u){const a=$.add(f.id,{kind:d.kind,problem:d.title,validatedFix:d.content,tags:d.tags??[],confidence:d.confidence??.7,needsReview:1});n.push({id:a.id,kind:d.kind,title:d.title})}const h={status:"queued_for_review",mode:"session",refs:M("mem",n.map(d=>d.id)),count:n.length,artifacts:n};return E(h,(d,a)=>{let g=T(a.brand,`**Distillation Complete** -- ${d.count} artifact${d.count!==1?"s":""} queued for review`)+`
33
+
34
+ `;return g+=d.artifacts.map(w=>`${a.bullet} [#${w.id}] **${w.kind}**: ${w.title}`).join(`
35
+ `),g+="\n\nUse `wyrm_review` with each ID to approve or reject.",g})}},{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:D.wyrm_harvest,aliases:[],handler:(m,{store:y,raw:$,memory:_,symbols:u,cache:f})=>{const{projectPath:n,dryRun:h,gitLimit:d,includeCode:a}=m,g=$(),w=[],b={existsBySig:(e,k)=>!!g.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(e,"%"+W(k)+"%"),addCandidate:(e,k)=>{const p=_.add(e,{kind:k.kind,problem:k.text,tags:[...k.tags,k.sig],confidence:k.confidence,needsReview:1,createdBy:"harvest"});w.push(p.id),A(g,{tool:"wyrm_harvest",outcome:"queued",refTable:"mem",refId:p.id,reason:"needs_review \u2014 harvested; review to activate",source:"harvest",projectId:e})},onSkip:e=>{A(g,{tool:"wyrm_harvest",outcome:"dropped",reason:"dedup \u2014 signature already harvested",source:"harvest",projectId:e})}};let s;if(n){const e=y.getProject(n);if(!e)return{content:[{type:"text",text:"Project not found"}],isError:!0};s=[{id:e.id,name:e.name,path:e.path}]}else s=y.getAllProjects(500).map(e=>({id:e.id,name:e.name,path:e.path}));const{reports:o,totalAdded:v,totalSkipped:i}=ce(b,s,{dryRun:h,gitLimit:d,includeCode:a});!h&&v>0&&f.invalidate();let t=null;if(a&&!h){let e=0,k=0,p=0;for(const c of s)try{const P=u.indexProject(c.id,c.path);e+=P.files,k+=P.symbols,p++}catch{}t={symbols:k,projects:p,files:e}}const r=o.filter(e=>e.added>0).sort((e,k)=>k.added-e.added).slice(0,15).map(e=>({project:e.project,added:e.added,skipped:e.skipped})),x=o.flatMap(e=>e.sample??[]).slice(0,6),I={status:!h&&v>0?"queued_for_review":"skipped",mode:"artifacts",refs:M("mem",w),dry_run:!!h,projects_scanned:s.length,added:v,skipped:i,top:r,sample:x,code_symbols:t};return E(I,(e,k)=>{const p=e.code_symbols?`
36
+ Indexed ${e.code_symbols.symbols} code symbols across ${e.code_symbols.projects} project(s) (${e.code_symbols.files} files).`:"",c=e.top.map(q=>` +${String(q.added).padStart(3)} (skip ${q.skipped}) ${q.project}`).join(`
37
+ `),P=e.sample.map(q=>` ${k.bullet} ${q}`).join(`
38
+ `);return T(k.brand,`Harvest ${e.dry_run?"(dry run) ":""}-- ${e.added} candidate(s) -> review queue across ${e.projects_scanned} project(s); ${e.skipped} already present.`)+`${p}
39
+ ${c}`+(P?`
40
40
 
41
41
  Sample:
42
- ${u}`:"")+`
42
+ ${P}`:"")+`
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:D.wyrm_import_git,aliases:[],handler:(_,{store:w,memory:x,raw:v})=>{const{project_id:c,commits:f,auto_approve:s}=_;if(!w.getProjectById(c))return{content:[{type:"text",text:`Project ID ${c} not found.`}],isError:!0};let n=0,a=0;const g=v();for(const d of f){const i=d.message??"";if(/^Merge /i.test(i)){a++;continue}if(/^(chore|bump|release|version)/i.test(i)){a++;continue}const p="gi:"+K("sha1").update(`${i}
45
- ${d.diff_summary??""}`).digest("hex").slice(0,16);if(g.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(c,"%"+O(p)+"%")){a++,A(g,{tool:"wyrm_import_git",outcome:"dropped",reason:"dedup \u2014 this commit was already imported (gi: signature match)",source:"import:git",projectId:c});continue}let k="pattern";/^fix(\(.+\))?:/i.test(i)?k="lesson":/^feat(\(.+\))?:/i.test(i)?k="pattern":/^refactor(\(.+\))?:/i.test(i)&&(k="heuristic");const e=i.split(":")[0]??"commit",t=x.add(c,{kind:k,problem:i,validatedFix:d.diff_summary??"",whyItWorked:`Committed by ${d.author??"unknown"} on ${d.date??"unknown"}`,tags:["git","commit",e.toLowerCase(),p],confidence:.6,needsReview:s?0:1});A(g,{tool:"wyrm_import_git",outcome:s?"stored":"queued",refTable:"mem",refId:t.id,reason:s?void 0:"needs_review \u2014 imported; review to activate",source:"import:git",projectId:c}),n++}return E({status:n===0?"skipped":s?"captured":"queued_for_review",captured:n,skipped:a,auto_approved:!!s},(d,i)=>T(i.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:D.wyrm_import_git,aliases:[],handler:(m,{store:y,memory:$,raw:_})=>{const{project_id:u,commits:f,auto_approve:n}=m;if(!y.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let d=0,a=0;const g=[],w=_();for(const s of f){const o=s.message??"";if(/^Merge /i.test(o)){a++;continue}if(/^(chore|bump|release|version)/i.test(o)){a++;continue}const v="gi:"+K("sha1").update(`${o}
45
+ ${s.diff_summary??""}`).digest("hex").slice(0,16);if(w.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(u,"%"+W(v)+"%")){a++,A(w,{tool:"wyrm_import_git",outcome:"dropped",reason:"dedup \u2014 this commit was already imported (gi: signature match)",source:"import:git",projectId:u});continue}let i="pattern";/^fix(\(.+\))?:/i.test(o)?i="lesson":/^feat(\(.+\))?:/i.test(o)?i="pattern":/^refactor(\(.+\))?:/i.test(o)&&(i="heuristic");const t=o.split(":")[0]??"commit",r=$.add(u,{kind:i,problem:o,validatedFix:s.diff_summary??"",whyItWorked:`Committed by ${s.author??"unknown"} on ${s.date??"unknown"}`,tags:["git","commit",t.toLowerCase(),v],confidence:.6,needsReview:n?0:1});g.push(r.id),A(w,{tool:"wyrm_import_git",outcome:n?"stored":"queued",refTable:"mem",refId:r.id,reason:n?void 0:"needs_review \u2014 imported; review to activate",source:"import:git",projectId:u}),d++}const b={status:d===0?"skipped":n?"captured":"queued_for_review",mode:"import",refs:M("mem",g),captured:d,skipped:a,auto_approved:!!n};return E(b,(s,o)=>T(o.brand,"**Git Import Complete**")+`
46
46
 
47
- ${i.bullet} **Captured:** ${d.captured}
48
- ${i.bullet} **Skipped:** ${d.skipped} (merges/chores)
49
- ${i.bullet} **Review needed:** ${d.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${d.captured} artifact${d.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:D.wyrm_import_pr,aliases:[],handler:(_,{store:w,memory:x,raw:v})=>{const{project_id:c,title:f,body:s,review_comments:h,auto_approve:n}=_;if(!w.getProjectById(c))return{content:[{type:"text",text:`Project ID ${c} not found.`}],isError:!0};let g=0,$=0;const d=n?0:1,i=v(),p=(e,t,o)=>{const P="pr:"+K("sha1").update(t).digest("hex").slice(0,16);if(i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(c,"%"+O(P)+"%")){$++,A(i,{tool:"wyrm_import_pr",outcome:"dropped",reason:"dedup \u2014 already imported (pr: signature)",source:"import:pr",projectId:c});return}const r=x.add(c,{kind:e,problem:t,validatedFix:"",whyItWorked:"",tags:[...o,P],confidence:.65,needsReview:d,createdBy:"import:pr"});A(i,{tool:"wyrm_import_pr",outcome:n?"stored":"queued",refTable:"mem",refId:r.id,reason:n?void 0:"needs_review \u2014 imported; review to activate",source:"import:pr",projectId:c}),g++};p("pattern",`${f}
47
+ ${o.bullet} **Captured:** ${s.captured}
48
+ ${o.bullet} **Skipped:** ${s.skipped} (merges/chores)
49
+ ${o.bullet} **Review needed:** ${s.auto_approved?"No (auto-approved)":`Yes -- run \`wyrm_review\` to activate ${s.captured} artifact${s.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:D.wyrm_import_pr,aliases:[],handler:(m,{store:y,memory:$,raw:_})=>{const{project_id:u,title:f,body:n,review_comments:h,auto_approve:d}=m;if(!y.getProjectById(u))return{content:[{type:"text",text:`Project ID ${u} not found.`}],isError:!0};let g=0,w=0;const b=[],s=d?0:1,o=_(),v=(t,r,x)=>{const I="pr:"+K("sha1").update(r).digest("hex").slice(0,16);if(o.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(u,"%"+W(I)+"%")){w++,A(o,{tool:"wyrm_import_pr",outcome:"dropped",reason:"dedup \u2014 already imported (pr: signature)",source:"import:pr",projectId:u});return}const e=$.add(u,{kind:t,problem:r,validatedFix:"",whyItWorked:"",tags:[...x,I],confidence:.65,needsReview:s,createdBy:"import:pr"});b.push(e.id),A(o,{tool:"wyrm_import_pr",outcome:d?"stored":"queued",refTable:"mem",refId:e.id,reason:d?void 0:"needs_review \u2014 imported; review to activate",source:"import:pr",projectId:u}),g++};v("pattern",`${f}
50
50
 
51
- ${s}`,["git","pr"]);for(const e of h??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(e)&&p("heuristic",e,["git","pr","review"]);return E({status:g===0?"skipped":n?"captured":"queued_for_review",created:g,skipped:$,auto_approved:!!n},(e,t)=>T(t.brand,"**PR Import Complete**")+`
51
+ ${n}`,["git","pr"]);for(const t of h??[])/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(t)&&v("heuristic",t,["git","pr","review"]);const i={status:g===0?"skipped":d?"captured":"queued_for_review",mode:"import",refs:M("mem",b),created:g,skipped:w,auto_approved:!!d};return E(i,(t,r)=>T(r.brand,"**PR Import Complete**")+`
52
52
 
53
- ${t.bullet} **Artifacts created:** ${e.created}
54
- ${t.bullet} **Review needed:** ${e.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:D.wyrm_import_rules,aliases:[],handler:async(_,{store:w,memory:x,truths:v,raw:c})=>{const{project_id:f,content:s,format:h,source_file:n}=_;if(!w.getProjectById(f))return{content:[{type:"text",text:`Project ID ${f} not found.`}],isError:!0};const g=h??"plain",$=n??"rules",d=["imported",g,$],i=s.split(/\n(?=#)/),p=s.split(/\n\n+/),k=(i.length>=p.length?i:p).map(y=>y.trim()).filter(y=>y.length>=15);let e=0,t=0,o=0;const{isConfigShapedTruth:P}=await import("../metabolize.js"),r=c();for(const y of k){const b=y.slice(0,50).replace(/\n/g," ");if(/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(y)&&!P(b,y)){const u=v.set(f,{category:"constraint",key:b,value:y,source:$});A(r,{tool:"wyrm_import_rules",outcome:"stored",refTable:"truth",refId:u.id,source:"import:rules",projectId:f}),e++}else{const u=x.add(f,{kind:"heuristic",problem:y,validatedFix:"",whyItWorked:"",tags:d,confidence:.7,needsReview:1,createdBy:"import:rules"});P(b,y)&&o++,A(r,{tool:"wyrm_import_rules",outcome:"queued",refTable:"mem",refId:u.id,reason:P(b,y)?"truth-shape lint \u2014 config shape routed to review":"needs_review \u2014 imported; review to activate",source:"import:rules",projectId:f}),t++}}const l={status:t>0?"queued_for_review":e>0?"captured":"skipped",format:g,source:$,truths_created:e,artifacts_created:t,linted:o};return E(l,(y,b)=>T(b.brand,`**Rules Import Complete** (${y.format})`)+`
53
+ ${r.bullet} **Artifacts created:** ${t.created}
54
+ ${r.bullet} **Review needed:** ${t.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:D.wyrm_import_rules,aliases:[],handler:async(m,{store:y,memory:$,truths:_,raw:u})=>{const{project_id:f,content:n,format:h,source_file:d}=m;if(!y.getProjectById(f))return{content:[{type:"text",text:`Project ID ${f} not found.`}],isError:!0};const g=h??"plain",w=d??"rules",b=["imported",g,w],s=n.split(/\n(?=#)/),o=n.split(/\n\n+/),v=(s.length>=o.length?s:o).map(p=>p.trim()).filter(p=>p.length>=15);let i=0,t=0,r=0;const x=[],{isConfigShapedTruth:I}=await import("../metabolize.js"),e=u();for(const p of v){const c=p.slice(0,50).replace(/\n/g," ");if(/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(p)&&!I(c,p)){const P=_.set(f,{category:"constraint",key:c,value:p,source:w});A(e,{tool:"wyrm_import_rules",outcome:"stored",refTable:"truth",refId:P.id,source:"import:rules",projectId:f}),x.push(`truth:${P.id}`),i++}else{const P=$.add(f,{kind:"heuristic",problem:p,validatedFix:"",whyItWorked:"",tags:b,confidence:.7,needsReview:1,createdBy:"import:rules"});x.push(`mem:${P.id}`),I(c,p)&&r++,A(e,{tool:"wyrm_import_rules",outcome:"queued",refTable:"mem",refId:P.id,reason:I(c,p)?"truth-shape lint \u2014 config shape routed to review":"needs_review \u2014 imported; review to activate",source:"import:rules",projectId:f}),t++}}const k={status:t>0?"queued_for_review":i>0?"captured":"skipped",mode:"import",refs:x,format:g,source:w,truths_created:i,artifacts_created:t,linted:r};return E(k,(p,c)=>T(c.brand,`**Rules Import Complete** (${p.format})`)+`
55
55
 
56
- ${b.bullet} **Ground truths created:** ${y.truths_created} (active immediately)
57
- ${b.bullet} **Memory artifacts created:** ${y.artifacts_created} (pending review)
58
- ${b.bullet} **Source:** ${y.source}
56
+ ${c.bullet} **Ground truths created:** ${p.truths_created} (active immediately)
57
+ ${c.bullet} **Memory artifacts created:** ${p.artifacts_created} (pending review)
58
+ ${c.bullet} **Source:** ${p.source}
59
59
 
60
- 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:D.wyrm_spec_register,aliases:[],handler:(_,{store:w,indexing:x})=>{const{specDir:v,projectPath:c,priority:f}=_,s=["specDir","projectPath"].filter(e=>{const t=_[e];return!t&&t!==0&&t!==!1});if(s.length>0)return{content:[{type:"text",text:`Missing required arguments: ${s.join(", ")}`}],isError:!0};const h=w.getProject(c);if(!h)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${c}". Register it first (wyrm_project action=scan, or wyrm_session action=start).`}],isError:!0};let n;try{const e=typeof h.path=="string"&&h.path?[h.path]:[];n=U(v,H(e),"wyrm_spec_register.specDir")}catch(e){if(e instanceof V)return R(C("wyrm_spec_register","specDir",e.message));throw e}const a=le(n);if(a.tasks.length===0){const e={status:"skipped",spec_dir:v,title:a.title??null,project:h.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return E(e,(t,o)=>T(o.brand,`**Spec Register**: No tasks found in ${te(t.spec_dir,"tasks.md")}. Nothing to create.`))}const g=f||"medium";let $=0,d=0;const i=[];for(const e of a.tasks){const t=me(v,e.id),o=`${e.id}: ${e.title}`,P=t,r=w.findQuestBySpecSignature(h.id,t);if(r)w.updateQuestFields(r.id,{title:o,tags:P}),i.push(r.id),d++;else{const l=w.addQuest(h.id,o,`spec-kit task from ${v}`,g,P);x()?.enqueue("quest",l.id,h.id),i.push(l.id),$++}}const p=w.upsertSpec(h.id,v,a.title,a.summary,a.tasks.length),k={status:"captured",spec_dir:p.spec_dir,title:p.title??null,project:h.name,tasks_parsed:a.tasks.length,quests_created:$,quests_updated:d,quest_ids:i};return E(k,(e,t)=>{let o=T(t.brand,"**Spec Registered**")+`
60
+ 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:D.wyrm_spec_register,aliases:[],handler:(m,{store:y,indexing:$})=>{const{specDir:_,projectPath:u,priority:f}=m,n=["specDir","projectPath"].filter(i=>{const t=m[i];return!t&&t!==0&&t!==!1});if(n.length>0)return{content:[{type:"text",text:`Missing required arguments: ${n.join(", ")}`}],isError:!0};const h=y.getProject(u);if(!h)return{content:[{type:"text",text:`**Spec Register**: Project not found at "${u}". Register it first (wyrm_project action=scan, or wyrm_session action=start).`}],isError:!0};let d;try{const i=typeof h.path=="string"&&h.path?[h.path]:[];d=U(_,H(i),"wyrm_spec_register.specDir")}catch(i){if(i instanceof V)return R("wyrm_spec_register","specDir",i.message);throw i}const a=me(d);if(a.tasks.length===0){const i={status:"skipped",mode:"spec",refs:[],spec_dir:_,title:a.title??null,project:h.name,tasks_parsed:0,quests_created:0,quests_updated:0,quest_ids:[]};return E(i,(t,r)=>T(r.brand,`**Spec Register**: No tasks found in ${re(t.spec_dir,"tasks.md")}. Nothing to create.`))}const g=f||"medium";let w=0,b=0;const s=[];for(const i of a.tasks){const t=ye(_,i.id),r=`${i.id}: ${i.title}`,x=t,I=y.findQuestBySpecSignature(h.id,t);if(I)y.updateQuestFields(I.id,{title:r,tags:x}),s.push(I.id),b++;else{const e=y.addQuest(h.id,r,`spec-kit task from ${_}`,g,x);$()?.enqueue("quest",e.id,h.id),s.push(e.id),w++}}const o=y.upsertSpec(h.id,_,a.title,a.summary,a.tasks.length),v={status:"captured",mode:"spec",refs:M("quest",s),spec_dir:o.spec_dir,title:o.title??null,project:h.name,tasks_parsed:a.tasks.length,quests_created:w,quests_updated:b,quest_ids:s};return E(v,(i,t)=>{let r=T(t.brand,"**Spec Registered**")+`
61
61
 
62
- `;return o+=`Spec: ${e.title||"(untitled)"}
63
- `,p.summary&&(o+=`Summary: ${p.summary}
64
- `),o+=`Dir: ${e.spec_dir}
65
- `,o+=`Project: ${e.project}
66
- `,o+=`Tasks parsed: ${e.tasks_parsed}
67
- `,o+=`Quests created: ${e.quests_created} - updated: ${e.quests_updated}
68
- `,o+=`
62
+ `;return r+=`Spec: ${i.title||"(untitled)"}
63
+ `,o.summary&&(r+=`Summary: ${o.summary}
64
+ `),r+=`Dir: ${i.spec_dir}
65
+ `,r+=`Project: ${i.project}
66
+ `,r+=`Tasks parsed: ${i.tasks_parsed}
67
+ `,r+=`Quests created: ${i.quests_created} - updated: ${i.quests_updated}
68
+ `,r+=`
69
69
  \`\`\`json
70
- ${JSON.stringify({spec:e.spec_dir,title:e.title,project:e.project,tasksParsed:e.tasks_parsed,questsCreated:e.quests_created,questsUpdated:e.quests_updated,questIds:e.quest_ids},null,2)}
71
- \`\`\``,o})}},{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:D.wyrm_scaffold_save,aliases:[],handler:(_,{store:w,scaffolds:x,cache:v})=>{const{projectPath:c,problemType:f,description:s,whenToUse:h,steps:n,verificationSteps:a,doNotApplyIf:g,tags:$}=_,d=c?w.getProject(c)?.id??void 0:void 0,i=x.save({projectId:d,problemType:f,description:s,whenToUse:h,steps:n,verificationSteps:a??void 0,doNotApplyIf:g?[g]:void 0,tags:$??[]});v.invalidate("wyrm_scaffold_get"),v.invalidate("wyrm_context_build");const p={status:"captured",id:i.id,problem_type:f,description:s,steps:n.length};return E(p,(k,e)=>T(e.brand,"**Reasoning Scaffold Saved**")+`
70
+ ${JSON.stringify({spec:i.spec_dir,title:i.title,project:i.project,tasksParsed:i.tasks_parsed,questsCreated:i.quests_created,questsUpdated:i.quests_updated,questIds:i.quest_ids},null,2)}
71
+ \`\`\``,r})}},{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:D.wyrm_scaffold_save,aliases:[],handler:(m,{store:y,scaffolds:$,cache:_})=>{const{projectPath:u,problemType:f,description:n,whenToUse:h,steps:d,verificationSteps:a,doNotApplyIf:g,tags:w}=m,b=u?y.getProject(u)?.id??void 0:void 0,s=$.save({projectId:b,problemType:f,description:n,whenToUse:h,steps:d,verificationSteps:a??void 0,doNotApplyIf:g?[g]:void 0,tags:w??[]});_.invalidate("wyrm_scaffold_get"),_.invalidate("wyrm_context_build");const o={status:"captured",mode:"checklist",refs:M("scaffold",[s.id]),id:s.id,problem_type:f,description:n,steps:d.length};return E(o,(v,i)=>T(i.brand,"**Reasoning Scaffold Saved**")+`
72
72
 
73
- ${e.bullet} **Problem Type:** ${k.problem_type}
74
- ${e.bullet} **Description:** ${k.description}
75
- ${e.bullet} **Steps:** ${k.steps} checklist items
76
- ${e.bullet} **ID:** ${k.id}
73
+ ${i.bullet} **Problem Type:** ${v.problem_type}
74
+ ${i.bullet} **Description:** ${v.description}
75
+ ${i.bullet} **Steps:** ${v.steps} checklist items
76
+ ${i.bullet} **ID:** ${v.id}
77
77
 
78
- This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${k.problem_type}".`)}}];export{Ae as captureToolSpecs,fe as truthMayConflict};
78
+ This scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${v.problem_type}".`)}}];export{De as captureToolSpecs,he as truthMayConflict};