Skip to content

feat(antigravity): strict hook-guard gate for the headless agy CLI - #3293

Open
logan683 wants to merge 14 commits into
Graphify-Labs:v8from
logan683:pr/agy-hook-guard
Open

feat(antigravity): strict hook-guard gate for the headless agy CLI#3293
logan683 wants to merge 14 commits into
Graphify-Labs:v8from
logan683:pr/agy-hook-guard

Conversation

@logan683

@logan683 logan683 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3291 (which is stacked on #3281); diff against v8 includes both until they merge.

Problem

graphify antigravity install writes .agents/rules/graphify.md and a workflow. Measured on the Antigravity CLI (agy -p, headless):

  • workspace .agents/rules and AGENTS.md are never loaded (probe rules with unique tokens in a plain and a git init workspace returned nothing; the same rules under ~/.gemini/config/ returned their tokens);
  • an always-on graph rule that does load is advice only: claude-sonnet-4-6 and gpt-oss-120b-medium both made grep_search their first call on a neutral question, in three rule placements;
  • hook payloads carry workspacePaths: [], and hooks run from the hooks.json directory, so a gate keyed on workspace or cwd sees nothing.

Fix

  • hook-guard agy — PreToolUse + PreInvocation handler for agy's camelCase payload. call_mcp_toolgraphify writes a per-conversation marker (~/.graphify/agy_sessions, same TTL and identity hashing as the Claude gate). grep_search, find_by_name, list_dir, or run_command running a recursive search (grep -r, rg, find, Get-ChildItem -Recurse, Select-String -Path <dir\*>, including inside powershell -Command "…") are denied with a reason naming the exact call until the marker exists. Workspace = workspacePaths[0] when sent, else the call's own target path walked up to graphify-out/graph.json. view_file, bounded searches, other MCP servers, no conversationId, no graph, malformed stdin → allow. Fails open.
  • graphify antigravity install --strict merges a graphify-graph-first entry into ~/.gemini/config/hooks.json (module form of the pinned interpreter — a quoted script path after -File breaks under agy's cmd /c launch); antigravity uninstall removes only that entry.

Evidence

  • tests/test_hook_agy.py: 11 cases — RED on 968715a, GREEN after; 248 passed across the hook/install suites; tools.skillgen --check 134 OK; ruff clean.
  • Live, with the PowerShell twin of this gate installed on the author's machine and the same neutral question: gpt-oss-120b-medium (conv 4a618532) find_by_name denied → call_mcp_tool graphify query_graph → allowed; claude-sonnet-4-6 (conv 1df5adbc) grep_search denied → "The graph-first rule requires a graphify query first. Let me comply" → query → allowed. Before the gate, eight runs across three rule placements all grepped first.

Not covered

  • Gemini tiers (usage unavailable during measurement).
  • IDE (non-headless) Antigravity: workspacePaths is presumably populated there; the gate uses it when present, unmeasured.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy

logan683 and others added 14 commits September 2, 2026 03:23
…se hooks

The generated hook resolved its command via shutil.which("graphify"), which
returns the console-script shim. For uv-tool and pipx installs that shim is
unsigned; on Windows with Smart App Control enabled, code-integrity policy
refuses to load it, so the hook can never run. Both ~/.local/bin/graphify.exe
and the venv Scripts/graphify.exe are NotSigned, so resolving to a different
shim directory does not help.

The hook also had no failure guard. A PreToolUse hook that exits non-zero
blocks the tool call it was meant to advise, so an advisory graph check could
break the host on any launcher failure.

Resolve to '<sys.executable> -m graphify' via the existing hooks._pinned_python(),
the same reasoning already applied to git hooks, and make both the POSIX and
Windows commands fail open with a timeout.

The Graphify-Labs#2165 regression parses the subcommand out of the hook command; it now
handles both supported grammars rather than assuming the subcommand is the
second token, so it keeps proving the same contract.

Refs Graphify-Labs#3280
The generated Windows command may be run by cmd.exe or by PowerShell, and no
bare fail-open suffix is correct in both. `& exit /b 0` is cmd syntax; run by
PowerShell the bare `&` is the background operator, so the hook spawns a job
and exits 1 - the fail-open suffix caused the very failure it was added to
prevent (Graphify-Labs#3280). `; exit 0` has the mirror problem: valid in PowerShell,
rejected by cmd.exe.

Naming the interpreter removes the ambiguity. Measured on Windows, the
generated string now exits 0 under both pwsh and cmd.exe; the previous form
exited 1 under pwsh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPaGbwQeyWLwbVm9uRgHtU
Strict Claude hooks recorded no query evidence after the graph-wide stamp
was removed: the Read deny fired once per session unconditionally and the
retry was unconditional, and Bash/Grep search was nudge-only by design. An
agent instructed to read and search through Bash never met a block, and a
nudge that costs nothing is learned in one turn to cost nothing.

- PostToolUse `hook-guard mark-queried` writes cache/hook_sessions/<sid>
  [--agent-<id>].queried after mcp__graphify__{query_graph,get_node,
  get_neighbors,get_community,god_nodes,shortest_path} or a Bash command
  that runs `-m graphify query|explain|path` (project sidecar form included).
- `hook-guard search --strict` denies a recursive in-project corpus search
  (grep -r/-R, rg, find, fd, ack, ag, or the Grep tool on a directory) until
  that marker exists. Not once-per-session: the way out is a query, not a
  retry. Exact-file grep, stdin grep, git grep, out-of-project targets, Glob,
  soft mode, no session_id, malformed stdin and GRAPHIFY_HOOK_STRICT=0 never
  block. Deny text is constant; parsing never executes the command.
- Quoted spans are protected before the operator split so a regex with
  parens or a "$(cat graphify-out/.graphify_python)" interpreter cannot
  fragment its segment.
- install: --strict now applies to Bash|Grep; the PostToolUse marker hook is
  registered and removed with the others.

Tests: tests/test_search_strict.py (RED on 2a83725: 12 failed / 5 passed;
GREEN: 301 passed across the hook/install suites), skillgen --check 134 OK,
ruff clean. Live proof: a fresh `claude -p --model claude-opus-5` session
in a scratch project with the candidate hooks had its second tool call
(grep -rn ... .) denied, queried the graph next, and was then allowed.

Stacked on Graphify-Labs#3281 (pr/hook-fail-open); follow-up to Graphify-Labs#3280.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy
Round 1 review findings on the strict search gate:

- F1: a Bash-only session never reached the 24-hour marker sweep that lived
  only inside _mark_session_denied, so .queried markers accumulated and a
  reused session id stayed pre-authorized. Both writers now call one shared
  _gc_session_markers.
- F2: Claude Code on Windows exposes a PowerShell tool (tool_name
  "PowerShell", tool_input.command; PreToolUse:PowerShell hook events were
  measured in a live transcript). Its recursive in-project searches now reach
  the same guard: matcher Bash|Grep|PowerShell, and two measured shapes are
  denied until query evidence exists — `Get-ChildItem|gci|ls|dir ... -Recurse
  ... | Select-String|sls` and `Select-String ... -Path <dir or dir\*>`.
  Exact-file -Path, Get-Content | Select-String and out-of-project targets
  only nudge; non-search PowerShell stays silent.

Tests: 5 RED on a0a4f1d, 306 passed after; skillgen --check 134 OK; ruff clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy
…ll listing

Round 3 review findings:

- F1: _session_has_queried now enforces the 24-hour TTL itself and best-effort
  unlinks an expired marker, so a stale .queried never pre-authorizes a reused
  session id while no writer happens to sweep. One _SESSION_MARKER_TTL shared
  with the GC.
- F2: a recursive PowerShell listing without a Select-String pipe
  (Get-ChildItem|gci|ls|dir ... -Recurse) is the same corpus-enumeration class
  as `find .` and now denies in-project, nudges out-of-project, and stays
  silent when not recursive.

Tests: 2 RED on 8d04a72, 326 passed after; skillgen --check 134 OK; ruff clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy
Measured on Google Antigravity CLI (agy -p): global rules load but are advice
only — claude-sonnet-4-6 and gpt-oss-120b both grep the tree first with an
always_on graph rule active — workspace .agents/rules never load headless, and
hook payloads carry workspacePaths: []. `graphify antigravity install` therefore
changed nothing for the CLI.

- `hook-guard agy`: PreToolUse + PreInvocation handler for agy's camelCase
  payload. call_mcp_tool -> graphify writes a per-conversation marker
  (~/.graphify/agy_sessions, TTL shared with the Claude gate); grep_search,
  find_by_name, list_dir, or run_command running a recursive search
  (including `powershell -Command "..."` payloads) are denied until it exists.
  Workspace = workspacePaths[0] when sent, else the call's own target path
  walked up to graphify-out/graph.json; never cwd (agy runs hooks from the
  hooks.json directory). PreInvocation injects the same instruction while the
  workspace is known and unqueried. Fails open.
- `graphify antigravity install --strict` merges a `graphify-graph-first`
  entry into ~/.gemini/config/hooks.json using the pinned interpreter in
  module form (a quoted script path after -File breaks under agy's cmd /c);
  `antigravity uninstall` removes only that entry.
- `_ps_recursive_search_targets`: `-Path dir\*.md` names the directory.

Tests: tests/test_hook_agy.py (11; RED on 968715a, GREEN after), 248 passed
across the hook/install suites; skillgen --check 134 OK; ruff clean.
Live: the PowerShell twin of this gate, installed on the author's machine,
forced both models to query first (conversations 4a618532, 1df5adbc).

Stacked on Graphify-Labs#3291.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy
…separator

On POSIX a literal backslash is part of a file name, so the fabricated
"<ws>\*.md" climbed to the pytest dir and the gate correctly allowed;
CI on Linux failed while Windows passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 2 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_claude\_pretooluse\_hooks changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_claude\_pretooluse\_hooks behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"strict":"True","project":"True"\}, the old code produced \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search'\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guar… but the new code produces \[\{'matcher': 'Bash\|Grep\|PowerShell', 'hooks': \[\{'type': 'command', 'command': '"$\(cat graphify\-out/\.graphify\_python\)" \-m graphify hook\-guard search \-\-strict \|\| true', 'commandWindows…. Paste that input straight into a regression test.

Behavior changes: \_resolve\_graphify\_exe changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_resolve\_graphify\_exe behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"project":"True"\}, the old code produced 'graphify' but the new code produces '"$\(cat graphify\-out/\.graphify\_python\)" \-m graphify'. Paste that input straight into a regression test.


Graphify review — findings

Adds a graphify antigravity install --strict mode that registers an agy hook-guard gate in ~/.gemini/config/hooks.json (PreToolUse + PreInvocation), deriving the graph root from each call's own target path—since the headless Antigravity CLI loads rules as advice only and sends empty workspace paths—and denying grep_search, find_by_name, list_dir, and recursive run_command searches until the conversation has queried the graph. Extends strict Claude hooks to block a first recursive in-project search (grep -r, rg, find, the Grep tool on a directory, or PowerShell Get-ChildItem -Recurse | Select-String) until the session records one graph traversal via a PostToolUse mark-queried marker keyed per session_id and agent_id; both marker writers GC entries older than 24h, and exact-file grep, stdin grep, git grep, out-of-project paths, Glob, and soft mode never block. Fixes the per-agent guard leak where one graph-wide 30-minute stamp let a parent's first read disable every subagent's block, and reroutes generated agent guidance and project hooks to prefer the MCP query_graph tool with the graph's recorded interpreter (graphify-out/.graphify_python) as CLI fallback, failing open when that sidecar is absent so hardened Windows hosts avoid the unsigned graphify.exe shim.

Worth a look

  • Command substitution inside double quotes bypasses search detectiongraphify/cli.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Always-on instructions execute an interpreter path read from the workspacegraphify/always_on/gemini-md.md:6 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Always-on instructions execute an interpreter path read from the workspacegraphify/always_on/kiro-steering.md:5 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Always-on instructions execute an interpreter path read from the workspacegraphify/always_on/vscode-instructions.md:5 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Double-quoted command substitutions are ignoredgraphify/cli.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2343 functions depend on the 2165 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 122 callees
  • new: codebuddy_install() — 20 callers, 5 callees
  • new: _run_hook_guard() — 6 callers, 15 callees
  • new: claude_install() — 19 callers, 4 callees
  • new: gemini_install() — 10 callers, 7 callees
  • new: claude_uninstall() — 17 callers, 4 callees
  • new: _project_uninstall() — 5 callers, 13 callees
  • new: render() — 13 callers, 5 callees
  • …and 23 more — each is listed as a finding

Verification — 2343 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2289 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_claude\_pretooluse\_hooks changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_claude\_pretooluse\_hooks behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"strict":"True","project":"True"\}, the old code produced \[\{'matcher': 'Bash\|Grep', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guard search'\}\]\}, \{'matcher': 'Read\|Glob', 'hooks': \[\{'type': 'command', 'command': 'graphify hook\-guar… but the new code produces \[\{'matcher': 'Bash\|Grep\|PowerShell', 'hooks': \[\{'type': 'command', 'command': '"$\(cat graphify\-out/\.graphify\_python\)" \-m graphify hook\-guard search \-\-strict \|\| true', 'commandWindows…. Paste that input straight into a regression test.

Behavior changes: \_resolve\_graphify\_exe changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_resolve\_graphify\_exe behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"project":"True"\}, the old code produced 'graphify' but the new code produces '"$\(cat graphify\-out/\.graphify\_python\)" \-m graphify'. Paste that input straight into a regression test.

No difference found (not proven): No behavior difference found in \_bash\_invokes\_search (not a proof).

The verifier ran both versions of \_bash\_invokes\_search on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

No difference found (not proven): No behavior difference found in \_mark\_session\_denied (not a proof).

The verifier ran both versions of \_mark\_session\_denied on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

No difference found (not proven): No behavior difference found in \_run\_hook\_guard (not a proof).

The verifier ran both versions of \_run\_hook\_guard on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_antigravity\_install.

The verifier did not have enough to check \_antigravity\_install, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_antigravity\_uninstall.

The verifier did not have enough to check \_antigravity\_uninstall, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify claude\_install.

The verifier did not have enough to check claude\_install, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 30 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)

No difference found (not proven): No behavior difference found in dispatch\_install\_cli (not a proof).

The verifier ran both versions of dispatch\_install\_cli on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_install\_claude\_hook.

The verifier did not have enough to check \_install\_claude\_hook, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_install\_codebuddy\_hook.

The verifier did not have enough to check \_install\_codebuddy\_hook, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_install\_codex\_hook.

The verifier did not have enough to check \_install\_codex\_hook, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_print\_install\_usage (not a proof).

The verifier ran both versions of \_print\_install\_usage on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_strip\_graphify\_hook.

The verifier did not have enough to check \_strip\_graphify\_hook, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `settings_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_uninstall\_codebuddy\_hook.

The verifier did not have enough to check \_uninstall\_codebuddy\_hook, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `project_dir` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 grounded finding(s) anchored inline below; 24 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/cli.py
return None


def _run_hook_guard_agy() -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_run_hook_guard_agy()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/cli.py
out({"decision": "allow"})


def _run_hook_guard(kind: str, strict: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_run_hook_guard()

fans out to 15 callees (efferent coupling); 6 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
print(f" {path} -> {_ANTIGRAVITY_HOOK_KEY} hook removed")


def _antigravity_install(project_dir: Path, strict: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_antigravity_install()

high coupling complexity (Ca·Ce = 16).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
'powershell.exe -NoProfile -NonInteractive -Command '
f'"{inner}; exit 0"'
)
def _install_codex_hook(project_dir: Path, project: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_install_codex_hook()

7 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
print("Strict mode: the first raw file read per session is blocked until one")
print("graph traversal runs, and recursive in-project searches stay blocked until")
print("this session has run one (toggle with GRAPHIFY_HOOK_STRICT=0).")
def _install_claude_hook(project_dir: Path, strict: bool = False, project: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_install_claude_hook()

fans out to 6 callees (efferent coupling); 7 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8")
print(f" .claude/{settings_path.name} -> PreToolUse hook removed")
print(f" .claude/{settings_path.name} -> graphify hooks removed")
def uninstall_all(project_dir: Path | None = None, purge: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionuninstall_all()

fans out to 13 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert _is_deny(_invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch))


def test_allows_after_mcp_query(tmp_path, monkeypatch):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_allows_after_mcp_query()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant