wyrm-mcp 9.2.2 → 9.2.3

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.
Files changed (118) hide show
  1. package/dist/activation.js +1 -1
  2. package/dist/agent-loop.js +2 -2
  3. package/dist/audit.js +2 -2
  4. package/dist/autoconfig.js +6 -6
  5. package/dist/backup-cli.js +6 -0
  6. package/dist/backup.js +1 -0
  7. package/dist/bridge.js +1 -1
  8. package/dist/buddy-runner.js +1 -1
  9. package/dist/buddy.js +4 -4
  10. package/dist/capabilities.js +1 -1
  11. package/dist/causality.js +3 -3
  12. package/dist/cloud/cli.js +3 -3
  13. package/dist/cloud/sync-daemon-entrypoint.js +1 -1
  14. package/dist/cloud/sync-engine.js +8 -7
  15. package/dist/cloud-backup.js +4 -4
  16. package/dist/cloud-sync-entrypoint.js +1 -1
  17. package/dist/cloud-sync.js +1 -1
  18. package/dist/connectors/ingest.js +2 -2
  19. package/dist/constellation.js +1 -1
  20. package/dist/context-build-budgeted.js +4 -4
  21. package/dist/context-ranking.js +1 -1
  22. package/dist/daemon-writer.js +1 -1
  23. package/dist/database.js +41 -23
  24. package/dist/design.js +1 -1
  25. package/dist/erasure-cli.js +3 -0
  26. package/dist/erasure-log.js +8 -0
  27. package/dist/erasure.js +1 -0
  28. package/dist/event-replication.js +1 -1
  29. package/dist/events.js +11 -11
  30. package/dist/failure-patterns.js +67 -44
  31. package/dist/federation.js +11 -10
  32. package/dist/governance.js +1 -0
  33. package/dist/handlers/agent.js +4 -4
  34. package/dist/handlers/alias-adapters.js +1 -1
  35. package/dist/handlers/audit.js +1 -1
  36. package/dist/handlers/boundary.js +1 -1
  37. package/dist/handlers/capture.js +54 -52
  38. package/dist/handlers/causality.js +2 -2
  39. package/dist/handlers/cloud.js +9 -9
  40. package/dist/handlers/companion.js +4 -4
  41. package/dist/handlers/entity.js +6 -6
  42. package/dist/handlers/events.js +4 -4
  43. package/dist/handlers/failure.js +15 -12
  44. package/dist/handlers/goals.js +2 -2
  45. package/dist/handlers/intelligence.js +81 -74
  46. package/dist/handlers/mcpclient.js +3 -3
  47. package/dist/handlers/presence.js +1 -1
  48. package/dist/handlers/prompts.js +2 -2
  49. package/dist/handlers/quest.js +11 -4
  50. package/dist/handlers/recall.js +10 -10
  51. package/dist/handlers/resources.js +1 -1
  52. package/dist/handlers/review.js +3 -3
  53. package/dist/handlers/run.js +3 -3
  54. package/dist/handlers/search.js +1 -1
  55. package/dist/handlers/session.js +27 -25
  56. package/dist/handlers/share.js +3 -3
  57. package/dist/handlers/skill.js +2 -2
  58. package/dist/handlers/symbols.js +5 -5
  59. package/dist/handlers/syncops.js +2 -2
  60. package/dist/harvest.js +5 -5
  61. package/dist/hours.js +1 -1
  62. package/dist/http-auth.js +2 -2
  63. package/dist/http-fast.js +17 -17
  64. package/dist/http-projects.js +4 -0
  65. package/dist/http-rate-limit.js +1 -0
  66. package/dist/index-health.js +52 -0
  67. package/dist/index.js +2 -2
  68. package/dist/indexer.js +4 -4
  69. package/dist/intelligence.js +36 -29
  70. package/dist/lane-provenance.js +39 -0
  71. package/dist/license.js +1 -1
  72. package/dist/lineage.js +15 -0
  73. package/dist/live-embedding-state.js +2 -2
  74. package/dist/maintenance.js +2 -2
  75. package/dist/mcp-integrity.js +2 -2
  76. package/dist/memory-artifacts.js +39 -37
  77. package/dist/metabolize.js +9 -7
  78. package/dist/metrics.js +1 -1
  79. package/dist/migrate-prompt.js +1 -1
  80. package/dist/migrations.js +236 -45
  81. package/dist/priority-embed.js +1 -1
  82. package/dist/providers/embedding-provider.js +1 -1
  83. package/dist/readonly-gate.js +1 -1
  84. package/dist/redact-backfill.js +1 -0
  85. package/dist/rehydration.js +30 -20
  86. package/dist/reindex.js +1 -1
  87. package/dist/render-target.js +6 -6
  88. package/dist/repl-guard.js +1 -1
  89. package/dist/replication-daemon-entrypoint.js +1 -1
  90. package/dist/replication-daemon.js +1 -1
  91. package/dist/retention.js +1 -0
  92. package/dist/reverse-bridge.js +6 -6
  93. package/dist/review-sample.js +31 -0
  94. package/dist/security.js +1 -1
  95. package/dist/self-maintenance.js +4 -4
  96. package/dist/served-records.js +8 -2
  97. package/dist/setup-wizard.js +1 -1
  98. package/dist/setup.js +1 -1
  99. package/dist/skill-author.js +2 -2
  100. package/dist/store-open.js +1 -0
  101. package/dist/store-readonly.js +1 -0
  102. package/dist/sub-agent.js +2 -2
  103. package/dist/tool-manifest-v2.json +1 -1
  104. package/dist/tool-manifest.json +1 -1
  105. package/dist/truth-freshness.js +8 -0
  106. package/dist/ui-dashboard.js +4 -4
  107. package/dist/vault.js +1 -1
  108. package/dist/vector-init.js +1 -1
  109. package/dist/vectors.js +4 -3
  110. package/dist/visibility.js +12 -12
  111. package/dist/write-guard.js +1 -0
  112. package/dist/wyrm-cli.js +108 -92
  113. package/dist/wyrm-guard.js +13 -12
  114. package/dist/wyrm-loop.js +3 -3
  115. package/dist/wyrm-manifest.json +1 -1
  116. package/dist/wyrm-memo.js +5 -5
  117. package/package.json +1 -1
  118. package/scripts/hooks/wyrm-session-capture.mjs +3 -0
