/** * Hooks: commands KONECK runs at the moments that matter, declared rather than coded. * * The point is extending KONECK without touching KONECK. A team that must run `prettier` after * every write, or log every shell command to an audit trail, or refuse edits to `entitymodel.xml` * outside a review window, should not have to fork an agent to get it — those are policies about * their workspace, and they change more often than an agent does. * * Two kinds, and the difference is the whole design: * * - A `pre-tool` hook is asked, and may REFUSE. A non-zero exit blocks the tool and its stderr * becomes the reason the model is given. This is the one that makes hooks worth having: it is * an enforcement point, not a notification. * - Every other hook is told. It runs, its output is collected, and a failure is reported but * changes nothing — a broken notifier must not be able to stop work. * * Declared in `.koneck/hooks.json` in the project, and `~/.koneck/hooks.json` for every project. * Project first, because a repository's own rules should win over a personal default. * * { * "hooks": [ * { "on": "pre-tool", "match": "execute_command", "run": "scripts/audit.sh" }, * { "on": "post-tool", "match": "write_file|edit_file", "run": "npx prettier --write \"$KONECK_PATH\"" }, * { "on": "session-end", "run": "notify-send 'KONECK finished'" } * ] * } * * `match` is a regular expression against the tool name, absent meaning every tool. The command is * run through the shell, in the workspace, with the event in the environment rather than on the * command line — an argument list is a quoting bug waiting to happen, and a path from a model is * exactly the kind of string that finds it. */ export type HookEvent = 'session-start' | 'session-end' | 'user-prompt' | 'pre-tool' | 'post-tool' | 'turn-end'; export declare const HOOK_EVENTS: readonly HookEvent[]; export interface Hook { on: HookEvent; /** A regular expression against the tool name. Absent matches every tool. */ match?: string; run: string; /** Seconds before the hook is killed. A hook must never be able to hang a run. */ timeout?: number; /** Shown instead of the command when a hook blocks something. */ name?: string; /** Where it was declared, for reporting. Filled in by the loader. */ source?: 'project' | 'user'; } /** What a hook is told about, in its environment. */ export interface HookContext { event: HookEvent; cwd: string; tool?: string; args?: string; /** The file a tool is acting on, when there is one. */ filePath?: string; sessionId?: string; ok?: boolean; /** The prompt, for user-prompt. */ text?: string; } export interface HookOutcome { hook: Hook; ok: boolean; /** Set when a pre-tool hook refused, and given to the model as the reason. */ refusal?: string; stdout: string; stderr: string; ms: number; timedOut: boolean; } /** A hook gets ten seconds unless it says otherwise. Long enough to format a file. */ export declare const DEFAULT_TIMEOUT_S = 10; /** Nothing above this, or "timeout" stops meaning anything. */ export declare const MAX_TIMEOUT_S = 120; export declare function projectHooksPath(cwd: string): string; export declare function userHooksPath(home: string): string; /** Project hooks first: a repository's own rules outrank a personal default. */ export declare function loadHooks(cwd: string, home: string): Promise; /** The hooks that apply to an event, in declaration order. */ export declare function hooksFor(hooks: readonly Hook[], event: HookEvent, tool?: string): Hook[]; /** The file a tool is acting on, from its arguments — what a formatter or a linter needs. */ export declare function filePathFrom(args: string | undefined): string | undefined; /** The event, as environment. Not as arguments: a path from a model is a quoting bug waiting. */ export declare function envFor(ctx: HookContext): Record; /** * Runs one hook. * * Never throws. A hook that fails to launch at all is an ordinary failure with the reason in * stderr, because the alternative is that a typo in a config file takes down a session. */ export declare function runHook(hook: Hook, ctx: HookContext): Promise; /** * Asks every pre-tool hook, and stops at the first refusal. * * Sequential on purpose: hooks at this point are policy, and policy that runs in a race is policy * you cannot reason about. The first refusal is the answer, and the rest are not consulted — * exactly as a chain of guards should behave. */ export declare function askHooks(hooks: readonly Hook[], ctx: HookContext): Promise<{ allowed: boolean; reason?: string; ran: HookOutcome[]; }>; /** * Runs the hooks that are merely told, and reports how it went. * * Concurrent, because none of them can change the outcome and a formatter should not have to wait * for a notifier. Failures are collected rather than thrown. */ export declare function notifyHooks(hooks: readonly Hook[], ctx: HookContext): Promise; //# sourceMappingURL=hooks.d.ts.map