@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
@@ -41,7 +41,7 @@ For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-sta
41
41
 
42
42
  When the decision tree is resolved and we're aligned, **write the agreed plan to `PLAN.md`** in this structure, then move to Act 2:
43
43
 
44
- > **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.
44
+ > **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.
45
45
 
46
46
  ```markdown
47
47
  # Plan: <task>
@@ -76,8 +76,9 @@ Act 1 (grill) complete — plan locked with the user. MAX_ROUNDS=<n>.
76
76
  Now hand the locked plan to Codex for adversarial review. Same engine, mechanics verified end-to-end (2026-06-04).
77
77
 
78
78
  ### Prerequisites (verify once, fast)
79
- - `codex --version` 0.130 (older CLIs error on the default `gpt-5.5` model).
80
- - Codex authenticated (prior `codex login`; ChatGPT account is fine). On auth/model error, surface it — don't silently retry.
79
+ - Let `scripts/codex-exec.sh` preflight Codex before launch. It enforces the
80
+ exact tested-version allowlist, authentication, platform, and capabilities;
81
+ surface any failure rather than retrying silently.
81
82
  - Do NOT pin `-m`. Use the config default. Pinning `gpt-5.x-codex` variants 400s on ChatGPT-account auth.
82
83
  - **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.
83
84
 
@@ -93,50 +94,59 @@ If invoked with e.g. `rounds=3`, use that for `MAX_ROUNDS`. Echo resolved values
93
94
  ### The review prompt (sent each round)
94
95
  > 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.
95
96
 
96
- ### Round 1 — fresh session (capture `thread_id`)
97
- 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.
97
+ ### Round 1 — fresh wrapper-owned session
98
+
98
99
  ```bash
99
- 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
100
- codex exec -s read-only --json -o $CODEX_TMP/verdict.txt "$(cat REVIEW_PROMPT)" \
101
- < /dev/null > $CODEX_TMP/r1.jsonl 2>/dev/null &
102
- CODEX_PID=$!
103
- sleep 90 # liveness probe (REQUIRED)
104
- if kill -0 "$CODEX_PID" 2>/dev/null; then
105
- CPU=$(ps -o time= -p "$CODEX_PID" 2>/dev/null | tr -dc '0-9:') # cumulative CPU, e.g. 00:00:00
106
- BYTES=$(wc -c < $CODEX_TMP/r1.jsonl 2>/dev/null || echo 0)
107
- if [ "${CPU:-00:00:00}" = "00:00:00" ] && [ "${BYTES:-0}" -eq 0 ]; then
108
- kill -9 "$CODEX_PID" 2>/dev/null; echo "CODEX-HUNG" # alive + 0 CPU + 0 bytes = blocked, NOT working
109
- fi
100
+ if ROUND_RESULT=$(scripts/codex-exec.sh new --profile review --mode read-only --prompt "$REVIEW_PROMPT"); then
101
+ RUN_ID=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["runId"])')
102
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
103
+ else
104
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT") || :
105
+ printf '%s\n' "$FAILURE_RESULT" >&2
106
+ exit 1
110
107
  fi
111
- wait "$CODEX_PID" 2>/dev/null
112
- THREAD_ID=$(grep -o '"thread_id":"[^"]*"' $CODEX_TMP/r1.jsonl | head -1 | cut -d'"' -f4)
113
108
  ```
114
- - **`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 Act 2**: tell the user, offer to sign off without the cross-model review or retry once. Don't touch other codex processes.
115
- - **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.
116
- - **Clean finish but no verdict file + no `THREAD_ID`** = auth/model failure → stop, tell the user.
117
109
 
