@danmoisan/drm-copilot-mcp 1.1.3 → 1.1.5

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 (35) hide show
  1. package/out/mcp-server.js +286 -10
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +8 -10
  4. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +477 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +120 -12
  6. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +130 -33
  7. package/resources/claude-customizations/.claude/rules/benchmark-baselines.md +7 -0
  8. package/resources/claude-customizations/.claude/rules/ci-workflows.md +6 -0
  9. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +24 -0
  10. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
  11. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +143 -2
  12. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +9 -0
  13. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +27 -10
  14. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +10 -0
  15. package/resources/claude-customizations/config/orchestration-routing.json +1 -0
  16. package/resources/claude-customizations/pack-manifests/core.json +1 -0
  17. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +10 -0
  18. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c1.toml +22 -0
  19. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c2.toml +22 -0
  20. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c3-elevated.toml +22 -0
  21. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c3.toml +22 -0
  22. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward-c4.toml +22 -0
  23. package/resources/codex-and-agents-customizations/.codex/agents/commit-steward.toml +2 -0
  24. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +7 -0
  25. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +7 -0
  26. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +7 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +7 -0
  28. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +7 -0
  29. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +7 -0
  30. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  31. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +477 -0
  32. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +125 -12
  33. package/resources/codex-and-agents-customizations/pack-manifests/core.json +8 -1
  34. package/resources/config/orchestration-routing.json +1 -0
  35. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +32 -2
