Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)

## Unreleased

- Feat: IBM Bob support (#1725). `graphify install --platform bob` installs the skill to `~/.bob/skills/graphify/` (Bob's global skills dir); `graphify bob install` writes the project-scoped skill to `.bob/skills/graphify/` plus an always-on rules file at `.bob/rules/graphify.md` (Bob loads every file under `.bob/rules/` as always-on context; it has no PreToolUse-hook equivalent, so the rules file is the always-on mechanism). Rides kiro's host-neutral skill bundle; `graphify bob uninstall` and the all-platform `graphify uninstall` remove both files.

## 0.9.14 (2026-07-12)

- Fix: Visual Studio *solution folder* nodes no longer embed the absolute scan path (including the local username) in their `id` and `source_file` (#1789, thanks @fremat79). A solution folder is a virtual grouping, not a file — VS writes its name as both the display name and the "path" — but `extract_sln` resolved it to an absolute filesystem path anyway and keyed the node id off that. The CLI's id-relativization pass only remaps ids of real files in the scan set, so a virtual folder never matched and its absolute id survived into a committed `graph.json` (e.g. `id=/Users/<name>/proj/Plugins` instead of `id=plugins`). Solution folders are now detected (name == path) and keyed off the folder name only; real project files still resolve as before. (The earlier fix covered `.csproj`/`.sln` file nodes but missed the virtual folders — this completes it.)
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ for example `graphify claude install --project` or `graphify codex install --pro
| Amp | `graphify amp install` |
| Agent Skills (cross-framework) | `graphify install --platform agents` (alias `--platform skills`) |
| Kiro IDE/CLI | `graphify kiro install` |
| IBM Bob | `graphify install --platform bob` |
| Pi coding agent | `graphify install --platform pi` |
| Cursor | `graphify cursor install` |
| Devin CLI | `graphify devin install` |
Expand Down Expand Up @@ -290,6 +291,7 @@ Run this once in your project after building a graph:
| Amp | `graphify amp install` |
| Agent Skills (cross-framework) | `graphify agents install` (alias `graphify skills install`) |
| Kiro IDE/CLI | `graphify kiro install` |
| IBM Bob | `graphify bob install` |
| Pi coding agent | `graphify pi install` |
| Devin CLI | `graphify devin install` |
| Google Antigravity | `graphify antigravity install` |
Expand Down Expand Up @@ -686,6 +688,8 @@ graphify agents install # ~/.agents/skills/ + AGENTS.md (cross-framew
graphify agents uninstall
graphify kiro install # .kiro/skills/ + .kiro/steering/graphify.md (Kiro IDE/CLI)
graphify kiro uninstall
graphify bob install # .bob/skills/ + .bob/rules/graphify.md (IBM Bob)
graphify bob uninstall
graphify pi install # skill file (Pi coding agent)
graphify pi uninstall
graphify devin install # skill file + .windsurf/rules/graphify.md (Devin CLI)
Expand Down
8 changes: 7 additions & 1 deletion graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
_antigravity_finalize,
_antigravity_install,
_antigravity_uninstall,
_bob_install,
_bob_uninstall,
_canonical_platform,
_claude_pretooluse_hooks,
_copy_skill_file,
Expand Down Expand Up @@ -107,6 +109,8 @@
_ANTIGRAVITY_WORKFLOW,
_CURSOR_RULE_PATH,
_CURSOR_RULE,
_BOB_RULES_PATH,
_BOB_RULES,
_DEVIN_RULES_PATH,
_DEVIN_RULES,
_KILO_PLUGIN_JS,
Expand Down Expand Up @@ -507,7 +511,7 @@ def _run_cli() -> None:
print("Usage: graphify <command>")
print()
print("Commands:")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codebuddy|codex|opencode|aider|amp|agents|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|agents|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|bob|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")
Expand Down Expand Up @@ -681,6 +685,8 @@ def _run_cli() -> None:
" kiro install write skill to .kiro/skills/graphify/ + steering file (Kiro IDE/CLI)"
)
print(" kiro uninstall remove skill + steering file")
print(" bob install write skill to .bob/skills/graphify/ + rules file (IBM Bob)")
print(" bob uninstall remove skill + rules file")
print(" pi install write skill to ~/.pi/agent/skills/graphify/ (Pi coding agent)")
print(" pi uninstall remove skill from ~/.pi/agent/skills/graphify/")
print(" devin install write skill to ~/.config/devin/skills/graphify/ (Devin CLI)")
Expand Down
79 changes: 79 additions & 0 deletions graphify/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,15 @@ def _skill_registration(skill_path: str = "~/.claude/skills/graphify/SKILL.md")
"claude_md": False,
"skill_refs": "kiro",
},
"bob": {
# IBM Bob. Reuses kiro's split bundle (host-neutral body + references).
# Bob reads .bob/skills/<name>/SKILL.md (project) and ~/.bob/skills/
# (global), which the generic destination fallback already produces.
"skill_file": "skill-kiro.md",
"skill_dst": Path(".bob") / "skills" / "graphify" / "SKILL.md",
"claude_md": False,
"skill_refs": "kiro",
},
"pi": {
"skill_file": "skill-pi.md",
"skill_dst": Path(".pi") / "agent" / "skills" / "graphify" / "SKILL.md",
Expand Down Expand Up @@ -867,6 +876,60 @@ def _kiro_uninstall(project_dir: Path) -> None:
steering_dst.unlink()
removed.append(str(steering_dst.relative_to(project_dir)))

print("Removed: " + (", ".join(removed) if removed else "nothing to remove"))
# IBM Bob — .bob/skills/graphify/SKILL.md (skill) + .bob/rules/graphify.md
# (always-on rules). Bob loads every file under .bob/rules/ alphabetically as
# always-on context and auto-triggers skills by frontmatter description; it has
# no PreToolUse-hook equivalent, so the rules file is the always-on mechanism.
_BOB_RULES_PATH = Path(".bob") / "rules" / "graphify.md"
_BOB_RULES = """\
## graphify

This project has a graphify knowledge graph at graphify-out/.

Rules:
- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query "<question>"` (or `graphify path "<A>" "<B>"` / `graphify explain "<concept>"`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context
- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost)
"""
def _bob_install(project_dir: Path) -> None:
"""Write graphify skill + always-on rules file for IBM Bob."""
project_dir = project_dir or Path(".")

# Skill file + references/ sidecar + .graphify_version stamp via the shared
# progressive-disclosure helper.
_copy_skill_file("bob", project=True, project_dir=project_dir)

# Rules file → .bob/rules/graphify.md (always-on)
rules_dst = project_dir / _BOB_RULES_PATH
rules_dst.parent.mkdir(parents=True, exist_ok=True)
if rules_dst.exists() and rules_dst.read_text(encoding="utf-8") == _BOB_RULES:
print(" .bob/rules/graphify.md -> already configured (no change)")
else:
# File is wholly graphify-owned. Overwrite on upgrade so older wording
# does not silently linger.
action = "updated" if rules_dst.exists() else "written"
rules_dst.write_text(_BOB_RULES, encoding="utf-8")
print(f" .bob/rules/graphify.md -> always-on rules {action}")

print()
print("Bob will now read the knowledge graph before every conversation.")
print("Use /graphify to build or update the graph.")
def _bob_uninstall(project_dir: Path) -> None:
"""Remove graphify skill + rules file for IBM Bob."""
project_dir = project_dir or Path(".")
removed = []

skill_dst = _platform_skill_destination("bob", project=True, project_dir=project_dir)
if _remove_skill_file("bob", project=True, project_dir=project_dir):
removed.append(str(skill_dst.relative_to(project_dir)))

rules_dst = project_dir / _BOB_RULES_PATH
if rules_dst.exists():
rules_dst.unlink()
removed.append(str(rules_dst.relative_to(project_dir)))

print("Removed: " + (", ".join(removed) if removed else "nothing to remove"))
def _antigravity_finalize(skill_dst: Path, project_dir: Path) -> None:
"""Write Antigravity's always-on layer next to an installed skill.
Expand Down Expand Up @@ -1440,6 +1503,9 @@ def _project_install(platform_name: str, project_dir: Path | None = None) -> Non
elif platform_name == "kiro":
_kiro_install(project_dir)
_print_project_git_add_hint([project_dir / ".kiro"])
elif platform_name == "bob":
_bob_install(project_dir)
_print_project_git_add_hint([project_dir / ".bob"])
elif platform_name in ("aider", "amp", "codex", "opencode", "claw", "droid", "trae", "trae-cn", "hermes"):
skill_dst = _copy_skill_file(platform_name, project=True, project_dir=project_dir)
_agents_install(project_dir, platform_name)
Expand Down Expand Up @@ -1481,6 +1547,8 @@ def _project_uninstall(platform_name: str, project_dir: Path | None = None) -> N
_cursor_uninstall(project_dir)
elif platform_name == "kiro":
_kiro_uninstall(project_dir)
elif platform_name == "bob":
_bob_uninstall(project_dir)
elif platform_name in ("aider", "amp", "codex", "opencode", "claw", "droid", "trae", "trae-cn", "hermes"):
_remove_skill_file(platform_name, project=True, project_dir=project_dir)
_agents_uninstall(project_dir, platform=platform_name)
Expand Down Expand Up @@ -1668,6 +1736,7 @@ def uninstall_all(project_dir: Path | None = None, purge: bool = False) -> None:
vscode_uninstall(pd)
_cursor_uninstall(pd)
_kiro_uninstall(pd)
_bob_uninstall(pd)
_antigravity_uninstall(pd)
# AGENTS.md covers: codex, aider, opencode, claw, droid, trae, trae-cn, hermes, copilot
_agents_uninstall(pd)
Expand Down Expand Up @@ -1858,6 +1927,7 @@ def codebuddy_uninstall(project_dir: Path | None = None, *, project: bool = Fals
"aider",
"amp",
"antigravity",
"bob",
"claude",
"claw",
"codebuddy",
Expand Down Expand Up @@ -2052,6 +2122,15 @@ def dispatch_install_cli(cmd: str) -> bool:
else:
print("Usage: graphify kiro [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd == "bob":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
_bob_install(Path("."))
elif subcmd == "uninstall":
_bob_uninstall(Path("."))
else:
print("Usage: graphify bob [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd == "devin":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
Expand Down
Loading