@eventmodelers/cli 1.0.120 → 1.0.122
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/cli.js +1 -1
- package/package.json +1 -1
- package/shared/build-kit/lib/ralph.js +13 -2
- package/shared/build-kit/ralph-claude.js +2 -6
- package/stacks/bridge/templates/bridge/ralph-claude.js +2 -4
- package/stacks/modeling-kit/templates/.claude/skills/{detect-architecture-drift → detect-model-drift}/SKILL.md +12 -6
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +2 -2
package/cli.js
CHANGED
|
@@ -2106,7 +2106,7 @@ async function runModeling(kitDir, projectDir, { codeDir = null, verbose = false
|
|
|
2106
2106
|
const claudeArgs = ['--dangerously-skip-permissions', '-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose'];
|
|
2107
2107
|
if (cfg.model) claudeArgs.push('--model', cfg.model);
|
|
2108
2108
|
// The global install runs in ~/.eventmodelers/kit, away from the code base the person started
|
|
2109
|
-
// the agent in — hand that directory over so code-reading skills (detect-
|
|
2109
|
+
// the agent in — hand that directory over so code-reading skills (detect-model-drift)
|
|
2110
2110
|
// can still see it.
|
|
2111
2111
|
if (codeDir) claudeArgs.push('--add-dir', codeDir);
|
|
2112
2112
|
const claudeEnv = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.122",
|
|
4
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": {
|
|
@@ -689,6 +689,17 @@ async function runWithRetry(label, fn) {
|
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
+
// prompt.md and backend-prompt.md are instruction manuals with no request in them. Handed one
|
|
693
|
+
// alone, the agent read it as background, answered "what would you like me to help with?" with
|
|
694
|
+
// no tool calls, and a Planned slice stayed Planned until the stuck-slice guard blocked it. So
|
|
695
|
+
// every turn ends with the concrete request — here, once, for every runner.
|
|
696
|
+
function taskRequest(planned = null) {
|
|
697
|
+
const task = planned
|
|
698
|
+
? `build the Planned slice "${planned.title}" (id=${planned.id}, context="${planned.ctx}")`
|
|
699
|
+
: 'process the pending tasks in tasks.json';
|
|
700
|
+
return `\n\n---\n\nYour task now: ${task}, following the instructions above. Start immediately.\n`;
|
|
701
|
+
}
|
|
702
|
+
|
|
692
703
|
async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false) {
|
|
693
704
|
const promptFile = join(kitDir, 'lib', 'prompt.md');
|
|
694
705
|
const backendPromptFile = join(kitDir, 'lib', 'backend-prompt.md');
|
|
@@ -707,7 +718,7 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false)
|
|
|
707
718
|
let didWork = false;
|
|
708
719
|
|
|
709
720
|
if (credentialed && hasPendingTasks(kitDir)) {
|
|
710
|
-
const prompt = readFileSync(promptFile, 'utf-8');
|
|
721
|
+
const prompt = readFileSync(promptFile, 'utf-8') + taskRequest();
|
|
711
722
|
for (const t of readTasks(join(kitDir, 'tasks.json'))) await fetchFullSlice(cfg, kitDir, t.payload?.sliceId);
|
|
712
723
|
await runWithRetry('onTask: loading slice from board...', () => inTurn(kitDir, () => onTask(prompt)));
|
|
713
724
|
await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
|
|
@@ -727,7 +738,7 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false)
|
|
|
727
738
|
continue;
|
|
728
739
|
}
|
|
729
740
|
|
|
730
|
-
const prompt = readFileSync(backendPromptFile, 'utf-8');
|
|
741
|
+
const prompt = readFileSync(backendPromptFile, 'utf-8') + taskRequest(planned);
|
|
731
742
|
if (credentialed) await fetchFullSlice(cfg, kitDir, planned.id);
|
|
732
743
|
lastReply = null;
|
|
733
744
|
const built = await runWithRetry(`onPlannedSlice: building slice "${planned.title}"...`, async () => { lastReply = await onPlannedSlice(prompt, {
|
|
@@ -80,15 +80,11 @@ function describeToolUse(block) {
|
|
|
80
80
|
// `slice` is set for a planned-slice build (see startRalph) — the only turns that are traced.
|
|
81
81
|
function runClaude(prompt, slice = null) {
|
|
82
82
|
return new Promise((resolve, reject) => {
|
|
83
|
-
|
|
84
|
-
// is cut at its first newline, so `claude` saw only the connect header, answered "I don't see
|
|
85
|
-
// a request" and the slice stayed Planned until the stuck-slice guard blocked it.
|
|
86
|
-
const proc = spawn('claude', [...claudeArgs, '-p'], {
|
|
83
|
+
const proc = spawn('claude', [...claudeArgs, '-p', inlineHeader + prompt], {
|
|
87
84
|
cwd: projectDir,
|
|
88
|
-
stdio: ['
|
|
85
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
89
86
|
env: claudeEnv,
|
|
90
87
|
});
|
|
91
|
-
proc.stdin.end(inlineHeader + prompt);
|
|
92
88
|
const turn = superviseTurn(proc, { timeoutMs, label: 'Claude turn', log: (line) => console.error(`[ralph] ${line}`) });
|
|
93
89
|
|
|
94
90
|
let buffer = '';
|
|
@@ -26,13 +26,11 @@ const claudeEnv = cfg.anthropicBaseUrl
|
|
|
26
26
|
|
|
27
27
|
function runClaude(prompt) {
|
|
28
28
|
return new Promise((resolve, reject) => {
|
|
29
|
-
|
|
30
|
-
const proc = spawn('claude', [...claudeArgs, '-p'], {
|
|
29
|
+
const proc = spawn('claude', [...claudeArgs, '-p', inlineHeader + prompt], {
|
|
31
30
|
cwd: projectDir,
|
|
32
|
-
stdio:
|
|
31
|
+
stdio: 'inherit',
|
|
33
32
|
env: claudeEnv,
|
|
34
33
|
});
|
|
35
|
-
proc.stdin.end(inlineHeader + prompt);
|
|
36
34
|
proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`Claude exited ${code}`))));
|
|
37
35
|
proc.on('error', reject);
|
|
38
36
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: detect-
|
|
2
|
+
name: detect-model-drift
|
|
3
3
|
description: Compare the event model on the board with the code in this repository and report where they have drifted apart — fields on commands/events/read models, specifications vs tests, screens, edges, slices missing in code, and slices in code that the model doesn't have. Read-only; every finding comes with a suggested fix the user confirms first.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Detect
|
|
6
|
+
# Detect Model Drift
|
|
7
7
|
|
|
8
8
|
## Step 0 — Is there a code base?
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ Drift is the gap between the board and the code, so it can only be detected wher
|
|
|
11
11
|
|
|
12
12
|
If it is not (an empty folder, a modeling-only workspace holding just `.claude/`, `.eventmodelers/` or `.agent-modeling-kit/`), **stop and answer in one or two plain sentences**, e.g.:
|
|
13
13
|
|
|
14
|
-
>
|
|
14
|
+
> Model drift can't be detected here — there is no code to compare the board against. Run this in the code base that implements the model.
|
|
15
15
|
|
|
16
16
|
Do not connect, do not read the board, do not produce a report or snippet, and do not offer other fixes.
|
|
17
17
|
|
|
@@ -52,6 +52,8 @@ Use the **full** slice data (no `projection`): you need fields with types and `i
|
|
|
52
52
|
|
|
53
53
|
Call `validate_model { boardId, chapterId }` for a scoped timeline — it already lists structural gaps on the board side (zero/multi-issuer commands, sourceless read models, missing scenarios). Those are model problems, not drift: mention them in one line, don't duplicate them as findings.
|
|
54
54
|
|
|
55
|
+
**Always read each chapter's context note before comparing.** The MARKDOWN note in the `feedback` lane of a chapter's **first column** holds the context for that chapter — architecture decisions, intended deviations, what the code is supposed to look like. For every chapter in scope, call `get_board_outline { boardId, chapterId }`, take the first column's node with `type: "MARKDOWN"` in a `feedback` lane, and read its `meta.description` with `get_node`. Judge every finding against it: a difference the note explains is intentional, not drift. A chapter without such a note is fine — carry on without it.
|
|
56
|
+
|
|
55
57
|
Index the result in memory: per slice → commands, events, read models, screens, automations, specifications, dependencies.
|
|
56
58
|
|
|
57
59
|
---
|
|
@@ -91,7 +93,7 @@ Rules for judging, in order:
|
|
|
91
93
|
- **Obvious typos and clearly wrong flags are one finding with a direct fix** (`custoemrId` in the code vs `customerId` on the board; `idAttribute: false` on the entity's own id) — name the correct side, don't ask which it is.
|
|
92
94
|
- **Direction matters.** A mismatch with no earlier evidence has no "right" side: propose the fix in the direction the evidence points (see Step 5), and say when it is a judgement call.
|
|
93
95
|
- Don't report framework noise: generated ids, metadata/envelope fields, serialization annotations, test fixtures' helper types, routes when the slice has no screen.
|
|
94
|
-
- Don't flag what is clearly intentional (an internal slice with no screen; a read model used only by another read model).
|
|
96
|
+
- Don't flag what is clearly intentional (an internal slice with no screen; a read model used only by another read model), or what the chapter's first-column feedback note (Step 2) describes as intended.
|
|
95
97
|
- Report each underlying difference **once**, at the element where it starts; a renamed field shows up on the command and the event and the read model — that is one finding listing the chain, not three.
|
|
96
98
|
|
|
97
99
|
---
|
|
@@ -102,12 +104,14 @@ For every finding, pick who should move:
|
|
|
102
104
|
|
|
103
105
|
| Evidence | Fix goes to | How it's done |
|
|
104
106
|
|----------|-------------|---------------|
|
|
105
|
-
| Code differs from `.build-kit/.slices` snapshot, board still equals the snapshot | **Model** — the code moved on deliberately | Skill that changes the board: `/attributes` (fields), `/timeline` / `/place-element` (elements), `/eventmodeling-elaborating-scenarios` (specs), `/storyboard-screen` or `/html-screen` (screens) |
|
|
107
|
+
| Code differs from `.build-kit/.slices` snapshot, board still equals the snapshot | **Model** — the code moved on deliberately | Skill that changes the board: `/attributes` (fields), `/timeline` / `/place-element` (elements), `/eventmodeling-elaborating-scenarios` (specs), `/storyboard-screen` or `/html-screen` (screens — including updating every existing screen connected to a changed element, see below) |
|
|
106
108
|
| Board differs from the snapshot, code still equals it | **Code** — the model moved on | Set the slice back to `Planned` with `/update-slice-status` so the build kit picks it up again and diffs code against `slice.json` |
|
|
107
109
|
| Both moved, or no snapshot | **Judgement call** — present both options, recommend one | Recommend by which side is more specific/complete |
|
|
108
110
|
| Slice only in code | **Model** | Add the slice, via `/eventmodeling-slicing-event-models` after placing its elements |
|
|
109
111
|
| Slice missing in code, board says `Done` | **Model status** (it isn't done) | `/update-slice-status` → `Planned` |
|
|
110
112
|
|
|
113
|
+
**A model fix always includes the existing screens.** When a field, command or read model changes on the board, every SCREEN/HTML_SCREEN that shows or submits it must change with it — otherwise the drift just moves from the code into the storyboard. List each affected screen in the same finding (follow the read model → screen and screen → command edges), and update it in place: `/html-screen` with its `nodeId` (loads the current pages and edits them, plus its `meta.fields`), `/storyboard-screen` for a wireframe sketch. Never create a new screen next to an outdated one.
|
|
114
|
+
|
|
111
115
|
Never propose "fix the code" by editing it from this skill — code changes belong to the build kit (`/update-slice-status` → `Planned`).
|
|
112
116
|
|
|
113
117
|
---
|
|
@@ -150,7 +154,7 @@ Print the report below, with every finding as a `- [ ]` checkbox carrying its pr
|
|
|
150
154
|
Name each slice and element as `[<title>](ref:<nodeId>)` (the slice border's id for a slice). The terminal shows the title, and once the report is in a board note or an element description, a click zooms the canvas to it (see `/learn-eventmodelers-api` *Linking to elements in markdown*). A slice that exists only in code has no node, so it stays plain text.
|
|
151
155
|
|
|
152
156
|
```
|
|
153
|
-
##
|
|
157
|
+
## Model Drift — <scope: board | timeline "<title>" | context "<name>">
|
|
154
158
|
Compared: <n> slices on the board, <n> slice folders in code Analysed: <ISO timestamp>
|
|
155
159
|
|
|
156
160
|
### Slices
|
|
@@ -178,3 +182,5 @@ Omit empty sections. Say plainly when nothing drifted.
|
|
|
178
182
|
- Read-only until confirmed — the report is the product; the fixes run as separate, ticked work.
|
|
179
183
|
- A scoped run (timeline in focus) never reports "slice only in code" for code outside the scope.
|
|
180
184
|
- One difference, one finding: follow a rename along the chain instead of listing it per element.
|
|
185
|
+
- Fixing drift on the model side means updating the existing screens connected to the changed elements too (pages and `meta.fields`), in place — not just the command/event/read model.
|
|
186
|
+
- Always read the MARKDOWN note in the feedback lane of each chapter's first column first — it is the chapter's context, and differences it explains are not drift.
|
|
@@ -203,7 +203,7 @@ for it. Instead:
|
|
|
203
203
|
and posts nothing to the board until they answer (`/wdyt` Step 4.0). Your reply here is one line —
|
|
204
204
|
*"On it — looking at Registration."*
|
|
205
205
|
- **a skill by name** — the message starts with `/<name>` of one of your skills (the chat panel's menu
|
|
206
|
-
writes these: `/analyze-existing-model`, `/analyze-code-base`, `/detect-
|
|
206
|
+
writes these: `/analyze-existing-model`, `/analyze-code-base`, `/detect-model-drift`, `/add-next-slice`, `/examples`, `/timeline …`,
|
|
207
207
|
`/html-screen …`, `/storyboard …`): the person chose that skill, so it **is** the work — create one prompt,
|
|
208
208
|
*"Run /<name> <the rest of the message>"*, with the target resolved from `context=` like any other
|
|
209
209
|
(selected elements, chapter), and say so in one line. Don't re-interpret it as a question. A skill that
|
|
@@ -357,7 +357,7 @@ nothing to sanitize either, since a board change is not user text.
|
|
|
357
357
|
| Business analysis, gap spotting, posting questions | `/wdyt` |
|
|
358
358
|
| Analyse the existing model structure, slice coverage, element counts | `/analyze-existing-model` |
|
|
359
359
|
| Reverse-engineer the business process of an existing code base into the model, as an interview — all chapters first, then one chapter at a time | `/analyze-code-base` |
|
|
360
|
-
| Find where the model and the code have drifted apart (fields, specs, screens, edges, missing/extra slices) and propose fixes | `/detect-
|
|
360
|
+
| Find where the model and the code have drifted apart (fields, specs, screens, edges, missing/extra slices) and propose fixes | `/detect-model-drift` |
|
|
361
361
|
| Turn a document or image (e.g. a tender/RFP) into a chaptered event model with screens, scenarios and a written blueprint summary | `/summarize` |
|
|
362
362
|
| Look up any API endpoint or element type not already covered by the skill you're executing | `/learn-eventmodelers-api` |
|
|
363
363
|
| Add or rename an attribute across a chain of elements | `/attributes` |
|