@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.
- package/README.md +35 -4
- package/bin/cli.js +97 -0
- package/bin/init.js +307 -0
- package/cache-proxy.js +191 -0
- package/dist/cjs/autopilot/AutopilotWatcher.d.ts +3 -2
- package/dist/cjs/autopilot/AutopilotWatcher.d.ts.map +1 -1
- package/dist/cjs/autopilot/AutopilotWatcher.js +48 -54
- package/dist/cjs/autopilot/AutopilotWatcher.js.map +1 -1
- package/dist/cjs/autopilot/Notifier.d.ts +5 -0
- package/dist/cjs/autopilot/Notifier.d.ts.map +1 -0
- package/dist/cjs/autopilot/Notifier.js +59 -0
- package/dist/cjs/autopilot/Notifier.js.map +1 -0
- package/dist/cjs/autopilot/PatchStore.d.ts +26 -0
- package/dist/cjs/autopilot/PatchStore.d.ts.map +1 -0
- package/dist/cjs/autopilot/PatchStore.js +88 -0
- package/dist/cjs/autopilot/PatchStore.js.map +1 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +123 -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 +303 -77
- 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/autopilot/AutopilotWatcher.d.ts +3 -2
- package/dist/esm/autopilot/AutopilotWatcher.d.ts.map +1 -1
- package/dist/esm/autopilot/AutopilotWatcher.js +48 -54
- package/dist/esm/autopilot/AutopilotWatcher.js.map +1 -1
- package/dist/esm/autopilot/Notifier.d.ts +5 -0
- package/dist/esm/autopilot/Notifier.d.ts.map +1 -0
- package/dist/esm/autopilot/Notifier.js +51 -0
- package/dist/esm/autopilot/Notifier.js.map +1 -0
- package/dist/esm/autopilot/PatchStore.d.ts +26 -0
- package/dist/esm/autopilot/PatchStore.d.ts.map +1 -0
- package/dist/esm/autopilot/PatchStore.js +77 -0
- package/dist/esm/autopilot/PatchStore.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +123 -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 +303 -77
- 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 +7 -2
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;
|
|
@@ -30,11 +36,16 @@ function isProUser() {
|
|
|
30
36
|
_proStatusCache = false;
|
|
31
37
|
return false;
|
|
32
38
|
}
|
|
39
|
+
const FREE_TOOLS = new Set([
|
|
40
|
+
// Cache & Memory — the hook that shows instant value
|
|
41
|
+
"smarter_cache", "search_memory", "store_memory",
|
|
42
|
+
// Token optimization — shows what they're saving
|
|
43
|
+
"token_budget", "squeeze_prompt", "turbosqueeze",
|
|
44
|
+
// Utility — just enough to function
|
|
45
|
+
"read_workspace_file", "scrub_privacy", "summarize_long_text",
|
|
46
|
+
]);
|
|
33
47
|
const PRO_GATE_MESSAGE = [
|
|
34
|
-
"🔒
|
|
35
|
-
"",
|
|
36
|
-
"All 32 MCP tools — including The Brain, zero-token static analysis,",
|
|
37
|
-
"PreCrime predictor, Coupling Radar, and Entropy Score — are Pro features.",
|
|
48
|
+
"🔒 This tool requires Lemma Pro.",
|
|
38
49
|
"",
|
|
39
50
|
"✨ Activate your license:",
|
|
40
51
|
" lemma activate <your-key>",
|
|
@@ -320,6 +331,67 @@ const toolDefinitions = [
|
|
|
320
331
|
required: ["text"],
|
|
321
332
|
},
|
|
322
333
|
},
|
|
334
|
+
{
|
|
335
|
+
name: "get_symbol_surgical_context",
|
|
336
|
+
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.",
|
|
337
|
+
inputSchema: {
|
|
338
|
+
type: "object",
|
|
339
|
+
properties: {
|
|
340
|
+
filePath: { type: "string", description: "Ruta relativa del archivo que contiene el símbolo" },
|
|
341
|
+
symbolName: { type: "string", description: "Nombre de la función, clase, tipo o interfaz a extraer" },
|
|
342
|
+
},
|
|
343
|
+
required: ["filePath", "symbolName"],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "wormhole_squeeze",
|
|
348
|
+
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.",
|
|
349
|
+
inputSchema: {
|
|
350
|
+
type: "object",
|
|
351
|
+
properties: {
|
|
352
|
+
text: { type: "string", description: "El código fuente a comprimir" },
|
|
353
|
+
},
|
|
354
|
+
required: ["text"],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "generate_executive_roi_report",
|
|
359
|
+
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.",
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: "object",
|
|
362
|
+
properties: {
|
|
363
|
+
teamSize: { type: "number", description: "Número de ingenieros en el equipo (default: 10)", default: 10 },
|
|
364
|
+
costPerDevHour: { type: "number", description: "Costo estimado en USD por hora de desarrollador (default: 50)", default: 50 },
|
|
365
|
+
targetPath: { type: "string", description: "Nombre de archivo del reporte generado (default: LEMMA_ROI_REPORT.md)", default: "LEMMA_ROI_REPORT.md" },
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: "surgical_ast_insert",
|
|
371
|
+
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.",
|
|
372
|
+
inputSchema: {
|
|
373
|
+
type: "object",
|
|
374
|
+
properties: {
|
|
375
|
+
filePath: { type: "string", description: "Ruta del archivo TypeScript (relativo al root)" },
|
|
376
|
+
symbolName: { type: "string", description: "Nombre de la clase o interfaz donde insertar (opcional)" },
|
|
377
|
+
anchorSymbol: { type: "string", description: "Nombre del método o propiedad como anclaje (ej. 'login')" },
|
|
378
|
+
insertPosition: { type: "string", enum: ["before", "after", "start", "end"], description: "Posición relativa al anclaje (default: 'after')", default: "after" },
|
|
379
|
+
codeToInsert: { type: "string", description: "El código fuente exacto a insertar" },
|
|
380
|
+
},
|
|
381
|
+
required: ["filePath", "codeToInsert"],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: "local_semantic_autofix",
|
|
386
|
+
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.",
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
errorMessage: { type: "string", description: "El traceback del error o mensaje de error a resolver" },
|
|
391
|
+
},
|
|
392
|
+
required: ["errorMessage"],
|
|
393
|
+
},
|
|
394
|
+
},
|
|
323
395
|
{
|
|
324
396
|
name: "compress_context",
|
|
325
397
|
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 +620,11 @@ const toolHandlers = {
|
|
|
548
620
|
diff_only: handleDiffOnly,
|
|
549
621
|
batch_tool_calls: handleBatchToolCalls,
|
|
550
622
|
turbosqueeze: handleTurboSqueeze,
|
|
623
|
+
get_symbol_surgical_context: handleGetSymbolSurgicalContext,
|
|
624
|
+
wormhole_squeeze: handleWormholeSqueeze,
|
|
625
|
+
generate_executive_roi_report: handleGenerateExecutiveRoiReport,
|
|
626
|
+
surgical_ast_insert: handleSurgicalAstInsert,
|
|
627
|
+
local_semantic_autofix: handleLocalSemanticAutofix,
|
|
551
628
|
compress_context: handleCompressContext,
|
|
552
629
|
smarter_cache: handleSmarterCache,
|
|
553
630
|
token_budget: handleTokenBudget,
|
|
@@ -572,8 +649,8 @@ export function setupToolsHandlers(server, onToolCall) {
|
|
|
572
649
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
573
650
|
const { name, arguments: args } = request.params;
|
|
574
651
|
const startTime = Date.now();
|
|
575
|
-
// ── Pro License Gate
|
|
576
|
-
if (!isProUser()) {
|
|
652
|
+
// ── Pro License Gate (granular: free tools work without license) ────────
|
|
653
|
+
if (!isProUser() && !FREE_TOOLS.has(name)) {
|
|
577
654
|
onToolCall?.({
|
|
578
655
|
tool: name,
|
|
579
656
|
args: args || {},
|
|
@@ -636,22 +713,21 @@ async function handleSearchMemory(args) {
|
|
|
636
713
|
if (!query)
|
|
637
714
|
throw new Error("Query is required");
|
|
638
715
|
try {
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
const results = response.data.results || [];
|
|
716
|
+
const brain = getBrain();
|
|
717
|
+
const results = brain.search(query, limit);
|
|
642
718
|
if (results.length === 0) {
|
|
643
719
|
return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
|
|
644
720
|
}
|
|
645
721
|
const formatted = results
|
|
646
722
|
.map((r, i) => {
|
|
647
|
-
return `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.
|
|
723
|
+
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
724
|
})
|
|
649
725
|
.join("\n\n---\n\n");
|
|
650
726
|
return { content: [{ type: "text", text: `Lemma found ${results.length} memories:\n\n${formatted}` }] };
|
|
651
727
|
}
|
|
652
728
|
catch (e) {
|
|
653
729
|
logError("search_memory", e);
|
|
654
|
-
return { content: [{ type: "text", text: `
|
|
730
|
+
return { content: [{ type: "text", text: `Local Search failed: ${e.message}` }] };
|
|
655
731
|
}
|
|
656
732
|
}
|
|
657
733
|
async function handleStoreMemory(args) {
|
|
@@ -661,12 +737,8 @@ async function handleStoreMemory(args) {
|
|
|
661
737
|
if (!query || !responseText)
|
|
662
738
|
throw new Error("Query and response are required");
|
|
663
739
|
try {
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
query,
|
|
667
|
-
response: responseText,
|
|
668
|
-
provider,
|
|
669
|
-
});
|
|
740
|
+
const brain = getBrain();
|
|
741
|
+
const storeRes = brain.store(query, responseText, provider);
|
|
670
742
|
const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
|
|
671
743
|
reportSavings({
|
|
672
744
|
source: "cache",
|
|
@@ -674,11 +746,11 @@ async function handleStoreMemory(args) {
|
|
|
674
746
|
toolName: "store_memory",
|
|
675
747
|
query: query.substring(0, 100),
|
|
676
748
|
});
|
|
677
|
-
return { content: [{ type: "text", text: `Success: ${
|
|
749
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}` }] };
|
|
678
750
|
}
|
|
679
751
|
catch (e) {
|
|
680
752
|
logError("store_memory", e);
|
|
681
|
-
return { content: [{ type: "text", text: `Failed to store memory in
|
|
753
|
+
return { content: [{ type: "text", text: `Failed to store memory in local Brain: ${e.message}` }] };
|
|
682
754
|
}
|
|
683
755
|
}
|
|
684
756
|
async function handleGetRoutingAdvice(args) {
|
|
@@ -1246,24 +1318,20 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1246
1318
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.8;
|
|
1247
1319
|
if (!query)
|
|
1248
1320
|
throw new Error("query is required");
|
|
1249
|
-
const port = getProxyPort();
|
|
1250
1321
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1251
1322
|
try {
|
|
1252
|
-
const
|
|
1253
|
-
const results =
|
|
1323
|
+
const brain = getBrain();
|
|
1324
|
+
const results = brain.search(fullQuery, 3);
|
|
1254
1325
|
const topHit = results[0];
|
|
1255
1326
|
if (topHit && topHit.similarity >= threshold) {
|
|
1256
|
-
const savedTokens = Math.floor((topHit.response?.
|
|
1327
|
+
const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
|
|
1257
1328
|
reportSavings({
|
|
1258
1329
|
source: "cache",
|
|
1259
1330
|
tokens: savedTokens,
|
|
1260
1331
|
toolName: "query_hybrid_consensus",
|
|
1261
1332
|
query: query.substring(0, 100),
|
|
1262
1333
|
});
|
|
1263
|
-
const responseText =
|
|
1264
|
-
? topHit.response
|
|
1265
|
-
: topHit.response?.choices?.[0]?.message?.content ||
|
|
1266
|
-
JSON.stringify(topHit.response, null, 2);
|
|
1334
|
+
const responseText = topHit.response;
|
|
1267
1335
|
return {
|
|
1268
1336
|
content: [
|
|
1269
1337
|
{
|
|
@@ -1289,7 +1357,7 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1289
1357
|
logError("query_hybrid_consensus", e);
|
|
1290
1358
|
return {
|
|
1291
1359
|
content: [
|
|
1292
|
-
{ type: "text", text: `Hybrid Consensus failed: ${e.message}
|
|
1360
|
+
{ type: "text", text: `Hybrid Consensus failed: ${e.message}` },
|
|
1293
1361
|
],
|
|
1294
1362
|
};
|
|
1295
1363
|
}
|
|
@@ -1300,7 +1368,6 @@ async function handleGetTelepathicHints(args) {
|
|
|
1300
1368
|
const limit = typeof args?.limit === "number" ? args.limit : 5;
|
|
1301
1369
|
if (!activeFile)
|
|
1302
1370
|
throw new Error("activeFile is required");
|
|
1303
|
-
const port = getProxyPort();
|
|
1304
1371
|
const basename = path.basename(activeFile, path.extname(activeFile));
|
|
1305
1372
|
const dirContext = path
|
|
1306
1373
|
.dirname(activeFile)
|
|
@@ -1311,8 +1378,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1311
1378
|
const ext = path.extname(activeFile).replace(".", "");
|
|
1312
1379
|
const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
|
|
1313
1380
|
try {
|
|
1314
|
-
const
|
|
1315
|
-
const results =
|
|
1381
|
+
const brain = getBrain();
|
|
1382
|
+
const results = brain.search(telepathicQuery, limit);
|
|
1316
1383
|
if (results.length === 0) {
|
|
1317
1384
|
return {
|
|
1318
1385
|
content: [
|
|
@@ -1326,11 +1393,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1326
1393
|
let hintsText = `Telepathic Hints for \`${activeFile}\`\n*(${results.length} relevant memories surfaced from The Brain)*\n\n`;
|
|
1327
1394
|
results.forEach((r, i) => {
|
|
1328
1395
|
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);
|
|
1396
|
+
const prompt = r.query || "Unknown";
|
|
1397
|
+
const responseContent = r.response || "";
|
|
1334
1398
|
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
1335
1399
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
1336
1400
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
@@ -1350,7 +1414,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
1350
1414
|
content: [
|
|
1351
1415
|
{
|
|
1352
1416
|
type: "text",
|
|
1353
|
-
text: `Telepathic Hints failed: ${e.message}
|
|
1417
|
+
text: `Telepathic Hints failed: ${e.message}`,
|
|
1354
1418
|
},
|
|
1355
1419
|
],
|
|
1356
1420
|
};
|
|
@@ -1362,17 +1426,8 @@ async function handleSummarizeLongText(args) {
|
|
|
1362
1426
|
const maxLength = args?.maxLength || 500;
|
|
1363
1427
|
if (!text)
|
|
1364
1428
|
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
1429
|
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();
|
|
1430
|
+
const summary = collapseContext(text, { maxLength });
|
|
1376
1431
|
reportSavings({
|
|
1377
1432
|
source: "contextSqueeze",
|
|
1378
1433
|
charsBefore: text.length,
|
|
@@ -1383,7 +1438,7 @@ async function handleSummarizeLongText(args) {
|
|
|
1383
1438
|
}
|
|
1384
1439
|
catch (e) {
|
|
1385
1440
|
logError("summarize_long_text", e);
|
|
1386
|
-
return { content: [{ type: "text", text: `Summarization failed: ${e.message}
|
|
1441
|
+
return { content: [{ type: "text", text: `Summarization failed: ${e.message}` }] };
|
|
1387
1442
|
}
|
|
1388
1443
|
}
|
|
1389
1444
|
// ── Prune Conversation History ──────────────────────────────────────
|
|
@@ -1532,21 +1587,14 @@ async function handleCompressContext(args) {
|
|
|
1532
1587
|
const recent = messages.slice(-keepRecent);
|
|
1533
1588
|
const old = messages.slice(0, -keepRecent);
|
|
1534
1589
|
let summary = "";
|
|
1535
|
-
let summaryMethod = "
|
|
1590
|
+
let summaryMethod = "local-collapser";
|
|
1536
1591
|
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";
|
|
1592
|
+
const oldText = old.map((m, i) => `[Turn ${i + 1} / ${m.role}]: ${typeof m.content === 'string' ? m.content : '...'}`).join('\n');
|
|
1593
|
+
summary = collapseContext(oldText, { maxLength: 800 });
|
|
1546
1594
|
}
|
|
1547
|
-
catch {
|
|
1595
|
+
catch (err) {
|
|
1548
1596
|
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";
|
|
1597
|
+
summaryMethod = "truncate-fallback";
|
|
1550
1598
|
}
|
|
1551
1599
|
const oldLength = JSON.stringify(old).length;
|
|
1552
1600
|
const summaryLength = summary.length;
|
|
@@ -1574,16 +1622,13 @@ async function handleSmarterCache(args) {
|
|
|
1574
1622
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.75;
|
|
1575
1623
|
if (!query)
|
|
1576
1624
|
throw new Error("query is required");
|
|
1577
|
-
const port = getProxyPort();
|
|
1578
1625
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1579
1626
|
try {
|
|
1580
|
-
const
|
|
1581
|
-
const results =
|
|
1627
|
+
const brain = getBrain();
|
|
1628
|
+
const results = brain.search(fullQuery, 5);
|
|
1582
1629
|
const topHit = results[0];
|
|
1583
1630
|
if (topHit && topHit.similarity >= threshold) {
|
|
1584
|
-
const responseText =
|
|
1585
|
-
? topHit.response
|
|
1586
|
-
: topHit.response?.choices?.[0]?.message?.content || JSON.stringify(topHit.response, null, 2);
|
|
1631
|
+
const responseText = topHit.response;
|
|
1587
1632
|
const tokensSaved = Math.floor(responseText.length / 4);
|
|
1588
1633
|
try {
|
|
1589
1634
|
const { reportSavings } = await import("../utils/reportSavings");
|
|
@@ -1628,7 +1673,7 @@ async function handleSmarterCache(args) {
|
|
|
1628
1673
|
hit: false,
|
|
1629
1674
|
error: e.message,
|
|
1630
1675
|
source: "cache-unavailable",
|
|
1631
|
-
hint: "
|
|
1676
|
+
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
1632
1677
|
}, null, 2),
|
|
1633
1678
|
}],
|
|
1634
1679
|
};
|
|
@@ -2281,10 +2326,9 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2281
2326
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
|
|
2282
2327
|
if (!query || !response)
|
|
2283
2328
|
throw new Error("query and response are required");
|
|
2284
|
-
const port = getProxyPort();
|
|
2285
2329
|
try {
|
|
2286
|
-
const
|
|
2287
|
-
const results =
|
|
2330
|
+
const brain = getBrain();
|
|
2331
|
+
const results = brain.search(query, 3);
|
|
2288
2332
|
const topHit = results[0];
|
|
2289
2333
|
if (!topHit) {
|
|
2290
2334
|
return {
|
|
@@ -2295,17 +2339,18 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2295
2339
|
}, null, 2) }],
|
|
2296
2340
|
};
|
|
2297
2341
|
}
|
|
2298
|
-
|
|
2342
|
+
let sim = topHit.similarity || 0;
|
|
2343
|
+
if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
|
|
2344
|
+
sim = 1.0;
|
|
2345
|
+
}
|
|
2299
2346
|
if (sim >= threshold) {
|
|
2300
2347
|
return {
|
|
2301
2348
|
content: [{ type: "text", text: JSON.stringify({
|
|
2302
2349
|
verdict: "DUPLICATE_REJECTED",
|
|
2303
2350
|
reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
|
|
2304
2351
|
similarity: sim,
|
|
2305
|
-
existingQuery: typeof topHit.
|
|
2306
|
-
existingResponse: (
|
|
2307
|
-
? topHit.response
|
|
2308
|
-
: topHit.response?.choices?.[0]?.message?.content || "").substring(0, 300),
|
|
2352
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
2353
|
+
existingResponse: (topHit.response || "").substring(0, 300),
|
|
2309
2354
|
tokensSaved: Math.floor(response.length / 4),
|
|
2310
2355
|
}, null, 2) }],
|
|
2311
2356
|
};
|
|
@@ -2316,7 +2361,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2316
2361
|
verdict: "SIMILAR_WARNING",
|
|
2317
2362
|
reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
|
|
2318
2363
|
similarity: sim,
|
|
2319
|
-
existingQuery: typeof topHit.
|
|
2364
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
2320
2365
|
recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
|
|
2321
2366
|
}, null, 2) }],
|
|
2322
2367
|
};
|
|
@@ -2334,7 +2379,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2334
2379
|
return {
|
|
2335
2380
|
content: [{ type: "text", text: JSON.stringify({
|
|
2336
2381
|
verdict: "STORE_ALLOWED",
|
|
2337
|
-
reason: "
|
|
2382
|
+
reason: "El cache local del brain falló. Procediendo con store.",
|
|
2338
2383
|
error: e.message,
|
|
2339
2384
|
}, null, 2) }],
|
|
2340
2385
|
};
|
|
@@ -3240,4 +3285,185 @@ async function handleRefactor(args) {
|
|
|
3240
3285
|
return { content: [{ type: "text", text: `refactor failed: ${err.message}` }] };
|
|
3241
3286
|
}
|
|
3242
3287
|
}
|
|
3288
|
+
// ── Surgical Symbol Context ──────────────────────────────────────────
|
|
3289
|
+
async function handleGetSymbolSurgicalContext(args) {
|
|
3290
|
+
const filePath = args?.filePath;
|
|
3291
|
+
const symbolName = args?.symbolName;
|
|
3292
|
+
if (!filePath || !symbolName)
|
|
3293
|
+
throw new Error("filePath and symbolName are required");
|
|
3294
|
+
const workspaceRoot = process.cwd();
|
|
3295
|
+
try {
|
|
3296
|
+
const res = getSymbolSurgicalContext(workspaceRoot, filePath, symbolName);
|
|
3297
|
+
const lines = [];
|
|
3298
|
+
lines.push(`# 🧬 Surgical Context: Symbol \`${symbolName}\` in \`${filePath}\``);
|
|
3299
|
+
lines.push(`## Implementation`);
|
|
3300
|
+
lines.push(`\`\`\`typescript\n${res.implementation}\n\`\`\``);
|
|
3301
|
+
if (res.dependencies.length > 0) {
|
|
3302
|
+
lines.push(`## Workspace Dependencies (Signatures ONLY)`);
|
|
3303
|
+
for (const dep of res.dependencies) {
|
|
3304
|
+
lines.push(`### \`${dep.name}\` (${dep.type}) declared in \`${dep.filePath}\``);
|
|
3305
|
+
lines.push(`\`\`\`typescript\n${dep.signature}\n\`\`\``);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
else {
|
|
3309
|
+
lines.push(`*No local workspace dependencies detected for this symbol.*`);
|
|
3310
|
+
}
|
|
3311
|
+
const output = lines.join("\n\n");
|
|
3312
|
+
const originalSize = fs.readFileSync(path.resolve(workspaceRoot, filePath), "utf8").length;
|
|
3313
|
+
const totalRawSize = originalSize + res.dependencies.reduce((sum, dep) => {
|
|
3314
|
+
try {
|
|
3315
|
+
return sum + fs.readFileSync(path.resolve(workspaceRoot, dep.filePath), "utf8").length;
|
|
3316
|
+
}
|
|
3317
|
+
catch {
|
|
3318
|
+
return sum;
|
|
3319
|
+
}
|
|
3320
|
+
}, 0);
|
|
3321
|
+
reportSavings({
|
|
3322
|
+
source: "contextSqueeze",
|
|
3323
|
+
charsBefore: totalRawSize,
|
|
3324
|
+
charsAfter: output.length,
|
|
3325
|
+
toolName: "get_symbol_surgical_context",
|
|
3326
|
+
filePath
|
|
3327
|
+
});
|
|
3328
|
+
return {
|
|
3329
|
+
content: [{ type: "text", text: output }]
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
catch (err) {
|
|
3333
|
+
logError("get_symbol_surgical_context", err);
|
|
3334
|
+
return {
|
|
3335
|
+
content: [{ type: "text", text: `Surgical context extraction failed: ${err.message}` }]
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
// ── Wormhole Squeeze ────────────────────────────────────────────────
|
|
3340
|
+
async function handleWormholeSqueeze(args) {
|
|
3341
|
+
const text = args?.text;
|
|
3342
|
+
if (!text)
|
|
3343
|
+
throw new Error("text is required");
|
|
3344
|
+
try {
|
|
3345
|
+
const compressed = compressWormhole(text);
|
|
3346
|
+
reportSavings({
|
|
3347
|
+
source: "contextSqueeze",
|
|
3348
|
+
charsBefore: text.length,
|
|
3349
|
+
charsAfter: compressed.length,
|
|
3350
|
+
toolName: "wormhole_squeeze"
|
|
3351
|
+
});
|
|
3352
|
+
return {
|
|
3353
|
+
content: [{ type: "text", text: compressed }]
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
catch (err) {
|
|
3357
|
+
logError("wormhole_squeeze", err);
|
|
3358
|
+
return {
|
|
3359
|
+
content: [{ type: "text", text: `Wormhole compression failed: ${err.message}` }]
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
// ── Executive ROI Report ──────────────────────────────────────────────
|
|
3364
|
+
async function handleGenerateExecutiveRoiReport(args) {
|
|
3365
|
+
const teamSize = typeof args?.teamSize === "number" ? args.teamSize : 10;
|
|
3366
|
+
const costPerDevHour = typeof args?.costPerDevHour === "number" ? args.costPerDevHour : 50;
|
|
3367
|
+
const targetPath = args?.targetPath || "LEMMA_ROI_REPORT.md";
|
|
3368
|
+
const workspaceRoot = process.cwd();
|
|
3369
|
+
try {
|
|
3370
|
+
const res = generateExecutiveReport(workspaceRoot, {
|
|
3371
|
+
teamSize,
|
|
3372
|
+
costPerDevHour,
|
|
3373
|
+
targetPath
|
|
3374
|
+
});
|
|
3375
|
+
return {
|
|
3376
|
+
content: [{
|
|
3377
|
+
type: "text",
|
|
3378
|
+
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`
|
|
3379
|
+
}]
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
catch (err) {
|
|
3383
|
+
logError("generate_executive_roi_report", err);
|
|
3384
|
+
return {
|
|
3385
|
+
content: [{ type: "text", text: `ROI Report generation failed: ${err.message}` }]
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
// ── Surgical AST Insert ──────────────────────────────────────────────
|
|
3390
|
+
async function handleSurgicalAstInsert(args) {
|
|
3391
|
+
const filePath = args?.filePath;
|
|
3392
|
+
const symbolName = args?.symbolName;
|
|
3393
|
+
const anchorSymbol = args?.anchorSymbol;
|
|
3394
|
+
const insertPosition = args?.insertPosition;
|
|
3395
|
+
const codeToInsert = args?.codeToInsert;
|
|
3396
|
+
if (!filePath || !codeToInsert)
|
|
3397
|
+
throw new Error("filePath and codeToInsert are required");
|
|
3398
|
+
const workspaceRoot = process.cwd();
|
|
3399
|
+
try {
|
|
3400
|
+
const resolvedPath = surgicalASTInsert(workspaceRoot, {
|
|
3401
|
+
filePath,
|
|
3402
|
+
symbolName,
|
|
3403
|
+
anchorSymbol,
|
|
3404
|
+
insertPosition,
|
|
3405
|
+
codeToInsert
|
|
3406
|
+
});
|
|
3407
|
+
const newTextSize = fs.readFileSync(resolvedPath, "utf8").length;
|
|
3408
|
+
const outputCharsSaved = Math.max(0, newTextSize - codeToInsert.length);
|
|
3409
|
+
const tokensSaved = Math.floor(outputCharsSaved / 4);
|
|
3410
|
+
reportSavings({
|
|
3411
|
+
source: "contextSqueeze",
|
|
3412
|
+
tokens: tokensSaved,
|
|
3413
|
+
toolName: "surgical_ast_insert",
|
|
3414
|
+
filePath
|
|
3415
|
+
});
|
|
3416
|
+
return {
|
|
3417
|
+
content: [{
|
|
3418
|
+
type: "text",
|
|
3419
|
+
text: `Success: Surgically inserted code into ${filePath} using AST compiler.`
|
|
3420
|
+
}]
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
catch (err) {
|
|
3424
|
+
logError("surgical_ast_insert", err);
|
|
3425
|
+
return {
|
|
3426
|
+
content: [{ type: "text", text: `Surgical AST insertion failed: ${err.message}` }]
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
// ── Local Semantic Autofix ────────────────────────────────────────────
|
|
3431
|
+
async function handleLocalSemanticAutofix(args) {
|
|
3432
|
+
const errorMessage = args?.errorMessage;
|
|
3433
|
+
if (!errorMessage)
|
|
3434
|
+
throw new Error("errorMessage is required");
|
|
3435
|
+
try {
|
|
3436
|
+
const brain = getBrain();
|
|
3437
|
+
const results = brain.search(errorMessage, 3, 0.70);
|
|
3438
|
+
const topHit = results[0];
|
|
3439
|
+
if (topHit) {
|
|
3440
|
+
const responseText = topHit.response;
|
|
3441
|
+
const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
|
|
3442
|
+
reportSavings({
|
|
3443
|
+
source: "cache",
|
|
3444
|
+
tokens: tokensSaved,
|
|
3445
|
+
toolName: "local_semantic_autofix",
|
|
3446
|
+
query: errorMessage.substring(0, 100)
|
|
3447
|
+
});
|
|
3448
|
+
return {
|
|
3449
|
+
content: [{
|
|
3450
|
+
type: "text",
|
|
3451
|
+
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}`
|
|
3452
|
+
}]
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
return {
|
|
3456
|
+
content: [{
|
|
3457
|
+
type: "text",
|
|
3458
|
+
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!`
|
|
3459
|
+
}]
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
catch (err) {
|
|
3463
|
+
logError("local_semantic_autofix", err);
|
|
3464
|
+
return {
|
|
3465
|
+
content: [{ type: "text", text: `Local auto-fix failed: ${err.message}` }]
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3243
3469
|
//# sourceMappingURL=tools.js.map
|