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.
@@ -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.12.0",
4
- "description": "Speccle's deterministic tooling: spec lint and the oracle-strength heatmap. Never calls an LLM.",
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.