@cleocode/contracts 2026.9.19 → 2026.9.21

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 (169) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/decide.test.d.ts +7 -0
  3. package/dist/__tests__/decide.test.d.ts.map +1 -0
  4. package/dist/__tests__/decide.test.js +91 -0
  5. package/dist/__tests__/decide.test.js.map +1 -0
  6. package/dist/__tests__/evidence-atom-schema.test.js +2 -1
  7. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
  8. package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
  9. package/dist/acceptance-gate-schema.d.ts +5 -0
  10. package/dist/acceptance-gate-schema.d.ts.map +1 -1
  11. package/dist/acceptance-gate-schema.js +26 -0
  12. package/dist/acceptance-gate-schema.js.map +1 -1
  13. package/dist/acceptance-gate.d.ts +70 -3
  14. package/dist/acceptance-gate.d.ts.map +1 -1
  15. package/dist/backup-manifest.d.ts +13 -2
  16. package/dist/backup-manifest.d.ts.map +1 -1
  17. package/dist/boundary.js +1 -1
  18. package/dist/boundary.js.map +1 -1
  19. package/dist/branch-lock.d.ts +11 -0
  20. package/dist/branch-lock.d.ts.map +1 -1
  21. package/dist/branch-lock.js +31 -4
  22. package/dist/branch-lock.js.map +1 -1
  23. package/dist/cloud/api.d.ts +607 -0
  24. package/dist/cloud/api.d.ts.map +1 -0
  25. package/dist/cloud/api.js +417 -0
  26. package/dist/cloud/api.js.map +1 -0
  27. package/dist/cloud/envelope.d.ts +84 -0
  28. package/dist/cloud/envelope.d.ts.map +1 -0
  29. package/dist/cloud/envelope.js +54 -0
  30. package/dist/cloud/envelope.js.map +1 -0
  31. package/dist/cloud/ids.d.ts +30 -0
  32. package/dist/cloud/ids.d.ts.map +1 -0
  33. package/dist/cloud/ids.js +32 -0
  34. package/dist/cloud/ids.js.map +1 -0
  35. package/dist/cloud/index.d.ts +17 -0
  36. package/dist/cloud/index.d.ts.map +1 -0
  37. package/dist/cloud/index.js +17 -0
  38. package/dist/cloud/index.js.map +1 -0
  39. package/dist/data-accessor.d.ts +23 -3
  40. package/dist/data-accessor.d.ts.map +1 -1
  41. package/dist/data-accessor.js.map +1 -1
  42. package/dist/decide.d.ts +333 -0
  43. package/dist/decide.d.ts.map +1 -0
  44. package/dist/decide.js +184 -0
  45. package/dist/decide.js.map +1 -0
  46. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  47. package/dist/dispatch/operations-registry.js +55 -0
  48. package/dist/dispatch/operations-registry.js.map +1 -1
  49. package/dist/done-plan.d.ts +311 -0
  50. package/dist/done-plan.d.ts.map +1 -0
  51. package/dist/done-plan.js +17 -0
  52. package/dist/done-plan.js.map +1 -0
  53. package/dist/evidence-atom-schema.d.ts +18 -1
  54. package/dist/evidence-atom-schema.d.ts.map +1 -1
  55. package/dist/evidence-atom-schema.js +27 -2
  56. package/dist/evidence-atom-schema.js.map +1 -1
  57. package/dist/exit-codes.d.ts +40 -0
  58. package/dist/exit-codes.d.ts.map +1 -1
  59. package/dist/exit-codes.js +41 -0
  60. package/dist/exit-codes.js.map +1 -1
  61. package/dist/graph.d.ts +40 -0
  62. package/dist/graph.d.ts.map +1 -1
  63. package/dist/index.d.ts +14 -6
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +4 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/memory/observe.d.ts +16 -0
  68. package/dist/memory/observe.d.ts.map +1 -1
  69. package/dist/operations/__tests__/release-shared-surface.test.d.ts +10 -0
  70. package/dist/operations/__tests__/release-shared-surface.test.d.ts.map +1 -0
  71. package/dist/operations/__tests__/release-shared-surface.test.js +84 -0
  72. package/dist/operations/__tests__/release-shared-surface.test.js.map +1 -0
  73. package/dist/operations/dialectic.d.ts +16 -0
  74. package/dist/operations/dialectic.d.ts.map +1 -1
  75. package/dist/operations/memory.d.ts +48 -0
  76. package/dist/operations/memory.d.ts.map +1 -1
  77. package/dist/operations/nexus-scope-map.d.ts +8 -0
  78. package/dist/operations/nexus-scope-map.d.ts.map +1 -1
  79. package/dist/operations/nexus-scope-map.js +8 -0
  80. package/dist/operations/nexus-scope-map.js.map +1 -1
  81. package/dist/operations/nexus-user-profile.d.ts +27 -1
  82. package/dist/operations/nexus-user-profile.d.ts.map +1 -1
  83. package/dist/operations/nexus-user-profile.js +11 -1
  84. package/dist/operations/nexus-user-profile.js.map +1 -1
  85. package/dist/operations/nexus.d.ts +394 -3
  86. package/dist/operations/nexus.d.ts.map +1 -1
  87. package/dist/operations/release.d.ts +259 -0
  88. package/dist/operations/release.d.ts.map +1 -1
  89. package/dist/operations/release.js +130 -1
  90. package/dist/operations/release.js.map +1 -1
  91. package/dist/operations/session.d.ts +6 -4
  92. package/dist/operations/session.d.ts.map +1 -1
  93. package/dist/operations/tasks.d.ts +6 -0
  94. package/dist/operations/tasks.d.ts.map +1 -1
  95. package/dist/operations/tasks.js +1 -0
  96. package/dist/operations/tasks.js.map +1 -1
  97. package/dist/operations/validate.d.ts +5 -0
  98. package/dist/operations/validate.d.ts.map +1 -1
  99. package/dist/operations/worktree.d.ts +125 -1
  100. package/dist/operations/worktree.d.ts.map +1 -1
  101. package/dist/process-probe.d.ts +17 -0
  102. package/dist/process-probe.d.ts.map +1 -0
  103. package/dist/process-probe.js +21 -0
  104. package/dist/process-probe.js.map +1 -0
  105. package/dist/project-context.d.ts +44 -2
  106. package/dist/project-context.d.ts.map +1 -1
  107. package/dist/project-lifecycle.d.ts +143 -0
  108. package/dist/project-lifecycle.d.ts.map +1 -0
  109. package/dist/project-lifecycle.js +21 -0
  110. package/dist/project-lifecycle.js.map +1 -0
  111. package/dist/release/plan.d.ts +6 -2
  112. package/dist/release/plan.d.ts.map +1 -1
  113. package/dist/release/plan.js +8 -2
  114. package/dist/release/plan.js.map +1 -1
  115. package/dist/release/plan.test.js +6 -1
  116. package/dist/release/plan.test.js.map +1 -1
  117. package/dist/skills/install-gate.d.ts +109 -0
  118. package/dist/skills/install-gate.d.ts.map +1 -0
  119. package/dist/skills/install-gate.js +18 -0
  120. package/dist/skills/install-gate.js.map +1 -0
  121. package/dist/table-classification.d.ts +208 -0
  122. package/dist/table-classification.d.ts.map +1 -0
  123. package/dist/table-classification.js +23 -0
  124. package/dist/table-classification.js.map +1 -0
  125. package/dist/task.d.ts +79 -0
  126. package/dist/task.d.ts.map +1 -1
  127. package/package.json +6 -2
  128. package/schemas/gate-result.schema.json +43 -0
  129. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  130. package/src/__tests__/decide.test.ts +139 -0
  131. package/src/__tests__/evidence-atom-schema.test.ts +2 -1
  132. package/src/__tests__/memory-wire-shapes.test.ts +3 -0
  133. package/src/acceptance-gate-schema.ts +29 -0
  134. package/src/acceptance-gate.ts +68 -3
  135. package/src/backup-manifest.ts +13 -2
  136. package/src/boundary.ts +1 -1
  137. package/src/branch-lock.ts +36 -4
  138. package/src/cloud/api.ts +512 -0
  139. package/src/cloud/envelope.ts +63 -0
  140. package/src/cloud/ids.ts +51 -0
  141. package/src/cloud/index.ts +16 -0
  142. package/src/data-accessor.ts +24 -3
  143. package/src/decide.ts +355 -0
  144. package/src/dispatch/operations-registry.ts +58 -0
  145. package/src/done-plan.ts +350 -0
  146. package/src/evidence-atom-schema.ts +31 -2
  147. package/src/exit-codes.ts +42 -0
  148. package/src/graph.ts +44 -0
  149. package/src/index.ts +133 -0
  150. package/src/memory/observe.ts +16 -0
  151. package/src/operations/__tests__/release-shared-surface.test.ts +112 -0
  152. package/src/operations/dialectic.ts +17 -0
  153. package/src/operations/memory.ts +48 -0
  154. package/src/operations/nexus-scope-map.ts +9 -0
  155. package/src/operations/nexus-user-profile.ts +29 -1
  156. package/src/operations/nexus.ts +416 -3
  157. package/src/operations/release.ts +197 -0
  158. package/src/operations/session.ts +6 -4
  159. package/src/operations/tasks.ts +7 -0
  160. package/src/operations/validate.ts +5 -0
  161. package/src/operations/worktree.ts +133 -1
  162. package/src/process-probe.ts +21 -0
  163. package/src/project-context.ts +42 -2
  164. package/src/project-lifecycle.ts +149 -0
  165. package/src/release/plan.test.ts +6 -1
  166. package/src/release/plan.ts +8 -2
  167. package/src/skills/install-gate.ts +134 -0
  168. package/src/table-classification.ts +232 -0
  169. package/src/task.ts +84 -1
