@nxuss/lemma 0.9.3 → 1.0.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 +18 -3
- package/bin/cli.js +97 -0
- package/bin/init.js +307 -0
- package/cache-proxy.js +191 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +2 -4
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +1 -1
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +292 -71
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.js +444 -0
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
- package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
- package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/cjs/utils/ContextCollapser.js +132 -0
- package/dist/cjs/utils/ContextCollapser.js.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.js +111 -0
- package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
- package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.js +152 -0
- package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +2 -4
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +292 -71
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.js +431 -0
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
- package/dist/esm/utils/ContextCollapser.d.ts +21 -0
- package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/esm/utils/ContextCollapser.js +128 -0
- package/dist/esm/utils/ContextCollapser.js.map +1 -0
- package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
- package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/esm/utils/ExecutiveReport.js +105 -0
- package/dist/esm/utils/ExecutiveReport.js.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.js +134 -0
- package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
- package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
- package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/esm/utils/WormholeCompressor.js +148 -0
- package/dist/esm/utils/WormholeCompressor.js.map +1 -0
- package/lemma-proxy.cjs +31 -14
- package/mcp-server.js +220 -0
- package/package.json +5 -1
package/dist/esm/mcp/tools.js
CHANGED
|
@@ -13,6 +13,12 @@ import { getProxyPort, safeResolvePath, logError, logWarn, estimateTokensFromRes
|
|
|
13
13
|
import { runAnalysis, parseDiff } from "../pr-review/PRReviewEngine";
|
|
14
14
|
import { ReviewStore } from "../pr-review/ReviewStore";
|
|
15
15
|
import { runPRReview } from "../pr-review/reviewRunner";
|
|
16
|
+
import { getBrain } from "../subconscious/TheBrainV2";
|
|
17
|
+
import { collapseContext } from "../utils/ContextCollapser";
|
|
18
|
+
import { getSymbolSurgicalContext } from "../utils/SymbolSurgicalContext";
|
|
19
|
+
import { compressWormhole } from "../utils/WormholeCompressor";
|
|
20
|
+
import { generateExecutiveReport } from "../utils/ExecutiveReport";
|
|
21
|
+
import { surgicalASTInsert } from "../utils/SurgicalASTInsert";
|
|
16
22
|
// ── Pro License Gate ──────────────────────────────────────────────────────────
|
|
17
23
|
const LICENSE_FILE = path.join(os.homedir(), '.lemma-cache', 'license.json');
|
|
18
24
|
let _proStatusCache = null;
|
|
@@ -320,6 +326,67 @@ const toolDefinitions = [
|
|
|
320
326
|
required: ["text"],
|
|
321
327
|
},
|
|
322
328
|
},
|
|
329
|
+
{
|
|
330
|
+
name: "get_symbol_surgical_context",
|
|
331
|
+
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.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
filePath: { type: "string", description: "Ruta relativa del archivo que contiene el símbolo" },
|
|
336
|
+
symbolName: { type: "string", description: "Nombre de la función, clase, tipo o interfaz a extraer" },
|
|
337
|
+
},
|
|
338
|
+
required: ["filePath", "symbolName"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "wormhole_squeeze",
|
|
343
|
+
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.",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
text: { type: "string", description: "El código fuente a comprimir" },
|
|
348
|
+
},
|
|
349
|
+
required: ["text"],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "generate_executive_roi_report",
|
|
354
|
+
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.",
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
teamSize: { type: "number", description: "Número de ingenieros en el equipo (default: 10)", default: 10 },
|
|
359
|
+
costPerDevHour: { type: "number", description: "Costo estimado en USD por hora de desarrollador (default: 50)", default: 50 },
|
|
360
|
+
targetPath: { type: "string", description: "Nombre de archivo del reporte generado (default: LEMMA_ROI_REPORT.md)", default: "LEMMA_ROI_REPORT.md" },
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "surgical_ast_insert",
|
|
366
|
+
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.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
filePath: { type: "string", description: "Ruta del archivo TypeScript (relativo al root)" },
|
|
371
|
+
symbolName: { type: "string", description: "Nombre de la clase o interfaz donde insertar (opcional)" },
|
|
372
|
+
anchorSymbol: { type: "string", description: "Nombre del método o propiedad como anclaje (ej. 'login')" },
|
|
373
|
+
insertPosition: { type: "string", enum: ["before", "after", "start", "end"], description: "Posición relativa al anclaje (default: 'after')", default: "after" },
|
|
374
|
+
codeToInsert: { type: "string", description: "El código fuente exacto a insertar" },
|
|
375
|
+
},
|
|
376
|
+
required: ["filePath", "codeToInsert"],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "local_semantic_autofix",
|
|
381
|
+
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.",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties: {
|
|
385
|
+
errorMessage: { type: "string", description: "El traceback del error o mensaje de error a resolver" },
|
|
386
|
+
},
|
|
387
|
+
required: ["errorMessage"],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
323
390
|
{
|
|
324
391
|
name: "compress_context",
|
|
325
392
|
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.",
|
|
@@ -548,6 +615,11 @@ const toolHandlers = {
|
|
|
548
615
|
diff_only: handleDiffOnly,
|
|
549
616
|
batch_tool_calls: handleBatchToolCalls,
|
|
550
617
|
turbosqueeze: handleTurboSqueeze,
|
|
618
|
+
get_symbol_surgical_context: handleGetSymbolSurgicalContext,
|
|
619
|
+
wormhole_squeeze: handleWormholeSqueeze,
|
|
620
|
+
generate_executive_roi_report: handleGenerateExecutiveRoiReport,
|
|
621
|
+
surgical_ast_insert: handleSurgicalAstInsert,
|
|
622
|
+
local_semantic_autofix: handleLocalSemanticAutofix,
|
|
551
623
|
compress_context: handleCompressContext,
|
|
552
624
|
smarter_cache: handleSmarterCache,
|
|
553
625
|
token_budget: handleTokenBudget,
|
|
@@ -636,22 +708,21 @@ async function handleSearchMemory(args) {
|
|
|
636
708
|
if (!query)
|
|
637
709
|
throw new Error("Query is required");
|
|
638
710
|
try {
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
const results = response.data.results || [];
|
|
711
|
+
const brain = getBrain();
|
|
712
|
+
const results = brain.search(query, limit);
|
|
642
713
|
if (results.length === 0) {
|
|
643
714
|
return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
|
|
644
715
|
}
|
|
645
716
|
const formatted = results
|
|
646
717
|
.map((r, i) => {
|
|
647
|
-
return `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.
|
|
718
|
+
return `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)}`;
|
|
648
719
|
})
|
|
649
720
|
.join("\n\n---\n\n");
|
|
650
721
|
return { content: [{ type: "text", text: `Lemma found ${results.length} memories:\n\n${formatted}` }] };
|
|
651
722
|
}
|
|
652
723
|
catch (e) {
|
|
653
724
|
logError("search_memory", e);
|
|
654
|
-
return { content: [{ type: "text", text: `
|
|
725
|
+
return { content: [{ type: "text", text: `Local Search failed: ${e.message}` }] };
|
|
655
726
|
}
|
|
656
727
|
}
|
|
657
728
|
async function handleStoreMemory(args) {
|
|
@@ -661,12 +732,8 @@ async function handleStoreMemory(args) {
|
|
|
661
732
|
if (!query || !responseText)
|
|
662
733
|
throw new Error("Query and response are required");
|
|
663
734
|
try {
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
query,
|
|
667
|
-
response: responseText,
|
|
668
|
-
provider,
|
|
669
|
-
});
|
|
735
|
+
const brain = getBrain();
|
|
736
|
+
const storeRes = brain.store(query, responseText, provider);
|
|
670
737
|
const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
|
|
671
738
|
reportSavings({
|
|
672
739
|
source: "cache",
|
|
@@ -674,11 +741,11 @@ async function handleStoreMemory(args) {
|
|
|
674
741
|
toolName: "store_memory",
|
|
675
742
|
query: query.substring(0, 100),
|
|
676
743
|
});
|
|
677
|
-
return { content: [{ type: "text", text: `Success: ${
|
|
744
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}` }] };
|
|
678
745
|
}
|
|
679
746
|
catch (e) {
|
|
680
747
|
logError("store_memory", e);
|
|
681
|
-
return { content: [{ type: "text", text: `Failed to store memory in
|
|
748
|
+
return { content: [{ type: "text", text: `Failed to store memory in local Brain: ${e.message}` }] };
|
|
682
749
|
}
|
|
683
750
|
}
|
|
684
751
|
async function handleGetRoutingAdvice(args) {
|
|
@@ -1246,24 +1313,20 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1246
1313
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.8;
|
|
1247
1314
|
if (!query)
|
|
1248
1315
|
throw new Error("query is required");
|
|
1249
|
-
const port = getProxyPort();
|
|
1250
1316
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1251
1317
|
try {
|
|
1252
|
-
const
|
|
1253
|
-
const results =
|
|
1318
|
+
const brain = getBrain();
|
|
1319
|
+
const results = brain.search(fullQuery, 3);
|
|
1254
1320
|
const topHit = results[0];
|
|
1255
1321
|
if (topHit && topHit.similarity >= threshold) {
|
|
1256
|
-
const savedTokens = Math.floor((topHit.response?.
|
|
1322
|
+
const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
|
|
1257
1323
|
reportSavings({
|
|
1258
1324
|
source: "cache",
|
|
1259
1325
|
tokens: savedTokens,
|
|
1260
1326
|
toolName: "query_hybrid_consensus",
|
|
1261
1327
|
query: query.substring(0, 100),
|
|
1262
1328
|
});
|
|
1263
|
-
const responseText =
|
|
1264
|
-
? topHit.response
|
|
1265
|
-
: topHit.response?.choices?.[0]?.message?.content ||
|
|
1266
|
-
JSON.stringify(topHit.response, null, 2);
|
|
1329
|
+
const responseText = topHit.response;
|
|
1267
1330
|
return {
|
|
1268
1331
|
content: [
|
|
1269
1332
|
{
|
|
@@ -1289,7 +1352,7 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1289
1352
|
logError("query_hybrid_consensus", e);
|
|
1290
1353
|
return {
|
|
1291
1354
|
content: [
|
|
1292
|
-
{ type: "text", text: `Hybrid Consensus failed: ${e.message}
|
|
1355
|
+
{ type: "text", text: `Hybrid Consensus failed: ${e.message}` },
|
|
1293
1356
|
],
|
|
1294
1357
|
};
|
|
1295
1358
|
}
|
|
@@ -1300,7 +1363,6 @@ async function handleGetTelepathicHints(args) {
|
|
|
1300
1363
|
const limit = typeof args?.limit === "number" ? args.limit : 5;
|
|
1301
1364
|
if (!activeFile)
|
|
1302
1365
|
throw new Error("activeFile is required");
|
|
1303
|
-
const port = getProxyPort();
|
|
1304
1366
|
const basename = path.basename(activeFile, path.extname(activeFile));
|
|
1305
1367
|
const dirContext = path
|
|
1306
1368
|
.dirname(activeFile)
|
|
@@ -1311,8 +1373,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1311
1373
|
const ext = path.extname(activeFile).replace(".", "");
|
|
1312
1374
|
const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
|
|
1313
1375
|
try {
|
|
1314
|
-
const
|
|
1315
|
-
const results =
|
|
1376
|
+
const brain = getBrain();
|
|
1377
|
+
const results = brain.search(telepathicQuery, limit);
|
|
1316
1378
|
if (results.length === 0) {
|
|
1317
1379
|
return {
|
|
1318
1380
|
content: [
|
|
@@ -1326,11 +1388,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1326
1388
|
let hintsText = `Telepathic Hints for \`${activeFile}\`\n*(${results.length} relevant memories surfaced from The Brain)*\n\n`;
|
|
1327
1389
|
results.forEach((r, i) => {
|
|
1328
1390
|
const similarity = (r.similarity * 100).toFixed(1);
|
|
1329
|
-
const prompt =
|
|
1330
|
-
const responseContent =
|
|
1331
|
-
? r.response
|
|
1332
|
-
: r.response?.choices?.[0]?.message?.content ||
|
|
1333
|
-
JSON.stringify(r.response).substring(0, 300);
|
|
1391
|
+
const prompt = r.query || "Unknown";
|
|
1392
|
+
const responseContent = r.response || "";
|
|
1334
1393
|
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
1335
1394
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
1336
1395
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
@@ -1350,7 +1409,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
1350
1409
|
content: [
|
|
1351
1410
|
{
|
|
1352
1411
|
type: "text",
|
|
1353
|
-
text: `Telepathic Hints failed: ${e.message}
|
|
1412
|
+
text: `Telepathic Hints failed: ${e.message}`,
|
|
1354
1413
|
},
|
|
1355
1414
|
],
|
|
1356
1415
|
};
|
|
@@ -1362,17 +1421,8 @@ async function handleSummarizeLongText(args) {
|
|
|
1362
1421
|
const maxLength = args?.maxLength || 500;
|
|
1363
1422
|
if (!text)
|
|
1364
1423
|
throw new Error("text is required");
|
|
1365
|
-
const host = process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
1366
|
-
const model = process.env.OLLAMA_GEN_MODEL || 'llama3';
|
|
1367
1424
|
try {
|
|
1368
|
-
const
|
|
1369
|
-
const resp = await axios.post(`${host}/api/generate`, {
|
|
1370
|
-
model,
|
|
1371
|
-
prompt,
|
|
1372
|
-
stream: false,
|
|
1373
|
-
options: { temperature: 0.1, num_predict: maxLength * 2 },
|
|
1374
|
-
});
|
|
1375
|
-
const summary = resp.data.response.trim();
|
|
1425
|
+
const summary = collapseContext(text, { maxLength });
|
|
1376
1426
|
reportSavings({
|
|
1377
1427
|
source: "contextSqueeze",
|
|
1378
1428
|
charsBefore: text.length,
|
|
@@ -1383,7 +1433,7 @@ async function handleSummarizeLongText(args) {
|
|
|
1383
1433
|
}
|
|
1384
1434
|
catch (e) {
|
|
1385
1435
|
logError("summarize_long_text", e);
|
|
1386
|
-
return { content: [{ type: "text", text: `Summarization failed: ${e.message}
|
|
1436
|
+
return { content: [{ type: "text", text: `Summarization failed: ${e.message}` }] };
|
|
1387
1437
|
}
|
|
1388
1438
|
}
|
|
1389
1439
|
// ── Prune Conversation History ──────────────────────────────────────
|
|
@@ -1532,21 +1582,14 @@ async function handleCompressContext(args) {
|
|
|
1532
1582
|
const recent = messages.slice(-keepRecent);
|
|
1533
1583
|
const old = messages.slice(0, -keepRecent);
|
|
1534
1584
|
let summary = "";
|
|
1535
|
-
let summaryMethod = "
|
|
1585
|
+
let summaryMethod = "local-collapser";
|
|
1536
1586
|
try {
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
const summaryPrompt = `Summarize the following conversation history into 2-3 concise sentences. Capture ONLY: key decisions made, code patterns established, unresolved issues. Be extremely brief.\n\n${JSON.stringify(old.map((m) => ({ role: m.role, content: typeof m.content === 'string' ? m.content.substring(0, 400) : '...' })))}`;
|
|
1540
|
-
const resp = await axios.post(`${host}/api/generate`, {
|
|
1541
|
-
model, prompt: summaryPrompt, stream: false,
|
|
1542
|
-
options: { temperature: 0.1, num_predict: 300 },
|
|
1543
|
-
});
|
|
1544
|
-
summary = resp.data.response.trim();
|
|
1545
|
-
summaryMethod = "ollama";
|
|
1587
|
+
const oldText = old.map((m, i) => `[Turn ${i + 1} / ${m.role}]: ${typeof m.content === 'string' ? m.content : '...'}`).join('\n');
|
|
1588
|
+
summary = collapseContext(oldText, { maxLength: 800 });
|
|
1546
1589
|
}
|
|
1547
|
-
catch {
|
|
1590
|
+
catch (err) {
|
|
1548
1591
|
summary = old.map((m, i) => `[Turn ${i + 1} / ${m.role}]: ${typeof m.content === 'string' ? m.content.substring(0, 150).replace(/\n/g, ' ') : '...'}`).join('\n').substring(0, 2000);
|
|
1549
|
-
summaryMethod = "truncate";
|
|
1592
|
+
summaryMethod = "truncate-fallback";
|
|
1550
1593
|
}
|
|
1551
1594
|
const oldLength = JSON.stringify(old).length;
|
|
1552
1595
|
const summaryLength = summary.length;
|
|
@@ -1574,16 +1617,13 @@ async function handleSmarterCache(args) {
|
|
|
1574
1617
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.75;
|
|
1575
1618
|
if (!query)
|
|
1576
1619
|
throw new Error("query is required");
|
|
1577
|
-
const port = getProxyPort();
|
|
1578
1620
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1579
1621
|
try {
|
|
1580
|
-
const
|
|
1581
|
-
const results =
|
|
1622
|
+
const brain = getBrain();
|
|
1623
|
+
const results = brain.search(fullQuery, 5);
|
|
1582
1624
|
const topHit = results[0];
|
|
1583
1625
|
if (topHit && topHit.similarity >= threshold) {
|
|
1584
|
-
const responseText =
|
|
1585
|
-
? topHit.response
|
|
1586
|
-
: topHit.response?.choices?.[0]?.message?.content || JSON.stringify(topHit.response, null, 2);
|
|
1626
|
+
const responseText = topHit.response;
|
|
1587
1627
|
const tokensSaved = Math.floor(responseText.length / 4);
|
|
1588
1628
|
try {
|
|
1589
1629
|
const { reportSavings } = await import("../utils/reportSavings");
|
|
@@ -1628,7 +1668,7 @@ async function handleSmarterCache(args) {
|
|
|
1628
1668
|
hit: false,
|
|
1629
1669
|
error: e.message,
|
|
1630
1670
|
source: "cache-unavailable",
|
|
1631
|
-
hint: "
|
|
1671
|
+
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
1632
1672
|
}, null, 2),
|
|
1633
1673
|
}],
|
|
1634
1674
|
};
|
|
@@ -2281,10 +2321,9 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2281
2321
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
|
|
2282
2322
|
if (!query || !response)
|
|
2283
2323
|
throw new Error("query and response are required");
|
|
2284
|
-
const port = getProxyPort();
|
|
2285
2324
|
try {
|
|
2286
|
-
const
|
|
2287
|
-
const results =
|
|
2325
|
+
const brain = getBrain();
|
|
2326
|
+
const results = brain.search(query, 3);
|
|
2288
2327
|
const topHit = results[0];
|
|
2289
2328
|
if (!topHit) {
|
|
2290
2329
|
return {
|
|
@@ -2295,17 +2334,18 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2295
2334
|
}, null, 2) }],
|
|
2296
2335
|
};
|
|
2297
2336
|
}
|
|
2298
|
-
|
|
2337
|
+
let sim = topHit.similarity || 0;
|
|
2338
|
+
if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
|
|
2339
|
+
sim = 1.0;
|
|
2340
|
+
}
|
|
2299
2341
|
if (sim >= threshold) {
|
|
2300
2342
|
return {
|
|
2301
2343
|
content: [{ type: "text", text: JSON.stringify({
|
|
2302
2344
|
verdict: "DUPLICATE_REJECTED",
|
|
2303
2345
|
reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
|
|
2304
2346
|
similarity: sim,
|
|
2305
|
-
existingQuery: typeof topHit.
|
|
2306
|
-
existingResponse: (
|
|
2307
|
-
? topHit.response
|
|
2308
|
-
: topHit.response?.choices?.[0]?.message?.content || "").substring(0, 300),
|
|
2347
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
2348
|
+
existingResponse: (topHit.response || "").substring(0, 300),
|
|
2309
2349
|
tokensSaved: Math.floor(response.length / 4),
|
|
2310
2350
|
}, null, 2) }],
|
|
2311
2351
|
};
|
|
@@ -2316,7 +2356,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2316
2356
|
verdict: "SIMILAR_WARNING",
|
|
2317
2357
|
reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
|
|
2318
2358
|
similarity: sim,
|
|
2319
|
-
existingQuery: typeof topHit.
|
|
2359
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
2320
2360
|
recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
|
|
2321
2361
|
}, null, 2) }],
|
|
2322
2362
|
};
|
|
@@ -2334,7 +2374,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2334
2374
|
return {
|
|
2335
2375
|
content: [{ type: "text", text: JSON.stringify({
|
|
2336
2376
|
verdict: "STORE_ALLOWED",
|
|
2337
|
-
reason: "
|
|
2377
|
+
reason: "El cache local del brain falló. Procediendo con store.",
|
|
2338
2378
|
error: e.message,
|
|
2339
2379
|
}, null, 2) }],
|
|
2340
2380
|
};
|
|
@@ -3240,4 +3280,185 @@ async function handleRefactor(args) {
|
|
|
3240
3280
|
return { content: [{ type: "text", text: `refactor failed: ${err.message}` }] };
|
|
3241
3281
|
}
|
|
3242
3282
|
}
|
|
3283
|
+
// ── Surgical Symbol Context ──────────────────────────────────────────
|
|
3284
|
+
async function handleGetSymbolSurgicalContext(args) {
|
|
3285
|
+
const filePath = args?.filePath;
|
|
3286
|
+
const symbolName = args?.symbolName;
|
|
3287
|
+
if (!filePath || !symbolName)
|
|
3288
|
+
throw new Error("filePath and symbolName are required");
|
|
3289
|
+
const workspaceRoot = process.cwd();
|
|
3290
|
+
try {
|
|
3291
|
+
const res = getSymbolSurgicalContext(workspaceRoot, filePath, symbolName);
|
|
3292
|
+
const lines = [];
|
|
3293
|
+
lines.push(`# 🧬 Surgical Context: Symbol \`${symbolName}\` in \`${filePath}\``);
|
|
3294
|
+
lines.push(`## Implementation`);
|
|
3295
|
+
lines.push(`\`\`\`typescript\n${res.implementation}\n\`\`\``);
|
|
3296
|
+
if (res.dependencies.length > 0) {
|
|
3297
|
+
lines.push(`## Workspace Dependencies (Signatures ONLY)`);
|
|
3298
|
+
for (const dep of res.dependencies) {
|
|
3299
|
+
lines.push(`### \`${dep.name}\` (${dep.type}) declared in \`${dep.filePath}\``);
|
|
3300
|
+
lines.push(`\`\`\`typescript\n${dep.signature}\n\`\`\``);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
else {
|
|
3304
|
+
lines.push(`*No local workspace dependencies detected for this symbol.*`);
|
|
3305
|
+
}
|
|
3306
|
+
const output = lines.join("\n\n");
|
|
3307
|
+
const originalSize = fs.readFileSync(path.resolve(workspaceRoot, filePath), "utf8").length;
|
|
3308
|
+
const totalRawSize = originalSize + res.dependencies.reduce((sum, dep) => {
|
|
3309
|
+
try {
|
|
3310
|
+
return sum + fs.readFileSync(path.resolve(workspaceRoot, dep.filePath), "utf8").length;
|
|
3311
|
+
}
|
|
3312
|
+
catch {
|
|
3313
|
+
return sum;
|
|
3314
|
+
}
|
|
3315
|
+
}, 0);
|
|
3316
|
+
reportSavings({
|
|
3317
|
+
source: "contextSqueeze",
|
|
3318
|
+
charsBefore: totalRawSize,
|
|
3319
|
+
charsAfter: output.length,
|
|
3320
|
+
toolName: "get_symbol_surgical_context",
|
|
3321
|
+
filePath
|
|
3322
|
+
});
|
|
3323
|
+
return {
|
|
3324
|
+
content: [{ type: "text", text: output }]
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
catch (err) {
|
|
3328
|
+
logError("get_symbol_surgical_context", err);
|
|
3329
|
+
return {
|
|
3330
|
+
content: [{ type: "text", text: `Surgical context extraction failed: ${err.message}` }]
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
// ── Wormhole Squeeze ────────────────────────────────────────────────
|
|
3335
|
+
async function handleWormholeSqueeze(args) {
|
|
3336
|
+
const text = args?.text;
|
|
3337
|
+
if (!text)
|
|
3338
|
+
throw new Error("text is required");
|
|
3339
|
+
try {
|
|
3340
|
+
const compressed = compressWormhole(text);
|
|
3341
|
+
reportSavings({
|
|
3342
|
+
source: "contextSqueeze",
|
|
3343
|
+
charsBefore: text.length,
|
|
3344
|
+
charsAfter: compressed.length,
|
|
3345
|
+
toolName: "wormhole_squeeze"
|
|
3346
|
+
});
|
|
3347
|
+
return {
|
|
3348
|
+
content: [{ type: "text", text: compressed }]
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
catch (err) {
|
|
3352
|
+
logError("wormhole_squeeze", err);
|
|
3353
|
+
return {
|
|
3354
|
+
content: [{ type: "text", text: `Wormhole compression failed: ${err.message}` }]
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
// ── Executive ROI Report ──────────────────────────────────────────────
|
|
3359
|
+
async function handleGenerateExecutiveRoiReport(args) {
|
|
3360
|
+
const teamSize = typeof args?.teamSize === "number" ? args.teamSize : 10;
|
|
3361
|
+
const costPerDevHour = typeof args?.costPerDevHour === "number" ? args.costPerDevHour : 50;
|
|
3362
|
+
const targetPath = args?.targetPath || "LEMMA_ROI_REPORT.md";
|
|
3363
|
+
const workspaceRoot = process.cwd();
|
|
3364
|
+
try {
|
|
3365
|
+
const res = generateExecutiveReport(workspaceRoot, {
|
|
3366
|
+
teamSize,
|
|
3367
|
+
costPerDevHour,
|
|
3368
|
+
targetPath
|
|
3369
|
+
});
|
|
3370
|
+
return {
|
|
3371
|
+
content: [{
|
|
3372
|
+
type: "text",
|
|
3373
|
+
text: `Success: ROI Report generated at ${targetPath}.\n\nTotal Business Value Saved: $${res.metrics.totalBusinessValue.toFixed(2)} USD\nEstimated Dev Time Saved: ${res.metrics.devHoursSaved} Hours\nDirect API Cost Saved: $${res.metrics.totalCostSaved.toFixed(4)} USD`
|
|
3374
|
+
}]
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
catch (err) {
|
|
3378
|
+
logError("generate_executive_roi_report", err);
|
|
3379
|
+
return {
|
|
3380
|
+
content: [{ type: "text", text: `ROI Report generation failed: ${err.message}` }]
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
// ── Surgical AST Insert ──────────────────────────────────────────────
|
|
3385
|
+
async function handleSurgicalAstInsert(args) {
|
|
3386
|
+
const filePath = args?.filePath;
|
|
3387
|
+
const symbolName = args?.symbolName;
|
|
3388
|
+
const anchorSymbol = args?.anchorSymbol;
|
|
3389
|
+
const insertPosition = args?.insertPosition;
|
|
3390
|
+
const codeToInsert = args?.codeToInsert;
|
|
3391
|
+
if (!filePath || !codeToInsert)
|
|
3392
|
+
throw new Error("filePath and codeToInsert are required");
|
|
3393
|
+
const workspaceRoot = process.cwd();
|
|
3394
|
+
try {
|
|
3395
|
+
const resolvedPath = surgicalASTInsert(workspaceRoot, {
|
|
3396
|
+
filePath,
|
|
3397
|
+
symbolName,
|
|
3398
|
+
anchorSymbol,
|
|
3399
|
+
insertPosition,
|
|
3400
|
+
codeToInsert
|
|
3401
|
+
});
|
|
3402
|
+
const newTextSize = fs.readFileSync(resolvedPath, "utf8").length;
|
|
3403
|
+
const outputCharsSaved = Math.max(0, newTextSize - codeToInsert.length);
|
|
3404
|
+
const tokensSaved = Math.floor(outputCharsSaved / 4);
|
|
3405
|
+
reportSavings({
|
|
3406
|
+
source: "contextSqueeze",
|
|
3407
|
+
tokens: tokensSaved,
|
|
3408
|
+
toolName: "surgical_ast_insert",
|
|
3409
|
+
filePath
|
|
3410
|
+
});
|
|
3411
|
+
return {
|
|
3412
|
+
content: [{
|
|
3413
|
+
type: "text",
|
|
3414
|
+
text: `Success: Surgically inserted code into ${filePath} using AST compiler.`
|
|
3415
|
+
}]
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
catch (err) {
|
|
3419
|
+
logError("surgical_ast_insert", err);
|
|
3420
|
+
return {
|
|
3421
|
+
content: [{ type: "text", text: `Surgical AST insertion failed: ${err.message}` }]
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
// ── Local Semantic Autofix ────────────────────────────────────────────
|
|
3426
|
+
async function handleLocalSemanticAutofix(args) {
|
|
3427
|
+
const errorMessage = args?.errorMessage;
|
|
3428
|
+
if (!errorMessage)
|
|
3429
|
+
throw new Error("errorMessage is required");
|
|
3430
|
+
try {
|
|
3431
|
+
const brain = getBrain();
|
|
3432
|
+
const results = brain.search(errorMessage, 3, 0.70);
|
|
3433
|
+
const topHit = results[0];
|
|
3434
|
+
if (topHit) {
|
|
3435
|
+
const responseText = topHit.response;
|
|
3436
|
+
const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
|
|
3437
|
+
reportSavings({
|
|
3438
|
+
source: "cache",
|
|
3439
|
+
tokens: tokensSaved,
|
|
3440
|
+
toolName: "local_semantic_autofix",
|
|
3441
|
+
query: errorMessage.substring(0, 100)
|
|
3442
|
+
});
|
|
3443
|
+
return {
|
|
3444
|
+
content: [{
|
|
3445
|
+
type: "text",
|
|
3446
|
+
text: `🎯 Local Auto-Fix HIT (similarity: ${(topHit.similarity * 100).toFixed(1)}%)\n\nFound matching resolution in global memory. Cost: 0 tokens.\n\n---\n\n${responseText}`
|
|
3447
|
+
}]
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
return {
|
|
3451
|
+
content: [{
|
|
3452
|
+
type: "text",
|
|
3453
|
+
text: `Brain MISS (No matching resolutions for this error in local memory).\n\nPlease proceed to ask the LLM for guidance. Once resolved, make sure to call store_memory with:\n- query: "[Error description/traceback]"\n- response: "[Exact code patch or steps used to fix]"\n\nThis will allow resolving this error instantly with 0 tokens next time!`
|
|
3454
|
+
}]
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
catch (err) {
|
|
3458
|
+
logError("local_semantic_autofix", err);
|
|
3459
|
+
return {
|
|
3460
|
+
content: [{ type: "text", text: `Local auto-fix failed: ${err.message}` }]
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3243
3464
|
//# sourceMappingURL=tools.js.map
|