vigiles 2.5.0 → 3.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-plugin/plugin.json +9 -0
- package/README.md +73 -434
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
- package/dist/adapters/claude-code/egress-proxy.js +60 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
- package/dist/adapters/claude-code/eval-baseline.js +173 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/adapters/claude-code/run-hook.d.ts +204 -0
- package/dist/adapters/claude-code/run-hook.js +418 -0
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claude-code.js +32 -0
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +60 -11
- package/dist/harness-assert.js +111 -9
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +35 -16
- 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 +103 -0
- package/skills/generate-rule/SKILL.md +64 -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 +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-hook.d.ts +0 -77
- package/dist/run-hook.js +0 -80
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Ruff — 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 Rules First
|
|
6
|
+
|
|
7
|
+
Ruff reimplements 800+ rules from flake8, pylint, isort, pyupgrade, and others. Before writing a custom rule, check if Ruff already covers it:
|
|
8
|
+
|
|
9
|
+
| Prefix | Source | Key rules |
|
|
10
|
+
| --------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `E` / `W` | pycodestyle | `E501` (line-too-long), `W291` (trailing-whitespace), `E711` (none-comparison) |
|
|
12
|
+
| `F` | pyflakes | `F401` (unused-import), `F841` (unused-variable), `F811` (redefined-unused-name) |
|
|
13
|
+
| `I` | isort | `I001` (unsorted-imports), `I002` (missing-required-import) |
|
|
14
|
+
| `N` | pep8-naming | `N801` (invalid-class-name), `N802` (invalid-function-name), `N806` (non-lowercase-variable) |
|
|
15
|
+
| `UP` | pyupgrade | `UP006` (non-pep585-annotation), `UP007` (non-pep604-annotation), `UP035` (deprecated-import) |
|
|
16
|
+
| `S` | flake8-bandit | `S101` (assert), `S105` (hardcoded-password-string), `S301` (suspicious-pickle-usage) |
|
|
17
|
+
| `B` | flake8-bugbear | `B006` (mutable-argument-default), `B007` (unused-loop-control-variable), `B905` (zip-without-explicit-strict) |
|
|
18
|
+
| `A` | flake8-builtins | `A001` (builtin-variable-shadowing), `A002` (builtin-argument-shadowing) |
|
|
19
|
+
| `C4` | flake8-comprehensions | `C400` (unnecessary-generator-list), `C401` (unnecessary-generator-set), `C408` (unnecessary-collection-call) |
|
|
20
|
+
| `T20` | flake8-print | `T201` (print), `T203` (pprint) |
|
|
21
|
+
| `SIM` | flake8-simplify | `SIM102` (collapsible-if), `SIM108` (if-else-block-instead-of-if-exp), `SIM110` (reimplemented-builtin) |
|
|
22
|
+
| `PL` | pylint | `PLC0414` (useless-import-alias), `PLE1205` (logging-too-many-args), `PLR0913` (too-many-arguments), `PLW0602` (global-variable-not-assigned) |
|
|
23
|
+
| `PTH` | flake8-use-pathlib | `PTH100` (os-path-abspath), `PTH118` (os-path-join), `PTH123` (builtin-open) |
|
|
24
|
+
| `RUF` | ruff-specific | `RUF001` (ambiguous-unicode-character), `RUF005` (collection-literal-concatenation), `RUF013` (implicit-optional) |
|
|
25
|
+
| `D` | pydocstyle | `D100` (undocumented-public-module), `D103` (undocumented-public-function), `D400` (first-line-should-end-with-period) |
|
|
26
|
+
| `ANN` | flake8-annotations | `ANN001` (missing-type-function-argument), `ANN201` (missing-return-type-public-function) |
|
|
27
|
+
| `ARG` | flake8-unused-arguments | `ARG001` (unused-function-argument), `ARG002` (unused-method-argument) |
|
|
28
|
+
| `ERA` | eradicate | `ERA001` (commented-out-code) |
|
|
29
|
+
| `TCH` | flake8-type-checking | `TCH001` (typing-only-first-party-import), `TCH002` (typing-only-third-party-import) |
|
|
30
|
+
| `FBT` | flake8-boolean-trap | `FBT001` (boolean-typed-positional-argument), `FBT002` (boolean-default-value-positional-argument) |
|
|
31
|
+
| `ICN` | flake8-import-conventions | `ICN001` (unconventional-import-alias) — e.g., `import numpy as np` |
|
|
32
|
+
| `PIE` | flake8-pie | `PIE790` (unnecessary-placeholder), `PIE804` (no-unnecessary-dict-kwargs) |
|
|
33
|
+
| `RSE` | flake8-raise | `RSE102` (unnecessary-paren-on-raise-exception) |
|
|
34
|
+
| `RET` | flake8-return | `RET501` (unnecessary-return-none), `RET504` (unnecessary-assign) |
|
|
35
|
+
| `TID` | flake8-tidy-imports | `TID252` (relative-imports) — ban relative imports |
|
|
36
|
+
| `PERF` | perflint | `PERF101` (unnecessary-list-cast), `PERF401` (manual-list-comprehension) |
|
|
37
|
+
| `FURB` | refurb | `FURB105` (print-empty-string), `FURB118` (reimplemented-operator) |
|
|
38
|
+
|
|
39
|
+
**Tip:** Run `ruff rule <CODE>` to see the full description of any rule. Run `ruff linter` to see all available rule groups.
|
|
40
|
+
|
|
41
|
+
## Rule Selection
|
|
42
|
+
|
|
43
|
+
Configure in `pyproject.toml`:
|
|
44
|
+
|
|
45
|
+
```toml
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = [
|
|
48
|
+
"E", "W", # pycodestyle
|
|
49
|
+
"F", # pyflakes
|
|
50
|
+
"I", # isort
|
|
51
|
+
"B", # flake8-bugbear
|
|
52
|
+
"S", # flake8-bandit
|
|
53
|
+
"UP", # pyupgrade
|
|
54
|
+
"SIM", # flake8-simplify
|
|
55
|
+
"T20", # flake8-print
|
|
56
|
+
"RUF", # ruff-specific
|
|
57
|
+
]
|
|
58
|
+
ignore = [
|
|
59
|
+
"E501", # line-too-long (handled by formatter)
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint.per-file-ignores]
|
|
63
|
+
"tests/**" = ["S101"] # allow assert in tests
|
|
64
|
+
"__init__.py" = ["F401"] # allow unused imports in __init__
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or in `ruff.toml` (same syntax without `[tool.ruff]` prefix):
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
[lint]
|
|
71
|
+
select = ["E", "F", "I", "B"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Selecting individual rules vs groups
|
|
75
|
+
|
|
76
|
+
- `"F"` enables all pyflakes rules
|
|
77
|
+
- `"F401"` enables only `F401`
|
|
78
|
+
- `"ALL"` enables everything (noisy — use `ignore` to subtract)
|
|
79
|
+
- Use `extend-select` to add rules without overriding the default set
|
|
80
|
+
|
|
81
|
+
## Custom Rules
|
|
82
|
+
|
|
83
|
+
**Ruff does not support user-defined rules.** If no existing rule covers your pattern:
|
|
84
|
+
|
|
85
|
+
1. **Configure existing rules** — many rules accept options (e.g., `flake8-import-conventions` lets you set allowed aliases)
|
|
86
|
+
2. **Use `ruff.lint.flake8-import-conventions.aliases`** — for import alias enforcement
|
|
87
|
+
3. **Use `ruff.lint.flake8-tidy-imports.banned-api`** — for banning specific imports/modules
|
|
88
|
+
4. **Use ast-grep** — for arbitrary AST pattern matching, reference via `enforce()` with an ast-grep rule
|
|
89
|
+
5. **Write a Pylint checker** — if you need the full power of a custom rule with AST analysis, use Pylint (Ruff can coexist with Pylint in CI)
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
# Ban specific APIs without a custom rule
|
|
93
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
94
|
+
"os.system".msg = "Use subprocess.run instead."
|
|
95
|
+
"typing.Dict".msg = "Use dict instead (PEP 585)."
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Auto-Fix
|
|
99
|
+
|
|
100
|
+
Ruff provides auto-fix for many rules:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ruff check --fix # apply safe fixes only
|
|
104
|
+
ruff check --fix --unsafe-fixes # include unsafe fixes
|
|
105
|
+
ruff check --fix-only # only fix, don't report remaining violations
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Safe vs unsafe fixes:**
|
|
109
|
+
|
|
110
|
+
- **Safe** — guaranteed to not change semantics (e.g., removing unused imports)
|
|
111
|
+
- **Unsafe** — may change semantics (e.g., `UP007` rewriting `Optional[X]` to `X | None` can break runtime type checking)
|
|
112
|
+
|
|
113
|
+
Check fixability per rule with `ruff rule <CODE>` — it shows whether the rule has a fix and if it's safe.
|
|
114
|
+
|
|
115
|
+
### vigiles enforce() reference
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
enforce("ruff/F401", "Remove unused imports.");
|
|
119
|
+
enforce("ruff/T201", "Use logging module instead of print.");
|
|
120
|
+
enforce("ruff/S101", "Don't use assert in production code.");
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
vigiles checks `ruff rule <CODE>` and verifies the rule exists, then parses `ruff check --show-settings` to confirm it's enabled.
|
|
124
|
+
|
|
125
|
+
## Edge Cases and Gotchas
|
|
126
|
+
|
|
127
|
+
### Ruff vs Pylint (PL prefix)
|
|
128
|
+
|
|
129
|
+
Ruff reimplements many Pylint rules with the `PL` prefix. The mapping is not 1:1 — some Pylint rules have no Ruff equivalent, and Ruff's implementations may differ in edge cases.
|
|
130
|
+
|
|
131
|
+
| Ruff code | Pylint equivalent | Notes |
|
|
132
|
+
| --------- | -------------------------------------- | ------------- |
|
|
133
|
+
| `PLR0913` | `R0913` (too-many-arguments) | Same behavior |
|
|
134
|
+
| `PLC0414` | `C0414` (useless-import-alias) | Same behavior |
|
|
135
|
+
| `PLW0602` | `W0602` (global-variable-not-assigned) | Same behavior |
|
|
136
|
+
| `PLE1205` | `E1205` (logging-too-many-args) | Same behavior |
|
|
137
|
+
|
|
138
|
+
If the project uses both Ruff and Pylint, disable Pylint rules that Ruff already covers to avoid duplicate reports.
|
|
139
|
+
|
|
140
|
+
### Preview rules
|
|
141
|
+
|
|
142
|
+
Some rules are behind `--preview` flag. These are not stable and may change between versions. Don't use `enforce()` on preview rules unless you pin the Ruff version.
|
|
143
|
+
|
|
144
|
+
```toml
|
|
145
|
+
[tool.ruff]
|
|
146
|
+
preview = true # enables preview rules
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Formatter vs linter conflicts
|
|
150
|
+
|
|
151
|
+
Ruff has both a linter (`ruff check`) and a formatter (`ruff format`). Some linter rules conflict with the formatter:
|
|
152
|
+
|
|
153
|
+
- `E501` (line-too-long) — the formatter handles line length; disable in the linter
|
|
154
|
+
- `W291`/`W292`/`W293` — whitespace rules handled by formatter
|
|
155
|
+
- `COM812` (missing-trailing-comma) — conflicts with formatter in some cases
|
|
156
|
+
|
|
157
|
+
The Ruff docs recommend: `ignore = ["E501", "W291", "W292", "W293"]` when using `ruff format`.
|
|
158
|
+
|
|
159
|
+
### Monorepo config inheritance
|
|
160
|
+
|
|
161
|
+
Ruff resolves config by walking up from the file being linted. In a monorepo:
|
|
162
|
+
|
|
163
|
+
- Each package can have its own `pyproject.toml` with `[tool.ruff]`
|
|
164
|
+
- A root config applies to all packages unless overridden
|
|
165
|
+
- Use `extend` to inherit from a shared config: `extend = "../../pyproject.toml"`
|
|
166
|
+
|
|
167
|
+
vigiles discovers Ruff config at `basePath` only. In a monorepo, run vigiles from each package root.
|
|
168
|
+
|
|
169
|
+
## Mapping PR Feedback to Rule Strategy
|
|
170
|
+
|
|
171
|
+
| PR comment pattern | Best approach |
|
|
172
|
+
| ----------------------------- | ------------------------------------------------------------------ |
|
|
173
|
+
| "Remove unused imports" | `ruff/F401` — already exists |
|
|
174
|
+
| "Don't use print()" | `ruff/T201` — already exists |
|
|
175
|
+
| "Sort your imports" | `ruff/I001` — already exists |
|
|
176
|
+
| "Use pathlib not os.path" | `ruff/PTH*` — enable the PTH group |
|
|
177
|
+
| "Don't use assert in prod" | `ruff/S101` — already exists, ignore in tests via per-file-ignores |
|
|
178
|
+
| "Remove commented-out code" | `ruff/ERA001` — already exists |
|
|
179
|
+
| "Add type annotations" | `ruff/ANN*` — enable the ANN group |
|
|
180
|
+
| "Use comprehensions" | `ruff/C4*` — enable the C4 group |
|
|
181
|
+
| "Don't shadow builtins" | `ruff/A001` / `A002` — already exists |
|
|
182
|
+
| "Simplify this if" | `ruff/SIM102` / `SIM108` — already exists |
|
|
183
|
+
| "Don't use mutable defaults" | `ruff/B006` — already exists |
|
|
184
|
+
| "Ban specific import" | `flake8-tidy-imports.banned-api` config — no custom rule needed |
|
|
185
|
+
| "Naming convention violated" | `ruff/N*` — enable the N group |
|
|
186
|
+
| "Don't use os.system" | `flake8-tidy-imports.banned-api` config |
|
|
187
|
+
| "Complex pattern not in Ruff" | Write a Pylint checker or use ast-grep |
|
|
@@ -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,126 @@
|
|
|
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
|
+
> **Don't need full TypeScript?** A typed spec is the deepest commitment level. If the user only wants verified rules without a build step, point them at markdown mode first: inline `<!-- vigiles:enforce ... -->` comments (Level 0) or a `vigiles:` YAML frontmatter block with `vigiles generate-schema` for editor autocomplete (Level 1). Both are verified by `vigiles audit` with the same engine as a spec. See `docs/markdown-mode.md`. Migrate to a spec only when they want compiler-grade guarantees.
|
|
11
|
+
|
|
12
|
+
## Instructions
|
|
13
|
+
|
|
14
|
+
### Step 1: Read the Existing File
|
|
15
|
+
|
|
16
|
+
Read the target instruction file (default: `CLAUDE.md` in the repo root). If the user specified a path, use that.
|
|
17
|
+
|
|
18
|
+
Also check if vigiles is installed: look for `vigiles` in `package.json` devDependencies. If not, suggest:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -D vigiles
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Step 2: Parse the Structure
|
|
25
|
+
|
|
26
|
+
Identify these sections in the markdown:
|
|
27
|
+
|
|
28
|
+
- **Commands** — lines like `` `npm run build` — description `` or ``- `command` — description``
|
|
29
|
+
- **Key files** — lines like `` `src/foo.ts` — description `` listing important files
|
|
30
|
+
- **Rules** — `###` headings with `**Enforced by:**` or `**Guidance only**` annotations
|
|
31
|
+
- **Prose sections** — everything else (positioning, architecture, principles, etc.)
|
|
32
|
+
|
|
33
|
+
For each rule, classify it:
|
|
34
|
+
|
|
35
|
+
- Has `**Enforced by:** \`linter/rule\``→`enforce("linter/rule", "why")`
|
|
36
|
+
- Has `**Enforced by:** \`code-review\``or similar non-linter →`guidance("...")`
|
|
37
|
+
- Has `**Guidance only**` → `guidance("...")`
|
|
38
|
+
- Has no annotation → mark as TODO for the user to classify
|
|
39
|
+
|
|
40
|
+
### Step 3: Generate the Spec File
|
|
41
|
+
|
|
42
|
+
Create `CLAUDE.md.spec.ts` (or the appropriate name based on the source file) with this structure:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import {
|
|
46
|
+
claude,
|
|
47
|
+
enforce,
|
|
48
|
+
guidance,
|
|
49
|
+
check,
|
|
50
|
+
every,
|
|
51
|
+
file,
|
|
52
|
+
cmd,
|
|
53
|
+
ref,
|
|
54
|
+
instructions,
|
|
55
|
+
} from "vigiles/spec";
|
|
56
|
+
|
|
57
|
+
export default claude({
|
|
58
|
+
sections: {
|
|
59
|
+
// Prose sections here
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
keyFiles: {
|
|
63
|
+
// Key files here
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
commands: {
|
|
67
|
+
// Commands here
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
rules: {
|
|
71
|
+
// Rules here
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Important guidelines:**
|
|
77
|
+
|
|
78
|
+
- Use `file()` refs in sections where file paths appear in backticks — this enables stale reference detection
|
|
79
|
+
- Use `cmd()` refs for any `npm run` commands mentioned in sections
|
|
80
|
+
- Convert `**Enforced by:** \`code-review\``rules to`guidance()` — code review is not a mechanical enforcement
|
|
81
|
+
- For rules with no annotation, add a `// TODO: classify as enforce() or guidance()` comment
|
|
82
|
+
- Keep rule IDs as kebab-case versions of the heading text
|
|
83
|
+
- Preserve the `**Why:**` text as the second argument to `enforce()` or `guidance()`
|
|
84
|
+
- If sections reference other files or skills, use `ref()` for cross-references
|
|
85
|
+
|
|
86
|
+
### Step 4: Verify the Spec Compiles
|
|
87
|
+
|
|
88
|
+
Run:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm run build
|
|
92
|
+
npx vigiles compile CLAUDE.md.spec.ts
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
### Step 5: Present the Result
|
|
98
|
+
|
|
99
|
+
Show the user:
|
|
100
|
+
|
|
101
|
+
1. The generated spec file
|
|
102
|
+
2. How many rules were converted (enforce vs guidance vs TODO)
|
|
103
|
+
3. How many file/cmd refs were added for stale reference detection
|
|
104
|
+
4. The command to compile: `npx vigiles compile`
|
|
105
|
+
5. The command to verify: `npx vigiles check`
|
|
106
|
+
|
|
107
|
+
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`.
|
|
108
|
+
|
|
109
|
+
### Step 6: Optional — Set Up CI
|
|
110
|
+
|
|
111
|
+
If the user wants CI integration, suggest adding to their GitHub Actions workflow:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
- name: Compile specs
|
|
115
|
+
run: npx vigiles compile
|
|
116
|
+
- name: Verify integrity
|
|
117
|
+
run: npx vigiles check
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Or using the vigiles GitHub Action:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
- uses: zernie/vigiles@main
|
|
124
|
+
with:
|
|
125
|
+
command: check
|
|
126
|
+
```
|
|
@@ -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.
|