@dreamtree-org/graphify 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,8 +16,12 @@ the right files. A graph fixes retrieval, but node lists alone still leave the a
16
16
  chase files. graphify closes the loop: its answers are **task-shaped** — actual code
17
17
  snippets, test lists, impact checklists, diffs — not just structure.
18
18
 
19
- Measured on this repository itself (`graphify benchmark`): answering through the graph uses
20
- **~8x fewer tokens** than reading the files each answer touches.
19
+ Token savings depend on the shape of your repo: graph answers have a roughly fixed cost
20
+ (they fill toward their token budget), so the win grows with how much code a naive file
21
+ read would have pulled in. Measured with `graphify benchmark`: **up to ~8x** on this
22
+ repository (~100 files, deep cross-file structure), closer to **~1.5x** on small,
23
+ tightly-organized libraries — and on a trivial question in a tiny repo, reading the file
24
+ directly can be cheaper. Run `graphify benchmark` on your own repo before quoting a number.
21
25
 
22
26
  ## Install
23
27
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  affectedBy,
3
3
  testsForNode
4
- } from "./chunk-YT7B6DOD.js";
4
+ } from "./chunk-ZK3TA6FW.js";
5
5
  import {
6
6
  escapeHtml,
7
7
  sanitizeLabel,
@@ -1535,10 +1535,10 @@ async function extractRust(filePath, displayPath) {
1535
1535
  }
1536
1536
  }
