| name | sdd:implement |
| description | SDD — Spec-Driven Development: execute tasks, run checkpoints, commit and open PR. |
Shared Instructions
- Event Journal — append context updates to a per-spec write-ahead log; a single drain script materializes them into
.spec-context.json
- Transition Logging — transition entry shape and millisecond
at precision (the drainer materializes one transition per journal event)
- Hook Execution — run user-configured hooks at supported pipeline points
- Branch Creation — optional feature-branch creation driven by
.sdd.json branchStage
- Layered Context — Layer 2 → Layer 1 delta sync at CP3 (Step 7b)
Context writes: journal + drain
This skill does not read-merge-write .spec-context.json on every task.
Per event-journal, each context
update is a one-line append to specs/{NNN}-{slug}/.spec-context.events.jsonl
(cheap; no read, no merge), and a single drain materializes the journal into
.spec-context.json at the boundaries called out below.
Steps
1. Load
If $ARGUMENTS is provided, use specs/{$ARGUMENTS}/ as the target directory.
Otherwise, find the most recently modified directory under specs/ that contains tasks.md.
Read in parallel:
specs/{NNN}-{slug}/tasks.md — all Phase 1 tasks
specs/{NNN}-{slug}/spec.md — feature name, requirements, scenarios (for CP1 verification)
specs/{NNN}-{slug}/plan.md — approach, files, issue number if present
specs/{NNN}-{slug}/.spec-context.json — current step/task (if exists; note if resuming mid-implement)
Determine commit scope from the primary directory being modified (e.g., toolbar, ui, core). If unclear, omit scope.
Determine issue number from plan.md or spec.md if present.
If no tasks found, stop: "Run /sdd:specify, /sdd:plan, and /sdd:tasks first."
Append a progress event to the journal per event-journal (seq counter initialized as described above):
{ "seq": <n>, "at": "<ms-ISO>", "kind": "progress", "step": "implement", "substep": "phase1", "from": { "step": "tasks", "substep": null }, "set": { "currentStep": "implement", "currentTask": "T001", "progress": "phase1", "next": "implement" } }
(No drain needed yet — Context Recovery and the first checkpoint will drain.)
1b. Optional Branch Creation
Skip this step on resume — if .spec-context.json already showed currentStep = "implement" on entry, branch creation has already run (or been skipped) on the first pass.
Follow branch-creation with stage="implement". It is a no-op unless .sdd.json has branchStage: "implement".
Run pre:implement hooks per hook-execution with vars = { slug, spec-dir, files: <empty string — no files modified yet> }.
Context Recovery (if resuming)
If .spec-context.json shows currentStep = "implement":
- Drain first. Run
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/ so any events appended before the interruption are materialized before you read. The drainer is idempotent. Then initialize your seq counter (see "Context writes" above).
- Read
.spec-context.json fully — extract approach, last_action, task_summaries, concerns, decisions, files_modified, and step_summaries if present
- Use these fields to reconstruct context efficiently:
approach tells you the implementation strategy without re-reading plan.md
last_action tells you what just happened before context was lost
task_summaries for completed tasks tells you what each task did, what files it touched, and any concerns — without re-deriving from code
step_summaries.plan (if present) provides the planned approach, file count, and risks — skip re-reading plan.md
concerns and decisions carry forward flagged issues and key choices
- Read
tasks.md — [x] = done, [ ] = remaining (still needed for remaining task definitions)
- Read
spec.md for scenarios (needed at CP1 for verification). If step_summaries.specify exists in .spec-context.json, you can skip re-reading spec.md for feature context (use the key_finding instead) — but still read it for CP1 scenario verification.
- Skip re-reading
plan.md if step_summaries.plan and approach exist in .spec-context.json — these provide sufficient context
- Read
progress from .spec-context.json and use it to skip completed phases:
progress value | Resume point |
|---|
phase1 | Resume from first unchecked task in Phase 1 |
hooks | Skip Phase 1, resume at hooks execution |
code-review | Skip Phase 1 + hooks, resume at CP1 — Code Review |
test-results | Skip through CP1, resume at CP2 — Test Results |
commit-review | Skip through CP2, resume at CP3 — Commit Review |
commit | Skip through CP3, resume at Step 8 (stage + commit) |
null or missing | Fall back to task-based recovery: resume from first unchecked task |
- Do NOT re-run completed phases — trust the progress marker and existing checkmarks
2. Phase 1 — Core Implementation
Walk Phase 1 tasks in order. Tasks group into two execution shapes:
- Solo task — any task that does not start with
[P]. Execute it by itself.
- Parallel group — a run of consecutive
[P] tasks. Execute them as a batch of concurrent subagents.
2a. Solo task execution
For each solo task:
- Perform the work described in the Do field.
- Run the Verify check.
- Mark complete in
specs/{NNN}-{slug}/tasks.md: - [ ] → - [x].
- Append one
task_done event to the journal per event-journal (no read-merge; files_modified is union, decisions/concerns are append, the rest is set):
{ "seq": <n>, "at": "<ms-ISO>", "kind": "task_done", "step": "implement", "substep": "phase1", "from": { "step": "implement", "substep": "phase1" },
"set": { "task_summaries.{taskId}": { "status": "DONE|DONE_WITH_CONCERNS", "did": "<one line>", "files": ["..."], "concerns": ["..."] }, "currentTask": "<next T### or null>", "last_action": "<one line>" },
"union": { "files_modified": ["<files this task touched>"] },
"append": { "decisions": ["<if a non-trivial decision was made>"], "concerns": [{ "task": "{taskId}", "note": "<description>" }] } }
status: DONE_WITH_CONCERNS if any silent fixes, type workarounds, or edge cases were noted; else DONE.
- Omit
decisions/concerns from append when there are none.
- The drainer preserves all other fields (
currentStep, progress, approach, step_summaries, previous task_summaries, etc.) — you do not restate them.
- Run
post:task hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files from this task's report> } before proceeding. (Hooks read vars.files from the task, not the JSON, so no drain is required first.)
- Drain every ~3 completed solo tasks, and before yielding the turn, with
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/ — this bounds how stale the viewer can get and keeps the file crash-current at every yield.
2b. Parallel group execution
When you reach a run of consecutive [P] tasks:
- Collect the full run (stop at the first non-
[P] task or end-of-phase). That run is the parallel group.
- For each task in the group, build a subagent prompt from its Do, Verify, and (if present) Files / Leverage fields. State explicitly that the subagent must only do the described work and return a short report — it must not edit
.spec-context.json, append to .spec-context.events.jsonl, or tick checkboxes in tasks.md. The main thread owns all of those writes to avoid races.
- In a single message, spawn one
Agent call per task (subagent_type: "general-purpose"). Do not spawn them one at a time.
- When every subagent has returned:
- For each task in order: tick
- [ ] → - [x] in tasks.md.
- Append one
group_done event to the journal per event-journal that sets every task's task_summaries.{Tn} entry (each with status, did, files, concerns), currentTask (the task after the group, or null), and last_action (a one-line group summary, e.g. "T004–T006 complete — updated three independent call sites in parallel"); unions all modified files into files_modified; and appends any new decisions/concerns. The drainer materializes one transition for this event.
- Run
post:task hooks once per task in the group, sequentially, each with that task's own files in vars.
- Drain at the group end:
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/.
- If any subagent fails or reports a concern, record that task as
DONE_WITH_CONCERNS (or stop per the Deviation rules below). Partial success is fine — the main thread still ticks only the tasks that completed successfully.
Resume note: if execution is interrupted mid-group, none of the group's checkboxes will be ticked (the main thread only ticks after the whole group returns). On resume, progress: "phase1" will land on the group's first task and the group will re-run in full. Task work should be idempotent where possible.
Deviation rules:
| Situation | Action |
|---|
| Bug, import error, or type mismatch | Fix silently — note for CP1 |
| Missing dependency | Fix silently — note for CP1 |
| Architectural approach needs to change | STOP. Explain to user and ask how to proceed. |
| Task is impossible as written | STOP. Explain why and ask how to proceed. |
After the last Phase 1 task, check if a build command is configured in .sdd.json. If so, start the build in background. Otherwise, check if common build commands exist (e.g., package.json scripts) and run the appropriate one.
4. Phase 2 — Hooks
Append a progress event to the journal per event-journal: set progress to "hooks" (from = {step:"implement", substep:"phase1"}).
Read .sdd.json from the project root (if it exists):
- If
.sdd.json has no hooks key but has an agents key, log: ⚠ "agents" config is deprecated — migrate to "hooks". See docs/CONFIGURATION.md and skip to CP1.
- Otherwise run
pre:checkpoint:code-review hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files_modified> }. The executor automatically merges hooks["pre:code-review"] entries for backward compatibility.
Proceed to CP1 when hooks complete.
5. Checkpoint 1 — Code Review
Append a progress event (set progress to "code-review", from = {step:"implement", substep:"hooks"}) per event-journal, then drain so the display reads materialized state:
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/
Read concerns[], files_modified, task_summaries, and step_summaries.plan from .spec-context.json for the display below.
Display exactly this format, then use the AskUserQuestion tool:
🔍 **Code Review**
All {N} tasks complete. Here's what changed:
**Task Summaries**:
- **T001**: {task_summaries.T001.did}
- **T002**: {task_summaries.T002.did}
- ...
**Changes** ({N} files):
- `path/to/file` — [one line description]
- `path/to/file` — [one line description] (not in plan)
For each file in `files_modified`, check if it appears in the plan's file list (from `step_summaries.plan` or plan.md). If a file was NOT in the original plan, append "(not in plan)" to its line.
⚠️ **Concerns** ({N}):
- **T002**: {concern note from concerns[]}
- **T004**: {concern note from concerns[]}
(If no concerns, display: "none")
⚠️ **Silent fixes**: [list any deviations beyond what's in concerns[], or "none"]
**Does it match the spec?**
- [ ] {scenario from spec} → expected result
- [ ] {edge case from spec} → expected result
Call AskUserQuestion with these options:
- Continue — proceed to commit
- Fix — user provides fix notes in the "Other" field; address the issue, update
tasks.md, return to CP1
6. Checkpoint 2 — Test Results
Append a progress event (set progress to "test-results", from = {step:"implement", substep:"code-review"}) per event-journal, then drain: python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/.
Run pre:checkpoint:test-results hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files_modified> }.
Only show this checkpoint if the user ran tests after CP1.
Display exactly this format, then use the AskUserQuestion tool:
🧪 **Test Results**
✅ All {N} tests passing — ready to commit
— or —
❌ {N} tests failed:
- `test name` — {brief diagnosis}
Call AskUserQuestion with these options:
- Continue — proceed to CP3
- Fix — user provides fix notes in the "Other" field; fix failing tests, return to CP2
7. Checkpoint 3 — Commit + PR
Append a progress event (set progress to "commit-review", from = {step:"implement", substep:"test-results"}) per event-journal, then drain: python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/.
Run pre:checkpoint:commit-review hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files_modified> }.
Display exactly this format, then use the AskUserQuestion tool:
💾 **Ready to ship**
**Commit**: `{type}({scope}): {short description}`
**PR**:
> ## What
> - [bullet from spec]
>
> ## Why
> [one sentence from spec]
>
> ## Testing
> - [verify step from tasks]
>
> Closes #{N} (if issue exists)
Call AskUserQuestion with these options:
- Approve — proceed to commit and PR
- Edit commit — user provides notes in the "Other" field; apply changes to commit message, redisplay CP3
- Edit PR — user provides notes in the "Other" field; apply changes to PR body, redisplay CP3
7b. Sync Layer 1 (Living Specs)
Runs only after CP3 is approved, before staging in Step 8 — so the sync edits are part of the same commit as the implementation.
Read loadedDomains from .spec-context.json. If empty, skip this step (nothing to sync).
Per layered-context, parse specs/{NNN}-{slug}/spec.md for delta blocks:
## ADDED Requirements — append each ### R<id> subsection to the living spec's ## Requirements section.
## MODIFIED Requirements — replace the matching ### R<id> block in place (matched by R-id; the heading name may change).
## REMOVED Requirements — for each - **R<id>** bullet, delete the matching ### R<id> subsection from the living spec.
## RENAMED Requirements — for each - **R<id>**: \Old` → `New`bullet, update only the heading name on the matching### R` subsection.
Application rules:
Get each domain's resolved .spec.md path from the resolver script — python3 lib/scripts/resolve-spec-paths.py --changed <files_modified> (the JSON matched[] is already ordered most-specific first). Write deltas to those resolved paths; never hardcode .specs/<domain>/spec.md.
- If a delta operation references an
R<id> not present in the target living spec (MODIFIED / REMOVED / RENAMED), record a concern (Sync skipped: R<id> not found in the domain's living spec — naming the resolved path) but do not halt — continue with the next operation.
- Write to the most-specific domain only. When
matched[] has multiple entries (a parent + leaf tree), apply each delta block to the most-specific matched domain (the first entry) — not every loaded domain. To target a different/additional domain, the block must carry a <!-- domain: <name> --> marker on the line immediately above the heading; markered blocks apply only to the named domain(s). (Read-all for context earlier; write-most-specific here.)
- Update the
**Last updated:** line in each modified living spec (at its resolved path) to today's date.
After all writes succeed, append a field_set event per event-journal that unions the synced names into syncedDomains (the union op deduplicates). No drain required here — Step 8 drains before the commit.
Display a one-line summary per domain:
🔄 Synced delta into {resolved path for {domain}} ({N} added, {N} modified, {N} removed, {N} renamed)
If the per-feature spec.md has no delta blocks, the step is a silent no-op.
8. Commit + PR
Append a field_set event per event-journal with step: "done", substep: null, from: { "step": "implement", "substep": "commit-review" }, and:
"set": { "progress": null, "next": "done", "currentStep": "done", "status": "completed", "checkpointStatus": { "commit": true, "pr": true } }
currentStep: "done" is the terminal state — nothing left to execute.
status: "completed" drives tree-view grouping; without it the spec stays in the Active group forever.
Then drain before staging so the committed file is fully materialized:
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/
status is separate from currentStep: it is the lifecycle field (active | tasks-done | completed | archived) that downstream tools (tree views, dashboards) group by. PR-open is treated as "completed" — if you need a distinct merged-vs-open gate, wire a post-merge hook that sets status: "archived" or equivalent.
Run pre:commit hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files_modified> }. A blocking failure here halts the pipeline before any commit is made.
Stage the changed files explicitly (no git add -A). Always include the spec artifacts (specs/{NNN}-{slug}/) alongside implementation files. If Step 7b synced any living specs (i.e., syncedDomains is non-empty), stage each synced domain's resolved path (resolve(<domain>) per layered-context) so the Layer 1 update lands in the same commit. The resolved path may be a colocated file outside .specs/ (e.g. src/app/auth/auth.spec.md) — stage those colocated paths too:
git add path/to/file1 path/to/file2 ... specs/{NNN}-{slug}/ <resolved domain path>
Commit using conventional commit format:
git commit -m "{type}({scope}): {short description}" -m "Closes #{N}"
Rules:
type: feat, fix, refactor, docs, or chore
scope: lowercase, from primary directory modified. Omit if unclear.
- Short description: imperative, lowercase, no period, max 72 chars
Closes #N line: only if issue number exists
- No Co-Authored-By or attribution lines
Before pushing, apply the main-branch push guard per branch-creation: if .sdd.json has branchStage set to "specify" or "implement" and the current branch is main/master, halt with the refusal message. When branchStage is "manual" (default), skip the guard — the user is responsible for their branch.
Push:
git push -u origin $(git branch --show-current)
Open PR:
gh pr create \
--title "{type}({scope}): {short description}" \
--body "$(cat <<'EOF'
## What
- [bullet from spec]
- [bullet from spec]
## Why
[one sentence from spec]
## Testing
- [verify step from tasks]
- [verify step from tasks]
Closes #{N}
EOF
)"
Rules:
- PR title matches commit message exactly
Closes #N only if issue exists — omit otherwise
- No "Generated with Claude Code" or any AI attribution
Run post:pr hooks per hook-execution with vars = { slug, spec-dir, files: <space-separated files_modified> } after gh pr create succeeds.
8b. Finalize spec context
The Step 8 commit captures .spec-context.json in its pre-PR state — prUrl and the final last_action cannot exist until gh pr create returns. Ship those in a second commit on the same branch so the PR carries the fully finalized context.
Append a field_set event per event-journal with step: "done", substep: null, from: { "step": "done", "substep": null }, and:
"set": { "prUrl": "<gh pr create URL>", "prNumber": <N>, "last_action": "PR #{N} opened — {type}({scope}): {short description}" }
Then drain so the finalize commit captures the materialized file:
python3 lib/scripts/drain-spec-context.py specs/{NNN}-{slug}/
Stage, commit, and push — context file only, no -A:
git add specs/{NNN}-{slug}/.spec-context.json
git commit -m "chore(specs): mark {NNN} shipped — PR #{N}"
git push
Skip this step if gh pr create failed or no prUrl was captured — leave the context in its pre-PR state rather than committing half-finalized data.
9. Summary
Display exactly this format:
✅ **Shipped!**
{Feature Name} is live.
**Commit**: `{type}({scope}): {description}`
**PR**: {PR URL}
**Scope**: {N} files changed