@eventmodelers/cli 1.0.72 → 1.0.74

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 (75) hide show
  1. package/README.md +29 -8
  2. package/RELEASE_NOTES.md +5 -0
  3. package/cli.js +119 -28
  4. package/package.json +2 -2
  5. package/shared/demo-slices/Understanding Eventsourcing/additem/slice.json +176 -0
  6. package/shared/demo-slices/Understanding Eventsourcing/archiveitem/slice.json +209 -0
  7. package/shared/demo-slices/Understanding Eventsourcing/cartitems/slice.json +236 -0
  8. package/shared/demo-slices/Understanding Eventsourcing/cartpublicationfailed/slice.json +65 -0
  9. package/shared/demo-slices/Understanding Eventsourcing/cartpublished/slice.json +65 -0
  10. package/shared/demo-slices/Understanding Eventsourcing/cartswithproducts/slice.json +105 -0
  11. package/shared/demo-slices/Understanding Eventsourcing/changedprices/slice.json +91 -0
  12. package/shared/demo-slices/Understanding Eventsourcing/changeinventory/slice.json +191 -0
  13. package/shared/demo-slices/Understanding Eventsourcing/changeprice/slice.json +220 -0
  14. package/shared/demo-slices/Understanding Eventsourcing/clearcart/slice.json +219 -0
  15. package/shared/demo-slices/Understanding Eventsourcing/config.json +2631 -0
  16. package/shared/demo-slices/Understanding Eventsourcing/context.json +3 -0
  17. package/shared/demo-slices/Understanding Eventsourcing/index.json +132 -0
  18. package/shared/demo-slices/Understanding Eventsourcing/inventories/slice.json +132 -0
  19. package/shared/demo-slices/Understanding Eventsourcing/itemadded/slice.json +137 -0
  20. package/shared/demo-slices/Understanding Eventsourcing/publishcart/slice.json +230 -0
  21. package/shared/demo-slices/Understanding Eventsourcing/removeitem/slice.json +239 -0
  22. package/shared/demo-slices/Understanding Eventsourcing/submitcart/slice.json +199 -0
  23. package/shared/demo-slices/Understanding Eventsourcing/submittedcartdata/slice.json +90 -0
  24. package/shared/demo-slices/current_context.json +3 -0
  25. package/stacks/blank/templates/.claude/skills/build-automation/SKILL.md +1 -1
  26. package/stacks/blank/templates/.claude/skills/build-state-change/SKILL.md +1 -1
  27. package/stacks/blank/templates/.claude/skills/build-state-view/SKILL.md +1 -1
  28. package/stacks/blank/templates/root/README.md +1 -1
  29. package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +8 -6
  30. package/stacks/modeling-kit/templates/kit/CLAUDE.md +12 -7
  31. package/stacks/modeling-kit/templates/kit/README.md +1 -1
  32. package/stacks/react/templates/.claude/skills/build-automation/SKILL.md +1 -1
  33. package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +1 -1
  34. package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +1 -1
  35. package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +0 -251
  36. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +0 -122
  37. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +0 -115
  38. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +0 -191
  39. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +0 -234
  40. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +0 -149
  41. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +0 -166
  42. package/stacks/cratis-csharp/templates/build-kit/CLAUDE.md +0 -78
  43. package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +0 -59
  44. package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +0 -140
  45. package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +0 -126
  46. package/stacks/cratis-csharp/templates/root/.frontend/index.css +0 -29
  47. package/stacks/cratis-csharp/templates/root/.frontend/index.html +0 -17
  48. package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +0 -18
  49. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +0 -42
  50. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +0 -11
  51. package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +0 -56
  52. package/stacks/cratis-csharp/templates/root/App.tsx +0 -23
  53. package/stacks/cratis-csharp/templates/root/CratisApp.csproj +0 -25
  54. package/stacks/cratis-csharp/templates/root/CratisApp.sln +0 -18
  55. package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +0 -3
  56. package/stacks/cratis-csharp/templates/root/Home.tsx +0 -102
  57. package/stacks/cratis-csharp/templates/root/Program.cs +0 -26
  58. package/stacks/cratis-csharp/templates/root/README.md +0 -192
  59. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +0 -47
  60. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +0 -11
  61. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +0 -12
  62. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +0 -17
  63. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +0 -1
  64. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +0 -51
  65. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +0 -18
  66. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +0 -27
  67. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +0 -1
  68. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +0 -22
  69. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +0 -3
  70. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +0 -1
  71. package/stacks/cratis-csharp/templates/root/appsettings.Development.json +0 -9
  72. package/stacks/cratis-csharp/templates/root/appsettings.json +0 -26
  73. package/stacks/cratis-csharp/templates/root/docker-compose.yml +0 -23
  74. package/stacks/cratis-csharp/templates/root/package.json +0 -33
  75. package/stacks/cratis-csharp/templates/root/tsconfig.json +0 -3
package/README.md CHANGED
@@ -18,7 +18,6 @@ Running without `--stack` shows an arrow-key picker. Or go straight to a stack:
18
18
  npx @eventmodelers/cli init --stack node # Node.js / TypeScript
19
19
  npx @eventmodelers/cli init --stack supabase # Supabase
20
20
  npx @eventmodelers/cli init --stack axon # Axon Framework (Java/Kotlin)
21
- npx @eventmodelers/cli init --stack cratis-csharp # Cratis (.NET/C#)
22
21
  npx @eventmodelers/cli init --stack opencqrs # OpenCQRS (Java, EventSourcingDB)
23
22
  npx @eventmodelers/cli init --stack umadb # UmaDB (Java)
