v0.4.12: add Kiro IDE/CLI support, fix cache portability across machines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-04-13 22:46:13 +01:00
co-authored by Claude Sonnet 4.6
parent 9fd6810e4e
commit c657eb2049
7 changed files with 1292 additions and 11 deletions
+5
View File
@@ -2,6 +2,11 @@
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
## 0.4.12 (2026-04-13)
- Add: Kiro IDE/CLI support — `graphify kiro install` writes `.kiro/skills/graphify/SKILL.md` (invoked via `/graphify`) and `.kiro/steering/graphify.md` (`inclusion: always` — always-on context before every conversation) (#319, #321)
- Fix: cache `file_hash()` now uses the path relative to project root instead of the resolved absolute path — cache entries are now portable across machines, CI runners, and different checkout directories (#311)
## 0.4.11 (2026-04-13)
- Fix: `graphify query` no longer crashes with `ValueError` on MultiGraph graphs — `G.edges[u, v]` replaced with `G[u][v]` + MultiGraph guard (#305)
+8 -2
View File
@@ -8,7 +8,7 @@
[![Sponsor](https://img.shields.io/badge/sponsor-safishamsi-ea4aaa?logo=github-sponsors)](https://github.com/sponsors/safishamsi)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-Safi%20Shamsi-0077B5?logo=linkedin)](https://www.linkedin.com/in/safi-shamsi)
**An AI coding assistant skill.** Type `/graphify` in Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, or Google Antigravity - it reads your files, builds a knowledge graph, and gives you back structure you didn't know was there. Understand a codebase faster. Find the "why" behind architectural decisions.
**An AI coding assistant skill.** Type `/graphify` in Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, or Google Antigravity - it reads your files, builds a knowledge graph, and gives you back structure you didn't know was there. Understand a codebase faster. Find the "why" behind architectural decisions.
Fully multimodal. Drop in code, PDFs, markdown, screenshots, diagrams, whiteboard photos, images in other languages, or video and audio files - graphify extracts concepts and relationships from all of it and connects them into one graph. Videos are transcribed with Whisper using a domain-aware prompt derived from your corpus. 23 languages supported via tree-sitter AST (Python, JS, TS, Go, Rust, Java, C, C++, Ruby, C#, Kotlin, Scala, PHP, Swift, Lua, Zig, PowerShell, Elixir, Objective-C, Julia, Vue, Svelte, Dart).
@@ -48,7 +48,7 @@ Every relationship is tagged `EXTRACTED` (found directly in source), `INFERRED`
## Install
**Requires:** Python 3.10+ and one of: [Claude Code](https://claude.ai/code), [Codex](https://openai.com/codex), [OpenCode](https://opencode.ai), [Cursor](https://cursor.com), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli), [Aider](https://aider.chat), [OpenClaw](https://openclaw.ai), [Factory Droid](https://factory.ai), [Trae](https://trae.ai), Hermes, or [Google Antigravity](https://antigravity.google)
**Requires:** Python 3.10+ and one of: [Claude Code](https://claude.ai/code), [Codex](https://openai.com/codex), [OpenCode](https://opencode.ai), [Cursor](https://cursor.com), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli), [Aider](https://aider.chat), [OpenClaw](https://openclaw.ai), [Factory Droid](https://factory.ai), [Trae](https://trae.ai), [Kiro](https://kiro.dev), Hermes, or [Google Antigravity](https://antigravity.google)
```bash
pip install graphifyy && graphify install
@@ -72,6 +72,7 @@ pip install graphifyy && graphify install
| Trae CN | `graphify install --platform trae-cn` |
| Gemini CLI | `graphify install --platform gemini` |
| Hermes | `graphify install --platform hermes` |
| Kiro IDE/CLI | `graphify kiro install` |
| Cursor | `graphify cursor install` |
| Google Antigravity | `graphify antigravity install` |
@@ -103,6 +104,7 @@ After building a graph, run this once in your project:
| Cursor | `graphify cursor install` |
| Gemini CLI | `graphify gemini install` |
| Hermes | `graphify hermes install` |
| Kiro IDE/CLI | `graphify kiro install` |
| Google Antigravity | `graphify antigravity install` |
**Claude Code** does two things: writes a `CLAUDE.md` section telling Claude to read `graphify-out/GRAPH_REPORT.md` before answering architecture questions, and installs a **PreToolUse hook** (`settings.json`) that fires before every Glob and Grep call. If a knowledge graph exists, Claude sees: _"graphify: Knowledge graph exists. Read GRAPH_REPORT.md for god nodes and community structure before searching raw files."_ — so Claude navigates via the graph instead of grepping through every file.
@@ -117,6 +119,8 @@ After building a graph, run this once in your project:
**Aider, OpenClaw, Factory Droid, Trae, and Hermes** write the same rules to `AGENTS.md` in your project root and copy the skill to the platform's global skill directory. These platforms don't support tool hooks, so AGENTS.md is the always-on mechanism.
**Kiro IDE/CLI** writes the skill to `.kiro/skills/graphify/SKILL.md` (invoked via `/graphify`) and a steering file to `.kiro/steering/graphify.md` with `inclusion: always` — Kiro injects this into every conversation automatically, no hook needed.
**Google Antigravity** writes `.agent/rules/graphify.md` (always-on rules) and `.agent/workflows/graphify.md` (registers `/graphify` as a slash command). No hook equivalent exists in Antigravity — rules are the always-on mechanism.
**GitHub Copilot CLI** copies the skill to `~/.copilot/skills/graphify/SKILL.md`. Run `graphify copilot install` to set it up.
@@ -253,6 +257,8 @@ graphify trae-cn install # AGENTS.md (Trae CN)
graphify trae-cn uninstall
graphify hermes install # AGENTS.md + ~/.hermes/skills/ (Hermes)
graphify hermes uninstall
graphify kiro install # .kiro/skills/ + .kiro/steering/graphify.md (Kiro IDE/CLI)
graphify kiro uninstall
graphify antigravity install # .agent/rules + .agent/workflows (Google Antigravity)
graphify antigravity uninstall
+80 -1
View File
@@ -97,6 +97,11 @@ _PLATFORM_CONFIG: dict[str, dict] = {
"skill_dst": Path(".hermes") / "skills" / "graphify" / "SKILL.md",
"claude_md": False,
},
"kiro": {
"skill_file": "skill-kiro.md",
"skill_dst": Path(".kiro") / "skills" / "graphify" / "SKILL.md",
"claude_md": False,
},
"antigravity": {
"skill_file": "skill.md",
"skill_dst": Path(".agent") / "skills" / "graphify" / "SKILL.md",
@@ -335,6 +340,69 @@ If no path argument is given, use `.` (current directory).
"""
_KIRO_STEERING = """\
---
inclusion: always
---
graphify: A knowledge graph of this project lives in `graphify-out/`. \
If `graphify-out/GRAPH_REPORT.md` exists, read it before answering architecture questions, \
tracing dependencies, or searching files — it contains god nodes, community structure, \
and surprising connections the graph found. Navigate by graph structure instead of grepping raw files.
"""
_KIRO_STEERING_MARKER = "graphify: A knowledge graph of this project"
def _kiro_install(project_dir: Path) -> None:
"""Write graphify skill + steering file for Kiro IDE/CLI."""
project_dir = project_dir or Path(".")
# Skill file → .kiro/skills/graphify/SKILL.md
skill_src = Path(__file__).parent / "skill-kiro.md"
skill_dst = project_dir / ".kiro" / "skills" / "graphify" / "SKILL.md"
skill_dst.parent.mkdir(parents=True, exist_ok=True)
skill_dst.write_text(skill_src.read_text(encoding="utf-8"), encoding="utf-8")
print(f" {skill_dst.relative_to(project_dir)} -> /graphify skill")
# Steering file → .kiro/steering/graphify.md (always-on)
steering_dir = project_dir / ".kiro" / "steering"
steering_dir.mkdir(parents=True, exist_ok=True)
steering_dst = steering_dir / "graphify.md"
if steering_dst.exists() and _KIRO_STEERING_MARKER in steering_dst.read_text(encoding="utf-8"):
print(f" .kiro/steering/graphify.md -> already configured")
else:
steering_dst.write_text(_KIRO_STEERING, encoding="utf-8")
print(f" .kiro/steering/graphify.md -> always-on steering written")
print()
print("Kiro will now read the knowledge graph before every conversation.")
print("Use /graphify to build or update the graph.")
def _kiro_uninstall(project_dir: Path) -> None:
"""Remove graphify skill + steering file for Kiro."""
project_dir = project_dir or Path(".")
removed = []
skill_dst = project_dir / ".kiro" / "skills" / "graphify" / "SKILL.md"
if skill_dst.exists():
skill_dst.unlink()
removed.append(str(skill_dst.relative_to(project_dir)))
# Remove parent dir if empty
try:
skill_dst.parent.rmdir()
except OSError:
pass
steering_dst = project_dir / ".kiro" / "steering" / "graphify.md"
if steering_dst.exists():
steering_dst.unlink()
removed.append(str(steering_dst.relative_to(project_dir)))
print("Removed: " + (", ".join(removed) if removed else "nothing to remove"))
def _antigravity_install(project_dir: Path) -> None:
"""Install graphify for Google Antigravity: skill + .agent/rules + .agent/workflows."""
# 1. Copy skill file to ~/.agent/skills/graphify/SKILL.md
@@ -750,7 +818,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|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes)")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codex|opencode|aider|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro)")
print(" path \"A\" \"B\" shortest path between two nodes in graph.json")
print(" --graph <path> path to graph.json (default graphify-out/graph.json)")
print(" explain \"X\" plain-language explanation of a node and its neighbors")
@@ -802,6 +870,8 @@ def main() -> None:
print(" antigravity uninstall remove .agent/rules, .agent/workflows, and skill")
print(" hermes install write skill to ~/.hermes/skills/graphify/ (Hermes)")
print(" hermes uninstall remove skill from ~/.hermes/skills/graphify/")
print(" kiro install write skill to .kiro/skills/graphify/ + steering file (Kiro IDE/CLI)")
print(" kiro uninstall remove skill + steering file")
print()
return
@@ -871,6 +941,15 @@ def main() -> None:
else:
print("Usage: graphify copilot [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd == "kiro":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
_kiro_install(Path("."))
elif subcmd == "uninstall":
_kiro_uninstall(Path("."))
else:
print("Usage: graphify kiro [install|uninstall]", file=sys.stderr)
sys.exit(1)
elif cmd in ("aider", "codex", "opencode", "claw", "droid", "trae", "trae-cn", "hermes"):
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
if subcmd == "install":
+13 -5
View File
@@ -17,8 +17,12 @@ def _body_content(content: bytes) -> bytes:
return content
def file_hash(path: Path) -> str:
"""SHA256 of file contents + resolved path. Prevents cache collisions on identical content.
def file_hash(path: Path, root: Path = Path(".")) -> str:
"""SHA256 of file contents + path relative to root.
Using a relative path (not absolute) makes cache entries portable across
machines and checkout directories, so shared caches and CI work correctly.
Falls back to the resolved absolute path if the file is outside root.
For Markdown files (.md), only the body below the YAML frontmatter is hashed,
so metadata-only changes (e.g. reviewed, status, tags) do not invalidate the cache.
@@ -29,7 +33,11 @@ def file_hash(path: Path) -> str:
h = hashlib.sha256()
h.update(content)
h.update(b"\x00")
h.update(str(p.resolve()).encode())
try:
rel = p.resolve().relative_to(Path(root).resolve())
h.update(str(rel).encode())
except ValueError:
h.update(str(p.resolve()).encode())
return h.hexdigest()
@@ -48,7 +56,7 @@ def load_cached(path: Path, root: Path = Path(".")) -> dict | None:
Returns None if no cache entry or file has changed.
"""
try:
h = file_hash(path)
h = file_hash(path, root)
except OSError:
return None
entry = cache_dir(root) / f"{h}.json"
@@ -66,7 +74,7 @@ def save_cached(path: Path, result: dict, root: Path = Path(".")) -> None:
Stores as graphify-out/cache/{hash}.json where hash = SHA256 of current file contents.
result should be a dict with 'nodes' and 'edges' lists.
"""
h = file_hash(path)
h = file_hash(path, root)
entry = cache_dir(root) / f"{h}.json"
tmp = entry.with_suffix(".tmp")
try:
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "graphifyy"
version = "0.4.11"
version = "0.4.12"
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, OpenClaw, Factory Droid, Trae) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
readme = "README.md"
license = { file = "LICENSE" }
+2 -2
View File
@@ -62,8 +62,8 @@ def test_cached_files(tmp_path, cache_root):
save_cached(f2, {"nodes": [], "edges": []}, root=cache_root)
hashes = cached_files(cache_root)
assert file_hash(f1) in hashes
assert file_hash(f2) in hashes
assert file_hash(f1, cache_root) in hashes
assert file_hash(f2, cache_root) in hashes
def test_clear_cache(tmp_file, cache_root):