@dreamtree-org/graphify 1.1.3 → 1.2.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/README.md CHANGED
@@ -31,6 +31,14 @@ npm install -g @dreamtree-org/graphify
31
31
  npx @dreamtree-org/graphify .
32
32
  ```
33
33
 
34
+ A default run does more than build the graph — it also makes it instantly usable by the AI
35
+ agents your project already has: it installs the graphify skill/rules files and registers the
36
+ project-scoped MCP server. Claude Code is always set up (`.claude/skills/graphify/SKILL.md` +
37
+ `.mcp.json`); Cursor, Windsurf, Cline, AGENTS.md hosts, and Gemini CLI are set up when their
38
+ config dir/file is detected in the project. All writes are idempotent and never touch your
39
+ existing config (`.mcp.json` entries for other servers, user content in `AGENTS.md`, ...).
40
+ Opt out with `--no-install`.
41
+
34
42
  ## Build the graph
35
43
 
36
44
  ```bash
@@ -43,6 +51,7 @@ graphify . --leiden # Leiden instead of Louvain community detection
43
51
  graphify . --watch # rebuild on file change
44
52
  graphify . --mysql <dsn> # also extract a MySQL schema (mysql://user:pass@host:port/db) into the same graph
45
53
  graphify . --mcp # start the MCP stdio server instead of running the pipeline
54
+ graphify . --no-install # skip the automatic agent skill + MCP config install
46
55
  ```
47
56
 
48
57
  Outputs land in `graphify-out/`: `graph.json` (GraphRAG-ready), `graph.html` (interactive,
@@ -79,8 +88,10 @@ graphify save-result --question Q --answer A --nodes id... --outcome useful|dead
79
88
  graphify reflect # aggregate results into graphify-out/reflections/LESSONS.md
80
89
  # (recency-weighted; agents read it at session start)
81
90
  graphify hook install # auto-rebuild (incremental) after every commit and pull
82
- graphify install --platform all # install the agent skill/rules: claude, cursor, windsurf, cline,
83
- # agents (AGENTS.md: Codex/opencode/Amp), gemini
91
+ graphify install --platform all # install the agent skill/rules + project MCP config: claude,
92
+ # cursor, windsurf, cline, agents (AGENTS.md: Codex/opencode/Amp),
93
+ # gemini — a plain `graphify .` run already does this for
94
+ # detected hosts
84
95
  ```
85
96
 
86
97
  With hooks installed the MCP server always serves a fresh graph — it re-reads `graph.json`
@@ -102,6 +113,11 @@ so repos that use the same packages are automatically bridged in the merged grap
102
113
  `graphify --mcp` (or the `graphify-mcp` binary) exposes `context`, `query`, `path`,
103
114
  `explain`, `affected`, and `tests` as MCP tools over stdio. No network listener.
104
115
 
116
+ You normally never start it by hand: the default pipeline run registers it in the project's
117
+ MCP config (`.mcp.json` for Claude Code, `.cursor/mcp.json` for Cursor, `.gemini/settings.json`
118
+ for Gemini CLI) as `npx -y @dreamtree-org/graphify --mcp`, so the host launches it on demand —
119
+ and teammates who pull the repo get it with zero setup.
120
+
105
121
  ## Guardrails file
106
122
 
107
123
  `graphify.rules.json` at the repo root (this repo dogfoods its own):
@@ -4778,6 +4778,29 @@ function packageRoot() {
4778
4778
  const packageJsonPath = require4.resolve("@dreamtree-org/graphify/package.json");
4779
4779
  return path15.dirname(packageJsonPath);
4780
4780
  }
4781
+ var MCP_SERVER_ENTRY = {
4782
+ command: "npx",
4783
+ args: ["-y", "@dreamtree-org/graphify", "--mcp"]
4784
+ };
4785
+ async function upsertMcpServer(filePath, serversKey = "mcpServers") {
4786
+ let config = {};
4787
+ try {
4788
+ config = JSON.parse(await fs22.readFile(filePath, "utf-8"));
4789
+ } catch (error) {
4790
+ if (error.code !== "ENOENT") {
4791
+ throw new Error(`cannot update ${filePath} \u2014 it exists but is not valid JSON; fix or remove it and re-run.`, {
4792
+ cause: error
4793
+ });
4794
+ }
4795
+ }
4796
+ const servers = config[serversKey] ?? {};
4797
+ servers["graphify"] = MCP_SERVER_ENTRY;
4798
+ config[serversKey] = servers;
4799
+ await fs22.mkdir(path15.dirname(filePath), { recursive: true });
4800
+ await fs22.writeFile(filePath, `${JSON.stringify(config, null, 2)}
4801
+ `, "utf-8");
4802
+ return filePath;
4803
+ }
4781
4804
  var MD_MARKER_BEGIN = "<!-- >>> graphify >>> -->";
4782
4805
  var MD_MARKER_END = "<!-- <<< graphify <<< -->";
4783
4806
  async function writeOwnedFile(filePath, content) {
@@ -4812,7 +4835,8 @@ var PLATFORMS = {
4812
4835
  claude: async (cwd, content) => {
4813
4836
  const target = path15.join(cwd, ".claude", "skills", "graphify", "SKILL.md");
4814
4837
  await writeOwnedFile(target, content);
4815
- return { host: "Claude Code", path: target };
4838
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".mcp.json"));
4839
+ return { host: "Claude Code", path: target, mcpPath };
4816
4840
  },
4817
4841
  cursor: async (cwd, content) => {
4818
4842
  const target = path15.join(cwd, ".cursor", "rules", "graphify.mdc");
@@ -4825,7 +4849,8 @@ alwaysApply: false
4825
4849
  ---
4826
4850
  ${body}`
4827
4851
  );
4828
- return { host: "Cursor", path: target };
4852
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".cursor", "mcp.json"));
4853
+ return { host: "Cursor", path: target, mcpPath };
4829
4854
  },
4830
4855
  windsurf: async (cwd, content) => {
4831
4856
  const target = path15.join(cwd, ".windsurf", "rules", "graphify.md");
@@ -4845,10 +4870,32 @@ ${body}`
4845
4870
  gemini: async (cwd, content) => {
4846
4871
  const target = path15.join(cwd, "GEMINI.md");
4847
4872
  await upsertMarkedBlock(target, content.replace(/^---[\s\S]*?---\n/, ""));
4848
- return { host: "Gemini CLI", path: target };
4873
+ const mcpPath = await upsertMcpServer(path15.join(cwd, ".gemini", "settings.json"));
4874
+ return { host: "Gemini CLI", path: target, mcpPath };
4849
4875
  }
4850
4876
  };
4851
4877
  var SUPPORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => a.localeCompare(b));
4878
+ var PLATFORM_MARKERS = {
4879
+ cursor: [".cursor"],
4880
+ windsurf: [".windsurf"],
4881
+ cline: [".clinerules"],
4882
+ agents: ["AGENTS.md"],
4883
+ gemini: ["GEMINI.md", ".gemini"]
4884
+ };
4885
+ async function detectPlatforms(cwd = process.cwd()) {
4886
+ const platforms = ["claude"];
4887
+ for (const [platform, markers] of Object.entries(PLATFORM_MARKERS)) {
4888
+ for (const marker of markers) {
4889
+ try {
4890
+ await fs22.access(path15.join(cwd, marker));
4891
+ platforms.push(platform);
4892
+ break;
4893
+ } catch {
4894
+ }
4895
+ }
4896
+ }
4897
+ return platforms;
4898
+ }
4852
4899
  async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
4853
4900
  const sourcePath = path15.join(packageRoot(), "src", "skill", "SKILL.md");
4854
4901
  const content = await fs22.readFile(sourcePath, "utf-8");
@@ -4864,7 +4911,8 @@ async function runInstall(platforms = ["claude"], cwd = process.cwd()) {
4864
4911
  results.push(await installer(cwd, content));
4865
4912
  }
4866
4913
  for (const result of results) {
4867
- console.log(`Installed graphify for ${result.host} -> ${result.path}`);
4914
+ const mcpNote = result.mcpPath ? ` (MCP server registered in ${result.mcpPath})` : "";
4915
+ console.log(`Installed graphify for ${result.host} -> ${result.path}${mcpNote}`);
4868
4916
  }
4869
4917
  return results;
4870
4918
  }
@@ -4980,7 +5028,7 @@ async function watchAndRebuild(root, runOnce) {
4980
5028
  });
4981
5029
  }
4982
5030
  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) => {
5031
+ 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
5032
  try {
4985
5033
  if (options.mcp) {
4986
5034
  const outDir = path17.resolve(targetArg === "." ? process.cwd() : targetArg, "graphify-out");
@@ -4989,7 +5037,8 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
4989
5037
  return;
4990
5038
  }
4991
5039
  let root = targetArg;
4992
- if (looksLikeGitUrl(targetArg)) {
5040
+ const cloned = looksLikeGitUrl(targetArg);
5041
+ if (cloned) {
4993
5042
  console.error(`Cloning ${targetArg} ...`);
4994
5043
  root = await cloneRepo(targetArg);
4995
5044
  }
@@ -5015,6 +5064,13 @@ program.name("graphify").description("Turn a folder of code/docs/papers into a q
5015
5064
  onProgress: (m) => console.error(m)
5016
5065
  });
5017
5066
  await runOnce();
5067
+ if (options.install && !cloned) {
5068
+ try {
5069
+ await runInstall(await detectPlatforms(root), root);
5070
+ } catch (error) {
5071
+ console.error(`graphify: agent skill/MCP install skipped: ${error.message}`);
5072
+ }
5073
+ }
5018
5074
  if (options.watch) {
5019
5075
  console.error(`Watching ${root} for changes (Ctrl+C to stop) ...`);
5020
5076
  await watchAndRebuild(root, runOnce);
@@ -5096,7 +5152,7 @@ program.command("benchmark [questions...]").description("measure token savings o
5096
5152
  process.exitCode = 1;
5097
5153
  }
5098
5154
  });
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) => {
5155
+ 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
5156
  try {
5101
5157
  await runInstall(options.platform);
5102
5158
  } catch (error) {