work
Pick up tracker issues and implement them in parallel using git worktrees. Use when the user says "work on issues", "implement the spec", "start coding", or wants agents to build from planned issues.
Governing Artifacts
Usage
/sdd:work [SPEC-XXXX | issue numbers | (empty = propose from backlog)] [--max-agents N] [--draft] [--dry-run] [--no-tests] [--module <name>] [--loop [--max-iterations N] [--max-prs N] [--max-minutes N] [--max-dollars N] [--lock={skip|wait|force}] [--resume] [--budget-file PATH]] [--no-chain]
Required Tools
BashReadWriteEditGlobGrepTaskWebFetchWebSearchTeamCreateTeamDeleteTaskCreateTaskUpdateTaskListTaskGetSendMessageAskUserQuestionToolSearchEnterWorktree
Overview
You are picking up tracker issues and implementing them in parallel using git worktrees. Each issue gets its own worktree and worker agent.
<!-- Governing: ADR-0028 (/loop Autonomous Mode), SPEC-0020 REQ "Lockfile Schema and Acquisition", SPEC-0020 REQ "Budget Schema and Persistence", SPEC-0020 REQ "Telemetry Schema", SPEC-0020 REQ "Resume Contract", SPEC-0020 REQ "Resume Contract Reconciliation" -->
Loop Mode (V1, opt-in). When invoked under
/loopwith the--loopflag, this skill enters autonomous-mode and uses the lockfile + budget primitives documented inreferences/loop-primitives.md(acquired on entry, released on exit) and the telemetry + resume contract documented inreferences/loop-telemetry.md(every iteration appends ahistory.jsonlline and emits a stdout status block;--resumereconcilestracked_prs[]andactive_worktrees[]from the last line). The full CLI surface, all 12 stop conditions, and all 6 AskUserQuestion gates are wired in story #144 (SPEC-0020). Without--loop, behavior is unchanged from the rest of this document and no.sdd/loop/artifacts are created.
Process
<!-- Governing: ADR-0016 (Workspace Mode), SPEC-0014 REQ "Artifact Path Resolution" -->
-
Resolve artifact paths: Follow the Artifact Path Resolution pattern from
references/shared-patterns.mdto determine the ADR and spec directories. If$ARGUMENTScontains--module <name>, resolve paths relative to that module. The resolved spec directory is\{spec-dir\}. -
Parse arguments: Parse
$ARGUMENTS.Target resolution:
- If a SPEC number is provided (e.g.,
<a href="/specs/init-and-priming/spec#spec-0003" className="rfc-ref">SPEC-0003</a>), find all open tracker issues referencing that spec. - If issue numbers are provided (e.g.,
42 43 47), work on those specific issues. - If
$ARGUMENTSis empty (ignoring flags), review the backlog (see step 4) and propose a work plan (see step 1a below) — do NOT require a spec.
1a. Backlog proposal (no arguments): After discovering workable issues in step 4, analyze the backlog with a bias toward unblocking work and feature development:
- Prefer issues that are blocking other issues over isolated work.
- Prefer feature/enhancement issues over maintenance or chore issues when all else is equal.
- Prefer issues with no dependencies (immediately startable) over blocked ones.
- Group by epic or project when available to cluster related work.
- Select up to
--max-agentsissues (default 4) as the proposed batch.
Present the proposed batch to the user using
AskUserQuestion:"Here are the issues I'd like to work on. Approve or adjust before I start."
Show a table:
| # | Issue | Project/Epic | Rationale |
|---|-------|-------------|-----------|
| 1 | #42 JWT Token Generation | Auth Module | Unblocks #43 and #44 |
| 2 | #47 Setup DB Schema | Core | No dependencies, foundational |
| 3 | #51 User Registration API | Auth Module | High priority feature |Options: "Approve this batch" / "Let me pick manually" (if chosen, present full backlog and ask for issue numbers).
Flag parsing:
--max-agents N: Maximum concurrent worker agents. Default: 4 (or CLAUDE.mdWorktrees > Max Agents).--draft: Create draft PRs instead of regular PRs. Default: off (or CLAUDE.mdWorktrees > PR Mode).--dry-run: Preview what would happen without creating worktrees or doing any work. Default: off.--no-tests: Skip test execution in workers. Default: off.--module <name>: Resolve artifact paths relative to the named module. Default: none.
- If a SPEC number is provided (e.g.,
-
Load architecture context (when a spec is provided or issues reference a spec): Read the spec's
spec.mdanddesign.md. Validate spec pairing perreferences/shared-patterns.md§ "Spec Pairing Validation". Scan for referenced ADRs (e.g.,<a href="/decisions/ADR-0001-drift-introspection-skills" className="rfc-ref">📝 ADR-0001</a>) and read those too. This context will be sent to every worker. If no spec is associated with the selected issues, skip this step — workers will rely on issue body and codebase context alone. -
Detect tracker: Follow the "Tracker Detection" flow in the plugin's
references/shared-patterns.md. Fallback totasks.mdparsing if no tracker is found.
3a. Ensure lifecycle labels exist (Governing: SPEC-0015 REQ "Issue Lifecycle Labels"):
Create the lifecycle labels using the try-then-create pattern (see references/shared-patterns.md) — attempt to use each label, and only create it if it doesn't exist. This avoids failures on repeated runs.
| Label | Color | Meaning |
|---|---|---|
queued | #CCCCCC | Issue is in the work queue, not yet started |
in-progress | #FBCA04 | An agent is actively working on this issue |
in-review | #0E8A16 | A PR has been created and is awaiting review |
merged | #6E40C9 | The PR has been merged |
For GitHub:
gh label create "queued" --color "CCCCCC" --description "Issue is queued for work" --force
gh label create "in-progress" --color "FBCA04" --description "Agent is actively working" --force
gh label create "in-review" --color "0E8A16" --description "PR created, awaiting review" --force
gh label create "merged" --color "6E40C9" --description "PR has been merged" --force
For Gitea: Use ToolSearch to discover label MCP tools (e.g., mcp__gitea__label_write). Create labels via the API equivalent. If labels already exist, the API will return an error — ignore it and proceed.
For tasks.md fallback: Skip label creation (labels are not applicable to file-based tracking).
3b. Define protected paths (Governing: 📝 ADR-0017, 📝 ADR-0020, SPEC-0015 REQ "Design Document Isolation"):
The following paths are protected and MUST NOT be modified by worker agents in feature branches:
| Protected Path | Reason |
|---|---|
\{spec-dir\} | Spec changes require coordinated review |
\{adr-dir\} | ADR changes require coordinated review |
CLAUDE.md (project root) | Shared configuration — concurrent edits cause conflicts |
.claude-plugin-design.json | Plugin configuration — concurrent edits cause conflicts |
Exception: Governing comments (per 📝 ADR-0020) MUST be added in feature PRs, not deferred. These are inline code comments (e.g., // Governing: ADR-XXXX, SPEC-XXXX REQ "...") and are NOT considered design document modifications.
This list is passed to every worker in step 9.4 and enforced in worker step 7a.
3c. Tier 4 issues sync (v5.0.0+):
<!-- Governing: ADR-0026 (Tiered Index Freshness), SPEC-0019 REQ "Tier 4 Always-Sync Issues for Sprint Skills" -->
Before discovering workable issues (Step 4), sync the \{repo\}-issues qmd collection from the tracker so the lead and all workers see current issue state. This also feeds the Sibling PR Manifest (Step 8a) with fresh data. Subject to the 5-min dedup window per references/tracker-sync.md § "Cursor Management".
-
Read
.sdd/issues/_meta.json. Iflast_syncis within the last 5 minutes, skip the sync silently. -
Otherwise, invoke per-tracker fetch+normalize per
references/tracker-sync.md. Print: "Syncing N issues from {tracker}…". -
On sync failure, surface a one-line warning per
tracker-sync.md§ "Failure Modes and Degradation" and proceed with live tracker queries (the pre-v5 path) for this run. Do NOT block; work dispatch is the user's primary intent. -
Discover workable issues: Search the tracker for open issues:
- If a spec was provided: find all open issues referencing that spec.
- If issue numbers were provided: fetch those specific issues.
- If no arguments were provided: fetch all open, non-epic issues across the tracker (or the configured project/milestone scope in CLAUDE.md
Projectsif present).
Filtering rules:
-
Skip epics: Issues labeled
epicor titled "Implement ..." are grouping issues, not implementation work. -
Skip issues without
### Branchsections: These lack branch naming conventions. If any are found, suggest/sdd:enrichto add them and report which issues were skipped. -
Extract branch names: Parse the
### Branchsection from each issue body to get the deterministic branch name (e.g.,feature/42-jwt-token-generation). -
Extract PR conventions: Parse the
### PR Conventionsection for close keywords and epic references. -
Detect dependency ordering: If issue bodies reference dependencies or logical ordering, respect that order when queuing work. For Gitea, query native dependencies via
GET /repos/\{owner\}/\{repo\}/issues/\{index\}/dependencies(or via MCP tools discovered byToolSearch) to find unblocked stories. (Governing: SPEC-0011 REQ "Gitea Native Dependencies") -
Enforce dependency readiness (Governing: SPEC-0015 REQ "Issue Lifecycle Labels"): Before marking any issue as ready to work, check its dependencies:
- Parse the issue body for dependency references:
Depends on #NNN,Blocked by #NNN,blocks:syntax, or any#NNNreference in a dependencies section. - For each dependency, query the tracker for that issue's labels.
- If any dependency does NOT have the
mergedlabel, the issue is blocked and MUST NOT be started. - Report blocked issues clearly:
Issue #274 is blocked by #273 (currently: in-progress) - Place blocked issues in a deferred queue. When a dependency reaches
mergedstate (detected during monitoring in step 11), automatically move newly-unblocked issues to the ready queue. - For Gitea trackers, also check native dependencies via the API in addition to body parsing.
- Parse the issue body for dependency references:
If no workable issues are found after filtering, report why and suggest
/sdd:plan(no issues at all) or/sdd:enrich(issues exist but lack branch sections). -
Dry-run gate: If
--dry-runis set, output a preview table and stop:## Dry Run: /sdd:work [SPEC-0003 | issue batch]
Would create {N} worktrees with up to {max-agents} parallel agents.
| # | Issue | Branch | Status |
|---|-------|--------|--------|
| 1 | #42 JWT Token Generation | feature/42-jwt-token-generation | Ready |
| 2 | #43 Token Validation | feature/43-token-validation | Ready |
| 3 | #44 Token Refresh | feature/44-token-refresh | Blocked (depends on #42) |
| 4 | #47 Setup Auth Module | feature/47-setup-auth-module | Skipped (no ### Branch) |
### Skipped Issues
- #45 Implement Auth Service (epic — skipped)
- #47 Setup Auth Module (no ### Branch section — run `/sdd:enrich`)
No changes were made.
5a. Apply queued labels (Governing: SPEC-0015 REQ "Issue Lifecycle Labels"): For all workable issues that passed filtering, apply the queued label to indicate they are in the work queue. This provides visibility into the batch before agents start.
For GitHub:
gh issue edit {issue-number} --add-label "queued"
For Gitea: Use ToolSearch to discover label MCP tools and apply the label via API.
-
Verify git state:
- Run
git statusto check for uncommitted changes. If there are uncommitted changes, useAskUserQuestionto ask:- "You have uncommitted changes. Continue anyway, or commit first?"
- Options: "Continue anyway" / "Stop so I can commit"
- If the user says stop, halt and report.
- Run
git fetchto ensure we have the latest remote state.
- Run
-
Read worktree config from CLAUDE.md: Follow the "Config Resolution" pattern in the plugin's
references/shared-patterns.md. Read the#### Worktreessubsection from the### SDD Configurationsection in CLAUDE.md. Defaults:Base Dir=.claude/worktrees/,Max Agents=3,Auto Cleanup=false,PR Mode="ready". CLI flags override config values.
7a. Resolve parallelism limit (Governing: SPEC-0015 REQ "Parallelism Limits", 📝 ADR-0017 Layer 1):
Determine the maximum number of concurrent agents using this precedence order (highest to lowest):
--max-agents NCLI flag (if provided)- CLAUDE.md
## SDD Configurationsection, keymax-parallel-agents(if present) - Default: 4
Reading from CLAUDE.md: Scan the project's CLAUDE.md for a ## SDD Configuration section. Look for a line matching - **Max parallel agents**: N or max-parallel-agents: N. Parse the integer value. Example:
## SDD Configuration
- **Max parallel agents**: 2
- **Hotspot threshold**: 40%
Enforce the cap: The resolved limit MUST NOT be exceeded. When more stories are ready for parallel execution than the limit allows:
- Start up to
max-parallel-agentsstories immediately - Queue excess stories in dependency-respecting order
- As active agents complete (transition to
in-reviewormerged), start the next queued story
Report before starting: Before spawning any agents, report the parallelism plan to the user:
Starting {N} of {M} ready stories ({Q} queued, max-parallel-agents: {limit})
Example: "Starting 4 of 8 ready stories (4 queued, max-parallel-agents: 4)"
-
Create team: Use
TeamCreateto create a coordination team following the "Worker Coordination" protocol fromreferences/shared-patterns.md§ "Multi-Agent Team Protocols". The lead (you) manages the task queue and monitors progress. Spawn up to the resolved parallelism limit (from step 7a) worker agents usingTaskwithsubagent_type: "general-purpose".If
TeamCreatefails, fall back to single-agent sequential mode: work through each issue one at a time in the main session usinggit worktree addfor each.
8a. Build sibling PR manifest (Governing: SPEC-0015 REQ "Pre-Flight PR Awareness"):
Before dispatching any workers, build a pre-flight awareness manifest so each agent knows what siblings are doing. Follow the Pre-Flight PR Awareness pattern in references/shared-patterns.md.
-
Query the tracker for all open PRs in the current sprint, epic, or spec scope:
gh pr list --search "SPEC-XXXX" --json number,title,headRefName,body,url,labels --limit 50Include PRs from previous
/sdd:workruns that haven't merged yet. -
For each open PR, extract file ownership:
gh pr diff {number} --name-onlyScan the diff for new type/struct/interface/class/function definitions to identify shared artifacts.
-
For foundation PRs (labeled
foundation), catalog all shared types and helpers with their file paths and merge status (merged = available on main, open = available after merge). -
Assemble the Sibling PR Manifest with three sections:
- Files Currently Being Modified by Siblings — file paths with owning PR/issue numbers
- Shared Types Available — types, helpers, and their locations (with merge status)
- In-Progress Sibling PRs — table of PR number, issue, branch, files, and status
This manifest is injected into each worker's context in step 9.4. Workers keep it current via live SendMessage broadcasts per the Worker Communication Protocol in references/shared-patterns.md.
-
Create worktrees and assign work: For each workable issue (respecting dependency order and max-agents concurrency):
9.1: Create the worktree.
git worktree add .claude/worktrees/{branch-name} -b {branch-name}Use the base directory from CLAUDE.md
Worktrees > Base Dirif set, otherwise.claude/worktrees/.9.2: Transition to
in-progress(Governing: SPEC-0015 REQ "Issue Lifecycle Labels"): When assigning an issue to a worker, update its lifecycle state:-
Set assignee: Assign the current user (or a worker identifier) to the issue.
gh issue edit {issue-number} --add-assignee "@me"For Gitea, use
ToolSearchto discover issue MCP tools and set the assignee via API. -
Remove
queued, applyin-progress: Each transition MUST remove the previous label before applying the new one.gh issue edit {issue-number} --remove-label "queued" --add-label "in-progress"
9.3: Create a task using
TaskCreatefor each issue, with the issue details, branch name, and worktree path.9.4: Assign to a worker using
TaskUpdatewith the worker's name asowner. Send the worker a message viaSendMessagewith all context needed to implement the issue. -
-
Worker implementation protocol: Each worker receives and follows this protocol:
Worker receives:
- Issue number, title, and full body (with acceptance criteria)
- Branch name (from
### Branchsection) - PR convention (from
### PR Conventionsection) - Spec content (spec.md) — if a spec was resolved for this issue; omitted otherwise
- Design content (design.md) — if a spec was resolved; omitted otherwise
- ADR content (any referenced ADRs) — if a spec was resolved; omitted otherwise
- Sibling PR Manifest (from step 8a) — files being modified by siblings, shared types available, in-progress sibling PRs
- Worktree absolute path
- Whether to run tests (
--no-testsflag) - PR mode (
draftorready)
Worker steps:
- All file operations use the worktree absolute path (read, write, edit, glob, grep).
- Read the issue body and understand the acceptance criteria.
- Explore existing code in the worktree to understand the codebase structure.
3a. qmd-aware code pre-search before writing (v5.0.0+).
<!-- Governing: ADR-0024 (qmd as hard dependency), SPEC-0019 REQ "qmd-Smart Sprint Skills" -->
Before writing any new helper, type, struct, interface, or substantial code block, qmd-search
\{repo\}-codefor existing patterns. This complements the Sibling PR Manifest (which covers in-flight work) by surfacing patterns already on main that the worker would otherwise re-create. Mitigates the duplicate-implementation drift Foundation Story Detection (per 📝 ADR-0017) was designed to catch.-
Construct a hybrid query per
references/qmd-helpers.md§ "Hybrid Retrieval":lex: the planned helper / type / function name AND key terms from its purpose (e.g., for aparseUserIDhelper: "parseUserID parse user id authentication token")vec: a one-sentence framing of what the worker is about to implement (e.g., "extract a numeric user ID from an authenticated request context")intent: "/sdd:work — find existing helpers/types/patterns to import rather than recreate"collections: ["\{repo\}-code"]limit: 6,minScore: 0.4
-
For each match above the threshold, the worker MUST:
- Read the matched file's relevant portion in full
- If the existing implementation covers the worker's need, IMPORT it rather than create a duplicate
- Broadcast
TYPE_IMPORTED: #\{issue\} will import \{TypeName\} from \{file-path\}per the Worker Communication Protocol (already inshared-patterns.md) so the lead and siblings know
-
If qmd returns zero matches above the threshold, proceed with the new implementation as planned.
-
On qmd unreachable / timeout per
qmd-helpers.md§ "Error Handling", surface the error to the lead via SendMessage and stop work on this issue. Per 📝 ADR-0024, the pre-v5 fallback ("just write new code") is gone in v5; the failure mode is "fix qmd, retry."
3b. Coordinate with sibling workers (Governing: SPEC-0015 REQ "Pre-Flight PR Awareness"). Follow the "Worker Communication Protocol" in
references/shared-patterns.md. Before modifying any file:- Check the Sibling PR Manifest for files already claimed by siblings. If the file appears under "Files Currently Being Modified by Siblings", send
CONFLICT_ALERTand wait for lead coordination instead of modifying it. - Check for shared types in the manifest's "Shared Types Available" section. If a needed type, struct, interface, or helper already exists (from a merged foundation PR or an in-progress sibling), import it from the expected location instead of creating a duplicate.
- Broadcast live updates via
SendMessageto all siblings:FILE_CLAIM: #\{issue\} claiming \{file-path\}— before modifying any fileTYPE_CREATED: #\{issue\} created \{TypeName\} in \{file-path\}— after creating new shared types, structs, interfaces, or helpersAVAILABILITY: #\{issue\} PR merged — \{artifact list\} now on main— when the lead detects a sibling PR has merged
- Listen for sibling broadcasts and update the local manifest accordingly:
- On
FILE_CLAIMfrom a sibling: add the file to the "avoid modifying" list - On
TYPE_CREATEDfrom a sibling: add the type to "Shared Types Available" and import it rather than recreating - On
AVAILABILITYfrom lead: move artifacts from "in-progress" to "available on main" and lift file avoidance for those files
- On
-
Implement changes to satisfy the acceptance criteria.
-
If spec context was provided, leave governing comments in the code per
references/shared-patterns.md§ "Governing Comment Format":// Governing: ADR-XXXX (short description), SPEC-XXXX REQ "Requirement Name" -
Run tests (unless
--no-tests). If tests fail, attempt to fix (max 2 fix attempts). If still failing after 2 attempts, report blocked with details. -
Assess PR size before creating. Run
git -C \{worktree-path\} diff --statto see the scope of changes. Use judgement about whether this warrants a standalone PR:- Comments-only changes (only comment lines added/changed, no logic): not worth a standalone PR
- Trivially small (fewer than ~30 lines of substantive code across the whole branch): likely not worth a standalone PR
- 100+ lines of meaningful code changes: clearly worth a standalone PR
- The in-between range (30-100 lines) requires judgement — consider whether a reviewer would find it worthwhile to context-switch for
If the implementation is too small to justify its own PR, send the lead a bundle request:
BUNDLE_REQUEST: #42 implementation is trivially small (8 lines changed, comments only). Requesting additional issues to bundle into this branch before opening a PR.Wait for the lead to either assign additional issues or confirm proceeding with a small PR (e.g., queue is exhausted). If additional issues are assigned, return to step 2 for each, implementing them in the same worktree, then proceed with a combined commit and PR covering all bundled issues. 7a. Validate diff against protected paths (Governing: SPEC-0015 REQ "Design Document Isolation"): Before staging, check for modifications to protected paths defined in step 3b:
git -C {worktree-path} diff --name-onlyFor each modified file, check if it falls under a protected path (
\{spec-dir\},\{adr-dir\}, rootCLAUDE.md,.claude-plugin-design.json).If protected files are found:
- Revert each protected file:
git -C {worktree-path} checkout -- {protected-file} - Record the intended changes as deferred updates. For each reverted file, capture what was changed and why.
- Include a
### Deferred Design Doc Updatessection in the PR body listing each deferred change:### Deferred Design Doc Updates
- `docs/adrs/ADR-0005.md`: Update status from "proposed" to "accepted"
- `docs/openspec/specs/auth/spec.md`: Add new requirement for token rotation
Exception: Governing comments in source code files are NOT protected — they are inline code annotations, not design document modifications. Only files under the protected paths listed in step 3b are subject to this validation.
-
Stage and commit changes:
git -C {worktree-path} add .
git -C {worktree-path} commit -m "{descriptive message}\n\nImplements #{issue-number}\nGoverning: SPEC-XXXX"If multiple issues were bundled, list all of them in the commit message.
-
Push the branch:
git -C {worktree-path} push -u origin {branch-name} -
Create a PR using the tracker's tools or CLI:
- Title: the issue title (or a combined title if issues were bundled)
- Body: Include close keywords for all bundled issues, reference the epic, reference the spec
- Regular (non-draft) by default, draft if
--draftwas set
-
Transition to
in-review(Governing: SPEC-0015 REQ "Issue Lifecycle Labels"): After the PR is created, update lifecycle labels:gh issue edit {issue-number} --remove-label "in-progress" --add-label "in-review"If multiple issues were bundled into one PR, transition ALL bundled issues to
in-review. -
Report outcome to lead via
SendMessage: success (with PR URL and list of bundled issues) or failure (with details).
-
Monitor, queue, and lifecycle transitions: The lead tracks worker progress (Governing: SPEC-0015 REQ "Parallelism Limits", SPEC-0015 REQ "Issue Lifecycle Labels"):
- Enforce parallelism cap: Never exceed the resolved
max-parallel-agentslimit from step 7a. Track the count of active agents at all times. - Transition to
mergedon PR merge: When a worker reports success and the PR is subsequently merged (either via/sdd:reviewor manually), transition the issue:If the work session itself does not merge PRs, thegh issue edit {issue-number} --remove-label "in-review" --add-label "merged"mergedtransition will be handled by/sdd:reviewor the next/sdd:workinvocation that detects merged PRs. - Tier 1 mutation update on merge (v5.0.0+, Governing: 📝 ADR-0026, SPEC-0019 REQ "Tier 1 Mutation-Aware Updates"): After detecting a PR merge, before transitioning to
merged, trigger a narrow re-sync of\{repo\}-codeso the qmd index reflects the newly-merged code. Use the canonical update pattern fromreferences/qmd-helpers.md§ "Update Patterns". Best-effort and silent on success. On failure, append a one-line warning to the run log ("Index refresh failed for\{repo\}-codeafter merging PR #{N} — run/sdd:index updatemanually") but the merged-label transition still proceeds. - Unblock deferred issues: After any issue transitions to
merged, re-check the deferred queue from step 4. For each deferred issue, re-query its dependencies. If ALL dependencies now have themergedlabel, move the issue to the ready queue and start it if an agent slot is available. - When a worker finishes, check if there are queued issues waiting.
- If queued issues have dependency requirements, check if dependencies are now satisfied.
- Assign the next available issue to the freed worker, maintaining up to
max-parallel-agentsactive agents. - If a worker reports failure, note it and continue with other issues. The freed slot is available for queued work. The issue retains its
in-progresslabel (do NOT transition failed issues tomerged). - Handle bundle requests: When a worker sends a
BUNDLE_REQUEST, check the issue queue for additional issues that could be bundled into the same branch. If available (and not blocked by dependencies), assign them to the same worker with instructions to implement in the same worktree before creating a PR. If the queue is exhausted or all remaining issues are blocked, tell the worker to proceed with the small PR as-is. - Queue status reporting: After each agent completion or queue change, log the current state: "Active: {N}/{limit}, Queued: {Q}, Completed: {C}, Failed: {F}, Blocked: {B}"
- Enforce parallelism cap: Never exceed the resolved
11a. Compute topological merge order (Governing: SPEC-0015 REQ "Topological Merge Ordering"):
After all workers have completed and PRs are in in-review state, compute the optimal merge order before merging begins. Follow the Topological Merge Ordering pattern in references/shared-patterns.md.
-
Collect file lists for each PR:
gh pr diff {number} --name-only -
Build file overlap graph. For each pair of PRs, compute the intersection of modified files. PRs that share files have an ordering dependency.
-
Classify PRs into tiers:
- Tier 0 (isolated): PRs with zero file overlap with any other PR — merge first, in any order
- Tier 1 (foundation): PRs labeled
foundationwith dependents — merge after Tier 0 - Tier N (dependent): PRs overlapping with Tier N-1 PRs — merge after predecessors, rebase first
-
Detect circular dependencies. If the overlap graph contains a cycle, report it to the user and request manual resolution:
Circular file dependency detected between PRs #X and #Y (both modify {files}).
Please specify which should merge first.Do NOT proceed with merging the cycle until the user resolves it.
-
Offer PR stacking when a direct dependency exists (PR B depends on PR A via issue body AND they share files):
PR #143 depends on PR #141. Stack #143 on top of #141's branch to avoid rebase conflicts? (Y/n) -
Report the merge order to the user before merging:
### Merge Order
| Order | PR | Issue | Overlapping Files | Action |
|-------|----|-------|-------------------|--------|
| 1 | #142 | #42 Isolated feature | (none) | Merge |
| 1 | #145 | #45 Another isolated | (none) | Merge |
| 2 | #141 | #41 Foundation types | main.go, sync.go | Merge, then rebase remaining |
| 3 | #143 | #43 Depends on #141 | sync.go | Rebase, then merge |
| 4 | #144 | #44 Touches everything | main.go, sync.go, config.go | Rebase, then merge |
Proceed with this merge order? (Y/n) -
Execute the merge sequence (after user approval):
- Merge all Tier 0 PRs (parallel-safe)
- After each merge, auto-rebase all remaining open PRs against main:
git fetch origin main
git -C {worktree-path} rebase origin/main
git -C {worktree-path} push --force-with-lease - If a rebase fails, report the failure and preserve the worktree for manual resolution
- Proceed tier by tier until all PRs are merged
- Transition merged issues to
mergedlabel per step 11's lifecycle rules
-
Cleanup and report: After all issues are processed:
12.1: Shut down team. Send
shutdown_requestto all workers viaSendMessage.12.2: Offer worktree cleanup. If CLAUDE.md
Worktrees > Auto Cleanupistrue, remove worktrees for successfully-PRed issues automatically. Otherwise, useAskUserQuestion:- "Remove worktrees for completed issues? (Failed issue worktrees are always preserved.)"
- Options: "Yes, clean up" / "No, keep them"
- If yes:
git worktree remove .claude/worktrees/\{branch-name\}for each successful issue.
12.3: Batch deferred design doc updates (Governing: SPEC-0015 REQ "Design Document Isolation"):
After all sprint PRs are merged, collect deferred design document updates from all PR bodies:
- Scan each merged PR body for a
### Deferred Design Doc Updatessection. - If any deferred updates exist, create a single batch PR:
- Branch:
docs/sprint-\{N\}-design-updates(ordocs/design-updates-\{date\}if no sprint number is available) - Apply all deferred changes to the protected files on this branch
- PR title: "Update design docs for Sprint {N}" (or "Batch design doc updates for {date}")
- PR body: List all changes grouped by file, with references to the originating PRs
- Label:
documentation
- Branch:
- If no deferred updates were found across any PRs, skip this step.
This ensures design documents are updated in a single, reviewable PR rather than risking merge conflicts from parallel modifications.
12.4: Final report.
## Work Complete: [SPEC-0003 | issue batch]
Implemented {N} of {M} issues using {agent-count} parallel agents.
### Results
| Issue | Branch | PR | Status |
|-------|--------|----|--------|
| #42 JWT Token Generation | feature/42-jwt-token-generation | #101 | in-review |
| #43 Token Validation | feature/43-token-validation | #102 | in-review |
| #44 Token Refresh | feature/44-token-refresh | — | Failed (in-progress) |
| #45 Session Revocation | — | — | Blocked by #44 |
### Failed Issues
- **#44 Token Refresh**: Tests failing after 2 fix attempts. Worktree preserved at `.claude/worktrees/feature/44-token-refresh` for manual pickup. Error: `TokenRefreshService.refresh() returns expired token in test_refresh_expired`.
### Blocked Issues
- **#45 Session Revocation**: Blocked by #44 (currently: in-progress). Will be unblocked when #44 reaches `merged`.
### Worktrees
- Cleaned up: 2
- Preserved (failed): 1
### Next Steps
- Run `/sdd:review` for automated spec-aware PR review and merge
- Fix failing issue #44 manually or re-run `/sdd:work 44`
- Run `/sdd:check` to verify implementation alignment
- Run `/sdd:audit` for comprehensive drift analysis
Rules
- A spec is NOT required —
/sdd:workcan operate from the backlog alone - When no arguments are provided, MUST analyze the backlog and propose a batch to the user before starting any work
- MUST read spec.md and design.md before dispatching workers only when a spec is provided or resolvable from issue bodies
- MUST use
ToolSearchto discover tracker MCP tools at runtime — never assume specific tools are available - MUST follow the Config Resolution pattern from
references/shared-patterns.mdto read configuration from CLAUDE.md - MUST extract branch names from issue bodies — never invent branch names
- MUST skip epics (labeled
epicor titled "Implement ...") — only work on implementation issues - MUST skip issues without
### Branchsections and suggest/sdd:enrich - MUST respect dependency ordering when queuing work
- MUST create regular (non-draft) PRs by default — only create draft PRs with
--draft - MUST leave governing comments per
references/shared-patterns.md§ "Governing Comment Format" in implemented code when spec context is available; omit when there is no spec - MUST report all failures with actionable details — never silently skip
- MUST preserve worktrees for failed issues — never auto-clean failures
- Workers MUST use worktree absolute paths for all file operations
- Workers MUST NOT modify files outside their assigned worktree
- Workers MUST push and create PRs before reporting success
- Workers MUST assess PR size before opening a PR — do NOT create comments-only PRs or trivially small PRs (<30 lines of substantive code) as standalone PRs; send a
BUNDLE_REQUESTto the lead instead - Lead MUST handle
BUNDLE_REQUESTby checking the queue for additional bundleable issues before telling the worker to proceed - If no additional issues are available to bundle, worker MAY create a small PR and SHOULD note in the PR body why it is small (no more queued work to combine)
- The lead MUST stay in the main working tree — only workers operate in worktrees
--dry-runMUST NOT create any worktrees, branches, or PRs- Maximum 2 test-fix attempts per worker before reporting blocked
- When
TeamCreatefails, MUST fall back to single-agent sequential mode — never error out - For Gitea trackers, MUST query native dependencies via API to determine unblocked stories (Governing: SPEC-0011 REQ "Gitea Native Dependencies")
- MUST NOT spawn more than
max-parallel-agentsconcurrent agents (default: 4); resolve from CLI flag → CLAUDE.md → default (Governing: SPEC-0015 REQ "Parallelism Limits", 📝 ADR-0017 Layer 1) - MUST read
## SDD Configurationfrom CLAUDE.md formax-parallel-agentssetting before falling back to default - MUST queue excess stories when more are ready than the parallelism limit allows, starting them as active agents complete
- MUST report active agent count and queue depth to the user before starting work ("Starting N of M ready stories (Q queued, max-parallel-agents: limit)")
- Workers MUST broadcast
FILE_CLAIMviaSendMessagebefore modifying any file - Workers MUST broadcast
TYPE_CREATEDviaSendMessageafter creating new types, structs, interfaces, or shared helpers - Workers receiving
TYPE_CREATEDMUST import the type rather than creating a duplicate - Workers receiving
FILE_CLAIMfor a file they also need MUST sendCONFLICT_ALERTand wait for lead coordination - MUST ensure lifecycle labels (
queued,in-progress,in-review,merged) exist before assigning work — using the try-then-create pattern (seereferences/shared-patterns.md) (Governing: SPEC-0015 REQ "Issue Lifecycle Labels") - MUST apply
queuedlabel to all workable issues upon discovery - MUST transition
queued->in-progresswhen an agent picks up an issue, removing the previous label first - MUST transition
in-progress->in-reviewwhen a PR is created, removing the previous label first - MUST transition
in-review->mergedwhen a PR is merged, removing the previous label first - Each label transition MUST remove the outgoing label before applying the incoming label — no issue should have two lifecycle labels simultaneously
- Label management is best-effort — if label operations fail, log the failure but do NOT block implementation work
- MUST set assignee on the issue when an agent picks it up (
gh issue edit --add-assignee "@me"or equivalent) - MUST check dependency status before starting work on any issue — parse issue body for
Depends on #NNN,Blocked by #NNN, andblocks:syntax - MUST refuse to start work on issues whose dependencies have not reached
mergedstate - MUST report blocked issues with the specific blocking dependency and its current state (e.g., "Issue #274 is blocked by #273 (currently: in-progress)")
- MUST place blocked issues in a deferred queue and re-check when dependencies transition to
merged - MUST detect circular dependencies and refuse to start any issue in the cycle
- Queue status reporting MUST include blocked count: "Active: {N}/{limit}, Queued: {Q}, Completed: {C}, Failed: {F}, Blocked: {B}"
- MUST build a sibling PR manifest before dispatching workers by querying the tracker for all open PRs in the sprint/epic scope (Governing: SPEC-0015 REQ "Pre-Flight PR Awareness")
- MUST inject the sibling PR manifest into each worker's context when assigning work
- Workers MUST check the sibling PR manifest before modifying files or creating types — if a file or type is already claimed by a sibling, the worker MUST coordinate rather than duplicate
- Workers MUST update their local manifest dynamically via
SendMessagebroadcasts (FILE_CLAIM, TYPE_CREATED, AVAILABILITY) - Lead MUST broadcast
AVAILABILITYmessages when foundation or sibling PRs merge so workers can lift file avoidance directives - MUST compute topological merge order based on file overlap analysis after all PRs reach
in-reviewstate (Governing: SPEC-0015 REQ "Topological Merge Ordering") - PRs with zero file overlap MUST be merged first (Tier 0, any order)
- PRs sharing files with already-merged PRs MUST rebase before merging
- MUST report the computed merge order to the user and await approval before merging begins
- MUST offer PR stacking when two PRs have a direct dependency relationship AND share modified files
- MUST auto-rebase all remaining open PRs after each merge
- MUST detect circular file dependencies in the merge order graph and request manual resolution — do NOT merge PRs in a cycle without user input
- If a rebase fails during merge ordering, MUST preserve the worktree and report the conflict for manual resolution
- Workers MUST NOT modify protected paths (
\{spec-dir\},\{adr-dir\}, rootCLAUDE.md,.claude-plugin-design.json) in feature branches (Governing: SPEC-0015 REQ "Design Document Isolation") - Workers MUST run
git diff --name-onlybefore staging and revert any protected files that were modified - Workers MUST record reverted protected-file changes in a
### Deferred Design Doc Updatessection in the PR body - Governing comments (per 📝 ADR-0020) are inline code annotations and MUST be added in feature PRs — they are NOT subject to design document isolation
- Lead MUST collect all deferred design doc updates after sprint PRs merge and create a single batch PR for them
- If no deferred design doc updates exist across any PRs, the batch PR step MUST be skipped
- v5.0.0+: MUST trigger Tier 4 issues sync on entry per Step 3c — sync from tracker before Step 4 issue discovery, subject to 5-min dedup. On failure, fall back to live queries with a warning, never block (Governing: 📝 ADR-0026, SPEC-0019 REQ "Tier 4 Always-Sync Issues for Sprint Skills")
- v5.0.0+: Workers MUST run qmd-aware code pre-search per worker Step 3a before writing any new helper/type/struct/interface — qmd-search
\{repo\}-codefor matches; if found above threshold (minScore 0.4), MUST import the existing implementation and broadcastTYPE_IMPORTEDrather than recreate (Governing: 📝 ADR-0024, SPEC-0019 REQ "qmd-Smart Sprint Skills") - v5.0.0+: Lead MUST trigger Tier 1 update of
\{repo\}-codeafter detecting a PR merge per Step 11 — best-effort, silent on success, one-line warning on failure (Governing: 📝 ADR-0026, SPEC-0019 REQ "Tier 1 Mutation-Aware Updates")
Why git worktree add Instead of EnterWorktree
EnterWorktreecreates random branch names; we need deterministic names matching### Branchconventions from issue bodies.EnterWorktreeswitches the session's working directory; the lead agent must stay in the main tree to coordinate.git worktree addgives full control over branch name and worktree location.
Error Handling
| Condition | Behavior |
|---|---|
| Worker can't complete implementation | Reports failure to lead, worktree preserved for manual pickup |
| Tests fail after 2 retries | Worker reports blocked with error details, moves to next issue |
TeamCreate fails | Falls back to single-agent sequential mode |
| No workable issues found | Suggest /sdd:plan (no issues at all) or /sdd:enrich (issues exist but lack ### Branch) |
| Uncommitted changes in main tree | Ask user whether to continue or commit first |
git worktree add fails (branch exists) | Check if the branch already exists remotely. If so, use git worktree add .claude/worktrees/\{branch-name\} \{branch-name\} (without -b) to check out the existing branch |
| Push fails (remote rejection) | Worker reports the error to lead; worktree preserved |
| PR creation fails | Worker reports the error to lead; branch is still pushed, user can create PR manually |
| Tracker not available | Suggest /sdd:plan to create issues first |
| Issue has no acceptance criteria | Worker uses the issue title and body as guidance, warns in PR description |
| Label creation fails (permissions) | Warn the user that lifecycle labels could not be created, continue without label management |
| Label transition fails | Log the failure but do NOT block work — label management is best-effort, implementation is primary |
| Dependency issue not found in tracker | Treat as unblocked (dependency may have been deleted or moved) with a warning |
| All issues are blocked by dependencies | Report the dependency graph and suggest resolving blocking issues first |
| Circular dependency detected | Report the cycle and refuse to start any issue in the cycle; suggest the user break the cycle manually |
| Sibling PR manifest query fails | Proceed without manifest — workers operate independently with live broadcasting only |
gh pr diff fails for a sibling PR | Omit that PR from the manifest with a warning; continue with available data |
| Rebase fails during merge ordering | Preserve the worktree, report conflicting files, skip that PR, and continue with remaining PRs |
| Circular file dependency in merge order | Report the cycle to the user and request manual merge order specification |
| All PRs have file overlaps (no Tier 0) | Start with foundation PRs, then proceed by overlap count (fewest overlaps first) |
| Worker modifies protected path (design docs) | Revert the protected file, record change in ### Deferred Design Doc Updates PR body section, continue with implementation |
| No deferred design doc updates after sprint | Skip batch PR creation — nothing to consolidate |
| Batch design doc PR conflicts | Report conflict to user for manual resolution |
Loop Mode (autonomous)
<!-- Governing: ADR-0028 (/loop Autonomous Mode for /sdd:work and /sdd:review), SPEC-0020 REQ "Loop Mode Opt-In", REQ "CLI Surface for Loop Controls", REQ "Backlog-Empty Stop", REQ "Iteration Budget Stop", REQ "PR-Touch Budget Stop", REQ "Wall-Clock Budget Stop", REQ "Repeated-Failure Stop", REQ "Dependency-Cycle Stop", REQ "User Interrupt Stop", REQ "Lockfile Contention Skip", REQ "Prior-Gate-Stop Honor", REQ "qmd-Unreachable Stop", REQ "Cost Budget Stop", REQ "Concurrency Invariants for /sdd:work", REQ "Backlog-Drift Gate", REQ "Ambiguous-Acceptance-Criteria Gate", REQ "Budget-Escalation Gate", REQ "Force-Unlock Gate", REQ "Repeated-Failure Gate", REQ "Gates Are Not Debounced Across Iterations", REQ "Final Report on Stop" -->
When /sdd:work is invoked under /loop with the --loop flag (e.g. /loop /sdd:work --loop), the skill enters autonomous-mode and follows the contract below on every tick. Without --loop, the skill behaves exactly as the rest of this document specifies and creates no .sdd/loop/ artifacts (per SPEC-0020 REQ "Loop Mode Opt-In").
The runtime /loop skill is unchanged. --loop is a skill-side opt-in. /loop schedules ticks; everything inside a tick is the wrapped skill's concern.
CLI surface
When --loop is set, /sdd:work accepts the following additional flags. All are optional with the documented conservative defaults (per SPEC-0020 REQ "CLI Surface for Loop Controls"). Budgets are inclusive across the entire loop run, not per-iteration.
| Flag | Default | Purpose |
|---|---|---|
--loop | off | Opt into autonomous-mode |
--max-iterations N | 5 | Iteration ceiling across the run |
--max-prs N | 20 | Distinct-PR ceiling across the run |
--max-minutes N | 60 | Wall-clock ceiling across the run |
--max-dollars N | 25 | Dollar-cost ceiling; 0 disables condition #12 (estimate still tracked) |
--lock=\{skip|wait|force\} | skip | Concurrency mode on lockfile contention |
--resume | off | Recover state from the most recent history.jsonl line |
--budget-file PATH | .sdd/loop/work.budget.json | Override the budget-file location |
--no-chain | off | Skip the post-PR chain (/sdd:review + /autofix-pr); restores legacy "open PR, stop" behavior. See "Post-PR Chain" below. |
The first write of budget.json records the active ceilings (per references/loop-primitives.md § First-write rule) so a later --resume cannot silently widen them.
Per-tick flow
Each tick follows this canonical flow (control flow diagram in docs/openspec/specs/loop-autonomous-mode/design.md):
- Acquire lockfile at
.sdd/loop/work.lockperreferences/loop-primitives.md§ Acquisition flow (skip / wait / force per--lock). - Read budget from
.sdd/loop/work.budget.json; on first write, initialize ceilings,started_at, andrate_table_source(perreferences/loop-primitives.md§ First-write rule). - Evaluate stop conditions on entry (see "Stop conditions" below). Any matching condition halts the loop, emits the final report, and releases the lockfile.
- Run the gate block (see "AskUserQuestion Gates" below). Any gate answered
stophalts the loop. - Run the iteration body — discover workable issues per Step 4 of the main flow above (skipping issues labeled
in-progressper "Concurrency invariants"), dispatch workers, open PRs. - For each PR opened in this iteration, invoke the post-PR chain per "Post-PR Chain" below (unless
--no-chainor--dry-run). - Update budget — increment
iterations_used, unionprs_touched, accumulatetokens_in/tokens_out/agents_dispatched, recomputedollars_estimate, evaluate exit-time stop conditions 3 / 4 / 5 / 12. - Emit telemetry — append a line to
.sdd/loop/work.history.jsonl(perreferences/loop-telemetry.md) and emit the stdout status block. - Release lockfile and let
/loopschedule the next tick.
Stop conditions
The loop halts on any matching condition. The cause is recorded in stop_conditions_fired[] of the final history line.
| # | Condition | Behavior |
|---|---|---|
| 1 | Backlog empty (no unblocked, unworked, in-scope issues on entry) | Final report names empty queue; release lock; do NOT signal another tick (SPEC-0020 REQ "Backlog-Empty Stop") |
| 3 | iterations_used >= max_iterations (entry-time check) | stop_conditions_fired: ["iteration_budget"] (SPEC-0020 REQ "Iteration Budget Stop") |
| 4 | len(prs_touched) >= max_prs (deduplicated set; entry-time + after each PR open) | stop_conditions_fired: ["prs_touched_budget"] (SPEC-0020 REQ "PR-Touch Budget Stop") |
| 5 | minutes_elapsed >= max_minutes (clock anchored at started_at, persists across --resume) | stop_conditions_fired: ["wall_clock_budget"] (SPEC-0020 REQ "Wall-Clock Budget Stop") |
| 6 | Same issue/PR failed twice consecutively with the same root cause | Fire the Repeated-Failure gate (does NOT silently halt) (SPEC-0020 REQ "Repeated-Failure Stop") |
| 7 | Issue-dependency analysis (per SPEC-0015 Layer 2) detects a cycle | Halt and surface the cycle's edges; do NOT attempt automatic break (SPEC-0020 REQ "Dependency-Cycle Stop") |
| 8 | User interrupt (Ctrl-C / session close / explicit /loop stop) | Drain in-flight workers (no new dispatch), release lock, emit final report, no half-states (SPEC-0020 REQ "User Interrupt Stop") |
| 9 | Lockfile holds a live PID under --lock=skip | Emit one-line skip note; do NOT increment counters (SPEC-0020 REQ "Lockfile Contention Skip"). --lock=wait blocks bounded by max_minutes; --lock=force fires the Force-Unlock gate. |
| 10 | Any prior gates[] entry recorded answer == "stop" | "Loop already stopped at gate {name} in iteration {N}"; release lock; do NOT increment counters (SPEC-0020 REQ "Prior-Gate-Stop Honor") |
| 11 | qmd unreachable for 2 consecutive iterations | Halt with the 📝 ADR-0024 remediation message; the wrapped skill signals via stderr token qmd-unreachable OR exit code EX_QMD_UNREACHABLE=78; any successful iteration resets qmd_failures_consecutive to 0 (SPEC-0020 REQ "qmd-Unreachable Stop") |
| 12 | dollars_estimate >= max_dollars (and max_dollars > 0) | "Cost budget reached: $X / $Y"; --max-dollars 0 disables the stop but dollars_estimate is still tracked (SPEC-0020 REQ "Cost Budget Stop") |
Condition #2 (terminal PR state) does NOT apply to /sdd:work — it is a /sdd:review --loop --pr <N> condition.
qmd-unreachable detection protocol
The wrapped skill signals qmd-unreachable using either of these signals (per SPEC-0020 REQ "qmd-Unreachable Stop"):
- Stderr sentinel: a line on stderr containing the literal token
qmd-unreachable(perreferences/qmd-helpers.md§ "Error Handling") - Reserved exit code:
EX_QMD_UNREACHABLE = 78(matches BSDsysexits.hEX_CONFIG)
The loop reads exit status first; on non-zero, scans stderr for the sentinel as a fallback. Either signal is sufficient. On detection, increment qmd_failures_consecutive. On any successful iteration, reset to 0. On increment to 2, condition #11 trips.
AskUserQuestion gates
All gates are re-evaluated on every tick (per SPEC-0020 REQ "Gates Are Not Debounced Across Iterations") — the skill MUST NOT cache or reuse a prior iteration's answer to suppress a current iteration's gate. Each invocation is captured verbatim in the iteration's gates[] array per references/loop-telemetry.md.
| Gate | Trigger | Prompt template | Options |
|---|---|---|---|
| Backlog Drift | Unblocked-issue snapshot differs from the prior iteration's recorded snapshot | "Backlog changed since last iteration. Re-propose the next batch?" | re-propose, continue, stop |
| Ambiguous Criteria | Issue lacks ### Acceptance Criteria OR section contains TBD/TODO markers | "Issue #{N} has ambiguous criteria. Skip, escalate, or proceed with my best interpretation?" | skip, escalate, proceed, stop |
| Budget Escalation (80%) | One or more active budgets cross 80% on this tick | "Approaching {budget(s)} ({used}/{total} each). Continue, raise ceiling(s), or stop?" — combined into a single prompt across all simultaneously-tripped budgets | continue, raise, stop |
| Force-Unlock | --lock=force AND lockfile is present | "Force-unlock previous iteration's lock? This may corrupt in-flight work." | yes, no, stop |
| Repeated Failure | Same issue/PR failed in two consecutive iterations with the same root cause | "Issue/PR #{N} failed twice with: {root-cause}. Skip, retry once more, or stop the loop?" | skip, retry, stop |
| Resume-Divergence (resume only) | A tracked_prs[] entry's head_sha_at_iteration_end does not match the live remote HEAD | "PR #{N} has diverged since the prior iteration crashed — re-attach, skip, or stop the loop?" | re-attach, skip, stop |
Note: the Post-Feedback Merge gate is a /sdd:review --loop gate; it does NOT fire on the work side. Backlog-Drift is a work-side-only gate.
Multi-budget batching (gate 80%)
When two or more budgets cross 80% in the same tick, the gate fires once with a combined message listing every tripped budget (per SPEC-0020 REQ "Budget-Escalation Gate"). When any budget reaches 100% in the same tick that another crosses 80%, the 100%-stop wins (conditions 3 / 4 / 5 / 12 take precedence) and the gate is suppressed. Pseudocode in docs/openspec/specs/loop-autonomous-mode/design.md § Multi-budget 80% gate batching.
Concurrency invariants
/sdd:work --loop MUST NOT pick up an issue already labeled in-progress by a sibling iteration's worktree (per SPEC-0020 REQ "Concurrency Invariants for /sdd:work"). The check happens during workable-issue discovery in each iteration, before dispatch. The iteration MUST NOT clear or contest the label — it skips the issue and picks the next one.
This invariant is independent of the lockfile (which scopes "is another iteration running?"). The label invariant scopes "is this issue already being implemented somewhere?".
Final report
When the loop halts for any reason, the wrapped skill MUST emit a final report before lockfile release (per SPEC-0020 REQ "Final Report on Stop"). The report covers:
- The stop cause (single line, machine-readable name + human prose)
- Total iterations used / max
- Total PRs touched (deduplicated count) / max
- Total minutes elapsed / max
- Total dollars estimated / max
- List of every gate fired with its answer (across all iterations)
- Path to
.sdd/loop/work.budget.jsonand.sdd/loop/work.history.jsonlfor further inspection
The lockfile is released after the report is emitted so a stale-lock reaper on a subsequent tick sees a complete telemetry trail.
Post-PR Chain
<!-- Governing: ADR-0030 (Post-PR Chain Pattern in /sdd:work), ADR-0010 (bounded one-round invariant), ADR-0024 (qmd as Hard Dependency), SPEC-0020 REQ "Post-PR Chain Invocation", SPEC-0020 REQ "Chain Outcome Telemetry" -->
After each PR opened in an iteration, /sdd:work invokes the post-PR chain — a bounded architectural review pass (/sdd:review, exactly one round per 📝 ADR-0010) followed by an open-ended CI / conflict / comment maintenance pass (/autofix-pr, the Claude Code built-in). The chain is unconditional within an iteration unless --no-chain or --dry-run is set (per SPEC-0020 REQ "Post-PR Chain Invocation"). It also runs on single-shot /sdd:work invocations — not just under --loop — so single-PR sessions get the same autonomy benefit (per 📝 ADR-0030 sub-decision 1).
This contract applies to both --loop and non-loop invocations of /sdd:work. In non-loop mode, the worker that opens a PR follows the same chain — and the per-PR history.jsonl line still records the chain outcome fields (per SPEC-0020 REQ "Chain Outcome Telemetry"; the file is single-line in non-loop mode rather than appended).
Chain sequence (per PR)
For each PR a worker opens in an iteration:
-
Check
--no-chain. If set: log "Chain skipped for PR #{N} (--no-chain)"; recordchain_invoked: falsein the iteration'shistory.jsonlline; omitreview_outcome,autofix_pr_invoked, andautofix_pr_invocation_status(per SPEC-0020 REQ "Chain Outcome Telemetry"); proceed to next PR (or exit iteration body). -
Check
--dry-run. If set: log "Would invoke /sdd:review on PR #{N}, then /autofix-pr"; recordchain_invoked: false; omit the per-stage fields; proceed. -
Invoke
/sdd:reviewon PR #{N}, scoped to exactly one architectural round (📝 ADR-0010 invariant):- Reviewer evaluates the PR diff against acceptance criteria
- If REQUEST_CHANGES: responder addresses, reviewer re-evaluates within the same round
- Round closes with one of four outcomes:
"approve","changes-requested"(resolved in round),"needs-human", or"errored"(qmd / infrastructure failure)
-
Branch on review outcome:
Outcome Behavior "approve"Proceed to step 5 ( /autofix-prinvocation)"changes-requested"(resolved in round)Proceed to step 5 "needs-human"Apply the needs-human-follow-uplabel to PR #{N}; proceed to step 5 (the maintenance loop may still resolve CI flakes / conflicts independently of the architectural concern)"errored"Apply the chain-failed-pre-autofixlabel to PR #{N}; skip step 5; recordautofix_pr_invoked: false; omitautofix_pr_invocation_status(per SPEC-0020 REQ "Post-PR Chain Invocation"); proceed to next PR. The user can rerun/sdd:reviewafter fixing the infrastructure issue (e.g., qmd unreachable per 📝 ADR-0024). -
Check
/autofix-pravailability. The command is a Claude Code built-in (not a plugin-provided skill). Probe by attempting to introspect the runtime's available commands.- Unavailable (the build does not ship the command, or introspection returns "not found"): log a one-line warning ("
/autofix-pris not available in this Claude Code build — install or upgrade Claude Code to enable post-PR autofix chain"); open a tracker issue taggedclaude-code-version-required(deduped per/sdd:workinvocation — open at most one such issue per session); recordautofix_pr_invoked: falseandautofix_pr_invocation_status: "unavailable"; proceed to next PR. PR creation is NOT blocked. - Available: continue to step 6.
- Unavailable (the build does not ship the command, or introspection returns "not found"): log a one-line warning ("
-
Invoke
/autofix-pron PR #{N} fire-and-forget:/sdd:workdoes NOT wait for/autofix-prto terminate; the built-in runs in its own background lifecycle managed by Claude Code, watching CI failures, review comments, and merge conflicts, and pushing corrective commits until the PR merges or is closed./sdd:workreturns once the invocation is accepted (the command was parsed and the lifecycle started).- On accepted invocation: record
autofix_pr_invoked: true,autofix_pr_invocation_status: "accepted". - On invocation error (parse failure or other invocation-time error): record
autofix_pr_invoked: true,autofix_pr_invocation_status: "errored"; emit a one-line warning. The PR is NOT blocked.
- On accepted invocation: record
-
Telemetry. The iteration's
history.jsonlline records the four chain fields per the canonical schema inreferences/loop-telemetry.md:{
"chain_invoked": true,
"review_outcome": "approve",
"autofix_pr_invoked": true,
"autofix_pr_invocation_status": "accepted"
}In non-loop mode, the same fields are still emitted (a non-loop
/sdd:workwrites a single-linehistory.jsonlfor that invocation; this is the trade documented in 📝 ADR-0030 sub-decision 6 to keep telemetry shape uniform).
Failure modes (summary)
| Failure | Behavior |
|---|---|
User passed --no-chain | Skip both invocations; record chain_invoked: false and omit per-stage fields. Legacy "open PR, stop" behavior. |
User passed --dry-run | Log "would invoke" for both; skip both invocations; record chain_invoked: false. |
/sdd:review returns "approve", "changes-requested" (resolved in round), or "needs-human" | Proceed to /autofix-pr |
/sdd:review returns "errored" (qmd unreachable per 📝 ADR-0024 sub-decision 2, or other infrastructure failure) | Apply chain-failed-pre-autofix label; skip /autofix-pr; record autofix_pr_invoked: false; omit autofix_pr_invocation_status |
/autofix-pr unavailable in current Claude Code build | Log warning; open one tracker issue with claude-code-version-required label per /sdd:work session (deduped); exit cleanly. PR is NOT blocked. |
/autofix-pr invocation parse error | Record autofix_pr_invocation_status: "errored"; one-line warning; PR NOT blocked. |
Concurrency under --loop
Each loop iteration's per-PR chain is a self-contained unit (per 📝 ADR-0030 sub-decision 5). When a worker opens N PRs in parallel within a single iteration, the chain runs once per PR, in parallel across workers (one chain per PR). The chain invocations are NOT serialized across the workers in a single iteration.
📝 ADR-0028's gates (backlog drift, ambiguous criteria, budget escalation, force-unlock, repeated failure) fire before the chain — they decide whether to start the iteration. Once an iteration runs and a PR is opened, the chain is unconditional within that iteration unless --no-chain is set. There is no per-PR gate that interposes between PR creation and /sdd:review; the architectural review is the bounded round (📝 ADR-0010), and the post-feedback-merge gate (/sdd:review --loop only) is the only human-mediated checkpoint that can pause the chain's eventual merge path.
Cost accounting
Each chain invocation is metered by its own tool's cost surface (per 📝 ADR-0030 sub-decision 6). /sdd:work does not double-count /sdd:review's tokens or /autofix-pr's tokens against its own budget; those costs accrue under the invoked command's accounting. What /sdd:work records in history.jsonl is the event of invocation (chain_invoked, review_outcome, autofix_pr_invoked) so users correlating cost spikes after the fact can map them back to the iteration that opened the PR. The chain is therefore visible in telemetry as an event but not as a token line item.
Why this preserves ADR-0010
/sdd:review is invoked exactly once by /sdd:work per PR (per chain invocation). The chain does NOT loop /sdd:review. A second architectural round on the same PR is forbidden by 📝 ADR-0010 and forbidden here. If the user wants another round, they invoke /sdd:review directly (or, under --loop, the next iteration's /sdd:review --loop invocation does its own one round on that PR).
Bootstrap note (V1)
This contract IS the implementation of the chain; before story #148 lands, /sdd:work opens a PR and stops (legacy behavior). Once #148 merges, the chain is the new default unless --no-chain is set.
Resume
--resume recovers state from the most recent history.jsonl line per references/loop-telemetry.md § Resume Contract. Counters are restored; gate evaluations are recomputed; the lockfile is treated as stale per the PID-liveness rule; tracked_prs[] and active_worktrees[] are reconciled by SHA equality with no external probing substituted.
Telemetry
Every iteration appends a line to .sdd/loop/work.history.jsonl and emits the stdout status block. Skipped ticks (lockfile contention) MUST also append a line with outcome: "skipped_lock" and MUST NOT increment iterations_used. Schema details in references/loop-telemetry.md.
Loop Mode Rules
- MUST NOT modify the runtime
/loopskill — re-invocation cadence is/loop's concern; the wrapped skill enforces only intra-iteration semantics - MUST acquire the lockfile on entry, before any other work, per
references/loop-primitives.md§ Acquisition flow - MUST evaluate PID liveness as the sole staleness signal; worktree presence and team-membership state MUST NOT be consulted
- MUST persist the budget atomically (write-temp + rename) on every tick
- MUST record active ceilings on first write so
--resumecannot silently widen them - MUST deduplicate
prs_touched(a PR re-touched across iterations counts once) - MUST emit the stdout status block on every iteration including skipped ticks
- MUST append a
history.jsonlline on every iteration including skipped ticks - MUST NOT increment
iterations_usedfor a skipped tick - MUST NOT cache or reuse a prior iteration's gate answer; every gate is re-evaluated on every tick
- MUST refuse to start an iteration when any prior
gates[]entry recordedanswer == "stop"(condition #10) - MUST NOT pick up issues labeled
in-progressby a sibling iteration's worktree (concurrency invariant) - MUST signal qmd unreachability via stderr token
qmd-unreachableOR exit codeEX_QMD_UNREACHABLE=78(the wrapped skill emits; the loop layer detects) - MUST emit the final report before releasing the lockfile on any halt path
- MUST invoke the post-PR chain (per "Post-PR Chain" above) for every PR opened in an iteration unless
--no-chainor--dry-runis set - The post-PR chain MUST also run on non-loop
/sdd:workinvocations (single-shot mode); the chain is not gated by--loop(per 📝 ADR-0030 sub-decision 1) - The chain MUST invoke
/sdd:reviewexactly once per PR per chain invocation (preserves 📝 ADR-0010's bounded one-round invariant) - WHEN
/sdd:reviewexits withreview_outcome: "errored"THEN MUST apply thechain-failed-pre-autofixlabel, MUST NOT invoke/autofix-pr, and MUST omitautofix_pr_invocation_statusfrom the per-iteration telemetry - WHEN
/sdd:reviewexits with"needs-human"THEN MUST apply theneeds-human-follow-uplabel AND proceed to/autofix-pr(the maintenance loop may resolve CI / conflicts independently) - WHEN
/autofix-pris unavailable THEN MUST log a warning AND open a tracker issue with theclaude-code-version-requiredlabel (deduped at one issue per session) AND exit cleanly (PR creation MUST NOT be blocked) - The
/autofix-prinvocation MUST be fire-and-forget —/sdd:workMUST NOT wait for/autofix-prto terminate - The chain MUST NOT be double-counted in
/sdd:work's budget — costs of/sdd:reviewand/autofix-praccrue under their own tool accounting;/sdd:workrecords only the invocation events inhistory.jsonl
Example Invocations
pick up issue #64 and start implementing it
/sdd:work
implement the foundation stories from the auth-rewrite plan, in parallel
spawn agents to work on issues #45, #46, #47 in worktrees
/sdd:work issue 64