chore: bootstrap skills library — 19 skills + installer + CI auto-tag
Some checks failed
release / tag (push) Has been cancelled

Phase 1 of mathias/skills extraction (infra#62 Track D — homelab
next-step plan addendum). Imports ~/dev/.skills/ verbatim (19 skill
dirs + SKILLS_INDEX.md) and adds the installation surface:

- Taskfile.yml — install / update / list / release / check targets
- install.sh — bootstrap installer for hosts without Task. Idempotent
  symlink wirer; default checkout at ~/.local/share/skills/ on every
  host; SKILLS_REF env var pins a tag (default: main).
- .gitea/workflows/release.yml — auto-tag every push to main by
  Bump-Type footer (major/minor/patch, default patch). Skipped when
  commit contains [skip-release].
- README — usage, versioning, contribution flow, secret-hygiene rule.

Phase 1 wires Claude Code only (~/.claude/skills/<name> global +
<repo>/.claude/skills/<name> per-repo). Phase 2 adds Crush, opencode,
antigravity, and gitea-resident agents (cobalt-dingo, agentsquad)
once their skill conventions are researched.

Public repo, markdown-only — no secrets, no client names. Verified
via pre-push grep before initial push.

[skip-release]
This commit is contained in:
Mathias
2026-05-24 14:59:54 +02:00
commit d6a71e370e
33 changed files with 8688 additions and 0 deletions

294
atdd/SKILL.md Normal file
View File

@@ -0,0 +1,294 @@
---
name: atdd
description: 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):**
```go
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:**
```bash
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.
```go
// 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:**
```bash
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.
```bash
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
//go:build acceptance
package service_test
```
```bash
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:
```go
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:
```go
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