@nxuss/lemma 1.10.0 → 1.12.0
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 +92 -819
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +21 -2
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/infra/mcp-tools.d.ts +7 -0
- package/dist/cjs/infra/mcp-tools.d.ts.map +1 -1
- package/dist/cjs/infra/mcp-tools.js +22 -0
- package/dist/cjs/infra/mcp-tools.js.map +1 -1
- package/dist/cjs/mcp/index.js +32 -7
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/mcp/prompts.d.ts +1 -0
- package/dist/cjs/mcp/prompts.d.ts.map +1 -1
- package/dist/cjs/mcp/prompts.js +98 -0
- package/dist/cjs/mcp/prompts.js.map +1 -1
- package/dist/cjs/mcp/resources.d.ts +2 -0
- package/dist/cjs/mcp/resources.d.ts.map +1 -1
- package/dist/cjs/mcp/resources.js +26 -0
- package/dist/cjs/mcp/resources.js.map +1 -1
- package/dist/cjs/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/cjs/mcp/tool-profiles.js +64 -45
- package/dist/cjs/mcp/tool-profiles.js.map +1 -1
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +309 -65
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/proxy/Gatekeeper.d.ts +29 -0
- package/dist/cjs/proxy/Gatekeeper.d.ts.map +1 -1
- package/dist/cjs/proxy/Gatekeeper.js +122 -1
- package/dist/cjs/proxy/Gatekeeper.js.map +1 -1
- package/dist/cjs/utils/AstSymbolCache.d.ts +29 -0
- package/dist/cjs/utils/AstSymbolCache.d.ts.map +1 -0
- package/dist/cjs/utils/AstSymbolCache.js +66 -0
- package/dist/cjs/utils/AstSymbolCache.js.map +1 -0
- package/dist/cjs/utils/ContextSqueezer.d.ts +24 -4
- package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.js +32 -3
- package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
- package/dist/cjs/utils/OutputLedger.d.ts.map +1 -1
- package/dist/cjs/utils/OutputLedger.js +20 -2
- package/dist/cjs/utils/OutputLedger.js.map +1 -1
- package/dist/cjs/utils/ReadWorkspaceCache.d.ts.map +1 -1
- package/dist/cjs/utils/ReadWorkspaceCache.js +25 -2
- package/dist/cjs/utils/ReadWorkspaceCache.js.map +1 -1
- package/dist/cjs/utils/RoiLedger.d.ts +31 -0
- package/dist/cjs/utils/RoiLedger.d.ts.map +1 -0
- package/dist/cjs/utils/RoiLedger.js +113 -0
- package/dist/cjs/utils/RoiLedger.js.map +1 -0
- package/dist/cjs/utils/SharedLedgerStore.d.ts +26 -0
- package/dist/cjs/utils/SharedLedgerStore.d.ts.map +1 -0
- package/dist/cjs/utils/SharedLedgerStore.js +130 -0
- package/dist/cjs/utils/SharedLedgerStore.js.map +1 -0
- package/dist/cjs/utils/SpeculativeWarmer.d.ts +24 -0
- package/dist/cjs/utils/SpeculativeWarmer.d.ts.map +1 -0
- package/dist/cjs/utils/SpeculativeWarmer.js +73 -0
- package/dist/cjs/utils/SpeculativeWarmer.js.map +1 -0
- package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
- package/dist/cjs/utils/reportSavings.js +11 -0
- package/dist/cjs/utils/reportSavings.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +21 -2
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/infra/mcp-tools.d.ts +7 -0
- package/dist/esm/infra/mcp-tools.d.ts.map +1 -1
- package/dist/esm/infra/mcp-tools.js +22 -0
- package/dist/esm/infra/mcp-tools.js.map +1 -1
- package/dist/esm/mcp/index.js +34 -9
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/prompts.d.ts +1 -0
- package/dist/esm/mcp/prompts.d.ts.map +1 -1
- package/dist/esm/mcp/prompts.js +95 -1
- package/dist/esm/mcp/prompts.js.map +1 -1
- package/dist/esm/mcp/resources.d.ts +2 -0
- package/dist/esm/mcp/resources.d.ts.map +1 -1
- package/dist/esm/mcp/resources.js +27 -2
- package/dist/esm/mcp/resources.js.map +1 -1
- package/dist/esm/mcp/tool-profiles.d.ts.map +1 -1
- package/dist/esm/mcp/tool-profiles.js +64 -45
- package/dist/esm/mcp/tool-profiles.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +309 -65
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/proxy/Gatekeeper.d.ts +29 -0
- package/dist/esm/proxy/Gatekeeper.d.ts.map +1 -1
- package/dist/esm/proxy/Gatekeeper.js +120 -0
- package/dist/esm/proxy/Gatekeeper.js.map +1 -1
- package/dist/esm/utils/AstSymbolCache.d.ts +29 -0
- package/dist/esm/utils/AstSymbolCache.d.ts.map +1 -0
- package/dist/esm/utils/AstSymbolCache.js +58 -0
- package/dist/esm/utils/AstSymbolCache.js.map +1 -0
- package/dist/esm/utils/ContextSqueezer.d.ts +24 -4
- package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/esm/utils/ContextSqueezer.js +32 -3
- package/dist/esm/utils/ContextSqueezer.js.map +1 -1
- package/dist/esm/utils/OutputLedger.d.ts.map +1 -1
- package/dist/esm/utils/OutputLedger.js +20 -2
- package/dist/esm/utils/OutputLedger.js.map +1 -1
- package/dist/esm/utils/ReadWorkspaceCache.d.ts.map +1 -1
- package/dist/esm/utils/ReadWorkspaceCache.js +25 -2
- package/dist/esm/utils/ReadWorkspaceCache.js.map +1 -1
- package/dist/esm/utils/RoiLedger.d.ts +31 -0
- package/dist/esm/utils/RoiLedger.d.ts.map +1 -0
- package/dist/esm/utils/RoiLedger.js +104 -0
- package/dist/esm/utils/RoiLedger.js.map +1 -0
- package/dist/esm/utils/SharedLedgerStore.d.ts +26 -0
- package/dist/esm/utils/SharedLedgerStore.d.ts.map +1 -0
- package/dist/esm/utils/SharedLedgerStore.js +122 -0
- package/dist/esm/utils/SharedLedgerStore.js.map +1 -0
- package/dist/esm/utils/SpeculativeWarmer.d.ts +24 -0
- package/dist/esm/utils/SpeculativeWarmer.d.ts.map +1 -0
- package/dist/esm/utils/SpeculativeWarmer.js +66 -0
- package/dist/esm/utils/SpeculativeWarmer.js.map +1 -0
- package/dist/esm/utils/reportSavings.d.ts.map +1 -1
- package/dist/esm/utils/reportSavings.js +11 -0
- package/dist/esm/utils/reportSavings.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/mcp/tools.js
CHANGED
|
@@ -42,6 +42,8 @@ import { checkAlreadySent, recordSent, getLastEmitted } from "../utils/ReadWorks
|
|
|
42
42
|
import { shouldEmitDelta } from "../utils/OutputDelta.js";
|
|
43
43
|
import { ledgerKey, checkOutput, recordOutput as recordLedgerOutput } from "../utils/OutputLedger.js";
|
|
44
44
|
import { buildDepGraphMap, bfsDepGraph } from "../utils/DepGraph.js";
|
|
45
|
+
import { getOrExtractSymbols } from "../utils/AstSymbolCache.js";
|
|
46
|
+
import { warmNeighbors, registerSymbolExtractor } from "../utils/SpeculativeWarmer.js";
|
|
45
47
|
import { findAffectedTests, formatAffectedTests } from "../utils/AffectedTests.js";
|
|
46
48
|
import { mapSqueezedToOriginal, withLineNumbers, sequentialNumbers } from "../utils/LineNumberMap.js";
|
|
47
49
|
import { fileOutline, formatOutline } from "../utils/FileOutline.js";
|
|
@@ -90,9 +92,55 @@ const PRO_GATE_MESSAGE = [
|
|
|
90
92
|
"🚀 Get Lemma Pro:",
|
|
91
93
|
" https://lemma.nxus.studio/upgrade",
|
|
92
94
|
].join("\n");
|
|
95
|
+
// ── Elicitation (spec 2026-07-28's recommended pattern for destructive actions) ────
|
|
96
|
+
//
|
|
97
|
+
// Set once by setupToolsHandlers so the standalone handler functions below — which don't
|
|
98
|
+
// otherwise see the Server instance — can ask the connected client to confirm before a
|
|
99
|
+
// mutating tool runs.
|
|
100
|
+
let mcpServerRef = null;
|
|
101
|
+
/**
|
|
102
|
+
* Ask the client to confirm a destructive action before it happens.
|
|
103
|
+
*
|
|
104
|
+
* Mirrors the defensive shape of trySamplingContext() in src/mcp/index.ts: most clients
|
|
105
|
+
* today don't support elicitation, so any failure (unsupported capability, timeout,
|
|
106
|
+
* malformed response) must fall back to "proceed without confirmation" rather than block
|
|
107
|
+
* or error out the tool. This is a courtesy prompt for clients that support it, not a
|
|
108
|
+
* security boundary — the allowlist/path-safety checks each handler already does are that.
|
|
109
|
+
*/
|
|
110
|
+
async function tryElicitConfirmation(summary) {
|
|
111
|
+
if (!mcpServerRef)
|
|
112
|
+
return { supported: false, confirmed: true };
|
|
113
|
+
try {
|
|
114
|
+
const result = await mcpServerRef.elicitInput({
|
|
115
|
+
message: summary,
|
|
116
|
+
requestedSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
confirm: {
|
|
120
|
+
type: "boolean",
|
|
121
|
+
title: "Proceed?",
|
|
122
|
+
description: "Confirm this action should be applied.",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ["confirm"],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
if (result.action !== "accept") {
|
|
129
|
+
// "decline" or "cancel" — the user (or client policy) said no.
|
|
130
|
+
return { supported: true, confirmed: false };
|
|
131
|
+
}
|
|
132
|
+
const confirmed = result.content?.confirm !== false;
|
|
133
|
+
return { supported: true, confirmed };
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
logWarn("elicitation", "Client does not support elicitation (or the request failed) — proceeding without confirmation");
|
|
137
|
+
return { supported: false, confirmed: true };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
93
140
|
const toolDefinitions = [
|
|
94
141
|
{
|
|
95
142
|
name: "scrub_privacy",
|
|
143
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
96
144
|
description: "Mask sensitive data (PII, API Keys, Credentials) from a text block using Lemma's Privacy Firewall.",
|
|
97
145
|
inputSchema: {
|
|
98
146
|
type: "object",
|
|
@@ -104,6 +152,7 @@ const toolDefinitions = [
|
|
|
104
152
|
},
|
|
105
153
|
{
|
|
106
154
|
name: "search_memory",
|
|
155
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
107
156
|
description: "Search Lemma's semantic memory (The Brain) before investigating something from scratch — retrieves past solutions, fixes, and context from ALL your projects globally. Results are split into 'fresh' (safe to reuse — either untracked general knowledge, or every file it depended on still hashes the same) and 'stale' (a similar question was answered before, but a tracked file changed since — re-verify against current state before reusing). Never treat a stale result as current.",
|
|
108
157
|
inputSchema: {
|
|
109
158
|
type: "object",
|
|
@@ -121,6 +170,7 @@ const toolDefinitions = [
|
|
|
121
170
|
},
|
|
122
171
|
{
|
|
123
172
|
name: "store_memory",
|
|
173
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
124
174
|
description: "Persist a technical solution, bug fix, architecture decision, or key fact into Lemma's Brain — so future questions on the same topic (even phrased differently) don't require re-investigating the repo. Pass filePaths for anything derived from specific files (e.g. 'where is X implemented', 'how does Y work') so the memory auto-invalidates the moment those files change, instead of risking a stale answer being reused. If the answer is really about one function/class rather than the whole file, pass `symbols` instead (or in addition) so an unrelated edit elsewhere in that file doesn't stale it out. Pass outcome='failed' for an approach that was tried and did NOT work — that's just as worth remembering as a fix, so the Brain can warn 'already tried, didn't work' instead of only ever suggesting reuse.",
|
|
125
175
|
inputSchema: {
|
|
126
176
|
type: "object",
|
|
@@ -172,6 +222,7 @@ const toolDefinitions = [
|
|
|
172
222
|
},
|
|
173
223
|
{
|
|
174
224
|
name: "get_routing_advice",
|
|
225
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
175
226
|
description: "Analyzes a prompt and suggests the best model based on Lemma's Complexity Router.",
|
|
176
227
|
inputSchema: {
|
|
177
228
|
type: "object",
|
|
@@ -181,9 +232,20 @@ const toolDefinitions = [
|
|
|
181
232
|
},
|
|
182
233
|
required: ["prompt"],
|
|
183
234
|
},
|
|
235
|
+
outputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
recommendedModel: { type: "string", description: "The model the router recommends" },
|
|
239
|
+
complexity: { type: "string", enum: ["low", "high"], description: "Estimated complexity of the prompt" },
|
|
240
|
+
intendedModel: { type: ["string", "null"], description: "The model passed in intended_model, or null if omitted" },
|
|
241
|
+
switched: { type: "boolean", description: "true if recommendedModel differs from intendedModel" },
|
|
242
|
+
},
|
|
243
|
+
required: ["recommendedModel", "complexity", "switched"],
|
|
244
|
+
},
|
|
184
245
|
},
|
|
185
246
|
{
|
|
186
247
|
name: "auto_heal",
|
|
248
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
187
249
|
description: "Diagnose and auto-heal the latest local server crash registered in Lemma's context logs.",
|
|
188
250
|
inputSchema: {
|
|
189
251
|
type: "object",
|
|
@@ -194,6 +256,7 @@ const toolDefinitions = [
|
|
|
194
256
|
},
|
|
195
257
|
{
|
|
196
258
|
name: "read_workspace_file",
|
|
259
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
197
260
|
description: "Read a file inside the workspace. Compresses comments/whitespace and scrubs API keys. Every line carries its REAL line number in the original file (compression removes lines, it never renumbers them), so you can cite file:line and patch precisely without re-reading. The header lists which imports were compressed away. Use offset/limit to read a line range of a large file. If this exact view was already read this session and hasn't changed on disk, returns an UNCHANGED marker plus a symbol outline. If it changed only slightly, returns a CHANGED marker with a unified diff to apply to the copy you already hold. Either way, pass force:true to get the full file when it is no longer in your context.",
|
|
198
261
|
inputSchema: {
|
|
199
262
|
type: "object",
|
|
@@ -210,6 +273,7 @@ const toolDefinitions = [
|
|
|
210
273
|
},
|
|
211
274
|
{
|
|
212
275
|
name: "write_workspace_file",
|
|
276
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
213
277
|
description: "Write full contents to a file inside the workspace. Creates parent directories automatically.",
|
|
214
278
|
inputSchema: {
|
|
215
279
|
type: "object",
|
|
@@ -222,6 +286,7 @@ const toolDefinitions = [
|
|
|
222
286
|
},
|
|
223
287
|
{
|
|
224
288
|
name: "create_workspace_file",
|
|
289
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
225
290
|
description: "Create a NEW file in the workspace. Fails immediately if the file already exists — use write_workspace_file or apply_workspace_patch to modify existing files. Creates parent directories automatically. Returns a minimal token-free ACK: no file content is echoed back, saving provider output tokens.",
|
|
226
291
|
inputSchema: {
|
|
227
292
|
type: "object",
|
|
@@ -234,6 +299,7 @@ const toolDefinitions = [
|
|
|
234
299
|
},
|
|
235
300
|
{
|
|
236
301
|
name: "apply_workspace_patch",
|
|
302
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
237
303
|
description: "Apply a smart search-and-replace patch to an existing file. Falls back through whitespace-normalized, indent-anchored, and fuzzy matching if the exact block isn't found — so a searchContent built from a compacted read still applies. Safe against duplicate matches. On total failure, the error includes a diff against the closest candidate block.",
|
|
238
304
|
inputSchema: {
|
|
239
305
|
type: "object",
|
|
@@ -247,6 +313,7 @@ const toolDefinitions = [
|
|
|
247
313
|
},
|
|
248
314
|
{
|
|
249
315
|
name: "run_workspace_command",
|
|
316
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
250
317
|
description: "Execute a bash command in the workspace root. Default timeout 120s — raise it with timeoutMs for a full test or build run (max 600s). Captures both stdout and stderr, and returns whatever was produced even if the command times out. Long test/build/lint output is distilled deterministically — for jest, vitest and tsc it returns the failing tests or diagnostics with their locations and reasons, dropping code frames and node_modules stack frames; anything else falls back to head/tail plus error-matching lines. The complete output is always stored first and the reply carries a handle: nothing is lost, use output_region to retrieve any part verbatim. Pass raw:true to skip distillation.",
|
|
251
318
|
inputSchema: {
|
|
252
319
|
type: "object",
|
|
@@ -268,6 +335,7 @@ const toolDefinitions = [
|
|
|
268
335
|
},
|
|
269
336
|
{
|
|
270
337
|
name: "output_region",
|
|
338
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
271
339
|
description: "Retrieve any part of a command output previously stored by run_workspace_command, verbatim. This is what makes distillation lossless — use it when the distilled view isn't enough. Pass all:true for the whole output, section for one named block (a failing test title or a file path, as listed in the distilled view), or startLine/endLine for a range.",
|
|
272
340
|
inputSchema: {
|
|
273
341
|
type: "object",
|
|
@@ -283,6 +351,7 @@ const toolDefinitions = [
|
|
|
283
351
|
},
|
|
284
352
|
{
|
|
285
353
|
name: "list_workspace_dir",
|
|
354
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
286
355
|
description: "List files and subdirectories recursively to navigate the repository structure.",
|
|
287
356
|
inputSchema: {
|
|
288
357
|
type: "object",
|
|
@@ -294,6 +363,7 @@ const toolDefinitions = [
|
|
|
294
363
|
},
|
|
295
364
|
{
|
|
296
365
|
name: "search_workspace",
|
|
366
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
297
367
|
description: "Perform a fast local text search (grep) across all files in the project workspace. Respects .gitignore, skips binaries and files over 500KB, groups results by file (capped per file), and stops at maxResults. For code files (.ts/.tsx/.js/.jsx), matches inside the same function/class/method are collapsed into one entry with its signature and line range instead of raw duplicate lines. Repeating an identical query while the workspace is unchanged returns the cached result instantly instead of re-scanning.",
|
|
298
368
|
inputSchema: {
|
|
299
369
|
type: "object",
|
|
@@ -307,6 +377,7 @@ const toolDefinitions = [
|
|
|
307
377
|
},
|
|
308
378
|
{
|
|
309
379
|
name: "squeeze_prompt",
|
|
380
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
310
381
|
description: "Compress code blocks, comments, and boilerplate in any prompt. Saves up to 80% tokens.",
|
|
311
382
|
inputSchema: {
|
|
312
383
|
type: "object",
|
|
@@ -319,6 +390,7 @@ const toolDefinitions = [
|
|
|
319
390
|
},
|
|
320
391
|
{
|
|
321
392
|
name: "get_project_onboarding",
|
|
393
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
322
394
|
description: "Fetch dynamic architectural and stack overview of the current project in markdown.",
|
|
323
395
|
inputSchema: {
|
|
324
396
|
type: "object",
|
|
@@ -327,6 +399,7 @@ const toolDefinitions = [
|
|
|
327
399
|
},
|
|
328
400
|
{
|
|
329
401
|
name: "get_project_history",
|
|
402
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
330
403
|
description: "Answers 'what have we done in this project': merges recent git commits, session checkpoints, and The Brain's memories (decisions, fixes, prior PR reviews) for the current project into one narrative. Use this instead of piecing the same picture together from search_memory + git log + reading checkpoint files separately.",
|
|
331
404
|
inputSchema: {
|
|
332
405
|
type: "object",
|
|
@@ -337,6 +410,7 @@ const toolDefinitions = [
|
|
|
337
410
|
},
|
|
338
411
|
{
|
|
339
412
|
name: "get_ast_hologram",
|
|
413
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
340
414
|
description: "Generate a dense, token-efficient Holographic AST Map of the workspace using the TypeScript compiler. Returns structured JSON of all exported symbols with file paths and line numbers.",
|
|
341
415
|
inputSchema: {
|
|
342
416
|
type: "object",
|
|
@@ -353,6 +427,7 @@ const toolDefinitions = [
|
|
|
353
427
|
},
|
|
354
428
|
{
|
|
355
429
|
name: "validate_patch_sandbox",
|
|
430
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
356
431
|
description: "Validate proposed code in isolated sandbox via tsc + syntax check before applying.",
|
|
357
432
|
inputSchema: {
|
|
358
433
|
type: "object",
|
|
@@ -365,6 +440,7 @@ const toolDefinitions = [
|
|
|
365
440
|
},
|
|
366
441
|
{
|
|
367
442
|
name: "query_hybrid_consensus",
|
|
443
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
368
444
|
description: "Search The Brain before reasoning. Brain HIT returns cached answer instantly. Brain MISS proceeds to cloud.",
|
|
369
445
|
inputSchema: {
|
|
370
446
|
type: "object",
|
|
@@ -378,6 +454,7 @@ const toolDefinitions = [
|
|
|
378
454
|
},
|
|
379
455
|
{
|
|
380
456
|
name: "get_telepathic_hints",
|
|
457
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
381
458
|
description: "Surface relevant past solutions from The Brain based on the active file path.",
|
|
382
459
|
inputSchema: {
|
|
383
460
|
type: "object",
|
|
@@ -390,6 +467,7 @@ const toolDefinitions = [
|
|
|
390
467
|
},
|
|
391
468
|
{
|
|
392
469
|
name: "summarize_long_text",
|
|
470
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
393
471
|
description: "Summarize long text locally using Ollama. Compresses verbose content into concise summaries, saving context window for subsequent turns.",
|
|
394
472
|
inputSchema: {
|
|
395
473
|
type: "object",
|
|
@@ -402,7 +480,8 @@ const toolDefinitions = [
|
|
|
402
480
|
},
|
|
403
481
|
{
|
|
404
482
|
name: "prune_conversation_history",
|
|
405
|
-
|
|
483
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
484
|
+
description: "Compress conversation history by decimating old turns and compacting large code blocks in historical messages. Keeps recent context intact. Refuses to mutate anything by default because pruning invalidates the prompt-cache prefix, which is usually a net loss — pass force:true to actually apply it, or call without force to just get the estimated cache-invalidation cost back.",
|
|
406
485
|
inputSchema: {
|
|
407
486
|
type: "object",
|
|
408
487
|
properties: {
|
|
@@ -411,12 +490,18 @@ const toolDefinitions = [
|
|
|
411
490
|
items: { type: "object" },
|
|
412
491
|
description: "Array of conversation messages with role + content",
|
|
413
492
|
},
|
|
493
|
+
force: {
|
|
494
|
+
type: "boolean",
|
|
495
|
+
description: "Actually apply the prune despite the prompt-cache-invalidation cost. Default false — the call is a dry run that reports cacheInvalidationEstimate instead.",
|
|
496
|
+
default: false,
|
|
497
|
+
},
|
|
414
498
|
},
|
|
415
499
|
required: ["messages"],
|
|
416
500
|
},
|
|
417
501
|
},
|
|
418
502
|
{
|
|
419
503
|
name: "diff_only",
|
|
504
|
+
annotations: { readOnlyHint: true, idempotentHint: false, openWorldHint: false },
|
|
420
505
|
description: "Compute a compact line-by-line diff between the last known state of a file and its current content. Tracks file state in memory per session.",
|
|
421
506
|
inputSchema: {
|
|
422
507
|
type: "object",
|
|
@@ -429,6 +514,7 @@ const toolDefinitions = [
|
|
|
429
514
|
},
|
|
430
515
|
{
|
|
431
516
|
name: "batch_tool_calls",
|
|
517
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
432
518
|
description: "Execute multiple tools in a single MCP call. Accepts an array of { tool, args } and runs them in parallel via Promise.all. Results include per-tool success/error status.",
|
|
433
519
|
inputSchema: {
|
|
434
520
|
type: "object",
|
|
@@ -451,6 +537,7 @@ const toolDefinitions = [
|
|
|
451
537
|
},
|
|
452
538
|
{
|
|
453
539
|
name: "turbosqueeze",
|
|
540
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
454
541
|
description: "COMPRIME prompts hasta 90%. Elimina comentarios, imports redundantes, whitespace excesivo, y compacta JSON. Usar ANTES de enviar código largo al LLM para maximizar ahorro de tokens.",
|
|
455
542
|
inputSchema: {
|
|
456
543
|
type: "object",
|
|
@@ -465,6 +552,7 @@ const toolDefinitions = [
|
|
|
465
552
|
},
|
|
466
553
|
{
|
|
467
554
|
name: "get_symbol_surgical_context",
|
|
555
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
468
556
|
description: "Extrae quirúrgicamente la implementación de un símbolo específico (clase, función, interfaz) y las firmas (pero no la implementación completa) de todas sus dependencias locales del workspace. Ahorra hasta 90% de tokens.",
|
|
469
557
|
inputSchema: {
|
|
470
558
|
type: "object",
|
|
@@ -477,6 +565,7 @@ const toolDefinitions = [
|
|
|
477
565
|
},
|
|
478
566
|
{
|
|
479
567
|
name: "wormhole_squeeze",
|
|
568
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
480
569
|
description: "Comprime código a formato WORMHOLE súper denso usando tokens de palabra clave y abreviación de variables reversible. Ideal para alimentar contextos de lectura al LLM.",
|
|
481
570
|
inputSchema: {
|
|
482
571
|
type: "object",
|
|
@@ -488,6 +577,7 @@ const toolDefinitions = [
|
|
|
488
577
|
},
|
|
489
578
|
{
|
|
490
579
|
name: "generate_executive_roi_report",
|
|
580
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
491
581
|
description: "Genera un reporte ejecutivo en formato Markdown de Retorno de Inversión (ROI), dinero ahorrado en API, horas de desarrollo recuperadas y proyecciones de escala para la gerencia.",
|
|
492
582
|
inputSchema: {
|
|
493
583
|
type: "object",
|
|
@@ -500,6 +590,7 @@ const toolDefinitions = [
|
|
|
500
590
|
},
|
|
501
591
|
{
|
|
502
592
|
name: "surgical_ast_insert",
|
|
593
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
503
594
|
description: "Inserta quirúrgicamente código (métodos, propiedades, funciones) en una clase, interfaz o ámbito de archivo de TypeScript utilizando el AST Compiler. Evita escribir diffs de búsqueda y reemplazo grandes o reescribir todo el archivo. Ahorra 95% de tokens de salida.",
|
|
504
595
|
inputSchema: {
|
|
505
596
|
type: "object",
|
|
@@ -515,6 +606,7 @@ const toolDefinitions = [
|
|
|
515
606
|
},
|
|
516
607
|
{
|
|
517
608
|
name: "local_semantic_autofix",
|
|
609
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
518
610
|
description: "Intenta resolver errores de compilación o ejecución localmente consultando The Brain por tracebacks/mensajes similares. Si hay coincidencia, devuelve la solución/parche instantáneamente sin gastar tokens de LLM.",
|
|
519
611
|
inputSchema: {
|
|
520
612
|
type: "object",
|
|
@@ -526,6 +618,7 @@ const toolDefinitions = [
|
|
|
526
618
|
},
|
|
527
619
|
{
|
|
528
620
|
name: "compress_context",
|
|
621
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
529
622
|
description: "Comprime el historial de conversación: resume turns antiguos vía Ollama, mantiene solo los últimos N intactos. Reduce contexto 70-80%. USAR antes de cada turno extendido.",
|
|
530
623
|
inputSchema: {
|
|
531
624
|
type: "object",
|
|
@@ -542,6 +635,7 @@ const toolDefinitions = [
|
|
|
542
635
|
},
|
|
543
636
|
{
|
|
544
637
|
name: "smarter_cache",
|
|
638
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
545
639
|
description: "Cache predictivo con threshold bajo (75%). Busca en The Brain antes de cualquier razonamiento. Si hay hit >= 75%, devuelve respuesta instantánea sin gastar tokens en LLM.",
|
|
546
640
|
inputSchema: {
|
|
547
641
|
type: "object",
|
|
@@ -552,9 +646,25 @@ const toolDefinitions = [
|
|
|
552
646
|
},
|
|
553
647
|
required: ["query"],
|
|
554
648
|
},
|
|
649
|
+
outputSchema: {
|
|
650
|
+
type: "object",
|
|
651
|
+
properties: {
|
|
652
|
+
hit: { type: "boolean", description: "true si hubo un hit por encima del threshold" },
|
|
653
|
+
source: { type: "string", enum: ["lemma-brain", "llm-call-required", "cache-unavailable"], description: "De dónde salió (o no) la respuesta" },
|
|
654
|
+
similarity: { type: "number", description: "Similitud del mejor match (0.0-1.0). Ausente en la rama de error." },
|
|
655
|
+
threshold: { type: "number", description: "Threshold usado. Ausente en la rama de error." },
|
|
656
|
+
answer: { type: ["string", "null"], description: "Respuesta cacheada si hit=true; null si no. Ausente en la rama de error." },
|
|
657
|
+
tokensSaved: { type: "number", description: "Solo presente cuando hit=true" },
|
|
658
|
+
tokensSavedFormatted: { type: "string", description: "Solo presente cuando hit=true" },
|
|
659
|
+
hint: { type: "string", description: "Sugerencia de siguiente paso; presente en miss y en error" },
|
|
660
|
+
error: { type: "string", description: "Mensaje de error; solo presente si el brain local falló" },
|
|
661
|
+
},
|
|
662
|
+
required: ["hit", "source"],
|
|
663
|
+
},
|
|
555
664
|
},
|
|
556
665
|
{
|
|
557
666
|
name: "state_hash_cache",
|
|
667
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
558
668
|
description: "Cache de razonamiento con invalidación EXACTA (no probabilística): la respuesta queda atada al sha256 del contenido de los archivos de los que depende. Si esos archivos no cambiaron, el hit es matemáticamente válido, no una adivinanza por similitud. Usa action='lookup' antes de razonar sobre una pregunta que dependa de archivos específicos, y action='store' después de responder para cachearla.",
|
|
559
669
|
inputSchema: {
|
|
560
670
|
type: "object",
|
|
@@ -566,9 +676,24 @@ const toolDefinitions = [
|
|
|
566
676
|
},
|
|
567
677
|
required: ["action", "query", "filePaths"],
|
|
568
678
|
},
|
|
679
|
+
outputSchema: {
|
|
680
|
+
type: "object",
|
|
681
|
+
properties: {
|
|
682
|
+
status: { type: "string", enum: ["hit", "miss", "stored"], description: "Resultado de lookup (hit/miss) o de store (stored)" },
|
|
683
|
+
answer: { type: "string", description: "Solo presente cuando status='hit'" },
|
|
684
|
+
note: { type: "string", description: "Solo presente cuando status='hit'" },
|
|
685
|
+
tokensSaved: { type: "number", description: "Solo presente cuando status='hit'" },
|
|
686
|
+
reason: { type: "string", description: "Solo presente cuando status='miss'" },
|
|
687
|
+
hint: { type: "string", description: "Solo presente cuando status='miss'" },
|
|
688
|
+
id: { type: "string", description: "Solo presente cuando status='stored'" },
|
|
689
|
+
filesTracked: { type: "array", items: { type: "string" }, description: "Solo presente cuando status='stored'" },
|
|
690
|
+
},
|
|
691
|
+
required: ["status"],
|
|
692
|
+
},
|
|
569
693
|
},
|
|
570
694
|
{
|
|
571
695
|
name: "token_receipt",
|
|
696
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
572
697
|
description: "Recibo auditable de la sesión: de dónde vino cada respuesta (cache exacto, cache semántico, lectura de archivo, o razonamiento fresco). No es una estadística de 'ahorro' — es una bitácora verificable para que el usuario pueda comprobar que no se le está mintiendo con un cache hit inventado. Llamar con action='summary' para ver el reporte.",
|
|
573
698
|
inputSchema: {
|
|
574
699
|
type: "object",
|
|
@@ -577,9 +702,42 @@ const toolDefinitions = [
|
|
|
577
702
|
limit: { type: "number", description: "Cuántos eventos recientes incluir en el detalle", default: 20 },
|
|
578
703
|
},
|
|
579
704
|
},
|
|
705
|
+
outputSchema: {
|
|
706
|
+
type: "object",
|
|
707
|
+
properties: {
|
|
708
|
+
totalEvents: { type: "number", description: "Total de eventos registrados en la sesión" },
|
|
709
|
+
byType: {
|
|
710
|
+
type: "object",
|
|
711
|
+
properties: {
|
|
712
|
+
exact_cache_hit: { type: "number" },
|
|
713
|
+
semantic_cache_hit: { type: "number" },
|
|
714
|
+
file_read: { type: "number" },
|
|
715
|
+
reasoning: { type: "number" },
|
|
716
|
+
tool_call: { type: "number" },
|
|
717
|
+
},
|
|
718
|
+
description: "Conteo de eventos por tipo de origen",
|
|
719
|
+
},
|
|
720
|
+
recentEvents: {
|
|
721
|
+
type: "array",
|
|
722
|
+
items: {
|
|
723
|
+
type: "object",
|
|
724
|
+
properties: {
|
|
725
|
+
type: { type: "string" },
|
|
726
|
+
label: { type: "string" },
|
|
727
|
+
timestamp: { type: "number" },
|
|
728
|
+
meta: { type: "object" },
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
description: "Últimos `limit` eventos, en orden cronológico",
|
|
732
|
+
},
|
|
733
|
+
note: { type: "string" },
|
|
734
|
+
},
|
|
735
|
+
required: ["totalEvents", "byType", "recentEvents"],
|
|
736
|
+
},
|
|
580
737
|
},
|
|
581
738
|
{
|
|
582
739
|
name: "token_budget",
|
|
740
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
583
741
|
description: "Muestra el dashboard de consumo: multiplicador actual de membresía, tokens ahorrados, tools usadas, y recomendaciones para optimizar más. Consultar periódicamente para auto-regular consumo.",
|
|
584
742
|
inputSchema: {
|
|
585
743
|
type: "object",
|
|
@@ -588,6 +746,7 @@ const toolDefinitions = [
|
|
|
588
746
|
},
|
|
589
747
|
{
|
|
590
748
|
name: "entropy_score",
|
|
749
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
591
750
|
description: "Calcula la entropía matemática (complejidad) de uno o más archivos usando el TypeScript Compiler API. Sin LLM. Sin tokens. Devuelve: cyclomatic complexity, nesting depth, ratio de 'any', tamaño de funciones, y un score compuesto 0-100 (0=limpio, 100=caos puro). Usa esto para identificar qué archivos necesitan refactor ANTES de tocarlos.",
|
|
592
751
|
inputSchema: {
|
|
593
752
|
type: "object",
|
|
@@ -599,6 +758,7 @@ const toolDefinitions = [
|
|
|
599
758
|
},
|
|
600
759
|
{
|
|
601
760
|
name: "coupling_radar",
|
|
761
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
602
762
|
description: "Construye un grafo de acoplamiento entre módulos analizando imports/exports con el TypeScript Compiler. Sin LLM. Sin tokens. Detecta: ciclos de dependencia, módulos 'dios' (importados por todo), islas muertas (nadie los importa), y fan-in/fan-out por módulo. Esencial antes de refactors grandes.",
|
|
603
763
|
inputSchema: {
|
|
604
764
|
type: "object",
|
|
@@ -610,6 +770,7 @@ const toolDefinitions = [
|
|
|
610
770
|
},
|
|
611
771
|
{
|
|
612
772
|
name: "pattern_fossil",
|
|
773
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
613
774
|
description: "Detecta código zombie: patrones que ya fueron reemplazados en la mayoría del codebase pero siguen vivos en archivos viejos. Sin LLM. Sin tokens. Detecta: callbacks vs async/await, var vs let/const, require() vs import, any vs generics. Devuelve porcentaje de adopción del patrón nuevo vs. los fósiles que quedan.",
|
|
614
775
|
inputSchema: {
|
|
615
776
|
type: "object",
|
|
@@ -620,6 +781,7 @@ const toolDefinitions = [
|
|
|
620
781
|
},
|
|
621
782
|
{
|
|
622
783
|
name: "git_heatmap_risk",
|
|
784
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
623
785
|
description: "Analiza el historial de git para identificar zonas de alto riesgo. Sin LLM. Sin tokens. Calcula: churn rate (frecuencia de cambios), co-edición oculta (archivos que siempre cambian juntos = acoplamiento implícito), y un risk score compuesto por archivo. Detecta los archivos que estadísticamente tienen más probabilidad de tener un bug.",
|
|
624
786
|
inputSchema: {
|
|
625
787
|
type: "object",
|
|
@@ -631,6 +793,7 @@ const toolDefinitions = [
|
|
|
631
793
|
},
|
|
632
794
|
{
|
|
633
795
|
name: "precrime_static",
|
|
796
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
634
797
|
description: "MINORITY REPORT para tu código. Combina entropy_score + coupling_radar + git_heatmap_risk en un predictor de riesgo compuesto. Sin LLM. Sin tokens. Devuelve un ranking de archivos y funciones con mayor probabilidad de causar un bug, con justificación matemática de cada factor. Úsalo antes de un deploy o un PR review.",
|
|
635
798
|
inputSchema: {
|
|
636
799
|
type: "object",
|
|
@@ -643,6 +806,7 @@ const toolDefinitions = [
|
|
|
643
806
|
},
|
|
644
807
|
{
|
|
645
808
|
name: "semantic_dedup_guard",
|
|
809
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
646
810
|
description: "Firewall anti-redundancia para The Brain. Antes de hacer store_memory, pasa el contenido por aquí. Consulta ChromaDB localmente sin gastar tokens. Si hay un hit >= 92% → rechaza el store y devuelve el duplicado. Si 75-91% → advierte y muestra el similar. Mantiene The Brain denso y limpio.",
|
|
647
811
|
inputSchema: {
|
|
648
812
|
type: "object",
|
|
@@ -656,6 +820,7 @@ const toolDefinitions = [
|
|
|
656
820
|
},
|
|
657
821
|
{
|
|
658
822
|
name: "dead_export_necromancer",
|
|
823
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
659
824
|
description: "Resucita el código muerto. Usa el TypeScript Compiler para mapear TODOS los exports del workspace y los cruza contra TODOS los imports. Lo que se exporta pero nadie importa = código zombie que está inflando tu contexto y desperdiciando tokens. Sin LLM. Sin tokens. Devuelve lista de exports muertos con estimación de tokens desperdiciados.",
|
|
660
825
|
inputSchema: {
|
|
661
826
|
type: "object",
|
|
@@ -666,6 +831,7 @@ const toolDefinitions = [
|
|
|
666
831
|
},
|
|
667
832
|
{
|
|
668
833
|
name: "review_diff",
|
|
834
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
669
835
|
description: "Analyze a raw git diff locally for regressions, security issues, code quality, and static analysis. Zero-cost static analysis using local TypeScript compiler API — no LLM calls. Returns score, verdict, and detailed findings with suggestions.",
|
|
670
836
|
inputSchema: {
|
|
671
837
|
type: "object",
|
|
@@ -679,6 +845,7 @@ const toolDefinitions = [
|
|
|
679
845
|
},
|
|
680
846
|
{
|
|
681
847
|
name: "review_pr",
|
|
848
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
682
849
|
description: "Analyze a Pull Request via GitHub, Azure DevOps, or GitLab API. Detects regressions, security issues, code quality problems, and breaking changes. Posts results as PR comments and can auto-approve if quality threshold is met. Pro license required.",
|
|
683
850
|
inputSchema: {
|
|
684
851
|
type: "object",
|
|
@@ -695,6 +862,7 @@ const toolDefinitions = [
|
|
|
695
862
|
},
|
|
696
863
|
{
|
|
697
864
|
name: "pr_status",
|
|
865
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
698
866
|
description: "Get the status and detailed results of a previously reviewed PR. Returns score, verdict, findings, and approval status.",
|
|
699
867
|
inputSchema: {
|
|
700
868
|
type: "object",
|
|
@@ -708,6 +876,7 @@ const toolDefinitions = [
|
|
|
708
876
|
},
|
|
709
877
|
{
|
|
710
878
|
name: "generate_pr_workflow",
|
|
879
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
711
880
|
description: "Generate a CI/CD workflow file (GitHub Actions or Azure Pipelines) that runs Lemma PR Review on every pull request. Writes the file to the workspace.",
|
|
712
881
|
inputSchema: {
|
|
713
882
|
type: "object",
|
|
@@ -722,6 +891,7 @@ const toolDefinitions = [
|
|
|
722
891
|
},
|
|
723
892
|
{
|
|
724
893
|
name: "depgraph",
|
|
894
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
725
895
|
description: "Builds a real-time dependency graph for any file in the workspace using the TypeScript Compiler API. Shows what a file imports, what imports it (reverse deps), and what it exports. Zero LLM calls. Essential before any refactor to understand blast radius.",
|
|
726
896
|
inputSchema: {
|
|
727
897
|
type: "object",
|
|
@@ -738,6 +908,7 @@ const toolDefinitions = [
|
|
|
738
908
|
},
|
|
739
909
|
{
|
|
740
910
|
name: "affected_tests",
|
|
911
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
741
912
|
description: "Given the current diff (or an explicit file list), returns only the test files that transitively import the changed code, plus the exact command to run just those. Uses the TypeScript import graph — zero LLM calls. Run this instead of the full suite after an edit: fewer minutes, and far less test output in context. Reports honestly when the full suite is the right answer (unbounded change, runtime-only coupling, most of the suite affected).",
|
|
742
913
|
inputSchema: {
|
|
743
914
|
type: "object",
|
|
@@ -746,9 +917,24 @@ const toolDefinitions = [
|
|
|
746
917
|
baseRef: { type: "string", description: "Additional git ref to diff against, e.g. 'main'. The working tree is always included." },
|
|
747
918
|
},
|
|
748
919
|
},
|
|
920
|
+
outputSchema: {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: {
|
|
923
|
+
changed: { type: "array", items: { type: "string" }, description: "Files the diff touched, repo-relative" },
|
|
924
|
+
directTests: { type: "array", items: { type: "string" }, description: "Changed files that are themselves tests" },
|
|
925
|
+
affected: { type: "array", items: { type: "string" }, description: "Test files that transitively import a changed file" },
|
|
926
|
+
totalTests: { type: "number", description: "Total test files in the repo" },
|
|
927
|
+
runner: { type: "string", enum: ["jest", "vitest", "unknown"] },
|
|
928
|
+
command: { type: ["string", "null"], description: "Command to run just the affected tests, or null if the full suite is recommended" },
|
|
929
|
+
fullSuiteReason: { type: ["string", "null"], description: "Why the full suite is recommended instead, when command is null" },
|
|
930
|
+
ungraphed: { type: "array", items: { type: "string" }, description: "Changed files absent from the import graph — blast radius unknown" },
|
|
931
|
+
},
|
|
932
|
+
required: ["changed", "directTests", "affected", "totalTests", "runner", "command", "fullSuiteReason", "ungraphed"],
|
|
933
|
+
},
|
|
749
934
|
},
|
|
750
935
|
{
|
|
751
936
|
name: "refactor",
|
|
937
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
752
938
|
description: "Declarative multi-file codemod engine. Rename symbols across the entire workspace or move files updating all imports. Uses TypeScript Compiler API — zero LLM calls. Supports dry-run diff preview and post-refactor tsc verification.",
|
|
753
939
|
inputSchema: {
|
|
754
940
|
type: "object",
|
|
@@ -766,6 +952,7 @@ const toolDefinitions = [
|
|
|
766
952
|
},
|
|
767
953
|
{
|
|
768
954
|
name: "smart_file_slice",
|
|
955
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
769
956
|
description: "Read only the lines semánticamente relevantes around a search query in a file, avoiding sending the entire file context. Returns lines around the matches.",
|
|
770
957
|
inputSchema: {
|
|
771
958
|
type: "object",
|
|
@@ -779,6 +966,7 @@ const toolDefinitions = [
|
|
|
779
966
|
},
|
|
780
967
|
{
|
|
781
968
|
name: "test_oracle",
|
|
969
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
782
970
|
description: "Execute unit tests locally and output ONLY the failing tests and compressed stack traces, keeping context clean.",
|
|
783
971
|
inputSchema: {
|
|
784
972
|
type: "object",
|
|
@@ -789,6 +977,7 @@ const toolDefinitions = [
|
|
|
789
977
|
},
|
|
790
978
|
{
|
|
791
979
|
name: "schema_extract",
|
|
980
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
792
981
|
description: "Extract high-level schemas, Zod types, interfaces, or database models from a file using the TypeScript AST, removing all implementation code.",
|
|
793
982
|
inputSchema: {
|
|
794
983
|
type: "object",
|
|
@@ -800,6 +989,7 @@ const toolDefinitions = [
|
|
|
800
989
|
},
|
|
801
990
|
{
|
|
802
991
|
name: "changelog_auto",
|
|
992
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
803
993
|
description: "Generate a clean, token-efficient changelog summary from git logs based on Conventional Commits.",
|
|
804
994
|
inputSchema: {
|
|
805
995
|
type: "object",
|
|
@@ -810,6 +1000,7 @@ const toolDefinitions = [
|
|
|
810
1000
|
},
|
|
811
1001
|
{
|
|
812
1002
|
name: "spec_to_stub",
|
|
1003
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
813
1004
|
description: "Parse a TypeScript interface/type definition from a file and output a template mock/stub object configuration using pure AST analysis.",
|
|
814
1005
|
inputSchema: {
|
|
815
1006
|
type: "object",
|
|
@@ -822,6 +1013,7 @@ const toolDefinitions = [
|
|
|
822
1013
|
},
|
|
823
1014
|
{
|
|
824
1015
|
name: "env_snapshot",
|
|
1016
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
825
1017
|
description: "Capture environmental state like Node version, dependencies in package.json, and variables declared in .env files (hiding actual values/credentials).",
|
|
826
1018
|
inputSchema: {
|
|
827
1019
|
type: "object",
|
|
@@ -830,6 +1022,7 @@ const toolDefinitions = [
|
|
|
830
1022
|
},
|
|
831
1023
|
{
|
|
832
1024
|
name: "migration_tracer",
|
|
1025
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
833
1026
|
description: "Inspect changes in database schemas (tables, columns) across local migrations (e.g. Prisma migration directories).",
|
|
834
1027
|
inputSchema: {
|
|
835
1028
|
type: "object",
|
|
@@ -840,6 +1033,7 @@ const toolDefinitions = [
|
|
|
840
1033
|
},
|
|
841
1034
|
{
|
|
842
1035
|
name: "multi_file_patch",
|
|
1036
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
843
1037
|
description: "Apply search-and-replace patches to multiple workspace files in a single turn. Decreases round-trips for multi-file refactoring.",
|
|
844
1038
|
inputSchema: {
|
|
845
1039
|
type: "object",
|
|
@@ -864,6 +1058,7 @@ const toolDefinitions = [
|
|
|
864
1058
|
},
|
|
865
1059
|
{
|
|
866
1060
|
name: "file_intent_index",
|
|
1061
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
867
1062
|
description: "Map and index files inside the workspace by domain intent (e.g., auth, billing, routes, config) to speed up navigation.",
|
|
868
1063
|
inputSchema: {
|
|
869
1064
|
type: "object",
|
|
@@ -874,6 +1069,7 @@ const toolDefinitions = [
|
|
|
874
1069
|
},
|
|
875
1070
|
{
|
|
876
1071
|
name: "cognitive_map",
|
|
1072
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
877
1073
|
description: "Access and interact with the Auto-Cognitive Mind Map of the workspace (persisted globally). Holds structured high-level system domains, patterns, decisions, and bugs.",
|
|
878
1074
|
inputSchema: {
|
|
879
1075
|
type: "object",
|
|
@@ -896,6 +1092,7 @@ const toolDefinitions = [
|
|
|
896
1092
|
},
|
|
897
1093
|
{
|
|
898
1094
|
name: "semantic_grep",
|
|
1095
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
899
1096
|
description: "Search the codebase for conceptual terms using natural language and BM25 token relevance instead of exact substring matching.",
|
|
900
1097
|
inputSchema: {
|
|
901
1098
|
type: "object",
|
|
@@ -909,6 +1106,7 @@ const toolDefinitions = [
|
|
|
909
1106
|
},
|
|
910
1107
|
{
|
|
911
1108
|
name: "imports_skeleton_resolver",
|
|
1109
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
912
1110
|
description: "Resolve all imported local files inside a file and print only their signatures (classes, functions, interfaces) in a consolidated view.",
|
|
913
1111
|
inputSchema: {
|
|
914
1112
|
type: "object",
|
|
@@ -920,6 +1118,7 @@ const toolDefinitions = [
|
|
|
920
1118
|
},
|
|
921
1119
|
{
|
|
922
1120
|
name: "ast_flow_visualizer",
|
|
1121
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
923
1122
|
description: "Analyze a function's control flow statements (ifs, loops, try/catch) inside a file using AST compilation and output a visual flowchart in Mermaid syntax.",
|
|
924
1123
|
inputSchema: {
|
|
925
1124
|
type: "object",
|
|
@@ -932,6 +1131,7 @@ const toolDefinitions = [
|
|
|
932
1131
|
},
|
|
933
1132
|
{
|
|
934
1133
|
name: "file_fingerprint",
|
|
1134
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
935
1135
|
description: "Compute SHA256 fingerprint of a file. Returns 'unchanged' if hash matches cache, avoiding full re-reads. Saves 60-70% tokens on repeated file access.",
|
|
936
1136
|
inputSchema: {
|
|
937
1137
|
type: "object",
|
|
@@ -944,6 +1144,7 @@ const toolDefinitions = [
|
|
|
944
1144
|
},
|
|
945
1145
|
{
|
|
946
1146
|
name: "git_blame_heat",
|
|
1147
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
947
1148
|
description: "Analyze git blame history to identify high-risk lines and authors. Zero LLM cost. Returns risk scores per line, hotspots, and top contributors by risk.",
|
|
948
1149
|
inputSchema: {
|
|
949
1150
|
type: "object",
|
|
@@ -955,6 +1156,7 @@ const toolDefinitions = [
|
|
|
955
1156
|
},
|
|
956
1157
|
{
|
|
957
1158
|
name: "type_coverage_report",
|
|
1159
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
958
1160
|
description: "Analyze TypeScript type coverage of a file using AST. Returns declaration count, typed vs untyped ratio, any-usage count, and a safety score 0-100. Zero LLM cost.",
|
|
959
1161
|
inputSchema: {
|
|
960
1162
|
type: "object",
|
|
@@ -966,6 +1168,7 @@ const toolDefinitions = [
|
|
|
966
1168
|
},
|
|
967
1169
|
{
|
|
968
1170
|
name: "import_tree_context",
|
|
1171
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
969
1172
|
description: "Build a bidirectional import graph for a file. Shows what it imports, what imports it, and external dependencies. Zero LLM cost. Essential before refactors.",
|
|
970
1173
|
inputSchema: {
|
|
971
1174
|
type: "object",
|
|
@@ -978,6 +1181,7 @@ const toolDefinitions = [
|
|
|
978
1181
|
},
|
|
979
1182
|
{
|
|
980
1183
|
name: "read_token_budgeted",
|
|
1184
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
981
1185
|
description: "Read a file with a hard token budget. Returns only the most relevant sections up to maxTokens. Saves 70-90% vs full reads.",
|
|
982
1186
|
inputSchema: {
|
|
983
1187
|
type: "object",
|
|
@@ -991,6 +1195,7 @@ const toolDefinitions = [
|
|
|
991
1195
|
},
|
|
992
1196
|
{
|
|
993
1197
|
name: "bulk_file_digest",
|
|
1198
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
994
1199
|
description: "Read multiple files in one call and return a compressed digest. Saves 80% round-trips vs reading files individually.",
|
|
995
1200
|
inputSchema: {
|
|
996
1201
|
type: "object",
|
|
@@ -1008,6 +1213,7 @@ const toolDefinitions = [
|
|
|
1008
1213
|
},
|
|
1009
1214
|
{
|
|
1010
1215
|
name: "auto_context_bundle",
|
|
1216
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1011
1217
|
description: "Get everything about a file in one call: compressed content, import graph, schema types, and telepathic hints from The Brain. Replaces 4-5 separate tool calls.",
|
|
1012
1218
|
inputSchema: {
|
|
1013
1219
|
type: "object",
|
|
@@ -1019,6 +1225,7 @@ const toolDefinitions = [
|
|
|
1019
1225
|
},
|
|
1020
1226
|
{
|
|
1021
1227
|
name: "repeat_guard",
|
|
1228
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1022
1229
|
description: "Check if a draft output is repetitive compared to recent responses. Returns similarity score and suggestion. Prevents duplicate explanations.",
|
|
1023
1230
|
inputSchema: {
|
|
1024
1231
|
type: "object",
|
|
@@ -1031,6 +1238,7 @@ const toolDefinitions = [
|
|
|
1031
1238
|
},
|
|
1032
1239
|
{
|
|
1033
1240
|
name: "conversation_checkpoint",
|
|
1241
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1034
1242
|
description: "Save or load session state. Prevents re-explaining context across turns. Save current progress or load last checkpoint.",
|
|
1035
1243
|
inputSchema: {
|
|
1036
1244
|
type: "object",
|
|
@@ -1046,6 +1254,7 @@ const toolDefinitions = [
|
|
|
1046
1254
|
},
|
|
1047
1255
|
{
|
|
1048
1256
|
name: "test_autofix_interceptor",
|
|
1257
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1049
1258
|
description: "Run tests and auto-search The Brain for fixes to failures. Returns failure list with suggested fixes from past sessions. Saves 4-5 turns of debugging.",
|
|
1050
1259
|
inputSchema: {
|
|
1051
1260
|
type: "object",
|
|
@@ -1056,6 +1265,7 @@ const toolDefinitions = [
|
|
|
1056
1265
|
},
|
|
1057
1266
|
{
|
|
1058
1267
|
name: "prompt_pattern_cache",
|
|
1268
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1059
1269
|
description: "Find or store optimized prompt templates. Reuses past prompt patterns to save 20-30% tokens on repetitive operations.",
|
|
1060
1270
|
inputSchema: {
|
|
1061
1271
|
type: "object",
|
|
@@ -1075,6 +1285,7 @@ const _infraToolDefs = getInfraToolDefinitions();
|
|
|
1075
1285
|
// than in the main array because it must never itself be hidden.
|
|
1076
1286
|
const TOOLBOX_TOOL_DEFINITION = {
|
|
1077
1287
|
name: "lemma_toolbox",
|
|
1288
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
1078
1289
|
description: "Discover and invoke Lemma tools whose schemas are not loaded this session. Use action='list' (optionally with a query) to see what exists, 'schema' to read one tool's inputs, and 'call' to run it. Every Lemma capability is reachable here.",
|
|
1079
1290
|
inputSchema: {
|
|
1080
1291
|
type: "object",
|
|
@@ -1280,6 +1491,8 @@ function receiptLabelMeta(name, args) {
|
|
|
1280
1491
|
return meta;
|
|
1281
1492
|
}
|
|
1282
1493
|
export function setupToolsHandlers(server, onToolCall) {
|
|
1494
|
+
mcpServerRef = server;
|
|
1495
|
+
registerSymbolExtractor(extractSymbolsWithTsCompiler);
|
|
1283
1496
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1284
1497
|
tools: toolDefinitionsArray,
|
|
1285
1498
|
}));
|
|
@@ -1475,7 +1688,8 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1475
1688
|
const intendedModel = args?.intended_model;
|
|
1476
1689
|
const router = new ComplexityRouter();
|
|
1477
1690
|
const decision = router.evaluate(prompt, intendedModel);
|
|
1478
|
-
|
|
1691
|
+
const switched = !!intendedModel && decision.model !== intendedModel;
|
|
1692
|
+
if (decision.complexity === "low" && switched) {
|
|
1479
1693
|
const estimatedPromptTokens = Math.floor((prompt?.length || 0) / 4);
|
|
1480
1694
|
reportSavings({
|
|
1481
1695
|
source: "complexityRouting",
|
|
@@ -1484,6 +1698,12 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1484
1698
|
query: prompt?.substring(0, 100),
|
|
1485
1699
|
});
|
|
1486
1700
|
}
|
|
1701
|
+
const structured = {
|
|
1702
|
+
recommendedModel: decision.model,
|
|
1703
|
+
complexity: decision.complexity,
|
|
1704
|
+
intendedModel: intendedModel || null,
|
|
1705
|
+
switched,
|
|
1706
|
+
};
|
|
1487
1707
|
return {
|
|
1488
1708
|
content: [
|
|
1489
1709
|
{
|
|
@@ -1491,6 +1711,7 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1491
1711
|
text: `Lemma Routing Advice: Use ${decision.model}. Reason: Complexity is ${decision.complexity}.`,
|
|
1492
1712
|
},
|
|
1493
1713
|
],
|
|
1714
|
+
structuredContent: structured,
|
|
1494
1715
|
};
|
|
1495
1716
|
}
|
|
1496
1717
|
async function handleAutoHeal(args) {
|
|
@@ -1611,6 +1832,7 @@ async function handleReadWorkspaceFile(args) {
|
|
|
1611
1832
|
filePath,
|
|
1612
1833
|
});
|
|
1613
1834
|
recordSent(resolved, view, dedup.hash, content);
|
|
1835
|
+
warmNeighbors(workspaceRoot, resolved);
|
|
1614
1836
|
return {
|
|
1615
1837
|
content: [
|
|
1616
1838
|
{
|
|
@@ -1629,6 +1851,7 @@ async function handleReadWorkspaceFile(args) {
|
|
|
1629
1851
|
}
|
|
1630
1852
|
}
|
|
1631
1853
|
recordSent(resolved, view, dedup.hash, content);
|
|
1854
|
+
warmNeighbors(workspaceRoot, resolved);
|
|
1632
1855
|
return { content: [{ type: "text", text: rangeHeader + content }] };
|
|
1633
1856
|
}
|
|
1634
1857
|
catch (err) {
|
|
@@ -1761,6 +1984,11 @@ async function handleApplyWorkspacePatch(args) {
|
|
|
1761
1984
|
const updatedLines = [...contentLines.slice(0, match.startLine), ...finalReplace.split("\n"), ...contentLines.slice(match.endLine + 1)];
|
|
1762
1985
|
updatedContent = updatedLines.join("\n");
|
|
1763
1986
|
}
|
|
1987
|
+
const elicited = await tryElicitConfirmation(`Apply a patch to ${filePath}? This replaces ${searchContent.length} char(s) with ${replaceContent.length} char(s) ` +
|
|
1988
|
+
`starting at line ${match.startLine + 1}.`);
|
|
1989
|
+
if (elicited.supported && !elicited.confirmed) {
|
|
1990
|
+
return { content: [{ type: "text", text: `Patch to ${filePath} was not applied: declined during confirmation.` }] };
|
|
1991
|
+
}
|
|
1764
1992
|
fs.writeFileSync(resolved, updatedContent, "utf8");
|
|
1765
1993
|
const note = match.strategy === "exact" ? "" : ` (matched via ${match.strategy}, score ${match.score.toFixed(2)})`;
|
|
1766
1994
|
return { content: [{ type: "text", text: `Success: Patch successfully applied to ${filePath}${note}` }] };
|
|
@@ -1810,6 +2038,10 @@ async function handleRunWorkspaceCommand(args) {
|
|
|
1810
2038
|
const MAX_TIMEOUT_MS = 600000;
|
|
1811
2039
|
const requested = typeof args?.timeoutMs === "number" && args.timeoutMs > 0 ? args.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1812
2040
|
const timeoutMs = Math.min(Math.floor(requested), MAX_TIMEOUT_MS);
|
|
2041
|
+
const elicited = await tryElicitConfirmation(`Run this command in the workspace root?\n\n${command}`);
|
|
2042
|
+
if (elicited.supported && !elicited.confirmed) {
|
|
2043
|
+
return { content: [{ type: "text", text: `Command was not run: declined during confirmation.\n\nCommand: ${command}` }] };
|
|
2044
|
+
}
|
|
1813
2045
|
const result = spawnSync(command, {
|
|
1814
2046
|
cwd: workspaceRoot,
|
|
1815
2047
|
shell: true,
|
|
@@ -2366,7 +2598,7 @@ function walkDirForHologram(dir, rel, extensions, stats) {
|
|
|
2366
2598
|
const ext = entry.name.split(".").pop() || "";
|
|
2367
2599
|
if (extensions.includes(ext)) {
|
|
2368
2600
|
stats.filesScanned++;
|
|
2369
|
-
allSymbols = allSymbols.concat(
|
|
2601
|
+
allSymbols = allSymbols.concat(getOrExtractSymbols(fullPath, relPath, extractSymbolsWithTsCompiler, stats.parseErrors));
|
|
2370
2602
|
}
|
|
2371
2603
|
}
|
|
2372
2604
|
}
|
|
@@ -2637,13 +2869,16 @@ async function handlePruneConversationHistory(args) {
|
|
|
2637
2869
|
const messages = args?.messages;
|
|
2638
2870
|
if (!messages)
|
|
2639
2871
|
throw new Error("messages is required");
|
|
2872
|
+
const force = !!args?.force;
|
|
2640
2873
|
try {
|
|
2641
|
-
const result = pruneHistoryMessages(messages);
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2874
|
+
const result = pruneHistoryMessages(messages, { force });
|
|
2875
|
+
if (result.applied) {
|
|
2876
|
+
reportSavings({
|
|
2877
|
+
source: "historyPrune",
|
|
2878
|
+
tokens: result.tokensSavedEstimate,
|
|
2879
|
+
toolName: "prune_conversation_history",
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2647
2882
|
return {
|
|
2648
2883
|
content: [
|
|
2649
2884
|
{
|
|
@@ -2651,6 +2886,9 @@ async function handlePruneConversationHistory(args) {
|
|
|
2651
2886
|
text: JSON.stringify({
|
|
2652
2887
|
messages: result.messages,
|
|
2653
2888
|
tokensSavedEstimate: result.tokensSavedEstimate,
|
|
2889
|
+
cacheInvalidationEstimate: result.cacheInvalidationEstimate,
|
|
2890
|
+
applied: result.applied,
|
|
2891
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
2654
2892
|
}, null, 2),
|
|
2655
2893
|
},
|
|
2656
2894
|
],
|
|
@@ -2826,47 +3064,44 @@ async function handleSmarterCache(args) {
|
|
|
2826
3064
|
reportSavings({ source: "cache", tokens: tokensSaved, toolName: "smarter_cache", query: query.substring(0, 100) });
|
|
2827
3065
|
}
|
|
2828
3066
|
catch { }
|
|
3067
|
+
const structured = {
|
|
3068
|
+
hit: true,
|
|
3069
|
+
similarity: topHit.similarity,
|
|
3070
|
+
threshold,
|
|
3071
|
+
answer: responseText,
|
|
3072
|
+
source: "lemma-brain",
|
|
3073
|
+
tokensSaved,
|
|
3074
|
+
tokensSavedFormatted: `~${tokensSaved.toLocaleString()} tokens`,
|
|
3075
|
+
};
|
|
2829
3076
|
return {
|
|
2830
|
-
content: [{
|
|
2831
|
-
|
|
2832
|
-
text: JSON.stringify({
|
|
2833
|
-
hit: true,
|
|
2834
|
-
similarity: topHit.similarity,
|
|
2835
|
-
threshold,
|
|
2836
|
-
answer: responseText,
|
|
2837
|
-
source: "lemma-brain",
|
|
2838
|
-
tokensSaved,
|
|
2839
|
-
tokensSavedFormatted: `~${tokensSaved.toLocaleString()} tokens`,
|
|
2840
|
-
}, null, 2),
|
|
2841
|
-
}],
|
|
3077
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3078
|
+
structuredContent: structured,
|
|
2842
3079
|
};
|
|
2843
3080
|
}
|
|
3081
|
+
const structured = {
|
|
3082
|
+
hit: false,
|
|
3083
|
+
similarity: topHit?.similarity || 0,
|
|
3084
|
+
threshold,
|
|
3085
|
+
answer: null,
|
|
3086
|
+
source: "llm-call-required",
|
|
3087
|
+
hint: "Después de resolver, llama store_memory para cachear y no gastar tokens en esto otra vez.",
|
|
3088
|
+
};
|
|
2844
3089
|
return {
|
|
2845
|
-
content: [{
|
|
2846
|
-
|
|
2847
|
-
text: JSON.stringify({
|
|
2848
|
-
hit: false,
|
|
2849
|
-
similarity: topHit?.similarity || 0,
|
|
2850
|
-
threshold,
|
|
2851
|
-
answer: null,
|
|
2852
|
-
source: "llm-call-required",
|
|
2853
|
-
hint: "Después de resolver, llama store_memory para cachear y no gastar tokens en esto otra vez.",
|
|
2854
|
-
}, null, 2),
|
|
2855
|
-
}],
|
|
3090
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3091
|
+
structuredContent: structured,
|
|
2856
3092
|
};
|
|
2857
3093
|
}
|
|
2858
3094
|
catch (e) {
|
|
2859
3095
|
logError("smarter_cache", e);
|
|
3096
|
+
const structured = {
|
|
3097
|
+
hit: false,
|
|
3098
|
+
error: e.message,
|
|
3099
|
+
source: "cache-unavailable",
|
|
3100
|
+
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
3101
|
+
};
|
|
2860
3102
|
return {
|
|
2861
|
-
content: [{
|
|
2862
|
-
|
|
2863
|
-
text: JSON.stringify({
|
|
2864
|
-
hit: false,
|
|
2865
|
-
error: e.message,
|
|
2866
|
-
source: "cache-unavailable",
|
|
2867
|
-
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
2868
|
-
}, null, 2),
|
|
2869
|
-
}],
|
|
3103
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3104
|
+
structuredContent: structured,
|
|
2870
3105
|
};
|
|
2871
3106
|
}
|
|
2872
3107
|
}
|
|
@@ -2887,23 +3122,21 @@ async function handleStateHashCache(args) {
|
|
|
2887
3122
|
reportSavings({ source: "cache", tokens: tokensSaved, toolName: "state_hash_cache", query: query.substring(0, 100) });
|
|
2888
3123
|
}
|
|
2889
3124
|
catch { }
|
|
3125
|
+
const structured = {
|
|
3126
|
+
status: "hit",
|
|
3127
|
+
answer: result.entry.answer,
|
|
3128
|
+
note: "Respuesta desde caché exacto (hash de archivos sin cambios) — no necesita re-razonar.",
|
|
3129
|
+
tokensSaved,
|
|
3130
|
+
};
|
|
2890
3131
|
return {
|
|
2891
|
-
content: [{
|
|
2892
|
-
|
|
2893
|
-
text: JSON.stringify({
|
|
2894
|
-
status: "hit",
|
|
2895
|
-
answer: result.entry.answer,
|
|
2896
|
-
note: "Respuesta desde caché exacto (hash de archivos sin cambios) — no necesita re-razonar.",
|
|
2897
|
-
tokensSaved,
|
|
2898
|
-
}, null, 2),
|
|
2899
|
-
}],
|
|
3132
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3133
|
+
structuredContent: structured,
|
|
2900
3134
|
};
|
|
2901
3135
|
}
|
|
3136
|
+
const structured = { status: "miss", reason: result.reason, hint: "Razona normalmente y luego llama action='store' con la respuesta." };
|
|
2902
3137
|
return {
|
|
2903
|
-
content: [{
|
|
2904
|
-
|
|
2905
|
-
text: JSON.stringify({ status: "miss", reason: result.reason, hint: "Razona normalmente y luego llama action='store' con la respuesta." }, null, 2),
|
|
2906
|
-
}],
|
|
3138
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3139
|
+
structuredContent: structured,
|
|
2907
3140
|
};
|
|
2908
3141
|
}
|
|
2909
3142
|
if (action === "store") {
|
|
@@ -2911,8 +3144,10 @@ async function handleStateHashCache(args) {
|
|
|
2911
3144
|
if (!answer)
|
|
2912
3145
|
throw new Error("answer is required for action='store'");
|
|
2913
3146
|
const entry = storeStateHash(workspaceRoot, query, answer, filePaths);
|
|
3147
|
+
const structured = { status: "stored", id: entry.id, filesTracked: Object.keys(entry.fileHashes) };
|
|
2914
3148
|
return {
|
|
2915
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
3149
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3150
|
+
structuredContent: structured,
|
|
2916
3151
|
};
|
|
2917
3152
|
}
|
|
2918
3153
|
throw new Error(`Unknown action: ${action}. Use 'lookup' or 'store'.`);
|
|
@@ -2921,16 +3156,15 @@ async function handleStateHashCache(args) {
|
|
|
2921
3156
|
async function handleTokenReceipt(args) {
|
|
2922
3157
|
const limit = typeof args?.limit === "number" ? args.limit : 20;
|
|
2923
3158
|
const summary = getReceiptSummary(limit);
|
|
3159
|
+
const structured = {
|
|
3160
|
+
totalEvents: summary.totalEvents,
|
|
3161
|
+
byType: summary.byType,
|
|
3162
|
+
recentEvents: summary.recent,
|
|
3163
|
+
note: "Esto es una bitácora real de esta sesión, no una proyección de ahorro. Si byType.reasoning es alto, no hubo mucho cache — y está bien, es honesto.",
|
|
3164
|
+
};
|
|
2924
3165
|
return {
|
|
2925
|
-
content: [{
|
|
2926
|
-
|
|
2927
|
-
text: JSON.stringify({
|
|
2928
|
-
totalEvents: summary.totalEvents,
|
|
2929
|
-
byType: summary.byType,
|
|
2930
|
-
recentEvents: summary.recent,
|
|
2931
|
-
note: "Esto es una bitácora real de esta sesión, no una proyección de ahorro. Si byType.reasoning es alto, no hubo mucho cache — y está bien, es honesto.",
|
|
2932
|
-
}, null, 2),
|
|
2933
|
-
}],
|
|
3166
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3167
|
+
structuredContent: structured,
|
|
2934
3168
|
};
|
|
2935
3169
|
}
|
|
2936
3170
|
// ── Token Budget ─────────────────────────────────────────────────
|
|
@@ -4214,7 +4448,17 @@ async function handleAffectedTests(args) {
|
|
|
4214
4448
|
totalTests: result.totalTests,
|
|
4215
4449
|
narrowed: result.command !== null,
|
|
4216
4450
|
});
|
|
4217
|
-
|
|
4451
|
+
const structured = {
|
|
4452
|
+
changed: result.changed,
|
|
4453
|
+
directTests: result.directTests,
|
|
4454
|
+
affected: result.affected,
|
|
4455
|
+
totalTests: result.totalTests,
|
|
4456
|
+
runner: result.runner,
|
|
4457
|
+
command: result.command,
|
|
4458
|
+
fullSuiteReason: result.fullSuiteReason,
|
|
4459
|
+
ungraphed: result.ungraphed,
|
|
4460
|
+
};
|
|
4461
|
+
return { content: [{ type: "text", text }], structuredContent: structured };
|
|
4218
4462
|
}
|
|
4219
4463
|
catch (err) {
|
|
4220
4464
|
logError("affected_tests", err);
|