@cleocode/core 2026.4.11 → 2026.4.13

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 (184) hide show
  1. package/dist/codebase-map/analyzers/architecture.d.ts.map +1 -1
  2. package/dist/codebase-map/analyzers/architecture.js +0 -1
  3. package/dist/codebase-map/analyzers/architecture.js.map +1 -1
  4. package/dist/conduit/local-transport.d.ts +18 -8
  5. package/dist/conduit/local-transport.d.ts.map +1 -1
  6. package/dist/conduit/local-transport.js +23 -13
  7. package/dist/conduit/local-transport.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +0 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +6 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.js +175 -68950
  16. package/dist/index.js.map +1 -7
  17. package/dist/init.d.ts +1 -2
  18. package/dist/init.d.ts.map +1 -1
  19. package/dist/init.js +1 -2
  20. package/dist/init.js.map +1 -1
  21. package/dist/internal.d.ts +8 -3
  22. package/dist/internal.d.ts.map +1 -1
  23. package/dist/internal.js +13 -6
  24. package/dist/internal.js.map +1 -1
  25. package/dist/memory/learnings.d.ts +2 -2
  26. package/dist/memory/patterns.d.ts +6 -6
  27. package/dist/output.d.ts +32 -11
  28. package/dist/output.d.ts.map +1 -1
  29. package/dist/output.js +67 -67
  30. package/dist/output.js.map +1 -1
  31. package/dist/paths.js +80 -14
  32. package/dist/paths.js.map +1 -1
  33. package/dist/skills/dynamic-skill-generator.d.ts +0 -2
  34. package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
  35. package/dist/skills/dynamic-skill-generator.js.map +1 -1
  36. package/dist/store/agent-registry-accessor.d.ts +203 -12
  37. package/dist/store/agent-registry-accessor.d.ts.map +1 -1
  38. package/dist/store/agent-registry-accessor.js +618 -100
  39. package/dist/store/agent-registry-accessor.js.map +1 -1
  40. package/dist/store/api-key-kdf.d.ts +73 -0
  41. package/dist/store/api-key-kdf.d.ts.map +1 -0
  42. package/dist/store/api-key-kdf.js +84 -0
  43. package/dist/store/api-key-kdf.js.map +1 -0
  44. package/dist/store/cleanup-legacy.js +171 -0
  45. package/dist/store/cleanup-legacy.js.map +1 -0
  46. package/dist/store/conduit-sqlite.d.ts +184 -0
  47. package/dist/store/conduit-sqlite.d.ts.map +1 -0
  48. package/dist/store/conduit-sqlite.js +570 -0
  49. package/dist/store/conduit-sqlite.js.map +1 -0
  50. package/dist/store/global-salt.d.ts +78 -0
  51. package/dist/store/global-salt.d.ts.map +1 -0
  52. package/dist/store/global-salt.js +147 -0
  53. package/dist/store/global-salt.js.map +1 -0
  54. package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
  55. package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
  56. package/dist/store/migrate-signaldock-to-conduit.js +555 -0
  57. package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
  58. package/dist/store/nexus-sqlite.js +28 -3
  59. package/dist/store/nexus-sqlite.js.map +1 -1
  60. package/dist/store/signaldock-sqlite.d.ts +122 -19
  61. package/dist/store/signaldock-sqlite.d.ts.map +1 -1
  62. package/dist/store/signaldock-sqlite.js +401 -251
  63. package/dist/store/signaldock-sqlite.js.map +1 -1
  64. package/dist/store/sqlite-backup.js +122 -4
  65. package/dist/store/sqlite-backup.js.map +1 -1
  66. package/dist/system/backup.d.ts +0 -26
  67. package/dist/system/backup.d.ts.map +1 -1
  68. package/dist/system/runtime.d.ts +0 -2
  69. package/dist/system/runtime.d.ts.map +1 -1
  70. package/dist/system/runtime.js +3 -3
  71. package/dist/system/runtime.js.map +1 -1
  72. package/dist/tasks/add.d.ts +1 -1
  73. package/dist/tasks/add.d.ts.map +1 -1
  74. package/dist/tasks/add.js +98 -23
  75. package/dist/tasks/add.js.map +1 -1
  76. package/dist/tasks/complete.d.ts.map +1 -1
  77. package/dist/tasks/complete.js +4 -1
  78. package/dist/tasks/complete.js.map +1 -1
  79. package/dist/tasks/find.d.ts.map +1 -1
  80. package/dist/tasks/find.js +4 -1
  81. package/dist/tasks/find.js.map +1 -1
  82. package/dist/tasks/labels.d.ts.map +1 -1
  83. package/dist/tasks/labels.js +4 -1
  84. package/dist/tasks/labels.js.map +1 -1
  85. package/dist/tasks/relates.d.ts.map +1 -1
  86. package/dist/tasks/relates.js +16 -4
  87. package/dist/tasks/relates.js.map +1 -1
  88. package/dist/tasks/show.d.ts.map +1 -1
  89. package/dist/tasks/show.js +4 -1
  90. package/dist/tasks/show.js.map +1 -1
  91. package/dist/tasks/update.d.ts.map +1 -1
  92. package/dist/tasks/update.js +32 -6
  93. package/dist/tasks/update.js.map +1 -1
  94. package/dist/validation/engine.d.ts.map +1 -1
  95. package/dist/validation/engine.js +16 -4
  96. package/dist/validation/engine.js.map +1 -1
  97. package/dist/validation/param-utils.d.ts +5 -3
  98. package/dist/validation/param-utils.d.ts.map +1 -1
  99. package/dist/validation/param-utils.js +8 -6
  100. package/dist/validation/param-utils.js.map +1 -1
  101. package/dist/validation/protocols/_shared.d.ts.map +1 -1
  102. package/dist/validation/protocols/_shared.js +13 -6
  103. package/dist/validation/protocols/_shared.js.map +1 -1
  104. package/package.json +9 -7
  105. package/src/adapters/__tests__/manager.test.ts +0 -1
  106. package/src/codebase-map/analyzers/architecture.ts +0 -1
  107. package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
  108. package/src/conduit/__tests__/local-transport.test.ts +14 -12
  109. package/src/conduit/local-transport.ts +23 -13
  110. package/src/config.ts +0 -1
  111. package/src/errors.ts +24 -0
  112. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
  113. package/src/init.ts +1 -2
  114. package/src/internal.ts +96 -2
  115. package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
  116. package/src/memory/__tests__/engine-compat.test.ts +2 -2
  117. package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
  118. package/src/observability/__tests__/index.test.ts +4 -4
  119. package/src/observability/__tests__/log-filter.test.ts +4 -4
  120. package/src/output.ts +73 -75
  121. package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
  122. package/src/sessions/__tests__/session-grade.test.ts +2 -2
  123. package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
  124. package/src/skills/dynamic-skill-generator.ts +0 -2
  125. package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
  126. package/src/store/__tests__/api-key-kdf.test.ts +113 -0
  127. package/src/store/__tests__/backup-crypto.test.ts +101 -0
  128. package/src/store/__tests__/backup-pack.test.ts +491 -0
  129. package/src/store/__tests__/backup-unpack.test.ts +298 -0
  130. package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
  131. package/src/store/__tests__/global-salt.test.ts +195 -0
  132. package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
  133. package/src/store/__tests__/regenerators.test.ts +234 -0
  134. package/src/store/__tests__/restore-conflict-report.test.ts +274 -0
  135. package/src/store/__tests__/restore-json-merge.test.ts +521 -0
  136. package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
  137. package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
  138. package/src/store/__tests__/sqlite-backup.test.ts +5 -1
  139. package/src/store/__tests__/t310-integration.test.ts +1150 -0
  140. package/src/store/__tests__/t310-readiness.test.ts +111 -0
  141. package/src/store/__tests__/t311-integration.test.ts +661 -0
  142. package/src/store/agent-registry-accessor.ts +847 -140
  143. package/src/store/api-key-kdf.ts +104 -0
  144. package/src/store/backup-crypto.ts +209 -0
  145. package/src/store/backup-pack.ts +739 -0
  146. package/src/store/backup-unpack.ts +583 -0
  147. package/src/store/conduit-sqlite.ts +655 -0
  148. package/src/store/global-salt.ts +175 -0
  149. package/src/store/migrate-signaldock-to-conduit.ts +669 -0
  150. package/src/store/regenerators.ts +243 -0
  151. package/src/store/restore-conflict-report.ts +317 -0
  152. package/src/store/restore-json-merge.ts +653 -0
  153. package/src/store/signaldock-sqlite.ts +431 -254
  154. package/src/store/sqlite-backup.ts +185 -10
  155. package/src/store/t310-readiness.ts +119 -0
  156. package/src/system/backup.ts +2 -62
  157. package/src/system/runtime.ts +4 -6
  158. package/src/tasks/__tests__/error-hints.test.ts +256 -0
  159. package/src/tasks/add.ts +99 -9
  160. package/src/tasks/complete.ts +4 -1
  161. package/src/tasks/find.ts +4 -1
  162. package/src/tasks/labels.ts +4 -1
  163. package/src/tasks/relates.ts +16 -4
  164. package/src/tasks/show.ts +4 -1
  165. package/src/tasks/update.ts +32 -3
  166. package/src/validation/__tests__/error-hints.test.ts +97 -0
  167. package/src/validation/engine.ts +16 -1
  168. package/src/validation/param-utils.ts +10 -7
  169. package/src/validation/protocols/_shared.ts +14 -6
  170. package/src/validation/protocols/cant/architecture-decision.cant +80 -0
  171. package/src/validation/protocols/cant/artifact-publish.cant +95 -0
  172. package/src/validation/protocols/cant/consensus.cant +74 -0
  173. package/src/validation/protocols/cant/contribution.cant +82 -0
  174. package/src/validation/protocols/cant/decomposition.cant +92 -0
  175. package/src/validation/protocols/cant/implementation.cant +67 -0
  176. package/src/validation/protocols/cant/provenance.cant +88 -0
  177. package/src/validation/protocols/cant/release.cant +96 -0
  178. package/src/validation/protocols/cant/research.cant +66 -0
  179. package/src/validation/protocols/cant/specification.cant +67 -0
  180. package/src/validation/protocols/cant/testing.cant +88 -0
  181. package/src/validation/protocols/cant/validation.cant +65 -0
  182. package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
  183. package/templates/config.template.json +0 -1
  184. package/templates/global-config.template.json +0 -1
