import type { ArmsCheckReport, EvalDriver, ArmsMeasureSpec, CheckReport, EvalReport, EvalSpec, MeasureSpec, Metrics, TriggerRateReport, TriggerRateSpec } from "./eval.js"; import type { SelectionMatrixOptions, SelectionReport } from "./scan-behavioral.js"; /** * Brand marking a value as built by {@link defineEval}. A registered symbol, so * a descriptor still reads as one across two copies of the package on disk — * the shape a monorepo produces routinely. */ export declare const EVAL_DEFINITION: unique symbol; /** `measureSelectionMatrix`'s two arguments as one declarable object. */ export interface SelectionMatrixSpec extends SelectionMatrixOptions { /** The plugin root whose skills are measured against each other. */ readonly pluginDir: string; } /** * The five measurements an eval file may declare — the runners that already * exist, keyed by their own names, so migrating is renaming a call to a key. * EXACTLY ONE must be present: zero is a file that declares nothing, and two is * a file whose author expected both to run. */ export interface EvalMeasurements { /** A/B across arms with derived metrics — `paid_runEval`. */ readonly runEval?: EvalSpec; /** Checks scored over N trials of one task — `paid_measure`. */ readonly measure?: MeasureSpec; /** The same checks per arm — `paid_measureArms`. */ readonly measureArms?: ArmsMeasureSpec; /** Does a skill's description actually fire — `paid_measureTriggerRate`. */ readonly measureTriggerRate?: TriggerRateSpec; /** * The N×N skill-selection collision matrix — `measureSelectionMatrix` from * `vigiles/claude-code`. Declared here because a description is a description * whatever harness answers it; only `eval-entry.ts` resolves it to a runner. */ readonly measureSelectionMatrix?: SelectionMatrixSpec; } /** The report type produced by each declared measurement. */ export interface EvalReports { readonly runEval: EvalReport; readonly measure: CheckReport; readonly measureArms: ArmsCheckReport; readonly measureTriggerRate: TriggerRateReport; readonly measureSelectionMatrix: SelectionReport; } /** The measurement names, as a type. */ export type EvalKind = keyof EvalMeasurements; /** Everything an eval file declares beyond the measurement itself. */ export interface EvalHooks { /** * A lazy precondition. Return a reason to SKIP (exit 77, loud — never a silent * green); return nothing to run. Lazy is the point: a probe at the top of the * file would be work at import time, which is the shape being removed. */ readonly skipIf?: () => string | false | undefined | null; /** * What the report has to show. Throw to fail. The runner has already printed * the standard report by the time this is called, so this is for the derived * reads — gates, per-arm significance, a verdict line. */ readonly assert?: (report: EvalReports[K]) => void | Promise; /** * Drive the measurement with this harness instead of the real `claude` CLI — * the declarative home for the second argument of * `measureTriggerRate(spec, { evalDriver })`, e.g. `codexEvalDriver` from * `vigiles/codex`, or a fake runner in a test. * * ⚠️ `measureTriggerRate` ONLY. That is not a limitation of this shape but of * the product as it stands: `measureTriggerRate` is the one measurement with a * public driver seam, and `runEval(spec)` always drives Claude Code (see * docs/harnesses.md, footnote 2). Declaring it with any other measurement is a * loud error rather than a field that silently does nothing. */ readonly evalDriver?: EvalDriver; } /** One measurement + its hooks: what an eval file default-exports. */ export type EvalDefinitionInput = Pick & EvalHooks; /** The branded value {@link defineEval} returns. */ export type EvalDefinition = EvalDefinitionInput & { readonly [EVAL_DEFINITION]: true; }; /** * What a module's default export declares. Total: every answer the runner has to * distinguish is a case here, so no caller can forget one. */ export type DeclaredEval = { readonly ok: true; readonly kind: EvalKind; readonly spec: unknown; } | { readonly ok: false; readonly why: "not-a-definition"; } | { readonly ok: false; readonly why: "declares-nothing"; } | { readonly ok: false; readonly why: "declares-several"; readonly kinds: readonly EvalKind[]; }; /** The measurement keys, in a fixed order — the one list, read by everything. */ export declare const EVAL_KINDS: readonly EvalKind[]; /** Whether a value came from {@link defineEval}. */ export declare function isEvalDefinition(v: unknown): v is EvalDefinition; /** * Read a module's default export as a declaration. Pure — this is the whole * reason the descriptor is data: the runner answers "what does this file * declare?" without executing anything and without spending anything. */ export declare function declaredEval(def: unknown): DeclaredEval; /** * The definition a module namespace carries, through the CJS/ESM interop layer. * * 🔴 THE SECOND `.default` IS NOT DEFENSIVE — it is the only way a TypeScript * eval file works, and it was found by a test, not by reading. Three shapes * reach this function and they are genuinely different objects: * * x.eval.mjs real ESM → `mod.default` IS the definition * x.eval.cjs `module.exports = defineEval(…)` * → `mod.default` is `module.exports`, the definition * x.eval.ts `export default …`, transpiled to CJS by tsx * → `mod.default` is `module.exports`, and the * definition sits at `mod.default.default` * * Measured 2026-08-18: without the unwrap a `.eval.ts` file reported * "no `export default defineEval({…})` found" — a correct file, refused, with a * message that sent its author looking in the wrong place. * * Brand-directed rather than shape-directed: it reaches deeper ONLY when the * outer value is not a definition, so a definition that happens to carry a * `default` field of its own is never skipped over. */ export declare function moduleDefault(mod: unknown): unknown; /** * Was node pointed STRAIGHT at an eval file? `argv1` is `process.argv[1]`: the * path node was started with, which no stray configuration can forge. * * node x.eval.mjs → the file → true * vigiles eval x.eval.mjs → dist/eval-entry.js → false * node -e 'import("x.eval.mjs")' → undefined → false * npx vitest run → …/vitest/…/forks.js → false * * Pure: a fact in, a boolean out. */ export declare function ranAsEntry(argv1: string | undefined): boolean; /** The words shown when someone runs an eval file directly. Asserted by a test: * a refusal that stops a run without saying what to do instead is a ticket. */ export declare function ranAsEntryRefusal(argv1: string): string; /** * Declare the eval a file describes. Returns a plain, branded value; it starts * nothing, spends nothing, and touches no filesystem. * * ```js * import { defineEval, assertRates } from "vigiles"; * import { skill } from "vigiles"; * * export default defineEval({ * measure: { pluginDir, task: "…", checks: [skill("my:skill")], trials: 3 }, * assert: (report) => assertRates(report, { min: 0.6 }), * }); * ``` * * @throws if node was pointed straight at the eval file — see the module doc. * That is the ONE thing this function does besides build a value, and it is * here rather than in each file precisely so that no author can forget it. */ export declare function defineEval(def: EvalDefinitionInput): EvalDefinition; //# sourceMappingURL=eval-define.d.ts.map