Files
skills/skills/atdd/SKILL.md
T
mathiasandClaude Opus 5 56a947b2a0 refactor(plugin)!: ship as a Claude Code plugin, one dir link per harness
Skills move to skills/<name>/SKILL.md and the repo gains both manifests, so
it is now a marketplace hosting exactly one plugin whose root is the repo
root (source: "."). Claude Code installs it from git; no symlinks involved.

Why: install.sh wired the library into every consuming repo as an ABSOLUTE
symlink to $CHECKOUT_DIR. The checkout moved three times, most recently when
local-dev@1966d5a dropped the embedded .skills/ copy, and nothing migrated
the links — 243 dangling links across 12 repos, hidden from git by the
**/.claude/skills/ ignore rule. Worse, ~/dev spans two path roots
(/Users/mathias on the iCloud-shared Macs, /home/mathias on koala), so an
absolute link stored there is broken on one side by construction. Re-running
the installer cannot fix that; only removing the mechanism can.

install.sh keeps the harnesses with no plugin system and drops from 42 links
per harness to one directory link, via the convergent ~/.agents/skills
convention that Pi and OpenCode both read:

  ~/.agents/skills          -> $CHECKOUT_DIR/skills
  ~/.config/crush/skills    -> ~/.agents/skills
  ~/.config/opencode/skills -> ~/.agents/skills

wire_claude_repo is deleted outright rather than gated behind a flag — on a
mixed-root fleet it cannot be made correct. Antigravity and Vibe targets go
too; both unused. CHECKOUT_DIR keeps its host-local default deliberately:
pointing it into ~/dev would put a git working tree inside iCloud with two
concurrent writers.

Taskfile drops five near-identical wiring loops (they had already drifted)
and delegates to install.sh. `task check` gains frontmatter validation, a
stray-root-entry gate, and manifest/tag version parity — the loose
secret-hygiene.md sat unloadable for months because nothing checked layout.

Verified: claude plugin validate . passes clean; a probe skill loaded as
skills:zzprobe through --plugin-dir; task check green at 42 skills.

Refs: #23
Bump-Type: minor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-23 14:29:51 +02:00

8.4 KiB

name, description
name description
atdd Implement user stories using the ATDD workflow — Red (failing acceptance test) → Green (minimal implementation) → Refactor. One story at a time.

Acceptance Test-Driven Development (ATDD)

Overview

ATDD extends TDD to the acceptance level. You write tests that capture the behavior described in a user story's acceptance criteria — then implement just enough to make them pass.

Input required: User stories with clear acceptance criteria. Load user-stories skill first.

Core Methodology: Red-Green-Refactor at Acceptance Level

RED    → Write failing acceptance test (one story at a time)
GREEN  → Write minimal code to make it pass
REFACTOR → Clean up code (load clean-code skill)
COMMIT → Commit with reference to the story

Key principle: Never mix phases. RED is only writing tests. GREEN is only making tests pass. REFACTOR is only cleaning up.

Stop and confirm before advancing from RED to GREEN, and from GREEN to REFACTOR.

Working on One Story at a Time

Pick the highest-priority story from the backlog. Do not start a second story until the first is done (committed, clean, all tests green).

Phase 1: RED — Write Failing Acceptance Test

Translate the story's acceptance criteria into executable tests.

For each acceptance criterion:

Given [context]
When [action]
Then [observable outcome]

Becomes a test case.

Go ATDD Example

Story: "Users can register with email and password"

Acceptance criteria:

  • Given valid email and password, registration succeeds and returns the new user
  • Given an email already in use, registration returns ErrDuplicateEmail
  • Given an invalid email format, registration returns ErrInvalidEmail
  • Given a password shorter than 8 chars, registration returns ErrWeakPassword

Acceptance tests (write these BEFORE implementation):

