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
package/README.md
CHANGED
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
<h1 align="center">vigiles</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<
|
|
8
|
+
<strong>Lint & test the harness your AI agent runs on.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<
|
|
13
|
-
vigiles <strong>lints</strong> the references your instruction files make — linter rules, file paths, scripts, code symbols — and <strong>tests</strong> whether your hooks, skills, and CLAUDE.md actually change what the agent does.
|
|
12
|
+
Your CLAUDE.md, hooks, and skills steer the agent — but nothing checks they're <em>true</em>, and nothing tests they <em>work</em>. vigiles does both.
|
|
14
13
|
</p>
|
|
15
14
|
|
|
16
15
|
<p align="center">
|
|
@@ -22,130 +21,33 @@
|
|
|
22
21
|
---
|
|
23
22
|
|
|
24
23
|
`Agent = Model + Harness`. You'd never ship an app without a linter and a test
|
|
25
|
-
suite — yet
|
|
26
|
-
deterministic layer for
|
|
27
|
-
files make and **tests** that your hooks and skills actually fire. Two independent
|
|
28
|
-
pillars — adopt either, or both:
|
|
24
|
+
suite — yet the harness steering your agent runs on vibes. vigiles[^name] is the
|
|
25
|
+
deterministic layer for it, and does two independent things — adopt either, or both:
|
|
29
26
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
27
|
+
| | |
|
|
28
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
29
|
+
| **🔎 Lint** | Every file path, script, code symbol, and linter rule your CLAUDE.md cites is checked against reality — so a renamed file or a disabled rule can't silently mislead the agent. **[→](docs/verifying-instruction-files.md)** |
|
|
30
|
+
| **🧪 Test** | Hooks, skills, and subagents are code. vigiles tests they _do their job_ — and almost all of it is **deterministic, no API key**; the real-model evals run on your **Claude subscription**, not metered tokens. **[→](docs/harness-testing.md)** |
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## ① Lint — your CLAUDE.md lies to your agent
|
|
40
|
-
|
|
41
|
-
Your CLAUDE.md says _"enforce `eslint/no-console`."_ But it was switched off
|
|
42
|
-
months ago — and the agent trusts the claim. (Same story for the file path it
|
|
43
|
-
cites that got renamed, and the script that was deleted.)
|
|
44
|
-
|
|
45
|
-
**Without vigiles:** nobody checks. The agent acts on fiction.
|
|
46
|
-
|
|
47
|
-
**With vigiles:** `npx vigiles lint` resolves every reference against reality —
|
|
48
|
-
|
|
49
|
-
```text
|
|
50
|
-
CLAUDE.md (inline mode):
|
|
51
|
-
✗ line 1: Rule "eslint/no-console" exists but is disabled in eslint config
|
|
52
|
-
✓ line 2: eslint/eqeqeq
|
|
53
|
-
✗ line 3: Rule "no-consoel" not found in eslint. Did you mean: "eslint/no-console"?
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
It resolves rule names across **7 linter catalogs** — the rule exists **and is
|
|
57
|
-
enabled** — and checks file paths, scripts, and code symbols the same way. Start
|
|
58
|
-
with one comment, no new files:
|
|
59
|
-
|
|
60
|
-
```md
|
|
61
|
-
<!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Step up to a typed `.spec.ts` (compiled to CLAUDE.md, compiler-grade) when you
|
|
65
|
-
want it. **[Full guide →](docs/verifying-instruction-files.md)**
|
|
66
|
-
|
|
67
|
-
## ② Test — does your harness actually fire?
|
|
68
|
-
|
|
69
|
-
A hook can be wired wrong, a skill's description can fail to trigger, injected
|
|
70
|
-
context can never reach the model — silently, all passing a naive "did it run?"
|
|
71
|
-
check.
|
|
72
|
-
|
|
73
|
-
**Without vigiles:** you assume your `--no-verify` guard blocks. You don't know.
|
|
74
|
-
|
|
75
|
-
**With vigiles:** a deterministic test proves it — no model, no API key,
|
|
76
|
-
milliseconds:
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
import { runHook } from "vigiles/testing";
|
|
80
|
-
|
|
81
|
-
const r = runHook(guard, {
|
|
82
|
-
hook_event_name: "PreToolUse",
|
|
83
|
-
tool_name: "Bash",
|
|
84
|
-
tool_input: { command: "git commit --no-verify" },
|
|
85
|
-
});
|
|
86
|
-
assert(r.blocked); // a red ✗ here means your hook silently lets it through
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```text
|
|
90
|
-
✓ guard blocks --no-verify and allows a clean commit
|
|
91
|
-
|
|
92
|
-
2 passed.
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Three tiers, cheapest first: **`runHook`** (a hook's logic), **`runHarnessTest`**
|
|
96
|
-
(the real agent CLI against a scripted mock model), and the real-model scored tier
|
|
97
|
-
(**`measure`** / **`runEval`**). **Testing a skill?** Two questions, both covered:
|
|
98
|
-
does its description **fire** (`measureTriggerRate` — recall across varied prompts
|
|
99
|
-
without hijacking unrelated ones, precision), **and** does its guidance actually
|
|
100
|
-
**work**. For "is this exact skill any good?" score the output directly —
|
|
101
|
-
`measure({ checks: [judged(rubric)] })` + `assertRates` (the **absolute** oracle,
|
|
102
|
-
what promptfoo/DeepEval lead with; no on/off baseline needed). When you need the
|
|
103
|
-
**relative** lift over no-skill — regression, or proving the change isn't noise —
|
|
104
|
-
A/B it on-vs-off with `runEval` + `assertSignificant`. Description _and_ behavior,
|
|
105
|
-
not just one. **Need a safety property** — that the agent
|
|
106
|
-
**didn't** push to the wrong branch or call a paid API? `notTool` + `interceptTools`
|
|
107
|
-
intercept the tool in the real hook layer, so the attempt is caught and the side
|
|
108
|
-
effect never happens. **[Full guide →](docs/harness-testing.md)** · vigiles runs
|
|
109
|
-
foreign code (and a real model) safely by default — **[safety model →](docs/safety.md)**
|
|
110
|
-
|
|
111
|
-
Most of what real plugins do is testable cheaply — fire / trigger / contract /
|
|
112
|
-
safety, plus **record-replay** for the tool/API results a skill consumes (recorded
|
|
113
|
-
once from the real tool, replayed deterministically — no live service, no Docker).
|
|
114
|
-
That covers ~90%+ of real plugin surface on your subscription; the rare case that
|
|
115
|
-
needs a real browser or database **composes with Docker** rather than us
|
|
116
|
-
reinventing the sandbox. **[What we test, how →](research/eval-coverage-and-isolation.md)**
|
|
117
|
-
|
|
118
|
-
**Affordable by design — the eval you can actually run.** Almost nobody evals
|
|
119
|
-
their harness, because the usual tools (promptfoo, DeepEval, …) hit the API SDK
|
|
120
|
-
and bill **per token on every run**. vigiles inverts that: most questions are
|
|
121
|
-
answered with **no model at all** (free, every commit), and when you do reach for
|
|
122
|
-
a real-model eval, vigiles drives your `claude` CLI — so it runs on the **Pro/Max
|
|
123
|
-
subscription you already pay for**, not metered API billing. CI runs only the free
|
|
124
|
-
deterministic tiers; you run the real-model eval where the subscription already is
|
|
125
|
-
— a Claude Code session or locally — when it's worth it, not on every PR.
|
|
126
|
-
|
|
127
|
-
This affordability story is **ToS-clean**: vigiles drives _your own_ `claude` CLI
|
|
128
|
-
to test _your own_ harness on _your own_ subscription — the same thing you do when
|
|
129
|
-
you run Claude Code. (The Claude Agent SDK's ToS restricts _productizing_ claude.ai
|
|
130
|
-
login/limits in a third-party offering; running your own tests on your own sub is
|
|
131
|
-
exactly the supported posture, not that.)
|
|
32
|
+
Pick the one that hurts today. **Works with Claude Code and Codex**
|
|
33
|
+
([`vigiles/codex`](docs/harnesses.md)), and you can
|
|
34
|
+
[teach it your own harness](docs/authoring-an-adapter.md).
|
|
132
35
|
|
|
133
36
|
## Quick start
|
|
134
37
|
|
|
135
38
|
**Paste into Claude Code or Codex:**
|
|
136
39
|
|
|
137
40
|
```text
|
|
138
|
-
|
|
139
|
-
references and show me what's stale, then write
|
|
140
|
-
of my hooks or skills.
|
|
141
|
-
|
|
142
|
-
whether to enforce strictly (--strict).
|
|
41
|
+
Set up vigiles in this repo with good defaults (lint + test, non-interactive).
|
|
42
|
+
Verify my CLAUDE.md / AGENTS.md references and show me what's stale, then write
|
|
43
|
+
and run a harness test for one of my hooks or skills. Ask me first before gating
|
|
44
|
+
it in CI, adding a real-model eval, or enforcing strictly (--strict).
|
|
143
45
|
```
|
|
144
46
|
|
|
145
47
|
Or do it yourself:
|
|
146
48
|
|
|
147
49
|
```bash
|
|
148
|
-
npx vigiles init # sets up
|
|
50
|
+
npx vigiles init # sets up lint + test: spec + harness test + CI + plugin
|
|
149
51
|
```
|
|
150
52
|
|
|
151
53
|
It's interactive in a terminal and non-interactive for agents/CI (or with
|
|
@@ -156,7 +58,7 @@ tell your agent _"test my skills"_ and it picks the tier and writes the test.
|
|
|
156
58
|
<details>
|
|
157
59
|
<summary>What <code>init</code> sets up</summary>
|
|
158
60
|
|
|
159
|
-
- **Both
|
|
61
|
+
- **Both lint and test** by default; scope with `--lint` / `--test` (one or both).
|
|
160
62
|
- Adds `vigiles` to your `devDependencies`.
|
|
161
63
|
- Installs the Claude Code plugin (skills + hooks) via the marketplace —
|
|
162
64
|
globally, never vendored into your repo.
|
|
@@ -172,9 +74,71 @@ Prefer to write tests yourself? They can be JS **or** TS
|
|
|
172
74
|
|
|
173
75
|
</details>
|
|
174
76
|
|
|
77
|
+
## ① Lint — your CLAUDE.md lies to your agent
|
|
78
|
+
|
|
79
|
+
Your CLAUDE.md points the agent at `src/auth/login.ts` and tells it to run
|
|
80
|
+
`npm run check`. But the file moved to `src/auth/session.ts` six commits ago, and
|
|
81
|
+
the script was renamed. The agent trusts the stale claim and acts on fiction.
|
|
82
|
+
|
|
83
|
+
`npx vigiles lint` resolves every reference against reality:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
CLAUDE.md:
|
|
87
|
+
✗ src/auth/login.ts — no such file (renamed or moved?)
|
|
88
|
+
✗ npm run check — not in package.json. Did you mean: "check:types"?
|
|
89
|
+
✓ @typescript-eslint/no-floating-promises — exists and enabled in eslint config
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
File paths, scripts, and code symbols — plus linter rules across **7 catalogs**
|
|
93
|
+
(the rule exists **and is enabled**). Start with one inline comment, no new files;
|
|
94
|
+
step up to a typed `.spec.ts` (compiled to CLAUDE.md, compiler-grade) when you want
|
|
95
|
+
it. **[Full guide →](docs/verifying-instruction-files.md)**
|
|
96
|
+
|
|
97
|
+
**Same cross-reference, any plugin.** `npx vigiles scan` checks a plugin's
|
|
98
|
+
_contracts_ — every subagent tool, `mcp__server__tool`, `mcp_tool` hook, hook
|
|
99
|
+
event, and script path actually **exists and resolves**, not just parses (valid
|
|
100
|
+
YAML ≠ a tool that's real). A **superset of Anthropic's `claude plugin validate`**,
|
|
101
|
+
no key. **[Audit any plugin →](docs/cli.md#scan-dir)**
|
|
102
|
+
|
|
103
|
+
## ② Test — does your harness do its job?
|
|
104
|
+
|
|
105
|
+
A hook can be wired wrong. A skill's description can fail to trigger — or hijack
|
|
106
|
+
unrelated prompts. Injected context can never reach the model. All of it passes a
|
|
107
|
+
naive "did it run?" check. vigiles tests the assembled harness for real:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { runHook } from "vigiles/testing";
|
|
111
|
+
|
|
112
|
+
const r = runHook(guard, {
|
|
113
|
+
hook_event_name: "PreToolUse",
|
|
114
|
+
tool_name: "Bash",
|
|
115
|
+
tool_input: { command: "git commit --no-verify" },
|
|
116
|
+
});
|
|
117
|
+
assert(r.blocked); // a red ✗ means your guard silently lets it through
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It goes well past _"did it fire?"_:
|
|
121
|
+
|
|
122
|
+
- **Hooks block** what they must — `runHook`, or the real agent CLI via `runHarnessTest`.
|
|
123
|
+
- **Skills trigger** on the right prompts and stay quiet on the wrong ones — recall _and_ precision (`measureTriggerRate`).
|
|
124
|
+
- **Behaviour is good** — score a skill's output directly, or A/B it on-vs-off for the real lift over no-skill (`measure` / `runEval`, with significance testing).
|
|
125
|
+
- **Safety holds** — the agent _didn't_ push to the wrong branch or hit a paid API; `interceptTools` catches the attempt so the side effect never happens.
|
|
126
|
+
|
|
127
|
+
**The eval you can actually afford.** Almost every tier runs with **no model and
|
|
128
|
+
no API key** — milliseconds, on every commit. The rest drive your own `claude` CLI:
|
|
129
|
+
|
|
130
|
+
| | Runs on | Cost |
|
|
131
|
+
| ---------------------- | ----------------------- | ------------------------------------------- |
|
|
132
|
+
| promptfoo, DeepEval, … | metered API SDK | billed **per token, every run** |
|
|
133
|
+
| **vigiles** | your Claude Pro/Max sub | **$0 extra** — and most tiers need no model |
|
|
134
|
+
|
|
135
|
+
That's why you can eval your harness on every change, not just once.
|
|
136
|
+
**[How it works →](docs/harness-testing.md)** · **[Why it's affordable →](docs/eval-architecture.md)** · **[Safety model →](docs/safety.md)**
|
|
137
|
+
|
|
175
138
|
## More
|
|
176
139
|
|
|
177
|
-
- **[
|
|
140
|
+
- **[Plugin health leaderboard →](docs/cli.md#scan-dir)** — point `scan` at a marketplace (e.g. `wshobson/agents`) and it ranks every plugin by structural health (0–100, A–F), worst issues first — still **no key**. Add `--trigger` for the model-gated column: do the skills actually fire?
|
|
141
|
+
- **[CLI & GitHub Action →](docs/cli.md)** — every command, the Action (inputs / output / versioning), the Claude Code plugin, and the `lint` rules.
|
|
178
142
|
- **[Skills →](docs/skills.md)** — consumer skills installed as a Claude Code plugin: `/plugin marketplace add zernie/vigiles` then `/plugin install vigiles@vigiles` (or let `vigiles init` do it). The model-invocable ones (`test-harness`, `strengthen`, `edit-spec`) fire on their own — ask _"test my skills"_, _"strengthen my rules"_, or _"add a rule to CLAUDE.md"_ and the agent reaches for them; `migrate-to-spec` and `linter-docs` are user-invoked.
|
|
179
143
|
- **[Docs index →](docs/README.md)** · **[Research →](research/README.md)** · **[Related tools →](docs/related-tools.md)** (ast-grep, Dependency Cruiser, Ruler, rulesync).
|
|
180
144
|
- Companion to [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need).
|
|
@@ -182,3 +146,5 @@ Prefer to write tests yourself? They can be JS **or** TS
|
|
|
182
146
|
## License
|
|
183
147
|
|
|
184
148
|
[MIT](LICENSE)
|
|
149
|
+
|
|
150
|
+
[^name]: **vigiles** — the watchmen of ancient Rome, who guarded the city (and fought its fires) by night. _Quis custodiet ipsos custodes?_ — "who watches the watchmen?" (Juvenal, _Satire VI_).
|
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
* an empty list (`tools:` with nothing after it) means "no tools allowed".
|
|
32
32
|
*/
|
|
33
33
|
export declare function parseAgentTools(markdown: string): string[] | null;
|
|
34
|
+
/**
|
|
35
|
+
* Parse a comma/array tool list under an arbitrary frontmatter `key` (e.g.
|
|
36
|
+
* `tools:` or `disallowedTools:`) via the shared lenient reader
|
|
37
|
+
* (core/frontmatter-read.ts): a real YAML parse (so `key: [Read, "Bash"]` is a
|
|
38
|
+
* native array and `key: Read, Bash` a comma scalar) with a regex salvage when
|
|
39
|
+
* the block is malformed — the rail still reads the contract. `null` when the key
|
|
40
|
+
* is absent (inherits all), `[]` when present-but-empty (no tools). Shared by the
|
|
41
|
+
* rail (`tools:`) and the `disallowed-tools-contract` scan/lint.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseAgentToolList(markdown: string, key: string): string[] | null;
|
|
34
44
|
export interface PreToolDecision {
|
|
35
45
|
/** Whether the tool call is allowed (true) or blocked (false). */
|
|
36
46
|
readonly allow: boolean;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
27
|
exports.parseAgentTools = parseAgentTools;
|
|
28
|
+
exports.parseAgentToolList = parseAgentToolList;
|
|
28
29
|
exports.decidePreToolUse = decidePreToolUse;
|
|
29
30
|
exports.setActiveAgent = setActiveAgent;
|
|
30
31
|
exports.clearActiveAgent = clearActiveAgent;
|
|
@@ -32,28 +33,10 @@ exports.readActiveAgent = readActiveAgent;
|
|
|
32
33
|
exports.evaluatePreToolUse = evaluatePreToolUse;
|
|
33
34
|
const node_fs_1 = require("node:fs");
|
|
34
35
|
const node_path_1 = require("node:path");
|
|
36
|
+
const frontmatter_read_js_1 = require("../../core/frontmatter-read.js");
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
36
38
|
// Parse the tool contract from a compiled agent .md
|
|
37
39
|
// ---------------------------------------------------------------------------
|
|
38
|
-
/** Extract the YAML frontmatter block (between the first pair of `---` fences). */
|
|
39
|
-
function extractFrontmatter(markdown) {
|
|
40
|
-
const lines = markdown.split("\n");
|
|
41
|
-
let start = -1;
|
|
42
|
-
for (let i = 0; i < lines.length; i++) {
|
|
43
|
-
if (lines[i].trim() === "---") {
|
|
44
|
-
start = i;
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (start === -1)
|
|
49
|
-
return null;
|
|
50
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
51
|
-
if (lines[i].trim() === "---") {
|
|
52
|
-
return lines.slice(start + 1, i).join("\n");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
40
|
/**
|
|
58
41
|
* Parse an agent's allowed-tools contract from its compiled markdown.
|
|
59
42
|
*
|
|
@@ -63,16 +46,19 @@ function extractFrontmatter(markdown) {
|
|
|
63
46
|
* an empty list (`tools:` with nothing after it) means "no tools allowed".
|
|
64
47
|
*/
|
|
65
48
|
function parseAgentTools(markdown) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
49
|
+
return parseAgentToolList(markdown, "tools");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse a comma/array tool list under an arbitrary frontmatter `key` (e.g.
|
|
53
|
+
* `tools:` or `disallowedTools:`) via the shared lenient reader
|
|
54
|
+
* (core/frontmatter-read.ts): a real YAML parse (so `key: [Read, "Bash"]` is a
|
|
55
|
+
* native array and `key: Read, Bash` a comma scalar) with a regex salvage when
|
|
56
|
+
* the block is malformed — the rail still reads the contract. `null` when the key
|
|
57
|
+
* is absent (inherits all), `[]` when present-but-empty (no tools). Shared by the
|
|
58
|
+
* rail (`tools:`) and the `disallowed-tools-contract` scan/lint.
|
|
59
|
+
*/
|
|
60
|
+
function parseAgentToolList(markdown, key) {
|
|
61
|
+
return (0, frontmatter_read_js_1.frontmatterList)((0, frontmatter_read_js_1.readFrontmatter)(markdown), key);
|
|
76
62
|
}
|
|
77
63
|
/**
|
|
78
64
|
* Decide whether `tool` is allowed under an agent's tool contract. Pure, so the
|
|
@@ -9,9 +9,13 @@ exports.claudeCodeDialect = {
|
|
|
9
9
|
"Read",
|
|
10
10
|
"Write",
|
|
11
11
|
"Edit",
|
|
12
|
+
"MultiEdit",
|
|
12
13
|
"Bash",
|
|
14
|
+
"BashOutput",
|
|
15
|
+
"KillBash",
|
|
13
16
|
"Grep",
|
|
14
17
|
"Glob",
|
|
18
|
+
"LS",
|
|
15
19
|
"WebSearch",
|
|
16
20
|
"WebFetch",
|
|
17
21
|
"NotebookEdit",
|
|
@@ -30,12 +34,24 @@ exports.claudeCodeDialect = {
|
|
|
30
34
|
"WaitForMcpServers",
|
|
31
35
|
],
|
|
32
36
|
mcpToolPattern: /^mcp__[a-z0-9_-]+__[a-z0-9_-]+$/i,
|
|
37
|
+
// Claude Code's own built-in MCP server: the IDE integration provides
|
|
38
|
+
// `mcp__ide__getDiagnostics` / `mcp__ide__executeCode` at runtime without any
|
|
39
|
+
// plugin declaring it, so a contract that lists those must NOT be flagged as
|
|
40
|
+
// referencing an undeclared server (the mcp-tool-resolves allowlist).
|
|
41
|
+
knownMcpServers: ["ide"],
|
|
42
|
+
// The real Claude Code hook events. (Was wrong: PreSession/PostSession don't
|
|
43
|
+
// exist; SessionStart/SessionEnd/Stop/SubagentStop/UserPromptSubmit/PreCompact
|
|
44
|
+
// were missing — verified against the events real plugins register.)
|
|
33
45
|
hookEvents: [
|
|
34
46
|
"PreToolUse",
|
|
35
47
|
"PostToolUse",
|
|
36
|
-
"
|
|
37
|
-
"PostSession",
|
|
48
|
+
"UserPromptSubmit",
|
|
38
49
|
"Notification",
|
|
50
|
+
"Stop",
|
|
51
|
+
"SubagentStop",
|
|
52
|
+
"PreCompact",
|
|
53
|
+
"SessionStart",
|
|
54
|
+
"SessionEnd",
|
|
39
55
|
],
|
|
40
56
|
// Claude Code natively reads CLAUDE.md only — it does NOT auto-load AGENTS.md
|
|
41
57
|
// (anthropics/claude-code#34235 is open; AGENTS.md works solely via an
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex EVAL-tier transport — the runner + trace parser that
|
|
3
|
+
* `measureTriggerRate`/`runEval` dispatch to via the `ModelOutputParser` seam.
|
|
4
|
+
*
|
|
5
|
+
* SCHEMA: CONFIRMED against real `codex exec --json` (codex-cli 0.139.0, ChatGPT
|
|
6
|
+
* auth). The stream is the thread/item model:
|
|
7
|
+
*
|
|
8
|
+
* {"type":"thread.started","thread_id":"…"}
|
|
9
|
+
* {"type":"turn.started"}
|
|
10
|
+
* {"type":"item.started","item":{"id":"item_0","type":"command_execution",…}} // mid-flight
|
|
11
|
+
* {"type":"item.completed","item":{"id":"item_0","type":"command_execution","command":"…","aggregated_output":"…","exit_code":0}}
|
|
12
|
+
* {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"…"}}
|
|
13
|
+
* {"type":"turn.completed","usage":{"input_tokens":…,"cached_input_tokens":…,"output_tokens":…}}
|
|
14
|
+
*
|
|
15
|
+
* So: assistant text = `item.completed` with `item.type:"agent_message"` →
|
|
16
|
+
* `item.text`; a tool call = `item.type:"command_execution"` → `item.command`;
|
|
17
|
+
* usage rides `turn.completed`. We count `item.completed` ONLY (an `item.started`
|
|
18
|
+
* carries the same `id` mid-flight — counting both double-counts).
|
|
19
|
+
*
|
|
20
|
+
* THE SKILL FINDING: Codex has NO discrete "skill selected" event (its CLI has no
|
|
21
|
+
* Skill-tool concept). When a skill triggers, the model READS the skill's
|
|
22
|
+
* `SKILL.md` via a `command_execution` (`sed/cat … skills/<name>/SKILL.md`) and
|
|
23
|
+
* usually says so in an `agent_message`. So "did skill X fire" on Codex is not a
|
|
24
|
+
* clean trace event like Claude's `Skill` tool_use — it's detected by the
|
|
25
|
+
* SKILL.md read (`codexSkillFired`). Best-effort by nature (a cached skill might
|
|
26
|
+
* not be re-read); pair with a behavioral/judged check for certainty.
|
|
27
|
+
*/
|
|
28
|
+
import type { ParsedModelRun, AgentRunArgs, RunOut, EvalDriver } from "../../eval.js";
|
|
29
|
+
import type { ToolCall } from "../../core/harness-driver.js";
|
|
30
|
+
/** Parse `codex exec --json` stdout into the common trace fields (confirmed schema). */
|
|
31
|
+
export declare function parseCodexEvalRun(out: {
|
|
32
|
+
stdout: string;
|
|
33
|
+
}): ParsedModelRun;
|
|
34
|
+
/**
|
|
35
|
+
* The error message if the run errored or was rate-limited (an `error` /
|
|
36
|
+
* `turn.failed` event), else null. CRITICAL for the eval tier: an errored turn
|
|
37
|
+
* must NOT be scored as a clean "skill didn't fire" miss — dogfooding hit a Codex
|
|
38
|
+
* usage limit ("You've hit your usage limit…") whose `error` event left an empty
|
|
39
|
+
* trace that `codexSkillFired` read as recall 0. A caller should skip/retry an
|
|
40
|
+
* errored run, not count it. (The Claude path has `isRateLimited` + backoff; this
|
|
41
|
+
* is the Codex equivalent detector.)
|
|
42
|
+
*/
|
|
43
|
+
export declare function codexRunError(out: {
|
|
44
|
+
stdout: string;
|
|
45
|
+
}): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Did Codex activate skill `name` on this run? Detected by the SKILL.md read —
|
|
48
|
+
* Codex has no discrete skill-selection event, so when a skill triggers the model
|
|
49
|
+
* reads its `…/<name>/SKILL.md` via a `command_execution`. Best-effort (a cached
|
|
50
|
+
* skill might not be re-read); for the trigger-rate `fired` predicate over Codex.
|
|
51
|
+
*/
|
|
52
|
+
export declare function codexSkillFired(run: {
|
|
53
|
+
toolCalls: readonly ToolCall[];
|
|
54
|
+
}, name: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Materialize a (Claude-shaped) plugin dir's skills into `<cwd>/.codex/skills/` —
|
|
57
|
+
* where Codex actually discovers them (validated live: codex reads
|
|
58
|
+
* `<cwd>/.codex/skills/<name>/SKILL.md`). This is the Codex analog of Claude's
|
|
59
|
+
* `--plugin-dir`: `measureTriggerRate` hands the runner a `pluginDir` (the
|
|
60
|
+
* stubbed/packaged skills), and the Codex runner installs them here before the
|
|
61
|
+
* turn. Pure fs — unit-testable without a binary.
|
|
62
|
+
*/
|
|
63
|
+
export declare function installCodexSkills(pluginDir: string, cwd: string): number;
|
|
64
|
+
/**
|
|
65
|
+
* The Codex eval-tier `AgentRunner`: install the run's skills into `.codex/skills`
|
|
66
|
+
* (Codex's discovery path, vs Claude's `--plugin-dir`), then drive a real
|
|
67
|
+
* `codex exec --json` turn. The seam `measureTriggerRate(spec, { evalDriver:
|
|
68
|
+
* codexEvalDriver })` dispatches through.
|
|
69
|
+
*/
|
|
70
|
+
export declare function codexEvalAgentRunner(args: AgentRunArgs): Promise<RunOut>;
|
|
71
|
+
/**
|
|
72
|
+
* The Codex eval driver — pass to `measureTriggerRate(spec, { evalDriver:
|
|
73
|
+
* codexEvalDriver })` to run a trigger-rate eval natively on `codex exec`. Pair
|
|
74
|
+
* the spec's `fired` with `codexSkillFired` (Codex has no Skill-tool event).
|
|
75
|
+
*/
|
|
76
|
+
export declare const codexEvalDriver: EvalDriver;
|
|
77
|
+
/**
|
|
78
|
+
* Spawn real `codex exec --json` for the eval tier (real model, the user's codex
|
|
79
|
+
* auth — NOT the mock). CONFIRMED flags (codex 0.139.0): `--json` for the event
|
|
80
|
+
* stream, `--skip-git-repo-check` for a bare cwd, the approvals/sandbox bypass so
|
|
81
|
+
* the turn runs unattended, `-C <cwd>` for the working dir, prompt as the trailing
|
|
82
|
+
* positional, and stdin = /dev/null (`stdio: ["ignore",…]`) — codex otherwise
|
|
83
|
+
* blocks on "Reading additional input from stdin…". Needs ChatGPT/API auth +
|
|
84
|
+
* network egress to the model backend.
|
|
85
|
+
*/
|
|
86
|
+
export declare function codexEvalRunner(args: {
|
|
87
|
+
task: string;
|
|
88
|
+
cwd: string;
|
|
89
|
+
timeoutMs: number;
|
|
90
|
+
}): {
|
|
91
|
+
code: number;
|
|
92
|
+
stdout: string;
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=eval.d.ts.map
|