wyrm-mcp 7.2.0 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. package/package.json +4 -2
@@ -1,674 +1,126 @@
1
- /**
2
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
3
- * @license AGPL-3.0-or-later
4
- */
5
- import { TOOL_ANNOTATIONS } from "../tool-annotations.js";
6
- import { currentReadCacheKey } from "./boundary.js";
7
- import { autoConfigureAll, findWyrmServerPath, getDefaultDbPath, getStatusSummary, removeFromAll } from "../autoconfig.js";
8
- import { daemonOr } from "../daemon-writer.js";
9
- import { renderFailureStats } from "../failure-patterns.js";
10
- import { computeStaleness } from "../intelligence.js";
11
- import { logger } from "../logger.js";
12
- import { runMaintenance } from "../maintenance.js";
13
- import { cache } from "../performance.js";
14
- import { createProvider } from "../providers/embedding-provider.js";
15
- import { reindexProjects } from "../reindex.js";
16
- import { requireString } from "../validate.js";
17
- import { createVectorStore } from "../vectors.js";
18
- export const intelligenceToolSpecs = [
19
- {
20
- name: "wyrm_feedback",
21
- 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.",
22
- inputSchema: {
23
- type: "object",
24
- properties: {
25
- artifactId: { type: "number", description: "ID of the memory artifact (from wyrm_recall results)" },
26
- success: { type: "boolean", description: "Was this artifact helpful for the task?" },
27
- },
28
- required: ["artifactId", "success"],
29
- },
30
- annotations: TOOL_ANNOTATIONS["wyrm_feedback"],
31
- aliases: [],
32
- handler: async (args, ctx) => {
33
- const { memory } = ctx;
34
- const { artifactId, success } = args;
35
- const artifact = memory.get(artifactId);
36
- if (!artifact)
37
- return { content: [{ type: "text", text: `Artifact #${artifactId} not found.` }], isError: true };
38
- memory.recordFeedback(artifactId, success);
39
- const updated = memory.get(artifactId);
40
- const icon = success ? '✅' : '❌';
41
- return { content: [{ type: "text", text: `󱅝 Feedback recorded ${icon}\n\n- Artifact #${artifactId}: "${artifact.problem.slice(0, 60)}"\n- New confidence: ${(updated.confidence * 100).toFixed(0)}%\n- Total uses: ${updated.reuse_count} (✅ ${updated.reuse_success_count} / ❌ ${updated.reuse_failure_count})` }] };
42
- },
43
- },
44
- {
45
- name: "wyrm_context_build",
46
- 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.",
47
- inputSchema: {
48
- type: "object",
49
- properties: {
50
- // v7 F3 (T026): property prose compressed to fund the hot-path
51
- // outputSchemas under the 8K default-surface pin (T022/T025 trade).
52
- projectPath: { type: "string" },
53
- task: { type: "string", description: "The current task" },
54
- maxItems: { type: "number", description: "Default 10, max 20" },
55
- kinds: { type: "array", items: { type: "string" }, description: "Artifact kinds (see wyrm_recall)" },
56
- minConfidence: { type: "number", description: "Default 0.3" },
57
- max_tokens: { type: "number", description: "Token budget; low-score items elide to stubs" },
58
- session_id: { type: "number", description: "Already-seen dedup" },
59
- strict_budget: { type: "boolean", description: "Also elide truths over budget" },
60
- },
61
- required: ["projectPath", "task"],
62
- },
63
- annotations: TOOL_ANNOTATIONS["wyrm_context_build"],
64
- aliases: [],
65
- handler: async (args, ctx) => {
66
- const { cachedResponse, db, groundTruths, memory, runBudgetedContextBuild, scaffoldLib } = ctx;
67
- const a = args || {};
68
- const cacheKey = currentReadCacheKey();
69
- const { projectPath: cbPath, task: cbTask, maxItems: cbMax, kinds: cbKinds, minConfidence: cbMinConf, max_tokens: cbMaxTokens, session_id: cbSessionId, strict_budget: cbStrictBudget, } = args;
70
- const cbProject = db.getProject(cbPath);
71
- if (!cbProject)
72
- return { content: [{ type: "text", text: `Project not found: ${cbPath}` }], isError: true };
73
- // Spec 014 budgeted path — only when max_tokens is supplied (backwards compat: criterion 7).
74
- // Kill switch: WYRM_DISABLE_TOKEN_BUDGET=1 routes everything to legacy.
75
- const wantsBudget = cbMaxTokens != null && !process.env.WYRM_DISABLE_TOKEN_BUDGET;
76
- if (wantsBudget) {
77
- return runBudgetedContextBuild({
78
- project: cbProject,
79
- task: cbTask,
80
- maxTokens: cbMaxTokens,
81
- sessionId: cbSessionId,
82
- strictBudget: cbStrictBudget === true,
83
- kinds: cbKinds,
84
- minConfidence: cbMinConf,
85
- });
86
- }
87
- const sections = [];
88
- // Section 1: Ground truths (~1200 char budget)
89
- const truthsText = groundTruths.formatForContext(cbProject.id);
90
- if (truthsText) {
91
- sections.push(truthsText.slice(0, 1200));
92
- }
93
- // Section 2: Best reasoning scaffold (~1500 char budget)
94
- const scaffoldMatch = scaffoldLib.findBest(cbTask, cbProject.id);
95
- if (scaffoldMatch) {
96
- const scaffoldText = scaffoldLib.formatForContext(scaffoldMatch);
97
- sections.push(scaffoldText.slice(0, 1500));
98
- }
99
- // Section 3: Memory artifacts brief (~2000 char budget)
100
- const brief = memory.buildContextBrief(cbProject.id, cbTask, {
101
- maxItems: Math.min(cbMax ?? 10, 20),
102
- kinds: cbKinds,
103
- minConfidence: cbMinConf,
104
- });
105
- if (brief.sections.length > 0) {
106
- const truncated = brief.text.slice(0, 2000);
107
- sections.push(truncated);
108
- }
109
- if (sections.length === 0) {
110
- return { content: [{ type: "text", text: `󱅝 **Context Brief**\n\nNo relevant memory found for this task yet.\n\nAs you work, use \`wyrm_remember\` to store:\n- ✅ Patterns that work\n- ⚠️ Anti-patterns to avoid\n- 💡 Heuristics and shortcuts\n- 🧠 Reasoning traces from complex problems\n\nFuture context briefs will become richer over time.` }] };
111
- }
112
- const stats = memory.getStats(cbProject.id);
113
- const truthStats = groundTruths.getStats(cbProject.id);
114
- // Spec 018 phase 4: cache-stable preamble wrapped in marker
115
- // comments. Anthropic prompt-caching keys off prefix stability;
116
- // ground truths + scaffold rarely change between calls, so we
117
- // segregate them into a stable preamble and put per-call data
118
- // (memory artifacts) after the marker. The markdown comment is
119
- // a fallback until MCP standardises a structured annotation.
120
- const stableHeader = `󱅝 **Context Brief** "${cbTask}"\n\n`;
121
- const stableParts = [];
122
- if (truthsText)
123
- stableParts.push(truthsText.slice(0, 1200));
124
- if (scaffoldMatch)
125
- stableParts.push(scaffoldLib.formatForContext(scaffoldMatch).slice(0, 1500));
126
- const stablePreamble = stableParts.length > 0
127
- ? `<!-- cache-stable -->\n${stableParts.join('\n\n---\n\n')}\n<!-- /cache-stable -->\n\n---\n\n`
128
- : '';
129
- const volatileBody = brief.sections.length > 0 ? brief.text.slice(0, 2000) : '';
130
- let text = `${stableHeader}${stablePreamble}${volatileBody}`;
131
- text += `\n\n_Brief: ${truthStats.current} ground truth${truthStats.current !== 1 ? 's' : ''}, scaffold: ${scaffoldMatch ? scaffoldMatch.scaffold.problem_type : 'none'}, ${stats.total} memory artifact${stats.total !== 1 ? 's' : ''}.`;
132
- if (brief.sourceIds.length > 0) {
133
- text += ` Memory IDs: [${brief.sourceIds.join(', ')}] — use \`wyrm_feedback\` to rate._`;
134
- }
135
- else {
136
- text += `_`;
137
- }
138
- // Log cached-preamble savings (best-effort).
139
- try {
140
- const { logSavings } = await import('../statusline.js');
141
- const rawDb = db.getDatabase();
142
- const sess = rawDb.prepare(`SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1`).get(cbProject.id);
143
- // Tokens saved by caching = preamble size * 0.9 (Anthropic cached pricing is 10% of base)
144
- const preambleTokens = Math.round(stablePreamble.length / 4);
145
- if (preambleTokens > 0)
146
- logSavings(rawDb, 'wyrm_context_build', 'cached_preamble', Math.round(preambleTokens * 0.9), sess?.id);
147
- }
148
- catch { /* best-effort */ }
149
- const response = cachedResponse(text);
150
- if (cacheKey)
151
- cache.set(cacheKey, response, 10000);
152
- return response;
153
- },
154
- },
155
- {
156
- name: "wyrm_truth_set",
157
- 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).",
158
- inputSchema: {
159
- type: "object",
160
- properties: {
161
- projectPath: { type: "string" },
162
- category: { type: "string", description: "e.g. architecture, conventions" },
163
- key: { type: "string", description: "snake_case key" },
164
- value: { type: "string", description: "The statement" },
165
- rationale: { type: "string", description: "Why this is true" },
166
- source: { type: "string", description: "'user'|'derived'|'observed'|'confirmed'" },
167
- confidence: { type: "number", description: "0-1 (default 1.0)" },
168
- ttl_days: { type: "number", description: "Stale after N days" },
169
- },
170
- required: ["projectPath", "category", "key", "value"],
171
- },
172
- annotations: TOOL_ANNOTATIONS["wyrm_truth_set"],
173
- aliases: [],
174
- handler: async (args, ctx) => {
175
- const { db, groundTruths, presence } = ctx;
176
- const { projectPath: tPath, category: tCat, key: tKey, value: tVal, rationale: tRat, source: tSrc, confidence: tConf, ttl_days: tTtl } = args;
177
- // Truth categories are an OPEN vocabulary at the storage layer:
178
- // ground_truths.category carries no schema CHECK, the wire schema
179
- // documents free-form values ('conventions', 'decisions', ...), and
180
- // the renderer passes unknown categories through with their own
181
- // label. Closed-enum rejection here would invalidate legal stored
182
- // data — so the boundary enforces presence/type only (there is no
183
- // CHECK-violation path for this field in either write mode).
184
- const tSafeCat = requireString('category', tCat, { maxLen: 200 });
185
- const tProject = db.getProject(tPath);
186
- if (!tProject)
187
- return { content: [{ type: "text", text: `Project not found: ${tPath}` }], isError: true };
188
- // v7 F2 (T012): canonical write seam — truth_set.
189
- const tInput = {
190
- category: tSafeCat,
191
- key: tKey,
192
- value: tVal,
193
- rationale: tRat,
194
- source: tSrc,
195
- confidence: tConf ?? 1.0,
196
- ttl_days: tTtl,
197
- };
198
- const truth = await daemonOr('truth_set', tProject.id, tInput, () => groundTruths.set(tProject.id, tInput));
199
- cache.invalidate('wyrm_truth_get');
200
- cache.invalidate('wyrm_context_build');
201
- return { content: [{ type: "text", text: `󱅝 **Ground Truth Set** ✅\n\n- **Category:** ${tCat}\n- **Key:** ${tKey}\n- **Value:** ${tVal}${tRat ? `\n- **Rationale:** ${tRat}` : ''}${tTtl ? `\n- **TTL:** ${tTtl} days` : ''}\n- **ID:** ${truth.id}\n\nThis truth will be injected at the top of every \`wyrm_context_build\` response for this project.` }] };
202
- },
203
- },
204
- {
205
- name: "wyrm_truth_get",
206
- 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.",
207
- inputSchema: {
208
- type: "object",
209
- properties: {
210
- projectPath: { type: "string" },
211
- category: { type: "string", description: "Category filter" },
212
- },
213
- required: ["projectPath"],
214
- },
215
- annotations: TOOL_ANNOTATIONS["wyrm_truth_get"],
216
- aliases: [],
217
- handler: async (args, ctx) => {
218
- const { cachedResponse, db, groundTruths } = ctx;
219
- const cacheKey = currentReadCacheKey();
220
- const { projectPath: tgPath, category: tgCat } = args;
221
- const tgProject = db.getProject(tgPath);
222
- if (!tgProject)
223
- return { content: [{ type: "text", text: `Project not found: ${tgPath}` }], isError: true };
224
- const truths = groundTruths.getCurrent(tgProject.id, tgCat);
225
- if (truths.length === 0) {
226
- const msg = tgCat
227
- ? `No ground truths in category "${tgCat}" for this project.`
228
- : `No ground truths stored yet. Use \`wyrm_truth_set\` to add validated facts.`;
229
- return { content: [{ type: "text", text: `󱅝 **Ground Truths**\n\n${msg}` }] };
230
- }
231
- const byCategory = new Map();
232
- for (const t of truths) {
233
- if (!byCategory.has(t.category))
234
- byCategory.set(t.category, []);
235
- byCategory.get(t.category).push(t);
236
- }
237
- let text = `󱅝 **Ground Truths** — ${truths.length} active\n\n`;
238
- for (const [cat, items] of byCategory) {
239
- text += `### ${cat}\n`;
240
- for (const t of items) {
241
- const staleness = computeStaleness(t);
242
- const stale = staleness !== null && staleness > 0.7;
243
- const stalePrefix = stale ? '[⚠️ STALE] ' : '';
244
- text += `- ${stalePrefix}**${t.key}:** ${t.value}`;
245
- if (t.confidence < 1)
246
- text += ` _(confidence: ${(t.confidence * 100).toFixed(0)}%)_`;
247
- if (t.rationale)
248
- text += `\n _${t.rationale}_`;
249
- if (staleness !== null)
250
- text += `\n _staleness: ${(staleness * 100).toFixed(0)}% (TTL: ${t.ttl_days}d)_`;
251
- text += '\n';
252
- }
253
- text += '\n';
254
- }
255
- const response = cachedResponse(text);
256
- if (cacheKey)
257
- cache.set(cacheKey, response, 15000);
258
- return response;
259
- },
260
- },
261
- {
262
- name: "wyrm_scaffold_get",
263
- 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.",
264
- inputSchema: {
265
- type: "object",
266
- properties: {
267
- projectPath: { type: "string", description: "Project path (searches project + global)" },
268
- task: { type: "string", description: "Task description to match against scaffolds" },
269
- minConfidence: { type: "number", description: "Minimum match confidence 0-1 (default: 0.3)" },
270
- },
271
- required: ["task"],
272
- },
273
- annotations: TOOL_ANNOTATIONS["wyrm_scaffold_get"],
274
- aliases: [],
275
- handler: async (args, ctx) => {
276
- const { cachedResponse, db, scaffoldLib } = ctx;
277
- const cacheKey = currentReadCacheKey();
278
- const { projectPath: sgPath, task: sgTask, minConfidence: sgConf } = args;
279
- const sgProjectId = sgPath ? db.getProject(sgPath)?.id ?? undefined : undefined;
280
- const match = scaffoldLib.findBest(sgTask, sgProjectId, sgConf);
281
- if (!match) {
282
- return { content: [{ type: "text", text: `󱅝 **Reasoning Scaffold**\n\nNo scaffold matched for: "${sgTask}"\n\nUse \`wyrm_scaffold_save\` to add scaffolds for this problem type.` }] };
283
- }
284
- const text = scaffoldLib.formatForContext(match);
285
- const response = cachedResponse(`󱅝 **Reasoning Scaffold Match** (${(match.matchScore * 100).toFixed(0)}% confidence)\n\n${text}`);
286
- if (cacheKey)
287
- cache.set(cacheKey, response, 15000);
288
- return response;
289
- },
290
- },
291
- {
292
- name: "wyrm_sync",
293
- description: "Sync database with .wyrm folders in all projects",
294
- inputSchema: {
295
- type: "object",
296
- properties: {
297
- projectPath: { type: "string", description: "Sync specific project, or all if not specified" },
298
- direction: { type: "string", enum: ["import", "export", "both"], description: "Sync direction" },
299
- },
300
- },
301
- annotations: TOOL_ANNOTATIONS["wyrm_sync"],
302
- aliases: [],
303
- handler: async (args, ctx) => {
304
- const { db, sync } = ctx;
305
- const { projectPath, direction } = args;
306
- const dir = direction || 'both';
307
- let count = 0;
308
- if (projectPath) {
309
- const project = db.getProject(projectPath);
310
- if (project) {
311
- if (dir === 'import' || dir === 'both')
312
- sync.importFromFolder(projectPath);
313
- if (dir === 'export' || dir === 'both')
314
- sync.exportToFolder(projectPath);
315
- count = 1;
316
- }
317
- }
318
- else {
319
- const projects = db.getAllProjects(1000);
320
- for (const p of projects) {
321
- try {
322
- if (dir === 'import' || dir === 'both')
323
- sync.importFromFolder(p.path);
324
- if (dir === 'export' || dir === 'both')
325
- sync.exportToFolder(p.path);
326
- count++;
327
- }
328
- catch {
329
- // Skip failed syncs
330
- }
331
- }
332
- }
333
- return {
334
- content: [{
335
- type: "text",
336
- text: `󱅝 Synced ${count} project(s)`
337
- }]
338
- };
339
- },
340
- },
341
- {
342
- name: "wyrm_stats",
343
- description: "Use for a health snapshot of the memory database - row counts, vector index coverage, usage.",
344
- inputSchema: {
345
- type: "object",
346
- properties: {
347
- view: { type: "string", enum: ["failures"], description: "'failures' = prevented-repeat analytics" },
348
- projectPath: { type: "string", description: "failures view scope" },
349
- },
350
- },
351
- annotations: TOOL_ANNOTATIONS["wyrm_stats"],
352
- aliases: [],
353
- handler: async (args, ctx) => {
354
- const { WRITE_TOOLS, analytics, cachedResponse, db, failures, getUsageStats, orchestrator, vectorStore } = ctx;
355
- const a = args || {};
356
- const cacheKey = currentReadCacheKey();
357
- // v7 F2 (T017): prevented-repeat analytics view (spec FR-2). NOT
358
- // cached on purpose: the failure_blocks ledger is written by
359
- // wyrm_failure_check, which is not in WRITE_TOOLS, so the stats cache
360
- // invalidation never fires for it — a 15s-stale "repeats blocked this
361
- // run" would lie to an orchestrator polling mid-fleet, and the view is
362
- // a handful of indexed COUNTs (cheap enough to stay fresh). "this run"
363
- // resolves from the ambient actor envelope (failureStats → getActor()).
364
- const { view: statsViewArg, projectPath: statsProjectPath } = args;
365
- if (statsViewArg === "failures") {
366
- const statsProject = statsProjectPath ? db.getProject(statsProjectPath) : null;
367
- const failuresView = failures.failureStats({ projectId: statsProject?.id ?? null });
368
- return {
369
- content: [{ type: "text", text: renderFailureStats(failuresView) }],
370
- // Pre-T019 dual-emit (the T011/T014 precedent): structuredContent
371
- // carries the canonical view; outputSchema lands with F3 ToolSpec.
372
- structuredContent: failuresView,
373
- };
374
- }
375
- const stats = db.getStats();
376
- const cacheStats = cache.stats();
377
- const usage = getUsageStats();
378
- // Vector coverage stats
379
- let vectorStatsText = '';
380
- if (vectorStore) {
381
- const vectorStats = vectorStore.getStats();
382
- const totalEmbeddable = stats.sessions + stats.quests + stats.dataPoints;
383
- const vectorCoverage = totalEmbeddable > 0
384
- ? ((vectorStats.total / totalEmbeddable) * 100).toFixed(1)
385
- : '0';
386
- vectorStatsText =
387
- `\n**Vectors:**\n` +
388
- `- **Provider:** ${vectorStats.provider} (${vectorStats.model})\n` +
389
- `- **Embeddings:** ${vectorStats.total} vectors\n` +
390
- `- **Coverage:** ${vectorCoverage}% of embeddable content\n` +
391
- `- **By Type:** ${Object.entries(vectorStats.byType).map(([t, c]) => `${t}: ${c}`).join(', ')}\n`;
392
- }
393
- const response = cachedResponse(`󱅝 **Wyrm Statistics**\n\n` +
394
- `- **Projects:** ${stats.projects}\n` +
395
- `- **Sessions:** ${stats.sessions}\n` +
396
- `- **Quests:** ${stats.quests}\n` +
397
- `- **Data Points:** ${stats.dataPoints}\n` +
398
- `- **Active Tokens:** ~${stats.totalTokens.toLocaleString()}\n` +
399
- `- **Database Size:** ${stats.dbSize}\n` +
400
- vectorStatsText +
401
- `\n**Cache:** ${cacheStats.size} entries | Hit rate: ${usage.cacheHitRate}\n` +
402
- `**Usage:** ${usage.totalCalls} calls | ~${usage.tokensSaved.toLocaleString()} tokens saved by cache`);
403
- if (cacheKey)
404
- cache.set(cacheKey, response, 15000);
405
- return response;
406
- },
407
- },
408
- {
409
- name: "wyrm_vector_setup",
410
- description: "Configure embedding provider and download models. Supports local, OpenAI, and Ollama.",
411
- inputSchema: {
412
- type: "object",
413
- properties: {
414
- provider: { type: "string", enum: ["auto", "local", "openai", "ollama", "none"], description: "Provider: auto-detect, local hash, OpenAI API, Ollama, or none" },
415
- model: { type: "string", description: "Model name (e.g., 'text-embedding-3-small' for OpenAI, 'nomic-embed-text' for Ollama)" },
416
- apiKey: { type: "string", description: "OpenAI API key (if using openai provider)" },
417
- ollamaUrl: { type: "string", description: "Ollama URL (default: http://localhost:11434)" },
418
- },
419
- },
420
- annotations: TOOL_ANNOTATIONS["wyrm_vector_setup"],
421
- aliases: [],
422
- handler: async (args, ctx) => {
423
- const { db, indexingPipeline, memory, setVectorStore, vectorStore } = ctx;
424
- const { provider: prov, model: mod, apiKey: key, ollamaUrl: url } = args;
425
- // Build new provider config
426
- const providerConfig = {
427
- provider: (prov || 'auto'),
428
- model: mod,
429
- apiKey: key,
430
- ollamaUrl: url,
431
- };
432
- try {
433
- // Create new provider to test
434
- const testProvider = createProvider(providerConfig);
435
- const isReady = await testProvider.isReady();
436
- if (!isReady && providerConfig.provider !== 'none') {
437
- return {
438
- content: [{
439
- type: "text",
440
- text: `❌ **Provider Not Ready**\n\nCouldn't connect to ${testProvider.name}. Check your configuration and try again.`
441
- }],
442
- isError: true
443
- };
444
- }
445
- // Update global vector store
446
- const __vs = createVectorStore(providerConfig, db.getDatabase());
447
- setVectorStore(__vs);
448
- memory.setVectorStore(__vs); // keep hybrid recall pointed at the live store
449
- if (indexingPipeline)
450
- indexingPipeline.updateStore(__vs);
451
- const stats = __vs.getStats();
452
- return {
453
- content: [{
454
- type: "text",
455
- text: `✅ **Vector Setup Complete**\n\n` +
456
- `- **Provider:** ${testProvider.name}\n` +
457
- `- **Model:** ${testProvider.model}\n` +
458
- `- **Dimensions:** ${testProvider.dimensions}\n` +
459
- `- **Status:** ${isReady ? 'Ready' : 'Ready (will auto-connect)'}\n\n` +
460
- `_Reindex your projects with wyrm_reindex to generate embeddings._`
461
- }]
462
- };
463
- }
464
- catch (err) {
465
- return {
466
- content: [{
467
- type: "text",
468
- text: `❌ **Vector Setup Failed**\n\n${String(err)}`
469
- }],
470
- isError: true
471
- };
472
- }
473
- },
474
- },
475
- {
476
- name: "wyrm_reindex",
477
- description: "Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",
478
- inputSchema: {
479
- type: "object",
480
- properties: {
481
- projectPath: { type: "string", description: "Project to reindex, or leave empty for all projects" },
482
- dryRun: { type: "boolean", description: "Show what would be indexed without making changes" },
483
- },
484
- },
485
- annotations: TOOL_ANNOTATIONS["wyrm_reindex"],
486
- aliases: [],
487
- handler: async (args, ctx) => {
488
- const { db, failures, vectorStore } = ctx;
489
- const { projectPath: ppath, dryRun } = args;
490
- if (!vectorStore) {
491
- return {
492
- content: [{
493
- type: "text",
494
- text: `❌ **Vector Indexing Disabled**\n\nRun wyrm_vector_setup first to enable vector search.`
495
- }],
496
- isError: true
497
- };
498
- }
499
- let projectIds = [];
500
- if (ppath) {
501
- const proj = db.getProject(ppath);
502
- if (!proj) {
503
- return {
504
- content: [{
505
- type: "text",
506
- text: `❌ **Project Not Found:** ${ppath}`
507
- }],
508
- isError: true
509
- };
510
- }
511
- projectIds = [proj.id];
512
- }
513
- else {
514
- const allProjs = db.getAllProjects(1000);
515
- projectIds = allProjs.map(p => p.id);
516
- }
517
- // v7 F3 (T023): the project loop moved VERBATIM to src/reindex.ts
518
- // (reindexProjects) so the new `wyrm index rebuild` CLI subcommand and
519
- // this tool run the SAME code path. Per-row failures still log here.
520
- const { indexed, skipped } = await reindexProjects(db.getDatabase(), vectorStore, projectIds, {
521
- dryRun,
522
- onError: (message, context) => logger.error(message, context),
523
- });
524
- return {
525
- content: [{
526
- type: "text",
527
- text: `󱅝 **Reindexing ${dryRun ? '(Dry Run)' : 'Complete'}**\n\n` +
528
- `- **Projects:** ${projectIds.length}\n` +
529
- `- **Indexed:** ${indexed}\n` +
530
- `- **Skipped:** ${skipped}\n\n` +
531
- (dryRun ? '_Run without dryRun to actually index._' : '_Vectors are now up to date._')
532
- }]
533
- };
534
- },
535
- },
536
- {
537
- name: "wyrm_maintenance",
538
- description: "Use for periodic upkeep of the memory store - archive old sessions, vacuum, prune stale events, sweep run quarantine, rebuild vector embeddings (reindex). Admin-gated, destructive; fleets run it between waves.",
539
- inputSchema: {
540
- type: "object",
541
- properties: {
542
- vacuum: { type: "boolean", description: "Reclaim space" },
543
- archiveDays: { type: "number", description: "Archive sessions > N days" },
544
- },
545
- },
546
- annotations: TOOL_ANNOTATIONS["wyrm_maintenance"],
547
- aliases: [],
548
- handler: async (args, ctx) => {
549
- const { db, failures, presence, sessionSeen } = ctx;
550
- const a = args || {};
551
- const { vacuum, archiveDays } = args;
552
- // v7 F3 (T023): the step sequence moved VERBATIM to
553
- // src/maintenance.ts (runMaintenance) so the new `wyrm maintenance`
554
- // CLI subcommand and this tool run the SAME code path. The 6.x text
555
- // is reassembled byte-identically below (every line carried "- " and
556
- // a trailing newline).
557
- // v7 F3 (T029): + presence — stale-claim/presence eviction now rides
558
- // maintenance (reap previously ran only inline at claim time).
559
- const report = runMaintenance({ db, sessionSeen: sessionSeen(), failures, presence }, { vacuum, archiveDays });
560
- const text = '󱅝 **Maintenance Complete**\n\n' +
561
- report.lines.map((line) => `- ${line}\n`).join('') +
562
- `\n**New Database Size:** ${report.dbSize}`;
563
- return { content: [{ type: "text", text }] };
564
- },
565
- },
566
- {
567
- name: "wyrm_setup",
568
- 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.",
569
- inputSchema: {
570
- type: "object",
571
- properties: {
572
- action: { type: "string", enum: ["configure", "check", "remove"], description: "Action: configure (default), check status, or remove from all" },
573
- serverPath: { type: "string", description: "Override Wyrm server path (auto-detected if empty)" },
574
- dbPath: { type: "string", description: "Override database path (default: ~/.wyrm/wyrm.db)" },
575
- },
576
- },
577
- annotations: TOOL_ANNOTATIONS["wyrm_setup"],
578
- aliases: [],
579
- handler: async (args, ctx) => {
580
- const { action, serverPath, dbPath } = args;
581
- const setupAction = action || 'configure';
582
- if (setupAction === 'check') {
583
- return {
584
- content: [{
585
- type: "text",
586
- text: getStatusSummary()
587
- }]
588
- };
589
- }
590
- if (setupAction === 'remove') {
591
- const results = removeFromAll();
592
- const removed = results.filter(r => r.action === 'configured');
593
- const text = `󱅝 **Wyrm Removed**\n\n` +
594
- `Removed from ${removed.length} AI client(s):\n` +
595
- results.map(r => `- ${r.client.icon} ${r.client.name}: ${r.message}`).join('\n') +
596
- `\n\nRun wyrm_setup again to reconnect.`;
597
- return { content: [{ type: "text", text }] };
598
- }
599
- // Default: configure
600
- const results = autoConfigureAll({
601
- serverPath: serverPath || undefined,
602
- dbPath: dbPath || undefined,
603
- });
604
- const configured = results.filter(r => r.action === 'configured' || r.action === 'updated');
605
- const failed = results.filter(r => r.action === 'failed');
606
- let text = `󱅝 **Wyrm Auto-Configure Complete**\n\n`;
607
- text += `Connected to ${configured.length} AI client(s):\n`;
608
- for (const r of results) {
609
- const icon = r.action === 'configured' ? '✅' :
610
- r.action === 'updated' ? '🔄' :
611
- r.action === 'skipped' ? '○' : '❌';
612
- text += `- ${icon} ${r.client.icon} ${r.client.name}: ${r.message}\n`;
613
- }
614
- if (failed.length > 0) {
615
- text += `\n⚠️ ${failed.length} client(s) failed. Check errors above.`;
616
- }
617
- text += `\n\nServer: ${findWyrmServerPath()}\nDB: ${getDefaultDbPath()}`;
618
- text += `\n\n_Switch AIs anytime — run wyrm_setup again to reconnect._`;
619
- return { content: [{ type: "text", text }] };
620
- },
621
- },
622
- {
623
- name: "wyrm_usage",
624
- description: "View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",
625
- inputSchema: {
626
- type: "object",
627
- properties: {
628
- last: { type: "number", description: "Show stats for last N calls (default: all)" },
629
- reset: { type: "boolean", description: "Reset usage counters" },
630
- },
631
- },
632
- annotations: TOOL_ANNOTATIONS["wyrm_usage"],
633
- aliases: [],
634
- handler: async (args, ctx) => {
635
- const { cachedResponse, getUsageStats, responseFingerprints, usageLog } = ctx;
636
- const { last, reset } = args;
637
- if (reset) {
638
- usageLog.length = 0;
639
- responseFingerprints.clear();
640
- cache.invalidate();
641
- return { content: [{ type: "text", text: "󱅝 Usage counters reset, caches cleared." }] };
642
- }
643
- const usage = getUsageStats(last);
644
- const cacheStats = cache.stats();
645
- let text = `󱅝 **Wyrm Usage Report**\n\n`;
646
- text += `## Overview${last ? ` (last ${last} calls)` : ''}\n`;
647
- text += `- **Total Calls:** ${usage.totalCalls}\n`;
648
- text += `- **Cache Hits:** ${usage.cachedCalls} (${usage.cacheHitRate})\n`;
649
- text += `- **Tokens In:** ~${usage.totalTokensIn.toLocaleString()}\n`;
650
- text += `- **Tokens Out:** ~${usage.totalTokensOut.toLocaleString()}\n`;
651
- text += `- **Tokens Saved (cache):** ~${usage.tokensSaved.toLocaleString()}\n`;
652
- text += `- **Avg Response:** ${usage.avgResponseMs}ms\n`;
653
- text += `- **Active Cache Entries:** ${cacheStats.size}\n\n`;
654
- if (usage.topTools.length > 0) {
655
- text += `## Top Tools by Token Usage\n`;
656
- for (const t of usage.topTools) {
657
- text += `- **${t.tool}:** ${t.calls} calls, ~${t.tokens.toLocaleString()} tokens\n`;
658
- }
659
- text += '\n';
660
- }
661
- // Cost estimate (Claude Opus pricing: $15/M input, $75/M output)
662
- const costInput = (usage.totalTokensIn / 1_000_000) * 15;
663
- const costOutput = (usage.totalTokensOut / 1_000_000) * 75;
664
- const costSaved = (usage.tokensSaved / 1_000_000) * 75;
665
- text += `## Estimated Cost (Claude Opus rates)\n`;
666
- text += `- **Input:** $${costInput.toFixed(4)}\n`;
667
- text += `- **Output:** $${costOutput.toFixed(4)}\n`;
668
- text += `- **Saved by cache:** $${costSaved.toFixed(4)}\n`;
669
- text += `- **Net cost:** $${(costInput + costOutput - costSaved).toFixed(4)}`;
670
- return cachedResponse(text, true); // ephemeral — don't cache the usage report itself
671
- },
672
- },
673
- ];
674
- //# sourceMappingURL=intelligence.js.map
1
+ import{TOOL_ANNOTATIONS as b}from"../tool-annotations.js";import{currentReadCacheKey as T}from"./boundary.js";import{autoConfigureAll as B,findWyrmServerPath as U,getDefaultDbPath as N,getStatusSummary as V,removeFromAll as W}from"../autoconfig.js";import{daemonOr as K}from"../daemon-writer.js";import{renderFailureStats as q}from"../failure-patterns.js";import{computeStaleness as H}from"../intelligence.js";import{logger as z}from"../logger.js";import{runMaintenance as G}from"../maintenance.js";import{cache as v}from"../performance.js";import{createProvider as Y}from"../providers/embedding-provider.js";import{reindexProjects as Q}from"../reindex.js";import{requireString as Z}from"../validate.js";import{createVectorStore as J}from"../vectors.js";const ue=[{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:b.wyrm_feedback,aliases:[],handler:async(u,f)=>{const{memory:d}=f,{artifactId:c,success:i}=u,a=d.get(c);if(!a)return{content:[{type:"text",text:`Artifact #${c} not found.`}],isError:!0};d.recordFeedback(c,i);const t=d.get(c);return{content:[{type:"text",text:`\u{F115D} Feedback recorded ${i?"\u2705":"\u274C"}
2
+
3
+ - Artifact #${c}: "${a.problem.slice(0,60)}"
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:b.wyrm_context_build,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:c,groundTruths:i,memory:a,runBudgetedContextBuild:t,scaffoldLib:s}=f,o=u||{},r=T(),{projectPath:e,task:n,maxItems:m,kinds:l,minConfidence:p,max_tokens:y,session_id:g,strict_budget:j}=u,h=c.getProject(e);if(!h)return{content:[{type:"text",text:`Project not found: ${e}`}],isError:!0};if(y!=null&&!process.env.WYRM_DISABLE_TOKEN_BUDGET)return t({project:h,task:n,maxTokens:y,sessionId:g,strictBudget:j===!0,kinds:l,minConfidence:p});const x=[],w=i.formatForContext(h.id);w&&x.push(w.slice(0,1200));const _=s.findBest(n,h.id);if(_){const k=s.formatForContext(_);x.push(k.slice(0,1500))}const S=a.buildContextBrief(h.id,n,{maxItems:Math.min(m??10,20),kinds:l,minConfidence:p});if(S.sections.length>0){const k=S.text.slice(0,2e3);x.push(k)}if(x.length===0)return{content:[{type:"text",text:`\u{F115D} **Context Brief**
6
+
7
+ No relevant memory found for this task yet.
8
+
9
+ As you work, use \`wyrm_remember\` to store:
10
+ - \u2705 Patterns that work
11
+ - \u26A0\uFE0F Anti-patterns to avoid
12
+ - \u{1F4A1} Heuristics and shortcuts
13
+ - \u{1F9E0} Reasoning traces from complex problems
14
+
15
+ Future context briefs will become richer over time.`}]};const R=a.getStats(h.id),I=i.getStats(h.id),E=`\u{F115D} **Context Brief** \u2014 "${n}"
16
+
17
+ `,P=[];w&&P.push(w.slice(0,1200)),_&&P.push(s.formatForContext(_).slice(0,1500));const A=P.length>0?`<!-- cache-stable -->
18
+ ${P.join(`
19
+
20
+ ---
21
+
22
+ `)}
23
+ <!-- /cache-stable -->
24
+
25
+ ---
26
+
27
+ `:"",M=S.sections.length>0?S.text.slice(0,2e3):"";let C=`${E}${A}${M}`;C+=`
28
+
29
+ _Brief: ${I.current} ground truth${I.current!==1?"s":""}, scaffold: ${_?_.scaffold.problem_type:"none"}, ${R.total} memory artifact${R.total!==1?"s":""}.`,S.sourceIds.length>0?C+=` Memory IDs: [${S.sourceIds.join(", ")}] \u2014 use \`wyrm_feedback\` to rate._`:C+="_";try{const{logSavings:k}=await import("../statusline.js"),O=c.getDatabase(),L=O.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(h.id),D=Math.round(A.length/4);D>0&&k(O,"wyrm_context_build","cached_preamble",Math.round(D*.9),L?.id)}catch{}const F=d(C);return r&&v.set(r,F,1e4),F}},{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:b.wyrm_truth_set,aliases:[],handler:async(u,f)=>{const{db:d,groundTruths:c,presence:i}=f,{projectPath:a,category:t,key:s,value:o,rationale:r,source:e,confidence:n,ttl_days:m}=u,l=Z("category",t,{maxLen:200}),p=d.getProject(a);if(!p)return{content:[{type:"text",text:`Project not found: ${a}`}],isError:!0};const y={category:l,key:s,value:o,rationale:r,source:e,confidence:n??1,ttl_days:m},g=await K("truth_set",p.id,y,()=>c.set(p.id,y));return v.invalidate("wyrm_truth_get"),v.invalidate("wyrm_context_build"),{content:[{type:"text",text:`\u{F115D} **Ground Truth Set** \u2705
30
+
31
+ - **Category:** ${t}
32
+ - **Key:** ${s}
33
+ - **Value:** ${o}${r?`
34
+ - **Rationale:** ${r}`:""}${m?`
35
+ - **TTL:** ${m} days`:""}
36
+ - **ID:** ${g.id}
37
+
38
+ 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:b.wyrm_truth_get,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:c,groundTruths:i}=f,a=T(),{projectPath:t,category:s}=u,o=c.getProject(t);if(!o)return{content:[{type:"text",text:`Project not found: ${t}`}],isError:!0};const r=i.getCurrent(o.id,s);if(r.length===0)return{content:[{type:"text",text:`\u{F115D} **Ground Truths**
39
+
40
+ ${s?`No ground truths in category "${s}" for this project.`:"No ground truths stored yet. Use `wyrm_truth_set` to add validated facts."}`}]};const e=new Map;for(const l of r)e.has(l.category)||e.set(l.category,[]),e.get(l.category).push(l);let n=`\u{F115D} **Ground Truths** \u2014 ${r.length} active
41
+
42
+ `;for(const[l,p]of e){n+=`### ${l}
43
+ `;for(const y of p){const g=H(y),h=g!==null&&g>.7?"[\u26A0\uFE0F STALE] ":"";n+=`- ${h}**${y.key}:** ${y.value}`,y.confidence<1&&(n+=` _(confidence: ${(y.confidence*100).toFixed(0)}%)_`),y.rationale&&(n+=`
44
+ _${y.rationale}_`),g!==null&&(n+=`
45
+ _staleness: ${(g*100).toFixed(0)}% (TTL: ${y.ttl_days}d)_`),n+=`
46
+ `}n+=`
47
+ `}const m=d(n);return a&&v.set(a,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:b.wyrm_scaffold_get,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,db:c,scaffoldLib:i}=f,a=T(),{projectPath:t,task:s,minConfidence:o}=u,r=t?c.getProject(t)?.id??void 0:void 0,e=i.findBest(s,r,o);if(!e)return{content:[{type:"text",text:`\u{F115D} **Reasoning Scaffold**
48
+
49
+ No scaffold matched for: "${s}"
50
+
51
+ Use \`wyrm_scaffold_save\` to add scaffolds for this problem type.`}]};const n=i.formatForContext(e),m=d(`\u{F115D} **Reasoning Scaffold Match** (${(e.matchScore*100).toFixed(0)}% confidence)
52
+
53
+ ${n}`);return a&&v.set(a,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:b.wyrm_sync,aliases:[],handler:async(u,f)=>{const{db:d,sync:c}=f,{projectPath:i,direction:a}=u,t=a||"both";let s=0;if(i)d.getProject(i)&&((t==="import"||t==="both")&&c.importFromFolder(i),(t==="export"||t==="both")&&c.exportToFolder(i),s=1);else{const o=d.getAllProjects(1e3);for(const r of o)try{(t==="import"||t==="both")&&c.importFromFolder(r.path),(t==="export"||t==="both")&&c.exportToFolder(r.path),s++}catch{}}return{content:[{type:"text",text:`\u{F115D} Synced ${s} 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:b.wyrm_stats,aliases:[],handler:async(u,f)=>{const{WRITE_TOOLS:d,analytics:c,cachedResponse:i,db:a,failures:t,getUsageStats:s,orchestrator:o,vectorStore:r}=f,e=u||{},n=T(),{view:m,projectPath:l}=u;if(m==="failures"){const $=l?a.getProject(l):null,x=t.failureStats({projectId:$?.id??null});return{content:[{type:"text",text:q(x)}],structuredContent:x}}const p=a.getStats(),y=v.stats(),g=s();let j="";if(r){const $=r.getStats(),x=p.sessions+p.quests+p.dataPoints,w=x>0?($.total/x*100).toFixed(1):"0";j=`
54
+ **Vectors:**
55
+ - **Provider:** ${$.provider} (${$.model})
56
+ - **Embeddings:** ${$.total} vectors
57
+ - **Coverage:** ${w}% of embeddable content
58
+ - **By Type:** ${Object.entries($.byType).map(([_,S])=>`${_}: ${S}`).join(", ")}
59
+ `}const h=i(`\u{F115D} **Wyrm Statistics**
60
+
61
+ - **Projects:** ${p.projects}
62
+ - **Sessions:** ${p.sessions}
63
+ - **Quests:** ${p.quests}
64
+ - **Data Points:** ${p.dataPoints}
65
+ - **Active Tokens:** ~${p.totalTokens.toLocaleString()}
66
+ - **Database Size:** ${p.dbSize}
67
+ `+j+`
68
+ **Cache:** ${y.size} entries | Hit rate: ${g.cacheHitRate}
69
+ **Usage:** ${g.totalCalls} calls | ~${g.tokensSaved.toLocaleString()} tokens saved by cache`);return n&&v.set(n,h,15e3),h}},{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:b.wyrm_vector_setup,aliases:[],handler:async(u,f)=>{const{db:d,indexingPipeline:c,memory:i,setVectorStore:a,vectorStore:t}=f,{provider:s,model:o,apiKey:r,ollamaUrl:e}=u,n={provider:s||"auto",model:o,apiKey:r,ollamaUrl:e};try{const m=Y(n),l=await m.isReady();if(!l&&n.provider!=="none")return{content:[{type:"text",text:`\u274C **Provider Not Ready**
70
+
71
+ Couldn't connect to ${m.name}. Check your configuration and try again.`}],isError:!0};const p=J(n,d.getDatabase());a(p),i.setVectorStore(p),c&&c.updateStore(p);const y=p.getStats();return{content:[{type:"text",text:`\u2705 **Vector Setup Complete**
72
+
73
+ - **Provider:** ${m.name}
74
+ - **Model:** ${m.model}
75
+ - **Dimensions:** ${m.dimensions}
76
+ - **Status:** ${l?"Ready":"Ready (will auto-connect)"}
77
+
78
+ _Reindex your projects with wyrm_reindex to generate embeddings._`}]}}catch(m){return{content:[{type:"text",text:`\u274C **Vector Setup Failed**
79
+
80
+ ${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:b.wyrm_reindex,aliases:[],handler:async(u,f)=>{const{db:d,failures:c,vectorStore:i}=f,{projectPath:a,dryRun:t}=u;if(!i)return{content:[{type:"text",text:`\u274C **Vector Indexing Disabled**
81
+
82
+ Run wyrm_vector_setup first to enable vector search.`}],isError:!0};let s=[];if(a){const e=d.getProject(a);if(!e)return{content:[{type:"text",text:`\u274C **Project Not Found:** ${a}`}],isError:!0};s=[e.id]}else s=d.getAllProjects(1e3).map(n=>n.id);const{indexed:o,skipped:r}=await Q(d.getDatabase(),i,s,{dryRun:t,onError:(e,n)=>z.error(e,n)});return{content:[{type:"text",text:`\u{F115D} **Reindexing ${t?"(Dry Run)":"Complete"}**
83
+
84
+ - **Projects:** ${s.length}
85
+ - **Indexed:** ${o}
86
+ - **Skipped:** ${r}
87
+
88
+ `+(t?"_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, rebuild vector embeddings (reindex). 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:b.wyrm_maintenance,aliases:[],handler:async(u,f)=>{const{db:d,failures:c,presence:i,sessionSeen:a}=f,t=u||{},{vacuum:s,archiveDays:o}=u,r=G({db:d,sessionSeen:a(),failures:c,presence:i},{vacuum:s,archiveDays:o});return{content:[{type:"text",text:`\u{F115D} **Maintenance Complete**
89
+
90
+ `+r.lines.map(n=>`- ${n}
91
+ `).join("")+`
92
+ **New Database Size:** ${r.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:b.wyrm_setup,aliases:[],handler:async(u,f)=>{const{action:d,serverPath:c,dbPath:i}=u,a=d||"configure";if(a==="check")return{content:[{type:"text",text:V()}]};if(a==="remove"){const e=W();return{content:[{type:"text",text:`\u{F115D} **Wyrm Removed**
93
+
94
+ Removed from ${e.filter(l=>l.action==="configured").length} AI client(s):
95
+ `+e.map(l=>`- ${l.client.icon} ${l.client.name}: ${l.message}`).join(`
96
+ `)+`
97
+
98
+ Run wyrm_setup again to reconnect.`}]}}const t=B({serverPath:c||void 0,dbPath:i||void 0}),s=t.filter(e=>e.action==="configured"||e.action==="updated"),o=t.filter(e=>e.action==="failed");let r=`\u{F115D} **Wyrm Auto-Configure Complete**
99
+
100
+ `;r+=`Connected to ${s.length} AI client(s):
101
+ `;for(const e of t){const n=e.action==="configured"?"\u2705":e.action==="updated"?"\u{1F504}":e.action==="skipped"?"\u25CB":"\u274C";r+=`- ${n} ${e.client.icon} ${e.client.name}: ${e.message}
102
+ `}return o.length>0&&(r+=`
103
+ \u26A0\uFE0F ${o.length} client(s) failed. Check errors above.`),r+=`
104
+
105
+ Server: ${U()}
106
+ DB: ${N()}`,r+=`
107
+
108
+ _Switch AIs anytime \u2014 run wyrm_setup again to reconnect._`,{content:[{type:"text",text:r}]}}},{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:b.wyrm_usage,aliases:[],handler:async(u,f)=>{const{cachedResponse:d,getUsageStats:c,responseFingerprints:i,usageLog:a}=f,{last:t,reset:s}=u;if(s)return a.length=0,i.clear(),v.invalidate(),{content:[{type:"text",text:"\u{F115D} Usage counters reset, caches cleared."}]};const o=c(t),r=v.stats();let e=`\u{F115D} **Wyrm Usage Report**
109
+
110
+ `;if(e+=`## Overview${t?` (last ${t} calls)`:""}
111
+ `,e+=`- **Total Calls:** ${o.totalCalls}
112
+ `,e+=`- **Cache Hits:** ${o.cachedCalls} (${o.cacheHitRate})
113
+ `,e+=`- **Tokens In:** ~${o.totalTokensIn.toLocaleString()}
114
+ `,e+=`- **Tokens Out:** ~${o.totalTokensOut.toLocaleString()}
115
+ `,e+=`- **Tokens Saved (cache):** ~${o.tokensSaved.toLocaleString()}
116
+ `,e+=`- **Avg Response:** ${o.avgResponseMs}ms
117
+ `,e+=`- **Active Cache Entries:** ${r.size}
118
+
119
+ `,o.topTools.length>0){e+=`## Top Tools by Token Usage
120
+ `;for(const p of o.topTools)e+=`- **${p.tool}:** ${p.calls} calls, ~${p.tokens.toLocaleString()} tokens
121
+ `;e+=`
122
+ `}const n=o.totalTokensIn/1e6*15,m=o.totalTokensOut/1e6*75,l=o.tokensSaved/1e6*75;return e+=`## Estimated Cost (Claude Opus rates)
123
+ `,e+=`- **Input:** $${n.toFixed(4)}
124
+ `,e+=`- **Output:** $${m.toFixed(4)}
125
+ `,e+=`- **Saved by cache:** $${l.toFixed(4)}
126
+ `,e+=`- **Net cost:** $${(n+m-l).toFixed(4)}`,d(e,!0)}}];export{ue as intelligenceToolSpecs};