@dreamtree-org/graphify 1.1.3 → 1.3.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.
@@ -246,22 +246,73 @@ var init_security = __esm({
246
246
  }
247
247
  });
248
248
 
249
+ // src/store/serialize.ts
250
+ function serializeGraph(graph) {
251
+ return graph.export();
252
+ }
253
+ function deserializeGraph(data) {
254
+ return import_graphology2.default.from(data);
255
+ }
256
+ var import_graphology2;
257
+ var init_serialize = __esm({
258
+ "src/store/serialize.ts"() {
259
+ "use strict";
260
+ init_cjs_shims();
261
+ import_graphology2 = __toESM(require("graphology"), 1);
262
+ }
263
+ });
264
+
265
+ // src/store/localFile.ts
266
+ var fs2, LocalFileGraphStore;
267
+ var init_localFile = __esm({
268
+ "src/store/localFile.ts"() {
269
+ "use strict";
270
+ init_cjs_shims();
271
+ fs2 = __toESM(require("fs/promises"), 1);
272
+ init_security();
273
+ init_serialize();
274
+ LocalFileGraphStore = class {
275
+ async load(ref) {
276
+ let jsonPath;
277
+ try {
278
+ jsonPath = validateGraphPath("graph.json", ref);
279
+ } catch (error) {
280
+ if (error.message.startsWith("Base directory does not exist")) return null;
281
+ throw error;
282
+ }
283
+ let raw;
284
+ try {
285
+ raw = await fs2.readFile(jsonPath, "utf-8");
286
+ } catch (error) {
287
+ if (error.code === "ENOENT") return null;
288
+ throw error;
289
+ }
290
+ return deserializeGraph(JSON.parse(raw));
291
+ }
292
+ async save(ref, graph) {
293
+ await fs2.mkdir(ref, { recursive: true });
294
+ const jsonPath = validateGraphPath("graph.json", ref);
295
+ await fs2.writeFile(jsonPath, JSON.stringify(serializeGraph(graph), null, 2), "utf-8");
296
+ }
297
+ };
298
+ }
299
+ });
300
+
249
301
  // src/graphStore.ts
250
302
  async function loadGraph(outDir = path2.join(process.cwd(), "graphify-out")) {
251
- const jsonPath = validateGraphPath("graph.json", outDir);
252
- const raw = await fs3.readFile(jsonPath, "utf-8");
253
- const data = JSON.parse(raw);
254
- return import_graphology2.default.from(data);
303
+ const graph = await new LocalFileGraphStore().load(outDir);
304
+ if (graph === null) {
305
+ throw new Error(`No graph found in ${outDir} \u2014 run \`graphify <path>\` to build one first.`);
306
+ }
307
+ return graph;
255
308
  }
256
- var fs3, path2, import_graphology2;
309
+ var path2;
257
310
  var init_graphStore = __esm({
258
311
  "src/graphStore.ts"() {
259
312
  "use strict";
260
313
  init_cjs_shims();
261
- fs3 = __toESM(require("fs/promises"), 1);
262
314
  path2 = __toESM(require("path"), 1);
263
- import_graphology2 = __toESM(require("graphology"), 1);
264
- init_security();
315
+ init_localFile();
265
316
  }
266
317
  });
267
318
 
