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.
Files changed (156) hide show
  1. package/.claude/settings.json +46 -0
  2. package/.claude/settings.local.json +8 -0
  3. package/.claude-plugin/hooks/post-edit.sh +34 -0
  4. package/.claude-plugin/hooks/pre-edit.sh +40 -0
  5. package/.claude-plugin/hooks/session-start.sh +38 -0
  6. package/.claude-plugin/marketplace.json +14 -0
  7. package/.claude-plugin/plugin.json +47 -0
  8. package/.github/workflows/ci.yml +81 -0
  9. package/.prettierignore +1 -0
  10. package/.vigiles/generated.d.ts +205 -0
  11. package/CLAUDE.md +95 -0
  12. package/CLAUDE.md.spec.ts +142 -0
  13. package/CONTRIBUTING.md +121 -0
  14. package/LICENSE +21 -0
  15. package/README.md +377 -0
  16. package/action.yml +25 -0
  17. package/dist/action.d.ts +7 -0
  18. package/dist/action.d.ts.map +1 -0
  19. package/dist/action.js +180 -0
  20. package/dist/action.js.map +1 -0
  21. package/dist/cli.d.ts +12 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +1267 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/cli.test.d.ts +2 -0
  26. package/dist/cli.test.d.ts.map +1 -0
  27. package/dist/cli.test.js +650 -0
  28. package/dist/cli.test.js.map +1 -0
  29. package/dist/compile.d.ts +101 -0
  30. package/dist/compile.d.ts.map +1 -0
  31. package/dist/compile.js +503 -0
  32. package/dist/compile.js.map +1 -0
  33. package/dist/evolve.d.ts +132 -0
  34. package/dist/evolve.d.ts.map +1 -0
  35. package/dist/evolve.js +599 -0
  36. package/dist/evolve.js.map +1 -0
  37. package/dist/freshness.d.ts +67 -0
  38. package/dist/freshness.d.ts.map +1 -0
  39. package/dist/freshness.js +244 -0
  40. package/dist/freshness.js.map +1 -0
  41. package/dist/freshness.test.d.ts +2 -0
  42. package/dist/freshness.test.d.ts.map +1 -0
  43. package/dist/freshness.test.js +356 -0
  44. package/dist/freshness.test.js.map +1 -0
  45. package/dist/generate-types.d.ts +34 -0
  46. package/dist/generate-types.d.ts.map +1 -0
  47. package/dist/generate-types.js +381 -0
  48. package/dist/generate-types.js.map +1 -0
  49. package/dist/inline.d.ts +58 -0
  50. package/dist/inline.d.ts.map +1 -0
  51. package/dist/inline.js +142 -0
  52. package/dist/inline.js.map +1 -0
  53. package/dist/inline.test.d.ts +5 -0
  54. package/dist/inline.test.d.ts.map +1 -0
  55. package/dist/inline.test.js +152 -0
  56. package/dist/inline.test.js.map +1 -0
  57. package/dist/linters.d.ts +38 -0
  58. package/dist/linters.d.ts.map +1 -0
  59. package/dist/linters.js +588 -0
  60. package/dist/linters.js.map +1 -0
  61. package/dist/proofs.d.ts +272 -0
  62. package/dist/proofs.d.ts.map +1 -0
  63. package/dist/proofs.js +622 -0
  64. package/dist/proofs.js.map +1 -0
  65. package/dist/proofs.test.d.ts +9 -0
  66. package/dist/proofs.test.d.ts.map +1 -0
  67. package/dist/proofs.test.js +952 -0
  68. package/dist/proofs.test.js.map +1 -0
  69. package/dist/spec.d.ts +258 -0
  70. package/dist/spec.d.ts.map +1 -0
  71. package/dist/spec.js +113 -0
  72. package/dist/spec.js.map +1 -0
  73. package/dist/spec.test.d.ts +2 -0
  74. package/dist/spec.test.d.ts.map +1 -0
  75. package/dist/spec.test.js +1222 -0
  76. package/dist/spec.test.js.map +1 -0
  77. package/dist/types.d.ts +101 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +3 -0
  80. package/dist/types.js.map +1 -0
  81. package/dist/validate.d.ts +10 -0
  82. package/dist/validate.d.ts.map +1 -0
  83. package/dist/validate.js +286 -0
  84. package/dist/validate.js.map +1 -0
  85. package/dist/validate.test.d.ts +2 -0
  86. package/dist/validate.test.d.ts.map +1 -0
  87. package/dist/validate.test.js +531 -0
  88. package/dist/validate.test.js.map +1 -0
  89. package/docs/agent-setup.md +85 -0
  90. package/docs/agent-workflows.md +103 -0
  91. package/docs/comparison.md +71 -0
  92. package/docs/freshness.md +124 -0
  93. package/docs/inline-mode.md +119 -0
  94. package/docs/linter-support.md +166 -0
  95. package/docs/spec-format.md +194 -0
  96. package/eslint.config.mjs +79 -0
  97. package/examples/CLAUDE.md +54 -0
  98. package/examples/CLAUDE.md.spec.ts +65 -0
  99. package/examples/SKILL.md +50 -0
  100. package/examples/SKILL.md.spec.ts +57 -0
  101. package/fixtures/example-project/CLAUDE.md +11 -0
  102. package/fixtures/example-project/package.json +9 -0
  103. package/fixtures/example-project/src/index.ts +3 -0
  104. package/fixtures/example-project/src/utils.test.ts +2 -0
  105. package/fixtures/example-project/src/utils.ts +3 -0
  106. package/logo.png +0 -0
  107. package/package.json +42 -0
  108. package/research/adoption-strategy.md +111 -0
  109. package/research/agent-integration.md +145 -0
  110. package/research/ai-code-quality.md +197 -0
  111. package/research/code-search-for-agents.md +313 -0
  112. package/research/competitive-landscape.md +163 -0
  113. package/research/doc-freshness.md +516 -0
  114. package/research/executable-specs.md +368 -0
  115. package/research/feature-ideas.md +464 -0
  116. package/research/formal-proofs-for-agents.md +338 -0
  117. package/research/fp-for-agent-harness.md +150 -0
  118. package/research/fp-for-deterministic-ai.md +131 -0
  119. package/research/self-evolving-specs.md +298 -0
  120. package/schemas/claude-md-strict.yml +18 -0
  121. package/schemas/claude-md.yml +6 -0
  122. package/schemas/skill-strict.yml +12 -0
  123. package/schemas/skill.yml +5 -0
  124. package/skills/audit-feedback-loop/SKILL.md +76 -0
  125. package/skills/edit-spec/SKILL.md +131 -0
  126. package/skills/enforce-rules-format/SKILL.md +71 -0
  127. package/skills/generate-logo/SKILL.md +102 -0
  128. package/skills/generate-rule/SKILL.md +90 -0
  129. package/skills/linter-docs/clippy.md +241 -0
  130. package/skills/linter-docs/eslint.md +384 -0
  131. package/skills/linter-docs/pylint.md +288 -0
  132. package/skills/linter-docs/rubocop.md +277 -0
  133. package/skills/linter-docs/ruff.md +187 -0
  134. package/skills/linter-docs/stylelint.md +247 -0
  135. package/skills/migrate-to-spec/SKILL.md +124 -0
  136. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  137. package/skills/strengthen/SKILL.md +168 -0
  138. package/src/action.ts +214 -0
  139. package/src/cli.test.ts +914 -0
  140. package/src/cli.ts +1631 -0
  141. package/src/compile.ts +691 -0
  142. package/src/evolve.ts +781 -0
  143. package/src/freshness.test.ts +449 -0
  144. package/src/freshness.ts +299 -0
  145. package/src/generate-types.ts +448 -0
  146. package/src/inline.test.ts +206 -0
  147. package/src/inline.ts +164 -0
  148. package/src/linters.ts +739 -0
  149. package/src/proofs.test.ts +1314 -0
  150. package/src/proofs.ts +849 -0
  151. package/src/spec.test.ts +1471 -0
  152. package/src/spec.ts +427 -0
  153. package/src/types.ts +117 -0
  154. package/src/validate.test.ts +701 -0
  155. package/src/validate.ts +381 -0
  156. 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.