import { type CompilableMarker, type SourceKind } from "./source-kinds.js"; import { type HookRuntime } from "./core/hook-command.js"; /** The committed home for registered context-provider SOURCE (v2). */ export declare const PROVIDERS_DIR = ".vigiles/providers"; /** What discovery found in one source directory, split by what `compile` owes each part. */ export interface Discovered { /** Files this directory's role compiles: `.hook.` files in `.vigiles/hooks/`, `.provider.` files in `.vigiles/providers/`. */ readonly claimed: readonly string[]; /** * Runnable files nothing claims here. `compile` REFUSES these out loud * ({@link unclaimedMessage}); dropping them would make a hook that lost its * marker vanish from the build without a word. */ readonly unclaimed: readonly string[]; } /** Discover hook sources under {@link HOOKS_DIR}: `.hook.` files, and the runnable files that are nobody's. */ export declare function discoverHookFiles(cwd: string): Discovered; /** Discover registered-provider sources under {@link PROVIDERS_DIR}. */ export declare function discoverProviderFiles(cwd: string): Discovered; /** Why an explicit `compile` argument is an error rather than a hook, a refusal or a skip. */ export type InvalidArgReason = "missing" | "directory" | "not-a-source"; /** What `vigiles compile ` makes of the paths it was handed. */ export interface HookArgs { readonly hooks: readonly string[]; /** Runnable files nothing claims (or a marked file in the wrong directory): refused, with the fix. */ readonly unclaimed: readonly string[]; /** Named on purpose, not compilable (a test, a stamp…): said out loud, not an error. */ readonly skipped: readonly { readonly path: string; readonly kind: SourceKind["kind"]; }[]; /** Not a file at all, or not a source: an error, the way it always was. */ readonly invalid: readonly { readonly path: string; readonly reason: InvalidArgReason; }[]; } /** What the file system says a path is. Injected so the rules below stay pure. */ export type PathProbe = (path: string) => "file" | "directory" | "missing" | "other"; /** * Sort explicit `compile` arguments by the same classifier discovery uses, so * `compile guard.mjs` and a bare `compile` give the same answer about it — for a * path ANYWHERE, not just under `.vigiles/hooks/`: `compile .claude/hooks/x.mjs` * or a plugin's `hooks/guard.mjs` is refused unless it carries `.hook.`. * * A path that is missing, a directory or not a regular file is an error (it used * to fail, and the first version of the marker rule turned it into a green run). * The one lenient case exists for a shell glob (`compile .vigiles/hooks/*`): a * test, declaration or stamp is reported as skipped, and so is a README or * `.gitkeep` — but only inside the two vigiles source directories, where a glob * would sweep one up. A `/etc/passwd` is not a README. */ export declare function partitionHookArgs(args: readonly string[], probe?: PathProbe): HookArgs; /** The line `compile` prints for an argument it cannot treat as a hook source at all. */ export declare function invalidArgMessage(path: string, reason: InvalidArgReason): string; /** * Discovery, shaped like {@link partitionHookArgs}'s answer, so `compile` handles * "no arguments" and "these paths" through one code path afterwards. */ export declare function discoveredHookArgs(cwd: string): HookArgs; /** * The ONE shell command that renames an unclaimed file to its marked name and * (for a hook) recompiles it. Every path is single-quoted: this is printed by a * tool and pasted by a person, and a repository can plant a file name that is a * command. * * 🔴 THE HOOK COMMAND COPIES FIRST AND DELETES LAST, and that order is the * point. Already-wired hooks keep running after an upgrade: the runtime loads * whatever path `settings.json` names, and never asks what a file is called. So * the break is the NEXT compile, and a plain `mv` + `compile` has one bad * moment — the old path is gone and the wiring still names it. For a gate * wired `|| exit 2` that is a hook that cannot load, which blocks every Bash * call (the repair is a file write, not a command). Compiling the renamed copy * while the old file still exists, and deleting the old one only after that * compile succeeded, has no such moment: if anything fails the old hook is * still on disk and still wired. `compile` replaces the old wiring itself * ({@link ownedRefs}), so nothing is left naming the deleted path. * * 🔴 `cp -n`, because the marked name may already exist: a plain `cp` replaced * an edited `guard.hook.mjs` with the stale `guard.mjs`, and the weaker hook * became the wired gate. {@link unclaimedMessage} checks first and prints no * command at all in that case; `-n` is for the file that appears in between. */ export declare function renameCommand(path: string, role: CompilableMarker): string; /** * {@link unclaimedMessage} with the one fact it cannot know, read off the disk: * whether the marked name is already there. */ export declare function unclaimedReport(path: string, role: CompilableMarker, cwd?: string): string; /** * The text `compile` prints for one file it will not compile, with the exact * command to fix it. `targetExists` is whether the marked name is already on * disk: then there is nothing safe to copy, and the message says to merge by * hand instead. */ export declare function unclaimedMessage(path: string, role: CompilableMarker, targetExists?: boolean): string; interface CommandHook { readonly type: "command"; readonly command: string; } interface HookEntry { readonly matcher?: string; readonly hooks: readonly CommandHook[]; } /** The CC-shaped structured block a compiled hook program carries. */ export type CompiledHooks = Record; interface SettingsJson { hooks?: Readonly>; [k: string]: unknown; } /** * The CANONICAL form of a hook-source reference — how the path is written into * the emitted runtime command AND how an existing entry is recognized as "this * hook", so the merge is keyed by the FILE, not by the string the user typed. * * Without this, `vigiles compile x.hook.ts` and `vigiles compile ./x.hook.ts` * wired the SAME file twice: the second run's `hookPath` (`./x.hook.ts`) wasn't a * substring of the first run's command (`… run-program x.hook.ts`), so nothing * was replaced and a second `{matcher, hooks:[…]}` block was appended. A few * iterations of an edit-compile loop left duplicate wirings that all fire. * * Canonical = POSIX separators, no `./` prefix, resolved against the cwd and made * relative when it lives under it (an absolute path outside the repo is kept * absolute — still stable, just not relative to anything). */ export declare function normalizeHookRef(hookPath: string, cwd?: string): string; /** {@link hookRuntimeFor} over `/package.json`; no or unreadable manifest is a consumer. */ export declare function readHookRuntime(cwd: string): HookRuntime; /** * Idempotently merge a compiled hook's block into an existing `settings.json` * object. Commands managed by THIS hook file (the runtime command references * `hookPath`) are replaced; every unrelated command — including the user's own * hand-written hooks SHARING A MATCHER BLOCK with ours — is preserved. See * {@link withoutHookCommands} for why the granularity is the command and not * the entry. */ export declare function mergeHooksJson(existing: SettingsJson, compiled: CompiledHooks, hookPath: string, /** The harness's project-root variables, from its layout: the merge key strips them. */ rootTokens: readonly string[]): SettingsJson; interface TomlHookEntry { readonly matcher?: string; readonly command: string; } interface ConfigToml { hooks?: Readonly>; [k: string]: unknown; } /** The TOML sibling of {@link mergeHooksJson} (Codex `[[hooks.]]`). */ export declare function mergeHooksToml(existing: ConfigToml, compiled: CompiledHooks, hookPath: string, rootTokens: readonly string[]): ConfigToml; /** * Serialize a merged config back to its on-disk text. * * ⚠️ DEPRECATED IN PLACE, not deleted, and the distinction matters: the * harness-driven path (`installHookFile` in `cli-main.ts`) goes through * `PluginLayout.settings.render` now, so no adapter's encoding is decided here * any more. The one remaining caller is `cli-main.ts`'s Codex-plugin wiring, * which writes `.codex/config.toml` for a harness it names ITSELF, at the * composition root — a caller that already knows the encoding, rather than one * branching on a layout field. Its `format` argument is therefore a literal at * the call site, not a value read off a port. */ export declare function serializeConfig(merged: Record, format: "json" | "toml"): string; export {}; //# sourceMappingURL=hook-install.d.ts.map