wyrm-mcp 7.2.1 → 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 (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. 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};