@nxuss/lemma 0.9.3 → 1.0.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.
Files changed (93) hide show
  1. package/README.md +35 -4
  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/autopilot/AutopilotWatcher.d.ts +3 -2
  6. package/dist/cjs/autopilot/AutopilotWatcher.d.ts.map +1 -1
  7. package/dist/cjs/autopilot/AutopilotWatcher.js +48 -54
  8. package/dist/cjs/autopilot/AutopilotWatcher.js.map +1 -1
  9. package/dist/cjs/autopilot/Notifier.d.ts +5 -0
  10. package/dist/cjs/autopilot/Notifier.d.ts.map +1 -0
  11. package/dist/cjs/autopilot/Notifier.js +59 -0
  12. package/dist/cjs/autopilot/Notifier.js.map +1 -0
  13. package/dist/cjs/autopilot/PatchStore.d.ts +26 -0
  14. package/dist/cjs/autopilot/PatchStore.d.ts.map +1 -0
  15. package/dist/cjs/autopilot/PatchStore.js +88 -0
  16. package/dist/cjs/autopilot/PatchStore.js.map +1 -0
  17. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  18. package/dist/cjs/cli/lemma-proxy.js +123 -4
  19. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  20. package/dist/cjs/mcp/index.js +1 -1
  21. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  22. package/dist/cjs/mcp/tools.js +303 -77
  23. package/dist/cjs/mcp/tools.js.map +1 -1
  24. package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
  25. package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
  26. package/dist/cjs/subconscious/TheBrainV2.js +444 -0
  27. package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
  28. package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
  29. package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
  30. package/dist/cjs/utils/ContextCollapser.js +132 -0
  31. package/dist/cjs/utils/ContextCollapser.js.map +1 -0
  32. package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
  33. package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
  34. package/dist/cjs/utils/ExecutiveReport.js +111 -0
  35. package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
  36. package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
  37. package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
  38. package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
  39. package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
  40. package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
  41. package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
  42. package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
  43. package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
  44. package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
  45. package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
  46. package/dist/cjs/utils/WormholeCompressor.js +152 -0
  47. package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
  48. package/dist/esm/autopilot/AutopilotWatcher.d.ts +3 -2
  49. package/dist/esm/autopilot/AutopilotWatcher.d.ts.map +1 -1
  50. package/dist/esm/autopilot/AutopilotWatcher.js +48 -54
  51. package/dist/esm/autopilot/AutopilotWatcher.js.map +1 -1
  52. package/dist/esm/autopilot/Notifier.d.ts +5 -0
  53. package/dist/esm/autopilot/Notifier.d.ts.map +1 -0
  54. package/dist/esm/autopilot/Notifier.js +51 -0
  55. package/dist/esm/autopilot/Notifier.js.map +1 -0
  56. package/dist/esm/autopilot/PatchStore.d.ts +26 -0
  57. package/dist/esm/autopilot/PatchStore.d.ts.map +1 -0
  58. package/dist/esm/autopilot/PatchStore.js +77 -0
  59. package/dist/esm/autopilot/PatchStore.js.map +1 -0
  60. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  61. package/dist/esm/cli/lemma-proxy.js +123 -4
  62. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  63. package/dist/esm/mcp/index.js +1 -1
  64. package/dist/esm/mcp/tools.d.ts.map +1 -1
  65. package/dist/esm/mcp/tools.js +303 -77
  66. package/dist/esm/mcp/tools.js.map +1 -1
  67. package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
  68. package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
  69. package/dist/esm/subconscious/TheBrainV2.js +431 -0
  70. package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
  71. package/dist/esm/utils/ContextCollapser.d.ts +21 -0
  72. package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
  73. package/dist/esm/utils/ContextCollapser.js +128 -0
  74. package/dist/esm/utils/ContextCollapser.js.map +1 -0
  75. package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
  76. package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
  77. package/dist/esm/utils/ExecutiveReport.js +105 -0
  78. package/dist/esm/utils/ExecutiveReport.js.map +1 -0
  79. package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
  80. package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
  81. package/dist/esm/utils/SurgicalASTInsert.js +134 -0
  82. package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
  83. package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
  84. package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
  85. package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
  86. package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
  87. package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
  88. package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
  89. package/dist/esm/utils/WormholeCompressor.js +148 -0
  90. package/dist/esm/utils/WormholeCompressor.js.map +1 -0
  91. package/lemma-proxy.cjs +31 -14
  92. package/mcp-server.js +220 -0
  93. package/package.json +7 -2
