From e3adc0417a966d15dc953cc7e53190d622157fca Mon Sep 17 00:00:00 2001 From: Safi Date: Fri, 10 Apr 2026 10:07:11 +0100 Subject: [PATCH] fix hook reinstall, CRLF labels, skill-windows missing commands (0.3.28) --- CHANGELOG.md | 6 ++++++ graphify/__main__.py | 21 ++++++--------------- graphify/export.py | 4 ++-- graphify/extract.py | 2 +- graphify/skill-windows.md | 3 +++ pyproject.toml | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166955e9..c7aeddc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/graphify/__main__.py b/graphify/__main__.py index 6a86661d..a8a56c07 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -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") diff --git a/graphify/export.py b/graphify/export.py index 498e514a..e58df176 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -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: diff --git a/graphify/extract.py b/graphify/extract.py index c767e07f..65e62c64 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -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) diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index 1f174cb8..26984312 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -15,6 +15,7 @@ Turn any folder of files into a navigable knowledge graph with community detecti /graphify # full pipeline on specific path /graphify --mode deep # thorough extraction, richer INFERRED edges /graphify --update # incremental - re-extract only new/changed files +/graphify --directed # build directed graph (preserves edge direction: source→target) /graphify --cluster-only # rerun clustering on existing graph /graphify --no-viz # skip visualization, just report + JSON /graphify --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 --graphml # export graph.graphml (Gephi, yEd) /graphify --neo4j # generate graphify-out/cypher.txt for Neo4j /graphify --neo4j-push bolt://localhost:7687 # push directly to Neo4j +/graphify --wiki # build agent-crawlable wiki (index.md + one article per community) +/graphify --obsidian --obsidian-dir ~/vaults/my-project # write vault to custom path (e.g. existing vault) /graphify --mcp # start MCP stdio server for agent access /graphify --watch # watch folder, auto-rebuild on code changes (no LLM needed) /graphify add # fetch URL, save to ./raw, update graph diff --git a/pyproject.toml b/pyproject.toml index eac661e8..2fc9ed25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" }