@ikon85/agent-workflow-kit 0.27.1 → 0.29.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 (79) hide show
  1. package/.agents/skills/ask-matt/SKILL.md +2 -2
  2. package/.agents/skills/board-to-waves/SKILL.md +13 -3
  3. package/.agents/skills/grill-me/SKILL.md +1 -1
  4. package/.agents/skills/grill-with-docs/SKILL.md +1 -1
  5. package/.agents/skills/kit-update/SKILL.md +12 -0
  6. package/.agents/skills/orchestrate-wave/SKILL.md +68 -21
  7. package/.agents/skills/orchestrate-wave/references/builder-contract.md +6 -4
  8. package/.agents/skills/retro/SKILL.md +24 -1
  9. package/.agents/skills/scale-check/SKILL.md +2 -2
  10. package/.agents/skills/setup-workflow/SKILL.md +44 -1
  11. package/.agents/skills/setup-workflow/workflow-overview.md +5 -5
  12. package/.agents/skills/tdd/SKILL.md +68 -14
  13. package/.agents/skills/to-issues/SKILL.md +104 -29
  14. package/.agents/skills/to-prd/SKILL.md +13 -3
  15. package/.agents/skills/to-waves/SKILL.md +16 -4
  16. package/.agents/skills/verify-spike/SKILL.md +1 -1
  17. package/.agents/skills/wrapup/SKILL.md +14 -3
  18. package/.claude/hooks/drift-guard.py +5 -2
  19. package/.claude/hooks/kit-origin-edit-hint.py +64 -0
  20. package/.claude/skills/ask-matt/SKILL.md +2 -2
  21. package/.claude/skills/board-to-waves/SKILL.md +13 -3
  22. package/.claude/skills/codex-build/SKILL.md +69 -23
  23. package/.claude/skills/codex-review/SKILL.md +47 -37
  24. package/.claude/skills/grill-me/SKILL.md +1 -1
  25. package/.claude/skills/grill-me-codex/SKILL.md +45 -34
  26. package/.claude/skills/grill-with-docs/SKILL.md +1 -1
  27. package/.claude/skills/grill-with-docs-codex/SKILL.md +46 -34
  28. package/.claude/skills/kit-update/SKILL.md +12 -0
  29. package/.claude/skills/orchestrate-wave/SKILL.md +68 -21
  30. package/.claude/skills/orchestrate-wave/references/builder-contract.md +6 -4
  31. package/.claude/skills/retro/SKILL.md +23 -0
  32. package/.claude/skills/scale-check/SKILL.md +2 -2
  33. package/.claude/skills/setup-workflow/SKILL.md +44 -1
  34. package/.claude/skills/setup-workflow/workflow-overview.md +5 -5
  35. package/.claude/skills/skill-manifest.json +1 -1
  36. package/.claude/skills/tdd/SKILL.md +68 -14
  37. package/.claude/skills/to-issues/SKILL.md +104 -29
  38. package/.claude/skills/to-prd/SKILL.md +13 -3
  39. package/.claude/skills/to-waves/SKILL.md +16 -4
  40. package/.claude/skills/verify-spike/SKILL.md +1 -1
  41. package/.claude/skills/wrapup/SKILL.md +14 -3
  42. package/README.md +70 -7
  43. package/agent-workflow-kit.package.json +99 -43
  44. package/docs/adr/0001-consumer-divergence-policy.md +49 -0
  45. package/docs/agents/wave-anchor-template.md +1 -1
  46. package/docs/research/wave-152-consumer-acceptance.md +98 -0
  47. package/package.json +1 -1
  48. package/scripts/board-sync.py +187 -12
  49. package/scripts/build-kit.test.mjs +42 -1
  50. package/scripts/codex-exec-scenarios/fake-codex.mjs +152 -0
  51. package/scripts/codex-exec.sh +566 -0
  52. package/scripts/codex-exec.test.mjs +815 -0
  53. package/scripts/codex_proc.py +602 -0
  54. package/scripts/find-by-marker.py +68 -0
  55. package/scripts/marker_lib.py +88 -0
  56. package/scripts/pr-body-check.py +34 -6
  57. package/scripts/pr_body_e2e.py +83 -0
  58. package/scripts/render-anchor.py +111 -0
  59. package/scripts/test_board_sync.py +208 -0
  60. package/scripts/test_census_backstop.py +56 -3
  61. package/scripts/test_marker_lib.py +154 -0
  62. package/scripts/test_orchestrate_wave_contract.py +116 -0
  63. package/scripts/test_pr_body_check.py +245 -0
  64. package/scripts/test_render_anchor.py +267 -0
  65. package/scripts/test_retro_wrapup_contract.py +117 -0
  66. package/scripts/test_skill_codex_exec_lifecycle.py +123 -0
  67. package/scripts/test_tdd_contract.py +78 -0
  68. package/src/cli.mjs +44 -8
  69. package/src/commands/diff.mjs +5 -2
  70. package/src/commands/init.mjs +12 -0
  71. package/src/commands/own.mjs +16 -0
  72. package/src/commands/uninstall.mjs +8 -1
  73. package/src/commands/update.mjs +37 -9
  74. package/src/lib/bundle.mjs +14 -0
  75. package/src/lib/consumerPath.mjs +30 -0
  76. package/src/lib/manifest.mjs +18 -0
  77. package/src/lib/ownedDiff.mjs +88 -0
  78. package/src/lib/updateCandidate.mjs +14 -0
  79. package/src/lib/updateReconcile.mjs +45 -6
@@ -54,7 +54,7 @@ Even when the slices were already cut upstream (a grill/PRD slice table), do NOT
54
54
  - **Seam ownership check (Fix A):** does any slice **replace/unify/retire a central mechanism**? If yes, it MUST be its own 🧊 grill-needed slice — NOT hidden in a behavior-preserving naming/tweak leaf. A byte-neutral slice does not discharge the seam.
