@cleocode/cleo 2026.5.111 → 2026.5.112

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/dist/cli/index.js CHANGED
@@ -1079,6 +1079,33 @@ var init_braille = __esm({
1079
1079
  }
1080
1080
  });
1081
1081
 
1082
+ // packages/animations/src/render/legend.ts
1083
+ function renderLegend(input2) {
1084
+ if (!input2.ctx.enabled) return "";
1085
+ if (input2.items.length === 0) return "";
1086
+ const threshold = input2.multiLineThreshold ?? DEFAULT_MULTILINE_THRESHOLD;
1087
+ const formatted = input2.items.map((item) => `${item.icon} ${item.label}`);
1088
+ if (input2.items.length <= threshold) {
1089
+ return formatted.join(" ");
1090
+ }
1091
+ return formatted.join("\n");
1092
+ }
1093
+ function renderSummary(input2) {
1094
+ if (!input2.ctx.enabled) return "";
1095
+ if (input2.counts.length === 0) return "";
1096
+ const separator = input2.ctx.inputs.noColor ? SUMMARY_SEPARATOR_ASCII : SUMMARY_SEPARATOR;
1097
+ return input2.counts.map((c) => `${c.n} ${c.label}`).join(separator);
1098
+ }
1099
+ var DEFAULT_MULTILINE_THRESHOLD, SUMMARY_SEPARATOR, SUMMARY_SEPARATOR_ASCII;
1100
+ var init_legend = __esm({
1101
+ "packages/animations/src/render/legend.ts"() {
1102
+ "use strict";
1103
+ DEFAULT_MULTILINE_THRESHOLD = 8;
1104
+ SUMMARY_SEPARATOR = " \xB7 ";
1105
+ SUMMARY_SEPARATOR_ASCII = " | ";
1106
+ }
1107
+ });
1108
+
1082
1109
  // packages/animations/src/render/tree.ts
1083
1110
  import { ascii, KindIcon, StatusIcon } from "@cleocode/contracts/render/icon.js";
