@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 +6 -2
- package/dist/{chunk-EW23BLJC.js → chunk-NS5HB65S.js} +194 -75
- package/dist/chunk-NS5HB65S.js.map +1 -0
- package/dist/{chunk-YT7B6DOD.js → chunk-ZK3TA6FW.js} +33 -7
- package/dist/chunk-ZK3TA6FW.js.map +1 -0
- package/dist/cli/index.cjs +382 -228
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +18 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +245 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +2 -2
- package/dist/mcp/server.cjs +26 -5
- package/dist/mcp/server.cjs.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EW23BLJC.js.map +0 -1
- package/dist/chunk-YT7B6DOD.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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
affectedBy,
|
|
3
3
|
testsForNode
|
|
4
|
-
} from "./chunk-
|
|
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
|
|
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";
|
|
@@ -1609,13 +1609,11 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
1609
1609
|
});
|
|
1610
1610
|
}
|
|
1611
1611
|
function importTargetId(binding, property) {
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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
|
-
|
|
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(
|
|
1677
|
+
functionNodeMap.set(core.id, id);
|
|
1669
1678
|
continue;
|
|
1670
1679
|
}
|
|
1671
|
-
if (
|
|
1672
|
-
const callee =
|
|
1673
|
-
const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(
|
|
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(
|
|
2104
|
+
function uniqueMatch(candidates, real) {
|
|
2056
2105
|
const matches = /* @__PURE__ */ new Set();
|
|
2057
2106
|
for (const candidate of candidates) {
|
|
2058
|
-
if (
|
|
2107
|
+
if (real.has(candidate)) matches.add(candidate);
|
|
2059
2108
|
}
|
|
2060
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2160
|
+
resolved = uniqueMatch(candidatePaths(id), realFiles);
|
|
2092
2161
|
}
|
|
2093
2162
|
if (resolved !== null) remap.set(id, resolved);
|
|
2094
2163
|
return resolved;
|
|
2095
2164
|
};
|
|
2096
|
-
|
|
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
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
edge.
|
|
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
|
|
2387
|
-
import * as
|
|
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 =
|
|
2508
|
+
const root = path6.dirname(packageJsonPath);
|
|
2392
2509
|
return {
|
|
2393
|
-
jsPath:
|
|
2394
|
-
cssPath:
|
|
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
|
-
|
|
2454
|
-
|
|
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 =
|
|
2495
|
-
await
|
|
2611
|
+
const outDir = path6.resolve(options.outDir);
|
|
2612
|
+
await fs10.mkdir(outDir, { recursive: true });
|
|
2496
2613
|
const jsonPath = validateGraphPath("graph.json", outDir);
|
|
2497
|
-
await
|
|
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
|
|
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
|
|
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
|
|
2519
|
-
import * as
|
|
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 =
|
|
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
|
|
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
|
|
2540
|
-
await
|
|
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
|
|
2546
|
-
import * as
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
2679
|
+
const filePath = path8.relative(process.cwd(), path8.join(resolvedRoot, relFile)) || relFile;
|
|
2563
2680
|
try {
|
|
2564
|
-
const key = cache.key(relFile, await
|
|
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
|
|
2675
|
-
import * as
|
|
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
|
|
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 =
|
|
2698
|
-
await
|
|
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
|
|
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
|
|
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
|
|
2906
|
+
import * as fs14 from "fs/promises";
|
|
2788
2907
|
import * as os from "os";
|
|
2789
|
-
import * as
|
|
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(
|
|
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
|
|
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
|
-
|
|
2871
|
-
|
|
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-
|
|
3123
|
+
//# sourceMappingURL=chunk-NS5HB65S.js.map
|