@@ -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;
@@ -69,11 +75,16 @@ function isProUser() {
69
75
  _proStatusCache = false;
70
76
  return false;
71
77
  }
78
+ const FREE_TOOLS = new Set([
79
+ // Cache & Memory — the hook that shows instant value
80
+ "smarter_cache", "search_memory", "store_memory",
81
+ // Token optimization — shows what they're saving
82
+ "token_budget", "squeeze_prompt", "turbosqueeze",
83
+ // Utility — just enough to function
84
+ "read_workspace_file", "scrub_privacy", "summarize_long_text",
85
+ ]);
72
86
  const PRO_GATE_MESSAGE = [
73
- "🔒 Lemma MCP requires a Pro license.",
74
- "",
75
- "All 32 MCP tools — including The Brain, zero-token static analysis,",
76
- "PreCrime predictor, Coupling Radar, and Entropy Score — are Pro features.",
87
+ "🔒 This tool requires Lemma Pro.",
77
88
  "",
78
89
  "✨ Activate your license:",
79
90
  " lemma activate <your-key>",
@@ -359,6 +370,67 @@ const toolDefinitions = [
359
370
  required: ["text"],
360
371
  },
361
372
  },
373
+ {
374
+ name: "get_symbol_surgical_context",
375
+ 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.",
376
+ inputSchema: {
377
+ type: "object",
378
+ properties: {
379
+ filePath: { type: "string", description: "Ruta relativa del archivo que contiene el símbolo" },
380
+ symbolName: { type: "string", description: "Nombre de la función, clase, tipo o interfaz a extraer" },
381
+ },
382
+ required: ["filePath", "symbolName"],
383
+ },
384
+ },
385
+ {
386
+ name: "wormhole_squeeze",
387
+ 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.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
391
+ text: { type: "string", description: "El código fuente a comprimir" },
392
+ },
393
+ required: ["text"],
394
+ },
395
+ },
396
+ {
397
+ name: "generate_executive_roi_report",
398
+ 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.",
399
+ inputSchema: {
400
+ type: "object",
401
+ properties: {
402
+ teamSize: { type: "number", description: "Número de ingenieros en el equipo (default: 10)", default: 10 },
403
+ costPerDevHour: { type: "number", description: "Costo estimado en USD por hora de desarrollador (default: 50)", default: 50 },
404
+ targetPath: { type: "string", description: "Nombre de archivo del reporte generado (default: LEMMA_ROI_REPORT.md)", default: "LEMMA_ROI_REPORT.md" },
405
+ },
406
+ },
407
+ },
408
+ {
409
+ name: "surgical_ast_insert",
410
+ 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.",
411
+ inputSchema: {
412
+ type: "object",
413
+ properties: {
414
+ filePath: { type: "string", description: "Ruta del archivo TypeScript (relativo al root)" },
415
+ symbolName: { type: "string", description: "Nombre de la clase o interfaz donde insertar (opcional)" },
416
+ anchorSymbol: { type: "string", description: "Nombre del método o propiedad como anclaje (ej. 'login')" },
417
+ insertPosition: { type: "string", enum: ["before", "after", "start", "end"], description: "Posición relativa al anclaje (default: 'after')", default: "after" },
418
+ codeToInsert: { type: "string", description: "El código fuente exacto a insertar" },
419
+ },
420
+ required: ["filePath", "codeToInsert"],
421
+ },
422
+ },
423
+ {
424
+ name: "local_semantic_autofix",
425
+ 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.",
426
+ inputSchema: {
427
+ type: "object",
428
+ properties: {
429
+ errorMessage: { type: "string", description: "El traceback del error o mensaje de error a resolver" },
430
+ },
431
+ required: ["errorMessage"],
432
+ },
433
+ },
362
434
  {
363
435
  name: "compress_context",
364
436
  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 +659,11 @@ const toolHandlers = {
587
659
  diff_only: handleDiffOnly,
588
660
  batch_tool_calls: handleBatchToolCalls,
589
661
  turbosqueeze: handleTurboSqueeze,
662
+ get_symbol_surgical_context: handleGetSymbolSurgicalContext,
663
+ wormhole_squeeze: handleWormholeSqueeze,
664
+ generate_executive_roi_report: handleGenerateExecutiveRoiReport,
665
+ surgical_ast_insert: handleSurgicalAstInsert,
666
+ local_semantic_autofix: handleLocalSemanticAutofix,
590
667
  compress_context: handleCompressContext,
591
668
  smarter_cache: handleSmarterCache,
592
669
  token_budget: handleTokenBudget,
@@ -611,8 +688,8 @@ function setupToolsHandlers(server, onToolCall) {
611
688
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
612
689
  const { name, arguments: args } = request.params;
613
690
  const startTime = Date.now();
614
- // ── Pro License Gate ────────────────────────────────────────────────────
615
- if (!isProUser()) {
691
+ // ── Pro License Gate (granular: free tools work without license) ────────
692
+ if (!isProUser() && !FREE_TOOLS.has(name)) {
616
693
  onToolCall?.({
617
694
  tool: name,
618
695
  args: args || {},
@@ -675,22 +752,21 @@ async function handleSearchMemory(args) {
675
752
  if (!query)
676
753
  throw new Error("Query is required");
677
754
  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 || [];
755
+ const brain = (0, TheBrainV2_1.getBrain)();
756
+ const results = brain.search(query, limit);
681
757
  if (results.length === 0) {
682
758
  return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
683
759
  }
684
760
  const formatted = results
685
761
  .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)}`;
762
+ 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
763
  })
688
764
  .join("\n\n---\n\n");
689
765
  return { content: [{ type: "text", text: `Lemma found ${results.length} memories:\n\n${formatted}` }] };
690
766
  }
691
767
  catch (e) {
692
768
  (0, utils_1.logError)("search_memory", e);
693
- return { content: [{ type: "text", text: `Neural Search failed: ${e.message}. Is Lemma Proxy running?` }] };
769
+ return { content: [{ type: "text", text: `Local Search failed: ${e.message}` }] };
694
770
  }
695
771
  }
696
772
  async function handleStoreMemory(args) {
@@ -700,12 +776,8 @@ async function handleStoreMemory(args) {
700
776
  if (!query || !responseText)
701
777
  throw new Error("Query and response are required");
702
778
  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
- });
779
+ const brain = (0, TheBrainV2_1.getBrain)();
780
+ const storeRes = brain.store(query, responseText, provider);
709
781
  const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
710
782
  (0, reportSavings_1.reportSavings)({
711
783
  source: "cache",
@@ -713,11 +785,11 @@ async function handleStoreMemory(args) {
713
785
  toolName: "store_memory",
714
786
  query: query.substring(0, 100),
715
787
  });
716
- return { content: [{ type: "text", text: `Success: ${response.data.message}` }] };
788
+ return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}` }] };
717
789
  }
718
790
  catch (e) {
719
791
  (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?` }] };
792
+ return { content: [{ type: "text", text: `Failed to store memory in local Brain: ${e.message}` }] };
721
793
  }
722
794
  }
723
795
  async function handleGetRoutingAdvice(args) {
@@ -1285,24 +1357,20 @@ async function handleQueryHybridConsensus(args) {
1285
1357
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.8;
1286
1358
  if (!query)
1287
1359
  throw new Error("query is required");
1288
- const port = (0, utils_1.getProxyPort)();
1289
1360
  const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
1290
1361
  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 || [];
1362
+ const brain = (0, TheBrainV2_1.getBrain)();
1363
+ const results = brain.search(fullQuery, 3);
1293
1364
  const topHit = results[0];
1294
1365
  if (topHit && topHit.similarity >= threshold) {
1295
- const savedTokens = Math.floor((topHit.response?.choices?.[0]?.message?.content?.length || 500) / 4);
1366
+ const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
1296
1367
  (0, reportSavings_1.reportSavings)({
1297
1368
  source: "cache",
1298
1369
  tokens: savedTokens,
1299
1370
  toolName: "query_hybrid_consensus",
1300
1371
  query: query.substring(0, 100),
1301
1372
  });
1302
- const responseText = typeof topHit.response === "string"
1303
- ? topHit.response
1304
- : topHit.response?.choices?.[0]?.message?.content ||
1305
- JSON.stringify(topHit.response, null, 2);
1373
+ const responseText = topHit.response;
1306
1374
  return {
1307
1375
  content: [
1308
1376
  {
@@ -1328,7 +1396,7 @@ async function handleQueryHybridConsensus(args) {
1328
1396
  (0, utils_1.logError)("query_hybrid_consensus", e);
1329
1397
  return {
1330
1398
  content: [
1331
- { type: "text", text: `Hybrid Consensus failed: ${e.message}. Is Lemma Proxy running?` },
1399
+ { type: "text", text: `Hybrid Consensus failed: ${e.message}` },
1332
1400
  ],
1333
1401
  };
1334
1402
  }
@@ -1339,7 +1407,6 @@ async function handleGetTelepathicHints(args) {
1339
1407
  const limit = typeof args?.limit === "number" ? args.limit : 5;
1340
1408
  if (!activeFile)
1341
1409
  throw new Error("activeFile is required");
1342
- const port = (0, utils_1.getProxyPort)();
1343
1410
  const basename = path_1.default.basename(activeFile, path_1.default.extname(activeFile));
1344
1411
  const dirContext = path_1.default
1345
1412
  .dirname(activeFile)
@@ -1350,8 +1417,8 @@ async function handleGetTelepathicHints(args) {
1350
1417
  const ext = path_1.default.extname(activeFile).replace(".", "");
1351
1418
  const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
1352
1419
  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 || [];
1420
+ const brain = (0, TheBrainV2_1.getBrain)();
1421
+ const results = brain.search(telepathicQuery, limit);
1355
1422
  if (results.length === 0) {
1356
1423
  return {
1357
1424
  content: [
@@ -1365,11 +1432,8 @@ async function handleGetTelepathicHints(args) {
1365
1432
  let hintsText = `Telepathic Hints for \`${activeFile}\`\n*(${results.length} relevant memories surfaced from The Brain)*\n\n`;
1366
1433
  results.forEach((r, i) => {
1367
1434
  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);
1435
+ const prompt = r.query || "Unknown";
1436
+ const responseContent = r.response || "";
1373
1437
  hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
1374
1438
  hintsText += `**Memory:** ${prompt}\n\n`;
1375
1439
  hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
@@ -1389,7 +1453,7 @@ async function handleGetTelepathicHints(args) {
1389
1453
  content: [
1390
1454
  {
1391
1455
  type: "text",
1392
- text: `Telepathic Hints failed: ${e.message}. Is Lemma Proxy running?`,
1456
+ text: `Telepathic Hints failed: ${e.message}`,
1393
1457
  },
1394
1458
  ],
1395
1459
  };
@@ -1401,17 +1465,8 @@ async function handleSummarizeLongText(args) {
1401
1465
  const maxLength = args?.maxLength || 500;
1402
1466
  if (!text)
1403
1467
  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
1468
  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();
1469
+ const summary = (0, ContextCollapser_1.collapseContext)(text, { maxLength });
1415
1470
  (0, reportSavings_1.reportSavings)({
1416
1471
  source: "contextSqueeze",
1417
1472
  charsBefore: text.length,
@@ -1422,7 +1477,7 @@ async function handleSummarizeLongText(args) {
1422
1477
  }
1423
1478
  catch (e) {
1424
1479
  (0, utils_1.logError)("summarize_long_text", e);
1425
- return { content: [{ type: "text", text: `Summarization failed: ${e.message}. Is Ollama running?` }] };
1480
+ return { content: [{ type: "text", text: `Summarization failed: ${e.message}` }] };
1426
1481
  }
1427
1482
  }
1428
1483
  // ── Prune Conversation History ──────────────────────────────────────
@@ -1571,21 +1626,14 @@ async function handleCompressContext(args) {
1571
1626
  const recent = messages.slice(-keepRecent);
1572
1627
  const old = messages.slice(0, -keepRecent);
1573
1628
  let summary = "";
1574
- let summaryMethod = "trim";
1629
+ let summaryMethod = "local-collapser";
1575
1630
  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";
1631
+ const oldText = old.map((m, i) => `[Turn ${i + 1} / ${m.role}]: ${typeof m.content === 'string' ? m.content : '...'}`).join('\n');
1632
+ summary = (0, ContextCollapser_1.collapseContext)(oldText, { maxLength: 800 });
1585
1633
  }
1586
- catch {
1634
+ catch (err) {
1587
1635
  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";
1636
+ summaryMethod = "truncate-fallback";
1589
1637
  }
1590
1638
  const oldLength = JSON.stringify(old).length;
1591
1639
  const summaryLength = summary.length;
@@ -1613,16 +1661,13 @@ async function handleSmarterCache(args) {
1613
1661
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.75;
1614
1662
  if (!query)
1615
1663
  throw new Error("query is required");
1616
- const port = (0, utils_1.getProxyPort)();
1617
1664
  const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
1618
1665
  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 || [];
1666
+ const brain = (0, TheBrainV2_1.getBrain)();
1667
+ const results = brain.search(fullQuery, 5);
1621
1668
  const topHit = results[0];
1622
1669
  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);
1670
+ const responseText = topHit.response;
1626
1671
  const tokensSaved = Math.floor(responseText.length / 4);
1627
1672
  try {
1628
1673
  const { reportSavings } = await Promise.resolve().then(() => __importStar(require("../utils/reportSavings")));
@@ -1667,7 +1712,7 @@ async function handleSmarterCache(args) {
1667
1712
  hit: false,
1668
1713
  error: e.message,
1669
1714
  source: "cache-unavailable",
1670
- hint: "Lemma Proxy no está corriendo. El LLM funcionará normalmente sin cache.",
1715
+ hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
1671
1716
  }, null, 2),
1672
1717
  }],
1673
1718
  };
@@ -2320,10 +2365,9 @@ async function handleSemanticDedupGuard(args) {
2320
2365
  const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
2321
2366
  if (!query || !response)
2322
2367
  throw new Error("query and response are required");
2323
- const port = (0, utils_1.getProxyPort)();
2324
2368
  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 || [];
2369
+ const brain = (0, TheBrainV2_1.getBrain)();
2370
+ const results = brain.search(query, 3);
2327
2371
  const topHit = results[0];
2328
2372
  if (!topHit) {
2329
2373
  return {
@@ -2334,17 +2378,18 @@ async function handleSemanticDedupGuard(args) {
2334
2378
  }, null, 2) }],
2335
2379
  };
2336
2380
  }
2337
- const sim = topHit.similarity || 0;
2381
+ let sim = topHit.similarity || 0;
2382
+ if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
2383
+ sim = 1.0;
2384
+ }
2338
2385
  if (sim >= threshold) {
2339
2386
  return {
2340
2387
  content: [{ type: "text", text: JSON.stringify({
2341
2388
  verdict: "DUPLICATE_REJECTED",
2342
2389
  reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
2343
2390
  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),
2391
+ existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
2392
+ existingResponse: (topHit.response || "").substring(0, 300),
2348
2393
  tokensSaved: Math.floor(response.length / 4),
2349
2394
  }, null, 2) }],
2350
2395
  };
@@ -2355,7 +2400,7 @@ async function handleSemanticDedupGuard(args) {
2355
2400
  verdict: "SIMILAR_WARNING",
2356
2401
  reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
2357
2402
  similarity: sim,
2358
- existingQuery: typeof topHit.prompt === "string" ? topHit.prompt.substring(0, 200) : "N/A",
2403
+ existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
2359
2404
  recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
2360
2405
  }, null, 2) }],
