@nxuss/lemma 1.7.2 → 1.8.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 +69 -8
- package/bin/brain-ingest.js +56 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +132 -33
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.d.ts +11 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.d.ts.map +1 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.js +18 -23
- package/dist/cjs/pr-review/bridge/BrainBridge.js.map +1 -1
- package/dist/cjs/protocol/utils.d.ts.map +1 -1
- package/dist/cjs/protocol/utils.js +6 -5
- package/dist/cjs/protocol/utils.js.map +1 -1
- package/dist/cjs/subconscious/GitIngest.d.ts +48 -0
- package/dist/cjs/subconscious/GitIngest.d.ts.map +1 -0
- package/dist/cjs/subconscious/GitIngest.js +175 -0
- package/dist/cjs/subconscious/GitIngest.js.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts +23 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +124 -17
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.d.ts +2 -0
- package/dist/cjs/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.js +38 -0
- package/dist/cjs/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +134 -35
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/pr-review/bridge/BrainBridge.d.ts +11 -1
- package/dist/esm/pr-review/bridge/BrainBridge.d.ts.map +1 -1
- package/dist/esm/pr-review/bridge/BrainBridge.js +18 -20
- package/dist/esm/pr-review/bridge/BrainBridge.js.map +1 -1
- package/dist/esm/protocol/utils.d.ts.map +1 -1
- package/dist/esm/protocol/utils.js +6 -5
- package/dist/esm/protocol/utils.js.map +1 -1
- package/dist/esm/subconscious/GitIngest.d.ts +48 -0
- package/dist/esm/subconscious/GitIngest.d.ts.map +1 -0
- package/dist/esm/subconscious/GitIngest.js +166 -0
- package/dist/esm/subconscious/GitIngest.js.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts +23 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +124 -17
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.d.ts +2 -0
- package/dist/esm/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.js +37 -0
- package/dist/esm/utils/ConversationCheckpoint.js.map +1 -1
- package/package.json +4 -2
package/dist/esm/mcp/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { execSync, spawnSync } from "child_process";
|
|
4
|
+
import { execSync, execFileSync, spawnSync } from "child_process";
|
|
5
5
|
import os from "os";
|
|
6
6
|
import axios from "axios";
|
|
7
7
|
import * as ts from "typescript";
|
|
@@ -18,7 +18,7 @@ import { readTokenBudgeted } from "../utils/TokenBudgetedReader.js";
|
|
|
18
18
|
import { bulkFileDigest } from "../utils/BulkFileDigest.js";
|
|
19
19
|
import { autoContextBundle } from "../utils/AutoContextBundle.js";
|
|
20
20
|
import { checkRepeat, recordOutput } from "../utils/RepeatGuard.js";
|
|
21
|
-
import { saveCheckpoint, loadLatestCheckpoint, buildCheckpointSummary } from "../utils/ConversationCheckpoint.js";
|
|
21
|
+
import { saveCheckpoint, loadLatestCheckpoint, loadRecentCheckpoints, buildCheckpointSummary } from "../utils/ConversationCheckpoint.js";
|
|
22
22
|
import { runTestAutofix } from "../utils/TestAutofixInterceptor.js";
|
|
23
23
|
import { findPattern, storePattern, getPatternStats } from "../utils/PromptPatternCache.js";
|
|
24
24
|
import { runAnalysis, parseDiff } from "../pr-review/PRReviewEngine.js";
|
|
@@ -58,7 +58,7 @@ function isProUser() {
|
|
|
58
58
|
}
|
|
59
59
|
const FREE_TOOLS = new Set([
|
|
60
60
|
// Cache & Memory — the hook that shows instant value
|
|
61
|
-
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory",
|
|
61
|
+
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory", "get_project_history",
|
|
62
62
|
// Token optimization — shows what they're saving
|
|
63
63
|
"token_budget", "squeeze_prompt", "turbosqueeze",
|
|
64
64
|
// Utility — just enough to function
|
|
@@ -114,7 +114,7 @@ const toolDefinitions = [
|
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
name: "store_memory",
|
|
117
|
-
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.",
|
|
117
|
+
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.",
|
|
118
118
|
inputSchema: {
|
|
119
119
|
type: "object",
|
|
120
120
|
properties: {
|
|
@@ -122,6 +122,19 @@ const toolDefinitions = [
|
|
|
122
122
|
response: { type: "string", description: "The complete technical content to memorize" },
|
|
123
123
|
provider: { type: "string", description: "Optional model provider name", default: "generic" },
|
|
124
124
|
filePaths: { type: "array", items: { type: "string" }, description: "Paths (relative to project root) this answer depends on. If any changes later, this memory is marked stale instead of being silently reused." },
|
|
125
|
+
symbols: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
filePath: { type: "string", description: "Path (relative to project root) containing the symbol" },
|
|
131
|
+
symbolName: { type: "string", description: "Function/class/interface/type/enum/const name" },
|
|
132
|
+
},
|
|
133
|
+
required: ["filePath", "symbolName"],
|
|
134
|
+
},
|
|
135
|
+
description: "Symbols (not whole files) this answer depends on. Freshness is then judged by that symbol's own source, so edits elsewhere in the same file don't stale this memory out.",
|
|
136
|
+
},
|
|
137
|
+
outcome: { type: "string", enum: ["confirmed", "failed"], description: "Default 'confirmed' (a verified working solution). Set 'failed' to record a dead end — an approach ruled out, not one to recommend." },
|
|
125
138
|
},
|
|
126
139
|
required: ["query", "response"],
|
|
127
140
|
},
|
|
@@ -273,6 +286,16 @@ const toolDefinitions = [
|
|
|
273
286
|
properties: {},
|
|
274
287
|
},
|
|
275
288
|
},
|
|
289
|
+
{
|
|
290
|
+
name: "get_project_history",
|
|
291
|
+
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.",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
limit: { type: "number", description: "Max items per section (commits, checkpoints, Brain memories)", default: 10 },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
276
299
|
{
|
|
277
300
|
name: "get_ast_hologram",
|
|
278
301
|
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.",
|
|
@@ -1121,6 +1144,7 @@ const toolHandlers = {
|
|
|
1121
1144
|
auto_context_bundle: handleAutoContextBundle,
|
|
1122
1145
|
repeat_guard: handleRepeatGuard,
|
|
1123
1146
|
conversation_checkpoint: handleConversationCheckpoint,
|
|
1147
|
+
get_project_history: handleGetProjectHistory,
|
|
1124
1148
|
test_autofix_interceptor: handleTestAutofixInterceptor,
|
|
1125
1149
|
prompt_pattern_cache: handlePromptPatternCache,
|
|
1126
1150
|
lemma_toolbox: handleToolbox,
|
|
@@ -1306,7 +1330,7 @@ async function handleSearchMemory(args) {
|
|
|
1306
1330
|
}
|
|
1307
1331
|
const fresh = results.filter((r) => r.fresh);
|
|
1308
1332
|
const stale = results.filter((r) => !r.fresh);
|
|
1309
|
-
const formatResult = (r, i) => `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.query.substring(0, 300)}...\nResponse: ${typeof r.response === "string" ? r.response : JSON.stringify(r.response, null, 2)}`;
|
|
1333
|
+
const formatResult = (r, i) => `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)${r.outcome === "failed" ? "\n⚠️ TAGGED AS A FAILED ATTEMPT — this was tried before and did NOT work. Do not repeat it; treat this as a warning, not a suggestion." : ""}\nPrompt: ${r.query.substring(0, 300)}...\nResponse: ${typeof r.response === "string" ? r.response : JSON.stringify(r.response, null, 2)}`;
|
|
1310
1334
|
if (fresh.length > 0) {
|
|
1311
1335
|
// Only a fresh hit (or an untracked, purely conceptual entry) counts as a real
|
|
1312
1336
|
// avoided re-investigation — this is the only case worth crediting as savings.
|
|
@@ -1347,11 +1371,15 @@ async function handleStoreMemory(args) {
|
|
|
1347
1371
|
const responseText = args?.response;
|
|
1348
1372
|
const provider = args?.provider || "generic";
|
|
1349
1373
|
const filePaths = Array.isArray(args?.filePaths) ? args.filePaths : undefined;
|
|
1374
|
+
const outcome = args?.outcome === "failed" ? "failed" : undefined;
|
|
1375
|
+
const symbols = Array.isArray(args?.symbols)
|
|
1376
|
+
? args.symbols.filter((s) => s?.filePath && s?.symbolName)
|
|
1377
|
+
: undefined;
|
|
1350
1378
|
if (!query || !responseText)
|
|
1351
1379
|
throw new Error("Query and response are required");
|
|
1352
1380
|
try {
|
|
1353
1381
|
const brain = getBrain();
|
|
1354
|
-
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths);
|
|
1382
|
+
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths, undefined, outcome, symbols);
|
|
1355
1383
|
// Storing a memory saves nothing — it only creates the chance of a saving later.
|
|
1356
1384
|
// Crediting tokens here inflated the ledger on write and then credited the same
|
|
1357
1385
|
// answer again on every read. The saving is booked by search_memory on a fresh hit.
|
|
@@ -1361,7 +1389,9 @@ async function handleStoreMemory(args) {
|
|
|
1361
1389
|
note: "memory written — no tokens saved yet",
|
|
1362
1390
|
});
|
|
1363
1391
|
const trackingNote = filePaths && filePaths.length > 0 ? ` Tracking freshness against ${filePaths.length} file(s) — this memory auto-invalidates if they change.` : "";
|
|
1364
|
-
|
|
1392
|
+
const symbolNote = symbols && symbols.length > 0 ? ` Tracking freshness against ${symbols.length} symbol(s) specifically — unrelated edits elsewhere in those files won't stale this out.` : "";
|
|
1393
|
+
const outcomeNote = outcome === "failed" ? " Tagged as a FAILED attempt — future searches will surface it as a warning, not a suggestion." : "";
|
|
1394
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}${trackingNote}${symbolNote}${outcomeNote}` }] };
|
|
1365
1395
|
}
|
|
1366
1396
|
catch (e) {
|
|
1367
1397
|
logError("store_memory", e);
|
|
@@ -1880,6 +1910,59 @@ async function handleGetProjectOnboarding(_args) {
|
|
|
1880
1910
|
],
|
|
1881
1911
|
};
|
|
1882
1912
|
}
|
|
1913
|
+
// ── Project History (aggregate: git log + checkpoints + Brain) ─────
|
|
1914
|
+
/**
|
|
1915
|
+
* "What have we done in this project" today means piecing together search_memory, git log,
|
|
1916
|
+
* and manually reading .lemma/session/ checkpoint files by hand — three separate calls whose
|
|
1917
|
+
* results the model has to merge itself. This does that merge in one call.
|
|
1918
|
+
*/
|
|
1919
|
+
async function handleGetProjectHistory(args) {
|
|
1920
|
+
const cwd = process.cwd();
|
|
1921
|
+
const limit = typeof args?.limit === "number" ? args.limit : 10;
|
|
1922
|
+
try {
|
|
1923
|
+
const projectId = deriveProjectId(cwd);
|
|
1924
|
+
const brainEntries = getBrain()
|
|
1925
|
+
.getEntriesForProject(projectId)
|
|
1926
|
+
.sort((a, b) => (b.hits - a.hits) || (new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()))
|
|
1927
|
+
.slice(0, limit);
|
|
1928
|
+
const checkpoints = loadRecentCheckpoints(cwd, Math.min(limit, 5));
|
|
1929
|
+
let recentCommits = [];
|
|
1930
|
+
try {
|
|
1931
|
+
const log = execFileSync("git", ["-C", cwd, "log", `-${limit}`, "--pretty=format:%h %s (%ar)"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
1932
|
+
recentCommits = log.split("\n").filter(Boolean);
|
|
1933
|
+
}
|
|
1934
|
+
catch {
|
|
1935
|
+
// Not a git repo, or no commits yet — sections below just won't include this one.
|
|
1936
|
+
}
|
|
1937
|
+
const parts = [`# Project history — ${path.basename(cwd)}`];
|
|
1938
|
+
if (recentCommits.length > 0) {
|
|
1939
|
+
parts.push(`## Recent commits\n${recentCommits.map((c) => `- ${c}`).join("\n")}`);
|
|
1940
|
+
}
|
|
1941
|
+
if (checkpoints.length > 0) {
|
|
1942
|
+
const checkpointLines = checkpoints.map((cp) => {
|
|
1943
|
+
const date = new Date(cp.timestamp).toISOString().replace("T", " ").slice(0, 16);
|
|
1944
|
+
return `- **${date}** — ${cp.summary.replace(/\n+/g, " ")}`;
|
|
1945
|
+
});
|
|
1946
|
+
parts.push(`## Session checkpoints\n${checkpointLines.join("\n")}`);
|
|
1947
|
+
}
|
|
1948
|
+
if (brainEntries.length > 0) {
|
|
1949
|
+
const brainLines = brainEntries.map((e) => {
|
|
1950
|
+
const tag = e.outcome === "failed" ? " ⚠️ FAILED ATTEMPT" : "";
|
|
1951
|
+
const snippet = e.response.length > 300 ? `${e.response.substring(0, 300)}...` : e.response;
|
|
1952
|
+
return `- **${e.query}**${tag} _(${e.provider}, ${e.hits} hit(s))_\n ${snippet}`;
|
|
1953
|
+
});
|
|
1954
|
+
parts.push(`## Brain memories (decisions, fixes, prior reviews)\n${brainLines.join("\n")}`);
|
|
1955
|
+
}
|
|
1956
|
+
if (parts.length === 1) {
|
|
1957
|
+
parts.push("Nothing recorded yet — no git history, checkpoints, or Brain memories found for this project.");
|
|
1958
|
+
}
|
|
1959
|
+
return { content: [{ type: "text", text: parts.join("\n\n") }] };
|
|
1960
|
+
}
|
|
1961
|
+
catch (e) {
|
|
1962
|
+
logError("get_project_history", e);
|
|
1963
|
+
return { content: [{ type: "text", text: `get_project_history failed: ${e.message}` }] };
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1883
1966
|
function extractSymbolsWithTsCompiler(filePath, relPath, parseErrors) {
|
|
1884
1967
|
try {
|
|
1885
1968
|
const src = fs.readFileSync(filePath, "utf8");
|
|
@@ -2200,7 +2283,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
2200
2283
|
const similarity = (r.similarity * 100).toFixed(1);
|
|
2201
2284
|
const prompt = r.query || "Unknown";
|
|
2202
2285
|
const responseContent = r.response || "";
|
|
2203
|
-
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
2286
|
+
hintsText += `### Hint ${i + 1} (${similarity}% match)${r.outcome === "failed" ? " — ⚠️ FAILED ATTEMPT, do not repeat" : ""}\n`;
|
|
2204
2287
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
2205
2288
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
2206
2289
|
});
|
|
@@ -2549,48 +2632,64 @@ async function handleTokenReceipt(args) {
|
|
|
2549
2632
|
}
|
|
2550
2633
|
// ── Token Budget ─────────────────────────────────────────────────
|
|
2551
2634
|
async function handleTokenBudget(_args) {
|
|
2552
|
-
const statsFile = path.join(os.homedir(), '.lemma-cache/stats.json');
|
|
2553
2635
|
const port = getProxyPort();
|
|
2554
|
-
|
|
2636
|
+
const schemaTokens = Math.round(getToolSchemaChars() / 4);
|
|
2637
|
+
// /api/savings-breakdown serves SavingsLedger.getSnapshot() — the same ledger that
|
|
2638
|
+
// already nets tokensSaved against what Lemma itself spent (MCP schema injection,
|
|
2639
|
+
// tool-result bytes, cache misses). There is no separate "membership multiplier" to
|
|
2640
|
+
// compute: reusing that one number keeps this tool from reporting a bigger win than
|
|
2641
|
+
// the ledger the user can audit via `token_receipt` actually shows.
|
|
2642
|
+
let snapshot = null;
|
|
2555
2643
|
try {
|
|
2556
|
-
|
|
2644
|
+
const resp = await axios.get(`http://localhost:${port}/api/savings-breakdown`);
|
|
2645
|
+
snapshot = resp.data;
|
|
2557
2646
|
}
|
|
2558
2647
|
catch { }
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2648
|
+
if (!snapshot) {
|
|
2649
|
+
return {
|
|
2650
|
+
content: [{
|
|
2651
|
+
type: "text",
|
|
2652
|
+
text: JSON.stringify({
|
|
2653
|
+
note: "El proxy de Lemma no está corriendo, así que no hay bitácora de ahorro/costo que leer — no se inventa un número aquí. Arranca el proxy para que este reporte sea real.",
|
|
2654
|
+
perTurnOverhead: { ...getToolSurfaceSummary(), schemaTokens },
|
|
2655
|
+
}, null, 2),
|
|
2656
|
+
}],
|
|
2657
|
+
};
|
|
2563
2658
|
}
|
|
2564
|
-
|
|
2565
|
-
const
|
|
2566
|
-
const monthlyTokens = stats.monthlyTokens || stats.totalTokens || 100000;
|
|
2567
|
-
const multiplier = monthlyTokens > 0 ? ((monthlyTokens + totalTokensSaved) / monthlyTokens).toFixed(1) : "N/A";
|
|
2568
|
-
const toolCallCount = ledger.length || stats.toolCalls || 0;
|
|
2659
|
+
const { total, totalCost, net } = snapshot;
|
|
2660
|
+
const ratioStr = net.ratio === Infinity ? "n/a (sin costo registrado todavía)" : `${net.ratio.toFixed(2)}x`;
|
|
2569
2661
|
return {
|
|
2570
2662
|
content: [{
|
|
2571
2663
|
type: "text",
|
|
2572
2664
|
text: JSON.stringify({
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2665
|
+
netRatio: ratioStr,
|
|
2666
|
+
note: "netRatio = tokensSaved / tokensSpent, ambos de la misma bitácora auditable que expone token_receipt. Por debajo de 1.0x, Lemma está costando más de lo que ahorra en esta sesión.",
|
|
2667
|
+
tokensSaved: total.tokensSaved.toLocaleString(),
|
|
2668
|
+
tokensSpent: totalCost.tokensSpent.toLocaleString(),
|
|
2669
|
+
netTokens: net.netTokens.toLocaleString(),
|
|
2670
|
+
netCostUSD: net.netCost.toFixed(4),
|
|
2671
|
+
breakdown: {
|
|
2672
|
+
saved: {
|
|
2673
|
+
cache: snapshot.cache?.tokensSaved ?? 0,
|
|
2674
|
+
contextSqueeze: snapshot.contextSqueeze?.tokensSaved ?? 0,
|
|
2675
|
+
historyPrune: snapshot.historyPrune?.tokensSaved ?? 0,
|
|
2676
|
+
complexityRouting: snapshot.complexityRouting?.tokensSaved ?? 0,
|
|
2677
|
+
clipboard: snapshot.clipboard?.tokensSaved ?? 0,
|
|
2678
|
+
},
|
|
2679
|
+
spent: {
|
|
2680
|
+
mcpInstructions: snapshot.costs?.mcpInstructions?.tokensSpent ?? 0,
|
|
2681
|
+
toolResult: snapshot.costs?.toolResult?.tokensSpent ?? 0,
|
|
2682
|
+
cacheMiss: snapshot.costs?.cacheMiss?.tokensSpent ?? 0,
|
|
2683
|
+
},
|
|
2585
2684
|
},
|
|
2586
2685
|
// The fixed cost of being connected, re-sent on every request of the session.
|
|
2587
2686
|
perTurnOverhead: {
|
|
2588
2687
|
...getToolSurfaceSummary(),
|
|
2589
|
-
schemaTokens
|
|
2688
|
+
schemaTokens,
|
|
2590
2689
|
note: "Hidden tools stay callable via lemma_toolbox. Set mcp.toolProfile='full' in lemma.config.json to advertise all of them.",
|
|
2591
2690
|
},
|
|
2592
|
-
recommendations:
|
|
2593
|
-
? ["
|
|
2691
|
+
recommendations: net.ratio !== Infinity && net.ratio < 1
|
|
2692
|
+
? ["netRatio < 1.0x: revisa si search_memory/smarter_cache se están llamando antes de razonar, o si el toolProfile anuncia más tools de las que este proyecto necesita — cada schema extra es costo fijo por turno."]
|
|
2594
2693
|
: ["Sigue usando turbosqueeze antes de cada code block grande.", "Usa smarter_cache antes de razonar."],
|
|
2595
2694
|
}, null, 2),
|
|
2596
2695
|
}],
|