@gobing-ai/spur 0.3.41 → 0.3.43

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 (185) hide show
  1. package/.claude-plugin/marketplace.json +15 -0
  2. package/README.md +15 -1
  3. package/config/templates/AGENTS.md +5 -3
  4. package/config/workflows/task-pipeline.yaml +113 -12
  5. package/package.json +15 -12
  6. package/plugins/README.md +655 -0
  7. package/plugins/sp/README.md +621 -0
  8. package/plugins/sp/agents/expert-spur.md +109 -0
  9. package/plugins/sp/agents/super-coder.md +126 -0
  10. package/plugins/sp/agents/super-planner.md +283 -0
  11. package/plugins/sp/agents/super-reviewer.md +154 -0
  12. package/plugins/sp/commands/dev-arch.md +30 -0
  13. package/plugins/sp/commands/dev-brainstorm.md +40 -0
  14. package/plugins/sp/commands/dev-changelog.md +28 -0
  15. package/plugins/sp/commands/dev-daily.md +29 -0
  16. package/plugins/sp/commands/dev-debug.md +29 -0
  17. package/plugins/sp/commands/dev-dogfood.md +33 -0
  18. package/plugins/sp/commands/dev-featurechange.md +130 -0
  19. package/plugins/sp/commands/dev-find-conflict.md +51 -0
  20. package/plugins/sp/commands/dev-find-issue.md +76 -0
  21. package/plugins/sp/commands/dev-find-next.md +60 -0
  22. package/plugins/sp/commands/dev-fixall.md +33 -0
  23. package/plugins/sp/commands/dev-gitmsg.md +28 -0
  24. package/plugins/sp/commands/dev-gtd.md +97 -0
  25. package/plugins/sp/commands/dev-handover.md +26 -0
  26. package/plugins/sp/commands/dev-idea.md +50 -0
  27. package/plugins/sp/commands/dev-next.md +31 -0
  28. package/plugins/sp/commands/dev-parallel.md +30 -0
  29. package/plugins/sp/commands/dev-plan.md +46 -0
  30. package/plugins/sp/commands/dev-refine.md +48 -0
  31. package/plugins/sp/commands/dev-refineall.md +65 -0
  32. package/plugins/sp/commands/dev-refresh.md +32 -0
  33. package/plugins/sp/commands/dev-reverse.md +32 -0
  34. package/plugins/sp/commands/dev-review.md +31 -0
  35. package/plugins/sp/commands/dev-run.md +63 -0
  36. package/plugins/sp/commands/dev-runall.md +83 -0
  37. package/plugins/sp/commands/dev-simplify.md +39 -0
  38. package/plugins/sp/commands/dev-unit.md +29 -0
  39. package/plugins/sp/commands/dev-verify.md +48 -0
  40. package/plugins/sp/commands/dev-verifyall.md +72 -0
  41. package/plugins/sp/commands/dev-wrap.md +48 -0
  42. package/plugins/sp/commands/dev-wrapall.md +51 -0
  43. package/plugins/sp/commands/rule-add.md +18 -0
  44. package/plugins/sp/commands/rule-refine.md +18 -0
  45. package/plugins/sp/commands/rule-scan.md +18 -0
  46. package/plugins/sp/commands/spur-init.md +22 -0
  47. package/plugins/sp/commands/workflow-add.md +18 -0
  48. package/plugins/sp/commands/workflow-refine.md +18 -0
  49. package/plugins/sp/hooks/agent-hint.ts +60 -0
  50. package/plugins/sp/hooks/careful-guard.ts +147 -0
  51. package/plugins/sp/hooks/context-post-tool.ts +325 -0
  52. package/plugins/sp/hooks/context-session-start.ts +182 -0
  53. package/plugins/sp/hooks/context-session-stop.ts +95 -0
  54. package/plugins/sp/hooks/hooks.json +51 -0
  55. package/plugins/sp/hooks/pi/guard-extension.ts +341 -0
  56. package/plugins/sp/hooks/task-write-guard.ts +84 -0
  57. package/plugins/sp/plugin.json +8 -0
  58. package/plugins/sp/scripts/batch-preflight.ts +204 -0
  59. package/plugins/sp/scripts/daily-summary/daily-summary.ts +606 -0
  60. package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
  61. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +363 -0
  62. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +168 -0
  63. package/plugins/sp/scripts/feature-sync-bounded.ts +451 -0
  64. package/plugins/sp/scripts/stage-registry-adapter.ts +1341 -0
  65. package/plugins/sp/scripts/task-size-precheck.ts +202 -0
  66. package/plugins/sp/scripts/validate-commands.ts +651 -0
  67. package/plugins/sp/scripts/validate-flag-contracts.ts +875 -0
  68. package/plugins/sp/skills/brainstorm/SKILL.md +361 -0
  69. package/plugins/sp/skills/brainstorm/agents/openai.yaml +14 -0
  70. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +209 -0
  71. package/plugins/sp/skills/brainstorm/metadata.openclaw +7 -0
  72. package/plugins/sp/skills/brainstorm/references/grilling-interview.md +68 -0
  73. package/plugins/sp/skills/brainstorm/references/workflows.md +255 -0
  74. package/plugins/sp/skills/branch-workflow/SKILL.md +109 -0
  75. package/plugins/sp/skills/branch-workflow/references/branch-lifecycle.md +69 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +100 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +203 -0
  78. package/plugins/sp/skills/code-implementation/references/debugging.md +64 -0
  79. package/plugins/sp/skills/code-implementation/references/implementation-patterns.md +113 -0
  80. package/plugins/sp/skills/code-improvement/SKILL.md +231 -0
  81. package/plugins/sp/skills/code-improvement/references/deepening-signals.md +205 -0
  82. package/plugins/sp/skills/code-review/SKILL.md +112 -0
  83. package/plugins/sp/skills/code-review/references/receiving-code-review.md +70 -0
  84. package/plugins/sp/skills/code-review/references/review-lenses.md +117 -0
  85. package/plugins/sp/skills/code-review/references/self-review-checklist.md +53 -0
  86. package/plugins/sp/skills/code-review/references/verification-before-completion.md +68 -0
  87. package/plugins/sp/skills/code-simplification/SKILL.md +225 -0
  88. package/plugins/sp/skills/code-testing/SKILL.md +108 -0
  89. package/plugins/sp/skills/code-testing/references/stacks/bun-ts.md +165 -0
  90. package/plugins/sp/skills/code-testing/references/stacks/go.md +88 -0
  91. package/plugins/sp/skills/code-testing/references/stacks/python.md +117 -0
  92. package/plugins/sp/skills/code-testing/references/test-loop-breaker.md +37 -0
  93. package/plugins/sp/skills/code-testing/references/test-output-discipline.md +52 -0
  94. package/plugins/sp/skills/code-testing/references/unit-testing.md +212 -0
  95. package/plugins/sp/skills/code-verification/SKILL.md +535 -0
  96. package/plugins/sp/skills/code-verification/references/code-improvement.md +39 -0
  97. package/plugins/sp/skills/code-verification/references/secu-review.md +94 -0
  98. package/plugins/sp/skills/code-verification/references/verdict-schema.md +141 -0
  99. package/plugins/sp/skills/conflict-finding/SKILL.md +254 -0
  100. package/plugins/sp/skills/conflict-finding/references/authority-resolution.md +258 -0
  101. package/plugins/sp/skills/conflict-finding/references/comparison-protocol.md +277 -0
  102. package/plugins/sp/skills/conflict-finding/references/finding-contract.md +346 -0
  103. package/plugins/sp/skills/conflict-finding/references/remediation-routing.md +276 -0
  104. package/plugins/sp/skills/daily-summary/SKILL.md +171 -0
  105. package/plugins/sp/skills/daily-summary/agents/openai.yaml +13 -0
  106. package/plugins/sp/skills/doc-evolve/SKILL.md +175 -0
  107. package/plugins/sp/skills/doc-evolve/references/operations.md +93 -0
  108. package/plugins/sp/skills/dogfood-testing/SKILL.md +608 -0
  109. package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +211 -0
  110. package/plugins/sp/skills/dogfood-testing/references/report-template.md +351 -0
  111. package/plugins/sp/skills/doubt-driven-development/SKILL.md +136 -0
  112. package/plugins/sp/skills/functional-review/SKILL.md +364 -0
  113. package/plugins/sp/skills/functional-review/references/verdict-schema.md +133 -0
  114. package/plugins/sp/skills/indexed-context/SKILL.md +164 -0
  115. package/plugins/sp/skills/issue-finding/SKILL.md +442 -0
  116. package/plugins/sp/skills/issue-finding/agents/openai.yaml +15 -0
  117. package/plugins/sp/skills/issue-finding/examples/expected-findings.json +27 -0
  118. package/plugins/sp/skills/issue-finding/examples/session-test-loop.jsonl +23 -0
  119. package/plugins/sp/skills/issue-finding/references/session-formats.md +151 -0
  120. package/plugins/sp/skills/next-feature/SKILL.md +125 -0
  121. package/plugins/sp/skills/next-feature/references/handoff-routing.md +92 -0
  122. package/plugins/sp/skills/next-feature/references/proposal-contract.md +49 -0
  123. package/plugins/sp/skills/next-feature/references/ranking-rubric.md +52 -0
  124. package/plugins/sp/skills/next-feature/references/signal-derivation.md +67 -0
  125. package/plugins/sp/skills/next-router/SKILL.md +181 -0
  126. package/plugins/sp/skills/next-router/references/messages.md +90 -0
  127. package/plugins/sp/skills/next-router/references/routing-table.md +216 -0
  128. package/plugins/sp/skills/parallel-execution/SKILL.md +148 -0
  129. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +109 -0
  130. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +101 -0
  131. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +94 -0
  132. package/plugins/sp/skills/reverse-engineering/SKILL.md +375 -0
  133. package/plugins/sp/skills/reverse-engineering/agents/openai.yaml +18 -0
  134. package/plugins/sp/skills/source-driven-development/SKILL.md +124 -0
  135. package/plugins/sp/skills/spec-decomposition/SKILL.md +117 -0
  136. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +539 -0
  137. package/plugins/sp/skills/spur-cli/SKILL.md +133 -0
  138. package/plugins/sp/skills/spur-cli/references/agent.md +179 -0
  139. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +82 -0
  140. package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +177 -0
  141. package/plugins/sp/skills/spur-cli/references/features/roadmap-priority.md +84 -0
  142. package/plugins/sp/skills/spur-cli/references/features/verbs.md +122 -0
  143. package/plugins/sp/skills/spur-cli/references/features.md +234 -0
  144. package/plugins/sp/skills/spur-cli/references/init.md +119 -0
  145. package/plugins/sp/skills/spur-cli/references/message.md +98 -0
  146. package/plugins/sp/skills/spur-cli/references/rules/authoring-rules.md +212 -0
  147. package/plugins/sp/skills/spur-cli/references/rules/fine-tuning.md +148 -0
  148. package/plugins/sp/skills/spur-cli/references/rules/operations.md +228 -0
  149. package/plugins/sp/skills/spur-cli/references/rules/validation-and-extension.md +129 -0
  150. package/plugins/sp/skills/spur-cli/references/rules.md +209 -0
  151. package/plugins/sp/skills/spur-cli/references/serve.md +58 -0
  152. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +98 -0
  153. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +95 -0
  154. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +361 -0
  155. package/plugins/sp/skills/spur-cli/references/tasks.md +280 -0
  156. package/plugins/sp/skills/spur-cli/references/team.md +133 -0
  157. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +226 -0
  158. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +211 -0
  159. package/plugins/sp/skills/spur-cli/references/workflows/validation-and-extension.md +129 -0
  160. package/plugins/sp/skills/spur-cli/references/workflows.md +355 -0
  161. package/plugins/sp/skills/spur-dev/SKILL.md +224 -0
  162. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +196 -0
  163. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +680 -0
  164. package/plugins/sp/skills/spur-dev/references/decision-brief.md +80 -0
  165. package/plugins/sp/skills/spur-dev/references/dev-operations.md +546 -0
  166. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +164 -0
  167. package/plugins/sp/skills/spur-dev/references/execution-batch.md +790 -0
  168. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +352 -0
  169. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +191 -0
  170. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +417 -0
  171. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +171 -0
  172. package/plugins/sp/skills/spur-dev/references/glossary.md +95 -0
  173. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +80 -0
  174. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +120 -0
  175. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +302 -0
  176. package/plugins/sp/skills/spur-dev/references/product-planning.md +206 -0
  177. package/plugins/sp/skills/spur-dev/references/section-batching.md +24 -0
  178. package/plugins/sp/skills/sys-architecture/SKILL.md +155 -0
  179. package/plugins/sp/skills/sys-architecture/references/decision-method.md +137 -0
  180. package/plugins/sp/skills/sys-architecture/references/upkeep-survey.md +75 -0
  181. package/plugins/sp/skills/sys-debugging/SKILL.md +228 -0
  182. package/plugins/sp/skills/sys-debugging/references/debugging-protocol.md +62 -0
  183. package/plugins/sp/skills/test-driven-development/SKILL.md +248 -0
  184. package/plugins/sp/skills/wayfinder/SKILL.md +292 -0
  185. package/spur.js +6035 -4963
