engramx 2.1.0 → 3.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/CHANGELOG.md +110 -0
- package/README.md +114 -17
- package/dist/{aider-context-J557IHIP.js → aider-context-6IDE3R7U.js} +1 -1
- package/dist/{chunk-PEH54LYC.js → chunk-645NBY6L.js} +42 -5
- package/dist/chunk-73IBCRFI.js +215 -0
- package/dist/{chunk-ZVWRIVWQ.js → chunk-B4UOE64J.js} +29 -11
- package/dist/{chunk-XFE6ZANP.js → chunk-FKY6HIT2.js} +1 -1
- package/dist/chunk-RJC6RNXJ.js +1405 -0
- package/dist/{chunk-4XA6ENNL.js → chunk-VLTWBTQ7.js} +14 -15
- package/dist/chunk-ZUC6OXSL.js +178 -0
- package/dist/cli.js +277 -1259
- package/dist/{core-TSXA5XZH.js → core-77F2BVYV.js} +2 -2
- package/dist/{cursor-mdc-VEOFFDVO.js → cursor-mdc-EEO7PYZ3.js} +1 -1
- package/dist/{exporter-AWXS34AS.js → exporter-ZYJ4WM2F.js} +1 -1
- package/dist/{importer-3Q5M6QBL.js → importer-4UWQDH4W.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/mcp-client-ROOJF76V.js +9 -0
- package/dist/mcp-config-QD4NPVXB.js +12 -0
- package/dist/{migrate-UKCO6BUU.js → migrate-KJ5K5NWO.js} +1 -1
- package/dist/{plugin-loader-STTGYIL5.js → plugin-loader-SQQB6V74.js} +69 -23
- package/dist/resolver-H7GXVP73.js +21 -0
- package/dist/serve.js +2 -2
- package/dist/{server-A6MUVKQK.js → server-2ZQKXJ5M.js} +74 -6
- package/dist/{windsurf-rules-RWPKBHRD.js → windsurf-rules-XF7MYF6J.js} +1 -1
- package/dist/{wizard-AOXWMSXW.js → wizard-UH27IO4I.js} +2 -2
- package/package.json +8 -3
- package/scripts/postinstall.mjs +32 -0
- package/scripts/preuninstall.mjs +200 -0
- package/dist/{tuner-KFNNGKG3.js → tuner-Y2YENAZC.js} +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ESTIMATED_TOKENS_PER_READ_DENY,
|
|
4
|
+
formatStatsSummary,
|
|
5
|
+
summarizeHookLog
|
|
6
|
+
} from "./chunk-FKY6HIT2.js";
|
|
7
|
+
import {
|
|
8
|
+
logHookEvent,
|
|
9
|
+
readHookLog
|
|
10
|
+
} from "./chunk-KL6NSPVA.js";
|
|
2
11
|
import {
|
|
3
12
|
formatInstallDiff,
|
|
4
13
|
installEngramHooks,
|
|
5
14
|
uninstallEngramHooks
|
|
6
15
|
} from "./chunk-SMU4WR3D.js";
|
|
7
|
-
import {
|
|
8
|
-
ESTIMATED_TOKENS_PER_READ_DENY,
|
|
9
|
-
formatStatsSummary,
|
|
10
|
-
summarizeHookLog
|
|
11
|
-
} from "./chunk-XFE6ZANP.js";
|
|
12
16
|
import {
|
|
13
17
|
formatHudStatus,
|
|
14
18
|
getComponentStatus
|
|
15
19
|
} from "./chunk-G4U3QOOW.js";
|
|
16
20
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
readHookLog
|
|
22
|
-
} from "./chunk-KL6NSPVA.js";
|
|
21
|
+
resolveRichPacket,
|
|
22
|
+
warmAllProviders
|
|
23
|
+
} from "./chunk-RJC6RNXJ.js";
|
|
24
|
+
import "./chunk-22INHMKB.js";
|
|
23
25
|
import {
|
|
24
26
|
autogen,
|
|
25
27
|
install,
|
|
26
28
|
status,
|
|
27
29
|
uninstall
|
|
28
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-VLTWBTQ7.js";
|
|
29
31
|
import {
|
|
30
32
|
benchmark,
|
|
31
33
|
computeKeywordIDF,
|
|
@@ -40,26 +42,25 @@ import {
|
|
|
40
42
|
mistakes,
|
|
41
43
|
path,
|
|
42
44
|
query,
|
|
43
|
-
renderFileStructure,
|
|
44
45
|
stats,
|
|
45
46
|
toPosixPath
|
|
46
|
-
} from "./chunk-
|
|
47
|
-
import "./chunk-
|
|
47
|
+
} from "./chunk-B4UOE64J.js";
|
|
48
|
+
import "./chunk-645NBY6L.js";
|
|
48
49
|
|
|
49
50
|
// src/cli.ts
|
|
50
51
|
import { Command } from "commander";
|
|
51
52
|
import chalk2 from "chalk";
|
|
52
53
|
import {
|
|
53
|
-
existsSync as
|
|
54
|
-
readFileSync as
|
|
54
|
+
existsSync as existsSync7,
|
|
55
|
+
readFileSync as readFileSync4,
|
|
55
56
|
writeFileSync as writeFileSync2,
|
|
56
57
|
mkdirSync,
|
|
57
58
|
unlinkSync,
|
|
58
59
|
copyFileSync,
|
|
59
60
|
renameSync as renameSync2
|
|
60
61
|
} from "fs";
|
|
61
|
-
import { dirname as
|
|
62
|
-
import { fileURLToPath
|
|
62
|
+
import { dirname as dirname3, join as join7, resolve as pathResolve2 } from "path";
|
|
63
|
+
import { fileURLToPath } from "url";
|
|
63
64
|
import { homedir } from "os";
|
|
64
65
|
|
|
65
66
|
// src/intercept/safety.ts
|
|
@@ -364,1145 +365,6 @@ function buildSessionContextResponse(eventName, additionalContext) {
|
|
|
364
365
|
};
|
|
365
366
|
}
|
|
366
367
|
|
|
367
|
-
// src/providers/types.ts
|
|
368
|
-
var PROVIDER_PRIORITY = [
|
|
369
|
-
"engram:ast",
|
|
370
|
-
"engram:structure",
|
|
371
|
-
"engram:mistakes",
|
|
372
|
-
"mempalace",
|
|
373
|
-
"context7",
|
|
374
|
-
"engram:git",
|
|
375
|
-
"obsidian",
|
|
376
|
-
"engram:lsp"
|
|
377
|
-
];
|
|
378
|
-
var DEFAULT_CACHE_TTL_SEC = 3600;
|
|
379
|
-
|
|
380
|
-
// src/providers/ast.ts
|
|
381
|
-
import { readFileSync } from "fs";
|
|
382
|
-
|
|
383
|
-
// src/providers/grammar-loader.ts
|
|
384
|
-
import { existsSync as existsSync3 } from "fs";
|
|
385
|
-
import { join as join3, dirname as dirname2 } from "path";
|
|
386
|
-
import { createRequire } from "module";
|
|
387
|
-
import { fileURLToPath } from "url";
|
|
388
|
-
var require2 = createRequire(import.meta.url);
|
|
389
|
-
var parserCache = /* @__PURE__ */ new Map();
|
|
390
|
-
var tsParserInit = false;
|
|
391
|
-
var EXT_TO_LANG = {
|
|
392
|
-
ts: "typescript",
|
|
393
|
-
tsx: "tsx",
|
|
394
|
-
js: "javascript",
|
|
395
|
-
jsx: "javascript",
|
|
396
|
-
mjs: "javascript",
|
|
397
|
-
cjs: "javascript",
|
|
398
|
-
py: "python",
|
|
399
|
-
go: "go",
|
|
400
|
-
rs: "rust",
|
|
401
|
-
rb: "ruby",
|
|
402
|
-
java: "java",
|
|
403
|
-
c: "c",
|
|
404
|
-
cpp: "cpp",
|
|
405
|
-
h: "c",
|
|
406
|
-
hpp: "cpp",
|
|
407
|
-
php: "php"
|
|
408
|
-
};
|
|
409
|
-
var LANG_TO_PKG = {
|
|
410
|
-
typescript: "tree-sitter-typescript",
|
|
411
|
-
tsx: "tree-sitter-typescript",
|
|
412
|
-
javascript: "tree-sitter-javascript",
|
|
413
|
-
python: "tree-sitter-python",
|
|
414
|
-
go: "tree-sitter-go",
|
|
415
|
-
rust: "tree-sitter-rust"
|
|
416
|
-
};
|
|
417
|
-
function getSupportedLang(filePath) {
|
|
418
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
419
|
-
return ext ? EXT_TO_LANG[ext] ?? null : null;
|
|
420
|
-
}
|
|
421
|
-
function findGrammarWasm(lang) {
|
|
422
|
-
const pkg = LANG_TO_PKG[lang];
|
|
423
|
-
if (!pkg) return null;
|
|
424
|
-
const wasmName = lang === "tsx" ? "tree-sitter-tsx.wasm" : `tree-sitter-${lang}.wasm`;
|
|
425
|
-
const candidates = [];
|
|
426
|
-
try {
|
|
427
|
-
const here = dirname2(fileURLToPath(import.meta.url));
|
|
428
|
-
candidates.push(join3(here, "..", "grammars", wasmName));
|
|
429
|
-
candidates.push(join3(here, "grammars", wasmName));
|
|
430
|
-
} catch {
|
|
431
|
-
}
|
|
432
|
-
try {
|
|
433
|
-
const here = dirname2(fileURLToPath(import.meta.url));
|
|
434
|
-
candidates.push(join3(here, "..", "..", "node_modules", pkg, wasmName));
|
|
435
|
-
} catch {
|
|
436
|
-
}
|
|
437
|
-
try {
|
|
438
|
-
const pkgMain = require2.resolve(`${pkg}/package.json`);
|
|
439
|
-
const pkgDir = dirname2(pkgMain);
|
|
440
|
-
candidates.push(join3(pkgDir, wasmName));
|
|
441
|
-
} catch {
|
|
442
|
-
}
|
|
443
|
-
return candidates.find((c) => existsSync3(c)) ?? null;
|
|
444
|
-
}
|
|
445
|
-
async function getParser(lang) {
|
|
446
|
-
const cached = parserCache.get(lang);
|
|
447
|
-
if (cached) return cached;
|
|
448
|
-
try {
|
|
449
|
-
const { Parser, Language } = await import("web-tree-sitter");
|
|
450
|
-
if (!tsParserInit) {
|
|
451
|
-
await Parser.init();
|
|
452
|
-
tsParserInit = true;
|
|
453
|
-
}
|
|
454
|
-
const wasmPath = findGrammarWasm(lang);
|
|
455
|
-
if (!wasmPath) return null;
|
|
456
|
-
const language = await Language.load(wasmPath);
|
|
457
|
-
const parser = new Parser();
|
|
458
|
-
parser.setLanguage(language);
|
|
459
|
-
parserCache.set(lang, parser);
|
|
460
|
-
return parser;
|
|
461
|
-
} catch {
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/providers/ast.ts
|
|
467
|
-
function extractParams(node) {
|
|
468
|
-
const paramsNode = node.childForFieldName("parameters") ?? node.childForFieldName("formal_parameters");
|
|
469
|
-
if (!paramsNode) return "";
|
|
470
|
-
return paramsNode.text.replace(/\n/g, " ").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
471
|
-
}
|
|
472
|
-
function extractSymbols(rootNode) {
|
|
473
|
-
const symbols = [];
|
|
474
|
-
function visit(node) {
|
|
475
|
-
switch (node.type) {
|
|
476
|
-
// ── Functions ───────────────────────────────────────────────
|
|
477
|
-
case "function_declaration":
|
|
478
|
-
case "function_definition": {
|
|
479
|
-
const nameNode = node.childForFieldName("name");
|
|
480
|
-
if (nameNode) {
|
|
481
|
-
symbols.push({
|
|
482
|
-
name: nameNode.text,
|
|
483
|
-
kind: "function",
|
|
484
|
-
line: node.startPosition.row + 1,
|
|
485
|
-
params: extractParams(node)
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
// ── Classes ─────────────────────────────────────────────────
|
|
491
|
-
case "class_declaration":
|
|
492
|
-
case "class_definition": {
|
|
493
|
-
const nameNode = node.childForFieldName("name");
|
|
494
|
-
if (nameNode) {
|
|
495
|
-
symbols.push({
|
|
496
|
-
name: nameNode.text,
|
|
497
|
-
kind: "class",
|
|
498
|
-
line: node.startPosition.row + 1
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
// ── Methods ─────────────────────────────────────────────────
|
|
504
|
-
case "method_definition":
|
|
505
|
-
case "method_declaration": {
|
|
506
|
-
const nameNode = node.childForFieldName("name");
|
|
507
|
-
if (nameNode) {
|
|
508
|
-
symbols.push({
|
|
509
|
-
name: nameNode.text,
|
|
510
|
-
kind: "method",
|
|
511
|
-
line: node.startPosition.row + 1,
|
|
512
|
-
params: extractParams(node)
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
// ── TypeScript interfaces ────────────────────────────────────
|
|
518
|
-
case "interface_declaration": {
|
|
519
|
-
const nameNode = node.childForFieldName("name");
|
|
520
|
-
if (nameNode) {
|
|
521
|
-
symbols.push({
|
|
522
|
-
name: nameNode.text,
|
|
523
|
-
kind: "interface",
|
|
524
|
-
line: node.startPosition.row + 1
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
// ── TypeScript type aliases ──────────────────────────────────
|
|
530
|
-
case "type_alias_declaration": {
|
|
531
|
-
const nameNode = node.childForFieldName("name");
|
|
532
|
-
if (nameNode) {
|
|
533
|
-
symbols.push({
|
|
534
|
-
name: nameNode.text,
|
|
535
|
-
kind: "type",
|
|
536
|
-
line: node.startPosition.row + 1
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
// ── Exported variable declarations (incl. arrow functions) ──
|
|
542
|
-
case "lexical_declaration":
|
|
543
|
-
case "variable_declaration": {
|
|
544
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
545
|
-
const child = node.child(i);
|
|
546
|
-
if (!child || child.type !== "variable_declarator") continue;
|
|
547
|
-
const nameNode = child.childForFieldName("name");
|
|
548
|
-
const valueNode = child.childForFieldName("value");
|
|
549
|
-
if (!nameNode) continue;
|
|
550
|
-
const isArrow = valueNode?.type === "arrow_function" || valueNode?.type === "function";
|
|
551
|
-
symbols.push({
|
|
552
|
-
name: nameNode.text,
|
|
553
|
-
kind: isArrow ? "function" : "variable",
|
|
554
|
-
line: node.startPosition.row + 1,
|
|
555
|
-
params: isArrow && valueNode ? extractParams(valueNode) : void 0
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
default:
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
564
|
-
const child = node.child(i);
|
|
565
|
-
if (child) visit(child);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
visit(rootNode);
|
|
569
|
-
return symbols;
|
|
570
|
-
}
|
|
571
|
-
function formatSymbols(symbols, tokenBudget) {
|
|
572
|
-
const lines = symbols.map((s) => {
|
|
573
|
-
const params = s.params !== void 0 ? `(${s.params})` : "";
|
|
574
|
-
return `${s.kind.toUpperCase()} ${s.name}${params} L${s.line}`;
|
|
575
|
-
});
|
|
576
|
-
const charBudget = tokenBudget * 4;
|
|
577
|
-
let text = lines.join("\n");
|
|
578
|
-
if (text.length > charBudget) {
|
|
579
|
-
text = text.slice(0, charBudget).trimEnd() + "\n... (truncated)";
|
|
580
|
-
}
|
|
581
|
-
return text;
|
|
582
|
-
}
|
|
583
|
-
var astProvider = {
|
|
584
|
-
name: "engram:ast",
|
|
585
|
-
label: "AST STRUCTURE",
|
|
586
|
-
tier: 1,
|
|
587
|
-
tokenBudget: 300,
|
|
588
|
-
timeoutMs: 200,
|
|
589
|
-
async resolve(filePath, _context) {
|
|
590
|
-
const lang = getSupportedLang(filePath);
|
|
591
|
-
if (!lang) return null;
|
|
592
|
-
const parser = await getParser(lang);
|
|
593
|
-
if (!parser) return null;
|
|
594
|
-
try {
|
|
595
|
-
const source = readFileSync(filePath, "utf-8");
|
|
596
|
-
const tree = parser.parse(source);
|
|
597
|
-
if (!tree) return null;
|
|
598
|
-
const symbols = extractSymbols(tree.rootNode);
|
|
599
|
-
if (symbols.length === 0) return null;
|
|
600
|
-
return {
|
|
601
|
-
provider: "engram:ast",
|
|
602
|
-
content: formatSymbols(symbols, this.tokenBudget),
|
|
603
|
-
confidence: 1,
|
|
604
|
-
cached: false
|
|
605
|
-
};
|
|
606
|
-
} catch {
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
async isAvailable() {
|
|
611
|
-
try {
|
|
612
|
-
const { Parser } = await import("web-tree-sitter");
|
|
613
|
-
await Parser.init();
|
|
614
|
-
return true;
|
|
615
|
-
} catch {
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
// src/providers/engram-structure.ts
|
|
622
|
-
var structureProvider = {
|
|
623
|
-
name: "engram:structure",
|
|
624
|
-
label: "STRUCTURE",
|
|
625
|
-
tier: 1,
|
|
626
|
-
tokenBudget: 250,
|
|
627
|
-
timeoutMs: 500,
|
|
628
|
-
async resolve(filePath, context) {
|
|
629
|
-
try {
|
|
630
|
-
const store = await getStore(context.projectRoot);
|
|
631
|
-
try {
|
|
632
|
-
const result = renderFileStructure(store, filePath);
|
|
633
|
-
if (!result || result.nodeCount === 0) return null;
|
|
634
|
-
return {
|
|
635
|
-
provider: "engram:structure",
|
|
636
|
-
content: result.text,
|
|
637
|
-
confidence: result.avgConfidence,
|
|
638
|
-
cached: false
|
|
639
|
-
};
|
|
640
|
-
} finally {
|
|
641
|
-
store.close();
|
|
642
|
-
}
|
|
643
|
-
} catch {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
async isAvailable() {
|
|
648
|
-
return true;
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
// src/providers/engram-mistakes.ts
|
|
653
|
-
var mistakesProvider = {
|
|
654
|
-
name: "engram:mistakes",
|
|
655
|
-
label: "KNOWN ISSUES",
|
|
656
|
-
tier: 1,
|
|
657
|
-
tokenBudget: 50,
|
|
658
|
-
timeoutMs: 200,
|
|
659
|
-
async resolve(filePath, context) {
|
|
660
|
-
try {
|
|
661
|
-
const store = await getStore(context.projectRoot);
|
|
662
|
-
try {
|
|
663
|
-
const allMistakes = store.getNodesByFile(filePath).filter((n) => n.kind === "mistake");
|
|
664
|
-
if (allMistakes.length === 0) return null;
|
|
665
|
-
const lines = allMistakes.slice(0, 5).map((m) => ` ! ${m.label} (flagged ${formatAge(m.lastVerified)})`).join("\n");
|
|
666
|
-
return {
|
|
667
|
-
provider: "engram:mistakes",
|
|
668
|
-
content: lines,
|
|
669
|
-
confidence: 0.95,
|
|
670
|
-
cached: false
|
|
671
|
-
};
|
|
672
|
-
} finally {
|
|
673
|
-
store.close();
|
|
674
|
-
}
|
|
675
|
-
} catch {
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
},
|
|
679
|
-
async isAvailable() {
|
|
680
|
-
return true;
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
function formatAge(timestampMs) {
|
|
684
|
-
if (timestampMs === 0) return "unknown";
|
|
685
|
-
const days = Math.floor((Date.now() - timestampMs) / (1e3 * 60 * 60 * 24));
|
|
686
|
-
if (days === 0) return "today";
|
|
687
|
-
if (days === 1) return "yesterday";
|
|
688
|
-
if (days < 30) return `${days}d ago`;
|
|
689
|
-
return `${Math.floor(days / 30)}mo ago`;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// src/providers/engram-git.ts
|
|
693
|
-
import { execFileSync } from "child_process";
|
|
694
|
-
var gitProvider = {
|
|
695
|
-
name: "engram:git",
|
|
696
|
-
label: "CHANGES",
|
|
697
|
-
tier: 1,
|
|
698
|
-
tokenBudget: 50,
|
|
699
|
-
timeoutMs: 200,
|
|
700
|
-
async resolve(filePath, context) {
|
|
701
|
-
try {
|
|
702
|
-
const cwd = context.projectRoot;
|
|
703
|
-
const lastLog = git(
|
|
704
|
-
["log", "-1", "--format=%ar|%an|%s", "--", filePath],
|
|
705
|
-
cwd
|
|
706
|
-
);
|
|
707
|
-
if (!lastLog) return null;
|
|
708
|
-
const [timeAgo, author, message] = lastLog.split("|", 3);
|
|
709
|
-
const recentCount = git(
|
|
710
|
-
[
|
|
711
|
-
"rev-list",
|
|
712
|
-
"--count",
|
|
713
|
-
"--since=30.days",
|
|
714
|
-
"HEAD",
|
|
715
|
-
"--",
|
|
716
|
-
filePath
|
|
717
|
-
],
|
|
718
|
-
cwd
|
|
719
|
-
);
|
|
720
|
-
const churnNote = context.churnRate > 0.3 ? "high churn" : context.churnRate > 0.1 ? "moderate" : "stable";
|
|
721
|
-
const parts = [
|
|
722
|
-
` Last modified: ${timeAgo} by ${author} (${truncate(message, 50)})`,
|
|
723
|
-
` Churn: ${context.churnRate.toFixed(2)} (${churnNote}) | ${recentCount || "0"} changes in 30d`
|
|
724
|
-
];
|
|
725
|
-
return {
|
|
726
|
-
provider: "engram:git",
|
|
727
|
-
content: parts.join("\n"),
|
|
728
|
-
confidence: 0.9,
|
|
729
|
-
cached: false
|
|
730
|
-
};
|
|
731
|
-
} catch {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
},
|
|
735
|
-
async isAvailable() {
|
|
736
|
-
try {
|
|
737
|
-
execFileSync("git", ["--version"], {
|
|
738
|
-
encoding: "utf-8",
|
|
739
|
-
timeout: 2e3
|
|
740
|
-
});
|
|
741
|
-
return true;
|
|
742
|
-
} catch {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
function git(args, cwd) {
|
|
748
|
-
try {
|
|
749
|
-
return execFileSync("git", args, {
|
|
750
|
-
cwd,
|
|
751
|
-
encoding: "utf-8",
|
|
752
|
-
timeout: 3e3,
|
|
753
|
-
maxBuffer: 1024 * 1024
|
|
754
|
-
}).trim();
|
|
755
|
-
} catch {
|
|
756
|
-
return "";
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function truncate(s, max) {
|
|
760
|
-
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// src/providers/mempalace.ts
|
|
764
|
-
import { execFile } from "child_process";
|
|
765
|
-
var MAX_SEARCH_RESULTS = 3;
|
|
766
|
-
var mempalaceProvider = {
|
|
767
|
-
name: "mempalace",
|
|
768
|
-
label: "DECISIONS",
|
|
769
|
-
tier: 2,
|
|
770
|
-
tokenBudget: 100,
|
|
771
|
-
timeoutMs: 200,
|
|
772
|
-
async resolve(filePath, context) {
|
|
773
|
-
try {
|
|
774
|
-
const store = await getStore(context.projectRoot);
|
|
775
|
-
try {
|
|
776
|
-
const cached = store.getCachedContextForProvider(
|
|
777
|
-
"mempalace",
|
|
778
|
-
filePath
|
|
779
|
-
);
|
|
780
|
-
if (cached) {
|
|
781
|
-
return {
|
|
782
|
-
provider: "mempalace",
|
|
783
|
-
content: cached.content,
|
|
784
|
-
confidence: 0.8,
|
|
785
|
-
cached: true
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
} finally {
|
|
789
|
-
store.close();
|
|
790
|
-
}
|
|
791
|
-
const query2 = buildQuery(filePath, context);
|
|
792
|
-
const raw = await searchMempalace(query2);
|
|
793
|
-
if (!raw) return null;
|
|
794
|
-
const content = formatResults(raw);
|
|
795
|
-
if (!content) return null;
|
|
796
|
-
const store2 = await getStore(context.projectRoot);
|
|
797
|
-
try {
|
|
798
|
-
store2.setCachedContext(
|
|
799
|
-
"mempalace",
|
|
800
|
-
filePath,
|
|
801
|
-
content,
|
|
802
|
-
DEFAULT_CACHE_TTL_SEC,
|
|
803
|
-
query2
|
|
804
|
-
);
|
|
805
|
-
store2.save();
|
|
806
|
-
} finally {
|
|
807
|
-
store2.close();
|
|
808
|
-
}
|
|
809
|
-
return {
|
|
810
|
-
provider: "mempalace",
|
|
811
|
-
content,
|
|
812
|
-
confidence: 0.8,
|
|
813
|
-
cached: false
|
|
814
|
-
};
|
|
815
|
-
} catch {
|
|
816
|
-
return null;
|
|
817
|
-
}
|
|
818
|
-
},
|
|
819
|
-
async warmup(projectRoot) {
|
|
820
|
-
const start = Date.now();
|
|
821
|
-
const entries = [];
|
|
822
|
-
try {
|
|
823
|
-
const store = await getStore(projectRoot);
|
|
824
|
-
let projectName;
|
|
825
|
-
try {
|
|
826
|
-
projectName = store.getStat("project_name") ?? projectRoot.split("/").pop() ?? "";
|
|
827
|
-
} finally {
|
|
828
|
-
store.close();
|
|
829
|
-
}
|
|
830
|
-
if (!projectName) {
|
|
831
|
-
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
832
|
-
}
|
|
833
|
-
const raw = await searchMempalace(
|
|
834
|
-
`${projectName} decisions architecture patterns`
|
|
835
|
-
);
|
|
836
|
-
if (!raw) {
|
|
837
|
-
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
838
|
-
}
|
|
839
|
-
const content = formatResults(raw);
|
|
840
|
-
if (content) {
|
|
841
|
-
entries.push({ filePath: "__project__", content });
|
|
842
|
-
}
|
|
843
|
-
} catch {
|
|
844
|
-
}
|
|
845
|
-
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
846
|
-
},
|
|
847
|
-
async isAvailable() {
|
|
848
|
-
try {
|
|
849
|
-
const result = await execFilePromise("mcp-mempalace", [
|
|
850
|
-
"mempalace-status"
|
|
851
|
-
]);
|
|
852
|
-
return result.includes("palace") || result.includes("drawers");
|
|
853
|
-
} catch {
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
function buildQuery(filePath, context) {
|
|
859
|
-
const fileName = filePath.split("/").pop()?.replace(/\.\w+$/, "") ?? "";
|
|
860
|
-
const importTerms = context.imports.slice(0, 3).join(" ");
|
|
861
|
-
return `${fileName} ${importTerms}`.trim();
|
|
862
|
-
}
|
|
863
|
-
function searchMempalace(query2) {
|
|
864
|
-
return new Promise((resolve7) => {
|
|
865
|
-
const timeout = setTimeout(() => resolve7(null), 3e3);
|
|
866
|
-
execFile(
|
|
867
|
-
"mcp-mempalace",
|
|
868
|
-
["mempalace-search", "--query", query2],
|
|
869
|
-
{ encoding: "utf-8", timeout: 3e3, maxBuffer: 1024 * 1024 },
|
|
870
|
-
(err, stdout) => {
|
|
871
|
-
clearTimeout(timeout);
|
|
872
|
-
if (err || !stdout.trim()) {
|
|
873
|
-
resolve7(null);
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
resolve7(stdout.trim());
|
|
877
|
-
}
|
|
878
|
-
);
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
function formatResults(raw) {
|
|
882
|
-
try {
|
|
883
|
-
const parsed = JSON.parse(raw);
|
|
884
|
-
const results = Array.isArray(parsed) ? parsed : parsed?.results ?? parsed?.drawers ?? [];
|
|
885
|
-
if (results.length === 0) return null;
|
|
886
|
-
const lines = results.slice(0, MAX_SEARCH_RESULTS).map((r) => {
|
|
887
|
-
const content = r.content ?? r.text ?? r.summary ?? "";
|
|
888
|
-
const truncated = content.split(/\s+/).slice(0, 30).join(" ");
|
|
889
|
-
return ` - ${truncated}`;
|
|
890
|
-
}).filter((l) => l.length > 4);
|
|
891
|
-
return lines.length > 0 ? lines.join("\n") : null;
|
|
892
|
-
} catch {
|
|
893
|
-
const lines = raw.split("\n").filter((l) => l.trim()).slice(0, MAX_SEARCH_RESULTS).map((l) => ` - ${l.trim().slice(0, 120)}`);
|
|
894
|
-
return lines.length > 0 ? lines.join("\n") : null;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
function execFilePromise(cmd, args) {
|
|
898
|
-
return new Promise((resolve7, reject) => {
|
|
899
|
-
execFile(
|
|
900
|
-
cmd,
|
|
901
|
-
args,
|
|
902
|
-
{ encoding: "utf-8", timeout: 3e3 },
|
|
903
|
-
(err, stdout) => {
|
|
904
|
-
if (err) reject(err);
|
|
905
|
-
else resolve7(stdout.trim());
|
|
906
|
-
}
|
|
907
|
-
);
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// src/providers/context7.ts
|
|
912
|
-
import { execFile as execFile2 } from "child_process";
|
|
913
|
-
var LIBRARY_CACHE_TTL = 4 * 3600;
|
|
914
|
-
var context7Provider = {
|
|
915
|
-
name: "context7",
|
|
916
|
-
label: "LIBRARY",
|
|
917
|
-
tier: 2,
|
|
918
|
-
tokenBudget: 100,
|
|
919
|
-
timeoutMs: 200,
|
|
920
|
-
async resolve(filePath, context) {
|
|
921
|
-
if (context.imports.length === 0) return null;
|
|
922
|
-
try {
|
|
923
|
-
const store = await getStore(context.projectRoot);
|
|
924
|
-
try {
|
|
925
|
-
const cached = store.getCachedContextForProvider("context7", filePath);
|
|
926
|
-
if (cached) {
|
|
927
|
-
return {
|
|
928
|
-
provider: "context7",
|
|
929
|
-
content: cached.content,
|
|
930
|
-
confidence: 0.85,
|
|
931
|
-
cached: true
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
} finally {
|
|
935
|
-
store.close();
|
|
936
|
-
}
|
|
937
|
-
const primaryImport = context.imports[0];
|
|
938
|
-
const docs = await queryContext7(primaryImport);
|
|
939
|
-
if (!docs) return null;
|
|
940
|
-
const content = formatDocs(primaryImport, docs);
|
|
941
|
-
if (!content) return null;
|
|
942
|
-
const store2 = await getStore(context.projectRoot);
|
|
943
|
-
try {
|
|
944
|
-
store2.setCachedContext(
|
|
945
|
-
"context7",
|
|
946
|
-
filePath,
|
|
947
|
-
content,
|
|
948
|
-
LIBRARY_CACHE_TTL,
|
|
949
|
-
primaryImport
|
|
950
|
-
);
|
|
951
|
-
store2.save();
|
|
952
|
-
} finally {
|
|
953
|
-
store2.close();
|
|
954
|
-
}
|
|
955
|
-
return {
|
|
956
|
-
provider: "context7",
|
|
957
|
-
content,
|
|
958
|
-
confidence: 0.85,
|
|
959
|
-
cached: false
|
|
960
|
-
};
|
|
961
|
-
} catch {
|
|
962
|
-
return null;
|
|
963
|
-
}
|
|
964
|
-
},
|
|
965
|
-
async warmup(projectRoot) {
|
|
966
|
-
const start = Date.now();
|
|
967
|
-
const entries = [];
|
|
968
|
-
try {
|
|
969
|
-
const store = await getStore(projectRoot);
|
|
970
|
-
let importEdges;
|
|
971
|
-
try {
|
|
972
|
-
const allEdges = store.getAllEdges();
|
|
973
|
-
importEdges = allEdges.filter((e) => e.relation === "imports").map((e) => ({ source: e.sourceFile, target: e.target }));
|
|
974
|
-
} finally {
|
|
975
|
-
store.close();
|
|
976
|
-
}
|
|
977
|
-
const packages = [
|
|
978
|
-
...new Set(
|
|
979
|
-
importEdges.map((e) => {
|
|
980
|
-
const parts = e.target.split("::");
|
|
981
|
-
return parts[parts.length - 1];
|
|
982
|
-
}).filter(isExternalPackage)
|
|
983
|
-
)
|
|
984
|
-
].slice(0, 10);
|
|
985
|
-
for (const pkg of packages) {
|
|
986
|
-
const docs = await queryContext7(pkg);
|
|
987
|
-
if (docs) {
|
|
988
|
-
const content = formatDocs(pkg, docs);
|
|
989
|
-
if (content) {
|
|
990
|
-
const files = importEdges.filter((e) => e.target.includes(pkg)).map((e) => e.source);
|
|
991
|
-
for (const file of [...new Set(files)]) {
|
|
992
|
-
entries.push({ filePath: file, content });
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
} catch {
|
|
998
|
-
}
|
|
999
|
-
return { provider: "context7", entries, durationMs: Date.now() - start };
|
|
1000
|
-
},
|
|
1001
|
-
async isAvailable() {
|
|
1002
|
-
try {
|
|
1003
|
-
const result = await execFilePromise2("mcp-context7", ["--list"]);
|
|
1004
|
-
return result.includes("resolve-library-id");
|
|
1005
|
-
} catch {
|
|
1006
|
-
return false;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
function isExternalPackage(name) {
|
|
1011
|
-
if (!name) return false;
|
|
1012
|
-
if (name.startsWith(".") || name.startsWith("/")) return false;
|
|
1013
|
-
if ([
|
|
1014
|
-
"fs",
|
|
1015
|
-
"path",
|
|
1016
|
-
"os",
|
|
1017
|
-
"url",
|
|
1018
|
-
"http",
|
|
1019
|
-
"https",
|
|
1020
|
-
"crypto",
|
|
1021
|
-
"stream",
|
|
1022
|
-
"util",
|
|
1023
|
-
"events",
|
|
1024
|
-
"child_process",
|
|
1025
|
-
"node:fs",
|
|
1026
|
-
"node:path",
|
|
1027
|
-
"node:os",
|
|
1028
|
-
"node:url",
|
|
1029
|
-
"node:http",
|
|
1030
|
-
"node:https",
|
|
1031
|
-
"node:crypto",
|
|
1032
|
-
"node:stream",
|
|
1033
|
-
"node:util",
|
|
1034
|
-
"node:events",
|
|
1035
|
-
"node:child_process"
|
|
1036
|
-
].includes(name))
|
|
1037
|
-
return false;
|
|
1038
|
-
return true;
|
|
1039
|
-
}
|
|
1040
|
-
function queryContext7(packageName) {
|
|
1041
|
-
return new Promise((resolve7) => {
|
|
1042
|
-
const timeout = setTimeout(() => resolve7(null), 5e3);
|
|
1043
|
-
execFile2(
|
|
1044
|
-
"mcp-context7",
|
|
1045
|
-
[
|
|
1046
|
-
"query-docs",
|
|
1047
|
-
"--context7CompatibleLibraryID",
|
|
1048
|
-
packageName,
|
|
1049
|
-
"--topic",
|
|
1050
|
-
"API reference quick start"
|
|
1051
|
-
],
|
|
1052
|
-
{ encoding: "utf-8", timeout: 5e3, maxBuffer: 2 * 1024 * 1024 },
|
|
1053
|
-
(err, stdout) => {
|
|
1054
|
-
clearTimeout(timeout);
|
|
1055
|
-
if (err || !stdout.trim()) {
|
|
1056
|
-
resolve7(null);
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
resolve7(stdout.trim());
|
|
1060
|
-
}
|
|
1061
|
-
);
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
function formatDocs(pkg, raw) {
|
|
1065
|
-
const truncated = raw.slice(0, 400);
|
|
1066
|
-
const lines = truncated.split("\n").filter((l) => l.trim()).slice(0, 5).map((l) => ` ${l.trim()}`);
|
|
1067
|
-
if (lines.length === 0) return null;
|
|
1068
|
-
return ` ${pkg}:
|
|
1069
|
-
${lines.join("\n")}`;
|
|
1070
|
-
}
|
|
1071
|
-
function execFilePromise2(cmd, args) {
|
|
1072
|
-
return new Promise((resolve7, reject) => {
|
|
1073
|
-
execFile2(
|
|
1074
|
-
cmd,
|
|
1075
|
-
args,
|
|
1076
|
-
{ encoding: "utf-8", timeout: 3e3 },
|
|
1077
|
-
(err, stdout) => {
|
|
1078
|
-
if (err) reject(err);
|
|
1079
|
-
else resolve7(stdout.trim());
|
|
1080
|
-
}
|
|
1081
|
-
);
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// src/providers/obsidian.ts
|
|
1086
|
-
var OBSIDIAN_PORT = 27124;
|
|
1087
|
-
var OBSIDIAN_BASE = `http://127.0.0.1:${OBSIDIAN_PORT}`;
|
|
1088
|
-
var obsidianProvider = {
|
|
1089
|
-
name: "obsidian",
|
|
1090
|
-
label: "PROJECT NOTES",
|
|
1091
|
-
tier: 2,
|
|
1092
|
-
tokenBudget: 50,
|
|
1093
|
-
timeoutMs: 200,
|
|
1094
|
-
async resolve(filePath, context) {
|
|
1095
|
-
try {
|
|
1096
|
-
const store = await getStore(context.projectRoot);
|
|
1097
|
-
try {
|
|
1098
|
-
const cached = store.getCachedContextForProvider("obsidian", filePath);
|
|
1099
|
-
if (cached) {
|
|
1100
|
-
return {
|
|
1101
|
-
provider: "obsidian",
|
|
1102
|
-
content: cached.content,
|
|
1103
|
-
confidence: 0.7,
|
|
1104
|
-
cached: true
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
} finally {
|
|
1108
|
-
store.close();
|
|
1109
|
-
}
|
|
1110
|
-
const projectName = context.projectRoot.split("/").pop() ?? "";
|
|
1111
|
-
const fileName = filePath.split("/").pop()?.replace(/\.\w+$/, "") ?? "";
|
|
1112
|
-
const query2 = `${projectName} ${fileName}`;
|
|
1113
|
-
const results = await searchObsidian(query2);
|
|
1114
|
-
if (!results) return null;
|
|
1115
|
-
const content = formatResults2(results);
|
|
1116
|
-
if (!content) return null;
|
|
1117
|
-
const store2 = await getStore(context.projectRoot);
|
|
1118
|
-
try {
|
|
1119
|
-
store2.setCachedContext(
|
|
1120
|
-
"obsidian",
|
|
1121
|
-
filePath,
|
|
1122
|
-
content,
|
|
1123
|
-
DEFAULT_CACHE_TTL_SEC,
|
|
1124
|
-
query2
|
|
1125
|
-
);
|
|
1126
|
-
store2.save();
|
|
1127
|
-
} finally {
|
|
1128
|
-
store2.close();
|
|
1129
|
-
}
|
|
1130
|
-
return {
|
|
1131
|
-
provider: "obsidian",
|
|
1132
|
-
content,
|
|
1133
|
-
confidence: 0.7,
|
|
1134
|
-
cached: false
|
|
1135
|
-
};
|
|
1136
|
-
} catch {
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
async warmup(projectRoot) {
|
|
1141
|
-
const start = Date.now();
|
|
1142
|
-
const entries = [];
|
|
1143
|
-
try {
|
|
1144
|
-
const projectName = projectRoot.split("/").pop() ?? "";
|
|
1145
|
-
if (!projectName) {
|
|
1146
|
-
return { provider: "obsidian", entries, durationMs: Date.now() - start };
|
|
1147
|
-
}
|
|
1148
|
-
const results = await searchObsidian(
|
|
1149
|
-
`${projectName} architecture design decisions`
|
|
1150
|
-
);
|
|
1151
|
-
if (results) {
|
|
1152
|
-
const content = formatResults2(results);
|
|
1153
|
-
if (content) {
|
|
1154
|
-
entries.push({ filePath: "__project__", content });
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
} catch {
|
|
1158
|
-
}
|
|
1159
|
-
return { provider: "obsidian", entries, durationMs: Date.now() - start };
|
|
1160
|
-
},
|
|
1161
|
-
async isAvailable() {
|
|
1162
|
-
try {
|
|
1163
|
-
const response = await fetchWithTimeout(
|
|
1164
|
-
`${OBSIDIAN_BASE}/`,
|
|
1165
|
-
1e3
|
|
1166
|
-
);
|
|
1167
|
-
return response.ok;
|
|
1168
|
-
} catch {
|
|
1169
|
-
return false;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
|
-
async function searchObsidian(query2) {
|
|
1174
|
-
try {
|
|
1175
|
-
const response = await fetchWithTimeout(
|
|
1176
|
-
`${OBSIDIAN_BASE}/search/simple/?query=${encodeURIComponent(query2)}`,
|
|
1177
|
-
2e3
|
|
1178
|
-
);
|
|
1179
|
-
if (!response.ok) return null;
|
|
1180
|
-
const data = await response.json();
|
|
1181
|
-
if (!Array.isArray(data) || data.length === 0) return null;
|
|
1182
|
-
return data.slice(0, 3);
|
|
1183
|
-
} catch {
|
|
1184
|
-
return null;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
function formatResults2(results) {
|
|
1188
|
-
if (results.length === 0) return null;
|
|
1189
|
-
const lines = results.slice(0, 3).map((r) => {
|
|
1190
|
-
const name = r.filename.replace(/\.md$/, "");
|
|
1191
|
-
return ` Related: ${name}`;
|
|
1192
|
-
});
|
|
1193
|
-
return lines.join("\n");
|
|
1194
|
-
}
|
|
1195
|
-
async function fetchWithTimeout(url, timeoutMs) {
|
|
1196
|
-
const controller = new AbortController();
|
|
1197
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1198
|
-
try {
|
|
1199
|
-
return await fetch(url, { signal: controller.signal });
|
|
1200
|
-
} finally {
|
|
1201
|
-
clearTimeout(timer);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// src/providers/lsp-connection.ts
|
|
1206
|
-
import { connect } from "net";
|
|
1207
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1208
|
-
import { tmpdir } from "os";
|
|
1209
|
-
import { join as join4 } from "path";
|
|
1210
|
-
function candidateSockets() {
|
|
1211
|
-
const uid = process.getuid?.() ?? 0;
|
|
1212
|
-
const tmp = tmpdir();
|
|
1213
|
-
return [
|
|
1214
|
-
// TypeScript language server (used by VS Code)
|
|
1215
|
-
join4(tmp, `tsserver-${uid}.sock`),
|
|
1216
|
-
// Generic LSP socket (some editors, e.g. Helix)
|
|
1217
|
-
join4(tmp, "lsp-server.sock"),
|
|
1218
|
-
// TypeScript language server alternate path
|
|
1219
|
-
join4(tmp, "typescript-language-server.sock"),
|
|
1220
|
-
// Pyright (Python)
|
|
1221
|
-
join4(tmp, `pyright-${uid}.sock`),
|
|
1222
|
-
// rust-analyzer
|
|
1223
|
-
join4(tmp, "rust-analyzer.sock")
|
|
1224
|
-
];
|
|
1225
|
-
}
|
|
1226
|
-
var LspConnection = class _LspConnection {
|
|
1227
|
-
socket = null;
|
|
1228
|
-
_requestId = 0;
|
|
1229
|
-
/**
|
|
1230
|
-
* Attempt to connect to any currently-running LSP server socket.
|
|
1231
|
-
* Returns null — not throws — if no socket is found or connection fails.
|
|
1232
|
-
* Timeout per candidate: 500ms.
|
|
1233
|
-
*/
|
|
1234
|
-
static async tryConnect() {
|
|
1235
|
-
const candidates = candidateSockets().filter((p) => existsSync4(p));
|
|
1236
|
-
if (candidates.length === 0) return null;
|
|
1237
|
-
for (const path2 of candidates) {
|
|
1238
|
-
try {
|
|
1239
|
-
const conn = new _LspConnection();
|
|
1240
|
-
await conn._connect(path2);
|
|
1241
|
-
return conn;
|
|
1242
|
-
} catch {
|
|
1243
|
-
continue;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
return null;
|
|
1247
|
-
}
|
|
1248
|
-
/** Internal: open a socket to the given path with a 500ms timeout. */
|
|
1249
|
-
_connect(socketPath) {
|
|
1250
|
-
return new Promise((resolve7, reject) => {
|
|
1251
|
-
const socket = connect(socketPath);
|
|
1252
|
-
const timeout = setTimeout(() => {
|
|
1253
|
-
socket.destroy();
|
|
1254
|
-
reject(new Error("LSP connect timeout"));
|
|
1255
|
-
}, 500);
|
|
1256
|
-
socket.on("connect", () => {
|
|
1257
|
-
clearTimeout(timeout);
|
|
1258
|
-
this.socket = socket;
|
|
1259
|
-
resolve7();
|
|
1260
|
-
});
|
|
1261
|
-
socket.on("error", (err) => {
|
|
1262
|
-
clearTimeout(timeout);
|
|
1263
|
-
reject(err);
|
|
1264
|
-
});
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Request hover info for a position.
|
|
1269
|
-
*
|
|
1270
|
-
* Stub: returns null. A full implementation would send a JSON-RPC
|
|
1271
|
-
* textDocument/hover request and parse the response. Left as a stub
|
|
1272
|
-
* because the response requires a request/response correlation loop
|
|
1273
|
-
* over a streaming socket — non-trivial, and out of scope for v0.5.x.
|
|
1274
|
-
* The provider benefits from the availability check alone.
|
|
1275
|
-
*/
|
|
1276
|
-
async hover(_filePath, _line, _character) {
|
|
1277
|
-
if (!this.socket) return null;
|
|
1278
|
-
return null;
|
|
1279
|
-
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Fetch diagnostics for a file.
|
|
1282
|
-
*
|
|
1283
|
-
* Stub: returns []. A full implementation would use the
|
|
1284
|
-
* textDocument/diagnostic pull request (LSP 3.17+) or subscribe to
|
|
1285
|
-
* publishDiagnostics push notifications. Deferred to a future sprint.
|
|
1286
|
-
*/
|
|
1287
|
-
async getDiagnostics(_filePath) {
|
|
1288
|
-
if (!this.socket) return [];
|
|
1289
|
-
return [];
|
|
1290
|
-
}
|
|
1291
|
-
/** Whether this connection has a live socket. */
|
|
1292
|
-
get connected() {
|
|
1293
|
-
return this.socket !== null && !this.socket.destroyed;
|
|
1294
|
-
}
|
|
1295
|
-
/** Close and destroy the socket. Safe to call multiple times. */
|
|
1296
|
-
close() {
|
|
1297
|
-
this.socket?.destroy();
|
|
1298
|
-
this.socket = null;
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
// src/providers/lsp.ts
|
|
1303
|
-
var cachedConnection = void 0;
|
|
1304
|
-
async function getConnection() {
|
|
1305
|
-
if (cachedConnection instanceof LspConnection) {
|
|
1306
|
-
if (cachedConnection.connected) return cachedConnection;
|
|
1307
|
-
cachedConnection.close();
|
|
1308
|
-
cachedConnection = void 0;
|
|
1309
|
-
}
|
|
1310
|
-
if (cachedConnection === null) return null;
|
|
1311
|
-
cachedConnection = await LspConnection.tryConnect();
|
|
1312
|
-
return cachedConnection;
|
|
1313
|
-
}
|
|
1314
|
-
var lspProvider = {
|
|
1315
|
-
name: "engram:lsp",
|
|
1316
|
-
label: "LSP CONTEXT",
|
|
1317
|
-
tier: 1,
|
|
1318
|
-
tokenBudget: 100,
|
|
1319
|
-
timeoutMs: 100,
|
|
1320
|
-
async resolve(filePath, _context) {
|
|
1321
|
-
try {
|
|
1322
|
-
const conn = await getConnection();
|
|
1323
|
-
if (!conn) return null;
|
|
1324
|
-
const hover = await conn.hover(filePath, 0, 0);
|
|
1325
|
-
if (!hover?.contents) return null;
|
|
1326
|
-
const content = typeof hover.contents === "string" ? hover.contents : JSON.stringify(hover.contents);
|
|
1327
|
-
const charBudget = this.tokenBudget * 4;
|
|
1328
|
-
const truncated = content.length > charBudget ? content.slice(0, charBudget) + "..." : content;
|
|
1329
|
-
return {
|
|
1330
|
-
provider: "engram:lsp",
|
|
1331
|
-
content: truncated,
|
|
1332
|
-
confidence: 0.95,
|
|
1333
|
-
cached: false
|
|
1334
|
-
};
|
|
1335
|
-
} catch {
|
|
1336
|
-
return null;
|
|
1337
|
-
}
|
|
1338
|
-
},
|
|
1339
|
-
async isAvailable() {
|
|
1340
|
-
try {
|
|
1341
|
-
const conn = await getConnection();
|
|
1342
|
-
return conn !== null;
|
|
1343
|
-
} catch {
|
|
1344
|
-
return false;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
// src/providers/resolver.ts
|
|
1350
|
-
var BUILTIN_PROVIDERS = [
|
|
1351
|
-
astProvider,
|
|
1352
|
-
structureProvider,
|
|
1353
|
-
mistakesProvider,
|
|
1354
|
-
gitProvider,
|
|
1355
|
-
mempalaceProvider,
|
|
1356
|
-
context7Provider,
|
|
1357
|
-
obsidianProvider,
|
|
1358
|
-
lspProvider
|
|
1359
|
-
];
|
|
1360
|
-
var BUILTIN_NAMES = new Set(BUILTIN_PROVIDERS.map((p) => p.name));
|
|
1361
|
-
async function getAllProviders() {
|
|
1362
|
-
const { getLoadedPlugins } = await import("./plugin-loader-STTGYIL5.js");
|
|
1363
|
-
const { loaded } = await getLoadedPlugins();
|
|
1364
|
-
const safePlugins = loaded.filter((p) => !BUILTIN_NAMES.has(p.name));
|
|
1365
|
-
return [...BUILTIN_PROVIDERS, ...safePlugins];
|
|
1366
|
-
}
|
|
1367
|
-
var ALL_PROVIDERS = BUILTIN_PROVIDERS;
|
|
1368
|
-
function estimateTokens(text) {
|
|
1369
|
-
return Math.ceil(text.length / 4);
|
|
1370
|
-
}
|
|
1371
|
-
async function resolveRichPacket(filePath, context, enabledProviders) {
|
|
1372
|
-
const start = Date.now();
|
|
1373
|
-
let allProviders;
|
|
1374
|
-
try {
|
|
1375
|
-
allProviders = await getAllProviders();
|
|
1376
|
-
} catch {
|
|
1377
|
-
allProviders = BUILTIN_PROVIDERS;
|
|
1378
|
-
}
|
|
1379
|
-
const providers = allProviders.filter((p) => {
|
|
1380
|
-
if (enabledProviders && !enabledProviders.includes(p.name)) return false;
|
|
1381
|
-
return true;
|
|
1382
|
-
});
|
|
1383
|
-
const available = await filterAvailable(providers);
|
|
1384
|
-
if (available.length === 0) return null;
|
|
1385
|
-
const settled = await Promise.allSettled(
|
|
1386
|
-
available.map((p) => resolveWithTimeout(p, filePath, context))
|
|
1387
|
-
);
|
|
1388
|
-
const results = [];
|
|
1389
|
-
for (const outcome of settled) {
|
|
1390
|
-
if (outcome.status === "fulfilled" && outcome.value) {
|
|
1391
|
-
results.push(outcome.value);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
if (results.length === 0) return null;
|
|
1395
|
-
const hasAst = results.some((r) => r.provider === "engram:ast");
|
|
1396
|
-
const deduped = hasAst ? results.filter((r) => r.provider !== "engram:structure") : results;
|
|
1397
|
-
const sorted = deduped.sort((a, b) => {
|
|
1398
|
-
const aIdx = PROVIDER_PRIORITY.indexOf(a.provider);
|
|
1399
|
-
const bIdx = PROVIDER_PRIORITY.indexOf(b.provider);
|
|
1400
|
-
return (aIdx === -1 ? 99 : aIdx) - (bIdx === -1 ? 99 : bIdx);
|
|
1401
|
-
});
|
|
1402
|
-
const config = readConfig(context.projectRoot);
|
|
1403
|
-
const budget = config.totalTokenBudget;
|
|
1404
|
-
const sections = [];
|
|
1405
|
-
let totalTokens = 0;
|
|
1406
|
-
for (const result of sorted) {
|
|
1407
|
-
const sectionTokens = estimateTokens(result.content);
|
|
1408
|
-
if (totalTokens + sectionTokens > budget) {
|
|
1409
|
-
break;
|
|
1410
|
-
}
|
|
1411
|
-
const provider = allProviders.find((p) => p.name === result.provider);
|
|
1412
|
-
const label = provider?.label ?? result.provider.toUpperCase();
|
|
1413
|
-
const cacheTag = result.cached ? ", cached" : "";
|
|
1414
|
-
sections.push(`${label} (${result.provider}${cacheTag}):
|
|
1415
|
-
${result.content}`);
|
|
1416
|
-
totalTokens += sectionTokens;
|
|
1417
|
-
}
|
|
1418
|
-
if (sections.length === 0) return null;
|
|
1419
|
-
const providerNames = sorted.filter((_, i) => i < sections.length).map((r) => r.provider);
|
|
1420
|
-
const isEnrichment = enabledProviders && !enabledProviders.includes("engram:structure");
|
|
1421
|
-
const header = isEnrichment ? `[engram] Additional context (${providerNames.length} providers, ~${totalTokens} tokens)` : `[engram] Rich context for ${filePath} (${providerNames.length} providers, ~${totalTokens} tokens)`;
|
|
1422
|
-
const text = `${header}
|
|
1423
|
-
|
|
1424
|
-
${sections.join("\n\n")}`;
|
|
1425
|
-
return {
|
|
1426
|
-
text,
|
|
1427
|
-
providerCount: providerNames.length,
|
|
1428
|
-
providers: providerNames,
|
|
1429
|
-
estimatedTokens: totalTokens + estimateTokens(header),
|
|
1430
|
-
durationMs: Date.now() - start
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
async function warmAllProviders(projectRoot, enabledProviders) {
|
|
1434
|
-
const start = Date.now();
|
|
1435
|
-
const warmed = [];
|
|
1436
|
-
const tier2 = ALL_PROVIDERS.filter(
|
|
1437
|
-
(p) => p.tier === 2 && p.warmup && (!enabledProviders || enabledProviders.includes(p.name))
|
|
1438
|
-
);
|
|
1439
|
-
const available = await filterAvailable(tier2);
|
|
1440
|
-
const settled = await Promise.allSettled(
|
|
1441
|
-
available.map(async (p) => {
|
|
1442
|
-
try {
|
|
1443
|
-
const result = await withTimeout2(p.warmup(projectRoot), 5e3);
|
|
1444
|
-
if (result && result.entries.length > 0) {
|
|
1445
|
-
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
1446
|
-
const store = await getStore2(projectRoot);
|
|
1447
|
-
try {
|
|
1448
|
-
store.warmCache(
|
|
1449
|
-
result.provider,
|
|
1450
|
-
[...result.entries],
|
|
1451
|
-
result.provider === "context7" ? 4 * 3600 : 3600
|
|
1452
|
-
);
|
|
1453
|
-
store.save();
|
|
1454
|
-
} finally {
|
|
1455
|
-
store.close();
|
|
1456
|
-
}
|
|
1457
|
-
warmed.push(p.name);
|
|
1458
|
-
}
|
|
1459
|
-
} catch {
|
|
1460
|
-
}
|
|
1461
|
-
})
|
|
1462
|
-
);
|
|
1463
|
-
return { warmed, durationMs: Date.now() - start };
|
|
1464
|
-
}
|
|
1465
|
-
var availabilityCache = /* @__PURE__ */ new Map();
|
|
1466
|
-
async function filterAvailable(providers) {
|
|
1467
|
-
const checks = providers.map(async (p) => {
|
|
1468
|
-
let available = availabilityCache.get(p.name);
|
|
1469
|
-
if (available === void 0) {
|
|
1470
|
-
try {
|
|
1471
|
-
const timeout = p.tier === 1 ? 200 : 500;
|
|
1472
|
-
available = await withTimeout2(p.isAvailable(), timeout);
|
|
1473
|
-
} catch {
|
|
1474
|
-
available = false;
|
|
1475
|
-
}
|
|
1476
|
-
availabilityCache.set(p.name, available);
|
|
1477
|
-
}
|
|
1478
|
-
return { provider: p, available };
|
|
1479
|
-
});
|
|
1480
|
-
const settled = await Promise.all(checks);
|
|
1481
|
-
return settled.filter((c) => c.available).map((c) => c.provider);
|
|
1482
|
-
}
|
|
1483
|
-
async function resolveWithTimeout(provider, filePath, context) {
|
|
1484
|
-
try {
|
|
1485
|
-
return await withTimeout2(
|
|
1486
|
-
provider.resolve(filePath, context),
|
|
1487
|
-
provider.timeoutMs
|
|
1488
|
-
);
|
|
1489
|
-
} catch {
|
|
1490
|
-
return null;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
function withTimeout2(promise, ms) {
|
|
1494
|
-
return new Promise((resolve7, reject) => {
|
|
1495
|
-
const timer = setTimeout(() => reject(new Error("timeout")), ms);
|
|
1496
|
-
promise.then((val) => {
|
|
1497
|
-
clearTimeout(timer);
|
|
1498
|
-
resolve7(val);
|
|
1499
|
-
}).catch((err) => {
|
|
1500
|
-
clearTimeout(timer);
|
|
1501
|
-
reject(err);
|
|
1502
|
-
});
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
368
|
// src/intercept/handlers/read.ts
|
|
1507
369
|
var READ_CONFIDENCE_THRESHOLD = 0.7;
|
|
1508
370
|
async function handleRead(payload) {
|
|
@@ -1678,27 +540,175 @@ async function handleBash(payload) {
|
|
|
1678
540
|
});
|
|
1679
541
|
}
|
|
1680
542
|
|
|
543
|
+
// src/intercept/handlers/mistake-guard.ts
|
|
544
|
+
import { relative as relative3 } from "path";
|
|
545
|
+
function currentGuardMode() {
|
|
546
|
+
const raw = process.env.ENGRAM_MISTAKE_GUARD;
|
|
547
|
+
if (raw === "1") return "permissive";
|
|
548
|
+
if (raw === "2") return "strict";
|
|
549
|
+
return "off";
|
|
550
|
+
}
|
|
551
|
+
function extractTargetResource(kind, toolInput) {
|
|
552
|
+
if (!toolInput) return null;
|
|
553
|
+
if (kind === "edit-write") {
|
|
554
|
+
const fp = toolInput.file_path;
|
|
555
|
+
if (typeof fp !== "string" || fp.length === 0) return null;
|
|
556
|
+
return { kind: "file", filePath: fp };
|
|
557
|
+
}
|
|
558
|
+
if (kind === "bash") {
|
|
559
|
+
const cmd = toolInput.command;
|
|
560
|
+
if (typeof cmd !== "string" || cmd.length === 0) return null;
|
|
561
|
+
return { kind: "command", command: cmd };
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
async function findMatchingMistakesAsync(target, projectRoot) {
|
|
566
|
+
if (!target) return [];
|
|
567
|
+
const now = Date.now();
|
|
568
|
+
try {
|
|
569
|
+
const store = await getStore(projectRoot);
|
|
570
|
+
try {
|
|
571
|
+
const matches = [];
|
|
572
|
+
if (target.kind === "file") {
|
|
573
|
+
let normalized = target.filePath;
|
|
574
|
+
try {
|
|
575
|
+
const rel = relative3(projectRoot, target.filePath);
|
|
576
|
+
if (rel && !rel.startsWith("..")) {
|
|
577
|
+
normalized = rel.split(/[\\/]/).join("/");
|
|
578
|
+
}
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
const candidates = [
|
|
582
|
+
...store.getNodesByFile(normalized),
|
|
583
|
+
...normalized === target.filePath ? [] : store.getNodesByFile(target.filePath)
|
|
584
|
+
];
|
|
585
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
586
|
+
for (const m of candidates) {
|
|
587
|
+
if (seenIds.has(m.id)) continue;
|
|
588
|
+
seenIds.add(m.id);
|
|
589
|
+
if (m.kind !== "mistake") continue;
|
|
590
|
+
if (m.validUntil !== void 0 && m.validUntil <= now) continue;
|
|
591
|
+
matches.push({
|
|
592
|
+
label: m.label,
|
|
593
|
+
sourceFile: m.sourceFile,
|
|
594
|
+
ageMs: now - m.lastVerified
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
const allMistakes = store.getAllNodes().filter((n) => n.kind === "mistake").filter((n) => n.validUntil === void 0 || n.validUntil > now);
|
|
599
|
+
if (allMistakes.length === 0) return [];
|
|
600
|
+
const command = target.command.toLowerCase();
|
|
601
|
+
for (const m of allMistakes) {
|
|
602
|
+
const pattern = m.metadata?.commandPattern;
|
|
603
|
+
const patternStr = typeof pattern === "string" ? pattern.toLowerCase() : "";
|
|
604
|
+
const fileStr = m.sourceFile.toLowerCase();
|
|
605
|
+
if (patternStr && patternStr.length > 2 && command.includes(patternStr)) {
|
|
606
|
+
matches.push({
|
|
607
|
+
label: m.label,
|
|
608
|
+
sourceFile: m.sourceFile,
|
|
609
|
+
ageMs: now - m.lastVerified
|
|
610
|
+
});
|
|
611
|
+
} else if (fileStr && fileStr.length > 3 && command.includes(fileStr)) {
|
|
612
|
+
matches.push({
|
|
613
|
+
label: m.label,
|
|
614
|
+
sourceFile: m.sourceFile,
|
|
615
|
+
ageMs: now - m.lastVerified
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return matches;
|
|
621
|
+
} finally {
|
|
622
|
+
store.close();
|
|
623
|
+
}
|
|
624
|
+
} catch {
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function formatAge(ms) {
|
|
629
|
+
if (ms < 0) return "unknown";
|
|
630
|
+
const days = Math.floor(ms / (1e3 * 60 * 60 * 24));
|
|
631
|
+
if (days === 0) return "today";
|
|
632
|
+
if (days === 1) return "yesterday";
|
|
633
|
+
if (days < 30) return `${days}d ago`;
|
|
634
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
635
|
+
}
|
|
636
|
+
function formatWarning(matches) {
|
|
637
|
+
if (matches.length === 0) return "";
|
|
638
|
+
const lines = matches.slice(0, 5).map((m) => ` \u26A0 ${m.label} (flagged ${formatAge(m.ageMs)}, file: ${m.sourceFile})`);
|
|
639
|
+
const more = matches.length > 5 ? `
|
|
640
|
+
\u2026 and ${matches.length - 5} more` : "";
|
|
641
|
+
return [
|
|
642
|
+
"\u26D4 engramx pre-mortem \u2014 this target has recurred as a mistake before:",
|
|
643
|
+
...lines,
|
|
644
|
+
more
|
|
645
|
+
].filter((s) => s.length > 0).join("\n");
|
|
646
|
+
}
|
|
647
|
+
async function applyMistakeGuard(rawResult, payload, kind) {
|
|
648
|
+
const mode = currentGuardMode();
|
|
649
|
+
if (mode === "off") return rawResult;
|
|
650
|
+
try {
|
|
651
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : "";
|
|
652
|
+
const projectRoot = findProjectRoot(cwd);
|
|
653
|
+
if (!projectRoot) return rawResult;
|
|
654
|
+
const toolInput = payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : void 0;
|
|
655
|
+
const target = extractTargetResource(kind, toolInput);
|
|
656
|
+
const matches = await findMatchingMistakesAsync(target, projectRoot);
|
|
657
|
+
if (matches.length === 0) return rawResult;
|
|
658
|
+
const warning = formatWarning(matches);
|
|
659
|
+
if (mode === "strict") {
|
|
660
|
+
return buildDenyResponse(warning);
|
|
661
|
+
}
|
|
662
|
+
if (rawResult && typeof rawResult === "object") {
|
|
663
|
+
const res = rawResult;
|
|
664
|
+
const hso = res.hookSpecificOutput && typeof res.hookSpecificOutput === "object" ? res.hookSpecificOutput : void 0;
|
|
665
|
+
const existingContext = typeof hso?.additionalContext === "string" ? hso.additionalContext : "";
|
|
666
|
+
const merged = existingContext ? `${warning}
|
|
667
|
+
|
|
668
|
+
${existingContext}` : warning;
|
|
669
|
+
return {
|
|
670
|
+
...res,
|
|
671
|
+
hookSpecificOutput: {
|
|
672
|
+
...hso ?? {},
|
|
673
|
+
hookEventName: "PreToolUse",
|
|
674
|
+
permissionDecision: typeof hso?.permissionDecision === "string" ? hso.permissionDecision : "allow",
|
|
675
|
+
additionalContext: merged
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
hookSpecificOutput: {
|
|
681
|
+
hookEventName: "PreToolUse",
|
|
682
|
+
permissionDecision: "allow",
|
|
683
|
+
additionalContext: warning
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
} catch {
|
|
687
|
+
return rawResult;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
1681
691
|
// src/intercept/handlers/session-start.ts
|
|
1682
|
-
import { existsSync as
|
|
1683
|
-
import { execFile
|
|
692
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
693
|
+
import { execFile } from "child_process";
|
|
1684
694
|
import { promisify } from "util";
|
|
1685
|
-
import { basename, dirname as
|
|
1686
|
-
var execFileAsync = promisify(
|
|
695
|
+
import { basename, dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
696
|
+
var execFileAsync = promisify(execFile);
|
|
1687
697
|
var MAX_GOD_NODES = 10;
|
|
1688
698
|
var MAX_LANDMINES_IN_BRIEF = 3;
|
|
1689
699
|
function readGitBranch(projectRoot) {
|
|
1690
700
|
try {
|
|
1691
701
|
let current = resolve2(projectRoot);
|
|
1692
702
|
for (let depth = 0; depth < 10; depth++) {
|
|
1693
|
-
const headPath =
|
|
1694
|
-
if (
|
|
1695
|
-
const content =
|
|
703
|
+
const headPath = join3(current, ".git", "HEAD");
|
|
704
|
+
if (existsSync3(headPath)) {
|
|
705
|
+
const content = readFileSync(headPath, "utf-8").trim();
|
|
1696
706
|
const refMatch = content.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
1697
707
|
if (refMatch) return refMatch[1];
|
|
1698
708
|
if (/^[0-9a-f]{7,40}$/i.test(content)) return "detached";
|
|
1699
709
|
return null;
|
|
1700
710
|
}
|
|
1701
|
-
const parent =
|
|
711
|
+
const parent = dirname2(current);
|
|
1702
712
|
if (parent === current) return null;
|
|
1703
713
|
current = parent;
|
|
1704
714
|
}
|
|
@@ -2051,8 +1061,8 @@ function handleBashPostTool(payload) {
|
|
|
2051
1061
|
}
|
|
2052
1062
|
|
|
2053
1063
|
// src/watcher.ts
|
|
2054
|
-
import { watch, existsSync as
|
|
2055
|
-
import { resolve as resolve3, relative as
|
|
1064
|
+
import { watch, existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
1065
|
+
import { resolve as resolve3, relative as relative4, extname } from "path";
|
|
2056
1066
|
var WATCHABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2057
1067
|
".ts",
|
|
2058
1068
|
".tsx",
|
|
@@ -2087,9 +1097,9 @@ function shouldIgnore(relPath) {
|
|
|
2087
1097
|
async function syncFile(absPath, projectRoot) {
|
|
2088
1098
|
const ext = extname(absPath).toLowerCase();
|
|
2089
1099
|
if (!WATCHABLE_EXTENSIONS.has(ext)) return { action: "skipped", count: 0 };
|
|
2090
|
-
const relPath = toPosixPath(
|
|
1100
|
+
const relPath = toPosixPath(relative4(projectRoot, absPath));
|
|
2091
1101
|
if (shouldIgnore(relPath)) return { action: "skipped", count: 0 };
|
|
2092
|
-
if (!
|
|
1102
|
+
if (!existsSync4(absPath)) {
|
|
2093
1103
|
const store2 = await getStore(projectRoot);
|
|
2094
1104
|
try {
|
|
2095
1105
|
const prior = store2.countBySourceFile(relPath);
|
|
@@ -2146,7 +1156,7 @@ async function runReindexHook(payload) {
|
|
|
2146
1156
|
function watchProject(projectRoot, options = {}) {
|
|
2147
1157
|
const root = resolve3(projectRoot);
|
|
2148
1158
|
const controller = new AbortController();
|
|
2149
|
-
if (!
|
|
1159
|
+
if (!existsSync4(getDbPath(root))) {
|
|
2150
1160
|
throw new Error(
|
|
2151
1161
|
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
2152
1162
|
);
|
|
@@ -2156,7 +1166,7 @@ function watchProject(projectRoot, options = {}) {
|
|
|
2156
1166
|
const handleEvent = (_eventType, filename) => {
|
|
2157
1167
|
if (typeof filename !== "string") return;
|
|
2158
1168
|
const absPath = resolve3(root, filename);
|
|
2159
|
-
const relPath = toPosixPath(
|
|
1169
|
+
const relPath = toPosixPath(relative4(root, absPath));
|
|
2160
1170
|
if (shouldIgnore(relPath)) return;
|
|
2161
1171
|
const ext = extname(filename).toLowerCase();
|
|
2162
1172
|
if (!WATCHABLE_EXTENSIONS.has(ext)) return;
|
|
@@ -2440,11 +1450,13 @@ async function dispatchPreToolUse(payload) {
|
|
|
2440
1450
|
result = await runHandler(
|
|
2441
1451
|
() => handleEditOrWrite(handlerPayload)
|
|
2442
1452
|
);
|
|
1453
|
+
result = await applyMistakeGuard(result, handlerPayload, "edit-write");
|
|
2443
1454
|
break;
|
|
2444
1455
|
case "Bash":
|
|
2445
1456
|
result = await runHandler(
|
|
2446
1457
|
() => handleBash(handlerPayload)
|
|
2447
1458
|
);
|
|
1459
|
+
result = await applyMistakeGuard(result, handlerPayload, "bash");
|
|
2448
1460
|
break;
|
|
2449
1461
|
default:
|
|
2450
1462
|
return PASSTHROUGH;
|
|
@@ -2482,8 +1494,8 @@ function extractPreToolDecision(result) {
|
|
|
2482
1494
|
|
|
2483
1495
|
// src/dashboard.ts
|
|
2484
1496
|
import chalk from "chalk";
|
|
2485
|
-
import { existsSync as
|
|
2486
|
-
import { join as
|
|
1497
|
+
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
1498
|
+
import { join as join5, resolve as resolve6, basename as basename4 } from "path";
|
|
2487
1499
|
var AMBER = chalk.hex("#d97706");
|
|
2488
1500
|
var DIM = chalk.dim;
|
|
2489
1501
|
var GREEN = chalk.green;
|
|
@@ -2603,8 +1615,8 @@ function startDashboard(projectRoot, options = {}) {
|
|
|
2603
1615
|
const tick = () => {
|
|
2604
1616
|
if (controller.signal.aborted) return;
|
|
2605
1617
|
try {
|
|
2606
|
-
const logPath =
|
|
2607
|
-
if (
|
|
1618
|
+
const logPath = join5(root, ".engram", "hook-log.jsonl");
|
|
1619
|
+
if (existsSync5(logPath)) {
|
|
2608
1620
|
const currentSize = statSync3(logPath).size;
|
|
2609
1621
|
if (currentSize !== lastSize) {
|
|
2610
1622
|
cachedEntries = readHookLog(root);
|
|
@@ -2664,13 +1676,13 @@ async function handleCursorBeforeReadFile(payload) {
|
|
|
2664
1676
|
|
|
2665
1677
|
// src/intercept/memory-md.ts
|
|
2666
1678
|
import {
|
|
2667
|
-
existsSync as
|
|
2668
|
-
readFileSync as
|
|
1679
|
+
existsSync as existsSync6,
|
|
1680
|
+
readFileSync as readFileSync3,
|
|
2669
1681
|
writeFileSync,
|
|
2670
1682
|
renameSync,
|
|
2671
1683
|
statSync as statSync4
|
|
2672
1684
|
} from "fs";
|
|
2673
|
-
import { join as
|
|
1685
|
+
import { join as join6 } from "path";
|
|
2674
1686
|
var ENGRAM_MARKER_START = "<!-- engram:structural-facts:start -->";
|
|
2675
1687
|
var ENGRAM_MARKER_END = "<!-- engram:structural-facts:end -->";
|
|
2676
1688
|
var MAX_MEMORY_FILE_BYTES = 1e6;
|
|
@@ -2741,15 +1753,15 @@ function writeEngramSectionToMemoryMd(projectRoot, engramSection) {
|
|
|
2741
1753
|
if (engramSection.length > MAX_ENGRAM_SECTION_BYTES) {
|
|
2742
1754
|
return false;
|
|
2743
1755
|
}
|
|
2744
|
-
const memoryPath =
|
|
1756
|
+
const memoryPath = join6(projectRoot, "MEMORY.md");
|
|
2745
1757
|
try {
|
|
2746
1758
|
let existing = "";
|
|
2747
|
-
if (
|
|
1759
|
+
if (existsSync6(memoryPath)) {
|
|
2748
1760
|
const st = statSync4(memoryPath);
|
|
2749
1761
|
if (st.size > MAX_MEMORY_FILE_BYTES) {
|
|
2750
1762
|
return false;
|
|
2751
1763
|
}
|
|
2752
|
-
existing =
|
|
1764
|
+
existing = readFileSync3(memoryPath, "utf-8");
|
|
2753
1765
|
}
|
|
2754
1766
|
const updated = upsertEngramSection(existing, engramSection);
|
|
2755
1767
|
const tmpPath = memoryPath + ".engram-tmp";
|
|
@@ -2763,9 +1775,9 @@ function writeEngramSectionToMemoryMd(projectRoot, engramSection) {
|
|
|
2763
1775
|
|
|
2764
1776
|
// src/cli.ts
|
|
2765
1777
|
import { basename as basename5 } from "path";
|
|
2766
|
-
import { createRequire
|
|
2767
|
-
var
|
|
2768
|
-
var { version: PKG_VERSION } =
|
|
1778
|
+
import { createRequire } from "module";
|
|
1779
|
+
var require2 = createRequire(import.meta.url);
|
|
1780
|
+
var { version: PKG_VERSION } = require2("../package.json");
|
|
2769
1781
|
var program = new Command();
|
|
2770
1782
|
program.name("engram").description(
|
|
2771
1783
|
"Context as infra for AI coding tools \u2014 hook-based Read/Edit interception + structural graph summaries"
|
|
@@ -2812,9 +1824,9 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2812
1824
|
console.log(chalk2.green("\n\u2705 Ready. Your AI now has persistent memory."));
|
|
2813
1825
|
console.log(chalk2.dim(" Graph stored in .engram/graph.db"));
|
|
2814
1826
|
const resolvedProject = pathResolve2(projectPath);
|
|
2815
|
-
const localSettings =
|
|
2816
|
-
const projectSettings =
|
|
2817
|
-
const hasHooks =
|
|
1827
|
+
const localSettings = join7(resolvedProject, ".claude", "settings.local.json");
|
|
1828
|
+
const projectSettings = join7(resolvedProject, ".claude", "settings.json");
|
|
1829
|
+
const hasHooks = existsSync7(localSettings) && readFileSync4(localSettings, "utf-8").includes("engram intercept") || existsSync7(projectSettings) && readFileSync4(projectSettings, "utf-8").includes("engram intercept");
|
|
2818
1830
|
if (!hasHooks) {
|
|
2819
1831
|
console.log(
|
|
2820
1832
|
chalk2.yellow("\n\u{1F4A1} Next step: ") + chalk2.white("engram install-hook") + chalk2.dim(
|
|
@@ -2828,15 +1840,15 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2828
1840
|
);
|
|
2829
1841
|
}
|
|
2830
1842
|
if (opts.withHook) {
|
|
2831
|
-
const localSettingsPath =
|
|
1843
|
+
const localSettingsPath = join7(
|
|
2832
1844
|
pathResolve2(projectPath),
|
|
2833
1845
|
".claude",
|
|
2834
1846
|
"settings.local.json"
|
|
2835
1847
|
);
|
|
2836
1848
|
let settings = {};
|
|
2837
|
-
if (
|
|
1849
|
+
if (existsSync7(localSettingsPath)) {
|
|
2838
1850
|
try {
|
|
2839
|
-
const raw =
|
|
1851
|
+
const raw = readFileSync4(localSettingsPath, "utf-8");
|
|
2840
1852
|
settings = raw.trim() ? JSON.parse(raw) : {};
|
|
2841
1853
|
} catch {
|
|
2842
1854
|
console.log(
|
|
@@ -2850,7 +1862,7 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2850
1862
|
const hookResult = installEngramHooks(settings);
|
|
2851
1863
|
if (hookResult.added.length > 0 || hookResult.statusLineAdded) {
|
|
2852
1864
|
try {
|
|
2853
|
-
mkdirSync(
|
|
1865
|
+
mkdirSync(dirname3(localSettingsPath), { recursive: true });
|
|
2854
1866
|
writeFileSync2(
|
|
2855
1867
|
localSettingsPath,
|
|
2856
1868
|
JSON.stringify(hookResult.updated, null, 2) + "\n"
|
|
@@ -2878,7 +1890,7 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2878
1890
|
}
|
|
2879
1891
|
}
|
|
2880
1892
|
if (opts.fromCcs) {
|
|
2881
|
-
const { importCcs } = await import("./importer-
|
|
1893
|
+
const { importCcs } = await import("./importer-4UWQDH4W.js");
|
|
2882
1894
|
const resolvedProjectPath = pathResolve2(projectPath);
|
|
2883
1895
|
const ccsResult = await importCcs(resolvedProjectPath);
|
|
2884
1896
|
if (ccsResult.nodesCreated > 0) {
|
|
@@ -2926,7 +1938,7 @@ program.command("watch").description("Watch project for file changes and re-inde
|
|
|
2926
1938
|
program.command("reindex").description("Re-index a single file into the knowledge graph").argument("<file>", "File path (absolute or relative to --project)").option("-p, --project <path>", "Project directory", ".").option("--verbose", "Print stack traces on error", false).action(
|
|
2927
1939
|
async (file, opts) => {
|
|
2928
1940
|
const root = pathResolve2(opts.project);
|
|
2929
|
-
if (!
|
|
1941
|
+
if (!existsSync7(join7(root, ".engram", "graph.db"))) {
|
|
2930
1942
|
console.error(
|
|
2931
1943
|
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
2932
1944
|
);
|
|
@@ -2985,8 +1997,8 @@ program.command("reindex-hook").description(
|
|
|
2985
1997
|
});
|
|
2986
1998
|
program.command("dashboard").alias("hud").description("Live terminal dashboard showing hook activity and token savings").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2987
1999
|
const resolvedPath = pathResolve2(projectPath);
|
|
2988
|
-
const dbPath =
|
|
2989
|
-
if (!
|
|
2000
|
+
const dbPath = join7(resolvedPath, ".engram", "graph.db");
|
|
2001
|
+
if (!existsSync7(dbPath)) {
|
|
2990
2002
|
console.error(
|
|
2991
2003
|
chalk2.red("No engram graph found at ") + chalk2.white(resolvedPath)
|
|
2992
2004
|
);
|
|
@@ -3006,11 +2018,11 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
3006
2018
|
let resolvedPath = pathResolve2(projectPath);
|
|
3007
2019
|
let found = false;
|
|
3008
2020
|
for (let depth = 0; depth < 20; depth++) {
|
|
3009
|
-
if (
|
|
2021
|
+
if (existsSync7(join7(resolvedPath, ".engram", "graph.db"))) {
|
|
3010
2022
|
found = true;
|
|
3011
2023
|
break;
|
|
3012
2024
|
}
|
|
3013
|
-
const parent =
|
|
2025
|
+
const parent = dirname3(resolvedPath);
|
|
3014
2026
|
if (parent === resolvedPath) break;
|
|
3015
2027
|
resolvedPath = parent;
|
|
3016
2028
|
}
|
|
@@ -3018,8 +2030,8 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
3018
2030
|
console.log('{"label":""}');
|
|
3019
2031
|
return;
|
|
3020
2032
|
}
|
|
3021
|
-
const logPath =
|
|
3022
|
-
if (!
|
|
2033
|
+
const logPath = join7(resolvedPath, ".engram", "hook-log.jsonl");
|
|
2034
|
+
if (!existsSync7(logPath)) {
|
|
3023
2035
|
console.log('{"label":"\u26A1engram \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 ready"}');
|
|
3024
2036
|
return;
|
|
3025
2037
|
}
|
|
@@ -3158,22 +2170,28 @@ var hooks = program.command("hooks").description("Manage git hooks");
|
|
|
3158
2170
|
hooks.command("install").description("Install post-commit and post-checkout hooks").argument("[path]", "Project directory", ".").action((p) => console.log(install(p)));
|
|
3159
2171
|
hooks.command("uninstall").description("Remove engram git hooks").argument("[path]", "Project directory", ".").action((p) => console.log(uninstall(p)));
|
|
3160
2172
|
hooks.command("status").description("Check if hooks are installed").argument("[path]", "Project directory", ".").action((p) => console.log(status(p)));
|
|
3161
|
-
program.command("gen").description(
|
|
2173
|
+
program.command("gen").description(
|
|
2174
|
+
"Generate CLAUDE.md + AGENTS.md (default) or a single file via --target"
|
|
2175
|
+
).option("-p, --project <path>", "Project directory", ".").option(
|
|
2176
|
+
"-t, --target <type>",
|
|
2177
|
+
"Single-file target: claude, cursor, agents. Default: emit both CLAUDE.md and AGENTS.md."
|
|
2178
|
+
).option(
|
|
3162
2179
|
"--task <name>",
|
|
3163
2180
|
"Task-aware view: general (default), bug-fix, feature, refactor"
|
|
3164
2181
|
).action(
|
|
3165
2182
|
async (opts) => {
|
|
3166
2183
|
const target = opts.target;
|
|
3167
2184
|
const result = await autogen(opts.project, target, opts.task);
|
|
2185
|
+
const fileList = result.files.map((f) => chalk2.bold(f)).join(", ");
|
|
3168
2186
|
console.log(
|
|
3169
2187
|
chalk2.green(
|
|
3170
|
-
`\u2705 Updated ${
|
|
2188
|
+
`\u2705 Updated ${fileList} (${result.nodesIncluded} nodes, view: ${result.view})`
|
|
3171
2189
|
)
|
|
3172
2190
|
);
|
|
3173
2191
|
}
|
|
3174
2192
|
);
|
|
3175
2193
|
program.command("gen-mdc").description("Generate .cursor/rules/engram-context.mdc from knowledge graph").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3176
|
-
const { generateCursorMdc } = await import("./cursor-mdc-
|
|
2194
|
+
const { generateCursorMdc } = await import("./cursor-mdc-EEO7PYZ3.js");
|
|
3177
2195
|
const result = await generateCursorMdc(opts.project);
|
|
3178
2196
|
console.log(
|
|
3179
2197
|
chalk2.green(
|
|
@@ -3198,7 +2216,7 @@ program.command("gen-mdc").description("Generate .cursor/rules/engram-context.md
|
|
|
3198
2216
|
}
|
|
3199
2217
|
});
|
|
3200
2218
|
program.command("gen-ccs").description("Export knowledge graph as .context/index.md (CCS format)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3201
|
-
const { exportCcs } = await import("./exporter-
|
|
2219
|
+
const { exportCcs } = await import("./exporter-ZYJ4WM2F.js");
|
|
3202
2220
|
const result = await exportCcs(pathResolve2(opts.project));
|
|
3203
2221
|
console.log(
|
|
3204
2222
|
chalk2.green(
|
|
@@ -3207,7 +2225,7 @@ program.command("gen-ccs").description("Export knowledge graph as .context/index
|
|
|
3207
2225
|
);
|
|
3208
2226
|
});
|
|
3209
2227
|
program.command("gen-aider").description("Generate .aider-context.md from knowledge graph").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3210
|
-
const { generateAiderContext } = await import("./aider-context-
|
|
2228
|
+
const { generateAiderContext } = await import("./aider-context-6IDE3R7U.js");
|
|
3211
2229
|
const result = await generateAiderContext(pathResolve2(opts.project));
|
|
3212
2230
|
console.log(
|
|
3213
2231
|
chalk2.green(
|
|
@@ -3232,7 +2250,7 @@ program.command("gen-aider").description("Generate .aider-context.md from knowle
|
|
|
3232
2250
|
}
|
|
3233
2251
|
});
|
|
3234
2252
|
program.command("gen-windsurfrules").description("Generate .windsurfrules from knowledge graph (Windsurf IDE)").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3235
|
-
const { generateWindsurfRules } = await import("./windsurf-rules-
|
|
2253
|
+
const { generateWindsurfRules } = await import("./windsurf-rules-XF7MYF6J.js");
|
|
3236
2254
|
const result = await generateWindsurfRules(pathResolve2(opts.project));
|
|
3237
2255
|
console.log(
|
|
3238
2256
|
chalk2.green(
|
|
@@ -3260,11 +2278,11 @@ function resolveSettingsPath(scope, projectPath) {
|
|
|
3260
2278
|
const absProject = pathResolve2(projectPath);
|
|
3261
2279
|
switch (scope) {
|
|
3262
2280
|
case "local":
|
|
3263
|
-
return
|
|
2281
|
+
return join7(absProject, ".claude", "settings.local.json");
|
|
3264
2282
|
case "project":
|
|
3265
|
-
return
|
|
2283
|
+
return join7(absProject, ".claude", "settings.json");
|
|
3266
2284
|
case "user":
|
|
3267
|
-
return
|
|
2285
|
+
return join7(homedir(), ".claude", "settings.json");
|
|
3268
2286
|
default:
|
|
3269
2287
|
return null;
|
|
3270
2288
|
}
|
|
@@ -3362,9 +2380,9 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3362
2380
|
process.exit(1);
|
|
3363
2381
|
}
|
|
3364
2382
|
let existing = {};
|
|
3365
|
-
if (
|
|
2383
|
+
if (existsSync7(settingsPath)) {
|
|
3366
2384
|
try {
|
|
3367
|
-
const raw =
|
|
2385
|
+
const raw = readFileSync4(settingsPath, "utf-8");
|
|
3368
2386
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
3369
2387
|
} catch (err) {
|
|
3370
2388
|
console.error(
|
|
@@ -3416,8 +2434,8 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3416
2434
|
return;
|
|
3417
2435
|
}
|
|
3418
2436
|
try {
|
|
3419
|
-
mkdirSync(
|
|
3420
|
-
if (
|
|
2437
|
+
mkdirSync(dirname3(settingsPath), { recursive: true });
|
|
2438
|
+
if (existsSync7(settingsPath)) {
|
|
3421
2439
|
const backupPath = `${settingsPath}.engram-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
3422
2440
|
copyFileSync(settingsPath, backupPath);
|
|
3423
2441
|
console.log(chalk2.dim(` Backup: ${backupPath}`));
|
|
@@ -3469,13 +2487,13 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3469
2487
|
);
|
|
3470
2488
|
}
|
|
3471
2489
|
);
|
|
3472
|
-
program.command("uninstall-hook").description("Remove engram hook entries from Claude Code settings").option("--scope <scope>", "local | project | user", "local").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
2490
|
+
program.command("uninstall-hook").alias("repair-hooks").description("Remove engram hook entries from Claude Code settings (also: 'repair-hooks' \u2014 same thing, named for users who ended up with orphaned hooks after npm uninstall)").option("--scope <scope>", "local | project | user (default user on fresh runs)", "local").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3473
2491
|
const settingsPath = resolveSettingsPath(opts.scope, opts.project);
|
|
3474
2492
|
if (!settingsPath) {
|
|
3475
2493
|
console.error(chalk2.red(`Unknown scope: ${opts.scope}`));
|
|
3476
2494
|
process.exit(1);
|
|
3477
2495
|
}
|
|
3478
|
-
if (!
|
|
2496
|
+
if (!existsSync7(settingsPath)) {
|
|
3479
2497
|
console.log(
|
|
3480
2498
|
chalk2.yellow(`No settings file at ${settingsPath} \u2014 nothing to remove.`)
|
|
3481
2499
|
);
|
|
@@ -3483,7 +2501,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
3483
2501
|
}
|
|
3484
2502
|
let existing;
|
|
3485
2503
|
try {
|
|
3486
|
-
const raw =
|
|
2504
|
+
const raw = readFileSync4(settingsPath, "utf-8");
|
|
3487
2505
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
3488
2506
|
} catch (err) {
|
|
3489
2507
|
console.error(
|
|
@@ -3598,7 +2616,7 @@ program.command("hook-disable").description("Disable engram hooks via kill switc
|
|
|
3598
2616
|
console.error(chalk2.dim("Run 'engram init' first."));
|
|
3599
2617
|
process.exit(1);
|
|
3600
2618
|
}
|
|
3601
|
-
const flagPath =
|
|
2619
|
+
const flagPath = join7(projectRoot, ".engram", "hook-disabled");
|
|
3602
2620
|
try {
|
|
3603
2621
|
writeFileSync2(flagPath, (/* @__PURE__ */ new Date()).toISOString());
|
|
3604
2622
|
console.log(
|
|
@@ -3622,8 +2640,8 @@ program.command("hook-enable").description("Re-enable engram hooks (remove kill
|
|
|
3622
2640
|
console.error(chalk2.red(`Not an engram project: ${absProject}`));
|
|
3623
2641
|
process.exit(1);
|
|
3624
2642
|
}
|
|
3625
|
-
const flagPath =
|
|
3626
|
-
if (!
|
|
2643
|
+
const flagPath = join7(projectRoot, ".engram", "hook-disabled");
|
|
2644
|
+
if (!existsSync7(flagPath)) {
|
|
3627
2645
|
console.log(
|
|
3628
2646
|
chalk2.yellow(`engram hooks already enabled for ${projectRoot}`)
|
|
3629
2647
|
);
|
|
@@ -3665,9 +2683,9 @@ program.command("memory-sync").description(
|
|
|
3665
2683
|
}
|
|
3666
2684
|
let branch = null;
|
|
3667
2685
|
try {
|
|
3668
|
-
const headPath =
|
|
3669
|
-
if (
|
|
3670
|
-
const content =
|
|
2686
|
+
const headPath = join7(projectRoot, ".git", "HEAD");
|
|
2687
|
+
if (existsSync7(headPath)) {
|
|
2688
|
+
const content = readFileSync4(headPath, "utf-8").trim();
|
|
3671
2689
|
const m = content.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
3672
2690
|
if (m) branch = m[1];
|
|
3673
2691
|
}
|
|
@@ -3693,7 +2711,7 @@ program.command("memory-sync").description(
|
|
|
3693
2711
|
\u{1F4DD} engram memory-sync`)
|
|
3694
2712
|
);
|
|
3695
2713
|
console.log(
|
|
3696
|
-
chalk2.dim(` Target: ${
|
|
2714
|
+
chalk2.dim(` Target: ${join7(projectRoot, "MEMORY.md")}`)
|
|
3697
2715
|
);
|
|
3698
2716
|
if (opts.dryRun) {
|
|
3699
2717
|
console.log(chalk2.cyan("\n Section to write (dry-run):\n"));
|
|
@@ -3728,7 +2746,7 @@ program.command("memory-sync").description(
|
|
|
3728
2746
|
}
|
|
3729
2747
|
);
|
|
3730
2748
|
program.command("stress-test").description("Run stress tests: memory, concurrency, large-graph, hook-log replay").option("--reads <n>", "Rapid-reads test: call resolveRichPacket N times", parseInt).option("--providers", "Concurrency test: 50 parallel resolveRichPacket calls").option("--large-graph", "Large-graph test: insert N synthetic nodes and query").option("--nodes <n>", "Node count for --large-graph (default 1000)", parseInt).option("--replay <path>", "Hook-log replay: path to hook-log.jsonl").option("--limit <n>", "Entry limit for --replay (default 500)", parseInt).action(async (opts) => {
|
|
3731
|
-
const { execFileSync
|
|
2749
|
+
const { execFileSync } = await import("child_process");
|
|
3732
2750
|
const args = ["bench/stress-test.ts"];
|
|
3733
2751
|
if (opts.reads) args.push("--reads", String(opts.reads));
|
|
3734
2752
|
if (opts.providers) args.push("--providers");
|
|
@@ -3737,25 +2755,25 @@ program.command("stress-test").description("Run stress tests: memory, concurrenc
|
|
|
3737
2755
|
if (opts.replay) args.push("--replay", opts.replay);
|
|
3738
2756
|
if (opts.limit) args.push("--limit", String(opts.limit));
|
|
3739
2757
|
try {
|
|
3740
|
-
|
|
2758
|
+
execFileSync("npx", ["tsx", ...args], { stdio: "inherit", shell: true, cwd: join7(dirname3(fileURLToPath(import.meta.url)), "..") });
|
|
3741
2759
|
} catch {
|
|
3742
2760
|
process.exit(1);
|
|
3743
2761
|
}
|
|
3744
2762
|
});
|
|
3745
2763
|
program.command("server").description("Start engram HTTP REST server (binds to 127.0.0.1 only)").option("--http", "Enable HTTP server (default)").option("--port <port>", "HTTP port", "7337").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3746
|
-
const { startHttpServer } = await import("./server-
|
|
2764
|
+
const { startHttpServer } = await import("./server-2ZQKXJ5M.js");
|
|
3747
2765
|
await startHttpServer(pathResolve2(opts.project), parseInt(opts.port, 10));
|
|
3748
2766
|
});
|
|
3749
2767
|
program.command("ui").description("Open the web dashboard (auto-starts HTTP server if needed)").option("--port <port>", "HTTP port", "7337").option("-p, --project <path>", "Project directory", ".").option("--no-open", "Don't launch browser, just print the URL").action(async (opts) => {
|
|
3750
2768
|
const port = parseInt(opts.port, 10);
|
|
3751
2769
|
const publicUrl = `http://127.0.0.1:${port}/ui`;
|
|
3752
2770
|
const projectRoot = pathResolve2(opts.project);
|
|
3753
|
-
const { existsSync:
|
|
3754
|
-
const pidPath =
|
|
2771
|
+
const { existsSync: existsSync8, readFileSync: readFileSync5 } = await import("fs");
|
|
2772
|
+
const pidPath = join7(projectRoot, ".engram", "http-server.pid");
|
|
3755
2773
|
let alreadyRunning = false;
|
|
3756
|
-
if (
|
|
2774
|
+
if (existsSync8(pidPath)) {
|
|
3757
2775
|
try {
|
|
3758
|
-
const pid = parseInt(
|
|
2776
|
+
const pid = parseInt(readFileSync5(pidPath, "utf-8"), 10);
|
|
3759
2777
|
process.kill(pid, 0);
|
|
3760
2778
|
alreadyRunning = true;
|
|
3761
2779
|
} catch {
|
|
@@ -3782,8 +2800,8 @@ program.command("ui").description("Open the web dashboard (auto-starts HTTP serv
|
|
|
3782
2800
|
const { platform } = process;
|
|
3783
2801
|
const opener = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
3784
2802
|
try {
|
|
3785
|
-
const { execFile:
|
|
3786
|
-
|
|
2803
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
2804
|
+
execFile2(opener, [bootUrl], { shell: platform === "win32" }, () => {
|
|
3787
2805
|
});
|
|
3788
2806
|
} catch {
|
|
3789
2807
|
console.log(chalk2.dim(` Open manually: ${bootUrl}`));
|
|
@@ -3791,19 +2809,19 @@ program.command("ui").description("Open the web dashboard (auto-starts HTTP serv
|
|
|
3791
2809
|
}
|
|
3792
2810
|
});
|
|
3793
2811
|
program.command("context-server").description("Start Zed-compatible context server (JSON-RPC over stdio)").action(async () => {
|
|
3794
|
-
const { execFileSync
|
|
2812
|
+
const { execFileSync } = await import("child_process");
|
|
3795
2813
|
try {
|
|
3796
|
-
|
|
2814
|
+
execFileSync("npx", ["tsx", "adapters/zed/index.ts"], {
|
|
3797
2815
|
stdio: "inherit",
|
|
3798
2816
|
shell: true,
|
|
3799
|
-
cwd:
|
|
2817
|
+
cwd: join7(dirname3(fileURLToPath(import.meta.url)), "..")
|
|
3800
2818
|
});
|
|
3801
2819
|
} catch {
|
|
3802
2820
|
process.exit(1);
|
|
3803
2821
|
}
|
|
3804
2822
|
});
|
|
3805
2823
|
program.command("tune").description("Analyze hook-log and propose provider config changes").option("-p, --project <path>", "Project directory", ".").option("--dry-run", "Show proposed changes without applying (default)").option("--apply", "Apply proposed changes to .engram/config.json").action(async (opts) => {
|
|
3806
|
-
const { analyzeTuning, applyTuning } = await import("./tuner-
|
|
2824
|
+
const { analyzeTuning, applyTuning } = await import("./tuner-Y2YENAZC.js");
|
|
3807
2825
|
const proposal = analyzeTuning(pathResolve2(opts.project));
|
|
3808
2826
|
if (proposal.changes.length === 0) {
|
|
3809
2827
|
console.log(
|
|
@@ -3834,8 +2852,8 @@ program.command("tune").description("Analyze hook-log and propose provider confi
|
|
|
3834
2852
|
});
|
|
3835
2853
|
var dbCmd = program.command("db").description("Database management");
|
|
3836
2854
|
dbCmd.command("status").description("Show schema version and migration status").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3837
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3838
|
-
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-
|
|
2855
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
2856
|
+
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-KJ5K5NWO.js");
|
|
3839
2857
|
const store = await getStore2(pathResolve2(opts.project));
|
|
3840
2858
|
try {
|
|
3841
2859
|
const version = getSchemaVersion(store.db);
|
|
@@ -3851,11 +2869,11 @@ dbCmd.command("status").description("Show schema version and migration status").
|
|
|
3851
2869
|
}
|
|
3852
2870
|
});
|
|
3853
2871
|
dbCmd.command("migrate").description("Run pending schema migrations").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3854
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3855
|
-
const { runMigrations } = await import("./migrate-
|
|
2872
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
2873
|
+
const { runMigrations } = await import("./migrate-KJ5K5NWO.js");
|
|
3856
2874
|
const store = await getStore2(pathResolve2(opts.project));
|
|
3857
2875
|
try {
|
|
3858
|
-
const dbPath =
|
|
2876
|
+
const dbPath = join7(pathResolve2(opts.project), ".engram", "graph.db");
|
|
3859
2877
|
const result = runMigrations(
|
|
3860
2878
|
store.db,
|
|
3861
2879
|
dbPath
|
|
@@ -3886,11 +2904,11 @@ dbCmd.command("rollback").description("Roll back to an earlier schema version (D
|
|
|
3886
2904
|
console.error(chalk2.red(`Invalid version: ${opts.to}`));
|
|
3887
2905
|
process.exit(1);
|
|
3888
2906
|
}
|
|
3889
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3890
|
-
const { rollback, getSchemaVersion } = await import("./migrate-
|
|
2907
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
2908
|
+
const { rollback, getSchemaVersion } = await import("./migrate-KJ5K5NWO.js");
|
|
3891
2909
|
const store = await getStore2(pathResolve2(opts.project));
|
|
3892
2910
|
try {
|
|
3893
|
-
const dbPath =
|
|
2911
|
+
const dbPath = join7(pathResolve2(opts.project), ".engram", "graph.db");
|
|
3894
2912
|
const current = getSchemaVersion(
|
|
3895
2913
|
store.db
|
|
3896
2914
|
);
|
|
@@ -3940,7 +2958,7 @@ dbCmd.command("rollback").description("Roll back to an earlier schema version (D
|
|
|
3940
2958
|
});
|
|
3941
2959
|
var pluginCmd = program.command("plugin").description("Manage context provider plugins");
|
|
3942
2960
|
pluginCmd.command("list").description("List installed provider plugins").action(async () => {
|
|
3943
|
-
const { loadPlugins, getPluginsDir, ensurePluginsDir } = await import("./plugin-loader-
|
|
2961
|
+
const { loadPlugins, getPluginsDir, ensurePluginsDir } = await import("./plugin-loader-SQQB6V74.js");
|
|
3944
2962
|
const dir = getPluginsDir();
|
|
3945
2963
|
ensurePluginsDir(dir);
|
|
3946
2964
|
const { loaded, failed } = await loadPlugins(dir);
|
|
@@ -3973,10 +2991,10 @@ pluginCmd.command("list").description("List installed provider plugins").action(
|
|
|
3973
2991
|
pluginCmd.command("install").description("Install a plugin by copying its .mjs file into ~/.engram/plugins/").argument("<file>", "Path to plugin .mjs file").action(async (file) => {
|
|
3974
2992
|
const { copyFileSync: copyFileSync2, statSync: statSync5 } = await import("fs");
|
|
3975
2993
|
const { basename: basename6 } = await import("path");
|
|
3976
|
-
const { getPluginsDir, ensurePluginsDir, validatePlugin } = await import("./plugin-loader-
|
|
2994
|
+
const { getPluginsDir, ensurePluginsDir, validatePlugin } = await import("./plugin-loader-SQQB6V74.js");
|
|
3977
2995
|
const { pathToFileURL } = await import("url");
|
|
3978
2996
|
const absPath = pathResolve2(file);
|
|
3979
|
-
if (!
|
|
2997
|
+
if (!existsSync7(absPath)) {
|
|
3980
2998
|
console.error(chalk2.red(`File not found: ${absPath}`));
|
|
3981
2999
|
process.exit(1);
|
|
3982
3000
|
}
|
|
@@ -4005,15 +3023,15 @@ pluginCmd.command("install").description("Install a plugin by copying its .mjs f
|
|
|
4005
3023
|
const pluginsDir = getPluginsDir();
|
|
4006
3024
|
ensurePluginsDir(pluginsDir);
|
|
4007
3025
|
const destName = basename6(absPath);
|
|
4008
|
-
const destPath =
|
|
3026
|
+
const destPath = join7(pluginsDir, destName);
|
|
4009
3027
|
copyFileSync2(absPath, destPath);
|
|
4010
3028
|
console.log(chalk2.green(`\u2713 Installed: ${destPath}`));
|
|
4011
3029
|
});
|
|
4012
3030
|
pluginCmd.command("remove").description("Remove an installed plugin by filename").argument("<filename>", "Plugin filename (e.g., my-provider.mjs)").action(async (filename) => {
|
|
4013
|
-
const { getPluginsDir } = await import("./plugin-loader-
|
|
3031
|
+
const { getPluginsDir } = await import("./plugin-loader-SQQB6V74.js");
|
|
4014
3032
|
const pluginsDir = getPluginsDir();
|
|
4015
|
-
const target =
|
|
4016
|
-
if (!
|
|
3033
|
+
const target = join7(pluginsDir, filename);
|
|
3034
|
+
if (!existsSync7(target)) {
|
|
4017
3035
|
console.error(chalk2.red(`No such plugin: ${filename}`));
|
|
4018
3036
|
console.log(chalk2.dim(`Plugins directory: ${pluginsDir}`));
|
|
4019
3037
|
process.exit(1);
|
|
@@ -4023,7 +3041,7 @@ pluginCmd.command("remove").description("Remove an installed plugin by filename"
|
|
|
4023
3041
|
});
|
|
4024
3042
|
var cacheCmd = program.command("cache").description("Inspect and manage the context cache");
|
|
4025
3043
|
cacheCmd.command("stats").description("Show cache hit rate, entries, and LRU sizes").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
4026
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3044
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
4027
3045
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
4028
3046
|
const store = await getStore2(pathResolve2(opts.project));
|
|
4029
3047
|
try {
|
|
@@ -4057,7 +3075,7 @@ cacheCmd.command("stats").description("Show cache hit rate, entries, and LRU siz
|
|
|
4057
3075
|
}
|
|
4058
3076
|
});
|
|
4059
3077
|
cacheCmd.command("clear").description("Flush all cache layers (query, pattern, hot files)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
4060
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3078
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
4061
3079
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
4062
3080
|
const store = await getStore2(pathResolve2(opts.project));
|
|
4063
3081
|
try {
|
|
@@ -4076,7 +3094,7 @@ cacheCmd.command("clear").description("Flush all cache layers (query, pattern, h
|
|
|
4076
3094
|
}
|
|
4077
3095
|
});
|
|
4078
3096
|
cacheCmd.command("warm").description("Pre-warm hot file cache from access frequency (top-N)").option("-p, --project <path>", "Project directory", ".").option("-n, --limit <n>", "Number of files to warm", "20").action(async (opts) => {
|
|
4079
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3097
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
4080
3098
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
4081
3099
|
const store = await getStore2(pathResolve2(opts.project));
|
|
4082
3100
|
try {
|
|
@@ -4175,7 +3193,7 @@ program.command("setup").description("Zero-friction first-run wizard (init + ins
|
|
|
4175
3193
|
"local"
|
|
4176
3194
|
).action(
|
|
4177
3195
|
async (opts) => {
|
|
4178
|
-
const { runSetup } = await import("./wizard-
|
|
3196
|
+
const { runSetup } = await import("./wizard-UH27IO4I.js");
|
|
4179
3197
|
const scope = opts.scope === "local" || opts.scope === "project" || opts.scope === "user" ? opts.scope : "local";
|
|
4180
3198
|
const result = await runSetup({
|
|
4181
3199
|
projectPath: opts.project,
|
|
@@ -4204,10 +3222,10 @@ function maybePrintFirstRunHint() {
|
|
|
4204
3222
|
if (FIRST_RUN_SILENT_CMDS.has(subcommand)) return;
|
|
4205
3223
|
try {
|
|
4206
3224
|
const cwd = process.cwd();
|
|
4207
|
-
if (
|
|
4208
|
-
const sentinel =
|
|
4209
|
-
if (
|
|
4210
|
-
mkdirSync(
|
|
3225
|
+
if (existsSync7(join7(cwd, ".engram", "graph.db"))) return;
|
|
3226
|
+
const sentinel = join7(homedir(), ".engram", "first-run-shown");
|
|
3227
|
+
if (existsSync7(sentinel)) return;
|
|
3228
|
+
mkdirSync(dirname3(sentinel), { recursive: true });
|
|
4211
3229
|
writeFileSync2(sentinel, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
4212
3230
|
process.stderr.write(
|
|
4213
3231
|
chalk2.dim("\u{1F4A1} ") + chalk2.yellow("First time in this repo?") + chalk2.dim(" Run ") + chalk2.white("engram setup") + chalk2.dim(" for a zero-friction install.\n")
|