vigiles 5.2.0 → 6.0.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 CHANGED
@@ -5,11 +5,11 @@
5
5
  <h1 align="center">vigiles</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Lint &amp; test the harness your AI agent runs on.</strong>
8
+ <strong>Lint, test &amp; measure the harness your AI agent runs on.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
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.
12
+ Your CLAUDE.md, hooks, and skills steer the agent — but nothing checks they're <em>true</em>, nothing tests they <em>work</em>, and nothing measures whether they actually <em>help</em>. vigiles does all three.
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -20,14 +20,17 @@
20
20
 
21
21
  ---
22
22
 
23
- `Agent = Model + Harness`. You'd never ship an app without a linter and a test
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:
23
+ `Agent = Model + Harness`. You'd never ship an app without a linter, a test suite,
24
+ and a benchmark — yet the harness steering your agent runs on vibes. vigiles[^name]
25
+ is the deterministic layer for it and the only one that can **A/B-test what
26
+ actually moves the needle on your Claude subscription**, not metered API. Three
27
+ things, adopt any:
26
28
 
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)** |
29
+ | | |
30
+ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
+ | **🔎 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)** |
32
+ | **🧪 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)** |
33
+ | **📊 Measure** | Does that skill or plugin actually help — or just add cost? **A/B it on real tasks** and read the bill + correctness, not the vendor's headline. The same engine ranks the hyped ecosystem: **what works vs hype**. **[→](docs/measuring-skills.md)** |
31
34
 
32
35
  Pick the one that hurts today. **Works with Claude Code and Codex**
33
36
  ([`vigiles/codex`](docs/harnesses.md)), and you can
@@ -92,7 +95,16 @@ CLAUDE.md:
92
95
  File paths, scripts, and code symbols — plus linter rules across **7 catalogs**
93
96
  (the rule exists **and is enabled**). Start with one inline comment, no new files;
94
97
  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)**
98
+ it and that same spec is what makes your skills & subagents deterministically
99
+ **testable** (a `result()` contract → an assert, no LLM judge — see ② below), not
100
+ just lint-able. **[Full guide →](docs/verifying-instruction-files.md)**
101
+
102
+ > **Markdown is prose; a typed spec is a _program_.** Opt in and an agent that
103
+ > leaks, exceeds its effect floor, or hands off mismatched data is a **type
104
+ > error** — your **multi-agent pipeline won't compile if the handoffs don't line
105
+ > up**. Others lint prose after the fact; vigiles is a **compiler for harnesses**.
106
+ > Graduated like TypeScript's `strict` — markdown still works, type-safety is
107
+ > opt-in. **[The moat →](research/typed-spec-moat.md)**
96
108
 
97
109
  **Same cross-reference, any plugin.** `npx vigiles scan` checks a plugin's
98
110
  _contracts_ — every subagent tool, `mcp__server__tool`, `mcp_tool` hook, hook
@@ -121,20 +133,49 @@ It goes well past _"did it fire?"_:
121
133
 
122
134
  - **Hooks block** what they must — `runHook`, or the real agent CLI via `runHarnessTest`.
123
135
  - **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).
136
+ - **Behaviour is good** — score a skill's output directly, or A/B it on-vs-off for the real lift (`measure` / `runEval`, with significance testing).
125
137
  - **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
