@cleocode/core 2026.6.5 → 2026.6.7

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 (156) hide show
  1. package/dist/dispatch/contracts/output-contracts.d.ts +36 -0
  2. package/dist/dispatch/contracts/output-contracts.d.ts.map +1 -0
  3. package/dist/dispatch/contracts/output-contracts.js +38 -0
  4. package/dist/dispatch/contracts/output-contracts.js.map +1 -0
  5. package/dist/dispatch/describe-operation.d.ts +98 -0
  6. package/dist/dispatch/describe-operation.d.ts.map +1 -0
  7. package/dist/dispatch/describe-operation.js +101 -0
  8. package/dist/dispatch/describe-operation.js.map +1 -0
  9. package/dist/docs/export-document.js +933 -489
  10. package/dist/docs/export-document.js.map +3 -3
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +5 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal.d.ts +2 -0
  16. package/dist/internal.d.ts.map +1 -1
  17. package/dist/internal.js +6 -0
  18. package/dist/internal.js.map +1 -1
  19. package/dist/llm/api-mode.d.ts +64 -0
  20. package/dist/llm/api-mode.d.ts.map +1 -0
  21. package/dist/llm/api-mode.js +72 -0
  22. package/dist/llm/api-mode.js.map +1 -0
  23. package/dist/llm/api.d.ts.map +1 -1
  24. package/dist/llm/api.js +6 -37
  25. package/dist/llm/api.js.map +1 -1
  26. package/dist/llm/auxiliary-fallback.d.ts.map +1 -1
  27. package/dist/llm/auxiliary-fallback.js +24 -38
  28. package/dist/llm/auxiliary-fallback.js.map +1 -1
  29. package/dist/llm/index.d.ts +1 -3
  30. package/dist/llm/index.d.ts.map +1 -1
  31. package/dist/llm/index.js +1 -2
  32. package/dist/llm/index.js.map +1 -1
  33. package/dist/llm/model-metadata.d.ts +14 -0
  34. package/dist/llm/model-metadata.d.ts.map +1 -1
  35. package/dist/llm/model-metadata.js +23 -0
  36. package/dist/llm/model-metadata.js.map +1 -1
  37. package/dist/llm/model-runner.d.ts +127 -0
  38. package/dist/llm/model-runner.d.ts.map +1 -0
  39. package/dist/llm/model-runner.js +312 -0
  40. package/dist/llm/model-runner.js.map +1 -0
  41. package/dist/llm/plugin-facade.js +30084 -1748
  42. package/dist/llm/plugin-facade.js.map +3 -3
  43. package/dist/llm/provider-registry/builtin/anthropic.d.ts.map +1 -1
  44. package/dist/llm/provider-registry/builtin/anthropic.js +4 -0
  45. package/dist/llm/provider-registry/builtin/anthropic.js.map +1 -1
  46. package/dist/llm/provider-registry/builtin/gemini.d.ts.map +1 -1
  47. package/dist/llm/provider-registry/builtin/gemini.js +4 -0
  48. package/dist/llm/provider-registry/builtin/gemini.js.map +1 -1
  49. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  50. package/dist/llm/provider-registry/builtin/ollama.js +4 -0
  51. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  52. package/dist/llm/provider-registry/builtin/openai.d.ts.map +1 -1
  53. package/dist/llm/provider-registry/builtin/openai.js +6 -0
  54. package/dist/llm/provider-registry/builtin/openai.js.map +1 -1
  55. package/dist/llm/role-executor.d.ts +6 -5
  56. package/dist/llm/role-executor.d.ts.map +1 -1
  57. package/dist/llm/role-executor.js +40 -86
  58. package/dist/llm/role-executor.js.map +1 -1
  59. package/dist/llm/role-resolver.d.ts +28 -1
  60. package/dist/llm/role-resolver.d.ts.map +1 -1
  61. package/dist/llm/role-resolver.js +10 -0
  62. package/dist/llm/role-resolver.js.map +1 -1
  63. package/dist/llm/session-factory.d.ts +4 -6
  64. package/dist/llm/session-factory.d.ts.map +1 -1
  65. package/dist/llm/session-factory.js +6 -72
  66. package/dist/llm/session-factory.js.map +1 -1
  67. package/dist/llm/system-resolver.d.ts.map +1 -1
  68. package/dist/llm/system-resolver.js +6 -0
  69. package/dist/llm/system-resolver.js.map +1 -1
  70. package/dist/llm/tool-loop.d.ts.map +1 -1
  71. package/dist/llm/tool-loop.js +9 -32
  72. package/dist/llm/tool-loop.js.map +1 -1
  73. package/dist/llm/transports/index.d.ts +5 -3
  74. package/dist/llm/transports/index.d.ts.map +1 -1
  75. package/dist/llm/transports/index.js +5 -2
  76. package/dist/llm/transports/index.js.map +1 -1
  77. package/dist/reconciliation/reconciliation-engine.d.ts.map +1 -1
  78. package/dist/reconciliation/reconciliation-engine.js +3 -0
  79. package/dist/reconciliation/reconciliation-engine.js.map +1 -1
  80. package/dist/release/plan.d.ts +27 -0
  81. package/dist/release/plan.d.ts.map +1 -1
  82. package/dist/release/plan.js +36 -2
  83. package/dist/release/plan.js.map +1 -1
  84. package/dist/release/provenance-fk.d.ts +74 -0
  85. package/dist/release/provenance-fk.d.ts.map +1 -0
  86. package/dist/release/provenance-fk.js +122 -0
  87. package/dist/release/provenance-fk.js.map +1 -0
  88. package/dist/release/reconcile.d.ts +10 -0
  89. package/dist/release/reconcile.d.ts.map +1 -1
  90. package/dist/release/reconcile.js +107 -2
  91. package/dist/release/reconcile.js.map +1 -1
  92. package/dist/sticky/convert.d.ts.map +1 -1
  93. package/dist/sticky/convert.js +3 -0
  94. package/dist/sticky/convert.js.map +1 -1
  95. package/dist/store/exodus/column-transforms.d.ts +275 -0
  96. package/dist/store/exodus/column-transforms.d.ts.map +1 -0
  97. package/dist/store/exodus/column-transforms.js +478 -0
  98. package/dist/store/exodus/column-transforms.js.map +1 -0
  99. package/dist/store/exodus/count-parity.d.ts +71 -0
  100. package/dist/store/exodus/count-parity.d.ts.map +1 -0
  101. package/dist/store/exodus/count-parity.js +124 -0
  102. package/dist/store/exodus/count-parity.js.map +1 -0
  103. package/dist/store/exodus/health.d.ts +70 -0
  104. package/dist/store/exodus/health.d.ts.map +1 -0
  105. package/dist/store/exodus/health.js +130 -0
  106. package/dist/store/exodus/health.js.map +1 -0
  107. package/dist/store/exodus/index.d.ts +3 -0
  108. package/dist/store/exodus/index.d.ts.map +1 -1
  109. package/dist/store/exodus/index.js +3 -0
  110. package/dist/store/exodus/index.js.map +1 -1
  111. package/dist/store/exodus/migrate.d.ts.map +1 -1
  112. package/dist/store/exodus/migrate.js +103 -298
  113. package/dist/store/exodus/migrate.js.map +1 -1
  114. package/dist/store/exodus/plan.d.ts +48 -4
  115. package/dist/store/exodus/plan.d.ts.map +1 -1
  116. package/dist/store/exodus/plan.js +67 -9
  117. package/dist/store/exodus/plan.js.map +1 -1
  118. package/dist/store/exodus/seal.d.ts +69 -0
  119. package/dist/store/exodus/seal.d.ts.map +1 -0
  120. package/dist/store/exodus/seal.js +73 -0
  121. package/dist/store/exodus/seal.js.map +1 -0
  122. package/dist/store/exodus/types.d.ts +24 -1
  123. package/dist/store/exodus/types.d.ts.map +1 -1
  124. package/dist/store/exodus/types.js.map +1 -1
  125. package/dist/store/exodus/verify-migration.d.ts.map +1 -1
  126. package/dist/store/exodus/verify-migration.js +109 -24
  127. package/dist/store/exodus/verify-migration.js.map +1 -1
  128. package/dist/tasks/add.d.ts +13 -0
  129. package/dist/tasks/add.d.ts.map +1 -1
  130. package/dist/tasks/add.js +50 -18
  131. package/dist/tasks/add.js.map +1 -1
  132. package/dist/tasks/archive.d.ts.map +1 -1
  133. package/dist/tasks/archive.js +12 -7
  134. package/dist/tasks/archive.js.map +1 -1
  135. package/dist/tasks/child-disposition.d.ts +66 -0
  136. package/dist/tasks/child-disposition.d.ts.map +1 -0
  137. package/dist/tasks/child-disposition.js +80 -0
  138. package/dist/tasks/child-disposition.js.map +1 -0
  139. package/dist/tasks/delete-preview.js +1 -1
  140. package/dist/tasks/delete-preview.js.map +1 -1
  141. package/dist/tasks/deletion-strategy.d.ts +21 -3
  142. package/dist/tasks/deletion-strategy.d.ts.map +1 -1
  143. package/dist/tasks/deletion-strategy.js +61 -15
  144. package/dist/tasks/deletion-strategy.js.map +1 -1
  145. package/dist/tasks/engine-wrap.d.ts +8 -0
  146. package/dist/tasks/engine-wrap.d.ts.map +1 -1
  147. package/dist/tasks/engine-wrap.js +22 -9
  148. package/dist/tasks/engine-wrap.js.map +1 -1
  149. package/dist/tasks/update.d.ts.map +1 -1
  150. package/dist/tasks/update.js +12 -0
  151. package/dist/tasks/update.js.map +1 -1
  152. package/package.json +12 -12
  153. package/dist/llm/transports/openai.d.ts +0 -181
  154. package/dist/llm/transports/openai.d.ts.map +0 -1
  155. package/dist/llm/transports/openai.js +0 -645
  156. package/dist/llm/transports/openai.js.map +0 -1
@@ -11251,6 +11251,87 @@ var init_nexus_scope_map = __esm({
11251
11251
  }
11252
11252
  });
11253
11253
 
11254
+ // packages/contracts/src/operations/output-contracts-data.ts
11255
+ var TASK_MUTATION_DATA_SCHEMA, tasksAddOutputContract, tasksAddBatchOutputContract, tasksUpdateOutputContract, tasksCompleteOutputContract;
11256
+ var init_output_contracts_data = __esm({
11257
+ "packages/contracts/src/operations/output-contracts-data.ts"() {
11258
+ "use strict";
11259
+ TASK_MUTATION_DATA_SCHEMA = {
11260
+ type: "object",
11261
+ required: ["count", "created", "updated", "deleted"],
11262
+ additionalProperties: true,
11263
+ properties: {
11264
+ count: { type: "number", description: "Number of records the mutation affected." },
11265
+ created: {
11266
+ type: "array",
11267
+ description: 'Task IDs created by the mutation (bare strings, e.g. "T11692"). Empty for update/delete-only mutations.',
11268
+ items: { type: "string" }
11269
+ },
11270
+ updated: {
11271
+ type: "array",
11272
+ description: "Task IDs updated by the mutation (bare strings). Empty for create/delete-only mutations.",
11273
+ items: { type: "string" }
11274
+ },
11275
+ deleted: {
11276
+ type: "array",
11277
+ description: "Task IDs deleted by the mutation (bare strings). Empty for create/update-only mutations.",
11278
+ items: { type: "string" }
11279
+ },
11280
+ ids: {
11281
+ type: "array",
11282
+ description: "Deprecated alias for the non-empty bucket. Prefer created/updated/deleted.",
11283
+ items: { type: "string" }
11284
+ },
11285
+ dryRun: { type: "boolean", description: "True when this was a preview-only mutation." },
11286
+ status: { type: "string", description: "Post-mutation task status (add/update/complete)." }
11287
+ }
11288
+ };
11289
+ tasksAddOutputContract = {
11290
+ operation: "tasks.add",
11291
+ shapeNote: 'The created task ID (bare string) is at /data/created/0 \u2014 NOT /data/created/0/id. Example: /data/created/0 \u2192 "T11692".',
11292
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11293
+ fieldPointers: ["/data/created/0", "/data/count"]
11294
+ };
11295
+ tasksAddBatchOutputContract = {
11296
+ operation: "tasks.add-batch",
11297
+ shapeNote: "Atomic batch insert. Each created task ID (bare string) is in /data/created (array). Dry-run projections are at root: /data/wouldCreate and /data/insertedCount (=0). NOT under /data/dryRunSummary.",
11298
+ dataSchema: {
11299
+ type: "object",
11300
+ required: ["count", "created", "updated", "deleted"],
11301
+ additionalProperties: true,
11302
+ properties: {
11303
+ ...TASK_MUTATION_DATA_SCHEMA.properties,
11304
+ wouldCreate: {
11305
+ type: "number",
11306
+ description: "Dry-run: predicted write count. Present only when dryRun=true."
11307
+ },
11308
+ insertedCount: {
11309
+ type: "number",
11310
+ description: "Dry-run: always 0 (no DB write). Present only when dryRun=true."
11311
+ },
11312
+ wouldAffect: {
11313
+ type: "number",
11314
+ description: "Dry-run: generic affected count. Present only when dryRun=true."
11315
+ }
11316
+ }
11317
+ },
11318
+ fieldPointers: ["/data/created/0", "/data/count", "/data/wouldCreate", "/data/insertedCount"]
11319
+ };
11320
+ tasksUpdateOutputContract = {
11321
+ operation: "tasks.update",
11322
+ shapeNote: "The updated task ID (bare string) is at /data/updated/0 \u2014 NOT /data/updated/0/id. Use /data/status for the post-mutation status.",
11323
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11324
+ fieldPointers: ["/data/updated/0", "/data/status", "/data/count"]
11325
+ };
11326
+ tasksCompleteOutputContract = {
11327
+ operation: "tasks.complete",
11328
+ shapeNote: "Completion is a status mutation \u2014 the task ID (bare string) is at /data/updated/0 (status=done). Use /data/status for the post-mutation status.",
11329
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11330
+ fieldPointers: ["/data/updated/0", "/data/status", "/data/count"]
11331
+ };
11332
+ }
11333
+ });
11334
+
11254
11335
  // packages/contracts/src/peer.ts
