@milehimikey/em 1.1.0 → 1.2.0

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 (49) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +64 -81
  2. package/.claude/skills/event-modeling/reference/em-dsl.md +23 -18
  3. package/.claude/skills/event-modeling/reference/extract.md +123 -0
  4. package/.claude/skills/event-modeling/reference/methodology.md +23 -0
  5. package/.claude/skills/event-modeling/templates/live.html +65 -13
  6. package/.claude/skills/event-modeling/templates/model-readme.md +9 -6
  7. package/.claude/skills/event-modeling/templates/slice.md +2 -4
  8. package/.claude/skills/event-modeling/templates/state.md +16 -11
  9. package/README.md +44 -199
  10. package/dist/cli.js +21 -227
  11. package/dist/cli.js.map +1 -1
  12. package/dist/model/validate.d.ts +0 -2
  13. package/dist/model/validate.js +1 -1
  14. package/dist/model/validate.js.map +1 -1
  15. package/dist/render/serve.d.ts +20 -0
  16. package/dist/render/serve.js +202 -0
  17. package/dist/render/serve.js.map +1 -0
  18. package/package.json +4 -5
  19. package/.claude/skills/event-modeling/reference/frontmatter.md +0 -129
  20. package/dist/config.d.ts +0 -15
  21. package/dist/config.js +0 -60
  22. package/dist/config.js.map +0 -1
  23. package/dist/migrate/migrate.d.ts +0 -26
  24. package/dist/migrate/migrate.js +0 -276
  25. package/dist/migrate/migrate.js.map +0 -1
  26. package/dist/model/deriveSliceFields.d.ts +0 -21
  27. package/dist/model/deriveSliceFields.js +0 -60
  28. package/dist/model/deriveSliceFields.js.map +0 -1
  29. package/dist/model/frontmatter.d.ts +0 -21
  30. package/dist/model/frontmatter.js +0 -38
  31. package/dist/model/frontmatter.js.map +0 -1
  32. package/dist/model/sliceDoc.d.ts +0 -56
  33. package/dist/model/sliceDoc.js +0 -28
  34. package/dist/model/sliceDoc.js.map +0 -1
  35. package/dist/model/validateSliceDocs.d.ts +0 -7
  36. package/dist/model/validateSliceDocs.js +0 -195
  37. package/dist/model/validateSliceDocs.js.map +0 -1
  38. package/dist/slice/generate.d.ts +0 -17
  39. package/dist/slice/generate.js +0 -61
  40. package/dist/slice/generate.js.map +0 -1
  41. package/dist/slice/query.d.ts +0 -42
  42. package/dist/slice/query.js +0 -96
  43. package/dist/slice/query.js.map +0 -1
  44. package/dist/slice/sync.d.ts +0 -18
  45. package/dist/slice/sync.js +0 -104
  46. package/dist/slice/sync.js.map +0 -1
  47. package/dist/util/findModel.d.ts +0 -1
  48. package/dist/util/findModel.js +0 -27
  49. package/dist/util/findModel.js.map +0 -1
@@ -4,23 +4,28 @@ The skill reads this on `/event-modeling` (no arg) to resume where you left off.
4
4
  Keep it current at the end of every working session.
5
5
  -->
6
6
 
7
- ---
8
- schemaVersion: 1
9
- model: {{model-name}}.em
10
- ---
11
-
12
7
  # Event Modeling Progress — {{Model Name}}
13
8
 
14
9
  - **Model file:** `{{model-name}}.em`
15
- - **Current phase:** {{discover | model | slice}}
16
- - **Current step:** {{1–7, see methodology}}
10
+ - **Current phase:** {{extract | discover | model | slice}}
11
+ - **Current step:** {{1–7, see methodology; or extraction round R1–R7}}
17
12
  - **Last updated:** {{YYYY-MM-DD}}
18
13
 
19
14
  ## Session inputs