138
 
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:
139
+ Almost every tier runs with **no model and no API key** — milliseconds, on every
140
+ commit. Only the real-model evals need a model, and they run on your own `claude`
141
+ CLI. **[How it works →](docs/harness-testing.md)**
142
+
143
+ ## ③ Measure — does it actually help, or just cost more?
144
+
145
+ A skill claims "65% fewer tokens." A plugin promises "3× faster." Stars and
146
+ vibes — **zero measurement**. vigiles A/Bs the claim on real coding tasks, the
147
+ harness loaded exactly as it ships, and reports the **metric triple**:
148
+
149
+ ```typescript
150
+ import { measureArms } from "vigiles/testing";
151
+
152
+ const r = await measureArms({
153
+ fixture: { "in.txt": "Implement a slug helper." },
154
+ task: "Read in.txt, write slugify() to slug.js, explain. Stop.",
155
+ arms: { baseline: {}, skill: { files: { "SKILL.md": THE_SKILL } } },
156
+ measure: (ctx) => ({ cost: ctx.usage.costUsd, correct: check(ctx) }),
157
+ });
158
+ // → is the bill lower? did correctness survive? (not just "fewer output tokens")
159
+ ```
160
+
161
+ - **The bill (`costUsd`)** — the honest number; it weights cache ~0.1× and output 1×, so a "saved tokens" headline can't hide behind cheap cache.
162
+ - **The optimization target** — whatever the skill claims to move (output tokens, latency, tool calls). Verified on its own terms.
163
+ - **The blast radius** — correctness, a deterministic 1/0. A token win that breaks the code is **not a win**.
164
+
165
+ The kicker: every run is **your own `claude` CLI on your Pro/Max subscription** —
166
+ so you can measure on every change, and the same engine powers the **ecosystem
167
+ benchmark** ("we A/B-tested the most-hyped skills — what works vs hype"). That's
168
+ the moat: continuous, sub-affordable measurement nobody metered-by-the-token can
169
+ match. **[Measure a skill →](docs/measuring-skills.md)** · **[Why it's affordable →](docs/eval-architecture.md)**
170
+
171
+ **The eval you can actually afford.** Most of vigiles needs no model at all; the
172
+ rest drives your subscription:
129
173
 
130
174
  | | Runs on | Cost |
131
175
  | ---------------------- | ----------------------- | ------------------------------------------- |
132
176
  | promptfoo, DeepEval, … | metered API SDK | billed **per token, every run** |
133
177
  | **vigiles** | your Claude Pro/Max sub | **$0 extra** — and most tiers need no model |
134
178
 
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
-
138
179
  ## More
139
180
 
140
181
  - **[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?
@@ -72,6 +72,31 @@ export declare function setActiveAgent(cwd: string, agentPath: string): void;
72
72
  export declare function clearActiveAgent(cwd: string): void;
73
73
  /** The path of the active agent's compiled `.md`, or null when none is active. */
74
74
  export declare function readActiveAgent(cwd: string): string | null;
75
+ /**
76
+ * EXPERIMENTAL — parked (P3), flat-only, do NOT auto-wire. This deterministic
77
+ * subagent-window tracking assumes FLAT dispatch (one active subagent at a time);
78
+ * it is NOT nesting-safe — Claude Code v2.1.172 added depth-5 nested subagents, so
79
+ * correct tracking needs a depth-aware STACK (push on dispatch, pop on SubagentStop)
80
+ * + verifying the spawn tool name (`Agent` vs `Task`). See
81
+ * research/effect-boundary-design.md ("Why dropped").
82
+ *
83
+ * Resolve a `Task` tool's `subagent_type` to the compiled agent `.md` to
84
+ * activate, or null when none is found. The DETERMINISTIC open signal that
85
+ * replaces the model-invoked `agent-start`: Claude Code fires `PreToolUse` for
86
+ * the parent's `Task` dispatch (and `SubagentStop` when it returns), so the
87
+ * harness — not the model — brackets the subagent's active window. The name is
88
+ * the last ":"-segment (a `--plugin-dir` subagent_type is namespaced
89
+ * "plugin:name"); searched in `agents/` under the cwd then the plugin root. A
90
+ * path under the cwd is returned relative (readActiveAgent resolves vs cwd); a
91
+ * plugin-root hit is absolute. Returns null on an unknown agent (fail-open: an
92
+ * unresolved subagent is simply not gated, exactly as before agent-start ran).
93
+ */
94
+ export declare function resolveDispatchedAgent(subagentType: string, cwd: string, pluginRoot?: string): string | null;
95
+ /**
96
+ * The agent `.md` to activate for a `PreToolUse(Task)` event, or null. Pure
97
+ * (reads `tool_input.subagent_type`, resolves via {@link resolveDispatchedAgent}).
98
+ */
99
+ export declare function decideTaskDispatch(toolInput: unknown, cwd: string, pluginRoot?: string): string | null;
75
100
  /**
76
101
  * PreToolUse-hook decision. If an agent is active, enforce BOTH deterministic
77
102
  * rails its compiled `.md` declares, in order:
@@ -31,6 +31,8 @@ exports.decidePreToolUse = decidePreToolUse;
31
31
  exports.setActiveAgent = setActiveAgent;
32
32
  exports.clearActiveAgent = clearActiveAgent;
33
33
  exports.readActiveAgent = readActiveAgent;
34
+ exports.resolveDispatchedAgent = resolveDispatchedAgent;
35
+ exports.decideTaskDispatch = decideTaskDispatch;
34
36
  exports.evaluatePreToolUse = evaluatePreToolUse;
35
37
  const node_fs_1 = require("node:fs");
36
38
  const node_path_1 = require("node:path");
@@ -129,6 +131,47 @@ function readActiveAgent(cwd) {
129
131
  // ---------------------------------------------------------------------------
130
132
  // PreToolUse-hook decision
131
133
  // ---------------------------------------------------------------------------
134
+ /**
135
+ * EXPERIMENTAL — parked (P3), flat-only, do NOT auto-wire. This deterministic
136
+ * subagent-window tracking assumes FLAT dispatch (one active subagent at a time);
137
+ * it is NOT nesting-safe — Claude Code v2.1.172 added depth-5 nested subagents, so
138
+ * correct tracking needs a depth-aware STACK (push on dispatch, pop on SubagentStop)
139
+ * + verifying the spawn tool name (`Agent` vs `Task`). See
140
+ * research/effect-boundary-design.md ("Why dropped").
141
+ *
142
+ * Resolve a `Task` tool's `subagent_type` to the compiled agent `.md` to
143
+ * activate, or null when none is found. The DETERMINISTIC open signal that
144
+ * replaces the model-invoked `agent-start`: Claude Code fires `PreToolUse` for
145
+ * the parent's `Task` dispatch (and `SubagentStop` when it returns), so the
146
+ * harness — not the model — brackets the subagent's active window. The name is
147
+ * the last ":"-segment (a `--plugin-dir` subagent_type is namespaced
148
+ * "plugin:name"); searched in `agents/` under the cwd then the plugin root. A
149
+ * path under the cwd is returned relative (readActiveAgent resolves vs cwd); a
150
+ * plugin-root hit is absolute. Returns null on an unknown agent (fail-open: an
151
+ * unresolved subagent is simply not gated, exactly as before agent-start ran).
152
+ */
153
+ function resolveDispatchedAgent(subagentType, cwd, pluginRoot) {
154
+ const name = subagentType.split(":").pop()?.trim();
155
+ if (!name)
156
+ return null;
157
+ const rel = (0, node_path_1.join)("agents", `${name}.md`);
158
+ if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, rel)))
159
+ return rel;
160
+ if (pluginRoot && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(pluginRoot, rel))) {
161
+ return (0, node_path_1.resolve)(pluginRoot, rel);
162
+ }
163
+ return null;
164
+ }
165
+ /**
166
+ * The agent `.md` to activate for a `PreToolUse(Task)` event, or null. Pure
167
+ * (reads `tool_input.subagent_type`, resolves via {@link resolveDispatchedAgent}).
168
+ */
169
+ function decideTaskDispatch(toolInput, cwd, pluginRoot) {
170
+ const st = toolInput?.subagent_type;
171
+ if (typeof st !== "string" || !st)
172
+ return null;
173
+ return resolveDispatchedAgent(st, cwd, pluginRoot);
174
+ }
132
175
  /**
133
176
  * PreToolUse-hook decision. If an agent is active, enforce BOTH deterministic
134
177
  * rails its compiled `.md` declares, in order:
@@ -8,6 +8,40 @@
8
8
  * in its adapter (e.g. `src/adapters/codex/dialect.ts` exporting `codexDialect`).
9
9
  */
