@drunkcoding/agents-and-skills 0.0.18 → 0.0.19

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/README.md +23 -0
  3. package/package.json +1 -1
  4. package/plugins/auto-power/.claude-plugin/plugin.json +1 -1
  5. package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
  6. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  7. package/plugins/team-superpower/.claude-plugin/plugin.json +1 -1
  8. package/plugins/team-superpower/agents/backend-developer.md +93 -3
  9. package/plugins/team-superpower/agents/designer.md +17 -2
  10. package/plugins/team-superpower/agents/frontend-developer.md +94 -3
  11. package/plugins/team-superpower/agents/planner.md +62 -6
  12. package/plugins/team-superpower/agents/qa-engineer.md +73 -17
  13. package/plugins/team-superpower/agents/reviewer.md +71 -6
  14. package/plugins/team-superpower/agents/security-engineer.md +16 -1
  15. package/plugins/team-superpower/agents/software-architect.md +16 -1
  16. package/plugins/team-superpower/assets/AGENTS.md.template +23 -0
  17. package/plugins/team-superpower/assets/CLAUDE.md.template +17 -0
  18. package/plugins/team-superpower/assets/SESSION_README.md +47 -0
  19. package/plugins/team-superpower/commands/team-feature.md +217 -11
  20. package/plugins/team-superpower/hooks/task-completed.sh +203 -0
  21. package/plugins/team-superpower/hooks/task-created.sh +42 -1
  22. package/plugins/team-superpower/scripts/assess-complexity.sh +194 -0
  23. package/plugins/team-superpower/scripts/detect-stack.sh +39 -0
  24. package/plugins/team-superpower/scripts/wave-collision-check.sh +60 -0
  25. package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
  26. package/plugins/html-effectiveness/tests/_tmp.test.js +0 -58
  27. package/plugins/html-effectiveness/tests/fixtures/01-exploration-code-approaches.data.json +0 -3
  28. package/plugins/html-effectiveness/tests/fixtures/02-exploration-visual-designs.data.json +0 -3
  29. package/plugins/html-effectiveness/tests/fixtures/03-code-review-pr.data.json +0 -3
  30. package/plugins/html-effectiveness/tests/fixtures/04-code-understanding.data.json +0 -3
  31. package/plugins/html-effectiveness/tests/fixtures/05-design-system.data.json +0 -3
  32. package/plugins/html-effectiveness/tests/fixtures/06-component-variants.data.json +0 -3
  33. package/plugins/html-effectiveness/tests/fixtures/07-prototype-animation.data.json +0 -3
  34. package/plugins/html-effectiveness/tests/fixtures/08-prototype-interaction.data.json +0 -3
  35. package/plugins/html-effectiveness/tests/fixtures/09-slide-deck.data.json +0 -3
  36. package/plugins/html-effectiveness/tests/fixtures/10-svg-illustrations.data.json +0 -3
  37. package/plugins/html-effectiveness/tests/fixtures/11-status-report.data.json +0 -37
  38. package/plugins/html-effectiveness/tests/fixtures/12-incident-report.data.json +0 -3
  39. package/plugins/html-effectiveness/tests/fixtures/13-flowchart-diagram.data.json +0 -3
  40. package/plugins/html-effectiveness/tests/fixtures/14-research-feature-explainer.data.json +0 -3
  41. package/plugins/html-effectiveness/tests/fixtures/15-research-concept-explainer.data.json +0 -3
  42. package/plugins/html-effectiveness/tests/fixtures/16-implementation-plan.data.json +0 -3
  43. package/plugins/html-effectiveness/tests/fixtures/17-pr-writeup.data.json +0 -3
  44. package/plugins/html-effectiveness/tests/fixtures/18-editor-triage-board.data.json +0 -3
  45. package/plugins/html-effectiveness/tests/fixtures/19-editor-feature-flags.data.json +0 -3
  46. package/plugins/html-effectiveness/tests/fixtures/20-editor-prompt-tuner.data.json +0 -3
  47. package/plugins/html-effectiveness/tests/fixtures/_canned.data.json +0 -7
  48. package/plugins/html-effectiveness/tests/fixtures/_canned.html.tmpl +0 -7
  49. package/plugins/html-effectiveness/tests/fixtures/_canned.manifest.json +0 -18
  50. package/plugins/html-effectiveness/tests/manifest.test.js +0 -61
  51. package/plugins/html-effectiveness/tests/mustache.test.js +0 -47
  52. package/plugins/html-effectiveness/tests/render.test.js +0 -118
@@ -13,6 +13,21 @@ Owner's feature request:
13
13
 
14
14
  $ARGUMENTS
15
15
 
16
+ ## Parsing the launch flags
17
+
18
+ Before doing anything else, parse override flags from the owner's request. The flags are space-separated tokens, may appear before the feature description, and use `=` to bind their value:
19
+
20
+ - `--mode=<solo|single-agent|team>` — force the execution mode; skip the heuristic ladder.
21
+ - `--size=<minimal|standard|full>` — force the team size (only meaningful with `--mode=team`; ignored otherwise — log the override but proceed).
22
+ - `--explain` — run Phase 0.5 to compute the decision, print it to the owner, and STOP. Do not spawn anything.
23
+
24
+ If any of these flags appear, strip them from `$ARGUMENTS` and treat the remainder as the actual launch message. Record the flags used in the checkpoint field `overrides_applied:` (a list of strings, empty if none).
25
+
26
+ Examples:
27
+ - `/team-feature --mode=solo update copy in welcome banner` → mode=solo, launch_message="update copy in welcome banner".
28
+ - `/team-feature --explain redesign the checkout flow` → run heuristic, print decision, stop.
29
+ - `/team-feature --mode=team --size=full add /healthcheck` → mode=team, size=full, launch_message="add /healthcheck".
30
+
16
31
  ## Your job
17
32
 
