@ikon85/agent-workflow-kit 0.28.0 → 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.
- package/.agents/skills/board-to-waves/SKILL.md +13 -3
- package/.agents/skills/to-issues/SKILL.md +102 -27
- package/.agents/skills/to-prd/SKILL.md +13 -3
- package/.agents/skills/to-waves/SKILL.md +16 -4
- package/.agents/skills/wrapup/SKILL.md +0 -1
- package/.claude/skills/board-to-waves/SKILL.md +13 -3
- package/.claude/skills/codex-build/SKILL.md +69 -23
- package/.claude/skills/codex-review/SKILL.md +47 -37
- package/.claude/skills/grill-me-codex/SKILL.md +45 -34
- package/.claude/skills/grill-with-docs-codex/SKILL.md +46 -34
- package/.claude/skills/to-issues/SKILL.md +102 -27
- package/.claude/skills/to-prd/SKILL.md +13 -3
- package/.claude/skills/to-waves/SKILL.md +16 -4
- package/.claude/skills/wrapup/SKILL.md +0 -1
- package/README.md +23 -0
- package/agent-workflow-kit.package.json +56 -16
- package/docs/research/wave-152-consumer-acceptance.md +98 -0
- package/package.json +1 -1
- package/scripts/board-sync.py +3 -7
- package/scripts/build-kit.test.mjs +42 -1
- package/scripts/codex-exec-scenarios/fake-codex.mjs +152 -0
- package/scripts/codex-exec.sh +566 -0
- package/scripts/codex-exec.test.mjs +815 -0
- package/scripts/codex_proc.py +602 -0
- package/scripts/find-by-marker.py +68 -0
- package/scripts/marker_lib.py +88 -0
- package/scripts/render-anchor.py +111 -0
- package/scripts/test_marker_lib.py +154 -0
- package/scripts/test_render_anchor.py +267 -0
- package/scripts/test_retro_wrapup_contract.py +6 -0
- package/scripts/test_skill_codex_exec_lifecycle.py +123 -0
- 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
|
|
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
|
-
|
|
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.
|
|
147
|
-
# body header `**Welle $WAVE — <
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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:**
|
|
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
|
-
|
|
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.**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
17
|
-
-
|
|
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
|
|
21
|
-
- Run from the target working directory's root
|
|
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
|
|
68
|
+
## Step 2 — Launch Codex (fresh wrapper-owned session)
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
|
-
codex
|
|
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
|
-
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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` +
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
codex
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
107
|
-
|
|
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
|
-
- **
|
|
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
|
|
125
|
-
|
|
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
|
-
-
|
|
15
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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**
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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**
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
codex
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
```bash
|
|
117
|
+
scripts/codex-exec.sh abort "$RUN_ID"
|
|
118
|
+
```
|
|
118
119
|
|
|
119
120
|
**Each round, after Codex returns:**
|
|
120
|
-
1. Read
|
|
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
|
|
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.
|