@dreamtree-org/graphify 1.2.0 → 1.4.0

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/dist/cli/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  runPipeline,
14
14
  saveResult,
15
15
  validateRules
16
- } from "../chunk-NS5HB65S.js";
16
+ } from "../chunk-QEB7A5KB.js";
17
17
  import {
18
18
  affectedBy,
19
19
  buildContextPack,
@@ -25,7 +25,7 @@ import {
25
25
  shortestPath,
26
26
  testsForChangedFiles,
27
27
  testsForNode
28
- } from "../chunk-ZK3TA6FW.js";
28
+ } from "../chunk-7LTO76UD.js";
29
29
  import {
30
30
  validateDsn,
31
31
  validateUrl
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  ExtractionCache: () => ExtractionCache,
35
35
  ExtractionResultSchema: () => ExtractionResultSchema,
36
36
  ExtractionValidationError: () => ExtractionValidationError,
37
+ LocalFileGraphStore: () => LocalFileGraphStore,
37
38
  affectedBy: () => affectedBy,
38
39
  analyze: () => analyze,
39
40
  buildContextPack: () => buildContextPack,
@@ -41,7 +42,9 @@ __export(index_exports, {
41
42
  checkRules: () => checkRules,
42
43
  cluster: () => cluster,
43
44
  collectFiles: () => collectFiles,
45
+ deserializeGraph: () => deserializeGraph,
44
46
  diffGraphs: () => diffGraphs,
47
+ expandRetrieval: () => expandRetrieval,
45
48
  explainNode: () => explainNode,
46
49
  exportGraph: () => exportGraph,
47
50
  extract: () => extract,
@@ -64,6 +67,7 @@ __export(index_exports, {
64
67
  runPipeline: () => runPipeline,
65
68
  saveResult: () => saveResult,
66
69
  scoreNodes: () => scoreNodes,
70
+ serializeGraph: () => serializeGraph,
67
71
  shortestPath: () => shortestPath,
68
72
  testsForChangedFiles: () => testsForChangedFiles,
69
73
  testsForNode: () => testsForNode,
@@ -2570,7 +2574,7 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
2570
2574
 
2571
2575
  // src/export.ts
2572
2576
  var import_node_module2 = require("module");
2573
- var fs11 = __toESM(require("fs/promises"), 1);
2577
+ var fs12 = __toESM(require("fs/promises"), 1);
2574
2578
  var path6 = __toESM(require("path"), 1);
2575
2579
 
2576
2580
  // src/security.ts
@@ -2645,6 +2649,44 @@ function escapeHtml(text) {
2645
2649
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2646
2650
  }
2647
2651
 
2652
+ // src/store/localFile.ts
2653
+ var fs11 = __toESM(require("fs/promises"), 1);
2654
+
2655
+ // src/store/serialize.ts
2656
+ var import_graphology3 = __toESM(require("graphology"), 1);
2657
+ function serializeGraph(graph) {
2658
+ return graph.export();
2659
+ }
2660
+ function deserializeGraph(data) {
2661
+ return import_graphology3.default.from(data);
2662
+ }
2663
+
2664
+ // src/store/localFile.ts
2665
+ var LocalFileGraphStore = class {
2666
+ async load(ref) {
2667
+ let jsonPath;
2668
+ try {
2669
+ jsonPath = validateGraphPath("graph.json", ref);
2670
+ } catch (error) {
2671
+ if (error.message.startsWith("Base directory does not exist")) return null;
2672
+ throw error;
2673
+ }
2674
+ let raw;
2675
+ try {
2676
+ raw = await fs11.readFile(jsonPath, "utf-8");
2677
+ } catch (error) {
2678
+ if (error.code === "ENOENT") return null;
2679
+ throw error;
2680
+ }
2681
+ return deserializeGraph(JSON.parse(raw));
2682
+ }
2683
+ async save(ref, graph) {
2684
+ await fs11.mkdir(ref, { recursive: true });
2685
+ const jsonPath = validateGraphPath("graph.json", ref);
2686
+ await fs11.writeFile(jsonPath, JSON.stringify(serializeGraph(graph), null, 2), "utf-8");
2687
+ }
2688
+ };
2689
+
2648
2690
  // src/export.ts
2649
2691
  var require3 = (0, import_node_module2.createRequire)(importMetaUrl);
2650
2692
  function resolveVisNetworkAssets() {
@@ -2711,8 +2753,8 @@ community: ${communityLabel}` : ""}`,
2711
2753
  async function renderHtml(graph) {
2712
2754
  const { jsPath, cssPath } = resolveVisNetworkAssets();
2713
2755
  const [visJs, visCss] = await Promise.all([
2714
- fs11.readFile(jsPath, "utf-8"),
2715
- fs11.readFile(cssPath, "utf-8")
2756
+ fs12.readFile(jsPath, "utf-8"),
2757
+ fs12.readFile(cssPath, "utf-8")
2716
2758
  ]);
2717
2759
  const { nodes, edges } = buildVisNodesAndEdges(graph);
2718
2760
  const dataJson = safeInlineJson({ nodes, edges });
@@ -2753,16 +2795,15 @@ ${visJs}
2753
2795
  }
2754
2796
  async function exportGraph(graph, options, report) {
2755
2797
  const outDir = path6.resolve(options.outDir);
2756
- await fs11.mkdir(outDir, { recursive: true });
2757
- const jsonPath = validateGraphPath("graph.json", outDir);
2758
- await fs11.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
2798
+ await fs12.mkdir(outDir, { recursive: true });
2799
+ await new LocalFileGraphStore().save(outDir, graph);
2759
2800
  if (options.html !== false) {
2760
2801
  const htmlPath = validateGraphPath("graph.html", outDir);
2761
- await fs11.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2802
+ await fs12.writeFile(htmlPath, await renderHtml(graph), "utf-8");
2762
2803
  }
2763
2804
  if (report !== void 0) {
2764
2805
  const reportPath = validateGraphPath("GRAPH_REPORT.md", outDir);
2765
- await fs11.writeFile(reportPath, report, "utf-8");
2806
+ await fs12.writeFile(reportPath, report, "utf-8");
2766
2807
  }
2767
2808
  const notImplemented = [];
2768
2809
  if (options.svg) notImplemented.push("--svg");
@@ -2775,12 +2816,12 @@ async function exportGraph(graph, options, report) {
2775
2816
  }
2776
2817
 
2777
2818
  // src/pipeline.ts
2778
- var fs13 = __toESM(require("fs/promises"), 1);
2819
+ var fs14 = __toESM(require("fs/promises"), 1);
2779
2820
  var path8 = __toESM(require("path"), 1);
2780
2821
 
2781
2822
  // src/extractionCache.ts
2782
2823
  var import_node_crypto3 = require("crypto");
2783
- var fs12 = __toESM(require("fs/promises"), 1);
2824
+ var fs13 = __toESM(require("fs/promises"), 1);
2784
2825
  var path7 = __toESM(require("path"), 1);
2785
2826
  var ExtractionCache = class {
2786
2827
  dir;
@@ -2792,7 +2833,7 @@ var ExtractionCache = class {
2792
2833
  }
2793
2834
  async get(key) {
2794
2835
  try {
2795
- const raw = await fs12.readFile(path7.join(this.dir, `${key}.json`), "utf-8");
2836
+ const raw = await fs13.readFile(path7.join(this.dir, `${key}.json`), "utf-8");
2796
2837
  const parsed = JSON.parse(raw);
2797
2838
  if (!Array.isArray(parsed.nodes) || !Array.isArray(parsed.edges)) return null;
2798
2839
  return parsed;
@@ -2801,8 +2842,8 @@ var ExtractionCache = class {
2801
2842
  }
2802
2843
  }
2803
2844
  async put(key, extraction) {
2804
- await fs12.mkdir(this.dir, { recursive: true });
2805
- await fs12.writeFile(path7.join(this.dir, `${key}.json`), JSON.stringify(extraction), "utf-8");
2845
+ await fs13.mkdir(this.dir, { recursive: true });
2846
+ await fs13.writeFile(path7.join(this.dir, `${key}.json`), JSON.stringify(extraction), "utf-8");
2806
2847
  }
2807
2848
  };
2808
2849
 
@@ -2824,7 +2865,7 @@ async function runPipeline(root, options = {}) {
2824
2865
  for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
2825
2866
  const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
2826
2867
  try {
2827
- const key = cache.key(relFile, await fs13.readFile(path8.join(resolvedRoot, relFile)));
2868
+ const key = cache.key(relFile, await fs14.readFile(path8.join(resolvedRoot, relFile)));
2828
2869
  let extraction = options.update ? await cache.get(key) : null;
2829
2870
  if (extraction) {
2830
2871
  cacheHits++;
@@ -2874,19 +2915,23 @@ async function runPipeline(root, options = {}) {
2874
2915
  },
2875
2916
  report
2876
2917
  );
2918
+ if (options.store) {
2919
+ const storeRef = options.storeRef ?? outDir;
2920
+ progress(`Saving graph to store (${storeRef}) ...`);
2921
+ await options.store.save(storeRef, graph);
2922
+ }
2877
2923
  progress("Done.");
2878
2924
  return { manifest, graph, analysis, report };
2879
2925
  }
2880
2926
 
2881
2927
  // src/graphStore.ts
2882
- var fs14 = __toESM(require("fs/promises"), 1);
2883
2928
  var path9 = __toESM(require("path"), 1);
2884
- var import_graphology3 = __toESM(require("graphology"), 1);
2885
2929
  async function loadGraph(outDir = path9.join(process.cwd(), "graphify-out")) {
2886
- const jsonPath = validateGraphPath("graph.json", outDir);
2887
- const raw = await fs14.readFile(jsonPath, "utf-8");
2888
- const data = JSON.parse(raw);
2889
- return import_graphology3.default.from(data);
2930
+ const graph = await new LocalFileGraphStore().load(outDir);
2931
+ if (graph === null) {
2932
+ throw new Error(`No graph found in ${outDir} \u2014 run \`graphify <path>\` to build one first.`);
2933
+ }
2934
+ return graph;
2890
2935
  }
2891
2936
 
2892
2937
  // src/query.ts
@@ -3156,6 +3201,141 @@ function explainNode(graph, query) {
3156
3201
  };
3157
3202
  }
3158
3203
 
3204
+ // src/retrieval.ts
3205
+ var DEFAULT_TOKEN_BUDGET2 = 2e3;
3206
+ var DEFAULT_MAX_HOPS = 2;
3207
+ function resolveSeeds(graph, hits) {
3208
+ const seeds = /* @__PURE__ */ new Map();
3209
+ for (const hit of hits) {
3210
+ let id = null;
3211
+ if (hit.nodeId !== void 0 && graph.hasNode(hit.nodeId)) {
3212
+ id = hit.nodeId;
3213
+ } else if (hit.text !== void 0) {
3214
+ id = resolveNode(graph, hit.text)?.id ?? null;
3215
+ }
3216
+ const key = id ?? hit.nodeId ?? hit.text ?? "";
3217
+ if (key === "") continue;
3218
+ const existing = seeds.get(key);
3219
+ if (existing) {
3220
+ if (hit.score !== void 0 && (existing.score === void 0 || hit.score > existing.score)) {
3221
+ existing.score = hit.score;
3222
+ }
3223
+ continue;
3224
+ }
3225
+ seeds.set(key, { id: key, score: hit.score, inGraph: id !== null, text: hit.text });
3226
+ }
3227
+ return [...seeds.values()];
3228
+ }
3229
+ function expandRetrieval(graph, hits, options = {}) {
3230
+ const tokenBudget = options.tokenBudget ?? DEFAULT_TOKEN_BUDGET2;
3231
+ const maxHops = options.maxHops ?? DEFAULT_MAX_HOPS;
3232
+ const relations = options.relations ? new Set(options.relations) : null;
3233
+ const seeds = resolveSeeds(graph, hits);
3234
+ const graphSeeds = seeds.filter((s) => s.inGraph);
3235
+ const seedScore = new Map(graphSeeds.map((s) => [s.id, s.score]));
3236
+ const hops = /* @__PURE__ */ new Map();
3237
+ const via = /* @__PURE__ */ new Map();
3238
+ const frontier = [];
3239
+ let spentTokens = 0;
3240
+ for (const seed of graphSeeds) {
3241
+ hops.set(seed.id, 0);
3242
+ frontier.push(seed.id);
3243
+ spentTokens += nodeTokenCost(graph, seed.id);
3244
+ }
3245
+ let truncated = false;
3246
+ while (frontier.length > 0) {
3247
+ const current = frontier.shift();
3248
+ const currentHops = hops.get(current);
3249
+ if (currentHops >= maxHops) continue;
3250
+ const candidates = [];
3251
+ graph.forEachEdge(current, (_key, attrs, source, target) => {
3252
+ const relation = String(attrs.relation);
3253
+ if (relations && !relations.has(relation)) return;
3254
+ const neighbor = source === current ? target : source;
3255
+ candidates.push({
3256
+ neighbor,
3257
+ edge: { source, target, relation, confidence: String(attrs.confidence) }
3258
+ });
3259
+ });
3260
+ candidates.sort((a, b) => a.neighbor.localeCompare(b.neighbor));
3261
+ for (const { neighbor, edge } of candidates) {
3262
+ if (hops.has(neighbor)) continue;
3263
+ const cost = nodeTokenCost(graph, neighbor);
3264
+ if (spentTokens + cost > tokenBudget) {
3265
+ truncated = true;
3266
+ continue;
3267
+ }
3268
+ spentTokens += cost;
3269
+ hops.set(neighbor, currentHops + 1);
3270
+ via.set(neighbor, edge);
3271
+ frontier.push(neighbor);
3272
+ }
3273
+ }
3274
+ const degree = (id) => graph.degree(id);
3275
+ const nodes = [...hops.entries()].map(([id, hopCount]) => {
3276
+ const attrs = graph.getNodeAttributes(id);
3277
+ return {
3278
+ id,
3279
+ label: attrs.label ?? id,
3280
+ ...attrs.kind !== void 0 ? { kind: String(attrs.kind) } : {},
3281
+ sourceFile: attrs.sourceFile ?? "",
3282
+ sourceLocation: attrs.sourceLocation ?? "",
3283
+ hops: hopCount,
3284
+ ...via.has(id) ? { via: via.get(id) } : {},
3285
+ ...hopCount === 0 && seedScore.get(id) !== void 0 ? { seedScore: seedScore.get(id) } : {}
3286
+ };
3287
+ });
3288
+ for (const seed of seeds) {
3289
+ if (seed.inGraph) continue;
3290
+ nodes.push({
3291
+ id: seed.id,
3292
+ label: seed.text ?? seed.id,
3293
+ sourceFile: "",
3294
+ sourceLocation: "",
3295
+ hops: 0,
3296
+ ...seed.score !== void 0 ? { seedScore: seed.score } : {}
3297
+ });
3298
+ }
3299
+ nodes.sort((a, b) => {
3300
+ if (a.hops !== b.hops) return a.hops - b.hops;
3301
+ if (a.hops === 0) {
3302
+ return (b.seedScore ?? -Infinity) - (a.seedScore ?? -Infinity) || a.id.localeCompare(b.id);
3303
+ }
3304
+ const degreeDiff = (graph.hasNode(b.id) ? degree(b.id) : 0) - (graph.hasNode(a.id) ? degree(a.id) : 0);
3305
+ return degreeDiff || a.id.localeCompare(b.id);
3306
+ });
3307
+ const included = /* @__PURE__ */ new Set([...hops.keys()]);
3308
+ const edges = [];
3309
+ graph.forEachEdge((_key, attrs, source, target) => {
3310
+ if (!included.has(source) || !included.has(target)) return;
3311
+ const relation = String(attrs.relation);
3312
+ if (relations && !relations.has(relation)) return;
3313
+ edges.push({ source, target, relation, confidence: String(attrs.confidence) });
3314
+ });
3315
+ edges.sort(
3316
+ (a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || a.relation.localeCompare(b.relation)
3317
+ );
3318
+ const communities = /* @__PURE__ */ new Map();
3319
+ for (const id of included) {
3320
+ const attrs = graph.getNodeAttributes(id);
3321
+ const community = attrs.community;
3322
+ if (community === void 0) continue;
3323
+ const entry = communities.get(community) ?? {
3324
+ id: community,
3325
+ label: attrs.communityLabel ?? String(community),
3326
+ seedCount: 0
3327
+ };
3328
+ if (hops.get(id) === 0) entry.seedCount += 1;
3329
+ communities.set(community, entry);
3330
+ }
3331
+ return {
3332
+ nodes,
3333
+ edges,
3334
+ communities: [...communities.values()].sort((a, b) => b.seedCount - a.seedCount || a.id - b.id),
3335
+ truncated
3336
+ };
3337
+ }
3338
+
3159
3339
  // src/impact.ts
3160
3340
  var DEPENDENCY_RELATIONS = /* @__PURE__ */ new Set([
3161
3341
  "calls",
@@ -3951,6 +4131,7 @@ function checkRules(graph, config) {
3951
4131
  ExtractionCache,
3952
4132
  ExtractionResultSchema,
3953
4133
  ExtractionValidationError,
4134
+ LocalFileGraphStore,
3954
4135
  affectedBy,
3955
4136
  analyze,
3956
4137
  buildContextPack,
@@ -3958,7 +4139,9 @@ function checkRules(graph, config) {
3958
4139
  checkRules,
3959
4140
  cluster,
3960
4141
  collectFiles,
4142
+ deserializeGraph,
3961
4143
  diffGraphs,
4144
+ expandRetrieval,
3962
4145
  explainNode,
3963
4146
  exportGraph,
3964
4147
  extract,
@@ -3981,6 +4164,7 @@ function checkRules(graph, config) {
3981
4164
  runPipeline,
3982
4165
  saveResult,
3983
4166
  scoreNodes,
4167
+ serializeGraph,
3984
4168
  shortestPath,
3985
4169
  testsForChangedFiles,
3986
4170
  testsForNode,