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,247 @@
1
+ # Stylelint — Reference
2
+
3
+ Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and `pr-to-lint-rule` (write custom rules).
4
+
5
+ ## Check Existing Plugins First
6
+
7
+ Before writing a custom rule, search these packages — the pattern may already be covered:
8
+
9
+ | Plugin | Scope | Key rules |
10
+ | ------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `stylelint-config-standard` | Standard CSS conventions | Extends `stylelint-config-recommended`, adds `declaration-block-no-redundant-longhand-properties`, `shorthand-property-no-redundant-values` |
12
+ | `stylelint-config-recommended` | Error prevention | `no-descending-specificity`, `no-duplicate-selectors`, `declaration-block-no-duplicate-properties` |
13
+ | `stylelint-order` | Property ordering | `order/properties-order`, `order/properties-alphabetical-order` — configurable sort orders |
14
+ | `stylelint-scss` | SCSS syntax | `scss/no-duplicate-mixins`, `scss/no-unused-private-members`, `scss/at-rule-no-unknown`, `scss/dollar-variable-pattern` |
15
+ | `stylelint-config-css-modules` | CSS Modules | Adjusts rules for `:global`, `:local`, `composes` syntax |
16
+ | `stylelint-config-tailwindcss` | Tailwind CSS | Allows Tailwind directives (`@tailwind`, `@apply`, `@screen`) |
17
+ | `stylelint-no-unsupported-browser-features` | Browser compat | Flags CSS features not supported by your browserslist targets |
18
+ | `stylelint-declaration-strict-value` | Value enforcement | Require variables/functions for specific properties (colors, fonts, z-index) |
19
+ | `stylelint-config-clean-order` | Property order | Opinionated property ordering (positioning → box model → typography → visual → misc) |
20
+ | `stylelint-config-prettier` | Prettier compat | Disables rules that conflict with Prettier (deprecated in Stylelint v15+) |
21
+ | `stylelint-a11y` | Accessibility | `a11y/no-outline-none`, `a11y/no-text-size-adjust`, `a11y/media-prefers-reduced-motion` |
22
+
23
+ **Tip:** Stylelint v15+ removed all stylistic rules (spacing, formatting). Use Prettier for formatting, Stylelint for correctness.
24
+
25
+ ## Rule Anatomy
26
+
27
+ Every Stylelint rule is a function that receives options and returns a checker:
28
+
29
+ ```js
30
+ const stylelint = require("stylelint");
31
+
32
+ const ruleName = "plugin/no-important";
33
+ const messages = stylelint.utils.ruleMessages(ruleName, {
34
+ rejected: "Unexpected !important — use utility classes instead.",
35
+ });
36
+ const meta = { url: "https://example.com/rules/no-important" };
37
+
38
+ /** @type {import('stylelint').Rule} */
39
+ const ruleFunction = (primary, secondary, context) => {
40
+ return (root, result) => {
41
+ const validOptions = stylelint.utils.validateOptions(result, ruleName, {
42
+ actual: primary,
43
+ possible: [true],
44
+ });
45
+ if (!validOptions) return;
46
+
47
+ root.walkDecls((decl) => {
48
+ if (decl.important) {
49
+ stylelint.utils.report({
50
+ message: messages.rejected,
51
+ node: decl,
52
+ result,
53
+ ruleName,
54
+ });
55
+ }
56
+ });
57
+ };
58
+ };
59
+
60
+ ruleFunction.ruleName = ruleName;
61
+ ruleFunction.messages = messages;
62
+ ruleFunction.meta = meta;
63
+
64
+ module.exports = stylelint.createPlugin(ruleName, ruleFunction);
65
+ ```
66
+
67
+ ### Key concepts
68
+
69
+ | Concept | Purpose | Notes |
70
+ | ----------------------------------- | ------------------------------------------ | --------------------------------------------------------- |
71
+ | `primary` | First option (usually `true` or a pattern) | What the user passes in config |
72
+ | `secondary` | Second option (usually an object) | Additional configuration |
73
+ | `context` | Execution context | Contains `fix` boolean for auto-fix mode |
74
+ | `root` | PostCSS AST root node | Entry point for walking the CSS tree |
75
+ | `result` | Results accumulator | Pass to `stylelint.utils.report()` |
76
+ | `stylelint.utils.report()` | Report a violation | Canonical way to report — handles severity, ignores, etc. |
77
+ | `stylelint.utils.validateOptions()` | Validate rule config | Returns false if config is invalid |
78
+
79
+ ### PostCSS AST node types
80
+
81
+ | Node type | What it represents | Walker method | Example |
82
+ | ------------- | ----------------------- | --------------------- | --------------------------- |
83
+ | `Root` | Entire stylesheet | — | Top-level container |
84
+ | `Rule` | Selector + declarations | `root.walkRules()` | `.button { color: red }` |
85
+ | `Declaration` | Property: value pair | `root.walkDecls()` | `color: red` |
86
+ | `AtRule` | `@` rule | `root.walkAtRules()` | `@media (min-width: 768px)` |
87
+ | `Comment` | CSS comment | `root.walkComments()` | `/* comment */` |
88
+
89
+ Properties on `Declaration`: `decl.prop` (property name), `decl.value` (value string), `decl.important` (boolean).
90
+
91
+ ## Configuration
92
+
93
+ ### `.stylelintrc.json`
94
+
95
+ ```json
96
+ {
97
+ "extends": ["stylelint-config-standard"],
98
+ "plugins": ["stylelint-order"],
99
+ "rules": {
100
+ "declaration-no-important": true,
101
+ "selector-max-specificity": "0,3,0",
102
+ "order/properties-alphabetical-order": true
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### `stylelint.config.js` (flat config)
108
+
109
+ ```js
110
+ module.exports = {
111
+ extends: ["stylelint-config-standard"],
112
+ rules: {
113
+ "declaration-no-important": true,
114
+ },
115
+ overrides: [
116
+ {
117
+ files: ["**/*.scss"],
118
+ extends: ["stylelint-config-standard-scss"],
119
+ },
120
+ ],
121
+ };
122
+ ```
123
+
124
+ ### Key config concepts
125
+
126
+ - **`extends`** — inherit from shared configs (order matters — later overrides earlier)
127
+ - **`plugins`** — load additional rule packages
128
+ - **`rules`** — enable/disable/configure individual rules (`true`, `null`, or `[value, options]`)
129
+ - **`overrides`** — per-file rule configuration (like ESLint overrides)
130
+
131
+ ## Testing Rules
132
+
133
+ ```js
134
+ const { lint } = require("stylelint");
135
+
136
+ async function testRule() {
137
+ const result = await lint({
138
+ code: "a { color: pink !important; }",
139
+ config: {
140
+ plugins: ["./plugin-no-important.js"],
141
+ rules: { "plugin/no-important": true },
142
+ },
143
+ });
144
+
145
+ console.log(result.results[0].warnings);
146
+ // [{ rule: "plugin/no-important", text: "Unexpected !important...", line: 1, column: 19 }]
147
+ }
148
+ ```
149
+
150
+ For plugin development, use `jest-preset-stylelint` or test via `lint()` directly.
151
+
152
+ **Testing best practices:**
153
+
154
+ 1. **Test with `lint()` API** — canonical method, works with any test runner
155
+ 2. **Test valid and invalid cases** — ensure no false positives
156
+ 3. **Test with SCSS/Less** if the plugin should support preprocessor syntax
157
+ 4. **Test `overrides`** — if the rule has options, test each configuration
158
+
159
+ ### vigiles enforce() reference
160
+
161
+ ```typescript
162
+ enforce("stylelint/declaration-no-important", "Use utility classes instead.");
163
+ enforce(
164
+ "stylelint/selector-max-specificity",
165
+ "Keep specificity low for maintainability.",
166
+ );
167
+ ```
168
+
169
+ vigiles loads Stylelint config via `createLinter` + `getConfigForFile`, checks if the rule value is not `null`.
170
+
171
+ ## Edge Cases and Gotchas
172
+
173
+ ### CSS-in-JS
174
+
175
+ Stylelint supports CSS-in-JS via custom syntaxes:
176
+
177
+ ```json
178
+ {
179
+ "overrides": [
180
+ {
181
+ "files": ["**/*.{js,jsx,ts,tsx}"],
182
+ "customSyntax": "@stylelint/postcss-css-in-js"
183
+ }
184
+ ]
185
+ }
186
+ ```
187
+
188
+ Note: `@stylelint/postcss-css-in-js` is deprecated as of Stylelint v15. For styled-components / emotion, consider `postcss-styled-syntax` or lint extracted CSS instead.
189
+
190
+ ### SCSS / Less
191
+
192
+ Use dedicated configs:
193
+
194
+ - SCSS: `stylelint-config-standard-scss` (includes `stylelint-scss` plugin)
195
+ - Less: `postcss-less` as `customSyntax`
196
+
197
+ SCSS nesting (`&-modifier`) and mixins may trigger false positives in standard rules. The SCSS config handles this.
198
+
199
+ ### Prettier conflicts (v15+)
200
+
201
+ Stylelint v15 removed all stylistic rules. If upgrading from v14:
202
+
203
+ - Remove `stylelint-config-prettier` (no longer needed)
204
+ - Remove manual disables of formatting rules (`indentation`, `string-quotes`, etc.)
205
+ - Stylelint now handles correctness only; Prettier handles formatting
206
+
207
+ ### Property order plugins
208
+
209
+ `stylelint-order` and `stylelint-config-clean-order` can conflict. Use one ordering strategy:
210
+
211
+ - Alphabetical: `order/properties-alphabetical-order`
212
+ - Grouped: `order/properties-order` with a custom group list
213
+ - Clean order: extend `stylelint-config-clean-order` (opinionated groups)
214
+
215
+ ### `extends` order matters
216
+
217
+ ```json
218
+ {
219
+ "extends": ["stylelint-config-standard", "stylelint-config-prettier"]
220
+ }
221
+ ```
222
+
223
+ Later configs override earlier ones. Put base configs first, overrides last.
224
+
225
+ ### Monorepo considerations
226
+
227
+ - Stylelint resolves config from the file being linted, walking up directories
228
+ - Each package can have its own `.stylelintrc.json`
229
+ - vigiles checks Stylelint config via `createLinter({ cwd: basePath })` — in a monorepo, run from each package root
230
+
231
+ ## Mapping PR Feedback to Rule Strategy
232
+
233
+ | PR comment pattern | Best approach |
234
+ | ---------------------------- | --------------------------------------------------------------------------------- |
235
+ | "Don't use !important" | `stylelint/declaration-no-important` — already exists |
236
+ | "Keep specificity low" | `stylelint/selector-max-specificity` with threshold |
237
+ | "Sort properties" | `stylelint-order` plugin — `order/properties-alphabetical-order` or custom groups |
238
+ | "Don't use ID selectors" | `stylelint/selector-max-id` — already exists |
239
+ | "Use variables for colors" | `stylelint-declaration-strict-value` plugin |
240
+ | "No vendor prefixes" | `stylelint/property-no-vendor-prefix`, `value-no-vendor-prefix` |
241
+ | "Don't nest too deep" | `stylelint/selector-max-compound-selectors` with threshold |
242
+ | "Use modern CSS" | `stylelint/declaration-property-value-no-unknown` + browserslist |
243
+ | "Remove empty blocks" | `stylelint/block-no-empty` — already exists |
244
+ | "Don't duplicate properties" | `stylelint/declaration-block-no-duplicate-properties` — already exists |
245
+ | "Use shorthand" | `stylelint/declaration-block-no-redundant-longhand-properties` — already exists |
246
+ | "Font naming convention" | `stylelint/font-family-name-quotes` — already exists |
247
+ | "No unknown @rules" | `stylelint/at-rule-no-unknown` (or `scss/at-rule-no-unknown` for SCSS) |
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: migrate-to-spec
3
+ description: Convert an existing hand-written CLAUDE.md into a typed .spec.ts file for incremental adoption
4
+ disable-model-invocation: true
5
+ argument-hint: <path to CLAUDE.md, defaults to CLAUDE.md>
6
+ ---
7
+
8
+ Convert an existing hand-written CLAUDE.md (or AGENTS.md) into a typed `CLAUDE.md.spec.ts` file. This is the incremental adoption path — you keep your existing instruction file as the starting point and get type safety going forward.
9
+
10
+ ## Instructions
11
+
12
+ ### Step 1: Read the Existing File
13
+
14
+ Read the target instruction file (default: `CLAUDE.md` in the repo root). If the user specified a path, use that.
15
+
16
+ Also check if vigiles is installed: look for `vigiles` in `package.json` devDependencies. If not, suggest:
17
+
18
+ ```bash
19
+ npm install -D vigiles
20
+ ```
21
+
22
+ ### Step 2: Parse the Structure
23
+
24
+ Identify these sections in the markdown:
25
+
26
+ - **Commands** — lines like `` `npm run build` — description `` or ``- `command` — description``
27
+ - **Key files** — lines like `` `src/foo.ts` — description `` listing important files
28
+ - **Rules** — `###` headings with `**Enforced by:**` or `**Guidance only**` annotations
29
+ - **Prose sections** — everything else (positioning, architecture, principles, etc.)
30
+
31
+ For each rule, classify it:
32
+
33
+ - Has `**Enforced by:** \`linter/rule\``→`enforce("linter/rule", "why")`
34
+ - Has `**Enforced by:** \`code-review\``or similar non-linter →`guidance("...")`
35
+ - Has `**Guidance only**` → `guidance("...")`
36
+ - Has no annotation → mark as TODO for the user to classify
37
+
38
+ ### Step 3: Generate the Spec File
39
+
40
+ Create `CLAUDE.md.spec.ts` (or the appropriate name based on the source file) with this structure:
41
+
42
+ ```typescript
43
+ import {
44
+ claude,
45
+ enforce,
46
+ guidance,
47
+ check,
48
+ every,
49
+ file,
50
+ cmd,
51
+ ref,
52
+ instructions,
53
+ } from "vigiles/spec";
54
+
55
+ export default claude({
56
+ sections: {
57
+ // Prose sections here
58
+ },
59
+
60
+ keyFiles: {
61
+ // Key files here
62
+ },
63
+
64
+ commands: {
65
+ // Commands here
66
+ },
67
+
68
+ rules: {
69
+ // Rules here
70
+ },
71
+ });
72
+ ```
73
+
74
+ **Important guidelines:**
75
+
76
+ - Use `file()` refs in sections where file paths appear in backticks — this enables stale reference detection
77
+ - Use `cmd()` refs for any `npm run` commands mentioned in sections
78
+ - Convert `**Enforced by:** \`code-review\``rules to`guidance()` — code review is not a mechanical enforcement
79
+ - For rules with no annotation, add a `// TODO: classify as enforce() or guidance()` comment
80
+ - Keep rule IDs as kebab-case versions of the heading text
81
+ - Preserve the `**Why:**` text as the second argument to `enforce()` or `guidance()`
82
+ - If sections reference other files or skills, use `ref()` for cross-references
83
+
84
+ ### Step 4: Verify the Spec Compiles
85
+
86
+ Run:
87
+
88
+ ```bash
89
+ npm run build
90
+ npx vigiles compile CLAUDE.md.spec.ts
91
+ ```
92
+
93
+ Compare the compiled output against the original file. Key differences are expected (formatting, section ordering), but all rules, commands, key files, and prose content should be preserved.
94
+
95
+ ### Step 5: Present the Result
96
+
97
+ Show the user:
98
+
99
+ 1. The generated spec file
100
+ 2. How many rules were converted (enforce vs guidance vs TODO)
101
+ 3. How many file/cmd refs were added for stale reference detection
102
+ 4. The command to compile: `npx vigiles compile`
103
+ 5. The command to verify: `npx vigiles check`
104
+
105
+ Ask if they want you to write the file. If yes, also suggest adding to `.gitignore` or updating CI to run `vigiles compile` and `vigiles check`.
106
+
107
+ ### Step 6: Optional — Set Up CI
108
+
109
+ If the user wants CI integration, suggest adding to their GitHub Actions workflow:
110
+
111
+ ```yaml
112
+ - name: Compile specs
113
+ run: npx vigiles compile
114
+ - name: Verify integrity
115
+ run: npx vigiles check
116
+ ```
117
+
118
+ Or using the vigiles GitHub Action:
119
+
120
+ ```yaml
121
+ - uses: zernie/vigiles@main
122
+ with:
123
+ command: check
124
+ ```
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: pr-to-lint-rule
3
+ description: Convert a recurring PR review comment into an automated lint rule with tests and spec entry
4
+ disable-model-invocation: true
5
+ argument-hint: <description of recurring PR feedback>
6
+ ---
7
+
8
+ Convert a recurring PR review comment into an automated lint rule.
9
+
10
+ ## Arguments
11
+
12
+ $ARGUMENTS — A natural language description of the pattern to enforce. Examples:
13
+
14
+ - "we keep telling people not to import directly from antd, use our design system barrel file instead"
15
+ - "people forget to use our custom logger instead of console.log"
16
+ - "don't use unwrap() in production code, use expect() or proper error handling"
17
+ - "API route handlers must use the withAuth wrapper"
18
+
19
+ ## Instructions
20
+
21
+ You are generating an automated lint rule from a recurring code review pattern. Follow these steps:
22
+
23
+ ### Step 1: Detect the Project Language and Toolchain
24
+
25
+ Look at the repository to determine:
26
+
27
+ - **Primary language** (JS/TS, Python, Rust, Go, Ruby, etc.)
28
+ - **Linter in use** (ESLint, Ruff, Clippy, golangci-lint, RuboCop, etc.)
29
+ - **Testing framework** (Vitest, Jest, pytest, cargo test, etc.)
30
+ - **Existing custom rules** (to match conventions)
31
+
32
+ **If the language or linter cannot be confidently detected** (e.g. polyglot repo, no linter config, or multiple candidates), **ask the user** which language and linter to target before generating anything.
33
+
34
+ ### Step 2: Generate the Lint Rule
35
+
36
+ Based on the detected (or user-specified) language, generate the appropriate rule type:
37
+
38
+ **Read the linter-specific reference doc before generating.** Each doc covers existing plugins to check first, rule/lint anatomy, AST patterns, auto-fix safety, testing, and edge cases.
39
+
40
+ | Language | Linter | Reference doc |
41
+ | --------------------- | --------- | ----------------------------- |
42
+ | JavaScript/TypeScript | ESLint | `../linter-docs/eslint.md` |
43
+ | Python | Ruff | `../linter-docs/ruff.md` |
44
+ | Python | Pylint | `../linter-docs/pylint.md` |
45
+ | Ruby | RuboCop | `../linter-docs/rubocop.md` |
46
+ | Rust | Clippy | `../linter-docs/clippy.md` |
47
+ | CSS | Stylelint | `../linter-docs/stylelint.md` |
48
+
49
+ For all linters, follow this order:
50
+
51
+ 1. **Check existing plugins/rules first** — see the plugin table in the linter doc
52
+ 2. **Try built-in config options** — most linters have `no-restricted-*` or equivalent rules that handle one-off patterns without custom code
53
+ 3. **Only write a custom rule** when you need AST analysis, auto-fix, or configurable options beyond what exists
54
+
55
+ If a custom rule is needed, the reference doc provides: rule anatomy, AST node cheat sheet, auto-fix/suggest patterns, testing examples, and registration instructions.
56
+
57
+ #### For Go (go/analysis)
58
+
59
+ No linter doc yet. Generate an analyzer using `golang.org/x/tools/go/analysis` with `analysistest` tests.
60
+
61
+ #### For other languages
62
+
63
+ Generate the most idiomatic linting approach with test cases and integration instructions.
64
+
65
+ ### Step 3: Add to Instruction File
66
+
67
+ **If the project uses v2 specs** (has `CLAUDE.md.spec.ts`):
68
+
69
+ Add an `enforce()` rule to the spec file:
70
+
71
+ ```typescript
72
+ "<rule-id>": enforce("<linter>/<rule-name>", "<why>"),
73
+ ```
74
+
75
+ Then run `npx vigiles compile` to regenerate CLAUDE.md.
76
+
77
+ **If the project uses v1** (hand-written CLAUDE.md):
78
+
79
+ Append an annotation block:
80
+
81
+ ```markdown
82
+ ### <Rule title — imperative, concise>
83
+
84
+ **Enforced by:** `<linter>/<rule-name>`
85
+ **Why:** <One sentence explaining the architectural reason>
86
+ ```
87
+
88
+ ### Step 4: Present the Output
89
+
90
+ Show the user:
91
+
92
+ 1. All generated files with full contents
93
+ 2. Step-by-step integration instructions
94
+ 3. The spec rule or CLAUDE.md block to add
95
+ 4. How to verify it works (run the linter, expect it to catch a violation)
96
+
97
+ Ask the user if they want you to write the files and update the spec/CLAUDE.md.
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: strengthen
3
+ description: Upgrade guidance() rules to enforce() by finding existing linter rules that match
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ Scan spec files for `guidance()` rules and suggest `enforce()` replacements backed by real linter rules.
8
+
9
+ ## Instructions
10
+
11
+ ### Step 0: Choose Mode
12
+
13
+ Ask the user:
14
+
15
+ > **Auto or interactive?**
16
+ >
17
+ > - **Auto** — I'll apply all safe changes (direct replacements where the rule is already enabled), commit, and show you the diff. Risky changes (require config edits or plugin installs) go in a summary for you to review.
18
+ > - **Interactive** — I'll present each suggestion and you pick which ones to apply.
19
+
20
+ Default to interactive if the user doesn't specify.
21
+
22
+ ### Step 1: Discover What's Installed
23
+
24
+ Run `npx vigiles generate-types` to get the full list of enabled linter rules in the project. Read `.vigiles/generated.d.ts` to see every rule available across all detected linters.
25
+
26
+ Note which linter prefixes appear in the generated types (e.g., `EslintRule`, `RuffRule`). You'll only need reference docs for detected linters.
27
+
28
+ ### Step 2: Find All Guidance Rules
29
+
30
+ Find all `.spec.ts` files in the project (`**/*.md.spec.ts`). For each file, identify every `guidance()` rule.
31
+
32
+ ### Step 3: Match Against Generated Types (Fast Path)
33
+
34
+ For each guidance rule, check if an enabled rule in `.vigiles/generated.d.ts` directly matches. This is the fast, deterministic path — no doc reading needed.
35
+
36
+ Look for:
37
+
38
+ - **Exact rule name in text** — guidance says "no-console" and `no-console` is in EslintRule
39
+ - **Semantic match** — guidance says "don't use console.log" and `no-console` is available
40
+ - **Rule description match** — guidance says "unused variables" and `no-unused-vars` or `@typescript-eslint/no-unused-vars` is available
41
+
42
+ If a match is found and the rule is in the generated types (meaning it's already enabled), this is a **direct replacement** — no config changes needed.
43
+
44
+ ### Step 4: Read Linter Docs (Slow Path)
45
+
46
+ For guidance rules that didn't match in Step 3, read the linter reference docs for the project's detected linters:
47
+
48
+ - ESLint → `../linter-docs/eslint.md`
49
+ - Stylelint → `../linter-docs/stylelint.md`
50
+ - Ruff → `../linter-docs/ruff.md`
51
+ - Pylint → `../linter-docs/pylint.md`
52
+ - RuboCop → `../linter-docs/rubocop.md`
53
+ - Clippy → `../linter-docs/clippy.md`
54
+
55
+ **Only read docs for linters the project actually uses.** Skip docs for linters with no rules in generated types.
56
+
57
+ Check the plugin tables and decision matrices. The guidance text may describe a pattern covered by:
58
+
59
+ - A plugin rule that's installed but not enabled
60
+ - A plugin that's not installed yet
61
+ - A `no-restricted-*` config pattern (see Step 4b)
62
+
63
+ ### Step 4b: `no-restricted-*` Patterns
64
+
65
+ Many guidance rules can be enforced via built-in linter config without a custom rule. This is the most common strengthen pattern — "don't do X" maps to a restriction config.
66
+
67
+ **ESLint:**
68
+
69
+ ```js
70
+ // "Don't import from internal modules"
71
+ "no-restricted-imports": ["error", {
72
+ patterns: [{ group: ["src/internal/*"], message: "Use the public API." }]
73
+ }],
74
+
75
+ // "Don't call console.log"
76
+ "no-restricted-syntax": ["error", {
77
+ selector: 'CallExpression[callee.object.name="console"]',
78
+ message: "Use the project logger."
79
+ }],
80
+
81
+ // "Don't use moment.js"
82
+ "no-restricted-imports": ["error", {
83
+ paths: [{ name: "moment", message: "Use dayjs instead." }]
84
+ }],
85
+ ```
86
+
87
+ **Ruff:**
88
+
89
+ ```toml
90
+ # "Don't use os.system"
91
+ [tool.ruff.lint.flake8-tidy-imports.banned-api]
92
+ "os.system".msg = "Use subprocess.run instead."
93
+ ```
94
+
95
+ **RuboCop:**
96
+
97
+ ```yaml
98
+ # "Don't use puts in production" — if Rails/Output doesn't fit
99
+ Custom/NoPuts:
100
+ Enabled: true
101
+ ```
102
+
103
+ When suggesting a `no-restricted-*` change:
104
+
105
+ 1. Show the exact config edit needed (which file, which section)
106
+ 2. Show the `enforce()` rule that references it
107
+ 3. Note that this changes linter config, not just the spec
108
+
109
+ ### Step 5: Present Suggestions
110
+
111
+ Group the output into tiers:
112
+
113
+ **Tier 1: Direct replacements** (rule already enabled — zero risk)
114
+
115
+ ```typescript
116
+ // Before
117
+ "no-console": guidance("Use structured logger instead of console.log"),
118
+ // After
119
+ "no-console": enforce("eslint/no-console", "Use structured logger instead of console.log"),
120
+ ```
121
+
122
+ **Tier 2: Config-backed** (rule exists but needs config options)
123
+
124
+ ```typescript
125
+ // Spec change:
126
+ "no-moment": enforce("eslint/no-restricted-imports", "Use dayjs instead of moment."),
127
+
128
+ // Config change needed (eslint.config.mjs):
129
+ "no-restricted-imports": ["error", {
130
+ paths: [{ name: "moment", message: "Use dayjs instead." }]
131
+ }],
132
+ ```
133
+
134
+ **Tier 3: Plugin install needed**
135
+
136
+ ```
137
+ "cognitive-complexity": guidance("Keep functions simple")
138
+ → Install eslint-plugin-sonarjs, enable sonarjs/cognitive-complexity
139
+ → enforce("eslint/sonarjs/cognitive-complexity", "Keep functions simple")
140
+ ```
141
+
142
+ **Tier 4: No match** (stays as guidance, or candidate for `/pr-to-lint-rule`)
143
+
144
+ ```
145
+ "research-first": guidance("Google unfamiliar APIs first.")
146
+ → No linter rule can enforce this. Stays as guidance.
147
+ → Want me to run /pr-to-lint-rule to create a custom rule?
148
+ ```
149
+
150
+ ### Step 6: Apply Changes
151
+
152
+ **In auto mode:**
153
+
154
+ 1. Apply all Tier 1 changes (edit spec files, replace `guidance()` with `enforce()`)
155
+ 2. Run `npm run build && npx vigiles compile` to verify each change compiles
156
+ 3. If any compilation fails, revert that specific change and report the error
157
+ 4. Commit all successful changes
158
+ 5. Present Tier 2-4 as a summary for the user to review
159
+
160
+ **In interactive mode:**
161
+
162
+ 1. Present all tiers
163
+ 2. Ask the user which suggestions to apply
164
+ 3. For approved Tier 2 changes: edit the linter config, then edit the spec
165
+ 4. Run `npm run build && npx vigiles compile` to verify
166
+ 5. If compilation fails, report the error and revert
167
+
168
+ **For Tier 4 (no match):** Ask the user if they want to run `/pr-to-lint-rule` for any of the unmatched rules to create custom rules.