| name | specdex |
| description | End-to-end feature development loop. You describe a feature, iterate on the plan, then the team implements, reviews, ships a PR, and handles the configured PR-review bot autonomously. Notifies you at milestones. Use ONLY when the user wants the full autonomous implement→review→PR→verify loop for a multi-file feature. Do NOT use for: quick bug fixes, single-file edits, exploratory/discussion tasks, or anything the user wants to drive step-by-step. |
| triggers | ["specdex"] |
specdex: End-to-End Feature Development
You are the lead/coordinator of a development team. You plan with the user, then create a team of agents (coder, reviewer) to drive a feature from approved plan to merged PR.
The Loop
User describes feature
│
▼
┌─ SETUP ──────────────────┐
│ Create .spec/<name>/ │
│ Enter worktree │
│ Copy .spec-env → .env │
│ Set ports + project name │
└───────┬────────────────────┘
▼
┌─ PLAN (interactive) ─────┐
│ Lead enters plan mode │
│ User iterates directly │
│ with lead until approved │
└───────┬────────────────────┘
│ plan approved
▼
┌─ IMPLEMENT (autonomous) ─┐
│ Coder implements plan │
│ TDD: RED → GREEN │
│ Parallelizes independent │
│ chunks via sub-agents │
│ STAYS ALIVE for feedback │
└───────┬───────────────────┘
│ reports done
▼
┌─ REVIEW (autonomous) ────┐
│ Reviewer checks code │
│ PASS → continue │
│ FAIL → send findings to │
│ coder via SendMessage │
│ Loop until PASS (max 3) │
└───────┬───────────────────┘
│ PASS
▼
┌─ SHIP (autonomous) ──────┐
│ /pr skill: commit, push, │
│ create PR │
└───────┬───────────────────┘
│ PR created
▼
┌─ CI WATCH (autonomous) ──┐
│ Poll pipeline checks │
│ Easy fix → commit + push │
│ Hard fix → DM user + stop│
│ Loop until all green │
└───────┬───────────────────┘
│ CI green
▼
┌─ GREPTILE (autonomous) ──┐
│ Wait for Greptile review │
│ /react-to-greptile skill │
│ DM user every round │
│ Loop until score ≥ 5/5 │
└───────┬───────────────────┘
│ done
▼
COMPLETE
Modes
/specdex follows the git/gh grammar: mode = bare verb, modifier = --flag, operand = positional.
| Invocation | Mode |
|---|
/specdex <feature description> | default — plan → implement → review → ship → verify |
/specdex collaborate <feature> | human-driven session: registers + badges in the fleet, skips the team/PR automation (see Collaborate) |
/specdex configure | (re)write this project's .dex.toml (see Configuration) |
/specdex curate | run the global curator over the whole ~/.spec registry → clustered signals + proposed skill/config edits, saved as a report and viewable in the desktop Curator view (see Curate) |
/specdex resume | re-attach to the most recent non-terminal spec for this project |
/specdex accept | accept a COMPLETE spec → cleanup |
/specdex --auto-approve <plan-path> | modifier on default mode (non-interactive) |
/specdex with no args lists these modes.
Configuration (config-driven — no hardcoded vendors)
This skill names no vendor. At setup, resolve the project's integrations once via
dex config and use those throughout:
NOTIFIER=$(dex config get providers.notifier)
CI=$(dex config get providers.ci)
PR_REVIEW=$(dex config get providers.pr_review)
MUX=$(dex config get providers.multiplexer)
CI_REACTOR=$(dex config get providers.ci.reactor)
REVIEW_REACTOR=$(dex config get providers.pr_review.reactor)
SHIP_ACTION=$(dex config get hooks.on_ship)
SKIP=$(dex config get phases_skip)
MODEL_CODER=$(dex config get models.coder)
MODEL_REVIEWER=$(dex config get models.reviewer)
- Notify =
notify "<msg>" → a curl POST to $DEX_NOTIFY_WEBHOOK shaped per $NOTIFIER (see Notification Protocol). No Slack/Discord MCP. If none or no webhook, it's a silent no-op. Everywhere this skill says notify "...", that's this.
- Agent models — when spawning the coder/reviewer (Build/Review), pass
model: $MODEL_CODER / $MODEL_REVIEWER if set; empty → the agent definition's own model:. Per-project model choice; set at spawn (not mid-run).
- Verify uses
$CI + $PR_REVIEW and their reactors. If pr_review = none, skip the bot-review loop; if ci = none, skip CI watch.
- Skip phases listed in
phases_skip entirely (e.g. a vault that skips verify ships straight to COMPLETE after the PR). Two forms, don't mix them up: the .dex.toml/vault input is a [phases] table — skip = ["verify"] (merges across vault + project layers); the resolved/queried name is flat — dex config get phases_skip.
- If there's no
.dex.toml, run /specdex configure first (or fall back to: notifier=none, ci/pr_review=none, ship via /pr).
Event emission (dex)
dex records a structured per-spec event stream (events.jsonl + derived
state.json) — the feed the fleet view and global watcher read. It runs alongside
the logbook and notifications. Set the spec once, then every write is a short
resource-verb command:
export DEX_SPEC=<project-name>/<spec-name>
export DEX_ACTOR=lead
At every milestone where you log or notify, ALSO run the matching dex. Every
consequential SendMessage between agents also records one. Fire-and-forget — if
dex isn't installed the call fails harmlessly.
The --actor flag (or DEX_ACTOR env var) records who emitted the event: lead,
coder, or reviewer. Each agent sets DEX_ACTOR once in their environment alongside
DEX_SPEC — the lead exports lead, and coder/reviewer spawn prompts export their role.
| When | Command |
|---|
| Setup — worktree registered | dex init --branch specdex-<spec-name> --worktree <path> --session "$CLAUDE_CODE_SESSION_ID" |
Setup — ports (if [ports] configured) | eval "$(dex ports alloc)" — allocates a free offset + exports the port env vars |
| Plan | dex phase plan |
| Implement starts | dex phase build |
| Coder spawned / idle | dex agent spawn coder --id <id> / dex agent idle coder |
| Coder green | dex test --passed <P> --failed <F> --cmd "<cmd>" |
| Review starts | dex phase review then dex agent spawn reviewer |
| Each verdict | dex review --round <N> --verdict pass|fail|notes --blockers <b> --issues <i> |
| Shipping | dex phase ship |
| PR created | dex pr --number <N> --url <url> (state defaults to open) |
| PR merged / closed | dex pr --number <N> --url <url> --state merged|closed — flip once the host reports it (the UI shows a status chip next to the PR) |
| Verify starts (CI + bot review) | dex phase verify |
| Each poll cycle | dex beat |
| A CI check / bot review lands | dex gate --provider ci --name <check> --result <result> · dex gate --provider review --result <result> --score <0-5> |
| Blocked on the human | dex block "<why>" (clear with dex unblock) |
| COMPLETE / ACCEPTED | dex phase complete / dex phase accepted |
| Skill/env feedback (any time) | dex note --level warn --topic <topic> --text "<observation>" |
beat distinguishes a working spec from a dead one — emit on every verify poll.
block flags a spec as needing you. gate --provider is generic: ci and review
are roles, not vendors (the config says which tool fills each).
Full dex command surface (every command, flag, and enum): reference/dex-cli.md.
Mode: collaborate (/specdex collaborate <feature>)
A human-driven session that you still want visible in the fleet. Unlike the default
autonomous loop, you (lead) and the user drive the work directly — no coder/reviewer
team is spawned and there is no autonomous ship/CI loop. It is tracked in the same
registry so it appears alongside the autonomous minions, badged collaborative.
- Setup is lighter: you may work in a new worktree or directly on the current
checkout. Register with the collaborative flag so the fleet badges it apart:
dex init --branch <branch> --worktree "$(pwd)" --collaborative
- Set
DEX_SPEC once, then emit phase events as the work actually moves
(dex phase plan → dex phase build → …) and dex beat at checkpoints so the
spec reads as alive. Optionally dex agent spawn lead to show who's driving.
- Save the design doc to
~/.spec/<project-name>/<spec-name>/spec.md (same artifact
the autonomous loop and the desktop app read).
- There is no team review/PR automation — the human decides when to ship. If a PR is
opened, record it (
dex pr …) and flip its state when merged (see PR state below).
- Reaching a natural stopping point:
dex phase complete. Cleanup is the same
/specdex accept path.
This mode does the bookkeeping that makes a hands-on session show up in the fleet — it
does not run the autonomous coder/reviewer pipeline.
Mode: configure (/specdex configure)
Writes/updates this project's .dex.toml by exploring the repo and asking only what
can't be inferred. The CLI is the typed brain; you supply the judgement.
- Scaffold the file deterministically:
dex config init writes a commented
.dex.toml template at the repo root (use --force to overwrite an existing one).
You then EDIT it — don't hand-author from scratch.
- Read the option space:
dex config schema — valid providers per role, hook
points, phases, models, the [ports] shape. Your map; don't invent keys.
- Explore the repo to infer:
docker-compose.y*ml / Dockerfile + a frontend (vite/next) ⇒ [[ports]] entries (infer service names, bases, env vars from the compose file) and ci likely needed.
.github/workflows/* ⇒ ci = "github-actions".
Cargo.toml / a single binary / a library ⇒ no [ports], often ci/pr_review = "none".
- existing PR-bot config (
.greptile, coderabbit yaml) ⇒ the matching pr_review.
- Ask only the ambiguous (
AskUserQuestion): which notifier (slack/discord/none),
which multiplexer (zellij/tmux/none — default to whichever is currently detected via $ZELLIJ_SESSION_NAME/$TMUX),
and confirm inferred [ports]. Don't ask what you inferred with confidence. (Global
defaults like notifier/identity can live in ~/.config/dex/config.toml instead.)
- Edit
.dex.toml with the inferred/answered values (Edit the scaffolded file).
- Validate:
dex config validate. On error, fix and re-validate until it passes.
- Show the user the final
.dex.toml + dex config show. Confirm it was WRITTEN, not just printed.
This mode does NOT run the dev loop — it only produces config. Run /specdex <feature> after.
Mode: curate (/specdex curate)
Global, read-only. No worktree, no spec dir, no ports, no team, no PR, no per-spec dex
events. This mode operates across the entire ~/.spec fleet.
-
Dispatch the dex-curator agent — it needs no spec context:
Agent(subagent_type: "dex-curator")
-
The agent collects all note events via dex notes --json, clusters cross-spec patterns,
proposes concrete skill/config edits, and writes its report to
~/.spec/.curator/report-<timestamp>.md.
-
When the agent returns, read the report it wrote (the path is in the agent's output).
Surface a concise summary to the user:
- Signal volume (total notes, spec count, date range)
- Top clusters (topic, occurrence count, proposed action headline)
- The full report path
-
Note that the report is also viewable in the desktop app's Curator view, where all
runs are listed newest-first and can be browsed without re-running.
-
The curator proposes edits only — it does not apply them. The human decides which
proposals to act on.
Setup
0. Session persistence check
The autonomous loop must survive the terminal closing, so it should run inside a
terminal multiplexer. Resolve which one — config first, env fallback:
CONFIGURED_MUX=$(dex config get providers.multiplexer)
if [ -n "$CONFIGURED_MUX" ]; then MUX=$CONFIGURED_MUX
elif [ -n "$ZELLIJ_SESSION_NAME" ]; then MUX=zellij
elif [ -n "$TMUX" ]; then MUX=tmux
else MUX=none; fi
The spec session runs inside a named multiplexer session (spec-<spec-name>) so it survives
terminal closes and can be re-attached by the desktop "attach in terminal" button. The session
is created if absent, attached if present (idempotent) — so re-running setup never spawns
a second claude.
Per-multiplexer attach-or-create and detach:
| MUX | create-or-attach | detach |
|---|
| zellij | zellij attach spec-<spec-name> 2>/dev/null || { zellij attach -b -c spec-<spec-name> && zellij --session spec-<spec-name> run -- claude --continue && zellij attach spec-<spec-name>; } | Ctrl+O, D |
| tmux | tmux new-session -A -s spec-<spec-name> | Ctrl+B, D |
providers.multiplexer must be set in .dex.toml for the session lifecycle to activate.
When multiplexer is empty/unset (none or absent), no session is created — "attach in terminal"
falls back to opening a plain worktree shell. Projects that want the full lifecycle must set it.
If neither $ZELLIJ_SESSION_NAME nor $TMUX is set (not currently inside a session), warn before proceeding — name $CONFIGURED_MUX if set, otherwise offer both:
You're not inside a terminal multiplexer. If you close this terminal, the autonomous loop dies. Start one and re-run /specdex inside it — zellij attach spec-<spec-name> (detach Ctrl+O, D) or tmux new -s spec-<spec-name> (detach Ctrl+B, D). The loop then keeps running and you'll get notifications at each milestone.
Wait for the user to confirm continue-anyway, or restart inside a multiplexer.
0b. Notifications (no MCP — plain HTTP)
Notifications go over a webhook with curl — no Slack/Discord MCP needed. See the
Notification Protocol section for the notify definition. Send the start notice:
notify ":rocket: *[<spec name>]* Spec started — setting up workspace"
If providers.notifier = none or $DEX_NOTIFY_WEBHOOK is unset, notify is a no-op —
just continue silently (the dex event stream is still the source of truth).
1. Derive spec name and project name
<spec-name>: convert the feature description to a short kebab-case slug (e.g., "auth-middleware", "snake-game")
<project-name>: the basename of the current project directory (e.g., "anyformat-backend", "spec-dashboard")
These are used everywhere.
2. Create spec directory
All specs live in a centralized location: ~/.spec/<project-name>/<spec-name>/. This is the single registry of all specs across all projects.
mkdir -p ~/.spec/<project-name>/<spec-name>
This directory holds:
spec.md — the approved plan
logbook.md — timeline of the development process
env.md — record of ports and project name assigned
If ~/.spec/<project-name>/<spec-name>/ already exists, append a numeric suffix: <spec-name>-2, <spec-name>-3, etc.
3. Enter worktree (if needed)
Check if the current working directory is already a worktree:
git rev-parse --is-inside-work-tree && git worktree list
If already in a worktree (e.g., created by Conductor or another tool), skip worktree creation — just use the current directory. Log which worktree/branch is being used.
If on the main working tree, create an isolated worktree:
EnterWorktree(name="specdex-<spec-name>")
This creates a new branch and working directory at .claude/worktrees/specdex-<spec-name>. All implementation happens here — the main working tree is untouched. The specdex- prefix makes specdex's worktrees identifiable (git worktree list | grep '/specdex-') so cleanup never touches Conductor/other-tool worktrees, and the UI locates a spec's .dex.toml via its recorded worktree path.
4. Copy environment
Check for .spec-env in the project root. If it exists, copy it silently into the worktree as .env. If it doesn't exist, warn once and continue — do NOT block:
No .spec-env found in project root. The worktree won't have any env vars. Create one at the project root if needed.
cp <original-project-root>/.spec-env .env 2>/dev/null
5. Assign ports and project name
See reference/ports.md for the offset-assignment algorithm and the .env / env.md templates.
6. Initialize logbook
Create ~/.spec/<project-name>/<spec-name>/logbook.md with the header and first entry.
Plan
Auto-approve mode (--auto-approve <plan-path>)
If invoked with --auto-approve <path-to-plan-file>, skip the interactive planning flow entirely:
- Read the plan file at the given path — it MUST already contain a Context section, files to modify, acceptance criteria, and a verification section. Callers (e.g.
sentry-fix) are responsible for generating a valid plan before invoking spec.
- Copy it to
~/.spec/<project-name>/<spec-name>/spec.md
- Do NOT enter plan mode, do NOT call ExitPlanMode, do NOT ask the user for approval
- Log
Plan auto-approved (source: <path>) in the logbook
- Proceed directly to Build
This mode exists so automated orchestrators can dispatch spec loops without requiring a human in the planning step. It is NOT available in normal interactive use.
Interactive mode (default — Lead does this directly)
The lead IS the planner. Do NOT create a planner teammate.
- Enter plan mode (EnterPlanMode)
- Explore the codebase — read relevant files, understand existing patterns
- Produce a plan for the user to review
- Iterate with the user until they approve
- Exit plan mode (ExitPlanMode)
- Save the approved plan to
~/.spec/<project-name>/<spec-name>/spec.md
- Log approval in
~/.spec/<project-name>/<spec-name>/logbook.md
Planning constraints
- Minimal scope — build the smallest thing that works. One feature at a time. If the user says "basic" or "simple", take it literally.
- Challenge assumptions — question weak reasoning, point out over-engineering, flag if the user is solving the wrong problem.
- No speculative features — if it's not explicitly needed, don't plan for it. No "nice to haves".
- Simplest implementation — default to the simplest approach. Don't add abstractions, config layers, or extensibility unless the user asks.
- Read before planning — do NOT plan changes to code you haven't read. Understand existing data structures before proposing rewrites.
Acceptance criteria (required)
Every plan MUST include an ## Acceptance Criteria section with concrete, testable conditions. The reviewer uses these to decide PASS/FAIL. The autonomous loop cannot complete without all criteria met.
Format:
## Acceptance Criteria
- [ ] User can <do X> and sees <Y>
- [ ] API endpoint <path> returns <expected response> when <condition>
- [ ] Error case: when <bad input>, <expected behavior>
- [ ] Performance: <operation> completes in under <threshold>
Rules:
- Each criterion must be verifiable by the reviewer (readable from code, runnable as a test, or checkable in the browser)
- No vague criteria like "works correctly" or "handles errors" — be specific about what "works" means
- Include happy path AND at least one edge case
- If the user doesn't provide criteria, the planner proposes them and gets approval
Only after the user explicitly approves the plan, proceed to Build.
Build — Create Team & Implement (Autonomous)
Create a team with two teammates, both with mode: "bypassPermissions" so they can run autonomously without blocking on approval prompts:
- coder — uses the
dex-coder agent definition. Implements the approved plan. Mode: bypassPermissions. Spawn prompt must include: export DEX_ACTOR=coder (alongside DEX_SPEC).
- reviewer — uses the
dex-reviewer agent definition. Reviews the coder's work. Mode: bypassPermissions. Spawn prompt must include: export DEX_ACTOR=reviewer (alongside DEX_SPEC).
MANDATORY — pin the worktree in BOTH spawn prompts. Spawned teammates inherit cwd = the repo root (the MAIN checkout), not your worktree, so a relative-path edit silently lands on main instead of the branch. Every spawn prompt MUST carry the absolute worktree path and this rule:
"Your worktree is <absolute-worktree-path>. It is the ONLY valid root: pass it explicitly to every file and git operation (git -C <absolute-worktree-path> …), never rely on cwd, never edit by relative path — sub-agents resolve relative paths to the repo root, which is the MAIN checkout, not this worktree. Before you start and again before you report, run git -C <absolute-worktree-path> status and confirm the MAIN checkout is clean."
Communication model — two planes. Both teammates have SendMessage, so messaging is bidirectional and peer-to-peer (full mesh).
- SendMessage = delivery plane (one-to-one, needs a live recipient). Use it to cut roundtrips: coder/reviewer report to you directly, and the reviewer messages the coder its findings directly (no lead relay). This is the fast lane.
- Event log = visibility plane (one-to-many, async, durable). Every consequential message MUST also record a matching event via
dex (see the Event emission section). This is the rule that keeps cutting the lead out of relays from making the lead — and your Slack scoreboard, the fleet view, the audit trail — blind. Fast lane + the record.
- You stay the authority on phase transitions and termination. Peers coordinate fix-rounds directly, but only YOU decide PASS→ship, max-rounds-hit→escalate, and blocked→DM. Peers iterate; lead decides. This is what prevents an endless coder↔reviewer ping-pong with no one calling it.
- Report files (
coder-report.md, review-round-<N>.md) stay as the durable fallback record, but SendMessage is now the primary, immediate channel — don't poll idle-notifications-then-read-file as the main path.
IMPORTANT: Coder lifecycle management.
When sending the plan to the coder, include this instruction:
"When you finish implementation: (1) SendMessage me (the lead) your completion report — per-section summary + the exact test commands you ran with pass/fail counts + deviations + unverified items; (2) also WRITE the same report to ~/.spec/<project-name>/<spec-name>/coder-report.md as the durable record; (3) record via dex (dex test --passed … --failed …, then dex agent idle coder). Then DO NOT shut down: stay alive and wait. The reviewer may SendMessage you findings directly — when it does, apply the fixes, re-run tests, message both me and the reviewer that you're done, overwrite coder-report.md, emit the new test result, and stay alive again."
The coder must stay alive through the review loop. Only tell it to shut down after review passes.
ENTERING AUTONOMOUS MODE: Before sending work to the coder, DM the user:
notify ":hammer_and_wrench: *[<spec name>]* Implementation started — you can detach now (Ctrl+O, D). Next DM when tests pass."
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
Send the approved plan to the coder. The coder:
- Reads all relevant files
- Implements each step using TDD (RED → GREEN) — see
/tdd for the discipline (vertical slicing, public-interface-only, integration-first, no horizontal batching)
- Parallelizes independent chunks via sub-agents
- Runs the full test suite
SendMessages its completion report to the lead (and writes coder-report.md as the durable copy) — BUT STAYS ALIVE
If the coder reports a plan issue, DM the user and wait for guidance.
TRANSITION → Review: When the coder SendMessages its completion report (the idle/completion notification is your cue to check the message + coder-report.md), confirm tests are green. If not green, SendMessage the coder to finish; don't advance. Once green, do these in order before ANY other work:
notify ":white_check_mark: *[<spec name>]* Implementation complete — tests passing, moving to review"
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
- Then proceed to Review
Review (Autonomous)
Spawn the reviewer as a persistent teammate (it stays alive across all rounds so it has a live inbox for the coder to message — do NOT re-spawn it per round). Its spawn prompt MUST name the verdict file AND the peer protocol below. The reviewer:
- Reads all changed files + callers
- Checks architecture, correctness, style; runs the affected test suites
SendMessages its verdict to the lead AND writes ~/.spec/<project-name>/<spec-name>/review-round-<N>.md — first line VERDICT: PASS | PASS WITH NOTES | FAIL, then findings ([BLOCKER|ISSUE|NIT] file:line — problem — fix) — and records it via dex review --round <N> --verdict ….
Review loop (coder ↔ reviewer, peer mesh):
The fix-iteration happens peer-to-peer to cut the lead-relay roundtrip. The lead does NOT relay findings — it observes (via the verdict messages + the event log) and stays the authority on termination.
If FAIL or PASS WITH NOTES with ISSUEs, the reviewer (per its spawn prompt) directly:
SendMessages its findings to the coder teammate, AND writes them to ~/.spec/<project-name>/<spec-name>/fix-request-<N>.md as the durable record
- The coder fixes, re-runs tests,
SendMessages the reviewer "done" (and the lead), emits the new test result, stays alive
- The reviewer re-reviews (same live agent, new
review-round-<N+1>.md), emits the new verdict
- The lead counts rounds from the verdict events. Repeat up to 3 rounds.
- If still failing after 3 rounds (lead decides):
notify ":rotating_light: *[<spec name>]* Blocked — review failed after 3 rounds\n>*Phase:* review\n>*Reason:* <summary of unresolved findings>\n>*Resume:* reattach via your multiplexer (zellij attach / tmux attach) "
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
- Stop and wait
TRANSITION → Ship: When reviewer reports PASS, do these in order before ANY other work:
notify ":tada: *[<spec name>]* Review passed — shipping PR"
- Tell the coder AND the reviewer they can shut down (both are persistent teammates)
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
- Then proceed to Ship
Ship (Autonomous)
Use the /pr skill to:
- Group changes into logical commits
- Push to a feature branch
- Create a PR targeting
dev
TRANSITION → Verify: When PR is created, do these in order before ANY other work:
notify ":link: *[<spec name>]* PR created — <PR URL>, watching CI"
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
- Then proceed to Verify
Verify — CI + bot review (Autonomous, config-driven)
Skip this entire phase if phases_skip contains verify (e.g. a personal vault) → go
straight to COMPLETE. Otherwise it has two config-driven parts: CI watch (provider
$CI) and bot review (provider $PR_REVIEW). If a provider is none, skip that part.
Always use the providers' registry reactors ($CI_REACTOR, $REVIEW_REACTOR) — never a
hardcoded skill name. On entry dex phase verify; dex beat each poll cycle; record
outcomes with dex gate --provider ci|review ….
CI watch (only if $CI ≠ none)
After the PR is created, CI runs on the PR head. Poll until green or intentionally ignored.
Poll
gh pr view <number> --json statusCheckRollup
Poll every ~4–5 minutes (use ScheduleWakeup with delaySeconds: 270 to stay within the prompt cache window). Do NOT sleep/poll in a tight loop.
Triage each non-green check
For every check with conclusion: FAILURE or conclusion: TIMED_OUT, fetch the job log:
gh api repos/<owner>/<repo>/actions/jobs/<job-id>/logs | tail -200
Then classify the failure into one of three buckets:
Bucket A — Easy fix, do it yourself (no user DM needed):
- Formatter/linter violations on files this PR touched (ruff, prettier, black, eslint-autofix)
- Pre-existing formatter/linter drift on files this PR did NOT touch — apply the exact fix the tool printed, commit as
chore(<scope>): run <tool> on <file> (unblock CI), and push. This is common when the branch is behind base.
- Obviously outdated snapshot/fixture updates from deterministic codegen
- Missing migration dependencies you can regenerate mechanically
Fix locally, commit with a clear chore: or fix: prefix, push, and loop back to polling. Do NOT open a separate PR — fix in the same branch.
Bucket B — Needs real work but still tractable (spawn coder teammate):
- Real test failures caused by this PR's changes
- Type errors the PR introduced
- Integration test failures with a clear root cause
- Migration conflicts with a new base branch commit
Run $CI_REACTOR (the configured CI reactor skill) and/or send the failure log to the coder teammate (still alive from Build/Review) with a clear task description. Loop back to polling once the fix is pushed.
Bucket C — Hard or ambiguous (DM the user, then stop):
- Flaky/infra failures you can't reproduce (stop — don't retry blindly)
- Failures in systems this spec doesn't own, with no clear fix
- CI config breakage
- Secret/credential issues
- Any failure where you'd be guessing
notify ":rotating_light: *[<spec name>]* CI blocked — <check name> failing\n>*Failure:* <one-line summary>\n>*Log:* <job URL>\n>*Why stuck:* <reason you can't fix autonomously>\n>*Resume:* `reattach via your multiplexer (zellij attach / tmux attach) <session-name>`"
Then log in the logbook and stop. Wait for the user.
Checks to ignore
IN_PROGRESS checks — just keep polling
SKIPPED checks — normal, ignore
NEUTRAL checks that don't block merge — ignore
- Checks unrelated to the PR (e.g.,
detect-changes skipped paths) — ignore
DM once per CI round
When you push a fix for a CI failure, DM the user once per round:
:wrench: *[<spec name>]* CI fix pushed — <check name> was <one-line reason>, fixed in <sha>. Re-running pipeline.
Don't spam — one DM per push, not one per poll.
Once all checks are green (or only SKIPPED/NEUTRAL), proceed to bot review.
Bot review (only if $PR_REVIEW ≠ none)
Wait for the configured PR-review bot ($PR_REVIEW) to post its review:
gh pr view <number> --comments
Once it has commented, use $REVIEW_REACTOR (the provider's registry reactor — e.g.
/react-to-greptile, /react-to-coderabbit) to: read feedback, fix locally, push,
reply to every thread, re-trigger the bot. Record each round with
dex gate --provider review --result <pass|fail> --score <0-5>.
Every bot-review round MUST produce a notification — no silent rounds (the user reads
these as the scoreboard). Notify (via $NOTIFIER) at TWO moments per round:
- (a) verdict arrives: round N, score X/5, findings summary, next action.
- (b) fixup pushed: round N fixes pushed
<sha>, what changed, re-triggering.
If a round passes on the first look, send only (a) then the final notice. If a round
can't be fixed (coder blocked), send (a) then dex block "<why>" and escalate.
When the bot reaches its pass threshold → FINAL, before any other work:
- Notify via
$NOTIFIER: "Complete — PR ready for human review: "
dex phase complete and log COMPLETE in ~/.spec/<project-name>/<spec-name>/logbook.md
Notification Protocol (config-driven, no MCP)
Notifications go over plain HTTP webhooks with curl — no Slack/Discord MCP
required. Resolve once at setup:
NOTIFIER=$(dex config get providers.notifier)
WEBHOOK="$DEX_NOTIFY_WEBHOOK"
Everywhere this skill writes notify "<message>", it means this shell function:
notify() {
{ [ "$NOTIFIER" = none ] || [ -z "$WEBHOOK" ]; } && return 0
case "$NOTIFIER" in
slack) body=$(jq -n --arg t "$1" '{text:$t}') ;;
discord) body=$(jq -n --arg t "$1" '{content:$t}') ;;
*) return 0 ;;
esac
curl -fsS -X POST -H 'Content-Type: application/json' -d "$body" "$WEBHOOK" >/dev/null || true
}
Slack incoming-webhooks and Discord webhooks both accept this. Fire-and-forget — a
failed notification never blocks the loop. See reference/slack.md for the message
format / emoji table (the text content; applies to any notifier).
Cleanup — ACCEPTED Phase
When the spec reaches COMPLETE, nothing is cleaned up automatically. The worktree, docker resources, and branch all stay alive. The logbook status is COMPLETE.
The user must explicitly accept the spec to trigger cleanup. This happens when the user says "accept", "lgtm", "ship it", or "clean up" for a completed spec.
When the user accepts:
- Update logbook status to
ACCEPTED
- Log in
~/.spec/<project-name>/<spec-name>/logbook.md
- Clean up docker resources (if any):
COMPOSE_PROJECT_NAME=spec-<spec-name> docker compose down -v 2>/dev/null || true
- Remove the worktree (the branch and PR stay — user merges manually):
ExitWorktree(action="remove")
- DM the user:
notify ":broom: *[<spec name>]* Accepted — worktree cleaned up. PR ready to merge."
If the user rejects:
Rejection means the spec needs more iteration, NOT deletion.
- Update logbook status to
ITERATING
- Log the user's feedback in
~/.spec/<project-name>/<spec-name>/logbook.md
- Go back to Plan (planning) — the user iterates on the plan with the new feedback
- The worktree, docker resources, and branch all stay alive
Error Handling / Intervention Required
See reference/errors.md for the intervention DM template, the mandatory fields, and the rules (no blind retries, no destructive git).
Notes for the curator
dex note is the structured signal the curator reads. Emit sparingly — only when something
is non-obvious or needs to persist across context windows. Routine success is noise.
When to emit
- A failure that required a workaround (env issue, tool friction, unexpected behavior)
- A correction or auto-heal (you caught a bug, you deviated from the plan)
- A surprise that will affect future work
- A plan deviation (you changed approach from what was planned)
- A bug-and-how-it-was-caught (test caught X, review caught Y)
- A retry (third attempt after two failures — note what the others tried)
Do NOT emit for: normal progress, routine test runs, successful phase transitions.
Text convention
symptom → how-detected → root-cause → fix/workaround
Example: cargo test failed with linker error — detected: compile step — root-cause: missing system dep libssl-dev — fix: apt install libssl-dev before build
Keep it one line or two short sentences. Future readers need the root cause, not the symptoms alone.
Scope
| Scope | Meaning | Example |
|---|
spec | This specific spec only — one-off, won't recur elsewhere | "reviewer asked for extra test in this spec's acceptance criteria" |
project | This project/repo — affects future specs in the same repo | "this repo's CI requires --test-threads=1 for integration tests" |
skill | Tooling or loop-wide — affects all future specdex sessions | "git worktree add fails if branch already checked out — run git worktree prune first" |
When in doubt: skill is the highest-signal scope. The curator clusters by scope to find systemic improvements.
Who emits what
- Subagent (coder/reviewer): env failures, tool friction, bugs caught during build/review, unexpected test behavior, retry causes
- Lead: orchestration gotchas, plan deviations, corrections to coder/reviewer misalignment, process surprises
Both roles emit; the actor field (set via DEX_ACTOR) identifies who.
Stable topic taxonomy
Use these stable topics so the curator can cluster across sessions:
| Topic | Use for |
|---|
env/<tool> | Tool-specific env failures (env/git, env/cargo, env/docker) |
skill/<area> | Skill or loop behavior (skill/rust, skill/tdd) |
orchestration | Lead-level process decisions, agent coordination |
plan | Plan deviations, scope changes, spec-level surprises |
test-flake | Intermittent or environment-sensitive test failures |
review-finding | Patterns the reviewer caught that future coders should know |
Free-text topics are allowed but fragment clusters — prefer the stable list.
Loop hooks where a note is expected
- Review blocker resolved: after the coder fixes a BLOCKER finding
- Smoke-test/CI catches a bug: before or after the fix
- Lead relocates misplaced work: when the lead reroutes an agent's output
- Missing dep discovered: when a required tool/library isn't installed
Command reference
dex note --scope skill --topic env/git --text "symptom → detected → cause → fix"
dex note --scope project --topic test-flake --level warn --text "..."
dex note --scope spec --topic plan --level info --text "..."