@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.
Files changed (73) hide show
  1. package/.claude/agents/em-implementer.md +10 -0
  2. package/.claude/agents/em-validator.md +1 -0
  3. package/.claude/skills/event-modeling/SKILL.md +1 -1
  4. package/.claude/skills/event-modeling-conform/SKILL.md +7 -4
  5. package/.claude/skills/event-modeling-conform/reference/conform.md +82 -4
  6. package/.claude/skills/event-modeling-design/SKILL.md +1 -1
  7. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  8. package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
  9. package/.claude/skills/event-modeling-engagement/reference/engagement.md +1 -0
  10. package/.claude/skills/event-modeling-implement/SKILL.md +5 -3
  11. package/.claude/skills/event-modeling-implement/reference/implement.md +14 -0
  12. package/.claude/skills/event-modeling-review/SKILL.md +1 -1
  13. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +19 -6
  14. package/.claude/skills/event-modeling-shared/reference/operating-principles.md +26 -15
  15. package/.claude-plugin/marketplace.json +2 -2
  16. package/dist/cli/api.d.ts +84 -0
  17. package/dist/cli/api.js +146 -15
  18. package/dist/cli/api.js.map +1 -1
  19. package/dist/cli/bindings.d.ts +152 -0
  20. package/dist/cli/bindings.js +462 -0
  21. package/dist/cli/bindings.js.map +1 -0
  22. package/dist/cli/conformScope.d.ts +32 -0
  23. package/dist/cli/conformScope.js +68 -5
  24. package/dist/cli/conformScope.js.map +1 -1
  25. package/dist/cli/engagement.d.ts +24 -1
  26. package/dist/cli/engagement.js +50 -6
  27. package/dist/cli/engagement.js.map +1 -1
  28. package/dist/cli/engagementBrief.d.ts +127 -0
  29. package/dist/cli/engagementBrief.js +313 -0
  30. package/dist/cli/engagementBrief.js.map +1 -0
  31. package/dist/cli/engagementFile.d.ts +14 -0
  32. package/dist/cli/engagementFile.js +54 -0
  33. package/dist/cli/engagementFile.js.map +1 -1
  34. package/dist/cli/status.d.ts +5 -0
  35. package/dist/cli/status.js +1 -0
  36. package/dist/cli/status.js.map +1 -1
  37. package/dist/cli/upgrade.js +9 -10
  38. package/dist/cli/upgrade.js.map +1 -1
  39. package/dist/cli.js +143 -11
  40. package/dist/cli.js.map +1 -1
  41. package/dist/emit/api.d.ts +8 -1
  42. package/dist/emit/api.js +68 -24
  43. package/dist/emit/api.js.map +1 -1
  44. package/dist/emit/apiSurfaceJson.d.ts +3 -0
  45. package/dist/emit/apiSurfaceJson.js +26 -0
  46. package/dist/emit/apiSurfaceJson.js.map +1 -0
  47. package/dist/emit/engagementJson.d.ts +8 -3
  48. package/dist/emit/engagementJson.js +31 -4
  49. package/dist/emit/engagementJson.js.map +1 -1
  50. package/dist/mcp/server.js +91 -3
  51. package/dist/mcp/server.js.map +1 -1
  52. package/dist/model/model.d.ts +17 -5
  53. package/dist/model/model.js +18 -17
  54. package/dist/model/model.js.map +1 -1
  55. package/dist/templates.d.ts +4 -0
  56. package/dist/templates.js +29 -0
  57. package/dist/templates.js.map +1 -1
  58. package/package.json +1 -1
  59. package/plugin/.claude-plugin/plugin.json +2 -2
  60. package/plugin/agents/em-implementer.md +10 -0
  61. package/plugin/agents/em-validator.md +1 -0
  62. package/plugin/shared/reference/em-dsl.md +19 -6
  63. package/plugin/shared/reference/operating-principles.md +26 -15
  64. package/plugin/skills/conform/SKILL.md +7 -4
  65. package/plugin/skills/conform/reference/conform.md +82 -4
  66. package/plugin/skills/design/SKILL.md +1 -1
  67. package/plugin/skills/discover/SKILL.md +1 -1
  68. package/plugin/skills/engagement/SKILL.md +1 -1
  69. package/plugin/skills/engagement/reference/engagement.md +1 -0
  70. package/plugin/skills/event-modeling/SKILL.md +1 -1
  71. package/plugin/skills/implement/SKILL.md +5 -3
  72. package/plugin/skills/implement/reference/implement.md +14 -0
  73. 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("<key>", "slice export key")
2831
- .requiredOption("--state <state>", `one of: ${LEDGER_STATES.join(" | ")}`)
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, ...opts });
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) {