func TestUserRegistration_Acceptance(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        pass    string
        wantErr error
    }{
        {
            name:    "valid registration creates user",
            email:   "alice@example.com",
            pass:    "securePass1!",
            wantErr: nil,
        },
        {
            name:    "duplicate email returns ErrDuplicateEmail",
            email:   "existing@example.com",
            pass:    "securePass1!",
            wantErr: ErrDuplicateEmail,
        },
        {
            name:    "invalid email format returns ErrInvalidEmail",
            email:   "not-an-email",
            pass:    "securePass1!",
            wantErr: ErrInvalidEmail,
        },
        {
            name:    "short password returns ErrWeakPassword",
            email:   "alice@example.com",
            pass:    "short",
            wantErr: ErrWeakPassword,
        },
    }

    store := NewInMemoryUserStore()
    // Pre-seed duplicate email
    store.Seed(User{Email: "existing@example.com"})

    svc := NewUserService(store)

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            _, err := svc.Register(context.Background(), tt.email, tt.pass)
            if tt.wantErr != nil {
                assert.ErrorIs(t, err, tt.wantErr)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

Run and verify they fail:

go test -run TestUserRegistration_Acceptance ./...
# FAIL: UserService.Register undefined

The test must fail because the implementation doesn't exist yet. If it passes, you're testing existing behavior. Check your test.

STOP here. Confirm before moving to GREEN.

Phase 2: GREEN — Minimal Implementation

Implement the absolute minimum code necessary to make the acceptance tests pass. Ugly code is acceptable at this stage. The goal is green, not clean.

// Minimal implementation — prioritize passing tests over elegance
func (s *UserService) Register(ctx context.Context, email, password string) (User, error) {
    if !strings.Contains(email, "@") {
        return User{}, ErrInvalidEmail
    }
    if len(password) < 8 {
        return User{}, ErrWeakPassword
    }
    if s.store.Exists(ctx, email) {
        return User{}, ErrDuplicateEmail
    }
    u := User{ID: newID(), Email: email}
    if err := s.store.Save(ctx, u); err != nil {
        return User{}, fmt.Errorf("save user: %w", err)
    }
    return u, nil
}

Verify GREEN:

go test -run TestUserRegistration_Acceptance ./...
# ok
go test ./... # All other tests still pass
go test -race ./...

STOP here. Confirm before moving to REFACTOR.

Phase 3: REFACTOR — Clean Up

With tests green, clean the code. Do not change behavior.

During REFACTOR, load and follow the clean-code skill.

Apply:

  • Extract functions for complex logic
  • Better naming
  • Remove duplication
  • Apply SOLID principles
  • For design decisions, load solid skill

After every change: go test ./... must stay green.

Do NOT refactor the acceptance tests themselves unless they have a bug. The tests are your specification — changing them changes what you've committed to.

STOP here. Confirm before committing.

Phase 4: COMMIT

Commit the completed story. Reference the story in the commit message.

git commit -m "feat: user registration with email validation

Implements acceptance criteria for US-001 (User Registration).
- Valid email/password creates a user
- Duplicate email returns ErrDuplicateEmail
- Invalid email format returns ErrInvalidEmail
- Short password returns ErrWeakPassword"

For Test Design Questions

When writing acceptance tests, load the test-design skill to ensure:

  • Tests are understandable (Farley: Understandable)
  • Tests don't couple to implementation details (Farley: Maintainable)
  • Tests are repeatable across environments (Farley: Repeatable)

For Code Review Before Commit

Load the code-review skill before committing to check:

  • Error handling follows fmt.Errorf("op: %w", err) pattern
  • Context propagation is correct
  • No race conditions in concurrent code
  • Exported API is minimal

For Refactoring Guidance

Load the refactoring skill for systematic techniques to apply during REFACTOR phase.

For Clean Code Principles

Load the clean-code skill during REFACTOR phase to apply:

  • Naming conventions
  • Function size and responsibility
  • SOLID principles

Go-Specific ATDD Notes

Acceptance Test Location

Place acceptance tests in _test.go files with the _acceptance_test.go suffix or //go:build acceptance build tag to separate from fast unit tests:

//go:build acceptance

package service_test
go test ./...              # Unit tests only (fast)
go test -tags=acceptance ./...  # Include acceptance tests

In-Memory Implementations for Acceptance Tests

Use in-memory implementations of repositories for acceptance tests:

type InMemoryUserStore struct {
    mu    sync.Mutex
    users map[string]User
}

func (s *InMemoryUserStore) Save(ctx context.Context, u User) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, exists := s.users[u.Email]; exists {
        return ErrDuplicateEmail
    }
    s.users[u.Email] = u
    return nil
}

func (s *InMemoryUserStore) Exists(ctx context.Context, email string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    _, exists := s.users[email]
    return exists
}

This keeps acceptance tests fast and deterministic.

Given-When-Then in Go

The t.Run structure maps naturally to Given-When-Then:

t.Run("given valid credentials, when logging in, then returns token", func(t *testing.T) {
    // Given: valid user exists
    store := InMemoryUserStore{...}

    // When: login is called
    token, err := svc.Login(ctx, email, pass)

    // Then: token is returned
    require.NoError(t, err)
    assert.NotEmpty(t, token)
})

Verification Checklist

Before committing:

  • All acceptance tests pass
  • All existing tests still pass
  • Race detector clean: go test -race ./...
  • REFACTOR phase complete — code is clean
  • Commit message references the user story
  • No behavior added beyond the acceptance criteria

Cross-References

  • Requires: user-stories skill output (stories with acceptance criteria)
  • During REFACTOR: load clean-code skill
  • For test quality: load test-design skill
  • For code review: load code-review skill
  • For refactoring guidance: load refactoring skill
  • Foundation: load tdd skill for core TDD methodology