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.
- package/README.md +82 -116
- package/dist/adapters/claude-code/agent-runtime.d.ts +10 -0
- package/dist/adapters/claude-code/agent-runtime.js +15 -29
- package/dist/adapters/claude-code/dialect.js +18 -2
- package/dist/adapters/codex/eval.d.ts +94 -0
- package/dist/adapters/codex/eval.js +227 -0
- package/dist/cli.js +464 -8
- package/dist/codex.d.ts +1 -0
- package/dist/codex.js +3 -0
- package/dist/core/compile.js +8 -36
- package/dist/core/description-overlap.d.ts +27 -0
- package/dist/core/description-overlap.js +53 -0
- package/dist/core/dialect.d.ts +8 -0
- package/dist/core/frontmatter-read.d.ts +25 -0
- package/dist/core/frontmatter-read.js +138 -0
- package/dist/core/hook-events.d.ts +34 -0
- package/dist/core/hook-events.js +48 -0
- package/dist/core/mcp-config.d.ts +20 -0
- package/dist/core/mcp-config.js +40 -0
- package/dist/core/mcp-hook.d.ts +35 -0
- package/dist/core/mcp-hook.js +70 -0
- package/dist/core/mcp-tool.d.ts +50 -0
- package/dist/core/mcp-tool.js +61 -0
- package/dist/core/tool-contract.d.ts +68 -0
- package/dist/core/tool-contract.js +113 -0
- package/dist/core/types.d.ts +89 -0
- package/dist/core/validate.js +22 -0
- package/dist/eval.d.ts +69 -13
- package/dist/eval.js +106 -51
- package/dist/leaderboard.js +61 -3
- package/dist/plugin-loader.d.ts +1 -0
- package/dist/plugin-loader.js +71 -18
- package/dist/scan-behavioral.d.ts +73 -0
- package/dist/scan-behavioral.js +150 -0
- package/dist/scan.d.ts +126 -1
- package/dist/scan.js +559 -40
- package/package.json +27 -4
- 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
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/validate.js
CHANGED
|
@@ -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
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
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
|
|
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
|