@@ -2,22 +2,36 @@
2
2
  * SQLite backup via VACUUM INTO with snapshot rotation.
3
3
  *
4
4
  * Produces self-contained, WAL-free copies of CLEO SQLite databases
5
- * (tasks.db, brain.db at project tier; nexus.db at global tier) into
6
- * `.cleo/backups/sqlite/` (project) or `$XDG_DATA_HOME/cleo/backups/sqlite/`
7
- * (global) with a configurable rotation limit. All errors are swallowed —
8
- * backup failure must never interrupt normal operation.
5
+ * (tasks.db, brain.db, conduit.db at project tier; nexus.db, signaldock.db at
6
+ * global tier) into `.cleo/backups/sqlite/` (project) or
7
+ * `$XDG_DATA_HOME/cleo/backups/sqlite/` (global) with a configurable rotation
8
+ * limit. Also provides raw-file backup for the global-salt binary (not SQLite).
9
+ * All errors are swallowed — backup failure must never interrupt normal operation.
9
10
  *
10
11
  * @task T4873
11
12
  * @task T5158 — extended to cover brain.db
12
13
  * @task T306 — extended to cover global-tier nexus.db (epic T299)
14
+ * @task T369 — extended to cover conduit.db (project), signaldock.db (global),
15
+ * and global-salt raw-file backup (epic T310)
13
16
  * @epic T4867
