| name | scaffold-infra-db |
| description | Implement the infrastructure (RDB) layer Repository for one feature, derived from domain Repository IF + sqlc gen functions (lean A โ no spec file). Reads the domain Repository Interface at `internal/domain/<aggregate>/<aggregate>_repository.go` for method list, the sqlc gen functions at `internal/infrastructure/rdb/sqlc/gen/*.gen.go` for available DB calls, and `internal/infrastructure/rdb/README.md` for naming convention. Derives the mapping Repository method โ sqlc gen function via name-match heuristic. For methods without a derivable mapping, **leaves TODO at the impl location + reports** (user resolves: add SQL + re-run `make gen-query`, or accept hand-write). Wraps each method with tracer span + `pgerror.NormalizeError` + rowโentity conversion per the sibling pattern. Reads `internal/infrastructure/README.md` + `internal/infrastructure/rdb/README.md` + an existing `repository/<sibling>/` as structural template, invokes a test-perspective subagent (real DB + rollback via testkit, sqlc gen wrap correctness, pgerror normalization paths, observability spans), writes Go impl + integration-style test, updates `internal/di/module/infrastructure.go` with `fx.Provide`. Verifies with `make fix` + `make test`. Prerequisites: (1) domain Repository IF exists; (2) `make gen-query` has run. Does NOT generate SQL or run `make gen-query` itself. |
Scaffold Infra DB
Generate the infrastructure (RDB) layer Repository for a feature. lean A: no spec file โ Repository is derived from domain Repository IF + sqlc gen functions via naming convention.
A Japanese reference translation of this skill is available at SKILL.ja.md in the same directory (not loaded as a skill; for human reference only).
When to Use
- After
scaffold-domain has created the Repository interface AND make gen-query has produced the sqlc gen functions.
- As the second step of
scaffold-endpoint (auto-chained after scaffold-domain).
- Standalone when only the infra layer needs scaffolding.
Do NOT use for:
- Generating SQL or running
make gen-query (out of scope; precondition).
- Adding a single new method to an existing Repository impl โ edit by hand.
- Implementing non-DB infrastructure.
What This Skill Reads / Writes
Reads (always):
internal/domain/<aggregate>/<aggregate>_repository.go โ target interface to implement (method list + signatures)
internal/infrastructure/rdb/sqlc/gen/*.gen.go โ available sqlc functions (for mapping derivation)
internal/infrastructure/rdb/README.md โ naming convention + impl rules
internal/infrastructure/README.md โ infra-layer convention
internal/infrastructure/rdb/pgerror/README.md โ error normalization rules (SQLSTATE mapping, single-normalization-point principle)
internal/infrastructure/rdb/repository/<sibling>/<sibling>_repository.go โ de facto reference implementation (infra READMEs describe principles textually but do not carry full impl snippets, so sibling code is the closest reference; still, if it drifts from README rules the README wins)
internal/di/module/infrastructure.go โ DI registration target
Writes (with confirmation):
internal/infrastructure/rdb/repository/<aggregate>/<aggregate>_repository.go
internal/infrastructure/rdb/repository/<aggregate>/<aggregate>_repository_test.go
internal/di/module/infrastructure.go (append fx.Provide(<aggregate>.New))
Triggers (via make):
make fix + make test โ final verification
Never touches:
- SQL files or sqlc gen output (read-only references)
- Other aggregates' repository directories
- domain layer
Preconditions
The skill verifies before any write:
internal/domain/<aggregate>/<aggregate>_repository.go exists with the Repository interface.
internal/infrastructure/rdb/sqlc/gen/ exists with at least some sqlc gen functions.
internal/infrastructure/rdb/repository/<aggregate>/ does NOT already exist (abort if it does).
If any precondition fails, abort with corrective guidance (/scaffold-domain, make gen-query, manual cleanup).
Environment note: make gen-query dumps the live DB schema via pg_dump, so the database must be running first. Use the dedicated make targets โ not raw docker compose โ to prepare it: make serve (starts the development profile incl. the database service) โ make db-init โ make gen-query. make db-init migrates and seeds both the local and test DBs in one shot; the integration-style tests this skill writes (make test) also need the running, seeded test DB, so db-init (not a bare db-*-migrate-up) is the correct setup.
Toolchain note: if the final make fix / make test (or make lint) fails on a tool version mismatch (e.g. golangci-lint v1-vs-v2 config error), realign the local toolchain with make install-tools (make sync-tools first if tools.yaml changed) rather than hand-editing PATH โ then re-run.
First Step: Resolve Identity
This skill MUST call AskUserQuestion immediately after invocation (unless invoked from scaffold-endpoint with the aggregate name in context):
- ่ณชๅ: ใๅฏพ่ฑก aggregate ๅ (PascalCase, e.g.,
User)ใ โ resolves to internal/domain/<aggregate-lower>/ + internal/infrastructure/rdb/repository/<aggregate-lower>/
Step 1. Read Inputs
- Read
internal/domain/<aggregate>/<aggregate>_repository.go and extract Repository interface method list (each with signature).
- Read
internal/infrastructure/rdb/sqlc/gen/*.gen.go and extract sqlc-generated function list (the Queries methods).
- Read
internal/infrastructure/rdb/README.md for the naming convention (how Repository method names typically map to sqlc gen function names) and impl rules.
- Read
internal/infrastructure/README.md for layer rules.
- Read
internal/infrastructure/rdb/pgerror/README.md for SQLSTATE โ apperror mapping and the single-normalization-point principle (every sqlc call's error MUST flow through pgerror.NormalizeError).
- Read 1 sibling repository (
internal/infrastructure/rdb/repository/user/user_repository.go etc.) as concrete reference โ tracer wiring, gen.New(driver.New(ctx, r.db)) usage, pgerror normalization placement, conversion helper pattern. The infra READMEs do not carry a full code snippet, so sibling code is the closest concrete reference; on any conflict the READMEs win.
Step 2. Derive Mapping (lean A core)
For each Repository method, find the corresponding sqlc gen function(s) via name-match heuristic:
- Exact match:
Repository.Save โ Queries.SaveUser โ โ mapped
- Stem match:
FindByActive โ Queries.ListUsers / ListActiveUsers / ListDeletedUsers (multi-target for switch dispatch) โ โ mapped (multi)
- Aggregate-aware:
Repository.Find โ Queries.GetUser โ โ mapped (synonyms)
For each Repository method WITHOUT a derivable sqlc gen match:
-
Do NOT halt. Generate the method stub with a TODO explaining what's missing:
func (r *repository) CountByActive(ctx context.Context, status user.ActiveStatus) (int, error) {
ctx, endSpan := r.tracer.Start(ctx)
defer endSpan()
return 0, errors.New("not implemented")
}
-
Surface in final report which methods got TODO stubs.
This differs from scaffold-controller (which halts) because partial Repository implementation can still compile + run for the methods that ARE mapped.
Step 3. Test-Perspective Subagent
Invoke the Agent tool to enumerate infra-layer test viewpoints BEFORE writing code.
subagent_type: general-purpose
- Prompt (Japanese): pass the derived mapping +
internal/infrastructure/rdb/README.md Test Strategy + expected infra viewpoints:
- real DB + rollback isolation per test
- sqlc gen wrap correctness (param mapping)
- pgerror normalization paths (unique violation, no rows, connection error)
- observability span emission
- parallel execution safety (Tx serialization)
- Output: structured list of test cases per method.
Step 4. Plan and Confirm
Display a Japanese summary:
- Files to be created + DI module update
- For each Repository method: signature + mapped sqlc gen function(s) (or TODO marker if unmapped)
- Test method list from subagent
Ask:
- ใไปฅไธใฎๆงๆใง infra-db ๅฑคใ็ๆใใพใใ๏ผใ
- Options: ใ็ๆใใใ / ใไฟฎๆญฃใใใ็ฎๆใๆๆใใใ / ใใญใฃใณใปใซใ
Step 5. Write Files
Order:
<aggregate>_repository.go โ main implementation
<aggregate>_repository_test.go โ testkit-backed integration tests
- Update
internal/di/module/infrastructure.go โ append <aggregate>.New under repository module
Implementation file conventions:
package <aggregate>
type repository struct { db driver.DatabaseDriver; tracer observability.LayerTracer }
func New(db driver.DatabaseDriver, tf observability.TracerFactory) <domain>.Repository { return &repository{db: db, tracer: tf.Infra()} }
- Each method (mapped):
ctx, endSpan := r.tracer.Start(ctx); defer endSpan()
db := gen.New(driver.New(ctx, r.db)) (SQL logging / tracing is applied at the driver connection level; no per-repository wrapper)
- Map domain params โ sqlc params via heuristic (1:1 by name, types adjusted)
- Call
db.<SqlcFunc>(ctx, params)
- Convert sqlc rows โ domain entity per sibling pattern
- Wrap every sqlc-call error with
pgerror.NormalizeError(err) (single-normalization principle โ see Step 1.5 / pgerror/README.md)
- Each method (unmapped): TODO stub as in Step 2
- Complex helpers (multi-row โ slice): follow sibling pattern (helper function in same file)
Test file conventions:
- Use
testkit for real DB + rollback
- Japanese subtest names
- Tests only for mapped methods; unmapped TODO methods get a skipped test stub
Step 6. Verify
make fix
make test
Confirm Repository package coverage. Infra layer targets โฅ85%. On failure: TODO + FB; no auto-rollback.
DI verification (runtime): go build / make test do NOT construct the Fx graph โ a missing provider, an unregistered New, or a mismatched constructor signature only fails at app startup, not at compile/test time. After the DI registration (fx.Provide(<aggregate>.New)), confirm the app actually boots: with make serve running, air rebuilds on save โ verify the api_server logs reach [Fx] RUNNING ("http server started") with no Fx provide / invoke errors. Fresh-env caveat: the container builds in vendor mode, so run make tidy-lib (generates vendor/) first โ otherwise it fails with inconsistent vendoring before Fx even runs.
Step 7. Closing
<Aggregate> infra-db ๅฑคใ็ๆใใพใใใ<N> ใใกใคใซไฝๆ + DI 1 ่ก่ฟฝๅ ใ
mapped: <X> methods (sqlc gen ็ต็ฑ)
unmapped: <Y> methods (TODO stubใSQL ่ฟฝๅ + make gen-query ๅพใซๅ scaffold or ๆๅๅฎ่ฃ
)
make test OKใcoverage <Z>%ใ
ๆฌกใฏ scaffold-usecase ใง application serviceใใพใใฏ scaffold-endpoint ใงๆฎๅฑคใ็ถ่กใงใใพใใ
Do NOT commit.
AI Modification Scope
Per the "Exception: Skill Execution" clause:
- Write scope:
internal/infrastructure/rdb/repository/<aggregate>/ (new directory) + internal/di/module/infrastructure.go (1 entry append).
- Aborts if the aggregate directory already exists.
Remains protected:
- SQL / sqlc gen output (read-only references)
- Other repository / query_service / system_query directories
- domain layer
Constraints
- โ Add comments that restate the code or explain why a choice was made โ keep code comments minimal (behavior / contract only); rationale belongs in the commit message / README, not the code. One-line declaration godoc stays (even on unexported symbols).
- โ Invent business logic in Repository (data orchestration only)
- โ Generate SQL or run
make gen-query (out of scope)
- โ Hand-edit sqlc generated files
- โ Skip the test-perspective subagent
- โ Skip the identity-confirmation
AskUserQuestion
- โ Overwrite an existing aggregate repository directory
- โ Touch other aggregates' repositories
- โ Auto-rollback on failure (TODO + FB instead)
- โ Auto-generate dummy sqlc functions for unmapped methods (TODO stub + hand-off)
- โ
Japanese user-facing output + Japanese test case names
- โ
Use existing
repository/<sibling>/ as concrete reference (infra READMEs do not include a full impl snippet); READMEs still win on any rule conflict
- โ
Mapping derivation strictly via naming convention + sibling pattern
- โ
Update DI registration in same skill run
- โ
Partial implementation OK โ mapped methods generate, unmapped methods get TODO stubs
Checklist