@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 +6 -2
- package/dist/{chunk-EW23BLJC.js → chunk-PXVI2L45.js} +89 -44
- package/dist/chunk-PXVI2L45.js.map +1 -0
- package/dist/cli/index.cjs +248 -195
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +15 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +108 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EW23BLJC.js.map +0 -1
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
|
-
|
|
20
|
-
|
|
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
|
|
1538
|
+
const path11 = fn.childForFieldName("path")?.text;
|
|
1539
1539
|
const name = fn.childForFieldName("name")?.text;
|
|
1540
|
-
if (
|
|
1541
|
-
const viaImport = importIndex.get(
|
|
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
|
|
2387
|
-
import * as
|
|
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 =
|
|
2434
|
+
const root = path6.dirname(packageJsonPath);
|
|
2392
2435
|
return {
|
|
2393
|
-
jsPath:
|
|
2394
|
-
cssPath:
|
|
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
|
-
|
|
2454
|
-
|
|
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 =
|
|
2495
|
-
await
|
|
2537
|
+
const outDir = path6.resolve(options.outDir);
|
|
2538
|
+
await fs10.mkdir(outDir, { recursive: true });
|
|
2496
2539
|
const jsonPath = validateGraphPath("graph.json", outDir);
|
|
2497
|
-
await
|
|
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
|
|
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
|
|
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
|
|
2519
|
-
import * as
|
|
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 =
|
|
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
|
|
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
|
|
2540
|
-
await
|
|
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
|
|
2546
|
-
import * as
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
2605
|
+
const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
|
|
2563
2606
|
try {
|
|
2564
|
-
const key = cache.key(relFile, await
|
|
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
|
|
2675
|
-
import * as
|
|
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
|
|
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 =
|
|
2698
|
-
await
|
|
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
|
|
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
|
|
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
|
|
2832
|
+
import * as fs14 from "fs/promises";
|
|
2788
2833
|
import * as os from "os";
|
|
2789
|
-
import * as
|
|
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(
|
|
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
|
|
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
|
-
|
|
2871
|
-
|
|
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-
|
|
3049
|
+
//# sourceMappingURL=chunk-PXVI2L45.js.map
|