{"slug": "cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex", "title": "CLI That Enforces Spec-Driven Development with Claude Code, OpenCode, and Codex", "summary": "Developer David P. V. released a stack-agnostic CLI tool called opsx that enforces spec-driven development workflows on top of OpenSpec, supporting agent CLIs including OpenCode, Claude Code, and Codex. The tool separates a management plane for tracking work from a governance plane that controls system behavior, ensuring code is only produced after spec changes are reviewed and approved. Opsx requires Node.js 18 or later and can be initialized with npx @davidpv/opsx init.", "body_md": "A stack-agnostic CLI that scaffolds **spec-driven development** on top of [OpenSpec](https://github.com/Fission-AI/OpenSpec), for any of [opencode](https://opencode.ai), [Claude Code](https://docs.claude.com/en/docs/claude-code) and [Codex](https://developers.openai.com/codex).\n\nThe core idea: **Spec → Plan → Code.** Code is the last artifact produced, never the first. OpenSpec structures every change as proposal + specs + design + tasks, and opencode executes the implementation with full traceability back to the requirements.\n\nTwo planes coexist and must not be confused. The **management plane** (Jira vocabulary) decides *what work exists and tracks it*; the **governance plane** (OpenSpec) decides *how the system must behave* — and only the latter authorizes code:\n\n```\nflowchart LR\n    subgraph MGMT[\"Management plane — what work exists (Jira)\"]\n        direction LR\n        D[\"Discovery<br/>backlog/discovery/\"] --> T[\"Task PROJ-123<br/>backlog/tasks/<br/>goal + acceptance criteria\"]\n    end\n\n    subgraph GOV[\"Governance plane — how the system must behave (OpenSpec)\"]\n        direction LR\n        C[\"Change<br/>proposal + delta specs<br/>+ design + steps\"] -- \"/opsx:archive\" --> S[\"openspec/specs/<br/>SOURCE OF TRUTH\"]\n        S -. \"read before proposing\" .-> C\n    end\n\n    CODE[\"Code<br/>feature branch, commits, PR\"]\n\n    T -- \"a task authorizes NO code<br/>it only motivates a change\" --> C\n    C -- \"the ONLY gate to code\" --> CODE\n    CODE -. \"traces back: Jira / Change / Task footers\" .-> T\n```\n\nA task being well written changes nothing: implementation starts only when an OpenSpec change exists, is reviewed, and its branch gate is resolved. If a task ever contains design or implementation detail, that content belongs in the change — `task-reviewer`\n\nflags it. This is what keeps the workflow *spec-driven* even though the backlog speaks Jira: **Jira rules the backlog, the spec rules the code.**\n\nTo run `opsx init`\n\nitself you only need **Node.js >= 18** (`npx @davidpv/opsx init`\n\nworks with nothing else installed — it just writes files). To actually *use* the workflow it scaffolds, you need:\n\n| Requirement | Needed for | Install |\n|---|---|---|\nGit repository |\nThe whole workflow assumes git (branches, gates, traceability) | `git init` |\nOpenSpec CLI (required) |\nEvery `/opsx:*` command shells out to `openspec` |\n`npm install -g @fission-ai/openspec` |\nAt least one agent CLI |\nRunning the commands/skills | opencode: `npm install -g opencode-ai` · Claude Code: `npm install -g @anthropic-ai/claude-code` · Codex: `npm install -g @openai/codex` |\n`gh` or `glab` (optional) |\n`/pr-open` and `/ship` PR automation; without them the PR description is written to `backlog/exports/pr/` |\n|\n\nYou don't need all three agent CLIs — only the ones you selected as targets during `init`\n\n. `opsx doctor`\n\nchecks all of this and tells you exactly what's missing.\n\n```\n# In your existing project\nnpx @davidpv/opsx init          # pick targets: opencode / Claude Code / Codex, configure branches, Jira key, language\nnpx @davidpv/opsx doctor        # verify required tooling (openspec CLI, agent CLIs)\nnpx @davidpv/opsx update        # after upgrading the opsx package: deploy new commands/skills to your project\n```\n\n`init`\n\nwrites the shared, tool-agnostic layer once (`workflow.yaml`\n\n, `AGENTS.md`\n\nmanaged block, `backlog/`\n\n, `templates/`\n\n, `openspec/`\n\n) and a native layer per agent: `.opencode/`\n\n(commands + skills + agents + `opencode.json`\n\nmerge), `.claude/`\n\n(commands + skills + subagents + `settings.json`\n\nmerge + `CLAUDE.md`\n\nimporting `AGENTS.md`\n\n), `.codex/skills/`\n\n(skills; commands and reviewers compiled as skills, since Codex has no project-level slash commands or subagents).\n\nThen, inside your agent (shown here with opencode):\n\n```\n/start                            # guided entry: routes you to the right first command\n/req-capture checkout-flow        # 0. requirements interview → discovery doc\n/task-generate checkout-flow      # 1. tasks with Jira IDs (PROJ-123)\n/task-enrich PROJ-123             #    edge cases, scenarios, estimate\n/task-jira PROJ-123               #    export as Jira wiki markup\n/opsx:propose speed-up-search     # 2. proposal + delta specs + design + tasks\n/review-change speed-up-search    # 3. audit before building\n/opsx:apply                       # 4. implement task by task\n/git-commit                       #    semantic commits traced to tasks\n/pr-open                          # 5. PR against the integration branch\n/ship                             # 6. validate + archive + merge\n```\n\nStages 0–1 are optional for small technical changes; everything from `/opsx:propose`\n\non is mandatory.\n\nThe flow is **guided**: `/start`\n\nis the entry point — it asks how the work begins (existing Jira ticket → `/task-import`\n\n; no ticket, propose directly → `/opsx:explore`\n\n/`/opsx:propose`\n\n; no ticket, create the task first → `/task-new`\n\nor `/req-capture`\n\n) and routes you there. Every command ends by suggesting the next step, and `/next`\n\ntells you where you are and what to do whenever you're lost or coming back to the repo (it inspects branch, task frontmatter, change artifacts, and PR state).\n\nFull map of flows and options. 🧑 = needs human interaction, 🤖 = agentic (runs on its own), 🧑🤖 = agentic with human checkpoint (approval, language gate, or fallback).\n\n```\nflowchart TD\n    classDef human  fill:#fff3cd,stroke:#996f00,color:#000\n    classDef agent  fill:#d6eaf8,stroke:#1a5276,color:#000\n    classDef mixed  fill:#e8daef,stroke:#6c3483,color:#000\n    classDef art    fill:#eaeded,stroke:#7f8c8d,color:#000\n\n    START([\"New work arrives\"]) --> ENTRY[\"/start 🧑<br/>guided entry\"]:::human\n    ENTRY --> CASE{\"How does this work start?\"}\n\n    %% Entry routes\n    CASE -- \"Jira ticket exists\" --> IMP[\"/task-import 🧑🤖<br/>paste ticket + normalize + task-reviewer\"]:::mixed\n    IMP --> ST\n    CASE -- \"no ticket:<br/>investigate / propose\" --> PROP\n    CASE -- \"no ticket:<br/>create task first\" --> SIZE{\"Initiative or single task?\"}\n    SIZE -- \"single task\" --> TN[\"/task-new 🧑🤖<br/>mini interview + draft ID\"]:::mixed\n    TN --> ST\n\n    %% Product phase (optional)\n    SIZE -- initiative --> RC[\"/req-capture 🧑<br/>interview + language gate\"]:::human\n    RC --> DISC[\"backlog/discovery/topic.md\"]:::art\n    DISC --> SG[\"/task-generate 🧑🤖<br/>language gate + Jira IDs from user\"]:::mixed\n    SG --> ST[\"backlog/tasks/PROJ-123.md\"]:::art\n    ST --> SE[\"/task-enrich 🤖<br/>asks only on ambiguity\"]:::agent\n    SE --> RS[\"/review-task 🤖\"]:::agent\n    RS -- REVISE --> SE\n    RS -- APPROVE --> JIRAQ{\"Export to Jira?\"}\n    JIRAQ -- yes --> SJ[\"/task-jira 🧑🤖<br/>language gate + translation\"]:::mixed\n    SJ --> JEXP[\"backlog/exports/jira/\"]:::art\n    JEXP --> PASTE[\"paste into Jira & confirm real key 🧑\"]:::human\n    JIRAQ -- no --> PROP\n    PASTE --> PROP\n\n    %% Spec phase\n    PROP[\"/opsx:propose 🧑🤖<br/>you describe, agent writes artifacts\"]:::mixed\n    PROP --> CHG[\"openspec/changes/name/\"]:::art\n    CHG --> RV[\"/review-change 🤖<br/>spec-reviewer + validate --strict\"]:::agent\n    RV -- REVISE --> FIX[\"fix artifacts 🤖\"]:::agent\n    FIX --> RV\n    RV -- APPROVE --> BRANCH{\"Branch gate 🧑<br/>where to implement? (git.work_mode)<br/>blocks any code until resolved\"}\n    BRANCH -- \"create feature branch<br/>(reviewed PR)\" --> APPLY\n    BRANCH -- \"directly on develop<br/>(flexible mode, no review)\" --> APPLY\n\n    %% Build phase\n    APPLY[\"/opsx:apply 🤖\"]:::agent\n    APPLY --> DRIFT{\"Spec wrong or drifted?\"}\n    DRIFT -- yes --> SYNC[\"/opsx:sync 🤖\"]:::agent\n    SYNC --> APPLY\n    DRIFT -- no --> GC[\"/git-commit 🧑🤖<br/>agent drafts, you approve message\"]:::mixed\n    GC -- more tasks --> APPLY\n\n    %% Delivery phase\n    GC -- all tasks done --> ONBR{\"On a feature branch?\"}\n    ONBR -- \"no: on develop\" --> SHIPD[\"/ship 🤖<br/>validate + archive + push<br/>(skips PR and review)\"]:::agent\n    SHIPD --> DONE\n    ONBR -- yes --> PRO[\"/pr-open 🧑🤖<br/>language gate + draft decision\"]:::mixed\n    PRO --> CLI{\"gh / glab available?\"}\n    CLI -- yes --> PR[\"PR created against develop\"]:::art\n    CLI -- no --> PRF[\"description in backlog/exports/pr/<br/>you open the PR manually 🧑\"]:::human\n    PRF --> PR\n    PR --> REVIEW[\"code review & approval 🧑\"]:::human\n    REVIEW --> SHIP[\"/ship 🤖<br/>validate + archive + merge\"]:::agent\n    SHIP --> MERGEQ{\"CLI + CI green?\"}\n    MERGEQ -- yes --> DONE([\"change archived, task done, branch deleted\"])\n    MERGEQ -- no --> WEBUI[\"merge in web UI 🧑\"]:::human\n    WEBUI --> DONE\n```\n\n\"Spec wrong or drifted?\"— checkpoint during implementation.Wrong: while coding you discover a requirement or scenario was incorrect or incomplete.Drifted:`openspec/specs/`\n\nno longer matches what the code actually does (hotfixes, old unspec'd commits). In both cases the rule is the same: never diverge silently — run`/opsx:sync`\n\nto fix the spec first, then resume`/opsx:apply`\n\n. Code must always trace back to a correct spec.\n\nBranch policy—`main`\n\nis release-only and never worked on directly.`git.work_mode`\n\nin`workflow.yaml`\n\ndecides the rest:`flexible`\n\n(default) lets you implement and commit directly on the integration branch —`/ship`\n\nthen just validates, archives, and pushes, skipping PR and review;`feature`\n\nmakes feature branches + PR mandatory. A mandatorybranch gateruns before`/opsx:apply`\n\nwrites any code: the working branch must be resolved (created and checked out) first;`/git-commit`\n\nre-checks at commit time as a safety net. Feature branches are named`feature/<task id>-<change>`\n\nwhen the change is linked to a backlog task with a real Jira key (e.g.`feature/PROJ-123-speed-up-search`\n\n),`feature/<change>`\n\notherwise.\n\nHuman checkpoints, summarized: the `/start`\n\nentry questions, the `/req-capture`\n\ninterview, pasting an existing Jira ticket (`/task-import`\n\n), every language gate (es/en, mandatory on client-facing text), choosing where to implement (feature branch vs develop), commit message approval, providing Jira IDs and pasting Jira exports, PR code review, and merging via web UI when no platform CLI exists. Everything else runs agentically.\n\nTraceability chain: **Discovery → Task (Jira) → Change → tasks.md step → Commit → PR**. Each link is recorded where it happens: task frontmatter (`change:`\n\n), commit footers (`Change:`\n\n/`Task:`\n\n/`Jira:`\n\n), PR description. Task IDs ARE Jira keys (`PROJ-123`\n\n, provided by you; `PROJ-Dnn`\n\ndrafts until the issue exists). Note the naming: a *task* is a backlog/Jira work item; `tasks.md`\n\ninside a change holds implementation steps.\n\nEach change lives in `openspec/changes/<name>/`\n\nuntil archived. Archiving merges its delta specs into `openspec/specs/`\n\n, the living description of how the system behaves. `workflow.yaml`\n\nconfigures branches (git-flow by default: `feature/* → develop`\n\n), commit convention, and Jira export — commands read it, so the pipeline stays platform-agnostic.\n\n```\n.\n├── AGENTS.md                  # Rules every agent must follow\n├── opencode.json              # opencode project config\n├── workflow.yaml              # Pipeline config: branches, commits, Jira (tool-agnostic)\n├── templates/                 # discovery.md, task.md, pr-description.md\n├── backlog/\n│   ├── discovery/             # Requirements-gathering notes (/req-capture)\n│   ├── tasks/                 # Tasks with Jira IDs (/task-generate, /task-enrich)\n│   └── exports/               # jira/ (wiki markup) and pr/ (fallback PR descriptions)\n├── .opencode/\n│   ├── agents/\n│   │   ├── spec-reviewer.md   # Subagent: audits changes before apply/archive\n│   │   └── task-reviewer.md   # Subagent: audits tasks (sizing, testability)\n│   ├── commands/\n│   │   ├── start.md           # /start — guided entry point (routes new work)\n│   │   ├── req-capture.md     # /req-capture — requirements interview\n│   │   ├── task-*.md          # /task-import|new|generate|enrich|jira\n│   │   ├── review-*.md        # /review-change, /review-task\n│   │   ├── git-commit.md      # /git-commit — semantic commits with traceability\n│   │   ├── pr-open.md         # /pr-open — platform-agnostic PR creation\n│   │   ├── ship.md            # /ship — validate + archive + merge\n│   │   └── opsx-*.md          # /opsx:* — OpenSpec lifecycle (generated)\n│   └── skills/                # OpenSpec workflow skills (generated)\n└── openspec/\n    ├── config.yaml            # Project context + per-artifact rules\n    ├── specs/                 # Source of truth (current behavior)\n    └── changes/               # In-flight changes; archive/ keeps history\n```\n\n| Command | Stage | What it does |\n|---|---|---|\n`/start` |\nEntry | Guided entry point: asks how the work starts and routes you (ticket exists / propose directly / create task first) |\n`/next` |\nAny | Detect where you are in the pipeline and suggest the next step |\n`/req-capture <topic>` |\nDiscover | Guided requirements interview → `backlog/discovery/<topic>.md` |\n`/task-import <id>` |\nTasks | Import an existing Jira ticket (you paste it) into `backlog/tasks/` |\n`/task-new <title>` |\nTasks | Create a single task directly, without a discovery doc (draft ID) |\n`/task-generate <topic>` |\nTasks | Slice a discovery doc into tasks; you provide the Jira IDs |\n`/task-enrich <id>` |\nTasks | Add edge cases, unhappy paths, estimate; runs task-reviewer |\n`/review-task <id>` |\nTasks | Audit a task: sizing, testability, traceability |\n`/task-jira <id|all>` |\nTasks | Export tasks as Jira wiki markup to `backlog/exports/jira/` |\n`/opsx:propose <name>` |\nSpec | Create a change: proposal, delta specs, design, tasks |\n`/opsx:explore` |\nSpec | Investigate the codebase/specs before proposing |\n`/review-change <name>` |\nSpec | Spec-reviewer audit + `openspec validate --strict` |\n`/opsx:apply` |\nBuild | Implement the tasks of a change |\n`/git-commit` |\nBuild | Conventional commit traced to change/step/Jira task |\n`/opsx:sync` |\nBuild | Sync specs with reality when they drift |\n`/pr-open [name]` |\nDeliver | Create the PR against the integration branch (gh/glab/file fallback) |\n`/ship [name]` |\nDeliver | Validate + `/opsx:archive` + merge + close the task |\n`/opsx:verify <name>` |\nDeliver | Validate implementation matches change artifacts (run before archive) |\n`/opsx:archive` |\nDeliver | Merge delta specs into `openspec/specs/` and file the change |\n\n- Copy or clone this repo and re-init git.\n- Fill the\n`context:`\n\nblock in`openspec/config.yaml`\n\nwith your stack and conventions. - Adjust\n`workflow.yaml`\n\n: branches (git-flow vs trunk-based), Jira project key, platform. - Extend\n`AGENTS.md`\n\nwith project-specific rules. - Start with\n`/start`\n\n— it routes you to`/task-import`\n\n(existing ticket),`/req-capture`\n\n/`/task-new`\n\n(create the task first), or`/opsx:propose`\n\n(direct change).\n\nSince opsx is an evolving toolkit (new commands like `/opsx:verify`\n\n, skill improvements, schema updates), keeping it current ensures your workflow stays complete.\n\nUpgrading the npm package alone does **not** deploy new commands or skills to an existing project — they live in the shipped `payload/`\n\ndirectory and must be written to your project's `.opencode/`\n\n(or `.claude/`\n\n, `.codex/`\n\n) structure by `opsx update`\n\n.\n\n```\n# Step 1 — upgrade the opsx package itself\nnpm update -g @davidpv/opsx\n\n# Or, if installed locally in your project:\nnpm update @davidpv/opsx\n\n# Step 2 — deploy new/changed files to your project\nnpx @davidpv/opsx update\n```\n\n**Both steps are required.** Step 1 updates the code that `npx`\n\nresolves; step 2 writes the new payload files (commands, skills, agents, templates) into your project. If you skip step 2, new capabilities like `/opsx:verify`\n\nwon't appear even though the package is the latest version.\n\nThe command compares the shipped payload against what's already on disk in your project:\n\n| Scenario | What happens |\n|---|---|\nNew file (e.g., `opsx-verify.md` added in a newer opsx version) |\nCreated automatically |\nChanged file, not user-modified |\nSilently overwritten |\nChanged file, user-modified |\nKept as-is (use `opsx update --force` to overwrite) |\n`AGENTS.md` managed block |\nOnly the `<!-- OPSX:START -->` block is refreshed; your surrounding custom rules are preserved |\n`opencode.json` / `settings.json` |\nNew top-level keys merged in; your overrides stay |\n\nTo see exactly what changed on the last run, check the output — it lists `Added:`\n\n, `Updated:`\n\n, and `Kept (not touched):`\n\nfiles.\n\n```\nnpm outdated -g @davidpv/opsx     # check if a newer npm version exists\nnpx @davidpv/opsx --version       # show installed version\nnpx @davidpv/opsx doctor          # verify tooling and project state\n```\n\nNew opsx versions are published to npm. Check the [CHANGELOG](https://github.com/anomalyco/opsx-spec-driven-development-toolkit/releases) for breaking changes before updating.", "url": "https://wpnews.pro/news/cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex", "canonical_source": "https://github.com/davidpv/opsx-spec-driven-development-toolkit", "published_at": "2026-06-18 08:43:49+00:00", "updated_at": "2026-06-18 08:53:06.300558+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["OpenSpec", "OpenCode", "Claude Code", "Codex", "Fission AI", "David P. V.", "Jira"], "alternates": {"html": "https://wpnews.pro/news/cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex", "markdown": "https://wpnews.pro/news/cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex.md", "text": "https://wpnews.pro/news/cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex.txt", "jsonld": "https://wpnews.pro/news/cli-that-enforces-spec-driven-development-with-claude-code-opencode-and-codex.jsonld"}}