@milehimikey/em 1.12.0 → 1.13.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.
Files changed (112) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +5 -2
  2. package/.claude/skills/event-modeling-conform/SKILL.md +20 -9
  3. package/.claude/skills/event-modeling-conform/reference/conform.md +107 -10
  4. package/.claude/skills/event-modeling-design/SKILL.md +26 -4
  5. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  6. package/.claude/skills/event-modeling-implement/SKILL.md +7 -4
  7. package/.claude/skills/event-modeling-implement/reference/implement.md +26 -8
  8. package/.claude/skills/event-modeling-review/SKILL.md +5 -1
  9. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +45 -6
  10. package/.claude/skills/event-modeling-shared/reference/operating-principles.md +13 -3
  11. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +39 -0
  12. package/.claude/skills/event-modeling-shared/templates/conformance-report.md +8 -1
  13. package/.claude/skills/event-modeling-shared/templates/state.md +3 -0
  14. package/README.md +1 -0
  15. package/dist/catalog/docJoin.d.ts +9 -0
  16. package/dist/catalog/docJoin.js +6 -0
  17. package/dist/catalog/docJoin.js.map +1 -1
  18. package/dist/catalog/driftSignal.d.ts +11 -5
  19. package/dist/catalog/driftSignal.js +18 -4
  20. package/dist/catalog/driftSignal.js.map +1 -1
  21. package/dist/catalog/modelVersionValidate.d.ts +9 -0
  22. package/dist/catalog/modelVersionValidate.js +38 -0
  23. package/dist/catalog/modelVersionValidate.js.map +1 -0
  24. package/dist/catalog/sliceDoc.d.ts +12 -0
  25. package/dist/catalog/sliceDoc.js +15 -0
  26. package/dist/catalog/sliceDoc.js.map +1 -1
  27. package/dist/cli/ciInit.d.ts +24 -0
  28. package/dist/cli/ciInit.js +85 -15
  29. package/dist/cli/ciInit.js.map +1 -1
  30. package/dist/cli/conformScope.d.ts +1 -0
  31. package/dist/cli/conformScope.js +3 -1
  32. package/dist/cli/conformScope.js.map +1 -1
  33. package/dist/cli/conformSupersede.d.ts +24 -2
  34. package/dist/cli/conformSupersede.js +44 -3
  35. package/dist/cli/conformSupersede.js.map +1 -1
  36. package/dist/cli/coverage.d.ts +16 -2
  37. package/dist/cli/coverage.js +26 -4
  38. package/dist/cli/coverage.js.map +1 -1
  39. package/dist/cli/findings.d.ts +134 -0
  40. package/dist/cli/findings.js +355 -0
  41. package/dist/cli/findings.js.map +1 -0
  42. package/dist/cli/metrics.d.ts +93 -0
  43. package/dist/cli/metrics.js +331 -0
  44. package/dist/cli/metrics.js.map +1 -0
  45. package/dist/cli/modelVersion.d.ts +139 -0
  46. package/dist/cli/modelVersion.js +256 -0
  47. package/dist/cli/modelVersion.js.map +1 -0
  48. package/dist/cli/reratify.d.ts +24 -1
  49. package/dist/cli/reratify.js +73 -17
  50. package/dist/cli/reratify.js.map +1 -1
  51. package/dist/cli/skillCheck.js +2 -2
  52. package/dist/cli/skillCheck.js.map +1 -1
  53. package/dist/cli/sliceConform.d.ts +50 -0
  54. package/dist/cli/sliceConform.js +206 -0
  55. package/dist/cli/sliceConform.js.map +1 -0
  56. package/dist/cli/sliceIndex.d.ts +2 -0
  57. package/dist/cli/sliceIndex.js +15 -3
  58. package/dist/cli/sliceIndex.js.map +1 -1
  59. package/dist/cli/sliceNew.d.ts +12 -0
  60. package/dist/cli/sliceNew.js +23 -0
  61. package/dist/cli/sliceNew.js.map +1 -1
  62. package/dist/cli/sliceStubAll.d.ts +76 -0
  63. package/dist/cli/sliceStubAll.js +189 -0
  64. package/dist/cli/sliceStubAll.js.map +1 -0
  65. package/dist/cli/stateFile.d.ts +63 -7
  66. package/dist/cli/stateFile.js +179 -18
  67. package/dist/cli/stateFile.js.map +1 -1
  68. package/dist/cli/status.d.ts +79 -1
  69. package/dist/cli/status.js +134 -15
  70. package/dist/cli/status.js.map +1 -1
  71. package/dist/cli/upgrade.d.ts +132 -0
  72. package/dist/cli/upgrade.js +524 -0
  73. package/dist/cli/upgrade.js.map +1 -0
  74. package/dist/cli.js +609 -50
  75. package/dist/cli.js.map +1 -1
  76. package/dist/emit/coverageJson.d.ts +5 -2
  77. package/dist/emit/coverageJson.js +6 -2
  78. package/dist/emit/coverageJson.js.map +1 -1
  79. package/dist/emit/freshnessJson.d.ts +1 -1
  80. package/dist/emit/freshnessJson.js +3 -1
  81. package/dist/emit/freshnessJson.js.map +1 -1
  82. package/dist/emit/json.d.ts +11 -1
  83. package/dist/emit/json.js +35 -1
  84. package/dist/emit/json.js.map +1 -1
  85. package/dist/emit/metricsJson.d.ts +7 -0
  86. package/dist/emit/metricsJson.js +27 -0
  87. package/dist/emit/metricsJson.js.map +1 -0
  88. package/dist/emit/statusJson.d.ts +1 -1
  89. package/dist/emit/statusJson.js +17 -1
  90. package/dist/emit/statusJson.js.map +1 -1
  91. package/dist/emit/upgradeJson.d.ts +5 -0
  92. package/dist/emit/upgradeJson.js +29 -0
  93. package/dist/emit/upgradeJson.js.map +1 -0
  94. package/dist/emit/validateJson.d.ts +6 -2
  95. package/dist/emit/validateJson.js +12 -5
  96. package/dist/emit/validateJson.js.map +1 -1
  97. package/dist/mcp/server.d.ts +1 -1
  98. package/dist/mcp/server.js +137 -9
  99. package/dist/mcp/server.js.map +1 -1
  100. package/dist/model/rules.d.ts +7 -0
  101. package/dist/model/rules.js +12 -0
  102. package/dist/model/rules.js.map +1 -1
  103. package/dist/model/validate.d.ts +20 -0
  104. package/dist/model/validate.js +94 -45
  105. package/dist/model/validate.js.map +1 -1
  106. package/dist/templates.d.ts +2 -2
  107. package/dist/templates.js +7 -1
  108. package/dist/templates.js.map +1 -1
  109. package/dist/util/semver.d.ts +19 -0
  110. package/dist/util/semver.js +54 -0
  111. package/dist/util/semver.js.map +1 -0
  112. package/package.json +2 -2