10
10
  import type { HarnessDialect } from "../../core/dialect.js";
11
+ /**
12
+ * The Claude Code built-in subagent tool catalog as a `const` tuple, so a typed
13
+ * authoring surface can derive a LITERAL union (`ClaudeCodeBuiltinTool`) from it.
14
+ * `claudeCodeDialect.builtinAgentTools` references this same array — one source
15
+ * of truth for the runtime catalog AND the compile-time tool vocabulary.
16
+ */
17
+ export declare const claudeCodeBuiltinAgentTools: readonly ["Read", "Write", "Edit", "MultiEdit", "Bash", "BashOutput", "KillBash", "Grep", "Glob", "LS", "WebSearch", "WebFetch", "NotebookEdit", "TodoWrite", "Task", "Skill"];
18
+ /**
19
+ * The Claude Code side-effecting tools as a `const` tuple (the complement of
20
+ * read-only within `builtinAgentTools`). `claudeCodeDialect.sideEffectingTools`
21
+ * references this; the typed vocabulary derives the read-only / bounded splits.
22
+ */
23
+ export declare const claudeCodeSideEffectingTools: readonly ["Bash", "BashOutput", "KillBash", "Edit", "MultiEdit", "Write", "NotebookEdit", "WebFetch", "WebSearch", "Skill", "Task", "TodoWrite"];
11
24
  export declare const claudeCodeDialect: HarnessDialect;
