@dreamtree-org/graphify 1.0.0
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/LICENSE +21 -0
- package/README.md +69 -0
- package/bin/graphify-mcp.js +7 -0
- package/bin/graphify.js +2 -0
- package/dist/chunk-5ANIDX3G.js +364 -0
- package/dist/chunk-5ANIDX3G.js.map +1 -0
- package/dist/chunk-DG5FECXV.js +2474 -0
- package/dist/chunk-DG5FECXV.js.map +1 -0
- package/dist/cli/index.cjs +3250 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +267 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +2759 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +301 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.cjs +336 -0
- package/dist/mcp/server.cjs.map +1 -0
- package/dist/mcp/server.d.cts +22 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.js +88 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +81 -0
- package/src/skill/SKILL.md +74 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
analyze,
|
|
4
|
+
cluster,
|
|
5
|
+
exportGraph,
|
|
6
|
+
renderReport,
|
|
7
|
+
runPipeline
|
|
8
|
+
} from "../chunk-DG5FECXV.js";
|
|
9
|
+
import {
|
|
10
|
+
explainNode,
|
|
11
|
+
loadGraph,
|
|
12
|
+
queryGraph,
|
|
13
|
+
shortestPath,
|
|
14
|
+
validateUrl
|
|
15
|
+
} from "../chunk-5ANIDX3G.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/index.ts
|
|
18
|
+
import { execFile } from "child_process";
|
|
19
|
+
import * as fs2 from "fs/promises";
|
|
20
|
+
import * as os from "os";
|
|
21
|
+
import * as path2 from "path";
|
|
22
|
+
import { promisify } from "util";
|
|
23
|
+
import { watch as watchFiles } from "chokidar";
|
|
24
|
+
import { Command } from "commander";
|
|
25
|
+
|
|
26
|
+
// src/cli/commands/explain.ts
|
|
27
|
+
async function runExplain(nodeName) {
|
|
28
|
+
const graph = await loadGraph();
|
|
29
|
+
const result = explainNode(graph, nodeName);
|
|
30
|
+
if (!result) {
|
|
31
|
+
console.log(`No node matched "${nodeName}".`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const location = result.sourceLocation ? `:${result.sourceLocation}` : "";
|
|
35
|
+
console.log(result.label);
|
|
36
|
+
console.log(` location: ${result.sourceFile}${location}`);
|
|
37
|
+
if (result.communityLabel) {
|
|
38
|
+
console.log(` community: ${result.communityLabel}`);
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log(`Calls / references out (${result.outgoing.length}):`);
|
|
42
|
+
if (result.outgoing.length === 0) {
|
|
43
|
+
console.log(" (none)");
|
|
44
|
+
} else {
|
|
45
|
+
for (const edge of result.outgoing) {
|
|
46
|
+
console.log(` --${edge.relation}--> ${edge.target} (${edge.confidence})`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(`Referenced by (${result.incoming.length}):`);
|
|
51
|
+
if (result.incoming.length === 0) {
|
|
52
|
+
console.log(" (none)");
|
|
53
|
+
} else {
|
|
54
|
+
for (const edge of result.incoming) {
|
|
55
|
+
console.log(` ${edge.source} --${edge.relation}--> (${edge.confidence})`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/cli/commands/install.ts
|
|
61
|
+
import { createRequire } from "module";
|
|
62
|
+
import * as fs from "fs/promises";
|
|
63
|
+
import * as path from "path";
|
|
64
|
+
var require2 = createRequire(import.meta.url);
|
|
65
|
+
function packageRoot() {
|
|
66
|
+
const packageJsonPath = require2.resolve("@dreamtree-org/graphify/package.json");
|
|
67
|
+
return path.dirname(packageJsonPath);
|
|
68
|
+
}
|
|
69
|
+
async function runInstall(cwd = process.cwd()) {
|
|
70
|
+
const sourcePath = path.join(packageRoot(), "src", "skill", "SKILL.md");
|
|
71
|
+
const content = await fs.readFile(sourcePath, "utf-8");
|
|
72
|
+
const results = [];
|
|
73
|
+
const claudeCodeDir = path.join(cwd, ".claude", "skills", "graphify");
|
|
74
|
+
await fs.mkdir(claudeCodeDir, { recursive: true });
|
|
75
|
+
const claudeCodePath = path.join(claudeCodeDir, "SKILL.md");
|
|
76
|
+
await fs.writeFile(claudeCodePath, content, "utf-8");
|
|
77
|
+
results.push({ host: "Claude Code", path: claudeCodePath });
|
|
78
|
+
for (const result of results) {
|
|
79
|
+
console.log(`Installed graphify skill for ${result.host} -> ${result.path}`);
|
|
80
|
+
}
|
|
81
|
+
console.log("");
|
|
82
|
+
console.log(
|
|
83
|
+
"Other hosts (Cursor, etc.) are not implemented yet by `graphify install` \u2014 see ARCHITECTURE.md."
|
|
84
|
+
);
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/cli/commands/path.ts
|
|
89
|
+
async function runPath(fromNode, toNode) {
|
|
90
|
+
const graph = await loadGraph();
|
|
91
|
+
const result = shortestPath(graph, fromNode, toNode);
|
|
92
|
+
if (!result) {
|
|
93
|
+
console.log(`Could not resolve "${fromNode}" and/or "${toNode}" to a node in the graph.`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!result.found) {
|
|
97
|
+
console.log(
|
|
98
|
+
`No path found between "${result.from.label}" and "${result.to.label}" \u2014 they may be in disconnected parts of the graph (see GRAPH_REPORT.md \xA7 Open Questions).`
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log(`${result.from.label} -> ${result.to.label} (${result.path.length} node(s)):`);
|
|
103
|
+
console.log(` ${result.path.join(" -> ")}`);
|
|
104
|
+
if (result.edges.length > 0) {
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log("Edges along the path:");
|
|
107
|
+
for (const edge of result.edges) {
|
|
108
|
+
console.log(` ${edge.source} --${edge.relation}--> ${edge.target} (${edge.confidence})`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/cli/commands/query.ts
|
|
114
|
+
async function runQuery(question, options = {}) {
|
|
115
|
+
const graph = await loadGraph();
|
|
116
|
+
const result = queryGraph(graph, question, { dfs: options.dfs, budget: options.budget });
|
|
117
|
+
if (result.seeds.length === 0) {
|
|
118
|
+
console.log(
|
|
119
|
+
`No nodes matched "${question}". Try different keywords, or run \`graphify explain <node>\` if you already know the name.`
|
|
120
|
+
);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
console.log(`Matched ${result.seeds.length} seed node(s):`);
|
|
124
|
+
for (const seed of result.seeds) {
|
|
125
|
+
console.log(` - ${seed.label} (${seed.id})`);
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log(`Context \u2014 ${result.visited.length} node(s), ${options.dfs ? "DFS" : "BFS"} traversal:`);
|
|
129
|
+
for (const node of result.visited) {
|
|
130
|
+
const location = node.sourceLocation ? `:${node.sourceLocation}` : "";
|
|
131
|
+
console.log(` [depth ${node.depth}] ${node.label} \u2014 ${node.sourceFile}${location}`);
|
|
132
|
+
}
|
|
133
|
+
console.log("");
|
|
134
|
+
console.log(`Edges within this context (${result.edges.length}):`);
|
|
135
|
+
for (const edge of result.edges) {
|
|
136
|
+
console.log(` ${edge.source} --${edge.relation}--> ${edge.target} (${edge.confidence})`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/cli/index.ts
|
|
141
|
+
var execFileAsync = promisify(execFile);
|
|
142
|
+
function looksLikeGitUrl(value) {
|
|
143
|
+
if (!/^https?:\/\//i.test(value)) return false;
|
|
144
|
+
return /github\.com|gitlab\.com|bitbucket\.org/i.test(value) || value.endsWith(".git");
|
|
145
|
+
}
|
|
146
|
+
async function cloneRepo(url) {
|
|
147
|
+
const validated = validateUrl(url);
|
|
148
|
+
const dest = await fs2.mkdtemp(path2.join(os.tmpdir(), "graphify-clone-"));
|
|
149
|
+
await execFileAsync("git", ["clone", "--depth", "1", validated, dest]);
|
|
150
|
+
return dest;
|
|
151
|
+
}
|
|
152
|
+
function exportOptionsFrom(options) {
|
|
153
|
+
return { html: options.viz, svg: options.svg, graphml: options.graphml, neo4j: options.neo4j };
|
|
154
|
+
}
|
|
155
|
+
function clusterOptionsFrom(options) {
|
|
156
|
+
return {
|
|
157
|
+
algorithm: options.leiden ? "leiden" : "louvain",
|
|
158
|
+
maxIterations: options.maxIterations
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function runClusterOnly(root, options) {
|
|
162
|
+
const outDir = path2.join(root, "graphify-out");
|
|
163
|
+
const graph = await loadGraph(outDir);
|
|
164
|
+
cluster(graph, clusterOptionsFrom(options));
|
|
165
|
+
const analysis = analyze(graph);
|
|
166
|
+
const report = renderReport(graph, analysis);
|
|
167
|
+
await exportGraph(graph, { outDir, ...exportOptionsFrom(options) }, report);
|
|
168
|
+
console.error(`Re-clustered the existing graph (${clusterOptionsFrom(options).algorithm}).`);
|
|
169
|
+
}
|
|
170
|
+
async function watchAndRebuild(root, runOnce) {
|
|
171
|
+
const watcher = watchFiles(root, {
|
|
172
|
+
ignored: (filePath) => /(^|[/\\])(node_modules|\.git|graphify-out)([/\\]|$)/.test(filePath),
|
|
173
|
+
ignoreInitial: true
|
|
174
|
+
});
|
|
175
|
+
let timer = null;
|
|
176
|
+
let running = false;
|
|
177
|
+
const schedule = () => {
|
|
178
|
+
if (timer) clearTimeout(timer);
|
|
179
|
+
timer = setTimeout(() => {
|
|
180
|
+
if (running) return;
|
|
181
|
+
running = true;
|
|
182
|
+
console.error("Change detected, rebuilding...");
|
|
183
|
+
runOnce().catch((error) => console.error(`graphify --watch: rebuild failed: ${error.message}`)).finally(() => {
|
|
184
|
+
running = false;
|
|
185
|
+
});
|
|
186
|
+
}, 300);
|
|
187
|
+
};
|
|
188
|
+
watcher.on("add", schedule).on("change", schedule).on("unlink", schedule);
|
|
189
|
+
await new Promise(() => {
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
var program = new Command();
|
|
193
|
+
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 re-extract of changed files only (not implemented in v1 \u2014 runs full pipeline)").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").action(async (targetArg, options) => {
|
|
194
|
+
try {
|
|
195
|
+
if (options.mcp) {
|
|
196
|
+
const outDir = path2.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
|
|
197
|
+
const { startServer } = await import("../mcp/server.js");
|
|
198
|
+
await startServer(path2.join(outDir, "graph.json"));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let root = targetArg;
|
|
202
|
+
if (looksLikeGitUrl(targetArg)) {
|
|
203
|
+
console.error(`Cloning ${targetArg} ...`);
|
|
204
|
+
root = await cloneRepo(targetArg);
|
|
205
|
+
}
|
|
206
|
+
root = path2.resolve(root);
|
|
207
|
+
if (options.update) {
|
|
208
|
+
console.error(
|
|
209
|
+
"--update (incremental re-extract) is not implemented yet in v1 \u2014 running the full pipeline instead."
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (options.mode) {
|
|
213
|
+
console.error(`--mode ${options.mode} is reserved for a future richer-INFERRED-edges pass \u2014 currently a no-op.`);
|
|
214
|
+
}
|
|
215
|
+
if (options.clusterOnly) {
|
|
216
|
+
await runClusterOnly(root, options);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const runOnce = () => runPipeline(root, {
|
|
220
|
+
...exportOptionsFrom(options),
|
|
221
|
+
...clusterOptionsFrom(options),
|
|
222
|
+
onProgress: (m) => console.error(m)
|
|
223
|
+
});
|
|
224
|
+
await runOnce();
|
|
225
|
+
if (options.watch) {
|
|
226
|
+
console.error(`Watching ${root} for changes (Ctrl+C to stop) ...`);
|
|
227
|
+
await watchAndRebuild(root, runOnce);
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error(`graphify: ${error.message}`);
|
|
231
|
+
process.exitCode = 1;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
program.command("query <question>").description("BFS (default) or DFS traversal answering a question against the existing graph").option("--dfs", "depth-first traversal (trace a specific path)").option("--budget <tokens>", "cap answer at N tokens", Number).action(async (question, options) => {
|
|
235
|
+
try {
|
|
236
|
+
await runQuery(question, options);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error(`graphify query: ${error.message}`);
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
program.command("path <from> <to>").description("shortest path between two named nodes").action(async (from, to) => {
|
|
243
|
+
try {
|
|
244
|
+
await runPath(from, to);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error(`graphify path: ${error.message}`);
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
program.command("explain <node>").description("plain-language explanation of a node").action(async (node) => {
|
|
251
|
+
try {
|
|
252
|
+
await runExplain(node);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error(`graphify explain: ${error.message}`);
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
program.command("install").description("install the skill files into the local agent(s)").action(async () => {
|
|
259
|
+
try {
|
|
260
|
+
await runInstall();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error(`graphify install: ${error.message}`);
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
program.parseAsync(process.argv);
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/explain.ts","../../src/cli/commands/install.ts","../../src/cli/commands/path.ts","../../src/cli/commands/query.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { execFile } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\nimport { watch as watchFiles } from 'chokidar';\nimport { Command } from 'commander';\nimport { analyze } from '../analyze.js';\nimport { cluster } from '../cluster.js';\nimport { exportGraph } from '../export.js';\nimport { loadGraph } from '../graphStore.js';\nimport { runPipeline } from '../pipeline.js';\nimport { renderReport } from '../report.js';\nimport { validateUrl } from '../security.js';\nimport { runExplain } from './commands/explain.js';\nimport { runInstall } from './commands/install.js';\nimport { runPath } from './commands/path.js';\nimport { runQuery } from './commands/query.js';\n\nconst execFileAsync = promisify(execFile);\n\ninterface DefaultCommandOptions {\n mode?: string;\n update?: boolean;\n watch?: boolean;\n clusterOnly?: boolean;\n viz: boolean; // commander negatable --no-viz -> options.viz\n svg?: boolean;\n graphml?: boolean;\n neo4j?: boolean;\n mcp?: boolean;\n leiden?: boolean;\n maxIterations?: number;\n}\n\nfunction looksLikeGitUrl(value: string): boolean {\n if (!/^https?:\\/\\//i.test(value)) return false;\n return /github\\.com|gitlab\\.com|bitbucket\\.org/i.test(value) || value.endsWith('.git');\n}\n\n/** Clone `url` into a fresh temp directory using an argv-array child_process call (never shell: true, never a shell string built from input). */\nasync function cloneRepo(url: string): Promise<string> {\n const validated = validateUrl(url);\n const dest = await fs.mkdtemp(path.join(os.tmpdir(), 'graphify-clone-'));\n await execFileAsync('git', ['clone', '--depth', '1', validated, dest]);\n return dest;\n}\n\nfunction exportOptionsFrom(options: DefaultCommandOptions) {\n return { html: options.viz, svg: options.svg, graphml: options.graphml, neo4j: options.neo4j };\n}\n\nfunction clusterOptionsFrom(options: DefaultCommandOptions) {\n return {\n algorithm: options.leiden ? ('leiden' as const) : ('louvain' as const),\n maxIterations: options.maxIterations,\n };\n}\n\nasync function runClusterOnly(root: string, options: DefaultCommandOptions): Promise<void> {\n const outDir = path.join(root, 'graphify-out');\n const graph = await loadGraph(outDir);\n cluster(graph, clusterOptionsFrom(options));\n const analysis = analyze(graph);\n const report = renderReport(graph, analysis);\n await exportGraph(graph, { outDir, ...exportOptionsFrom(options) }, report);\n console.error(`Re-clustered the existing graph (${clusterOptionsFrom(options).algorithm}).`);\n}\n\n/** Watch `root` (ignoring graphify-out/, node_modules/, .git/) and re-run `runOnce` on any change, debounced. Never resolves — the process stays alive until interrupted, matching normal watch-mode CLI behavior. */\nasync function watchAndRebuild(root: string, runOnce: () => Promise<unknown>): Promise<void> {\n const watcher = watchFiles(root, {\n ignored: (filePath: string) => /(^|[/\\\\])(node_modules|\\.git|graphify-out)([/\\\\]|$)/.test(filePath),\n ignoreInitial: true,\n });\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n let running = false;\n const schedule = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n if (running) return;\n running = true;\n console.error('Change detected, rebuilding...');\n runOnce()\n .catch((error: unknown) => console.error(`graphify --watch: rebuild failed: ${(error as Error).message}`))\n .finally(() => {\n running = false;\n });\n }, 300);\n };\n\n watcher.on('add', schedule).on('change', schedule).on('unlink', schedule);\n\n await new Promise<never>(() => {\n /* keep the process alive until the user interrupts it (Ctrl+C) */\n });\n}\n\nconst program = new Command();\n\nprogram\n .name('graphify')\n .description('Turn a folder of code/docs/papers into a queryable knowledge graph.')\n .argument('[path]', 'path to scan, or a GitHub URL', '.')\n .option('--mode <mode>', 'extraction mode (deep for richer INFERRED edges — reserved, no-op in v1)')\n .option('--update', 'incremental re-extract of changed files only (not implemented in v1 — runs full pipeline)')\n .option('--watch', 'rebuild on file change')\n .option('--cluster-only', 'rerun clustering on an existing graph')\n .option('--leiden', 'use Leiden instead of Louvain for community detection (v2 — better community quality)')\n .option('--max-iterations <n>', 'Leiden only: cap on outer iterations for very large graphs', Number)\n .option('--no-viz', 'skip graph.html')\n .option('--svg', 'also export graph.svg (not implemented in v1)')\n .option('--graphml', 'also export graph.graphml (not implemented in v1)')\n .option('--neo4j', 'generate graphify-out/cypher.txt for Neo4j (not implemented in v1)')\n .option('--mcp', 'start MCP stdio server instead of running the pipeline')\n .action(async (targetArg: string, options: DefaultCommandOptions) => {\n try {\n if (options.mcp) {\n const outDir = path.resolve(targetArg === '.' ? process.cwd() : targetArg, 'graphify-out');\n const { startServer } = await import('../mcp/server.js');\n await startServer(path.join(outDir, 'graph.json'));\n return;\n }\n\n let root = targetArg;\n if (looksLikeGitUrl(targetArg)) {\n console.error(`Cloning ${targetArg} ...`);\n root = await cloneRepo(targetArg);\n }\n root = path.resolve(root);\n\n if (options.update) {\n console.error(\n '--update (incremental re-extract) is not implemented yet in v1 — running the full pipeline instead.',\n );\n }\n if (options.mode) {\n console.error(`--mode ${options.mode} is reserved for a future richer-INFERRED-edges pass — currently a no-op.`);\n }\n\n if (options.clusterOnly) {\n await runClusterOnly(root, options);\n return;\n }\n\n const runOnce = () =>\n runPipeline(root, {\n ...exportOptionsFrom(options),\n ...clusterOptionsFrom(options),\n onProgress: (m) => console.error(m),\n });\n await runOnce();\n\n if (options.watch) {\n console.error(`Watching ${root} for changes (Ctrl+C to stop) ...`);\n await watchAndRebuild(root, runOnce);\n }\n } catch (error) {\n console.error(`graphify: ${(error as Error).message}`);\n process.exitCode = 1;\n }\n });\n\nprogram\n .command('query <question>')\n .description('BFS (default) or DFS traversal answering a question against the existing graph')\n .option('--dfs', 'depth-first traversal (trace a specific path)')\n .option('--budget <tokens>', 'cap answer at N tokens', Number)\n .action(async (question: string, options: { dfs?: boolean; budget?: number }) => {\n try {\n await runQuery(question, options);\n } catch (error) {\n console.error(`graphify query: ${(error as Error).message}`);\n process.exitCode = 1;\n }\n });\n\nprogram\n .command('path <from> <to>')\n .description('shortest path between two named nodes')\n .action(async (from: string, to: string) => {\n try {\n await runPath(from, to);\n } catch (error) {\n console.error(`graphify path: ${(error as Error).message}`);\n process.exitCode = 1;\n }\n });\n\nprogram\n .command('explain <node>')\n .description('plain-language explanation of a node')\n .action(async (node: string) => {\n try {\n await runExplain(node);\n } catch (error) {\n console.error(`graphify explain: ${(error as Error).message}`);\n process.exitCode = 1;\n }\n });\n\nprogram\n .command('install')\n .description('install the skill files into the local agent(s)')\n .action(async () => {\n try {\n await runInstall();\n } catch (error) {\n console.error(`graphify install: ${(error as Error).message}`);\n process.exitCode = 1;\n }\n });\n\nprogram.parseAsync(process.argv);\n","import { loadGraph } from '../../graphStore.js';\nimport { explainNode } from '../../query.js';\n\n/** Plain-language explanation of a single node. */\nexport async function runExplain(nodeName: string): Promise<void> {\n const graph = await loadGraph();\n const result = explainNode(graph, nodeName);\n\n if (!result) {\n console.log(`No node matched \"${nodeName}\".`);\n return;\n }\n\n const location = result.sourceLocation ? `:${result.sourceLocation}` : '';\n console.log(result.label);\n console.log(` location: ${result.sourceFile}${location}`);\n if (result.communityLabel) {\n console.log(` community: ${result.communityLabel}`);\n }\n\n console.log('');\n console.log(`Calls / references out (${result.outgoing.length}):`);\n if (result.outgoing.length === 0) {\n console.log(' (none)');\n } else {\n for (const edge of result.outgoing) {\n console.log(` --${edge.relation}--> ${edge.target} (${edge.confidence})`);\n }\n }\n\n console.log('');\n console.log(`Referenced by (${result.incoming.length}):`);\n if (result.incoming.length === 0) {\n console.log(' (none)');\n } else {\n for (const edge of result.incoming) {\n console.log(` ${edge.source} --${edge.relation}--> (${edge.confidence})`);\n }\n }\n}\n","import { createRequire } from 'node:module';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Resolve this package's own root directory, whether we're running from\n * source (tests/dev), the built dist/ bundle, or installed as a real\n * dependency elsewhere. Uses Node's self-reference resolution (walking up\n * from this module's location to the nearest package.json named\n * \"@dreamtree-org/graphify\") rather than a hardcoded relative path, since\n * the number of directories between this file and the package root\n * differs between src/ and the bundled dist/ output.\n */\nfunction packageRoot(): string {\n const packageJsonPath = require.resolve('@dreamtree-org/graphify/package.json');\n return path.dirname(packageJsonPath);\n}\n\nexport interface InstallResult {\n host: string;\n path: string;\n}\n\n/**\n * Copy SKILL.md into the local agent's skill directory. Claude Code only\n * for v1 (project-local `.claude/skills/graphify/SKILL.md`) — other hosts\n * (Cursor, etc.) are a clearly-flagged follow-up, not faked with an empty\n * or copy-pasted file.\n */\nexport async function runInstall(cwd: string = process.cwd()): Promise<InstallResult[]> {\n const sourcePath = path.join(packageRoot(), 'src', 'skill', 'SKILL.md');\n const content = await fs.readFile(sourcePath, 'utf-8');\n\n const results: InstallResult[] = [];\n\n const claudeCodeDir = path.join(cwd, '.claude', 'skills', 'graphify');\n await fs.mkdir(claudeCodeDir, { recursive: true });\n const claudeCodePath = path.join(claudeCodeDir, 'SKILL.md');\n await fs.writeFile(claudeCodePath, content, 'utf-8');\n results.push({ host: 'Claude Code', path: claudeCodePath });\n\n for (const result of results) {\n console.log(`Installed graphify skill for ${result.host} -> ${result.path}`);\n }\n console.log('');\n console.log(\n 'Other hosts (Cursor, etc.) are not implemented yet by `graphify install` — see ARCHITECTURE.md.',\n );\n\n return results;\n}\n","import { loadGraph } from '../../graphStore.js';\nimport { shortestPath } from '../../query.js';\n\n/** Shortest path between two named nodes. */\nexport async function runPath(fromNode: string, toNode: string): Promise<void> {\n const graph = await loadGraph();\n const result = shortestPath(graph, fromNode, toNode);\n\n if (!result) {\n console.log(`Could not resolve \"${fromNode}\" and/or \"${toNode}\" to a node in the graph.`);\n return;\n }\n\n if (!result.found) {\n console.log(\n `No path found between \"${result.from.label}\" and \"${result.to.label}\" — they may be in ` +\n 'disconnected parts of the graph (see GRAPH_REPORT.md § Open Questions).',\n );\n return;\n }\n\n console.log(`${result.from.label} -> ${result.to.label} (${result.path.length} node(s)):`);\n console.log(` ${result.path.join(' -> ')}`);\n if (result.edges.length > 0) {\n console.log('');\n console.log('Edges along the path:');\n for (const edge of result.edges) {\n console.log(` ${edge.source} --${edge.relation}--> ${edge.target} (${edge.confidence})`);\n }\n }\n}\n","import { loadGraph } from '../../graphStore.js';\nimport { queryGraph } from '../../query.js';\n\nexport interface QueryOptions {\n dfs?: boolean;\n budget?: number;\n}\n\n/** BFS (default) or DFS traversal answering a natural-language question against the graph. */\nexport async function runQuery(question: string, options: QueryOptions = {}): Promise<void> {\n const graph = await loadGraph();\n const result = queryGraph(graph, question, { dfs: options.dfs, budget: options.budget });\n\n if (result.seeds.length === 0) {\n console.log(\n `No nodes matched \"${question}\". Try different keywords, or run \\`graphify explain <node>\\` ` +\n 'if you already know the name.',\n );\n return;\n }\n\n console.log(`Matched ${result.seeds.length} seed node(s):`);\n for (const seed of result.seeds) {\n console.log(` - ${seed.label} (${seed.id})`);\n }\n\n console.log('');\n console.log(`Context — ${result.visited.length} node(s), ${options.dfs ? 'DFS' : 'BFS'} traversal:`);\n for (const node of result.visited) {\n const location = node.sourceLocation ? `:${node.sourceLocation}` : '';\n console.log(` [depth ${node.depth}] ${node.label} — ${node.sourceFile}${location}`);\n }\n\n console.log('');\n console.log(`Edges within this context (${result.edges.length}):`);\n for (const edge of result.edges) {\n console.log(` ${edge.source} --${edge.relation}--> ${edge.target} (${edge.confidence})`);\n }\n}\n"],"mappings":"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","names":["fs","path","require"]}
|