@ikon85/agent-workflow-kit 0.28.0 → 0.29.1

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 (33) hide show
  1. package/.agents/skills/board-to-waves/SKILL.md +13 -3
  2. package/.agents/skills/to-issues/SKILL.md +102 -27
  3. package/.agents/skills/to-prd/SKILL.md +13 -3
  4. package/.agents/skills/to-waves/SKILL.md +16 -4
  5. package/.agents/skills/wrapup/SKILL.md +0 -1
  6. package/.claude/skills/board-to-waves/SKILL.md +13 -3
  7. package/.claude/skills/codex-build/SKILL.md +69 -23
  8. package/.claude/skills/codex-review/SKILL.md +47 -37
  9. package/.claude/skills/grill-me-codex/SKILL.md +45 -34
  10. package/.claude/skills/grill-with-docs-codex/SKILL.md +46 -34
  11. package/.claude/skills/to-issues/SKILL.md +102 -27
  12. package/.claude/skills/to-prd/SKILL.md +13 -3
  13. package/.claude/skills/to-waves/SKILL.md +16 -4
  14. package/.claude/skills/wrapup/SKILL.md +0 -1
  15. package/README.md +27 -0
  16. package/agent-workflow-kit.package.json +56 -16
  17. package/docs/research/wave-152-consumer-acceptance.md +98 -0
  18. package/package.json +1 -1
  19. package/scripts/board-sync.py +3 -7
  20. package/scripts/build-kit.test.mjs +42 -1
  21. package/scripts/codex-exec-scenarios/fake-codex.mjs +152 -0
  22. package/scripts/codex-exec.sh +566 -0
  23. package/scripts/codex-exec.test.mjs +815 -0
  24. package/scripts/codex_proc.py +602 -0
  25. package/scripts/find-by-marker.py +68 -0
  26. package/scripts/marker_lib.py +88 -0
  27. package/scripts/release-state.test.mjs +53 -1
  28. package/scripts/render-anchor.py +111 -0
  29. package/scripts/test_marker_lib.py +154 -0
  30. package/scripts/test_render_anchor.py +267 -0
  31. package/scripts/test_retro_wrapup_contract.py +6 -0
  32. package/scripts/test_skill_codex_exec_lifecycle.py +123 -0
  33. package/src/lib/bundle.mjs +10 -0
@@ -97,11 +97,21 @@ Body from `docs/agents/wave-anchor-template.md` **stage 1** (header + cluster or
97
97
 
98
98
  **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:
99
99
  - **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).
100
- - **search-before-create** per candidate **before** the `create`. **No** reliance on GitHub search (doesn't index HTML comments) — bounded, local:
100
+ - **search-before-create** per candidate **before** the `create`, through the
101
+ shared all-state exact-marker lookup (it uses `gh api --paginate`, discards REST
102
+ pull-request items, and never relies on GitHub Search or a capped issue list):
101
103
  ```bash
102
- gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,body,labels
103
- # locally filter on `wave-stub-source: <topic-slug>` → 1 match ⇒ skip + report (stub exists); >1 ⇒ STOP + report; 0 ⇒ create
104
+ python3 scripts/find-by-marker.py --kind wave-stub-source --slug "<topic-slug>"
104
105
  ```
106
+ Branch on its JSON contract (`count`, `issues[].number`, `issues[].state`,
107
+ `verdict`): `0` / `create` → create; exactly one `open` / `update` → skip the
108
+ create and report/update that stub; exactly one `closed` / `user-decision` → ask
109
+ the user whether to reopen, use a new identity, or stop; `>1` / `STOP` → stop
110
+ and report every number/state. Never auto-delete or silently replace a
111
+ closed/duplicate identity. Immediately after create, run the same lookup with
112
+ `--created <new-issue-number>`; continue only when it returns exactly the
113
+ newly-created open issue. Duplicate reconciliation stops loudly and reports
114
+ both/all numbers for user-decided resolution.
105
115
 
106
116
  **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):
