@nxuss/lemma 1.7.2 → 1.8.1
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 +146 -179
- package/bin/brain-ingest.js +56 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +145 -37
- 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/security/SemanticScrubber.d.ts +2 -0
- package/dist/cjs/security/SemanticScrubber.d.ts.map +1 -1
- package/dist/cjs/security/SemanticScrubber.js +53 -13
- package/dist/cjs/security/SemanticScrubber.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/cjs/utils/PatchMatcher.d.ts +2 -0
- package/dist/cjs/utils/PatchMatcher.d.ts.map +1 -1
- package/dist/cjs/utils/PatchMatcher.js +9 -1
- package/dist/cjs/utils/PatchMatcher.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +147 -39
- 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/security/SemanticScrubber.d.ts +2 -0
- package/dist/esm/security/SemanticScrubber.d.ts.map +1 -1
- package/dist/esm/security/SemanticScrubber.js +53 -13
- package/dist/esm/security/SemanticScrubber.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/dist/esm/utils/PatchMatcher.d.ts +2 -0
- package/dist/esm/utils/PatchMatcher.d.ts.map +1 -1
- package/dist/esm/utils/PatchMatcher.js +9 -1
- package/dist/esm/utils/PatchMatcher.js.map +1 -1
- package/package.json +4 -2
package/dist/cjs/mcp/tools.js
CHANGED
|
@@ -101,7 +101,7 @@ function isProUser() {
|
|
|
101
101
|
}
|
|
102
102
|
const FREE_TOOLS = new Set([
|
|
103
103
|
// Cache & Memory — the hook that shows instant value
|
|
104
|
-
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory",
|
|
104
|
+
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory", "get_project_history",
|
|
105
105
|
// Token optimization — shows what they're saving
|
|
106
106
|
"token_budget", "squeeze_prompt", "turbosqueeze",
|
|
107
107
|
// Utility — just enough to function
|
|
@@ -157,7 +157,7 @@ const toolDefinitions = [
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
name: "store_memory",
|
|
160
|
-
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.",
|
|
160
|
+
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.",
|
|
161
161
|
inputSchema: {
|
|
162
162
|
type: "object",
|
|
163
163
|
properties: {
|
|
@@ -165,6 +165,19 @@ const toolDefinitions = [
|
|
|
165
165
|
response: { type: "string", description: "The complete technical content to memorize" },
|
|
166
166
|
provider: { type: "string", description: "Optional model provider name", default: "generic" },
|
|
167
167
|
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." },
|
|
168
|
+
symbols: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
filePath: { type: "string", description: "Path (relative to project root) containing the symbol" },
|
|
174
|
+
symbolName: { type: "string", description: "Function/class/interface/type/enum/const name" },
|
|
175
|
+
},
|
|
176
|
+
required: ["filePath", "symbolName"],
|
|
177
|
+
},
|
|
178
|
+
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.",
|
|
179
|
+
},
|
|
180
|
+
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." },
|
|
168
181
|
},
|
|
169
182
|
required: ["query", "response"],
|
|
170
183
|
},
|
|
@@ -316,6 +329,16 @@ const toolDefinitions = [
|
|
|
316
329
|
properties: {},
|
|
317
330
|
},
|
|
318
331
|
},
|
|
332
|
+
{
|
|
333
|
+
name: "get_project_history",
|
|
334
|
+
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.",
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: "object",
|
|
337
|
+
properties: {
|
|
338
|
+
limit: { type: "number", description: "Max items per section (commits, checkpoints, Brain memories)", default: 10 },
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
319
342
|
{
|
|
320
343
|
name: "get_ast_hologram",
|
|
321
344
|
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.",
|
|
@@ -1164,6 +1187,7 @@ const toolHandlers = {
|
|
|
1164
1187
|
auto_context_bundle: handleAutoContextBundle,
|
|
1165
1188
|
repeat_guard: handleRepeatGuard,
|
|
1166
1189
|
conversation_checkpoint: handleConversationCheckpoint,
|
|
1190
|
+
get_project_history: handleGetProjectHistory,
|
|
1167
1191
|
test_autofix_interceptor: handleTestAutofixInterceptor,
|
|
1168
1192
|
prompt_pattern_cache: handlePromptPatternCache,
|
|
1169
1193
|
lemma_toolbox: handleToolbox,
|
|
@@ -1349,7 +1373,7 @@ async function handleSearchMemory(args) {
|
|
|
1349
1373
|
}
|
|
1350
1374
|
const fresh = results.filter((r) => r.fresh);
|
|
1351
1375
|
const stale = results.filter((r) => !r.fresh);
|
|
1352
|
-
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)}`;
|
|
1376
|
+
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)}`;
|
|
1353
1377
|
if (fresh.length > 0) {
|
|
1354
1378
|
// Only a fresh hit (or an untracked, purely conceptual entry) counts as a real
|
|
1355
1379
|
// avoided re-investigation — this is the only case worth crediting as savings.
|
|
@@ -1390,11 +1414,15 @@ async function handleStoreMemory(args) {
|
|
|
1390
1414
|
const responseText = args?.response;
|
|
1391
1415
|
const provider = args?.provider || "generic";
|
|
1392
1416
|
const filePaths = Array.isArray(args?.filePaths) ? args.filePaths : undefined;
|
|
1417
|
+
const outcome = args?.outcome === "failed" ? "failed" : undefined;
|
|
1418
|
+
const symbols = Array.isArray(args?.symbols)
|
|
1419
|
+
? args.symbols.filter((s) => s?.filePath && s?.symbolName)
|
|
1420
|
+
: undefined;
|
|
1393
1421
|
if (!query || !responseText)
|
|
1394
1422
|
throw new Error("Query and response are required");
|
|
1395
1423
|
try {
|
|
1396
1424
|
const brain = (0, TheBrainV2_1.getBrain)();
|
|
1397
|
-
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths);
|
|
1425
|
+
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths, undefined, outcome, symbols);
|
|
1398
1426
|
// Storing a memory saves nothing — it only creates the chance of a saving later.
|
|
1399
1427
|
// Crediting tokens here inflated the ledger on write and then credited the same
|
|
1400
1428
|
// answer again on every read. The saving is booked by search_memory on a fresh hit.
|
|
@@ -1404,7 +1432,9 @@ async function handleStoreMemory(args) {
|
|
|
1404
1432
|
note: "memory written — no tokens saved yet",
|
|
1405
1433
|
});
|
|
1406
1434
|
const trackingNote = filePaths && filePaths.length > 0 ? ` Tracking freshness against ${filePaths.length} file(s) — this memory auto-invalidates if they change.` : "";
|
|
1407
|
-
|
|
1435
|
+
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.` : "";
|
|
1436
|
+
const outcomeNote = outcome === "failed" ? " Tagged as a FAILED attempt — future searches will surface it as a warning, not a suggestion." : "";
|
|
1437
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}${trackingNote}${symbolNote}${outcomeNote}` }] };
|
|
1408
1438
|
}
|
|
1409
1439
|
catch (e) {
|
|
1410
1440
|
(0, utils_1.logError)("store_memory", e);
|
|
@@ -1594,10 +1624,19 @@ async function handleApplyWorkspacePatch(args) {
|
|
|
1594
1624
|
};
|
|
1595
1625
|
}
|
|
1596
1626
|
const match = matchResult[0];
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1627
|
+
let updatedContent;
|
|
1628
|
+
if (match.strategy === "exact" && match.charStart !== undefined && match.charEnd !== undefined) {
|
|
1629
|
+
// Splice by character offset — the search block may be a mid-line fragment, and
|
|
1630
|
+
// a whole-line splice would discard everything else sharing that line.
|
|
1631
|
+
updatedContent = originalContent.slice(0, match.charStart) + replaceContent + originalContent.slice(match.charEnd);
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
const contentLines = originalContent.split("\n");
|
|
1635
|
+
const finalReplace = (0, PatchMatcher_1.reindentReplacement)(replaceContent, searchContent, contentLines[match.startLine]);
|
|
1636
|
+
const updatedLines = [...contentLines.slice(0, match.startLine), ...finalReplace.split("\n"), ...contentLines.slice(match.endLine + 1)];
|
|
1637
|
+
updatedContent = updatedLines.join("\n");
|
|
1638
|
+
}
|
|
1639
|
+
fs_1.default.writeFileSync(resolved, updatedContent, "utf8");
|
|
1601
1640
|
const note = match.strategy === "exact" ? "" : ` (matched via ${match.strategy}, score ${match.score.toFixed(2)})`;
|
|
1602
1641
|
return { content: [{ type: "text", text: `Success: Patch successfully applied to ${filePath}${note}` }] };
|
|
1603
1642
|
}
|
|
@@ -1923,6 +1962,59 @@ async function handleGetProjectOnboarding(_args) {
|
|
|
1923
1962
|
],
|
|
1924
1963
|
};
|
|
1925
1964
|
}
|
|
1965
|
+
// ── Project History (aggregate: git log + checkpoints + Brain) ─────
|
|
1966
|
+
/**
|
|
1967
|
+
* "What have we done in this project" today means piecing together search_memory, git log,
|
|
1968
|
+
* and manually reading .lemma/session/ checkpoint files by hand — three separate calls whose
|
|
1969
|
+
* results the model has to merge itself. This does that merge in one call.
|
|
1970
|
+
*/
|
|
1971
|
+
async function handleGetProjectHistory(args) {
|
|
1972
|
+
const cwd = process.cwd();
|
|
1973
|
+
const limit = typeof args?.limit === "number" ? args.limit : 10;
|
|
1974
|
+
try {
|
|
1975
|
+
const projectId = (0, TheBrainV2_1.deriveProjectId)(cwd);
|
|
1976
|
+
const brainEntries = (0, TheBrainV2_1.getBrain)()
|
|
1977
|
+
.getEntriesForProject(projectId)
|
|
1978
|
+
.sort((a, b) => (b.hits - a.hits) || (new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()))
|
|
1979
|
+
.slice(0, limit);
|
|
1980
|
+
const checkpoints = (0, ConversationCheckpoint_1.loadRecentCheckpoints)(cwd, Math.min(limit, 5));
|
|
1981
|
+
let recentCommits = [];
|
|
1982
|
+
try {
|
|
1983
|
+
const log = (0, child_process_1.execFileSync)("git", ["-C", cwd, "log", `-${limit}`, "--pretty=format:%h %s (%ar)"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
1984
|
+
recentCommits = log.split("\n").filter(Boolean);
|
|
1985
|
+
}
|
|
1986
|
+
catch {
|
|
1987
|
+
// Not a git repo, or no commits yet — sections below just won't include this one.
|
|
1988
|
+
}
|
|
1989
|
+
const parts = [`# Project history — ${path_1.default.basename(cwd)}`];
|
|
1990
|
+
if (recentCommits.length > 0) {
|
|
1991
|
+
parts.push(`## Recent commits\n${recentCommits.map((c) => `- ${c}`).join("\n")}`);
|
|
1992
|
+
}
|
|
1993
|
+
if (checkpoints.length > 0) {
|
|
1994
|
+
const checkpointLines = checkpoints.map((cp) => {
|
|
1995
|
+
const date = new Date(cp.timestamp).toISOString().replace("T", " ").slice(0, 16);
|
|
1996
|
+
return `- **${date}** — ${cp.summary.replace(/\n+/g, " ")}`;
|
|
1997
|
+
});
|
|
1998
|
+
parts.push(`## Session checkpoints\n${checkpointLines.join("\n")}`);
|
|
1999
|
+
}
|
|
2000
|
+
if (brainEntries.length > 0) {
|
|
2001
|
+
const brainLines = brainEntries.map((e) => {
|
|
2002
|
+
const tag = e.outcome === "failed" ? " ⚠️ FAILED ATTEMPT" : "";
|
|
2003
|
+
const snippet = e.response.length > 300 ? `${e.response.substring(0, 300)}...` : e.response;
|
|
2004
|
+
return `- **${e.query}**${tag} _(${e.provider}, ${e.hits} hit(s))_\n ${snippet}`;
|
|
2005
|
+
});
|
|
2006
|
+
parts.push(`## Brain memories (decisions, fixes, prior reviews)\n${brainLines.join("\n")}`);
|
|
2007
|
+
}
|
|
2008
|
+
if (parts.length === 1) {
|
|
2009
|
+
parts.push("Nothing recorded yet — no git history, checkpoints, or Brain memories found for this project.");
|
|
2010
|
+
}
|
|
2011
|
+
return { content: [{ type: "text", text: parts.join("\n\n") }] };
|
|
2012
|
+
}
|
|
2013
|
+
catch (e) {
|
|
2014
|
+
(0, utils_1.logError)("get_project_history", e);
|
|
2015
|
+
return { content: [{ type: "text", text: `get_project_history failed: ${e.message}` }] };
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
1926
2018
|
function extractSymbolsWithTsCompiler(filePath, relPath, parseErrors) {
|
|
1927
2019
|
try {
|
|
1928
2020
|
const src = fs_1.default.readFileSync(filePath, "utf8");
|
|
@@ -2243,7 +2335,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
2243
2335
|
const similarity = (r.similarity * 100).toFixed(1);
|
|
2244
2336
|
const prompt = r.query || "Unknown";
|
|
2245
2337
|
const responseContent = r.response || "";
|
|
2246
|
-
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
2338
|
+
hintsText += `### Hint ${i + 1} (${similarity}% match)${r.outcome === "failed" ? " — ⚠️ FAILED ATTEMPT, do not repeat" : ""}\n`;
|
|
2247
2339
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
2248
2340
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
2249
2341
|
});
|
|
@@ -2592,48 +2684,64 @@ async function handleTokenReceipt(args) {
|
|
|
2592
2684
|
}
|
|
2593
2685
|
// ── Token Budget ─────────────────────────────────────────────────
|
|
2594
2686
|
async function handleTokenBudget(_args) {
|
|
2595
|
-
const statsFile = path_1.default.join(os_1.default.homedir(), '.lemma-cache/stats.json');
|
|
2596
2687
|
const port = (0, utils_1.getProxyPort)();
|
|
2597
|
-
|
|
2688
|
+
const schemaTokens = Math.round(getToolSchemaChars() / 4);
|
|
2689
|
+
// /api/savings-breakdown serves SavingsLedger.getSnapshot() — the same ledger that
|
|
2690
|
+
// already nets tokensSaved against what Lemma itself spent (MCP schema injection,
|
|
2691
|
+
// tool-result bytes, cache misses). There is no separate "membership multiplier" to
|
|
2692
|
+
// compute: reusing that one number keeps this tool from reporting a bigger win than
|
|
2693
|
+
// the ledger the user can audit via `token_receipt` actually shows.
|
|
2694
|
+
let snapshot = null;
|
|
2598
2695
|
try {
|
|
2599
|
-
|
|
2696
|
+
const resp = await axios_1.default.get(`http://localhost:${port}/api/savings-breakdown`);
|
|
2697
|
+
snapshot = resp.data;
|
|
2600
2698
|
}
|
|
2601
2699
|
catch { }
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2700
|
+
if (!snapshot) {
|
|
2701
|
+
return {
|
|
2702
|
+
content: [{
|
|
2703
|
+
type: "text",
|
|
2704
|
+
text: JSON.stringify({
|
|
2705
|
+
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.",
|
|
2706
|
+
perTurnOverhead: { ...getToolSurfaceSummary(), schemaTokens },
|
|
2707
|
+
}, null, 2),
|
|
2708
|
+
}],
|
|
2709
|
+
};
|
|
2606
2710
|
}
|
|
2607
|
-
|
|
2608
|
-
const
|
|
2609
|
-
const monthlyTokens = stats.monthlyTokens || stats.totalTokens || 100000;
|
|
2610
|
-
const multiplier = monthlyTokens > 0 ? ((monthlyTokens + totalTokensSaved) / monthlyTokens).toFixed(1) : "N/A";
|
|
2611
|
-
const toolCallCount = ledger.length || stats.toolCalls || 0;
|
|
2711
|
+
const { total, totalCost, net } = snapshot;
|
|
2712
|
+
const ratioStr = net.ratio === Infinity ? "n/a (sin costo registrado todavía)" : `${net.ratio.toFixed(2)}x`;
|
|
2612
2713
|
return {
|
|
2613
2714
|
content: [{
|
|
2614
2715
|
type: "text",
|
|
2615
2716
|
text: JSON.stringify({
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2717
|
+
netRatio: ratioStr,
|
|
2718
|
+
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.",
|
|
2719
|
+
tokensSaved: total.tokensSaved.toLocaleString(),
|
|
2720
|
+
tokensSpent: totalCost.tokensSpent.toLocaleString(),
|
|
2721
|
+
netTokens: net.netTokens.toLocaleString(),
|
|
2722
|
+
netCostUSD: net.netCost.toFixed(4),
|
|
2723
|
+
breakdown: {
|
|
2724
|
+
saved: {
|
|
2725
|
+
cache: snapshot.cache?.tokensSaved ?? 0,
|
|
2726
|
+
contextSqueeze: snapshot.contextSqueeze?.tokensSaved ?? 0,
|
|
2727
|
+
historyPrune: snapshot.historyPrune?.tokensSaved ?? 0,
|
|
2728
|
+
complexityRouting: snapshot.complexityRouting?.tokensSaved ?? 0,
|
|
2729
|
+
clipboard: snapshot.clipboard?.tokensSaved ?? 0,
|
|
2730
|
+
},
|
|
2731
|
+
spent: {
|
|
2732
|
+
mcpInstructions: snapshot.costs?.mcpInstructions?.tokensSpent ?? 0,
|
|
2733
|
+
toolResult: snapshot.costs?.toolResult?.tokensSpent ?? 0,
|
|
2734
|
+
cacheMiss: snapshot.costs?.cacheMiss?.tokensSpent ?? 0,
|
|
2735
|
+
},
|
|
2628
2736
|
},
|
|
2629
2737
|
// The fixed cost of being connected, re-sent on every request of the session.
|
|
2630
2738
|
perTurnOverhead: {
|
|
2631
2739
|
...getToolSurfaceSummary(),
|
|
2632
|
-
schemaTokens
|
|
2740
|
+
schemaTokens,
|
|
2633
2741
|
note: "Hidden tools stay callable via lemma_toolbox. Set mcp.toolProfile='full' in lemma.config.json to advertise all of them.",
|
|
2634
2742
|
},
|
|
2635
|
-
recommendations:
|
|
2636
|
-
? ["
|
|
2743
|
+
recommendations: net.ratio !== Infinity && net.ratio < 1
|
|
2744
|
+
? ["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."]
|
|
2637
2745
|
: ["Sigue usando turbosqueeze antes de cada code block grande.", "Usa smarter_cache antes de razonar."],
|
|
2638
2746
|
}, null, 2),
|
|
2639
2747
|
}],
|