@eventmodelers/cli 2.0.1 → 2.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.
Files changed (80) hide show
  1. package/README.md +27 -9
  2. package/cli.js +369 -36
  3. package/lib/build-kits.js +23 -0
  4. package/lib/scaffold-model.js +71 -0
  5. package/lib/scaffold.js +224 -0
  6. package/lib/supervise.js +60 -0
  7. package/lib/ui-baseline.js +123 -0
  8. package/package.json +1 -1
  9. package/shared/build-kit/lib/build-log-share.js +149 -0
  10. package/shared/build-kit/lib/events-view.js +18 -3
  11. package/shared/build-kit/lib/ralph.js +27 -16
  12. package/shared/build-kit/lib/slice-events.js +42 -2
  13. package/shared/build-kit/lib/slice-info.js +52 -0
  14. package/shared/build-kit/lib/slice-run.js +2 -2
  15. package/shared/build-kit/lib/slice-state.js +6 -4
  16. package/shared/scaffold/LICENSE-em-scaffold +21 -0
  17. package/shared/scaffold/supabase/generate.mjs +290 -0
  18. package/shared/scaffold/supabase/template/scripts/create-user.mjs.tmpl +29 -0
  19. package/shared/scaffold/supabase/template/scripts/dev.mjs.tmpl +145 -0
  20. package/shared/scaffold/supabase/template/scripts/write-env.mjs.tmpl +44 -0
  21. package/shared/scaffold/supabase/template/server.ts.tmpl +168 -0
  22. package/shared/scaffold/supabase/template/src/common/extraEndpoints.ts.tmpl +5 -0
  23. package/shared/scaffold/supabase/template/src/common/notImplemented.test.ts.tmpl +58 -0
  24. package/shared/scaffold/supabase/template/src/common/notImplemented.ts.tmpl +29 -0
  25. package/shared/scaffold/supabase/template/supabase/migrations/_V0__supabase_stubs.sql.tmpl +34 -0
  26. package/shared/scaffold/supabase/template/web/src/App.tsx.tmpl +38 -0
  27. package/shared/scaffold/supabase/template/web/src/app/Layout.tsx.tmpl +48 -0
  28. package/shared/scaffold/supabase/template/web/src/app/NotFound.tsx.tmpl +12 -0
  29. package/shared/scaffold/supabase/template/web/src/app/SignIn.tsx.tmpl +42 -0
  30. package/shared/scaffold/supabase/template/web/src/app/TimelinePage.tsx.tmpl +73 -0
  31. package/shared/scaffold/supabase/template/web/src/app/pages.ts.tmpl +26 -0
  32. package/shared/scaffold/supabase/template/web/src/app/skeleton.css.tmpl +25 -0
  33. package/shared/scaffold/supabase/template/web/src/lib/NotBuilt.tsx.tmpl +12 -0
  34. package/shared/scaffold/supabase/template/web/src/lib/api.ts.tmpl +226 -0
  35. package/shared/scaffold/supabase/template/web/src/main.tsx.tmpl +13 -0
  36. package/shared/skills/learn-eventmodelers-api/SKILL.md +4 -4
  37. package/shared/skills/ui-contract/SKILL.md +6 -6
  38. package/shared/skills/update-slice-status/SKILL.md +1 -1
  39. package/stacks/axon/templates/build-kit/CLAUDE.md +4 -3
  40. package/stacks/axon/templates/build-kit/lib/backend-prompt.md +11 -1
  41. package/stacks/axon/templates/build-kit/lib/prompt.md +24 -0
  42. package/stacks/blank/templates/build-kit/CLAUDE.md +4 -3
  43. package/stacks/blank/templates/build-kit/lib/backend-prompt.md +11 -1
  44. package/stacks/blank/templates/build-kit/lib/prompt.md +24 -0
  45. package/stacks/classic/templates/build-kit/CLAUDE.md +6 -5
  46. package/stacks/classic/templates/build-kit/lib/backend-prompt.md +11 -1
  47. package/stacks/classic/templates/build-kit/lib/prompt.md +24 -0
  48. package/stacks/kurrent/templates/build-kit/CLAUDE.md +4 -3
  49. package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +11 -1
  50. package/stacks/kurrent/templates/build-kit/lib/prompt.md +24 -0
  51. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +1 -1
  52. package/stacks/modeling-kit/templates/.claude/skills/prototype/SKILL.md +2 -0
  53. package/stacks/modeling-kit/templates/.claude/skills/prototype/assets/prototype.mjs +7 -5
  54. package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +1 -1
  55. package/stacks/modeling-kit/templates/kit/CLAUDE.md +11 -5
  56. package/stacks/node/templates/build-kit/CLAUDE.md +4 -3
  57. package/stacks/node/templates/build-kit/lib/backend-prompt.md +11 -1
  58. package/stacks/node/templates/build-kit/lib/prompt.md +10 -0
  59. package/stacks/opencqrs/templates/build-kit/CLAUDE.md +4 -3
  60. package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +11 -1
  61. package/stacks/opencqrs/templates/build-kit/lib/prompt.md +24 -0
  62. package/stacks/react/templates/build-kit/CLAUDE.md +4 -3
  63. package/stacks/react/templates/build-kit/lib/AGENT.md +1 -1
  64. package/stacks/react/templates/build-kit/lib/backend-prompt.md +17 -7
  65. package/stacks/react/templates/build-kit/lib/kit.json +1 -1
  66. package/stacks/react/templates/build-kit/lib/prompt.md +39 -2
  67. package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +15 -0
  68. package/stacks/supabase/templates/build-kit/CLAUDE.md +4 -3
  69. package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +11 -1
  70. package/stacks/supabase/templates/build-kit/lib/prompt.md +10 -0
  71. package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +78 -6
  72. package/stacks/supabase-react/templates/.claude/skills/build-state-view/SKILL.md +64 -11
  73. package/stacks/supabase-react/templates/build-kit/CLAUDE.md +5 -4
  74. package/stacks/supabase-react/templates/build-kit/lib/AGENT.md +1 -1
  75. package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +17 -7
  76. package/stacks/supabase-react/templates/build-kit/lib/kit.json +1 -1
  77. package/stacks/supabase-react/templates/build-kit/lib/prompt.md +40 -3
  78. package/stacks/umadb/templates/build-kit/CLAUDE.md +4 -3
  79. package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +11 -1
  80. package/stacks/umadb/templates/build-kit/lib/prompt.md +24 -0
