mandrel 1.68.0 → 1.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.agents/docs/agentrc-reference.json +1 -2
  2. package/.agents/docs/configuration.md +2 -4
  3. package/.agents/schemas/agentrc.schema.json +1 -5
  4. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +2 -1
  5. package/.agents/scripts/epic-deliver-preflight.js +30 -13
  6. package/.agents/scripts/epic-deliver-prepare.js +40 -53
  7. package/.agents/scripts/epic-execute-record-wave.js +119 -133
  8. package/.agents/scripts/lib/baselines/refresh-service.js +13 -1
  9. package/.agents/scripts/lib/config/explain.js +0 -2
  10. package/.agents/scripts/lib/config/limits.js +19 -8
  11. package/.agents/scripts/lib/config-settings-schema.js +1 -2
  12. package/.agents/scripts/lib/maintainability-utils.js +32 -9
  13. package/.agents/scripts/lib/orchestration/epic-cleanup.js +11 -7
  14. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +6 -6
  15. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +11 -5
  16. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +203 -110
  17. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +38 -78
  18. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +16 -13
  19. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +10 -7
  20. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +37 -24
  21. package/.agents/scripts/lib/orchestration/manifest-builder.js +6 -0
  22. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +6 -2
  23. package/.agents/scripts/lib/orchestration/wave-record-io.js +18 -77
  24. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +78 -122
  25. package/.agents/scripts/lib/orchestration/wave-record-projection.js +21 -226
  26. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +18 -1
  27. package/.agents/scripts/lib/presentation/manifest-render-waves.js +77 -4
  28. package/.agents/scripts/lib/story-adjacency.js +14 -10
  29. package/.agents/scripts/lib/story-body/story-body.js +36 -4
  30. package/.agents/scripts/lib/templates/decomposer-prompts.js +23 -3
  31. package/.agents/scripts/lib/wave-runner/ready-set.js +295 -0
  32. package/.agents/scripts/lib/wave-runner/tick.js +312 -206
  33. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +2 -1
  34. package/.agents/scripts/lint-label-vocabulary.js +1 -1
  35. package/.agents/scripts/stories-wave-tick.js +262 -161
  36. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +6 -0
  37. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +108 -101
  38. package/.agents/skills/skills.index.json +2 -2
  39. package/.agents/workflows/deliver.md +12 -9
  40. package/.agents/workflows/helpers/deliver-epic.md +126 -90
  41. package/.agents/workflows/helpers/deliver-stories.md +131 -85
  42. package/.agents/workflows/helpers/plan-epic.md +13 -10
  43. package/.agents/workflows/plan.md +1 -1
  44. package/docs/CHANGELOG.md +14 -0
  45. package/package.json +1 -1
  46. package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +0 -91
@@ -26,7 +26,7 @@ back to the operator-merges-button path.
26
26
  ```text
27
27
  /deliver <epicId>
28
28
  → Phase 1 — prepare (epic-deliver-prepare.js)
29
- → Phase 2 — wave loop (wave-tick.js + Agent fan-out × concurrencyCap)
29
+ → Phase 2 — ready-set loop (wave-tick.js dispatch ready set → observe → re-tick)
30
30
  → Phase 3 — close-validation (lint + test + ratchets on epic/<id>)
31
31
  → Phase 4 — epic-audit (helpers/epic-audit.md — change-set audits via selectAudits)
32
32
  → Phase 5 — code-review (helpers/code-review.md with scope: epic)
@@ -124,9 +124,9 @@ Every other runtime modifier is sourced from the Epic's labels or from
124
124
 
125
125
  ### Phase 1 prelude — Delivery preflight (Story #2899 / F13)
126
126
 
127
- Before `epic-deliver-prepare.js` seeds the wave plan, run
127
+ Before `epic-deliver-prepare.js` seeds the checkpoint, run
128
128
  `epic-deliver-preflight.js` so the operator (and any reviewer reading the
129
- Epic ticket) sees the estimated Story count, install cost, wave count,
129
+ Epic ticket) sees the estimated Story count, install cost, dependency depth,
130
130
  GitHub API request volume, and Claude Max quota burn for the run that is
131
131
  about to fan out. **Preflight always runs before Story fan-out.**
132
132
 
@@ -137,9 +137,10 @@ node .agents/scripts/epic-deliver-preflight.js --epic <epicId> --post
137
137
  The CLI upserts a `delivery-preflight` structured comment on the Epic
138
138
  (idempotent across re-runs) and prints a JSON envelope on stdout with
139
139
  the canonical metric keys `storyCount`, `installCostSeconds`,
140
- `waveCount`, `githubApiRequests`, `claudeQuotaTokens`, plus a `breaches`
141
- array describing any `delivery.preflight.max*` thresholds the estimate
142
- exceeds.
140
+ `dependencyDepth` (the longest dependency chain — the ready-set wall-clock
141
+ floor, replacing the retired wave count), `githubApiRequests`,
142
+ `claudeQuotaTokens`, plus a `breaches` array describing any
143
+ `delivery.preflight.max*` thresholds the estimate exceeds.
143
144
 
144
145
  **Breach handling.** When `breaches` is non-empty, the workflow MUST
145
146
  flip the Epic to `agent::blocked`, surface the envelope in chat for the
@@ -162,11 +163,14 @@ node .agents/scripts/epic-deliver-prepare.js --epic <epicId> [--steal] [--as <ha
162
163
  ```
