@milehimikey/em 1.21.0 → 1.22.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 +10 -0
- package/.claude/agents/em-validator.md +1 -0
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +7 -4
- package/.claude/skills/event-modeling-conform/reference/conform.md +82 -4
- package/.claude/skills/event-modeling-design/SKILL.md +1 -1
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +1 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +5 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +14 -0
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +19 -6
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +26 -15
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/cli/api.d.ts +84 -0
- package/dist/cli/api.js +146 -15
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/bindings.d.ts +152 -0
- package/dist/cli/bindings.js +462 -0
- package/dist/cli/bindings.js.map +1 -0
- package/dist/cli/conformScope.d.ts +32 -0
- package/dist/cli/conformScope.js +68 -5
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/engagement.d.ts +24 -1
- package/dist/cli/engagement.js +50 -6
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementBrief.d.ts +127 -0
- package/dist/cli/engagementBrief.js +313 -0
- package/dist/cli/engagementBrief.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +14 -0
- package/dist/cli/engagementFile.js +54 -0
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/status.d.ts +5 -0
- package/dist/cli/status.js +1 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/upgrade.js +9 -10
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli.js +143 -11
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +8 -1
- package/dist/emit/api.js +68 -24
- package/dist/emit/api.js.map +1 -1
- package/dist/emit/apiSurfaceJson.d.ts +3 -0
- package/dist/emit/apiSurfaceJson.js +26 -0
- package/dist/emit/apiSurfaceJson.js.map +1 -0
- package/dist/emit/engagementJson.d.ts +8 -3
- package/dist/emit/engagementJson.js +31 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/mcp/server.js +91 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/model.d.ts +17 -5
- package/dist/model/model.js +18 -17
- package/dist/model/model.js.map +1 -1
- package/dist/templates.d.ts +4 -0
- package/dist/templates.js +29 -0
- 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 +10 -0
- package/plugin/agents/em-validator.md +1 -0
- package/plugin/shared/reference/em-dsl.md +19 -6
- package/plugin/shared/reference/operating-principles.md +26 -15
- package/plugin/skills/conform/SKILL.md +7 -4
- package/plugin/skills/conform/reference/conform.md +82 -4
- package/plugin/skills/design/SKILL.md +1 -1
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/engagement/reference/engagement.md +1 -0
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +5 -3
- package/plugin/skills/implement/reference/implement.md +14 -0
- package/plugin/skills/review/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -25,8 +25,9 @@ import { startLiveServer } from "./render/serve.js";
|
|
|
25
25
|
import { formatDiagnostic, hasErrors, publicViewsWithoutInModelReader, formatDiagnosticWithFile } from "./model/validate.js";
|
|
26
26
|
import { buildExport, buildSliceExport, GENERATOR_VERSION } from "./emit/json.js";
|
|
27
27
|
import { buildDocJsonExport } from "./emit/docJson.js";
|
|
28
|
-
import { generateContract, runApiCheck, formatApiCheckText } from "./cli/api.js";
|
|
28
|
+
import { generateContract, runApiCheck, formatApiCheckText, runApiSurface, formatApiSurfaceText } from "./cli/api.js";
|
|
29
29
|
import { buildApiCheckJson } from "./emit/apiCheckJson.js";
|
|
30
|
+
import { buildApiSurfaceJson } from "./emit/apiSurfaceJson.js";
|
|
30
31
|
import { buildValidateJson, buildShapeJson, buildSliceReadyJson, buildValidateListJson, collectMarkers } from "./emit/validateJson.js";
|
|
31
32
|
import { buildDiffJson } from "./emit/diffJson.js";
|
|
32
33
|
import { diffModels, formatModelDiff, hasChanges } from "./model/diff.js";
|
|
@@ -46,10 +47,11 @@ import { observeSigners, renderRoster } from "./roster/init.js";
|
|
|
46
47
|
import { rosterContextFor } from "./roster/context.js";
|
|
47
48
|
import { computeModelKey } from "./model/qualifiedRef.js";
|
|
48
49
|
import { checkRoster, formatRosterCheck, buildRosterCheckJson, grandfatheredNotice } from "./roster/check.js";
|
|
49
|
-
import { applyEngagementClose, applyEngagementSet, buildNewEngagement, buildUnitIndex, effectiveTracking, engagementMembersFor, engagementPlanFor, engagementStatusFor, formatEngagementPlanText, formatEngagementStatusText, } from "./cli/engagement.js";
|
|
50
|
+
import { applyEngagementClose, applyEngagementSet, applyEngagementSetContext, buildNewEngagement, buildUnitIndex, effectiveTracking, engagementMembersFor, engagementPlanFor, engagementStatusFor, formatEngagementPlanText, formatEngagementStatusText, } from "./cli/engagement.js";
|
|
50
51
|
import { LEDGER_STATES, engagementPath, loadEngagement, readOpenEngagements } from "./cli/engagementFile.js";
|
|
51
|
-
import { buildEngagementFoundationJson, buildEngagementPlanJson, buildEngagementStatusJson } from "./emit/engagementJson.js";
|
|
52
|
+
import { buildEngagementBriefJson, buildEngagementFoundationJson, buildEngagementPlanJson, buildEngagementStatusJson } from "./emit/engagementJson.js";
|
|
52
53
|
import { checkFoundation, formatFoundationText } from "./cli/engagementFoundation.js";
|
|
54
|
+
import { engagementBriefFor, formatEngagementBriefText } from "./cli/engagementBrief.js";
|
|
53
55
|
import { detectSliceDocCollisions } from "./catalog/modelCollisionValidate.js";
|
|
54
56
|
import { checkLedger, readLedgerWaiverTrailers, applyLedgerWaivers } from "./cli/ledgerCheck.js";
|
|
55
57
|
import { planMigration, verifyMigration } from "./cli/migrateReactionShape.js";
|
|
@@ -98,9 +100,10 @@ import { runDefer, runDeferFromIssue } from "./cli/defer.js";
|
|
|
98
100
|
import { runSliceConform, runSliceConformPreview, writePreviewFindings, resolveFindingsForReport, findingsFilesOf, unruledAcrossFiles, } from "./cli/sliceConform.js";
|
|
99
101
|
import { checkPrdTriage, buildCheckPrdTriageJson, renderPrdTriageMarkdown, validatePrdTriage } from "./cli/prdTriage.js";
|
|
100
102
|
import { checkFindingsFile, buildCheckFindingsJson, FINDING_LOCI } from "./cli/findings.js";
|
|
103
|
+
import { runBindingsCheck, buildBindingsCheckJson, renderBindingsCheckText } from "./cli/bindings.js";
|
|
101
104
|
import { STATE_FILE_NAME, PHASES, isPhase, loadStateFile, parseState, setPhase, setConformance, setModelVersion, setCertified, setEmVersion, setReview, isValidDateString, modelPathMismatch, } from "./cli/stateFile.js";
|
|
102
105
|
import { findCertifiedVersion, latestModelVersionNumber, modelVersionDrift, runCertifyModelVersion, runModelVersionBump, } from "./cli/modelVersion.js";
|
|
103
|
-
import { STARTER_EM, starterEmFor, scaffoldReadme, scaffoldStateFile, scaffoldConstitution } from "./templates.js";
|
|
106
|
+
import { STARTER_EM, starterEmFor, scaffoldReadme, scaffoldStateFile, scaffoldConstitution, SLICES_DIR_README, CONTRACTS_DIR_README, MODEL_VERSIONS_DIR_README, CONFORMANCE_BINDINGS_DIR_README, } from "./templates.js";
|
|
104
107
|
import { kebabSlug } from "./util/slug.js";
|
|
105
108
|
import { ciWorkflowPath, conformWorkflowPath, buildCiWorkflowFile, buildConformWorkflowFile, buildCiWorkflowFileMulti, buildConformWorkflowFileMulti, ciManagedBody, ciManagedBodyMulti, conformManagedBody, conformManagedBodyMulti, ciModelsFromManifest, describeOtherModels, describeSetChange, fileBlockModels, planCiFile, refreshCiPullRequestPaths, applyCiPullRequestPathsRefresh, applyCiFile, findUnsafeCiInitArg, CI_WORKFLOW_MARKER, CONFORM_WORKFLOW_MARKER, } from "./cli/ciInit.js";
|
|
106
109
|
import { resolveRepoRoot, detectUpgrade, applyUpgrade, checkUpgrade } from "./cli/upgrade.js";
|
|
@@ -162,7 +165,10 @@ program
|
|
|
162
165
|
.command("scaffold")
|
|
163
166
|
.description("scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md, and " +
|
|
164
167
|
"constitution.md (the implementation constitution — skipped, with a note, in a spec-kit " +
|
|
165
|
-
"project, where `.specify/memory/constitution.md` IS that document) " +
|
|
168
|
+
"project, where `.specify/memory/constitution.md` IS that document), plus the standard " +
|
|
169
|
+
"slices/, contracts/, model-versions/ and conformance/bindings/ directories (each seeded " +
|
|
170
|
+
"with a one-line README naming what lands there and which command writes it, instead of " +
|
|
171
|
+
"a .gitkeep) " +
|
|
166
172
|
"(see docs/cli.md — for just a starter .em, use `em init`; for a multi-model project, " +
|
|
167
173
|
"pass --under to nest it under a shared parent directory)")
|
|
168
174
|
.argument("<name>", "model display name — kebab-cased for the directory and file names, used as-is for titles/prose")
|
|
@@ -186,6 +192,20 @@ program
|
|
|
186
192
|
writeFileSync(join(dirPath, `${slugName}.em`), starterEmFor(name));
|
|
187
193
|
writeFileSync(join(dirPath, "README.md"), scaffoldReadme(name, slugName));
|
|
188
194
|
writeFileSync(join(dirPath, STATE_FILE_NAME), scaffoldStateFile(name, slugName, today, PKG_VERSION));
|
|
195
|
+
// GH #453: the full standardized model-directory family, not just the four files above —
|
|
196
|
+
// every doc-aware command eventually writes into one of these. Git doesn't track empty
|
|
197
|
+
// directories, so each gets a one-line README (never a .gitkeep) naming what lands there
|
|
198
|
+
// and which command writes it.
|
|
199
|
+
await mkdir(join(dirPath, "slices"), { recursive: true });
|
|
200
|
+
writeFileSync(join(dirPath, "slices", "README.md"), SLICES_DIR_README);
|
|
201
|
+
await mkdir(join(dirPath, "contracts"), { recursive: true });
|
|
202
|
+
writeFileSync(join(dirPath, "contracts", "README.md"), CONTRACTS_DIR_README);
|
|
203
|
+
await mkdir(join(dirPath, "model-versions"), { recursive: true });
|
|
204
|
+
writeFileSync(join(dirPath, "model-versions", "README.md"), MODEL_VERSIONS_DIR_README);
|
|
205
|
+
// conformance/bindings/ (GH #449) — creating it also creates its conformance/ parent, which
|
|
206
|
+
// otherwise only appears once the conform phase writes its first report/findings file.
|
|
207
|
+
await mkdir(join(dirPath, "conformance", "bindings"), { recursive: true });
|
|
208
|
+
writeFileSync(join(dirPath, "conformance", "bindings", "README.md"), CONFORMANCE_BINDINGS_DIR_README);
|
|
189
209
|
// MIL-202: the implementation constitution. One document per project, never two — in a
|
|
190
210
|
// spec-kit project `.specify/memory/constitution.md` already IS that slot, so em defers to it
|
|
191
211
|
// and writes nothing rather than seeding a second, competing copy beside the model. Same
|
|
@@ -442,6 +462,25 @@ api
|
|
|
442
462
|
if (!outcome.report.current)
|
|
443
463
|
process.exitCode = 1;
|
|
444
464
|
});
|
|
465
|
+
api
|
|
466
|
+
.command("surface")
|
|
467
|
+
.description("the model's public commands, events, views and reachable declared types as a versioned " +
|
|
468
|
+
"JSON document for companion tools (GH #448, see docs/cli.md)")
|
|
469
|
+
.argument("<file>", "input .em file")
|
|
470
|
+
.option("--json", "print a JSON document instead of the text listing (see docs/cli.md)")
|
|
471
|
+
.action((file, opts) => {
|
|
472
|
+
const outcome = runApiSurface(file);
|
|
473
|
+
printDiagnostics(outcome.diagnostics);
|
|
474
|
+
if (!outcome.ok) {
|
|
475
|
+
console.error(outcome.message);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
if (opts.json)
|
|
479
|
+
process.stdout.write(buildApiSurfaceJson(outcome.report) + "\n");
|
|
480
|
+
else
|
|
481
|
+
for (const line of formatApiSurfaceText(outcome.report))
|
|
482
|
+
console.log(line);
|
|
483
|
+
});
|
|
445
484
|
program
|
|
446
485
|
.command("typespec")
|
|
447
486
|
.description("DEPRECATED alias of `em api generate --stdout` (use em api generate)")
|
|
@@ -1739,6 +1778,44 @@ conformFindings
|
|
|
1739
1778
|
if (!result.ok)
|
|
1740
1779
|
process.exit(1);
|
|
1741
1780
|
});
|
|
1781
|
+
const conform = program.command("conform").description("file-contract conformance checks (advisory only, GH #449 — never wired into validate/--slice-ready)");
|
|
1782
|
+
const conformBindings = conform.command("bindings").description("per-slice bindings manifests (conformance/bindings/<slice-key>.yaml, see docs/bindings.md)");
|
|
1783
|
+
conformBindings
|
|
1784
|
+
.command("check")
|
|
1785
|
+
.description("verify every per-slice bindings manifest (conformance/bindings/<slice-key>.yaml, see " +
|
|
1786
|
+
"docs/bindings.md): each bound element's `ref` resolves against the compiled model, and " +
|
|
1787
|
+
"each bound `file` exists under --repo and contains its `identifier` as a plain substring " +
|
|
1788
|
+
"(grep-level only, never code-parsed — language-agnostic). Distinct failure kinds: an " +
|
|
1789
|
+
"unknown `--slice` key, a missing bindings file, malformed YAML, an unknown slice named " +
|
|
1790
|
+
"inside a file, and an unknown element ref. Advisory only — exit code stays 0 unless " +
|
|
1791
|
+
"--exit-code is given and a binding failed (the opt-in CI gate, same posture as `em refs " +
|
|
1792
|
+
"--reachable-from`).")
|
|
1793
|
+
.argument("<file>", "input .em file")
|
|
1794
|
+
.requiredOption("--repo <path>", "path to the target codebase (bound `file`s resolve relative to it; never required to be a git repository)")
|
|
1795
|
+
.option("--slice <key>", "check only this slice's bindings file (default: every conformance/bindings/*.yaml)")
|
|
1796
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1797
|
+
.option("--exit-code", "exit 1 if any binding failed (git-diff convention) — the opt-in CI gate")
|
|
1798
|
+
.action((file, opts) => {
|
|
1799
|
+
const { model, diagnostics, refs } = compileFile(file);
|
|
1800
|
+
printDiagnostics(diagnostics);
|
|
1801
|
+
if (hasErrors(diagnostics)) {
|
|
1802
|
+
console.error("em conform bindings check: fix the model's errors above first");
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
const sliceKey = opts.slice !== undefined ? resolveSliceOrExit("em conform bindings check", model, refs, opts.slice) : undefined;
|
|
1806
|
+
const result = runBindingsCheck(refs, dirname(file), opts.repo, sliceKey);
|
|
1807
|
+
if (opts.json) {
|
|
1808
|
+
console.log(buildBindingsCheckJson(result));
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
console.log(renderBindingsCheckText(result));
|
|
1812
|
+
}
|
|
1813
|
+
// Same rationale as `em diff`/`em api check`'s --exit-code: set the code rather than
|
|
1814
|
+
// process.exit() so a large --json document piped downstream isn't truncated, and so this
|
|
1815
|
+
// advisory check NEVER fails a run by default (GH #449 — opt-in CI gate only).
|
|
1816
|
+
if (opts.exitCode && !result.ok)
|
|
1817
|
+
process.exitCode = 1;
|
|
1818
|
+
});
|
|
1742
1819
|
const discover = program.command("discover").description("checks for the discover skill's records (GH #295)");
|
|
1743
1820
|
discover
|
|
1744
1821
|
.command("triage-check")
|
|
@@ -2462,7 +2539,7 @@ program
|
|
|
2462
2539
|
// merged `sliceFacts` above (which spans every input file when several are given).
|
|
2463
2540
|
const conformance = compiled.map(({ file }) => {
|
|
2464
2541
|
const facts = factsByFile.get(file) ?? [];
|
|
2465
|
-
const sliceDocFacts = facts.map((f) => ({ key: f.key, status: f.rawStatus, implementedIn: f.implementedIn, shipped: f.shipped }));
|
|
2542
|
+
const sliceDocFacts = facts.map((f) => ({ key: f.key, status: f.rawStatus, implementedIn: f.implementedIn, shipped: f.shipped, file: f.sliceFile }));
|
|
2466
2543
|
return resolveConformanceEntry(file, opts.repo, sliceDocFacts);
|
|
2467
2544
|
});
|
|
2468
2545
|
// MIL-218: one model-version entry per input file.
|
|
@@ -2724,6 +2801,7 @@ engagement
|
|
|
2724
2801
|
.option("--by <name>", "who created the engagement (createdBy)")
|
|
2725
2802
|
.option("--tracking <key>", "an issue-tracker key or URL the slice PRs cite (a slice doc's own tracking: wins)")
|
|
2726
2803
|
.option("--pr-title <template>", "PR title template for the slice PRs; {key} = tracking key, {slice} = slice name, {sliceKey} = slice key (default \"{key}: {slice}\", or \"{slice}\" without a key)")
|
|
2804
|
+
.option("--context-doc <path>", "repo-relative path to a document implementers need beyond the slice doc — the constitution, an NFR doc, an ADR (repeatable; GH #455)", (value, previous) => [...previous, value], [])
|
|
2727
2805
|
.option("--force", "overwrite an existing engagement file with this slug")
|
|
2728
2806
|
.action((file, slug, opts) => {
|
|
2729
2807
|
const fail = (m) => {
|
|
@@ -2743,7 +2821,7 @@ engagement
|
|
|
2743
2821
|
const parallel = /^\d+$/.test(opts.parallel) ? Number(opts.parallel) : NaN;
|
|
2744
2822
|
const { model, refs } = compileFile(file);
|
|
2745
2823
|
const path = engagementPath(file, slug);
|
|
2746
|
-
const result = buildNewEngagement({ file, model, refs, baseDir: dirname(file) }, { slug, selector, parallel, createdBy: opts.by ?? null, tracking: opts.tracking ?? null, prTitle: opts.prTitle ?? null, created: localIsoDate() });
|
|
2824
|
+
const result = buildNewEngagement({ file, model, refs, baseDir: dirname(file) }, { slug, selector, parallel, createdBy: opts.by ?? null, tracking: opts.tracking ?? null, prTitle: opts.prTitle ?? null, context: opts.contextDoc, created: localIsoDate() });
|
|
2747
2825
|
if (!result.ok)
|
|
2748
2826
|
return fail(result.message);
|
|
2749
2827
|
if (existsSync(path) && !opts.force)
|
|
@@ -2821,22 +2899,76 @@ engagement
|
|
|
2821
2899
|
if (!check.ok)
|
|
2822
2900
|
process.exitCode = 1;
|
|
2823
2901
|
});
|
|
2902
|
+
engagement
|
|
2903
|
+
.command("brief")
|
|
2904
|
+
.description("GH #454: a deterministic, self-contained per-slice work brief for implementers em does not " +
|
|
2905
|
+
"control (a ticket-driven cloud agent) — the bound slice doc verbatim (or the model's own " +
|
|
2906
|
+
"elements when there is none), model context from the edge list, the public-contract " +
|
|
2907
|
+
"surface, a definition-of-done (bindings manifest skeleton + conform check + isolation + PR " +
|
|
2908
|
+
"title), and the engagement's context: documents. One brief per engagement slice unless " +
|
|
2909
|
+
"--slice narrows it")
|
|
2910
|
+
.argument("<file>", "input .em file")
|
|
2911
|
+
.argument("<slug>", "engagement slug")
|
|
2912
|
+
.option("--slice <key>", "only this slice's brief (export key, display name, or unique prefix)")
|
|
2913
|
+
.option("--inline-context", "embed each context document's full text instead of just its path")
|
|
2914
|
+
.option("--json", "print a JSON document instead of text (same document as the MCP engagement_brief tool)")
|
|
2915
|
+
.action((file, slug, opts) => {
|
|
2916
|
+
const { model, refs } = compileFile(file);
|
|
2917
|
+
// GH #237: an input that resolves to no slice goes through as typed, so the engagement's own
|
|
2918
|
+
// "is not in engagement" refusal (which names what the Ledger holds) still speaks.
|
|
2919
|
+
let sliceKey = null;
|
|
2920
|
+
if (opts.slice !== undefined) {
|
|
2921
|
+
const resolved = resolveSliceKey(model, refs, opts.slice);
|
|
2922
|
+
sliceKey = resolved.ok ? resolved.key : opts.slice;
|
|
2923
|
+
}
|
|
2924
|
+
const result = engagementBriefFor({ file, model, refs, baseDir: dirname(file) }, slug, { sliceKey, inlineContext: opts.inlineContext === true });
|
|
2925
|
+
if (!result.ok) {
|
|
2926
|
+
console.error(`em engagement brief: ${result.message}`);
|
|
2927
|
+
process.exit(1);
|
|
2928
|
+
}
|
|
2929
|
+
process.stdout.write((opts.json ? buildEngagementBriefJson(result.brief) : formatEngagementBriefText(result.brief)) + "\n");
|
|
2930
|
+
});
|
|
2824
2931
|
engagement
|
|
2825
2932
|
.command("set")
|
|
2826
2933
|
.description("the only write path to the Ledger: set one slice's state (and optionally branch/base/pr); " +
|
|
2827
|
-
"idempotent; merged is terminal; --state held records a human hold"
|
|
2934
|
+
"idempotent; merged is terminal; --state held records a human hold. Or, on its own (no slice " +
|
|
2935
|
+
"key), --context-doc replaces the engagement-level context: list (GH #455)")
|
|
2828
2936
|
.argument("<file>", "input .em file")
|
|
2829
2937
|
.argument("<slug>", "engagement slug")
|
|
2830
|
-
.argument("
|
|
2831
|
-
.
|
|
2938
|
+
.argument("[key]", "slice export key (omit when using --context-doc alone)")
|
|
2939
|
+
.option("--state <state>", `one of: ${LEDGER_STATES.join(" | ")}`)
|
|
2832
2940
|
.option("--branch <name>", "the slice's branch")
|
|
2833
2941
|
.option("--base <name>", "the branch the slice's PR targets")
|
|
2834
2942
|
.option("--pr <url>", "the slice's PR URL")
|
|
2943
|
+
.option("--context-doc <path>", "repo-relative path to a document implementers need beyond the slice doc (repeatable; replaces the whole engagement-level context: list; GH #455)", (value, previous) => [...previous, value], [])
|
|
2835
2944
|
.action((file, slug, key, opts) => {
|
|
2836
2945
|
const fail = (m) => {
|
|
2837
2946
|
console.error(`em engagement set: ${m}`);
|
|
2838
2947
|
process.exit(1);
|
|
2839
2948
|
};
|
|
2949
|
+
const sliceFields = opts.state !== undefined || opts.branch !== undefined || opts.base !== undefined || opts.pr !== undefined;
|
|
2950
|
+
if (opts.contextDoc.length > 0) {
|
|
2951
|
+
if (key !== undefined || sliceFields) {
|
|
2952
|
+
return fail("--context-doc sets the engagement-level context: list on its own — pass it without a slice key or --state/--branch/--base/--pr");
|
|
2953
|
+
}
|
|
2954
|
+
const loaded = loadEngagement(file, slug);
|
|
2955
|
+
if (!loaded.ok)
|
|
2956
|
+
return fail(loaded.message);
|
|
2957
|
+
const result = applyEngagementSetContext(slug, loaded.text, loaded.file, opts.contextDoc);
|
|
2958
|
+
if (!result.ok)
|
|
2959
|
+
return fail(result.message);
|
|
2960
|
+
if (!result.changed) {
|
|
2961
|
+
console.log(`no change: context is already [${result.context.join(", ")}] (${loaded.path})`);
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
writeFileSync(loaded.path, result.text);
|
|
2965
|
+
console.log(`set context: ${result.context.length} path${result.context.length === 1 ? "" : "s"} (${loaded.path})`);
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
if (!key)
|
|
2969
|
+
return fail("a slice export key is required (or pass --context-doc to set the engagement-level context: list)");
|
|
2970
|
+
if (!opts.state)
|
|
2971
|
+
return fail("--state is required when setting a slice");
|
|
2840
2972
|
const loaded = loadEngagement(file, slug);
|
|
2841
2973
|
if (!loaded.ok)
|
|
2842
2974
|
return fail(loaded.message);
|
|
@@ -2846,7 +2978,7 @@ engagement
|
|
|
2846
2978
|
const resolved = resolveSliceKey(model, refs, key);
|
|
2847
2979
|
if (resolved.ok)
|
|
2848
2980
|
key = resolved.key;
|
|
2849
|
-
const result = applyEngagementSet({ file, model, refs, baseDir: dirname(file) }, slug, loaded.text, loaded.file, { key,
|
|
2981
|
+
const result = applyEngagementSet({ file, model, refs, baseDir: dirname(file) }, slug, loaded.text, loaded.file, { key, state: opts.state, branch: opts.branch, base: opts.base, pr: opts.pr });
|
|
2850
2982
|
if (!result.ok)
|
|
2851
2983
|
return fail(result.message);
|
|
2852
2984
|
if (!result.changed) {
|