118
- ### Rounds 2..MAX — resume the SAME session (Codex remembers its prior critiques)
110
+ ### Rounds 2..MAX — resume the SAME session
111
+
119
112
  ```bash
120
- # resume REJECTS -s. Force read-only via -c sandbox_mode, or Codex inherits
121
- # config.toml (possibly danger-full-access) and could WRITE files. This is the
122
- # single most important safety line in the skill — verified 2026-06-04.
123
- 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
124
- codex exec resume "$THREAD_ID" -c sandbox_mode="read-only" --json \
125
- -o $CODEX_TMP/verdict.txt \
126
- "I revised the plan. Re-review PLAN.md — check whether your prior findings are addressed and flag anything new. End with VERDICT: APPROVED or VERDICT: REVISE." \
127
- < /dev/null 2>/dev/null >/dev/null &
113
+ if ROUND_RESULT=$(scripts/codex-exec.sh resume "$RUN_ID" --prompt "I revised the plan. Re-review PLAN.md check whether your prior findings are addressed and flag anything new. End with VERDICT: APPROVED or VERDICT: REVISE."); then
114
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
115
+ else
116
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT" --run-id "$RUN_ID") || :
117
+ printf '%s\n' "$FAILURE_RESULT" >&2
118
+ exit 1
119
+ fi
128
120
  ```
129
- 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. Both `codex exec` and `codex exec resume` support `--json` and `-o/--output-last-message`.
130
121
 
131
- **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.
122
+ The wrapper persists read-only mode and owns stdin closure, hang detection, the
123
+ overall timeout, stderr redaction, and its process group. `handle-failure`
124
+ surfaces the structured status and cleanup metadata, then the caller stops
125
+ before report handling. A surfaced `HUNG` returns to the user for the choice to
126
+ retry once with a fresh run or sign off without cross-model review. Never
127
+ inspect, signal, or kill foreign Codex processes.
128
+
129
+ Separately, cancellation or a decision to stop orchestration after an OK round
130
+ but before normal finalize must abort the known run:
131
+
132
+ ```bash
133
+ scripts/codex-exec.sh abort "$RUN_ID"
134
+ ```
132
135
 
133
136
  ### Each round, after Codex returns
134
- 1. Read `$CODEX_TMP/verdict.txt`; append to `LOG_FILE`: `## Round <n> — Codex` + the full critique.
137
+ 1. Read `CODEX_REPORT`; append to `LOG_FILE`: `## Round <n> — Codex` + the full critique.
135
138
  2. Grep the last line for the verdict:
136
139
  - `VERDICT: APPROVED` → break to Resolution (converged).
137
140
  - `VERDICT: REVISE` → Claude decides **what's actually worth acting on** (Claude is final arbiter — Codex advises, doesn't command). Revise `PLAN_FILE`. Append `### Claude's response` to `LOG_FILE`: what changed, what was rejected, why. Increment round.
138
141
  3. If round > `MAX_ROUNDS` → break to Resolution (deadlock).
139
142
 
143
+ After harvesting the final report and updating the log, delete the successful
144
+ run state:
145
+
146
+ ```bash
147
+ scripts/codex-exec.sh finalize "$RUN_ID"
148
+ ```
149
+
140
150
  ### Resolution (you sign off — final gate)
141
151
  - **APPROVED:** present the final `PLAN_FILE`, a 3-bullet summary of what the two acts improved, and the round count. Ask: *"Grilled + survived N rounds of Codex. Implement it now — Codex builds it (`/codex-build`), Claude builds it, or stop here?"* Code only on a yes. **No code is written during either act.**
142
152
  - **MAX_ROUNDS hit without APPROVED (deadlock):** do NOT fake convergence. List each unresolved point + Claude's counter-position; hand it to the user to break the tie. A flagged disagreement beats a false "approved."
@@ -149,7 +159,8 @@ If the user picks Codex: invoke the `codex-build` skill with `SPEC_FILE=PLAN.md`
149
159
 
150
160
  ## Hard rules
151
161
  - Act 1 always precedes Act 2 — don't write `PLAN.md` until the grill has actually resolved the decision tree with the user.
152
- - Codex is read-only EVERY round — `-s read-only` first call, `-c sandbox_mode="read-only"` on every resume (resume has no `-s`). It never writes.
162
+ - Codex is read-only EVERY round — establish it once through the wrapper's
163
+ `review` + `read-only` new call; every resume inherits it.
153
164
  - The loop ALWAYS terminates at `MAX_ROUNDS`.
