@dreamtree-org/graphify 1.1.1 → 1.1.2

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.
@@ -156,7 +156,7 @@ function validateDsn(dsn) {
156
156
  }
157
157
  };
158
158
  }
159
- function validateGraphPath(path17, base) {
159
+ function validateGraphPath(path18, base) {
160
160
  const baseDir = base ?? nodePath.join(process.cwd(), "graphify-out");
161
161
  let resolvedBase;
162
162
  try {
@@ -164,7 +164,7 @@ function validateGraphPath(path17, base) {
164
164
  } catch {
165
165
  throw new Error(`Base directory does not exist: ${baseDir}`);
166
166
  }
167
- const candidate = nodePath.isAbsolute(path17) ? path17 : nodePath.join(resolvedBase, path17);
167
+ const candidate = nodePath.isAbsolute(path18) ? path18 : nodePath.join(resolvedBase, path18);
168
168
  const resolvedCandidate = nodePath.resolve(candidate);
169
169
  let realCandidate = resolvedCandidate;
170
170
  try {
@@ -174,7 +174,7 @@ function validateGraphPath(path17, base) {
174
174
  const relative4 = nodePath.relative(resolvedBase, realCandidate);
175
175
  const escapes = relative4 === ".." || relative4.startsWith(`..${nodePath.sep}`) || nodePath.isAbsolute(relative4);
176
176
  if (escapes) {
177
- throw new Error(`Path escapes graphify-out/: ${path17}`);
177
+ throw new Error(`Path escapes graphify-out/: ${path18}`);
178
178
  }
179
179
  return realCandidate;
180
180
  }
@@ -443,25 +443,25 @@ function shortestPath(graph, fromQuery, toQuery) {
443
443
  if (!visited.has(to.id)) {
444
444
  return { from, to, found: false, path: [], edges: [] };
445
445
  }
446
- const path17 = [to.id];
446
+ const path18 = [to.id];
447
447
  let cursor = to.id;
448
448
  while (cursor !== from.id) {
449
449
  const prev = predecessor.get(cursor);
450
450
  if (!prev) break;
451
- path17.unshift(prev);
451
+ path18.unshift(prev);
452
452
  cursor = prev;
453
453
  }
454
454
  const edges = [];
455
- for (let i = 0; i < path17.length - 1; i++) {
456
- const a = path17[i];
457
- const b = path17[i + 1];
455
+ for (let i = 0; i < path18.length - 1; i++) {
456
+ const a = path18[i];
457
+ const b = path18[i + 1];
458
458
  const key = graph.edges(a, b)[0] ?? graph.edges(b, a)[0];
459
459
  if (key) {
460
460
  const attrs = graph.getEdgeAttributes(key);
461
461
  edges.push({ source: a, target: b, relation: String(attrs.relation), confidence: String(attrs.confidence) });
462
462
  }
463
463
  }
464
- return { from, to, found: true, path: path17, edges };
464
+ return { from, to, found: true, path: path18, edges };
465
465
  }
466
466
  function explainNode(graph, query) {
467
467
  const match = resolveNode(graph, query);
@@ -607,6 +607,78 @@ var init_impact = __esm({
607
607
  }
608
608
  });
609
609
 
610
+ // src/testmap.ts
611
+ function isTestFile(path18) {
612
+ return TEST_FILE_PATTERNS.some((p) => p.test(path18));
613
+ }
614
+ function selectionFromImpact(graph, targetIds, targetLabel) {
615
+ const best = /* @__PURE__ */ new Map();
616
+ let affectedNodeCount = 0;
617
+ for (const id of targetIds) {
618
+ const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT });
619
+ if (!impact) continue;
620
+ affectedNodeCount += impact.affected.length;
621
+ for (const node of impact.affected) {
622
+ if (!isTestFile(node.sourceFile)) continue;
623
+ const existing = best.get(node.sourceFile);
624
+ if (!existing || node.depth < existing.depth) {
625
+ best.set(node.sourceFile, { depth: node.depth, via: node.via.dependsOn });
626
+ }
627
+ }
628
+ }
629
+ const testFiles = [...best.entries()].map(([file, info]) => ({ file, depth: info.depth, via: info.via })).sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
630
+ return { target: targetLabel, testFiles, affectedNodeCount };
631
+ }
632
+ function testsForNode(graph, nodeQuery) {
633
+ const match = resolveNode(graph, nodeQuery);
634
+ if (!match) return null;
635
+ return selectionFromImpact(graph, [match.id], match.id);
636
+ }
637
+ function testsForChangedFiles(graph, changedFiles) {
638
+ const fileIds = [];
639
+ const selfSelected = /* @__PURE__ */ new Map();
640
+ for (const file of changedFiles) {
641
+ if (isTestFile(file)) {
642
+ selfSelected.set(file, { depth: 0, via: "(changed directly)" });
643
+ continue;
644
+ }
645
+ if (graph.hasNode(file)) fileIds.push(file);
646
+ }
647
+ const selection = selectionFromImpact(graph, fileIds, changedFiles.join(", "));
648
+ for (const [file, info] of selfSelected) {
649
+ if (!selection.testFiles.some((t) => t.file === file)) {
650
+ selection.testFiles.push({ file, depth: info.depth, via: info.via });
651
+ }
652
+ }
653
+ selection.testFiles.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
654
+ return selection;
655
+ }
656
+ var TEST_FILE_PATTERNS, TEST_REACH_DEPTH, TEST_REACH_LIMIT;
657
+ var init_testmap = __esm({
658
+ "src/testmap.ts"() {
659
+ "use strict";
660
+ init_cjs_shims();
661
+ init_impact();
662
+ init_query();
663
+ TEST_FILE_PATTERNS = [
664
+ /(^|\/)tests?\//,
665
+ // tests/ or test/ directory
666
+ /(^|\/)__tests__\//,
667
+ /(^|\/)spec\//,
668
+ /\.test\.[cm]?[jt]sx?$/,
669
+ /\.spec\.[cm]?[jt]sx?$/,
670
+ /(^|\/)test_[^/]+\.py$/,
671
+ /_test\.py$/,
672
+ /_test\.go$/,
673
+ /Tests?\.(java|cs)$/,
674
+ /_spec\.rb$/,
675
+ /_test\.rb$/
676
+ ];
677
+ TEST_REACH_DEPTH = 4;
678
+ TEST_REACH_LIMIT = 2e3;
679
+ }
680
+ });
681
+
610
682
  // src/context.ts
611
683
  function lineNumberOf(sourceLocation) {
612
684
  const match = /^L(\d+)$/.exec(sourceLocation);
@@ -641,7 +713,7 @@ function snippetRange(startLine, fileLineCount, entityStartsInFile, maxLines) {
641
713
  const hardEnd = nextStart !== void 0 ? nextStart - 1 : fileLineCount;
642
714
  return { start: startLine, end: Math.min(hardEnd, startLine + maxLines - 1) };
643
715
  }
644
- async function buildContextPack(graph, task, readFile20, options = {}) {
716
+ async function buildContextPack(graph, task, readFile21, options = {}) {
645
717
  const tokenBudget = options.tokenBudget ?? DEFAULT_CONTEXT_BUDGET;
646
718
  const maxSnippetLines = options.maxSnippetLines ?? DEFAULT_MAX_SNIPPET_LINES;
647
719
  const ranked = rankForTask(graph, task, options.maxSeeds);
@@ -662,7 +734,7 @@ async function buildContextPack(graph, task, readFile20, options = {}) {
662
734
  if (cached !== void 0) return cached;
663
735
  let lines;
664
736
  try {
665
- lines = (await readFile20(file)).split("\n");
737
+ lines = (await readFile21(file)).split("\n");
666
738
  } catch {
667
739
  lines = null;
668
740
  }
@@ -748,78 +820,6 @@ var init_context = __esm({
748
820
  }
749
821
  });
750
822
 
751
- // src/testmap.ts
752
- function isTestFile(path17) {
753
- return TEST_FILE_PATTERNS.some((p) => p.test(path17));
754
- }
755
- function selectionFromImpact(graph, targetIds, targetLabel) {
756
- const best = /* @__PURE__ */ new Map();
757
- let affectedNodeCount = 0;
758
- for (const id of targetIds) {
759
- const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT });
760
- if (!impact) continue;
761
- affectedNodeCount += impact.affected.length;
762
- for (const node of impact.affected) {
763
- if (!isTestFile(node.sourceFile)) continue;
764
- const existing = best.get(node.sourceFile);
765
- if (!existing || node.depth < existing.depth) {
766
- best.set(node.sourceFile, { depth: node.depth, via: node.via.dependsOn });
767
- }
768
- }
769
- }
770
- const testFiles = [...best.entries()].map(([file, info]) => ({ file, depth: info.depth, via: info.via })).sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
771
- return { target: targetLabel, testFiles, affectedNodeCount };
772
- }
773
- function testsForNode(graph, nodeQuery) {
774
- const match = resolveNode(graph, nodeQuery);
775
- if (!match) return null;
776
- return selectionFromImpact(graph, [match.id], match.id);
777
- }
778
- function testsForChangedFiles(graph, changedFiles) {
779
- const fileIds = [];
780
- const selfSelected = /* @__PURE__ */ new Map();
781
- for (const file of changedFiles) {
782
- if (isTestFile(file)) {
783
- selfSelected.set(file, { depth: 0, via: "(changed directly)" });
784
- continue;
785
- }
786
- if (graph.hasNode(file)) fileIds.push(file);
787
- }
788
- const selection = selectionFromImpact(graph, fileIds, changedFiles.join(", "));
789
- for (const [file, info] of selfSelected) {
790
- if (!selection.testFiles.some((t) => t.file === file)) {
791
- selection.testFiles.push({ file, depth: info.depth, via: info.via });
792
- }
793
- }
794
- selection.testFiles.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
795
- return selection;
796
- }
797
- var TEST_FILE_PATTERNS, TEST_REACH_DEPTH, TEST_REACH_LIMIT;
798
- var init_testmap = __esm({
799
- "src/testmap.ts"() {
800
- "use strict";
801
- init_cjs_shims();
802
- init_impact();
803
- init_query();
804
- TEST_FILE_PATTERNS = [
805
- /(^|\/)tests?\//,
806
- // tests/ or test/ directory
807
- /(^|\/)__tests__\//,
808
- /(^|\/)spec\//,
809
- /\.test\.[cm]?[jt]sx?$/,
810
- /\.spec\.[cm]?[jt]sx?$/,
811
- /(^|\/)test_[^/]+\.py$/,
812
- /_test\.py$/,
813
- /_test\.go$/,
814
- /Tests?\.(java|cs)$/,
815
- /_spec\.rb$/,
816
- /_test\.rb$/
817
- ];
818
- TEST_REACH_DEPTH = 4;
819
- TEST_REACH_LIMIT = 2e3;
820
- }
821
- });
822
-
823
823
  // src/mcp/server.ts
824
824
  var server_exports = {};
825
825
  __export(server_exports, {
@@ -914,7 +914,7 @@ function createGraphifyMcpServer(outDir) {
914
914
  },
915
915
  async ({ task, budget }) => {
916
916
  const graph = await loadGraph(outDir);
917
- const pack = await buildContextPack(graph, task, (p) => fs22.readFile(p, "utf-8"), {
917
+ const pack = await buildContextPack(graph, task, (p) => fs23.readFile(p, "utf-8"), {
918
918
  tokenBudget: budget
919
919
  });
920
920
  return textResult(renderContextPack(pack));
@@ -940,23 +940,23 @@ function createGraphifyMcpServer(outDir) {
940
940
  return server;
941
941
  }
942
942
  async function startServer(graphPath, transport = new import_stdio.StdioServerTransport()) {
943
- const outDir = path15.dirname(path15.resolve(graphPath));
944
- const fileName = path15.basename(graphPath);
943
+ const outDir = path16.dirname(path16.resolve(graphPath));
944
+ const fileName = path16.basename(graphPath);
945
945
  validateGraphPath(fileName, outDir);
946
946
  const server = createGraphifyMcpServer(outDir);
947
947
  await server.connect(transport);
948
948
  }
949
949
  async function main(argv = process.argv) {
950
- const graphPath = argv[2] ?? path15.join(process.cwd(), "graphify-out", "graph.json");
950
+ const graphPath = argv[2] ?? path16.join(process.cwd(), "graphify-out", "graph.json");
951
951
  await startServer(graphPath);
952
952
  }
953
- var fs22, path15, import_mcp, import_stdio, import_zod2;
953
+ var fs23, path16, import_mcp, import_stdio, import_zod2;
954
954
  var init_server = __esm({
955
955
  "src/mcp/server.ts"() {
956
956
  "use strict";
957
957
  init_cjs_shims();
958
- fs22 = __toESM(require("fs/promises"), 1);
959
- path15 = __toESM(require("path"), 1);
958
+ fs23 = __toESM(require("fs/promises"), 1);
959
+ path16 = __toESM(require("path"), 1);
960
960
  import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
961
961
  import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
962
962
  import_zod2 = require("zod");
@@ -1131,9 +1131,9 @@ var init_mysql = __esm({
1131
1131
  // src/cli/index.ts
1132
1132
  init_cjs_shims();
1133
1133
  var import_node_child_process3 = require("child_process");
1134
- var fs23 = __toESM(require("fs/promises"), 1);
1134
+ var fs24 = __toESM(require("fs/promises"), 1);
1135
1135
  var os3 = __toESM(require("os"), 1);
1136
- var path16 = __toESM(require("path"), 1);
1136
+ var path17 = __toESM(require("path"), 1);
1137
1137
  var import_node_util3 = require("util");
1138
1138
  var import_chokidar = require("chokidar");
1139
1139
  var import_commander = require("commander");
@@ -1465,8 +1465,8 @@ init_graphStore();
1465
1465
 
1466
1466
  // src/pipeline.ts
1467
1467
  init_cjs_shims();
1468
- var fs13 = __toESM(require("fs/promises"), 1);
1469
- var path8 = __toESM(require("path"), 1);
1468
+ var fs14 = __toESM(require("fs/promises"), 1);
1469
+ var path9 = __toESM(require("path"), 1);
1470
1470
 
1471
1471
  // src/build.ts
1472
1472
  init_cjs_shims();
@@ -3138,10 +3138,10 @@ async function extractRust(filePath, displayPath) {
3138
3138
  }
3139
3139
  }
3140
3140
  } else if (fn.type === "scoped_identifier") {
3141
- const path17 = fn.childForFieldName("path")?.text;
3141
+ const path18 = fn.childForFieldName("path")?.text;
3142
3142
  const name = fn.childForFieldName("name")?.text;
3143
- if (path17 && name) {
3144
- const viaImport = importIndex.get(path17);
3143
+ if (path18 && name) {
3144
+ const viaImport = importIndex.get(path18);
3145
3145
  if (viaImport) {
3146
3146
  targetId = viaImport.id;
3147
3147
  confidence = "INFERRED";
@@ -3601,6 +3601,8 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
3601
3601
 
3602
3602
  // src/resolve.ts
3603
3603
  init_cjs_shims();
3604
+ var fs13 = __toESM(require("fs/promises"), 1);
3605
+ var path8 = __toESM(require("path"), 1);
3604
3606
  var CODE_EXTENSIONS2 = [
3605
3607
  ".ts",
3606
3608
  ".tsx",
@@ -3620,6 +3622,29 @@ var CODE_EXTENSIONS2 = [
3620
3622
  function isOpaque(id) {
3621
3623
  return id.startsWith("module:") || id.startsWith("external:") || id.startsWith("db:");
3622
3624
  }
3625
+ var SELF_IMPORT_STEMS = (subpath) => subpath === "" ? ["index", "src/index", "lib/index", "source/index", "src/main"] : [
3626
+ subpath,
3627
+ `src/${subpath}`,
3628
+ `lib/${subpath}`,
3629
+ `source/${subpath}`,
3630
+ `${subpath}/index`,
3631
+ `src/${subpath}/index`,
3632
+ `lib/${subpath}/index`
3633
+ ];
3634
+ function selfImportCandidates(spec, selfNames) {
3635
+ const selfName = selfNames.find((n) => spec === n || spec.startsWith(`${n}/`));
3636
+ if (selfName === void 0) return null;
3637
+ const subpath = spec === selfName ? "" : spec.slice(selfName.length + 1);
3638
+ return SELF_IMPORT_STEMS(subpath).flatMap((stem) => CODE_EXTENSIONS2.map((ext) => `${stem}${ext}`));
3639
+ }
3640
+ async function readSelfNames(root) {
3641
+ try {
3642
+ const pkg = JSON.parse(await fs13.readFile(path8.join(root, "package.json"), "utf-8"));
3643
+ return typeof pkg.name === "string" && pkg.name.length > 0 ? [pkg.name] : [];
3644
+ } catch {
3645
+ return [];
3646
+ }
3647
+ }
3623
3648
  function stripKnownExtension(p) {
3624
3649
  for (const ext of CODE_EXTENSIONS2) {
3625
3650
  if (p.endsWith(ext)) return p.slice(0, -ext.length);
@@ -3644,11 +3669,16 @@ function uniqueMatch(guessed, candidates, realIds) {
3644
3669
  if (matches.size !== 1) return null;
3645
3670
  return [...matches][0];
3646
3671
  }
3647
- function resolveCrossFileReferences(extractions) {
3672
+ function resolveCrossFileReferences(extractions, options = {}) {
3673
+ const selfNames = options.selfNames ?? [];
3648
3674
  const realIds = /* @__PURE__ */ new Set();
3649
3675
  const realFiles = /* @__PURE__ */ new Set();
3650
3676
  for (const extraction of extractions) {
3651
3677
  for (const node of extraction.nodes) realIds.add(node.id);
3678
+ const first = extraction.nodes[0];
3679
+ if (first && first.id === first.sourceFile && !isOpaque(first.id)) {
3680
+ realFiles.add(first.id);
3681
+ }
3652
3682
  for (const node of extraction.nodes) {
3653
3683
  const sep3 = node.id.indexOf("::");
3654
3684
  if (sep3 > 0) realFiles.add(node.id.slice(0, sep3));
@@ -3659,6 +3689,19 @@ function resolveCrossFileReferences(extractions) {
3659
3689
  }
3660
3690
  const remap = /* @__PURE__ */ new Map();
3661
3691
  const resolveId = (id) => {
3692
+ if (id.startsWith("module:") && selfNames.length > 0) {
3693
+ const cachedSelf = remap.get(id);
3694
+ if (cachedSelf !== void 0) return cachedSelf;
3695
+ const candidates = selfImportCandidates(id.slice("module:".length), selfNames);
3696
+ if (candidates !== null) {
3697
+ const resolved2 = uniqueMatch(id, candidates, realFiles);
3698
+ if (resolved2 !== null) {
3699
+ remap.set(id, resolved2);
3700
+ return resolved2;
3701
+ }
3702
+ }
3703
+ return null;
3704
+ }
3662
3705
  if (isOpaque(id)) return null;
3663
3706
  const cached = remap.get(id);
3664
3707
  if (cached !== void 0) return cached;
@@ -3705,21 +3748,21 @@ function resolveCrossFileReferences(extractions) {
3705
3748
  async function runPipeline(root, options = {}) {
3706
3749
  const progress = options.onProgress ?? (() => {
3707
3750
  });
3708
- const resolvedRoot = path8.resolve(root);
3751
+ const resolvedRoot = path9.resolve(root);
3709
3752
  progress(`Scanning ${resolvedRoot} ...`);
3710
3753
  const manifest = collectFiles(resolvedRoot);
3711
3754
  progress(
3712
3755
  `Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
3713
3756
  );
3714
- const outDir = options.outDir ?? path8.join(resolvedRoot, "graphify-out");
3757
+ const outDir = options.outDir ?? path9.join(resolvedRoot, "graphify-out");
3715
3758
  const cache = new ExtractionCache(outDir);
3716
3759
  progress("Extracting...");
3717
3760
  const extractions = [];
3718
3761
  let cacheHits = 0;
3719
3762
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
3720
- const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
3763
+ const filePath = path9.relative(process.cwd(), path9.join(resolvedRoot, relFile)) || relFile;
3721
3764
  try {
3722
- const key = cache.key(relFile, await fs13.readFile(path8.join(resolvedRoot, relFile)));
3765
+ const key = cache.key(relFile, await fs14.readFile(path9.join(resolvedRoot, relFile)));
3723
3766
  let extraction = options.update ? await cache.get(key) : null;
3724
3767
  if (extraction) {
3725
3768
  cacheHits++;
@@ -3740,7 +3783,9 @@ async function runPipeline(root, options = {}) {
3740
3783
  extractions.push(...options.extraExtractions);
3741
3784
  }
3742
3785
  progress("Resolving cross-file references...");
3743
- const resolveStats = resolveCrossFileReferences(extractions);
3786
+ const resolveStats = resolveCrossFileReferences(extractions, {
3787
+ selfNames: await readSelfNames(resolvedRoot)
3788
+ });
3744
3789
  if (resolveStats.resolvedEndpoints > 0) {
3745
3790
  progress(
3746
3791
  ` re-pointed ${resolveStats.resolvedEndpoints} guessed reference(s) at real nodes (${resolveStats.droppedPlaceholders} placeholder node(s) dropped).`
@@ -3797,9 +3842,10 @@ async function runAffected(nodeName, options = {}) {
3797
3842
  console.log("");
3798
3843
  console.log(currentDepth === 1 ? "Directly affected:" : `Affected at depth ${currentDepth}:`);
3799
3844
  }
3800
- const location = node.sourceLocation ? `:${node.sourceLocation}` : "";
3845
+ const isPlaceholder = node.sourceFile === "<unknown>" || node.sourceFile === "<external>" || node.sourceFile === "";
3846
+ const where = isPlaceholder ? "(reference only)" : `${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ""}`;
3801
3847
  console.log(
3802
- ` ${node.label} \u2014 ${node.sourceFile}${location} (--${node.via.relation}--> ${node.via.dependsOn}, ${node.via.confidence})`
3848
+ ` ${node.label} \u2014 ${where} (--${node.via.relation}--> ${node.via.dependsOn}, ${node.via.confidence})`
3803
3849
  );
3804
3850
  }
3805
3851
  console.log("");
@@ -3814,11 +3860,12 @@ async function runAffected(nodeName, options = {}) {
3814
3860
 
3815
3861
  // src/cli/commands/benchmark.ts
3816
3862
  init_cjs_shims();
3817
- var fs14 = __toESM(require("fs/promises"), 1);
3863
+ var fs15 = __toESM(require("fs/promises"), 1);
3818
3864
  init_graphStore();
3819
3865
  init_query();
3866
+ init_testmap();
3820
3867
  var tokensOf = (text) => Math.ceil(text.length / 4);
3821
- async function benchmarkQuestion(graph, question, readFile20) {
3868
+ async function benchmarkQuestion(graph, question, readFile21) {
3822
3869
  const result = queryGraph(graph, question);
3823
3870
  const graphTokens = tokensOf(JSON.stringify(result));
3824
3871
  const sourceFiles = /* @__PURE__ */ new Set();
@@ -3832,7 +3879,7 @@ async function benchmarkQuestion(graph, question, readFile20) {
3832
3879
  let filesMissing = 0;
3833
3880
  for (const file of [...sourceFiles].sort((a, b) => a.localeCompare(b))) {
3834
3881
  try {
3835
- naiveTokens += tokensOf(await readFile20(file));
3882
+ naiveTokens += tokensOf(await readFile21(file));
3836
3883
  filesRead++;
3837
3884
  } catch {
3838
3885
  filesMissing++;
@@ -3847,12 +3894,14 @@ async function benchmarkQuestion(graph, question, readFile20) {
3847
3894
  filesMissing
3848
3895
  };
3849
3896
  }
3897
+ var NON_QUESTION_FILE = /(^|\/)(examples?|scripts?|bench(?:mark)?s?|fixtures?|docs?|\.github)\/|\.(config|conf)\.[^/]+$|(^|\/)(rollup|webpack|vite|vitest|jest|babel|tsup|eslint|prettier)\.[^/]*$/i;
3850
3898
  function defaultQuestions(graph, count = 5) {
3851
3899
  const entries = [...graph.nodes()].map((id) => ({
3852
3900
  id,
3853
3901
  degree: graph.degree(id),
3854
- label: graph.getNodeAttribute(id, "label") ?? id
3855
- })).filter((e) => e.id.includes("::")).sort((a, b) => b.degree - a.degree || a.id.localeCompare(b.id));
3902
+ label: graph.getNodeAttribute(id, "label") ?? id,
3903
+ file: graph.getNodeAttribute(id, "sourceFile") ?? ""
3904
+ })).filter((e) => e.id.includes("::")).filter((e) => !isTestFile(e.file) && !NON_QUESTION_FILE.test(e.file)).sort((a, b) => b.degree - a.degree || a.id.localeCompare(b.id));
3856
3905
  return entries.slice(0, count).map((e) => `how does ${e.label} work`);
3857
3906
  }
3858
3907
  async function runBenchmark(questions) {
@@ -3864,7 +3913,7 @@ async function runBenchmark(questions) {
3864
3913
  }
3865
3914
  const rows = [];
3866
3915
  for (const question of effective) {
3867
- rows.push(await benchmarkQuestion(graph, question, (p) => fs14.readFile(p, "utf-8")));
3916
+ rows.push(await benchmarkQuestion(graph, question, (p) => fs15.readFile(p, "utf-8")));
3868
3917
  }
3869
3918
  console.log("Token cost per question \u2014 graph answer vs reading the files it touches:\n");
3870
3919
  for (const row of rows) {
@@ -3887,8 +3936,8 @@ Average reduction: ${avg.toFixed(1)}x across ${scored.length} question(s).`);
3887
3936
 
3888
3937
  // src/cli/commands/check.ts
3889
3938
  init_cjs_shims();
3890
- var fs15 = __toESM(require("fs/promises"), 1);
3891
- var path9 = __toESM(require("path"), 1);
3939
+ var fs16 = __toESM(require("fs/promises"), 1);
3940
+ var path10 = __toESM(require("path"), 1);
3892
3941
 
3893
3942
  // src/check.ts
3894
3943
  init_cjs_shims();
@@ -3959,10 +4008,10 @@ function checkRules(graph, config) {
3959
4008
  // src/cli/commands/check.ts
3960
4009
  init_graphStore();
3961
4010
  async function runCheck(options = {}) {
3962
- const rulesPath = path9.resolve(options.rules ?? "graphify.rules.json");
4011
+ const rulesPath = path10.resolve(options.rules ?? "graphify.rules.json");
3963
4012
  let raw;
3964
4013
  try {
3965
- raw = await fs15.readFile(rulesPath, "utf-8");
4014
+ raw = await fs16.readFile(rulesPath, "utf-8");
3966
4015
  } catch {
3967
4016
  throw new Error(
3968
4017
  `No rules file at ${rulesPath} \u2014 create graphify.rules.json with { "rules": [{ "name": "...", "from": "src/a/**", "disallow": ["src/b/**"] }] }.`
@@ -3985,12 +4034,12 @@ async function runCheck(options = {}) {
3985
4034
 
3986
4035
  // src/cli/commands/context.ts
3987
4036
  init_cjs_shims();
3988
- var fs16 = __toESM(require("fs/promises"), 1);
4037
+ var fs17 = __toESM(require("fs/promises"), 1);
3989
4038
  init_context();
3990
4039
  init_graphStore();
3991
4040
  async function runContext(task, options = {}) {
3992
4041
  const graph = await loadGraph();
3993
- const pack = await buildContextPack(graph, task, (p) => fs16.readFile(p, "utf-8"), {
4042
+ const pack = await buildContextPack(graph, task, (p) => fs17.readFile(p, "utf-8"), {
3994
4043
  tokenBudget: options.budget
3995
4044
  });
3996
4045
  console.log(renderContextPack(pack));
@@ -4007,9 +4056,13 @@ async function runExplain(nodeName) {
4007
4056
  console.log(`No node matched "${nodeName}".`);
4008
4057
  return;
4009
4058
  }
4010
- const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
4011
4059
  console.log(result.label);
4012
- console.log(` location: ${result.sourceFile}${location}`);
4060
+ if (result.sourceFile === "<unknown>" || result.sourceFile === "<external>" || result.sourceFile === "") {
4061
+ console.log(" location: (reference only \u2014 no declaration seen; check its outgoing edges for the real definition)");
4062
+ } else {
4063
+ const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
4064
+ console.log(` location: ${result.sourceFile}${location}`);
4065
+ }
4013
4066
  if (result.communityLabel) {
4014
4067
  console.log(` community: ${result.communityLabel}`);
4015
4068
  }
@@ -4039,9 +4092,9 @@ init_cjs_shims();
4039
4092
  // src/diff.ts
4040
4093
  init_cjs_shims();
4041
4094
  var import_node_child_process = require("child_process");
4042
- var fs17 = __toESM(require("fs/promises"), 1);
4095
+ var fs18 = __toESM(require("fs/promises"), 1);
4043
4096
  var os = __toESM(require("os"), 1);
4044
- var path10 = __toESM(require("path"), 1);
4097
+ var path11 = __toESM(require("path"), 1);
4045
4098
  var import_node_util = require("util");
4046
4099
  init_impact();
4047
4100
  init_testmap();
@@ -4050,13 +4103,13 @@ async function graphForDirectory(root) {
4050
4103
  const manifest = collectFiles(root);
4051
4104
  const extractions = [];
4052
4105
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
4053
- extractions.push(await extract(path10.join(root, relFile), relFile));
4106
+ extractions.push(await extract(path11.join(root, relFile), relFile));
4054
4107
  }
4055
- resolveCrossFileReferences(extractions);
4108
+ resolveCrossFileReferences(extractions, { selfNames: await readSelfNames(root) });
4056
4109
  return buildGraph(extractions);
4057
4110
  }
4058
4111
  async function checkoutRev(repoRoot, rev) {
4059
- const dest = await fs17.mkdtemp(path10.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
4112
+ const dest = await fs18.mkdtemp(path11.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
4060
4113
  const { stdout } = await execFileAsync(
4061
4114
  "git",
4062
4115
  ["-C", repoRoot, "archive", "--format=tar", rev],
@@ -4124,8 +4177,8 @@ async function reviewRevisions(repoRoot, base, head = "HEAD") {
4124
4177
  return diffGraphs(baseGraph, headGraph, base, head);
4125
4178
  } finally {
4126
4179
  await Promise.all([
4127
- fs17.rm(baseDir, { recursive: true, force: true }),
4128
- fs17.rm(headDir, { recursive: true, force: true })
4180
+ fs18.rm(baseDir, { recursive: true, force: true }),
4181
+ fs18.rm(headDir, { recursive: true, force: true })
4129
4182
  ]);
4130
4183
  }
4131
4184
  }
@@ -4217,9 +4270,9 @@ async function runTests(nodeQuery, options = {}) {
4217
4270
 
4218
4271
  // src/cli/commands/global.ts
4219
4272
  init_cjs_shims();
4220
- var fs18 = __toESM(require("fs/promises"), 1);
4273
+ var fs19 = __toESM(require("fs/promises"), 1);
4221
4274
  var os2 = __toESM(require("os"), 1);
4222
- var path11 = __toESM(require("path"), 1);
4275
+ var path12 = __toESM(require("path"), 1);
4223
4276
  init_graphStore();
4224
4277
 
4225
4278
  // src/merge.ts
@@ -4281,14 +4334,14 @@ function mergeGraphs(entries) {
4281
4334
 
4282
4335
  // src/cli/commands/global.ts
4283
4336
  function registryDir() {
4284
- return path11.join(os2.homedir(), ".graphify");
4337
+ return path12.join(os2.homedir(), ".graphify");
4285
4338
  }
4286
4339
  function registryPath() {
4287
- return path11.join(registryDir(), "global.json");
4340
+ return path12.join(registryDir(), "global.json");
4288
4341
  }
4289
4342
  async function readRegistry() {
4290
4343
  try {
4291
- const raw = await fs18.readFile(registryPath(), "utf-8");
4344
+ const raw = await fs19.readFile(registryPath(), "utf-8");
4292
4345
  const parsed = JSON.parse(raw);
4293
4346
  const projects = parsed.projects;
4294
4347
  if (!Array.isArray(projects)) return { projects: [] };
@@ -4298,23 +4351,23 @@ async function readRegistry() {
4298
4351
  }
4299
4352
  }
4300
4353
  async function writeRegistry(registry) {
4301
- await fs18.mkdir(registryDir(), { recursive: true });
4302
- await fs18.writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
4354
+ await fs19.mkdir(registryDir(), { recursive: true });
4355
+ await fs19.writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
4303
4356
  }
4304
4357
  async function projectNameFor(root) {
4305
4358
  try {
4306
- const raw = await fs18.readFile(path11.join(root, "package.json"), "utf-8");
4359
+ const raw = await fs19.readFile(path12.join(root, "package.json"), "utf-8");
4307
4360
  const name = JSON.parse(raw).name;
4308
4361
  if (typeof name === "string" && name.length > 0) return name;
4309
4362
  } catch {
4310
4363
  }
4311
- return path11.basename(root);
4364
+ return path12.basename(root);
4312
4365
  }
4313
4366
  async function runGlobalAdd(target = ".") {
4314
- const root = path11.resolve(target);
4315
- const graphJson = path11.join(root, "graphify-out", "graph.json");
4367
+ const root = path12.resolve(target);
4368
+ const graphJson = path12.join(root, "graphify-out", "graph.json");
4316
4369
  try {
4317
- await fs18.access(graphJson);
4370
+ await fs19.access(graphJson);
4318
4371
  } catch {
4319
4372
  throw new Error(`${root} has no graphify-out/graph.json \u2014 run \`graphify ${target}\` first, then add it.`);
4320
4373
  }
@@ -4385,9 +4438,9 @@ async function runGlobalBuild(options = {}) {
4385
4438
  throw new Error("No projects registered. Add some with `graphify global add <path>` first.");
4386
4439
  }
4387
4440
  const entries = await loadEntries(
4388
- registry.projects.map((p) => ({ name: p.name, outDir: path11.join(p.root, "graphify-out") }))
4441
+ registry.projects.map((p) => ({ name: p.name, outDir: path12.join(p.root, "graphify-out") }))
4389
4442
  );
4390
- await mergeAndExport(entries, path11.resolve(options.out ?? path11.join(registryDir(), "global-out")));
4443
+ await mergeAndExport(entries, path12.resolve(options.out ?? path12.join(registryDir(), "global-out")));
4391
4444
  }
4392
4445
  async function runMerge(targets, options = {}) {
4393
4446
  if (targets.length < 2) {
@@ -4395,8 +4448,8 @@ async function runMerge(targets, options = {}) {
4395
4448
  }
4396
4449
  const sources = [];
4397
4450
  for (const target of targets) {
4398
- const root = path11.resolve(target);
4399
- sources.push({ name: await projectNameFor(root), outDir: path11.join(root, "graphify-out") });
4451
+ const root = path12.resolve(target);
4452
+ sources.push({ name: await projectNameFor(root), outDir: path12.join(root, "graphify-out") });
4400
4453
  }
4401
4454
  const names = sources.map((s) => s.name);
4402
4455
  const duplicate = names.find((name, i) => names.indexOf(name) !== i);
@@ -4404,13 +4457,13 @@ async function runMerge(targets, options = {}) {
4404
4457
  throw new Error(`Two of the given projects resolve to the same name ("${duplicate}") \u2014 rename one.`);
4405
4458
  }
4406
4459
  const entries = await loadEntries(sources);
4407
- await mergeAndExport(entries, path11.resolve(options.out ?? "graphify-merged"));
4460
+ await mergeAndExport(entries, path12.resolve(options.out ?? "graphify-merged"));
4408
4461
  }
4409
4462
 
4410
4463
  // src/cli/commands/hook.ts
4411
4464
  init_cjs_shims();
4412
- var fs19 = __toESM(require("fs/promises"), 1);
4413
- var path12 = __toESM(require("path"), 1);
4465
+ var fs20 = __toESM(require("fs/promises"), 1);
4466
+ var path13 = __toESM(require("path"), 1);
4414
4467
  var MARKER_BEGIN = "# >>> graphify >>>";
4415
4468
  var MARKER_END = "# <<< graphify <<<";
4416
4469
  var HOOK_NAMES = ["post-commit", "post-merge"];
@@ -4421,34 +4474,34 @@ var HOOK_BLOCK = `${MARKER_BEGIN}
4421
4474
  (graphify . --update --no-viz >/dev/null 2>&1 &)
4422
4475
  ${MARKER_END}`;
4423
4476
  async function hooksDir(cwd) {
4424
- const gitDir = path12.join(cwd, ".git");
4477
+ const gitDir = path13.join(cwd, ".git");
4425
4478
  let stats;
4426
4479
  try {
4427
- stats = await fs19.stat(gitDir);
4480
+ stats = await fs20.stat(gitDir);
4428
4481
  } catch {
4429
4482
  throw new Error(`${cwd} is not a git repository (no .git directory) \u2014 run this from the repo root.`);
4430
4483
  }
4431
4484
  if (!stats.isDirectory()) {
4432
- const content = (await fs19.readFile(gitDir, "utf-8")).trim();
4485
+ const content = (await fs20.readFile(gitDir, "utf-8")).trim();
4433
4486
  const match = /^gitdir:\s*(.+)$/.exec(content);
4434
4487
  if (!match) throw new Error(`${gitDir} exists but is neither a directory nor a gitdir pointer.`);
4435
- return path12.resolve(cwd, match[1], "hooks");
4488
+ return path13.resolve(cwd, match[1], "hooks");
4436
4489
  }
4437
- return path12.join(gitDir, "hooks");
4490
+ return path13.join(gitDir, "hooks");
4438
4491
  }
4439
4492
  async function runHookInstall(cwd = process.cwd()) {
4440
4493
  const dir = await hooksDir(cwd);
4441
- await fs19.mkdir(dir, { recursive: true });
4494
+ await fs20.mkdir(dir, { recursive: true });
4442
4495
  const results = [];
4443
4496
  for (const hook of HOOK_NAMES) {
4444
- const hookPath = path12.join(dir, hook);
4497
+ const hookPath = path13.join(dir, hook);
4445
4498
  let existing = null;
4446
4499
  try {
4447
- existing = await fs19.readFile(hookPath, "utf-8");
4500
+ existing = await fs20.readFile(hookPath, "utf-8");
4448
4501
  } catch {
4449
4502
  }
4450
4503
  if (existing === null) {
4451
- await fs19.writeFile(hookPath, `#!/bin/sh
4504
+ await fs20.writeFile(hookPath, `#!/bin/sh
4452
4505
  ${HOOK_BLOCK}
4453
4506
  `, { mode: 493 });
4454
4507
  results.push({ hook, path: hookPath, action: "created" });
@@ -4456,9 +4509,9 @@ ${HOOK_BLOCK}
4456
4509
  results.push({ hook, path: hookPath, action: "already-installed" });
4457
4510
  } else {
4458
4511
  const separator = existing.endsWith("\n") ? "" : "\n";
4459
- await fs19.writeFile(hookPath, `${existing}${separator}${HOOK_BLOCK}
4512
+ await fs20.writeFile(hookPath, `${existing}${separator}${HOOK_BLOCK}
4460
4513
  `, "utf-8");
4461
- await fs19.chmod(hookPath, 493);
4514
+ await fs20.chmod(hookPath, 493);
4462
4515
  results.push({ hook, path: hookPath, action: "appended" });
4463
4516
  }
4464
4517
  }
@@ -4474,10 +4527,10 @@ async function runHookUninstall(cwd = process.cwd()) {
4474
4527
  const dir = await hooksDir(cwd);
4475
4528
  const results = [];
4476
4529
  for (const hook of HOOK_NAMES) {
4477
- const hookPath = path12.join(dir, hook);
4530
+ const hookPath = path13.join(dir, hook);
4478
4531
  let existing = null;
4479
4532
  try {
4480
- existing = await fs19.readFile(hookPath, "utf-8");
4533
+ existing = await fs20.readFile(hookPath, "utf-8");
4481
4534
  } catch {
4482
4535
  }
4483
4536
  if (existing === null || !existing.includes(MARKER_BEGIN)) {
@@ -4487,9 +4540,9 @@ async function runHookUninstall(cwd = process.cwd()) {
4487
4540
  const blockPattern = new RegExp(`\\n?${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}\\n?`);
4488
4541
  const remaining = existing.replace(blockPattern, "\n").trim();
4489
4542
  if (remaining === "" || remaining === "#!/bin/sh") {
4490
- await fs19.unlink(hookPath);
4543
+ await fs20.unlink(hookPath);
4491
4544
  } else {
4492
- await fs19.writeFile(hookPath, `${remaining}
4545
+ await fs20.writeFile(hookPath, `${remaining}
4493
4546
  `, "utf-8");
4494
4547
  }
4495
4548
  results.push({ hook, path: hookPath, action: "removed" });
@@ -4503,8 +4556,8 @@ async function runHookUninstall(cwd = process.cwd()) {
4503
4556
  // src/memory.ts
4504
4557
  init_cjs_shims();
4505
4558
  var import_node_crypto4 = require("crypto");
4506
- var fs20 = __toESM(require("fs/promises"), 1);
4507
- var path13 = __toESM(require("path"), 1);
4559
+ var fs21 = __toESM(require("fs/promises"), 1);
4560
+ var path14 = __toESM(require("path"), 1);
4508
4561
  var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
4509
4562
  var TYPES = /* @__PURE__ */ new Set(["query", "path", "explain", "affected"]);
4510
4563
  function validateResult(value) {
@@ -4522,25 +4575,25 @@ function validateResult(value) {
4522
4575
  }
4523
4576
  async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
4524
4577
  validateResult(entry);
4525
- await fs20.mkdir(memoryDir, { recursive: true });
4578
+ await fs21.mkdir(memoryDir, { recursive: true });
4526
4579
  const saved = { ...entry, savedAt: now.toISOString() };
4527
4580
  const hash = (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
4528
4581
  const stamp = saved.savedAt.replace(/[:.]/g, "-");
4529
- const filePath = path13.join(memoryDir, `result-${stamp}-${hash}.json`);
4530
- await fs20.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
4582
+ const filePath = path14.join(memoryDir, `result-${stamp}-${hash}.json`);
4583
+ await fs21.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
4531
4584
  return filePath;
4532
4585
  }
4533
4586
  async function loadResults(memoryDir) {
4534
4587
  let files;
4535
4588
  try {
4536
- files = (await fs20.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
4589
+ files = (await fs21.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
4537
4590
  } catch {
4538
4591
  return [];
4539
4592
  }
4540
4593
  const results = [];
4541
4594
  for (const file of files.sort((a, b) => a.localeCompare(b))) {
4542
4595
  try {
4543
- const parsed = JSON.parse(await fs20.readFile(path13.join(memoryDir, file), "utf-8"));
4596
+ const parsed = JSON.parse(await fs21.readFile(path14.join(memoryDir, file), "utf-8"));
4544
4597
  if (parsed.question && parsed.savedAt) results.push(parsed);
4545
4598
  } catch {
4546
4599
  }
@@ -4617,18 +4670,18 @@ function renderLessons(results, options = {}) {
4617
4670
  // src/cli/commands/install.ts
4618
4671
  init_cjs_shims();
4619
4672
  var import_node_module3 = require("module");
4620
- var fs21 = __toESM(require("fs/promises"), 1);
4621
- var path14 = __toESM(require("path"), 1);
4673
+ var fs22 = __toESM(require("fs/promises"), 1);
4674
+ var path15 = __toESM(require("path"), 1);
4622
4675
  var require4 = (0, import_node_module3.createRequire)(importMetaUrl);
4623
4676
  function packageRoot() {
4624
4677
  const packageJsonPath = require4.resolve("@dreamtree-org/graphify/package.json");
4625
- return path14.dirname(packageJsonPath);
4678
+ return path15.dirname(packageJsonPath);
4626
4679
  }
4627
4680
  var MD_MARKER_BEGIN = "<!-- >>> graphify >>> -->";
4628
4681
  var MD_MARKER_END = "<!-- <<< graphify <<< -->";
4629
4682
  async function writeOwnedFile(filePath, content) {
4630
- await fs21.mkdir(path14.dirname(filePath), { recursive: true });
4631
- await fs21.writeFile(filePath, content, "utf-8");
4683
+ await fs22.mkdir(path15.dirname(filePath), { recursive: true });
4684
+ await fs22.writeFile(filePath, content, "utf-8");
4632
4685
  }
4633
4686
  async function upsertMarkedBlock(filePath, content) {
4634
4687
  const block = `${MD_MARKER_BEGIN}
@@ -4636,32 +4689,32 @@ ${content.trim()}
4636
4689
  ${MD_MARKER_END}`;
4637
4690
  let existing = null;
4638
4691
  try {
4639
- existing = await fs21.readFile(filePath, "utf-8");
4692
+ existing = await fs22.readFile(filePath, "utf-8");
4640
4693
  } catch {
4641
4694
  }
4642
4695
  if (existing === null) {
4643
- await fs21.mkdir(path14.dirname(filePath), { recursive: true });
4644
- await fs21.writeFile(filePath, `${block}
4696
+ await fs22.mkdir(path15.dirname(filePath), { recursive: true });
4697
+ await fs22.writeFile(filePath, `${block}
4645
4698
  `, "utf-8");
4646
4699
  return;
4647
4700
  }
4648
4701
  if (existing.includes(MD_MARKER_BEGIN)) {
4649
4702
  const pattern = new RegExp(`${MD_MARKER_BEGIN}[\\s\\S]*?${MD_MARKER_END}`);
4650
- await fs21.writeFile(filePath, existing.replace(pattern, block), "utf-8");
4703
+ await fs22.writeFile(filePath, existing.replace(pattern, block), "utf-8");
4651
4704
  return;
4652
4705
  }
4653
4706
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4654
- await fs21.writeFile(filePath, `${existing}${separator}${block}
4707
+ await fs22.writeFile(filePath, `${existing}${separator}${block}
4655
4708
  `, "utf-8");
4656
4709
  }
4657
4710
  var PLATFORMS = {
4658
4711
  claude: async (cwd, content) => {
4659
- const target = path14.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
4712
+ const target = path15.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
4660
4713
  await writeOwnedFile(target, content);
4661
4714
  return { host: "Claude Code", path: target };
4662
4715
  },
4663
4716
  cursor: async (cwd, content) => {
4664
- const target = path14.join(cwd, ".cursor", "rules", "graphify.mdc");
4717
+ const target = path15.join(cwd, ".cursor", "rules", "graphify.mdc");
4665
4718
  const body = content.replace(/^---[\s\S]*?---\n/, "");
4666
4719
  await writeOwnedFile(
4667
4720
  target,
@@ -4674,30 +4727,30 @@ ${body}`
4674
4727
  return { host: "Cursor", path: target };
4675
4728
  },
4676
4729
  windsurf: async (cwd, content) => {
4677
- const target = path14.join(cwd, ".windsurf", "rules", "graphify.md");
4730
+ const target = path15.join(cwd, ".windsurf", "rules", "graphify.md");
4678
4731
  await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
4679
4732
  return { host: "Windsurf", path: target };
4680
4733
  },
4681
4734
  cline: async (cwd, content) => {
4682
- const target = path14.join(cwd, ".clinerules", "graphify.md");
4735
+ const target = path15.join(cwd, ".clinerules", "graphify.md");
4683
4736
  await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
4684
4737
  return { host: "Cline", path: target };
4685
4738
  },
4686
4739
  agents: async (cwd, content) => {
4687
- const target = path14.join(cwd, "AGENTS.md");
4740
+ const target = path15.join(cwd, "AGENTS.md");
4688
4741
  await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
4689
4742
  return { host: "AGENTS.md agents (Codex, opencode, Amp, ...)", path: target };
4690
4743
  },
4691
4744
  gemini: async (cwd, content) => {
4692
- const target = path14.join(cwd, "GEMINI.md");
4745
+ const target = path15.join(cwd, "GEMINI.md");
4693
4746
  await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
4694
4747
  return { host: "Gemini CLI", path: target };
4695
4748
  }
4696
4749
  };
4697
4750
  var SUPPORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => a.localeCompare(b));
4698
4751
  async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
4699
- const sourcePath = path14.join(packageRoot(), "src", "skill", "SKILL.md");
4700
- const content = await fs21.readFile(sourcePath, "utf-8");
4752
+ const sourcePath = path15.join(packageRoot(), "src", "skill", "SKILL.md");
4753
+ const content = await fs22.readFile(sourcePath, "utf-8");
4701
4754
  const requested = platforms.includes("all") ? SUPPORTED_PLATFORMS : platforms;
4702
4755
  const results = [];
4703
4756
  for (const platform of requested) {
@@ -4781,7 +4834,7 @@ function looksLikeGitUrl(value) {
4781
4834
  }
4782
4835
  async function cloneRepo(url) {
4783
4836
  const validated = validateUrl(url);
4784
- const dest = await fs23.mkdtemp(path16.join(os3.tmpdir(), "graphify-clone-"));
4837
+ const dest = await fs24.mkdtemp(path17.join(os3.tmpdir(), "graphify-clone-"));
4785
4838
  await execFileAsync3("git", ["clone", "--depth", "1", validated, dest]);
4786
4839
  return dest;
4787
4840
  }
@@ -4795,7 +4848,7 @@ function clusterOptionsFrom(options) {
4795
4848
  };
4796
4849
  }
4797
4850
  async function runClusterOnly(root, options) {
4798
- const outDir = path16.join(root, "graphify-out");
4851
+ const outDir = path17.join(root, "graphify-out");
4799
4852
  const graph = await loadGraph(outDir);
4800
4853
  cluster(graph, clusterOptionsFrom(options));
4801
4854
  const analysis = analyze(graph);
@@ -4829,9 +4882,9 @@ var program = new import_commander.Command();
4829
4882
  program.name("graphify").description("Turn a folder of code/docs/papers into a queryable knowledge graph.").argument("[path]", "path to scan, or a GitHub URL", ".").option("--mode <mode>", "extraction mode (deep for richer INFERRED edges \u2014 reserved, no-op in v1)").option("--update", "incremental rebuild \u2014 reuse cached extractions for unchanged files").option("--watch", "rebuild on file change").option("--cluster-only", "rerun clustering on an existing graph").option("--leiden", "use Leiden instead of Louvain for community detection (v2 \u2014 better community quality)").option("--max-iterations <n>", "Leiden only: cap on outer iterations for very large graphs", Number).option("--no-viz", "skip graph.html").option("--svg", "also export graph.svg (not implemented in v1)").option("--graphml", "also export graph.graphml (not implemented in v1)").option("--neo4j", "generate graphify-out/cypher.txt for Neo4j (not implemented in v1)").option("--mcp", "start MCP stdio server instead of running the pipeline").option("--mysql <dsn>", "also extract a MySQL schema (mysql://user:pass@host:port/db) into the graph").action(async (targetArg, options) => {
4830
4883
  try {
4831
4884
  if (options.mcp) {
4832
- const outDir = path16.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
4885
+ const outDir = path17.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
4833
4886
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
4834
- await startServer2(path16.join(outDir, "graph.json"));
4887
+ await startServer2(path17.join(outDir, "graph.json"));
4835
4888
  return;
4836
4889
  }
4837
4890
  let root = targetArg;
@@ -4839,7 +4892,7 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
4839
4892
  console.error(`Cloning ${targetArg} ...`);
4840
4893
  root = await cloneRepo(targetArg);
4841
4894
  }
4842
- root = path16.resolve(root);
4895
+ root = path17.resolve(root);
4843
4896
  if (options.mode) {
4844
4897
  console.error(`--mode ${options.mode} is reserved for a future richer-INFERRED-edges pass \u2014 currently a no-op.`);
4845
4898
  }
@@ -4993,7 +5046,7 @@ program.command("merge <dirs...>").description("one-shot merge of two or more bu
4993
5046
  });
4994
5047
  program.command("save-result").description("save a Q&A result to graphify-out/memory/ for the graph feedback loop").requiredOption("--question <q>", "the question that was asked").requiredOption("--answer <a>", "the answer that was given").option("--nodes <ids...>", "node ids/labels cited in the answer", []).option("--type <t>", "query|path|explain|affected", "query").option("--outcome <o>", "useful|dead_end|corrected", "useful").option("--correction <text>", "what the right answer was (pairs with --outcome corrected)").action(async (options) => {
4995
5048
  try {
4996
- const memoryDir = path16.join(process.cwd(), "graphify-out", "memory");
5049
+ const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
4997
5050
  const file = await saveResult(
4998
5051
  {
4999
5052
  question: options.question,
@@ -5013,12 +5066,12 @@ program.command("save-result").description("save a Q&A result to graphify-out/me
5013
5066
  });
5014
5067
  program.command("reflect").description("aggregate graphify-out/memory/ into a recency-weighted lessons doc").option("--half-life-days <n>", "a result loses half its weight every N days (default 30)", Number).option("--out <file>", "output path (default graphify-out/reflections/LESSONS.md)").action(async (options) => {
5015
5068
  try {
5016
- const memoryDir = path16.join(process.cwd(), "graphify-out", "memory");
5069
+ const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
5017
5070
  const results = await loadResults(memoryDir);
5018
5071
  const lessons = renderLessons(results, { halfLifeDays: options.halfLifeDays });
5019
- const outPath = path16.resolve(options.out ?? path16.join(process.cwd(), "graphify-out", "reflections", "LESSONS.md"));
5020
- await fs23.mkdir(path16.dirname(outPath), { recursive: true });
5021
- await fs23.writeFile(outPath, lessons, "utf-8");
5072
+ const outPath = path17.resolve(options.out ?? path17.join(process.cwd(), "graphify-out", "reflections", "LESSONS.md"));
5073
+ await fs24.mkdir(path17.dirname(outPath), { recursive: true });
5074
+ await fs24.writeFile(outPath, lessons, "utf-8");
5022
5075
  console.log(`Reflected ${results.length} result(s) -> ${outPath}`);
5023
5076
  } catch (error) {
5024
5077
  console.error(`graphify reflect: ${error.message}`);