@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.
Files changed (69) hide show
  1. package/README.md +18 -3
  2. package/bin/cli.js +97 -0
  3. package/bin/init.js +307 -0
  4. package/cache-proxy.js +191 -0
  5. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  6. package/dist/cjs/cli/lemma-proxy.js +2 -4
  7. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  8. package/dist/cjs/mcp/index.js +1 -1
  9. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  10. package/dist/cjs/mcp/tools.js +292 -71
  11. package/dist/cjs/mcp/tools.js.map +1 -1
  12. package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
  13. package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
  14. package/dist/cjs/subconscious/TheBrainV2.js +444 -0
  15. package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
  16. package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
  17. package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
  18. package/dist/cjs/utils/ContextCollapser.js +132 -0
  19. package/dist/cjs/utils/ContextCollapser.js.map +1 -0
  20. package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
  21. package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
  22. package/dist/cjs/utils/ExecutiveReport.js +111 -0
  23. package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
  24. package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
  25. package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
  26. package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
  27. package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
  28. package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
  29. package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
  30. package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
  31. package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
  32. package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
  33. package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
  34. package/dist/cjs/utils/WormholeCompressor.js +152 -0
  35. package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
  36. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  37. package/dist/esm/cli/lemma-proxy.js +2 -4
  38. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  39. package/dist/esm/mcp/index.js +1 -1
  40. package/dist/esm/mcp/tools.d.ts.map +1 -1
  41. package/dist/esm/mcp/tools.js +292 -71
  42. package/dist/esm/mcp/tools.js.map +1 -1
  43. package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
  44. package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
  45. package/dist/esm/subconscious/TheBrainV2.js +431 -0
  46. package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
  47. package/dist/esm/utils/ContextCollapser.d.ts +21 -0
  48. package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
  49. package/dist/esm/utils/ContextCollapser.js +128 -0
  50. package/dist/esm/utils/ContextCollapser.js.map +1 -0
  51. package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
  52. package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
  53. package/dist/esm/utils/ExecutiveReport.js +105 -0
  54. package/dist/esm/utils/ExecutiveReport.js.map +1 -0
  55. package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
  56. package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
  57. package/dist/esm/utils/SurgicalASTInsert.js +134 -0
  58. package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
  59. package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
  60. package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
  61. package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
  62. package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
  63. package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
  64. package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
  65. package/dist/esm/utils/WormholeCompressor.js +148 -0
  66. package/dist/esm/utils/WormholeCompressor.js.map +1 -0
  67. package/lemma-proxy.cjs +31 -14
  68. package/mcp-server.js +220 -0
  69. package/package.json +5 -1
@@ -52,6 +52,12 @@ const utils_1 = require("./utils");
52
52
  const PRReviewEngine_1 = require("../pr-review/PRReviewEngine");
53
53
  const ReviewStore_1 = require("../pr-review/ReviewStore");
54
54
  const reviewRunner_1 = require("../pr-review/reviewRunner");
55
+ const TheBrainV2_1 = require("../subconscious/TheBrainV2");
56
+ const ContextCollapser_1 = require("../utils/ContextCollapser");
57
+ const SymbolSurgicalContext_1 = require("../utils/SymbolSurgicalContext");
58
+ const WormholeCompressor_1 = require("../utils/WormholeCompressor");
59
+ const ExecutiveReport_1 = require("../utils/ExecutiveReport");
60
+ const SurgicalASTInsert_1 = require("../utils/SurgicalASTInsert");
55
61
  // ── Pro License Gate ──────────────────────────────────────────────────────────
56
62
  const LICENSE_FILE = path_1.default.join(os_1.default.homedir(), '.lemma-cache', 'license.json');
57
63
  let _proStatusCache = null;
@@ -359,6 +365,67 @@ const toolDefinitions = [
359
365
  required: ["text"],
360
366
  },
361
367
  },