2361
2406
  };
@@ -2373,7 +2418,7 @@ async function handleSemanticDedupGuard(args) {
2373
2418
  return {
2374
2419
  content: [{ type: "text", text: JSON.stringify({
2375
2420
  verdict: "STORE_ALLOWED",
2376
- reason: "No se pudo consultar The Brain (proxy no disponible). Procediendo con store.",
2421
+ reason: "El cache local del brain falló. Procediendo con store.",
2377
2422
  error: e.message,
2378
2423
  }, null, 2) }],
2379
2424
  };
@@ -3279,4 +3324,185 @@ async function handleRefactor(args) {
3279
3324
  return { content: [{ type: "text", text: `refactor failed: ${err.message}` }] };
3280
3325
  }
3281
3326
  }
3327
+ // ── Surgical Symbol Context ──────────────────────────────────────────
3328
+ async function handleGetSymbolSurgicalContext(args) {
3329
+ const filePath = args?.filePath;
3330
+ const symbolName = args?.symbolName;
3331
+ if (!filePath || !symbolName)
3332
+ throw new Error("filePath and symbolName are required");
3333
+ const workspaceRoot = process.cwd();
3334
+ try {
3335
+ const res = (0, SymbolSurgicalContext_1.getSymbolSurgicalContext)(workspaceRoot, filePath, symbolName);
3336
+ const lines = [];
3337
+ lines.push(`# 🧬 Surgical Context: Symbol \`${symbolName}\` in \`${filePath}\``);
3338
+ lines.push(`## Implementation`);
3339
+ lines.push(`\`\`\`typescript\n${res.implementation}\n\`\`\``);
3340
+ if (res.dependencies.length > 0) {
3341
+ lines.push(`## Workspace Dependencies (Signatures ONLY)`);
3342
+ for (const dep of res.dependencies) {
3343
+ lines.push(`### \`${dep.name}\` (${dep.type}) declared in \`${dep.filePath}\``);
3344
+ lines.push(`\`\`\`typescript\n${dep.signature}\n\`\`\``);
3345
+ }
3346
+ }
3347
+ else {
3348
+ lines.push(`*No local workspace dependencies detected for this symbol.*`);
3349
+ }
3350
+ const output = lines.join("\n\n");
3351
+ const originalSize = fs_1.default.readFileSync(path_1.default.resolve(workspaceRoot, filePath), "utf8").length;
3352
+ const totalRawSize = originalSize + res.dependencies.reduce((sum, dep) => {
3353
+ try {
3354
+ return sum + fs_1.default.readFileSync(path_1.default.resolve(workspaceRoot, dep.filePath), "utf8").length;
3355
+ }
3356
+ catch {
3357
+ return sum;
3358
+ }
3359
+ }, 0);
3360
+ (0, reportSavings_1.reportSavings)({
3361
+ source: "contextSqueeze",
3362
+ charsBefore: totalRawSize,
3363
+ charsAfter: output.length,
3364
+ toolName: "get_symbol_surgical_context",
3365
+ filePath
3366
+ });
3367
+ return {
3368
+ content: [{ type: "text", text: output }]
3369
+ };
3370
+ }
3371
+ catch (err) {
3372
+ (0, utils_1.logError)("get_symbol_surgical_context", err);
3373
+ return {
3374
+ content: [{ type: "text", text: `Surgical context extraction failed: ${err.message}` }]
3375
+ };
3376
+ }
3377
+ }
3378
+ // ── Wormhole Squeeze ────────────────────────────────────────────────
3379
+ async function handleWormholeSqueeze(args) {
3380
+ const text = args?.text;
3381
+ if (!text)
3382
+ throw new Error("text is required");
3383
+ try {
3384
+ const compressed = (0, WormholeCompressor_1.compressWormhole)(text);
3385
+ (0, reportSavings_1.reportSavings)({
3386
+ source: "contextSqueeze",
3387
+ charsBefore: text.length,
3388
+ charsAfter: compressed.length,
3389
+ toolName: "wormhole_squeeze"
3390
+ });
3391
+ return {
3392
+ content: [{ type: "text", text: compressed }]
3393
+ };
3394
+ }
3395
+ catch (err) {
3396
+ (0, utils_1.logError)("wormhole_squeeze", err);
3397
+ return {
3398
+ content: [{ type: "text", text: `Wormhole compression failed: ${err.message}` }]
3399
+ };
3400
+ }
3401
+ }
3402
+ // ── Executive ROI Report ──────────────────────────────────────────────
3403
+ async function handleGenerateExecutiveRoiReport(args) {
3404
+ const teamSize = typeof args?.teamSize === "number" ? args.teamSize : 10;
3405
+ const costPerDevHour = typeof args?.costPerDevHour === "number" ? args.costPerDevHour : 50;
3406
+ const targetPath = args?.targetPath || "LEMMA_ROI_REPORT.md";
3407
+ const workspaceRoot = process.cwd();
3408
+ try {
3409
+ const res = (0, ExecutiveReport_1.generateExecutiveReport)(workspaceRoot, {
3410
+ teamSize,
3411
+ costPerDevHour,
3412
+ targetPath
3413
+ });
3414
+ return {
3415
+ content: [{
3416
+ type: "text",
3417
+ 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`
3418
+ }]
3419
+ };
3420
+ }
3421
+ catch (err) {
3422
+ (0, utils_1.logError)("generate_executive_roi_report", err);
3423
+ return {
3424
+ content: [{ type: "text", text: `ROI Report generation failed: ${err.message}` }]
3425
+ };
3426
+ }
3427
+ }
3428
+ // ── Surgical AST Insert ──────────────────────────────────────────────
3429
+ async function handleSurgicalAstInsert(args) {
3430
+ const filePath = args?.filePath;
3431
+ const symbolName = args?.symbolName;
3432
+ const anchorSymbol = args?.anchorSymbol;
3433
+ const insertPosition = args?.insertPosition;
3434
+ const codeToInsert = args?.codeToInsert;
3435
+ if (!filePath || !codeToInsert)
3436
+ throw new Error("filePath and codeToInsert are required");
3437
+ const workspaceRoot = process.cwd();
3438
+ try {
3439
+ const resolvedPath = (0, SurgicalASTInsert_1.surgicalASTInsert)(workspaceRoot, {
3440
+ filePath,
3441
+ symbolName,
3442
+ anchorSymbol,
3443
+ insertPosition,
3444
+ codeToInsert
3445
+ });
3446
+ const newTextSize = fs_1.default.readFileSync(resolvedPath, "utf8").length;
3447
+ const outputCharsSaved = Math.max(0, newTextSize - codeToInsert.length);
3448
+ const tokensSaved = Math.floor(outputCharsSaved / 4);
3449
+ (0, reportSavings_1.reportSavings)({
3450
+ source: "contextSqueeze",
3451
+ tokens: tokensSaved,
3452
+ toolName: "surgical_ast_insert",
3453
+ filePath
3454
+ });
3455
+ return {
3456
+ content: [{
3457
+ type: "text",
3458
+ text: `Success: Surgically inserted code into ${filePath} using AST compiler.`
3459
+ }]
3460
+ };
3461
+ }
3462
+ catch (err) {
3463
+ (0, utils_1.logError)("surgical_ast_insert", err);
3464
+ return {
3465
+ content: [{ type: "text", text: `Surgical AST insertion failed: ${err.message}` }]
3466
+ };
3467
+ }
3468
+ }
3469
+ // ── Local Semantic Autofix ────────────────────────────────────────────
3470
+ async function handleLocalSemanticAutofix(args) {
3471
+ const errorMessage = args?.errorMessage;
3472
+ if (!errorMessage)
3473
+ throw new Error("errorMessage is required");
3474
+ try {
3475
+ const brain = (0, TheBrainV2_1.getBrain)();
3476
+ const results = brain.search(errorMessage, 3, 0.70);
3477
+ const topHit = results[0];
3478
+ if (topHit) {
3479
+ const responseText = topHit.response;
3480
+ const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
3481
+ (0, reportSavings_1.reportSavings)({
3482
+ source: "cache",
3483
+ tokens: tokensSaved,
3484
+ toolName: "local_semantic_autofix",
3485
+ query: errorMessage.substring(0, 100)
3486
+ });
3487
+ return {
3488
+ content: [{
3489
+ type: "text",
3490
+ 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}`
3491
+ }]
3492
+ };
3493
+ }
3494
+ return {
3495
+ content: [{
3496
+ type: "text",
3497
+ 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!`
3498
+ }]
3499
+ };
3500
+ }
3501
+ catch (err) {
3502
+ (0, utils_1.logError)("local_semantic_autofix", err);
3503
+ return {
3504
+ content: [{ type: "text", text: `Local auto-fix failed: ${err.message}` }]
3505
+ };
3506
+ }
3507
+ }
3282
3508
  //# sourceMappingURL=tools.js.map