Commit Graph
40 Commits
Author SHA1 Message Date
nothariharan 762cfd33ad fix(hooks): refuse a core.hooksPath that leaves the repository
hook install followed git's hooks path even when core.hooksPath resolved
outside the checkout, then created that directory and wrote post-commit
there. The bytes are Graphify's own hook, not repository-chosen code, but
the write still escapes the repo. Keep in-repo paths such as .husky, and
git's own hooks directory (including a linked worktree's shared .git/hooks).
Anything else falls back to that default.

Fixes #3869

Co-Authored-By: Grok 4.7 <noreply@x.ai>
(cherry picked from commit ee35087aea)
2026-10-04 20:40:34 +01:00
berce 81bb5280c0 fix(hooks): report hooks written by an older release as out of date (#3771)
Upgrading the package never touches hooks already on disk, so repos
initialised before a hook fix (such as the #2253 console-window launcher)
keep running the old script while `graphify hook status` says
"installed". Status now compares the installed graphify block with the
current template, ignoring the two per-install values (the pinned
interpreter and the viz limit export), and tells the user to run
`graphify hook install`, which already rewrites the block in place.

(cherry picked from commit 2e43d53ffa)
2026-09-30 20:41:10 +01:00
breken df9f37612f fix(hooks): resolve the output dir from GRAPHIFY_OUT in the sh gates, not a literal graphify-out/
#1423 moved the Python rebuild bodies onto GRAPHIFY_OUT, but the shell that
runs BEFORE them still hardcoded graphify-out/. With the documented override
(#686, e.g. GRAPHIFY_OUT=custom-out for worktree/shared-output setups):

- post-checkout exited at `[ ! -d "graphify-out" ]`, so a branch switch never
  launched the rebuild - a silent no-op with nothing in the log.
- post-commit's "only graph artifacts changed" filter only recognised
  graphify-out/ paths, so a commit touching only custom-out/graph.json
  launched a full rebuild - the loop the filter exists to prevent.

Both gates now read ${GRAPHIFY_OUT:-graphify-out}, the same source the rebuild
body reads. The commit filter is a POSIX case loop instead of grep so the dir
name is matched literally (no regex metacharacters); the leading ( on each
pattern keeps bash 3.2 (macOS /bin/sh) from mis-parsing the pattern's ) inside
the $(...) substitution.

Regression: tests/test_hooks.py runs the emitted scripts under sh with the
launcher stubbed and asserts whether the hook reaches the launch line.

Claude-Session: https://claude.ai/code/session_0134ujLF81GyXsCByibLcYsz
(cherry picked from commit 9efd052de4)
2026-09-14 18:49:21 +01:00
ayushcodes10 7a09d9ae57 Catch symlink loop RuntimeError in the root guard
Path.resolve() raises RuntimeError, not OSError, when a symlink chain
loops back on itself, so the #3265 in repo check let that propagate
up instead of falling back to the repo top like every other bad
marker value. Since a fork or PR can commit an actual symlink loop
alongside a crafted .graphify_root value, an untrusted checkout could
still turn the guard itself into a failure path. Both rebuild bodies
now catch RuntimeError alongside OSError. Found by the graphify
review bot on PR 3492.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 96d48c76b5)
2026-09-11 23:58:32 +01:00
ayushcodes10 e581565e95 Add regression tests for the git hook root guard
Executes the shipped snippet directly (extracted from the rebuild
body text) rather than a hand copy that could quietly drift from it,
covering both the rejected case (a marker pointing outside the repo)
and the case the guard must not break (a subdirectory scoped root).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 5976f7122a)
2026-09-11 23:58:32 +01:00
ktsang622 d9e094c2e2 fix: label via an installed claude CLI, and stop pinning a path that expires
Two independent failures, both silent, found while repairing 16 repositories whose
graphs had quietly stopped tracking their code.

1. `graphify label` downgraded graphs while reporting success

`detect_backend()` is key-based, and `claude-cli` is the one backend with no API key
to find — `_get_backend_api_key` can never return one for it, and the fallback loop
excludes it by name. Nothing else looked for it. So on a machine with the Claude Code
CLI installed and no API key, labelling announced "no LLM backend configured",
replaced every real community name with a `Community N` placeholder, and exited 0.
Run over an existing graph, that overwrites good names with worse ones and looks like
success; taking the exit code at face value commits the damage.

The fallback is placed in the labelling path, NOT in `detect_backend()`. Widening
detection itself was the first attempt and it broke
`test_mixed_repo_without_key_errors_and_points_at_code_only`: extraction deliberately
refuses to run without a configured backend and points at `--code-only`, and on any
machine with the CLI present that contract would have silently changed to "shell out
to the CLI instead". That contract is unchanged here, and a test now pins it.

2. The post-commit hook pinned a path with an expiry date

`_pinned_python()` wrote `sys.executable` verbatim. graphify installed from inside a
snap-confined editor lives under `~/snap/<app>/<revision>/`, and snap swaps that
revision on update and prunes the old tree. Every hook pinned to it then dies —
observed across 15 repositories at once when an editor snap moved past revision 259.
Each commit printed "could not locate a Python with graphify installed" and the graphs
stopped following the code, with nothing failing loudly enough to notice.

Two changes: `_pinned_python()` declines to pin a path under a rotating revision
directory, which its docstring already describes as safe degradation; and the
uv-tools probe — which already scans tool environments — additionally scans
snap-confined HOMEs by glob, since an install made inside a snap lands in that snap's
private HOME and the plain `$HOME` roots never see it once the hook runs from an
ordinary shell.

Verified: same 25 pre-existing failures before and after (unrelated: terraform
fixtures, a TS scaling test); passing goes 5110 -> 5119, the nine tests added here.
Each change was mutation-tested — removing it fails a test that the others still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c6065e24ea)
2026-09-10 22:44:40 +01:00
ayushcodes10 4a7653b609 Kill workers inside the SIGALRM handler too, before it raises
The no-SIGALRM fallback killing workers before os._exit does not
cover the primary path: on POSIX, where SIGALRM is available, a
TimeoutError raised while the main thread is waiting inside the
ProcessPoolExecutor with-block propagates straight through that
block's own __exit__, which calls shutdown(wait=True) and blocks
until every worker exits. Forever, for a worker stuck in a C-level
call the alarm firing does nothing to stop, the exact shape #3341
was. The except TimeoutError handler that would call sys.exit is
never reached, so the configured timeout bounds nothing in that case.

Reproduced directly: a worker in an unconditional loop, a SIGALRM
after 2s, and the process was still running 15s later.

Kills any live multiprocessing.active_children() inside the signal
handler itself, before it raises, so by the time the exception
reaches shutdown(wait=True) there is nothing left to wait for. Moved
the handler from an inline lambda (which cannot contain a statement,
only expressions) to a named function, matching the no-SIGALRM
fallback's own style.

Fixes #3396.

(cherry picked from commit ed948388db)
2026-09-07 18:42:57 +01:00
ayushcodes10 2226fd91b7 Kill orphaned workers before the rebuild watchdog exits
os._exit(1) terminates only the rebuild process itself, not any
ProcessPoolExecutor worker it spawned for a large corpus. It skips
every cleanup path, including the pool's own context-manager
shutdown, so a worker still running at the moment the watchdog fires
is orphaned, reparented to PID 1 on POSIX, and keeps going
unsupervised for as long as whatever it was doing takes. A worker
stuck in catastrophic regex backtracking, the exact shape #3341
fixed, was observed surviving 2.5 days that way on macOS.

extract.py already documents this risk for its own 1 worker special
case, but that only covers max_workers == 1, not a real multi worker
pool.

The watchdog fires on a separate timer thread with no reference to
the pool object, but multiprocessing.active_children() enumerates
every live worker process regardless of which thread asks, so the
fallback kills them before exiting itself. Kill, not terminate: a
worker stuck in a C level call like regex backtracking never gets a
chance to act on a signal a Python level handler would need to catch.

Fixes #3396.

(cherry picked from commit 319869e6c1)
2026-09-07 18:42:57 +01:00
abhay-codes07 b09c839eb3 fix(hooks): a graphify skip must not terminate the whole git hook (#2986)
The generated post-commit / post-checkout block is appended to whatever
hook a repo already has, and other tools chain their logic after it. Every
skip condition in the block was a bare `exit 0`, which in a flat sh script
ends the ENTIRE hook process — so anything after graphify's end marker
was silently dropped on every root commit (HEAD~1 does not exist), every
rebase/merge/cherry-pick, every linked worktree, every GRAPHIFY_SKIP_HOOK=1
and whenever no graphify-capable python could be found.

Both bodies now run inside a subshell `( ... )`, so an `exit` ends only
graphify's section: no rewriting of the seventeen skip sites, no
`return`-outside-a-function hazard for the _PYTHON_DETECT fragment that
tests run standalone, the detached rebuild launch unaffected, and the
hook's own exit status 0 as before. The markers stay outside the subshell
so reinstall/uninstall/status keep finding the block.

(cherry picked from commit 6f09919f10)
2026-09-06 16:17:37 +01:00
rohit-jsfreaky 3c448660cd fix(windows): stop a PowerShell BOM breaking hook rebuilds (#3028)
Windows PowerShell 5.1 writes a UTF-8 BOM, so the .graphify_root marker it produced carried
a leading BOM that Python's utf-8 decode kept as U+FEFF; .strip() left it in, breaking the
post-commit rebuild (WinError 123) and silently mis-rooting _infer_merge_root. Write the
marker BOM-less from PowerShell (WriteAllText + UTF8Encoding $false) and read the marker
with utf-8-sig at the reader sites so already-broken installs recover too.
2026-08-25 18:06:46 +01:00
santhiprakash 9452ef216b fix(hooks): resolve the uv tool venv when pin and launcher probes miss (#2852)
Installed via `uv tool install`, the post-commit hook could not find graphify: the pin and
launcher/shebang probes miss the uv tool venv, so the hook silently exit-0'd. Add a fourth
fallback probe that scans $UV_TOOL_DIR and the conventional uv tool locations for a python
whose env has graphify importable, firing only after the earlier probes miss and verifying
importability before adopting. Also gate the launcher read on a real #! header so a binary
trampoline is not misparsed as a shebang.
2026-08-21 18:26:11 +01:00
safishamsiandClaude Opus 4.8 355b6628b4 test(hooks): replace static string-match with a runtime same-head test (#2421)
The PR's test only asserted the guard line exists in the source — the exact
static-assertion anti-pattern #2126/#2641 removed for providing zero coverage.
Replace it with a runtime test that runs the real emitted post-checkout script
under sh up to the guard (with a sentinel, before the launch): same-head skips,
different-head falls through, file-checkout skips at the earlier guard. Adds the
CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:53:43 +01:00
nothariharanandClaude Opus 4.8 5c88af7617 fix(hooks): skip post-checkout rebuild when PREV_HEAD equals NEW_HEAD (#2421)
git checkout -b <new> reports a branch switch (flag=1) but passes the same SHA
for prev and new HEAD, so the post-checkout hook fired a full changed_paths-less
rebuild — the most expensive graphify op — on essentially every new branch. Add
a POSIX-sh guard that exits 0 when PREV_HEAD == NEW_HEAD; a real branch switch
(PREV != NEW) still rebuilds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-17 17:47:56 +01:00
safishamsiandClaude Opus 4.8 c389b3ee75 fix(hooks): baked viz limit must yield to a per-run override; status resilient (#2760)
Two fixes on top of #2768: (1) the baked line was an unconditional
export GRAPHIFY_VIZ_NODE_LIMIT="<n>", which clobbered an explicit
GRAPHIFY_VIZ_NODE_LIMIT=... git commit; use the ${VAR:-<n>} default form
(mirroring GRAPHIFY_MAX_WORKERS) so the per-run value wins, and widen the
status drift regex to read the new form (still accepting the old bare one).
(2) A malformed .graphifyrc turned the read-only hook status into a traceback;
status now catches the parse error, warns, and continues. Adds precedence and
malformed-status tests. Also adds the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 22:00:53 +01:00
hopstreaxandClaude Opus 4.8 dc895a2f7c fix(hooks): persist viz node limit in project config (#2760)
Adds a committed .graphifyrc (viz_node_limit=<int>) that `graphify hook install`
reads and bakes into the generated post-commit/post-checkout hooks, so a
project-wide viz node limit is shared via version control and survives hook
regeneration instead of needing a hand-edit that the next --force clobbers.
`hook status` reports the value and flags drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-15 21:55:47 +01:00
rajashidattapy 243f3e32c1 test(hooks): stop argv mangling the shell-arg verdict test on Windows (#2126) 2026-08-12 20:56:28 +01:00
himanshupatro-334 952b477a3f test(hooks): update comment to refer to #2253 2026-07-28 09:37:39 +01:00
himanshupatro-334 3bac3df666 fix(hooks): replace DETACHED_PROCESS with CREATE_NO_WINDOW on Windows 2026-07-28 09:37:39 +01:00
Souptik Chakraborty cfe15f9161 fix: pin interpreters whose path contains a space (#2166)
`graphify hook install` emitted `_PINNED=''` for some Windows uv-tool installs,
so every interpreter probe failed, each commit printed "could not locate a
Python with graphify installed" and the graph never rebuilt.

Root cause is the install-time allowlist in `_pinned_python()`, not the uv
layout: it accepted `[a-zA-Z0-9/_.@:\-]` but not a plain space, so any
`sys.executable` under a profile whose name contains one -- `C:\Users\First
Last\AppData\Roaming\uv\tools\graphifyy\Scripts\python.exe`, or the equally
common `C:\Program Files\Python312\python.exe` -- was rejected wholesale and
nothing was recorded. A space-free Windows uv path pins correctly, which is why
this looks layout-specific.

A space is safe to allow because every consumer already quotes the value: the
hook scripts embed it as `_PINNED='<path>'` and dereference `"$_PINNED"`, so a
space can neither split a word nor start a command. Adding it to the allowlist
therefore fixes the pin without weakening the injection guard -- `$`, backtick,
`;`, `'` and `"` are all still rejected.

`_register_merge_driver` did interpolate the path unquoted into the
`merge.graphify.driver` command, which git runs through a shell; that would
split a spaced path into two words, so it is now double-quoted. Double quotes
are safe here precisely because the allowlist keeps `$` and backticks out.

Tests: spaced Windows/POSIX paths are pinned; metacharacter paths are still
rejected (including `'` and `"`); the merge driver quotes a spaced interpreter;
and the installed post-commit/post-checkout hooks carry the real path rather
than `_PINNED=''`.
2026-07-26 11:08:10 +01:00
Rishet Mehra 0da6929e57 fix(hooks): match the post-checkout log prefix in the timeout fallback
The post-checkout body logs with [graphify] throughout, but the new
non-SIGALRM fallback used [graphify hook], so the same timeout read
differently depending on the platform. The post-commit body does use
[graphify hook] everywhere, so only the checkout copy was wrong.

Assert each body uses a single log prefix so this cannot drift again.
2026-07-24 23:37:14 +01:00
Rishet Mehra ca3113ac7b fix(hooks): arm the rebuild timeout without SIGALRM on Windows
The GRAPHIFY_REBUILD_TIMEOUT watchdog in both embedded rebuild bodies was
guarded by hasattr(signal, 'SIGALRM') with no else branch, so on Windows it
never armed and a hung rebuild survived indefinitely -- the exact tail case
the #791 timeout was added to catch.

Fall back to a daemon threading.Timer that prints the same message and calls
os._exit(1). The hard exit is deliberate: the process is already stuck, so a
clean shutdown may itself be blocked, and _rebuild_lock degrades to a no-op
yield on platforms without fcntl, so there is no lock to leave stale.
2026-07-24 23:37:14 +01:00
Rishet Mehra ce9ea7d7b4 test(hooks): surface bash failures in _shell_verdict helper
Assert returncode == 0 so a malformed case snippet fails fast with
stderr instead of silently returning an empty string. Addresses
Copilot review feedback on #2133.
2026-07-24 23:37:14 +01:00
Rishet Mehra e1e041b09b fix(hooks): accept Windows backslash paths in interpreter allowlist (#2126)
The post-commit/post-checkout hook's interpreter-detection allowlist
silently rejected valid Windows paths (C:\...\python.exe) on Git-Bash.
bash treats a lone backslash inside [...] as an escape that consumes
itself, so the emitted glob never matched a real backslash at runtime,
even though the pattern looked correct in Python's install-time re dialect.

Fix both allowlists (.graphify_python file path and shebang-parsed
launcher path) to emit [!a-zA-Z0-9/_.@:\\-], a doubled-backslash form
verified against bash and dash: it accepts Windows paths and still
rejects ; ` $ injection. The shebang allowlist additionally lacked :
and backslash entirely. Install-time _pinned_python() re is left as-is.

Add shell-runtime tests that execute the emitted case/esac glob directly
against Windows paths and shell metacharacters.
2026-07-24 23:37:14 +01:00
safishamsiandClaude Opus 4.8 f2a6139667 Fix spurious hooksPath warning and register graph.json merge driver
Fix #1907: _hooks_dir parsed .git/config with a strict configparser,
which rejects git-legal duplicate keys/sections (VS Code writes such
configs) and printed a spurious "could not read core.hooksPath" warning
on every hook command. Ask git itself (rev-parse --git-path hooks) as
the primary resolution instead; it handles core.hooksPath, includeIf
and linked worktrees, keeps the Windows-path and multiline/NUL guards,
and surfaces git's own stderr when the config is genuinely corrupt.
The .git/hooks final fallback is unchanged.

Fix #1902: hook install never registered the graph.json union merge
driver that README and CHANGELOG 0.7.0 document. install() now sets
merge.graphify.name/driver via git config (pinning sys.executable with
the same shell-safe allowlist the hook scripts use, falling back to the
graphify launcher) and idempotently appends the graph.json
merge=graphify line to .gitattributes without clobbering other entries.
uninstall() mirrors the removal, and status()/install() report the
merge-driver state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:41:36 +01:00
5f57c46b92 fix(detect,hooks): honor .git/info/exclude; guard worktrees (#1810, #1809)
#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>
2026-07-12 16:15:24 +01:00
JimandClaude Fable 5 1256d65214 perf(hooks): eliminate multi-second foreground stalls before the detached launch
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>
2026-07-02 11:50:12 +01:00
Matias Duarte 879c05894d fix(hooks): limit Windows hook rebuild workers 2026-07-01 10:47:10 +01:00
safishamsiandClaude Opus 4.8 d168de999a Relativize hyperedge source_file and honour GRAPHIFY_OUT everywhere (#1418, #1423)
#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>
2026-06-22 17:02:29 +01:00
SafiandClaude Opus 4.8 a78956424d Reject Windows-style git hooks paths instead of creating a junk dir (#1385)
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>
2026-06-19 10:06:07 +01:00
SafiandClaude Sonnet 4.6 6a549e42d5 fix: four bugs — affected direction, hook root, glob fish/zsh, manifest drift (#1174 #1173 #1172 #1163)
#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>
2026-06-07 12:40:44 +01:00
SafiandClaude Sonnet 4.6 a8dbbe59cf fix: land PRs #1170 #1169 #1165 (hooks, sensitive filter, score_nodes)
#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>
2026-06-07 12:20:36 +01:00
SafiandClaude Sonnet 4.6 0fdfdedcaa harden hook interpreter detection against injection and unquoted exec
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>
2026-06-03 15:40:06 +01:00
SafiandClaude Opus 4.8 88bb1864aa fix hook install silent no-op for uv tool and pipx installs (#1127)
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>
2026-06-03 15:40:06 +01:00
balloon72 44638dd424 test(hooks): cover old git hook path output (#910) 2026-05-18 12:04:49 +01:00
SafiandClaude Sonnet 4.6 cf7ce3a450 fix #651: hook-check exits silently instead of emitting unsupported additionalContext
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>
2026-05-02 17:29:40 +01:00
Safi e4bdcc2487 add tests for canvas vault-relative paths and hook .exe skip 2026-04-22 23:04:40 +01:00
SafiandClaude Sonnet 4.6 1fbcaf840f release: v0.4.9 — PHP extractor improvements, Dart, diacritics, Hermes, fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 08:39:28 +01:00
Safi c46a4ebb68 README: fix CI badge to v2, update curl URL, add how it works, graphify claude install section 2026-04-06 16:06:31 +01:00
Safi 317d73f8f7 v2: confidence scores, hyperedges, rationale extraction, git hooks, Claude Code hooks
- confidence_score required on every edge (INFERRED: 0.4-0.9, EXTRACTED: 1.0, AMBIGUOUS: 0.1-0.3)
- semantically_similar_to edges for non-obvious cross-file conceptual links
- hyperedges for 3+ node group relationships - fixed cache and merge pipeline that was silently dropping them
- check_semantic_cache returns 4-tuple including cached_hyperedges
- extract.py: mine the "why" - module/class/function docstrings and rationale comments (# NOTE: # IMPORTANT: # HACK: # WHY: # RATIONALE: # TODO: # FIXME:) as rationale_for nodes
- skill.md: rationale_for in relation schema, doc files extract design rationale
- obsidian output opt-in (--obsidian flag) - default output is graph.html + graph.json + GRAPH_REPORT.md only
- hooks.py: post-checkout hook added alongside post-commit - graph rebuilds on branch switch
- claude install: writes .claude/settings.json PreToolUse hook on Glob/Grep - Claude checks graph before searching raw files
- README updated with all v2 features
2026-04-06 16:06:31 +01:00
Safi 66f1f40de8 add git commit hook - auto-rebuilds graph after every commit 2026-04-06 16:06:31 +01:00