vigiles 2.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/.claude/settings.json +46 -0
- package/.claude/settings.local.json +8 -0
- package/.claude-plugin/hooks/post-edit.sh +34 -0
- package/.claude-plugin/hooks/pre-edit.sh +40 -0
- package/.claude-plugin/hooks/session-start.sh +38 -0
- package/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +47 -0
- package/.github/workflows/ci.yml +81 -0
- package/.prettierignore +1 -0
- package/.vigiles/generated.d.ts +205 -0
- package/CLAUDE.md +95 -0
- package/CLAUDE.md.spec.ts +142 -0
- package/CONTRIBUTING.md +121 -0
- package/LICENSE +21 -0
- package/README.md +377 -0
- package/action.yml +25 -0
- package/dist/action.d.ts +7 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +180 -0
- package/dist/action.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1267 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +650 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/compile.d.ts +101 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +503 -0
- package/dist/compile.js.map +1 -0
- package/dist/evolve.d.ts +132 -0
- package/dist/evolve.d.ts.map +1 -0
- package/dist/evolve.js +599 -0
- package/dist/evolve.js.map +1 -0
- package/dist/freshness.d.ts +67 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +244 -0
- package/dist/freshness.js.map +1 -0
- package/dist/freshness.test.d.ts +2 -0
- package/dist/freshness.test.d.ts.map +1 -0
- package/dist/freshness.test.js +356 -0
- package/dist/freshness.test.js.map +1 -0
- package/dist/generate-types.d.ts +34 -0
- package/dist/generate-types.d.ts.map +1 -0
- package/dist/generate-types.js +381 -0
- package/dist/generate-types.js.map +1 -0
- package/dist/inline.d.ts +58 -0
- package/dist/inline.d.ts.map +1 -0
- package/dist/inline.js +142 -0
- package/dist/inline.js.map +1 -0
- package/dist/inline.test.d.ts +5 -0
- package/dist/inline.test.d.ts.map +1 -0
- package/dist/inline.test.js +152 -0
- package/dist/inline.test.js.map +1 -0
- package/dist/linters.d.ts +38 -0
- package/dist/linters.d.ts.map +1 -0
- package/dist/linters.js +588 -0
- package/dist/linters.js.map +1 -0
- package/dist/proofs.d.ts +272 -0
- package/dist/proofs.d.ts.map +1 -0
- package/dist/proofs.js +622 -0
- package/dist/proofs.js.map +1 -0
- package/dist/proofs.test.d.ts +9 -0
- package/dist/proofs.test.d.ts.map +1 -0
- package/dist/proofs.test.js +952 -0
- package/dist/proofs.test.js.map +1 -0
- package/dist/spec.d.ts +258 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +113 -0
- package/dist/spec.js.map +1 -0
- package/dist/spec.test.d.ts +2 -0
- package/dist/spec.test.d.ts.map +1 -0
- package/dist/spec.test.js +1222 -0
- package/dist/spec.test.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +10 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +286 -0
- package/dist/validate.js.map +1 -0
- package/dist/validate.test.d.ts +2 -0
- package/dist/validate.test.d.ts.map +1 -0
- package/dist/validate.test.js +531 -0
- package/dist/validate.test.js.map +1 -0
- package/docs/agent-setup.md +85 -0
- package/docs/agent-workflows.md +103 -0
- package/docs/comparison.md +71 -0
- package/docs/freshness.md +124 -0
- package/docs/inline-mode.md +119 -0
- package/docs/linter-support.md +166 -0
- package/docs/spec-format.md +194 -0
- package/eslint.config.mjs +79 -0
- package/examples/CLAUDE.md +54 -0
- package/examples/CLAUDE.md.spec.ts +65 -0
- package/examples/SKILL.md +50 -0
- package/examples/SKILL.md.spec.ts +57 -0
- package/fixtures/example-project/CLAUDE.md +11 -0
- package/fixtures/example-project/package.json +9 -0
- package/fixtures/example-project/src/index.ts +3 -0
- package/fixtures/example-project/src/utils.test.ts +2 -0
- package/fixtures/example-project/src/utils.ts +3 -0
- package/logo.png +0 -0
- package/package.json +42 -0
- package/research/adoption-strategy.md +111 -0
- package/research/agent-integration.md +145 -0
- package/research/ai-code-quality.md +197 -0
- package/research/code-search-for-agents.md +313 -0
- package/research/competitive-landscape.md +163 -0
- package/research/doc-freshness.md +516 -0
- package/research/executable-specs.md +368 -0
- package/research/feature-ideas.md +464 -0
- package/research/formal-proofs-for-agents.md +338 -0
- package/research/fp-for-agent-harness.md +150 -0
- package/research/fp-for-deterministic-ai.md +131 -0
- package/research/self-evolving-specs.md +298 -0
- package/schemas/claude-md-strict.yml +18 -0
- package/schemas/claude-md.yml +6 -0
- package/schemas/skill-strict.yml +12 -0
- package/schemas/skill.yml +5 -0
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +102 -0
- package/skills/generate-rule/SKILL.md +90 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +124 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/src/action.ts +214 -0
- package/src/cli.test.ts +914 -0
- package/src/cli.ts +1631 -0
- package/src/compile.ts +691 -0
- package/src/evolve.ts +781 -0
- package/src/freshness.test.ts +449 -0
- package/src/freshness.ts +299 -0
- package/src/generate-types.ts +448 -0
- package/src/inline.test.ts +206 -0
- package/src/inline.ts +164 -0
- package/src/linters.ts +739 -0
- package/src/proofs.test.ts +1314 -0
- package/src/proofs.ts +849 -0
- package/src/spec.test.ts +1471 -0
- package/src/spec.ts +427 -0
- package/src/types.ts +117 -0
- package/src/validate.test.ts +701 -0
- package/src/validate.ts +381 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Executable Specification Files
|
|
2
|
+
|
|
3
|
+
Design document for vigiles v2's spec-driven compilation model. The spec is the source of truth. The markdown is a build artifact.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
vigiles v2 introduces `.spec.ts` files — TypeScript source files that compile to instruction files (CLAUDE.md, SKILL.md). TypeScript's type system catches errors at authoring time. vigiles validates everything else at build time. The annotation model from v1 becomes a compile-time guarantee: you can't create a rule without calling `enforce()`, `check()`, or `guidance()`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Core Concept
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
CLAUDE.md.spec.ts → compiles to → CLAUDE.md
|
|
17
|
+
SKILL.md.spec.ts → compiles to → SKILL.md
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `.claude/settings.json` — validated in place (no spec needed)
|
|
21
|
+
- `vigiles.config.ts` — optional project-level settings
|
|
22
|
+
- Generated files carry a hash comment for tamper detection
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Three Rule Types
|
|
27
|
+
|
|
28
|
+
| Type | Builder | What it means | vigiles responsibility |
|
|
29
|
+
| ------------- | ------------ | ----------------------------------------------------------------- | --------------------------------------------------- |
|
|
30
|
+
| **Delegated** | `enforce()` | Backed by external linter (ESLint, Ruff, Clippy, Pylint, RuboCop) | Verify rule exists and is enabled in project config |
|
|
31
|
+
| **Checked** | `check()` | vigiles-owned filesystem assertion (e.g., test file pairing) | Execute the assertion, fail build on violation |
|
|
32
|
+
| **Guidance** | `guidance()` | Prose-only, not mechanically enforced | Compile to `**Guidance only**` annotation |
|
|
33
|
+
|
|
34
|
+
Delegated rules reuse the existing linter cross-referencing engine and extend to additional tools (ast-grep, Dependency Cruiser, Steiger). Checked rules are scoped to filesystem assertions only — vigiles does NOT reimplement architectural linting. Guidance rules are pass-through.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Spec API
|
|
39
|
+
|
|
40
|
+
### `claude()` — CLAUDE.md spec
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { claude, enforce, check, guidance, every } from "vigiles/spec";
|
|
44
|
+
|
|
45
|
+
export default claude({
|
|
46
|
+
commands: {
|
|
47
|
+
"npm run build": "Compile TypeScript to dist/",
|
|
48
|
+
"npm test": "Build and run all tests",
|
|
49
|
+
"npm run fmt": "Format with prettier",
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
keyFiles: {
|
|
53
|
+
"src/validate.ts": "Core validation engine",
|
|
54
|
+
"src/cli.ts": "CLI entry point",
|
|
55
|
+
"src/types.ts": "TypeScript type definitions",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
sections: {
|
|
59
|
+
Architecture: `
|
|
60
|
+
TypeScript strict-mode codebase. Core engine in src/validate.ts.
|
|
61
|
+
`,
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
rules: {
|
|
65
|
+
"no-console-log-in-production": enforce(
|
|
66
|
+
"eslint/no-console",
|
|
67
|
+
"Use the structured logger which routes to Datadog.",
|
|
68
|
+
),
|
|
69
|
+
|
|
70
|
+
"use-barrel-imports": enforce(
|
|
71
|
+
"eslint/no-restricted-imports",
|
|
72
|
+
"Prevents import path drift during refactoring.",
|
|
73
|
+
),
|
|
74
|
+
|
|
75
|
+
"use-tailwind-spacing": guidance(
|
|
76
|
+
"Use spacing scale values (p-4, m-8) instead of arbitrary values.",
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
"controllers-have-tests": check(
|
|
80
|
+
every("src/**/*.controller.ts").has("{name}.test.ts"),
|
|
81
|
+
"Every controller must have a co-located test file.",
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Fields:
|
|
88
|
+
|
|
89
|
+
- `commands` — map of npm scripts/commands to descriptions. Verified against `package.json` at compile time.
|
|
90
|
+
- `keyFiles` — map of file paths to descriptions. Verified via `existsSync` at compile time.
|
|
91
|
+
- `sections` — map of section names to markdown prose. Pass-through.
|
|
92
|
+
- `rules` — map of rule IDs to `enforce()`, `check()`, or `guidance()` calls.
|
|
93
|
+
|
|
94
|
+
### `skill()` — SKILL.md spec
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { skill, file, cmd, ref } from "vigiles/spec";
|
|
98
|
+
|
|
99
|
+
export default skill({
|
|
100
|
+
name: "run-tests",
|
|
101
|
+
description: "Run the project test suite and report results",
|
|
102
|
+
argumentHint: "--filter <pattern>",
|
|
103
|
+
instructions: `
|
|
104
|
+
1. Read ${file("jest.config.ts")} to understand the test setup
|
|
105
|
+
2. Run ${cmd("npm test")} to execute the suite
|
|
106
|
+
3. If tests fail, check ${ref("skills/debug/SKILL.md")} for debugging steps
|
|
107
|
+
`,
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Tagged template helpers:
|
|
112
|
+
|
|
113
|
+
- `file("path")` — verified file reference, compiles to backtick path. Fails if file doesn't exist.
|
|
114
|
+
- `cmd("npm test")` — verified command, compiles to backtick command. Checks `package.json` scripts.
|
|
115
|
+
- `ref("skills/other/SKILL.md")` — verified cross-reference. Fails if target doesn't exist.
|
|
116
|
+
|
|
117
|
+
### Rule builders
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// Delegated — backed by external linter
|
|
121
|
+
enforce(linterRule: `${LinterName}/${string}`, why: string)
|
|
122
|
+
|
|
123
|
+
// Guidance — prose only
|
|
124
|
+
guidance(text: string)
|
|
125
|
+
|
|
126
|
+
// Proven — vigiles executes the check
|
|
127
|
+
check(assertion: Assertion, why: string)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Proof assertions (future — ast-grep powered)
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// Every file matching glob has a corresponding file matching pattern
|
|
134
|
+
every("src/**/*.controller.ts").has("{name}.test.ts");
|
|
135
|
+
|
|
136
|
+
// No file matching glob contains the AST pattern
|
|
137
|
+
no("src/**/*.ts").matches("console.log($$$)");
|
|
138
|
+
|
|
139
|
+
// Import boundaries between layers
|
|
140
|
+
layers({
|
|
141
|
+
domain: { allow: [] },
|
|
142
|
+
service: { allow: ["domain"] },
|
|
143
|
+
handler: { allow: ["service", "domain"] },
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- `every(glob).has(pattern)` — structural file pairing
|
|
148
|
+
- `no(glob).matches(astPattern)` — absence proof via AST
|
|
149
|
+
- `layers({...})` — import boundary verification
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Template Literal Types
|
|
154
|
+
|
|
155
|
+
TypeScript template literal types provide compile-time safety:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
type LinterName =
|
|
159
|
+
| "eslint"
|
|
160
|
+
| "stylelint"
|
|
161
|
+
| "ruff"
|
|
162
|
+
| "clippy"
|
|
163
|
+
| "pylint"
|
|
164
|
+
| "rubocop";
|
|
165
|
+
type LinterRule = `${LinterName}/${string}`;
|
|
166
|
+
|
|
167
|
+
// Catches typos at authoring time
|
|
168
|
+
enforce("eslit/no-console", "...");
|
|
169
|
+
// ^^^^^ Type error: "eslit" is not assignable to LinterName
|
|
170
|
+
|
|
171
|
+
type HookEvent =
|
|
172
|
+
| "PreToolUse"
|
|
173
|
+
| "PostToolUse"
|
|
174
|
+
| "Notification"
|
|
175
|
+
| "Stop"
|
|
176
|
+
| "SubagentStop";
|
|
177
|
+
|
|
178
|
+
type ClaudeTool =
|
|
179
|
+
| "Read"
|
|
180
|
+
| "Write"
|
|
181
|
+
| "Edit"
|
|
182
|
+
| "Bash"
|
|
183
|
+
| "Glob"
|
|
184
|
+
| "Grep"
|
|
185
|
+
| "WebSearch"
|
|
186
|
+
| "WebFetch"
|
|
187
|
+
| "TodoWrite"
|
|
188
|
+
| "NotebookEdit";
|
|
189
|
+
|
|
190
|
+
// Catches tool name typos in hook matchers
|
|
191
|
+
const hook: Hook = {
|
|
192
|
+
event: "PostToolUse",
|
|
193
|
+
matcher: ["Edti"], // Type error
|
|
194
|
+
command: "npx prettier --check .",
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Compilation
|
|
201
|
+
|
|
202
|
+
`vigiles compile` reads spec files and generates markdown.
|
|
203
|
+
|
|
204
|
+
### Output format
|
|
205
|
+
|
|
206
|
+
Generated CLAUDE.md includes:
|
|
207
|
+
|
|
208
|
+
- Hash comment at top: `<!-- vigiles:sha256:HASH compiled from CLAUDE.md.spec.ts -->`
|
|
209
|
+
- `## Commands` section from `commands` map
|
|
210
|
+
- `## Key Files` section from `keyFiles` map
|
|
211
|
+
- Custom `## {name}` sections from `sections` map
|
|
212
|
+
- `## Rules` section with properly annotated rules under `###` headings
|
|
213
|
+
|
|
214
|
+
Generated SKILL.md includes:
|
|
215
|
+
|
|
216
|
+
- Hash comment at top
|
|
217
|
+
- YAML frontmatter (`name`, `description`, `argument_hint`)
|
|
218
|
+
- Instruction body with resolved `file()`, `cmd()`, `ref()` references
|
|
219
|
+
|
|
220
|
+
### Compile-time checks
|
|
221
|
+
|
|
222
|
+
| Check | What fails |
|
|
223
|
+
| -------------------------------------------- | ---------------------- |
|
|
224
|
+
| `commands` key not in `package.json` scripts | Unknown command |
|
|
225
|
+
| `keyFiles` key doesn't exist on disk | Stale file reference |
|
|
226
|
+
| `file()` path doesn't exist | Broken file reference |
|
|
227
|
+
| `cmd()` script not in `package.json` | Unknown script |
|
|
228
|
+
| `ref()` target doesn't exist | Broken cross-reference |
|
|
229
|
+
| `enforce()` rule not found in linter catalog | Unknown linter rule |
|
|
230
|
+
| `enforce()` rule disabled in project config | Dead enforcement |
|
|
231
|
+
| `check()` assertion fails | Proof violation |
|
|
232
|
+
| Compiled output exceeds `max-lines` | File too long |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Hash Verification
|
|
237
|
+
|
|
238
|
+
Generated files carry a SHA-256 hash of their content (excluding the hash line itself).
|
|
239
|
+
|
|
240
|
+
On `vigiles check`:
|
|
241
|
+
|
|
242
|
+
1. Read the hash comment from the generated file
|
|
243
|
+
2. Recompute hash of content (excluding hash line)
|
|
244
|
+
3. If mismatch — file was manually edited after compilation
|
|
245
|
+
4. Report: `CLAUDE.md was modified after compilation. Run vigiles compile to regenerate, or vigiles adopt to pull changes back.`
|
|
246
|
+
|
|
247
|
+
This closes the loop: specs are the source of truth, but manual edits are detected rather than silently lost.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Commands
|
|
252
|
+
|
|
253
|
+
| Command | Purpose | Status |
|
|
254
|
+
| ------------------ | ------------------------------------------------------------- | ------- |
|
|
255
|
+
| `vigiles compile` | spec.ts files to .md files (with hash) | Phase 1 |
|
|
256
|
+
| `vigiles check` | Validate everything: specs, hashes, linter cross-refs, proofs | Phase 1 |
|
|
257
|
+
| `vigiles adopt` | Detect manual .md edits, help merge back into spec | Phase 4 |
|
|
258
|
+
| `vigiles discover` | Scan linter configs, report coverage gaps | Phase 3 |
|
|
259
|
+
|
|
260
|
+
Existing v1 commands (`vigiles validate`, `vigiles` with file args) continue to work unchanged.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Integration with Existing Linters
|
|
265
|
+
|
|
266
|
+
vigiles does NOT replace linters. Three relationships:
|
|
267
|
+
|
|
268
|
+
1. **Verify** — `enforce("eslint/no-console")` checks that the rule exists and is enabled. This is the existing `require-rule-file` behavior, moved to compile time.
|
|
269
|
+
2. **Fill gaps** — `check()` handles properties no single linter can express: cross-file pairing, import boundaries, AST absence across a codebase. Powered by ast-grep.
|
|
270
|
+
3. **Recommend** — if a `check()` assertion could be expressed as an existing linter rule, suggest delegating. Example: `no("src/**/*.ts").matches("console.log($$$)")` could be `enforce("eslint/no-console")`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## What Replaces What
|
|
275
|
+
|
|
276
|
+
| v1 Rule | v2 Equivalent | Why |
|
|
277
|
+
| --------------------- | -------------------------------------------------------------------- | --------------------------------------------- |
|
|
278
|
+
| `require-annotations` | Type system — can't create rule without `enforce`/`guidance`/`prove` | Compile-time guarantee replaces runtime check |
|
|
279
|
+
| `max-lines` | Build constraint on compiled output | Constrain the artifact, not the source |
|
|
280
|
+
| `require-rule-file` | `enforce()` verification during compile | Same check, earlier in the pipeline |
|
|
281
|
+
| `require-structure` | Compiler generates correct structure | We control the output format |
|
|
282
|
+
| `no-broken-links` | `file()` and `ref()` verified at compile | Typed references replace regex scanning |
|
|
283
|
+
|
|
284
|
+
v1 validation remains available for projects that don't adopt specs. No breaking changes.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Migration from v1
|
|
289
|
+
|
|
290
|
+
- `vigiles init --from-claude-md` parses an existing CLAUDE.md and generates a `.spec.ts` file
|
|
291
|
+
- `**Enforced by:** eslint/X` lines become `enforce("eslint/X", "...")`
|
|
292
|
+
- `**Guidance only**` lines become `guidance("...")`
|
|
293
|
+
- Commands, key files, and sections are extracted into the appropriate fields
|
|
294
|
+
- Existing `.vigilesrc.json` still works — v1 validation is unchanged
|
|
295
|
+
- Specs are opt-in — adoption is incremental, not a flag day
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Implementation Strategy
|
|
300
|
+
|
|
301
|
+
### Phase 1: Foundation
|
|
302
|
+
|
|
303
|
+
- Type definitions for spec system (`claude()`, `skill()`, `enforce()`, `guidance()`, `check()`)
|
|
304
|
+
- Builder functions with TypeScript template literal types
|
|
305
|
+
- Compiler: spec to markdown with hash
|
|
306
|
+
- `vigiles compile` and `vigiles check` commands
|
|
307
|
+
- `vigiles init --from-claude-md` migration tool
|
|
308
|
+
|
|
309
|
+
### Phase 2: Proofs
|
|
310
|
+
|
|
311
|
+
- ast-grep integration via `@ast-grep/napi`
|
|
312
|
+
- `every(glob).has(pattern)` — filesystem assertions
|
|
313
|
+
- `no(glob).matches(astPattern)` — AST pattern absence
|
|
314
|
+
- `layers({...})` — import graph analysis
|
|
315
|
+
|
|
316
|
+
### Phase 3: Discovery
|
|
317
|
+
|
|
318
|
+
- `vigiles discover` — scan linter configs, report coverage gaps
|
|
319
|
+
- Suggest undocumented rules that agents trip on frequently
|
|
320
|
+
- Reverse coverage: which enabled linter rules have no corresponding spec entry
|
|
321
|
+
|
|
322
|
+
### Phase 4: Adopt
|
|
323
|
+
|
|
324
|
+
- `vigiles adopt` — detect manual edits via hash mismatch
|
|
325
|
+
- Diff analysis to suggest spec updates
|
|
326
|
+
- Interactive merge workflow
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Technology Choices
|
|
331
|
+
|
|
332
|
+
| Choice | Why | Alternatives considered |
|
|
333
|
+
| ------------------------------------ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
334
|
+
| **ast-grep** (`@ast-grep/napi`) | 26 languages, Rust-fast, Node.js API, pattern syntax is readable | tree-sitter raw (too low-level), Semgrep (can't embed, restrictive license) |
|
|
335
|
+
| **TypeScript specs** (not YAML/JSON) | Full type system, template literal types, IDE autocomplete, conditional logic | YAML (no type safety), JSON (no comments, no logic) |
|
|
336
|
+
| **SHA-256 hash** | Fast, built into Node.js crypto, collision-resistant | MD5 (deprecated), content-addressed CAS (overengineered) |
|
|
337
|
+
| **Compile-to-markdown** | Agents read markdown natively, no runtime dependency on vigiles | Custom binary format (agents can't read), JSON (poor agent UX) |
|
|
338
|
+
|
|
339
|
+
### Future considerations
|
|
340
|
+
|
|
341
|
+
- **ESLint v10 inline plugins** — potential target for generating ESLint rules from `check()` assertions
|
|
342
|
+
- **TypeSpec emitter pattern** — one source, multiple outputs (CLAUDE.md + .cursorrules + AGENTS.md from single spec)
|
|
343
|
+
- **Pulumi model** — TypeScript as the authoring language for declarative output is a proven pattern
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Research Sources
|
|
348
|
+
|
|
349
|
+
| Source | Key insight |
|
|
350
|
+
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
351
|
+
| AST hallucination detection (arxiv 2601.19106) | ast-grep achieves 100% precision, 87.6% recall for detecting AST-inconsistent code |
|
|
352
|
+
| Factory.ai: "Agents write the code; linters write the law" | Linters direct agents, not the reverse. vigiles is the bridge |
|
|
353
|
+
| Martin Fowler: "Harness Engineering" (Feb 2026) | The harness (hooks, rules, CI) is as important as the agent itself |
|
|
354
|
+
| ETH Zurich: LLM-generated instruction files | Reduce success by 3%, increase cost by 20%. Over 50% of rules were noise |
|
|
355
|
+
| Spotify Honk | 650+ agent PRs/month with strong feedback loops. Linter enforcement is critical |
|
|
356
|
+
| CodeRabbit | AI-generated code has 1.7x more issues than human code. More enforcement, not less |
|
|
357
|
+
| TypeSpec emitter pattern | One source of truth, multiple output formats. Proven at Azure scale |
|
|
358
|
+
| Pulumi | TypeScript to declarative output. Developer ergonomics matter |
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Open Questions
|
|
363
|
+
|
|
364
|
+
1. **Spec file discovery** — should `vigiles compile` scan for `*.spec.ts` recursively, or require explicit listing in `vigiles.config.ts`?
|
|
365
|
+
2. **Proof caching** — ast-grep scans can be expensive. Cache proof results and invalidate on file change? Or always re-run?
|
|
366
|
+
3. **Multi-file specs** — can one spec import from another? Shared rule sets across teams?
|
|
367
|
+
4. **Watch mode** — `vigiles compile --watch` for development? Or rely on editor save hooks?
|
|
368
|
+
5. **Error recovery** — if one `enforce()` rule is unknown, should the whole compile fail or emit a warning annotation?
|