speccle 0.11.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.
Files changed (41) hide show
  1. package/README.md +290 -3
  2. package/dist/calibration.js +156 -0
  3. package/dist/changeset.js +141 -0
  4. package/dist/claims.js +5 -1
  5. package/dist/cli.js +596 -4
  6. package/dist/config.js +117 -0
  7. package/dist/doctor.js +141 -0
  8. package/dist/init.js +5 -4
  9. package/dist/lenses.js +68 -0
  10. package/dist/remedy.js +133 -0
  11. package/dist/render.js +323 -2
  12. package/dist/reviewinit.js +139 -0
  13. package/dist/reviewrun.js +516 -0
  14. package/dist/risk.js +228 -0
  15. package/dist/skills.js +63 -0
  16. package/dist/update.js +48 -0
  17. package/dist/verify.js +92 -0
  18. package/lenses/accessibility.md +39 -0
  19. package/lenses/architecture.md +38 -0
  20. package/lenses/correctness.md +36 -0
  21. package/lenses/house-conventions.md +37 -0
  22. package/lenses/performance.md +40 -0
  23. package/lenses/risk.md +45 -0
  24. package/lenses/security.md +42 -0
  25. package/lenses/test-quality.md +39 -0
  26. package/package.json +7 -5
  27. package/skills/carve-feature/SKILL.md +183 -0
  28. package/skills/carve-feature/references/convention.md +239 -0
  29. package/skills/conform/SKILL.md +129 -0
  30. package/skills/conform/references/convention.md +239 -0
  31. package/skills/feature/SKILL.md +103 -0
  32. package/skills/implement-feature/SKILL.md +152 -0
  33. package/skills/implement-feature/references/convention.md +239 -0
  34. package/skills/plan-feature/SKILL.md +125 -0
  35. package/skills/plan-feature/references/convention.md +239 -0
  36. package/skills/review/SKILL.md +190 -0
  37. package/skills/spec-feature/SKILL.md +163 -0
  38. package/skills/spec-feature/references/convention.md +239 -0
  39. package/skills/strengthen/SKILL.md +184 -0
  40. package/skills/strengthen/references/heatmap.md +39 -0
  41. package/skills/strengthen/references/stack.md +23 -0
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: strengthen
3
+ description: Evaluate how well a feature's tests defend its code — check report freshness first, hand the human the exact coverage and mutation commands when reports are stale or missing, then read the per-criterion oracle-strength heatmap and route every surviving mutant to a killing test or a sharper criterion, marking the heatmap evaluated when done. Use when the user wants to strengthen tests, check oracle strength, asks whether tests would catch a bug, wants to kill surviving mutants, or says "strengthen this", "how well is this defended", "run the heatmap".
4
+ allowed-tools: Read(/${CLAUDE_PLUGIN_ROOT}/skills/*/references/**)
5
+ ---
6
+
7
+ # strengthen
8
+
9
+ A slice can be green and badly defended: `implement-feature` leaves it that way on
10
+ purpose, and the `feature` pipeline's checks-gate never measures it. This skill is the
11
+ periodic audit that closes the gap — catching what no deterministic check sees:
12
+ behaviour no criterion covers, and tests that run without asserting.
13
+
14
+ The division of labour is fixed: **the human runs the expensive commands; this skill
15
+ evaluates what they produce.** Coverage and mutation are minutes of compute — never
16
+ run them yourself; hand over the exact commands and stop. Everything this skill runs
17
+ itself finishes in seconds.
18
+
19
+ Measure oracle strength per criterion, then act on every **surviving mutant** — the
20
+ exact code change no test noticed. The routing call is the whole job; §5 is how to
21
+ make it.
22
+
23
+ Speccle's words are mandatory: "oracle strength", not "mutation score"; "surviving
24
+ mutant", not "missed mutation"; "test-fitting", not "gaming the score"; "fresh" /
25
+ "stale" / "evaluated" as the freshness check reports them.
26
+
27
+ ## 1. Resolve the oracle
28
+
29
+ Once, in this order, and reuse what works:
30
+
31
+ 1. `<repo-root>/node_modules/.bin/speccle` — the repo's own pinned copy, put
32
+ there by `strength init`. A devDependency is never on `PATH` in this shell, so test
33
+ for the file; in a monorepo check the package you are working in as well as the
34
+ root. It wins over a global install: the pin is a committed choice, and lint rules
35
+ change between versions.
36
+ 2. `speccle` on `PATH` — a global install.
37
+ 3. Otherwise, from a clone of the speccle repo, run it from source — Node ≥ 24 executes
38
+ TypeScript directly, so no build is needed:
39
+ `node <speccle-repo>/packages/oracle/src/cli.ts`.
40
+
41
+ If none resolves, point the user at the install steps in Speccle's README and stop.
42
+
43
+ ## 2. Check freshness before anything else
44
+
45
+ ```sh
46
+ <oracle> strength <path> --check
47
+ ```
48
+
49
+ Seconds, no reports run: each report comes back **fresh**, **stale** (naming the
50
+ slice file that post-dates it), or **missing**, plus whether the current heatmap was
51
+ already **evaluated** and the marker path that records it. `--json` gives
52
+ `{ root, mutation, coverage, evaluated, marker }`; pass `--mutation` / `--coverage`
53
+ when the target writes reports somewhere non-default. Exit `0` means both fresh.
54
+
55
+ Route on what it says:
56
+
57
+ - **Fresh and not evaluated** — there is a heatmap waiting to be read. Go to §4.
58
+ - **Fresh and evaluated** — this heatmap has already been worked. Say so and stop;
59
+ re-evaluate only if the human asks for a second pass.
60
+ - **Stale or missing** — hand the human the exact commands and stop. Name the
61
+ target's own coverage and mutation commands (read its `package.json` scripts), and
62
+ when the work concerns one slice, scope the mutation run to it — mutating
63
+ `features/<name>/src/**` is minutes where the whole target is tens of them:
64
+
65
+ ```sh
66
+ pnpm coverage
67
+ pnpm mutation -- --mutate 'features/basket/src/**/*.ts'
68
+ ```
69
+
70
+ The run resumes when the human says the reports are in — re-run `--check` to
71
+ confirm rather than taking it on faith.
72
+
73
+ ## 3. Require the stack, and never install it silently
74
+
75
+ Oracle strength needs to know which tests covered each mutant, and that constrains the
76
+ target to TypeScript + vitest + StrykerJS. When `--check` says missing and the stack
77
+ itself is absent, check the four requirements in
78
+ `${CLAUDE_SKILL_DIR}/references/stack.md` before asking anyone to run anything.
79
+
80
+ **If something is missing, stop and offer.** Show what is absent, then offer the one
81
+ command that provisions all of it — `speccle strength init <path>` — and wait for
82
+ the user's go-ahead before running it. Never write to the target's `package.json`,
83
+ lockfile, or test config without the user agreeing first — this skill measures someone
84
+ else's project; it does not quietly re-tool it.
85
+
86
+ ## 4. Read the heatmap
87
+
88
+ ```sh
89
+ <oracle> strength <path>
90
+ ```
91
+
92
+ The reports are already on disk — §2 proved it. Show the human heatmap to the user: a
93
+ bar and `killed/covered` per criterion, each criterion's survivors listed beneath it. Use
94
+ `--json` for the routing work and never scrape the human output — the output shape and
95
+ the full `--json` schema are in `${CLAUDE_SKILL_DIR}/references/heatmap.md`. `strength`
96
+ is a report, not a gate: it exits `0` with survivors present, `2` on a bad or missing
97
+ report.
98
+
99
+ Four of the `--json` fields — `unclaimed`, `unknownClaims`, `unclaimedMutants`,
100
+ `staticMutants` — are **not** routing work: none is a survivor to route. Say them out
101
+ loud before you start; `references/heatmap.md` gives each one's shape and what it asks of
102
+ you instead.
103
+
104
+ ## 5. Route every surviving mutant
105
+
106
+ Never route a criterion, and never route a percentage — a criterion under 100% always has
107
+ at least one survivor to act on, and one at 100% has none, so the number carries nothing
108
+ the survivor list does not.
109
+ Route each survivor, and ask one question of it:
110
+
111
+ > Can I write an assertion that **follows from the criterion's statement**, and that fails
112
+ > against this mutant?
113
+
114
+ **Machine path — yes.** The statement already promises the behaviour this mutant breaks;
115
+ the suite just never checked it. Write the killing test under that criterion id, exactly
116
+ as `implement-feature` does — the `[KEY-n]` token in the full concatenated name. Run the
117
+ test suite — it must be green — and batch the machine-path work across survivors: each
118
+ killing test makes the reports staler, and only the human's re-run confirms the kills.
119
+
120
+ **Human path — no, and the behaviour matters.** No criterion entails it, so the spec is
121
+ silent or too vague to test. Draft a sharper statement, or the new criterion the spec
122
+ owes, taking the next never-used number under the key. Draft it in the target's `SPEC.md`
123
+ itself — the spec edit _is_ the draft, and lint reads the real file. Lint it clean,
124
+ **announce it** — id and statement — and keep going: the survivor takes the machine path
125
+ under the new id.
126
+ The human rules on every criterion this run added at the summary in §6; an overruled
127
+ one is reverted from the spec along with its test, returning its survivor to the report.
128
+
129
+ **Equivalent mutant — no, and no test ever could.** The mutated code means the same thing.
130
+ Annotate it where it lives and move on:
131
+
132
+ ```ts
133
+ // Stryker disable next-line ArithmeticOperator: x * 1 and x / 1 are the same value
134
+ ```
135
+
136
+ Argue it from the semantics. "The test is awkward to write" is not equivalence, and
137
+ reaching for this exit more than rarely means the answer was the machine or human path.
138
+
139
+ ### The trap
140
+
141
+ `[CHECKOUT-3]` above says _"checkout rejects it"_ of an oversized basket. Its two
142
+ survivors are the error's message string and `this.name`; the test asserts
143
+ `toThrow(TooManyLineItems)`, so `instanceof` reads neither.
144
+
145
+ Asserting the message text under `[CHECKOUT-3]` kills both mutants and turns the bar
146
+ green. Do not. "Rejects" promises a refusal and says nothing about what the refusal
147
+ _says_ — the test would pass because someone typed that string, and the next person to
148
+ reword the message gets a failure that teaches them nothing. That is test-fitting the
149
+ mutant, and it is how mutation testing rots into a score chase.
150
+
151
+ These survivors are the human path. The spec owes a criterion about what the rejection
152
+ tells the caller. Draft it, announce it, test it — and watch `[CHECKOUT-3]` reach 100%
153
+ untouched, because a kill counts for every criterion covering the mutant.
154
+
155
+ If oracle strength rose but the spec did not change, check that you did not fit a test to
156
+ a mutant.
157
+
158
+ ## 6. Confirm, mark evaluated, hand back
159
+
160
+ Confirmation needs fresh evidence, and producing it is the human's run: after your
161
+ last edit, hand back the same commands from §2 and stop. When the new reports land,
162
+ `--check` again, re-read the heatmap, and verify every machine-path survivor is gone.
163
+ A heatmap from before the change is not evidence.
164
+
165
+ When the evaluation is done — routed, confirmed, or explicitly parked — touch the
166
+ marker `--check` named, so the next run knows this heatmap was read:
167
+
168
+ ```sh
169
+ touch <marker-path>
170
+ ```
171
+
172
+ Then report, verified rather than assumed:
173
+
174
+ 1. The test suite is green.
175
+ 2. Every survivor you took the machine path on is gone from the fresh report.
176
+ 3. Oracle strength, before and after, headline and per criterion.
177
+ 4. The **spec summary**: every criterion this run added to the spec, id and statement.
178
+ The human rules here — an overruled criterion is reverted along with its killing
179
+ test, returning its survivor to the report.
180
+ 5. What is left, and why: each remaining survivor is annotated as equivalent, with its
181
+ argument, or named as a gap awaiting the human's ruling.
182
+
183
+ A survivor you cannot explain is not finished — say so plainly rather than letting a
184
+ nicer headline imply otherwise.
@@ -0,0 +1,39 @@
1
+ <!-- Generated from docs/strength-heatmap.md by scripts/sync-plugin-references.mjs — do not edit.
2
+ Edit the source and run `pnpm sync:plugin-refs`. -->
3
+
4
+ # The oracle-strength heatmap
5
+
6
+ `<oracle> strength <path>` prints a bar and `killed/covered` per criterion, with each
7
+ criterion's survivors listed beneath it:
8
+
9
+ ```
10
+ features/checkout/SPEC.md
11
+ CHECKOUT-1 ████████████████████ 100.0% 14/14 When a line item is taxed, tax rounds half-up to 2dp
12
+ CHECKOUT-3 ██████████████████░░ 88.2% 15/17 When a basket exceeds 100 line items, checkout rejects it
13
+ features/checkout/checkout.ts:13:11 StringLiteral → ``
14
+ features/checkout/checkout.ts:14:17 StringLiteral → ""
15
+ ```
16
+
17
+ Use `--json` for the routing work: `{ root, strength, lineCoverage, features[], unclaimed,
18
+ unknownClaims, unclaimedMutants, staticMutants }`, each criterion carrying `survivors[]`
19
+ with `file`, `line`, `column`, `mutator`, `replacement`. `strength` is a report, not a
20
+ gate: it exits `0` with survivors present, `2` on a bad or missing report. Never scrape
21
+ the human output.
22
+
23
+ ## The four fields that are not routing work
24
+
25
+ Say these out loud before you start — none of them is a survivor to route.
26
+
27
+ - **`unclaimed`** — a criterion no test's name carries. It scores nothing rather than
28
+ zero. It needs a test written against it before it can be weak; that is
29
+ `implement-feature`'s job, not a survivor to route.
30
+ - **`unknownClaims`** — a test claims an id no spec declares. Someone renamed or deleted a
31
+ criterion. Fix the test name or restore the criterion.
32
+ - **`unclaimedMutants`** — code the criteria do not reach at all, each entry naming its
33
+ `file`, `line`, `column`, `mutator`, `replacement`. Not weak criteria; a map of the
34
+ regions the spec is silent about. Do not route these as survivors — name the region to
35
+ the human and let them decide whether a feature owes a spec there.
36
+ - **`staticMutants`** — mutants that run at module load (word lists, regex literals), so
37
+ per-test coverage can attribute them to no criterion: `{ killed, survivors[] }`. The
38
+ killed ones are fine — some test noticed. A survivor is a real gap, but it can never be
39
+ claimed by a criterion id; name it to the human alongside the unclaimed mutants.
@@ -0,0 +1,23 @@
1
+ <!-- Generated from docs/strength-stack.md by scripts/sync-plugin-references.mjs — do not edit.
2
+ Edit the source and run `pnpm sync:plugin-refs`. -->
3
+
4
+ # The oracle-strength stack
5
+
6
+ Oracle strength needs to know which tests covered each mutant, and that constrains the
7
+ target to TypeScript + vitest + StrykerJS. When `--check` says missing and the stack
8
+ itself is absent, check for all four before asking anyone to run anything:
9
+
10
+ | Requirement | Where |
11
+ | --------------------------------- | ------------------------------------ |
12
+ | `@stryker-mutator/core` + runner | `package.json` devDependencies |
13
+ | `coverageAnalysis: "perTest"` | `stryker.config.json` |
14
+ | the `json` reporter, and its path | `stryker.config.json` `jsonReporter` |
15
+ | istanbul provider, `json-summary` | `vitest.config.ts` `test.coverage` |
16
+
17
+ `perTest` is the hard one: without it Stryker never records `coveredBy`, the join has
18
+ nothing to walk back to criterion ids, and `oracle strength` exits `2` saying so.
19
+
20
+ `speccle strength init <path>` provisions all of it — it installs the missing
21
+ devDependencies and writes the preset configs, keeping any that already exist. It runs
22
+ only on the user's explicit go-ahead; this skill measures someone else's project, it does
23
+ not quietly re-tool it.