e3d-code
v0.4.0
Published
Run Codex CLI or Claude CLI phase-by-phase from Markdown feature specs with model routing, shared context, and resumable execution.
Readme
e3d-code
Run Codex CLI or Claude CLI phase-by-phase from Markdown feature specs with model routing, shared context, and resumable execution.
Current version: 0.4.0
Renamed from
spec2code. The command is nowe3d-code. For compatibility,bin/spec2codestill works as a shim, repos that only have the old.spec2code/state directory keep using it (new runs use.e3d-code/), and the oldS2C_CLAUDE_API_KEY_FILEvariable is still honored alongsideE3D_CODE_CLAUDE_API_KEY_FILE.
e3d-code turns a phased Markdown spec into separate provider runs. Each phase gets a fresh conversation, a focused prompt, and a model selected from conservative defaults or explicit overrides.
Demo
Featured as an e3d-cast gallery sample:
Why
Large implementation specs can waste context when every phase runs in one long conversation. This runner keeps each phase isolated so the selected provider starts with only the relevant spec section, the current working tree, and the files you tell it to read first.
Requirements
- Bash
awk,grep,sort- Codex CLI and/or Claude CLI installed and authenticated for normal execution and
--preflight
Check provider CLIs:
codex --help
claude --helpInstall
Clone the repo and add bin to your PATH:
git clone https://github.com/spacepacket1/e3d-code.git
cd e3d-code
export PATH="$PWD/bin:$PATH"Or run it directly:
./bin/e3d-code path/to/spec.md --listSpec Format
The spec should be Markdown with phase headings that include a phase number:
## Phase 1 - Historical Replay
## Phase 2 - Walk-Forward Validation
## 6. Phase 3 - Execution SimulationThe runner extracts each phase section from its heading until the next phase heading. Phase metadata is parsed once per runner process, sorted numerically, and duplicate phase numbers fail before the provider is launched.
Usage
List phases with provider and model:
e3d-code docs/feature-ticket.md --listThis prints provider and model per phase, for example:
Phase 1 codex gpt-5.4-mini Core Parser
Phase 2 claude sonnet Report WriterBy default, a phase with no runner:model annotation and no MODEL_OVERRIDES entry gets its tier (mini/default/high) from a static keyword grep over its title and body (phase_model_tier in bin/e3d-code) — fast and free, but not actual judgment. Pass --auto-route to replace that keyword grep with one cheap model call per phase instead, asking the provider that will execute the phase (via its own mini tier — haiku for Claude, gpt-5.4-mini for Codex) to classify the phase's real complexity:
e3d-code docs/feature-ticket.md --list --auto-route
e3d-code docs/feature-ticket.md --list --provider claude --auto-routeAn explicit runner:model= annotation or a MODEL_OVERRIDES entry always wins over --auto-route — this only replaces the keyword-heuristic fallback, never author intent. Classifications are cached by (provider, title, body) content digest in AUTO_ROUTE_CACHE_FILE (default .e3d-code/route-cache.tsv), so re-running --list against an unchanged phase costs nothing on the second call. If the classifier call fails or returns something unparseable, the runner falls back to the keyword heuristic and prints a warning rather than failing the run.
Print the installed runner version:
e3d-code --versionDry-run one phase status:
e3d-code docs/feature-ticket.md 3 --dry-runPrint the full generated prompt for debugging:
e3d-code docs/feature-ticket.md 3 --dry-run --verboseRun one phase with Claude:
e3d-code docs/feature-ticket.md 3 --provider claude--provider sets the default provider for phases that do not have a mixed-provider override.
Run preflight checks only:
e3d-code docs/feature-ticket.md --preflightPrepare shared repo context only:
e3d-code docs/feature-ticket.md --prepare-contextRun one phase:
e3d-code docs/feature-ticket.md 3Run all phases:
e3d-code docs/feature-ticket.md allRefresh shared repo context before execution:
e3d-code docs/feature-ticket.md all --refresh-contextResume from a phase after a rate limit or interruption:
e3d-code docs/feature-ticket.md all --from 9Run a bounded range:
e3d-code docs/feature-ticket.md all --from 4 --to 8Add files the provider should read first:
e3d-code docs/feature-ticket.md 5 \
--read package.json \
--read pipeline.js \
--read server.jsRecommended workflow for a new spec:
e3d-code docs/feature-ticket.md --preflight
e3d-code docs/feature-ticket.md --prepare-context
e3d-code docs/feature-ticket.md allUse --refresh-context when the repo changed enough that the shared context should be regenerated before execution.
Normal non-dry-run execution runs preflight automatically unless SKIP_PREFLIGHT=1; --dry-run remains usable without the provider installed.
Runtime State
.e3d-code/ is runner runtime state. It is generated on demand and can contain:
context.md: shared repo context used to reduce repeated setup across phasesmanifest.tsv: one tab-separated row per attempted phase runsummaries/phase-N.md: lightweight per-phase summary placeholders
Shared context is generated automatically for both single-phase and all runs when USE_SHARED_CONTEXT=1 and context.md does not already exist. It summarizes cheap local facts only: timestamp, root path, git status, top-level layout, detected package/config files, likely verification commands, and configured common read files.
Providers are asked to record a concise implementation handoff with changes, decisions, tests, and follow-ups. The runner preserves that handoff, adds independently observed verification results and a worktree snapshot, and includes up to three successful summaries in later phase prompts by default.
These generated files are safe to delete:
.e3d-code/context.md.e3d-code/manifest.tsv.e3d-code/summaries/
Deleting them removes runner history and cached context, but does not affect your source files.
Spec Annotations
Specs can override a phase model, add extra read files, and include verification hints with HTML comments inside the phase body:
## Phase 2 - Report Writer
<!-- runner:model=mini -->
<!-- runner:read=docs/reporting-notes.md -->
<!-- runner:verify=bash tests/reporting.sh -->runner:modeloverrides heuristic routing for that phase across providersrunner:readadds files to the "Read first" blockrunner:verifytells the provider what to run and is rerun independently by the runner after a successful phase- legacy
codex:*annotations are still accepted;codex:modelonly applies whenPROVIDER=codex
See examples/feature-ticket.md for a complete example.
Model Routing
Default routing is intentionally conservative and tiered:
highfor architecture, security, migration, review/release, custody, routing, reconciliation, compliance, audit, and other high-risk phasesdefaultfor most implementation phasesminifor explicitly lightweight work
Provider defaults:
- Codex:
gpt-5.5,gpt-5.4,gpt-5.4-mini - Claude:
opus,sonnet,haiku
runner:model=high|default|mini is the preferred provider-neutral form. Exact model names are still supported when you want provider-specific routing.
Override exact phase-to-model mapping:
MODEL_OVERRIDES="4:gpt-5.5,14:gpt-5.4-mini" \
e3d-code docs/feature-ticket.md allOverride exact phase-to-provider-and-model mapping:
MODEL_OVERRIDES="1:codex:gpt-5.4,2:claude:sonnet" \
e3d-code docs/feature-ticket.md allOverride default model names:
DEFAULT_MODEL=gpt-5.4 \
HIGH_MODEL=gpt-5.5 \
MINI_MODEL=gpt-5.4-mini \
e3d-code docs/feature-ticket.md --listConfiguration
The runner invokes the selected provider like this:
PROVIDER=codex codex --ask-for-approval "$APPROVAL_POLICY" exec [--ephemeral] [--skip-git-repo-check] [--add-dir ...] --model "$model" --cd "$ROOT_DIR" --sandbox "$SANDBOX_MODE" -
PROVIDER=claude claude --print --model "$model" --permission-mode "$CLAUDE_PERMISSION_MODE" [--no-session-persistence] [--add-dir ...]Environment overrides:
ROOT_DIR=/path/to/repo
STATE_DIR=/path/to/repo/.e3d-code
CONTEXT_FILE=/path/to/repo/.e3d-code/context.md
MANIFEST_FILE=/path/to/repo/.e3d-code/manifest.tsv
SUMMARY_DIR=/path/to/repo/.e3d-code/summaries
PROVIDER=codex
CODEX_BIN=codex
CLAUDE_BIN=claude
SKIP_PREFLIGHT=0
USE_SHARED_CONTEXT=1
USE_PHASE_SUMMARIES=1
SUMMARY_LOOKBACK=3
RUN_VERIFICATION=1
VERIFY_SHELL=bash
SANDBOX_MODE=workspace-write
APPROVAL_POLICY=on-request
MODE=exec
CODEX_EPHEMERAL=1
CODEX_SKIP_GIT_REPO_CHECK=0
CODEX_NETWORK_ACCESS=0
CLAUDE_PERMISSION_MODE=bypassPermissions
CLAUDE_NO_SESSION_PERSISTENCE=1
ADD_DIRS="/path/to/extra/repo /path/to/output"
DEFAULT_MODEL=<provider default>
HIGH_MODEL=<provider high>
MINI_MODEL=<provider mini>
MODEL_OVERRIDES="4:gpt-5.5,14:claude:sonnet"
AUTO_ROUTE_TIER=0
AUTO_ROUTE_CACHE_FILE=/path/to/repo/.e3d-code/route-cache.tsv
COMMON_READ_FILES="package.json pipeline.js server.js"
MAX_PHASES_PER_RUN=8
MAX_PROVIDER_TOKENS_PER_PHASE=120000STATE_DIR,CONTEXT_FILE,MANIFEST_FILE,SUMMARY_DIR: runtime-state paths under.e3d-code/PROVIDER:codexorclaudeCODEX_BIN,CLAUDE_BIN: provider CLI binary name or pathSKIP_PREFLIGHT: set to1to skip automatic preflight on normal runsUSE_SHARED_CONTEXT: set to0to omitcontext.mdfrom promptsUSE_PHASE_SUMMARIES: set to0to omit prior phase summaries from later promptsSUMMARY_LOOKBACK: number of previous successful summaries to include in a later phase promptRUN_VERIFICATION: set to0to skip runner-managed verification after provider executionVERIFY_SHELL: shell used with-lcfor verification commandsSANDBOX_MODE,APPROVAL_POLICY,MODE: Codex-only execution settingsCODEX_EPHEMERAL: set to0to let Codex persist phase sessions; default1avoids stale session persistence during runner-managed phase runsCODEX_SKIP_GIT_REPO_CHECK: set to1to pass--skip-git-repo-checktocodex execCODEX_NETWORK_ACCESS: set to1to pass-c sandbox_workspace_write.network_access=truetocodex exec, enabling outbound network and inbound socket binds inside theworkspace-writesandbox; default0keeps Codex's own network-disabled default. Needed for phases whose verification binds a listening socket (e.g. a local web-server test) — without it,codex exec's sandbox denies the bind withEPERM.CLAUDE_PERMISSION_MODE: passed toclaude --permission-mode; defaults tobypassPermissionsbecause--printhas no TTY to answer an interactive approval prompt —defaultmode silently denies every Write/Edit/Bash call while the phase still exits 0 with nothing changed (confirmed directly againstclaude2.1.296). This mirrors Codex's own unattended default above (APPROVAL_POLICY=on-requestinsideworkspace-write); s2c's external safety rails (worktree isolation,protected_paths, diff ceilings, draft PRs) are the real guard for both providers, not per-call prompts that can't fire headless anyway.CLAUDE_NO_SESSION_PERSISTENCE: set to0to let Claude persist phase sessionsE3D_CODE_CLAUDE_API_KEY_FILE(default~/.config/e3d-pilot/anthropic-api-key): a shell snippet (export ANTHROPIC_API_KEY="...") sourced before a Claude phase, but only ifANTHROPIC_API_KEYisn't already set in the environment. Defaults unattended Claude phases to metered API billing instead of whatever interactive subscription auth the calling shell happens to have — setANTHROPIC_API_KEYyourself beforehand to use something else.ADD_DIRS: space-separated directories to pass as repeated--add-dirflags to the selected providerDEFAULT_MODEL,HIGH_MODEL,MINI_MODEL: provider-specific default routed model namesMODEL_OVERRIDES: exactphase:modelorphase:provider:modelmapping that beats heuristic routing and spec annotationsAUTO_ROUTE_TIER: set to1(or pass--auto-route) to classify tier via a cheap model call instead of the keyword heuristic, for phases with no annotation/override — see Usage aboveAUTO_ROUTE_CACHE_FILE(default.e3d-code/route-cache.tsv): where--auto-routeclassification results are cached, keyed by(provider, title, body)digestCOMMON_READ_FILES: space-separated files to include in every phase prompt when presentMAX_PHASES_PER_RUN(default8): anallor bounded--from/--torun that would execute more phases than this fails preflight before any provider is launched, forcing a smaller batch; set to0to disableMAX_PROVIDER_TOKENS_PER_PHASE(default120000): fails a phase after the fact if the provider reports using more tokens than this; set to0to disable
By default, runner output stays high level: current phase, provider, model, status, issues, and verification. Pass --verbose when you need full generated prompts or detailed provider output.
Manifest and summary behavior:
manifest.tsvrecords each attempted phase run, including dry-runs and non-zero exitssummaries/phase-N.mdis updated after each attempt with the provider handoff, verification results, and current worktree snapshot- later phases can read recent successful summaries in both single-phase and
allruns unlessUSE_PHASE_SUMMARIES=0
When a phase has one or more runner:verify annotations, the commands run in declaration order and the first failure fails the phase and stops an all run. Without annotations, the runner conservatively infers one project-level check from common package files or shell tests. Verification is never executed during --dry-run.
Run only trusted specs: verification annotations are shell commands executed from ROOT_DIR.
Safety Notes
Two runner-enforced ceilings bound a run independent of what the spec or provider do: MAX_PHASES_PER_RUN caps how many phases a single all/bounded invocation can select, and MAX_PROVIDER_TOKENS_PER_PHASE fails a phase that reports using more tokens than expected. Both default on; see Configuration above.
The generated prompt tells the provider to:
- keep edits scoped to the current phase
- preserve existing behavior unless the phase requires a change
- avoid
node_modules - avoid later phases
- inspect the working tree when resuming after an interrupted run
- generate focused tests and run verification before finishing
- write a concise handoff for later phases
You should still review diffs after each phase.
License
MIT © 2026 FutCo LLC. Part of the FutCo open-source ecosystem.
