@naswerks/engineering-loop 0.1.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/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +8 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/verify-staged.mjs +247 -0
- package/package.json +35 -0
- package/references/docs-workflow.md +417 -0
- package/references/engineering-loop.md +298 -0
- package/skills/docs-audit-feature/SKILL.md +88 -0
- package/skills/docs-audit-full/SKILL.md +54 -0
- package/skills/docs-backlog/SKILL.md +41 -0
- package/skills/docs-process/SKILL.md +146 -0
- package/skills/docs-status/SKILL.md +47 -0
- package/skills/docs-write/SKILL.md +43 -0
- package/skills/init/SKILL.md +163 -0
- package/skills/init/scripts/doctor.mjs +176 -0
- package/skills/spec-child/SKILL.md +149 -0
- package/skills/spec-ignite/SKILL.md +277 -0
- package/skills/spec-parent/SKILL.md +634 -0
- package/skills/spec-pipeline/SKILL.md +453 -0
- package/skills/spec-retro/SKILL.md +398 -0
- package/skills/spec-review/SKILL.md +266 -0
- package/skills/spec-seat/SKILL.md +512 -0
- package/skills/spec-witness/SKILL.md +222 -0
- package/templates/claude-md-stanza.md +13 -0
- package/templates/docs/_meta/doc-index.md +90 -0
- package/templates/docs/infrastructure/background-work.md +53 -0
- package/templates/docs/infrastructure/realtime-events.md +54 -0
- package/templates/docs/patterns/backend-patterns.md +96 -0
- package/templates/docs/patterns/codegen.md +69 -0
- package/templates/docs/patterns/frontend-patterns.md +92 -0
- package/templates/docs/patterns/state-management.md +73 -0
- package/templates/docs/patterns/testing.md +103 -0
- package/templates/docs/patterns/ui-style-guide.md +76 -0
- package/templates/docs/patterns/vertical-slice-anatomy.md +61 -0
- package/templates/docs/readme-archive.md +9 -0
- package/templates/docs/readme-features.md +7 -0
- package/templates/docs/readme-guides.md +7 -0
- package/templates/docs/readme-infrastructure.md +7 -0
- package/templates/docs/readme-patterns.md +9 -0
- package/templates/docs/readme-research.md +9 -0
- package/templates/docs/readme-working.md +9 -0
- package/templates/settings-pin.json +10 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-ignite
|
|
3
|
+
description: Become the IGNITION COPILOT for a spec pipeline — verify the committed ignition brief against live state, verify every step by receipt, and kick on the human's word. Authoring belongs to spec-pipeline; this seat VERIFIES, so it can run cold. Use right after spec-pipeline when the pipeline will run hosted (the manual alternative is spec-parent), or fresh with a topic whose specs are already authored. Two verbal gates: nothing moves before "stage it"; nothing spawns before "kick it".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# spec-ignite
|
|
7
|
+
|
|
8
|
+
Turn this session into the **ignition copilot**: the seat that puts an authored spec pipeline onto the
|
|
9
|
+
control plane. The division of labor is absolute: **the human owns the decision to spend, you verify; the
|
|
10
|
+
coordinator runs the pipeline, you hand it its orders; nothing moves without the human's words.**
|
|
11
|
+
|
|
12
|
+
**The doctrine in one line: THIS SEAT VERIFIES AND NARRATES; IT DOES NOT AUTHOR AND IT DOES NOT
|
|
13
|
+
DECIDE.** A gap you find is a finding to report, never a section for you to write. The control plane's
|
|
14
|
+
launcher creates a pipeline and kicks it from the browser with the actor stamped human; what that
|
|
15
|
+
replaced is the CLICKING, and nothing else — verify the brief before you believe it, re-run the
|
|
16
|
+
baselines, name every gap as a finding, two verbal gates, announce-before / verify-after are the half
|
|
17
|
+
that was never about plumbing, and they are why this seat exists.
|
|
18
|
+
|
|
19
|
+
## The choreography (two verbal gates, announced everything)
|
|
20
|
+
|
|
21
|
+
**ANNOUNCE BEFORE IGNITING, VERIFY AFTER** — the standing rule; the human must never miss the birth of a
|
|
22
|
+
run they built. They watch everything happen; receipts still gate every handoff.
|
|
23
|
+
|
|
24
|
+
**WHO CLICKS is a preference, not a contract.** The human drives the launcher when they want to feel the
|
|
25
|
+
flow; on their word you drive it instead. The two verbal gates, the announce-before/verify-after rule, and
|
|
26
|
+
the named-finding discipline are UNCHANGED either way — **only the mouse hand changes.**
|
|
27
|
+
|
|
28
|
+
### Phase 0 — prep (immediately; touches nothing)
|
|
29
|
+
|
|
30
|
+
1. Confirm the specs are **committed to the base branch** — seats read specs from their checkouts, so
|
|
31
|
+
uncommitted specs reach no worktree. If they are not committed, that commit is the coordinator's call
|
|
32
|
+
first.
|
|
33
|
+
2. Enumerate the **ignition gates** explicitly and check each: dependency efforts landed AND merged (name
|
|
34
|
+
the PRs); known-blocker fixits landed (name them); the control plane up and a runner connected; the
|
|
35
|
+
human's words per stage. Report any gate not yet green — you WAIT, never work around.
|
|
36
|
+
3. **VERIFY the ignition brief — you do not author it.** `spec-pipeline` wrote `00-ignition-brief.md`
|
|
37
|
+
complete, from the hot session that placed the specs. **You may be a cold session, and that is fine,
|
|
38
|
+
because your job is the check:** re-run the **Baselines** and confirm the quoted numbers; re-verify each
|
|
39
|
+
**Platform state** claim against the code or a row. Correct whatever drifted and say what you corrected.
|
|
40
|
+
A number you cannot reproduce is a finding, not a rounding error.
|
|
41
|
+
4. Prepare: (i) **the STAGE CARD**, DERIVED from the folder — see the table below; (ii) the brief's
|
|
42
|
+
**`## The kick`** text, which IS the coordinator's task — read it for review, don't rewrite it; (iii) a
|
|
43
|
+
read-only **heartbeat** plan (new-rows-only per tick, output to files, never into context).
|
|
44
|
+
|
|
45
|
+
<!-- control-plane-specific -->
|
|
46
|
+
### THE STAGE CARD IS DERIVED, NOT COMPOSED
|
|
47
|
+
|
|
48
|
+
**Every value the create door wants is already in the folder.** A card that lists FIELDS without
|
|
49
|
+
saying where they come from makes every ignition re-invent the mapping in prose — a wall of values to
|
|
50
|
+
retype, which is exactly what the launcher's form then asks for a second time. Read the folder; do not
|
|
51
|
+
compose from memory.
|
|
52
|
+
|
|
53
|
+
| Payload field | Derived from | Note |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `topic` | the folder name (`docs/research/{topic}/`) | kebab-case, as authored |
|
|
56
|
+
| `title` | the umbrella `01-*.md` H1 | |
|
|
57
|
+
| `specPath` | `docs/research/{topic}/` | **the folder, trailing slash** — a seat's own spec is a different field |
|
|
58
|
+
| `shape` | the brief's `## Birth` | closed set; the create door refuses an unknown by name |
|
|
59
|
+
| `defaultModel` | the brief's `## Birth` | **allowlist-exact** — a near-miss is refused, not coerced |
|
|
60
|
+
| `defaultEffort` | the brief's `## Birth` | |
|
|
61
|
+
| `seats[0]` | **THE COORDINATOR — it is a SEAT, and it is not one of the numbered specs** | omit it and the start door answers `pipeline-has-no-coordinator`; two answer `pipeline-has-many-coordinators` |
|
|
62
|
+
| `seats[1..]` | the brief's `## The sequence`, in order | one row per numbered spec, plus the review / docs-process rows if the pipeline has them |
|
|
63
|
+
| `seats[].kind` | the sequence row's kind, **verbatim from the closed vocabulary** | `build · fixit · review · docs-process · coordinator · retro · witness`. A kind nobody has heard of composes NOTHING; `unknown-seat-kind` names it |
|
|
64
|
+
| `seats[].title` | the spec's H1, or the sequence row's label | |
|
|
65
|
+
| `seats[].specPath` | `docs/research/{topic}/NN-{slug}.md` | **the FILE.** Confirm each exists — a spec named in the sequence and missing from disk reaches no worktree |
|
|
66
|
+
| `seats[].stageOrdinal` / `ordinal` | position in the sequence | sequential unless the brief declares stages |
|
|
67
|
+
| `edges[]` | the shape + the sequence | `fromSeatIndex`/`toSeatIndex` are **indices into your own `seats` array**, not seat ids — the ids do not exist until the create answers. `unknown-seat-relation` names a bad relation |
|
|
68
|
+
|
|
69
|
+
**A FIXIT ROW IS NEVER IN THE CREATE PAYLOAD.** `spec-pipeline` authors at most one and only as a
|
|
70
|
+
possibility — *"MAY materialise… not authored up front; the coordinator charters it from the review's
|
|
71
|
+
named list."* Staging a seat for it invents a seat the run may never want, and the coordinator cannot
|
|
72
|
+
un-charter it.
|
|
73
|
+
|
|
74
|
+
**DERIVING THE CARD *IS* THE THREE-DOCUMENT CHECK, AND THAT IS WHY THIS SEAT DERIVES IT.**
|
|
75
|
+
`spec-pipeline` requires the sequence to appear in THREE places and agree: the brief's
|
|
76
|
+
`## The sequence`, the umbrella's row table, and the tracker's `## Session status`. Build the seat
|
|
77
|
+
list from the brief, then read the other two and confirm they list the same rows in the same order.
|
|
78
|
+
**A disagreement is a finding, reported to the authoring seat — never something you reconcile
|
|
79
|
+
silently by picking one.** An effort whose three documents disagree has a seat acting on a shape the
|
|
80
|
+
others have stopped describing, and the create you are about to stage would make one of them true by
|
|
81
|
+
force.
|
|
82
|
+
|
|
83
|
+
**The card names only controls that exist.** A card that promises a budget cap or an artifact prefix
|
|
84
|
+
the launcher cannot take hands over values the form refuses and omits the ones it needs. **If you want
|
|
85
|
+
a budget cap, say so as a HUMAN STEP** — it is not staged from this card.
|
|
86
|
+
5. Report **PREP READY** — stage card + the kick text + gate states + **what you re-verified and what
|
|
87
|
+
drifted** + any precondition surprises, each named — and **STOP**.
|
|
88
|
+
|
|
89
|
+
### Phase 1 — STAGE (only on the human's "stage it")
|
|
90
|
+
|
|
91
|
+
6. Hand over the stage card. **Create happens in the launcher** — the human's click, or yours on their
|
|
92
|
+
word. You verify it by receipt, never by the form having been submitted: read the created pipeline back
|
|
93
|
+
and check **shape** / topic / path / defaults / **seats + edges** against the card (`shape`, not
|
|
94
|
+
`kind` — `kind` is the SEAT-level field, a different axis, and asking for the wrong one is how a
|
|
95
|
+
verification step reads as done while checking nothing). State plainly which values IF ANY are still
|
|
96
|
+
wrong, and verify again after they are fixed. **A create that answers 2xx is not a create that is
|
|
97
|
+
right** — read the row.
|
|
98
|
+
7. Announce: **"Staged and verified — nothing running, zero spend. Open the cockpit."** STOP.
|
|
99
|
+
|
|
100
|
+
### Phase 2 — KICK (only on the human's "kick it", cockpit open, human watching)
|
|
101
|
+
|
|
102
|
+
8. **Kick from the launcher**, with the brief's `## The kick` text as the coordinator's task. Verify the
|
|
103
|
+
kick by its receipt — the actor stamped on the act is the human's, and that stamp is the whole point of
|
|
104
|
+
the browser path. **The successful call is the birth timestamp**: a refused attempt spawns nothing, so
|
|
105
|
+
if a retry is needed, re-announce and say the failed attempts had zero side effects.
|
|
106
|
+
9. Verify ignition receipts — the coordinator's opening ledger write, its first `seat_spawn`, any scripted
|
|
107
|
+
probe's refusal — and **narrate each beat as it lands**, plain language. A loud row at ignite is
|
|
108
|
+
information, not a failure.
|
|
109
|
+
10. **HANDS OFF.** Heartbeats only (one line per phase change; long output to files, never into context).
|
|
110
|
+
The human owns the parks, the reaps, and the kill-switch from the first second. If the run wedges:
|
|
111
|
+
report state + STOP — the kill-switch is theirs.
|
|
112
|
+
|
|
113
|
+
**Operator doctrine (when the human rules a running pipeline through you):** rulings must direct SEAT
|
|
114
|
+
action, never promise operator actions ("the grant is coming" recreates a wait on the unobservable) · a
|
|
115
|
+
grant only lands on a LIVE park (requests expire — have the seat re-request first) · **NEVER raw terminal
|
|
116
|
+
injects** — the human keyboard and the hardened ladders are the only submit paths.
|
|
117
|
+
|
|
118
|
+
## The kick text — it is a POINTER, and `spec-pipeline` already wrote it
|
|
119
|
+
|
|
120
|
+
The coordinator's task is the brief's own **`## The kick`** section, copied verbatim. Roughly:
|
|
121
|
+
|
|
122
|
+
> You are the COORDINATOR of the {topic} pipeline. Invoke the `spec-parent` skill, then read
|
|
123
|
+
> `docs/research/{topic}/00-ignition-brief.md` and follow it. Your branch is `{the effort's branch}`.
|
|
124
|
+
|
|
125
|
+
**Do not draft a constitution here.** The seat's PROCEDURE is the `spec-parent` skill; its POLICY rides the
|
|
126
|
+
un-skippable policy suffix the control plane supplies at open (you do not paste it, and you cannot
|
|
127
|
+
override it); its HANDS are the coordinator charter the control plane attaches to a `coordinator` seed
|
|
128
|
+
and to no other kind. This RUN's choreography is the committed brief. A task string cannot be diffed or
|
|
129
|
+
reviewed the way a committed file can, so anything longer than a pointer is law in the worst available
|
|
130
|
+
place — and accumulated prompt-law breaks the coordinator it was meant to steer.
|
|
131
|
+
|
|
132
|
+
**Your job is to READ it and confirm the brief behind it holds up** — the coordinator reviews the text
|
|
133
|
+
before kick. Before you hand it over, check the brief actually covers: the sequence and roles · the
|
|
134
|
+
coordinator's beliefs with provenance · effort-specific law and per-spec riders · verified baselines ·
|
|
135
|
+
platform state · close-out (artifact filing, live-checks aggregation, what the PR must say). **A gap you
|
|
136
|
+
find is a finding to report, not a section for you to write** — send it back to the authoring seat.
|
|
137
|
+
|
|
138
|
+
<!-- control-plane-specific -->
|
|
139
|
+
## The doors — what the launcher drives, and what to call when it cannot
|
|
140
|
+
|
|
141
|
+
Use the launcher when it can do the step; these are the same doors underneath it, and they are what you
|
|
142
|
+
verify receipts against.
|
|
143
|
+
|
|
144
|
+
**THE CREDENTIAL IS THE HUMAN'S OWN BEARER TOKEN.** There is no shared machine key on this plane and
|
|
145
|
+
there will not be one. `Authorization: Bearer <token>`, audience-scoped to the API. A seat's job ticket
|
|
146
|
+
CANNOT create or start a pipeline — the pipeline is owner-scoped and the owner stamp refuses when there
|
|
147
|
+
is no user claim.
|
|
148
|
+
|
|
149
|
+
**WHERE ONE COMES FROM:** the runner's sign-in (`periscope login`) runs an interactive device-code flow
|
|
150
|
+
and writes the result to **`~/.periscope/token-cache.json`** — or `$PERISCOPE_CONFIG_DIR/token-cache.json`
|
|
151
|
+
when that is set; the runner reads the same file. So the token is **a file on this machine**, and a
|
|
152
|
+
session with shell access can use it.
|
|
153
|
+
|
|
154
|
+
**IT IS A CACHE, NOT A BARE TOKEN.** The shape belongs to the identity library, not to the loop — read it
|
|
155
|
+
as a cache and take what is actually there; do not hard-code a field name into a skill, and never paste a
|
|
156
|
+
token into a doc, a commit, or a task string.
|
|
157
|
+
|
|
158
|
+
**A paired runner needs none of this** — a paired credential has no idle clock. A sign-in token expires
|
|
159
|
+
when idle; if the hosts read below is empty, assume the token before you assume the runner.
|
|
160
|
+
|
|
161
|
+
**THIS IS WHY "the skill pops it into existence" IS ALREADY POSSIBLE.** Create + start are two
|
|
162
|
+
authenticated calls, the credential is a file, and both doors are documented below. **The launcher is
|
|
163
|
+
the mouse path, not the only path** — the choreography and the two verbal gates are unchanged either
|
|
164
|
+
way, and WHO CLICKS was already a preference rather than a contract. What is NOT optional: the human's
|
|
165
|
+
words before each stage, and a receipt read back after.
|
|
166
|
+
|
|
167
|
+
### Phase 0 — the hosts read, and it ends the guessing
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
GET {ApiBase}/api/periscope/hosts
|
|
171
|
+
-> { "hosts": [ { "hostId": "…", "scopedToYou": true, "connectedAt": "…" } ] }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**AN EMPTY LIST MEANS NOTHING CAN BE KICKED.** The runner is a SEPARATE PROCESS that dials OUT — it is
|
|
175
|
+
not part of the API. If the list is empty, stop and read the field notes below before staging anything;
|
|
176
|
+
the cause is almost always the credential, and the runner will look like it started.
|
|
177
|
+
|
|
178
|
+
### Phase 1 — create
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
POST {ApiBase}/api/pipelines
|
|
182
|
+
{ "shape": "supervisor", "topic": "…", "title": "…", "specPath": "docs/research/{topic}/",
|
|
183
|
+
"defaultModel": "<allowlist-exact>", "defaultEffort": "…",
|
|
184
|
+
"seats": [ { "kind": "coordinator"|"build", "stageOrdinal": 0, "ordinal": 0,
|
|
185
|
+
"title": "…", "specPath": "docs/research/{topic}/NN-….md" } ],
|
|
186
|
+
"edges": [ { "fromSeatIndex": 0, "toSeatIndex": 1, "relation": "supervises" } ] }
|
|
187
|
+
-> { "pipelineId", "seatIds": [ … ], "edgeCount" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**NEVER SEND `""` FOR AN OPTIONAL — send `null` or omit it.** An empty string defeats `??` defaulting
|
|
191
|
+
server-side and travels to the wire as a value.
|
|
192
|
+
|
|
193
|
+
### Phase 2 — start
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
POST {ApiBase}/api/pipelines/{id}/start
|
|
197
|
+
{ "hostId": "<from the hosts read — never guessed>",
|
|
198
|
+
"task": "<the brief's `## The kick` text, VERBATIM>",
|
|
199
|
+
"cwd": null, <-- null = "the runner's provider decides". Omitting is fine; "" is NOT.
|
|
200
|
+
"dispatch": "link" } <-- "none" writes bookkeeping only; that is what an integration test wants
|
|
201
|
+
-> { coordinatorSeatId, sessionId, sessionKey, spawnedActId, ticket, ticketScopes,
|
|
202
|
+
ticketRefusal, pipelineState, poll, + the kick DISPOSITION }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**THE STATUS LINE IS TRUSTWORTHY:**
|
|
206
|
+
- **200** — done. Covers `dispatched`, the two pinned-intentional cases: `not-asked`
|
|
207
|
+
(`dispatch:"none"`, bookkeeping by request) and `already-live` (idempotent adoption) — **and a
|
|
208
|
+
ticketless coordinator** (`ticketRefusal` names why; the seat is real and has NO TOOLS — a
|
|
209
|
+
degradation the body names, not a pending effect: a ticket is minted at open only, so a 202 there
|
|
210
|
+
would promise an effect that can never arrive).
|
|
211
|
+
- **202** — accepted, effect NOT yet observable: `deferred-to-replay` only (the reconnect replay
|
|
212
|
+
delivers). The body's `poll` member names the read to watch.
|
|
213
|
+
- **409** — the kick REFUSED, **and the error body is the FULL receipt** (sessionId, spawnedActId,
|
|
214
|
+
ticket state, `dispatchRefusal` by name; `pipelineState` stays `planned`). The committed rows are
|
|
215
|
+
facts — parse the 409 body, never discard it. Also `pipeline-not-startable` (a terminal pipeline)
|
|
216
|
+
and, on a second start against a live coordinator, 400 `seat-already-has-live-session` — the
|
|
217
|
+
respawn recipe below is the sanctioned path, not start-again.
|
|
218
|
+
- **400** — a pre-flight refusal by name, zero rows written.
|
|
219
|
+
|
|
220
|
+
**The disposition still rides the body and is still the authoritative DETAIL** — the status line agrees
|
|
221
|
+
with it. If a door answers 200 for a REFUSED kick with the truth only in the body, you are on an old
|
|
222
|
+
build, and that is a finding.
|
|
223
|
+
|
|
224
|
+
**The refusal vocabulary, exact literals:** `kick-names-no-host` (you omitted `hostId`) ·
|
|
225
|
+
`kick-names-no-task` (you omitted the task — refuses BEFORE writing any row) · `no-live-link-for-host`
|
|
226
|
+
(the host you named is not connected) · `pipeline-has-no-coordinator` / `pipeline-has-many-coordinators` ·
|
|
227
|
+
`unknown-seat-kind` / `unknown-seat-relation` (the CREATE door validates both, listing the closed set) ·
|
|
228
|
+
`path-input-missing` (a no-provider host got a null `cwd`) · `session-already-closed:{state}` ·
|
|
229
|
+
`session-has-no-host` (the STOP door only, where it is true).
|
|
230
|
+
|
|
231
|
+
### The release door — and the trap it replaced
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
POST {ApiBase}/api/pipelines/{id}/sessions/{sid}/release ends the session, seat stays usable
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Ends a session **without spending the seat**: `running` to `planned`, respawnable.
|
|
238
|
+
|
|
239
|
+
**NEVER USE `/close` TO RETRY A START.** Close ABANDONS the seat, `abandoned` is TERMINAL, and the
|
|
240
|
+
pipeline is burned — a start afterwards answers `seat-terminal`.
|
|
241
|
+
|
|
242
|
+
### Field notes
|
|
243
|
+
|
|
244
|
+
- **A SIGN-IN TOKEN DIES WHEN IDLE, AND THE RUNNER EXITS CLEAN.** It refuses and finishes with a
|
|
245
|
+
success-shaped exit. **If the hosts read is empty but the runner "ran", this is why.** Remedy: the
|
|
246
|
+
runner's sign-in again (an interactive browser flow), then restart the runner — or pair the runner,
|
|
247
|
+
which has no idle clock.
|
|
248
|
+
- **THE RUNNER IS NOT PART OF THE API.** It is a separate process that dials OUT over a WebSocket. A
|
|
249
|
+
stale build of it serves old code silently.
|
|
250
|
+
- **`ticketRefusal: null` is the receipt that the coordinator got TOOLS.** Non-null means a toolless seat
|
|
251
|
+
that will look alive and do nothing.
|
|
252
|
+
- **`cwd: null` is meaningful, `""` is fatal.** A required member arriving null is refused at the
|
|
253
|
+
runner's DECODER — and under the no-ack law that refusal goes to the runner's stdout and nowhere else,
|
|
254
|
+
so the controller sees nothing. The encoder validates outbound frames, which is why this is a named
|
|
255
|
+
refusal instead of a silent wedge.
|
|
256
|
+
- **A shell-serialized JSON body mangles a multi-line `task`** — write the complete JSON to a UTF-8 file
|
|
257
|
+
and POST it with `--data-binary "@file"`. Never inline a multi-line task through a shell.
|
|
258
|
+
- **If a restart rotates resource or container names on your stack**, re-discover before any substrate
|
|
259
|
+
check; a stale name fails as "no such container", which reads as a broken stack.
|
|
260
|
+
|
|
261
|
+
**IF THIS SECTION AND THE CODE DISAGREE, THE CODE WINS AND THE DISAGREEMENT IS A FINDING.** Report what
|
|
262
|
+
you found rather than working around it.
|
|
263
|
+
|
|
264
|
+
## Hard fences
|
|
265
|
+
|
|
266
|
+
- Nothing is created, spawned, or spent before the human's words — per stage, every time.
|
|
267
|
+
- Every UI shortfall is a NAMED FINDING, never a silent script step.
|
|
268
|
+
- You never approve parks, never reap seats, never touch the kill-switch — those are the human's; you
|
|
269
|
+
verify and narrate.
|
|
270
|
+
- The touch budget is stated HONESTLY at prep (parks + reaps per session count), including known
|
|
271
|
+
anomalies that may change it — surprises are findings, not adjustments.
|
|
272
|
+
|
|
273
|
+
## Related
|
|
274
|
+
- `spec-pipeline` (authors what you ignite, including the brief you verify) · `spec-parent` (the
|
|
275
|
+
coordinator's procedure, and the HAND-CRANKED alternative) · `spec-seat` + `spec-child` (what the
|
|
276
|
+
control plane seeds into every build seat) · `spec-retro` (the close-out audit) · `spec-witness`
|
|
277
|
+
(point one at the run you just ignited)
|