@@ -647,6 +647,12 @@ export interface NexusPermissionSetResult {
647
647
  export interface NexusReconcileParams {
648
648
  /** Override project root (defaults to cwd). */
649
649
  projectRoot?: string;
650
+ /**
651
+ * Rebind the registry row to this checkout even though its previous location
652
+ * still exists on this device (T12470). Without it such a checkout is only
653
+ * recorded as a `candidate` — a second checkout or a clone is not a move.
654
+ */
655
+ forceRebind?: boolean;
650
656
  }
651
657
  /** Result of `nexus.reconcile`. */
652
658
  export interface NexusReconcileResult {
@@ -1282,6 +1288,180 @@ export interface NexusProjectsListResult {
1282
1288
  projects: NexusProjectRecord[];
1283
1289
  /** Count of projects returned. */
1284
1290
  count: number;
1291
+ /**
1292
+ * Every device known to the global store (`nexus_devices`, T12510), most
1293
+ * recent heartbeat first. A location's `deviceId` resolves against this list.
1294
+ */
1295
+ devices: NexusDeviceRecord[];
1296
+ }
1297
+
1298
+ /**
1299
+ * One machine that runs CLEO against the global store (`nexus_devices`, T12510).
1300
+ * `deviceId` is the persisted `<cleoHome>/device-id` UUID that
1301
+ * {@link NexusProjectCheckout.deviceId} carries.
1302
+ */
1303
+ export interface NexusDeviceRecord {
1304
+ /** Stable device id (`<cleoHome>/device-id`). */
1305
+ deviceId: string;
1306
+ /** Hostname at the last heartbeat. */
1307
+ hostname: string;
1308
+ /** Operating system (`process.platform`, e.g. `darwin`, `linux`, `win32`). */
1309
+ os: string;
1310
+ /** CPU architecture (`process.arch`, e.g. `arm64`, `x64`). */
1311
+ arch: string;
1312
+ /** CLEO version that sent the last heartbeat. */
1313
+ cleoVersion: string;
1314
+ /** ISO 8601 timestamp of the first heartbeat. */
1315
+ firstSeen: string;
1316
+ /** ISO 8601 timestamp of the most recent heartbeat. */
1317
+ lastHeartbeatAt: string;
1318
+ /** `true` for the device running this command. */
1319
+ current: boolean;
1320
+ }
1321
+
1322
+ /**
1323
+ * One registry row a project NAME matched when the name is not unique
1324
+ * (T12510). Carried in the `E_NEXUS_PROJECT_AMBIGUOUS` error's
1325
+ * `details.candidates`; select one by its `projectId`.
1326
+ */
1327
+ export interface NexusProjectCandidate {
1328
+ /** Immutable project id — pass this instead of the name. */
1329
+ projectId: string;
1330
+ /** Registered project name (shared by every candidate). */
1331
+ name: string;
1332
+ /** Most recently encountered checkout path. */
1333
+ path: string;
1334
+ /** ISO 8601 timestamp the project was last seen. */
1335
+ lastSeen: string;
1336
+ }
1337
+
1338
+ /**
1339
+ * Why a git state probe could not describe (all of) a location (T12511).
1340
+ *
1341
+ * - `E_PATH_MISSING` — the location's directory does not exist.
1342
+ * - `E_PATH_ACCESS` — the directory exists but cannot be read (EACCES/EPERM).
1343
+ * - `E_NOT_GIT_REPO` — neither the directory nor its declared
1344
+ * `evidence.gitRoot` is inside a git work tree.
1345
+ * - `E_GIT_TIMEOUT` — git did not finish inside the per-row budget (a lock,
1346
+ * a credential prompt, a slow filesystem); its process group was killed.
1347
+ * - `E_GIT_FAILED` — git exited non-zero for another reason.
1348
+ * - `E_FETCH_FAILED` — `--fetch` was requested and the fetch failed; the local
1349
+ * fields are still filled and remote fields describe the PREVIOUS fetch.
1350
+ */
1351
+ export type NexusGitProbeErrorCode =
1352
+ | 'E_PATH_MISSING'
1353
+ | 'E_PATH_ACCESS'
1354
+ | 'E_NOT_GIT_REPO'
1355
+ | 'E_GIT_TIMEOUT'
1356
+ | 'E_GIT_FAILED'
1357
+ | 'E_FETCH_FAILED';
1358
+
1359
+ /**
1360
+ * Git state of one project location on one device (`nexus_project_git_state`,
1361
+ * T12511). Keyed like its location: `(projectId, deviceId, path)`.
1362
+ *
1363
+ * Remote fields (`ahead`, `behind`, `remoteHeadSha`) describe the remote as of
1364
+ * the LAST FETCH, never now: the probe does not touch the network unless asked.
1365
+ * `remoteFetchedAt` is that fetch's instant and `remoteStale` says whether it
1366
+ * is older than the staleness window (or unknown), so a `behind: 0` is never
1367
+ * read as "up to date" on the strength of a fetch from last month.
1368
+ */
1369
+ export interface NexusProjectGitState {
1370
+ /** Immutable project id. */
1371
+ projectId: string;
1372
+ /** Registered project name, when the registry row is present. */
1373
+ projectName: string | null;
1374
+ /** Stable id of the device the location is on. */
1375
+ deviceId: string;
1376
+ /** `true` when `deviceId` is the device running this command. */
1377
+ current: boolean;
1378
+ /** Location path (the checkout root CLEO recorded). */
1379
+ path: string;
1380
+ /** Directory git actually ran in: `path`, or its declared `evidence.gitRoot`. */
1381
+ gitRoot: string | null;
1382
+ /** Checked-out branch; `null` when detached or unknown. */
1383
+ branch: string | null;
1384
+ /** HEAD commit; `null` for an unborn branch or when the probe failed. */
1385
+ headSha: string | null;
1386
+ /** `true` when HEAD is detached. */
1387
+ detached: boolean;
1388
+ /** `true` for a shallow clone (ahead/behind may be truncated by the graft). */
1389
+ shallow: boolean;
1390
+ /** Tracked entries with staged or unstaged changes (incl. conflicts). */
1391
+ dirtyCount: number | null;
1392
+ /** Untracked, not-ignored entries. */
1393
+ untrackedCount: number | null;
1394
+ /** Upstream ref (e.g. `origin/main`); `null` when none is configured. */
1395
+ upstream: string | null;
1396
+ /** Commits on HEAD not on the upstream, as of the last fetch. */
1397
+ ahead: number | null;
1398
+ /** Commits on the upstream not on HEAD, as of the last fetch. */
1399
+ behind: number | null;
1400
+ /** Remote the upstream belongs to, else `origin`, else the first remote. */
1401
+ remoteName: string | null;
1402
+ /** That remote's configured URL. */
1403
+ remoteUrl: string | null;
1404
+ /** Upstream tracking-ref commit, as of the last fetch. */
1405
+ remoteHeadSha: string | null;
1406
+ /** ISO 8601 instant of the last fetch (FETCH_HEAD mtime); `null` if never. */
1407
+ remoteFetchedAt: string | null;
1408
+ /** `true` when `remoteFetchedAt` is unknown or older than the staleness window. */
1409
+ remoteStale: boolean;
1410
+ /** ISO 8601 instant this probe ran. */
1411
+ probedAt: string;
1412
+ /** Wall time of the probe, milliseconds. */
1413
+ durationMs: number;
1414
+ /** Why the probe could not describe (all of) the location; `null` on success. */
1415
+ probeErrorCode: NexusGitProbeErrorCode | null;
1416
+ /** Human-readable detail for `probeErrorCode` (git's stderr, the errno, …). */
1417
+ probeError: string | null;
1418
+ }
1419
+
1420
+ /** Parameters for `nexus.projects.status` (T12511). */
1421
+ export interface NexusProjectsStatusParams {
1422
+ /** Run `git fetch` per location first (network). Default `false`. */
1423
+ fetch?: boolean;
1424
+ /** Locations probed at once. Default 8, clamped to 1..64. */
1425
+ concurrency?: number;
1426
+ /** Per-location budget for every git call it makes, ms. Default 10000 (30000 with fetch). */
1427
+ timeoutMs?: number;
1428
+ /** A fetch older than this is `remoteStale`, ms. Default 24h. */
1429
+ staleAfterMs?: number;
1430
+ }
1431
+
1432
+ /** Result of `nexus.projects.status` (T12511). */
1433
+ export interface NexusProjectsStatusResult {
1434
+ /** Fresh probes of this device's live and missing locations. */
1435
+ rows: NexusProjectGitState[];
1436
+ /** Last recorded state of locations on OTHER devices (not re-probed). */
1437
+ otherDevices: NexusProjectGitState[];
1438
+ /** Number of fresh rows. */
1439
+ count: number;
1440
+ /** Device the probe ran on. */
1441
+ deviceId: string;
1442
+ /** Whether a fetch was attempted. */
1443
+ fetched: boolean;
1444
+ /** Effective concurrency. */
1445
+ concurrency: number;
1446
+ /** Effective per-location timeout, ms. */
1447
+ timeoutMs: number;
1448
+ /** Effective staleness window, ms. */
1449
+ staleAfterMs: number;
1450
+ /** Wall time of the whole probe, ms. */
1451
+ durationMs: number;
1452
+ /** Aggregate counts over `rows`. */
1453
+ summary: {
1454
+ /** Rows with no probe error. */
1455
+ ok: number;
1456
+ /** Rows with a probe error. */
1457
+ errored: number;
1458
+ /** Rows that timed out. */
1459
+ timedOut: number;
1460
+ /** Rows with at least one dirty or untracked entry. */
1461
+ dirty: number;
1462
+ /** Rows whose remote state is stale. */
1463
+ remoteStale: number;
1464
+ };
1285
1465
  }
1286
1466
 
1287
1467
  /** Parameters for `nexus.projects.register`. */
@@ -1363,9 +1543,16 @@ export interface NexusProjectsCleanParams {
1363
1543
  vacuum?: boolean;
1364
1544
  }
1365
1545
 
1366
- /** One checkout of a project recorded in the device-local path map (T12354). */
1546
+ /**
1547
+ * Lifecycle state of one project location in `nexus_project_locations` (T12469).
1548
+ * `candidate` (T12470): the checkout declares the id but was never confirmed —
1549
+ * it holds no registry binding and no permissions until promoted explicitly.
1550
+ */
1551
+ export type NexusProjectLocationState = 'live' | 'missing' | 'superseded' | 'candidate';
1552
+
1553
+ /** One recorded location (checkout) of a project on a device (T12354 · T12469). */
1367
1554
  export interface NexusProjectCheckout {
1368
- /** Absolute checkout root on this device. */
1555
+ /** Absolute checkout root on the device named by `deviceId`. */
1369
1556
  projectPath: string;
1370
1557
  /** Path fingerprint of the checkout. */
1371
1558
  projectHash: string;
@@ -1373,8 +1560,12 @@ export interface NexusProjectCheckout {
1373
1560
  firstSeen: string;
1374
1561
  /** ISO 8601 timestamp the checkout was last encountered. */
1375
1562
  lastSeen: string;
1376
- /** Whether the checkout directory exists now. */
1563
+ /** Whether the checkout directory exists now (always `false` for another device). */
1377
1564
  exists: boolean;
1565
+ /** Stable id of the device the checkout is on (T12469). */
1566
+ deviceId: string;
1567
+ /** Location state: `live`, `missing` (directory gone) or `superseded` (T12469). */
1568
+ state: NexusProjectLocationState;
1378
1569
  }
1379
1570
 
1380
1571
  /** Why a registry row matched `nexus.projects.clean` criteria (T12324). */
@@ -1464,6 +1655,227 @@ export interface NexusProjectsCleanResult {
1464
1655
  matchedByReason: Partial<Record<NexusProjectsCleanReason, number>>;
1465
1656
  /** Receipt of the applied removal; absent on dry-run or when nothing matched (T12324). */
1466
1657
  receipt?: NexusProjectsCleanReceipt;
1658
+ /**
1659
+ * Matched rows that were NOT removed because their id was found at another
1660
+ * path (T12471): the project moved, it is not an orphan. Absent when none.
1661
+ */
1662
+ relocated?: NexusProjectsCleanRelocated[];
1663
+ /**
1664
+ * Matched rows that were NOT removed because their path could not be read
1665
+ * (EACCES, EPERM, timeout): only ENOENT/ENOTDIR or a different declared id
1666
+ * prove a project gone (T12471). Absent when none.
1667
+ */
1668
+ unreadable?: NexusProjectsCleanUnreadable[];
1669
+ /**
1670
+ * Matched rows that were NOT removed because their path exists but declares
1671
+ * another project id (T12471). `.cleo/project-id` is tracked, so a branch
1672
+ * checkout, rebase or merge conflict flips it transiently; only an absent
1673
+ * path proves a project gone. Absent when none.
1674
+ */
1675
+ idMismatch?: NexusProjectsCleanIdMismatch[];
1676
+ }
1677
+
1678
+ /** A registry row `nexus.projects.clean` kept because its path declares another id (T12471). */
1679
+ export interface NexusProjectsCleanIdMismatch {
1680
+ /** Immutable registry project ID. */
1681
+ projectId: string;
1682
+ /** Registered path. */
1683
+ projectPath: string;
1684
+ /** The id the path declares now. */
1685
+ declares: string;
1686
+ }
1687
+
1688
+ /** A registry row `nexus.projects.clean` kept because its path was unreadable (T12471). */
1689
+ export interface NexusProjectsCleanUnreadable {
1690
+ /** Immutable registry project ID. */
1691
+ projectId: string;
1692
+ /** Registered path that could not be read. */
1693
+ projectPath: string;
1694
+ }
1695
+
1696
+ /**
1697
+ * A registry row `nexus.projects.clean` refused to remove because its project
1698
+ * id was found at another path on this device (T12471).
1699
+ */
1700
+ export interface NexusProjectsCleanRelocated {
1701
+ /** Immutable registry project ID. */
1702
+ projectId: string;
1703
+ /** Registered path (gone from disk). */
1704
+ projectPath: string;
1705
+ /** Path(s) that declare the same id now. */
1706
+ foundAt: string[];
1707
+ /** Exact command that rebinds the row instead of deleting it. */
1708
+ remedy: string;
1709
+ }
1710
+
1711
+ /**
1712
+ * Classification of one registry row by `cleo doctor projects` (T12471).
1713
+ *
1714
+ * - `ok` — the registered path holds the project.
1715
+ * - `moved` — the path provably no longer holds it and another path on this
1716
+ * device declares the id. `--apply` rebinds the row only when that path's
1717
+ * untracked checkout nonce equals one recorded for the id (`proof:
1718
+ * 'nonce'`); an `id-only` match (a clone, a copied `.cleo/project-id`) is
1719
+ * reported with the `cleo doctor project-identity --resolve` remedy and
1720
+ * never applied. Trash, CLEO-home, tombstoned and reroot-demoted paths are
1721
+ * never targets.
1722
+ * - `ambiguous` — the path is gone and several paths carry the id's nonce;
1723
+ * the operator confirms one with `cleo doctor project-identity --resolve`.
1724
+ * - `split` — one project under two ids, proven by repository evidence: the
1725
+ * registered path is gone and a same-named path with the same git remote or
1726
+ * root commit holds a different id (a re-minted identity), or two registered
1727
+ * rows, neither nested in another registered project, share a git remote.
1728
+ * Never changed automatically.
1729
+ * - `possible-split` — the path is gone and a same-named path holds another
1730
+ * id, with no repository evidence tying them: informational (inspect, never
1731
+ * rewrite or unregister the other project). `--apply` still records the
1732
+ * gone location as `missing`.
1733
+ * - `missing` — the path is gone and the id was found nowhere. `--apply`
1734
+ * records the location as `missing`; the row is kept.
1735
+ * - `temp` — the path is under a temp directory while the registry is
1736
+ * persistent (a fixture or scratch directory left in the registry).
1737
+ * - `root` — the path is the home directory or a filesystem root.
1738
+ * - `unreadable` — the path could not be read (EACCES, EPERM, timeout). Only
1739
+ * ENOENT/ENOTDIR prove a path gone, so nothing is changed.
1740
+ * - `other-device` — the path was recorded on another device; not probed.
1741
+ */
1742
+ export type NexusRegistryFindingKind =
1743
+ | 'ok'
1744
+ | 'moved'
1745
+ | 'ambiguous'
1746
+ | 'split'
1747
+ | 'possible-split'
1748
+ | 'missing'
1749
+ | 'temp'
1750
+ | 'root'
1751
+ | 'unreadable'
1752
+ | 'other-device';
1753
+
1754
+ /** The other side of a split identity (T12471). */
1755
+ export interface NexusRegistrySplitPeer {
1756
+ /** The other project id. */
1757
+ projectId: string;
1758
+ /** Where the other id lives. */
1759
+ projectPath: string;
1760
+ /** Whether the other id has its own registry row. */
1761
+ registered: boolean;
1762
+ /** What paired the two ids: a shared git remote or root commit, or the directory name alone. */
1763
+ matchedBy: 'name' | 'remote' | 'root-commit';
1764
+ }
1765
+
1766
+ /** One registry row as classified by `cleo doctor projects` (T12471). */
1767
+ export interface NexusRegistryFinding {
1768
+ /** Immutable registry project ID. */
1769
+ projectId: string;
1770
+ /** Registered project name. */
1771
+ name: string;
1772
+ /** Registered path (may be a `superseded:<id>` sentinel). */
1773
+ projectPath: string;
1774
+ /** Classification. */
1775
+ kind: NexusRegistryFindingKind;
1776
+ /** One-line explanation. */
1777
+ message: string;
1778
+ /** Exact command(s) that fix it, or `null` when nothing needs fixing. */
1779
+ remedy: string | null;
1780
+ /** `true` when `--apply` changes registry rows for this finding. */
1781
+ applicable: boolean;
1782
+ /** Paths on this device that declare this id (`moved`, `ambiguous`). */
1783
+ foundAt?: string[];
1784
+ /**
1785
+ * What ties a `moved` checkout to the row: `nonce` when its untracked
1786
+ * checkout nonce equals one recorded for the id (a real move), `id-only`
1787
+ * when only the committed id matches.
1788
+ */
1789
+ proof?: 'nonce' | 'id-only';
1790
+ /** The other ids of a `split` or `possible-split` identity. */
1791
+ splitWith?: NexusRegistrySplitPeer[];
1792
+ }
1793
+
1794
+ /**
1795
+ * An alias key that more than one registered project claims (T12589).
1796
+ *
1797
+ * The legacy project id encodes only the first 24 bytes of a path, so every
1798
+ * project under a long shared prefix derives the same key. Such a key names
1799
+ * no project: resolvers refuse it, and `cleo doctor projects --apply` removes
1800
+ * its alias row under a receipt.
1801
+ */
1802
+ export interface NexusRegistryAmbiguousAlias {
1803
+ /** The alias key (`nexus_project_id_aliases.legacy_id`). */
1804
+ legacyId: string;
1805
+ /** The project the alias row currently points at (the first to record it). */
1806
+ canonicalId: string;
1807
+ /** Every registered project that claims the key, sorted. */
1808
+ claimants: string[];
1809
+ /** One-line explanation. */
1810
+ message: string;
1811
+ /** Exact command that removes the row. */
1812
+ remedy: string;
1813
+ }
1814
+
1815
+ /** One registry change made by `cleo doctor projects --apply` (T12471). */
1816
+ export interface NexusRegistryRepairAction {
1817
+ /**
1818
+ * `rebind` points the row at `to`; `mark-missing` records the location as
1819
+ * `missing`; `drop-ambiguous-alias` removes the alias row for `alias`
1820
+ * (T12589).
1821
+ */
1822
+ action: 'rebind' | 'mark-missing' | 'drop-ambiguous-alias';
1823
+ /** Project the action applies to (for an alias, the row's `canonical_id`). */
1824
+ projectId: string;
1825
+ /** Registered path before the action. */
1826
+ from: string;
1827
+ /** New registered path (`rebind` only). */
1828
+ to?: string;
1829
+ /** Alias key removed (`drop-ambiguous-alias` only). */
1830
+ alias?: string;
1831
+ /** `applied`, or `skipped` when the row changed between inspection and apply. */
1832
+ outcome: 'applied' | 'skipped';
1833
+ }
1834
+
1835
+ /** Durable receipt of an applied `cleo doctor projects` run (T12471). */
1836
+ export interface NexusRegistryRepairReceipt {
1837
+ /** `nexus_audit_log.id` written in the same transaction as the changes. */
1838
+ receiptId: string;
1839
+ /** Absolute path of the global registry store that was changed. */
1840
+ storePath: string;
1841
+ /** ISO 8601 timestamp of the apply. */
1842
+ appliedAt: string;
1843
+ /** Every action, in order. */
1844
+ actions: NexusRegistryRepairAction[];
1845
+ /** Registry row count before and after; equal counts show no row was lost. */
1846
+ registryRows: { before: number; after: number };
1847
+ /** Exact command that restores the prior rows. */
1848
+ rollback: string;
1849
+ }
1850
+
1851
+ /** Result of `cleo doctor projects` (dry run or apply) (T12471). */
1852
+ export interface NexusRegistryIntegrityReport {
1853
+ /** `true` when nothing was written. */
1854
+ dryRun: boolean;
1855
+ /** Absolute path of the global registry store inspected. */
1856
+ storePath: string;
1857
+ /** Directories scanned for `.cleo/project-id`. */
1858
+ roots: string[];
1859
+ /** Scan coverage: projects found, and directories that timed out or were unreadable. */
1860
+ scan: { projectsFound: number; timedOut: string[]; unreadable: string[] };
1861
+ /** Registry rows per kind; every row is counted. */
1862
+ counts: Record<NexusRegistryFindingKind, number>;
1863
+ /** Every row whose kind is not `ok`. */
1864
+ findings: NexusRegistryFinding[];
1865
+ /** Alias rows whose key more than one project claims (T12589). */
1866
+ ambiguousAliases: NexusRegistryAmbiguousAlias[];
1867
+ /** Receipt of the applied changes; absent on a dry run or when nothing applied. */
1868
+ receipt?: NexusRegistryRepairReceipt;
1869
+ }
1870
+
1871
+ /** Result of `cleo doctor projects --rollback <receiptId>` (T12471). */
1872
+ export interface NexusRegistryRollbackResult {
1873
+ /** The receipt that was rolled back. */
1874
+ receiptId: string;
1875
+ /** `nexus_audit_log.id` of the rollback itself. */
1876
+ rollbackReceiptId: string;
1877
+ /** Rows written back per table (`aliases` when the repair removed alias rows). */
1878
+ restored: { registry: number; locations: number; paths: number; aliases?: number };
1467
1879
  }
1468
1880
 
1469
1881
  /** Parameters for `nexus.refresh-bridge`. */
@@ -1702,6 +2114,7 @@ export type NexusOps = {
1702
2114
  readonly 'projects.remove': readonly [NexusProjectsRemoveParams, NexusProjectsRemoveResult];
1703
2115
  readonly 'projects.scan': readonly [NexusProjectsScanParams, NexusProjectsScanResult];
1704
2116
  readonly 'projects.clean': readonly [NexusProjectsCleanParams, NexusProjectsCleanResult];
2117
+ readonly 'projects.status': readonly [NexusProjectsStatusParams, NexusProjectsStatusResult];
1705
2118
  readonly 'refresh-bridge': readonly [NexusRefreshBridgeParams, NexusRefreshBridgeResult];
1706
2119
  readonly diff: readonly [NexusDiffParams, NexusDiffResult];
1707
2120
  readonly 'query-cte': readonly [NexusQueryCteParams, NexusQueryCteResult];
@@ -2,6 +2,11 @@
2
2
  * Release Domain Operations (7 operations)
3
3
  *
4
4
  * All mutate operations
5
+ *
6
+ * Shared-surface taxonomy (T10483): this file also defines the contract that
7
+ * keeps shipped CLEO release commands and generated workflow templates aligned.
8
+ * The contract is intentionally data-only so downstream validators can assert
9
+ * the release pipeline without importing @cleocode/core or @cleocode/cleo.
5
10
  */
6
11
 
7
12
  /**
@@ -16,6 +21,198 @@ export interface ReleaseGate {
16
21
  reason?: string;
17
22
  }
18
23
 
24
+ // ── T10483: release shared-surface command/template contract ────────────────
25
+
26
+ /**
27
+ * Release-surface taxonomy used by T10468/T10476 to distinguish shipped CLEO
28
+ * consumer tooling, cleocode dogfood-only repository workflows, and contracts
29
+ * that are shared by both surfaces.
30
+ */
31
+ export type ReleaseSurfaceAudience =
32
+ | 'shipped-consumer-tooling'
33
+ | 'cleocode-dogfood-workflow'
34
+ | 'shared-surface';
35
+
36
+ /** Stability expectation for a release surface. */
37
+ export type ReleaseSurfaceStability = 'public-contract' | 'internal-template';
38
+
39
+ /** CLI or dispatch entry point participating in the release shared surface. */
40
+ export interface ReleaseCommandSurfaceContract {
41
+ /** Human-facing command form, e.g. `cleo release plan`. */
42
+ command: string;
43
+ /** Dispatch gateway used by the shipped CLI command. */
44
+ gateway: 'query' | 'mutate';
45
+ /**
46
+ * Dispatch domain and operation, e.g. `release.plan`. Operations routed
47
+ * through the pipeline domain carry the `pipeline.` prefix.
48
+ */
49
+ operation: string;
50
+ /** Surface audience classification for release-product taxonomy. */
51
+ audience: ReleaseSurfaceAudience;
52
+ /** Compatibility/stability promise. */
53
+ stability: ReleaseSurfaceStability;
54
+ /** True when the command may write local deterministic state. */
55
+ writesLocalState: boolean;
56
+ /** True when the command may call GitHub/npm/network services. */
57
+ mayCallNetwork: boolean;
58
+ /** True when an LLM call may block success. MUST remain false for release surfaces. */
59
+ llmBlockingPath: false;
60
+ /** True when identical inputs and repo state yield the same release artifact. */
61
+ deterministic: boolean;
62
+ /** True when this surface must be covered by a changeset before release. */
63
+ changesetRequired: boolean;
64
+ }
65
+
66
+ /** Generated workflow template participating in the release shared surface. */
67
+ export interface ReleaseTemplateSurfaceContract {
68
+ /** Template path in the source tree. */
69
+ template: string;
70
+ /** Rendered workflow filename installed into consumer repositories. */
71
+ renderedWorkflow: string;
72
+ /** Owning/recovering command that operators use for this workflow. */
73
+ owningCommand: string;
74
+ /** Surface audience classification for release-product taxonomy. */
75
+ audience: ReleaseSurfaceAudience;
76
+ /** Compatibility/stability promise. */
77
+ stability: ReleaseSurfaceStability;
78
+ /** True when template execution may call GitHub/npm/network services. */
79
+ mayCallNetwork: boolean;
80
+ /** True when an LLM call may block success. MUST remain false for release surfaces. */
81
+ llmBlockingPath: false;
82
+ /** True when the template consumes a precomputed deterministic plan. */
83
+ consumesReleasePlan: boolean;
84
+ /** True when template drift must be snapshot/render tested. */
85
+ renderSnapshotRequired: boolean;
86
+ }
87
+
88
+ /**
89
+ * Shipped release commands that form the public/operator contract. The first
90
+ * verb, `plan`, is deliberately deterministic and network-free: release
91
+ * planning must be a changesets-first local computation, not an LLM-first or
92
+ * GitHub-first blocking path.
93
+ */
94
+ export const RELEASE_SHARED_COMMAND_SURFACES = [
95
+ {
96
+ command: 'cleo release plan',
97
+ gateway: 'mutate',
98
+ operation: 'release.plan',
99
+ audience: 'shared-surface',
100
+ stability: 'public-contract',
101
+ writesLocalState: true,
102
+ mayCallNetwork: false,
103
+ llmBlockingPath: false,
104
+ deterministic: true,
105
+ changesetRequired: true,
106
+ },
107
+ {
108
+ command: 'cleo release open',
109
+ gateway: 'mutate',
110
+ operation: 'release.open',
111
+ audience: 'shared-surface',
112
+ stability: 'public-contract',
113
+ writesLocalState: true,
114
+ mayCallNetwork: true,
115
+ llmBlockingPath: false,
116
+ deterministic: false,
117
+ changesetRequired: true,
118
+ },
119
+ {
120
+ command: 'cleo release reconcile',
121
+ gateway: 'mutate',
122
+ operation: 'release.reconcile',
123
+ audience: 'shared-surface',
124
+ stability: 'public-contract',
125
+ writesLocalState: true,
126
+ mayCallNetwork: true,
127
+ llmBlockingPath: false,
128
+ deterministic: true,
129
+ changesetRequired: true,
130
+ },
131
+ {
132
+ command: 'cleo release rollback',
133
+ gateway: 'mutate',
134
+ operation: 'pipeline.release.rollback',
135
+ audience: 'shipped-consumer-tooling',
136
+ stability: 'public-contract',
137
+ writesLocalState: true,
138
+ mayCallNetwork: false,
139
+ llmBlockingPath: false,
140
+ deterministic: true,
141
+ changesetRequired: true,
142
+ },
143
+ ] as const satisfies readonly ReleaseCommandSurfaceContract[];
144
+
145
+ /**
146
+ * Workflow templates that must stay in parity with the release command surface.
147
+ *
148
+ * Every entry is a SHIPPED template (registered as kind `workflow` in the core
149
+ * template manifest and rendered into consumer repositories), so none is
150
+ * classified `cleocode-dogfood-workflow`: that audience is reserved for the
151
+ * cleocode repository's own `.github/workflows/*.yml` owner CI, which is never
152
+ * installed into consumers. `release-prepare` and `release-publish` bind to a
153
+ * public release verb and consume the release plan; `release-rollback` records
154
+ * through `cleo release reconcile --rollback`, and `release-fanout` runs on the
155
+ * GitHub `release: published` event with no CLEO verb of its own, so its
156
+ * recovering command is the template refresh surface `cleo upgrade workflows`.
157
+ */
158
+ export const RELEASE_SHARED_TEMPLATE_SURFACES = [
159
+ {
160
+ template: 'packages/core/templates/workflows/release-prepare.yml.tmpl',
161
+ renderedWorkflow: 'release-prepare.yml',
162
+ owningCommand: 'cleo release open',
163
+ audience: 'shared-surface',
164
+ stability: 'public-contract',
165
+ mayCallNetwork: true,
166
+ llmBlockingPath: false,
167
+ consumesReleasePlan: true,
168
+ renderSnapshotRequired: true,
169
+ },
170
+ {
171
+ template: 'packages/core/templates/workflows/release-publish.yml.tmpl',
172
+ renderedWorkflow: 'release-publish.yml',
173
+ owningCommand: 'cleo release reconcile',
174
+ audience: 'shared-surface',
175
+ stability: 'public-contract',
176
+ mayCallNetwork: true,
177
+ llmBlockingPath: false,
178
+ consumesReleasePlan: true,
179
+ renderSnapshotRequired: true,
180
+ },
181
+ {
182
+ template: 'packages/core/templates/workflows/release-rollback.yml.tmpl',
183
+ renderedWorkflow: 'release-rollback.yml',
184
+ owningCommand: 'cleo release reconcile',
185
+ audience: 'shipped-consumer-tooling',
186
+ stability: 'internal-template',
187
+ mayCallNetwork: true,
188
+ llmBlockingPath: false,
189
+ consumesReleasePlan: false,
190
+ renderSnapshotRequired: true,
191
+ },
192
+ {
193
+ template: 'packages/core/templates/workflows/release-fanout.yml.tmpl',
194
+ renderedWorkflow: 'release-fanout.yml',
195
+ owningCommand: 'cleo upgrade workflows',
196
+ audience: 'shipped-consumer-tooling',
197
+ stability: 'internal-template',
198
+ mayCallNetwork: true,
199
+ llmBlockingPath: false,
200
+ consumesReleasePlan: false,
201
+ renderSnapshotRequired: true,
202
+ },
203
+ ] as const satisfies readonly ReleaseTemplateSurfaceContract[];
204
+
205
+ /** All commands/templates that share the release-product contract. */
206
+ export const RELEASE_SHARED_SURFACE_CONTRACT = {
207
+ commands: RELEASE_SHARED_COMMAND_SURFACES,
208
+ templates: RELEASE_SHARED_TEMPLATE_SURFACES,
209
+ invariants: {
210
+ deterministicPlanningCommand: 'cleo release plan',
211
+ llmBlockingPathAllowed: false,
212
+ changesetRequiredForPublicCommands: true,
213
+ },
214
+ } as const;
215
+
19
216
  /**
20
217
  * Mutate Operations
21
218
  */
@@ -613,12 +613,14 @@ export interface SessionEndParams {
613
613
  /**
614
614
  * Explicit session id to end (T11346 · Epic T11284).
615
615
  *
616
- * When omitted, session-end targets the CALLER's session resolved env-first
617
- * (`CLEO_SESSION_ID` → … → most-recent active). When provided, this id
618
- * overrides env/active resolution. Either way, one agent can no longer end a
619
- * different concurrently-active agent's session implicitly.
616
+ * When omitted, session-end targets the CALLER's bound session (connection
617
+ * handle → `CLEO_SESSION_ID` → terminal binding). An unbound caller gets
618
+ * `E_SESSION_UNBOUND` while any session is active — the newest active row is
619
+ * never the implicit target (T12500). When provided, this id overrides
620
+ * resolution (CLI: `cleo session end --session <id>`).
620
621
  *
621
622
  * @task T11346
623
+ * @task T12500
622
624
  */
623
625
  sessionId?: string;
624
626
  }