@@ -69,6 +69,9 @@ to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of i
69
69
  | `ratifiedOn` | string | `YYYY-MM-DD` | written only by `em slice ratify` (MIL-165); joined into `em export`'s `slice.doc.ratifiedOn` (schema `1.8`) |
70
70
  | `owner` | string | free text (typically a person or team name) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.owner` (schema `1.9`, MIL-171), `em slice index`'s Owner column, and `em status`'s per-slice `owners[]` |
71
71
  | `tracking` | string | free text (typically an external ticket/issue URL) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.tracking` (schema `1.9`, MIL-171) and `em slice index`'s Tracking column. This is the exact field `em-tracker-bridge` reads to find the ticket mirroring this slice — `em` only stores and displays it, it never talks to a tracker itself |
72
+ | `conformedVersion` | integer | positive integer | written only by `em slice conform` (MIL-214 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-conform-file-slice-key---at-rev)): this slice's `version:` at the time a conform sweep last certified it. Joined into `em export`'s `slice.doc.conformedVersion` (schema `1.13`); paired with `version` to compute `slice.doc.driftSignal`'s `"uncertified"` case |
73
+ | `conformedAt` | string | free text (a target-repo revision, NOT a `<slice-key>@v<N>` lineage ref) | written only by `em slice conform` (MIL-214): the revision that certification sweep diffed against. Joined into `em export`'s `slice.doc.conformedAt` (schema `1.13`) and `em slice index`'s Conformed column |
74
+ | `conformedOn` | string | `YYYY-MM-DD` | written only by `em slice conform` (MIL-214). Joined into `em export`'s `slice.doc.conformedOn` (schema `1.13`) |
72
75
 
73
76
  Five keys — `schemaVersion`/`pattern`/`swimlane`/`status`/`version` — are what
74
77
  `hasUsableFrontmatter()` requires: a doc omitting any of them is `frontmatter-invalid` to
@@ -89,6 +92,7 @@ convention, not parser-enforced.
89
92
  | `reviewedBy`, `reviewedOn` | optional in every state — present once `em slice review` (MIL-201) has run at least once; a doc that never went through the review gate, or was hand-flipped to `reviewed`, simply omits both. `em slice ratify` never clears them; `em slice reratify` does (they describe the version that shipped) |
90
93
  | `ratifiedBy`, `ratifiedOn` | optional in every state — present once `em slice ratify` (MIL-165) has run at least once; a doc predating this feature, or ratified by hand, simply omits both |
91
94
  | `owner`, `tracking` | optional in every state — hand-filled whenever a team wants a who-holds-this / external-tracker link; most docs never carry either (MIL-171) |
95
+ | `conformedVersion`, `conformedAt`, `conformedOn` | optional in every state — present once `em slice conform` (MIL-214) has run at least once for the CURRENT version; a doc that's never been through a conform sweep, or whose `version` has since bumped past what was certified, simply has none of the three (or a stale triple — see `driftSignal`'s `uncertified` case below) |
92
96
 
93
97
  `em export`'s slice-doc join (MIL-91) is the first `em` command to mechanically check the
94
98
  required row above: a bound doc missing any of `schemaVersion`/`pattern`/`swimlane`/`status`/
@@ -101,6 +105,32 @@ No `em` command fails a build over this table. `em validate`'s frontmatter-coher
101
105
  (never fails) on the one combination checkable without git history: `status: implemented` with
102
106
  no `implementedIn` link at all.
103
107
 