107
117
  ```bash
@@ -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)
@@ -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
@@ -79,5 +79,4 @@ STOP → diagnose in the main conversation, fix, re-run `land` (an already-merge
79
79
 
80
80
  ## Out of scope
81
81
  - Live-verify / DoD: must happen **before** `/wrapup` — this skill lands, it does not verify.
82
- - `/retro`: offered before landing (step 2), never run by this skill.
83
82
  - Other worktrees / their servers stay untouched.
@@ -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.
@@ -11,11 +11,13 @@ This is a **deliberate, high-stakes tool** — reach for it on auth, data models
11
11
 
12
12
  ## Prerequisites (verify once, fast)
13
13
 
14
- - Codex CLI installed and recent: `codex --version` (need 0.130; the default `gpt-5.5` model errors on older CLIs).
15
- - Codex authenticated: a prior `codex login` (ChatGPT account is fine). If a run returns an auth/model error, surface it to the user — do not silently retry.
14
+ - Let `scripts/codex-exec.sh` preflight Codex before launch. It enforces the
15
+ exact tested-version allowlist, authentication, platform, and capabilities;
16
+ surface any failure to the user rather than retrying silently.
16
17
  - Do NOT pin `-m` unless the user asks. The user's `~/.codex/config.toml` default model is used. Pinning `gpt-5.x-codex` variants fails on ChatGPT-account auth.
17
18
  - **Echo the active model before Round 1** so the user can confirm: read the `model` line from `~/.codex/config.toml` (absent = "CLI default"); state it alongside the resolved tunables. If the user objects, stop before burning a round.
18
- - **Sandbox flag differs between the two commands.** `codex exec` accepts `-s read-only`. `codex exec resume` does NOT — it rejects `-s` ("unexpected argument"). On resume you MUST force read-only via `-c sandbox_mode="read-only"`, because `config.toml` may default `sandbox_mode` to `danger-full-access` (+ `approval_policy="never"`) — which would let Codex WRITE files mid-loop. This is the single most important safety detail in this skill: verified end-to-end on 2026-06-04.
19
+ - Use `scripts/codex-exec.sh` for every round. Its `review` profile persists the
20
+ initial `read-only` mode across resumes and rejects mode drift.
19
21
 
20
22
  ## Tunable variables (read from skill args, else default)
21
23
 
@@ -37,7 +39,7 @@ The invocation itself is the kickoff. Confirm scope in one line: what is being p
37
39
 
38
40
  Do real planning: read the relevant code, think through the approach, surface decisions and tradeoffs. Then write the plan to `PLAN_FILE` in this structure:
39
41
 
40
- > **Where to write it:** `PLAN.md` + `PLAN-REVIEW-LOG.md` are per-session scratch — write them in the working directory the implementing session will actually use, and run Codex from there (`-C <dir>` on the round-1 `exec`; `exec resume` rejects both `-C` and `-s`, so run resume from that cwd and force read-only via `-c sandbox_mode="read-only"`). A project may gitignore these files, so don't rely on git to carry them across checkouts/worktrees. In worktree-based repos, create the issue worktree BEFORE this write and plan inside it.
42
+ > **Where to write it:** `PLAN.md` + `PLAN-REVIEW-LOG.md` are per-session scratch — write them in the working directory the implementing session will actually use, and invoke the wrapper from that directory. A project may gitignore these files, so don't rely on git to carry them across checkouts/worktrees. In worktree-based repos, create the issue worktree BEFORE this write and plan inside it.
41
43
 
42
44
  ```markdown
43
45
  # Plan: <task>
@@ -69,60 +71,66 @@ Show the user the plan inline and say you're sending it to Codex for adversarial
69
71
 
70
72
  ### Step 2 — The loop
71
73
 
72
- Maintain `ROUND` (start 1) and `THREAD_ID` (empty until round 1 returns).
74
+ Maintain `ROUND` (start 1) and the wrapper's opaque `RUN_ID`.
73
75
 
74
76
  **The review prompt** sent to Codex each round (adjust the task line):
75
77
 
76
78
  > You are an adversarial reviewer for an implementation plan. Be skeptical and specific — your job is to find what breaks, not to be agreeable. Read the plan at `PLAN.md` (and any repo files you need; you are read-only). Identify concrete flaws: security holes, race conditions, missing edge cases, schema conflicts, wrong assumptions, observability gaps, simpler alternatives. For each, give a one-line fix. Do NOT modify any files. End your reply with EXACTLY one line: `VERDICT: APPROVED` if the plan is sound enough to implement, or `VERDICT: REVISE` if it still has material problems.
77
79
 
