@milehimikey/em 1.17.0 → 1.18.1
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/.claude/agents/em-implementer.md +7 -4
- package/.claude/agents/em-validator.md +2 -2
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +46 -5
- package/.claude/skills/event-modeling-discover/SKILL.md +43 -8
- package/.claude/skills/event-modeling-discover/reference/extract.md +6 -1
- package/.claude/skills/event-modeling-discover/reference/prd-rubric.md +102 -0
- package/.claude/skills/event-modeling-discover/reference/prd-spec-path.md +171 -0
- package/.claude/skills/event-modeling-engagement/SKILL.md +21 -9
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/reference/implement.md +18 -0
- package/.claude/skills/event-modeling-review/SKILL.md +4 -2
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +94 -6
- package/.claude/skills/event-modeling-shared/reference/methodology.md +268 -0
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +96 -2
- package/.claude/skills/event-modeling-shared/templates/slice.md +11 -3
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/catalog/docModelConsistencyValidate.js +160 -0
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/invariantsValidate.d.ts +2 -0
- package/dist/catalog/invariantsValidate.js +67 -1
- package/dist/catalog/invariantsValidate.js.map +1 -1
- package/dist/catalog/scenarioQualityValidate.d.ts +4 -0
- package/dist/catalog/scenarioQualityValidate.js +148 -0
- package/dist/catalog/scenarioQualityValidate.js.map +1 -0
- package/dist/catalog/scenarios.d.ts +5 -0
- package/dist/catalog/scenarios.js +80 -0
- package/dist/catalog/scenarios.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +4 -0
- package/dist/catalog/sliceDoc.js +16 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -2
- package/dist/catalog/sliceReadyValidate.js +62 -3
- package/dist/catalog/sliceReadyValidate.js.map +1 -1
- package/dist/catalog/sliceSections.d.ts +3 -2
- package/dist/catalog/sliceSections.js +99 -9
- package/dist/catalog/sliceSections.js.map +1 -1
- package/dist/cli/ciInit.d.ts +4 -11
- package/dist/cli/ciInit.js +24 -6
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/coverage.d.ts +9 -0
- package/dist/cli/coverage.js +18 -0
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/defer.d.ts +28 -0
- package/dist/cli/defer.js +87 -4
- package/dist/cli/defer.js.map +1 -1
- package/dist/cli/engagement.d.ts +13 -2
- package/dist/cli/engagement.js +60 -5
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementFile.d.ts +2 -0
- package/dist/cli/engagementFile.js +3 -1
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/markImplemented.d.ts +14 -10
- package/dist/cli/markImplemented.js +49 -1
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/prdTriage.d.ts +109 -0
- package/dist/cli/prdTriage.js +393 -0
- package/dist/cli/prdTriage.js.map +1 -0
- package/dist/cli/render-inputs.d.ts +2 -0
- package/dist/cli/render-inputs.js +4 -0
- package/dist/cli/render-inputs.js.map +1 -1
- package/dist/cli/review.d.ts +17 -2
- package/dist/cli/review.js +39 -10
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/revise.d.ts +2 -0
- package/dist/cli/revise.js +7 -2
- package/dist/cli/revise.js.map +1 -1
- package/dist/cli/shape.d.ts +27 -0
- package/dist/cli/shape.js +77 -0
- package/dist/cli/shape.js.map +1 -0
- package/dist/cli/sliceIndex.d.ts +6 -1
- package/dist/cli/sliceIndex.js +28 -13
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/sliceNew.d.ts +4 -1
- package/dist/cli/sliceNew.js +26 -9
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/status.d.ts +21 -1
- package/dist/cli/status.js +25 -3
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/storylineSuggest.d.ts +26 -0
- package/dist/cli/storylineSuggest.js +252 -0
- package/dist/cli/storylineSuggest.js.map +1 -0
- package/dist/cli/upgrade.d.ts +15 -3
- package/dist/cli/upgrade.js +516 -7
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli/upgradeReshape.d.ts +60 -0
- package/dist/cli/upgradeReshape.js +425 -0
- package/dist/cli/upgradeReshape.js.map +1 -0
- package/dist/cli.js +311 -35
- package/dist/cli.js.map +1 -1
- package/dist/emit/dot.d.ts +2 -0
- package/dist/emit/dot.js +44 -8
- package/dist/emit/dot.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +4 -3
- package/dist/emit/engagementJson.js +11 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/emit/json.d.ts +21 -1
- package/dist/emit/json.js +26 -2
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/queryJson.d.ts +1 -1
- package/dist/emit/queryJson.js +3 -1
- package/dist/emit/queryJson.js.map +1 -1
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +6 -1
- package/dist/emit/statusJson.js.map +1 -1
- package/dist/emit/theme.d.ts +1 -0
- package/dist/emit/theme.js +13 -0
- package/dist/emit/theme.js.map +1 -1
- package/dist/emit/validateJson.d.ts +9 -5
- package/dist/emit/validateJson.js +40 -5
- package/dist/emit/validateJson.js.map +1 -1
- package/dist/layout/grid.d.ts +5 -1
- package/dist/layout/grid.js +93 -2
- package/dist/layout/grid.js.map +1 -1
- package/dist/mcp/server.js +46 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/edges.d.ts +7 -0
- package/dist/model/edges.js +33 -16
- package/dist/model/edges.js.map +1 -1
- package/dist/model/model.d.ts +33 -0
- package/dist/model/model.js +87 -3
- package/dist/model/model.js.map +1 -1
- package/dist/model/queryIndex.js +2 -2
- package/dist/model/queryIndex.js.map +1 -1
- package/dist/model/refs.js +8 -5
- package/dist/model/refs.js.map +1 -1
- package/dist/model/rules.d.ts +149 -0
- package/dist/model/rules.js +144 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/shapeLint.d.ts +12 -0
- package/dist/model/shapeLint.js +170 -0
- package/dist/model/shapeLint.js.map +1 -0
- package/dist/model/storylines.d.ts +51 -0
- package/dist/model/storylines.js +188 -0
- package/dist/model/storylines.js.map +1 -0
- package/dist/model/validate.d.ts +6 -1
- package/dist/model/validate.js +317 -14
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/againSugar.d.ts +30 -0
- package/dist/parser/againSugar.js +105 -0
- package/dist/parser/againSugar.js.map +1 -0
- package/dist/parser/ast.d.ts +33 -0
- package/dist/parser/lexer.d.ts +2 -0
- package/dist/parser/lexer.js +4 -0
- package/dist/parser/lexer.js.map +1 -1
- package/dist/parser/parser.js +284 -4
- package/dist/parser/parser.js.map +1 -1
- package/dist/query/format.js +1 -0
- package/dist/query/format.js.map +1 -1
- package/dist/query/verbs.d.ts +4 -0
- package/dist/query/verbs.js +30 -3
- package/dist/query/verbs.js.map +1 -1
- package/dist/render/drawEdges.js +100 -4
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/render.js +20 -2
- package/dist/render/render.js.map +1 -1
- package/dist/render/sliceDiagram.js +5 -5
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/storylineBands.d.ts +11 -0
- package/dist/render/storylineBands.js +80 -0
- package/dist/render/storylineBands.js.map +1 -0
- package/dist/render/storylineDiagram.d.ts +14 -0
- package/dist/render/storylineDiagram.js +142 -0
- package/dist/render/storylineDiagram.js.map +1 -0
- package/dist/roster/check.d.ts +32 -0
- package/dist/roster/check.js +92 -0
- package/dist/roster/check.js.map +1 -0
- package/dist/roster/context.d.ts +3 -0
- package/dist/roster/context.js +23 -0
- package/dist/roster/context.js.map +1 -0
- package/dist/roster/init.d.ts +15 -0
- package/dist/roster/init.js +52 -0
- package/dist/roster/init.js.map +1 -0
- package/dist/roster/roster.d.ts +79 -0
- package/dist/roster/roster.js +298 -0
- package/dist/roster/roster.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/agents/em-implementer.md +7 -4
- package/plugin/agents/em-validator.md +2 -2
- package/plugin/shared/reference/em-dsl.md +94 -6
- package/plugin/shared/reference/methodology.md +268 -0
- package/plugin/shared/reference/slice-doc-schema.md +96 -2
- package/plugin/shared/templates/slice.md +11 -3
- package/plugin/skills/conform/SKILL.md +1 -1
- package/plugin/skills/design/SKILL.md +46 -5
- package/plugin/skills/discover/SKILL.md +43 -8
- package/plugin/skills/discover/reference/extract.md +6 -1
- package/plugin/skills/discover/reference/prd-rubric.md +103 -0
- package/plugin/skills/discover/reference/prd-spec-path.md +172 -0
- package/plugin/skills/engagement/SKILL.md +21 -9
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +1 -1
- package/plugin/skills/implement/reference/implement.md +18 -0
- package/plugin/skills/review/SKILL.md +4 -2
|
@@ -89,6 +89,68 @@ over an omission — it reports). `implementedIn` and the lineage keys are the g
|
|
|
89
89
|
optional ones; `implementedIn`'s required-once-implemented rule in the table below is
|
|
90
90
|
convention, not parser-enforced.
|
|
91
91
|
|
|
92
|
+
## Who may ratify
|
|
93
|
+
|
|
94
|
+
The schema records *who* signed (`reviewedBy`, `ratifiedBy`) but, by default, accepts any
|
|
95
|
+
non-empty name: who is allowed to sign is the host repo's decision. A host that wants em to
|
|
96
|
+
enforce it supplies an optional **roster**, `ratifiers.yaml`, beside the model. See
|
|
97
|
+
[examples/order-fulfillment/ratifiers.yaml](https://github.com/milehimikey/em/blob/main/examples/order-fulfillment/ratifiers.yaml).
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
rosterSchemaVersion: "1.0"
|
|
101
|
+
ratifiers:
|
|
102
|
+
- name: Aaron McKay
|
|
103
|
+
contexts: [Product, Attribute] # omit = the whole model
|
|
104
|
+
- name: Mike Key
|
|
105
|
+
- name: Dana Lee
|
|
106
|
+
models: [pim] # omit = every model the roster governs
|
|
107
|
+
reviewers: # optional; same shape, checked by `em slice review --by`
|
|
108
|
+
- name: Mike Key
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **Schema.** `rosterSchemaVersion` is required (`"1.0"`); at least one of `ratifiers:` /
|
|
112
|
+
`reviewers:` is required; each is a non-empty list of `{ name, contexts?, models? }`. Unknown
|
|
113
|
+
keys are errors (`roster-invalid`). A missing `ratifiers:` / `reviewers:` key leaves that role
|
|
114
|
+
unrestricted.
|
|
115
|
+
- **Placement.** The nearest `ratifiers.yaml` walking up from the model's directory, stopping at
|
|
116
|
+
the nearest directory holding `.git` (or the filesystem root). If no `.git` exists anywhere
|
|
117
|
+
above the model, only the model's own directory is searched. `--roster <path>` on `ratify`,
|
|
118
|
+
`review`, `validate --slice-ready`, `roster check` overrides discovery; a path that does not
|
|
119
|
+
exist is an error, never "no roster".
|
|
120
|
+
- **A slice's contexts** are the contexts of its **events** plus the contexts of the events its
|
|
121
|
+
**views read** - each view instance's **own** `from`, never a union across the read model's
|
|
122
|
+
instances (adding a later `again` instance never changes an earlier slice's contexts). A
|
|
123
|
+
continuation slice also takes its originating instance's own `from`. A slice with neither has no
|
|
124
|
+
context and is permitted only by entries without `contexts`.
|
|
125
|
+
A continuation fed from a different context than its originating view (e.g. `tool-shows-on-loan` =
|
|
126
|
+
Loan + Tool) therefore needs a ratifier permitted for every one of those contexts, so a team with
|
|
127
|
+
one lead per context needs a joint or whole-model roster entry for such slices.
|
|
128
|
+
- **An entry permits a slice** when it applies to the model and either has no `contexts` or lists
|
|
129
|
+
**every** context of the slice: a cross-context slice needs a ratifier who may sign for all of
|
|
130
|
+
them (a leading `@` is tolerated in `contexts`).
|
|
131
|
+
- **Per-model scoping.** `models: [<modelKey>]` (the key `em export` reports as `model.key`) limits
|
|
132
|
+
an entry to those models; an entry without it applies to every model the roster governs. Context
|
|
133
|
+
names are matched **per model** against that model's declared contexts, so a shared root roster
|
|
134
|
+
needs `models:` whenever the models' ratifiers differ. A `models:` key the enclosing
|
|
135
|
+
`system.yaml` does not list is `roster-invalid`.
|
|
136
|
+
- **Validation against the model.** A `contexts:` entry the model does not declare (case-sensitive)
|
|
137
|
+
is `roster-invalid`, naming the context; only entries that apply to the model are checked.
|
|
138
|
+
- **File absent: today's behaviour**, no warning. **File present but invalid: fail closed** -
|
|
139
|
+
`ratify` and `review` refuse, `--slice-ready` and `roster check` report `roster-invalid`.
|
|
140
|
+
- **`grandfathered (unsigned before em 1.14)`** is em's own migration marker (`em upgrade`'s
|
|
141
|
+
`ratified-signoff`), not a signer: exempt from the `--slice-ready` check and from `roster check`
|
|
142
|
+
(which counts it) until the slice is re-ratified - but only the **exact** string, and only on a
|
|
143
|
+
doc with no `ratifiedHash` (the state `em upgrade` leaves). `grandfathered by X`, or the marker on
|
|
144
|
+
a doc that carries a hash, is a signer and is checked.
|
|
145
|
+
- **Where it is enforced.** At write time by `em slice ratify` / `em slice review` (the refusal
|
|
146
|
+
lists the permitted names); at check time by `em validate --slice-ready <key>`
|
|
147
|
+
(`ratified-by-not-in-roster`, error - one `ready-to-implement` slice, ratifier only) and by
|
|
148
|
+
`em roster check <model>` (every doc, `ratifiedBy` on `ready-to-implement` and `implemented`,
|
|
149
|
+
`reviewedBy` on any), the command `em ci init` wires into CI when a roster exists - see
|
|
150
|
+
[ci.md](ci.md#ratifier-roster-gate). `em status` reports `roster: present|absent|invalid`.
|
|
151
|
+
- `em roster init <model>` seeds the file from the names already in the docs
|
|
152
|
+
([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-roster-init-file)).
|
|
153
|
+
|
|
92
154
|
## Required vs optional, by `status`
|
|
93
155
|
|
|
94
156
|
| Key | draft | reviewed | ready-to-implement | implemented |
|
|
@@ -262,6 +324,21 @@ the union of events every later `again` instance of that view reads via `from`,
|
|
|
262
324
|
order — so implementing the read model from one export never means walking every `again`
|
|
263
325
|
slice's doc by hand to assemble the full event list.
|
|
264
326
|
|
|
327
|
+
The generated `em-slice-view` region follows the same rule (GH #280, 1.18.0): its `built from
|
|
328
|
+
events:` line lists the union of `from` across the originating instance and every `again`
|
|
329
|
+
instance (originating first, then timeline order, deduped), and each field's `from event` note
|
|
330
|
+
resolves against that union (each later event annotated `(via "<slice>")` with its instance's slice) — whichever instance's slice the doc is bound to. `em slice sync`
|
|
331
|
+
rewrites existing view regions accordingly.
|
|
332
|
+
|
|
333
|
+
**Synthesized continuations (GH #287).** A `view X again` line written inside a command's own slice
|
|
334
|
+
is lifted into a continuation column right after it. That column is a continuation like any other
|
|
335
|
+
(`continuationOf` names the view's originating slice, `alsoReads` counts its events) but exists
|
|
336
|
+
only as sugar over the host slice's text: its export key is `<host-slice-key>--again` (one column per host slice, its lifted lines stacked),
|
|
337
|
+
it never has a doc, and `em slice index` lists no row for it. A slice key never contains `/` (the slug generator emits single dashes only, so `--` cannot occur in a hand-written key); the `/` in an element ref `<slice-key>/<kind>.<slug>` is always the first and only one. A hand-written continuation slice
|
|
338
|
+
may also hold several `again` views, and may omit its name (auto-named `"<View> View: <Event>"`).
|
|
339
|
+
When one slice stacks views of different read models, `continuationOf` names the originating
|
|
340
|
+
slice of the FIRST view; the others are listed in their originating slices' `alsoReads` (with this slice's key as `atSlice`).
|
|
341
|
+
|
|
265
342
|
**Authoring convention:** a continuation's behavior belongs in the originating doc's own
|
|
266
343
|
`## Scenarios (Given / When / Then)` section — one scenario per event a later `again` instance
|
|
267
344
|
feeds — rather than in a doc of its own; no new frontmatter is needed to record it.
|
|
@@ -398,7 +475,7 @@ re-run `em slice sync`.
|
|
|
398
475
|
|---|---|---|
|
|
399
476
|
| `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
|
|
400
477
|
| `em-slice-event` | `## Event(s) Emitted` | `**Event:** \`Name\` → context \`Ctx\`` + `\| Field \| Type \| Immutable Fact? \| Source / Notes \|` (Source: the same-slice command field it is copied from, `assigned` for a system-assigned field, else `{{ }}`) |
|
|
401
|
-
| `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` + `\| Field \| Type \| Source / Notes \|` (Source: the source event carrying the field, `Derived` for a `derived` field, else `{{ }}`) |
|
|
478
|
+
| `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` (the union across the originating and every `again` instance; each later event is annotated `(via "<slice>")` with the slice of the instance that adds it) + `\| Field \| Type \| Source / Notes \|` (Source: the source event, from that union, carrying the field, `Derived` for a `derived` field, `Collection of \`T\`, fed by …` for a `T[] derived from …` field — followed by one neutral Given/Then scenario stub per named event under the table, to copy into `## Scenarios` and complete there (the region itself stays read-only) — else `{{ }}`) |
|
|
402
479
|
| `em-slice-invariants` | `## Invariants / Business Rules` | one `- **INV-X** — rule` line per model-declared invariant of the slice's commands and events |
|
|
403
480
|
|
|
404
481
|
A slice with several elements of one kind gets one region per element, the name suffixed with
|
|
@@ -437,12 +514,22 @@ case is a block:
|
|
|
437
514
|
- **Given:**
|
|
438
515
|
- a basket whose total is 0
|
|
439
516
|
- **When:**
|
|
440
|
-
- the customer
|
|
517
|
+
- the customer issues PlaceOrder
|
|
441
518
|
- **Then:**
|
|
442
519
|
- the command is rejected with a validation error
|
|
443
520
|
- no event is recorded
|
|
444
521
|
```
|
|
445
522
|
|
|
523
|
+
**Scenario quality (citation convention).** A scenario is tied to exactly one command: its When
|
|
524
|
+
names the slice's command (a reaction slice: the command it triggers), its Then names an event
|
|
525
|
+
the slice records or a rejection/error, and it **cites the `INV-…` ids it exercises** anywhere in
|
|
526
|
+
the block (the title above, or a Given/When/Then bullet). Every invariant declared for the slice
|
|
527
|
+
(an `invariant` line in the model, or the doc's Invariants section) should be cited by at least one
|
|
528
|
+
scenario. `em validate` advises on all three — `scenario-when-not-command`,
|
|
529
|
+
`scenario-then-not-outcome`, `invariant-without-scenario` — see
|
|
530
|
+
[validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#scenario-quality). Older bullet-format scenarios are checked too; a doc with no scenarios is never
|
|
531
|
+
flagged.
|
|
532
|
+
|
|
446
533
|
A `### Scenario: <title>` heading, then the three column-0 bullets `- **Given:**`, `- **When:**`,
|
|
447
534
|
`- **Then:**`, each with nested ` - ` items (text after the label on the same line counts as a
|
|
448
535
|
first item). `em export --json` publishes the parsed blocks as `slice.doc.scenarios`
|
|
@@ -521,6 +608,13 @@ one it is asking about:
|
|
|
521
608
|
slice shipped, and which" is `shippedRecordOf()` (`src/catalog/sliceDoc.ts`); `em export`
|
|
522
609
|
publishes its answer as `slice.doc.shipped`.
|
|
523
610
|
|
|
611
|
+
**Reading a PR that shows `status: draft`.** When `mark-implemented` opened the next version
|
|
612
|
+
(`status: draft`, `version: <N+1>`), the diff of that slice doc does not say "never marked": the
|
|
613
|
+
`shippedVersion: <N>` / `implementedIn:` lines **are** the record that v<N> was implemented, and
|
|
614
|
+
`status:` is the state of v<N+1>. The command prints a notice naming both
|
|
615
|
+
([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-mark-implemented-file-slice-key-pr-url)), and `em status` lists the slice
|
|
616
|
+
as `implemented (v<N>) · draft v<N+1> open` rather than a bare `draft`.
|
|
617
|
+
|
|
524
618
|
So a shipped slice can be a `draft` again. `em slice revise <key>`
|
|
525
619
|
([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-revise-file-slice-key)) opens v<N+1> as `status: draft`, bumps
|
|
526
620
|
`version:`, clears the prior version's review and sign-off, and leaves `implementedIn`/`shipped*`
|
|
@@ -210,15 +210,23 @@ an event directly.}}
|
|
|
210
210
|
--json` → slice.doc.scenarios). Cover the happy path AND the key rule boundaries, one block
|
|
211
211
|
per case: a `### Scenario: <title>` heading, then exactly the three column-0 bullets
|
|
212
212
|
`- **Given:**`, `- **When:**`, `- **Then:**`, each with its beats as nested ` - ` bullets.
|
|
213
|
-
A block missing any of the three is flagged `slice-doc/structured-section-malformed`.
|
|
213
|
+
A block missing any of the three is flagged `slice-doc/structured-section-malformed`.
|
|
214
|
+
Quality (advisory warnings): each scenario is tied to exactly ONE command — its When names
|
|
215
|
+
this slice's command (a reaction slice: the reaction, its view, or the command it triggers); its Then names an event (or a stated no-op)
|
|
216
|
+
this slice records, or a rejection/error; and it CITES the INV ids it exercises (in the
|
|
217
|
+
title or any bullet). Every INV declared for the slice should be cited by some scenario
|
|
218
|
+
(`scenario-when-not-command`, `scenario-then-not-outcome`, `invariant-without-scenario`). -->
|
|
214
219
|
### Scenario: Happy path
|
|
215
220
|
- **Given:**
|
|
216
221
|
- {{starting state / prior events}}
|
|
217
222
|
- **When:**
|
|
218
|
-
- {{command
|
|
223
|
+
- {{the command, by name}}
|
|
219
224
|
- **Then:**
|
|
220
|
-
- {{event(s) recorded}}
|
|
225
|
+
- {{the event(s) recorded, by name}}
|
|
221
226
|
- {{resulting read-model change}}
|
|
227
|
+
<!-- Add one block per rule boundary, citing the INV id in the title, e.g.
|
|
228
|
+
`### Scenario: Non-positive total is rejected (INV-CHK-1)` — When names the command, Then
|
|
229
|
+
names the rejection. -->
|
|
222
230
|
|
|
223
231
|
## Alternate & Error Flows
|
|
224
232
|
<!-- Failure paths, retries, compensations, timeouts, idempotency. -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conform
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.18.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when checking a ratified event model (and its slice docs) for drift against the codebase
|
|
6
6
|
that implements it — advisory only, never a gate, never an unprompted edit — or when walking
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.18.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when structuring a draft event model into swimlanes and the four patterns (State Change,
|
|
6
6
|
State View, Automation, Translation), evaluating a model's structural completeness, or writing
|
|
@@ -66,7 +66,13 @@ evidence"). In short:
|
|
|
66
66
|
|
|
67
67
|
Goal: a structurally complete, **validated** model with correct patterns and swimlanes.
|
|
68
68
|
|
|
69
|
-
1. **Swimlanes & patterns (step 5).**
|
|
69
|
+
1. **Swimlanes & patterns (step 5).** **Name the storylines first**: the one main storyline (the
|
|
70
|
+
end-to-end journey) and the secondary ones (retire a thing, extend an attribute, reorganize).
|
|
71
|
+
Wrap each one's slices in a `storyline "Name" [source "url"] { slice … }` block (grammar and the
|
|
72
|
+
optional `after "<storyline>"` timeline clause: `em-dsl.md`) so the diagram gets a band per
|
|
73
|
+
story and `em render --storyline "Name"` can show one alone; slices keep their file order, and
|
|
74
|
+
a model can adopt storylines incrementally (`slice-outside-storyline` is only an advisory
|
|
75
|
+
warning) — but name them before you slice, not after. Then group events into **contexts** (bounded contexts /
|
|
70
76
|
aggregates) — ask which events share a consistency boundary and who owns them. Classify each
|
|
71
77
|
slice as one of the 4 patterns. **Share the slice between every automation or translation and
|
|
72
78
|
the command it triggers** (the reaction — processor/translation — together with the command
|
|
@@ -75,7 +81,21 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
|
|
|
75
81
|
directly**. Add `translation` slices for external inputs (externally triggered: external →
|
|
76
82
|
translation → command → event, no slice before) and for the system reacting to its own state
|
|
77
83
|
(internally triggered: read model in the slice before → translation → command → event).
|
|
78
|
-
2. **
|
|
84
|
+
2. **Storyline review — before any slice doc is scaffolded.** Read the timeline as a story, not a schema
|
|
85
|
+
(`${CLAUDE_PLUGIN_ROOT}/shared/reference/methodology.md`, "The story, not the schema"; em repo
|
|
86
|
+
`docs/storyline.md`). Name the **main storyline** (end-to-end happy path) and every
|
|
87
|
+
**secondary storyline** (retire, extend, reorganize), and group the slices under them; one
|
|
88
|
+
lifecycle block per entity (N × create/update/archive, one trailing view each) is the CRUD
|
|
89
|
+
shape — reshape it with the "Reshaping an existing model" checklist. Then the **intent test**
|
|
90
|
+
per command (would a business person say this name at this step in the story? an endpoint-shaped
|
|
91
|
+
name fails), the **question test** per view (write the question it answers; no question, no
|
|
92
|
+
read model; a child view only when its query needs what the parent view lacks), and the
|
|
93
|
+
**conditional-invariant signal** (invariants or fields that apply only "if `<field>` is set"
|
|
94
|
+
mean two intents in one command: split it). The `timeline-entity-clustered`,
|
|
95
|
+
`command-second-intent` and `automation-split-by-outcome` lints are the mechanical half; the
|
|
96
|
+
1.18.0 design exit gate is `em validate --shape`. Shape the model first, scaffold docs after: a rename,
|
|
97
|
+
split or merge from the reshape would orphan docs already wired with `--wire`. The `slice` phase is unchanged (timeline order).
|
|
98
|
+
3. **Elaborate scenarios — first pass (step 6).** Scaffold every slice's doc now, one per
|
|
79
99
|
slice, none skipped: `em slice new "<slice name>" --pattern <state-change|state-view|
|
|
80
100
|
automation|translation> --swimlane "<Persona> → <Context>" --wire <model>.em` writes
|
|
81
101
|
`slices/<slice-name>.md` at `status: draft` (the same command, flags, and `--wire`
|
|
@@ -96,7 +116,7 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
|
|
|
96
116
|
the one-command fast path: a near-free stub per undocumented slice, wired, in slice order
|
|
97
117
|
(MIL-184) — skip the per-slice `em slice new` calls above and deepen individual docs later
|
|
98
118
|
when the team is ready.
|
|
99
|
-
|
|
119
|
+
4. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
|
|
100
120
|
half-slice — **every command has something that triggers it**, every command emits an event,
|
|
101
121
|
**every event is read by a read model**, **every read model has a consumer**, every view has a
|
|
102
122
|
source, every UI is reachable, every connection is
|
|
@@ -125,6 +145,19 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
|
|
|
125
145
|
session and stage `contracts/<model key>.tsp` with the model edit — the one derived artifact
|
|
126
146
|
a design session produces, always by the command, never by hand.
|
|
127
147
|
|
|
148
|
+
5. **Shape exit gate (GH #282).** The model phase ends on the shape lints, not on your judgement
|
|
149
|
+
of the storyline: run `em validate <model>.em --shape` (add `--json` to read it; exit code is
|
|
150
|
+
always 0, so read the output). Every finding is either **fixed** in the model, or **accepted**
|
|
151
|
+
as one bullet in `.event-modeling.md` `## Decisions log`:
|
|
152
|
+
`- YYYY-MM-DD: shape-accepted: <rule> <refs> — <reason>`
|
|
153
|
+
The gate passes only when **every remaining finding, waived or not,** has such a bullet. A
|
|
154
|
+
baseline passed as `--waive-file <path>` (lines `<rule-code> <slice-key>`) only silences the CLI
|
|
155
|
+
count for an estate; it never replaces the bullet. Re-run until each remaining finding has its
|
|
156
|
+
bullet. **Refuse to move to the slice phase otherwise** (`em state set-phase slice` waits): the
|
|
157
|
+
same kind of exit gate as asserting the generated contract above. Do not accept a finding
|
|
158
|
+
without asking the user for the reason. The gate runs after doc scaffolding, so update or retire
|
|
159
|
+
wired docs for renamed, merged or split slices.
|
|
160
|
+
|
|
128
161
|
End of phase: render, update state (`em state set-phase slice` — the mechanical marker the state
|
|
129
162
|
machine expects), and stop. Every slice already has a draft doc ready to receive the deep spec,
|
|
130
163
|
but slicing is deliberate work the team starts when it's ready for it, not this phase's automatic
|
|
@@ -140,9 +173,17 @@ events already being settled, so working out of order means re-litigating an ear
|
|
|
140
173
|
contract mid-spec. Check `README.md`'s Slices table for what's already done (run `em slice index
|
|
141
174
|
<model-name>.em` first if it looks stale).
|
|
142
175
|
|
|
176
|
+
**Outcomes stay in the slice.** When the deep dive surfaces failure, retry or timeout, add the
|
|
177
|
+
outcome events (`Fulfilment Failed`, `Fulfilment Retry Scheduled`, ...) and their Given/When/Then
|
|
178
|
+
scenarios to the SAME slice, with retry policy and failure handling as invariants in its doc —
|
|
179
|
+
never a sibling slice per control-flow branch (1.18.0 adds the lint `automation-split-by-outcome`). A new slice
|
|
180
|
+
only when a different actor or a different trigger is involved. Canon: Dilger, "one business
|
|
181
|
+
action… two different possible outcomes" and "Linearize it!"; Dymitruk, "a few of these stories
|
|
182
|
+
to show how a command can and cannot succeed".
|
|
183
|
+
|
|
143
184
|
This is also where **branch / unhappy-path events** are discovered and added to the model — as a
|
|
144
185
|
slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
|
|
145
|
-
the
|
|
186
|
+
the outcome event to the same slice unless a different actor or trigger is involved, then re-render. The happy-path spine from earlier
|
|
146
187
|
phases is the starting point, not the finished event set. **Every new event needs its reader too**
|
|
147
188
|
— a rejection or cancellation that nothing projects will warn, and usually the missing piece is
|
|
148
189
|
real (someone has to see that the request was declined).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: discover
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.18.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when starting a brand-new event model from scratch — greenfield discovery: brainstorming
|
|
6
6
|
past-tense events, storyboarding the happy path, finding the commands and read models — or when
|
|
@@ -22,28 +22,40 @@ layout, the Socratic/validation discipline every phase follows) and
|
|
|
22
22
|
work — they are the source of truth. Run the preconditions there first.
|
|
23
23
|
|
|
24
24
|
**Existing system?** Use the `extract` phase below. **New feature inside an existing codebase,
|
|
25
|
-
not a whole-system extraction?** Stay in `discover`, but
|
|
26
|
-
Grep/Read adjacent real sources (OpenAPI
|
|
27
|
-
|
|
28
|
-
field tables (`em:design`).
|
|
25
|
+
not a whole-system extraction?** Stay in `discover`, but **only after the storyboard exists**
|
|
26
|
+
(step 2) and before locking in command/event names, Grep/Read adjacent real sources (OpenAPI
|
|
27
|
+
specs, DB migrations, existing DTOs/event classes in sibling contexts) rather than guessing
|
|
28
|
+
conventions — same check the `slice` phase applies to field tables (`em:design`).
|
|
29
|
+
Those sources fill operations no walkthrough touches; they never supply the story.
|
|
29
30
|
|
|
30
31
|
## Phase: `discover` — steps 1-4
|
|
31
32
|
|
|
32
33
|
Goal: a draft model of events, storyboard, commands, and views. Loose is OK; structure comes next.
|
|
33
34
|
|
|
34
|
-
1. **Brainstorm events (step 1).**
|
|
35
|
+
1. **Brainstorm events (step 1).** Draw the candidate events **from the walkthroughs and
|
|
36
|
+
journeys** (ask for them first; no walkthrough → *"what does success look like, start to
|
|
37
|
+
finish?"*): *"what happened, in the story's words?"* — business terms, never table or endpoint
|
|
38
|
+
vocabulary. Ask the user to name everything that happens, as past-tense
|
|
35
39
|
facts. Probe for missed state changes. For each candidate, apply the **"is it an event?"
|
|
36
40
|
test** (`methodology.md` step 1 — the "would this wake the CEO at 3am" heuristic):
|
|
37
41
|
reject derived values (belong in a read model) and telemetry/activity (not business state).
|
|
38
42
|
Don't silently drop rejections — park them in the state file's Decisions log with why, so
|
|
39
43
|
they aren't re-proposed later. Capture the survivors as a flat list.
|
|
40
|
-
2. **Plot / storyboard (step 2).**
|
|
44
|
+
2. **Plot / storyboard (step 2).** Start from the story, not the schema: ask for the spec's
|
|
45
|
+
**walkthroughs**, use cases and user journeys — or *"what does success look like, start to
|
|
46
|
+
finish?"* — and build the timeline from those (`methodology.md`, "The story, not the schema").
|
|
47
|
+
Name the **main storyline** (the end-to-end happy path) and each **secondary storyline**
|
|
48
|
+
(retire, extend, reorganize) before ordering anything; never one lifecycle block per entity
|
|
49
|
+
(the CRUD shape). The OpenAPI/migrations/resource reference comes **after** the storyboard,
|
|
50
|
+
only to fill operations no walkthrough touches. Order the events into the narrative. Identify **personas**
|
|
41
51
|
(actors) and the **UI** screens at each step. Add `persona` declarations and `ui` elements.
|
|
42
52
|
In a **headless/API** model there are no human personas or screens — identify the external
|
|
43
53
|
**callers** at each step instead; declare one persona per caller/role, and its `ui` boxes are
|
|
44
54
|
the API calls (see `methodology.md`).
|
|
45
55
|
3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
|
|
46
|
-
Form `command → event` slices (State Change pattern).
|
|
56
|
+
Form `command → event` slices (State Change pattern). Apply the **intent test**: name the
|
|
57
|
+
command for the intent a business person would say at this step of the story, not the
|
|
58
|
+
endpoint that carries it (`methodology.md`, "The story, not the schema"). Every command needs a **trigger** in the
|
|
47
59
|
same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
|
|
48
60
|
and Translation patterns — the reaction that triggers it, also in this slice. A command
|
|
49
61
|
nothing points at is a write nobody can start. *"Who does this, and where are they when they
|
|
@@ -66,6 +78,29 @@ slice already reads "no doc yet" — no slice docs exist until the `slice` phase
|
|
|
66
78
|
Tell the user they can stop here and resume with `/em:event-modeling` (or invoke
|
|
67
79
|
`em:design` directly).
|
|
68
80
|
|
|
81
|
+
## Path: PRD or spec in (a document is the input)
|
|
82
|
+
|
|
83
|
+
Use this path when the user hands you a **PRD, requirements document or API spec** instead of
|
|
84
|
+
starting from a blank page. **Entry:** `/em:discover` (plugin) or `/em:event-modeling discover`
|
|
85
|
+
(repo skills), with the opening message **`Discover from this PRD/spec: <path>`**. **Read `reference/prd-spec-path.md`
|
|
86
|
+
and `reference/prd-rubric.md` before doing any work on it** — the path is a triage, not a
|
|
87
|
+
transcription: a document describes the system its authors imagined (resources, endpoints,
|
|
88
|
+
stack, quality targets), and a model copied from it comes out as CRUD-per-entity.
|
|
89
|
+
|
|
90
|
+
The steps, in order (detail in the reference): **0** pull the walkthroughs and name the main and
|
|
91
|
+
secondary **storylines** (the resource/endpoint reference is consulted last); **1** split into
|
|
92
|
+
single-claim statements, each quoting the source verbatim; **2** classify each against the
|
|
93
|
+
shipped rubric, plus the independent *"is this about the business, independent of how it's
|
|
94
|
+
built?"* check; **3** group by reaction (shared trigger + acting system = one slice; failure,
|
|
95
|
+
retry and timeout become outcome events and scenarios of that slice, never new slices); **4**
|
|
96
|
+
rewrite, don't drop (implementation details become business language linked to their source;
|
|
97
|
+
real tech choices and NFRs go to a constraints list that feeds the constitution); **5** record
|
|
98
|
+
`discovery/prd-triage.json` and run `em discover triage-check discovery/prd-triage.json`
|
|
99
|
+
(MCP: `discover_triage_check`) until it passes; **6** every low-confidence or disagreeing
|
|
100
|
+
statement becomes a Socratic question — never an invented answer. Rejections and parked
|
|
101
|
+
statements go in the state file's Decisions log with why, as in step 1. Then continue with
|
|
102
|
+
`discover` steps 1-4 on the surviving groups, with the storylines as the timeline's spine.
|
|
103
|
+
|
|
69
104
|
## Phase: `extract` — current-state model of an existing system
|
|
70
105
|
|
|
71
106
|
Goal: a faithful **current-state** model extracted from an existing system — the as-is sibling
|
|
@@ -77,7 +77,12 @@ it is fine to finish a round with `# TBD`s still parked.
|
|
|
77
77
|
|
|
78
78
|
- **R1 — Candidate events.** Extract (event-driven) or synthesize (procedural) the past-tense
|
|
79
79
|
facts; apply the event test; user confirms/renames/rejects. Ambiguous candidates → `# TBD`.
|
|
80
|
-
- **R2 — Timeline order.**
|
|
80
|
+
- **R2 — Timeline order.** First **name the as-is process(es)** — which business journey does the
|
|
81
|
+
system run today, and what is its outcome? Take it from the system's walkthroughs, user journeys
|
|
82
|
+
and docs, and from the people who use it — not from the OpenAPI/migrations/DTO grep, which fills
|
|
83
|
+
operations no journey touches and comes **after** (`methodology.md`, "The story, not the
|
|
84
|
+
schema"). Name the main storyline and each secondary one, then order the confirmed events into
|
|
85
|
+
that as-is narrative, never one block per entity. Identify the
|
|
81
86
|
*actual* actors/callers and the *real* UI or API surfaces (headless systems: still `ui`/`persona`
|
|
82
87
|
— one persona per caller/role, its `ui` boxes are the API calls, per `em-dsl.md`). **First
|
|
83
88
|
`em render` here.**
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!-- GENERATED FILE from .claude/skills/event-modeling-discover/reference/prd-rubric.md by scripts/build-plugin.ts — do not hand-edit; run npm run docs:generate -->
|
|
2
|
+
# PRD/spec triage rubric — version 1.0
|
|
3
|
+
|
|
4
|
+
The shipped classification rubric for the PRD/spec path (`prd-spec-path.md`, step 2). It is data:
|
|
5
|
+
`em discover triage-check` rejects a `discovery/prd-triage.json` whose `rubricVersion` is not the
|
|
6
|
+
version below. Do not edit it per project; a changed rubric ships with a new `rubricVersion` in a
|
|
7
|
+
new `em` release.
|
|
8
|
+
|
|
9
|
+
## The independent check
|
|
10
|
+
|
|
11
|
+
Run on every statement in addition to its category: **"Is this about the business, independent of
|
|
12
|
+
how it is built?"** Record the answer as `aboutBusiness` (true/false), answering it separately from
|
|
13
|
+
the category. The table's `aboutBusiness` column is what the category *expects*.
|
|
14
|
+
|
|
15
|
+
## Categories
|
|
16
|
+
|
|
17
|
+
| Category id | What it is | aboutBusiness expected | Allowed outcomes |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| `business-fact` | Something that happens or is true in the business; candidate event or field. | true | modeled, rewritten, parked, question |
|
|
20
|
+
| `user-intent` | What an actor wants to accomplish; candidate command with its trigger. | true | modeled, rewritten, parked, question |
|
|
21
|
+
| `information-need` | What an actor needs to see or know to act; candidate read model. | true | modeled, rewritten, parked, question |
|
|
22
|
+
| `actor` | A person, role or calling system; candidate persona. | true | modeled, parked, question |
|
|
23
|
+
| `business-rule` | A constraint the business imposes; candidate invariant or scenario. | true | modeled, rewritten, parked, question |
|
|
24
|
+
| `external-system` | A system outside the boundary; candidate translation slice or constraint. | either | modeled, rewritten, constraint, parked, question |
|
|
25
|
+
| `implementation-detail` | How it is built. Rewritten in business language when it implies a business fact; a real tech choice goes to the constraints list. | false | rewritten, constraint, parked, question |
|
|
26
|
+
| `nfr` | Performance, availability, security, compliance: feeds the constitution, never a slice. | either | constraint, parked, question |
|
|
27
|
+
| `goal-background` | Why the product exists; context for the main storyline, not a slice. | true | parked, rewritten, question |
|
|
28
|
+
| `question` | An open question in the source; becomes a Socratic question, never an invented answer. | either | question, parked |
|
|
29
|
+
| `resource-or-endpoint` | Describes an API resource or endpoint. Rewritten to the intent it serves (one endpoint may carry several); never promoted to a command as-is. | false | rewritten, parked, question |
|
|
30
|
+
|
|
31
|
+
## Outcomes
|
|
32
|
+
|
|
33
|
+
`modeled` (joins a slice group as stated), `rewritten` (joins a group in business language, the
|
|
34
|
+
original linked through `quote`), `constraint` (constraints list), `parked` (a `reason`; also a
|
|
35
|
+
Decisions-log entry), `question` (a Socratic `question` for the human). `question` and `parked`
|
|
36
|
+
are allowed for every category: a doubt can always be asked, and an explicitly deferred intent is
|
|
37
|
+
legitimate when its `reason` says so.
|
|
38
|
+
|
|
39
|
+
## Rules the checker enforces
|
|
40
|
+
|
|
41
|
+
All of these are checked by `em discover triage-check`; write the record so they hold.
|
|
42
|
+
|
|
43
|
+
1. **Disagreement is a question.** If `aboutBusiness` contradicts the category's expectation
|
|
44
|
+
(a `business-fact` that names a table; an `implementation-detail` that states a business
|
|
45
|
+
fact), the outcome MUST be `question`. Put the proposed rewrite in the question text
|
|
46
|
+
("Is this a business fact? proposed: Stock is decremented when a pick is confirmed."); the
|
|
47
|
+
human resolves it and a later pass records `rewritten`.
|
|
48
|
+
2. **Low confidence is a question.** `confidence: low` means outcome `question`, whatever the category.
|
|
49
|
+
3. **An endpoint is never modeled as-is.** `resource-or-endpoint` is `rewritten` (to the intent it
|
|
50
|
+
serves), `parked` or `question` — never `modeled`.
|
|
51
|
+
4. **Only modeled and rewritten statements join a slice group.** They carry a declared `group` and
|
|
52
|
+
the `storyline` of that group. Every other outcome has `group: null` (and `storyline: null`),
|
|
53
|
+
except that a `question` may name the group it would join once answered (with that group's
|
|
54
|
+
storyline); that is a pointer only, never an anchor (rule 7).
|
|
55
|
+
5. `rewritten` needs `rewrite`; `parked` needs `reason`; `question` needs `question`; every
|
|
56
|
+
`constraint` appears in `constraints[]`.
|
|
57
|
+
6. **Provenance is checked, not declared.** Every statement says whether it came from a
|
|
58
|
+
`walkthrough` or from the `reference` (resource/endpoint reference, operational notes). A
|
|
59
|
+
`walkthrough` statement's quote must lie inside some storyline's span; a `reference`
|
|
60
|
+
statement's quote must lie outside every storyline span.
|
|
61
|
+
7. **A group is either anchored or reference-only.** An anchored group names a storyline and has at
|
|
62
|
+
least one `modeled` or `rewritten` `walkthrough` statement whose quote lies inside that storyline's span (a `question` never anchors: a group anchored by questions only is an error). A **reference-only
|
|
63
|
+
group** has only `reference` statements and **no storyline**: `storyline: null` on the group and
|
|
64
|
+
on its statements. It never joins a walkthrough storyline. The report lists reference-only
|
|
65
|
+
groups outside every storyline — operations no walkthrough touches; check these are not CRUD fill.
|
|
66
|
+
8. **One statement per claim.** A statement's normalised quote may not equal, contain, be contained in, or partially overlap (by character range in the normalised source) another statement's quote; the error names both. Ranges are taken at a quote's first occurrence in the normalised source, so a sentence repeated in the PRD can hide or produce an overlap.
|
|
67
|
+
9. **Quotes are verbatim** (Markdown emphasis marks, smart quotes and whitespace aside) and at
|
|
68
|
+
least 12 characters. A storyline `quote` is the **full** walkthrough span, start to finish.
|
|
69
|
+
|
|
70
|
+
## Rubric decisions
|
|
71
|
+
|
|
72
|
+
- `nfr` expects `aboutBusiness: either`: a retention or audit rule is a business NFR, so the check
|
|
73
|
+
must not force a disagreement on it.
|
|
74
|
+
- `external-system` also stays `either`: a system outside the boundary can be named in business
|
|
75
|
+
terms (the payment provider) or technical ones (a queue broker).
|
|
76
|
+
|
|
77
|
+
The checker cannot judge whether a classification is right, nor whether every claim in the
|
|
78
|
+
walkthrough has a statement; that is what the human review is for.
|
|
79
|
+
|
|
80
|
+
## Machine-readable copy
|
|
81
|
+
|
|
82
|
+
The same data as JSON. The checker's built-in copy is asserted identical to this block, and the
|
|
83
|
+
table above is asserted to match it, by `test/prdTriage.test.ts`.
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"rubricVersion": "1.0",
|
|
88
|
+
"independentCheck": "Is this about the business, independent of how it is built?",
|
|
89
|
+
"categories": [
|
|
90
|
+
{ "id": "business-fact", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "Something that happens or is true in the business; candidate event or field." },
|
|
91
|
+
{ "id": "user-intent", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "What an actor wants to accomplish; candidate command with its trigger." },
|
|
92
|
+
{ "id": "information-need", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "What an actor needs to see or know to act; candidate read model." },
|
|
93
|
+
{ "id": "actor", "aboutBusiness": true, "outcomes": ["modeled", "parked", "question"], "meaning": "A person, role or calling system; candidate persona." },
|
|
94
|
+
{ "id": "business-rule", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "A constraint the business imposes; candidate invariant or scenario." },
|
|
95
|
+
{ "id": "external-system", "aboutBusiness": "either", "outcomes": ["modeled", "rewritten", "constraint", "parked", "question"], "meaning": "A system outside the boundary; candidate translation slice or constraint." },
|
|
96
|
+
{ "id": "implementation-detail", "aboutBusiness": false, "outcomes": ["rewritten", "constraint", "parked", "question"], "meaning": "How it is built. Rewritten in business language when it implies a business fact; a real tech choice goes to the constraints list." },
|
|
97
|
+
{ "id": "nfr", "aboutBusiness": "either", "outcomes": ["constraint", "parked", "question"], "meaning": "Performance, availability, security, compliance: feeds the constitution, never a slice." },
|
|
98
|
+
{ "id": "goal-background", "aboutBusiness": true, "outcomes": ["parked", "rewritten", "question"], "meaning": "Why the product exists; context for the main storyline, not a slice." },
|
|
99
|
+
{ "id": "question", "aboutBusiness": "either", "outcomes": ["question", "parked"], "meaning": "An open question in the source; becomes a Socratic question, never an invented answer." },
|
|
100
|
+
{ "id": "resource-or-endpoint", "aboutBusiness": false, "outcomes": ["rewritten", "parked", "question"], "meaning": "Describes an API resource or endpoint. Rewritten to the intent it serves (one endpoint may carry several); never promoted to a command as-is." }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|