mandrel 1.91.0 → 1.93.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 (112) hide show
  1. package/.agents/README.md +5 -5
  2. package/.agents/docs/SDLC.md +75 -107
  3. package/.agents/docs/configuration.md +1 -1
  4. package/.agents/docs/quality-gates.md +8 -7
  5. package/.agents/docs/workflows.md +4 -10
  6. package/.agents/instructions.md +2 -2
  7. package/.agents/schemas/agentrc.schema.json +1 -1
  8. package/.agents/schemas/lifecycle/README.md +2 -1
  9. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  10. package/.agents/schemas/risk-verdict.schema.json +14 -1
  11. package/.agents/scripts/README.md +0 -46
  12. package/.agents/scripts/analyze-execution.js +76 -1
  13. package/.agents/scripts/check-doc-links.js +3 -0
  14. package/.agents/scripts/check-workflow-cli-lint.js +208 -0
  15. package/.agents/scripts/epic-audit-recheck.js +1 -1
  16. package/.agents/scripts/epic-plan-clarity.js +16 -6
  17. package/.agents/scripts/epic-plan-decompose.js +37 -59
  18. package/.agents/scripts/epic-plan-healthcheck.js +39 -6
  19. package/.agents/scripts/epic-plan-spec.js +41 -168
  20. package/.agents/scripts/epic-reconcile.js +7 -1
  21. package/.agents/scripts/lib/Logger.js +4 -4
  22. package/.agents/scripts/lib/audit-suite/index.js +4 -5
  23. package/.agents/scripts/lib/audit-suite/runner.js +6 -4
  24. package/.agents/scripts/lib/audit-suite/selector.js +2 -3
  25. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  26. package/.agents/scripts/lib/command-header.js +20 -0
  27. package/.agents/scripts/lib/config/github.js +1 -3
  28. package/.agents/scripts/lib/config-settings-schema.js +4 -3
  29. package/.agents/scripts/lib/degraded-mode.js +1 -1
  30. package/.agents/scripts/lib/label-constants.js +18 -3
  31. package/.agents/scripts/lib/label-taxonomy.js +14 -3
  32. package/.agents/scripts/lib/mutation/baseline-snapshot.js +7 -6
  33. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +18 -8
  34. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -4
  35. package/.agents/scripts/lib/orchestration/docs-digest.js +2 -2
  36. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +4 -0
  37. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +22 -12
  38. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +6 -1
  39. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +18 -2
  40. package/.agents/scripts/lib/orchestration/file-assumptions.js +68 -7
  41. package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  42. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  43. package/.agents/scripts/lib/orchestration/plan-context.js +696 -0
  44. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  45. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +99 -0
  46. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  49. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +1015 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +206 -0
  51. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  52. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  53. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  55. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -1
  56. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  57. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  58. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  59. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  60. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  61. package/.agents/scripts/plan-context.js +204 -0
  62. package/.agents/scripts/plan-critics.js +199 -0
  63. package/.agents/scripts/plan-persist.js +383 -0
  64. package/.agents/scripts/pr-watch-with-update.js +8 -7
  65. package/.agents/scripts/run-lint.js +10 -11
  66. package/.agents/scripts/story-plan.js +19 -7
  67. package/.agents/scripts/sync-claude-commands.js +10 -5
  68. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  69. package/.agents/workflows/audit-documentation.md +5 -7
  70. package/.agents/workflows/audit-lighthouse.md +1 -0
  71. package/.agents/workflows/audit-security.md +1 -0
  72. package/.agents/workflows/audit-to-stories.md +16 -5
  73. package/.agents/workflows/git-deliver.md +76 -243
  74. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  75. package/.agents/workflows/helpers/code-review.md +7 -6
  76. package/.agents/workflows/helpers/deliver-epic.md +5 -4
  77. package/.agents/workflows/helpers/plan-epic-reference.md +120 -96
  78. package/.agents/workflows/helpers/plan-epic.md +307 -994
  79. package/.agents/workflows/helpers/plan-story.md +14 -14
  80. package/.agents/workflows/helpers/scope-triage-gate.md +15 -4
  81. package/.agents/workflows/helpers/single-story-deliver.md +5 -4
  82. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  83. package/.agents/workflows/mandrel-update.md +144 -466
  84. package/.agents/workflows/plan.md +87 -145
  85. package/docs/CHANGELOG.md +20 -0
  86. package/lib/cli/registry.js +21 -3
  87. package/package.json +1 -1
  88. package/.agents/schemas/loop-unit.schema.json +0 -70
  89. package/.agents/scripts/assert-branch.js +0 -81
  90. package/.agents/scripts/check-loop-units.js +0 -204
  91. package/.agents/scripts/detect-merges.js +0 -111
  92. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  93. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  94. package/.agents/scripts/hierarchy-gate.js +0 -192
  95. package/.agents/scripts/hydrate-context.js +0 -179
  96. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  97. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  98. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  99. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  100. package/.agents/scripts/loc-delta.js +0 -205
  101. package/.agents/scripts/render-manifest.js +0 -143
  102. package/.agents/scripts/run-audit-suite.js +0 -97
  103. package/.agents/scripts/select-audits.js +0 -155
  104. package/.agents/scripts/update-mutation-baseline.js +0 -189
  105. package/.agents/workflows/explain.md +0 -118
  106. package/.agents/workflows/git-merge-pr.md +0 -377
  107. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  108. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  109. package/.agents/workflows/loops/README.md +0 -65
  110. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  111. package/.agents/workflows/loops/nightly-audit.md +0 -81
  112. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -240,13 +240,17 @@ export async function runDecomposePhase(
240
240
  * @param {typeof defaultRunPlanHealthcheck} args.runHealthcheckFn
241
241
  * @returns {Promise<{ ok: boolean, waived?: boolean, reason?: string|null }>}
242
242
  */
243
- function enforceFanOutGate(findings, allowLargeFanOut) {
243
+ export function enforceFanOutGate(
244
+ findings,
245
+ allowLargeFanOut,
246
+ tag = 'epic-plan-decompose',
247
+ ) {
244
248
  const fanOut = (findings ?? []).filter((f) => f.kind === 'fan-out-warning');
245
249
  if (fanOut.length === 0) return;
246
250
  if (allowLargeFanOut) {
247
251
  for (const f of fanOut) {
248
252
  Logger.warn(
249
- `[epic-plan-decompose] Persisting a large-fan-out deletion: ` +
253
+ `[${tag}] Persisting a large-fan-out deletion: ` +
250
254
  `Task "${f.taskSlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
251
255
  `call site(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.`,
252
256
  );
@@ -260,7 +264,7 @@ function enforceFanOutGate(findings, allowLargeFanOut) {
260
264
  )
261
265
  .join('\n');
262
266
  throw new Error(
263
- `[epic-plan-decompose] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
267
+ `[${tag}] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
264
268
  `Split each deletion into a subsystem-by-subsystem migration across multiple Stories, ` +
265
269
  `or rerun --allow-large-fan-out after confirming the deletion is intentional.`,
266
270
  );
@@ -284,24 +288,30 @@ function enforceFanOutGate(findings, allowLargeFanOut) {
284
288
  *
285
289
  * @param {object[]} findings
286
290
  */
287
- function surfaceSoftConflictFindings(findings) {
291
+ export function surfaceSoftConflictFindings(
292
+ findings,
293
+ tag = 'epic-plan-decompose',
294
+ ) {
288
295
  const soft = (findings ?? []).filter(
289
296
  (f) => f?.severity === 'soft' && f?.kind !== 'fan-out-warning',
290
297
  );
291
298
  if (soft.length === 0) return;
292
299
  Logger.warn(
293
- `[epic-plan-decompose] ${soft.length} soft cross-Story conflict finding(s) — review before approving the plan:`,
300
+ `[${tag}] ${soft.length} soft cross-Story conflict finding(s) — review before approving the plan:`,
294
301
  );
295
302
  for (const finding of soft) {
296
- Logger.warn(
297
- `[epic-plan-decompose] soft conflict: ${renderHardConflictError(finding)}`,
298
- );
303
+ Logger.warn(`[${tag}] soft conflict: ${renderHardConflictError(finding)}`);
299
304
  }
300
305
  }
301
306
 
302
- async function runHealthcheckGate({ epicId, epic, runHealthcheckFn }) {
307
+ export async function runHealthcheckGate({
308
+ epicId,
309
+ epic,
310
+ runHealthcheckFn,
311
+ tag = 'epic-plan-decompose',
312
+ }) {
303
313
  Logger.info(
304
- `[epic-plan-decompose] Running post-plan readiness healthcheck for Epic #${epicId}...`,
314
+ `[${tag}] Running post-plan readiness healthcheck for Epic #${epicId}...`,
305
315
  );
306
316
  let result;
307
317
  try {
@@ -321,13 +331,13 @@ async function runHealthcheckGate({ epicId, epic, runHealthcheckFn }) {
321
331
  const waived = labels.includes(PLANNING_HEALTHCHECK_WAIVED);
322
332
  if (waived) {
323
333
  Logger.warn(
324
- `[epic-plan-decompose] Healthcheck failed for Epic #${epicId} but '${PLANNING_HEALTHCHECK_WAIVED}' is applied — proceeding with agent::ready handoff. Reason: ${result?.reason ?? '(no reason reported)'}`,
334
+ `[${tag}] Healthcheck failed for Epic #${epicId} but '${PLANNING_HEALTHCHECK_WAIVED}' is applied — proceeding with agent::ready handoff. Reason: ${result?.reason ?? '(no reason reported)'}`,
325
335
  );
326
336
  return { ok: false, waived: true, reason: result?.reason ?? null };
327
337
  }
328
338
 
329
339
  throw new Error(
330
- `[epic-plan-decompose] Refusing agent::ready handoff for Epic #${epicId}: ` +
340
+ `[${tag}] Refusing agent::ready handoff for Epic #${epicId}: ` +
331
341
  `post-plan healthcheck failed (${result?.reason ?? '(no reason reported)'}). ` +
332
342
  `Resolve the failing check(s), or apply the '${PLANNING_HEALTHCHECK_WAIVED}' ` +
333
343
  `label to the Epic to override and rerun the persist phase.`,
@@ -113,7 +113,12 @@ export async function buildAuthoringContext(
113
113
  settings = {},
114
114
  opts = {},
115
115
  ) {
116
- const epic = await provider.getEpic(epicId);
116
+ // Epic #4474 (M3 PR2): `opts.epic` is an optional prefetched Epic object
117
+ // so a caller that already holds the issue (the folded `plan-context.js`
118
+ // envelope build, which also needs the raw body for clarity scoring and
119
+ // re-plan detection) does not pay a second provider fetch. Absent, the
120
+ // fetch behaviour is unchanged.
121
+ const epic = opts.epic ?? (await provider.getEpic(epicId));
117
122
  if (!epic) {
118
123
  throw new Error(`Epic #${epicId} not found.`);
119
124
  }
@@ -568,7 +568,20 @@ export function diff({ spec, state, ghState } = {}) {
568
568
 
569
569
  for (const entity of flattenSpec(spec)) {
570
570
  seenSpecSlugs.add(entity.slug);
571
- const mapped = mapping[entity.slug];
571
+ // Epic #4474 (PR3) — a mapping entry without a numeric `issueNumber`
572
+ // is a partial-failure tombstone: the apply's state writer projects an
573
+ // entry per spec slug, but only completed creations carry a number
574
+ // (`buildState` seeds new slugs with `issueNumber: null`). Treating
575
+ // such an entry as "mapped" made a `--resume` after a rate-limit crash
576
+ // silently skip every failed creation (no Create emitted; the content
577
+ // hash matches, so no Update either — a lossy resume). A numberless
578
+ // entry is therefore unmapped here → Create. No duplicate risk: any
579
+ // live same-title issue was already reseeded onto the slug (with its
580
+ // number) by `reseedMappingFromGh` before the diff runs.
581
+ const mappedEntry = mapping[entity.slug];
582
+ const mapped = Number.isInteger(mappedEntry?.issueNumber)
583
+ ? mappedEntry
584
+ : undefined;
572
585
 
573
586
  if (!mapped) {
574
587
  plan.creates.push(
@@ -630,9 +643,12 @@ export function diff({ spec, state, ghState } = {}) {
630
643
  }
631
644
  }
632
645
 
633
- // Closes — anything in mapping not seen in spec.
646
+ // Closes — anything in mapping not seen in spec. A numberless
647
+ // partial-failure tombstone (see above) has no live issue to close;
648
+ // dropping it from the spec simply drops the entry.
634
649
  for (const [slug, mapped] of Object.entries(mapping)) {
635
650
  if (seenSpecSlugs.has(slug)) continue;
651
+ if (!Number.isInteger(mapped?.issueNumber)) continue;
636
652
  plan.closes.push(
637
653
  closeOp({
638
654
  slug,
@@ -15,7 +15,13 @@
15
15
  *
16
16
  * Rules (one error per mismatched path):
17
17
  * - `creates` + path **exists** → error (Story would clobber).
18
- * - `refactors-existing` + path **absent** → error (no target to refactor).
18
+ * - `refactors-existing` (via `changes`) + path **absent** →
19
+ * auto-normalized to `creates` with a logged warning (#4496 fix 5):
20
+ * a refactor declaration against a base-untracked path is
21
+ * deterministically a create, so rejecting it only forces a
22
+ * reject→amend→re-persist cycle for a mechanical rewrite. Genuine
23
+ * mismatches keep failing — a `references`-sourced `refactors-existing`
24
+ * on an absent path is a missing read dependency and stays an error.
19
25
  * - `exists` + path **absent** → error (read dependency missing).
20
26
  * - `deletes` + path **absent** → error (nothing to delete).
21
27
  *
@@ -185,6 +191,18 @@ function renderMismatch({
185
191
  return `"${slug}" → body.${source} declares assumption="${assumption}" for ${path} but the path already exists at the base branch.`;
186
192
  }
187
193
 
194
+ /**
195
+ * Render an auto-normalization (#4496 fix 5) into a stable warning string.
196
+ * Kept pure and exported through the report so callers log a
197
+ * self-explanatory line rather than re-deriving the rationale.
198
+ *
199
+ * @param {{ slug: string, source: string, path: string, assumption: string }} normalization
200
+ * @returns {string}
201
+ */
202
+ function renderNormalization({ slug, source, path, assumption }) {
203
+ return `"${slug}" → body.${source} declares assumption="${assumption}" for ${path} but the path is untracked at the base branch — auto-normalized to "creates" (a refactor of a base-untracked path is deterministically a create). Declare assumption="creates" in the plan to silence this warning.`;
204
+ }
205
+
188
206
  /**
189
207
  * Index, across every Story, which Stories declare a `creates` (and which
190
208
  * declare a `deletes`) for each `changes`-sourced path. The maps drive the
@@ -251,8 +269,11 @@ function predecessorMutator(index, path, predecessors) {
251
269
  *
252
270
  * {
253
271
  * errors: string[] // one entry per mismatch, batched per Story
254
- * warnings: string[] // legacy/no-assumption deprecation nudges
272
+ * warnings: string[] // legacy/no-assumption deprecation nudges +
273
+ * // auto-normalization notices (#4496 fix 5)
255
274
  * mismatches: object[] // structured payload for downstream tooling
275
+ * normalizations: object[] // `refactors-existing`→`creates`
276
+ * // auto-normalizations on base-untracked paths
256
277
  * }
257
278
  *
258
279
  * Under the 2-tier hierarchy the Story is the implementation unit, so the
@@ -283,6 +304,7 @@ export function validateStoryFileAssumptions(opts) {
283
304
  const errors = [];
284
305
  const warnings = [];
285
306
  const mismatches = [];
307
+ const normalizations = [];
286
308
  const probeCache = new Map();
287
309
 
288
310
  // Wave-aware setup (Story #3960): transitive predecessor sets over the
@@ -350,6 +372,14 @@ export function validateStoryFileAssumptions(opts) {
350
372
  predecessorCreator,
351
373
  });
352
374
  if (mismatch !== null) {
375
+ // Auto-normalization (#4496 fix 5): a deterministic
376
+ // `refactors-existing`→`creates` rewrite is a warning, never a
377
+ // rejection — genuine mismatches keep flowing to `errors`.
378
+ if (mismatch.normalizedTo === 'creates') {
379
+ normalizations.push(mismatch);
380
+ warnings.push(renderNormalization(mismatch));
381
+ continue;
382
+ }
353
383
  mismatches.push(mismatch);
354
384
  errors.push(renderMismatch(mismatch));
355
385
  continue;
@@ -384,7 +414,7 @@ export function validateStoryFileAssumptions(opts) {
384
414
  }
385
415
  }
386
416
  }
387
- return { errors, warnings, mismatches };
417
+ return { errors, warnings, mismatches, normalizations };
388
418
  }
389
419
 
390
420
  /**
@@ -468,12 +498,43 @@ function checkAssumption({
468
498
  }
469
499
  return null;
470
500
  case 'refactors-existing':
501
+ // Validate against the simulated tree: a predecessor `creates` makes
502
+ // an otherwise-absent base path present, so `refactors-existing`
503
+ // against it is no longer a false-positive mismatch (Story #3960).
504
+ if (!simulatedExists) {
505
+ // Auto-normalization (#4496 fix 5): a `changes`-sourced refactor
506
+ // declaration on a path untracked at the base branch (and not
507
+ // produced by any predecessor) is deterministically a create —
508
+ // downgrade to a normalization warning instead of rejecting. Two
509
+ // genuine mismatches stay hard errors: a `references`-sourced entry
510
+ // (a read dependency this Story does not author cannot be "created"
511
+ // here), and a base-TRACKED path a predecessor deletes (the absence
512
+ // is a plan-shape conflict, not an untracked-path misdeclaration).
513
+ if (source === 'changes' && !baseExists) {
514
+ return {
515
+ slug,
516
+ source,
517
+ path,
518
+ assumption,
519
+ expected: 'creates',
520
+ actual: 'absent',
521
+ normalizedTo: 'creates',
522
+ };
523
+ }
524
+ return {
525
+ slug,
526
+ source,
527
+ path,
528
+ assumption,
529
+ expected: 'present',
530
+ actual: 'absent',
531
+ };
532
+ }
533
+ return null;
471
534
  case 'exists':
472
535
  case 'deletes':
473
- // Validate against the simulated tree: a predecessor `creates` makes
474
- // an otherwise-absent base path present, so `refactors-existing` /
475
- // `exists` / `deletes` against it is no longer a false-positive
476
- // mismatch (Story #3960).
536
+ // Same simulated-tree overlay as above (Story #3960); an absent path
537
+ // remains a genuine mismatch for both assumptions.
477
538
  if (!simulatedExists) {
478
539
  return {
479
540
  slug,
@@ -1,35 +1,22 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file */
3
-
4
1
  /**
5
- * lifecycle-diff.js — assertion modes over a lifecycle ledger file for
6
- * the repeatability invariants. The structural-`diff` helpers below are
7
- * exported for unit tests that pin the diff contract.
2
+ * ledger-diff.js — structural-diff and ordering-assertion helpers over a
3
+ * lifecycle ledger (`temp/epic-<id>/lifecycle.ndjson`) for the
4
+ * repeatability invariants.
8
5
  *
9
- * Usage:
10
- * node .agents/scripts/lifecycle-diff.js --assert <mode> <ledger>
11
- * `mode` is one of:
12
- * merge-gate-ordering — epic.merge.armed must be preceded
13
- * by epic.merge.ready (same seqId
14
- * chain; armed.seqId > ready.seqId).
15
- * reconcile-ordering — pr.created must be preceded by
16
- * acceptance.reconcile.ok.
17
- * — exits 0 on pass; exits 1 with a structured message on fail.
6
+ * Relocated from the retired `.agents/scripts/lifecycle-diff.js` CLI
7
+ * (#4482): the assertion helpers are the live surface — they pin the
8
+ * cross-listener ordering contracts in the lifecycle invariant tests —
9
+ * while the CLI wrapper had no workflow, script, or CI consumer.
18
10
  *
19
11
  * Invariants are derived from Tech Spec #2189 § Repeatability Acceptance
20
12
  * Criteria.
21
13
  */
22
14
 
23
- import { readFileSync } from 'node:fs';
24
- import { parseArgs } from 'node:util';
25
-
26
- import { runAsCli } from './lib/cli-utils.js';
27
-
28
15
  /**
29
16
  * Parse an NDJSON lifecycle ledger into an array of records. Blank
30
17
  * lines tolerated; malformed JSON throws with line number. Duplicated
31
- * from `lib/orchestration/lifecycle/trace-logger.js` to avoid coupling
32
- * the CLI to the listener surface.
18
+ * from `trace-logger.js` to keep the diff surface decoupled from the
19
+ * listener surface.
33
20
  */
34
21
  export function parseLedgerText(text) {
35
22
  const out = [];
@@ -41,7 +28,7 @@ export function parseLedgerText(text) {
41
28
  out.push(JSON.parse(line));
42
29
  } catch (_err) {
43
30
  throw new Error(
44
- `lifecycle-diff: malformed JSON in ledger on line ${i + 1}: ${line.slice(0, 80)}`,
31
+ `ledger-diff: malformed JSON in ledger on line ${i + 1}: ${line.slice(0, 80)}`,
45
32
  );
46
33
  }
47
34
  }
@@ -151,56 +138,3 @@ export function assertReconcileOrdering(records) {
151
138
  }
152
139
  return { ok: true };
153
140
  }
154
-
155
- const ASSERTIONS = new Map([
156
- ['merge-gate-ordering', assertMergeGateOrdering],
157
- ['reconcile-ordering', assertReconcileOrdering],
158
- ]);
159
-
160
- function loadLedger(p) {
161
- return parseLedgerText(readFileSync(p, 'utf8'));
162
- }
163
-
164
- async function main() {
165
- const { values, positionals } = parseArgs({
166
- options: {
167
- assert: { type: 'string' },
168
- },
169
- allowPositionals: true,
170
- args: process.argv.slice(2),
171
- });
172
-
173
- // --assert <mode> <ledger>
174
- if (!values.assert) {
175
- process.stderr.write('Usage: lifecycle-diff --assert <mode> <ledger>\n');
176
- return 2;
177
- }
178
- if (positionals.length !== 1) {
179
- process.stderr.write(
180
- 'lifecycle-diff --assert <mode> requires exactly one positional ledger path\n',
181
- );
182
- return 2;
183
- }
184
- const assertion = ASSERTIONS.get(values.assert);
185
- if (!assertion) {
186
- process.stderr.write(
187
- `lifecycle-diff: unknown --assert mode "${values.assert}". Valid: ${[...ASSERTIONS.keys()].join(', ')}\n`,
188
- );
189
- return 2;
190
- }
191
- const records = loadLedger(positionals[0]);
192
- const result = assertion(records);
193
- if (result.ok) {
194
- process.stdout.write(`[lifecycle-diff] PASS ${values.assert}\n`);
195
- return 0;
196
- }
197
- process.stderr.write(
198
- `[lifecycle-diff] FAIL ${values.assert}: ${result.reason}\n`,
199
- );
200
- return 1;
201
- }
202
-
203
- await runAsCli(import.meta.url, main, {
204
- source: 'lifecycle-diff',
205
- propagateExitCode: true,
206
- });
@@ -199,7 +199,7 @@ export class AcceptanceReconciler {
199
199
  ? epicIdFromPayload
200
200
  : this.epicId;
201
201
 
202
- // 1. Start emit. The downstream lifecycle-diff `reconcile-ordering`
202
+ // 1. Start emit. The downstream ledger-diff `reconcile-ordering`
203
203
  // assert checks for `acceptance.reconcile.ok` before `pr.created`,
204
204
  // but operators want to see the `.start` marker in the trace too.
205
205
  try {