78
- **Round 1** (creates the session capture `thread_id`):
79
-
80
- Stream `--json` to a FILE, never pipe to `grep` — `codex exec --json | grep` deadlocks on codex-cli ≥0.137. **Always launch with `< /dev/null`** — a backgrounded `codex exec … &` without it blocks on stdin and sits at **0 CPU / 0 bytes** forever (the #1 cause of the "silent hang"; verified 2026-06-09). Background it so a **90s liveness probe** still catches a genuine sandbox deadlock.
80
+ **Round 1** creates the session and returns the opaque run ID plus the report:
81
81
 
82
82
  ```bash
83
- CODEX_TMP="/tmp/codex-$(pwd | sha1sum | cut -c1-8)"; mkdir -p "$CODEX_TMP" # run-unique per worktree cwd: STABLE across round-1+resume turns, collision-free under parallel sessions
84
- codex exec -s read-only --json -o $CODEX_TMP/verdict.txt "$(cat REVIEW_PROMPT)" \
85
- < /dev/null > $CODEX_TMP/r1.jsonl 2>/dev/null &
86
- CODEX_PID=$!
87
- sleep 90 # liveness probe (REQUIRED)
88
- if kill -0 "$CODEX_PID" 2>/dev/null; then
89
- CPU=$(ps -o time= -p "$CODEX_PID" 2>/dev/null | tr -dc '0-9:') # cumulative CPU, e.g. 00:00:00
90
- BYTES=$(wc -c < $CODEX_TMP/r1.jsonl 2>/dev/null || echo 0)
91
- if [ "${CPU:-00:00:00}" = "00:00:00" ] && [ "${BYTES:-0}" -eq 0 ]; then
92
- kill -9 "$CODEX_PID" 2>/dev/null; echo "CODEX-HUNG" # alive + 0 CPU + 0 bytes = blocked, NOT working
93
- fi
83
+ if ROUND_RESULT=$(scripts/codex-exec.sh new --profile review --mode read-only --prompt "$REVIEW_PROMPT"); then
84
+ RUN_ID=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["runId"])')
85
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
86
+ else
87
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT") || :
88
+ printf '%s\n' "$FAILURE_RESULT" >&2
89
+ exit 1
94
90
  fi
95
- wait "$CODEX_PID" 2>/dev/null
96
- THREAD_ID=$(grep -o '"thread_id":"[^"]*"' $CODEX_TMP/r1.jsonl | head -1 | cut -d'"' -f4)
97
91
  ```
98
- - **`CODEX-HUNG` printed** (alive + 0 CPU + 0 bytes at 90s) → **first suspect the stdin block**: confirm the launch has `< /dev/null` and retry. That fixes it in nearly every case. **NEVER `pgrep`/`kill` codex procs to "clear contention"** — that murders the user's live, unrelated codex sessions and does **not** fix a stdin hang. Only if `< /dev/null` is present and it still hangs (genuine sandbox deadlock) → **STOP**: tell the user, offer to proceed without the cross-model review or retry once. Don't touch other codex processes.
99
- - **Healthy:** CPU climbs past `00:00:00` and/or `$CODEX_TMP/r1.jsonl` grows; `THREAD_ID` parses; critique lands in `$CODEX_TMP/verdict.txt`. `2>/dev/null` hides cosmetic MCP/auth noise.
100
- - **Clean finish but no verdict file + no `THREAD_ID`** = auth/model failure → stop, tell the user.
101
92
 
102
- **Rounds 2..MAX** (resume the SAME session Codex remembers its earlier critiques, won't re-litigate settled points):
93
+ **Rounds 2..MAX** resume the same wrapper-owned session; do not pass a mode
94
+ again because the wrapper enforces the persisted read-only mode:
103
95
 
104
96
  ```bash
105
- # NOTE: resume rejects -s. Force read-only via -c sandbox_mode, or Codex
106
- # inherits config.toml (possibly danger-full-access) and could write files.
107
- CODEX_TMP="/tmp/codex-$(pwd | sha1sum | cut -c1-8)"; mkdir -p "$CODEX_TMP" # run-unique per worktree cwd: STABLE across round-1+resume turns, collision-free under parallel sessions
108
- codex exec resume "$THREAD_ID" -c sandbox_mode="read-only" --json \
109
- -o $CODEX_TMP/verdict.txt \
110
- "I revised the plan. Re-review PLAN.md. Same rules. End with VERDICT: APPROVED or VERDICT: REVISE." \
111
- < /dev/null 2>/dev/null >/dev/null &
97
+ if ROUND_RESULT=$(scripts/codex-exec.sh resume "$RUN_ID" --prompt "I revised the plan. Re-review PLAN.md. Same rules. End with VERDICT: APPROVED or VERDICT: REVISE."); then
98
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
99
+ else
100
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT" --run-id "$RUN_ID") || :
101
+ printf '%s\n' "$FAILURE_RESULT" >&2
102
+ exit 1
103
+ fi
112
104
  ```
113
- Wrap resume in the **same 90s liveness probe** (background + `wait`). Resume discards the `--json` stream, so probe on the verdict file: `BYTES=$(wc -c < $CODEX_TMP/verdict.txt)` plus the `CPU` check — `00:00:00` CPU + empty verdict at 90s → kill, treat as `CODEX-HUNG`, same STOP path as round 1.
114
105
 
115
- **Overall ceiling (both rounds):** the 90s probe catches silent hangs, not long stuck runs. Cap every `codex exec` / `codex exec resume` at **10 minutes** — via Claude Code's Bash tool pass `timeout: 600000` on the tool call (the default 2-minute tool timeout would kill real reviews mid-run); in a plain shell prefix `timeout 600` (macOS: `gtimeout 600` via coreutils). If the ceiling trips, treat it as a failed round: stop and tell the user rather than retrying blind.
106
+ The wrapper owns stdin closure, hang detection, the overall timeout, stderr
107
+ redaction, and its process group. `handle-failure` surfaces the structured
108
+ status and cleanup metadata, then the caller stops before report handling. A
109
+ surfaced `HUNG` returns to the user for the choice to retry once with a fresh
110
+ run or continue without cross-model review. Never inspect, signal, or kill
111
+ foreign Codex processes.
112
+
113
+ Separately, cancellation or a decision to stop orchestration after an OK round
114
+ but before normal finalize must abort the known run:
116
115
 
117
- Both `codex exec` and `codex exec resume` support `--json` (stream → parse `thread_id` first round) and `-o/--output-last-message` (verdict capture).
116
+ ```bash
117
+ scripts/codex-exec.sh abort "$RUN_ID"
118
+ ```
118
119
 
119
120
  **Each round, after Codex returns:**
120
- 1. Read `$CODEX_TMP/verdict.txt`. Append to `LOG_FILE`: `## Round <n> — Codex` + the full critique.
121
+ 1. Read `CODEX_REPORT`. Append to `LOG_FILE`: `## Round <n> — Codex` + the full critique.
121
122
  2. Grep the last line for the verdict token.
122
123
  - `VERDICT: APPROVED` → break the loop, go to Step 3 (converged).
123
124
  - `VERDICT: REVISE` → Claude reads the critique, decides **what's actually worth acting on** (Claude has final say — Codex advises, it does not command). Revise `PLAN_FILE`. Append to `LOG_FILE`: `### Claude's response` + what you changed and what you rejected and why. Increment `ROUND`.
124
125
  3. If `ROUND > MAX_ROUNDS` → break to Step 3 (deadlock).
125
126
 
127
+ After harvesting the final report and updating the log, delete the successful
128
+ run state:
129
+
130
+ ```bash
131
+ scripts/codex-exec.sh finalize "$RUN_ID"
132
+ ```
133
+
126
134
  ### Step 3 — Resolution (human gate #2)
127
135
 
128
136
  **If APPROVED:** Present to the user — the final `PLAN_FILE`, a 3-bullet summary of what the argument improved, and the round count. Ask: *"Plan survived N rounds of Codex. Implement it now — Codex builds it (`/codex-build`), Claude builds it, or stop here?"* Only on a yes is code written. **No code is written during the loop.** If the user picks Codex, invoke the `codex-build` skill with `SPEC_FILE=PLAN.md` and the same `LOG_FILE` — roles flip (Codex writes in a bounded workspace-write sandbox, Claude reviews the diff) and the build rounds append to the same log.
@@ -131,7 +139,9 @@ Both `codex exec` and `codex exec resume` support `--json` (stream → parse `th
131
139
 
132
140
  ## Hard rules
133
141
 
134
- - Codex is read-only EVERY round `-s read-only` for the first call, `-c sandbox_mode="read-only"` for every resume (resume has no `-s`). It never writes. If you're tempted to give it write access, stop — that's a different skill.
142
+ - Codex is read-only EVERY round. Establish that once with the wrapper's
143
+ `review` + `read-only` new call; every resume inherits it. If you're tempted
144
+ to give it write access, stop — that's a different skill.
135
145
  - The loop ALWAYS terminates at `MAX_ROUNDS`. No unbounded recursion.
136
146
  - Claude is the final arbiter on every REVISE — incorporate good critiques, reject bad ones *with a reason logged*. Don't cave to Codex on everything (that defeats the cross-model check) and don't ignore it (that defeats the point).
137
147
  - Code only after human gate #2.