/** * The diablo config domain: a minimal, optional configuration plus the * three-layer precedence rule that governs model selection. * * built-in defaults <- diablo.config.json <- CLI flag * * Kept deliberately small (models, integration, gate, retry, skillsDir) — adding * knobs is resisted by design. Pure (no I/O): `parseConfig` turns JSON text into * a fully-defaulted config and `resolveModels` applies the precedence chain, so * both are unit-tested directly. The loader use-case wraps the filesystem read. * * Model resolution: * Every role (planner, worker, verifier) resolves to a concrete {provider, model} * pair. The resolution chain is: * * default_provider / default_model (top-level, REQUIRED) * <- models..provider / .model (per-role override, optional) * <- CLI ---model flag (model only, wins over config) * * The final Pi model string is: `{provider}/{model}:{tier}` * where tier is "high"/"medium" (planner) or "medium" (worker/verifier). */ import type { GateMode } from "../ports/gate.ts"; /** Valid Pi thinking levels (matches `pi --thinking` flag). */ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; /** * Per-role model override. All fields are optional — omitted fields fall back * to the top-level defaults (provider, model, thinking). */ export interface RoleModelOverride { provider?: string; model?: string; thinking?: ThinkingLevel; } /** The `models` config field: per-role optional overrides. */ export interface ConfigModels { architect?: RoleModelOverride; planner?: RoleModelOverride; worker?: RoleModelOverride; verifier?: RoleModelOverride; } /** Fully resolved per-role model (no optionals — all defaults applied). */ export interface ResolvedRoleModel { provider: string; model: string; thinking: ThinkingLevel; } /** All four roles resolved to concrete provider+model+thinking. */ export interface ResolvedModels { architect: ResolvedRoleModel; planner: ResolvedRoleModel; worker: ResolvedRoleModel; verifier: ResolvedRoleModel; } export interface ConfigIntegration { targetBranch: string; branchPrefix: string; autoMerge: boolean; } export interface ConfigRetry { /** Max worker re-attempts on an implementation FAIL before halting to a human. */ limit: number; } /** * Safety ceilings for an unattended run. Generous by default — these exist to * stop a pathological hang or runaway, never to clip a legitimately long run. */ export interface ConfigLimits { /** Max wall-clock for a SINGLE agent step before it is killed (ms). */ stepTimeoutMs: number; /** Max wall-clock for the WHOLE run before it aborts cleanly (ms). */ runBudgetMs: number; /** Max number of agent steps in a run before it aborts (circuit breaker). */ maxSteps: number; } /** * The deterministic verification gate diablo runs itself after a committing * step (ADR 0001). Each command is run in the worktree; a non-zero exit makes * the stage FAIL regardless of the verifier LLM's verdict. Empty by default — * a project with no commands runs LLM-verdict-only, and diablo says so loudly * rather than pretending the verdict is authoritative. */ export interface ConfigVerify { /** Shell gate commands (e.g. "bun run typecheck", "bun test"), run in order. */ commands: string[]; } export interface DiabloConfig { /** The default Pi provider name (e.g. "9router"). REQUIRED. */ defaultProvider: string; /** The default model identifier (e.g. "kr/claude-sonnet-4.5", "mimo/mimo-v2.5-pro"). REQUIRED. */ defaultModel: string; /** The default thinking level (e.g. "medium"). Applied to all roles unless overridden. */ defaultThinking: ThinkingLevel; models: ConfigModels; integration: ConfigIntegration; gate: GateMode; retry: ConfigRetry; limits: ConfigLimits; verify: ConfigVerify; /** Optional override for the vendored skills directory; resolver decides when absent. */ skillsDir?: string; } /** CLI model flags that win over config (the top precedence layer). */ export interface ModelFlags { plannerModel?: string; workerModel?: string; verifierModel?: string; } /** * The built-in defaults — used by `diablo init` to scaffold a config file. * NOT used as fallbacks during parsing (default_provider and default_model * are REQUIRED in the config file when it exists). */ export declare function defaultConfig(): DiabloConfig; /** * Parses `diablo.config.json` text into a fully-defaulted config. Missing keys * fall back to the built-in defaults (config is sparse-by-design); a present * key overrides only that field. Throws on malformed JSON, a non-object root, * an invalid enum value, or missing required fields (default_provider, * default_model), so a typo fails loudly rather than silently reverting to a * default. */ export declare function parseConfig(text: string): DiabloConfig; /** * Resolves the effective provider+model for each role from the three-layer * precedence chain: defaults ← config per-role overrides ← CLI flags. * * CLI flags (`--planner-model` etc.) only override the MODEL name; the provider * always comes from config (top-level default or per-role override). */ export declare function resolveModels(config: DiabloConfig, flags: ModelFlags): ResolvedModels;