@@ -1355,9 +1406,10 @@ function cluster(graph, options = {}) {
1355
1406
  // src/export.ts
1356
1407
  init_cjs_shims();
1357
1408
  var import_node_module = require("module");
1358
- var fs2 = __toESM(require("fs/promises"), 1);
1409
+ var fs3 = __toESM(require("fs/promises"), 1);
1359
1410
  var path = __toESM(require("path"), 1);
1360
1411
  init_security();
1412
+ init_localFile();
1361
1413
  var require2 = (0, import_node_module.createRequire)(importMetaUrl);
1362
1414
  function resolveVisNetworkAssets() {
1363
1415
  const packageJsonPath = require2.resolve("vis-network/package.json");
@@ -1423,8 +1475,8 @@ community: ${communityLabel}` : ""}`,
1423
1475
  async function renderHtml(graph) {
1424
1476
  const { jsPath, cssPath } = resolveVisNetworkAssets();
1425
1477
  const [visJs, visCss] = await Promise.all([
1426
- fs2.readFile(jsPath, "utf-8"),
1427
- fs2.readFile(cssPath, "utf-8")
1478
+ fs3.readFile(jsPath, "utf-8"),
1479
+ fs3.readFile(cssPath, "utf-8")
1428
1480
  ]);
1429
1481
  const { nodes, edges } = buildVisNodesAndEdges(graph);
1430
1482
  const dataJson = safeInlineJson({ nodes, edges });
@@ -1465,16 +1517,15 @@ ${visJs}
1465
1517
  }
1466
1518
  async function exportGraph(graph, options, report) {
1467
1519
  const outDir = path.resolve(options.outDir);
1468
- await fs2.mkdir(outDir, { recursive: true });
1469
- const jsonPath = validateGraphPath("graph.json", outDir);
1470
- await fs2.writeFile(jsonPath, JSON.stringify(graph.toJSON(), null, 2), "utf-8");
1520
+ await fs3.mkdir(outDir, { recursive: true });
1521
+ await new LocalFileGraphStore().save(outDir, graph);
1471
1522
  if (options.html !== false) {
1472
1523
  const htmlPath = validateGraphPath("graph.html", outDir);
1473
- await fs2.writeFile(htmlPath, await renderHtml(graph), "utf-8");
1524
+ await fs3.writeFile(htmlPath, await renderHtml(graph), "utf-8");
1474
1525
  }
1475
1526
  if (report !== void 0) {
1476
1527
  const reportPath = validateGraphPath("GRAPH_REPORT.md", outDir);
1477
- await fs2.writeFile(reportPath, report, "utf-8");
1528
+ await fs3.writeFile(reportPath, report, "utf-8");
1478
1529
  }
1479
1530
  const notImplemented = [];
1480
1531
  if (options.svg) notImplemented.push("--svg");
@@ -3912,6 +3963,11 @@ async function runPipeline(root, options = {}) {
3912
3963
  },
3913
3964
  report
3914
3965
  );
3966
+ if (options.store) {
3967
+ const storeRef = options.storeRef ?? outDir;
3968
+ progress(`Saving graph to store (${storeRef}) ...`);
3969
+ await options.store.save(storeRef, graph);
3970
+ }
3915
3971
  progress("Done.");
3916
3972
  return { manifest, graph, analysis, report };
3917
3973
  }
@@ -4778,6 +4834,29 @@ function packageRoot() {
4778
4834
  const packageJsonPath = require4.resolve("@dreamtree-org/graphify/package.json");
4779
4835
  return path15.dirname(packageJsonPath);
4780
4836
  }
4837
+ var MCP_SERVER_ENTRY = {
4838
+ command: "npx",
4839
+ args: ["-y", "@dreamtree-org/graphify", "--mcp"]
4840
+ };
4841
+ async function upsertMcpServer(filePath, serversKey = "mcpServers") {
4842
+ let config = {};
4843
+ try {
4844
+ config = JSON.parse(await fs22.readFile(filePath, "utf-8"));
4845
+ } catch (error) {
4846
+ if (error.code !== "ENOENT") {
4847
+ throw new Error(`cannot update ${filePath} \u2014 it exists but is not valid JSON; fix or remove it and re-run.`, {
4848
+ cause: error
4849
+ });
4850
+ }
4851
+ }
4852
+ const servers = config[serversKey] ?? {};
4853
+ servers["graphify"] = MCP_SERVER_ENTRY;
4854
+ config[serversKey] = servers;
4855
+ await fs22.mkdir(path15.dirname(filePath), { recursive: true });
4856
+ await fs22.writeFile(filePath, `${JSON.stringify(config, null, 2)}
4857
+ `, "utf-8");
4858
+ return filePath;
4859
+ }
4781
4860
  var MD_MARKER_BEGIN = "<!-- >>> graphify >>> -->";
4782
4861
  var MD_MARKER_END = "<!-- <<< graphify <<< -->";
4783
4862
  async function writeOwnedFile(filePath, content) {
@@ -4812,7 +4891,8 @@ var PLATFORMS = {
4812
4891
  claude: async (cwd, content) => {
4813
4892
  const target = path15.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
4814
4893
  await writeOwnedFile(target, content);
4815
- return { host: "Claude Code", path: target };
4894
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".mcp.json"));
4895
+ return { host: "Claude Code", path: target, mcpPath };
4816
4896
  },
4817
4897
  cursor: async (cwd, content) => {
4818
4898
  const target = path15.join(cwd, ".cursor", "rules", "graphify.mdc");
@@ -4825,7 +4905,8 @@ alwaysApply: false
4825
4905
  ---
4826
4906
  ${body}`
4827
4907
  );
4828
- return { host: "Cursor", path: target };
4908
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".cursor", "mcp.json"));
4909
+ return { host: "Cursor", path: target, mcpPath };
4829
4910
  },
4830
4911
  windsurf: async (cwd, content) => {
4831
4912
  const target = path15.join(cwd, ".windsurf", "rules", "graphify.md");
@@ -4845,10 +4926,32 @@ ${body}`
4845
4926
  gemini: async (cwd, content) => {
4846
4927
  const target = path15.join(cwd, "GEMINI.md");
4847
4928
  await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
4848
- return { host: "Gemini CLI", path: target };
4929
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".gemini", "settings.json"));
4930
+ return { host: "Gemini CLI", path: target, mcpPath };
4849
4931
  }
