@eventmodelers/cli 1.0.19 → 1.0.21
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/README.md +3 -2
- package/cli.js +24 -8
- package/package.json +1 -1
- package/shared/skills/request-feedback/SKILL.md +155 -0
- package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +0 -2
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +1 -2
- package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +12 -2
- package/stacks/axon/templates/build-kit/CLAUDE.md +7 -0
- package/stacks/axon/templates/build-kit/lib/backend-prompt.md +11 -0
- package/stacks/axon/templates/build-kit/lib/prompt.md +2 -0
- package/stacks/blank/templates/build-kit/CLAUDE.md +7 -0
- package/stacks/blank/templates/build-kit/lib/backend-prompt.md +11 -0
- package/stacks/blank/templates/build-kit/lib/prompt.md +11 -0
- package/stacks/cratis-csharp/templates/build-kit/CLAUDE.md +7 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +11 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +2 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +22 -2
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +50 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +41 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +46 -1
- package/stacks/node/templates/build-kit/CLAUDE.md +7 -0
- package/stacks/node/templates/build-kit/lib/backend-prompt.md +11 -0
- package/stacks/node/templates/build-kit/lib/prompt.md +2 -0
- package/stacks/supabase/templates/build-kit/CLAUDE.md +7 -0
- package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +11 -0
- package/stacks/supabase/templates/build-kit/lib/prompt.md +2 -0
package/README.md
CHANGED
|
@@ -94,8 +94,9 @@ Use skills in Claude Code with `/skill-name`:
|
|
|
94
94
|
| `/update-slice-status` | Update slice status on the board |
|
|
95
95
|
| `/load-slice` | Persist board slices to disk (backend stacks) |
|
|
96
96
|
| `/build-state-change`, `/build-state-view`, `/build-automation`, `/build-webhook` | Implement a slice's command/view/automation/webhook (backend stacks) |
|
|
97
|
+
| `/request-feedback` | Post a QUESTION comment and mark a slice `Blocked` when it's genuinely ambiguous (backend stacks) |
|
|
97
98
|
|
|
98
|
-
Which skills install depends on the chosen stack — see `stacks/<name>/templates/.claude/skills/`. `/connect`, `/learn-eventmodelers-api`,
|
|
99
|
+
Which skills install depends on the chosen stack — see `stacks/<name>/templates/.claude/skills/`. `/connect`, `/learn-eventmodelers-api`, `/update-slice-status`, and `/request-feedback` have no stack-specific content and install into every stack from `shared/skills/` instead.
|
|
99
100
|
|
|
100
101
|
## Everyday commands
|
|
101
102
|
|
|
@@ -346,7 +347,7 @@ npx @eventmodelers/cli uninstall --modeling-kit # remove .agent-modeling-ki
|
|
|
346
347
|
|
|
347
348
|
### Adding a stack
|
|
348
349
|
|
|
349
|
-
Each stack lives under `stacks/<name>/templates/` with `.claude/` (skills), `root/` (spread into the project root), and either `build-kit/` (backend stacks) or `kit/` (modeling-only) for the agent runner. Files identical across all backend stacks live once in `shared/build-kit/` and get layered in automatically — only put stack-specific overrides under `stacks/<name>/templates/build-kit/`. Skills with no stack-specific content (`connect`, `learn-eventmodelers-api`, `update-slice-status`) work the same way via `shared/skills/` — a new stack gets them for free without copying anything; add a skill there only once it needs a stack-specific fork.
|
|
350
|
+
Each stack lives under `stacks/<name>/templates/` with `.claude/` (skills), `root/` (spread into the project root), and either `build-kit/` (backend stacks) or `kit/` (modeling-only) for the agent runner. Files identical across all backend stacks live once in `shared/build-kit/` and get layered in automatically — only put stack-specific overrides under `stacks/<name>/templates/build-kit/`. Skills with no stack-specific content (`connect`, `learn-eventmodelers-api`, `update-slice-status`, `request-feedback`) work the same way via `shared/skills/` — a new stack gets them for free without copying anything; add a skill there only once it needs a stack-specific fork.
|
|
350
351
|
|
|
351
352
|
Once your `init --build-kit` scaffold (see above) works against a real backend, promote it to a first-class stack:
|
|
352
353
|
|
package/cli.js
CHANGED
|
@@ -1413,6 +1413,22 @@ async function runModeling(kitDir, projectDir, verbose = false) {
|
|
|
1413
1413
|
|
|
1414
1414
|
const channelName = `org:${cfg.organizationId}`;
|
|
1415
1415
|
const realtime = await createRealtimeAdapter(cfg, realtimeToken);
|
|
1416
|
+
|
|
1417
|
+
let lastTokenRefreshAt = 0;
|
|
1418
|
+
async function refreshRealtimeToken(reason) {
|
|
1419
|
+
// Guard against hammering the token endpoint: a rejected channel retries every
|
|
1420
|
+
// ~14s on its own, so without this a bad token would trigger a refresh call per retry.
|
|
1421
|
+
if (Date.now() - lastTokenRefreshAt < 5000) return;
|
|
1422
|
+
lastTokenRefreshAt = Date.now();
|
|
1423
|
+
try {
|
|
1424
|
+
realtimeToken = await getRealtimeToken();
|
|
1425
|
+
await realtime.setAuth(realtimeToken);
|
|
1426
|
+
log(`token refreshed (${reason})`);
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
log(`token refresh failed (${reason}): ${err.message}`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1416
1432
|
realtime.subscribe(
|
|
1417
1433
|
channelName,
|
|
1418
1434
|
{
|
|
@@ -1429,19 +1445,19 @@ async function runModeling(kitDir, projectDir, verbose = false) {
|
|
|
1429
1445
|
(status) => {
|
|
1430
1446
|
log(`channel "${channelName}": ${status}`);
|
|
1431
1447
|
if (status === 'SUBSCRIBED') drain().catch((err) => log(`initial drain error: ${err.message}`));
|
|
1448
|
+
// A bad/stale token otherwise sits in realtime-js's own rejoin-retry loop until the
|
|
1449
|
+
// next scheduled refresh below — up to 10 minutes of failed joins. Refresh immediately
|
|
1450
|
+
// instead of waiting on the clock.
|
|
1451
|
+
if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
|
1452
|
+
refreshRealtimeToken(status).catch(() => {});
|
|
1453
|
+
}
|
|
1432
1454
|
},
|
|
1433
1455
|
).catch((err) => {
|
|
1434
1456
|
log(`realtime subscribe failed, prompts won't be pushed live: ${err.message}`);
|
|
1435
1457
|
});
|
|
1436
1458
|
|
|
1437
|
-
setInterval(
|
|
1438
|
-
|
|
1439
|
-
realtimeToken = await getRealtimeToken();
|
|
1440
|
-
await realtime.setAuth(realtimeToken);
|
|
1441
|
-
log('token refreshed');
|
|
1442
|
-
} catch (err) {
|
|
1443
|
-
log(`token refresh failed: ${err.message}`);
|
|
1444
|
-
}
|
|
1459
|
+
setInterval(() => {
|
|
1460
|
+
refreshRealtimeToken('scheduled').catch(() => {});
|
|
1445
1461
|
}, 10 * 60 * 1000);
|
|
1446
1462
|
|
|
1447
1463
|
const ping = async () => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: request-feedback
|
|
3
|
+
description: Post a QUESTION comment on a slice and mark it Blocked when the slice's requirements are genuinely ambiguous, contradictory, or missing something a decision depends on. This is an escalation path, not a routine step — reach for it only when you cannot proceed without guessing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Request Feedback
|
|
7
|
+
|
|
8
|
+
> **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until the connect skill has completed.
|
|
9
|
+
|
|
10
|
+
Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## When to use this skill — the exception, not the rule
|
|
15
|
+
|
|
16
|
+
Building a slice is normally unambiguous: `slice.json` names every command field, event field, and
|
|
17
|
+
GWT scenario, and the matching build skill (`build-state-change`, `build-state-view`,
|
|
18
|
+
`build-automation`, ...) tells you exactly how to turn that into code. **Read `slice.json` fully, and
|
|
19
|
+
read the build skill's own instructions and reference docs, before ever concluding something is
|
|
20
|
+
missing.** Most slices need none of this.
|
|
21
|
+
|
|
22
|
+
Only invoke `request-feedback` when, after that reading, a decision the implementation depends on
|
|
23
|
+
still cannot be made without guessing — for example:
|
|
24
|
+
|
|
25
|
+
- A business rule mentioned in `description`/`comments`/`notes` isn't backed by any field, event, or
|
|
26
|
+
`specifications[]` scenario, so there's no way to encode it.
|
|
27
|
+
- Two fields, or a field and an `idAttribute` flag, contradict each other about what the slice is
|
|
28
|
+
supposed to do.
|
|
29
|
+
- A `specifications[]` scenario references a prior event or state that no command/event in this slice
|
|
30
|
+
(or its declared dependencies) actually produces.
|
|
31
|
+
- The slice depends on another slice, screen, or read model that doesn't exist yet and it's unclear
|
|
32
|
+
whether one should be created, or the dependency was meant to point elsewhere.
|
|
33
|
+
|
|
34
|
+
**Do not use this skill for:**
|
|
35
|
+
- Implementation-detail choices the build skill's own instructions already answer (e.g. package
|
|
36
|
+
naming, file layout, which annotation to use) — those aren't ambiguity in the slice, they're just
|
|
37
|
+
reading the skill more carefully.
|
|
38
|
+
- Style or naming preferences with no functional consequence — pick the reasonable option and move on.
|
|
39
|
+
- "This would be nice to confirm" — if you *can* proceed correctly from what `slice.json` says, proceed.
|
|
40
|
+
|
|
41
|
+
When in doubt, prefer finishing the slice over escalating. Escalating on every minor uncertainty
|
|
42
|
+
defeats the purpose — it should be rare enough that a `Blocked` slice reliably means "a human needs to
|
|
43
|
+
look at this," not "the agent didn't feel like deciding."
|
|
44
|
+
|
|
45
|
+
**When this skill does apply: do not guess and build anyway.** Post the question and stop work on this
|
|
46
|
+
slice for this run — do not implement your best interpretation first. A wrong guess encoded into
|
|
47
|
+
working, tested, committed code is harder to catch and undo than an unbuilt slice waiting for an
|
|
48
|
+
answer.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Step 1 — Parse arguments
|
|
53
|
+
|
|
54
|
+
From `$ARGUMENTS` or the calling skill's context, extract:
|
|
55
|
+
|
|
56
|
+
| Field | How to find it | Default |
|
|
57
|
+
|-------|---------------|---------|
|
|
58
|
+
| `sliceName` or `sliceId` | the slice being worked on | **required** — one of the two |
|
|
59
|
+
| `question` | the specific ambiguity or missing piece, phrased as a question | **required** |
|
|
60
|
+
| `author` | author identifier string | `agent` |
|
|
61
|
+
|
|
62
|
+
## Step 2 — Resolve the slice's node id
|
|
63
|
+
|
|
64
|
+
Prefer MCP:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
mcp__eventmodelers__list_slices { "boardId": "<BOARD_ID>" }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Fallback (no MCP):**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl -s \
|
|
74
|
+
-H "x-token: <TOKEN>" \
|
|
75
|
+
-H "x-board-id: <BOARD_ID>" \
|
|
76
|
+
-H "x-user-id: request-feedback-skill" \
|
|
77
|
+
"<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/slicedata/slices"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Find the slice whose `title` matches `sliceName` (case-insensitive), or whose `id` matches `sliceId`.
|
|
81
|
+
If no match is found, stop and list the available slice titles so the caller can pick one. Save the
|
|
82
|
+
matched slice's `id` as `SLICE_NODE_ID` and its current `status` as `CURRENT_STATUS`.
|
|
83
|
+
|
|
84
|
+
## Step 3 — Post the QUESTION comment
|
|
85
|
+
|
|
86
|
+
Prefer MCP:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
mcp__eventmodelers__add_comment { "boardId": "<BOARD_ID>", "nodeId": "<SLICE_NODE_ID>", "text": "<question>", "type": "QUESTION", "author": "<author>" }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Fallback (no MCP):**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/<SLICE_NODE_ID>/comments" \
|
|
96
|
+
-H "Authorization: Bearer <TOKEN>" \
|
|
97
|
+
-H "Content-Type: application/json" \
|
|
98
|
+
-d '{"text":"<question>","type":"QUESTION","author":"<author>"}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Response: `201 {"id":"<commentId>"}`. Save it as `COMMENT_ID` — the calling skill may want to reference
|
|
102
|
+
it later once the question is answered.
|
|
103
|
+
|
|
104
|
+
Write `<question>` so a human reading it cold understands the gap without re-reading the slice
|
|
105
|
+
themselves: name the slice, name the specific field/rule/scenario in question, and say what's missing
|
|
106
|
+
or contradictory — not just "please clarify this slice."
|
|
107
|
+
|
|
108
|
+
## Step 4 — Mark the slice Blocked
|
|
109
|
+
|
|
110
|
+
Prefer MCP:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
mcp__eventmodelers__update_slice_status { "boardId": "<BOARD_ID>", "sliceId": "<SLICE_NODE_ID>", "newStatus": "Blocked" }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Also mark the slice 'blocked' locally in the index.json if possible.
|
|
117
|
+
|
|
118
|
+
**Fallback (no MCP)** — send a `node:changed` event to update the `sliceStatus` field in the
|
|
119
|
+
SLICE_BORDER node's meta directly:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
123
|
+
-H "Content-Type: application/json" \
|
|
124
|
+
-H "x-token: <TOKEN>" \
|
|
125
|
+
-H "x-board-id: <BOARD_ID>" \
|
|
126
|
+
-H "x-user-id: request-feedback-skill" \
|
|
127
|
+
-d '[{
|
|
128
|
+
"id": "<new-random-uuid>",
|
|
129
|
+
"eventType": "node:changed",
|
|
130
|
+
"nodeId": "<SLICE_NODE_ID>",
|
|
131
|
+
"boardId": "<BOARD_ID>",
|
|
132
|
+
"timestamp": <Date.now()>,
|
|
133
|
+
"changedAttributes": ["sliceStatus"],
|
|
134
|
+
"meta": {
|
|
135
|
+
"sliceStatus": "Blocked"
|
|
136
|
+
}
|
|
137
|
+
}]'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If `CURRENT_STATUS` was already `Blocked`, this step is a no-op — don't treat that as an error, and
|
|
141
|
+
don't retry. It just means someone (possibly this same agent, on an earlier prompt) already flagged it.
|
|
142
|
+
|
|
143
|
+
## Step 5 — Stop and report
|
|
144
|
+
|
|
145
|
+
Do not continue implementing the slice after this. Report back to whoever invoked this skill:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Requested feedback on slice "<sliceName>" (<SLICE_NODE_ID>)
|
|
149
|
+
Question posted: "<question>"
|
|
150
|
+
Status: <CURRENT_STATUS> → Blocked
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Then stop work on this slice for this run. If the caller has other, unrelated slices queued, it may
|
|
154
|
+
move on to those — but this specific slice stays untouched until the question is answered and the
|
|
155
|
+
slice is moved out of `Blocked`.
|
|
@@ -415,8 +415,6 @@ than fabricating an assertion.
|
|
|
415
415
|
|
|
416
416
|
## References
|
|
417
417
|
|
|
418
|
-
- [Stateless Automation Example](references/automation-test-example.md) — Complete Java test example
|
|
419
|
-
- [Automation with Read Model Example](references/automation-with-read-model-test-example.md) — Multi-command test
|
|
420
418
|
- [Feature Flag Patterns](references/feature-flag-patterns.md) — `@ConditionalOnProperty` and alternatives
|
|
421
419
|
|
|
422
420
|
---
|
|
@@ -7,8 +7,7 @@ description: >
|
|
|
7
7
|
(long-running, durable, multi-step processes) instead of plain @EventHandler automations.
|
|
8
8
|
Determines WHICH slices need a Workflow, then implements them using the AF5 Workflow engine.
|
|
9
9
|
Use when: (1) given a set of automation slice.json definitions, decide which ones should be
|
|
10
|
-
Workflows; (2) user asks to "convert automation to
|
|
11
|
-
low", "implement as workflow",
|
|
10
|
+
Workflows; (2) user asks to "convert automation to workflow", "implement as workflow",
|
|
12
11
|
or "use Axon Workflow for this slice"; (3) a slice has multi-step logic, needs to wait for
|
|
13
12
|
external input/approval, requires compensation/rollback, or spans significant time.
|
|
14
13
|
NOTE: AF5 Workflows are in Preview — APIs may change; not intended for production use yet.
|
|
@@ -121,8 +121,16 @@ public static final String {TAG_CONSTANT} = "idField";
|
|
|
121
121
|
## Step 3: Decision-model entity
|
|
122
122
|
|
|
123
123
|
Package-private, mutable field(s) — **not** an immutable `State` record with free-standing
|
|
124
|
-
`decide()`/`evolve()` static methods.
|
|
125
|
-
|
|
124
|
+
`decide()`/`evolve()` static methods.
|
|
125
|
+
|
|
126
|
+
**Derive the field(s) from this slice's `specifications[]` (Step 0), not from the event's shape.**
|
|
127
|
+
Each GWT scenario's `given`/`then` pair states the one decision the command handler must make and the
|
|
128
|
+
prior fact that decision depends on — that fact is the field. Re-read the scenarios before naming
|
|
129
|
+
fields: a "given no prior activity" / "given already {X}" pair means one boolean flag for {X}; a
|
|
130
|
+
scenario that discriminates on a value (not just presence/absence) means a value field holding that
|
|
131
|
+
value, not a boolean. Add exactly one field per fact a scenario actually branches on — an event can
|
|
132
|
+
carry several fields, but the entity only needs the ones a `specifications[]` scenario checks. Do not
|
|
133
|
+
add a field just because the corresponding event happens to carry that data.
|
|
126
134
|
|
|
127
135
|
```java
|
|
128
136
|
package {basePackage}.slices.{context}.{slicename};
|
|
@@ -312,6 +320,8 @@ in all three places:
|
|
|
312
320
|
- `META-INF/additional-spring-configuration-metadata.json` — add a `{"name": "...", "type":
|
|
313
321
|
"java.lang.Boolean", "description": "..."}` entry
|
|
314
322
|
|
|
323
|
+
See [references/feature-flag-patterns.md](references/feature-flag-patterns.md) for the full pattern.
|
|
324
|
+
|
|
315
325
|
This flag is irrelevant to the Step 7 test below — that test never boots Spring, so
|
|
316
326
|
`@ConditionalOnProperty` never runs.
|
|
317
327
|
|
|
@@ -42,6 +42,13 @@ When starting to work on a slice, invoke the `update-slice-status` skill with `I
|
|
|
42
42
|
**CRITICAL: You MUST always use the provided skills to build slices. NEVER implement a slice manually.**
|
|
43
43
|
**ALL fields, event names, command names, and business rules MUST come exclusively from slice.json. Do NOT invent, assume, or guess any field or logic not present in the slice definition.**
|
|
44
44
|
|
|
45
|
+
**If, at any point below, the slice's requirements are genuinely ambiguous, contradictory, or missing
|
|
46
|
+
a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke the
|
|
47
|
+
`request-feedback` skill with the specific question; it posts the question as a comment on the slice
|
|
48
|
+
and marks it `Blocked`, and you then stop work on this slice for this run. This is an escalation path,
|
|
49
|
+
not a routine step — read `slice.json` and the matching build skill's own instructions fully first;
|
|
50
|
+
most slices are fully specified and need none of this.
|
|
51
|
+
|
|
45
52
|
When asked to build a slice, always follow this flow:
|
|
46
53
|
|
|
47
54
|
1. Read the slice definition from `.build-kit/.slices/<context>/<slicename>/slice.json`.
|
|
@@ -48,6 +48,17 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
|
|
|
48
48
|
18. append your new learnings to `.build-kit/AGENTS.md` in a compressed form, reusable for future iterations. Only add learnings if they are not already there.
|
|
49
49
|
19. Finish the iteration.
|
|
50
50
|
|
|
51
|
+
## Escalating Ambiguity
|
|
52
|
+
|
|
53
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
54
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
55
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
56
|
+
board (overriding the `InProgress` set earlier), then stop this iteration without finishing the
|
|
57
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
58
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
59
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
60
|
+
this.
|
|
61
|
+
|
|
51
62
|
## Progress Report Format
|
|
52
63
|
|
|
53
64
|
APPEND to progress.txt (never replace, always append):
|
|
@@ -48,6 +48,8 @@ Inspect the `sliceStatus` in the payload:
|
|
|
48
48
|
|
|
49
49
|
This is the build trigger. Setting `InProgress` and building are one atomic step:
|
|
50
50
|
|
|
51
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the specific question; it posts the question as a comment on the slice and marks it `Blocked` (superseding the `InProgress` set in step 1), then drop this task without finishing the build. This is an escalation path, not a routine step — most slices are fully specified.
|
|
52
|
+
|
|
51
53
|
1. Immediately call `/update-slice-status` to set the slice to `InProgress` on the board.
|
|
52
54
|
|
|
53
55
|
**Claim conflict**: if this call reports the slice is already in `InProgress` (or any status other than `Planned`), another agent already claimed it first — this is expected, not an error. Log it in `progress.txt`, drop this task without building, and continue the loop (the next task will naturally cover the next slice). Do not retry.
|
|
@@ -32,6 +32,13 @@ When starting to work on a slice, invoke the `update-slice-status` skill with `I
|
|
|
32
32
|
**CRITICAL: You MUST always use the provided skills to build slices. NEVER implement a slice manually.**
|
|
33
33
|
**ALL fields, event names, command names, and business rules MUST come exclusively from slice.json. Do NOT invent, assume, or guess any field or logic not present in the slice definition.**
|
|
34
34
|
|
|
35
|
+
**If, at any point below, the slice's requirements are genuinely ambiguous, contradictory, or missing
|
|
36
|
+
a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke the
|
|
37
|
+
`request-feedback` skill with the specific question; it posts the question as a comment on the slice
|
|
38
|
+
and marks it `Blocked`, and you then stop work on this slice for this run. This is an escalation path,
|
|
39
|
+
not a routine step — read `slice.json` and the matching build skill's own instructions fully first;
|
|
40
|
+
most slices are fully specified and need none of this.
|
|
41
|
+
|
|
35
42
|
When asked to build a slice, always follow this flow:
|
|
36
43
|
|
|
37
44
|
1. Read the slice definition from `.build-kit/.slices/<context>/<slicename>/slice.json`.
|
|
@@ -91,6 +91,17 @@ Use the skills available in `.claude/skills/` to interact with the board.
|
|
|
91
91
|
|
|
92
92
|
After completing a task, remove it from the array and write the updated array back to `.build-kit/tasks.json`. If the array is now empty, write `[]`.
|
|
93
93
|
|
|
94
|
+
## Escalating Ambiguity
|
|
95
|
+
|
|
96
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
97
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
98
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
99
|
+
board (overriding the `InProgress` set earlier), then stop this iteration without finishing the
|
|
100
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
101
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
102
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
103
|
+
this.
|
|
104
|
+
|
|
94
105
|
## Progress Report Format
|
|
95
106
|
|
|
96
107
|
APPEND to `progress.txt` (never replace):
|
|
@@ -38,6 +38,17 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
|
|
|
38
38
|
17. Append your new learnings to `.build-kit/AGENTS.md` in a compressed form, reusable for future iterations. Only add learnings if they are not already there.
|
|
39
39
|
18. Finish the iteration.
|
|
40
40
|
|
|
41
|
+
## Escalating Ambiguity
|
|
42
|
+
|
|
43
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
44
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
45
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
46
|
+
board (overriding the `InProgress` set in step 5), then stop this iteration without finishing the
|
|
47
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
48
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
49
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
50
|
+
this.
|
|
51
|
+
|
|
41
52
|
## Progress Report Format
|
|
42
53
|
|
|
43
54
|
APPEND to progress.txt (never replace, always append):
|
|
@@ -9,6 +9,13 @@ and follow the conventions distilled in
|
|
|
9
9
|
`.build-kit/.claude/skills/_shared/cratis-conventions.md`. **The shipped example slice
|
|
10
10
|
under `SomeModule/SomeFeature/` is the concrete pattern to copy** — match its structure exactly.
|
|
11
11
|
|
|
12
|
+
**If a slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need in
|
|
13
|
+
order to proceed — do not guess, and do not build anyway.** Invoke the `request-feedback` skill with
|
|
14
|
+
the specific question; it posts the question as a comment on the slice and marks it `Blocked`, and you
|
|
15
|
+
then stop work on this slice for this run. This is an escalation path, not a routine step — read
|
|
16
|
+
`slice.json` and the matching build skill's own instructions fully first; most slices are fully
|
|
17
|
+
specified and need none of this.
|
|
18
|
+
|
|
12
19
|
## Structure (learn from `SomeModule/SomeFeature/`)
|
|
13
20
|
|
|
14
21
|
```
|
|
@@ -73,6 +73,17 @@ implementing.
|
|
|
73
73
|
until the backend compiles**. Always: Backend → `dotnet build` → Specs → Frontend → Composition.
|
|
74
74
|
Never implement a slice's frontend before its backend builds.
|
|
75
75
|
|
|
76
|
+
## Escalating Ambiguity
|
|
77
|
+
|
|
78
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
79
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
80
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
81
|
+
board (overriding the `InProgress` set earlier), then stop this iteration without finishing the
|
|
82
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
83
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
84
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
85
|
+
this.
|
|
86
|
+
|
|
76
87
|
## Progress Report Format
|
|
77
88
|
|
|
78
89
|
APPEND to `progress.txt` (never replace):
|
|
@@ -48,6 +48,8 @@ Inspect the `sliceStatus` in the payload:
|
|
|
48
48
|
|
|
49
49
|
This is the build trigger. Setting `InProgress` and building are one atomic step:
|
|
50
50
|
|
|
51
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the specific question; it posts the question as a comment on the slice and marks it `Blocked` (superseding the `InProgress` set in step 1), then drop this task without finishing the build. This is an escalation path, not a routine step — most slices are fully specified.
|
|
52
|
+
|
|
51
53
|
1. Immediately call `/update-slice-status` to set the slice to `InProgress` on the board.
|
|
52
54
|
|
|
53
55
|
2. Read the slice definition from `.build-kit/.slices/<contextSlug>/<sliceFolder>/slice.json` (written by `/load-slice`).
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -384,6 +384,16 @@ After the step is done, **every SCREEN and every AUTOMATION on the board must be
|
|
|
384
384
|
|
|
385
385
|
> **Placement rule**: A read model must be placed in a column that already contains a SCREEN or AUTOMATION it serves. Do not place read models in columns with no screen or automation — doing so creates orphaned read models that will never have a consumer.
|
|
386
386
|
|
|
387
|
+
### Pull field mappings from Step 3 — they are the spec, not a guess
|
|
388
|
+
|
|
389
|
+
**Do not re-derive read model needs from a screen's title or description alone, and do not rely on the orchestrator's phase-summary handoff for this** — if you arrived here via `eventmodeling-orchestrating-event-modeling`, the handoff after Step 3 is a short hand-written prose summary (`.trogonai/interviews/.../EVENTMODELING.md`), not the actual field data. It will not reliably carry the per-field mappings forward. Go back to the board itself:
|
|
390
|
+
|
|
391
|
+
For every SCREEN node, fetch it directly (`get_node`/`get_nodes`, never from memory) and read its `meta.fields`. Step 3 already required every field to carry a `mapping`, and for view fields that mapping is already in the exact form `"<ReadModelTitle>.<fieldName>"` — recorded specifically so this step doesn't have to re-guess it.
|
|
392
|
+
|
|
393
|
+
- **Group the screen's fields by the `<ReadModelTitle>` already named in their `mapping`.** That grouping — not a fresh read of the screen's visuals — is the read model's title and field list. Build the READMODEL node from it directly.
|
|
394
|
+
- If a field's `mapping` names a read model that isn't `"<CommandTitle>.<fieldName>"` or `"session:..."` or `"derived:..."`, it is a read-model reference — treat it as a requirement, not a suggestion.
|
|
395
|
+
- A screen with no fields, or with fields that carry no read-model-shaped mapping, is **not** evidence that it needs no read model. Re-check it against the three rules above (view screen / automation / command screen showing prior state) before concluding it's the rare blank-form exception — and say explicitly why it qualifies.
|
|
396
|
+
|
|
387
397
|
### Field data lineage — the `mapping` attribute on READMODEL fields
|
|
388
398
|
|
|
389
399
|
Every field on a READMODEL must carry a `mapping` that says exactly which event (or command) field it is projected from. Use one of these forms:
|
|
@@ -582,6 +592,16 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
582
592
|
|
|
583
593
|
Skip a connection silently if the target cell is empty. Log each created arrow: `→ connected EVENT→READMODEL "OrderPlaced"→"OrderStatusView"`, `→ connected READMODEL→SCREEN "OrderStatusView"→"Order Status Screen"`, or `→ connected READMODEL→AUTOMATION "OrderStatusView"→"Fulfillment Processor"`.
|
|
584
594
|
|
|
595
|
+
### Mandatory per-node verification (run before declaring this step done)
|
|
596
|
+
|
|
597
|
+
Do not declare Step 5 complete on the strength of the read models you happened to design. Instead, **re-fetch every SCREEN and AUTOMATION node on the board** (`get_nodes` per type — don't rely on the list built earlier in this step, the board may have moved on) and check each one individually:
|
|
598
|
+
|
|
599
|
+
1. Does it now have an incoming `READMODEL → SCREEN` or `READMODEL → AUTOMATION` connection?
|
|
600
|
+
2. If not — is it a provably blank creation form with no prior state? State the reason in one line (e.g. `"Register Account" screen: blank form, no prior state — exempt`).
|
|
601
|
+
3. If it's neither connected nor exempt, it is an **unresolved gap**. Fix it now: design the missing read model (pulling from its `meta.fields`/`mapping` as above) and wire the connection. Do not move to Step 6 with an unresolved gap silently carried forward — either fix it or explicitly flag it to the user as accepted debt.
|
|
602
|
+
|
|
603
|
+
List the result of this pass (connected / exempt / fixed) for every screen and automation checked — this list is the evidence the orchestrator's Step 5 gate ("every screen data need is satisfied by a read model") actually holds, not just an assumption.
|
|
604
|
+
|
|
585
605
|
After all read models, screens, automations, and connections are in place, present the Read Model Catalog summary as text to the user.
|
|
586
606
|
|
|
587
607
|
---
|
|
@@ -694,8 +714,8 @@ Identify UI needs without event sources:
|
|
|
694
714
|
|
|
695
715
|
### Read Model Design
|
|
696
716
|
- [ ] **Typical pattern applied**: most screens follow `READ MODEL → SCREEN → COMMAND → EVENT`
|
|
697
|
-
- [ ] **Every SCREEN from storyboarding is connected to at least one read model** (via `READMODEL → SCREEN`); only blank creation forms may be exempt
|
|
698
|
-
- [ ] **Every AUTOMATION from storyboarding is connected to at least one read model** (via `READMODEL → AUTOMATION`)
|
|
717
|
+
- [ ] **Every SCREEN from storyboarding is connected to at least one read model** (via `READMODEL → SCREEN`); only blank creation forms may be exempt — verified via the mandatory per-node pass above, not assumed
|
|
718
|
+
- [ ] **Every AUTOMATION from storyboarding is connected to at least one read model** (via `READMODEL → AUTOMATION`) — same per-node verification
|
|
699
719
|
- [ ] **No read model is placed without a connected SCREEN or AUTOMATION consumer**
|
|
700
720
|
- [ ] Every read model has clear purpose
|
|
701
721
|
- [ ] Every data field has event source
|
|
@@ -79,6 +79,8 @@ This skill only has a `pages`/`backgroundColor` field to send (no separate marks
|
|
|
79
79
|
|
|
80
80
|
Apply these only to the specific element(s) the request describes — don't guess at additional areas to call out.
|
|
81
81
|
|
|
82
|
+
**Marked screens and field scoping**: when the same underlying screen is rendered multiple times as separate nodes — once per slice, each with a different mark/highlight calling out a different part of the UI — scope each node's `meta.fields` (Step 5 below) to only the data inside that node's highlighted area, not the full screen. Three slice-specific screen nodes sharing one visual base should end up with three different, narrower field lists, each matching what that node's mark calls out.
|
|
83
|
+
|
|
82
84
|
## Step 4 — Render the pages
|
|
83
85
|
|
|
84
86
|
**Updating an existing node** (`nodeId` was given) — always sends the **complete** pages array, not just the changed/new entry:
|
|
@@ -133,7 +135,54 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/html-screen-nodes/$N
|
|
|
133
135
|
|
|
134
136
|
Expect `204 No Content` on success from either curl call.
|
|
135
137
|
|
|
136
|
-
## Step 5 —
|
|
138
|
+
## Step 5 — Define field data lineage (mandatory)
|
|
139
|
+
|
|
140
|
+
Every screen — new or updated — needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a `mapping` naming where that data comes from. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even if the mockup itself looks complete.
|
|
141
|
+
|
|
142
|
+
| Field type | `mapping` | Example |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| User types a value, sent as a command | `"<CommandTitle>.<fieldName>"` | `"ReserveBike.bikeId"` |
|
|
145
|
+
| Read from session | `"session:<fieldName>"` | `"session:customerId"` |
|
|
146
|
+
| Displayed data, sourced from a read model | `"<ReadModelTitle>.<fieldName>"` | `"ActiveReservationView.status"` |
|
|
147
|
+
| Calculated/formatted only for display | `"derived:<expression>"` | `"derived:formatDuration(durationMinutes)"` |
|
|
148
|
+
|
|
149
|
+
Name the read model even if it doesn't exist as a board node yet — this skill only renders the screen, it does not create READMODEL nodes or connections (that's `eventmodeling-identifying-outputs` or `place-element`, if the model is taken that far). But naming the source is **not optional**: a screen displaying data should almost never have a field with no mapping. If you can't say which read model a displayed field comes from, that's a sign the model is missing something — not a reason to skip the field.
|
|
150
|
+
|
|
151
|
+
If this node is one of several sharing the same visual base with different marks/highlights (see "Marked screens and field scoping" above), only list the fields that fall inside *this* node's highlighted area — not every field the shared screen shows.
|
|
152
|
+
|
|
153
|
+
Set `cardinality` too (`"Single"` unless the field is a repeated/list value), then push the fields onto the node:
|
|
154
|
+
|
|
155
|
+
**Prefer MCP:**
|
|
156
|
+
```
|
|
157
|
+
mcp__eventmodelers__submit_node_events {
|
|
158
|
+
"boardId": "<BOARD_ID>",
|
|
159
|
+
"events": [{
|
|
160
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
161
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
162
|
+
"changedAttributes": ["meta.fields"],
|
|
163
|
+
"meta": { "type": "HTML_SCREEN", "fields": [
|
|
164
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
165
|
+
] }
|
|
166
|
+
}]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Fallback (no MCP):**
|
|
171
|
+
```bash
|
|
172
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
173
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: agent" \
|
|
174
|
+
-H "Content-Type: application/json" \
|
|
175
|
+
-d '[{
|
|
176
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
177
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
178
|
+
"changedAttributes": ["meta.fields"],
|
|
179
|
+
"meta": { "type": "HTML_SCREEN", "fields": [
|
|
180
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
181
|
+
] }
|
|
182
|
+
}]'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Step 6 — Report back
|
|
137
186
|
|
|
138
187
|
Tell the user:
|
|
139
188
|
- The node ID that was created or updated
|
|
@@ -35,6 +35,9 @@ Before making any API calls, plan all N screens. For each screen, decide:
|
|
|
35
35
|
- `screenTitle` — human-readable name (e.g. "Enter Credentials")
|
|
36
36
|
- `pages` (default) — one or more complete HTML/CSS fragments for this screen (see "HTML page design" below), or `elements` — a minimal list of grid elements (see "Sketch path" below, aim for 5–8 elements) **only** when the sketch path applies for this storyboard
|
|
37
37
|
- `visualDescription` — a prose description of the screen's visual layout and content (2–4 sentences) that lets someone who cannot see the image understand what is shown: what UI sections appear, what text/labels are visible, where buttons and inputs are placed, and the overall purpose of the screen
|
|
38
|
+
- `fields` — one entry per piece of data this screen displays or captures, each with a `mapping` naming its source (see "Field data lineage" in Step 5b below). Plan this alongside the visuals, not as an afterthought — every displayed value needs a named source.
|
|
39
|
+
|
|
40
|
+
If several screens in this storyboard share the same visual base but each highlights a different part of it (e.g. one shared mockup, marked up differently per slice), scope each screen's `fields` to only the data inside *that* screen's highlighted area — not the full shared mockup. Different highlight, different (narrower) field list.
|
|
38
41
|
|
|
39
42
|
Then **create one task per screen** using TaskCreate, naming each task after the screen title. This gives you a visible queue of work. Create the screens directly after each task has been planned.
|
|
40
43
|
|
|
@@ -257,6 +260,44 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/image-nodes/$SCREEN_
|
|
|
257
260
|
|
|
258
261
|
Pass the already-computed `actorCellId` directly as `cellId` in either path. Expect success (MCP: `created: true`; curl: `204`). On failure, read the validation error, fix the payload, and retry once before reporting failure.
|
|
259
262
|
|
|
263
|
+
### Step 5b(ii) — Set field data lineage (mandatory)
|
|
264
|
+
|
|
265
|
+
Push the `fields` planned in Step 2 onto the node with a `node:changed` call. Every field needs a `mapping`:
|
|
266
|
+
|
|
267
|
+
| Field type | `mapping` | Example |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| User types a value, sent as a command | `"<CommandTitle>.<fieldName>"` | `"ReserveBike.bikeId"` |
|
|
270
|
+
| Displayed data, sourced from a read model | `"<ReadModelTitle>.<fieldName>"` | `"ActiveReservationView.status"` |
|
|
271
|
+
| Calculated/formatted only for display | `"derived:<expression>"` | `"derived:formatDuration(durationMinutes)"` |
|
|
272
|
+
|
|
273
|
+
Name the read model even if it doesn't exist as a board node yet — this skill only creates SCREEN/HTML_SCREEN nodes, never READMODEL nodes or connections. But naming the source is **not optional**: a screen displaying data should almost never have a field with no mapping. Set `cardinality` too (`"Single"` unless it's a repeated/list value).
|
|
274
|
+
|
|
275
|
+
**Prefer MCP:**
|
|
276
|
+
```
|
|
277
|
+
mcp__eventmodelers__submit_node_events {
|
|
278
|
+
"boardId": "<BOARD_ID>",
|
|
279
|
+
"events": [{
|
|
280
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<SCREEN_NODE_ID>",
|
|
281
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
282
|
+
"changedAttributes": ["meta.fields"],
|
|
283
|
+
"meta": { "type": "HTML_SCREEN", "fields": [ /* planned fields */ ] }
|
|
284
|
+
}]
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Fallback (no MCP):**
|
|
289
|
+
```bash
|
|
290
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
291
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: agent" \
|
|
292
|
+
-H "Content-Type: application/json" \
|
|
293
|
+
-d '[{
|
|
294
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<SCREEN_NODE_ID>",
|
|
295
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
296
|
+
"changedAttributes": ["meta.fields"],
|
|
297
|
+
"meta": { "type": "HTML_SCREEN", "fields": [ /* planned fields */ ] }
|
|
298
|
+
}]'
|
|
299
|
+
```
|
|
300
|
+
|
|
260
301
|
### Step 5c — Mark the task complete
|
|
261
302
|
|
|
262
303
|
After the node and sketch succeed, mark the task for this screen as completed using TaskUpdate.
|
|
@@ -129,7 +129,52 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/images/$NODE_ID/sket
|
|
|
129
129
|
|
|
130
130
|
Expect `204 No Content` on success.
|
|
131
131
|
|
|
132
|
-
## Step 5 —
|
|
132
|
+
## Step 5 — Define field data lineage (mandatory)
|
|
133
|
+
|
|
134
|
+
Every screen — new or updated — needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a `mapping` naming where that data comes from. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even once the wireframe is rendered.
|
|
135
|
+
|
|
136
|
+
| Field type | `mapping` | Example |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| User types a value, sent as a command | `"<CommandTitle>.<fieldName>"` | `"ReserveBike.bikeId"` |
|
|
139
|
+
| Read from session | `"session:<fieldName>"` | `"session:customerId"` |
|
|
140
|
+
| Displayed data, sourced from a read model | `"<ReadModelTitle>.<fieldName>"` | `"ActiveReservationView.status"` |
|
|
141
|
+
| Calculated/formatted only for display | `"derived:<expression>"` | `"derived:formatDuration(durationMinutes)"` |
|
|
142
|
+
|
|
143
|
+
Name the read model even if it doesn't exist as a board node yet — this skill only renders the screen, it does not create READMODEL nodes or connections. But naming the source is **not optional**: a screen displaying data should almost never have a field with no mapping. If you can't say which read model a displayed field comes from, that's a sign the model is missing something — not a reason to skip the field.
|
|
144
|
+
|
|
145
|
+
Set `cardinality` too (`"Single"` unless the field is a repeated/list value), then push the fields onto the node:
|
|
146
|
+
|
|
147
|
+
**Prefer MCP:**
|
|
148
|
+
```
|
|
149
|
+
mcp__eventmodelers__submit_node_events {
|
|
150
|
+
"boardId": "<BOARD_ID>",
|
|
151
|
+
"events": [{
|
|
152
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
153
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
154
|
+
"changedAttributes": ["meta.fields"],
|
|
155
|
+
"meta": { "type": "SCREEN", "fields": [
|
|
156
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
157
|
+
] }
|
|
158
|
+
}]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Fallback (no MCP):**
|
|
163
|
+
```bash
|
|
164
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
165
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: agent" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
-d '[{
|
|
168
|
+
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
169
|
+
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
170
|
+
"changedAttributes": ["meta.fields"],
|
|
171
|
+
"meta": { "type": "SCREEN", "fields": [
|
|
172
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
173
|
+
] }
|
|
174
|
+
}]'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Step 6 — Report back
|
|
133
178
|
|
|
134
179
|
Tell the user:
|
|
135
180
|
- The node ID that was updated
|
|
@@ -35,6 +35,13 @@ When starting to work on a slice, invoke the `update-slice-status` skill with `I
|
|
|
35
35
|
**CRITICAL: You MUST always use the provided skills to build slices. NEVER implement a slice manually.**
|
|
36
36
|
**ALL fields, event names, command names, and business rules MUST come exclusively from slice.json. Do NOT invent, assume, or guess any field or logic not present in the slice definition.**
|
|
37
37
|
|
|
38
|
+
**If, at any point below, the slice's requirements are genuinely ambiguous, contradictory, or missing
|
|
39
|
+
a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke the
|
|
40
|
+
`request-feedback` skill with the specific question; it posts the question as a comment on the slice
|
|
41
|
+
and marks it `Blocked`, and you then stop work on this slice for this run. This is an escalation path,
|
|
42
|
+
not a routine step — read `slice.json` and the matching build skill's own instructions fully first;
|
|
43
|
+
most slices are fully specified and need none of this.
|
|
44
|
+
|
|
38
45
|
When asked to build a slice, always follow this flow:
|
|
39
46
|
|
|
40
47
|
1. Read the slice definition from `.build-kit/.slices/<context>/<slicename>/slice.json`.
|
|
@@ -42,6 +42,17 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
|
|
|
42
42
|
18. append your new learnings to `.build-kit/AGENTS.md` in a compressed form, reusable for future iterations. Only add learnings if they are not already there.
|
|
43
43
|
19. Finish the iteration.
|
|
44
44
|
|
|
45
|
+
## Escalating Ambiguity
|
|
46
|
+
|
|
47
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
48
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
49
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
50
|
+
board (overriding the `InProgress` set earlier), then stop this iteration without finishing the
|
|
51
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
52
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
53
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
54
|
+
this.
|
|
55
|
+
|
|
45
56
|
## Progress Report Format
|
|
46
57
|
|
|
47
58
|
APPEND to progress.txt (never replace, always append):
|
|
@@ -48,6 +48,8 @@ Inspect the `sliceStatus` in the payload:
|
|
|
48
48
|
|
|
49
49
|
This is the build trigger. Setting `InProgress` and building are one atomic step:
|
|
50
50
|
|
|
51
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the specific question; it posts the question as a comment on the slice and marks it `Blocked` (superseding the `InProgress` set in step 1), then drop this task without finishing the build. This is an escalation path, not a routine step — most slices are fully specified.
|
|
52
|
+
|
|
51
53
|
1. Immediately call `/update-slice-status` to set the slice to `InProgress` on the board.
|
|
52
54
|
|
|
53
55
|
**Claim conflict**: if this call reports the slice is already in `InProgress` (or any status other than `Planned`), another agent already claimed it first — this is expected, not an error. Log it in `progress.txt`, drop this task without building, and continue the loop (the next task will naturally cover the next slice). Do not retry.
|
|
@@ -35,6 +35,13 @@ When starting to work on a slice, invoke the `update-slice-status` skill with `I
|
|
|
35
35
|
**CRITICAL: You MUST always use the provided skills to build slices. NEVER implement a slice manually.**
|
|
36
36
|
**ALL fields, event names, command names, and business rules MUST come exclusively from slice.json. Do NOT invent, assume, or guess any field or logic not present in the slice definition.**
|
|
37
37
|
|
|
38
|
+
**If, at any point below, the slice's requirements are genuinely ambiguous, contradictory, or missing
|
|
39
|
+
a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke the
|
|
40
|
+
`request-feedback` skill with the specific question; it posts the question as a comment on the slice
|
|
41
|
+
and marks it `Blocked`, and you then stop work on this slice for this run. This is an escalation path,
|
|
42
|
+
not a routine step — read `slice.json` and the matching build skill's own instructions fully first;
|
|
43
|
+
most slices are fully specified and need none of this.
|
|
44
|
+
|
|
38
45
|
When asked to build a slice, always follow this flow:
|
|
39
46
|
|
|
40
47
|
1. Read the slice definition from `.build-kit/.slices/<context>/<slicename>/slice.json`.
|
|
@@ -42,6 +42,17 @@ You work within **exactly ONE context at a time** — the one named in `.build-k
|
|
|
42
42
|
18. append your new learnings to `.build-kit/AGENTS.md` in a compressed form, reusable for future iterations. Only add learnings if they are not already there.
|
|
43
43
|
19. Finish the iteration.
|
|
44
44
|
|
|
45
|
+
## Escalating Ambiguity
|
|
46
|
+
|
|
47
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need
|
|
48
|
+
in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the
|
|
49
|
+
specific question; it posts the question as a comment on the slice and marks it `Blocked` on the
|
|
50
|
+
board (overriding the `InProgress` set earlier), then stop this iteration without finishing the
|
|
51
|
+
build — reply `<promise>DONE</promise>` as if the iteration's work was to raise the question, not to
|
|
52
|
+
implement the slice. This is an escalation path, not a routine step — read the slice.json and the
|
|
53
|
+
matching build skill's own instructions fully first; most slices are fully specified and need none of
|
|
54
|
+
this.
|
|
55
|
+
|
|
45
56
|
## Progress Report Format
|
|
46
57
|
|
|
47
58
|
APPEND to progress.txt (never replace, always append):
|
|
@@ -48,6 +48,8 @@ Inspect the `sliceStatus` in the payload:
|
|
|
48
48
|
|
|
49
49
|
This is the build trigger. Setting `InProgress` and building are one atomic step:
|
|
50
50
|
|
|
51
|
+
**If the slice's requirements are genuinely ambiguous, contradictory, or missing a decision you need in order to proceed — do not guess, and do not build anyway.** Invoke `/request-feedback` with the specific question; it posts the question as a comment on the slice and marks it `Blocked` (superseding the `InProgress` set in step 1), then drop this task without finishing the build. This is an escalation path, not a routine step — most slices are fully specified.
|
|
52
|
+
|
|
51
53
|
1. Immediately call `/update-slice-status` to set the slice to `InProgress` on the board.
|
|
52
54
|
|
|
53
55
|
**Claim conflict**: if this call reports the slice is already in `InProgress` (or any status other than `Planned`), another agent already claimed it first — this is expected, not an error. Log it in `progress.txt`, drop this task without building, and continue the loop (the next task will naturally cover the next slice). Do not retry.
|