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.
- package/README.md +72 -327
- package/dist/agent-result.d.ts +40 -0
- package/dist/agent-result.js +97 -0
- package/dist/agent-runtime.d.ts +64 -0
- package/dist/agent-runtime.js +147 -0
- package/dist/claude-code.d.ts +10 -0
- package/dist/claude-code.js +26 -0
- package/dist/cli.js +106 -0
- package/dist/compile.d.ts +32 -3
- package/dist/compile.js +268 -0
- package/dist/egress-proxy.d.ts +2 -0
- package/dist/egress-proxy.js +60 -0
- package/dist/eval-baseline.d.ts +68 -0
- package/dist/eval-baseline.js +173 -0
- package/dist/eval-cache.d.ts +33 -0
- package/dist/eval-cache.js +94 -0
- package/dist/eval.d.ts +172 -9
- package/dist/eval.js +319 -58
- package/dist/harness-assert.d.ts +175 -13
- package/dist/harness-assert.js +358 -25
- package/dist/harness-test.d.ts +97 -11
- package/dist/harness-test.js +147 -37
- package/dist/judge.js +2 -0
- package/dist/linters.d.ts +6 -0
- package/dist/linters.js +1 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/mock-entry.d.ts +2 -0
- package/dist/mock-entry.js +36 -0
- package/dist/mock-model.d.ts +29 -0
- package/dist/mock-model.js +40 -0
- package/dist/plugin-loader.js +51 -17
- package/dist/run-hook.d.ts +81 -1
- package/dist/run-hook.js +189 -11
- package/dist/sandbox.d.ts +107 -0
- package/dist/sandbox.js +307 -0
- package/dist/spec.d.ts +130 -0
- package/dist/spec.js +55 -0
- package/dist/stats.d.ts +49 -0
- package/dist/stats.js +109 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/package.json +10 -4
package/dist/harness-assert.d.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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
|