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,464 @@
|
|
|
1
|
+
# vigiles Feature Ideas: Programming Techniques as Product Features
|
|
2
|
+
|
|
3
|
+
Focus: **deterministic, mechanically checkable** features that vigiles provides **to users** of the tool. Each maps a proven programming technique to a real problem in messy production AI-adopting codebases.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Custom Rule Plugin API (Railway-Composable)
|
|
8
|
+
|
|
9
|
+
**Analog:** Railway-oriented programming + ESLint's plugin system.
|
|
10
|
+
|
|
11
|
+
**User problem:** Every team has conventions vigiles can't anticipate. "All rules must reference a Jira ticket." "Every section must have examples." No way to add custom checks without forking.
|
|
12
|
+
|
|
13
|
+
**What vigiles provides:** A plugin API where each rule is a pure function `(parsedRule) → Diagnostic | null`. Rules compose in a pipeline — collect-all mode for IDEs, short-circuit for CI.
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// .vigiles/rules/require-jira.mjs
|
|
17
|
+
export default {
|
|
18
|
+
name: "require-jira",
|
|
19
|
+
meta: { description: "Every rule must reference a Jira ticket" },
|
|
20
|
+
check(rule, context) {
|
|
21
|
+
if (!/[A-Z]+-\d+/.test(rule.body)) {
|
|
22
|
+
return {
|
|
23
|
+
message: `Rule "${rule.title}" missing Jira reference`,
|
|
24
|
+
line: rule.line,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{ "plugins": ["./.vigiles/rules/require-jira.mjs"] }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Railway composition:** Rules can't have side effects — they receive parsed data and return diagnostics only. Pipeline ordering is user-controlled. Each step is `Content → Result<ok, Diagnostic[]>`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. Reverse Coverage: Linter → Instruction Mapping
|
|
40
|
+
|
|
41
|
+
**Analog:** Code coverage reports — but inverted. "Which linter rules lack a corresponding instruction?"
|
|
42
|
+
|
|
43
|
+
**User problem:** Team has 200 ESLint rules configured. CLAUDE.md explains 5 of them. When an agent trips `no-restricted-imports`, it has no context about _why_ that rule exists — it just blindly fixes. The agent is following rules it doesn't understand.
|
|
44
|
+
|
|
45
|
+
**What vigiles provides:** A report showing which configured linter rules have corresponding CLAUDE.md entries and which don't.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
vigiles coverage:
|
|
49
|
+
|
|
50
|
+
ESLint: 5 / 47 rules documented (10.6%)
|
|
51
|
+
|
|
52
|
+
Documented:
|
|
53
|
+
✓ no-console → CLAUDE.md:42 "No console.log in production"
|
|
54
|
+
✓ no-restricted-imports → CLAUDE.md:48 "Always use barrel file imports"
|
|
55
|
+
|
|
56
|
+
Undocumented (top 10 most-triggered):
|
|
57
|
+
✗ @typescript-eslint/no-explicit-any
|
|
58
|
+
✗ import/no-cycle
|
|
59
|
+
✗ react-hooks/exhaustive-deps
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
Ruff: 0 / 12 rules documented (0%)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Why this matters:** This is the inverse of `require-rule-file` (which checks instruction→linter). This checks linter→instruction. Together they form a bidirectional consistency check. The agent doesn't just follow rules — it _understands_ them.
|
|
66
|
+
|
|
67
|
+
**Implementation:**
|
|
68
|
+
|
|
69
|
+
- Read linter configs (`.eslintrc`, `ruff.toml`, etc.) to get list of enabled rules
|
|
70
|
+
- Cross-reference against `**Enforced by:**` annotations in instruction files
|
|
71
|
+
- Report coverage percentage and undocumented rules
|
|
72
|
+
- `vigiles coverage` CLI command + `--json` output
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 3. Dead Enforcement Detection
|
|
77
|
+
|
|
78
|
+
**Analog:** Dead code detection / tests marked `skip()` that still count as "covered."
|
|
79
|
+
|
|
80
|
+
**User problem:** CLAUDE.md says `**Enforced by:** eslint/no-console` but `.eslintrc` has `"no-console": "off"`. The enforcement is a lie. The agent thinks there's a safety net, but nothing actually catches violations. It's like a smoke detector with dead batteries.
|
|
81
|
+
|
|
82
|
+
**What vigiles provides:** Cross-checks `**Enforced by:**` claims against actual linter configuration to verify the rule is enabled.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
vigiles validate CLAUDE.md:
|
|
86
|
+
|
|
87
|
+
CLAUDE.md:42 Dead enforcement: "no-console" is referenced but disabled in .eslintrc.json
|
|
88
|
+
CLAUDE.md:55 Dead enforcement: "no-restricted-imports" rule not found in ESLint config
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Implementation:**
|
|
92
|
+
|
|
93
|
+
- Extend `require-rule-file` (which already resolves linter rules) to also check if the rule is _enabled_
|
|
94
|
+
- ESLint: load flat config, check if rule severity > 0
|
|
95
|
+
- Ruff: parse `ruff.toml` / `pyproject.toml` select/ignore lists
|
|
96
|
+
- RuboCop: parse `.rubocop.yml` enabled/disabled cops
|
|
97
|
+
- New rule: `no-dead-enforcement` (default: "auto" like require-rule-file)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 4. Instruction Snapshot Testing
|
|
102
|
+
|
|
103
|
+
**Analog:** Jest snapshot testing — lock down expected output, CI alerts on unexpected changes.
|
|
104
|
+
|
|
105
|
+
**User problem:** Instruction files change silently. Someone refactors CLAUDE.md, accidentally removes a rule or changes an enforcement annotation. Without structural awareness, PR reviewers just see markdown diffs — easy to miss that a rule was weakened.
|
|
106
|
+
|
|
107
|
+
**What vigiles provides:** `vigiles snapshot` generates a structured JSON summary of all instruction files. Commit it. CI diffs against it. Any unexpected structural change fails the build.
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
// .vigiles/snapshot.json (committed)
|
|
111
|
+
{
|
|
112
|
+
"CLAUDE.md": {
|
|
113
|
+
"rules": [
|
|
114
|
+
{
|
|
115
|
+
"title": "No console.log in production",
|
|
116
|
+
"enforcement": "enforced",
|
|
117
|
+
"enforcedBy": "eslint/no-console",
|
|
118
|
+
"line": 42
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"title": "Use Tailwind spacing scale",
|
|
122
|
+
"enforcement": "guidance",
|
|
123
|
+
"line": 55
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"lineCount": 89,
|
|
127
|
+
"enforced": 3,
|
|
128
|
+
"guidance": 2
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
$ vigiles snapshot --check
|
|
135
|
+
Snapshot mismatch:
|
|
136
|
+
- Removed rule: "Use barrel file imports" (was enforced)
|
|
137
|
+
+ Added rule: "Use direct imports" (guidance only)
|
|
138
|
+
~ Changed: "No console.log" enforcement: enforced → guidance
|
|
139
|
+
|
|
140
|
+
Run `vigiles snapshot --update` to accept changes.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Implementation:**
|
|
144
|
+
|
|
145
|
+
- `vigiles snapshot` — generate/update snapshot file
|
|
146
|
+
- `vigiles snapshot --check` — compare current state against committed snapshot
|
|
147
|
+
- Snapshot includes: rules, enforcement status, line numbers, counts
|
|
148
|
+
- Integrates with existing `parseClaudeMd` output
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 5. Stale Reference Detection
|
|
153
|
+
|
|
154
|
+
**Analog:** Broken link checkers / unused import warnings / dead code elimination.
|
|
155
|
+
|
|
156
|
+
**User problem:** Rules reference specific files, packages, and scripts that change over time. "Always use `src/utils/logger.ts`" persists months after `logger.ts` was renamed to `telemetry.ts`. The instruction is actively misleading.
|
|
157
|
+
|
|
158
|
+
**What vigiles provides:** Validates that file paths, package names, and script references in instruction files actually exist.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
CLAUDE.md:42 Stale reference: `src/utils/logger.ts` does not exist
|
|
162
|
+
CLAUDE.md:55 Stale reference: `npm run typecheck` — no "typecheck" script in package.json
|
|
163
|
+
CLAUDE.md:68 Stale reference: package `lodash` not found in package.json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**What it checks (all deterministic):**
|
|
167
|
+
|
|
168
|
+
- File paths in backticks → `fs.existsSync()`
|
|
169
|
+
- `npm run <script>` → check `package.json` scripts
|
|
170
|
+
- Package names → check manifest files (package.json, requirements.txt, Cargo.toml)
|
|
171
|
+
- Command names in hooks → `which` check
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. `vigiles init` — Scaffold from Existing Linter Config
|
|
176
|
+
|
|
177
|
+
**Analog:** `eslint --init` / `npm init` / scaffolding generators.
|
|
178
|
+
|
|
179
|
+
**User problem:** Team has 200 ESLint rules, a Ruff config, and RuboCop setup — but no CLAUDE.md. Writing one from scratch is tedious and error-prone. Most teams never start because the blank page is too daunting.
|
|
180
|
+
|
|
181
|
+
**What vigiles provides:** Auto-generates a CLAUDE.md skeleton from existing linter configurations, pre-populated with `**Enforced by:**` annotations.
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
$ vigiles init
|
|
185
|
+
|
|
186
|
+
Detected linters:
|
|
187
|
+
✓ ESLint (47 rules enabled)
|
|
188
|
+
✓ Ruff (12 rules enabled)
|
|
189
|
+
|
|
190
|
+
Detected AI tools:
|
|
191
|
+
✓ Claude Code (.claude/ directory found)
|
|
192
|
+
✓ Cursor (.cursor/ directory found)
|
|
193
|
+
|
|
194
|
+
Generated:
|
|
195
|
+
✓ CLAUDE.md (47 rules from ESLint, 12 from Ruff)
|
|
196
|
+
✓ .cursorrules (copied from CLAUDE.md)
|
|
197
|
+
|
|
198
|
+
$ head CLAUDE.md
|
|
199
|
+
# CLAUDE.md
|
|
200
|
+
|
|
201
|
+
## Rules
|
|
202
|
+
|
|
203
|
+
### No console.log in production
|
|
204
|
+
**Enforced by:** `eslint/no-console`
|
|
205
|
+
|
|
206
|
+
### No explicit any
|
|
207
|
+
**Enforced by:** `@typescript-eslint/no-explicit-any`
|
|
208
|
+
...
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Implementation:**
|
|
212
|
+
|
|
213
|
+
- Read linter configs using existing resolver infrastructure
|
|
214
|
+
- Generate markdown with proper annotation format
|
|
215
|
+
- Group rules by linter/category
|
|
216
|
+
- Generate for all detected AI tools
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 7. Token Budget Linting
|
|
221
|
+
|
|
222
|
+
**Analog:** Webpack bundle size budgets / Lighthouse performance budgets.
|
|
223
|
+
|
|
224
|
+
**User problem:** `max-lines: 500` is crude. A 200-line file with code block examples burns more tokens than a 400-line file of terse rules. Teams have no visibility into what's eating their context window — the scarce resource.
|
|
225
|
+
|
|
226
|
+
**What vigiles provides:** Actual token counting with per-section breakdown and configurable budgets.
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
CLAUDE.md token budget: 1550 / 2000
|
|
230
|
+
|
|
231
|
+
## Commands 120 tokens (8%)
|
|
232
|
+
## Architecture 340 tokens (22%)
|
|
233
|
+
## Rules 890 tokens (57%) ← largest
|
|
234
|
+
## Examples 200 tokens (13%)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Implementation:**
|
|
238
|
+
|
|
239
|
+
- Vendor minimal BPE tokenizer (cl100k_base, ~100KB pure JS, no API calls)
|
|
240
|
+
- New rule: `token-budget` with configurable limit
|
|
241
|
+
- Section-level breakdown keyed off `##` headers
|
|
242
|
+
- `--token-report` CLI flag for report-only mode
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 8. Skill Coloring: Side-Effect Classification
|
|
247
|
+
|
|
248
|
+
**Analog:** Function coloring (async/sync, `&`/`&mut`, IO monad). "What color is your function?"
|
|
249
|
+
|
|
250
|
+
**User problem:** Teams write skills and hooks but can't mechanically distinguish "safe to auto-run" from "touches production." A hook called "validate" could secretly `curl` an external API. Without coloring, every skill is equally opaque.
|
|
251
|
+
|
|
252
|
+
**What vigiles provides:** Validates that skills declare their side-effect level, and that the declaration matches the skill body.
|
|
253
|
+
|
|
254
|
+
```markdown
|
|
255
|
+
<!-- In SKILL.md -->
|
|
256
|
+
|
|
257
|
+
**Side effects:** none
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
vigiles scans for tool references and command patterns:
|
|
261
|
+
|
|
262
|
+
- `Read`, `Grep`, `Glob` → `none`
|
|
263
|
+
- `Write`, `Edit` → `local-fs`
|
|
264
|
+
- `curl`, `git push`, `deploy` → `network`
|
|
265
|
+
|
|
266
|
+
Mismatch = lint error: `Skill "audit" declares "none" but references Write tool`
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 9. Hook Validation (Contract Testing)
|
|
271
|
+
|
|
272
|
+
**Analog:** Contract testing / executable specification / CI pipeline linting.
|
|
273
|
+
|
|
274
|
+
**User problem:** PostToolUse hooks in `.claude/settings.json` are opaque shell strings. They reference nonexistent scripts, use invalid matchers, or silently fail. Nobody discovers the breakage until an agent session goes wrong.
|
|
275
|
+
|
|
276
|
+
**What vigiles provides:** Validates hook commands, matchers, and file references.
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
.claude/settings.json:
|
|
280
|
+
Hook[0] ✗ Command references `validate.mjs` which does not exist
|
|
281
|
+
Hook[1] ✗ Matcher "Edit|Writ" — did you mean "Edit|Write"? (no known tool matches "Writ")
|
|
282
|
+
Hook[2] ✓ `npx prettier --check .` — command valid
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Checks:**
|
|
286
|
+
|
|
287
|
+
- Command target exists (file or binary on PATH)
|
|
288
|
+
- Matcher regex is valid and matches known tool names
|
|
289
|
+
- File references in commands resolve
|
|
290
|
+
- Hook ordering (formatter before linter = wasted work)
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 10. Instruction Diff Reviews (Migration Safety)
|
|
295
|
+
|
|
296
|
+
**Analog:** Database migration safety checks / API breaking change detection / semver.
|
|
297
|
+
|
|
298
|
+
**User problem:** Someone removes `**Enforced by:**` in a PR. No CI catches the regression. The rule silently becomes unenforced — the instruction equivalent of `DROP CONSTRAINT` with no migration review.
|
|
299
|
+
|
|
300
|
+
**What vigiles provides:** A `diff` command that structurally compares instruction files between versions and classifies changes.
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
vigiles diff base..head:
|
|
304
|
+
|
|
305
|
+
✓ added "Validate API responses" (enforced by zod/schema)
|
|
306
|
+
⚠ weakened "No console.log" — was enforced, now guidance-only
|
|
307
|
+
⚠ removed "Use barrel imports"
|
|
308
|
+
✗ added "New rule" — missing enforcement annotation
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Classifications:** `added` ✓, `strengthened` ✓, `weakened` ⚠, `removed` ⚠, `added-unenforced` ✗
|
|
312
|
+
|
|
313
|
+
**Implementation:**
|
|
314
|
+
|
|
315
|
+
- `vigiles diff <base-file> <head-file>` CLI
|
|
316
|
+
- GitHub Action mode: auto-fetch base, post PR comment
|
|
317
|
+
- Suppress with `<!-- vigiles: intentional-weakening -->`
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## 11. Instruction File Dependency Graph
|
|
322
|
+
|
|
323
|
+
**Analog:** Module dependency graph / build system DAG / broken link checker.
|
|
324
|
+
|
|
325
|
+
**User problem:** Root CLAUDE.md says "See `src/api/CLAUDE.md` for API conventions." That file was deleted last sprint. Or: two files reference each other cyclically, creating ambiguity about which takes precedence.
|
|
326
|
+
|
|
327
|
+
**What vigiles provides:** Maps cross-references between instruction files, validates targets exist, detects cycles.
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
vigiles graph:
|
|
331
|
+
CLAUDE.md → src/api/CLAUDE.md ✓
|
|
332
|
+
CLAUDE.md → src/ui/CLAUDE.md ✗ (file not found)
|
|
333
|
+
src/api/CLAUDE.md → CLAUDE.md (cycle detected ⚠)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 12. Annotation Typo Detection
|
|
339
|
+
|
|
340
|
+
**Analog:** TypeScript strict mode / config key spell-check.
|
|
341
|
+
|
|
342
|
+
**User problem:** `**Enforced By:**` (wrong case), `**Enforce by:**` (wrong word), `**Guidance:**` (missing "only") — these silently fail to be recognized. The rule looks annotated to humans, but vigiles doesn't match it, producing confusing false positives.
|
|
343
|
+
|
|
344
|
+
**What vigiles provides:** Catches near-miss annotations via Levenshtein distance and suggests fixes.
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
CLAUDE.md:15 Near-miss: "**Enforced By:**" → did you mean "**Enforced by:**"?
|
|
348
|
+
CLAUDE.md:28 Near-miss: "**Guidance:**" → did you mean "**Guidance only**"?
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Also optionally enforces `**Why:**` explanations: `{ "requireWhy": true }`
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Summary
|
|
356
|
+
|
|
357
|
+
| # | Feature | Programming Analog | User Problem Solved |
|
|
358
|
+
| --- | ---------------------- | ------------------------------------ | --------------------------------------------------------- |
|
|
359
|
+
| 1 | **Plugin API** | Railway composition / ESLint plugins | Can't add custom checks without forking |
|
|
360
|
+
| 2 | **Reverse Coverage** | Code coverage (inverted) | Agent follows 200 rules it doesn't understand |
|
|
361
|
+
| 3 | **Dead Enforcement** | Dead code / skipped tests | "Enforced by X" but X is disabled in config |
|
|
362
|
+
| 4 | **Snapshot Testing** | Jest snapshots | Structural instruction changes slip through PRs |
|
|
363
|
+
| 5 | **Stale References** | Broken link checker | Rules reference deleted files/packages |
|
|
364
|
+
| 6 | **`init` Scaffolding** | `eslint --init` / generators | Blank page problem — no one writes CLAUDE.md from scratch |
|
|
365
|
+
| 7 | **Token Budgets** | Bundle size budgets | No visibility into context window cost |
|
|
366
|
+
| 8 | **Skill Coloring** | Function coloring (pure/impure) | Can't tell if a skill is safe to auto-run |
|
|
367
|
+
| 9 | **Hook Validation** | Contract testing | Hooks break silently at runtime |
|
|
368
|
+
| 10 | **Instruction Diffs** | Migration safety | Enforcement removed in PRs, nobody notices |
|
|
369
|
+
| 11 | **Dependency Graph** | Build DAG / import graph | Cross-references to deleted instruction files |
|
|
370
|
+
| 12 | **Typo Detection** | Type checking / strict mode | Near-miss annotations silently ignored |
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Research: Code Clone Detection & Deterministic Similarity Techniques
|
|
375
|
+
|
|
376
|
+
Collected April 2026 during investigation of [this Mastodon thread](https://neuromatch.social/@jonny/116328694967192899) about LLM code inconsistency — the same task implemented 3 different ways (set membership, regex, string methods).
|
|
377
|
+
|
|
378
|
+
### Clone Type Taxonomy
|
|
379
|
+
|
|
380
|
+
| Type | What it catches | Deterministic? | Example |
|
|
381
|
+
| ---------- | -------------------------------------------- | ------------------------------------ | --------------------------------- |
|
|
382
|
+
| **Type-1** | Exact clones (modulo whitespace/comments) | Yes | Copy-paste with reformatting |
|
|
383
|
+
| **Type-2** | Renamed identifiers/literals | Yes | Same logic, different var names |
|
|
384
|
+
| **Type-3** | Near-miss (added/deleted statements) | Yes (with fixed threshold) | Structural modifications |
|
|
385
|
+
| **Type-4** | Semantically equivalent, textually different | **No** (undecidable, Rice's theorem) | `set.has(x)` vs `/regex/.test(x)` |
|
|
386
|
+
|
|
387
|
+
Type-4 is the core complaint from the post. It's provably undecidable in the general case.
|
|
388
|
+
|
|
389
|
+
### Practical Tools
|
|
390
|
+
|
|
391
|
+
#### Token-Based (Type-1/2) — Fast, CI-ready
|
|
392
|
+
|
|
393
|
+
- **[PMD CPD](https://pmd.github.io/pmd/pmd_userdocs_cpd.html)** — Token stream matching, 31 languages. GitLab CI integration, Maven plugin. More comprehensive than jscpd for 3+ duplications.
|
|
394
|
+
- **[jscpd](https://github.com/kucherenko/jscpd)** — Rabin-Karp hash fingerprinting, 150+ languages. ~1.4s for 100 files. npm package, Codacy/GitHub Actions integration.
|
|
395
|
+
- **[SourcererCC](https://arxiv.org/abs/1512.06448)** — Token-based inverted index. Scales to 250 MLOC on 12GB RAM, 86% precision. Research tool, not CI-native. Twice as fast as CCFinderX at largest input sizes.
|
|
396
|
+
|
|
397
|
+
#### AST Tree Edit Distance (Type-3) — Promising
|
|
398
|
+
|
|
399
|
+
- **[similarity-ts](https://github.com/mizchi/similarity)** — Rust-based, uses Bloom filter + APTED tree edit distance. Built specifically for detecting LLM-generated structural duplicates. <1s for 60K LOC. ~50x speedup from Bloom filter (5x) + multithreading (4x) combined. TypeScript/JS only.
|
|
400
|
+
- **[APTED](https://github.com/DatabaseGroup/apted)** — State-of-the-art optimal tree edit distance. O(n²) worst case. Requires pre-filtering for practical use (n functions = n(n-1)/2 comparisons).
|
|
401
|
+
- **[tree-sitter](https://tree-sitter.github.io/tree-sitter/)** — GLR parser used by similarity-ts and academic tools for AST generation across languages.
|
|
402
|
+
|
|
403
|
+
#### PDG / Graph-Based (Type-3/4) — Academic
|
|
404
|
+
|
|
405
|
+
- **CCGraph** (ASE 2020) — PDG + approximate graph matching. Catches non-contiguous clones but graph isomorphism is NP-complete.
|
|
406
|
+
- **Scorpio** — PDG subgraph isomorphism. Academic prototype.
|
|
407
|
+
- **[HideNoSeek](https://github.com/aurore54f/hidenoseek)** — Static data flow analysis for JS syntactic clones.
|
|
408
|
+
|
|
409
|
+
#### Locality-Sensitive Hashing
|
|
410
|
+
|
|
411
|
+
Hash code features into buckets where similar items collide. Probabilistic but tunable false-positive rate. Used as pre-filter in tools like SourcererCC.
|
|
412
|
+
|
|
413
|
+
### Key Insight
|
|
414
|
+
|
|
415
|
+
For CI today: jscpd/PMD CPD for copy-paste (seconds), similarity-ts for structural near-misses (sub-second, JS/TS only), custom lint rules for known patterns. Type-4 detection (semantically identical, textually different) remains unsolved in production.
|
|
416
|
+
|
|
417
|
+
### Markdown Structure Validation Tools
|
|
418
|
+
|
|
419
|
+
- **[mdschema](https://github.com/jackchuka/mdschema)** — Declarative YAML schema for markdown structure. Go binary with npm wrapper. Supports required/optional sections, regex heading patterns, nested children, count constraints, frontmatter validation, word counts, code block requirements, link validation. **Integrated into vigiles as `require-structure` rule.**
|
|
420
|
+
- **[markdown-validator](https://github.com/mattbriggs/markdown-validator)** — Declarative rules for Hugo/DocFX-style markdown.
|
|
421
|
+
- **[markdownlint](https://github.com/DavidAnson/markdownlint)** — Formatting rules (no skipped levels, consistent lists) but not structural schemas.
|
|
422
|
+
- **[Vale](https://vale.sh)** — Prose linter with YAML rule collections. Focuses on writing style, not document structure.
|
|
423
|
+
|
|
424
|
+
### AI in CI Research
|
|
425
|
+
|
|
426
|
+
The "LLM reviews PRs in CI" approach hasn't worked due to non-determinism. What works:
|
|
427
|
+
|
|
428
|
+
- **Semgrep** — AI helps _write_ custom rules, but rules run deterministically.
|
|
429
|
+
- **SonarQube** — Added LLM explanations of findings, detection stays rule-based.
|
|
430
|
+
- **[Factory.ai](https://factory.ai/news/using-linters-to-direct-agents)** — Linters direct agents, not the reverse.
|
|
431
|
+
- **Hybrid SAST + LLM post-processing** — 91% false positive reduction vs standalone Semgrep.
|
|
432
|
+
|
|
433
|
+
Pattern: **LLM proposes, deterministic tool disposes.** The CI gate stays deterministic.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## TODO: Type System Enhancements
|
|
438
|
+
|
|
439
|
+
### Exhaustive Rule Coverage Type
|
|
440
|
+
|
|
441
|
+
A utility type that diffs all enabled linter rules against the rules referenced in the spec. `vigiles discover` does this at runtime — the type system could do it at authoring time:
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
type UncoveredRules = Exclude<EslintRule, ReferencedEslintRules>;
|
|
445
|
+
type _assert = [UncoveredRules] extends [never] ? true : never; // compile error if gaps
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
This would make "100% rule coverage" a type-checked property of the spec itself. Requires `generate-types` to emit a `ReferencedRules` type alongside the linter rule unions.
|
|
449
|
+
|
|
450
|
+
### Variadic `check()` — Multiple Assertions per Rule
|
|
451
|
+
|
|
452
|
+
Currently `check()` takes a single assertion. A variadic overload could accept multiple:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
"test-coverage": check(
|
|
456
|
+
every("src/**/*.service.ts").has("{name}.test.ts"),
|
|
457
|
+
every("src/**/*.service.ts").has("{name}.schema.ts"),
|
|
458
|
+
"Every service must have tests and a schema.",
|
|
459
|
+
),
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Requires expanding `CheckRule.assertion` to `FilePairingAssertion | FilePairingAssertion[]` and updating the compiler to iterate.
|
|
463
|
+
|
|
464
|
+
See also: [research/competitive-landscape.md](./competitive-landscape.md) for the full competitive landscape, moat analysis, pain points, and transferable concepts from other linters.
|