@skitterbyte/skitterspec-jira 0.0.0-stage → 2.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 +1266 -0
- package/README.md +44 -2
- package/assets/claude-md-section.md +85 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/commands/spec-remote-review.md +37 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/SETUP.md +192 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +606 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/jira.config.json.example +18 -0
- package/assets/core/jira.config.md +88 -0
- package/assets/hooks/main-guard.cjs +162 -0
- package/assets/hooks/review-gate.cjs +146 -0
- package/assets/review/page.html +3384 -0
- package/assets/rules/commit-trailers.md +96 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +702 -0
- package/assets/rules/spec-reports.md +572 -0
- package/assets/skills/no-spec/SKILL.md +249 -0
- package/assets/skills/spec/SKILL.md +714 -0
- package/assets/skills/spec-bug/SKILL.md +585 -0
- package/assets/skills/spec-cancel/SKILL.md +261 -0
- package/assets/skills/spec-complete/SKILL.md +451 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +607 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-jira-setup/SKILL.md +221 -0
- package/assets/skills/spec-list/SKILL.md +173 -0
- package/assets/skills/spec-next/SKILL.md +678 -0
- package/assets/skills/spec-push/SKILL.md +306 -0
- package/assets/skills/spec-review/SKILL.md +256 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +496 -0
- package/assets/skills/spec-status/SKILL.md +114 -0
- package/assets/skills/spec-sync/SKILL.md +220 -0
- package/assets/skills/spec-to-main/SKILL.md +141 -0
- package/bin/skitterspec-jira.js +68 -0
- package/package.json +40 -4
- package/src/cli.js +5967 -0
- package/src/deprecate.js +138 -0
- package/src/env/bootstrap.js +188 -0
- package/src/env/building.js +143 -0
- package/src/env/classify.js +176 -0
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +850 -0
- package/src/env/dev.js +63 -0
- package/src/env/glob.js +34 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +413 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +167 -0
- package/src/env/live.js +479 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +225 -0
- package/src/env/prune.js +119 -0
- package/src/env/registry.js +159 -0
- package/src/env/render.js +17 -0
- package/src/env/resolve.js +716 -0
- package/src/env/review.js +2622 -0
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +1044 -0
- package/src/env/supervise.js +157 -0
- package/src/env/teardown.js +248 -0
- package/src/env/trust.js +87 -0
- package/src/gating.js +155 -0
- package/src/init.js +1150 -0
- package/src/lines-diff.js +114 -0
- package/src/prompts.js +129 -0
- package/src/vendor/jira/adf.js +414 -0
- package/src/vendor/jira/api.js +295 -0
- package/src/vendor/jira/cli-sync.js +1252 -0
- package/src/vendor/jira/commands.js +44 -0
- package/src/vendor/jira/config.js +286 -0
- package/src/vendor/jira/credentials.js +108 -0
- package/src/vendor/jira/dialect.js +45 -0
- package/src/vendor/jira/mcp.js +129 -0
- package/src/vendor/provider-kit/index.js +41 -0
- package/src/vendor/provider-kit/src/adapter.js +23 -0
- package/src/vendor/provider-kit/src/apply.js +207 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +251 -0
- package/src/vendor/provider-kit/src/credentials.js +404 -0
- package/src/vendor/provider-kit/src/doctor.js +35 -0
- package/src/vendor/provider-kit/src/http.js +63 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/list-assignee.js +71 -0
- package/src/vendor/provider-kit/src/list-rows.js +35 -0
- package/src/vendor/provider-kit/src/md-compare.js +39 -0
- package/src/vendor/provider-kit/src/md-tables.js +94 -0
- package/src/vendor/provider-kit/src/prompt.js +50 -0
- package/src/vendor/provider-kit/src/released.js +219 -0
- package/src/vendor/provider-kit/src/specs.js +248 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +92 -0
- package/src/vendor/sync-core/src/base.js +81 -0
- package/src/vendor/sync-core/src/compare.js +338 -0
- package/src/vendor/sync-core/src/dialect.js +51 -0
- package/src/vendor/sync-core/src/fields.js +36 -0
- package/src/vendor/sync-core/src/legacy.js +100 -0
- package/src/vendor/sync-core/src/normalize.js +971 -0
- package/src/vendor/sync-core/src/push.js +85 -0
- package/src/vendor/sync-core/src/retarget.js +278 -0
- package/src/vendor/sync-core/src/task-block.js +308 -0
- package/src/vendor/sync-core/src/verify.js +67 -0
- package/src/vendor/sync-core/src/write.js +191 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# Spec Reports
|
|
2
|
+
|
|
3
|
+
Every lifecycle skill ends with the same block. This file is its whole
|
|
4
|
+
definition — a skill's `## Report` section names only which fields it emits and
|
|
5
|
+
any behaviour that was always there.
|
|
6
|
+
|
|
7
|
+
Two things it buys: a run's outcome is scannable in one shape rather than
|
|
8
|
+
composed fresh each time, and "nothing happened" becomes a reported outcome
|
|
9
|
+
instead of an absent one.
|
|
10
|
+
|
|
11
|
+
## Silence during the run
|
|
12
|
+
|
|
13
|
+
**Do not narrate.** Not what you are about to do, not why, not which step you
|
|
14
|
+
are on. The tool calls are already on screen, so a line per step re-states the
|
|
15
|
+
transcript in words that never vary.
|
|
16
|
+
|
|
17
|
+
Speak mid-run for exactly two reasons:
|
|
18
|
+
|
|
19
|
+
- **A question you cannot answer yourself** — a decision the spec left open, a
|
|
20
|
+
confirmation a guard demands, an ambiguity with more than one defensible
|
|
21
|
+
answer.
|
|
22
|
+
- **A failure, at the moment it happens** — a red test, a refused command, a
|
|
23
|
+
conflict. Say it where it occurs; do not hold it back for the block.
|
|
24
|
+
|
|
25
|
+
Everything else waits.
|
|
26
|
+
|
|
27
|
+
## The block
|
|
28
|
+
|
|
29
|
+
A verdict sentence, a blank line, then a two-column table:
|
|
30
|
+
|
|
31
|
+
✅ **Phase 2 built** — `feat-foo`, 2 of 4
|
|
32
|
+
|
|
33
|
+
| | |
|
|
34
|
+
|---|---|
|
|
35
|
+
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · phase 2 moved |
|
|
36
|
+
| **Branch** | `spec/feat-foo` · 3 commits, clean |
|
|
37
|
+
| **Built** | POST /orders handler, orders schema |
|
|
38
|
+
| **Tests** | 128 passed · dotnet test |
|
|
39
|
+
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) also [http://100.126.111.34:7760/…](http://100.126.111.34:7760/…) (tailnet) · **remote** off |
|
|
40
|
+
| **Follow-ups** | none |
|
|
41
|
+
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
42
|
+
|
|
43
|
+
**A table, and never a fenced block.** An aligned label column is only
|
|
44
|
+
achievable inside a fence, because markdown collapses runs of spaces outside
|
|
45
|
+
one — so alignment and being recognised as a report are mutually exclusive, and
|
|
46
|
+
the report wins. A fence renders as the same grey box as a code sample, and a
|
|
47
|
+
reader who has been shown code all session reads one more box as code: the first
|
|
48
|
+
person to receive one of these did not register it as output at all, and missed
|
|
49
|
+
the `Next` row inside it. That is the exact scanning failure this contract exists
|
|
50
|
+
to fix, so producing it in the contract's own format is not a cosmetic mistake.
|
|
51
|
+
|
|
52
|
+
**The verdict sentence** is `<state> **<one clause>** — <spec>, <where it got
|
|
53
|
+
to>`. The clause says what happened; the tail says to what, and how far. Name
|
|
54
|
+
the skill in the clause when the run's identity is not obvious from the
|
|
55
|
+
conversation — a refusal, or a skill invoked by another. A run with no spec to
|
|
56
|
+
name drops that segment rather than inventing one.
|
|
57
|
+
|
|
58
|
+
**The header row is empty.** The labels are the left column, and a header over
|
|
59
|
+
them would only be able to say "field", which no one needs told.
|
|
60
|
+
|
|
61
|
+
**The block is the last thing in the message.** Nothing follows it — not a
|
|
62
|
+
closing line, not a next step, not the offer. Everything the reader must act on
|
|
63
|
+
is a row, which is what earns the block its position.
|
|
64
|
+
|
|
65
|
+
**And that is a rule about the destination, not only the prohibition.** It was
|
|
66
|
+
stated plainly and broken repeatedly, by runs that had something worth saying
|
|
67
|
+
and nowhere in the block to say it — so the paragraph went after the table,
|
|
68
|
+
where it buried the rows it was meant to sit beside. A missing destination reads
|
|
69
|
+
as a rule that does not fit the work, and a rule that does not fit gets ignored.
|
|
70
|
+
So: **if it is worth telling the reader, it is a row.**
|
|
71
|
+
**If it is not a row, it is not worth telling them.**
|
|
72
|
+
`Notes` is the row most of that content belongs in.
|
|
73
|
+
|
|
74
|
+
**Two controls may follow the block, and nothing else ever.** The ban exists
|
|
75
|
+
because *prose* after the block competes with it for the reader's attention and
|
|
76
|
+
loses them the rows. A control does not compete: it is a row made actionable, in
|
|
77
|
+
the place the reader's eye already finishes. Prose after either of them is the
|
|
78
|
+
same violation wearing a control's clothes.
|
|
79
|
+
|
|
80
|
+
**The first is a picker.** Where the run ends in a choice the reader must make,
|
|
81
|
+
the options may be offered after the table.
|
|
82
|
+
|
|
83
|
+
**The second is the review call-to-action**, and only where the run is actually
|
|
84
|
+
**waiting** on a verdict. It is a banner, not a sentence:
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ⏸ Review ready — 7 files, +212 −18
|
|
89
|
+
|
|
90
|
+
- **local** — [http://127.0.0.1:7760/7e9e123e7540/feat-orders](http://127.0.0.1:7760/7e9e123e7540/feat-orders)
|
|
91
|
+
- **network** — [http://192.168.0.136:7760/7e9e123e7540/feat-orders](http://192.168.0.136:7760/7e9e123e7540/feat-orders)
|
|
92
|
+
- also — [http://100.126.111.34:7760/7e9e123e7540/feat-orders](http://100.126.111.34:7760/7e9e123e7540/feat-orders) (tailnet)
|
|
93
|
+
- **remote** — off · `/spec-remote-review` turns it on
|
|
94
|
+
|
|
95
|
+
live: off — /spec-live to put it live
|
|
96
|
+
|
|
97
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
98
|
+
|
|
99
|
+
`/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
A rule, a heading naming the state and the size, **the stack**, the `live:`
|
|
104
|
+
line, one line saying the run is stopped and which tiers it covers, and the two
|
|
105
|
+
exits. Nothing else, and never a paragraph explaining it.
|
|
106
|
+
|
|
107
|
+
**The `live:` line, and why a rigid contract took a second amendment.** Reading
|
|
108
|
+
a diff is not the only way to judge a change — often the question is whether it
|
|
109
|
+
*works* — and the answer to that lives at the running URL. A reader deciding
|
|
110
|
+
whether to open the page at all wants to know whether it is already running
|
|
111
|
+
somewhere, and finding that out any other way is a trip to a terminal in the
|
|
112
|
+
middle of a review.
|
|
113
|
+
|
|
114
|
+
**Each line names a command a person types**, rather than describing a
|
|
115
|
+
capability. `live: off — the page can put it live` told the reader that a page
|
|
116
|
+
somewhere could do it and left them to find the verb — and the verb is what they
|
|
117
|
+
need when they are in a terminal rather than on the page. So the lines say
|
|
118
|
+
`/spec-live`, `/spec-live main`, `/spec-remote-review`. Making them *clickable*
|
|
119
|
+
was considered and rejected: the only clickable thing markdown has is a URL, and
|
|
120
|
+
a URL that acts when it is **fetched** is one a link previewer or a prefetcher
|
|
121
|
+
fires with nobody involved.
|
|
122
|
+
|
|
123
|
+
It is the engine's line, copied like the stack: `spec-env review` prints it and
|
|
124
|
+
`--json` carries the same answer as `live`, from one function, so the report and
|
|
125
|
+
the command cannot disagree. Four states, and only three of them are a line —
|
|
126
|
+
`on` (with the URL when the project configured one), `off`, `held` (naming the
|
|
127
|
+
spec holding the workbench). The fourth, `unavailable`,
|
|
128
|
+
**prints nothing at all**:
|
|
129
|
+
a project with no isolation and a spec with no worktree both land there,
|
|
130
|
+
and a line about a surface that does not exist is an accusation against a
|
|
131
|
+
healthy repo.
|
|
132
|
+
|
|
133
|
+
**This is the second amendment to this section in two specs**, which is worth
|
|
134
|
+
naming rather than letting pass. The contract earns its rigidity from real
|
|
135
|
+
failures, and two amendments in a row is how a rigid contract quietly becomes a
|
|
136
|
+
negotiable one. Both have carried their reason and both replaced a line rather
|
|
137
|
+
than adding a paragraph.
|
|
138
|
+
**A third should have to argue harder than either did.**
|
|
139
|
+
|
|
140
|
+
**The stack is the engine's, copied rather than composed.** `spec-env review`
|
|
141
|
+
prints one line per tier and `--json` carries the same thing as `tiers`, so take
|
|
142
|
+
it from there in the order it comes — and do not decide which tiers are worth
|
|
143
|
+
mentioning. The whole reason the stack exists is that the engine was being asked
|
|
144
|
+
to guess where the reader was sitting, and it guessed wrong three separate ways
|
|
145
|
+
in one day; a skill re-deciding that in prose is the same guess one layer up.
|
|
146
|
+
|
|
147
|
+
**Three tiers, always all three, in this order.**
|
|
148
|
+
|
|
149
|
+
| Tier | Is | Off when |
|
|
150
|
+
|------|----|----------|
|
|
151
|
+
| `local` | the loopback URL — the machine the run is on | never. With nothing served it is the `file://` page, and says it cannot send a verdict |
|
|
152
|
+
| `network` | the LAN URL — a phone on the same wifi | `review.allowNetwork` is false, or this machine has no network address |
|
|
153
|
+
| `remote` | a published page, for a reader off the network | `review.allowRemote` is false, or nothing has been published |
|
|
154
|
+
|
|
155
|
+
A tier that is off **keeps its line** and names the one command that turns it
|
|
156
|
+
on. Dropping the line is what left a reader who had walked out of the house with
|
|
157
|
+
nothing on screen to tell them a remote page was even possible — they could not
|
|
158
|
+
ask for what they could not see.
|
|
159
|
+
|
|
160
|
+
**The `network` tier may carry alternates, and they are part of it — not a fourth tier.**
|
|
161
|
+
The engine offers the other addresses this machine has (a
|
|
162
|
+
tailnet one, for a reader off the wifi) as `also:` lines under `network`, each
|
|
163
|
+
labelled; `--json` carries them as that tier's `alternates`, labelled the same
|
|
164
|
+
way. Copy them as indented sub-bullets under the network bullet, verbatim,
|
|
165
|
+
labels included. This is not an amendment to the tier rule: the stack is the
|
|
166
|
+
engine's, copied rather than composed, and the engine was already printing
|
|
167
|
+
these — a contract that made the skill edit them out was the composing this
|
|
168
|
+
section forbids. A machine with no alternates renders the banner exactly as
|
|
169
|
+
above, minus the sub-bullet.
|
|
170
|
+
|
|
171
|
+
**Only promise a wait the transport can deliver.** The wait itself is real in
|
|
172
|
+
every case; what varies is what carries it back, not whether the run is
|
|
173
|
+
stopped. Three transports, three true sentences:
|
|
174
|
+
|
|
175
|
+
- **A page the engine SERVES** hands its pass to the local store, and
|
|
176
|
+
`spec-env review wait` returns when it lands. The run is woken by the press:
|
|
177
|
+
*"I'm holding here until you send a verdict."*
|
|
178
|
+
|
|
179
|
+
**That sentence is only true for as long as the wait is actually running**, so
|
|
180
|
+
the wait is the engine's and never one composed for the occasion. A watcher
|
|
181
|
+
written per run is proven by nothing: one was written
|
|
182
|
+
`until [ -f "$P" ] && [ "$x" \> "$y" ]` — valid bash, a syntax error in zsh,
|
|
183
|
+
a predicate that could never be true — and it spun for five minutes while the
|
|
184
|
+
report underneath it said the run was holding.
|
|
185
|
+
**Silence is what made that expensive**:
|
|
186
|
+
a wait that cannot fire and one patiently working look identical,
|
|
187
|
+
so nothing seemed wrong until the reader asked. Give the wait no timeout
|
|
188
|
+
either; it lasts as long as the session, because a reader who walks away from
|
|
189
|
+
a diff is the normal case and a bounded watch once lost a verdict to a lunch
|
|
190
|
+
break.
|
|
191
|
+
- **A `file://` page** has no server to POST to, so the pass is copied and
|
|
192
|
+
pasted — the reader's next message is what carries it. The run is stopped just
|
|
193
|
+
the same, and says so:
|
|
194
|
+
*"I'm holding here — paste the pass when you have it."*
|
|
195
|
+
This is the same wait, carried by the conversation instead of a watch. It is
|
|
196
|
+
not a lesser one, and it is not an excuse to ask without waiting.
|
|
197
|
+
- **A PUBLISHED page** writes to the artifact's own store, and nothing pushes
|
|
198
|
+
from there into the conversation. That is the one case where a verdict is
|
|
199
|
+
genuinely invisible until someone asks for it, so its line in the stack says
|
|
200
|
+
that rather than letting the holding line speak for it:
|
|
201
|
+
|
|
202
|
+
- **remote** — [https://…](https://…) · a verdict here needs `/spec-reviewed`
|
|
203
|
+
|
|
204
|
+
Where `remote` is the only tier a reader can reach, the holding line says so
|
|
205
|
+
too: *press a verdict, then type `/spec-reviewed` — I cannot see it until you
|
|
206
|
+
do.* Where a served tier is reachable as well, the holding line keeps naming
|
|
207
|
+
the tiers the wait covers, and `remote`'s own caveat sits on `remote`'s line
|
|
208
|
+
where it belongs.
|
|
209
|
+
|
|
210
|
+
**A harness with no file-watch does not get an exemption.** It once read
|
|
211
|
+
"change nothing" — keep the row, keep the question, do not wait — and that
|
|
212
|
+
escape hatch is what let a whole class of runs go on asking questions nobody was
|
|
213
|
+
listening for. Where a watch is unavailable the wait is the turn ending, which
|
|
214
|
+
every harness can do.
|
|
215
|
+
|
|
216
|
+
**Run it under a persistent watch primitive where the harness has one** — one
|
|
217
|
+
that lives for the whole session and whose deliberate stop is an explicit act
|
|
218
|
+
(Claude Code: the `Monitor` tool with `persistent: true`, ended by `TaskStop`).
|
|
219
|
+
A backgrounded shell is reclaimed on a long idle — that is the death the next
|
|
220
|
+
paragraph recovers from — where a session-length primitive is designed to
|
|
221
|
+
survive it. Where the harness has none, background the command exactly as
|
|
222
|
+
before. The engine's wait stays the one watcher either way: its heartbeat goes
|
|
223
|
+
to stderr, so stdout carries only the start line and the arrival, and the exit
|
|
224
|
+
ends the watch.
|
|
225
|
+
|
|
226
|
+
**A watch that dies is re-armed, silently, on the same window.** A backgrounded
|
|
227
|
+
wait does not always survive a long idle: three sessions lost theirs over one
|
|
228
|
+
lunch break, and two in another were killed at 43 and 30 minutes. So a wait that
|
|
229
|
+
ends **without a verdict** and **was not stopped by the operator** is started
|
|
230
|
+
again on the **same** `--since`. Not a fresh one — a pass that arrived during
|
|
231
|
+
the gap is still inside the original window, and re-arming on a new timestamp
|
|
232
|
+
would strand exactly that pass.
|
|
233
|
+
|
|
234
|
+
**Say nothing when you do it.** Nothing happened and the window is unchanged; a
|
|
235
|
+
line on every death turns a working loop into a stream of non-events.
|
|
236
|
+
|
|
237
|
+
**An operator who stopped it is not a death** — re-arming over a deliberate
|
|
238
|
+
stop makes the wait unstoppable, which is worse than a wait that stops. But a
|
|
239
|
+
deliberate stop is an **explicit act with positive evidence**: this session
|
|
240
|
+
called TaskStop, or the operator said to stop waiting. A bare killed-task
|
|
241
|
+
notification carries neither and is read as a **death**. This clause once said
|
|
242
|
+
to treat the ambiguous kill as deliberate, and that safe branch ate the
|
|
243
|
+
recovery whole: every observed kill was the harness reclaiming an idle shell
|
|
244
|
+
and none was a person, so the case it protected against does not occur — while
|
|
245
|
+
the `/spec-reviewed` it charged was paid on every idle review. The deliberate
|
|
246
|
+
stop is the positive signal, so its absence is what a death looks like
|
|
247
|
+
(`.claude/rules/negative-checks.md` rule 1).
|
|
248
|
+
|
|
249
|
+
**It is bounded by the age of the window, not by a count of retries.** Keep
|
|
250
|
+
re-arming while the window is younger than **12 hours**, then stop. A count means
|
|
251
|
+
something different depending on whether deaths come five minutes or forty-five
|
|
252
|
+
apart, and the observed interval already varies that much. Twelve hours
|
|
253
|
+
deliberately does not cover an overnight gap — the pass is never lost, and
|
|
254
|
+
`/spec-reviewed` is the designed recovery — because the alternative is waking
|
|
255
|
+
the session every half hour all night for a review nobody is returning to
|
|
256
|
+
before morning.
|
|
257
|
+
|
|
258
|
+
**Past the bound the banner degrades**, to the sentence this file already
|
|
259
|
+
defines for a transport that cannot push into the conversation:
|
|
260
|
+
|
|
261
|
+
*press a verdict, then type `/spec-reviewed` — I cannot see it until you do.*
|
|
262
|
+
|
|
263
|
+
That is a new **state** for an existing sentence, not a new sentence. The
|
|
264
|
+
holding line is only ever printed while a wait is actually running, which is the
|
|
265
|
+
rule this section has always carried: *only promise a wait the transport can
|
|
266
|
+
deliver.*
|
|
267
|
+
|
|
268
|
+
**The `Continue` ending, and why the banner's exits differ mid-run.** A page
|
|
269
|
+
rendered part-way through a run offers `Continue` — *I have read it, carry on* —
|
|
270
|
+
in place of the committing verdicts, because "commit" is the wrong verb for
|
|
271
|
+
unfinished work. Its banner names the same state and the same stack, and
|
|
272
|
+
different exits:
|
|
273
|
+
|
|
274
|
+
…the stack, unchanged…
|
|
275
|
+
|
|
276
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
277
|
+
|
|
278
|
+
`Continue` carries on · `Request changes` works them now
|
|
279
|
+
|
|
280
|
+
The stack is written out once, above, and referred to everywhere else. Two
|
|
281
|
+
copies of it is how the two come to disagree about which tiers exist.
|
|
282
|
+
|
|
283
|
+
**It is not the `none` verdict that was removed.** `none` recorded itself and
|
|
284
|
+
did nothing, which is the record-and-do-nothing ending this contract exists
|
|
285
|
+
against. `Continue` **resumes the run** — it names an action, which is the bar
|
|
286
|
+
every verdict has to clear. What it does not do is commit, and it can never
|
|
287
|
+
clear the gate a finished phase armed: that gate is discharged by a committing
|
|
288
|
+
verdict or a recorded skip, and by nothing else.
|
|
289
|
+
|
|
290
|
+
**So waiting and arming are separate**, and only one of them is about this
|
|
291
|
+
block. *Waiting* is what any offer does, and it is what this section governs.
|
|
292
|
+
*Arming* asserts an obligation that outlives the turn, and belongs only to work
|
|
293
|
+
that is finished. A mid-run render waits without arming — walking away from it
|
|
294
|
+
costs nothing, which is exactly why `Continue` is safe to offer there.
|
|
295
|
+
|
|
296
|
+
**One link per reachable store, each labelled.** This amends
|
|
297
|
+
*"exactly one link, never two"*, and the evidence that rule was written on is
|
|
298
|
+
the reason it still reads the way it does. Two links were handed over, the wait
|
|
299
|
+
stood behind only one of them, and a verdict was pressed on the published page
|
|
300
|
+
three times while each one sat unread under a line claiming the run was waiting.
|
|
301
|
+
Two links was not a convenience with a caveat; the caveat existing was the bug.
|
|
302
|
+
|
|
303
|
+
**What that evidence is about is the store, not the count.** A served page hands
|
|
304
|
+
its pass to the local pending store, which `spec-env review wait` watches; a
|
|
305
|
+
published page writes to the artifact's own store, which nothing here can see.
|
|
306
|
+
Two links into two stores asks the reader to know which door the run is standing
|
|
307
|
+
behind, and they cannot.
|
|
308
|
+
|
|
309
|
+
**Local and network are two doors into one room.** The page POSTs with
|
|
310
|
+
`fetch(location.pathname, …)`, so a page opened at `127.0.0.1:7760/…` and one
|
|
311
|
+
opened at `192.168.0.136:7760/…` reach the same server process and the same
|
|
312
|
+
pending store. One wait covers both, and a verdict pressed on either wakes the
|
|
313
|
+
same run. A `network` alternate — the `also:` tailnet address — is one more
|
|
314
|
+
door into the same room: the same server answers on it, so the wait-coverage
|
|
315
|
+
line stays true with nothing added to it. Between all of those the objection
|
|
316
|
+
does not apply — which is why they are offered, and why offering them gives
|
|
317
|
+
nothing up.
|
|
318
|
+
|
|
319
|
+
**So two things are required, and the second is what the old rule lacked.**
|
|
320
|
+
Every link is **labelled** with its tier, and one line says
|
|
321
|
+
**which tiers the wait covers**.
|
|
322
|
+
Two *unlabelled* links stay forbidden exactly as before, because
|
|
323
|
+
the reader still cannot tell them apart.
|
|
324
|
+
|
|
325
|
+
**`remote` keeps the caveat it always had.** It is listed so a reader off the
|
|
326
|
+
network knows the surface exists and what turns it on — never as a door the wait
|
|
327
|
+
is claimed to cover. Where it is on and published, its line says what is true of
|
|
328
|
+
it: *a verdict here needs `/spec-reviewed`*.
|
|
329
|
+
|
|
330
|
+
**Still forbidden**, in the words of the rule this amends: offering two links
|
|
331
|
+
into **different** stores without saying which one the wait is watching. That is
|
|
332
|
+
the failure that cost three unread verdicts, and nothing here touches it.
|
|
333
|
+
|
|
334
|
+
**This is not the old failure returning**, and the difference is the whole
|
|
335
|
+
justification. The offer used to be two quoted lines in the tail of a long
|
|
336
|
+
report, addressed to nobody, under a closing line that told the reader to move
|
|
337
|
+
on — and it was never once taken. It was moved into a labelled row to make it
|
|
338
|
+
findable, and that worked. What changed since is that the run now *waits*: the
|
|
339
|
+
reader is not being offered something optional, they are being told the work has
|
|
340
|
+
stopped until they answer. A row cannot carry that, because a row is scanned at
|
|
341
|
+
the same weight as every other row. So the state gets the loudest shape on
|
|
342
|
+
screen, at the end, where reading finishes.
|
|
343
|
+
|
|
344
|
+
**ASKING IMPLIES WAITING — one rule, and the two shapes follow from it.** Any
|
|
345
|
+
render that asks the reader for a verdict ends the turn watching for one. A run
|
|
346
|
+
that does not intend to wait does not ask: its `Review` row keeps the counts and
|
|
347
|
+
the link and **loses the question**.
|
|
348
|
+
|
|
349
|
+
So there are two shapes and no third:
|
|
350
|
+
|
|
351
|
+
- **Asking** → the banner above, and the run waits.
|
|
352
|
+
- **Not asking** → the `Review` row: counts and link, no question.
|
|
353
|
+
|
|
354
|
+
**What this guards against is a reader taught that the button is decorative.**
|
|
355
|
+
`/spec-bug` and `/spec-hotfix` used to render the page, emit a row
|
|
356
|
+
asking *"want a written review before you commit?"*, and finish — with nothing
|
|
357
|
+
watching. A verdict pressed on that page landed in the holding area and stayed
|
|
358
|
+
there, because the run had told the reader the page was **ready** rather than
|
|
359
|
+
that it was **waiting**. It happened twice on one spec, and the second press
|
|
360
|
+
existed only because the first appeared to do nothing. An offer that cannot be
|
|
361
|
+
answered costs more than never having offered.
|
|
362
|
+
|
|
363
|
+
**Rejected: keeping the question and adding a caveat** — *"press a verdict, but
|
|
364
|
+
I will not see it until you ask"*. That is exactly the shape that stranded those
|
|
365
|
+
two passes, and a truthful caveat does not make an unanswerable question worth
|
|
366
|
+
asking. Either watch for the answer, or do not ask.
|
|
367
|
+
|
|
368
|
+
**A render nobody is being held for is still worth reporting** — a mid-phase
|
|
369
|
+
`/spec-diff`, a page produced alongside other work. It gets the row, because
|
|
370
|
+
giving it a banner teaches the reader to scroll past banners. The shape marks
|
|
371
|
+
the difference between *here is a page* and *nothing proceeds until you answer*.
|
|
372
|
+
|
|
373
|
+
**Never fence a message to the reader.** A fenced block is for a command to
|
|
374
|
+
run, code, or engine output quoted verbatim — things the reader copies or
|
|
375
|
+
compares. A question, an offer, a hand-off is prose. The same mechanism that
|
|
376
|
+
made the old report unreadable applies to anything else in a grey box: it is
|
|
377
|
+
read as an artefact to skim rather than as something addressed to someone, so a
|
|
378
|
+
question inside one goes unanswered and an instruction inside one goes unread.
|
|
379
|
+
|
|
380
|
+
**It reports this run and nothing else.** Not what else is in flight, not the
|
|
381
|
+
other worktrees, not the backlog, not the state of the repo at large. A reader
|
|
382
|
+
finishing one piece of work cannot tell whether a line about some other spec is
|
|
383
|
+
a consequence of what just happened or an unrelated aside, and has to stop and
|
|
384
|
+
work it out — which is the cost this block exists to remove. If they want the
|
|
385
|
+
wider picture there is a skill that answers it; volunteering it here muddies the
|
|
386
|
+
one thing they asked about.
|
|
387
|
+
|
|
388
|
+
`Next` is the single next action **for this work**, not a menu of what else
|
|
389
|
+
could be done.
|
|
390
|
+
|
|
391
|
+
**And it must be runnable from the state the run actually leaves behind.** Most
|
|
392
|
+
of these skills deliberately do not commit — they finish, verify, and wait — so
|
|
393
|
+
the tree they hand back is dirty, and several of the commands they would name
|
|
394
|
+
next refuse on a dirty tree. A `Next` that omits the commit therefore sends the
|
|
395
|
+
reader to a refusal: `/spec-next` leaves a phase uncommitted and its own §2
|
|
396
|
+
refuses on exactly that, and `/spec-bug` leaves a fix uncommitted where
|
|
397
|
+
`/spec-complete` refuses the same way. Where the skill does not commit, the row
|
|
398
|
+
says `/commit, then <the thing>`.
|
|
399
|
+
|
|
400
|
+
This is not a style rule. `Next` is the one row a reader is meant to act on, and
|
|
401
|
+
it closes the block because the closing row should be the one that moves the
|
|
402
|
+
work on. A row that cannot be run spends the trust the position was designed to
|
|
403
|
+
earn — and the two skills that got it wrong got it wrong independently, which is
|
|
404
|
+
what makes it a missing constraint rather than two slips.
|
|
405
|
+
|
|
406
|
+
## Field vocabulary, in this order
|
|
407
|
+
|
|
408
|
+
A skill emits only the fields it declares, in this order, skipping the rest.
|
|
409
|
+
`Follow-ups` is the exception: always present, second to last.
|
|
410
|
+
|
|
411
|
+
`Tracker` is the conditional one: a skill may declare it and still never emit
|
|
412
|
+
it, because no ticketing provider is installed. That is an absence with nothing
|
|
413
|
+
behind it — say nothing rather than reporting that there was nothing to report.
|
|
414
|
+
|
|
415
|
+
| Field | Carries |
|
|
416
|
+
|-------|---------|
|
|
417
|
+
| `Tracker` | **First.** The ticket id, linked where the spec has a url, the spec's folder name beside it, and what changed there. |
|
|
418
|
+
| `Why` | **Non-`✅` only.** What stopped it, in one clause. |
|
|
419
|
+
| `Branch` | Branch name, commit count, clean or dirty. |
|
|
420
|
+
| `Spec` | The spec document's own state: status, bucket, phase. |
|
|
421
|
+
| `Cause` | The root cause, for work that diagnosed one. |
|
|
422
|
+
| `Built` | What the run produced — the code, the edits, the spec written. |
|
|
423
|
+
| `Tests` | The result and the command that produced it. |
|
|
424
|
+
| `Notes` | What this run hit and handled — one short paragraph. Named `Snags` once, which announced a problem before the reader had read one; most of what belongs here is neither good nor bad, just worth knowing. A wrong turn, a guard that fired, a check of yours that turned out to be a false negative. Not a caveat on the outcome (`⚠️` and `Why` carry those) and not future work (`Follow-ups` carries that): this is what happened on the way. |
|
|
425
|
+
| `Landed` | A fast-forward, a tag, a cherry-pick. |
|
|
426
|
+
| `Worktree` | A worktree provisioned, entered, or torn down. |
|
|
427
|
+
| `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
|
|
428
|
+
| `Review` | The rendered diff page: files, `+`/`−`, and the stack — the same tiers the banner lists, labelled, run together with `·` because a row is one cell, with any `network` alternates inline after the network URL (`also <url> (tailnet)`), plus `live` where the engine reported one. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
|
|
429
|
+
| `Follow-ups` | **Always.** `none`, or one line each. |
|
|
430
|
+
| `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
|
|
431
|
+
|
|
432
|
+
The order runs identity → context → what happened → where it went → what to do.
|
|
433
|
+
**`Tracker` is first** because the id is what addresses this work outside the
|
|
434
|
+
repo, and **`Next` is last** because it is the only row the reader acts on: the
|
|
435
|
+
closing row should be the one that moves the work on.
|
|
436
|
+
|
|
437
|
+
**`Untouched` is a fact, not a caveat.** A run that provisions beside another
|
|
438
|
+
spec's uncommitted work has done nothing wrong and nothing partial — it left
|
|
439
|
+
alone something that was never its business — so it stays `✅` with a row, and
|
|
440
|
+
does not become `⚠️`. Several specs in flight at once is what the worktree mode
|
|
441
|
+
is *for*; spending the "something is worth knowing" verdict on the normal case
|
|
442
|
+
is how that verdict stops meaning anything. It is still reported rather than
|
|
443
|
+
silent, because "I provisioned, and your four files are still sitting there" is
|
|
444
|
+
a different sentence from "I provisioned".
|
|
445
|
+
|
|
446
|
+
**`Review` is one row, not two** — and where the run is waiting, it is no rows
|
|
447
|
+
at all. The page and what to do with it are the same subject, and splitting them
|
|
448
|
+
made the reader resolve a distinction before acting on either. A waiting run
|
|
449
|
+
promotes the whole subject into the banner; keeping the row as well would
|
|
450
|
+
recreate that split with the two halves further apart than ever.
|
|
451
|
+
|
|
452
|
+
**The row carries no question, and that is the rule above applied here.** It
|
|
453
|
+
once ended *"want a written review before you commit?"* — a question addressed
|
|
454
|
+
to a reader the run was not waiting for. Putting it in a findable row fixed
|
|
455
|
+
where it was; it did not fix that nothing was listening. A run that wants that
|
|
456
|
+
question answered waits for it, and waiting means the banner.
|
|
457
|
+
|
|
458
|
+
## The four verdicts
|
|
459
|
+
|
|
460
|
+
| State | Means |
|
|
461
|
+
|-------|-------|
|
|
462
|
+
| `✅` | Done. Everything the skill set out to do happened. |
|
|
463
|
+
| `⚠️` | Done with caveats — it finished, but something is worth knowing. |
|
|
464
|
+
| `❌` | Failed part-way. It acted, and the repo is mid-something. |
|
|
465
|
+
| `⏸` | Refused before acting. Nothing changed. |
|
|
466
|
+
|
|
467
|
+
**`❌` and `⏸` are different facts about the repo**, and the difference decides
|
|
468
|
+
what happens next. A failed landing leaves a conflicted rebase and a standing
|
|
469
|
+
worktree — there is a mess to clear. A refusal leaves nothing at all, and the
|
|
470
|
+
only thing to do is supply what was missing. Collapsing them sends people
|
|
471
|
+
looking for wreckage that is not there, or past wreckage that is.
|
|
472
|
+
|
|
473
|
+
**A refusal still emits the block.** That is the point of having four states:
|
|
474
|
+
every invocation ends the same way, so an outcome is never inferred from
|
|
475
|
+
silence.
|
|
476
|
+
|
|
477
|
+
## Where the block may grow
|
|
478
|
+
|
|
479
|
+
One place only. A non-`✅` verdict adds a `Why` row, and quotes the output that
|
|
480
|
+
justifies it — the failing assertion, the conflict, the engine's refusal — in a
|
|
481
|
+
fenced block **above** the table, where a fence means what a fence should mean:
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
✖ rejects a negative quantity
|
|
485
|
+
expected 422, got 500
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
❌ **Phase 2 stopped — tests red** — `feat-foo`, 2 of 4
|
|
489
|
+
|
|
490
|
+
| | |
|
|
491
|
+
|---|---|
|
|
492
|
+
| **Why** | 3 assertions fail in `test_orders.py`; nothing committed |
|
|
493
|
+
| **Branch** | `spec/feat-foo` · 2 commits, dirty |
|
|
494
|
+
| **Built** | POST /orders handler, orders schema |
|
|
495
|
+
| **Tests** | 125 passed, 3 failed · pytest |
|
|
496
|
+
| **Follow-ups** | none |
|
|
497
|
+
| **Next** | fix the failures, then `/spec-next` to finish phase 2 |
|
|
498
|
+
|
|
499
|
+
Everywhere else the block stays the size it is. Prose after it, an extra row of
|
|
500
|
+
your own invention, a second block — all three are the shape drifting back to
|
|
501
|
+
what this replaced.
|
|
502
|
+
|
|
503
|
+
## Follow-ups
|
|
504
|
+
|
|
505
|
+
**Always present.** A recorded `none` is a decision; a missing line is an
|
|
506
|
+
oversight — the same logic as the `Gating` header.
|
|
507
|
+
|
|
508
|
+
The bar is deliberately high. A follow-up is something *this* work surfaced
|
|
509
|
+
that *this* spec will not fix — a wrong assumption, a missing guard, a decision
|
|
510
|
+
the spec deferred and the code then leaned on. It is **not** "could be tidier",
|
|
511
|
+
and it is **not** anything already sitting in the spec's Open questions.
|
|
512
|
+
|
|
513
|
+
When there is one, **offer to `/spec` it on the spot**. If the user does not
|
|
514
|
+
take the offer, write one dated line into the spec's Changelog before you
|
|
515
|
+
finish, so it outlives the session:
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
- 2026-01-09 — Follow-up surfaced: connect's port picker assumes a free
|
|
519
|
+
canonical port; not in scope here.
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**The offer needs no caveat about where.** These skills run inside the spec's own
|
|
523
|
+
worktree, and a follow-up spec authored *there* would physically live on that
|
|
524
|
+
branch — invisible from the base branch, and cancelled along with its host. That
|
|
525
|
+
used to make the offer name the primary checkout as the place to write it.
|
|
526
|
+
It no longer does: `/spec` provisions the **new** spec's own worktree before it
|
|
527
|
+
writes anything and moves the session there, so the follow-up lands on its own
|
|
528
|
+
branch and its own ticket without anyone naming a location. Take the offer and
|
|
529
|
+
let `/spec` handle it. Where isolation is absent there is no worktree to move to,
|
|
530
|
+
and the old caveat applies — `spec-planning.md` and `commit-trailers.md` carry
|
|
531
|
+
the reasoning.
|
|
532
|
+
|
|
533
|
+
## Worked examples
|
|
534
|
+
|
|
535
|
+
**`⚠️` — it finished, with something worth knowing.**
|
|
536
|
+
|
|
537
|
+
⚠️ **Phase 3 built; tracker not updated** — `feat-foo`, 3 of 4
|
|
538
|
+
|
|
539
|
+
| | |
|
|
540
|
+
|---|---|
|
|
541
|
+
| **Branch** | `spec/feat-foo` · 4 commits, clean |
|
|
542
|
+
| **Built** | Auth middleware, session table |
|
|
543
|
+
| **Tests** | 140 passed · go test ./... |
|
|
544
|
+
| **Follow-ups** | Session expiry is read from two places; phase 4 leans on one |
|
|
545
|
+
| **Next** | `/spec-next` → phase 4 (Docs) |
|
|
546
|
+
|
|
547
|
+
**`⏸` — refused, nothing changed.**
|
|
548
|
+
|
|
549
|
+
⏸ **`/spec-next` refused — no spec in flight**
|
|
550
|
+
|
|
551
|
+
| | |
|
|
552
|
+
|---|---|
|
|
553
|
+
| **Why** | Not standing in a worktree, and 2 specs are provisioned |
|
|
554
|
+
| **Follow-ups** | none |
|
|
555
|
+
| **Next** | `/spec-start <name>`, or `cd` into `../repo-wt/feat-orders` or `../repo-wt/feat-auth` |
|
|
556
|
+
|
|
557
|
+
**`✅` — a spec finished and landed.**
|
|
558
|
+
|
|
559
|
+
✅ **Landed and torn down** — `feat-foo`, complete
|
|
560
|
+
|
|
561
|
+
| | |
|
|
562
|
+
|---|---|
|
|
563
|
+
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · moved to its done state |
|
|
564
|
+
| **Spec** | Complete · `specs/complete/feat-foo` |
|
|
565
|
+
| **Tests** | 131 passed · npm test |
|
|
566
|
+
| **Landed** | base fast-forwarded to 4a1c9e2 |
|
|
567
|
+
| **Worktree** | removed · `../repo-wt/feat-foo` |
|
|
568
|
+
| **Follow-ups** | none |
|
|
569
|
+
| **Next** | pick the next spec from `specs/backlog/` |
|
|
570
|
+
|
|
571
|
+
The `❌` example is in **Where the block may grow** above, since the failing
|
|
572
|
+
output it quotes is the thing that example exists to show.
|