@@ -71,8 +71,9 @@ Extract:
71
71
  - **screens[]** — the actor `SCREEN` element(s) that trigger this command, if any. Empty when the
72
72
  command has no UI trigger (rare — build the component from fields alone in that case, skip Step
73
73
  2).
74
- - **specifications[]** — GWT scenarios; treat as acceptance criteria for the component's business
75
- logic (e.g. "given empty `email`, submit is disabled").
74
+ - **specifications[]** and **storylines[]** (optional) — GWT scenarios / board walkthroughs. These
75
+ describe how the slice should **behave** — read them in full (Step 3a) to understand the outcomes
76
+ and states the UI must present. The business logic itself lives in the backend; the UI does not implement it.
76
77
 
77
78
  > **Comments**: each element carries `comments: string[]` (board comments) — read them as
78
79
  > implementation hints, and resolve consumed ones via
@@ -80,6 +81,10 @@ Extract:
80
81
 
81
82
  ## Step 2 — Read the screen, and especially its marks
82
83
 
84
+ > **Gated by `ui-baseline check`** (build prompt, `lib/prompt.md`, Step 4): do this step when the verdict
85
+ > is `SCREEN_CHANGED`, `NO_BASELINE` or `UNKNOWN` — and then also diff the existing JSX against the fetched
86
+ > screen and adjust the UI to it. On `UNCHANGED` skip it.
87
+
83
88
  `slice.json`'s `screens[]` entries only carry `id`/`title`/`fields[]`/`description` (fields already
84
89
  scoped to this slice) — not the actual markup. Fetch the live node to get that:
85
90
 
@@ -132,19 +137,76 @@ lane concept, so the frontend path isn't a strict 1:1 mirror of it. `CommandTitl
132
137
  **Markup**: translate the blueprint HTML from Step 2 into JSX **1:1** — same tags, same classes
133
138
  (the screen's `cssFrameworks` framework), same layout and copy. This is not a redesign; the screen *is*
134
139
  the design. Only change what must become dynamic:
135
- - A field's example value becomes that input's controlled `value`.
140
+ - A field's example value becomes that input's **initial/default** `value` only for what the user is
141
+ *entering* (a new-record form). Anything the screen *displays* about an existing entity (names,
142
+ titles, current selection, existing tags, list rows) is **not** static — see "Data binding" below.
136
143
  - An element repeated for a `cardinality: "List"` field becomes a `.map()` over that field.
137
144
  - `class`/`style`/structure otherwise stay exactly as drawn — except the board's own mark plumbing:
138
145
  drop every `data-em-mark-id` attribute and `em-mark`/`em-mark-<hex>` class (they only draw the
139
146
  highlight outline on the canvas; the app never ships it).
140
147
  - The classes only render correctly if the screen's CSS framework (`cssFrameworks`) is loaded in the app — see Step 7 (styles); don't hand-write CSS to compensate.
141
148
 
149
+ **Data binding — mockup text is sample data, not copy.** The example values drawn in a screen are
150
+ placeholders from the model. Before translating, classify every piece of text/value in the fragment:
151
+ *label/copy* (headings like "Tags", button captions, placeholders — keep verbatim) vs. *data* (a person's
152
+ name, a role/organisation line, a pre-selected dropdown value, dropdown options, existing list rows,
153
+ badges, counts, dates). For each *data* item find its source in `slice.json` / the model — the
154
+ `readmodels[]` whose fields carry it, or the command's own `fields[]` — and render it from there.
155
+ **Never hardcode a data value from the mockup into JSX.**
156
+
157
+ **Find the screen's data sources on the board, not just in `slice.json`.** A screen is often shown by
158
+ one slice but fed by a read model that belongs to *another* slice (e.g. the Contact Details screen
159
+ sits in the Assign Tag slice, yet its contact data comes from the Contact List read model via an edge
160
+ READMODEL → SCREEN). `slice.json` only lists this slice's own elements, so also call
161
+ `mcp__eventmodelers__get_connected_nodes { boardId, nodeId: <screen.id> }` and take every READMODEL
162
+ wired into the screen. Each one is a data source for the displayed values; look up its fields in that
163
+ read model's slice (`.build-kit/.slices/*/*/slice.json`, or `get_node`). A list read model feeding a
164
+ detail screen (`Contact List[]` → one contact) means: query it filtered by the entity id the page
165
+ gets from the route (match the id field, e.g. `contactId` / the command's `recordId`) and render that
166
+ row. If the source read model's slice isn't built, still type and wire against its fields (TODO), do
167
+ not substitute the mockup's values.
168
+
169
+ - Data comes from a read model (query component of the same page/screen, via `queryReadModel` /
170
+ `getReadModel`) → bind it to that data (props from the parent that fetched it, or fetch it here
171
+ when this component owns that read). If that read-model slice is not built yet, wire the component
172
+ against the typed shape and leave a `// TODO(<read model title>)` — do not paste the example in.
173
+ - Select/dropdown *options* come from a read model (e.g. the customer list), never from the
174
+ options drawn in the mockup; the *selected* value comes from the entity's own read model.
175
+ - The only legitimate static content is label/copy and `VITE_DATA_MODE=mock` samples in `samples/*.json`.
176
+ - Self-check before finishing: grep the component for the mockup's example strings (e.g. the person's
177
+ name, organisation, tag text). Any hit outside `samples/` is a bug.
178
+
142
179
  **Props**: exactly `command.fields[]`, typed per each field's `type` (`String`→`string`,
143
180
  `Boolean`→`boolean`, `Int`/`Long`→`number`, etc. — `subfields[]` become a nested object type),
144
181
  respecting `optional`. Add two callback props: `onSuccess?: (result: TResponse) => void` and