@@ -0,0 +1,212 @@
1
+ ---
2
+ name: authoring-rules
3
+ description: Author new spur constraint rules — evaluator selection, real config shapes, JSON finding schema, smoke-testing.
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # Authoring Rules
9
+
10
+ A rule codifies one constraint as machine-checkable policy. When a new standard or anti-pattern
11
+ emerges, turn it into a rule so the gate catches it forever instead of relying on review.
12
+
13
+ ## Rule anatomy
14
+
15
+ A rule file is YAML (or JSON) with an optional quoted `$schema` and a `rules:` array. Each rule:
16
+
17
+ ```yaml
18
+ rules:
19
+ - id: no-console-log # stable identifier — NEVER rename (CI/reports/suppressions reference it)
20
+ description: > # the WHY, not the WHAT. A consuming agent fixes from this line alone.
21
+ console.log leaks to stdout and breaks --json output. Use the project logger. (ADR-xxx)
22
+ severity: error # error | warning | info — see fine-tuning.md
23
+ evaluator:
24
+ type: regex # which check to run
25
+ config: { mode: forbid, pattern: "console\\.log\\(" }
26
+ include: ["apps/**/src/**/*.ts"] # rule-level globs (optional)
27
+ exclude: ["**/*.test.ts"]
28
+ ```
29
+
30
+ Norms (verified against Spur's own catalog):
31
+
32
+ - **`description` is a contract.** Encode the reason + a decision reference (`ADR-011`). Spur rules
33
+ do this in YAML block scalars (`>`). An agent reading a finding fixes from the description.
34
+ - **Group by concern, not by mechanism.** `boundary/dao-boundary.yaml` holds all DAO rules; do not
35
+ make an `all-regex-rules.yaml`.
36
+ - **Co-locate rationale.** A top-of-file comment explains why the file exists and any re-scoping
37
+ ("absorbed from ts-libs, re-scoped to Spur's monorepo layout").
38
+ - **Rule IDs are stable.** Renaming an ID silently breaks every downstream reference. Pick a good
39
+ name once.
40
+
41
+ ## Evaluator selection
42
+
43
+ Pick the narrowest evaluator that expresses the constraint. Decision order:
44
+
45
+ ```
46
+ Is it a structural code pattern (throw a string, await in loop, specific call shape)?
47
+ → sg (ast-grep). AST-aware, won't match comments/strings. Requires the `sg` CLI.
48
+ Is it a text pattern (forbidden/required substring or regex)?
49
+ → rg if the pattern is ripgrep-dialect-safe (fast, ReDoS-immune, parallel)
50
+ → regex if it needs lookbehind (?<=) or backreferences (\1) [rg can't compile these]
51
+ Is it about imports / package boundaries?
52
+ → forbidden-import (block specifiers) or import-boundary (scoped architectural seams)
53
+ Is it about files existing / not existing?
54
+ → path / file-exist
55
+ Is it about test presence for sources?
56
+ → test-location (with a ts/python/go/rust resolver)
57
+ Is it about coverage thresholds?
58
+ → coverage-gate (reads lcov.info)
59
+ Is it about doc comments on exports?
60
+ → tsdoc-export
61
+ Is it a hardcoded-secret scan?
62
+ → secrets-scanner
63
+ Is it a JSON schema artifact's structure?
64
+ → schema-artifact
65
+ Can none of the built-ins express it?
66
+ → exit-code: run any command, gate on its exit status (escape hatch)
67
+ ```
68
+
69
+ ## Real config shapes
70
+
71
+ These match Spur's live catalog and differ from the README's simplified examples — copy from here.
72
+
73
+ ### regex / rg — forbidden or required text
74
+
75
+ ```yaml
76
+ - id: no-debugger
77
+ description: Do not commit debugger statements
78
+ severity: error
79
+ evaluator:
80
+ type: regex # use `rg` instead if pattern is dialect-safe
81
+ config: { mode: forbid, pattern: "\\bdebugger\\b" }
82
+ include: ["apps/**/src/**/*.ts"]
83
+ ```
84
+
85
+ `mode: require` flips the meaning — the finding fires when the pattern is *absent*. `rg` rules run
86
+ the real ripgrep CLI: no lookbehind, no backreferences. Use `isRipgrepCompatiblePattern(pattern)`
87
+ (exported by `@gobing-ai/ts-rule-engine`) or the `rg-migration` preset to check before promoting a
88
+ `regex` rule to `rg`.
89
+
90
+ ### forbidden-import — block specifiers in a scope
91
+
92
+ The real shape uses `forbidden: [{ specifier }]` + a `scope` block (NOT a flat `forbidden: [string]`):
93
+
94
+ ```yaml
95
+ - id: ts-db-only-in-domain
96
+ description: "@gobing-ai/ts-db may only be imported inside packages/domain. (ADR-011)"
97
+ severity: error
98
+ evaluator:
99
+ type: forbidden-import
100
+ config:
101
+ forbidden:
102
+ - specifier: "@gobing-ai/ts-db"
103
+ - specifier: "@gobing-ai/ts-db/schema"
104
+ scope:
105
+ include: ["apps/**/src/**/*.ts", "packages/config/src/**/*.ts"]
106
+ exclude: ["**/node_modules/**", "**/tests/**"]
107
+ ```
108
+
109
+ This is an **architecture fitness function** — an architectural decision (ADR-011) made into an
110
+ assertion that runs on every gate. Keep the seam honest without spending review bandwidth.
111
+
112
+ ### coverage-gate — per-file line coverage
113
+
114
+ The real Spur rule uses `include`/`exclude` globs (not the README's `exemptions` array):
115
+
116
+ ```yaml
117
+ - id: coverage-gate
118
+ description: "Per-file line coverage meets 90% threshold (read from lcov)"
119
+ severity: error
120
+ evaluator:
121
+ type: coverage-gate
122
+ config:
123
+ lcovPath: .coverage/lcov.info # Spur writes here via bun test --coverage-dir=.coverage
124
+ threshold: 90
125
+ include: ["apps/**", "packages/**"]
126
+ exclude: ["**/node_modules/**"]
127
+ ```
128
+
129
+ Because it reads `lcov.info`, this rule belongs in a **post-test** preset (`recommended-post-check`),
130
+ never the pre-check — there is no coverage file before tests run.
131
+
132
+ ### exit-code — the escape hatch
133
+
134
+ When no built-in fits, shell out and gate on exit status. Spur's `rg-dialect` meta-rule uses this to
135
+ scan the rule catalog itself with `rg` + `yq`:
136
+
137
+ ```yaml
138
+ - id: rg-evaluator-patterns-are-ripgrep-dialect
139
+ description: "rg-typed rules must use ripgrep dialect (no lookbehind/backreferences)."
140
+ severity: error
141
+ evaluator:
142
+ type: exit-code
143
+ config:
144
+ command: sh
145
+ args: ["-c", "…script that exits non-zero on violation…"]
146
+ ```
147
+
148
+ This is also how you build **meta-rules** — rules that validate the rule catalog. Any required CLI
149
+ (`rg`, `sg`, `yq`) must be present in every environment that runs the gate, including CI.
150
+
151
+ ## The JSON finding schema (verified against live CLI)
152
+
153
+ `spur rule run --json` emits:
154
+
155
+ ```json
156
+ {
157
+ "preset": "recommended-pre-check",
158
+ "ruleCount": 17,
159
+ "findings": [
160
+ {
161
+ "ruleId": "probe-forbid-shape",
162
+ "severity": "error",
163
+ "message": "forbidden pattern found: registerRuleCommand",
164
+ "filePath": "apps/cli/src/commands/rule.ts",
165
+ "line": 12,
166
+ "code": "regex:found"
167
+ }
168
+ ],
169
+ "fixes": []
170
+ }
171
+ ```
172
+
173
+ Field notes (ground truth, not assumptions):
174
+
175
+ - `line` is **optional** — present on a forbid-match (`code: regex:found`), absent on a require-miss
176
+ (`code: regex:missing`). Do not assume every finding has a location.
177
+ - `column` is generally **not** present. Locate by `filePath` + `line` (when present) + `message`.
178
+ - `code` is `"<evaluator>:<result>"`, e.g. `regex:found`, `regex:missing`.
179
+ - `fixes` is populated only when a rule declares `fix:` and the engine is invoked with fix authority.
180
+ **The `spur rule run` CLI never applies fixes** — it surfaces findings. The agent reads the
181
+ finding and makes the edit itself, then re-runs. (Library `applyFixes()` exists for programmatic
182
+ consumers only.)
183
+
184
+ ### Evaluator error vs. policy violation (first-class distinction)
185
+
186
+ A finding with `kind: "error"` and `code: "evaluator:<type>"` and `filePath: null` is **not** a code
187
+ violation — the rule itself crashed (missing `rg`/`sg`/`lcov.info`, or a misconfigured pattern). The
188
+ fix is the rule or the environment, never the source code. Filter them apart:
189
+
190
+ ```ts
191
+ const evaluatorErrors = findings.filter((f) => f.kind === "error");
192
+ const violations = findings.filter((f) => f.kind !== "error");
193
+ ```
194
+
195
+ ## Smoke-test a new rule before trusting it
196
+
197
+ A rule you have not watched fire is a rule you do not trust. Before adding it to a gate preset, run
198
+ the shared **validate-and-smoke-test** core ([operations.md](operations.md#sub-procedure-validate-and-smoke-test)):
199
+ schema/Zod validate → run against a **should-fire** fixture (expect a finding) → run against a
200
+ **should-pass** fixture (expect none). Fixtures follow the
201
+ [convention](operations.md#fixture-convention) `.spur/rules/fixtures/<rule-id>/should-{fire,pass}.<ext>`.
202
+
203
+ ```bash
204
+ spur rule validate --file path/to/new-rule.yaml --json # 1. schema/Zod
205
+ # 2 & 3: run a FIXTURE-SCOPED copy (include → the fixture path) so the fixture is in scope:
206
+ spur rule run --file /tmp/<id>-fixture-scoped.yaml --rule <id> --fail-on info --json
207
+ ```
208
+
209
+ The smoke-test copy points `include` at the in-repo fixture (the shipped `apps/`/`packages/` scope
210
+ won't match a fixture under `.spur/rules/fixtures/`); never change the shipped rule's scope to match.
211
+ Only after schema-valid AND both directions pass do you fold the rule into a preset (see
212
+ fine-tuning.md → Preset composition).
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: fine-tuning
3
+ description: Dial in delivery quality — severity, glob scoping, exemptions, preset composition, layering, governance.
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # Fine-Tuning Rules and Presets
9
+
10
+ Authoring a rule is half the work; dialing it in so it catches real problems without crying wolf is
11
+ the other half. A gate with a high false-positive rate gets ignored, and an ignored gate is worthless.
12
+
13
+ ## Severity calibration
14
+
15
+ Severity drives the `--fail-on` gate. Choose by blast radius:
16
+
17
+ | Severity | Use when | Gate behavior |
18
+ | -------- | -------- | ------------- |
19
+ | `error` | Breaks delivery: security, architecture seam, broken build | Blocks at `--fail-on error` (default) |
20
+ | `warning` | Should fix, won't break prod: style, missing docs | Blocks only at `--fail-on warning` |
21
+ | `info` | Advisory / informational | Blocks only at `--fail-on info` |
22
+
23
+ Default `--fail-on error` means warnings and info never fail the gate unless you ask. Reserve `error`
24
+ for things you are willing to block a commit on; over-using `error` trains people to bypass the gate.
25
+
26
+ ## Glob scoping: the FP/FN dial
27
+
28
+ The single biggest source of false positives is a loose include glob; the biggest source of false
29
+ negatives is a missing nesting level.
30
+
31
+ - **Tighten `include`** to exactly the files the rule should police. A boundary rule for app source
32
+ is `apps/**/src/**/*.ts`, not `apps/**`.
33
+ - **`exclude` the legitimate exceptions**: tests, generated code, the one package allowed to break
34
+ the rule. Spur's `ts-db-only-in-domain` excludes `**/tests/**` and the domain schema dir.
35
+ - **Loose-match footgun (`regex`/`rg`/`forbidden-import`/`secrets-scanner`).** These evaluators match
36
+ files in *loose* mode: the engine strips every `*`/`**` from your glob and matches the remainder by
37
+ **substring / suffix**, not by true glob. So `apps/**/src/**/*.ts` effectively becomes the fragment
38
+ `apps/src/.ts` and matches any path containing it. Practical consequences: write `include` as a
39
+ **distinctive path fragment** (`apps/cli/src`, `/src/`, `.ts`) and lean on `exclude` for carve-outs;
40
+ don't assume a precise multi-level glob narrows the way it reads. `coverage-gate`, `test-location`,
41
+ and `path` use stricter glob matching — the loose behavior is specific to the content-scanning
42
+ evaluators above. (Verified against `ts-rule-engine` `matchesAny`.)
43
+
44
+ Rule-level `include`/`exclude` scopes one rule; some evaluators (`forbidden-import`,
45
+ `coverage-gate`) take their own `scope`/`include` inside `config` — use the evaluator's own scoping
46
+ when present (see authoring-rules.md for the real shapes).
47
+
48
+ ## Exemptions for known-legitimate cases
49
+
50
+ When one file legitimately violates a rule, prefer a scoped `exclude` with a rationale comment over
51
+ disabling the whole rule:
52
+
53
+ ```yaml
54
+ evaluator:
55
+ type: forbidden-import
56
+ config:
57
+ scope:
58
+ include: ["packages/**/src/**/*.ts"]
59
+ exclude:
60
+ - "packages/domain/src/schema/**" # the ONE place drizzle is allowed (ADR-011)
61
+ ```
62
+
63
+ Never widen `exclude` just to make the gate green — that is gate-gaming (see SKILL.md gotchas).
64
+
65
+ ### Exclusions are exceptions, not a whitelist
66
+
67
+ The chronic failure mode of refining is **exclusion creep**: each carve-out looks justified on its own,
68
+ but they accumulate until the rule's `exclude` hollows out its `include` and the rule enforces almost
69
+ nothing — a blocklist degraded into a de-facto allowlist. The per-change overlap check does not catch
70
+ this (it guards rule-vs-rule collision, not erosion of a rule's own coverage), so guard it explicitly
71
+ when adding any exclusion:
72
+
73
+ - **Every exclusion earns its place.** It needs a specific, documented reason — the legitimate case
74
+ *and* why it is legitimate (`# the ONE place X is allowed (ADR-n)`), not a bare path. An exclusion
75
+ you cannot justify in one line does not belong.
76
+ - **Narrowest possible.** Exclude a single file or the one directory that genuinely must be exempt —
77
+ never a broad glob that silences a whole tree to dodge one finding.
78
+ - **Accumulation is a smell, not a solution.** When a rule keeps needing more exclusions, the rule's
79
+ scope is wrong: re-scope its `include`, split it into concern-specific rules, or reconsider whether
80
+ the policy holds — do not keep carving. A rule whose exclusions cover most of its scope should be
81
+ re-scoped or retired, not extended.
82
+ - **Test the net.** After adding an exclusion, confirm the rule still fires on the cases it must catch
83
+ (the should-fire fixture still fires); an exclusion that also suppresses a real violation is a
84
+ false negative, not an exemption.
85
+
86
+ ## Preset composition
87
+
88
+ A preset bundles rules for a lifecycle stage. The shape (verified against Spur's presets):
89
+
90
+ ```yaml
91
+ $schema: "@gobing-ai/ts-rule-engine/schemas/preset.schema.json"
92
+ name: recommended-post-check
93
+ extends: # category DIRECTORIES or other presets, resolved across roots
94
+ - quality
95
+ - surface
96
+ disable: # suppress an inherited rule that doesn't fit this project
97
+ - legacy-rule
98
+ overrides: # re-tune an inherited rule without forking it
99
+ no-console-log:
100
+ severity: warning
101
+ fix: { mode: suggest }
102
+ ```
103
+
104
+ - **`extends`** pulls in whole category folders (`quality/`, `surface/`) or named presets.
105
+ - **`disable`** turns off an inherited rule by ID.
106
+ - **`overrides`** changes severity or fix mode of an inherited rule in place — the lever for
107
+ project-specific tuning without copying the rule.
108
+
109
+ ## Lifecycle-stage presets (Spur's actual layout)
110
+
111
+ | Preset | Stage | Why it exists |
112
+ | ------ | ----- | ------------- |
113
+ | `recommended-pre-check` | inner loop, before tests | Fast: style, boundaries, structure. No coverage (no lcov yet). |
114
+ | `recommended-post-check` | after tests | Adds `coverage-gate` (needs `.coverage/lcov.info`) + export docs + CLI surface. |
115
+ | `strict-check` | optional deep audit | Hardened runtime/http/rule-file structural boundaries. |
116
+ | `rg-migration` | on-demand, transitional | Meta-rule guarding `rg` patterns against ripgrep-incompatible constructs. |
117
+
118
+ **Gate ordering rationale (not arbitrary):** cheap, always-available checks run first (pre-check);
119
+ checks that need produced artifacts run after the step that produces them (coverage-gate after
120
+ tests). This minimizes feedback latency — you fail on a style violation in seconds, not after a full
121
+ test run.
122
+
123
+ ## Layering — project shadows bundled
124
+
125
+ Rule roots resolve highest-priority-first: project `.spur/rules/` → user-global
126
+ (`~/.config/spur/rules`) → bundled (shipped with the package). If two roots contain the same relative
127
+ rule path, the **first wins** and lower roots fill gaps. This is why a project can shadow a single
128
+ bundled rule file while inheriting the rest of the catalog.
129
+
130
+ Confirm what is actually resolved before assuming:
131
+
132
+ ```bash
133
+ spur rule list --json # discovered files + their source layer
134
+ spur rule list --preset recommended-pre-check --json # the resolved rule set for one preset
135
+ ```
136
+
137
+ ## Governance & evolution
138
+
139
+ - **Where does a rule live?** Project-specific policy → `.spur/rules/`. A rule useful to every
140
+ consumer of `ts-rule-engine` → propose it upstream in the shared library (per the project's
141
+ shared-library evolution principle), don't fork it locally.
142
+ - **Track false positives.** There is no built-in FP tracking (a tool gap). Convention: log FP
143
+ incidents in a YAML comment on the rule; if a rule cannot be tuned below an acceptable FP rate,
144
+ retire it — a noisy rule erodes trust in the whole gate.
145
+ - **Link intent to decisions.** `description` carries the one-line why; reference the governing ADR
146
+ so a future maintainer (human or agent) understands the rule before changing it.
147
+ - **Meta-rules keep the catalog honest.** `rg-migration` and the `strict/rule-files-structural`
148
+ rules validate the rule files themselves — rule governance as code.
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: operations
3
+ description: Named operation procedures (run/scan/add/refine/validate/list), the shared find-existing-coverage and validate-and-smoke-test cores that back the spur rule slash commands.
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # Operations
9
+
10
+ The skill's operations as discrete procedures. The deterministic ones (`run`, `validate`, `list`,
11
+ `trace`) are direct CLI verbs — documented briefly here for completeness, but you run them straight
12
+ (no slash command). The agent-driven ones (`scan`, `add`, `refine`) convert fuzzy intent into a reliable
13
+ sequence and are what the slash commands delegate to; their full steps live below.
14
+
15
+ A rule you have not watched fire is a rule you do not trust. So both `add` and `refine` end in the
16
+ same verification core ([validate-and-smoke-test](#sub-procedure-validate-and-smoke-test)) — that
17
+ shared core is why a tightened rule is re-checked exactly like a freshly authored one. Behavioral
18
+ rule-testing ("does this rule fire correctly?") is that core, not a separate operation. `scan`, `add`,
19
+ and `refine` also share the [find-existing-coverage](#sub-procedure-find-existing-coverage) core so the
20
+ catalog never diverges no matter which operation touches it.
21
+
22
+ ## Fixture convention
23
+
24
+ Smoke-testing needs a file the rule **should fire on** and a file it **should stay quiet on**. Without
25
+ a convention, fixtures are invented ad-hoc and verification is not reproducible. Convention:
26
+
27
+ ```
28
+ .spur/rules/fixtures/<rule-id>/should-fire.<ext> # MUST produce a finding
29
+ .spur/rules/fixtures/<rule-id>/should-pass.<ext> # MUST produce no finding
30
+ ```
31
+
32
+ `<ext>` matches the rule's target (`.ts` for a TS rule, `.yaml` for a meta-rule, etc.). Keep fixtures
33
+ minimal — the smallest snippet that exercises the constraint. If a caller supplies `--fixture-fire` /
34
+ `--fixture-pass`, use those paths instead; otherwise default to the convention. When neither exists,
35
+ synthesize a fire fixture from the rule's own pattern/intent and a pass fixture by negating it, write
36
+ them to the convention path, then run the core.
37
+
38
+ **Fixtures must live inside the repo** (`.spur/rules/fixtures/...`) — the engine scans relative to the
39
+ working tree, so an out-of-repo fixture (e.g. under `/tmp`) silently never fires. **Smoke-test runs
40
+ use a copy of the rule with `include` pointed at the fixture fragment** (e.g. `fixtures/<rule-id>`),
41
+ not the shipped rule's `apps/`/`packages/` scope — otherwise the fixture is out of scope and the
42
+ fire-test produces a false negative. The shipped rule's scope is never changed to accommodate a
43
+ fixture. (Verified: an in-repo fixture matched by any distinctive path fragment fires correctly; an
44
+ out-of-repo fixture does not.)
45
+
46
+ ## Sub-procedure: find-existing-coverage
47
+
48
+ Reconciliation core — run this **before authoring anything**. Authoring without checking the catalog
49
+ breeds redundant, diverged, and contradictory rules (two `no-console` variants, one rule for `apps/**`
50
+ and a twin for `packages/**`, the same concern at conflicting severities). In a layered preset system
51
+ that is silent conflict, not just noise. Inputs: the clarified constraint (one checkable sentence).
52
+ Steps:
53
+
54
+ 1. **Enumerate the catalog** — `spur rule list --json` for every rule ID + source layer, plus grep the
55
+ rule files for evaluator `type`, `pattern`/`specifier`, and `scope`/`include` so matches are found
56
+ by *substance*, not just by ID text.
57
+ 2. **Classify the strongest match** against the new constraint:
58
+
59
+ | Match | Meaning | Action |
60
+ | ----- | ------- | ------ |
61
+ | Exact / near-duplicate | A rule already enforces this concern on this scope | **STOP — do not add.** It is covered; if behavior is off, hand to [refine](#refine). |
62
+ | Same concern, different scope | A twin exists for other paths/specifiers | **EXTEND** — widen the existing rule's globs / add the specifier. One rule, not two. |
63
+ | Adjacent, same category/file | A sibling rule lives in the file this concern belongs to | **ADD into the existing file** (group by concern); reuse the category's preset wiring. |
64
+ | No real match | Genuinely new concern | **ADD new** — the only branch that authors from scratch. |
65
+
66
+ 3. **extend vs. refine boundary** (they are close): **extend** = the rule should now *also* cover this
67
+ case (same intent, wider net → broaden scope / add specifier). **refine** = the rule's *behavior*
68
+ is wrong (same net, better tuned → severity/scope/FP-FN). If the match means "already covered, you
69
+ want it tuned," that is refine, not add.
70
+ 4. **Recommend + confirm** — report the strongest match, the classification, and the recommended
71
+ action (extend / refine / add-into-file / add-new). **Require user confirmation before acting** —
72
+ surface the conflict; the human decides. Never silently duplicate, and never silently edit a rule
73
+ the user did not name.
74
+
75
+ Output contract: `{ match: rule-id|none, classification, recommendation, evidence }` + the confirmed
76
+ action to take.
77
+
78
+ ## Sub-procedure: validate-and-smoke-test
79
+
80
+ The shared verification core. Inputs: a rule file path + a rule ID. Steps:
81
+
82
+ 1. **Schema/Zod validate** — `spur rule validate --file <rule-file> --json`. On error, surface
83
+ root-cause + fix (see [validate](#validate)); stop until clean. The most common error is an
84
+ unquoted `$schema` (`@` is YAML-reserved).
85
+ 2. **Smoke-test FIRE** — write a copy of the rule whose `include` points at the fire fixture
86
+ (the shipped scope won't match an in-repo fixture — see [fixture convention](#fixture-convention)),
87
+ then `spur rule run --file <fixture-scoped-rule> --rule <id> --fail-on info --json`. **Expect a
88
+ finding.** Zero findings means the rule does not catch what it claims — a false negative; fix the
89
+ pattern. (Verify the *pattern*, not the shipped scope.)
90
+ 3. **Smoke-test PASS** — same fixture-scoped rule against the pass fixture; **expect zero findings**.
91
+ A finding here is a false positive — fix the pattern (the shipped `include`/`exclude` is tested
92
+ separately by scoping precision, not by the fixture).
93
+ 4. **Report** — `{ schemaValid, firedAsExpected, falsePositiveFree }`. The rule is verified only when
94
+ all three are true.
95
+
96
+ > The fixture must be in the rule's scope, or it cannot fire. A rule scoped to `apps/...` won't match
97
+ > a fixture under `.spur/rules/fixtures/<id>/` — and for content-scanning evaluators (`regex`/`rg`/
98
+ > `forbidden-import`/`secrets-scanner`) `include` is matched as a **substring/suffix fragment**, not a
99
+ > true glob (see [fine-tuning.md → loose-match footgun](fine-tuning.md#glob-scoping-the-fpfn-dial)),
100
+ > so the fixture path must contain the rule's scope fragment. Two safe options: (a) keep fixtures
101
+ > under a path the rule's scope already covers, or (b) for the smoke-test run only, point `include` at
102
+ > the fixture path — **never widen the shipped rule's scope to make a fixture match.**
103
+
104
+ ## run (direct CLI — the harness loop)
105
+
106
+ `spur rule run` is a direct CLI verb; there is no slash command for it. The skill drives the loop when
107
+ asked in natural language. Detailed in SKILL.md → "The harness loop". Procedure:
108
+
109
+ 1. `spur rule run [--preset <name>] [--rule <id>] [--file <path>] [--fail-on <sev>] [--stop-on-first [<sev>]] [--fix-mode <none|suggest|auto>] [--dry-run] --json`
110
+ 2. Read the exit code (binary gate) AND `findings[]` (actionable detail).
111
+ 3. Split findings: policy violations (fix the code) vs. evaluator errors
112
+ (`kind: "error"`, `code: "evaluator:<type>"`, `filePath: null` — fix the rule/environment).
113
+ 4. Apply the **specific** fix per violation — no drive-by refactors. By default the CLI surfaces
114
+ findings only; use `--fix-mode auto` to apply mechanically fixable violations, then re-run to
115
+ confirm green.
116
+ 5. Re-run the same command. Loop until exit 0.
117
+
118
+ Output contract: exit code + parsed findings + violation/evaluator-error split + per-finding fix + optional `fixes[]`/`applied` (when `--fix-mode` ≠ `none`).
119
+
120
+ ## add
121
+
122
+ Turn a natural-language constraint into a validated, smoke-tested rule. Procedure:
123
+
124
+ 1. **Clarify intent** — restate the constraint as one checkable sentence. If the description is
125
+ ambiguous (which paths? forbid or require? which severity?), state the interpretation taken.
126
+ 2. **Reconcile against the catalog** — run [find-existing-coverage](#sub-procedure-find-existing-coverage).
127
+ This is mandatory and gating: if it finds the concern already covered, **stop and hand to refine
128
+ (or extend the existing rule) on confirmation** — do not author a redundant rule. Only the
129
+ "no real match" branch proceeds to author from scratch below.
130
+ 3. **Select evaluator** — apply the decision tree in
131
+ [authoring-rules.md → Evaluator selection](authoring-rules.md#evaluator-selection)
132
+ (`sg` for structural, `rg`/`regex` for text, `forbidden-import`/`import-boundary` for imports,
133
+ `path` for files, `coverage-gate`/`test-location`/`tsdoc-export`/`secrets-scanner`/`schema-artifact`
134
+ for their specific concerns, `exit-code` as the escape hatch).
135
+ 4. **Write config** — use the **real config shapes** from
136
+ [authoring-rules.md → Real config shapes](authoring-rules.md#real-config-shapes)
137
+ (not the README's simplified ones). Set `id` (stable, kebab), `description` (the WHY + decision
138
+ ref), `severity` (see [fine-tuning.md → Severity](fine-tuning.md#severity-calibration)), and tight
139
+ `include`/`exclude`. Quote `$schema`.
140
+ 5. **Place the file** — default `.spur/rules/<category>/<rule-id>.yaml`, grouped by concern (a
141
+ `--file` arg overrides). One file per concern, not per mechanism.
142
+ 6. **Verify** — run the [validate-and-smoke-test core](#sub-procedure-validate-and-smoke-test). Not
143
+ done until all three checks pass.
144
+ 7. **Wire into preset (optional)** — if `--preset <target>` was given, add the rule's category to the
145
+ preset's `extends` (if not already present), or add the rule file under an extended category. Then
146
+ `spur rule validate --preset <target> --json` to confirm the preset still resolves. Re-tuning an
147
+ inherited rule uses `overrides`, not duplication.
148
+
149
+ Output contract: YAML rule content + destination path + smoke-test result (both directions) + validate
150
+ result. Done only when validate passes AND both smoke-test directions pass.
151
+
152
+ ## refine
153
+
154
+ Tighten or adjust an existing rule/preset with the smallest change that meets the intent. Procedure:
155
+
156
+ 1. **Locate the target** — if a rule file/preset is named, load it. If the user describes a *concern*
157
+ instead ("the import-boundary rule is too noisy"), run
158
+ [find-existing-coverage](#sub-procedure-find-existing-coverage) to resolve which rule they mean
159
+ before editing. Read its current shape.
160
+ 2. **Identify the dimension** from `--intent` and flags:
161
+ - false positive → tighten `include` or add `exclude` (`--scope`, `--exempt`)
162
+ - false negative → broaden `include` or fix the pattern
163
+ - wrong blocking behavior → `--severity`
164
+ - inherited rule wrong for this project → preset `--disable <id>` or `--override <id>`
165
+ See [fine-tuning.md](fine-tuning.md) for each mechanism's real shape.
166
+ 3. **Apply the smallest change.** Add a rationale comment for any exemption (`# <path> is the one
167
+ place X is allowed (ADR-n)`). Never widen scope merely to pass a gate — that is gate-gaming.
168
+ **For any `exclude`/`--exempt` change, apply exclusion discipline**
169
+ ([fine-tuning.md → Exclusions are exceptions, not a whitelist](fine-tuning.md#exclusions-are-exceptions-not-a-whitelist)):
170
+ each exclusion must be the narrowest possible, carry a one-line justification, and leave the rule
171
+ still firing on the cases it must catch. If a rule needs ever more exclusions, re-scope or split it
172
+ rather than carving further — accumulating carve-outs turns a blocklist into a de-facto allowlist.
173
+ 4. **Preview if `--dry-run`** — emit a unified diff of the YAML change and stop (no write). This is a
174
+ skill-level output; the CLI has no dry-run for edits.
175
+ 5. **Verify** — run the [validate-and-smoke-test core](#sub-procedure-validate-and-smoke-test). A
176
+ refine that improves FP/FN must prove it: the fixture that previously misbehaved now behaves.
177
+ 6. **Overlap check (only when the change LOOSENS or WIDENS)** — broadening scope, relaxing a pattern,
178
+ or lowering severity can make this rule collide with or subsume a sibling — the inverse of add's
179
+ duplicate problem. Run [find-existing-coverage](#sub-procedure-find-existing-coverage) on the
180
+ *widened* rule; if it now overlaps another rule's concern/scope, **stop and report the collision**,
181
+ recommending which rule should own the overlap (merge, or keep the boundary). Require confirmation
182
+ before persisting a widening that overlaps. Tightening/narrowing changes skip this step.
183
+ 7. **Confirm preset (if target was a preset)** — `spur rule validate --preset <name> --json`.
184
+
185
+ Output contract: diff of the change + validate result + smoke-test result (both directions) + overlap
186
+ verdict on widening changes. With `--dry-run`, the diff only.
187
+
188
+ ## scan
189
+
190
+ Proactive pattern discovery — survey the codebase for **recurring anti-patterns that should become
191
+ rules**, so the catalog gets ahead of defects instead of only reacting to them. **Propose-only:**
192
+ `scan` discovers and recommends candidates; it does **not** author. Each accepted candidate flows to
193
+ [add](#add) (which reconciles against the catalog) on confirmation. This keeps broad/fuzzy discovery
194
+ cleanly separated from precise/gated authoring. Procedure:
195
+
196
+ 1. **Scope the survey** — default to changed/working-tree code; accept a path/glob to bound it. Heavy
197
+ scans should run in a forked sub-context so raw output stays out of the main window.
198
+ 2. **Hunt recurring patterns** — look for smells that *repeat* (a one-off is not a rule):
199
+ - the same forbidden construct in multiple files (`console.log`, `debugger`, `as any`, raw `throw "string"`)
200
+ - ad-hoc boundary violations (a package importing something it shouldn't, repeated)
201
+ - modules with no corresponding test (systematic, not a single gap)
202
+ - inconsistent conventions applied unevenly (naming, error handling, logging)
203
+ Use `rg`/`sg` to count occurrences — evidence is the number of hits across files.
204
+ 3. **Cluster into candidate concerns** — group hits by the single concern they imply, and for each
205
+ pick the evaluator that would catch it ([authoring-rules.md → Evaluator selection](authoring-rules.md#evaluator-selection)).
206
+ 4. **Filter against the catalog** — run [find-existing-coverage](#sub-procedure-find-existing-coverage)
207
+ per candidate. Drop concerns already enforced; mark ones that an existing rule should *extend*
208
+ (→ refine/extend) vs. genuinely new (→ add).
209
+ 5. **Rank and report** — order candidates by occurrence count × severity-of-concern. For each:
210
+ the concern, evidence (hit count + sample files), proposed evaluator, and the recommended route
211
+ (`add` new / `refine`-extend existing / skip-already-covered). **Author nothing.**
212
+
213
+ Output contract: a ranked list of rule candidates, each with `{ concern, evidence, evaluator,
214
+ route: add|extend|covered }`. The user picks which to act on; accepted ones hand off to `add`/`refine`.
215
+
216
+ ## validate / list (direct CLI — no command, no procedure)
217
+
218
+ Deterministic single-verb CLI calls. Run them straight; the skill interprets results when asked.
219
+
220
+ - `spur rule validate [--file <path>|--preset <name>|<path>] [--no-schema] --json` — schema + Zod
221
+ check. Classify any error as **schema** (violates `rule-file.schema.json`/`preset.schema.json` —
222
+ often an unquoted `$schema`, `@` reserved) vs. **Zod** (regex `config` missing `pattern`, invalid
223
+ severity, a preset `override`/`disable`/`extends` targeting something that doesn't exist). The
224
+ `validate-and-smoke-test` core calls this as step 1.
225
+ - `spur rule list --json` — discovered rule files + source layer (project/user/bundled).
226
+ - `spur rule list --preset <name> --json` — the resolved rule set for one preset (after layering).
227
+
228
+ Use `list` before `refine` to see which rules (and which shadowing layer) are actually in effect.