@gobing-ai/spur 0.3.40 → 0.3.42
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/marketplace.json +15 -0
- package/README.md +15 -1
- package/config/templates/AGENTS.md +5 -3
- package/config/workflows/task-pipeline.yaml +103 -12
- package/package.json +15 -12
- package/plugins/README.md +655 -0
- package/plugins/sp/README.md +621 -0
- package/plugins/sp/agents/expert-spur.md +109 -0
- package/plugins/sp/agents/super-coder.md +126 -0
- package/plugins/sp/agents/super-planner.md +283 -0
- package/plugins/sp/agents/super-reviewer.md +154 -0
- package/plugins/sp/commands/dev-arch.md +30 -0
- package/plugins/sp/commands/dev-brainstorm.md +40 -0
- package/plugins/sp/commands/dev-changelog.md +28 -0
- package/plugins/sp/commands/dev-daily.md +29 -0
- package/plugins/sp/commands/dev-debug.md +29 -0
- package/plugins/sp/commands/dev-dogfood.md +33 -0
- package/plugins/sp/commands/dev-featurechange.md +130 -0
- package/plugins/sp/commands/dev-find-conflict.md +51 -0
- package/plugins/sp/commands/dev-find-issue.md +76 -0
- package/plugins/sp/commands/dev-find-next.md +60 -0
- package/plugins/sp/commands/dev-fixall.md +33 -0
- package/plugins/sp/commands/dev-gitmsg.md +28 -0
- package/plugins/sp/commands/dev-gtd.md +97 -0
- package/plugins/sp/commands/dev-handover.md +26 -0
- package/plugins/sp/commands/dev-idea.md +50 -0
- package/plugins/sp/commands/dev-next.md +31 -0
- package/plugins/sp/commands/dev-parallel.md +30 -0
- package/plugins/sp/commands/dev-plan.md +46 -0
- package/plugins/sp/commands/dev-refine.md +48 -0
- package/plugins/sp/commands/dev-refineall.md +65 -0
- package/plugins/sp/commands/dev-refresh.md +32 -0
- package/plugins/sp/commands/dev-reverse.md +32 -0
- package/plugins/sp/commands/dev-review.md +31 -0
- package/plugins/sp/commands/dev-run.md +63 -0
- package/plugins/sp/commands/dev-runall.md +80 -0
- package/plugins/sp/commands/dev-simplify.md +39 -0
- package/plugins/sp/commands/dev-unit.md +29 -0
- package/plugins/sp/commands/dev-verify.md +48 -0
- package/plugins/sp/commands/dev-verifyall.md +72 -0
- package/plugins/sp/commands/dev-wrap.md +30 -0
- package/plugins/sp/commands/dev-wrapall.md +33 -0
- package/plugins/sp/commands/rule-add.md +18 -0
- package/plugins/sp/commands/rule-refine.md +18 -0
- package/plugins/sp/commands/rule-scan.md +18 -0
- package/plugins/sp/commands/spur-init.md +22 -0
- package/plugins/sp/commands/workflow-add.md +18 -0
- package/plugins/sp/commands/workflow-refine.md +18 -0
- package/plugins/sp/hooks/agent-hint.ts +60 -0
- package/plugins/sp/hooks/careful-guard.ts +147 -0
- package/plugins/sp/hooks/context-post-tool.ts +325 -0
- package/plugins/sp/hooks/context-session-start.ts +182 -0
- package/plugins/sp/hooks/context-session-stop.ts +95 -0
- package/plugins/sp/hooks/hooks.json +51 -0
- package/plugins/sp/hooks/pi/guard-extension.ts +341 -0
- package/plugins/sp/hooks/task-write-guard.ts +84 -0
- package/plugins/sp/plugin.json +8 -0
- package/plugins/sp/scripts/batch-preflight.ts +204 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +606 -0
- package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +363 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +168 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +451 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1341 -0
- package/plugins/sp/scripts/task-size-precheck.ts +202 -0
- package/plugins/sp/scripts/validate-commands.ts +651 -0
- package/plugins/sp/scripts/validate-flag-contracts.ts +875 -0
- package/plugins/sp/skills/brainstorm/SKILL.md +361 -0
- package/plugins/sp/skills/brainstorm/agents/openai.yaml +14 -0
- package/plugins/sp/skills/brainstorm/examples/ideation-example.md +209 -0
- package/plugins/sp/skills/brainstorm/metadata.openclaw +7 -0
- package/plugins/sp/skills/brainstorm/references/grilling-interview.md +68 -0
- package/plugins/sp/skills/brainstorm/references/workflows.md +255 -0
- package/plugins/sp/skills/branch-workflow/SKILL.md +109 -0
- package/plugins/sp/skills/branch-workflow/references/branch-lifecycle.md +69 -0
- package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +100 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +176 -0
- package/plugins/sp/skills/code-implementation/references/debugging.md +64 -0
- package/plugins/sp/skills/code-implementation/references/implementation-patterns.md +113 -0
- package/plugins/sp/skills/code-improvement/SKILL.md +231 -0
- package/plugins/sp/skills/code-improvement/references/deepening-signals.md +205 -0
- package/plugins/sp/skills/code-review/SKILL.md +112 -0
- package/plugins/sp/skills/code-review/references/receiving-code-review.md +70 -0
- package/plugins/sp/skills/code-review/references/review-lenses.md +117 -0
- package/plugins/sp/skills/code-review/references/self-review-checklist.md +53 -0
- package/plugins/sp/skills/code-review/references/verification-before-completion.md +68 -0
- package/plugins/sp/skills/code-simplification/SKILL.md +225 -0
- package/plugins/sp/skills/code-testing/SKILL.md +108 -0
- package/plugins/sp/skills/code-testing/references/stacks/bun-ts.md +165 -0
- package/plugins/sp/skills/code-testing/references/stacks/go.md +88 -0
- package/plugins/sp/skills/code-testing/references/stacks/python.md +117 -0
- package/plugins/sp/skills/code-testing/references/test-loop-breaker.md +37 -0
- package/plugins/sp/skills/code-testing/references/test-output-discipline.md +52 -0
- package/plugins/sp/skills/code-testing/references/unit-testing.md +212 -0
- package/plugins/sp/skills/code-verification/SKILL.md +535 -0
- package/plugins/sp/skills/code-verification/references/code-improvement.md +39 -0
- package/plugins/sp/skills/code-verification/references/secu-review.md +94 -0
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +141 -0
- package/plugins/sp/skills/conflict-finding/SKILL.md +254 -0
- package/plugins/sp/skills/conflict-finding/references/authority-resolution.md +258 -0
- package/plugins/sp/skills/conflict-finding/references/comparison-protocol.md +277 -0
- package/plugins/sp/skills/conflict-finding/references/finding-contract.md +346 -0
- package/plugins/sp/skills/conflict-finding/references/remediation-routing.md +276 -0
- package/plugins/sp/skills/daily-summary/SKILL.md +171 -0
- package/plugins/sp/skills/daily-summary/agents/openai.yaml +13 -0
- package/plugins/sp/skills/doc-evolve/SKILL.md +175 -0
- package/plugins/sp/skills/doc-evolve/references/operations.md +93 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +608 -0
- package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +211 -0
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +351 -0
- package/plugins/sp/skills/doubt-driven-development/SKILL.md +136 -0
- package/plugins/sp/skills/functional-review/SKILL.md +364 -0
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +133 -0
- package/plugins/sp/skills/indexed-context/SKILL.md +164 -0
- package/plugins/sp/skills/issue-finding/SKILL.md +424 -0
- package/plugins/sp/skills/issue-finding/agents/openai.yaml +15 -0
- package/plugins/sp/skills/issue-finding/examples/expected-findings.json +27 -0
- package/plugins/sp/skills/issue-finding/examples/session-test-loop.jsonl +23 -0
- package/plugins/sp/skills/issue-finding/references/session-formats.md +121 -0
- package/plugins/sp/skills/next-feature/SKILL.md +125 -0
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +92 -0
- package/plugins/sp/skills/next-feature/references/proposal-contract.md +49 -0
- package/plugins/sp/skills/next-feature/references/ranking-rubric.md +52 -0
- package/plugins/sp/skills/next-feature/references/signal-derivation.md +67 -0
- package/plugins/sp/skills/next-router/SKILL.md +181 -0
- package/plugins/sp/skills/next-router/references/messages.md +90 -0
- package/plugins/sp/skills/next-router/references/routing-table.md +216 -0
- package/plugins/sp/skills/parallel-execution/SKILL.md +148 -0
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +100 -0
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +101 -0
- package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +94 -0
- package/plugins/sp/skills/reverse-engineering/SKILL.md +375 -0
- package/plugins/sp/skills/reverse-engineering/agents/openai.yaml +18 -0
- package/plugins/sp/skills/source-driven-development/SKILL.md +124 -0
- package/plugins/sp/skills/spec-decomposition/SKILL.md +117 -0
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +539 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +133 -0
- package/plugins/sp/skills/spur-cli/references/agent.md +179 -0
- package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +82 -0
- package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +177 -0
- package/plugins/sp/skills/spur-cli/references/features/roadmap-priority.md +84 -0
- package/plugins/sp/skills/spur-cli/references/features/verbs.md +122 -0
- package/plugins/sp/skills/spur-cli/references/features.md +234 -0
- package/plugins/sp/skills/spur-cli/references/init.md +119 -0
- package/plugins/sp/skills/spur-cli/references/message.md +98 -0
- package/plugins/sp/skills/spur-cli/references/rules/authoring-rules.md +212 -0
- package/plugins/sp/skills/spur-cli/references/rules/fine-tuning.md +148 -0
- package/plugins/sp/skills/spur-cli/references/rules/operations.md +228 -0
- package/plugins/sp/skills/spur-cli/references/rules/validation-and-extension.md +129 -0
- package/plugins/sp/skills/spur-cli/references/rules.md +209 -0
- package/plugins/sp/skills/spur-cli/references/serve.md +58 -0
- package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +98 -0
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +95 -0
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +361 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +278 -0
- package/plugins/sp/skills/spur-cli/references/team.md +133 -0
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +226 -0
- package/plugins/sp/skills/spur-cli/references/workflows/operations.md +211 -0
- package/plugins/sp/skills/spur-cli/references/workflows/validation-and-extension.md +129 -0
- package/plugins/sp/skills/spur-cli/references/workflows.md +355 -0
- package/plugins/sp/skills/spur-dev/SKILL.md +224 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +196 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +664 -0
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +80 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +546 -0
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +164 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +727 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +350 -0
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +191 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +417 -0
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +171 -0
- package/plugins/sp/skills/spur-dev/references/glossary.md +95 -0
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +80 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +88 -0
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +302 -0
- package/plugins/sp/skills/spur-dev/references/product-planning.md +206 -0
- package/plugins/sp/skills/spur-dev/references/section-batching.md +24 -0
- package/plugins/sp/skills/sys-architecture/SKILL.md +155 -0
- package/plugins/sp/skills/sys-architecture/references/decision-method.md +137 -0
- package/plugins/sp/skills/sys-architecture/references/upkeep-survey.md +75 -0
- package/plugins/sp/skills/sys-debugging/SKILL.md +228 -0
- package/plugins/sp/skills/sys-debugging/references/debugging-protocol.md +62 -0
- package/plugins/sp/skills/test-driven-development/SKILL.md +248 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +292 -0
- package/spur.js +6093 -5180
|
@@ -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.
|