145
182
  `onError?: (error: ApiError) => void`. **No other inputs** — no context/store reads, no fetching
146
183
  inside the component. All business data the component needs arrives via props from its parent.
147
184
 
185
+ ### Step 3a — Read the scenarios to understand behavior (not to implement rules)
186
+
187
+ **Business logic lives in the backend. The UI never re-implements it.** Do not copy a scenario's
188
+ rules (preconditions, limits, uniqueness, state checks) into the component as validation or guards —
189
+ the backend decides, and a duplicated rule drifts the moment the model changes.
190
+
191
+ Read every entry in `specifications[]` (and the beat runs in `storylines[]` containing this command)
192
+ to understand **how the component should behave around the command**: what the user is trying to
193
+ accomplish, what outcomes exist, and what each outcome must look like. Use them to decide:
194
+
195
+ | Scenario shows | What it tells the UI |
196
+ |---|---|
197
+ | `when` this command → `then` EVENT(s) | the success outcome: what to confirm or reset after `postCommand` resolves, and what `onSuccess` hands the parent |
198
+ | `when` this command → `then` an error / rejection (e.g. "already reserved") | the failure outcomes that exist: render each as its own readable message from the `ApiError` the backend returns — not one generic failure. The backend decides *whether* it fails; the UI only shows it |
199
+ | `optional` fields, fields that vary between scenarios | which inputs are required vs. optional in the form (mirror the flags, no invented rules) |
200
+ | `given` events before the command | context for what the user was doing — background to understand the flow, not logic to reproduce |
201
+ | a `STORYLINE_NOTE` beat | background only |
202
+
203
+ Rules:
204
+ - Understand, don't implement: no business validation, no precondition checks, no computed outcomes
205
+ in the component. Only presentation: states (idle/submitting/success/error), required-vs-optional
206
+ inputs, and showing what the backend returns.
207
+ - If a scenario's outcome needs a UI state the markup doesn't show, add the minimal state and note
208
+ the scenario in a one-line comment (e.g. `// spec: "already reserved" → backend 409 shown inline`).
209
+
148
210
  **Default — keep the logic in the component.** For an ordinary command (one form, one submit
149
211
  action, which is most of them) local state and the `postCommand` call live directly inside the
150
212
  component. This is the common case and needs nothing more:
@@ -211,9 +273,10 @@ is driven entirely by props, each sample is a **full set of prop values** — th
211
273
  renders this component in isolation (a `src/pages/` composition, or ad hoc while developing) to
212
274
  spread as props, e.g. `<ReserveBike {...sample1} onSuccess={...} />`.
213
275
 
214
- Add `sample-1.json` for the ordinary case (`command.fields[].example` values); add more numbered
215
- ones for other scenarios `specifications[]` calls out (an edge case, an `optional` field omitted,
216
- ...). As in `build-state-view`, the same number across different components' samples is meant to
276
+ Add `sample-1.json` for the ordinary case (`command.fields[].example` values); add a numbered
277
+ sample for **each** scenario in `specifications[]` that changes what the component does (an invalid
278
+ input that blocks submit, an `optional` field omitted, a precondition that disables the trigger).
279
+ Take the values from the scenario's own example data. As in `build-state-view`, the same number across different components' samples is meant to
217
280
  represent one coherent scenario across the app, not an arbitrary per-component index.
218
281
 
219
282
  ## Step 5 — Context/Provider, only if the marked fragment needs shared local state
@@ -290,6 +353,13 @@ how many pages this app needs — not one page per slice.
290
353
  content there, passing whatever props it declares. Only substitute positions whose slice
291
354
  component already exists; leave any not-yet-built slice's marked area as the mockup's static
292
355
  placeholder until that slice is built and this step runs again for it.
356
+ - **Unmarked/unbuilt regions are not an excuse to hardcode data.** Static placeholder is only for
357
+ label/copy. A displayed data value (e.g. the contact's name and "role · organisation" line, the
358
+ selected organisation and the organisation dropdown options) must be bound to the read model that
359
+ carries it — if that read model's component exists (e.g. the contact list/details read), fetch
360
+ through it and pass the values down; if not, type the prop and leave a `// TODO(<read model>)`.
361
+ Never paste the mockup's example (or a slice's `samples/sample-1.json`) into the page as the
362
+ data. The page reads the entity id from the route/query param.
293
363
  - The page owns threading shared inputs (route/query params, the current session, etc.) down as
294
364
  props to each slice component — a slice component still only knows its own declared props, not
295
365
  the rest of the page or its siblings.
@@ -348,6 +418,8 @@ After writing the hook/component (and context/page/base page, if any), confirm:
348
418
  time, the provisional `/api/mock/...` path noted in a comment (Step 1).
349
419
  - At least one numbered sample exists under this slice's `samples/` folder, shaped exactly like
350
420
  this component's props.
421
+ - Every scenario outcome (success, each named failure) has a rendered state, and **no business rule
422
+ from a scenario was re-implemented in the component** — the backend owns it.
351
423
  - If a screen was read in Step 2, the rendered JSX matches the marked (or whole, if unmarked)
352
424
  HTML fragment structurally — no invented buttons/fields the mockup didn't show, nothing from
353
425
  the mockup silently dropped.
@@ -67,10 +67,13 @@ Extract:
67
67
  a Slice" step 6) — the built, mocked component still needs a real table/view before it's done,
68
68
  so don't flip it to `Done`.
69
69
  - `description` — implementation hints (filtering rules, sort order, business meaning).
70
- - **UI contract (read)** — the backend slice's published `GET` contract: look it up as described in
70
+ - **UI contract (read)** — **not used for a read model backed by a table, which is the normal case on this
71
+ stack: that is always read directly with `queryReadModel`, even when the backend published a `GET`
72
+ contract.** Only when `slice.json` says the read model is not a persisted table (a computed view, an
73
+ external source) does the backend's published `GET` contract apply: look it up as described in
71
74
  the `ui-contract` skill ("How the UI build kit looks the contract up" — the `## UI Contract` entry in
72
- `slice.json` `notes[]`; parse only its `ui-contract` block — an OpenAPI operation). **When present it
73
- is the specification and you build the call from it** — it overrides `apiEndpoint` and the table read:
75
+ `slice.json` `notes[]`; parse only its `ui-contract` block — an OpenAPI operation). **In that case it
76
+ is the specification and you build the call from it** — it overrides `apiEndpoint`:
74
77
  - fetch with `getReadModel(buildPath('<contract path>', { <path params> }), { <query params> }, samples)`
