vigiles 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +82 -116
  2. package/dist/adapters/claude-code/agent-runtime.d.ts +10 -0
  3. package/dist/adapters/claude-code/agent-runtime.js +15 -29
  4. package/dist/adapters/claude-code/dialect.js +18 -2
  5. package/dist/adapters/codex/eval.d.ts +94 -0
  6. package/dist/adapters/codex/eval.js +227 -0
  7. package/dist/cli.js +464 -8
  8. package/dist/codex.d.ts +1 -0
  9. package/dist/codex.js +3 -0
  10. package/dist/core/compile.js +8 -36
  11. package/dist/core/description-overlap.d.ts +27 -0
  12. package/dist/core/description-overlap.js +53 -0
  13. package/dist/core/dialect.d.ts +8 -0
  14. package/dist/core/frontmatter-read.d.ts +25 -0
  15. package/dist/core/frontmatter-read.js +138 -0
  16. package/dist/core/hook-events.d.ts +34 -0
  17. package/dist/core/hook-events.js +48 -0
  18. package/dist/core/mcp-config.d.ts +20 -0
  19. package/dist/core/mcp-config.js +40 -0
  20. package/dist/core/mcp-hook.d.ts +35 -0
  21. package/dist/core/mcp-hook.js +70 -0
  22. package/dist/core/mcp-tool.d.ts +50 -0
  23. package/dist/core/mcp-tool.js +61 -0
  24. package/dist/core/tool-contract.d.ts +68 -0
  25. package/dist/core/tool-contract.js +113 -0
  26. package/dist/core/types.d.ts +89 -0
  27. package/dist/core/validate.js +22 -0
  28. package/dist/eval.d.ts +69 -13
  29. package/dist/eval.js +106 -51
  30. package/dist/leaderboard.js +61 -3
  31. package/dist/plugin-loader.d.ts +1 -0
  32. package/dist/plugin-loader.js +71 -18
  33. package/dist/scan-behavioral.d.ts +73 -0
  34. package/dist/scan-behavioral.js +150 -0
  35. package/dist/scan.d.ts +126 -1
  36. package/dist/scan.js +559 -40
  37. package/package.json +27 -4
  38. package/skills/migrate-to-spec/SKILL.md +0 -2
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Tool-contract verification — the cross-referencing moat ("valid is not true")
3
+ * applied to a subagent's declared `tools:` rail. A subagent may only run
4
+ * built-in tools from the harness dialect's catalog or an MCP tool; anything else
5
+ * is a typo or a nonexistent / never-available tool — a guaranteed-dead reference
6
+ * a compiler catches, not a runtime surprise.
7
+ *
8
+ * ONE pure detector (`one-detector-no-drift`), reused by THREE callers so they
9
+ * can't disagree: `compileAgent` (spec authoring), `scan` (read-only audit of a
10
+ * shipped plugin), and the `agent-tool-contract` lint rule (the severity-gated
11
+ * commit gate). The dialect is injected (core ⊄ adapter) — the composition root
12
+ * passes `claudeCodeDialect` / `codexDialect`.
13
+ *
14
+ * Scope note: this validates a SUBAGENT contract against the SUBAGENT catalog
15
+ * (`builtinAgentTools` / `neverAvailableTools`). A skill's `allowed-tools` is a
16
+ * DIFFERENT namespace (skills legitimately use `AskUserQuestion`, `TaskCreate`,
17
+ * … which are never-available to a subagent), so it is deliberately NOT validated
18
+ * here — doing so against the agent catalog would be a false-positive factory.
19
+ */
20
+ import type { HarnessDialect } from "./dialect.js";
21
+ export type ToolIssueKind = "never-available" | "unknown";
22
+ export interface ToolIssue {
23
+ readonly tool: string;
24
+ readonly kind: ToolIssueKind;
25
+ /** Closest known built-in tool (did-you-mean), or null. */
26
+ readonly suggestion: string | null;
27
+ /** A ready-to-show, actionable message. */
28
+ readonly message: string;
29
+ }
30
+ /**
31
+ * Closest known built-in tool by edit distance (≤ 2), for a "did you mean" hint.
32
+ * The ≤ 2 bound is deliberately tight: a suggestion is a CONFIDENCE signal (this
33
+ * `unknown` is really a typo of a real tool), and a loose bound mis-suggests —
34
+ * `TaskGet → Task?` (distance 3) is a real tool set, not a typo of `Task`.
35
+ */
36
+ export declare function closestTool(tool: string, dialect: HarnessDialect): string | null;
37
+ /**
38
+ * The HIGH-CONFIDENCE subset of a contract's issues — the ones safe to flag when
39
+ * AUDITING a third-party plugin (scan / lint), where the catalog can't know
40
+ * every tool (plugin-/MCP-provided, newer platform tools). Only two are confident:
41
+ * a `never-available` tool (a curated denylist) and an `unknown` with a close
42
+ * typo suggestion (`Edt → Edit`). A bare `unknown` with no near match is NOT
43
+ * flagged here — it is more likely a tool vigiles doesn't know than a defect
44
+ * (sweeping real plugins surfaced a 280★ plugin using `TaskCreate/TaskGet/…`
45
+ * consistently; flagging those would be crying wolf). `compileAgent` stays strict
46
+ * — when you author your OWN spec, every unrecognized tool is worth an error.
47
+ */
48
+ export declare function confidentToolIssues(issues: readonly ToolIssue[]): ToolIssue[];
49
+ /**
50
+ * Verify a subagent's `disallowedTools:` BLOCK-list — the mirror of the allow
51
+ * contract. A typo here is dangerous: you meant to block `Bash` but wrote `Bsh`,
52
+ * so nothing is blocked and the dangerous tool stays available, silently. Returns
53
+ * one {@link ToolIssue} per entry that's a CLOSE TYPO of a real built-in (the
54
+ * high-confidence signal). Deliberately NOT flagged: a real built-in (it IS being
55
+ * blocked — correct), a never-available tool (harmless to block), an MCP tool (a
56
+ * legitimate plugin tool to block), or a bare unknown with no near match (likely
57
+ * a plugin/MCP tool, not a typo — the cry-wolf trap). The block-list inverts the
58
+ * allow check: never-available is fine to list, a typo is the actual defect.
59
+ */
60
+ export declare function disallowedToolIssues(tools: readonly string[], dialect: HarnessDialect): ToolIssue[];
61
+ /**
62
+ * Verify a subagent's `tools:` contract against the dialect catalog. Returns one
63
+ * {@link ToolIssue} per offending entry (empty when every tool is a real built-in
64
+ * or a well-formed MCP tool). A `Tool(restriction)` suffix (e.g. `Bash(git:*)`)
65
+ * is stripped to its base tool before checking.
66
+ */
67
+ export declare function verifyToolContract(tools: readonly string[], dialect: HarnessDialect): ToolIssue[];
68
+ //# sourceMappingURL=tool-contract.d.ts.map
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.closestTool = closestTool;
4
+ exports.confidentToolIssues = confidentToolIssues;
5
+ exports.disallowedToolIssues = disallowedToolIssues;
6
+ exports.verifyToolContract = verifyToolContract;
7
+ const linters_js_1 = require("./linters.js");
8
+ /**
9
+ * Closest known built-in tool by edit distance (≤ 2), for a "did you mean" hint.
10
+ * The ≤ 2 bound is deliberately tight: a suggestion is a CONFIDENCE signal (this
11
+ * `unknown` is really a typo of a real tool), and a loose bound mis-suggests —
12
+ * `TaskGet → Task?` (distance 3) is a real tool set, not a typo of `Task`.
13
+ */
14
+ function closestTool(tool, dialect) {
15
+ let best = null;
16
+ let bestDistance = Infinity;
17
+ for (const known of dialect.builtinAgentTools) {
18
+ const d = (0, linters_js_1.editDistance)(tool.toLowerCase(), known.toLowerCase());
19
+ if (d < bestDistance) {
20
+ bestDistance = d;
21
+ best = known;
22
+ }
23
+ }
24
+ return bestDistance <= 2 ? best : null;
25
+ }
26
+ /**
27
+ * The HIGH-CONFIDENCE subset of a contract's issues — the ones safe to flag when
28
+ * AUDITING a third-party plugin (scan / lint), where the catalog can't know
29
+ * every tool (plugin-/MCP-provided, newer platform tools). Only two are confident:
30
+ * a `never-available` tool (a curated denylist) and an `unknown` with a close
31
+ * typo suggestion (`Edt → Edit`). A bare `unknown` with no near match is NOT
32
+ * flagged here — it is more likely a tool vigiles doesn't know than a defect
33
+ * (sweeping real plugins surfaced a 280★ plugin using `TaskCreate/TaskGet/…`
34
+ * consistently; flagging those would be crying wolf). `compileAgent` stays strict
35
+ * — when you author your OWN spec, every unrecognized tool is worth an error.
36
+ */
37
+ function confidentToolIssues(issues) {
38
+ return issues.filter((i) => i.kind === "never-available" || i.suggestion !== null);
39
+ }
40
+ /**
41
+ * Verify a subagent's `disallowedTools:` BLOCK-list — the mirror of the allow
42
+ * contract. A typo here is dangerous: you meant to block `Bash` but wrote `Bsh`,
43
+ * so nothing is blocked and the dangerous tool stays available, silently. Returns
44
+ * one {@link ToolIssue} per entry that's a CLOSE TYPO of a real built-in (the
45
+ * high-confidence signal). Deliberately NOT flagged: a real built-in (it IS being
46
+ * blocked — correct), a never-available tool (harmless to block), an MCP tool (a
47
+ * legitimate plugin tool to block), or a bare unknown with no near match (likely
48
+ * a plugin/MCP tool, not a typo — the cry-wolf trap). The block-list inverts the
49
+ * allow check: never-available is fine to list, a typo is the actual defect.
50
+ */
51
+ function disallowedToolIssues(tools, dialect) {
52
+ const never = new Set(dialect.neverAvailableTools);
53
+ const issues = [];
54
+ for (const raw of tools) {
55
+ const tool = raw.split("(")[0].trim();
56
+ if (tool === "" || tool === "*")
57
+ continue;
58
+ if (dialect.builtinAgentTools.includes(tool))
59
+ continue; // legitimately blocked
60
+ if (never.has(tool))
61
+ continue; // harmless to list (already unavailable)
62
+ if (dialect.mcpToolPattern.test(tool))
63
+ continue; // a real plugin/MCP tool to block
64
+ const near = closestTool(tool, dialect);
65
+ if (near === null)
66
+ continue; // bare unknown → likely a plugin tool, not a typo
67
+ issues.push({
68
+ tool,
69
+ kind: "unknown",
70
+ suggestion: near,
71
+ message: `disallowedTools entry "${tool}" matches no real tool — it blocks nothing. Did you mean "${near}"?`,
72
+ });
73
+ }
74
+ return issues;
75
+ }
76
+ /**
77
+ * Verify a subagent's `tools:` contract against the dialect catalog. Returns one
78
+ * {@link ToolIssue} per offending entry (empty when every tool is a real built-in
79
+ * or a well-formed MCP tool). A `Tool(restriction)` suffix (e.g. `Bash(git:*)`)
80
+ * is stripped to its base tool before checking.
81
+ */
82
+ function verifyToolContract(tools, dialect) {
83
+ const never = new Set(dialect.neverAvailableTools);
84
+ const issues = [];
85
+ for (const raw of tools) {
86
+ const tool = raw.split("(")[0].trim(); // strip a Tool(restriction) suffix
87
+ if (tool === "" || tool === "*")
88
+ continue; // "" / "*" = wildcard, inherits all
89
+ if (never.has(tool)) {
90
+ issues.push({
91
+ tool,
92
+ kind: "never-available",
93
+ suggestion: null,
94
+ message: `Tool "${tool}" is never available to a subagent — remove it from the tools list.`,
95
+ });
96
+ continue;
97
+ }
98
+ if (dialect.builtinAgentTools.includes(tool))
99
+ continue;
100
+ if (dialect.mcpToolPattern.test(tool))
101
+ continue;
102
+ const near = closestTool(tool, dialect);
103
+ const hint = near ? ` Did you mean "${near}"?` : "";
104
+ issues.push({
105
+ tool,
106
+ kind: "unknown",
107
+ suggestion: near,
108
+ message: `Unknown tool "${tool}" — use a built-in tool (${dialect.builtinAgentTools.join(", ")}) or an MCP tool (mcp__server__tool).${hint}`,
109
+ });
110
+ }
111
+ return issues;
112
+ }
113
+ //# sourceMappingURL=tool-contract.js.map
@@ -108,6 +108,95 @@ export interface RulesConfig {
108
108
  * block the edit, false → off.
109
109
  */
110
110
  "unmarked-refs"?: RuleSeverity;
111
+ /**
112
+ * Cross-reference each subagent's `tools:` rail against the harness tool
113
+ * catalog — flag a never-available tool or a close typo (the moat). Only
114
+ * high-confidence issues are reported (a bare unrecognized tool is likely
115
+ * plugin/MCP-provided, never flagged). Off unless set; "warn" surfaces,
116
+ * "error" gates CI. Same detector as `scan` + `compileAgent`.
117
+ */
118
+ "agent-tool-contract"?: RuleSeverity;
119
+ /**
120
+ * Flag a hook registered under an event name the harness doesn't define (a
121
+ * typo → the hook never fires). High-precision: close typos only, never a
122
+ * framework/custom event. Default "warn"; "error" gates CI. Same detector as
123
+ * `scan`.
124
+ */
125
+ "hook-events"?: RuleSeverity;
126
+ /**
127
+ * Flag a skill/agent missing a required frontmatter field — a skill needs
128
+ * `name` (to load), an agent needs `name` + `description`. A broken surface
129
+ * that won't register. Default "warn"; "error" gates CI. Same detector as `scan`.
130
+ */
131
+ "agent-frontmatter"?: RuleSeverity;
132
+ /**
133
+ * Flag a declared MCP server that can't start — neither a `command` (stdio)
134
+ * nor a `url` (http/sse). Default "warn"; "error" gates CI. Same detector as
135
+ * `scan`. (JSON `.mcp.json`/manifest `mcpServers`; Codex TOML not yet parsed.)
136
+ */
137
+ "mcp-config"?: RuleSeverity;
138
+ /**
139
+ * RECOMMEND (not require) that a SKILL.md declares an explicit `name` +
140
+ * `description` rather than relying on the dir-name / first-paragraph
141
+ * fallbacks — a more reliable trigger surface. The skill still loads without
142
+ * them, so this is a best-practice nudge: default "warn"; set "error" to
143
+ * enforce on your own skills. Same detector as `scan` (skillMetaIssues).
144
+ */
145
+ "skill-frontmatter"?: RuleSeverity;
146
+ /**
147
+ * Cross-reference an `mcp__server__tool` in a subagent's contract against the
148
+ * plugin's declared `mcpServers` — flag a server the plugin doesn't declare
149
+ * (the MCP half of the tool moat; `agent-tool-contract` checks the built-in
150
+ * half). High-precision: only flags when the plugin SHIPS a declared set,
151
+ * allowlists harness built-ins (`ide`), and skips the plugin-namespaced
152
+ * `mcp__plugin_…` form. Default "warn"; "error" gates CI. Same detector as
153
+ * `scan` (mcpToolIssues).
154
+ */
155
+ "mcp-tool-resolves"?: RuleSeverity;
156
+ /**
157
+ * Flag a hook command that references a script file which doesn't exist on
158
+ * disk (with `${CLAUDE_PLUGIN_ROOT}` resolved) — the hook silently never runs.
159
+ * FP-safe: skips unresolved `$VAR` paths, existence-guarded one-liners, and
160
+ * inline commands. Matches Anthropic's own `claude plugin validate`. Default
161
+ * "warn"; "error" gates CI. Same detector as `scan` (hooks status "missing").
162
+ */
163
+ "hook-script-exists"?: RuleSeverity;
164
+ /**
165
+ * Cross-reference a subagent's `disallowedTools:` block-list against the
166
+ * catalog — the deny-side mirror of `agent-tool-contract`. A close typo there
167
+ * blocks NOTHING (you meant to deny `Bash`, wrote `Bsh`), leaving the tool
168
+ * available. High-precision: close-typo only (a never-available tool is
169
+ * harmless to list, a bare unknown is likely a plugin tool). Default "warn";
170
+ * "error" gates CI. Same detector as `scan` (disallowedToolIssues).
171
+ */
172
+ "disallowed-tools-contract"?: RuleSeverity;
173
+ /**
174
+ * Flag two model-invocable skills whose descriptions are near-identical — the
175
+ * selector can't tell them apart, so the wrong one fires (a precision
176
+ * collision). A DETERMINISTIC NCD proxy for a `--trigger`-class behavioral bug;
177
+ * calibrated FP-safe (only basically-identical text, below the sweep's
178
+ * most-similar distinct pair). Default "warn"; "error" gates CI. Same detector
179
+ * as `scan` (descriptionOverlaps).
180
+ */
181
+ "description-overlap"?: RuleSeverity;
182
+ /**
183
+ * Flag a skill/agent whose `---` frontmatter block EXISTS but isn't valid YAML
184
+ * — fields may not parse as intended. CAVEAT: a real YAML parser (js-yaml) is
185
+ * stricter than some loaders, so a one-line `description:` containing a `: `
186
+ * colon or an `<example>` block is flagged even though it may still load.
187
+ * Hence default "warn" (a nudge), not "error" — verify before enforcing. Same
188
+ * detector as `scan` (malformedFrontmatter).
189
+ */
190
+ "frontmatter-valid"?: RuleSeverity;
191
+ /**
192
+ * Flag a `type: "mcp_tool"` hook action that's incomplete (missing `server` /
193
+ * `tool`) or targets a server the plugin doesn't declare in `mcpServers` — the
194
+ * hook silently never dispatches. High-precision: the undeclared-server half is
195
+ * gated on the plugin shipping a declared set and allowlists built-ins (`ide`),
196
+ * mirroring `mcp-tool-resolves`. Default "warn"; "error" gates CI. Same detector
197
+ * as `scan` (mcpHookIssues).
198
+ */
199
+ "mcp-hook-target-resolves"?: RuleSeverity;
111
200
  }