154
165
  - Claude is final arbiter on every REVISE — incorporate good critiques, reject bad ones *with a logged reason*. Don't cave to everything (defeats the cross-model check) and don't ignore it (defeats the point).
155
166
  - Code only after the user's final sign-off.
@@ -125,7 +125,7 @@ For a cross-cutting plan, run `python3 .claude/hooks/drift-guard.py --census-sta
125
125
 
126
126
  When the decision tree is resolved, the glossary/ADRs are updated, and we're aligned, **write the agreed plan to `PLAN.md`** (use the canonical terms from `CONTEXT.md`), then run Act 2:
127
127
 
128
- > **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.
128
+ > **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.
129
129
 
130
130
  ```markdown
131
131
  # Plan: <task>
@@ -160,8 +160,9 @@ Act 1 (grill-with-docs) complete — plan locked, CONTEXT.md/ADRs updated. MAX_R
160
160
  Hand the locked plan to Codex for adversarial review. Mechanics verified end-to-end (2026-06-04).
161
161
 
162
162
  ### Prerequisites
163
- - `codex --version` 0.130 (older CLIs error on the default `gpt-5.5` model).
164
- - Codex authenticated (`codex login`; ChatGPT account fine). On auth/model error, surface it — don't silently retry.
163
+ - Let `scripts/codex-exec.sh` preflight Codex before launch. It enforces the
164
+ exact tested-version allowlist, authentication, platform, and capabilities;
165
+ surface any failure rather than retrying silently.
165
166
  - Do NOT pin `-m` (config default is used; `gpt-5.x-codex` variants 400 on ChatGPT-account auth).
166
167
  - **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.
167
168
 
@@ -177,48 +178,57 @@ Invoked with e.g. `rounds=3` → use it. Echo resolved values first.
177
178
  ### Review prompt (each round)
178
179
  > 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 `CONTEXT.md`/ADRs for the domain language) and any repo files you need (you are read-only). Identify concrete flaws: security holes, race conditions, missing edge cases, schema conflicts, domain-language mismatches, wrong assumptions, observability gaps, simpler alternatives. For each, give a one-line fix. Do NOT modify any files. End with EXACTLY one line: `VERDICT: APPROVED` or `VERDICT: REVISE`.
179
180
 
180
- ### Round 1 — fresh session (capture `thread_id`)
181
- 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). Launch in the background so a **90s liveness probe** still catches a genuine sandbox deadlock.
181
+ ### Round 1 — fresh wrapper-owned session
182
+
182
183
  ```bash
183
- 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
184
- codex exec -s read-only --json -o $CODEX_TMP/verdict.txt "$(cat REVIEW_PROMPT)" \
185
- < /dev/null > $CODEX_TMP/r1.jsonl 2>/dev/null &
186
- CODEX_PID=$!
187
- sleep 90 # liveness probe (REQUIRED)
188
- if kill -0 "$CODEX_PID" 2>/dev/null; then
189
- CPU=$(ps -o time= -p "$CODEX_PID" 2>/dev/null | tr -dc '0-9:') # cumulative CPU, e.g. 00:00:00
190
- BYTES=$(wc -c < $CODEX_TMP/r1.jsonl 2>/dev/null || echo 0)
191
- if [ "${CPU:-00:00:00}" = "00:00:00" ] && [ "${BYTES:-0}" -eq 0 ]; then
192
- kill -9 "$CODEX_PID" 2>/dev/null; echo "CODEX-HUNG" # alive + 0 CPU + 0 bytes = blocked, NOT working
193
- fi
184
+ if ROUND_RESULT=$(scripts/codex-exec.sh new --profile review --mode read-only --prompt "$REVIEW_PROMPT"); then
185
+ RUN_ID=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["runId"])')
186
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
187
+ else
188
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT") || :
189
+ printf '%s\n' "$FAILURE_RESULT" >&2
190
+ exit 1
194
191
  fi
195
- wait "$CODEX_PID" 2>/dev/null
196
- THREAD_ID=$(grep -o '"thread_id":"[^"]*"' $CODEX_TMP/r1.jsonl | head -1 | cut -d'"' -f4)
197
192
  ```
198
- - **`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 (verified 2026-06-09). **NEVER `pgrep`/`kill` codex procs to "clear contention"** — that murders the user's live, unrelated codex sessions and does **not** fix a stdin hang. If `< /dev/null` is already present and it still hangs (genuine sandbox deadlock) → **STOP Act 2**: append the hang to `LOG_FILE`, tell the user, and offer to (a) proceed to sign-off **without** the cross-model review, or (b) retry once more. Do **not** keep waiting minutes, and do **not** touch other codex processes.
199
- - **Healthy:** CPU climbs past `00:00:00` and/or `$CODEX_TMP/r1.jsonl` grows; `THREAD_ID` parses; critique lands in `$CODEX_TMP/verdict.txt`.
200
- - **Clean finish but no verdict file + no `THREAD_ID`** = auth/model failure → stop, tell the user. `2>/dev/null` hides cosmetic MCP/auth noise.
201
193
 
202
194
  ### Rounds 2..MAX — resume SAME session
195
+
203
196
  ```bash
204
- # resume REJECTS -s. Force read-only via -c sandbox_mode, or Codex inherits
205
- # config.toml (possibly danger-full-access) and could WRITE files. Critical
206
- # safety line — verified 2026-06-04.
207
- 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
208
- codex exec resume "$THREAD_ID" -c sandbox_mode="read-only" --json \
209
- -o $CODEX_TMP/verdict.txt \
210
- "I revised the plan. Re-review PLAN.md — check prior findings + flag anything new. End with VERDICT: APPROVED or VERDICT: REVISE." \
211
- < /dev/null 2>/dev/null >/dev/null &
197
+ if ROUND_RESULT=$(scripts/codex-exec.sh resume "$RUN_ID" --prompt "I revised the plan. Re-review PLAN.md check prior findings + flag anything new. End with VERDICT: APPROVED or VERDICT: REVISE."); then
198
+ CODEX_REPORT=$(printf '%s\n' "$ROUND_RESULT" | python3 -c 'import json,sys; print(json.load(sys.stdin)["verdict"])')
199
+ else
200
+ FAILURE_RESULT=$(scripts/codex-exec.sh handle-failure --result "$ROUND_RESULT" --run-id "$RUN_ID") || :
201
+ printf '%s\n' "$FAILURE_RESULT" >&2
202
+ exit 1
203
+ fi
212
204
  ```
213
- Wrap resume in the **same 90s liveness probe** (background + `wait`). Resume discards the `--json` stream, so probe on the verdict file instead: `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.
214
205
 
215
- **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.
206
+ The wrapper persists read-only mode and owns stdin closure, hang detection, the
207
+ overall timeout, stderr redaction, and its process group. `handle-failure`
208
+ surfaces the structured status and cleanup metadata for `LOG_FILE`, then the
209
+ caller stops before report handling. A surfaced `HUNG` returns to the user for
210
+ the choice to retry once with a fresh run or proceed without cross-model
211
+ review. Never inspect, signal, or kill foreign Codex processes.
212
+
213
+ Separately, cancellation or a decision to stop orchestration after an OK round
214
+ but before normal finalize must abort the known run:
215
+
216
+ ```bash
217
+ scripts/codex-exec.sh abort "$RUN_ID"
218
+ ```
216
219
 
217
220
  ### Each round