14
17
  */
15
18
 
16
- import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';
19
+ import {
20
+ chmodSync,
21
+ copyFileSync,
22
+ existsSync,
23
+ mkdirSync,
24
+ readdirSync,
25
+ statSync,
26
+ unlinkSync,
27
+ } from 'node:fs';
17
28
  import { join } from 'node:path';
18
29
  import { getCleoDir, getCleoHome } from '../paths.js';
19
30
  import { getBrainNativeDb } from './brain-sqlite.js';
31
+ import { getConduitNativeDb } from './conduit-sqlite.js';
32
+ import { getGlobalSaltPath } from './global-salt.js';
20
33
  import { getNexusNativeDb } from './nexus-sqlite.js';
34
+ import { getGlobalSignaldockNativeDb } from './signaldock-sqlite.js';
21
35
  import { getNativeDb } from './sqlite.js';
22
36
 
23
37
  /** Maximum number of snapshots retained per database (oldest rotated out). */
@@ -46,11 +60,16 @@ interface SnapshotTarget {
46
60
 
47
61
  /**
48
62
  * Canonical list of snapshot targets. Ordering is insertion order — tasks.db
49
- * snapshots first (highest-value operational state), then brain.db.
63
+ * snapshots first (highest-value operational state), then brain.db, then
64
+ * conduit.db (project messaging state).
65
+ *
66
+ * @task T369
67
+ * @epic T310
50
68
  */
51
69
  const SNAPSHOT_TARGETS: SnapshotTarget[] = [
52
70
  { prefix: 'tasks', getDb: getNativeDb },
53
71
  { prefix: 'brain', getDb: getBrainNativeDb },
72
+ { prefix: 'conduit', getDb: getConduitNativeDb }, // Added T369 — project messaging DB
54
73
  ];
55
74
 
56
75
  /**
@@ -308,10 +327,16 @@ export function listSqliteBackupsAll(
308
327
  export type BackupScope = 'project' | 'global';
309
328
 
310
329
  /**
311
- * Registered global-tier snapshot targets. `signaldock` is reserved for T310
312
- * only `nexus` is active in v2026.4.11.
330
+ * Registered global-tier snapshot targets. Both `nexus` and `signaldock` are
331
+ * active as of T369 (epic T310).
332
+ *
333
+ * @task T369
334
+ * @epic T310
313
335
  */
314
- const GLOBAL_SNAPSHOT_TARGETS: SnapshotTarget[] = [{ prefix: 'nexus', getDb: getNexusNativeDb }];
336
+ const GLOBAL_SNAPSHOT_TARGETS: SnapshotTarget[] = [
337
+ { prefix: 'nexus', getDb: getNexusNativeDb },
338
+ { prefix: 'signaldock', getDb: getGlobalSignaldockNativeDb }, // Activated T369 — global agent registry
339
+ ];
315
340
 
316
341
  /**
317
342
  * Resolve the global-tier backup directory, creating it on first use.
@@ -333,12 +358,13 @@ function resolveGlobalBackupDir(cleoHomeOverride?: string): string {
333
358
  * Non-fatal: errors from any individual step are surfaced via the return value
334
359
  * but never thrown — a failed snapshot MUST NOT interrupt normal operation.
335
360
  *
336
- * @param dbName - Which global-tier DB to snapshot (`'nexus'`; `'signaldock'` reserved for T310)
361
+ * @param dbName - Which global-tier DB to snapshot (`'nexus'` or `'signaldock'`)
337
362
  * @param opts.rotation - Maximum retained snapshots per prefix (default 10)
338
363
  * @param opts.cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
339
364
  * @returns Object containing the new snapshot path and any rotated (deleted) file paths
340
365
  *
341
366
  * @task T306
367
+ * @task T369 — activated signaldock target (epic T310)
342
368
  * @epic T299
343
369
  * @why ADR-036 §Backup Mechanism requires VACUUM INTO rotation at the global tier;
344
370
  * nexus.db has zero backup coverage prior to v2026.4.11.
@@ -453,3 +479,152 @@ export function listGlobalSqliteBackups(
453
479
  return [];
454
480
  }
455
481
  }
482
+
483
+ // ============================================================================
484
+ // Global-salt raw-file backup (ADR-037 §5)
485
+ // @task T369
486
+ // @epic T310
487
+ // ============================================================================
488
+
489
+ /** Filename prefix for global-salt backup files. */
490
+ const GLOBAL_SALT_BACKUP_PREFIX = 'global-salt';
491
+
492
+ /** Regex matching global-salt backup filenames: `global-salt-YYYYMMDD-HHmmss`. */
493
+ const GLOBAL_SALT_BACKUP_PATTERN = /^global-salt-\d{8}-\d{6}$/;
494
+
495
+ /**
496
+ * Resolve the backup directory for global-salt files: `{cleoHome}/backups/`.
497
+ * Global-salt backups live directly under `backups/` (not `backups/sqlite/`)
498
+ * to make clear they are binary files, not SQLite databases.
499
+ */
500
+ function resolveGlobalSaltBackupDir(cleoHomeOverride?: string): string {
501
+ const base = cleoHomeOverride ?? getCleoHome();
502
+ return join(base, 'backups');
503
+ }
504
+
505
+ /**
506
+ * Rotate global-salt backup files: delete the oldest until fewer than
507
+ * {@link MAX_SNAPSHOTS} remain. Returns the paths of deleted files.
508
+ * Non-fatal on any filesystem error.
509
+ */
510
+ function rotateGlobalSaltBackups(backupDir: string): string[] {
511
+ const rotated: string[] = [];
512
+ try {
513
+ const files = readdirSync(backupDir)
514
+ .filter((f) => GLOBAL_SALT_BACKUP_PATTERN.test(f))
515
+ .map((f) => ({
516
+ name: f,
517
+ path: join(backupDir, f),
518
+ mtimeMs: statSync(join(backupDir, f)).mtimeMs,
519
+ }))
520
+ .sort((a, b) => a.mtimeMs - b.mtimeMs); // oldest first
521
+
522
+ while (files.length >= MAX_SNAPSHOTS) {
523
+ const oldest = files.shift();
524
+ if (!oldest) break;
525
+ try {
526
+ unlinkSync(oldest.path);
527
+ rotated.push(oldest.path);
528
+ } catch {
529
+ // non-fatal rotation failure
530
+ }
531
+ }
532
+ } catch {
533
+ // non-fatal
534
+ }
535
+ return rotated;
536
+ }
537
+
538
+ /**
539
+ * Creates a raw-file backup of the global-salt binary at
540
+ * `${getCleoHome()}/backups/global-salt-YYYYMMDD-HHmmss` with `0o600`
541
+ * permissions. Rotates to {@link MAX_SNAPSHOTS} (10) copies, deleting the
542
+ * oldest when the limit is reached.
543
+ *
544
+ * Non-fatal: errors are swallowed — salt backup failure must never block cleo.
545
+ * Returns empty strings and no rotated paths on failure.
546
+ *
547
+ * @param opts.cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
548
+ * @returns Object with the new snapshot path and any rotated (deleted) file paths
549
+ *
550
+ * @task T369
551
+ * @epic T310
552
+ * @why ADR-037 §5 — global-salt is security-critical; losing it invalidates
553
+ * all API keys. Backup enables recovery from accidental deletion.
554
+ */
555
+ export async function backupGlobalSalt(opts?: {
556
+ cleoHomeOverride?: string;
557
+ }): Promise<{ snapshotPath: string; rotated: string[] }> {
558
+ try {
559
+ const cleoHome = opts?.cleoHomeOverride ?? getCleoHome();
560
+ const saltSourcePath = opts?.cleoHomeOverride
561
+ ? join(cleoHome, 'global-salt')
562
+ : getGlobalSaltPath();
563
+
564
+ if (!existsSync(saltSourcePath)) {
565
+ return { snapshotPath: '', rotated: [] };
566
+ }
567
+
568
+ const backupDir = resolveGlobalSaltBackupDir(opts?.cleoHomeOverride);
569
+ mkdirSync(backupDir, { recursive: true });
570
+
571
+ const rotated = rotateGlobalSaltBackups(backupDir);
572
+
573
+ const snapshotName = `${GLOBAL_SALT_BACKUP_PREFIX}-${formatTimestamp(new Date())}`;
574
+ const snapshotPath = join(backupDir, snapshotName);
575
+
576
+ copyFileSync(saltSourcePath, snapshotPath);
577
+ chmodSync(snapshotPath, 0o600);
578
+
579
+ return { snapshotPath, rotated };
580
+ } catch {
581
+ // non-fatal — backup failure must never interrupt normal operation
582
+ return { snapshotPath: '', rotated: [] };
583
+ }
584
+ }
585
+
586
+ /**
587
+ * A single entry returned by {@link listGlobalSaltBackups}.
588
+ *
589
+ * @task T369
590
+ * @epic T310
591
+ */
592
+ export interface GlobalSaltBackupEntry {
593
+ /** Backup filename, e.g. `global-salt-20260408-143022`. */
594
+ name: string;
595
+ /** Absolute path to the backup file. */
596
+ path: string;
597
+ /** File size in bytes (should be 32 for a valid global-salt). */
598
+ size: number;
599
+ /** Last-modified timestamp. */
600
+ mtime: Date;
601
+ }
602
+
603
+ /**
604
+ * List global-salt backup files from `$XDG_DATA_HOME/cleo/backups/`, sorted
605
+ * newest-first by mtime.
606
+ *
607
+ * Returns an empty array when the backup directory does not exist.
608
+ *
609
+ * @param cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
610
+ *
611
+ * @task T369
612
+ * @epic T310
613
+ */
614
+ export function listGlobalSaltBackups(cleoHomeOverride?: string): GlobalSaltBackupEntry[] {
615
+ try {
616
+ const backupDir = resolveGlobalSaltBackupDir(cleoHomeOverride);
617
+ if (!existsSync(backupDir)) return [];
618
+
619
+ return readdirSync(backupDir)
620
+ .filter((f) => GLOBAL_SALT_BACKUP_PATTERN.test(f))
621
+ .map((f) => {
622
+ const filePath = join(backupDir, f);
623
+ const s = statSync(filePath);
624
+ return { name: f, path: filePath, size: s.size, mtime: new Date(s.mtimeMs) };
625
+ })
626
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // newest first
627
+ } catch {
628
+ return [];
629
+ }
630
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * T310-readiness gate: detect conduit.db vs legacy signaldock.db at project tier.
3
+ *
4
+ * T311 backup/restore code paths reference `.cleo/conduit.db` (project tier) and
5
+ * `$XDG_DATA_HOME/cleo/signaldock.db` (global tier) per ADR-037. A project that
6
+ * has not yet been migrated (still has `.cleo/signaldock.db` without a
7
+ * `.cleo/conduit.db`) will confuse T311 export/import commands. This gate runs
8
+ * as a precondition on every T311 CLI verb.
9
+ *
10
+ * @task T342
11
+ * @epic T311
12
+ * @why T311 export/import references .cleo/conduit.db (project tier) and
13
+ * $XDG_DATA_HOME/cleo/signaldock.db (global tier) per ADR-037.
14
+ * If the current project is still on the pre-T310 topology, T311
15
+ * commands must surface a clear error telling the user to run a
16
+ * cleo command first to trigger migration.
17
+ * @what Throws T310MigrationRequiredError with instructions if legacy
18
+ * signaldock.db exists AND conduit.db does not.
19
+ */
20
+
21
+ import { existsSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { getProjectRoot } from '../paths.js';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Error class
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Thrown by `assertT310Ready` when the current project is still on the
31
+ * pre-T310 topology: `.cleo/signaldock.db` is present but `.cleo/conduit.db`
32
+ * is absent. T311 commands cannot proceed until migration has run.
33
+ *
34
+ * @task T342
35
+ * @epic T311
36
+ */
37
+ export class T310MigrationRequiredError extends Error {
38
+ /**
39
+ * @param projectRoot - Absolute path to the project root that needs migration.
40
+ */
41
+ constructor(public readonly projectRoot: string) {
42
+ super(
43
+ `T310 migration required: .cleo/signaldock.db still exists at ${projectRoot} ` +
44
+ `without a .cleo/conduit.db. Run any cleo command from within the project ` +
45
+ `(e.g. \`cleo version\`) to trigger the automatic T310 migration, then retry.`,
46
+ );
47
+ this.name = 'T310MigrationRequiredError';
48
+ }
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Public API
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Asserts the current project is on the post-T310 topology. Does nothing
57
+ * if conduit.db exists (migration already ran) OR if no legacy signaldock.db
58
+ * exists (fresh install — no migration needed).
59
+ *
60
+ * Throws when legacy signaldock.db is present AND conduit.db is absent,
61
+ * which indicates the project has not yet been migrated to the T310 topology
62
+ * expected by T311 backup/restore commands.
63
+ *
64
+ * @param projectRoot - Absolute path to the project root. Defaults to
65
+ * `getProjectRoot()` (walks ancestors for `.cleo/` sentinel).
66
+ * @throws {T310MigrationRequiredError} if legacy signaldock.db exists
67
+ * without conduit.db at the project tier.
68
+ *
69
+ * @task T342
70
+ * @epic T311
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Precondition guard at the top of every T311 CLI verb handler:
75
+ * assertT310Ready();
76
+ * ```
77
+ */
78
+ export function assertT310Ready(projectRoot?: string): void {
79
+ const root = projectRoot ?? getProjectRoot();
80
+ const legacyPath = join(root, '.cleo', 'signaldock.db');
81
+ const conduitPath = join(root, '.cleo', 'conduit.db');
82
+
83
+ if (existsSync(legacyPath) && !existsSync(conduitPath)) {
84
+ throw new T310MigrationRequiredError(root);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Returns true if T311 commands can safely run on the current project.
90
+ *
91
+ * This is the non-throwing companion to `assertT310Ready`. Returns false
92
+ * only when the pre-T310 topology is detected (legacy signaldock.db exists
93
+ * without conduit.db). All other states — fresh installs, fully-migrated
94
+ * projects — return true.
95
+ *
96
+ * @param projectRoot - Absolute path to the project root. Defaults to
97
+ * `getProjectRoot()` (walks ancestors for `.cleo/` sentinel).
98
+ * @returns `true` if the project is on the post-T310 topology or is a fresh
99
+ * install; `false` if migration is required.
100
+ *
101
+ * @task T342
102
+ * @epic T311
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * if (!isT310Ready()) {
107
+ * console.error('Run `cleo version` to trigger T310 migration first.');
108
+ * }
109
+ * ```
110
+ */
111
+ export function isT310Ready(projectRoot?: string): boolean {
112
+ try {
113
+ assertT310Ready(projectRoot);
114
+ return true;
115
+ } catch (err) {
116
+ if (err instanceof T310MigrationRequiredError) return false;
117
+ throw err;
118
+ }
119
+ }
@@ -22,7 +22,6 @@
22
22
  *
23
23
  * @task T4783
24
24
  * @task T5158 — extended to use VACUUM INTO for .db files and atomicWrite for JSON
25
- * @task T306 — extended to support global-tier scope (nexus.db; epic T299)
26
25
  */
27
26
 
28
27
  import {
@@ -87,15 +86,6 @@ export interface BackupResult {
87
86
  type: string;
88
87
  /** Files that were successfully captured into this backup. */
89
88
  files: string[];
90
- /**
91
- * Global-tier snapshot results, populated when `opts.includeGlobal` is true.
92
- * Key is the DB name (e.g. `'nexus'`), value is the snapshot path or an
93
- * empty string when the DB was not initialized.
94
- *
95
- * @task T306
96
- * @epic T299
97
- */
98
- global?: Record<string, string>;
99
89
  }
100
90
 
101
91
  /** Result shape returned by {@link restoreBackup}. */
@@ -122,31 +112,12 @@ export interface RestoreResult {
122
112
  * when `safeSqliteSnapshot` asks for them. This makes the function
123
113
  * self-contained — callers do not need to pre-open the DBs.
124
114
  *
125
- * When `opts.includeGlobal` is true, also snapshots global-tier databases
126
- * (currently `nexus.db`) via {@link vacuumIntoGlobalBackup}. Global snapshots
127
- * are written to `$XDG_DATA_HOME/cleo/backups/sqlite/` and returned in
128
- * `result.global`.
129
- *
130
115
  * Async because opening the database engines requires async migration
131
116
  * reconciliation (ADR-012). The CLI dispatch layer awaits this result.
132
- *
133
- * @task T306
134
- * @epic T299
135
117
  */
136
118
  export async function createBackup(
137
119
  projectRoot: string,
138
- opts?: {
139
- type?: string;
140
- note?: string;
141
- /**
142
- * When true, also snapshot global-tier databases (nexus.db).
143
- * Defaults to false for backwards compatibility.
144
- *
145
- * @task T306
146
- * @epic T299
147
- */
148
- includeGlobal?: boolean;
149
- },
120
+ opts?: { type?: string; note?: string },
150
121
  ): Promise<BackupResult> {
151
122
  const cleoDir = join(projectRoot, '.cleo');
152
123
  const btype = opts?.type || 'snapshot';
@@ -241,38 +212,7 @@ export async function createBackup(
241
212
  // non-fatal
242
213
  }
243
214
 
244
- // Global-tier backup (nexus.db) only when explicitly requested.
245
- // @task T306 @epic T299
246
- const globalResults: Record<string, string> = {};
247
- if (opts?.includeGlobal) {
248
- // Ensure nexus.db is initialized so its native handle is available.
249
- try {
250
- const { getNexusDb } = await import('../store/nexus-sqlite.js');
251
- await getNexusDb();
252
- } catch {
253
- // nexus.db open failed — vacuumIntoGlobalBackup will return empty path
254
- }
255
- try {
256
- const { vacuumIntoGlobalBackup } = await import('../store/sqlite-backup.js');
257
- const nexusResult = await vacuumIntoGlobalBackup('nexus');
258
- globalResults['nexus'] = nexusResult.snapshotPath;
259
- } catch {
260
- // non-fatal — global backup failure must not block project backup
261
- globalResults['nexus'] = '';
262
- }
263
- }
264
-
265
- const result: BackupResult = {
266
- backupId,
267
- path: backupDir,
268
- timestamp,
269
- type: btype,
270
- files: backedUp,
271
- };
272
- if (opts?.includeGlobal) {
273
- result.global = globalResults;
274
- }
275
- return result;
215
+ return { backupId, path: backupDir, timestamp, type: btype, files: backedUp };
276
216
  }
277
217
 
278
218
  /** A single backup entry returned by listSystemBackups. */
@@ -35,8 +35,6 @@ export interface RuntimeDiagnostics {
35
35
  };
36
36
  naming: {
37
37
  cli: string;
38
- /** Legacy field. CLI dispatch only. */
39
- mcp: string;
40
38
  server: string;
41
39
  };
42
40
  node: string;
@@ -73,14 +71,14 @@ function detectFromDataRoot(dataRoot: string): RuntimeChannel | null {
73
71
  return null;
74
72
  }
75
73
 
76
- function getExpectedNaming(channel: RuntimeChannel): { cli: string; mcp: string; server: string } {
74
+ function getExpectedNaming(channel: RuntimeChannel): { cli: string; server: string } {
77
75
  switch (channel) {
78
76
  case 'dev':
79
- return { cli: 'cleo-dev', mcp: 'cli', server: 'cleo-dev' };
77
+ return { cli: 'cleo-dev', server: 'cleo-dev' };
80
78
  case 'beta':
81
- return { cli: 'cleo-beta', mcp: 'cli', server: 'cleo-beta' };
79
+ return { cli: 'cleo-beta', server: 'cleo-beta' };
82
80
  default:
83
- return { cli: 'cleo', mcp: 'cli', server: 'cleo' };
81
+ return { cli: 'cleo', server: 'cleo' };
84
82
  }
85
83
  }
86
84