112
201
  /** Extract severity from a rule value (handles both simple and tuple forms). */
113
202
  export declare function ruleSeverity<T>(rule: RuleWithOptions<T> | undefined): RuleSeverity;
@@ -46,6 +46,28 @@ const DEFAULT_RULES = {
46
46
  "untested-agent": "warn",
47
47
  "untested-hook": "warn",
48
48
  "unmarked-refs": "warn",
49
+ // High-precision (never-available + close typos only), so on by default at warn.
50
+ "agent-tool-contract": "warn",
51
+ // High-precision (close typos only), on by default at warn.
52
+ "hook-events": "warn",
53
+ // Missing required frontmatter (name/description) — on by default at warn.
54
+ "agent-frontmatter": "warn",
55
+ // A declared MCP server with no command/url can't start — on by default at warn.
56
+ "mcp-config": "warn",
57
+ // Best-practice nudge (skills load without frontmatter) — warn, not error.
58
+ "skill-frontmatter": "warn",
59
+ // High-precision (gated on a declared MCP set; built-ins allowlisted) — warn.
60
+ "mcp-tool-resolves": "warn",
61
+ // A hook script referenced but missing never runs — on by default at warn.
62
+ "hook-script-exists": "warn",
63
+ // High-precision (close-typo only) deny-list mirror of agent-tool-contract.
64
+ "disallowed-tools-contract": "warn",
65
+ // Deterministic NCD precision proxy (near-identical skill descriptions) — warn.
66
+ "description-overlap": "warn",
67
+ // Malformed-YAML frontmatter — WARN only (js-yaml is stricter than some loaders).
68
+ "frontmatter-valid": "warn",
69
+ // A mcp_tool hook incomplete / targeting an undeclared server — on by default at warn.
70
+ "mcp-hook-target-resolves": "warn",
49
71
  };
50
72
  const DEFAULT_CONFIG = {
51
73
  ruleMarkers: ["headings", "checkboxes"],
package/dist/eval.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ToolCall, type Trace } from "./harness-test.js";
1
+ import { parseToolCalls, parseHooks, parseSubagents, type ToolCall, type Trace } from "./harness-test.js";
2
2
  import { type CacheMode } from "./eval-cache.js";
3
3
  import type { Check, CheckJSON } from "./check.js";
4
4
  import { type Comparison } from "./stats.js";
@@ -250,6 +250,9 @@ export type AgentRunner = (args: AgentRunArgs) => Promise<RunOut>;
250
250
  * leak the host environment into an untrusted, model-driven run.
251
251
  */
252
252
  export declare function resolveSpawnEnv(a: Pick<AgentRunArgs, "env" | "replaceEnv">, base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
253
+ /** The real `claude`-spawning runner (composition root). Exported so other
254
+ * real-model entries (e.g. `scan --trigger`) bind the same runner. */
255
+ export declare function spawnAgent(a: AgentRunArgs): Promise<RunOut>;
253
256
  /**
254
257
  * Run the eval: every arm × every trial against the real `claude` CLI, with the
255
258
  * metric computed per run and aggregated per arm. Requires `claude` on PATH and
@@ -392,6 +395,24 @@ export declare function checkReportToJUnit(report: CheckReport, opts?: {
392
395
  }): string;
393
396
  /** Parse per-run cost/latency/tokens from a stream — pure, model-free. */
394
397
  export declare function parseUsage(stdout: string): EvalUsage;
398
+ /**
399
+ * The harness-specific half of a run trace: how a real model's raw stdout maps
400
+ * to the common fields. Claude Code's `parseClaudeRun` reads its stream-json; a
401
+ * second harness (Codex) supplies its own parser of `codex exec --json` JSONL, so
402
+ * the eval tier (`measureTriggerRate`/`runEval`) isn't bound to Claude's format.
403
+ * The non-harness fields (cwd/exitCode/stdout/file/sh) stay in `makeContext`.
404
+ */
405
+ export interface ParsedModelRun {
406
+ readonly turns: number;
407
+ readonly output: string;
408
+ readonly toolCalls: ReturnType<typeof parseToolCalls>;
409
+ readonly hooks: ReturnType<typeof parseHooks>;
410
+ readonly subagents: ReturnType<typeof parseSubagents>;
411
+ readonly usage: EvalUsage;
412
+ }
413
+ export type ModelOutputParser = (out: RunOut) => ParsedModelRun;
414
+ /** Parse Claude Code's stream-json stdout into the common trace fields. */
415
+ export declare function parseClaudeRun(out: RunOut): ParsedModelRun;
395
416
  /** Aggregate per-run metrics: mean for numbers, fraction-true (0..1) for booleans. */
396
417
  export declare function aggregate(rows: readonly Metrics[]): Record<string, number>;
397
418
  /**
@@ -593,6 +614,22 @@ export interface TriggerRateSpec {
593
614
  readonly timeoutMs?: number;
594
615
  /** Seconds to wait between runs (avoid rate-limit bursts). Default 4. */
595
616
  readonly spacingSec?: number;
617
+ /**
618
+ * Files (path → contents) seeded into every run's cwd before the prompt — the
619
+ * filesystem CONTEXT the skill is measured in. The default empty cwd is faithful
620
+ * for opening-move skills ("describe a feature", "debug this") but biased-low for
621
+ * skills whose trigger is a repo STATE ("in a git repo", "dirty tree"); seed that
622
+ * state here so recall is honest instead of an artifact of the cold start. Mirrors
623
+ * `MeasureSpec.fixture`. See `research/plugin-behavioral-findings.md`.
624
+ */
625
+ readonly fixture?: Record<string, string>;
626
+ /**
627
+ * How many runs to execute in parallel across the whole prompts × trials grid.
628
+ * Default 1 (serial, the politest to rate limits). Raise it to cut wall-clock on
629
+ * a large prompt set or roster sweep — the `spacingSec` pause still applies per
630
+ * run, so it stays best-effort polite. Mirrors `EvalSpec.concurrency`.
631
+ */
632
+ readonly concurrency?: number;
596
633
  }
