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,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.
|