fix hook reinstall, CRLF labels, skill-windows missing commands (0.3.28)

This commit is contained in:
Safi
2026-04-10 10:07:11 +01:00
parent f7ee752eb1
commit 210243fa58
6 changed files with 19 additions and 19 deletions
+6
View File
@@ -2,6 +2,12 @@
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
## 0.3.28 (2026-04-10)
- Fix: hook installers (Claude Code, Codex, Gemini CLI) now always remove and reinstall the hook on re-run — users upgrading from old versions no longer get stuck with a broken hook format (#182)
- Fix: rationale node labels no longer contain bare `\r` characters on Windows/WSL CRLF files — breaks Obsidian export was silently producing invalid filenames (#176)
- Fix: `skill-windows.md` now includes `--wiki`, `--obsidian-dir`, and `--directed` which were missing vs the main skill (#177)
## 0.3.27 (2026-04-10)
- Fix: graphify install --platform gemini now also copies the skill file to ~/.gemini/skills/graphify/SKILL.md so the /graphify trigger works in Gemini CLI (#174)
+6 -15
View File
@@ -241,10 +241,8 @@ def _install_gemini_hook(project_dir: Path) -> None:
except json.JSONDecodeError:
settings = {}
before_tool = settings.setdefault("hooks", {}).setdefault("BeforeTool", [])
if any("graphify" in str(h) for h in before_tool):
print(" .gemini/settings.json -> hook already registered (no change)")
return
before_tool.append(_GEMINI_HOOK)
settings["hooks"]["BeforeTool"] = [h for h in before_tool if "graphify" not in str(h)]
settings["hooks"]["BeforeTool"].append(_GEMINI_HOOK)
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
print(" .gemini/settings.json -> BeforeTool hook registered")
@@ -455,11 +453,8 @@ def _install_codex_hook(project_dir: Path) -> None:
existing = {}
pre_tool = existing.setdefault("hooks", {}).setdefault("PreToolUse", [])
if any("graphify" in str(h) for h in pre_tool):
print(f" .codex/hooks.json -> hook already registered (no change)")
return
pre_tool.extend(_CODEX_HOOK["hooks"]["PreToolUse"])
existing["hooks"]["PreToolUse"] = [h for h in pre_tool if "graphify" not in str(h)]
existing["hooks"]["PreToolUse"].extend(_CODEX_HOOK["hooks"]["PreToolUse"])
hooks_path.write_text(json.dumps(existing, indent=2), encoding="utf-8")
print(f" .codex/hooks.json -> PreToolUse hook registered")
@@ -578,12 +573,8 @@ def _install_claude_hook(project_dir: Path) -> None:
hooks = settings.setdefault("hooks", {})
pre_tool = hooks.setdefault("PreToolUse", [])
# Check if already installed
if any(h.get("matcher") == "Glob|Grep" and "graphify" in str(h) for h in pre_tool):
print(f" .claude/settings.json -> hook already registered (no change)")
return
pre_tool.append(_SETTINGS_HOOK)
hooks["PreToolUse"] = [h for h in pre_tool if not (h.get("matcher") == "Glob|Grep" and "graphify" in str(h))]
hooks["PreToolUse"].append(_SETTINGS_HOOK)
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
print(f" .claude/settings.json -> PreToolUse hook registered")
+2 -2
View File
@@ -463,7 +463,7 @@ def to_obsidian(
# Map node_id → safe filename so wikilinks stay consistent.
# Deduplicate: if two nodes produce the same filename, append a numeric suffix.
def safe_name(label: str) -> str:
return re.sub(r'[\\/*?:"<>|#^[\]]', "", label).strip() or "unnamed"
return re.sub(r'[\\/*?:"<>|#^[\]]', "", label.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")).strip() or "unnamed"
node_filename: dict[str, str] = {}
seen_names: dict[str, int] = {}
@@ -699,7 +699,7 @@ def to_canvas(
CANVAS_COLORS = ["1", "2", "3", "4", "5", "6"] # red, orange, yellow, green, cyan, purple
def safe_name(label: str) -> str:
return re.sub(r'[\\/*?:"<>|#^[\]]', "", label).strip() or "unnamed"
return re.sub(r'[\\/*?:"<>|#^[\]]', "", label.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")).strip() or "unnamed"
# Build node_filenames if not provided (same dedup logic as to_obsidian)
if node_filenames is None:
+1 -1
View File
@@ -1021,7 +1021,7 @@ def _extract_python_rationale(path: Path, result: dict) -> None:
return None
def _add_rationale(text: str, line: int, parent_nid: str) -> None:
label = text[:80].replace("\n", " ").strip()
label = text[:80].replace("\r\n", " ").replace("\r", " ").replace("\n", " ").strip()
rid = _make_id(stem, "rationale", str(line))
if rid not in seen_ids:
seen_ids.add(rid)
+3
View File
@@ -15,6 +15,7 @@ Turn any folder of files into a navigable knowledge graph with community detecti
/graphify <path> # full pipeline on specific path
/graphify <path> --mode deep # thorough extraction, richer INFERRED edges
/graphify <path> --update # incremental - re-extract only new/changed files
/graphify <path> --directed # build directed graph (preserves edge direction: source→target)
/graphify <path> --cluster-only # rerun clustering on existing graph
/graphify <path> --no-viz # skip visualization, just report + JSON
/graphify <path> --html # (HTML is generated by default - this flag is a no-op)
@@ -22,6 +23,8 @@ Turn any folder of files into a navigable knowledge graph with community detecti
/graphify <path> --graphml # export graph.graphml (Gephi, yEd)
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
/graphify <path> --neo4j-push bolt://localhost:7687 # push directly to Neo4j
/graphify <path> --wiki # build agent-crawlable wiki (index.md + one article per community)
/graphify <path> --obsidian --obsidian-dir ~/vaults/my-project # write vault to custom path (e.g. existing vault)
/graphify <path> --mcp # start MCP stdio server for agent access
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
/graphify add <url> # fetch URL, save to ./raw, update graph
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "graphifyy"
version = "0.3.27"
version = "0.3.28"
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, OpenClaw, Factory Droid, Trae) - turn any folder of code, docs, papers, or images into a queryable knowledge graph"
readme = "README.md"
license = { file = "LICENSE" }