@@ -1,8 +1,8 @@
1
- import{TOOL_ANNOTATIONS as _}from"../tool-annotations.js";import{currentReadCacheKey as D}from"./boundary.js";import{resolvePolicy as K,policyHash as z}from"../recall-policy.js";import{autoConfigureAll as G,findWyrmServerPath as Y,getDefaultDbPath as Q,getStatusSummary as Z,removeFromAll as J}from"../autoconfig.js";import{daemonOr as X}from"../daemon-writer.js";import{renderFailureStats as ee}from"../failure-patterns.js";import{computeStaleness as te}from"../intelligence.js";import{logger as ne}from"../logger.js";import{runMaintenance as oe}from"../maintenance.js";import{cache as S}from"../performance.js";import{createProvider as re}from"../providers/embedding-provider.js";import{reindexProjects as se}from"../reindex.js";import{asEnum as ae,asNumber as ie,requireBool as ce,requireInt as de,requireString as O}from"../validate.js";import{requireProjectFromArg as F}from"./project-arg.js";import{createVectorStore as le}from"../vectors.js";import{recordWrite as W,buildWriteDigest as ue}from"../receipts.js";import{renderResult as pe}from"../render.js";import{getActor as me}from"./boundary.js";const Ae=[{name:"wyrm_feedback",description:"Record whether a recalled knowledge artifact was useful. Adjusts confidence over time \u2014 successful reuse boosts it, failure lowers it. Call after applying a recalled pattern or lesson.",inputSchema:{type:"object",properties:{artifactId:{type:"number",description:"ID of the memory artifact (from wyrm_recall results)"},success:{type:"boolean",description:"Was this artifact helpful for the task?"}},required:["artifactId","success"]},annotations:_.wyrm_feedback,aliases:[],handler:async(p,f)=>{const{memory:l}=f,d=p,i=de("artifactId",d.artifactId,{min:1}),c=ce("success",d.success),r=l.get(i);if(!r)return{content:[{type:"text",text:`Artifact #${i} not found.`}],isError:!0};l.recordFeedback(i,c);const n=l.get(i),o={status:"captured",mode:"feedback",refs:[`mem:${i}`],artifact_id:i,success:c,problem:r.problem.slice(0,60),confidence:n.confidence,reuse_count:n.reuse_count,reuse_success_count:n.reuse_success_count,reuse_failure_count:n.reuse_failure_count};return pe(o,t=>`\u{F115D} Feedback recorded ${t.success?"\u2705":"\u274C"}
1
+ import{TOOL_ANNOTATIONS as $}from"../tool-annotations.js";import{currentReadCacheKey as E}from"./boundary.js";import{resolvePolicy as K,policyHash as z}from"../recall-policy.js";import{autoConfigureAll as G,findWyrmServerPath as Y,getDefaultDbPath as Q,getStatusSummary as Z,removeFromAll as J}from"../autoconfig.js";import{daemonOr as X}from"../daemon-writer.js";import{renderFailureStats as ee}from"../failure-patterns.js";import{computeStaleness as te}from"../intelligence.js";import{ageLabel as ne,crossCategoryKeys as oe,crossCategoryWarning as re,truthFreshness as se}from"../truth-freshness.js";import{logger as ae}from"../logger.js";import{runMaintenance as ie}from"../maintenance.js";import{cache as P}from"../performance.js";import{createProvider as ce}from"../providers/embedding-provider.js";import{reindexProjects as de}from"../reindex.js";import{asEnum as le,asNumber as ue,requireBool as pe,requireInt as me,requireString as L}from"../validate.js";import{requireProjectFromArg as M}from"./project-arg.js";import{createVectorStore as fe}from"../vectors.js";import{recordWrite as q,buildWriteDigest as ye}from"../receipts.js";import{renderResult as he}from"../render.js";import{getActor as ge}from"./boundary.js";const Le=[{name:"wyrm_feedback",description:"Record whether a recalled knowledge artifact was useful. Adjusts confidence over time: successful reuse boosts it, failure lowers it. Call after applying a recalled pattern or lesson.",inputSchema:{type:"object",properties:{artifactId:{type:"number",description:"ID of the memory artifact (from wyrm_recall results)"},success:{type:"boolean",description:"Was this artifact helpful for the task?"}},required:["artifactId","success"]},annotations:$.wyrm_feedback,aliases:[],handler:async(u,f)=>{const{memory:d}=f,l=u,i=me("artifactId",l.artifactId,{min:1}),c=pe("success",l.success),o=d.get(i);if(!o)return{content:[{type:"text",text:`Artifact #${i} not found.`}],isError:!0};d.recordFeedback(i,c);const t=d.get(i),r={status:"captured",mode:"feedback",refs:[`mem:${i}`],artifact_id:i,success:c,problem:o.problem.slice(0,60),confidence:t.confidence,reuse_count:t.reuse_count,reuse_success_count:t.reuse_success_count,reuse_failure_count:t.reuse_failure_count};return he(r,n=>`\u{F115D} Feedback recorded ${n.success?"\u2705":"\u274C"}
2
2
 
3
- - Artifact #${t.artifact_id}: "${t.problem}"
4
- - New confidence: ${(t.confidence*100).toFixed(0)}%
5
- - Total uses: ${t.reuse_count} (\u2705 ${t.reuse_success_count} / \u274C ${t.reuse_failure_count})`)}},{name:"wyrm_context_build",description:"Use to build a compact context brief for the current task under a token budget - relevant patterns, lessons, and truths in one formatted block; max_tokens elides low-score items to recallable stubs.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},task:{type:"string",description:"The current task"},maxItems:{type:"number",description:"Default 10, max 20"},kinds:{type:"array",items:{type:"string"},description:"Artifact kinds (see wyrm_recall)"},minConfidence:{type:"number",description:"Default 0.3"},max_tokens:{type:"number",description:"Token budget; low-score items elide to stubs"},session_id:{type:"number",description:"Already-seen dedup"},strict_budget:{type:"boolean",description:"Also elide truths over budget"}},required:["projectPath","task"]},annotations:_.wyrm_context_build,aliases:[],handler:async(p,f)=>{const{cachedResponse:l,db:d,groundTruths:i,memory:c,runBudgetedContextBuild:r,scaffoldLib:n}=f,o=p||{},t=D(),e=me().run_id,s=K(d.getDatabase(),e),m=s.mode==="full"&&s.mutedIds.length===0&&s.challengedTruthIds.length===0,u=t&&!m?`${t}::${z(s)}`:t,{projectPath:h,task:a,maxItems:$,kinds:x,minConfidence:w,max_tokens:b,session_id:P,strict_budget:g}=p,y=F(d,d.getDatabase(),h);if(!y)return{content:[{type:"text",text:`Project not found: ${h}`}],isError:!0};if(!a||typeof a!="string"||!a.trim())return{content:[{type:"text",text:"wyrm_context_build: 'task' is required \u2014 pass a short description of the current task (not 'query')."}],isError:!0};if(b!=null&&!process.env.WYRM_DISABLE_TOKEN_BUDGET)return r({project:y,task:a,maxTokens:b,sessionId:P,strictBudget:g===!0,kinds:x,minConfidence:w,runId:e});const v=[],C=i.formatForContext(y.id);C&&v.push(C.slice(0,1200));const k=n.findBest(a,y.id);if(k){const I=n.formatForContext(k);v.push(I.slice(0,1500))}const j=c.buildContextBrief(y.id,a,{maxItems:Math.min($??10,20),kinds:x,minConfidence:w,runId:e});if(j.sections.length>0){const I=j.text.slice(0,2e3);v.push(I)}if(v.length===0)return{content:[{type:"text",text:`\u{F115D} **Context Brief**
3
+ - Artifact #${n.artifact_id}: "${n.problem}"
4
+ - New confidence: ${(n.confidence*100).toFixed(0)}%
5
+ - Total uses: ${n.reuse_count} (\u2705 ${n.reuse_success_count} / \u274C ${n.reuse_failure_count})`)}},{name:"wyrm_context_build",description:"Use to build a compact context brief for the current task under a token budget - relevant patterns, lessons, and truths in one formatted block; max_tokens elides low-score items to recallable stubs.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},task:{type:"string",description:"The current task"},maxItems:{type:"number",description:"Default 10, max 20"},kinds:{type:"array",items:{type:"string"},description:"Artifact kinds (see wyrm_recall)"},minConfidence:{type:"number",description:"Default 0.3"},max_tokens:{type:"number",description:"Token budget; low-score items elide to stubs"},session_id:{type:"number",description:"Already-seen dedup"},strict_budget:{type:"boolean",description:"Also elide truths over budget"}},required:["projectPath","task"]},annotations:$.wyrm_context_build,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:l,groundTruths:i,memory:c,runBudgetedContextBuild:o,scaffoldLib:t}=f,r=u||{},n=E(),e=ge().run_id,s=K(l.getDatabase(),e),p=s.mode==="full"&&s.mutedIds.length===0&&s.challengedTruthIds.length===0,m=n&&!p?`${n}::${z(s)}`:n,{projectPath:b,task:a,maxItems:C,kinds:h,minConfidence:_,max_tokens:w,session_id:v,strict_budget:y}=u,g=M(l,l.getDatabase(),b);if(!g)return{content:[{type:"text",text:`Project not found: ${b}`}],isError:!0};if(!a||typeof a!="string"||!a.trim())return{content:[{type:"text",text:"wyrm_context_build: 'task' is required; pass a short description of the current task (not 'query')."}],isError:!0};if(w!=null&&!process.env.WYRM_DISABLE_TOKEN_BUDGET)return o({project:g,task:a,maxTokens:w,sessionId:v,strictBudget:y===!0,kinds:h,minConfidence:_,runId:e});const S=[],T=i.formatForContext(g.id);T&&S.push(T.slice(0,1200));const k=t.findBest(a,g.id);if(k){const R=t.formatForContext(k);S.push(R.slice(0,1500))}const j=c.buildContextBrief(g.id,a,{maxItems:Math.min(C??10,20),kinds:h,minConfidence:_,runId:e});if(j.sections.length>0){const R=j.text.slice(0,2e3);S.push(R)}if(S.length===0)return{content:[{type:"text",text:`\u{F115D} **Context Brief**
6
6
 
7
7
  No relevant memory found for this task yet.
8
8
 
@@ -12,10 +12,10 @@ As you work, use \`wyrm_remember\` to store:
12
12
  - \u{1F4A1} Heuristics and shortcuts
13
13
  - \u{1F9E0} Reasoning traces from complex problems
14
14
 
15
- Future context briefs will become richer over time.`}]};const E=c.getStats(y.id),L=i.getStats(y.id),B=`\u{F115D} **Context Brief** \u2014 "${a}"
15
+ Future context briefs will become richer over time.`}]};const A=c.getStats(g.id),F=i.getStats(g.id),D=`\u{F115D} **Context Brief**: "${a}"
16
16
 
17
- `,R=[];C&&R.push(C.slice(0,1200)),k&&R.push(n.formatForContext(k).slice(0,1500));const M=R.length>0?`<!-- cache-stable -->
18
- ${R.join(`
17
+ `,I=[];T&&I.push(T.slice(0,1200)),k&&I.push(t.formatForContext(k).slice(0,1500));const N=I.length>0?`<!-- cache-stable -->
18
+ ${I.join(`
19
19
 
20
20
  ---
21
21
 
@@ -24,43 +24,50 @@ ${R.join(`
24
24
 
25
25
  ---
26
26
 
27
- `:"",V=j.sections.length>0?j.text.slice(0,2e3):"";let A=`${B}${M}${V}`;A+=`
27
+ `:"",V=j.sections.length>0?j.text.slice(0,2e3):"";let O=`${D}${N}${V}`;O+=`
28
28
 
29
- _Brief: ${L.current} ground truth${L.current!==1?"s":""}, scaffold: ${k?k.scaffold.problem_type:"none"}, ${E.total} memory artifact${E.total!==1?"s":""}.`,j.sourceIds.length>0?A+=` Memory IDs: [${j.sourceIds.join(", ")}]; rate them with \`wyrm_capture\` mode=feedback._`:A+="_";try{const{logSavings:I}=await import("../statusline.js"),U=d.getDatabase(),H=U.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(y.id),q=Math.round(M.length/4);q>0&&I(U,"wyrm_context_build","cached_preamble",Math.round(q*.9),H?.id)}catch{}const N=l(A);return u&&S.set(u,N,1e4),N}},{name:"wyrm_truth_set",description:"Use to lock in a confirmed fact or constraint as project ground truth - architecture, conventions, key decisions. Injected first into every brief; re-setting the same category+key supersedes (history kept).",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"e.g. architecture, conventions"},key:{type:"string",description:"snake_case key"},value:{type:"string",description:"The statement"},rationale:{type:"string",description:"Why this is true"},source:{type:"string",description:"'user'|'derived'|'observed'|'confirmed'"},confidence:{type:"number",description:"0-1 (default 1.0)"},ttl_days:{type:"number",description:"Stale after N days"}},required:["projectPath","category","key","value"]},annotations:_.wyrm_truth_set,aliases:[],handler:async(p,f)=>{const{db:l,groundTruths:d,presence:i}=f,{projectPath:c,category:r,key:n,value:o,rationale:t,source:e,confidence:s,ttl_days:m}=p,u=O("category",r,{maxLen:200});O("key",n,{minLen:1}),O("value",o,{minLen:1});const h=ie("confidence",s,{min:0,max:1}),a=F(l,l.getDatabase(),c);if(!a)return{content:[{type:"text",text:`Project not found: ${c}`}],isError:!0};const{isConfigShapedTruth:$}=await import("../metabolize.js");if(process.env.WYRM_NO_TRUTH_LINT!=="1"&&$(n,o)){const g=(await import("../memory-artifacts.js")).MemoryArtifacts,T=new g(l.getDatabase()).add(a.id,{kind:"pattern",problem:`TRUTH-SHAPE LINT: "${n}" looks like a config assignment, not a ground truth: ${o.slice(0,120)}`,validatedFix:"If this really is a durable truth, re-set it as a full statement with a subject (or bypass once with WYRM_NO_TRUTH_LINT=1). If it is scan/tool config, it belongs in the data lake, not ground truths.",tags:["metabolize","truth-lint"],confidence:.5,needsReview:1}),v=W(l.getDatabase(),{tool:"wyrm_truth_set",outcome:"queued",refTable:"mem",refId:T.id,reason:"truth-shape lint \u2014 config-assignment shape queued for review instead of stored as a bare truth",source:e,projectId:a.id});return{content:[{type:"text",text:`\u{F115D} **Truth-shape lint** \u2014 "${n}" looks like a config assignment (\`ident = value\`), not a ground truth. Queued for review as mem:${T.id} instead of storing (receipt: ${v.outcome}). Re-phrase as a full statement, or set WYRM_NO_TRUTH_LINT=1 to bypass once.`}]}}const x={category:u,key:n,value:o,rationale:t,source:e,confidence:h??1,ttl_days:m};d.lastCascade=null;const w=await X("truth_set",a.id,x,()=>d.set(a.id,x));S.invalidate("wyrm_truth_get"),S.invalidate("wyrm_context_build");const b=d.lastCascade,P=b&&b.invalidated>0?`
30
- - **Auto-cascade:** ${b.invalidated} downstream decision edge(s) invalidated (rested on the superseded value \u2014 review with \`wyrm_decision_downstream\`)`:"";return W(l.getDatabase(),{tool:"wyrm_truth_set",outcome:"stored",refTable:"truth",refId:w.id,reason:b&&b.invalidated>0?`supersede cascaded \u2014 ${b.invalidated} downstream decision edge(s) invalidated`:void 0,source:e,projectId:a.id}),{content:[{type:"text",text:`\u{F115D} **Ground Truth Set** \u2705
29
+ _Brief: ${F.current} ground truth${F.current!==1?"s":""}, scaffold: ${k?k.scaffold.problem_type:"none"}, ${A.total} memory artifact${A.total!==1?"s":""}.`,j.sourceIds.length>0?O+=` Memory IDs: [${j.sourceIds.join(", ")}]; rate them with \`wyrm_capture\` mode=feedback._`:O+="_";try{const{logSavings:R}=await import("../statusline.js"),U=l.getDatabase(),H=U.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(g.id),B=Math.round(N.length/4);B>0&&R(U,"wyrm_context_build","cached_preamble",Math.round(B*.9),H?.id)}catch{}const W=d(O);return m&&P.set(m,W,1e4),W}},{name:"wyrm_truth_set",description:"Use to lock in a confirmed fact or constraint as project ground truth - architecture, conventions, key decisions. Injected first into every brief; re-setting the same category+key supersedes (history kept).",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"e.g. architecture, conventions"},key:{type:"string",description:"snake_case key"},value:{type:"string",description:"The statement"},rationale:{type:"string",description:"Why this is true"},source:{type:"string",description:"'user'|'derived'|'observed'|'confirmed'"},confidence:{type:"number",description:"0-1 (default 1.0)"},ttl_days:{type:"number",description:"Stale after N days"}},required:["projectPath","category","key","value"]},annotations:$.wyrm_truth_set,aliases:[],handler:async(u,f)=>{const{db:d,groundTruths:l,presence:i}=f,{projectPath:c,category:o,key:t,value:r,rationale:n,source:e,confidence:s,ttl_days:p}=u,m=L("category",o,{maxLen:200});L("key",t,{minLen:1}),L("value",r,{minLen:1});const b=ue("confidence",s,{min:0,max:1}),a=M(d,d.getDatabase(),c);if(!a)return{content:[{type:"text",text:`Project not found: ${c}`}],isError:!0};const{isConfigShapedTruth:C}=await import("../metabolize.js");if(process.env.WYRM_NO_TRUTH_LINT!=="1"&&C(t,r)){const A=(await import("../memory-artifacts.js")).MemoryArtifacts,D=new A(d.getDatabase()).add(a.id,{kind:"pattern",problem:`TRUTH-SHAPE LINT: "${t}" looks like a config assignment, not a ground truth: ${r.slice(0,120)}`,validatedFix:"If this really is a durable truth, re-set it as a full statement with a subject (or bypass once with WYRM_NO_TRUTH_LINT=1). If it is scan/tool config, it belongs in the data lake, not ground truths.",tags:["metabolize","truth-lint"],confidence:.5,needsReview:1}),I=q(d.getDatabase(),{tool:"wyrm_truth_set",outcome:"queued",refTable:"mem",refId:D.id,reason:"truth-shape lint: config-assignment shape queued for review instead of stored as a bare truth",source:e,projectId:a.id});return{content:[{type:"text",text:`\u{F115D} **Truth-shape lint**: "${t}" looks like a config assignment (\`ident = value\`), not a ground truth. Queued for review as mem:${D.id} instead of storing (receipt: ${I.outcome}). Re-phrase as a full statement, or set WYRM_NO_TRUTH_LINT=1 to bypass once.`}]}}const h={category:m,key:t,value:r,rationale:n,source:e,confidence:b??1,ttl_days:p};l.lastCascade=null;const _=d.getDatabase(),w=_.prepare("SELECT id FROM ground_truths WHERE project_id = ? AND category = ? AND key = ? AND is_current = 1 ORDER BY id DESC LIMIT 1").get(a.id,m,t),v=re(t,oe(_,a.id,m,t)),y=await X("truth_set",a.id,h,()=>l.set(a.id,h)),g=w!==void 0&&w.id===y.id;P.invalidate("wyrm_truth_get"),P.invalidate("wyrm_context_build");const x=l.lastCascade,S=x&&x.invalidated>0?`
30
+ - **Auto-cascade:** ${x.invalidated} downstream decision edge(s) invalidated (rested on the superseded value; review with \`wyrm_decision_downstream\`)`:"",T=[g?"verified: unchanged value re-asserted, last_verified_at stamped, no new version":"",x&&x.invalidated>0?`supersede cascaded: ${x.invalidated} downstream decision edge(s) invalidated`:"",v?`warning: ${v}`:""].filter(Boolean);q(d.getDatabase(),{tool:"wyrm_truth_set",outcome:"stored",refTable:"truth",refId:y.id,reason:T.length>0?T.join("; "):void 0,source:e,projectId:a.id});const k=v?`
31
+ - **Warning:** ${v}`:"";if(g)return{content:[{type:"text",text:`\u{F115D} **Ground Truth Verified** \u2705
31
32
 
32
- - **Category:** ${r}
33
- - **Key:** ${n}
34
- - **Value:** ${o}${t?`
35
- - **Rationale:** ${t}`:""}${m?`
36
- - **TTL:** ${m} days`:""}
37
- - **ID:** ${w.id}${P}
33
+ - **Category:** ${o}
34
+ - **Key:** ${t}
35
+ - **ID:** ${y.id}
36
+ - **last_verified_at:** ${y.last_verified_at} (same value re-asserted: verified in place, no new version)${k}`}]};const j=y.ttl_days?`
37
+ - **TTL:** ${y.ttl_days} days${p?"":" (default for this category and key)"}`:"";return{content:[{type:"text",text:`\u{F115D} **Ground Truth Set** \u2705
38
38
 
39
- This truth will be injected at the top of every \`wyrm_context_build\` response for this project.`}]}}},{name:"wyrm_truth_get",description:"Use to read the established facts and ground rules for a codebase - validated truths the AI should treat as baseline, optionally filtered by category.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"Category filter"}},required:["projectPath"]},annotations:_.wyrm_truth_get,aliases:[],handler:async(p,f)=>{const{cachedResponse:l,db:d,groundTruths:i}=f,c=D(),{projectPath:r,category:n}=p,o=F(d,d.getDatabase(),r);if(!o)return{content:[{type:"text",text:`Project not found: ${r}`}],isError:!0};const t=i.getCurrent(o.id,n);if(t.length===0)return{content:[{type:"text",text:`\u{F115D} **Ground Truths**
39
+ - **Category:** ${o}
40
+ - **Key:** ${t}
41
+ - **Value:** ${r}${n?`
42
+ - **Rationale:** ${n}`:""}${j}
43
+ - **ID:** ${y.id}${S}${k}
40
44
 
41
- ${n?`No ground truths in category "${n}" for this project.`:"No ground truths stored yet. Use `wyrm_truth_set` to add validated facts."}`}]};const e=new Map;for(const u of t)e.has(u.category)||e.set(u.category,[]),e.get(u.category).push(u);let s=`\u{F115D} **Ground Truths** \u2014 ${t.length} active
45
+ This truth will be injected at the top of every \`wyrm_context_build\` response for this project.`}]}}},{name:"wyrm_truth_get",description:"Use to read the established facts and ground rules for a codebase - validated truths the AI should treat as baseline, optionally filtered by category.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},category:{type:"string",description:"Category filter"}},required:["projectPath"]},annotations:$.wyrm_truth_get,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:l,groundTruths:i}=f,c=E(),{projectPath:o,category:t}=u,r=M(l,l.getDatabase(),o);if(!r)return{content:[{type:"text",text:`Project not found: ${o}`}],isError:!0};const n=i.getCurrent(r.id,t);if(n.length===0)return{content:[{type:"text",text:`\u{F115D} **Ground Truths**
42
46
 
43
- `;for(const[u,h]of e){s+=`### ${u}
44
- `;for(const a of h){const $=te(a),w=$!==null&&$>.7?"[\u26A0\uFE0F STALE] ":"";s+=`- ${w}**${a.key}:** ${a.value}`,a.confidence<1&&(s+=` _(confidence: ${(a.confidence*100).toFixed(0)}%)_`),a.rationale&&(s+=`
45
- _${a.rationale}_`),$!==null&&(s+=`
46
- _staleness: ${($*100).toFixed(0)}% (TTL: ${a.ttl_days}d)_`),s+=`
47
+ ${t?`No ground truths in category "${t}" for this project.`:"No ground truths stored yet. Use `wyrm_truth_set` to add validated facts."}`}]};const e=new Map;for(const a of n)e.has(a.category)||e.set(a.category,[]),e.get(a.category).push(a);let s=`\u{F115D} **Ground Truths**: ${n.length} active
48
+
49
+ `;const p=Date.now();let m=0;for(const[a,C]of e){s+=`### ${a}
50
+ `;for(const h of C){const _=te(h),w=se(h,p),v=_!==null&&_>.7||w.stale;v&&m++,s+=`- ${v?"[\u26A0\uFE0F STALE] ":""}**${h.key}:** ${h.value} _(${ne(w)})_`,h.confidence<1&&(s+=` _(confidence: ${(h.confidence*100).toFixed(0)}%)_`),h.rationale&&(s+=`
51
+ _${h.rationale}_`),_!==null&&(s+=`
52
+ _staleness: ${(_*100).toFixed(0)}% (TTL: ${h.ttl_days}d)_`),s+=`
47
53
  `}s+=`
48
- `}const m=l(s);return c&&S.set(c,m,15e3),m}},{name:"wyrm_scaffold_get",description:"Find the best reasoning scaffold for a task description. Returns the most relevant structured checklist, or null if no match meets the confidence threshold.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (searches project + global)"},task:{type:"string",description:"Task description to match against scaffolds"},minConfidence:{type:"number",description:"Minimum match confidence 0-1 (default: 0.3)"}},required:["task"]},annotations:_.wyrm_scaffold_get,aliases:[],handler:async(p,f)=>{const{cachedResponse:l,db:d,scaffoldLib:i}=f,c=D(),{projectPath:r,task:n,minConfidence:o}=p,t=r?d.getProject(r)?.id??void 0:void 0,e=i.findBest(n,t,o);if(!e)return{content:[{type:"text",text:`\u{F115D} **Reasoning Scaffold**
54
+ `}m>0&&(s+=`_${m} truth(s) past their TTL. Verify one that still holds by re-setting the same value (wyrm_truth_set) or \`wyrm truth verify <id>\`; supersede one that does not._
55
+ `);const b=d(s);return c&&P.set(c,b,15e3),b}},{name:"wyrm_scaffold_get",description:"Find the best reasoning scaffold for a task description. Returns the most relevant structured checklist, or null if no match meets the confidence threshold.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (searches project + global)"},task:{type:"string",description:"Task description to match against scaffolds"},minConfidence:{type:"number",description:"Minimum match confidence 0-1 (default: 0.3)"}},required:["task"]},annotations:$.wyrm_scaffold_get,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:l,scaffoldLib:i}=f,c=E(),{projectPath:o,task:t,minConfidence:r}=u,n=o?l.getProject(o)?.id??void 0:void 0,e=i.findBest(t,n,r);if(!e)return{content:[{type:"text",text:`\u{F115D} **Reasoning Scaffold**
49
56
 
50
- No scaffold matched for: "${n}"
57
+ No scaffold matched for: "${t}"
51
58
 
52
- Use \`wyrm_scaffold_save\` to add scaffolds for this problem type.`}]};const s=i.formatForContext(e),m=l(`\u{F115D} **Reasoning Scaffold Match** (${(e.matchScore*100).toFixed(0)}% confidence)
59
+ Use \`wyrm_scaffold_save\` to add scaffolds for this problem type.`}]};const s=i.formatForContext(e),p=d(`\u{F115D} **Reasoning Scaffold Match** (${(e.matchScore*100).toFixed(0)}% confidence)
53
60
 
54
- ${s}`);return c&&S.set(c,m,15e3),m}},{name:"wyrm_sync",description:"Sync database with .wyrm folders in all projects",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Sync specific project, or all if not specified"},direction:{type:"string",enum:["import","export","both"],description:"Sync direction"}}},annotations:_.wyrm_sync,aliases:[],handler:async(p,f)=>{const{db:l,sync:d}=f,{projectPath:i,direction:c}=p,r=c||"both";let n=0;if(i)l.getProject(i)&&((r==="import"||r==="both")&&d.importFromFolder(i),(r==="export"||r==="both")&&d.exportToFolder(i),n=1);else{const o=l.getAllProjects(1e3);for(const t of o)try{(r==="import"||r==="both")&&d.importFromFolder(t.path),(r==="export"||r==="both")&&d.exportToFolder(t.path),n++}catch{}}return{content:[{type:"text",text:`\u{F115D} Synced ${n} project(s)`}]}}},{name:"wyrm_stats",description:"Use for a health snapshot of the memory database - row counts, vector index coverage, usage.",inputSchema:{type:"object",properties:{view:{type:"string",enum:["failures"],description:"'failures' = prevented-repeat analytics"},projectPath:{type:"string",description:"failures view scope"}}},annotations:_.wyrm_stats,aliases:[],handler:async(p,f)=>{const{WRITE_TOOLS:l,analytics:d,cachedResponse:i,db:c,failures:r,getUsageStats:n,orchestrator:o,vectorStore:t}=f,e=p||{},s=D(),{view:m,projectPath:u}=p;if(ae("view",m,["failures"])==="failures"){const g=u?c.getProject(u):null,y=r.failureStats({projectId:g?.id??null});return{content:[{type:"text",text:ee(y)}],structuredContent:y}}const a=c.getStats(),$=S.stats(),x=n();let w="";if(t){const g=t.getStats(),y=g.byType||{},T=(y.artifact||0)+(y.quest||0)+(y.session||0),v=a.artifacts+a.quests+a.sessions,C=v>0?Math.min(100,T/v*100).toFixed(1):"0";w=`
61
+ ${s}`);return c&&P.set(c,p,15e3),p}},{name:"wyrm_sync",description:"Sync database with .wyrm folders in all projects",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Sync specific project, or all if not specified"},direction:{type:"string",enum:["import","export","both"],description:"Sync direction"}}},annotations:$.wyrm_sync,aliases:[],handler:async(u,f)=>{const{db:d,sync:l}=f,{projectPath:i,direction:c}=u,o=c||"both";let t=0;if(i)d.getProject(i)&&((o==="import"||o==="both")&&l.importFromFolder(i),(o==="export"||o==="both")&&l.exportToFolder(i),t=1);else{const r=d.getAllProjects(1e3);for(const n of r)try{(o==="import"||o==="both")&&l.importFromFolder(n.path),(o==="export"||o==="both")&&l.exportToFolder(n.path),t++}catch{}}return{content:[{type:"text",text:`\u{F115D} Synced ${t} project(s)`}]}}},{name:"wyrm_stats",description:"Use for a health snapshot of the memory database - row counts, vector index coverage, usage.",inputSchema:{type:"object",properties:{view:{type:"string",enum:["failures"],description:"'failures' = prevented-repeat analytics"},projectPath:{type:"string",description:"failures view scope"}}},annotations:$.wyrm_stats,aliases:[],handler:async(u,f)=>{const{WRITE_TOOLS:d,analytics:l,cachedResponse:i,db:c,failures:o,getUsageStats:t,orchestrator:r,vectorStore:n}=f,e=u||{},s=E(),{view:p,projectPath:m}=u;if(le("view",p,["failures"])==="failures"){const y=m?c.getProject(m):null,g=o.failureStats({projectId:y?.id??null});return{content:[{type:"text",text:ee(g)}],structuredContent:g}}const a=c.getStats(),C=P.stats(),h=t();let _="";if(n){const y=n.getStats(),g=y.byType||{},x=(g.artifact||0)+(g.quest||0)+(g.session||0),S=a.artifacts+a.quests+a.sessions,T=S>0?Math.min(100,x/S*100).toFixed(1):"0";_=`
55
62
  **Vectors:**
56
- - **Provider:** ${g.provider} (${g.model})
57
- - **Embeddings:** ${g.total} vectors
58
- - **Coverage:** ${C}% of embeddable content
59
- - **By Type:** ${Object.entries(g.byType).map(([k,j])=>`${k}: ${j}`).join(", ")}
60
- `}let b="";try{const g=ue(c.getDatabase(),1);b=`
61
- **Writes (24h):** ${Object.entries(g.byOutcome).map(([T,v])=>`${T}: ${v}`).join(", ")||"none"}
62
- **Review queue:** ${g.reviewQueueDepth} awaiting wyrm_review${g.reviewQueueDepth>0?" (invisible to recall until approved)":""}
63
- `}catch{}const P=i(`\u{F115D} **Wyrm Statistics**
63
+ - **Provider:** ${y.provider} (${y.model})
64
+ - **Embeddings:** ${y.total} vectors
65
+ - **Coverage:** ${T}% of embeddable content
66
+ - **By Type:** ${Object.entries(y.byType).map(([k,j])=>`${k}: ${j}`).join(", ")}
67
+ `}let w="";try{const y=ye(c.getDatabase(),1);w=`
68
+ **Writes (24h):** ${Object.entries(y.byOutcome).map(([x,S])=>`${x}: ${S}`).join(", ")||"none"}
69
+ **Review queue:** ${y.reviewQueueDepth} awaiting wyrm_review${y.reviewQueueDepth>0?" (invisible to recall until approved)":""}
70
+ `}catch{}const v=i(`\u{F115D} **Wyrm Statistics**
64
71
 
65
72
  - **Projects:** ${a.projects}
66
73
  - **Sessions:** ${a.sessions}
@@ -68,63 +75,63 @@ ${s}`);return c&&S.set(c,m,15e3),m}},{name:"wyrm_sync",description:"Sync databas
68
75
  - **Data Points:** ${a.dataPoints}
69
76
  - **Active Tokens:** ~${a.totalTokens.toLocaleString()}
70
77
  - **Database Size:** ${a.dbSize}
71
- `+w+b+`
72
- **Cache:** ${$.size} entries | Hit rate: ${x.cacheHitRate}
73
- **Usage:** ${x.totalCalls} calls | ~${x.tokensSaved.toLocaleString()} tokens saved by cache`);return s&&S.set(s,P,15e3),P}},{name:"wyrm_vector_setup",description:"Configure embedding provider and download models. Supports local, OpenAI, and Ollama.",inputSchema:{type:"object",properties:{provider:{type:"string",enum:["auto","local","openai","ollama","none"],description:"Provider: auto-detect, local hash, OpenAI API, Ollama, or none"},model:{type:"string",description:"Model name (e.g., 'text-embedding-3-small' for OpenAI, 'nomic-embed-text' for Ollama)"},apiKey:{type:"string",description:"OpenAI API key (if using openai provider)"},ollamaUrl:{type:"string",description:"Ollama URL (default: http://localhost:11434)"}}},annotations:_.wyrm_vector_setup,aliases:[],handler:async(p,f)=>{const{db:l,indexingPipeline:d,memory:i,setVectorStore:c,vectorStore:r}=f,{provider:n,model:o,apiKey:t,ollamaUrl:e}=p,s={provider:n||"auto",model:o,apiKey:t,ollamaUrl:e};try{const m=re(s),u=await m.isReady();if(!u&&s.provider!=="none")return{content:[{type:"text",text:`\u274C **Provider Not Ready**
78
+ `+_+w+`
79
+ **Cache:** ${C.size} entries | Hit rate: ${h.cacheHitRate}
80
+ **Usage:** ${h.totalCalls} calls | ~${h.tokensSaved.toLocaleString()} tokens saved by cache`);return s&&P.set(s,v,15e3),v}},{name:"wyrm_vector_setup",description:"Configure embedding provider and download models. Supports local, OpenAI, and Ollama.",inputSchema:{type:"object",properties:{provider:{type:"string",enum:["auto","local","openai","ollama","none"],description:"Provider: auto-detect, local hash, OpenAI API, Ollama, or none"},model:{type:"string",description:"Model name (e.g., 'text-embedding-3-small' for OpenAI, 'nomic-embed-text' for Ollama)"},apiKey:{type:"string",description:"OpenAI API key (if using openai provider)"},ollamaUrl:{type:"string",description:"Ollama URL (default: http://localhost:11434)"}}},annotations:$.wyrm_vector_setup,aliases:[],handler:async(u,f)=>{const{db:d,indexingPipeline:l,memory:i,setVectorStore:c,vectorStore:o}=f,{provider:t,model:r,apiKey:n,ollamaUrl:e}=u,s={provider:t||"auto",model:r,apiKey:n,ollamaUrl:e};try{const p=ce(s),m=await p.isReady();if(!m&&s.provider!=="none")return{content:[{type:"text",text:`\u274C **Provider Not Ready**
74
81
 
75
- Couldn't connect to ${m.name}. Check your configuration and try again.`}],isError:!0};const h=le(s,l.getDatabase());c(h),i.setVectorStore(h),d&&d.updateStore(h);const a=h.getStats();return{content:[{type:"text",text:`\u2705 **Vector Setup Complete**
82
+ Couldn't connect to ${p.name}. Check your configuration and try again.`}],isError:!0};const b=fe(s,d.getDatabase());c(b),i.setVectorStore(b),l&&l.updateStore(b);const a=b.getStats();return{content:[{type:"text",text:`\u2705 **Vector Setup Complete**
76
83
 
77
- - **Provider:** ${m.name}
78
- - **Model:** ${m.model}
79
- - **Dimensions:** ${m.dimensions}
80
- - **Status:** ${u?"Ready":"Ready (will auto-connect)"}
84
+ - **Provider:** ${p.name}
85
+ - **Model:** ${p.model}
86
+ - **Dimensions:** ${p.dimensions}
87
+ - **Status:** ${m?"Ready":"Ready (will auto-connect)"}
81
88
 
82
- _Generate embeddings with \`wyrm index rebuild\` (CLI)._`}]}}catch(m){return{content:[{type:"text",text:`\u274C **Vector Setup Failed**
89
+ _Generate embeddings with \`wyrm index rebuild\` (CLI)._`}]}}catch(p){return{content:[{type:"text",text:`\u274C **Vector Setup Failed**
83
90
 
84
- ${String(m)}`}],isError:!0}}}},{name:"wyrm_reindex",description:"Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to reindex, or leave empty for all projects"},dryRun:{type:"boolean",description:"Show what would be indexed without making changes"}}},annotations:_.wyrm_reindex,aliases:[],handler:async(p,f)=>{const{db:l,failures:d,vectorStore:i}=f,{projectPath:c,dryRun:r}=p;if(!i)return{content:[{type:"text",text:`\u274C **Vector Indexing Disabled**
91
+ ${String(p)}`}],isError:!0}}}},{name:"wyrm_reindex",description:"Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project to reindex, or leave empty for all projects"},dryRun:{type:"boolean",description:"Show what would be indexed without making changes"}}},annotations:$.wyrm_reindex,aliases:[],handler:async(u,f)=>{const{db:d,failures:l,vectorStore:i}=f,{projectPath:c,dryRun:o}=u;if(!i)return{content:[{type:"text",text:`\u274C **Vector Indexing Disabled**
85
92
 
86
- Run wyrm_vector_setup first to enable vector search.`}],isError:!0};let n=[];if(c){const e=l.getProject(c);if(!e)return{content:[{type:"text",text:`\u274C **Project Not Found:** ${c}`}],isError:!0};n=[e.id]}else n=l.getAllProjects(1e3).map(s=>s.id);const{indexed:o,skipped:t}=await se(l.getDatabase(),i,n,{dryRun:r,onError:(e,s)=>ne.error(e,s)});return{content:[{type:"text",text:`\u{F115D} **Reindexing ${r?"(Dry Run)":"Complete"}**
93
+ Run wyrm_vector_setup first to enable vector search.`}],isError:!0};let t=[];if(c){const e=d.getProject(c);if(!e)return{content:[{type:"text",text:`\u274C **Project Not Found:** ${c}`}],isError:!0};t=[e.id]}else t=d.getAllProjects(1e3).map(s=>s.id);const{indexed:r,skipped:n}=await de(d.getDatabase(),i,t,{dryRun:o,onError:(e,s)=>ae.error(e,s)});return{content:[{type:"text",text:`\u{F115D} **Reindexing ${o?"(Dry Run)":"Complete"}**
87
94
 
88
- - **Projects:** ${n.length}
89
- - **Indexed:** ${o}
90
- - **Skipped:** ${t}
95
+ - **Projects:** ${t.length}
96
+ - **Indexed:** ${r}
97
+ - **Skipped:** ${n}
91
98
 
92
- `+(r?"_Run without dryRun to actually index._":"_Vectors are now up to date._")}]}}},{name:"wyrm_maintenance",description:"Use for periodic upkeep of the memory store - archive old sessions, vacuum, prune stale events, sweep run quarantine. Admin-gated, destructive; fleets run it between waves.",inputSchema:{type:"object",properties:{vacuum:{type:"boolean",description:"Reclaim space"},archiveDays:{type:"number",description:"Archive sessions > N days"}}},annotations:_.wyrm_maintenance,aliases:[],handler:async(p,f)=>{const{db:l,failures:d,presence:i,sessionSeen:c}=f,r=p||{},{vacuum:n,archiveDays:o}=p,t=oe({db:l,sessionSeen:c(),failures:d,presence:i},{vacuum:n,archiveDays:o});return{content:[{type:"text",text:`\u{F115D} **Maintenance Complete**
99
+ `+(o?"_Run without dryRun to actually index._":"_Vectors are now up to date._")}]}}},{name:"wyrm_maintenance",description:"Use for periodic upkeep of the memory store - archive old sessions, vacuum, prune stale events, sweep run quarantine. Admin-gated, destructive; fleets run it between waves.",inputSchema:{type:"object",properties:{vacuum:{type:"boolean",description:"Reclaim space"},archiveDays:{type:"number",description:"Archive sessions > N days"}}},annotations:$.wyrm_maintenance,aliases:[],handler:async(u,f)=>{const{db:d,failures:l,presence:i,sessionSeen:c,vectorStore:o}=f,t=u||{},{vacuum:r,archiveDays:n}=u,e=ie({db:d,sessionSeen:c(),failures:l,presence:i},{vacuum:r,archiveDays:n,activeModel:o?o.getProvider().model:null});return{content:[{type:"text",text:`\u{F115D} **Maintenance Complete**
93
100
 
94
- `+t.lines.map(s=>`- ${s}
101
+ `+e.lines.map(p=>`- ${p}
95
102
  `).join("")+`
96
- **New Database Size:** ${t.dbSize}`}]}}},{name:"wyrm_setup",description:"Auto-detect installed AI clients (VS Code, Claude Desktop, Cursor, Windsurf, Zed) and configure Wyrm's MCP server in all of them. Run this to connect Wyrm to a new AI or after switching providers.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["configure","check","remove"],description:"Action: configure (default), check status, or remove from all"},serverPath:{type:"string",description:"Override Wyrm server path (auto-detected if empty)"},dbPath:{type:"string",description:"Override database path (default: ~/.wyrm/wyrm.db)"}}},annotations:_.wyrm_setup,aliases:[],handler:async(p,f)=>{const{action:l,serverPath:d,dbPath:i}=p,c=l||"configure";if(c==="check")return{content:[{type:"text",text:Z()}]};if(c==="remove"){const e=J();return{content:[{type:"text",text:`\u{F115D} **Wyrm Removed**
103
+ **New Database Size:** ${e.dbSize}`}]}}},{name:"wyrm_setup",description:"Auto-detect installed AI clients (VS Code, Claude Desktop, Cursor, Windsurf, Zed) and configure Wyrm's MCP server in all of them. Run this to connect Wyrm to a new AI or after switching providers.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["configure","check","remove"],description:"Action: configure (default), check status, or remove from all"},serverPath:{type:"string",description:"Override Wyrm server path (auto-detected if empty)"},dbPath:{type:"string",description:"Override database path (default: ~/.wyrm/wyrm.db)"}}},annotations:$.wyrm_setup,aliases:[],handler:async(u,f)=>{const{action:d,serverPath:l,dbPath:i}=u,c=d||"configure";if(c==="check")return{content:[{type:"text",text:Z()}]};if(c==="remove"){const e=J();return{content:[{type:"text",text:`\u{F115D} **Wyrm Removed**
97
104
 
98
- Removed from ${e.filter(u=>u.action==="configured").length} AI client(s):
99
- `+e.map(u=>`- ${u.client.icon} ${u.client.name}: ${u.message}`).join(`
105
+ Removed from ${e.filter(m=>m.action==="configured").length} AI client(s):
106
+ `+e.map(m=>`- ${m.client.icon} ${m.client.name}: ${m.message}`).join(`
100
107
  `)+`
101
108
 
102
- Run wyrm_setup again to reconnect.`}]}}const r=G({serverPath:d||void 0,dbPath:i||void 0}),n=r.filter(e=>e.action==="configured"||e.action==="updated"),o=r.filter(e=>e.action==="failed");let t=`\u{F115D} **Wyrm Auto-Configure Complete**
109
+ Run wyrm_setup again to reconnect.`}]}}const o=G({serverPath:l||void 0,dbPath:i||void 0}),t=o.filter(e=>e.action==="configured"||e.action==="updated"),r=o.filter(e=>e.action==="failed");let n=`\u{F115D} **Wyrm Auto-Configure Complete**
103
110
 
104
- `;t+=`Connected to ${n.length} AI client(s):
105
- `;for(const e of r){const s=e.action==="configured"?"\u2705":e.action==="updated"?"\u{1F504}":e.action==="skipped"?"\u25CB":"\u274C";t+=`- ${s} ${e.client.icon} ${e.client.name}: ${e.message}
106
- `}return o.length>0&&(t+=`
107
- \u26A0\uFE0F ${o.length} client(s) failed. Check errors above.`),t+=`
111
+ `;n+=`Connected to ${t.length} AI client(s):
112
+ `;for(const e of o){const s=e.action==="configured"?"\u2705":e.action==="updated"?"\u{1F504}":e.action==="skipped"?"\u25CB":"\u274C";n+=`- ${s} ${e.client.icon} ${e.client.name}: ${e.message}
113
+ `}return r.length>0&&(n+=`
114
+ \u26A0\uFE0F ${r.length} client(s) failed. Check errors above.`),n+=`
108
115
 
109
116
  Server: ${Y()}
110
- DB: ${Q()}`,t+=`
117
+ DB: ${Q()}`,n+=`
111
118
 
112
- _Switch AIs anytime \u2014 run wyrm_setup again to reconnect._`,{content:[{type:"text",text:t}]}}},{name:"wyrm_usage",description:"View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",inputSchema:{type:"object",properties:{last:{type:"number",description:"Show stats for last N calls (default: all)"},reset:{type:"boolean",description:"Reset usage counters"}}},annotations:_.wyrm_usage,aliases:[],handler:async(p,f)=>{const{cachedResponse:l,getUsageStats:d,responseFingerprints:i,usageLog:c}=f,{last:r,reset:n}=p;if(n)return c.length=0,i.clear(),S.invalidate(),{content:[{type:"text",text:"\u{F115D} Usage counters reset, caches cleared."}]};const o=d(r),t=S.stats();let e=`\u{F115D} **Wyrm Usage Report**
119
+ _Switch AIs anytime: run wyrm_setup again to reconnect._`,{content:[{type:"text",text:n}]}}},{name:"wyrm_usage",description:"View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",inputSchema:{type:"object",properties:{last:{type:"number",description:"Show stats for last N calls (default: all)"},reset:{type:"boolean",description:"Reset usage counters"}}},annotations:$.wyrm_usage,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,getUsageStats:l,responseFingerprints:i,usageLog:c}=f,{last:o,reset:t}=u;if(t)return c.length=0,i.clear(),P.invalidate(),{content:[{type:"text",text:"\u{F115D} Usage counters reset, caches cleared."}]};const r=l(o),n=P.stats();let e=`\u{F115D} **Wyrm Usage Report**
113
120
 
114
- `;if(e+=`## Overview${r?` (last ${r} calls)`:""}
115
- `,e+=`- **Total Calls:** ${o.totalCalls}
116
- `,e+=`- **Cache Hits:** ${o.cachedCalls} (${o.cacheHitRate})
117
- `,e+=`- **Tokens In:** ~${o.totalTokensIn.toLocaleString()}
118
- `,e+=`- **Tokens Out:** ~${o.totalTokensOut.toLocaleString()}
119
- `,e+=`- **Tokens Saved (cache):** ~${o.tokensSaved.toLocaleString()}
120
- `,e+=`- **Avg Response:** ${o.avgResponseMs}ms
121
- `,e+=`- **Active Cache Entries:** ${t.size}
121
+ `;if(e+=`## Overview${o?` (last ${o} calls)`:""}
122
+ `,e+=`- **Total Calls:** ${r.totalCalls}
123
+ `,e+=`- **Cache Hits:** ${r.cachedCalls} (${r.cacheHitRate})
124
+ `,e+=`- **Tokens In:** ~${r.totalTokensIn.toLocaleString()}
125
+ `,e+=`- **Tokens Out:** ~${r.totalTokensOut.toLocaleString()}
126
+ `,e+=`- **Tokens Saved (cache):** ~${r.tokensSaved.toLocaleString()}
127
+ `,e+=`- **Avg Response:** ${r.avgResponseMs}ms
128
+ `,e+=`- **Active Cache Entries:** ${n.size}
122
129
 
123
- `,o.topTools.length>0){e+=`## Top Tools by Token Usage
124
- `;for(const h of o.topTools)e+=`- **${h.tool}:** ${h.calls} calls, ~${h.tokens.toLocaleString()} tokens
130
+ `,r.topTools.length>0){e+=`## Top Tools by Token Usage
131
+ `;for(const b of r.topTools)e+=`- **${b.tool}:** ${b.calls} calls, ~${b.tokens.toLocaleString()} tokens
125
132
  `;e+=`
126
- `}const s=o.totalTokensIn/1e6*15,m=o.totalTokensOut/1e6*75,u=o.tokensSaved/1e6*75;return e+=`## Estimated Cost (Claude Opus rates)
133
+ `}const s=r.totalTokensIn/1e6*15,p=r.totalTokensOut/1e6*75,m=r.tokensSaved/1e6*75;return e+=`## Estimated Cost (Claude Opus rates)
127
134
  `,e+=`- **Input:** $${s.toFixed(4)}
128
- `,e+=`- **Output:** $${m.toFixed(4)}
129
- `,e+=`- **Saved by cache:** $${u.toFixed(4)}
130
- `,e+=`- **Net cost:** $${(s+m-u).toFixed(4)}`,l(e,!0)}}];export{Ae as intelligenceToolSpecs};
135
+ `,e+=`- **Output:** $${p.toFixed(4)}
136
+ `,e+=`- **Saved by cache:** $${m.toFixed(4)}
137
+ `,e+=`- **Net cost:** $${(s+p-m).toFixed(4)}`,d(e,!0)}}];export{Le as intelligenceToolSpecs};
@@ -1,6 +1,6 @@
1
- import{TOOL_ANNOTATIONS as c}from"../tool-annotations.js";const d=[{name:"wyrm_mcp_register",description:"Register an outbound MCP server config. Wyrm can then call its tools via wyrm_call_external. e.g. server_name='github', command='npx', args=['-y', '@modelcontextprotocol/server-github'], env={GITHUB_PERSONAL_ACCESS_TOKEN: '...'}.",inputSchema:{type:"object",properties:{server_name:{type:"string"},command:{type:"string",description:"Executable (e.g. 'npx', '/usr/local/bin/some-mcp')"},args:{type:"array",items:{type:"string"}},env:{type:"object",description:"Env vars passed to the spawned server"}},required:["server_name","command"]},annotations:c.wyrm_mcp_register,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=o,t=r.register(e);return{content:[{type:"text",text:`\u{F115D} MCP server '${t.server_name}' registered (command: ${t.command}).`}]}}},{name:"wyrm_mcp_list",description:"List registered MCP server configs.",inputSchema:{type:"object",properties:{}},annotations:c.wyrm_mcp_list,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=r.list();if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No outbound MCP servers registered."}]};const t=e.map(a=>`\u2022 ${a.server_name} ${a.enabled?"[on]":"[off]"} \u2014 ${a.command} (used ${a.use_count}\xD7)`).join(`
1
+ import{TOOL_ANNOTATIONS as c}from"../tool-annotations.js";const d=[{name:"wyrm_mcp_register",description:"Register an outbound MCP server config. Wyrm can then call its tools via wyrm_call_external. e.g. server_name='github', command='npx', args=['-y', '@modelcontextprotocol/server-github'], env={GITHUB_PERSONAL_ACCESS_TOKEN: '...'}.",inputSchema:{type:"object",properties:{server_name:{type:"string"},command:{type:"string",description:"Executable (e.g. 'npx', '/usr/local/bin/some-mcp')"},args:{type:"array",items:{type:"string"}},env:{type:"object",description:"Env vars passed to the spawned server"}},required:["server_name","command"]},annotations:c.wyrm_mcp_register,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=o,t=r.register(e);return{content:[{type:"text",text:`\u{F115D} MCP server '${t.server_name}' registered (command: ${t.command}).`}]}}},{name:"wyrm_mcp_list",description:"List registered MCP server configs.",inputSchema:{type:"object",properties:{}},annotations:c.wyrm_mcp_list,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=r.list();if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No outbound MCP servers registered."}]};const t=e.map(a=>`\u2022 ${a.server_name} ${a.enabled?"[on]":"[off]"}: ${a.command} (used ${a.use_count}\xD7)`).join(`
2
2
  `);return{content:[{type:"text",text:`\u{F115D} ${e.length} server(s):
3
- ${t}`}]}}},{name:"wyrm_mcp_tools",description:"List the tools a registered server exposes (one-shot tools/list call over MCP).",inputSchema:{type:"object",properties:{server_name:{type:"string"}},required:["server_name"]},annotations:c.wyrm_mcp_tools,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=o,t=await r.listTools(e.server_name);if(!t)return{content:[{type:"text",text:`Could not reach server '${e.server_name}' (not registered, disabled, or spawn failed).`}],isError:!0};const a=t.map(i=>`\u2022 ${i.name}${i.description?` \u2014 ${i.description.slice(0,120)}`:""}`).join(`
3
+ ${t}`}]}}},{name:"wyrm_mcp_tools",description:"List the tools a registered server exposes (one-shot tools/list call over MCP).",inputSchema:{type:"object",properties:{server_name:{type:"string"}},required:["server_name"]},annotations:c.wyrm_mcp_tools,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=o,t=await r.listTools(e.server_name);if(!t)return{content:[{type:"text",text:`Could not reach server '${e.server_name}' (not registered, disabled, or spawn failed).`}],isError:!0};const a=t.map(i=>`\u2022 ${i.name}${i.description?`: ${i.description.slice(0,120)}`:""}`).join(`
4
4
  `);return{content:[{type:"text",text:`\u{F115D} ${t.length} tool(s) on ${e.server_name}:
5
5
  ${a}`}]}}},{name:"wyrm_mcp_disable",description:"Disable an MCP server config (kept in DB but won't be spawned).",inputSchema:{type:"object",properties:{server_name:{type:"string"}},required:["server_name"]},annotations:c.wyrm_mcp_disable,aliases:[],handler:async(o,n)=>{const{mcpClient:r}=n,s=o,e=r.disable(s.server_name);return{content:[{type:"text",text:e?`\u{F115D} ${s.server_name} disabled.`:`${s.server_name} not found.`}],isError:!e}}},{name:"wyrm_call_external",description:"Use to call a tool on an outbound MCP server registered via wyrm_mcp - lazily spawned over stdio, result returned and logged to external_call_log.",inputSchema:{type:"object",properties:{server:{type:"string",description:"Registered server_name"},tool:{type:"string",description:"Tool name to invoke"},args:{type:"object",description:"Tool arguments"}},required:["server","tool"]},annotations:c.wyrm_call_external,aliases:[],handler:async(o,n)=>{const{mcpClient:r,server:s}=n,e=o,t=await r.call(e.server,e.tool,e.args??{});return t.ok?{content:[{type:"text",text:`\u{F115D} ${e.server}.${e.tool} (${t.latency_ms}ms):
6
- ${JSON.stringify(t.result,null,2)}`}]}:{content:[{type:"text",text:`\u274C ${e.server}.${e.tool}: ${String(t.error).replace(/\bwyrm_mcp_register\b/g,"wyrm_mcp action=register")}`}],isError:!0}}},{name:"wyrm_tool_analytics",description:"Wyrm watches Wyrm \u2014 per-tool usage stats (volume, error rate, p50/p95 latency) over a configurable window. Use to find under-performing tools and tune retrieval/ranking. Pass mode='overview' for the total summary, 'usage' for per-tool, 'errors' for recent error sample.",inputSchema:{type:"object",properties:{mode:{type:"string",enum:["overview","usage","errors"],description:"Which view (default: usage)"},days:{type:"number",description:"Window in days (default 30)"},tool_name:{type:"string",description:"Required when mode='errors'"},limit:{type:"number",description:"Max rows (default 100 for usage, 10 for errors)"}}},annotations:c.wyrm_tool_analytics,aliases:[],handler:async(o,n)=>{const{toolAnalytics:r}=n,{mode:s,days:e,tool_name:t,limit:a}=o,i=s??"usage";if(i==="overview")return{content:[{type:"text",text:JSON.stringify(r.overview({days:e}),null,2)}]};if(i==="errors"){if(!t)return{content:[{type:"text",text:"tool_name required for mode=errors"}],isError:!0};const m=r.recentErrors(t,a??10);return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}const l=r.usageStats({days:e,limit:a??100});return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}}];export{d as mcpclientToolSpecs};
6
+ ${JSON.stringify(t.result,null,2)}`}]}:{content:[{type:"text",text:`\u274C ${e.server}.${e.tool}: ${String(t.error).replace(/\bwyrm_mcp_register\b/g,"wyrm_mcp action=register")}`}],isError:!0}}},{name:"wyrm_tool_analytics",description:"Wyrm watches Wyrm: per-tool usage stats (volume, error rate, p50/p95 latency) over a configurable window. Use to find under-performing tools and tune retrieval/ranking. Pass mode='overview' for the total summary, 'usage' for per-tool, 'errors' for recent error sample.",inputSchema:{type:"object",properties:{mode:{type:"string",enum:["overview","usage","errors"],description:"Which view (default: usage)"},days:{type:"number",description:"Window in days (default 30)"},tool_name:{type:"string",description:"Required when mode='errors'"},limit:{type:"number",description:"Max rows (default 100 for usage, 10 for errors)"}}},annotations:c.wyrm_tool_analytics,aliases:[],handler:async(o,n)=>{const{toolAnalytics:r}=n,{mode:s,days:e,tool_name:t,limit:a}=o,i=s??"usage";if(i==="overview")return{content:[{type:"text",text:JSON.stringify(r.overview({days:e}),null,2)}]};if(i==="errors"){if(!t)return{content:[{type:"text",text:"tool_name required for mode=errors"}],isError:!0};const m=r.recentErrors(t,a??10);return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}const l=r.usageStats({days:e,limit:a??100});return{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}}];export{d as mcpclientToolSpecs};
@@ -1,3 +1,3 @@
1
- import{TOOL_ANNOTATIONS as d}from"../tool-annotations.js";import{asString as f,requireString as u}from"../validate.js";const j=[{name:"wyrm_presence_announce",description:"Use from every fleet agent to announce presence and heartbeat liveness: upserts a TTL row that orchestrators and sibling agents poll before assigning or stealing work, stamped with the ambient run_id and the agent's fleet role so the board reads per-run. Re-announce to heartbeat; dead sessions expire by TTL and are reaped by wyrm_maintenance. Announce BEFORE wyrm_quest_claim - claims require a presence row.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Stable per-session identifier (e.g. 'claude-pid12345')"},agent_kind:{type:"string",description:"'claude', 'codex', 'cursor', 'human', etc."},projectPath:{type:"string",description:"Project path (optional)"},current_file:{type:"string",description:"File currently being edited"},current_symbol:{type:"string",description:"Symbol currently focused"},current_quest_id:{type:"number",description:"Quest currently being worked on"},ttl_seconds:{type:"number",description:"Heartbeat TTL \u2014 agent is considered live for this long after announce (default 300)"},metadata:{type:"object",description:"Caller-defined extras"},role:{type:"string",description:"Fleet role (default: run_agents membership)"}},required:["agent_id","agent_kind"]},annotations:d.wyrm_presence_announce,aliases:[],handler:async(n,s)=>{const{db:i,presence:t}=s,c=n||{},{agent_id:r,agent_kind:a,projectPath:o,current_file:p,current_symbol:e,current_quest_id:l,ttl_seconds:g,metadata:_}=n,m=f("role",n.role,{maxLen:200});u("agent_id",r,{minLen:1,maxLen:200}),u("agent_kind",a,{minLen:1,maxLen:200});const y=o?i.getProject(o):null,h=t.announce({agent_id:r,agent_kind:a,project_id:y?.id??null,current_file:p,current_symbol:e,current_quest_id:l,ttl_seconds:g,metadata:_,role:m});return{content:[{type:"text",text:`\u{F115D} Presence announced \u2014 ${a}:${r} (TTL ${h.ttl_seconds}s)`}]}}},{name:"wyrm_presence_list",description:"Use from an orchestrator to see which agents are live right now (heartbeat within TTL) before dispatching work - optionally scoped to a project or to agents touching a specific file, so file-level collisions surface before they happen.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional)"},on_file:{type:"string",description:"Filter to agents working on this file (optional)"}}},annotations:d.wyrm_presence_list,aliases:[],handler:async(n,s)=>{const{db:i,presence:t}=s,{projectPath:c,on_file:r}=n,a=c?i.getProject(c):null,o=r?t.agentsOnFile(r):t.liveAgents(a?.id??null);if(o.length===0)return{content:[{type:"text",text:"\u{F115D} No live agents."}]};const p=o.map(e=>{const l=[e.current_file&&`file:${e.current_file}`,e.current_symbol&&`sym:${e.current_symbol}`,e.current_quest_id&&`quest:${e.current_quest_id}`].filter(Boolean).join(" ");return`\u2022 ${e.agent_kind}:${e.agent_id}${l?` \u2014 ${l}`:""} (last ${e.last_heartbeat})`}).join(`
1
+ import{TOOL_ANNOTATIONS as d}from"../tool-annotations.js";import{asString as f,requireString as u}from"../validate.js";const j=[{name:"wyrm_presence_announce",description:"Use from every fleet agent to announce presence and heartbeat liveness: upserts a TTL row that orchestrators and sibling agents poll before assigning or stealing work, stamped with the ambient run_id and the agent's fleet role so the board reads per-run. Re-announce to heartbeat; dead sessions expire by TTL and are reaped by wyrm_maintenance. Announce BEFORE wyrm_quest_claim - claims require a presence row.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Stable per-session identifier (e.g. 'claude-pid12345')"},agent_kind:{type:"string",description:"'claude', 'codex', 'cursor', 'human', etc."},projectPath:{type:"string",description:"Project path (optional)"},current_file:{type:"string",description:"File currently being edited"},current_symbol:{type:"string",description:"Symbol currently focused"},current_quest_id:{type:"number",description:"Quest currently being worked on"},ttl_seconds:{type:"number",description:"Heartbeat TTL: agent is considered live for this long after announce (default 300)"},metadata:{type:"object",description:"Caller-defined extras"},role:{type:"string",description:"Fleet role (default: run_agents membership)"}},required:["agent_id","agent_kind"]},annotations:d.wyrm_presence_announce,aliases:[],handler:async(n,s)=>{const{db:i,presence:t}=s,c=n||{},{agent_id:r,agent_kind:a,projectPath:o,current_file:p,current_symbol:e,current_quest_id:l,ttl_seconds:g,metadata:_}=n,m=f("role",n.role,{maxLen:200});u("agent_id",r,{minLen:1,maxLen:200}),u("agent_kind",a,{minLen:1,maxLen:200});const y=o?i.getProject(o):null,h=t.announce({agent_id:r,agent_kind:a,project_id:y?.id??null,current_file:p,current_symbol:e,current_quest_id:l,ttl_seconds:g,metadata:_,role:m});return{content:[{type:"text",text:`\u{F115D} Presence announced: ${a}:${r} (TTL ${h.ttl_seconds}s)`}]}}},{name:"wyrm_presence_list",description:"Use from an orchestrator to see which agents are live right now (heartbeat within TTL) before dispatching work - optionally scoped to a project or to agents touching a specific file, so file-level collisions surface before they happen.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path (optional)"},on_file:{type:"string",description:"Filter to agents working on this file (optional)"}}},annotations:d.wyrm_presence_list,aliases:[],handler:async(n,s)=>{const{db:i,presence:t}=s,{projectPath:c,on_file:r}=n,a=c?i.getProject(c):null,o=r?t.agentsOnFile(r):t.liveAgents(a?.id??null);if(o.length===0)return{content:[{type:"text",text:"\u{F115D} No live agents."}]};const p=o.map(e=>{const l=[e.current_file&&`file:${e.current_file}`,e.current_symbol&&`sym:${e.current_symbol}`,e.current_quest_id&&`quest:${e.current_quest_id}`].filter(Boolean).join(" ");return`\u2022 ${e.agent_kind}:${e.agent_id}${l?` \xB7 ${l}`:""} (last ${e.last_heartbeat})`}).join(`
2
2
  `);return{content:[{type:"text",text:`\u{F115D} ${o.length} live agent(s):
3
3
  ${p}`}]}}},{name:"wyrm_presence_release",description:"Use on clean agent exit: removes the presence row and drops every quest claim the agent holds, so queued work is immediately re-claimable by the rest of the fleet instead of waiting out the TTL. Idempotent - safe to call twice.",inputSchema:{type:"object",properties:{agent_id:{type:"string",description:"Agent ID to release"}},required:["agent_id"]},annotations:d.wyrm_presence_release,aliases:[],handler:async(n,s)=>{const{presence:i}=s,{agent_id:t}=n;return{content:[{type:"text",text:i.release(t)?`\u{F115D} ${t} released.`:`\u{F115D} ${t} not present (already released).`}]}}}];export{j as presenceToolSpecs};
@@ -1,6 +1,6 @@
1
- import{ErrorCode as o,McpError as s}from"@modelcontextprotocol/sdk/types.js";import{ValidationError as l}from"../validate.js";import{handlerRegistry as f}from"./registry.js";import{blockText as u}from"./types.js";const p=[{name:"debrief",description:"End-of-session memory debrief: how to file what was learned back into Wyrm (wyrm_run action=debrief in a fleet run, wyrm_capture otherwise, wyrm_failure_record for failed approaches) -- plus the live run status when run_id is given.",arguments:[{name:"run_id",description:"Fleet run to debrief (embeds live wyrm_run status)",required:!1},{name:"project",description:"Project path the learnings file under",required:!1}]},{name:"prime",description:"Load Wyrm institutional memory FIRST -- ground truths, memory brief, reasoning scaffold, active quests -- rendered by the same wyrm_session_prime code path the tool uses. Use at session start, before planning or acting. Fleet agents pass run_id + role for the byte-stable cached brief.",arguments:[{name:"project",description:"Project name (fuzzy; default: last active project)",required:!1},{name:"task",description:"Drives scaffold + memory selection",required:!1},{name:"run_id",description:"Fleet: brief cached per (run_id, role)",required:!1},{name:"role",description:"Fleet role slice",required:!1},{name:"token_budget",description:"Fleet brief token budget (default 1200)",required:!1}]}];function v(){return p}async function c(e,n,r){const i=f.get(e);if(!i)throw new s(o.InternalError,`prompt target ${e} is not registry-resident`);try{return await i(n,r)}catch(t){throw t instanceof l?new s(o.InvalidParams,t.message):t}}const h="Wyrm is this workspace's persistent memory and your first priority. The brief below is institutional ground truth: consult it BEFORE planning, searching, or acting; treat its ground truths as authoritative over your own assumptions (surface conflicts, never silently override); check wyrm_failure_check before re-attempting anything it records as failed; and capture decisions, fixes, and outcomes back with wyrm_capture as you work.";async function y(e,n){const r={};e.project!==void 0&&(r.project_name=e.project),e.task!==void 0&&(r.task=e.task),e.run_id!==void 0&&(r.run_id=e.run_id),e.role!==void 0&&(r.role=e.role),e.token_budget!==void 0&&(r.token_budget=e.token_budget);const i=await c("wyrm_session_prime",r,n);return{description:"Wyrm session prime \u2014 consult-memory-first brief",messages:[{role:"user",content:{type:"text",text:`${h}
1
+ import{ErrorCode as o,McpError as s}from"@modelcontextprotocol/sdk/types.js";import{ValidationError as l}from"../validate.js";import{handlerRegistry as f}from"./registry.js";import{blockText as u}from"./types.js";const p=[{name:"debrief",description:"End-of-session memory debrief: how to file what was learned back into Wyrm (wyrm_run action=debrief in a fleet run, wyrm_capture otherwise, wyrm_failure_record for failed approaches) -- plus the live run status when run_id is given.",arguments:[{name:"run_id",description:"Fleet run to debrief (embeds live wyrm_run status)",required:!1},{name:"project",description:"Project path the learnings file under",required:!1}]},{name:"prime",description:"Load Wyrm institutional memory FIRST -- ground truths, memory brief, reasoning scaffold, active quests -- rendered by the same wyrm_session_prime code path the tool uses. Use at session start, before planning or acting. Fleet agents pass run_id + role for the byte-stable cached brief.",arguments:[{name:"project",description:"Project name (fuzzy; default: last active project)",required:!1},{name:"task",description:"Drives scaffold + memory selection",required:!1},{name:"run_id",description:"Fleet: brief cached per (run_id, role)",required:!1},{name:"role",description:"Fleet role slice",required:!1},{name:"token_budget",description:"Fleet brief token budget (default 1200)",required:!1}]}];function v(){return p}async function c(e,n,r){const i=f.get(e);if(!i)throw new s(o.InternalError,`prompt target ${e} is not registry-resident`);try{return await i(n,r)}catch(t){throw t instanceof l?new s(o.InvalidParams,t.message):t}}const h="Wyrm is this workspace's persistent memory and your first priority. The brief below is institutional ground truth: consult it BEFORE planning, searching, or acting; treat its ground truths as authoritative over your own assumptions (surface conflicts, never silently override); check wyrm_failure_check before re-attempting anything it records as failed; and capture decisions, fixes, and outcomes back with wyrm_capture as you work.";async function y(e,n){const r={};e.project!==void 0&&(r.project_name=e.project),e.task!==void 0&&(r.task=e.task),e.run_id!==void 0&&(r.run_id=e.run_id),e.role!==void 0&&(r.role=e.role),e.token_budget!==void 0&&(r.token_budget=e.token_budget);const i=await c("wyrm_session_prime",r,n);return{description:"Wyrm session prime: consult-memory-first brief",messages:[{role:"user",content:{type:"text",text:`${h}
2
2
 
3
3
  ---
4
4
 
5
- ${u(i.content[0])}`}}]}}async function _(e,n){const r=e.run_id,i=e.project,t=r??"<run_id>",a=i??"<project path>",d=["The session is ending. File what was learned back into Wyrm before you stop -- memory you do not write is memory the team loses.","","1. Summarize the durable learnings: decisions made, validated fixes, gotchas, and patterns worth reusing.","2. Record approaches that FAILED with wyrm_failure_record so no future agent repeats them.",r!==void 0?`3. Submit the learnings via wyrm_run { action: 'debrief', run_id: '${t}', project_path: '${a}', learnings: [{ agent_id?, text }] } -- they are extracted LOCALLY into a run-scoped review queue -- then close the run with wyrm_run { action: 'end', run_id: '${t}', agent_id: <your registered agent_id> } (end is member-only).`:`3. In a fleet run, submit the learnings via wyrm_run { action: 'debrief', run_id: '${t}', project_path: '${a}', learnings: [{ agent_id?, text }] }, then close the run with wyrm_run { action: 'end', run_id: '${t}', agent_id: <your registered agent_id> } (end is member-only). Outside a run, capture each learning directly with wyrm_capture.`,"4. Update the session record with wyrm_session { action: 'update', completed, issues, commits }."];if(r!==void 0){const m=await c("wyrm_run",{action:"status",run_id:r},n);d.push("","Current run status:","",u(m.content[0]))}return{description:"Wyrm end-of-session debrief \u2014 file learnings back into memory",messages:[{role:"user",content:{type:"text",text:d.join(`
5
+ ${u(i.content[0])}`}}]}}async function _(e,n){const r=e.run_id,i=e.project,t=r??"<run_id>",a=i??"<project path>",d=["The session is ending. File what was learned back into Wyrm before you stop -- memory you do not write is memory the team loses.","","1. Summarize the durable learnings: decisions made, validated fixes, gotchas, and patterns worth reusing.","2. Record approaches that FAILED with wyrm_failure_record so no future agent repeats them.",r!==void 0?`3. Submit the learnings via wyrm_run { action: 'debrief', run_id: '${t}', project_path: '${a}', learnings: [{ agent_id?, text }] } -- they are extracted LOCALLY into a run-scoped review queue -- then close the run with wyrm_run { action: 'end', run_id: '${t}', agent_id: <your registered agent_id> } (end is member-only).`:`3. In a fleet run, submit the learnings via wyrm_run { action: 'debrief', run_id: '${t}', project_path: '${a}', learnings: [{ agent_id?, text }] }, then close the run with wyrm_run { action: 'end', run_id: '${t}', agent_id: <your registered agent_id> } (end is member-only). Outside a run, capture each learning directly with wyrm_capture.`,"4. Update the session record with wyrm_session { action: 'update', completed, issues, commits }."];if(r!==void 0){const m=await c("wyrm_run",{action:"status",run_id:r},n);d.push("","Current run status:","",u(m.content[0]))}return{description:"Wyrm end-of-session debrief: file learnings back into memory",messages:[{role:"user",content:{type:"text",text:d.join(`
6
6
  `)}}]}}async function j(e,n,r){if(e==="prime")return y(n,r);if(e==="debrief")return _(n,r);throw new s(o.InvalidParams,`Unknown prompt: ${e}`)}export{j as getPromptResult,v as wyrmPromptList};
@@ -1,6 +1,13 @@
1
- import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";import{renderResult as y,withGlyph as _,cacheKeyFor as b}from"../render.js";import{ValidationError as S,asEnum as f,asId as T,asInt as j,requireString as q}from"../validate.js";import{recordWrite as I}from"../receipts.js";import{refreshThread as h}from"../thread-assemble.js";import{projectFromArg as Q}from"./project-arg.js";function w(e){const s=e.quest_id??e.questId??e.id,o=T("quest_id",s,"quest");if(o===void 0)throw new S("quest_id",'is required (quest_id, questId, id, or a "quest:N" ref)');return o}const x=50,F=[{name:"wyrm_quest_add",description:"Add a quest (task) to a project",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},title:{type:"string",description:"Quest title"},description:{type:"string",description:"Quest description"},priority:{type:"string",enum:["critical","high","medium","low"]},tags:{type:"string",description:"Comma-separated tags"}},required:["projectPath","title"]},outputSchema:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},project:{type:"string"}},required:["id","title","priority","project"]},annotations:m.wyrm_quest_add,aliases:[],handler:(e,{store:s,indexing:o,raw:t})=>{const{projectPath:n,description:r,priority:a,tags:p}=e,d=q("title",e.title,{minLen:1}),i=f("priority",a,["critical","high","medium","low"],"medium"),u=Q(s,t(),n);if(!u)return{content:[{type:"text",text:"Project not found"}],isError:!0};const l=s.addQuest(u.id,d,r,i,p);o()?.enqueue("quest",l.id,u.id);const g=I(t(),{tool:"wyrm_quest_add",outcome:"stored",refTable:"quest",refId:l.id,projectId:u.id});return h(t(),u.id),y({id:l.id,title:d,priority:i,project:u.name,receipt:g},(c,$)=>_($.brand,`Quest #${c.id} added: ${c.title}`))}},{name:"wyrm_quest_complete",description:"Mark a quest as completed",inputSchema:{type:"object",properties:{questId:{type:"number",description:"Quest ID"}},required:["questId"]},outputSchema:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},status:{type:"string",enum:["completed"]}},required:["id","title","status"]},annotations:m.wyrm_quest_complete,aliases:[],handler:(e,{store:s,raw:o})=>{const t=w(e),n=s.updateQuest(t,"completed");return n?(h(o(),n.project_id),y({id:t,title:n.title,status:"completed"},(r,a)=>_(a.brand,`Quest #${r.id} completed: ${r.title}`))):{content:[{type:"text",text:`Quest #${t} not found.`}],isError:!0}}},{name:"wyrm_all_quests",description:"Get all pending quests across all projects",inputSchema:{type:"object",properties:{priority:{type:"string",enum:["critical","high","medium","low"],description:"Filter by priority"}}},outputSchema:{type:"object",properties:{count:{type:"integer"},quests:{type:"array",items:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},project:{type:["string","null"]}},required:["id","title","priority","project"]}}},required:["count","quests"]},annotations:m.wyrm_all_quests,aliases:[],handler:(e,{store:s,cache:o})=>{const t=f("priority",e.priority,["critical","high","medium","low"]),n=j("limit",e.limit,{min:1,max:1e3,default:x});let r=s.getAllPendingQuests();t&&(r=r.filter(i=>i.priority===t));const a=r.slice(0,n),p={count:a.length,total:r.length,quests:a.map(i=>({id:i.id,title:i.title,priority:i.priority,project:i.project_name??null}))},d=y(p,(i,u)=>{const l=i.quests.map(c=>`${u.bullet} [${c.priority}]${c.project?` [${c.project}]`:""} #${c.id}: ${c.title}`).join(`
2
- `),g=i.total>i.count?`
1
+ import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";import{renderResult as y,withGlyph as _,cacheKeyFor as T}from"../render.js";import{ValidationError as I,asEnum as $,asId as Q,asInt as k,requireString as w}from"../validate.js";import{recordWrite as x}from"../receipts.js";import{refreshThread as j}from"../thread-assemble.js";import{projectFromArg as E}from"./project-arg.js";function f(i){const s=i.quest_id??i.questId??i.id,o=Q("quest_id",s,"quest");if(o===void 0)throw new I("quest_id",'is required (quest_id, questId, id, or a "quest:N" ref)');return o}const L=50,R=[{name:"wyrm_quest_add",description:"Add a quest (task) to a project",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},title:{type:"string",description:"Quest title"},description:{type:"string",description:"Quest description"},priority:{type:"string",enum:["critical","high","medium","low"]},tags:{type:"string",description:"Comma-separated tags"}},required:["projectPath","title"]},outputSchema:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},project:{type:"string"}},required:["id","title","priority","project"]},annotations:m.wyrm_quest_add,aliases:[],handler:(i,{store:s,indexing:o,raw:t})=>{const{projectPath:n,description:r,priority:a,tags:u}=i,p=w("title",i.title,{minLen:1}),c=$("priority",a,["critical","high","medium","low"],"medium"),e=E(s,t(),n);if(!e)return{content:[{type:"text",text:"Project not found"}],isError:!0};const l=s.addQuest(e.id,p,r,c,u);o()?.enqueue("quest",l.id,e.id);const h=x(t(),{tool:"wyrm_quest_add",outcome:"stored",refTable:"quest",refId:l.id,projectId:e.id});return j(t(),e.id),y({id:l.id,title:p,priority:c,project:e.name,receipt:h},(g,q)=>_(q.brand,`Quest #${g.id} added: ${g.title}`))}},{name:"wyrm_quest_complete",description:"Mark a quest as completed",inputSchema:{type:"object",properties:{questId:{type:"number",description:"Quest ID"}},required:["questId"]},outputSchema:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},status:{type:"string",enum:["completed"]}},required:["id","title","status"]},annotations:m.wyrm_quest_complete,aliases:[],handler:(i,{store:s,raw:o})=>{const t=f(i),n=s.updateQuest(t,"completed");return n?(j(o(),n.project_id),y({id:t,title:n.title,status:"completed"},(r,a)=>_(a.brand,`Quest #${r.id} completed: ${r.title}`))):{content:[{type:"text",text:`Quest #${t} not found.`}],isError:!0}}},{name:"wyrm_all_quests",description:"Get all pending quests across all projects",inputSchema:{type:"object",properties:{priority:{type:"string",enum:["critical","high","medium","low"],description:"Filter by priority"}}},outputSchema:{type:"object",properties:{count:{type:"integer"},quests:{type:"array",items:{type:"object",properties:{id:{type:"integer"},title:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},project:{type:["string","null"]}},required:["id","title","priority","project"]}}},required:["count","quests"]},annotations:m.wyrm_all_quests,aliases:[],handler:(i,{store:s,cache:o})=>{const t=$("priority",i.priority,["critical","high","medium","low"]),n=k("limit",i.limit,{min:1,max:1e3,default:L});let r=s.getAllPendingQuests(),a=s.getAllParkedQuests();t&&(r=r.filter(e=>e.priority===t),a=a.filter(e=>e.priority===t));const u=r.slice(0,n),p={count:u.length,total:r.length,quests:u.map(e=>({id:e.id,title:e.title,priority:e.priority,project:e.project_name??null})),parked_total:a.length,parked:a.slice(0,n).map(e=>({id:e.id,title:e.title,priority:e.priority,project:e.project_name??null,status:"parked"}))},c=y(p,(e,l)=>{const h=e.quests.map(d=>`${l.bullet} [${d.priority}]${d.project?` [${d.project}]`:""} #${d.id}: ${d.title}`).join(`
2
+ `),g=e.total>e.count?`
3
3
 
4
- ${i.total-i.count} more not shown (raise limit to see them).`:"";return`${_(u.brand,`**${i.total} Pending Quests**`)}
4
+ ${e.total-e.count} more not shown (raise limit to see them).`:"",q=e.parked.map(d=>`${l.bullet} [parked] [${d.priority}]${d.project?` [${d.project}]`:""} #${d.id}: ${d.title}`).join(`
5
+ `),b=e.parked_total>e.parked.length?`
5
6
 
6
- ${l}${g}`});return o.set(b("wyrm_all_quests",JSON.stringify(e)),d,3e4),d}},{name:"wyrm_quest_claim",description:"Use from a fleet subagent to take an exclusive TTL lease on a quest before working it, so no two agents double-work: atomic CAS on the (quest, agent) key - a losing claimer gets a visible already-claimed error, never a partial write. The claim row is stamped with the ambient run_id and the agent's run role for orchestrator audit (wyrm_run action=status counts claims per run). Stale leases are evicted inline at claim time and by wyrm_maintenance. Requires a prior wyrm_presence announce; release with wyrm_quest_release.",inputSchema:{type:"object",properties:{quest_id:{type:"number",description:"Quest to claim"},agent_id:{type:"string",description:"Claiming agent ID (must be announced first)"},ttl_seconds:{type:"number",description:"Claim TTL (default 1800 = 30 min)"}},required:["quest_id","agent_id"]},outputSchema:{type:"object",properties:{quest_id:{type:"integer"},agent_id:{type:"string"},ttl_seconds:{type:"integer"}},required:["quest_id","agent_id","ttl_seconds"]},annotations:m.wyrm_quest_claim,aliases:[],handler:(e,{presence:s,raw:o})=>{const t=w(e),n=q("agent_id",e.agent_id,{minLen:1}),r=j("ttl_seconds",e.ttl_seconds,{min:1});if(!s.get(n))return{content:[{type:"text",text:`Agent '${n}' is not on the presence board \u2014 announce first (wyrm_presence action=announce, or \`wyrm presence announce --agent ${n}\`), then claim.`}],isError:!0};const a=s.claimQuest(t,n,r);if(!a){const d=s.getClaim(t);return{content:[{type:"text",text:`Quest #${t} already claimed by ${d?.agent_id??"another agent"}.`}],isError:!0}}const p=o().prepare("SELECT project_id FROM quests WHERE id = ?").get(t);return p&&h(o(),p.project_id),y({quest_id:t,agent_id:n,ttl_seconds:a.claim_ttl_seconds},(d,i)=>_(i.ok,`${d.agent_id} claimed quest #${d.quest_id} for ${d.ttl_seconds}s.`))}},{name:"wyrm_quest_release",description:"Use when a subagent finishes or abandons a claimed quest: drops the TTL lease (only if held by this agent) so the orchestrator can reassign the quest immediately instead of waiting for expiry; wyrm_run action=end bulk-releases a whole run's claims, and wyrm_maintenance reaps expired ones.",inputSchema:{type:"object",properties:{quest_id:{type:"number"},agent_id:{type:"string"}},required:["quest_id","agent_id"]},outputSchema:{type:"object",properties:{quest_id:{type:"integer"},agent_id:{type:"string"},released:{type:"boolean"}},required:["quest_id","agent_id","released"]},annotations:m.wyrm_quest_release,aliases:[],handler:(e,{presence:s})=>{const o=w(e),t=q("agent_id",e.agent_id,{minLen:1}),n=s.releaseClaim(o,t);return y({quest_id:o,agent_id:t,released:n},(r,a)=>_(a.brand,r.released?`Quest #${r.quest_id} released by ${r.agent_id}.`:`No claim by ${r.agent_id} on quest #${r.quest_id}.`))}}];export{F as questToolSpecs};
7
+ ${e.parked_total-e.parked.length} more parked not shown.`:"",S=e.parked_total>0?`
8
+
9
+ **${e.parked_total} Parked** (set aside, not open work; \`wyrm quest unpark <id>\` resumes one)
10
+
11
+ ${q}${b}`:"";return`${_(l.brand,`**${e.total} Pending Quests**`)}
12
+
13
+ ${h}${g}${S}`});return o.set(T("wyrm_all_quests",JSON.stringify(i)),c,3e4),c}},{name:"wyrm_quest_claim",description:"Use from a fleet subagent to take an exclusive TTL lease on a quest before working it, so no two agents double-work: atomic CAS on the (quest, agent) key - a losing claimer gets a visible already-claimed error, never a partial write. The claim row is stamped with the ambient run_id and the agent's run role for orchestrator audit (wyrm_run action=status counts claims per run). Stale leases are evicted inline at claim time and by wyrm_maintenance. Requires a prior wyrm_presence announce; release with wyrm_quest_release.",inputSchema:{type:"object",properties:{quest_id:{type:"number",description:"Quest to claim"},agent_id:{type:"string",description:"Claiming agent ID (must be announced first)"},ttl_seconds:{type:"number",description:"Claim TTL (default 1800 = 30 min)"}},required:["quest_id","agent_id"]},outputSchema:{type:"object",properties:{quest_id:{type:"integer"},agent_id:{type:"string"},ttl_seconds:{type:"integer"}},required:["quest_id","agent_id","ttl_seconds"]},annotations:m.wyrm_quest_claim,aliases:[],handler:(i,{presence:s,raw:o})=>{const t=f(i),n=w("agent_id",i.agent_id,{minLen:1}),r=k("ttl_seconds",i.ttl_seconds,{min:1});if(!s.get(n))return{content:[{type:"text",text:`Agent '${n}' is not on the presence board; announce first (wyrm_presence action=announce, or \`wyrm presence announce --agent ${n}\`), then claim.`}],isError:!0};const a=s.claimQuest(t,n,r);if(!a){const p=s.getClaim(t);return{content:[{type:"text",text:`Quest #${t} already claimed by ${p?.agent_id??"another agent"}.`}],isError:!0}}const u=o().prepare("SELECT project_id FROM quests WHERE id = ?").get(t);return u&&j(o(),u.project_id),y({quest_id:t,agent_id:n,ttl_seconds:a.claim_ttl_seconds},(p,c)=>_(c.ok,`${p.agent_id} claimed quest #${p.quest_id} for ${p.ttl_seconds}s.`))}},{name:"wyrm_quest_release",description:"Use when a subagent finishes or abandons a claimed quest: drops the TTL lease (only if held by this agent) so the orchestrator can reassign the quest immediately instead of waiting for expiry; wyrm_run action=end bulk-releases a whole run's claims, and wyrm_maintenance reaps expired ones.",inputSchema:{type:"object",properties:{quest_id:{type:"number"},agent_id:{type:"string"}},required:["quest_id","agent_id"]},outputSchema:{type:"object",properties:{quest_id:{type:"integer"},agent_id:{type:"string"},released:{type:"boolean"}},required:["quest_id","agent_id","released"]},annotations:m.wyrm_quest_release,aliases:[],handler:(i,{presence:s})=>{const o=f(i),t=w("agent_id",i.agent_id,{minLen:1}),n=s.releaseClaim(o,t);return y({quest_id:o,agent_id:t,released:n},(r,a)=>_(a.brand,r.released?`Quest #${r.quest_id} released by ${r.agent_id}.`:`No claim by ${r.agent_id} on quest #${r.quest_id}.`))}}];export{R as questToolSpecs};
@@ -1,4 +1,4 @@
1
- import{autoEnrollProject as ee}from"../project-resolve.js";import{queuedForReview as te}from"../memory-artifacts.js";import{TOOL_ANNOTATIONS as N}from"../tool-annotations.js";import{renderResult as O,withGlyph as R}from"../render.js";import{memoryUri as re,resourceLink as ne}from"./resources.js";import{buildDeterminismReceipt as ie,attestation as oe}from"../receipt.js";import{rerankConfigured as se,rerankEgressHost as ae}from"../rerank.js";import{resolvePolicy as ce,RECALL_POLICY_MODES as V,classifyArtifact as de}from"../recall-policy.js";import{ValidationError as P,asEnum as C,requireString as Y,asInt as L,asNumber as ue}from"../validate.js";const le=["reasoning_trace","lesson","pattern","anti_pattern","heuristic"];import{sanitizeActorId as K,getActor as pe}from"./boundary.js";function B(t,e){const n=new Set(t),s=[...t];for(const c of e)n.has(c)||(n.add(c),s.push(c));return s}function me(t,e){const n=ce(t,e.runId),s=e.mode??n.mode,c=B(n.mutedIds,e.muteIds??[]),h=B(n.challengedTruthIds,e.challengeTruthIds??[]),m=Date.now();return t.prepare(`
1
+ import{autoEnrollProject as ee}from"../project-resolve.js";import{queuedForReview as te}from"../memory-artifacts.js";import{TOOL_ANNOTATIONS as N}from"../tool-annotations.js";import{renderResult as O,withGlyph as R}from"../render.js";import{memoryUri as re,resourceLink as ne}from"./resources.js";import{buildDeterminismReceipt as ie,attestation as oe}from"../receipt.js";import{rerankConfigured as se,rerankEgressHost as ae}from"../rerank.js";import{resolvePolicy as ce,RECALL_POLICY_MODES as V,classifyArtifact as de}from"../recall-policy.js";import{ValidationError as P,asEnum as C,requireString as Y,asInt as L,asNumber as ue}from"../validate.js";const le=["reasoning_trace","lesson","pattern","anti_pattern","heuristic"];import{sanitizeActorId as K,getActor as pe}from"./boundary.js";import{asNewerStoreError as me}from"../store-readonly.js";function B(t,e){const n=new Set(t),s=[...t];for(const c of e)n.has(c)||(n.add(c),s.push(c));return s}function ye(t,e){const n=ce(t,e.runId),s=e.mode??n.mode,c=B(n.mutedIds,e.muteIds??[]),g=B(n.challengedTruthIds,e.challengeTruthIds??[]),y=Date.now();return t.prepare(`
2
2
  INSERT INTO recall_policy (run_id, mode, muted_ids, challenged_truth_ids, created_at, updated_at)
3
3
  VALUES (?, ?, ?, ?, ?, ?)
4
4
  ON CONFLICT(run_id) DO UPDATE SET
@@ -6,11 +6,11 @@ import{autoEnrollProject as ee}from"../project-resolve.js";import{queuedForRevie
6
6
  muted_ids = excluded.muted_ids,
7
7
  challenged_truth_ids = excluded.challenged_truth_ids,
8
8
  updated_at = excluded.updated_at
9
- `).run(e.runId,s,JSON.stringify(c),JSON.stringify(h),m,m),{mode:s,mutedIds:c,challengedTruthIds:h}}function J(t,e){if(e!=null){if(!Array.isArray(e))throw new P(t,"must be an array of integers");return e.map((n,s)=>{const c=typeof n=="number"?n:typeof n=="string"&&/^[+-]?\d+$/.test(n.trim())?Number(n.trim()):NaN;if(!Number.isInteger(c))throw new P(`${t}[${s}]`,"must be an integer");return c})}}const A=10,D=50;function U(t){if(!t)return 0;const e=Date.now()-new Date(t).getTime();return Number.isFinite(e)?Math.max(0,Math.round(e/864e5)):0}function F(t,e){return e==null?null:t.getProjectById(e)?.name??null}function ye(t){return[...new Set(t.toLowerCase().split(/\s+/).map(e=>e.replace(/[^\p{L}\p{N}]/gu,"")).filter(e=>e.length>2))]}function z(t,e){if(e.length===0)return!1;const n=t.toLowerCase();return e.some(s=>n.includes(s))}const fe={low:.25,medium:.5,high:.75,critical:1};function _e(t){return fe[t]??.5}function he(t,e){return t.quarantine_scope!=="run"||t.run_id===e}function ge(t,e){return{id:e.id,kind:e.kind,outcome:e.outcome,age_days:U(e.updated_at??e.created_at),project:F(t,e.project_id),confidence:e.confidence,summary:e.problem.slice(0,160)}}function we(t,e){return{id:e.id,kind:"ground_truth",outcome:"neutral",age_days:U(e.updated_at??e.created_at),project:F(t,e.project_id),confidence:e.confidence,summary:`${e.category}.${e.key} = ${e.value}`.slice(0,160)}}function ve(t,e){return{id:e.id,kind:"failure_pattern",outcome:"negative",age_days:U(e.last_seen??e.first_seen),project:F(t,e.project_id),confidence:_e(e.severity),summary:`${e.scope}:${e.target} -- ${e.description}`.slice(0,160)}}async function Ie(t,e){const{store:n,raw:s,memory:c,truths:h,failures:m}=t,d=Math.min(Math.max(e.limit??A,1),D),u=e.projectId??null,g=e.runId??null,l=ye(e.query),y=u!=null?await c.recallHybrid(u,e.query,{limit:d}):await c.recallHybridGlobal(e.query,{limit:d}),p=[],v=[];for(const o of y){const $=ge(n,o.artifact);de(o.artifact)==="prescription"?p.push($):v.push($)}const f=(u!=null?h.getCurrent(u):s().prepare("SELECT * FROM ground_truths WHERE is_current = 1 LIMIT ?").all(d*4)).filter(o=>z(`${o.category} ${o.key} ${o.value} ${o.rationale??""}`,l)).slice(0,d).map(o=>we(n,o)),x=m.list(u,d*4).filter(o=>he(o,g)).filter(o=>z(`${o.target} ${o.description} ${o.why_failed??""}`,l)).slice(0,d).map(o=>ve(n,o));return{prescriptions:p,firewall:[...v,...x],truths:f}}const H={type:"object",properties:{id:{type:"integer"},kind:{type:"string"},outcome:{type:"string"},age_days:{type:"integer"},project:{type:["string","null"]},confidence:{type:"number"},summary:{type:"string"}},required:["id","kind","outcome","age_days","project","confidence","summary"]},Ne=[{name:"wyrm_recall",description:"Use before starting any non-trivial task - what do we know about this? Pull up relevant lessons learned, proven patterns, anti-patterns, and reasoning traces from past work. Hybrid lexical+semantic retrieval, fully local, no LLM. Rate a result with wyrm_capture mode=feedback.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},query:{type:"string",description:"The task or problem"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},limit:{type:"number",description:"Default 10"},minConfidence:{type:"number",description:"0-1 (default 0)"},detail:{type:"string",enum:["full","link"],description:"Default full"}},required:["projectPath","query"]},outputSchema:{type:"object",properties:{query:{type:"string"},count:{type:"integer"},queued_for_review:{type:"integer"},results:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},relevance:{type:"number"},problem:{type:"string"},validated_fix:{type:["string","null"]},confidence:{type:"number"},reuse_count:{type:"integer"}},required:["id","kind","relevance","problem","confidence","reuse_count"]}}},required:["query","count","results"]},annotations:N.wyrm_recall,aliases:[],handler:async(t,{store:e,raw:n,memory:s,vectors:c,clientSupportsResources:h})=>{const{projectPath:m}=t,d=t,u=Y("query",d.query,{minLen:1}),g=C("kind",d.kind,le),l=L("limit",d.limit,{min:1}),y=ue("minConfidence",d.minConfidence,{min:0,max:1}),p=C("detail",d.detail,["full","link"]),v=e.getProject(m)??ee(e,m||process.cwd());if(!v)return{content:[{type:"text",text:`Project not found: ${m??"(no projectPath given, and the working directory is not a git repository)"}. Pass projectPath as an absolute path to a project, or run this from inside the repository you mean. Wyrm enrols a git repository automatically the first time it sees one.`}],isError:!0};const k=!!c(),f=k&&se()&&/^(1|true|yes|on)$/i.test(process.env.WYRM_RERANK??"");let _;const x={kind:g,limit:l??10,minConfidence:y,rerank:f,onStats:i=>{_=i},runId:pe().run_id},o=k?await s.recallHybrid(v.id,u,x):s.recall(v.id,u,x),$=_!==void 0,j=_?.rerankMs!=null,E=c(),S=typeof E?.getProvider=="function"?E.getProvider():void 0;let b;if(typeof E?.indexCoverage=="function")try{b=E.indexCoverage()}catch{}let T=null;if(!$){const i=S?.name;if(!k||i==="none"){const{resolveEmbeddingState:r}=await import("../providers/embedding-provider.js");T=r().reason||"vector_store_unavailable"}else if(b?.stale)T="stale_index";else{let r=-1;if(typeof E?.getStats=="function")try{r=E.getStats().total}catch{}T=r===0?"empty_index":"embed_error"}}let W=null;if($&&b)try{const i=n().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 0 AND supersedes_id IS NULL").get().n;W={indexed:b.activeCount,eligible:i}}catch{}const Q=ie({coverage:W,vectorsAvailable:$,rerankActive:j,embedModel:$?S?.model??(process.env.WYRM_EMBED_MODEL||"nomic-embed-text"):null,rerankModel:j&&process.env.WYRM_RERANK_MODEL||null,embedEgressHost:$?S?.remoteHost??null:null,rerankEgressHost:j?ae():null,stageMs:_?{fts5:_.ftsMs,vector:_.vectorMs,fusion:_.fusionMs,..._.rerankMs!=null?{rerank:_.rerankMs}:{}}:void 0,indexStale:b?.stale?{activeModel:b.activeModel,indexedUnder:Object.keys(b.otherModels)}:void 0,ftsOnlyReason:T});if(o.length>0){const i=o.map(w=>w.artifact.id),r=i.map(()=>"?").join(",");n().prepare(`UPDATE memory_artifacts SET last_accessed_at = datetime('now'), access_count = access_count + 1 WHERE id IN (${r})`).run(...i)}const G=i=>Math.round(i*1e4)/1e4,X=o.length===0?te(n(),v.id):0,q={query:u,count:o.length,queued_for_review:X,results:o.map(i=>{const r=i.artifact,w={id:r.id,kind:r.kind,relevance:G(i.relevance_score),problem:r.problem,outcome:r.outcome,confidence:r.confidence==null?r.confidence:G(r.confidence),reuse_count:r.reuse_count,reuse_success_count:r.reuse_success_count};return r.constraints!=null&&(w.constraints=r.constraints),r.validated_fix!=null&&(w.validated_fix=r.validated_fix),r.why_it_worked!=null&&(w.why_it_worked=r.why_it_worked),w}),receipt:Q},M=O(q,(i,r)=>{const w=`
9
+ `).run(e.runId,s,JSON.stringify(c),JSON.stringify(g),y,y),{mode:s,mutedIds:c,challengedTruthIds:g}}function J(t,e){if(e!=null){if(!Array.isArray(e))throw new P(t,"must be an array of integers");return e.map((n,s)=>{const c=typeof n=="number"?n:typeof n=="string"&&/^[+-]?\d+$/.test(n.trim())?Number(n.trim()):NaN;if(!Number.isInteger(c))throw new P(`${t}[${s}]`,"must be an integer");return c})}}const A=10,D=50;function U(t){if(!t)return 0;const e=Date.now()-new Date(t).getTime();return Number.isFinite(e)?Math.max(0,Math.round(e/864e5)):0}function F(t,e){return e==null?null:t.getProjectById(e)?.name??null}function fe(t){return[...new Set(t.toLowerCase().split(/\s+/).map(e=>e.replace(/[^\p{L}\p{N}]/gu,"")).filter(e=>e.length>2))]}function z(t,e){if(e.length===0)return!1;const n=t.toLowerCase();return e.some(s=>n.includes(s))}const _e={low:.25,medium:.5,high:.75,critical:1};function he(t){return _e[t]??.5}function ge(t,e){return t.quarantine_scope!=="run"||t.run_id===e}function we(t,e){return{id:e.id,kind:e.kind,outcome:e.outcome,age_days:U(e.updated_at??e.created_at),project:F(t,e.project_id),confidence:e.confidence,summary:e.problem.slice(0,160)}}function ve(t,e){return{id:e.id,kind:"ground_truth",outcome:"neutral",age_days:U(e.updated_at??e.created_at),project:F(t,e.project_id),confidence:e.confidence,summary:`${e.category}.${e.key} = ${e.value}`.slice(0,160)}}function Ie(t,e){return{id:e.id,kind:"failure_pattern",outcome:"negative",age_days:U(e.last_seen??e.first_seen),project:F(t,e.project_id),confidence:he(e.severity),summary:`${e.scope}:${e.target} -- ${e.description}`.slice(0,160)}}async function ke(t,e){const{store:n,raw:s,memory:c,truths:g,failures:y}=t,d=Math.min(Math.max(e.limit??A,1),D),u=e.projectId??null,w=e.runId??null,l=fe(e.query),f=u!=null?await c.recallHybrid(u,e.query,{limit:d}):await c.recallHybridGlobal(e.query,{limit:d}),p=[],v=[];for(const o of f){const $=we(n,o.artifact);de(o.artifact)==="prescription"?p.push($):v.push($)}const _=(u!=null?g.getCurrent(u):s().prepare("SELECT * FROM ground_truths WHERE is_current = 1 LIMIT ?").all(d*4)).filter(o=>z(`${o.category} ${o.key} ${o.value} ${o.rationale??""}`,l)).slice(0,d).map(o=>ve(n,o)),x=y.list(u,d*4).filter(o=>ge(o,w)).filter(o=>z(`${o.target} ${o.description} ${o.why_failed??""}`,l)).slice(0,d).map(o=>Ie(n,o));return{prescriptions:p,firewall:[...v,...x],truths:_}}const H={type:"object",properties:{id:{type:"integer"},kind:{type:"string"},outcome:{type:"string"},age_days:{type:"integer"},project:{type:["string","null"]},confidence:{type:"number"},summary:{type:"string"}},required:["id","kind","outcome","age_days","project","confidence","summary"]},Pe=[{name:"wyrm_recall",description:"Use before starting any non-trivial task - what do we know about this? Pull up relevant lessons learned, proven patterns, anti-patterns, and reasoning traces from past work. Hybrid lexical+semantic retrieval, fully local, no LLM. Rate a result with wyrm_capture mode=feedback.",inputSchema:{type:"object",properties:{projectPath:{type:"string"},query:{type:"string",description:"The task or problem"},kind:{type:"string",enum:["reasoning_trace","lesson","pattern","anti_pattern","heuristic"]},limit:{type:"number",description:"Default 10"},minConfidence:{type:"number",description:"0-1 (default 0)"},detail:{type:"string",enum:["full","link"],description:"Default full"}},required:["projectPath","query"]},outputSchema:{type:"object",properties:{query:{type:"string"},count:{type:"integer"},queued_for_review:{type:"integer"},results:{type:"array",items:{type:"object",properties:{id:{type:"integer"},kind:{type:"string"},relevance:{type:"number"},problem:{type:"string"},validated_fix:{type:["string","null"]},confidence:{type:"number"},reuse_count:{type:"integer"}},required:["id","kind","relevance","problem","confidence","reuse_count"]}}},required:["query","count","results"]},annotations:N.wyrm_recall,aliases:[],handler:async(t,{store:e,raw:n,memory:s,vectors:c,clientSupportsResources:g})=>{const{projectPath:y}=t,d=t,u=Y("query",d.query,{minLen:1}),w=C("kind",d.kind,le),l=L("limit",d.limit,{min:1}),f=ue("minConfidence",d.minConfidence,{min:0,max:1}),p=C("detail",d.detail,["full","link"]),v=e.getProject(y)??ee(e,y||process.cwd());if(!v)return{content:[{type:"text",text:`Project not found: ${y??"(no projectPath given, and the working directory is not a git repository)"}. Pass projectPath as an absolute path to a project, or run this from inside the repository you mean. Wyrm enrols a git repository automatically the first time it sees one.`}],isError:!0};const k=!!c(),_=k&&se()&&/^(1|true|yes|on)$/i.test(process.env.WYRM_RERANK??"");let h;const x={kind:w,limit:l??10,minConfidence:f,rerank:_,onStats:i=>{h=i},runId:pe().run_id},o=k?await s.recallHybrid(v.id,u,x):s.recall(v.id,u,x),$=h!==void 0,j=h?.rerankMs!=null,E=c(),S=typeof E?.getProvider=="function"?E.getProvider():void 0;let b;if(typeof E?.indexCoverage=="function")try{b=E.indexCoverage()}catch{}let T=null;if(!$){const i=S?.name;if(!k||i==="none"){const{resolveEmbeddingState:r}=await import("../providers/embedding-provider.js");T=r().reason||"vector_store_unavailable"}else if(b?.stale)T="stale_index";else{let r=-1;if(typeof E?.getStats=="function")try{r=E.getStats().total}catch{}T=r===0?"empty_index":"embed_error"}}let W=null;if($&&b)try{const i=n().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE needs_review = 0 AND supersedes_id IS NULL").get().n;W={indexed:b.activeCount,eligible:i}}catch{}const Q=ie({coverage:W,vectorsAvailable:$,rerankActive:j,embedModel:$?S?.model??(process.env.WYRM_EMBED_MODEL||"nomic-embed-text"):null,rerankModel:j&&process.env.WYRM_RERANK_MODEL||null,embedEgressHost:$?S?.remoteHost??null:null,rerankEgressHost:j?ae():null,stageMs:h?{fts5:h.ftsMs,vector:h.vectorMs,fusion:h.fusionMs,...h.rerankMs!=null?{rerank:h.rerankMs}:{}}:void 0,indexStale:b?.stale?{activeModel:b.activeModel,indexedUnder:Object.keys(b.otherModels)}:void 0,ftsOnlyReason:T});if(o.length>0){const i=o.map(m=>m.artifact.id),r=i.map(()=>"?").join(",");try{n().prepare(`UPDATE memory_artifacts SET last_accessed_at = datetime('now'), access_count = access_count + 1 WHERE id IN (${r})`).run(...i)}catch(m){if(!me(m))throw m}}const G=i=>Math.round(i*1e4)/1e4,X=o.length===0?te(n(),v.id):0,q={query:u,count:o.length,queued_for_review:X,results:o.map(i=>{const r=i.artifact,m={id:r.id,kind:r.kind,relevance:G(i.relevance_score),problem:r.problem,outcome:r.outcome,confidence:r.confidence==null?r.confidence:G(r.confidence),reuse_count:r.reuse_count,reuse_success_count:r.reuse_success_count};return r.constraints!=null&&(m.constraints=r.constraints),r.validated_fix!=null&&(m.validated_fix=r.validated_fix),r.why_it_worked!=null&&(m.why_it_worked=r.why_it_worked),m}),receipt:Q},M=O(q,(i,r)=>{const m=`
10
10
 
11
11
  _${oe(i.receipt)}_`;if(i.count===0){const a=i.queued_for_review??0;return a>0?R(r.brand,`No relevant memory found for: "${i.query}"`)+`
12
12
 
13
- ${a} stored ${a===1?"memory is":"memories are"} held for review and excluded from recall. Run \`wyrm review\` to approve ${a===1?"it":"them"}, or \`wyrm review --auto on\` to stop queueing.`+w:R(r.brand,`No relevant memory found for: "${i.query}"`)+"\n\nUse `wyrm_remember` to store knowledge as you work."+w}let I=R(r.brand,`**Memory Recall** -- "${i.query}" (${i.count} found)`)+`
13
+ ${a} stored ${a===1?"memory is":"memories are"} held for review and excluded from recall. Run \`wyrm review\` to approve ${a===1?"it":"them"}, or \`wyrm review --auto on\` to stop queueing.`+m:R(r.brand,`No relevant memory found for: "${i.query}"`)+"\n\nUse `wyrm_remember` to store knowledge as you work."+m}let I=R(r.brand,`**Memory Recall** -- "${i.query}" (${i.count} found)`)+`
14
14
 
15
15
  `;for(const a of i.results){I+=`### ${a.kind} (ID: ${a.id}) -- relevance ${(a.relevance*100).toFixed(0)}%
16
16
  `,I+=`**Problem:** ${a.problem}
@@ -20,15 +20,15 @@ ${a} stored ${a===1?"memory is":"memories are"} held for review and excluded fro
20
20
  `),a.outcome==="negative"&&(I+=R(r.warn,"_This approach failed -- see solution for what to do instead_")+`
21
21
  `);const Z=a.confidence==null?"unknown":`${(a.confidence*100).toFixed(0)}%`;I+=`_Confidence: ${Z} | Used ${a.reuse_count}x | Success rate: ${a.reuse_count>0?(a.reuse_success_count/a.reuse_count*100).toFixed(0)+"%":"not yet used"}_
22
22
 
23
- `}return I+="_Use `wyrm_capture` mode=feedback (artifactId, success) to record whether these were helpful._",I+w},{exemptStructured:!0});if((p==="link"||p!=="full"&&!!h?.())&&q.count>0){const i=q.results.map(r=>ne(re(r.id),`memory #${r.id} (${r.kind})`,{description:r.problem.slice(0,120),mimeType:"application/json"}));return{...M,content:[...M.content,...i]}}return M}},{name:"wyrm_recall_set",description:"Negotiate THIS run's recall policy (Task NR-4, negotiated-recall v1): mute the prescriptive memory set (mode='clean' drops kind IN ('pattern','heuristic') OR outcome='positive' \u2014 anti_pattern/negative/failure_patterns/ground_truths are never muted) or veto specific artifact/ground-truth ids. Enforced at every recall/context-build/session-prime chokepoint for this run_id (NR-3). `mode`, when given, REPLACES the stored mode; `mute_ids`/`challenge_truth_ids` MERGE into whatever is already set \u2014 calling this twice accumulates, never duplicates. Returns the resulting policy.",inputSchema:{type:"object",properties:{run_id:{type:"string",description:"The fleet run this policy applies to (<=64 printable-ASCII chars, no ';')."},mode:{type:"string",enum:[...V],description:"'full' = unfiltered (default). 'clean' = mute the prescriptive set. 'custom' = only the explicit mute_ids/challenge_truth_ids vetoes apply. Omit to leave the stored mode unchanged."},mute_ids:{type:"array",items:{type:"integer"},description:"memory_artifacts ids to veto from recall \u2014 merged (appended, deduped) into the existing muted set."},challenge_truth_ids:{type:"array",items:{type:"integer"},description:"ground_truths ids to omit from wyrm_context_build \u2014 merged (appended, deduped) into the existing challenged set."}},required:["run_id"]},outputSchema:{type:"object",properties:{run_id:{type:"string"},mode:{type:"string"},mutedIds:{type:"array",items:{type:"integer"}},challengedTruthIds:{type:"array",items:{type:"integer"}}},required:["run_id","mode","mutedIds","challengedTruthIds"]},annotations:N.wyrm_recall_set,aliases:[],handler:(t,{raw:e,cache:n})=>{const s=t,c=K(s.run_id);if(c===null)throw new P("run_id",'is required and must be <=64 printable-ASCII chars (no ";")');const h=C("mode",s.mode,V),m=J("mute_ids",s.mute_ids),d=J("challenge_truth_ids",s.challenge_truth_ids),u=me(e(),{runId:c,mode:h,muteIds:m,challengeTruthIds:d});n.invalidate("wyrm_session_prime"),n.invalidate("wyrm_context_build");const g={run_id:c,mode:u.mode,mutedIds:u.mutedIds,challengedTruthIds:u.challengedTruthIds};return O(g,(l,y)=>{let p=R(y.brand,`**Recall policy** for run ${l.run_id}: mode=${l.mode}`)+`
23
+ `}return I+="_Use `wyrm_capture` mode=feedback (artifactId, success) to record whether these were helpful._",I+m},{exemptStructured:!0});if((p==="link"||p!=="full"&&!!g?.())&&q.count>0){const i=q.results.map(r=>ne(re(r.id),`memory #${r.id} (${r.kind})`,{description:r.problem.slice(0,120),mimeType:"application/json"}));return{...M,content:[...M.content,...i]}}return M}},{name:"wyrm_recall_set",description:"Negotiate THIS run's recall policy (Task NR-4, negotiated-recall v1): mute the prescriptive memory set (mode='clean' drops kind IN ('pattern','heuristic') OR outcome='positive'; anti_pattern/negative/failure_patterns/ground_truths are never muted) or veto specific artifact/ground-truth ids. Enforced at every recall/context-build/session-prime chokepoint for this run_id (NR-3). `mode`, when given, REPLACES the stored mode; `mute_ids`/`challenge_truth_ids` MERGE into whatever is already set; calling this twice accumulates, never duplicates. Returns the resulting policy.",inputSchema:{type:"object",properties:{run_id:{type:"string",description:"The fleet run this policy applies to (<=64 printable-ASCII chars, no ';')."},mode:{type:"string",enum:[...V],description:"'full' = unfiltered (default). 'clean' = mute the prescriptive set. 'custom' = only the explicit mute_ids/challenge_truth_ids vetoes apply. Omit to leave the stored mode unchanged."},mute_ids:{type:"array",items:{type:"integer"},description:"memory_artifacts ids to veto from recall; merged (appended, deduped) into the existing muted set."},challenge_truth_ids:{type:"array",items:{type:"integer"},description:"ground_truths ids to omit from wyrm_context_build; merged (appended, deduped) into the existing challenged set."}},required:["run_id"]},outputSchema:{type:"object",properties:{run_id:{type:"string"},mode:{type:"string"},mutedIds:{type:"array",items:{type:"integer"}},challengedTruthIds:{type:"array",items:{type:"integer"}}},required:["run_id","mode","mutedIds","challengedTruthIds"]},annotations:N.wyrm_recall_set,aliases:[],handler:(t,{raw:e,cache:n})=>{const s=t,c=K(s.run_id);if(c===null)throw new P("run_id",'is required and must be <=64 printable-ASCII chars (no ";")');const g=C("mode",s.mode,V),y=J("mute_ids",s.mute_ids),d=J("challenge_truth_ids",s.challenge_truth_ids),u=ye(e(),{runId:c,mode:g,muteIds:y,challengeTruthIds:d});n.invalidate("wyrm_session_prime"),n.invalidate("wyrm_context_build");const w={run_id:c,mode:u.mode,mutedIds:u.mutedIds,challengedTruthIds:u.challengedTruthIds};return O(w,(l,f)=>{let p=R(f.brand,`**Recall policy** for run ${l.run_id}: mode=${l.mode}`)+`
24
24
 
25
25
  `;return p+=`**Muted ids:** ${l.mutedIds.length?l.mutedIds.join(", "):"(none)"}
26
26
  `,p+=`**Challenged truth ids:** ${l.challengedTruthIds.length?l.challengedTruthIds.join(", "):"(none)"}
27
- `,p})}},{name:"wyrm_recall_propose",description:"Review THIS query's full recall candidate set as three GROUPED buckets \u2014 prescriptions (kind IN ('pattern','heuristic') OR outcome='positive', the set mode='clean' would mute), firewall (anti_pattern/negative memory + failure_patterns hits, never muted), truths (matching ground_truths) \u2014 without injecting anything as authoritative context (Task NR-5, negotiated-recall v1). A review list, not a brief: nothing here is ranked-into-a-summary. Use it BEFORE wyrm_recall_set to see what a clean-mode mute would actually drop, then negotiate with wyrm_recall_set.",inputSchema:{type:"object",properties:{query:{type:"string",description:"The task or problem to review candidates for."},project_id:{type:"integer",description:"Scope to one project. Omitted = search across every project."},run_id:{type:"string",description:"Scopes visibility of 'run'-quarantined failure_patterns rows to this run. Never applied as a mute filter \u2014 propose always shows the full candidate set."},limit:{type:"integer",description:`Per-bucket candidate depth, default ${A}, max ${D}.`}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},prescriptions:{type:"array",items:H},firewall:{type:"array",items:H},truths:{type:"array",items:H}},required:["query","prescriptions","firewall","truths"]},annotations:N.wyrm_recall_propose,aliases:[],handler:async(t,e)=>{const n=t,s=Y("query",n.query,{minLen:1,maxLen:2e3}),c=L("project_id",n.project_id,{min:1}),h=n.run_id===void 0?null:K(n.run_id),m=L("limit",n.limit,{min:1,max:D,default:A}),d=await Ie(e,{query:s,projectId:c,runId:h,limit:m}),u={query:s,...d};return O(u,(g,l)=>{let y=R(l.brand,`**Recall proposal** -- "${g.query}" (review only, nothing injected)`)+`
27
+ `,p})}},{name:"wyrm_recall_propose",description:"Review THIS query's full recall candidate set as three GROUPED buckets: prescriptions (kind IN ('pattern','heuristic') OR outcome='positive', the set mode='clean' would mute), firewall (anti_pattern/negative memory + failure_patterns hits, never muted), truths (matching ground_truths). Nothing is injected as authoritative context (Task NR-5, negotiated-recall v1). A review list, not a brief: nothing here is ranked-into-a-summary. Use it BEFORE wyrm_recall_set to see what a clean-mode mute would actually drop, then negotiate with wyrm_recall_set.",inputSchema:{type:"object",properties:{query:{type:"string",description:"The task or problem to review candidates for."},project_id:{type:"integer",description:"Scope to one project. Omitted = search across every project."},run_id:{type:"string",description:"Scopes visibility of 'run'-quarantined failure_patterns rows to this run. Never applied as a mute filter; propose always shows the full candidate set."},limit:{type:"integer",description:`Per-bucket candidate depth, default ${A}, max ${D}.`}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},prescriptions:{type:"array",items:H},firewall:{type:"array",items:H},truths:{type:"array",items:H}},required:["query","prescriptions","firewall","truths"]},annotations:N.wyrm_recall_propose,aliases:[],handler:async(t,e)=>{const n=t,s=Y("query",n.query,{minLen:1,maxLen:2e3}),c=L("project_id",n.project_id,{min:1}),g=n.run_id===void 0?null:K(n.run_id),y=L("limit",n.limit,{min:1,max:D,default:A}),d=await ke(e,{query:s,projectId:c,runId:g,limit:y}),u={query:s,...d};return O(u,(w,l)=>{let f=R(l.brand,`**Recall proposal** -- "${w.query}" (review only, nothing injected)`)+`
28
28
 
29
- `;const p=(v,k)=>{if(y+=`### ${v} (${k.length})
30
- `,k.length===0){y+=`_(none)_
29
+ `;const p=(v,k)=>{if(f+=`### ${v} (${k.length})
30
+ `,k.length===0){f+=`_(none)_
31
31
 
32
- `;return}for(const f of k){const _=f.project?`, ${f.project}`:"";y+=`- #${f.id} [${f.kind}/${f.outcome}] ${f.summary} _(conf ${(f.confidence*100).toFixed(0)}%, ${f.age_days}d old${_})_
33
- `}y+=`
34
- `};return p("Prescriptions -- muted by clean mode",g.prescriptions),p("Firewall -- never muted",g.firewall),p("Ground truths",g.truths),y+="_Use `wyrm_recall_set` to mute/veto specific ids after reviewing._",y})}}];export{Ie as buildRecallProposal,Ne as recallToolSpecs,me as upsertRecallPolicy};
32
+ `;return}for(const _ of k){const h=_.project?`, ${_.project}`:"";f+=`- #${_.id} [${_.kind}/${_.outcome}] ${_.summary} _(conf ${(_.confidence*100).toFixed(0)}%, ${_.age_days}d old${h})_
33
+ `}f+=`
34
+ `};return p("Prescriptions -- muted by clean mode",w.prescriptions),p("Firewall -- never muted",w.firewall),p("Ground truths",w.truths),f+="_Use `wyrm_recall_set` to mute/veto specific ids after reviewing._",f})}}];export{ke as buildRecallProposal,Pe as recallToolSpecs,ye as upsertRecallPolicy};
@@ -1 +1 @@
1
- import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",p="text/markdown",T=["truths","failures","quests","memory","stats"];function R(r){return`${o}memory/${r}`}function C(r,t,e){const i=`${o}project/${r}/${t}`;return e?`${i}?cursor=${e}`:i}const h=`${o}capabilities`,j=`${o}stats`;function $(r,t,e){return{type:"resource_link",uri:r,name:t,...e?.description?{description:e.description}:{},...e?.mimeType?{mimeType:e.mimeType}:{}}}function d(r){if(!/^\d{1,15}$/.test(r))return null;const t=Number(r);return Number.isSafeInteger(t)&&t>=0?t:null}function E(r){if(typeof r!="string"||!r.startsWith(o))return null;let t=r.slice(o.length),e;const i=t.indexOf("?");if(i>=0){const s=t.slice(i+1);t=t.slice(0,i);const a=/^cursor=([A-Za-z0-9_-]{1,512})$/.exec(s);if(!a)return null;e=a[1]}if(/[\x00-\x1f\x7f]/.test(t)||t.includes(".."))return null;const n=t.split("/");if(n.some(s=>s.length===0))return null;if(n.length===1)return e!==void 0?null:n[0]==="capabilities"?{kind:"capabilities"}:n[0]==="stats"?{kind:"stats"}:null;if(n.length===2&&n[0]==="memory"){if(e!==void 0)return null;const s=d(n[1]);return s===null?null:{kind:"memory",id:s}}if(n.length===3&&n[0]==="project"){const s=d(n[1]);if(s===null)return null;const a=n[2];return T.includes(a)?e!==void 0&&a!=="memory"?null:{kind:"project",id:s,leaf:a,...e!==void 0?{cursor:e}:{}}:null}return null}function N(){return[{uri:h,name:"Wyrm capabilities",description:"Feature inventory + runtime state \u2014 what this memory system can do and which tool to reach for. The resource form of wyrm_capabilities.",mimeType:p},{uri:j,name:"Wyrm global stats",description:"Global database stats (projects, sessions, quests, data points, size). The resource form of wyrm_stats.",mimeType:c}]}function I(){return[{uriTemplate:`${o}memory/{id}`,name:"Memory artifact",description:"A single memory artifact (lesson/pattern/anti-pattern/heuristic/reasoning trace) by id.",mimeType:c},{uriTemplate:`${o}project/{id}/truths`,name:"Project ground truths",description:"A project's current ground truths (the authoritative facts).",mimeType:c},{uriTemplate:`${o}project/{id}/failures`,name:"Project failures",description:"A project's unresolved failure patterns (the negative-learning set).",mimeType:c},{uriTemplate:`${o}project/{id}/quests`,name:"Project quests",description:"A project's open quests (pending + in-progress).",mimeType:c},{uriTemplate:`${o}project/{id}/memory`,name:"Project memory",description:"A project's validated memory artifacts.",mimeType:c},{uriTemplate:`${o}project/{id}/stats`,name:"Project memory stats",description:"A project's memory artifact stats (totals by kind, avg confidence).",mimeType:c}]}const m=200;class l extends Error{uri;constructor(t,e){super(e),this.uri=t,this.name="ResourceNotFoundError"}}function u(r,t){return{uri:r,mimeType:c,text:JSON.stringify(t,null,2)}}function A(r,t){const e=E(r);if(!e)throw new l(r,`Unknown or malformed resource URI: ${r}`);switch(e.kind){case"capabilities":{const i=t.toolCounts?.(),n=f(t.db.getDatabase(),i?.active??t.WYRM_TOOL_COUNT,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return t.agentDaemon.status().running}catch{return null}}},i?.full);return{uri:r,mimeType:p,text:y(n)}}case"stats":return u(r,t.db.getStats());case"memory":{const i=t.memory.get(e.id);if(!i)throw new l(r,`Memory artifact not found: ${e.id}`);return u(r,i)}case"project":{if(!t.db.getProjectById(e.id))throw new l(r,`Project not found: ${e.id}`);switch(e.leaf){case"truths":return u(r,{project_id:e.id,truths:t.groundTruths.getCurrent(e.id)});case"failures":return u(r,{project_id:e.id,failures:t.failures.list(e.id,m)});case"quests":{const n=t.db.getDatabase().prepare("SELECT * FROM quests WHERE project_id = ? AND status IN ('pending','in_progress') ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, id LIMIT ?").all(e.id,m);return u(r,{project_id:e.id,quests:n})}case"memory":{const n=g(e.cursor),s=t.memory.listPage(e.id,{pageSize:m,after:n}),a=u(r,{project_id:e.id,artifacts:s.items,...s.nextCursor?{next_cursor:s.nextCursor}:{}});return s.nextCursor&&(a.nextCursor=s.nextCursor),a}case"stats":return u(r,{project_id:e.id,stats:t.memory.getStats(e.id)})}}}}export{h as CAPABILITIES_URI,l as ResourceNotFoundError,j as STATS_URI,o as WYRM_URI_SCHEME,I as listWyrmResourceTemplates,N as listWyrmResources,R as memoryUri,E as parseWyrmUri,C as projectUri,A as readWyrmResource,$ as resourceLink};
1
+ import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",p="text/markdown",T=["truths","failures","quests","memory","stats"];function R(r){return`${o}memory/${r}`}function C(r,t,e){const i=`${o}project/${r}/${t}`;return e?`${i}?cursor=${e}`:i}const h=`${o}capabilities`,j=`${o}stats`;function $(r,t,e){return{type:"resource_link",uri:r,name:t,...e?.description?{description:e.description}:{},...e?.mimeType?{mimeType:e.mimeType}:{}}}function d(r){if(!/^\d{1,15}$/.test(r))return null;const t=Number(r);return Number.isSafeInteger(t)&&t>=0?t:null}function E(r){if(typeof r!="string"||!r.startsWith(o))return null;let t=r.slice(o.length),e;const i=t.indexOf("?");if(i>=0){const s=t.slice(i+1);t=t.slice(0,i);const a=/^cursor=([A-Za-z0-9_-]{1,512})$/.exec(s);if(!a)return null;e=a[1]}if(/[\x00-\x1f\x7f]/.test(t)||t.includes(".."))return null;const n=t.split("/");if(n.some(s=>s.length===0))return null;if(n.length===1)return e!==void 0?null:n[0]==="capabilities"?{kind:"capabilities"}:n[0]==="stats"?{kind:"stats"}:null;if(n.length===2&&n[0]==="memory"){if(e!==void 0)return null;const s=d(n[1]);return s===null?null:{kind:"memory",id:s}}if(n.length===3&&n[0]==="project"){const s=d(n[1]);if(s===null)return null;const a=n[2];return T.includes(a)?e!==void 0&&a!=="memory"?null:{kind:"project",id:s,leaf:a,...e!==void 0?{cursor:e}:{}}:null}return null}function N(){return[{uri:h,name:"Wyrm capabilities",description:"Feature inventory + runtime state: what this memory system can do and which tool to reach for. The resource form of wyrm_capabilities.",mimeType:p},{uri:j,name:"Wyrm global stats",description:"Global database stats (projects, sessions, quests, data points, size). The resource form of wyrm_stats.",mimeType:c}]}function I(){return[{uriTemplate:`${o}memory/{id}`,name:"Memory artifact",description:"A single memory artifact (lesson/pattern/anti-pattern/heuristic/reasoning trace) by id.",mimeType:c},{uriTemplate:`${o}project/{id}/truths`,name:"Project ground truths",description:"A project's current ground truths (the authoritative facts).",mimeType:c},{uriTemplate:`${o}project/{id}/failures`,name:"Project failures",description:"A project's unresolved failure patterns (the negative-learning set).",mimeType:c},{uriTemplate:`${o}project/{id}/quests`,name:"Project quests",description:"A project's open quests (pending + in-progress).",mimeType:c},{uriTemplate:`${o}project/{id}/memory`,name:"Project memory",description:"A project's validated memory artifacts.",mimeType:c},{uriTemplate:`${o}project/{id}/stats`,name:"Project memory stats",description:"A project's memory artifact stats (totals by kind, avg confidence).",mimeType:c}]}const m=200;class l extends Error{uri;constructor(t,e){super(e),this.uri=t,this.name="ResourceNotFoundError"}}function u(r,t){return{uri:r,mimeType:c,text:JSON.stringify(t,null,2)}}function A(r,t){const e=E(r);if(!e)throw new l(r,`Unknown or malformed resource URI: ${r}`);switch(e.kind){case"capabilities":{const i=t.toolCounts?.(),n=f(t.db.getDatabase(),i?.active??t.WYRM_TOOL_COUNT,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return t.agentDaemon.status().running}catch{return null}}},i?.full);return{uri:r,mimeType:p,text:y(n)}}case"stats":return u(r,t.db.getStats());case"memory":{const i=t.memory.get(e.id);if(!i)throw new l(r,`Memory artifact not found: ${e.id}`);return u(r,i)}case"project":{if(!t.db.getProjectById(e.id))throw new l(r,`Project not found: ${e.id}`);switch(e.leaf){case"truths":return u(r,{project_id:e.id,truths:t.groundTruths.getCurrent(e.id)});case"failures":return u(r,{project_id:e.id,failures:t.failures.list(e.id,m)});case"quests":{const n=t.db.getDatabase().prepare("SELECT * FROM quests WHERE project_id = ? AND status IN ('pending','in_progress') ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, id LIMIT ?").all(e.id,m);return u(r,{project_id:e.id,quests:n})}case"memory":{const n=g(e.cursor),s=t.memory.listPage(e.id,{pageSize:m,after:n}),a=u(r,{project_id:e.id,artifacts:s.items,...s.nextCursor?{next_cursor:s.nextCursor}:{}});return s.nextCursor&&(a.nextCursor=s.nextCursor),a}case"stats":return u(r,{project_id:e.id,stats:t.memory.getStats(e.id)})}}}}export{h as CAPABILITIES_URI,l as ResourceNotFoundError,j as STATS_URI,o as WYRM_URI_SCHEME,I as listWyrmResourceTemplates,N as listWyrmResources,R as memoryUri,E as parseWyrmUri,C as projectUri,A as readWyrmResource,$ as resourceLink};