/** * Pure helpers for merging Auden's hooks into a project's * `.claude/settings.json`. * * The logger is the bare command `auden-hook`, resolved through PATH, so the * settings file is identical on every machine and can be committed without * churning. */ /** A single hook invocation inside a settings.json hook group. */ export type HookEntry = { type: string; command: string; timeout?: number; }; /** A hook group — settings.json nests entries one level deep under `hooks`. */ export type HookGroup = { hooks?: unknown; [key: string]: unknown; }; /** * The command `auden init` writes for the logger hook, and the only form * settings files contain. Resolved through PATH from the `auden-hook` bin, so * it carries nothing machine-specific. */ export declare const AUDEN_HOOK_COMMAND = "auden-hook"; /** * The Stop-hook command that uploads a session's actions to the dashboard. * * The only thing a Stop hook does now. It used to sit beside `auden eval`, * which graded the session on-device and printed a summary; that command is * gone (docs/plans/cli-local-grading-removal-plan.md) and grading happens * server-side off what this uploads. */ export declare const AUDEN_SYNC_COMMAND = "auden sync"; /** * The removed local-grading command, kept only so init can recognize and clean * up the Stop hooks earlier versions installed. * * A settings file that still invokes it runs a command the CLI no longer has, * so Claude Code surfaces a hook error at the end of every assistant turn. * Recognizing it is what lets `auden init` fix that in place rather than * leaving the user to diagnose it. */ export declare const REMOVED_EVAL_COMMAND = "auden eval"; /** * The `SessionEnd`-hook command that uploads the session's remaining actions * and tells the server the agent's context has ended. * * `Stop` cannot do this job: it fires at the end of every assistant *turn*, so * a sync from it says nothing about whether the session is over. `SessionEnd` * fires once, when the context goes away — including on `/clear`, `--resume` * and `logout`, which is why the server treats the resulting close as a * revisable assertion rather than a fact (see the `completedSessionIds` field * on `DashboardSyncRequestSchema`). * * Deliberately a *different command string* from `AUDEN_SYNC_COMMAND` rather * than the same one with a flag appended by the caller: `hookGroupsHaveCommand` * matches on words, so an entry carrying the flag also satisfies a search for * the bare `auden sync`. Keeping the full flagged form here is what lets the * `SessionEnd` merge below recognize its own entry instead of matching any * sync hook and skipping installation. */ export declare const AUDEN_SESSION_END_COMMAND = "auden sync --session-complete"; /** * True for the portable command, tolerating the surrounding whitespace a * hand-edited settings file may carry. Deliberately exact otherwise: a command * that merely *starts* with `auden-hook` (`auden-hook --foo`, `auden-hooks`) * is someone else's and must not be rewritten or counted as ours. */ export declare function isAudenLoggerCommand(command: unknown): boolean; export type LoggerHookMerge = { /** * The PostToolUse groups to persist. A fresh array; the input is untouched. * Typed loosely because entries this module does not recognize are passed * through verbatim rather than dropped. */ postToolUse: unknown[]; /** * - `unchanged` — this machine's hook is already wired up. * - `repaired` — an Auden entry was normalized (whitespace) or duplicates * were collapsed. * - `installed` — no Auden entry existed, so one was appended. */ action: 'unchanged' | 'repaired' | 'installed'; /** Prior command strings rewritten by a repair, for reporting. Empty otherwise. */ repairedFrom: string[]; /** Redundant Auden logger entries removed, for reporting. */ removedDuplicates: number; }; /** * Merge Auden's logger hook into one event's groups — `PostToolUse` or * `PostToolUseFailure`, the two events `auden init` wires it to. Both carry * the identical `auden-hook` command, so one merge covers either; the field is * named `postToolUse` for the caller that shipped first, not because the * event is fixed. * * Dedupes rather than stacks: two Auden loggers that fire on the same event * make Claude Code run the logger twice. Entries that are not Auden's are * preserved untouched and never satisfy the "already installed" check. */ export declare function mergeAudenLoggerHook(existingPostToolUse: unknown, hookEntry: HookEntry): LoggerHookMerge; /** * True when a settings file's hook groups for one event (`Stop`, `SessionEnd`, * …) already invoke `command`. * * Matches the command anywhere inside an entry's text, unlike the logger * recognizer above, and deliberately so: the Stop hooks people actually have * are wrapped in shell — `auden sync --no-docs || true`, or a single compound * entry chaining several commands (`RemoteAgentsOpenAI.tsx`). Demanding * equality would read every one of those as absent and append a second entry * beside it on each init, so the sync would run twice on every turn. * * Word-bounded on both sides so a longer command that merely begins with the * same words is not mistaken for this one. */ export declare function hookGroupsHaveCommand(existingGroups: unknown, command: string): boolean; /** * True when this project's Stop hooks actually upload the session's actions. * * Stricter than "mentions `auden sync`", because the two flags above turn the * same command into one that deliberately ships nothing. Counting those would * be the worst possible answer: init would skip installing a real upload hook * and doctor would call the project healthy, in the one state where the * actions provably never leave the machine — which is the exact failure this * whole check exists to catch. */ export declare function stopHookUploadsActions(existingStop: unknown): boolean; export type StaleEvalHookPrune = { /** The event's hook groups to persist. A fresh array; the input is untouched. */ groups: unknown[]; /** Entries removed because they only ran the deleted command. */ removed: number; /** * Commands that mention the deleted command but do something else too, so * they were left in place for the user to edit. Reported, never rewritten. */ unhandled: string[]; }; /** * Drop the `auden eval` Stop hooks earlier CLI versions installed. * * Local grading is gone (docs/plans/cli-local-grading-removal-plan.md), so an * entry invoking it runs a command that no longer exists and Claude Code * reports a failing hook at the end of every assistant turn. The settings file * is the user's, but this particular line was written by `auden init` and now * only produces errors, so init removes what it wrote and names what it did * not. */ export declare function pruneStaleEvalHooks(existingGroups: unknown): StaleEvalHookPrune; /** * Warning naming the hook commands `pruneStaleEvalHooks` found but would not * rewrite — returns null when there are none. */ export declare function staleEvalHookWarning(unhandled: readonly string[]): string | null; export type HookEntryMerge = { /** The event's hook groups to persist. A fresh array; the input is untouched. */ groups: unknown[]; /** `installed` when an entry was appended, `unchanged` when one was found. */ action: 'installed' | 'unchanged'; }; /** * Add a hook entry to one event's groups unless `isPresent` finds one already * doing its job. * * Event-agnostic — `Stop` and `SessionEnd` merge through this same function, * because "append unless already present" is the whole rule and neither event * needs its own copy of it. * * Each command is merged independently rather than as a set, because a project * wired by an older CLI carries some of these entries and not others: it needs * the missing ones added without its existing entries being rewritten, * reordered, or counted as "already wired". * * "Already there" is the caller's to define, and is not always "the same words * appear": the entry this installs carries flags, and an existing entry that * names the command while disabling what it was installed for * (`stopHookUploadsActions`) must not satisfy it. */ export declare function mergeAudenHookEntry(existingGroups: unknown, entry: HookEntry, isPresent: (groups: unknown) => boolean): HookEntryMerge; /** * Warning to print after `auden init` wires the hooks, when the bare commands * they invoke are not reachable on PATH — returns null when both resolve. * * The hooks call `auden-hook` (PostToolUse) and `auden` (Stop, SessionEnd) as * bare commands, resolved through the hook shell's PATH. auden's hooks are * global-only by design: a global install (`npm install -g auden`) is what puts * those names on PATH. When init ran without one — `npx auden init`, or a * project-local bin — the settings are written correctly but the hooks will * never fire, and Claude Code would otherwise surface only a bare * command-not-found at session end. The resolver is injected so this stays a * pure unit; it defaults to real PATH resolution at the call site. */ export declare function missingHookPathWarning(resolve: (command: string) => string | null): string | null; //# sourceMappingURL=hooks-settings.d.ts.map