55
55
  - **Blast-radius threshold:** for each slice, estimate the blast radius (~N files, from recon/grep — not a guess; workflow slices count SKILL.md + adapter mirrors + tests). **≥ 10 estimated files OR not estimable → check for a split.** If the slice stays deliberately large, it MUST be 🧊 **grill-needed** (HITL) with a "why indivisible" justification **in the issue body** — a guideline, not a hard block, but the deviation lives in the body, not in the agent's head. (Incident: a slice cut too coarse ballooned to 34 prod files at execute-recon, an emergency in-build split — no gate at the cut existed.) (Terminology note: this blast radius is a **slice estimate** — ~N files for this one cut — not a project-owned rollout-completeness census (e.g. an-style, code-derived "X of Y" census over all surfaces of a cross-cutting concept, if your repo runs one).)
56
56
  <!-- mirror-xform:start codex-escalation -->
57
- - **Gate type + sequencing (Retro):** every slice that is **not** a clean AFK build gets a gate tag — 🧭 **Design Grill** (a decision with alternatives, hard-to-reverse, ADR-worthy → `grill-with-docs`), 🔬 **Verify Spike** (a pure fact question, read-only), 📐 **Trade-off/Research** (a concrete trade-off choice OR "needs more research", **below** the grill threshold — read-only research + a documented trade-off in the issue), 📝 **Review Note** (a finding, **not** a build slice). A gate slice (🧭/🔬/📐) is cut as its **own slice**, sequenced **before** its dependent build slice, and blocks it (gate-before-build — published as a **native blocking edge** (§5a step 5), visible in the mirrored "Blocked by" section + table order). "Needs a decision / open call / research gap" while cutting → gate slice, **never** a blind AFK `/tdd`. (🔬 **Verify Spike** runs via the `verify-spike` skill — read-only fact question, throwaway harness, verdict as ADR/comment, the throwaway deleted. 📐 **Trade-off/Research** runs via the `decision-gate` skill — options + criteria, read-only research/measurement, a documented trade-off table, a reasoned decision as ADR/comment; throwaway measurement code deleted.)
57
+ - **Gate type + sequencing (Retro):** every slice that is **not** a clean AFK build gets a gate tag — 🧭 **Design Grill** (a decision with alternatives, hard-to-reverse, ADR-worthy → `grill-with-docs`), 🔬 **Verify Spike** (a pure fact question, read-only), 📐 **Trade-off/Research** (a concrete trade-off choice OR "needs more research", **below** the grill threshold — read-only research + a documented trade-off in the issue), 📝 **Review Note** (a finding, **not** a build slice). A gate slice (🧭/🔬/📐) is cut as its **own slice**, sequenced **before** its dependent build slice, and blocks it (gate-before-build — published as a **native blocking edge** (§5a step 5), visible in the mirrored "Blocked by" section + table order). "Needs a decision / open call / research gap" while cutting → gate slice, **never** a blind AFK `/implement`. (🔬 **Verify Spike** runs via the `verify-spike` skill — read-only fact question, throwaway harness, verdict as ADR/comment, the throwaway deleted. 📐 **Trade-off/Research** runs via the `decision-gate` skill — options + criteria, read-only research/measurement, a documented trade-off table, a reasoned decision as ADR/comment; throwaway measurement code deleted.)
58
58
  <!-- mirror-xform:end -->
59
59
  - **🧭-vs.-📐 discriminator:** if the decision replaces/unifies/retires a **central seam**, is a **one-way door** (hard/costly to reverse), or a **schema migration** → 🧭 Design Grill (ADR-worthy, see Seam Ownership above). Otherwise — a **bounded choice between concrete options**, easily revised → 📐 Trade-off/Research.
60
60
  - **Absence-before-build (Retro):** a slice cut as "build feature/page/endpoint/flow X" (or a gate "X gap unclear") MUST cite an **existence grep** — *absent* (`grep <pattern> = 0` across route **and** UI **and** repo) OR *partial* (`exists @<file>, gap = <Y>`). A hedge ("current state unclear → verify during build") is **not** a valid slice state: the check is cheap (minutes), so it happens **at the cut**, not deferred into a gate. (Incident: a gate was cut for a feature that greps already showed built — the gap was deferred instead of resolved.)
@@ -143,33 +143,108 @@ else
143
143
  WAVE="$CURRENT" # Stufe-1p Program-stub — reuse
144
144
  fi
145
145
 
