@nxuss/lemma 0.9.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -12
- package/bin/cli.js +97 -0
- package/bin/init.js +307 -0
- package/cache-proxy.js +191 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +2 -4
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +1 -1
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +911 -71
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.js +444 -0
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -0
- package/dist/cjs/utils/ContextCollapser.d.ts +21 -0
- package/dist/cjs/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/cjs/utils/ContextCollapser.js +132 -0
- package/dist/cjs/utils/ContextCollapser.js.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts +29 -0
- package/dist/cjs/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/cjs/utils/ExecutiveReport.js +111 -0
- package/dist/cjs/utils/ExecutiveReport.js.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/cjs/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/cjs/utils/SurgicalASTInsert.js +173 -0
- package/dist/cjs/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/cjs/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js +265 -0
- package/dist/cjs/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts +22 -0
- package/dist/cjs/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/cjs/utils/WormholeCompressor.js +152 -0
- package/dist/cjs/utils/WormholeCompressor.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +2 -4
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +911 -71
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts +102 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.js +431 -0
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -0
- package/dist/esm/utils/ContextCollapser.d.ts +21 -0
- package/dist/esm/utils/ContextCollapser.d.ts.map +1 -0
- package/dist/esm/utils/ContextCollapser.js +128 -0
- package/dist/esm/utils/ContextCollapser.js.map +1 -0
- package/dist/esm/utils/ExecutiveReport.d.ts +29 -0
- package/dist/esm/utils/ExecutiveReport.d.ts.map +1 -0
- package/dist/esm/utils/ExecutiveReport.js +105 -0
- package/dist/esm/utils/ExecutiveReport.js.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts +21 -0
- package/dist/esm/utils/SurgicalASTInsert.d.ts.map +1 -0
- package/dist/esm/utils/SurgicalASTInsert.js +134 -0
- package/dist/esm/utils/SurgicalASTInsert.js.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts +25 -0
- package/dist/esm/utils/SymbolSurgicalContext.d.ts.map +1 -0
- package/dist/esm/utils/SymbolSurgicalContext.js +226 -0
- package/dist/esm/utils/SymbolSurgicalContext.js.map +1 -0
- package/dist/esm/utils/WormholeCompressor.d.ts +22 -0
- package/dist/esm/utils/WormholeCompressor.d.ts.map +1 -0
- package/dist/esm/utils/WormholeCompressor.js +148 -0
- package/dist/esm/utils/WormholeCompressor.js.map +1 -0
- package/lemma-proxy.cjs +31 -14
- package/mcp-server.js +220 -0
- package/package.json +5 -1
package/dist/cjs/mcp/tools.js
CHANGED
|
@@ -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.",
|
|
@@ -530,6 +597,39 @@ const toolDefinitions = [
|
|
|
530
597
|
required: ["platform"],
|
|
531
598
|
},
|
|
532
599
|
},
|
|
600
|
+
{
|
|
601
|
+
name: "depgraph",
|
|
602
|
+
description: "Builds a real-time dependency graph for any file in the workspace using the TypeScript Compiler API. Shows what a file imports, what imports it (reverse deps), and what it exports. Zero LLM calls. Essential before any refactor to understand blast radius.",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
filePath: { type: "string", description: "Target file path relative to workspace root" },
|
|
607
|
+
reverse: { type: "boolean", description: "Include reverse dependencies (who imports this file)", default: true },
|
|
608
|
+
depth: { type: "number", description: "Graph traversal depth 1-5 (default: 1)", default: 1 },
|
|
609
|
+
format: { type: "string", enum: ["text", "mermaid", "json"], description: "Output format: text tree, Mermaid flowchart, or JSON", default: "text" },
|
|
610
|
+
external: { type: "boolean", description: "Include node_modules dependencies (default: false)", default: false },
|
|
611
|
+
circular: { type: "boolean", description: "Detect and highlight circular dependencies", default: false },
|
|
612
|
+
},
|
|
613
|
+
required: ["filePath"],
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
name: "refactor",
|
|
618
|
+
description: "Declarative multi-file codemod engine. Rename symbols across the entire workspace or move files updating all imports. Uses TypeScript Compiler API — zero LLM calls. Supports dry-run diff preview and post-refactor tsc verification.",
|
|
619
|
+
inputSchema: {
|
|
620
|
+
type: "object",
|
|
621
|
+
properties: {
|
|
622
|
+
operation: { type: "string", enum: ["rename", "move"], description: "Refactor operation: 'rename' a symbol everywhere, or 'move' a file updating all importers" },
|
|
623
|
+
from: { type: "string", description: "Symbol name to rename (rename op) OR source file path relative to workspace root (move op)" },
|
|
624
|
+
to: { type: "string", description: "New symbol name (rename op) OR destination file path relative to workspace root (move op)" },
|
|
625
|
+
file: { type: "string", description: "File where the symbol is declared — required for 'rename' operation" },
|
|
626
|
+
dryRun: { type: "boolean", description: "Preview changes as a unified diff without applying them", default: false },
|
|
627
|
+
tscCheck: { type: "boolean", description: "Run tsc --noEmit after applying to guarantee zero TypeScript errors", default: false },
|
|
628
|
+
backup: { type: "boolean", description: "Create .bak backup of each modified file before changing", default: false },
|
|
629
|
+
},
|
|
630
|
+
required: ["operation", "from", "to"],
|
|
631
|
+
},
|
|
632
|
+
},
|
|
533
633
|
];
|
|
534
634
|
const toolHandlers = {
|
|
535
635
|
scrub_privacy: handleScrubPrivacy,
|
|
@@ -554,6 +654,11 @@ const toolHandlers = {
|
|
|
554
654
|
diff_only: handleDiffOnly,
|
|
555
655
|
batch_tool_calls: handleBatchToolCalls,
|
|
556
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,
|
|
557
662
|
compress_context: handleCompressContext,
|
|
558
663
|
smarter_cache: handleSmarterCache,
|
|
559
664
|
token_budget: handleTokenBudget,
|
|
@@ -568,6 +673,8 @@ const toolHandlers = {
|
|
|
568
673
|
review_pr: handleReviewPR,
|
|
569
674
|
pr_status: handlePRStatus,
|
|
570
675
|
generate_pr_workflow: handleGeneratePRWorkflow,
|
|
676
|
+
depgraph: handleDepgraph,
|
|
677
|
+
refactor: handleRefactor,
|
|
571
678
|
};
|
|
572
679
|
function setupToolsHandlers(server, onToolCall) {
|
|
573
680
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
@@ -640,22 +747,21 @@ async function handleSearchMemory(args) {
|
|
|
640
747
|
if (!query)
|
|
641
748
|
throw new Error("Query is required");
|
|
642
749
|
try {
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
const results = response.data.results || [];
|
|
750
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
751
|
+
const results = brain.search(query, limit);
|
|
646
752
|
if (results.length === 0) {
|
|
647
753
|
return { content: [{ type: "text", text: "No relevant memories found in Lemma's Brain." }] };
|
|
648
754
|
}
|
|
649
755
|
const formatted = results
|
|
650
756
|
.map((r, i) => {
|
|
651
|
-
return `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.
|
|
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)}`;
|
|
652
758
|
})
|
|
653
759
|
.join("\n\n---\n\n");
|
|
654
760
|
return { content: [{ type: "text", text: `Lemma found ${results.length} memories:\n\n${formatted}` }] };
|
|
655
761
|
}
|
|
656
762
|
catch (e) {
|
|
657
763
|
(0, utils_1.logError)("search_memory", e);
|
|
658
|
-
return { content: [{ type: "text", text: `
|
|
764
|
+
return { content: [{ type: "text", text: `Local Search failed: ${e.message}` }] };
|
|
659
765
|
}
|
|
660
766
|
}
|
|
661
767
|
async function handleStoreMemory(args) {
|
|
@@ -665,12 +771,8 @@ async function handleStoreMemory(args) {
|
|
|
665
771
|
if (!query || !responseText)
|
|
666
772
|
throw new Error("Query and response are required");
|
|
667
773
|
try {
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
query,
|
|
671
|
-
response: responseText,
|
|
672
|
-
provider,
|
|
673
|
-
});
|
|
774
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
775
|
+
const storeRes = brain.store(query, responseText, provider);
|
|
674
776
|
const tokensSaved = Math.max(100, Math.floor(responseText.length / 4));
|
|
675
777
|
(0, reportSavings_1.reportSavings)({
|
|
676
778
|
source: "cache",
|
|
@@ -678,11 +780,11 @@ async function handleStoreMemory(args) {
|
|
|
678
780
|
toolName: "store_memory",
|
|
679
781
|
query: query.substring(0, 100),
|
|
680
782
|
});
|
|
681
|
-
return { content: [{ type: "text", text: `Success: ${
|
|
783
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}` }] };
|
|
682
784
|
}
|
|
683
785
|
catch (e) {
|
|
684
786
|
(0, utils_1.logError)("store_memory", e);
|
|
685
|
-
return { content: [{ type: "text", text: `Failed to store memory in
|
|
787
|
+
return { content: [{ type: "text", text: `Failed to store memory in local Brain: ${e.message}` }] };
|
|
686
788
|
}
|
|
687
789
|
}
|
|
688
790
|
async function handleGetRoutingAdvice(args) {
|
|
@@ -1250,24 +1352,20 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1250
1352
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.8;
|
|
1251
1353
|
if (!query)
|
|
1252
1354
|
throw new Error("query is required");
|
|
1253
|
-
const port = (0, utils_1.getProxyPort)();
|
|
1254
1355
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1255
1356
|
try {
|
|
1256
|
-
const
|
|
1257
|
-
const results =
|
|
1357
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
1358
|
+
const results = brain.search(fullQuery, 3);
|
|
1258
1359
|
const topHit = results[0];
|
|
1259
1360
|
if (topHit && topHit.similarity >= threshold) {
|
|
1260
|
-
const savedTokens = Math.floor((topHit.response?.
|
|
1361
|
+
const savedTokens = Math.floor((topHit.response?.length || 500) / 4);
|
|
1261
1362
|
(0, reportSavings_1.reportSavings)({
|
|
1262
1363
|
source: "cache",
|
|
1263
1364
|
tokens: savedTokens,
|
|
1264
1365
|
toolName: "query_hybrid_consensus",
|
|
1265
1366
|
query: query.substring(0, 100),
|
|
1266
1367
|
});
|
|
1267
|
-
const responseText =
|
|
1268
|
-
? topHit.response
|
|
1269
|
-
: topHit.response?.choices?.[0]?.message?.content ||
|
|
1270
|
-
JSON.stringify(topHit.response, null, 2);
|
|
1368
|
+
const responseText = topHit.response;
|
|
1271
1369
|
return {
|
|
1272
1370
|
content: [
|
|
1273
1371
|
{
|
|
@@ -1293,7 +1391,7 @@ async function handleQueryHybridConsensus(args) {
|
|
|
1293
1391
|
(0, utils_1.logError)("query_hybrid_consensus", e);
|
|
1294
1392
|
return {
|
|
1295
1393
|
content: [
|
|
1296
|
-
{ type: "text", text: `Hybrid Consensus failed: ${e.message}
|
|
1394
|
+
{ type: "text", text: `Hybrid Consensus failed: ${e.message}` },
|
|
1297
1395
|
],
|
|
1298
1396
|
};
|
|
1299
1397
|
}
|
|
@@ -1304,7 +1402,6 @@ async function handleGetTelepathicHints(args) {
|
|
|
1304
1402
|
const limit = typeof args?.limit === "number" ? args.limit : 5;
|
|
1305
1403
|
if (!activeFile)
|
|
1306
1404
|
throw new Error("activeFile is required");
|
|
1307
|
-
const port = (0, utils_1.getProxyPort)();
|
|
1308
1405
|
const basename = path_1.default.basename(activeFile, path_1.default.extname(activeFile));
|
|
1309
1406
|
const dirContext = path_1.default
|
|
1310
1407
|
.dirname(activeFile)
|
|
@@ -1315,8 +1412,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1315
1412
|
const ext = path_1.default.extname(activeFile).replace(".", "");
|
|
1316
1413
|
const telepathicQuery = `${basename} ${dirContext} ${ext} patterns solutions architecture`;
|
|
1317
1414
|
try {
|
|
1318
|
-
const
|
|
1319
|
-
const results =
|
|
1415
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
1416
|
+
const results = brain.search(telepathicQuery, limit);
|
|
1320
1417
|
if (results.length === 0) {
|
|
1321
1418
|
return {
|
|
1322
1419
|
content: [
|
|
@@ -1330,11 +1427,8 @@ async function handleGetTelepathicHints(args) {
|
|
|
1330
1427
|
let hintsText = `Telepathic Hints for \`${activeFile}\`\n*(${results.length} relevant memories surfaced from The Brain)*\n\n`;
|
|
1331
1428
|
results.forEach((r, i) => {
|
|
1332
1429
|
const similarity = (r.similarity * 100).toFixed(1);
|
|
1333
|
-
const prompt =
|
|
1334
|
-
const responseContent =
|
|
1335
|
-
? r.response
|
|
1336
|
-
: r.response?.choices?.[0]?.message?.content ||
|
|
1337
|
-
JSON.stringify(r.response).substring(0, 300);
|
|
1430
|
+
const prompt = r.query || "Unknown";
|
|
1431
|
+
const responseContent = r.response || "";
|
|
1338
1432
|
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
1339
1433
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
1340
1434
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
@@ -1354,7 +1448,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
1354
1448
|
content: [
|
|
1355
1449
|
{
|
|
1356
1450
|
type: "text",
|
|
1357
|
-
text: `Telepathic Hints failed: ${e.message}
|
|
1451
|
+
text: `Telepathic Hints failed: ${e.message}`,
|
|
1358
1452
|
},
|
|
1359
1453
|
],
|
|
1360
1454
|
};
|
|
@@ -1366,17 +1460,8 @@ async function handleSummarizeLongText(args) {
|
|
|
1366
1460
|
const maxLength = args?.maxLength || 500;
|
|
1367
1461
|
if (!text)
|
|
1368
1462
|
throw new Error("text is required");
|
|
1369
|
-
const host = process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
1370
|
-
const model = process.env.OLLAMA_GEN_MODEL || 'llama3';
|
|
1371
1463
|
try {
|
|
1372
|
-
const
|
|
1373
|
-
const resp = await axios_1.default.post(`${host}/api/generate`, {
|
|
1374
|
-
model,
|
|
1375
|
-
prompt,
|
|
1376
|
-
stream: false,
|
|
1377
|
-
options: { temperature: 0.1, num_predict: maxLength * 2 },
|
|
1378
|
-
});
|
|
1379
|
-
const summary = resp.data.response.trim();
|
|
1464
|
+
const summary = (0, ContextCollapser_1.collapseContext)(text, { maxLength });
|
|
1380
1465
|
(0, reportSavings_1.reportSavings)({
|
|
1381
1466
|
source: "contextSqueeze",
|
|
1382
1467
|
charsBefore: text.length,
|
|
@@ -1387,7 +1472,7 @@ async function handleSummarizeLongText(args) {
|
|
|
1387
1472
|
}
|
|
1388
1473
|
catch (e) {
|
|
1389
1474
|
(0, utils_1.logError)("summarize_long_text", e);
|
|
1390
|
-
return { content: [{ type: "text", text: `Summarization failed: ${e.message}
|
|
1475
|
+
return { content: [{ type: "text", text: `Summarization failed: ${e.message}` }] };
|
|
1391
1476
|
}
|
|
1392
1477
|
}
|
|
1393
1478
|
// ── Prune Conversation History ──────────────────────────────────────
|
|
@@ -1536,21 +1621,14 @@ async function handleCompressContext(args) {
|
|
|
1536
1621
|
const recent = messages.slice(-keepRecent);
|
|
1537
1622
|
const old = messages.slice(0, -keepRecent);
|
|
1538
1623
|
let summary = "";
|
|
1539
|
-
let summaryMethod = "
|
|
1624
|
+
let summaryMethod = "local-collapser";
|
|
1540
1625
|
try {
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
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) : '...' })))}`;
|
|
1544
|
-
const resp = await axios_1.default.post(`${host}/api/generate`, {
|
|
1545
|
-
model, prompt: summaryPrompt, stream: false,
|
|
1546
|
-
options: { temperature: 0.1, num_predict: 300 },
|
|
1547
|
-
});
|
|
1548
|
-
summary = resp.data.response.trim();
|
|
1549
|
-
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 });
|
|
1550
1628
|
}
|
|
1551
|
-
catch {
|
|
1629
|
+
catch (err) {
|
|
1552
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);
|
|
1553
|
-
summaryMethod = "truncate";
|
|
1631
|
+
summaryMethod = "truncate-fallback";
|
|
1554
1632
|
}
|
|
1555
1633
|
const oldLength = JSON.stringify(old).length;
|
|
1556
1634
|
const summaryLength = summary.length;
|
|
@@ -1578,16 +1656,13 @@ async function handleSmarterCache(args) {
|
|
|
1578
1656
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.75;
|
|
1579
1657
|
if (!query)
|
|
1580
1658
|
throw new Error("query is required");
|
|
1581
|
-
const port = (0, utils_1.getProxyPort)();
|
|
1582
1659
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|
|
1583
1660
|
try {
|
|
1584
|
-
const
|
|
1585
|
-
const results =
|
|
1661
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
1662
|
+
const results = brain.search(fullQuery, 5);
|
|
1586
1663
|
const topHit = results[0];
|
|
1587
1664
|
if (topHit && topHit.similarity >= threshold) {
|
|
1588
|
-
const responseText =
|
|
1589
|
-
? topHit.response
|
|
1590
|
-
: topHit.response?.choices?.[0]?.message?.content || JSON.stringify(topHit.response, null, 2);
|
|
1665
|
+
const responseText = topHit.response;
|
|
1591
1666
|
const tokensSaved = Math.floor(responseText.length / 4);
|
|
1592
1667
|
try {
|
|
1593
1668
|
const { reportSavings } = await Promise.resolve().then(() => __importStar(require("../utils/reportSavings")));
|
|
@@ -1632,7 +1707,7 @@ async function handleSmarterCache(args) {
|
|
|
1632
1707
|
hit: false,
|
|
1633
1708
|
error: e.message,
|
|
1634
1709
|
source: "cache-unavailable",
|
|
1635
|
-
hint: "
|
|
1710
|
+
hint: "El cache local del brain falló. El LLM funcionará normalmente sin cache.",
|
|
1636
1711
|
}, null, 2),
|
|
1637
1712
|
}],
|
|
1638
1713
|
};
|
|
@@ -2285,10 +2360,9 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2285
2360
|
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
|
|
2286
2361
|
if (!query || !response)
|
|
2287
2362
|
throw new Error("query and response are required");
|
|
2288
|
-
const port = (0, utils_1.getProxyPort)();
|
|
2289
2363
|
try {
|
|
2290
|
-
const
|
|
2291
|
-
const results =
|
|
2364
|
+
const brain = (0, TheBrainV2_1.getBrain)();
|
|
2365
|
+
const results = brain.search(query, 3);
|
|
2292
2366
|
const topHit = results[0];
|
|
2293
2367
|
if (!topHit) {
|
|
2294
2368
|
return {
|
|
@@ -2299,17 +2373,18 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2299
2373
|
}, null, 2) }],
|
|
2300
2374
|
};
|
|
2301
2375
|
}
|
|
2302
|
-
|
|
2376
|
+
let sim = topHit.similarity || 0;
|
|
2377
|
+
if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
|
|
2378
|
+
sim = 1.0;
|
|
2379
|
+
}
|
|
2303
2380
|
if (sim >= threshold) {
|
|
2304
2381
|
return {
|
|
2305
2382
|
content: [{ type: "text", text: JSON.stringify({
|
|
2306
2383
|
verdict: "DUPLICATE_REJECTED",
|
|
2307
2384
|
reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
|
|
2308
2385
|
similarity: sim,
|
|
2309
|
-
existingQuery: typeof topHit.
|
|
2310
|
-
existingResponse: (
|
|
2311
|
-
? topHit.response
|
|
2312
|
-
: 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),
|
|
2313
2388
|
tokensSaved: Math.floor(response.length / 4),
|
|
2314
2389
|
}, null, 2) }],
|
|
2315
2390
|
};
|
|
@@ -2320,7 +2395,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2320
2395
|
verdict: "SIMILAR_WARNING",
|
|
2321
2396
|
reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
|
|
2322
2397
|
similarity: sim,
|
|
2323
|
-
existingQuery: typeof topHit.
|
|
2398
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
2324
2399
|
recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
|
|
2325
2400
|
}, null, 2) }],
|
|
2326
2401
|
};
|
|
@@ -2338,7 +2413,7 @@ async function handleSemanticDedupGuard(args) {
|
|
|
2338
2413
|
return {
|
|
2339
2414
|
content: [{ type: "text", text: JSON.stringify({
|
|
2340
2415
|
verdict: "STORE_ALLOWED",
|
|
2341
|
-
reason: "
|
|
2416
|
+
reason: "El cache local del brain falló. Procediendo con store.",
|
|
2342
2417
|
error: e.message,
|
|
2343
2418
|
}, null, 2) }],
|
|
2344
2419
|
};
|
|
@@ -2660,4 +2735,769 @@ function generateAzurePipelineYaml(threshold) {
|
|
|
2660
2735
|
` AZURE_DEVOPS_TOKEN: \$(AZURE_DEVOPS_TOKEN)`,
|
|
2661
2736
|
].join("\n");
|
|
2662
2737
|
}
|
|
2738
|
+
function buildDepGraphMap(workspaceRoot) {
|
|
2739
|
+
const map = new Map();
|
|
2740
|
+
const IGNORE_DG = new Set(["node_modules", ".git", "dist", "chroma_data", ".lemma", "build", "coverage", ".next"]);
|
|
2741
|
+
function walkDG(dir) {
|
|
2742
|
+
let entries = [];
|
|
2743
|
+
try {
|
|
2744
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
2745
|
+
}
|
|
2746
|
+
catch {
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
for (const e of entries) {
|
|
2750
|
+
if (IGNORE_DG.has(e.name))
|
|
2751
|
+
continue;
|
|
2752
|
+
const abs = path_1.default.join(dir, e.name);
|
|
2753
|
+
if (e.isDirectory()) {
|
|
2754
|
+
walkDG(abs);
|
|
2755
|
+
}
|
|
2756
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(e.name) && !e.name.endsWith(".d.ts")) {
|
|
2757
|
+
const rel = path_1.default.relative(workspaceRoot, abs).replace(/\\/g, "/");
|
|
2758
|
+
map.set(rel, { rel, abs, imports: [], importedBy: [], externalImports: [], exports: [] });
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
walkDG(workspaceRoot);
|
|
2763
|
+
for (const [, node] of map) {
|
|
2764
|
+
try {
|
|
2765
|
+
const src = fs_1.default.readFileSync(node.abs, "utf8");
|
|
2766
|
+
const sf = ts.createSourceFile(node.abs, src, ts.ScriptTarget.Latest, true);
|
|
2767
|
+
ts.forEachChild(sf, (n) => {
|
|
2768
|
+
if (ts.isImportDeclaration(n) && ts.isStringLiteral(n.moduleSpecifier)) {
|
|
2769
|
+
const spec = n.moduleSpecifier.text;
|
|
2770
|
+
if (spec.startsWith(".")) {
|
|
2771
|
+
const dir = path_1.default.dirname(node.abs);
|
|
2772
|
+
let resolved = path_1.default.resolve(dir, spec);
|
|
2773
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"]) {
|
|
2774
|
+
if (fs_1.default.existsSync(resolved + ext)) {
|
|
2775
|
+
resolved += ext;
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
const rel = path_1.default.relative(workspaceRoot, resolved).replace(/\\/g, "/");
|
|
2780
|
+
if (!node.imports.includes(rel))
|
|
2781
|
+
node.imports.push(rel);
|
|
2782
|
+
}
|
|
2783
|
+
else {
|
|
2784
|
+
const pkg = spec.split("/")[0];
|
|
2785
|
+
if (!node.externalImports.includes(pkg))
|
|
2786
|
+
node.externalImports.push(pkg);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
const hasExportMod = (nd) => ts.canHaveModifiers(nd) &&
|
|
2790
|
+
(ts.getModifiers(nd) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
2791
|
+
if (hasExportMod(n)) {
|
|
2792
|
+
if (ts.isFunctionDeclaration(n) && n.name)
|
|
2793
|
+
node.exports.push(n.name.text);
|
|
2794
|
+
else if (ts.isClassDeclaration(n) && n.name)
|
|
2795
|
+
node.exports.push(n.name.text);
|
|
2796
|
+
else if (ts.isVariableStatement(n)) {
|
|
2797
|
+
for (const decl of n.declarationList.declarations) {
|
|
2798
|
+
if (ts.isIdentifier(decl.name))
|
|
2799
|
+
node.exports.push(decl.name.text);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
else if (ts.isInterfaceDeclaration(n))
|
|
2803
|
+
node.exports.push(n.name.text);
|
|
2804
|
+
else if (ts.isTypeAliasDeclaration(n))
|
|
2805
|
+
node.exports.push(n.name.text);
|
|
2806
|
+
else if (ts.isEnumDeclaration(n))
|
|
2807
|
+
node.exports.push(n.name.text);
|
|
2808
|
+
}
|
|
2809
|
+
if (ts.isExportDeclaration(n) && n.exportClause && ts.isNamedExports(n.exportClause)) {
|
|
2810
|
+
for (const el of n.exportClause.elements)
|
|
2811
|
+
node.exports.push(el.name.text);
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
catch { /* skip unparseable */ }
|
|
2816
|
+
}
|
|
2817
|
+
for (const [, node] of map) {
|
|
2818
|
+
for (const imp of node.imports) {
|
|
2819
|
+
const target = map.get(imp);
|
|
2820
|
+
if (target && !target.importedBy.includes(node.rel))
|
|
2821
|
+
target.importedBy.push(node.rel);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return map;
|
|
2825
|
+
}
|
|
2826
|
+
function bfsDepGraph(map, startRel, direction, maxDepth) {
|
|
2827
|
+
const visited = new Map();
|
|
2828
|
+
const queue = [{ rel: startRel, depth: 0 }];
|
|
2829
|
+
while (queue.length > 0) {
|
|
2830
|
+
const item = queue.shift();
|
|
2831
|
+
if (visited.has(item.rel) || item.depth > maxDepth)
|
|
2832
|
+
continue;
|
|
2833
|
+
const node = map.get(item.rel);
|
|
2834
|
+
if (!node)
|
|
2835
|
+
continue;
|
|
2836
|
+
visited.set(item.rel, { node, depth: item.depth });
|
|
2837
|
+
if (item.depth < maxDepth) {
|
|
2838
|
+
const neighbors = direction === "forward" ? node.imports : node.importedBy;
|
|
2839
|
+
for (const n of neighbors)
|
|
2840
|
+
queue.push({ rel: n, depth: item.depth + 1 });
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
return visited;
|
|
2844
|
+
}
|
|
2845
|
+
function detectDepCycles(map, subset) {
|
|
2846
|
+
const cycles = [];
|
|
2847
|
+
const visited = new Set();
|
|
2848
|
+
const stack = new Set();
|
|
2849
|
+
function dfs(rel, currentPath) {
|
|
2850
|
+
if (stack.has(rel)) {
|
|
2851
|
+
const idx = currentPath.indexOf(rel);
|
|
2852
|
+
if (idx >= 0)
|
|
2853
|
+
cycles.push([...currentPath.slice(idx), rel]);
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
if (visited.has(rel))
|
|
2857
|
+
return;
|
|
2858
|
+
visited.add(rel);
|
|
2859
|
+
stack.add(rel);
|
|
2860
|
+
const node = map.get(rel);
|
|
2861
|
+
if (node) {
|
|
2862
|
+
for (const imp of node.imports) {
|
|
2863
|
+
if (!subset || subset.has(imp))
|
|
2864
|
+
dfs(imp, [...currentPath, rel]);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
stack.delete(rel);
|
|
2868
|
+
}
|
|
2869
|
+
const keys = subset ? [...subset] : [...map.keys()];
|
|
2870
|
+
for (const k of keys)
|
|
2871
|
+
dfs(k, []);
|
|
2872
|
+
return cycles;
|
|
2873
|
+
}
|
|
2874
|
+
function formatDGText(targetNode, forwardVisited, backwardVisited, cycles, includeExternal, showReverse) {
|
|
2875
|
+
const lines = [`📦 ${targetNode.rel}`];
|
|
2876
|
+
const directImports = targetNode.imports;
|
|
2877
|
+
const externalImps = includeExternal ? targetNode.externalImports : [];
|
|
2878
|
+
const allImports = [...directImports, ...externalImps];
|
|
2879
|
+
if (allImports.length > 0) {
|
|
2880
|
+
lines.push("├── importa:");
|
|
2881
|
+
allImports.forEach((imp, i) => {
|
|
2882
|
+
const isLast = i === allImports.length - 1;
|
|
2883
|
+
const prefix = isLast ? "│ └──" : "│ ├──";
|
|
2884
|
+
const isExt = externalImps.includes(imp);
|
|
2885
|
+
if (isExt) {
|
|
2886
|
+
lines.push(`${prefix} ${imp} (external)`);
|
|
2887
|
+
}
|
|
2888
|
+
else {
|
|
2889
|
+
const impNode = forwardVisited.get(imp)?.node;
|
|
2890
|
+
const expSuffix = impNode && impNode.exports.length > 0
|
|
2891
|
+
? ` → ${impNode.exports.slice(0, 3).join(", ")}${impNode.exports.length > 3 ? "…" : ""}`
|
|
2892
|
+
: "";
|
|
2893
|
+
lines.push(`${prefix} ${imp}${expSuffix}`);
|
|
2894
|
+
if (impNode && impNode.imports.length > 0) {
|
|
2895
|
+
impNode.imports.forEach((subImp, si) => {
|
|
2896
|
+
const subPrefix = si === impNode.imports.length - 1 ? "│ └──" : "│ ├──";
|
|
2897
|
+
if (subImp !== targetNode.rel)
|
|
2898
|
+
lines.push(`${subPrefix} ${subImp}`);
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
else {
|
|
2905
|
+
lines.push("├── importa: (ninguno)");
|
|
2906
|
+
}
|
|
2907
|
+
if (showReverse) {
|
|
2908
|
+
const revDeps = targetNode.importedBy;
|
|
2909
|
+
if (revDeps.length > 0) {
|
|
2910
|
+
lines.push("├── es importado por:");
|
|
2911
|
+
revDeps.forEach((dep, i) => {
|
|
2912
|
+
const prefix = i === revDeps.length - 1 ? "│ └──" : "│ ├──";
|
|
2913
|
+
lines.push(`${prefix} ${dep}`);
|
|
2914
|
+
const depNode = backwardVisited.get(dep)?.node;
|
|
2915
|
+
if (depNode && depNode.importedBy.length > 0) {
|
|
2916
|
+
depNode.importedBy.forEach((subDep, si) => {
|
|
2917
|
+
const subPrefix = si === depNode.importedBy.length - 1 ? "│ └──" : "│ ├──";
|
|
2918
|
+
if (subDep !== targetNode.rel)
|
|
2919
|
+
lines.push(`${subPrefix} ${subDep}`);
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
else {
|
|
2925
|
+
lines.push("├── es importado por: (nadie — posible dead export)");
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
if (targetNode.exports.length > 0) {
|
|
2929
|
+
lines.push("└── exporta:");
|
|
2930
|
+
targetNode.exports.forEach((exp, i) => {
|
|
2931
|
+
const prefix = i === targetNode.exports.length - 1 ? " └──" : " ├──";
|
|
2932
|
+
lines.push(`${prefix} ${exp}`);
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
else {
|
|
2936
|
+
lines.push("└── exporta: (ningún export detectado)");
|
|
2937
|
+
}
|
|
2938
|
+
if (cycles.length > 0) {
|
|
2939
|
+
lines.push("");
|
|
2940
|
+
lines.push("⚠️ DEPENDENCIAS CIRCULARES DETECTADAS:");
|
|
2941
|
+
cycles.slice(0, 5).forEach((c) => lines.push(` 🔄 ${c.join(" → ")}`));
|
|
2942
|
+
}
|
|
2943
|
+
return lines.join("\n");
|
|
2944
|
+
}
|
|
2945
|
+
function formatDGMermaid(targetNode, forwardVisited, backwardVisited, cycles, includeExternal, showReverse) {
|
|
2946
|
+
const lines = ["flowchart LR"];
|
|
2947
|
+
const addedEdges = new Set();
|
|
2948
|
+
const safeId = (s) => s.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
2949
|
+
const tid = safeId(targetNode.rel);
|
|
2950
|
+
lines.push(` ${tid}["📦 ${path_1.default.basename(targetNode.rel)}"]`);
|
|
2951
|
+
lines.push(` style ${tid} fill:#6366f1,color:#fff,stroke:#4f46e5`);
|
|
2952
|
+
for (const imp of targetNode.imports) {
|
|
2953
|
+
const eid = `${tid}-->${safeId(imp)}`;
|
|
2954
|
+
if (!addedEdges.has(eid)) {
|
|
2955
|
+
addedEdges.add(eid);
|
|
2956
|
+
lines.push(` ${tid} --> ${safeId(imp)}["${path_1.default.basename(imp)}"]`);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
if (includeExternal) {
|
|
2960
|
+
for (const ext of targetNode.externalImports) {
|
|
2961
|
+
const extId = safeId(`ext_${ext}`);
|
|
2962
|
+
lines.push(` ${tid} --> ${extId}["📦 ${ext}"]`);
|
|
2963
|
+
lines.push(` style ${extId} fill:#374151,color:#9ca3af,stroke:#374151`);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
for (const [rel, { node }] of forwardVisited) {
|
|
2967
|
+
if (rel === targetNode.rel)
|
|
2968
|
+
continue;
|
|
2969
|
+
for (const imp of node.imports) {
|
|
2970
|
+
const eid = `${safeId(rel)}-->${safeId(imp)}`;
|
|
2971
|
+
if (!addedEdges.has(eid)) {
|
|
2972
|
+
addedEdges.add(eid);
|
|
2973
|
+
lines.push(` ${safeId(rel)} --> ${safeId(imp)}["${path_1.default.basename(imp)}"]`);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
if (showReverse) {
|
|
2978
|
+
for (const dep of targetNode.importedBy) {
|
|
2979
|
+
const eid = `${safeId(dep)}-->${tid}`;
|
|
2980
|
+
if (!addedEdges.has(eid)) {
|
|
2981
|
+
addedEdges.add(eid);
|
|
2982
|
+
lines.push(` ${safeId(dep)}["${path_1.default.basename(dep)}"] --> ${tid}`);
|
|
2983
|
+
lines.push(` style ${safeId(dep)} fill:#065f46,color:#fff,stroke:#047857`);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
for (const [rel, { node }] of backwardVisited) {
|
|
2987
|
+
if (rel === targetNode.rel)
|
|
2988
|
+
continue;
|
|
2989
|
+
for (const dep of node.importedBy) {
|
|
2990
|
+
const eid = `${safeId(dep)}-->${safeId(rel)}`;
|
|
2991
|
+
if (!addedEdges.has(eid)) {
|
|
2992
|
+
addedEdges.add(eid);
|
|
2993
|
+
lines.push(` ${safeId(dep)}["${path_1.default.basename(dep)}"] --> ${safeId(rel)}["${path_1.default.basename(rel)}"]`);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
if (cycles.length > 0) {
|
|
2999
|
+
lines.push(` %% ⚠️ Cycles: ${cycles.length}`);
|
|
3000
|
+
cycles.slice(0, 3).forEach(c => lines.push(` %% ${c.join(" → ")}`));
|
|
3001
|
+
}
|
|
3002
|
+
return lines.join("\n");
|
|
3003
|
+
}
|
|
3004
|
+
async function handleDepgraph(args) {
|
|
3005
|
+
const filePath = args?.filePath;
|
|
3006
|
+
const showReverse = args?.reverse !== false;
|
|
3007
|
+
const depth = Math.min(Math.max(typeof args?.depth === "number" ? args.depth : 1, 1), 5);
|
|
3008
|
+
const format = args?.format || "text";
|
|
3009
|
+
const includeExternal = !!(args?.external);
|
|
3010
|
+
const detectCircular = !!(args?.circular);
|
|
3011
|
+
const workspaceRoot = process.cwd();
|
|
3012
|
+
if (!filePath)
|
|
3013
|
+
throw new Error("filePath is required");
|
|
3014
|
+
try {
|
|
3015
|
+
const { resolved } = (0, utils_1.safeResolvePath)(workspaceRoot, filePath);
|
|
3016
|
+
const relTarget = path_1.default.relative(workspaceRoot, resolved).replace(/\\/g, "/");
|
|
3017
|
+
const map = buildDepGraphMap(workspaceRoot);
|
|
3018
|
+
const targetNode = map.get(relTarget);
|
|
3019
|
+
if (!targetNode) {
|
|
3020
|
+
return { content: [{ type: "text", text: `File not found in workspace graph: ${relTarget}\nCheck path is correct and has .ts/.tsx/.js/.jsx extension.` }] };
|
|
3021
|
+
}
|
|
3022
|
+
const forwardVisited = bfsDepGraph(map, relTarget, "forward", depth);
|
|
3023
|
+
const backwardVisited = showReverse ? bfsDepGraph(map, relTarget, "backward", depth) : new Map();
|
|
3024
|
+
const allRelInScope = new Set([...forwardVisited.keys(), ...(showReverse ? backwardVisited.keys() : [])]);
|
|
3025
|
+
const cycles = detectCircular ? detectDepCycles(map, allRelInScope) : [];
|
|
3026
|
+
let output;
|
|
3027
|
+
if (format === "json") {
|
|
3028
|
+
const jsonResult = {
|
|
3029
|
+
target: relTarget,
|
|
3030
|
+
imports: targetNode.imports,
|
|
3031
|
+
importedBy: showReverse ? targetNode.importedBy : undefined,
|
|
3032
|
+
externalImports: includeExternal ? targetNode.externalImports : undefined,
|
|
3033
|
+
exports: targetNode.exports,
|
|
3034
|
+
cycles: cycles.length > 0 ? cycles : undefined,
|
|
3035
|
+
subGraph: {
|
|
3036
|
+
forward: Object.fromEntries([...forwardVisited.entries()].map(([k, v]) => [k, { depth: v.depth, imports: v.node.imports }])),
|
|
3037
|
+
backward: showReverse ? Object.fromEntries([...backwardVisited.entries()].map(([k, v]) => [k, { depth: v.depth, importedBy: v.node.importedBy }])) : undefined,
|
|
3038
|
+
},
|
|
3039
|
+
};
|
|
3040
|
+
output = JSON.stringify(jsonResult, null, 2);
|
|
3041
|
+
}
|
|
3042
|
+
else if (format === "mermaid") {
|
|
3043
|
+
output = formatDGMermaid(targetNode, forwardVisited, backwardVisited, cycles, includeExternal, showReverse);
|
|
3044
|
+
}
|
|
3045
|
+
else {
|
|
3046
|
+
output = formatDGText(targetNode, forwardVisited, backwardVisited, cycles, includeExternal, showReverse);
|
|
3047
|
+
}
|
|
3048
|
+
const stats = `\n\n📊 Stats: ${forwardVisited.size} nodos forward, ${backwardVisited.size} nodos reverse, depth=${depth}`;
|
|
3049
|
+
return { content: [{ type: "text", text: output + (format === "json" ? "" : stats) }] };
|
|
3050
|
+
}
|
|
3051
|
+
catch (err) {
|
|
3052
|
+
(0, utils_1.logError)("depgraph", err);
|
|
3053
|
+
return { content: [{ type: "text", text: `depgraph failed: ${err.message}` }] };
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
3057
|
+
// ─── refactor ─────────────────────────────────────────────────────────────────
|
|
3058
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
3059
|
+
function escapeRegexStr(s) {
|
|
3060
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3061
|
+
}
|
|
3062
|
+
function findSymbolRefs(workspaceRoot, symbolName) {
|
|
3063
|
+
const results = [];
|
|
3064
|
+
const IGNORE_RF = new Set(["node_modules", ".git", "dist", "chroma_data", ".lemma", "build", "coverage", ".next"]);
|
|
3065
|
+
function walkRF(dir) {
|
|
3066
|
+
let entries = [];
|
|
3067
|
+
try {
|
|
3068
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
3069
|
+
}
|
|
3070
|
+
catch {
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
for (const e of entries) {
|
|
3074
|
+
if (IGNORE_RF.has(e.name))
|
|
3075
|
+
continue;
|
|
3076
|
+
const abs = path_1.default.join(dir, e.name);
|
|
3077
|
+
if (e.isDirectory()) {
|
|
3078
|
+
walkRF(abs);
|
|
3079
|
+
}
|
|
3080
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(e.name) && !e.name.endsWith(".d.ts")) {
|
|
3081
|
+
const rel = path_1.default.relative(workspaceRoot, abs).replace(/\\/g, "/");
|
|
3082
|
+
try {
|
|
3083
|
+
const content = fs_1.default.readFileSync(abs, "utf8");
|
|
3084
|
+
if (!content.includes(symbolName))
|
|
3085
|
+
continue;
|
|
3086
|
+
const sf = ts.createSourceFile(abs, content, ts.ScriptTarget.Latest, true);
|
|
3087
|
+
const contentLines = content.split("\n");
|
|
3088
|
+
const identifierLines = new Set();
|
|
3089
|
+
function collectIdents(node) {
|
|
3090
|
+
if (ts.isIdentifier(node) && node.text === symbolName) {
|
|
3091
|
+
const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
|
|
3092
|
+
identifierLines.add(line);
|
|
3093
|
+
}
|
|
3094
|
+
ts.forEachChild(node, collectIdents);
|
|
3095
|
+
}
|
|
3096
|
+
collectIdents(sf);
|
|
3097
|
+
const matchingLines = [...identifierLines].map(li => ({ lineIndex: li, lineContent: contentLines[li] ?? "" }));
|
|
3098
|
+
if (matchingLines.length > 0)
|
|
3099
|
+
results.push({ abs, rel, lines: matchingLines });
|
|
3100
|
+
}
|
|
3101
|
+
catch { /* skip */ }
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
walkRF(workspaceRoot);
|
|
3106
|
+
return results;
|
|
3107
|
+
}
|
|
3108
|
+
function applySymbolRenameInContent(content, fromName, toName) {
|
|
3109
|
+
const regex = new RegExp(`(?<![a-zA-Z0-9_$])${escapeRegexStr(fromName)}(?![a-zA-Z0-9_$])`, "g");
|
|
3110
|
+
return content.replace(regex, toName);
|
|
3111
|
+
}
|
|
3112
|
+
function buildUnifiedDiff(filePath, before, after) {
|
|
3113
|
+
if (before === after)
|
|
3114
|
+
return "";
|
|
3115
|
+
const bl = before.split("\n");
|
|
3116
|
+
const al = after.split("\n");
|
|
3117
|
+
const diffLines = [`--- a/${filePath}`, `+++ b/${filePath}`];
|
|
3118
|
+
const changed = new Set();
|
|
3119
|
+
const maxLen = Math.max(bl.length, al.length);
|
|
3120
|
+
for (let i = 0; i < maxLen; i++) {
|
|
3121
|
+
if (bl[i] !== al[i])
|
|
3122
|
+
changed.add(i);
|
|
3123
|
+
}
|
|
3124
|
+
let i = 0;
|
|
3125
|
+
while (i < maxLen) {
|
|
3126
|
+
if (!changed.has(i)) {
|
|
3127
|
+
i++;
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
const hunkStart = Math.max(0, i - 2);
|
|
3131
|
+
let hunkEnd = i + 1;
|
|
3132
|
+
while (hunkEnd < maxLen && changed.has(hunkEnd))
|
|
3133
|
+
hunkEnd++;
|
|
3134
|
+
hunkEnd = Math.min(maxLen, hunkEnd + 2);
|
|
3135
|
+
const bc = Math.min(hunkEnd, bl.length) - hunkStart;
|
|
3136
|
+
const ac = Math.min(hunkEnd, al.length) - hunkStart;
|
|
3137
|
+
diffLines.push(`@@ -${hunkStart + 1},${bc} +${hunkStart + 1},${ac} @@`);
|
|
3138
|
+
for (let j = hunkStart; j < hunkEnd; j++) {
|
|
3139
|
+
if (j >= maxLen)
|
|
3140
|
+
break;
|
|
3141
|
+
if (changed.has(j)) {
|
|
3142
|
+
if (j < bl.length)
|
|
3143
|
+
diffLines.push(`-${bl[j]}`);
|
|
3144
|
+
if (j < al.length)
|
|
3145
|
+
diffLines.push(`+${al[j]}`);
|
|
3146
|
+
}
|
|
3147
|
+
else {
|
|
3148
|
+
if (j < bl.length)
|
|
3149
|
+
diffLines.push(` ${bl[j]}`);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
i = hunkEnd;
|
|
3153
|
+
}
|
|
3154
|
+
return diffLines.join("\n");
|
|
3155
|
+
}
|
|
3156
|
+
async function handleRefactorRename(args, workspaceRoot) {
|
|
3157
|
+
const fromName = args.from;
|
|
3158
|
+
const toName = args.to;
|
|
3159
|
+
const dryRun = !!(args.dryRun);
|
|
3160
|
+
const tscCheck = !!(args.tscCheck);
|
|
3161
|
+
const backup = !!(args.backup);
|
|
3162
|
+
if (!fromName || !toName)
|
|
3163
|
+
throw new Error("'from' and 'to' are required for rename");
|
|
3164
|
+
const refs = findSymbolRefs(workspaceRoot, fromName);
|
|
3165
|
+
if (refs.length === 0) {
|
|
3166
|
+
return { content: [{ type: "text", text: `No references found to symbol "${fromName}" in the workspace.` }] };
|
|
3167
|
+
}
|
|
3168
|
+
let filesModified = 0;
|
|
3169
|
+
let totalRefs = 0;
|
|
3170
|
+
const report = [];
|
|
3171
|
+
const diffs = [];
|
|
3172
|
+
for (const ref of refs) {
|
|
3173
|
+
const before = fs_1.default.readFileSync(ref.abs, "utf8");
|
|
3174
|
+
const after = applySymbolRenameInContent(before, fromName, toName);
|
|
3175
|
+
if (before === after)
|
|
3176
|
+
continue;
|
|
3177
|
+
const count = (before.match(new RegExp(`(?<![a-zA-Z0-9_$])${escapeRegexStr(fromName)}(?![a-zA-Z0-9_$])`, "g")) || []).length;
|
|
3178
|
+
totalRefs += count;
|
|
3179
|
+
filesModified++;
|
|
3180
|
+
if (dryRun) {
|
|
3181
|
+
const d = buildUnifiedDiff(ref.rel, before, after);
|
|
3182
|
+
if (d)
|
|
3183
|
+
diffs.push(d);
|
|
3184
|
+
}
|
|
3185
|
+
else {
|
|
3186
|
+
if (backup)
|
|
3187
|
+
fs_1.default.writeFileSync(ref.abs + ".bak", before, "utf8");
|
|
3188
|
+
fs_1.default.writeFileSync(ref.abs, after, "utf8");
|
|
3189
|
+
report.push(` ✅ ${ref.rel} (${count} refs)`);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
if (dryRun) {
|
|
3193
|
+
return { content: [{ type: "text", text: [`🔍 DRY RUN — Rename "${fromName}" → "${toName}"`, ``, `Archivos que cambiarían: ${filesModified} | Referencias: ${totalRefs}`, ``, `=== DIFF PREVIEW ===`, ...diffs].join("\n") }] };
|
|
3194
|
+
}
|
|
3195
|
+
let tscOutput = "";
|
|
3196
|
+
if (tscCheck && filesModified > 0) {
|
|
3197
|
+
try {
|
|
3198
|
+
const out = (0, child_process_1.execSync)("npx tsc --noEmit 2>&1 | head -30 || true", { cwd: workspaceRoot, encoding: "utf8", timeout: 30000 });
|
|
3199
|
+
const errors = out.split("\n").filter((l) => l.includes("error TS"));
|
|
3200
|
+
tscOutput = errors.length === 0 ? "\n\n✅ tsc --noEmit: 0 errores" : `\n\n⚠️ tsc: ${errors.length} error(es):\n${errors.join("\n")}`;
|
|
3201
|
+
}
|
|
3202
|
+
catch (e) {
|
|
3203
|
+
tscOutput = `\n\n⚠️ tsc check falló: ${e.message}`;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
return { content: [{ type: "text", text: [`✅ Rename: "${fromName}" → "${toName}"`, ``, `📁 ${filesModified} archivos | 🔗 ${totalRefs} refs`, ``, ...report].join("\n") + tscOutput }] };
|
|
3207
|
+
}
|
|
3208
|
+
async function handleRefactorMove(args, workspaceRoot) {
|
|
3209
|
+
const fromRel = args.from;
|
|
3210
|
+
const toRel = args.to;
|
|
3211
|
+
const dryRun = !!(args.dryRun);
|
|
3212
|
+
const tscCheck = !!(args.tscCheck);
|
|
3213
|
+
const backup = !!(args.backup);
|
|
3214
|
+
const { resolved: fromAbs } = (0, utils_1.safeResolvePath)(workspaceRoot, fromRel);
|
|
3215
|
+
const toAbs = path_1.default.resolve(workspaceRoot, toRel);
|
|
3216
|
+
const fromRelNorm = path_1.default.relative(workspaceRoot, fromAbs).replace(/\\/g, "/");
|
|
3217
|
+
const toRelNorm = path_1.default.relative(workspaceRoot, toAbs).replace(/\\/g, "/");
|
|
3218
|
+
if (!fs_1.default.existsSync(fromAbs)) {
|
|
3219
|
+
return { content: [{ type: "text", text: `Source file not found: ${fromRelNorm}` }] };
|
|
3220
|
+
}
|
|
3221
|
+
const IGNORE_MV = new Set(["node_modules", ".git", "dist", "chroma_data", ".lemma", "build", "coverage", ".next"]);
|
|
3222
|
+
const importers = [];
|
|
3223
|
+
function walkMV(dir) {
|
|
3224
|
+
let entries = [];
|
|
3225
|
+
try {
|
|
3226
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
3227
|
+
}
|
|
3228
|
+
catch {
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
for (const e of entries) {
|
|
3232
|
+
if (IGNORE_MV.has(e.name))
|
|
3233
|
+
continue;
|
|
3234
|
+
const abs = path_1.default.join(dir, e.name);
|
|
3235
|
+
if (e.isDirectory()) {
|
|
3236
|
+
walkMV(abs);
|
|
3237
|
+
}
|
|
3238
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(e.name) && !e.name.endsWith(".d.ts")) {
|
|
3239
|
+
const rel = path_1.default.relative(workspaceRoot, abs).replace(/\\/g, "/");
|
|
3240
|
+
if (rel === fromRelNorm)
|
|
3241
|
+
return;
|
|
3242
|
+
try {
|
|
3243
|
+
const content = fs_1.default.readFileSync(abs, "utf8");
|
|
3244
|
+
const sf = ts.createSourceFile(abs, content, ts.ScriptTarget.Latest, true);
|
|
3245
|
+
let found = false;
|
|
3246
|
+
ts.forEachChild(sf, (n) => {
|
|
3247
|
+
if (!found && ts.isImportDeclaration(n) && ts.isStringLiteral(n.moduleSpecifier)) {
|
|
3248
|
+
const spec = n.moduleSpecifier.text;
|
|
3249
|
+
if (spec.startsWith(".")) {
|
|
3250
|
+
const res = path_1.default.resolve(path_1.default.dirname(abs), spec).replace(/\\/g, "/");
|
|
3251
|
+
const fromN = fromAbs.replace(/\\/g, "/");
|
|
3252
|
+
const fromNoExt = fromN.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
3253
|
+
if (res === fromN || res === fromNoExt)
|
|
3254
|
+
found = true;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
});
|
|
3258
|
+
if (found)
|
|
3259
|
+
importers.push({ abs, rel, content });
|
|
3260
|
+
}
|
|
3261
|
+
catch { /* skip */ }
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
walkMV(workspaceRoot);
|
|
3266
|
+
const addDot = (p) => p.startsWith(".") ? p : `./${p}`;
|
|
3267
|
+
const patches = [];
|
|
3268
|
+
for (const imp of importers) {
|
|
3269
|
+
const oldRelImport = path_1.default.relative(path_1.default.dirname(imp.abs), fromAbs).replace(/\\/g, "/");
|
|
3270
|
+
const newRelImport = path_1.default.relative(path_1.default.dirname(imp.abs), toAbs).replace(/\\/g, "/");
|
|
3271
|
+
const oldSpec = addDot(oldRelImport.replace(/\.(ts|tsx|js|jsx)$/, ""));
|
|
3272
|
+
const newSpec = addDot(newRelImport.replace(/\.(ts|tsx|js|jsx)$/, ""));
|
|
3273
|
+
const after = imp.content.replace(new RegExp(`(['"])${escapeRegexStr(oldSpec)}(['"])`, "g"), `$1${newSpec}$2`);
|
|
3274
|
+
if (after !== imp.content)
|
|
3275
|
+
patches.push({ rel: imp.rel, abs: imp.abs, before: imp.content, after });
|
|
3276
|
+
}
|
|
3277
|
+
if (dryRun) {
|
|
3278
|
+
const diffs = patches.map(p => buildUnifiedDiff(p.rel, p.before, p.after)).filter(Boolean);
|
|
3279
|
+
return { content: [{ type: "text", text: [`🔍 DRY RUN — Move "${fromRelNorm}" → "${toRelNorm}"`, ``, `Importadores que se actualizarían: ${patches.length}`, ``, `=== DIFF PREVIEW ===`, ...diffs].join("\n") }] };
|
|
3280
|
+
}
|
|
3281
|
+
fs_1.default.mkdirSync(path_1.default.dirname(toAbs), { recursive: true });
|
|
3282
|
+
if (backup)
|
|
3283
|
+
fs_1.default.copyFileSync(fromAbs, fromAbs + ".bak");
|
|
3284
|
+
fs_1.default.copyFileSync(fromAbs, toAbs);
|
|
3285
|
+
fs_1.default.unlinkSync(fromAbs);
|
|
3286
|
+
for (const p of patches) {
|
|
3287
|
+
if (backup)
|
|
3288
|
+
fs_1.default.writeFileSync(p.abs + ".bak", p.before, "utf8");
|
|
3289
|
+
fs_1.default.writeFileSync(p.abs, p.after, "utf8");
|
|
3290
|
+
}
|
|
3291
|
+
let tscOutput = "";
|
|
3292
|
+
if (tscCheck) {
|
|
3293
|
+
try {
|
|
3294
|
+
const out = (0, child_process_1.execSync)("npx tsc --noEmit 2>&1 | head -30 || true", { cwd: workspaceRoot, encoding: "utf8", timeout: 30000 });
|
|
3295
|
+
const errors = out.split("\n").filter((l) => l.includes("error TS"));
|
|
3296
|
+
tscOutput = errors.length === 0 ? "\n\n✅ tsc --noEmit: 0 errores" : `\n\n⚠️ tsc: ${errors.length} error(es):\n${errors.join("\n")}`;
|
|
3297
|
+
}
|
|
3298
|
+
catch (e) {
|
|
3299
|
+
tscOutput = `\n\n⚠️ tsc check falló: ${e.message}`;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
return { content: [{ type: "text", text: [`✅ Move: "${fromRelNorm}" → "${toRelNorm}"`, ``, `📁 ${patches.length} importadores actualizados:`, ...patches.map(p => ` ✅ ${p.rel}`)].join("\n") + tscOutput }] };
|
|
3303
|
+
}
|
|
3304
|
+
async function handleRefactor(args) {
|
|
3305
|
+
const operation = args?.operation;
|
|
3306
|
+
const workspaceRoot = process.cwd();
|
|
3307
|
+
if (!operation)
|
|
3308
|
+
throw new Error("'operation' is required (rename | move)");
|
|
3309
|
+
try {
|
|
3310
|
+
switch (operation) {
|
|
3311
|
+
case "rename": return await handleRefactorRename(args, workspaceRoot);
|
|
3312
|
+
case "move": return await handleRefactorMove(args, workspaceRoot);
|
|
3313
|
+
default:
|
|
3314
|
+
return { content: [{ type: "text", text: `Unknown operation: "${operation}". Supported: rename, move` }] };
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
catch (err) {
|
|
3318
|
+
(0, utils_1.logError)("refactor", err);
|
|
3319
|
+
return { content: [{ type: "text", text: `refactor failed: ${err.message}` }] };
|
|
3320
|
+
}
|
|
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
|
+
}
|
|
2663
3503
|
//# sourceMappingURL=tools.js.map
|