1537
1537
  } else if (fn.type === "scoped_identifier") {
1538
- const path10 = fn.childForFieldName("path")?.text;
1538
+ const path11 = fn.childForFieldName("path")?.text;
1539
1539
  const name = fn.childForFieldName("name")?.text;
1540
- if (path10 && name) {
1541
- const viaImport = importIndex.get(path10);
1540
+ if (path11 && name) {
1541
+ const viaImport = importIndex.get(path11);
1542
1542
  if (viaImport) {
1543
1543
  targetId = viaImport.id;
1544
1544
  confidence = "INFERRED";
@@ -1609,13 +1609,11 @@ async function extractTypeScript(filePath, displayPath) {
1609
1609
  });
1610
1610
  }
1611
1611
  function importTargetId(binding, property) {
1612
- if (!binding.ref.external) {
1613
- if (binding.kind === "named" && binding.importedName) {
1614
- return entityId(binding.ref.id, binding.importedName);
1615
- }
1616
- if (binding.kind === "namespace" && property) {
1617
- return entityId(binding.ref.id, property);
1618
- }
1612
+ if (binding.kind === "named" && binding.importedName) {
1613
+ return entityId(binding.ref.id, binding.importedName);
1614
+ }
1615
+ if (binding.kind === "namespace" && property) {
1616
+ return entityId(binding.ref.id, property);
1619
1617
  }
1620
1618
  return binding.ref.id;
1621
1619
  }
@@ -1653,24 +1651,35 @@ async function extractTypeScript(filePath, displayPath) {
1653
1651
  }
1654
1652
  }
1655
1653
  }
1654
+ function unwrapExpression(node) {
1655
+ let current = node;
1656
+ while (current.type === "as_expression" || current.type === "satisfies_expression" || current.type === "non_null_expression" || current.type === "parenthesized_expression") {
1657
+ const inner = namedChildren(current)[0];
1658
+ if (!inner) break;
1659
+ current = inner;
1660
+ }
1661
+ return current;
1662
+ }
1656
1663
  function handleTopLevelVariableFunctions(node) {
1664
+ const isExported = node.parent?.type === "export_statement";
1657
1665
  for (const declarator of namedChildren(node)) {
1658
1666
  if (declarator.type !== "variable_declarator") continue;
1659
1667
  const value = declarator.childForFieldName("value");
1660
1668
  if (!value) continue;
1661
- if (value.type === "arrow_function" || value.type === "function_expression") {
1669
+ const core = unwrapExpression(value);
1670
+ if (core.type === "arrow_function" || core.type === "function_expression") {
1662
1671
  const name = declarator.childForFieldName("name")?.text;
1663
1672
  if (!name) continue;
1664
1673
  const id = entityId(sourceFile, name);
1665
1674
  builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
1666
1675
  builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1667
1676
  nameIndex.set(name, id);
1668
- functionNodeMap.set(value.id, id);
1677
+ functionNodeMap.set(core.id, id);
1669
1678
  continue;
1670
1679
  }
1671
- if (value.type === "call_expression") {
1672
- const callee = value.childForFieldName("function");
1673
- const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(value) : null;
1680
+ if (core.type === "call_expression") {
1681
+ const callee = core.childForFieldName("function");
1682
+ const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(core) : null;
1674
1683
  if (specifier) {
1675
1684
  const nameNode = declarator.childForFieldName("name");
1676
1685
  if (nameNode) {
@@ -1678,6 +1687,21 @@ async function extractTypeScript(filePath, displayPath) {
1678
1687
  addModuleNode(ref);
1679
1688
  registerRequireBinding(nameNode, ref);
1680
1689
  }
1690
+ continue;
1691
+ }
1692
+ }
1693
+ if (isExported) {
1694
+ const name = declarator.childForFieldName("name")?.text;
1695
+ if (!name) continue;
1696
+ const id = entityId(sourceFile, name);
1697
+ builder.addNode({ id, label: `const ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
1698
+ builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
1699
+ nameIndex.set(name, id);
1700
+ if (core.type === "identifier") {
1701
+ const aliased = nameIndex.get(core.text);
1702
+ if (aliased !== void 0 && aliased !== id) {
1703
+ builder.addEdge({ source: id, target: aliased, relation: "references", confidence: "EXTRACTED" });
1704
+ }
1681
1705
  }
1682
1706
  }
1683
1707
  }
@@ -2017,6 +2041,8 @@ function buildGraph(extractions) {
2017
2041
  }
2018
2042
 
2019
2043
  // src/resolve.ts
2044
+ import * as fs9 from "fs/promises";
2045
+ import * as path5 from "path";
2020
2046
  var CODE_EXTENSIONS2 = [
2021
2047
  ".ts",
2022
2048
  ".tsx",
@@ -2036,6 +2062,29 @@ var CODE_EXTENSIONS2 = [
2036
2062
  function isOpaque(id) {
2037
2063
  return id.startsWith("module:") || id.startsWith("external:") || id.startsWith("db:");
2038
2064
  }
2065
+ var SELF_IMPORT_STEMS = (subpath) => subpath === "" ? ["index", "src/index", "lib/index", "source/index", "src/main"] : [
2066
+ subpath,
2067
+ `src/${subpath}`,
2068
+ `lib/${subpath}`,
2069
+ `source/${subpath}`,
2070
+ `${subpath}/index`,
2071
+ `src/${subpath}/index`,
2072
+ `lib/${subpath}/index`
2073
+ ];
2074
+ function selfImportCandidates(spec, selfNames) {
2075
+ const selfName = selfNames.find((n) => spec === n || spec.startsWith(`${n}/`));
2076
+ if (selfName === void 0) return null;
2077
+ const subpath = spec === selfName ? "" : spec.slice(selfName.length + 1);
2078
+ return SELF_IMPORT_STEMS(subpath).flatMap((stem) => CODE_EXTENSIONS2.map((ext) => `${stem}${ext}`));
2079
+ }
2080
+ async function readSelfNames(root) {
2081
+ try {
2082
+ const pkg = JSON.parse(await fs9.readFile(path5.join(root, "package.json"), "utf-8"));
2083
+ return typeof pkg.name === "string" && pkg.name.length > 0 ? [pkg.name] : [];
2084
+ } catch {
2085
+ return [];
2086
+ }
2087
+ }
2039
2088
  function stripKnownExtension(p) {
2040
2089
  for (const ext of CODE_EXTENSIONS2) {
2041
2090
  if (p.endsWith(ext)) return p.slice(0, -ext.length);
@@ -2052,19 +2101,23 @@ function candidatePaths(guessed) {
2052
2101
  }
2053
2102
  return candidates.filter((c) => c !== guessed);
2054
2103
  }
2055
- function uniqueMatch(guessed, candidates, realIds) {
2104
+ function uniqueMatch(candidates, real) {
2056
2105
  const matches = /* @__PURE__ */ new Set();
2057
2106
  for (const candidate of candidates) {
2058
- if (realIds.has(candidate)) matches.add(candidate);
2107
+ if (real.has(candidate)) matches.add(candidate);
2059
2108
  }
2060
- if (matches.size !== 1) return null;
2061
- return [...matches][0];
2109
+ return matches.size === 1 ? [...matches][0] : null;
2062
2110
  }
2063
- function resolveCrossFileReferences(extractions) {
2111
+ function resolveCrossFileReferences(extractions, options = {}) {
2112
+ const selfNames = options.selfNames ?? [];
2064
2113
  const realIds = /* @__PURE__ */ new Set();
2065
2114
  const realFiles = /* @__PURE__ */ new Set();
2066
2115
  for (const extraction of extractions) {
2067
2116
  for (const node of extraction.nodes) realIds.add(node.id);
2117
+ const first = extraction.nodes[0];
2118
+ if (first && first.id === first.sourceFile && !isOpaque(first.id)) {
2119
+ realFiles.add(first.id);
2120
+ }
2068
2121
  for (const node of extraction.nodes) {
2069
2122
  const sep2 = node.id.indexOf("::");
2070
2123
  if (sep2 > 0) realFiles.add(node.id.slice(0, sep2));
@@ -2074,7 +2127,24 @@ function resolveCrossFileReferences(extractions) {
2074
2127
  }
2075
2128
  }
2076
2129
  const remap = /* @__PURE__ */ new Map();
2077
- const resolveId = (id) => {
2130
+ let resolvedEndpoints = 0;
2131
+ const applyRemap = (resolveId) => {
2132
+ for (const extraction of extractions) {
2133
+ for (const edge of extraction.edges) {
2134
+ const source = resolveId(edge.source);
2135
+ if (source !== null) {
2136
+ edge.source = source;
2137
+ resolvedEndpoints++;
2138
+ }
2139
+ const target = resolveId(edge.target);
2140
+ if (target !== null) {
2141
+ edge.target = target;
2142
+ resolvedEndpoints++;
2143
+ }
2144
+ }
2145
+ }
2146
+ };
2147
+ const resolvePathId = (id) => {
2078
2148
  if (isOpaque(id)) return null;
2079
2149
  const cached = remap.get(id);
2080
2150
  if (cached !== void 0) return cached;
@@ -2084,30 +2154,77 @@ function resolveCrossFileReferences(extractions) {
2084
2154
  if (realIds.has(id)) return null;
2085
2155
  const guessedPath = id.slice(0, sep2);
2086
2156
  const name = id.slice(sep2);
2087
- const candidates = candidatePaths(guessedPath).map((p) => `${p}${name}`);
2088
- resolved = uniqueMatch(id, candidates, realIds);
2157
+ resolved = uniqueMatch(candidatePaths(guessedPath).map((p) => `${p}${name}`), realIds);
2089
2158
  } else {
2090
2159
  if (realFiles.has(id)) return null;
2091
- resolved = uniqueMatch(id, candidatePaths(id), realFiles);
2160
+ resolved = uniqueMatch(candidatePaths(id), realFiles);
2092
2161
  }
2093
2162
  if (resolved !== null) remap.set(id, resolved);
2094
2163
  return resolved;
2095
2164
  };
2096
- let resolvedEndpoints = 0;
2165
+ applyRemap(resolvePathId);
2166
+ const namedAlias = /* @__PURE__ */ new Map();
2167
+ const starTargets = /* @__PURE__ */ new Map();
2097
2168
  for (const extraction of extractions) {
2098
2169
  for (const edge of extraction.edges) {
2099
- const source = resolveId(edge.source);
2100
- if (source !== null) {
2101
- edge.source = source;
2102
- resolvedEndpoints++;
2103
- }
2104
- const target = resolveId(edge.target);
2105
- if (target !== null) {
2106
- edge.target = target;
2107
- resolvedEndpoints++;
2170
+ if (edge.relation !== "re_exports" || !realFiles.has(edge.source)) continue;
2171
+ const sep2 = edge.target.lastIndexOf("::");
2172
+ if (sep2 > 0 && realIds.has(edge.target)) {
2173
+ namedAlias.set(`${edge.source}|${edge.target.slice(sep2 + 2)}`, edge.target);
2174
+ } else if (sep2 < 0 && realFiles.has(edge.target)) {
2175
+ const targets = starTargets.get(edge.source) ?? [];
2176
+ targets.push(edge.target);
2177
+ starTargets.set(edge.source, targets);
2108
2178
  }
2109
2179
  }
2110
2180
  }
2181
+ const throughBarrel = (file, name) => {
2182
+ const direct = `${file}::${name}`;
2183
+ if (realIds.has(direct)) return direct;
2184
+ const aliased = namedAlias.get(`${file}|${name}`);
2185
+ if (aliased !== void 0) return aliased;
2186
+ const viaStar = (starTargets.get(file) ?? []).map((sf) => `${sf}::${name}`).filter((id) => realIds.has(id));
2187
+ return viaStar.length === 1 ? viaStar[0] : null;
2188
+ };
2189
+ const resolveBarrelId = (id) => {
2190
+ const cached = remap.get(id);
2191
+ if (cached !== void 0) return cached;
2192
+ if (id.startsWith("module:")) {
2193
+ const spec = id.slice("module:".length);
2194
+ const sep3 = spec.indexOf("::");
2195
+ const moduleSpec = sep3 > 0 ? spec.slice(0, sep3) : spec;
2196
+ const name = sep3 > 0 ? spec.slice(sep3 + 2) : null;
2197
+ const candidates = selfImportCandidates(moduleSpec, selfNames);
2198
+ if (candidates === null) {
2199
+ if (name === null) return null;
2200
+ remap.set(id, `module:${moduleSpec}`);
2201
+ return `module:${moduleSpec}`;
2202
+ }
2203
+ const file = uniqueMatch(candidates, realFiles);
2204
+ let resolved = null;
2205
+ if (file !== null) {
2206
+ resolved = name !== null ? throughBarrel(file, name) ?? file : file;
2207
+ } else if (name !== null) {
2208
+ resolved = `module:${moduleSpec}`;
2209
+ }
2210
+ if (resolved !== null) remap.set(id, resolved);
2211
+ return resolved;
2212
+ }
2213
+ const sep2 = id.indexOf("::");
2214
+ if (sep2 > 0 && !realIds.has(id)) {
2215
+ const guessedPath = id.slice(0, sep2);
2216
+ const name = id.slice(sep2 + 2);
2217
+ const files = [guessedPath, ...candidatePaths(guessedPath)].filter((f) => realFiles.has(f));
2218
+ const resolvedSet = new Set(files.map((f) => throughBarrel(f, name)).filter((r) => r !== null));
2219
+ if (resolvedSet.size === 1) {
2220
+ const resolved = [...resolvedSet][0];
2221
+ remap.set(id, resolved);
2222
+ return resolved;
2223
+ }
2224
+ }
2225
+ return null;
2226
+ };
2227
+ applyRemap(resolveBarrelId);
2111
2228
  let droppedPlaceholders = 0;
2112
2229
  for (const extraction of extractions) {
2113
2230
  const before = extraction.nodes.length;
@@ -2383,15 +2500,15 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
2383
2500
 
2384
2501
  // src/export.ts
2385
2502
  import { createRequire as createRequire2 } from "module";
2386
- import * as fs9 from "fs/promises";
2387
- import * as path5 from "path";
2503
+ import * as fs10 from "fs/promises";
2504
+ import * as path6 from "path";
2388
2505
  var require3 = createRequire2(import.meta.url);
2389
2506
  function resolveVisNetworkAssets() {
2390
2507
  const packageJsonPath = require3.resolve("vis-network/package.json");
2391
- const root = path5.dirname(packageJsonPath);
2508
+ const root = path6.dirname(packageJsonPath);
2392
2509
  return {
2393
- jsPath: path5.join(root, "standalone", "umd", "vis-network.min.js"),
2394
- cssPath: path5.join(root, "styles", "vis-network.min.css")
2510
+ jsPath: path6.join(root, "standalone", "umd", "vis-network.min.js"),
2511
+ cssPath: path6.join(root, "styles", "vis-network.min.css")
2395
2512
  };
2396
2513
  }
2397
2514
  var COMMUNITY_PALETTE = [
@@ -2450,8 +2567,8 @@ community: ${communityLabel}` : ""}`,
2450
2567
  async function renderHtml(graph) {
2451
2568
  const { jsPath, cssPath } = resolveVisNetworkAssets();
2452
2569
  const [visJs, visCss] = await Promise.all([
2453
- fs9.readFile(jsPath, "utf-8"),
2454
- fs9.readFile(cssPath, "utf-8")
2570
+ fs10.readFile(jsPath, "utf-8"),
2571
+ fs10.readFile(cssPath, "utf-8")
2455
2572
  ]);
2456
2573
  const { nodes, edges } = buildVisNodesAndEdges(graph);
2457
2574
  const dataJson = safeInlineJson({ nodes, edges });
@@ -2491,17 +2608,17 @@ ${visJs}
2491
2608
  `;
2492
2609
  }
2493
2610
  async function exportGraph(graph, options, report) {
2494
- const outDir = path5.resolve(options.outDir);
2495
- await fs9.mkdir(outDir, { recursive: true });
2611
+ const outDir = path6.resolve(options.outDir);
2612
+ await fs10.mkdir(outDir, { recursive: true });
2496
2613
  const jsonPath = validateGraphPath("graph.json", outDir);
2497
- await fs9.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2614
+ await fs10.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2498
2615
  if (options.html !== false) {
2499
2616
  const htmlPath = validateGraphPath("graph.html", outDir);
2500
- await fs9.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2617
+ await fs10.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2501
2618
  }
2502
2619
  if (report !== void 0) {
2503
2620
  const reportPath = validateGraphPath("GRAPH_REPORT.md", outDir);
2504
- await fs9.writeFile(reportPath, report, "utf-8");
2621
+ await fs10.writeFile(reportPath, report, "utf-8");
2505
2622
  }
2506
2623
  const notImplemented = [];
2507
2624
  if (options.svg) notImplemented.push("--svg");
@@ -2515,19 +2632,19 @@ async function exportGraph(graph, options, report) {
2515
2632
 
2516
2633
  // src/extractionCache.ts
2517
2634
  import { createHash as createHash2 } from "crypto";
2518
- import * as fs10 from "fs/promises";
2519
- import * as path6 from "path";
2635
+ import * as fs11 from "fs/promises";
2636
+ import * as path7 from "path";
2520
2637
  var ExtractionCache = class {
2521
2638
  dir;
2522
2639
  constructor(outDir) {
2523
- this.dir = path6.join(outDir, "cache");
2640
+ this.dir = path7.join(outDir, "cache");
2524
2641
  }
2525
2642
  key(relFile, content) {
2526
2643
  return createHash2("sha256").update(relFile).update("\0").update(content).digest("hex");
2527
2644
  }
2528
2645
  async get(key) {
2529
2646
  try {
2530
- const raw = await fs10.readFile(path6.join(this.dir, `${key}.json`), "utf-8");
2647
+ const raw = await fs11.readFile(path7.join(this.dir, `${key}.json`), "utf-8");
2531
2648
  const parsed = JSON.parse(raw);
2532
2649
  if (!Array.isArray(parsed.nodes) || !Array.isArray(parsed.edges)) return null;
2533
2650
  return parsed;
@@ -2536,32 +2653,32 @@ var ExtractionCache = class {
2536
2653
  }
2537
2654
  }
2538
2655
  async put(key, extraction) {
2539
- await fs10.mkdir(this.dir, { recursive: true });
2540
- await fs10.writeFile(path6.join(this.dir, `${key}.json`), JSON.stringify(extraction), "utf-8");
2656
+ await fs11.mkdir(this.dir, { recursive: true });
2657
+ await fs11.writeFile(path7.join(this.dir, `${key}.json`), JSON.stringify(extraction), "utf-8");
2541
2658
  }
2542
2659
  };
2543
2660
 
2544
2661
  // src/pipeline.ts
2545
- import * as fs11 from "fs/promises";
2546
- import * as path7 from "path";
2662
+ import * as fs12 from "fs/promises";
2663
+ import * as path8 from "path";
2547
2664
  async function runPipeline(root, options = {}) {
2548
2665
  const progress = options.onProgress ?? (() => {
2549
2666
  });
2550
- const resolvedRoot = path7.resolve(root);
2667
+ const resolvedRoot = path8.resolve(root);
2551
2668
  progress(`Scanning ${resolvedRoot} ...`);
2552
2669
  const manifest = collectFiles(resolvedRoot);
2553
2670
  progress(
2554
2671
  `Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
2555
2672
  );
2556
- const outDir = options.outDir ?? path7.join(resolvedRoot, "graphify-out");
2673
+ const outDir = options.outDir ?? path8.join(resolvedRoot, "graphify-out");
2557
2674
  const cache = new ExtractionCache(outDir);
2558
2675
  progress("Extracting...");
2559
2676
  const extractions = [];
2560
2677
  let cacheHits = 0;
2561
2678
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
2562
- const filePath = path7.relative(process.cwd(), path7.join(resolvedRoot, relFile)) || relFile;
2679
+ const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
2563
2680
  try {
2564
- const key = cache.key(relFile, await fs11.readFile(path7.join(resolvedRoot, relFile)));
2681
+ const key = cache.key(relFile, await fs12.readFile(path8.join(resolvedRoot, relFile)));
2565
2682
  let extraction = options.update ? await cache.get(key) : null;
2566
2683
  if (extraction) {
2567
2684
  cacheHits++;
@@ -2582,7 +2699,9 @@ async function runPipeline(root, options = {}) {
2582
2699
  extractions.push(...options.extraExtractions);
2583
2700
  }
2584
2701
  progress("Resolving cross-file references...");
2585
- const resolveStats = resolveCrossFileReferences(extractions);
2702
+ const resolveStats = resolveCrossFileReferences(extractions, {
2703
+ selfNames: await readSelfNames(resolvedRoot)
2704
+ });
2586
2705
  if (resolveStats.resolvedEndpoints > 0) {
2587
2706
  progress(
2588
2707
  ` re-pointed ${resolveStats.resolvedEndpoints} guessed reference(s) at real nodes (${resolveStats.droppedPlaceholders} placeholder node(s) dropped).`
@@ -2671,8 +2790,8 @@ function mergeGraphs(entries) {
2671
2790
 
2672
2791
  // src/memory.ts
2673
2792
  import { createHash as createHash3 } from "crypto";
2674
- import * as fs12 from "fs/promises";
2675
- import * as path8 from "path";
2793
+ import * as fs13 from "fs/promises";
2794
+ import * as path9 from "path";
2676
2795
  var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
2677
2796
  var TYPES = /* @__PURE__ */ new Set(["query", "path", "explain", "affected"]);
2678
2797
  function validateResult(value) {
@@ -2690,25 +2809,25 @@ function validateResult(value) {
2690
2809
  }
2691
2810
  async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
2692
2811
  validateResult(entry);
2693
- await fs12.mkdir(memoryDir, { recursive: true });
2812
+ await fs13.mkdir(memoryDir, { recursive: true });
2694
2813
  const saved = { ...entry, savedAt: now.toISOString() };
2695
2814
  const hash = createHash3("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
2696
2815
  const stamp = saved.savedAt.replace(/[:.]/g, "-");
2697
- const filePath = path8.join(memoryDir, `result-${stamp}-${hash}.json`);
2698
- await fs12.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
2816
+ const filePath = path9.join(memoryDir, `result-${stamp}-${hash}.json`);
2817
+ await fs13.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
2699
2818
  return filePath;
2700
2819
  }
2701
2820
  async function loadResults(memoryDir) {
2702
2821
  let files;
2703
2822
  try {
2704
- files = (await fs12.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
2823
+ files = (await fs13.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
2705
2824
  } catch {
2706
2825
  return [];
2707
2826
  }
2708
2827
  const results = [];
2709
2828
  for (const file of files.sort((a, b) => a.localeCompare(b))) {
2710
2829
  try {
2711
- const parsed = JSON.parse(await fs12.readFile(path8.join(memoryDir, file), "utf-8"));
2830
+ const parsed = JSON.parse(await fs13.readFile(path9.join(memoryDir, file), "utf-8"));
2712
2831
  if (parsed.question && parsed.savedAt) results.push(parsed);
2713
2832
  } catch {
2714
2833
  }
@@ -2784,22 +2903,22 @@ function renderLessons(results, options = {}) {
2784
2903
 
2785
2904
  // src/diff.ts
2786
2905
  import { execFile } from "child_process";
2787
- import * as fs13 from "fs/promises";
2906
+ import * as fs14 from "fs/promises";
2788
2907
  import * as os from "os";
2789
- import * as path9 from "path";
2908
+ import * as path10 from "path";
2790
2909
  import { promisify } from "util";
2791
2910
  var execFileAsync = promisify(execFile);
2792
2911
  async function graphForDirectory(root) {
2793
2912
  const manifest = collectFiles(root);
2794
2913
  const extractions = [];
2795
2914
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
2796
- extractions.push(await extract(path9.join(root, relFile), relFile));
2915
+ extractions.push(await extract(path10.join(root, relFile), relFile));
2797
2916
  }
2798
- resolveCrossFileReferences(extractions);
2917
+ resolveCrossFileReferences(extractions, { selfNames: await readSelfNames(root) });
2799
2918
  return buildGraph(extractions);
2800
2919
  }
2801
2920
  async function checkoutRev(repoRoot, rev) {
2802
- const dest = await fs13.mkdtemp(path9.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
2921
+ const dest = await fs14.mkdtemp(path10.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
2803
2922
  const { stdout } = await execFileAsync(
2804
2923
  "git",
2805
2924
  ["-C", repoRoot, "archive", "--format=tar", rev],
@@ -2867,8 +2986,8 @@ async function reviewRevisions(repoRoot, base, head = "HEAD") {
2867
2986
  return diffGraphs(baseGraph, headGraph, base, head);
2868
2987
  } finally {
2869
2988
  await Promise.all([
2870
- fs13.rm(baseDir, { recursive: true, force: true }),
2871
- fs13.rm(headDir, { recursive: true, force: true })
2989
+ fs14.rm(baseDir, { recursive: true, force: true }),
2990
+ fs14.rm(headDir, { recursive: true, force: true })
2872
2991
  ]);
2873
2992
  }
2874
2993
  }
@@ -3001,4 +3120,4 @@ export {
3001
3120
  validateRules,
3002
3121
  checkRules
3003
3122
  };
3004
- //# sourceMappingURL=chunk-EW23BLJC.js.map
3123
+ //# sourceMappingURL=chunk-NS5HB65S.js.map