bmad-method-quarkus 1.0.1 → 1.0.3
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.
- package/package.json +1 -1
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-error-handling-i18n/SKILL.md +61 -27
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-grpc-services/SKILL.md +60 -41
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-hexagonal-core/SKILL.md +482 -213
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-kafka-messaging/SKILL.md +46 -29
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-observability-otel/SKILL.md +47 -31
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-openapi-tmforum/SKILL.md +42 -25
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-sql-jdbc-agroal/SKILL.md +115 -85
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md +61 -27
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md +60 -41
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md +482 -213
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md +46 -29
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md +47 -31
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md +42 -25
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md +115 -85
- package/src/commands/dev-all.md +105 -0
- package/src/commands/quarkus-all.md +156 -0
- package/tools/installer/ide/_config-driven.js +43 -0
- package/tools/installer/ide/platform-codes.yaml +2 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quarkus-sql-jdbc-agroal
|
|
3
|
-
description: Standard for executing SQL queries, updates, batches, and transactions in Quarkus WITHOUT Panache or any ORM — using the Agroal connection pool with plain JDBC (default) or the reactive Vert.x SQL client (justified cases only). Use this skill whenever the user writes or reviews ANY repository, DAO, SQL statement, SELECT/INSERT/UPDATE/DELETE, batch operation, transaction, pagination query, jsonb access, connection pool configuration, or mentions Agroal, JDBC, PreparedStatement, datasource, or "query the database" — all persistence code must follow these patterns,
|
|
3
|
+
description: Standard for executing SQL queries, updates, batches, and transactions in Quarkus WITHOUT Panache or any ORM — using the Agroal connection pool with plain JDBC (default) or the reactive Vert.x SQL client (justified cases only). Use this skill whenever the user writes or reviews ANY repository, DAO, SQL statement, SELECT/INSERT/UPDATE/DELETE, batch operation, transaction, pagination query, jsonb access, connection pool configuration, or mentions Agroal, JDBC, PreparedStatement, datasource, or "query the database" — all persistence code must follow these patterns. Data access lives in the slice `<Slice>Sql` class with `Connection` as the first parameter of every method (see quarkus-hexagonal-core skill); there are no repositories, DAOs or row-mapper classes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SQL Execution Standard: Agroal + Plain JDBC (No ORM)
|
|
7
7
|
|
|
8
8
|
Applies to any Quarkus backend project; project directives (CLAUDE.md, ADRs, explicit instructions) override these defaults where they conflict.
|
|
9
9
|
|
|
10
|
-
Persistence is explicit SQL through the Agroal pool. No Panache, no Hibernate, no reflection-based row mappers.
|
|
10
|
+
Persistence is explicit SQL through the Agroal pool. No Panache, no Hibernate, no reflection-based row mappers. Data access lives in the slice's `<Slice>Sql` class — the driven adapter of the vertical slice (see quarkus-hexagonal-core skill). There are no `*Repository` ports and no `Jdbc*` adapters: one `Sql` class per slice, called only by that slice's `Handler`.
|
|
11
11
|
|
|
12
12
|
One clarification to keep teams from chasing ghosts: **Agroal is a JDBC (blocking) pool** — there is no "reactive Agroal". The reactive path in Quarkus is the Vert.x SQL client (`quarkus-reactive-pg-client`) with its own pool. Default choice here is **Agroal + JDBC** (simpler, dominant skill base, works perfectly in native); reactive client only for measured hot paths with extreme concurrency (see §7).
|
|
13
13
|
|
|
@@ -42,131 +42,149 @@ Rules:
|
|
|
42
42
|
|
|
43
43
|
## 2. Injection and the golden resource pattern
|
|
44
44
|
|
|
45
|
-
Inject the pool, never raw drivers, never `DriverManager
|
|
45
|
+
Inject the pool, never raw drivers, never `DriverManager`. The `Sql` class holds the `DataSource` but **does not open connections** — the `Handler` opens exactly one per business operation and passes it in (see §3):
|
|
46
46
|
|
|
47
47
|
```java
|
|
48
48
|
@ApplicationScoped
|
|
49
|
-
public class
|
|
49
|
+
public class CreatePartyIndividualSql {
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
@Inject
|
|
52
|
+
DataSource dataSource; // injected for symmetry/health checks; connections come from the Handler
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Every
|
|
56
|
+
**Every method's first parameter is `Connection conn`.** That signature is the contract the `Handler` must honour exactly — same name, order, arity and types. Statements use **try-with-resources on PreparedStatement AND ResultSet** (the `Connection` is owned and closed by the caller). A missed close under load exhausts the pool and takes the native pod down:
|
|
57
57
|
|
|
58
58
|
```java
|
|
59
|
-
private static final String
|
|
60
|
-
SELECT id,
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
private static final String SELECT_PARTY_BY_ID = """
|
|
60
|
+
SELECT id, tenant_id, party_type, status, created_at
|
|
61
|
+
FROM customer.party
|
|
62
|
+
WHERE id = ?
|
|
63
|
+
AND tenant_id = ?
|
|
63
64
|
""";
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return rs.next() ? Optional.of(mapRow(rs)) : Optional.empty();
|
|
66
|
+
public PartyDto findPartyById(Connection conn, UUID id, String tenantId) throws SQLException {
|
|
67
|
+
try (PreparedStatement ps = conn.prepareStatement(SELECT_PARTY_BY_ID)) {
|
|
68
|
+
ps.setObject(1, id);
|
|
69
|
+
ps.setString(2, tenantId);
|
|
70
|
+
try (ResultSet rs = ps.executeQuery()) {
|
|
71
|
+
return rs.next() ? mapRow(rs) : null;
|
|
72
72
|
}
|
|
73
|
-
} catch (SQLException e) {
|
|
74
|
-
throw translate("user.find_by_id", e);
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
```
|
|
78
76
|
|
|
79
77
|
Absolute rules:
|
|
80
|
-
- SQL as `private static final String` text blocks. Never concatenate user input — `PreparedStatement` placeholders ALWAYS (SQL injection + plan cache).
|
|
78
|
+
- SQL as `private static final String` text blocks (comma-first concatenation is equally acceptable), schema-qualified. Never concatenate user input — `PreparedStatement` placeholders ALWAYS (SQL injection + plan cache).
|
|
79
|
+
- Quote reserved-word schemas and tables: `"order".party`. Verify the real schema name before writing the query.
|
|
81
80
|
- Dynamic WHERE clauses: build from a whitelist of column/operator constants, values still as placeholders.
|
|
82
|
-
-
|
|
81
|
+
- Methods declare `throws SQLException` and never catch it — the `Handler` translates it (§8). No `commit`, `rollback` or `setAutoCommit` here, ever.
|
|
82
|
+
- One private `mapRow(ResultSet) -> Dto` per `Sql` class. Rows map to the slice's own DTOs; there is no separate domain entity and no `*RowMapper` class. No reflection mappers: they break native and hide cost.
|
|
83
83
|
|
|
84
84
|
Naming (canonical rules in the quarkus-hexagonal-core skill):
|
|
85
85
|
|
|
86
86
|
| Artifact | Convention | Example |
|
|
87
87
|
|---|---|---|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
88
|
+
| Data access class | `${SERVICE_CLASS_PREFIX}Sql`, in the slice folder | `CreatePartyIndividualSql` |
|
|
89
|
+
| Method | imperative verb, matching the query | `insertParty`, `findPartyById`, `listPartiesByTenant` |
|
|
90
|
+
| SQL constant | `UPPER_SNAKE_CASE` matching the method, `private static final String` | `SELECT_PARTY_BY_ID`, `INSERT_PARTY` |
|
|
91
|
+
| Table / column | `snake_case`, singular table name, schema-qualified | `customer.party`, `created_at` |
|
|
92
|
+
| Row target | the slice's `*Dto` | `PartyDto` |
|
|
93
93
|
| Technical exception | `PersistenceException` + specific subtypes (§8) | `TransientPersistenceException` |
|
|
94
94
|
|
|
95
|
-
Never name
|
|
95
|
+
Never name this class `*Repository`, `*Dao`, `*Manager` or `*Service` — those suffixes are banned by the core skill's ArchUnit rules. An `Sql` class that grows an `if` encoding a business rule has swallowed logic that belongs in the `Handler`.
|
|
96
96
|
|
|
97
|
-
## 3. Transactions:
|
|
97
|
+
## 3. Transactions and connections: one of each, on `Handler.process()`
|
|
98
98
|
|
|
99
|
-
The unit of work is the business operation
|
|
99
|
+
The unit of work is the business operation, which in a slice is `process()`. The `Handler` opens **one** `Connection` in `execution()` and passes it to every `Sql` call:
|
|
100
100
|
|
|
101
101
|
```java
|
|
102
102
|
@ApplicationScoped
|
|
103
|
-
public class
|
|
104
|
-
|
|
105
|
-
@
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
public class CreatePartyIndividualHandler {
|
|
104
|
+
|
|
105
|
+
@Inject DataSource dataSource;
|
|
106
|
+
@Inject CreatePartyIndividualSql sql;
|
|
107
|
+
|
|
108
|
+
@Transactional // the ONLY @Transactional in the slice
|
|
109
|
+
public CreatePartyIndividualResponseDto process(CreatePartyIndividualRequestDto request) {
|
|
110
|
+
validate(request);
|
|
111
|
+
return getResult(execution(request));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private UUID execution(CreatePartyIndividualRequestDto request) {
|
|
115
|
+
try (Connection conn = dataSource.getConnection()) { // exactly one, for the whole operation
|
|
116
|
+
UUID partyId = sql.insertParty(conn, request.getTenantId(), "Individual",
|
|
117
|
+
"Active", request.getCreatedBy());
|
|
118
|
+
sql.insertIndividual(conn, partyId, request.getGivenName(), request.getFamilyName());
|
|
119
|
+
sql.insertOutboxEvent(conn, partyId, "party", EVENT_TYPE, payloadJson(partyId, request), traceparent());
|
|
120
|
+
return partyId;
|
|
121
|
+
} catch (SQLException e) {
|
|
122
|
+
throw SqlStateTranslator.translate("PTY-500-001", e); // unchecked -> container rolls back
|
|
123
|
+
}
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
```
|
|
114
127
|
|
|
115
|
-
|
|
128
|
+
**Why the explicit `Connection` parameter (performance):** inside an active JTA transaction Agroal returns the *same* enlisted connection for every `getConnection()` call, so both styles cost one physical connection. The difference shows on **read paths that carry no `@Transactional`** — there, a per-method `ds.getConnection()` takes *N* pool leases for *N* queries, each with its own acquisition and return. Passing `conn` makes every path, transactional or not, cost exactly one lease. It also keeps session state (`statement_timeout`, prepared-statement cache, temp tables) coherent across the operation.
|
|
116
129
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
130
|
+
- `@Transactional` goes on `process()` only — never on a private method (CDI interceptors don't fire on self-invocation, so the transaction would silently never open), never on the `Sql` class.
|
|
131
|
+
- **Read-only slices skip `@Transactional` entirely.** A single `SELECT`, or several reads that tolerate a non-repeatable view, are cheaper without a JTA transaction — still one `Connection`, opened and closed in `execution()`. Add the annotation the moment there is a write, or when several reads must see one snapshot.
|
|
132
|
+
- Runtime exceptions roll back by default; `BusinessException` extends `RuntimeException`, so throwing it rolls back — correct by construction. A checked `SQLException` would **not** roll back, which is why §8 translation is mandatory.
|
|
133
|
+
- Programmatic control when annotations don't fit (loops with per-item commit, batch jobs) — from a `common/` bean or a `*Job`, never from inside a slice `Handler`:
|
|
119
134
|
|
|
120
135
|
```java
|
|
121
|
-
QuarkusTransaction.requiringNew().timeout(30).run(() -> { ...
|
|
136
|
+
QuarkusTransaction.requiringNew().timeout(30).run(() -> { ...sql calls... });
|
|
122
137
|
```
|
|
123
138
|
|
|
124
|
-
- Never call `con.commit()`/`setAutoCommit()`
|
|
125
|
-
- Read-only single queries need no `@Transactional` (auto-commit read is fine); multi-read consistency or any write → transaction.
|
|
139
|
+
- Never call `con.commit()`/`rollback()`/`setAutoCommit()` anywhere. The core skill ships a CI `grep` gate for exactly these three tokens.
|
|
126
140
|
|
|
127
141
|
## 4. Updates, inserts, upserts, generated keys
|
|
128
142
|
|
|
129
143
|
```java
|
|
130
|
-
private static final String
|
|
131
|
-
INSERT INTO
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
private static final String INSERT_PARTY = """
|
|
145
|
+
INSERT INTO customer.party (tenant_id, party_type, status, created_by, updated_by)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?)
|
|
147
|
+
RETURNING id
|
|
134
148
|
""";
|
|
135
149
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
try (
|
|
139
|
-
ps.
|
|
140
|
-
ps.setString(2,
|
|
141
|
-
ps.setString(3,
|
|
142
|
-
ps.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
public UUID insertParty(Connection conn, String tenantId, String partyType,
|
|
151
|
+
String status, String createdBy) throws SQLException {
|
|
152
|
+
try (PreparedStatement ps = conn.prepareStatement(INSERT_PARTY)) {
|
|
153
|
+
ps.setString(1, tenantId);
|
|
154
|
+
ps.setString(2, partyType);
|
|
155
|
+
ps.setString(3, status);
|
|
156
|
+
ps.setString(4, createdBy);
|
|
157
|
+
ps.setString(5, createdBy);
|
|
158
|
+
try (ResultSet rs = ps.executeQuery()) {
|
|
159
|
+
rs.next();
|
|
160
|
+
return rs.getObject("id", UUID.class);
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
}
|
|
148
164
|
```
|
|
149
165
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
- Optimistic locking: `version` column, `UPDATE ... WHERE id = ? AND version = ?`; 0 rows → `StaleVersionException` (
|
|
153
|
-
- Upserts: `ON CONFLICT ... DO UPDATE` explicitly; never SELECT-then-INSERT races.
|
|
166
|
+
- **Generated ids**: when the PK has a database default (`id uuid DEFAULT customer.uuidv7() NOT NULL`), exclude `id` from the column list and never bind it. Read it back with `RETURNING id` + `executeQuery()` — preferred over `getGeneratedKeys()`.
|
|
167
|
+
- Check `executeUpdate()` counts — 0 rows on an expected UPDATE is a bug or a concurrency signal, not a success. The `Handler` decides what that means; `Sql` just returns the count.
|
|
168
|
+
- Optimistic locking: `version` column, `UPDATE ... WHERE id = ? AND version = ?`; 0 rows → the `Handler` throws `StaleVersionException` (code-carrying, in `common/exception`) with the slice's conflict code → 409 via its dedicated mapper (see quarkus-error-handling-i18n skill). It is not SQLState-derived — no `SQLException` occurs, so `SqlStateTranslator` never sees it; the `Handler` checks the update count. Do NOT name it `ConcurrentModificationException` — it shadows `java.util.ConcurrentModificationException` and an accidental import turns the 409 mapping into a 500.
|
|
169
|
+
- Upserts: `ON CONFLICT ... DO UPDATE` explicitly; never SELECT-then-INSERT races. A unique-violation (`23505`) surfacing as a `SQLException` is translated by the `Handler` (§8), not swallowed here.
|
|
154
170
|
|
|
155
171
|
## 5. Batches, pagination, jsonb
|
|
156
172
|
|
|
157
173
|
**Batch** (bulk inserts/updates — outbox relays, imports):
|
|
158
174
|
|
|
159
175
|
```java
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
public int[] insertPartiesBatch(Connection conn, List<PartyDto> items) throws SQLException {
|
|
177
|
+
try (PreparedStatement ps = conn.prepareStatement(INSERT_PARTY)) {
|
|
178
|
+
for (PartyDto item : items) {
|
|
179
|
+
bind(ps, item);
|
|
180
|
+
ps.addBatch();
|
|
181
|
+
}
|
|
182
|
+
return ps.executeBatch(); // add ?reWriteBatchedInserts=true to the JDBC URL (Postgres)
|
|
164
183
|
}
|
|
165
|
-
ps.executeBatch(); // add ?reWriteBatchedInserts=true to the JDBC URL (Postgres)
|
|
166
184
|
}
|
|
167
185
|
```
|
|
168
186
|
|
|
169
|
-
Chunk batches (500–1000) inside long jobs; combine with `QuarkusTransaction` per chunk
|
|
187
|
+
Chunk batches (500–1000) inside long jobs; combine with `QuarkusTransaction` per chunk, driven from a `*Job` in `common/`, not from a slice `Handler`.
|
|
170
188
|
|
|
171
189
|
**Pagination**: keyset over OFFSET for anything user-facing/deep:
|
|
172
190
|
|
|
@@ -192,15 +210,17 @@ Read side: `rs.getString("context")` then parse. Index jsonb lookups you actuall
|
|
|
192
210
|
To kill boilerplate, ONE small internal helper class (~50 lines) per service or shared lib is the sanctioned maximum:
|
|
193
211
|
|
|
194
212
|
```java
|
|
195
|
-
public final class Jdbc {
|
|
213
|
+
public final class Jdbc { // common/util — the one sanctioned exception to the banned-suffix rule
|
|
196
214
|
@FunctionalInterface public interface RowMapper<T> { T map(ResultSet rs) throws SQLException; }
|
|
197
215
|
|
|
198
|
-
public static <T> Optional<T> queryOne(
|
|
199
|
-
public static <T> List<T> queryList(
|
|
200
|
-
public static int update(
|
|
216
|
+
public static <T> Optional<T> queryOne(Connection conn, String sql, RowMapper<T> m, Object... params) throws SQLException { ... }
|
|
217
|
+
public static <T> List<T> queryList(Connection conn, String sql, RowMapper<T> m, Object... params) throws SQLException { ... }
|
|
218
|
+
public static int update(Connection conn, String sql, Object... params) throws SQLException { ... }
|
|
201
219
|
}
|
|
202
220
|
```
|
|
203
221
|
|
|
222
|
+
Note the signatures take `Connection`, not `DataSource` — the helper must not open connections either, or it defeats the one-lease-per-operation rule of §3.
|
|
223
|
+
|
|
204
224
|
- Pure delegation to the try-with-resources pattern above; no reflection, no annotations, no SQL generation. If someone proposes adding criteria builders or entity mapping to it, that's an ORM growing back — reject.
|
|
205
225
|
- The class is named `Jdbc` — a namespace, not `JdbcUtils`/`JdbcHelper`. The banned-suffix ArchUnit rule (see quarkus-hexagonal-core skill) exists precisely to stop this class from becoming a junk drawer.
|
|
206
226
|
- jOOQ (code-gen, type-safe SQL) MAY be evaluated as an alternative via formal ADR; MyBatis/Hibernate remain excluded.
|
|
@@ -216,30 +236,40 @@ JDBC blocks. Never run it on the event loop:
|
|
|
216
236
|
|
|
217
237
|
## 8. SQLException translation
|
|
218
238
|
|
|
219
|
-
|
|
239
|
+
`Sql` methods propagate `SQLException`; the `Handler` catches it once, in `execution()`, and translates it through `SqlStateTranslator` (`common/error`) so nothing checked ever escapes `process()` — a checked exception would not trigger the container rollback:
|
|
240
|
+
|
|
241
|
+
```java
|
|
242
|
+
} catch (SQLException e) {
|
|
243
|
+
throw SqlStateTranslator.translate("PTY-500-001", e); // default code if the state is not mapped
|
|
244
|
+
}
|
|
245
|
+
```
|
|
220
246
|
|
|
221
|
-
| SQLState | Meaning |
|
|
247
|
+
| SQLState | Meaning | Resulting exception (all unchecked) |
|
|
222
248
|
|---|---|---|
|
|
223
|
-
| `23505` | unique violation |
|
|
224
|
-
| `23503` | FK violation |
|
|
249
|
+
| `23505` | unique violation | `BusinessException` with the slice's conflict code (→ 409) |
|
|
250
|
+
| `23503` | FK violation | `BusinessException` with the slice's integrity code (→ 409/422) |
|
|
225
251
|
| `40001` / `40P01` | serialization failure / deadlock | retryable `TransientPersistenceException` |
|
|
226
252
|
| `57014` | statement timeout/cancel | `PersistenceTimeoutException` |
|
|
227
253
|
| other | infrastructure failure | `PersistenceException(code, e)` (→ 500) |
|
|
228
254
|
|
|
255
|
+
Every resulting code is registered in `ErrorCatalog` and present in all locale bundles (see quarkus-error-handling-i18n skill). The client sees a localized message; the `SQLException` stays in the logs.
|
|
256
|
+
|
|
229
257
|
Set `statement_timeout` (session or per-datasource via `quarkus.datasource.jdbc.additional-jdbc-properties.options=-c statement_timeout=5000`) so runaway queries fail fast instead of holding pool connections.
|
|
230
258
|
|
|
231
259
|
## 9. Testing
|
|
232
260
|
|
|
233
|
-
-
|
|
261
|
+
- `<Slice>Sql` tests: `@QuarkusTest` + Dev Services (Testcontainers Postgres starts automatically — no config). Real SQL against real Postgres; never H2 (dialect lies). Obtain a `Connection` in the test and pass it in, exactly as the `Handler` does.
|
|
262
|
+
- `<Slice>Handler` tests are pure Mockito with a mocked `Sql` — no database (see quarkus-hexagonal-core skill).
|
|
234
263
|
- Flyway migrations run at test start (`quarkus.flyway.migrate-at-start=true`) — tests validate DDL and queries together.
|
|
235
264
|
- Native verification: `@QuarkusIntegrationTest` re-runs the same tests against the binary.
|
|
236
265
|
|
|
237
|
-
## Checklist for a new
|
|
266
|
+
## Checklist for a new `Sql` method
|
|
238
267
|
|
|
239
|
-
0. Names follow the canonical table above (`
|
|
240
|
-
1.
|
|
241
|
-
2.
|
|
242
|
-
3.
|
|
243
|
-
4. `
|
|
244
|
-
5.
|
|
245
|
-
6.
|
|
268
|
+
0. Names follow the canonical table above (`<Slice>Sql` class, imperative method name, `UPPER_SNAKE` SQL constant).
|
|
269
|
+
1. First parameter is `Connection conn`; the method declares `throws SQLException` and opens no connection of its own.
|
|
270
|
+
2. SQL text block constant, schema-qualified, reserved words quoted, placeholders only; whitelist for any dynamic fragment.
|
|
271
|
+
3. try-with-resources on statement/resultset (the caller owns the connection).
|
|
272
|
+
4. No `commit`/`rollback`/`setAutoCommit`; the transaction boundary is `Handler.process()`.
|
|
273
|
+
5. `executeUpdate()` count returned or checked; `SQLException` propagated for the `Handler` to translate, never swallowed.
|
|
274
|
+
6. Generated ids via `RETURNING`; jsonb via `PGobject`; batch + chunking for bulk; keyset pagination if deep.
|
|
275
|
+
7. Rows mapped by hand into the slice's `*Dto`; Dev Services test written.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Batch-run the BMAD dev-story workflow across all pending stories, with per-epic checkpoints and optional commit-per-story.
|
|
3
|
+
argument-hint: "[commit] [skip-review] [checkpoint] [epic=<id>] e.g. `/dev-all` or `/dev-all commit skip-review` or `/dev-all checkpoint epic=3`"
|
|
4
|
+
allowed-tools: Read, Edit, Write, Bash(git status:*), Bash(git add:*), Bash(git commit:*), Bash(git log:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Batch develop BMAD stories
|
|
8
|
+
|
|
9
|
+
You are running an **unattended batch** of the BMAD dev-story workflow. Work through
|
|
10
|
+
stories one at a time (preserving per-story context), but do **not** stop for
|
|
11
|
+
confirmation between stories. Only the per-epic checkpoint below is a hard stop.
|
|
12
|
+
|
|
13
|
+
## The BMAD build cycle — run these EXACT commands per story
|
|
14
|
+
|
|
15
|
+
For each story, run the DEV agent's three-step cycle, in order:
|
|
16
|
+
|
|
17
|
+
1. `bmad-create-story` — create the story file from the epic
|
|
18
|
+
2. `bmad-dev-story` — implement the story
|
|
19
|
+
3. `bmad-code-review` — quality validation
|
|
20
|
+
|
|
21
|
+
Invoke these real BMAD workflows — do NOT improvise your own create/implement/review
|
|
22
|
+
steps. They carry the architecture context and dev-notes inheritance that keep output
|
|
23
|
+
coherent.
|
|
24
|
+
|
|
25
|
+
> **Note on "fresh chats":** BMAD's docs say to repeat this cycle *in a fresh chat per
|
|
26
|
+
> story*, so each story gets a clean context window. This batch command runs multiple
|
|
27
|
+
> stories in one session, which sacrifices that guarantee for hands-off throughput.
|
|
28
|
+
> That's an acceptable trade for a small/well-understood epic. For a large epic, prefer
|
|
29
|
+
> running the cycle manually per story, or use the per-epic checkpoint below to resume
|
|
30
|
+
> each epic in a fresh session.
|
|
31
|
+
|
|
32
|
+
- **State file:** `sprint-status.yaml` (source of truth for story status)
|
|
33
|
+
- **Epic definitions:** `epics.md`
|
|
34
|
+
|
|
35
|
+
## Arguments
|
|
36
|
+
|
|
37
|
+
Parse `$ARGUMENTS`:
|
|
38
|
+
- `commit` → enable **commit-per-story** (off by default). When enabled, after a
|
|
39
|
+
story passes review, run `git add -A && git commit` with a message like
|
|
40
|
+
`feat(<epic>): <story-id> <short title>`.
|
|
41
|
+
- `epic=<id>` → restrict the batch to a single epic.
|
|
42
|
+
- `checkpoint` → **pause** for confirmation at each epic boundary (off by default —
|
|
43
|
+
by default the run is fully unattended and does NOT stop between epics).
|
|
44
|
+
- `skip-review` → skip the `bmad-code-review` step. The per-story cycle becomes
|
|
45
|
+
`bmad-create-story` → `bmad-dev-story` only.
|
|
46
|
+
|
|
47
|
+
**Default (no `epic=` given): process ALL epics**, in order, running every not-done
|
|
48
|
+
story across the whole project **without stopping between epics**. So bare `/dev-all`
|
|
49
|
+
develops everything unattended; `/dev-all epic=2` develops only epic 2; add
|
|
50
|
+
`checkpoint` if you want a pause + summary before each new epic.
|
|
51
|
+
|
|
52
|
+
## Loop
|
|
53
|
+
|
|
54
|
+
1. **Read `sprint-status.yaml`.** Build the ordered list of stories whose status is
|
|
55
|
+
NOT `done` (e.g. `pending`, `ready`, `in-progress`, `blocked` should be surfaced —
|
|
56
|
+
see step 6 for blocked). Respect the `epic=` filter if present.
|
|
57
|
+
|
|
58
|
+
2. **If the list is empty**, report "All stories already done" and stop.
|
|
59
|
+
|
|
60
|
+
3. **For each story, in order:**
|
|
61
|
+
a. Announce which story you're starting (id + title).
|
|
62
|
+
b. Run the BMAD cycle for that story, in order:
|
|
63
|
+
`bmad-create-story` → `bmad-dev-story` → `bmad-code-review`.
|
|
64
|
+
**If `skip-review` is set, omit `bmad-code-review`** (run create → dev only).
|
|
65
|
+
c. If review passed (or `skip-review` is set and implementation completed without
|
|
66
|
+
error), set that story's status to `done` in `sprint-status.yaml`.
|
|
67
|
+
d. **If commit-per-story is enabled**, stage and commit (see Arguments). Keep each
|
|
68
|
+
commit scoped to that one story so a bad story can be rolled back in isolation.
|
|
69
|
+
e. Continue to the next story WITHOUT asking for confirmation.
|
|
70
|
+
|
|
71
|
+
4. **Per-epic boundary.** When you finish the last story of an epic and more epics
|
|
72
|
+
remain, post a short summary: epic name, stories completed/blocked, tests added,
|
|
73
|
+
files touched, and anything that looked off (scope creep, tests worked around,
|
|
74
|
+
architectural surprises).
|
|
75
|
+
- **Default: do NOT stop.** Print the summary and immediately continue into the
|
|
76
|
+
next epic without asking for confirmation.
|
|
77
|
+
- **Only if `checkpoint` is set:** stop after the summary and wait for my go-ahead
|
|
78
|
+
before starting the next epic.
|
|
79
|
+
|
|
80
|
+
5. **Context hygiene on long runs.** If you notice your context getting large mid-run,
|
|
81
|
+
say so in the nearest epic-boundary summary and recommend I resume the remaining
|
|
82
|
+
epics in a fresh session. Don't silently push through degraded context. (This is the
|
|
83
|
+
main risk of a fully unattended all-epics run — flag it rather than hide it.)
|
|
84
|
+
|
|
85
|
+
6. **Error / blocked handling.** If a story fails `bmad-code-review` twice, or you hit an error you
|
|
86
|
+
can't resolve, or a dependency is missing:
|
|
87
|
+
- Set its status to `blocked` in `sprint-status.yaml` with a one-line reason.
|
|
88
|
+
- Do NOT mark it `done`. Do NOT keep retrying indefinitely.
|
|
89
|
+
- Skip it and continue with the remaining stories, then report all blocked stories
|
|
90
|
+
in the next checkpoint / final summary.
|
|
91
|
+
|
|
92
|
+
## Final summary
|
|
93
|
+
|
|
94
|
+
When every eligible story is `done` or `blocked`, stop and report:
|
|
95
|
+
- Count done vs. blocked, grouped by epic
|
|
96
|
+
- List of blocked stories with reasons
|
|
97
|
+
- Commits made (if commit-per-story was on)
|
|
98
|
+
- Any follow-ups I should handle manually
|
|
99
|
+
|
|
100
|
+
## Guardrails
|
|
101
|
+
|
|
102
|
+
- Never mark a story `done` unless its review actually passed.
|
|
103
|
+
- Never force-push or rewrite history. Commits only, and only when `commit` is set.
|
|
104
|
+
- If `sprint-status.yaml` and `epics.md` disagree about which stories exist, trust
|
|
105
|
+
`sprint-status.yaml` for status but flag the mismatch in the summary.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Batch-build BMAD stories through Marcus (bmad-quarkus-build) — one skill invocation per story instead of the create-story/dev-story/code-review cycle.
|
|
3
|
+
argument-hint: "[commit] [checkpoint] [review] [force] [dry-run] [epic=<id>] [limit=<n>] e.g. `/quarkus-all` or `/quarkus-all commit epic=20` or `/quarkus-all dry-run`"
|
|
4
|
+
allowed-tools: Read, Edit, Write, Bash(git status:*), Bash(git add:*), Bash(git commit:*), Bash(git log:*), Bash(git diff:*), Bash(./mvnw:*), Bash(mvn:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Batch build BMAD stories with the Quarkus agent
|
|
8
|
+
|
|
9
|
+
You are running an **unattended batch** of Quarkus story implementation. Work through
|
|
10
|
+
stories one at a time (preserving per-story context), but do **not** stop for
|
|
11
|
+
confirmation between stories. Only the per-epic checkpoint below is a hard stop.
|
|
12
|
+
|
|
13
|
+
## The cycle — ONE command per story
|
|
14
|
+
|
|
15
|
+
For each story, invoke exactly **one** skill:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
bmad-quarkus-build → "Marcus, implement story <story-key>"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That is the whole per-story cycle. **Do NOT run `bmad-create-story`, `bmad-dev-story`,
|
|
22
|
+
or `bmad-code-review` as separate steps** — Marcus owns the full loop (story
|
|
23
|
+
preparation → red-green-refactor implementation → self-review against the ACs) and
|
|
24
|
+
routes into `bmad-build` plus whichever of the 7 Quarkus domain standards the story
|
|
25
|
+
touches (`quarkus-hexagonal-core`, `quarkus-sql-jdbc-agroal`,
|
|
26
|
+
`quarkus-error-handling-i18n`, `quarkus-openapi-tmforum`, `quarkus-grpc-services`,
|
|
27
|
+
`quarkus-kafka-messaging`, `quarkus-observability-otel`). Splitting the cycle back into
|
|
28
|
+
three commands loses that routing and the native-image/hexagonal review that comes with it.
|
|
29
|
+
|
|
30
|
+
Pass the story key in the invocation so Marcus dispatches directly instead of rendering
|
|
31
|
+
his menu (his activation Step 8 skips the menu when the intent is already named).
|
|
32
|
+
|
|
33
|
+
Marcus stays active across stories once activated — do not re-run his activation
|
|
34
|
+
greeting for every story, just hand him the next story key.
|
|
35
|
+
|
|
36
|
+
- **State file:** `_bmad-output/implementation-artifacts/sprint-status.yaml` (source of truth for story status)
|
|
37
|
+
- **Story files:** `_bmad-output/implementation-artifacts/<story-key>.md`
|
|
38
|
+
- **Epic definitions:** `governance/_bmad-output/planning-artifacts/epics.md`
|
|
39
|
+
|
|
40
|
+
> **Note on "fresh chats":** BMAD's docs say to repeat the build cycle *in a fresh chat
|
|
41
|
+
> per story*, so each story gets a clean context window. This batch command runs multiple
|
|
42
|
+
> stories in one session, which sacrifices that guarantee for hands-off throughput. That's
|
|
43
|
+
> an acceptable trade for a small/well-understood epic. For a large epic, prefer running
|
|
44
|
+
> `bmad-quarkus-build` manually per story, or use the per-epic checkpoint below to resume
|
|
45
|
+
> each epic in a fresh session.
|
|
46
|
+
|
|
47
|
+
## Arguments
|
|
48
|
+
|
|
49
|
+
Parse `$ARGUMENTS`:
|
|
50
|
+
- `commit` → enable **commit-per-story** (off by default). After a story passes, run
|
|
51
|
+
`git add -A && git commit` with a message like `feat(epic-20): 20-1 create a digital identity`.
|
|
52
|
+
- `epic=<id>` → restrict the batch to a single epic (`epic=20`, `epic=256`).
|
|
53
|
+
- `limit=<n>` → stop after `n` stories have been completed this run.
|
|
54
|
+
- `checkpoint` → **pause** for confirmation at each epic boundary (off by default — by
|
|
55
|
+
default the run is fully unattended and does NOT stop between epics).
|
|
56
|
+
- `review` → add a separate `bmad-code-review` pass **after** Marcus finishes each story.
|
|
57
|
+
Off by default: Marcus already reviews his own output, and this is the one case where a
|
|
58
|
+
second command per story is intentional (fresh-context adversarial review).
|
|
59
|
+
- `force` → override the build-eligibility gate below. Requires that I said so explicitly;
|
|
60
|
+
never infer it.
|
|
61
|
+
- `dry-run` → print the ordered story list, the eligibility verdict per epic, and stop.
|
|
62
|
+
Change nothing. Use this first on any wide run.
|
|
63
|
+
|
|
64
|
+
**Default (no `epic=` given): process all *build-eligible* epics**, in order, running every
|
|
65
|
+
not-done story **without stopping between epics**.
|
|
66
|
+
|
|
67
|
+
## Build eligibility — read this BEFORE touching any story
|
|
68
|
+
|
|
69
|
+
`sprint-status.yaml` carries BUILD-STATUS markers as trailing comments on epic headings.
|
|
70
|
+
**They are not statuses.** A `backlog` status on a marked epic means NOT STARTED — it does
|
|
71
|
+
**not** mean cleared to start.
|
|
72
|
+
|
|
73
|
+
- `[V1]` → **build-ready.** Epics 20, 21, 22, 23, 24, 26, 28, 30, 31.
|
|
74
|
+
- `[BUILD-PROHIBITED]` → **skip.** Epics 32, 33, 34, 35, 36 (36 also reassigned to BC-05).
|
|
75
|
+
Feature files exist, but building is forbidden — ADR-024 requires a P2→P10 re-run, never
|
|
76
|
+
a local fix.
|
|
77
|
+
- `[BUILD-PROHIBITED in part]` → **skip epic 27** unless I name a specific permitted story.
|
|
78
|
+
- `[UNCLASSIFIED]` → **skip.** Epics 25, 29, 37–45, 262, 263. Per OI-50 the build decision
|
|
79
|
+
belongs to the Product Owner + Architect Lead, not to us.
|
|
80
|
+
- `[BLOCKED]` → **skip.** Epics 256, 263.
|
|
81
|
+
|
|
82
|
+
Re-read the markers from the file rather than trusting this list — the file is the source of
|
|
83
|
+
truth and its scope can change. If `epic=<id>` names a non-eligible epic, refuse and say
|
|
84
|
+
which marker blocks it; only `force` overrides, and even then print the marker you are
|
|
85
|
+
overriding.
|
|
86
|
+
|
|
87
|
+
The whole file is scoped to **BC-01 only** (29 of the 263 epics in `epics.md`). The other
|
|
88
|
+
epics are absent by design — never treat their absence as done or as something to go build.
|
|
89
|
+
|
|
90
|
+
## Loop
|
|
91
|
+
|
|
92
|
+
1. **Read `sprint-status.yaml`.** Build the ordered list of stories whose status is NOT
|
|
93
|
+
`done`. Legal story statuses in this file are `backlog`, `ready-for-dev`, `in-progress`,
|
|
94
|
+
`review`, `done`. Apply the eligibility gate, then the `epic=` filter, then `limit=`.
|
|
95
|
+
|
|
96
|
+
2. **If the list is empty**, report "No eligible stories pending" and stop.
|
|
97
|
+
|
|
98
|
+
3. **For each story, in order:**
|
|
99
|
+
a. Announce which story you're starting (key + title + epic).
|
|
100
|
+
b. Invoke `bmad-quarkus-build` with that story key. One invocation. Nothing else.
|
|
101
|
+
c. If `review` is set, run `bmad-code-review` on the resulting diff.
|
|
102
|
+
d. On success — ACs met, tests written test-first and passing — set the story's status to
|
|
103
|
+
`done` in `sprint-status.yaml` and update `last_updated`.
|
|
104
|
+
e. If it's the epic's last story and all its stories are now `done`, set `epic-<id>: done`.
|
|
105
|
+
Set `epic-<id>: in-progress` when starting the epic's first story.
|
|
106
|
+
f. **If commit-per-story is enabled**, stage and commit. Keep each commit scoped to that
|
|
107
|
+
one story so a bad story can be rolled back in isolation.
|
|
108
|
+
g. Continue to the next story WITHOUT asking for confirmation.
|
|
109
|
+
|
|
110
|
+
4. **Per-epic boundary.** When you finish the last story of an epic and more epics remain,
|
|
111
|
+
post a short summary: epic, stories completed/held, tests added, files touched, and
|
|
112
|
+
anything that looked off (scope creep, tests worked around, hexagonal or native-image
|
|
113
|
+
surprises, a domain standard that contradicted the story).
|
|
114
|
+
- **Default: do NOT stop.** Print the summary and continue into the next epic.
|
|
115
|
+
- **Only if `checkpoint` is set:** stop after the summary and wait for my go-ahead.
|
|
116
|
+
|
|
117
|
+
5. **Context hygiene on long runs.** If your context is getting large mid-run, say so in the
|
|
118
|
+
nearest epic-boundary summary and recommend resuming the remaining epics in a fresh
|
|
119
|
+
session. Don't silently push through degraded context — that's the main risk of a fully
|
|
120
|
+
unattended all-epics run.
|
|
121
|
+
|
|
122
|
+
6. **Error / held handling.** If Marcus can't complete a story, a second attempt fails, or a
|
|
123
|
+
dependency is missing:
|
|
124
|
+
- **Do not invent a `blocked` status** — this file's state machine has no such value and
|
|
125
|
+
the header says so explicitly. Leave the story at its current status and append a
|
|
126
|
+
trailing comment on its line: `# HELD 2026-xx-xx: <one-line reason>`.
|
|
127
|
+
- Do NOT mark it `done`. Do NOT retry indefinitely (two attempts max).
|
|
128
|
+
- Skip it and continue, then report all held stories in the next checkpoint / final summary.
|
|
129
|
+
|
|
130
|
+
## Final summary
|
|
131
|
+
|
|
132
|
+
When every eligible story is `done` or held, stop and report:
|
|
133
|
+
- Count done vs. held, grouped by epic
|
|
134
|
+
- Held stories with reasons
|
|
135
|
+
- Epics skipped by the eligibility gate, with the marker that skipped them
|
|
136
|
+
- Commits made (if `commit` was on)
|
|
137
|
+
- Any follow-ups I should handle manually
|
|
138
|
+
|
|
139
|
+
## Guardrails
|
|
140
|
+
|
|
141
|
+
- **One `bmad-quarkus-build` invocation per story.** Never decompose it back into
|
|
142
|
+
`bmad-create-story` → `bmad-dev-story` → `bmad-code-review`.
|
|
143
|
+
- Never mark a story `done` unless its tests actually pass and every AC is met. ACs in these
|
|
144
|
+
stories are verbatim Gherkin — do not edit, reword, or renumber them.
|
|
145
|
+
- Never start a story in a `[BUILD-PROHIBITED]`, `[BUILD-PROHIBITED in part]`,
|
|
146
|
+
`[UNCLASSIFIED]`, or `[BLOCKED]` epic without explicit `force`.
|
|
147
|
+
- FR-1243 forbids marking anything Done while a blocker is in scope, and the 2026-08-13
|
|
148
|
+
readiness report reads NEEDS WORK. This command asserts no gate verdict and closes no open
|
|
149
|
+
item — it only advances story statuses it actually completed.
|
|
150
|
+
- Never force-push or rewrite history. Commits only, and only when `commit` is set.
|
|
151
|
+
- `sprint-status.yaml` has a stale `story_location:` pointing at a macOS path
|
|
152
|
+
(`/Users/diego/...`). Trust the real repo path `_bmad-output/implementation-artifacts`;
|
|
153
|
+
flag the mismatch in the final summary rather than editing it mid-run.
|
|
154
|
+
- If `sprint-status.yaml` and `epics.md` disagree about which stories exist, trust
|
|
155
|
+
`sprint-status.yaml` for status and flag the mismatch. Declared-vs-actual epic count gaps
|
|
156
|
+
(the file notes one) are a PM finding — report, don't reconcile.
|