Plan-to-Dex Runner
Translate a Superpowers implementation plan into a dex-compatible plan.md, import it, and run dex's autonomous loop (apply → review) end to end with codex as the fixed backend.
The plan is the source of truth. Do NOT re-interview the user, regenerate requirements, or let dex re-plan via dex plan.
Backend is fixed to codex: the skill never asks which backend and never writes .dex/config.json. apply runs gpt-5.6-luna at high, review runs gpt-5.6-sol at high — two codex-<model>-<effort> entries Step 4 provisions automatically, so neither phase inherits from ~/.codex/config.toml. Either phase can be re-pointed with $CODEX_MODEL_* / $CODEX_EFFORT_* (Step 4 provisions the derived entry) or at an entry you named yourself ($DEX_CLI_BUILD / $DEX_CLI_REVIEW, which wins) — see Step 6 and docs/codex-tuning.md.
Prerequisite — dex apply cannot run under Claude Code's auto mode without an explicit Bash allowlist entry. Every backend this skill provisions runs codex exec --yolo --ephemeral: a coding-agent loop with its approval gate disabled, which spawns further agents. Claude Code's auto-mode security classifier refuses commands of that shape under the category [Create Unsafe Agents], and the refusal is broad — a live run had dex --cli codex apply, the provisioning write in Step 4, and even a bare cat ~/.codex/config.toml issued next to a dex command all denied in the same session. Under permissions.defaultMode: auto, the operator must allowlist the dex commands in that workspace's Claude Code settings before this skill can complete. This skill names the requirement and does not recommend an entry to satisfy it. What the classifier is refusing is a real thing to refuse — an approval-gate-disabled agent loop, on hosts where an autonomous pipeline may hold push rights — so whether to open that gate, and how narrowly, is the operator's security decision, and the settings it lives in are theirs, not this repo's. Step 4 probes for the refusal so a run that cannot finish stops in seconds instead of after a full translation pass.
A classifier refusal is not a confirmation gate. They look alike from inside the session and are opposites: a confirmation gate is a question this skill asked, and an autonomous caller answering it yes is exactly right (/ship-it does). A classifier refusal is the harness declining to run the command — there is no prompt, no yes to give, and no amount of proceeding-anyway makes it execute. An agent told to "treat every confirmation gate as an answered yes" must not read a denial as one of those: report it, name the remediation, and stop. Retrying it in another phrasing is the failure mode to avoid — that is working around a security control, not clearing a gate.
The Job
- Locate and validate the implementation plan
- Translate plan tasks into a dex checkbox-group
plan.md - Preflight (dex + codex on PATH; classifier probe; verify entitlement and provision the two
codex-<model>-<effort>backends; branch guard) - One confirmation before the autonomous chain
- Run
dex import→dex --cli "${DEX_CLI_BUILD:-codex-${CODEX_MODEL_BUILD:-gpt-5.6-luna}-${CODEX_EFFORT_BUILD:-high}}" apply→dex --cli "${DEX_CLI_REVIEW:-codex-${CODEX_MODEL_REVIEW:-gpt-5.6-sol}-${CODEX_EFFORT_REVIEW:-high}}" review - Show the handoff report
Output file: tasks/dex-plan.md — the translated plan, then installed by dex import into .dex/plan.md.
Step 1: Locate the Plan
- If the user provided a file path as argument, use it.
- Otherwise, scan for the most recent plan file by date prefix (
YYYY-MM-DD), in this order:docs/superpowers/plans/(default forsuperpowers:writing-plans≥ 5.1.0)docs/plans/(legacy location) MatchYYYY-MM-DD-*.md(do NOT match*-design.md— those are design docs).
- If no plan found: STOP — "No implementation plan found. Run /superpowers:writing-plans first."
Read the plan file. 5.1.0+ plans are self-contained — the header carries **Goal:**, **Architecture:**, and **Tech Stack:**. A companion design doc is optional and not required here.
Header callout to ignore: 5.1.0+ plans begin with a blockquote > **For agentic workers:** REQUIRED SUB-SKILL:. It is metadata, not a task — skip it.
Step 2: Validate the Plan
Verify the plan contains:
- A
**Goal:**line (5.1.0 header) or## Goalsection (legacy) - At least one task heading:
### Task N: [Component Name](accept## Task N:too) - A
**Files:**block per task withCreate:/Modify:/Test:bullets (older plans may list paths inline — accept either) - Per-step verification:
- [ ] **Step N:**checkboxes withRun:/Expected:lines, OR a fenced bash block followed by anExpected:paragraph
If validation fails, list what is missing and ask whether to proceed. Never invent requirements to fill gaps.
Step 3: Translate to dex plan.md
Read the template at ${CLAUDE_PLUGIN_ROOT}/skills/plan-to-dex/templates/dex-plan.md and write the result to tasks/dex-plan.md (mkdir -p tasks first).
Granularity rule: one source ### Task N: Component = one dex ### Task N: Component heading = one dex iteration. dex hands the entire group body (heading + checkboxes + prose) to codex at once. The 5 TDD sub-steps inside a task become checkboxes under that heading — never separate headings.
For each source Task, emit one group following this mapping:
| Source plan element | → dex plan.md |
|---|---|
### Task N: Component heading |
### Task N: Component heading (one group = one iteration) |
| TDD sub-steps (test→fail→impl→pass→commit) | - [ ] checkboxes under that heading |
**Files:** block |
preserved as a **Files:** prose line (codex context) |
Run: / Expected: lines |
folded into the relevant checkbox text |
| Detected quality gates | a - [ ] Quality gates: <cmds> pass checkbox per task |
[manual] verification step |
- [ ] [manual] ... checkbox, surfaced in the confirmation |
Plan **Goal:** |
the ## Overview context group (no checkbox) |
Example of one emitted group:
### Task 3: Auth guard
**Files:** Create `src/auth/guard.ts`, Modify `src/router.ts`, Test `tests/auth/guard.test.ts`
- [ ] Write failing test in `tests/auth/guard.test.ts` — `pnpm test tests/auth/guard.test.ts` should FAIL
- [ ] Implement guard in `src/auth/guard.ts` so the test passes
- [ ] Verify `pnpm test tests/auth/guard.test.ts` PASSES
- [ ] Quality gates: `pnpm typecheck` and `pnpm lint` pass
- [ ] Commit
Quality-gate detection
Detect project quality tooling and append a Quality gates: checkbox to every task:
| File | Check for | Quality-gate commands |
|---|---|---|
package.json |
typecheck, lint, test scripts |
pnpm typecheck, pnpm lint, … |
Makefile |
test, lint, typecheck targets |
make typecheck, … |
pyproject.toml |
pytest, ruff, mypy |
pytest, ruff check, … |
Cargo.toml |
— | cargo test, cargo clippy |
If no tooling is detected, ask: "What commands must pass for every task?" Do not write a top-level array of gate commands — dex has no equivalent; codex just runs the commands named in the checkbox.
Manual criteria
If a verification step requires human judgment ("inspect the output", "if WARN lines appear…"), emit it as - [ ] [manual] <text> and list every [manual] checkbox in the Step 5 confirmation, so the user knows codex will tick it without true verification.
Step 4: Preflight Checks
dexon PATH:command -v dex— else STOP: "dex not found. Install:curl -sSfL https://raw.githubusercontent.com/francescoalemanno/dex/main/install.sh | bash".codexon PATH:command -v codex— else STOP: "codex CLI not found; this skill runs dex with the codex backend."Classifier probe — run this before anything expensive. The command below is a no-op that prints usage and exits 0, but it has the exact shape the auto-mode classifier objects to (
dex --cli codex-… apply), so it trips the same denial as the real run:dex --cli "${DEX_CLI_BUILD:-codex-${CODEX_MODEL_BUILD:-gpt-5.6-luna}-${CODEX_EFFORT_BUILD:-high}}" apply --helpRead the kind of failure, not just the failure:
What came back Meaning Do usage text, exit 0 the gate is open continue the tool call was denied by the harness — a permission/policy refusal, no shell exit code, wording about unsafe agents the auto-mode classifier is blocking this skill STOP non-zero shell exit with dex's own stderr a dex problem, not a permission one fix per checks 1–2 and 4 On the classifier STOP, report exactly this and nothing else:
dex applyis blocked by this session's Claude Code auto-mode security classifier ([Create Unsafe Agents]) — it refuses commands that run a codex backend withexec --yolo --ephemeral. This is not a confirmation gate; there is no way to answer it from inside the session. To proceed, an operator must allowlist the dex commands in this workspace's Claude Code Bash permissions, or rundex import/dex apply/dex reviewmanually outside the sandboxed session. No plan was imported and nothing was run.Name it as a permission denial, never as a timeout, a dex bug, or a missing binary — those have entirely different remedies, and a run that reports the wrong one sends the operator to the wrong file. Do not rephrase the command and try again, and do not route around it by inlining the
codex exec --yolocall yourself: the classifier is refusing the loop, not the spelling.--cliflag form:dex --cli codex --helpshould exit 0 — confirms--cliis accepted as a global option (the form Step 6 uses). If this fails, a future dex version may have moved--cliunder the subcommand; rundex --helpto check and adjust the Step 6 commands accordingly. (Validated against dex 0.4.9, where--cliis global.)Provision the model+effort-pinned backends (idempotent — a no-op on every run after the first). dex has neither a
--modelnor an effort flag, so a phase can only run on a pinned model at a pinned effort by naming aclisentry that carries both. Each phase resolves tocodex-<model>-<effort>; provision those two entries, add them if missing, never overwrite one that exists — and verify entitlement first, because a pinned model is the one thing that can be wrong on a machine that never opted in:DEX_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/dex/config.json" MC="$HOME/.codex/models_cache.json" mkdir -p "$(dirname "$DEX_CFG")" [ -s "$DEX_CFG" ] || echo '{}' > "$DEX_CFG" for slot in "${CODEX_MODEL_BUILD:-gpt-5.6-luna}:${CODEX_EFFORT_BUILD:-high}" \ "${CODEX_MODEL_REVIEW:-gpt-5.6-sol}:${CODEX_EFFORT_REVIEW:-high}"; do model="${slot%:*}"; effort="${slot#*:}"; cli="codex-$model-$effort" if [ -s "$MC" ] && ! jq -e --arg m "$model" --arg e "$effort" \ '.models[] | select(.slug==$m) | [.supported_reasoning_levels[].effort] | index($e)' "$MC" >/dev/null 2>&1; then echo "UNAVAILABLE: '$model' at effort '$effort' — entitled: $(jq -r '[.models[].slug] | join(", ")' "$MC")" continue fi jq -e --arg c "$cli" '.clis[$c]' "$DEX_CFG" >/dev/null 2>&1 || { tmp=$(mktemp) jq --arg c "$cli" --arg m "$model" --arg e "$effort" \ '.clis[$c] = {command:"codex", args:["exec","--yolo","--ephemeral","--json","-m",$m,"-c","model_reasoning_effort=\($e)"], stdin:true, env:{}, output_format:"json_nd"}' \ "$DEX_CFG" > "$tmp" && mv "$tmp" "$DEX_CFG" \ && echo "provisioned dex backend '$cli' in $DEX_CFG" } doneIf this write is refused by the harness rather than by
jq, it is check 3's classifier again — the block it writes containsexec --yolo --ephemeralverbatim. Same STOP, same report; do not hand-editconfig.jsonaround it.With nothing set this provisions
codex-gpt-5.6-luna-highfor apply andcodex-gpt-5.6-sol-highfor review. AnyUNAVAILABLEline → STOP and report it with the entitled list: the entry is deliberately not written, so continuing would only reach dex'sunknown CLI(which exits 0) several minutes later.The write is additive: jq creates
.clisif absent and leaves every other key (the user'stimeout,clidefault, and their own entries) untouched. Thejq -eguard means a user who has already defined the entry keeps their version. Mention it in the Step 5 confirmation if aprovisionedline printed — it edits a file outside the repo, so it should not be silent, but it needs no approval: additive keys in a local config, removable withjq 'del(.clis["codex-gpt-5.6-luna-high"])'.The entitlement check replaced an allowlist, because model×effort cannot be enumerated. It reads codex's own
models_cache.json, so it validates the pair this account can actually run —gpt-5.6-lunaatultrafails (luna caps atmax) even though both words are individually valid. It is skipped, not failed, when the cache is absent: that file is a cache, and treating a cold one as "unentitled" would block a machine whose model is fine. A$DEX_CLI_*name the user chose bypasses this loop entirely and is never auto-created — that entry is theirs to define, and inventing one would turn a typo into a silently-working backend.The
codex-prefix is load-bearing, not cosmetic — the Step 6 live-worker guard greps for the literaldex --cli codex. The derived names keep it (codex-gpt-5.6-luna-high). If you ever rename these entries, that guard goes blind to its own worker.Branch guard: get the current branch (
git rev-parse --abbrev-ref HEAD). If it ismainormaster, resolve a feature branch (use a name the user provided, else ask for one) andgit switch -c <name>before anydex apply.dex applyauto-commits across iterations — those commits must land on a throwaway branch, nevermain/master.
Step 5: Confirm
Present a single confirmation and wait for a yes/no:
plan-to-dex — ready to run
--------------------------
Source plan: <path>
Tasks: <N> (→ <N> dex iterations)
Backend: apply → <${DEX_CLI_BUILD:-codex-${CODEX_MODEL_BUILD:-gpt-5.6-luna}-${CODEX_EFFORT_BUILD:-high}}>
review → <${DEX_CLI_REVIEW:-codex-${CODEX_MODEL_REVIEW:-gpt-5.6-sol}-${CODEX_EFFORT_REVIEW:-high}}>
Branch: <resolved branch>
Manual criteria (codex will tick without proof):
- [manual] <text> # omit this block if none
This runs dex autonomously: it will implement and COMMIT across <N> iterations,
then run a multi-reviewer pass. Proceed? [y/N]
This is the standard confirm-before-a-hard-to-reverse-action check, NOT a plan review gate — the plan was hardened upstream. If the user declines, stop (the tasks/dex-plan.md file is already written for them to inspect).
Both backend names state their own model and effort, so the confirmation needs no extra annotation — codex-gpt-5.6-luna-high says exactly what will run. Nothing is inherited from ~/.codex/config.toml any more: both phases are pinned.
Step 6: Run the dex Chain
Run these in order, streaming output.
dex import --force tasks/dex-plan.md
dex --cli "${DEX_CLI_BUILD:-codex-${CODEX_MODEL_BUILD:-gpt-5.6-luna}-${CODEX_EFFORT_BUILD:-high}}" apply # long-running — see the poll-to-completion contract below
dex --cli "${DEX_CLI_REVIEW:-codex-${CODEX_MODEL_REVIEW:-gpt-5.6-sol}-${CODEX_EFFORT_REVIEW:-high}}" review # ALSO long-running — same poll-to-completion contract
--cli is a global dex option (validated against dex 0.4.9) and must precede the subcommand — dex --cli codex apply, not dex apply --cli codex (the latter errors with Unrecognized argument: --cli). dex import takes a path arg and needs no --cli.
Why each phase names a different backend. dex 0.4.9 has no --model flag and no reasoning-effort option — dex --help lists only --cli, --verbose, --timeout, --update-prompts. The single lever is which clis entry --cli names, so running the two phases on different models requires two entries, which Step 4 provisions — that is why this needs no setup on the user's part.
Each phase resolves its backend in one expansion, three-way. Explicit entry ($DEX_CLI_*) wins; else codex-<model>-<effort> built from $CODEX_MODEL_* / $CODEX_EFFORT_*, the entry Step 4 provisions; else the phase's pinned default. Type the fragment literally — never resolve it to a concrete entry name yourself. It resolves to:
| Set | apply | review |
|---|---|---|
| nothing | codex-gpt-5.6-luna-high |
codex-gpt-5.6-sol-high |
CODEX_EFFORT_BUILD=xhigh |
codex-gpt-5.6-luna-xhigh |
codex-gpt-5.6-sol-high |
CODEX_MODEL_REVIEW=gpt-5.6-terra |
codex-gpt-5.6-luna-high |
codex-gpt-5.6-terra-high |
DEX_CLI_BUILD=mine |
mine |
codex-gpt-5.6-sol-high |
The split is deliberate: the cheap fast model implements, the frontier model reviews. apply runs gpt-5.6-luna ("fast and affordable agentic coding") because implementation is the bulk of the token spend and the work is already specified by a hardened plan; review runs gpt-5.6-sol ("latest frontier agentic coding") because adversarial review is where depth pays for itself. Both at high. Neither phase inherits from ~/.codex/config.toml any more — the pipeline behaves identically on every machine, at the cost of a pinned slug that will eventually age out, which is what Step 4's entitlement check catches.
If dex answers unknown CLI "<name>" (note: it prints this and still exits 0 — check the output, not the status), either the user named a $DEX_CLI_* entry that is not in their dex config, or Step 4 refused to provision the derived entry because the model/effort pair was unavailable. Report it verbatim and stop; do not silently fall back to codex, which would run the phase on the machine's own model without saying so.
dex apply AND dex review are long-running — poll each to completion in THIS invocation
Both phases drive codex through work that will likely exceed a single foreground Bash timeout (the harness caps foreground Bash at 10 minutes / 600000ms). Run each foreground with the maximum timeout, then loop — the contract is identical for both.
dex apply:
- Run
dex --cli "${DEX_CLI_BUILD:-codex-${CODEX_MODEL_BUILD:-gpt-5.6-luna}-${CODEX_EFFORT_BUILD:-high}}" apply(foreground,timeout: 600000). - Re-read
.dex/plan.mdafter the pass. - If any checkbox is still
[ ]and dex did not report a terminal state, run the same command again (it resumes where it left off). - Repeat until all checkboxes are
[x]or dex reports terminal (STALEMATE/ non-zero exit).
dex review: the same loop — re-run dex --cli "${DEX_CLI_REVIEW:-codex-${CODEX_MODEL_REVIEW:-gpt-5.6-sol}-${CODEX_EFFORT_REVIEW:-high}}" review foreground at max timeout every time the 10-min ceiling truncates it, until it reaches a terminal state: it prints its completion line (Review complete / DONE) and/or writes .dex/review-*.md, or reports a terminal STALEMATE / non-zero exit. Terminal on quota: if codex exhausts its usage quota after apply is fully done (every .dex/plan.md checkbox [x]) and at least one reviewer pass has written its findings, treat the quota state as terminal — report it and stop. Do NOT loop forever on quota backoff.
⚠️ Co-edit warning: the FORBIDDEN list and the pre-return verification below are duplicated verbatim into
/ship-it's execute-and-report subagent prompt (skills/ship-it/SKILL.md) and summarized indocs/skills/plan-to-dex.md./ship-itmust carry its own copy — the guard binds the invoking subagent, whose backgrounded processes are reaped on return, not the skill it invokes. Touch any of the three and touch all three:grep -rn '\[d\]ex --cli codex' skills/ docs/finds every site.
FORBIDDEN — no escape hatches (this is exactly where past runs failed):
- Never run
dex applyordex reviewwithrun_in_background: true. - Never arm a
Monitor/ waiter /ScheduleWakeupto "come back later" — nothing re-invokes a returned invocation, so the child is reaped and only thedex importsetup commit lands. - Never return on an intermediate "waiting for / running in background / iteration N in progress" state.
Backgrounded work dies the instant this invocation (especially a subagent) returns. The loop MUST reach a terminal state inside the current invocation. If you need to wait on a foreground pid, block on it directly (wait <pid>, or while kill -0 <pid> 2>/dev/null; do sleep 15; done) — never rely on any waiter or Monitor to resume you.
Before you return: pre-return verification (mandatory)
Do NOT return until you have run these three checks and can state their results. If any check fails, keep looping — returning early is a bug, not a shortcut:
Plan done:
grep -c '\[ \]' .dex/plan.mdprints0— every checkbox is[x](or dex printed a terminalSTALEMATE/quota state, which you name explicitly).No live worker in this worktree:
pgrep -f '[d]ex --cli codex|[c]odex exec' | while read -r p; do lsof -a -d cwd -p "$p" -Fn 2>/dev/null | grep -q "^n$(pwd -P)" && echo "live worker $p" doneprints nothing. Two details are load-bearing, and a bare
pgrep -fl 'dex --cli codex|codex exec'gets both wrong:- Scope to your own worktree. Concurrent
/ship-itruns in sibling worktrees have their owndex --cli codex applyandcodex execprocesses. A machine-widepgrepsees them, reports a "live worker" that is not yours, and makes this check permanently unsatisfiable. Thelsof -d cwdfilter keeps only processes whose working directory is under this worktree. - Bracket the first character (
[d]ex,[c]odex). Without it the pattern matches the command line of the shell running the check — the check reports itself as a live worker and never passes.
The pattern is a literal prefix, so a custom
$DEX_CLI_BUILD/$DEX_CLI_REVIEWentry still matches as long as it is namedcodex-*(dex --cli codex-deep applycontainsdex --cli codex). If the user named theirs something else, say so in the handoff report — the guard is blind to it and cannot prove the worker exited.Never kill, signal, or wait on a process you did not start. A sibling run's dex is not your problem, and killing it corrupts that run. If this check still prints after your own foreground
dexcommand returned, you backgrounded it — that is the only failure it is looking for.- Scope to your own worktree. Concurrent
Commits landed:
git log --onelineshows per-task dex commits (not just the lonedex importsetup commit), andgit statusis as expected.
- If
dex applyprintsSTALEMATEor exits non-zero, STOP — report dex's output verbatim and do NOT rundex review. - If
dex importfails validation, STOP and show the error (the translated file lacks an open checkbox — a translation bug).
Step 7: Handoff Report
After the chain completes, report:
plan-to-dex complete
--------------------
Branch: <branch>
Tasks completed: <done>/<total> (from dex plan-step counts)
Review findings: .dex/review-*.md
Next: review the diff, then `dex finalize --onto main`
Do NOT run dex finalize automatically — merging back is the user's call.