#1810 — detection read only .gitignore/.graphifyignore, never
.git/info/exclude, which is where git records local-only excludes and
where `git worktree add` writes nested worktree paths. graphify walked
into those worktree copies and the graph exploded (one 5-worktree repo:
9.4k nodes/10MB -> 210k nodes/311MB, ~77% duplicate). detect now loads
info/exclude at lowest precedence (below every per-dir .gitignore, per
git, so a nearer `!` still wins) and resolves the linked-worktree /
submodule case where `.git` is a file to the shared common git dir.
#1809 — two git-hook gaps: (a) post-checkout never honored
GRAPHIFY_SKIP_HOOK, so the var stopped commit rebuilds but not
branch-switch ones; now checked in both. (b) with core.hooksPath shared
across worktrees, a commit in any linked worktree fired post-commit,
which wrote a rogue delta-only graph.json into it and raced deploy/CI
`git clean` against the detached rebuild. Both hooks now short-circuit
in a linked worktree (git-dir != git-common-dir), comparing ABSOLUTE
paths so the primary checkout (where --git-common-dir is the relative
".git") is never false-positived and skipped.
Adds regression tests: info/exclude honored + negation precedence;
both hooks honor the skip env and carry the worktree guard; and an
end-to-end guard check against a real `git worktree`.
Reported by @cdahl86-cyber (#1810, #1809); the worktree guard was
co-developed with @Claude-Madera's PR #1806.
Co-Authored-By: cdahl86-cyber <cdahl86-cyber@users.noreply.github.com>
Co-Authored-By: Claude-Madera <Claude-Madera@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three foreground costs ran on every commit before the detached rebuild
launcher even started:
1. Interpreter probes imported the full package. Each probe executed
'import graphify' wholesale — measured 13s per probe cold on a Windows 11
dev box with AV-scanned site-packages — and up to four probes could run
synchronously. Probes now use importlib.util.find_spec, which locates the
package without executing it (interpreter startup cost only). A broken
install under the selected interpreter still fails loudly in the rebuild
log, as before.
2. The shebang probe read a binary. Git for Windows' command -v can return
the launcher path WITHOUT its .exe suffix, so the '*.exe)' guard missed
and head -1 read a PE binary: the shell warned 'ignored null byte in
input' on every commit and the garbage always fell through to the slow
python3/python fallbacks. The Windows pip layout is now resolved directly
(Scripts/graphify -> sibling ../python.exe, or ./python.exe for venvs),
and the remaining POSIX shebang read strips NULs first.
3. GIT_DIR was re-derived. git exports GIT_DIR to hooks; the unconditional
rev-parse added ~1.3s more on machines where every git exec is scanned.
Now reused from the environment with rev-parse as the manual-run fallback.
Measured on the affected machine: hook foreground drops from 26s+ (cold) to
~1.4s, warnings gone. Behavior is unchanged on healthy POSIX setups — probe
order and fallback semantics are preserved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1418: build_from_json relativized source_file on nodes and edges but stored
graph.hyperedges[] verbatim, so a semantic subagent's absolute path leaked into
graph.json. Relativize hyperedges in build_from_json (to_json has no root to
relativize against), mirroring the existing node/edge handling.
#1423: consolidate the GRAPHIFY_OUT output-dir name into a single graphify.paths
module (was duplicated in __main__, cache, watch) and route the path guards
through it — security.validate_graph_path's base=None discovery + fallback,
callflow_html's project-root resolution, and the post-commit/post-checkout hook
bodies (which now read the env var at hook-run time). A renamed output dir is no
longer validated against the wrong base or missed by the hook.
Tests: hyperedge relativization (test_hypergraph), GRAPHIFY_OUT discovery
(test_security), updated the hook-body contract assertion (test_hooks).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On WSL/POSIX, Path("C:\\...").is_absolute() is False, so a Windows absolute
core.hooksPath (or rev-parse --git-path output) was joined under the repo root
and mkdir'd as a literal backslash-named junk directory while install reported
success and the real .git/hooks got nothing. Both branches of _hooks_dir now
reject drive-letter / backslash paths with a clear error so the failure is loud.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#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>
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>
Codex Desktop rejects hookSpecificOutput.additionalContext on PreToolUse.
hook-check is now a no-op — graph guidance reaches the agent via AGENTS.md/skill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>