18
33
  You are a **conductor**, not an implementer. Spawn teammates and coordinate them through the canonical Superpowers skill chain. Do not run skills yourself — delegate every skill to the correct teammate. The team-superpower agent definitions (`designer`, `planner`, `software-architect`, `security-engineer`, `backend-developer`, `frontend-developer`, `qa-engineer`, `reviewer`) shipped with this plugin tell each teammate exactly which Superpowers skill to run.
@@ -20,6 +35,17 @@ You are a **conductor**, not an implementer. Spawn teammates and coordinate them
20
35
  ## Required prechecks (run these first, in order)
21
36
 
22
37
  0. **Lead-model self-attestation.** Before doing anything else, state which model you (the lead) are currently running on. If you are not running on Opus, halt and instruct the owner: "Lead must be on Opus. Relaunch this session with `claude --model opus` (or pick Opus in the model switcher) and rerun `/team-feature`." Teammates are pinned to Sonnet via their agent frontmatter; only the lead model is set by the session.
38
+ 0b. **Teammate model attestation (v3).** When you spawn each teammate, capture the teammate's first heartbeat/checkpoint message and parse two self-report fields:
39
+ - `model_actual:` — the model the teammate is actually running on (per its `/model` output).
40
+ - `effort_set:` — the effort level it set on first turn.
41
+
42
+ Compare `model_actual` against the teammate's frontmatter `model:` pin (`opus` or `sonnet` alias). If they differ — usually because a usage-threshold fallback dropped Opus to Sonnet, or vice versa — log the mismatch to the checkpoint and surface a one-question **recovery touchpoint** to the owner:
43
+
44
+ > Teammate `<role>` is running `<actual>` instead of the pinned `<expected>` (likely a usage-threshold fallback). Continue or abort?
45
+
46
+ This recovery touchpoint is NOT counted against the 3-touchpoint budget — it only fires on fallback, which is rare. Owner answers `continue` (proceed) or `abort` (halt and re-launch when usage resets).
47
+
48
+ If `effort_set` is missing or differs from the recommended level for that role (per §11.1: designer/architect/security/reviewer/planner/qa = `high`; backend/frontend = `medium`), log a warning to the checkpoint but do NOT surface to owner — soft enforcement only.
23
49
  1. Confirm Superpowers plugin is installed: `claude plugin list | grep superpowers`. If missing, **halt** and instruct the owner: `/plugin install superpowers@claude-plugins-official`. Capture the version string from `claude plugin list --json` (e.g. `5.0.7`) — you'll write it to the checkpoint in phase 0 step 5 below.
24
50
  2. Confirm Claude Code version is `2.1.32` or later: `claude --version`. If older, halt.
25
51
  3. Confirm `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set in the environment. If not, halt and instruct the owner to add it to `~/.claude/settings.json` under `env`.
@@ -93,6 +119,20 @@ The `TaskCreated` hook reads this marker to enforce shape-appropriate `impl:` su
93
119
 
94
120
  Spawning happens at phase boundaries (you don't spawn implementers until phase 4 starts; you don't spawn the reviewer until phase 6) — this section just decides which teammates the team will EVER spawn for this feature. Record the list in the checkpoint.
95
121
 
122
+ ### 0.5b — Scaffold AGENTS.md (v4 §7)
123
+
124
+ Check for `docs/superpowers/AGENTS.md`. If it does NOT exist, create it from the asset template:
125
+
126
+ ```bash
127
+ if [ ! -f docs/superpowers/AGENTS.md ]; then
128
+ mkdir -p docs/superpowers
129
+ cp "${CLAUDE_PLUGIN_ROOT}/assets/AGENTS.md.template" docs/superpowers/AGENTS.md
130
+ git add docs/superpowers/AGENTS.md
131
+ fi
132
+ ```
133
+
134
+ If the file already exists, leave it untouched — `AGENTS.md` is owner-curated; you NEVER overwrite it. The template stub has empty sections that the reviewer suggests filling via `AGENTS.suggestions.md` at end of feature.
135
+
96
136
  ### 0.6 — Pin the Superpowers version
97
137
 
98
138
  Read the installed Superpowers version (from precheck step 1) and write it to the checkpoint frontmatter. This pins the skill-set for this feature. `/team-feature-resume` reads it back and refuses to continue if the installed version has drifted.
@@ -106,6 +146,94 @@ claude_code_version: <e.g. 2.1.32>
106
146
  stack_shape: full-stack | be-only | fe-only
107
147
  ```
108
148
 
