#1174: affected.py load_graph now forces directed=True before
node_link_graph, matching the identical fix in serve.py and __main__.py.
Undirected graphs (directed:false in graph.json) were causing in_edges
to fall back to a direction-blind scan, missing true callers and
reporting false positives. Regression test added.
#1173: post-commit and post-checkout hook bodies now read
graphify-out/.graphify_root before calling _rebuild_code, falling back
to Path('.') if absent. A scoped build (graphify src/) no longer gets
silently expanded to the full repo on the next commit. Tests added.
#1172: Step 9 cleanup split into rm -f for fixed files and
find -maxdepth 1 -delete for the chunk glob. Under fish/zsh an
unmatched glob aborts the entire rm -f line, leaving temp files on disk.
Fixed in the three skillgen source fragments and regenerated.
#1163: detect_incremental type guard on stored mtime — if the manifest
contains a dict-valued mtime (schema drift from older versions), coerce
to None rather than propagating a non-numeric into comparisons.
Regression test added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#1170 — replace nohup with cross-platform Python detach in git hooks.
Git for Windows MSYS has no nohup so post-commit/post-checkout hooks
silently failed. Now uses subprocess.Popen with DETACHED_PROCESS |
CREATE_NEW_PROCESS_GROUP on Windows, start_new_session=True on POSIX.
Quoting-safe (argv list). Fixes#1161.
#1169 — fix _is_sensitive false positives on topic-mentioning filenames.
token-economics-of-recall.md and password-policy-discussion.md were
silently dropped as secrets. Generic keywords (token/secret/password)
now only fire when the keyword ends the filename stem or the stem is
≤2 words. Specific patterns (.env/.pem/id_rsa etc.) remain unconditional.
#1165 — fix multi-word endpoint resolution in _score_nodes.
graphify path "AuthService" "UserRepo" never fired the exact-match bonus
because per-token comparison never equalled the full label. Now joins
normalized tokens and compares against the full label and its tokenized
form. O(1) per node, affects query_graph and shortest_path uniformly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per adversarial review of the #1127 fix:
- Apply the filesystem-path allowlist to sys.executable before embedding it in
the generated hook script. Paths with metacharacters outside [a-zA-Z0-9/_.@:\-]
(spaces, dollar signs, backticks, semicolons) are replaced with an empty string
so the pinned probe is safely skipped rather than injecting shell commands that
execute on every git commit.
- Change _PINNED='...' to single-quote assignment so no shell expansion can occur
even if a character slipped through the allowlist (belt and suspenders).
- Quote $GRAPHIFY_PYTHON in both nohup exec lines so paths with spaces work
(common in Windows C:\Program Files\... installs).
- Update test to assert the sanitized value, not raw sys.executable, so the test
stays correct after any future sanitization changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The issue reporter correctly noted that the hooks never consulted
graphify-out/.graphify_python -- the runtime interpreter source the skill and
README already rely on. Added as a second probe (after the pinned sys.executable,
before the launcher shebang and python3/python fallbacks). This covers Windows/Git
Bash where the uv-tool launcher is a binary with no parseable shebang, and where
the pinned exe may go stale after a reinstall.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
graphify hook install generated scripts that resolved the Python interpreter
purely at git-trigger time via 'command -v graphify'. GUI git clients (VS Code,
GitKraken), CI runners, and non-login shells often run with a minimal PATH that
omits ~/.local/bin -- the uv-tool / pipx launcher location. command -v graphify
returned empty, the python3/python fallbacks could not import graphify (it lives
only in the isolated venv), and the hook silently exited 0 with no output.
Fix: embed sys.executable of the currently-running install process as a pinned
first probe. Since 'graphify hook install' itself runs under the correct isolated
interpreter, sys.executable is always the right path. It is validated at
hook-runtime via 'import graphify' before use, so a stale pinned path safely
falls through to the existing dynamic detection rather than breaking the hook.
Also make the final fallback loud: print a diagnostic to stderr before exiting 0
so the failure is visible rather than invisible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
graphify-out regenerates differently on every `graphify update` even
when no source changed, so the committed graph is perpetually dirty and
the post-commit/post-checkout hooks fight every commit. Two independent
nondeterminism sources, each fixed here:
1. Edge direction flips. build.py builds an undirected graph and stores
direction in _src/_tgt; collapsing two edges onto the same node pair
is last-write-wins, and unstable edge iteration order flips them
run-to-run. Fixed by sorting edges by (source, target, relation)
before the add loop.
2. Clustering churn. The networkx Louvain fallback iterates string-keyed
sets whose order is randomized per-process by PYTHONHASHSEED, so
community assignments differ run-to-run even with seed=42. Fixed by
exporting PYTHONHASHSEED=0 in the generated post-commit and
post-checkout hook scripts.
With both fixes, `graphify update` is idempotent: rebuilding an
already-converged graphify-out reproduces graph.json and GRAPH_REPORT.md
byte-for-byte.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- hooks.py: drop --path-format=absolute (added git 2.31), validate no newlines in path, anchor relative paths on repo root (#907)
- detect.py: seed save_manifest from existing manifest before loop so incremental callers don't erase untouched file entries (#917)
- cluster.py: drop round(..., 2) from cohesion_score so split threshold 0.05 fires correctly; add resolution param to _partition and cluster; add exclude_hubs_percentile to cluster with majority-vote reattachment (#919)
- report.py: format cohesion with :.2f for display
- __main__.py: wire --resolution and --exclude-hubs into extract and cluster-only commands (#919)
- C++ inheritance already written to disk by analysis agent (#915)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In a linked worktree .git is a file not a directory, so root/.git/hooks
fails with NotADirectoryError. Fall back to git rev-parse --path-format=absolute
--git-path hooks which resolves the correct hooks dir from any worktree.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#791: per-repo fcntl flock in _rebuild_code prevents concurrent hook rebuilds
from exhausting memory; changed_paths wired through so only modified files are
re-extracted; stale nodes evicted on deletion; SIGALRM watchdog with
GRAPHIFY_REBUILD_TIMEOUT; Darwin-aware RLIMIT_DATA memory cap
#792: remove hard 8-worker cap (GRAPHIFY_MAX_WORKERS env var); add
--max-workers, --token-budget, --max-concurrency, --api-timeout CLI flags
to graphify extract; fix ollama API key gate for loopback URLs; explicit
timeout on OpenAI client (GRAPHIFY_API_TIMEOUT, default 600s); per-chunk
progress prints during extraction
#453 + #785: rename .agent -> .agents throughout antigravity install/uninstall;
add trigger:always_on YAML frontmatter to _ANTIGRAVITY_RULES so Antigravity
recognises the rules file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bypasses the node-count safety check in to_json for refactors that
legitimately shrink the graph (renames, package deletions). Also
honored by post-commit and post-checkout hooks via GRAPHIFY_FORCE=1.
Implements the approach from #639 (targeted at v5) adapted for v6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
watch.py: filter preserved nodes by ID membership in new AST output instead
of file_type — INFERRED/AMBIGUOUS nodes on code files also carry file_type=code
and were being wrongly dropped, triggering the to_json safety check refusal.
hooks.py: detach post-commit and post-checkout rebuilds with nohup + disown
so git commit returns immediately instead of blocking for the full rebuild
duration. Rebuild log written to ~/.cache/graphify-rebuild.log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- build/validate: accept NetworkX <=3.1 "links" key alongside "edges" (#212)
- __main__: skip version check during install/uninstall, deduplicate paths (#220)
- all file IO: explicit encoding="utf-8" to prevent crashes on Windows CJK locales (#204)
- hooks: add newline="\n" on write to prevent CRLF shebang breakage on Windows (#204)
- export: strip trailing .md from safe_name so "CLAUDE.md" doesn't become "CLAUDE.md.md" (#221)
- report: add Community Hubs navigation block so Obsidian vault stays connected (#221)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extract.py: use str(path) for node IDs to prevent same-basename collision (#211)
- build.py: normalize from/to edge keys before KeyError (#216)
- export.py: guard ZeroDivisionError when graph has no edges (#217)
- hooks.py: remove stale CODE_EXTS filter, rebuild on any changed file (#222)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: git hooks fail when graphify is installed via pipx
When installed via pipx, the graphify module is only available in
pipx's isolated venv, not the system python3. The git hooks
(post-commit, post-checkout) hardcoded `python3` which cannot import
graphify in this case.
Detect the correct Python interpreter from the graphify binary's
shebang line, matching the approach already used in skill.md Step 1.
Falls back to python3 for system installs.
* fix: handle env-style shebangs and improve interpreter detection
- Use POSIX `command -v` instead of non-standard `which`
- Parse `#!/usr/bin/env python3` shebangs correctly (previous
`tr -d ' '` would produce `/usr/bin/envpython3`)
- Add import validation fallback to python3 if resolved interpreter
cannot import graphify