597
634
  /** Per-prompt trigger result: how many of its trials fired. */
598
635
  export interface PromptTriggerStat {
@@ -630,7 +667,29 @@ export interface TriggerRateReport {
630
667
  * description). A non-zero count is the whole-harness measurement.
631
668
  */
632
669
  readonly competitors: number;
670
+ /**
671
+ * Runs EXCLUDED because the turn errored / was rate-limited (detected by the
672
+ * driver's `runError`), present only when > 0. These are NOT counted in `n` or
673
+ * as misses — so `rate` reflects only valid runs. A large `errored` relative to
674
+ * `n` means the measurement is thin (e.g. a Codex usage limit was hit); re-run.
675
+ */
676
+ readonly errored?: number;
633
677
  }
678
+ /**
679
+ * An eval-tier transport: how to RUN a real harness turn and PARSE its output.
680
+ * The default is Claude Code (`claudeEvalDriver`); a second harness supplies its
681
+ * own (e.g. `codexEvalDriver` from `vigiles/codex`) and passes it as
682
+ * `measureTriggerRate(spec, { evalDriver })` — the eval-tier analog of
683
+ * `runHarnessTest`'s `{ adapter }`. `runError` lets the loop drop an
684
+ * errored/rate-limited turn instead of scoring it as a miss.
685
+ */
686
+ export interface EvalDriver {
687
+ readonly runner: AgentRunner;
688
+ readonly parse: ModelOutputParser;
689
+ readonly runError?: (out: RunOut) => string | null;
690
+ }
691
+ /** The default (Claude Code) eval driver: real `claude` + stream-json parsing. */
692
+ export declare const claudeEvalDriver: EvalDriver;
634
693
  /**
635
694
  * Package loose `<skillsDir>/<name>/SKILL.md` skills into a throwaway plugin dir
636
695
  * that `claude --plugin-dir` accepts — so repo-local skills (e.g. `.claude/skills`)
@@ -707,20 +766,17 @@ export declare function packageInstallSet(opts: {
707
766
  dir: string;
708
767
  added: number;
709
768
  };
769
+ export declare function measureTriggerRateWith(spec: TriggerRateSpec, runner: AgentRunner, parse?: ModelOutputParser, runError?: (out: RunOut) => string | null): Promise<TriggerRateReport>;
710
770
  /**
711
- * Trigger-rate orchestration every prompt × trial via `runner`, the `fired`
712
- * predicate evaluated per run and aggregated into an overall + per-prompt rate.
713
- * With `irrelevantPrompts`, also runs the precision side (firing there is a false
714
- * positive) and adds `falsePositiveRate` + `precision`. Exported with an
715
- * injectable `runner` so the loop is unit-testable without a model;
716
- * `measureTriggerRate` is this with the real agent runner.
717
- */
718
- export declare function measureTriggerRateWith(spec: TriggerRateSpec, runner: AgentRunner): Promise<TriggerRateReport>;
719
- /**
720
- * Measure a skill/behaviour's real trigger rate across prompts × trials against
721
- * the real `claude` CLI. Requires `claude` + model auth.
771
+ * Measure a skill/behaviour's real trigger rate across prompts × trials. Defaults
772
+ * to the real `claude` CLI (`claudeEvalDriver`); pass `{ evalDriver }` to drive a
773
+ * second harness e.g. `measureTriggerRate(spec, { evalDriver: codexEvalDriver })`
774
+ * from `vigiles/codex` (the eval-tier analog of `runHarnessTest`'s `{ adapter }`).
775
+ * Requires that harness's binary + auth.
722
776
  */
723
- export declare function measureTriggerRate(spec: TriggerRateSpec): Promise<TriggerRateReport>;
777
+ export declare function measureTriggerRate(spec: TriggerRateSpec, opts?: {
778
+ evalDriver?: EvalDriver;
779
+ }): Promise<TriggerRateReport>;
724
780
  /** Format a trigger-rate report: overall %, then each prompt's rate. */
725
781
  export declare function formatTriggerRateReport(report: TriggerRateReport): string;
726
782
  //# sourceMappingURL=eval.d.ts.map