218
- 1. Read verdict file; append `## Round <n> — Codex` + critique to `LOG_FILE`.
221
+ 1. Read `CODEX_REPORT`; append `## Round <n> — Codex` + critique to `LOG_FILE`.
219
222
  2. Last line verdict: `APPROVED` → Resolution (converged); `REVISE` → Claude decides what's worth acting on (final arbiter), revise `PLAN_FILE`, append `### Claude's response` (what changed/rejected + why), increment.
220
223
  3. round > `MAX_ROUNDS` → Resolution (deadlock).
221
224
 
225
+ After harvesting the final report and updating the log, delete the successful
226
+ run state:
227
+
228
+ ```bash
229
+ scripts/codex-exec.sh finalize "$RUN_ID"
230
+ ```
231
+
222
232
  ### Resolution (you sign off)
223
233
  - **APPROVED:** present final plan + 3-bullet summary of what the two acts improved + round count. Ask: implement now — Codex builds it (`/codex-build`), Claude builds it, or stop? No code during either act.
224
234
  - **Deadlock (cap hit, no APPROVED):** list unresolved points + Claude's counter-position; hand to user. Don't fake convergence.
@@ -228,9 +238,11 @@ Wrap resume in the **same 90s liveness probe** (background + `wait`). Resume dis
228
238
 
229
239
  ## Hard rules
230
240
  - Act 1 precedes Act 2. `CONTEXT.md` stays a glossary only — no implementation details.
231
- - Codex read-only EVERY round (`-s read-only` first, `-c sandbox_mode="read-only"` on resume — resume has no `-s`). Never writes.
241
+ - Codex is read-only EVERY round establish it once through the wrapper's
242
+ `review` + `read-only` new call; every resume inherits it.
232
243
  - Loop ALWAYS terminates at `MAX_ROUNDS`. Claude is final arbiter on REVISE (reject with logged reason). Code only after sign-off. `LOG_FILE` is the deliverable.
233
- - EVERY codex round runs behind the **90s liveness probe** (background + CPU/output check). Never let a silent codex hang burn minutes — kill at 90s if 0 CPU + 0 output, surface it, give the user the proceed-without / retry choice.
244
+ - A `HUNG` wrapper result always returns to the user for the proceed-without or
245
+ retry-once choice.
234
246
 
235
247
  ## What NOT to do
236
248
  - Don't review already-written code (`/codex:review`). Don't pin `-codex` variants on ChatGPT auth. Don't let Codex edit files. Don't skip Act 1.
@@ -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
@@ -89,5 +89,4 @@ STOP → diagnose in the main conversation, fix, re-run `land` (an already-merge
89
89
 
90
90
  ## Out of scope
91
91
  - Live-verify / DoD: must happen **before** `/wrapup` — this skill lands, it does not verify.
92
- - `/retro`: offered before landing (step 2), never run by this skill.
93
92
  - Other worktrees / their servers stay untouched.
package/README.md CHANGED
@@ -348,6 +348,33 @@ concurrency-safe. Do not run manifest-mutating commands concurrently. Flags:
348
348
 
349
349
  ## Release notes
350
350
 
351
+ ### 0.29.1
352
+
353
+ - Metadata-only release.
354
+
355
+ ### 0.29.0
356
+
357
+ - added: `scripts/codex-exec.sh`
358
+ - added: `scripts/codex_proc.py`
359
+ - added: `scripts/find-by-marker.py`
360
+ - added: `scripts/marker_lib.py`
361
+ - added: `scripts/render-anchor.py`
362
+ - changed: `.agents/skills/board-to-waves/SKILL.md`
363
+ - changed: `.agents/skills/to-issues/SKILL.md`
364
+ - changed: `.agents/skills/to-prd/SKILL.md`
365
+ - changed: `.agents/skills/to-waves/SKILL.md`
366
+ - changed: `.agents/skills/wrapup/SKILL.md`
367
+ - changed: `.claude/skills/board-to-waves/SKILL.md`
368
+ - changed: `.claude/skills/codex-build/SKILL.md`
369
+ - changed: `.claude/skills/codex-review/SKILL.md`
370
+ - changed: `.claude/skills/grill-me-codex/SKILL.md`
371
+ - changed: `.claude/skills/grill-with-docs-codex/SKILL.md`
372
+ - changed: `.claude/skills/to-issues/SKILL.md`
373
+ - changed: `.claude/skills/to-prd/SKILL.md`
374
+ - changed: `.claude/skills/to-waves/SKILL.md`
375
+ - changed: `.claude/skills/wrapup/SKILL.md`
376
+ - changed: `scripts/board-sync.py`
377
+
351
378
  ### 0.28.0
352
379
 
353
380
  - added: `.claude/hooks/kit-origin-edit-hint.py`