@dreamtree-org/graphify 1.1.2 → 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/dist/{chunk-PXVI2L45.js → chunk-NS5HB65S.js} +116 -42
- 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 +149 -48
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +147 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- 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-PXVI2L45.js.map +0 -1
- package/dist/chunk-YT7B6DOD.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1677,13 +1677,11 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
1677
1677
|
});
|
|
1678
1678
|
}
|
|
1679
1679
|
function importTargetId(binding, property) {
|
|
1680
|
-
if (
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
return entityId(binding.ref.id, property);
|
|
1686
|
-
}
|
|
1680
|
+
if (binding.kind === "named" && binding.importedName) {
|
|
1681
|
+
return entityId(binding.ref.id, binding.importedName);
|
|
1682
|
+
}
|
|
1683
|
+
if (binding.kind === "namespace" && property) {
|
|
1684
|
+
return entityId(binding.ref.id, property);
|
|
1687
1685
|
}
|
|
1688
1686
|
return binding.ref.id;
|
|
1689
1687
|
}
|
|
@@ -1721,24 +1719,35 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
1721
1719
|
}
|
|
1722
1720
|
}
|
|
1723
1721
|
}
|
|
1722
|
+
function unwrapExpression(node) {
|
|
1723
|
+
let current = node;
|
|
1724
|
+
while (current.type === "as_expression" || current.type === "satisfies_expression" || current.type === "non_null_expression" || current.type === "parenthesized_expression") {
|
|
1725
|
+
const inner = namedChildren(current)[0];
|
|
1726
|
+
if (!inner) break;
|
|
1727
|
+
current = inner;
|
|
1728
|
+
}
|
|
1729
|
+
return current;
|
|
1730
|
+
}
|
|
1724
1731
|
function handleTopLevelVariableFunctions(node) {
|
|
1732
|
+
const isExported = node.parent?.type === "export_statement";
|
|
1725
1733
|
for (const declarator of namedChildren(node)) {
|
|
1726
1734
|
if (declarator.type !== "variable_declarator") continue;
|
|
1727
1735
|
const value = declarator.childForFieldName("value");
|
|
1728
1736
|
if (!value) continue;
|
|
1729
|
-
|
|
1737
|
+
const core = unwrapExpression(value);
|
|
1738
|
+
if (core.type === "arrow_function" || core.type === "function_expression") {
|
|
1730
1739
|
const name = declarator.childForFieldName("name")?.text;
|
|
1731
1740
|
if (!name) continue;
|
|
1732
1741
|
const id = entityId(sourceFile, name);
|
|
1733
1742
|
builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
|
|
1734
1743
|
builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
1735
1744
|
nameIndex.set(name, id);
|
|
1736
|
-
functionNodeMap.set(
|
|
1745
|
+
functionNodeMap.set(core.id, id);
|
|
1737
1746
|
continue;
|
|
1738
1747
|
}
|
|
1739
|
-
if (
|
|
1740
|
-
const callee =
|
|
1741
|
-
const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(
|
|
1748
|
+
if (core.type === "call_expression") {
|
|
1749
|
+
const callee = core.childForFieldName("function");
|
|
1750
|
+
const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(core) : null;
|
|
1742
1751
|
if (specifier) {
|
|
1743
1752
|
const nameNode = declarator.childForFieldName("name");
|
|
1744
1753
|
if (nameNode) {
|
|
@@ -1746,6 +1755,21 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
1746
1755
|
addModuleNode(ref);
|
|
1747
1756
|
registerRequireBinding(nameNode, ref);
|
|
1748
1757
|
}
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
if (isExported) {
|
|
1762
|
+
const name = declarator.childForFieldName("name")?.text;
|
|
1763
|
+
if (!name) continue;
|
|
1764
|
+
const id = entityId(sourceFile, name);
|
|
1765
|
+
builder.addNode({ id, label: `const ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
|
|
1766
|
+
builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
1767
|
+
nameIndex.set(name, id);
|
|
1768
|
+
if (core.type === "identifier") {
|
|
1769
|
+
const aliased = nameIndex.get(core.text);
|
|
1770
|
+
if (aliased !== void 0 && aliased !== id) {
|
|
1771
|
+
builder.addEdge({ source: id, target: aliased, relation: "references", confidence: "EXTRACTED" });
|
|
1772
|
+
}
|
|
1749
1773
|
}
|
|
1750
1774
|
}
|
|
1751
1775
|
}
|
|
@@ -2147,13 +2171,12 @@ function candidatePaths(guessed) {
|
|
|
2147
2171
|
}
|
|
2148
2172
|
return candidates.filter((c) => c !== guessed);
|
|
2149
2173
|
}
|
|
2150
|
-
function uniqueMatch(
|
|
2174
|
+
function uniqueMatch(candidates, real) {
|
|
2151
2175
|
const matches = /* @__PURE__ */ new Set();
|
|
2152
2176
|
for (const candidate of candidates) {
|
|
2153
|
-
if (
|
|
2177
|
+
if (real.has(candidate)) matches.add(candidate);
|
|
2154
2178
|
}
|
|
2155
|
-
|
|
2156
|
-
return [...matches][0];
|
|
2179
|
+
return matches.size === 1 ? [...matches][0] : null;
|
|
2157
2180
|
}
|
|
2158
2181
|
function resolveCrossFileReferences(extractions, options = {}) {
|
|
2159
2182
|
const selfNames = options.selfNames ?? [];
|
|
@@ -2174,20 +2197,24 @@ function resolveCrossFileReferences(extractions, options = {}) {
|
|
|
2174
2197
|
}
|
|
2175
2198
|
}
|
|
2176
2199
|
const remap = /* @__PURE__ */ new Map();
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2200
|
+
let resolvedEndpoints = 0;
|
|
2201
|
+
const applyRemap = (resolveId) => {
|
|
2202
|
+
for (const extraction of extractions) {
|
|
2203
|
+
for (const edge of extraction.edges) {
|
|
2204
|
+
const source = resolveId(edge.source);
|
|
2205
|
+
if (source !== null) {
|
|
2206
|
+
edge.source = source;
|
|
2207
|
+
resolvedEndpoints++;
|
|
2208
|
+
}
|
|
2209
|
+
const target = resolveId(edge.target);
|
|
2210
|
+
if (target !== null) {
|
|
2211
|
+
edge.target = target;
|
|
2212
|
+
resolvedEndpoints++;
|
|
2187
2213
|
}
|
|
2188
2214
|
}
|
|
2189
|
-
return null;
|
|
2190
2215
|
}
|
|
2216
|
+
};
|
|
2217
|
+
const resolvePathId = (id) => {
|
|
2191
2218
|
if (isOpaque(id)) return null;
|
|
2192
2219
|
const cached = remap.get(id);
|
|
2193
2220
|
if (cached !== void 0) return cached;
|
|
@@ -2197,30 +2224,77 @@ function resolveCrossFileReferences(extractions, options = {}) {
|
|
|
2197
2224
|
if (realIds.has(id)) return null;
|
|
2198
2225
|
const guessedPath = id.slice(0, sep3);
|
|
2199
2226
|
const name = id.slice(sep3);
|
|
2200
|
-
|
|
2201
|
-
resolved = uniqueMatch(id, candidates, realIds);
|
|
2227
|
+
resolved = uniqueMatch(candidatePaths(guessedPath).map((p) => `${p}${name}`), realIds);
|
|
2202
2228
|
} else {
|
|
2203
2229
|
if (realFiles.has(id)) return null;
|
|
2204
|
-
resolved = uniqueMatch(
|
|
2230
|
+
resolved = uniqueMatch(candidatePaths(id), realFiles);
|
|
2205
2231
|
}
|
|
2206
2232
|
if (resolved !== null) remap.set(id, resolved);
|
|
2207
2233
|
return resolved;
|
|
2208
2234
|
};
|
|
2209
|
-
|
|
2235
|
+
applyRemap(resolvePathId);
|
|
2236
|
+
const namedAlias = /* @__PURE__ */ new Map();
|
|
2237
|
+
const starTargets = /* @__PURE__ */ new Map();
|
|
2210
2238
|
for (const extraction of extractions) {
|
|
2211
2239
|
for (const edge of extraction.edges) {
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
edge.
|
|
2220
|
-
|
|
2240
|
+
if (edge.relation !== "re_exports" || !realFiles.has(edge.source)) continue;
|
|
2241
|
+
const sep3 = edge.target.lastIndexOf("::");
|
|
2242
|
+
if (sep3 > 0 && realIds.has(edge.target)) {
|
|
2243
|
+
namedAlias.set(`${edge.source}|${edge.target.slice(sep3 + 2)}`, edge.target);
|
|
2244
|
+
} else if (sep3 < 0 && realFiles.has(edge.target)) {
|
|
2245
|
+
const targets = starTargets.get(edge.source) ?? [];
|
|
2246
|
+
targets.push(edge.target);
|
|
2247
|
+
starTargets.set(edge.source, targets);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
const throughBarrel = (file, name) => {
|
|
2252
|
+
const direct = `${file}::${name}`;
|
|
2253
|
+
if (realIds.has(direct)) return direct;
|
|
2254
|
+
const aliased = namedAlias.get(`${file}|${name}`);
|
|
2255
|
+
if (aliased !== void 0) return aliased;
|
|
2256
|
+
const viaStar = (starTargets.get(file) ?? []).map((sf) => `${sf}::${name}`).filter((id) => realIds.has(id));
|
|
2257
|
+
return viaStar.length === 1 ? viaStar[0] : null;
|
|
2258
|
+
};
|
|
2259
|
+
const resolveBarrelId = (id) => {
|
|
2260
|
+
const cached = remap.get(id);
|
|
2261
|
+
if (cached !== void 0) return cached;
|
|
2262
|
+
if (id.startsWith("module:")) {
|
|
2263
|
+
const spec = id.slice("module:".length);
|
|
2264
|
+
const sep4 = spec.indexOf("::");
|
|
2265
|
+
const moduleSpec = sep4 > 0 ? spec.slice(0, sep4) : spec;
|
|
2266
|
+
const name = sep4 > 0 ? spec.slice(sep4 + 2) : null;
|
|
2267
|
+
const candidates = selfImportCandidates(moduleSpec, selfNames);
|
|
2268
|
+
if (candidates === null) {
|
|
2269
|
+
if (name === null) return null;
|
|
2270
|
+
remap.set(id, `module:${moduleSpec}`);
|
|
2271
|
+
return `module:${moduleSpec}`;
|
|
2272
|
+
}
|
|
2273
|
+
const file = uniqueMatch(candidates, realFiles);
|
|
2274
|
+
let resolved = null;
|
|
2275
|
+
if (file !== null) {
|
|
2276
|
+
resolved = name !== null ? throughBarrel(file, name) ?? file : file;
|
|
2277
|
+
} else if (name !== null) {
|
|
2278
|
+
resolved = `module:${moduleSpec}`;
|
|
2279
|
+
}
|
|
2280
|
+
if (resolved !== null) remap.set(id, resolved);
|
|
2281
|
+
return resolved;
|
|
2282
|
+
}
|
|
2283
|
+
const sep3 = id.indexOf("::");
|
|
2284
|
+
if (sep3 > 0 && !realIds.has(id)) {
|
|
2285
|
+
const guessedPath = id.slice(0, sep3);
|
|
2286
|
+
const name = id.slice(sep3 + 2);
|
|
2287
|
+
const files = [guessedPath, ...candidatePaths(guessedPath)].filter((f) => realFiles.has(f));
|
|
2288
|
+
const resolvedSet = new Set(files.map((f) => throughBarrel(f, name)).filter((r) => r !== null));
|
|
2289
|
+
if (resolvedSet.size === 1) {
|
|
2290
|
+
const resolved = [...resolvedSet][0];
|
|
2291
|
+
remap.set(id, resolved);
|
|
2292
|
+
return resolved;
|
|
2221
2293
|
}
|
|
2222
2294
|
}
|
|
2223
|
-
|
|
2295
|
+
return null;
|
|
2296
|
+
};
|
|
2297
|
+
applyRemap(resolveBarrelId);
|
|
2224
2298
|
let droppedPlaceholders = 0;
|
|
2225
2299
|
for (const extraction of extractions) {
|
|
2226
2300
|
const before = extraction.nodes.length;
|
|
@@ -2918,7 +2992,10 @@ function scoreNodes(graph, query) {
|
|
|
2918
2992
|
}
|
|
2919
2993
|
if (score > 0) matches.push({ id, label, score });
|
|
2920
2994
|
});
|
|
2921
|
-
|
|
2995
|
+
const placeholderRank = (id) => id.startsWith("module:") || id.startsWith("external:") ? 1 : 0;
|
|
2996
|
+
matches.sort(
|
|
2997
|
+
(a, b) => b.score - a.score || placeholderRank(a.id) - placeholderRank(b.id) || a.id.localeCompare(b.id)
|
|
2998
|
+
);
|
|
2922
2999
|
return matches;
|
|
2923
3000
|
}
|
|
2924
3001
|
function resolveNode(graph, query) {
|
|
@@ -3101,6 +3178,7 @@ function affectedBy(graph, nodeQuery, options = {}) {
|
|
|
3101
3178
|
if (!target) return null;
|
|
3102
3179
|
const maxDepth = options.maxDepth ?? DEFAULT_IMPACT_DEPTH;
|
|
3103
3180
|
const limit = options.limit ?? DEFAULT_IMPACT_LIMIT;
|
|
3181
|
+
const followed = options.relations ? new Set(options.relations) : DEPENDENCY_RELATIONS;
|
|
3104
3182
|
const affected = [];
|
|
3105
3183
|
const visited = /* @__PURE__ */ new Set([target.id]);
|
|
3106
3184
|
let frontier = [target.id];
|
|
@@ -3111,7 +3189,7 @@ function affectedBy(graph, nodeQuery, options = {}) {
|
|
|
3111
3189
|
graph.forEachInEdge(current, (_edge, edgeAttrs, source) => {
|
|
3112
3190
|
if (visited.has(source)) return;
|
|
3113
3191
|
const relation = edgeAttrs.relation;
|
|
3114
|
-
if (!
|
|
3192
|
+
if (!followed.has(relation)) return;
|
|
3115
3193
|
const confidence = edgeAttrs.confidence;
|
|
3116
3194
|
const existing = nextFrontier.get(source);
|
|
3117
3195
|
if (existing && CONFIDENCE_RANK2[existing.via.confidence] >= CONFIDENCE_RANK2[confidence]) return;
|
|
@@ -3611,11 +3689,12 @@ function isTestFile(path12) {
|
|
|
3611
3689
|
}
|
|
3612
3690
|
var TEST_REACH_DEPTH = 4;
|
|
3613
3691
|
var TEST_REACH_LIMIT = 2e3;
|
|
3614
|
-
|
|
3692
|
+
var USAGE_RELATIONS = ["calls", "inherits", "implements", "mixes_in", "embeds", "references"];
|
|
3693
|
+
function selectionFromImpact(graph, targetIds, targetLabel, relations) {
|
|
3615
3694
|
const best = /* @__PURE__ */ new Map();
|
|
3616
3695
|
let affectedNodeCount = 0;
|
|
3617
3696
|
for (const id of targetIds) {
|
|
3618
|
-
const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT });
|
|
3697
|
+
const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT, relations });
|
|
3619
3698
|
if (!impact) continue;
|
|
3620
3699
|
affectedNodeCount += impact.affected.length;
|
|
3621
3700
|
for (const node of impact.affected) {
|
|
@@ -3627,11 +3706,27 @@ function selectionFromImpact(graph, targetIds, targetLabel) {
|
|
|
3627
3706
|
}
|
|
3628
3707
|
}
|
|
3629
3708
|
const testFiles = [...best.entries()].map(([file, info]) => ({ file, depth: info.depth, via: info.via })).sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
|
|
3630
|
-
return {
|
|
3709
|
+
return {
|
|
3710
|
+
target: targetLabel,
|
|
3711
|
+
testFiles,
|
|
3712
|
+
affectedNodeCount,
|
|
3713
|
+
precision: relations ? "usage" : "import-reachability"
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function entityRootsOf(graph, id) {
|
|
3717
|
+
if (id.includes("::")) return [id];
|
|
3718
|
+
const roots = [];
|
|
3719
|
+
graph.forEachOutEdge(id, (_edge, attrs, _source, target) => {
|
|
3720
|
+
if (String(attrs.relation) === "contains") roots.push(target);
|
|
3721
|
+
});
|
|
3722
|
+
roots.sort((a, b) => a.localeCompare(b));
|
|
3723
|
+
return roots.length > 0 ? roots : [id];
|
|
3631
3724
|
}
|
|
3632
3725
|
function testsForNode(graph, nodeQuery) {
|
|
3633
3726
|
const match = resolveNode(graph, nodeQuery);
|
|
3634
3727
|
if (!match) return null;
|
|
3728
|
+
const precise = selectionFromImpact(graph, entityRootsOf(graph, match.id), match.id, USAGE_RELATIONS);
|
|
3729
|
+
if (precise.testFiles.length > 0) return precise;
|
|
3635
3730
|
return selectionFromImpact(graph, [match.id], match.id);
|
|
3636
3731
|
}
|
|
3637
3732
|
function testsForChangedFiles(graph, changedFiles) {
|
|
@@ -3644,7 +3739,12 @@ function testsForChangedFiles(graph, changedFiles) {
|
|
|
3644
3739
|
}
|
|
3645
3740
|
if (graph.hasNode(file)) fileIds.push(file);
|
|
3646
3741
|
}
|
|
3647
|
-
const
|
|
3742
|
+
const label = changedFiles.join(", ");
|
|
3743
|
+
const preciseRoots = fileIds.flatMap((f) => entityRootsOf(graph, f));
|
|
3744
|
+
let selection = selectionFromImpact(graph, preciseRoots, label, USAGE_RELATIONS);
|
|
3745
|
+
if (selection.testFiles.length === 0) {
|
|
3746
|
+
selection = selectionFromImpact(graph, fileIds, label);
|
|
3747
|
+
}
|
|
3648
3748
|
for (const [file, info] of selfSelected) {
|
|
3649
3749
|
if (!selection.testFiles.some((t) => t.file === file)) {
|
|
3650
3750
|
selection.testFiles.push({ file, depth: info.depth, via: info.via });
|