codex-sdlc
Universal SDLC orchestrator for OpenAI Codex. One command takes a feature from requirements to Pull Request, dispatching a specialist subagent per phase and picking the right framework conventions automatically.
$sdlc-start "add subscription billing with Stripe"
detect stack ─▶ detect git flow ─▶ classify task ─▶ pick recipe ─▶ apply skip rules
│ │ │
│ └─ branch gate: create/continue/rename/abort
│
▼
BA ──▶ Dev (plan ▸ approve ▸ implement) ──▶ [database] ──▶ [QA ∥ Security] ──▶ PR
sol sol human terra terra terra/sol luna
Which phases run is decided per task, not fixed: a docs change runs one phase, a hotfix
skips business analysis, a full-stack feature fans development and QA out across aspects.
Ported from AratKruglik/claude-sdlc,
release 2.0.0 at commit 4c09bc9658e2672683691ac476368c4a1c696b27. The
Codex harness research and
v2 decisions explain which upstream contracts are
preserved and where the two hosts require different mechanisms.
Install
Install it as a native Codex plugin from the repository marketplace:
git clone <this repo> ~/codex-sdlc
codex plugin marketplace add ~/codex-sdlc
codex plugin add codex-sdlc@codex-sdlc-marketplace
Review and trust the bundled hooks when Codex asks. The plugin is self-contained: its skills, hooks, stack profiles, provider registry and generated role instructions are all resolved from the installed snapshot.
For standalone skill discovery and named custom-agent registration, use the compatibility installer:
cd ~/codex-sdlc
bash scripts/install.sh
install.sh is idempotent. It links the generated roles and skills and writes one managed
block in $CODEX_HOME/config.toml; native plugin installs use bundled-role dispatch and do
not depend on that global registration.
Commands
| Skill | Purpose |
|---|---|
$sdlc-start <feature> | Run the full pipeline |
$sdlc-batch | Run several pipelines in parallel, one git worktree each |
$sdlc-doctor | Diagnose install, agent registration, model-routing integrity |
$sdlc-list-stacks | Show all stack profiles and which would win here |
$sdlc-security-init | Materialize stack security rules into the current project |
Supported stacks
Detection is per aspect — a Laravel + Inertia/Vue project gets laravel for backend
and database, inertia-vue for frontend, and one QA pass per aspect.
| Stack | Priority | Aspects | Origin |
|---|---|---|---|
react-native | 300 | [frontend] | react-native-plugin |
nextjs | 250 | [backend, frontend] | nextjs-plugin |
angular | 200 | [frontend] | angular-plugin |
nestjs | 200 | [backend, database] | nestjs-plugin |
inertia-react | 175 | [frontend] | inertia-react-plugin |
inertia-vue | 175 | [frontend] | inertia-vue-plugin |
django | 150 | [backend, database] | django-plugin |
fastapi | 150 | [backend, database] | fastapi-plugin |
flask | 150 | [backend, database] | flask-plugin |
react | 150 | [frontend] | react-plugin |
spring-boot | 150 | [backend] | spring-boot-plugin |
vue | 150 | [frontend] | vue-plugin |
aspnet-core | 100 | [backend, database] | aspnet-core-plugin |
java | 100 | [backend] | java-plugin |
laravel | 100 | [backend, database] | laravel-plugin |
nodejs | 100 | [backend] | nodejs-plugin |
python | 100 | [backend] | python-plugin |
symfony | 100 | [backend, database] | symfony-plugin |
vanilla | 0 | — | sdlc |
Detection is independent of the visible skill catalog. scripts/detect-stack.py reads all
profiles, selects the highest-priority provider for each aspect, rejects equal-priority
ambiguity, and resolves foundations and provider dependencies through
references/providers.json. Its content-addressed cache is invalidated when a profile or
the registry changes.
To add a provider, add its stack profile and its registry entry together. The profile owns detection and aspect priority; the registry owns dependencies, roles, skills, protected configuration and post-implementation checks.
What the pipeline decides before it spawns anything
A run is not a fixed script. Four decisions are made up front, each printed verbatim so you can see what was chosen and why:
- Stack profiles — per aspect, highest priority wins (see above).
- Branching model and task type — how this repo branches, what kind of change this is, what branch to work on, what the PR should target.
- Workflow recipe — which phases actually run.
- Skip rules — which of those phases the measured diff makes pointless.
Workflow recipes
The task description is classified into a task_type against
references/task-type-patterns.json, and the type selects a recipe:
task_type | Recipe | Phases | Output-token cap |
|---|---|---|---|
| feature, release, chore | default | business_analysis → development → (qa ∥ security) → documentation | — |
| fix, bugfix | bugfix | development → (qa ∥ security) → documentation | — |
| hotfix | hotfix | development → (qa ∥ security) → documentation | 70000 |
| refactor | refactor | development → (qa ∥ security) → documentation | — |
| docs | docs-only | documentation | 12000 |
Recipes live in workflows/*.yaml and are validated against
schemas/workflow.schema.json. Selection follows five rules in order
(workflows/RESOLVER.md), and the deciding rule is recorded in telemetry as
workflow_selection_reason — the choice must always be explainable:
| # | Rule | Reason recorded |
|---|---|---|
| 1 | --workflow=NAME in the prompt | flag |
| 2 | active_workflow in .codex/sdlc.local.yaml | config |
| 3 | The task_type mapping above | task_type={type} |
| 4 | Match scan over remaining recipes, alphabetical | match:{name} |
| 5 | Fall back to default | fallback |
Two subtleties that exist because they were bugs once:
- Rule 3 vetoes, rule 4 selects. A
match:constraint under rule 3 only fires when the diff contradicts the classified type (a 600-LOC change described as a "fix" falls through to rule 5). A constraint that cannot be measured has nothing to contradict. - An unmeasurable diff is unknown, not small. On a freshly created branch
LOC_TOUCHEDis 0, which would satisfy every ceiling in the recipe set and hand a brand-new feature thehotfixpipeline.loc_touched_*constraints are therefore never satisfied whilediff_scope == "prospective".
Classification is language-aware. терміново виправити продакшн → hotfix,
оновити документацію → docs, refactoring done → refactor, while суперрефакторинг
and newsletter correctly match nothing (patterns use (?<![\p{L}\p{N}]) rather than
\b, which cannot assert a boundary next to a non-Latin character). When several types
match, a fixed precedence decides: urgently refactor the broken payment fix → hotfix.
Override with --type=NAME or --workflow=NAME. An explicitly named recipe is never
second-guessed.
Git flow and branching
scripts/detect-git-flow.sh infers the branching model from topology, git config and
documented conventions, then the run stops at a gate before writing anything:
🌿 Git flow: git-flow (confidence: high, from topology:develop-branch)
Task type: hotfix Branch: hotfix/null-pointer-in-payment-handler
Base: main → PR targets main, back-merge into develop
create / continue / rename / change type / abort?
The PR base comes from the task type and the detected model, never from the repository
default — on a git-flow project that difference silently retargets a feature PR from
develop to main. When a hotfix or release requires a back-merge, the final summary says
so explicitly; the pipeline does not open that second PR for you.
The decision is cached in .codex/.sdlc-git-flow.json. Re-detect with --redetect-git-flow.
Full algorithm: references/GIT-FLOW.md.
Skip rules
Measured against the detected base branch, conservative by design — when in doubt, the phase runs:
| Rule | Fires when | Skips |
|---|---|---|
typo-fix | description matches ^(typo|fix typo|rename .* to|format) and < 30 LOC | business_analysis |
whitespace-only | diff is empty under git diff -w | business_analysis, qa |
config-only | every changed path is .env/.ya?ml/.json/.toml/.ini and < 200 LOC | qa |
lightweight-no-db | < 50 LOC, no migrations, no auth/secret/token paths | security (an inline secret scan is injected into development instead) |
Every rule that fires is printed and recorded in telemetry with its reason.
What a run produces
docs/plans/{task_slug}/
├── _brief.md original request
├── 01-business-analysis.md requirements, Gherkin, data model, edge cases
├── 02-development-plan*.md the plan you approved (per aspect)
├── 02-development*.md implementation report (per aspect)
├── 03-qa*.md tests added, pass/fail, coverage
├── 04-security.md report-only OWASP findings and fix verification
├── 05-pr.md PR description
├── _usage.jsonl observed subagent lifecycle and recognized usage events
├── _state.json resumable dispatch and approval state
└── _telemetry.json per-phase model, tokens, timings, decisions
Phase agents hand each other files, not context: every phase writes its full deliverable to disk and returns a ≤2-3K-token summary. The orchestrator's context does not grow with the size of the work.
_telemetry.json records the resolved stack, workflow, git-flow decision and every skip
rule that fired. _usage.jsonl records measured token counts only when a recognized
turn.completed.usage event is present. A compact-summary estimate is stored in a separate
field; missing runtime usage remains unavailable and is never presented as measured phase
usage.
No currency figures anywhere. OpenAI publishes no per-token price for the Codex model
family — usage is billed as ChatGPT Credits — so budgets, caps and baselines are all in
output tokens. If prices appear, add pricing.toml and derive cost from the counts already
recorded; the telemetry shape does not change. See ADR-11.
MCP servers and project runtimes
One stack ships an MCP server: Laravel Boost (mcp/laravel-boost.toml). Its command is a
launcher, not a fixed runtime, because Codex MCP config is static TOML while how you run
artisan is a property of the project:
| Setup | Resolves to |
|---|---|
| Laravel Sail | vendor/bin/sail php artisan boost:mcp |
| docker compose | docker compose exec -T <service> php <artisan> boost:mcp — service and in-container path are probed, not assumed |
| Laravel Herd | herd php artisan boost:mcp |
| System PHP | php artisan boost:mcp |
| Anything else | mcp.laravel_boost.command in .codex/sdlc.local.yaml, or $SDLC_ARTISAN_RUNNER |
A compose file that only carries postgres and redis while PHP runs on the host is explicitly handled: the launcher requires a service that actually bears PHP before choosing Docker.
Inspect the decision for any project:
bash scripts/laravel-boost-launcher.sh --explain
# command: herd php artisan boost:mcp
# reason: Laravel Herd (herd on PATH)
$sdlc-doctor reports the same thing, plus whether laravel/boost is actually installed.
When nothing resolves the pipeline still runs — agents fall back to artisan through Bash;
only the Artisan-aware tools are lost.
Model tiers
| Role | Model | Effort | Why |
|---|---|---|---|
| business_analyst | gpt-5.6-sol | high | Ambiguous input, highest leverage on everything downstream |
| *_plan (19 roles) | gpt-5.6-sol | high | Small output, governs the whole implementation, passes a human gate |
| architects, qa_engineer | gpt-5.6-terra | medium | High-volume execution against an approved spec |
| security_analyst | gpt-5.6-sol | xhigh | Silent-failure risk — a missed vulnerability looks identical to none |
| document_writer | gpt-5.6-luna | low | Structured output from finished artifacts |
The role definition is authoritative. Named roles get their model and effort from their
Codex TOML. Bundled-role mode supplies the same generated instructions and explicit tier to
a default subagent when plugin installation has not registered named agents. The model
hook corrects an explicit mismatch when the lifecycle payload exposes one, and otherwise
fails open; reconciliation distinguishes a declared tier from a runtime-observed model.
How it differs from the Claude Code version
| claude-sdlc | codex-sdlc | |
|---|---|---|
| Distribution | 24 installable providers | one native all-stack plugin with 24 logical providers |
| Entry points | /sdlc:start slash commands | $sdlc-start skills (ADR-08) |
| Stack discovery | provider metadata and cache scan | deterministic stacks/ + provider registry resolver |
| Accounting | USD from a price table | output tokens (ADR-11) |
| Batch isolation | isolation: "worktree" tool param | scripts/batch-run.sh (ADR-13) |
| Batch branch names | tool-chosen, ignored convention | follow the project convention |
| Phase output validation | prose check | JSON Schema under codex exec (ADR-12) |
| Custom roles | plugin agents load directly | native bundled-role fallback; optional named-role installer |
Recipe selection, task typing, git-flow detection, skip rules, the two-pass approval gate, provider dependencies, parallel phase groups, resume state, security fix handoff and file-based phase artifacts follow the upstream 2.0 contracts. Host-specific dispatch, hooks, telemetry and packaging use documented Codex mechanisms.
Current reasoning: docs/port-notes/v2/DECISIONS.md. The older
docs/port-notes/01-DECISIONS.md records the initial v0.1 port and is retained as history.
Repository layout
agents/ 48 subagent roles (generated — do not hand-edit)
skills/ orchestrator, entry points, 61 convention skills (69 total)
stacks/ 19 stack profiles — the discovery root
hooks/ stack cache, guards, model enforcement, formatter, telemetry
workflows/ 5 pipeline recipes + RESOLVER.md
schemas/ stack + workflow + phase-output contracts
security-patterns/ core and per-provider security fragments
references/ provider registry, git-flow and task-type contracts
scripts/ converters, install, verify, batch runner
Verifying a change
bash scripts/verify-port.sh # 8 structural checks, must stay green
bash hooks/test-enforce-agent-model.sh # 6 hook behaviour tests
python3 hooks/test-safety-hooks.py # guard fail-open and telemetry tests
python3 scripts/test-detect-stack.py # stack priority and dependency-composition tests
python3 scripts/test-runtime-dependencies.py # Codex plugin/skill preflight tests
bash scripts/test-detect-git-flow.sh # 53 git-flow detection tests
bash scripts/test-laravel-boost-launcher.sh # 8 MCP runtime detection tests
bash scripts/test-task-typing.sh # 13 task-type classification tests
bash scripts/test-schema-validation.sh # workflow/stack schema validation tests
bash scripts/test-install.sh # isolated installer end-to-end tests
bash scripts/check-verification-counts.sh # README/script count drift detector
CI (.github/workflows/ci.yml) runs this same suite on Ubuntu with Python 3.12,
plus ShellCheck and a non-blocking Markdown lint pass.