75
78
  from `src/lib/api.ts` — path and query `parameters` exactly as the contract names them, never a
76
79
  guessed URL, never `supabase.from(...)`;
@@ -84,14 +87,19 @@ Extract:
84
87
  `readmodel.apiEndpoint` (table/view name) + `fields[]` via `queryReadModel` — and note the assumption
85
88
  as a code comment.
86
89
  - **screens[]** — the actor `SCREEN` element(s) that display this data, if any (see Step 2).
87
- - **specifications[]** — GWT scenarios/storyline; treat as acceptance criteria (e.g. "given no
88
- active reservation, show an empty state").
90
+ - **specifications[]** and **storylines[]** (optional) — GWT scenarios / board walkthroughs. These
91
+ describe how the slice should **behave** — read them in full (Step 3a) to understand the outcomes
92
+ and states the UI must present. The business logic itself lives in the backend; the UI does not implement it.
89
93
 
90
94
  > **Comments**: as in `build-state-change` — resolve consumed ones via the same node-comment
91
95
  > endpoint.
92
96
 
93
97
  ## Step 2 — Read the screen, and especially its marks
94
98
 
99
+ > **Gated by `ui-baseline check`** (build prompt, `lib/prompt.md`, Step 4): do this step when the verdict
100
+ > is `SCREEN_CHANGED`, `NO_BASELINE` or `UNKNOWN` — and then also diff the existing JSX against the fetched
101
+ > screen and adjust the UI to it. On `UNCHANGED` skip it.
102
+
95
103
  Identical to `build-state-change` Step 2 — fetch the live screen node
96
104
  (`mcp__eventmodelers__get_node`), find this slice's marked fragment in `meta.pages`/`meta.marks`
97
105
  (or the whole page if unmarked), and treat it as the blueprint. The same `learn-styleguide` note
@@ -112,16 +120,39 @@ multi-read-model component.
112
120
  **Markup**: same rule as `build-state-change` — translate the blueprint from Step 2 1:1 into JSX;
113
121
  the mockup is the design, not a starting point for a redesign.
114
122
 
123
+ **Data binding — the whole point of a read-model component.** Every *data* value in the mockup (names,
124
+ role/organisation lines, selected values, dropdown options, list rows, badges, counts, dates) is a
125
+ sample taken from the model's examples; it must be rendered from the queried data, mapped from
126
+ `readmodel.fields[]` — keep only label/copy (headings, button captions, placeholders) verbatim.
127
+ Check where each displayed value comes from before writing JSX: a field of this read model → bind it;
128
+ a field of another read model on the same screen → it arrives as a prop from the parent that fetched
129
+ it (or a sibling read-model component), never from the mockup. List rows are a `.map()` over the
130
+ fetched rows, not the rows drawn. Self-check: grep the component for the mockup's example strings;
131
+ any hit outside `samples/` means hardcoded data — fix it.
132
+
133
+ **Find the screen's data sources on the board, not just in `slice.json`.** A screen is often shown by
134
+ one slice but fed by a read model that belongs to *another* slice (e.g. the Contact Details screen
135
+ sits in the Assign Tag slice, yet its contact data comes from the Contact List read model via an edge
136
+ READMODEL → SCREEN). `slice.json` only lists this slice's own elements, so also call
137
+ `mcp__eventmodelers__get_connected_nodes { boardId, nodeId: <screen.id> }` and take every READMODEL
138
+ wired into the screen. Each one is a data source for the displayed values; look up its fields in that
139
+ read model's slice (`.build-kit/.slices/*/*/slice.json`, or `get_node`). A list read model feeding a
140
+ detail screen (`Contact List[]` → one contact) means: query it filtered by the entity id the page
141
+ gets from the route (match the id field, e.g. `contactId` / the command's `recordId`) and render that
142
+ row. If the source read model's slice isn't built, still type and wire against its fields (TODO), do
143
+ not substitute the mockup's values.
144
+
145
+
115
146
  **Props**: whatever the component needs to *scope* its query (e.g. `customerId`) — passed in from
116
147
  its parent, never read from a global store. The queried data itself is **not** a prop — the
117
148
  component fetches it.
118
149
 
119
- **With a UI contract (Step 1)**: call `getReadModel(buildPath(path, pathParams), queryParams, samples)`
150
+ **With a UI contract (Step 1, non-table read models only)**: call `getReadModel(buildPath(path, pathParams), queryParams, samples)`
120
151
  from `src/lib/api.ts` inside the component instead of the `queryReadModel` snippet below — same
121
152
  effect shape (`useEffect` → loading / empty / error / data), but each documented non-2xx response
122
153
  (`ApiError.status`) maps to its own state. Never a raw `fetch`.
123
154
 
124
- **Default (no contract) — call `queryReadModel` from `src/lib/api.ts` inside the component**. Never
155
+ **Default (a table-backed read model — always, unless the contract case above applies) — call `queryReadModel` from `src/lib/api.ts` inside the component**. Never
125
156
  `supabase.from(...)` directly, never a raw `fetch`:
126
157
 
127
158
  ```tsx
@@ -182,6 +213,26 @@ export function ActiveReservationView({ customerId }: ActiveReservationViewProps
182
213
  }
183
214
  ```
184
215
 
216
+ ### Step 3a — Read the scenarios to understand behavior (not to implement rules)
217
+
218
+ **Business logic lives in the backend (the projection). The UI never re-derives it** — no
219
+ recomputing totals, statuses or membership from events; render what the read model holds.
220
+
221
+ Read every entry in `specifications[]` (and the READMODEL beats in `storylines[]`) to understand
222
+ **what the read model looks like in different situations and how the component should present each**:
223
+
224
+ | Scenario shows | What it tells the UI |
225
+ |---|---|
226
+ | `then` read model is empty (`expectEmptyList` / no rows) | an **empty state** must exist, distinct from loading and error |
227
+ | the same field takes different values across scenarios (e.g. status `open` → `closed`) | each value needs a sensible presentation (label, badge, shown/hidden actions) — presentation only |
228
+ | rows appear/disappear between scenarios | the component renders whatever rows the read model returns; it does not decide which belong |
229
+ | row order visible across scenarios | hint for `order`/sort on the query, if the read model doesn't already guarantee it |
230
+ | a `STORYLINE_NOTE` beat | background only |
231
+
232
+ Rules:
233
+ - Understand, don't implement: presentation of what the backend provides, nothing more.
234
+ - Cite the scenario behind a non-obvious state in a one-line comment.
235
+
185
236
  Select fields explicitly by name rather than `select('*')`. Map each field's `name` to its actual
186
237
  column name — check the paired backend stack's migrations if the naming convention (e.g. camelCase
187
238
  vs. snake_case) isn't obvious rather than guessing; if genuinely unclear, use `request-feedback`.
@@ -202,7 +253,9 @@ otherwise; use `null`/`[]` for a sample meant to exercise the empty/background s
202
253
 
203
254
  Add at least one sample (`sample-1.json`) covering the ordinary case from
204
255
  `readmodel.fields[].example` values; add more numbered ones for whatever other scenarios this
205
- read model's `specifications[]` call out (e.g. an empty result, a second customer's data). The
256
+ read model's `specifications[]` show — **one sample per distinct scenario outcome**, using the
257
+ scenario's own example values (an empty result, each status a row can take, a second customer's
258
+ data). The
206
259
  same number across *different* components' `samples/` folders is meant to represent one coherent
