From 71b4e57b67e2fa0482e49bf775ff0e0a9b3759c1 Mon Sep 17 00:00:00 2001 From: Safi Date: Sat, 23 May 2026 23:17:40 +0100 Subject: [PATCH] fix Husky 9 hook path, skill.md INPUT_PATH literal, per-worker exception isolation - hooks.py: add _user_hooks_dir() to target .husky/ instead of .husky/_ on Husky 9 repos (#987) - skill.md: replace unsubstituted 'INPUT_PATH' literal with '.' in generate() calls (#986) - extract.py: wrap future.result() per-future so a single worker failure prints a warning instead of falling back to full sequential re-extraction (#943) Co-Authored-By: Claude Sonnet 4.6 --- graphify/extract.py | 11 +++++++++-- graphify/hooks.py | 19 ++++++++++++++++--- graphify/skill.md | 4 ++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/graphify/extract.py b/graphify/extract.py index d180e958..a0fdc585 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -7434,8 +7434,15 @@ def _extract_parallel( pool.submit(_extract_single_file, item): item[0] for item in work_items } for future in concurrent.futures.as_completed(futures): - idx, result = future.result() - per_file[idx] = result + try: + idx, result = future.result() + per_file[idx] = result + except Exception as exc: + idx = futures[future] + print( + f" warning: worker failed for {work_items[idx][1]}: {exc}", + file=sys.stderr, flush=True, + ) done_count += 1 if ( total_files >= _PROGRESS_INTERVAL diff --git a/graphify/hooks.py b/graphify/hooks.py index ae701b9f..4792319d 100644 --- a/graphify/hooks.py +++ b/graphify/hooks.py @@ -259,13 +259,26 @@ def _uninstall_hook(hooks_dir: Path, name: str, marker: str, marker_end: str) -> return f"graphify removed from {name} at {hook_path} (other hook content preserved)" +def _user_hooks_dir(hooks_dir: Path) -> Path: + """Return the user-editable hooks directory. + + Husky 9 sets core.hooksPath to .husky/_ (wrapper scripts auto-generated by + Husky), while user-editable hooks live in the parent .husky/. Return the + parent when the resolved dir ends in '_' so install/status/uninstall target + the correct location (#987). + """ + if hooks_dir.name == "_": + return hooks_dir.parent + return hooks_dir + + def install(path: Path = Path(".")) -> str: """Install graphify post-commit and post-checkout hooks in the nearest git repo.""" root = _git_root(path) if root is None: raise RuntimeError(f"No git repository found at or above {path.resolve()}") - hooks_dir = _hooks_dir(root) + hooks_dir = _user_hooks_dir(_hooks_dir(root)) commit_msg = _install_hook(hooks_dir, "post-commit", _HOOK_SCRIPT, _HOOK_MARKER) checkout_msg = _install_hook(hooks_dir, "post-checkout", _CHECKOUT_SCRIPT, _CHECKOUT_MARKER) @@ -279,7 +292,7 @@ def uninstall(path: Path = Path(".")) -> str: if root is None: raise RuntimeError(f"No git repository found at or above {path.resolve()}") - hooks_dir = _hooks_dir(root) + hooks_dir = _user_hooks_dir(_hooks_dir(root)) commit_msg = _uninstall_hook(hooks_dir, "post-commit", _HOOK_MARKER, _HOOK_MARKER_END) checkout_msg = _uninstall_hook(hooks_dir, "post-checkout", _CHECKOUT_MARKER, _CHECKOUT_MARKER_END) @@ -291,7 +304,7 @@ def status(path: Path = Path(".")) -> str: root = _git_root(path) if root is None: return "Not in a git repository." - hooks_dir = _hooks_dir(root) + hooks_dir = _user_hooks_dir(_hooks_dir(root)) def _check(name: str, marker: str) -> str: p = hooks_dir / name diff --git a/graphify/skill.md b/graphify/skill.md index c3e39b3f..98960147 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -537,7 +537,7 @@ labels = {cid: 'Community ' + str(cid) for cid in communities} # Placeholder questions - regenerated with real labels in Step 5 questions = suggest_questions(G, communities, labels) -report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, 'INPUT_PATH', suggested_questions=questions) +report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, '.', suggested_questions=questions) Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding=\"utf-8\") to_json(G, communities, 'graphify-out/graph.json') @@ -591,7 +591,7 @@ labels = LABELS_DICT # Regenerate questions with real community labels (labels affect question phrasing) questions = suggest_questions(G, communities, labels) -report = generate(G, communities, cohesion, labels, analysis['gods'], analysis['surprises'], detection, tokens, 'INPUT_PATH', suggested_questions=questions) +report = generate(G, communities, cohesion, labels, analysis['gods'], analysis['surprises'], detection, tokens, '.', suggested_questions=questions) Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding=\"utf-8\") Path('graphify-out/.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()}, ensure_ascii=False), encoding=\"utf-8\") print('Report updated with community labels')