108
+ ## Stub docs
109
+
110
+ `em slice new --stub` and `em slice stub-all` (MIL-184, ruled 2026-09-08 on GH #128 — see that
111
+ issue for the fuller design rationale) write a **stub**: the exact same 5 required-at-`status:
112
+ draft` frontmatter keys above, but a one-line placeholder body (`_Stub — deepen with the slice
113
+ phase (see slice-doc-schema.md)._`) instead of the diagram-image stub and every judgment section
114
+ (Intent, Command, Scenarios, Open Questions, …) a full slice doc eventually carries.
115
+
116
+ A stub is not a lesser doc, frontmatter-wise — it's a real doc with a real `status`, parsed and
117
+ joined by `em export`/`em catalog`/`em status`/`em slice index` exactly like a fully-written one,
118
+ because it's the same frontmatter dialect. The doc stays the single source of truth for
119
+ `status`; a stub is just the cheapest possible way to give a slice one before anyone's ready to
120
+ write the real spec.
121
+
122
+ **A stub CAN pass `em validate --slice-ready`.** That gate checks four things (`computeSliceReadyGates`,
123
+ [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)): a doc is bound, its frontmatter
124
+ is usable, `status` is `ready-to-implement`, and its Open Questions count has none unchecked. A
125
+ stub with no `## Open Questions` section at all has `openQuestionsTotal: 0` (see
126
+ [Open Questions section: lifecycle](#open-questions-section-lifecycle) below) — zero unchecked is
127
+ vacuously true — so a stub escalated to `ready-to-implement` (`em slice stub-all --status
128
+ ready-to-implement --by <name>`) passes cleanly. The readiness gate is frontmatter-plus-checkbox-
129
+ count only; it has no opinion about whether Intent/Command/Scenarios/Invariants were ever
130
+ written. Demanding that content is the `slice` phase's (and, for a live walkthrough, the review
131
+ skill's) job, not a machine gate's — see `../.claude/skills/event-modeling-design/SKILL.md` and
132
+ `../.claude/skills/event-modeling-review/SKILL.md`.
133
+
104
134
  ## Lineage: grammar and cardinality
105
135
 
106
136
  Grammar: `<slice-key>@v<N>` — `<slice-key>` is the referenced slice's kebab-case filename stem
@@ -368,6 +398,15 @@ frontmatter-coherence check (MIL-85) deliberately never flags this combination
368
398
  `status: implemented` with no `implementedIn` link at all is checkable incoherence; see
369
399
  [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence).
370
400
 
401
+ **An unshipped version has nothing to flip back** (MIL-258). A ratified doc that has not shipped
402
+ yet (`status: ready-to-implement` with `ratifiedBy:` set) changes the same way: `em slice
403
+ reratify` bumps `version:` and clears `ratifiedBy`/`ratifiedOn`/`reviewedBy`/`reviewedOn`, but
404
+ leaves `status` at `ready-to-implement` (and any `implementedIn` as it was — an absent one stays
405
+ absent). This is the path for an answered gap mid-build. In both cases the new version is not
406
+ ratified until `em slice ratify --by <name>` records the sign-off. A `ready-to-implement` doc with
407
+ no `ratifiedBy` is awaiting that sign-off, so a second `reratify` refuses rather than
408
+ double-bump; `draft`/`reviewed` docs are not ratified and are simply edited.
409
+
371
410
  Pair a re-ratification with a `## Delta` section (see
372
411
  [Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle) above) recording
373
412
  the ratified change in typed operation blocks, so it's reviewable without opening git.
@@ -31,7 +31,14 @@ Replace bracketed placeholders; delete guidance comments before finishing.
31
31
  ## Findings
32
32
 
33
33
  <!-- One subsection per finding. Group however reads clearest (by slice, or by surface) but keep
34
- every field below for every finding. Order doesn't matter; classification does. -->
34
+ every field below for every finding. Order doesn't matter; classification does.
35
+
36
+ MIL-214: this section's structured twin is `conformance/{{YYYY-MM-DD}}-findings.json`,
37
+ written ALONGSIDE this report (not after it) — one JSON entry per `### n.` finding below,
38
+ `id` matching `{{n}}`. See reference/conform.md step 5 for the exact shape and the write-then-
39
+ `em conform-findings check`-it discipline. A later `em conform-supersede --locus --by` call
40
+ records the ruling on both the JSON (locus/resolvedBy/resolvedOn) and this report's banner
41
+ in one call. -->
35
42
 
36
43
  ### {{n}}. {{Short finding title}}
37
44
 
@@ -7,10 +7,13 @@ Keep it current at the end of every working session.
7
7
  # Event Modeling Progress — {{Model Name}}
8
8
 
9
9
  - **Model file:** `{{model-name}}.em`
10
+ - **Em version:** {{X.Y.Z}}
10
11
  - **Current phase:** {{discover | extract | model | slice | implement | conform | review | validate}}
11
12
  - **Current step:** {{1–7, see methodology; or extraction round R1–R7}}
12
13
  - **Last updated:** {{YYYY-MM-DD}}
13
14
  - **Last conformance:** {{YYYY-MM-DD @ <target-repo revision> — report: conformance/<date>-report.md | never}}
15
+ - **Model version:** {{N | none}}
16
+ - **Certified:** {{v<N> @ <target-repo revision> (YYYY-MM-DD) | never}}
14
17
  - **Last stakeholder review:** {{YYYY-MM-DD — attendees: see Participants | never}}
15
18
 
16
19
  ## Session inputs
package/README.md CHANGED
@@ -118,6 +118,7 @@ built this way.
118
118
  | [docs/cli.md](docs/cli.md) | Every command and flag |
119
119
  | [docs/validation.md](docs/validation.md) | Every rule `em validate` checks, and the fixes |
120
120
  | [docs/ci.md](docs/ci.md) | Copy-paste CI recipes: validate `.em` changes, run conformance on a schedule |
121
+ | [docs/upgrading.md](docs/upgrading.md) | Bring a model repo forward across em releases: `em upgrade`, release by release |
121
122
  | [docs/timeline.md](docs/timeline.md) | The Two Laws of the Timeline |
122
123
  | [docs/ai-workflow.md](docs/ai-workflow.md) | The Claude Code skill: install, phases, artifacts |
123
124
  | [docs/dependencies.md](docs/dependencies.md) | What's bundled vs. what needs a system install |
@@ -73,6 +73,15 @@ export interface SliceDocExport {
73
73
  * em-tracker-bridge reads to find the mirrored ticket — its name/shape here is a cross-tool
74
74
  * contract, not just an internal display field. */
75
75
  tracking: string | null;
76
+ /** MIL-214: this slice's `version:` at the time a conform sweep last certified it (frontmatter
77
+ * `conformedVersion:`), written only by `em slice conform` — null when never certified. */
78
+ conformedVersion: number | null;
79
+ /** MIL-214: the target repo's revision that certification sweep diffed against (frontmatter
80
+ * `conformedAt:`), written only by `em slice conform` — null when never certified. */
81
+ conformedAt: string | null;
82
+ /** MIL-214: the local date that certification was recorded (frontmatter `conformedOn:`),
83
+ * written only by `em slice conform` — null when never certified. */
84
+ conformedOn: string | null;
76
85
  }
77
86
  export interface SliceDocJoinResult {
78
87
  doc: SliceDocExport;
@@ -73,6 +73,9 @@ const EMPTY_CONTENT = {
73
73
  ratifiedOn: null,
74
74
  owner: null,
75
75
  tracking: null,
76
+ conformedVersion: null,
77
+ conformedAt: null,
78
+ conformedOn: null,
76
79
  };
77
80
  /**
78
81
  * Resolve one slice's doc join. `baseDir` is the `.em` file's directory (doc paths, like note
@@ -184,6 +187,9 @@ function foundDoc(path, parsed) {
184
187
  ratifiedOn: parsed.ratifiedOn,
185
188
  owner: parsed.owner,
186
189
  tracking: parsed.tracking,
190
+ conformedVersion: parsed.conformedVersion,
191
+ conformedAt: parsed.conformedAt,
192
+ conformedOn: parsed.conformedOn,
187
193
  };
188
194
  }
189
195
  //# sourceMappingURL=docJoin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"docJoin.js","sourceRoot":"","sources":["../../src/catalog/docJoin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"docJoin.js","sourceRoot":"","sources":["../../src/catalog/docJoin.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { SliceDoc } from "./sliceDoc.js";
2
2
  export type DriftSignalKind =
3
- /** status: implemented, implementedIn set — normal shipped state. */
3
+ /** status: implemented, implementedIn set, conformedVersion === version — certified current. */
4
4
  "in-sync"
5
5
  /** status not implemented, implementedIn absent — normal pre-ship state. */
6
6
  | "never-implemented"
@@ -8,9 +8,15 @@ export type DriftSignalKind =
8
8
  * Expected, not a defect: never surface this as fresh drift or a validate diagnostic. */
9
9
  | "unpropagated-delta"
10
10
  /** status: implemented, implementedIn absent — genuine incoherence (em validate warns). */
11
- | "implemented-without-link";
11
+ | "implemented-without-link"
12
+ /** status: implemented, implementedIn set, but conformedVersion is absent or doesn't match the
13
+ * current version — nobody has certified THIS version yet. Expected post-ship default, not a
14
+ * defect: never surface this as an `em validate` diagnostic (see
15
+ * catalog/frontmatterCoherenceValidate.ts, which only ever flags implemented-without-link). */
16
+ | "uncertified";
12
17
  /**
13
- * Classify a doc's status/implementedIn pair. Takes a `Pick` rather than the full `SliceDoc` so
14
- * callers building a partial/synthetic doc (tests, future callers) don't need every field.
18
+ * Classify a doc's status/implementedIn/version/conformedVersion quadruple. Takes a `Pick`
19
+ * rather than the full `SliceDoc` so callers building a partial/synthetic doc (tests, future
20
+ * callers) don't need every field.
15
21
  */
16
- export declare function classifyImplementationDrift(doc: Pick<SliceDoc, "status" | "implementedIn">): DriftSignalKind;
22
+ export declare function classifyImplementationDrift(doc: Pick<SliceDoc, "status" | "implementedIn" | "version" | "conformedVersion">): DriftSignalKind;
@@ -11,14 +11,28 @@
11
11
  // naming the prior version's PR. That combination — `unpropagated-delta` — is the EXPECTED drift
12
12
  // signal, not staleness, and must never be flagged as incoherence. Only `implemented-without-link`
13
13
  // (status: implemented, no link at all) is a genuine coherence problem worth a diagnostic.
14
+ //
15
+ // MIL-214: certification is per slice PER VERSION, not per model — `implementedIn` only ever
16
+ // said "code exists somewhere for this slice," never "the code that's there was actually walked
17
+ // against version N of this doc." `conformedVersion` (frontmatter, written only by
18
+ // `em slice conform` — cli/sliceConform.ts) records the version a conform run last certified.
19
+ // `uncertified` is the new, EXPECTED post-ship default: a slice reaches `status: implemented`
20
+ // long before anyone runs a conform sweep against it, and every version bump (`em slice
21
+ // reratify`) invalidates the prior certification even though `status`/`implementedIn` still read
22
+ // "shipped" — so this is not folded into `in-sync` and not flagged by `em validate` (same
23
+ // "expected, not a defect" treatment `unpropagated-delta` gets).
14
24
  /**
15
- * Classify a doc's status/implementedIn pair. Takes a `Pick` rather than the full `SliceDoc` so
16
- * callers building a partial/synthetic doc (tests, future callers) don't need every field.
25
+ * Classify a doc's status/implementedIn/version/conformedVersion quadruple. Takes a `Pick`
26
+ * rather than the full `SliceDoc` so callers building a partial/synthetic doc (tests, future
27
+ * callers) don't need every field.
17
28
  */
18
29
  export function classifyImplementationDrift(doc) {
19
30
  const hasLink = typeof doc.implementedIn === "string" && doc.implementedIn.trim().length > 0;
20
- if (doc.status === "implemented")
21
- return hasLink ? "in-sync" : "implemented-without-link";
31
+ if (doc.status === "implemented") {
32
+ if (!hasLink)
33
+ return "implemented-without-link";
34
+ return doc.conformedVersion !== null && doc.conformedVersion === doc.version ? "in-sync" : "uncertified";
35
+ }
22
36
  return hasLink ? "unpropagated-delta" : "never-implemented";
23
37
  }
24
38
  //# sourceMappingURL=driftSignal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"driftSignal.js","sourceRoot":"","sources":["../../src/catalog/driftSignal.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,4FAA4F;AAC5F,uFAAuF;AACvF,2FAA2F;AAC3F,iGAAiG;AACjG,yFAAyF;AACzF,kCAAkC;AAClC,EAAE;AACF,6FAA6F;AAC7F,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,2FAA2F;AAe3F;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAA+C;IACzF,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7F,IAAI,GAAG,CAAC,MAAM,KAAK,aAAa;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC;IAC1F,OAAO,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,mBAAmB,CAAC;AAC9D,CAAC"}
1
+ {"version":3,"file":"driftSignal.js","sourceRoot":"","sources":["../../src/catalog/driftSignal.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,4FAA4F;AAC5F,uFAAuF;AACvF,2FAA2F;AAC3F,iGAAiG;AACjG,yFAAyF;AACzF,kCAAkC;AAClC,EAAE;AACF,6FAA6F;AAC7F,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,2FAA2F;AAC3F,EAAE;AACF,6FAA6F;AAC7F,gGAAgG;AAChG,mFAAmF;AACnF,8FAA8F;AAC9F,8FAA8F;AAC9F,wFAAwF;AACxF,iGAAiG;AACjG,0FAA0F;AAC1F,iEAAiE;AAoBjE;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,GAAgF;IAEhF,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7F,IAAI,GAAG,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO;YAAE,OAAO,0BAA0B,CAAC;QAChD,OAAO,GAAG,CAAC,gBAAgB,KAAK,IAAI,IAAI,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;IAC3G,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,mBAAmB,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ import { Diagnostic } from "../model/validate.js";
4
+ /**
5
+ * `baseDir` is the `.em` file's directory (manifests live at `<baseDir>/model-versions/`, same
6
+ * convention every other doc/note/manifest path in `em` uses); `source` is the `.em` file's own
7
+ * text, for the modelHash comparison.
8
+ */
9
+ export declare function validateModelVersionStale(model: NormalizedModel, refs: RefsResult, baseDir: string, source: string): Diagnostic[];
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em validate`'s `model-version-stale` rule (MIL-218): warns when the `.em` content hash or
3
+ // the slice-version vector has moved since the last `em model version bump` — the same
4
+ // `modelVersionDrift` predicate `em status` and the `em slice ratify`/`reratify` advisory
5
+ // share, never re-derived a third way. Same fs-aware-sibling shape as
6
+ // catalog/frontmatterCoherenceValidate.ts/orphanedSliceDocValidate.ts: a module next to
7
+ // model/validate.ts, not folded into it, because it needs `baseDir`/fs access (the
8
+ // `model-versions/*.json` manifest) the rest of validate deliberately never touches.
9
+ //
10
+ // Silent when no manifest exists at all (`drift.current === null`) — a repo that never bumped a
11
+ // design version has nothing to be stale against; nagging a project that has never opted into
12
+ // this feature would be exactly the "surprise new warning on every model" failure mode the
13
+ // ruling's "silent when no manifest exists" clause exists to avoid. Warning, never error:
14
+ // nothing about a stale design version blocks anything else in the model.
15
+ import { pushDiag } from "../model/rules.js";
16
+ import { modelVersionDrift } from "../cli/modelVersion.js";
17
+ /**
18
+ * `baseDir` is the `.em` file's directory (manifests live at `<baseDir>/model-versions/`, same
19
+ * convention every other doc/note/manifest path in `em` uses); `source` is the `.em` file's own
20
+ * text, for the modelHash comparison.
21
+ */
22
+ export function validateModelVersionStale(model, refs, baseDir, source) {
23
+ const drift = modelVersionDrift(baseDir, model, refs, source);
24
+ if (drift.current === null || (!drift.hashChanged && drift.slicesChanged.length === 0))
25
+ return [];
26
+ const parts = [];
27
+ if (drift.hashChanged)
28
+ parts.push("the model's own content changed");
29
+ if (drift.slicesChanged.length > 0) {
30
+ parts.push(`${drift.slicesChanged.length} slice(s) moved (${drift.slicesChanged.map((c) => c.key).join(", ")})`);
31
+ }
32
+ const diags = [];
33
+ pushDiag(diags, "model-version-stale", {
34
+ message: `model version v${drift.current} is stale — ${parts.join(" and ")} since it was bumped; run \`em model version bump\` to record it`,
35
+ });
36
+ return diags;
37
+ }
38
+ //# sourceMappingURL=modelVersionValidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelVersionValidate.js","sourceRoot":"","sources":["../../src/catalog/modelVersionValidate.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,uFAAuF;AACvF,0FAA0F;AAC1F,sEAAsE;AACtE,wFAAwF;AACxF,mFAAmF;AACnF,qFAAqF;AACrF,EAAE;AACF,gGAAgG;AAChG,8FAA8F;AAC9F,2FAA2F;AAC3F,0FAA0F;AAC1F,0EAA0E;AAK1E,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAsB,EAAE,IAAgB,EAAE,OAAe,EAAE,MAAc;IACjH,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAElG,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,oBAAoB,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnH,CAAC;IACD,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,qBAAqB,EAAE;QACrC,OAAO,EAAE,kBAAkB,KAAK,CAAC,OAAO,eAAe,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,kEAAkE;KAC7I,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -82,6 +82,18 @@ export interface SliceDoc {
82
82
  * to find the ticket mirroring this slice — its export name/shape (docJoin.ts) is a
83
83
  * cross-tool contract, not just an internal display field. */
84
84
  tracking: string | null;
85
+ /** MIL-214: `conformedVersion:` — this slice's `version:` at the time a conform sweep last
86
+ * certified it, or null when never certified. Written only by `em slice conform`. Paired with
87
+ * `version` to compute `driftSignal`'s `uncertified` case (catalog/driftSignal.ts). */
88
+ conformedVersion: number | null;
89
+ /** MIL-214: `conformedAt:` — the target repo's revision that certification sweep diffed
90
+ * against, or null when absent. Written only by `em slice conform`; a plain revision string,
91
+ * NOT a `<slice-key>@v<N>` lineage ref. */
92
+ conformedAt: string | null;
93
+ /** MIL-214: `conformedOn:` — a `YYYY-MM-DD` date string, or null when absent. Written only by
94
+ * `em slice conform`; validated by the CLI layer, not re-validated here (this parser stays as
95
+ * lenient about value shape as every other frontmatter field). */
96
+ conformedOn: string | null;
85
97
  /** True when a well-formed leading `---`/`---` frontmatter fence was found and
86
98
  * closed — independent of which keys it contained. False for a legacy
87
99
  * status-bullet-only doc, a doc with no frontmatter at all, or an
@@ -47,6 +47,18 @@
47
47
  // particular is the field em-tracker-bridge reads to find the mirrored ticket, so its export
48
48
  // name/shape is a cross-tool contract — see docJoin.ts/emit/json.ts. Additive,
49
49
  // tolerate-unknown-fields: absent on every doc predating this feature.
50
+ //
51
+ // `conformedVersion`/`conformedAt`/`conformedOn` (MIL-214) are a ninth, tenth, and eleventh
52
+ // optional frontmatter-only field: per-slice-per-version certification, written only by
53
+ // `em slice conform` (cli/sliceConform.ts) — same "one write path" discipline `implementedIn`
54
+ // has with `em slice mark-implemented`. `conformedVersion` is this slice's own `version:` at the
55
+ // time a conform sweep last certified it (a cache of git truth, not derived/validated against
56
+ // git history — same convention `version` itself already uses); `conformedAt` is the TARGET
57
+ // REPO'S revision that sweep diffed against (a plain revision string, not a slice-doc lineage
58
+ // ref — deliberately NOT `<slice-key>@v<N>` shaped); `conformedOn` is the local date the
59
+ // certification was recorded. All three additive, tolerate-unknown-fields: absent on every doc
60
+ // predating this feature, or one never yet conformed. See catalog/driftSignal.ts's `uncertified`
61
+ // classification, which compares `conformedVersion` against `version`.
50
62
  import { marked } from "marked";
51
63
  /** Frontmatter keys docs/slice-doc-schema.md's required-vs-optional table marks
52
64
  * required at every `status` — the mechanical basis for `frontmatter-invalid`
@@ -202,6 +214,9 @@ export function parseSliceDoc(markdown) {
202
214
  ratifiedOn: fields.get("ratifiedon") ?? null,
203
215
  owner: fields.get("owner") ?? null,
204
216
  tracking: fields.get("tracking") ?? null,
217
+ conformedVersion: parseVersion(fields.get("conformedversion")),
218
+ conformedAt: fields.get("conformedat") ?? null,
219
+ conformedOn: fields.get("conformedon") ?? null,
205
220
  frontmatterPresent,
206
221
  missingRequiredFields: REQUIRED_FRONTMATTER_KEYS.filter((k) => !fields.has(k)),
207
222
  html: marked.parse(body, { async: false }),
@@ -1 +1 @@
1
- {"version":3,"file":"sliceDoc.js","sourceRoot":"","sources":["../../src/catalog/sliceDoc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sliceDoc.js","sourceRoot":"","sources":["../../src/catalog/sliceDoc.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,25 @@ export type CiFileStatus = {
34
34
  } | {
35
35
  kind: "would-replace";
36
36
  content: string;
37
+ } | {
38
+ kind: "other-model";
39
+ previous: string;
40
+ } | {
41
+ kind: "replace-model";
42
+ content: string;
43
+ previous: string;
37
44
  };
45
+ /**
46
+ * Which model an existing managed block was generated for, read from the block's own content
47
+ * (MIL-256). Deliberately content-derived rather than a new marker line: blocks written by em
48
+ * 1.13.0 and earlier carry no machine-readable model, but every one of them bakes the model into
49
+ * the per-model `run:` lines (em-ci.yml: `slice index "<model>" --check`; em-conform.yml:
50
+ * `MODEL_DIR: <dir>`). Neither pattern includes the `npx @milehimikey/em@<ver>` pin, so a
51
+ * version difference can never read as a different model. Returns null when the block was
52
+ * hand-edited past recognition - callers then treat it as the same model (the conservative,
53
+ * pre-MIL-256 behavior) rather than refusing on a guess.
54
+ */
55
+ export declare function managedBlockModel(markerName: string, blockBody: string): string | null;
38
56
  /**
39
57
  * Decide what to do with one generated file: create it if missing, patch the managed block if
40
58
  * the marker pair is present, or (without `force`) leave an existing unmarked file alone. Pure
@@ -44,6 +62,12 @@ export type CiFileStatus = {
44
62
  * marker-wrapped body) — written verbatim for `create`/`would-replace`. `managedBody` is just
45
63
  * the text between the markers, reused to patch an existing marked file in place without
46
64
  * touching whatever a repo added around it.
65
+ *
66
+ * MIL-256 (#174): the managed block is single-model (multi-model is MIL-233). A marked file
67
+ * whose block was generated for a DIFFERENT model than `managedBody` is `other-model` (refused)
68
+ * unless `force`, then `replace-model`. The same model with any other difference (version pin,
69
+ * old text) stays `stale`, exactly as before. `em upgrade`'s ci-block step re-derives the model
70
+ * from the existing file itself, so it only ever plans the same model.
47
71
  */
48
72
  export declare function planCiFile(path: string, generated: string, managedBody: string, markerName: string, force: boolean): CiFileStatus;
49
73
  export declare function applyCiFile(path: string, status: CiFileStatus): void;
@@ -25,7 +25,7 @@
25
25
  // check` as its own gate instead of silently overwriting").
26
26
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
27
27
  import { dirname, join } from "node:path";
28
- import { applyMarker, markerPair } from "../util/markers.js";
28
+ import { applyMarker, markerPair, markerRegex } from "../util/markers.js";
29
29
  export const CI_WORKFLOW_MARKER = "em-ci";
30
30
  export const CONFORM_WORKFLOW_MARKER = "em-conform";
31
31
  const CI_WORKFLOW_RELPATH = join(".github", "workflows", "em-ci.yml");
@@ -107,7 +107,7 @@ export function ciManagedBody(model, testsDir, emVersion) {
107
107
  with:
108
108
  node-version: 20
109
109
  - name: Check invariant test coverage
110
- # Counts INV-* IDs in \`implemented\` slice docs only (MIL-207) — a ready-to-implement
110
+ # Counts INV-* IDs in \`implemented\` slice docs only (MIL-207) - a ready-to-implement
111
111
  # doc has nothing yet to cite it, so this job is green on a fresh scaffold and on a
112
112
  # doc-only ratification PR. Pass --include-ready for the older, forward-looking report.
113
113
  run: ${em} coverage "${model}" --tests "${testsDir}" --strict
@@ -140,9 +140,26 @@ export function ciManagedBody(model, testsDir, emVersion) {
140
140
  if [ -d .claude/skills/event-modeling ]; then
141
141
  ${em} skill check
142
142
  else
143
- echo "no vendored skill at .claude/skills/event-modeling — skipping (run \`em skill install\` to opt in)"
143
+ echo "no vendored skill at .claude/skills/event-modeling - skipping (run em skill install to opt in)"
144
144
  fi
145
145
 
146
+ upgrade-check:
147
+ name: "em upgrade --check (advisory - hard incompatibilities only, MIL-219)"
148
+ if: github.event_name == 'pull_request'
149
+ runs-on: ubuntu-latest
150
+ steps:
151
+ - uses: actions/checkout@v4
152
+ with:
153
+ fetch-depth: 0
154
+ - uses: actions/setup-node@v4
155
+ with:
156
+ node-version: 20
157
+ - name: Check for hard upgrade incompatibilities
158
+ # Advisory: exits non-zero ONLY on a hard incompatibility (an unparseable state file, an
159
+ # old .em shape em migrate itself refuses) - never on the ordinary "some mechanical
160
+ # steps are applicable" case. See docs/upgrading.md.
161
+ run: ${em} upgrade "${model}" --check
162
+
146
163
  glossary:
147
164
  name: "em glossary --fail-on-conflicts (cross-model vocabulary)"
148
165
  if: github.event_name == 'pull_request'
@@ -153,10 +170,12 @@ export function ciManagedBody(model, testsDir, emVersion) {
153
170
  with:
154
171
  node-version: 20
155
172
  - name: Check glossary consistency across models
156
- run: ${em} glossary $(git ls-files '*.em') --fail-on-conflicts
173
+ run: |
174
+ mapfile -t models < <(git ls-files '*.em')
175
+ ${em} glossary "\${models[@]}" --fail-on-conflicts
157
176
 
158
177
  status-badge:
159
- name: rebuild status badge (advisory — publish only, never a gate)
178
+ name: rebuild status badge (advisory - publish only, never a gate)
160
179
  if: github.event_name == 'push'
161
180
  runs-on: ubuntu-latest
162
181
  permissions:
@@ -181,14 +200,14 @@ export function ciManagedBody(model, testsDir, emVersion) {
181
200
  git push`;
182
201
  }
183
202
  function ciWorkflowHeader(model, emVersion) {
184
- return `# Generated once by \`em ci init ${model}\` (em ${emVersion}) — docs/ci.md
203
+ return `# Generated once by \`em ci init ${model}\` (em ${emVersion}) - docs/ci.md
185
204
  #
186
205
  # This file is yours from here: edit it, add jobs, remove ones you don't want. The content
187
206
  # between the GENERATED:${CI_WORKFLOW_MARKER} markers below is what a future \`em ci init\`
188
- # (e.g. after upgrading em) refreshes in place — add your own jobs above or below the markers,
207
+ # (e.g. after upgrading em) refreshes in place - add your own jobs above or below the markers,
189
208
  # at the same indent under \`jobs:\`, and they survive a re-run untouched. Every gate here fails
190
209
  # the PR the same way any other required check does; the status-badge job only ever publishes.
191
- # \`em ci init ${model} --check\` reports drift in the managed block — advisory unless you wire
210
+ # \`em ci init ${model} --check\` reports drift in the managed block - advisory unless you wire
192
211
  # it into a gate yourself (docs/ci.md).
193
212
  name: em ci
194
213
 
@@ -229,7 +248,7 @@ export function conformManagedBody(model, emVersion) {
229
248
  node-version: 20
230
249
 
231
250
  - name: Note reports already present
232
- run: ls "$MODEL_DIR"/conformance/*-report.md 2>/dev/null | sort > /tmp/reports-before
251
+ run: find "$MODEL_DIR/conformance" -maxdepth 1 -name '*-report.md' 2>/dev/null | sort > /tmp/reports-before
233
252
 
234
253
  - name: Run conform phase
235
254
  run: |
@@ -242,10 +261,10 @@ export function conformManagedBody(model, emVersion) {
242
261
 
243
262
  - name: Post report
244
263
  run: |
245
- ls "$MODEL_DIR"/conformance/*-report.md 2>/dev/null | sort > /tmp/reports-after
264
+ find "$MODEL_DIR/conformance" -maxdepth 1 -name '*-report.md' 2>/dev/null | sort > /tmp/reports-after
246
265
  report=$(comm -13 /tmp/reports-before /tmp/reports-after | tail -1)
247
266
  if [ -z "$report" ]; then
248
- echo "the run produced no new report — nothing to post"
267
+ echo "the run produced no new report - nothing to post"
249
268
  exit 0
250
269
  fi
251
270
  gh issue create --title "Model conformance report $(date +%F)" \\
@@ -256,9 +275,9 @@ export function conformManagedBody(model, emVersion) {
256
275
  MODEL_DIR: ${modelDir}`;
257
276
  }
258
277
  function conformWorkflowHeader(model, emVersion) {
259
- return `# Generated once by \`em ci init ${model}\` (em ${emVersion}) — docs/ci.md#conformance-cadence-advisory
278
+ return `# Generated once by \`em ci init ${model}\` (em ${emVersion}) - docs/ci.md#conformance-cadence-advisory
260
279
  #
261
- # This file is yours from here: edit it freely. It is advisory-only by construction — the job
280
+ # This file is yours from here: edit it freely. It is advisory-only by construction - the job
262
281
  # never fails the build on drift (findings become a GitHub issue for a human to ratify), and
263
282
  # the state file's \`Last conformance:\` marker only advances when a human ratifies the run's
264
283
  # outcome locally and commits that update. Resist wiring this to every push; cadence, not
@@ -281,6 +300,41 @@ export function buildConformWorkflowFile(model, emVersion) {
281
300
  const { start, end } = markerPair(CONFORM_WORKFLOW_MARKER, "hash");
282
301
  return `${conformWorkflowHeader(model, emVersion)}${start}\n${conformManagedBody(model, emVersion)}\n${end}\n`;
283
302
  }
303
+ /**
304
+ * Which model an existing managed block was generated for, read from the block's own content
305
+ * (MIL-256). Deliberately content-derived rather than a new marker line: blocks written by em
306
+ * 1.13.0 and earlier carry no machine-readable model, but every one of them bakes the model into
307
+ * the per-model `run:` lines (em-ci.yml: `slice index "<model>" --check`; em-conform.yml:
308
+ * `MODEL_DIR: <dir>`). Neither pattern includes the `npx @milehimikey/em@<ver>` pin, so a
309
+ * version difference can never read as a different model. Returns null when the block was
310
+ * hand-edited past recognition - callers then treat it as the same model (the conservative,
311
+ * pre-MIL-256 behavior) rather than refusing on a guess.
312
+ */
313
+ export function managedBlockModel(markerName, blockBody) {
314
+ if (markerName === CONFORM_WORKFLOW_MARKER) {
315
+ const m = /^\s*MODEL_DIR: (.+?)\s*$/m.exec(blockBody);
316
+ return m ? m[1] : null;
317
+ }
318
+ const m = /\bslice index "([^"]*)" --check\b/.exec(blockBody);
319
+ return m ? m[1] : null;
320
+ }
321
+ function existingBlockBody(content, markerName) {
322
+ const m = markerRegex(markerName, "hash").exec(content);
323
+ return m ? m[2] : null;
324
+ }
325
+ /** `--force` over another model's block also retargets the generated header's own wording
326
+ * (`em ci init <model>` on `#` comment lines above the start marker) at the new model, taken
327
+ * from the freshly generated header. Only those exact generated phrasings are touched; a
328
+ * header the repo reworded, and everything below the start marker, is left alone. */
329
+ function retargetHeader(content, generated, markerName) {
330
+ const startLine = markerPair(markerName, "hash").start;
331
+ const at = content.indexOf(startLine);
332
+ const nextModel = /^#.*`em ci init ([^ `]+)/m.exec(generated)?.[1];
333
+ if (at < 0 || !nextModel)
334
+ return content;
335
+ const head = content.slice(0, at).replace(/^(#.*`em ci init )([^ `]+)/gm, (_w, pre) => `${pre}${nextModel}`);
336
+ return head + content.slice(at);
337
+ }
284
338
  /**
285
339
  * Decide what to do with one generated file: create it if missing, patch the managed block if
286
340
  * the marker pair is present, or (without `force`) leave an existing unmarked file alone. Pure
@@ -290,6 +344,12 @@ export function buildConformWorkflowFile(model, emVersion) {
290
344
  * marker-wrapped body) — written verbatim for `create`/`would-replace`. `managedBody` is just
291
345
  * the text between the markers, reused to patch an existing marked file in place without
292
346
  * touching whatever a repo added around it.
347
+ *
348
+ * MIL-256 (#174): the managed block is single-model (multi-model is MIL-233). A marked file
349
+ * whose block was generated for a DIFFERENT model than `managedBody` is `other-model` (refused)
350
+ * unless `force`, then `replace-model`. The same model with any other difference (version pin,
351
+ * old text) stays `stale`, exactly as before. `em upgrade`'s ci-block step re-derives the model
352
+ * from the existing file itself, so it only ever plans the same model.
293
353
  */
294
354
  export function planCiFile(path, generated, managedBody, markerName, force) {
295
355
  if (!existsSync(path))
@@ -299,10 +359,20 @@ export function planCiFile(path, generated, managedBody, markerName, force) {
299
359
  if (updated === null) {
300
360
  return force ? { kind: "would-replace", content: generated } : { kind: "missing-markers" };
301
361
  }
302
- return updated === original ? { kind: "ok", content: original } : { kind: "stale", content: updated, current: original };
362
+ if (updated === original)
363
+ return { kind: "ok", content: original };
364
+ const existing = existingBlockBody(original, markerName);
365
+ const previous = existing === null ? null : managedBlockModel(markerName, existing);
366
+ const next = managedBlockModel(markerName, managedBody);
367
+ if (previous !== null && next !== null && previous !== next) {
368
+ if (!force)
369
+ return { kind: "other-model", previous };
370
+ return { kind: "replace-model", content: retargetHeader(updated, generated, markerName), previous };
371
+ }
372
+ return { kind: "stale", content: updated, current: original };
303
373
  }
304
374
  export function applyCiFile(path, status) {
305
- if (status.kind === "create" || status.kind === "stale" || status.kind === "would-replace") {
375
+ if (status.kind === "create" || status.kind === "stale" || status.kind === "would-replace" || status.kind === "replace-model") {
306
376
  writeFileSync(path, status.content, "utf8");
307
377
  }
308
378
  }