149
+ ## Phase 0.5 — Complexity assessment (mode and size)
150
+
151
+ This phase runs after stack detection and before the initial checkpoint. It picks an execution mode (solo / single-agent / team) and, when applicable, a team size (minimal / standard / full). The decision is autonomous — the owner can override via `--mode` / `--size` but there is NO owner touchpoint here.
152
+
153
+ ### 0.5.1 — Determine mode and size
154
+
155
+ 1. If `--mode=` was supplied in the launch flags: use it directly. Skip step 2.
156
+ 2. Otherwise, run the heuristic ladder:
157
+ ```bash
158
+ bash ${CLAUDE_PLUGIN_ROOT}/scripts/assess-complexity.sh "$LAUNCH_MESSAGE" "$PWD"
159
+ ```
160
+ Capture stdout — it is YAML containing `mode:`, optionally `size:`, `shape:`, and `mode_reasoning:`. Exit 0 = confident; exit 1 = ambiguous (the script defaults to team and includes the ambiguity in `mode_reasoning`).
161
+ 3. If `--size=` was supplied AND the resolved mode is `team`, override the `size:` field with the flag value. If the resolved mode is `solo` or `single-agent`, log that `--size=` was ignored.
162
+
163
+ ### 0.5.2 — Handle `--explain`
164
+
165
+ If `--explain` was supplied, print the YAML decision to the owner with this header and STOP — do not write the marker file, do not write the checkpoint, do not spawn anything:
166
+
167
+ ```
168
+ Heuristic decision for your launch message:
169
+
170
+ <paste the YAML block from assess-complexity.sh>
171
+
172
+ Re-run without `--explain` to proceed, or supply `--mode=` / `--size=` to override.
173
+ ```
174
+
175
+ ### 0.5.3 — Write the mode marker
176
+
177
+ Otherwise, write the mode to a marker file the hooks can read:
178
+
179
+ ```bash
180
+ mkdir -p docs/superpowers/sessions
181
+ echo "$mode" > docs/superpowers/sessions/<slug>.mode
182
+ git add docs/superpowers/sessions/<slug>.mode
183
+ ```
184
+
185
+ The `TaskCreated` hook reads this marker to reject `impl:*` titles when `mode=solo`.
186
+
187
+ ### 0.5.4 — Write mode and size to the checkpoint frontmatter
188
+
189
+ Extend the checkpoint frontmatter with:
190
+
191
+ ```yaml
192
+ mode: solo | single-agent | team
193
+ size: minimal | standard | full # only when mode=team
194
+ mode_reasoning: |
195
+ <copy of mode_reasoning from assess-complexity.sh output, or "owner override via --mode=..." when flagged>
196
+ overrides_applied: [] # list of flag strings, e.g. ["--mode=team", "--size=full"]
197
+ ```
198
+
199
+ `mode_reasoning` is mandatory — it makes a wrong heuristic call debuggable later.
200
+
201
+ Phase 4 adds three more frontmatter fields (`wave`, `wave_replans`, `tasks_complete`) tracking wave progress; see the canonical checkpoint block under "## Checkpointing" below.
202
+
203
+ ## Mode-specific execution
204
+
205
+ The phase chain that follows depends on the mode:
206
+
207
+ ### Solo mode
208
+
209
+ - Do NOT spawn any teammates. Do NOT create a team. Skip `TeamCreate`.
210
+ - The lead does the work itself in its own session.
211
+ - Touchpoint 1 — **Plan-and-diff review**: write a one-paragraph description of the change + the proposed diff; ask the owner "Approve and apply?".
212
+ - On approval, apply the change.
213
+ - Touchpoint 2 — **Finish decision**: present the change as applied and ask the owner "Commit / discard?".
214
+ - On commit, write the commit and stop. Do NOT run a CI gate — solo changes are too small to justify it.
215
+ - The 3-touchpoint promise becomes 2 for solo mode (spec §4.6).
216
+
217
+ ### Single-agent mode
218
+
219
+ - Skip designer, planner, software-architect, security-engineer, qa-engineer, reviewer.
220
+ - Spawn exactly ONE implementer matching shape: `backend-developer` if `side: be-only`, `frontend-developer` if `side: fe-only`. (Use the `side_signal:` line from `assess-complexity.sh`'s `mode_reasoning` to decide.)
221
+ - Touchpoint 1 — **Inline spec sign-off**: lead writes a one-paragraph spec at `docs/superpowers/specs/<slug>.md` and asks the owner "Ok to proceed?".
222
+ - Touchpoint 2 — **Plan approval**: lead writes a one-task inline plan at `docs/superpowers/plans/<slug>.md` (the single task usually 5 lines: `Files`, `Depends on: []`, `Verification`, code outline) and asks the owner "Approve plan?".
223
+ - Dispatch the implementer with that single task. Wait for `done`.
224
+ - Lead reviews the implementer's diff itself in a single pass (no separate reviewer teammate).
225
+ - Touchpoint 3 — **Finish decision**: run `superpowers:finishing-a-development-branch` (CI gate per `CLAUDE.md`'s `ci` block).
226
+
227
+ ### Team mode
228
+
229
+ Run the full v2 phase chain with the chosen size (per the existing spawn table in Phase 0 section 0.5 of this file). Size determines whether `software-architect`, `security-engineer`, and `qa-engineer` are spawned:
230
+
231
+ - `minimal`: designer, planner, implementer(s), reviewer (no architect / security / QA).
232
+ - `standard`: + `qa-engineer`.
233
+ - `full`: + `software-architect` + `security-engineer`.
234
+
235
+ The existing v2 phase chain (design → plan → arch+sec → impl → QA → review → finish) runs unchanged. The shape-adaptive spawn from Phase 0 section 0.5 still applies on top of size.
236
+
109
237
  ## Preflight — detect stale or orphaned state
110
238
 
111
239
  Before writing any checkpoint or spawning any teammate, run the helper:
@@ -133,7 +261,7 @@ Same-session check: if the current Claude Code session already manages an agent
133
261
 
134
262
  ## Initial checkpoint and heartbeat
135
263
 
136
- After preflight clears AND phase 0 has decided the shape:
264
+ After preflight clears AND phase 0 has decided the shape AND phase 0.5 has decided the mode/size:
137
265
 
138
266
  1. Write the initial checkpoint `docs/superpowers/sessions/YYYY-MM-DD-<slug>.md` per the format in the **Checkpointing** section — including the v2 frontmatter fields (`superpowers_version`, `plugin_version`, `claude_code_version`, `stack_shape`) — and commit it.
139
267
  2. `touch docs/superpowers/sessions/<slug>.heartbeat` and commit (or leave uncommitted — the file is intentionally ephemeral; either is fine). **Touch this heartbeat at every phase boundary** and any time you remain active for more than ~10 minutes inside a phase. The cleanup script uses its mtime to decide whether a future session is allowed to wipe state.
@@ -229,6 +357,15 @@ Mailbox signal expected back: <e.g. DESIGN_APPROVED <path>, PLAN_READY <path>, A
229
357
 
230
358
  Fill every field. If a field is genuinely N/A for a role (e.g. there is no QA report when spawning the designer), write `n/a` rather than omitting the line — the template's stability is what keeps respawns deterministic.
231
359
 
360
+ **Heartbeat self-reports (v3).** Every teammate's first checkpoint message back to the lead MUST include these self-report fields so preflight model attestation works:
361
+
362
+ ```
363
+ effort_set: <level the teammate set with /effort>
364
+ model_actual: <model from /model output>
365
+ ```
366
+
367
+ If a teammate omits these, the lead logs `MISSING_MODEL_ATTESTATION` to the checkpoint and asks the teammate once to add them. Persistent omission is logged but not blocked — soft enforcement.
368
+
232
369
  ## Phase chain (strict order — no skipping, no inlining)
233
370
 
234
371
  1. **Design (designer).** Spawn the `designer` teammate. Hand it `<slug>` and the owner's request. Wait for `DESIGN_APPROVED <path>` in your mailbox. If the designer asks a clarifying question, answer from project context if unambiguous; otherwise batch with any open questions and use the §7 escalation template to the owner. Checkpoint: `phase: design, status: complete`. Touch heartbeat.
@@ -237,22 +374,40 @@ Fill every field. If a field is genuinely N/A for a role (e.g. there is no QA re
237
374
 
238
375
  3. **Pre-impl review gate (software-architect + security-engineer, parallel).** Spawn both. Hand each the design doc path AND the plan path. Wait for `ARCH_PASSED <path>` AND `SEC_PASSED <path>`. If either posts `ARCH_BLOCKED` / `SEC_BLOCKED`, route the findings to `planner` for a plan revision, then re-route to whichever gate is still blocking. Cap at three plan-revision rounds — escalate to owner via §7 if it does not converge. Checkpoint: `phase: pre_impl_review, status: passed | blocked`. Touch heartbeat.
239
376
 
240
- 4. **Implementation (shape-adaptive, parallel where allowed).** Read the approved plan. Create one shared-task-list entry per plan task with the planner's assigned title (`impl:be-*`, `impl:fe-*`, `impl:be-migration-*`, `impl:be-contract-publish-*`), body = full task text including verification, and `depends_on` + `files` + `tests` + `estimated_minutes` + `plan_approved_at` metadata from the plan.
377
+ 4. **Implementation — Phase 4 wave dispatcher (wave-based, shape-adaptive).** Read the approved plan's `## Waves` section. For each wave N in order:
378
+
379
+ **4.1 Collision check.** Build a wave manifest by concatenating each task's `id` + space-separated `files:` metadata, one task per line. Pipe it to `bash ${CLAUDE_PLUGIN_ROOT}/scripts/wave-collision-check.sh`. Exit 0 → proceed. Exit 1 → halt the wave, do NOT dispatch any task in it, post `WAVE_COLLISION wave=N tasks=[…] shared_files=[…]` to the planner's mailbox (verbatim from the helper output), and wait for a fresh `PLAN_READY <path>`. Re-read the plan, re-build the manifest, re-run the collision check. Cap the loop at **3 re-plan retries on the same wave** (`wave_replans: K/3` in the checkpoint). On the 4th attempt, escalate to owner via §7 template — planner cannot converge on this dependency graph.
380
+
381
+ **4.2 Create task entries.** For every task in this wave, create one shared-task-list entry with title from the plan (`impl:be-*`, `impl:fe-*`, `impl:be-migration-*`, `impl:be-contract-publish-*`, `impl:contract-update-*`). Set metadata: `wave: N`, `depends_on: [...]`, `files: [...]`, `tests: [...]`, `estimated_minutes`, `plan_approved_at`, `iteration_count: 0`. The `TaskCreated` hook will warn on missing wave.
382
+
383
+ **4.3 Spawn counts.**
384
+ - `be_count = count(impl:be-* tasks in wave N)`; `fe_count = count(impl:fe-* tasks in wave N)`.
385
+ - Live BE instances target = `min(be_count, 2)`; FE target = `min(fe_count, 2)`.
386
+ - If a live instance is below target, spawn additional implementer(s) for that side using the canonical spawn-prompt template (§Spawn prompt template). Reuse already-spawned implementers across waves — do NOT respawn.
387
+ - If a live instance is above target (previous wave had more tasks than this one), let it idle. Idle instances do NOT trigger `TeammateIdle` because that hook checks unanswered peer mail, not work activity.
388
+
389
+ **4.4 Task claim.** Each implementer self-claims one task from the wave queue matching its side prefix. The lead does NOT assign tasks explicitly — implementers pull from the queue. If a side has more tasks than instances, the extras get claimed serially by whichever instance frees up first.
241
390
 
242
- **Spawn rule (shape-adaptive):**
243
- - `full-stack`: spawn one `backend-developer` AND one `frontend-developer`.
244
- - `be-only`: spawn one `backend-developer` only. Do NOT spawn `frontend-developer`.
245
- - `fe-only`: spawn one `frontend-developer` only. Do NOT spawn `backend-developer`.
391
+ **4.5 Contract gate.** If the wave contains `impl:be-contract-publish-<slug>`, do NOT release any `impl:fe-*` queue items until `CONTRACT_PUBLISHED <task-id>` arrives, even if the FE tasks technically live in a later wave. Plan dependencies already enforce this; the gate is a backstop.
246
392
 
247
- **Contract publish (full-stack only).** If the planner emitted `impl:be-contract-publish-<slug>` as the first task, the backend-developer claims it first. Do NOT release any `impl:fe-*` task to the frontend-developer until you see `CONTRACT_PUBLISHED <task-id>` in your mailbox. The plan tasks already encode `depends_on: [impl:be-contract-publish-<slug>]` on every FE task, but you enforce the gate at the assignment level too.
393
+ **4.6 Migration serialization.** `impl:be-migration-*` tasks must occupy a wave alone on the BE side. The planner enforces upfront via `Depends on:`; the `TaskCompleted` hook is a final backstop with `MIGRATION_RACE`.
248
394
 
249
- **Mid-implementation contract drift.** If you receive `CONTRACT_DRIFT_DETECTED` from frontend-developer, or backend-developer files an `impl:contract-update-*` task on its own, pause all `impl:fe-*` in-flight work (post a "pause" message to frontend-developer's mailbox; it will idle on its current task). Wait for `CONTRACT_UPDATED <task-id>` from backend-developer, then unpause FE. Frontend-developer re-pulls the contract hash on resume.
395
+ **4.7 Mid-wave collision.** If an implementer posts `WAVE_COLLISION` mid-wave (an undeclared overlap surfaced during work), halt the wave: keep in-flight tasks running to completion, do not claim any further task, route the collision to planner as in 4.1. Same 3-retry cap.
250
396
 
251
- **Migration serialization.** `impl:be-migration-*` tasks must run one at a time. The planner chains them via `depends_on`, the `TaskCompleted` hook is a backstop with `MIGRATION_RACE`, and you enforce it at assignment: do not release a second migration task while one is `in_progress`.
397
+ **4.8 Mid-implementation contract drift.** If `CONTRACT_DRIFT_DETECTED` arrives from frontend-developer, or backend-developer files `impl:contract-update-*` on its own, pause all `impl:fe-*` claims until `CONTRACT_UPDATED <task-id>` arrives. Frontend-developer re-pulls the contract hash on resume.
252
398
 
253
- **File-scope conflict check.** Verify no two active implementer tasks overlap in file scope — if a conflict appears, serialize by holding the second task. Watch for `BE_DONE` / `FE_DONE`.
399
+ **4.9 Wave completion.** A wave completes when:
400
+ - every task in the wave has status `done`,
401
+ - every task's `TaskCompleted` hook returned 0 (verified via the JSONL log),
402
+ - no implementer holds unanswered peer mail relevant to this wave.
254
403
 
255
- Checkpoint after each task transition: `phase: implementation, tasks_complete: M/N`. Touch heartbeat at every transition.
404
+ On completion, checkpoint: `phase: implementation, wave: N/M, tasks_complete: X/Y` and advance to wave N+1.
405
+
406
+ **4.10 Task failure inside a wave.** If any task fails (`iteration_count` exceeded with no reflection, two-stage review rejects, test never goes green), halt the wave at that task. Other in-flight tasks finish; no new claims until the failure resolves via the four-class clarification routing. On resolution: resume the wave from where it stopped — do NOT restart.
407
+
408
+ **4.11 Idle implementer cleanup.** Between waves, if any implementer instance has been idle for the entire previous wave AND no upcoming wave will use it, the lead MAY shut it down to free context. Fresh implementers spawn for later waves on demand. (Optional; harmless to leave idle implementers alive.)
409
+
410
+ Checkpoint after each wave: `phase: implementation, wave: N/M, tasks_complete: X/Y`. Touch heartbeat. Do not advance to phase 5 until wave M/M completes.
256
411
 
257
412
  5. **QA gate (qa-engineer).** Once every `impl:` task is complete, spawn `qa-engineer`. Wait for `QA_PASSED <path>` or `QA_BLOCKED <path>`. If blocked, the QA report contains `impl:qa-fix-be-` / `impl:qa-fix-fe-` tasks — file them in the shared task list and loop to phase 4. Checkpoint: `phase: qa, status: passed | blocked`. Touch heartbeat.
258
413
 
@@ -469,6 +624,49 @@ The watchdog is **not** an owner touchpoint by itself — pinging the teammate i
469
624
 
470
625
  Reset the watchdog on every received mailbox message and every task transition. Touch the heartbeat each time you reset.
471
626
 
627
+ ## v4 mailbox handlers (per-task QA, token budget, retrieval)
628
+
629
+ These handlers run inside your normal mailbox-processing loop. They are non-touchpoint by construction — handle inline, never page the owner.
630
+
631
+ ### Handler: `BUDGET_85_REACHED <task-id> tokens=<used>/<cap> current_state=<...> blocker=<...>`
632
+
633
+ Decision matrix:
634
+
635
+ - If `current_state` is `QA-loop round=2`, `QA-loop round=3`, or `REFACTOR` → respond `BUDGET_EXTEND <task-id> additional=50000` (close to completion, worth extending).
636
+ - If `blocker` mentions "task scope larger than estimated" / "scope" / "decomposition" → respond `BUDGET_ABORT <task-id>` and post `TASK_OVERSCOPED <task-id>` to the planner for re-decomposition.
637
+ - If `current_state` is `RED` or `GREEN` and `tokens > 200000` (mostly exploration with no clear completion path) → respond `BUDGET_REASSIGN <task-id>` (kill, unclaim, let a fresh implementer try).
638
+ - Otherwise respond `BUDGET_EXTEND <task-id> additional=50000` and log a warning to the checkpoint for retrospective tuning.
639
+
640
+ Increment the task's `task_token_budget` metadata by `additional` on EXTEND so subsequent 85% checks use the new ceiling.
641
+
642
+ ### Handler: `RETRIEVAL_REQUEST <task-id> cycle=<N> need=<...> because=<...>`
643
+
644
+ Steps:
645
+
646
+ 1. Validate `because` clause is specific. Reject vague phrasings (matches: "might need", "more context", "to be safe", "in case", "not sure if"). If vague → respond `RETRIEVAL_DENIED <task-id> reason="be specific — what exactly and why?"`. Vague rejections DO NOT count against the budget.
647
+ 2. Validate `cycle ≤ 2`. If exceeded → respond `RETRIEVAL_DENIED <task-id> reason="budget exhausted, produce best-effort with Flagged-assumptions:"`.
648
+ 3. Locate the requested files / symbols / ADR contents. Read them with `Read` tool.
649
+ 4. Respond `RETRIEVAL_RESPONSE <task-id> cycle=<N> content=<inline file contents>`.
650
+ 5. Increment the task's `retrieval_requests` metadata counter (this is the value the hook checks against the cap).
651
+
652
+ ### Handler: AGENTS.md suggestions (post-`REVIEW_PASSED`)
653
+
654
+ After `reviewer` posts `REVIEW_PASSED`, check `docs/superpowers/AGENTS.suggestions.md`:
655
+
656
+ 1. Count the `## Candidate <N>` entries (if any).
657
+ 2. Read the `## Stale entries to remove` section; count its non-empty bullets.
658
+ 3. When you compose the owner's finish notification (in phase 7 after `FINISH_DONE`), include:
659
+ ```
660
+ 📝 Reviewer suggested <N> lessons for AGENTS.md<and flagged <M> stale entry|stale entries> — see docs/superpowers/AGENTS.suggestions.md
661
+ ```
662
+ Show the line only when N > 0 or M > 0. Omit entirely otherwise.
663
+
664
+ Never auto-promote a candidate to `AGENTS.md` — the owner is the only role that may promote entries. The hook backstops this (`AGENT_WROTE_AGENTS_MD`).
665
+
666
+ ### Handler: `VERIFY_REQUEST <task-id> round=<N>` (lead is a passthrough)
667
+
668
+ You do NOT process `VERIFY_REQUEST` messages yourself — they are addressed to `qa-engineer`. If a message accidentally lands in your queue, forward it to `qa-engineer` and log a routing warning. Only QA responses (`QA_PASS` / `QA_ISSUES`) flow back to implementers without your involvement; you track `qa_rounds` from the §7 escalations only.
669
+
472
670
  ## Owner touchpoints (the ONLY allowed pings to the owner)
473
671
 
474
672
  1. Design sign-off (phase 1, the brainstorming skill's built-in step).
@@ -489,6 +687,14 @@ superpowers_version: <e.g. 5.0.7>
489
687
  plugin_version: <team-superpower plugin version>
490
688
  claude_code_version: <e.g. 2.1.32>
491
689
  stack_shape: full-stack | be-only | fe-only
690
+ mode: solo | single-agent | team # v3, written in phase 0.5
691
+ size: minimal | standard | full # v3, only when mode=team
692
+ mode_reasoning: | # v3, populated by scripts/assess-complexity.sh or "owner override via ..."
693
+ <multi-line reasoning trace>
694
+ overrides_applied: [] # v3, list of flag strings, e.g. ["--mode=team", "--size=full"]
695
+ wave: 0/0 # v3, current wave / total waves during phase 4 (set when phase 4 starts)
696
+ wave_replans: 0/3 # v3, collision-driven re-plans for the current wave; cap 3 before owner escalation
697
+ tasks_complete: 0/0 # v3, tasks complete in current wave / wave size
492
698
  ---
493
699
 
494
700
  # Session: <slug>
@@ -53,6 +53,209 @@ case "$title" in
53
53
  ;;
54
54
  esac
55
55
 
56
+ # v3: wave metadata. impl:* tasks must carry a `wave:` integer (planner sets
57
+ # it; the lead mirrors it into shared-task-list metadata at dispatch time).
58
+ case "$title" in
59
+ impl:*)
60
+ wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
61
+ if [ -z "$wave" ] || ! printf '%s' "$wave" | grep -qE '^[0-9]+$'; then
62
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_WAVE_METADATA","title":%s}\n' \
63
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
64
+ fi
65
+ ;;
66
+ esac
67
+
68
+ # v3: MAX_ITERATIONS guardrail. impl:* completions whose iteration_count
69
+ # exceeds the per-project cap (default 8) must carry a reflection: block.
70
+ case "$title" in
71
+ impl:*)
72
+ iteration_count="$(printf '%s' "$payload" | jq -r '.task.metadata.iteration_count // .metadata.iteration_count // 0' 2>/dev/null || echo 0)"
73
+ reflection="$(printf '%s' "$payload" | jq -r '.task.metadata.reflection // .metadata.reflection // ""' 2>/dev/null || echo "")"
74
+
75
+ cap=8
76
+ parse_helper="${CLAUDE_PLUGIN_ROOT:-}/scripts/parse-claudemd.sh"
77
+ if [ -z "${CLAUDE_PLUGIN_ROOT:-}" ]; then
78
+ # Fall back to a relative path the hook can find when CLAUDE_PLUGIN_ROOT is unset.
79
+ hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
80
+ parse_helper="$hook_dir/../scripts/parse-claudemd.sh"
81
+ fi
82
+ if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
83
+ configured="$(bash "$parse_helper" get limits.max_iterations_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
84
+ if [ -n "$configured" ] && printf '%s' "$configured" | grep -qE '^[0-9]+$'; then
85
+ cap="$configured"
86
+ fi
87
+ fi
88
+
89
+ if [ "${iteration_count:-0}" -gt "$cap" ] && [ -z "$reflection" ]; then
90
+ printf '{"ts":"%s","hook":"task-completed","warn":"ITERATION_CAP_EXCEEDED","title":%s,"iteration_count":%d,"cap":%d}\n' \
91
+ "$ts" \
92
+ "$(printf '%s' "$title" | jq -Rs .)" \
93
+ "$iteration_count" \
94
+ "$cap" \
95
+ >> "$LOG_FILE"
96
+ fi
97
+ ;;
98
+ esac
99
+
100
+ # v4: per-task QA verification. impl:* completions must carry
101
+ # `qa_verified_at:` metadata AND a `QA-verified: round=N` line in at least one
102
+ # commit message body (N ≤ max_qa_rounds_per_task, default 3). The hook also
103
+ # rejects `trivial=true` claims for diffs >20 lines or new-file additions, and
104
+ # rejects `qa_rounds: 3` escalations missing the cross-role fields.
105
+ case "$title" in
106
+ impl:*)
107
+ qa_verified_at="$(printf '%s' "$payload" | jq -r '.task.metadata.qa_verified_at // .metadata.qa_verified_at // ""' 2>/dev/null || echo "")"
108
+ qa_rounds="$(printf '%s' "$payload" | jq -r '.task.metadata.qa_rounds // .metadata.qa_rounds // 0' 2>/dev/null || echo 0)"
109
+ trivial_claim="$(printf '%s' "$payload" | jq -r '.task.metadata.trivial // .metadata.trivial // false' 2>/dev/null || echo false)"
110
+
111
+ qa_cap=3
112
+ if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
113
+ configured_qa="$(bash "$parse_helper" get limits.max_qa_rounds_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
114
+ if [ -n "$configured_qa" ] && printf '%s' "$configured_qa" | grep -qE '^[0-9]+$'; then
115
+ qa_cap="$configured_qa"
116
+ fi
117
+ fi
118
+
119
+ # Check 7a: qa_verified_at metadata present.
120
+ if [ -z "$qa_verified_at" ]; then
121
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_QA_VERIFICATION","title":%s,"reason":"qa_verified_at metadata missing"}\n' \
122
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
123
+ fi
124
+
125
+ # Check 7b: QA-verified: round=N commit body line, verified via git log if commits recorded.
126
+ if command -v git >/dev/null 2>&1; then
127
+ qa_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
128
+ if [ -n "$qa_commits" ]; then
129
+ qa_line_found=0
130
+ qa_round_observed=0
131
+ while IFS= read -r sha; do
132
+ [ -z "$sha" ] && continue
133
+ body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
134
+ line="$(printf '%s\n' "$body" | grep -E '^QA-verified:[[:space:]]*round=[0-9]+' | head -n1 || true)"
135
+ if [ -n "$line" ]; then
136
+ qa_line_found=1
137
+ n="$(printf '%s' "$line" | sed -nE 's/^QA-verified:[[:space:]]*round=([0-9]+).*/\1/p')"
138
+ if [ -n "$n" ] && [ "$n" -gt "$qa_round_observed" ]; then
139
+ qa_round_observed="$n"
140
+ fi
141
+ fi
142
+ done <<< "$qa_commits"
143
+ if [ "$qa_line_found" -ne 1 ]; then
144
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_QA_VERIFICATION","title":%s,"reason":"no QA-verified line in commit body"}\n' \
145
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
146
+ elif [ "$qa_round_observed" -gt "$qa_cap" ]; then
147
+ printf '{"ts":"%s","hook":"task-completed","warn":"QA_ROUND_OVER_CAP","title":%s,"round":%d,"cap":%d}\n' \
148
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$qa_round_observed" "$qa_cap" >> "$LOG_FILE"
149
+ fi
150
+ fi
151
+ fi
152
+
153
+ # Check 8: qa_rounds at cap requires §7 cross-role escalation.
154
+ if [ "${qa_rounds:-0}" -ge "$qa_cap" ] && [ -z "$qa_verified_at" ]; then
155
+ # An escalation entry tagged class:cross-role with qa_rounds, what_failed, one_change_to_fix must be present.
156
+ escalation_ok=0
157
+ escalation_entries="$(printf '%s' "$payload" | jq -c '(.task.metadata.blocked_questions // .metadata.blocked_questions // [])[]?' 2>/dev/null || true)"
158
+ if [ -n "$escalation_entries" ]; then
159
+ while IFS= read -r entry; do
160
+ [ -z "$entry" ] && continue
161
+ raw="$(printf '%s' "$entry" | jq -r '.' 2>/dev/null || printf '%s' "$entry")"
162
+ if printf '%s' "$raw" | grep -qE 'class[[:space:]]*:[[:space:]]*cross-role' \
163
+ && printf '%s' "$raw" | grep -qE 'qa_rounds[[:space:]]*:' \
164
+ && printf '%s' "$raw" | grep -qE 'what_failed[[:space:]]*:' \
165
+ && printf '%s' "$raw" | grep -qE 'one_change_to_fix[[:space:]]*:'; then
166
+ escalation_ok=1
167
+ break
168
+ fi
169
+ done <<< "$escalation_entries"
170
+ fi
171
+ if [ "$escalation_ok" -ne 1 ]; then
172
+ printf '{"ts":"%s","hook":"task-completed","warn":"QA_CAP_EXCEEDED","title":%s,"qa_rounds":%d,"cap":%d}\n' \
173
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$qa_rounds" "$qa_cap" >> "$LOG_FILE"
174
+ fi
175
+ fi
176
+
177
+ # Pre-check: INVALID_TRIVIAL_CLAIM — trivial=true requires diff ≤20 lines AND no new files.
178
+ if [ "$trivial_claim" = "true" ] && command -v git >/dev/null 2>&1; then
179
+ tc_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
180
+ tc_added=0
181
+ tc_lines=0
182
+ if [ -n "$tc_commits" ]; then
183
+ while IFS= read -r sha; do
184
+ [ -z "$sha" ] && continue
185
+ st="$(git show --no-color --name-status --pretty=format: "$sha" 2>/dev/null || true)"
186
+ if printf '%s\n' "$st" | grep -qE '^A[[:space:]]'; then
187
+ tc_added=1
188
+ fi
189
+ n="$(git show --no-color --shortstat --pretty=format: "$sha" 2>/dev/null | grep -oE '[0-9]+ insertion' | head -n1 | grep -oE '[0-9]+' || echo 0)"
190
+ tc_lines=$((tc_lines + ${n:-0}))
191
+ done <<< "$tc_commits"
192
+ if [ "$tc_added" -eq 1 ] || [ "$tc_lines" -gt 20 ]; then
193
+ printf '{"ts":"%s","hook":"task-completed","warn":"INVALID_TRIVIAL_CLAIM","title":%s,"added_files":%d,"insertions":%d}\n' \
194
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$tc_added" "$tc_lines" >> "$LOG_FILE"
195
+ fi
196
+ fi
197
+ fi
198
+ ;;
199
+ esac
200
+
201
+ # v4: retrieval budget. impl:* tasks may not exceed `retrieval_budget_per_task`
202
+ # (default 2). Also: a `Flagged-assumptions:` line in a commit body is only
203
+ # permitted when retrieval_requests == budget (cannot flag assumptions
204
+ # prematurely without exhausting the budget).
205
+ case "$title" in
206
+ impl:*)
207
+ retrieval_requests="$(printf '%s' "$payload" | jq -r '.task.metadata.retrieval_requests // .metadata.retrieval_requests // 0' 2>/dev/null || echo 0)"
208
+ retrieval_cap=2
209
+ if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
210
+ configured_r="$(bash "$parse_helper" get limits.retrieval_budget_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
211
+ if [ -n "$configured_r" ] && printf '%s' "$configured_r" | grep -qE '^[0-9]+$'; then
212
+ retrieval_cap="$configured_r"
213
+ fi
214
+ fi
215
+
216
+ if [ "${retrieval_requests:-0}" -gt "$retrieval_cap" ]; then
217
+ printf '{"ts":"%s","hook":"task-completed","warn":"RETRIEVAL_BUDGET_EXCEEDED","title":%s,"requests":%d,"cap":%d}\n' \
218
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
219
+ fi
220
+
221
+ if command -v git >/dev/null 2>&1; then
222
+ r_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
223
+ if [ -n "$r_commits" ]; then
224
+ flagged=0
225
+ while IFS= read -r sha; do
226
+ [ -z "$sha" ] && continue
227
+ body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
228
+ if printf '%s\n' "$body" | grep -qE '^Flagged-assumptions:'; then
229
+ flagged=1; break
230
+ fi
231
+ done <<< "$r_commits"
232
+ if [ "$flagged" -eq 1 ] && [ "${retrieval_requests:-0}" -lt "$retrieval_cap" ]; then
233
+ printf '{"ts":"%s","hook":"task-completed","warn":"PREMATURE_ASSUMPTION_FLAG","title":%s,"requests":%d,"cap":%d}\n' \
234
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
235
+ fi
236
+ fi
237
+ fi
238
+ ;;
239
+ esac
240
+
241
+ # v4: AGENTS.md protection. Agents never write to docs/superpowers/AGENTS.md.
242
+ # Reviewer writes only to docs/superpowers/AGENTS.suggestions.md. If any commit
243
+ # on this task touches AGENTS.md, warn AGENT_WROTE_AGENTS_MD — the owner is the
244
+ # only role that may promote suggestions to AGENTS.md.
245
+ if command -v git >/dev/null 2>&1; then
246
+ agm_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
247
+ if [ -n "$agm_commits" ]; then
248
+ while IFS= read -r sha; do
249
+ [ -z "$sha" ] && continue
250
+ files="$(git show --no-color --name-only --pretty=format: "$sha" 2>/dev/null || true)"
251
+ if printf '%s\n' "$files" | grep -qE '^docs/superpowers/AGENTS\.md$'; then
252
+ printf '{"ts":"%s","hook":"task-completed","warn":"AGENT_WROTE_AGENTS_MD","title":%s,"sha":%s}\n' \
253
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$(printf '%s' "$sha" | jq -Rs .)" >> "$LOG_FILE"
254
+ fi
255
+ done <<< "$agm_commits"
256
+ fi
257
+ fi
258
+
56
259
  # v2: migration serialization. If this is a migration task, verify no other
57
260
  # `impl:be-migration-*` task is currently in_progress in the shared task list
58
261
  # (payload.tasks[]). The lead also enforces this; the hook is a backstop.
@@ -59,10 +59,17 @@ if [ -d "$SESSIONS_DIR" ]; then
59
59
  fi
60
60
  fi
61
61
 
62
- printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s}\n' \
62
+ wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
63
+ wave_json="null"
64
+ if printf '%s' "$wave" | grep -qE '^[0-9]+$'; then
65
+ wave_json="$wave"
66
+ fi
67
+
68
+ printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s,"wave":%s}\n' \
63
69
  "$ts" \
