vigiles API reference
    Preparing search index...

    Function outcomeWrites

    vigiles/hook — the closed vocabulary for authoring a compiled hook.

    A hook today is opaque shell (bash guard.sh): un-analyzable, and the author hand-writes the fragile parts (exit code, JSON field, a grep matcher) that cause the #1 verified hook pain — FALSE CONFIDENCE (a guardrail that looks like it blocks but silently doesn't). Invert it: author a pure typed function (event) => Decision against THIS surface, and vigiles compile emits the harness protocol for you. The whole false-confidence class becomes UNREPRESENTABLE — you never write the exit code or the field.

    The roles, each with its own output type so a category mistake is a tsc error, not a silent no-op:

    • experimental_defineHook / experimental_defineFileGate — a gate returns a Decision (allow/deny/ask); deny is the only thing that blocks.
    • experimental_definePromptGate — a prompt gate (UserPromptSubmit) sees the prompt TEXT and may deny to block it (a security filter).
    • experimental_defineStopGate — a stop gate (Stop/SubagentStop) may deny to keep the agent going (gate-until-tests-pass).
    • experimental_defineInject — an inject returns an Injection (context text); it has no deny, so "block on a SessionStart hook" won't compile.
    • experimental_defineReact — a react (PostToolUse) returns a Reaction; it sees the tool RESPONSE, its run(cmd) is effect-classified at construction, and it can't block (the tool already ran).

    Every gate takes a mode: enforce (default, blocks) or observe (the shadow/rollout mode — records what it WOULD block, never blocks). observe is harness-neutral (it just exits 0 + writes a local record).

    Matching is AST-backed (command.runs("git push", { force })), so it catches cd x && git push -f that the native Bash(git:*) glob misses.

    A gate may also decide on EXTERNAL STATE by declaring needs (e.g. needs: ["git.branch"]): the trusted runtime gathers those read-only facts and hands them in as e.ctx — the hook still does zero I/O, and reading an undeclared fact is a tsc error. Built-ins: git.branch/git.isDirty/git.root/cwd/os.platform/env.isCI. For a one-off off-catalog fact, the lightweight opt-out is an inline provide(name, cmd) (read-only) or dangerously(name, cmd) (the loud escape) right in needs. See research/hook-context-providers.md.

    ⚠️ Honest scope: compile/verify fix the hook's AUTHORING + LOGIC, not the harness's DELIVERY. The delivery floor MOVED — #34692 (a subagent's tool calls never reaching PreToolUse) is FIXED as of Claude Code 2.1.241, measured against a stock registry install and pinned by src/subagent-delivery.test.ts, which goes red if it regresses. SCOPE of that measurement: headless claude -p only — interactive is unmeasured, and depth-2 subagent nesting does not occur there at all. What has NOT changed: a model can still route around a tool entirely (#45427 / #32376 — a Bash heredoc instead of Write), so a gate is a strong default and is NEVER an unbypassable wall. See docs/compiled-hooks.md.

    • The state writes an outcome declares, filtered to the ones the runtime may actually perform. A gate's Decision carries none — deliberately: a gate is the role that must be trustworthy and runs on every tool call, so it READS state (via needs) and never writes it. Adding a write there later is easy; removing one would not be.

      Parameters

      Returns { ok: readonly StateWrite[]; refused: readonly string[] }