vigiles 2.4.0 → 2.6.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.
@@ -13,9 +13,16 @@
13
13
  * `expect(...).toHaveCreated(...)` sugar. The signature is identical for
14
14
  * vitest and jest, so the same object supports both.
15
15
  */
16
- import { type HarnessTestSpec, type HarnessTestResult, type ToolCall } from "./harness-test.js";
17
- import type { EvalReport } from "./eval.js";
18
- import type { HookRunResult } from "./run-hook.js";
16
+ import { type HarnessTestSpec, type HarnessTestResult, type ToolCall, type Trace } from "./harness-test.js";
17
+ import type { EvalReport, TriggerRateReport } from "./eval.js";
18
+ import type { HookRunResult, EgressAttempt } from "./run-hook.js";
19
+ import type { OutputContract } from "./spec.js";
20
+ import { type ParsedAgentResult } from "./agent-result.js";
21
+ import { type BaselineFile, type DiffOptions } from "./eval-baseline.js";
22
+ export { compareArms } from "./stats.js";
23
+ export type { Comparison } from "./stats.js";
24
+ export { diffReports, toBaselineFile, parseBaselineFile, readBaseline, writeBaseline, formatBaselineDiff, diffToJUnit, } from "./eval-baseline.js";
25
+ export type { BaselineFile, BaselineDiff, MetricDiff, DiffStatus, DiffOptions, } from "./eval-baseline.js";
19
26
  /**
20
27
  * Run a harness test, hand the result to `fn`, and always clean up the sandbox.
21
28
  * Returns whatever `fn` returns. Use this instead of calling `cleanup()` by
@@ -32,30 +39,134 @@ export declare function assertServedTurns(r: HarnessTestResult, n: number): void
32
39
  export declare function assertHookBlocked(r: HookRunResult): void;
33
40
  /** Assert a `runHook` result allowed (did not block). */
34
41
  export declare function assertHookAllowed(r: HookRunResult): void;
