vigiles 7.0.0 → 9.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 +207 -88
- package/dist/adoptability.d.ts +55 -0
- package/dist/adoptability.js +196 -0
- package/dist/audit-html.d.ts +20 -0
- package/dist/audit-html.js +61 -0
- package/dist/audit-prompts.d.ts +46 -0
- package/dist/audit-prompts.js +90 -0
- package/dist/audit-report.d.ts +70 -0
- package/dist/audit-report.js +51 -0
- package/dist/audit-report.template.html +110 -0
- package/dist/audit-score.d.ts +44 -0
- package/dist/audit-score.js +221 -0
- package/dist/cli-commands.d.ts +1 -1
- package/dist/cli-commands.js +3 -7
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +749 -180
- package/dist/core/adopt.d.ts +65 -0
- package/dist/core/adopt.js +199 -0
- package/dist/core/compose.d.ts +1 -1
- package/dist/core/compose.js +1 -1
- package/dist/core/evolve.d.ts +4 -0
- package/dist/core/evolve.js +4 -0
- package/dist/core/frontmatter.d.ts +8 -7
- package/dist/core/frontmatter.js +8 -7
- package/dist/core/generate-harness.d.ts +1 -1
- package/dist/core/generate-harness.js +3 -3
- package/dist/core/generate-schema.js +1 -1
- package/dist/core/inline.d.ts +6 -6
- package/dist/core/inline.js +17 -7
- package/dist/core/integrity.d.ts +31 -0
- package/dist/core/integrity.js +45 -0
- package/dist/core/orphans.js +1 -1
- package/dist/core/spec.d.ts +40 -2
- package/dist/core/spec.js +16 -1
- package/dist/core/types.d.ts +42 -6
- package/dist/core/validate.js +26 -26
- package/dist/dialect-drift.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/guardrail-check.d.ts +1 -1
- package/dist/guardrail-check.js +1 -1
- package/dist/optimize.d.ts +12 -5
- package/dist/optimize.js +27 -5
- package/dist/scan-behavioral.d.ts +8 -2
- package/dist/scan-behavioral.js +6 -4
- package/dist/scan-trigger-suggest.d.ts +91 -0
- package/dist/scan-trigger-suggest.js +103 -0
- package/dist/scan.d.ts +53 -12
- package/dist/scan.js +92 -16
- package/dist/score-explainer.d.ts +1 -1
- package/dist/setup-plan.d.ts +59 -1
- package/dist/setup-plan.js +103 -5
- package/hooks/post-edit.sh +1 -1
- package/package.json +4 -2
- package/skills/adopt-spec/SKILL.md +7 -7
- package/skills/linter-docs/eslint.md +1 -1
- package/skills/strengthen/SKILL.md +1 -1
- package/skills/test-harness/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
README DIRECTION — read before editing; keep changes aligned.
|
|
3
|
+
This file is the FRONT DOOR + a marketing asset for someone who already lives
|
|
4
|
+
in Claude Code / Codex. Optimize for a phone-skimmer.
|
|
5
|
+
|
|
6
|
+
1. LEAD WITH BENEFITS, not mechanics or vocabulary. Say what the user GETS
|
|
7
|
+
(a guard that can't silently fail; a CLAUDE.md that stops lying) before how.
|
|
8
|
+
1b. NEVER OPEN WITH A NEGATIVE, APOLOGY, OR CAVEAT. A bolded lead-in is the
|
|
9
|
+
FIRST thing a skimmer reads, so it must be the benefit/on-ramp, never a
|
|
10
|
+
deficiency or competitor: write "Start in plain markdown", NOT "No
|
|
11
|
+
TypeScript?". Put the STRONGEST proof (e.g. 2/7→7/7) on its OWN line, never
|
|
12
|
+
buried mid-paragraph, and END a section on the win, not the caveat (demote
|
|
13
|
+
trade-offs to a trailing aside). Break run-on em-dash/semicolon chains — a
|
|
14
|
+
paragraph is ≤ ~3 lines, one idea.
|
|
15
|
+
1c. LEAD WITH THE CONCRETE PAIN the reader already feels — named in THEIR
|
|
16
|
+
situation, with the SPECIFIC silent failure, not an abstraction. "You
|
|
17
|
+
installed plugins and wrote skills — but do they actually work? A skill that
|
|
18
|
+
never fires, a hook that blocks nothing, a CLAUDE.md full of dead refs" beats
|
|
19
|
+
"reliability for your harness"; "a library with no tests" is the anchoring
|
|
20
|
+
analogy. This is NOT a 1b violation: a pain about the READER's situation is a
|
|
21
|
+
hook, not an apology — 1b bans opening with vigiles's OWN deficiency, a
|
|
22
|
+
caveat, or a competitor, never the user's pain. SAME FOR THE SUBDOCS: open
|
|
23
|
+
every guide with the concrete pain, THEN the "what this doc is" line + the
|
|
24
|
+
README uplink (per docs-quality in CLAUDE.md).
|
|
25
|
+
2. SPEC-FIRST IS THE DEFAULT — but easy, never intimidating. `init` adopts your
|
|
26
|
+
existing CLAUDE.md INTO a spec, and model-invocable skills (edit-spec /
|
|
27
|
+
strengthen / test-harness) author + edit it, so you rarely hand-write a
|
|
28
|
+
.spec.ts and hooks auto-compile on save. Present the typed spec as the default
|
|
29
|
+
the agent manages for you, NOT an advanced opt-in or a "step up". Inline
|
|
30
|
+
markdown is the ZERO-TS FLOOR for anyone who skips `init` (progressive
|
|
31
|
+
adoption) — the on-ramp, not the default starting point. `eject` always
|
|
32
|
+
reverses. NEVER a wall.
|
|
33
|
+
3. THE INSTRUMENTS stay first-class — including Eval (measuring whether a skill
|
|
34
|
+
actually helps is core, not optional). NOTE: Guard / compiled hooks is PARKED
|
|
35
|
+
FOR LAUNCH (commented out below; see research/roadmap.md "Launch readiness") —
|
|
36
|
+
so the live set is Lint/Test/Eval ("three instruments"); re-add Guard post-HN.
|
|
37
|
+
4. SCANNABLE + SHORT — ~200-line cap; punchy table cells, bullets, runnable
|
|
38
|
+
blocks; benefits over jargon. Push depth into docs/ and LINK it.
|
|
39
|
+
5. NO INTERNAL VOCABULARY (moat / measurement-authority / flywheel) and NO
|
|
40
|
+
research/ links — name the user benefit (see public-vs-internal-docs +
|
|
41
|
+
readme-brevity in CLAUDE.md).
|
|
42
|
+
-->
|
|
43
|
+
|
|
1
44
|
<p align="center">
|
|
2
45
|
<img src="logo.png" width="140" alt="vigiles logo" />
|
|
3
46
|
</p>
|
|
@@ -8,10 +51,6 @@
|
|
|
8
51
|
<strong>Make the harness your AI agent runs on reliable.</strong>
|
|
9
52
|
</p>
|
|
10
53
|
|
|
11
|
-
<p align="center">
|
|
12
|
-
Your CLAUDE.md, hooks, and skills steer the agent — but nothing checks they're <em>true</em>, nothing stops them entering a <em>bad state</em>, nothing tests they <em>work</em>, and nothing measures whether they actually <em>help</em>. vigiles does all four — deterministically, mostly with no API key.
|
|
13
|
-
</p>
|
|
14
|
-
|
|
15
54
|
<p align="center">
|
|
16
55
|
<a href="https://www.npmjs.com/package/vigiles"><img src="https://img.shields.io/npm/v/vigiles?color=orange" alt="npm version" /></a>
|
|
17
56
|
<a href="https://github.com/zernie/vigiles/actions"><img src="https://img.shields.io/github/actions/workflow/status/zernie/vigiles/ci.yml?branch=main" alt="CI" /></a>
|
|
@@ -20,47 +59,100 @@
|
|
|
20
59
|
|
|
21
60
|
---
|
|
22
61
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
62
|
+
**You installed a bunch of plugins and wrote a few skills — but do they actually work?**
|
|
63
|
+
A skill that never fires, a subagent wired to a tool that doesn't exist, a CLAUDE.md
|
|
64
|
+
full of dead references — your harness fails **silently**, and you find out mid-task.
|
|
65
|
+
|
|
66
|
+
**It's a library with no tests.**
|
|
67
|
+
|
|
68
|
+
**One command shows you — like a Lighthouse report for your harness:**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx vigiles audit # no key, no config, safe to run anywhere
|
|
72
|
+
```
|
|
27
73
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| **🔎 Lint** | Every file path, script, code symbol, and linter rule your CLAUDE.md cites is checked against reality — a lint that asks _is this **true**_, not just _well-formed_ — so a renamed file or a disabled rule can't silently mislead the agent. **[→](docs/verifying-instruction-files.md)** |
|
|
31
|
-
| **🛡 Guard** | Author a hook as a pure typed function and vigiles compiles it — making **whole classes of hook bugs unrepresentable** (the guard that silently doesn't block). **[→](docs/compiled-hooks.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
|
-
| **📊 Eval** | 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 eval you can afford. The same engine ranks the hyped ecosystem: **what works vs hype**. **[→](docs/measuring-skills.md)** |
|
|
74
|
+
It **reads** your harness deterministically — every reference checked, four categories
|
|
75
|
+
scored, each finding's fix inline — and writes a shareable HTML report:
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
77
|
+
<p align="center">
|
|
78
|
+
<img src="vigiles-audit.png" width="760" alt="vigiles audit report: an overall score with four category rings — Truthfulness, Triggering, Structure, Tested — and fix cards" />
|
|
79
|
+
</p>
|
|
80
|
+
|
|
81
|
+
| Ring | What it proves |
|
|
82
|
+
| ------------------- | --------------------------------------------------------------------- |
|
|
83
|
+
| **🔎 Truthfulness** | Every path / script / symbol / linter rule in your CLAUDE.md resolves |
|
|
84
|
+
| **🎯 Triggering** | Skills fire on the right prompts and don't collide |
|
|
85
|
+
| **🔧 Structure** | Tool contracts, MCP servers & frontmatter are sound |
|
|
86
|
+
| **🧪 Tested** | Every surface ships a test |
|
|
87
|
+
|
|
88
|
+
Like Lighthouse, `audit` is a **local report you run on your machine** — a
|
|
89
|
+
deterministic read, safe on any repo (even one wired to prod), identical on every
|
|
90
|
+
OS. **Not a CI step** (CI uses `lint`, the deterministic gate). The **executing
|
|
91
|
+
checks** (resolve MCP live · **do your skills fire?** · **what would vigiles
|
|
92
|
+
catch in your repo?**) run only when there's a human to consent: `audit` **asks
|
|
93
|
+
once** at a terminal and remembers. **[Audit a harness →](docs/for-plugin-authors.md)**
|
|
94
|
+
|
|
95
|
+
`Agent = Model + Harness` — the model gets the headlines, the harness is the half you
|
|
96
|
+
own. vigiles[^name] is how you make it prove itself: `audit` is the dashboard, and
|
|
97
|
+
**three instruments** fix and prove what it finds —
|
|
98
|
+
|
|
99
|
+
| | |
|
|
100
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| **🔎 Lint** | Your CLAUDE.md stops lying — every path, script, symbol & linter rule checked against **reality**. **[→](docs/verifying-instruction-files.md)** |
|
|
102
|
+
| **🧪 Test** | Prove your hooks, skills & subagents do their job — **free, no API key**. **[→](docs/harness-testing.md)** |
|
|
103
|
+
| **📊 Eval** | Know if a skill helps or just costs — **A/B on real tasks**, on your own subscription. **[→](docs/measuring-skills.md)** |
|
|
104
|
+
|
|
105
|
+
<!-- PARKED FOR LAUNCH — Guard / compiled hooks. Re-add this row + the ④ section below post-HN. See research/roadmap.md "Launch readiness".
|
|
106
|
+
| **🛡 Guard** | A safety hook that **can't silently fail open** — write a typed function, get a guard that blocks. **[→](docs/compiled-hooks.md)** |
|
|
107
|
+
-->
|
|
108
|
+
|
|
109
|
+
**Two ways in** — pick the pain that's yours:
|
|
110
|
+
|
|
111
|
+
- **Run agents on your own repo?** `npx vigiles audit`, then `npx vigiles init`.
|
|
112
|
+
- **Ship plugins to a marketplace?** `npx vigiles audit ./plugins/*/` ranks a whole
|
|
113
|
+
marketplace (0–100, A–F) — see the **[plugin-author guide →](docs/for-plugin-authors.md)**.
|
|
114
|
+
|
|
115
|
+
**Your agent writes the spec — and you can always eject.** Skills author the
|
|
116
|
+
`.spec.ts` for you, **`init` adopts an existing CLAUDE.md non-destructively**
|
|
117
|
+
(untouched until you compile), and plain markdown + inline `<!-- vigiles:enforce -->`
|
|
118
|
+
comments work with zero TypeScript. Works with **Claude Code and Codex**
|
|
119
|
+
([`vigiles/codex`](docs/harnesses.md)) or [your own harness](docs/authoring-an-adapter.md).
|
|
38
120
|
|
|
39
121
|
## Quick start
|
|
40
122
|
|
|
41
123
|
**Paste into Claude Code or Codex:**
|
|
42
124
|
|
|
43
125
|
```text
|
|
44
|
-
Set up vigiles in this repo
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
126
|
+
Set up vigiles in this repo: run `npx vigiles init` and accept the defaults. If I
|
|
127
|
+
already have a CLAUDE.md or AGENTS.md, adopt it into a spec and show me which
|
|
128
|
+
references are stale. Then install the dep, compile, and write + run one harness
|
|
129
|
+
test for a hook or skill of mine. Don't enforce a spec-per-file or add a real-model
|
|
130
|
+
eval without asking me first.
|
|
48
131
|
```
|
|
49
132
|
|
|
133
|
+
The same prompt works in Codex.
|
|
134
|
+
|
|
50
135
|
Or do it yourself:
|
|
51
136
|
|
|
52
137
|
```bash
|
|
53
138
|
npx vigiles init # sets up lint + test: spec + harness test + CI + plugin
|
|
54
139
|
```
|
|
55
140
|
|
|
56
|
-
Interactive in a terminal, non-interactive for agents/CI (or `--yes`).
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
Interactive in a terminal, non-interactive for agents/CI (or `--yes`).
|
|
142
|
+
|
|
143
|
+
**You don't hand-write any of this — your agent does.** `init` installs
|
|
144
|
+
model-invocable skills, so a plain-English ask does the work (it edits the source
|
|
145
|
+
and recompiles on save; you never touch it by hand):
|
|
146
|
+
|
|
147
|
+
- _"test my skills"_ → scaffolds **and runs** a trigger/behaviour test (`test-harness`)
|
|
148
|
+
- _"harden my rules"_ → upgrades prose guidance into enforced linter rules (`strengthen`)
|
|
149
|
+
- _"add a rule to my CLAUDE.md"_ → edits the source and recompiles (`edit-spec`)
|
|
59
150
|
|
|
60
151
|
<details>
|
|
61
152
|
<summary>What <code>init</code> sets up</summary>
|
|
62
153
|
|
|
63
154
|
- **Both lint and test** by default; scope with `--lint` / `--test`.
|
|
155
|
+
- **Already have a CLAUDE.md / AGENTS.md? `init` adopts it** into a spec faithfully and **non-destructively** — your file is left untouched until you choose to `compile` (and `eject` undoes it).
|
|
64
156
|
- Adds `vigiles` to `devDependencies`; installs the Claude Code plugin (skills + hooks) via the marketplace — globally, never vendored.
|
|
65
157
|
- Wires CI as a `zernie/vigiles@v1` workflow (a composite over the same CLI) that posts a sticky PR comment + a `valid` output.
|
|
66
158
|
|
|
@@ -71,10 +163,10 @@ Prefer to write tests yourself? They can be JS **or** TS
|
|
|
71
163
|
|
|
72
164
|
## ① Lint — your CLAUDE.md lies to your agent
|
|
73
165
|
|
|
74
|
-
Your CLAUDE.md
|
|
75
|
-
|
|
76
|
-
trusts the stale claim and acts on
|
|
77
|
-
reference against reality:
|
|
166
|
+
**Your CLAUDE.md drifts the moment you refactor.** It points the agent at
|
|
167
|
+
`src/auth/login.ts` and says run `npm run check` — but the file moved six commits
|
|
168
|
+
ago and the script was renamed. The agent trusts the stale claim and acts on
|
|
169
|
+
fiction. `npx vigiles lint` resolves every reference against reality:
|
|
78
170
|
|
|
79
171
|
```text
|
|
80
172
|
CLAUDE.md:
|
|
@@ -83,57 +175,33 @@ CLAUDE.md:
|
|
|
83
175
|
✓ @typescript-eslint/no-floating-promises — exists and enabled in eslint config
|
|
84
176
|
```
|
|
85
177
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
> **Markdown is prose; a typed spec is a _program_.** Opt in and an agent that
|
|
92
|
-
> leaks or hands off mismatched data is a **type error** — your multi-agent
|
|
93
|
-
> pipeline won't compile if the handoffs don't line up. Others lint prose; vigiles
|
|
94
|
-
> is a **compiler for harnesses**, graduated like `strict`. **[The moat →](research/typed-spec-moat.md)**
|
|
95
|
-
|
|
96
|
-
**Same cross-reference, any plugin.** `npx vigiles scan` checks a plugin's
|
|
97
|
-
_contracts_ — every subagent tool, `mcp__server__tool`, `mcp_tool` hook, hook
|
|
98
|
-
event, and script path actually **exists and resolves**, not just parses (valid
|
|
99
|
-
YAML ≠ a real tool). A **superset of `claude plugin validate`**, no key. **[Audit any plugin →](docs/cli.md#scan-dir)**
|
|
100
|
-
|
|
101
|
-
## ② Guard — author a hook that can't be wrong
|
|
178
|
+
<p align="center">
|
|
179
|
+
<img src="vigiles-demo.gif" width="720" alt="vigiles lint catching a file that moved and a script that was renamed" />
|
|
180
|
+
</p>
|
|
181
|
+
<!-- Regenerate the GIF: `python3 scripts/make-demo-gif.py` (output is verbatim CLI; see scripts/demo.sh for a live asciinema recording). -->
|
|
102
182
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
JSON field, a `grep` that misses `cd x && git push -f`): they look like a guard
|
|
106
|
-
and block nothing. Write the hook as a pure typed function instead — vigiles
|
|
107
|
-
compiles the protocol for you:
|
|
183
|
+
File paths, scripts, code symbols — plus linter rules across **7 linters**
|
|
184
|
+
(ESLint, Ruff, Clippy + four more): each rule exists **and is enabled**.
|
|
108
185
|
|
|
109
|
-
|
|
110
|
-
|
|
186
|
+
**Your agent writes the spec — `init` adopts your existing CLAUDE.md into one,
|
|
187
|
+
faithfully and non-destructively.** Prefer zero new files? Plain markdown + one
|
|
188
|
+
inline `<!-- vigiles:enforce -->` comment lints too — no spec, no TypeScript. And
|
|
189
|
+
`vigiles eject` hands a spec back to markdown anytime.
|
|
190
|
+
**[Full guide →](docs/verifying-instruction-files.md)**
|
|
111
191
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
e.command.runs("git push", { force: true })
|
|
117
|
-
? deny("no force-push to a protected branch")
|
|
118
|
-
: allow(),
|
|
119
|
-
});
|
|
120
|
-
```
|
|
192
|
+
> **Want more? Bad states can stop compiling.** Opt in deeper and a broken
|
|
193
|
+
> hand-off between agents becomes a build error instead of a runtime surprise —
|
|
194
|
+
> graduated like TypeScript's `strict`, on only when you want it.
|
|
195
|
+
> **[How →](docs/compiled-hooks.md)**
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
field (false confidence), the matcher is **AST-backed** (catches the compound
|
|
124
|
-
bypass a glob misses), the only import allowed is `vigiles/hook` (capability = API
|
|
125
|
-
surface), and the artifact is **stamped** (a later hand-edit is refused). A real,
|
|
126
|
-
widely-copied OSS safety hook blocks **2/7** of the disaster battery; the compiled
|
|
127
|
-
rewrite blocks **7/7**. Honest about the cons: this fixes a hook's _logic_, not
|
|
128
|
-
the harness's _delivery_ — a subagent's tool calls still bypass any PreToolUse
|
|
129
|
-
hook ([#34692](https://github.com/anthropics/claude-code/issues/34692)), so it's a
|
|
130
|
-
strong default, not an unbypassable wall. **[Compiled hooks — bug classes + trade-offs →](docs/compiled-hooks.md)**
|
|
197
|
+
## ② Test — does your harness do its job?
|
|
131
198
|
|
|
132
|
-
|
|
199
|
+
**You wired the hook — but does it actually block?** A skill's description can fail
|
|
200
|
+
to trigger, or hijack unrelated prompts; injected context can silently never reach
|
|
201
|
+
the model. All of it passes a naive "did it run?" check. vigiles tests the
|
|
202
|
+
assembled harness for real.
|
|
133
203
|
|
|
134
|
-
|
|
135
|
-
unrelated prompts; injected context can never reach the model. All of it passes a
|
|
136
|
-
naive "did it run?" check. vigiles tests the assembled harness for real:
|
|
204
|
+
Start with the cheapest tier — a hook, called directly. No model, no key:
|
|
137
205
|
|
|
138
206
|
```typescript
|
|
139
207
|
import { runHook } from "vigiles/testing";
|
|
@@ -150,18 +218,19 @@ It goes well past _"did it fire?"_:
|
|
|
150
218
|
|
|
151
219
|
- **Hooks block** what they must — `runHook`, or the real agent CLI via `runHarnessTest`.
|
|
152
220
|
- **Skills trigger** on the right prompts and stay quiet on the wrong ones — recall _and_ precision (`measureTriggerRate`).
|
|
221
|
+
- **Subagents finish right** — assert a subagent ended in the success (or error) outcome it promised, with a plain check, no LLM judge (`assertAgentOk` / `assertAgentErr`).
|
|
153
222
|
- **Behaviour is good** — score a skill's output, or A/B it on-vs-off for the real lift (`measure` / `runEval`, with significance testing).
|
|
154
223
|
- **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.
|
|
155
224
|
|
|
156
225
|
Almost every tier runs with **no model and no API key** — milliseconds, on every
|
|
157
|
-
commit; only the real-model
|
|
226
|
+
commit; only the real-model tier needs a model, on your own `claude` CLI.
|
|
158
227
|
**[How it works →](docs/harness-testing.md)**
|
|
159
228
|
|
|
160
|
-
##
|
|
229
|
+
## ③ Eval — does it actually help, or just cost more?
|
|
161
230
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
harness loaded exactly as it ships, and reports
|
|
231
|
+
**"65% fewer tokens." "3× faster." Says who?** A skill claims it, a plugin promises
|
|
232
|
+
it — stars and vibes, **zero measurement**. vigiles A/Bs the claim on real coding
|
|
233
|
+
tasks, the harness loaded exactly as it ships, and reports **three numbers**:
|
|
165
234
|
|
|
166
235
|
```typescript
|
|
167
236
|
import { measureArms } from "vigiles/testing";
|
|
@@ -178,20 +247,70 @@ const r = await measureArms({
|
|
|
178
247
|
- **The target** — whatever the skill claims to move (output tokens, latency, tool calls), verified on its own terms.
|
|
179
248
|
- **The blast radius** — correctness, a deterministic 1/0. A token win that breaks the code is **not a win**.
|
|
180
249
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
250
|
+
**Safe to repeat.** Each real-model run is sandboxed (ephemeral dir, egress blocked
|
|
251
|
+
or allow-listed), and `interceptTools` catches an irreversible external — a push, a
|
|
252
|
+
paid API — as an _attempt_, never running it. **[Safety, sandboxing & FAQ →](docs/safety.md)**
|
|
253
|
+
|
|
254
|
+
**The eval you can actually afford.** promptfoo / DeepEval hit a metered API and
|
|
255
|
+
bill **per token, every run**. vigiles answers most questions with **no model at
|
|
256
|
+
all**, and runs the rest on your own **Claude Pro/Max subscription — $0 extra**. So
|
|
257
|
+
you can measure on every change. **[Eval a skill →](docs/measuring-skills.md)** · **[Why it's affordable →](docs/eval-architecture.md)**
|
|
258
|
+
|
|
259
|
+
<!-- PARKED FOR LAUNCH — Guard / compiled hooks. Re-add this whole section (and the table row above) post-HN. See research/roadmap.md "Launch readiness".
|
|
260
|
+
|
|
261
|
+
## ④ Guard — a safety hook that can't silently fail open
|
|
262
|
+
|
|
263
|
+
**Your safety hook looks like it blocks — and doesn't.** A guard is your last stop
|
|
264
|
+
before something irreversible, but a hand-written one **fails open** without telling
|
|
265
|
+
you. _(Already write safety hooks? This is the power tool.)_ Write a pure typed
|
|
266
|
+
function instead; vigiles emits the exit code, the JSON, and an AST-backed matcher:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { defineHook, tool, deny, allow } from "vigiles/hook";
|
|
270
|
+
|
|
271
|
+
export default defineHook({
|
|
272
|
+
on: "PreToolUse",
|
|
273
|
+
match: tool("Bash"),
|
|
274
|
+
decide: (e) =>
|
|
275
|
+
e.command.runs("git push", { force: true })
|
|
276
|
+
? deny("no force-push to a protected branch")
|
|
277
|
+
: allow(),
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**The proof:** a widely-copied OSS safety hook blocks **2/7** of the disaster
|
|
282
|
+
battery. The compiled rewrite blocks **7/7** — measured, not asserted.
|
|
283
|
+
|
|
284
|
+
You never hand-write the exit code or JSON field (the usual false confidence), the
|
|
285
|
+
matcher is **AST-backed** (it catches the `cd x && git push -f` a glob misses), and
|
|
286
|
+
the artifact is **stamped** so a later hand-edit is refused.
|
|
287
|
+
|
|
288
|
+
_Scope: this fixes a hook's logic, not the harness's delivery — a subagent's tool
|
|
289
|
+
calls still bypass any PreToolUse hook
|
|
290
|
+
([#34692](https://github.com/anthropics/claude-code/issues/34692)), so it's a strong
|
|
291
|
+
default, not an unbypassable wall._
|
|
292
|
+
**[Compiled hooks — bug classes + trade-offs →](docs/compiled-hooks.md)**
|
|
293
|
+
|
|
294
|
+
-->
|
|
295
|
+
|
|
296
|
+
## FAQ
|
|
297
|
+
|
|
298
|
+
- **Isn't this just a markdown linter?** No — it checks whether your instruction file is _true_ (every path/script/symbol/rule exists and is enabled), then tests and measures your harness. A style linter can't do any of that.
|
|
299
|
+
- **Do I have to write TypeScript?** No — your agent writes the spec (`init` adopts your CLAUDE.md into one). Prefer zero new files? Plain markdown lints too. The deeper compiler-grade guarantees are the gradual, opt-in part — like TS's `strict`.
|
|
300
|
+
- **Does it overwrite my files?** No. `init` adopts an existing CLAUDE.md _non-destructively_ — untouched until you `compile`, and `eject` reverses it.
|
|
301
|
+
- **Need an API key?** No for almost everything (free, every commit). Real-model evals run on your Claude Pro/Max subscription — $0 metered tokens.
|
|
302
|
+
- **Non-JS repo?** `npx vigiles lint` verifies your CLAUDE.md with no install (Ruff/Clippy/Pylint/… too).
|
|
184
303
|
|
|
185
|
-
**
|
|
186
|
-
SDK and bill **per token, every run**; vigiles answers most questions with **no
|
|
187
|
-
model at all** and runs the rest on **your Claude Pro/Max subscription — $0 extra**.
|
|
304
|
+
**[Full FAQ →](docs/faq.md)**
|
|
188
305
|
|
|
189
306
|
## More
|
|
190
307
|
|
|
191
|
-
- **[
|
|
192
|
-
- **[
|
|
193
|
-
- **[
|
|
194
|
-
- **[Docs index →](docs/README.md)** · **[
|
|
308
|
+
- **[CLI →](docs/cli.md)** — every command and the plugin · **[GitHub Action →](docs/github-action.md)** — run it in CI. The full **[lint rules matrix →](docs/verifying-instruction-files.md#the-validation-rules--the-full-matrix)** lives with the linting guide.
|
|
309
|
+
- **[Skills →](docs/skills.md)** — the skills `init` installs, and how the model-invocable ones trigger.
|
|
310
|
+
- **[Ship plugins? The plugin-author guide →](docs/for-plugin-authors.md)** — scan a draft for structural health, make your skills fire for users, then rank a whole marketplace (0–100, A–F, worst issues first) — **no key**.
|
|
311
|
+
- **[Docs index →](docs/README.md)** · **[API reference →](https://zernie.github.io/vigiles/)** · **[Related tools →](docs/related-tools.md)** (ast-grep, Dependency Cruiser, Ruler, rulesync).
|
|
312
|
+
- **[Stability →](STABILITY.md)** — 0.x: the CLI is stable; the library API is still evolving; experimental surfaces are marked.
|
|
313
|
+
- **Not for you if** you want a model/capability benchmark or runtime guardrails in the request path — vigiles is build-/CI-time.
|
|
195
314
|
- Companion to [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need).
|
|
196
315
|
|
|
197
316
|
## License
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type AgentRunner, type ModelOutputParser } from "./eval.js";
|
|
2
|
+
/** A reference the model proposes as machine-verifiable. */
|
|
3
|
+
export interface DraftedRef {
|
|
4
|
+
readonly kind: "enforce" | "file" | "cmd" | "dir";
|
|
5
|
+
readonly ref: string;
|
|
6
|
+
}
|
|
7
|
+
/** A drafted ref that failed verification — the value proof. */
|
|
8
|
+
export interface BrokenRef {
|
|
9
|
+
readonly kind: DraftedRef["kind"];
|
|
10
|
+
readonly ref: string;
|
|
11
|
+
readonly issue: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AdoptabilityResult {
|
|
14
|
+
/** Distinct verifiable references the model found (the surface a spec would protect). */
|
|
15
|
+
readonly total: number;
|
|
16
|
+
/** How many of those are broken in this repo right now. */
|
|
17
|
+
readonly broken: number;
|
|
18
|
+
readonly brokenRefs: readonly BrokenRef[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Deterministic verdict over drafted refs — the "disposes" half. Dedupes
|
|
22
|
+
* (kind+ref), routes each to the real cross-ref/filesystem check, and counts the
|
|
23
|
+
* broken. Pure: a hallucinated rule resolves to broken, never trusted as a pass.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyDraftedRefs(refs: readonly DraftedRef[], basePath: string): AdoptabilityResult;
|
|
26
|
+
/**
|
|
27
|
+
* Tolerant parse of the model's draft output into `DraftedRef[]`. The model is
|
|
28
|
+
* asked for a bare JSON array, but tolerate prose-wrapped / fenced output by
|
|
29
|
+
* extracting the outermost `[...]`. Drops any entry with an unknown kind or a
|
|
30
|
+
* non-string ref (the verifier is the guard, but a malformed shape is just noise).
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseDraftJson(text: string): DraftedRef[];
|
|
33
|
+
/** Options for the real model draft (the one v8-ignored seam). */
|
|
34
|
+
export interface DraftOptions {
|
|
35
|
+
readonly model?: string;
|
|
36
|
+
readonly cwd?: string;
|
|
37
|
+
readonly runner?: AgentRunner;
|
|
38
|
+
readonly parse?: ModelOutputParser;
|
|
39
|
+
}
|
|
40
|
+
/** Injectable drafter — the real one calls a model; tests pass a fake. */
|
|
41
|
+
export type Drafter = (content: string) => Promise<DraftedRef[]>;
|
|
42
|
+
export interface AdoptabilityTierOptions {
|
|
43
|
+
readonly instructionContent: string;
|
|
44
|
+
readonly basePath: string;
|
|
45
|
+
/** Injectable for tests; defaults to the real one-shot model draft. */
|
|
46
|
+
readonly draft?: Drafter;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run the preview: draft refs from the instruction file (model), then verify them
|
|
50
|
+
* (deterministic). The composition root of "LLM proposes, deterministic disposes".
|
|
51
|
+
*/
|
|
52
|
+
export declare function runAdoptabilityTier(opts: AdoptabilityTierOptions): Promise<AdoptabilityResult>;
|
|
53
|
+
/** Terminal section — the adoption invitation, not a graded ring. */
|
|
54
|
+
export declare function formatAdoptability(r: AdoptabilityResult, instructionFile: string): string;
|
|
55
|
+
//# sourceMappingURL=adoptability.d.ts.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyDraftedRefs = verifyDraftedRefs;
|
|
4
|
+
exports.parseDraftJson = parseDraftJson;
|
|
5
|
+
exports.runAdoptabilityTier = runAdoptabilityTier;
|
|
6
|
+
exports.formatAdoptability = formatAdoptability;
|
|
7
|
+
/**
|
|
8
|
+
* Adoptability preview — "what would vigiles catch in YOUR repo?"
|
|
9
|
+
*
|
|
10
|
+
* The audit's adoption front door for a NON-adopter: instead of grading the hygiene
|
|
11
|
+
* of an already-adopted spec, it shows the concrete bugs a spec WOULD catch today.
|
|
12
|
+
*
|
|
13
|
+
* Architecture (research/adoption-gateway-preview.md): **LLM proposes, deterministic
|
|
14
|
+
* disposes.** A model DRAFTS the verifiable references in an instruction file (high
|
|
15
|
+
* recall, incl. prose intent a regex can't see — `draftRefs`); the deterministic
|
|
16
|
+
* cross-reference engine VERIFIES each one (`verifyDraftedRefs`, reusing
|
|
17
|
+
* `checkLinterRule` + the compile validators). The model never gets to assert a
|
|
18
|
+
* pass — only the verifier does — so the "M broken right now" number is trustworthy
|
|
19
|
+
* even though the extraction was probabilistic.
|
|
20
|
+
*
|
|
21
|
+
* The verifier + parser + formatter are pure and model-free (fully unit-tested); the
|
|
22
|
+
* single real model call (`defaultDraft`) is the only v8-ignored seam, injected so
|
|
23
|
+
* the orchestration is testable without a model.
|
|
24
|
+
*/
|
|
25
|
+
const linters_js_1 = require("./core/linters.js");
|
|
26
|
+
const compile_js_1 = require("./core/compile.js");
|
|
27
|
+
const hash_js_1 = require("./core/hash.js");
|
|
28
|
+
const eval_js_1 = require("./eval.js");
|
|
29
|
+
/** Verify ONE drafted ref against the real repo; null = resolves, else the breakage. */
|
|
30
|
+
function verifyOne(r, basePath) {
|
|
31
|
+
switch (r.kind) {
|
|
32
|
+
case "enforce": {
|
|
33
|
+
const res = (0, linters_js_1.checkLinterRule)(r.ref, basePath);
|
|
34
|
+
if (!res.exists)
|
|
35
|
+
return {
|
|
36
|
+
...r,
|
|
37
|
+
issue: res.error ?? `linter rule "${r.ref}" does not exist`,
|
|
38
|
+
};
|
|
39
|
+
if (res.enabled === "disabled")
|
|
40
|
+
return { ...r, issue: `rule "${r.ref}" exists but is not enabled` };
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
case "file": {
|
|
44
|
+
const e = (0, compile_js_1.validateFileRef)(r.ref, basePath);
|
|
45
|
+
return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
|
|
46
|
+
}
|
|
47
|
+
case "cmd": {
|
|
48
|
+
const e = (0, compile_js_1.validateCommandRef)(r.ref, basePath);
|
|
49
|
+
return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
|
|
50
|
+
}
|
|
51
|
+
case "dir": {
|
|
52
|
+
const e = (0, compile_js_1.validateDirRef)(r.ref, basePath);
|
|
53
|
+
return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return (0, hash_js_1.assertNever)(r.kind);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Deterministic verdict over drafted refs — the "disposes" half. Dedupes
|
|
61
|
+
* (kind+ref), routes each to the real cross-ref/filesystem check, and counts the
|
|
62
|
+
* broken. Pure: a hallucinated rule resolves to broken, never trusted as a pass.
|
|
63
|
+
*/
|
|
64
|
+
function verifyDraftedRefs(refs, basePath) {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const unique = [];
|
|
67
|
+
for (const r of refs) {
|
|
68
|
+
const key = `${r.kind}:${r.ref}`;
|
|
69
|
+
if (seen.has(key))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(key);
|
|
72
|
+
unique.push(r);
|
|
73
|
+
}
|
|
74
|
+
const brokenRefs = unique
|
|
75
|
+
.map((r) => verifyOne(r, basePath))
|
|
76
|
+
.filter((b) => b !== null);
|
|
77
|
+
return { total: unique.length, broken: brokenRefs.length, brokenRefs };
|
|
78
|
+
}
|
|
79
|
+
const VALID_KINDS = new Set([
|
|
80
|
+
"enforce",
|
|
81
|
+
"file",
|
|
82
|
+
"cmd",
|
|
83
|
+
"dir",
|
|
84
|
+
]);
|
|
85
|
+
/**
|
|
86
|
+
* Tolerant parse of the model's draft output into `DraftedRef[]`. The model is
|
|
87
|
+
* asked for a bare JSON array, but tolerate prose-wrapped / fenced output by
|
|
88
|
+
* extracting the outermost `[...]`. Drops any entry with an unknown kind or a
|
|
89
|
+
* non-string ref (the verifier is the guard, but a malformed shape is just noise).
|
|
90
|
+
*/
|
|
91
|
+
function parseDraftJson(text) {
|
|
92
|
+
const raw = extractJsonArray(text);
|
|
93
|
+
if (raw === null)
|
|
94
|
+
return [];
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(parsed))
|
|
103
|
+
return [];
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const item of parsed) {
|
|
106
|
+
if (typeof item !== "object" || item === null)
|
|
107
|
+
continue;
|
|
108
|
+
const rec = item;
|
|
109
|
+
const kind = rec.kind;
|
|
110
|
+
const ref = rec.ref;
|
|
111
|
+
if (typeof kind === "string" &&
|
|
112
|
+
VALID_KINDS.has(kind) &&
|
|
113
|
+
typeof ref === "string" &&
|
|
114
|
+
ref.trim()) {
|
|
115
|
+
out.push({ kind: kind, ref: ref.trim() });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/** Pull the outermost `[...]` from a possibly prose/fence-wrapped string. */
|
|
121
|
+
function extractJsonArray(text) {
|
|
122
|
+
const start = text.indexOf("[");
|
|
123
|
+
const end = text.lastIndexOf("]");
|
|
124
|
+
if (start === -1 || end === -1 || end < start)
|
|
125
|
+
return null;
|
|
126
|
+
return text.slice(start, end + 1);
|
|
127
|
+
}
|
|
128
|
+
/** The drafting prompt — reuse the strengthen/adopt-spec mapping intent. */
|
|
129
|
+
function draftPrompt(content) {
|
|
130
|
+
return [
|
|
131
|
+
"You are evaluating whether a coding-agent instruction file's references can be",
|
|
132
|
+
"machine-verified. Read the instruction file and identify every reference to a",
|
|
133
|
+
"CONCRETE, VERIFIABLE artifact:",
|
|
134
|
+
'- a linter rule (kind "enforce", ref like "eslint/no-console" or',
|
|
135
|
+
' "@typescript-eslint/no-floating-promises") — INCLUDING prose intent you can',
|
|
136
|
+
' confidently map to a real rule (e.g. "always await promises" ->',
|
|
137
|
+
' "@typescript-eslint/no-floating-promises", "no console.log" -> "eslint/no-console").',
|
|
138
|
+
'- a file path (kind "file", ref like "src/index.ts").',
|
|
139
|
+
'- an npm script (kind "cmd", ref like "npm run build" or "npm test").',
|
|
140
|
+
'- a directory (kind "dir", ref like "src/components").',
|
|
141
|
+
"",
|
|
142
|
+
'Output ONLY a JSON array of {"kind","ref"} objects — no markdown, no prose. If',
|
|
143
|
+
"none, output []. Do not invent references that aren't grounded in the text.",
|
|
144
|
+
"",
|
|
145
|
+
"Instruction file:",
|
|
146
|
+
"---",
|
|
147
|
+
content,
|
|
148
|
+
"---",
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
/* v8 ignore start — the single real model call; the orchestration is tested with a fake draft. */
|
|
152
|
+
/** The "proposes" half: one model call drafting the verifiable refs from prose. */
|
|
153
|
+
async function defaultDraft(content, opts = {}) {
|
|
154
|
+
const runner = opts.runner ?? eval_js_1.spawnAgent;
|
|
155
|
+
const parse = opts.parse ?? eval_js_1.parseClaudeRun;
|
|
156
|
+
const out = await runner({
|
|
157
|
+
task: draftPrompt(content),
|
|
158
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
159
|
+
model: opts.model ?? "sonnet",
|
|
160
|
+
tools: [], // the content is inline — no file tools needed (deterministic-ish)
|
|
161
|
+
hasSettings: false,
|
|
162
|
+
pluginDir: undefined,
|
|
163
|
+
timeoutMs: 120000,
|
|
164
|
+
env: process.env,
|
|
165
|
+
});
|
|
166
|
+
return parseDraftJson(parse(out).output);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Run the preview: draft refs from the instruction file (model), then verify them
|
|
170
|
+
* (deterministic). The composition root of "LLM proposes, deterministic disposes".
|
|
171
|
+
*/
|
|
172
|
+
async function runAdoptabilityTier(opts) {
|
|
173
|
+
const draft = opts.draft ?? ((c) => defaultDraft(c));
|
|
174
|
+
const refs = await draft(opts.instructionContent);
|
|
175
|
+
return verifyDraftedRefs(refs, opts.basePath);
|
|
176
|
+
}
|
|
177
|
+
/** Terminal section — the adoption invitation, not a graded ring. */
|
|
178
|
+
function formatAdoptability(r, instructionFile) {
|
|
179
|
+
const lines = ["Adoptability — what vigiles would lock in"];
|
|
180
|
+
if (r.total === 0) {
|
|
181
|
+
lines.push(` no machine-verifiable references found in ${instructionFile}.`);
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
184
|
+
lines.push(` vigiles drafted a spec from ${instructionFile}: ${String(r.total)} verifiable reference(s) found`);
|
|
185
|
+
if (r.broken === 0) {
|
|
186
|
+
lines.push(" ✓ all resolve right now — adopt a spec to keep it that way.");
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
lines.push(` ${String(r.broken)} broken right now:`);
|
|
190
|
+
for (const b of r.brokenRefs) {
|
|
191
|
+
lines.push(` ✗ ${b.issue}`);
|
|
192
|
+
}
|
|
193
|
+
lines.push(" → run `vigiles init` to adopt the spec and catch these at edit time.");
|
|
194
|
+
return lines.join("\n");
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=adoptability.js.map
|