25
+ /** Every Claude Code built-in subagent tool (literal union). */
26
+ export type ClaudeCodeBuiltinTool = (typeof claudeCodeBuiltinAgentTools)[number];
27
+ /** The side-effecting subset (literal union). */
28
+ export type ClaudeCodeSideEffectingTool = (typeof claudeCodeSideEffectingTools)[number];
29
+ /**
30
+ * Read-only built-in tools — the complement of the side-effecting set within
31
+ * the built-in catalog. The tools a `pure` CC unit may declare.
32
+ */
33
+ export type ClaudeCodeReadOnlyTool = Exclude<ClaudeCodeBuiltinTool, ClaudeCodeSideEffectingTool>;
34
+ /**
35
+ * Tools a `bounded` CC unit may declare: read-only ∪ the decidable
36
+ * side-effecting tools (Write/Edit/MultiEdit/NotebookEdit) ∪ `Bash` (its
37
+ * command is decided at RUNTIME by the gate). Bars MCP / unknown / wildcard —
38
+ * those are simply not in the built-in union, so listing one is a `tsc` error.
39
+ *
40
+ * NOTE: `BashOutput`/`KillBash` are read-only-ish helpers tied to a running
41
+ * Bash; `Bash` is the admitting tool, so they're included via the read-only
42
+ * exclusion path only if read-only — here they stay side-effecting, hence the
43
+ * explicit add of the bounded-decidable set plus `Bash`.
44
+ */
45
+ export type ClaudeCodeBoundedTool = ClaudeCodeReadOnlyTool | "Write" | "Edit" | "MultiEdit" | "NotebookEdit" | "Bash";
12
46
  export type { HarnessDialect } from "../../core/dialect.js";
13
47
  //# sourceMappingURL=dialect.d.ts.map
