speccle 0.12.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -2
- package/dist/calibration.js +156 -0
- package/dist/changeset.js +141 -0
- package/dist/cli.js +486 -9
- package/dist/config.js +13 -10
- package/dist/doctor.js +19 -5
- package/dist/lenses.js +68 -0
- package/dist/remedy.js +133 -0
- package/dist/render.js +225 -10
- package/dist/reviewinit.js +139 -0
- package/dist/reviewrun.js +516 -0
- package/dist/risk.js +228 -0
- package/dist/update.js +15 -6
- package/dist/verify.js +92 -0
- package/lenses/accessibility.md +39 -0
- package/lenses/architecture.md +38 -0
- package/lenses/correctness.md +36 -0
- package/lenses/house-conventions.md +37 -0
- package/lenses/performance.md +40 -0
- package/lenses/risk.md +45 -0
- package/lenses/security.md +42 -0
- package/lenses/test-quality.md +39 -0
- package/package.json +5 -4
- package/skills/review/SKILL.md +190 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# test-quality lens
|
|
2
|
+
|
|
3
|
+
**Stance:** a test's only job is to fail when the behaviour breaks. Read each test the change
|
|
4
|
+
adds or touches and ask what broken code it would let through.
|
|
5
|
+
|
|
6
|
+
## What to look for
|
|
7
|
+
|
|
8
|
+
- **Asserts nothing** — a test that exercises code but checks no outcome; an `expect` with no
|
|
9
|
+
matcher; a snapshot accepted without reading it; a `try/catch` that passes on either path.
|
|
10
|
+
- **Cannot fail** — an assertion tautologically true (`expect(x).toBe(x)`), a mock asserted
|
|
11
|
+
against its own return, a condition that guards the only assertion so it never runs.
|
|
12
|
+
- **Tests the mock** — every collaborator stubbed until the test only proves the stubs were
|
|
13
|
+
called in the order the test wrote; nothing of the real unit is left under test.
|
|
14
|
+
- **Behaviour changed, test did not** — production logic in the diff whose test file is
|
|
15
|
+
untouched, or touched only to keep it compiling; a new branch with no case.
|
|
16
|
+
- **Coupled to implementation** — assertions on private internals, call order, or log
|
|
17
|
+
strings rather than observable behaviour, so a safe refactor breaks the test and a real
|
|
18
|
+
regression slips by.
|
|
19
|
+
- **Weak oracle** — asserts a value is truthy where the exact value matters; `toThrow()`
|
|
20
|
+
with no error type; checks a collection's length but not its contents.
|
|
21
|
+
- **Missing negative space** — only the happy path; the error, the empty, and the rejected
|
|
22
|
+
input the change introduced go unchecked.
|
|
23
|
+
|
|
24
|
+
## How to report
|
|
25
|
+
|
|
26
|
+
Report only findings anchored to a **changed line** — a test in the diff, or production
|
|
27
|
+
behaviour the diff added that no test claims. For each finding give:
|
|
28
|
+
|
|
29
|
+
- `path:line` — the test line, or the unclaimed production line
|
|
30
|
+
- **severity** — major (a real regression would pass) · minor · nit
|
|
31
|
+
- **what** — the gap in one line
|
|
32
|
+
- **why** — the broken implementation this test would wave through
|
|
33
|
+
- **fix** — the assertion to add, or the case the suite owes
|
|
34
|
+
- **route** — `criterion` (behaviour the spec never named, so no test could claim it) ·
|
|
35
|
+
`check` · `lens` · `none`
|
|
36
|
+
|
|
37
|
+
Route to `criterion` when the gap is that nothing specifies the behaviour — a missing
|
|
38
|
+
assertion is a test fix, a missing specification is not. Do not reward a test that raises a
|
|
39
|
+
coverage number while asserting nothing. An empty report is a valid result.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speccle",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Speccle's deterministic tooling: spec lint and the oracle-strength heatmap.
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Speccle's deterministic tooling: spec lint and the oracle-strength heatmap. No LLM, bar the opt-in CI review driver.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Matt Alton",
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"skills"
|
|
30
|
+
"skills",
|
|
31
|
+
"lenses"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc -p tsconfig.build.json && node ../../scripts/bundle-skills.mjs",
|
|
34
|
-
"prepublishOnly": "pnpm build && pnpm test",
|
|
35
|
+
"prepublishOnly": "node ../../scripts/check-plugin-version.mjs --release && pnpm build && pnpm test",
|
|
35
36
|
"typecheck": "tsc --noEmit",
|
|
36
37
|
"test": "vitest run",
|
|
37
38
|
"coverage": "vitest run --coverage",
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Review a change set the way the outer loop does — fan a panel of lenses over the working diff, let `oracle risk` decide whether a human is needed, then below that threshold fix what the lenses find (re-running the checks-gate after each fix and reverting any that goes red) and above it report findings and stop, closing with an overruleable summary that proposes a remedy for each finding — recalled from and recorded to the remedy record — and records the change to the calibration record. Use when the user wants to review a branch or the pending change, asks to "review this", "review my changes", "run the lenses", find-and-fix issues before a PR, or check a change set for correctness, security, accessibility, architecture, performance, test-quality, or house-convention problems.
|
|
4
|
+
allowed-tools: Read(/${CLAUDE_PLUGIN_ROOT}/skills/*/references/**)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# review
|
|
8
|
+
|
|
9
|
+
The outer loop. Its unit is the **change set** — the branch or PR's pending change,
|
|
10
|
+
not a slice — which is why it is separate from `feature`. It fans a panel of **lenses**
|
|
11
|
+
over the working diff, and — gated by the change's **risk score** — either fixes what it
|
|
12
|
+
finds or reports and stops for a human. It generates feedback; it does not itself change
|
|
13
|
+
the inner loop.
|
|
14
|
+
|
|
15
|
+
Speccle's words are fixed and mandatory: "change set", not "diff" or "PR"; "lens", not
|
|
16
|
+
"reviewer" or "rule"; "finding", not "issue" or "comment"; "remedy", not "fix" alone;
|
|
17
|
+
"risk score" / "review threshold" / "floor"; "escalate", never "de-escalate". Speccle's
|
|
18
|
+
posture is **announce, never gate**: below the threshold the fixes just land and the
|
|
19
|
+
summary reports them; there is no "apply? y/n".
|
|
20
|
+
|
|
21
|
+
## 1. Resolve the oracle
|
|
22
|
+
|
|
23
|
+
Once, in this order, and reuse what works:
|
|
24
|
+
|
|
25
|
+
1. `<repo-root>/node_modules/.bin/speccle` — the repo's own pinned copy. A devDependency
|
|
26
|
+
is never on `PATH` in this shell, so test for the file; in a monorepo check the package
|
|
27
|
+
you are working in as well as the root. It wins over a global install: the pin is a
|
|
28
|
+
committed choice, and rules change between versions.
|
|
29
|
+
2. `speccle` on `PATH` — a global install.
|
|
30
|
+
3. Otherwise, from a clone of the speccle repo, run it from source — Node ≥ 24 executes
|
|
31
|
+
TypeScript directly: `node <speccle-repo>/packages/oracle/src/cli.ts`.
|
|
32
|
+
|
|
33
|
+
If none resolves, point the user at the install steps in Speccle's README and stop. If
|
|
34
|
+
`.speccle/lenses/` is missing, the repo was never initialized for review — offer
|
|
35
|
+
`speccle init` and stop.
|
|
36
|
+
|
|
37
|
+
## 2. The change set
|
|
38
|
+
|
|
39
|
+
Everything here runs over the same change set: the working tree's pending change against
|
|
40
|
+
its last commit. Read it once — `git status --porcelain --untracked-files=all` for the
|
|
41
|
+
files, `git diff HEAD` for the lines — and hand the changed files and their diff to every
|
|
42
|
+
lens. A finding must anchor to a **changed line**; a lens does not audit the whole repo.
|
|
43
|
+
|
|
44
|
+
If the working tree is clean, there is nothing to review — say so and stop.
|
|
45
|
+
|
|
46
|
+
## 3. Risk — the authority gate, before any fixing
|
|
47
|
+
|
|
48
|
+
Fix authority is not a global setting; it depends on the change. Decide it first, because
|
|
49
|
+
it decides whether the panel may fix at all.
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
<oracle> risk <path> --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This returns the deterministic **floor**: `score`, `threshold`, `humanRequired`, and the
|
|
56
|
+
spec-aware `signals` that fired, each with its evidence. Then apply the **risk lens**
|
|
57
|
+
(`.speccle/lenses/risk.md`) yourself: read the diff for the consequential change no signal
|
|
58
|
+
could see, and emit its one escalation decision. The lens may **raise** the floor or
|
|
59
|
+
require a human outright; it may **never lower** either. A concern that the floor is too
|
|
60
|
+
high goes in the summary for the human — you do not act on it.
|
|
61
|
+
|
|
62
|
+
The verdict a human is required when **any** of these holds: `risk.humanRequired` is true,
|
|
63
|
+
the risk lens set `requireHuman`, or `score + raiseFloorBy ≥ threshold`.
|
|
64
|
+
|
|
65
|
+
- **Below the threshold, no escalation** → `review` may fix unasked (§5).
|
|
66
|
+
- **At or above, or the lens escalated** → `review` still runs the panel to report, but
|
|
67
|
+
**fixes nothing**; a human is required.
|
|
68
|
+
|
|
69
|
+
Both the computed floor and any escalation must be legible in the summary — a risk verdict
|
|
70
|
+
with no visible reasoning is not auditable.
|
|
71
|
+
|
|
72
|
+
## 4. The lens panel — fan out over the diff
|
|
73
|
+
|
|
74
|
+
Fan out one subagent per lens in `.speccle/lenses/*.md`, in parallel — this is the local
|
|
75
|
+
driver, so the panel runs as subagents in this session, no API key. Skip two:
|
|
76
|
+
|
|
77
|
+
- `risk.md` — already applied in §3; it escalates authority, it is not a finding lens.
|
|
78
|
+
- `house-conventions.md` **when it still reads as the shipped template** — its body carries
|
|
79
|
+
the `speccle:lens-template` marker until a repo authors it. An unauthored lens is inert;
|
|
80
|
+
skip it and note that the repo has not written its house-conventions lens yet.
|
|
81
|
+
|
|
82
|
+
Each subagent's prompt carries the lens file verbatim (its stance, what to look for, how to
|
|
83
|
+
report), the changed files and their diff, and the instruction to return findings anchored
|
|
84
|
+
to changed lines only, in the shape the lens describes: `path:line`, severity, what, why,
|
|
85
|
+
the fix, and a remedy route. A lens that finds nothing returns an empty list — the common,
|
|
86
|
+
valid result. Collect every finding across the panel.
|
|
87
|
+
|
|
88
|
+
## 5. Fix — only with authority, and never without the checks-gate
|
|
89
|
+
|
|
90
|
+
If §3 said a human is required, **fix nothing**; go to §6 and report.
|
|
91
|
+
|
|
92
|
+
Otherwise fix each finding, smallest change first, and guard every one:
|
|
93
|
+
|
|
94
|
+
1. Apply the fix the finding names.
|
|
95
|
+
2. **Re-run the checks-gate** — resolve the same oracle and run `<oracle> lint` on any
|
|
96
|
+
governed slice the fix touched, `<oracle> claims <root>`, `<oracle> verify <root>` (the
|
|
97
|
+
cross-file invariants), and the project's own test suite.
|
|
98
|
+
3. **Green** — keep the fix. **Red** — **revert it, do not salvage**: restore the file to its
|
|
99
|
+
pre-fix state and carry the finding forward as unfixed. A fix that cannot pass the gate is
|
|
100
|
+
the human's call, not a thing to patch around.
|
|
101
|
+
|
|
102
|
+
Batch sensibly, but keep each fix independently revertible — a fix you cannot back out on
|
|
103
|
+
its own is one you cannot safely apply. The fixes join the working change set; `review` does
|
|
104
|
+
**not** commit — the change set is the human's or the inner loop's to land.
|
|
105
|
+
|
|
106
|
+
## 6. Route each finding to a remedy
|
|
107
|
+
|
|
108
|
+
For every finding — fixed, reverted, or left for the human — name the durable artefact that
|
|
109
|
+
stops the class recurring, routed on **what the finding is**, never on a count, the same
|
|
110
|
+
posture `strengthen` takes to a surviving mutant. The **remedy record** is the meta loop's
|
|
111
|
+
memory of that routing: consult it before deriving, add to it after applying.
|
|
112
|
+
|
|
113
|
+
**Recall first — answer a repeat the way you answered it before.** Give the finding a short,
|
|
114
|
+
stable class handle (kebab-case, e.g. `missing-model-roundtrip-test`) and ask the record:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
<oracle> remedy recall <path> --class <handle>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A hit is the known-correct remedy — reuse its route and artefact rather than re-deriving, so
|
|
121
|
+
the same finding gets the same answer. A miss means route it fresh:
|
|
122
|
+
|
|
123
|
+
- a deterministic, cross-file invariant → an `oracle verify` check in `.speccle/checks/`.
|
|
124
|
+
- behaviour a criterion should own → a new acceptance criterion, and its tagged test, in the
|
|
125
|
+
owning `SPEC.md`.
|
|
126
|
+
- a dimension a lens missed or stated too weakly → sharpen the lens in `.speccle/lenses/`,
|
|
127
|
+
the house-conventions lens above all.
|
|
128
|
+
- a genuine one-off → none.
|
|
129
|
+
|
|
130
|
+
**Then record what you applied**, so the next review recalls it:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
<oracle> remedy record <path> --class <handle> --finding <what> --fix <what you did> \
|
|
134
|
+
--route <check|criterion|lens|none> [--artefact <ref>]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`--artefact` names the prevention home — the `.speccle/checks/` or `.speccle/lenses/` path, or
|
|
138
|
+
the `SPEC.md` criterion id — and is required for every route but `none`. Record the remedies you
|
|
139
|
+
**applied** this run; a finding left for the human (§3 required one, so nothing was fixed) is
|
|
140
|
+
proposed in the summary, never recorded — the record holds enacted remedies, not intentions, the
|
|
141
|
+
same honesty §8 keeps for the verdict.
|
|
142
|
+
|
|
143
|
+
## 7. Summary — announce, never gate
|
|
144
|
+
|
|
145
|
+
Render one screen, in product voice. The human rules by reading it, not by being asked:
|
|
146
|
+
|
|
147
|
+
1. **The risk verdict** — `score` against `threshold`, the floor's fired signals with their
|
|
148
|
+
evidence, any risk-lens escalation with its reason, and whether a human was required.
|
|
149
|
+
2. **Every finding** — its lens, `path:line`, severity, what and why, and its outcome:
|
|
150
|
+
**fixed**, **reverted** (the checks-gate went red — say what failed), or **left for the
|
|
151
|
+
human** (a human was required, so nothing was fixed).
|
|
152
|
+
3. **The proposed remedy** per finding (§6).
|
|
153
|
+
4. **The checks-gate now** — green or red, named.
|
|
154
|
+
|
|
155
|
+
There is no approval gate. Below the threshold the fixes already landed and the human
|
|
156
|
+
reverts what the summary makes them regret; at or above it nothing was fixed and the findings
|
|
157
|
+
await the human. Either way the summary is the whole interaction — overruleable, never a
|
|
158
|
+
"proceed? y/n".
|
|
159
|
+
|
|
160
|
+
## 8. Calibrate — record the change, then surface the evidence
|
|
161
|
+
|
|
162
|
+
The review is the outer loop's eyes; the **calibration record** is its memory — one entry per
|
|
163
|
+
reviewed change, the evidence a **review threshold** earns a move on. Close every review by
|
|
164
|
+
adding this change to it.
|
|
165
|
+
|
|
166
|
+
The floor is known (§3) and the panel's outcome is known (§4). The one thing neither can
|
|
167
|
+
answer is the honest verdict — _did this change actually need a human?_ That answer is the
|
|
168
|
+
human's, and the record is worthless without it, so never invent it:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
<oracle> calibrate record <path> --found-real <true|false> --needed-human <true|false> [--escalated]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- `--found-real` — true when any lens returned a real finding this run; you know this.
|
|
175
|
+
- `--escalated` — include it only if the risk lens escalated the floor in §3.
|
|
176
|
+
- `--needed-human` — the human's honest call, whatever the floor said. With a human here to
|
|
177
|
+
answer, ask once and record it; with none, print the command for them to run and stop. A
|
|
178
|
+
threshold that rose on a guessed verdict is worse than one that never rose.
|
|
179
|
+
|
|
180
|
+
Then read the record back and fold its proposals into the close:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
<oracle> calibrate report <path>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
It names the signals that have never fired on a change that mattered, the signals that fired
|
|
187
|
+
on every change that needed a human, and the threshold the record would support. These are
|
|
188
|
+
**evidence, not instructions**: escalation is free, but only a human moves a weight or the
|
|
189
|
+
review threshold, and only on this evidence. Surface them so ignoring them is a choice — never
|
|
190
|
+
act on them here.
|