Skip to content

Fix sdd-done Merge Conflicts — Design Spec

Date: 2026-08-05 Status: Approved Scope: .claude/commands/sdd-done.md, CLAUDE.md (table update)


Problem

/sdd-done Step 7 closes tasks on dev (moves active/completed/, updates the per-spec index via close_task.sh) before merging the feature branch. The feature branch already carries those same state transitions from sdd-worker/sdd-start. This causes:

File Feature branch dev (Step 7) Merge result
active/TASK-NNN-*.md Deleted Deleted ✅ delete/delete — OK
completed/TASK-NNN-*.md Created Created 💥 add/add conflict
index/<slug>.json Modified (status→done) Modified (status→done + verification) 💥 modify/modify conflict

The root cause is that Step 7 violates the FEAT-145 design principle: "the merge in /sdd-done brings both [code and per-spec index] to base_branch atomically."

Design: Merge-First, Stamp-on-Feature-Branch

Core Principle

The feature branch is the single source of truth for task state. /sdd-done never modifies SDD task files or the index on dev before the merge.

The only metadata the feature branch doesn't already carry is: 1. Per-task verification field (only set by close_task.sh) 2. Feature-level completed_at on the index header (set when all tasks done)

Both are stamped on the feature branch (in the worktree) before push, so both --merge and PR flows receive the complete state atomically.

Changes

1. Rewrite Step 7 — Stamp Verification in Worktree

Replace the current Step 7 (which runs close_task.sh on dev) with a lightweight verification stamp on the feature branch:

WORKTREE_PATH=".claude/worktrees/feat-<FEAT-ID>-<slug>"
INDEX="sdd/tasks/index/${FEATURE_SLUG}.json"
NOW="$(date -u +%Y-%m-%dT%H:%M:%S+00:00)"

# Stamp verification on each verified task
for TASK_ID in "${VERIFIED_TASKS[@]}"; do
  jq --arg id "$TASK_ID" --arg now "$NOW" --arg ver "verified" '
    (.tasks[] | select(.id == $id) | .verification) = $ver
  ' "$WORKTREE_PATH/$INDEX" > tmp && mv tmp "$WORKTREE_PATH/$INDEX"
done

# Stamp feature-level completed_at if all tasks are done
jq --arg now "$NOW" '
  if all(.tasks[]; .status == "done") then .completed_at = $now else . end
' "$WORKTREE_PATH/$INDEX" > tmp && mv tmp "$WORKTREE_PATH/$INDEX"

# Commit on the feature branch
git -C "$WORKTREE_PATH" add "$INDEX"
git -C "$WORKTREE_PATH" commit -m "sdd: close tasks for FEAT-<ID> — <slug>"

What this does: - Adds only the 2 missing fields to the index already in the worktree - No file moves (already in completed/) - No modifications to dev

Verification values by task classification (from Step 5): - ✅ VERIFIED → verification: "verified" - ⚠️ PARTIAL → verification: "partial" - --forceverification: "forced"

2. Steps 8-9 — Push and Merge (now conflict-free)

Step 8 (Push): Unchanged — push the feature branch from the worktree.

Step 9 — PR flow (default): Unchanged. The PR now carries complete state (files in completed/, index with verification + completed_at). When GitHub merges the PR, everything arrives on dev atomically. No prior commit on dev to conflict with.

Step 9 — --merge flow: Simplified. The merge is clean because dev has no competing SDD changes:

git merge --no-edit feat-<FEAT-ID>-<slug>

# Safety net — heal_orphans.sh (kept, cheap and defensive)
scripts/sdd/heal_orphans.sh <feature-slug>
if ! git diff --cached --quiet -- sdd/tasks/active sdd/tasks/completed; then
  git commit -m "sdd: reap stalled active task orphans for FEAT-<ID> — <slug>"
fi

git push origin "$BASE_BRANCH"

The "merge has conflicts" warning block for SDD files is removed from Step 9. Code-level merge conflicts remain possible and are handled normally.

Steps 9.5–12: Unchanged (sync-down, Jira, cleanup).

3. Update CLAUDE.md Auto-Commit Table

The /sdd-done row in the "SDD Auto-Commit Rule" table changes:

Before After
What it commits: "Per-spec index final state + task file moves" What it commits: "Verification stamp on per-spec index"
Where: base_branch Where: worktree (feature branch); merged to base_branch by Step 9

Files Changed

File Change
.claude/commands/sdd-done.md Rewrite Step 7, simplify Step 9 --merge block
CLAUDE.md Update /sdd-done row in Auto-Commit table

Files NOT Changed

File Reason
scripts/sdd/close_task.sh Still used by /sdd-start in worktree; just no longer invoked by /sdd-done
scripts/sdd/heal_orphans.sh Still runs as post-merge safety net
.claude/commands/sdd-start.md Unchanged — closes tasks in worktree as before
.claude/agents/sdd-worker.md Unchanged — closes tasks in worktree as before

Risk Assessment

Risk: Low. The change is a subtraction (stop doing something redundant) plus a lightweight metadata stamp.

Assumption: All tasks are always closed on the feature branch before /sdd-done runs. Confirmed by the user — no partial implementation scenario.

Verification

Run /sdd-done FEAT-XXX on a completed feature and verify: 1. No commit on dev before the merge 2. The merge is clean (no SDD conflicts) 3. The index on dev post-merge has verification and completed_at fields