163
164
 
164
165
  Validates `type::epic`, enumerates `type::story` descendants, parses
165
- `blocked by #N` plus explicit `dependencies`, runs `Graph.computeWaves()`,
166
- and upserts the `epic-run-state` checkpoint. Treat the printed JSON as
167
- `state`: `{ epicId, totalWaves, concurrencyCap, plan, checkpointInitializedAt }`.
168
- `plan[N]` is the Stories assigned to wave `N`. Flip the Epic to
169
- `agent::executing` (idempotent) after the CLI returns.
166
+ `blocked by #N` plus explicit `dependencies`, computes the dependency DAG
167
+ (to enumerate the open Story set), and upserts the `epic-run-state`
168
+ checkpoint in the per-Story-status shape (a flat `stories` map seeded at
169
+ `pending`, plus the global `concurrencyCap`). Treat the printed JSON as
170
+ `state`: `{ epicId, storyCount, concurrencyCap, stories, checkpointInitializedAt }`.
171
+ `stories` is the flat dispatch hint (`{ storyId, worktree, title }` per open
172
+ Story); the ready-set `tick` (Phase 2) decides which to dispatch on each
173
+ beat. Flip the Epic to `agent::executing` (idempotent) after the CLI returns.
170
174
 
171
175
  > **Preflight guards (Story #3482 / F-workflow-guards).** Before the
172
176
  > snapshot phase runs — and before any worktree is created — prepare runs
@@ -217,14 +221,28 @@ Once the preflight guards pass, the snapshot phase applies one more gate:
217
221
 
218
222
  ---
219
223
 
220
- ## Phase 2 — Wave loop
224
+ ## Phase 2 — Ready-set loop
221
225
 
222
- The wave-loop state machine lives in
223
- [`lib/wave-runner/tick.js`](../../scripts/lib/wave-runner/tick.js) — one
224
- stateless `tick({ epic })` call returns one `WaveTickResult` describing
225
- the next action. The slash command's job is to call `tick()` via its CLI
226
- shim, dispatch from `nextAction.stories` via the Agent tool, persist the
227
- outcome, and loop until terminal.
226
+ The scheduler lives in
227
+ [`lib/wave-runner/tick.js`](../../scripts/lib/wave-runner/tick.js) — a thin
228
+ **Epic adapter over the ready-set core**
229
+ ([`lib/wave-runner/ready-set.js`](../../scripts/lib/wave-runner/ready-set.js)).
230
+ One stateless `tick({ epic })` call re-derives readiness from the **live**
231
+ Story bodies + labels on every beat and returns one `WaveTickResult`
232
+ describing the next action. There is **no wave barrier** (Story #4155): a
233
+ Story whose own dependencies are all done is dispatched the instant a slot is
234
+ free under the GLOBAL in-flight cap, even while an unrelated sibling Story is
235
+ still `agent::executing`. The loop is simply:
236
+
237
+ ```text
238
+ tick → dispatch the ready set → observe → re-tick → … → epic-complete
239
+ ```
240
+
241
+ The slash command's job each beat is to call `tick()` via its CLI shim,
242
+ dispatch the Stories in `nextAction.stories` via the Agent tool, record each
243
+ returned Story's terminal status, and re-tick until terminal. There is no
244
+ `record-wave` / `currentWave` step — the checkpoint carries only a flat
245
+ per-Story status map (for resume + the operator rollup) and the global cap.
228
246
 
229
247
  ### 2a. Tick — plan the next action
230
248
 
@@ -237,55 +255,59 @@ Stdout is one `WaveTickResult` envelope:
237
255
  ```json
238
256
  {
239
257
  "nextAction":
240
- { "kind": "dispatch", "stories": [{ "id": <n>, "title": "…", "worktree"?: "" }, ...] }
241
- | { "kind": "observe", "waitingOn": [<storyId>, ...] }
242
- | { "kind": "wave-complete", "index": <n> }
243
- | { "kind": "epic-complete" },
258
+ { "kind": "dispatch", "stories": [{ "id": <n>, "title": "…" }, ...], "in-flight": [<storyId>, ...] }
259
+ | { "kind": "observe", "waitingOn": [<storyId>, ...], "in-flight": [<storyId>, ...] }
260
+ | { "kind": "halt", "reason": "dependency-cycle" | "unsatisfiable-dependency", "stuckStories": [<storyId>, ...], "cycle"?: [<storyId>, ...], "in-flight": [<storyId>, ...] }
261
+ | { "kind": "epic-complete", "in-flight": [<storyId>, ...] },
244
262
  "blockedStories": [{ "storyId": <n>, "reason": "…", "detail"?: "…" }, ...],
245
263
  "gateFailures": [{ "storyId": <n>, "gate": "…", "detail"?: "…" }, ...],
246
- "currentWave": <n>,
247
- "totalWaves": <n>
264
+ "readyCount": <n>,
265
+ "inFlight": [<storyId>, ...]
248
266
  }
249
267
  ```
250
268
 
251
- The CLI is a planner: it returns the `nextAction` envelope above. Wave
252
- progress is durable on two operator-facing surfaces — the `epic-run-state`
253
- checkpoint (resume) and the `epic-run-progress` rollup comment, both written
254
- by `epic-execute-record-wave.js` at the wave boundary. The CLI itself emits
255
- only the two wave-window forensics signals that have a live consumer —
256
- `wave-start` and `wave-complete`, which the perf-aggregator brackets into the
257
- `waveParallelism` report (and `wave-start` anchors span-tree Story spans).
258
- Story #3909 retired the write-only wave events with no reader (`wave-tick`,
259
- `epic-complete`) they duplicated the checkpoint + rollup. The
260
- [`signals` helper](signals.md) (`node .agents/scripts/signals-view.js`)
261
- renders the forensics signals in the span-tree view.
269
+ `nextAction.stories` is the **ready set** for this beat the
270
+ dependency-satisfied, overlap-free subset of open Stories, capped at
271
+ `globalCap inFlight`. The CLI is a planner: it dispatches nothing and
272
+ persists nothing. It emits only the two wave-window forensics signals that
273
+ have a live consumer — `wave-start` (on the run's first dispatch) and
274
+ `wave-complete` (when the run finishes), which the perf-aggregator brackets
275
+ into the `waveParallelism` report (and `wave-start` anchors span-tree Story
276
+ spans). The [`signals` helper](signals.md)
277
+ (`node .agents/scripts/signals-view.js`) renders the forensics signals in the
278
+ span-tree view.
279
+
280
+ > **Fail-closed on an old-shape checkpoint.** If the Epic still carries a
281
+ > pre-ready-set (`plan` / `currentWave` / `totalWaves`) `epic-run-state`
282
+ > checkpoint, the tick **refuses to run** and throws an explicit operator
283
+ > message — re-run `epic-deliver-prepare.js --epic <id>` to re-seed the
284
+ > checkpoint in the per-Story-status shape, then re-run `/deliver`.
262
285
 
263
286
  ### 2b. Dispatch — fan out per-Story Agent calls
264
287
 
265
- *You* (the LLM running this skill) are the wave dispatcher; you never
266
- invoke `helpers/epic-deliver-story` yourself. Emit **one `Agent` tool call per
288
+ *You* (the LLM running this skill) are the dispatcher; you never invoke
289
+ `helpers/epic-deliver-story` yourself. Emit **one `Agent` tool call per
267
290
  Story** in `nextAction.stories` (even when `length === 1` — the
268
291
  parent-child boundary keeps the return-parser uniform). The *children*
269
292
  run [`helpers/epic-deliver-story`](epic-deliver-story.md). Use
270
293
  `subagent_type: general-purpose`.
271
294
 
272
295
  Emit **one assistant turn** with **N parallel `Agent` calls** where
273
- `N === min(nextAction.stories.length, concurrencyCap)`. When the wave
274
- exceeds `concurrencyCap`, dispatch the first `concurrencyCap` Stories
275
- as background calls (`run_in_background: true`) and refill from
276
- `nextAction.stories` immediately as each child returns never exceed
277
- the cap, never wait for a whole batch before refilling.
278
-
279
- > **Throughput tradeoff.** The default `concurrencyCap` of 3 is
280
- > intentionally conservative — it keeps host-quota consumption low on
281
- > Epics with small waves and avoids flooding the GitHub API. For
282
- > wide-wave Epics (many Stories per wave) where the host has adequate
283
- > parallel-agent quota, raising `delivery.deliverRunner.concurrencyCap`
284
- > in `.agentrc.json` reduces wall-clock time proportionally to the
285
- > extra concurrency. The safe default is left in place; this is a
286
- > deliberate operator-tuning knob, not a hidden performance ceiling.
287
- > See `agentrc-reference.json` `delivery.deliverRunner.concurrencyCap` for
288
- > the configuration surface.
296
+ `N === nextAction.stories.length` (the ready set is already capped at
297
+ `globalCap − inFlight` by the tick, so it never exceeds available slots).
298
+ Dispatch the ready set as background calls (`run_in_background: true`) and,
299
+ as each child returns, record it (§ 2c) and **re-tick** (§ 2a) to pull the
300
+ next ready set — never wait for the whole set before refilling.
301
+
302
+ > **Throughput tradeoff.** The default `concurrencyCap` of 3 is the GLOBAL
303
+ > in-flight cap, intentionally conservative — it keeps host-quota
304
+ > consumption low and avoids flooding the GitHub API. For Epics with wide
305
+ > dependency-free fronts where the host has adequate parallel-agent quota,
306
+ > raising `delivery.deliverRunner.concurrencyCap` in `.agentrc.json` reduces
307
+ > wall-clock time proportionally to the extra concurrency. The safe default
308
+ > is left in place; this is a deliberate operator-tuning knob, not a hidden
309
+ > performance ceiling. See `agentrc-reference.json`
310
+ > `delivery.deliverRunner.concurrencyCap` for the configuration surface.
289
311
 
290
312
  **Ledger the dispatch BEFORE the Agent call.** Immediately before each
291
313
  per-Story `Agent` tool call (one shell-out per Story, every attempt —
@@ -294,19 +316,21 @@ including retries from a refill), invoke
294
316
  so the lifecycle ledger durably records the dispatch attempt. The
295
317
  emit must happen **before** the Agent call fires — never after — so
296
318
  that a host-process crash mid-Agent leaves a `story.dispatch.start`
297
- record that `wave-tick.js` (see § 2a) can surface under
298
- `nextAction['in-flight']` on the next tick:
319
+ record that `wave-tick.js` (see § 2a) excludes from the next beat's ready
320
+ set and surfaces under `nextAction['in-flight']`:
299
321
 
300
322
  ```bash
301
323
  node .agents/scripts/lifecycle-emit-story-dispatch.js \
302
324
  --epic <epicId> --story <storyId> \
303
- --wave <currentWave> --attempt <attempt>
325
+ --wave 0 --attempt <attempt>
304
326
  ```
305
327
 
306
- `<attempt>` starts at 1 for the Story's first dispatch in this wave
307
- and increments on each retry/refill. The CLI appends exactly one
308
- NDJSON line to `temp/epic-<epicId>/lifecycle.ndjson`; the matching
309
- `story.dispatch.end` record is appended later by
328
+ Pass `--wave 0` the ready-set runtime has a single continuous front, so
329
+ the ledger's `waveIndex` is a fixed `0` (it is metadata for the start/end
330
+ pairing math, not a scheduling input). `<attempt>` starts at 1 for the
331
+ Story's first dispatch and increments on each retry/refill. The CLI appends
332
+ exactly one NDJSON line to `temp/epic-<epicId>/lifecycle.ndjson`; the
333
+ matching `story.dispatch.end` record is appended later by
310
334
  `epic-execute-record-wave.js` (via `emit-story-dispatch-end.js`, Story #3900)
311
335
  after the Agent return is recorded in § 2c.
312
336
 
@@ -329,7 +353,7 @@ There is **no per-child JSON return-parsing ceremony** for the parent
329
353
  to enforce. GitHub state is the contract: `epic-execute-record-wave.js`
330
354
  (§ 2c, mode B) treats each child's raw return text as a best-effort
331
355
  hint and reconciles any unparseable, empty, or missing return directly
332
- from the Story's live labels and comments (Story #3907).
356
+ from the Story's live labels and comments.
333
357
 
334
358
  **Sub-agent dispatch.** `Agent` calls emit no `model:` argument by
335
359
  default — children inherit from the `general-purpose` sub-agent
@@ -338,53 +362,66 @@ definition and the parent's worktree context. No
338
362
  specific call needs to override the inherited model, pass `model:` as a
339
363
  per-call literal at the `Agent(...)` site.
340
364
 
341
- ### 2c. Record the wave outcome
365
+ ### 2c. Record the Story outcomes
342
366
 
343
- Once every dispatched Story has returned, persist via
344
- `epic-execute-record-wave.js`:
367
+ As dispatched Stories return (record them as they land — you need not wait
368
+ for the whole ready set), persist each Story's terminal status via
369
+ `epic-execute-record-wave.js`. There is **no `--wave` flag and no
370
+ `currentWave`** — the recorder splices each Story's status into the
371
+ checkpoint's flat per-Story map and re-renders the rollup:
345
372
 
346
373
  ```bash
347
374
  # Mode A — host LLM already parsed each child return.
348
375
  node .agents/scripts/epic-execute-record-wave.js \
349
- --epic <epicId> --wave <N> [--concurrency-cap <N>] \
350
- --results @<file>|<inline-json>
376
+ --epic <epicId> --results @<file>|<inline-json>
351
377
 
352
378
  # Mode B — pipe the raw per-Story sub-agent return texts directly.
353
379
  node .agents/scripts/epic-execute-record-wave.js \
354
- --epic <epicId> --wave <N> [--concurrency-cap <N>] \
355
- --returns @<file>|<inline-json>
380
+ --epic <epicId> --returns @<file>|<inline-json>
356
381
  # `<inline-json>` shape: [{ "storyId": <n>, "returnText": "<raw text>" }]
357
382
  ```
358
383
 
359
384
  **Mode B is the default path** — pipe the raw return texts through
360
- without inspecting them. The CLI reconciles parse failures from
361
- GitHub, aggregates terminal status, appends to `state.waves[]`,
362
- re-renders `epic-run-progress`, and prints
363
- `{ status, nextAction, renderedBody, ... }`. Print `renderedBody`
364
- verbatim, then optionally append a short **Notable** section (0–5
365
- bullets on newly blocked / failed / slow Stories, friction,
385
+ without inspecting them. The CLI reconciles parse failures from GitHub,
386
+ records each Story's terminal status, emits one `story.dispatch.end` per
387
+ recorded Story (closing the ledger pairing), re-renders
388
+ `epic-run-progress`, and prints `{ status, nextAction, renderedBody, ... }`.
389
+ Print `renderedBody` verbatim, then optionally append a short **Notable**
390
+ section (0–5 bullets on newly blocked / failed / slow Stories, friction,
366
391
  elapsed-time surprises).
367
392
 
368
- > **Crash recovery empty mode-B returns (Story #3907).** If the host
369
- > crashed *after* this wave's children finished but *before* `record-wave`
370
- > ran, no return text survives. Re-run mode B with an **empty** returns
371
- > array (`--returns '[]'`): the CLI reconciles **every** Story in
372
- > `plan[<N>]` directly from GitHub (label + `state`) and records the wave
373
- > from that live state instead of recording a falsely-`complete` empty
374
- > wave. This is what lets `currentWave` advance after a crash — only
375
- > `record-wave` advances it, so without this path the loop would return
376
- > `wave-complete` for the same index forever.
393
+ > **Crash recovery.** If the host crashed *after* a child finished but
394
+ > *before* its return was recorded, the next `tick` re-derives that Story's
395
+ > state directly from its live label (the tick reads labels every beat), so a
396
+ > done-but-unrecorded Story is recognised as done and never re-dispatched —
397
+ > there is no falsely-`complete` empty wave to recover from. If you want to
398
+ > reconcile a known-completed Story whose return text was lost, re-record it
399
+ > from its live state by passing `--results '[{"storyId":<n>,"status":"done"}]'`
400
+ > (verification re-checks the live label before recording `done`).
377
401
 
378
402
  ### 2d. Loop on `nextAction`
379
403
 
380
404
  After `2c`, re-run `wave-tick.js`. Branch on the new envelope:
381
405
 
382
- - `dispatch` → repeat 2b/2c for the same wave (refill) or the next wave.
406
+ - `dispatch` → repeat 2b/2c for the new ready set (the next beat's
407
+ dependency-satisfied Stories), then re-tick.
383
408
  - `observe` → poll the Epic (children may still be in flight, or some
384
409
  are `agent::blocked`). If `blockedStories` is non-empty, post a
385
410
  friction comment, flip Epic to `agent::blocked`, park.
386
- - `wave-complete` → loop to the next wave.
387
- - `epic-complete` proceed to Phase 3.
411
+ - `halt` → the run is stuck: no Story is dispatchable, nothing is in
412
+ flight, yet not every Story is done. `reason` distinguishes the two
413
+ causes — `dependency-cycle` (the in-scope Stories form a `blocked by`
414
+ cycle; `cycle` lists the offending Story ids) or
415
+ `unsatisfiable-dependency` (a Story is gated on a dependency that can
416
+ never satisfy). `stuckStories` names the Story id(s) that stranded the
417
+ run. Post a friction comment quoting `reason` + `stuckStories`, flip the
418
+ Epic to `agent::blocked`, and park for the operator. **Never** treat a
419
+ `halt` as completion — proceeding to Phase 3 would silently drop the
420
+ stuck Story.
421
+ - `epic-complete` → **every** in-scope Story is done and nothing is in
422
+ flight; proceed to Phase 3. (The tick returns `epic-complete` only when
423
+ the done count equals the in-scope Story count — a stuck Story surfaces
424
+ as `halt`, not a false `epic-complete`.)
388
425
 
389
426
  ### 2e. Idle Watchdog
390
427
 
@@ -394,7 +431,7 @@ A Story's implementation loop can run for many minutes between
394
431
  sub-agent that has gone silent (host crash, mid-Story stall, lost
395
432
  return). The Idle Watchdog closes that gap.
396
433
 
397
- **Cadence.** While any wave is in flight (i.e. `nextAction.kind` is
434
+ **Cadence.** While any Story is in flight (i.e. `nextAction.kind` is
398
435
  `observe` or the most recent dispatch's `in-flight` list is non-empty),
399
436
  re-tick every **30 minutes** with the watchdog flag:
400
437
 
@@ -447,9 +484,8 @@ incrementing the `--attempt` counter; if the child is alive but
447
484
  genuinely blocked, flip the Story to `agent::blocked` and proceed per
448
485
  § 2d's `observe` branch.
449
486
 
450
- Stop the watchdog cadence once `wave-tick.js` returns
451
- `wave-complete` or `epic-complete` — there are no in-flight Stories
452
- left to monitor.
487
+ Stop the watchdog cadence once `wave-tick.js` returns `epic-complete` —
488
+ there are no in-flight Stories left to monitor.
453
489
 
454
490
  ---
455
491