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 +57 -16
- package/dist/adapters/claude-code/agent-runtime.d.ts +25 -0
- package/dist/adapters/claude-code/agent-runtime.js +43 -0
- package/dist/adapters/claude-code/dialect.d.ts +34 -0
- package/dist/adapters/claude-code/dialect.js +46 -33
- package/dist/adapters/claude-code/skill-runtime.js +0 -8
- package/dist/adapters/claude-code/typed-spec.d.ts +58 -0
- package/dist/adapters/claude-code/typed-spec.js +55 -0
- package/dist/claude-code.d.ts +1 -0
- package/dist/claude-code.js +8 -1
- package/dist/cli.js +150 -4
- package/dist/core/compile.d.ts +1 -1
- package/dist/core/compile.js +14 -0
- package/dist/core/generate-harness.d.ts +187 -0
- package/dist/core/generate-harness.js +337 -0
- package/dist/core/spec.d.ts +290 -8
- package/dist/core/spec.js +118 -3
- package/dist/scaffold-test.d.ts +28 -0
- package/dist/scaffold-test.js +113 -8
- package/package.json +1 -1
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 &
|
|
8
|
+
<strong>Lint, test & 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>,
|
|
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
|
|
24
|
-
|
|
25
|
-
deterministic layer for it
|
|
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**
|
|
30
|
-
| **🧪 Test**
|
|
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
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
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
|
package/dist/claude-code.d.ts
CHANGED
|
@@ -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";
|
package/dist/claude-code.js
CHANGED
|
@@ -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);
|