@milehimikey/em 1.19.7 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/reference/conform.md +29 -1
- package/.claude/skills/event-modeling-design/SKILL.md +14 -3
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-discover/reference/extract.md +8 -0
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +7 -2
- package/.claude/skills/event-modeling-implement/reference/implement.md +73 -6
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +22 -2
- package/.claude/skills/event-modeling-shared/reference/methodology.md +16 -0
- package/.claude/skills/event-modeling-shared/templates/slice.md +6 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/catalog/docBadge.d.ts +16 -0
- package/dist/catalog/docBadge.js +50 -0
- package/dist/catalog/docBadge.js.map +1 -0
- package/dist/catalog/docModelConsistencyValidate.js +9 -2
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/invTokens.d.ts +49 -0
- package/dist/catalog/invTokens.js +94 -0
- package/dist/catalog/invTokens.js.map +1 -0
- package/dist/catalog/pages.js +48 -1
- package/dist/catalog/pages.js.map +1 -1
- package/dist/catalog/scenarios.d.ts +4 -0
- package/dist/catalog/scenarios.js +4 -1
- package/dist/catalog/scenarios.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +46 -1
- package/dist/catalog/sliceDoc.js +90 -2
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceDocRender.d.ts +3 -0
- package/dist/catalog/sliceDocRender.js +295 -0
- package/dist/catalog/sliceDocRender.js.map +1 -0
- package/dist/cli/coverage.d.ts +13 -23
- package/dist/cli/coverage.js +51 -91
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/refsCheck.d.ts +52 -0
- package/dist/cli/refsCheck.js +127 -1
- package/dist/cli/refsCheck.js.map +1 -1
- package/dist/cli/sliceConform.d.ts +57 -1
- package/dist/cli/sliceConform.js +109 -9
- package/dist/cli/sliceConform.js.map +1 -1
- package/dist/cli/watchStoryline.d.ts +27 -0
- package/dist/cli/watchStoryline.js +55 -0
- package/dist/cli/watchStoryline.js.map +1 -0
- package/dist/cli.js +161 -7
- package/dist/cli.js.map +1 -1
- package/dist/emit/docJson.d.ts +99 -0
- package/dist/emit/docJson.js +197 -0
- package/dist/emit/docJson.js.map +1 -0
- package/dist/emit/refsJson.d.ts +5 -3
- package/dist/emit/refsJson.js +5 -2
- package/dist/emit/refsJson.js.map +1 -1
- package/dist/mcp/server.js +23 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/continuation.d.ts +9 -0
- package/dist/model/continuation.js +30 -0
- package/dist/model/continuation.js.map +1 -1
- package/dist/model/rules.d.ts +8 -0
- package/dist/model/rules.js +8 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/shapeLint.js +48 -0
- package/dist/model/shapeLint.js.map +1 -1
- package/dist/model/validate.js +119 -0
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +8 -0
- package/dist/parser/parser.js +51 -1
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/render.js +1 -1
- package/dist/render/render.js.map +1 -1
- package/dist/render/sliceOverlay.d.ts +7 -2
- package/dist/render/sliceOverlay.js +28 -3
- package/dist/render/sliceOverlay.js.map +1 -1
- package/dist/render/storylinePicker.d.ts +9 -0
- package/dist/render/storylinePicker.js +17 -0
- package/dist/render/storylinePicker.js.map +1 -0
- package/dist/render/viewerHtml.js +1 -1
- package/dist/render/viewerHtml.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/shared/reference/em-dsl.md +22 -2
- package/plugin/shared/reference/methodology.md +16 -0
- package/plugin/shared/templates/slice.md +6 -1
- package/plugin/skills/conform/SKILL.md +1 -1
- package/plugin/skills/conform/reference/conform.md +29 -1
- package/plugin/skills/design/SKILL.md +14 -3
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/discover/reference/extract.md +8 -0
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +7 -2
- package/plugin/skills/implement/reference/implement.md +73 -6
- package/plugin/skills/review/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,11 +14,14 @@ import { renderDot, layoutDot, composeSvg, writeRendered, formatFromPath } from
|
|
|
14
14
|
import { buildSliceDiagram } from "./render/sliceDiagram.js";
|
|
15
15
|
import { resolveSliceArg, defaultSliceOut, defaultStorylineOut } from "./cli/render-inputs.js";
|
|
16
16
|
import { buildStorylineDiagram, resolveStorylineArg } from "./render/storylineDiagram.js";
|
|
17
|
+
import { embedStorylinePicker } from "./render/storylinePicker.js";
|
|
18
|
+
import { resolveWatchStoryline, storylineFileFor, storylineVariants } from "./cli/watchStoryline.js";
|
|
17
19
|
import { resolveSliceKey } from "./model/resolveSlice.js";
|
|
18
20
|
import { serializeBuilds, watchFile } from "./render/watch.js";
|
|
19
21
|
import { startLiveServer } from "./render/serve.js";
|
|
20
22
|
import { formatDiagnostic, hasErrors, publicViewsWithoutInModelReader } from "./model/validate.js";
|
|
21
23
|
import { buildExport, buildSliceExport, GENERATOR_VERSION } from "./emit/json.js";
|
|
24
|
+
import { buildDocJsonExport } from "./emit/docJson.js";
|
|
22
25
|
import { generateContract, runApiCheck, formatApiCheckText } from "./cli/api.js";
|
|
23
26
|
import { buildApiCheckJson } from "./emit/apiCheckJson.js";
|
|
24
27
|
import { buildValidateJson, buildShapeJson, buildSliceReadyJson, buildValidateListJson, collectMarkers } from "./emit/validateJson.js";
|
|
@@ -50,7 +53,7 @@ import { planMigration, verifyMigration } from "./cli/migrateReactionShape.js";
|
|
|
50
53
|
import { buildLedgerJson } from "./emit/ledgerJson.js";
|
|
51
54
|
import { buildRefsJson } from "./emit/refsJson.js";
|
|
52
55
|
import { buildShapeReport, formatShapeText } from "./cli/shape.js";
|
|
53
|
-
import { applyRefWaivers, checkRefs, readWaiveFile } from "./cli/refsCheck.js";
|
|
56
|
+
import { applyRefWaivers, checkRatificationOrder, checkRefs, readWaiveFile } from "./cli/refsCheck.js";
|
|
54
57
|
import { buildCoverageReport, resolveScopedSlices, resolveCoverageSliceKey } from "./cli/coverage.js";
|
|
55
58
|
import { buildCoverageJson } from "./emit/coverageJson.js";
|
|
56
59
|
import { resolveSliceStatusFacts, countOpenIssues, resolveConformanceEntry, aggregateInvariantTotals, buildStatusReport, formatStatusText, formatStatusMarkdown, buildStatusBadge, formatConformancePart, findSpecifyRoot, resolveModelVersionStatusEntry, resolveEmVersionStatusEntry, } from "./cli/status.js";
|
|
@@ -89,7 +92,7 @@ import { listModelCommits } from "./cli/changelog-git.js";
|
|
|
89
92
|
import { buildChangelogDoc } from "./cli/changelogBuild.js";
|
|
90
93
|
import { runRevise } from "./cli/revise.js";
|
|
91
94
|
import { runDefer, runDeferFromIssue } from "./cli/defer.js";
|
|
92
|
-
import { runSliceConform, resolveFindingsForReport, findingsFilesOf, unruledAcrossFiles } from "./cli/sliceConform.js";
|
|
95
|
+
import { runSliceConform, runSliceConformPreview, writePreviewFindings, resolveFindingsForReport, findingsFilesOf, unruledAcrossFiles, } from "./cli/sliceConform.js";
|
|
93
96
|
import { checkPrdTriage, buildCheckPrdTriageJson, renderPrdTriageMarkdown, validatePrdTriage } from "./cli/prdTriage.js";
|
|
94
97
|
import { checkFindingsFile, buildCheckFindingsJson, FINDING_LOCI } from "./cli/findings.js";
|
|
95
98
|
import { STATE_FILE_NAME, PHASES, isPhase, loadStateFile, parseState, setPhase, setConformance, setModelVersion, setCertified, setEmVersion, setReview, isValidDateString, modelPathMismatch, } from "./cli/stateFile.js";
|
|
@@ -283,9 +286,35 @@ program
|
|
|
283
286
|
.option("--slice <key>", "export only this slice's object (pattern/fields/doc) instead of the whole model (export " +
|
|
284
287
|
"key, MIL-128) — refuses only if THIS slice has an error; an unrelated slice's breakage " +
|
|
285
288
|
"elsewhere in the model doesn't block it (see docs/cli.md)")
|
|
289
|
+
.option("--doc-json", "emit the structured slice-doc export (GH #401) instead of the model snapshot: per slice the " +
|
|
290
|
+
"frontmatter join plus the parsed sections as data (fields, invariants, scenarios, error " +
|
|
291
|
+
"flows, open questions) and a model-level invariant catalog. Own schema version; whole " +
|
|
292
|
+
"model only (does not combine with --slice)")
|
|
293
|
+
.option("--include-html", "with --doc-json, include each slice doc's rendered HTML body (excluded by default; the " +
|
|
294
|
+
"authored body never appears in a plain export)")
|
|
286
295
|
.action((file, opts) => {
|
|
287
296
|
const { model, refs, diagnostics, source } = compileFile(file);
|
|
288
297
|
printDiagnostics(diagnostics);
|
|
298
|
+
if (opts.docJson) {
|
|
299
|
+
if (opts.slice) {
|
|
300
|
+
console.error("em export --doc-json: exports the whole model and does not combine with --slice");
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
if (hasErrors(diagnostics)) {
|
|
304
|
+
console.error("not exporting: fix the errors above");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const exportedDoc = buildDocJsonExport(model, refs, diagnostics, source, file, { includeHtml: opts.includeHtml });
|
|
308
|
+
printDiagnostics(newDiagnostics(exportedDoc.diagnostics, diagnostics));
|
|
309
|
+
if (opts.out) {
|
|
310
|
+
writeFileSync(opts.out, exportedDoc.text + "\n");
|
|
311
|
+
console.log(`wrote ${opts.out}`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
process.stdout.write(exportedDoc.text + "\n");
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
289
318
|
if (opts.slice) {
|
|
290
319
|
// Scoped export deliberately does NOT reuse the whole-model error guard below: the
|
|
291
320
|
// ticket's own motivation for `--slice` is an agent implementing one already-ratified
|
|
@@ -1088,7 +1117,10 @@ slice
|
|
|
1088
1117
|
"later re-certification is normal — there's no way for em to tell 'later' from 'earlier' " +
|
|
1089
1118
|
"for an arbitrary revision string, so there's no --force to reach for). Refuses when the " +
|
|
1090
1119
|
"conformance/<date>[-<name>]-findings.json file matching --at still has an unruled finding in scope " +
|
|
1091
|
-
"for this slice; --skip-findings-check overrides with a loud notice."
|
|
1120
|
+
"for this slice; --skip-findings-check overrides with a loud notice. GH #400: --preview runs the " +
|
|
1121
|
+
"same doc-resolution and findings-check read for a slice that hasn't shipped yet — the shipped/" +
|
|
1122
|
+
"implementedIn gates are waived and NOTHING is ever written to the doc; output is ephemeral " +
|
|
1123
|
+
"(text or --json) unless --write-findings opts into persisting a normal findings file.")
|
|
1092
1124
|
.argument("<file>", "input .em file")
|
|
1093
1125
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
1094
1126
|
.requiredOption("--at <rev>", "the target-repo revision this certification sweep diffed against — or, when what was exercised is not a commit " +
|
|
@@ -1096,12 +1128,31 @@ slice
|
|
|
1096
1128
|
"`<kind>:<id>` such as build:1234 or deploy:eu-1-2026-10-15 (GH #261); recorded verbatim, skipped by git-reachability checks")
|
|
1097
1129
|
.option("--on <date>", "certification date, YYYY-MM-DD (default: today, local date)")
|
|
1098
1130
|
.option("--skip-findings-check", "certify even with unruled findings in scope — prints a loud notice on stderr")
|
|
1131
|
+
.option("--preview", "GH #400: pre-ship advisory mode — waives the shipped-status/`implementedIn:` gates, writes NOTHING to the " +
|
|
1132
|
+
"doc (no conformedVersion/conformedAt/conformedOn, ever); ephemeral by default, --write-findings opts " +
|
|
1133
|
+
"into persisting a normal findings file. --skip-findings-check has no effect in this mode (preview " +
|
|
1134
|
+
"never refuses on findings)")
|
|
1135
|
+
.option("--write-findings", "with --preview: persist the preview's own unruled findings as conformance/<on>-<slice-key>-preview-findings.json")
|
|
1136
|
+
.option("--json", "with --preview: print a JSON document instead of the text report")
|
|
1099
1137
|
.action((file, sliceKey, opts) => {
|
|
1138
|
+
if (opts.writeFindings === true && opts.preview !== true) {
|
|
1139
|
+
console.error("em slice conform: --write-findings only applies with --preview");
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
if (opts.json === true && opts.preview !== true) {
|
|
1143
|
+
console.error("em slice conform: --json only applies with --preview");
|
|
1144
|
+
process.exit(1);
|
|
1145
|
+
}
|
|
1146
|
+
if (opts.preview === true && opts.skipFindingsCheck === true) {
|
|
1147
|
+
console.error("em slice conform: --skip-findings-check has no effect with --preview (preview never blocks on findings)");
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1100
1150
|
const { model, refs, diagnostics } = compileFile(file);
|
|
1101
1151
|
printDiagnostics(diagnostics);
|
|
1102
1152
|
sliceKey = resolveSliceOrExit("em slice conform", model, refs, sliceKey);
|
|
1103
1153
|
// Scoped the same way `em slice ratify`/`em slice revise`/`em slice mark-implemented` are:
|
|
1104
|
-
// only an error concerning THIS slice refuses.
|
|
1154
|
+
// only an error concerning THIS slice refuses. Not waived by --preview — an erroring model
|
|
1155
|
+
// isn't a fact worth previewing against either.
|
|
1105
1156
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
1106
1157
|
if (scopedErrors.length > 0) {
|
|
1107
1158
|
console.error(`em slice conform: slice "${sliceKey}" has errors — fix them first`);
|
|
@@ -1113,6 +1164,44 @@ slice
|
|
|
1113
1164
|
console.error(`em slice conform: invalid --on date "${opts.on}" — expected YYYY-MM-DD`);
|
|
1114
1165
|
process.exit(1);
|
|
1115
1166
|
}
|
|
1167
|
+
if (opts.preview === true) {
|
|
1168
|
+
const baseDir = dirname(file);
|
|
1169
|
+
const result = runSliceConformPreview(model, refs, baseDir, sliceKey, opts.at);
|
|
1170
|
+
if (!result.ok) {
|
|
1171
|
+
if (opts.json) {
|
|
1172
|
+
console.log(JSON.stringify({ ok: false, message: result.message }, null, 2));
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
console.error(`em slice conform --preview: ${result.message}`);
|
|
1176
|
+
}
|
|
1177
|
+
process.exit(1);
|
|
1178
|
+
}
|
|
1179
|
+
const writtenTo = opts.writeFindings === true ? writePreviewFindings(baseDir, file, sliceKey, result.at, on, result.unruledFindings) : null;
|
|
1180
|
+
if (opts.json) {
|
|
1181
|
+
console.log(JSON.stringify({
|
|
1182
|
+
ok: true,
|
|
1183
|
+
preview: true,
|
|
1184
|
+
slice: sliceKey,
|
|
1185
|
+
path: result.path,
|
|
1186
|
+
status: result.status,
|
|
1187
|
+
version: result.version,
|
|
1188
|
+
at: result.at,
|
|
1189
|
+
findingsFiles: result.findingsFiles,
|
|
1190
|
+
unruledFindings: result.unruledFindings,
|
|
1191
|
+
writtenTo,
|
|
1192
|
+
}, null, 2));
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
const findingsNote = result.unruledFindings.length === 0
|
|
1196
|
+
? result.findingsFiles.length === 0
|
|
1197
|
+
? `no conformance findings file matches --at ${result.at} (conformance/<date>[-<name>]-findings.json with that revision)`
|
|
1198
|
+
: `no unruled conformance findings in scope (checked: ${result.findingsFiles.join(", ")})`
|
|
1199
|
+
: `${result.unruledFindings.length} unruled conformance finding(s) in scope (id ${result.unruledFindings.map((f) => f.id).join(", ")}) in ${result.findingsFiles.join(", ")}`;
|
|
1200
|
+
console.log(`preview: ${result.path} (status: ${result.status ?? "(empty)"}) @ ${result.at} — ${findingsNote}`);
|
|
1201
|
+
console.log(writtenTo !== null ? `wrote: ${writtenTo}` : "ephemeral — nothing written; pass --write-findings to persist a snapshot");
|
|
1202
|
+
}
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1116
1205
|
const result = runSliceConform(model, refs, dirname(file), sliceKey, opts.at, on, opts.skipFindingsCheck === true);
|
|
1117
1206
|
if (!result.ok) {
|
|
1118
1207
|
console.error(`em slice conform: ${result.message}`);
|
|
@@ -1638,10 +1727,24 @@ program
|
|
|
1638
1727
|
.option("--keep-empty-lanes", "keep the API lane even when empty")
|
|
1639
1728
|
.option("--serve", "serve a live viewer with instant push-reload (no polling)")
|
|
1640
1729
|
.option("--port <n>", "port for --serve (default 5173)", (v) => parseInt(v, 10))
|
|
1730
|
+
.option("--storyline <name>", "GH #309 (deferred from #277): filter the live view to one storyline's subset — the same " +
|
|
1731
|
+
"subset `em render --storyline` draws (its own slices in order, plus dimmed stub columns " +
|
|
1732
|
+
"for what they read from outside it and the `after` end state). Whenever the model " +
|
|
1733
|
+
"declares any storylines the viewer also offers a picker to switch to the full model or " +
|
|
1734
|
+
"another declared storyline, live, without restarting. An unknown name errors, listing " +
|
|
1735
|
+
"the declared storylines; requires --serve")
|
|
1641
1736
|
.action(async (file, opts) => {
|
|
1642
1737
|
const out = opts.out ?? defaultOut(file, opts.format ?? "svg");
|
|
1643
1738
|
const fmt = opts.format ?? formatFromPath(out);
|
|
1739
|
+
if (opts.storyline !== undefined && !opts.serve) {
|
|
1740
|
+
console.error("em watch: --storyline applies only with --serve");
|
|
1741
|
+
process.exit(1);
|
|
1742
|
+
}
|
|
1644
1743
|
let server;
|
|
1744
|
+
// The file the printed live-view URL points at — basename(out) (the full model) unless
|
|
1745
|
+
// --storyline resolves, in which case build() below points it at that storyline's own
|
|
1746
|
+
// subset file instead (GH #309).
|
|
1747
|
+
let liveFile = basename(out);
|
|
1645
1748
|
// serializeBuilds: chokidar can fire several events for one save (and an
|
|
1646
1749
|
// editor's write can land while a slow png/pdf render is still in flight) —
|
|
1647
1750
|
// overlapping builds would race each other writing the same output file.
|
|
@@ -1662,7 +1765,40 @@ program
|
|
|
1662
1765
|
server?.notifyError(first ? formatDiagnostic(first).trim() : "model has errors — see terminal");
|
|
1663
1766
|
return;
|
|
1664
1767
|
}
|
|
1665
|
-
|
|
1768
|
+
// GH #309: --storyline is validated on every build, same as `em render --storyline`
|
|
1769
|
+
// (resolveWatchStoryline shares findStoryline's resolution) — a save that renames or
|
|
1770
|
+
// drops the storyline must not kill the watcher.
|
|
1771
|
+
const resolved = resolveWatchStoryline(model, opts.storyline);
|
|
1772
|
+
if ("error" in resolved) {
|
|
1773
|
+
console.error(resolved.error);
|
|
1774
|
+
server?.notifyError(resolved.error);
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
if (opts.serve && fmt === "svg") {
|
|
1778
|
+
const baseDir = dirname(file);
|
|
1779
|
+
const outDir = dirname(out);
|
|
1780
|
+
const outFile = basename(out);
|
|
1781
|
+
liveFile = "storyline" in resolved ? storylineFileFor(resolved.storyline.name) : outFile;
|
|
1782
|
+
// The full model, always rendered to `out` (unchanged default) — carries the
|
|
1783
|
+
// storyline-picker metadata (a no-op when the model declares none) so the viewer can
|
|
1784
|
+
// offer it regardless of whether --storyline was passed.
|
|
1785
|
+
const fullRaw = await layoutDot(dot);
|
|
1786
|
+
const fullSvg = embedStorylinePicker(composeSvg(fullRaw, model, grid, baseDir, outDir), storylineVariants(model, outFile, liveFile));
|
|
1787
|
+
await writeRendered(fullSvg, out, "svg");
|
|
1788
|
+
// Every declared storyline's own subset (GH #277's `em render --storyline` subset,
|
|
1789
|
+
// reused verbatim) — rendered alongside the full model on every build, independent of
|
|
1790
|
+
// --storyline, so the viewer's picker always has somewhere to navigate to.
|
|
1791
|
+
for (const st of model.storylines ?? []) {
|
|
1792
|
+
const sub = buildStorylineDiagram(model, st, { keepEmptyLanes: opts.keepEmptyLanes });
|
|
1793
|
+
const subFile = storylineFileFor(st.name);
|
|
1794
|
+
const subRaw = await layoutDot(sub.dot);
|
|
1795
|
+
const subSvg = embedStorylinePicker(composeSvg(subRaw, sub.model, sub.grid, baseDir, outDir), storylineVariants(model, outFile, subFile));
|
|
1796
|
+
await writeRendered(subSvg, join(outDir, subFile), "svg");
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
await renderDot(dot, model, grid, out, fmt, dirname(file));
|
|
1801
|
+
}
|
|
1666
1802
|
console.log(`rendered ${out} (${Date.now() - started}ms)`);
|
|
1667
1803
|
server?.notify();
|
|
1668
1804
|
}
|
|
@@ -1680,7 +1816,7 @@ program
|
|
|
1680
1816
|
// The viewer inlines SVG only — for png/pdf watches there's nothing
|
|
1681
1817
|
// live to embed, so don't print a ?svg= URL that can't work.
|
|
1682
1818
|
if (fmt === "svg") {
|
|
1683
|
-
console.log(`→ live view: ${server.url}/?svg=${encodeURIComponent(
|
|
1819
|
+
console.log(`→ live view: ${server.url}/?svg=${encodeURIComponent(liveFile)}`);
|
|
1684
1820
|
console.log(" open it in a browser and share your screen");
|
|
1685
1821
|
}
|
|
1686
1822
|
else {
|
|
@@ -1938,6 +2074,10 @@ program
|
|
|
1938
2074
|
.requiredOption("--reachable-from <rev>", "the branch or commit every ref must be an ancestor of (e.g. origin/main)")
|
|
1939
2075
|
.option("--waive <slice-key>", "excuse this slice's non-ancestor/unresolved/undecidable results from the exit code (repeatable); still reported", (value, previous) => [...previous, value], [])
|
|
1940
2076
|
.option("--waive-file <path>", "a baseline file of slice keys to waive, one per line (`#` comments allowed); missing file = no waivers")
|
|
2077
|
+
.option("--check-order", "advisory-only (GH #399): for every slice doc with a ratifiedRef, report whether a commit between the branch's " +
|
|
2078
|
+
"fork point and ratifiedRef already touched code other than the doc itself — a retrofit signal for inline " +
|
|
2079
|
+
"ratification (spec written to describe code that already exists). History is rewritable, so this NEVER " +
|
|
2080
|
+
"affects the exit code or `ok`, and is never a validate gate — see docs/process.md's inline-ratification section")
|
|
1941
2081
|
.option("--json", "print a JSON document instead of the text report (see docs/cli.md)")
|
|
1942
2082
|
.action((file, opts) => {
|
|
1943
2083
|
const { model, refs } = compileFile(file);
|
|
@@ -1953,8 +2093,11 @@ program
|
|
|
1953
2093
|
}
|
|
1954
2094
|
if (result.shallow)
|
|
1955
2095
|
console.error("warn: shallow clone — every ref is undecidable; check out with fetch-depth: 0");
|
|
2096
|
+
// GH #399: computed only with --check-order, and never folded into `result`/`result.ok` —
|
|
2097
|
+
// it is a sibling report, not a new ref field, and must never move the exit code (R5).
|
|
2098
|
+
const orderResults = opts.checkOrder ? checkRatificationOrder(model, refs, dirname(file), result.target, result.shallow) : null;
|
|
1956
2099
|
if (opts.json) {
|
|
1957
|
-
process.stdout.write(buildRefsJson(result, file) + "\n");
|
|
2100
|
+
process.stdout.write(buildRefsJson(result, file, orderResults) + "\n");
|
|
1958
2101
|
}
|
|
1959
2102
|
else {
|
|
1960
2103
|
for (const r of result.results) {
|
|
@@ -1971,8 +2114,19 @@ program
|
|
|
1971
2114
|
else {
|
|
1972
2115
|
console.log(`${bad} ref(s) not reachable from ${opts.reachableFrom} (${result.checkedRefs} checked on ${result.checkedDocs} doc(s)${waivedNote})`);
|
|
1973
2116
|
}
|
|
2117
|
+
if (orderResults) {
|
|
2118
|
+
console.log("");
|
|
2119
|
+
console.log("ratification order (advisory — never affects exit code):");
|
|
2120
|
+
if (orderResults.length === 0) {
|
|
2121
|
+
console.log(" no slice doc carries a ratifiedRef to check");
|
|
2122
|
+
}
|
|
2123
|
+
for (const o of orderResults) {
|
|
2124
|
+
console.log(` ${o.status.padEnd(23)} ${o.sliceKey} ratifiedRef: ${o.ratifiedRef}${o.detail ? ` — ${o.detail}` : ""}`);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1974
2127
|
}
|
|
1975
2128
|
// Set the code rather than process.exit(): stdout to a pipe shouldn't risk truncation.
|
|
2129
|
+
// `orderResults` never participates — advisory only, per R5.
|
|
1976
2130
|
if (!result.ok)
|
|
1977
2131
|
process.exitCode = 1;
|
|
1978
2132
|
});
|