vigiles 2.0.0 → 2.2.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 +182 -134
- package/dist/action-gate.d.ts +28 -0
- package/dist/action-gate.js +73 -0
- package/dist/cli.js +705 -230
- package/dist/community-skills.d.ts +22 -0
- package/dist/community-skills.js +86 -0
- package/dist/compile-generator.d.ts +48 -0
- package/dist/compile-generator.js +322 -0
- package/dist/compile.d.ts +4 -0
- package/dist/compile.js +239 -45
- package/dist/coverage.d.ts +56 -0
- package/dist/coverage.js +178 -0
- package/dist/doc-refs.d.ts +60 -0
- package/dist/doc-refs.js +246 -0
- package/dist/eval.d.ts +62 -0
- package/dist/eval.js +174 -0
- package/dist/evolve.js +60 -125
- package/dist/frontmatter.d.ts +75 -0
- package/dist/frontmatter.js +263 -0
- package/dist/generate-schema.d.ts +51 -0
- package/dist/generate-schema.js +125 -0
- package/dist/generate-types.js +39 -1
- package/dist/harness-test.d.ts +38 -0
- package/dist/harness-test.js +129 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.js +16 -0
- package/dist/inline.d.ts +22 -4
- package/dist/inline.js +60 -13
- package/dist/integrity.d.ts +29 -0
- package/dist/integrity.js +44 -0
- package/dist/linters.d.ts +5 -3
- package/dist/linters.js +144 -4
- package/dist/mock-model.d.ts +31 -0
- package/dist/mock-model.js +189 -0
- package/dist/orphans.d.ts +52 -0
- package/dist/orphans.js +124 -0
- package/dist/proofs.js +22 -16
- package/dist/refs.d.ts +44 -0
- package/dist/refs.js +144 -0
- package/dist/session.d.ts +97 -0
- package/dist/session.js +306 -0
- package/dist/sidecar.d.ts +35 -0
- package/dist/sidecar.js +102 -0
- package/dist/skill-driver.d.ts +77 -0
- package/dist/skill-driver.js +76 -0
- package/dist/skill-runtime.d.ts +101 -0
- package/dist/skill-runtime.js +289 -0
- package/dist/skill-test.d.ts +47 -0
- package/dist/skill-test.js +77 -0
- package/dist/spec.d.ts +119 -13
- package/dist/spec.js +51 -4
- package/dist/symbols.d.ts +30 -0
- package/dist/symbols.js +142 -0
- package/dist/test-utils.d.ts +8 -0
- package/dist/test-utils.js +41 -0
- package/dist/types.d.ts +34 -8
- package/dist/types.js +19 -0
- package/dist/validate.js +5 -3
- package/package.json +26 -5
- package/.claude/settings.json +0 -46
- package/.claude/settings.local.json +0 -8
- package/.github/workflows/ci.yml +0 -81
- package/.prettierignore +0 -1
- package/.vigiles/generated.d.ts +0 -205
- package/CLAUDE.md +0 -95
- package/CLAUDE.md.spec.ts +0 -142
- package/CONTRIBUTING.md +0 -121
- package/dist/action.d.ts.map +0 -1
- package/dist/action.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cli.test.d.ts +0 -2
- package/dist/cli.test.d.ts.map +0 -1
- package/dist/cli.test.js +0 -650
- package/dist/cli.test.js.map +0 -1
- package/dist/compile.d.ts.map +0 -1
- package/dist/compile.js.map +0 -1
- package/dist/evolve.d.ts.map +0 -1
- package/dist/evolve.js.map +0 -1
- package/dist/freshness.d.ts +0 -67
- package/dist/freshness.d.ts.map +0 -1
- package/dist/freshness.js +0 -244
- package/dist/freshness.js.map +0 -1
- package/dist/freshness.test.d.ts +0 -2
- package/dist/freshness.test.d.ts.map +0 -1
- package/dist/freshness.test.js +0 -356
- package/dist/freshness.test.js.map +0 -1
- package/dist/generate-types.d.ts.map +0 -1
- package/dist/generate-types.js.map +0 -1
- package/dist/inline.d.ts.map +0 -1
- package/dist/inline.js.map +0 -1
- package/dist/inline.test.d.ts +0 -5
- package/dist/inline.test.d.ts.map +0 -1
- package/dist/inline.test.js +0 -152
- package/dist/inline.test.js.map +0 -1
- package/dist/linters.d.ts.map +0 -1
- package/dist/linters.js.map +0 -1
- package/dist/proofs.d.ts.map +0 -1
- package/dist/proofs.js.map +0 -1
- package/dist/proofs.test.d.ts +0 -9
- package/dist/proofs.test.d.ts.map +0 -1
- package/dist/proofs.test.js +0 -952
- package/dist/proofs.test.js.map +0 -1
- package/dist/spec.d.ts.map +0 -1
- package/dist/spec.js.map +0 -1
- package/dist/spec.test.d.ts +0 -2
- package/dist/spec.test.d.ts.map +0 -1
- package/dist/spec.test.js +0 -1222
- package/dist/spec.test.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/validate.test.d.ts +0 -2
- package/dist/validate.test.d.ts.map +0 -1
- package/dist/validate.test.js +0 -531
- package/dist/validate.test.js.map +0 -1
- package/docs/agent-setup.md +0 -85
- package/docs/agent-workflows.md +0 -103
- package/docs/comparison.md +0 -71
- package/docs/freshness.md +0 -124
- package/docs/inline-mode.md +0 -119
- package/docs/linter-support.md +0 -166
- package/docs/spec-format.md +0 -194
- package/eslint.config.mjs +0 -79
- package/examples/CLAUDE.md +0 -54
- package/examples/CLAUDE.md.spec.ts +0 -65
- package/examples/SKILL.md +0 -50
- package/examples/SKILL.md.spec.ts +0 -57
- package/fixtures/example-project/CLAUDE.md +0 -11
- package/fixtures/example-project/package.json +0 -9
- package/fixtures/example-project/src/index.ts +0 -3
- package/fixtures/example-project/src/utils.test.ts +0 -2
- package/fixtures/example-project/src/utils.ts +0 -3
- package/logo.png +0 -0
- package/research/adoption-strategy.md +0 -111
- package/research/agent-integration.md +0 -145
- package/research/ai-code-quality.md +0 -197
- package/research/code-search-for-agents.md +0 -313
- package/research/competitive-landscape.md +0 -163
- package/research/doc-freshness.md +0 -516
- package/research/executable-specs.md +0 -368
- package/research/feature-ideas.md +0 -464
- package/research/formal-proofs-for-agents.md +0 -338
- package/research/fp-for-agent-harness.md +0 -150
- package/research/fp-for-deterministic-ai.md +0 -131
- package/research/self-evolving-specs.md +0 -298
- package/schemas/claude-md-strict.yml +0 -18
- package/schemas/claude-md.yml +0 -6
- package/schemas/skill-strict.yml +0 -12
- package/schemas/skill.yml +0 -5
- package/skills/audit-feedback-loop/SKILL.md +0 -76
- package/skills/edit-spec/SKILL.md +0 -131
- package/skills/enforce-rules-format/SKILL.md +0 -71
- package/skills/generate-logo/SKILL.md +0 -102
- package/skills/generate-rule/SKILL.md +0 -90
- package/skills/linter-docs/clippy.md +0 -241
- package/skills/linter-docs/eslint.md +0 -384
- package/skills/linter-docs/pylint.md +0 -288
- package/skills/linter-docs/rubocop.md +0 -277
- package/skills/linter-docs/ruff.md +0 -187
- package/skills/linter-docs/stylelint.md +0 -247
- package/skills/migrate-to-spec/SKILL.md +0 -124
- package/skills/pr-to-lint-rule/SKILL.md +0 -97
- package/skills/strengthen/SKILL.md +0 -168
- package/src/action.ts +0 -214
- package/src/cli.test.ts +0 -914
- package/src/cli.ts +0 -1631
- package/src/compile.ts +0 -691
- package/src/evolve.ts +0 -781
- package/src/freshness.test.ts +0 -449
- package/src/freshness.ts +0 -299
- package/src/generate-types.ts +0 -448
- package/src/inline.test.ts +0 -206
- package/src/inline.ts +0 -164
- package/src/linters.ts +0 -739
- package/src/proofs.test.ts +0 -1314
- package/src/proofs.ts +0 -849
- package/src/spec.test.ts +0 -1471
- package/src/spec.ts +0 -427
- package/src/types.ts +0 -117
- package/src/validate.test.ts +0 -701
- package/src/validate.ts +0 -381
- package/tsconfig.json +0 -23
package/README.md
CHANGED
|
@@ -16,27 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Your CLAUDE.md lies to your agent. Here's the fix.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
**Markdown can't be validated. TypeScript can.**
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npx vigiles init
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
vigiles compiles typed TypeScript specs to instruction files (CLAUDE.md, AGENTS.md). Every linter reference is verified against your actual config — not just that it exists, but that it's enabled. Every file path is checked against the filesystem. Every command is validated against package.json. If something is stale, broken, or disabled — you find out at compile time, not when the agent silently ignores your instructions.
|
|
30
|
-
|
|
31
|
-
After setup, the agent edits the spec instead of the markdown. Hooks auto-compile. Types catch typos in the editor. CI catches drift. It's self-maintaining.
|
|
32
|
-
|
|
33
|
-
Not using vigiles with AI agents is like not using ESLint with JavaScript. You can do it. You can't afford to.
|
|
34
|
-
|
|
35
|
-
Companion repo for [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need).
|
|
36
|
-
|
|
37
|
-
## The Problem
|
|
38
|
-
|
|
39
|
-
Hand-written CLAUDE.md files rot silently. Here's what they actually look like:
|
|
21
|
+
Hand-written CLAUDE.md files rot silently. Here's what a typical one looks like:
|
|
40
22
|
|
|
41
23
|
```markdown
|
|
42
24
|
## Code Style
|
|
@@ -58,43 +40,45 @@ Reads fine. Four things are wrong:
|
|
|
58
40
|
3. `npm run typecheck` — script removed from package.json
|
|
59
41
|
4. Service/test pairing — no automated check, just a hope
|
|
60
42
|
|
|
61
|
-
The agent reads this, trusts it, and writes code based on stale claims nobody verified.
|
|
43
|
+
The agent reads this, trusts it, and writes code based on stale claims nobody verified. vigiles **verifies the references in your instruction files** — that each linter rule exists and is enabled, that every file path and script is real, and that referenced **code symbols** (functions, classes, constants) actually exist in the files that define them — and meets you at whatever commitment level you want.
|
|
62
44
|
|
|
63
|
-
|
|
45
|
+
Three levels. Each is independently useful; adopt as far up as you like.
|
|
64
46
|
|
|
65
|
-
###
|
|
47
|
+
### Level 0 — inline comments (30 seconds, no new files)
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
| ----------------------------------- | ---------------------------- | -------------------------------------------------------------- |
|
|
69
|
-
| **Instructions** | Hand-written CLAUDE.md | Compiled from `.spec.ts` (build artifact) |
|
|
70
|
-
| **Linter rule references** | Trust-based (nobody checks) | Verified at compile time against real config |
|
|
71
|
-
| **File paths** | Rot silently when renamed | `file()` references checked against filesystem |
|
|
72
|
-
| **Commands** | Stale scripts go unnoticed | `cmd()` references checked against package.json |
|
|
73
|
-
| **Direct edits to CLAUDE.md** | Anyone can, nobody knows | PreToolUse hook blocks edits, redirects to spec |
|
|
74
|
-
| **Linter config changes** | CLAUDE.md drifts out of sync | PostToolUse hook auto-regenerates types |
|
|
75
|
-
| **guidance → enforce upgrades** | Manual guesswork | `/strengthen` reads per-linter docs, suggests upgrades |
|
|
76
|
-
| **New lint rules from PR feedback** | Copy-paste from review | `/pr-to-lint-rule` generates rule + tests + spec entry |
|
|
77
|
-
| **CI** | Nothing to verify | `vigiles audit` catches hash drift, disabled rules, stale refs |
|
|
49
|
+
Add a comment to your existing CLAUDE.md and audit it:
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
51
|
+
```md
|
|
52
|
+
<!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->
|
|
53
|
+
```
|
|
81
54
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| **Linter rule references** | Trust-based | Verified at compile time |
|
|
86
|
-
| **File paths / commands** | Rot silently | Checked at compile time |
|
|
87
|
-
| **Direct edits to AGENTS.md** | Undetected | CI catches hash mismatch |
|
|
88
|
-
| **Hooks / auto-compile** | Not available (no plugin system) | Not available — run `vigiles compile` manually or in CI |
|
|
89
|
-
| **CI** | Nothing to verify | Same `vigiles audit` pipeline as Claude |
|
|
55
|
+
```bash
|
|
56
|
+
npx vigiles audit CLAUDE.md
|
|
57
|
+
```
|
|
90
58
|
|
|
91
|
-
|
|
59
|
+
Each rule is checked against your real linter config — typos get closest-match suggestions, disabled rules are flagged. Zero install commitment, zero new files.
|
|
92
60
|
|
|
93
|
-
|
|
61
|
+
### Level 1 — YAML frontmatter (editor autocomplete, still no TypeScript)
|
|
94
62
|
|
|
95
|
-
|
|
63
|
+
Promote your rules into a `vigiles:` block at the top of the file:
|
|
96
64
|
|
|
97
|
-
|
|
65
|
+
```yaml
|
|
66
|
+
---
|
|
67
|
+
# yaml-language-server: $schema=./.vigiles/schema.json
|
|
68
|
+
vigiles:
|
|
69
|
+
enforce:
|
|
70
|
+
- rule: "@typescript-eslint/no-explicit-any"
|
|
71
|
+
why: "Use unknown and narrow with type guards."
|
|
72
|
+
- rule: eslint/no-console
|
|
73
|
+
why: "Route output through logger.ts"
|
|
74
|
+
---
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`npx vigiles generate-schema` emits a JSON Schema from your project's _actual_ enabled rules, so your editor's built-in YAML language server (VS Code, JetBrains, neovim) autocompletes rule names and red-squiggles typos — at edit time, with no TypeScript in the project. `vigiles audit` enforces the same rules in CI. [Markdown mode →](docs/markdown-mode.md)
|
|
78
|
+
|
|
79
|
+
### Level 2 — typed spec (compiler-grade guarantees)
|
|
80
|
+
|
|
81
|
+
When you want the strongest guarantees, compile a typed spec. Every linter rule reference is verified against your real config, every file path against the filesystem, every npm script against package.json. Stale references become compile errors — caught at edit time, not when the agent silently ignores you.
|
|
98
82
|
|
|
99
83
|
```typescript
|
|
100
84
|
// CLAUDE.md.spec.ts
|
|
@@ -132,53 +116,54 @@ $ npx vigiles compile
|
|
|
132
116
|
~180 tokens
|
|
133
117
|
```
|
|
134
118
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
## Quick Start
|
|
119
|
+
At this level the spec is the source of truth and CLAUDE.md is a build artifact. The agent edits the spec — hooks auto-compile, types catch typos in the editor, CI catches drift.
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
npx vigiles init
|
|
141
|
-
```
|
|
121
|
+
Companion repo for [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need).
|
|
142
122
|
|
|
143
|
-
|
|
123
|
+
## What Changes With vigiles
|
|
144
124
|
|
|
145
|
-
|
|
146
|
-
npx vigiles init guidance() rules, zero config
|
|
147
|
-
│
|
|
148
|
-
▼
|
|
149
|
-
agent edits spec ◄────────── hooks auto-compile (self-maintaining)
|
|
150
|
-
│
|
|
151
|
-
▼
|
|
152
|
-
npx vigiles audit full verification: hashes + linters + coverage
|
|
153
|
-
│
|
|
154
|
-
▼
|
|
155
|
-
CI catches drift stale refs, disabled rules, typos, duplicates
|
|
156
|
-
```
|
|
125
|
+
### Claude Code
|
|
157
126
|
|
|
158
|
-
|
|
127
|
+
| | Without vigiles | With vigiles |
|
|
128
|
+
| ----------------------------------- | ---------------------------- | -------------------------------------------------------------- |
|
|
129
|
+
| **Instructions** | Hand-written CLAUDE.md | Compiled from `.spec.ts` (build artifact) |
|
|
130
|
+
| **Linter rule references** | Trust-based (nobody checks) | Verified at compile time against real config |
|
|
131
|
+
| **File paths** | Rot silently when renamed | `file()` references checked against filesystem |
|
|
132
|
+
| **Commands** | Stale scripts go unnoticed | `cmd()` references checked against package.json |
|
|
133
|
+
| **Direct edits to CLAUDE.md** | Anyone can, nobody knows | PreToolUse hook blocks edits, redirects to spec |
|
|
134
|
+
| **Spec edits** | N/A | PostToolUse hook auto-compiles to markdown |
|
|
135
|
+
| **Linter config changes** | CLAUDE.md drifts out of sync | PostToolUse hook auto-regenerates types |
|
|
136
|
+
| **guidance → enforce upgrades** | Manual guesswork | `/strengthen` reads per-linter docs, suggests upgrades |
|
|
137
|
+
| **New lint rules from PR feedback** | Copy-paste from review | `/pr-to-lint-rule` generates rule + tests + spec entry |
|
|
138
|
+
| **CI** | Nothing to verify | `vigiles audit` catches hand-edits, disabled rules, stale refs |
|
|
159
139
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
- Agent edits `eslint.config.ts` → plugin auto-regenerates types
|
|
163
|
-
- CI runs `vigiles audit` → catches stale specs, disabled rules, missing files
|
|
140
|
+
<details>
|
|
141
|
+
<summary><b>Codex</b> (same compile-time checks, no hooks)</summary>
|
|
164
142
|
|
|
165
|
-
|
|
143
|
+
| | Without vigiles | With vigiles |
|
|
144
|
+
| ----------------------------- | -------------------------------- | ------------------------------------------------------- |
|
|
145
|
+
| **Instructions** | Hand-written AGENTS.md | Compiled from `.spec.ts` |
|
|
146
|
+
| **Linter rule references** | Trust-based | Verified at compile time |
|
|
147
|
+
| **File paths / commands** | Rot silently | Checked at compile time |
|
|
148
|
+
| **Direct edits to AGENTS.md** | Undetected | CI catches hash mismatch |
|
|
149
|
+
| **Hooks / auto-compile** | Not available (no plugin system) | Not available — run `vigiles compile` manually or in CI |
|
|
150
|
+
| **CI** | Nothing to verify | Same `vigiles audit` pipeline as Claude |
|
|
166
151
|
|
|
167
|
-
|
|
152
|
+
</details>
|
|
168
153
|
|
|
169
|
-
|
|
170
|
-
<!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->
|
|
171
|
-
```
|
|
154
|
+
Everything vigiles compiles and audits is **deterministic** — same input, same output, no LLM in the loop. The non-deterministic parts (authoring specs, suggesting upgrades, writing custom rules) are agent skills that run outside the compilation pipeline. [Determinism breakdown and flow diagram →](docs/comparison.md)
|
|
172
155
|
|
|
173
|
-
|
|
156
|
+
## Quick Start
|
|
174
157
|
|
|
175
|
-
|
|
158
|
+
The fastest path is markdown mode — add a marker to your existing CLAUDE.md and audit it, no install or new files (see [Level 0 / Level 1](#level-0--inline-comments-30-seconds-no-new-files) above and [docs/markdown-mode.md](docs/markdown-mode.md)). When you want compiler-grade guarantees, scaffold a typed spec:
|
|
176
159
|
|
|
177
|
-
|
|
160
|
+
```bash
|
|
161
|
+
npx vigiles init
|
|
162
|
+
```
|
|
178
163
|
|
|
179
|
-
|
|
164
|
+
The wizard auto-detects your project, creates a spec, scans your linters, compiles to markdown, adds a CI step, and installs Claude Code hooks. After install: the agent edits the spec (hooks block direct CLAUDE.md edits), the spec auto-compiles on save, and `vigiles audit` catches drift in CI.
|
|
180
165
|
|
|
181
|
-
|
|
166
|
+
Start with `guidance()` rules (zero config). When you're ready, run `/strengthen` to find rules that can be upgraded to compile-verified `enforce()`. Already have a hand-written CLAUDE.md? The wizard detects it and offers migration.
|
|
182
167
|
|
|
183
168
|
| Flag | Effect |
|
|
184
169
|
| -------------------- | ----------------------------------------------------- |
|
|
@@ -188,17 +173,19 @@ Running `vigiles audit CLAUDE.md` verifies each inline rule against your real li
|
|
|
188
173
|
|
|
189
174
|
Works the same for humans and agents — fully non-interactive. [Agent setup guide →](docs/agent-setup.md) | [Agent workflows →](docs/agent-workflows.md)
|
|
190
175
|
|
|
191
|
-
##
|
|
176
|
+
## Three Rule Types
|
|
192
177
|
|
|
193
178
|
**`enforce()`** — delegated to a linter. vigiles verifies the rule exists in the catalog AND is enabled in your project config. A disabled rule is a compile error.
|
|
194
179
|
|
|
180
|
+
<!-- vigiles:ignore -->
|
|
181
|
+
|
|
195
182
|
```typescript
|
|
196
183
|
"no-any": enforce("@typescript-eslint/no-explicit-any", "Use unknown and narrow."),
|
|
197
184
|
"no-print": enforce("ruff/T201", "Use logging module."),
|
|
198
185
|
"no-unwrap": enforce("clippy/unwrap_used", "Use expect() with context."),
|
|
199
186
|
```
|
|
200
187
|
|
|
201
|
-
Supports ESLint, Stylelint, Ruff, Clippy, Pylint, and
|
|
188
|
+
Supports ESLint, Stylelint, Ruff, Clippy, Pylint, RuboCop, and Cedar policies. [Full linter support details →](docs/linter-support.md)
|
|
202
189
|
|
|
203
190
|
**`guidance()`** — prose advice. No mechanical enforcement, but not untracked: guidance rules participate in the monotonicity proof system. Once a rule exists, it can be strengthened ( `guidance` → `enforce` ) but never weakened or removed without an explicit allowlist. This prevents silent erosion of conventions over time.
|
|
204
191
|
|
|
@@ -206,21 +193,37 @@ Supports ESLint, Stylelint, Ruff, Clippy, Pylint, and RuboCop. [Full linter supp
|
|
|
206
193
|
"research-first": guidance("Google unfamiliar APIs first."),
|
|
207
194
|
```
|
|
208
195
|
|
|
196
|
+
**`guard()`** — reactive: runs a command when watched files change. One declaration emits hooks for every supported system (Claude Code PostToolUse, husky pre-commit, etc.). Eliminates copy-pasting the same trigger across `.claude/settings.json`, `.husky/`, and CI configs.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
"recompile-specs": guard(
|
|
200
|
+
{ watch: "*.spec.ts", run: "npx vigiles compile" },
|
|
201
|
+
"Recompile instruction files when any spec changes.",
|
|
202
|
+
),
|
|
203
|
+
"regen-types": guard(
|
|
204
|
+
{ watch: ["eslint.config.*", "package.json"], run: "npx vigiles generate-types" },
|
|
205
|
+
"Regenerate types when linter config or deps change.",
|
|
206
|
+
),
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Same monotonicity guarantees as `enforce()` — guards can't be silently removed.
|
|
210
|
+
|
|
209
211
|
## Verified References
|
|
210
212
|
|
|
211
|
-
`file()`, `cmd()`, and `ref()` catch stale references at compile time:
|
|
213
|
+
`file()`, `cmd()`, `symbol()`, and `ref()` catch stale references at compile time:
|
|
212
214
|
|
|
213
215
|
```typescript
|
|
214
|
-
import { claude, file, cmd, ref, instructions } from "vigiles/spec";
|
|
216
|
+
import { claude, file, cmd, symbol, ref, instructions } from "vigiles/spec";
|
|
215
217
|
|
|
216
218
|
export default claude({
|
|
217
219
|
sections: {
|
|
218
220
|
architecture: instructions`
|
|
219
221
|
Core engine in ${file("src/compile.ts")}.
|
|
222
|
+
Compile specs with ${symbol("src/compile.ts", "compileClaude")}.
|
|
220
223
|
Run ${cmd("npm test")} to verify.
|
|
221
|
-
See ${ref("skills/
|
|
224
|
+
See ${ref("skills/strengthen/SKILL.md")} for the strengthen skill.
|
|
222
225
|
`,
|
|
223
|
-
// If any path is stale → compile error
|
|
226
|
+
// If any path / script / symbol is stale → compile error
|
|
224
227
|
},
|
|
225
228
|
// ...
|
|
226
229
|
});
|
|
@@ -228,6 +231,12 @@ export default claude({
|
|
|
228
231
|
|
|
229
232
|
Skill specs use the same helpers for verified references inside instructions. [Full spec format →](docs/spec-format.md)
|
|
230
233
|
|
|
234
|
+
### Symbol references (cross-language)
|
|
235
|
+
|
|
236
|
+
`symbol("file", "name")` (and the markdown mark `` `vigiles:symbol file#name` ``) verify that the named file actually **defines** the symbol — a function, class, method, or constant — parsed with [ast-grep](https://ast-grep.github.io) across **JS/TS, Python, Ruby, Rust, and CSS**. Rename the function and `audit` fails; no project-wide index, no autoloader guessing — it parses the one named file.
|
|
237
|
+
|
|
238
|
+
In markdown mode the `refs-hook` (PostToolUse) **forces the mark**: it blocks an edit that leaves a code reference bare, telling the agent to write `` `vigiles:symbol path#name` `` or opt out with `<!-- vigiles:ignore -->`. The harness makes the agent mark its references at write time, with full context; `audit` re-verifies them. [Symbol verification →](research/symbol-verification.md)
|
|
239
|
+
|
|
231
240
|
## Type-Safe Rule References
|
|
232
241
|
|
|
233
242
|
`vigiles generate-types` scans your linter configs and emits `.vigiles/generated.d.ts`. With this file, `enforce("eslint/no-consolee")` is a red squiggle in your editor — a typo caught at authoring time, not a runtime surprise. Without it, everything falls back to broad types and still works.
|
|
@@ -240,14 +249,19 @@ $ npx vigiles generate-types
|
|
|
240
249
|
|
|
241
250
|
Commit the file to git. CI can verify it's fresh: `npx vigiles generate-types --check`. [How it works →](docs/linter-support.md#generate-types)
|
|
242
251
|
|
|
252
|
+
For markdown frontmatter (Level 1), `vigiles generate-schema` gives the same authoring-time feedback without TypeScript: it emits a JSON Schema from your enabled rules, and your editor's YAML language server autocompletes rule names and squiggles typos. CI freshness check: `npx vigiles generate-schema --check`.
|
|
253
|
+
|
|
243
254
|
## CLI
|
|
244
255
|
|
|
245
256
|
```bash
|
|
246
257
|
npx vigiles init [--target=X.md] # Scaffold a spec (runs full setup wizard by default)
|
|
247
258
|
npx vigiles compile [files...] # Compile .spec.ts → .md
|
|
248
|
-
npx vigiles audit [files...] # Verify hashes +
|
|
249
|
-
npx vigiles
|
|
259
|
+
npx vigiles audit [files...] # Verify hashes + inline/frontmatter/spec rules + symbols + coverage
|
|
260
|
+
npx vigiles refs <file.md> # Check the symbol references in an instruction file
|
|
261
|
+
npx vigiles generate-types # Emit .d.ts from project state (for spec mode)
|
|
250
262
|
npx vigiles generate-types --check # Verify .d.ts is up to date
|
|
263
|
+
npx vigiles generate-schema # Emit JSON Schema for vigiles: frontmatter (Level 1)
|
|
264
|
+
npx vigiles generate-schema --check # Verify schema.json is up to date
|
|
251
265
|
```
|
|
252
266
|
|
|
253
267
|
## GitHub Action
|
|
@@ -280,17 +294,14 @@ The plugin provides two hooks:
|
|
|
280
294
|
|
|
281
295
|
## Validation
|
|
282
296
|
|
|
283
|
-
`vigiles audit` validates instruction files with
|
|
297
|
+
`vigiles audit` validates instruction files with four rules:
|
|
284
298
|
|
|
285
|
-
| Rule
|
|
286
|
-
|
|
|
287
|
-
| `require-spec`
|
|
288
|
-
| `require-skill-spec` | `"warn"` | Every SKILL.md has a `.spec.ts`
|
|
289
|
-
| `
|
|
290
|
-
|
|
291
|
-
```bash
|
|
292
|
-
npx vigiles audit # checks specs, hashes, freshness, coverage, duplicates
|
|
293
|
-
```
|
|
299
|
+
| Rule | Default | What it checks |
|
|
300
|
+
| -------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- |
|
|
301
|
+
| [`require-spec`](docs/rules/require-spec.md) | `"warn"` | Every CLAUDE.md/AGENTS.md has a spec, inline rule, or `vigiles:` frontmatter |
|
|
302
|
+
| [`require-skill-spec`](docs/rules/require-skill-spec.md) | `"warn"` | Every SKILL.md has a `.spec.ts` |
|
|
303
|
+
| [`integrity`](docs/rules/integrity.md) | `"warn"` | Compiled markdown wasn't hand-edited (SHA-256 check) |
|
|
304
|
+
| [`coverage`](docs/rules/coverage.md) | `false` | Spec covers enough of the project surface |
|
|
294
305
|
|
|
295
306
|
Configure in `.vigilesrc.json`:
|
|
296
307
|
|
|
@@ -298,41 +309,13 @@ Configure in `.vigilesrc.json`:
|
|
|
298
309
|
{
|
|
299
310
|
"rules": {
|
|
300
311
|
"require-spec": "error",
|
|
301
|
-
"
|
|
312
|
+
"integrity": "error",
|
|
313
|
+
"coverage": ["warn", { "scripts": 50, "linterRules": 5 }]
|
|
302
314
|
}
|
|
303
315
|
}
|
|
304
316
|
```
|
|
305
317
|
|
|
306
|
-
Disable per-file with
|
|
307
|
-
|
|
308
|
-
```markdown
|
|
309
|
-
<!-- vigiles-disable require-spec -->
|
|
310
|
-
|
|
311
|
-
# CLAUDE.md
|
|
312
|
-
|
|
313
|
-
...
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Freshness
|
|
317
|
-
|
|
318
|
-
The `freshness` rule detects when compiled markdown has drifted from project state — disabled linter rules, deleted files, changed configs. Three detection modes:
|
|
319
|
-
|
|
320
|
-
| Mode | What it does | Cost |
|
|
321
|
-
| -------------------- | ----------------------------------------------------------------------- | ------ |
|
|
322
|
-
| `"strict"` (default) | Recompiles in memory, diffs output | 2-5s |
|
|
323
|
-
| `"input-hash"` | Checks fingerprint of tracked inputs (spec, linter configs, lock files) | <100ms |
|
|
324
|
-
| `"output-hash"` | Only detects hand-edits to compiled markdown | <1ms |
|
|
325
|
-
|
|
326
|
-
Strict mode has zero false positives and zero false negatives. Input-hash mode is faster but can false-positive on config whitespace changes. Set the mode in `.vigilesrc.json`:
|
|
327
|
-
|
|
328
|
-
```json
|
|
329
|
-
{
|
|
330
|
-
"freshnessMode": "input-hash",
|
|
331
|
-
"freshnessInputs": ["../../yarn.lock"]
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
In input-hash mode, vigiles auto-detects lock files across 15 ecosystems (npm, Yarn, pnpm, Bun, Bundler, Poetry, uv, PDM, pip, Cargo, Go, Composer, NuGet, SPM, Mix) and tracks them alongside linter configs, package.json, keyFiles references, and generated types. [Full details →](docs/freshness.md)
|
|
318
|
+
Disable per-file with `<!-- vigiles-disable require-spec -->` at the top of the markdown.
|
|
336
319
|
|
|
337
320
|
## Skills
|
|
338
321
|
|
|
@@ -348,6 +331,69 @@ Install with [Vercel Skills](https://github.com/vercel-labs/skills): `npx skills
|
|
|
348
331
|
| `enforce-rules-format` | Validate all rules have enforcement classification |
|
|
349
332
|
| `audit-feedback-loop` | Score your repo's feedback loop maturity |
|
|
350
333
|
|
|
334
|
+
## Test your Claude Code harness
|
|
335
|
+
|
|
336
|
+
vigiles also ships a library for **testing the harness itself** — your hooks,
|
|
337
|
+
settings, skills, and instruction files. `Agent = Model + Harness`; this tests
|
|
338
|
+
the harness, at two levels.
|
|
339
|
+
|
|
340
|
+
**Evals — does my change actually move agent behaviour?** Define a fixture, a set
|
|
341
|
+
of **arms** (a hook on vs off, with/without a CLAUDE.md rule), a task, and a
|
|
342
|
+
metric; `runEval` drives the real `claude` CLI N trials per arm and aggregates.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { runEval, formatEvalReport } from "vigiles/eval";
|
|
346
|
+
|
|
347
|
+
const report = await runEval({
|
|
348
|
+
fixture: { "src/billing.ts": "export function chargeCard() {}" },
|
|
349
|
+
arms: {
|
|
350
|
+
vanilla: {},
|
|
351
|
+
gated: { settings: { hooks: { PostToolUse: [refsHook] } } },
|
|
352
|
+
},
|
|
353
|
+
task: "Document chargeCard in SKILL.md, referencing it by name.",
|
|
354
|
+
measure: (ctx) => ({
|
|
355
|
+
marked: ctx.sh("grep -c vigiles:symbol SKILL.md") !== "0",
|
|
356
|
+
}),
|
|
357
|
+
trials: 6,
|
|
358
|
+
});
|
|
359
|
+
console.log(formatEvalReport(report)); // vanilla marked=0.00 gated marked=0.50
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Deterministic tests — does my hook fire correctly?** No API key, no cost.
|
|
363
|
+
`runHarnessTest` runs real `claude` against a **scripted mock model**
|
|
364
|
+
(`vigiles/mock-model`), so your real hooks fire but the agent's turns are fixed.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { runHarnessTest, scriptModel } from "vigiles/harness-test";
|
|
368
|
+
|
|
369
|
+
const r = await runHarnessTest({
|
|
370
|
+
settings: {
|
|
371
|
+
hooks: {
|
|
372
|
+
Stop: [
|
|
373
|
+
{
|
|
374
|
+
hooks: [
|
|
375
|
+
{
|
|
376
|
+
type: "command",
|
|
377
|
+
command: "test -f DONE || { echo 'not done' >&2; exit 2; }",
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
model: scriptModel([
|
|
385
|
+
{ text: "I'm done" }, // tries to stop → blocked
|
|
386
|
+
{ tool: "Bash", input: { command: "touch DONE" } },
|
|
387
|
+
{ text: "now done" },
|
|
388
|
+
]),
|
|
389
|
+
});
|
|
390
|
+
assert(JSON.parse(r.stdout).num_turns > 1); // the Stop hook forced more work
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The deterministic tier is reliable for **Stop hooks**; tool-event hooks
|
|
394
|
+
(Edit/Write) are headless-gated, so test those via the eval tier. Our own
|
|
395
|
+
findings from this harness live in [`research/benchmarks-runtime-gates.md`](research/benchmarks-runtime-gates.md).
|
|
396
|
+
|
|
351
397
|
## Maturity Levels
|
|
352
398
|
|
|
353
399
|
From [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need):
|
|
@@ -365,12 +411,14 @@ Specs compile to `CLAUDE.md` by default. Set `target: "AGENTS.md"` or `target: [
|
|
|
365
411
|
|
|
366
412
|
## Related Tools
|
|
367
413
|
|
|
368
|
-
vigiles doesn't try to do everything:
|
|
414
|
+
vigiles doesn't try to do everything. It owns one thing: compile-time verification of typed specs against real linter configs, filesystems, and package.json. Everything else, compose:
|
|
369
415
|
|
|
370
416
|
- **Architectural linting** — [ast-grep](https://ast-grep.github.io/), [Dependency Cruiser](https://github.com/sverweij/dependency-cruiser), [Steiger](https://github.com/feature-sliced/steiger). Reference their rules via `enforce()`.
|
|
371
|
-
- **File sync** — [Ruler](https://github.com/intellectronica/ruler), [rulesync](https://github.com/dyoshikawa/rulesync), [block/ai-rules](https://github.com/block/ai-rules). vigiles compiles the source; sync tools distribute.
|
|
372
|
-
- **Markdown linting** — [markdownlint](https://github.com/DavidAnson/markdownlint). vigiles generates
|
|
417
|
+
- **File sync** across agents — [Ruler](https://github.com/intellectronica/ruler), [rulesync](https://github.com/dyoshikawa/rulesync), [block/ai-rules](https://github.com/block/ai-rules). vigiles compiles the source; sync tools distribute.
|
|
418
|
+
- **Markdown linting** — [markdownlint](https://github.com/DavidAnson/markdownlint). vigiles generates markdown; structure is correct by construction.
|
|
419
|
+
- **Code-block linting in docs** — [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) for syntax, [twoslash](https://shikijs.github.io/twoslash/) for TS type-checking.
|
|
373
420
|
- **Prose quality** — [Vale](https://vale.sh). Different concern.
|
|
421
|
+
- **Runtime LLM rule checking** (e.g. ai-rulez `"AI-Powered Rule Enforcement"`) — opposite paradigm. Those tools send your code to a model on every check, costing tokens and giving non-reproducible verdicts. vigiles compiles once and checks deterministically forever after with `eslint`, `ruff`, `tsc`, Cedar evaluation — tools as deterministic as their inputs.
|
|
374
422
|
|
|
375
423
|
## License
|
|
376
424
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type RuntimeGate } from "./skill-runtime.js";
|
|
2
|
+
export interface ActionGate {
|
|
3
|
+
/** Tool name to gate, e.g. "Write" | "Edit" | "Bash". */
|
|
4
|
+
readonly on: string;
|
|
5
|
+
/** Deterministic check; a `cmd` command may include `{file}`. */
|
|
6
|
+
readonly gate: RuntimeGate;
|
|
7
|
+
/** Optional substring the (JSON-serialized) tool input must contain. */
|
|
8
|
+
readonly when?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ActionEvent {
|
|
11
|
+
/** The tool that just ran (PostToolUse `tool_name`). */
|
|
12
|
+
readonly tool: string;
|
|
13
|
+
/** The tool input (`tool_input`), e.g. `{ file_path, command }`. */
|
|
14
|
+
readonly input?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface ActionDecision {
|
|
17
|
+
readonly allow: boolean;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate action gates against a tool event. Runs every gate whose `on`
|
|
22
|
+
* matches the tool (and whose `when` substring matches the input); the first
|
|
23
|
+
* failure blocks. Plan-agnostic — order in any runtime workflow is irrelevant.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateAction(event: ActionEvent, gates: readonly ActionGate[], cwd: string): ActionDecision;
|
|
26
|
+
/** Load action gates from `.vigiles/action-gates.json`. */
|
|
27
|
+
export declare function loadActionGates(cwd: string): ActionGate[];
|
|
28
|
+
//# sourceMappingURL=action-gate.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateAction = evaluateAction;
|
|
4
|
+
exports.loadActionGates = loadActionGates;
|
|
5
|
+
/**
|
|
6
|
+
* vigiles — Action gates (the dynamic-workflow reframe).
|
|
7
|
+
*
|
|
8
|
+
* A skill gate is bound to a *step* (a fixed position in a plan). When the plan
|
|
9
|
+
* is generated at runtime (dynamic workflows), the step is the wrong unit. An
|
|
10
|
+
* **action gate** binds a deterministic check to a *tool action type* instead —
|
|
11
|
+
* "any time a Write happens to a `.ts` file, eslint must pass on it" — so it
|
|
12
|
+
* fires regardless of where in the runtime plan the action occurs.
|
|
13
|
+
*
|
|
14
|
+
* It is the same deterministic gate primitive (reuses `runGate` + the
|
|
15
|
+
* author-time reference resolution), re-anchored from step → action. Delivered
|
|
16
|
+
* as a PostToolUse hook (`vigiles action-hook`): exit 2 blocks the action and
|
|
17
|
+
* feeds the reason back, exit 0 allows it.
|
|
18
|
+
*
|
|
19
|
+
* Config: `.vigiles/action-gates.json` → `{ "gates": [ { on, gate, when? } ] }`.
|
|
20
|
+
* The gate command may contain `{file}`, substituted with the action's path.
|
|
21
|
+
*/
|
|
22
|
+
const node_fs_1 = require("node:fs");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const skill_runtime_js_1 = require("./skill-runtime.js");
|
|
25
|
+
/** The file path an action touched, for `{file}` substitution. */
|
|
26
|
+
function fileOf(event) {
|
|
27
|
+
const i = event.input ?? {};
|
|
28
|
+
const v = i.file_path ?? i.path;
|
|
29
|
+
return typeof v === "string" ? v : "";
|
|
30
|
+
}
|
|
31
|
+
/** Substitute `{file}` in a cmd gate with the action's path. */
|
|
32
|
+
function resolveGate(gate, event) {
|
|
33
|
+
if (gate.kind !== "cmd" || !gate.command.includes("{file}"))
|
|
34
|
+
return gate;
|
|
35
|
+
return { ...gate, command: gate.command.replaceAll("{file}", fileOf(event)) };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Evaluate action gates against a tool event. Runs every gate whose `on`
|
|
39
|
+
* matches the tool (and whose `when` substring matches the input); the first
|
|
40
|
+
* failure blocks. Plan-agnostic — order in any runtime workflow is irrelevant.
|
|
41
|
+
*/
|
|
42
|
+
function evaluateAction(event, gates, cwd) {
|
|
43
|
+
const inputStr = JSON.stringify(event.input ?? "");
|
|
44
|
+
for (const g of gates) {
|
|
45
|
+
if (g.on !== event.tool)
|
|
46
|
+
continue;
|
|
47
|
+
if (g.when && !inputStr.includes(g.when))
|
|
48
|
+
continue;
|
|
49
|
+
const outcome = (0, skill_runtime_js_1.runGate)(resolveGate(g.gate, event), cwd);
|
|
50
|
+
if (!outcome.ok) {
|
|
51
|
+
const tail = outcome.output ? `\n${outcome.output}` : "";
|
|
52
|
+
return {
|
|
53
|
+
allow: false,
|
|
54
|
+
message: `Action gate failed after ${event.tool}: ${(0, skill_runtime_js_1.gateLabel)(g.gate)} did not pass.${tail}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { allow: true, message: "" };
|
|
59
|
+
}
|
|
60
|
+
/** Load action gates from `.vigiles/action-gates.json`. */
|
|
61
|
+
function loadActionGates(cwd) {
|
|
62
|
+
const p = (0, node_path_1.resolve)(cwd, ".vigiles/action-gates.json");
|
|
63
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
64
|
+
return [];
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
|
|
67
|
+
return Array.isArray(parsed.gates) ? parsed.gates : [];
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=action-gate.js.map
|