11255
11336
  var init_peer = __esm({
11256
11337
  "packages/contracts/src/peer.ts"() {
@@ -12512,6 +12593,7 @@ var init_src = __esm({
12512
12593
  init_docs();
12513
12594
  init_operations();
12514
12595
  init_nexus_scope_map();
12596
+ init_output_contracts_data();
12515
12597
  init_params();
12516
12598
  init_tasks();
12517
12599
  init_peer();
@@ -22514,8 +22596,8 @@ function probeAndMarkApplied(nativeDb, migration, logSubsystem) {
22514
22596
  });
22515
22597
  if (allAltersPresent && allTablesPresent && allIndexesPresent && allTriggersPresent) {
22516
22598
  insertJournalEntry(nativeDb, migration.hash, migration.folderMillis, migration.name ?? "");
22517
- const log8 = getLogger(logSubsystem);
22518
- log8.debug(
22599
+ const log10 = getLogger(logSubsystem);
22600
+ log10.debug(
22519
22601
  {
22520
22602
  migration: migration.name,
22521
22603
  alters: alterTargets.length,
@@ -22555,14 +22637,14 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22555
22637
  if (hasOrphanedEntries) {
22556
22638
  const dbHashes = new Set(dbEntries.map((e) => e.hash));
22557
22639
  const allLocalHashesPresentInDb = localMigrations.every((m) => dbHashes.has(m.hash));
22558
- const log8 = getLogger(logSubsystem);
22640
+ const log10 = getLogger(logSubsystem);
22559
22641
  if (allLocalHashesPresentInDb) {
22560
- log8.debug(
22642
+ log10.debug(
22561
22643
  { extra: orphanedEntries.length },
22562
22644
  `Migration journal has ${orphanedEntries.length} entries for migrations not known to this install (DB is ahead). Skipping reconciliation.`
22563
22645
  );
22564
22646
  } else {
22565
- log8.warn(
22647
+ log10.warn(
22566
22648
  { orphaned: orphanedEntries.length },
22567
22649
  `Detected ${orphanedEntries.length} true-orphan journal entries from a previous CLEO lineage. Reconciling via DDL probe.`
22568
22650
  );
@@ -22598,8 +22680,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22598
22680
  if (alterMatches.length === 0) {
22599
22681
  const stripped = fullSql.replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
22600
22682
  if (stripped === "") {
22601
- const log8 = getLogger(logSubsystem);
22602
- log8.debug(
22683
+ const log10 = getLogger(logSubsystem);
22684
+ log10.debug(
22603
22685
  { migration: migration.name },
22604
22686
  `Migration ${migration.name} is a comment-only baseline marker \u2014 marking applied on existing DB.`
22605
22687
  );
@@ -22636,8 +22718,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22636
22718
  }
22637
22719
  }
22638
22720
  if (missingColumns.length === 0) {
22639
- const log8 = getLogger(logSubsystem);
22640
- log8.warn(
22721
+ const log10 = getLogger(logSubsystem);
22722
+ log10.warn(
22641
22723
  { migration: migration.name, columns: alterMatches },
22642
22724
  `Detected partially-applied migration ${migration.name} \u2014 columns exist but journal entry missing. Auto-reconciling.`
22643
22725
  );
@@ -22645,8 +22727,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22645
22727
  continue;
22646
22728
  }
22647
22729
  if (existingColumns.length > 0 && missingColumns.length > 0) {
22648
- const log8 = getLogger(logSubsystem);
22649
- log8.warn(
22730
+ const log10 = getLogger(logSubsystem);
22731
+ log10.warn(
22650
22732
  {
22651
22733
  migration: migration.name,
22652
22734
  existingColumns: existingColumns.map((c) => `${c.table}.${c.column}`),
@@ -22658,12 +22740,12 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22658
22740
  if (!tableExists(nativeDb, table)) continue;
22659
22741
  try {
22660
22742
  nativeDb.exec(`ALTER TABLE ${table} ADD COLUMN ${column}${ddl ? ` ${ddl}` : ""}`);
22661
- log8.warn(
22743
+ log10.warn(
22662
22744
  { migration: migration.name, table, column },
22663
22745
  `T920: Added missing column ${table}.${column} to complete partial migration.`
22664
22746
  );
22665
22747
  } catch {
22666
- log8.warn(
22748
+ log10.warn(
22667
22749
  { migration: migration.name, table, column },
22668
22750
  `T920: Could not add missing column ${table}.${column} \u2014 will let Drizzle migrate() handle it.`
22669
22751
  );
@@ -22683,8 +22765,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22683
22765
  for (const entry of unnamedEntries) {
22684
22766
  const migrationName = hashToName.get(entry.hash);
22685
22767
  if (!migrationName) continue;
22686
- const log8 = getLogger(logSubsystem);
22687
- log8.debug(
22768
+ const log10 = getLogger(logSubsystem);
22769
+ log10.debug(
22688
22770
  { id: entry.id, hash: entry.hash, name: migrationName },
22689
22771
  `Backfilling missing name on journal entry id=${entry.id} (Drizzle v1 beta legacy compat).`
22690
22772
  );
@@ -22825,13 +22907,13 @@ function ensureColumns(nativeDb, tableName, requiredColumns, logSubsystem, conte
22825
22907
  const existingCols = new Set(columns.map((c) => c.name));
22826
22908
  for (const req of requiredColumns) {
22827
22909
  if (!existingCols.has(req.name)) {
22828
- const log8 = getLogger(logSubsystem);
22910
+ const log10 = getLogger(logSubsystem);
22829
22911
  const message = `Adding missing column ${tableName}.${req.name} via ALTER TABLE`;
22830
22912
  const fields = { column: req.name, context };
22831
22913
  if (context === "fresh") {
22832
- log8.error(fields, `${message} \u2014 MIGRATION DEFECT (fresh DB should not need repair)`);
22914
+ log10.error(fields, `${message} \u2014 MIGRATION DEFECT (fresh DB should not need repair)`);
22833
22915
  } else {
22834
- log8.warn(fields, message);
22916
+ log10.warn(fields, message);
22835
22917
  }
22836
22918
  nativeDb.exec(`ALTER TABLE ${tableName} ADD COLUMN ${req.name} ${req.ddl}`);
22837
22919
  }
@@ -32023,103 +32105,314 @@ var init_table_name_map = __esm({
32023
32105
  }
32024
32106
  });
32025
32107
 
32026
- // packages/core/src/store/exodus/types.ts
32027
- var EXODUS_TARGET_SCHEMA_VERSION;
32028
- var init_types = __esm({
32029
- "packages/core/src/store/exodus/types.ts"() {
32030
- "use strict";
32031
- EXODUS_TARGET_SCHEMA_VERSION = "drizzle-v1.0.0-rc.3/dual-scope/2026-05";
32032
- }
32033
- });
32034
-
32035
- // packages/core/src/store/exodus/migrate.ts
32036
- import {
32037
- copyFileSync as copyFileSync3,
32038
- existsSync as existsSync7,
32039
- mkdirSync as mkdirSync2,
32040
- readFileSync as readFileSync3,
32041
- renameSync as renameSync2,
32042
- unlinkSync as unlinkSync2,
32043
- writeFileSync as writeFileSync3
32044
- } from "node:fs";
32045
- import { join as join9 } from "node:path";
32046
- function getSqliteVersion(db) {
32047
- try {
32048
- const row = db.prepare("SELECT sqlite_version() AS v").get();
32049
- return row?.v ?? "unknown";
32050
- } catch {
32051
- return "unknown";
32052
- }
32053
- }
32108
+ // packages/core/src/store/exodus/count-parity.ts
32109
+ import { existsSync as existsSync7 } from "node:fs";
32054
32110
  function listTables(db) {
32055
- const rows = db.prepare(
32111
+ return db.prepare(
32056
32112
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
32057
- ).all();
32058
- return rows.map((r) => r.name);
32113
+ ).all().map((r) => r.name);
32059
32114
  }
32060
- function writeJournal(stagingDir, journal) {
32061
- const journalPath = join9(stagingDir, JOURNAL_FILENAME);
32062
- const tmpPath = `${journalPath}.tmp`;
32063
- writeFileSync3(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
32064
- renameSync2(tmpPath, journalPath);
32115
+ function tableExists2(db, tableName) {
32116
+ const escaped = tableName.replace(/'/g, "''");
32117
+ return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
32065
32118
  }
32066
- function readJournal(stagingDir) {
32067
- const journalPath = join9(stagingDir, JOURNAL_FILENAME);
32068
- if (!existsSync7(journalPath)) return null;
32119
+ function rowCount(db, tableName) {
32069
32120
  try {
32070
- return JSON.parse(readFileSync3(journalPath, "utf8"));
32121
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
32122
+ return Number(row?.c ?? 0);
32071
32123
  } catch {
32072
32124
  return null;
32073
32125
  }
32074
32126
  }
32075
- function clearExodusJournal(stagingDir) {
32076
- const journalPath = join9(stagingDir, JOURNAL_FILENAME);
32127
+ function computeCountParity(sources, projectDbPath, globalDbPath) {
32128
+ const entries = [];
32129
+ let skipped = 0;
32130
+ if (!existsSync7(projectDbPath) || !existsSync7(globalDbPath)) {
32131
+ return { ok: false, entries: [], deficits: [], checked: 0, skipped: 0 };
32132
+ }
32133
+ const projectSnap = openCleoDbSnapshot(projectDbPath, { readOnly: true });
32134
+ const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
32077
32135
  try {
32078
- if (!existsSync7(journalPath)) return false;
32079
- unlinkSync2(journalPath);
32080
- log2.info(
32081
- { stagingDir },
32082
- "exodus: cleared migrate journal after abort/rollback \u2014 a retry will RE-COPY all tables"
32083
- );
32084
- return true;
32085
- } catch (err) {
32136
+ for (const src of sources) {
32137
+ if (!existsSync7(src.path)) continue;
32138
+ const srcSnap = openCleoDbSnapshot(src.path, { readOnly: true });
32139
+ try {
32140
+ for (const legacyTable of listTables(srcSnap.db)) {
32141
+ const resolution = resolveConsolidatedTableName(src.name, legacyTable);
32142
+ if (resolution.kind === "skip") {
32143
+ skipped++;
32144
+ continue;
32145
+ }
32146
+ const targetTable = resolution.targetName;
32147
+ const scope = resolveTableTargetScope(src.name, legacyTable, src.targetScope);
32148
+ const targetSnap = scope === "project" ? projectSnap : globalSnap;
32149
+ const sourceCount = rowCount(srcSnap.db, legacyTable);
32150
+ if (sourceCount === null) {
32151
+ skipped++;
32152
+ continue;
32153
+ }
32154
+ const targetCount = tableExists2(targetSnap.db, targetTable) ? rowCount(targetSnap.db, targetTable) ?? 0 : 0;
32155
+ const deficit = targetCount < sourceCount ? sourceCount - targetCount : 0;
32156
+ entries.push({
32157
+ sourceDb: src.name,
32158
+ sourceTable: legacyTable,
32159
+ targetTable,
32160
+ scope,
32161
+ sourceCount,
32162
+ targetCount,
32163
+ deficit
32164
+ });
32165
+ }
32166
+ } finally {
32167
+ srcSnap.close();
32168
+ }
32169
+ }
32170
+ } finally {
32171
+ projectSnap.close();
32172
+ globalSnap.close();
32173
+ }
32174
+ const deficits = entries.filter((e) => e.deficit > 0);
32175
+ if (deficits.length > 0) {
32086
32176
  log2.warn(
32087
- { err, stagingDir },
32088
- 'exodus: failed to clear migrate journal after rollback (a retry may skip already-"done" tables)'
32177
+ { deficitCount: deficits.length, sample: deficits.slice(0, 5) },
32178
+ `exodus count-parity: ${deficits.length} table(s) have FEWER rows in the consolidated target than the legacy source`
32089
32179
  );
32090
- return false;
32091
32180
  }
32181
+ return { ok: deficits.length === 0, entries, deficits, checked: entries.length, skipped };
32092
32182
  }
32093
- function initJournal(sqliteVersion) {
32094
- const now = (/* @__PURE__ */ new Date()).toISOString();
32095
- return {
32096
- version: 1,
32097
- cleoVersion: getCleoVersion(),
32098
- targetSchemaVersion: EXODUS_TARGET_SCHEMA_VERSION,
32099
- nodeVersion: process.version,
32100
- sqliteVersion,
32101
- startedAt: now,
32102
- updatedAt: now,
32103
- tables: []
32104
- };
32183
+ var log2;
32184
+ var init_count_parity = __esm({
32185
+ "packages/core/src/store/exodus/count-parity.ts"() {
32186
+ "use strict";
32187
+ init_logger2();
32188
+ init_open_cleo_db();
32189
+ init_table_name_map();
32190
+ log2 = getLogger("exodus-count-parity");
32191
+ }
32192
+ });
32193
+
32194
+ // packages/core/src/store/exodus/plan.ts
32195
+ import { existsSync as existsSync8, readdirSync as readdirSync2, statfsSync, statSync as statSync2 } from "node:fs";
32196
+ import { join as join9 } from "node:path";
32197
+ function computeRequiredBytes(totalSourceBytes, largestSourceBytes) {
32198
+ return Math.ceil(STAGING_HEADROOM_FACTOR * largestSourceBytes) + totalSourceBytes;
32105
32199
  }
32106
- function lockPath(dbPath) {
32107
- return `${dbPath}${LOCK_SENTINEL_SUFFIX}`;
32200
+ function buildSourceDescriptors(cwd) {
32201
+ const cleoDir = resolveCleoDir(cwd);
32202
+ const cleoHome = getCleoHome();
32203
+ return [
32204
+ // Project-tier — go into consolidated project-scope cleo.db
32205
+ {
32206
+ name: "tasks",
32207
+ path: join9(cleoDir, "tasks.db"),
32208
+ targetScope: "project"
32209
+ },
32210
+ {
32211
+ name: "brain (project)",
32212
+ path: join9(cleoDir, "brain.db"),
32213
+ targetScope: "project"
32214
+ },
32215
+ {
32216
+ name: "conduit",
32217
+ path: join9(cleoDir, "conduit.db"),
32218
+ targetScope: "project"
32219
+ },
32220
+ // Global-tier — go into consolidated global-scope cleo.db
32221
+ {
32222
+ name: "nexus",
32223
+ path: join9(cleoHome, "nexus.db"),
32224
+ targetScope: "global"
32225
+ },
32226
+ {
32227
+ name: "signaldock",
32228
+ path: join9(cleoHome, "signaldock.db"),
32229
+ targetScope: "global"
32230
+ },
32231
+ {
32232
+ name: "skills",
32233
+ path: join9(cleoHome, "skills.db"),
32234
+ targetScope: "global"
32235
+ }
32236
+ ];
32108
32237
  }
32109
- function acquireAdvisoryLock(dbPath) {
32110
- const lp = lockPath(dbPath);
32111
- writeFileSync3(lp, JSON.stringify({ pid: process.pid, ts: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
32238
+ function safeFileBytes(filePath) {
32239
+ try {
32240
+ return statSync2(filePath).size;
32241
+ } catch {
32242
+ return 0;
32243
+ }
32112
32244
  }
32113
- function releaseAdvisoryLock(dbPath) {
32245
+ function getAvailableBytes(dir) {
32114
32246
  try {
32115
- unlinkSync2(lockPath(dbPath));
32247
+ const result = statfsSync(dir);
32248
+ return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
32116
32249
  } catch {
32250
+ return 0;
32117
32251
  }
32118
32252
  }
32119
- function makeAttachAlias(name2, index2) {
32120
- const safe = name2.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").slice(0, 20);
32121
- return `_src_${safe}_${index2}`;
32253
+ function deriveStagingDirName() {
32254
+ const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z");
32255
+ return `exodus-staging-${iso}`;
32256
+ }
32257
+ function findExistingStaging(cleoDir) {
32258
+ try {
32259
+ const entries = readdirSync2(cleoDir, { withFileTypes: true });
32260
+ const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
32261
+ if (stagingDirs.length > 0) {
32262
+ return join9(cleoDir, stagingDirs[0]);
32263
+ }
32264
+ } catch {
32265
+ }
32266
+ return null;
32267
+ }
32268
+ function buildExodusPlan(cwd) {
32269
+ const cleoDir = resolveCleoDir(cwd);
32270
+ const sources = buildSourceDescriptors(cwd);
32271
+ const sourceBytes = sources.map((s) => safeFileBytes(s.path));
32272
+ const totalSourceBytes = sourceBytes.reduce((sum, b) => sum + b, 0);
32273
+ const largestSourceBytes = sourceBytes.reduce((max, b) => Math.max(max, b), 0);
32274
+ const requiredBytes = computeRequiredBytes(totalSourceBytes, largestSourceBytes);
32275
+ const availableBytes = getAvailableBytes(cleoDir);
32276
+ const diskPreflight = totalSourceBytes === 0 || availableBytes >= requiredBytes;
32277
+ const existingStaging = findExistingStaging(cleoDir);
32278
+ const stagingDir = existingStaging ?? join9(cleoDir, deriveStagingDirName());
32279
+ const resumeFromStaging = existingStaging !== null;
32280
+ const projectDbPath = resolveDualScopeDbPath("project", cwd);
32281
+ const globalDbPath = resolveDualScopeDbPath("global");
32282
+ return {
32283
+ sources,
32284
+ totalSourceBytes,
32285
+ largestSourceBytes,
32286
+ requiredBytes,
32287
+ availableBytes,
32288
+ diskPreflight,
32289
+ stagingCopyThresholdBytes: STAGING_COPY_SKIP_THRESHOLD_BYTES,
32290
+ stagingDir,
32291
+ resumeFromStaging,
32292
+ projectDbPath,
32293
+ globalDbPath
32294
+ };
32295
+ }
32296
+ function sourcesPresent(sources) {
32297
+ return sources.some((s) => existsSync8(s.path));
32298
+ }
32299
+ var STAGING_HEADROOM_FACTOR, STAGING_COPY_SKIP_THRESHOLD_BYTES;
32300
+ var init_plan2 = __esm({
32301
+ "packages/core/src/store/exodus/plan.ts"() {
32302
+ "use strict";
32303
+ init_paths();
32304
+ init_dual_scope_db();
32305
+ STAGING_HEADROOM_FACTOR = 1.2;
32306
+ STAGING_COPY_SKIP_THRESHOLD_BYTES = 256 * 1024 * 1024;
32307
+ }
32308
+ });
32309
+
32310
+ // packages/core/src/store/exodus/health.ts
32311
+ import { existsSync as existsSync9, statSync as statSync3 } from "node:fs";
32312
+ function buildExodusHealth(cwd) {
32313
+ const plan = buildExodusPlan(cwd);
32314
+ const anyLegacyPresent = plan.sources.some((s) => existsSync9(s.path));
32315
+ const parity = anyLegacyPresent ? computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath) : { ok: true, entries: [], deficits: [], checked: 0, skipped: 0 };
32316
+ const consolidatedExistsByScope = {
32317
+ project: existsSync9(plan.projectDbPath),
32318
+ global: existsSync9(plan.globalDbPath)
32319
+ };
32320
+ const buildScope = (scope) => {
32321
+ const sources = plan.sources.filter((s) => s.targetScope === scope).map((s) => {
32322
+ const present = existsSync9(s.path);
32323
+ let bytes = 0;
32324
+ if (present) {
32325
+ try {
32326
+ bytes = statSync3(s.path).size;
32327
+ } catch {
32328
+ bytes = 0;
32329
+ }
32330
+ }
32331
+ return { name: s.name, path: s.path, present, bytes, large: bytes >= LARGE_DB_BYTES };
32332
+ });
32333
+ const markerPresent = hasExodusCompleteMarker(scope, cwd);
32334
+ const legacyPresent = sources.some((s) => s.present);
32335
+ const consolidatedExists = consolidatedExistsByScope[scope];
32336
+ const scopeEntries = parity.entries.filter((e) => e.scope === scope);
32337
+ const scopeHasDeficit = scopeEntries.some((e) => e.deficit > 0);
32338
+ const scopeHasData = scopeEntries.some((e) => e.targetCount > 0);
32339
+ let state;
32340
+ if (markerPresent) {
32341
+ state = "sealed";
32342
+ } else if (legacyPresent && consolidatedExists && scopeHasData && !scopeHasDeficit) {
32343
+ state = "migrated-unsealed";
32344
+ } else if (legacyPresent) {
32345
+ state = "needs-migration";
32346
+ } else {
32347
+ state = "no-cleo-data";
32348
+ }
32349
+ const stranded = sources.filter((s) => s.present && markerPresent).map((s) => s.name);
32350
+ return {
32351
+ scope,
32352
+ state,
32353
+ consolidatedExists,
32354
+ markerPresent,
32355
+ legacySources: sources,
32356
+ strandedResidue: stranded
32357
+ };
32358
+ };
32359
+ const project = buildScope("project");
32360
+ const global = buildScope("global");
32361
+ const largeLegacyDbs = [
32362
+ ...project.legacySources.filter((s) => s.large).map((s) => ({ name: s.name, scope: "project", bytes: s.bytes })),
32363
+ ...global.legacySources.filter((s) => s.large).map((s) => ({ name: s.name, scope: "global", bytes: s.bytes }))
32364
+ ];
32365
+ const recommendations = [];
32366
+ for (const sc of [project, global]) {
32367
+ if (sc.state === "migrated-unsealed") {
32368
+ recommendations.push(
32369
+ `${sc.scope}: data is consolidated but unsealed \u2014 run \`cleo exodus seal --scope ${sc.scope}\` to archive legacy DBs + stop on-open re-firing.`
32370
+ );
32371
+ } else if (sc.state === "needs-migration") {
32372
+ recommendations.push(
32373
+ `${sc.scope}: legacy data not yet consolidated \u2014 run \`cleo exodus migrate --scope ${sc.scope}\`.`
32374
+ );
32375
+ } else if (sc.strandedResidue.length > 0) {
32376
+ recommendations.push(
32377
+ `${sc.scope}: ${sc.strandedResidue.length} stranded legacy DB(s) after a sealed cutover \u2014 run \`cleo doctor exodus-residue --fix\`.`
32378
+ );
32379
+ }
32380
+ }
32381
+ if (largeLegacyDbs.length > 0) {
32382
+ recommendations.push(
32383
+ `${largeLegacyDbs.length} large legacy DB(s) (\u2265500 MB) \u2014 migrate these only with the streamed-verify build (T11834) to avoid the verify OOM.`
32384
+ );
32385
+ }
32386
+ if (!parity.ok) {
32387
+ recommendations.push(
32388
+ `${parity.deficits.length} table(s) show a row deficit in cleo.db \u2014 DO NOT seal; run \`cleo exodus migrate\` first.`
32389
+ );
32390
+ }
32391
+ return {
32392
+ project,
32393
+ global,
32394
+ diskHeadroomOk: plan.diskPreflight,
32395
+ availableBytes: plan.availableBytes,
32396
+ requiredBytes: 3 * plan.totalSourceBytes,
32397
+ killSwitchSet: process.env.CLEO_DISABLE_EXODUS_ON_OPEN === "1",
32398
+ dataParityOk: parity.ok,
32399
+ dataDeficits: parity.deficits.length,
32400
+ largeLegacyDbs,
32401
+ recommendations
32402
+ };
32122
32403
  }
32404
+ var LARGE_DB_BYTES;
32405
+ var init_health2 = __esm({
32406
+ "packages/core/src/store/exodus/health.ts"() {
32407
+ "use strict";
32408
+ init_archive2();
32409
+ init_count_parity();
32410
+ init_plan2();
32411
+ LARGE_DB_BYTES = 500 * 1024 * 1024;
32412
+ }
32413
+ });
32414
+
32415
+ // packages/core/src/store/exodus/column-transforms.ts
32123
32416
  function typeDefaultLiteral(colType) {
32124
32417
  const upper = colType.toUpperCase();
32125
32418
  if (upper.includes("INT")) return "0";
@@ -32137,13 +32430,6 @@ function numericClampExpr(targetTableName, col, srcRef) {
32137
32430
  const fn = NUMERIC_CLAMPS.get(key);
32138
32431
  return fn ? fn(srcRef) : null;
32139
32432
  }
32140
- function epochUnitForSource(sourceName) {
32141
- const key = sourceName.toLowerCase();
32142
- for (const [pattern, unit] of SOURCE_EPOCH_UNITS) {
32143
- if (key === pattern || key.startsWith(pattern)) return unit;
32144
- }
32145
- return "seconds";
32146
- }
32147
32433
  function buildEpochToIsoExpr(srcRef) {
32148
32434
  return `CASE WHEN ${srcRef} IS NULL THEN NULL WHEN ${srcRef} < ${EPOCH_SECONDS_THRESHOLD} THEN strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}, 'unixepoch') ELSE strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}/1000.0, 'unixepoch') END`;
32149
32435
  }
@@ -32160,16 +32446,256 @@ function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
32160
32446
  }
32161
32447
  return isoColumns;
32162
32448
  }
32163
- function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType, tgtInfo, isoGlobCols) {
32164
- const srcRef = `"${attachAlias}"."${legacyTable}"."${col}"`;
32165
- const srcUpper = srcType.toUpperCase();
32166
- const isIntegerSource = srcUpper.includes("INT") || srcUpper === "" || srcUpper === "NUMERIC";
32167
- const isNotNullWithoutDefault = tgtInfo.notnull === 1 && tgtInfo.dflt_value === null;
32168
- if (isoGlobCols.has(col) && isIntegerSource) {
32169
- const isoExpr = buildEpochToIsoExpr(srcRef);
32170
- if (isNotNullWithoutDefault) {
32171
- return `COALESCE(${isoExpr}, '') AS "${col}"`;
32172
- }
32449
+ function isIntegerSourceType(srcType) {
32450
+ const upper = srcType.toUpperCase();
32451
+ return upper.includes("INT") || upper === "" || upper === "NUMERIC";
32452
+ }
32453
+ function maybeCoalesceNotNull(expr, tgtCol) {
32454
+ if (tgtCol === void 0) return expr;
32455
+ const isNotNullWithoutDefault = tgtCol.notnull === 1 && tgtCol.dflt_value === null;
32456
+ if (!isNotNullWithoutDefault) return expr;
32457
+ return `COALESCE(${expr}, ${typeDefaultLiteral(tgtCol.type)})`;
32458
+ }
32459
+ function buildDigestExpr(targetTableName, col, srcType, isoGlobCols, tgtCol) {
32460
+ const srcRef = `"${col}"`;
32461
+ if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) {
32462
+ return maybeCoalesceNotNull(buildEpochToIsoExpr(srcRef), tgtCol);
32463
+ }
32464
+ const clampExpr = numericClampExpr(targetTableName, col, srcRef);
32465
+ if (clampExpr !== null) return maybeCoalesceNotNull(clampExpr, tgtCol);
32466
+ const normExpr = enumNormExpr(targetTableName, col, srcRef);
32467
+ if (normExpr !== null) return maybeCoalesceNotNull(normExpr, tgtCol);
32468
+ return maybeCoalesceNotNull(srcRef, tgtCol);
32469
+ }
32470
+ var ENUM_NORMALIZATIONS, NUMERIC_CLAMPS, ISO_CHECK_REGEX, EPOCH_SECONDS_THRESHOLD;
32471
+ var init_column_transforms = __esm({
32472
+ "packages/core/src/store/exodus/column-transforms.ts"() {
32473
+ "use strict";
32474
+ ENUM_NORMALIZATIONS = /* @__PURE__ */ new Map([
32475
+ // --- task_commits.link_source -------------------------------------------
32476
+ // 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
32477
+ [
32478
+ "tasks_task_commits.link_source",
32479
+ (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`
32480
+ ],
32481
+ // --- architecture_decisions.status (case + date-suffix normalization) ----
32482
+ // 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
32483
+ // 'Proposed', 'PROPOSED' → 'proposed'
32484
+ // 'Superseded', 'SUPERSEDED' → 'superseded'
32485
+ [
32486
+ "tasks_architecture_decisions.status",
32487
+ (src) => `CASE WHEN lower(${src}) = 'accepted' OR lower(${src}) LIKE 'accepted %' OR lower(${src}) = 'approved' THEN 'accepted' WHEN lower(${src}) = 'proposed' THEN 'proposed' WHEN lower(${src}) = 'superseded' THEN 'superseded' WHEN lower(${src}) = 'deprecated' THEN 'deprecated' ELSE ${src} END`
32488
+ ],
32489
+ // --- brain_* enum normalizations REMOVED (T11647) -----------------------
32490
+ // The brain memory family now lands in the consolidated cleo.db in its LEGACY
32491
+ // RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
32492
+ // constraints (the `text({ enum })` unions are enforced only at the
32493
+ // application layer, exactly as the runtime `drizzle-brain` tables are). With
32494
+ // no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
32495
+ // value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
32496
+ // 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
32497
+ // nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
32498
+ // The previous brain entries (brain_observations.{source_type,type},
32499
+ // brain_decisions.{confirmation_state,decision_category,confidence,outcome,
32500
+ // decided_by}) are therefore deleted. The non-brain entries below still apply
32501
+ // because those consolidated tables retain their CHECK constraints.
32502
+ // --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
32503
+ // NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
32504
+ // requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
32505
+ // to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
32506
+ // 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
32507
+ // coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
32508
+ // of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
32509
+ // (brain_decisions.{decision_category,confidence} normalizations removed —
32510
+ // T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
32511
+ // --- tasks_commits.conventional_type (T11548 + T11578) -------------------
32512
+ // The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
32513
+ // perf/revert/breaking. Real git history carries non-conventional subjects:
32514
+ // - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
32515
+ // - 'merge'/'release' → 'chore' (T11578): merge + release commits are
32516
+ // maintenance-class; the precise semantic is preserved by the dedicated
32517
+ // `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
32518
+ // `conventional_type` to the maintenance catch-all 'chore' is lossless at
32519
+ // the row grain. Without this the 'merge'/'release' rows violate the CHECK,
32520
+ // `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
32521
+ // data-continuity gate aborts the cutover (T11578 CI regression).
32522
+ // - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
32523
+ // subjects must never re-break the zero-deficit gate; the boolean flags and
32524
+ // raw subject text remain the precise provenance).
32525
+ [
32526
+ "tasks_commits.conventional_type",
32527
+ (src) => `CASE WHEN ${src} IS NULL THEN NULL WHEN ${src} IN ('feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'build', 'ci', 'perf', 'revert', 'breaking') THEN ${src} ELSE 'chore' END`
32528
+ ],
32529
+ // --- tasks_task_relations.relation_type (T11548) -------------------------
32530
+ // 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
32531
+ // supersedes/groups). 4 rows.
32532
+ [
32533
+ "tasks_task_relations.relation_type",
32534
+ (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`
32535
+ ],
32536
+ // --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
32537
+ // Legacy camelCase / past-tense values → canonical snake_case stage names.
32538
+ // 'implemented' → 'implementation', 'qaPassed' → 'validation',
32539
+ // 'testsPassed' → 'testing'. 3 rows.
32540
+ [
32541
+ "tasks_lifecycle_stages.stage_name",
32542
+ (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`
32543
+ ],
32544
+ // --- tasks_architecture_decisions.gate_status (T11548) ------------------
32545
+ // 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
32546
+ // (enum: pending/passed/failed/waived). 2 rows.
32547
+ [
32548
+ "tasks_architecture_decisions.gate_status",
32549
+ (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`
32550
+ ],
32551
+ // --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
32552
+ // Values with a 'validator:...' prefix → 'direct'
32553
+ // (enum: direct/satisfies/coverage). 3 rows.
32554
+ // Strip the namespace prefix introduced before the enum was tightened.
32555
+ [
32556
+ "tasks_evidence_ac_bindings.binding_type",
32557
+ (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`
32558
+ ]
32559
+ // (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
32560
+ // brain target = runtime shape with no CHECK; legacy values like 'accepted',
32561
+ // 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
32562
+ ]);
32563
+ NUMERIC_CLAMPS = /* @__PURE__ */ new Map([
32564
+ // --- brain_weight_history.delta_weight (T11782) -------------------------
32565
+ // +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
32566
+ // depression), NaN → 0.0 (no-op delta). Finite values pass through.
32567
+ [
32568
+ "brain_weight_history.delta_weight",
32569
+ (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`
32570
+ ]
32571
+ ]);
32572
+ ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
32573
+ EPOCH_SECONDS_THRESHOLD = 1e11;
32574
+ }
32575
+ });
32576
+
32577
+ // packages/core/src/store/exodus/types.ts
32578
+ var EXODUS_TARGET_SCHEMA_VERSION;
32579
+ var init_types = __esm({
32580
+ "packages/core/src/store/exodus/types.ts"() {
32581
+ "use strict";
32582
+ EXODUS_TARGET_SCHEMA_VERSION = "drizzle-v1.0.0-rc.3/dual-scope/2026-05";
32583
+ }
32584
+ });
32585
+
32586
+ // packages/core/src/store/exodus/migrate.ts
32587
+ import {
32588
+ copyFileSync as copyFileSync3,
32589
+ existsSync as existsSync10,
32590
+ mkdirSync as mkdirSync2,
32591
+ readFileSync as readFileSync3,
32592
+ renameSync as renameSync2,
32593
+ statSync as statSync4,
32594
+ unlinkSync as unlinkSync2,
32595
+ writeFileSync as writeFileSync3
32596
+ } from "node:fs";
32597
+ import { join as join10 } from "node:path";
32598
+ function getSqliteVersion(db) {
32599
+ try {
32600
+ const row = db.prepare("SELECT sqlite_version() AS v").get();
32601
+ return row?.v ?? "unknown";
32602
+ } catch {
32603
+ return "unknown";
32604
+ }
32605
+ }
32606
+ function listTables2(db) {
32607
+ const rows = db.prepare(
32608
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
32609
+ ).all();
32610
+ return rows.map((r) => r.name);
32611
+ }
32612
+ function writeJournal(stagingDir, journal) {
32613
+ const journalPath = join10(stagingDir, JOURNAL_FILENAME);
32614
+ const tmpPath = `${journalPath}.tmp`;
32615
+ writeFileSync3(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
32616
+ renameSync2(tmpPath, journalPath);
32617
+ }
32618
+ function readJournal(stagingDir) {
32619
+ const journalPath = join10(stagingDir, JOURNAL_FILENAME);
32620
+ if (!existsSync10(journalPath)) return null;
32621
+ try {
32622
+ return JSON.parse(readFileSync3(journalPath, "utf8"));
32623
+ } catch {
32624
+ return null;
32625
+ }
32626
+ }
32627
+ function clearExodusJournal(stagingDir) {
32628
+ const journalPath = join10(stagingDir, JOURNAL_FILENAME);
32629
+ try {
32630
+ if (!existsSync10(journalPath)) return false;
32631
+ unlinkSync2(journalPath);
32632
+ log3.info(
32633
+ { stagingDir },
32634
+ "exodus: cleared migrate journal after abort/rollback \u2014 a retry will RE-COPY all tables"
32635
+ );
32636
+ return true;
32637
+ } catch (err) {
32638
+ log3.warn(
32639
+ { err, stagingDir },
32640
+ 'exodus: failed to clear migrate journal after rollback (a retry may skip already-"done" tables)'
32641
+ );
32642
+ return false;
32643
+ }
32644
+ }
32645
+ function initJournal(sqliteVersion) {
32646
+ const now = (/* @__PURE__ */ new Date()).toISOString();
32647
+ return {
32648
+ version: 1,
32649
+ cleoVersion: getCleoVersion(),
32650
+ targetSchemaVersion: EXODUS_TARGET_SCHEMA_VERSION,
32651
+ nodeVersion: process.version,
32652
+ sqliteVersion,
32653
+ startedAt: now,
32654
+ updatedAt: now,
32655
+ tables: []
32656
+ };
32657
+ }
32658
+ function lockPath(dbPath) {
32659
+ return `${dbPath}${LOCK_SENTINEL_SUFFIX}`;
32660
+ }
32661
+ function acquireAdvisoryLock(dbPath) {
32662
+ const lp = lockPath(dbPath);
32663
+ writeFileSync3(lp, JSON.stringify({ pid: process.pid, ts: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
32664
+ }
32665
+ function releaseAdvisoryLock(dbPath) {
32666
+ try {
32667
+ unlinkSync2(lockPath(dbPath));
32668
+ } catch {
32669
+ }
32670
+ }
32671
+ function safeStatBytes(filePath) {
32672
+ try {
32673
+ return statSync4(filePath).size;
32674
+ } catch {
32675
+ return 0;
32676
+ }
32677
+ }
32678
+ function makeAttachAlias(name2, index2) {
32679
+ const safe = name2.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").slice(0, 20);
32680
+ return `_src_${safe}_${index2}`;
32681
+ }
32682
+ function epochUnitForSource(sourceName) {
32683
+ const key = sourceName.toLowerCase();
32684
+ for (const [pattern, unit] of SOURCE_EPOCH_UNITS) {
32685
+ if (key === pattern || key.startsWith(pattern)) return unit;
32686
+ }
32687
+ return "seconds";
32688
+ }
32689
+ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType, tgtInfo, isoGlobCols) {
32690
+ const srcRef = `"${attachAlias}"."${legacyTable}"."${col}"`;
32691
+ const srcUpper = srcType.toUpperCase();
32692
+ const isIntegerSource = srcUpper.includes("INT") || srcUpper === "" || srcUpper === "NUMERIC";
32693
+ const isNotNullWithoutDefault = tgtInfo.notnull === 1 && tgtInfo.dflt_value === null;
32694
+ if (isoGlobCols.has(col) && isIntegerSource) {
32695
+ const isoExpr = buildEpochToIsoExpr(srcRef);
32696
+ if (isNotNullWithoutDefault) {
32697
+ return `COALESCE(${isoExpr}, '') AS "${col}"`;
32698
+ }
32173
32699
  return `${isoExpr} AS "${col}"`;
32174
32700
  }
32175
32701
  const clampExpr = numericClampExpr(targetTableName, col, srcRef);
@@ -32197,7 +32723,7 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
32197
32723
  function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyTableName, sourceName, targetSchema = "main") {
32198
32724
  const resolution = resolveConsolidatedTableName(sourceName, legacyTableName);
32199
32725
  if (resolution.kind === "skip") {
32200
- log2.warn(
32726
+ log3.warn(
32201
32727
  { legacyTableName, sourceName, reason: resolution.reason },
32202
32728
  `Exodus: explicitly skipping table \u2014 ${resolution.reason}`
32203
32729
  );
@@ -32216,7 +32742,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32216
32742
  ).get();
32217
32743
  if (!existsRow) {
32218
32744
  const reason = `consolidated target '${targetTableName}' not found (mapped from legacy '${legacyTableName}')`;
32219
- log2.warn(
32745
+ log3.warn(
32220
32746
  { legacyTableName, targetTableName, sourceName, attachAlias, targetSchema },
32221
32747
  `Exodus: ${reason}`
32222
32748
  );
@@ -32227,13 +32753,13 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32227
32753
  const sharedColumns = srcPragma.map((r) => r.name).filter((col) => tgtColMap.has(col));
32228
32754
  if (sharedColumns.length === 0) {
32229
32755
  const reason = `no overlapping columns between source '${legacyTableName}' and target '${targetTableName}'`;
32230
- log2.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32756
+ log3.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32231
32757
  return { rowsCopied: 0, skipped: true, reason };
32232
32758
  }
32233
32759
  const srcOnlyColumns = srcPragma.map((r) => r.name).filter((c) => !tgtColMap.has(c));
32234
32760
  const tgtOnlyColumns = tgtPragma.map((r) => r.name).filter((c) => !srcColumns.has(c));
32235
32761
  if (srcOnlyColumns.length > 0 || tgtOnlyColumns.length > 0) {
32236
- log2.info(
32762
+ log3.info(
32237
32763
  { legacyTableName, targetTableName, sourceName, srcOnlyColumns, tgtOnlyColumns },
32238
32764
  "Exodus: column drift detected \u2014 copying intersection, dropping src-only cols, using defaults for tgt-only cols"
32239
32765
  );
@@ -32248,7 +32774,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32248
32774
  return isoGlobCols.has(col) && (upper.includes("INT") || upper === "" || upper === "NUMERIC");
32249
32775
  });
32250
32776
  if (coercedCols.length > 0) {
32251
- log2.info(
32777
+ log3.info(
32252
32778
  {
32253
32779
  legacyTableName,
32254
32780
  targetTableName,
@@ -32264,7 +32790,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32264
32790
  (col) => ENUM_NORMALIZATIONS.has(`${targetTableName}.${col}`)
32265
32791
  );
32266
32792
  if (normalizedCols.length > 0) {
32267
- log2.info(
32793
+ log3.info(
32268
32794
  { legacyTableName, targetTableName, sourceName, normalizedCols },
32269
32795
  `Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`
32270
32796
  );
@@ -32273,7 +32799,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32273
32799
  (col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`)
32274
32800
  );
32275
32801
  if (clampedCols.length > 0) {
32276
- log2.info(
32802
+ log3.info(
32277
32803
  { legacyTableName, targetTableName, sourceName, clampedCols },
32278
32804
  `Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`
32279
32805
  );
@@ -32305,25 +32831,37 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32305
32831
  ];
32306
32832
  const colList = allInsertCols.map((c) => `"${c}"`).join(", ");
32307
32833
  const selectList = allSelectExprs.join(", ");
32834
+ const existingBeforeRow = targetNativeDb.prepare(`SELECT COUNT(*) AS c FROM "${targetSchema}"."${targetTableName}"`).get();
32835
+ const existingBefore = Number(existingBeforeRow?.c ?? 0);
32308
32836
  const stmt = targetNativeDb.prepare(
32309
32837
  `INSERT OR IGNORE INTO "${targetSchema}"."${targetTableName}" (${colList}) SELECT ${selectList} FROM "${attachAlias}"."${legacyTableName}"`
32310
32838
  );
32311
32839
  const result = stmt.run();
32312
32840
  const rowsCopied = result.changes ?? 0;
32313
32841
  if (rowsCopied < sourceCount) {
32314
- const dropped = sourceCount - rowsCopied;
32315
- if (rowsCopied === 0) {
32316
- const reason = `INSERT OR IGNORE dropped ALL ${sourceCount} rows from '${legacyTableName}'\u2192'${targetTableName}' (rowsCopied=0, sourceCount=${sourceCount}). Likely a CHECK/type constraint violation \u2014 check epoch coercion or enum values.`;
32317
- log2.error(
32318
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied },
32319
- `Exodus: ${reason}`
32842
+ const presentAccountedFor = existingBefore + rowsCopied;
32843
+ if (presentAccountedFor >= sourceCount) {
32844
+ log3.info(
32845
+ { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, existingBefore },
32846
+ `Exodus: '${legacyTableName}'\u2192'${targetTableName}' \u2014 ${sourceCount - rowsCopied} of ${sourceCount} row(s) already present in target (idempotent PK dedup), ${rowsCopied} newly copied; no loss`
32320
32847
  );
32321
- return { rowsCopied: 0, skipped: false, reason };
32848
+ return { rowsCopied, skipped: false };
32322
32849
  }
32323
- log2.warn(
32324
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, dropped },
32325
- `Exodus: INSERT OR IGNORE dropped ${dropped}/${sourceCount} rows from '${legacyTableName}'\u2192'${targetTableName}' \u2014 may be idempotent-resume dedup or a constraint violation; verify will confirm`
32850
+ const missing = sourceCount - presentAccountedFor;
32851
+ const reason = `INSERT OR IGNORE lost ${missing} of ${sourceCount} rows from '${legacyTableName}'\u2192'${targetTableName}' (rowsCopied=${rowsCopied}, existingBefore=${existingBefore}) \u2014 a CHECK/NOT NULL/type/enum constraint rejected them (NOT PK dedup); inspect epoch coercion + enum normalization. verify will confirm.`;
32852
+ log3.error(
32853
+ {
32854
+ legacyTableName,
32855
+ targetTableName,
32856
+ sourceName,
32857
+ sourceCount,
32858
+ rowsCopied,
32859
+ existingBefore,
32860
+ missing
32861
+ },
32862
+ `Exodus: ${reason}`
32326
32863
  );
32864
+ return { rowsCopied, skipped: false, reason };
32327
32865
  }
32328
32866
  return { rowsCopied, skipped: false };
32329
32867
  }
@@ -32331,10 +32869,10 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32331
32869
  if (journal.targetSchemaVersion !== EXODUS_TARGET_SCHEMA_VERSION) {
32332
32870
  const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
32333
32871
  if (forceCrossVersion) {
32334
- log2.warn(msg + " (--force-cross-version: continuing anyway)");
32872
+ log3.warn(msg + " (--force-cross-version: continuing anyway)");
32335
32873
  return true;
32336
32874
  }
32337
- log2.error(msg + " \u2014 pass --force-cross-version to override");
32875
+ log3.error(msg + " \u2014 pass --force-cross-version to override");
32338
32876
  return false;
32339
32877
  }
32340
32878
  return true;
@@ -32342,18 +32880,19 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32342
32880
  async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32343
32881
  const { sources, stagingDir, diskPreflight, projectDbPath, globalDbPath } = plan;
32344
32882
  if (!diskPreflight) {
32883
+ const shortfall = Math.max(0, plan.requiredBytes - plan.availableBytes);
32345
32884
  return {
32346
32885
  ok: false,
32347
32886
  tables: [],
32348
32887
  stagingDir,
32349
32888
  backupPaths: [],
32350
- error: `Insufficient disk space: need \u22653\xD7 source size (${plan.totalSourceBytes} bytes source, ${plan.availableBytes} bytes available). Free up space or use a different storage location.`
32889
+ error: `Insufficient disk space for exodus: need \u2265${plan.requiredBytes} bytes (\u2248${STAGING_HEADROOM_FACTOR}\xD7 largest source ${plan.largestSourceBytes} + consolidated estimate ${plan.totalSourceBytes}), but only ${plan.availableBytes} bytes are free on the target filesystem \u2014 ${shortfall} bytes short. Free up at least ${shortfall} bytes (e.g. \`cleo backup prune\`, clear caches), or move the .cleo/ directory to a larger volume, then retry.`
32351
32890
  };
32352
32891
  }
32353
32892
  mkdirSync2(stagingDir, { recursive: true });
32354
32893
  let sqliteVersion = "unknown";
32355
32894
  for (const src of sources) {
32356
- if (existsSync7(src.path)) {
32895
+ if (existsSync10(src.path)) {
32357
32896
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32358
32897
  sqliteVersion = getSqliteVersion(snap.db);
32359
32898
  snap.close();
@@ -32394,9 +32933,15 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32394
32933
  };
32395
32934
  var extractNativeDb = extractNativeDb2;
32396
32935
  for (const src of sources) {
32397
- if (!existsSync7(src.path)) continue;
32398
- const backupDest = join9(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
32399
- if (!existsSync7(backupDest)) {
32936
+ if (!existsSync10(src.path)) continue;
32937
+ const backupDest = join10(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
32938
+ const srcBytes = safeStatBytes(src.path);
32939
+ const skipStagingCopy = srcBytes > plan.stagingCopyThresholdBytes;
32940
+ if (skipStagingCopy) {
32941
+ onProgress?.(
32942
+ `Skipping full staging copy of ${src.name} (${srcBytes} bytes > ${plan.stagingCopyThresholdBytes} threshold) \u2014 source is archived, not deleted, on success.`
32943
+ );
32944
+ } else if (!existsSync10(backupDest)) {
32400
32945
  onProgress?.(`Backing up ${src.name} \u2192 staging dir\u2026`);
32401
32946
  copyFileSync3(src.path, backupDest);
32402
32947
  backupPaths.push(backupDest);
@@ -32414,8 +32959,8 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32414
32959
  });
32415
32960
  const projectNative = extractNativeDb2(projectHandle);
32416
32961
  const globalNative = extractNativeDb2(globalHandle);
32417
- const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync7(s.path));
32418
- const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync7(s.path));
32962
+ const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync10(s.path));
32963
+ const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync10(s.path));
32419
32964
  await migrateScope(
32420
32965
  "project",
32421
32966
  projectSources,
@@ -32440,7 +32985,7 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32440
32985
  return { ok: true, tables: allTableResults, stagingDir, backupPaths };
32441
32986
  } catch (err) {
32442
32987
  const error = err instanceof Error ? err.message : String(err);
32443
- log2.error({ err }, "Exodus migration failed");
32988
+ log3.error({ err }, "Exodus migration failed");
32444
32989
  return { ok: false, tables: allTableResults, stagingDir, backupPaths, error };
32445
32990
  } finally {
32446
32991
  try {
@@ -32460,7 +33005,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32460
33005
  if (sources.length === 0) return;
32461
33006
  onProgress?.(`Migrating ${scope}-scope sources\u2026`);
32462
33007
  targetNativeDb.exec("PRAGMA foreign_keys = OFF");
32463
- log2.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
33008
+ log3.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
32464
33009
  try {
32465
33010
  for (let i = 0; i < sources.length; i++) {
32466
33011
  const src = sources[i];
@@ -32477,7 +33022,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32477
33022
  }
32478
33023
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32479
33024
  try {
32480
- const tables = listTables(snap.db);
33025
+ const tables = listTables2(snap.db);
32481
33026
  targetNativeDb.exec("BEGIN");
32482
33027
  let txOpen = true;
32483
33028
  try {
@@ -32527,7 +33072,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32527
33072
  }
32528
33073
  } catch (err) {
32529
33074
  const msg = err instanceof Error ? err.message : String(err);
32530
- log2.warn({ tableName, sourceDb: src.name, err }, "Table copy failed \u2014 skipping");
33075
+ log3.warn({ tableName, sourceDb: src.name, err }, "Table copy failed \u2014 skipping");
32531
33076
  status = "skipped";
32532
33077
  errorMsg = msg;
32533
33078
  skipped = true;
@@ -32575,7 +33120,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32575
33120
  targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
32576
33121
  onProgress?.(` [${src.name}] Detached "${attachAlias}"`);
32577
33122
  } catch (detachErr) {
32578
- log2.warn(
33123
+ log3.warn(
32579
33124
  { attachAlias, sourceDb: src.name, err: detachErr },
32580
33125
  "DETACH failed \u2014 alias will be released on DB close"
32581
33126
  );
@@ -32585,7 +33130,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32585
33130
  targetNativeDb.exec(`DETACH DATABASE "${crossAlias}"`);
32586
33131
  onProgress?.(` [${src.name}] Cross-scope target detached "${crossAlias}"`);
32587
33132
  } catch (detachErr) {
32588
- log2.warn(
33133
+ log3.warn(
32589
33134
  { crossAlias, sourceDb: src.name, err: detachErr },
32590
33135
  "Cross-scope DETACH failed \u2014 alias will be released on DB close"
32591
33136
  );
@@ -32597,28 +33142,28 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32597
33142
  try {
32598
33143
  const orphans = targetNativeDb.prepare("PRAGMA foreign_key_check").all();
32599
33144
  if (orphans.length > 0) {
32600
- log2.warn(
33145
+ log3.warn(
32601
33146
  { scope, orphanCount: orphans.length, sample: orphans.slice(0, 5) },
32602
33147
  `Exodus: PRAGMA foreign_key_check found ${orphans.length} orphan row(s) after bulk copy \u2014 these are genuine data orphans (not ordering artifacts)`
32603
33148
  );
32604
33149
  } else {
32605
- log2.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED \u2014 no orphan rows");
33150
+ log3.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED \u2014 no orphan rows");
32606
33151
  }
32607
33152
  } catch (checkErr) {
32608
- log2.warn(
33153
+ log3.warn(
32609
33154
  { scope, err: checkErr },
32610
33155
  "Exodus: PRAGMA foreign_key_check failed (non-fatal) \u2014 target schema may not have FK constraints enabled"
32611
33156
  );
32612
33157
  }
32613
33158
  try {
32614
33159
  targetNativeDb.exec("PRAGMA foreign_keys = ON");
32615
- log2.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
33160
+ log3.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
32616
33161
  } catch (fkErr) {
32617
- log2.warn({ scope, err: fkErr }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
33162
+ log3.warn({ scope, err: fkErr }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
32618
33163
  }
32619
33164
  }
32620
33165
  }
32621
- var log2, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, ENUM_NORMALIZATIONS, NUMERIC_CLAMPS, ISO_CHECK_REGEX, SOURCE_EPOCH_UNITS, EPOCH_SECONDS_THRESHOLD;
33166
+ var log3, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
32622
33167
  var init_migrate = __esm({
32623
33168
  "packages/core/src/store/exodus/migrate.ts"() {
32624
33169
  "use strict";
@@ -32626,110 +33171,13 @@ var init_migrate = __esm({
32626
33171
  init_ensure_config();
32627
33172
  init_dual_scope_db();
32628
33173
  init_open_cleo_db();
33174
+ init_column_transforms();
33175
+ init_plan2();
32629
33176
  init_table_name_map();
32630
33177
  init_types();
32631
- log2 = getLogger("exodus-migrate");
33178
+ log3 = getLogger("exodus-migrate");
32632
33179
  LOCK_SENTINEL_SUFFIX = ".exodus-lock";
32633
33180
  JOURNAL_FILENAME = "exodus-journal.json";
32634
- ENUM_NORMALIZATIONS = /* @__PURE__ */ new Map([
32635
- // --- task_commits.link_source -------------------------------------------
32636
- // 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
32637
- [
32638
- "tasks_task_commits.link_source",
32639
- (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`
32640
- ],
32641
- // --- architecture_decisions.status (case + date-suffix normalization) ----
32642
- // 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
32643
- // 'Proposed', 'PROPOSED' → 'proposed'
32644
- // 'Superseded', 'SUPERSEDED' → 'superseded'
32645
- [
32646
- "tasks_architecture_decisions.status",
32647
- (src) => `CASE WHEN lower(${src}) = 'accepted' OR lower(${src}) LIKE 'accepted %' OR lower(${src}) = 'approved' THEN 'accepted' WHEN lower(${src}) = 'proposed' THEN 'proposed' WHEN lower(${src}) = 'superseded' THEN 'superseded' WHEN lower(${src}) = 'deprecated' THEN 'deprecated' ELSE ${src} END`
32648
- ],
32649
- // --- brain_* enum normalizations REMOVED (T11647) -----------------------
32650
- // The brain memory family now lands in the consolidated cleo.db in its LEGACY
32651
- // RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
32652
- // constraints (the `text({ enum })` unions are enforced only at the
32653
- // application layer, exactly as the runtime `drizzle-brain` tables are). With
32654
- // no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
32655
- // value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
32656
- // 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
32657
- // nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
32658
- // The previous brain entries (brain_observations.{source_type,type},
32659
- // brain_decisions.{confirmation_state,decision_category,confidence,outcome,
32660
- // decided_by}) are therefore deleted. The non-brain entries below still apply
32661
- // because those consolidated tables retain their CHECK constraints.
32662
- // --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
32663
- // NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
32664
- // requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
32665
- // to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
32666
- // 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
32667
- // coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
32668
- // of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
32669
- // (brain_decisions.{decision_category,confidence} normalizations removed —
32670
- // T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
32671
- // --- tasks_commits.conventional_type (T11548 + T11578) -------------------
32672
- // The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
32673
- // perf/revert/breaking. Real git history carries non-conventional subjects:
32674
- // - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
32675
- // - 'merge'/'release' → 'chore' (T11578): merge + release commits are
32676
- // maintenance-class; the precise semantic is preserved by the dedicated
32677
- // `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
32678
- // `conventional_type` to the maintenance catch-all 'chore' is lossless at
32679
- // the row grain. Without this the 'merge'/'release' rows violate the CHECK,
32680
- // `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
32681
- // data-continuity gate aborts the cutover (T11578 CI regression).
32682
- // - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
32683
- // subjects must never re-break the zero-deficit gate; the boolean flags and
32684
- // raw subject text remain the precise provenance).
32685
- [
32686
- "tasks_commits.conventional_type",
32687
- (src) => `CASE WHEN ${src} IS NULL THEN NULL WHEN ${src} IN ('feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'build', 'ci', 'perf', 'revert', 'breaking') THEN ${src} ELSE 'chore' END`
32688
- ],
32689
- // --- tasks_task_relations.relation_type (T11548) -------------------------
32690
- // 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
32691
- // supersedes/groups). 4 rows.
32692
- [
32693
- "tasks_task_relations.relation_type",
32694
- (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`
32695
- ],
32696
- // --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
32697
- // Legacy camelCase / past-tense values → canonical snake_case stage names.
32698
- // 'implemented' → 'implementation', 'qaPassed' → 'validation',
32699
- // 'testsPassed' → 'testing'. 3 rows.
32700
- [
32701
- "tasks_lifecycle_stages.stage_name",
32702
- (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`
32703
- ],
32704
- // --- tasks_architecture_decisions.gate_status (T11548) ------------------
32705
- // 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
32706
- // (enum: pending/passed/failed/waived). 2 rows.
32707
- [
32708
- "tasks_architecture_decisions.gate_status",
32709
- (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`
32710
- ],
32711
- // --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
32712
- // Values with a 'validator:...' prefix → 'direct'
32713
- // (enum: direct/satisfies/coverage). 3 rows.
32714
- // Strip the namespace prefix introduced before the enum was tightened.
32715
- [
32716
- "tasks_evidence_ac_bindings.binding_type",
32717
- (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`
32718
- ]
32719
- // (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
32720
- // brain target = runtime shape with no CHECK; legacy values like 'accepted',
32721
- // 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
32722
- ]);
32723
- NUMERIC_CLAMPS = /* @__PURE__ */ new Map([
32724
- // --- brain_weight_history.delta_weight (T11782) -------------------------
32725
- // +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
32726
- // depression), NaN → 0.0 (no-op delta). Finite values pass through.
32727
- [
32728
- "brain_weight_history.delta_weight",
32729
- (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`
32730
- ]
32731
- ]);
32732
- ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
32733
33181
  SOURCE_EPOCH_UNITS = /* @__PURE__ */ new Map([
32734
33182
  ["conduit", "seconds"],
32735
33183
  ["brain", "milliseconds"],
@@ -32740,120 +33188,58 @@ var init_migrate = __esm({
32740
33188
  ["nexus", "milliseconds"],
32741
33189
  ["skills", "milliseconds"]
32742
33190
  ]);
32743
- EPOCH_SECONDS_THRESHOLD = 1e11;
32744
33191
  }
32745
33192
  });
32746
33193
 
32747
- // packages/core/src/store/exodus/plan.ts
32748
- import { existsSync as existsSync8, readdirSync as readdirSync2, statfsSync, statSync as statSync2 } from "node:fs";
32749
- import { join as join10 } from "node:path";
32750
- function buildSourceDescriptors(cwd) {
32751
- const cleoDir = resolveCleoDir(cwd);
32752
- const cleoHome = getCleoHome();
32753
- return [
32754
- // Project-tier — go into consolidated project-scope cleo.db
32755
- {
32756
- name: "tasks",
32757
- path: join10(cleoDir, "tasks.db"),
32758
- targetScope: "project"
32759
- },
32760
- {
32761
- name: "brain (project)",
32762
- path: join10(cleoDir, "brain.db"),
32763
- targetScope: "project"
32764
- },
32765
- {
32766
- name: "conduit",
32767
- path: join10(cleoDir, "conduit.db"),
32768
- targetScope: "project"
32769
- },
32770
- // Global-tier — go into consolidated global-scope cleo.db
32771
- {
32772
- name: "nexus",
32773
- path: join10(cleoHome, "nexus.db"),
32774
- targetScope: "global"
32775
- },
32776
- {
32777
- name: "signaldock",
32778
- path: join10(cleoHome, "signaldock.db"),
32779
- targetScope: "global"
32780
- },
32781
- {
32782
- name: "skills",
32783
- path: join10(cleoHome, "skills.db"),
32784
- targetScope: "global"
32785
- }
32786
- ];
32787
- }
32788
- function safeFileBytes(filePath) {
32789
- try {
32790
- return statSync2(filePath).size;
32791
- } catch {
32792
- return 0;
32793
- }
33194
+ // packages/core/src/store/exodus/seal.ts
33195
+ function resolveScopes(arg) {
33196
+ return arg === "both" ? ["project", "global"] : [arg];
32794
33197
  }
32795
- function getAvailableBytes(dir) {
32796
- try {
32797
- const result = statfsSync(dir);
32798
- return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
32799
- } catch {
32800
- return 0;
33198
+ function sealExodus(plan, scopeArg, cwd) {
33199
+ const parity = computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath);
33200
+ if (!parity.ok) {
33201
+ const refusedReason = `Refusing to seal: ${parity.deficits.length} table(s) have FEWER rows in the consolidated cleo.db than the legacy source \u2014 the data is NOT fully migrated. Run \`cleo exodus migrate\` first. Deficits: ${parity.deficits.map((d) => `${d.targetTable}(${d.sourceCount}\u2192${d.targetCount})`).join(", ")}`;
33202
+ log4.error({ deficits: parity.deficits.length }, `exodus seal refused \u2014 ${refusedReason}`);
33203
+ return { ok: false, refusedReason, parity, scopes: [] };
32801
33204
  }
32802
- }
32803
- function deriveStagingDirName() {
32804
- const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z");
32805
- return `exodus-staging-${iso}`;
32806
- }
32807
- function findExistingStaging(cleoDir) {
32808
- try {
32809
- const entries = readdirSync2(cleoDir, { withFileTypes: true });
32810
- const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
32811
- if (stagingDirs.length > 0) {
32812
- return join10(cleoDir, stagingDirs[0]);
32813
- }
32814
- } catch {
33205
+ const outcomes = [];
33206
+ for (const scope of resolveScopes(scopeArg)) {
33207
+ const alreadySealed = hasExodusCompleteMarker(scope, cwd);
33208
+ const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
33209
+ const archived = scopeSources.map((s) => {
33210
+ const r = archiveSourceDb(s, cwd);
33211
+ return { name: r.name, action: r.action, archivedTo: r.archivedTo };
33212
+ });
33213
+ const markerPath = writeExodusCompleteMarker(
33214
+ scope,
33215
+ scopeSources.map((s) => s.name),
33216
+ cwd
33217
+ );
33218
+ log4.info(
33219
+ { scope, alreadySealed, archived: archived.filter((a) => a.action === "archived").length },
33220
+ `exodus seal: scope '${scope}' certified (count-parity verified, ${parity.checked} tables)`
33221
+ );
33222
+ outcomes.push({ scope, alreadySealed, archived, markerPath });
32815
33223
  }
32816
- return null;
33224
+ return { ok: true, parity, scopes: outcomes };
32817
33225
  }
32818
- function buildExodusPlan(cwd) {
32819
- const cleoDir = resolveCleoDir(cwd);
32820
- const sources = buildSourceDescriptors(cwd);
32821
- const totalSourceBytes = sources.reduce((sum, s) => sum + safeFileBytes(s.path), 0);
32822
- const availableBytes = getAvailableBytes(cleoDir);
32823
- const diskPreflight = totalSourceBytes === 0 || availableBytes >= 3 * totalSourceBytes;
32824
- const existingStaging = findExistingStaging(cleoDir);
32825
- const stagingDir = existingStaging ?? join10(cleoDir, deriveStagingDirName());
32826
- const resumeFromStaging = existingStaging !== null;
32827
- const projectDbPath = resolveDualScopeDbPath("project", cwd);
32828
- const globalDbPath = resolveDualScopeDbPath("global");
32829
- return {
32830
- sources,
32831
- totalSourceBytes,
32832
- availableBytes,
32833
- diskPreflight,
32834
- stagingDir,
32835
- resumeFromStaging,
32836
- projectDbPath,
32837
- globalDbPath
32838
- };
32839
- }
32840
- function sourcesPresent(sources) {
32841
- return sources.some((s) => existsSync8(s.path));
32842
- }
32843
- var init_plan2 = __esm({
32844
- "packages/core/src/store/exodus/plan.ts"() {
33226
+ var log4;
33227
+ var init_seal = __esm({
33228
+ "packages/core/src/store/exodus/seal.ts"() {
32845
33229
  "use strict";
32846
- init_paths();
32847
- init_dual_scope_db();
33230
+ init_logger2();
33231
+ init_archive2();
33232
+ init_count_parity();
33233
+ log4 = getLogger("exodus-seal");
32848
33234
  }
32849
33235
  });
32850
33236
 
32851
33237
  // packages/core/src/store/exodus/status.ts
32852
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
33238
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
32853
33239
  import { join as join11 } from "node:path";
32854
33240
  function readJournal2(stagingDir) {
32855
33241
  const p = join11(stagingDir, JOURNAL_FILENAME2);
32856
- if (!existsSync9(p)) return null;
33242
+ if (!existsSync11(p)) return null;
32857
33243
  try {
32858
33244
  return JSON.parse(readFileSync4(p, "utf8"));
32859
33245
  } catch {
@@ -32869,7 +33255,7 @@ function findStagingDirs(cleoDir) {
32869
33255
  }
32870
33256
  function safeBytes(p) {
32871
33257
  try {
32872
- return statSync3(p).size;
33258
+ return statSync5(p).size;
32873
33259
  } catch {
32874
33260
  return 0;
32875
33261
  }
@@ -32885,15 +33271,15 @@ function runExodusStatus(cwd) {
32885
33271
  const sourcesInfo = plan.sources.map((s) => ({
32886
33272
  name: s.name,
32887
33273
  path: s.path,
32888
- exists: existsSync9(s.path),
33274
+ exists: existsSync11(s.path),
32889
33275
  bytes: safeBytes(s.path)
32890
33276
  }));
32891
33277
  return {
32892
33278
  hasStaging: latestStaging !== null,
32893
33279
  stagingDir: latestStaging,
32894
33280
  journal,
32895
- projectDbExists: existsSync9(projectDbPath),
32896
- globalDbExists: existsSync9(globalDbPath),
33281
+ projectDbExists: existsSync11(projectDbPath),
33282
+ globalDbExists: existsSync11(globalDbPath),
32897
33283
  sourcesPresent: sourcesInfo.some((s) => s.exists),
32898
33284
  sources: sourcesInfo
32899
33285
  };
@@ -32910,7 +33296,7 @@ var init_status = __esm({
32910
33296
  });
32911
33297
 
32912
33298
  // packages/core/src/store/exodus/verify-migration.ts
32913
- import { existsSync as existsSync10 } from "node:fs";
33299
+ import { existsSync as existsSync12 } from "node:fs";
32914
33300
  import { createRequire as createRequire3 } from "node:module";
32915
33301
  function orderByClause(db, tableName) {
32916
33302
  try {
@@ -32923,28 +33309,56 @@ function orderByClause(db, tableName) {
32923
33309
  }
32924
33310
  return "rowid";
32925
33311
  }
32926
- function computeTableDigest(db, tableName, columns) {
33312
+ function computeTableDigest(db, tableName, columns, transform) {
33313
+ let count = 0;
33314
+ try {
33315
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
33316
+ count = Number(row?.c ?? 0);
33317
+ } catch (err) {
33318
+ const msg = err instanceof Error ? err.message : String(err);
33319
+ log5.warn(
33320
+ { tableName, err: msg },
33321
+ "computeTableDigest: COUNT(*) failed (possibly a virtual/FTS table) \u2014 treating as 0 rows"
33322
+ );
33323
+ return { count: 0, hash: "" };
33324
+ }
32927
33325
  const { createHash: createHash3 } = _require("node:crypto");
32928
33326
  const hasher = createHash3("sha256");
32929
33327
  const orderBy = orderByClause(db, tableName);
32930
- const selectClause = columns !== null && columns.length > 0 ? columns.map((c) => `"${c}"`).join(", ") : "*";
32931
- let rows;
33328
+ let selectClause;
33329
+ if (columns !== null && columns.length > 0) {
33330
+ selectClause = columns.map((c) => {
33331
+ if (transform === void 0) return `"${c}"`;
33332
+ const srcType = transform.srcTypeByCol.get(c) ?? "";
33333
+ const tgtCol = transform.tgtColByCol.get(c);
33334
+ const expr = buildDigestExpr(
33335
+ transform.targetTableName,
33336
+ c,
33337
+ srcType,
33338
+ transform.isoGlobCols,
33339
+ tgtCol
33340
+ );
33341
+ return `${expr} AS "${c}"`;
33342
+ }).join(", ");
33343
+ } else {
33344
+ selectClause = "*";
33345
+ }
32932
33346
  try {
32933
- rows = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`).all();
33347
+ const stmt = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`);
33348
+ for (const row of stmt.iterate()) {
33349
+ const rowObj = row;
33350
+ hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
33351
+ }
32934
33352
  } catch (err) {
32935
33353
  const msg = err instanceof Error ? err.message : String(err);
32936
- log3.warn(
33354
+ log5.warn(
32937
33355
  { tableName, err: msg },
32938
- "computeTableDigest: SELECT failed (possibly a virtual/FTS table) \u2014 treating as 0 rows"
33356
+ "computeTableDigest: streamed SELECT failed (possibly a virtual/FTS table) \u2014 digest skipped (COUNT(*) parity still enforced)"
32939
33357
  );
32940
- return { count: 0, hash: "" };
32941
- }
32942
- for (const row of rows) {
32943
- const rowObj = row;
32944
- hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
33358
+ return { count, hash: "" };
32945
33359
  }
32946
33360
  return {
32947
- count: rows.length,
33361
+ count,
32948
33362
  hash: hasher.digest("hex").slice(0, 32)
32949
33363
  };
32950
33364
  }
@@ -32962,13 +33376,30 @@ function sharedColumnsSorted(srcDb, srcTable, tgtDb, tgtTable) {
32962
33376
  return null;
32963
33377
  }
32964
33378
  }
32965
- function listTables2(db) {
33379
+ function buildSourceDigestTransform(srcDb, srcTable, tgtDb, targetTableName) {
33380
+ try {
33381
+ const srcTypeByCol = new Map(
33382
+ srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => [r.name, r.type])
33383
+ );
33384
+ const isoGlobCols = detectIsoGlobColumns(tgtDb, targetTableName);
33385
+ const tgtColByCol = new Map(
33386
+ tgtDb.prepare(`PRAGMA table_info("${targetTableName}")`).all().map((r) => [
33387
+ r.name,
33388
+ { notnull: r.notnull, dflt_value: r.dflt_value, type: r.type }
33389
+ ])
33390
+ );
33391
+ return { targetTableName, srcTypeByCol, isoGlobCols, tgtColByCol };
33392
+ } catch {
33393
+ return void 0;
33394
+ }
33395
+ }
33396
+ function listTables3(db) {
32966
33397
  const rows = db.prepare(
32967
33398
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
32968
33399
  ).all();
32969
33400
  return rows.map((r) => r.name);
32970
33401
  }
32971
- function tableExists2(db, tableName) {
33402
+ function tableExists3(db, tableName) {
32972
33403
  try {
32973
33404
  const escaped = tableName.replace(/'/g, "''");
32974
33405
  return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
@@ -33046,7 +33477,7 @@ function foreignKeyCheck(db, scope) {
33046
33477
  try {
33047
33478
  const rows = db.prepare("PRAGMA foreign_key_check").all();
33048
33479
  if (rows.length > 0) {
33049
- log3.warn(
33480
+ log5.warn(
33050
33481
  { scope, count: rows.length, sample: rows.slice(0, 5) },
33051
33482
  `verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`
33052
33483
  );
@@ -33058,7 +33489,7 @@ function foreignKeyCheck(db, scope) {
33058
33489
  fkid: r.fkid
33059
33490
  }));
33060
33491
  } catch (err) {
33061
- log3.warn({ scope, err }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
33492
+ log5.warn({ scope, err }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
33062
33493
  return [];
33063
33494
  }
33064
33495
  }
@@ -33095,13 +33526,13 @@ function sourceOrphanSignatures(db, sourceName, scope) {
33095
33526
  const rows = db.prepare("PRAGMA foreign_key_check").all();
33096
33527
  for (const r of rows) sigs.add(orphanSignature(db, r, sourceName));
33097
33528
  if (rows.length > 0) {
33098
- log3.warn(
33529
+ log5.warn(
33099
33530
  { scope, count: rows.length, sample: rows.slice(0, 5) },
33100
33531
  `verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) \u2014 these are tolerated (carried forward losslessly, flagged for data-hygiene)`
33101
33532
  );
33102
33533
  }
33103
33534
  } catch (err) {
33104
- log3.warn({ scope, err }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
33535
+ log5.warn({ scope, err }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
33105
33536
  }
33106
33537
  return sigs;
33107
33538
  }
@@ -33113,7 +33544,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33113
33544
  const preExistingForeignKeyViolations = [];
33114
33545
  const failureLines = [];
33115
33546
  const sourceOrphanSigs = /* @__PURE__ */ new Set();
33116
- if (!existsSync10(projectDbPath)) {
33547
+ if (!existsSync12(projectDbPath)) {
33117
33548
  return {
33118
33549
  ok: false,
33119
33550
  tables: [],
@@ -33124,7 +33555,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33124
33555
  error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`
33125
33556
  };
33126
33557
  }
33127
- if (!existsSync10(globalDbPath)) {
33558
+ if (!existsSync12(globalDbPath)) {
33128
33559
  return {
33129
33560
  ok: false,
33130
33561
  tables: [],
@@ -33139,7 +33570,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33139
33570
  const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
33140
33571
  try {
33141
33572
  for (const src of sources) {
33142
- if (!existsSync10(src.path)) {
33573
+ if (!existsSync12(src.path)) {
33143
33574
  onProgress?.(`Skipping ${src.name} (not present)`);
33144
33575
  continue;
33145
33576
  }
@@ -33148,9 +33579,9 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33148
33579
  for (const sig of sourceOrphanSignatures(srcSnap.db, src.name, `source:${src.name}`)) {
33149
33580
  sourceOrphanSigs.add(sig);
33150
33581
  }
33151
- const sourceTables = listTables2(srcSnap.db);
33152
- const projectTables = new Set(listTables2(projectSnap.db));
33153
- const globalTables = new Set(listTables2(globalSnap.db));
33582
+ const sourceTables = listTables3(srcSnap.db);
33583
+ const projectTables = new Set(listTables3(projectSnap.db));
33584
+ const globalTables = new Set(listTables3(globalSnap.db));
33154
33585
  for (const legacyTableName of sourceTables) {
33155
33586
  onProgress?.(`Verifying ${src.name}.${legacyTableName}\u2026`);
33156
33587
  const resolution = resolveConsolidatedTableName(src.name, legacyTableName);
@@ -33209,7 +33640,13 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33209
33640
  targetSnap.db,
33210
33641
  targetTableName
33211
33642
  );
33212
- const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols);
33643
+ const srcTransform = buildSourceDigestTransform(
33644
+ srcSnap.db,
33645
+ legacyTableName,
33646
+ targetSnap.db,
33647
+ targetTableName
33648
+ );
33649
+ const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols, srcTransform);
33213
33650
  const tgtDigest = computeTableDigest(targetSnap.db, targetTableName, cols);
33214
33651
  const countMatch = srcDigest.count === tgtDigest.count;
33215
33652
  const hashMatch = srcDigest.hash === tgtDigest.hash;
@@ -33218,7 +33655,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33218
33655
  `[${scope}] ${src.name}.${legacyTableName} \u2192 ${targetTableName}: DEFICIT \u2014 source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`
33219
33656
  );
33220
33657
  } else if (tgtDigest.count > srcDigest.count) {
33221
- log3.warn(
33658
+ log5.warn(
33222
33659
  {
33223
33660
  scope,
33224
33661
  source: src.name,
@@ -33255,7 +33692,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33255
33692
  targetOrphans.push(...foreignKeyCheck(globalSnap.db, "global"));
33256
33693
  foreignKeyViolations.push(...targetOrphans);
33257
33694
  for (const fk of targetOrphans) {
33258
- const orphanDb = tableExists2(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33695
+ const orphanDb = tableExists3(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33259
33696
  const sig = orphanSignature(orphanDb, fk);
33260
33697
  const preExisting = sourceOrphanSigs.has(sig);
33261
33698
  if (preExisting) {
@@ -33268,7 +33705,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33268
33705
  }
33269
33706
  }
33270
33707
  if (preExistingForeignKeyViolations.length > 0) {
33271
- log3.warn(
33708
+ log5.warn(
33272
33709
  {
33273
33710
  count: preExistingForeignKeyViolations.length,
33274
33711
  sample: preExistingForeignKeyViolations.slice(0, 5)
@@ -33278,7 +33715,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33278
33715
  }
33279
33716
  } catch (err) {
33280
33717
  const error = err instanceof Error ? err.message : String(err);
33281
- log3.error({ err }, "verifyMigration failed");
33718
+ log5.error({ err }, "verifyMigration failed");
33282
33719
  return {
33283
33720
  ok: false,
33284
33721
  tables,
@@ -33295,7 +33732,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33295
33732
  if (failureLines.length > 0) {
33296
33733
  const error = `verifyMigration FAILED: ${failureLines.length} issue(s):
33297
33734
  ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33298
- log3.error({ failureCount: failureLines.length }, error);
33735
+ log5.error({ failureCount: failureLines.length }, error);
33299
33736
  return {
33300
33737
  ok: false,
33301
33738
  tables,
@@ -33315,15 +33752,16 @@ ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33315
33752
  enumDrift
33316
33753
  };
33317
33754
  }
33318
- var log3, _require, CHECK_ENUM_REGEX;
33755
+ var log5, _require, CHECK_ENUM_REGEX;
33319
33756
  var init_verify_migration = __esm({
33320
33757
  "packages/core/src/store/exodus/verify-migration.ts"() {
33321
33758
  "use strict";
33322
33759
  init_src();
33323
33760
  init_logger2();
33324
33761
  init_open_cleo_db();
33762
+ init_column_transforms();
33325
33763
  init_table_name_map();
33326
- log3 = getLogger("verify-migration");
33764
+ log5 = getLogger("verify-migration");
33327
33765
  _require = createRequire3(import.meta.url);
33328
33766
  CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
33329
33767
  }
@@ -33366,8 +33804,10 @@ __export(exodus_exports, {
33366
33804
  archiveMigratedSources: () => archiveMigratedSources,
33367
33805
  archiveSourceDb: () => archiveSourceDb,
33368
33806
  archiveStrandedResidue: () => archiveStrandedResidue,
33807
+ buildExodusHealth: () => buildExodusHealth,
33369
33808
  buildExodusPlan: () => buildExodusPlan,
33370
33809
  clearExodusJournal: () => clearExodusJournal,
33810
+ computeCountParity: () => computeCountParity,
33371
33811
  deriveStagingDirName: () => deriveStagingDirName,
33372
33812
  detectStrandedResidue: () => detectStrandedResidue,
33373
33813
  exodusArchiveDir: () => exodusArchiveDir,
@@ -33379,6 +33819,7 @@ __export(exodus_exports, {
33379
33819
  runExodusMigrate: () => runExodusMigrate,
33380
33820
  runExodusStatus: () => runExodusStatus,
33381
33821
  runExodusVerify: () => runExodusVerify,
33822
+ sealExodus: () => sealExodus,
33382
33823
  sourcesPresent: () => sourcesPresent,
33383
33824
  verifyMigration: () => verifyMigration,
33384
33825
  writeExodusCompleteMarker: () => writeExodusCompleteMarker
@@ -33387,8 +33828,11 @@ var init_exodus = __esm({
33387
33828
  "packages/core/src/store/exodus/index.ts"() {
33388
33829
  "use strict";
33389
33830
  init_archive2();
33831
+ init_count_parity();
33832
+ init_health2();
33390
33833
  init_migrate();
33391
33834
  init_plan2();
33835
+ init_seal();
33392
33836
  init_status();
33393
33837
  init_table_name_map();
33394
33838
  init_types();
@@ -33404,7 +33848,7 @@ __export(on_open_exports, {
33404
33848
  isDataContinuityOk: () => isDataContinuityOk,
33405
33849
  maybeRunExodusOnOpen: () => maybeRunExodusOnOpen
33406
33850
  });
33407
- import { existsSync as existsSync11 } from "node:fs";
33851
+ import { existsSync as existsSync13 } from "node:fs";
33408
33852
  function isDisabledByEnv() {
33409
33853
  const v = process.env.CLEO_DISABLE_EXODUS_ON_OPEN;
33410
33854
  return v === "1" || v === "true";
@@ -33430,7 +33874,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33430
33874
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%'"
33431
33875
  ).all().map((r) => r.name);
33432
33876
  } catch (err) {
33433
- log4.error({ err, scope }, "exodus-on-open: failed to enumerate tables for rollback");
33877
+ log6.error({ err, scope }, "exodus-on-open: failed to enumerate tables for rollback");
33434
33878
  return;
33435
33879
  }
33436
33880
  try {
@@ -33440,7 +33884,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33440
33884
  try {
33441
33885
  nativeDb.exec(`DELETE FROM "${table}"`);
33442
33886
  } catch (err) {
33443
- log4.warn({ err, table, scope }, "exodus-on-open: failed to clear table during rollback");
33887
+ log6.warn({ err, table, scope }, "exodus-on-open: failed to clear table during rollback");
33444
33888
  }
33445
33889
  }
33446
33890
  nativeDb.exec("COMMIT");
@@ -33449,7 +33893,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33449
33893
  nativeDb.exec("ROLLBACK");
33450
33894
  } catch {
33451
33895
  }
33452
- log4.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33896
+ log6.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33453
33897
  } finally {
33454
33898
  try {
33455
33899
  nativeDb.exec("PRAGMA foreign_keys = ON");
@@ -33469,7 +33913,7 @@ async function rollbackBothScopes(scope, projectDbPath, globalDbPath) {
33469
33913
  rollbackConsolidatedToEmpty(native, s);
33470
33914
  }
33471
33915
  } catch (err) {
33472
- log4.warn(
33916
+ log6.warn(
33473
33917
  { err, scope: s, openingScope: scope },
33474
33918
  "exodus-on-open: could not roll back scope (best-effort)"
33475
33919
  );
@@ -33485,7 +33929,7 @@ function isDataContinuityOk(result) {
33485
33929
  const deficits = result.tables.filter((t) => t.targetCount < t.sourceCount);
33486
33930
  const surpluses = result.tables.filter((t) => t.targetCount > t.sourceCount);
33487
33931
  if (surpluses.length > 0) {
33488
- log4.warn(
33932
+ log6.warn(
33489
33933
  {
33490
33934
  surpluses: surpluses.map((t) => ({
33491
33935
  table: t.targetTable,
@@ -33519,7 +33963,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33519
33963
  const { buildExodusPlan: buildExodusPlan2, runExodusMigrate: runExodusMigrate2, verifyMigration: verifyMigration2, clearExodusJournal: clearExodusJournal2 } = await Promise.resolve().then(() => (init_exodus(), exodus_exports));
33520
33964
  const plan = buildExodusPlan2(cwd);
33521
33965
  const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
33522
- if (!scopeSources.some((s) => existsSync11(s.path))) {
33966
+ if (!scopeSources.some((s) => existsSync13(s.path))) {
33523
33967
  return {
33524
33968
  outcome: "skipped",
33525
33969
  reason: `no legacy ${scope}-scope source DBs present (fresh install or cross-scope-only)`
@@ -33532,11 +33976,11 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33532
33976
  if (!consolidatedIsEmpty(nativeDb, scope)) {
33533
33977
  return { outcome: "skipped", reason: "migrated by a concurrent process (lock winner)" };
33534
33978
  }
33535
- log4.info(
33979
+ log6.info(
33536
33980
  {
33537
33981
  scope,
33538
33982
  dbPath,
33539
- sources: plan.sources.filter((s) => existsSync11(s.path)).map((s) => s.name)
33983
+ sources: plan.sources.filter((s) => existsSync13(s.path)).map((s) => s.name)
33540
33984
  },
33541
33985
  "exodus-on-open: consolidated cleo.db is empty and legacy data present \u2014 auto-migrating"
33542
33986
  );
@@ -33545,23 +33989,23 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33545
33989
  const migrateResult = await runExodusMigrate2(
33546
33990
  plan,
33547
33991
  false,
33548
- (msg) => log4.debug({ scope }, `exodus-on-open: ${msg}`)
33992
+ (msg) => log6.debug({ scope }, `exodus-on-open: ${msg}`)
33549
33993
  );
33550
33994
  if (!migrateResult.ok) {
33551
33995
  await rollbackBothScopes(scope, plan.projectDbPath, plan.globalDbPath);
33552
33996
  clearExodusJournal2(migrateResult.stagingDir);
33553
33997
  const reason = `migration failed: ${migrateResult.error ?? "unknown error"} \u2014 legacy DBs kept as source`;
33554
- log4.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33998
+ log6.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33555
33999
  return { outcome: "aborted", reason };
33556
34000
  }
33557
34001
  const verifyResult = verifyMigration2(
33558
34002
  plan.sources,
33559
34003
  plan.projectDbPath,
33560
34004
  plan.globalDbPath,
33561
- (msg) => log4.debug({ scope }, `exodus-on-open verify: ${msg}`)
34005
+ (msg) => log6.debug({ scope }, `exodus-on-open verify: ${msg}`)
33562
34006
  );
33563
34007
  if (!verifyResult.ok) {
33564
- log4.warn(
34008
+ log6.warn(
33565
34009
  {
33566
34010
  scope,
33567
34011
  enumDrift: verifyResult.enumDrift.length,
@@ -33575,7 +34019,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33575
34019
  clearExodusJournal2(plan.stagingDir);
33576
34020
  const deficits = verifyResult.tables.filter((t) => t.targetCount < t.sourceCount).map((t) => `${t.targetTable}(${t.sourceCount}\u2192${t.targetCount})`);
33577
34021
  const reason = `parity verification failed \u2014 cutover aborted, legacy DBs kept as source. count deficits: [${deficits.join(", ")}]; INTRODUCED fk orphans: ${verifyResult.introducedForeignKeyViolations.length} (pre-existing source orphans tolerated: ${verifyResult.preExistingForeignKeyViolations.length}). ` + `${verifyResult.error ?? ""}`.trim();
33578
- log4.error(
34022
+ log6.error(
33579
34023
  {
33580
34024
  scope,
33581
34025
  countDeficits: deficits,
@@ -33587,14 +34031,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33587
34031
  return { outcome: "aborted", reason };
33588
34032
  }
33589
34033
  const rowsCopied = migrateResult.tables.filter((t) => !t.skipped).reduce((n, t) => n + t.rowsCopied, 0);
33590
- log4.info(
34034
+ log6.info(
33591
34035
  { scope, rowsCopied, tables: migrateResult.tables.length },
33592
34036
  "exodus-on-open: parity verified \u2014 legacy data migrated into consolidated cleo.db"
33593
34037
  );
33594
34038
  try {
33595
- const consumed = plan.sources.filter((s) => existsSync11(s.path));
34039
+ const consumed = plan.sources.filter((s) => existsSync13(s.path));
33596
34040
  const archiveResult = archiveMigratedSources(consumed, cwd);
33597
- log4.info(
34041
+ log6.info(
33598
34042
  {
33599
34043
  scope,
33600
34044
  archived: archiveResult.sources.filter((s) => s.action === "archived").length,
@@ -33603,7 +34047,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33603
34047
  "exodus-on-open: archived legacy sources + sealed completion marker(s)"
33604
34048
  );
33605
34049
  } catch (err) {
33606
- log4.error(
34050
+ log6.error(
33607
34051
  { err, scope },
33608
34052
  "exodus-on-open: post-migration archive/marker step failed (migration itself succeeded \u2014 legacy DBs left in place, will be re-checked by `cleo doctor exodus-residue`)"
33609
34053
  );
@@ -33625,14 +34069,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33625
34069
  function _isExodusInProgress() {
33626
34070
  return _exodusInProgress;
33627
34071
  }
33628
- var log4, _exodusInProgress;
34072
+ var log6, _exodusInProgress;
33629
34073
  var init_on_open = __esm({
33630
34074
  "packages/core/src/store/exodus/on-open.ts"() {
33631
34075
  "use strict";
33632
34076
  init_logger2();
33633
34077
  init_lock();
33634
34078
  init_archive2();
33635
- log4 = getLogger("exodus-on-open");
34079
+ log6 = getLogger("exodus-on-open");
33636
34080
  _exodusInProgress = false;
33637
34081
  }
33638
34082
  });
@@ -33647,7 +34091,7 @@ __export(dual_scope_db_exports, {
33647
34091
  resolveDualScopeDbPath: () => resolveDualScopeDbPath,
33648
34092
  upsertIdempotent: () => upsertIdempotent
33649
34093
  });
33650
- import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
34094
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3 } from "node:fs";
33651
34095
  import { createRequire as createRequire4 } from "node:module";
33652
34096
  import { dirname as dirname5, join as join12 } from "node:path";
33653
34097
  function cacheKey(scope, dbPath) {
@@ -33695,10 +34139,10 @@ async function openDualScopeDb(scope, cwd) {
33695
34139
  const dbPath = resolveDualScopeDbPath(scope, cwd);
33696
34140
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath, cwd) : openDualScopeDbAtPath("global", dbPath, cwd);
33697
34141
  }
33698
- async function openDedicatedDualScopeDb(scope, dbPath, log8) {
33699
- log8.debug({ scope, dbPath }, "opening DEDICATED (non-cached) dual-scope cleo.db (T11782 FIX D)");
34142
+ async function openDedicatedDualScopeDb(scope, dbPath, log10) {
34143
+ log10.debug({ scope, dbPath }, "opening DEDICATED (non-cached) dual-scope cleo.db (T11782 FIX D)");
33700
34144
  const dir = dirname5(dbPath);
33701
- if (!existsSync12(dir)) {
34145
+ if (!existsSync14(dir)) {
33702
34146
  mkdirSync3(dir, { recursive: true });
33703
34147
  }
33704
34148
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33716,7 +34160,7 @@ async function openDedicatedDualScopeDb(scope, dbPath, log8) {
33716
34160
  existenceTable(scope),
33717
34161
  `dual-scope-db[${scope}]`
33718
34162
  );
33719
- log8.debug({ scope, dbPath }, "DEDICATED dual-scope cleo.db ready (T11782 FIX D)");
34163
+ log10.debug({ scope, dbPath }, "DEDICATED dual-scope cleo.db ready (T11782 FIX D)");
33720
34164
  return {
33721
34165
  db,
33722
34166
  scope,
@@ -33741,14 +34185,14 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33741
34185
  return existing.handle;
33742
34186
  }
33743
34187
  }
33744
- const log8 = getLogger("dual-scope-db");
34188
+ const log10 = getLogger("dual-scope-db");
33745
34189
  if (dedicated) {
33746
- return openDedicatedDualScopeDb(scope, dbPath, log8);
34190
+ return openDedicatedDualScopeDb(scope, dbPath, log10);
33747
34191
  }
33748
34192
  const initPromise = (async () => {
33749
- log8.debug({ scope, dbPath }, "opening dual-scope cleo.db");
34193
+ log10.debug({ scope, dbPath }, "opening dual-scope cleo.db");
33750
34194
  const dir = dirname5(dbPath);
33751
- if (!existsSync12(dir)) {
34195
+ if (!existsSync14(dir)) {
33752
34196
  mkdirSync3(dir, { recursive: true });
33753
34197
  }
33754
34198
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33766,7 +34210,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33766
34210
  existenceTable(scope),
33767
34211
  `dual-scope-db[${scope}]`
33768
34212
  );
33769
- log8.debug({ scope, dbPath }, "dual-scope cleo.db ready");
34213
+ log10.debug({ scope, dbPath }, "dual-scope cleo.db ready");
33770
34214
  const handle = {
33771
34215
  db,
33772
34216
  scope,
@@ -33790,7 +34234,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33790
34234
  const result = await maybeRunExodusOnOpen2(scope, dbPath, nativeDb, exodusCwd);
33791
34235
  if (result.outcome === "migrated" || result.outcome === "aborted") {
33792
34236
  if (result.outcome === "aborted") {
33793
- log8.warn(
34237
+ log10.warn(
33794
34238
  { scope, reason: result.reason },
33795
34239
  "exodus-on-open aborted; consolidated cleo.db left empty, legacy kept as source"
33796
34240
  );
@@ -33798,7 +34242,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33798
34242
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath) : openDualScopeDbAtPath("global", dbPath);
33799
34243
  }
33800
34244
  } catch (err) {
33801
- log8.warn(
34245
+ log10.warn(
33802
34246
  { err, scope },
33803
34247
  "exodus-on-open hook failed (non-fatal); re-opening consolidated handle"
33804
34248
  );
@@ -34382,7 +34826,7 @@ __export(nexus_sqlite_exports, {
34382
34826
  resetNexusDbState: () => resetNexusDbState,
34383
34827
  resolveNexusMigrationsFolder: () => resolveNexusMigrationsFolder
34384
34828
  });
34385
- import { copyFileSync as copyFileSync4, existsSync as existsSync13 } from "node:fs";
34829
+ import { copyFileSync as copyFileSync4, existsSync as existsSync15 } from "node:fs";
34386
34830
  import { join as join13 } from "node:path";
34387
34831
  function getNexusDbPath(cwd) {
34388
34832
  return resolveDualScopeDbPath("project", cwd);
@@ -34424,12 +34868,12 @@ function detectAndWarnOnNestedNexus() {
34424
34868
  } catch {
34425
34869
  return false;
34426
34870
  }
34427
- if (!existsSync13(nestedPath)) return false;
34871
+ if (!existsSync15(nestedPath)) return false;
34428
34872
  if (_warnedNestedPaths.has(nestedPath)) return false;
34429
34873
  _warnedNestedPaths.add(nestedPath);
34430
34874
  const canonicalPath = getNexusDbPath();
34431
- const log8 = getLogger("nexus-sqlite");
34432
- log8.warn(
34875
+ const log10 = getLogger("nexus-sqlite");
34876
+ log10.warn(
34433
34877
  {
34434
34878
  nestedPath,
34435
34879
  canonicalPath,
@@ -34444,7 +34888,7 @@ function detectAndWarnOnNestedNexus() {
34444
34888
  function _resetNestedNexusWarningGate() {
34445
34889
  _warnedNestedPaths.clear();
34446
34890
  }
34447
- function tableExists3(nativeDb, tableName) {
34891
+ function tableExists4(nativeDb, tableName) {
34448
34892
  const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
34449
34893
  return !!result;
34450
34894
  }
@@ -34453,7 +34897,7 @@ function objectExists(nativeDb, type, name2) {
34453
34897
  return !!result;
34454
34898
  }
34455
34899
  function ensureNexusFts5(nativeDb) {
34456
- if (!tableExists3(nativeDb, "nexus_nodes")) return;
34900
+ if (!tableExists4(nativeDb, "nexus_nodes")) return;
34457
34901
  if (!objectExists(nativeDb, "table", "nexus_symbols_fts")) {
34458
34902
  nativeDb.exec(`
34459
34903
  CREATE VIRTUAL TABLE nexus_symbols_fts USING fts5(
@@ -34499,7 +34943,7 @@ function ensureNexusFts5(nativeDb) {
34499
34943
  `);
34500
34944
  }
34501
34945
  function ensureNexusRelationWeights(nativeDb) {
34502
- if (!tableExists3(nativeDb, "nexus_relations")) return;
34946
+ if (!tableExists4(nativeDb, "nexus_relations")) return;
34503
34947
  nativeDb.exec(`
34504
34948
  CREATE TABLE IF NOT EXISTS nexus_relation_weights (
34505
34949
  relation_id TEXT PRIMARY KEY NOT NULL,
@@ -34581,9 +35025,9 @@ function ensureNexusRelationWeights(nativeDb) {
34581
35025
  }
34582
35026
  function runNexusMigrations(nativeDb, db) {
34583
35027
  const migrationsFolder = resolveNexusMigrationsFolder();
34584
- if (tableExists3(nativeDb, "nexus_nodes") && _nexusDbPath) {
35028
+ if (tableExists4(nativeDb, "nexus_nodes") && _nexusDbPath) {
34585
35029
  const backupPath = _nexusDbPath.replace(/\.db$/, "-pre-cleo.db.bak");
34586
- if (!existsSync13(backupPath)) {
35030
+ if (!existsSync15(backupPath)) {
34587
35031
  try {
34588
35032
  copyFileSync4(_nexusDbPath, backupPath);
34589
35033
  } catch {
@@ -34715,7 +35159,7 @@ var init_nexus_sqlite = __esm({
34715
35159
 
34716
35160
  // packages/core/src/paths.ts
34717
35161
  import { AsyncLocalStorage } from "node:async_hooks";
34718
- import { existsSync as existsSync14, readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
35162
+ import { existsSync as existsSync16, readFileSync as readFileSync5, statSync as statSync6 } from "node:fs";
34719
35163
  import { createRequire as createRequire5 } from "node:module";
34720
35164
  import { homedir } from "node:os";
34721
35165
  import { basename as basename4, dirname as dirname6, join as join14, resolve as resolve3 } from "node:path";
@@ -34809,7 +35253,7 @@ function _resolveProjectByCwdFromNexus(cwd) {
34809
35253
  try {
34810
35254
  const cleoHome = getCleoHome();
34811
35255
  const globalDbPath = join14(cleoHome, "cleo.db");
34812
- if (!existsSync14(globalDbPath)) return null;
35256
+ if (!existsSync16(globalDbPath)) return null;
34813
35257
  const start = resolve3(cwd ?? process.cwd());
34814
35258
  let current = start;
34815
35259
  const DatabaseSync3 = _getDatabaseSyncCtor();
@@ -34848,7 +35292,7 @@ function _findCleoDirRoot(cwd) {
34848
35292
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
34849
35293
  if (!isDangerousRoot) {
34850
35294
  try {
34851
- if (statSync4(join14(current, ".cleo")).isDirectory()) {
35295
+ if (statSync6(join14(current, ".cleo")).isDirectory()) {
34852
35296
  return current;
34853
35297
  }
34854
35298
  } catch {
@@ -34922,7 +35366,7 @@ function _cwdHasGitAncestor(cwd) {
34922
35366
  while (true) {
34923
35367
  const gitMarker = join14(current, ".git");
34924
35368
  try {
34925
- if (existsSync14(gitMarker)) {
35369
+ if (existsSync16(gitMarker)) {
34926
35370
  return true;
34927
35371
  }
34928
35372
  } catch {
@@ -34935,15 +35379,15 @@ function _cwdHasGitAncestor(cwd) {
34935
35379
  function _resolveMainRepoFromGitlink(gitlinkDir) {
34936
35380
  try {
34937
35381
  const gitLinkPath = join14(gitlinkDir, ".git");
34938
- if (!existsSync14(gitLinkPath)) return null;
34939
- const stat2 = statSync4(gitLinkPath);
35382
+ if (!existsSync16(gitLinkPath)) return null;
35383
+ const stat2 = statSync6(gitLinkPath);
34940
35384
  if (!stat2.isFile()) return null;
34941
35385
  const gitLinkContent = readFileSync5(gitLinkPath, "utf-8").trim();
34942
35386
  const match = gitLinkContent.match(/^gitdir:\s*(.+)$/m);
34943
35387
  if (!match) return null;
34944
35388
  const gitdir = match[1].trim();
34945
35389
  const mainRepo = dirname6(dirname6(dirname6(gitdir)));
34946
- if (existsSync14(join14(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
35390
+ if (existsSync16(join14(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
34947
35391
  return mainRepo;
34948
35392
  }
34949
35393
  } catch {
@@ -34952,19 +35396,19 @@ function _resolveMainRepoFromGitlink(gitlinkDir) {
34952
35396
  }
34953
35397
  function validateProjectRoot(candidate) {
34954
35398
  const cleoDir = join14(candidate, ".cleo");
34955
- if (!existsSync14(cleoDir)) {
35399
+ if (!existsSync16(cleoDir)) {
34956
35400
  return false;
34957
35401
  }
34958
35402
  const projectInfoPath = join14(cleoDir, "project-info.json");
34959
- if (existsSync14(projectInfoPath)) {
35403
+ if (existsSync16(projectInfoPath)) {
34960
35404
  try {
34961
35405
  const raw = readFileSync5(projectInfoPath, "utf-8");
34962
35406
  const parsed = JSON.parse(raw);
34963
35407
  if (typeof parsed === "object" && parsed !== null && "projectId" in parsed && typeof parsed["projectId"] === "string" && parsed["projectId"] !== "") {
34964
35408
  const gitMarker = join14(candidate, ".git");
34965
- if (existsSync14(gitMarker)) {
35409
+ if (existsSync16(gitMarker)) {
34966
35410
  try {
34967
- if (!statSync4(gitMarker).isDirectory()) {
35411
+ if (!statSync6(gitMarker).isDirectory()) {
34968
35412
  return false;
34969
35413
  }
34970
35414
  } catch {
@@ -34977,10 +35421,10 @@ function validateProjectRoot(candidate) {
34977
35421
  }
34978
35422
  }
34979
35423
  const gitDir = join14(candidate, ".git");
34980
- if (existsSync14(gitDir)) {
35424
+ if (existsSync16(gitDir)) {
34981
35425
  let isRealGitDir = false;
34982
35426
  try {
34983
- const stat2 = statSync4(gitDir);
35427
+ const stat2 = statSync6(gitDir);
34984
35428
  isRealGitDir = stat2.isDirectory();
34985
35429
  } catch {
34986
35430
  isRealGitDir = false;
@@ -35027,16 +35471,16 @@ function getProjectRoot(cwd) {
35027
35471
  const cleoDir = join14(current, ".cleo");
35028
35472
  const gitDir = join14(current, ".git");
35029
35473
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
35030
- if (existsSync14(cleoDir) && !isDangerousRoot) {
35474
+ if (existsSync16(cleoDir) && !isDangerousRoot) {
35031
35475
  if (validateProjectRoot(current)) {
35032
35476
  return current;
35033
35477
  }
35034
35478
  skippedCleoDirs.push(current);
35035
35479
  }
35036
- if (existsSync14(gitDir) && !isDangerousRoot) {
35480
+ if (existsSync16(gitDir) && !isDangerousRoot) {
35037
35481
  let isRealGitDir = false;
35038
35482
  try {
35039
- isRealGitDir = statSync4(gitDir).isDirectory();
35483
+ isRealGitDir = statSync6(gitDir).isDirectory();
35040
35484
  } catch {
35041
35485
  isRealGitDir = false;
35042
35486
  }
@@ -35089,7 +35533,7 @@ function isAbsolutePath(path2) {
35089
35533
  function _readProjectNameFromInfo(projectRoot) {
35090
35534
  try {
35091
35535
  const infoPath = join14(projectRoot, ".cleo", "project-info.json");
35092
- if (!existsSync14(infoPath)) return void 0;
35536
+ if (!existsSync16(infoPath)) return void 0;
35093
35537
  const raw = readFileSync5(infoPath, "utf-8");
35094
35538
  const data = JSON.parse(raw);
35095
35539
  return typeof data.name === "string" && data.name.length > 0 ? data.name : void 0;
@@ -35363,7 +35807,7 @@ __export(config_exports, {
35363
35807
  parseConfigValue: () => parseConfigValue,
35364
35808
  setConfigValue: () => setConfigValue
35365
35809
  });
35366
- import { existsSync as existsSync15 } from "node:fs";
35810
+ import { existsSync as existsSync17 } from "node:fs";
35367
35811
  import { mkdir as mkdir3, writeFile } from "node:fs/promises";
35368
35812
  import { dirname as dirname7 } from "node:path";
35369
35813
  function getNestedValue(obj, path2) {
@@ -35487,7 +35931,7 @@ function parseConfigValue(value) {
35487
35931
  }
35488
35932
  async function setConfigValue(key, value, cwd, opts) {
35489
35933
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35490
- if (!existsSync15(configPath)) {
35934
+ if (!existsSync17(configPath)) {
35491
35935
  const dir = dirname7(configPath);
35492
35936
  await mkdir3(dir, { recursive: true });
35493
35937
  await writeFile(configPath, "{}", "utf-8");
@@ -35501,7 +35945,7 @@ async function setConfigValue(key, value, cwd, opts) {
35501
35945
  async function applyStrictnessPreset(preset, cwd, opts) {
35502
35946
  const definition = STRICTNESS_PRESETS[preset];
35503
35947
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35504
- if (!existsSync15(configPath)) {
35948
+ if (!existsSync17(configPath)) {
35505
35949
  const dir = dirname7(configPath);
35506
35950
  await mkdir3(dir, { recursive: true });
35507
35951
  await writeFile(configPath, "{}", "utf-8");
@@ -35847,7 +36291,7 @@ function brainTablesAreConsolidatedShape(nativeDb) {
35847
36291
  return createdAt !== void 0 && createdAt.type.toUpperCase() !== "INTEGER";
35848
36292
  }
35849
36293
  function establishLegacyBrainSchema(nativeDb, db) {
35850
- const log8 = getLogger("brain-schema");
36294
+ const log10 = getLogger("brain-schema");
35851
36295
  if (brainTablesAreConsolidatedShape(nativeDb)) {
35852
36296
  const fkRow = nativeDb.prepare("PRAGMA foreign_keys").get();
35853
36297
  const fkWasOn = fkRow?.foreign_keys === 1;
@@ -35857,7 +36301,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35857
36301
  try {
35858
36302
  nativeDb.exec(`DROP TABLE IF EXISTS \`${table}\``);
35859
36303
  } catch (err) {
35860
- log8.warn(
36304
+ log10.warn(
35861
36305
  { table, err },
35862
36306
  "Failed to drop consolidated brain table during legacy rebuild."
35863
36307
  );
@@ -35866,7 +36310,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35866
36310
  } finally {
35867
36311
  nativeDb.exec(`PRAGMA foreign_keys=${fkWasOn ? "ON" : "OFF"}`);
35868
36312
  }
35869
- log8.debug(
36313
+ log10.debug(
35870
36314
  { count: CONSOLIDATED_BRAIN_TABLES.length },
35871
36315
  "Dropped consolidated (exodus-target) brain tables \u2014 rebuilding in legacy runtime shape."
35872
36316
  );
@@ -35877,7 +36321,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35877
36321
  nativeDb,
35878
36322
  migrationsFolder
35879
36323
  );
35880
- log8.debug(
36324
+ log10.debug(
35881
36325
  { marked, applied },
35882
36326
  "brain consolidated cleo.db reconcile (T11647) \u2014 marked already-present migrations applied + executed the missing unprefixed-table migrations directly."
35883
36327
  );
@@ -36427,7 +36871,7 @@ async function upsertTask(db, row, archiveFields, allowOrphanParent = false) {
36427
36871
  if (allowOrphanParent) {
36428
36872
  row = { ...row, parentId: null };
36429
36873
  } else {
36430
- log5.warn(
36874
+ log7.warn(
36431
36875
  { taskId: row.id, parentId: row.parentId },
36432
36876
  "upsertTask: parentId references a non-existent task \u2014 parent relationship may be lost"
36433
36877
  );
@@ -36610,13 +37054,13 @@ async function loadRelationsForTasks(db, tasks2) {
36610
37054
  task.relates = relations && relations.length > 0 ? relations : [];
36611
37055
  }
36612
37056
  }
36613
- var log5;
37057
+ var log7;
36614
37058
  var init_db_helpers = __esm({
36615
37059
  "packages/core/src/store/db-helpers.ts"() {
36616
37060
  "use strict";
36617
37061
  init_logger2();
36618
37062
  init_tasks_schema();
36619
- log5 = getLogger("db-helpers");
37063
+ log7 = getLogger("db-helpers");
36620
37064
  }
36621
37065
  });
36622
37066
 
@@ -36758,7 +37202,7 @@ var init_sqlite2 = __esm({
36758
37202
  });
36759
37203
 
36760
37204
  // packages/core/src/store/agent-registry-store.ts
36761
- import { existsSync as existsSync16 } from "node:fs";
37205
+ import { existsSync as existsSync18 } from "node:fs";
36762
37206
  import { join as join16 } from "node:path";
36763
37207
  function getGlobalAgentRegistryDbPath() {
36764
37208
  const cleoHome = getCleoHome();
@@ -36803,7 +37247,7 @@ function writeAgentRegistrySchemaVersionSentinel(db) {
36803
37247
  }
36804
37248
  async function ensureGlobalAgentRegistryDb() {
36805
37249
  const dbPath = getGlobalAgentRegistryDbPath();
36806
- const alreadyExists = existsSync16(dbPath);
37250
+ const alreadyExists = existsSync18(dbPath);
36807
37251
  const dualHandle = await openDualScopeDb("global");
36808
37252
  const nativeDb = dualHandle.db.$client ?? null;
36809
37253
  if (!nativeDb) {
@@ -36852,7 +37296,7 @@ var init_agent_registry_store = __esm({
36852
37296
  });
36853
37297
 
36854
37298
  // packages/core/src/store/conduit-sqlite.ts
36855
- import { existsSync as existsSync17 } from "node:fs";
37299
+ import { existsSync as existsSync19 } from "node:fs";
36856
37300
  import { createRequire as createRequire7 } from "node:module";
36857
37301
  function _getDrizzle2() {
36858
37302
  if (_drizzle3 === null) {
@@ -36883,7 +37327,7 @@ async function ensureConduitDb(projectRoot) {
36883
37327
  }
36884
37328
  if (_initPromise3) return _initPromise3;
36885
37329
  _initPromise3 = (async () => {
36886
- const alreadyExists = existsSync17(dbPath);
37330
+ const alreadyExists = existsSync19(dbPath);
36887
37331
  const dualHandle = await openDualScopeDb("project", projectRoot);
36888
37332
  const nativeDb = dualHandle.db.$client ?? null;
36889
37333
  if (!nativeDb) {
@@ -37177,10 +37621,10 @@ var init_skills_db = __esm({
37177
37621
  import {
37178
37622
  chmodSync,
37179
37623
  copyFileSync as copyFileSync5,
37180
- existsSync as existsSync18,
37624
+ existsSync as existsSync20,
37181
37625
  mkdirSync as mkdirSync5,
37182
37626
  readdirSync as readdirSync4,
37183
- statSync as statSync5,
37627
+ statSync as statSync7,
37184
37628
  unlinkSync as unlinkSync3
37185
37629
  } from "node:fs";
37186
37630
  import { join as join18 } from "node:path";
@@ -37219,7 +37663,7 @@ async function openAgentRegistryDbForSnapshot() {
37219
37663
  async function openTelemetryDbForSnapshot() {
37220
37664
  try {
37221
37665
  const path2 = join18(getCleoHome(), "telemetry.db");
37222
- if (!existsSync18(path2)) return null;
37666
+ if (!existsSync20(path2)) return null;
37223
37667
  } catch {
37224
37668
  return null;
37225
37669
  }
@@ -37234,7 +37678,7 @@ function buildRawFileVacuumOpener(entry) {
37234
37678
  return async (cwd) => {
37235
37679
  const path2 = resolveInventoryPath(entry, cwd);
37236
37680
  if (!path2) return null;
37237
- if (!existsSync18(path2)) return null;
37681
+ if (!existsSync20(path2)) return null;
37238
37682
  try {
37239
37683
  const { DatabaseSync: DatabaseSync3 } = await import("node:sqlite");
37240
37684
  return new DatabaseSync3(path2, { readOnly: true });
@@ -37325,7 +37769,7 @@ function rotateSnapshots(backupDir, prefix) {
37325
37769
  const files = readdirSync4(backupDir).filter((f) => pattern.test(f)).map((f) => ({
37326
37770
  name: f,
37327
37771
  path: join18(backupDir, f),
37328
- mtimeMs: statSync5(join18(backupDir, f)).mtimeMs
37772
+ mtimeMs: statSync7(join18(backupDir, f)).mtimeMs
37329
37773
  })).sort((a, b) => a.mtimeMs - b.mtimeMs);
37330
37774
  while (files.length >= MAX_SNAPSHOTS) {
37331
37775
  const oldest = files.shift();
@@ -37384,12 +37828,12 @@ function listSqliteBackupsForPrefix(prefix, cwd) {
37384
37828
  try {
37385
37829
  const cleoDir = getCleoDir(cwd);
37386
37830
  const backupDir = join18(cleoDir, "backups", "sqlite");
37387
- if (!existsSync18(backupDir)) return [];
37831
+ if (!existsSync20(backupDir)) return [];
37388
37832
  const pattern = snapshotPattern(prefix);
37389
37833
  return readdirSync4(backupDir).filter((f) => pattern.test(f)).map((f) => ({
37390
37834
  name: f,
37391
37835
  path: join18(backupDir, f),
37392
- mtimeMs: statSync5(join18(backupDir, f)).mtimeMs
37836
+ mtimeMs: statSync7(join18(backupDir, f)).mtimeMs
37393
37837
  })).sort((a, b) => b.mtimeMs - a.mtimeMs);
37394
37838
  } catch {
37395
37839
  return [];
@@ -37572,7 +38016,7 @@ __export(sqlite_exports, {
37572
38016
  resolveMigrationsFolder: () => resolveMigrationsFolder,
37573
38017
  schema: () => tasks_schema_exports
37574
38018
  });
37575
- import { copyFileSync as copyFileSync6, existsSync as existsSync19, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "node:fs";
38019
+ import { copyFileSync as copyFileSync6, existsSync as existsSync21, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "node:fs";
37576
38020
  import { createRequire as createRequire9 } from "node:module";
37577
38021
  import { eq as eq5 } from "drizzle-orm";
37578
38022
  function _getDrizzle3() {
@@ -37614,7 +38058,7 @@ function countBackupTasks(backupDb) {
37614
38058
  }
37615
38059
  async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37616
38060
  const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
37617
- const log8 = getLogger("sqlite");
38061
+ const log10 = getLogger("sqlite");
37618
38062
  try {
37619
38063
  const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks_tasks").get();
37620
38064
  const taskCount = countResult?.cnt ?? 0;
@@ -37640,13 +38084,13 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37640
38084
  async () => {
37641
38085
  const currentTaskCount = await recountTasksFromDisk(dbPath);
37642
38086
  if (currentTaskCount > 0) {
37643
- log8.info(
38087
+ log10.info(
37644
38088
  { dbPath, currentTaskCount },
37645
38089
  "Auto-recovery skipped: database was populated by a concurrent process while acquiring the first-open lock (T11662 double-checked re-query)."
37646
38090
  );
37647
38091
  return;
37648
38092
  }
37649
- log8.warn(
38093
+ log10.warn(
37650
38094
  { dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
37651
38095
  `Empty database detected with ${backupTaskCount}-task backup available. Auto-recovering from backup. This likely happened because git-tracked WAL/SHM files were overwritten during a branch switch (T5188).`
37652
38096
  );
@@ -37666,7 +38110,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37666
38110
  const tempPath = dbPath + ".recovery-tmp";
37667
38111
  copyFileSync6(newestBackup.path, tempPath);
37668
38112
  renameSync3(tempPath, dbPath);
37669
- log8.info(
38113
+ log10.info(
37670
38114
  { dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
37671
38115
  "Database auto-recovered from backup successfully."
37672
38116
  );
@@ -37683,7 +38127,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37683
38127
  { stale: 6e5, retries: 30 }
37684
38128
  );
37685
38129
  } catch (err) {
37686
- log8.error({ err, dbPath }, "Auto-recovery from backup failed. Continuing with empty database.");
38130
+ log10.error({ err, dbPath }, "Auto-recovery from backup failed. Continuing with empty database.");
37687
38131
  }
37688
38132
  }
37689
38133
  async function getDb(cwd) {
@@ -37783,7 +38227,7 @@ async function getSchemaVersion(cwd) {
37783
38227
  return result[0]?.value ?? null;
37784
38228
  }
37785
38229
  function dbExists(cwd) {
37786
- return existsSync19(resolveDualScopeDbPath("project", cwd));
38230
+ return existsSync21(resolveDualScopeDbPath("project", cwd));
37787
38231
  }
37788
38232
  function getNativeDb() {
37789
38233
  return _nativeDb3;
@@ -38971,7 +39415,7 @@ var init_sqlite_data_accessor = __esm({
38971
39415
  });
38972
39416
 
38973
39417
  // packages/core/src/sequence/index.ts
38974
- import { existsSync as existsSync20, readFileSync as readFileSync6, renameSync as renameSync4 } from "node:fs";
39418
+ import { existsSync as existsSync22, readFileSync as readFileSync6, renameSync as renameSync4 } from "node:fs";
38975
39419
  import { join as join20 } from "node:path";
38976
39420
  function getLegacySequenceJsonPath(cwd) {
38977
39421
  return join20(resolveOrCwd(cwd), ".cleo", ".sequence.json");
@@ -38988,7 +39432,7 @@ function isSeedSequence(value) {
38988
39432
  return value.counter === 0 && value.lastId === "T000" && value.checksum === "seed";
38989
39433
  }
38990
39434
  function readLegacySequenceFile(path2) {
38991
- if (!existsSync20(path2)) return null;
39435
+ if (!existsSync22(path2)) return null;
38992
39436
  try {
38993
39437
  const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
38994
39438
  return isValidSequenceState(parsed) ? parsed : null;
@@ -38997,10 +39441,10 @@ function readLegacySequenceFile(path2) {
38997
39441
  }
38998
39442
  }
38999
39443
  function renameLegacyFile(path2) {
39000
- if (!existsSync20(path2)) return;
39444
+ if (!existsSync22(path2)) return;
39001
39445
  const migratedPath = `${path2}.migrated`;
39002
39446
  try {
39003
- if (!existsSync20(migratedPath)) {
39447
+ if (!existsSync22(migratedPath)) {
39004
39448
  renameSync4(path2, migratedPath);
39005
39449
  return;
39006
39450
  }
@@ -39163,7 +39607,7 @@ var init_sequence = __esm({
39163
39607
 
39164
39608
  // packages/core/src/store/git-checkpoint.ts
39165
39609
  import { execFile as execFile2 } from "node:child_process";
39166
- import { existsSync as existsSync21 } from "node:fs";
39610
+ import { existsSync as existsSync23 } from "node:fs";
39167
39611
  import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
39168
39612
  import { join as join21, resolve as resolve5 } from "node:path";
39169
39613
  import { promisify as promisify2 } from "node:util";
@@ -39189,7 +39633,7 @@ async function cleoGitCommand(args, cleoDir) {
39189
39633
  }
39190
39634
  }
39191
39635
  function isCleoGitInitialized(cleoDir) {
39192
- return existsSync21(join21(cleoDir, ".git", "HEAD"));
39636
+ return existsSync23(join21(cleoDir, ".git", "HEAD"));
39193
39637
  }
39194
39638
  async function loadStateFileAllowlist(cwd) {
39195
39639
  try {
@@ -39233,14 +39677,14 @@ async function isCleoGitRepo(cleoDir) {
39233
39677
  return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
39234
39678
  }
39235
39679
  function isMergeInProgress(cleoDir) {
39236
- return existsSync21(join21(cleoDir, ".git", "MERGE_HEAD"));
39680
+ return existsSync23(join21(cleoDir, ".git", "MERGE_HEAD"));
39237
39681
  }
39238
39682
  async function isDetachedHead(cleoDir) {
39239
39683
  const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
39240
39684
  return !result.success;
39241
39685
  }
39242
39686
  function isRebaseInProgress(cleoDir) {
39243
- return existsSync21(join21(cleoDir, ".git", "rebase-merge")) || existsSync21(join21(cleoDir, ".git", "rebase-apply"));
39687
+ return existsSync23(join21(cleoDir, ".git", "rebase-merge")) || existsSync23(join21(cleoDir, ".git", "rebase-apply"));
39244
39688
  }
39245
39689
  async function recordCheckpointTime(cleoDir) {
39246
39690
  try {
@@ -39264,7 +39708,7 @@ async function getChangedStateFiles(cleoDir, cwd) {
39264
39708
  const allStateFiles = await getAllStateFiles(cwd);
39265
39709
  for (const stateFile of allStateFiles) {
39266
39710
  const fullPath = join21(cleoDir, stateFile);
39267
- if (!existsSync21(fullPath)) continue;
39711
+ if (!existsSync23(fullPath)) continue;
39268
39712
  const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
39269
39713
  const cachedResult = await cleoGitCommand(
39270
39714
  ["diff", "--cached", "--quiet", "--", stateFile],
@@ -39291,7 +39735,7 @@ async function shouldCheckpoint(options) {
39291
39735
  const config = await loadCheckpointConfig(cwd);
39292
39736
  if (!config.enabled) return false;
39293
39737
  const cleoDir = getCleoDir(cwd);
39294
- if (!existsSync21(cleoDir)) return false;
39738
+ if (!existsSync23(cleoDir)) return false;
39295
39739
  if (!isCleoGitInitialized(cleoDir)) return false;
39296
39740
  if (!await isCleoGitRepo(cleoDir)) return false;
39297
39741
  if (isMergeInProgress(cleoDir)) return false;
@@ -39369,7 +39813,7 @@ async function ensureSequenceValid(cwd, options) {
39369
39813
  if (!options?.validateSequence) return;
39370
39814
  const check = await checkSequence(cwd);
39371
39815
  if (!check.valid) {
39372
- log6.warn({ counter: check.counter, maxId: check.maxIdInData }, "Sequence behind, repairing");
39816
+ log8.warn({ counter: check.counter, maxId: check.maxIdInData }, "Sequence behind, repairing");
39373
39817
  const repair = await repairSequence(cwd);
39374
39818
  if (!repair.repaired && options.strict) {
39375
39819
  throw new DataSafetyError(`Sequence repair failed: ${repair.message}`, "SEQUENCE_INVALID", {
@@ -39386,7 +39830,7 @@ async function checkpoint(context, cwd, options) {
39386
39830
  stats.checkpoints++;
39387
39831
  stats.lastCheckpoint = /* @__PURE__ */ new Date();
39388
39832
  } catch (err) {
39389
- log6.warn({ err }, "Checkpoint failed (non-fatal)");
39833
+ log8.warn({ err }, "Checkpoint failed (non-fatal)");
39390
39834
  }
39391
39835
  vacuumIntoBackup({ cwd }).catch(() => {
39392
39836
  });
@@ -39448,7 +39892,7 @@ async function safeAppendLog(accessor, entry, cwd, options) {
39448
39892
  stats.writes++;
39449
39893
  await checkpoint("log entry", cwd, opts);
39450
39894
  }
39451
- var log6, DataSafetyError, DEFAULT_SAFETY, stats;
39895
+ var log8, DataSafetyError, DEFAULT_SAFETY, stats;
39452
39896
  var init_data_safety_central = __esm({
39453
39897
  "packages/core/src/store/data-safety-central.ts"() {
39454
39898
  "use strict";
@@ -39458,7 +39902,7 @@ var init_data_safety_central = __esm({
39458
39902
  init_sqlite3();
39459
39903
  init_sqlite_backup();
39460
39904
  init_tasks_schema();
39461
- log6 = getLogger("data-safety");
39905
+ log8 = getLogger("data-safety");
39462
39906
  DataSafetyError = class extends Error {
39463
39907
  constructor(message, code, context) {
39464
39908
  super(message);
@@ -39498,7 +39942,7 @@ function isSafetyDisabled() {
39498
39942
  }
39499
39943
  function wrapWithSafety(accessor, cwd) {
39500
39944
  if (isSafetyDisabled()) {
39501
- log7.warn(
39945
+ log9.warn(
39502
39946
  "Safety disabled - emergency mode (CLEO_DISABLE_SAFETY=true). Data integrity checks bypassed."
39503
39947
  );
39504
39948
  return accessor;
@@ -39519,13 +39963,13 @@ function getSafetyStatus() {
39519
39963
  enabled: true
39520
39964
  };
39521
39965
  }
39522
- var log7, SafetyDataAccessor;
39966
+ var log9, SafetyDataAccessor;
39523
39967
  var init_safety_data_accessor = __esm({
39524
39968
  "packages/core/src/store/safety-data-accessor.ts"() {
39525
39969
  "use strict";
39526
39970
  init_logger2();
39527
39971
  init_data_safety_central();
39528
- log7 = getLogger("data-safety");
39972
+ log9 = getLogger("data-safety");
39529
39973
  SafetyDataAccessor = class {
39530
39974
  /** The underlying accessor being wrapped. */
39531
39975
  inner;
@@ -39549,7 +39993,7 @@ var init_safety_data_accessor = __esm({
39549
39993
  ...config
39550
39994
  };
39551
39995
  if (this.config.verbose) {
39552
- log7.debug({ engine: inner.engine }, "SafetyDataAccessor initialized");
39996
+ log9.debug({ engine: inner.engine }, "SafetyDataAccessor initialized");
39553
39997
  }
39554
39998
  }
39555
39999
  /** The storage engine backing this accessor. */
@@ -39561,7 +40005,7 @@ var init_safety_data_accessor = __esm({
39561
40005
  */
39562
40006
  logVerbose(message) {
39563
40007
  if (this.config.verbose) {
39564
- log7.debug(message);
40008
+ log9.debug(message);
39565
40009
  }
39566
40010
  }
39567
40011
  /**