mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-19 20:15:42 +00:00
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:
co-authored by
Claude Sonnet 4.6
parent
9fd6810e4e
commit
c657eb2049
@@ -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,7 +8,7 @@
|
||||
[](https://github.com/sponsors/safishamsi)
|
||||
[](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
@@ -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
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user