@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/dist/cli/index.cjs
CHANGED
|
@@ -156,7 +156,7 @@ function validateDsn(dsn) {
|
|
|
156
156
|
}
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
function validateGraphPath(
|
|
159
|
+
function validateGraphPath(path18, base) {
|
|
160
160
|
const baseDir = base ?? nodePath.join(process.cwd(), "graphify-out");
|
|
161
161
|
let resolvedBase;
|
|
162
162
|
try {
|
|
@@ -164,7 +164,7 @@ function validateGraphPath(path17, base) {
|
|
|
164
164
|
} catch {
|
|
165
165
|
throw new Error(`Base directory does not exist: ${baseDir}`);
|
|
166
166
|
}
|
|
167
|
-
const candidate = nodePath.isAbsolute(
|
|
167
|
+
const candidate = nodePath.isAbsolute(path18) ? path18 : nodePath.join(resolvedBase, path18);
|
|
168
168
|
const resolvedCandidate = nodePath.resolve(candidate);
|
|
169
169
|
let realCandidate = resolvedCandidate;
|
|
170
170
|
try {
|
|
@@ -174,7 +174,7 @@ function validateGraphPath(path17, base) {
|
|
|
174
174
|
const relative4 = nodePath.relative(resolvedBase, realCandidate);
|
|
175
175
|
const escapes = relative4 === ".." || relative4.startsWith(`..${nodePath.sep}`) || nodePath.isAbsolute(relative4);
|
|
176
176
|
if (escapes) {
|
|
177
|
-
throw new Error(`Path escapes graphify-out/: ${
|
|
177
|
+
throw new Error(`Path escapes graphify-out/: ${path18}`);
|
|
178
178
|
}
|
|
179
179
|
return realCandidate;
|
|
180
180
|
}
|
|
@@ -332,7 +332,10 @@ function scoreNodes(graph, query) {
|
|
|
332
332
|
}
|
|
333
333
|
if (score > 0) matches.push({ id, label, score });
|
|
334
334
|
});
|
|
335
|
-
|
|
335
|
+
const placeholderRank = (id) => id.startsWith("module:") || id.startsWith("external:") ? 1 : 0;
|
|
336
|
+
matches.sort(
|
|
337
|
+
(a, b) => b.score - a.score || placeholderRank(a.id) - placeholderRank(b.id) || a.id.localeCompare(b.id)
|
|
338
|
+
);
|
|
336
339
|
return matches;
|
|
337
340
|
}
|
|
338
341
|
function resolveNode(graph, query) {
|
|
@@ -443,25 +446,25 @@ function shortestPath(graph, fromQuery, toQuery) {
|
|
|
443
446
|
if (!visited.has(to.id)) {
|
|
444
447
|
return { from, to, found: false, path: [], edges: [] };
|
|
445
448
|
}
|
|
446
|
-
const
|
|
449
|
+
const path18 = [to.id];
|
|
447
450
|
let cursor = to.id;
|
|
448
451
|
while (cursor !== from.id) {
|
|
449
452
|
const prev = predecessor.get(cursor);
|
|
450
453
|
if (!prev) break;
|
|
451
|
-
|
|
454
|
+
path18.unshift(prev);
|
|
452
455
|
cursor = prev;
|
|
453
456
|
}
|
|
454
457
|
const edges = [];
|
|
455
|
-
for (let i = 0; i <
|
|
456
|
-
const a =
|
|
457
|
-
const b =
|
|
458
|
+
for (let i = 0; i < path18.length - 1; i++) {
|
|
459
|
+
const a = path18[i];
|
|
460
|
+
const b = path18[i + 1];
|
|
458
461
|
const key = graph.edges(a, b)[0] ?? graph.edges(b, a)[0];
|
|
459
462
|
if (key) {
|
|
460
463
|
const attrs = graph.getEdgeAttributes(key);
|
|
461
464
|
edges.push({ source: a, target: b, relation: String(attrs.relation), confidence: String(attrs.confidence) });
|
|
462
465
|
}
|
|
463
466
|
}
|
|
464
|
-
return { from, to, found: true, path:
|
|
467
|
+
return { from, to, found: true, path: path18, edges };
|
|
465
468
|
}
|
|
466
469
|
function explainNode(graph, query) {
|
|
467
470
|
const match = resolveNode(graph, query);
|
|
@@ -542,6 +545,7 @@ function affectedBy(graph, nodeQuery, options = {}) {
|
|
|
542
545
|
if (!target) return null;
|
|
543
546
|
const maxDepth = options.maxDepth ?? DEFAULT_IMPACT_DEPTH;
|
|
544
547
|
const limit = options.limit ?? DEFAULT_IMPACT_LIMIT;
|
|
548
|
+
const followed = options.relations ? new Set(options.relations) : DEPENDENCY_RELATIONS;
|
|
545
549
|
const affected = [];
|
|
546
550
|
const visited = /* @__PURE__ */ new Set([target.id]);
|
|
547
551
|
let frontier = [target.id];
|
|
@@ -552,7 +556,7 @@ function affectedBy(graph, nodeQuery, options = {}) {
|
|
|
552
556
|
graph.forEachInEdge(current, (_edge, edgeAttrs, source) => {
|
|
553
557
|
if (visited.has(source)) return;
|
|
554
558
|
const relation = edgeAttrs.relation;
|
|
555
|
-
if (!
|
|
559
|
+
if (!followed.has(relation)) return;
|
|
556
560
|
const confidence = edgeAttrs.confidence;
|
|
557
561
|
const existing = nextFrontier.get(source);
|
|
558
562
|
if (existing && CONFIDENCE_RANK2[existing.via.confidence] >= CONFIDENCE_RANK2[confidence]) return;
|
|
@@ -607,6 +611,100 @@ var init_impact = __esm({
|
|
|
607
611
|
}
|
|
608
612
|
});
|
|
609
613
|
|
|
614
|
+
// src/testmap.ts
|
|
615
|
+
function isTestFile(path18) {
|
|
616
|
+
return TEST_FILE_PATTERNS.some((p) => p.test(path18));
|
|
617
|
+
}
|
|
618
|
+
function selectionFromImpact(graph, targetIds, targetLabel, relations) {
|
|
619
|
+
const best = /* @__PURE__ */ new Map();
|
|
620
|
+
let affectedNodeCount = 0;
|
|
621
|
+
for (const id of targetIds) {
|
|
622
|
+
const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT, relations });
|
|
623
|
+
if (!impact) continue;
|
|
624
|
+
affectedNodeCount += impact.affected.length;
|
|
625
|
+
for (const node of impact.affected) {
|
|
626
|
+
if (!isTestFile(node.sourceFile)) continue;
|
|
627
|
+
const existing = best.get(node.sourceFile);
|
|
628
|
+
if (!existing || node.depth < existing.depth) {
|
|
629
|
+
best.set(node.sourceFile, { depth: node.depth, via: node.via.dependsOn });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
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));
|
|
634
|
+
return {
|
|
635
|
+
target: targetLabel,
|
|
636
|
+
testFiles,
|
|
637
|
+
affectedNodeCount,
|
|
638
|
+
precision: relations ? "usage" : "import-reachability"
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function entityRootsOf(graph, id) {
|
|
642
|
+
if (id.includes("::")) return [id];
|
|
643
|
+
const roots = [];
|
|
644
|
+
graph.forEachOutEdge(id, (_edge, attrs, _source, target) => {
|
|
645
|
+
if (String(attrs.relation) === "contains") roots.push(target);
|
|
646
|
+
});
|
|
647
|
+
roots.sort((a, b) => a.localeCompare(b));
|
|
648
|
+
return roots.length > 0 ? roots : [id];
|
|
649
|
+
}
|
|
650
|
+
function testsForNode(graph, nodeQuery) {
|
|
651
|
+
const match = resolveNode(graph, nodeQuery);
|
|
652
|
+
if (!match) return null;
|
|
653
|
+
const precise = selectionFromImpact(graph, entityRootsOf(graph, match.id), match.id, USAGE_RELATIONS);
|
|
654
|
+
if (precise.testFiles.length > 0) return precise;
|
|
655
|
+
return selectionFromImpact(graph, [match.id], match.id);
|
|
656
|
+
}
|
|
657
|
+
function testsForChangedFiles(graph, changedFiles) {
|
|
658
|
+
const fileIds = [];
|
|
659
|
+
const selfSelected = /* @__PURE__ */ new Map();
|
|
660
|
+
for (const file of changedFiles) {
|
|
661
|
+
if (isTestFile(file)) {
|
|
662
|
+
selfSelected.set(file, { depth: 0, via: "(changed directly)" });
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (graph.hasNode(file)) fileIds.push(file);
|
|
666
|
+
}
|
|
667
|
+
const label = changedFiles.join(", ");
|
|
668
|
+
const preciseRoots = fileIds.flatMap((f) => entityRootsOf(graph, f));
|
|
669
|
+
let selection = selectionFromImpact(graph, preciseRoots, label, USAGE_RELATIONS);
|
|
670
|
+
if (selection.testFiles.length === 0) {
|
|
671
|
+
selection = selectionFromImpact(graph, fileIds, label);
|
|
672
|
+
}
|
|
673
|
+
for (const [file, info] of selfSelected) {
|
|
674
|
+
if (!selection.testFiles.some((t) => t.file === file)) {
|
|
675
|
+
selection.testFiles.push({ file, depth: info.depth, via: info.via });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
selection.testFiles.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
|
|
679
|
+
return selection;
|
|
680
|
+
}
|
|
681
|
+
var TEST_FILE_PATTERNS, TEST_REACH_DEPTH, TEST_REACH_LIMIT, USAGE_RELATIONS;
|
|
682
|
+
var init_testmap = __esm({
|
|
683
|
+
"src/testmap.ts"() {
|
|
684
|
+
"use strict";
|
|
685
|
+
init_cjs_shims();
|
|
686
|
+
init_impact();
|
|
687
|
+
init_query();
|
|
688
|
+
TEST_FILE_PATTERNS = [
|
|
689
|
+
/(^|\/)tests?\//,
|
|
690
|
+
// tests/ or test/ directory
|
|
691
|
+
/(^|\/)__tests__\//,
|
|
692
|
+
/(^|\/)spec\//,
|
|
693
|
+
/\.test\.[cm]?[jt]sx?$/,
|
|
694
|
+
/\.spec\.[cm]?[jt]sx?$/,
|
|
695
|
+
/(^|\/)test_[^/]+\.py$/,
|
|
696
|
+
/_test\.py$/,
|
|
697
|
+
/_test\.go$/,
|
|
698
|
+
/Tests?\.(java|cs)$/,
|
|
699
|
+
/_spec\.rb$/,
|
|
700
|
+
/_test\.rb$/
|
|
701
|
+
];
|
|
702
|
+
TEST_REACH_DEPTH = 4;
|
|
703
|
+
TEST_REACH_LIMIT = 2e3;
|
|
704
|
+
USAGE_RELATIONS = ["calls", "inherits", "implements", "mixes_in", "embeds", "references"];
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
610
708
|
// src/context.ts
|
|
611
709
|
function lineNumberOf(sourceLocation) {
|
|
612
710
|
const match = /^L(\d+)$/.exec(sourceLocation);
|
|
@@ -641,7 +739,7 @@ function snippetRange(startLine, fileLineCount, entityStartsInFile, maxLines) {
|
|
|
641
739
|
const hardEnd = nextStart !== void 0 ? nextStart - 1 : fileLineCount;
|
|
642
740
|
return { start: startLine, end: Math.min(hardEnd, startLine + maxLines - 1) };
|
|
643
741
|
}
|
|
644
|
-
async function buildContextPack(graph, task,
|
|
742
|
+
async function buildContextPack(graph, task, readFile21, options = {}) {
|
|
645
743
|
const tokenBudget = options.tokenBudget ?? DEFAULT_CONTEXT_BUDGET;
|
|
646
744
|
const maxSnippetLines = options.maxSnippetLines ?? DEFAULT_MAX_SNIPPET_LINES;
|
|
647
745
|
const ranked = rankForTask(graph, task, options.maxSeeds);
|
|
@@ -662,7 +760,7 @@ async function buildContextPack(graph, task, readFile20, options = {}) {
|
|
|
662
760
|
if (cached !== void 0) return cached;
|
|
663
761
|
let lines;
|
|
664
762
|
try {
|
|
665
|
-
lines = (await
|
|
763
|
+
lines = (await readFile21(file)).split("\n");
|
|
666
764
|
} catch {
|
|
667
765
|
lines = null;
|
|
668
766
|
}
|
|
@@ -748,78 +846,6 @@ var init_context = __esm({
|
|
|
748
846
|
}
|
|
749
847
|
});
|
|
750
848
|
|
|
751
|
-
// src/testmap.ts
|
|
752
|
-
function isTestFile(path17) {
|
|
753
|
-
return TEST_FILE_PATTERNS.some((p) => p.test(path17));
|
|
754
|
-
}
|
|
755
|
-
function selectionFromImpact(graph, targetIds, targetLabel) {
|
|
756
|
-
const best = /* @__PURE__ */ new Map();
|
|
757
|
-
let affectedNodeCount = 0;
|
|
758
|
-
for (const id of targetIds) {
|
|
759
|
-
const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT });
|
|
760
|
-
if (!impact) continue;
|
|
761
|
-
affectedNodeCount += impact.affected.length;
|
|
762
|
-
for (const node of impact.affected) {
|
|
763
|
-
if (!isTestFile(node.sourceFile)) continue;
|
|
764
|
-
const existing = best.get(node.sourceFile);
|
|
765
|
-
if (!existing || node.depth < existing.depth) {
|
|
766
|
-
best.set(node.sourceFile, { depth: node.depth, via: node.via.dependsOn });
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
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));
|
|
771
|
-
return { target: targetLabel, testFiles, affectedNodeCount };
|
|
772
|
-
}
|
|
773
|
-
function testsForNode(graph, nodeQuery) {
|
|
774
|
-
const match = resolveNode(graph, nodeQuery);
|
|
775
|
-
if (!match) return null;
|
|
776
|
-
return selectionFromImpact(graph, [match.id], match.id);
|
|
777
|
-
}
|
|
778
|
-
function testsForChangedFiles(graph, changedFiles) {
|
|
779
|
-
const fileIds = [];
|
|
780
|
-
const selfSelected = /* @__PURE__ */ new Map();
|
|
781
|
-
for (const file of changedFiles) {
|
|
782
|
-
if (isTestFile(file)) {
|
|
783
|
-
selfSelected.set(file, { depth: 0, via: "(changed directly)" });
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
if (graph.hasNode(file)) fileIds.push(file);
|
|
787
|
-
}
|
|
788
|
-
const selection = selectionFromImpact(graph, fileIds, changedFiles.join(", "));
|
|
789
|
-
for (const [file, info] of selfSelected) {
|
|
790
|
-
if (!selection.testFiles.some((t) => t.file === file)) {
|
|
791
|
-
selection.testFiles.push({ file, depth: info.depth, via: info.via });
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
selection.testFiles.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
|
|
795
|
-
return selection;
|
|
796
|
-
}
|
|
797
|
-
var TEST_FILE_PATTERNS, TEST_REACH_DEPTH, TEST_REACH_LIMIT;
|
|
798
|
-
var init_testmap = __esm({
|
|
799
|
-
"src/testmap.ts"() {
|
|
800
|
-
"use strict";
|
|
801
|
-
init_cjs_shims();
|
|
802
|
-
init_impact();
|
|
803
|
-
init_query();
|
|
804
|
-
TEST_FILE_PATTERNS = [
|
|
805
|
-
/(^|\/)tests?\//,
|
|
806
|
-
// tests/ or test/ directory
|
|
807
|
-
/(^|\/)__tests__\//,
|
|
808
|
-
/(^|\/)spec\//,
|
|
809
|
-
/\.test\.[cm]?[jt]sx?$/,
|
|
810
|
-
/\.spec\.[cm]?[jt]sx?$/,
|
|
811
|
-
/(^|\/)test_[^/]+\.py$/,
|
|
812
|
-
/_test\.py$/,
|
|
813
|
-
/_test\.go$/,
|
|
814
|
-
/Tests?\.(java|cs)$/,
|
|
815
|
-
/_spec\.rb$/,
|
|
816
|
-
/_test\.rb$/
|
|
817
|
-
];
|
|
818
|
-
TEST_REACH_DEPTH = 4;
|
|
819
|
-
TEST_REACH_LIMIT = 2e3;
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
849
|
// src/mcp/server.ts
|
|
824
850
|
var server_exports = {};
|
|
825
851
|
__export(server_exports, {
|
|
@@ -914,7 +940,7 @@ function createGraphifyMcpServer(outDir) {
|
|
|
914
940
|
},
|
|
915
941
|
async ({ task, budget }) => {
|
|
916
942
|
const graph = await loadGraph(outDir);
|
|
917
|
-
const pack = await buildContextPack(graph, task, (p) =>
|
|
943
|
+
const pack = await buildContextPack(graph, task, (p) => fs23.readFile(p, "utf-8"), {
|
|
918
944
|
tokenBudget: budget
|
|
919
945
|
});
|
|
920
946
|
return textResult(renderContextPack(pack));
|
|
@@ -940,23 +966,23 @@ function createGraphifyMcpServer(outDir) {
|
|
|
940
966
|
return server;
|
|
941
967
|
}
|
|
942
968
|
async function startServer(graphPath, transport = new import_stdio.StdioServerTransport()) {
|
|
943
|
-
const outDir =
|
|
944
|
-
const fileName =
|
|
969
|
+
const outDir = path16.dirname(path16.resolve(graphPath));
|
|
970
|
+
const fileName = path16.basename(graphPath);
|
|
945
971
|
validateGraphPath(fileName, outDir);
|
|
946
972
|
const server = createGraphifyMcpServer(outDir);
|
|
947
973
|
await server.connect(transport);
|
|
948
974
|
}
|
|
949
975
|
async function main(argv = process.argv) {
|
|
950
|
-
const graphPath = argv[2] ??
|
|
976
|
+
const graphPath = argv[2] ?? path16.join(process.cwd(), "graphify-out", "graph.json");
|
|
951
977
|
await startServer(graphPath);
|
|
952
978
|
}
|
|
953
|
-
var
|
|
979
|
+
var fs23, path16, import_mcp, import_stdio, import_zod2;
|
|
954
980
|
var init_server = __esm({
|
|
955
981
|
"src/mcp/server.ts"() {
|
|
956
982
|
"use strict";
|
|
957
983
|
init_cjs_shims();
|
|
958
|
-
|
|
959
|
-
|
|
984
|
+
fs23 = __toESM(require("fs/promises"), 1);
|
|
985
|
+
path16 = __toESM(require("path"), 1);
|
|
960
986
|
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
961
987
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
962
988
|
import_zod2 = require("zod");
|
|
@@ -1131,9 +1157,9 @@ var init_mysql = __esm({
|
|
|
1131
1157
|
// src/cli/index.ts
|
|
1132
1158
|
init_cjs_shims();
|
|
1133
1159
|
var import_node_child_process3 = require("child_process");
|
|
1134
|
-
var
|
|
1160
|
+
var fs24 = __toESM(require("fs/promises"), 1);
|
|
1135
1161
|
var os3 = __toESM(require("os"), 1);
|
|
1136
|
-
var
|
|
1162
|
+
var path17 = __toESM(require("path"), 1);
|
|
1137
1163
|
var import_node_util3 = require("util");
|
|
1138
1164
|
var import_chokidar = require("chokidar");
|
|
1139
1165
|
var import_commander = require("commander");
|
|
@@ -1465,8 +1491,8 @@ init_graphStore();
|
|
|
1465
1491
|
|
|
1466
1492
|
// src/pipeline.ts
|
|
1467
1493
|
init_cjs_shims();
|
|
1468
|
-
var
|
|
1469
|
-
var
|
|
1494
|
+
var fs14 = __toESM(require("fs/promises"), 1);
|
|
1495
|
+
var path9 = __toESM(require("path"), 1);
|
|
1470
1496
|
|
|
1471
1497
|
// src/build.ts
|
|
1472
1498
|
init_cjs_shims();
|
|
@@ -3138,10 +3164,10 @@ async function extractRust(filePath, displayPath) {
|
|
|
3138
3164
|
}
|
|
3139
3165
|
}
|
|
3140
3166
|
} else if (fn.type === "scoped_identifier") {
|
|
3141
|
-
const
|
|
3167
|
+
const path18 = fn.childForFieldName("path")?.text;
|
|
3142
3168
|
const name = fn.childForFieldName("name")?.text;
|
|
3143
|
-
if (
|
|
3144
|
-
const viaImport = importIndex.get(
|
|
3169
|
+
if (path18 && name) {
|
|
3170
|
+
const viaImport = importIndex.get(path18);
|
|
3145
3171
|
if (viaImport) {
|
|
3146
3172
|
targetId = viaImport.id;
|
|
3147
3173
|
confidence = "INFERRED";
|
|
@@ -3213,13 +3239,11 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
3213
3239
|
});
|
|
3214
3240
|
}
|
|
3215
3241
|
function importTargetId(binding, property) {
|
|
3216
|
-
if (
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
return entityId(binding.ref.id, property);
|
|
3222
|
-
}
|
|
3242
|
+
if (binding.kind === "named" && binding.importedName) {
|
|
3243
|
+
return entityId(binding.ref.id, binding.importedName);
|
|
3244
|
+
}
|
|
3245
|
+
if (binding.kind === "namespace" && property) {
|
|
3246
|
+
return entityId(binding.ref.id, property);
|
|
3223
3247
|
}
|
|
3224
3248
|
return binding.ref.id;
|
|
3225
3249
|
}
|
|
@@ -3257,24 +3281,35 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
3257
3281
|
}
|
|
3258
3282
|
}
|
|
3259
3283
|
}
|
|
3284
|
+
function unwrapExpression(node) {
|
|
3285
|
+
let current = node;
|
|
3286
|
+
while (current.type === "as_expression" || current.type === "satisfies_expression" || current.type === "non_null_expression" || current.type === "parenthesized_expression") {
|
|
3287
|
+
const inner = namedChildren(current)[0];
|
|
3288
|
+
if (!inner) break;
|
|
3289
|
+
current = inner;
|
|
3290
|
+
}
|
|
3291
|
+
return current;
|
|
3292
|
+
}
|
|
3260
3293
|
function handleTopLevelVariableFunctions(node) {
|
|
3294
|
+
const isExported = node.parent?.type === "export_statement";
|
|
3261
3295
|
for (const declarator of namedChildren(node)) {
|
|
3262
3296
|
if (declarator.type !== "variable_declarator") continue;
|
|
3263
3297
|
const value = declarator.childForFieldName("value");
|
|
3264
3298
|
if (!value) continue;
|
|
3265
|
-
|
|
3299
|
+
const core = unwrapExpression(value);
|
|
3300
|
+
if (core.type === "arrow_function" || core.type === "function_expression") {
|
|
3266
3301
|
const name = declarator.childForFieldName("name")?.text;
|
|
3267
3302
|
if (!name) continue;
|
|
3268
3303
|
const id = entityId(sourceFile, name);
|
|
3269
3304
|
builder.addNode({ id, label: `function ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
|
|
3270
3305
|
builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
3271
3306
|
nameIndex.set(name, id);
|
|
3272
|
-
functionNodeMap.set(
|
|
3307
|
+
functionNodeMap.set(core.id, id);
|
|
3273
3308
|
continue;
|
|
3274
3309
|
}
|
|
3275
|
-
if (
|
|
3276
|
-
const callee =
|
|
3277
|
-
const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(
|
|
3310
|
+
if (core.type === "call_expression") {
|
|
3311
|
+
const callee = core.childForFieldName("function");
|
|
3312
|
+
const specifier = callee?.type === "identifier" && callee.text === "require" ? firstStringArgument(core) : null;
|
|
3278
3313
|
if (specifier) {
|
|
3279
3314
|
const nameNode = declarator.childForFieldName("name");
|
|
3280
3315
|
if (nameNode) {
|
|
@@ -3282,6 +3317,21 @@ async function extractTypeScript(filePath, displayPath) {
|
|
|
3282
3317
|
addModuleNode(ref);
|
|
3283
3318
|
registerRequireBinding(nameNode, ref);
|
|
3284
3319
|
}
|
|
3320
|
+
continue;
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
if (isExported) {
|
|
3324
|
+
const name = declarator.childForFieldName("name")?.text;
|
|
3325
|
+
if (!name) continue;
|
|
3326
|
+
const id = entityId(sourceFile, name);
|
|
3327
|
+
builder.addNode({ id, label: `const ${name}`, sourceFile, sourceLocation: lineOf(declarator) });
|
|
3328
|
+
builder.addEdge({ source: fileId, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
3329
|
+
nameIndex.set(name, id);
|
|
3330
|
+
if (core.type === "identifier") {
|
|
3331
|
+
const aliased = nameIndex.get(core.text);
|
|
3332
|
+
if (aliased !== void 0 && aliased !== id) {
|
|
3333
|
+
builder.addEdge({ source: id, target: aliased, relation: "references", confidence: "EXTRACTED" });
|
|
3334
|
+
}
|
|
3285
3335
|
}
|
|
3286
3336
|
}
|
|
3287
3337
|
}
|
|
@@ -3601,6 +3651,8 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
|
|
|
3601
3651
|
|
|
3602
3652
|
// src/resolve.ts
|
|
3603
3653
|
init_cjs_shims();
|
|
3654
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3655
|
+
var path8 = __toESM(require("path"), 1);
|
|
3604
3656
|
var CODE_EXTENSIONS2 = [
|
|
3605
3657
|
".ts",
|
|
3606
3658
|
".tsx",
|
|
@@ -3620,6 +3672,29 @@ var CODE_EXTENSIONS2 = [
|
|
|
3620
3672
|
function isOpaque(id) {
|
|
3621
3673
|
return id.startsWith("module:") || id.startsWith("external:") || id.startsWith("db:");
|
|
3622
3674
|
}
|
|
3675
|
+
var SELF_IMPORT_STEMS = (subpath) => subpath === "" ? ["index", "src/index", "lib/index", "source/index", "src/main"] : [
|
|
3676
|
+
subpath,
|
|
3677
|
+
`src/${subpath}`,
|
|
3678
|
+
`lib/${subpath}`,
|
|
3679
|
+
`source/${subpath}`,
|
|
3680
|
+
`${subpath}/index`,
|
|
3681
|
+
`src/${subpath}/index`,
|
|
3682
|
+
`lib/${subpath}/index`
|
|
3683
|
+
];
|
|
3684
|
+
function selfImportCandidates(spec, selfNames) {
|
|
3685
|
+
const selfName = selfNames.find((n) => spec === n || spec.startsWith(`${n}/`));
|
|
3686
|
+
if (selfName === void 0) return null;
|
|
3687
|
+
const subpath = spec === selfName ? "" : spec.slice(selfName.length + 1);
|
|
3688
|
+
return SELF_IMPORT_STEMS(subpath).flatMap((stem) => CODE_EXTENSIONS2.map((ext) => `${stem}${ext}`));
|
|
3689
|
+
}
|
|
3690
|
+
async function readSelfNames(root) {
|
|
3691
|
+
try {
|
|
3692
|
+
const pkg = JSON.parse(await fs13.readFile(path8.join(root, "package.json"), "utf-8"));
|
|
3693
|
+
return typeof pkg.name === "string" && pkg.name.length > 0 ? [pkg.name] : [];
|
|
3694
|
+
} catch {
|
|
3695
|
+
return [];
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3623
3698
|
function stripKnownExtension(p) {
|
|
3624
3699
|
for (const ext of CODE_EXTENSIONS2) {
|
|
3625
3700
|
if (p.endsWith(ext)) return p.slice(0, -ext.length);
|
|
@@ -3636,19 +3711,23 @@ function candidatePaths(guessed) {
|
|
|
3636
3711
|
}
|
|
3637
3712
|
return candidates.filter((c) => c !== guessed);
|
|
3638
3713
|
}
|
|
3639
|
-
function uniqueMatch(
|
|
3714
|
+
function uniqueMatch(candidates, real) {
|
|
3640
3715
|
const matches = /* @__PURE__ */ new Set();
|
|
3641
3716
|
for (const candidate of candidates) {
|
|
3642
|
-
if (
|
|
3717
|
+
if (real.has(candidate)) matches.add(candidate);
|
|
3643
3718
|
}
|
|
3644
|
-
|
|
3645
|
-
return [...matches][0];
|
|
3719
|
+
return matches.size === 1 ? [...matches][0] : null;
|
|
3646
3720
|
}
|
|
3647
|
-
function resolveCrossFileReferences(extractions) {
|
|
3721
|
+
function resolveCrossFileReferences(extractions, options = {}) {
|
|
3722
|
+
const selfNames = options.selfNames ?? [];
|
|
3648
3723
|
const realIds = /* @__PURE__ */ new Set();
|
|
3649
3724
|
const realFiles = /* @__PURE__ */ new Set();
|
|
3650
3725
|
for (const extraction of extractions) {
|
|
3651
3726
|
for (const node of extraction.nodes) realIds.add(node.id);
|
|
3727
|
+
const first = extraction.nodes[0];
|
|
3728
|
+
if (first && first.id === first.sourceFile && !isOpaque(first.id)) {
|
|
3729
|
+
realFiles.add(first.id);
|
|
3730
|
+
}
|
|
3652
3731
|
for (const node of extraction.nodes) {
|
|
3653
3732
|
const sep3 = node.id.indexOf("::");
|
|
3654
3733
|
if (sep3 > 0) realFiles.add(node.id.slice(0, sep3));
|
|
@@ -3658,7 +3737,24 @@ function resolveCrossFileReferences(extractions) {
|
|
|
3658
3737
|
}
|
|
3659
3738
|
}
|
|
3660
3739
|
const remap = /* @__PURE__ */ new Map();
|
|
3661
|
-
|
|
3740
|
+
let resolvedEndpoints = 0;
|
|
3741
|
+
const applyRemap = (resolveId) => {
|
|
3742
|
+
for (const extraction of extractions) {
|
|
3743
|
+
for (const edge of extraction.edges) {
|
|
3744
|
+
const source = resolveId(edge.source);
|
|
3745
|
+
if (source !== null) {
|
|
3746
|
+
edge.source = source;
|
|
3747
|
+
resolvedEndpoints++;
|
|
3748
|
+
}
|
|
3749
|
+
const target = resolveId(edge.target);
|
|
3750
|
+
if (target !== null) {
|
|
3751
|
+
edge.target = target;
|
|
3752
|
+
resolvedEndpoints++;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
};
|
|
3757
|
+
const resolvePathId = (id) => {
|
|
3662
3758
|
if (isOpaque(id)) return null;
|
|
3663
3759
|
const cached = remap.get(id);
|
|
3664
3760
|
if (cached !== void 0) return cached;
|
|
@@ -3668,30 +3764,77 @@ function resolveCrossFileReferences(extractions) {
|
|
|
3668
3764
|
if (realIds.has(id)) return null;
|
|
3669
3765
|
const guessedPath = id.slice(0, sep3);
|
|
3670
3766
|
const name = id.slice(sep3);
|
|
3671
|
-
|
|
3672
|
-
resolved = uniqueMatch(id, candidates, realIds);
|
|
3767
|
+
resolved = uniqueMatch(candidatePaths(guessedPath).map((p) => `${p}${name}`), realIds);
|
|
3673
3768
|
} else {
|
|
3674
3769
|
if (realFiles.has(id)) return null;
|
|
3675
|
-
resolved = uniqueMatch(
|
|
3770
|
+
resolved = uniqueMatch(candidatePaths(id), realFiles);
|
|
3676
3771
|
}
|
|
3677
3772
|
if (resolved !== null) remap.set(id, resolved);
|
|
3678
3773
|
return resolved;
|
|
3679
3774
|
};
|
|
3680
|
-
|
|
3775
|
+
applyRemap(resolvePathId);
|
|
3776
|
+
const namedAlias = /* @__PURE__ */ new Map();
|
|
3777
|
+
const starTargets = /* @__PURE__ */ new Map();
|
|
3681
3778
|
for (const extraction of extractions) {
|
|
3682
3779
|
for (const edge of extraction.edges) {
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
edge.
|
|
3691
|
-
resolvedEndpoints++;
|
|
3780
|
+
if (edge.relation !== "re_exports" || !realFiles.has(edge.source)) continue;
|
|
3781
|
+
const sep3 = edge.target.lastIndexOf("::");
|
|
3782
|
+
if (sep3 > 0 && realIds.has(edge.target)) {
|
|
3783
|
+
namedAlias.set(`${edge.source}|${edge.target.slice(sep3 + 2)}`, edge.target);
|
|
3784
|
+
} else if (sep3 < 0 && realFiles.has(edge.target)) {
|
|
3785
|
+
const targets = starTargets.get(edge.source) ?? [];
|
|
3786
|
+
targets.push(edge.target);
|
|
3787
|
+
starTargets.set(edge.source, targets);
|
|
3692
3788
|
}
|
|
3693
3789
|
}
|
|
3694
3790
|
}
|
|
3791
|
+
const throughBarrel = (file, name) => {
|
|
3792
|
+
const direct = `${file}::${name}`;
|
|
3793
|
+
if (realIds.has(direct)) return direct;
|
|
3794
|
+
const aliased = namedAlias.get(`${file}|${name}`);
|
|
3795
|
+
if (aliased !== void 0) return aliased;
|
|
3796
|
+
const viaStar = (starTargets.get(file) ?? []).map((sf) => `${sf}::${name}`).filter((id) => realIds.has(id));
|
|
3797
|
+
return viaStar.length === 1 ? viaStar[0] : null;
|
|
3798
|
+
};
|
|
3799
|
+
const resolveBarrelId = (id) => {
|
|
3800
|
+
const cached = remap.get(id);
|
|
3801
|
+
if (cached !== void 0) return cached;
|
|
3802
|
+
if (id.startsWith("module:")) {
|
|
3803
|
+
const spec = id.slice("module:".length);
|
|
3804
|
+
const sep4 = spec.indexOf("::");
|
|
3805
|
+
const moduleSpec = sep4 > 0 ? spec.slice(0, sep4) : spec;
|
|
3806
|
+
const name = sep4 > 0 ? spec.slice(sep4 + 2) : null;
|
|
3807
|
+
const candidates = selfImportCandidates(moduleSpec, selfNames);
|
|
3808
|
+
if (candidates === null) {
|
|
3809
|
+
if (name === null) return null;
|
|
3810
|
+
remap.set(id, `module:${moduleSpec}`);
|
|
3811
|
+
return `module:${moduleSpec}`;
|
|
3812
|
+
}
|
|
3813
|
+
const file = uniqueMatch(candidates, realFiles);
|
|
3814
|
+
let resolved = null;
|
|
3815
|
+
if (file !== null) {
|
|
3816
|
+
resolved = name !== null ? throughBarrel(file, name) ?? file : file;
|
|
3817
|
+
} else if (name !== null) {
|
|
3818
|
+
resolved = `module:${moduleSpec}`;
|
|
3819
|
+
}
|
|
3820
|
+
if (resolved !== null) remap.set(id, resolved);
|
|
3821
|
+
return resolved;
|
|
3822
|
+
}
|
|
3823
|
+
const sep3 = id.indexOf("::");
|
|
3824
|
+
if (sep3 > 0 && !realIds.has(id)) {
|
|
3825
|
+
const guessedPath = id.slice(0, sep3);
|
|
3826
|
+
const name = id.slice(sep3 + 2);
|
|
3827
|
+
const files = [guessedPath, ...candidatePaths(guessedPath)].filter((f) => realFiles.has(f));
|
|
3828
|
+
const resolvedSet = new Set(files.map((f) => throughBarrel(f, name)).filter((r) => r !== null));
|
|
3829
|
+
if (resolvedSet.size === 1) {
|
|
3830
|
+
const resolved = [...resolvedSet][0];
|
|
3831
|
+
remap.set(id, resolved);
|
|
3832
|
+
return resolved;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return null;
|
|
3836
|
+
};
|
|
3837
|
+
applyRemap(resolveBarrelId);
|
|
3695
3838
|
let droppedPlaceholders = 0;
|
|
3696
3839
|
for (const extraction of extractions) {
|
|
3697
3840
|
const before = extraction.nodes.length;
|
|
@@ -3705,21 +3848,21 @@ function resolveCrossFileReferences(extractions) {
|
|
|
3705
3848
|
async function runPipeline(root, options = {}) {
|
|
3706
3849
|
const progress = options.onProgress ?? (() => {
|
|
3707
3850
|
});
|
|
3708
|
-
const resolvedRoot =
|
|
3851
|
+
const resolvedRoot = path9.resolve(root);
|
|
3709
3852
|
progress(`Scanning ${resolvedRoot} ...`);
|
|
3710
3853
|
const manifest = collectFiles(resolvedRoot);
|
|
3711
3854
|
progress(
|
|
3712
3855
|
`Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
|
|
3713
3856
|
);
|
|
3714
|
-
const outDir = options.outDir ??
|
|
3857
|
+
const outDir = options.outDir ?? path9.join(resolvedRoot, "graphify-out");
|
|
3715
3858
|
const cache = new ExtractionCache(outDir);
|
|
3716
3859
|
progress("Extracting...");
|
|
3717
3860
|
const extractions = [];
|
|
3718
3861
|
let cacheHits = 0;
|
|
3719
3862
|
for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
|
|
3720
|
-
const filePath =
|
|
3863
|
+
const filePath = path9.relative(process.cwd(), path9.join(resolvedRoot, relFile)) || relFile;
|
|
3721
3864
|
try {
|
|
3722
|
-
const key = cache.key(relFile, await
|
|
3865
|
+
const key = cache.key(relFile, await fs14.readFile(path9.join(resolvedRoot, relFile)));
|
|
3723
3866
|
let extraction = options.update ? await cache.get(key) : null;
|
|
3724
3867
|
if (extraction) {
|
|
3725
3868
|
cacheHits++;
|
|
@@ -3740,7 +3883,9 @@ async function runPipeline(root, options = {}) {
|
|
|
3740
3883
|
extractions.push(...options.extraExtractions);
|
|
3741
3884
|
}
|
|
3742
3885
|
progress("Resolving cross-file references...");
|
|
3743
|
-
const resolveStats = resolveCrossFileReferences(extractions
|
|
3886
|
+
const resolveStats = resolveCrossFileReferences(extractions, {
|
|
3887
|
+
selfNames: await readSelfNames(resolvedRoot)
|
|
3888
|
+
});
|
|
3744
3889
|
if (resolveStats.resolvedEndpoints > 0) {
|
|
3745
3890
|
progress(
|
|
3746
3891
|
` re-pointed ${resolveStats.resolvedEndpoints} guessed reference(s) at real nodes (${resolveStats.droppedPlaceholders} placeholder node(s) dropped).`
|
|
@@ -3797,9 +3942,10 @@ async function runAffected(nodeName, options = {}) {
|
|
|
3797
3942
|
console.log("");
|
|
3798
3943
|
console.log(currentDepth === 1 ? "Directly affected:" : `Affected at depth ${currentDepth}:`);
|
|
3799
3944
|
}
|
|
3800
|
-
const
|
|
3945
|
+
const isPlaceholder = node.sourceFile === "<unknown>" || node.sourceFile === "<external>" || node.sourceFile === "";
|
|
3946
|
+
const where = isPlaceholder ? "(reference only)" : `${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ""}`;
|
|
3801
3947
|
console.log(
|
|
3802
|
-
` ${node.label} \u2014 ${
|
|
3948
|
+
` ${node.label} \u2014 ${where} (--${node.via.relation}--> ${node.via.dependsOn}, ${node.via.confidence})`
|
|
3803
3949
|
);
|
|
3804
3950
|
}
|
|
3805
3951
|
console.log("");
|
|
@@ -3814,11 +3960,12 @@ async function runAffected(nodeName, options = {}) {
|
|
|
3814
3960
|
|
|
3815
3961
|
// src/cli/commands/benchmark.ts
|
|
3816
3962
|
init_cjs_shims();
|
|
3817
|
-
var
|
|
3963
|
+
var fs15 = __toESM(require("fs/promises"), 1);
|
|
3818
3964
|
init_graphStore();
|
|
3819
3965
|
init_query();
|
|
3966
|
+
init_testmap();
|
|
3820
3967
|
var tokensOf = (text) => Math.ceil(text.length / 4);
|
|
3821
|
-
async function benchmarkQuestion(graph, question,
|
|
3968
|
+
async function benchmarkQuestion(graph, question, readFile21) {
|
|
3822
3969
|
const result = queryGraph(graph, question);
|
|
3823
3970
|
const graphTokens = tokensOf(JSON.stringify(result));
|
|
3824
3971
|
const sourceFiles = /* @__PURE__ */ new Set();
|
|
@@ -3832,7 +3979,7 @@ async function benchmarkQuestion(graph, question, readFile20) {
|
|
|
3832
3979
|
let filesMissing = 0;
|
|
3833
3980
|
for (const file of [...sourceFiles].sort((a, b) => a.localeCompare(b))) {
|
|
3834
3981
|
try {
|
|
3835
|
-
naiveTokens += tokensOf(await
|
|
3982
|
+
naiveTokens += tokensOf(await readFile21(file));
|
|
3836
3983
|
filesRead++;
|
|
3837
3984
|
} catch {
|
|
3838
3985
|
filesMissing++;
|
|
@@ -3847,12 +3994,14 @@ async function benchmarkQuestion(graph, question, readFile20) {
|
|
|
3847
3994
|
filesMissing
|
|
3848
3995
|
};
|
|
3849
3996
|
}
|
|
3997
|
+
var NON_QUESTION_FILE = /(^|\/)(examples?|scripts?|bench(?:mark)?s?|fixtures?|docs?|\.github)\/|\.(config|conf)\.[^/]+$|(^|\/)(rollup|webpack|vite|vitest|jest|babel|tsup|eslint|prettier)\.[^/]*$/i;
|
|
3850
3998
|
function defaultQuestions(graph, count = 5) {
|
|
3851
3999
|
const entries = [...graph.nodes()].map((id) => ({
|
|
3852
4000
|
id,
|
|
3853
4001
|
degree: graph.degree(id),
|
|
3854
|
-
label: graph.getNodeAttribute(id, "label") ?? id
|
|
3855
|
-
|
|
4002
|
+
label: graph.getNodeAttribute(id, "label") ?? id,
|
|
4003
|
+
file: graph.getNodeAttribute(id, "sourceFile") ?? ""
|
|
4004
|
+
})).filter((e) => e.id.includes("::")).filter((e) => !isTestFile(e.file) && !NON_QUESTION_FILE.test(e.file)).sort((a, b) => b.degree - a.degree || a.id.localeCompare(b.id));
|
|
3856
4005
|
return entries.slice(0, count).map((e) => `how does ${e.label} work`);
|
|
3857
4006
|
}
|
|
3858
4007
|
async function runBenchmark(questions) {
|
|
@@ -3864,7 +4013,7 @@ async function runBenchmark(questions) {
|
|
|
3864
4013
|
}
|
|
3865
4014
|
const rows = [];
|
|
3866
4015
|
for (const question of effective) {
|
|
3867
|
-
rows.push(await benchmarkQuestion(graph, question, (p) =>
|
|
4016
|
+
rows.push(await benchmarkQuestion(graph, question, (p) => fs15.readFile(p, "utf-8")));
|
|
3868
4017
|
}
|
|
3869
4018
|
console.log("Token cost per question \u2014 graph answer vs reading the files it touches:\n");
|
|
3870
4019
|
for (const row of rows) {
|
|
@@ -3887,8 +4036,8 @@ Average reduction: ${avg.toFixed(1)}x across ${scored.length} question(s).`);
|
|
|
3887
4036
|
|
|
3888
4037
|
// src/cli/commands/check.ts
|
|
3889
4038
|
init_cjs_shims();
|
|
3890
|
-
var
|
|
3891
|
-
var
|
|
4039
|
+
var fs16 = __toESM(require("fs/promises"), 1);
|
|
4040
|
+
var path10 = __toESM(require("path"), 1);
|
|
3892
4041
|
|
|
3893
4042
|
// src/check.ts
|
|
3894
4043
|
init_cjs_shims();
|
|
@@ -3959,10 +4108,10 @@ function checkRules(graph, config) {
|
|
|
3959
4108
|
// src/cli/commands/check.ts
|
|
3960
4109
|
init_graphStore();
|
|
3961
4110
|
async function runCheck(options = {}) {
|
|
3962
|
-
const rulesPath =
|
|
4111
|
+
const rulesPath = path10.resolve(options.rules ?? "graphify.rules.json");
|
|
3963
4112
|
let raw;
|
|
3964
4113
|
try {
|
|
3965
|
-
raw = await
|
|
4114
|
+
raw = await fs16.readFile(rulesPath, "utf-8");
|
|
3966
4115
|
} catch {
|
|
3967
4116
|
throw new Error(
|
|
3968
4117
|
`No rules file at ${rulesPath} \u2014 create graphify.rules.json with { "rules": [{ "name": "...", "from": "src/a/**", "disallow": ["src/b/**"] }] }.`
|
|
@@ -3985,12 +4134,12 @@ async function runCheck(options = {}) {
|
|
|
3985
4134
|
|
|
3986
4135
|
// src/cli/commands/context.ts
|
|
3987
4136
|
init_cjs_shims();
|
|
3988
|
-
var
|
|
4137
|
+
var fs17 = __toESM(require("fs/promises"), 1);
|
|
3989
4138
|
init_context();
|
|
3990
4139
|
init_graphStore();
|
|
3991
4140
|
async function runContext(task, options = {}) {
|
|
3992
4141
|
const graph = await loadGraph();
|
|
3993
|
-
const pack = await buildContextPack(graph, task, (p) =>
|
|
4142
|
+
const pack = await buildContextPack(graph, task, (p) => fs17.readFile(p, "utf-8"), {
|
|
3994
4143
|
tokenBudget: options.budget
|
|
3995
4144
|
});
|
|
3996
4145
|
console.log(renderContextPack(pack));
|
|
@@ -4007,9 +4156,13 @@ async function runExplain(nodeName) {
|
|
|
4007
4156
|
console.log(`No node matched "${nodeName}".`);
|
|
4008
4157
|
return;
|
|
4009
4158
|
}
|
|
4010
|
-
const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
|
|
4011
4159
|
console.log(result.label);
|
|
4012
|
-
|
|
4160
|
+
if (result.sourceFile === "<unknown>" || result.sourceFile === "<external>" || result.sourceFile === "") {
|
|
4161
|
+
console.log(" location: (reference only \u2014 no declaration seen; check its outgoing edges for the real definition)");
|
|
4162
|
+
} else {
|
|
4163
|
+
const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
|
|
4164
|
+
console.log(` location: ${result.sourceFile}${location}`);
|
|
4165
|
+
}
|
|
4013
4166
|
if (result.communityLabel) {
|
|
4014
4167
|
console.log(` community: ${result.communityLabel}`);
|
|
4015
4168
|
}
|
|
@@ -4039,9 +4192,9 @@ init_cjs_shims();
|
|
|
4039
4192
|
// src/diff.ts
|
|
4040
4193
|
init_cjs_shims();
|
|
4041
4194
|
var import_node_child_process = require("child_process");
|
|
4042
|
-
var
|
|
4195
|
+
var fs18 = __toESM(require("fs/promises"), 1);
|
|
4043
4196
|
var os = __toESM(require("os"), 1);
|
|
4044
|
-
var
|
|
4197
|
+
var path11 = __toESM(require("path"), 1);
|
|
4045
4198
|
var import_node_util = require("util");
|
|
4046
4199
|
init_impact();
|
|
4047
4200
|
init_testmap();
|
|
@@ -4050,13 +4203,13 @@ async function graphForDirectory(root) {
|
|
|
4050
4203
|
const manifest = collectFiles(root);
|
|
4051
4204
|
const extractions = [];
|
|
4052
4205
|
for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
|
|
4053
|
-
extractions.push(await extract(
|
|
4206
|
+
extractions.push(await extract(path11.join(root, relFile), relFile));
|
|
4054
4207
|
}
|
|
4055
|
-
resolveCrossFileReferences(extractions);
|
|
4208
|
+
resolveCrossFileReferences(extractions, { selfNames: await readSelfNames(root) });
|
|
4056
4209
|
return buildGraph(extractions);
|
|
4057
4210
|
}
|
|
4058
4211
|
async function checkoutRev(repoRoot, rev) {
|
|
4059
|
-
const dest = await
|
|
4212
|
+
const dest = await fs18.mkdtemp(path11.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
|
|
4060
4213
|
const { stdout } = await execFileAsync(
|
|
4061
4214
|
"git",
|
|
4062
4215
|
["-C", repoRoot, "archive", "--format=tar", rev],
|
|
@@ -4124,8 +4277,8 @@ async function reviewRevisions(repoRoot, base, head = "HEAD") {
|
|
|
4124
4277
|
return diffGraphs(baseGraph, headGraph, base, head);
|
|
4125
4278
|
} finally {
|
|
4126
4279
|
await Promise.all([
|
|
4127
|
-
|
|
4128
|
-
|
|
4280
|
+
fs18.rm(baseDir, { recursive: true, force: true }),
|
|
4281
|
+
fs18.rm(headDir, { recursive: true, force: true })
|
|
4129
4282
|
]);
|
|
4130
4283
|
}
|
|
4131
4284
|
}
|
|
@@ -4208,7 +4361,8 @@ async function runTests(nodeQuery, options = {}) {
|
|
|
4208
4361
|
);
|
|
4209
4362
|
return;
|
|
4210
4363
|
}
|
|
4211
|
-
|
|
4364
|
+
const how = selection.precision === "usage" ? "selected by actual usage (calls/inheritance)" : "selected by import reachability \u2014 may over-include files sharing an entry point";
|
|
4365
|
+
console.log(`Test files for ${selection.target} (${how}; most direct first):`);
|
|
4212
4366
|
for (const test of selection.testFiles) {
|
|
4213
4367
|
const via = test.depth === 0 ? test.via : `depth ${test.depth}, via ${test.via}`;
|
|
4214
4368
|
console.log(` ${test.file} (${via})`);
|
|
@@ -4217,9 +4371,9 @@ async function runTests(nodeQuery, options = {}) {
|
|
|
4217
4371
|
|
|
4218
4372
|
// src/cli/commands/global.ts
|
|
4219
4373
|
init_cjs_shims();
|
|
4220
|
-
var
|
|
4374
|
+
var fs19 = __toESM(require("fs/promises"), 1);
|
|
4221
4375
|
var os2 = __toESM(require("os"), 1);
|
|
4222
|
-
var
|
|
4376
|
+
var path12 = __toESM(require("path"), 1);
|
|
4223
4377
|
init_graphStore();
|
|
4224
4378
|
|
|
4225
4379
|
// src/merge.ts
|
|
@@ -4281,14 +4435,14 @@ function mergeGraphs(entries) {
|
|
|
4281
4435
|
|
|
4282
4436
|
// src/cli/commands/global.ts
|
|
4283
4437
|
function registryDir() {
|
|
4284
|
-
return
|
|
4438
|
+
return path12.join(os2.homedir(), ".graphify");
|
|
4285
4439
|
}
|
|
4286
4440
|
function registryPath() {
|
|
4287
|
-
return
|
|
4441
|
+
return path12.join(registryDir(), "global.json");
|
|
4288
4442
|
}
|
|
4289
4443
|
async function readRegistry() {
|
|
4290
4444
|
try {
|
|
4291
|
-
const raw = await
|
|
4445
|
+
const raw = await fs19.readFile(registryPath(), "utf-8");
|
|
4292
4446
|
const parsed = JSON.parse(raw);
|
|
4293
4447
|
const projects = parsed.projects;
|
|
4294
4448
|
if (!Array.isArray(projects)) return { projects: [] };
|
|
@@ -4298,23 +4452,23 @@ async function readRegistry() {
|
|
|
4298
4452
|
}
|
|
4299
4453
|
}
|
|
4300
4454
|
async function writeRegistry(registry) {
|
|
4301
|
-
await
|
|
4302
|
-
await
|
|
4455
|
+
await fs19.mkdir(registryDir(), { recursive: true });
|
|
4456
|
+
await fs19.writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
4303
4457
|
}
|
|
4304
4458
|
async function projectNameFor(root) {
|
|
4305
4459
|
try {
|
|
4306
|
-
const raw = await
|
|
4460
|
+
const raw = await fs19.readFile(path12.join(root, "package.json"), "utf-8");
|
|
4307
4461
|
const name = JSON.parse(raw).name;
|
|
4308
4462
|
if (typeof name === "string" && name.length > 0) return name;
|
|
4309
4463
|
} catch {
|
|
4310
4464
|
}
|
|
4311
|
-
return
|
|
4465
|
+
return path12.basename(root);
|
|
4312
4466
|
}
|
|
4313
4467
|
async function runGlobalAdd(target = ".") {
|
|
4314
|
-
const root =
|
|
4315
|
-
const graphJson =
|
|
4468
|
+
const root = path12.resolve(target);
|
|
4469
|
+
const graphJson = path12.join(root, "graphify-out", "graph.json");
|
|
4316
4470
|
try {
|
|
4317
|
-
await
|
|
4471
|
+
await fs19.access(graphJson);
|
|
4318
4472
|
} catch {
|
|
4319
4473
|
throw new Error(`${root} has no graphify-out/graph.json \u2014 run \`graphify ${target}\` first, then add it.`);
|
|
4320
4474
|
}
|
|
@@ -4385,9 +4539,9 @@ async function runGlobalBuild(options = {}) {
|
|
|
4385
4539
|
throw new Error("No projects registered. Add some with `graphify global add <path>` first.");
|
|
4386
4540
|
}
|
|
4387
4541
|
const entries = await loadEntries(
|
|
4388
|
-
registry.projects.map((p) => ({ name: p.name, outDir:
|
|
4542
|
+
registry.projects.map((p) => ({ name: p.name, outDir: path12.join(p.root, "graphify-out") }))
|
|
4389
4543
|
);
|
|
4390
|
-
await mergeAndExport(entries,
|
|
4544
|
+
await mergeAndExport(entries, path12.resolve(options.out ?? path12.join(registryDir(), "global-out")));
|
|
4391
4545
|
}
|
|
4392
4546
|
async function runMerge(targets, options = {}) {
|
|
4393
4547
|
if (targets.length < 2) {
|
|
@@ -4395,8 +4549,8 @@ async function runMerge(targets, options = {}) {
|
|
|
4395
4549
|
}
|
|
4396
4550
|
const sources = [];
|
|
4397
4551
|
for (const target of targets) {
|
|
4398
|
-
const root =
|
|
4399
|
-
sources.push({ name: await projectNameFor(root), outDir:
|
|
4552
|
+
const root = path12.resolve(target);
|
|
4553
|
+
sources.push({ name: await projectNameFor(root), outDir: path12.join(root, "graphify-out") });
|
|
4400
4554
|
}
|
|
4401
4555
|
const names = sources.map((s) => s.name);
|
|
4402
4556
|
const duplicate = names.find((name, i) => names.indexOf(name) !== i);
|
|
@@ -4404,13 +4558,13 @@ async function runMerge(targets, options = {}) {
|
|
|
4404
4558
|
throw new Error(`Two of the given projects resolve to the same name ("${duplicate}") \u2014 rename one.`);
|
|
4405
4559
|
}
|
|
4406
4560
|
const entries = await loadEntries(sources);
|
|
4407
|
-
await mergeAndExport(entries,
|
|
4561
|
+
await mergeAndExport(entries, path12.resolve(options.out ?? "graphify-merged"));
|
|
4408
4562
|
}
|
|
4409
4563
|
|
|
4410
4564
|
// src/cli/commands/hook.ts
|
|
4411
4565
|
init_cjs_shims();
|
|
4412
|
-
var
|
|
4413
|
-
var
|
|
4566
|
+
var fs20 = __toESM(require("fs/promises"), 1);
|
|
4567
|
+
var path13 = __toESM(require("path"), 1);
|
|
4414
4568
|
var MARKER_BEGIN = "# >>> graphify >>>";
|
|
4415
4569
|
var MARKER_END = "# <<< graphify <<<";
|
|
4416
4570
|
var HOOK_NAMES = ["post-commit", "post-merge"];
|
|
@@ -4421,34 +4575,34 @@ var HOOK_BLOCK = `${MARKER_BEGIN}
|
|
|
4421
4575
|
(graphify . --update --no-viz >/dev/null 2>&1 &)
|
|
4422
4576
|
${MARKER_END}`;
|
|
4423
4577
|
async function hooksDir(cwd) {
|
|
4424
|
-
const gitDir =
|
|
4578
|
+
const gitDir = path13.join(cwd, ".git");
|
|
4425
4579
|
let stats;
|
|
4426
4580
|
try {
|
|
4427
|
-
stats = await
|
|
4581
|
+
stats = await fs20.stat(gitDir);
|
|
4428
4582
|
} catch {
|
|
4429
4583
|
throw new Error(`${cwd} is not a git repository (no .git directory) \u2014 run this from the repo root.`);
|
|
4430
4584
|
}
|
|
4431
4585
|
if (!stats.isDirectory()) {
|
|
4432
|
-
const content = (await
|
|
4586
|
+
const content = (await fs20.readFile(gitDir, "utf-8")).trim();
|
|
4433
4587
|
const match = /^gitdir:\s*(.+)$/.exec(content);
|
|
4434
4588
|
if (!match) throw new Error(`${gitDir} exists but is neither a directory nor a gitdir pointer.`);
|
|
4435
|
-
return
|
|
4589
|
+
return path13.resolve(cwd, match[1], "hooks");
|
|
4436
4590
|
}
|
|
4437
|
-
return
|
|
4591
|
+
return path13.join(gitDir, "hooks");
|
|
4438
4592
|
}
|
|
4439
4593
|
async function runHookInstall(cwd = process.cwd()) {
|
|
4440
4594
|
const dir = await hooksDir(cwd);
|
|
4441
|
-
await
|
|
4595
|
+
await fs20.mkdir(dir, { recursive: true });
|
|
4442
4596
|
const results = [];
|
|
4443
4597
|
for (const hook of HOOK_NAMES) {
|
|
4444
|
-
const hookPath =
|
|
4598
|
+
const hookPath = path13.join(dir, hook);
|
|
4445
4599
|
let existing = null;
|
|
4446
4600
|
try {
|
|
4447
|
-
existing = await
|
|
4601
|
+
existing = await fs20.readFile(hookPath, "utf-8");
|
|
4448
4602
|
} catch {
|
|
4449
4603
|
}
|
|
4450
4604
|
if (existing === null) {
|
|
4451
|
-
await
|
|
4605
|
+
await fs20.writeFile(hookPath, `#!/bin/sh
|
|
4452
4606
|
${HOOK_BLOCK}
|
|
4453
4607
|
`, { mode: 493 });
|
|
4454
4608
|
results.push({ hook, path: hookPath, action: "created" });
|
|
@@ -4456,9 +4610,9 @@ ${HOOK_BLOCK}
|
|
|
4456
4610
|
results.push({ hook, path: hookPath, action: "already-installed" });
|
|
4457
4611
|
} else {
|
|
4458
4612
|
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
4459
|
-
await
|
|
4613
|
+
await fs20.writeFile(hookPath, `${existing}${separator}${HOOK_BLOCK}
|
|
4460
4614
|
`, "utf-8");
|
|
4461
|
-
await
|
|
4615
|
+
await fs20.chmod(hookPath, 493);
|
|
4462
4616
|
results.push({ hook, path: hookPath, action: "appended" });
|
|
4463
4617
|
}
|
|
4464
4618
|
}
|
|
@@ -4474,10 +4628,10 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4474
4628
|
const dir = await hooksDir(cwd);
|
|
4475
4629
|
const results = [];
|
|
4476
4630
|
for (const hook of HOOK_NAMES) {
|
|
4477
|
-
const hookPath =
|
|
4631
|
+
const hookPath = path13.join(dir, hook);
|
|
4478
4632
|
let existing = null;
|
|
4479
4633
|
try {
|
|
4480
|
-
existing = await
|
|
4634
|
+
existing = await fs20.readFile(hookPath, "utf-8");
|
|
4481
4635
|
} catch {
|
|
4482
4636
|
}
|
|
4483
4637
|
if (existing === null || !existing.includes(MARKER_BEGIN)) {
|
|
@@ -4487,9 +4641,9 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4487
4641
|
const blockPattern = new RegExp(`\\n?${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}\\n?`);
|
|
4488
4642
|
const remaining = existing.replace(blockPattern, "\n").trim();
|
|
4489
4643
|
if (remaining === "" || remaining === "#!/bin/sh") {
|
|
4490
|
-
await
|
|
4644
|
+
await fs20.unlink(hookPath);
|
|
4491
4645
|
} else {
|
|
4492
|
-
await
|
|
4646
|
+
await fs20.writeFile(hookPath, `${remaining}
|
|
4493
4647
|
`, "utf-8");
|
|
4494
4648
|
}
|
|
4495
4649
|
results.push({ hook, path: hookPath, action: "removed" });
|
|
@@ -4503,8 +4657,8 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4503
4657
|
// src/memory.ts
|
|
4504
4658
|
init_cjs_shims();
|
|
4505
4659
|
var import_node_crypto4 = require("crypto");
|
|
4506
|
-
var
|
|
4507
|
-
var
|
|
4660
|
+
var fs21 = __toESM(require("fs/promises"), 1);
|
|
4661
|
+
var path14 = __toESM(require("path"), 1);
|
|
4508
4662
|
var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
|
|
4509
4663
|
var TYPES = /* @__PURE__ */ new Set(["query", "path", "explain", "affected"]);
|
|
4510
4664
|
function validateResult(value) {
|
|
@@ -4522,25 +4676,25 @@ function validateResult(value) {
|
|
|
4522
4676
|
}
|
|
4523
4677
|
async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
|
|
4524
4678
|
validateResult(entry);
|
|
4525
|
-
await
|
|
4679
|
+
await fs21.mkdir(memoryDir, { recursive: true });
|
|
4526
4680
|
const saved = { ...entry, savedAt: now.toISOString() };
|
|
4527
4681
|
const hash = (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
|
|
4528
4682
|
const stamp = saved.savedAt.replace(/[:.]/g, "-");
|
|
4529
|
-
const filePath =
|
|
4530
|
-
await
|
|
4683
|
+
const filePath = path14.join(memoryDir, `result-${stamp}-${hash}.json`);
|
|
4684
|
+
await fs21.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
|
|
4531
4685
|
return filePath;
|
|
4532
4686
|
}
|
|
4533
4687
|
async function loadResults(memoryDir) {
|
|
4534
4688
|
let files;
|
|
4535
4689
|
try {
|
|
4536
|
-
files = (await
|
|
4690
|
+
files = (await fs21.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
|
|
4537
4691
|
} catch {
|
|
4538
4692
|
return [];
|
|
4539
4693
|
}
|
|
4540
4694
|
const results = [];
|
|
4541
4695
|
for (const file of files.sort((a, b) => a.localeCompare(b))) {
|
|
4542
4696
|
try {
|
|
4543
|
-
const parsed = JSON.parse(await
|
|
4697
|
+
const parsed = JSON.parse(await fs21.readFile(path14.join(memoryDir, file), "utf-8"));
|
|
4544
4698
|
if (parsed.question && parsed.savedAt) results.push(parsed);
|
|
4545
4699
|
} catch {
|
|
4546
4700
|
}
|
|
@@ -4617,18 +4771,18 @@ function renderLessons(results, options = {}) {
|
|
|
4617
4771
|
// src/cli/commands/install.ts
|
|
4618
4772
|
init_cjs_shims();
|
|
4619
4773
|
var import_node_module3 = require("module");
|
|
4620
|
-
var
|
|
4621
|
-
var
|
|
4774
|
+
var fs22 = __toESM(require("fs/promises"), 1);
|
|
4775
|
+
var path15 = __toESM(require("path"), 1);
|
|
4622
4776
|
var require4 = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
4623
4777
|
function packageRoot() {
|
|
4624
4778
|
const packageJsonPath = require4.resolve("@dreamtree-org/graphify/package.json");
|
|
4625
|
-
return
|
|
4779
|
+
return path15.dirname(packageJsonPath);
|
|
4626
4780
|
}
|
|
4627
4781
|
var MD_MARKER_BEGIN = "<!-- >>> graphify >>> -->";
|
|
4628
4782
|
var MD_MARKER_END = "<!-- <<< graphify <<< -->";
|
|
4629
4783
|
async function writeOwnedFile(filePath, content) {
|
|
4630
|
-
await
|
|
4631
|
-
await
|
|
4784
|
+
await fs22.mkdir(path15.dirname(filePath), { recursive: true });
|
|
4785
|
+
await fs22.writeFile(filePath, content, "utf-8");
|
|
4632
4786
|
}
|
|
4633
4787
|
async function upsertMarkedBlock(filePath, content) {
|
|
4634
4788
|
const block = `${MD_MARKER_BEGIN}
|
|
@@ -4636,32 +4790,32 @@ ${content.trim()}
|
|
|
4636
4790
|
${MD_MARKER_END}`;
|
|
4637
4791
|
let existing = null;
|
|
4638
4792
|
try {
|
|
4639
|
-
existing = await
|
|
4793
|
+
existing = await fs22.readFile(filePath, "utf-8");
|
|
4640
4794
|
} catch {
|
|
4641
4795
|
}
|
|
4642
4796
|
if (existing === null) {
|
|
4643
|
-
await
|
|
4644
|
-
await
|
|
4797
|
+
await fs22.mkdir(path15.dirname(filePath), { recursive: true });
|
|
4798
|
+
await fs22.writeFile(filePath, `${block}
|
|
4645
4799
|
`, "utf-8");
|
|
4646
4800
|
return;
|
|
4647
4801
|
}
|
|
4648
4802
|
if (existing.includes(MD_MARKER_BEGIN)) {
|
|
4649
4803
|
const pattern = new RegExp(`${MD_MARKER_BEGIN}[\\s\\S]*?${MD_MARKER_END}`);
|
|
4650
|
-
await
|
|
4804
|
+
await fs22.writeFile(filePath, existing.replace(pattern, block), "utf-8");
|
|
4651
4805
|
return;
|
|
4652
4806
|
}
|
|
4653
4807
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4654
|
-
await
|
|
4808
|
+
await fs22.writeFile(filePath, `${existing}${separator}${block}
|
|
4655
4809
|
`, "utf-8");
|
|
4656
4810
|
}
|
|
4657
4811
|
var PLATFORMS = {
|
|
4658
4812
|
claude: async (cwd, content) => {
|
|
4659
|
-
const target =
|
|
4813
|
+
const target = path15.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
|
|
4660
4814
|
await writeOwnedFile(target, content);
|
|
4661
4815
|
return { host: "Claude Code", path: target };
|
|
4662
4816
|
},
|
|
4663
4817
|
cursor: async (cwd, content) => {
|
|
4664
|
-
const target =
|
|
4818
|
+
const target = path15.join(cwd, ".cursor", "rules", "graphify.mdc");
|
|
4665
4819
|
const body = content.replace(/^---[\s\S]*?---\n/, "");
|
|
4666
4820
|
await writeOwnedFile(
|
|
4667
4821
|
target,
|
|
@@ -4674,30 +4828,30 @@ ${body}`
|
|
|
4674
4828
|
return { host: "Cursor", path: target };
|
|
4675
4829
|
},
|
|
4676
4830
|
windsurf: async (cwd, content) => {
|
|
4677
|
-
const target =
|
|
4831
|
+
const target = path15.join(cwd, ".windsurf", "rules", "graphify.md");
|
|
4678
4832
|
await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4679
4833
|
return { host: "Windsurf", path: target };
|
|
4680
4834
|
},
|
|
4681
4835
|
cline: async (cwd, content) => {
|
|
4682
|
-
const target =
|
|
4836
|
+
const target = path15.join(cwd, ".clinerules", "graphify.md");
|
|
4683
4837
|
await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4684
4838
|
return { host: "Cline", path: target };
|
|
4685
4839
|
},
|
|
4686
4840
|
agents: async (cwd, content) => {
|
|
4687
|
-
const target =
|
|
4841
|
+
const target = path15.join(cwd, "AGENTS.md");
|
|
4688
4842
|
await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4689
4843
|
return { host: "AGENTS.md agents (Codex, opencode, Amp, ...)", path: target };
|
|
4690
4844
|
},
|
|
4691
4845
|
gemini: async (cwd, content) => {
|
|
4692
|
-
const target =
|
|
4846
|
+
const target = path15.join(cwd, "GEMINI.md");
|
|
4693
4847
|
await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4694
4848
|
return { host: "Gemini CLI", path: target };
|
|
4695
4849
|
}
|
|
4696
4850
|
};
|
|
4697
4851
|
var SUPPORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => a.localeCompare(b));
|
|
4698
4852
|
async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
|
|
4699
|
-
const sourcePath =
|
|
4700
|
-
const content = await
|
|
4853
|
+
const sourcePath = path15.join(packageRoot(), "src", "skill", "SKILL.md");
|
|
4854
|
+
const content = await fs22.readFile(sourcePath, "utf-8");
|
|
4701
4855
|
const requested = platforms.includes("all") ? SUPPORTED_PLATFORMS : platforms;
|
|
4702
4856
|
const results = [];
|
|
4703
4857
|
for (const platform of requested) {
|
|
@@ -4781,7 +4935,7 @@ function looksLikeGitUrl(value) {
|
|
|
4781
4935
|
}
|
|
4782
4936
|
async function cloneRepo(url) {
|
|
4783
4937
|
const validated = validateUrl(url);
|
|
4784
|
-
const dest = await
|
|
4938
|
+
const dest = await fs24.mkdtemp(path17.join(os3.tmpdir(), "graphify-clone-"));
|
|
4785
4939
|
await execFileAsync3("git", ["clone", "--depth", "1", validated, dest]);
|
|
4786
4940
|
return dest;
|
|
4787
4941
|
}
|
|
@@ -4795,7 +4949,7 @@ function clusterOptionsFrom(options) {
|
|
|
4795
4949
|
};
|
|
4796
4950
|
}
|
|
4797
4951
|
async function runClusterOnly(root, options) {
|
|
4798
|
-
const outDir =
|
|
4952
|
+
const outDir = path17.join(root, "graphify-out");
|
|
4799
4953
|
const graph = await loadGraph(outDir);
|
|
4800
4954
|
cluster(graph, clusterOptionsFrom(options));
|
|
4801
4955
|
const analysis = analyze(graph);
|
|
@@ -4829,9 +4983,9 @@ var program = new import_commander.Command();
|
|
|
4829
4983
|
program.name("graphify").description("Turn a folder of code/docs/papers into a queryable knowledge graph.").argument("[path]", "path to scan, or a GitHub URL", ".").option("--mode <mode>", "extraction mode (deep for richer INFERRED edges \u2014 reserved, no-op in v1)").option("--update", "incremental rebuild \u2014 reuse cached extractions for unchanged files").option("--watch", "rebuild on file change").option("--cluster-only", "rerun clustering on an existing graph").option("--leiden", "use Leiden instead of Louvain for community detection (v2 \u2014 better community quality)").option("--max-iterations <n>", "Leiden only: cap on outer iterations for very large graphs", Number).option("--no-viz", "skip graph.html").option("--svg", "also export graph.svg (not implemented in v1)").option("--graphml", "also export graph.graphml (not implemented in v1)").option("--neo4j", "generate graphify-out/cypher.txt for Neo4j (not implemented in v1)").option("--mcp", "start MCP stdio server instead of running the pipeline").option("--mysql <dsn>", "also extract a MySQL schema (mysql://user:pass@host:port/db) into the graph").action(async (targetArg, options) => {
|
|
4830
4984
|
try {
|
|
4831
4985
|
if (options.mcp) {
|
|
4832
|
-
const outDir =
|
|
4986
|
+
const outDir = path17.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
|
|
4833
4987
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4834
|
-
await startServer2(
|
|
4988
|
+
await startServer2(path17.join(outDir, "graph.json"));
|
|
4835
4989
|
return;
|
|
4836
4990
|
}
|
|
4837
4991
|
let root = targetArg;
|
|
@@ -4839,7 +4993,7 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
|
|
|
4839
4993
|
console.error(`Cloning ${targetArg} ...`);
|
|
4840
4994
|
root = await cloneRepo(targetArg);
|
|
4841
4995
|
}
|
|
4842
|
-
root =
|
|
4996
|
+
root = path17.resolve(root);
|
|
4843
4997
|
if (options.mode) {
|
|
4844
4998
|
console.error(`--mode ${options.mode} is reserved for a future richer-INFERRED-edges pass \u2014 currently a no-op.`);
|
|
4845
4999
|
}
|
|
@@ -4993,7 +5147,7 @@ program.command("merge <dirs...>").description("one-shot merge of two or more bu
|
|
|
4993
5147
|
});
|
|
4994
5148
|
program.command("save-result").description("save a Q&A result to graphify-out/memory/ for the graph feedback loop").requiredOption("--question <q>", "the question that was asked").requiredOption("--answer <a>", "the answer that was given").option("--nodes <ids...>", "node ids/labels cited in the answer", []).option("--type <t>", "query|path|explain|affected", "query").option("--outcome <o>", "useful|dead_end|corrected", "useful").option("--correction <text>", "what the right answer was (pairs with --outcome corrected)").action(async (options) => {
|
|
4995
5149
|
try {
|
|
4996
|
-
const memoryDir =
|
|
5150
|
+
const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
|
|
4997
5151
|
const file = await saveResult(
|
|
4998
5152
|
{
|
|
4999
5153
|
question: options.question,
|
|
@@ -5013,12 +5167,12 @@ program.command("save-result").description("save a Q&A result to graphify-out/me
|
|
|
5013
5167
|
});
|
|
5014
5168
|
program.command("reflect").description("aggregate graphify-out/memory/ into a recency-weighted lessons doc").option("--half-life-days <n>", "a result loses half its weight every N days (default 30)", Number).option("--out <file>", "output path (default graphify-out/reflections/LESSONS.md)").action(async (options) => {
|
|
5015
5169
|
try {
|
|
5016
|
-
const memoryDir =
|
|
5170
|
+
const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
|
|
5017
5171
|
const results = await loadResults(memoryDir);
|
|
5018
5172
|
const lessons = renderLessons(results, { halfLifeDays: options.halfLifeDays });
|
|
5019
|
-
const outPath =
|
|
5020
|
-
await
|
|
5021
|
-
await
|
|
5173
|
+
const outPath = path17.resolve(options.out ?? path17.join(process.cwd(), "graphify-out", "reflections", "LESSONS.md"));
|
|
5174
|
+
await fs24.mkdir(path17.dirname(outPath), { recursive: true });
|
|
5175
|
+
await fs24.writeFile(outPath, lessons, "utf-8");
|
|
5022
5176
|
console.log(`Reflected ${results.length} result(s) -> ${outPath}`);
|
|
5023
5177
|
} catch (error) {
|
|
5024
5178
|
console.error(`graphify reflect: ${error.message}`);
|