import type { PendingConfirmFile, PendingDecision } from "./dev-confirm-paths.js"; /** * v1.3.0 Part A — the approve-flow PreToolUse(Bash) hook. Supersedes * `bash-deny-hook.ts` (which only ever denied): this hook turns the dormant * dev-confirm gate live by writing a `pending-confirms/.json` request and * polling for the bridge's `.decision` before allowing or blocking the * push. * * Decision matrix: * - not a sensitive command → exit 0 (allow) * - `git push` to a protected branch → exit 2 (BLOCK — fail-closed guard, * independent of the error policy) * - `gh pr merge` / `gh pr close` → confirm flow (no branch concept) * - `git push` to a feature branch → confirm flow * * Confirm flow → write pending file, poll for decision (default 30 min): * - decision "y" → exit 0 (push proceeds) * - decision "n" → exit 2 (blocked) * - timeout → exit 2 (blocked — fail-closed: no approval = no push) * * Failure policy (PRD Open Q#2): the gate fails OPEN on hook error (bad stdin, * unwritable pending dir, missing config) → exit 0, so a buggy hook never * permanently bricks every push. The protected-branch guard is the one * exception — it stays fail-closed regardless. Mirrors `bash-deny-hook.ts`'s * "fail open on malformed input" stance. * * Runs as a standalone node script (`node dev-confirm-hook.js`) inside the * claude sub-process; the bot injects context via env (see claude-process.ts): * SOLOSQUAD_DEV_CONFIRM_DIR absolute pending-confirms dir * SOLOSQUAD_DEV_CONFIRM_ORG org slug * SOLOSQUAD_DEV_CONFIRM_USER messenger user id * SOLOSQUAD_DEV_CONFIRM_HANDLE command- owner * SOLOSQUAD_DEV_CONFIRM_WORKFLOW active workflow id (optional) * SOLOSQUAD_DEV_CONFIRM_TIMEOUT_MS approval timeout in ms * SOLOSQUAD_DEV_CONFIRM_PROTECTED comma-separated protected branches */ export type HookAction = "allow" | "block" | "confirm"; export interface HookEnv { dir?: string; org: string; user: string; handle?: string; workflowId?: string; timeoutMs: number; protectedBranches: string[]; } export declare function readHookEnv(env: NodeJS.ProcessEnv): HookEnv; /** Extract `tool_input.command` from a PreToolUse payload; null on any error. */ export declare function extractCommand(stdin: string): string | null; /** * Decide what to do with a command. Pure — `resolveBranch` is injected so the * current-branch lookup (a `git rev-parse`) can be faked in tests. */ export declare function classifySensitive(cmd: string, opts: { protectedBranches: string[]; resolveBranch: () => string | null; }): { action: HookAction; branch: string | null; }; export interface HookDeps { readStdin: () => Promise; env: NodeJS.ProcessEnv; cwd: string; resolveBranch: (cwd: string) => string | null; collectCommits: (cwd: string, branch: string | null) => string[]; makeId: (seed: { user: string; skill: string; ts: string; }) => string; writePending: (file: string, body: PendingConfirmFile) => void; pollDecision: (decisionFile: string, timeoutMs: number) => Promise; warn: (msg: string) => void; now: () => Date; } /** Run the gate. Returns the process exit code (0 allow / 2 block). */ export declare function runHook(deps: HookDeps): Promise; export declare const REAL_HOOK_DEPS: HookDeps;