import Anthropic from "@anthropic-ai/sdk"; import type { ForgeAdapter, Spec } from "@slowcook-ai/core"; import { type StackConfig } from "@slowcook-ai/stack-ts"; import { type HaltReason, type HaltReport } from "./halt.js"; /** ------------------------- Context + options ------------------------- */ export interface BrewContext { repoRoot: string; storyId: string; spec: Spec; stackConfig: StackConfig; forge: ForgeAdapter; anthropic: Anthropic; model: string; budgetUsd: number; maxIterations: number; wallClockMs: number; now: () => Date; /** Branch to push checkpoints onto. */ branchName: string; /** Paths writable by this brew session, as declared in the spec (or inferred). */ allowedPaths: string[]; /** Paths frozen under .brewing/frozen-paths.json (relative to cwd). */ frozenPaths: FrozenPaths; /** Where to write halt reports. */ haltDir: string; /** * Optional path for the rolling iteration log. One line per loop state * change (baseline, per-iter outcome, halt) so an operator can tail the * file during a long brew without waiting for the CI log to flush. */ runLogPath?: string; /** slowcook CLI version; threaded into the code map's `slowcook_version` field. */ cliVersion: string; /** * 0.15.0-α.4 — execution mode. * * `freehand`: wide-scope. Brew has wide allowed_paths and writes * implementation from empty stubs. Used for backend-only stories * and any story where the plate track was skipped. * * `plate`: the mockup is on main (committed by plate after PM * approval). Brew's allowed_paths exclude UI files entirely; the * system prompt is augmented with "do NOT redesign components". * Brew's job collapses to swapping `.ts` stubs for real * fetches + writing API handlers + writing migrations. Halts with * MOCKUP_DESIGN_CONFLICT when a test cannot be satisfied without * editing a frozen UI file. */ mode?: "freehand" | "plate"; /** * 0.19.0-alpha.4 — pair-brew navigator hook (production wiring of * the validated pair-sim experiment). Fires AFTER the iteration's * existing regression / no-progress checks have passed, BEFORE the * checkpoint is committed. Use it to add a "design fidelity / cross- * story risk / responsive / accessibility" verdict beyond what * vitest can observe. * * Returning a verdict with `overall: "block"` causes the iteration * to revert + treat as a no-progress iter (its concerns fold into * the next iter's `prior_attempts` history). Returning null OR a * non-block verdict lets the iteration proceed to checkpoint as * normal. * * Default: undefined — no behavioral change vs pre-α.4 brew. Inject * via the cli's `--with-navigator` flag (wired up in α.5+) or via * a unit-test stub. */ navigatorHook?: NavigatorHook; } /** * 0.19.0-alpha.4 — interface for the pair-brew navigator review. * Pure I/O contract; brew/agent.ts calls it with iteration context * and consumes the verdict deterministically. The default Anthropic- * backed implementation lives in `pair-navigator.ts`; tests inject * a stub. */ export interface NavigatorHook { /** * Called after a successful iteration's existing checks pass + before * the checkpoint is committed. Implementations may call an LLM, do * static analysis, or return null to abstain. * * @returns null when the navigator abstains (no opinion / disabled * for this iter); a NavigatorHookVerdict otherwise. */ review(input: NavigatorHookInput): Promise; } export interface NavigatorHookInput { iteration: number; storyId: string; /** Files changed by this iteration. */ filesTouched: string[]; /** Diff lines added in this iteration. */ linesAdded: number; /** Diff lines removed in this iteration. */ linesRemoved: number; /** Driver's stated rationale for the changes. */ rationale: string; /** Tests that just went red→green this iter. */ gainedTests: string[]; /** Repo root, so the hook can read additional context if needed. */ repoRoot: string; /** * α.55 — the test id the iteration was targeting. Navigator scopes * concerns to this target; non-target concerns are warn-only. */ targetTestId?: string; } export interface NavigatorHookVerdict { overall: "approve" | "block"; /** Per-axis concerns; surfaced in audit + folded into next iter on block. */ concerns: string[]; /** Cost incurred (USD). Tracked against budget by callers that care. */ costUsd?: number; /** * 0.19.0-alpha.5 (#77) — when navigator's soft signals (concerns * folded into prompts) have been ignored across iterations, it may * emit a HARD signal: a failing test that codifies the concern. The * test file is written into tests/navigator/ + becomes part of the * next iter's red-set, so driver MUST satisfy it. * * Path constraints (validated by validateProposedTestPath): * - must start with `tests/navigator/` * - must end with `.test.ts` or `.test.tsx` * - no .. or absolute paths * Content is the literal file body (vitest test). * * Optional. Hook implementations gate this on their own escalation * heuristics (e.g., 2+ consecutive blocks on the same concern). */ proposedTest?: { path: string; content: string; }; } /** * 0.19.0-alpha.4 — pure decision helper for navigator verdicts. Pulled * out of the runBrew loop so unit tests can exercise the consumer * logic without standing up the full brew context. * * Inputs: a verdict (or null when no hook configured / abstaining). * Output: { action, concernsSummary, costUsd } — concernsSummary is * only set when action='block' (used for revert-history note). */ export declare function decideNavigatorAction(verdict: NavigatorHookVerdict | null): { action: "approve" | "block"; concernsSummary: string; costUsd: number; }; /** * 0.19.0-alpha.5 (#77) — validate a navigator-proposed test path. * Pure: takes a candidate path string + returns either the normalized * path or an error reason. Centralizes the path-shape rules so brew's * apply step + tests share the same gate. * * Rules: * - must be relative * - must start with `tests/navigator/` * - must end with `.test.ts` or `.test.tsx` * - no `..` segments * - no leading slash */ export declare function validateProposedTestPath(path: string): { ok: true; path: string; } | { ok: false; reason: string; }; /** * 0.19.0-alpha.5 (#77) — extract a navigator-proposed test from a * verdict. Pure: returns the validated file payload OR null when the * verdict has no proposedTest / fails validation. Caller owns the * write side-effect. * * Note: only proposedTests on BLOCK verdicts are honored. An approve * verdict with proposedTest is a logic error in the hook and we * silently drop it (this prevents a navigator from polluting tests/ * mid-run on iterations it actually approved of). */ export declare function extractNavigatorProposedTest(verdict: NavigatorHookVerdict | null): { path: string; content: string; } | null; export interface FrozenPaths { directories: string[]; files: string[]; partial: Record; } /** ------------------------- Result ------------------------- */ export type BrewOutcome = { kind: "success"; iterations: number; checkpoints: number; spendUsd: number; } | { kind: "halted"; report: HaltReport; }; export declare function runBrew(ctx: BrewContext): Promise; /** ------------------------- Brewing-agent focus helpers ------------------------- */ /** * Resolve an API route spec entry like `{ method: "POST", path: "/api/rewos" }` * to the concrete handler file the brewing agent should edit. Saves the * exploratory iteration where the agent greps around to find a route file. * * Today this supports Next.js App Router only (detected by `src/app/` in * the repo root). Other frameworks fall through with `exists: false` + * `framework: "unknown"` so the agent can fall back to `list_directory` / * `read_file` manually. Future: detect Rails/Django/Go mux. * * Path-param convention: `:id` or `{id}` becomes `[id]` in the filesystem. */ export interface FindHandlerResult { framework: "next-app-router" | "unknown"; file: string | null; function: string | null; exists: boolean; note?: string; } export declare function findHandler(repoRoot: string, method: string, path: string): FindHandlerResult; /** * Produce a compact "outline" of a TypeScript/JavaScript source file: * imports, exports, top-level signatures, and line counts. Returns * something the brewing agent can read in ~200 tokens instead of the * ~5k-per-read-file that drove most of the 2026-04-21 brew spend. * * Heuristic-only (regex + line scan). Good enough for deciding "is the * handler I need in here?" without a full AST. When the agent needs to * look inside a function body, it uses `read_file` normally. */ export declare function outlineFile(pathHint: string, source: string): string; /** * 0.16.0-α.30 — halt-envelope parser. * * The plate-mode prompt instructs the agent to halt by emitting an * XML envelope in its text output: * * * tests/integration/story-N-ui.test.tsx > "owner sees Pin" * The test asserts X but the mock renders Y. * PM should /plate or /refine. * * * Without this parser, brew never recognises its own agent's halt * envelopes. The agent's perfect classification gets converted to a * generic AGENT_STALLED_NO_EDITS halt because brew only counts tool * calls. Returns null when no envelope is found OR when the class * isn't in the recognised whitelist (defensive against typos). */ type HaltEnvelope = { class: HaltReason; summary: string; }; export declare function parseHaltEnvelope(rationale: string): HaltEnvelope | null; /** ------------------------- Entry helpers ------------------------- */ export declare function readFrozenPaths(repoRoot: string): FrozenPaths; export declare function readStackConfig(repoRoot: string): StackConfig; export declare function loadSpec(repoRoot: string, storyId: string): Spec; export {}; //# sourceMappingURL=agent.d.ts.map