207
260
  scenario across the whole app (e.g. every component's `sample-2.json` together tell the story of
208
261
  "a long-time customer") — keep that in mind when choosing what a given number represents, rather
@@ -230,7 +283,7 @@ one page per slice.
230
283
  one place allowed to import across slice folders. Translate the screen's whole, unmarked
231
284
  `meta.pages` markup into JSX (the page-level layout), and at each `data-em-mark-id` position
232
285
  render that mark's owning slice's component (only for slices already built; leave the rest as
233
- the mockup's static content until they are). The page threads shared inputs down as props; a
286
+ the mockup's static content until they are). Static placeholder content is label/copy only — displayed data (names, selections, options, rows) is bound to its read model, never the mockup's examples or a sample file. The page threads shared inputs down as props; a
234
287
  slice component still only knows its own declared props. Update only this slice's marked
235
288
  position if the page file already exists.
236
289
 
@@ -289,8 +342,8 @@ leaves it alone.
289
342
  slice.json.
290
343
  - At least one numbered sample exists under this slice's `samples/` folder, shaped exactly like
291
344
  `queryReadModel`'s return value.
292
- - The component renders a sensible empty/background state, distinct from loading/error, when the
293
- data is `null`/`[]`.
345
+ - Every scenario situation (empty, each distinct field value) has a rendered presentation, and the
346
+ component re-derives nothing the read model already holds.
294
347
  - If a screen was read in Step 2, the rendered JSX matches the marked (or whole, if unmarked) HTML
295
348
  fragment structurally.
296
349
  - Nothing in this slice's folder is imported from another slice's folder, and nothing outside it
@@ -28,7 +28,7 @@ not attempt to build it, flag it via `request-feedback` instead.
28
28
  2. Maintain clear separation of concerns within each slice
29
29
  3. Headers, auth, `fetch`, and Supabase table access live exclusively in `src/lib/api.ts` — a
30
30
  slice component never imports `src/lib/supabase.ts` itself, never calls `fetch`, and never
31
- calls `supabase.from(...)` directly; when the backend slice published a UI contract (`ui-contract` skill, `slice.json` `notes[]`) the slice is built *from* it — path, parameters, body and every error response — and falls back to `apiEndpoint`/`fields[]` only when none exists; it calls `postCommand`/`getReadModel`/`queryReadModel` and only contains
31
+ calls `supabase.from(...)` directly; a table-backed read model is always read directly with `queryReadModel` (the backend's `GET` contract is ignored for it); when the backend slice published a UI contract (`ui-contract` skill, `slice.json` `notes[]`) for a *write* or a non-table read, the slice is built *from* it — path, parameters, body and every error response — and falls back to `apiEndpoint`/`fields[]` only when none exists; it calls `postCommand`/`getReadModel`/`queryReadModel` and only contains
32
32
  business logic (validation, request-body shaping, response handling)
33
33
  4. A command's/read-model's screen markup (`meta.pages` on its actor HTML_SCREEN node, scoped to
34
34
  this slice's `meta.marks` when the screen is shared) is the visual blueprint — translate it into
@@ -122,9 +122,10 @@ When asked to build a slice, always follow this flow:
122
122
  - **`AUTOMATION`** → not this stack's job (no UI counterpart) — invoke `request-feedback`
123
123
  rather than building it; it belongs to whichever backend stack is installed alongside this one
124
124
  3. Invoke the matching skill and follow its instructions completely. Do not deviate.
125
- 4. **Verify against slice.json**: After the skill completes, check that every command field, event field, and specification in slice.json appears in the implementation. No invented fields — if it is not in slice.json, it must not be in the code.
126
- 5. Run `npm run build` and `npm run lint` (both must pass clean), then the slice's own tests only, if any exist.
127
- 6. If checks pass, commit with `feat: [Slice Name]` and set slice status to `Done` — **unless** the
125
+ 4. **UI baseline gate**: run `npx @eventmodelers/cli ui-baseline check` first and act only on its verdict — `UNCHANGED` means skip all UI work; `NO_BASELINE`/`UNKNOWN` mean one full screen diff and every `codeGen.uiPrompts` entry; `CHANGED` names the elements that changed, were added or removed (work on exactly those) and flags `SCREEN_CHANGED` (re-fetch the screen node and adjust the components to it), `ELEMENTS_CHANGED` (diff slice.json against the code) and `PROMPTS_CHANGED` (apply the printed prompts). Details in `lib/prompt.md`, Step 4. After the checks pass and before committing, run `ui-baseline record`.
126
+ 5. **Verify against slice.json**: After the skill completes, check that every command field, event field, and specification in slice.json appears in the implementation — specifications are read (Step 3a of the build skill) to understand how the UI should behave and present outcomes; the business logic lives in the backend and is not re-implemented here. No invented fields — if it is not in slice.json, it must not be in the code.
127
+ 6. Run `npm run build` and `npm run lint` (both must pass clean), then the slice's own tests only, if any exist.
128
+ 7. If checks pass, commit with `feat: [Slice Name]` and set slice status to `Done` — **unless** the
128
129
  matching build skill flagged a missing `apiEndpoint` and already marked the slice `Blocked`
129
130
  (Step 1 exception above); in that case, commit as normal but leave the status `Blocked`.
130
131
 
@@ -43,4 +43,4 @@ These files are always up to date — read them directly before invoking any ski
43
43
  - The `boardId` and `organizationId` from each payload provide full context — pass them to skills.
44
44
  - Node events use `node:created`, `node:changed`, `node:deleted` — always POST to `/api/org/:orgId/boards/:boardId/nodes/events`.
45
45
  - Slice metadata (title, status) lives on the SLICE_BORDER node under `meta.sliceStatus` and `meta.title`.
46
- - `/update-slice-status` rejects moving a slice into a status it's already in — this is a concurrency guard, not a bug. It means another agent already claimed the slice. Treat it as `ALREADY_IN_STATUS`, skip that slice, and move on to the next `Handover` one instead of erroring out. This applies to queued tasks only: when the task request names the slice to build, the run loop owns its status — never call `/update-slice-status` there.
46
+ - `/update-slice-status` rejects moving a slice into a status it's already in — this is a concurrency guard, not a bug. It means another agent already claimed the slice. Treat it as `ALREADY_IN_STATUS`, skip that slice, and move on to the next `Waiting` one instead of erroring out. This applies to queued tasks only: when the task request names the slice to build, the run loop owns its status — never call `/update-slice-status` there.
@@ -9,9 +9,9 @@ The structure defined in the Project-Skills is relevant.
9
9
  You work within **exactly ONE context at a time** — the one named in `.build-kit/.slices/current_context.json` (the runner picks it).
10
10
 
11
11
  - **ONLY** look for and build slices inside `.build-kit/.slices/<currentContext>/`.
12
- - **NEVER** read, scan, or build slices from any other context directory, even if it has "Handover" slices, and even if the current context has no work left.
13
- - A "Handover" slice in a *different* context is **NOT yours to build**. Ignore it completely.
14
- - If the current context has no "Handover" slice, you are **done for this iteration** — reply `<promise>NO_TASKS</promise>` and stop. Do not go looking elsewhere. The runner moves `current_context.json` on to the next context with Handover work between iterations — never change it yourself.
12
+ - **NEVER** read, scan, or build slices from any other context directory, even if it has "Waiting" slices, and even if the current context has no work left.
13
+ - A "Waiting" slice in a *different* context is **NOT yours to build**. Ignore it completely.
14
+ - If the current context has no "Waiting" slice, you are **done for this iteration** — reply `<promise>NO_TASKS</promise>` and stop. Do not go looking elsewhere. The runner moves `current_context.json` on to the next context with Waiting work between iterations — never change it yourself.
15
15
 
16
16
  ## Your Task
17
17
 
@@ -19,8 +19,8 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
19
19
  1. Read `.build-kit/.slices/current_context.json` to find the active context name, then read `.build-kit/.slices/<contextName>/index.json`. Every item in status "handover" is a task.
20
20
  2. Read `.build-kit/AGENTS.md` **if it exists** — the project's accumulated learnings, generated from what earlier builds recorded. If the slice's `slice.json` has a `lastAttempt`, an earlier attempt at it failed: read its `reason`, `lastReply`, `branch` and `changed`, and do not repeat what failed.
21
21
  3. Make sure you are on the right branch "feature/<slicename>", if unsure, start from main.
22
- 5. Your slice is the one the task request names (the run loop picked the highest priority slice with status "Handover"); it becomes your PRD. The run loop has already claimed it — its status is "InProgress" in index.json and on the board. Build that slice and do not call `update-slice-status`: the run loop owns the slice's status on the board.
23
- **IMPORTANT: Only work on the slice named in the task request. Never switch to another slice, whatever its status. If the task request names no slice and no slice has status "Handover" in the current context, reply with:**
22
+ 5. Your slice is the one the task request names (the run loop picked the highest priority slice with status "Waiting"); it becomes your PRD. The run loop has already claimed it — its status is "InProgress" in index.json and on the board. Build that slice and do not call `update-slice-status`: the run loop owns the slice's status on the board.
23
+ **IMPORTANT: Only work on the slice named in the task request. Never switch to another slice, whatever its status. If the task request names no slice and no slice has status "Waiting" in the current context, reply with:**
24
24
  <promise>NO_TASKS</promise> and stop immediately. Do not work on other slices and do not switch to another context.
25
25
  6. Pick the slice definition from `.build-kit/.slices/<contextName>/<folder>/slice.json` as defined in the prd. Never work on more than one slice per iteration.
26
26
  7. A slice can define additional prompts as codegen/backendPrompt. Any additional prompts defined in backend are hints for the implementation of the slice and have to be taken into account.
@@ -54,6 +54,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
54
54
  - `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
55
55
  - One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
56
56
 
57
+ ### Recording Info
58
+
59
+ Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
60
+
61
+ ```
62
+ npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
63
+ ```
64
+
65
+ It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
66
+
57
67
  ## Update AGENTS.md Files
58
68
 
59
69
  Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files — API patterns/conventions, gotchas, dependencies between files, testing approaches, configuration/environment requirements.
@@ -90,12 +100,12 @@ A Slice is not complete if specifications are missing or can't be executed.
90
100
 
91
101
  ## Stop Condition
92
102
 
93
- **After completing ONE slice, always stop — regardless of whether more slices are in Handover.** The ralph loop will invoke you again for the next slice. Never chain multiple slices in one iteration.
103
+ **After completing ONE slice, always stop — regardless of whether more slices are in Waiting.** The ralph loop will invoke you again for the next slice. Never chain multiple slices in one iteration.
94
104
 
95
105
  If the slice was completed and committed successfully, reply with:
96
106
  <promise>DONE</promise>
97
107
 
98
- If no slice has status "Handover" in the current context, reply with:
108
+ If no slice has status "Waiting" in the current context, reply with:
99
109
  <promise>NO_TASKS</promise>
100
110
  (Do NOT switch to another context to find work — stop here.)
101
111
 
@@ -1,3 +1,3 @@
1
1
  {
2
- "pickupStatus": "Handover"
2
+ "pickupStatus": "Waiting"
3
3
  }
@@ -38,13 +38,13 @@ Run `/load-slice sliceId=<payload.sliceId>` to fetch full slice details (title,
38
38
 
39
39
  Inspect the `sliceStatus` in the payload:
40
40
 
41
- #### `Handover` — build the slice
41
+ #### `Waiting` — build the slice
42
42
 
43
43
  This is the build trigger. Setting `InProgress` and building are one atomic step:
44
44
 
45
45
  1. Immediately call `/update-slice-status` to set the slice to `InProgress` on the board.
46
46
 
47
- **Claim conflict**: if this call reports the slice is already in `InProgress` (or any status other than `Handover`), another agent already claimed it first — this is expected, not an error. Say so in your reply, drop this task without building, and continue the loop (the next task will naturally cover the next slice). Do not retry.
47
+ **Claim conflict**: if this call reports the slice is already in `InProgress` (or any status other than `Waiting`), another agent already claimed it first — this is expected, not an error. Say so in your reply, drop this task without building, and continue the loop (the next task will naturally cover the next slice). Do not retry.
48
48
 
49
49
  2. Run `/load-slice sliceId=<payload.sliceId>` to fetch the full slice, then read the slice definition from `.build-kit/.slices/<contextSlug>/<sliceFolder>/slice.json`. The runner keeps a `slice.json` fresh from a summary endpoint, so a file holding only `id`, `title`, `status` and `sliceType` is that stub, not an empty slice — never conclude a slice has no elements from it. Only if the full fetch itself returns no commands/events/read models/fields is the slice actually empty.
50
50
 
@@ -56,7 +56,34 @@ This is the build trigger. Setting `InProgress` and building are one atomic step
56
56
 
57
57
  4. Invoke the matching skill and follow its instructions **completely**. Do NOT implement the slice manually.
58
58
 
59
- 5. **Verify against slice.json**: cross-check the implementation — every command field, event field, and specification in slice.json must appear in the code. No invented fields — if it is not in slice.json, it must not be in the code.
59
+ **UI baseline gate** — before touching any code, run `npx @eventmodelers/cli ui-baseline check`. It
60
+ compares the board's node hashes (the slice's screen and every element) and `codeGen.uiPrompts` with
61
+ what was recorded after the last UI build, and decides what this build has to do:
62
+ - `UNCHANGED` — nothing on the board changed since the last UI build. Skip all UI work; do not
63
+ re-fetch or diff the screen. Only now is "no code changes needed" a valid outcome.
64
+ - `NO_BASELINE` — no UI build recorded yet (first build, or the slice predates the baseline). Do one
65
+ full screen diff (below), apply every UI prompt.
66
+ - `UNKNOWN` — the board could not be read. Treat it as `NO_BASELINE`; never as `UNCHANGED`.
67
+ - `CHANGED` — it also lists, per element, what changed, was added or was removed since the last recorded
68
+ build (e.g. `changed: Contact Details (screen), Assign Tag (command)`). Work on exactly those nodes
69
+ and leave what matches alone. Act on the parts it flags:
70
+ - `SCREEN_CHANGED`: re-fetch the live screen node (`get_node`, its `meta.pages` / `meta.marks`) —
71
+ never trust the JSX already in the repo — and diff the existing component(s) against the marked
72
+ (or whole, if unmarked) HTML: tags, classes, layout, copy, fields/buttons added, removed or
73
+ reordered, and `cssFrameworks`. Edit the components (and `src/pages/<ScreenTitle>.tsx`, for this
74
+ slice's marked position only) until they match 1:1 and update `samples/`. Keep what already
75
+ matches; do not regenerate the slice from scratch.
76
+ - `ELEMENTS_CHANGED`: diff slice.json against the code field by field (fields,
77
+ `apiEndpoint`/UI contract) and fix every drift.
78
+ - `PROMPTS_CHANGED`: apply the UI prompts it prints — each is an instruction the user typed on the
79
+ board for this slice's UI (e.g. "adjust the UI to the new layout!") — to the component(s) and
80
+ screen markup. Name each prompt and the change you made for it in your final reply.
81
+
82
+ When the UI work is done and the checks in step 6 pass, run `npx @eventmodelers/cli ui-baseline record`
83
+ **before committing**, so `ui-baseline.json` is committed with the slice. Never record without having
84
+ done the work the verdict asked for.
85
+
86
+ 5. **Verify against slice.json**: first check where every displayed value comes from — screen examples are samples, so names, selections, options and list rows must be bound to the read model (grep the components for the mockup's example strings; hits outside `samples/` are hardcoded data and must be fixed). Then cross-check the implementation — every command field, event field, and specification in slice.json must appear in the code — specifications are read (Step 3a of the build skill) to understand how the UI should behave and present outcomes; the business logic itself lives in the backend and is not re-implemented here. No invented fields — if it is not in slice.json, it must not be in the code.
60
87
 
61
88
  6. Run `npm run build` and `npm run lint` (both must pass clean) — plus the slice's own tests only, if any exist.
62
89
 
@@ -108,6 +135,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
108
135
  - `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
109
136
  - One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
110
137
 
138
+ ### Recording Info
139
+
140
+ Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
141
+
142
+ ```
143
+ npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
144
+ ```
145
+
146
+ It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
147
+
111
148
  ## Important
112
149
 
113
150
  - Process **one task per iteration**.
@@ -59,9 +59,10 @@ When asked to build a slice, always follow this flow:
59
59
  - **State-view** — `projections` or `queries` array is non-empty → invoke `/build-state-view`
60
60
  - **State-change** — default (has `commands` / `events`) → invoke `/build-state-change`
61
61
  3. Invoke the matching skill and follow its instructions completely. Do not deviate.
62
- 4. **Verify against slice.json**: After the skill completes, check that every command field, event field, and specification in slice.json appears in the implementation. No invented fields — if it is not in slice.json, it must not be in the code. This applies even when the slice was previously `Done` and reappears as `Planned` — never dismiss a mismatch as "already implemented" or harmless drift; diff slice.json against the code field by field and update the code to match every change.
63
- 5. Run quality checks (`./mvnw compile -q`, then the slice tests only).
64
- 6. If checks pass, commit with `feat: [Slice Name]` and set slice status to `Done`. For STATE_VIEW/STATE_CHANGE slices when `handover` is enabled the status to set is `Handover`, not `Done` — see the `ui-contract` skill, Step 4.
62
+ 4. **Baseline gate**: run `npx @eventmodelers/cli build-baseline check --scope backend` first and act only on its verdict — `UNCHANGED` means nothing on the board changed since the last recorded build: do not rebuild, just run the slice's tests and finish; `NO_BASELINE`/`UNKNOWN` mean one full field-by-field diff of slice.json against the code; `CHANGED` lists exactly which elements changed, were added or removed — diff and fix only those. After the checks pass and before committing, run `build-baseline record --scope backend`. A screen or UI prompt change never triggers a backend build.
63
+ 5. **Verify against slice.json**: After the skill completes, check that every command field, event field, and specification in slice.json appears in the implementation. No invented fields — if it is not in slice.json, it must not be in the code. This applies even when the slice was previously `Done` and reappears as `Planned` — never dismiss a mismatch as "already implemented" or harmless drift; diff slice.json against the code field by field and update the code to match every change.
64
+ 6. Run quality checks (`./mvnw compile -q`, then the slice tests only).
65
+ 7. If checks pass, commit with `feat: [Slice Name]` and set slice status to `Done`. For STATE_VIEW/STATE_CHANGE slices when `handover` is enabled the status to set is `Waiting`, not `Done` — see the `ui-contract` skill, Step 4.
65
66
 
66
67
  After you are done, automatically run the tests for the slice that was edited.
67
68
 
@@ -42,7 +42,7 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
42
42
  If the tests for the slice are not yet named predictably, run `./mvnw test -q` and check for failures.
43
43
  15. If checks pass, commit ALL changes with message: `feat: [Slice Name]` and merge back to main as FF merge ( update
44
44
  first )
45
- 16. Record the outcome with `npx @eventmodelers/cli slice-status Done` — not by editing index.json, and do not call `update-slice-status`: the run loop pushes the status to the board when your turn ends. For STATE_VIEW/STATE_CHANGE slices when `handover` is enabled the status to record is `Handover`, not `Done` — see the `ui-contract` skill, Step 4. A slice you could not finish needs no status: leave it as it is and the run loop discards the attempt.
45
+ 16. Record the outcome with `npx @eventmodelers/cli slice-status Done` — not by editing index.json, and do not call `update-slice-status`: the run loop pushes the status to the board when your turn ends. For STATE_VIEW/STATE_CHANGE slices when `handover` is enabled the status to record is `Waiting`, not `Done` — see the `ui-contract` skill, Step 4. A slice you could not finish needs no status: leave it as it is and the run loop discards the attempt.
46
46
  17. Record any reusable learning you have not recorded yet (see **Recording Learnings**).
47
47
  18. If an entry in `.build-kit/AGENTS.md` turned out wrong or obsolete, retract it (see **Recording Learnings**).
48
48
  19. Finish the iteration.
@@ -64,6 +64,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
64
64
  - `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
65
65
  - One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
66
66
 
67
+ ### Recording Info
68
+
69
+ Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
70
+
71
+ ```
72
+ npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
73
+ ```
74
+
75
+ It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
76
+
67
77
  ## Update AGENTS.md Files
68
78
 
69
79
  Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
@@ -58,6 +58,20 @@ This is the build trigger. Setting `InProgress` and building are one atomic step
58
58
 
59
59
  4. Invoke the matching skill and follow its instructions **completely**. Do NOT implement the slice manually.
60
60
 
61
+ **Baseline gate** — before touching any code, run `npx @eventmodelers/cli build-baseline check --scope backend`.
62
+ It compares the board's node hashes of every element of the slice (commands, events, read models,
63
+ processors, specifications, ...) with what was recorded after the last successful build:
64
+ - `UNCHANGED` — nothing on the board changed. Do not rebuild or re-diff; run the slice's tests and
65
+ finish. Only now is "no code changes needed" a valid outcome.
66
+ - `NO_BASELINE` / `UNKNOWN` — no recorded build, or the board could not be read: do one full
67
+ field-by-field diff of slice.json against the code (never treat `UNKNOWN` as `UNCHANGED`).
68
+ - `CHANGED` — it lists the elements that changed, were added or were removed. Diff slice.json
69
+ against the code for exactly those and fix every drift; leave what matches alone.
70
+
71
+ When the checks pass, run `npx @eventmodelers/cli build-baseline record --scope backend` **before
72
+ committing**, so `ui-baseline.json` is committed with the slice. Never record without having done the
73
+ work the verdict asked for. Screens and UI prompts are not this kit's business and never trigger a build.
74
+
61
75
  5. **Verify against slice.json**: Cross-check the implementation — every command field, event field, and specification in slice.json must appear in the code. No invented fields — if it is not in slice.json, it must not be in the code.
62
76
 
63
77
  6. Run quality checks:
@@ -98,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
98
112
  - `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
99
113
  - One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
100
114
 
115
+ ### Recording Info
116
+
117
+ Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
118
+
119
+ ```
120
+ npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
121
+ ```
122
+
123
+ It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
124
+
101
125
  ## Important
102
126
 
103
127
  - Process **one task per iteration**.