Skip to content

Ship a PreToolUse hook with the plugin: gate Edit/Write on Lien-indexed files until impact analysis has been done #560

Description

@alfhen

Context

The MCP-server instructions block already declares:

REQUIRED before Edit/Write on any file: get_files_context({ filepaths })
REQUIRED before renaming/removing/changing the signature of any exported symbol: get_dependents({ filepath, symbol })

In practice these are advisory — the model frequently goes straight to Edit/Write. A PreToolUse hook shipped with the plugin converts the "REQUIRED" wording into an actual gate, with zero per-user ~/.claude/settings.json wiring.

The intent is "did you do impact analysis on this file?", not "did you call this specific tool?" — three Lien tools can satisfy it.

Proposal

Ship a plugin-level hook bundle (PreToolUse + PostToolUse + SessionStart) that:

  1. On Edit/Write/MultiEdit, reads tool_input.file_path and session_id from stdin.
  2. Skips if extension is outside Lien's indexed-extension set (no friction on .md/.yml/.json).
  3. Looks up a session sentinel under ~/.lien/indices/<repoId>/gate-sessions/<session_id>/:
    • existing-file edit: any of fc-<hash> / dep-<hash> / fs-<hash> for that path
    • new-file write: any recent fs-* (loose by design)
  4. Applies a TTL (default 60 min) so stale sentinels don't satisfy the gate.
  5. If unsatisfied → emits systemMessage naming the call that would satisfy it; advisory by default (exit 0 + stderr), blocking opt-in via plugin setting.

PostToolUse matched against mcp__plugin_lien_lien__(get_files_context|get_dependents|find_similar) writes the sentinels. SessionStart garbage-collects any gate-sessions/<id>/ whose id ≠ the current one.

Decisions

  • Storage path~/.lien/indices/<repoId>/gate-sessions/<session_id>/. repoId derives deterministically via extractRepoId(cwd) from @liendev/parser (<basename>-<md5(path)[:8]>). Reuses the same path the vectorDB already creates. Will add lien path --store (one-line subcommand) so the shell script doesn't reimplement repoId derivation.
  • Scope — per-repo, keyed off cwd. Matches index scope; session sub-dirs avoid cross-session bleed.
  • Default modeadvisory (exit 0, systemMessage only). Blocking is one config flag away. Rationale: a noisy first release will get the plugin uninstalled; advisory still reaches the model via systemMessage.
  • OverrideLIEN_GATE=off env var (one-shot for the next call) plus a /lien-gate off slash command shipped with the plugin for the duration of the session. No magic comments (fragile).
  • v2 deferred — symbol-level demand on Edit (get_dependents for exported identifiers in old_str); tightening of find_similar looseness for new files.

v1 scope (PR-ready)

  • plugins/claude/hooks/hooks.json registering PreToolUse (matcher Edit|Write|MultiEdit), PostToolUse (matcher mcp__plugin_lien_lien__(get_files_context|get_dependents|find_similar)), SessionStart.
  • plugins/claude/hooks/gate.sh — PreToolUse logic; reads tool_input.file_path + session_id; respects LIEN_GATE=off.
  • plugins/claude/hooks/sentinel.sh — PostToolUse; parses tool_input (and for find_similar, scans tool_result JSON for cited filepaths).
  • plugins/claude/hooks/session-clean.sh — SessionStart; removes stale gate-sessions/<id>/.
  • lien path --store and lien path --extensions CLI subcommands.

Verified against installed-plugin examples

  • Plugins can ship hooks (hooks key in plugin.json or hooks/hooks.json wrapper). ${CLAUDE_PLUGIN_ROOT} resolves at runtime.
  • SessionStart/SessionEnd are both valid events.
  • PreToolUse payload contains session_id, cwd, tool_name, tool_input.file_path.
  • PostToolUse matchers handle mcp__* regex (see security-guidance and hookify plugins).

Simpler alternatives considered

  • Sharper MCP instructions — already tried; model still skips. Rejected as standalone, kept as a parallel improvement.
  • Status-line nag — visible to the human, not the model. Doesn't change behavior on the next tool call. Useful adjacent; not a substitute.
  • Stop hook reminder — fires too late; the edits already shipped.

Risks & escape hatches

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions