@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 +18 -2
- package/dist/cli/index.cjs +63 -7
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +63 -7
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skill/SKILL.md +9 -4
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
|
|
83
|
-
# agents (AGENTS.md: Codex/opencode/Amp),
|
|
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):
|
package/dist/cli/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|