42
+ /** Anything carrying recorded egress attempts (a runHook recordEgress result). */
43
+ interface HasEgress {
44
+ readonly egress: readonly EgressAttempt[];
45
+ }
46
+ /** The `host:port` strings a run attempted, e.g. `["registry.npmjs.org:443"]`. */
47
+ export declare function egressHosts(r: HasEgress): string[];
48
+ /** Assert the confined run made NO network egress attempt at all. */
49
+ export declare function assertNoEgress(r: HasEgress): void;
50
+ /**
51
+ * Assert every egress attempt went to an allowed host. `allowed` matches a host
52
+ * (exact string or regex), or a specific `host:port`. Any attempt outside the
53
+ * allowlist fails, naming the offender — exfil / unexpected-registry detection.
54
+ */
55
+ export declare function assertEgressOnly(r: HasEgress, allowed: ReadonlyArray<string | RegExp>): void;
56
+ /** Anything carrying recorded file writes (a confined runHook result). */
57
+ interface HasWrites {
58
+ readonly filesWritten: readonly string[];
59
+ }
60
+ /** Assert the run wrote NO file matching `pattern` (substring or regex). */
61
+ export declare function assertNoWrite(r: HasWrites, pattern: string | RegExp): void;
62
+ /** Assert every file the run wrote matches one of `allowed` (substring or regex). */
63
+ export declare function assertWroteOnly(r: HasWrites, allowed: ReadonlyArray<string | RegExp>): void;
64
+ /**
65
+ * Assert the worker's output is a success result, and return its `value`. With a
66
+ * `contract`, the value is validated against the success shape (a wrong/missing
67
+ * field fails the assertion). A malformed or error result throws.
68
+ */
69
+ export declare function assertAgentOk(output: string, contract?: OutputContract): Record<string, unknown>;
70
+ /**
71
+ * Assert the worker's output is an error result, and return its `error`. The
72
+ * railway's error track — proves the worker reported failure with rich detail
73
+ * (not that it crashed or returned prose). A malformed or success result throws.
74
+ */
75
+ export declare function assertAgentErr(output: string, contract?: OutputContract): Record<string, unknown>;
76
+ /**
77
+ * Assert the parsed result satisfies `predicate` — the general form, for
78
+ * checking rich detail (e.g. `(r) => r.kind === "ok" && r.value.files.length > 0`).
79
+ */
80
+ export declare function assertAgentResult(output: string, predicate: (r: ParsedAgentResult) => boolean, contract?: OutputContract): void;
81
+ /**
82
+ * Did the agent invoke a tool whose name matches `name` (string = exact,
83
+ * RegExp = test)? The predicate behind `assertToolUsed` / `assertToolNotUsed`.
84
+ */
85
+ export declare function usedTool(trace: Trace, name: string | RegExp): boolean;
86
+ /** How many tools matching `name` the agent invoked. Behind `assertToolCount`. */
87
+ export declare function toolCount(trace: Trace, name: string | RegExp): number;
88
+ /**
89
+ * Did the `Skill` tool resolve `skill` (e.g. `"superpowers:test-driven-development"`)
90
+ * without error? The skill-activation predicate behind `assertSkillResolved`.
91
+ */
92
+ export declare function skillResolved(trace: Trace, skill: string): boolean;
93
+ /**
94
+ * Did the agent invoke a tool matching `name` whose INPUT satisfies
95
+ * `inputMatcher` — a tool-ARGUMENT predicate (DeepEval-style), e.g. an `Edit`
96
+ * that targeted the right file. The predicate behind `assertToolUsedWith`.
97
+ */
98
+ export declare function toolUsedWith(trace: Trace, name: string | RegExp, inputMatcher: (input: unknown) => boolean): boolean;
99
+ /**
100
+ * Does the agent's final answer (`trace.output`) contain `needle` (string =
101
+ * substring, RegExp = test)? The output predicate behind `assertOutputContains`
102
+ * — the DeepEval-style "what did the agent actually say" check.
103
+ */
104
+ export declare function outputContains(trace: Trace, needle: string | RegExp): boolean;
105
+ /**
106
+ * Did ANY request the model received contain `needle` — searching the system
107
+ * prompt and every message across all requests? The predicate that proves
108
+ * injected context *reached the model*: a SessionStart hook's `additionalContext`
109
+ * or a slash command's expansion. Harness tier only — the eval tier drives the
110
+ * real API, so its `modelRequests` (and this) is empty. Behind `assertRequestContains`.
111
+ */
112
+ export declare function requestContains(trace: Trace, needle: string | RegExp): boolean;
113
+ /**
114
+ * Did a hook matching `name` fire? Matches against both the hook label
115
+ * (`"PreToolUse:Edit"`) and the bare event (`"PreToolUse"`), so `/PreToolUse/`
116
+ * or `"PreToolUse:Edit"` both work. The predicate behind `assertHookFired`.
117
+ */
118
+ export declare function hookFired(trace: Trace, name: string | RegExp): boolean;
119
+ /** Did a hook matching `name` fire AND block (exit ≠ 0 / outcome "error")? */
120
+ export declare function hookBlocked(trace: Trace, name: string | RegExp): boolean;
35
121
  /**
36
122
  * Assert the agent invoked a tool whose name matches `name` (string = exact,
37
123
  * RegExp = test) — e.g. a skill (`"Skill"`), an MCP tool (`/^mcp__github__/`), or
38
124
  * a subagent (`"Task"`). Needs `transcript: true`. The action invariant the
39
125
  * skill/MCP/command surfaces are really about.
40
126
  */
41
- export declare function assertToolUsed(r: HarnessTestResult, name: string | RegExp): void;
127
+ export declare function assertToolUsed(trace: Trace, name: string | RegExp): void;
42
128
  /**
43
129
  * Assert the agent did NOT invoke any tool matching `name` — the safety negative
44
130
  * (e.g. a destructive MCP tool was never called). "File unchanged" can pass by
45
131
  * accident; "the tool was never used" is the real invariant. Needs `transcript`.
46
132
  */
47
- export declare function assertToolNotUsed(r: HarnessTestResult, name: string | RegExp): void;
133
+ export declare function assertToolNotUsed(trace: Trace, name: string | RegExp): void;
48
134
  /**
49
135
  * Assert the `Skill` tool resolved `skill` (e.g. `"superpowers:test-driven-development"`)
50
136
  * without error — the correct skill-activation invariant, vs. grepping the body.
51
137
  */
52
- export declare function assertSkillResolved(r: HarnessTestResult, skill: string): void;
138
+ export declare function assertSkillResolved(trace: Trace, skill: string): void;
139
+ /**
140
+ * Assert the agent invoked a tool matching `name` whose INPUT satisfies
141
+ * `inputMatcher` — a tool-ARGUMENT invariant (DeepEval-style). Asserts not just
142
+ * *that* a tool ran but *with what args*, e.g. an `Edit` that targeted the right
143
+ * file: `assertToolUsedWith(r, "Edit", (i) => (i as { file_path?: string })
144
+ * .file_path === "src/x.ts")`. Needs `transcript`.
145
+ */
146
+ export declare function assertToolUsedWith(trace: Trace, name: string | RegExp, inputMatcher: (input: unknown) => boolean, message?: string): void;
147
+ /** Assert the agent's final answer contains `needle` (string substring / RegExp). */
148
+ export declare function assertOutputContains(trace: Trace, needle: string | RegExp): void;
149
+ /**
150
+ * Assert some request the model received contained `needle` — the "did the
151
+ * injected context land" invariant (SessionStart `additionalContext`, slash
152
+ * command expansion). Harness tier only; a zero-request trace fails with a hint
153
+ * that the eval tier can't capture requests.
154
+ */
155
+ export declare function assertRequestContains(trace: Trace, needle: string | RegExp): void;
156
+ /**
157
+ * Assert a hook matching `name` fired (and, with `{ blocked: true }`, that it
158
+ * blocked) — the honest hook-firing check, recorded from the run's stream rather
159
+ * than inferred from a marker file the hook had to write. Needs `transcript`.
160
+ */
161
+ export declare function assertHookFired(trace: Trace, name: string | RegExp, opts?: {
162
+ blocked?: boolean;
163
+ }): void;
53
164
  /**
54
165
  * Assert how many tools matching `name` the agent invoked is within bounds — a
55
166
  * budget invariant (e.g. `{ max: 1 }` = "at most one Write", `{ exactly: 0 }` =
56
167
  * "never touched it"). Catches runaway loops and wasted work. Needs `transcript`.
57
168
  */
