From 91f4d120b630ee35c79bf3c75ccd186870a808f9 Mon Sep 17 00:00:00 2001 From: Safi Date: Thu, 14 May 2026 23:03:21 +0100 Subject: [PATCH] fix worktree hook crash, prune isolated code nodes, restore rationale-node prohibition - hooks.py: use git rev-parse --git-path hooks so install/status/uninstall work in linked worktrees where .git is a file not a directory (fixes #865) - build.py: prune degree-0 code nodes after graph assembly to remove bundled/synthetic symbols with no connections (fixes #728) - skill.md: restore explicit rule prohibiting file_type:"rationale" nodes and remind model to store rationale as an attribute instead (fixes #751) Co-Authored-By: Claude Sonnet 4.6 --- graphify/build.py | 9 +++++++++ graphify/hooks.py | 15 ++++++++++++++- graphify/skill.md | 5 ++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/graphify/build.py b/graphify/build.py index 0975d7a5..ca3c0f8f 100644 --- a/graphify/build.py +++ b/graphify/build.py @@ -28,6 +28,15 @@ def build_from_json(extraction: dict) -> nx.Graph: hyperedges = extraction.get("hyperedges", []) if hyperedges: G.graph["hyperedges"] = hyperedges + # Strip degree-0 code nodes — they are bundled/synthetic symbols with no + # connections and only inflate god-node centrality and clustering noise. + # Document, paper, and image nodes are kept even when isolated since they + # may be leaf concepts intentionally referenced by the skill. + isolated_code = [ + n for n in list(G.nodes()) + if G.degree(n) == 0 and G.nodes[n].get("file_type") == "code" + ] + G.remove_nodes_from(isolated_code) return G diff --git a/graphify/hooks.py b/graphify/hooks.py index 0c86f969..f66c60cc 100644 --- a/graphify/hooks.py +++ b/graphify/hooks.py @@ -75,8 +75,21 @@ def _hooks_dir(root: Path) -> Path: return p except (OSError, FileNotFoundError): pass + # In a linked worktree .git is a file not a directory, so we ask git + # for the real hooks path rather than constructing it ourselves. + try: + result = subprocess.run( + ["git", "-C", str(root), "rev-parse", "--path-format=absolute", "--git-path", "hooks"], + capture_output=True, text=True, + ) + if result.returncode == 0: + d = Path(result.stdout.strip()) + d.mkdir(parents=True, exist_ok=True) + return d + except (OSError, FileNotFoundError): + pass d = root / ".git" / "hooks" - d.mkdir(exist_ok=True) + d.mkdir(parents=True, exist_ok=True) return d diff --git a/graphify/skill.md b/graphify/skill.md index 865b3429..c9683689 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -195,7 +195,10 @@ Rules: Code files: focus on semantic edges AST cannot find (call relationships, shared data, arch patterns). Do not re-extract imports - AST already has those. -Doc/paper files: extract named concepts, entities, citations. +Doc/paper files: extract named concepts, entities, citations. For rationale (WHY decisions were made, + trade-offs, design intent): store as a `rationale` attribute on the relevant concept node — do NOT + create a separate rationale node. Valid `file_type` values are ONLY `code|document|paper|image` — + never emit `file_type:"rationale"` or `file_type:"concept"`. Image files: use vision to understand what the image IS - do not just OCR. UI screenshot: layout patterns, design decisions, key elements, purpose. Chart: metric, trend/insight, data source.