litclaude-ai 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,471 @@
1
+ # Database Stack — sqlc + pgx + goose + testcontainers
2
+
3
+ The canonical 2026 PostgreSQL stack. **Type-safe SQL with zero runtime reflection**, hot-path-friendly connection pooling, sane migrations, real Postgres in tests.
4
+
5
+ If you came here from a `gorm` project: gorm is rejected. See "Why not gorm" at the end.
6
+
7
+ ---
8
+
9
+ ## Toolchain
10
+
11
+ ```bash
12
+ go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
13
+ go install github.com/pressly/goose/v3/cmd/goose@latest
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Layout
19
+
20
+ ```
21
+ internal/store/
22
+ ├── sqlc.yaml # sqlc config
23
+ ├── schema.sql # the cumulative DDL sqlc parses
24
+ ├── queries/ # one *.sql per resource
25
+ │ ├── users.sql
26
+ │ ├── orders.sql
27
+ │ └── sessions.sql
28
+ ├── sqlc/ # GENERATED — do not hand-edit
29
+ │ ├── db.go
30
+ │ ├── models.go
31
+ │ ├── users.sql.go
32
+ │ ├── orders.sql.go
33
+ │ └── sessions.sql.go
34
+ ├── migrations/ # goose migrations, ordered
35
+ │ ├── 20260101000001_create_users.sql
36
+ │ └── 20260102000001_add_orders.sql
37
+ ├── pool.go # pgxpool factory
38
+ ├── user_store.go # domain-facing wrapper around sqlc
39
+ └── user_store_test.go # testcontainers integration test
40
+ ```
41
+
42
+ ---
43
+
44
+ ## `sqlc.yaml`
45
+
46
+ ```yaml
47
+ version: "2"
48
+ sql:
49
+ - engine: "postgresql"
50
+ schema: "schema.sql"
51
+ queries: "queries"
52
+ gen:
53
+ go:
54
+ package: "sqlc"
55
+ out: "sqlc"
56
+ sql_package: "pgx/v5"
57
+ emit_json_tags: false
58
+ emit_prepared_queries: false
59
+ emit_interface: true # generates a Querier interface
60
+ emit_exact_table_names: false
61
+ emit_pointers_for_null_types: true
62
+ emit_empty_slices: true
63
+ overrides:
64
+ - db_type: "uuid"
65
+ go_type:
66
+ import: "github.com/google/uuid"
67
+ type: "UUID"
68
+ - db_type: "timestamptz"
69
+ go_type:
70
+ import: "time"
71
+ type: "Time"
72
+ ```
73
+
74
+ Key choices:
75
+
76
+ - `sql_package: "pgx/v5"` — generated code uses pgx directly, not `database/sql`. Faster, type-safer.
77
+ - `emit_interface: true` — generates a `Querier` interface. Lets stores accept either `*pgxpool.Pool` or `pgx.Tx` for transaction support.
78
+ - `emit_pointers_for_null_types: true` — nullable columns become `*T`, not `sql.NullString`. Cleaner mapping to domain types.
79
+ - `overrides` for `uuid` → `google/uuid.UUID` and `timestamptz` → `time.Time`.
80
+
81
+ ---
82
+
83
+ ## `schema.sql`
84
+
85
+ ```sql
86
+ -- internal/store/schema.sql
87
+ -- The CUMULATIVE schema sqlc parses. Not migrations — the end state.
88
+ -- Regenerate from a fresh DB via `pg_dump --schema-only`, or hand-maintain.
89
+
90
+ CREATE TABLE users (
91
+ id UUID PRIMARY KEY,
92
+ email TEXT NOT NULL UNIQUE,
93
+ username TEXT NOT NULL UNIQUE,
94
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
95
+ );
96
+
97
+ CREATE INDEX idx_users_created_at ON users(created_at DESC);
98
+ ```
99
+
100
+ ---
101
+
102
+ ## `queries/users.sql`
103
+
104
+ ```sql
105
+ -- name: GetUser :one
106
+ SELECT id, email, username, created_at
107
+ FROM users
108
+ WHERE id = $1;
109
+
110
+ -- name: ListUsers :many
111
+ SELECT id, email, username, created_at
112
+ FROM users
113
+ ORDER BY created_at DESC
114
+ LIMIT $1 OFFSET $2;
115
+
116
+ -- name: CreateUser :one
117
+ INSERT INTO users (id, email, username)
118
+ VALUES ($1, $2, $3)
119
+ RETURNING id, email, username, created_at;
120
+
121
+ -- name: UpdateUserEmail :exec
122
+ UPDATE users
123
+ SET email = $2
124
+ WHERE id = $1;
125
+
126
+ -- name: DeleteUser :exec
127
+ DELETE FROM users WHERE id = $1;
128
+ ```
129
+
130
+ sqlc directives:
131
+
132
+ - `:one` — exactly one row; returns `(T, error)`. Returns `pgx.ErrNoRows` on miss.
133
+ - `:many` — zero or more rows; returns `([]T, error)`.
134
+ - `:exec` — no rows returned; returns `error`.
135
+ - `:execrows` — returns `(int64, error)` with affected row count.
136
+ - `:batchone` / `:batchmany` / `:batchexec` — pgx batch mode for bulk operations.
137
+
138
+ Run `task gen:sqlc` (or `sqlc generate`). The generated file is committed; CI checks it is up-to-date.
139
+
140
+ ---
141
+
142
+ ## Generated code shape (`sqlc/users.sql.go`)
143
+
144
+ ```go
145
+ // GENERATED — do not edit
146
+ type User struct {
147
+ ID uuid.UUID
148
+ Email string
149
+ Username string
150
+ CreatedAt time.Time
151
+ }
152
+
153
+ const getUser = `-- name: GetUser :one
154
+ SELECT id, email, username, created_at FROM users WHERE id = $1`
155
+
156
+ func (q *Queries) GetUser(ctx context.Context, id uuid.UUID) (User, error) {
157
+ row := q.db.QueryRow(ctx, getUser, id)
158
+ var u User
159
+ err := row.Scan(&u.ID, &u.Email, &u.Username, &u.CreatedAt)
160
+ return u, err
161
+ }
162
+ ```
163
+
164
+ Type-safe inputs, type-safe outputs, compile-time-checked column-to-field mapping. **A schema change that drops a column breaks compilation.** Hand-rolled SQL would have failed at runtime.
165
+
166
+ ---
167
+
168
+ ## `store/pool.go`
169
+
170
+ ```go
171
+ package store
172
+
173
+ import (
174
+ "context"
175
+ "fmt"
176
+ "time"
177
+
178
+ "github.com/jackc/pgx/v5/pgxpool"
179
+ )
180
+
181
+ func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
182
+ cfg, err := pgxpool.ParseConfig(dsn)
183
+ if err != nil { return nil, fmt.Errorf("parse dsn: %w", err) }
184
+
185
+ cfg.MaxConns = 25
186
+ cfg.MinConns = 5
187
+ cfg.MaxConnLifetime = time.Hour
188
+ cfg.MaxConnIdleTime = 30 * time.Minute
189
+ cfg.HealthCheckPeriod = 1 * time.Minute
190
+
191
+ pool, err := pgxpool.NewWithConfig(ctx, cfg)
192
+ if err != nil { return nil, fmt.Errorf("connect: %w", err) }
193
+
194
+ if err := pool.Ping(ctx); err != nil {
195
+ pool.Close()
196
+ return nil, fmt.Errorf("ping: %w", err)
197
+ }
198
+ return pool, nil
199
+ }
200
+ ```
201
+
202
+ `pgxpool.Pool` is `Querier`-compatible (implements the interface sqlc generates). Same pool flows into sqlc queries unchanged.
203
+
204
+ ---
205
+
206
+ ## `store/user_store.go` — domain ↔ sqlc
207
+
208
+ ```go
209
+ package store
210
+
211
+ import (
212
+ "context"
213
+ "errors"
214
+ "fmt"
215
+
216
+ "github.com/google/uuid"
217
+ "github.com/jackc/pgx/v5"
218
+ "github.com/jackc/pgx/v5/pgxpool"
219
+
220
+ "github.com/your-org/myservice/internal/domain"
221
+ "github.com/your-org/myservice/internal/store/sqlc"
222
+ )
223
+
224
+ type UserStore struct {
225
+ q *sqlc.Queries
226
+ }
227
+
228
+ func NewUserStore(pool *pgxpool.Pool) *UserStore {
229
+ return &UserStore{q: sqlc.New(pool)}
230
+ }
231
+
232
+ func (s *UserStore) Get(ctx context.Context, id domain.UserID) (domain.User, error) {
233
+ row, err := s.q.GetUser(ctx, uuid.UUID(id))
234
+ if err != nil {
235
+ if errors.Is(err, pgx.ErrNoRows) {
236
+ return domain.User{}, domain.ErrUserNotFound
237
+ }
238
+ return domain.User{}, fmt.Errorf("get user %s: %w", id, err)
239
+ }
240
+ return rowToDomain(row)
241
+ }
242
+
243
+ func (s *UserStore) Create(ctx context.Context, u domain.User) (domain.User, error) {
244
+ row, err := s.q.CreateUser(ctx, sqlc.CreateUserParams{
245
+ ID: uuid.UUID(u.ID),
246
+ Email: u.Email.String(),
247
+ Username: u.Username.String(),
248
+ })
249
+ if err != nil {
250
+ return domain.User{}, fmt.Errorf("create user: %w", err)
251
+ }
252
+ return rowToDomain(row)
253
+ }
254
+
255
+ func rowToDomain(r sqlc.User) (domain.User, error) {
256
+ email, err := domain.NewEmail(r.Email)
257
+ if err != nil {
258
+ return domain.User{}, fmt.Errorf("db invariant: email %q: %w", r.Email, err)
259
+ }
260
+ username, err := domain.NewUsername(r.Username)
261
+ if err != nil {
262
+ return domain.User{}, fmt.Errorf("db invariant: username %q: %w", r.Username, err)
263
+ }
264
+ return domain.User{
265
+ ID: domain.UserID(r.ID),
266
+ Email: email,
267
+ Username: username,
268
+ CreatedAt: r.CreatedAt,
269
+ }, nil
270
+ }
271
+ ```
272
+
273
+ The wrapping is verbose. **That is the point.** sqlc rows are storage representations; domain types are business representations. Mapping them explicitly is where invariants are enforced.
274
+
275
+ `pgx.ErrNoRows` becomes `domain.ErrUserNotFound` — callers never see storage-level errors.
276
+
277
+ ---
278
+
279
+ ## Transactions — pgx.Tx satisfies the Querier interface
280
+
281
+ ```go
282
+ func (s *UserStore) CreateWithProfile(
283
+ ctx context.Context,
284
+ pool *pgxpool.Pool,
285
+ u domain.User,
286
+ p domain.Profile,
287
+ ) error {
288
+ tx, err := pool.Begin(ctx)
289
+ if err != nil { return fmt.Errorf("begin: %w", err) }
290
+ defer tx.Rollback(ctx) // no-op if Commit succeeded
291
+
292
+ q := s.q.WithTx(tx) // sqlc.Queries bound to the tx
293
+
294
+ if _, err := q.CreateUser(ctx, /* ... */); err != nil {
295
+ return fmt.Errorf("create user: %w", err)
296
+ }
297
+ if _, err := q.CreateProfile(ctx, /* ... */); err != nil {
298
+ return fmt.Errorf("create profile: %w", err)
299
+ }
300
+ return tx.Commit(ctx)
301
+ }
302
+ ```
303
+
304
+ Pattern:
305
+
306
+ - `defer tx.Rollback(ctx)` immediately after `Begin` — safe even after Commit (returns "tx closed", which we ignore via the unhandled return).
307
+ - `q.WithTx(tx)` returns a `*Queries` bound to the tx.
308
+ - Last line: `tx.Commit(ctx)`.
309
+
310
+ For nested transactions across multiple stores, accept a `Querier` parameter:
311
+
312
+ ```go
313
+ func (s *UserStore) CreateTx(ctx context.Context, q sqlc.Querier, u domain.User) (domain.User, error) {
314
+ // uses q instead of s.q — caller decides if it's pool or tx
315
+ }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Migrations — goose
321
+
322
+ ```bash
323
+ goose -dir internal/store/migrations create create_users sql
324
+ ```
325
+
326
+ ```sql
327
+ -- migrations/20260101000001_create_users.sql
328
+ -- +goose Up
329
+ CREATE TABLE users (
330
+ id UUID PRIMARY KEY,
331
+ email TEXT NOT NULL UNIQUE,
332
+ username TEXT NOT NULL UNIQUE,
333
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
334
+ );
335
+
336
+ -- +goose Down
337
+ DROP TABLE users;
338
+ ```
339
+
340
+ Run:
341
+
342
+ ```bash
343
+ goose -dir internal/store/migrations postgres "$DATABASE_URL" up
344
+ goose -dir internal/store/migrations postgres "$DATABASE_URL" status
345
+ goose -dir internal/store/migrations postgres "$DATABASE_URL" down
346
+ ```
347
+
348
+ Rules:
349
+
350
+ - One DDL change per migration. Never combine schema + data migrations in one file.
351
+ - `Down` is real, not a stub. CI runs `up` → `down` → `up` on a fresh container to prove reversibility.
352
+ - Migrations are append-only. Never edit a merged migration; add a new one.
353
+
354
+ `goose` can run programmatically as well:
355
+
356
+ ```go
357
+ import "github.com/pressly/goose/v3"
358
+
359
+ if err := goose.UpContext(ctx, db, "migrations"); err != nil { ... }
360
+ ```
361
+
362
+ Useful for tools that own their schema (CI runner, integration test setup).
363
+
364
+ ---
365
+
366
+ ## Integration tests — testcontainers
367
+
368
+ ```go
369
+ package store_test
370
+
371
+ import (
372
+ "context"
373
+ "testing"
374
+
375
+ "github.com/stretchr/testify/require"
376
+ "github.com/testcontainers/testcontainers-go/modules/postgres"
377
+ )
378
+
379
+ func newTestDB(t *testing.T) *pgxpool.Pool {
380
+ t.Helper()
381
+ ctx := context.Background()
382
+
383
+ pgC, err := postgres.Run(ctx,
384
+ "postgres:16-alpine",
385
+ postgres.WithDatabase("test"),
386
+ postgres.WithUsername("test"),
387
+ postgres.WithPassword("test"),
388
+ postgres.BasicWaitStrategies(),
389
+ )
390
+ require.NoError(t, err)
391
+ t.Cleanup(func() { _ = pgC.Terminate(ctx) })
392
+
393
+ dsn, err := pgC.ConnectionString(ctx, "sslmode=disable")
394
+ require.NoError(t, err)
395
+
396
+ pool, err := store.NewPool(ctx, dsn)
397
+ require.NoError(t, err)
398
+ t.Cleanup(pool.Close)
399
+
400
+ require.NoError(t, goose.UpContext(ctx, /* sql.DB from pool */, "../migrations"))
401
+ return pool
402
+ }
403
+
404
+ func TestUserStore_Create_returns_new_user(t *testing.T) {
405
+ // Given
406
+ pool := newTestDB(t)
407
+ s := store.NewUserStore(pool)
408
+ ctx := context.Background()
409
+
410
+ // When
411
+ user, err := s.Create(ctx, domain.User{
412
+ ID: domain.UserID(uuid.Must(uuid.NewV7())),
413
+ Email: mustEmail("a@b.com"),
414
+ Username: mustUsername("alice"),
415
+ })
416
+
417
+ // Then
418
+ require.NoError(t, err)
419
+ require.NotEmpty(t, user.ID)
420
+
421
+ fetched, err := s.Get(ctx, user.ID)
422
+ require.NoError(t, err)
423
+ require.Equal(t, user.Email, fetched.Email)
424
+ }
425
+ ```
426
+
427
+ testcontainers spins a real Postgres in Docker, runs migrations, hands you a pool. Tests are slow (~2s startup) but **real** — no fake that diverges from production.
428
+
429
+ For test suites with many cases, share one container across tests in the same package via `TestMain`:
430
+
431
+ ```go
432
+ var testPool *pgxpool.Pool
433
+
434
+ func TestMain(m *testing.M) {
435
+ ctx := context.Background()
436
+ pgC, _ := postgres.Run(ctx, "postgres:16-alpine", /* ... */)
437
+ defer pgC.Terminate(ctx)
438
+ dsn, _ := pgC.ConnectionString(ctx, "sslmode=disable")
439
+ testPool, _ = store.NewPool(ctx, dsn)
440
+ // run migrations once
441
+ os.Exit(m.Run())
442
+ }
443
+ ```
444
+
445
+ Each test then uses a transaction it rolls back at the end — fast and isolated.
446
+
447
+ ---
448
+
449
+ ## Why NOT gorm
450
+
451
+ | Concern | gorm | sqlc + pgx |
452
+ |---|---|---|
453
+ | Type safety | runtime reflection; column-to-field via tags | compile-time-checked from SQL |
454
+ | Performance | 2–5x slower than pgx | pgx is the fastest Go pg driver |
455
+ | N+1 queries | encouraged by `Preload` API | explicit JOIN in `.sql` |
456
+ | Migrations | AutoMigrate (unsafe in prod) | goose, explicit |
457
+ | Debugging | "what query did it run?" requires logging | the query IS the source |
458
+ | Cancellation | spotty ctx support | first-class |
459
+ | Active development | Yes but with churn and breaking changes | sqlc is stable |
460
+
461
+ Existing gorm projects: leave them. New code: sqlc + pgx.
462
+
463
+ ---
464
+
465
+ ## Sources
466
+
467
+ - sqlc docs: https://docs.sqlc.dev
468
+ - pgx: https://github.com/jackc/pgx
469
+ - goose: https://github.com/pressly/goose
470
+ - testcontainers-go: https://golang.testcontainers.org
471
+ - pgx pool config: https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool#Config