1084
1111
  function renderTree(resp, opts) {
@@ -1192,6 +1219,8 @@ var init_tree = __esm({
1192
1219
  // packages/animations/src/render/index.ts
1193
1220
  var init_render = __esm({
1194
1221
  "packages/animations/src/render/index.ts"() {
1222
+ "use strict";
1223
+ init_legend();
1195
1224
  init_tree();
1196
1225
  }
1197
1226
  });
@@ -1308,7 +1337,6 @@ var init_spinner_handle = __esm({
1308
1337
  var init_src = __esm({
1309
1338
  "packages/animations/src/index.ts"() {
1310
1339
  init_animate_context();
1311
- init_render();
1312
1340
  init_spinner_handle();
1313
1341
  }
1314
1342
  });
@@ -10184,59 +10212,139 @@ var init_errors = __esm({
10184
10212
  }
10185
10213
  });
10186
10214
 
10187
- // packages/contracts/src/evidence-record-schema.ts
10215
+ // packages/contracts/src/evidence-atom-schema.ts
10188
10216
  import { z as z4 } from "zod";
10217
+ var commitAtomSchema, filesAtomSchema, testRunAtomSchema, toolAtomSchema, urlAtomSchema, noteAtomSchema, decisionAtomSchema, prAtomSchema, locDropAtomSchema, callsiteCoverageAtomSchema, EvidenceAtomSchema, GATE_EVIDENCE_REQUIREMENTS;
10218
+ var init_evidence_atom_schema = __esm({
10219
+ "packages/contracts/src/evidence-atom-schema.ts"() {
10220
+ "use strict";
10221
+ commitAtomSchema = z4.object({
10222
+ kind: z4.literal("commit"),
10223
+ sha: z4.string().regex(/^[0-9a-f]{7,40}$/i, "commit sha must be 7-40 hex characters")
10224
+ });
10225
+ filesAtomSchema = z4.object({
10226
+ kind: z4.literal("files"),
10227
+ paths: z4.array(z4.string().min(1)).min(1, "files atom requires at least one path")
10228
+ });
10229
+ testRunAtomSchema = z4.object({
10230
+ kind: z4.literal("test-run"),
10231
+ path: z4.string().min(1, "test-run atom requires a non-empty path")
10232
+ });
10233
+ toolAtomSchema = z4.object({
10234
+ kind: z4.literal("tool"),
10235
+ tool: z4.string().min(1, "tool atom requires a non-empty tool name")
10236
+ });
10237
+ urlAtomSchema = z4.object({
10238
+ kind: z4.literal("url"),
10239
+ url: z4.string().min(1).regex(/^https?:\/\//, "url atom must start with http:// or https://")
10240
+ });
10241
+ noteAtomSchema = z4.object({
10242
+ kind: z4.literal("note"),
10243
+ note: z4.string().min(1, "note atom must be non-empty").max(512, "note atom is too long (max 512 chars)")
10244
+ });
10245
+ decisionAtomSchema = z4.object({
10246
+ kind: z4.literal("decision"),
10247
+ decisionId: z4.string().min(1, "decision atom requires a non-empty decision ID")
10248
+ });
10249
+ prAtomSchema = z4.object({
10250
+ kind: z4.literal("pr"),
10251
+ prNumber: z4.number().int().positive("pr atom requires a positive integer PR number")
10252
+ });
10253
+ locDropAtomSchema = z4.object({
10254
+ kind: z4.literal("loc-drop"),
10255
+ fromLines: z4.number().int().nonnegative("loc-drop fromLines must be \u2265 0"),
10256
+ toLines: z4.number().int().nonnegative("loc-drop toLines must be \u2265 0")
10257
+ });
10258
+ callsiteCoverageAtomSchema = z4.object({
10259
+ kind: z4.literal("callsite-coverage"),
10260
+ symbolName: z4.string().min(1, "callsite-coverage atom requires a non-empty symbolName"),
10261
+ relativeSourcePath: z4.string().min(1, "callsite-coverage atom requires a non-empty relativeSourcePath")
10262
+ });
10263
+ EvidenceAtomSchema = z4.discriminatedUnion("kind", [
10264
+ commitAtomSchema,
10265
+ filesAtomSchema,
10266
+ testRunAtomSchema,
10267
+ toolAtomSchema,
10268
+ urlAtomSchema,
10269
+ noteAtomSchema,
10270
+ decisionAtomSchema,
10271
+ prAtomSchema,
10272
+ locDropAtomSchema,
10273
+ callsiteCoverageAtomSchema
10274
+ ]);
10275
+ GATE_EVIDENCE_REQUIREMENTS = Object.freeze({
10276
+ implemented: {
10277
+ oneOf: [
10278
+ ["commit", "files"],
10279
+ ["commit", "note"],
10280
+ ["decision", "files"],
10281
+ ["decision", "note"],
10282
+ ["pr"]
10283
+ ]
10284
+ },
10285
+ testsPassed: { oneOf: [["test-run"], ["tool"], ["pr"]] },
10286
+ qaPassed: { oneOf: [["tool"], ["pr"]] },
10287
+ documented: { oneOf: [["files"], ["url"]] },
10288
+ securityPassed: { oneOf: [["tool"], ["note"]] },
10289
+ cleanupDone: { oneOf: [["note"]] },
10290
+ nexusImpact: { oneOf: [["tool"], ["note"]] }
10291
+ });
10292
+ }
10293
+ });
10294
+
10295
+ // packages/contracts/src/evidence-record-schema.ts
10296
+ import { z as z5 } from "zod";
10189
10297
  var evidenceBaseSchema, implDiffRecordSchema, validateSpecCheckRecordSchema, testOutputRecordSchema, lintReportRecordSchema, commandOutputRecordSchema, evidenceRecordSchema;
10190
10298
  var init_evidence_record_schema = __esm({
10191
10299
  "packages/contracts/src/evidence-record-schema.ts"() {
10192
10300
  "use strict";
10193
- evidenceBaseSchema = z4.object({
10301
+ evidenceBaseSchema = z5.object({
10194
10302
  /** Identity string of the agent that produced this record. */
10195
- agentIdentity: z4.string().min(1),
10303
+ agentIdentity: z5.string().min(1),
10196
10304
  /** SHA-256 hex digest (64 chars) of the attached artifact. */
10197
- attachmentSha256: z4.string().length(64),
10305
+ attachmentSha256: z5.string().length(64),
10198
10306
  /** ISO 8601 timestamp at which the action ran. */
10199
- ranAt: z4.string().datetime(),
10307
+ ranAt: z5.string().datetime(),
10200
10308
  /** Wall-clock duration of the action in milliseconds. */
10201
- durationMs: z4.number().nonnegative()
10309
+ durationMs: z5.number().nonnegative()
10202
10310
  });
10203
10311
  implDiffRecordSchema = evidenceBaseSchema.extend({
10204
- kind: z4.literal("impl-diff"),
10205
- phase: z4.literal("implement"),
10206
- filesChanged: z4.array(z4.string().min(1)).min(1),
10207
- linesAdded: z4.number().int().nonnegative(),
10208
- linesRemoved: z4.number().int().nonnegative()
10312
+ kind: z5.literal("impl-diff"),
10313
+ phase: z5.literal("implement"),
10314
+ filesChanged: z5.array(z5.string().min(1)).min(1),
10315
+ linesAdded: z5.number().int().nonnegative(),
10316
+ linesRemoved: z5.number().int().nonnegative()
10209
10317
  });
10210
10318
  validateSpecCheckRecordSchema = evidenceBaseSchema.extend({
10211
- kind: z4.literal("validate-spec-check"),
10212
- phase: z4.literal("validate"),
10213
- reqIdsChecked: z4.array(z4.string().min(1)).min(1),
10214
- passed: z4.boolean(),
10215
- details: z4.string().min(1)
10319
+ kind: z5.literal("validate-spec-check"),
10320
+ phase: z5.literal("validate"),
10321
+ reqIdsChecked: z5.array(z5.string().min(1)).min(1),
10322
+ passed: z5.boolean(),
10323
+ details: z5.string().min(1)
10216
10324
  });
10217
10325
  testOutputRecordSchema = evidenceBaseSchema.extend({
10218
- kind: z4.literal("test-output"),
10219
- phase: z4.literal("test"),
10220
- command: z4.string().min(1),
10221
- exitCode: z4.number().int(),
10222
- testsPassed: z4.number().int().nonnegative(),
10223
- testsFailed: z4.number().int().nonnegative()
10326
+ kind: z5.literal("test-output"),
10327
+ phase: z5.literal("test"),
10328
+ command: z5.string().min(1),
10329
+ exitCode: z5.number().int(),
10330
+ testsPassed: z5.number().int().nonnegative(),
10331
+ testsFailed: z5.number().int().nonnegative()
10224
10332
  });
10225
10333
  lintReportRecordSchema = evidenceBaseSchema.extend({
10226
- kind: z4.literal("lint-report"),
10227
- phase: z4.enum(["implement", "test"]),
10228
- tool: z4.string().min(1),
10229
- passed: z4.boolean(),
10230
- warnings: z4.number().int().nonnegative(),
10231
- errors: z4.number().int().nonnegative()
10334
+ kind: z5.literal("lint-report"),
10335
+ phase: z5.enum(["implement", "test"]),
10336
+ tool: z5.string().min(1),
10337
+ passed: z5.boolean(),
10338
+ warnings: z5.number().int().nonnegative(),
10339
+ errors: z5.number().int().nonnegative()
10232
10340
  });
10233
10341
  commandOutputRecordSchema = evidenceBaseSchema.extend({
10234
- kind: z4.literal("command-output"),
10235
- phase: z4.enum(["implement", "validate", "test"]),
10236
- cmd: z4.string().min(1),
10237
- exitCode: z4.number().int()
10342
+ kind: z5.literal("command-output"),
10343
+ phase: z5.enum(["implement", "validate", "test"]),
10344
+ cmd: z5.string().min(1),
10345
+ exitCode: z5.number().int()
10238
10346
  });
10239
- evidenceRecordSchema = z4.discriminatedUnion("kind", [
10347
+ evidenceRecordSchema = z5.discriminatedUnion("kind", [
10240
10348
  implDiffRecordSchema,
10241
10349
  validateSpecCheckRecordSchema,
10242
10350
  testOutputRecordSchema,
@@ -10260,6 +10368,154 @@ var init_graph = __esm({
10260
10368
  }
10261
10369
  });
10262
10370
 
10371
+ // packages/contracts/src/invariants/adr-073-saga.ts
10372
+ var SAGA_ENFORCEMENT_MODULE, SAGA_ENFORCEMENT_TESTS, ADR_073_INVARIANTS;
10373
+ var init_adr_073_saga = __esm({
10374
+ "packages/contracts/src/invariants/adr-073-saga.ts"() {
10375
+ "use strict";
10376
+ SAGA_ENFORCEMENT_MODULE = "packages/core/src/sagas/enforcement.ts";
10377
+ SAGA_ENFORCEMENT_TESTS = "packages/core/src/sagas/__tests__/enforcement.test.ts";
10378
+ ADR_073_INVARIANTS = Object.freeze([
10379
+ {
10380
+ adr: "ADR-073",
10381
+ code: "I1",
10382
+ name: "Storage uniformity",
10383
+ description: 'All task IDs are stored as T#### and the type column is the canonical tier discriminator. There is no separate ID space for Sagas, Epics, Tasks, or Subtasks; label="saga" elevates a type="epic" row to Saga semantics.',
10384
+ severity: "info",
10385
+ // I1 is enforced by the DB CHECK constraints introduced in W1.B (T10329)
10386
+ // and by TASK_ID_PATTERN in packages/core/src/tasks/id-generator.ts —
10387
+ // not by a single runtime function.
10388
+ runtimeGate: null,
10389
+ lintRule: null,
10390
+ doctorAudit: null,
10391
+ tests: ["packages/core/src/tasks/__tests__/id-generator.test.ts"]
10392
+ },
10393
+ {
10394
+ adr: "ADR-073",
10395
+ code: "I2",
10396
+ name: "Conceptual prefixes are display + import only",
10397
+ description: "SG-, E-, T- (and Subtask's implicit absence) are documentation, CLI display, and import-mapping conventions only. They MUST NOT be used as DB primary keys \u2014 display-only with no runtime enforcement.",
10398
+ severity: "info",
10399
+ // I2 is a display convention; the SG- prefix preservation snapshot
10400
+ // (T10333) protects the display contract but there is no runtime guard.
10401
+ runtimeGate: null,
10402
+ lintRule: null,
10403
+ doctorAudit: null,
10404
+ tests: []
10405
+ },
10406
+ {
10407
+ adr: "ADR-073",
10408
+ code: "I3",
10409
+ name: "Tier promotion mandatory when scope outgrows the tier",
10410
+ description: "A Subtask whose change exceeds 2 files or crosses a module boundary MUST be split or promoted to a sibling Task. A Task that requires more than one PR or wave MUST be split. An Epic that spans more than one release MUST be regrouped under a Saga.",
10411
+ severity: "error",
10412
+ runtimeGate: {
10413
+ module: SAGA_ENFORCEMENT_MODULE,
10414
+ functionName: "assertSagaInvariantI3"
10415
+ },
10416
+ lintRule: null,
10417
+ doctorAudit: null,
10418
+ tests: [SAGA_ENFORCEMENT_TESTS]
10419
+ },
10420
+ {
10421
+ adr: "ADR-073",
10422
+ code: "I4",
10423
+ name: "Ownership non-overlapping",
10424
+ description: "A single tier maps to a single orchestration role (per ADR-070). Workers MUST NOT spawn other Workers. Phase Leads MUST NOT own multiple Epics simultaneously. The Orchestrator MUST NOT spawn Workers directly when fan-out exceeds the ADR-070 migration threshold.",
10425
+ severity: "warning",
10426
+ // I4 is partially covered by ADR-070 spawn guards but the unification
10427
+ // with the registry happens in R2 (T10336 — ORC codes). For now this
10428
+ // entry stays warning + runtimeGate:null to mark the gap explicitly.
10429
+ runtimeGate: null,
10430
+ lintRule: null,
10431
+ doctorAudit: null,
10432
+ tests: []
10433
+ },
10434
+ {
10435
+ adr: "ADR-073",
10436
+ code: "I5",
10437
+ name: "Sagas link via groups, not parent",
10438
+ description: `task_relations.type="groups" is the ONLY relation type that links a Saga to its member Epics. The Saga row's parent_id MUST be NULL. Enforced at runtime by assertSagaInvariantI5 AND by the DB CHECK constraint from W1.B (T10329) on label="saga" rows.`,
10439
+ severity: "error",
10440
+ runtimeGate: {
10441
+ module: SAGA_ENFORCEMENT_MODULE,
10442
+ functionName: "assertSagaInvariantI5"
10443
+ },
10444
+ lintRule: null,
10445
+ doctorAudit: null,
10446
+ tests: [SAGA_ENFORCEMENT_TESTS]
10447
+ },
10448
+ {
10449
+ adr: "ADR-073",
10450
+ code: "I6",
10451
+ name: "Acceptance criteria required at every tier",
10452
+ description: 'Per ADR-066 \xA7"Ownership Matrix" invariant #5, all tasks regardless of type or kind MUST have --acceptance set at creation time. No tier exemption exists. Delegated to the ADR-066 --acceptance requirement on cleo add/add-batch.',
10453
+ severity: "warning",
10454
+ // I6 is enforced by ADR-066's CLI requirement, not by a saga runtime
10455
+ // function. UNENFORCED in the saga module — left as a warning so R6
10456
+ // doctor audit reminds operators of the upstream guard's location.
10457
+ runtimeGate: null,
10458
+ lintRule: null,
10459
+ doctorAudit: null,
10460
+ tests: []
10461
+ },
10462
+ {
10463
+ adr: "ADR-073",
10464
+ code: "I7",
10465
+ name: "Maximum parent depth is 3",
10466
+ description: "The parent ladder Subtask \u2192 Task \u2192 Epic is fixed at depth 3 (hierarchy.maxDepth=3). Sagas do NOT consume depth \u2014 they attach via groups relations, not parent edges. Enforced at runtime by assertSagaInvariantI7.",
10467
+ severity: "error",
10468
+ runtimeGate: {
10469
+ module: SAGA_ENFORCEMENT_MODULE,
10470
+ functionName: "assertSagaInvariantI7"
10471
+ },
10472
+ lintRule: null,
10473
+ doctorAudit: null,
10474
+ tests: [SAGA_ENFORCEMENT_TESTS]
10475
+ },
10476
+ {
10477
+ adr: "ADR-073",
10478
+ code: "I8",
10479
+ name: "Subtask-to-PR aggregation rule",
10480
+ description: "A Task ships as exactly one PR. The PR's commit history is the union of the Task's Subtask commits. A Subtask never produces its own PR; if a unit of work warrants its own PR, it is a Task, not a Subtask. UNENFORCED at runtime today \u2014 load-bearing convention enforced via code review and the lifecycle decision table.",
10481
+ severity: "warning",
10482
+ // I8 is explicitly "UNENFORCED, load-bearing" — there is no automated
10483
+ // gate. R6 doctor audit surfaces this entry so it stays visible.
10484
+ runtimeGate: null,
10485
+ lintRule: null,
10486
+ doctorAudit: null,
10487
+ tests: []
10488
+ }
10489
+ ]);
10490
+ }
10491
+ });
10492
+
10493
+ // packages/contracts/src/invariants/index.ts
10494
+ function buildKey(invariant) {
10495
+ return `${invariant.adr}.${invariant.code}`;
10496
+ }
10497
+ function buildRegistry() {
10498
+ const entries = [...ADR_073_INVARIANTS];
10499
+ const record = {};
10500
+ for (const entry of entries) {
10501
+ const key = buildKey(entry);
10502
+ if (record[key] !== void 0) {
10503
+ throw new Error(`Duplicate invariant key registered: ${key}`);
10504
+ }
10505
+ record[key] = entry;
10506
+ }
10507
+ return Object.freeze(record);
10508
+ }
10509
+ var INVARIANTS_REGISTRY;
10510
+ var init_invariants = __esm({
10511
+ "packages/contracts/src/invariants/index.ts"() {
10512
+ "use strict";
10513
+ init_adr_073_saga();
10514
+ init_adr_073_saga();
10515
+ INVARIANTS_REGISTRY = buildRegistry();
10516
+ }
10517
+ });
10518
+
10263
10519
  // packages/contracts/src/lafs.ts
10264
10520
  var init_lafs = __esm({
10265
10521
  "packages/contracts/src/lafs.ts"() {
@@ -11066,31 +11322,31 @@ var init_peer = __esm({
11066
11322
  });
11067
11323
 
11068
11324
  // packages/contracts/src/release/evidence-atoms.ts
11069
- import { z as z5 } from "zod";
11325
+ import { z as z6 } from "zod";
11070
11326
  var parsedPrEvidenceAtomSchema, prEvidenceStateModifierSchema, ghPrViewSchema, PR_REQUIRED_WORKFLOWS;
11071
11327
  var init_evidence_atoms = __esm({
11072
11328
  "packages/contracts/src/release/evidence-atoms.ts"() {
11073
11329
  "use strict";
11074
- parsedPrEvidenceAtomSchema = z5.object({
11075
- kind: z5.literal("pr"),
11076
- prNumber: z5.number().int().positive()
11077
- });
11078
- prEvidenceStateModifierSchema = z5.object({
11079
- kind: z5.literal("state"),
11080
- value: z5.literal("MERGED")
11081
- });
11082
- ghPrViewSchema = z5.object({
11083
- state: z5.enum(["OPEN", "CLOSED", "MERGED"]),
11084
- mergedAt: z5.string().nullable(),
11085
- headRefOid: z5.string().optional(),
11086
- mergeable: z5.string().optional(),
11087
- statusCheckRollup: z5.array(
11088
- z5.object({
11089
- __typename: z5.string().optional(),
11090
- name: z5.string().optional(),
11091
- workflowName: z5.string().optional(),
11092
- conclusion: z5.string().nullable().optional(),
11093
- status: z5.string().optional()
11330
+ parsedPrEvidenceAtomSchema = z6.object({
11331
+ kind: z6.literal("pr"),
11332
+ prNumber: z6.number().int().positive()
11333
+ });
11334
+ prEvidenceStateModifierSchema = z6.object({
11335
+ kind: z6.literal("state"),
11336
+ value: z6.literal("MERGED")
11337
+ });
11338
+ ghPrViewSchema = z6.object({
11339
+ state: z6.enum(["OPEN", "CLOSED", "MERGED"]),
11340
+ mergedAt: z6.string().nullable(),
11341
+ headRefOid: z6.string().optional(),
11342
+ mergeable: z6.string().optional(),
11343
+ statusCheckRollup: z6.array(
11344
+ z6.object({
11345
+ __typename: z6.string().optional(),
11346
+ name: z6.string().optional(),
11347
+ workflowName: z6.string().optional(),
11348
+ conclusion: z6.string().nullable().optional(),
11349
+ status: z6.string().optional()
11094
11350
  }).passthrough()
11095
11351
  ).optional().default([])
11096
11352
  }).passthrough();
@@ -11103,7 +11359,7 @@ var init_evidence_atoms = __esm({
11103
11359
  });
11104
11360
 
11105
11361
  // packages/contracts/src/release/plan.ts
11106
- import { z as z6 } from "zod";
11362
+ import { z as z7 } from "zod";
11107
11363
  var RELEASE_CHANNEL, RELEASE_SCHEME, RELEASE_KIND, RELEASE_STATUS, GATE_STATUS, GATE_NAME, PLATFORM_TUPLE, PUBLISHER, TASK_KIND, IMPACT, RESOLVED_SOURCE, ReleaseChannelSchema, ReleaseSchemeSchema, ReleaseKindSchema, ReleaseStatusSchema, GateStatusSchema, GateNameSchema, PlatformTupleSchema, PublisherSchema, TaskKindSchema, ImpactSchema, ResolvedSourceSchema, Iso8601, NonEmptyString, ReleasePlanTaskSchema, ReleaseGateSchema, ReleasePlatformMatrixEntrySchema, ReleasePreflightSummarySchema, ReleasePlanChangelogSchema, ReleasePlanMetaSchema, ReleasePlanSchema;
11108
11364
  var init_plan = __esm({
11109
11365
  "packages/contracts/src/release/plan.ts"() {
@@ -11146,20 +11402,20 @@ var init_plan = __esm({
11146
11402
  ];
11147
11403
  IMPACT = ["major", "minor", "patch"];
11148
11404
  RESOLVED_SOURCE = ["project-context", "language-default", "legacy-alias"];
11149
- ReleaseChannelSchema = z6.enum(RELEASE_CHANNEL);
11150
- ReleaseSchemeSchema = z6.enum(RELEASE_SCHEME);
11151
- ReleaseKindSchema = z6.enum(RELEASE_KIND);
11152
- ReleaseStatusSchema = z6.enum(RELEASE_STATUS);
11153
- GateStatusSchema = z6.enum(GATE_STATUS);
11154
- GateNameSchema = z6.enum(GATE_NAME);
11155
- PlatformTupleSchema = z6.enum(PLATFORM_TUPLE);
11156
- PublisherSchema = z6.enum(PUBLISHER);
11157
- TaskKindSchema = z6.enum(TASK_KIND);
11158
- ImpactSchema = z6.enum(IMPACT);
11159
- ResolvedSourceSchema = z6.enum(RESOLVED_SOURCE);
11160
- Iso8601 = z6.iso.datetime({ offset: true });
11161
- NonEmptyString = z6.string().min(1);
11162
- ReleasePlanTaskSchema = z6.object({
11405
+ ReleaseChannelSchema = z7.enum(RELEASE_CHANNEL);
11406
+ ReleaseSchemeSchema = z7.enum(RELEASE_SCHEME);
11407
+ ReleaseKindSchema = z7.enum(RELEASE_KIND);
11408
+ ReleaseStatusSchema = z7.enum(RELEASE_STATUS);
11409
+ GateStatusSchema = z7.enum(GATE_STATUS);
11410
+ GateNameSchema = z7.enum(GATE_NAME);
11411
+ PlatformTupleSchema = z7.enum(PLATFORM_TUPLE);
11412
+ PublisherSchema = z7.enum(PUBLISHER);
11413
+ TaskKindSchema = z7.enum(TASK_KIND);
11414
+ ImpactSchema = z7.enum(IMPACT);
11415
+ ResolvedSourceSchema = z7.enum(RESOLVED_SOURCE);
11416
+ Iso8601 = z7.iso.datetime({ offset: true });
11417
+ NonEmptyString = z7.string().min(1);
11418
+ ReleasePlanTaskSchema = z7.object({
11163
11419
  /** Task ID (e.g. "T10001"). Format intentionally loose so historical IDs validate. */
11164
11420
  id: NonEmptyString,
11165
11421
  /** Conventional-commit-aligned task classification. */
@@ -11167,20 +11423,20 @@ var init_plan = __esm({
11167
11423
  /** SemVer impact classification. */
11168
11424
  impact: ImpactSchema,
11169
11425
  /** Human-readable changelog line for this task. */
11170
- userFacingSummary: z6.string(),
11426
+ userFacingSummary: z7.string(),
11171
11427
  /**
11172
11428
  * ADR-051 evidence atoms attesting the task's gate results. Format is
11173
11429
  * `kind:value` (e.g. `commit:abc123`, `test-run:vitest.json`). The contract
11174
11430
  * accepts empty arrays so legacy plans validate; `cleo release plan`
11175
11431
  * enforces non-empty via R-301.
11176
11432
  */
11177
- evidenceAtoms: z6.array(NonEmptyString),
11433
+ evidenceAtoms: z7.array(NonEmptyString),
11178
11434
  /** IVTR phase at plan time — informational only per R-316. */
11179
- ivtrPhaseAtPlan: z6.string().optional(),
11435
+ ivtrPhaseAtPlan: z7.string().optional(),
11180
11436
  /** Epic this task rolls up to, locked at plan time per R-303. */
11181
11437
  epicAncestor: NonEmptyString
11182
11438
  });
11183
- ReleaseGateSchema = z6.object({
11439
+ ReleaseGateSchema = z7.object({
11184
11440
  /** Canonical gate name. */
11185
11441
  name: GateNameSchema,
11186
11442
  /** ADR-051 atom string identifying the resolved tool (e.g. `tool:test`). */
@@ -11190,11 +11446,11 @@ var init_plan = __esm({
11190
11446
  /** ISO-8601 timestamp the gate was last verified. */
11191
11447
  lastVerifiedAt: Iso8601,
11192
11448
  /** Resolved shell command (e.g. `pnpm run test`). Optional for unresolved gates. */
11193
- resolvedCommand: z6.string().optional(),
11449
+ resolvedCommand: z7.string().optional(),
11194
11450
  /** Provenance of the resolved command. Optional for unresolved gates. */
11195
11451
  resolvedSource: ResolvedSourceSchema.optional()
11196
11452
  });
11197
- ReleasePlatformMatrixEntrySchema = z6.object({
11453
+ ReleasePlatformMatrixEntrySchema = z7.object({
11198
11454
  /** Target platform tuple. */
11199
11455
  platform: PlatformTupleSchema,
11200
11456
  /** Distribution backend. */
@@ -11202,47 +11458,47 @@ var init_plan = __esm({
11202
11458
  /** Package identifier on the target backend (e.g. `@cleocode/cleo`). */
11203
11459
  package: NonEmptyString,
11204
11460
  /** Whether to run the GHA smoke job for this matrix entry. */
11205
- smoke: z6.boolean().default(true).optional()
11461
+ smoke: z7.boolean().default(true).optional()
11206
11462
  });
11207
- ReleasePreflightSummarySchema = z6.object({
11463
+ ReleasePreflightSummarySchema = z7.object({
11208
11464
  /** True if esbuild externals are out of sync with package.json. */
11209
- esbuildExternalsDrift: z6.boolean(),
11465
+ esbuildExternalsDrift: z7.boolean(),
11210
11466
  /** True if `pnpm-lock.yaml` diverges from the workspace manifest. */
11211
- lockfileDrift: z6.boolean(),
11467
+ lockfileDrift: z7.boolean(),
11212
11468
  /** True if all epic children are in terminal lifecycle states. */
11213
- epicCompletenessClean: z6.boolean(),
11469
+ epicCompletenessClean: z7.boolean(),
11214
11470
  /** True if no task appears in multiple in-flight release plans. */
11215
- doubleListingClean: z6.boolean(),
11471
+ doubleListingClean: z7.boolean(),
11216
11472
  /** Non-fatal preflight warnings (e.g. unresolved tools per R-024). */
11217
- preflightWarnings: z6.array(z6.string()).default([]).optional()
11473
+ preflightWarnings: z7.array(z7.string()).default([]).optional()
11218
11474
  });
11219
- ReleasePlanChangelogSchema = z6.object({
11475
+ ReleasePlanChangelogSchema = z7.object({
11220
11476
  /** `kind=feat` tasks. */
11221
- features: z6.array(NonEmptyString).default([]),
11477
+ features: z7.array(NonEmptyString).default([]),
11222
11478
  /** `kind=fix` or `kind=hotfix` tasks. */
11223
- fixes: z6.array(NonEmptyString).default([]),
11479
+ fixes: z7.array(NonEmptyString).default([]),
11224
11480
  /** `kind=chore`, `docs`, `refactor`, `test`, `perf` tasks. */
11225
- chores: z6.array(NonEmptyString).default([]),
11481
+ chores: z7.array(NonEmptyString).default([]),
11226
11482
  /** `kind=breaking` or `kind=revert` tasks. */
11227
- breaking: z6.array(NonEmptyString).default([])
11483
+ breaking: z7.array(NonEmptyString).default([])
11228
11484
  });
11229
- ReleasePlanMetaSchema = z6.object({
11485
+ ReleasePlanMetaSchema = z7.object({
11230
11486
  /** True if this is the project's first ever release. */
11231
- firstEverRelease: z6.boolean().optional(),
11487
+ firstEverRelease: z7.boolean().optional(),
11232
11488
  /** Canonical tool names that could not be resolved at plan time. */
11233
- unresolvedTools: z6.array(z6.string()).optional(),
11489
+ unresolvedTools: z7.array(z7.string()).optional(),
11234
11490
  /** Project archetype detected at plan time. */
11235
- archetype: z6.string().optional()
11236
- }).catchall(z6.unknown());
11237
- ReleasePlanSchema = z6.object({
11491
+ archetype: z7.string().optional()
11492
+ }).catchall(z7.unknown());
11493
+ ReleasePlanSchema = z7.object({
11238
11494
  /** Schema URL for this plan version. */
11239
- $schema: z6.string().optional(),
11495
+ $schema: z7.string().optional(),
11240
11496
  /** Requested version string (e.g. "v2026.6.0"). Includes the leading `v`. */
11241
11497
  version: NonEmptyString,
11242
11498
  /** Resolved version string after suffix application (e.g. "v2026.6.0.2"). */
11243
11499
  resolvedVersion: NonEmptyString,
11244
11500
  /** True if a `calver-suffix` was applied to disambiguate a same-day hotfix. */
11245
- suffixApplied: z6.boolean(),
11501
+ suffixApplied: z7.boolean(),
11246
11502
  /** Versioning scheme governing `version` / `resolvedVersion`. */
11247
11503
  scheme: ReleaseSchemeSchema,
11248
11504
  /** npm dist-tag channel for this release. */
@@ -11259,27 +11515,27 @@ var init_plan = __esm({
11259
11515
  * Version of the previous release on the same channel. MUST be `null` only
11260
11516
  * for first-ever releases (R-300, enforced at the verb layer).
11261
11517
  */
11262
- previousVersion: z6.string().nullable(),
11518
+ previousVersion: z7.string().nullable(),
11263
11519
  /** Git tag of the previous release (typically `previousVersion` prefixed). */
11264
- previousTag: z6.string().nullable(),
11520
+ previousTag: z7.string().nullable(),
11265
11521
  /** ISO-8601 timestamp the previous release was published. */
11266
11522
  previousShippedAt: Iso8601.nullable(),
11267
11523
  /** Tasks rolled into this release. */
11268
- tasks: z6.array(ReleasePlanTaskSchema),
11524
+ tasks: z7.array(ReleasePlanTaskSchema),
11269
11525
  /** Bucketed changelog. */
11270
11526
  changelog: ReleasePlanChangelogSchema,
11271
11527
  /** Per-gate verification status. */
11272
- gates: z6.array(ReleaseGateSchema),
11528
+ gates: z7.array(ReleaseGateSchema),
11273
11529
  /** Platform / publisher matrix. */
11274
- platformMatrix: z6.array(ReleasePlatformMatrixEntrySchema),
11530
+ platformMatrix: z7.array(ReleasePlatformMatrixEntrySchema),
11275
11531
  /** Preflight summary from `cleo release plan`. */
11276
11532
  preflightSummary: ReleasePreflightSummarySchema,
11277
11533
  /** URL of the GHA workflow run (populated by `release-prepare.yml`). */
11278
- workflowRunUrl: z6.string().nullable(),
11534
+ workflowRunUrl: z7.string().nullable(),
11279
11535
  /** URL of the bump PR (populated by `cleo release open`). */
11280
- prUrl: z6.string().nullable(),
11536
+ prUrl: z7.string().nullable(),
11281
11537
  /** Merge commit SHA on `main` (populated by `release-publish.yml`). */
11282
- mergeCommitSha: z6.string().nullable(),
11538
+ mergeCommitSha: z7.string().nullable(),
11283
11539
  /** Current FSM state per R-302. */
11284
11540
  status: ReleaseStatusSchema,
11285
11541
  /** Informational / forward-compat metadata. */
@@ -11335,52 +11591,52 @@ var init_session2 = __esm({
11335
11591
  });
11336
11592
 
11337
11593
  // packages/contracts/src/session-journal.ts
11338
- import { z as z7 } from "zod";
11594
+ import { z as z8 } from "zod";
11339
11595
  var SESSION_JOURNAL_SCHEMA_VERSION, sessionJournalDoctorSummarySchema, sessionJournalDebriefSummarySchema, sessionJournalEntrySchema;
11340
11596
  var init_session_journal = __esm({
11341
11597
  "packages/contracts/src/session-journal.ts"() {
11342
11598
  "use strict";
11343
11599
  SESSION_JOURNAL_SCHEMA_VERSION = "1.0";
11344
- sessionJournalDoctorSummarySchema = z7.object({
11600
+ sessionJournalDoctorSummarySchema = z8.object({
11345
11601
  /** `true` when zero noise patterns were detected. */
11346
- isClean: z7.boolean(),
11602
+ isClean: z8.boolean(),
11347
11603
  /** Total number of noise findings across all patterns. */
11348
- findingsCount: z7.number().int().nonnegative(),
11604
+ findingsCount: z8.number().int().nonnegative(),
11349
11605
  /** Pattern names that were detected (empty when isClean). */
11350
- patterns: z7.array(z7.string()),
11606
+ patterns: z8.array(z8.string()),
11351
11607
  /** Total brain entries scanned. `0` = empty or unavailable. */
11352
- totalScanned: z7.number().int().nonnegative()
11608
+ totalScanned: z8.number().int().nonnegative()
11353
11609
  });
11354
- sessionJournalDebriefSummarySchema = z7.object({
11610
+ sessionJournalDebriefSummarySchema = z8.object({
11355
11611
  /** First 200 characters of the session end note (if provided). */
11356
- noteExcerpt: z7.string().max(200).optional(),
11612
+ noteExcerpt: z8.string().max(200).optional(),
11357
11613
  /** Number of tasks completed during the session. */
11358
- tasksCompletedCount: z7.number().int().nonnegative(),
11614
+ tasksCompletedCount: z8.number().int().nonnegative(),
11359
11615
  /** Up to 5 task IDs (not titles) that were the focus of the session. */
11360
- tasksFocused: z7.array(z7.string()).max(5).optional()
11616
+ tasksFocused: z8.array(z8.string()).max(5).optional()
11361
11617
  });
11362
- sessionJournalEntrySchema = z7.object({
11618
+ sessionJournalEntrySchema = z8.object({
11363
11619
  // Identity
11364
11620
  /** Schema version for forward-compatibility. Always `'1.0'` in this release. */
11365
- schemaVersion: z7.literal(SESSION_JOURNAL_SCHEMA_VERSION),
11621
+ schemaVersion: z8.literal(SESSION_JOURNAL_SCHEMA_VERSION),
11366
11622
  /** ISO 8601 timestamp when the entry was written. */
11367
- timestamp: z7.string(),
11623
+ timestamp: z8.string(),
11368
11624
  /** CLEO session ID (e.g. `ses_20260424055456_ede571`). */
11369
- sessionId: z7.string(),
11625
+ sessionId: z8.string(),
11370
11626
  /** Event type that triggered this journal entry. */
11371
- eventType: z7.enum(["session_start", "session_end", "observation", "decision", "error"]),
11627
+ eventType: z8.enum(["session_start", "session_end", "observation", "decision", "error"]),
11372
11628
  // Session metadata (set on session_start / session_end)
11373
11629
  /** Agent identifier (e.g. `cleo-prime`, `claude-code`). */
11374
- agentIdentifier: z7.string().optional(),
11630
+ agentIdentifier: z8.string().optional(),
11375
11631
  /** Provider adapter ID active for this session. */
11376
- providerId: z7.string().optional(),
11632
+ providerId: z8.string().optional(),
11377
11633
  /** Session scope string (e.g. `'global'` or `'epic:T1263'`). */
11378
- scope: z7.string().optional(),
11634
+ scope: z8.string().optional(),
11379
11635
  // Session-end fields
11380
11636
  /** Duration of the session in seconds (session_end only). */
11381
- duration: z7.number().int().nonnegative().optional(),
11637
+ duration: z8.number().int().nonnegative().optional(),
11382
11638
  /** Task IDs (not titles) completed during the session. */
11383
- tasksCompleted: z7.array(z7.string()).optional(),
11639
+ tasksCompleted: z8.array(z8.string()).optional(),
11384
11640
  // Doctor summary (T1262 absorbed)
11385
11641
  /** Compact result of `scanBrainNoise` run at session-end. */
11386
11642
  doctorSummary: sessionJournalDoctorSummarySchema.optional(),
@@ -11389,7 +11645,7 @@ var init_session_journal = __esm({
11389
11645
  debriefSummary: sessionJournalDebriefSummarySchema.optional(),
11390
11646
  // Optional hash chain
11391
11647
  /** SHA-256 hex of the previous entry's raw JSON string (for integrity chain). */
11392
- prevEntryHash: z7.string().optional()
11648
+ prevEntryHash: z8.string().optional()
11393
11649
  });
11394
11650
  }
11395
11651
  });
@@ -11409,52 +11665,52 @@ var init_task = __esm({
11409
11665
  });
11410
11666
 
11411
11667
  // packages/contracts/src/task-evidence.ts
11412
- import { z as z8 } from "zod";
11668
+ import { z as z9 } from "zod";
11413
11669
  var fileEvidenceSchema, logEvidenceSchema, screenshotEvidenceSchema, testOutputEvidenceSchema, commandOutputEvidenceSchema, taskEvidenceSchema;
11414
11670
  var init_task_evidence = __esm({
11415
11671
  "packages/contracts/src/task-evidence.ts"() {
11416
11672
  "use strict";
11417
- fileEvidenceSchema = z8.object({
11418
- kind: z8.literal("file"),
11419
- sha256: z8.string().length(64),
11420
- timestamp: z8.string().datetime(),
11421
- path: z8.string().min(1),
11422
- mime: z8.string().optional(),
11423
- description: z8.string().optional()
11424
- });
11425
- logEvidenceSchema = z8.object({
11426
- kind: z8.literal("log"),
11427
- sha256: z8.string().length(64),
11428
- timestamp: z8.string().datetime(),
11429
- source: z8.string().min(1),
11430
- description: z8.string().optional()
11431
- });
11432
- screenshotEvidenceSchema = z8.object({
11433
- kind: z8.literal("screenshot"),
11434
- sha256: z8.string().length(64),
11435
- timestamp: z8.string().datetime(),
11436
- mime: z8.enum(["image/png", "image/jpeg", "image/webp"]).optional(),
11437
- description: z8.string().optional()
11438
- });
11439
- testOutputEvidenceSchema = z8.object({
11440
- kind: z8.literal("test-output"),
11441
- sha256: z8.string().length(64),
11442
- timestamp: z8.string().datetime(),
11443
- passed: z8.number().int().nonnegative(),
11444
- failed: z8.number().int().nonnegative(),
11445
- skipped: z8.number().int().nonnegative(),
11446
- exitCode: z8.number().int(),
11447
- description: z8.string().optional()
11448
- });
11449
- commandOutputEvidenceSchema = z8.object({
11450
- kind: z8.literal("command-output"),
11451
- sha256: z8.string().length(64),
11452
- timestamp: z8.string().datetime(),
11453
- cmd: z8.string().min(1),
11454
- exitCode: z8.number().int(),
11455
- description: z8.string().optional()
11456
- });
11457
- taskEvidenceSchema = z8.discriminatedUnion("kind", [
11673
+ fileEvidenceSchema = z9.object({
11674
+ kind: z9.literal("file"),
11675
+ sha256: z9.string().length(64),
11676
+ timestamp: z9.string().datetime(),
11677
+ path: z9.string().min(1),
11678
+ mime: z9.string().optional(),
11679
+ description: z9.string().optional()
11680
+ });
11681
+ logEvidenceSchema = z9.object({
11682
+ kind: z9.literal("log"),
11683
+ sha256: z9.string().length(64),
11684
+ timestamp: z9.string().datetime(),
11685
+ source: z9.string().min(1),
11686
+ description: z9.string().optional()
11687
+ });
11688
+ screenshotEvidenceSchema = z9.object({
11689
+ kind: z9.literal("screenshot"),
11690
+ sha256: z9.string().length(64),
11691
+ timestamp: z9.string().datetime(),
11692
+ mime: z9.enum(["image/png", "image/jpeg", "image/webp"]).optional(),
11693
+ description: z9.string().optional()
11694
+ });
11695
+ testOutputEvidenceSchema = z9.object({
11696
+ kind: z9.literal("test-output"),
11697
+ sha256: z9.string().length(64),
11698
+ timestamp: z9.string().datetime(),
11699
+ passed: z9.number().int().nonnegative(),
11700
+ failed: z9.number().int().nonnegative(),
11701
+ skipped: z9.number().int().nonnegative(),
11702
+ exitCode: z9.number().int(),
11703
+ description: z9.string().optional()
11704
+ });
11705
+ commandOutputEvidenceSchema = z9.object({
11706
+ kind: z9.literal("command-output"),
11707
+ sha256: z9.string().length(64),
11708
+ timestamp: z9.string().datetime(),
11709
+ cmd: z9.string().min(1),
11710
+ exitCode: z9.number().int(),
11711
+ description: z9.string().optional()
11712
+ });
11713
+ taskEvidenceSchema = z9.discriminatedUnion("kind", [
11458
11714
  fileEvidenceSchema,
11459
11715
  logEvidenceSchema,
11460
11716
  screenshotEvidenceSchema,
@@ -11465,12 +11721,12 @@ var init_task_evidence = __esm({
11465
11721
  });
11466
11722
 
11467
11723
  // packages/contracts/src/tasks/archive.ts
11468
- import { z as z9 } from "zod";
11724
+ import { z as z10 } from "zod";
11469
11725
  var ArchiveReason, ARCHIVE_REASON_VALUES;
11470
11726
  var init_archive = __esm({
11471
11727
  "packages/contracts/src/tasks/archive.ts"() {
11472
11728
  "use strict";
11473
- ArchiveReason = z9.enum([
11729
+ ArchiveReason = z10.enum([
11474
11730
  "verified",
11475
11731
  "reconciled",
11476
11732
  "superseded",
@@ -11499,10 +11755,12 @@ var init_src2 = __esm({
11499
11755
  init_engine_result();
11500
11756
  init_enums();
11501
11757
  init_errors();
11758
+ init_evidence_atom_schema();
11502
11759
  init_evidence_record_schema();
11503
11760
  init_exit_codes();
11504
11761
  init_facade();
11505
11762
  init_graph();
11763
+ init_invariants();
11506
11764
  init_lafs();
11507
11765
  init_plugin_llm();
11508
11766
  init_observe();
@@ -41190,7 +41448,13 @@ var init_colors = __esm({
41190
41448
  });
41191
41449
 
41192
41450
  // packages/cleo/src/cli/renderers/generic-tree.ts
41193
- import { ascii as ascii2, KindIcon as KindIcon2, pickIcon, RelationIcon } from "@cleocode/contracts/render/icon.js";
41451
+ import {
41452
+ ascii as ascii2,
41453
+ KindIcon as KindIcon2,
41454
+ pickIcon,
41455
+ RelationIcon,
41456
+ StatusIcon as StatusIcon2
41457
+ } from "@cleocode/contracts/render/icon.js";
41194
41458
  function renderGenericTree(result, opts) {
41195
41459
  const explicitCtx = opts.ctx !== void 0;
41196
41460
  const ctx = opts.ctx ?? resolveAnimateContext();
@@ -41209,8 +41473,74 @@ function renderGenericTree(result, opts) {
41209
41473
  const annotations = renderAnnotations(result.tree, opts);
41210
41474
  if (annotations) lines.push(annotations);
41211
41475
  }
41476
+ const footer = renderFooterLegend(result, ctx, useAscii);
41477
+ if (footer) {
41478
+ lines.push("");
41479
+ lines.push(footer);
41480
+ }
41212
41481
  return lines.join("\n");
41213
41482
  }
41483
+ function renderFooterLegend(result, ctx, useAscii) {
41484
+ if (!ctx.enabled) return "";
41485
+ const kindsSeen = /* @__PURE__ */ new Set();
41486
+ const statusesSeen = /* @__PURE__ */ new Set();
41487
+ let hasGroupsEdge = false;
41488
+ for (const node of result.tree.tree) {
41489
+ kindsSeen.add(node.kind);
41490
+ statusesSeen.add(node.status);
41491
+ if (node.metadata.edgeType === "groups") hasGroupsEdge = true;
41492
+ }
41493
+ const items = [];
41494
+ const KIND_ORDER = [
41495
+ ["saga", "saga"],
41496
+ ["epic", "epic"],
41497
+ ["task", "task"],
41498
+ ["subtask", "subtask"]
41499
+ ];
41500
+ for (const [kind, label] of KIND_ORDER) {
41501
+ if (!kindsSeen.has(kind)) continue;
41502
+ items.push({ icon: pickIcon(kindIconOf(kind), { noColor: useAscii }), label });
41503
+ }
41504
+ if (hasGroupsEdge) {
41505
+ items.push({
41506
+ icon: useAscii ? ascii2(RelationIcon.GROUPS) : RelationIcon.GROUPS,
41507
+ label: "groups-edge"
41508
+ });
41509
+ }
41510
+ const STATUS_ORDER = [
41511
+ ["done", StatusIcon2.DONE],
41512
+ ["in_progress", StatusIcon2.ACTIVE],
41513
+ ["pending", StatusIcon2.PENDING],
41514
+ ["blocked", StatusIcon2.BLOCKED],
41515
+ ["archived", StatusIcon2.ARCHIVED],
41516
+ ["cancelled", StatusIcon2.CANCELLED]
41517
+ ];
41518
+ const STATUS_LABEL = {
41519
+ done: "done",
41520
+ in_progress: "active",
41521
+ pending: "pending",
41522
+ blocked: "blocked",
41523
+ archived: "archived",
41524
+ cancelled: "cancelled"
41525
+ };
41526
+ for (const [status, icon] of STATUS_ORDER) {
41527
+ if (!statusesSeen.has(status)) continue;
41528
+ items.push({
41529
+ icon: pickIcon(icon, { noColor: useAscii }),
41530
+ label: STATUS_LABEL[status] ?? status
41531
+ });
41532
+ }
41533
+ const legend = renderLegend({ items, ctx });
41534
+ const sagaMembers2 = result.tree.tree.filter((n) => n.metadata.edgeType === "groups").length;
41535
+ const counts2 = [
41536
+ { label: "nodes", n: result.tree.totalNodes },
41537
+ { label: "depth", n: result.tree.maxDepth }
41538
+ ];
41539
+ if (sagaMembers2 > 0) counts2.push({ label: "saga members", n: sagaMembers2 });
41540
+ const summary = renderSummary({ counts: counts2, ctx });
41541
+ const divider = `${DIM}\u2500\u2500\u2500 Legend \u2500\u2500\u2500${NC}`;
41542
+ return [divider, legend, summary].filter(Boolean).join("\n");
41543
+ }
41214
41544
  function decorateGroupsEdges(tree, useAscii) {
41215
41545
  const glyph = useAscii ? ascii2(RelationIcon.GROUPS) : RelationIcon.GROUPS;
41216
41546
  const prefix = `${glyph} `;