Skip to content

Commit 7ab62fd

Browse files
safishamsiclaude
andcommitted
v0.4.15: VS Code Copilot Chat, OpenCode/Gemini Windows fixes, .mjs/.ejs, macOS watch, god_nodes degree rename
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9866cbc commit 7ab62fd

15 files changed

Lines changed: 376 additions & 21 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

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

5+
## 0.4.15 (2026-04-15)
6+
7+
- Feat: VS Code Copilot Chat support — `graphify vscode install` installs a Python-only skill (works on Windows PowerShell) and writes `.github/copilot-instructions.md` for always-on graph context (#206)
8+
- Fix: OpenCode plugin path used backslashes on Windows causing duplicate entries in `opencode.json` — now uses forward slashes via `.as_posix()` (#378)
9+
- Fix: Gemini CLI on Windows now installs skill to `~/.agents/skills/` (higher priority) instead of `~/.gemini/skills/` (#368)
10+
- Fix: `.mjs` and `.ejs` files now recognised by the AST extractor as JavaScript (#365, #372)
11+
- Fix: `god_nodes()` field renamed from `edges` to `degree` for clarity — updated in report, wiki, serve, and all tests (#375)
12+
- Fix: macOS `graphify watch` now uses `PollingObserver` by default to avoid missed events with FSEvents (#373)
13+
514
## 0.4.14 (2026-04-15)
615

716
- Fix: cross-file call edges now emitted for all languages (Swift, Go, Rust, Java, C#, Kotlin, Scala, Ruby, PHP, and others) — previously only Python had cross-file resolution; unresolved call sites are now saved per file and resolved against a global label map in a post-pass (#348)

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Sponsor](https://img.shields.io/badge/sponsor-safishamsi-ea4aaa?logo=github-sponsors)](https://github.com/sponsors/safishamsi)
99
[![LinkedIn](https://img.shields.io/badge/LinkedIn-Safi%20Shamsi-0077B5?logo=linkedin)](https://www.linkedin.com/in/safi-shamsi)
1010

11-
**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.
11+
**An AI coding assistant skill.** Type `/graphify` in Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot CLI, VS Code Copilot Chat, 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.
1212

1313
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. 25 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, Verilog, SystemVerilog, Vue, Svelte, Dart).
1414

@@ -48,7 +48,7 @@ Every relationship is tagged `EXTRACTED` (found directly in source), `INFERRED`
4848

4949
## Install
5050

51-
**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)
51+
**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), [VS Code Copilot Chat](https://code.visualstudio.com/docs/copilot/overview), [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)
5252

5353
```bash
5454
pip install graphifyy && graphify install
@@ -65,6 +65,7 @@ pip install graphifyy && graphify install
6565
| Codex | `graphify install --platform codex` |
6666
| OpenCode | `graphify install --platform opencode` |
6767
| GitHub Copilot CLI | `graphify install --platform copilot` |
68+
| VS Code Copilot Chat | `graphify vscode install` |
6869
| Aider | `graphify install --platform aider` |
6970
| OpenClaw | `graphify install --platform claw` |
7071
| Factory Droid | `graphify install --platform droid` |
@@ -96,6 +97,7 @@ After building a graph, run this once in your project:
9697
| Codex | `graphify codex install` |
9798
| OpenCode | `graphify opencode install` |
9899
| GitHub Copilot CLI | `graphify copilot install` |
100+
| VS Code Copilot Chat | `graphify vscode install` |
99101
| Aider | `graphify aider install` |
100102
| OpenClaw | `graphify claw install` |
101103
| Factory Droid | `graphify droid install` |
@@ -125,6 +127,8 @@ After building a graph, run this once in your project:
125127

126128
**GitHub Copilot CLI** copies the skill to `~/.copilot/skills/graphify/SKILL.md`. Run `graphify copilot install` to set it up.
127129

130+
**VS Code Copilot Chat** installs a Python-only skill (works on Windows PowerShell and macOS/Linux alike) and writes `.github/copilot-instructions.md` in your project root — VS Code reads this automatically every session, making graph context always-on without any hook mechanism. Run `graphify vscode install`. Note: this configures the chat panel in VS Code, not the Copilot CLI terminal tool.
131+
128132
Uninstall with the matching uninstall command (e.g. `graphify claude uninstall`).
129133

130134
**Always-on vs explicit trigger — what's the difference?**

graphify/__main__.py

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,12 @@ def install(platform: str = "claude") -> None:
225225
def gemini_install(project_dir: Path | None = None) -> None:
226226
"""Copy skill file to ~/.gemini/skills/graphify/, write GEMINI.md section, and install BeforeTool hook."""
227227
# Copy skill file to ~/.gemini/skills/graphify/SKILL.md
228+
# On Windows, Gemini CLI prioritises ~/.agents/skills/ over ~/.gemini/skills/
228229
skill_src = Path(__file__).parent / "skill.md"
229-
skill_dst = Path.home() / ".gemini" / "skills" / "graphify" / "SKILL.md"
230+
if platform.system() == "Windows":
231+
skill_dst = Path.home() / ".agents" / "skills" / "graphify" / "SKILL.md"
232+
else:
233+
skill_dst = Path.home() / ".gemini" / "skills" / "graphify" / "SKILL.md"
230234
skill_dst.parent.mkdir(parents=True, exist_ok=True)
231235
shutil.copy(skill_src, skill_dst)
232236
(skill_dst.parent / ".graphify_version").write_text(__version__, encoding="utf-8")
@@ -284,8 +288,11 @@ def _uninstall_gemini_hook(project_dir: Path) -> None:
284288

285289
def gemini_uninstall(project_dir: Path | None = None) -> None:
286290
"""Remove the graphify section from GEMINI.md, uninstall hook, and remove skill file."""
287-
# Remove skill file
288-
skill_dst = Path.home() / ".gemini" / "skills" / "graphify" / "SKILL.md"
291+
# Remove skill file (mirror the install path detection)
292+
if platform.system() == "Windows":
293+
skill_dst = Path.home() / ".agents" / "skills" / "graphify" / "SKILL.md"
294+
else:
295+
skill_dst = Path.home() / ".gemini" / "skills" / "graphify" / "SKILL.md"
289296
if skill_dst.exists():
290297
skill_dst.unlink()
291298
print(f" skill removed -> {skill_dst}")
@@ -316,6 +323,75 @@ def gemini_uninstall(project_dir: Path | None = None) -> None:
316323
_uninstall_gemini_hook(project_dir or Path("."))
317324

318325

326+
_VSCODE_INSTRUCTIONS_MARKER = "## graphify"
327+
_VSCODE_INSTRUCTIONS_SECTION = """\
328+
## graphify
329+
330+
Before answering architecture or codebase questions, read `graphify-out/GRAPH_REPORT.md` if it exists.
331+
If `graphify-out/wiki/index.md` exists, navigate it for deep questions.
332+
Type `/graphify` in Copilot Chat to build or update the knowledge graph.
333+
"""
334+
335+
336+
def vscode_install(project_dir: Path | None = None) -> None:
337+
"""Install graphify skill for VS Code Copilot Chat + write .github/copilot-instructions.md."""
338+
skill_src = Path(__file__).parent / "skill-vscode.md"
339+
if not skill_src.exists():
340+
skill_src = Path(__file__).parent / "skill-copilot.md"
341+
skill_dst = Path.home() / ".copilot" / "skills" / "graphify" / "SKILL.md"
342+
skill_dst.parent.mkdir(parents=True, exist_ok=True)
343+
shutil.copy(skill_src, skill_dst)
344+
(skill_dst.parent / ".graphify_version").write_text(__version__, encoding="utf-8")
345+
print(f" skill installed -> {skill_dst}")
346+
347+
instructions = (project_dir or Path(".")) / ".github" / "copilot-instructions.md"
348+
instructions.parent.mkdir(parents=True, exist_ok=True)
349+
if instructions.exists():
350+
content = instructions.read_text(encoding="utf-8")
351+
if _VSCODE_INSTRUCTIONS_MARKER in content:
352+
print(f" {instructions} -> already configured (no change)")
353+
else:
354+
instructions.write_text(content.rstrip() + "\n\n" + _VSCODE_INSTRUCTIONS_SECTION, encoding="utf-8")
355+
print(f" {instructions} -> graphify section added")
356+
else:
357+
instructions.write_text(_VSCODE_INSTRUCTIONS_SECTION, encoding="utf-8")
358+
print(f" {instructions} -> created")
359+
360+
print()
361+
print("VS Code Copilot Chat configured. Type /graphify in the chat panel to build the graph.")
362+
print("Note: for GitHub Copilot CLI (terminal), use: graphify copilot install")
363+
364+
365+
def vscode_uninstall(project_dir: Path | None = None) -> None:
366+
"""Remove graphify VS Code Copilot Chat skill and .github/copilot-instructions.md section."""
367+
skill_dst = Path.home() / ".copilot" / "skills" / "graphify" / "SKILL.md"
368+
if skill_dst.exists():
369+
skill_dst.unlink()
370+
print(f" skill removed -> {skill_dst}")
371+
version_file = skill_dst.parent / ".graphify_version"
372+
if version_file.exists():
373+
version_file.unlink()
374+
for d in (skill_dst.parent, skill_dst.parent.parent, skill_dst.parent.parent.parent):
375+
try:
376+
d.rmdir()
377+
except OSError:
378+
break
379+
380+
instructions = (project_dir or Path(".")) / ".github" / "copilot-instructions.md"
381+
if not instructions.exists():
382+
return
383+
content = instructions.read_text(encoding="utf-8")
384+
if _VSCODE_INSTRUCTIONS_MARKER not in content:
385+
return
386+
cleaned = re.sub(r"\n*## graphify\n.*?(?=\n## |\Z)", "", content, flags=re.DOTALL).rstrip()
387+
if cleaned:
388+
instructions.write_text(cleaned + "\n", encoding="utf-8")
389+
print(f" graphify section removed from {instructions}")
390+
else:
391+
instructions.unlink()
392+
print(f" {instructions} -> deleted (was empty after removal)")
393+
394+
319395
_ANTIGRAVITY_RULES_PATH = Path(".agent") / "rules" / "graphify.md"
320396
_ANTIGRAVITY_WORKFLOW_PATH = Path(".agent") / "workflows" / "graphify.md"
321397

@@ -566,7 +642,7 @@ def _install_opencode_plugin(project_dir: Path) -> None:
566642
config = {}
567643

568644
plugins = config.setdefault("plugin", [])
569-
entry = str(_OPENCODE_PLUGIN_PATH)
645+
entry = _OPENCODE_PLUGIN_PATH.as_posix()
570646
if entry not in plugins:
571647
plugins.append(entry)
572648
config_file.write_text(json.dumps(config, indent=2), encoding="utf-8")
@@ -590,7 +666,7 @@ def _uninstall_opencode_plugin(project_dir: Path) -> None:
590666
except json.JSONDecodeError:
591667
return
592668
plugins = config.get("plugin", [])
593-
entry = str(_OPENCODE_PLUGIN_PATH)
669+
entry = _OPENCODE_PLUGIN_PATH.as_posix()
594670
if entry in plugins:
595671
plugins.remove(entry)
596672
if not plugins:
@@ -861,6 +937,8 @@ def main() -> None:
861937
print(" aider uninstall remove graphify section from AGENTS.md")
862938
print(" copilot install copy graphify skill to ~/.copilot/skills (GitHub Copilot CLI)")
863939
print(" copilot uninstall remove graphify skill from ~/.copilot/skills")
940+
print(" vscode install configure VS Code Copilot Chat (skill + .github/copilot-instructions.md)")
941+
print(" vscode uninstall remove VS Code Copilot Chat configuration")
864942
print(" claw install write graphify section to AGENTS.md (OpenClaw)")
865943
print(" claw uninstall remove graphify section from AGENTS.md")
866944
print(" droid install write graphify section to AGENTS.md (Factory Droid)")
@@ -922,6 +1000,15 @@ def main() -> None:
9221000
else:
9231001
print("Usage: graphify cursor [install|uninstall]", file=sys.stderr)
9241002
sys.exit(1)
1003+
elif cmd == "vscode":
1004+
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
1005+
if subcmd == "install":
1006+
vscode_install()
1007+
elif subcmd == "uninstall":
1008+
vscode_uninstall()
1009+
else:
1010+
print("Usage: graphify vscode [install|uninstall]", file=sys.stderr)
1011+
sys.exit(1)
9251012
elif cmd == "copilot":
9261013
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
9271014
if subcmd == "install":

graphify/analyze.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def god_nodes(G: nx.Graph, top_n: int = 10) -> list[dict]:
5151
result.append({
5252
"id": node_id,
5353
"label": G.nodes[node_id].get("label", node_id),
54-
"edges": deg,
54+
"degree": deg,
5555
})
5656
if len(result) >= top_n:
5757
break

graphify/detect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class FileType(str, Enum):
1818

1919
_MANIFEST_PATH = "graphify-out/manifest.json"
2020

21-
CODE_EXTENSIONS = {'.py', '.ts', '.js', '.jsx', '.tsx', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.toc', '.zig', '.ps1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.dart', '.v', '.sv'}
21+
CODE_EXTENSIONS = {'.py', '.ts', '.js', '.jsx', '.tsx', '.mjs', '.ejs', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.toc', '.zig', '.ps1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.dart', '.v', '.sv'}
2222
DOC_EXTENSIONS = {'.md', '.txt', '.rst'}
2323
PAPER_EXTENSIONS = {'.pdf'}
2424
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}

graphify/report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def generate(
7272
"## God Nodes (most connected - your core abstractions)",
7373
]
7474
for i, node in enumerate(god_node_list, 1):
75-
lines.append(f"{i}. `{node['label']}` - {node['edges']} edges")
75+
lines.append(f"{i}. `{node['label']}` - {node['degree']} edges")
7676

7777
lines += ["", "## Surprising Connections (you probably didn't know these)"]
7878
if surprise_list:

graphify/serve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def _tool_god_nodes(arguments: dict) -> str:
295295
from .analyze import god_nodes as _god_nodes
296296
nodes = _god_nodes(G, top_n=int(arguments.get("top_n", 10)))
297297
lines = ["God nodes (most connected):"]
298-
lines += [f" {i}. {n['label']} - {n['edges']} edges" for i, n in enumerate(nodes, 1)]
298+
lines += [f" {i}. {n['label']} - {n['degree']} edges" for i, n in enumerate(nodes, 1)]
299299
return "\n".join(lines)
300300

301301
def _tool_graph_stats(_: dict) -> str:

0 commit comments

Comments
 (0)