58
- export declare function assertToolCount(r: HarnessTestResult, name: string | RegExp, bounds: {
169
+ export declare function assertToolCount(trace: Trace, name: string | RegExp, bounds: {
59
170
  min?: number;
60
171
  max?: number;
61
172
  exactly?: number;
@@ -66,25 +177,77 @@ export declare function assertToolCount(r: HarnessTestResult, name: string | Reg
66
177
  * Edit. For a stricter rule (every Edit preceded by a Read), use `assertToolCalls`.
67
178
  * Needs `transcript`.
68
179
  */
69
- export declare function assertToolSequence(r: HarnessTestResult, names: ReadonlyArray<string | RegExp>): void;
180
+ export declare function assertToolSequence(trace: Trace, names: ReadonlyArray<string | RegExp>): void;
70
181
  /**
71
182
  * The escape hatch: assert any custom invariant over the full list of tool calls
72
183
  * the agent made — for rules the helpers above don't express, e.g. "every Edit
73
184
  * was preceded by a Read of that file". Needs `transcript`.
74
185
  */
75
- export declare function assertToolCalls(r: HarnessTestResult, predicate: (calls: readonly ToolCall[]) => boolean, message?: string): void;
186
+ export declare function assertToolCalls(trace: Trace, predicate: (calls: readonly ToolCall[]) => boolean, message?: string): void;
187
+ /**
188
+ * Did `arm` succeed on EVERY trial for `metric` — τ-bench pass^k = 1? The
189
+ * reliability predicate over an eval report (vs. `improvement`, which reads the
190
+ * mean gap). Reads `report.arms[arm].stats[metric].passK`.
191
+ */
192
+ export declare function reliable(report: EvalReport, arm: string, metric: string): boolean;
193
+ /**
194
+ * Assert `arm` passed `metric` on every trial (pass^k = 1) — the reliability
195
+ * gate for a non-deterministic harness ("worked every time", not "on average").
196
+ */
197
+ export declare function assertReliable(report: EvalReport, opts: {
198
+ arm: string;
199
+ metric: string;
200
+ }): void;
76
201
  /** The gap on `metric` between two arms (arm − baseline). */
77
202
  export declare function improvement(report: EvalReport, baseline: string, arm: string, metric: string): number;
78
203
  /**
79
- * Assert `arm` beats `baseline` on `metric` by more than `by`. With `by` left at
80
- * 0 this just asserts a positive gap; pass the combined se to demand the gap
81
- * clear the noise floor.
204
+ * Did `arm` *significantly* beat `baseline` on `metric` a positive gap whose
205
+ * two-sided Welch t-test p-value is below `alpha` (default 0.05)? The grounded
206
+ * upgrade over `improvement`: the noise floor is computed from the arms' spread,
207
+ * not hand-fed. False when either arm/metric is missing. See `src/stats.ts`.
208
+ */
209
+ export declare function significantlyBeats(report: EvalReport, baseline: string, arm: string, metric: string, alpha?: number): boolean;
210
+ /**
211
+ * Assert `arm` significantly beats `baseline` on `metric` (positive gap, p < α).
212
+ * The statistical gate for a non-deterministic A/B — "the gap clears the noise",
213
+ * with the noise floor computed, not supplied. The honest version of
214
+ * `assertImproves(..., { by: se })`.
215
+ */
216
+ export declare function assertSignificant(report: EvalReport, opts: {
217
+ baseline: string;
218
+ arm: string;
219
+ metric: string;
220
+ alpha?: number;
221
+ }): void;
222
+ /**
223
+ * Assert `arm` beats `baseline` on `metric`. By default just a positive gap > `by`
224
+ * (pass the combined se to clear the noise floor by hand). Pass `{ significant:
225
+ * true }` to demand a Welch t-test at `alpha` instead — the computed noise floor.
82
226
  */
83
227
  export declare function assertImproves(report: EvalReport, opts: {
84
228
  baseline: string;
85
229
  arm: string;
86
230
  metric: string;
87
231
  by?: number;
232
+ significant?: boolean;
233
+ alpha?: number;
234
+ }): void;
235
+ /**
236
+ * Assert the current run has not *regressed* against a committed baseline — the
237
+ * CI gate (Phase C). A regression is an arm×metric that moved **significantly in
238
+ * the bad direction** vs. `baseline` (Welch t-test, so sampling noise doesn't
239
+ * trip it; see `src/eval-baseline.ts`). Higher is better by default; list
240
+ * `lowerIsBetter` metrics (cost/latency) to flip them. Load the baseline with
241
+ * `readBaseline(path)` and record a fresh one with `writeBaseline(path, reports)`.
242
+ */
243
+ export declare function assertNoRegression(current: EvalReport | readonly EvalReport[], baseline: BaselineFile, opts?: DiffOptions): void;
244
+ /**
245
+ * Assert a skill/behaviour triggered on at least `min` (0..1) of its runs — the
246
+ * reliability gate for a skill's *activation* (does its description fire on the
247
+ * task), over a {@link TriggerRateReport} from `measureTriggerRate`.
248
+ */
249
+ export declare function assertTriggerRate(report: TriggerRateReport, opts: {
250
+ min: number;
88
251
  }): void;
89
252
  interface MatcherOutput {
90
253
  pass: boolean;
@@ -105,5 +268,4 @@ export declare const vigilesMatchers: {
105
268
  toBlock(received: HookRunResult): MatcherOutput;
106
269
  toBeatBaseline(received: EvalReport, baseline: string, arm: string, metric: string, by?: number): MatcherOutput;
107
270
  };
108
- export {};
109
271
  //# sourceMappingURL=harness-assert.d.ts.map