20
15
  - **Scope line:** {{one-line description of what's in/out of bounds for this model}}
21
16
  - **PRD / spec reference:** {{path, link, or "none"}}
22
17
  - **Headless/API model:** {{yes | no}}
23
- - **Regulatory scope:** {{e.g. PCI-DSS, SOX, HIPAA, or "none"}}
18
+ - **Source mode:** {{greenfield | extract-event-driven | extract-procedural}}
19
+ - **Existing system refs:** {{repo paths, event-schema/topic locations, docs — or "n/a"}}
20
+
21
+ ## Extraction progress (existing-system models only — delete for greenfield)
22
+ - [ ] R1. Candidate events (extracted/synthesized, filtered, confirmed)
23
+ - [ ] R2. Timeline order (as-is narrative, actors/callers, first render)
24
+ - [ ] R3. Commands / inputs
25
+ - [ ] R4. Read models / outputs (validate from here)
26
+ - [ ] R5. Boundaries & reactions (two-slice splits)
27
+ - [ ] R6. Gap & TBD reconciliation
28
+ - [ ] R7. Convergence (render + validate clean, user confirmed as-is)
24
29
 
25
30
  ## Steps completed
26
31
  - [ ] 1. Brainstorm events
@@ -40,6 +45,6 @@ model: {{model-name}}.em
40
45
  - [ ] {{question}}
41
46
 
42
47
  ## Slice inventory
43
- | Slice | Id | Pattern | Doc status |
44
- |-------|-----|---------|------------|
45
- | {{Slice Name}} | {{slice-id}} | {{state-change/state-view/automation/translation}} | {{none / draft / reviewed / ready-to-implement}} |
48
+ | Slice | Pattern | Doc status |
49
+ |-------|---------|------------|
50
+ | {{Slice Name}} | {{State Change/View/Automation/Translation}} | {{none / draft / reviewed / ready-to-implement}} |
package/README.md CHANGED
@@ -1,214 +1,85 @@
1
- # em — AI friendly event modeling
1
+ # em — event modeling in plain text
2
2
 
3
3
  `em` is a command-line tool for [Event Modeling](https://eventmodeling.org/). You write a
4
- model in a small, **plain-text, slice-first DSL** (`.em`) and `em` renders it to a clean,
5
- deterministic diagram (SVG by default, PNG on request). Because the source is just text —
6
- diff-able, reviewable, and unambiguous — it's as easy for an LLM to generate and edit as it
7
- is for a person to write by hand. Layout is a **strict grid**: swimlane rows and
8
- time-ordered slice columns stay perfectly aligned with no manual offsets.
4
+ model in a small, slice-first DSL and `em` renders it as a clean, deterministic diagram.
5
+ Because the source is plain text — diff-able, reviewable, unambiguous — it's as easy for an
6
+ AI to write and edit as it is for a person, and `em validate` keeps either one honest.
9
7
 
10
- ```
11
- examples/order-fulfillment.em -> em render -> order-fulfillment.svg
12
- ```
8
+ ![An order-fulfillment event model rendered by em](https://raw.githubusercontent.com/milehimikey/em/main/examples/order-fulfillment.svg)
13
9
 
14
- Rendering is **self-contained** — Graphviz runs as bundled WebAssembly and PNG is
15
- rasterized in-process, so there's nothing to `apt`/`brew` install for SVG or PNG output.
10
+ The source for that diagram is [about 70 lines of text](examples/order-fulfillment.em).
16
11
 
17
12
  ## Install
18
13
 
19
14
  ```bash
20
- npm install -g @milehimikey/em # then `em` is on your PATH
15
+ npm install -g @milehimikey/em
21
16
  ```
22
17
 
23
- Requires **Node ≥ 18**. SVG and PNG need no other tools. PDF (and other Graphviz raster
24
- formats) are optional and use a system `rsvg-convert` (librsvg) if one is installed.
25
-
26
- Run without installing:
27
-
28
- ```bash
29
- npx @milehimikey/em init model.em
30
- npx @milehimikey/em render model.em
31
- ```
18
+ Requires Node ≥ 18. SVG and PNG rendering are fully self-contained (Graphviz runs as
19
+ bundled WebAssembly); nothing else to install. PDF is optional — see
20
+ [docs/dependencies.md](docs/dependencies.md).
32
21
 
33
22
  ## Quickstart
34
23
 
35
24
  ```bash
36
25
  em init model.em # scaffold a starter model
37
26
  em render model.em # -> model.svg (open it in a browser)
38
- em watch model.em # re-render on every save
27
+ em watch model.em --serve # live browser view, re-renders on every save
39
28
  em validate model.em # check event-modeling rules
40
29
  ```
41
30
 
42
- A `.em` model is a list of **slices** (vertical time steps), each holding elements that fall
43
- into swimlane rows:
31
+ A model is a list of slices — vertical time steps, read left to right — whose elements land
32
+ in swimlane rows:
44
33
 
45
- ```
34
+ ```em
46
35
  model "Order Fulfillment"
47
36
 
48
- persona Customer # UI swimlanes (top), one row per persona
49
- context Order # event swimlanes (bottom), one row per context
50
- context Payment
51
-
52
- slice "Browse Catalog" { # each slice is one column (time, left -> right)
53
- ui Product Catalog @Customer # @Persona places the screen in a persona row
54
- command Place Order { # a `{ … }` block declares the element's fields
55
- customerId
56
- total: Money # optional `: Type` annotation
57
- }
58
- event Order Placed @Order note "notes/order-placed.md" { # note "…" links docs
59
- orderId
60
- total: Money
61
- }
37
+ persona Customer
38
+ context Order
39
+
40
+ slice "Browse Catalog" {
41
+ ui Product Catalog @Customer
42
+ command Place Order
43
+ event Order Placed @Order
62
44
  }
63
45
 
64
46
  slice "View Open Orders" {
65
- view Open Orders from "Order Placed" # from "<event>" wires the data flow
47
+ view Open Orders from "Order Placed"
66
48
  ui Order List @Customer
67
49
  }
68
50
  ```
69
51
 
70
- ## The four patterns
71
-
72
- Event Modeling is built from four patterns, all expressible as slices:
73
-
74
- | Pattern | Flow | DSL elements |
75
- |---|---|---|
76
- | **Input** (state change) | UI → command → event | `ui`, `command`, `event` |
77
- | **Output** (state view) | event → read model → UI | `event`, `view from "…"`, `ui` |
78
- | **Automation** | read model (todo) → processor → command → event | `view from "…"` + `processor`, then `command` + `event` |
79
- | **Translation** | external read model → translation → command → event | `view from "…"` + `translation`, then `command` + `event` |
80
-
81
- Automation/translation slices are **not** triggered by a command — they react to a read
82
- model (a "todo list") or a timer and *issue* a command when done. The automation slice
83
- contains **only** the read model it reads and the processor/translation; the command it
84
- triggers (and that command's event) is the **next** slice.
52
+ The [tutorial](docs/tutorial.md) builds a complete model from an empty file in about twenty
53
+ minutes.
85
54
 
86
- ## DSL reference
55
+ ## Model with AI
87
56
 
88
- ### Element keywords → swimlane bands (top → bottom)
89
-
90
- 0. *(header)* — each slice name is rendered as a title cell in the top row
91
- 1. `automation` / `processor` / `saga` / `translation` — **Automation** band (only shown if used)
92
- 2. `ui` — **persona** rows (one per `persona`)
93
- 3. `command` + `view` — the **API** row (commands and read models share one lane)
94
- 4. `event` — **context/concept** rows (one per `context`)
95
-
96
- Commands and read models share the **API** row, so a slice holds *either* a command *or* a
97
- read model. The empty API lane is dropped when a model has neither; pass `--keep-empty-lanes`
98
- to keep it.
99
-
100
- `@Persona` / `@Context` choose the row within a band (undeclared tags create a new row).
101
- `view <Name> again [from "Event", …]` declares a LATER INSTANCE of an already-declared read
102
- model — the Event Modeling device for keeping the timeline forward-only as a view evolves.
103
- Instances are one logical view: the first declaration owns the `note` doc; each instance lists
104
- the events that reach it there; instances are linked left-to-right with a continuity arrow, and
105
- reactions (`processor … from "View"`) read the nearest instance at-or-before their slice.
106
-
107
- `view … from "Event"[, "Event2"]` declares which events feed a read model and draws the
108
- data-flow arrow. Arrows within a slice are inferred from the pattern; use `arrow A -> B` for
109
- anything extra (e.g. a read model feeding a different screen).
110
-
111
- ### Fields
112
-
113
- Any element can declare **data fields** in a `{ … }` block — the data it accepts (command),
114
- records (event), projects (read model), or shows (UI). Each field is `name` with an optional
115
- `: Type`; write one per line, or inline and comma-separated:
116
-
117
- ```
118
- command Place Order { # one per line
119
- customerId
120
- items: List<LineItem>
121
- total: Money
122
- }
123
-
124
- event Payment Requested @Payment { orderId, amount: Money } # inline
125
- ```
126
-
127
- Fields render **in the box**, UML-style: the name, a divider rule, then the fields. The box
128
- grows to fit them (width stays fixed, so columns stay aligned), and arrows still anchor to
129
- the box edges so the lines stay stable. A field block coexists with `note`/`from` clauses on
130
- the same element. Fields are the foundation of the later event-modeling phases (the slicing /
131
- information-completeness process); field-level validation across slices is planned.
132
-
133
- ### Notes
134
-
135
- Any element can carry `note "path.md"` (valid on every keyword). The prose lives in the
136
- markdown file — keeping the diagram uncluttered — and the box gets a small **numbered
137
- folded-corner marker** in its top-right corner. A **legend** is appended below the diagram
138
- mapping each number to its element and note file, so even a static export tells you which
139
- note belongs to which box.
140
-
141
- In **SVG** output the markers and legend rows are links; clicking one opens the markdown
142
- file. Links resolve relative to the **output SVG's location**, so they keep working whether
143
- the SVG is rendered beside the model or into another folder (as long as the notes travel
144
- with it). Open the SVG in a **web browser** to use the links — image viewers like macOS
145
- Preview/Quick Look show the markers but ignore SVG hyperlinks. Raster output (PNG/PDF) can't
146
- carry links — that's what the footnote numbers + legend are for.
147
-
148
- ## Commands
57
+ `em` ships a Claude Code skill that runs a facilitated Event Modeling session: the AI asks
58
+ the questions, you supply the domain, and the model renders live as it grows.
149
59
 
150
60
  ```bash
151
- em init [file] # scaffold a starter model (default: model.em)
152
- em render <file> [-o out.svg] # render (format from -o extension or -T)
153
- em render <file> -o out.png # PNG (in-process, no system deps)
154
- em render <file> --emit-dot # print the generated DOT instead of rendering
155
- em render <file> --keep-empty-lanes # don't collapse the API lane when empty
156
- em watch <file> [-o out.svg] # re-render on every save
157
- em validate <file> # check event-modeling rules + slice-doc frontmatter
158
- em migrate <model-dir> # one-time upgrade of a pre-1.0 model to the frontmatter standard
61
+ em skill install # copy the skill into .claude/skills/event-modeling/
159
62
  ```
160
63
 
161
- See [Slice docs & frontmatter](#slice-docs--frontmatter) below for the `em slice` command group.
162
-
163
- ## Validation rules (`em validate`)
164
-
165
- - two same-band elements in one slice (collision) → **error** (split into separate slices)
166
- - a read model whose `from "Event"` doesn't exist → **error**
167
- - **time flows left to right** (the Two Laws): an event feeding an EARLIER view instance,
168
- a reaction reading a view that only exists later, or an explicit backward `arrow` → **error**
169
- (fix: add `view X again` at the point on the timeline where the event lands)
170
- - `again` on a view with no earlier declaration → **error**; `again` on non-views → parse error
171
- - multi-event slices always render as a FAN from the command (never event→event); fanned
172
- arrows route around the column gutter so they cannot read as a chain
173
- - an automation/translation whose `from "<read model>"` doesn't exist → **error**
174
- - an `arrow` endpoint that matches no element → **error**
175
- - an automation/translation slice that also holds the command it triggers → **warning**
176
- - a command that records no event → **warning**
177
- - a read model with no source event → **warning**
178
- - a name defined more than once and referenced → **warning** (resolves to first)
179
-
180
- ## Slice docs & frontmatter
181
-
182
- Slice design docs (`slices/<id>.md`, written during the skill's `slice` phase or via
183
- `em slice new`) carry a required YAML frontmatter block — a stable `id`, lifecycle `status`,
184
- integer `version`, and fields generated from the `.em` (`commands`/`events`/`readModels`/
185
- `contexts`/`personas`, plus `triggers`/`triggeredBy` for automation/translation) — so an AI agent
186
- can search and filter across a model with hundreds of slices from frontmatter alone, without
187
- reading every doc's body or re-parsing the `.em`:
188
-
189
- ```bash
190
- em slice new "Place Order" --pattern state-change # scaffold slices/place-order.md
191
- em slice sync place-order # after wiring note "slices/..." into the .em
192
- em slice list --status ready-to-implement
193
- em slice search --pattern automation --context Payment --format json
194
- em slice search --status ready-to-implement --format json --full # every field, not just a summary
195
- em slice update place-order --status reviewed --bump-version
196
- ```
197
-
198
- The doc body itself is **yours to define** — point `em.config.json` at a custom template
199
- (`{ "sliceTemplate": "path/to/template.md" }`) and `em` injects the frontmatter on top of it
200
- regardless of what sections you ask for. Full schema, lifecycle model, and the handoff contract a
201
- build/SDD tool should treat as authoritative once a slice is `ready-to-implement`:
202
- [docs/1.0.0-spec.md](docs/1.0.0-spec.md).
64
+ Then run `/event-modeling` in Claude Code. See [docs/ai-workflow.md](docs/ai-workflow.md)
65
+ for the phases and what a session produces, and the
66
+ [em-with-ai repository](https://github.com/milehimikey/em-with-ai) for a ~50-slice model
67
+ built this way.
203
68
 
204
69
  ## Documentation
205
70
 
206
- - [docs/1.0.0-spec.md](docs/1.0.0-spec.md) — slice frontmatter standard, config, `em slice`/
207
- `em migrate`, and the SDD handoff contract
208
- - [docs/why-dot-not-plantuml.md](docs/why-dot-not-plantuml.md) — why DOT + the strict-grid /
209
- self-drawn-edges architecture
210
- - [docs/features.md](docs/features.md) — full feature list and roadmap
211
- - [docs/dependencies.md](docs/dependencies.md) — how rendering works with no system deps
71
+ | Doc | What it answers |
72
+ |---|---|
73
+ | [docs/tutorial.md](docs/tutorial.md) | Learn the tool by building a model from scratch |
74
+ | [docs/patterns.md](docs/patterns.md) | The four Event Modeling patterns and their DSL shapes |
75
+ | [docs/dsl.md](docs/dsl.md) | Full DSL reference: keywords, `from`, `again`, fields, notes |
76
+ | [docs/cli.md](docs/cli.md) | Every command and flag |
77
+ | [docs/validation.md](docs/validation.md) | Every rule `em validate` checks, and the fixes |
78
+ | [docs/timeline.md](docs/timeline.md) | The Two Laws of the Timeline |
79
+ | [docs/ai-workflow.md](docs/ai-workflow.md) | The Claude Code skill: install, phases, artifacts |
80
+ | [docs/dependencies.md](docs/dependencies.md) | What's bundled vs. what needs a system install |
81
+ | [docs/architecture.md](docs/architecture.md) | How rendering works; why Graphviz, not PlantUML |
82
+ | [docs/roadmap.md](docs/roadmap.md) | What's planned |
212
83
 
213
84
  ## Development
214
85
 
@@ -219,32 +90,6 @@ npm test # vitest
219
90
  npx tsx src/cli.ts <command> ... # run straight from source
220
91
  ```
221
92
 
222
- ## AI Assistant (Claude Code)
223
-
224
- `em` ships a **Claude Code skill** that guides you and your team through Event Modeling using the 7-step process and 4 design patterns, producing implementation-ready slice specifications. The AI drives the model — asking focused questions, never guessing domain facts — while `em validate` keeps the diagram honest.
225
-
226
- **Install the skill into your project:**
227
-
228
- ```bash
229
- em skill install # copies the skill into .claude/skills/event-modeling/
230
- em skill install --force # overwrite an existing installation
231
- ```
232
-
233
- Then, in Claude Code, run:
234
-
235
- ```
236
- /event-modeling # start (or resume) a guided session
237
- /event-modeling discover # steps 1–4: brainstorm events, timeline, commands, read models
238
- /event-modeling model # steps 5–7: swimlanes, patterns, completeness check
239
- /event-modeling slice # deep implementation specs, one per slice (frontmatter via `em slice new`)
240
- /event-modeling watch # open a live browser view for team modeling sessions
241
- /event-modeling validate # run em validate and resolve all diagnostics
242
- ```
243
-
244
- Sessions are **resumable** — the skill checkpoints progress in `.event-modeling.md` so you can continue across conversations.
245
-
246
- For a complete worked example (a headless CPQ system with ~50 slices), see the [em-with-ai repository](https://github.com/milehimikey/em-with-ai).
247
-
248
93
  ## License
249
94
 
250
95
  [MIT](LICENSE) © milehimikey
package/dist/cli.js CHANGED
@@ -4,24 +4,19 @@ import { readFileSync, writeFileSync, existsSync } from "node:fs";
4
4
  import { cp, mkdir } from "node:fs/promises";
5
5
  import { basename, dirname, extname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { Command, Option } from "commander";
7
+ import { Command } from "commander";
8
8
  import { compile } from "./pipeline.js";
9
9
  import { ParseError } from "./parser/parser.js";
10
10
  import { renderDot, formatFromPath } from "./render/render.js";
11
11
  import { watchFile } from "./render/watch.js";
12
+ import { startLiveServer } from "./render/serve.js";
12
13
  import { formatDiagnostic, hasErrors } from "./model/validate.js";
13
- import { loadSliceDocs } from "./model/sliceDoc.js";
14
- import { validateSliceDocs, VALID_PATTERNS, VALID_STATUSES } from "./model/validateSliceDocs.js";
15
- import { newSlice } from "./slice/generate.js";
16
- import { syncAll, syncSlice } from "./slice/sync.js";
17
- import { listSliceRecords, listSlices, searchSliceRecords, searchSlices, showSlice, updateSlice, } from "./slice/query.js";
18
- import { applyMigration, assertCleanWorkingTree, formatMigrationReport, planMigration, } from "./migrate/migrate.js";
19
14
  import { STARTER_EM } from "./templates.js";
20
15
  const program = new Command();
21
16
  program
22
17
  .name("em")
23
18
  .description("Event Modeling CLI — slice-first DSL rendered as a strict Graphviz grid")
24
- .version("1.1.0");
19
+ .version("1.2.0");
25
20
  program
26
21
  .command("init")
27
22
  .description("scaffold a starter .em model")
@@ -75,9 +70,12 @@ program
75
70
  .option("-o, --out <path>", "output path (extension picks the format)")
76
71
  .option("-T, --format <fmt>", "output format (svg, png, pdf, ...)")
77
72
  .option("--keep-empty-lanes", "keep the API lane even when empty")
73
+ .option("--serve", "serve a live viewer with instant push-reload (no polling)")
74
+ .option("--port <n>", "port for --serve (default 5173)", (v) => parseInt(v, 10))
78
75
  .action(async (file, opts) => {
79
76
  const out = opts.out ?? defaultOut(file, opts.format ?? "svg");
80
77
  const fmt = opts.format ?? formatFromPath(out);
78
+ let server;
81
79
  const build = async () => {
82
80
  const started = Date.now();
83
81
  try {
@@ -92,12 +90,25 @@ program
92
90
  }
93
91
  await renderDot(dot, model, out, fmt, dirname(file));
94
92
  console.log(`rendered ${out} (${Date.now() - started}ms)`);
93
+ server?.notify();
95
94
  }
96
95
  catch (e) {
97
96
  reportError(e);
98
97
  }
99
98
  };
100
99
  await build();
100
+ if (opts.serve) {
101
+ try {
102
+ // Serve the directory the SVG is written to — that's what the browser
103
+ // fetches, and note "..." links inside the SVG resolve relative to it.
104
+ server = await startLiveServer({ dir: dirname(resolve(out)), port: opts.port });
105
+ console.log(`→ live view: ${server.url}/?svg=${encodeURIComponent(basename(out))}`);
106
+ console.log(" open it in a browser and share your screen");
107
+ }
108
+ catch (e) {
109
+ reportError(e);
110
+ }
111
+ }
101
112
  watchFile(file, build);
102
113
  console.log(`watching ${file} … (ctrl-c to stop)`);
103
114
  });
@@ -105,13 +116,8 @@ program
105
116
  .command("validate")
106
117
  .description("check a model against event-modeling rules")
107
118
  .argument("<file>", "input .em file")
108
- .option("--skip-slices", "don't validate slice docs (slices/*.md)")
109
- .action((file, opts) => {
110
- const { model, diagnostics } = compileFile(file);
111
- if (!opts.skipSlices) {
112
- const docs = loadSliceDocs(dirname(file));
113
- diagnostics.push(...validateSliceDocs(model, file, docs));
114
- }
119
+ .action((file) => {
120
+ const { diagnostics } = compileFile(file);
115
121
  printDiagnostics(diagnostics);
116
122
  if (hasErrors(diagnostics))
117
123
  process.exit(1);
@@ -139,198 +145,6 @@ skill
139
145
  console.log(`installed event-modeling skill → ${dest}`);
140
146
  console.log("in Claude Code, run /event-modeling to start a guided session");
141
147
  });
142
- const slice = program.command("slice").description("manage slice design docs (slices/*.md)");
143
- slice
144
- .command("new")
145
- .description("scaffold a new slice doc with injected frontmatter")
146
- .argument("<name>", 'slice title, e.g. "Place Order"')
147
- .option("--dir <path>", "model directory", ".")
148
- .option("--model <path>", "explicit .em file (default: the only .em in --dir)")
149
- .option("--config <path>", "explicit em.config.json path")
150
- .addOption(new Option("--pattern <pattern>", "slice pattern").choices(VALID_PATTERNS).default("state-change"))
151
- .option("--id <id>", "explicit slice id (default: kebab-case of the name)")
152
- .option("--slice-element <id>", "the .em Element.id this doc documents, if already known")
153
- .option("-f, --force", "overwrite an existing doc with this id")
154
- .action((name, opts) => {
155
- try {
156
- const result = newSlice(name, {
157
- dir: opts.dir,
158
- modelPath: opts.model,
159
- configPath: opts.config,
160
- pattern: opts.pattern,
161
- id: opts.id,
162
- sliceElement: opts.sliceElement,
163
- force: opts.force,
164
- });
165
- console.log(`wrote ${result.path} (id: ${result.id})`);
166
- }
167
- catch (e) {
168
- reportError(e);
169
- process.exit(1);
170
- }
171
- });
172
- slice
173
- .command("sync")
174
- .description("recompute generated frontmatter fields (commands/events/readModels/contexts/personas/triggers) from the .em")
175
- .argument("[id]", "slice id to sync (omit with --all)")
176
- .option("--dir <path>", "model directory", ".")
177
- .option("--model <path>", "explicit .em file")
178
- .option("--all", "sync every slice doc in the directory")
179
- .action((id, opts) => {
180
- try {
181
- if (opts.all) {
182
- const { synced, skipped } = syncAll({ dir: opts.dir, modelPath: opts.model });
183
- for (const s of synced)
184
- console.log(`synced ${s.path}${s.changed ? "" : " (no changes)"}`);
185
- for (const s of skipped)
186
- console.warn(` skip ${s.file}: ${s.reason}`);
187
- }
188
- else {
189
- if (!id) {
190
- console.error("pass a slice id, or --all");
191
- process.exit(1);
192
- }
193
- const result = syncSlice(id, { dir: opts.dir, modelPath: opts.model });
194
- console.log(`synced ${result.path}${result.changed ? "" : " (no changes)"}`);
195
- }
196
- }
197
- catch (e) {
198
- reportError(e);
199
- process.exit(1);
200
- }
201
- });
202
- slice
203
- .command("list")
204
- .description("list slice docs from frontmatter only (no .em parse, no body read)")
205
- .option("--dir <path>", "model directory", ".")
206
- .addOption(new Option("--status <status>", "filter by status").choices(VALID_STATUSES))
207
- .addOption(new Option("--pattern <pattern>", "filter by pattern").choices(VALID_PATTERNS))
208
- .option("--context <context>", "filter by context")
209
- .option("--tag <tag>", "filter by tag")
210
- .option("--format <fmt>", "table | json", "table")
211
- .option("--full", "emit every frontmatter field per slice (requires --format json)")
212
- .action((opts) => {
213
- const filter = {
214
- dir: opts.dir,
215
- status: opts.status,
216
- pattern: opts.pattern,
217
- context: opts.context,
218
- tag: opts.tag,
219
- };
220
- if (opts.full) {
221
- requireJson(opts.format);
222
- console.log(JSON.stringify(listSliceRecords(filter), null, 2));
223
- return;
224
- }
225
- printSlices(listSlices(filter), opts.format);
226
- });
227
- slice
228
- .command("show")
229
- .description("show one slice doc's frontmatter")
230
- .argument("<id>", "slice id")
231
- .option("--dir <path>", "model directory", ".")
232
- .option("--format <fmt>", "table | json", "table")
233
- .option("--body", "include the doc body")
234
- .action((id, opts) => {
235
- const doc = showSlice(id, { dir: opts.dir });
236
- if (!doc) {
237
- console.error(`no slice doc with id "${id}" found in ${opts.dir}`);
238
- process.exit(1);
239
- }
240
- if (opts.format === "json") {
241
- const payload = opts.body ? { ...doc.frontmatter, body: doc.body } : doc.frontmatter;
242
- console.log(JSON.stringify(payload, null, 2));
243
- return;
244
- }
245
- console.log(`# ${doc.file}`);
246
- for (const [k, v] of Object.entries(doc.frontmatter ?? {})) {
247
- console.log(`${k}: ${JSON.stringify(v)}`);
248
- }
249
- if (opts.body) {
250
- console.log("");
251
- console.log(doc.body);
252
- }
253
- });
254
- slice
255
- .command("search")
256
- .description("search slice docs by frontmatter only (title/tags/commands/events/readModels)")
257
- .argument("[query]", "free-text query", "")
258
- .option("--dir <path>", "model directory", ".")
259
- .addOption(new Option("--status <status>", "filter by status").choices(VALID_STATUSES))
260
- .addOption(new Option("--pattern <pattern>", "filter by pattern").choices(VALID_PATTERNS))
261
- .option("--context <context>", "filter by context")
262
- .option("--tag <tag>", "filter by tag")
263
- .option("--format <fmt>", "table | json", "table")
264
- .option("--full", "emit every frontmatter field per slice (requires --format json)")
265
- .action((query, opts) => {
266
- const filter = {
267
- dir: opts.dir,
268
- status: opts.status,
269
- pattern: opts.pattern,
270
- context: opts.context,
271
- tag: opts.tag,
272
- };
273
- if (opts.full) {
274
- requireJson(opts.format);
275
- console.log(JSON.stringify(searchSliceRecords(query, filter), null, 2));
276
- return;
277
- }
278
- printSlices(searchSlices(query, filter), opts.format);
279
- });
280
- slice
281
- .command("update")
282
- .description("write status/version changes back into a slice doc's frontmatter")
283
- .argument("<id>", "slice id")
284
- .option("--dir <path>", "model directory", ".")
285
- .addOption(new Option("--status <status>", "new status").choices(VALID_STATUSES))
286
- .option("--bump-version", "increment version by 1")
287
- .action((id, opts) => {
288
- try {
289
- const result = updateSlice(id, {
290
- dir: opts.dir,
291
- status: opts.status,
292
- bumpVersion: opts.bumpVersion,
293
- });
294
- console.log(`updated ${result.path}`);
295
- }
296
- catch (e) {
297
- reportError(e);
298
- process.exit(1);
299
- }
300
- });
301
- program
302
- .command("migrate")
303
- .description("upgrade a pre-1.0 model directory to the frontmatter standard (docs/1.0.0-spec.md)")
304
- .argument("<model-dir>", "model directory")
305
- .option("--model <path>", "explicit .em file")
306
- .option("--dry-run", "print the plan/report without writing")
307
- .option("--force", "proceed even with uncommitted changes or no git repo")
308
- .option("--report <path>", "write the migration report to a file instead of stdout")
309
- .action((dir, opts) => {
310
- try {
311
- if (!opts.dryRun)
312
- assertCleanWorkingTree(dir, opts.force);
313
- const plan = planMigration(dir, { modelPath: opts.model });
314
- const report = formatMigrationReport(plan);
315
- if (opts.report) {
316
- writeFileSync(opts.report, report);
317
- console.log(`wrote migration report -> ${opts.report}`);
318
- }
319
- else {
320
- console.log(report);
321
- }
322
- if (opts.dryRun) {
323
- console.log("dry run — no files written");
324
- return;
325
- }
326
- applyMigration(plan);
327
- console.log("migration applied");
328
- }
329
- catch (e) {
330
- reportError(e);
331
- process.exit(1);
332
- }
333
- });
334
148
  program.parseAsync().catch((e) => {
335
149
  reportError(e);
336
150
  process.exit(1);
@@ -383,24 +197,4 @@ function printDiagnostics(diags) {
383
197
  function reportError(e) {
384
198
  console.error(e instanceof Error ? e.message : String(e));
385
199
  }
386
- /** --full emits a shape only JSON can carry; fail loudly rather than silently ignoring it. */
387
- function requireJson(format) {
388
- if (format !== "json") {
389
- console.error("--full requires --format json");
390
- process.exit(1);
391
- }
392
- }
393
- function printSlices(results, format) {
394
- if (format === "json") {
395
- console.log(JSON.stringify(results, null, 2));
396
- return;
397
- }
398
- if (results.length === 0) {
399
- console.log("no matching slices");
400
- return;
401
- }
402
- for (const s of results) {
403
- console.log(`${s.id}\t${s.pattern ?? "?"}\t${s.status ?? "?"}\tv${s.version ?? "?"}\t${s.title}`);
404
- }
405
- }
406
200
  //# sourceMappingURL=cli.js.map