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.
Files changed (29) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +114 -17
  3. package/dist/{aider-context-J557IHIP.js → aider-context-6IDE3R7U.js} +1 -1
  4. package/dist/{chunk-PEH54LYC.js → chunk-645NBY6L.js} +42 -5
  5. package/dist/chunk-73IBCRFI.js +215 -0
  6. package/dist/{chunk-ZVWRIVWQ.js → chunk-B4UOE64J.js} +29 -11
  7. package/dist/{chunk-XFE6ZANP.js → chunk-FKY6HIT2.js} +1 -1
  8. package/dist/chunk-RJC6RNXJ.js +1405 -0
  9. package/dist/{chunk-4XA6ENNL.js → chunk-VLTWBTQ7.js} +14 -15
  10. package/dist/chunk-ZUC6OXSL.js +178 -0
  11. package/dist/cli.js +277 -1259
  12. package/dist/{core-TSXA5XZH.js → core-77F2BVYV.js} +2 -2
  13. package/dist/{cursor-mdc-VEOFFDVO.js → cursor-mdc-EEO7PYZ3.js} +1 -1
  14. package/dist/{exporter-AWXS34AS.js → exporter-ZYJ4WM2F.js} +1 -1
  15. package/dist/{importer-3Q5M6QBL.js → importer-4UWQDH4W.js} +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/mcp-client-ROOJF76V.js +9 -0
  18. package/dist/mcp-config-QD4NPVXB.js +12 -0
  19. package/dist/{migrate-UKCO6BUU.js → migrate-KJ5K5NWO.js} +1 -1
  20. package/dist/{plugin-loader-STTGYIL5.js → plugin-loader-SQQB6V74.js} +69 -23
  21. package/dist/resolver-H7GXVP73.js +21 -0
  22. package/dist/serve.js +2 -2
  23. package/dist/{server-A6MUVKQK.js → server-2ZQKXJ5M.js} +74 -6
  24. package/dist/{windsurf-rules-RWPKBHRD.js → windsurf-rules-XF7MYF6J.js} +1 -1
  25. package/dist/{wizard-AOXWMSXW.js → wizard-UH27IO4I.js} +2 -2
  26. package/package.json +8 -3
  27. package/scripts/postinstall.mjs +32 -0
  28. package/scripts/preuninstall.mjs +200 -0
  29. 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
- readConfig
18
- } from "./chunk-22INHMKB.js";
19
- import {
20
- logHookEvent,
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-4XA6ENNL.js";
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-ZVWRIVWQ.js";
47
- import "./chunk-PEH54LYC.js";
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 existsSync9,
54
- readFileSync as readFileSync5,
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 dirname4, join as join9, resolve as pathResolve2 } from "path";
62
- import { fileURLToPath as fileURLToPath2 } from "url";
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 existsSync5, readFileSync as readFileSync2 } from "fs";
1683
- import { execFile as execFile3 } from "child_process";
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 dirname3, join as join5, resolve as resolve2 } from "path";
1686
- var execFileAsync = promisify(execFile3);
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 = join5(current, ".git", "HEAD");
1694
- if (existsSync5(headPath)) {
1695
- const content = readFileSync2(headPath, "utf-8").trim();
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 = dirname3(current);
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 existsSync6, statSync as statSync2 } from "fs";
2055
- import { resolve as resolve3, relative as relative3, extname } from "path";
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(relative3(projectRoot, absPath));
1100
+ const relPath = toPosixPath(relative4(projectRoot, absPath));
2091
1101
  if (shouldIgnore(relPath)) return { action: "skipped", count: 0 };
