import type { ActionRecipe } from "./types.js"; export interface RecipeStoreOptions { /** Project-local recipe dir. Default: `./chaosbringer-recipes`. Set to `false` to disable. */ localDir?: string | false; /** Cross-project recipe dir. Default: `$XDG_DATA_HOME/chaosbringer/recipes` or `~/.chaosbringer/recipes`. Set to `false` to disable. */ globalDir?: string | false; /** * Promotion threshold: a candidate becomes verified once it has * ≥ `minRuns` total recorded runs AND a success ratio ≥ `minSuccessRate`. */ minRuns?: number; minSuccessRate?: number; /** * Demotion threshold: a verified recipe drops back to candidate once * its recent failure rate (computed across the last `minRuns` runs) * exceeds `1 - minSuccessRate`. Conservative — we'd rather keep using * a slightly flaky recipe than blow away accumulated stats. */ silent?: boolean; } /** * Sanitise a recipe name into a safe filename. We keep alphanumerics, * `-`, `_`, and `.`, and replace `/` with `__` so namespacing like * `shop/checkout` survives. Anything else collapses to `_` to keep the * filesystem happy across platforms. */ export declare function recipeFilename(name: string): string; /** * Filename for a HISTORICAL version of a recipe. Versions accumulate * as siblings of the current file: `auth__login.v1.json`, * `auth__login.v2.json`, etc. The current/active file stays at the * un-versioned path. */ export declare function recipeHistoryFilename(name: string, version: number): string; /** Parse a history filename back into `{ name, version }` or null. */ export declare function parseRecipeHistoryFilename(file: string): { stem: string; version: number; } | null; export declare class RecipeStore { private readonly local; private readonly global; private readonly minRuns; private readonly minSuccessRate; private readonly silent; private cache; private loaded; constructor(opts?: RecipeStoreOptions); /** * Directory where new writes land (local tier when present, else * global). Returns null if both tiers are disabled. Exposed for * the snapshot module (issue #89) which co-locates `*.state.json` * files alongside the recipe files. */ get writeDir(): string | null; /** Load both tiers into the in-memory cache. Local overrides global. */ load(): void; private loadFrom; /** Returns a deep-copied snapshot — caller mutations don't leak. */ get(name: string): ActionRecipe | null; /** All recipes currently in the store (cloned). Order is unspecified. */ list(): ActionRecipe[]; /** Only `verified` recipes — what `recipeDriver` actually replays. */ verified(): ActionRecipe[]; /** * Recipes whose first URL precondition matches the given hostname. * The match is a substring check on the regex source — e.g. * `byDomain("github.com")` matches `urlPattern: "github\\.com\\/.*"`. * Recipes with no URL precondition (cross-host) are returned for * every domain. * * Convenience for multi-host crawls: drivers can scope replay to * "only github.com recipes when on github.com" without re-scanning * the full store on every step. */ byDomain(host: string): ActionRecipe[]; /** Distinct domains across the cached recipes. */ domains(): string[]; /** * Insert or replace a recipe. New recipes start as `candidate` with * empty stats unless explicitly provided. The write target is the * local dir if available, otherwise global. Writing to neither is * an error (call sites should check `canWrite` first). * * Before overwriting, if the existing version differs from the * incoming one, the current file is renamed to its history slot * (`name.vN.json`). This is the rollback safety net for * `repairRecipe` — never lose the previous good version. */ upsert(recipe: ActionRecipe): void; /** * Return the recipe's historical versions (newest first). The * current version is NOT included — use `get(name)` for that. * Empty array when no history exists. */ history(name: string): ActionRecipe[]; /** * Resolve a recipe at a specific version — current if `version` * matches `get(name).version`, otherwise the history entry. Returns * null when the recipe or the version doesn't exist. */ getVersion(name: string, version: number): ActionRecipe | null; /** * Replace the current version with a prior one. The historical * file is consumed (moved back to current); the current pre-rollback * version is archived in turn so rollback is fully reversible. * * The rolled-back recipe is bumped to a NEW version number so * downstream `recipe.version === N` checks see the change clearly. * Stats from the current version are PRESERVED — rollback is a * step-list swap, not a stats reset. */ rollback(name: string, opts: { toVersion: number; }): ActionRecipe | null; /** * Trim historical versions. Keeps the `keepLast` newest entries and * deletes the rest. The current version is never touched. */ pruneHistory(name: string, opts: { keepLast: number; }): number; private archive; private deleteHistoryEntry; /** Remove from cache + disk. No-op if the recipe doesn't exist. */ delete(name: string): void; recordSuccess(name: string, durationMs: number): void; recordFailure(name: string): void; /** Direct status override — used by `verifyAndPromote` after a batch run. */ setStatus(name: string, status: ActionRecipe["status"]): void; private computeStatus; private persist; private ensureLoaded; } //# sourceMappingURL=store.d.ts.map