@milehimikey/em 1.14.2 → 1.16.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/agents/em-implementer.md +12 -2
- package/.claude/agents/em-validator.md +2 -1
- package/.claude/skills/event-modeling/SKILL.md +3 -3
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +29 -18
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +60 -56
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +13 -19
- package/.claude/skills/event-modeling-implement/SKILL.md +3 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +40 -27
- package/.claude/skills/event-modeling-review/SKILL.md +13 -3
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +234 -228
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +3 -3
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +69 -27
- package/.claude/skills/event-modeling-shared/templates/constitution.md +5 -7
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/catalog/deferQueue.d.ts +20 -0
- package/dist/catalog/deferQueue.js +63 -0
- package/dist/catalog/deferQueue.js.map +1 -0
- package/dist/catalog/docJoin.d.ts +22 -1
- package/dist/catalog/docJoin.js +13 -4
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/docModelConsistencyValidate.js +4 -8
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/driftSignal.d.ts +16 -12
- package/dist/catalog/driftSignal.js +14 -9
- package/dist/catalog/driftSignal.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +51 -0
- package/dist/catalog/sliceDoc.js +47 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.js +19 -1
- package/dist/catalog/sliceReadyValidate.js.map +1 -1
- package/dist/catalog/sliceSections.d.ts +5 -0
- package/dist/catalog/sliceSections.js +11 -2
- package/dist/catalog/sliceSections.js.map +1 -1
- package/dist/cli/conformScope.d.ts +4 -0
- package/dist/cli/conformScope.js +2 -2
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/coverage.js +7 -3
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/defer.d.ts +106 -2
- package/dist/cli/defer.js +357 -24
- package/dist/cli/defer.js.map +1 -1
- package/dist/cli/engagement.d.ts +26 -5
- package/dist/cli/engagement.js +55 -28
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementFile.d.ts +2 -0
- package/dist/cli/engagementFile.js +5 -3
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/findings.d.ts +2 -2
- package/dist/cli/findings.js +2 -2
- package/dist/cli/frontmatterSurgery.d.ts +3 -3
- package/dist/cli/frontmatterSurgery.js +3 -3
- package/dist/cli/frontmatterSurgery.js.map +1 -1
- package/dist/cli/markImplemented.d.ts +23 -18
- package/dist/cli/markImplemented.js +99 -15
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/ratify.d.ts +36 -6
- package/dist/cli/ratify.js +130 -35
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/review.js +3 -3
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/revise.d.ts +67 -0
- package/dist/cli/revise.js +229 -0
- package/dist/cli/revise.js.map +1 -0
- package/dist/cli/sliceConform.d.ts +4 -3
- package/dist/cli/sliceConform.js +29 -12
- package/dist/cli/sliceConform.js.map +1 -1
- package/dist/cli/status.d.ts +30 -6
- package/dist/cli/status.js +59 -19
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/upgrade.js +17 -12
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli.js +173 -85
- package/dist/cli.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +7 -4
- package/dist/emit/engagementJson.js +11 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/emit/json.d.ts +1 -1
- package/dist/emit/json.js +18 -5
- package/dist/emit/json.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/mcp/server.js +5 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/continuation.d.ts +11 -1
- package/dist/model/continuation.js +29 -0
- package/dist/model/continuation.js.map +1 -1
- package/dist/model/resolveSlice.d.ts +19 -0
- package/dist/model/resolveSlice.js +69 -0
- package/dist/model/resolveSlice.js.map +1 -0
- 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/sliceDocIndex.d.ts +5 -0
- package/dist/model/sliceDocIndex.js +6 -4
- package/dist/model/sliceDocIndex.js.map +1 -1
- package/dist/model/validate.js +5 -2
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/parser.js +55 -1
- package/dist/parser/parser.js.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +5 -7
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/agents/em-implementer.md +12 -2
- package/plugin/agents/em-validator.md +2 -1
- package/plugin/shared/reference/em-dsl.md +234 -228
- package/plugin/shared/reference/operating-principles.md +3 -3
- package/plugin/shared/reference/slice-doc-schema.md +69 -27
- package/plugin/shared/templates/constitution.md +5 -7
- package/plugin/skills/conform/SKILL.md +1 -1
- package/plugin/skills/design/SKILL.md +29 -18
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/engagement/SKILL.md +60 -56
- package/plugin/skills/engagement/reference/engagement.md +13 -19
- package/plugin/skills/event-modeling/SKILL.md +3 -3
- package/plugin/skills/implement/SKILL.md +3 -3
- package/plugin/skills/implement/reference/implement.md +40 -27
- package/plugin/skills/review/SKILL.md +13 -3
- package/dist/cli/reratify.d.ts +0 -58
- package/dist/cli/reratify.js +0 -238
- package/dist/cli/reratify.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ import { ParseError } from "./parser/parser.js";
|
|
|
13
13
|
import { renderDot, layoutDot, composeSvg, writeRendered, formatFromPath } from "./render/render.js";
|
|
14
14
|
import { buildSliceDiagram } from "./render/sliceDiagram.js";
|
|
15
15
|
import { resolveSliceArg, defaultSliceOut } from "./cli/render-inputs.js";
|
|
16
|
+
import { resolveSliceKey } from "./model/resolveSlice.js";
|
|
16
17
|
import { serializeBuilds, watchFile } from "./render/watch.js";
|
|
17
18
|
import { startLiveServer } from "./render/serve.js";
|
|
18
19
|
import { formatDiagnostic, hasErrors, publicViewsWithoutInModelReader } from "./model/validate.js";
|
|
@@ -75,8 +76,8 @@ import { wireSliceNote } from "./cli/sliceLink.js";
|
|
|
75
76
|
import { isStubStatus, runStubAll, STUB_STATUSES } from "./cli/sliceStubAll.js";
|
|
76
77
|
import { listModelCommits } from "./cli/changelog-git.js";
|
|
77
78
|
import { buildChangelogDoc } from "./cli/changelogBuild.js";
|
|
78
|
-
import {
|
|
79
|
-
import { runDefer } from "./cli/defer.js";
|
|
79
|
+
import { runRevise } from "./cli/revise.js";
|
|
80
|
+
import { runDefer, runDeferFromIssue } from "./cli/defer.js";
|
|
80
81
|
import { runSliceConform } from "./cli/sliceConform.js";
|
|
81
82
|
import { checkFindingsFile, buildCheckFindingsJson, lookupFindingsBesideReport, unruledFindingsInScope, FINDING_LOCI } from "./cli/findings.js";
|
|
82
83
|
import { STATE_FILE_NAME, PHASES, isPhase, loadStateFile, parseState, setPhase, setConformance, setModelVersion, setCertified, setEmVersion, setReview, isValidDateString, modelPathMismatch, } from "./cli/stateFile.js";
|
|
@@ -246,7 +247,7 @@ program
|
|
|
246
247
|
// "don't gate on breakage elsewhere" call `--slice-ready` already made (see its own
|
|
247
248
|
// scoping comment above). Only an error that concerns THIS slice (bare key, or an
|
|
248
249
|
// element ref prefixed `<key>/`) refuses.
|
|
249
|
-
const key = opts.slice;
|
|
250
|
+
const key = resolveSliceOrExit("em export --slice", model, refs, opts.slice);
|
|
250
251
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === key || r.startsWith(`${key}/`)));
|
|
251
252
|
if (scopedErrors.length > 0) {
|
|
252
253
|
console.error(`not exporting: slice "${key}" has errors — fix them first`);
|
|
@@ -571,6 +572,8 @@ slice
|
|
|
571
572
|
process.exit(1);
|
|
572
573
|
}
|
|
573
574
|
const check = opts.check === true;
|
|
575
|
+
if (key !== undefined)
|
|
576
|
+
key = resolveSliceOrExit("em slice sync", model, refs, key);
|
|
574
577
|
const outcome = runSliceSync(model, refs, dirname(file), key, check);
|
|
575
578
|
if (!outcome.ok) {
|
|
576
579
|
console.error(`em slice sync: ${outcome.message}`);
|
|
@@ -672,14 +675,23 @@ slice
|
|
|
672
675
|
"one edit an implementing agent makes to a ratified doc at merge (MIL-103, replaces the " +
|
|
673
676
|
"em-sdd-bridge `em-sdd-mark-implemented` script; see reference/implement.md §6). " +
|
|
674
677
|
"Applies only to a `ready-to-implement` doc (MIL-277: a `draft`/`reviewed` doc refuses — " +
|
|
675
|
-
"ratify first).
|
|
676
|
-
"
|
|
678
|
+
"ratify first). Also records the shipped version beside the link (MIL-284): `shippedVersion:`, " +
|
|
679
|
+
"`shippedRef:` (the doc's `ratifiedRef:`, when it has one) and `shippedOn:`, and opens the next " +
|
|
680
|
+
"version as a `draft` when an Open Question was deferred to it (MIL-283). Idempotent on " +
|
|
681
|
+
"the same URL — also after that draft opened; refuses to overwrite a different one; never touches " +
|
|
682
|
+
"the doc body except to re-open those deferred questions")
|
|
677
683
|
.argument("<file>", "input .em file")
|
|
678
684
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
679
685
|
.argument("<pr-url>", "merged PR (or commit) URL")
|
|
680
|
-
.
|
|
686
|
+
.option("--on <date>", "merge date recorded as `shippedOn:`, YYYY-MM-DD (default: today, local date; MIL-284)")
|
|
687
|
+
.action((file, sliceKey, prUrl, opts) => {
|
|
688
|
+
if (opts.on !== undefined && !isValidDateString(opts.on)) {
|
|
689
|
+
console.error(`em slice mark-implemented: invalid --on date "${opts.on}" — expected YYYY-MM-DD`);
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
681
692
|
const { model, refs, diagnostics } = compileFile(file);
|
|
682
693
|
printDiagnostics(diagnostics);
|
|
694
|
+
sliceKey = resolveSliceOrExit("em slice mark-implemented", model, refs, sliceKey);
|
|
683
695
|
// Scoped the same way `em export --slice`/`em validate --slice-ready` are: only an error
|
|
684
696
|
// concerning THIS slice (bare key, or an element ref prefixed `<key>/`) refuses — an
|
|
685
697
|
// unrelated slice's breakage elsewhere in a large, still-WIP model doesn't block marking
|
|
@@ -689,7 +701,7 @@ slice
|
|
|
689
701
|
console.error(`em slice mark-implemented: slice "${sliceKey}" has errors — fix them first`);
|
|
690
702
|
process.exit(1);
|
|
691
703
|
}
|
|
692
|
-
const result = runMarkImplemented(model, refs, dirname(file), sliceKey, prUrl);
|
|
704
|
+
const result = runMarkImplemented(model, refs, dirname(file), sliceKey, prUrl, opts.on ?? localIsoDate());
|
|
693
705
|
if (!result.ok) {
|
|
694
706
|
console.error(`em slice mark-implemented: ${result.message}`);
|
|
695
707
|
process.exit(1);
|
|
@@ -697,6 +709,11 @@ slice
|
|
|
697
709
|
console.log(result.changed
|
|
698
710
|
? `marked implemented: ${result.path} (implementedIn: ${prUrl})`
|
|
699
711
|
: `already implemented (no-op): ${result.path}`);
|
|
712
|
+
if (result.ok && result.openedDraft) {
|
|
713
|
+
console.log(`opened draft v${result.openedDraft.version}: ${result.path} — re-opened ${result.openedDraft.reopened} deferred question(s)` +
|
|
714
|
+
(result.openedDraft.injected > 0 ? `, injected ${result.openedDraft.injected} queued question(s) (GH #236)` : "") +
|
|
715
|
+
`; v${result.openedDraft.version - 1} stays shipped`);
|
|
716
|
+
}
|
|
700
717
|
});
|
|
701
718
|
slice
|
|
702
719
|
.command("review")
|
|
@@ -713,6 +730,7 @@ slice
|
|
|
713
730
|
.action((file, sliceKey, opts) => {
|
|
714
731
|
const { model, refs, diagnostics } = compileFile(file);
|
|
715
732
|
printDiagnostics(diagnostics);
|
|
733
|
+
sliceKey = resolveSliceOrExit("em slice review", model, refs, sliceKey);
|
|
716
734
|
// Scoped the same way `em slice ratify`/`em slice mark-implemented`/`em export --slice`/
|
|
717
735
|
// `em validate --slice-ready` are: only an error concerning THIS slice refuses.
|
|
718
736
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
@@ -745,11 +763,13 @@ slice
|
|
|
745
763
|
"`ratifiedOn:` — the handoff sign-off (MIL-165, docs/process.md#the-slice-lifecycle-gates) " +
|
|
746
764
|
"that makes who ratified, and when, a first-class recorded fact. The review gate is a check " +
|
|
747
765
|
"on the doc's current `status:`, not on whether `reviewedBy:`/`reviewedOn:` are populated: " +
|
|
748
|
-
"a doc at `reviewed`
|
|
749
|
-
"
|
|
766
|
+
"a doc at `reviewed` passes; a `ready-to-implement` doc only when the same signer is " +
|
|
767
|
+
"completing their own record (since 1.15.0, MIL-283 — a new sign-off is a new version: " +
|
|
768
|
+
"`em slice revise`); any other status " +
|
|
750
769
|
"— a `draft` that never went through `em slice review` — is refused unless --skip-review " +
|
|
751
770
|
"is passed. Idempotent on " +
|
|
752
|
-
"the same --by/--on pair; refuses to overwrite a different one already recorded
|
|
771
|
+
"the same --by/--on pair; refuses to overwrite a different one already recorded, and (MIL-284) a " +
|
|
772
|
+
"same-pair re-run on a doc whose body moved after sign-off; never " +
|
|
753
773
|
"touches `version:` or the doc body")
|
|
754
774
|
.argument("<file>", "input .em file")
|
|
755
775
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
@@ -768,6 +788,7 @@ slice
|
|
|
768
788
|
}
|
|
769
789
|
const { model, refs, diagnostics, source } = compileFile(file);
|
|
770
790
|
printDiagnostics(diagnostics);
|
|
791
|
+
sliceKey = resolveSliceOrExit("em slice ratify", model, refs, sliceKey);
|
|
771
792
|
// Scoped the same way `em slice mark-implemented`/`em export --slice`/`em validate
|
|
772
793
|
// --slice-ready` are: only an error concerning THIS slice refuses.
|
|
773
794
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
@@ -803,64 +824,83 @@ slice
|
|
|
803
824
|
: `already ratified (no-op): ${result.path}`);
|
|
804
825
|
warnModelVersionDrift(dirname(file), model, refs, source, `ratifying "${sliceKey}"`);
|
|
805
826
|
});
|
|
827
|
+
/** MIL-283: `em slice revise` and its deprecated alias `em slice reratify` share one action. */
|
|
828
|
+
function reviseAction(commandName) {
|
|
829
|
+
return (file, sliceKey, opts) => {
|
|
830
|
+
if (commandName === "reratify") {
|
|
831
|
+
console.error("notice: `em slice reratify` is deprecated since 1.15.0 — it is `em slice revise`, and the result is a " +
|
|
832
|
+
"`draft` of the next version (not an unsigned `ready-to-implement`). Review and ratify it like v1.");
|
|
833
|
+
}
|
|
834
|
+
if (opts.meaningUnchanged || opts.contractChange !== undefined) {
|
|
835
|
+
console.error(`notice: --meaning-unchanged/--contract-change are not taken by \`em slice ${commandName}\` since 1.15.0 — the ` +
|
|
836
|
+
"API-first confirmation belongs to the ratification of the new version (`em slice ratify --by ... --meaning-unchanged`)");
|
|
837
|
+
}
|
|
838
|
+
const { model, refs, diagnostics, source } = compileFile(file);
|
|
839
|
+
printDiagnostics(diagnostics);
|
|
840
|
+
sliceKey = resolveSliceOrExit(`em slice ${commandName}`, model, refs, sliceKey);
|
|
841
|
+
// Scoped the same way `em slice ratify`/`em slice mark-implemented`/`em export --slice`/
|
|
842
|
+
// `em validate --slice-ready` are: only an error concerning THIS slice refuses.
|
|
843
|
+
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
844
|
+
if (scopedErrors.length > 0) {
|
|
845
|
+
console.error(`em slice ${commandName}: slice "${sliceKey}" has errors — fix them first`);
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
const result = runRevise(model, refs, dirname(file), sliceKey, opts.withdraw === true);
|
|
849
|
+
if (!result.ok) {
|
|
850
|
+
console.error(`em slice ${commandName}: ${result.message}`);
|
|
851
|
+
process.exit(1);
|
|
852
|
+
}
|
|
853
|
+
// MIL-214: advisory only, never refuses — see revise.ts's reviseAdvisory. Both warnings can
|
|
854
|
+
// fire together (a shipped version that was never certified AND still carries unruled findings).
|
|
855
|
+
if (result.advisory?.neverCertified) {
|
|
856
|
+
console.error(`warn: revising "${sliceKey}" whose shipped v${result.newVersion - 1} was never certified`);
|
|
857
|
+
}
|
|
858
|
+
if (result.advisory && result.advisory.unruledFindingsCount > 0) {
|
|
859
|
+
console.error(`warn: revising "${sliceKey}" with ${result.advisory.unruledFindingsCount} unruled conformance finding(s) on the shipped version`);
|
|
860
|
+
}
|
|
861
|
+
const what = result.kind === "shipped"
|
|
862
|
+
? `v${result.newVersion - 1} stays shipped; v${result.newVersion} opened as draft`
|
|
863
|
+
: result.kind === "unshipped"
|
|
864
|
+
? `ratification of v${result.newVersion - 1} withdrawn (never shipped); v${result.newVersion} opened as draft`
|
|
865
|
+
: `v${result.newVersion} was never signed; reopened as draft at the same version`;
|
|
866
|
+
console.log(`revised: ${result.path} (status: draft, version: ${result.newVersion} — ${what})`);
|
|
867
|
+
if (result.reopened > 0)
|
|
868
|
+
console.log(`re-opened ${result.reopened} deferred question(s)`);
|
|
869
|
+
if (result.injected > 0)
|
|
870
|
+
console.log(`injected ${result.injected} queued question(s) into Open Questions (queue cleared)`);
|
|
871
|
+
warnModelVersionDrift(dirname(file), model, refs, source, `revising "${sliceKey}"`);
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
const REVISE_DESCRIPTION = "open the next version of a slice doc as a `draft` (MIL-283): bumps `version:`, flips `status:` to " +
|
|
875
|
+
"`draft`, clears the prior version's review and sign-off (`reviewedBy/On`, `ratifiedBy/On/Ref/Hash`, " +
|
|
876
|
+
"`meaningConfirmed`/`contractChange`) and re-opens the questions deferred to the new version. The " +
|
|
877
|
+
"shipped version stays shipped: `implementedIn:` and the `shipped*` keys are untouched (written first on " +
|
|
878
|
+
"a doc shipped before 1.15). Applies to `status: implemented` (the shipped case) and to a ratified " +
|
|
879
|
+
"`ready-to-implement` doc (ratification withdrawn, never shipped); a `ready-to-implement` doc with no " +
|
|
880
|
+
"`ratifiedBy:` reopens as draft at the same version; `draft`/`reviewed` refuse — the next version is " +
|
|
881
|
+
"already open. The new version then takes the normal road: `em slice review --by`, `em slice ratify --by`. " +
|
|
882
|
+
"Questions queued by `em slice defer` for the new version (GH #236) are injected into its Open Questions. " +
|
|
883
|
+
"A ratified, never-shipped doc REFUSES without --withdraw (revising withdraws the only implementable version; " +
|
|
884
|
+
"queue a question with `em slice defer` instead)";
|
|
885
|
+
slice
|
|
886
|
+
.command("revise")
|
|
887
|
+
.description(REVISE_DESCRIPTION)
|
|
888
|
+
.argument("<file>", "input .em file")
|
|
889
|
+
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
890
|
+
.option("--withdraw", "GH #236: required on a ratified, never-shipped doc — confirms withdrawing the ratification of the only implementable version")
|
|
891
|
+
.action(reviseAction("revise"));
|
|
806
892
|
slice
|
|
807
893
|
.command("reratify")
|
|
808
|
-
.description("
|
|
809
|
-
"ready-to-implement`
|
|
810
|
-
"
|
|
811
|
-
"
|
|
812
|
-
"and clears the sign-off but leaves `status:` alone; a `ready-to-implement` doc with no " +
|
|
813
|
-
"`ratifiedBy:` refuses (awaiting `em slice ratify --by`). Clears any stale " +
|
|
814
|
-
"`ratifiedBy:`/`ratifiedOn:`/`reviewedBy:`/`reviewedOn:` (they describe the PRIOR version's " +
|
|
815
|
-
"review and sign-off) so a follow-up `em slice ratify --by` applies cleanly — and needs no " +
|
|
816
|
-
"fresh review; never touches `implementedIn:` or the doc body")
|
|
894
|
+
.description("DEPRECATED alias of `em slice revise` (MIL-283, since 1.15.0; removed in a later minor). Until 1.14 this " +
|
|
895
|
+
"left the doc at an unsigned `ready-to-implement`; it now opens a `draft` of the next version like `revise`. " +
|
|
896
|
+
"The --meaning-unchanged/--contract-change flags are accepted and ignored with a notice — the API-first " +
|
|
897
|
+
"confirmation is taken by `em slice ratify` on the new version")
|
|
817
898
|
.argument("<file>", "input .em file")
|
|
818
899
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
819
|
-
.option("--meaning-unchanged", "
|
|
820
|
-
"
|
|
821
|
-
.option("--
|
|
822
|
-
"
|
|
823
|
-
.action((file, sliceKey, opts) => {
|
|
824
|
-
const flags = parseMeaningFlags(opts.meaningUnchanged, opts.contractChange);
|
|
825
|
-
if (!flags.ok) {
|
|
826
|
-
console.error(`em slice reratify: ${flags.message}`);
|
|
827
|
-
process.exit(1);
|
|
828
|
-
}
|
|
829
|
-
const { model, refs, diagnostics, source } = compileFile(file);
|
|
830
|
-
printDiagnostics(diagnostics);
|
|
831
|
-
// Scoped the same way `em slice ratify`/`em slice mark-implemented`/`em export --slice`/
|
|
832
|
-
// `em validate --slice-ready` are: only an error concerning THIS slice refuses.
|
|
833
|
-
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
834
|
-
if (scopedErrors.length > 0) {
|
|
835
|
-
console.error(`em slice reratify: slice "${sliceKey}" has errors — fix them first`);
|
|
836
|
-
process.exit(1);
|
|
837
|
-
}
|
|
838
|
-
const result = runReratify(model, refs, dirname(file), sliceKey, flags.confirmation);
|
|
839
|
-
if (!result.ok) {
|
|
840
|
-
console.error(`em slice reratify: ${result.message}`);
|
|
841
|
-
process.exit(1);
|
|
842
|
-
}
|
|
843
|
-
// MIL-214: advisory only, never refuses — see reratify.ts's reratifyAdvisory. Both warnings
|
|
844
|
-
// can fire together (a version that was never certified AND still carries unruled findings).
|
|
845
|
-
if (result.advisory?.neverCertified) {
|
|
846
|
-
console.error(`warn: reratifying "${sliceKey}" whose v${result.newVersion - 1} was never certified`);
|
|
847
|
-
}
|
|
848
|
-
if (result.advisory && result.advisory.unruledFindingsCount > 0) {
|
|
849
|
-
console.error(`warn: reratifying "${sliceKey}" has ${result.advisory.unruledFindingsCount} unruled conformance finding(s)`);
|
|
850
|
-
}
|
|
851
|
-
if (result.kind === "unshipped") {
|
|
852
|
-
// MIL-258: nothing was flipped — say what DID happen, and that the new version is unsigned.
|
|
853
|
-
console.log(`reratified: ${result.path} (version: ${result.newVersion - 1} -> ${result.newVersion}, sign-off cleared, ` +
|
|
854
|
-
`status unchanged: ready-to-implement${confirmationSuffix(flags.confirmation)}) — not ratified until ` +
|
|
855
|
-
`\`em slice ratify --by <name>\` records the new sign-off`);
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
console.log(`reratified: ${result.path} (version: ${result.newVersion}, status: ready-to-implement${confirmationSuffix(flags.confirmation)})`);
|
|
859
|
-
}
|
|
860
|
-
if (result.reopened > 0)
|
|
861
|
-
console.log(`re-opened ${result.reopened} deferred question(s)`);
|
|
862
|
-
warnModelVersionDrift(dirname(file), model, refs, source, `reratifying "${sliceKey}"`);
|
|
863
|
-
});
|
|
900
|
+
.option("--meaning-unchanged", "ignored since 1.15.0 (see above)")
|
|
901
|
+
.option("--contract-change <why>", "ignored since 1.15.0 (see above)")
|
|
902
|
+
.option("--withdraw", "see `em slice revise --withdraw`")
|
|
903
|
+
.action(reviseAction("reratify"));
|
|
864
904
|
slice
|
|
865
905
|
.command("defer")
|
|
866
906
|
.description("record a question this version will not answer as deferred to a later version (MIL-275): " +
|
|
@@ -868,12 +908,19 @@ slice
|
|
|
868
908
|
"v<current>: <decision>; deferred to v<n> (<date>[, <by>])` so `--slice-ready` passes without a " +
|
|
869
909
|
"guess, and mirrors it into `.event-modeling.md` (the parking lot and a dated Decisions-log " +
|
|
870
910
|
"bullet; the file must exist, it is never created). Idempotent; `em slice reratify` re-opens " +
|
|
871
|
-
"the item when it bumps to v<n>"
|
|
911
|
+
"the item when it bumps to v<n>. With --from-issue <text> instead of <question> (MIL-281): " +
|
|
912
|
+
"promotes the single open `issue \"...\"` on the slice's own elements containing <text> into " +
|
|
913
|
+
"the doc's Open Questions as that same deferred item, mirrors it, and removes the `issue` " +
|
|
914
|
+
"clause from the .em — the one-command path for a question raised in a review session. " +
|
|
915
|
+
"On a RATIFIED (`ready-to-implement` + `ratifiedBy:`) or shipped (`implemented`) doc (GH #236) the " +
|
|
916
|
+
"doc is NOT edited — its body is the signed spec — and the question is queued in `.event-modeling.md` " +
|
|
917
|
+
"for v<N+1>; `em slice revise` / `mark-implemented` inject it into the next draft")
|
|
872
918
|
.argument("<file>", "input .em file")
|
|
873
919
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
874
|
-
.argument("
|
|
920
|
+
.argument("[question]", "text identifying the unchecked Open Question (case-sensitive substring); omit when using --from-issue")
|
|
875
921
|
.requiredOption("--until <version>", "the version that must answer it, v<n> (greater than the doc's version)")
|
|
876
922
|
.requiredOption("--decision <text>", "what this version does instead")
|
|
923
|
+
.option("--from-issue <text>", "defer a captured `issue \"...\"` on this slice instead of a doc Open Question (case-sensitive substring of its text)")
|
|
877
924
|
.option("--by <name>", "who deferred it")
|
|
878
925
|
.option("--on <date>", "deferral date, YYYY-MM-DD (default: today, local date)")
|
|
879
926
|
.action((file, sliceKey, question, opts) => {
|
|
@@ -881,24 +928,47 @@ slice
|
|
|
881
928
|
console.error(`em slice defer: invalid --on date "${opts.on}" — expected YYYY-MM-DD`);
|
|
882
929
|
process.exit(1);
|
|
883
930
|
}
|
|
931
|
+
if ((question === undefined) === (opts.fromIssue === undefined)) {
|
|
932
|
+
console.error('em slice defer: pass either "<question>" or --from-issue <text>, not both and not neither');
|
|
933
|
+
process.exit(1);
|
|
934
|
+
}
|
|
884
935
|
const { model, refs, diagnostics } = compileFile(file);
|
|
885
936
|
printDiagnostics(diagnostics);
|
|
937
|
+
sliceKey = resolveSliceOrExit("em slice defer", model, refs, sliceKey);
|
|
886
938
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
887
939
|
if (scopedErrors.length > 0) {
|
|
888
940
|
console.error(`em slice defer: slice "${sliceKey}" has errors — fix them first`);
|
|
889
941
|
process.exit(1);
|
|
890
942
|
}
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
943
|
+
const common = { until: opts.until, decision: opts.decision, by: opts.by, on: opts.on ?? localIsoDate() };
|
|
944
|
+
if (opts.fromIssue !== undefined) {
|
|
945
|
+
const result = runDeferFromIssue(model, refs, dirname(file), file, sliceKey, opts.fromIssue, common);
|
|
946
|
+
if (!result.ok) {
|
|
947
|
+
console.error(`em slice defer: ${result.message}`);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
if (result.queued) {
|
|
951
|
+
console.log(result.changed
|
|
952
|
+
? `queued, not edited: ${result.path} stays as ratified (v${result.queued.docVersion}) — issue "${result.issue}" → ${opts.until} queued for ${sliceKey}@v${result.queued.forVersion} in .event-modeling.md; issue clause removed from ${file}:${result.modelLine}`
|
|
953
|
+
: `already queued (no-op): ${result.path}`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log(result.changed
|
|
957
|
+
? `deferred: ${result.path} (issue "${result.issue}" → ${opts.until}; issue clause removed from ${file}:${result.modelLine})`
|
|
958
|
+
: `already deferred (no-op): ${result.path}`);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const result = runDefer(model, refs, dirname(file), sliceKey, { question: question, ...common });
|
|
898
962
|
if (!result.ok) {
|
|
899
963
|
console.error(`em slice defer: ${result.message}`);
|
|
900
964
|
process.exit(1);
|
|
901
965
|
}
|
|
966
|
+
if (result.queued) {
|
|
967
|
+
console.log(result.changed
|
|
968
|
+
? `queued, not edited: ${result.path} stays as ratified (v${result.queued.docVersion}) — "${question}" → ${opts.until} queued for ${sliceKey}@v${result.queued.forVersion} in .event-modeling.md; \`em slice revise\` or \`mark-implemented\` injects it into the next draft`
|
|
969
|
+
: `already queued (no-op): ${result.path}`);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
902
972
|
console.log(result.changed
|
|
903
973
|
? `deferred: ${result.path} (${question} → ${opts.until})`
|
|
904
974
|
: `already deferred (no-op): ${result.path}`);
|
|
@@ -907,7 +977,8 @@ slice
|
|
|
907
977
|
.command("conform")
|
|
908
978
|
.description("record per-slice-per-version conformance certification: sets `conformedVersion:`/" +
|
|
909
979
|
"`conformedAt:`/`conformedOn:` on a slice doc (MIL-214) — the fact `driftSignal: in-sync` " +
|
|
910
|
-
"now depends on. Legal only for `status: implemented
|
|
980
|
+
"now depends on. Legal only for a shipped slice (`status: implemented`, or any status with " +
|
|
981
|
+
"a `shippedVersion:` — MIL-283) with an `implementedIn:` link; `conformedVersion:` is the shipped version. " +
|
|
911
982
|
"Idempotent on the same (version, --at) pair; a different --at simply overwrites (a " +
|
|
912
983
|
"later re-certification is normal — there's no way for em to tell 'later' from 'earlier' " +
|
|
913
984
|
"for an arbitrary revision string, so there's no --force to reach for). Refuses when the " +
|
|
@@ -921,7 +992,8 @@ slice
|
|
|
921
992
|
.action((file, sliceKey, opts) => {
|
|
922
993
|
const { model, refs, diagnostics } = compileFile(file);
|
|
923
994
|
printDiagnostics(diagnostics);
|
|
924
|
-
|
|
995
|
+
sliceKey = resolveSliceOrExit("em slice conform", model, refs, sliceKey);
|
|
996
|
+
// Scoped the same way `em slice ratify`/`em slice revise`/`em slice mark-implemented` are:
|
|
925
997
|
// only an error concerning THIS slice refuses.
|
|
926
998
|
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
927
999
|
if (scopedErrors.length > 0) {
|
|
@@ -1080,7 +1152,7 @@ state
|
|
|
1080
1152
|
const compiled = compileFile(modelFile);
|
|
1081
1153
|
if (!hasErrors(compiled.diagnostics)) {
|
|
1082
1154
|
const { facts } = resolveSliceDocFacts(compiled.model, compiled.refs, modelDir);
|
|
1083
|
-
inScope = new Set(facts.filter((f) => f.
|
|
1155
|
+
inScope = new Set(facts.filter((f) => f.shipped).map((f) => f.key));
|
|
1084
1156
|
compiledForCertify = { model: compiled.model, refs: compiled.refs, source: compiled.source };
|
|
1085
1157
|
}
|
|
1086
1158
|
}
|
|
@@ -1514,7 +1586,10 @@ program
|
|
|
1514
1586
|
// R34 (MIL-268): one shared verdict helper for this flag, the MCP `slice_ready` tool, and
|
|
1515
1587
|
// `em engagement plan`.
|
|
1516
1588
|
const contractInput = { file, source, diagnostics };
|
|
1517
|
-
|
|
1589
|
+
// GH #237: an unresolvable input goes through as typed so the gate reports its own
|
|
1590
|
+
// `slice-ready-unknown-slice` error (carrying the resolver's message).
|
|
1591
|
+
const resolved = resolveSliceKey(model, refs, opts.sliceReady);
|
|
1592
|
+
const key = resolved.ok ? resolved.key : opts.sliceReady;
|
|
1518
1593
|
const { scoped, ready, result } = computeSliceReadiness(model, refs, dirname(file), key, contractInput, allDiagnostics);
|
|
1519
1594
|
printDiagnostics(scoped);
|
|
1520
1595
|
if (opts.json) {
|
|
@@ -1711,11 +1786,8 @@ program
|
|
|
1711
1786
|
let onlyKey;
|
|
1712
1787
|
let continuationOfKey = null;
|
|
1713
1788
|
if (opts.slice !== undefined) {
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
console.error(`em coverage --slice: no slice with export key "${opts.slice}" in this model`);
|
|
1717
|
-
process.exit(1);
|
|
1718
|
-
}
|
|
1789
|
+
const resolved = resolveSliceOrExit("em coverage --slice", model, refs, opts.slice);
|
|
1790
|
+
const target = resolveCoverageSliceKey(model, refs, resolved);
|
|
1719
1791
|
onlyKey = target.key;
|
|
1720
1792
|
continuationOfKey = target.continuationOf;
|
|
1721
1793
|
}
|
|
@@ -1873,7 +1945,7 @@ program
|
|
|
1873
1945
|
// merged `sliceFacts` above (which spans every input file when several are given).
|
|
1874
1946
|
const conformance = compiled.map(({ file }) => {
|
|
1875
1947
|
const facts = factsByFile.get(file) ?? [];
|
|
1876
|
-
const sliceDocFacts = facts.map((f) => ({ key: f.key, status: f.rawStatus, implementedIn: f.implementedIn }));
|
|
1948
|
+
const sliceDocFacts = facts.map((f) => ({ key: f.key, status: f.rawStatus, implementedIn: f.implementedIn, shipped: f.shipped }));
|
|
1877
1949
|
return resolveConformanceEntry(file, opts.repo, sliceDocFacts);
|
|
1878
1950
|
});
|
|
1879
1951
|
// MIL-218: one model-version entry per input file.
|
|
@@ -2131,6 +2203,7 @@ engagement
|
|
|
2131
2203
|
.option("--downstream-of <ref>", "a slice key, element ref, or display name: it and everything downstream of it (loops-to excluded)")
|
|
2132
2204
|
.option("--parallel <n>", "how many slices of one level may be in flight at once", "3")
|
|
2133
2205
|
.option("--by <name>", "who created the engagement (createdBy)")
|
|
2206
|
+
.option("--tracking <key>", "an issue-tracker key or URL the slice PRs cite (a slice doc's own tracking: wins)")
|
|
2134
2207
|
.option("--force", "overwrite an existing engagement file with this slug")
|
|
2135
2208
|
.action((file, slug, opts) => {
|
|
2136
2209
|
const fail = (m) => {
|
|
@@ -2148,7 +2221,7 @@ engagement
|
|
|
2148
2221
|
const parallel = /^\d+$/.test(opts.parallel) ? Number(opts.parallel) : NaN;
|
|
2149
2222
|
const { model, refs } = compileFile(file);
|
|
2150
2223
|
const path = engagementPath(file, slug);
|
|
2151
|
-
const result = buildNewEngagement({ file, model, refs, baseDir: dirname(file) }, { slug, selector, parallel, createdBy: opts.by ?? null, created: localIsoDate() });
|
|
2224
|
+
const result = buildNewEngagement({ file, model, refs, baseDir: dirname(file) }, { slug, selector, parallel, createdBy: opts.by ?? null, tracking: opts.tracking ?? null, created: localIsoDate() });
|
|
2152
2225
|
if (!result.ok)
|
|
2153
2226
|
return fail(result.message);
|
|
2154
2227
|
if (existsSync(path) && !opts.force)
|
|
@@ -2159,6 +2232,16 @@ engagement
|
|
|
2159
2232
|
writeFileSync(path, result.text);
|
|
2160
2233
|
console.log(`wrote ${path} (${result.units.length} slice${result.units.length === 1 ? "" : "s"}: ${result.units.join(", ")})`);
|
|
2161
2234
|
});
|
|
2235
|
+
/** GH #237: resolve a `<slice-key>` argument (export key, display name, or unique prefix —
|
|
2236
|
+
* docs/cli.md "Slice keys") to the canonical export key, or print the resolver's error and exit 1. */
|
|
2237
|
+
function resolveSliceOrExit(command, model, refs, input) {
|
|
2238
|
+
const resolved = resolveSliceKey(model, refs, input);
|
|
2239
|
+
if (!resolved.ok) {
|
|
2240
|
+
console.error(`${command}: ${resolved.message}`);
|
|
2241
|
+
process.exit(1);
|
|
2242
|
+
}
|
|
2243
|
+
return resolved.key;
|
|
2244
|
+
}
|
|
2162
2245
|
/** Compile `file` with the full `em validate` diagnostic set — what `engagement plan` needs for
|
|
2163
2246
|
* each slice's `--slice-ready` verdict (R34). */
|
|
2164
2247
|
function engagementModelInput(file) {
|
|
@@ -2169,7 +2252,7 @@ function engagementModelInput(file) {
|
|
|
2169
2252
|
engagement
|
|
2170
2253
|
.command("plan")
|
|
2171
2254
|
.description("the engagement's build plan: dependency levels over model.edges (loops-to excluded), and per " +
|
|
2172
|
-
"slice its readiness, branch impl/<key>, base (main
|
|
2255
|
+
"slice its readiness, branch impl/<key>, base (always main — MIL-280), and any hold")
|
|
2173
2256
|
.argument("<file>", "input .em file")
|
|
2174
2257
|
.argument("<slug>", "engagement slug")
|
|
2175
2258
|
.option("--json", "print a JSON document instead of text (same document as the MCP engagement_plan tool)")
|
|
@@ -2201,6 +2284,11 @@ engagement
|
|
|
2201
2284
|
if (!loaded.ok)
|
|
2202
2285
|
return fail(loaded.message);
|
|
2203
2286
|
const { model, refs } = compileFile(file);
|
|
2287
|
+
// GH #237: an input that resolves to no slice goes through as typed, so the engagement's own
|
|
2288
|
+
// "is not in engagement" refusal (which names what the Ledger holds) still speaks.
|
|
2289
|
+
const resolved = resolveSliceKey(model, refs, key);
|
|
2290
|
+
if (resolved.ok)
|
|
2291
|
+
key = resolved.key;
|
|
2204
2292
|
const result = applyEngagementSet({ file, model, refs, baseDir: dirname(file) }, slug, loaded.text, loaded.file, { key, ...opts });
|
|
2205
2293
|
if (!result.ok)
|
|
2206
2294
|
return fail(result.message);
|