@eventmodelers/cli 1.0.119 → 1.0.121

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 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-architecture-drift)
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.119",
3
+ "version": "1.0.121",
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": {
@@ -5,6 +5,7 @@
5
5
  // onTask(prompt) — called when tasks.json has entries
6
6
  // onPlannedSlice(prompt, slice) — called when .slices/ has a "Planned" entry (omit to skip);
7
7
  // `slice` ({sliceId, sliceTitle, context, ticketNumber, boardId, attempt}) lets a runner record what building it cost.
8
+ // May resolve with the agent's final reply — quoted in the note if the slice ends up auto-blocked.
8
9
 
9
10
  import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, renameSync } from 'fs';
10
11
  import { join, dirname } from 'path';
@@ -590,11 +591,14 @@ const MAX_PLANNED_ATTEMPTS = Number(process.env.RALPH_MAX_PLANNED_ATTEMPTS) || 2
590
591
 
591
592
  // Marks a stuck slice Blocked (locally, and on the board if credentialed) and
592
593
  // records why, so the loop can move on instead of looping or exiting.
593
- async function blockStuckSlice(kitDir, cfg, credentialed, planned, attempts) {
594
+ // `lastReply` is the build agent's final message on the last attempt (when the runner reports one),
595
+ // so the note says what actually happened instead of only listing what might have.
596
+ async function blockStuckSlice(kitDir, cfg, credentialed, planned, attempts, lastReply) {
594
597
  const now = new Date().toISOString();
595
598
  const reason = `Ralph loop picked up this slice ${attempts} times in a row without its status ever leaving ` +
596
599
  `"Planned" — the build agent kept declining to build it, or kept building it but its own status change kept ` +
597
- `getting reverted (e.g. a failed check). Auto-blocked to stop the loop from retrying it forever.`;
600
+ `getting reverted (e.g. a failed check). Auto-blocked to stop the loop from retrying it forever.` +
601
+ (lastReply ? ` Build agent's last reply: "${String(lastReply).replace(/\s+/g, ' ').trim().slice(0, 500)}"` : '');
598
602
 
599
603
  const indexPath = join(kitDir, '.slices', planned.ctx, 'index.json');
600
604
  let folder;
@@ -662,7 +666,7 @@ async function blockStuckSlice(kitDir, cfg, credentialed, planned, attempts) {
662
666
  console.error(`[ralph] ${reason} Marked "${planned.title}" (id=${planned.id}) as Blocked — moving on.`);
663
667
  }
664
668
 
665
- // Returns true once fn succeeds; false for a turn that timed out (see lib/turn.js). A timeout
669
+ // Returns true once fn succeeds (fn's own result is the caller's to capture); false for a turn that timed out (see lib/turn.js). A timeout
666
670
  // is not retried in place: re-running the same hung turn every 60s would spin forever without
667
671
  // the stuck-slice guard ever seeing it, so it goes back to ralphLoop, which recounts the slice.
668
672
  // Any other failure is retried in place, with backoff (30s → 10min) so a harness that keeps
@@ -685,6 +689,17 @@ async function runWithRetry(label, fn) {
685
689
  }
686
690
  }
687
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
+
688
703
  async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false) {
689
704
  const promptFile = join(kitDir, 'lib', 'prompt.md');
690
705
  const backendPromptFile = join(kitDir, 'lib', 'backend-prompt.md');
@@ -696,12 +711,14 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false)
696
711
  // Tracks consecutive sightings of the same Planned slice id — see
697
712
  // MAX_PLANNED_ATTEMPTS above.
698
713
  let stuckSlice = { id: null, count: 0 };
714
+ // The build agent's final reply on the most recent planned-slice turn — quoted in the Blocked note.
715
+ let lastReply = null;
699
716
 