24
23
  npx @eventmodelers/cli init --stack kurrent # Kurrent (Java, KurrentDB)
@@ -38,6 +37,16 @@ npx @eventmodelers/cli init --stack node
38
37
 
39
38
  Answer the credential prompts once — the stack's scaffold, skills, and agent loop are all installed in this one step.
40
39
 
40
+ **Trying it out before you have a board of your own:**
41
+
42
+ ```bash
43
+ npx @eventmodelers/cli init --stack node --demo
44
+ ```
45
+
46
+ `--demo` additionally writes a ready-made model into the kit's `.slices/` — the **Understanding Eventsourcing** context, a 16-slice shopping cart covering every slice type (state change, state view, automation, translation). It's ordinary fetched slice data in exactly the shape `fetch` writes, so the build skills, `activate-context`, `set-slice-status`, and the agent loop all work against it immediately, with no board connected. Build one with `/build-state-change` in Claude Code, or start `run` and let the agent work the queue.
47
+
48
+ Nothing downstream treats it specially: `fetch --context <name>` replaces it with your own board whenever you're ready. `--demo` is skipped (with a message) if `.slices/` already holds slices, so it can never overwrite fetched work.
49
+
41
50
  **Already initialized — you just want the agent to start working the board:**
42
51
 
43
52
  ```bash
@@ -71,14 +80,15 @@ your-project/
71
80
  │ ├── ralph-claude.js ← realtime agent + task loop
72
81
  │ ├── ralph-ollama.js ← same, via local Ollama
73
82
  │ ├── ralph.sh ← bash-only loop (no realtime)
74
- └── lib/ ← stack-specific agent prompts + helpers
83
+ ├── lib/ ← stack-specific agent prompts + helpers
84
+ │ └── .slices/ ← board slices, written by `fetch`/`listen` (or pre-seeded by `init --demo`)
75
85
  ├── .claude/
76
86
  │ └── skills/ ← eventmodelers skills for Claude Code
77
87
  ├── src/ … (or the stack's own layout)
78
88
  └── CLAUDE.md ← agent instructions
79
89
  ```
80
90
 
81
- The seven backend stacks (`node`, `supabase`, `axon`, `cratis-csharp`, `opencqrs`, `umadb`, `kurrent`) also scaffold a real project skeleton into your project root (`templates/root/`) — source layout, build files, migrations, etc.
91
+ The six backend stacks (`node`, `supabase`, `axon`, `opencqrs`, `umadb`, `kurrent`) also scaffold a real project skeleton into your project root (`templates/root/`) — source layout, build files, migrations, etc.
82
92
 
83
93
  `react` and `supabase-react` are two more registered stacks (installable the same way). `supabase-react` is real, filled-in content — a Vite + React 19 + TypeScript scaffold that authenticates and issues command POSTs via a Supabase session (`src/lib/api.ts`/`src/lib/supabase.ts`), plus `init-style-guide`/`learn-styleguide` skills so generated UI stays on-brand. It's UI-only: `.build-kit/CLAUDE.md` only routes `STATE_CHANGE`/`STATE_VIEW` slices to `build-state-change`/`build-state-view` — an `AUTOMATION` slice has no UI counterpart and gets flagged via `request-feedback` instead, since it belongs to whichever backend stack is installed alongside this one. It needs no overrides at all and uses `shared/build-kit`'s realtime agent as-is.
84
94
 
@@ -111,6 +121,7 @@ Which skills install depends on the chosen stack — see `stacks/<name>/template
111
121
 
112
122
  ```bash
113
123
  npx @eventmodelers/cli init --stack <name> # scaffold a stack + install + configure (alias: install)
124
+ npx @eventmodelers/cli init --stack <name> --demo # same, plus a ready-made demo model in the kit's .slices/ to build against
114
125
  npx @eventmodelers/cli re-init # refresh an already-installed kit's scripts/skills only — never touches the root scaffold
115
126
  npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
116
127
  npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
@@ -130,7 +141,7 @@ npx @eventmodelers/cli config # print the fully resolved c
130
141
 