@@ -1,28 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.claudeCodeDialect = void 0;
3
+ exports.claudeCodeDialect = exports.claudeCodeSideEffectingTools = exports.claudeCodeBuiltinAgentTools = void 0;
4
+ /**
5
+ * The Claude Code built-in subagent tool catalog as a `const` tuple, so a typed
6
+ * authoring surface can derive a LITERAL union (`ClaudeCodeBuiltinTool`) from it.
7
+ * `claudeCodeDialect.builtinAgentTools` references this same array — one source
8
+ * of truth for the runtime catalog AND the compile-time tool vocabulary.
9
+ */
10
+ exports.claudeCodeBuiltinAgentTools = [
11
+ "Read",
12
+ "Write",
13
+ "Edit",
14
+ "MultiEdit",
15
+ "Bash",
16
+ "BashOutput",
17
+ "KillBash",
18
+ "Grep",
19
+ "Glob",
20
+ "LS",
21
+ "WebSearch",
22
+ "WebFetch",
23
+ "NotebookEdit",
24
+ "TodoWrite",
25
+ "Task",
26
+ "Skill",
27
+ ];
28
+ /**
29
+ * The Claude Code side-effecting tools as a `const` tuple (the complement of
30
+ * read-only within `builtinAgentTools`). `claudeCodeDialect.sideEffectingTools`
31
+ * references this; the typed vocabulary derives the read-only / bounded splits.
32
+ */
33
+ exports.claudeCodeSideEffectingTools = [
34
+ "Bash",
35
+ "BashOutput",
36
+ "KillBash",
37
+ "Edit",
38
+ "MultiEdit",
39
+ "Write",
40
+ "NotebookEdit",
41
+ "WebFetch",
42
+ "WebSearch",
43
+ "Skill",
44
+ "Task",
45
+ "TodoWrite",
46
+ ];
4
47
  exports.claudeCodeDialect = {
5
48
  name: "claude-code",
6
49
  // The tool contract a subagent may declare — the rails it runs on. Anything
7
50
  // else must be an MCP tool, else it's a typo / nonexistent tool.
8
- builtinAgentTools: [
9
- "Read",
10
- "Write",
11
- "Edit",
12
- "MultiEdit",
13
- "Bash",
14
- "BashOutput",
15
- "KillBash",
16
- "Grep",
17
- "Glob",
18
- "LS",
19
- "WebSearch",
20
- "WebFetch",
21
- "NotebookEdit",
22
- "TodoWrite",
23
- "Task",
24
- "Skill",
25
- ],
51
+ builtinAgentTools: exports.claudeCodeBuiltinAgentTools,
26
52
  // Tools the platform never exposes to a subagent, whatever the list says — so
27
53
  // a subagent listing one is a guaranteed-dead reference only a compiler catches.
28
54
  neverAvailableTools: [
@@ -68,19 +94,6 @@ exports.claudeCodeDialect = {
68
94
  // which are not in the subagent catalog). Bash is side-effecting because
69
95
  // `cat` and `rm -rf` are the same tool at the tool-name level — the
70
96
  // sandbox is the only closure for subprocess effects.
71
- sideEffectingTools: [
72
- "Bash",
73
- "BashOutput",
74
- "KillBash",
75
- "Edit",
76
- "MultiEdit",
77
- "Write",
78
- "NotebookEdit",
79
- "WebFetch",
80
- "WebSearch",
81
- "Skill",
82
- "Task",
83
- "TodoWrite",
84
- ],
97
+ sideEffectingTools: exports.claudeCodeSideEffectingTools,
85
98
  };
86
99
  //# sourceMappingURL=dialect.js.map
@@ -38,7 +38,6 @@ const node_fs_1 = require("node:fs");
38
38
  const node_path_1 = require("node:path");
39
39
  const effects_js_1 = require("../../core/effects.js");
40
40
  const dialect_js_1 = require("./dialect.js");
41
- const effect_region_js_1 = require("./effect-region.js");
42
41
  const STEP_RE = /^###\s+Step\s+(\d+)/;
43
42
  const GATE_CMD_RE = /<!--\s*vigiles:gate\s+"([^"]*)"(?:\s+retry:(\d+))?\s*-->/;
44
43
  const GATE_FILE_RE = /<!--\s*vigiles:gate\s+file:(\S+)\s*-->/;
