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
package/README.md CHANGED
@@ -5,12 +5,11 @@
5
5
  <h1 align="center">vigiles</h1>
6
6
 
7
7
  <p align="center">
8
- <em>Quis custodiet ipsos custodes?</em> Who watches the watchmen?
8
+ <strong>Lint &amp; test the harness your AI agent runs on.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <strong>The missing linting + testing layer for agentic coding.</strong><br />
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 an AI agent steering your repo is trusted on vibes. vigiles is the
26
- deterministic layer for the harness: it **lints** the references your instruction
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
- | | Pillar | What it does |
31
- | ----- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
- | **①** | **Lint your instruction files** | Every linter rule, file path, script, and code symbol your CLAUDE.md cites is checked against reality, so stale references can't silently mislead the agent. → [guide](docs/verifying-instruction-files.md) |
33
- | **②** | **Test your harness** | Your hooks and skills are code vigiles tests they actually fire, **deterministically and free** (no model, no API key); and when a question _does_ need a real-model eval, it runs on your **Claude subscription**, not metered API. → [guide](docs/harness-testing.md) |
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
- Neither pillar depends on the other — pick the one that hurts today. **Works with
36
- Claude Code and Codex** ([`vigiles/codex`](docs/harnesses.md)) behind a five-port
37
- adapter; [custom adapters welcome](docs/authoring-an-adapter.md).
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
- Install vigiles in this repo and run it. Verify my CLAUDE.md / AGENTS.md
139
- references and show me what's stale, then write and run a harness test for one
140
- of my hooks or skills. Use good defaults (both pillars, non-interactive), but
141
- ask me first whether to gate it in CI, whether to add a real-model eval, and
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 BOTH pillars: spec + harness test + CI + plugin
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 pillars** by default; scope with `--lint` / `--test` (one or 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
- - **[CLI & GitHub Action →](docs/cli.md)** — every command, the Action (inputs / output / versioning), the Claude Code plugin, and the five `lint` rules.
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
- const fm = extractFrontmatter(markdown);
67
- if (fm === null)
68
- return null;
69
- const match = /^tools:[ \t]*(.*)$/m.exec(fm);
70
- if (!match)
71
- return null;
72
- return match[1]
73
- .split(",")
74
- .map((t) => t.trim())
75
- .filter((t) => t.length > 0);
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
- "PreSession",
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