131
142
  `run` is a thin dispatcher — it just finds the installed kit dir (whatever it's named for the stack) and execs the runner file already sitting in it. The agent loop's actual logic stays in the scaffolded `<kit-dir>/`, not in this package, since you (and the agent itself, via `AGENT.md`) may customize those files per project.
132
143
 
133
- `fetch` calls `slicedata?contextName=<name>` for the required `--context` (full slice detail — commands/events/readmodels/screens/processors/specifications/comments), and writes `.slices/<context>/<slice>/slice.json`, `index.json`, and `context.json`. It does not fetch screen images (those only arrive via `listen`'s push, see Power users). Unlike every other command, `fetch` also works with no kit installed at all — it only needs credentials, not kit-specific files. If credentials are missing, it prompts the same way `init-config` does. `--slice-id`/`--slice-title` still fetch and persist the whole context, then just print the one slice you asked about.
144
+ `fetch` calls `slicedata?contextName=<name>` for the required `--context` (full slice detail — commands/events/readmodels/screens/processors/specifications/comments), and writes `.slices/<context>/<slice>/slice.json`, `index.json`, and `context.json`. It does not fetch screen images (those only arrive via `listen`'s push, see Power users). Unlike every other command, `fetch` also works with no kit installed at all — it only needs credentials, not kit-specific files. If credentials are missing, it prompts the same way `init-config` does. `--slice-id`/`--slice-title` still fetch and persist the whole context, then just print the one slice you asked about. `init --demo` seeds that same `.slices/` layout from a bundled example context instead of from a board, for trying the kit out before connecting one.
134
145
 
135
146
  ---
136
147
 
@@ -150,11 +161,19 @@ npx @eventmodelers/cli init --build-kit # blank build-kit scaffold
150
161
  **Building a new kit for an unsupported stack:**
151
162
 
152
163
  ```bash
153
- npx @eventmodelers/cli init --build-kit
164
+ npx @eventmodelers/cli init --build-kit --demo
154
165
  ```
155
166
 
156
167
  This scaffolds `.build-kit/CLAUDE.md`, `lib/prompt.md`, `lib/backend-prompt.md`, and the `build-*` skills with TODO placeholders instead of real content. Fill in the TODOs against the actual stack you're integrating (build/test commands, file layout, framework idioms) while building something real with it, then follow "Adding a stack" below to promote it to a first-class stack once it works.
157
168
 
169
+ `--demo` is what gives you something to build *against* while you do that: it seeds the kit's `.slices/` with the ready-made 16-slice **Understanding Eventsourcing** model (see "Trying it out before you have a board of your own" above), so you don't need a board, credentials, or a connected project to exercise the kit you're writing. With it in place the whole loop is:
170
+
171
+ ```bash
172
+ npx @eventmodelers/cli run --local
173
+ ```
174
+
175
+ and the agent starts building — `--local` skips platform config and credential lookup entirely, so it works straight out of `init` with nothing connected. Every TODO you fill in gets exercised on the next iteration against real slice data covering all four slice types (state change, state view, automation, translation), which is exactly the coverage a new stack's `build-*` skills need before it's worth promoting.
176
+
158
177
  Installing both a build stack and `init-modeling` into the same project reuses this one `.eventmodelers/config.json` — run whichever `init` command second and it finds the existing config already satisfies the required fields and skips straight past the credential prompt.
159
178
 
160
179
  ### The modeling agent — `run --modeling` and `--standalone`
@@ -284,10 +303,10 @@ windows if the defaults don't suit your board:
284
303
 
285
304
  | Env var | Default | What it controls |
286
305
  |---|---|---|
287
- | `EVENTMODELERS_STANDALONE_DEBOUNCE_MS` | `8000` | quiet period before buffered board changes turn into a turn |
306
+ | `EVENTMODELERS_STANDALONE_DEBOUNCE_MS` | `2500` | quiet period before buffered board changes turn into a turn — long enough to coalesce one gesture (a placement, a drag) into a single turn |
288
307
  | `EVENTMODELERS_STANDALONE_MAX_WAIT_MS` | `90000` | cap on that quiet period, so a board being edited continuously still gets a turn |
289
- | `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `20000` | after a turn, how long an *unattributed* incoming change is labelled as probably the agent's own echo (attributed ones are identified outright) |
290
- | `EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS` | `60000` | floor between two self-directed turns |
308
+ | `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `0` (off) | after a turn, how long an *unattributed* incoming change is labelled as probably the agent's own echo. Off because every event carries `agent_id`/`user_id`, which answers the same question exactly and costs no delay; set it only on a backend whose writes arrive unattributed (the run log names the origin of every change) |
309
+ | `EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS` | `60000` | floor between two self-directed turns — a runaway-loop guard, so a change written from a browser session skips it (a person is not a loop; another agent's write still waits) |
291
310
  | `EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS` | `900000` | ceiling that floor doubles up to while turns keep answering `NOOP` |
292
311
  | `EVENTMODELERS_STANDALONE_IDLE_MS` | `900000` | with nothing happening at all, how long before the agent reviews the model anyway (`0` disables it) |
293
312
 
@@ -541,6 +560,8 @@ npx @eventmodelers/cli uninstall --modeling-kit # remove .agent-modeling-ki
541
560
 
542
561
  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.
543
562
 
563
+ The demo model `init --demo` installs is shared the same way, in `shared/demo-slices/` — a verbatim `fetch --format json` tree (`current_context.json` plus `<context>/{context,index,config}.json` and `<slice>/slice.json`), copied as-is into the kit's `.slices/`. It's stack-agnostic board data, so a new stack gets `--demo` for free. To replace or extend it, `fetch` a context into an empty directory and copy the result in, then update `DEMO_CONTEXT_NAME`/`DEMO_SLICE_COUNT` in `cli.js` (used only for the line `init` prints).
564
+
544
565
  Once your `init --build-kit` scaffold (see above) works against a real backend, promote it to a first-class stack:
545
566
 
546
567
  1. Copy `.build-kit/` → `stacks/<name>/templates/build-kit/`, `.claude/skills/build-*` → `stacks/<name>/templates/.claude/skills/`, and whatever `root/` scaffold you built → `stacks/<name>/templates/root/`.
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v1.0.72
2
+
3
+ ### Features
4
+ - `init --demo` seeds the kit's `.slices/` with a ready-made model — the "Understanding Eventsourcing" context, a 16-slice shopping cart covering every slice type — so the `build-*` skills, `activate-context`, `set-slice-status`, and the agent loop all have something real to work on before the project is connected to a board. It is a verbatim `fetch --format json` tree, so nothing downstream has a demo-only path and a later `fetch --context <name>` simply replaces it. Skipped with a message when `.slices/` already holds fetched slices, so it can never overwrite real board state. Works with every install mode (`--stack`, `--modeling`, `--build-kit`, `--bridge`, `--git`); modeling-kit gets it at the project root, matching where `fetch` writes for that kit.
5
+
1
6
  ## v1.0.56
2
7
 
3
8
  ### Features
package/cli.js CHANGED
@@ -63,13 +63,6 @@ const STACKS = {
63
63
  useShared: true,
64
64
  needsBoardId: true,
65
65
  },
66
- 'cratis-csharp': {
67
- label: 'Cratis (.NET/C#)',
68
- kitSubdir: 'build-kit',
69
- kitDirName: '.build-kit',
70
- useShared: true,
71
- needsBoardId: true,
72
- },
73
66
  opencqrs: {
74
67
  label: 'OpenCQRS (Java, EventSourcingDB)',
75
68
  kitSubdir: 'build-kit',
@@ -176,6 +169,11 @@ const BLANK_BUILD_KIT = {
176
169
 
177
170
  const KIT_DIR_NAMES = [...new Set([...Object.values(STACKS), MODELING_KIT, BRIDGE_KIT, BLANK_BUILD_KIT].map((s) => s.kitDirName))];
178
171
 
172
+ // What `init --demo` installs, for the messages it prints. Kept in step with
173
+ // shared/demo-slices/ (context.json's name, and the slice folders beside it).
174
+ const DEMO_CONTEXT_NAME = 'Understanding Eventsourcing';
175
+ const DEMO_SLICE_COUNT = 16;
176
+
179
177
  // Same principle Playwright MCP uses per harness: one shared server, but each coding
180
178
  // agent has its own registration mechanism. Automate the ones with a real, verified
181
179
  // CLI install command; for the rest, print manual steps instead of guessing at an
@@ -1009,6 +1007,14 @@ async function installStack(stackKey, stackCfg, options = {}) {
1009
1007
  }
1010
1008
  }
1011
1009
 
1010
+ // --- 3c. Opt-in demo model (`init --demo`) ---
1011
+ // After the kit dir exists (the .slices/ tree nests inside it for every kit but
1012
+ // modeling-kit) and before npm install/credentials, so the "demo installed" line
1013
+ // lands with the rest of the file copying rather than after a credential prompt.
1014
+ if (options.demo) {
1015
+ installDemoSlices({ kitDir, targetDir, stackCfg });
1016
+ }
1017
+
1012
1018
  // --- 4. Install kit dependencies ---
1013
1019
  // modeling-kit's package.json has no dependencies at all — it exists purely for its
1014
1020
  // `"type": "module"`, so lib/config.js can be ESM-imported. Running npm for that buys
@@ -1281,6 +1287,46 @@ async function configureMcp(options = {}) {
1281
1287
  // standalone `init-hooks` command so all three copy/chmod/git-config identically
1282
1288
  // instead of drifting apart — callers are responsible for checking `hooksSrc`
1283
1289
  // exists first, since what "no template for this stack" means differs per caller.
1290
+ // `init --demo` — drop a ready-made .slices/ tree into the install so the build
1291
+ // skills (and `activate-context`/`slice-status`/the agent loop) have something real
1292
+ // to work on before the project is ever connected to a board. The tree under
1293
+ // shared/demo-slices/ is a verbatim `fetch --format json` output for the
1294
+ // "Understanding Eventsourcing" context (a 16-slice shopping-cart model, every slice
1295
+ // type represented), so it is byte-identical in shape to what a real fetch writes —
1296
+ // a later `fetch` just overwrites it, and nothing downstream needs a demo-only path.
1297
+ //
1298
+ // The destination mirrors `fetch`'s own resolution exactly (see the SLICES_DIR comment
1299
+ // there): nested under the kit dir for every kit whose code-export.mjs hardcodes
1300
+ // `.slices/` next to itself, and at the project root for modeling-kit, which has no
1301
+ // code-export.mjs and no skill reading a nested copy.
1302
+ function installDemoSlices({ kitDir, targetDir, stackCfg }) {
1303
+ const slicesDir = stackCfg.kitDirName === MODELING_KIT.kitDirName
1304
+ ? join(targetDir, '.slices')
1305
+ : join(kitDir, '.slices');
1306
+ const rel = relative(targetDir, slicesDir) || '.slices';
1307
+
1308
+ // An existing .slices/ is fetched board state — the user's real model. The copy
1309
+ // below merges rather than replaces, so installing over it would leave a half-demo,
1310
+ // half-real tree with a current_context.json pointing at the wrong one. Refuse
1311
+ // instead. Deliberately not overridable by --force: that flag means "don't re-ask
1312
+ // about credentials", never "discard fetched work".
1313
+ if (existsSync(slicesDir) && readdirSync(slicesDir).length > 0) {
1314
+ console.log(` ℹ️ --demo skipped — ${rel}/ already has slices in it (delete it first if you really want the demo model)`);
1315
+ return;
1316
+ }
1317
+
1318
+ const demoSrc = join(__dirname, 'shared', 'demo-slices');
1319
+ if (!existsSync(demoSrc)) {
1320
+ console.log(' ℹ️ --demo was given but this CLI build ships no shared/demo-slices/ — nothing to install');
1321
+ return;
1322
+ }
1323
+
1324
+ console.log('📦 Installing the demo model...');
1325
+ copyDirContents(demoSrc, slicesDir);
1326
+ console.log(` ✓ Demo context "${DEMO_CONTEXT_NAME}" (${DEMO_SLICE_COUNT} slices) is active in ${rel}/`);
1327
+ console.log(' ℹ️ It is ordinary fetched slice data — `fetch --context <name>` replaces it with your own board whenever you are ready');
1328
+ }
1329
+
1284
1330
  function configureHooks({ hooksSrc, targetDir }) {
1285
1331
  copyDirContents(hooksSrc, join(targetDir, '.githooks'));
1286
1332
  const preCommitHook = join(targetDir, '.githooks', 'pre-commit');
@@ -1890,11 +1936,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
1890
1936
  }
1891
1937
 
1892
1938
  // A standalone session is long-lived and spends most of its life waiting, so the setup
1893
- // every turn needs — read CLAUDE.md, run /connect, read the board — is done once at
1894
- // startup instead of being paid by whoever happens to send the first prompt. By the time
1895
- // a real turn arrives the credentials are resolved and the board picture is in context,
1896
- // and the turn is straight into the work. It reads only: nothing is placed, no prompt
1897
- // status is touched (there is no prompt_id here), no subagent is dispatched.
1939
+ // every turn needs — read CLAUDE.md, run /connect, find out which chapters exist — is done
1940
+ // once at startup instead of being paid by whoever happens to send the first prompt. By the
1941
+ // time a real turn arrives the credentials are resolved and the turn is straight into the
1942
+ // work. It reads only: nothing is placed, no prompt status is touched (there is no prompt_id
1943
+ // here), no subagent is dispatched.
1944
+ //
1945
+ // What it deliberately does *not* do is read the board. Reading every chapter up front cost a
1946
+ // minute and a dollar before anyone had asked for anything, on a board where a turn typically
1947
+ // touches one chapter — and it front-loaded the context every later turn then carries. Chapters
1948
+ // are read lazily instead: the first turn with business in one fetches its outline and keeps it
1949
+ // for the rest of the session, so the cost is paid once and only for chapters that see work.
1898
1950
  const WARM_UP_TASK =
1899
1951
  'This is the session warm-up, before any prompt or board change — nobody has asked for anything yet, and ' +
1900
1952
  'there is nothing to sanitize, no prompt_id and no progress entry. Do exactly this and then stop: ' +
@@ -1902,11 +1954,13 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
1902
1954
  'one-time reads for this session — do NOT read .agent-modeling-kit/CLAUDE-STANDALONE.md, that one still ' +
1903
1955
  'waits for the first self-directed turn; (2) invoke /connect with the credentials above and ' +
1904
1956
  `board=${cfg.boardId} — this is the session's one-time connect, so no later turn runs it again; ` +
1905
- '(3) orient yourself on the board: one get_board_outline per chapter (or get_nodes with ' +
1906
- 'projection: "line"), and keep what comes back as this session\'s board picturechapters, columns, ' +
1907
- 'elements, slice statuses so the first real turn starts from it instead of re-reading the board. ' +
1908
- 'Change nothing: no nodes, no comments, no slice statuses, no subagents. Reply <promise>READY</promise> ' +
1909
- 'with a one-line summary of the board (chapters, rough element count, slice statuses).';
1957
+ '(3) learn which chapters exist, and nothing beyond that: one get_chapter_bounds call returns every ' +
1958
+ 'chapter\'s id and title. Do NOT read any chapter\'s contents hereno get_board_outline, no ' +
1959
+ 'per-chapter get_nodes, and skip the board read /connect Step 5 would otherwise have you do. A chapter ' +
1960
+ 'is read on the first turn that actually has business in it, and kept for the rest of the session ' +
1961
+ 'from then on. Change nothing: no nodes, no comments, no slice statuses, no subagents. Reply ' +
1962
+ '<promise>READY</promise> with the chapter list — titles and how many; say nothing about what is in ' +
1963
+ 'them, you have not looked.';
1910
1964
 
1911
1965
  function buildWarmUpTurn() {
1912
1966
  const header = ['SESSION_START', `board_id=${cfg.boardId}`, `organization_id=${cfg.organizationId}`].join(' ');
@@ -1918,7 +1972,7 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
1918
1972
  function warmUpSession() {
1919
1973
  if (warmUp) return warmUp;
1920
1974
  if (!standalone) return (warmUp = Promise.resolve());
1921
- log('warm-up: connecting and reading the board before the first turn');
1975
+ log('warm-up: connecting and listing chapters before the first turn (chapters are read on first use)');
1922
1976
  warmingUp = true;
1923
1977
  warmUp = sendTurn(buildWarmUpTurn())
1924
1978
  .then((result) => log(`warm-up done — ${oneLine(result, 200) || 'session ready'}`))
@@ -2050,7 +2104,15 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2050
2104
  // org prompt queue, so the non-standalone case joins it too and simply throws every
2051
2105
  // event away (see onBoardEvent). One code path either way — and whatever the backend
2052
2106
  // later adds to these payloads lands here without a client change.
2053
- const BOARD_CHANGE_EVENTS = ['node:created', 'node:changed', 'node:deleted', 'edge:added', 'edge:removed', 'board:cleared'];
2107
+ //
2108
+ // The edge events are deliberately *not* in this list. An edge is almost never a change
2109
+ // on its own: placing an element auto-connects it to its neighbours, so `edge:added`
2110
+ // arrives as the tail of a `node:created` this loop already woke up for — the same
2111
+ // gesture counted twice, and counted onto `(board)` rather than onto a node, since an
2112
+ // edge payload names no single node to go look at. Wiring an existing chain by hand says
2113
+ // nothing about the model's content either. Subscribing to them bought a second turn per
2114
+ // placement and nothing else, so board changes here mean node changes.
2115
+ const BOARD_CHANGE_EVENTS = ['node:created', 'node:changed', 'node:deleted', 'board:cleared'];
2054
2116
 
2055
2117
  // Four knobs. They govern *when* a self-directed turn fires — never whether an event is
2056
2118
  // remembered: everything that arrives is buffered (see onBoardEvent), so a burst that
@@ -2068,10 +2130,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2068
2130
  // ECHO_WINDOW — how long after a turn its own writes are expected back; an
2069
2131
  // *unattributed* change in that window is labelled a possible echo, and
2070
2132
  // the next turn waits it out so a write and its echo don't each get one.
2133
+ // Off by default: it predates the `agent_id` above, which answers the same
2134
+ // question exactly and for free, and every turn paid its delay to cover a
2135
+ // residue of unattributed writes that a stamping backend never produces.
2136
+ // Set it (ms) on a backend where the logs below do show unattributed
2137
+ // changes.
2071
2138
  // MIN_INTERVAL — a floor between self-directed turns, so a mistake upstream can't
2072
2139
  // become a self-feeding loop burning tokens unattended. Doubles per
2073
2140
  // consecutive NOOP up to BACKOFF_CAP, and resets as soon as a turn
2074
- // actually does something.
2141
+ // actually does something. A person's edit is exempt (see
2142
+ // dispatchStandaloneTurn): a human cannot be the loop, and waiting a
2143
+ // minute before reacting to them is the whole latency complaint.
2075
2144
  // IDLE — with nothing at all happening on the board, how long before the agent
2076
2145
  // looks the model over anyway (a BOARD_REVIEW turn). 0 disables it, and
2077
2146
  // it answers to the same MIN_INTERVAL backoff, so a board with nothing
@@ -2080,18 +2149,22 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2080
2149
  const raw = Number(process.env[name]);
2081
2150
  return Number.isFinite(raw) && raw >= 0 ? raw : fallback;
2082
2151
  };
2083
- const STANDALONE_DEBOUNCE_MS = envMs('EVENTMODELERS_STANDALONE_DEBOUNCE_MS', 8_000);
2152
+ const STANDALONE_DEBOUNCE_MS = envMs('EVENTMODELERS_STANDALONE_DEBOUNCE_MS', 2_500);
2084
2153
  const STANDALONE_MAX_WAIT_MS = envMs('EVENTMODELERS_STANDALONE_MAX_WAIT_MS', 90_000);
2085
- const STANDALONE_ECHO_WINDOW_MS = envMs('EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS', 20_000);
2154
+ const STANDALONE_ECHO_WINDOW_MS = envMs('EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS', 0);
2086
2155
  const STANDALONE_MIN_INTERVAL_MS = envMs('EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS', 60_000);
2087
2156
  const STANDALONE_BACKOFF_CAP_MS = envMs('EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS', 15 * 60_000);
2088
2157
  const STANDALONE_IDLE_MS = envMs('EVENTMODELERS_STANDALONE_IDLE_MS', 15 * 60_000);
2089
2158
 
2090
- // node_id (or '(board)') -> { types: Set<string>, count, own, other, maybe } for
2159
+ // node_id (or '(board)') -> { types: Set<string>, count, own, other, maybe, person } for
2091
2160
  // everything seen since the last self-directed turn, each event counted into exactly one
2092
2161
  // origin bucket: `own` = attributed to this agent's own id, `other` = attributed to a human
2093
2162
  // or another agent, `maybe` = carries no attribution at all and landed inside the echo
2094
2163
  // window, so it *might* be this agent's. Only `maybe` is a guess.
2164
+ // `person` counts, alongside the bucket, the subset of `other` written from a browser
2165
+ // session rather than by another agent. It is what lets a human's edit skip the
2166
+ // MIN_INTERVAL floor: a person cannot be this agent's feedback loop, whereas two agents on
2167
+ // one board can ping-pong, so another agent's write keeps waiting its turn.
2095
2168
  const observed = new Map();
2096
2169
  let observedCount = 0;
2097
2170
  let seqLo = null;
@@ -2144,10 +2217,12 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2144
2217
  ? 'maybe'
2145
2218
  : 'other';
2146
2219
  const nodeId = payload?.node_id ?? '(board)';
2147
- const entry = observed.get(nodeId) ?? { types: new Set(), count: 0, own: 0, other: 0, maybe: 0 };
2220
+ const entry = observed.get(nodeId) ?? { types: new Set(), count: 0, own: 0, other: 0, maybe: 0, person: 0 };
2148
2221
  entry.types.add(type);
2149
2222
  entry.count += 1;
2150
2223
  entry[origin] += 1;
2224
+ // A browser session id and no agent id: a human at the canvas.
2225
+ if (writerUser && !writerAgent) entry.person += 1;
2151
2226
  observed.set(nodeId, entry);
2152
2227
  observedCount += 1;
2153
2228
  if (!firstObservedAt) firstObservedAt = Date.now();
@@ -2220,9 +2295,11 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2220
2295
  'best target for them, not a reason to wait, and the board was already quiet before this turn was ' +
2221
2296
  'handed to you. Only board-wide sweeps and structural moves (renames, deletions, re-shaping, slice ' +
2222
2297
  'statuses) get a comment first instead of being done. An unanswered question you posted earlier parks ' +
2223
- 'that one sweep, never the fill-in work. Read the board in two calls, not twenty: every nodeId above in one ' +
2224
- 'get_nodes, the area around them in one get_board_outline per chapter, and a full-meta read only on the nodes ' +
2225
- 'you conclude you will actually touch. You do the analysis: look at every entry above, decide what ' +
2298
+ 'that one sweep, never the fill-in work. Read what this turn needs and no more: every nodeId above in one ' +
2299
+ 'get_nodes, plus one get_board_outline for each chapter they land in that you have not already read this ' +
2300
+ 'session a chapter you already hold is not fetched again, you carry it forward and apply this turn\'s ' +
2301
+ 'changes to your copy. A full-meta read only on the nodes you conclude you will actually touch. ' +
2302
+ 'You do the analysis: look at every entry above, decide what ' +
2226
2303
  'actually needs doing, and then work in parallel rather than serially — dispatch one Agent per piece of ' +
2227
2304
  'work that needs doing, all in a single message, merging pieces that share a slice or chain so no two ' +
2228
2305
  `agents write to the same area. ${AGENT_BUDGET} Read .agent-modeling-kit/CLAUDE-STANDALONE.md now (once ` +
@@ -2306,12 +2383,20 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
2306
2383
  else armStandaloneTurn(nextDelayMs());
2307
2384
  return;
2308
2385
  }
2386
+ // The floor is a runaway-loop guard, and a person is not a loop. Making a human wait out
2387
+ // MIN_INTERVAL before their edit is even looked at is most of the delay they feel, and it
2388
+ // guards nothing: the loop it exists to stop is this agent (or another one) reacting to a
2389
+ // write and writing again, which `person` excludes by construction.
2390
+ const byPerson = !idle && [...observed.values()].some((entry) => entry.person > 0);
2309
2391
  const waitLeft = minIntervalMs() - (Date.now() - lastStandaloneAt);
2310
- if (lastStandaloneAt && waitLeft > 0) {
2392
+ if (lastStandaloneAt && waitLeft > 0 && !byPerson) {
2311
2393
  if (idle) armIdleReview();
2312
2394
  else armStandaloneTurn(waitLeft);
2313
2395
  return;
2314
2396
  }
2397
+ if (byPerson && lastStandaloneAt && waitLeft > 0) {
2398
+ log(`standalone floor skipped: a person edited the board (${Math.round(waitLeft / 1000)}s of min-interval left)`);
2399
+ }
2315
2400
  const text = idle ? buildIdleReviewTurn() : buildStandaloneTurn();
2316
2401
  if (idle) {
2317
2402
  log(`standalone review turn: board quiet for ${Math.round(STANDALONE_IDLE_MS / 1000)}s`);
@@ -2533,6 +2618,7 @@ credentialFlags(program
2533
2618
  .option('--hook <command>', 'Persist a default shell command hook for `bridge` to run per batch of slice changes instead of Claude/Ollama (e.g. commit + push .slices/ for a CI pipeline to pick up) — only meaningful with --bridge. Can also be set per-run with `bridge --hook`.')
2534
2619
  .option('--build-kit', 'Install a blank build-kit scaffold (.build-kit/ + .claude/skills/build-*/SKILL.md placeholders, all TODO-marked) for a stack not built into this CLI yet — no fixed backend. Mutually exclusive with --stack/--modeling/--bridge.')
2535
2620
  .option('--hooks', 'Install the slice commit-scope guard (.githooks/pre-commit, running .build-kit/lib/check-commit-scope.cjs) and wire it up via `git config core.hooksPath .githooks` — only meaningful with --stack (build-kit stacks). Off by default.')
2621
+ .option('--demo', 'Install a ready-made demo model into the kit\'s .slices/ — the "Understanding Eventsourcing" context (16 shopping-cart slices covering every slice type), in exactly the shape `fetch` writes, so the build skills and the agent loop have something real to work on before this project is connected to a board. Skipped if .slices/ already holds fetched slices. Off by default.')
2536
2622
  .option('--global', 'Install skills into ~/.claude/skills/ instead of the project — available in every project')
2537
2623
  .option('-f, --force', 'Re-prompt for credentials even if a config already has everything required — overwrites the existing config.json')
2538
2624
  .option(...AGENT_NAME_OPTION))
@@ -2554,6 +2640,7 @@ credentialFlags(program
2554
2640
  global: opts.global,
2555
2641
  force: opts.force,
2556
2642
  credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
2643
+ demo: opts.demo,
2557
2644
  });
2558
2645
  return;
2559
2646
  }
@@ -2565,6 +2652,7 @@ credentialFlags(program
2565
2652
  global: opts.global,
2566
2653
  force: opts.force,
2567
2654
  credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
2655
+ demo: opts.demo,
2568
2656
  });
2569
2657
  return;
2570
2658
  }
@@ -2584,6 +2672,7 @@ credentialFlags(program
2584
2672
  global: opts.global,
2585
2673
  force: opts.force,
2586
2674
  credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
2675
+ demo: opts.demo,
2587
2676
  target: opts.target,
2588
2677
  });
2589
2678
  // Deliberately NOT under .bridge-kit/.eventmodelers/ — that whole name is
@@ -2622,6 +2711,7 @@ credentialFlags(program
2622
2711
  global: opts.global,
2623
2712
  force: opts.force,
2624
2713
  credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
2714
+ demo: opts.demo,
2625
2715
  templatesSource: join(clonedDir, 'templates'),
2626
2716
  hooks: opts.hooks,
2627
2717
  });
@@ -2635,6 +2725,7 @@ credentialFlags(program
2635
2725
  global: opts.global,
2636
2726
  force: opts.force,
2637
2727
  credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
2728
+ demo: opts.demo,
2638
2729
  hooks: opts.hooks,
2639
2730
  });
2640
2731
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.72",
4
- "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
3
+ "version": "1.0.74",
4
+ "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "eventmodelers": "cli.js"
@@ -0,0 +1,176 @@
1
+ {
2
+ "id": "6829844f-fbb5-41ab-b948-fdef54c7e9c1",
3
+ "status": "Planned",
4
+ "title": "Add Item",
5
+ "context": "Understanding Eventsourcing",
6
+ "sliceType": "STATE_CHANGE",
7
+ "commands": [
8
+ {
9
+ "id": "75ed140d-12c0-44f9-bf81-c2fa4886cf9d",
10
+ "tags": [],
11
+ "lane": "Interaction",
12
+ "modelContext": "Understanding Eventsourcing",
13
+ "slice": "Add Item",
14
+ "title": "Add Item",
15
+ "fields": [
16
+ {
17
+ "name": "aggregateId",
18
+ "type": "UUID",
19
+ "example": "",
20
+ "mapping": "",
21
+ "optional": false,
22
+ "subfields": [],
23
+ "cardinality": "Single",
24
+ "idAttribute": false
25
+ },
26
+ {
27
+ "name": "description",
28
+ "type": "String",
29
+ "example": "",
30
+ "mapping": "",
31
+ "optional": false,
32
+ "subfields": [],
33
+ "cardinality": "Single",
34
+ "idAttribute": false
35
+ },
36
+ {
37
+ "name": "image",
38
+ "type": "String",
39
+ "example": "",
40
+ "mapping": "",
41
+ "optional": false,
42
+ "subfields": [],
43
+ "cardinality": "Single",
44
+ "idAttribute": false
45
+ },
46
+ {
47
+ "name": "price",
48
+ "type": "Double",
49
+ "example": "",
50
+ "mapping": "",
51
+ "optional": false,
52
+ "subfields": [],
53
+ "cardinality": "Single",
54
+ "idAttribute": false
55
+ },
56
+ {
57
+ "name": "itemId",
58
+ "type": "UUID",
59
+ "example": "",
60
+ "mapping": "",
61
+ "optional": false,
62
+ "subfields": [],
63
+ "cardinality": "Single",
64
+ "idAttribute": false
65
+ },
66
+ {
67
+ "name": "productId",
68
+ "type": "UUID",
69
+ "example": "",
70
+ "mapping": "",
71
+ "optional": false,
72
+ "subfields": [],
73
+ "cardinality": "Single",
74
+ "idAttribute": false
75
+ }
76
+ ],
77
+ "type": "COMMAND",
78
+ "description": "",
79
+ "aggregate": "default",
80
+ "aggregateDependencies": [],
81
+ "dependencies": [
82
+ {
83
+ "id": "a39b1e41-7b38-42b1-8ad3-c9cc18ca2f7c",
84
+ "type": "OUTBOUND",
85
+ "title": "Cart Created",
86
+ "elementType": "EVENT"
87
+ },
88
+ {
89
+ "id": "544d0a8c-809c-4670-a9d9-aa8259f43c46",
90
+ "type": "OUTBOUND",
91
+ "title": "Item Added",
92
+ "elementType": "EVENT"
93
+ }
94
+ ],
95
+ "apiEndpoint": "",
96
+ "createsAggregate": false,
97
+ "triggers": [],
98
+ "elementCopy": false,
99
+ "linkedId": "75ed140d-12c0-44f9-bf81-c2fa4886cf9d",
100
+ "sketched": false,
101
+ "prototype": {
102
+ "activeByDefault": false
103
+ },
104
+ "comments": []
105
+ }
106
+ ],
107
+ "events": [
108
+ {
109
+ "id": "a39b1e41-7b38-42b1-8ad3-c9cc18ca2f7c",
110
+ "tags": [],
111
+ "lane": "Events",
112
+ "modelContext": "Understanding Eventsourcing",
113
+ "slice": "Add Item",
114
+ "title": "Cart Created",
115
+ "fields": [
116
+ {
117
+ "name": "aggregateId",
118
+ "type": "UUID",
119
+ "example": "",
120
+ "mapping": "",
121
+ "optional": false,
122
+ "subfields": [],
123
+ "cardinality": "Single",
124
+ "idAttribute": false
125
+ }
126
+ ],
127
+ "type": "EVENT",
128
+ "description": "",
129
+ "aggregate": "default",
130
+ "aggregateDependencies": [],
131
+ "dependencies": [
132
+ {
133
+ "id": "22511305-2296-43ba-b8e8-d786d53cd25f",
134
+ "type": "OUTBOUND",
135
+ "title": "Carts with Products",
136
+ "elementType": "READMODEL"
137
+ },
138
+ {
139
+ "id": "631c5aa8-0f9e-4a8f-b7f6-91174cae2b82",
140
+ "type": "OUTBOUND",
141
+ "title": "cart items",
142
+ "elementType": "READMODEL"
143
+ },
144
+ {
145
+ "id": "75ed140d-12c0-44f9-bf81-c2fa4886cf9d",
146
+ "type": "INBOUND",
147
+ "title": "Add Item",
148
+ "elementType": "COMMAND"
149
+ }
150
+ ],
151
+ "apiEndpoint": "",
152
+ "createsAggregate": false,
153
+ "triggers": [],
154
+ "elementCopy": false,
155
+ "linkedId": "a39b1e41-7b38-42b1-8ad3-c9cc18ca2f7c",
156
+ "sketched": false,
157
+ "prototype": {
158
+ "activeByDefault": false
159
+ },
160
+ "comments": []
161
+ }
162
+ ],
163
+ "readmodels": [],
164
+ "screens": [],
165
+ "screenImages": [],
166
+ "screenLayouts": [],
167
+ "processors": [],
168
+ "tables": [],
169
+ "specifications": [],
170
+ "storylines": [],
171
+ "chapter": "Understanding Eventsourcing",
172
+ "codeGen": {},
173
+ "notes": [],
174
+ "comments": [],
175
+ "aggregates": []
176
+ }