@@ -294,13 +293,6 @@ function evaluateSkillPreToolUse(cwd, tool, command) {
294
293
  return { allow: true, message: "" };
295
294
  const md = (0, node_fs_1.readFileSync)(full, "utf-8");
296
295
  const purity = parseSkillPurity(md);
297
- const boundary = (0, effect_region_js_1.hasEffectBoundary)(md);
298
- if (boundary) {
299
- const effective = (0, effect_region_js_1.readEffectActive)(cwd)
300
- ? (purity ?? "unrestricted")
301
- : "pure";
302
- return (0, effects_js_1.decidePurityGate)(effective, tool, command, dialect_js_1.claudeCodeDialect);
303
- }
304
296
  if (!purity)
305
297
  return { allow: true, message: "" };
306
298
  return (0, effects_js_1.decidePurityGate)(purity, tool, command, dialect_js_1.claudeCodeDialect);
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Typed Claude Code authoring surface — the compile-time half of the purity
3
+ * contract, bound to the Claude Code tool vocabulary.
4
+ *
5
+ * The core `agent()` / `skill()` builders (`vigiles/spec`) are generic over a
6
+ * tool `ToolVocabulary` that DEFAULTS to fully-open (`string` at every purity
7
+ * level), so they accept any tools — backwards-compatible, harness-agnostic.
8
+ * This module re-binds them to the CONCRETE Claude Code vocabulary derived from
9
+ * `claudeCodeDialect`, so authoring a spec with an invalid `purity`×`tools`
10
+ * combination is a `tsc` error at EDIT TIME, before any vigiles command runs:
11
+ *
12
+ * import { agent } from "vigiles/claude-code";
13
+ *
14
+ * agent({ purity: "pure", tools: ["Read", "Bash"] });
15
+ * // ^^^^^^ tsc error — Bash side-effecting
16
+ *
17
+ * agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
18
+ * agent({ purity: "bounded", tools: ["mcp__x__y"] });
19
+ * // ^^^^^^^^^^^ tsc error — MCP not decidable
20
+ *
21
+ * agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
22
+ *
23
+ * This is a STRICT ADDITION to the runtime/compile checks: `purityViolations`
24
+ * (`vigiles compile`) and `decidePurityGate` (the PreToolUse gate) are unchanged
25
+ * and remain the universal backstop. In particular the command-level decision a
26
+ * `bounded` unit makes for `Bash` (read-only command allowed, mutating denied)
27
+ * is the RUNTIME gate's job — the type only admits the `Bash` TOOL at `bounded`.
28
+ *
29
+ * The CC literal tool names live ONLY in this adapter (and the dialect it reads
30
+ * from), never in core — the hexagonal boundary that keeps the domain
31
+ * harness-agnostic.
32
+ */
33
+ import { type AgentSpec, type AgentSpecInput, type SkillSpec, type SkillSpecInput, type AuthoredPurity, type ToolVocabulary } from "../../core/spec.js";
34
+ import type { ClaudeCodeReadOnlyTool, ClaudeCodeBoundedTool } from "./dialect.js";
35
+ /**
36
+ * The Claude Code tool vocabulary, split by the purity floor that admits each
37
+ * tool (mirrors the runtime ladder in `core/effects.ts`):
38
+ * - `readOnly`: tools a `pure` unit may declare (Read/Grep/Glob/LS).
39
+ * - `bounded`: read-only ∪ Write/Edit/MultiEdit/NotebookEdit ∪ `Bash`.
40
+ */
41
+ export interface ClaudeCodeToolVocabulary extends ToolVocabulary {
42
+ readonly readOnly: ClaudeCodeReadOnlyTool;
43
+ readonly bounded: ClaudeCodeBoundedTool;
44
+ }
45
+ /**
46
+ * Define a Claude Code subagent with the purity floor enforced AT COMPILE TIME
47
+ * against the Claude Code tool catalog. Identical to the core `agent()` at
48
+ * runtime (it IS the core builder); the only difference is the typed `tools`
49
+ * constraint. `P` is inferred from the literal `purity` field.
50
+ */
51
+ export declare function agent<const P extends AuthoredPurity | undefined = undefined>(spec: AgentSpecInput<P, ClaudeCodeToolVocabulary>): AgentSpec;
52
+ /**
53
+ * Define a Claude Code skill with the purity floor enforced AT COMPILE TIME
54
+ * against the Claude Code tool catalog. Identical to the core `skill()` at
55
+ * runtime; the typed `tools` constraint is the only difference.
56
+ */
57
+ export declare function skill<const P extends AuthoredPurity | undefined = undefined>(spec: SkillSpecInput<P, ClaudeCodeToolVocabulary>): SkillSpec;
58
+ //# sourceMappingURL=typed-spec.d.ts.map
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agent = agent;
4
+ exports.skill = skill;
5
+ /**
6
+ * Typed Claude Code authoring surface — the compile-time half of the purity
7
+ * contract, bound to the Claude Code tool vocabulary.
8
+ *
9
+ * The core `agent()` / `skill()` builders (`vigiles/spec`) are generic over a
10
+ * tool `ToolVocabulary` that DEFAULTS to fully-open (`string` at every purity
11
+ * level), so they accept any tools — backwards-compatible, harness-agnostic.
12
+ * This module re-binds them to the CONCRETE Claude Code vocabulary derived from
13
+ * `claudeCodeDialect`, so authoring a spec with an invalid `purity`×`tools`
14
+ * combination is a `tsc` error at EDIT TIME, before any vigiles command runs:
15
+ *
16
+ * import { agent } from "vigiles/claude-code";
17
+ *
18
+ * agent({ purity: "pure", tools: ["Read", "Bash"] });
19
+ * // ^^^^^^ tsc error — Bash side-effecting
20
+ *
21
+ * agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
22
+ * agent({ purity: "bounded", tools: ["mcp__x__y"] });
23
+ * // ^^^^^^^^^^^ tsc error — MCP not decidable
24
+ *
25
+ * agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
26
+ *
27
+ * This is a STRICT ADDITION to the runtime/compile checks: `purityViolations`
28
+ * (`vigiles compile`) and `decidePurityGate` (the PreToolUse gate) are unchanged
29
+ * and remain the universal backstop. In particular the command-level decision a
30
+ * `bounded` unit makes for `Bash` (read-only command allowed, mutating denied)
31
+ * is the RUNTIME gate's job — the type only admits the `Bash` TOOL at `bounded`.
32
+ *
33
+ * The CC literal tool names live ONLY in this adapter (and the dialect it reads
34
+ * from), never in core — the hexagonal boundary that keeps the domain
35
+ * harness-agnostic.
36
+ */
37
+ const spec_js_1 = require("../../core/spec.js");
38
+ /**
39
+ * Define a Claude Code subagent with the purity floor enforced AT COMPILE TIME
40
+ * against the Claude Code tool catalog. Identical to the core `agent()` at
41
+ * runtime (it IS the core builder); the only difference is the typed `tools`
42
+ * constraint. `P` is inferred from the literal `purity` field.
43
+ */
44
+ function agent(spec) {
45
+ return (0, spec_js_1.agent)(spec);
46
+ }
47
+ /**
48
+ * Define a Claude Code skill with the purity floor enforced AT COMPILE TIME
49
+ * against the Claude Code tool catalog. Identical to the core `skill()` at
50
+ * runtime; the typed `tools` constraint is the only difference.
51
+ */
52
+ function skill(spec) {
53
+ return (0, spec_js_1.skill)(spec);
54
+ }
55
+ //# sourceMappingURL=typed-spec.js.map
@@ -9,6 +9,7 @@ export * from "./adapters/claude-code/plugin-loader.js";
9
9
  export * from "./mock-model.js";
10
10
  export { claudeCodeDriver, buildClaudeArgs, parseClaudeRun, claudeAvailable, } from "./harness-test.js";
11
11
  export * from "./adapters/claude-code/dialect.js";
12
+ export { agent, skill, type ClaudeCodeToolVocabulary, } from "./adapters/claude-code/typed-spec.js";
12
13
  export * from "./adapters/claude-code/layout.js";
13
14
  export * from "./adapters/claude-code/runtime.js";
14
15
  export * from "./adapters/claude-code/hook-protocol.js";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
17
+ exports.skill = exports.agent = exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
18
18
  /**
19
19
  * `vigiles/claude-code` — the Claude Code-specific harness pieces a *different*
20
20
  * harness would swap out: the plugin/repo loader (reads real Claude Code plugin
@@ -35,6 +35,13 @@ Object.defineProperty(exports, "buildClaudeArgs", { enumerable: true, get: funct
35
35
  Object.defineProperty(exports, "parseClaudeRun", { enumerable: true, get: function () { return harness_test_js_1.parseClaudeRun; } });
36
36
  Object.defineProperty(exports, "claudeAvailable", { enumerable: true, get: function () { return harness_test_js_1.claudeAvailable; } });
37
37
  __exportStar(require("./adapters/claude-code/dialect.js"), exports);
38
+ // The typed Claude Code authoring surface: `agent` / `skill` with the `purity`
39
+ // floor enforced AT COMPILE TIME against the CC tool catalog (a `tsc` error for
40
+ // e.g. `purity: "pure"` + `"Bash"`). A strict addition to the runtime/compile
41
+ // purity checks; the bare core `agent()`/`skill()` (`vigiles/spec`) stay open.
42
+ var typed_spec_js_1 = require("./adapters/claude-code/typed-spec.js");
43
+ Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return typed_spec_js_1.agent; } });
44
+ Object.defineProperty(exports, "skill", { enumerable: true, get: function () { return typed_spec_js_1.skill; } });
38
45
  __exportStar(require("./adapters/claude-code/layout.js"), exports);
39
46
  __exportStar(require("./adapters/claude-code/runtime.js"), exports);
40
47
  __exportStar(require("./adapters/claude-code/hook-protocol.js"), exports);