import { type DispatchKind } from "./hook-program.js"; /** Where `compile` writes the start-of-session check. Committed, next to the hook sources. */ export declare const HOOK_CHECK_REF = ".vigiles/hook-check.sh"; /** The CLI verb every compiled hook line starts the runtime with. */ export declare const HOOK_RUNTIME_VERB = "hook-runtime"; /** The runtime kind that runs a compiled hook program; its one operand is the program. */ export declare const RUN_PROGRAM_KIND = "run-program"; /** * The lines `init` wires for a harness with no plugin store (Codex): the * shipped nudges, started through npx. Data, so the plan below knows them as * vigiles's own without reading their text. */ export declare const INIT_NUDGE_COMMANDS: { readonly evalLock: "npx --no-install vigiles hook-runtime eval-lock-nudge"; readonly refs: "npx --no-install vigiles hook-runtime refs"; }; /** * The command line `compile` writes to run a hook program: the runtime started * by `runnerRef`, the program at `programRef`. {@link plannedHookLines} uses it, so a reader compares a wired line with the same string. */ export declare function runProgramCommand(runnerRef: string, programRef: string): string; /** * Where the hook runtime lives, spelled so the shell can find it WITHOUT `npx`. * * 🔴 MEASURED 2026-09-19, warm cache, five runs each: * * node /dist/cli.js hook-runtime run-program … 193 ms * npx vigiles hook-runtime run-program … 2545 ms * * Thirteen times, on every tool call, because `npx` re-resolves the package on * each invocation — local, then global, then the registry. That search is the * single largest cost in a hook's life; everything the runtime does inside adds * up to less than a fifth of it. * * A harness with no project-root token gets the relative spelling, which is all * it can be given — see {@link hookGateRef} for the same fallback. */ export declare function hookRuntimeRef(projectRootTokens: readonly string[] | undefined, runtime?: HookRuntime): string; /** * Which file a project starts the hook runtime from, and the command that puts * that file on disk when it is missing. */ export interface HookRuntime { /** Project-relative path of the runtime's entry point. */ readonly path: string; /** What the start-of-session notice tells the user to run when it is missing. */ readonly cure: string; } /** A consumer's runtime: the vigiles it installed as a dependency. */ export declare const INSTALLED_HOOK_RUNTIME: HookRuntime; /** * The runtime a project with this `package.json` can actually start. * * 🔴 THE VIGILES REPOSITORY IS NOT ITS OWN DEPENDENCY. It compiles its own hooks, * and the installed path above does not exist in it: nothing puts a package into * its own `node_modules`. Measured 2026-10-11: both of the repository's compiled * nudges had died with MODULE_NOT_FOUND on every tool call since #260 * (2026-09-21), and each one's `|| exit 0` turned the death into silence — a * reminder that cannot load is skipped by design (see `hookRuntimeMissingExit`). * So the package itself starts the bin it declares, which its build writes in * place; the cure the notice names is that build, because `npm ci` alone does * not produce `dist/`. * * Decided from the manifest's CONTENT (name + declared bin), never from where the * running CLI happens to sit: a linked or pnpm-store copy would otherwise emit a * path that is only true on the machine that ran `compile`. */ export declare function hookRuntimeFor(manifest: unknown): HookRuntime; /** * The path token `compile` EMITS into the harness config — anchored at the project root * when the harness declares such a variable. * * 🔴 IT LIVES BESIDE {@link bareToken} ON PURPOSE. That function STRIPS exactly this prefix * and these quotes; this one ADDS them. They are one contract read from two ends, and while * the ends sat apart only one got fixed: 2026-08-21 taught the reader to understand the * anchored spelling, and the emitter went on writing the relative one for three more weeks. * * Why anchored at all, from the two measurements already in this file and in * `PluginLayout.projectRootTokens`: a hook command does not run with a stable cwd, so a * relative path "dies with exit 2 the moment the agent runs from a subdirectory". For a * PreToolUse gate that is not a lost nudge — a gate that cannot load must block, so the * repository seizes. Measured in a consumer repo 2026-09-10: recoverable by file writes * only, because every command was refused, including the one that repairs it. * * `bareToken(hookGateRef(ref, tokens)) === ref` is what keeps a recompile idempotent, and * it is asserted directly rather than left to inspection. */ export declare function hookGateRef(ref: string, projectRootTokens: readonly string[] | undefined): string; /** * A command token reduced to the PATH it names, so two spellings of the same * hook file compare equal. * * 🔴 BOTH STRIPS ARE REGRESSIONS, MEASURED IN A CONSUMER REPO 2026-08-21, and * they compound: a settings.json wired the Claude-Code-recommended way carries * BOTH a quote and the project-dir variable, so `managesHook` saw * `"$CLAUDE_PROJECT_DIR/.claude/hooks/x.hook.ts"`, canonicalized it to * something under the cwd that resembles nothing, and reported "not managed by * this hook". Recompiling then APPENDED its own block beside the existing one. * Twelve hooks, twelve duplicates, and the duplicate is the WORSE of the two: * it spells the path relative to the cwd, so it dies with exit 2 the moment the * agent runs from a subdirectory — while the healthy copy beside it keeps * working, which is why this survived a full day unnoticed. * * - QUOTES. A path with a space MUST be quoted, so the quoted form is not an * exotic spelling — it is the correct one. Comparing it raw could never match. * - the harness's PROJECT-ROOT variable (Claude Code's `$CLAUDE_PROJECT_DIR`), * handed in by the caller from its layout. It is defined as the project * root, which is exactly what a relative path resolves against, so stripping * it makes the two spellings the same path by definition rather than by * guess. `${NAME}` and `$NAME` are the same variable. Core names no harness * variable itself. * * Nothing else is stripped. A token this function does not recognise is left * alone and simply fails to match, which is the pre-existing behaviour: the * cost of a miss here is a duplicate block, and the cost of an over-match is * deleting a hook the user wrote themselves. */ export declare function bareToken(token: string, rootTokens: readonly string[]): string; /** The start-of-session check line `compile` wires beside the compiled hooks. */ export declare function sessionCheckCommand(projectRootTokens: readonly string[] | undefined): string; /** * A hook source and what its last compile recorded about it. `role` comes from * the stamp sidecar; a source never compiled (or compiled by a vigiles that did * not record it) has none, and so has no planned line. */ export interface HookSourceRecord { /** Repo-relative path of the hook source (`.vigiles/hooks/x.hook.mjs`). */ readonly ref: string; readonly role?: DispatchKind; } /** One line `compile` would write, and the files it needs on disk. */ export interface PlannedHookLine { readonly command: string; /** Root-anchored (`${TOKEN}/…`) or repo-relative paths. */ readonly files: readonly string[]; } /** * The lines `compile` and `init` would write in a repo, and the hook sources * whose wiring `compile` owns (its merge key) — what a reader compares a hook * line against. */ export interface HookLinePlan { readonly lines: readonly PlannedHookLine[]; /** Repo-relative refs of every hook source, compiled or not. */ readonly sources: readonly string[]; } /** * The plan for a repo, from what its files say: each hook source's role from * its stamp sidecar, the runtime from `package.json`. `readText` is the * engine's file port (disk or a fetched file map); `undefined` = absent. */ export declare function hookLinePlan(sourceRefs: readonly string[], readText: (ref: string) => string | undefined, projectRootTokens: readonly string[] | undefined): HookLinePlan; /** What the plan needs: the repo's hook sources, its runtime, the harness's root variables. */ export interface HookPlanInput { readonly sources: readonly HookSourceRecord[]; readonly runtime: HookRuntime; readonly projectRootTokens: readonly string[] | undefined; } /** * Every hook line `compile` (and `init`) would write in this repo — the same * template functions the writers call, so the reader cannot drift from them. */ export declare function plannedHookLines(input: HookPlanInput): readonly PlannedHookLine[]; /** * Whether a command names one of `refs` — `compile`'s MERGE KEY, the test it * uses to find the entries it owns when it rewrites a settings file. The * command is split on whitespace and each word reduced by {@link bareToken}; * nothing is parsed as shell. `normalize` makes a word comparable to a ref * (the writer and the reader each know their own root). */ export declare function commandNamesRef(command: string, refs: readonly string[], normalize: (word: string) => string, rootTokens: readonly string[]): boolean; //# sourceMappingURL=hook-command.d.ts.map