@dreamtree-org/graphify 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/{chunk-EW23BLJC.js → chunk-PXVI2L45.js} +89 -44
- package/dist/chunk-PXVI2L45.js.map +1 -0
- package/dist/cli/index.cjs +248 -195
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +15 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +108 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EW23BLJC.js.map +0 -1
package/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
|
}
|
|
@@ -443,25 +443,25 @@ function shortestPath(graph, fromQuery, toQuery) {
|
|
|
443
443
|
if (!visited.has(to.id)) {
|
|
444
444
|
return { from, to, found: false, path: [], edges: [] };
|
|
445
445
|
}
|
|
446
|
-
const
|
|
446
|
+
const path18 = [to.id];
|
|
447
447
|
let cursor = to.id;
|
|
448
448
|
while (cursor !== from.id) {
|
|
449
449
|
const prev = predecessor.get(cursor);
|
|
450
450
|
if (!prev) break;
|
|
451
|
-
|
|
451
|
+
path18.unshift(prev);
|
|
452
452
|
cursor = prev;
|
|
453
453
|
}
|
|
454
454
|
const edges = [];
|
|
455
|
-
for (let i = 0; i <
|
|
456
|
-
const a =
|
|
457
|
-
const b =
|
|
455
|
+
for (let i = 0; i < path18.length - 1; i++) {
|
|
456
|
+
const a = path18[i];
|
|
457
|
+
const b = path18[i + 1];
|
|
458
458
|
const key = graph.edges(a, b)[0] ?? graph.edges(b, a)[0];
|
|
459
459
|
if (key) {
|
|
460
460
|
const attrs = graph.getEdgeAttributes(key);
|
|
461
461
|
edges.push({ source: a, target: b, relation: String(attrs.relation), confidence: String(attrs.confidence) });
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
|
-
return { from, to, found: true, path:
|
|
464
|
+
return { from, to, found: true, path: path18, edges };
|
|
465
465
|
}
|
|
466
466
|
function explainNode(graph, query) {
|
|
467
467
|
const match = resolveNode(graph, query);
|
|
@@ -607,6 +607,78 @@ var init_impact = __esm({
|
|
|
607
607
|
}
|
|
608
608
|
});
|
|
609
609
|
|
|
610
|
+
// src/testmap.ts
|
|
611
|
+
function isTestFile(path18) {
|
|
612
|
+
return TEST_FILE_PATTERNS.some((p) => p.test(path18));
|
|
613
|
+
}
|
|
614
|
+
function selectionFromImpact(graph, targetIds, targetLabel) {
|
|
615
|
+
const best = /* @__PURE__ */ new Map();
|
|
616
|
+
let affectedNodeCount = 0;
|
|
617
|
+
for (const id of targetIds) {
|
|
618
|
+
const impact = affectedBy(graph, id, { maxDepth: TEST_REACH_DEPTH, limit: TEST_REACH_LIMIT });
|
|
619
|
+
if (!impact) continue;
|
|
620
|
+
affectedNodeCount += impact.affected.length;
|
|
621
|
+
for (const node of impact.affected) {
|
|
622
|
+
if (!isTestFile(node.sourceFile)) continue;
|
|
623
|
+
const existing = best.get(node.sourceFile);
|
|
624
|
+
if (!existing || node.depth < existing.depth) {
|
|
625
|
+
best.set(node.sourceFile, { depth: node.depth, via: node.via.dependsOn });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
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));
|
|
630
|
+
return { target: targetLabel, testFiles, affectedNodeCount };
|
|
631
|
+
}
|
|
632
|
+
function testsForNode(graph, nodeQuery) {
|
|
633
|
+
const match = resolveNode(graph, nodeQuery);
|
|
634
|
+
if (!match) return null;
|
|
635
|
+
return selectionFromImpact(graph, [match.id], match.id);
|
|
636
|
+
}
|
|
637
|
+
function testsForChangedFiles(graph, changedFiles) {
|
|
638
|
+
const fileIds = [];
|
|
639
|
+
const selfSelected = /* @__PURE__ */ new Map();
|
|
640
|
+
for (const file of changedFiles) {
|
|
641
|
+
if (isTestFile(file)) {
|
|
642
|
+
selfSelected.set(file, { depth: 0, via: "(changed directly)" });
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (graph.hasNode(file)) fileIds.push(file);
|
|
646
|
+
}
|
|
647
|
+
const selection = selectionFromImpact(graph, fileIds, changedFiles.join(", "));
|
|
648
|
+
for (const [file, info] of selfSelected) {
|
|
649
|
+
if (!selection.testFiles.some((t) => t.file === file)) {
|
|
650
|
+
selection.testFiles.push({ file, depth: info.depth, via: info.via });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
selection.testFiles.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
|
|
654
|
+
return selection;
|
|
655
|
+
}
|
|
656
|
+
var TEST_FILE_PATTERNS, TEST_REACH_DEPTH, TEST_REACH_LIMIT;
|
|
657
|
+
var init_testmap = __esm({
|
|
658
|
+
"src/testmap.ts"() {
|
|
659
|
+
"use strict";
|
|
660
|
+
init_cjs_shims();
|
|
661
|
+
init_impact();
|
|
662
|
+
init_query();
|
|
663
|
+
TEST_FILE_PATTERNS = [
|
|
664
|
+
/(^|\/)tests?\//,
|
|
665
|
+
// tests/ or test/ directory
|
|
666
|
+
/(^|\/)__tests__\//,
|
|
667
|
+
/(^|\/)spec\//,
|
|
668
|
+
/\.test\.[cm]?[jt]sx?$/,
|
|
669
|
+
/\.spec\.[cm]?[jt]sx?$/,
|
|
670
|
+
/(^|\/)test_[^/]+\.py$/,
|
|
671
|
+
/_test\.py$/,
|
|
672
|
+
/_test\.go$/,
|
|
673
|
+
/Tests?\.(java|cs)$/,
|
|
674
|
+
/_spec\.rb$/,
|
|
675
|
+
/_test\.rb$/
|
|
676
|
+
];
|
|
677
|
+
TEST_REACH_DEPTH = 4;
|
|
678
|
+
TEST_REACH_LIMIT = 2e3;
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
610
682
|
// src/context.ts
|
|
611
683
|
function lineNumberOf(sourceLocation) {
|
|
612
684
|
const match = /^L(\d+)$/.exec(sourceLocation);
|
|
@@ -641,7 +713,7 @@ function snippetRange(startLine, fileLineCount, entityStartsInFile, maxLines) {
|
|
|
641
713
|
const hardEnd = nextStart !== void 0 ? nextStart - 1 : fileLineCount;
|
|
642
714
|
return { start: startLine, end: Math.min(hardEnd, startLine + maxLines - 1) };
|
|
643
715
|
}
|
|
644
|
-
async function buildContextPack(graph, task,
|
|
716
|
+
async function buildContextPack(graph, task, readFile21, options = {}) {
|
|
645
717
|
const tokenBudget = options.tokenBudget ?? DEFAULT_CONTEXT_BUDGET;
|
|
646
718
|
const maxSnippetLines = options.maxSnippetLines ?? DEFAULT_MAX_SNIPPET_LINES;
|
|
647
719
|
const ranked = rankForTask(graph, task, options.maxSeeds);
|
|
@@ -662,7 +734,7 @@ async function buildContextPack(graph, task, readFile20, options = {}) {
|
|
|
662
734
|
if (cached !== void 0) return cached;
|
|
663
735
|
let lines;
|
|
664
736
|
try {
|
|
665
|
-
lines = (await
|
|
737
|
+
lines = (await readFile21(file)).split("\n");
|
|
666
738
|
} catch {
|
|
667
739
|
lines = null;
|
|
668
740
|
}
|
|
@@ -748,78 +820,6 @@ var init_context = __esm({
|
|
|
748
820
|
}
|
|
749
821
|
});
|
|
750
822
|
|
|
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
823
|
// src/mcp/server.ts
|
|
824
824
|
var server_exports = {};
|
|
825
825
|
__export(server_exports, {
|
|
@@ -914,7 +914,7 @@ function createGraphifyMcpServer(outDir) {
|
|
|
914
914
|
},
|
|
915
915
|
async ({ task, budget }) => {
|
|
916
916
|
const graph = await loadGraph(outDir);
|
|
917
|
-
const pack = await buildContextPack(graph, task, (p) =>
|
|
917
|
+
const pack = await buildContextPack(graph, task, (p) => fs23.readFile(p, "utf-8"), {
|
|
918
918
|
tokenBudget: budget
|
|
919
919
|
});
|
|
920
920
|
return textResult(renderContextPack(pack));
|
|
@@ -940,23 +940,23 @@ function createGraphifyMcpServer(outDir) {
|
|
|
940
940
|
return server;
|
|
941
941
|
}
|
|
942
942
|
async function startServer(graphPath, transport = new import_stdio.StdioServerTransport()) {
|
|
943
|
-
const outDir =
|
|
944
|
-
const fileName =
|
|
943
|
+
const outDir = path16.dirname(path16.resolve(graphPath));
|
|
944
|
+
const fileName = path16.basename(graphPath);
|
|
945
945
|
validateGraphPath(fileName, outDir);
|
|
946
946
|
const server = createGraphifyMcpServer(outDir);
|
|
947
947
|
await server.connect(transport);
|
|
948
948
|
}
|
|
949
949
|
async function main(argv = process.argv) {
|
|
950
|
-
const graphPath = argv[2] ??
|
|
950
|
+
const graphPath = argv[2] ?? path16.join(process.cwd(), "graphify-out", "graph.json");
|
|
951
951
|
await startServer(graphPath);
|
|
952
952
|
}
|
|
953
|
-
var
|
|
953
|
+
var fs23, path16, import_mcp, import_stdio, import_zod2;
|
|
954
954
|
var init_server = __esm({
|
|
955
955
|
"src/mcp/server.ts"() {
|
|
956
956
|
"use strict";
|
|
957
957
|
init_cjs_shims();
|
|
958
|
-
|
|
959
|
-
|
|
958
|
+
fs23 = __toESM(require("fs/promises"), 1);
|
|
959
|
+
path16 = __toESM(require("path"), 1);
|
|
960
960
|
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
961
961
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
962
962
|
import_zod2 = require("zod");
|
|
@@ -1131,9 +1131,9 @@ var init_mysql = __esm({
|
|
|
1131
1131
|
// src/cli/index.ts
|
|
1132
1132
|
init_cjs_shims();
|
|
1133
1133
|
var import_node_child_process3 = require("child_process");
|
|
1134
|
-
var
|
|
1134
|
+
var fs24 = __toESM(require("fs/promises"), 1);
|
|
1135
1135
|
var os3 = __toESM(require("os"), 1);
|
|
1136
|
-
var
|
|
1136
|
+
var path17 = __toESM(require("path"), 1);
|
|
1137
1137
|
var import_node_util3 = require("util");
|
|
1138
1138
|
var import_chokidar = require("chokidar");
|
|
1139
1139
|
var import_commander = require("commander");
|
|
@@ -1465,8 +1465,8 @@ init_graphStore();
|
|
|
1465
1465
|
|
|
1466
1466
|
// src/pipeline.ts
|
|
1467
1467
|
init_cjs_shims();
|
|
1468
|
-
var
|
|
1469
|
-
var
|
|
1468
|
+
var fs14 = __toESM(require("fs/promises"), 1);
|
|
1469
|
+
var path9 = __toESM(require("path"), 1);
|
|
1470
1470
|
|
|
1471
1471
|
// src/build.ts
|
|
1472
1472
|
init_cjs_shims();
|
|
@@ -3138,10 +3138,10 @@ async function extractRust(filePath, displayPath) {
|
|
|
3138
3138
|
}
|
|
3139
3139
|
}
|
|
3140
3140
|
} else if (fn.type === "scoped_identifier") {
|
|
3141
|
-
const
|
|
3141
|
+
const path18 = fn.childForFieldName("path")?.text;
|
|
3142
3142
|
const name = fn.childForFieldName("name")?.text;
|
|
3143
|
-
if (
|
|
3144
|
-
const viaImport = importIndex.get(
|
|
3143
|
+
if (path18 && name) {
|
|
3144
|
+
const viaImport = importIndex.get(path18);
|
|
3145
3145
|
if (viaImport) {
|
|
3146
3146
|
targetId = viaImport.id;
|
|
3147
3147
|
confidence = "INFERRED";
|
|
@@ -3601,6 +3601,8 @@ function renderReport(graph, analysis, now = /* @__PURE__ */ new Date()) {
|
|
|
3601
3601
|
|
|
3602
3602
|
// src/resolve.ts
|
|
3603
3603
|
init_cjs_shims();
|
|
3604
|
+
var fs13 = __toESM(require("fs/promises"), 1);
|
|
3605
|
+
var path8 = __toESM(require("path"), 1);
|
|
3604
3606
|
var CODE_EXTENSIONS2 = [
|
|
3605
3607
|
".ts",
|
|
3606
3608
|
".tsx",
|
|
@@ -3620,6 +3622,29 @@ var CODE_EXTENSIONS2 = [
|
|
|
3620
3622
|
function isOpaque(id) {
|
|
3621
3623
|
return id.startsWith("module:") || id.startsWith("external:") || id.startsWith("db:");
|
|
3622
3624
|
}
|
|
3625
|
+
var SELF_IMPORT_STEMS = (subpath) => subpath === "" ? ["index", "src/index", "lib/index", "source/index", "src/main"] : [
|
|
3626
|
+
subpath,
|
|
3627
|
+
`src/${subpath}`,
|
|
3628
|
+
`lib/${subpath}`,
|
|
3629
|
+
`source/${subpath}`,
|
|
3630
|
+
`${subpath}/index`,
|
|
3631
|
+
`src/${subpath}/index`,
|
|
3632
|
+
`lib/${subpath}/index`
|
|
3633
|
+
];
|
|
3634
|
+
function selfImportCandidates(spec, selfNames) {
|
|
3635
|
+
const selfName = selfNames.find((n) => spec === n || spec.startsWith(`${n}/`));
|
|
3636
|
+
if (selfName === void 0) return null;
|
|
3637
|
+
const subpath = spec === selfName ? "" : spec.slice(selfName.length + 1);
|
|
3638
|
+
return SELF_IMPORT_STEMS(subpath).flatMap((stem) => CODE_EXTENSIONS2.map((ext) => `${stem}${ext}`));
|
|
3639
|
+
}
|
|
3640
|
+
async function readSelfNames(root) {
|
|
3641
|
+
try {
|
|
3642
|
+
const pkg = JSON.parse(await fs13.readFile(path8.join(root, "package.json"), "utf-8"));
|
|
3643
|
+
return typeof pkg.name === "string" && pkg.name.length > 0 ? [pkg.name] : [];
|
|
3644
|
+
} catch {
|
|
3645
|
+
return [];
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3623
3648
|
function stripKnownExtension(p) {
|
|
3624
3649
|
for (const ext of CODE_EXTENSIONS2) {
|
|
3625
3650
|
if (p.endsWith(ext)) return p.slice(0, -ext.length);
|
|
@@ -3644,11 +3669,16 @@ function uniqueMatch(guessed, candidates, realIds) {
|
|
|
3644
3669
|
if (matches.size !== 1) return null;
|
|
3645
3670
|
return [...matches][0];
|
|
3646
3671
|
}
|
|
3647
|
-
function resolveCrossFileReferences(extractions) {
|
|
3672
|
+
function resolveCrossFileReferences(extractions, options = {}) {
|
|
3673
|
+
const selfNames = options.selfNames ?? [];
|
|
3648
3674
|
const realIds = /* @__PURE__ */ new Set();
|
|
3649
3675
|
const realFiles = /* @__PURE__ */ new Set();
|
|
3650
3676
|
for (const extraction of extractions) {
|
|
3651
3677
|
for (const node of extraction.nodes) realIds.add(node.id);
|
|
3678
|
+
const first = extraction.nodes[0];
|
|
3679
|
+
if (first && first.id === first.sourceFile && !isOpaque(first.id)) {
|
|
3680
|
+
realFiles.add(first.id);
|
|
3681
|
+
}
|
|
3652
3682
|
for (const node of extraction.nodes) {
|
|
3653
3683
|
const sep3 = node.id.indexOf("::");
|
|
3654
3684
|
if (sep3 > 0) realFiles.add(node.id.slice(0, sep3));
|
|
@@ -3659,6 +3689,19 @@ function resolveCrossFileReferences(extractions) {
|
|
|
3659
3689
|
}
|
|
3660
3690
|
const remap = /* @__PURE__ */ new Map();
|
|
3661
3691
|
const resolveId = (id) => {
|
|
3692
|
+
if (id.startsWith("module:") && selfNames.length > 0) {
|
|
3693
|
+
const cachedSelf = remap.get(id);
|
|
3694
|
+
if (cachedSelf !== void 0) return cachedSelf;
|
|
3695
|
+
const candidates = selfImportCandidates(id.slice("module:".length), selfNames);
|
|
3696
|
+
if (candidates !== null) {
|
|
3697
|
+
const resolved2 = uniqueMatch(id, candidates, realFiles);
|
|
3698
|
+
if (resolved2 !== null) {
|
|
3699
|
+
remap.set(id, resolved2);
|
|
3700
|
+
return resolved2;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
return null;
|
|
3704
|
+
}
|
|
3662
3705
|
if (isOpaque(id)) return null;
|
|
3663
3706
|
const cached = remap.get(id);
|
|
3664
3707
|
if (cached !== void 0) return cached;
|
|
@@ -3705,21 +3748,21 @@ function resolveCrossFileReferences(extractions) {
|
|
|
3705
3748
|
async function runPipeline(root, options = {}) {
|
|
3706
3749
|
const progress = options.onProgress ?? (() => {
|
|
3707
3750
|
});
|
|
3708
|
-
const resolvedRoot =
|
|
3751
|
+
const resolvedRoot = path9.resolve(root);
|
|
3709
3752
|
progress(`Scanning ${resolvedRoot} ...`);
|
|
3710
3753
|
const manifest = collectFiles(resolvedRoot);
|
|
3711
3754
|
progress(
|
|
3712
3755
|
`Found ${manifest.totalFiles} file(s) (${manifest.files.code.length} code, ${manifest.skippedSensitive.length} skipped as sensitive).`
|
|
3713
3756
|
);
|
|
3714
|
-
const outDir = options.outDir ??
|
|
3757
|
+
const outDir = options.outDir ?? path9.join(resolvedRoot, "graphify-out");
|
|
3715
3758
|
const cache = new ExtractionCache(outDir);
|
|
3716
3759
|
progress("Extracting...");
|
|
3717
3760
|
const extractions = [];
|
|
3718
3761
|
let cacheHits = 0;
|
|
3719
3762
|
for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
|
|
3720
|
-
const filePath =
|
|
3763
|
+
const filePath = path9.relative(process.cwd(), path9.join(resolvedRoot, relFile)) || relFile;
|
|
3721
3764
|
try {
|
|
3722
|
-
const key = cache.key(relFile, await
|
|
3765
|
+
const key = cache.key(relFile, await fs14.readFile(path9.join(resolvedRoot, relFile)));
|
|
3723
3766
|
let extraction = options.update ? await cache.get(key) : null;
|
|
3724
3767
|
if (extraction) {
|
|
3725
3768
|
cacheHits++;
|
|
@@ -3740,7 +3783,9 @@ async function runPipeline(root, options = {}) {
|
|
|
3740
3783
|
extractions.push(...options.extraExtractions);
|
|
3741
3784
|
}
|
|
3742
3785
|
progress("Resolving cross-file references...");
|
|
3743
|
-
const resolveStats = resolveCrossFileReferences(extractions
|
|
3786
|
+
const resolveStats = resolveCrossFileReferences(extractions, {
|
|
3787
|
+
selfNames: await readSelfNames(resolvedRoot)
|
|
3788
|
+
});
|
|
3744
3789
|
if (resolveStats.resolvedEndpoints > 0) {
|
|
3745
3790
|
progress(
|
|
3746
3791
|
` re-pointed ${resolveStats.resolvedEndpoints} guessed reference(s) at real nodes (${resolveStats.droppedPlaceholders} placeholder node(s) dropped).`
|
|
@@ -3797,9 +3842,10 @@ async function runAffected(nodeName, options = {}) {
|
|
|
3797
3842
|
console.log("");
|
|
3798
3843
|
console.log(currentDepth === 1 ? "Directly affected:" : `Affected at depth ${currentDepth}:`);
|
|
3799
3844
|
}
|
|
3800
|
-
const
|
|
3845
|
+
const isPlaceholder = node.sourceFile === "<unknown>" || node.sourceFile === "<external>" || node.sourceFile === "";
|
|
3846
|
+
const where = isPlaceholder ? "(reference only)" : `${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ""}`;
|
|
3801
3847
|
console.log(
|
|
3802
|
-
` ${node.label} \u2014 ${
|
|
3848
|
+
` ${node.label} \u2014 ${where} (--${node.via.relation}--> ${node.via.dependsOn}, ${node.via.confidence})`
|
|
3803
3849
|
);
|
|
3804
3850
|
}
|
|
3805
3851
|
console.log("");
|
|
@@ -3814,11 +3860,12 @@ async function runAffected(nodeName, options = {}) {
|
|
|
3814
3860
|
|
|
3815
3861
|
// src/cli/commands/benchmark.ts
|
|
3816
3862
|
init_cjs_shims();
|
|
3817
|
-
var
|
|
3863
|
+
var fs15 = __toESM(require("fs/promises"), 1);
|
|
3818
3864
|
init_graphStore();
|
|
3819
3865
|
init_query();
|
|
3866
|
+
init_testmap();
|
|
3820
3867
|
var tokensOf = (text) => Math.ceil(text.length / 4);
|
|
3821
|
-
async function benchmarkQuestion(graph, question,
|
|
3868
|
+
async function benchmarkQuestion(graph, question, readFile21) {
|
|
3822
3869
|
const result = queryGraph(graph, question);
|
|
3823
3870
|
const graphTokens = tokensOf(JSON.stringify(result));
|
|
3824
3871
|
const sourceFiles = /* @__PURE__ */ new Set();
|
|
@@ -3832,7 +3879,7 @@ async function benchmarkQuestion(graph, question, readFile20) {
|
|
|
3832
3879
|
let filesMissing = 0;
|
|
3833
3880
|
for (const file of [...sourceFiles].sort((a, b) => a.localeCompare(b))) {
|
|
3834
3881
|
try {
|
|
3835
|
-
naiveTokens += tokensOf(await
|
|
3882
|
+
naiveTokens += tokensOf(await readFile21(file));
|
|
3836
3883
|
filesRead++;
|
|
3837
3884
|
} catch {
|
|
3838
3885
|
filesMissing++;
|
|
@@ -3847,12 +3894,14 @@ async function benchmarkQuestion(graph, question, readFile20) {
|
|
|
3847
3894
|
filesMissing
|
|
3848
3895
|
};
|
|
3849
3896
|
}
|
|
3897
|
+
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
3898
|
function defaultQuestions(graph, count = 5) {
|
|
3851
3899
|
const entries = [...graph.nodes()].map((id) => ({
|
|
3852
3900
|
id,
|
|
3853
3901
|
degree: graph.degree(id),
|
|
3854
|
-
label: graph.getNodeAttribute(id, "label") ?? id
|
|
3855
|
-
|
|
3902
|
+
label: graph.getNodeAttribute(id, "label") ?? id,
|
|
3903
|
+
file: graph.getNodeAttribute(id, "sourceFile") ?? ""
|
|
3904
|
+
})).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
3905
|
return entries.slice(0, count).map((e) => `how does ${e.label} work`);
|
|
3857
3906
|
}
|
|
3858
3907
|
async function runBenchmark(questions) {
|
|
@@ -3864,7 +3913,7 @@ async function runBenchmark(questions) {
|
|
|
3864
3913
|
}
|
|
3865
3914
|
const rows = [];
|
|
3866
3915
|
for (const question of effective) {
|
|
3867
|
-
rows.push(await benchmarkQuestion(graph, question, (p) =>
|
|
3916
|
+
rows.push(await benchmarkQuestion(graph, question, (p) => fs15.readFile(p, "utf-8")));
|
|
3868
3917
|
}
|
|
3869
3918
|
console.log("Token cost per question \u2014 graph answer vs reading the files it touches:\n");
|
|
3870
3919
|
for (const row of rows) {
|
|
@@ -3887,8 +3936,8 @@ Average reduction: ${avg.toFixed(1)}x across ${scored.length} question(s).`);
|
|
|
3887
3936
|
|
|
3888
3937
|
// src/cli/commands/check.ts
|
|
3889
3938
|
init_cjs_shims();
|
|
3890
|
-
var
|
|
3891
|
-
var
|
|
3939
|
+
var fs16 = __toESM(require("fs/promises"), 1);
|
|
3940
|
+
var path10 = __toESM(require("path"), 1);
|
|
3892
3941
|
|
|
3893
3942
|
// src/check.ts
|
|
3894
3943
|
init_cjs_shims();
|
|
@@ -3959,10 +4008,10 @@ function checkRules(graph, config) {
|
|
|
3959
4008
|
// src/cli/commands/check.ts
|
|
3960
4009
|
init_graphStore();
|
|
3961
4010
|
async function runCheck(options = {}) {
|
|
3962
|
-
const rulesPath =
|
|
4011
|
+
const rulesPath = path10.resolve(options.rules ?? "graphify.rules.json");
|
|
3963
4012
|
let raw;
|
|
3964
4013
|
try {
|
|
3965
|
-
raw = await
|
|
4014
|
+
raw = await fs16.readFile(rulesPath, "utf-8");
|
|
3966
4015
|
} catch {
|
|
3967
4016
|
throw new Error(
|
|
3968
4017
|
`No rules file at ${rulesPath} \u2014 create graphify.rules.json with { "rules": [{ "name": "...", "from": "src/a/**", "disallow": ["src/b/**"] }] }.`
|
|
@@ -3985,12 +4034,12 @@ async function runCheck(options = {}) {
|
|
|
3985
4034
|
|
|
3986
4035
|
// src/cli/commands/context.ts
|
|
3987
4036
|
init_cjs_shims();
|
|
3988
|
-
var
|
|
4037
|
+
var fs17 = __toESM(require("fs/promises"), 1);
|
|
3989
4038
|
init_context();
|
|
3990
4039
|
init_graphStore();
|
|
3991
4040
|
async function runContext(task, options = {}) {
|
|
3992
4041
|
const graph = await loadGraph();
|
|
3993
|
-
const pack = await buildContextPack(graph, task, (p) =>
|
|
4042
|
+
const pack = await buildContextPack(graph, task, (p) => fs17.readFile(p, "utf-8"), {
|
|
3994
4043
|
tokenBudget: options.budget
|
|
3995
4044
|
});
|
|
3996
4045
|
console.log(renderContextPack(pack));
|
|
@@ -4007,9 +4056,13 @@ async function runExplain(nodeName) {
|
|
|
4007
4056
|
console.log(`No node matched "${nodeName}".`);
|
|
4008
4057
|
return;
|
|
4009
4058
|
}
|
|
4010
|
-
const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
|
|
4011
4059
|
console.log(result.label);
|
|
4012
|
-
|
|
4060
|
+
if (result.sourceFile === "<unknown>" || result.sourceFile === "<external>" || result.sourceFile === "") {
|
|
4061
|
+
console.log(" location: (reference only \u2014 no declaration seen; check its outgoing edges for the real definition)");
|
|
4062
|
+
} else {
|
|
4063
|
+
const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
|
|
4064
|
+
console.log(` location: ${result.sourceFile}${location}`);
|
|
4065
|
+
}
|
|
4013
4066
|
if (result.communityLabel) {
|
|
4014
4067
|
console.log(` community: ${result.communityLabel}`);
|
|
4015
4068
|
}
|
|
@@ -4039,9 +4092,9 @@ init_cjs_shims();
|
|
|
4039
4092
|
// src/diff.ts
|
|
4040
4093
|
init_cjs_shims();
|
|
4041
4094
|
var import_node_child_process = require("child_process");
|
|
4042
|
-
var
|
|
4095
|
+
var fs18 = __toESM(require("fs/promises"), 1);
|
|
4043
4096
|
var os = __toESM(require("os"), 1);
|
|
4044
|
-
var
|
|
4097
|
+
var path11 = __toESM(require("path"), 1);
|
|
4045
4098
|
var import_node_util = require("util");
|
|
4046
4099
|
init_impact();
|
|
4047
4100
|
init_testmap();
|
|
@@ -4050,13 +4103,13 @@ async function graphForDirectory(root) {
|
|
|
4050
4103
|
const manifest = collectFiles(root);
|
|
4051
4104
|
const extractions = [];
|
|
4052
4105
|
for (const relFile of manifest.files.code.sort((a, b) => a.localeCompare(b))) {
|
|
4053
|
-
extractions.push(await extract(
|
|
4106
|
+
extractions.push(await extract(path11.join(root, relFile), relFile));
|
|
4054
4107
|
}
|
|
4055
|
-
resolveCrossFileReferences(extractions);
|
|
4108
|
+
resolveCrossFileReferences(extractions, { selfNames: await readSelfNames(root) });
|
|
4056
4109
|
return buildGraph(extractions);
|
|
4057
4110
|
}
|
|
4058
4111
|
async function checkoutRev(repoRoot, rev) {
|
|
4059
|
-
const dest = await
|
|
4112
|
+
const dest = await fs18.mkdtemp(path11.join(os.tmpdir(), `graphify-review-${rev.replace(/[^a-zA-Z0-9]/g, "_")}-`));
|
|
4060
4113
|
const { stdout } = await execFileAsync(
|
|
4061
4114
|
"git",
|
|
4062
4115
|
["-C", repoRoot, "archive", "--format=tar", rev],
|
|
@@ -4124,8 +4177,8 @@ async function reviewRevisions(repoRoot, base, head = "HEAD") {
|
|
|
4124
4177
|
return diffGraphs(baseGraph, headGraph, base, head);
|
|
4125
4178
|
} finally {
|
|
4126
4179
|
await Promise.all([
|
|
4127
|
-
|
|
4128
|
-
|
|
4180
|
+
fs18.rm(baseDir, { recursive: true, force: true }),
|
|
4181
|
+
fs18.rm(headDir, { recursive: true, force: true })
|
|
4129
4182
|
]);
|
|
4130
4183
|
}
|
|
4131
4184
|
}
|
|
@@ -4217,9 +4270,9 @@ async function runTests(nodeQuery, options = {}) {
|
|
|
4217
4270
|
|
|
4218
4271
|
// src/cli/commands/global.ts
|
|
4219
4272
|
init_cjs_shims();
|
|
4220
|
-
var
|
|
4273
|
+
var fs19 = __toESM(require("fs/promises"), 1);
|
|
4221
4274
|
var os2 = __toESM(require("os"), 1);
|
|
4222
|
-
var
|
|
4275
|
+
var path12 = __toESM(require("path"), 1);
|
|
4223
4276
|
init_graphStore();
|
|
4224
4277
|
|
|
4225
4278
|
// src/merge.ts
|
|
@@ -4281,14 +4334,14 @@ function mergeGraphs(entries) {
|
|
|
4281
4334
|
|
|
4282
4335
|
// src/cli/commands/global.ts
|
|
4283
4336
|
function registryDir() {
|
|
4284
|
-
return
|
|
4337
|
+
return path12.join(os2.homedir(), ".graphify");
|
|
4285
4338
|
}
|
|
4286
4339
|
function registryPath() {
|
|
4287
|
-
return
|
|
4340
|
+
return path12.join(registryDir(), "global.json");
|
|
4288
4341
|
}
|
|
4289
4342
|
async function readRegistry() {
|
|
4290
4343
|
try {
|
|
4291
|
-
const raw = await
|
|
4344
|
+
const raw = await fs19.readFile(registryPath(), "utf-8");
|
|
4292
4345
|
const parsed = JSON.parse(raw);
|
|
4293
4346
|
const projects = parsed.projects;
|
|
4294
4347
|
if (!Array.isArray(projects)) return { projects: [] };
|
|
@@ -4298,23 +4351,23 @@ async function readRegistry() {
|
|
|
4298
4351
|
}
|
|
4299
4352
|
}
|
|
4300
4353
|
async function writeRegistry(registry) {
|
|
4301
|
-
await
|
|
4302
|
-
await
|
|
4354
|
+
await fs19.mkdir(registryDir(), { recursive: true });
|
|
4355
|
+
await fs19.writeFile(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
4303
4356
|
}
|
|
4304
4357
|
async function projectNameFor(root) {
|
|
4305
4358
|
try {
|
|
4306
|
-
const raw = await
|
|
4359
|
+
const raw = await fs19.readFile(path12.join(root, "package.json"), "utf-8");
|
|
4307
4360
|
const name = JSON.parse(raw).name;
|
|
4308
4361
|
if (typeof name === "string" && name.length > 0) return name;
|
|
4309
4362
|
} catch {
|
|
4310
4363
|
}
|
|
4311
|
-
return
|
|
4364
|
+
return path12.basename(root);
|
|
4312
4365
|
}
|
|
4313
4366
|
async function runGlobalAdd(target = ".") {
|
|
4314
|
-
const root =
|
|
4315
|
-
const graphJson =
|
|
4367
|
+
const root = path12.resolve(target);
|
|
4368
|
+
const graphJson = path12.join(root, "graphify-out", "graph.json");
|
|
4316
4369
|
try {
|
|
4317
|
-
await
|
|
4370
|
+
await fs19.access(graphJson);
|
|
4318
4371
|
} catch {
|
|
4319
4372
|
throw new Error(`${root} has no graphify-out/graph.json \u2014 run \`graphify ${target}\` first, then add it.`);
|
|
4320
4373
|
}
|
|
@@ -4385,9 +4438,9 @@ async function runGlobalBuild(options = {}) {
|
|
|
4385
4438
|
throw new Error("No projects registered. Add some with `graphify global add <path>` first.");
|
|
4386
4439
|
}
|
|
4387
4440
|
const entries = await loadEntries(
|
|
4388
|
-
registry.projects.map((p) => ({ name: p.name, outDir:
|
|
4441
|
+
registry.projects.map((p) => ({ name: p.name, outDir: path12.join(p.root, "graphify-out") }))
|
|
4389
4442
|
);
|
|
4390
|
-
await mergeAndExport(entries,
|
|
4443
|
+
await mergeAndExport(entries, path12.resolve(options.out ?? path12.join(registryDir(), "global-out")));
|
|
4391
4444
|
}
|
|
4392
4445
|
async function runMerge(targets, options = {}) {
|
|
4393
4446
|
if (targets.length < 2) {
|
|
@@ -4395,8 +4448,8 @@ async function runMerge(targets, options = {}) {
|
|
|
4395
4448
|
}
|
|
4396
4449
|
const sources = [];
|
|
4397
4450
|
for (const target of targets) {
|
|
4398
|
-
const root =
|
|
4399
|
-
sources.push({ name: await projectNameFor(root), outDir:
|
|
4451
|
+
const root = path12.resolve(target);
|
|
4452
|
+
sources.push({ name: await projectNameFor(root), outDir: path12.join(root, "graphify-out") });
|
|
4400
4453
|
}
|
|
4401
4454
|
const names = sources.map((s) => s.name);
|
|
4402
4455
|
const duplicate = names.find((name, i) => names.indexOf(name) !== i);
|
|
@@ -4404,13 +4457,13 @@ async function runMerge(targets, options = {}) {
|
|
|
4404
4457
|
throw new Error(`Two of the given projects resolve to the same name ("${duplicate}") \u2014 rename one.`);
|
|
4405
4458
|
}
|
|
4406
4459
|
const entries = await loadEntries(sources);
|
|
4407
|
-
await mergeAndExport(entries,
|
|
4460
|
+
await mergeAndExport(entries, path12.resolve(options.out ?? "graphify-merged"));
|
|
4408
4461
|
}
|
|
4409
4462
|
|
|
4410
4463
|
// src/cli/commands/hook.ts
|
|
4411
4464
|
init_cjs_shims();
|
|
4412
|
-
var
|
|
4413
|
-
var
|
|
4465
|
+
var fs20 = __toESM(require("fs/promises"), 1);
|
|
4466
|
+
var path13 = __toESM(require("path"), 1);
|
|
4414
4467
|
var MARKER_BEGIN = "# >>> graphify >>>";
|
|
4415
4468
|
var MARKER_END = "# <<< graphify <<<";
|
|
4416
4469
|
var HOOK_NAMES = ["post-commit", "post-merge"];
|
|
@@ -4421,34 +4474,34 @@ var HOOK_BLOCK = `${MARKER_BEGIN}
|
|
|
4421
4474
|
(graphify . --update --no-viz >/dev/null 2>&1 &)
|
|
4422
4475
|
${MARKER_END}`;
|
|
4423
4476
|
async function hooksDir(cwd) {
|
|
4424
|
-
const gitDir =
|
|
4477
|
+
const gitDir = path13.join(cwd, ".git");
|
|
4425
4478
|
let stats;
|
|
4426
4479
|
try {
|
|
4427
|
-
stats = await
|
|
4480
|
+
stats = await fs20.stat(gitDir);
|
|
4428
4481
|
} catch {
|
|
4429
4482
|
throw new Error(`${cwd} is not a git repository (no .git directory) \u2014 run this from the repo root.`);
|
|
4430
4483
|
}
|
|
4431
4484
|
if (!stats.isDirectory()) {
|
|
4432
|
-
const content = (await
|
|
4485
|
+
const content = (await fs20.readFile(gitDir, "utf-8")).trim();
|
|
4433
4486
|
const match = /^gitdir:\s*(.+)$/.exec(content);
|
|
4434
4487
|
if (!match) throw new Error(`${gitDir} exists but is neither a directory nor a gitdir pointer.`);
|
|
4435
|
-
return
|
|
4488
|
+
return path13.resolve(cwd, match[1], "hooks");
|
|
4436
4489
|
}
|
|
4437
|
-
return
|
|
4490
|
+
return path13.join(gitDir, "hooks");
|
|
4438
4491
|
}
|
|
4439
4492
|
async function runHookInstall(cwd = process.cwd()) {
|
|
4440
4493
|
const dir = await hooksDir(cwd);
|
|
4441
|
-
await
|
|
4494
|
+
await fs20.mkdir(dir, { recursive: true });
|
|
4442
4495
|
const results = [];
|
|
4443
4496
|
for (const hook of HOOK_NAMES) {
|
|
4444
|
-
const hookPath =
|
|
4497
|
+
const hookPath = path13.join(dir, hook);
|
|
4445
4498
|
let existing = null;
|
|
4446
4499
|
try {
|
|
4447
|
-
existing = await
|
|
4500
|
+
existing = await fs20.readFile(hookPath, "utf-8");
|
|
4448
4501
|
} catch {
|
|
4449
4502
|
}
|
|
4450
4503
|
if (existing === null) {
|
|
4451
|
-
await
|
|
4504
|
+
await fs20.writeFile(hookPath, `#!/bin/sh
|
|
4452
4505
|
${HOOK_BLOCK}
|
|
4453
4506
|
`, { mode: 493 });
|
|
4454
4507
|
results.push({ hook, path: hookPath, action: "created" });
|
|
@@ -4456,9 +4509,9 @@ ${HOOK_BLOCK}
|
|
|
4456
4509
|
results.push({ hook, path: hookPath, action: "already-installed" });
|
|
4457
4510
|
} else {
|
|
4458
4511
|
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
4459
|
-
await
|
|
4512
|
+
await fs20.writeFile(hookPath, `${existing}${separator}${HOOK_BLOCK}
|
|
4460
4513
|
`, "utf-8");
|
|
4461
|
-
await
|
|
4514
|
+
await fs20.chmod(hookPath, 493);
|
|
4462
4515
|
results.push({ hook, path: hookPath, action: "appended" });
|
|
4463
4516
|
}
|
|
4464
4517
|
}
|
|
@@ -4474,10 +4527,10 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4474
4527
|
const dir = await hooksDir(cwd);
|
|
4475
4528
|
const results = [];
|
|
4476
4529
|
for (const hook of HOOK_NAMES) {
|
|
4477
|
-
const hookPath =
|
|
4530
|
+
const hookPath = path13.join(dir, hook);
|
|
4478
4531
|
let existing = null;
|
|
4479
4532
|
try {
|
|
4480
|
-
existing = await
|
|
4533
|
+
existing = await fs20.readFile(hookPath, "utf-8");
|
|
4481
4534
|
} catch {
|
|
4482
4535
|
}
|
|
4483
4536
|
if (existing === null || !existing.includes(MARKER_BEGIN)) {
|
|
@@ -4487,9 +4540,9 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4487
4540
|
const blockPattern = new RegExp(`\\n?${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}\\n?`);
|
|
4488
4541
|
const remaining = existing.replace(blockPattern, "\n").trim();
|
|
4489
4542
|
if (remaining === "" || remaining === "#!/bin/sh") {
|
|
4490
|
-
await
|
|
4543
|
+
await fs20.unlink(hookPath);
|
|
4491
4544
|
} else {
|
|
4492
|
-
await
|
|
4545
|
+
await fs20.writeFile(hookPath, `${remaining}
|
|
4493
4546
|
`, "utf-8");
|
|
4494
4547
|
}
|
|
4495
4548
|
results.push({ hook, path: hookPath, action: "removed" });
|
|
@@ -4503,8 +4556,8 @@ async function runHookUninstall(cwd = process.cwd()) {
|
|
|
4503
4556
|
// src/memory.ts
|
|
4504
4557
|
init_cjs_shims();
|
|
4505
4558
|
var import_node_crypto4 = require("crypto");
|
|
4506
|
-
var
|
|
4507
|
-
var
|
|
4559
|
+
var fs21 = __toESM(require("fs/promises"), 1);
|
|
4560
|
+
var path14 = __toESM(require("path"), 1);
|
|
4508
4561
|
var OUTCOMES = /* @__PURE__ */ new Set(["useful", "dead_end", "corrected"]);
|
|
4509
4562
|
var TYPES = /* @__PURE__ */ new Set(["query", "path", "explain", "affected"]);
|
|
4510
4563
|
function validateResult(value) {
|
|
@@ -4522,25 +4575,25 @@ function validateResult(value) {
|
|
|
4522
4575
|
}
|
|
4523
4576
|
async function saveResult(entry, memoryDir, now = /* @__PURE__ */ new Date()) {
|
|
4524
4577
|
validateResult(entry);
|
|
4525
|
-
await
|
|
4578
|
+
await fs21.mkdir(memoryDir, { recursive: true });
|
|
4526
4579
|
const saved = { ...entry, savedAt: now.toISOString() };
|
|
4527
4580
|
const hash = (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(saved)).digest("hex").slice(0, 8);
|
|
4528
4581
|
const stamp = saved.savedAt.replace(/[:.]/g, "-");
|
|
4529
|
-
const filePath =
|
|
4530
|
-
await
|
|
4582
|
+
const filePath = path14.join(memoryDir, `result-${stamp}-${hash}.json`);
|
|
4583
|
+
await fs21.writeFile(filePath, JSON.stringify(saved, null, 2), "utf-8");
|
|
4531
4584
|
return filePath;
|
|
4532
4585
|
}
|
|
4533
4586
|
async function loadResults(memoryDir) {
|
|
4534
4587
|
let files;
|
|
4535
4588
|
try {
|
|
4536
|
-
files = (await
|
|
4589
|
+
files = (await fs21.readdir(memoryDir)).filter((f) => f.startsWith("result-") && f.endsWith(".json"));
|
|
4537
4590
|
} catch {
|
|
4538
4591
|
return [];
|
|
4539
4592
|
}
|
|
4540
4593
|
const results = [];
|
|
4541
4594
|
for (const file of files.sort((a, b) => a.localeCompare(b))) {
|
|
4542
4595
|
try {
|
|
4543
|
-
const parsed = JSON.parse(await
|
|
4596
|
+
const parsed = JSON.parse(await fs21.readFile(path14.join(memoryDir, file), "utf-8"));
|
|
4544
4597
|
if (parsed.question && parsed.savedAt) results.push(parsed);
|
|
4545
4598
|
} catch {
|
|
4546
4599
|
}
|
|
@@ -4617,18 +4670,18 @@ function renderLessons(results, options = {}) {
|
|
|
4617
4670
|
// src/cli/commands/install.ts
|
|
4618
4671
|
init_cjs_shims();
|
|
4619
4672
|
var import_node_module3 = require("module");
|
|
4620
|
-
var
|
|
4621
|
-
var
|
|
4673
|
+
var fs22 = __toESM(require("fs/promises"), 1);
|
|
4674
|
+
var path15 = __toESM(require("path"), 1);
|
|
4622
4675
|
var require4 = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
4623
4676
|
function packageRoot() {
|
|
4624
4677
|
const packageJsonPath = require4.resolve("@dreamtree-org/graphify/package.json");
|
|
4625
|
-
return
|
|
4678
|
+
return path15.dirname(packageJsonPath);
|
|
4626
4679
|
}
|
|
4627
4680
|
var MD_MARKER_BEGIN = "<!-- >>> graphify >>> -->";
|
|
4628
4681
|
var MD_MARKER_END = "<!-- <<< graphify <<< -->";
|
|
4629
4682
|
async function writeOwnedFile(filePath, content) {
|
|
4630
|
-
await
|
|
4631
|
-
await
|
|
4683
|
+
await fs22.mkdir(path15.dirname(filePath), { recursive: true });
|
|
4684
|
+
await fs22.writeFile(filePath, content, "utf-8");
|
|
4632
4685
|
}
|
|
4633
4686
|
async function upsertMarkedBlock(filePath, content) {
|
|
4634
4687
|
const block = `${MD_MARKER_BEGIN}
|
|
@@ -4636,32 +4689,32 @@ ${content.trim()}
|
|
|
4636
4689
|
${MD_MARKER_END}`;
|
|
4637
4690
|
let existing = null;
|
|
4638
4691
|
try {
|
|
4639
|
-
existing = await
|
|
4692
|
+
existing = await fs22.readFile(filePath, "utf-8");
|
|
4640
4693
|
} catch {
|
|
4641
4694
|
}
|
|
4642
4695
|
if (existing === null) {
|
|
4643
|
-
await
|
|
4644
|
-
await
|
|
4696
|
+
await fs22.mkdir(path15.dirname(filePath), { recursive: true });
|
|
4697
|
+
await fs22.writeFile(filePath, `${block}
|
|
4645
4698
|
`, "utf-8");
|
|
4646
4699
|
return;
|
|
4647
4700
|
}
|
|
4648
4701
|
if (existing.includes(MD_MARKER_BEGIN)) {
|
|
4649
4702
|
const pattern = new RegExp(`${MD_MARKER_BEGIN}[\\s\\S]*?${MD_MARKER_END}`);
|
|
4650
|
-
await
|
|
4703
|
+
await fs22.writeFile(filePath, existing.replace(pattern, block), "utf-8");
|
|
4651
4704
|
return;
|
|
4652
4705
|
}
|
|
4653
4706
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4654
|
-
await
|
|
4707
|
+
await fs22.writeFile(filePath, `${existing}${separator}${block}
|
|
4655
4708
|
`, "utf-8");
|
|
4656
4709
|
}
|
|
4657
4710
|
var PLATFORMS = {
|
|
4658
4711
|
claude: async (cwd, content) => {
|
|
4659
|
-
const target =
|
|
4712
|
+
const target = path15.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
|
|
4660
4713
|
await writeOwnedFile(target, content);
|
|
4661
4714
|
return { host: "Claude Code", path: target };
|
|
4662
4715
|
},
|
|
4663
4716
|
cursor: async (cwd, content) => {
|
|
4664
|
-
const target =
|
|
4717
|
+
const target = path15.join(cwd, ".cursor", "rules", "graphify.mdc");
|
|
4665
4718
|
const body = content.replace(/^---[\s\S]*?---\n/, "");
|
|
4666
4719
|
await writeOwnedFile(
|
|
4667
4720
|
target,
|
|
@@ -4674,30 +4727,30 @@ ${body}`
|
|
|
4674
4727
|
return { host: "Cursor", path: target };
|
|
4675
4728
|
},
|
|
4676
4729
|
windsurf: async (cwd, content) => {
|
|
4677
|
-
const target =
|
|
4730
|
+
const target = path15.join(cwd, ".windsurf", "rules", "graphify.md");
|
|
4678
4731
|
await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4679
4732
|
return { host: "Windsurf", path: target };
|
|
4680
4733
|
},
|
|
4681
4734
|
cline: async (cwd, content) => {
|
|
4682
|
-
const target =
|
|
4735
|
+
const target = path15.join(cwd, ".clinerules", "graphify.md");
|
|
4683
4736
|
await writeOwnedFile(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4684
4737
|
return { host: "Cline", path: target };
|
|
4685
4738
|
},
|
|
4686
4739
|
agents: async (cwd, content) => {
|
|
4687
|
-
const target =
|
|
4740
|
+
const target = path15.join(cwd, "AGENTS.md");
|
|
4688
4741
|
await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4689
4742
|
return { host: "AGENTS.md agents (Codex, opencode, Amp, ...)", path: target };
|
|
4690
4743
|
},
|
|
4691
4744
|
gemini: async (cwd, content) => {
|
|
4692
|
-
const target =
|
|
4745
|
+
const target = path15.join(cwd, "GEMINI.md");
|
|
4693
4746
|
await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
|
|
4694
4747
|
return { host: "Gemini CLI", path: target };
|
|
4695
4748
|
}
|
|
4696
4749
|
};
|
|
4697
4750
|
var SUPPORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => a.localeCompare(b));
|
|
4698
4751
|
async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
|
|
4699
|
-
const sourcePath =
|
|
4700
|
-
const content = await
|
|
4752
|
+
const sourcePath = path15.join(packageRoot(), "src", "skill", "SKILL.md");
|
|
4753
|
+
const content = await fs22.readFile(sourcePath, "utf-8");
|
|
4701
4754
|
const requested = platforms.includes("all") ? SUPPORTED_PLATFORMS : platforms;
|
|
4702
4755
|
const results = [];
|
|
4703
4756
|
for (const platform of requested) {
|
|
@@ -4781,7 +4834,7 @@ function looksLikeGitUrl(value) {
|
|
|
4781
4834
|
}
|
|
4782
4835
|
async function cloneRepo(url) {
|
|
4783
4836
|
const validated = validateUrl(url);
|
|
4784
|
-
const dest = await
|
|
4837
|
+
const dest = await fs24.mkdtemp(path17.join(os3.tmpdir(), "graphify-clone-"));
|
|
4785
4838
|
await execFileAsync3("git", ["clone", "--depth", "1", validated, dest]);
|
|
4786
4839
|
return dest;
|
|
4787
4840
|
}
|
|
@@ -4795,7 +4848,7 @@ function clusterOptionsFrom(options) {
|
|
|
4795
4848
|
};
|
|
4796
4849
|
}
|
|
4797
4850
|
async function runClusterOnly(root, options) {
|
|
4798
|
-
const outDir =
|
|
4851
|
+
const outDir = path17.join(root, "graphify-out");
|
|
4799
4852
|
const graph = await loadGraph(outDir);
|
|
4800
4853
|
cluster(graph, clusterOptionsFrom(options));
|
|
4801
4854
|
const analysis = analyze(graph);
|
|
@@ -4829,9 +4882,9 @@ var program = new import_commander.Command();
|
|
|
4829
4882
|
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
4883
|
try {
|
|
4831
4884
|
if (options.mcp) {
|
|
4832
|
-
const outDir =
|
|
4885
|
+
const outDir = path17.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
|
|
4833
4886
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4834
|
-
await startServer2(
|
|
4887
|
+
await startServer2(path17.join(outDir, "graph.json"));
|
|
4835
4888
|
return;
|
|
4836
4889
|
}
|
|
4837
4890
|
let root = targetArg;
|
|
@@ -4839,7 +4892,7 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
|
|
|
4839
4892
|
console.error(`Cloning ${targetArg} ...`);
|
|
4840
4893
|
root = await cloneRepo(targetArg);
|
|
4841
4894
|
}
|
|
4842
|
-
root =
|
|
4895
|
+
root = path17.resolve(root);
|
|
4843
4896
|
if (options.mode) {
|
|
4844
4897
|
console.error(`--mode ${options.mode} is reserved for a future richer-INFERRED-edges pass \u2014 currently a no-op.`);
|
|
4845
4898
|
}
|
|
@@ -4993,7 +5046,7 @@ program.command("merge <dirs...>").description("one-shot merge of two or more bu
|
|
|
4993
5046
|
});
|
|
4994
5047
|
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
5048
|
try {
|
|
4996
|
-
const memoryDir =
|
|
5049
|
+
const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
|
|
4997
5050
|
const file = await saveResult(
|
|
4998
5051
|
{
|
|
4999
5052
|
question: options.question,
|
|
@@ -5013,12 +5066,12 @@ program.command("save-result").description("save a Q&A result to graphify-out/me
|
|
|
5013
5066
|
});
|
|
5014
5067
|
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
5068
|
try {
|
|
5016
|
-
const memoryDir =
|
|
5069
|
+
const memoryDir = path17.join(process.cwd(), "graphify-out", "memory");
|
|
5017
5070
|
const results = await loadResults(memoryDir);
|
|
5018
5071
|
const lessons = renderLessons(results, { halfLifeDays: options.halfLifeDays });
|
|
5019
|
-
const outPath =
|
|
5020
|
-
await
|
|
5021
|
-
await
|
|
5072
|
+
const outPath = path17.resolve(options.out ?? path17.join(process.cwd(), "graphify-out", "reflections", "LESSONS.md"));
|
|
5073
|
+
await fs24.mkdir(path17.dirname(outPath), { recursive: true });
|
|
5074
|
+
await fs24.writeFile(outPath, lessons, "utf-8");
|
|
5022
5075
|
console.log(`Reflected ${results.length} result(s) -> ${outPath}`);
|
|
5023
5076
|
} catch (error) {
|
|
5024
5077
|
console.error(`graphify reflect: ${error.message}`);
|