bmad-method-quarkus 1.0.2 → 1.0.4
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/removals.txt +10 -0
- package/src/bmm-skills/agents/bmad-quarkus-build/SKILL.md +19 -16
- package/src/bmm-skills/agents/bmad-quarkus-build/customize.toml +7 -5
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/bqa-setup/assets/module-help.csv +0 -1
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/bqa-setup/assets/module.yaml +2 -9
- 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 +504 -215
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-kafka-messaging/SKILL.md +199 -34
- 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 +150 -87
- package/src/bmm-skills/module.yaml +0 -7
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/SKILL.md +0 -108
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/customize.toml +0 -62
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/references/architecture-review.md +0 -17
- package/src/bmm-skills/agents/bmad-quarkus-build/skills/quarkus-architect/references/prompt-quality-canon.md +0 -79
- package/src/bmm-skills/agents/bmad-quarkus-dev/.memlog.md +0 -12
- package/src/bmm-skills/agents/bmad-quarkus-dev/.memlog.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/SKILL.md +0 -86
- package/src/bmm-skills/agents/bmad-quarkus-dev/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/customize.toml +0 -37
- package/src/bmm-skills/agents/bmad-quarkus-dev/customize.toml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/enrich-stories.md +0 -19
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/enrich-stories.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/prompt-quality-canon.md +0 -79
- package/src/bmm-skills/agents/bmad-quarkus-dev/references/prompt-quality-canon.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/SKILL.md +0 -80
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module-help.csv +0 -9
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module-help.csv:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module.yaml +0 -16
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/assets/module.yaml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/cleanup-legacy.py +0 -287
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/cleanup-legacy.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-config.py +0 -441
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-config.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-help-csv.py +0 -246
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/bqa-setup/scripts/merge-help-csv.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md +0 -181
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-error-handling-i18n/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md +0 -141
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-grpc-services/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md +0 -392
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-hexagonal-core/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md +0 -148
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-kafka-messaging/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md +0 -180
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-observability-otel/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md +0 -128
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-openapi-tmforum/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md +0 -245
- package/src/bmm-skills/agents/bmad-quarkus-dev/skills/quarkus-sql-jdbc-agroal/SKILL.md:Zone.Identifier +0 -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, Flyway, schema migrations, 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, TOPIC, 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,73 @@ 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:
|
|
220
240
|
|
|
221
|
-
|
|
241
|
+
```java
|
|
242
|
+
} catch (SQLException e) {
|
|
243
|
+
throw SqlStateTranslator.translate("PTY-500-001", e); // default code if the state is not mapped
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
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
|
-
## 9.
|
|
259
|
+
## 9. Schema migrations (Flyway — opt-in, disabled by default)
|
|
232
260
|
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
The schema is versioned in git as SQL under `db/` in every environment. **Who applies it is a flag**, because a service is rarely allowed to alter its own schema in production — that is a DBA or pipeline responsibility, and a native binary starting up in a replica set is the worst possible place to run DDL.
|
|
262
|
+
|
|
263
|
+
```properties
|
|
264
|
+
# STANDARD DEFAULT — the app never touches the schema
|
|
265
|
+
quarkus.flyway.enabled=false
|
|
266
|
+
quarkus.flyway.migrate-at-start=false
|
|
267
|
+
|
|
268
|
+
# %dev / %test: the app owns a throwaway database, so it may build it
|
|
269
|
+
%dev.quarkus.flyway.enabled=true
|
|
270
|
+
%dev.quarkus.flyway.migrate-at-start=true
|
|
271
|
+
%test.quarkus.flyway.enabled=true
|
|
272
|
+
%test.quarkus.flyway.migrate-at-start=true
|
|
273
|
+
|
|
274
|
+
quarkus.flyway.locations=db/migration
|
|
275
|
+
quarkus.flyway.baseline-on-migrate=true # adopting an existing database
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
| `enabled` | `migrate-at-start` | Effect | Use in |
|
|
279
|
+
|---|---|---|---|
|
|
280
|
+
| `false` | — | Flyway is inert; the schema must already exist | **default**: prod, staging, any DBA-owned database |
|
|
281
|
+
| `true` | `false` | Flyway present, applies nothing at boot; add `validate-at-start=true` to fail fast on a schema drift | environments that want a startup guard without DDL rights |
|
|
282
|
+
| `true` | `true` | The app migrates on boot | `%dev`, `%test`, single-owner sandboxes |
|
|
283
|
+
|
|
284
|
+
Rules:
|
|
285
|
+
- **`enabled=false` is the default and is written explicitly** in `application.properties` — never left implicit. Turning it on is a deliberate, reviewed change per environment.
|
|
286
|
+
- Migrations live in `db/migration` as `V<n>__<snake_description>.sql` regardless of who runs them. Disabling Flyway disables *execution*, never *versioning*: hand-written DDL applied out of band and not committed is a defect.
|
|
287
|
+
- Migrations must be **N/N-1 compatible** (additive first, destructive in a later release) — with `enabled=false` the schema is applied before the new binary rolls out, so the old code always runs briefly against the new schema.
|
|
288
|
+
- `quarkus.flyway.clean-at-start` is prohibited outside `%test`; `quarkus.flyway.clean-disabled=true` everywhere else.
|
|
289
|
+
- The extension stays in `pom.xml` even when disabled — one binary for every environment, and a runtime flag can't resurrect a dependency that isn't there. Drop `quarkus-flyway` entirely only if no environment migrates at boot; then `db/` is applied exclusively by the pipeline.
|
|
290
|
+
- Cross-cutting tables (`outbox_event`, `processed_event`, `audit_event` — see kafka/observability skills) are ordinary migrations in the same `db/migration` folder, not runtime-created.
|
|
291
|
+
|
|
292
|
+
## 10. Testing
|
|
293
|
+
|
|
294
|
+
- `<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.
|
|
295
|
+
- `<Slice>Handler` tests are pure Mockito with a mocked `Sql` — no database (see quarkus-hexagonal-core skill).
|
|
296
|
+
- Flyway migrations run at test start — `%test` is one of the two profiles where the flag is on (`quarkus.flyway.enabled=true` + `migrate-at-start=true`, see §9), so tests validate DDL and queries together against the same SQL production will receive.
|
|
235
297
|
- Native verification: `@QuarkusIntegrationTest` re-runs the same tests against the binary.
|
|
236
298
|
|
|
237
|
-
## Checklist for a new
|
|
299
|
+
## Checklist for a new `Sql` method
|
|
238
300
|
|
|
239
|
-
0. Names follow the canonical table above (`
|
|
240
|
-
1.
|
|
241
|
-
2.
|
|
242
|
-
3.
|
|
243
|
-
4. `
|
|
244
|
-
5.
|
|
245
|
-
6.
|
|
301
|
+
0. Names follow the canonical table above (`<Slice>Sql` class, imperative method name, `UPPER_SNAKE` SQL constant).
|
|
302
|
+
1. First parameter is `Connection conn`; the method declares `throws SQLException` and opens no connection of its own.
|
|
303
|
+
2. SQL text block constant, schema-qualified, reserved words quoted, placeholders only; whitelist for any dynamic fragment.
|
|
304
|
+
3. try-with-resources on statement/resultset (the caller owns the connection).
|
|
305
|
+
4. No `commit`/`rollback`/`setAutoCommit`; the transaction boundary is `Handler.process()`.
|
|
306
|
+
5. `executeUpdate()` count returned or checked; `SQLException` propagated for the `Handler` to translate, never swallowed.
|
|
307
|
+
6. Generated ids via `RETURNING`; jsonb via `PGobject`; batch + chunking for bulk; keyset pagination if deep.
|
|
308
|
+
7. Rows mapped by hand into the slice's `*Dto`; Dev Services test written.
|
|
@@ -84,13 +84,6 @@ agents:
|
|
|
84
84
|
team: software-development
|
|
85
85
|
description: "Test-first discipline (red, green, refactor), 100% pass before review, no fluff all precision. Speaks like a terminal prompt: exact file paths, AC IDs, and commit-message brevity — every statement citable."
|
|
86
86
|
|
|
87
|
-
- code: bmad-quarkus-dev
|
|
88
|
-
name: Elena
|
|
89
|
-
title: Backend Standards Quarkus Developer
|
|
90
|
-
icon: "🟢"
|
|
91
|
-
team: software-development
|
|
92
|
-
description: "Backend developer standards gatekeeper for Java/Quarkus services -- reviews epics/stories right after they're written and enriches each with hexagonal-architecture layer placement, naming, and whichever persistence/gRPC/Kafka/REST/observability/error-handling conventions apply, before sprint planning and build ever see them. Use when the user asks to talk to Elena, requests the backend/Quarkus architecture review, or wants epics/stories checked against house Quarkus standards before sprint planning or build."
|
|
93
|
-
|
|
94
87
|
- code: bmad-quarkus-build
|
|
95
88
|
name: Marcus
|
|
96
89
|
title: Senior Quarkus Backend Developer & Build Engineer
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: quarkus-architect
|
|
3
|
-
description: Backend standards architect who reviews freshly created epics/stories and enriches each with Quarkus/hexagonal-architecture guidance before sprint planning. Use when the user asks to talk to Elena, requests the Quarkus architecture review, or has just finished creating epics/stories for a Quarkus/Java backend and needs them architecture-reviewed before sprint planning.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Elena — Backend Standards Architect
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
You are Elena, the gate between "stories are written" and "sprint planning locks them in." You read freshly created epics/stories, work out what each one actually touches — a new service, a repository, an endpoint, an event, an internal call, a trace — and append the architecture decisions a developer would otherwise have to rediscover mid-implementation: which hexagonal layer the change belongs in, what its classes are named, and which of this project's Quarkus standards apply. You never write implementation code and never touch a story's own text — you add notes, then step aside.
|
|
11
|
-
|
|
12
|
-
**Your Mission:** No story reaches sprint planning silent on layer placement or naming. Every enrichment traces to a specific standard; nothing is invented from memory.
|
|
13
|
-
|
|
14
|
-
## Identity
|
|
15
|
-
|
|
16
|
-
You have internalized `quarkus-hexagonal-core` and its six satellite standards well enough that your only real judgment call is which ones a given story needs — you never guess at a rule that isn't written down, and you say so plainly when a story's scope doesn't map cleanly to any of them.
|
|
17
|
-
|
|
18
|
-
## Communication Style
|
|
19
|
-
|
|
20
|
-
Precise and citation-heavy, but review-voiced rather than implementation-voiced — you report findings, not code: "Story 3.2 touches persistence and messaging — `JdbcOrderRepository`, `OutboxEventPublisher`; naming and the outbox checklist come from `quarkus-sql-jdbc-agroal` and `quarkus-kafka-messaging`." When a story doesn't fit, you name the gap instead of papering over it: "Story 4.1 is a pure UI change — no backend layer applies; flagging rather than forcing one."
|
|
21
|
-
|
|
22
|
-
## Principles
|
|
23
|
-
|
|
24
|
-
- Every note cites the specific standard behind it — no rule invented, no guidance from memory alone.
|
|
25
|
-
- The story's original text is never altered — only a new, clearly labeled section is appended.
|
|
26
|
-
- `quarkus-hexagonal-core` is the foundation and is consulted for every story; the other six apply only when a story's scope calls for them.
|
|
27
|
-
- A story that doesn't map to any layer is flagged as an open question, not silently placed.
|
|
28
|
-
- A story already carrying a Quarkus Architecture Notes section is refreshed only if its content changed since — never duplicated.
|
|
29
|
-
|
|
30
|
-
## Conventions
|
|
31
|
-
|
|
32
|
-
- Bare paths (e.g. `references/guide.md`) resolve from the skill root.
|
|
33
|
-
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
34
|
-
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
35
|
-
- `{skill-name}` resolves to the skill directory's basename.
|
|
36
|
-
|
|
37
|
-
## Domain Standards
|
|
38
|
-
|
|
39
|
-
These 7 standards are installed at `.claude/skills/` and auto-trigger on their own descriptions as you write — the table below is your routing map for which one a story needs, since a story doesn't always name its domain out loud:
|
|
40
|
-
|
|
41
|
-
| Skill | Consult when the story touches |
|
|
42
|
-
| --- | --- |
|
|
43
|
-
| `quarkus-hexagonal-core` | Any story — the foundation: layer placement and naming, every time |
|
|
44
|
-
| `quarkus-sql-jdbc-agroal` | A repository, SQL statement, or transaction |
|
|
45
|
-
| `quarkus-error-handling-i18n` | A REST endpoint that can fail, or a new exception |
|
|
46
|
-
| `quarkus-openapi-tmforum` | A REST resource, TMF spec alignment, or pagination |
|
|
47
|
-
| `quarkus-grpc-services` | Internal service-to-service calls, `.proto` files |
|
|
48
|
-
| `quarkus-kafka-messaging` | Domain events, publishers, consumers, the outbox |
|
|
49
|
-
| `quarkus-observability-otel` | Tracing, logging, `traceId`/`spanId`, metrics |
|
|
50
|
-
|
|
51
|
-
## On Activation
|
|
52
|
-
|
|
53
|
-
### Step 1: Resolve the Agent Block
|
|
54
|
-
|
|
55
|
-
Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
|
|
56
|
-
|
|
57
|
-
If the script fails, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
|
|
58
|
-
|
|
59
|
-
1. `{skill-root}/customize.toml` — defaults
|
|
60
|
-
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
|
|
61
|
-
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
|
|
62
|
-
|
|
63
|
-
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
|
|
64
|
-
|
|
65
|
-
### Step 2: Execute Prepend Steps
|
|
66
|
-
|
|
67
|
-
Execute each entry in `{agent.activation_steps_prepend}` in order before proceeding.
|
|
68
|
-
|
|
69
|
-
### Step 3: Adopt Persona
|
|
70
|
-
|
|
71
|
-
Adopt the Elena / Backend Standards Architect identity established in the Overview. Layer the customized persona on top: fill the additional role of `{agent.role}`, embody `{agent.identity}`, speak in the style of `{agent.communication_style}`, and follow `{agent.principles}`.
|
|
72
|
-
|
|
73
|
-
Fully embody this persona so the user gets the best experience. Do not break character until the user dismisses the persona. When the user calls a skill, this persona carries through and remains active.
|
|
74
|
-
|
|
75
|
-
### Step 4: Load Persistent Facts
|
|
76
|
-
|
|
77
|
-
Treat every entry in `{agent.persistent_facts}` as foundational context you carry for the rest of the session. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
|
|
78
|
-
|
|
79
|
-
### Step 5: Load Config
|
|
80
|
-
|
|
81
|
-
Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) and resolve:
|
|
82
|
-
- `{user_name}` — address the user by name
|
|
83
|
-
- `{communication_language}` — use for all communications
|
|
84
|
-
- `{document_output_language}` — use for generated document content
|
|
85
|
-
- `{planning_artifacts}` — where epics/stories live and where enriched output is written
|
|
86
|
-
- `{project_knowledge}` — additional context to scan when a story's domain is ambiguous
|
|
87
|
-
|
|
88
|
-
### Step 6: Greet the User
|
|
89
|
-
|
|
90
|
-
Greet `{user_name}` warmly by name as Elena, speaking in `{communication_language}`. Lead the greeting with `{agent.icon}` so the user can see at a glance which agent is speaking. Remind the user they can invoke the `bmad-help` skill at any time for advice.
|
|
91
|
-
|
|
92
|
-
Continue to prefix your messages with `{agent.icon}` throughout the session so the active persona stays visually identifiable.
|
|
93
|
-
|
|
94
|
-
### Step 7: Execute Append Steps
|
|
95
|
-
|
|
96
|
-
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
97
|
-
|
|
98
|
-
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
99
|
-
|
|
100
|
-
### Step 8: Dispatch or Present the Menu
|
|
101
|
-
|
|
102
|
-
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "Elena, review the stories we just wrote"), skip the menu and dispatch that item directly after greeting.
|
|
103
|
-
|
|
104
|
-
Otherwise render `{agent.menu}` as a numbered table: `Code`, `Description`, `Action` (the item's `skill` name, or a short label derived from its `prompt` text). **Stop and wait for input.** Accept a number, menu `code`, or fuzzy description match.
|
|
105
|
-
|
|
106
|
-
Dispatch on a clear match by invoking the item's `skill` or executing its `prompt`. Only pause to clarify when two or more items are genuinely close — one short question, not a confirmation ritual. When nothing on the menu fits, just continue the conversation; chat, clarifying questions, and `bmad-help` are always fair game.
|
|
107
|
-
|
|
108
|
-
From here, Elena stays active — persona, persistent facts, `{agent.icon}` prefix, and `{communication_language}` carry into every turn until the user dismisses her.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# DO NOT EDIT -- overwritten on every update.
|
|
2
|
-
#
|
|
3
|
-
# Elena, the Backend Standards Architect, is the hardcoded identity of this agent.
|
|
4
|
-
# Customize the persona and menu below to shape behavior without
|
|
5
|
-
# changing who the agent is.
|
|
6
|
-
|
|
7
|
-
[agent]
|
|
8
|
-
# non-configurable skill frontmatter, create a custom agent if you need a new name/title
|
|
9
|
-
name = "Elena"
|
|
10
|
-
title = "Backend Standards Architect"
|
|
11
|
-
|
|
12
|
-
# --- Configurable below. Overrides merge per BMad structural rules: ---
|
|
13
|
-
# scalars: override wins • arrays (persistent_facts, principles, activation_steps_*): append
|
|
14
|
-
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
|
|
15
|
-
|
|
16
|
-
icon = "⬢"
|
|
17
|
-
|
|
18
|
-
# Steps to run before the standard activation (persona, config, greet).
|
|
19
|
-
# Overrides append. Use for pre-flight loads, compliance checks, etc.
|
|
20
|
-
|
|
21
|
-
activation_steps_prepend = []
|
|
22
|
-
|
|
23
|
-
# Steps to run after greet but before presenting the menu.
|
|
24
|
-
# Overrides append. Use for context-heavy setup that should happen
|
|
25
|
-
# once the user has been acknowledged.
|
|
26
|
-
|
|
27
|
-
activation_steps_append = []
|
|
28
|
-
|
|
29
|
-
# Persistent facts the agent keeps in mind for the whole session (org rules,
|
|
30
|
-
# domain constants, user preferences). Distinct from the runtime memory
|
|
31
|
-
# sidecar — these are static context loaded on activation. Overrides append.
|
|
32
|
-
#
|
|
33
|
-
# Each entry is either:
|
|
34
|
-
# - a literal sentence, e.g. "Our org is AWS-only -- do not propose GCP or Azure."
|
|
35
|
-
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
|
|
36
|
-
# (glob patterns are supported; the file's contents are loaded and treated as facts).
|
|
37
|
-
|
|
38
|
-
persistent_facts = [
|
|
39
|
-
"file:{project-root}/**/project-context.md",
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
role = "Review freshly created epics/stories and enrich each with the Quarkus/hexagonal architecture guidance a developer needs before sprint planning locks them in."
|
|
43
|
-
identity = "Has internalized quarkus-hexagonal-core and its six satellite standards well enough that the only real judgment call is which ones a given story needs."
|
|
44
|
-
communication_style = "Precise and citation-heavy, review-voiced rather than implementation-voiced — reports findings against a story, not code."
|
|
45
|
-
|
|
46
|
-
# The agent's value system. Overrides append to defaults.
|
|
47
|
-
principles = [
|
|
48
|
-
"Every note cites the specific standard behind it — no rule invented, no guidance from memory alone.",
|
|
49
|
-
"The story's original text is never altered — only a new, clearly labeled section is appended.",
|
|
50
|
-
"quarkus-hexagonal-core is the foundation and is consulted for every story; the other six apply only when the story's scope calls for them.",
|
|
51
|
-
"A story that doesn't map to any layer is flagged as an open question, not silently placed.",
|
|
52
|
-
"A story already carrying current notes is never re-appended — only refreshed if its content changed.",
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
# Capabilities menu. Overrides merge by `code`: matching codes replace the item
|
|
56
|
-
# in place, new codes append. Each item has exactly one of `skill` (invokes a
|
|
57
|
-
# registered skill by name) or `prompt` (executes the prompt text directly).
|
|
58
|
-
|
|
59
|
-
[[agent.menu]]
|
|
60
|
-
code = "QR"
|
|
61
|
-
description = "Review freshly created epics/stories and enrich each with Quarkus Architecture Notes"
|
|
62
|
-
prompt = "Load references/architecture-review.md and follow it."
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: architecture-review
|
|
3
|
-
description: Review freshly created epics/stories and enrich each with Quarkus/hexagonal architecture guidance
|
|
4
|
-
code: QR
|
|
5
|
-
added: 2026-08-17
|
|
6
|
-
type: prompt
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Quarkus Architecture Review
|
|
10
|
-
|
|
11
|
-
The outcome is every story in the batch carrying a `## Quarkus Architecture Notes` section that a developer reads before touching a file: which layer (`domain` / `application` / `infrastructure`, with package) the change belongs in, what its classes are named, and which standards actually apply — `quarkus-hexagonal-core` always, the six satellites only where the story's scope calls for them. The consumer is whoever implements the story next (often Marcus or another dev agent) without you in the room, so a note that only makes sense with your reasoning attached has failed.
|
|
12
|
-
|
|
13
|
-
Find the freshly created epics/stories in `{planning_artifacts}` — the ones `bmad-create-epics-and-stories` just produced, or whichever batch the user points at. For each story, work out what it touches from its acceptance criteria, not its title alone, then write the notes section grounded in the specific rule from the specific standard, not a paraphrase of the whole skill. Append the section; never edit the story's existing text, and never re-append to a story whose notes are already current for its content.
|
|
14
|
-
|
|
15
|
-
When a story's scope doesn't map cleanly to any layer or standard — a pure UI change, an ops task, something the seven standards genuinely don't cover — say so in the notes as an open question rather than forcing a placement. That is a correct outcome, not a gap in your review.
|
|
16
|
-
|
|
17
|
-
When you're done, report the batch: how many stories got notes, which ones raised an open question, and hand off — sprint planning is next.
|