64
70
  "$(printf '%s' "$title" | jq -Rs .)" \
65
71
  "$(printf '%s' "$shape" | jq -Rs .)" \
72
+ "$wave_json" \
66
73
  >> "$LOG_FILE"
67
74
 
68
75
  # Top-level prefix check
@@ -77,6 +84,40 @@ case "$title" in
77
84
  ;;
78
85
  esac
79
86
 
87
+ # v3: solo-mode guard. If checkpoint mode is solo, impl:* tasks are invalid
88
+ # (solo means the lead does the work itself; no implementer is spawned).
89
+ mode_meta="$(printf '%s' "$payload" | jq -r '.task.metadata.mode // .metadata.mode // ""' 2>/dev/null || echo "")"
90
+ mode_marker=""
91
+ if [ -z "$mode_meta" ] && [ -d "$SESSIONS_DIR" ]; then
92
+ # Look for a <slug>.mode file the lead writes when it picks a mode.
93
+ marker_file=""
94
+ if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.mode" ]; then
95
+ marker_file="$SESSIONS_DIR/$slug.mode"
96
+ else
97
+ count="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | wc -l | tr -d ' ')"
98
+ if [ "$count" = "1" ]; then
99
+ marker_file="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | head -n1)"
100
+ fi
101
+ fi
102
+ if [ -n "$marker_file" ] && [ -f "$marker_file" ]; then
103
+ mode_marker="$(head -n1 "$marker_file" | tr -d '[:space:]')"
104
+ fi
105
+ fi
106
+ effective_mode="${mode_meta:-$mode_marker}"
107
+
108
+ case "$title" in
109
+ impl:*)
110
+ if [ "$effective_mode" = "solo" ]; then
111
+ printf '{"ts":"%s","hook":"task-created","warn":"INVALID_FOR_SOLO_MODE","title":%s}\n' \
112
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
113
+ fi
114
+ if [ -z "$wave" ]; then
115
+ printf '{"ts":"%s","hook":"task-created","warn":"MISSING_WAVE_METADATA","title":%s}\n' \
116
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
117
+ fi
118
+ ;;
119
+ esac
120
+
80
121
  # At this point title starts with `impl:`. Strip prefix and require a known
81
122
  # sub-prefix.
82
123
  rest="${title#impl:}"