2092
- if (!existsSync6(absPath)) {
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 (!existsSync6(getDbPath(root))) {
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(relative3(root, absPath));
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 existsSync7, statSync as statSync3 } from "fs";
2486
- import { join as join7, resolve as resolve6, basename as basename4 } from "path";
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 = join7(root, ".engram", "hook-log.jsonl");
2607
- if (existsSync7(logPath)) {
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 existsSync8,
2668
- readFileSync as readFileSync4,
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 join8 } from "path";
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 = join8(projectRoot, "MEMORY.md");
1756
+ const memoryPath = join6(projectRoot, "MEMORY.md");
2745
1757
  try {
2746
1758
  let existing = "";
2747
- if (existsSync8(memoryPath)) {
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 = readFileSync4(memoryPath, "utf-8");
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 as createRequire2 } from "module";
2767
- var require3 = createRequire2(import.meta.url);
2768
- var { version: PKG_VERSION } = require3("../package.json");
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 = join9(resolvedProject, ".claude", "settings.local.json");
2816
- const projectSettings = join9(resolvedProject, ".claude", "settings.json");
2817
- const hasHooks = existsSync9(localSettings) && readFileSync5(localSettings, "utf-8").includes("engram intercept") || existsSync9(projectSettings) && readFileSync5(projectSettings, "utf-8").includes("engram intercept");
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 = join9(
1843
+ const localSettingsPath = join7(
2832
1844
  pathResolve2(projectPath),
2833
1845
  ".claude",
2834
1846
  "settings.local.json"
2835
1847
  );
2836
1848
  let settings = {};
2837
- if (existsSync9(localSettingsPath)) {
1849
+ if (existsSync7(localSettingsPath)) {
2838
1850
  try {
2839
- const raw = readFileSync5(localSettingsPath, "utf-8");
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(dirname4(localSettingsPath), { recursive: true });
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-3Q5M6QBL.js");
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 (!existsSync9(join9(root, ".engram", "graph.db"))) {
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 = join9(resolvedPath, ".engram", "graph.db");
2989
- if (!existsSync9(dbPath)) {
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 (existsSync9(join9(resolvedPath, ".engram", "graph.db"))) {
2021
+ if (existsSync7(join7(resolvedPath, ".engram", "graph.db"))) {
3010
2022
  found = true;
3011
2023
  break;
3012
2024
  }
3013
- const parent = dirname4(resolvedPath);
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 = join9(resolvedPath, ".engram", "hook-log.jsonl");
3022
- if (!existsSync9(logPath)) {
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("Generate CLAUDE.md / .cursorrules section from graph").option("-p, --project <path>", "Project directory", ".").option("-t, --target <type>", "Target file: claude, cursor, agents").option(
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 ${result.file} (${result.nodesIncluded} nodes, view: ${result.view})`
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-VEOFFDVO.js");
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-AWXS34AS.js");
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-J557IHIP.js");
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-RWPKBHRD.js");
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 join9(absProject, ".claude", "settings.local.json");
2281
+ return join7(absProject, ".claude", "settings.local.json");
3264
2282
  case "project":
3265
- return join9(absProject, ".claude", "settings.json");
2283
+ return join7(absProject, ".claude", "settings.json");
3266
2284
  case "user":
3267
- return join9(homedir(), ".claude", "settings.json");
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 (existsSync9(settingsPath)) {
2383
+ if (existsSync7(settingsPath)) {
3366
2384
  try {
3367
- const raw = readFileSync5(settingsPath, "utf-8");
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(dirname4(settingsPath), { recursive: true });
3420
- if (existsSync9(settingsPath)) {
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 (!existsSync9(settingsPath)) {
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 = readFileSync5(settingsPath, "utf-8");
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 = join9(projectRoot, ".engram", "hook-disabled");
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 = join9(projectRoot, ".engram", "hook-disabled");
3626
- if (!existsSync9(flagPath)) {
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 = join9(projectRoot, ".git", "HEAD");
3669
- if (existsSync9(headPath)) {
3670
- const content = readFileSync5(headPath, "utf-8").trim();
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: ${join9(projectRoot, "MEMORY.md")}`)
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: execFileSync2 } = await import("child_process");
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
- execFileSync2("npx", ["tsx", ...args], { stdio: "inherit", shell: true, cwd: join9(dirname4(fileURLToPath2(import.meta.url)), "..") });
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-A6MUVKQK.js");
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: existsSync10, readFileSync: readFileSync6 } = await import("fs");
3754
- const pidPath = join9(projectRoot, ".engram", "http-server.pid");
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 (existsSync10(pidPath)) {
2774
+ if (existsSync8(pidPath)) {
3757
2775
  try {
3758
- const pid = parseInt(readFileSync6(pidPath, "utf-8"), 10);
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: execFile4 } = await import("child_process");
3786
- execFile4(opener, [bootUrl], { shell: platform === "win32" }, () => {
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: execFileSync2 } = await import("child_process");
2812
+ const { execFileSync } = await import("child_process");
3795
2813
  try {
3796
- execFileSync2("npx", ["tsx", "adapters/zed/index.ts"], {
2814
+ execFileSync("npx", ["tsx", "adapters/zed/index.ts"], {
3797
2815
  stdio: "inherit",
3798
2816
  shell: true,
3799
- cwd: join9(dirname4(fileURLToPath2(import.meta.url)), "..")
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-KFNNGKG3.js");
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-TSXA5XZH.js");
3838
- const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
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-TSXA5XZH.js");
3855
- const { runMigrations } = await import("./migrate-UKCO6BUU.js");
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 = join9(pathResolve2(opts.project), ".engram", "graph.db");
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-TSXA5XZH.js");
3890
- const { rollback, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
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 = join9(pathResolve2(opts.project), ".engram", "graph.db");
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-STTGYIL5.js");
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-STTGYIL5.js");
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 (!existsSync9(absPath)) {
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 = join9(pluginsDir, destName);
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-STTGYIL5.js");
3031
+ const { getPluginsDir } = await import("./plugin-loader-SQQB6V74.js");
4014
3032
  const pluginsDir = getPluginsDir();
4015
- const target = join9(pluginsDir, filename);
4016
- if (!existsSync9(target)) {
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-TSXA5XZH.js");
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-TSXA5XZH.js");
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-TSXA5XZH.js");
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-AOXWMSXW.js");
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 (existsSync9(join9(cwd, ".engram", "graph.db"))) return;
4208
- const sentinel = join9(homedir(), ".engram", "first-run-shown");
4209
- if (existsSync9(sentinel)) return;
4210
- mkdirSync(dirname4(sentinel), { recursive: true });
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")