@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.
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
 
@@ -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";
@@ -2017,6 +2017,8 @@ function buildGraph(extractions) {
2017
2017
  }
2018
2018
 
2019
2019
  // src/resolve.ts
2020
+ import * as fs9 from "fs/promises";
2021
+ import * as path5 from "path";
2020
2022
  var CODE_EXTENSIONS2 = [
2021
2023
  ".ts",
2022
2024
  ".tsx",
@@ -2036,6 +2038,29 @@ var CODE_EXTENSIONS2 = [
2036
2038
  function isOpaque(id) {
2037
2039
  return id.startsWith("module:") || id.startsWith("external:") || id.startsWith("db:");
2038
2040
  }
2041
+ var SELF_IMPORT_STEMS = (subpath) => subpath === "" ? ["index", "src/index", "lib/index", "source/index", "src/main"] : [
2042
+ subpath,
2043
+ `src/${subpath}`,
2044
+ `lib/${subpath}`,
2045
+ `source/${subpath}`,
2046
+ `${subpath}/index`,
2047
+ `src/${subpath}/index`,
2048
+ `lib/${subpath}/index`
2049
+ ];
2050
+ function selfImportCandidates(spec, selfNames) {
2051
+ const selfName = selfNames.find((n) => spec === n || spec.startsWith(`${n}/`));
2052
+ if (selfName === void 0) return null;
2053
+ const subpath = spec === selfName ? "" : spec.slice(selfName.length + 1);
2054
+ return SELF_IMPORT_STEMS(subpath).flatMap((stem) => CODE_EXTENSIONS2.map((ext) => `${stem}${ext}`));
2055
+ }
2056
+ async function readSelfNames(root) {
2057
+ try {
2058
+ const pkg = JSON.parse(await fs9.readFile(path5.join(root, "package.json"), "utf-8"));
2059
+ return typeof pkg.name === "string" && pkg.name.length > 0 ? [pkg.name] : [];
2060
+ } catch {
2061
+ return [];
2062
+ }
2063
+ }
2039
2064
  function stripKnownExtension(p) {
2040
2065
  for (const ext of CODE_EXTENSIONS2) {
2041
2066
  if (p.endsWith(ext)) return p.slice(0, -ext.length);
@@ -2060,11 +2085,16 @@ function uniqueMatch(guessed, candidates, realIds) {
2060
2085
  if (matches.size !== 1) return null;
2061
2086
  return [...matches][0];
2062
2087
  }
2063
- function resolveCrossFileReferences(extractions) {
2088
+ function resolveCrossFileReferences(extractions, options = {}) {
2089
+ const selfNames = options.selfNames ?? [];
2064
2090
  const realIds = /* @__PURE__ */ new Set();
2065
2091
  const realFiles = /* @__PURE__ */ new Set();
2066
2092
  for (const extraction of extractions) {
2067
2093
  for (const node of extraction.nodes) realIds.add(node.id);
2094
+ const first = extraction.nodes[0];
2095
+ if (first && first.id === first.sourceFile && !isOpaque(first.id)) {
2096
+ realFiles.add(first.id);
2097
+ }
2068
2098
  for (const node of extraction.nodes) {
2069
2099
  const sep2 = node.id.indexOf("::");
2070
2100
  if (sep2 > 0) realFiles.add(node.id.slice(0, sep2));
@@ -2075,6 +2105,19 @@ function resolveCrossFileReferences(extractions) {
2075
2105
  }
2076
2106
  const remap = /* @__PURE__ */ new Map();
2077
2107
  const resolveId = (id) => {
2108
+ if (id.startsWith("module:") && selfNames.length > 0) {
2109
+ const cachedSelf = remap.get(id);
2110
+ if (cachedSelf !== void 0) return cachedSelf;
2111
+ const candidates = selfImportCandidates(id.slice("module:".length), selfNames);
2112
+ if (candidates !== null) {
2113
+ const resolved2 = uniqueMatch(id, candidates, realFiles);
2114
+ if (resolved2 !== null) {
2115
+ remap.set(id, resolved2);
2116
+ return resolved2;
2117
+ }
2118
+ }
2119
+ return null;
2120
+ }
2078
2121
  if (isOpaque(id)) return null;
2079
2122
  const cached = remap.get(id);
2080
2123
  if (cached !== void 0) return cached;
@@ -2383,15 +2426,15 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
2383
2426
 
2384
2427
  // src/export.ts
2385
2428
  import { createRequire as createRequire2 } from "module";
2386
- import * as fs9 from "fs/promises";
2387
- import * as path5 from "path";
2429
+ import * as fs10 from "fs/promises";
2430
+ import * as path6 from "path";
2388
2431
  var require3 = createRequire2(import.meta.url);
2389
2432
  function resolveVisNetworkAssets() {
2390
2433
  const packageJsonPath = require3.resolve("vis-network/package.json");
2391
- const root = path5.dirname(packageJsonPath);
2434
+ const root = path6.dirname(packageJsonPath);
2392
2435
  return {
2393
- jsPath: path5.join(root, "standalone", "umd", "vis-network.min.js"),
2394
- cssPath: path5.join(root, "styles", "vis-network.min.css")
2436
+ jsPath: path6.join(root, "standalone", "umd", "vis-network.min.js"),
2437
+ cssPath: path6.join(root, "styles", "vis-network.min.css")
2395
2438
  };
2396
2439
  }
2397
2440
  var COMMUNITY_PALETTE = [
@@ -2450,8 +2493,8 @@ community: ${communityLabel}` : ""}`,
2450
2493
  async function renderHtml(graph) {
2451
2494
  const { jsPath, cssPath } = resolveVisNetworkAssets();
2452
2495
  const [visJs, visCss] = await Promise.all([
2453
- fs9.readFile(jsPath, "utf-8"),
2454
- fs9.readFile(cssPath, "utf-8")
2496
+ fs10.readFile(jsPath, "utf-8"),
2497
+ fs10.readFile(cssPath, "utf-8")
2455
2498
  ]);
2456
2499
  const { nodes, edges } = buildVisNodesAndEdges(graph);
2457
2500
  const dataJson = safeInlineJson({ nodes, edges });
@@ -2491,17 +2534,17 @@ ${visJs}
2491
2534
  `;
2492
2535
  }
2493
2536
  async function exportGraph(graph, options, report) {
2494
- const outDir = path5.resolve(options.outDir);
2495
- await fs9.mkdir(outDir, { recursive: true });
2537
+ const outDir = path6.resolve(options.outDir);
2538
+ await fs10.mkdir(outDir, { recursive: true });
2496
2539
  const jsonPath = validateGraphPath("graph.json", outDir);
2497
- await fs9.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2540
+ await fs10.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2498
2541
  if (options.html !== false) {
2499
2542
  const htmlPath = validateGraphPath("graph.html", outDir);
2500
- await fs9.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2543
+ await fs10.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2501
2544
  }
2502
2545
  if (report !== void 0) {
2503
2546
  const reportPath = validateGraphPath("GRAPH_REPORT.md", outDir);
2504
- await fs9.writeFile(reportPath, report, "utf-8");
2547
+ await fs10.writeFile(reportPath, report, "utf-8");
2505
2548
  }
2506
2549
  const notImplemented = [];
2507
2550
  if (options.svg) notImplemented.push("--svg");
@@ -2515,19 +2558,19 @@ async function exportGraph(graph, options, report) {
2515
2558
 
2516
2559
  // src/extractionCache.ts
2517
2560
  import { createHash as createHash2 } from "crypto";
2518
- import * as fs10 from "fs/promises";
2519
- import * as path6 from "path";
2561
+ import * as fs11 from "fs/promises";
2562
+ import * as path7 from "path";
2520
2563
  var ExtractionCache = class {
2521
2564
  dir;
2522
2565
  constructor(outDir) {
2523
- this.dir = path6.join(outDir, "cache");
2566
+ this.dir = path7.join(outDir, "cache");
2524
2567
  }
2525
2568
  key(relFile, content) {
2526
2569
  return createHash2("sha256").update(relFile).update("\0").update(content).digest("hex");
2527
2570
  }
2528
2571
  async get(key) {
2529
2572
  try {
2530
- const raw = await fs10.readFile(path6.join(this.dir, `${key}.json`), "utf-8");
2573
+ const raw = await fs11.readFile(path7.join(this.dir, `${key}.json`), "utf-8");
2531
2574
  const parsed = JSON.parse(raw);
2532
2575
  if (!Array.isArray(parsed.nodes) || !Array.isArray(parsed.edges)) return null;
2533
2576
  return parsed;
@@ -2536,32 +2579,32 @@ var ExtractionCache = class {
2536
2579
  }
2537
2580
  }
2538
2581
  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");
2582
+ await fs11.mkdir(this.dir, { recursive: true });
2583
+ await fs11.writeFile(path7.join(this.dir, `${key}.json`), JSON.stringify(extraction), "utf-8");
2541
2584
  }
2542
2585
  };
2543
2586
 
2544
2587
  // src/pipeline.ts
2545
- import * as fs11 from "fs/promises";
2546
- import * as path7 from "path";
2588
+ import * as fs12 from "fs/promises";
2589
+ import * as path8 from "path";
2547
2590
  async function runPipeline(root, options = {}) {
2548
2591
  const progress = options.onProgress ?? (() => {
2549
2592
  });
2550
- const resolvedRoot = path7.resolve(root);
2593
+ const resolvedRoot = path8.resolve(root);
2551
2594
  progress(`Scanning ${resolvedRoot} ...`);
2552
2595
  const manifest = collectFiles(resolvedRoot);
2553
2596
  progress(
2554
2597
  `Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
2555
2598
  );
2556
- const outDir = options.outDir ?? path7.join(resolvedRoot, "graphify-out");
2599
+ const outDir = options.outDir ?? path8.join(resolvedRoot, "graphify-out");
2557
2600
  const cache = new ExtractionCache(outDir);
2558
2601
  progress("Extracting...");
2559
2602
  const extractions = [];
2560
2603
  let cacheHits = 0;
2561
2604
  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;
2605
+ const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
2563
2606
  try {
2564
- const key = cache.key(relFile, await fs11.readFile(path7.join(resolvedRoot, relFile)));
2607
+ const key = cache.key(relFile, await fs12.readFile(path8.join(resolvedRoot, relFile)));
2565
2608
  let extraction = options.update ? await cache.get(key) : null;
2566
2609
  if (extraction) {
2567
2610
  cacheHits++;
@@ -2582,7 +2625,9 @@ async function runPipeline(root, options = {}) {
2582
2625
  extractions.push(...options.extraExtractions);
2583
2626
  }
2584
2627
  progress("Resolving cross-file references...");
2585
- const resolveStats = resolveCrossFileReferences(extractions);
2628
+ const resolveStats = resolveCrossFileReferences(extractions, {
2629
+ selfNames: await readSelfNames(resolvedRoot)
2630
+ });
2586
2631
  if (resolveStats.resolvedEndpoints > 0) {
2587
2632
  progress(
2588
2633
  ` re-pointed ${resolveStats.resolvedEndpoints} guessed reference(s) at real nodes (${resolveStats.droppedPlaceholders} placeholder node(s) dropped).`
@@ -2671,8 +2716,8 @@ function mergeGraphs(entries) {
2671
2716
 
2672
2717
  // src/memory.ts
2673
2718
  import { createHash as createHash3 } from "crypto";
2674
- import * as fs12 from "fs/promises";
2675
- import * as path8 from "path";
2719
+ import * as fs13 from "fs/promises";
2720
+ import * as path9 from "path";
2676
2721
  var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
2677
2722
  var TYPES = /* @__PURE__ */ new Set(["query", "path", "explain", "affected"]);
2678
2723
  function validateResult(value) {
@@ -2690,25 +2735,25 @@ function validateResult(value) {
2690
2735
  }
2691
2736
  async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
2692
2737
  validateResult(entry);
2693
- await fs12.mkdir(memoryDir, { recursive: true });
2738
+ await fs13.mkdir(memoryDir, { recursive: true });
2694
2739
  const saved = { ...entry, savedAt: now.toISOString() };
2695
2740
  const hash = createHash3("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
2696
2741
  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");
2742
+ const filePath = path9.join(memoryDir, `result-${stamp}-${hash}.json`);
2743
+ await fs13.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
2699
2744
  return filePath;
2700
2745
  }
2701
2746
  async function loadResults(memoryDir) {
2702
2747
  let files;
2703
2748
  try {
2704
- files = (await fs12.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
2749
+ files = (await fs13.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
2705
2750
  } catch {
2706
2751
  return [];
2707
2752
  }
2708
2753
  const results = [];
2709
2754
  for (const file of files.sort((a, b) => a.localeCompare(b))) {
2710
2755
  try {
2711
- const parsed = JSON.parse(await fs12.readFile(path8.join(memoryDir, file), "utf-8"));
2756
+ const parsed = JSON.parse(await fs13.readFile(path9.join(memoryDir, file), "utf-8"));
2712
2757
  if (parsed.question && parsed.savedAt) results.push(parsed);
2713
2758
  } catch {
2714
2759
  }
@@ -2784,22 +2829,22 @@ function renderLessons(results, options = {}) {
2784
2829
 
2785
2830
  // src/diff.ts
2786
2831
  import { execFile } from "child_process";
2787
- import * as fs13 from "fs/promises";
2832
+ import * as fs14 from "fs/promises";
2788
2833
  import * as os from "os";
2789
- import * as path9 from "path";
2834
+ import * as path10 from "path";
2790
2835
  import { promisify } from "util";
2791
2836
  var execFileAsync = promisify(execFile);
2792
2837
  async function graphForDirectory(root) {
2793
2838
  const manifest = collectFiles(root);
2794
2839
  const extractions = [];
2795
2840
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
2796
- extractions.push(await extract(path9.join(root, relFile), relFile));
2841
+ extractions.push(await extract(path10.join(root, relFile), relFile));
2797
2842
  }
2798
- resolveCrossFileReferences(extractions);
2843
+ resolveCrossFileReferences(extractions, { selfNames: await readSelfNames(root) });
2799
2844
  return buildGraph(extractions);
2800
2845
  }
2801
2846
  async function checkoutRev(repoRoot, rev) {
2802
- const dest = await fs13.mkdtemp(path9.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
2847
+ const dest = await fs14.mkdtemp(path10.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
2803
2848
  const { stdout } = await execFileAsync(
2804
2849
  "git",
2805
2850
  ["-C", repoRoot, "archive", "--format=tar", rev],
@@ -2867,8 +2912,8 @@ async function reviewRevisions(repoRoot, base, head = "HEAD") {
2867
2912
  return diffGraphs(baseGraph, headGraph, base, head);
2868
2913
  } finally {
2869
2914
  await Promise.all([
2870
- fs13.rm(baseDir, { recursive: true, force: true }),
2871
- fs13.rm(headDir, { recursive: true, force: true })
2915
+ fs14.rm(baseDir, { recursive: true, force: true }),
2916
+ fs14.rm(headDir, { recursive: true, force: true })
2872
2917
  ]);
2873
2918
  }
2874
2919
  }
@@ -3001,4 +3046,4 @@ export {
3001
3046
  validateRules,
3002
3047
  checkRules
3003
3048
  };
3004
- //# sourceMappingURL=chunk-EW23BLJC.js.map
3049
+ //# sourceMappingURL=chunk-PXVI2L45.js.map