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.
- package/README.md +290 -3
- package/dist/calibration.js +156 -0
- package/dist/changeset.js +141 -0
- package/dist/claims.js +5 -1
- package/dist/cli.js +596 -4
- package/dist/config.js +117 -0
- package/dist/doctor.js +141 -0
- package/dist/init.js +5 -4
- package/dist/lenses.js +68 -0
- package/dist/remedy.js +133 -0
- package/dist/render.js +323 -2
- package/dist/reviewinit.js +139 -0
- package/dist/reviewrun.js +516 -0
- package/dist/risk.js +228 -0
- package/dist/skills.js +63 -0
- package/dist/update.js +48 -0
- 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 +7 -5
- package/skills/carve-feature/SKILL.md +183 -0
- package/skills/carve-feature/references/convention.md +239 -0
- package/skills/conform/SKILL.md +129 -0
- package/skills/conform/references/convention.md +239 -0
- package/skills/feature/SKILL.md +103 -0
- package/skills/implement-feature/SKILL.md +152 -0
- package/skills/implement-feature/references/convention.md +239 -0
- package/skills/plan-feature/SKILL.md +125 -0
- package/skills/plan-feature/references/convention.md +239 -0
- package/skills/review/SKILL.md +190 -0
- package/skills/spec-feature/SKILL.md +163 -0
- package/skills/spec-feature/references/convention.md +239 -0
- package/skills/strengthen/SKILL.md +184 -0
- package/skills/strengthen/references/heatmap.md +39 -0
- package/skills/strengthen/references/stack.md +23 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<!-- Generated from docs/convention.md by scripts/sync-plugin-references.mjs — do not edit.
|
|
2
|
+
Edit the source and run `pnpm sync:plugin-refs`. -->
|
|
3
|
+
|
|
4
|
+
# The Speccle convention
|
|
5
|
+
|
|
6
|
+
The written contract every skill and every Speccle tool implements. Terminology is
|
|
7
|
+
defined in `CONTEXT.md`; decisions behind this shape are in
|
|
8
|
+
`docs/adr`.
|
|
9
|
+
|
|
10
|
+
## The feature folder
|
|
11
|
+
|
|
12
|
+
A feature is a directory **named for the feature**, owning one vertical slice. The name
|
|
13
|
+
announces the slice — `scanner/`, never an unnamed catch-all like `src/` or `lib/` —
|
|
14
|
+
and holds even when a project has only one feature: the slice is self-announcing, and a
|
|
15
|
+
second feature has an obvious home beside it.
|
|
16
|
+
|
|
17
|
+
Every feature folder has the same shape:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
scanner/
|
|
21
|
+
├── SPEC.md ← the acceptance criteria
|
|
22
|
+
├── CONTEXT.md ← the feature's language (a glossary, nothing else)
|
|
23
|
+
├── AGENTS.md ← how an agent works the slice
|
|
24
|
+
├── decisions/ ← the feature's ADRs, one file per decision
|
|
25
|
+
│ └── 0001-<slug>.md
|
|
26
|
+
└── src/
|
|
27
|
+
├── scanner.ts
|
|
28
|
+
└── scanner.test.ts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The root holds the markdown contract and nothing else. All code and tests sit in
|
|
32
|
+
`src/`, tests beside the code they defend, and the code subfolder is always named
|
|
33
|
+
`src` — every feature folder in every project opens the same way. `decisions/` appears
|
|
34
|
+
with the feature's first decision; the other four entries are the floor, even for a
|
|
35
|
+
tiny feature.
|
|
36
|
+
|
|
37
|
+
Inside `src/`, subfolders are allowed but not the default
|
|
38
|
+
(ADR-0037). Keep
|
|
39
|
+
`src/` flat while it holds **ten files or fewer** directly — count the code and test
|
|
40
|
+
files sitting in it, subfolders excluded. The file that would make it eleven triggers
|
|
41
|
+
grouping: gather the code into shallow (prefer one level), purpose-named subfolders,
|
|
42
|
+
and the same ten-file limit applies within each subfolder. A `src/` that has grown into
|
|
43
|
+
a pile is often a signal to split the slice into siblings instead; nest when it is
|
|
44
|
+
genuinely one cohesive feature that simply carries many files. Tests stay beside the
|
|
45
|
+
code they defend at whatever depth it sits.
|
|
46
|
+
|
|
47
|
+
Everything about the feature lives inside the subtree. An agent landing in the folder
|
|
48
|
+
needs nothing else to understand it — and `AGENTS.md` is where it starts reading.
|
|
49
|
+
|
|
50
|
+
## SPEC.md
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
---
|
|
54
|
+
key: CHECKOUT
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# Checkout
|
|
58
|
+
|
|
59
|
+
Optional intro prose about the feature.
|
|
60
|
+
|
|
61
|
+
## [CHECKOUT-1] When a line item is taxed, tax rounds half-up
|
|
62
|
+
|
|
63
|
+
Tax is computed per line item and rounded half-up to 2dp before summing.
|
|
64
|
+
|
|
65
|
+
Edge cases:
|
|
66
|
+
|
|
67
|
+
- three line items of £1.99 at 20% → £1.20 tax; taxing the £5.97 basket total would give
|
|
68
|
+
£1.19
|
|
69
|
+
|
|
70
|
+
## [CHECKOUT-2] An empty basket totals zero
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
|
|
75
|
+
1. **Frontmatter declares the feature key**: `key: <KEY>` where `<KEY>` matches
|
|
76
|
+
`[A-Z][A-Z0-9]{1,9}`. Keys are unique across the repo.
|
|
77
|
+
2. **Each criterion is an H2**: `## [KEY-n] <statement>`. The statement is one testable
|
|
78
|
+
clause — single behaviour, no weasel words, measurable.
|
|
79
|
+
3. **Statements speak product, defaulting to a When/Then shape**
|
|
80
|
+
(ADR-0032):
|
|
81
|
+
"When X, Y" — the trigger, then the outcome, readable without seeing code. A simple
|
|
82
|
+
invariant may stay plain declarative (`An empty basket totals zero`). Code-level
|
|
83
|
+
precision that is contractual — exact messages, ordering, regexes — lives in the
|
|
84
|
+
body, never the statement.
|
|
85
|
+
4. **The body is free.** Rationale, edge cases, examples — anything about that one
|
|
86
|
+
behaviour. A body may be empty.
|
|
87
|
+
5. **Ids are names, not order.** Document position carries order. A new criterion takes
|
|
88
|
+
the next never-used number under its key. An id is never renumbered or reused —
|
|
89
|
+
deleting `[CHECKOUT-2]` retires the number forever.
|
|
90
|
+
6. **H2s in a spec are criteria.** Non-criterion structure belongs in intro prose,
|
|
91
|
+
criterion bodies, or `CONTEXT.md`.
|
|
92
|
+
|
|
93
|
+
## CONTEXT.md (per feature)
|
|
94
|
+
|
|
95
|
+
The feature's glossary, in the style of this repo's own root `CONTEXT.md`: the domain
|
|
96
|
+
language — each term defined once, with an _Avoid_ line naming the synonyms not to use.
|
|
97
|
+
|
|
98
|
+
A glossary only. Decisions never live here — they are ADRs in `decisions/`
|
|
99
|
+
(ADR-0021). The
|
|
100
|
+
boundary: about a word → `CONTEXT.md`; about one behaviour → that criterion's body in
|
|
101
|
+
`SPEC.md`; a choice spanning criteria → `decisions/`.
|
|
102
|
+
|
|
103
|
+
## AGENTS.md (per feature)
|
|
104
|
+
|
|
105
|
+
The slice's agent-facing entry point, following the cross-tool convention of the same
|
|
106
|
+
name. It orients in one screenful, stating only what the folder cannot show:
|
|
107
|
+
|
|
108
|
+
- what the slice does — a sentence or two
|
|
109
|
+
- how to run its tests (and any other commands the slice needs)
|
|
110
|
+
- where the contract lives: `SPEC.md` for the criteria, `CONTEXT.md` for the language,
|
|
111
|
+
`decisions/` for the choices — and that tests claim criteria by `[KEY-n]` token
|
|
112
|
+
|
|
113
|
+
Facts about _working_ the slice belong here; facts about the feature's _behaviour_
|
|
114
|
+
belong in `SPEC.md`. Duplicating either in the other is drift waiting to happen.
|
|
115
|
+
|
|
116
|
+
## decisions/ (per feature)
|
|
117
|
+
|
|
118
|
+
The feature's architecture decision records: choices that span criteria, one numbered
|
|
119
|
+
file per decision — `decisions/0001-<slug>.md`, in the same form as any ADR (status,
|
|
120
|
+
context, decision, consequences). The folder appears with the first decision. A choice
|
|
121
|
+
about one behaviour is not a decision record — it is that criterion's body.
|
|
122
|
+
|
|
123
|
+
## Test linking
|
|
124
|
+
|
|
125
|
+
A test defends a criterion when the criterion id appears anywhere in its **full
|
|
126
|
+
concatenated name** — enclosing group titles included. One
|
|
127
|
+
`describe('[CHECKOUT-1] tax rounding', …)` claims every test inside it. Mutation and
|
|
128
|
+
coverage reports already carry full names, so the join is mechanical.
|
|
129
|
+
|
|
130
|
+
What counts as a test file, and what counts as its name, is the **test dialect**'s
|
|
131
|
+
business (ADR-0038).
|
|
132
|
+
Where a framework gives a test a string name, the id is claimed bracketed —
|
|
133
|
+
`[CHECKOUT-1]`. Where the name _is_ an identifier, the id takes its identifier-safe
|
|
134
|
+
spelling: `func test_CHECKOUT_1_taxRounds()` claims `CHECKOUT-1`. The two spellings are
|
|
135
|
+
one id, not two — `SPEC.md` headings and every report use the bracketed form
|
|
136
|
+
(ADR-0039).
|
|
137
|
+
|
|
138
|
+
## Spec discovery
|
|
139
|
+
|
|
140
|
+
Tools find every `SPEC.md` under the target root. Directories named `node_modules`,
|
|
141
|
+
`dist`, `fixtures`, or `__fixtures__` — and dot-directories — are never entered. The
|
|
142
|
+
skip list is fixed, not configuration
|
|
143
|
+
(ADR-0016): a project that
|
|
144
|
+
fixtures deliberately dirty specs for its own tests still lints clean at its root,
|
|
145
|
+
and a feature directory may not take one of the skipped names.
|
|
146
|
+
|
|
147
|
+
## Lint
|
|
148
|
+
|
|
149
|
+
`oracle lint` enforces this contract deterministically. Fixed rules, one severity, no
|
|
150
|
+
configuration. The rule set:
|
|
151
|
+
|
|
152
|
+
| Rule | Judges |
|
|
153
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
154
|
+
| `missing-key` | Frontmatter `key` absent or malformed |
|
|
155
|
+
| `key-collision` | Two specs declare the same key |
|
|
156
|
+
| `key-mismatch` | A criterion id's key differs from the spec's declared key |
|
|
157
|
+
| `malformed-id` | H2 without a well-formed `[KEY-n]` token |
|
|
158
|
+
| `duplicate-id` | The same id appears twice |
|
|
159
|
+
| `empty-statement` | Criterion heading has a token but no statement |
|
|
160
|
+
| `weasel-wording` | Statement hedges (`should`, `appropriately`, `as expected`, …) |
|
|
161
|
+
| `compound-criterion` | Statement contains more than one testable clause |
|
|
162
|
+
| `unmeasurable` | Statement asserts nothing observable |
|
|
163
|
+
| `code-voice` | Statement reads as code rather than product language |
|
|
164
|
+
|
|
165
|
+
Quality rules (`weasel-wording`, `compound-criterion`, `unmeasurable`, `code-voice`)
|
|
166
|
+
judge the heading statement only — the body is never linted.
|
|
167
|
+
|
|
168
|
+
`compound-criterion` exempts one bare `and`/`or` — a compound noun phrase names one
|
|
169
|
+
thing (`restores stock and credit`). A second bare conjunction in the main clause flags:
|
|
170
|
+
it reads as a list of behaviours (`restores stock and credits the customer and emails
|
|
171
|
+
them`). Conjunctions inside a condition (`when the card is expired and the retry limit
|
|
172
|
+
is reached`) qualify one outcome and are not counted.
|
|
173
|
+
|
|
174
|
+
`unmeasurable` never allow-lists the verbs a statement may use. It flags a closed list of
|
|
175
|
+
predicates that name activity without an outcome (`is handled`, `works`, `supports`, …)
|
|
176
|
+
and main clauses that name a property (`The dashboard is beautiful`). Any other verb
|
|
177
|
+
passes, including a domain verb the rule has never seen (`a refund credits the
|
|
178
|
+
customer`). It under-flags by design
|
|
179
|
+
(ADR-0010).
|
|
180
|
+
|
|
181
|
+
`code-voice` judges the raw statement — here a code span is the strongest signal, not a
|
|
182
|
+
stripped literal. Signals, first match wins: a code span; a file path, recognised by a
|
|
183
|
+
closed extension list (`cjs`, `css`, `html`, `js`, `json`, `jsx`, `md`, `mjs`, `sh`,
|
|
184
|
+
`toml`, `ts`, `tsx`, `txt`, `yaml`, `yml`); an identifier — camelCase with at least two
|
|
185
|
+
leading lowercase letters (brand names like iPhone pass), snake_case, or call
|
|
186
|
+
parentheses. Plain acronyms (JSON, HTTP) and unlisted extensions pass — the rule
|
|
187
|
+
under-flags by design
|
|
188
|
+
(ADR-0032).
|
|
189
|
+
|
|
190
|
+
## Supported stacks
|
|
191
|
+
|
|
192
|
+
Speccle is **multi-language across a supported set, not language-agnostic**. A repo
|
|
193
|
+
declares which test dialect it is on; it never declares how that dialect works, so a
|
|
194
|
+
clean `claims` run means the same thing in every repo
|
|
195
|
+
(ADR-0038). An
|
|
196
|
+
unsupported stack is unsupported, and says so.
|
|
197
|
+
|
|
198
|
+
Two frontiers, and they are not the same:
|
|
199
|
+
|
|
200
|
+
- **The contract, the lint, and the claim join** reach every supported dialect.
|
|
201
|
+
`ts-vitest` reads `describe`/`it`/`test` titles out of `*.test.*` and `*.spec.*`
|
|
202
|
+
files. `swift` reads Swift Testing `@Test("…")` and `@Suite("…")` display names, and
|
|
203
|
+
XCTest `func test…()` identifiers, out of `*Tests.swift` files and anything under a
|
|
204
|
+
`Tests` directory.
|
|
205
|
+
- **The oracle-strength heatmap** reaches TypeScript. It needs a mutation tool that
|
|
206
|
+
attributes coverage per test: StrykerJS with `perTest` coverage analysis, plus
|
|
207
|
+
Istanbul `json-summary` coverage. Elsewhere the reports are simply missing, and the
|
|
208
|
+
heatmap degrades on its own.
|
|
209
|
+
|
|
210
|
+
Adding a language is a Speccle change with tests behind it, never a regex a repo
|
|
211
|
+
supplies.
|
|
212
|
+
|
|
213
|
+
## Repo facts
|
|
214
|
+
|
|
215
|
+
Two things Speccle needs are true about a repo, not decidable by Speccle: which **test
|
|
216
|
+
dialect** the repo is on, and the **suite command** that runs its tests — the checks gate
|
|
217
|
+
cannot infer `xcodebuild test -scheme Ladder`. They live in `.speccle/config.json` at the
|
|
218
|
+
repo root:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"dialect": "swift",
|
|
223
|
+
"suite": "xcodebuild test -scheme Ladder",
|
|
224
|
+
"overrides": [{ "path": "web", "dialect": "ts-vitest", "suite": "pnpm test" }]
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`speccle init` auto-detects the dialect — `swift` from a `Package.swift`, `ts-vitest`
|
|
229
|
+
otherwise — and a default suite command, then **writes the result down**: the written
|
|
230
|
+
record is the source of truth, never the detection, and `init` never overwrites a config
|
|
231
|
+
it finds. A mixed-language tree corrects the defaults under a subtree with `overrides`,
|
|
232
|
+
where the longest matching path wins
|
|
233
|
+
(ADR-0040).
|
|
234
|
+
|
|
235
|
+
This is the one thing Speccle reads from a repo, and it never grows into a knob on
|
|
236
|
+
judgement. "Speccle has no configuration" is really **"Speccle has no configurable
|
|
237
|
+
judgement"**: a dialect name or a shell command is a fact — Speccle cannot know it and
|
|
238
|
+
there is nothing to game — while a lint rule or the claim join decides whether you pass,
|
|
239
|
+
and stays fixed so a clean run keeps meaning the same thing in every repo.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature
|
|
3
|
+
description: Build or change a feature end to end — plan it with the human (settling the open decisions and capturing each one in the slice's docs), gate once on plan approval, then run spec and implement in their own subagent sessions, close with the deterministic checks-gate (lint, claims, tests), and commit on green without asking. Use when the user wants to build, implement, or spec a feature, extend or amend an existing one, hands over a ticket or prose description to turn into a slice, or says "speccle this", "implement this feature", "add this to the checkout feature".
|
|
4
|
+
allowed-tools: Read(/${CLAUDE_PLUGIN_ROOT}/skills/*/references/**)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# feature
|
|
8
|
+
|
|
9
|
+
The pipeline: **plan → gate → spec → implement → checks → commit**. Planning is a
|
|
10
|
+
dialogue in this session; spec and implement each run in their own subagent session;
|
|
11
|
+
the checks are deterministic oracle commands; the commit is automatic. The pipeline
|
|
12
|
+
has exactly **one human gate** — plan approval — and it sits before anything is
|
|
13
|
+
built. Everything after it runs unattended.
|
|
14
|
+
|
|
15
|
+
Invoke each sibling skill under whatever namespace this skill itself runs in —
|
|
16
|
+
`speccle:plan-feature` when installed as the plugin, bare `plan-feature` when the
|
|
17
|
+
skills live project-level.
|
|
18
|
+
|
|
19
|
+
Speccle's words are fixed and mandatory: "criterion id", not "tag"; "amend", not
|
|
20
|
+
"edit" or "update"; "checks-gate", not "review step"; "spec summary", not "approval
|
|
21
|
+
gate".
|
|
22
|
+
|
|
23
|
+
## 1. Plan — in this session, with the human
|
|
24
|
+
|
|
25
|
+
Invoke `plan-feature` with the Skill tool, in this session — never in a subagent:
|
|
26
|
+
planning is a dialogue, and a subagent cannot talk to the human. It explores, routes
|
|
27
|
+
(**new** / **amend** / **carve**), settles the open key decisions one question at a
|
|
28
|
+
time, captures each settled decision into the slice's docs the moment it lands, and
|
|
29
|
+
ends with the plan summary.
|
|
30
|
+
|
|
31
|
+
If the route is **carve**, stop here: hand the user to `carve-feature` — the
|
|
32
|
+
behaviour already runs, and governing it is a different job. A mixed request is a
|
|
33
|
+
carve followed by a `feature` run in amend mode, never one pass.
|
|
34
|
+
|
|
35
|
+
## 2. The gate — plan approval
|
|
36
|
+
|
|
37
|
+
The plan summary is the pipeline's one approval. Where plan mode is available, enter
|
|
38
|
+
plan mode and present the summary as the plan — approving it launches everything
|
|
39
|
+
after. Where it is not, show the summary in chat and wait for the go-ahead.
|
|
40
|
+
|
|
41
|
+
- **Approval starts the machine.** If any decision was captured only in the summary
|
|
42
|
+
because writes were forbidden during planning (the session was already in plan
|
|
43
|
+
mode), write those docs now, before the spec stage — the subagents read the folder,
|
|
44
|
+
not this conversation.
|
|
45
|
+
- **An unattended run cannot approve.** Do not hang: continue with the plan as
|
|
46
|
+
announced, every open decision defaulted to its recommendation and flagged — in the
|
|
47
|
+
plan summary and again at the end. A defaulted decision is never silent.
|
|
48
|
+
|
|
49
|
+
## 3. Spec — a subagent, off the plan
|
|
50
|
+
|
|
51
|
+
Launch a subagent whose prompt carries the full hand-off: the route, the feature
|
|
52
|
+
folder, the key, the scope, each settled decision (agreed or defaulted), and any
|
|
53
|
+
per-behaviour choice the plan noted for a criterion body. Instruct it to invoke
|
|
54
|
+
`spec-feature` with that input and to return the criteria — ids and statements,
|
|
55
|
+
retirements included — once the folder lints clean.
|
|
56
|
+
|
|
57
|
+
The subagent never saw the planning dialogue. Everything it needs must be in the
|
|
58
|
+
prompt or on disk — that is the design, not a limitation: if the hand-off feels too
|
|
59
|
+
thin to spec from, the plan was too thin, and the fix is a better plan, not a fatter
|
|
60
|
+
prompt.
|
|
61
|
+
|
|
62
|
+
Show the returned criteria to the human as an FYI — reading five headings is cheap
|
|
63
|
+
insurance before implementation spends real effort on a wrong spec. Do not wait for
|
|
64
|
+
approval; an interjection is a change request (re-enter the stage it names), silence
|
|
65
|
+
is consent.
|
|
66
|
+
|
|
67
|
+
## 4. Implement — a subagent, off the spec
|
|
68
|
+
|
|
69
|
+
Launch a second subagent: the prompt names the feature folder, the route (tracer on
|
|
70
|
+
**new**, none on **amend**), and any retired ids whose tests must go. Instruct it to
|
|
71
|
+
invoke `implement-feature`. It works from the linted spec and the slice's docs — the
|
|
72
|
+
folder is the brief — and returns which criteria went green plus anything it changed
|
|
73
|
+
in the spec along the way.
|
|
74
|
+
|
|
75
|
+
## 5. The checks-gate — deterministic, no judgement
|
|
76
|
+
|
|
77
|
+
Run in this session, resolving the oracle as every Speccle skill does — the repo's own
|
|
78
|
+
`<repo-root>/node_modules/.bin/speccle` first (a devDependency is never on
|
|
79
|
+
`PATH`, so test for the file), else `speccle` on `PATH`, else
|
|
80
|
+
`node <speccle-repo>/packages/oracle/src/cli.ts`:
|
|
81
|
+
|
|
82
|
+
1. `<oracle> lint <feature-folder>` — exit `0`.
|
|
83
|
+
2. `<oracle> claims <target-root>` — exit `0`: every criterion claimed by a test
|
|
84
|
+
name, every claimed id real.
|
|
85
|
+
3. The project's test suite — green.
|
|
86
|
+
|
|
87
|
+
A failure returns to the implement subagent with the failing output — not to the
|
|
88
|
+
human. If the same failure comes back twice, stop and show the human what is stuck.
|
|
89
|
+
There is no judgement here and no oracle-strength measurement — the heatmap is
|
|
90
|
+
`strengthen`'s job, on its own cadence, and this gate stays seconds cheap.
|
|
91
|
+
|
|
92
|
+
## 6. Summary, then commit — no pause
|
|
93
|
+
|
|
94
|
+
Render one screen, in product voice: every criterion drafted, amended, or retired
|
|
95
|
+
(ids and statements), what claims it, the implement subagent's notes, and every
|
|
96
|
+
decision that was defaulted rather than agreed. This is the spec summary for the
|
|
97
|
+
whole run — the human rules by reading it, not by being asked.
|
|
98
|
+
|
|
99
|
+
Then commit, automatically: stage the feature folder and only the files this run
|
|
100
|
+
touched, and write the message the repo's conventions ask for, derived from the
|
|
101
|
+
route — a new slice is introduced, an amendment names the behaviour that changed. No
|
|
102
|
+
"commit? y/n": on a green gate the pause is ceremony, and a commit the summary makes
|
|
103
|
+
the human regret is one revert away.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement-feature
|
|
3
|
+
description: Implement an already-specced slice — write token-tagged tests first and the code that makes them green, one criterion at a time, tracer criterion first on a new slice. Use when a linted SPEC.md exists and the user wants it implemented, wants tests and code for drafted criteria, or says "make the slice green", "implement the spec". For a feature that has no spec yet, the whole job — plan, spec, implement — is the feature skill.
|
|
4
|
+
allowed-tools: Read(/${CLAUDE_PLUGIN_ROOT}/skills/*/references/**)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# implement-feature
|
|
8
|
+
|
|
9
|
+
Write the tagged tests and the code that satisfy an existing, linted `SPEC.md` — one
|
|
10
|
+
criterion at a time, tests first. Stage 3 of the `feature` pipeline, and complete on
|
|
11
|
+
its own when the spec already exists — hand-written to the convention, or drafted
|
|
12
|
+
earlier by `spec-feature`.
|
|
13
|
+
|
|
14
|
+
In the pipeline this skill runs in its own subagent session: the linted spec and the
|
|
15
|
+
slice's docs are the whole brief — read them; there is no earlier conversation to
|
|
16
|
+
consult. The final message is the hand-back in §3.
|
|
17
|
+
|
|
18
|
+
**This skill starts from a spec.** Handed a feature with no conventioned `SPEC.md`,
|
|
19
|
+
say so and point at the `feature` pipeline (or `spec-feature` for the contract alone)
|
|
20
|
+
rather than drafting one here — drafting has its own skill, and an unlinted spec must
|
|
21
|
+
not reach this one. Verify the precondition rather than assuming it:
|
|
22
|
+
`<oracle> lint <feature-folder>` exits `0` (resolve the oracle as every Speccle skill
|
|
23
|
+
does: the repo's own `<repo-root>/node_modules/.bin/speccle` first — a
|
|
24
|
+
devDependency is never on `PATH`, so test for the file — else `speccle` on
|
|
25
|
+
`PATH`, else `node <speccle-repo>/packages/oracle/src/cli.ts`; if none resolves, point
|
|
26
|
+
at the README's install steps and stop).
|
|
27
|
+
|
|
28
|
+
The folder shape and test-linking rules are fixed by the convention, bundled beside
|
|
29
|
+
this skill at `${CLAUDE_SKILL_DIR}/references/convention.md`.
|
|
30
|
+
|
|
31
|
+
Speccle's words are fixed and mandatory: "criterion id", not "tag"; "statement", not
|
|
32
|
+
"title"; "spec summary", not "approval gate".
|
|
33
|
+
|
|
34
|
+
**This skill does not measure oracle strength.** A slice can finish here
|
|
35
|
+
well-specified and weakly defended; closing that gap is `strengthen`'s job, on its
|
|
36
|
+
own cadence — the pipeline's checks-gate verifies lint, claims, and green tests, not
|
|
37
|
+
how hard the tests bite.
|
|
38
|
+
|
|
39
|
+
## 1. Scope the work
|
|
40
|
+
|
|
41
|
+
Everything this skill writes lands in the feature's `src/` — tests beside the code
|
|
42
|
+
they defend; the feature root stays pure markdown.
|
|
43
|
+
|
|
44
|
+
Keep `src/` flat while it holds **ten files or fewer** directly (code and tests
|
|
45
|
+
together — count the entries, don't judge the crowding). The file that would make it
|
|
46
|
+
eleven triggers grouping: gather the code into shallow, purpose-named subfolders — one
|
|
47
|
+
level, tests still beside the code they defend at that depth — and the same
|
|
48
|
+
ten-file limit then applies inside each subfolder. Before nesting, ask whether the
|
|
49
|
+
pile is really one slice: a `src/` that has grown two clearly separate concerns is
|
|
50
|
+
usually two slices, and splitting into a sibling folder beats burying the seam under
|
|
51
|
+
subfolders. Nest when it is genuinely one cohesive feature that just carries many
|
|
52
|
+
files.
|
|
53
|
+
|
|
54
|
+
- **New slice** (no code in `src/` yet): every criterion is unimplemented; the tracer
|
|
55
|
+
rule below applies.
|
|
56
|
+
- **Amended slice** (the spec changed over running code):
|
|
57
|
+
implement only the criteria no test yet claims, in document order. There is **no
|
|
58
|
+
tracer** — the slice's path already runs, so there is nothing to prove; the same
|
|
59
|
+
reason a carve has none.
|
|
60
|
+
A retired id takes its tests with it: delete them, and confirm the code they
|
|
61
|
+
defended is either still promised by a live criterion or removed too.
|
|
62
|
+
|
|
63
|
+
Find the unclaimed criteria mechanically, not by eye:
|
|
64
|
+
`<oracle> claims <feature-folder>` joins every criterion to the test names that
|
|
65
|
+
carry its token — `unclaimed` lists the ids still owed a test, `unknownClaims` the
|
|
66
|
+
tokens pointing at no criterion (a retired id someone's test still claims). One
|
|
67
|
+
caveat, because the scan is static: only string-literal `describe`/`it`/`test`
|
|
68
|
+
titles count, so write the `[KEY-n]` token as literal text, never built up at
|
|
69
|
+
runtime.
|
|
70
|
+
|
|
71
|
+
## 2. Red-green, one criterion at a time
|
|
72
|
+
|
|
73
|
+
Tests first, then the code that makes them pass — and **never more than one criterion
|
|
74
|
+
at a time**. Write the criterion's tests, run them, and watch them fail before
|
|
75
|
+
writing any code: a test that has never failed proves nothing. Then make it green,
|
|
76
|
+
then move on. Your instinct will be to build a layer at a time: every criterion's
|
|
77
|
+
parsing, then every criterion's calculation, then every criterion's persistence.
|
|
78
|
+
Resist it. A feature built that way does not execute until the last layer lands, and
|
|
79
|
+
by then the mistake is expensive.
|
|
80
|
+
|
|
81
|
+
### Fire the tracer criterion first (new slice only)
|
|
82
|
+
|
|
83
|
+
Pick the criterion whose passing test exercises the thinnest complete path through
|
|
84
|
+
every layer the feature touches — entry to exit, nothing stubbed. Choose it for
|
|
85
|
+
**path length, not importance**: the plainest success case, the one carrying the
|
|
86
|
+
least logic. An edge case or a rejection is never the tracer; it short-circuits the
|
|
87
|
+
very layers it was meant to prove. "When a line item is taxed, tax rounds half-up"
|
|
88
|
+
traces the path; "When a basket exceeds 100 line items, checkout rejects it" throws
|
|
89
|
+
before reaching it.
|
|
90
|
+
|
|
91
|
+
Make it green. Then say so: name the criterion, and name the layers its test now runs
|
|
92
|
+
through. Do not stop for approval — the green test _is_ the feedback, and this skill
|
|
93
|
+
has no stops.
|
|
94
|
+
|
|
95
|
+
When a feature has one layer — a pure function, a formatter — there is no path to
|
|
96
|
+
trace. The first criterion is the tracer, nothing special happens, and you should not
|
|
97
|
+
dress it up as though something did.
|
|
98
|
+
|
|
99
|
+
### Then thicken
|
|
100
|
+
|
|
101
|
+
Take the remaining criteria in document order, one at a time, each written against a
|
|
102
|
+
skeleton that already runs. The suite is green at every criterion boundary — if it is
|
|
103
|
+
not, finish that criterion before starting the next.
|
|
104
|
+
|
|
105
|
+
If a criterion cannot be made green without dragging two others in with it, stop and
|
|
106
|
+
look at the spec. That is a compound criterion that lint let through, and finding it
|
|
107
|
+
now is worth more than the detour costs. Amending the spec mid-implement is
|
|
108
|
+
`spec-feature`'s §2 in miniature: next never-used ids, re-lint, announce the change.
|
|
109
|
+
|
|
110
|
+
### Tagging tests
|
|
111
|
+
|
|
112
|
+
A test claims a criterion when the `[KEY-n]` token appears in its **full concatenated
|
|
113
|
+
name** — enclosing `describe` titles count. One
|
|
114
|
+
`describe('[CHECKOUT-1] tax rounding', …)` claims every test nested inside it, which
|
|
115
|
+
is the idiom to reach for.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
describe("[CHECKOUT-1] tax rounding", () => {
|
|
119
|
+
it("rounds each line's tax half-up before summing", () => {
|
|
120
|
+
const basket = [line("a", 199), line("b", 199), line("c", 199)];
|
|
121
|
+
expect(checkout(basket, 0.2).tax).toBe(120);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Write tests that would fail if the behaviour broke, not tests that merely execute the
|
|
127
|
+
code. Reach for the criterion body's edge cases — they are there because someone
|
|
128
|
+
thought the naïve implementation would miss them.
|
|
129
|
+
|
|
130
|
+
## 3. Confirm done
|
|
131
|
+
|
|
132
|
+
Done means all four, verified rather than assumed:
|
|
133
|
+
|
|
134
|
+
1. The feature folder is named for the feature and has the convention's shape:
|
|
135
|
+
`SPEC.md`, `CONTEXT.md`, `AGENTS.md` at the root, code and tests in `src/`.
|
|
136
|
+
2. `<oracle> lint <feature-folder>` exits `0`.
|
|
137
|
+
3. `<oracle> claims <feature-folder>` exits `0` — every criterion claimed, and no
|
|
138
|
+
test still claiming a retired id. An id nobody claims is an unimplemented
|
|
139
|
+
criterion, so go back to §2.
|
|
140
|
+
4. The **whole project's** test suite is green, not just the slice's — on an amended
|
|
141
|
+
slice, the pre-existing tests are exactly the ones a change breaks.
|
|
142
|
+
|
|
143
|
+
Do not report done on a spec with an unclaimed criterion.
|
|
144
|
+
|
|
145
|
+
Hand back by naming what went green: each criterion implemented this run, id and
|
|
146
|
+
statement. If the spec changed mid-run (a compound criterion found in §2), that is a
|
|
147
|
+
spec change and gets the **spec summary** treatment: list it for the human to rule
|
|
148
|
+
on.
|
|
149
|
+
|
|
150
|
+
There is no oracle-strength check here, deliberately. Say so when you hand back: the
|
|
151
|
+
slice is green, and how well it is _defended_ is a question `strengthen` answers, on
|
|
152
|
+
its own cadence.
|