@nxuss/lemma 1.11.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 +14 -0
- 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 +29 -50
- 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 +285 -57
- 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/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +14 -0
- 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 +29 -50
- 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 +285 -57
- 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/package.json +2 -2
package/dist/esm/mcp/tools.js
CHANGED
|
@@ -92,9 +92,55 @@ const PRO_GATE_MESSAGE = [
|
|
|
92
92
|
"🚀 Get Lemma Pro:",
|
|
93
93
|
" https://lemma.nxus.studio/upgrade",
|
|
94
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
|
+
}
|
|
95
140
|
const toolDefinitions = [
|
|
96
141
|
{
|
|
97
142
|
name: "scrub_privacy",
|
|
143
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
98
144
|
description: "Mask sensitive data (PII, API Keys, Credentials) from a text block using Lemma's Privacy Firewall.",
|
|
99
145
|
inputSchema: {
|
|
100
146
|
type: "object",
|
|
@@ -106,6 +152,7 @@ const toolDefinitions = [
|
|
|
106
152
|
},
|
|
107
153
|
{
|
|
108
154
|
name: "search_memory",
|
|
155
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
109
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.",
|
|
110
157
|
inputSchema: {
|
|
111
158
|
type: "object",
|
|
@@ -123,6 +170,7 @@ const toolDefinitions = [
|
|
|
123
170
|
},
|
|
124
171
|
{
|
|
125
172
|
name: "store_memory",
|
|
173
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
126
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.",
|
|
127
175
|
inputSchema: {
|
|
128
176
|
type: "object",
|
|
@@ -174,6 +222,7 @@ const toolDefinitions = [
|
|
|
174
222
|
},
|
|
175
223
|
{
|
|
176
224
|
name: "get_routing_advice",
|
|
225
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
177
226
|
description: "Analyzes a prompt and suggests the best model based on Lemma's Complexity Router.",
|
|
178
227
|
inputSchema: {
|
|
179
228
|
type: "object",
|
|
@@ -183,9 +232,20 @@ const toolDefinitions = [
|
|
|
183
232
|
},
|
|
184
233
|
required: ["prompt"],
|
|
185
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
|
+
},
|
|
186
245
|
},
|
|
187
246
|
{
|
|
188
247
|
name: "auto_heal",
|
|
248
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
189
249
|
description: "Diagnose and auto-heal the latest local server crash registered in Lemma's context logs.",
|
|
190
250
|
inputSchema: {
|
|
191
251
|
type: "object",
|
|
@@ -196,6 +256,7 @@ const toolDefinitions = [
|
|
|
196
256
|
},
|
|
197
257
|
{
|
|
198
258
|
name: "read_workspace_file",
|
|
259
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
199
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.",
|
|
200
261
|
inputSchema: {
|
|
201
262
|
type: "object",
|
|
@@ -212,6 +273,7 @@ const toolDefinitions = [
|
|
|
212
273
|
},
|
|
213
274
|
{
|
|
214
275
|
name: "write_workspace_file",
|
|
276
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
215
277
|
description: "Write full contents to a file inside the workspace. Creates parent directories automatically.",
|
|
216
278
|
inputSchema: {
|
|
217
279
|
type: "object",
|
|
@@ -224,6 +286,7 @@ const toolDefinitions = [
|
|
|
224
286
|
},
|
|
225
287
|
{
|
|
226
288
|
name: "create_workspace_file",
|
|
289
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
227
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.",
|
|
228
291
|
inputSchema: {
|
|
229
292
|
type: "object",
|
|
@@ -236,6 +299,7 @@ const toolDefinitions = [
|
|
|
236
299
|
},
|
|
237
300
|
{
|
|
238
301
|
name: "apply_workspace_patch",
|
|
302
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
239
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.",
|
|
240
304
|
inputSchema: {
|
|
241
305
|
type: "object",
|
|
@@ -249,6 +313,7 @@ const toolDefinitions = [
|
|
|
249
313
|
},
|
|
250
314
|
{
|
|
251
315
|
name: "run_workspace_command",
|
|
316
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
252
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.",
|
|
253
318
|
inputSchema: {
|
|
254
319
|
type: "object",
|
|
@@ -270,6 +335,7 @@ const toolDefinitions = [
|
|
|
270
335
|
},
|
|
271
336
|
{
|
|
272
337
|
name: "output_region",
|
|
338
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
273
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.",
|
|
274
340
|
inputSchema: {
|
|
275
341
|
type: "object",
|
|
@@ -285,6 +351,7 @@ const toolDefinitions = [
|
|
|
285
351
|
},
|
|
286
352
|
{
|
|
287
353
|
name: "list_workspace_dir",
|
|
354
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
288
355
|
description: "List files and subdirectories recursively to navigate the repository structure.",
|
|
289
356
|
inputSchema: {
|
|
290
357
|
type: "object",
|
|
@@ -296,6 +363,7 @@ const toolDefinitions = [
|
|
|
296
363
|
},
|
|
297
364
|
{
|
|
298
365
|
name: "search_workspace",
|
|
366
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
299
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.",
|
|
300
368
|
inputSchema: {
|
|
301
369
|
type: "object",
|
|
@@ -309,6 +377,7 @@ const toolDefinitions = [
|
|
|
309
377
|
},
|
|
310
378
|
{
|
|
311
379
|
name: "squeeze_prompt",
|
|
380
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
312
381
|
description: "Compress code blocks, comments, and boilerplate in any prompt. Saves up to 80% tokens.",
|
|
313
382
|
inputSchema: {
|
|
314
383
|
type: "object",
|
|
@@ -321,6 +390,7 @@ const toolDefinitions = [
|
|
|
321
390
|
},
|
|
322
391
|
{
|
|
323
392
|
name: "get_project_onboarding",
|
|
393
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
324
394
|
description: "Fetch dynamic architectural and stack overview of the current project in markdown.",
|
|
325
395
|
inputSchema: {
|
|
326
396
|
type: "object",
|
|
@@ -329,6 +399,7 @@ const toolDefinitions = [
|
|
|
329
399
|
},
|
|
330
400
|
{
|
|
331
401
|
name: "get_project_history",
|
|
402
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
332
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.",
|
|
333
404
|
inputSchema: {
|
|
334
405
|
type: "object",
|
|
@@ -339,6 +410,7 @@ const toolDefinitions = [
|
|
|
339
410
|
},
|
|
340
411
|
{
|
|
341
412
|
name: "get_ast_hologram",
|
|
413
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
342
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.",
|
|
343
415
|
inputSchema: {
|
|
344
416
|
type: "object",
|
|
@@ -355,6 +427,7 @@ const toolDefinitions = [
|
|
|
355
427
|
},
|
|
356
428
|
{
|
|
357
429
|
name: "validate_patch_sandbox",
|
|
430
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
358
431
|
description: "Validate proposed code in isolated sandbox via tsc + syntax check before applying.",
|
|
359
432
|
inputSchema: {
|
|
360
433
|
type: "object",
|
|
@@ -367,6 +440,7 @@ const toolDefinitions = [
|
|
|
367
440
|
},
|
|
368
441
|
{
|
|
369
442
|
name: "query_hybrid_consensus",
|
|
443
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
370
444
|
description: "Search The Brain before reasoning. Brain HIT returns cached answer instantly. Brain MISS proceeds to cloud.",
|
|
371
445
|
inputSchema: {
|
|
372
446
|
type: "object",
|
|
@@ -380,6 +454,7 @@ const toolDefinitions = [
|
|
|
380
454
|
},
|
|
381
455
|
{
|
|
382
456
|
name: "get_telepathic_hints",
|
|
457
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
383
458
|
description: "Surface relevant past solutions from The Brain based on the active file path.",
|
|
384
459
|
inputSchema: {
|
|
385
460
|
type: "object",
|
|
@@ -392,6 +467,7 @@ const toolDefinitions = [
|
|
|
392
467
|
},
|
|
393
468
|
{
|
|
394
469
|
name: "summarize_long_text",
|
|
470
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
395
471
|
description: "Summarize long text locally using Ollama. Compresses verbose content into concise summaries, saving context window for subsequent turns.",
|
|
396
472
|
inputSchema: {
|
|
397
473
|
type: "object",
|
|
@@ -404,6 +480,7 @@ const toolDefinitions = [
|
|
|
404
480
|
},
|
|
405
481
|
{
|
|
406
482
|
name: "prune_conversation_history",
|
|
483
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
407
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.",
|
|
408
485
|
inputSchema: {
|
|
409
486
|
type: "object",
|
|
@@ -424,6 +501,7 @@ const toolDefinitions = [
|
|
|
424
501
|
},
|
|
425
502
|
{
|
|
426
503
|
name: "diff_only",
|
|
504
|
+
annotations: { readOnlyHint: true, idempotentHint: false, openWorldHint: false },
|
|
427
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.",
|
|
428
506
|
inputSchema: {
|
|
429
507
|
type: "object",
|
|
@@ -436,6 +514,7 @@ const toolDefinitions = [
|
|
|
436
514
|
},
|
|
437
515
|
{
|
|
438
516
|
name: "batch_tool_calls",
|
|
517
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
439
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.",
|
|
440
519
|
inputSchema: {
|
|
441
520
|
type: "object",
|
|
@@ -458,6 +537,7 @@ const toolDefinitions = [
|
|
|
458
537
|
},
|
|
459
538
|
{
|
|
460
539
|
name: "turbosqueeze",
|
|
540
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
461
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.",
|
|
462
542
|
inputSchema: {
|
|
463
543
|
type: "object",
|
|
@@ -472,6 +552,7 @@ const toolDefinitions = [
|
|
|
472
552
|
},
|
|
473
553
|
{
|
|
474
554
|
name: "get_symbol_surgical_context",
|
|
555
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
475
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.",
|
|
476
557
|
inputSchema: {
|
|
477
558
|
type: "object",
|
|
@@ -484,6 +565,7 @@ const toolDefinitions = [
|
|
|
484
565
|
},
|
|
485
566
|
{
|
|
486
567
|
name: "wormhole_squeeze",
|
|
568
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
487
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.",
|
|
488
570
|
inputSchema: {
|
|
489
571
|
type: "object",
|
|
@@ -495,6 +577,7 @@ const toolDefinitions = [
|
|
|
495
577
|
},
|
|
496
578
|
{
|
|
497
579
|
name: "generate_executive_roi_report",
|
|
580
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
498
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.",
|
|
499
582
|
inputSchema: {
|
|
500
583
|
type: "object",
|
|
@@ -507,6 +590,7 @@ const toolDefinitions = [
|
|
|
507
590
|
},
|
|
508
591
|
{
|
|
509
592
|
name: "surgical_ast_insert",
|
|
593
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
510
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.",
|
|
511
595
|
inputSchema: {
|
|
512
596
|
type: "object",
|
|
@@ -522,6 +606,7 @@ const toolDefinitions = [
|
|
|
522
606
|
},
|
|
523
607
|
{
|
|
524
608
|
name: "local_semantic_autofix",
|
|
609
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
525
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.",
|
|
526
611
|
inputSchema: {
|
|
527
612
|
type: "object",
|
|
@@ -533,6 +618,7 @@ const toolDefinitions = [
|
|
|
533
618
|
},
|
|
534
619
|
{
|
|
535
620
|
name: "compress_context",
|
|
621
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
536
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.",
|
|
537
623
|
inputSchema: {
|
|
538
624
|
type: "object",
|
|
@@ -549,6 +635,7 @@ const toolDefinitions = [
|
|
|
549
635
|
},
|
|
550
636
|
{
|
|
551
637
|
name: "smarter_cache",
|
|
638
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
552
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.",
|
|
553
640
|
inputSchema: {
|
|
554
641
|
type: "object",
|
|
@@ -559,9 +646,25 @@ const toolDefinitions = [
|
|
|
559
646
|
},
|
|
560
647
|
required: ["query"],
|
|
561
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
|
+
},
|
|
562
664
|
},
|
|
563
665
|
{
|
|
564
666
|
name: "state_hash_cache",
|
|
667
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
565
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.",
|
|
566
669
|
inputSchema: {
|
|
567
670
|
type: "object",
|
|
@@ -573,9 +676,24 @@ const toolDefinitions = [
|
|
|
573
676
|
},
|
|
574
677
|
required: ["action", "query", "filePaths"],
|
|
575
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
|
+
},
|
|
576
693
|
},
|
|
577
694
|
{
|
|
578
695
|
name: "token_receipt",
|
|
696
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
579
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.",
|
|
580
698
|
inputSchema: {
|
|
581
699
|
type: "object",
|
|
@@ -584,9 +702,42 @@ const toolDefinitions = [
|
|
|
584
702
|
limit: { type: "number", description: "Cuántos eventos recientes incluir en el detalle", default: 20 },
|
|
585
703
|
},
|
|
586
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
|
+
},
|
|
587
737
|
},
|
|
588
738
|
{
|
|
589
739
|
name: "token_budget",
|
|
740
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
590
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.",
|
|
591
742
|
inputSchema: {
|
|
592
743
|
type: "object",
|
|
@@ -595,6 +746,7 @@ const toolDefinitions = [
|
|
|
595
746
|
},
|
|
596
747
|
{
|
|
597
748
|
name: "entropy_score",
|
|
749
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
598
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.",
|
|
599
751
|
inputSchema: {
|
|
600
752
|
type: "object",
|
|
@@ -606,6 +758,7 @@ const toolDefinitions = [
|
|
|
606
758
|
},
|
|
607
759
|
{
|
|
608
760
|
name: "coupling_radar",
|
|
761
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
609
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.",
|
|
610
763
|
inputSchema: {
|
|
611
764
|
type: "object",
|
|
@@ -617,6 +770,7 @@ const toolDefinitions = [
|
|
|
617
770
|
},
|
|
618
771
|
{
|
|
619
772
|
name: "pattern_fossil",
|
|
773
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
620
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.",
|
|
621
775
|
inputSchema: {
|
|
622
776
|
type: "object",
|
|
@@ -627,6 +781,7 @@ const toolDefinitions = [
|
|
|
627
781
|
},
|
|
628
782
|
{
|
|
629
783
|
name: "git_heatmap_risk",
|
|
784
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
630
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.",
|
|
631
786
|
inputSchema: {
|
|
632
787
|
type: "object",
|
|
@@ -638,6 +793,7 @@ const toolDefinitions = [
|
|
|
638
793
|
},
|
|
639
794
|
{
|
|
640
795
|
name: "precrime_static",
|
|
796
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
641
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.",
|
|
642
798
|
inputSchema: {
|
|
643
799
|
type: "object",
|
|
@@ -650,6 +806,7 @@ const toolDefinitions = [
|
|
|
650
806
|
},
|
|
651
807
|
{
|
|
652
808
|
name: "semantic_dedup_guard",
|
|
809
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
653
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.",
|
|
654
811
|
inputSchema: {
|
|
655
812
|
type: "object",
|
|
@@ -663,6 +820,7 @@ const toolDefinitions = [
|
|
|
663
820
|
},
|
|
664
821
|
{
|
|
665
822
|
name: "dead_export_necromancer",
|
|
823
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
666
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.",
|
|
667
825
|
inputSchema: {
|
|
668
826
|
type: "object",
|
|
@@ -673,6 +831,7 @@ const toolDefinitions = [
|
|
|
673
831
|
},
|
|
674
832
|
{
|
|
675
833
|
name: "review_diff",
|
|
834
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
676
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.",
|
|
677
836
|
inputSchema: {
|
|
678
837
|
type: "object",
|
|
@@ -686,6 +845,7 @@ const toolDefinitions = [
|
|
|
686
845
|
},
|
|
687
846
|
{
|
|
688
847
|
name: "review_pr",
|
|
848
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
689
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.",
|
|
690
850
|
inputSchema: {
|
|
691
851
|
type: "object",
|
|
@@ -702,6 +862,7 @@ const toolDefinitions = [
|
|
|
702
862
|
},
|
|
703
863
|
{
|
|
704
864
|
name: "pr_status",
|
|
865
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
705
866
|
description: "Get the status and detailed results of a previously reviewed PR. Returns score, verdict, findings, and approval status.",
|
|
706
867
|
inputSchema: {
|
|
707
868
|
type: "object",
|
|
@@ -715,6 +876,7 @@ const toolDefinitions = [
|
|
|
715
876
|
},
|
|
716
877
|
{
|
|
717
878
|
name: "generate_pr_workflow",
|
|
879
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
718
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.",
|
|
719
881
|
inputSchema: {
|
|
720
882
|
type: "object",
|
|
@@ -729,6 +891,7 @@ const toolDefinitions = [
|
|
|
729
891
|
},
|
|
730
892
|
{
|
|
731
893
|
name: "depgraph",
|
|
894
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
732
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.",
|
|
733
896
|
inputSchema: {
|
|
734
897
|
type: "object",
|
|
@@ -745,6 +908,7 @@ const toolDefinitions = [
|
|
|
745
908
|
},
|
|
746
909
|
{
|
|
747
910
|
name: "affected_tests",
|
|
911
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
748
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).",
|
|
749
913
|
inputSchema: {
|
|
750
914
|
type: "object",
|
|
@@ -753,9 +917,24 @@ const toolDefinitions = [
|
|
|
753
917
|
baseRef: { type: "string", description: "Additional git ref to diff against, e.g. 'main'. The working tree is always included." },
|
|
754
918
|
},
|
|
755
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
|
+
},
|
|
756
934
|
},
|
|
757
935
|
{
|
|
758
936
|
name: "refactor",
|
|
937
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
759
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.",
|
|
760
939
|
inputSchema: {
|
|
761
940
|
type: "object",
|
|
@@ -773,6 +952,7 @@ const toolDefinitions = [
|
|
|
773
952
|
},
|
|
774
953
|
{
|
|
775
954
|
name: "smart_file_slice",
|
|
955
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
776
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.",
|
|
777
957
|
inputSchema: {
|
|
778
958
|
type: "object",
|
|
@@ -786,6 +966,7 @@ const toolDefinitions = [
|
|
|
786
966
|
},
|
|
787
967
|
{
|
|
788
968
|
name: "test_oracle",
|
|
969
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
789
970
|
description: "Execute unit tests locally and output ONLY the failing tests and compressed stack traces, keeping context clean.",
|
|
790
971
|
inputSchema: {
|
|
791
972
|
type: "object",
|
|
@@ -796,6 +977,7 @@ const toolDefinitions = [
|
|
|
796
977
|
},
|
|
797
978
|
{
|
|
798
979
|
name: "schema_extract",
|
|
980
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
799
981
|
description: "Extract high-level schemas, Zod types, interfaces, or database models from a file using the TypeScript AST, removing all implementation code.",
|
|
800
982
|
inputSchema: {
|
|
801
983
|
type: "object",
|
|
@@ -807,6 +989,7 @@ const toolDefinitions = [
|
|
|
807
989
|
},
|
|
808
990
|
{
|
|
809
991
|
name: "changelog_auto",
|
|
992
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
810
993
|
description: "Generate a clean, token-efficient changelog summary from git logs based on Conventional Commits.",
|
|
811
994
|
inputSchema: {
|
|
812
995
|
type: "object",
|
|
@@ -817,6 +1000,7 @@ const toolDefinitions = [
|
|
|
817
1000
|
},
|
|
818
1001
|
{
|
|
819
1002
|
name: "spec_to_stub",
|
|
1003
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
820
1004
|
description: "Parse a TypeScript interface/type definition from a file and output a template mock/stub object configuration using pure AST analysis.",
|
|
821
1005
|
inputSchema: {
|
|
822
1006
|
type: "object",
|
|
@@ -829,6 +1013,7 @@ const toolDefinitions = [
|
|
|
829
1013
|
},
|
|
830
1014
|
{
|
|
831
1015
|
name: "env_snapshot",
|
|
1016
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
832
1017
|
description: "Capture environmental state like Node version, dependencies in package.json, and variables declared in .env files (hiding actual values/credentials).",
|
|
833
1018
|
inputSchema: {
|
|
834
1019
|
type: "object",
|
|
@@ -837,6 +1022,7 @@ const toolDefinitions = [
|
|
|
837
1022
|
},
|
|
838
1023
|
{
|
|
839
1024
|
name: "migration_tracer",
|
|
1025
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
840
1026
|
description: "Inspect changes in database schemas (tables, columns) across local migrations (e.g. Prisma migration directories).",
|
|
841
1027
|
inputSchema: {
|
|
842
1028
|
type: "object",
|
|
@@ -847,6 +1033,7 @@ const toolDefinitions = [
|
|
|
847
1033
|
},
|
|
848
1034
|
{
|
|
849
1035
|
name: "multi_file_patch",
|
|
1036
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
850
1037
|
description: "Apply search-and-replace patches to multiple workspace files in a single turn. Decreases round-trips for multi-file refactoring.",
|
|
851
1038
|
inputSchema: {
|
|
852
1039
|
type: "object",
|
|
@@ -871,6 +1058,7 @@ const toolDefinitions = [
|
|
|
871
1058
|
},
|
|
872
1059
|
{
|
|
873
1060
|
name: "file_intent_index",
|
|
1061
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
874
1062
|
description: "Map and index files inside the workspace by domain intent (e.g., auth, billing, routes, config) to speed up navigation.",
|
|
875
1063
|
inputSchema: {
|
|
876
1064
|
type: "object",
|
|
@@ -881,6 +1069,7 @@ const toolDefinitions = [
|
|
|
881
1069
|
},
|
|
882
1070
|
{
|
|
883
1071
|
name: "cognitive_map",
|
|
1072
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
884
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.",
|
|
885
1074
|
inputSchema: {
|
|
886
1075
|
type: "object",
|
|
@@ -903,6 +1092,7 @@ const toolDefinitions = [
|
|
|
903
1092
|
},
|
|
904
1093
|
{
|
|
905
1094
|
name: "semantic_grep",
|
|
1095
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
906
1096
|
description: "Search the codebase for conceptual terms using natural language and BM25 token relevance instead of exact substring matching.",
|
|
907
1097
|
inputSchema: {
|
|
908
1098
|
type: "object",
|
|
@@ -916,6 +1106,7 @@ const toolDefinitions = [
|
|
|
916
1106
|
},
|
|
917
1107
|
{
|
|
918
1108
|
name: "imports_skeleton_resolver",
|
|
1109
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
919
1110
|
description: "Resolve all imported local files inside a file and print only their signatures (classes, functions, interfaces) in a consolidated view.",
|
|
920
1111
|
inputSchema: {
|
|
921
1112
|
type: "object",
|
|
@@ -927,6 +1118,7 @@ const toolDefinitions = [
|
|
|
927
1118
|
},
|
|
928
1119
|
{
|
|
929
1120
|
name: "ast_flow_visualizer",
|
|
1121
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
930
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.",
|
|
931
1123
|
inputSchema: {
|
|
932
1124
|
type: "object",
|
|
@@ -939,6 +1131,7 @@ const toolDefinitions = [
|
|
|
939
1131
|
},
|
|
940
1132
|
{
|
|
941
1133
|
name: "file_fingerprint",
|
|
1134
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
942
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.",
|
|
943
1136
|
inputSchema: {
|
|
944
1137
|
type: "object",
|
|
@@ -951,6 +1144,7 @@ const toolDefinitions = [
|
|
|
951
1144
|
},
|
|
952
1145
|
{
|
|
953
1146
|
name: "git_blame_heat",
|
|
1147
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
954
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.",
|
|
955
1149
|
inputSchema: {
|
|
956
1150
|
type: "object",
|
|
@@ -962,6 +1156,7 @@ const toolDefinitions = [
|
|
|
962
1156
|
},
|
|
963
1157
|
{
|
|
964
1158
|
name: "type_coverage_report",
|
|
1159
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
965
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.",
|
|
966
1161
|
inputSchema: {
|
|
967
1162
|
type: "object",
|
|
@@ -973,6 +1168,7 @@ const toolDefinitions = [
|
|
|
973
1168
|
},
|
|
974
1169
|
{
|
|
975
1170
|
name: "import_tree_context",
|
|
1171
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
976
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.",
|
|
977
1173
|
inputSchema: {
|
|
978
1174
|
type: "object",
|
|
@@ -985,6 +1181,7 @@ const toolDefinitions = [
|
|
|
985
1181
|
},
|
|
986
1182
|
{
|
|
987
1183
|
name: "read_token_budgeted",
|
|
1184
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
988
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.",
|
|
989
1186
|
inputSchema: {
|
|
990
1187
|
type: "object",
|
|
@@ -998,6 +1195,7 @@ const toolDefinitions = [
|
|
|
998
1195
|
},
|
|
999
1196
|
{
|
|
1000
1197
|
name: "bulk_file_digest",
|
|
1198
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1001
1199
|
description: "Read multiple files in one call and return a compressed digest. Saves 80% round-trips vs reading files individually.",
|
|
1002
1200
|
inputSchema: {
|
|
1003
1201
|
type: "object",
|
|
@@ -1015,6 +1213,7 @@ const toolDefinitions = [
|
|
|
1015
1213
|
},
|
|
1016
1214
|
{
|
|
1017
1215
|
name: "auto_context_bundle",
|
|
1216
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
1018
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.",
|
|
1019
1218
|
inputSchema: {
|
|
1020
1219
|
type: "object",
|
|
@@ -1026,6 +1225,7 @@ const toolDefinitions = [
|
|
|
1026
1225
|
},
|
|
1027
1226
|
{
|
|
1028
1227
|
name: "repeat_guard",
|
|
1228
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1029
1229
|
description: "Check if a draft output is repetitive compared to recent responses. Returns similarity score and suggestion. Prevents duplicate explanations.",
|
|
1030
1230
|
inputSchema: {
|
|
1031
1231
|
type: "object",
|
|
@@ -1038,6 +1238,7 @@ const toolDefinitions = [
|
|
|
1038
1238
|
},
|
|
1039
1239
|
{
|
|
1040
1240
|
name: "conversation_checkpoint",
|
|
1241
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1041
1242
|
description: "Save or load session state. Prevents re-explaining context across turns. Save current progress or load last checkpoint.",
|
|
1042
1243
|
inputSchema: {
|
|
1043
1244
|
type: "object",
|
|
@@ -1053,6 +1254,7 @@ const toolDefinitions = [
|
|
|
1053
1254
|
},
|
|
1054
1255
|
{
|
|
1055
1256
|
name: "test_autofix_interceptor",
|
|
1257
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1056
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.",
|
|
1057
1259
|
inputSchema: {
|
|
1058
1260
|
type: "object",
|
|
@@ -1063,6 +1265,7 @@ const toolDefinitions = [
|
|
|
1063
1265
|
},
|
|
1064
1266
|
{
|
|
1065
1267
|
name: "prompt_pattern_cache",
|
|
1268
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1066
1269
|
description: "Find or store optimized prompt templates. Reuses past prompt patterns to save 20-30% tokens on repetitive operations.",
|
|
1067
1270
|
inputSchema: {
|
|
1068
1271
|
type: "object",
|
|
@@ -1082,6 +1285,7 @@ const _infraToolDefs = getInfraToolDefinitions();
|
|
|
1082
1285
|
// than in the main array because it must never itself be hidden.
|
|
1083
1286
|
const TOOLBOX_TOOL_DEFINITION = {
|
|
1084
1287
|
name: "lemma_toolbox",
|
|
1288
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
1085
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.",
|
|
1086
1290
|
inputSchema: {
|
|
1087
1291
|
type: "object",
|
|
@@ -1287,6 +1491,7 @@ function receiptLabelMeta(name, args) {
|
|
|
1287
1491
|
return meta;
|
|
1288
1492
|
}
|
|
1289
1493
|
export function setupToolsHandlers(server, onToolCall) {
|
|
1494
|
+
mcpServerRef = server;
|
|
1290
1495
|
registerSymbolExtractor(extractSymbolsWithTsCompiler);
|
|
1291
1496
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1292
1497
|
tools: toolDefinitionsArray,
|
|
@@ -1483,7 +1688,8 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1483
1688
|
const intendedModel = args?.intended_model;
|
|
1484
1689
|
const router = new ComplexityRouter();
|
|
1485
1690
|
const decision = router.evaluate(prompt, intendedModel);
|
|
1486
|
-
|
|
1691
|
+
const switched = !!intendedModel && decision.model !== intendedModel;
|
|
1692
|
+
if (decision.complexity === "low" && switched) {
|
|
1487
1693
|
const estimatedPromptTokens = Math.floor((prompt?.length || 0) / 4);
|
|
1488
1694
|
reportSavings({
|
|
1489
1695
|
source: "complexityRouting",
|
|
@@ -1492,6 +1698,12 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1492
1698
|
query: prompt?.substring(0, 100),
|
|
1493
1699
|
});
|
|
1494
1700
|
}
|
|
1701
|
+
const structured = {
|
|
1702
|
+
recommendedModel: decision.model,
|
|
1703
|
+
complexity: decision.complexity,
|
|
1704
|
+
intendedModel: intendedModel || null,
|
|
1705
|
+
switched,
|
|
1706
|
+
};
|
|
1495
1707
|
return {
|
|
1496
1708
|
content: [
|
|
1497
1709
|
{
|
|
@@ -1499,6 +1711,7 @@ async function handleGetRoutingAdvice(args) {
|
|
|
1499
1711
|
text: `Lemma Routing Advice: Use ${decision.model}. Reason: Complexity is ${decision.complexity}.`,
|
|
1500
1712
|
},
|
|
1501
1713
|
],
|
|
1714
|
+
structuredContent: structured,
|
|
1502
1715
|
};
|
|
1503
1716
|
}
|
|
1504
1717
|
async function handleAutoHeal(args) {
|
|
@@ -1771,6 +1984,11 @@ async function handleApplyWorkspacePatch(args) {
|
|
|
1771
1984
|
const updatedLines = [...contentLines.slice(0, match.startLine), ...finalReplace.split("\n"), ...contentLines.slice(match.endLine + 1)];
|
|
1772
1985
|
updatedContent = updatedLines.join("\n");
|
|
1773
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
|
+
}
|
|
1774
1992
|
fs.writeFileSync(resolved, updatedContent, "utf8");
|
|
1775
1993
|
const note = match.strategy === "exact" ? "" : ` (matched via ${match.strategy}, score ${match.score.toFixed(2)})`;
|
|
1776
1994
|
return { content: [{ type: "text", text: `Success: Patch successfully applied to ${filePath}${note}` }] };
|
|
@@ -1820,6 +2038,10 @@ async function handleRunWorkspaceCommand(args) {
|
|
|
1820
2038
|
const MAX_TIMEOUT_MS = 600000;
|
|
1821
2039
|
const requested = typeof args?.timeoutMs === "number" && args.timeoutMs > 0 ? args.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1822
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
|
+
}
|
|
1823
2045
|
const result = spawnSync(command, {
|
|
1824
2046
|
cwd: workspaceRoot,
|
|
1825
2047
|
shell: true,
|
|
@@ -2842,47 +3064,44 @@ async function handleSmarterCache(args) {
|
|
|
2842
3064
|
reportSavings({ source: "cache", tokens: tokensSaved, toolName: "smarter_cache", query: query.substring(0, 100) });
|
|
2843
3065
|
}
|
|
2844
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
|
+
};
|
|
2845
3076
|
return {
|
|
2846
|
-
content: [{
|
|
2847
|
-
|
|
2848
|
-
text: JSON.stringify({
|
|
2849
|
-
hit: true,
|
|
2850
|
-
similarity: topHit.similarity,
|
|
2851
|
-
threshold,
|
|
2852
|
-
answer: responseText,
|
|
2853
|
-
source: "lemma-brain",
|
|
2854
|
-
tokensSaved,
|
|
2855
|
-
tokensSavedFormatted: `~${tokensSaved.toLocaleString()} tokens`,
|
|
2856
|
-
}, null, 2),
|
|
2857
|
-
}],
|
|
3077
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3078
|
+
structuredContent: structured,
|
|
2858
3079
|
};
|
|
2859
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
|
+
};
|
|
2860
3089
|
return {
|
|
2861
|
-
content: [{
|
|
2862
|
-
|
|
2863
|
-
text: JSON.stringify({
|
|
2864
|
-
hit: false,
|
|
2865
|
-
similarity: topHit?.similarity || 0,
|
|
2866
|
-
threshold,
|
|
2867
|
-
answer: null,
|
|
2868
|
-
source: "llm-call-required",
|
|
2869
|
-
hint: "Después de resolver, llama store_memory para cachear y no gastar tokens en esto otra vez.",
|
|
2870
|
-
}, null, 2),
|
|
2871
|
-
}],
|
|
3090
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3091
|
+
structuredContent: structured,
|
|
2872
3092
|
};
|
|
2873
3093
|
}
|
|
2874
3094
|
catch (e) {
|
|
2875
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
|
+
};
|
|
2876
3102
|
return {
|
|
2877
|
-
content: [{
|
|
2878
|
-
|
|
2879
|
-
text: JSON.stringify({
|
|
2880
|
-
hit: false,
|
|
2881
|
-
error: e.message,
|
|
2882
|
-
source: "cache-unavailable",
|
|
2883
|
-
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
2884
|
-
}, null, 2),
|
|
2885
|
-
}],
|
|
3103
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3104
|
+
structuredContent: structured,
|
|
2886
3105
|
};
|
|
2887
3106
|
}
|
|
2888
3107
|
}
|
|
@@ -2903,23 +3122,21 @@ async function handleStateHashCache(args) {
|
|
|
2903
3122
|
reportSavings({ source: "cache", tokens: tokensSaved, toolName: "state_hash_cache", query: query.substring(0, 100) });
|
|
2904
3123
|
}
|
|
2905
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
|
+
};
|
|
2906
3131
|
return {
|
|
2907
|
-
content: [{
|
|
2908
|
-
|
|
2909
|
-
text: JSON.stringify({
|
|
2910
|
-
status: "hit",
|
|
2911
|
-
answer: result.entry.answer,
|
|
2912
|
-
note: "Respuesta desde caché exacto (hash de archivos sin cambios) — no necesita re-razonar.",
|
|
2913
|
-
tokensSaved,
|
|
2914
|
-
}, null, 2),
|
|
2915
|
-
}],
|
|
3132
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3133
|
+
structuredContent: structured,
|
|
2916
3134
|
};
|
|
2917
3135
|
}
|
|
3136
|
+
const structured = { status: "miss", reason: result.reason, hint: "Razona normalmente y luego llama action='store' con la respuesta." };
|
|
2918
3137
|
return {
|
|
2919
|
-
content: [{
|
|
2920
|
-
|
|
2921
|
-
text: JSON.stringify({ status: "miss", reason: result.reason, hint: "Razona normalmente y luego llama action='store' con la respuesta." }, null, 2),
|
|
2922
|
-
}],
|
|
3138
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3139
|
+
structuredContent: structured,
|
|
2923
3140
|
};
|
|
2924
3141
|
}
|
|
2925
3142
|
if (action === "store") {
|
|
@@ -2927,8 +3144,10 @@ async function handleStateHashCache(args) {
|
|
|
2927
3144
|
if (!answer)
|
|
2928
3145
|
throw new Error("answer is required for action='store'");
|
|
2929
3146
|
const entry = storeStateHash(workspaceRoot, query, answer, filePaths);
|
|
3147
|
+
const structured = { status: "stored", id: entry.id, filesTracked: Object.keys(entry.fileHashes) };
|
|
2930
3148
|
return {
|
|
2931
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
3149
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3150
|
+
structuredContent: structured,
|
|
2932
3151
|
};
|
|
2933
3152
|
}
|
|
2934
3153
|
throw new Error(`Unknown action: ${action}. Use 'lookup' or 'store'.`);
|
|
@@ -2937,16 +3156,15 @@ async function handleStateHashCache(args) {
|
|
|
2937
3156
|
async function handleTokenReceipt(args) {
|
|
2938
3157
|
const limit = typeof args?.limit === "number" ? args.limit : 20;
|
|
2939
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
|
+
};
|
|
2940
3165
|
return {
|
|
2941
|
-
content: [{
|
|
2942
|
-
|
|
2943
|
-
text: JSON.stringify({
|
|
2944
|
-
totalEvents: summary.totalEvents,
|
|
2945
|
-
byType: summary.byType,
|
|
2946
|
-
recentEvents: summary.recent,
|
|
2947
|
-
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.",
|
|
2948
|
-
}, null, 2),
|
|
2949
|
-
}],
|
|
3166
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
3167
|
+
structuredContent: structured,
|
|
2950
3168
|
};
|
|
2951
3169
|
}
|
|
2952
3170
|
// ── Token Budget ─────────────────────────────────────────────────
|
|
@@ -4230,7 +4448,17 @@ async function handleAffectedTests(args) {
|
|
|
4230
4448
|
totalTests: result.totalTests,
|
|
4231
4449
|
narrowed: result.command !== null,
|
|
4232
4450
|
});
|
|
4233
|
-
|
|
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 };
|
|
4234
4462
|
}
|
|
4235
4463
|
catch (err) {
|
|
4236
4464
|
logError("affected_tests", err);
|