fix hook reinstall, CRLF labels, skill-windows missing commands (0.3.28)
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
|
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)
|
## 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)
|
- 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
@@ -241,10 +241,8 @@ def _install_gemini_hook(project_dir: Path) -> None:
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
settings = {}
|
settings = {}
|
||||||
before_tool = settings.setdefault("hooks", {}).setdefault("BeforeTool", [])
|
before_tool = settings.setdefault("hooks", {}).setdefault("BeforeTool", [])
|
||||||
if any("graphify" in str(h) for h in before_tool):
|
settings["hooks"]["BeforeTool"] = [h for h in before_tool if "graphify" not in str(h)]
|
||||||
print(" .gemini/settings.json -> hook already registered (no change)")
|
settings["hooks"]["BeforeTool"].append(_GEMINI_HOOK)
|
||||||
return
|
|
||||||
before_tool.append(_GEMINI_HOOK)
|
|
||||||
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
||||||
print(" .gemini/settings.json -> BeforeTool hook registered")
|
print(" .gemini/settings.json -> BeforeTool hook registered")
|
||||||
|
|
||||||
@@ -455,11 +453,8 @@ def _install_codex_hook(project_dir: Path) -> None:
|
|||||||
existing = {}
|
existing = {}
|
||||||
|
|
||||||
pre_tool = existing.setdefault("hooks", {}).setdefault("PreToolUse", [])
|
pre_tool = existing.setdefault("hooks", {}).setdefault("PreToolUse", [])
|
||||||
if any("graphify" in str(h) for h in pre_tool):
|
existing["hooks"]["PreToolUse"] = [h for h in pre_tool if "graphify" not in str(h)]
|
||||||
print(f" .codex/hooks.json -> hook already registered (no change)")
|
existing["hooks"]["PreToolUse"].extend(_CODEX_HOOK["hooks"]["PreToolUse"])
|
||||||
return
|
|
||||||
|
|
||||||
pre_tool.extend(_CODEX_HOOK["hooks"]["PreToolUse"])
|
|
||||||
hooks_path.write_text(json.dumps(existing, indent=2), encoding="utf-8")
|
hooks_path.write_text(json.dumps(existing, indent=2), encoding="utf-8")
|
||||||
print(f" .codex/hooks.json -> PreToolUse hook registered")
|
print(f" .codex/hooks.json -> PreToolUse hook registered")
|
||||||
|
|
||||||
@@ -578,12 +573,8 @@ def _install_claude_hook(project_dir: Path) -> None:
|
|||||||
hooks = settings.setdefault("hooks", {})
|
hooks = settings.setdefault("hooks", {})
|
||||||
pre_tool = hooks.setdefault("PreToolUse", [])
|
pre_tool = hooks.setdefault("PreToolUse", [])
|
||||||
|
|
||||||
# Check if already installed
|
hooks["PreToolUse"] = [h for h in pre_tool if not (h.get("matcher") == "Glob|Grep" and "graphify" in str(h))]
|
||||||
if any(h.get("matcher") == "Glob|Grep" and "graphify" in str(h) for h in pre_tool):
|
hooks["PreToolUse"].append(_SETTINGS_HOOK)
|
||||||
print(f" .claude/settings.json -> hook already registered (no change)")
|
|
||||||
return
|
|
||||||
|
|
||||||
pre_tool.append(_SETTINGS_HOOK)
|
|
||||||
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
|
||||||
print(f" .claude/settings.json -> PreToolUse hook registered")
|
print(f" .claude/settings.json -> PreToolUse hook registered")
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -463,7 +463,7 @@ def to_obsidian(
|
|||||||
# Map node_id → safe filename so wikilinks stay consistent.
|
# Map node_id → safe filename so wikilinks stay consistent.
|
||||||
# Deduplicate: if two nodes produce the same filename, append a numeric suffix.
|
# Deduplicate: if two nodes produce the same filename, append a numeric suffix.
|
||||||
def safe_name(label: str) -> str:
|
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] = {}
|
node_filename: dict[str, str] = {}
|
||||||
seen_names: dict[str, int] = {}
|
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
|
CANVAS_COLORS = ["1", "2", "3", "4", "5", "6"] # red, orange, yellow, green, cyan, purple
|
||||||
|
|
||||||
def safe_name(label: str) -> str:
|
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)
|
# Build node_filenames if not provided (same dedup logic as to_obsidian)
|
||||||
if node_filenames is None:
|
if node_filenames is None:
|
||||||
|
|||||||
+1
-1
@@ -1021,7 +1021,7 @@ def _extract_python_rationale(path: Path, result: dict) -> None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _add_rationale(text: str, line: int, parent_nid: str) -> 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))
|
rid = _make_id(stem, "rationale", str(line))
|
||||||
if rid not in seen_ids:
|
if rid not in seen_ids:
|
||||||
seen_ids.add(rid)
|
seen_ids.add(rid)
|
||||||
|
|||||||
@@ -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> # full pipeline on specific path
|
||||||
/graphify <path> --mode deep # thorough extraction, richer INFERRED edges
|
/graphify <path> --mode deep # thorough extraction, richer INFERRED edges
|
||||||
/graphify <path> --update # incremental - re-extract only new/changed files
|
/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> --cluster-only # rerun clustering on existing graph
|
||||||
/graphify <path> --no-viz # skip visualization, just report + JSON
|
/graphify <path> --no-viz # skip visualization, just report + JSON
|
||||||
/graphify <path> --html # (HTML is generated by default - this flag is a no-op)
|
/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> --graphml # export graph.graphml (Gephi, yEd)
|
||||||
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
|
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
|
||||||
/graphify <path> --neo4j-push bolt://localhost:7687 # push directly to 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> --mcp # start MCP stdio server for agent access
|
||||||
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
|
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
|
||||||
/graphify add <url> # fetch URL, save to ./raw, update graph
|
/graphify add <url> # fetch URL, save to ./raw, update graph
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "graphifyy"
|
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"
|
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"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
|
|||||||
Reference in New Issue
Block a user