146
- # 2. render the LEAN Tier-2 anchor body from docs/agents/wave-anchor-template.md into /tmp/anchor.md:
147
- # body header `**Welle $WAVE — <Thema>**`, **plan_revision:** r<N> at top (before the first
148
- # heading), the FILLED Slices table (you know the cut), the To-Do checklist collapsed to its
149
- # one-line summary. The full grilled PRD does NOT go into the body (: every slice
150
- # session's `--json body` read loads the whole body a <details> block collapses only in
151
- # the browser). Save it to /tmp/prd-archive.md instead — **with ALL embedded PRD markers
152
- # stripped** (`plan_revision`, `prd-source-id`, `prd-content-fp`,
153
- # `<!-- prd: awaiting-decomposition -->`; a stray second `plan_revision` on the issue is
154
- # exactly the Retro confusion) posted as ISSUE COMMENT #1 in step 2b.
155
- # The anchor carries its own markers at the head. The issue TITLE is rewritten to
156
- # `Welle N — <Thema>` by the promote step below (step 3) — do NOT set it here; promote
157
- # prepends the wave prefix (and strips any `fix:`/`feat:` prefix) idempotently.
158
- # Rewrite the PRD body via skill-prose gh (body-fill is issue CONTENT, NOT a board write — the
159
- # helper owns board state only; cf. test_plan_body_fill_is_not_a_board_sync_op; gh-lint allows a
160
- # non-workflow-label `gh issue edit`). Content-edit FIRST so a failure stops before board mutation:
161
- gh issue edit <prd#> --body-file /tmp/anchor.md
162
-
163
- # 2b. archive the full PRD as comment #1 on the anchor: one click away for a human,
164
- # loaded only on demand by an agent (`--json body` never fetches comments). First line:
165
- # `📄 Full PRD (archive, r<N>) the body carries navigation/decisions only`.
166
- # Idempotent re-run: a comment already starting with `📄 Full PRD (archive` skip, never duplicate:
167
- gh issue comment <prd#> --body-file /tmp/prd-archive.md
168
-
169
- # 3. set the board state (type:cluster + Wave). If THIS fails AFTER the body edit, the title/body are
170
- # already rewritten STOP, report "board state incomplete (body/title already changed)", and
171
- # re-run the idempotent promote (do not leave a silent partial state):
172
- python3 scripts/board-sync.py promote --issue <prd#> --wave "$WAVE" # sets type:cluster + Wave + title `Welle N — …`
146
+ # 2. Fill /tmp/anchor-template.md from docs/agents/wave-anchor-template.md (Tier 2):
147
+ # body header `**Welle $WAVE — <Topic>**`, anchor-owned markers at the head,
148
+ # filled Slices table, and the To-Do checklist collapsed to one line. Save the
149
+ # unchanged source PRD body as /tmp/source-prd.md. The renderer reads only those
150
+ # two files and writes only stdout: no GitHub/network call and no mutation.
151
+ python3 scripts/render-anchor.py --template /tmp/anchor-template.md \
152
+ --prd /tmp/source-prd.md --document anchor > /tmp/anchor.md
153
+ python3 scripts/render-anchor.py --template /tmp/anchor-template.md \
154
+ --prd /tmp/source-prd.md --document archive > /tmp/prd-archive.md
155
+ ```
156
+
157
+ The anchor output is the filled template byte-for-byte. The archive starts with
158
+ the stable `📄 Full PRD` marker and strips every canonical marker line
159
+ (`plan_revision`, `prd-source-id`, `prd-content-fp`, `<!-- prd: -->`) only
160
+ from the source body's head block. Marker-looking text in a quote, fence, or
161
+ later section is content and remains untouched.
162
+
163
+ Promotion uses the remote issue itself as its journal. Classify four
164
+ observations before every resume. Before transition 1, `S=yes|no` compares a
165
+ freshly fetched remote body byte-for-byte with `/tmp/source-prd.md`, the source
166
+ snapshot that produced both rendered outputs. Once `B=yes`, that source
167
+ snapshot is no longer active and `S=n/a`. `B=yes|no` says whether the fetched
168
+ issue body is byte-identical to `/tmp/anchor.md`.
169
+ `C=0|exact-1|wrong-1|duplicates(<ids>)` classifies stable-marker comments:
170
+ none; exactly one whose whole body is byte-identical to
171
+ `/tmp/prd-archive.md`; exactly one with different bytes; or multiple matches
172
+ with every comment ID retained. `P=absent|complete|partial` classifies only
173
+ promotion-owned board state. `P=absent` accepts either an
174
+ ordinary pre-state (no `type:cluster`, Wave unset, no `Welle` title prefix) or a
175
+ valid Stufe-1p pre-state (`wave-stub` present, no `type:cluster`, and the
176
+ pre-stamped Wave/title match `$WAVE`). `P=complete` requires `type:cluster`, the
177
+ expected Wave/title, and no remaining `wave-stub`. Every other combination —
178
+ including a different Wave — is `P=partial`. Fetch comments with the paginated
179
+ API, never the first page alone:
180
+
181
+ ```bash
182
+ gh api --paginate --slurp \
183
+ "repos/<owner>/<repo>/issues/<prd#>/comments?per_page=100"
184
+ ```
185
+
186
+ Flatten every returned page, retain the comment `id`, and exact-match the
187
+ start-of-body marker. The four valid states and their sole resume action are:
188
+
189
+ <!-- promotion-board-observation-table:start -->
190
+ | Scenario | Observable board facts | P classification |
191
+ |---|---|---|
192
+ | `ordinary-prestate` | no cluster; Wave unset; no Wave title | `absent` |
193
+ | `stufe-1p-prestate` | wave-stub; no cluster; expected Wave/title | `absent` |
194
+ | `promoted` | cluster; expected Wave/title; no wave-stub | `complete` |
195
+ | `cluster-only` | cluster; Wave/title missing | `partial` |
196
+ | `wrong-wave` | any different Wave value | `partial` |
197
+ <!-- promotion-board-observation-table:end -->
198
+
199
+ <!-- promotion-state-table:start -->
200
+ | State | Observable predicates | Resume action |
201
+ |---|---|---|
202
+ | `initial` | `S=yes`, `B=no`, `C=0`, `P=absent` | render + write body |
203
+ | `body-written` | `S=n/a`, `B=yes`, `C=0`, `P=absent` | reconcile + write archive comment |
204
+ | `comment-written` | `S=n/a`, `B=yes`, `C=exact-1`, `P=absent` | promote board state |
205
+ | `promoted` | `S=n/a`, `B=yes`, `C=exact-1`, `P=complete` | no-op; continue publish audit |
206
+ <!-- promotion-state-table:end -->
207
+
208
+ Each transition is idempotent and has an explicit contract:
209
+
210
+ 1. **Write body (`initial → body-written`).** Pre: `S=yes`, `B=no`, `C=0`,
211
+ `P=absent`. Fetch the remote body again immediately before the command and
212
+ recompute `S`; a prior fetch is not sufficient.
213
+ Run `gh issue edit <prd#> --body-file /tmp/anchor.md`, refetch the body, and
214
+ require a byte match. Post: `S=n/a`, `B=yes`, `C=0`, `P=absent`.
215
+ 2. **Write archive (`body-written → comment-written`).** Pre: `S=n/a`, `B=yes`,
216
+ `C=0`, `P=absent`. Re-run the pagination-aware lookup immediately before
217
+ create; if it now yields `C=exact-1`, classify as `comment-written` and do
218
+ not create. `C=wrong-1` or `C=duplicates(<ids>)` is drift and enters repair,
219
+ never create. Only `C=0` runs `gh issue comment <prd#> --body-file
220
+ /tmp/prd-archive.md`, then immediately paginate and reconcile again. Post:
221
+ `S=n/a`, `B=yes`, `C=exact-1`, `P=absent`.
222
+ 3. **Promote (`comment-written → promoted`).** Pre: `S=n/a`, `B=yes`,
223
+ `C=exact-1`, `P=absent`. Run `python3 scripts/board-sync.py promote --issue <prd#> --wave
224
+ "$WAVE"`, refetch body, comments, labels, Wave, and title. Post: `S=n/a`,
225
+ `B=yes`, `C=exact-1`, `P=complete`. Re-running `promote` with the same Wave
226
+ is the repair for `P=partial`; a different Wave remains a hard stop.
227
+
228
+ Any other predicate combination is drift, not a fifth state. Repair it
229
+ explicitly, then reclassify. `S=no` before transition 1 means **STOP**: report
230
+ the diff between the fresh remote body and `/tmp/source-prd.md`, never write the
231
+ stale render, re-fetch the remote body into a reviewed source snapshot, and
232
+ rerender both outputs before recomputing `S`. `B=no` outside the valid `initial` tuple
233
+ means **STOP**, report the body diff against `/tmp/anchor.md`, and require
234
+ an explicit operator decision to restore the rendered anchor or adopt the remote
235
+ edit and rerender both outputs; never overwrite remote journal evidence automatically.
236
+ Only transition 1 performs the approved source-to-anchor body write without
237
+ this drift gate. `C=wrong-1` → report its ID and explicitly update it to the
238
+ rendered archive; `P=complete` with `C=0` → create/reconcile the archive without
239
+ demoting. For `P=partial`, a different Wave is a hard stop; otherwise rerun
240
+ same-Wave `promote`. `C=duplicates(<ids>)` always means **STOP and report every
241
+ comment ID**. An operator must choose and explicitly delete/update the
242
+ duplicates, then rerun the paginated lookup; never select the first match or
243
+ silently discard one. No local operation journal is written: these
244
+ observations are the journal.
245
+
246
+ ```bash
247
+ # 3. Continue only from the `promoted` state.
173
248
 
174
249
  # 4. create each child (dependency order), then link it under the anchor — BEFORE the §7 exit audit,
175
250
  # so the checker sees the anchor's children (a childless type:cluster anchor mis-reads as a leaf)
@@ -302,7 +377,7 @@ Reference the blocking ticket(s), or "None - can start immediately".
302
377
  <!-- mirror-xform:start codex-escalation -->
303
378
  ```
