Add CodeBuddy platform support

CodeBuddy uses the same Agent tool and PreToolUse hook mechanism as
Claude Code. Installs skill to ~/.codebuddy/skills/, writes CODEBUDDY.md
section, and registers a Glob|Grep PreToolUse hook in
.codebuddy/settings.json.
This commit is contained in:
DevinZeng
2026-06-05 19:15:09 +08:00
parent 75c4de7759
commit c299f31032
5 changed files with 229 additions and 14 deletions
+11 -2
View File
@@ -126,7 +126,8 @@ for example `graphify claude install --project` or `graphify codex install --pro
| Platform | Install command |
|----------|----------------|
| Claude Code (Linux/Mac) | `graphify install` |
| Claude Code (Windows) | `graphify install --platform windows` |
| Claude Code (Windows) | `graphify install` (auto-detected) or `graphify install --platform windows` |
| CodeBuddy | `graphify install --platform codebuddy` |
| Codex | `graphify install --platform codex` |
| OpenCode | `graphify install --platform opencode` |
| Kilo Code | `graphify install --platform kilo` |
@@ -147,7 +148,8 @@ for example `graphify claude install --project` or `graphify codex install --pro
| Devin CLI | `graphify devin install` |
| Google Antigravity | `graphify antigravity install` |
> Codex users: also add `multi_agent = true` under `[features]` in `~/.codex/config.toml`.
Codex users also need `multi_agent = true` under `[features]` in `~/.codex/config.toml` for parallel extraction. CodeBuddy uses the same Agent tool and PreToolUse hook mechanism as Claude Code. Factory Droid uses the `Task` tool for parallel subagent dispatch. OpenClaw and Aider use sequential extraction (parallel agent support is still early on those platforms). Trae uses the Agent tool for parallel subagent dispatch and does **not** support PreToolUse hooks — AGENTS.md is the always-on mechanism.
> Codex uses `$graphify` instead of `/graphify`.
### Optional extras
@@ -183,6 +185,7 @@ Run this once in your project after building a graph:
| Platform | Command |
|----------|---------|
| Claude Code | `graphify claude install` |
| CodeBuddy | `graphify codebuddy install` |
| Codex | `graphify codex install` |
| OpenCode | `graphify opencode install` |
| Kilo Code | `graphify kilo install` |
@@ -205,6 +208,10 @@ Run this once in your project after building a graph:
This writes a small config file that tells your assistant to consult the knowledge graph for codebase questions — preferring scoped queries like `graphify query "<question>"` over reading the full report or grepping raw files. On platforms that support payload-bearing hooks (Claude Code, Gemini CLI), a hook fires automatically before search-style tool calls (and, on Claude Code, before reading source files one by one via the Read/Glob tools) and nudges your assistant toward the graph path. On the others (Codex, OpenCode, Cursor, etc.), the persistent instruction files (`AGENTS.md`, `.cursor/rules/`, etc.) provide the same query-first guidance. `GRAPH_REPORT.md` is still available for broad architecture review.
**CodeBuddy** does the same two things as Claude Code: writes a `CODEBUDDY.md` section telling CodeBuddy to read `graphify-out/GRAPH_REPORT.md` before answering architecture questions, and installs a **PreToolUse hook** (`.codebuddy/settings.json`) that fires before every Glob and Grep call.
**Codex** writes to `AGENTS.md` and also installs a **PreToolUse hook** in `.codex/hooks.json` that fires before every Bash tool call — same always-on mechanism as Claude Code.
To remove graphify from all platforms at once: `graphify uninstall` (add `--purge` to also delete `graphify-out/`). Or use the per-platform command (e.g. `graphify claude uninstall`).
---
@@ -481,6 +488,8 @@ graphify hook status
# always-on assistant instructions - platform-specific
graphify claude install # CLAUDE.md + PreToolUse hook (Claude Code)
graphify claude uninstall
graphify codebuddy install # CODEBUDDY.md + PreToolUse hook (CodeBuddy)
graphify codebuddy uninstall
graphify codex install # AGENTS.md + PreToolUse hook in .codex/hooks.json (Codex)
graphify opencode install # AGENTS.md + tool.execute.before plugin (OpenCode)
graphify kilo install # native Kilo skill + /graphify command + AGENTS.md + .kilo plugin
+7 -3
View File
@@ -5,7 +5,7 @@
[![CI](https://github.com/safishamsi/graphify/actions/workflows/ci.yml/badge.svg?branch=v3)](https://github.com/safishamsi/graphify/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/graphifyy)](https://pypi.org/project/graphifyy/)
**一个面向 AI 编码助手的技能。** 在 Claude Code、Codex、OpenCode、OpenClaw、Factory Droid 或 Trae 中输入 `/graphify`,它会读取你的文件、构建知识图谱,并把原本不明显的结构关系还给你。更快理解代码库,找到架构决策背后的"为什么"。
**一个面向 AI 编码助手的技能。** 在 Claude Code、CodeBuddy、Codex、OpenCode、OpenClaw、Factory Droid 或 Trae 中输入 `/graphify`,它会读取你的文件、构建知识图谱,并把原本不明显的结构关系还给你。更快理解代码库,找到架构决策背后的"为什么"。
完全多模态。你可以直接丢进去代码、PDF、Markdown、截图、流程图、白板照片,甚至其他语言的图片 —— graphify 会用 Claude vision 从这些内容中提取概念和关系,并把它们连接到同一张图里。
@@ -33,7 +33,7 @@ graphify 分两轮执行。第一轮是确定性的 AST 提取,对代码文件
## 安装
**要求:** Python 3.10+,并且使用以下平台之一:[Claude Code](https://claude.ai/code)、[Codex](https://openai.com/codex)、[OpenCode](https://opencode.ai)、[OpenClaw](https://openclaw.ai)、[Factory Droid](https://factory.ai) 或 [Trae](https://trae.ai)
**要求:** Python 3.10+,并且使用以下平台之一:[Claude Code](https://claude.ai/code)、[CodeBuddy](https://codebuddy.ai)、[Codex](https://openai.com/codex)、[OpenCode](https://opencode.ai)、[OpenClaw](https://openclaw.ai)、[Factory Droid](https://factory.ai) 或 [Trae](https://trae.ai)
```bash
pip install graphifyy && graphify install
@@ -46,6 +46,7 @@ pip install graphifyy && graphify install
| 平台 | 安装命令 |
|------|----------|
| Claude Code | `graphify install` |
| CodeBuddy | `graphify install --platform codebuddy` |
| Codex | `graphify install --platform codex` |
| OpenCode | `graphify install --platform opencode` |
| OpenClaw | `graphify install --platform claw` |
@@ -53,7 +54,7 @@ pip install graphifyy && graphify install
| Trae | `graphify install --platform trae` |
| Trae CN | `graphify install --platform trae-cn` |
Codex 用户还需要在 `~/.codex/config.toml` 的 `[features]` 下打开 `multi_agent = true`,这样才能并行提取。OpenClaw 目前的并行 agent 支持还比较早期,所以使用顺序提取。Trae 使用 Agent 工具进行并行子代理调度,**不支持** PreToolUse hook,因此 AGENTS.md 是其常驻机制。
Codex 用户还需要在 `~/.codex/config.toml` 的 `[features]` 下打开 `multi_agent = true`,这样才能并行提取。CodeBuddy 使用与 Claude Code 相同的 Agent 工具和 PreToolUse hook 机制。OpenClaw 目前的并行 agent 支持还比较早期,所以使用顺序提取。Trae 使用 Agent 工具进行并行子代理调度,**不支持** PreToolUse hook,因此 AGENTS.md 是其常驻机制。
然后打开你的 AI 编码助手,输入:
@@ -68,6 +69,7 @@ Codex 用户还需要在 `~/.codex/config.toml` 的 `[features]` 下打开 `mult
| 平台 | 命令 |
|------|------|
| Claude Code | `graphify claude install` |
| CodeBuddy | `graphify codebuddy install` |
| Codex | `graphify codex install` |
| OpenCode | `graphify opencode install` |
| OpenClaw | `graphify claw install` |
@@ -81,6 +83,8 @@ Codex 用户还需要在 `~/.codex/config.toml` 的 `[features]` 下打开 `mult
如果知识图谱存在,Claude 会先看到:_"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files."_ —— 这样 Claude 会优先按图谱导航,而不是一上来就 grep 整个项目。
**CodeBuddy** 与 Claude Code 相同,也会做两件事:在 `CODEBUDDY.md` 中写入规则,并安装 **PreToolUse hook**(写入 `.codebuddy/settings.json`),在每次 `Glob` 和 `Grep` 前触发。
**Codex、OpenCode、OpenClaw、Factory Droid、Trae** 会把同样的规则写进项目根目录的 `AGENTS.md`。这些平台没有 PreToolUse hook,所以 `AGENTS.md` 是它们的常驻机制。
卸载时使用对应平台的 uninstall 命令即可(例如 `graphify claude uninstall`)。
+142 -7
View File
@@ -487,6 +487,11 @@ _PLATFORM_CONFIG: dict[str, dict] = {
"claude_md": False,
"skill_refs": "pi",
},
"codebuddy": {
"skill_file": "skill.md",
"skill_dst": Path(".codebuddy") / "skills" / "graphify" / "SKILL.md",
"claude_md": False,
},
"antigravity": {
# Rides claude's split bundle (shares skill.md).
"skill_file": "skill.md",
@@ -629,6 +634,22 @@ def install(platform: str = "claude", *, project: bool = False, project_dir: Pat
claude_md.write_text(registration.lstrip(), encoding="utf-8")
print(f" CLAUDE.md -> created at {claude_md}")
if platform == "codebuddy":
# Register in ~/.codebuddy/CODEBUDDY.md (CodeBuddy only)
codebuddy_md = Path.home() / ".codebuddy" / "CODEBUDDY.md"
registration = _skill_registration("~/.codebuddy/skills/graphify/SKILL.md")
if codebuddy_md.exists():
content = codebuddy_md.read_text(encoding="utf-8")
if "graphify" in content:
print(f" CODEBUDDY.md -> already registered (no change)")
else:
codebuddy_md.write_text(content.rstrip() + registration, encoding="utf-8")
print(f" CODEBUDDY.md -> skill registered in {codebuddy_md}")
else:
codebuddy_md.parent.mkdir(parents=True, exist_ok=True)
codebuddy_md.write_text(registration.lstrip(), encoding="utf-8")
print(f" CODEBUDDY.md -> created at {codebuddy_md}")
if platform == "opencode":
_install_opencode_plugin(project_dir if project else Path("."))
@@ -659,6 +680,19 @@ def _print_install_usage() -> None:
_CLAUDE_MD_MARKER = "## graphify"
_CODEBUDDY_MD_SECTION = """\
## graphify
This project has a graphify knowledge graph at graphify-out/.
Rules:
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- After modifying code files in this session, run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
"""
_CODEBUDDY_MD_MARKER = "## graphify"
# AGENTS.md section for Codex, OpenCode, and OpenClaw.
# All three platforms read AGENTS.md in the project root for persistent instructions.
@@ -1856,6 +1890,100 @@ def claude_uninstall(project_dir: Path | None = None, *, project: bool = False)
_uninstall_claude_hook(project_dir or Path("."))
def codebuddy_install(project_dir: Path | None = None) -> None:
"""Write the graphify section to the local CODEBUDDY.md."""
target = (project_dir or Path(".")) / "CODEBUDDY.md"
if target.exists():
content = target.read_text(encoding="utf-8")
if _CODEBUDDY_MD_MARKER in content:
print("graphify already configured in CODEBUDDY.md")
return
new_content = content.rstrip() + "\n\n" + _CODEBUDDY_MD_SECTION
else:
new_content = _CODEBUDDY_MD_SECTION
target.write_text(new_content, encoding="utf-8")
print(f"graphify section written to {target.resolve()}")
# Also write CodeBuddy PreToolUse hook to .codebuddy/settings.json
_install_codebuddy_hook(project_dir or Path("."))
print()
print("CodeBuddy will now check the knowledge graph before answering")
print("codebase questions and rebuild it after code changes.")
def _install_codebuddy_hook(project_dir: Path) -> None:
"""Add graphify PreToolUse hook to .codebuddy/settings.json."""
settings_path = project_dir / ".codebuddy" / "settings.json"
settings_path.parent.mkdir(parents=True, exist_ok=True)
if settings_path.exists():
try:
settings = json.loads(settings_path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
settings = {}
else:
settings = {}
hooks = settings.setdefault("hooks", {})
pre_tool = hooks.setdefault("PreToolUse", [])
hooks["PreToolUse"] = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash", "Read|Glob") and "graphify" in str(h))]
hooks["PreToolUse"].append(_SETTINGS_HOOK)
hooks["PreToolUse"].append(_READ_SETTINGS_HOOK)
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
print(f" .codebuddy/settings.json -> PreToolUse hooks registered")
def _uninstall_codebuddy_hook(project_dir: Path) -> None:
"""Remove graphify PreToolUse hook from .codebuddy/settings.json."""
settings_path = project_dir / ".codebuddy" / "settings.json"
if not settings_path.exists():
return
try:
settings = json.loads(settings_path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
return
pre_tool = settings.get("hooks", {}).get("PreToolUse", [])
filtered = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash", "Read|Glob") and "graphify" in str(h))]
if len(filtered) == len(pre_tool):
return
settings["hooks"]["PreToolUse"] = filtered
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
print(f" .codebuddy/settings.json -> PreToolUse hook removed")
def codebuddy_uninstall(project_dir: Path | None = None) -> None:
"""Remove the graphify section from the local CODEBUDDY.md."""
target = (project_dir or Path(".")) / "CODEBUDDY.md"
if not target.exists():
print("No CODEBUDDY.md found in current directory - nothing to do")
return
content = target.read_text(encoding="utf-8")
if _CODEBUDDY_MD_MARKER not in content:
print("graphify section not found in CODEBUDDY.md - nothing to do")
return
# Remove the ## graphify section: from the marker to the next ## heading or EOF
cleaned = re.sub(
r"\n*## graphify\n.*?(?=\n## |\Z)",
"",
content,
flags=re.DOTALL,
).rstrip()
if cleaned:
target.write_text(cleaned + "\n", encoding="utf-8")
print(f"graphify section removed from {target.resolve()}")
else:
target.unlink()
print(f"CODEBUDDY.md was empty after removal - deleted {target.resolve()}")
_uninstall_codebuddy_hook(project_dir or Path("."))
def _clone_repo(
url: str, branch: str | None = None, out_dir: Path | None = None
) -> Path:
@@ -1942,7 +2070,7 @@ def main() -> None:
print("Usage: graphify <command>")
print()
print("Commands:")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codex|opencode|aider|amp|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|pi|devin)")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codebuddy|codex|opencode|aider|amp|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|pi|devin)")
print(" uninstall remove graphify from all detected platforms in one shot")
print(" --purge also delete graphify-out/ directory")
print(" path \"A\" \"B\" shortest path between two nodes in graph.json")
@@ -2035,12 +2163,10 @@ def main() -> None:
print(" gemini uninstall remove GEMINI.md section + BeforeTool hook")
print(" cursor install write .cursor/rules/graphify.mdc (Cursor)")
print(" cursor uninstall remove .cursor/rules/graphify.mdc")
print(
" claude install write graphify section to CLAUDE.md + PreToolUse hook (Claude Code)"
)
print(
" claude uninstall remove graphify section from CLAUDE.md + PreToolUse hook"
)
print(" claude install write graphify section to CLAUDE.md + PreToolUse hook (Claude Code)")
print(" claude uninstall remove graphify section from CLAUDE.md + PreToolUse hook")
print(" codebuddy install write graphify section to CODEBUDDY.md + PreToolUse hook (CodeBuddy)")
print(" codebuddy uninstall remove graphify section from CODEBUDDY.md + PreToolUse hook")
print(" codex install write graphify section to AGENTS.md (Codex)")
print(" codex uninstall remove graphify section from AGENTS.md")
print(
@@ -2203,6 +2329,15 @@ def main() -> None:
else:
print("Usage: graphify claude [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd == "codebuddy":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
codebuddy_install()
elif subcmd == "uninstall":
codebuddy_uninstall()
else:
print("Usage: graphify codebuddy [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd == "gemini":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
+2 -2
View File
@@ -5,10 +5,10 @@ build-backend = "setuptools.build_meta"
[project]
name = "graphifyy"
version = "0.8.31"
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
description = "AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
readme = "README.md"
license = { file = "LICENSE" }
keywords = ["claude", "claude-code", "codex", "opencode", "kilo", "cursor", "gemini", "aider", "kiro", "pi", "devin", "knowledge-graph", "rag", "graphrag", "obsidian", "community-detection", "tree-sitter", "leiden", "llm"]
keywords = ["claude", "claude-code", "codebuddy", "codex", "opencode", "kilo", "cursor", "gemini", "aider", "kiro", "pi", "devin", "knowledge-graph", "rag", "graphrag", "obsidian", "community-detection", "tree-sitter", "leiden", "llm"]
requires-python = ">=3.10"
dependencies = [
"networkx>=3.4",
+67
View File
@@ -8,6 +8,7 @@ import pytest
PLATFORMS = {
"claude": (".claude/skills/graphify/SKILL.md",),
"codebuddy": (".codebuddy/skills/graphify/SKILL.md",),
"codex": (".agents/skills/graphify/SKILL.md",),
"opencode": (".config/opencode/skills/graphify/SKILL.md",),
"kilo": (
@@ -39,6 +40,11 @@ def test_install_default_claude(tmp_path):
assert (tmp_path / ".claude" / "skills" / "graphify" / "SKILL.md").exists()
def test_install_codebuddy(tmp_path):
_install(tmp_path, "codebuddy")
assert (tmp_path / ".codebuddy" / "skills" / "graphify" / "SKILL.md").exists()
def test_install_codex(tmp_path):
_install(tmp_path, "codex")
assert (tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md").exists()
@@ -334,6 +340,67 @@ def test_codex_install_does_not_write_claude_md(tmp_path):
assert not (tmp_path / ".claude" / "CLAUDE.md").exists()
# --- CodeBuddy CODEBUDDY.md + hook install/uninstall tests ---
def test_codebuddy_install_writes_codebuddy_md(tmp_path):
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
md = tmp_path / "CODEBUDDY.md"
assert md.exists()
assert "graphify-out/GRAPH_REPORT.md" in md.read_text()
def test_codebuddy_install_writes_hook(tmp_path):
import json as _json
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
settings = _json.loads((tmp_path / ".codebuddy" / "settings.json").read_text())
hooks = settings["hooks"]["PreToolUse"]
assert any("graphify" in str(h) for h in hooks)
def test_codebuddy_install_idempotent(tmp_path):
from graphify.__main__ import codebuddy_install
codebuddy_install(tmp_path)
codebuddy_install(tmp_path)
md = tmp_path / "CODEBUDDY.md"
assert md.read_text().count("## graphify") == 1
def test_codebuddy_install_merges_existing_codebuddy_md(tmp_path):
from graphify.__main__ import codebuddy_install
(tmp_path / "CODEBUDDY.md").write_text("# My project rules\n")
codebuddy_install(tmp_path)
content = (tmp_path / "CODEBUDDY.md").read_text()
assert "# My project rules" in content
assert "graphify-out/GRAPH_REPORT.md" in content
def test_codebuddy_uninstall_removes_section(tmp_path):
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
md = tmp_path / "CODEBUDDY.md"
assert not md.exists()
def test_codebuddy_uninstall_removes_hook(tmp_path):
import json as _json
from graphify.__main__ import codebuddy_install, codebuddy_uninstall
codebuddy_install(tmp_path)
codebuddy_uninstall(tmp_path)
settings_path = tmp_path / ".codebuddy" / "settings.json"
if settings_path.exists():
settings = _json.loads(settings_path.read_text())
hooks = settings.get("hooks", {}).get("PreToolUse", [])
assert not any("graphify" in str(h) for h in hooks)
def test_codebuddy_uninstall_noop_if_not_installed(tmp_path):
from graphify.__main__ import codebuddy_uninstall
codebuddy_uninstall(tmp_path) # should not raise
def test_uninstall_project_removes_project_skill_only(tmp_path, monkeypatch):
from graphify.__main__ import main
home = tmp_path / "home"