4850
4932
  };
4851
4933
  var SUPPORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => a.localeCompare(b));
4934
+ var PLATFORM_MARKERS = {
4935
+ cursor: [".cursor"],
4936
+ windsurf: [".windsurf"],
4937
+ cline: [".clinerules"],
4938
+ agents: ["AGENTS.md"],
4939
+ gemini: ["GEMINI.md", ".gemini"]
4940
+ };
4941
+ async function detectPlatforms(cwd = process.cwd()) {
4942
+ const platforms = ["claude"];
4943
+ for (const [platform, markers] of Object.entries(PLATFORM_MARKERS)) {
4944
+ for (const marker of markers) {
4945
+ try {
4946
+ await fs22.access(path15.join(cwd, marker));
4947
+ platforms.push(platform);
4948
+ break;
4949
+ } catch {
4950
+ }
4951
+ }
4952
+ }
4953
+ return platforms;
4954
+ }
4852
4955
  async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
4853
4956
  const sourcePath = path15.join(packageRoot(), "src", "skill", "SKILL.md");
4854
4957
  const content = await fs22.readFile(sourcePath, "utf-8");
@@ -4864,7 +4967,8 @@ async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
4864
4967
  results.push(await installer(cwd, content));
4865
4968
  }
4866
4969
  for (const result of results) {
4867
- console.log(`Installed graphify for ${result.host} -> ${result.path}`);
4970
+ const mcpNote = result.mcpPath ? ` (MCP server registered in ${result.mcpPath})` : "";
4971
+ console.log(`Installed graphify for ${result.host} -> ${result.path}${mcpNote}`);
4868
4972
  }
4869
4973
  return results;
4870
4974
  }
@@ -4980,7 +5084,7 @@ async function watchAndRebuild(root, runOnce) {
4980
5084
  });
4981
5085
  }
4982
5086
  var program = new import_commander.Command();
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) => {
5087
+ 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("--no-install", "skip auto-installing the agent skill + MCP config after the build").option("--mysql <dsn>", "also extract a MySQL schema (mysql://user:pass@host:port/db) into the graph").action(async (targetArg, options) => {
4984
5088
  try {
4985
5089
  if (options.mcp) {
4986
5090
  const outDir = path17.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
@@ -4989,7 +5093,8 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
4989
5093
  return;
4990
5094
  }
4991
5095
  let root = targetArg;
4992
- if (looksLikeGitUrl(targetArg)) {
5096
+ const cloned = looksLikeGitUrl(targetArg);
5097
+ if (cloned) {
4993
5098
  console.error(`Cloning ${targetArg} ...`);
4994
5099
  root = await cloneRepo(targetArg);
4995
5100
  }
@@ -5015,6 +5120,13 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
5015
5120
  onProgress: (m) => console.error(m)
5016
5121
  });
5017
5122
  await runOnce();
5123
+ if (options.install && !cloned) {
5124
+ try {
5125
+ await runInstall(await detectPlatforms(root), root);
5126
+ } catch (error) {
5127
+ console.error(`graphify: agent skill/MCP install skipped: ${error.message}`);
5128
+ }
5129
+ }
5018
5130
  if (options.watch) {
5019
5131
  console.error(`Watching ${root} for changes (Ctrl+C to stop) ...`);
5020
5132
  await watchAndRebuild(root, runOnce);
@@ -5096,7 +5208,7 @@ program.command("benchmark [questions...]").description("measure token savings o
5096
5208
  process.exitCode = 1;
5097
5209
  }
5098
5210
  });
5099
- program.command("install").description("install the skill/rules files into local agents (claude, cursor, windsurf, cline, agents, gemini, all)").option("--platform <names...>", "platform(s) to install for", ["claude"]).action(async (options) => {
5211
+ program.command("install").description("install the skill/rules files + project MCP config into local agents (claude, cursor, windsurf, cline, agents, gemini, all)").option("--platform <names...>", "platform(s) to install for", ["claude"]).action(async (options) => {
5100
5212
  try {
5101
5213
  await runInstall(options.platform);
5102
5214
  } catch (error) {