A lean AI development workflow for Claude Code, Codex, and OpenCode: one agent-dev
skill that takes a change from an (optional) ticket to a pull request by running the unmodified
superpowers workflow, generated per project from one small
config file.
The generator is a zero-dependency Node script, published to npm as
@strobl_dev/adw. The consuming project does
not need to be a Node project. It works in any repo (C#/.NET, Go, Rust, …) — the only
requirement is Node on the machine that runs the generator (your dev box and CI). Pin a version
(e.g. @0.30.0) so devs and CI stay in sync.
| File / dir | Owner | Committed? |
|---|---|---|
ai-project.json |
you — project identity + ticketing backend choice | yes |
AGENTS.md |
you — create with your coding agent's native /init; agent-dev reads it first (add an End-to-end testing section for agent-run e2e checks) |
yes |
agent-custom/ |
you (optional) — per-project tweaks to the skill body (see Customizing the skill) | yes |
docs/superpowers/{specs,plans}/ |
written by superpowers when a change needs them; the agreed scope is also posted to the ticket | yes |
.agents/skills/show-me/, .claude/skills/show-me, skills-lock.json |
third-party show-me skill, installed by init/upgrade with npx skills at the commit this adw version pins; never rendered or checked |
yes |
.claude/, .codex/, .opencode/, .agents/ |
generated output | yes (review diffs on update) |
.mcp.json |
merged (azure-devops backend only) — the shared ado server entry; other servers preserved |
yes |
.codex/config.toml |
merged (azure-devops backend only) — the Codex project-local ado MCP server entry; other Codex settings preserved |
yes |
Everything else (the skill source, ticketing/cost includes, the generator) lives in the package and
updates with it. See agent-src/README.md for how the sources are authored.
npx runs the bin straight from npm — nothing is installed into the repo:
# 1. run the guided onboarding — writes ai-project.json, prints the recommended tooling
npx @strobl_dev/adw@0.30.0 init
# 2. (the interview sets project identity, repository, and ticketing.backend.
# For azure-devops it also captures org/project + process template and pre-fills
# the state mapping; generate then merges the `ado` server into .mcp.json
# and .codex/config.toml.
# Install the tooling it lists — see Tooling below — then create AGENTS.md
# with your coding agent's native /init.)
# 3. generate the platform files
npx @strobl_dev/adw@0.30.0 generate
# 4. commit ai-project.json and the generated dirsPin the version (@0.30.0) so devs and CI stay in sync — a C# repo has no lockfile to do it for you.
Versions up to v0.23.0 were only distributed from Git (npx github:marxxxx/ai-dev-workflow#v0.23.0).
npm 12 refuses git specs by default (EALLOWGIT); add --allow-git=all if you still need one.
init prints this list. You install them — for whichever of Claude Code / Codex / OpenCode you
run. This workflow deliberately does not carry install instructions: they differ per harness and go
stale. Follow each project's own docs.
| Tool | What agent-dev uses it for |
|
|---|---|---|
| superpowers | required | The whole workflow: brainstorming → worktree → plan → implementation (TDD, review, verification) → finishing the branch |
| serena | recommended | MCP server: semantic, symbol-level code navigation and editing |
| playwright | recommended | MCP server: drives a real browser for the optional end-to-end check |
| context7 | recommended | MCP server: up-to-date library and framework documentation |
| ccusage | recommended | CLI behind the cost summary; without it the summary names the gap |
| show-me | installed for you | Compact views of a spec or plan for your review — see below |
For the azure-devops backend, generate merges the ado MCP server (pinned to
@azure-devops/mcp@2, whose tool names the Azure DevOps ticketing include is written against) into
.mcp.json and .codex/config.toml — nothing to install by hand.
The gitea backend requires tea, Gitea's official CLI, installed
and logged in — see Gitea backend setup.
| Command | Effect |
|---|---|
generate (default) |
Render all platform files to the project root |
check |
Render in memory and diff against disk; exit 1 on drift (CI / pre-commit gate) |
upgrade |
The command for every version bump. Deletes the files the pre-v0.23 workflow generated and strips obsolete keys from ai-project.json, then generates, then installs or updates the pinned show-me skill (unless "skills": { "show-me": false }). Only deletes files carrying the generator's banner for a removed unit; other skills and subagents are never touched. A failed skill install is a warning with the manual command, never an error. --dry-run lists the changes without writing anything. See Updating. |
init |
Interactive onboarding: prompts for project name, repository, ticketing backend (for azure-devops, the org/project and process template, pre-filling the state mapping; for gitea, the tea login profile), asks whether to install the show-me skill (default yes; runs npx skills add), then writes ai-project.json. It prints the tooling for you to install, and points you to create AGENTS.md with your coding agent's native /init. Falls back to a template scaffold when stdin is not a TTY. Never overwrites without confirmation. |
e2e |
adw e2e <base-commit|.> <ticket> [harness]: replay agent-dev on one of your tickets in a throwaway clone and score the result. See E2E on your project. |
All commands accept --root <dir> to target a project root other than the current directory.
ai-project.json and per-unit tokens cover most tuning. To change the instructions of the shipped
skill, add a committed agent-custom/ directory that mirrors the source layout
(agent-custom/skills/<name>/). Two knobs:
| File | Effect |
|---|---|
agent-custom/skills/agent-dev/append.md |
Appended to the package body. The safe default — upstream improvements keep flowing on update. |
agent-custom/skills/agent-dev/body.md |
Full override — replaces the package body. You then own that body (it no longer tracks upstream). |
Both files support the same {{tokens}} as package bodies ({{project.name}}, {{repo.slug}}, …);
an unresolved token fails the generator with a clear error. Example:
<!-- agent-custom/skills/agent-dev/append.md -->
## House rules
Always run `npm run lint` before finishing the branch.Then generate and commit. agent-custom/ files are inputs to generation, so check still
passes and still catches hand-edits to the generated files.
Run /agent-dev [ticket-id] (Codex: $agent-dev). The skill adds only what superpowers doesn't know
about — the ticket and its comments — and otherwise follows the superpowers skills as written:
- Start — record the start time, read
AGENTS.md; with a ticket, read it and move it toin-progress. Without one, it asks whether to create a ticket; if you say yes, it creates one once brainstorming has settled the scope and uses it for the rest of the run. A ticket alreadyin-progressresumes where the earlier run stopped: straight to planning if a spec file was approved, to the next unfinished plan task if a plan exists (offering a partial cost summary for the interrupted session), otherwise back into brainstorming with the ticket as context. - Brainstorming — the full requirements interview with you. A ticket (often an upstream ticket
with too little detail to implement from) is only background context; it never replaces the
interview. For changes with user-visible behavior it also asks whether the agent should test
end to end or you prefer to test manually. Superpowers sizes the work: a quick task goes ahead,
a small change is agreed in chat, a larger one gets a written spec in
docs/superpowers/specs/. Once the scope is agreed, the ticket must say what gets built: the spec, or the agreements from the conversation, is posted as an Approved Spec comment (split into numbered comments if it exceeds the backend's limit; the file backend gets just a spec's path); nothing is posted if the ticket already said everything. The plan, if any, stays in the repo. - The rest of the superpowers workflow — whatever superpowers chains from brainstorming
(today: worktree, plan, implementation with TDD and review, finishing the branch).
agent-devdoes not restate that chain; it only hooks in at events (scope agreed, branch created, integration offered or the workflow finished), so superpowers releases need no changes here. If you chose agent testing, an end-to-end check runs before integrating. Instead of superpowers' merge/PR/keep menu, the run always ends with a draft PR (Gitea: aWIP:title), so a human reviews and tests before publishing it. - Close-out — with a ticket, post an Implementation Summary comment (approach,
consequences, possible side effects, what to watch when testing) and a Cost Summary comment,
then move the ticket to
review. Without a ticket, both are printed instead.
Names derive from the ticket's number and a short version of its title: branch
<number>_<short_title_slug> (e.g. 42_user_login_form), PR title <number>: <short title> (e.g.
42: User login form). Without a ticket, both drop the number.
Ticket states are just new → in-progress → review; acceptance and closing stay with the human.
Nothing beyond the agreed scope and those two comments is written to the ticket.
Superpowers' specs and plans are thorough, which makes them long. When one is handed to you for
review, agent-dev leads with a compact view of its design from HumanLayer's
show-me skill — pseudocode, call and file trees, diff-shaped
changes, Mermaid diagrams — so you can judge the approach before reading the file. The file stays
the record. show-me is user-invoked only (/show-me works any time); agent-dev reads it at those
two moments. init and upgrade install it into the project at the commit the adw version pins;
generate and check print a note (exit code unchanged) when it is missing or outdated. Opt out
with "skills": { "show-me": false } in ai-project.json.
Superpowers itself never starts your app: its verification is satisfied by passing tests. So
agent-dev adds one decision and one step. During brainstorming, for a change with user-visible
behavior (UI, user flows), it asks whether you want the agent to test it end to end or you
prefer to test it manually, and records the answer in the agreed scope. If you chose the agent, it starts
the app, exercises the changed flows in a browser via Playwright, captures evidence, and shuts the
app down before opening the draft PR. The Implementation Summary always states the outcome:
tested by the agent (with what was checked), left to manual testing at your request, or not tested
and why. A missing setup, missing browser tooling, or a failed startup never blocks the PR; it is
reported there instead.
The workflow ships no start/stop scripts, because starting the app differs per stack and per OS.
Describe it in prose in an End-to-end testing section of your AGENTS.md, and the agent turns
that into commands for the OS it runs on. Cover:
- the backing services to start (database, cache, broker) and any migrate/seed steps;
- how to start the app and how to tell it's reachable;
- the base URL and ports;
- the test-locator attribute to select elements by (for example
data-testid).
Without that section, the agent tells you during brainstorming if you ask it to test end to end.
One total per run — tokens and estimated USD — from a single ccusage@20 session --json report,
which covers every harness in one JSON shape. It counts the sessions active in the run's time
window, which includes Codex/OpenCode subagent sessions with no bookkeeping during the run. The
report has no project field, so on a shared host other sessions active in that window are counted
too; the comment lists every counted session. In the container runtime the home volume is per
project, so the count is project-scoped there. ccusage's JSON is not a stable API, which is why the
major version is pinned; models ccusage cannot price are named instead of shown as $0. If a run
fails, is abandoned, or is interrupted, agent-dev offers a partial cost summary so failed
attempts still count; a ticket can therefore carry several cost comments, which together are its
cost. Cost reporting never blocks the close-out. The procedure lives in the generated
.agents/includes/cost.md.
Add it as a dev dependency with an exact version, and wire up scripts:
npx @strobl_dev/adw@<new-version> upgrade --dry-run # list what would change
npx @strobl_dev/adw@<new-version> upgrade # or bump the pinned version, then `upgrade`upgrade cleans up what older versions left behind, generates, and brings the pinned third-party
skills (show-me) to this version's commit. Review the diff in .claude//.codex//.agents/ and
commit. generate never touches ai-project.json; upgrade only removes keys that are obsolete
and records the skills opt-in. Run
check in CI to catch a stale or mismatched version. Every generated file carries a
DO NOT EDIT — generated from agent-src/… banner.
adw e2e first-run setup. The first adw e2e run after this update starts a new baseline (e2e.language
changes the config hash); no upgrade step is needed.
Upgrading to v0.30.0. agent-dev now names the superpowers skills by bare id (brainstorming, not
superpowers:brainstorming), which OpenCode 2 needs; upgrade regenerates it.
Upgrading to v0.29.1. The compose.ai-dev.yml that init wrote lacked the KILL capability,
so the container's init process (tini) could not forward signals to the agent and stopped the
container with Unexpected error when forwarding signal: 'Operation not permitted' (for example
on a terminal resize). The file is project-owned and upgrade does not change it; upgrade,
generate and check print a note until you add - KILL under cap_add of the ai-dev-workflow
service.
Upgrading to v0.29.0. adw now installs the show-me skill. Run upgrade instead of
generate: it records "skills": { "show-me": true } in ai-project.json and installs the skill
with npx skills (needs network access to npm and GitHub). To decline, set "show-me": false first. Until it is
installed, generate and check print a note, and agent-dev mentions it once per review.
Upgrading to v0.25.0. New upgrade command: it cleans up after the pre-v0.23 workflow (see
v0.23.0 below). init no longer asks for the project slug, Serena project name, description or
default branch, and {{project.slug}}, {{project.serena}}, {{project.description}} and
{{repo.defaultBranch}} are no longer tokens. upgrade removes those keys from ai-project.json;
generate ignores them. The generated output is unchanged unless an agent-custom/ file used one of
these tokens.
Upgrading to v0.24.0. The package moved to the npm registry as @strobl_dev/adw; replace
github:marxxxx/ai-dev-workflow#vX.Y.Z with @strobl_dev/adw@X.Y.Z in scripts, CI and
devDependencies. The generated output is unchanged. The bin is now adw; ai-dev-workflow still
works as an alias.
Upgrading to v0.23.0. The custom multi-agent workflow is replaced by the single agent-dev
skill. dev-cycle, product-architect, the developer / code-reviewer / qa-engineer agents,
the developer journal/handoff, and the e2e include are gone; ticket states shrink to
new → in-progress → review. superpowers is now required. generate does not delete files it no
longer produces; run upgrade instead, which removes them and then generates:
npx @strobl_dev/adw@0.30.0 upgrade --dry-run # list what would change
npx @strobl_dev/adw@0.30.0 upgradeIt deletes these files, but only where they still carry the generator's DO NOT EDIT banner (a
hand-written file with the same name is kept and reported), plus the unit directories left empty:
.claude/agents/{developer,code-reviewer,qa-engineer}.md
.codex/agents/{developer,code-reviewer,qa-engineer}.toml
.opencode/agents/{developer,code-reviewer,qa-engineer}.md
.claude/skills/{dev-cycle,product-architect}/SKILL.md
.agents/skills/{dev-cycle,product-architect}/{SKILL.md,agents/openai.yaml}
.opencode/skills/{dev-cycle,product-architect}/SKILL.md
.agents/includes/{handoff,e2e-runtime}.md
It also removes the keys nothing reads anymore from ai-project.json: git.branchPattern (branch
and PR names now derive from the ticket), ticketing.azureDevOps.stateMapping.{test,failed,acceptance-test},
project.{slug,serenaProject,description}, repository.defaultBranch, and the older ticketing.itemNoun,
e2e.{up,down,readinessTimeout,logsDir}, app and handoff blocks (the rest of e2e is
adw e2e's settings and stays). If a project's tokens or agent-custom/ files used
{{project.slug}}, {{project.serena}}, {{project.description}} or {{repo.defaultBranch}},
generation now fails on that token; replace it, or define it under tokens in ai-project.json.
agent-custom/ files for the removed units and the scripts/e2e-up / scripts/e2e-down scaffolded
by early versions are project-owned: upgrade reports them but leaves them in place. Move anything
you still need to agent-custom/skills/agent-dev/append.md. Tickets left in retired states
(test, failed, acceptance-test) need a manual move.
Upgrading to v0.20.0. The generator now requires Node >= 24 (previously >=18). Upgrade Node
on dev boxes and CI runners before bumping the pinned tag — older runtimes are unsupported and only get
an engine warning, not a clear error.
adw e2e replays the agent-dev workflow on one of your real tickets, at a fixed commit, and scores
the outcome. Compare runs after you change agent-custom/, AGENTS.md, the model or the adw version.
npx @strobl_dev/adw e2e <base-commit|.> <ticket> [claude-code|codex|opencode] [--repetitions n] [--model m] \
[--ticket-file <file>] [--baseline <runDir>] [--no-input] [--answers <file>] [--reconfigure]The ticket is read once from your real backend and never written: gh issue view, tea api,
az boards work-item show (the host needs az with the azure-devops extension, logged in or with
AZURE_DEVOPS_EXT_PAT set), or the file backend. --ticket-file <file> is the manual fallback: a
Markdown file whose first line is # <title>, followed by the body. --baseline <runDir> compares
with that run (.adw/e2e/<ticket>/runs/<ts>/<harness>) instead of the newest earlier one.
Every run happens in a throwaway clone at <base-commit> on the file ticketing backend, with its
remote pointing at a local bare repo, so nothing reaches your tracker or your real remote. Opening the draft PR
(gh, tea or az) is recorded and answered with a placeholder link, never sent to your git host. Its containers publish no host
ports, so a run does not clash with your own ./ai-dev.sh. The first run asks you the spec questions and
confirms drafted acceptance criteria; later runs replay your stored answers. When the agent only reports progress
and asks nothing, the run tells it to carry on (counted as nudged) instead of asking you.
Prerequisites: Docker and a git repo. On the first run adw e2e asks for the rest and writes it:
ai-project.json, if there is none: it offers theinitinterview first;- the container files (
compose.ai-dev.yml), ifinitdid not write them. The run builds a thin layer withacpxand the ACP adapters on your dev image (about 480 MB, cached until the image or the pins change; older layers are removed; the build uses your proxy variables andAGENT_CA_DIR); - the
e2eblock ofai-project.json: the agent's harness and model, the judge model,testCommand, provider variables and the language, shown before it is written; - values that
e2e.envnames but nothing sets (secrets are read without echo). They can be saved to.env.ai-devnext to the project, which the containers read; - the ticket, when your backend cannot be read from the host: it is fetched in your dev container, then by opening
$EDITOR(both interactive only).--ticket-fileskips this; - the logins, see below;
- an
AGENTS.mdsection "End-to-end testing" saying how to start and exercise the app, for the app-level check. If it is missing you can have the answerer role's harness and model draft it from your committed files ([d]raft), answer four questions ([a]sk) or [s]kip it; it is appended only after you confirm.e2e.testCommandoptionally runs your own tests too.
--no-input asks nothing, on a terminal too, and fails on what is missing; without a terminal nothing is asked
either. --answers <file> answers the questions from a JSON file nested by the dotted question keys, e.g.
{"setup":{"writeE2e":"y"},"e2e":{"agent":{"harness":"codex"}}}; every yes/no question it leaves out counts as
"no", so nothing is written without an explicit answer. --reconfigure asks the e2e questions again, with the
stored values as defaults. .adw/e2e/local.json (git-ignored) keeps this machine's choices: the volume logins and
a skipped AGENTS.md section are not asked again; delete it to be asked again.
When the base commit predates the AGENTS.md section, the run overlays the current one onto the clone.
. as the base commit runs on your working tree: uncommitted changes and untracked files on top of HEAD, without
committing anything (git-ignored files such as .env.ai-dev stay out; .adw/e2e/ stays as committed). Use it to try a
change, e.g. to AGENTS.md, before you commit it: adw e2e . 1 codex. The run is compared with the newest run on
the committed HEAD, and is itself never picked as a baseline; its report says + working tree.
Logins: the containers never share the project's agent-home volume, they get a login staged from the host.
- Codex:
~/.codex/auth.jsonon the host, orOPENAI_API_KEY. If you only logged in through./ai-dev.sh, the setup offers to copy the login from the project's volume. - OpenCode:
~/.config/adw/opencode-credentials.json, anopencode auth exportfile, or provider variables ine2e.env. The containers import it withopencode auth import. If the file is missing, the setup offers to export the login of the project's volume (after./ai-dev.sh opencode auth login) and prints the host command. - Claude Code: a long-lived token, never taken from the volume. Run
claude setup-tokenon the host and exportCLAUDE_CODE_OAUTH_TOKEN, or let the setup run it (docker run --rm -it --entrypoint claude <dev image> setup-token; it asks you to paste the token without echo and offers to save it to.env.ai-dev). For Bedrock or Vertex setCLAUDE_CODE_USE_BEDROCK/CLAUDE_CODE_USE_VERTEXand that provider's variables ine2e.env.
Each of the four LLM roles (agent, answerer, verifier, review) has its own harness and model, so a
project can stay inside the providers it may use. In ai-project.json:
"e2e": {
"agent": { "harness": "codex", "model": "gpt-6.1-sol" },
"answerer": { "harness": "opencode", "model": "azure/gpt-6.1-mini" },
"verifier": { "harness": "opencode", "model": "azure/gpt-6.1", "timeoutSec": 900 },
"review": { "harness": "opencode", "model": "azure/gpt-6.1" },
"env": ["AZURE_API_KEY", "AZURE_RESOURCE_NAME"],
"secretEnv": ["AZURE_API_KEY"],
"language": "en"
}A role without its own harness uses the agent's; a role without its own model uses that CLI's default
model. So one login is enough. language (en or de) is the language of the simulated user's answers; the
setup detects it from the ticket.
env lists variable names passed into the containers by name. Values of names in e2e.secretEnv are removed from transcripts and logs; other e2e.env values are kept as they are, so list every key or token there.
Under .adw/e2e/<ticket>/, commit ticket.md, answers.json and acceptance.md; runs/ holds the
per-run records and reports and is git-ignored (the command writes .adw/e2e/.gitignore). A run is
compared with the newest earlier fully replayed run with the same base commit, ticket and harness.
Before the first trial, one short call per answerer and judge harness and model checks that the model exists and the
login works, so a misspelled model fails in seconds, not after the agent's trial. A run whose verifier or review fails
(other than the verifier being skipped) is aborted: it gets no compare verdict and is never a baseline.
Trade-offs: no calibration, one repetition by default, and your first-time answers shape the result, so scores are less precise than the Harbor self-test. Variance between identical runs is large: compare medians over several repetitions, never single runs. When a judge shares the agent's harness and model, the report warns that it is self-judging.
The workflow ships a container runtime with Claude Code, Codex, OpenCode, Serena,
Playwright/Chromium, Context7, Azure DevOps MCP, Azure CLI (with the azure-devops extension),
Superpowers and ccusage.
Answer yes to Set up the dev container? during init: it writes compose.ai-dev.yml
(preset with the published image for the detected stack: Node, .NET or generic), a
certificate overlay, the launchers ai-dev.sh / ai-dev.ps1 and AI-DEV.md with the logins
for your coding agents and ticketing backend, then prints the exact next steps. It also asks
whether to enable the Docker overlay (AGENT_DIND=1, root-equivalent) and company root
certificates (AGENT_CA_DIR), and writes those to the git-ignored .env.ai-dev. These files are
project-owned. Run the generator on the host; the image does not install or run it.
./ai-dev.sh claude --dangerously-skip-permissions # PowerShell: .\ai-dev.ps1 …Two derived images add app-facing runtimes (ai-dev-workflow-node, ai-dev-workflow-dotnet). The
container assets live under docker/ and are hand-maintained, not generated. See
docker/README.md for the run model, mounts, auth/persistence, UID/GID mapping,
and how a consuming project extends the base.
To work on this repository itself in a container — tool stack only, without the generated
agent-dev skill — use the repo-root compose.ai-dev.yml (it sets
AGENT_TOOLSTACK_ONLY=1):
export PROJECT_ROOT="$(pwd)" HOST_UID="$(id -u)" HOST_GID="$(id -g)"
docker compose -f compose.ai-dev.yml run --rm ai-dev-workflow bashSee docker/README.md → Working on this repo in a container.
Behind a TLS-inspecting corporate proxy, set AGENT_CA_DIR and the proxy variables in the
project's git-ignored .env.ai-dev; the launchers add the certificate overlay and the published
images install the certificates at startup, so no per-company image build is needed. See
docker/README.md → Corporate TLS inspection.
End-to-end self-test. node e2e/run.mjs run exercises the whole workflow in Claude Code, Codex
and OpenCode and compares the result with the previous run — see docs/adw-e2e/README.md.
Apache-2.0