wyrm-mcp 7.3.2 → 7.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -14
- package/dist/activation.js +59 -1
- package/dist/agent-daemon.js +281 -4
- package/dist/agent-loop.js +332 -7
- package/dist/analytics.js +236 -13
- package/dist/attribution.js +49 -1
- package/dist/audit.js +457 -2
- package/dist/auto-capture.js +138 -3
- package/dist/auto-orchestrator.js +325 -1
- package/dist/autoconfig.d.ts +50 -0
- package/dist/autoconfig.d.ts.map +1 -1
- package/dist/autoconfig.js +1115 -39
- package/dist/autoconfig.js.map +1 -1
- package/dist/buddy-runner.js +109 -1
- package/dist/buddy.js +564 -14
- package/dist/build-flags.js +15 -1
- package/dist/capabilities.js +183 -3
- package/dist/capture.js +56 -1
- package/dist/causality.js +148 -8
- package/dist/cli.js +281 -20
- package/dist/cloud/cli.js +541 -5
- package/dist/cloud/client.js +221 -1
- package/dist/cloud/crypto.js +85 -1
- package/dist/cloud/machine-id.js +113 -2
- package/dist/cloud/recovery.js +60 -1
- package/dist/cloud/sync-engine.js +543 -7
- package/dist/cloud-backup.js +579 -5
- package/dist/cloud-profile.js +138 -1
- package/dist/cloud-sync-entrypoint.js +47 -1
- package/dist/cloud-sync.js +309 -2
- package/dist/connectors/bridge-source.d.ts +46 -0
- package/dist/connectors/bridge-source.d.ts.map +1 -0
- package/dist/connectors/bridge-source.js +77 -0
- package/dist/connectors/bridge-source.js.map +1 -0
- package/dist/connectors/index.d.ts +24 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +69 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/ingest.d.ts +16 -0
- package/dist/connectors/ingest.d.ts.map +1 -0
- package/dist/connectors/ingest.js +116 -0
- package/dist/connectors/ingest.js.map +1 -0
- package/dist/connectors/types.d.ts +99 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +17 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/constellation.js +168 -12
- package/dist/content-signature.js +45 -1
- package/dist/context-build-budgeted.js +144 -4
- package/dist/context-ranking.js +69 -1
- package/dist/crypto.js +179 -1
- package/dist/daemon-write-endpoint.js +290 -1
- package/dist/daemon-writer.js +406 -2
- package/dist/database.js +1278 -53
- package/dist/deprecations.js +162 -2
- package/dist/design.js +141 -13
- package/dist/event-replication.js +112 -1
- package/dist/events-sse.js +43 -7
- package/dist/events.js +238 -6
- package/dist/failure-patterns.d.ts +107 -0
- package/dist/failure-patterns.d.ts.map +1 -1
- package/dist/failure-patterns.js +924 -43
- package/dist/failure-patterns.js.map +1 -1
- package/dist/federation.js +236 -12
- package/dist/goals.js +101 -13
- package/dist/golden.js +355 -3
- package/dist/handlers/agent.js +165 -4
- package/dist/handlers/alias-adapters.js +129 -1
- package/dist/handlers/aliases.js +171 -1
- package/dist/handlers/audit.js +87 -1
- package/dist/handlers/boundary.js +221 -1
- package/dist/handlers/capture.js +1114 -73
- package/dist/handlers/causality.js +119 -9
- package/dist/handlers/cloud.js +382 -85
- package/dist/handlers/companion.js +459 -28
- package/dist/handlers/datalake.js +187 -7
- package/dist/handlers/dispatch-context.js +22 -0
- package/dist/handlers/entity.js +256 -25
- package/dist/handlers/events.js +335 -16
- package/dist/handlers/failure.d.ts.map +1 -1
- package/dist/handlers/failure.js +408 -13
- package/dist/handlers/failure.js.map +1 -1
- package/dist/handlers/goals.js +296 -4
- package/dist/handlers/intelligence.js +681 -126
- package/dist/handlers/invoicing.js +70 -1
- package/dist/handlers/mcpclient.js +137 -6
- package/dist/handlers/orchestration.js +125 -40
- package/dist/handlers/output-schemas.js +24 -1
- package/dist/handlers/presence.js +99 -3
- package/dist/handlers/project.js +182 -28
- package/dist/handlers/prompts.js +157 -6
- package/dist/handlers/quest.js +224 -4
- package/dist/handlers/recall.js +237 -13
- package/dist/handlers/registry.js +167 -1
- package/dist/handlers/resources.js +288 -1
- package/dist/handlers/review.js +74 -11
- package/dist/handlers/run.js +498 -16
- package/dist/handlers/search.js +338 -15
- package/dist/handlers/session.js +643 -31
- package/dist/handlers/share.js +184 -8
- package/dist/handlers/shims.js +464 -1
- package/dist/handlers/skill.js +449 -67
- package/dist/handlers/survivors.js +120 -1
- package/dist/handlers/symbols.js +109 -8
- package/dist/handlers/syncops.js +302 -4
- package/dist/handlers/types.js +27 -1
- package/dist/harvest.js +191 -5
- package/dist/hours.js +156 -7
- package/dist/http-auth.js +321 -3
- package/dist/http-fast.js +1302 -22
- package/dist/icons.js +47 -1
- package/dist/importers.js +268 -1
- package/dist/index.js +840 -2
- package/dist/indexer.js +145 -4
- package/dist/intelligence.js +261 -31
- package/dist/internal-dispatch.js +212 -3
- package/dist/keyset.js +110 -1
- package/dist/knowledge-graph.js +176 -12
- package/dist/license.js +441 -2
- package/dist/logger.js +199 -2
- package/dist/maintenance.js +148 -2
- package/dist/mcp-client.js +262 -6
- package/dist/memory-artifacts.js +596 -32
- package/dist/migrate-prompt.js +124 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +799 -42
- package/dist/migrations.js.map +1 -1
- package/dist/performance.js +228 -1
- package/dist/presence.js +140 -11
- package/dist/priority-embed.js +164 -5
- package/dist/providers/embedding-provider.js +196 -1
- package/dist/readonly-gate.js +29 -1
- package/dist/receipt.js +43 -1
- package/dist/rehydration.js +157 -9
- package/dist/reindex.js +88 -1
- package/dist/render-target.js +544 -21
- package/dist/render.js +280 -4
- package/dist/repl-guard.js +173 -1
- package/dist/replication-daemon-entrypoint.js +31 -1
- package/dist/replication-daemon.js +262 -2
- package/dist/rerank.js +142 -1
- package/dist/resilience.js +591 -1
- package/dist/reverse-bridge.js +360 -5
- package/dist/security.js +244 -1
- package/dist/session-seen.js +51 -3
- package/dist/setup.js +260 -1
- package/dist/skill-author.js +168 -5
- package/dist/spec-kit.js +191 -1
- package/dist/sqlite-busy.js +154 -1
- package/dist/statusline.js +315 -11
- package/dist/sub-agent.js +262 -13
- package/dist/summarizer.js +139 -13
- package/dist/symbols.js +283 -7
- package/dist/sync.js +359 -5
- package/dist/tasks-dispatch.js +84 -1
- package/dist/tasks.js +282 -1
- package/dist/token-budget.js +143 -1
- package/dist/tool-analytics.js +129 -7
- package/dist/tool-annotations.js +365 -1
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +75 -1
- package/dist/trace-harvest.js +244 -6
- package/dist/types.js +30 -1
- package/dist/ui-dashboard.js +50 -41
- package/dist/ulid.js +81 -1
- package/dist/usage-tracker.js +66 -1
- package/dist/validate.js +129 -1
- package/dist/vault.js +534 -1
- package/dist/vector-init.js +67 -1
- package/dist/vectors.js +184 -3
- package/dist/version-check.js +136 -4
- package/dist/visibility.js +155 -19
- package/dist/wyrm-cli.js +2845 -101
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.d.ts.map +1 -1
- package/dist/wyrm-guard.js +475 -14
- package/dist/wyrm-guard.js.map +1 -1
- package/dist/wyrm-loop.js +150 -3
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +11 -1
- package/dist/wyrm-statusline.js +56 -4
- package/dist/wyrm-ui.js +77 -9
- package/package.json +1 -1
|
@@ -1,126 +1,681 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
3
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
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
|
+
// Coverage compares vectors against the SAME content kinds that get
|
|
383
|
+
// embedded — artifacts/quests/sessions — NOT dataPoints (never
|
|
384
|
+
// embedded), and clamps at 100%. The old denominator omitted
|
|
385
|
+
// artifacts entirely while the numerator counted artifact vectors,
|
|
386
|
+
// so it read >1000% once the corpus was embedded.
|
|
387
|
+
const bt = (vectorStats.byType || {});
|
|
388
|
+
const embeddedTracked = (bt.artifact || 0) + (bt.quest || 0) + (bt.session || 0);
|
|
389
|
+
const totalEmbeddable = stats.artifacts + stats.quests + stats.sessions;
|
|
390
|
+
const vectorCoverage = totalEmbeddable > 0
|
|
391
|
+
? Math.min(100, (embeddedTracked / totalEmbeddable) * 100).toFixed(1)
|
|
392
|
+
: '0';
|
|
393
|
+
vectorStatsText =
|
|
394
|
+
`\n**Vectors:**\n` +
|
|
395
|
+
`- **Provider:** ${vectorStats.provider} (${vectorStats.model})\n` +
|
|
396
|
+
`- **Embeddings:** ${vectorStats.total} vectors\n` +
|
|
397
|
+
`- **Coverage:** ${vectorCoverage}% of embeddable content\n` +
|
|
398
|
+
`- **By Type:** ${Object.entries(vectorStats.byType).map(([t, c]) => `${t}: ${c}`).join(', ')}\n`;
|
|
399
|
+
}
|
|
400
|
+
const response = cachedResponse(` **Wyrm Statistics**\n\n` +
|
|
401
|
+
`- **Projects:** ${stats.projects}\n` +
|
|
402
|
+
`- **Sessions:** ${stats.sessions}\n` +
|
|
403
|
+
`- **Quests:** ${stats.quests}\n` +
|
|
404
|
+
`- **Data Points:** ${stats.dataPoints}\n` +
|
|
405
|
+
`- **Active Tokens:** ~${stats.totalTokens.toLocaleString()}\n` +
|
|
406
|
+
`- **Database Size:** ${stats.dbSize}\n` +
|
|
407
|
+
vectorStatsText +
|
|
408
|
+
`\n**Cache:** ${cacheStats.size} entries | Hit rate: ${usage.cacheHitRate}\n` +
|
|
409
|
+
`**Usage:** ${usage.totalCalls} calls | ~${usage.tokensSaved.toLocaleString()} tokens saved by cache`);
|
|
410
|
+
if (cacheKey)
|
|
411
|
+
cache.set(cacheKey, response, 15000);
|
|
412
|
+
return response;
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "wyrm_vector_setup",
|
|
417
|
+
description: "Configure embedding provider and download models. Supports local, OpenAI, and Ollama.",
|
|
418
|
+
inputSchema: {
|
|
419
|
+
type: "object",
|
|
420
|
+
properties: {
|
|
421
|
+
provider: { type: "string", enum: ["auto", "local", "openai", "ollama", "none"], description: "Provider: auto-detect, local hash, OpenAI API, Ollama, or none" },
|
|
422
|
+
model: { type: "string", description: "Model name (e.g., 'text-embedding-3-small' for OpenAI, 'nomic-embed-text' for Ollama)" },
|
|
423
|
+
apiKey: { type: "string", description: "OpenAI API key (if using openai provider)" },
|
|
424
|
+
ollamaUrl: { type: "string", description: "Ollama URL (default: http://localhost:11434)" },
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
annotations: TOOL_ANNOTATIONS["wyrm_vector_setup"],
|
|
428
|
+
aliases: [],
|
|
429
|
+
handler: async (args, ctx) => {
|
|
430
|
+
const { db, indexingPipeline, memory, setVectorStore, vectorStore } = ctx;
|
|
431
|
+
const { provider: prov, model: mod, apiKey: key, ollamaUrl: url } = args;
|
|
432
|
+
// Build new provider config
|
|
433
|
+
const providerConfig = {
|
|
434
|
+
provider: (prov || 'auto'),
|
|
435
|
+
model: mod,
|
|
436
|
+
apiKey: key,
|
|
437
|
+
ollamaUrl: url,
|
|
438
|
+
};
|
|
439
|
+
try {
|
|
440
|
+
// Create new provider to test
|
|
441
|
+
const testProvider = createProvider(providerConfig);
|
|
442
|
+
const isReady = await testProvider.isReady();
|
|
443
|
+
if (!isReady && providerConfig.provider !== 'none') {
|
|
444
|
+
return {
|
|
445
|
+
content: [{
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `❌ **Provider Not Ready**\n\nCouldn't connect to ${testProvider.name}. Check your configuration and try again.`
|
|
448
|
+
}],
|
|
449
|
+
isError: true
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
// Update global vector store
|
|
453
|
+
const __vs = createVectorStore(providerConfig, db.getDatabase());
|
|
454
|
+
setVectorStore(__vs);
|
|
455
|
+
memory.setVectorStore(__vs); // keep hybrid recall pointed at the live store
|
|
456
|
+
if (indexingPipeline)
|
|
457
|
+
indexingPipeline.updateStore(__vs);
|
|
458
|
+
const stats = __vs.getStats();
|
|
459
|
+
return {
|
|
460
|
+
content: [{
|
|
461
|
+
type: "text",
|
|
462
|
+
text: `✅ **Vector Setup Complete**\n\n` +
|
|
463
|
+
`- **Provider:** ${testProvider.name}\n` +
|
|
464
|
+
`- **Model:** ${testProvider.model}\n` +
|
|
465
|
+
`- **Dimensions:** ${testProvider.dimensions}\n` +
|
|
466
|
+
`- **Status:** ${isReady ? 'Ready' : 'Ready (will auto-connect)'}\n\n` +
|
|
467
|
+
`_Reindex your projects with wyrm_reindex to generate embeddings._`
|
|
468
|
+
}]
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
return {
|
|
473
|
+
content: [{
|
|
474
|
+
type: "text",
|
|
475
|
+
text: `❌ **Vector Setup Failed**\n\n${String(err)}`
|
|
476
|
+
}],
|
|
477
|
+
isError: true
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "wyrm_reindex",
|
|
484
|
+
description: "Rebuild embeddings for a project or all projects. Regenerates vectors for sessions, quests, data, and memory artifacts (the latter power hybrid recall).",
|
|
485
|
+
inputSchema: {
|
|
486
|
+
type: "object",
|
|
487
|
+
properties: {
|
|
488
|
+
projectPath: { type: "string", description: "Project to reindex, or leave empty for all projects" },
|
|
489
|
+
dryRun: { type: "boolean", description: "Show what would be indexed without making changes" },
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
annotations: TOOL_ANNOTATIONS["wyrm_reindex"],
|
|
493
|
+
aliases: [],
|
|
494
|
+
handler: async (args, ctx) => {
|
|
495
|
+
const { db, failures, vectorStore } = ctx;
|
|
496
|
+
const { projectPath: ppath, dryRun } = args;
|
|
497
|
+
if (!vectorStore) {
|
|
498
|
+
return {
|
|
499
|
+
content: [{
|
|
500
|
+
type: "text",
|
|
501
|
+
text: `❌ **Vector Indexing Disabled**\n\nRun wyrm_vector_setup first to enable vector search.`
|
|
502
|
+
}],
|
|
503
|
+
isError: true
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
let projectIds = [];
|
|
507
|
+
if (ppath) {
|
|
508
|
+
const proj = db.getProject(ppath);
|
|
509
|
+
if (!proj) {
|
|
510
|
+
return {
|
|
511
|
+
content: [{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: `❌ **Project Not Found:** ${ppath}`
|
|
514
|
+
}],
|
|
515
|
+
isError: true
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
projectIds = [proj.id];
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
const allProjs = db.getAllProjects(1000);
|
|
522
|
+
projectIds = allProjs.map(p => p.id);
|
|
523
|
+
}
|
|
524
|
+
// v7 F3 (T023): the project loop moved VERBATIM to src/reindex.ts
|
|
525
|
+
// (reindexProjects) so the new `wyrm index rebuild` CLI subcommand and
|
|
526
|
+
// this tool run the SAME code path. Per-row failures still log here.
|
|
527
|
+
const { indexed, skipped } = await reindexProjects(db.getDatabase(), vectorStore, projectIds, {
|
|
528
|
+
dryRun,
|
|
529
|
+
onError: (message, context) => logger.error(message, context),
|
|
530
|
+
});
|
|
531
|
+
return {
|
|
532
|
+
content: [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: ` **Reindexing ${dryRun ? '(Dry Run)' : 'Complete'}**\n\n` +
|
|
535
|
+
`- **Projects:** ${projectIds.length}\n` +
|
|
536
|
+
`- **Indexed:** ${indexed}\n` +
|
|
537
|
+
`- **Skipped:** ${skipped}\n\n` +
|
|
538
|
+
(dryRun ? '_Run without dryRun to actually index._' : '_Vectors are now up to date._')
|
|
539
|
+
}]
|
|
540
|
+
};
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: "wyrm_maintenance",
|
|
545
|
+
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.",
|
|
546
|
+
inputSchema: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {
|
|
549
|
+
vacuum: { type: "boolean", description: "Reclaim space" },
|
|
550
|
+
archiveDays: { type: "number", description: "Archive sessions > N days" },
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
annotations: TOOL_ANNOTATIONS["wyrm_maintenance"],
|
|
554
|
+
aliases: [],
|
|
555
|
+
handler: async (args, ctx) => {
|
|
556
|
+
const { db, failures, presence, sessionSeen } = ctx;
|
|
557
|
+
const a = args || {};
|
|
558
|
+
const { vacuum, archiveDays } = args;
|
|
559
|
+
// v7 F3 (T023): the step sequence moved VERBATIM to
|
|
560
|
+
// src/maintenance.ts (runMaintenance) so the new `wyrm maintenance`
|
|
561
|
+
// CLI subcommand and this tool run the SAME code path. The 6.x text
|
|
562
|
+
// is reassembled byte-identically below (every line carried "- " and
|
|
563
|
+
// a trailing newline).
|
|
564
|
+
// v7 F3 (T029): + presence — stale-claim/presence eviction now rides
|
|
565
|
+
// maintenance (reap previously ran only inline at claim time).
|
|
566
|
+
const report = runMaintenance({ db, sessionSeen: sessionSeen(), failures, presence }, { vacuum, archiveDays });
|
|
567
|
+
const text = ' **Maintenance Complete**\n\n' +
|
|
568
|
+
report.lines.map((line) => `- ${line}\n`).join('') +
|
|
569
|
+
`\n**New Database Size:** ${report.dbSize}`;
|
|
570
|
+
return { content: [{ type: "text", text }] };
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "wyrm_setup",
|
|
575
|
+
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.",
|
|
576
|
+
inputSchema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {
|
|
579
|
+
action: { type: "string", enum: ["configure", "check", "remove"], description: "Action: configure (default), check status, or remove from all" },
|
|
580
|
+
serverPath: { type: "string", description: "Override Wyrm server path (auto-detected if empty)" },
|
|
581
|
+
dbPath: { type: "string", description: "Override database path (default: ~/.wyrm/wyrm.db)" },
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
annotations: TOOL_ANNOTATIONS["wyrm_setup"],
|
|
585
|
+
aliases: [],
|
|
586
|
+
handler: async (args, ctx) => {
|
|
587
|
+
const { action, serverPath, dbPath } = args;
|
|
588
|
+
const setupAction = action || 'configure';
|
|
589
|
+
if (setupAction === 'check') {
|
|
590
|
+
return {
|
|
591
|
+
content: [{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: getStatusSummary()
|
|
594
|
+
}]
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
if (setupAction === 'remove') {
|
|
598
|
+
const results = removeFromAll();
|
|
599
|
+
const removed = results.filter(r => r.action === 'configured');
|
|
600
|
+
const text = ` **Wyrm Removed**\n\n` +
|
|
601
|
+
`Removed from ${removed.length} AI client(s):\n` +
|
|
602
|
+
results.map(r => `- ${r.client.icon} ${r.client.name}: ${r.message}`).join('\n') +
|
|
603
|
+
`\n\nRun wyrm_setup again to reconnect.`;
|
|
604
|
+
return { content: [{ type: "text", text }] };
|
|
605
|
+
}
|
|
606
|
+
// Default: configure
|
|
607
|
+
const results = autoConfigureAll({
|
|
608
|
+
serverPath: serverPath || undefined,
|
|
609
|
+
dbPath: dbPath || undefined,
|
|
610
|
+
});
|
|
611
|
+
const configured = results.filter(r => r.action === 'configured' || r.action === 'updated');
|
|
612
|
+
const failed = results.filter(r => r.action === 'failed');
|
|
613
|
+
let text = ` **Wyrm Auto-Configure Complete**\n\n`;
|
|
614
|
+
text += `Connected to ${configured.length} AI client(s):\n`;
|
|
615
|
+
for (const r of results) {
|
|
616
|
+
const icon = r.action === 'configured' ? '✅' :
|
|
617
|
+
r.action === 'updated' ? '🔄' :
|
|
618
|
+
r.action === 'skipped' ? '○' : '❌';
|
|
619
|
+
text += `- ${icon} ${r.client.icon} ${r.client.name}: ${r.message}\n`;
|
|
620
|
+
}
|
|
621
|
+
if (failed.length > 0) {
|
|
622
|
+
text += `\n⚠️ ${failed.length} client(s) failed. Check errors above.`;
|
|
623
|
+
}
|
|
624
|
+
text += `\n\nServer: ${findWyrmServerPath()}\nDB: ${getDefaultDbPath()}`;
|
|
625
|
+
text += `\n\n_Switch AIs anytime — run wyrm_setup again to reconnect._`;
|
|
626
|
+
return { content: [{ type: "text", text }] };
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "wyrm_usage",
|
|
631
|
+
description: "View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",
|
|
632
|
+
inputSchema: {
|
|
633
|
+
type: "object",
|
|
634
|
+
properties: {
|
|
635
|
+
last: { type: "number", description: "Show stats for last N calls (default: all)" },
|
|
636
|
+
reset: { type: "boolean", description: "Reset usage counters" },
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
annotations: TOOL_ANNOTATIONS["wyrm_usage"],
|
|
640
|
+
aliases: [],
|
|
641
|
+
handler: async (args, ctx) => {
|
|
642
|
+
const { cachedResponse, getUsageStats, responseFingerprints, usageLog } = ctx;
|
|
643
|
+
const { last, reset } = args;
|
|
644
|
+
if (reset) {
|
|
645
|
+
usageLog.length = 0;
|
|
646
|
+
responseFingerprints.clear();
|
|
647
|
+
cache.invalidate();
|
|
648
|
+
return { content: [{ type: "text", text: " Usage counters reset, caches cleared." }] };
|
|
649
|
+
}
|
|
650
|
+
const usage = getUsageStats(last);
|
|
651
|
+
const cacheStats = cache.stats();
|
|
652
|
+
let text = ` **Wyrm Usage Report**\n\n`;
|
|
653
|
+
text += `## Overview${last ? ` (last ${last} calls)` : ''}\n`;
|
|
654
|
+
text += `- **Total Calls:** ${usage.totalCalls}\n`;
|
|
655
|
+
text += `- **Cache Hits:** ${usage.cachedCalls} (${usage.cacheHitRate})\n`;
|
|
656
|
+
text += `- **Tokens In:** ~${usage.totalTokensIn.toLocaleString()}\n`;
|
|
657
|
+
text += `- **Tokens Out:** ~${usage.totalTokensOut.toLocaleString()}\n`;
|
|
658
|
+
text += `- **Tokens Saved (cache):** ~${usage.tokensSaved.toLocaleString()}\n`;
|
|
659
|
+
text += `- **Avg Response:** ${usage.avgResponseMs}ms\n`;
|
|
660
|
+
text += `- **Active Cache Entries:** ${cacheStats.size}\n\n`;
|
|
661
|
+
if (usage.topTools.length > 0) {
|
|
662
|
+
text += `## Top Tools by Token Usage\n`;
|
|
663
|
+
for (const t of usage.topTools) {
|
|
664
|
+
text += `- **${t.tool}:** ${t.calls} calls, ~${t.tokens.toLocaleString()} tokens\n`;
|
|
665
|
+
}
|
|
666
|
+
text += '\n';
|
|
667
|
+
}
|
|
668
|
+
// Cost estimate (Claude Opus pricing: $15/M input, $75/M output)
|
|
669
|
+
const costInput = (usage.totalTokensIn / 1_000_000) * 15;
|
|
670
|
+
const costOutput = (usage.totalTokensOut / 1_000_000) * 75;
|
|
671
|
+
const costSaved = (usage.tokensSaved / 1_000_000) * 75;
|
|
672
|
+
text += `## Estimated Cost (Claude Opus rates)\n`;
|
|
673
|
+
text += `- **Input:** $${costInput.toFixed(4)}\n`;
|
|
674
|
+
text += `- **Output:** $${costOutput.toFixed(4)}\n`;
|
|
675
|
+
text += `- **Saved by cache:** $${costSaved.toFixed(4)}\n`;
|
|
676
|
+
text += `- **Net cost:** $${(costInput + costOutput - costSaved).toFixed(4)}`;
|
|
677
|
+
return cachedResponse(text, true); // ephemeral — don't cache the usage report itself
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
];
|
|
681
|
+
//# sourceMappingURL=intelligence.js.map
|