mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-23 14:05:43 +00:00
Fix watch edge key, claw path, Blade support, WSL MCP docs (0.4.7)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
f8c91a987f
commit
4210de270f
@@ -2,6 +2,13 @@
|
||||
|
||||
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
|
||||
|
||||
## 0.4.7 (2026-04-12)
|
||||
|
||||
- Fix: `watch` semantic edge preservation was always empty — `graph.json` uses `links` key but code read `edges` (#269)
|
||||
- Fix: `graphify claw install` now writes to `.openclaw/` (correct OpenClaw directory) instead of `.claw/` (#208)
|
||||
- Add: Blade template support — `@include`, `<livewire:>` components, and `wire:click` bindings extracted from `.blade.php` files (#242)
|
||||
- Docs: WSL/Linux MCP setup note — package name is `graphifyy`, use `.venv/bin/python3` in `.mcp.json` (#250)
|
||||
|
||||
## 0.4.6 (2026-04-12)
|
||||
|
||||
- Add: Google Antigravity support — `graphify antigravity install` writes `.agent/rules/graphify.md` (always-on rules) and `.agent/workflows/graphify.md` (`/graphify` slash command) (#203, #199, #53)
|
||||
|
||||
@@ -165,6 +165,15 @@ python -m graphify.serve graphify-out/graph.json
|
||||
That gives the assistant structured graph access for repeated queries such as
|
||||
`query_graph`, `get_node`, `get_neighbors`, and `shortest_path`.
|
||||
|
||||
> **WSL / Linux note:** Ubuntu ships `python3`, not `python`. Install into a project venv to avoid PEP 668 conflicts, and use the full venv path in your `.mcp.json`:
|
||||
> ```bash
|
||||
> python3 -m venv .venv && .venv/bin/pip install "graphifyy[mcp]"
|
||||
> ```
|
||||
> ```json
|
||||
> { "mcpServers": { "graphify": { "type": "stdio", "command": ".venv/bin/python3", "args": ["-m", "graphify.serve", "graphify-out/graph.json"] } } }
|
||||
> ```
|
||||
> Also note: the PyPI package is `graphifyy` (double-y) — `pip install graphify` installs an unrelated package.
|
||||
|
||||
<details>
|
||||
<summary>Manual install (curl)</summary>
|
||||
|
||||
@@ -329,9 +338,25 @@ graphify sends file contents to your AI coding assistant's underlying model API
|
||||
|
||||
NetworkX + Leiden (graspologic) + tree-sitter + vis.js. Semantic extraction via Claude (Claude Code), GPT-4 (Codex), or whichever model your platform runs. Video transcription via faster-whisper + yt-dlp (optional, `pip install graphifyy[video]`). No Neo4j required, no server, runs entirely locally.
|
||||
|
||||
## Built on graphify — Penpax
|
||||
|
||||
[**Penpax**](https://safishamsi.github.io/penpax.ai) is the enterprise layer on top of graphify. Where graphify turns a folder of files into a knowledge graph, Penpax applies the same graph to your entire working life — continuously.
|
||||
|
||||
| | graphify | Penpax |
|
||||
|---|---|---|
|
||||
| Input | A folder of files | Browser history, meetings, emails, files, code — everything |
|
||||
| Runs | On demand | Continuously in the background |
|
||||
| Scope | A project | Your entire working life |
|
||||
| Query | CLI / MCP / AI skill | Natural language, always on |
|
||||
| Privacy | Local by default | Fully on-device, no cloud |
|
||||
|
||||
Built for lawyers, consultants, executives, doctors, researchers — anyone whose work lives across hundreds of conversations and documents they can never fully reconstruct.
|
||||
|
||||
**Free trial launching soon.** [Join the waitlist →](https://safishamsi.github.io/penpax.ai)
|
||||
|
||||
## What we are building next
|
||||
|
||||
graphify is the graph layer. We are building [Penpax](https://safishamsi.github.io/penpax.ai) on top of it — an on-device digital twin that connects your meetings, browser history, files, emails, and code into one continuously updating knowledge graph. No cloud, no training on your data. [Join the waitlist.](https://safishamsi.github.io/penpax.ai)
|
||||
graphify is the graph layer. Penpax is the always-on layer on top of it — an on-device digital twin that connects your meetings, browser history, files, emails, and code into one continuously updating knowledge graph. No cloud, no training on your data. [Join the waitlist.](https://safishamsi.github.io/penpax.ai)
|
||||
|
||||
## Star history
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ _PLATFORM_CONFIG: dict[str, dict] = {
|
||||
},
|
||||
"claw": {
|
||||
"skill_file": "skill-claw.md",
|
||||
"skill_dst": Path(".claw") / "skills" / "graphify" / "SKILL.md",
|
||||
"skill_dst": Path(".openclaw") / "skills" / "graphify" / "SKILL.md",
|
||||
"claude_md": False,
|
||||
},
|
||||
"droid": {
|
||||
|
||||
@@ -80,6 +80,9 @@ _ASSET_DIR_MARKERS = {".imageset", ".xcassets", ".appiconset", ".colorset", ".la
|
||||
|
||||
|
||||
def classify_file(path: Path) -> FileType | None:
|
||||
# Compound extensions must be checked before simple suffix lookup
|
||||
if path.name.lower().endswith(".blade.php"):
|
||||
return FileType.CODE
|
||||
ext = path.suffix.lower()
|
||||
if ext in CODE_EXTENSIONS:
|
||||
return FileType.CODE
|
||||
|
||||
+52
-1
@@ -1165,6 +1165,53 @@ def extract_php(path: Path) -> dict:
|
||||
return _extract_generic(path, _PHP_CONFIG)
|
||||
|
||||
|
||||
def extract_blade(path: Path) -> dict:
|
||||
"""Extract @include, <livewire:> components, and wire:click bindings from Blade templates."""
|
||||
import re
|
||||
try:
|
||||
src = path.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
return {"error": f"cannot read {path}"}
|
||||
|
||||
file_nid = _make_id(str(path))
|
||||
nodes = [{"id": file_nid, "label": path.name, "file_type": "code",
|
||||
"source_file": str(path), "source_location": None}]
|
||||
edges = []
|
||||
|
||||
# @include('path.to.partial') or @include("path.to.partial")
|
||||
for m in re.finditer(r"@include\(['\"]([^'\"]+)['\"]", src):
|
||||
tgt = m.group(1).replace(".", "/")
|
||||
tgt_nid = _make_id(tgt)
|
||||
if tgt_nid not in {n["id"] for n in nodes}:
|
||||
nodes.append({"id": tgt_nid, "label": m.group(1), "file_type": "code",
|
||||
"source_file": str(path), "source_location": None})
|
||||
edges.append({"source": file_nid, "target": tgt_nid, "relation": "includes",
|
||||
"confidence": "EXTRACTED", "confidence_score": 1.0,
|
||||
"source_file": str(path), "source_location": None, "weight": 1.0})
|
||||
|
||||
# <livewire:component.name /> or <livewire:component.name>
|
||||
for m in re.finditer(r"<livewire:([\w.\-]+)", src):
|
||||
tgt_nid = _make_id(m.group(1))
|
||||
if tgt_nid not in {n["id"] for n in nodes}:
|
||||
nodes.append({"id": tgt_nid, "label": m.group(1), "file_type": "code",
|
||||
"source_file": str(path), "source_location": None})
|
||||
edges.append({"source": file_nid, "target": tgt_nid, "relation": "uses_component",
|
||||
"confidence": "EXTRACTED", "confidence_score": 1.0,
|
||||
"source_file": str(path), "source_location": None, "weight": 1.0})
|
||||
|
||||
# wire:click="methodName"
|
||||
for m in re.finditer(r'wire:click=["\']([^"\']+)["\']', src):
|
||||
tgt_nid = _make_id(m.group(1))
|
||||
if tgt_nid not in {n["id"] for n in nodes}:
|
||||
nodes.append({"id": tgt_nid, "label": m.group(1), "file_type": "code",
|
||||
"source_file": str(path), "source_location": None})
|
||||
edges.append({"source": file_nid, "target": tgt_nid, "relation": "binds_method",
|
||||
"confidence": "EXTRACTED", "confidence_score": 1.0,
|
||||
"source_file": str(path), "source_location": None, "weight": 1.0})
|
||||
|
||||
return {"nodes": nodes, "edges": edges}
|
||||
|
||||
|
||||
def extract_lua(path: Path) -> dict:
|
||||
"""Extract functions, methods, require() imports, and calls from a .lua file."""
|
||||
return _extract_generic(path, _LUA_CONFIG)
|
||||
@@ -2655,7 +2702,11 @@ def extract(paths: list[Path]) -> dict:
|
||||
for i, path in enumerate(paths):
|
||||
if total >= _PROGRESS_INTERVAL and i % _PROGRESS_INTERVAL == 0 and i > 0:
|
||||
print(f" AST extraction: {i}/{total} files ({i * 100 // total}%)", flush=True)
|
||||
extractor = _DISPATCH.get(path.suffix)
|
||||
# .blade.php must be checked before suffix lookup since Path.suffix returns .php
|
||||
if path.name.endswith(".blade.php"):
|
||||
extractor = extract_blade
|
||||
else:
|
||||
extractor = _DISPATCH.get(path.suffix)
|
||||
if extractor is None:
|
||||
continue
|
||||
cached = load_cached(path, root)
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool:
|
||||
existing = json.loads(existing_graph.read_text(encoding="utf-8"))
|
||||
code_ids = {n["id"] for n in existing.get("nodes", []) if n.get("file_type") == "code"}
|
||||
sem_nodes = [n for n in existing.get("nodes", []) if n.get("file_type") != "code"]
|
||||
sem_edges = [e for e in existing.get("edges", [])
|
||||
sem_edges = [e for e in existing.get("links", existing.get("edges", []))
|
||||
if e.get("confidence") in ("INFERRED", "AMBIGUOUS")
|
||||
or (e.get("source") not in code_ids and e.get("target") not in code_ids)]
|
||||
result = {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "graphifyy"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
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" }
|
||||
|
||||
@@ -8,7 +8,7 @@ PLATFORMS = {
|
||||
"claude": (".claude/skills/graphify/SKILL.md",),
|
||||
"codex": (".agents/skills/graphify/SKILL.md",),
|
||||
"opencode": (".config/opencode/skills/graphify/SKILL.md",),
|
||||
"claw": (".claw/skills/graphify/SKILL.md",),
|
||||
"claw": (".openclaw/skills/graphify/SKILL.md",),
|
||||
"droid": (".factory/skills/graphify/SKILL.md",),
|
||||
"trae": (".trae/skills/graphify/SKILL.md",),
|
||||
"trae-cn": (".trae-cn/skills/graphify/SKILL.md",),
|
||||
@@ -39,7 +39,7 @@ def test_install_opencode(tmp_path):
|
||||
|
||||
def test_install_claw(tmp_path):
|
||||
_install(tmp_path, "claw")
|
||||
assert (tmp_path / ".claw" / "skills" / "graphify" / "SKILL.md").exists()
|
||||
assert (tmp_path / ".openclaw" / "skills" / "graphify" / "SKILL.md").exists()
|
||||
|
||||
|
||||
def test_install_droid(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user