368
+ {
369
+ name: "get_symbol_surgical_context",
370
+ 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.",
371
+ inputSchema: {
372
+ type: "object",
373
+ properties: {
374
+ filePath: { type: "string", description: "Ruta relativa del archivo que contiene el símbolo" },
375
+ symbolName: { type: "string", description: "Nombre de la función, clase, tipo o interfaz a extraer" },
376
+ },
377
+ required: ["filePath", "symbolName"],
378
+ },
379
+ },
380
+ {
381
+ name: "wormhole_squeeze",
382
+ 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.",
383
+ inputSchema: {
384
+ type: "object",
385
+ properties: {
386
+ text: { type: "string", description: "El código fuente a comprimir" },
387
+ },
388
+ required: ["text"],
389
+ },
390
+ },
391
+ {
392
+ name: "generate_executive_roi_report",
393
+ 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.",
394
+ inputSchema: {
395
+ type: "object",
396
+ properties: {
397
+ teamSize: { type: "number", description: "Número de ingenieros en el equipo (default: 10)", default: 10 },
398
+ costPerDevHour: { type: "number", description: "Costo estimado en USD por hora de desarrollador (default: 50)", default: 50 },
399
+ targetPath: { type: "string", description: "Nombre de archivo del reporte generado (default: LEMMA_ROI_REPORT.md)", default: "LEMMA_ROI_REPORT.md" },
400
+ },
401
+ },
402
+ },
403
+ {
404
+ name: "surgical_ast_insert",
405
+ 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.",
406
+ inputSchema: {
407
+ type: "object",
408
+ properties: {
409
+ filePath: { type: "string", description: "Ruta del archivo TypeScript (relativo al root)" },
410
+ symbolName: { type: "string", description: "Nombre de la clase o interfaz donde insertar (opcional)" },
411
+ anchorSymbol: { type: "string", description: "Nombre del método o propiedad como anclaje (ej. 'login')" },
412
+ insertPosition: { type: "string", enum: ["before", "after", "start", "end"], description: "Posición relativa al anclaje (default: 'after')", default: "after" },
413
+ codeToInsert: { type: "string", description: "El código fuente exacto a insertar" },
414
+ },
415
+ required: ["filePath", "codeToInsert"],
416
+ },
417
+ },
418
+ {
419
+ name: "local_semantic_autofix",
420
+ 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.",
421
+ inputSchema: {
422
+ type: "object",
423
+ properties: {
424
+ errorMessage: { type: "string", description: "El traceback del error o mensaje de error a resolver" },
425
+ },
426
+ required: ["errorMessage"],
427
+ },
428
+ },
362
429
  {
363
430
  name: "compress_context",
364
431
  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.",
@@ -587,6 +654,11 @@ const toolHandlers = {
587
654
  diff_only: handleDiffOnly,
588
655
  batch_tool_calls: handleBatchToolCalls,
589
656
  turbosqueeze: handleTurboSqueeze,
657
+ get_symbol_surgical_context: handleGetSymbolSurgicalContext,
658
+ wormhole_squeeze: handleWormholeSqueeze,
659
+ generate_executive_roi_report: handleGenerateExecutiveRoiReport,
660
+ surgical_ast_insert: handleSurgicalAstInsert,
661
+ local_semantic_autofix: handleLocalSemanticAutofix,
590
662
  compress_context: handleCompressContext,
591
663
  smarter_cache: handleSmarterCache,
592
664
  token_budget: handleTokenBudget,
@@ -675,22 +747,21 @@ async function handleSearchMemory(args) {
675
747
  if (!query)
676
748
  throw new Error("Query is required");
677
749
  try {
678
- const port = (0, utils_1.getProxyPort)();
679
- const response = await axios_1.default.get(`http://localhost:${port}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`);
680
- const results = response.data.results || [];
750
+ const brain = (0, TheBrainV2_1.getBrain)();
751
+ const results = brain.search(query, limit);
681
752
  if (results.length === 0) {
682
753
  return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
683
754
  }
684
755
  const formatted = results
685
756
  .map((r, i) => {
686
- return `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.prompt.substring(0, 300)}...\nResponse: ${typeof r.response === "string" ? r.response : JSON.stringify(r.response, null, 2)}`;
757
+ 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)}`;
687
758
  })
688
759
  .join("\n\n---\n\n");
689
760
  return { content: [{ type: "text", text: `Lemma found ${results.length} memories:\n\n${formatted}` }] };
690
761
  }
691
762
  catch (e) {
692
763
  (0, utils_1.logError)("search_memory", e);
693
- return { content: [{ type: "text", text: `Neural Search failed: ${e.message}. Is Lemma Proxy running?` }] };
764
+ return { content: [{ type: "text", text: `Local Search failed: ${e.message}` }] };
694
765
  }
695
766
  }
696
767
  async function handleStoreMemory(args) {
@@ -700,12 +771,8 @@ async function handleStoreMemory(args) {
700
771
  if (!query || !responseText)
701
772
  throw new Error("Query and response are required");
702
773
  try {
703
- const port = (0, utils_1.getProxyPort)();
704
- const response = await axios_1.default.post(`http://localhost:${port}/api/memory/store`, {
705
- query,
706
- response: responseText,
707
- provider,
708
- });
774
+ const brain = (0, TheBrainV2_1.getBrain)();
775
+ const storeRes = brain.store(query, responseText, provider);
709
776
  const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
710
777
  (0, reportSavings_1.reportSavings)({
711
778
  source: "cache",
@@ -713,11 +780,11 @@ async function handleStoreMemory(args) {
713
780
  toolName: "store_memory",
714
781
  query: query.substring(0, 100),
715
782
  });
716
- return { content: [{ type: "text", text: `Success: ${response.data.message}` }] };
783
+ return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}` }] };
717
784
  }
718
785
  catch (e) {
719
786
  (0, utils_1.logError)("store_memory", e);
720
- return { content: [{ type: "text", text: `Failed to store memory in Lemma: ${e.message}. Is Lemma Proxy running?` }] };
787
+ return { content: [{ type: "text", text: `Failed to store memory in local Brain: ${e.message}` }] };
721
788
  }
722
789
  }
723
790
  async function handleGetRoutingAdvice(args) {
@@ -1285,24 +1352,20 @@ async function handleQueryHybridConsensus(args) {
1285
1352
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.8;
1286
1353
  if (!query)
1287
1354
  throw new Error("query is required");
1288
- const port = (0, utils_1.getProxyPort)();
1289
1355
  const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
1290
1356
  try {
1291
- const searchRes = await axios_1.default.get(`http://localhost:${port}/api/search?q=${encodeURIComponent(fullQuery)}&limit=3`);
1292
- const results = searchRes.data.results || [];
1357
+ const brain = (0, TheBrainV2_1.getBrain)();
1358
+ const results = brain.search(fullQuery, 3);
1293
1359
  const topHit = results[0];
1294
1360
  if (topHit && topHit.similarity >= threshold) {
1295
- const savedTokens = Math.floor((topHit.response?.choices?.[0]?.message?.content?.length || 500) / 4);
1361
+ const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
1296
1362
  (0, reportSavings_1.reportSavings)({
1297
1363
  source: "cache",
1298
1364
  tokens: savedTokens,
1299
1365
  toolName: "query_hybrid_consensus",
1300
1366
  query: query.substring(0, 100),
1301
1367
  });
1302
- const responseText = typeof topHit.response === "string"
1303
- ? topHit.response
1304
- : topHit.response?.choices?.[0]?.message?.content ||
1305
- JSON.stringify(topHit.response, null, 2);
1368
+ const responseText = topHit.response;
1306
1369
  return {
1307
1370
  content: [
1308
1371
  {
@@ -1328,7 +1391,7 @@ async function handleQueryHybridConsensus(args) {
1328
1391
  (0, utils_1.logError)("query_hybrid_consensus", e);
1329
1392
  return {
1330
1393
  content: [
1331
- { type: "text", text: `Hybrid Consensus failed: ${e.message}. Is Lemma Proxy running?` },
1394
+ { type: "text", text: `Hybrid Consensus failed: ${e.message}` },
1332
1395
  ],
1333
1396
  };
1334
1397
  }
@@ -1339,7 +1402,6 @@ async function handleGetTelepathicHints(args) {
1339
1402
  const limit = typeof args?.limit === "number" ? args.limit : 5;
1340
1403
  if (!activeFile)
1341
1404
  throw new Error("activeFile is required");
1342
- const port = (0, utils_1.getProxyPort)();
1343
1405
  const basename = path_1.default.basename(activeFile, path_1.default.extname(activeFile));
1344
1406
  const dirContext = path_1.default
1345
1407
  .dirname(activeFile)
@@ -1350,8 +1412,8 @@ async function handleGetTelepathicHints(args) {
1350
1412
  const ext = path_1.default.extname(activeFile).replace(".", "");
1351
1413
  const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
1352
1414
  try {
1353
- const searchRes = await axios_1.default.get(`http://localhost:${port}/api/search?q=${encodeURIComponent(telepathicQuery)}&limit=${limit}`);
1354
- const results = searchRes.data.results || [];
1415
+ const brain = (0, TheBrainV2_1.getBrain)();
1416
+ const results = brain.search(telepathicQuery, limit);
1355
1417
  if (results.length === 0) {
1356
1418
  return {
1357
1419
  content: [
@@ -1365,11 +1427,8 @@ async function handleGetTelepathicHints(args) {
1365
1427
  let hintsText = `Telepathic Hints for \`${activeFile}\`\n*(${results.length} relevant memories surfaced from The Brain)*\n\n`;
1366
1428
  results.forEach((r, i) => {
1367
1429
  const similarity = (r.similarity * 100).toFixed(1);
1368
- const prompt = typeof r.prompt === "string" ? r.prompt.substring(0, 120) : "Unknown";
1369
- const responseContent = typeof r.response === "string"
1370
- ? r.response
1371
- : r.response?.choices?.[0]?.message?.content ||
1372
- JSON.stringify(r.response).substring(0, 300);
1430
+ const prompt = r.query || "Unknown";
1431
+ const responseContent = r.response || "";
1373
1432
  hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
1374
1433
  hintsText += `**Memory:** ${prompt}\n\n`;
1375
1434
  hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
@@ -1389,7 +1448,7 @@ async function handleGetTelepathicHints(args) {
1389
1448
  content: [
1390
1449
  {
1391
1450
  type: "text",
1392
- text: `Telepathic Hints failed: ${e.message}. Is Lemma Proxy running?`,
1451
+ text: `Telepathic Hints failed: ${e.message}`,
1393
1452
  },
1394
1453
  ],
1395
1454
  };
@@ -1401,17 +1460,8 @@ async function handleSummarizeLongText(args) {
1401
1460
  const maxLength = args?.maxLength || 500;
1402
1461
  if (!text)
1403
1462
  throw new Error("text is required");
1404
- const host = process.env.OLLAMA_HOST || 'http://localhost:11434';
1405
- const model = process.env.OLLAMA_GEN_MODEL || 'llama3';
1406
1463
  try {
1407
- const prompt = `TASK: Summarize the following text concisely. Max ${maxLength} characters. Focus on key facts, decisions, and action items.\n\nTEXT:\n${text}`;
1408
- const resp = await axios_1.default.post(`${host}/api/generate`, {
1409
- model,
1410
- prompt,
1411
- stream: false,
1412
- options: { temperature: 0.1, num_predict: maxLength * 2 },
1413
- });
1414
- const summary = resp.data.response.trim();
1464
+ const summary = (0, ContextCollapser_1.collapseContext)(text, { maxLength });
1415
1465
  (0, reportSavings_1.reportSavings)({
1416
1466
  source: "contextSqueeze",
1417
1467
  charsBefore: text.length,
@@ -1422,7 +1472,7 @@ async function handleSummarizeLongText(args) {
1422
1472
  }
1423
1473
  catch (e) {
1424
1474
  (0, utils_1.logError)("summarize_long_text", e);
1425
- return { content: [{ type: "text", text: `Summarization failed: ${e.message}. Is Ollama running?` }] };
1475
+ return { content: [{ type: "text", text: `Summarization failed: ${e.message}` }] };
1426
1476
  }
1427
1477
  }
1428
1478
  // ── Prune Conversation History ──────────────────────────────────────
@@ -1571,21 +1621,14 @@ async function handleCompressContext(args) {
1571
1621
  const recent = messages.slice(-keepRecent);
1572
1622
  const old = messages.slice(0, -keepRecent);
1573
1623
  let summary = "";
1574
- let summaryMethod = "trim";
1624
+ let summaryMethod = "local-collapser";
1575
1625
  try {
1576
- const host = process.env.OLLAMA_HOST || 'http://localhost:11434';
1577
- const model = process.env.OLLAMA_GEN_MODEL || 'llama3';
1578
- 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) : '...' })))}`;
1579
- const resp = await axios_1.default.post(`${host}/api/generate`, {
1580
- model, prompt: summaryPrompt, stream: false,
1581
- options: { temperature: 0.1, num_predict: 300 },
1582
- });
1583
- summary = resp.data.response.trim();
1584
- summaryMethod = "ollama";
1626
+ const oldText = old.map((m, i) => `[Turn ${i + 1} / ${m.role}]: ${typeof m.content === 'string' ? m.content : '...'}`).join('\n');
1627
+ summary = (0, ContextCollapser_1.collapseContext)(oldText, { maxLength: 800 });
1585
1628
  }
1586
- catch {
1629
+ catch (err) {
1587
1630
  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);
1588
- summaryMethod = "truncate";
1631
+ summaryMethod = "truncate-fallback";
1589
1632
  }
1590
1633
  const oldLength = JSON.stringify(old).length;
1591
1634
  const summaryLength = summary.length;
@@ -1613,16 +1656,13 @@ async function handleSmarterCache(args) {
1613
1656
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.75;
1614
1657
  if (!query)
1615
1658
  throw new Error("query is required");
1616
- const port = (0, utils_1.getProxyPort)();
1617
1659
  const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
1618
1660
  try {
1619
- const searchRes = await axios_1.default.get(`http://localhost:${port}/api/search?q=${encodeURIComponent(fullQuery)}&limit=5`);
1620
- const results = searchRes.data.results || [];
1661
+ const brain = (0, TheBrainV2_1.getBrain)();
1662
+ const results = brain.search(fullQuery, 5);
1621
1663
  const topHit = results[0];
1622
1664
  if (topHit && topHit.similarity >= threshold) {
1623
- const responseText = typeof topHit.response === "string"
1624
- ? topHit.response
1625
- : topHit.response?.choices?.[0]?.message?.content || JSON.stringify(topHit.response, null, 2);
1665
+ const responseText = topHit.response;
1626
1666
  const tokensSaved = Math.floor(responseText.length / 4);
1627
1667
  try {
1628
1668
  const { reportSavings } = await Promise.resolve().then(() => __importStar(require("../utils/reportSavings")));
@@ -1667,7 +1707,7 @@ async function handleSmarterCache(args) {
1667
1707
  hit: false,
1668
1708
  error: e.message,
1669
1709
  source: "cache-unavailable",
1670
- hint: "Lemma Proxy no está corriendo. El LLM funcionará normalmente sin cache.",
1710
+ hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
1671
1711
  }, null, 2),
1672
1712
  }],
1673
1713
  };
@@ -2320,10 +2360,9 @@ async function handleSemanticDedupGuard(args) {
2320
2360
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
2321
2361
  if (!query || !response)
2322
2362
  throw new Error("query and response are required");
2323
- const port = (0, utils_1.getProxyPort)();
2324
2363
  try {
2325
- const searchRes = await axios_1.default.get(`http://localhost:${port}/api/search?q=${encodeURIComponent(query)}&limit=3`);
2326
- const results = searchRes.data.results || [];
2364
+ const brain = (0, TheBrainV2_1.getBrain)();
2365
+ const results = brain.search(query, 3);
2327
2366
  const topHit = results[0];
2328
2367
  if (!topHit) {
2329
2368
  return {
@@ -2334,17 +2373,18 @@ async function handleSemanticDedupGuard(args) {
2334
2373
  }, null, 2) }],
2335
2374
  };
2336
2375
  }
2337
- const sim = topHit.similarity || 0;
2376
+ let sim = topHit.similarity || 0;
2377
+ if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
2378
+ sim = 1.0;
2379
+ }
2338
2380
  if (sim >= threshold) {
2339
2381
  return {
2340
2382
  content: [{ type: "text", text: JSON.stringify({
2341
2383
  verdict: "DUPLICATE_REJECTED",
2342
2384
  reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
2343
2385
  similarity: sim,
2344
- existingQuery: typeof topHit.prompt === "string" ? topHit.prompt.substring(0, 200) : "N/A",
2345
- existingResponse: (typeof topHit.response === "string"
2346
- ? topHit.response
2347
- : topHit.response?.choices?.[0]?.message?.content || "").substring(0, 300),
2386
+ existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
2387
+ existingResponse: (topHit.response || "").substring(0, 300),
2348
2388
  tokensSaved: Math.floor(response.length / 4),
2349
2389
  }, null, 2) }],
2350
2390
  };
@@ -2355,7 +2395,7 @@ async function handleSemanticDedupGuard(args) {
2355
2395
  verdict: "SIMILAR_WARNING",
2356
2396
  reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
2357
2397
  similarity: sim,
2358
- existingQuery: typeof topHit.prompt === "string" ? topHit.prompt.substring(0, 200) : "N/A",
2398
+ existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
2359
2399
  recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
2360
2400
  }, null, 2) }],
2361
2401
  };
@@ -2373,7 +2413,7 @@ async function handleSemanticDedupGuard(args) {
2373
2413
  return {
2374
2414
  content: [{ type: "text", text: JSON.stringify({
2375
2415
  verdict: "STORE_ALLOWED",
2376
- reason: "No se pudo consultar The Brain (proxy no disponible). Procediendo con store.",
2416
+ reason: "El cache local del brain falló. Procediendo con store.",
2377
2417
  error: e.message,
2378
2418
  }, null, 2) }],
2379
2419
  };
@@ -3279,4 +3319,185 @@ async function handleRefactor(args) {
3279
3319
  return { content: [{ type: "text", text: `refactor failed: ${err.message}` }] };
3280
3320
  }
3281
3321
  }
3322
+ // ── Surgical Symbol Context ──────────────────────────────────────────
3323
+ async function handleGetSymbolSurgicalContext(args) {
3324
+ const filePath = args?.filePath;
3325
+ const symbolName = args?.symbolName;
3326
+ if (!filePath || !symbolName)
3327
+ throw new Error("filePath and symbolName are required");
3328
+ const workspaceRoot = process.cwd();
3329
+ try {
3330
+ const res = (0, SymbolSurgicalContext_1.getSymbolSurgicalContext)(workspaceRoot, filePath, symbolName);
3331
+ const lines = [];
3332
+ lines.push(`# 🧬 Surgical Context: Symbol \`${symbolName}\` in \`${filePath}\``);
3333
+ lines.push(`## Implementation`);
3334
+ lines.push(`\`\`\`typescript\n${res.implementation}\n\`\`\``);
3335
+ if (res.dependencies.length > 0) {
3336
+ lines.push(`## Workspace Dependencies (Signatures ONLY)`);
3337
+ for (const dep of res.dependencies) {
3338
+ lines.push(`### \`${dep.name}\` (${dep.type}) declared in \`${dep.filePath}\``);
3339
+ lines.push(`\`\`\`typescript\n${dep.signature}\n\`\`\``);
3340
+ }
3341
+ }
3342
+ else {
3343
+ lines.push(`*No local workspace dependencies detected for this symbol.*`);
3344
+ }
3345
+ const output = lines.join("\n\n");
3346
+ const originalSize = fs_1.default.readFileSync(path_1.default.resolve(workspaceRoot, filePath), "utf8").length;
3347
+ const totalRawSize = originalSize + res.dependencies.reduce((sum, dep) => {
3348
+ try {
3349
+ return sum + fs_1.default.readFileSync(path_1.default.resolve(workspaceRoot, dep.filePath), "utf8").length;
3350
+ }
3351
+ catch {
3352
+ return sum;
3353
+ }
3354
+ }, 0);
3355
+ (0, reportSavings_1.reportSavings)({
3356
+ source: "contextSqueeze",
3357
+ charsBefore: totalRawSize,
3358
+ charsAfter: output.length,
3359
+ toolName: "get_symbol_surgical_context",
3360
+ filePath
3361
+ });
3362
+ return {
3363
+ content: [{ type: "text", text: output }]
3364
+ };
3365
+ }
3366
+ catch (err) {
3367
+ (0, utils_1.logError)("get_symbol_surgical_context", err);
3368
+ return {
3369
+ content: [{ type: "text", text: `Surgical context extraction failed: ${err.message}` }]
3370
+ };
3371
+ }
3372
+ }
3373
+ // ── Wormhole Squeeze ────────────────────────────────────────────────
3374
+ async function handleWormholeSqueeze(args) {
3375
+ const text = args?.text;
3376
+ if (!text)
3377
+ throw new Error("text is required");
3378
+ try {
3379
+ const compressed = (0, WormholeCompressor_1.compressWormhole)(text);
3380
+ (0, reportSavings_1.reportSavings)({
3381
+ source: "contextSqueeze",
3382
+ charsBefore: text.length,
3383
+ charsAfter: compressed.length,
3384
+ toolName: "wormhole_squeeze"
3385
+ });
3386
+ return {
3387
+ content: [{ type: "text", text: compressed }]
3388
+ };
3389
+ }
3390
+ catch (err) {
3391
+ (0, utils_1.logError)("wormhole_squeeze", err);
3392
+ return {
3393
+ content: [{ type: "text", text: `Wormhole compression failed: ${err.message}` }]
3394
+ };
3395
+ }
3396
+ }
3397
+ // ── Executive ROI Report ──────────────────────────────────────────────
3398
+ async function handleGenerateExecutiveRoiReport(args) {
3399
+ const teamSize = typeof args?.teamSize === "number" ? args.teamSize : 10;
3400
+ const costPerDevHour = typeof args?.costPerDevHour === "number" ? args.costPerDevHour : 50;
3401
+ const targetPath = args?.targetPath || "LEMMA_ROI_REPORT.md";
3402
+ const workspaceRoot = process.cwd();
3403
+ try {
3404
+ const res = (0, ExecutiveReport_1.generateExecutiveReport)(workspaceRoot, {
3405
+ teamSize,
3406
+ costPerDevHour,
3407
+ targetPath
3408
+ });
3409
+ return {
3410
+ content: [{
3411
+ type: "text",
3412
+ 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`
3413
+ }]
3414
+ };
3415
+ }
3416
+ catch (err) {
3417
+ (0, utils_1.logError)("generate_executive_roi_report", err);
3418
+ return {
3419
+ content: [{ type: "text", text: `ROI Report generation failed: ${err.message}` }]
3420
+ };
3421
+ }
3422
+ }
3423
+ // ── Surgical AST Insert ──────────────────────────────────────────────
3424
+ async function handleSurgicalAstInsert(args) {
3425
+ const filePath = args?.filePath;
3426
+ const symbolName = args?.symbolName;
3427
+ const anchorSymbol = args?.anchorSymbol;
3428
+ const insertPosition = args?.insertPosition;
3429
+ const codeToInsert = args?.codeToInsert;
3430
+ if (!filePath || !codeToInsert)
3431
+ throw new Error("filePath and codeToInsert are required");
3432
+ const workspaceRoot = process.cwd();
3433
+ try {
3434
+ const resolvedPath = (0, SurgicalASTInsert_1.surgicalASTInsert)(workspaceRoot, {
3435
+ filePath,
3436
+ symbolName,
3437
+ anchorSymbol,
3438
+ insertPosition,
3439
+ codeToInsert
3440
+ });
3441
+ const newTextSize = fs_1.default.readFileSync(resolvedPath, "utf8").length;
3442
+ const outputCharsSaved = Math.max(0, newTextSize - codeToInsert.length);
3443
+ const tokensSaved = Math.floor(outputCharsSaved / 4);
3444
+ (0, reportSavings_1.reportSavings)({
3445
+ source: "contextSqueeze",
3446
+ tokens: tokensSaved,
3447
+ toolName: "surgical_ast_insert",
3448
+ filePath
3449
+ });
3450
+ return {
3451
+ content: [{
3452
+ type: "text",
3453
+ text: `Success: Surgically inserted code into ${filePath} using AST compiler.`
3454
+ }]
3455
+ };
3456
+ }
3457
+ catch (err) {
3458
+ (0, utils_1.logError)("surgical_ast_insert", err);
3459
+ return {
3460
+ content: [{ type: "text", text: `Surgical AST insertion failed: ${err.message}` }]
3461
+ };
3462
+ }
3463
+ }
3464
+ // ── Local Semantic Autofix ────────────────────────────────────────────
3465
+ async function handleLocalSemanticAutofix(args) {
3466
+ const errorMessage = args?.errorMessage;
3467
+ if (!errorMessage)
3468
+ throw new Error("errorMessage is required");
3469
+ try {
3470
+ const brain = (0, TheBrainV2_1.getBrain)();
3471
+ const results = brain.search(errorMessage, 3, 0.70);
3472
+ const topHit = results[0];
3473
+ if (topHit) {
3474
+ const responseText = topHit.response;
3475
+ const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
3476
+ (0, reportSavings_1.reportSavings)({
3477
+ source: "cache",
3478
+ tokens: tokensSaved,
3479
+ toolName: "local_semantic_autofix",
3480
+ query: errorMessage.substring(0, 100)
3481
+ });
3482
+ return {
3483
+ content: [{
3484
+ type: "text",
3485
+ 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}`
3486
+ }]
3487
+ };
3488
+ }
3489
+ return {
3490
+ content: [{
3491
+ type: "text",
3492
+ 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!`
3493
+ }]
3494
+ };
3495
+ }
3496
+ catch (err) {
3497
+ (0, utils_1.logError)("local_semantic_autofix", err);
3498
+ return {
3499
+ content: [{ type: "text", text: `Local auto-fix failed: ${err.message}` }]
3500
+ };
3501
+ }
3502
+ }
3282
3503
  //# sourceMappingURL=tools.js.map