Skip to content

aratkruglik/codex-sdlc

v1.0.0

Universal stack-aware SDLC orchestration for Codex.

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

SkillPurpose
$sdlc-start <feature>Run the full pipeline
$sdlc-batchRun several pipelines in parallel, one git worktree each
$sdlc-doctorDiagnose install, agent registration, model-routing integrity
$sdlc-list-stacksShow all stack profiles and which would win here
$sdlc-security-initMaterialize 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.

StackPriorityAspectsOrigin
react-native300[frontend]react-native-plugin
nextjs250[backend, frontend]nextjs-plugin
angular200[frontend]angular-plugin
nestjs200[backend, database]nestjs-plugin
inertia-react175[frontend]inertia-react-plugin
inertia-vue175[frontend]inertia-vue-plugin
django150[backend, database]django-plugin
fastapi150[backend, database]fastapi-plugin
flask150[backend, database]flask-plugin
react150[frontend]react-plugin
spring-boot150[backend]spring-boot-plugin
vue150[frontend]vue-plugin
aspnet-core100[backend, database]aspnet-core-plugin
java100[backend]java-plugin
laravel100[backend, database]laravel-plugin
nodejs100[backend]nodejs-plugin
python100[backend]python-plugin
symfony100[backend, database]symfony-plugin
vanilla0sdlc

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:

  1. Stack profiles — per aspect, highest priority wins (see above).
  2. 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.
  3. Workflow recipe — which phases actually run.
  4. 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_typeRecipePhasesOutput-token cap
feature, release, choredefaultbusiness_analysis → development → (qa ∥ security) → documentation
fix, bugfixbugfixdevelopment → (qa ∥ security) → documentation
hotfixhotfixdevelopment → (qa ∥ security) → documentation70000
refactorrefactordevelopment → (qa ∥ security) → documentation
docsdocs-onlydocumentation12000

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:

#RuleReason recorded
1--workflow=NAME in the promptflag
2active_workflow in .codex/sdlc.local.yamlconfig
3The task_type mapping abovetask_type={type}
4Match scan over remaining recipes, alphabeticalmatch:{name}
5Fall back to defaultfallback

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_TOUCHED is 0, which would satisfy every ceiling in the recipe set and hand a brand-new feature the hotfix pipeline. loc_touched_* constraints are therefore never satisfied while diff_scope == "prospective".

Classification is language-aware. терміново виправити продакшнhotfix, оновити документаціюdocs, refactoring donerefactor, 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 fixhotfix.

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:

RuleFires whenSkips
typo-fixdescription matches ^(typo|fix typo|rename .* to|format) and < 30 LOCbusiness_analysis
whitespace-onlydiff is empty under git diff -wbusiness_analysis, qa
config-onlyevery changed path is .env/.ya?ml/.json/.toml/.ini and < 200 LOCqa
lightweight-no-db< 50 LOC, no migrations, no auth/secret/token pathssecurity (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:

SetupResolves to
Laravel Sailvendor/bin/sail php artisan boost:mcp
docker composedocker compose exec -T <service> php <artisan> boost:mcp — service and in-container path are probed, not assumed
Laravel Herdherd php artisan boost:mcp
System PHPphp artisan boost:mcp
Anything elsemcp.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

RoleModelEffortWhy
business_analystgpt-5.6-solhighAmbiguous input, highest leverage on everything downstream
*_plan (19 roles)gpt-5.6-solhighSmall output, governs the whole implementation, passes a human gate
architects, qa_engineergpt-5.6-terramediumHigh-volume execution against an approved spec
security_analystgpt-5.6-solxhighSilent-failure risk — a missed vulnerability looks identical to none
document_writergpt-5.6-lunalowStructured 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-sdlccodex-sdlc
Distribution24 installable providersone native all-stack plugin with 24 logical providers
Entry points/sdlc:start slash commands$sdlc-start skills (ADR-08)
Stack discoveryprovider metadata and cache scandeterministic stacks/ + provider registry resolver
AccountingUSD from a price tableoutput tokens (ADR-11)
Batch isolationisolation: "worktree" tool paramscripts/batch-run.sh (ADR-13)
Batch branch namestool-chosen, ignored conventionfollow the project convention
Phase output validationprose checkJSON Schema under codex exec (ADR-12)
Custom rolesplugin agents load directlynative 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.