@skitterbyte/skitterspec-linear 11.0.0 → 13.0.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/MIGRATION.md +260 -10
- package/README.md +32 -2
- package/assets/claude-md-section.md +48 -2
- package/assets/commands/spec-connect.md +2 -2
- package/assets/commands/spec-live.md +2 -2
- package/assets/core/SETUP.md +21 -3
- package/assets/core/env.config.json.example +9 -3
- package/assets/core/env.config.md +102 -30
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/linear.config.md +67 -8
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +224 -15
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +64 -13
- package/assets/skills/spec-bug/SKILL.md +193 -27
- package/assets/skills/spec-cancel/SKILL.md +99 -8
- package/assets/skills/spec-claim/SKILL.md +114 -0
- package/assets/skills/spec-complete/SKILL.md +123 -22
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +202 -22
- package/assets/skills/spec-init/SKILL.md +49 -9
- package/assets/skills/spec-linear-setup/SKILL.md +86 -7
- package/assets/skills/spec-list/SKILL.md +218 -0
- package/assets/skills/spec-next/SKILL.md +300 -7
- package/assets/skills/spec-push/SKILL.md +45 -22
- package/assets/skills/spec-review/SKILL.md +59 -11
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +426 -66
- package/assets/skills/spec-status/SKILL.md +24 -2
- package/assets/skills/spec-sync/SKILL.md +47 -11
- package/assets/skills/spec-to-main/SKILL.md +42 -20
- package/package.json +11 -7
- package/src/cli.js +1710 -80
- package/src/env/building.js +143 -0
- package/src/env/classify.js +91 -0
- package/src/env/config.js +57 -9
- package/src/env/provision.js +192 -19
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +296 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/gating.js +155 -0
- package/src/init.js +124 -2
- package/src/prompts.js +10 -1
- package/src/vendor/linear/api.js +104 -1
- package/src/vendor/linear/cli-sync.js +874 -17
- package/src/vendor/linear/config.js +8 -0
- package/src/vendor/linear/credentials.js +94 -0
- package/src/vendor/linear/doctor.js +35 -0
- package/src/vendor/linear/identity.js +105 -0
- package/src/vendor/linear/mcp.js +26 -0
- package/src/vendor/sync-core/index.js +6 -2
- package/src/vendor/sync-core/src/compare.js +74 -5
- package/src/vendor/sync-core/src/normalize.js +30 -0
- package/src/vendor/sync-core/src/push.js +11 -1
- package/src/vendor/sync-core/src/write.js +38 -0
- package/LICENSE +0 -21
|
@@ -5,16 +5,20 @@ description: Push a spec up to its linked Linear issue (repo → Linear, one-way
|
|
|
5
5
|
|
|
6
6
|
# /spec-push — send a spec up to Linear (one-way)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
13
|
+
Repo → Linear. The repo is the **source of truth**; Linear is a
|
|
14
|
+
**generated mirror**. A spec is a Linear **issue**; each phase is a
|
|
15
|
+
**sub-issue** (a child issue), and the phase file is mirrored into that
|
|
16
|
+
sub-issue's description as written — its prose, its sections, and its tasks as
|
|
17
|
+
a read-only checklist, never as issues of their own. Only the phase's h1 and
|
|
18
|
+
`> **Status:**` line are left out, because both are pushed as fields of their
|
|
19
|
+
own (the sub-issue's title and state). This skill computes what changed since
|
|
20
|
+
the last push and applies it — it never reads Linear content back or merges. A
|
|
21
|
+
person editing the mirror in Linear will see it overwritten on the next push.
|
|
18
22
|
|
|
19
23
|
**Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
|
|
20
24
|
tell the user how to enable Linear sync and stop.
|
|
@@ -68,7 +72,7 @@ get past a failing check.
|
|
|
68
72
|
## 3. Get the plan from the engine
|
|
69
73
|
|
|
70
74
|
```
|
|
71
|
-
skitterspec spec-sync
|
|
75
|
+
skitterspec spec-sync plan <spec> --workspace-states <file> --json
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
The engine prints a JSON **plan** (no network, no remote read):
|
|
@@ -190,10 +194,10 @@ skitterspec spec-sync verify <spec> --stored <file>
|
|
|
190
194
|
|
|
191
195
|
It compares against what the engine sent, ignoring the reformatting Linear
|
|
192
196
|
legitimately applies (renumbered ordered lists, `-`→`*`, collapsed table
|
|
193
|
-
separators, checkbox case, whitespace) and reporting only lost or altered
|
|
194
|
-
characters**. Relay any divergence — it prints both sides around the
|
|
195
|
-
difference. It exits 0 either way: the repo is unaffected and still
|
|
196
|
-
this is a warning, not a failure.
|
|
197
|
+
separators, checkbox case, whitespace) and reporting only lost or altered
|
|
198
|
+
**word characters**. Relay any divergence — it prints both sides around the
|
|
199
|
+
first difference. It exits 0 either way: the repo is unaffected and still
|
|
200
|
+
correct, so this is a warning, not a failure.
|
|
197
201
|
|
|
198
202
|
This is **not a pull**. Nothing read here is merged, stamped, or written
|
|
199
203
|
anywhere; the repo remains the only source of truth. Do it before step 5 so a
|
|
@@ -231,14 +235,33 @@ branch so the mirror-link rides in the PR.
|
|
|
231
235
|
|
|
232
236
|
## 6. Report
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
recorded. There is no pull — Linear is a generated mirror.
|
|
238
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
239
|
+
the shape; this section carries only what is specific here.
|
|
237
240
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
**Verdicts**
|
|
242
|
+
|
|
243
|
+
- `✅` — the mirror matches the repo and the snapshot is recorded.
|
|
244
|
+
- `⚠️` — pushed, with something worth knowing: Linear stored different text, a
|
|
245
|
+
project picker that could not be offered, `phasesDeferred` phases not yet
|
|
246
|
+
minted.
|
|
247
|
+
- `❌` — it wrote some objects and failed on another. Name what exists in Linear
|
|
248
|
+
now; `apply` stamps each id as it goes, so the re-run is an update, never a
|
|
249
|
+
duplicate.
|
|
250
|
+
- `⏸` — no config, no `linear_identifier`, an unvalidated state list, or a
|
|
251
|
+
pre-9.0 mirror. Nothing was written.
|
|
252
|
+
|
|
253
|
+
**Fields:** `Tracker` · `Follow-ups` · `Next`
|
|
254
|
+
|
|
255
|
+
`Tracker` carries what was created and updated — the spec issue and its
|
|
256
|
+
sub-issues — **the transport that did it**, and that the snapshot was recorded.
|
|
257
|
+
|
|
258
|
+
**Say the transport, every time.** On the API path you never saw the
|
|
259
|
+
descriptions, so "pushed 12 sub-issues" is the engine's report rather than your
|
|
260
|
+
observation, and that distinction is the difference between a fact and a
|
|
261
|
+
paraphrase. If it warned that Linear stored different text, relay it: the repo
|
|
262
|
+
is still correct and a re-push overwrites the mirror.
|
|
263
|
+
|
|
264
|
+
There is no pull — Linear is a generated mirror.
|
|
242
265
|
|
|
243
266
|
### Picking the Linear Project
|
|
244
267
|
|
|
@@ -5,6 +5,11 @@ description: Re-validate an existing spec against the current codebase — detec
|
|
|
5
5
|
|
|
6
6
|
# /spec-review — bring a spec back in sync with the codebase
|
|
7
7
|
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
8
13
|
Specs rot: the code moves on while a spec sits in the backlog or pauses
|
|
9
14
|
mid-build. This skill re-validates a spec against the **current** code and
|
|
10
15
|
rewrites the stale parts so it's safe to act on. It plans only — it does not
|
|
@@ -44,6 +49,19 @@ trust the spec's own wording — verify:
|
|
|
44
49
|
- **Backward compatibility.** Re-confirm the planned API/schema changes are still
|
|
45
50
|
additive/safe given the current code (see the project's compatibility rules).
|
|
46
51
|
|
|
52
|
+
### Release gating (only when configured)
|
|
53
|
+
|
|
54
|
+
**Only when `specs/.core/gating.config.json` exists.** Run
|
|
55
|
+
`skitterspec gating check <spec>`. A spec it names has no recorded gating
|
|
56
|
+
decision — or a bare `none` that records nothing — and that is
|
|
57
|
+
**drift like any other**: the question was never asked, or its answer was lost. Grill for it in
|
|
58
|
+
step 3 and write the header in step 4, exactly as you would a stale file path.
|
|
59
|
+
|
|
60
|
+
**This check is advisory.** It reports; it never refuses, and nothing below is
|
|
61
|
+
conditional on it. A spec written before the project adopted gating has no header
|
|
62
|
+
and is not broken — turning this into a gate would accuse the very specs the
|
|
63
|
+
feature was designed not to disturb.
|
|
64
|
+
|
|
47
65
|
## 3. Grill only where a decision is needed
|
|
48
66
|
|
|
49
67
|
Where the drift forces a choice (the old approach no longer fits, a referenced
|
|
@@ -54,12 +72,12 @@ resolve it by reading the code, do that instead of asking.
|
|
|
54
72
|
|
|
55
73
|
## 4. Update the spec
|
|
56
74
|
|
|
57
|
-
- Rewrite stale **Decisions** / **Solution overview** and refresh the
|
|
58
|
-
map** in `00-overview.md`, and stale **tasks** in the phase files,
|
|
59
|
-
match the current code and the resolved questions. Add/remove tasks
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
completed `[x]` history**.
|
|
75
|
+
- Rewrite stale **Decisions** / **Solution overview** and refresh the
|
|
76
|
+
**Impact map** in `00-overview.md`, and stale **tasks** in the phase files,
|
|
77
|
+
so they match the current code and the resolved questions. Add/remove tasks
|
|
78
|
+
within a phase file; add a new phase by creating a `0N-<slug>.md` file
|
|
79
|
+
**and** a matching overview index row, or drop a dead phase by removing both.
|
|
80
|
+
Keep the index and files in sync; **preserve completed `[x]` history**.
|
|
63
81
|
- **A phase's status is the `⬜`/`🔄`/`✅` on its H1** — `# Phase 2 — <goal> 🔄`.
|
|
64
82
|
It is mirrored in that file's `> **Status:**` line and in the matching
|
|
65
83
|
`00-overview.md` phase-index row, but the **heading is the authoritative one**:
|
|
@@ -102,6 +120,12 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
|
|
|
102
120
|
Do not roll anything back, and do not stop to ask.
|
|
103
121
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
104
122
|
unlinked, or failed with the reason.
|
|
123
|
+
- **There is no unassign step, and that is not an omission.** With `assignee` in
|
|
124
|
+
`sync.fieldOwnership`, the projection derives it from the spec's lifecycle
|
|
125
|
+
bucket — so moving the folder to `complete`/`cancelled` is *itself* what
|
|
126
|
+
releases the issue, and the push above carries it like any other field. The
|
|
127
|
+
`linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
|
|
128
|
+
they record who actioned the work, which outlives who is holding it.
|
|
105
129
|
|
|
106
130
|
### Why it sits here
|
|
107
131
|
|
|
@@ -112,12 +136,36 @@ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
|
|
|
112
136
|
its folder bucket. Push while the folder is still in `in-progress/` and the
|
|
113
137
|
issue is set to the state the spec is *leaving*.
|
|
114
138
|
- **Before the commit**, because the push stamps ids into the spec and writes a
|
|
115
|
-
snapshot under `specs/.core/`.
|
|
116
|
-
with the status change; push after it instead and those files are left
|
|
139
|
+
snapshot under `specs/.core/`. Push after the commit instead and both are left
|
|
117
140
|
uncommitted, which makes `spec-env integrate` refuse to land the branch.
|
|
118
141
|
|
|
142
|
+
The snapshot is reached **by name**, not by breadth. It is a declared
|
|
143
|
+
`spec.companionPaths` entry (`specs/.core/linear-base/{identifier}.base.json`),
|
|
144
|
+
so `spec-env stage` returns it among this spec's owned paths and the commit below
|
|
145
|
+
names it. This is why the commit no longer stages `specs/` wholesale: the one
|
|
146
|
+
file outside the spec's folder that had to be swept up is addressable, so the
|
|
147
|
+
breadth bought nothing and cost another session's work.
|
|
148
|
+
|
|
119
149
|
## 5. Report
|
|
120
150
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
the
|
|
151
|
+
Do **not** `git commit` unless the user asks.
|
|
152
|
+
|
|
153
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
154
|
+
the shape; this section carries only what is specific here.
|
|
155
|
+
|
|
156
|
+
**Verdicts**
|
|
157
|
+
|
|
158
|
+
- `✅` — checked against the code and brought up to date; safe to `/spec-start`.
|
|
159
|
+
- `⚠️` — updated, but dropped back to `Draft`: questions are open that only the
|
|
160
|
+
operator can answer. Name them.
|
|
161
|
+
- `⏸` — no such spec, or nothing to review. Nothing changed.
|
|
162
|
+
|
|
163
|
+
**Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
|
|
164
|
+
|
|
165
|
+
`Built` is the drift found and what you changed about it — a renamed file, an
|
|
166
|
+
API that moved, a task the code already does. `Spec` carries the status the
|
|
167
|
+
review leaves it in, which is the answer to "can I start this?".
|
|
168
|
+
|
|
169
|
+
**Drift found and not fixed is a `Follow-up`, not a silence.** A spec this skill
|
|
170
|
+
declared reviewed is one nobody will re-read; something it noticed and left
|
|
171
|
+
undone has to leave the session in writing or it did not happen.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-reviewed
|
|
3
|
+
description: Pick up the review you approved on the page — run it bare to pick up the single waiting pass, paste the six-digit code off the page ("/spec-reviewed 608223") to name one exactly, and be asked which only when two are waiting. Use when the user says "/spec-reviewed", "I approved it", "I've reviewed it", "pick up my review", "I pressed approve", or otherwise says they have finished reviewing a rendered diff.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /spec-reviewed — pick up the review you just approved
|
|
8
|
+
|
|
9
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
10
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
11
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
12
|
+
> skill ends with.
|
|
13
|
+
|
|
14
|
+
You read the diff somewhere else, pressed a verdict, and the engine is holding
|
|
15
|
+
the pass. This is how it gets picked up.
|
|
16
|
+
|
|
17
|
+
## What this is for, now that the picker exists
|
|
18
|
+
|
|
19
|
+
`/spec-next` and `/spec-diff` both end in a picker carrying these same endings,
|
|
20
|
+
so the usual way to pick a review up is to choose one there. This command is
|
|
21
|
+
what covers the case that picker cannot.
|
|
22
|
+
|
|
23
|
+
**A picker is consumed when the turn ends.** Nobody scrolls back and answers one
|
|
24
|
+
an hour later. So a review read over lunch, a session cleared, a fresh terminal
|
|
25
|
+
tomorrow morning — in every one of those the control is gone and the pass is
|
|
26
|
+
still waiting, and this is the way in. That is a narrower job than it once had,
|
|
27
|
+
and not an absent one.
|
|
28
|
+
|
|
29
|
+
**It is also the only claim path the harness itself enforces** — see below.
|
|
30
|
+
Deleting this skill as redundant with the picker would leave no path that has
|
|
31
|
+
the mechanism at all, only paths that have the property.
|
|
32
|
+
|
|
33
|
+
## Why this is user-only, and why that is the whole point
|
|
34
|
+
|
|
35
|
+
`disable-model-invocation: true` is not ergonomics here — it is the enforcement
|
|
36
|
+
of `/spec-diff` step 0's central rule:
|
|
37
|
+
**never claim a pass you were not asked to claim**.
|
|
38
|
+
|
|
39
|
+
That rule exists because a review pass can be POSTed by anything that reaches
|
|
40
|
+
the page, and what it cannot reach is **this conversation**. So a stranger's pass
|
|
41
|
+
sits in the holding area forever — unless an agent goes and fetches it, which is
|
|
42
|
+
exactly what happened once: an agent found a waiting approval, read its code off
|
|
43
|
+
disk, claimed it, and reported the round-trip working.
|
|
44
|
+
|
|
45
|
+
Prose alone did not prevent that. The harness does —
|
|
46
|
+
**the model cannot invoke this skill** — so a pass is only ever picked up
|
|
47
|
+
because a person typed the command. Typing it *is* the human signal. A later edit that makes this skill
|
|
48
|
+
model-invocable does not make it more convenient — it removes the only thing
|
|
49
|
+
standing between a stray approval and someone's repo.
|
|
50
|
+
|
|
51
|
+
## 1. Resolve the spec
|
|
52
|
+
|
|
53
|
+
Bare, exactly as every other bare command in this workflow: the worktree you are
|
|
54
|
+
standing in, else the sole provisioned spec. Ask the engine rather than guessing:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
skitterspec spec-env resolve
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Several provisioned and none resolved is a refusal — relay its list and stop,
|
|
61
|
+
never pick from it. See `.claude/rules/spec-planning.md`; do not restate the
|
|
62
|
+
rule here.
|
|
63
|
+
|
|
64
|
+
**Three argument shapes, and they cannot collide.** A **six-digit code**
|
|
65
|
+
matches `^\d{6}$`; a **tracker id** carries a letter and a hyphen; a
|
|
66
|
+
**spec name** is neither. So the parse needs no flag, and nothing has to be
|
|
67
|
+
guessed at from context.
|
|
68
|
+
|
|
69
|
+
A **six-digit code** is a pass the operator read off their own page. It says
|
|
70
|
+
**which pass**, not which spec — so resolve the spec exactly as a bare
|
|
71
|
+
invocation does, above, and claim the code *there*. Then go to step 4 — a named
|
|
72
|
+
pass has nothing to disambiguate.
|
|
73
|
+
|
|
74
|
+
A **name** targets that spec instead. A **tracker id** does too, but only
|
|
75
|
+
through a **provider seam**: the base knows nothing about tracker ids, so
|
|
76
|
+
resolve one by asking whichever provider is installed for its listing —
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
skitterspec spec-sync linked --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
— which answers `[{ spec, bucket, identifier }]`, and match on `identifier`.
|
|
83
|
+
**With no provider installed an id resolves to nothing**, and that is the right
|
|
84
|
+
answer rather than a guess: a `SKS-227`-shaped string is not evidence that a
|
|
85
|
+
tracker exists. Say the id matched no spec and stop.
|
|
86
|
+
|
|
87
|
+
## 1a. Targeting another spec? Get into its worktree first
|
|
88
|
+
|
|
89
|
+
**Only when the resolved spec's worktree is not where this session stands.**
|
|
90
|
+
Compare the `worktree:` line from `skitterspec spec-env resolve <spec>` against
|
|
91
|
+
cwd, resolving both paths first so a symlinked or trailing-slash spelling of one
|
|
92
|
+
tree does not read as two. **Same tree — say nothing and carry on.** That is the
|
|
93
|
+
ordinary case, and a line about it is narration.
|
|
94
|
+
|
|
95
|
+
**Different trees, and it matters for what comes after, not for the claim.**
|
|
96
|
+
Claiming is harmless from anywhere: the sidecar lives in the primary checkout.
|
|
97
|
+
But an honoured `commit` verdict **commits**, and `changes` **edits files**, and
|
|
98
|
+
both must land in *that spec's* worktree — done from here they would land in
|
|
99
|
+
this one, on the wrong branch, looking entirely normal at the time.
|
|
100
|
+
|
|
101
|
+
So **ask, then move**:
|
|
102
|
+
|
|
103
|
+
*"`feat-orders` lives in `../repo-wt/orders`, and picking its review up means
|
|
104
|
+
committing there — move this session over to carry on?"*
|
|
105
|
+
|
|
106
|
+
- **On a yes**, move with a plain `cd "<worktreePath>"`. That is the whole
|
|
107
|
+
mechanism, exactly as `/spec-start` does it — not a tool call, because an
|
|
108
|
+
approval prompt is unusable on a phone.
|
|
109
|
+
Then **confirm the move landed** rather than assuming it
|
|
110
|
+
(`.claude/rules/negative-checks.md` rule 1): run `skitterspec spec-env resolve`
|
|
111
|
+
with no argument and check its `spec:` line names the target. If it does not,
|
|
112
|
+
stop — do not claim a pass you are about to act on from a tree you could not
|
|
113
|
+
confirm.
|
|
114
|
+
- **On a no, stop without claiming.** A pass claimed here and acted on there is
|
|
115
|
+
exactly the split this guard exists to prevent, and claiming first would spend
|
|
116
|
+
the code for nothing.
|
|
117
|
+
|
|
118
|
+
**A target with no worktree is a refusal.** There is nowhere for a commit to
|
|
119
|
+
land. Name it, suggest `/spec-start <name>`, and stop.
|
|
120
|
+
|
|
121
|
+
**Why not simply refuse unless you are on the base branch?** Because that bans a
|
|
122
|
+
legitimate case — standing in one spec, picking up another's review — while
|
|
123
|
+
*still* leaving the work to be done in a tree you are not in. The relocation is
|
|
124
|
+
needed either way, so the relocation is the guard.
|
|
125
|
+
|
|
126
|
+
## 2. Read what is waiting
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
skitterspec spec-env review <spec>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Its `pending:` block lists each waiting pass — code, verdict, age:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
pending: 1 waiting
|
|
136
|
+
792969 · commit · 1 min ago
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Never open `.spec-env/reviews/<spec>.pending.json`.** The render carries
|
|
140
|
+
everything a decision needs, so there is nothing to go looking for, and going
|
|
141
|
+
looking is the bypass step 0 forbids. The file is right there and readable; that
|
|
142
|
+
is precisely why the rule is written down rather than assumed.
|
|
143
|
+
|
|
144
|
+
**Nothing waiting is an ordinary answer.** Say so and stop. Do not hunt through
|
|
145
|
+
other specs, and do not treat an empty holding area as a problem — a `file://`
|
|
146
|
+
page copies to the clipboard instead of sending, so mention that the pass may be
|
|
147
|
+
on their clipboard waiting to be pasted, and finish.
|
|
148
|
+
|
|
149
|
+
## 3. One pass waiting? Act on it
|
|
150
|
+
|
|
151
|
+
**Claim it and go to step 4.** Do not read the code out, and do not ask whether
|
|
152
|
+
it is theirs. The operator typed this command, which is the whole signal — and
|
|
153
|
+
this skill is the one place in the workflow where that signal cannot be
|
|
154
|
+
manufactured, because the model cannot invoke it.
|
|
155
|
+
|
|
156
|
+
**The code was never an authorisation.** A later edit must not restore it as
|
|
157
|
+
one. It earned its keep when the *page* pushed: the agent went looking, found
|
|
158
|
+
a pass, and had to prove which one it had. That channel is gone. What stops a
|
|
159
|
+
stranger's approval reaching your repo is that a device on your network can
|
|
160
|
+
reach your page and cannot reach this conversation — the confirmation step
|
|
161
|
+
added nothing to that, and cost a round-trip on every review.
|
|
162
|
+
|
|
163
|
+
So the code has exactly one job left: **telling two passes apart**. That is
|
|
164
|
+
disambiguation, not a gate.
|
|
165
|
+
|
|
166
|
+
**Two or more waiting is a refusal to guess.** Name them all — code, verdict,
|
|
167
|
+
age — and ask which. Never take the newest, the oldest, or the only `commit`:
|
|
168
|
+
this is the one case where a stranger's pass really is sitting beside theirs,
|
|
169
|
+
and the six digits are the only thing that separates them.
|
|
170
|
+
|
|
171
|
+
*"Two are waiting — 792969 (commit, 1 min ago) and 324199 (commit-continue,
|
|
172
|
+
just now). Which is yours?"*
|
|
173
|
+
|
|
174
|
+
**A pasted code skips even that.** `/spec-reviewed 324199` names the pass
|
|
175
|
+
outright, so there is nothing to disambiguate and nothing to ask — claim it and
|
|
176
|
+
act.
|
|
177
|
+
|
|
178
|
+
**On "that isn't mine"**, leave it and offer to drop it:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
skitterspec spec-env review <spec> --drop <code>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A pass that stays is reported on every render until the operator stops reading
|
|
185
|
+
the line — which is how the real one gets missed.
|
|
186
|
+
|
|
187
|
+
## 4. Claim it and act
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
skitterspec spec-env review <spec> --claim <code>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then **route on the verdict exactly as `/spec-diff` §2 does** — its steps 2, 2a
|
|
194
|
+
and 4 own that routing, including the commit hand-off through
|
|
195
|
+
`review.commitWith` and what `commit-continue` does after it. Follow it; do not
|
|
196
|
+
restate it here, because two copies of a routing rule is how the two come to
|
|
197
|
+
disagree.
|
|
198
|
+
|
|
199
|
+
That includes the one rule worth knowing before you invoke anything:
|
|
200
|
+
`commit-continue` runs `/spec-next` and **stops there**. It never completes,
|
|
201
|
+
lands or tears anything down.
|
|
202
|
+
|
|
203
|
+
**A code that matches nothing refuses, and names nothing.** The engine answers
|
|
204
|
+
that way and you relay it unchanged: do not list what *is* waiting, and never
|
|
205
|
+
fall back to "the only one". Both would hand a guesser the answer, and the
|
|
206
|
+
second is exactly the fallback that would let an unread pass through. A mistyped
|
|
207
|
+
digit is the ordinary cause — say the code matched nothing and let the operator
|
|
208
|
+
look again.
|
|
209
|
+
|
|
210
|
+
That holds whichever door the code came through, the paste included. A wrong
|
|
211
|
+
code is a wrong code.
|
|
212
|
+
|
|
213
|
+
A claim is a **delivery mechanism, not a second kind of review**: a pass that
|
|
214
|
+
arrived this way means exactly what the same pass pasted into the chat would
|
|
215
|
+
mean — or picked from `/spec-next`'s picker, which is the same conclusion
|
|
216
|
+
reached by a third route.
|
|
217
|
+
|
|
218
|
+
## 5. Report
|
|
219
|
+
|
|
220
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
221
|
+
the shape; this section carries only what is specific here.
|
|
222
|
+
|
|
223
|
+
**Verdicts**
|
|
224
|
+
|
|
225
|
+
- `✅` — a pass was claimed and its verdict acted on.
|
|
226
|
+
- `⚠️` — claimed and acted on, with something worth knowing.
|
|
227
|
+
- `❌` — it acted and stopped part-way; the commit failed, or the work did.
|
|
228
|
+
Quote it.
|
|
229
|
+
- `⏸` — nothing was waiting, the operator said the waiting pass was not theirs,
|
|
230
|
+
they declined the move to another spec's worktree, or the target has no
|
|
231
|
+
worktree at all. Nothing changed, and none of those is a failure.
|
|
232
|
+
|
|
233
|
+
**Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `Worktree` ·
|
|
234
|
+
`Follow-ups` · `Next`
|
|
235
|
+
|
|
236
|
+
`Worktree` appears only when this run **moved the session** — the path it moved
|
|
237
|
+
to, because the operator's next command depends on knowing where they now are.
|
|
238
|
+
A run that stayed put omits it.
|
|
239
|
+
|
|
240
|
+
`Built` is what the verdict produced — the commit, or the commented files
|
|
241
|
+
worked. A run that claimed nothing built nothing and omits it.
|