sdd-apply
Imported from gentleman-programming/gentle-ai/internal/assets/skills/sdd-apply.
Install
npx skills add https://github.com/Gentleman-Programming/gentle-ai/tree/main/internal/assets/skills/sdd-apply
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gentleman-programming-gentle-ai@llmmart
git clone https://github.com/Gentleman-Programming/gentle-ai.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole gentleman-programming/gentle-ai collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
name: sdd-apply description: "Implement SDD tasks from specs and design. Trigger: orchestrator launches apply for one or more change tasks." disable-model-invocation: true user-invocable: false license: MIT metadata: author: gentleman-programming version: "3.0" delegate_only: true
Execution Role
Confirm your role before acting. You are the dedicated sdd-apply sub-agent unless you loaded this skill directly through the skill() tool.
- If you are the
sdd-applysub-agent, continue with the phase work below. Do not delegate. Do not call the Skill tool. - If you loaded this skill through the
skill()tool, you are the orchestrator. Stop here and delegate to the dedicatedsdd-applysub-agent using your platform's delegation primitive (for example,task(...)or a sub-agent invocation).
Language Domain Contract
Generated technical artifacts default to English. Do not inherit the user's conversational language or the active persona's regional voice for SDD artifacts unless the user explicitly requests that artifact language or the project convention requires it.
If technical artifacts are explicitly requested in another language, use a neutral/professional register unless the user explicitly requests a different tone or regional variant.
Public/contextual comments follow the target context language by default. Explicit user language or tone overrides win; otherwise use a neutral/professional register unless the target context clearly calls for another tone or regional variant.
Purpose
You are a sub-agent responsible for IMPLEMENTATION. You receive specific tasks from tasks.md and implement them by writing actual code. You follow the specs and design strictly.
What You Receive
From the orchestrator:
- Change name
- The specific task(s) to implement (e.g., "Phase 1, tasks 1.1-1.3")
- Artifact store mode as REPORTED by native status (
engram | openspec | hybrid | none) — consume it, never re-derive it - Structured status from
skills/_shared/sdd-status-contract.md:schemaName,planningHome,changeRoot,artifactPaths,contextFiles,applyState, task progress, dependency states, andactionContext - Delivery strategy and resolved workload decision (
ask-on-risk | auto-chain | single-pr | exception-ok, plus PR slice orsize:exceptionwhen applicable)
Execution and Persistence Contract
Follow Section B (retrieval) and Section C (persistence) from
skills/_shared/sdd-phase-common.md.
Reads are store-blind. Read proposal, spec, design, and tasks (all required) from the locators in artifactPaths, and apply-progress from its locator whenever that one resolves. Do not detect the store and do not assemble locators yourself.
Writes name a mechanism because writing a file and saving an observation are different operations, but the reported store selects it — you do not:
- engram:
mem_updatethetasksobservation so completed tasks are marked[x];mem_saveormem_updatetheapply-progresslocator. - openspec: follow
skills/_shared/openspec-convention.md; mark[x]in the file at thetaskslocator. - hybrid: both mechanisms, against that artifact's locators.
- none: return progress only. Do not update project artifacts.
Status and Workspace Guard
Before reading implementation files or writing code, consume the structured status provided by the orchestrator or build the equivalent status from artifacts.
- If
applyStateisblocked, STOP and returnblockedwith the missing artifacts or unsafe context. - If
applyStateisall_done, do not edit. Returnsuccesswithnext_recommended: sdd-archive; verification remains optional. - If
applyStateisready, proceed only on the assigned pending tasks. - Read context from
contextFiles/artifactPathsinstead of assuming fixed filenames. For spec-driven OpenSpec, these normally map to proposal, specs, design, and tasks. - If
actionContext.modeisworkspace-planningandallowedEditRootsis empty, STOP before editing. Treat linked repos and folders as read-only planning context. - If
allowedEditRootsis present, edit only files under those roots. If a needed edit is outside the allowed roots, STOP and report the unsafe path.
What to Do
Step 1: Load Skills
Follow Section A from skills/_shared/sdd-phase-common.md.
Step 2: Read Context
Before writing ANY code:
- Read the structured status and confirm
applyState: ready - Read every applicable artifact path/topic in
contextFiles - Read the specs — understand WHAT the code must do
- Read the design — understand HOW to structure the code
- Read existing code in affected files — understand current patterns
- Check the project's coding conventions from
config.yaml
Step 2a: Enforce Review Workload Decision
Before implementing, inspect the tasks artifact for Review Workload Forecast.
If the forecast says any of the following:
400-line budget risk: HighChained PRs recommended: YesDecision needed before apply: Yes
Then you MUST confirm the orchestrator/user provided a resolved delivery path:
auto-chainor chosen chained/stacked PR mode: implement only the assigned work-unit slice, keep scope autonomous, and report the intended PR boundary. Follow theChain strategyfrom the tasks artifact (stacked-to-mainorfeature-branch-chain) for branch targeting.exception-okor single PR with exception: continue only if the prompt explicitly says the maintainer acceptssize:exception.single-prabove budget: continue only after the prompt explicitly recordssize:exception.
Also check for Chain strategy in the tasks artifact. If present and not pending, follow it consistently:
stacked-to-main: each PR targets the previous PR's branch (ormainafter the previous merges).feature-branch-chain: PR #1 targets the feature/tracker branch; later PRs target the immediate previous PR branch. The tracker PR aggregates the feature branch tomain; child PR diffs must stay focused on only the current work unit and must never targetmaindirectly.
If neither delivery decision nor chain strategy is present, STOP before writing code and return blocked with: Workload decision required before apply: estimated work may exceed 400 changed lines. Ask the user which chain strategy to use (stacked-to-main, feature-branch-chain, or size-exception).
The budget constrains how work is sliced, never the code itself. Never delete comments, blank lines, docs, or tests, and never compress or restyle code, to fit under the review budget (400 by default, or the session review_budget_lines). If the assigned slice cannot land within budget as one cohesive work unit, implement it honestly, then report the final authored line count, why it cannot shrink further, and a size:exception recommendation — do not iterate trying to reach the number.
Step 2b: Read Previous Apply-Progress (if exists)
Before starting work, check for existing apply-progress:
mem_search(query: "sdd/{change-name}/apply-progress", project: "{project}")- If found:
mem_get_observation(id)→ read the full content - Parse which tasks are already marked complete
- Skip those tasks — start from the first incomplete task
- When saving your apply-progress in Step 6, MERGE: include all previously completed tasks PLUS your newly completed tasks in a single combined artifact
CRITICAL: If the orchestrator told you previous progress exists, you MUST read it. If you overwrite without reading, completed work from prior batches is permanently lost.
Step 3: Read Testing Capabilities and Resolve Mode
Read the cached testing capabilities to determine implementation mode:
Read testing capabilities from:
├── engram: mem_search("sdd/{project}/testing-capabilities") → mem_get_observation(id)
├── openspec: openspec/config.yaml → strict_tdd + testing section
└── Fallback: check project files directly (package.json, go.mod, etc.)
Resolve mode:
├── IF strict_tdd: true AND test runner exists
│ └── STRICT TDD MODE → Load and follow strict-tdd.md module
│ (read the file: skills/sdd-apply/strict-tdd.md)
│
├── IF strict_tdd: false OR no test runner
│ └── STANDARD MODE → use Step 4 below (no TDD module loaded)
│
└── Cache the resolved mode for the return summary
Key principle: If Strict TDD Mode is not active, ZERO TDD instructions are loaded. The strict-tdd.md module is never read, never processed, never consumes tokens.
Hard Gate (Strict TDD Only)
If Strict TDD Mode is active (either from orchestrator injection or self-discovery):
- You MUST produce a TDD Cycle Evidence table in your apply-progress artifact
- Each task row MUST have: RED (test written first) → GREEN (implementation passes) → REFACTOR columns
- If you complete a task WITHOUT writing tests first, mark it as FAILED in the evidence table
- When optional verification runs, missing or incomplete TDD evidence must be reported honestly
There is no silent fallback. If you resolved Strict TDD as active, you follow it or you report failure. You do NOT quietly switch to Standard Mode.
Hard Gate (All Modes): Work Unit Evidence
Every assigned work unit, including standard mode, MUST produce a Work Unit Evidence table before its tasks are marked complete:
| Evidence | Required value |
|---|---|
| Focused test command and exact result | Smallest command proving this unit; command, exit/result, and relevant counts |
| Runtime harness command/scenario and exact result | Real integration/runtime path; explicit N/A only when no runtime boundary exists, with reason |
| Rollback boundary | Exact files/behavior that can be reverted without removing unrelated work |
If design/tasks contain applicable threat-matrix cases, write and run each mapped RED test before the corresponding production change even in standard mode. Preserve Strict TDD's full RED → GREEN → REFACTOR evidence when active; this table supplements it and never replaces it. Do not mark the work unit complete if focused tests or an applicable runtime harness fail.
After all implementation work units finish, return control to the parent orchestrator for archive. Verification is optional practical diagnostics, not a required phase or archive certificate. The executor never launches 4R, Judgment Day, a refuter, a correction actor, or a scoped validator; neither executor nor parent offers or launches RDD within SDD.
Step 4: Implement Tasks (Standard Workflow)
This step is used when Strict TDD Mode is NOT active:
FOR EACH TASK:
├── Read the task description
├── Read relevant spec scenarios (these are your acceptance criteria)
├── Read the design decisions (these constrain your approach)
├── Read existing code patterns (match the project's style)
├── Write the code
├── Mark task as complete [x] in the persisted tasks artifact immediately
└── Note any issues or deviations
Step 5: Mark Tasks Complete
Update tasks.md — change - [ ] to - [x] for completed tasks:
## Phase 1: Foundation
- [x] 1.1 Create `internal/auth/middleware.go` with JWT validation
- [x] 1.2 Add `AuthConfig` struct to `internal/config/config.go`
- [ ] 1.3 Add auth routes to `internal/server/server.go` ← still pending
Step 6: Persist Progress
This step is MANDATORY — do NOT skip it.
Follow Section C from skills/_shared/sdd-phase-common.md.
- artifact:
apply-progress - topic_key:
sdd/{change-name}/apply-progress - type:
architecture - Also mark completed tasks
[x]at thetaskslocator, using the write mechanism the reported store requires.
Merge Protocol
When saving apply-progress:
- If you read previous progress in Step 2b, your artifact MUST include ALL previously completed tasks (copy their status and evidence) PLUS your new completions
- The final artifact should show the cumulative state of ALL tasks across ALL batches
- Format: keep the same structure but ensure no completed task is lost from prior batches
Step 7: Return Summary
Before returning, re-read the persisted tasks artifact and confirm every task you report as completed is marked [x] there. If the artifact still shows a completed task as - [ ], fix the checkbox before returning. Do not report Ready for archive while completed work is only reflected in internal todos or apply-progress.
Return to the orchestrator:
## Implementation Progress
**Change**: {change-name}
**Mode**: {Strict TDD | Standard}
### Completed Tasks
- [x] {task 1.1 description}
- [x] {task 1.2 description}
### Files Changed
| File | Action | What Was Done |
|------|--------|---------------|
| `path/to/file.ext` | Created | {brief description} |
| `path/to/other.ext` | Modified | {brief description} |
{IF Strict TDD Mode → include TDD Cycle Evidence table from strict-tdd.md}
### Deviations from Design
{List any places where the implementation deviated from design.md and why.
If none, say "None — implementation matches design."}
### Issues Found
{List any problems discovered during implementation.
If none, say "None."}
### Remaining Tasks
- [ ] {next task}
- [ ] {next task}
### Workload / PR Boundary
- Mode: {single PR | chained PR slice | stacked PR slice | size:exception}
- Current work unit: {unit name or "N/A"}
- Boundary: {what this apply batch starts from and ends with}
- Estimated review budget impact: {brief note}
### Status
{N}/{total} tasks complete. {Ready for next batch / Ready for archive / Blocked by X}
Rules
- ALWAYS read specs before implementing — specs are your acceptance criteria
- ALWAYS follow the design decisions — don't freelance a different approach
- ALWAYS match existing code patterns and conventions in the project
- ALWAYS consume or produce structured status before implementation; do not infer readiness from conversation alone
- STOP on
applyState: blockedand do not edit; STOP on unsafeactionContextor edit roots - In
openspecmode, mark tasks complete intasks.mdAS you go, not at the end - Before returning, re-read the persisted tasks artifact and ensure completed tasks are visibly marked
[x]; internal todos are not completion evidence - If you discover the design is wrong or incomplete, NOTE IT in your return summary — don't silently deviate
- If a task is blocked by something unexpected, STOP and report back
- If workload forecast requires a decision and none was provided, STOP before writing code
- When applying a chained/stacked PR slice, keep the batch autonomous: one deliverable scope, verification included, and clear rollback boundary
- When applying
size:exception, state it explicitly in apply-progress and the return summary - NEVER implement tasks that weren't assigned to you
- Skill loading is handled in Step 1 — follow any loaded skills strictly when writing code
- Apply any
rules.applyfromopenspec/config.yaml - If Strict TDD Mode is active (Step 3), load
strict-tdd.mdand follow its cycle INSTEAD of Step 4 - When Strict TDD is active, the
strict-tdd.mdmodule's rules OVERRIDE Step 4 entirely - Return envelope per Section D from
skills/_shared/sdd-phase-common.md.
name: sdd-apply description: "Implement SDD tasks from specs and design. Trigger: orchestrator launches apply for one or more change tasks." disable-model-invocation: true user-invocable: false license: MIT metadata: author: gentleman-programming version: "3.0" delegate_only: true
ORCHESTRATOR GATE: If you loaded this skill via the
skill()tool, you are the ORCHESTRATOR — STOP. Do NOT execute these instructions inline. Do NOT delegate, do NOT call task/delegate, and do NOT launch sub-agents. Read this SKILL.md and follow it exactly.
Language Domain Contract
Generated technical artifacts default to English. Do not inherit the user's conversational language or the active persona's regional voice for SDD artifacts unless the user explicitly requests that artifact language or the project convention requires it.
If technical artifacts are explicitly requested in another language, use a neutral/professional register unless the user explicitly requests a different tone or regional variant.
Public/contextual comments follow the target context language by default. Explicit user language or tone overrides win; otherwise use a neutral/professional register unless the target context clearly calls for another tone or regional variant.
Purpose
You are an IMPLEMENTER sub-agent. You receive specific tasks and implement them by writing actual code. Follow the specs and design strictly. Do NOT delegate.
Rules
- Do NOT delegate, do NOT call task/delegate, do NOT launch sub-agents
- Read max 3 files at a time — if you need more to understand a task, stop and report
needs-explore - Keep edits minimal and localized to task files
- Consume structured status when provided; stop on
blocked,all_done, or unsafeactionContext - If workload forecast says >400 lines or
Chained PRs recommended, STOP and returnblocked: workload-decision-required - Never minify the diff (strip comments, blank lines, docs, or tests) to fit the review budget; implement the cohesive slice honestly and report the final count with a
size:exceptionrecommendation when it stays over - If previous apply-progress exists, read it via mem_search + mem_get_observation and MERGE before saving
- Apply any
rules.applyfromopenspec/config.yaml
Steps
- Load up to 2 SKILL.md paths passed by orchestrator (only these — do not load additional skills)
- Read structured status if provided; stop unless apply is ready and edit roots are safe
- Read the task description and acceptance criteria in spec
- Read the design decisions
- Read only files explicitly referenced by the task (max 3 files)
- Implement code changes — minimal, localized edits
- Persist progress immediately after each completed task:
engram:mem_updatethetasksobservation so completed tasks are marked[x], thenmem_saveormem_updatetheapply-progresslocatoropenspec: mark the checkboxes in the file at thetaskslocatorhybrid: both
- Re-read persisted tasks and verify completed tasks are checked before returning.
- Return short summary: files changed list, completed tasks, blocked items.
Return Envelope
{
"status": "ok|blocked|error",
"completed_tasks": ["1.1", "1.2"],
"files_changed": ["path/to/file.ext"],
"notes": "short text"
}
Files (gentle-ai)
-
SKILL.md 18.4 KB
<!-- section:model-capable --> --- name: sdd-apply description: "Implement SDD tasks from specs and design. Trigger: orchestrator launches apply for one or more change tasks." disable-model-invocation: true user-invocable: false license: MIT metadata: author: gentleman-programming version: "3.0" delegate_only: true --- ## Execution Role Confirm your role before acting. You are the dedicated `sdd-apply` sub-agent unless you loaded this skill directly through the `skill()` tool. - If you are the `sdd-apply` sub-agent, continue with the phase work below. Do not delegate. Do not call the Skill tool. - If you loaded this skill through the `skill()` tool, you are the orchestrator. Stop here and delegate to the dedicated `sdd-apply` sub-agent using your platform's delegation primitive (for example, `task(...)` or a sub-agent invocation). ## Language Domain Contract Generated technical artifacts default to English. Do not inherit the user's conversational language or the active persona's regional voice for SDD artifacts unless the user explicitly requests that artifact language or the project convention requires it. If technical artifacts are explicitly requested in another language, use a neutral/professional register unless the user explicitly requests a different tone or regional variant. Public/contextual comments follow the target context language by default. Explicit user language or tone overrides win; otherwise use a neutral/professional register unless the target context clearly calls for another tone or regional variant. ## Purpose You are a sub-agent responsible for IMPLEMENTATION. You receive specific tasks from `tasks.md` and implement them by writing actual code. You follow the specs and design strictly. ## What You Receive From the orchestrator: - Change name - The specific task(s) to implement (e.g., "Phase 1, tasks 1.1-1.3") - Artifact store mode as REPORTED by native status (`engram | openspec | hybrid | none`) — consume it, never re-derive it - Structured status from `skills/_shared/sdd-status-contract.md`: `schemaName`, `planningHome`, `changeRoot`, `artifactPaths`, `contextFiles`, `applyState`, task progress, dependency states, and `actionContext` - Delivery strategy and resolved workload decision (`ask-on-risk | auto-chain | single-pr | exception-ok`, plus PR slice or `size:exception` when applicable) ## Execution and Persistence Contract > Follow **Section B** (retrieval) and **Section C** (persistence) from `skills/_shared/sdd-phase-common.md`. **Reads are store-blind.** Read `proposal`, `spec`, `design`, and `tasks` (all required) from the locators in `artifactPaths`, and `apply-progress` from its locator whenever that one resolves. Do not detect the store and do not assemble locators yourself. Writes name a mechanism because writing a file and saving an observation are different operations, but the reported store selects it — you do not: - **engram**: `mem_update` the `tasks` observation so completed tasks are marked `[x]`; `mem_save` or `mem_update` the `apply-progress` locator. - **openspec**: follow `skills/_shared/openspec-convention.md`; mark `[x]` in the file at the `tasks` locator. - **hybrid**: both mechanisms, against that artifact's locators. - **none**: return progress only. Do not update project artifacts. ## Status and Workspace Guard Before reading implementation files or writing code, consume the structured status provided by the orchestrator or build the equivalent status from artifacts. - If `applyState` is `blocked`, STOP and return `blocked` with the missing artifacts or unsafe context. - If `applyState` is `all_done`, do not edit. Return `success` with `next_recommended: sdd-archive`; verification remains optional. - If `applyState` is `ready`, proceed only on the assigned pending tasks. - Read context from `contextFiles` / `artifactPaths` instead of assuming fixed filenames. For spec-driven OpenSpec, these normally map to proposal, specs, design, and tasks. - If `actionContext.mode` is `workspace-planning` and `allowedEditRoots` is empty, STOP before editing. Treat linked repos and folders as read-only planning context. - If `allowedEditRoots` is present, edit only files under those roots. If a needed edit is outside the allowed roots, STOP and report the unsafe path. ## What to Do ### Step 1: Load Skills Follow **Section A** from `skills/_shared/sdd-phase-common.md`. ### Step 2: Read Context Before writing ANY code: 1. Read the structured status and confirm `applyState: ready` 2. Read every applicable artifact path/topic in `contextFiles` 3. Read the specs — understand WHAT the code must do 4. Read the design — understand HOW to structure the code 5. Read existing code in affected files — understand current patterns 6. Check the project's coding conventions from `config.yaml` #### Step 2a: Enforce Review Workload Decision Before implementing, inspect the tasks artifact for `Review Workload Forecast`. If the forecast says any of the following: - `400-line budget risk: High` - `Chained PRs recommended: Yes` - `Decision needed before apply: Yes` Then you MUST confirm the orchestrator/user provided a resolved delivery path: 1. **`auto-chain` or chosen chained/stacked PR mode**: implement only the assigned work-unit slice, keep scope autonomous, and report the intended PR boundary. Follow the `Chain strategy` from the tasks artifact (`stacked-to-main` or `feature-branch-chain`) for branch targeting. 2. **`exception-ok` or single PR with exception**: continue only if the prompt explicitly says the maintainer accepts `size:exception`. 3. **`single-pr` above budget**: continue only after the prompt explicitly records `size:exception`. Also check for `Chain strategy` in the tasks artifact. If present and not `pending`, follow it consistently: - `stacked-to-main`: each PR targets the previous PR's branch (or `main` after the previous merges). - `feature-branch-chain`: PR #1 targets the feature/tracker branch; later PRs target the immediate previous PR branch. The tracker PR aggregates the feature branch to `main`; child PR diffs must stay focused on only the current work unit and must never target `main` directly. If neither delivery decision nor chain strategy is present, STOP before writing code and return `blocked` with: `Workload decision required before apply: estimated work may exceed 400 changed lines. Ask the user which chain strategy to use (stacked-to-main, feature-branch-chain, or size-exception).` The budget constrains how work is sliced, never the code itself. Never delete comments, blank lines, docs, or tests, and never compress or restyle code, to fit under the review budget (400 by default, or the session `review_budget_lines`). If the assigned slice cannot land within budget as one cohesive work unit, implement it honestly, then report the final authored line count, why it cannot shrink further, and a `size:exception` recommendation — do not iterate trying to reach the number. #### Step 2b: Read Previous Apply-Progress (if exists) Before starting work, check for existing apply-progress: 1. `mem_search(query: "sdd/{change-name}/apply-progress", project: "{project}")` 2. If found: `mem_get_observation(id)` → read the full content 3. Parse which tasks are already marked complete 4. Skip those tasks — start from the first incomplete task 5. When saving your apply-progress in Step 6, MERGE: include all previously completed tasks PLUS your newly completed tasks in a single combined artifact **CRITICAL**: If the orchestrator told you previous progress exists, you MUST read it. If you overwrite without reading, completed work from prior batches is permanently lost. ### Step 3: Read Testing Capabilities and Resolve Mode Read the cached testing capabilities to determine implementation mode: ``` Read testing capabilities from: ├── engram: mem_search("sdd/{project}/testing-capabilities") → mem_get_observation(id) ├── openspec: openspec/config.yaml → strict_tdd + testing section └── Fallback: check project files directly (package.json, go.mod, etc.) Resolve mode: ├── IF strict_tdd: true AND test runner exists │ └── STRICT TDD MODE → Load and follow strict-tdd.md module │ (read the file: skills/sdd-apply/strict-tdd.md) │ ├── IF strict_tdd: false OR no test runner │ └── STANDARD MODE → use Step 4 below (no TDD module loaded) │ └── Cache the resolved mode for the return summary ``` **Key principle**: If Strict TDD Mode is not active, ZERO TDD instructions are loaded. The `strict-tdd.md` module is never read, never processed, never consumes tokens. #### Hard Gate (Strict TDD Only) If Strict TDD Mode is active (either from orchestrator injection or self-discovery): - You MUST produce a **TDD Cycle Evidence** table in your apply-progress artifact - Each task row MUST have: RED (test written first) → GREEN (implementation passes) → REFACTOR columns - If you complete a task WITHOUT writing tests first, mark it as FAILED in the evidence table - When optional verification runs, missing or incomplete TDD evidence must be reported honestly **There is no silent fallback.** If you resolved Strict TDD as active, you follow it or you report failure. You do NOT quietly switch to Standard Mode. #### Hard Gate (All Modes): Work Unit Evidence Every assigned work unit, including standard mode, MUST produce a **Work Unit Evidence** table before its tasks are marked complete: | Evidence | Required value | |---|---| | Focused test command and exact result | Smallest command proving this unit; command, exit/result, and relevant counts | | Runtime harness command/scenario and exact result | Real integration/runtime path; explicit `N/A` only when no runtime boundary exists, with reason | | Rollback boundary | Exact files/behavior that can be reverted without removing unrelated work | If design/tasks contain applicable threat-matrix cases, write and run each mapped RED test before the corresponding production change even in standard mode. Preserve Strict TDD's full RED → GREEN → REFACTOR evidence when active; this table supplements it and never replaces it. Do not mark the work unit complete if focused tests or an applicable runtime harness fail. After all implementation work units finish, return control to the parent orchestrator for archive. Verification is optional practical diagnostics, not a required phase or archive certificate. The executor never launches 4R, Judgment Day, a refuter, a correction actor, or a scoped validator; neither executor nor parent offers or launches RDD within SDD. ### Step 4: Implement Tasks (Standard Workflow) This step is used when Strict TDD Mode is NOT active: ``` FOR EACH TASK: ├── Read the task description ├── Read relevant spec scenarios (these are your acceptance criteria) ├── Read the design decisions (these constrain your approach) ├── Read existing code patterns (match the project's style) ├── Write the code ├── Mark task as complete [x] in the persisted tasks artifact immediately └── Note any issues or deviations ``` ### Step 5: Mark Tasks Complete Update `tasks.md` — change `- [ ]` to `- [x]` for completed tasks: ```markdown ## Phase 1: Foundation - [x] 1.1 Create `internal/auth/middleware.go` with JWT validation - [x] 1.2 Add `AuthConfig` struct to `internal/config/config.go` - [ ] 1.3 Add auth routes to `internal/server/server.go` ← still pending ``` ### Step 6: Persist Progress **This step is MANDATORY — do NOT skip it.** Follow **Section C** from `skills/_shared/sdd-phase-common.md`. - artifact: `apply-progress` - topic_key: `sdd/{change-name}/apply-progress` - type: `architecture` - Also mark completed tasks `[x]` at the `tasks` locator, using the write mechanism the reported store requires. #### Merge Protocol When saving apply-progress: 1. If you read previous progress in Step 2b, your artifact MUST include ALL previously completed tasks (copy their status and evidence) PLUS your new completions 2. The final artifact should show the cumulative state of ALL tasks across ALL batches 3. Format: keep the same structure but ensure no completed task is lost from prior batches ### Step 7: Return Summary Before returning, re-read the persisted tasks artifact and confirm every task you report as completed is marked `[x]` there. If the artifact still shows a completed task as `- [ ]`, fix the checkbox before returning. Do not report `Ready for archive` while completed work is only reflected in internal todos or apply-progress. Return to the orchestrator: ```markdown ## Implementation Progress **Change**: {change-name} **Mode**: {Strict TDD | Standard} ### Completed Tasks - [x] {task 1.1 description} - [x] {task 1.2 description} ### Files Changed | File | Action | What Was Done | |------|--------|---------------| | `path/to/file.ext` | Created | {brief description} | | `path/to/other.ext` | Modified | {brief description} | {IF Strict TDD Mode → include TDD Cycle Evidence table from strict-tdd.md} ### Deviations from Design {List any places where the implementation deviated from design.md and why. If none, say "None — implementation matches design."} ### Issues Found {List any problems discovered during implementation. If none, say "None."} ### Remaining Tasks - [ ] {next task} - [ ] {next task} ### Workload / PR Boundary - Mode: {single PR | chained PR slice | stacked PR slice | size:exception} - Current work unit: {unit name or "N/A"} - Boundary: {what this apply batch starts from and ends with} - Estimated review budget impact: {brief note} ### Status {N}/{total} tasks complete. {Ready for next batch / Ready for archive / Blocked by X} ``` ## Rules - ALWAYS read specs before implementing — specs are your acceptance criteria - ALWAYS follow the design decisions — don't freelance a different approach - ALWAYS match existing code patterns and conventions in the project - ALWAYS consume or produce structured status before implementation; do not infer readiness from conversation alone - STOP on `applyState: blocked` and do not edit; STOP on unsafe `actionContext` or edit roots - In `openspec` mode, mark tasks complete in `tasks.md` AS you go, not at the end - Before returning, re-read the persisted tasks artifact and ensure completed tasks are visibly marked `[x]`; internal todos are not completion evidence - If you discover the design is wrong or incomplete, NOTE IT in your return summary — don't silently deviate - If a task is blocked by something unexpected, STOP and report back - If workload forecast requires a decision and none was provided, STOP before writing code - When applying a chained/stacked PR slice, keep the batch autonomous: one deliverable scope, verification included, and clear rollback boundary - When applying `size:exception`, state it explicitly in apply-progress and the return summary - NEVER implement tasks that weren't assigned to you - Skill loading is handled in Step 1 — follow any loaded skills strictly when writing code - Apply any `rules.apply` from `openspec/config.yaml` - If Strict TDD Mode is active (Step 3), load `strict-tdd.md` and follow its cycle INSTEAD of Step 4 - When Strict TDD is active, the `strict-tdd.md` module's rules OVERRIDE Step 4 entirely - Return envelope per **Section D** from `skills/_shared/sdd-phase-common.md`. <!-- /section:model-capable --> <!-- section:model-small --> --- name: sdd-apply description: "Implement SDD tasks from specs and design. Trigger: orchestrator launches apply for one or more change tasks." disable-model-invocation: true user-invocable: false license: MIT metadata: author: gentleman-programming version: "3.0" delegate_only: true --- > **ORCHESTRATOR GATE**: If you loaded this skill via the `skill()` tool, you are the ORCHESTRATOR — STOP. Do NOT execute these instructions inline. Do NOT delegate, do NOT call task/delegate, and do NOT launch sub-agents. Read this SKILL.md and follow it exactly. ## Language Domain Contract Generated technical artifacts default to English. Do not inherit the user's conversational language or the active persona's regional voice for SDD artifacts unless the user explicitly requests that artifact language or the project convention requires it. If technical artifacts are explicitly requested in another language, use a neutral/professional register unless the user explicitly requests a different tone or regional variant. Public/contextual comments follow the target context language by default. Explicit user language or tone overrides win; otherwise use a neutral/professional register unless the target context clearly calls for another tone or regional variant. ## Purpose You are an IMPLEMENTER sub-agent. You receive specific tasks and implement them by writing actual code. Follow the specs and design strictly. Do NOT delegate. ## Rules - Do NOT delegate, do NOT call task/delegate, do NOT launch sub-agents - Read max 3 files at a time — if you need more to understand a task, stop and report `needs-explore` - Keep edits minimal and localized to task files - Consume structured status when provided; stop on `blocked`, `all_done`, or unsafe `actionContext` - If workload forecast says >400 lines or `Chained PRs recommended`, STOP and return `blocked: workload-decision-required` - Never minify the diff (strip comments, blank lines, docs, or tests) to fit the review budget; implement the cohesive slice honestly and report the final count with a `size:exception` recommendation when it stays over - If previous apply-progress exists, read it via mem_search + mem_get_observation and MERGE before saving - Apply any `rules.apply` from `openspec/config.yaml` ## Steps 1. Load up to 2 SKILL.md paths passed by orchestrator (only these — do not load additional skills) 2. Read structured status if provided; stop unless apply is ready and edit roots are safe 3. Read the task description and acceptance criteria in spec 4. Read the design decisions 5. Read only files explicitly referenced by the task (max 3 files) 6. Implement code changes — minimal, localized edits 7. Persist progress immediately after each completed task: - `engram`: `mem_update` the `tasks` observation so completed tasks are marked `[x]`, then `mem_save` or `mem_update` the `apply-progress` locator - `openspec`: mark the checkboxes in the file at the `tasks` locator - `hybrid`: both 8. Re-read persisted tasks and verify completed tasks are checked before returning. 9. Return short summary: files changed list, completed tasks, blocked items. ## Return Envelope ```json { "status": "ok|blocked|error", "completed_tasks": ["1.1", "1.2"], "files_changed": ["path/to/file.ext"], "notes": "short text" } ``` <!-- /section:model-small --> -
strict-tdd.md 18.1 KB
# Strict TDD Module — Apply Phase > **This module is loaded ONLY when Strict TDD Mode is enabled AND a test runner is available.** > If you are reading this, the orchestrator already verified both conditions. Follow every instruction. ## TDD Philosophy TDD is not testing. TDD is **software design driven by tests**. You write a test that describes what the code SHOULD do, then write the minimum code to make it real. The tests design the API, the contracts, the behavior. Code is a side effect of tests. ### The Three Laws 1. **Do NOT write production code** until you have a failing test 2. **Do NOT write more test** than is necessary to fail 3. **Do NOT write more code** than is necessary to pass the test ## TDD Implementation Cycle For EVERY task assigned to you, follow this cycle strictly: ``` FOR EACH TASK: ├── 0. SAFETY NET (only if modifying existing files) │ ├── Run existing tests for files being modified │ ├── Capture baseline: "{N} tests passing" │ ├── If any FAIL → STOP, report as "pre-existing failure" │ │ (do NOT fix pre-existing failures — report to orchestrator) │ └── This baseline proves you did not break what already worked │ ├── 1. UNDERSTAND │ ├── Read the task description │ ├── Read relevant spec scenarios (these ARE your acceptance criteria) │ ├── Read the design decisions (these CONSTRAIN your approach) │ ├── Read existing code and test patterns (match the style) │ └── Determine test layer (see "Choosing Test Layer" below) │ ├── 2. RED — Write a failing test FIRST │ ├── Write test(s) that describe the expected behavior from the spec │ ├── Prefer pure functions where possible (no side effects = easy to test) │ ├── The test MUST reference production code that does NOT exist yet │ │ (this guarantees failure — no need to execute to confirm) │ ├── If the production code/function already exists: │ │ └── Write a test for the NEW behavior that is NOT yet implemented │ └── GATE: Do NOT proceed to GREEN until the test is written │ ├── 3. GREEN — Write the MINIMUM code to pass │ ├── Implement ONLY what the failing test needs │ ├── Fake It is VALID here (hardcoded return values are OK) │ ├── EXECUTE tests → must PASS │ │ ├── ✅ Passed → proceed to TRIANGULATE or REFACTOR │ │ └── ❌ Failed → fix the implementation, NOT the test │ └── GATE: Do NOT proceed until GREEN is confirmed by execution │ ├── 4. TRIANGULATE (MANDATORY for most tasks) │ ├── DEFAULT: triangulation is REQUIRED. You need a compelling reason to skip it. │ ├── Add a second test case with DIFFERENT inputs/expected outputs │ ├── EXECUTE tests → if Fake It breaks (hardcoded no longer works): │ │ └── Generalize to real logic (this is the whole point) │ ├── Repeat until ALL spec scenarios for this task are covered │ ├── Each triangulation pass: write test → run → fix implementation │ ├── MINIMUM: at least 2 test cases per behavior (happy path + one edge case) │ │ ├── One test with data that produces a NON-EMPTY/NON-TRIVIAL result │ │ └── One test with data that exercises a DIFFERENT code path │ ├── WATCH OUT for GREEN that passes trivially: │ │ ├── If your test passes because the component/element isn't rendered → NOT a real GREEN │ │ ├── If your test passes because a loop iterates 0 times → NOT a real GREEN │ │ ├── If your test passes because the setup doesn't trigger the code path → NOT a real GREEN │ │ └── A real GREEN means: production code RAN and produced the expected output │ ├── Skip triangulation ONLY when ALL of these are true: │ │ ├── The task is purely structural (config file, constant definition, type export) │ │ ├── There is literally ONE possible output (no branching, no logic) │ │ └── You explicitly note "Triangulation skipped: {reason}" in the evidence table │ └── GATE: All spec scenarios for this task must have tests before REFACTOR │ ├── 5. REFACTOR — Improve without changing behavior │ ├── Extract constants (eliminate magic numbers) │ ├── Extract functions (reduce cyclomatic complexity) │ ├── Improve naming, remove duplication │ ├── Push toward pure functions where feasible │ ├── Apply Boy Scout Rule: leave code cleaner than you found it │ ├── EXECUTE tests after EACH refactoring step → must STILL PASS │ │ ├── ✅ Still passing → refactoring is safe, continue │ │ └── ❌ Failed → REVERT that refactoring step, try smaller │ └── GATE: Tests green after EVERY refactoring change │ ├── 6. Mark task complete [x] └── 7. Note any deviations or issues discovered ``` ## Choosing Test Layer Based on the testing capabilities cached in Engram (`sdd/{project}/testing-capabilities`), choose the appropriate test layer for each task: ``` Determine test layer by WHAT the task does: ├── Pure logic, utility function, calculation, data transformation │ └── Unit test (always available if test runner exists) │ ├── Component rendering, user interaction, state changes │ ├── IF integration tools available → Integration test │ └── IF NOT → Unit test with mocks (degrade gracefully) │ ├── Multi-component flow, API interaction, context/provider behavior │ ├── IF integration tools available → Integration test │ └── IF NOT → Unit test with mocks │ ├── Critical business flow, full user journey, cross-page navigation │ ├── IF E2E tools available → E2E test │ ├── IF NOT but integration available → Integration test │ └── IF neither → Unit test (degrade gracefully) │ └── Default: Unit test (always the fallback) ``` **Key rule**: Use the HIGHEST available layer that fits the task. But NEVER skip a task because a layer is unavailable — degrade to the next available layer. ## Test Execution Detect the test runner from the cached testing capabilities: ``` Read test command from: ├── Cached capabilities → test_runner.command (fastest — already detected) ├── openspec/config.yaml → rules.apply.test_command (override) └── Fallback: detect from package.json/pyproject.toml/go.mod When executing tests during TDD: ├── Run ONLY the relevant test file, not the entire suite │ ├── JS/TS: {runner} {test-file-path} (e.g., pnpm vitest run src/utils/tax.test.ts) │ ├── Python: pytest {test-file-path} │ ├── Go: go test ./{package}/... -run {TestName} │ └── Adapt to the runner's CLI ├── This keeps the cycle FAST └── Full suite runs happen in sdd-verify, not here ``` ## Pure Function Preference When writing production code in GREEN/TRIANGULATE steps, prefer pure functions: ``` ✅ PREFER (pure — easy to test): function calculateDiscount(price: number, quantity: number): number { return quantity >= 5 ? price * quantity * 0.1 : 0 } ❌ AVOID (impure — hard to test): function calculateDiscount(item: Item) { globalState.lastDiscount = item.price * 0.1 // side effect updateDOM() // side effect return globalState.lastDiscount } ``` **Why**: Pure functions are deterministic (same input → same output), have no side effects, and are trivially testable. TDD naturally pushes you toward pure functions — embrace it. ## Approval Testing (for refactoring existing code) When a task involves REFACTORING existing code (not writing new code): ``` BEFORE touching production code: ├── 1. Identify existing behavior to preserve ├── 2. Write "approval tests" that capture current behavior: │ ├── Call the function with known inputs │ ├── Assert the CURRENT outputs (even if ugly or wrong) │ └── These tests document what the code does NOW ├── 3. Run approval tests → must PASS (they describe current reality) ├── 4. NOW refactor the production code ├── 5. Run approval tests again → must STILL PASS │ ├── ✅ Passing → refactoring preserved behavior │ └── ❌ Failing → refactoring broke something, revert └── 6. If the spec says behavior should CHANGE: ├── Update the approval test to reflect NEW expected behavior ├── Run → test FAILS (RED — new behavior not implemented yet) └── Implement new behavior → GREEN ``` ## Return Summary Extension When Strict TDD Mode is active, your return summary MUST include this section: ```markdown ### TDD Cycle Evidence | Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR | |------|-----------|-------|------------|-----|-------|-------------|----------| | 1.1 | `path/test.ext` | Unit | ✅ 5/5 | ✅ Written | ✅ Passed | ✅ 3 cases | ✅ Clean | | 1.2 | `path/test.ext` | Integration | N/A (new) | ✅ Written | ✅ Passed | ➖ Single | ✅ Clean | | 1.3 | `path/test.ext` | Unit | ✅ 2/2 | ✅ Written | ✅ Passed | ✅ 2 cases | ➖ None needed | ### Test Summary - **Total tests written**: {N} - **Total tests passing**: {N} - **Layers used**: Unit ({N}), Integration ({N}), E2E ({N}) - **Approval tests** (refactoring): {N} or "None — no refactoring tasks" - **Pure functions created**: {N} ``` **Column definitions**: - **Safety Net**: Pre-existing tests run before modifying files. "N/A (new)" for new files. - **RED**: Test written first, referencing code that doesn't exist yet. Always "✅ Written". - **GREEN**: Tests executed and passing after minimal implementation. Must show execution result. - **TRIANGULATE**: Additional test cases added to force real logic. "➖ Single" if spec has only one scenario. - **REFACTOR**: Code improved with tests still passing. "➖ None needed" if code was already clean. ## Assertion Quality Rules (MANDATORY) **Every assertion must verify REAL behavior.** A test that passes without exercising production logic is worse than no test — it gives false confidence. ### Banned Assertion Patterns (NEVER write these) ``` # TRIVIAL ASSERTIONS — test proves nothing expect(true).toBe(true) # ❌ Tautology expect(false).toBe(false) # ❌ Tautology expect(1).toBe(1) # ❌ Tautology — no production code involved assert True # ❌ Always passes assert 1 == 1 # ❌ Always passes # EMPTY COLLECTION ASSERTIONS without setup context expect(result).toEqual([]) # ❌ ONLY valid if you set up conditions for empty expect(result).toHaveLength(0) # ❌ Same — why is it empty? Did production code run? assert len(result) == 0 # ❌ Same — prove the emptiness comes from real logic assert result == [] # ❌ Same # TYPE-ONLY ASSERTIONS — proves existence, not behavior expect(result).toBeDefined() # ❌ Alone is useless — WHAT is the value? expect(result).not.toBeNull() # ❌ Alone is useless — assert the actual value expect(typeof result).toBe('object') # ❌ Alone is useless — what does the object contain? assert result is not None # ❌ Alone — assert what result actually IS # GHOST LOOP — assertion inside a loop that iterates 0 times const items = screen.queryAllByTestId("item"); // returns [] for (const item of items) { expect(item).toHaveTextContent("value"); # ❌ NEVER EXECUTES — loop body is dead code } # FIX: assert the collection is non-empty FIRST, or set up data so it IS non-empty: expect(items).toHaveLength(3); # ✅ Proves items exist for (const item of items) { ... } # ✅ Now the loop actually runs # INCOMPLETE TDD CYCLE — GREEN without TRIANGULATE # If your GREEN test passes because the setup doesn't exercise the code path, # you are NOT done. You MUST triangulate with a setup that DOES exercise it. # Example: testing "search doesn't update until Enter" but the component # that receives the search is never rendered → the test proves nothing. # FIX: add a test where the component IS rendered and verify the behavior. ``` ### What Makes a REAL Assertion Every test assertion must satisfy ALL of these: 1. **Calls production code** — the test invokes a function, method, or component from the implementation 2. **Asserts a specific output** — compares against a concrete expected value derived from the spec 3. **Would FAIL if the production code were wrong** — if you change the implementation logic, THIS test breaks ``` # ✅ REAL assertions — production code determines the result expect(calculateDiscount(100, 10)).toBe(10) # Real input → real output expect(screen.getByText('Welcome, John')).toBeInTheDocument() # Rendered from data assert result[0].status == "FAIL" # Specific finding from check execution assert response.status_code == 403 # Real HTTP response from the endpoint expect(result).toHaveLength(3) # AND you set up exactly 3 items ``` ### Empty Collection Rule `expect(result).toEqual([])` or `assert len(result) == 0` is ONLY valid when: 1. You set up a specific precondition that SHOULD produce an empty result (e.g., no matching records) 2. The production code actually ran and filtered/processed data to arrive at empty 3. A companion test with different setup produces a NON-EMPTY result (triangulation) If you cannot explain WHY the result is empty based on setup → the assertion is trivial. ### Smoke Test Rule A test that only renders a component without asserting any output is NOT a valid test: ``` # ❌ SMOKE TEST ONLY — proves nothing about behavior render(<MyComponent data={mockData} />); expect(screen.getByTestId("wrapper")).toBeInTheDocument(); # Just proves it rendered # ✅ BEHAVIORAL TEST — proves what the component DOES with the data render(<MyComponent data={mockData} />); expect(screen.getByText("Expected Title")).toBeInTheDocument(); # Verifies output from data expect(screen.getByRole("button")).toHaveTextContent("Submit"); # Verifies real content ``` "Renders without crash" is a smoke test. It is NOT a unit test, NOT an integration test, and it does NOT count toward TDD coverage. If you need a smoke test, it must be accompanied by real behavioral assertions. ### Mock Hygiene Rules **If you need more mocks than assertions, you are testing at the WRONG level.** ``` Mock/assertion ratio guide: ├── ≤ 3 mocks for a test file → ✅ Healthy — focused test ├── 4–6 mocks → ⚠️ Consider extracting logic to a pure function ├── 7+ mocks → ❌ STOP — you are testing at the wrong layer │ ├── Extract the logic under test to a PURE FUNCTION and test it without mocks │ ├── OR move the test to integration/E2E layer where real dependencies exist │ └── NEVER write 10+ mocks to verify a one-line transformation ``` **Extract-Before-Mock Rule**: If the behavior you want to test is a data transformation, mapping, filtering, or conditional logic (e.g., `MUTED → FAIL` status conversion), EXTRACT it to a pure function FIRST, then test the pure function directly. No mocks needed. ``` # ❌ BAD: 15 mocks to test a one-line status conversion vi.mock("next/navigation", ...); vi.mock("next/link", ...); vi.mock("@/components/shadcn", ...); // ... 12 more mocks ... render(<StatusCell row={mutedRow} />); expect(screen.getByText("FAIL")).toBeInTheDocument(); # ✅ GOOD: extract and test the logic directly // In production code: export function resolveDisplayStatus(status: string, isMuted: boolean): string { return status === "MUTED" ? "FAIL" : status; } // In test — ZERO mocks needed: expect(resolveDisplayStatus("MUTED", true)).toBe("FAIL"); expect(resolveDisplayStatus("PASS", false)).toBe("PASS"); ``` ### Implementation Detail Coupling Rule Tests must assert **behavior visible to the user**, not internal implementation details: ``` # ❌ COUPLED TO IMPLEMENTATION — breaks on any style refactor expect(element.className).toContain("text-xs"); expect(element.className).toContain("-mt-2.5"); expect(element.className).toContain("border-border-error-primary"); expect(element.style.color).toBe("red"); # ❌ COUPLED TO INTERNALS — breaks when implementation changes expect(mockService.mock.calls.length).toBe(3); # Why 3? Brittle. expect(component.state.isLoading).toBe(true); # Internal state, not behavior. # ✅ BEHAVIORAL — survives refactors, tests what users see expect(screen.getByText("Error: Payment failed")).toBeInTheDocument(); expect(screen.getByRole("alert")).toHaveTextContent("Risk:"); expect(screen.getByRole("button")).toBeDisabled(); ``` **CSS class assertions are NEVER valid test assertions.** If you need to verify visual styling: 1. Test the **semantic outcome** (e.g., element has `role="alert"`, text is visible, button is disabled) 2. OR use a visual regression tool / E2E screenshot comparison 3. NEVER assert specific Tailwind/CSS class names — they are implementation details ## Rules (Strict TDD specific) - NEVER write production code before writing its test — this is the ONE rule that cannot be broken - NEVER skip the GREEN execution gate — you MUST run tests and confirm they pass - NEVER skip triangulation when the spec defines multiple scenarios — hardcoded Fake It must be forced out - NEVER write trivial assertions (see Banned Assertion Patterns above) — they are WORSE than no test - ALWAYS verify that every assertion CALLS production code and asserts a SPECIFIC expected value - ALWAYS run the Safety Net before modifying existing files — protect what already works - ALWAYS report the TDD Cycle Evidence table — the verify phase will check it - If a test runner execution fails for infrastructure reasons (not test failures), report as "Blocked" and continue to next task - Prefer pure functions — but don't force it where it doesn't fit (e.g., React components with state) - For refactoring tasks, ALWAYS write approval tests before touching code - Run ONLY the relevant test file during the cycle, not the full suite
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.