700
717
  while (true) {
701
718
  let didWork = false;
702
719
 
703
720
  if (credentialed && hasPendingTasks(kitDir)) {
704
- const prompt = readFileSync(promptFile, 'utf-8');
721
+ const prompt = readFileSync(promptFile, 'utf-8') + taskRequest();
705
722
  for (const t of readTasks(join(kitDir, 'tasks.json'))) await fetchFullSlice(cfg, kitDir, t.payload?.sliceId);
706
723
  await runWithRetry('onTask: loading slice from board...', () => inTurn(kitDir, () => onTask(prompt)));
707
724
  await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
@@ -715,22 +732,23 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false)
715
732
  : { id: planned.id, count: 1 };
716
733
 
717
734
  if (stuckSlice.count > MAX_PLANNED_ATTEMPTS) {
718
- await blockStuckSlice(kitDir, cfg, credentialed, planned, stuckSlice.count);
735
+ await blockStuckSlice(kitDir, cfg, credentialed, planned, stuckSlice.count, lastReply);
719
736
  stuckSlice = { id: null, count: 0 };
720
737
  didWork = true;
721
738
  continue;
722
739
  }
723
740
 
724
- const prompt = readFileSync(backendPromptFile, 'utf-8');
741
+ const prompt = readFileSync(backendPromptFile, 'utf-8') + taskRequest(planned);
725
742
  if (credentialed) await fetchFullSlice(cfg, kitDir, planned.id);
726
- const built = await runWithRetry(`onPlannedSlice: building slice "${planned.title}"...`, () => onPlannedSlice(prompt, {
743
+ lastReply = null;
744
+ const built = await runWithRetry(`onPlannedSlice: building slice "${planned.title}"...`, async () => { lastReply = await onPlannedSlice(prompt, {
727
745
  sliceId: planned.id,
728
746
  sliceTitle: planned.title,
729
747
  context: planned.ctx,
730
748
  ticketNumber: planned.ticketNumber,
731
749
  boardId: cfg.boardId ?? null,
732
750
  attempt: stuckSlice.count,
733
- }));
751
+ }); });
734
752
  if (built) console.log(`[ralph] Slice build complete — waiting for next slice`);
735
753
  if (credentialed) await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
736
754
  didWork = true;
@@ -88,6 +88,7 @@ function runClaude(prompt, slice = null) {
88
88
  const turn = superviseTurn(proc, { timeoutMs, label: 'Claude turn', log: (line) => console.error(`[ralph] ${line}`) });
89
89
 
90
90
  let buffer = '';
91
+ let reply = null;
91
92
  proc.stdout.on('data', (chunk) => {
92
93
  buffer += chunk.toString();
93
94
  const lines = buffer.split('\n');
@@ -107,6 +108,7 @@ function runClaude(prompt, slice = null) {
107
108
  }
108
109
  }
109
110
  } else if (msg.type === 'result') {
111
+ reply = msg.result ?? null;
110
112
  console.log(`done (${msg.duration_ms}ms${msg.total_cost_usd ? `, $${msg.total_cost_usd.toFixed(4)}` : ''})`);
111
113
  if (slice) tracer.record({...slice, status: msg.is_error ? 'error' : 'ok'}, usageFromClaudeResult(msg));
112
114
  }
@@ -116,7 +118,7 @@ function runClaude(prompt, slice = null) {
116
118
  proc.on('close', (code) => {
117
119
  const timeout = turn.timeoutError();
118
120
  if (timeout) reject(timeout);
119
- else if (code === 0) resolve();
121
+ else if (code === 0) resolve(reply);
120
122
  else reject(new Error(`Claude exited ${code}`));
121
123
  });
122
124
  proc.on('error', reject);
@@ -40,7 +40,9 @@ Either way, append a row to the chapter's *Analysis history* (see *The analysis
40
40
 
41
41
  ## Step 1 — Is there a running UI? (once, when the analysis starts)
42
42
 
43
- Ask once, before reading the sources, and only when starting fresh (no chapters from this analysis yet): *"Is there a running instance I can inspect? URL, and a login or test account if needed."* Wait for the answer. Record it (URL or "no running UI") in each chapter's `Legacy Sources` note and reuse it on later passes. Skip it when the request already answers it.
43
+ Ask once, when starting fresh (no chapters from this analysis yet): *"Is there a running instance I can inspect? URL, and a login or test account if needed."* The question is **optional, not a gate**: post it at the start, then **continue straight away** with the code analysis (Pass 0) without waiting for the answer. Until an answer arrives, work from the code alone and record `running UI: not answered yet` in each chapter's `Legacy Sources` note. Skip the question when the request already answers it.
44
+
45
+ When the answer arrives later: a URL (and login) → start the UI analysis then — walk it via `discover-storyboard` (see the *UI* lens), add its screens as evidence and use its click path to confirm or correct the order already derived from the code; "no running UI" → nothing to do. Either way, update the `Legacy Sources` note with the answer and reuse it on later passes.
44
46
 
45
47
  Ask again mid-way only when it helps — typically when the order isn't clear from the code: *"I can't tell the order of these steps from the code. Is there a running UI I can use to check? Otherwise, here are my questions: …"*
46
48
 
@@ -63,12 +65,14 @@ Naming, what to model and the anti-patterns are in `eventmodeling-core-rules` (r
63
65
  Every pass reads the code through the same four lenses; each produces *hypotheses* to put to the person, never elements on its own. Read only as deep as the current layer needs.
64
66
 
65
67
  1. **API** (look hardest here) — REST controllers, routes, handlers, message listeners, scheduled jobs, outbound clients. Each entry point is a candidate **screen + command** (a person writes), **screen + read model** (a person reads) or **automation** (jobs, listeners, calls to other systems). Request/response payloads hint at fields; the caller hints at who triggers it.
66
- 2. **Tests** — test names, fixtures, assertions. Names reveal the business vocabulary; arrange/act/assert becomes Given/When/Then; error cases reveal the rules that matter. Fixtures are the source of realistic example values.
68
+ 2. **Tests** — test names, fixtures, assertions. Names reveal the business vocabulary; arrange/act/assert is a *hint* for Given/When/Then, not its shape — which element goes into Given, When and Then is decided by `eventmodeling-elaborating-scenarios`, never by the test's structure (see *Scenarios* below); error cases reveal the rules that matter. Fixtures are the source of realistic example values.
67
69
  3. **Persistence** (look hardest here, too — *what gets saved*) — entities, tables, migrations, repositories. Every write is a candidate **event**. Writes show which state changes exist (→ events); relations show aggregate boundaries; status/enum columns show lifecycles; derived or joined queries show read models. Tables are evidence, never elements.
68
- 4. **UI** — templates, views, components, forms, navigation. Forms and buttons are where a person triggers a command; list and detail pages are read models; the page order is the flow; visible labels are the business words. Whether a running UI exists is asked in **Step 1**. Without one, rely on the other three lenses and ask where the order stays unclear.
70
+ 4. **UI** — templates, views, components, forms, navigation. Forms and buttons are where a person triggers a command; list and detail pages are read models; the page order is the flow; visible labels are the business words. Whether a running UI exists is asked in **Step 1** — without waiting for the answer. Until there is a URL, rely on the other three lenses and ask where the order stays unclear.
69
71
 
70
72
  **With a URL, walk it via `discover-storyboard`** (URL already known — don't ask again; pass the flow as guidance and ask for screenshots). Its click path is the *proposed* order and its screens go into the chapter as evidence; confirm both against the API and persistence lenses. Submit forms only against a local or test system, or after the person agrees.
71
73
 
74
+ **Screens: HTML, except real screenshots.** A plain `SCREEN` node is used for exactly one thing — a screenshot Playwright took of the running UI (`contentType: "image"`). Every other screen — built from the templates, views and controllers, or for a step the running UI didn't show — is an `HTML_SCREEN` via `/html-screen` (`create_screen` with `contentType: "html"` and real pages: the template's labels, fields and buttons). Never a sketch, never a plain `SCREEN` without a screenshot on it.
75
+
72
76
  If the code is a generic engine (templates, schemas, configurable entities), model one real case it handles, not the engine (see *Concrete Over Generic* in `eventmodeling-core-rules`).
73
77
 
74
78
  Where the lenses disagree (a route nobody calls, a table never written, a test for removed behaviour, a screen with no backend), that is a question for the person — not something to model.
@@ -94,17 +98,17 @@ Rules of thumb: every API call is either a screen-driven command/read or an auto
94
98
  ## Progressive elaboration — all chapters first, then one chapter per pass
95
99
 
96
100
  ### Pass 0 — all chapters, high level
97
- 1. Read the sources broadly through the four lenses to spot the business workflows — walking the running UI from Step 1 via `discover-storyboard` if there is one.
101
+ 1. Read the sources broadly through the four lenses to spot the business workflows — don't wait for the Step 1 answer; walk the running UI via `discover-storyboard` once a URL has been given (also when it arrives after Pass 0).
98
102
  2. Group them into chapters and create them as `eventmodeling-brainstorming-events` (§ *Group events by workflow* / *Create one chapter per group*) does — a chapter is a user journey, never one endpoint, controller or CRUD operation. Code is split by operation (`OwnerController#initCreationForm`, `#processFindForm`, `#showOwner`, `#processUpdateOwnerForm`); the story is not — registering, finding, viewing and updating an owner are **one** chapter. Order the chapters per `eventmodeling-plotting-events`; ask where it is unclear. Add the **Decisions** lane, put the `Legacy Sources` note into **column 0** (see *The analysis log*), then sketch only the milestones (titles only) **from column 1 on**.
99
103
  3. Report the chapters and **ask which chapter to start with**. Never pick for them.
100
104
 
101
105
  ### Pass N — detail the chosen chapter
102
106
  1. Work **in the chapter the person picked**, reusing what is already there. Model the **whole workflow**, not its first slice, by running the step skills on it in order:
103
107
  - `eventmodeling-plotting-events` — the events in business order, start to outcome;
104
- - `eventmodeling-storyboarding-events` — what the person sees after each event (e.g. *Owner Registered* → owner details page → add a pet);
108
+ - `eventmodeling-storyboarding-events` — what the person sees after each event (e.g. *Owner Registered* → owner details page → add a pet). Every screen it places is an `HTML_SCREEN` (see *Screens* in the *UI* lens): skip its interview (the code answers it) and never take its sketch path;
105
109
  - `eventmodeling-identifying-inputs` / `eventmodeling-identifying-outputs` — commands and read models;
106
110
  - `eventmodeling-designing-automation-chains` / `eventmodeling-translating-external-events` — jobs, listeners, webhooks, outbound calls;
107
- - `eventmodeling-elaborating-scenarios` — rules from the tests (layer 1+ only).
111
+ - `eventmodeling-elaborating-scenarios` — rules from the tests (layer 1+ only), built exactly as that skill says (see *Scenarios* below).
108
112
  Feed each skill what the lenses and the mapping table give you; ask only where the code stops (see *Step 1* when the order is unclear).
109
113
  2. Stay within the layer's detail budget (below). Every placement batch carries its decision rows (see *Recording decisions*).
110
114
  3. Re-space the chapters (see *Keeping chapters apart*) after every batch.
@@ -119,7 +123,7 @@ Repeat until the person says stop. Chapters not chosen stay at high level.
119
123
  |-------|----------|--------|----------|-----------|---------|
120
124
  | **0 — High level** | one chapter per flow, business milestones as slices | none | none | none | none |
121
125
  | **1 — Flow** | real command / event / read model sequence of one flow | only identifying and business-critical | one per key element | happy path + the one or two rules that matter | only where a person decides |
122
- | **2+ — Deeper** | sub-flows, alternate paths, automations | full business field set incl. optional/list | realistic values from tests and fixtures | error cases and edge rules from the tests | sketches where a decision needs them |
126
+ | **2+ — Deeper** | sub-flows, alternate paths, automations | full business field set incl. optional/list | realistic values from tests and fixtures | error cases and edge rules from the tests | HTML screens where a decision needs them (screenshots only from the running UI) |
123
127
 
124
128
  Never add detail that belongs to a deeper layer. Every element must trace to sources you read or an answer the person gave. **Deeper never means more technical** — it means more business: decisions, rules, failure modes.
125
129
 
@@ -228,7 +232,7 @@ Element `description`s still carry their own short code reference. The feedback
228
232
 
229
233
  **When this run has a chat** (`CHAT_SESSION_ID` is set — the request came from the board's chat), a question the person answers with a pick is a **snippet** (`post_chat_message` with `snippet`; shapes in `/learn-eventmodelers-api` § 16 *Chat snippets*); an open question stays text. Without a chat, ask in prose.
230
234
 
231
- This overrides the `tasks`-snippet chat mode of `/timeline`. **Snippets are not a confirmation gate.** Never post a `tasks` snippet to let the person tick what you derived from the code — you build that directly (see *Principles*). Use a snippet only when you genuinely need the person (the Step 1 question about a running UI comes first, as plain text, and only when the analysis starts):
235
+ This overrides the `tasks`-snippet chat mode of `/timeline`. **Snippets are not a confirmation gate.** Never post a `tasks` snippet to let the person tick what you derived from the code — you build that directly (see *Principles*). Use a snippet only when you genuinely need the person (the Step 1 question about a running UI comes first, as plain text, when the analysis starts — then continue with the code analysis without waiting for the answer):
232
236
 
233
237
  1. **Order** — *"Which comes first?"* → a `poll` with the candidate orders; *"I'd put A before B — right?"* → a `confirm`.
234
238
  2. **Unclear business requirement** — the code leaves it open or contradicts itself (*"Can a visit be cancelled?"*, *"Is the specialty optional?"*) → a `confirm` or `poll` with your hypothesis; an open answer stays text.
@@ -253,7 +257,7 @@ One question per message, with the best hypothesis attached. The answer arrives
253
257
  | Given/When/Then scenarios from the tests | `/eventmodeling-elaborating-scenarios` |
254
258
  | Add or rename attributes along a chain | `/attributes` |
255
259
  | Realistic example data from tests and fixtures | `/examples` |
256
- | A screen where a person decides | `/html-screen` (wireframe sketch only on explicit request: `/storyboard-screen`) |
260
+ | A screen where a person decides | `/html-screen` — always HTML; a plain SCREEN only for a Playwright screenshot of the running UI (see *UI* lens), never `/storyboard-screen` |
257
261
  | Set a slice's status | `/update-slice-status` |
258
262
  | Record a decision with its code reference | *Recording decisions* (MARKDOWN note in the **Decisions** feedback lane) |
259
263
  | Open questions, unresolved code-vs-user conflicts | `/wdyt`, `/handle-comment` |
@@ -261,7 +265,16 @@ One question per message, with the best hypothesis attached. The answer arrives
261
265
  | Validate before reporting completion | `/eventmodeling-validating-event-models-checklist` |
262
266
  | Any endpoint or element type not covered above | `/learn-eventmodelers-api` |
263
267
 
264
- Scenarios: only business rules found in tests and comments — not simple validations ("must be a number"). Only create a storyline when the person explicitly asks.
268
+ ### Scenarios
269
+
270
+ *Which* rules to write is specific to legacy code: only business rules found in tests and comments — not simple validations ("must be a number"). Only create a storyline when the person explicitly asks.
271
+
272
+ *How* to write them is not — read `eventmodeling-elaborating-scenarios` before the first scenario and follow it; a test translated line by line is how wrong scenarios end up on the board. In particular:
273
+
274
+ - **What goes into Given, When and Then** — per command and per read model: § *1. Command Scenarios*, § *2. Command Scenarios - State Validation*, § *3. View Scenarios*, § *3b. List-type Read Model Scenarios*, § *4. Error Path Scenarios* (worked examples in its `references/examples.md`).
275
+ - **Which elements may appear in a step** — only those `get_spec_info` returns for the timeline: § *Post Scenarios to Board* → *Step 2 — Load valid step elements* and *Step 3 — Resolve step IDs*. A test's mocks, repositories or setup calls are never steps.
276
+ - **Rejections** — § *Step 4c — Error/rejection scenarios*.
277
+ - **Before reporting** — its § *Quality Checklist*.
265
278
 
266
279
  ---
267
280
 
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: detect-architecture-drift
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 Architecture Drift
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
- > Architecture 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.
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
 
@@ -150,7 +150,7 @@ Print the report below, with every finding as a `- [ ]` checkbox carrying its pr
150
150
  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
151
 
152
152
  ```
153
- ## Architecture Drift — <scope: board | timeline "<title>" | context "<name>">
153
+ ## Model Drift — <scope: board | timeline "<title>" | context "<name>">
154
154
  Compared: <n> slices on the board, <n> slice folders in code Analysed: <ISO timestamp>
155
155
 
156
156
  ### Slices
@@ -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-architecture-drift`, `/add-next-slice`, `/examples`, `/timeline …`,
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-architecture-drift` |
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` |