304
379
  Welle <N> · Slice <X> (<closes #x | refs #<prd#>, Parent #<prd#>>). Read #<prd#> for decisions.
305
- Start skill: 🧭 Design Grill → /grill-with-docs · 🔬 Verify Spike → /verify-spike · 📐 Trade-off/Research → /decision-gate · AFK → /tdd · HITL → /grill-me → /tdd. Recommended model: <Model [Effort]>.
380
+ Start skill: 🧭 Design Grill → /grill-with-docs · 🔬 Verify Spike → /verify-spike · 📐 Trade-off/Research → /decision-gate · AFK → /implement · HITL → /grill-me → /implement. Recommended model: <Model [Effort]>.
306
381
  Worktree: your project's worktree helper, or `git worktree add`
307
382
  Scope (<N> files) — REQUIRED FIELD, blast-radius estimate at cut time; the build session checks it against its own recon findings, >2x deviation → STOP:
308
383
  - <concrete file + change>
@@ -49,11 +49,21 @@ gh project item-list 1 --owner <owner> --limit 500 --format json # check targe
49
49
  - **Mode A — idempotency via two separate markers in the body:**
50
50
  - **Stable source identity** `<!-- prd-source-id: <id> -->` — **never** changes across plan content edits (otherwise search-before-create misses the changed re-run → duplicate). **Default rule** for `<id>` (identity ≠ content; set on the **first** to-prd run, **never** changed after — the slug then lives in the issue body and is discoverable via search-before-create): kebab-case slug of the plan topic. Priority: **(1)** explicitly passed ID / durable issue number → **(2)** existing slug from a prior run (found via search-before-create) → **(3)** new kebab-case slug from the plan/title topic. The `PLAN.md` path is only a **secondary hint** (not stable across worktrees; external specs have none), **never** the identity itself.
51
51
  - **Separate content fingerprint** `<!-- prd-content-fp: <hash> -->` — only for diff/audit/bump decisions, **not** for identity.
52
- - **search-before-create:** **no** reliance on GitHub Search (doesn't index HTML comments). Bounded, local:
52
+ - **search-before-create:** use the shared all-state exact-marker lookup (it uses
53
+ `gh api --paginate`, discards REST pull-request items, and never relies on
54
+ GitHub Search or a capped issue list):
53
55
  ```bash
54
- gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,body,labels
55
- # filter locally on `prd-source-id: <id>` → 1 match ⇒ update; >1 ⇒ STOP+report; 0 ⇒ create
56
+ python3 scripts/find-by-marker.py --kind prd-source-id --slug "<id>"
56
57
  ```
58
+ Branch on its JSON contract (`count`, `issues[].number`, `issues[].state`,
59
+ `verdict`): `0` / `create` → create; exactly one `open` / `update` → update
60
+ that issue; exactly one `closed` / `user-decision` → ask the user whether to
61
+ reopen, use a new identity, or stop; `>1` / `STOP` → stop and report every
62
+ number/state. Never auto-delete or silently replace a closed/duplicate identity.
63
+ Immediately after a Mode-A create, run the same lookup with
64
+ `--created <new-issue-number>`. Continue only when it returns exactly the
65
+ newly-created open issue; duplicate reconciliation is a loud `STOP` that
66
+ reports both/all numbers for user-decided resolution.
57
67
 
58
68
  ## 4. Write the Draft PRD (deliverable)
59
69
 
@@ -192,10 +192,22 @@ A re-run of to-waves on the same program is **delta-apply** and doubles as
192
192
  bottom-up `wave-stub-source` marker; `<prd-source-id>` is the PRD's own source
193
193
  slug). These never change across revisions — they are the identity for
194
194
  search-before-create.
195
- - **Delta apply.** Filter existing issues locally by their source marker
196
- (`gh issue list --json number,body` + a local filter, no reliance on GitHub search —
197
- it does not index HTML comments). For each planned stub/leaf: **match** → update in
198
- place (never duplicate); **missing** → create; a live issue carrying a
195
+ - **Delta apply.** Resolve every planned identity through the shared all-state,
196
+ exact-marker CLI (it uses `gh api --paginate`, discards REST pull-request
197
+ items, and does not rely on GitHub Search or a capped issue list):
198
+ ```bash
199
+ python3 scripts/find-by-marker.py --kind program-stub-source --slug "<prd-source-id>/w<N>"
200
+ python3 scripts/find-by-marker.py --kind program-leaf-source --slug "<prd-source-id>/<local-id>"
201
+ ```
202
+ Branch on each JSON result (`count`, `issues[].number`, `issues[].state`,
203
+ `verdict`): `0` / `create` → create; exactly one `open` / `update` → update in
204
+ place; exactly one `closed` / `user-decision` → ask the user whether to reopen,
205
+ use a new identity, or stop; `>1` / `STOP` → stop and report every number/state.
206
+ Never auto-delete or silently replace a closed/duplicate identity. Immediately
207
+ after every create, run the same lookup with `--created <new-issue-number>` and
208
+ continue only when exactly the newly-created open issue is returned; a duplicate
209
+ reconciliation stops loudly with both/all numbers for user-decided resolution.
210
+ A live issue carrying a
199
211
  `program-*-source` for this program that the current plan no longer references →
200
212
  report as **orphaned** (do not auto-close — closing is the abort convention, §8).
201
213
  Cross-check the native children (`children-of <prd#>` and each stub) against the
@@ -17,7 +17,7 @@ This is the "Verify First" rule with a runnable harness behind it: when you cann
17
17
  | "Which of these options, given these trade-offs?" / "I need to research this before I can choose." (bounded, sub-grill) | `decision-gate` |
18
18
  | "Does this design / state model / UI feel right?" (open-ended) | `prototype` |
19
19
  | "Why is this broken / slow?" (root-cause of a known defect) | `diagnose` |
20
- | "Build this behaviour, test-first." (new feature/fix) | `tdd` |
20
+ | "Build this behaviour, test-first." (new feature/fix) | `implement` |
21
21
 
22
22
  If the question is really "what should this be", it is not a verify-spike — those are open, not yes/no.
23
23
 
@@ -29,9 +29,21 @@ Hard stop (the only pure precondition): not in a feature worktree / on `main`
29
29
 
30
30
  ### 2 · Retro gate (blocking, optional retro-exit — before anything is committed)
31
31
  One reminder, not a merge confirmation:
32
- > "Already ran a retro? **(a)** yes / continue → landing now. **(b)** you want one first → clean abort here, run `/retro`, then `/wrapup` again — repo-file patches then travel in this PR."
32
+ > "Already ran a retro? **(a)** yes / continue → landing now. **(b)** you want one first → the retro starts now; afterwards, invoke `/wrapup` again — repo-file patches then travel in this PR."
33
33
 
34
- (b) → exit cleanly, touch nothing (a skill can't pause/resume another skill). **Never auto-run `/retro`.** The answer materializes as the mandatory `**Retro:**` PR-body line in step 4 — that's recording, not a second question. Why the gate lives here: in a foreign project this is the only portable retro touchpoint; a project-local "offer retro before PR" convention usually answers it already.
34
+ (b) → **invoke the `retro` skill immediately in this run.** Retro is
35
+ model-invocable and non-deploying, and every mutation still has its own approval
36
+ gate. After retro finishes, **land nothing in this run**. Require a **fresh
37
+ explicit `/wrapup` invocation** because retro may have changed the exact diff
38
+ that the next merge authorization covers.
39
+
40
+ General chaining rule: automatically chain only into a **model-invocable**,
41
+ **non-deploying** workflow. If the named target is user-only, deploys, or
42
+ depends on an external action, **return control to the user** and **state the
43
+ reason**. A forward chain never carries wrapup's merge/deploy authorization
44
+ into another run.
45
+
46
+ The answer materializes as the mandatory `**Retro:**` PR-body line in step 4 — that's recording, not a second question. Why the gate lives here: in a foreign project this is the only portable retro touchpoint; a project-local "offer retro before PR" convention usually answers it already.
35
47
 
36
48
  ### 3 · Commit (only if the tree is dirty)
37
49
  Judgment first, then the guarded commit:
@@ -67,5 +79,4 @@ STOP → diagnose in the main conversation, fix, re-run `land` (an already-merge
67
79
 
68
80
  ## Out of scope
69
81
  - Live-verify / DoD: must happen **before** `/wrapup` — this skill lands, it does not verify.
70
- - `/retro`: offered before landing (step 2), never run by this skill.
71
82
  - Other worktrees / their servers stay untouched.
@@ -48,8 +48,11 @@ GUARD_ACK_RE = re.compile(r"<!--\s*guard-ack:\s*#?\d+\s+r\d+\s+reason:.+\bby-use
48
48
  # Load the shared checker (hyphenated filename → importlib). Repo root = parents[2].
49
49
  _CHECKER_PATH = Path(__file__).resolve().parents[2] / "scripts" / "execute-ready-check.py"
50
50
  _CENSUS_MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "census" / "index.mjs"
51
- CENSUS_BRIDGE_TIMEOUT_SECONDS = 15
52
- CENSUS_PROOF_TIMEOUT_MS = 5_000
51
+ # Production repositories can need more than the former five-second proof and
52
+ # fifteen-second bridge budgets. Keep the default proof comfortably below the
53
+ # outer bridge so a hung repository-local proof still cannot wedge the hook.
54
+ CENSUS_BRIDGE_TIMEOUT_SECONDS = 30
55
+ CENSUS_PROOF_TIMEOUT_MS = 12_000
53
56
 
54
57
  _CENSUS_SCAN = r"""
55
58
  import { readFileSync } from 'node:fs';
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """Advisory PreToolUse hint for kit-origin Edit/Write targets.
3
+
4
+ Covered surface: agent Edit/Write tools only. Shell redirection, formatters, and
5
+ IDE edits are knowingly uncovered; the skills remain the rule's primary
6
+ carrier. This hook never enforces or blocks. It performs a single manifest read,
7
+ does no network work, and fails open silently when input or manifest is unusable.
8
+ """
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ MANIFEST = "agent-workflow-kit.json"
14
+ TOOLS = {"Edit", "Write"}
15
+ HINT = (
16
+ "This file has kit origin. Before editing, ask: should this change go "
17
+ "upstream to agent-workflow-kit, or should the consumer own this file?"
18
+ )
19
+
20
+
21
+ def manifest_path(file_path: str, root: Path) -> str | None:
22
+ try:
23
+ target = Path(file_path)
24
+ target = target if target.is_absolute() else root / target
25
+ normalized = target.resolve().relative_to(root.resolve()).as_posix()
26
+ except (OSError, ValueError):
27
+ return None
28
+ return normalized or None
29
+
30
+
31
+ def is_kit_origin(payload: object, root: Path) -> bool:
32
+ if not isinstance(payload, dict) or payload.get("tool_name") not in TOOLS:
33
+ return False
34
+ tool_input = payload.get("tool_input")
35
+ if not isinstance(tool_input, dict) or not isinstance(tool_input.get("file_path"), str):
36
+ return False
37
+ path = manifest_path(tool_input["file_path"], root)
38
+ if path is None:
39
+ return False
40
+ manifest = json.loads((root / MANIFEST).read_text(encoding="utf-8"))
41
+ installed = manifest.get("installed", []) if isinstance(manifest, dict) else []
42
+ return any(
43
+ isinstance(entry, dict) and entry.get("path") == path and entry.get("origin") == "kit"
44
+ for entry in installed
45
+ )
46
+
47
+
48
+ def main() -> int:
49
+ try:
50
+ payload = json.load(sys.stdin)
51
+ if is_kit_origin(payload, Path.cwd()):
52
+ print(json.dumps({
53
+ "hookSpecificOutput": {
54
+ "hookEventName": "PreToolUse",
55
+ "additionalContext": HINT,
56
+ }
57
+ }))
58
+ except (OSError, ValueError, TypeError):
59
+ pass
60
+ return 0
61
+
62
+
63
+ if __name__ == "__main__":
64
+ sys.exit(main())
@@ -53,7 +53,7 @@ Prefer the smallest depth that produces a clear next action.
53
53
  - **Deep:** `/grill-with-docs` followed by `/to-prd` and `/to-issues` when
54
54
  terminology, contracts, rollout order, or ownership are still uncertain.
55
55
  - **Medium:** `/to-issues` for a ready artefact that needs slicing.
56
- - **Light:** direct `/tdd` for a small, well-understood change.
56
+ - **Light:** direct `/implement` for a small, well-understood change.
57
57
  - **Gate:** insert `/verify-spike` or `/decision-gate` before any depth level
58
58
  when a slice hinges on an unresolved fact or trade-off.
59
59
 
@@ -75,7 +75,7 @@ A starting situation that generates work, then merges onto the main flow.
75
75
  - **Something's broken** → **`/diagnose`**. For the hard ones: the bug that resists a first glance, the intermittent flake, the regression between two known-good states. Refuses to theorise until it has a **tight feedback loop** — one command that already goes red on *this* bug — then fixes with a regression test. Its post-mortem hands off to **`/improve-codebase-architecture`** when the finding is that there's no good seam.
76
76
  - **A huge, foggy effort too big for one agent session** — a greenfield build or a sprawling feature where the way from here to the destination isn't yet visible, and it's still **pre-spec** (before any grill or PRD) → **`/wayfinder`** (user-invoked). It charts a **shared map** of investigation slices on the tracker and resolves them one at a time — producing **decisions, not deliverables** — until the fog lifts and the route is clear, then merges onto the main flow at **`/to-prd`** (or, if the effort turned out small enough, straight to **`/implement`**). Where `/grill-with-docs` sharpens an idea you can hold in one session, wayfinder is for the one you can't. Once an undertaking has already resolved into several independently-shippable waves, `/scale-check` → `/to-waves` stays the primary route.
77
77
  - **A backlog to cluster** → **`/board-to-waves`**. Groups an existing board into themed waves when you need to *find* the next wave rather than start fresh.
78
- - **A whole wave to LAND** → **`/orchestrate-wave`**. When a wave anchor (file-disjoint slices, specs already locked) is ready to build, verify and land end-to-end — often AFK: it dispatches an implementer per slice in its own worktree, integrates serially, verifies centrally, and lands the wave. The execute-and-land node of the wave ladder (`scale-check` → `to-waves`/`board-to-waves` → `orchestrate-wave`). A single slice just goes to `/tdd`.
78
+ - **A whole wave to LAND** → **`/orchestrate-wave`**. When a wave anchor (file-disjoint slices, specs already locked) is ready to build, verify and land end-to-end — often AFK: it dispatches an implementer per slice in its own worktree, integrates serially, verifies centrally, and lands the wave. The execute-and-land node of the wave ladder (`scale-check` → `to-waves`/`board-to-waves` → `orchestrate-wave`). A single slice just goes to `/implement`.
79
79
 
80
80
  ## Cross-model review (Codex)
81
81
 
@@ -110,11 +110,21 @@ Body from `docs/agents/wave-anchor-template.md` **stage 1** (header + cluster or
110
110
 
111
111
  **Idempotency — stub marker + search-before-create (mandatory, BEFORE `create`).** Re-runs of board-to-waves must **not** produce duplicate stubs (a duplicate stub would confuse Mode-B identity in `to-prd`). Mirrors the `to-prd` pattern:
112
112
  - **Stable stub marker** `<!-- wave-stub-source: <topic-slug> -->` as the **first body line** of every stub. `<topic-slug>` = kebab-case slug of the gate outcome; set on the **first** run, **never** changed after (identity ≠ content — the slug stays findable even if members/size change later).
113
- - **search-before-create** per candidate **before** the `create`. **No** reliance on GitHub search (doesn't index HTML comments) — bounded, local:
113
+ - **search-before-create** per candidate **before** the `create`, through the
114
+ shared all-state exact-marker lookup (it uses `gh api --paginate`, discards REST
115
+ pull-request items, and never relies on GitHub Search or a capped issue list):
114
116
  ```bash
115
- gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,body,labels
116
- # locally filter on `wave-stub-source: <topic-slug>` → 1 match ⇒ skip + report (stub exists); >1 ⇒ STOP + report; 0 ⇒ create
117
+ python3 scripts/find-by-marker.py --kind wave-stub-source --slug "<topic-slug>"
117
118
  ```
119
+ Branch on its JSON contract (`count`, `issues[].number`, `issues[].state`,
120
+ `verdict`): `0` / `create` → create; exactly one `open` / `update` → skip the
121
+ create and report/update that stub; exactly one `closed` / `user-decision` → ask
122
+ the user whether to reopen, use a new identity, or stop; `>1` / `STOP` → stop
123
+ and report every number/state. Never auto-delete or silently replace a
124
+ closed/duplicate identity. Immediately after create, run the same lookup with
125
+ `--created <new-issue-number>`; continue only when it returns exactly the
126
+ newly-created open issue. Duplicate reconciliation stops loudly and reports
127
+ both/all numbers for user-decided resolution.
118
128
 
119
129
  **Create the candidate stub (cluster/wave-less)** — issue **without** `type:cluster` and **without** `--wave` (exactly one `type:*` + one `priority:*`; title **without** a `Welle <N>` prefix, since the wave number is only assigned at promotion), **with `--wave-stub`** (a searchable "awaiting planning" filter — the HTML marker above is only locally greppable, GitHub doesn't index it), attach to the board, triaged-role status (clustered, not yet planned). `to-prd` then matures the stub (Mode B) into a Draft PRD, `to-issues` promotes it to an anchor (sets `type:cluster` + Wave then, **strips `wave-stub`** — the stub leaves the planning list):
120
130
  ```bash
@@ -13,12 +13,14 @@ The role-flip of `/codex-review`: there, Claude builds the plan and Codex critiq
13
13
 
14
14
  ## Prerequisites (verify once, fast)
15
15
 
16
- - `codex --version` 0.130 (older CLIs error on the default `gpt-5.5` model).
17
- - Codex authenticated (prior `codex login`; ChatGPT account is fine). On auth/model error, surface it — don't silently retry.
16
+ - Let `scripts/codex-exec.sh` preflight Codex before launch. It enforces the
17
+ exact tested-version allowlist, authentication, platform, and capabilities;
18
+ surface any failure rather than retrying silently.
18
19
  - Do NOT pin `-m` or model config (e.g. `model_reasoning_effort`) unless the user asks. Pinning `gpt-5.x-codex` variants 400s on ChatGPT-account auth; config defaults come from `~/.codex/config.toml`.
19
20
  - **Echo the active model at kickoff** so the user can confirm: read the `model` line from `~/.codex/config.toml` (absent = "CLI default"); state it with the resolved tunables. If the user objects, stop before launching the build.
20
- - **Codex has a native image-generation tool** in `codex exec` sessions (ChatGPT-account backed, no API key; upstream-verified 2026-07-08). Specs may therefore include "generate these image assets yourself" steps: name exact file paths, dimensions, and style in the prompt contract.
21
- - Run from the target working directory's root (both `exec` and `resume` then need no `-C`; `resume` doesn't support `-C` anyway).
21
+ - **Codex has a native image-generation tool** in delegated sessions (ChatGPT-account backed, no API key; upstream-verified 2026-07-08). Specs may therefore include "generate these image assets yourself" steps: name exact file paths, dimensions, and style in the prompt contract.
22
+ - Run the wrapper from the target working directory's root so every round keeps
23
+ the same bounded workspace.
22
24
 
23
25
  ## Tunables (read from args, else default)
24
26
 
@@ -63,17 +65,34 @@ OUTPUT: End with a report — files changed (one line each: path + what/why),
63
65
  EOF
64
66
  ```
65
67
 
66
- ## Step 2 — Launch Codex (fresh session, capture `thread_id`)
68
+ ## Step 2 — Launch Codex (fresh wrapper-owned session)
67
69
 
68
70
  ```bash
69
- codex exec -s workspace-write --json -o "$CODEX_TMP/build.txt" - <"$P" 2>/dev/null | grep '"type":"thread.started"'
71
+ if ROUND_RESULT=$(scripts/codex-exec.sh new --profile build --mode workspace-write --prompt-file "$P"); then
72
+ RUN_ID=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["runId"])')
73
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
74
+ else
75
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT") || :
76
+ printf '%s\n' "$FAILURE_RESULT" >&2
77
+ exit 1
78
+ fi
70
79
  ```
71
80
 
72
- - **`-s workspace-write`, never `--yolo` / `--dangerously-bypass-approvals-and-sandbox`, and never weaken the approval policy.** The sandbox bounds Codex to the working directory; the allowed-write gate (Step 3) bounds it further to the declared set. If the build genuinely needs something outside the sandbox (network installs, system config), that step is Claude's, not Codex's — split it out of the spec.
73
- - Prompt goes via stdin (`- <"$P"`) this both avoids quoting bugs AND sidesteps the non-TTY stdin hang (`codex exec` blocks forever waiting on stdin EOF under Claude Code's Bash tool; feeding the file gives immediate EOF).
74
- - Parse `thread_id` from the `{"type":"thread.started","thread_id":"..."}` line `THREAD_ID`. Codex's final report lands in `$CODEX_TMP/build.txt` — read that file; don't parse the JSONL stream for content.
75
- - `2>/dev/null` suppresses cosmetic MCP/auth stderr noise. Confirm success by the report file + a `thread.started` line; neither failed run (auth/model) stop and tell the user.
76
- - **Timing:** foreground with `timeout: 600000` on the Bash tool call (default 2-min tool timeout kills real builds). If the spec is clearly >10 min of work (multi-file feature, migration, anything with image generation), launch with `run_in_background: true` instead and read the `-o` file when it exits. Don't kill a quiet background run early — Codex builds are legitimately slow.
81
+ - The `build` profile establishes and persists `workspace-write`; never request
82
+ `danger-full-access` or weaken the approval policy. The wrapper bounds Codex
83
+ to the working directory, and the allowed-write gate (Step 3) narrows it to
84
+ the declared set. Work outside those bounds belongs to Claude and must be
85
+ split out of the spec.
86
+ - The wrapper owns stdin closure, hang detection, the overall build timeout,
87
+ stderr redaction, and the launched process group. Never inspect, signal, or
88
+ kill foreign Codex processes.
89
+ - `RUN_ID` is opaque. Retain it only for resume/finalize/abort; harvest the
90
+ report from `CODEX_REPORT` before deleting run state.
91
+ - `handle-failure` surfaces every failed or cancelled structured result plus
92
+ cleanup metadata; the caller then stops before report handling. A surfaced
93
+ `HUNG` returns to the user for the choice to retry once with a fresh run or
94
+ stop delegation and let Claude take over. Never target a process directly.
95
+
77
96
  - **Heads-up on completion (required):** when a background Codex run finishes, the FIRST line of your next message to the user must be a loud standalone banner — `🔔 CODEX FINISHED — <what> (exit ok/fail) — verifying now` — BEFORE any verification output. The user is not watching tool calls; never let a completed build slide silently into the verify phase.
78
97
 
79
98
  ## Step 3 — Verify (Claude, always, never delegated)
@@ -83,33 +102,60 @@ Codex's report is advisory. Verify yourself:
83
102
  1. **Allowed-write gate:** `git status --porcelain=v1` — every changed AND untracked path must be inside the allowed-write set from Step 0.4. Any path outside it is a finding: surface it to the user before anything else, and don't fold it silently into the diff review.
84
103
  2. Read the FULL diff (`git diff`). Judge it like a contributor PR: correctness, spec fidelity, style match with surrounding code, nothing touched outside scope.
85
104
  3. Run `PROOF_CMD` yourself (or the focused tests for the changed area). Codex's pasted output doesn't count as proof.
86
- 4. Append to `LOG_FILE` under `## Act 3 — Build`: `### Round <n> — Codex build` + its report summary + `### Claude's verdict` + what passed/failed review.
105
+ 4. Append to `LOG_FILE` under `## Act 3 — Build`: `### Round <n> — Codex build` + `CODEX_REPORT` + `### Claude's verdict` + what passed/failed review.
87
106
 
88
107
  ## Step 4 — Fix loop (same session, bounded)
89
108
 
90
109
  Problems found → resume the SAME session (Codex keeps its context; cheaper and better than a fresh run). Write the fix list to a second temp file (`$CODEX_TMP/fix-prompt.txt`), same contract discipline: exact problem, exact file, proof expected, same allowed-write set.
91
110
 
92
111
  ```bash
93
- # resume has no -s and no -C: run from the repo dir and force the sandbox via
94
- # config override, or Codex inherits config.toml's sandbox_mode (possibly
95
- # read-only — can't write; possibly danger-full-access — must never run).
96
- codex exec resume "$THREAD_ID" -c sandbox_mode="workspace-write" --json \
97
- -o "$CODEX_TMP/build.txt" - <"$CODEX_TMP/fix-prompt.txt" 2>/dev/null >/dev/null
112
+ if ROUND_RESULT=$(scripts/codex-exec.sh resume "$RUN_ID" --prompt-file "$CODEX_TMP/fix-prompt.txt"); then
113
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
114
+ else
115
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT" --run-id "$RUN_ID") || :
116
+ printf '%s\n' "$FAILURE_RESULT" >&2
117
+ exit 1
118
+ fi
98
119
  ```
99
120
 
100
- Re-verify (Step 3, including the allowed-write gate) after each round. After `MAX_FIX_ROUNDS` failed rounds: STOP delegating — Claude takes over and finishes the remaining fixes directly. Log the takeover. Ping-ponging trivia through delegation burns more than it saves.
121
+ The wrapper enforces the persisted workspace-write mode on every resume.
122
+ Re-verify (Step 3, including the allowed-write gate) after each round. After
123
+ `MAX_FIX_ROUNDS` failed rounds: abort this run, STOP delegating, and let Claude
124
+ take over and finish the remaining fixes directly. Log the takeover.
125
+
126
+ ```bash
127
+ scripts/codex-exec.sh abort "$RUN_ID"
128
+ ```
101
129
 
102
130
  ## Step 5 — Human gate (diff sign-off)
103
131
 
104
132
  Present: 3-bullet summary of what was built, files-changed list, proof-test output (pass/fail, verbatim tail), rounds used, any spec deviations. Ask: *"Codex built it, proof passes, diff reviewed. Commit?"*
105
133
 
106
- - Commit ONLY on yes and Claude writes the commit, never Codex.
107
- - Rejected → ask what's wrong, route back to Step 4 (or take over directly if fix rounds are spent).
134
+ - On yes, harvest and log the final report, then delete the wrapper state before
135
+ Claude commits:
136
+
137
+ ```bash
138
+ scripts/codex-exec.sh finalize "$RUN_ID"
139
+ ```
140
+
141
+ - Commit ONLY after that finalize succeeds — and Claude writes the commit,
142
+ never Codex.
143
+ - Rejected with another requested fix → keep the known run and route back to
144
+ Step 4 (or take over directly if fix rounds are spent).
145
+ - Cancellation or a decision to stop delegation → abort the known run before
146
+ returning control:
147
+
148
+ ```bash
149
+ scripts/codex-exec.sh abort "$RUN_ID"
150
+ ```
108
151
 
109
152
  ## Hard rules
110
153
 
111
154
  - Clean tree before launch. Always. No exceptions.
112
- - **Sandbox is bounded every round:** `-s workspace-write` on exec, `-c sandbox_mode="workspace-write"` on every resume. Never `--yolo`, never `danger-full-access`, never a weakened approval policy — if the task can't be done inside those bounds, it isn't a Codex delegation.
155
+ - **The mode is bounded every round:** establish `workspace-write` in the
156
+ wrapper's new call and let every resume inherit it. Never `--yolo`, never
157
+ `danger-full-access`, never a weakened approval policy — if the task can't
158
+ be done inside those bounds, it isn't a Codex delegation.
113
159
  - The allowed-write gate runs after EVERY round — out-of-set writes are findings, not noise.
114
160
  - Claude never skips the diff read. Codex claims are advisory until Claude has read the diff and run the proof.
115
161
  - Fix loop terminates at `MAX_FIX_ROUNDS` — then Claude takes over. No unbounded delegation ping-pong.
@@ -121,6 +167,6 @@ Present: 3-bullet summary of what was built, files-changed list, proof-test outp
121
167
  - Don't build without a spec — that's designing by delegation, and it fails. Route to `/grill-me-codex` or `/codex-review` first.
122
168
  - Don't use for ~<20-line single-obvious-change edits — just make the edit.
123
169
  - Don't pin `-codex` model variants on ChatGPT-account auth — 400s.
124
- - Don't resume with `--last` — capture and use the explicit `THREAD_ID` (parallel sessions make `--last` grab the wrong thread). And ECHO the id into the command visibly before running: `resume` with a missing/garbage id can silently fall back to the most recent session instead of erroring (observed upstream 2026-07-08) — a wrong-target resume looks exactly like a successful one.
125
- - Don't parse the JSONL stream for the report — read the `-o` file.
170
+ - Don't resume with `--last`; use the wrapper's explicit opaque `RUN_ID` so
171
+ parallel sessions cannot target each other.
126
172
  - Don't let Codex commit, and don't auto-commit yourself — human gate first.