package/out/mcp-server.js CHANGED
@@ -2671,18 +2671,18 @@ var require_validate = __commonJS({
2671
2671
  const { schemaCode } = this;
2672
2672
  this.fail((0, codegen_1._)`${schemaCode} !== undefined && (${(0, codegen_1.or)(this.invalid$data(), condition)})`);
2673
2673
  }
2674
- error(append, errorParams, errorPaths) {
2674
+ error(append2, errorParams, errorPaths) {
2675
2675
  if (errorParams) {
2676
2676
  this.setParams(errorParams);
2677
- this._error(append, errorPaths);
2677
+ this._error(append2, errorPaths);
2678
2678
  this.setParams({});
2679
2679
  return;
2680
2680
  }
2681
- this._error(append, errorPaths);
2681
+ this._error(append2, errorPaths);
2682
2682
  }
2683
- _error(append, errorPaths) {
2683
+ _error(append2, errorPaths) {
2684
2684
  ;
2685
- (append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths);
2685
+ (append2 ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths);
2686
2686
  }
2687
2687
  $dataError() {
2688
2688
  (0, errors_1.reportError)(this, this.def.$dataError || errors_1.keyword$DataError);
@@ -23551,6 +23551,7 @@ var GENERATED_AGENT_FAMILIES = /* @__PURE__ */ new Set([
23551
23551
  "atomic-planner",
23552
23552
  "atomic-executor",
23553
23553
  "feature-reviewer",
23554
+ "commit-steward",
23554
23555
  "task-researcher",
23555
23556
  "prd-feature",
23556
23557
  "pr-author",
@@ -26957,7 +26958,14 @@ function classifyKind(argv) {
26957
26958
  return "other";
26958
26959
  }
26959
26960
  function splitLines2(text) {
26960
- return text.split(/\r\n|\n|\r/);
26961
+ if (text === "") {
26962
+ return [];
26963
+ }
26964
+ const lines = text.split(/\r\n|\n|\r/);
26965
+ if (lines[lines.length - 1] === "") {
26966
+ lines.pop();
26967
+ }
26968
+ return lines;
26961
26969
  }
26962
26970
  function inlineSpans(line) {
26963
26971
  const spans = [];
@@ -26979,34 +26987,63 @@ function appendCommand(commands, taskId, sourceLine, rawSpan) {
26979
26987
  sourceLine,
26980
26988
  rawSpan,
26981
26989
  argv,
26982
- kind: classifyKind(argv)
26990
+ kind: classifyKind(argv),
26991
+ taskText: ""
26983
26992
  });
26984
26993
  }
26994
+ function closeWindow(commands, windowStart, windowLines) {
26995
+ if (windowStart >= commands.length) {
26996
+ return;
26997
+ }
26998
+ const taskText = windowLines.join("\n");
26999
+ for (let index = windowStart; index < commands.length; index += 1) {
27000
+ const record2 = commands[index];
27001
+ if (record2 === void 0) {
27002
+ continue;
27003
+ }
27004
+ commands[index] = { ...record2, taskText };
27005
+ }
27006
+ }
26985
27007
  function extractPlanCommands(text) {
26986
27008
  const commands = [];
26987
27009
  let currentTask = null;
26988
27010
  let inFence = false;
27011
+ let windowLines = [];
27012
+ let windowStart = 0;
26989
27013
  const lines = splitLines2(text);
26990
27014
  for (let index = 0; index < lines.length; index += 1) {
26991
27015
  const line = lines[index] ?? "";
26992
27016
  const lineNumber = index + 1;
26993
27017
  if (FENCE_RE.test(line)) {
26994
27018
  inFence = !inFence;
27019
+ if (currentTask !== null) {
27020
+ windowLines.push(line);
27021
+ }
26995
27022
  continue;
26996
27023
  }
26997
27024
  if (inFence) {
27025
+ if (currentTask === null) {
27026
+ continue;
27027
+ }
27028
+ windowLines.push(line);
26998
27029
  const fenced = line.trim();
26999
- if (currentTask !== null && fenced !== "") {
27030
+ if (fenced !== "") {
27000
27031
  appendCommand(commands, currentTask, lineNumber, fenced);
27001
27032
  }
27002
27033
  continue;
27003
27034
  }
27004
27035
  if (PLAN_GATE_HEADING_PATTERN.test(line)) {
27036
+ closeWindow(commands, windowStart, windowLines);
27037
+ windowStart = commands.length;
27038
+ windowLines = [];
27005
27039
  currentTask = null;
27006
27040
  continue;
27007
27041
  }
27008
27042
  const taskMatch = PLAN_GATE_TASK_PATTERN.exec(line);
27009
27043
  if (taskMatch !== null) {
27044
+ closeWindow(commands, windowStart, windowLines);
27045
+ windowStart = commands.length;
27046
+ windowLines = [];
27010
27047
  const phase = taskMatch.groups?.["phase"] ?? "";
27011
27048
  const task = taskMatch.groups?.["task"] ?? "";
27012
27049
  currentTask = `P${phase}-T${task}`;
@@ -27014,10 +27051,12 @@ function extractPlanCommands(text) {
27014
27051
  if (currentTask === null) {
27015
27052
  continue;
27016
27053
  }
27054
+ windowLines.push(line);
27017
27055
  for (const span of inlineSpans(line)) {
27018
27056
  appendCommand(commands, currentTask, lineNumber, span);
27019
27057
  }
27020
27058
  }
27059
+ closeWindow(commands, windowStart, windowLines);
27021
27060
  return commands;
27022
27061
  }
27023
27062
 
@@ -27167,6 +27206,242 @@ function hasCrossLinePresence(context, literal2) {
27167
27206
  return false;
27168
27207
  }
27169
27208
 
27209
+ // ../../extensions/drm-copilot/src/lib/validate/plan-gate-observability.ts
27210
+ var G7_SEVERITY = "warning";
27211
+ var G8_SEVERITY = "warning";
27212
+ var G8B_SEVERITY = "warning";
27213
+ var G9_SEVERITY = "warning";
27214
+ var WRITE_MODE_REGISTER = [
27215
+ {
27216
+ name: "black-write",
27217
+ shapes: [{ words: ["black"] }],
27218
+ excludes: ["--check", "--diff"],
27219
+ markers: ["reformatted", "left unchanged", "unchanged"]
27220
+ },
27221
+ {
27222
+ name: "ruff-fix",
27223
+ shapes: [{ words: ["ruff", "check"] }],
27224
+ excludes: ["--no-fix"],
27225
+ markers: ["Fixed", "All checks passed", "fixes applied"]
27226
+ },
27227
+ {
27228
+ name: "prettier-write",
27229
+ shapes: [
27230
+ { words: ["prettier"], requires: ["--write"] },
27231
+ { words: ["npm", "run", "format"] }
27232
+ ],
27233
+ excludes: [],
27234
+ markers: ["(unchanged)", "unchanged", "rewrote"]
27235
+ },
27236
+ {
27237
+ name: "poshqc-format",
27238
+ shapes: [{ suffix: "run_poshqc_format" }],
27239
+ excludes: [],
27240
+ markers: ["formatted", "unchanged"]
27241
+ },
27242
+ {
27243
+ name: "poshqc-analyze-autofix",
27244
+ shapes: [{ suffix: "run_poshqc_analyze_autofix" }],
27245
+ excludes: [],
27246
+ markers: ["autofix", "Fixed", "unchanged"]
27247
+ },
27248
+ {
27249
+ name: "poshqc-suite",
27250
+ shapes: [{ suffix: "run_poshqc_suite" }],
27251
+ excludes: [],
27252
+ markers: ["formatted", "unchanged"]
27253
+ }
27254
+ ];
27255
+ var EXECUTABLE_SCAN_LIMIT2 = 4;
27256
+ var PATHSPEC_SEPARATOR = "--";
27257
+ var INDEX_FLAGS = ["--cached", "--staged"];
27258
+ var NAME_LISTING_FLAGS = ["--name-only", "--name-status"];
27259
+ var GIT_DIFF_SPAN = "git diff";
27260
+ var GIT_STATUS_SPAN = "git status";
27261
+ var GIT_ADD_SPAN = "git add";
27262
+ var GIT_PORCELAIN_SPAN = "git status --porcelain";
27263
+ var PROJECT_CONFIG_PATH = "pyproject.toml";
27264
+ var TERMINAL_REPORTER_PREFIX = "--cov-report=term";
27265
+ var FAIL_UNDER_PREFIX = "--cov-fail-under";
27266
+ var ADDOPTS_PATTERNS = [
27267
+ /addopts[ \t]*=[ \t]*"([^"]*)"/,
27268
+ /addopts[ \t]*=[ \t]*'([^']*)'/
27269
+ ];
27270
+ function append(report, severity, finding) {
27271
+ const channel = severity === BLOCKING_CHANNEL ? report.blocking : report.warnings;
27272
+ channel.push(finding);
27273
+ }
27274
+ function executablePositions(argv) {
27275
+ const limit = Math.min(argv.length, EXECUTABLE_SCAN_LIMIT2);
27276
+ const positions = [];
27277
+ for (let index = 0; index < limit; index += 1) {
27278
+ if (index === 0 || !(argv[index - 1] ?? "").startsWith("-")) {
27279
+ positions.push(index);
27280
+ }
27281
+ }
27282
+ return positions;
27283
+ }
27284
+ function shapeMatches(argv, shape) {
27285
+ const requires = shape.requires ?? [];
27286
+ if (requires.some((required2) => !argv.includes(required2))) {
27287
+ return false;
27288
+ }
27289
+ const words2 = shape.words ?? [];
27290
+ for (const index of executablePositions(argv)) {
27291
+ if (shape.suffix !== void 0 && shape.suffix !== "") {
27292
+ if ((argv[index] ?? "").endsWith(shape.suffix)) {
27293
+ return true;
27294
+ }
27295
+ continue;
27296
+ }
27297
+ const end = index + words2.length;
27298
+ if (end <= argv.length && words2.every((word, offset) => argv[index + offset] === word)) {
27299
+ return true;
27300
+ }
27301
+ }
27302
+ return false;
27303
+ }
27304
+ function matchingEntry(argv) {
27305
+ for (const entry of WRITE_MODE_REGISTER) {
27306
+ if (entry.excludes.some((excluded) => argv.includes(excluded))) {
27307
+ continue;
27308
+ }
27309
+ if (entry.shapes.some((shape) => shapeMatches(argv, shape))) {
27310
+ return entry;
27311
+ }
27312
+ }
27313
+ return null;
27314
+ }
27315
+ function taskTextWithoutSpan(command) {
27316
+ return command.taskText.replace(command.rawSpan, " ");
27317
+ }
27318
+ function evaluateWriteMode(report, command) {
27319
+ const entry = matchingEntry(command.argv);
27320
+ if (entry === null) {
27321
+ return;
27322
+ }
27323
+ const remainder = taskTextWithoutSpan(command);
27324
+ if (entry.markers.some((marker) => remainder.includes(marker))) {
27325
+ return;
27326
+ }
27327
+ append(
27328
+ report,
27329
+ G7_SEVERITY,
27330
+ `[${command.taskId}] write-mode command \`${command.rawSpan}\` rewrites tracked source and exits 0 after rewriting; the attributed task text carries none of its observation markers. Record an observation beyond the exit code.`
27331
+ );
27332
+ }
27333
+ function gitDiffIndex(argv) {
27334
+ const limit = Math.min(argv.length, EXECUTABLE_SCAN_LIMIT2);
27335
+ for (let index = 0; index < limit; index += 1) {
27336
+ if (argv[index] === "git" && argv[index + 1] === "diff") {
27337
+ return index + 1;
27338
+ }
27339
+ }
27340
+ return null;
27341
+ }
27342
+ function diffOperands(argv, diffIndex) {
27343
+ const operands = [];
27344
+ for (const word of argv.slice(diffIndex + 1)) {
27345
+ if (word === PATHSPEC_SEPARATOR) {
27346
+ break;
27347
+ }
27348
+ operands.push(word);
27349
+ }
27350
+ return operands;
27351
+ }
27352
+ function carriesPairingCompanion(command) {
27353
+ const remainder = taskTextWithoutSpan(command);
27354
+ return remainder.includes(GIT_DIFF_SPAN) || remainder.includes(GIT_STATUS_SPAN);
27355
+ }
27356
+ function carriesListingCompanion(command) {
27357
+ const remainder = taskTextWithoutSpan(command);
27358
+ return remainder.includes(GIT_ADD_SPAN) || remainder.includes(GIT_PORCELAIN_SPAN);
27359
+ }
27360
+ function evaluateGitDiff(report, command) {
27361
+ const argv = command.argv;
27362
+ const diffIndex = gitDiffIndex(argv);
27363
+ if (diffIndex === null) {
27364
+ return;
27365
+ }
27366
+ const operands = diffOperands(argv, diffIndex);
27367
+ const hasRefOperand = operands.some((word) => !word.startsWith("-"));
27368
+ const hasIndexFlag = INDEX_FLAGS.some((flag) => argv.includes(flag));
27369
+ if (!hasRefOperand && !hasIndexFlag) {
27370
+ if (!carriesPairingCompanion(command)) {
27371
+ append(
27372
+ report,
27373
+ G8_SEVERITY,
27374
+ `[${command.taskId}] git diff span \`${command.rawSpan}\` carries no ref operand and no --cached flag; it compares the worktree against the index and passes vacuously once the change is committed. Anchor the diff to a ref.`
27375
+ );
27376
+ }
27377
+ return;
27378
+ }
27379
+ if (hasRefOperand && NAME_LISTING_FLAGS.some((flag) => argv.includes(flag))) {
27380
+ if (!carriesListingCompanion(command)) {
27381
+ append(
27382
+ report,
27383
+ G8B_SEVERITY,
27384
+ `[${command.taskId}] name-listing diff \`${command.rawSpan}\` never reports an untracked file, and the attributed task text carries neither a staging span nor a porcelain-status span; a path the plan creates is invisible to it. Add a staging or porcelain-status companion.`
27385
+ );
27386
+ }
27387
+ }
27388
+ }
27389
+ function projectAddopts(context) {
27390
+ const text = context.git.readTrackedText(PROJECT_CONFIG_PATH);
27391
+ if (text === "") {
27392
+ return null;
27393
+ }
27394
+ for (const pattern of ADDOPTS_PATTERNS) {
27395
+ const match = pattern.exec(text);
27396
+ if (match !== null) {
27397
+ return match[1] ?? "";
27398
+ }
27399
+ }
27400
+ return "";
27401
+ }
27402
+ function collectCoverageReporter(pending, command, addopts) {
27403
+ const argv = command.argv;
27404
+ if (!argv.some((word) => isCovFlagToken(word))) {
27405
+ return;
27406
+ }
27407
+ if (argv.some((word) => word.startsWith(TERMINAL_REPORTER_PREFIX))) {
27408
+ return;
27409
+ }
27410
+ if (argv.some((word) => word.startsWith(FAIL_UNDER_PREFIX))) {
27411
+ return;
27412
+ }
27413
+ if (addopts.includes(TERMINAL_REPORTER_PREFIX)) {
27414
+ return;
27415
+ }
27416
+ pending.push(
27417
+ `[${command.taskId}] coverage command \`${command.rawSpan}\` supplies no terminal reporter and the project addopts supplies none either, so no coverage table is printed. Add --cov-report=term-missing.`
27418
+ );
27419
+ }
27420
+ function evaluateObservabilityGates(report, commands, context) {
27421
+ for (const command of commands) {
27422
+ evaluateWriteMode(report, command);
27423
+ evaluateGitDiff(report, command);
27424
+ }
27425
+ if (context === void 0) {
27426
+ return;
27427
+ }
27428
+ const pending = [];
27429
+ try {
27430
+ const addopts = projectAddopts(context);
27431
+ if (addopts === null) {
27432
+ return;
27433
+ }
27434
+ for (const command of commands) {
27435
+ collectCoverageReporter(pending, command, addopts);
27436
+ }
27437
+ } catch {
27438
+ return;
27439
+ }
27440
+ for (const finding of pending) {
27441
+ append(report, G9_SEVERITY, finding);
27442
+ }
27443
+ }
27444
+
27170
27445
  // ../../extensions/drm-copilot/src/lib/validate/plan-gate-discrimination.ts
27171
27446
  var G5_SEVERITY = "warning";
27172
27447
  var CommandRunnerPlanGateRepository = class {
@@ -27262,6 +27537,7 @@ function evaluatePlanGates(text, context) {
27262
27537
  evaluateCovValue(report, command, cov, context);
27263
27538
  }
27264
27539
  }
27540
+ evaluateObservabilityGates(report, commands, context);
27265
27541
  if (context !== void 0) {
27266
27542
  evaluateLiteralRules(report, text, commands, context);
27267
27543
  }
@@ -31555,12 +31831,12 @@ function buildAppendixText(collected, clock) {
31555
31831
  }
31556
31832
  return appendixText;
31557
31833
  }
31558
- function writeOutput(fs10, text, outPath, append) {
31834
+ function writeOutput(fs10, text, outPath, append2) {
31559
31835
  const parent = parentDir(outPath);
31560
31836
  if (parent) {
31561
31837
  fs10.ensureDir(parent);
31562
31838
  }
31563
- if (append && fs10.isFile(outPath)) {
31839
+ if (append2 && fs10.isFile(outPath)) {
31564
31840
  const existing = fs10.readTextFile(outPath);
31565
31841
  fs10.writeTextFile(outPath, existing + text);
31566
31842
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -19,10 +19,7 @@ tools:
19
19
  skills:
20
20
  - policy-compliance-order
21
21
  - epic-orchestrate
22
- - feature-promotion-lifecycle
23
- - atomic-plan-contract
24
22
  - acceptance-criteria-tracking
25
- - evidence-and-timestamp-conventions
26
23
  memory: project
27
24
  hooks:
28
25
  SubagentStop:
@@ -54,15 +51,13 @@ maintenance. This agent frames the *who* and *when*; the skill documents the *ho
54
51
 
55
52
  On every invocation:
56
53
 
57
- 1. Read `CLAUDE.md` for repository tone policy and architecture context.
58
- 2. Read applicable `.claude/rules/` files for languages in scope.
59
- 3. Read `artifacts/orchestration/epic-orchestrator-state.json` to check for existing epic
54
+ 1. Read `artifacts/orchestration/epic-orchestrator-state.json` to check for existing epic
60
55
  checkpoint state.
61
- 4. If a valid epic checkpoint exists with a matching `epic_feature_folder`, resume from the
56
+ 2. If a valid epic checkpoint exists with a matching `epic_feature_folder`, resume from the
62
57
  recorded `next_step` (re-deriving durable ground truth via `git worktree list --porcelain`,
63
58
  `git branch`, and `gh pr view --json state,mergedAt,headRefOid` per the `epic-orchestrate`
64
59
  skill's resume procedure, not from in-memory notifications alone).
65
- 5. If no checkpoint exists or the objective is new, begin from manifest parsing
60
+ 3. If no checkpoint exists or the objective is new, begin from manifest parsing
66
61
  (`docs/features/epics/<epic-slug>/epic.md`).
67
62
 
68
63
  ## Invocation Origin
@@ -104,7 +99,8 @@ You delegate exclusively through two channels:
104
99
 
105
100
  - `Agent(orchestrator)` — one delegation per child feature in the manifest, carrying the epic-mode
106
101
  kickoff line and, for dependent features, the upstream-context citation lines (both defined in
107
- `spec.md` §4 and §10 of this feature and restated procedurally in the `epic-orchestrate` skill).
102
+ `.claude/skills/epic-orchestrate/SKILL.md` under `## Merge-on-Green Kickoff Parameter` and
103
+ `## Context Handoff to Dependent Features`).
108
104
  Each child `orchestrator` runs its own full small/large route (including its own delegation to
109
105
  `atomic-planner`, `atomic-executor`, `feature-review`, and, on CI-green in epic mode, the
110
106
  merge-on-green S9 step 6 extension) inside its own isolated worktree
@@ -133,7 +129,9 @@ at your own `SubagentStop` time.
133
129
  ## Checkpoint Persistence
134
130
 
135
131
  Update `artifacts/orchestration/epic-orchestrator-state.json` after every completed step, per the
136
- full schema defined in `spec.md` §6: `objective`, `route_id: "epic"`, `epic_feature_folder`,
132
+ full schema enforced by `validate_epic_orchestrator_state_text`, implemented in
133
+ `scripts/dev_tools/validate_epic_orchestrator_state.py`: `objective`, `route_id: "epic"`,
134
+ `epic_feature_folder`,
137
135
  `epic_manifest_path`, `epic_status_doc_path`, `integration_branch`, `completed_steps`, `next_step`,
138
136
  `last_updated`, `current_wave`, `waves[]`, `features[]` (including `merge_status` and the four
139
137
  lifecycle timestamps), `epic_merge_pr`, and the three receipt arrays