@cleocode/core 2026.6.6 → 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 (127) 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 +910 -518
  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/index.d.ts +1 -3
  20. package/dist/llm/index.d.ts.map +1 -1
  21. package/dist/llm/index.js +1 -2
  22. package/dist/llm/index.js.map +1 -1
  23. package/dist/llm/model-metadata.d.ts +14 -0
  24. package/dist/llm/model-metadata.d.ts.map +1 -1
  25. package/dist/llm/model-metadata.js +23 -0
  26. package/dist/llm/model-metadata.js.map +1 -1
  27. package/dist/llm/model-runner.d.ts.map +1 -1
  28. package/dist/llm/model-runner.js +104 -74
  29. package/dist/llm/model-runner.js.map +1 -1
  30. package/dist/llm/plugin-facade.js +1006 -588
  31. package/dist/llm/plugin-facade.js.map +3 -3
  32. package/dist/llm/provider-registry/builtin/anthropic.d.ts.map +1 -1
  33. package/dist/llm/provider-registry/builtin/anthropic.js +4 -0
  34. package/dist/llm/provider-registry/builtin/anthropic.js.map +1 -1
  35. package/dist/llm/provider-registry/builtin/gemini.d.ts.map +1 -1
  36. package/dist/llm/provider-registry/builtin/gemini.js +4 -0
  37. package/dist/llm/provider-registry/builtin/gemini.js.map +1 -1
  38. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  39. package/dist/llm/provider-registry/builtin/ollama.js +4 -0
  40. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  41. package/dist/llm/provider-registry/builtin/openai.d.ts.map +1 -1
  42. package/dist/llm/provider-registry/builtin/openai.js +6 -0
  43. package/dist/llm/provider-registry/builtin/openai.js.map +1 -1
  44. package/dist/llm/transports/index.d.ts +5 -3
  45. package/dist/llm/transports/index.d.ts.map +1 -1
  46. package/dist/llm/transports/index.js +5 -2
  47. package/dist/llm/transports/index.js.map +1 -1
  48. package/dist/reconciliation/reconciliation-engine.d.ts.map +1 -1
  49. package/dist/reconciliation/reconciliation-engine.js +3 -0
  50. package/dist/reconciliation/reconciliation-engine.js.map +1 -1
  51. package/dist/release/plan.d.ts +27 -0
  52. package/dist/release/plan.d.ts.map +1 -1
  53. package/dist/release/plan.js +36 -2
  54. package/dist/release/plan.js.map +1 -1
  55. package/dist/release/provenance-fk.d.ts +74 -0
  56. package/dist/release/provenance-fk.d.ts.map +1 -0
  57. package/dist/release/provenance-fk.js +122 -0
  58. package/dist/release/provenance-fk.js.map +1 -0
  59. package/dist/release/reconcile.d.ts +2 -53
  60. package/dist/release/reconcile.d.ts.map +1 -1
  61. package/dist/release/reconcile.js +13 -93
  62. package/dist/release/reconcile.js.map +1 -1
  63. package/dist/sticky/convert.d.ts.map +1 -1
  64. package/dist/sticky/convert.js +3 -0
  65. package/dist/sticky/convert.js.map +1 -1
  66. package/dist/store/exodus/column-transforms.d.ts +35 -8
  67. package/dist/store/exodus/column-transforms.d.ts.map +1 -1
  68. package/dist/store/exodus/column-transforms.js +47 -13
  69. package/dist/store/exodus/column-transforms.js.map +1 -1
  70. package/dist/store/exodus/count-parity.d.ts +71 -0
  71. package/dist/store/exodus/count-parity.d.ts.map +1 -0
  72. package/dist/store/exodus/count-parity.js +124 -0
  73. package/dist/store/exodus/count-parity.js.map +1 -0
  74. package/dist/store/exodus/health.d.ts +70 -0
  75. package/dist/store/exodus/health.d.ts.map +1 -0
  76. package/dist/store/exodus/health.js +130 -0
  77. package/dist/store/exodus/health.js.map +1 -0
  78. package/dist/store/exodus/index.d.ts +3 -0
  79. package/dist/store/exodus/index.d.ts.map +1 -1
  80. package/dist/store/exodus/index.js +3 -0
  81. package/dist/store/exodus/index.js.map +1 -1
  82. package/dist/store/exodus/migrate.d.ts.map +1 -1
  83. package/dist/store/exodus/migrate.js +98 -31
  84. package/dist/store/exodus/migrate.js.map +1 -1
  85. package/dist/store/exodus/plan.d.ts +48 -4
  86. package/dist/store/exodus/plan.d.ts.map +1 -1
  87. package/dist/store/exodus/plan.js +67 -9
  88. package/dist/store/exodus/plan.js.map +1 -1
  89. package/dist/store/exodus/seal.d.ts +69 -0
  90. package/dist/store/exodus/seal.d.ts.map +1 -0
  91. package/dist/store/exodus/seal.js +73 -0
  92. package/dist/store/exodus/seal.js.map +1 -0
  93. package/dist/store/exodus/types.d.ts +24 -1
  94. package/dist/store/exodus/types.d.ts.map +1 -1
  95. package/dist/store/exodus/types.js.map +1 -1
  96. package/dist/store/exodus/verify-migration.d.ts.map +1 -1
  97. package/dist/store/exodus/verify-migration.js +53 -26
  98. package/dist/store/exodus/verify-migration.js.map +1 -1
  99. package/dist/tasks/add.d.ts +13 -0
  100. package/dist/tasks/add.d.ts.map +1 -1
  101. package/dist/tasks/add.js +50 -18
  102. package/dist/tasks/add.js.map +1 -1
  103. package/dist/tasks/archive.d.ts.map +1 -1
  104. package/dist/tasks/archive.js +12 -7
  105. package/dist/tasks/archive.js.map +1 -1
  106. package/dist/tasks/child-disposition.d.ts +66 -0
  107. package/dist/tasks/child-disposition.d.ts.map +1 -0
  108. package/dist/tasks/child-disposition.js +80 -0
  109. package/dist/tasks/child-disposition.js.map +1 -0
  110. package/dist/tasks/delete-preview.js +1 -1
  111. package/dist/tasks/delete-preview.js.map +1 -1
  112. package/dist/tasks/deletion-strategy.d.ts +21 -3
  113. package/dist/tasks/deletion-strategy.d.ts.map +1 -1
  114. package/dist/tasks/deletion-strategy.js +61 -15
  115. package/dist/tasks/deletion-strategy.js.map +1 -1
  116. package/dist/tasks/engine-wrap.d.ts +8 -0
  117. package/dist/tasks/engine-wrap.d.ts.map +1 -1
  118. package/dist/tasks/engine-wrap.js +22 -9
  119. package/dist/tasks/engine-wrap.js.map +1 -1
  120. package/dist/tasks/update.d.ts.map +1 -1
  121. package/dist/tasks/update.js +12 -0
  122. package/dist/tasks/update.js.map +1 -1
  123. package/package.json +12 -12
  124. package/dist/llm/transports/openai.d.ts +0 -181
  125. package/dist/llm/transports/openai.d.ts.map +0 -1
  126. package/dist/llm/transports/openai.js +0 -645
  127. 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
  }
@@ -31695,162 +31777,6 @@ var init_open_cleo_db = __esm({
31695
31777
  }
31696
31778
  });
31697
31779
 
31698
- // packages/core/src/store/exodus/column-transforms.ts
31699
- function typeDefaultLiteral(colType) {
31700
- const upper = colType.toUpperCase();
31701
- if (upper.includes("INT")) return "0";
31702
- if (upper.includes("REAL") || upper.includes("FLOAT") || upper.includes("DOUBLE")) return "0.0";
31703
- if (upper.includes("BLOB")) return "x''";
31704
- return "''";
31705
- }
31706
- function enumNormExpr(targetTableName, col, srcRef) {
31707
- const key = `${targetTableName}.${col}`;
31708
- const fn = ENUM_NORMALIZATIONS.get(key);
31709
- return fn ? fn(srcRef) : null;
31710
- }
31711
- function numericClampExpr(targetTableName, col, srcRef) {
31712
- const key = `${targetTableName}.${col}`;
31713
- const fn = NUMERIC_CLAMPS.get(key);
31714
- return fn ? fn(srcRef) : null;
31715
- }
31716
- function buildEpochToIsoExpr(srcRef) {
31717
- 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`;
31718
- }
31719
- function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
31720
- const escapedTable = tableName.replace(/'/g, "''");
31721
- const row = db.prepare(
31722
- `SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`
31723
- ).get();
31724
- if (!row?.sql) return /* @__PURE__ */ new Set();
31725
- const isoColumns = /* @__PURE__ */ new Set();
31726
- ISO_CHECK_REGEX.lastIndex = 0;
31727
- for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) {
31728
- isoColumns.add(match[1]);
31729
- }
31730
- return isoColumns;
31731
- }
31732
- function isIntegerSourceType(srcType) {
31733
- const upper = srcType.toUpperCase();
31734
- return upper.includes("INT") || upper === "" || upper === "NUMERIC";
31735
- }
31736
- function buildDigestExpr(targetTableName, col, srcType, isoGlobCols) {
31737
- const srcRef = `"${col}"`;
31738
- if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) {
31739
- return buildEpochToIsoExpr(srcRef);
31740
- }
31741
- const clampExpr = numericClampExpr(targetTableName, col, srcRef);
31742
- if (clampExpr !== null) return clampExpr;
31743
- const normExpr = enumNormExpr(targetTableName, col, srcRef);
31744
- if (normExpr !== null) return normExpr;
31745
- return srcRef;
31746
- }
31747
- var ENUM_NORMALIZATIONS, NUMERIC_CLAMPS, ISO_CHECK_REGEX, EPOCH_SECONDS_THRESHOLD;
31748
- var init_column_transforms = __esm({
31749
- "packages/core/src/store/exodus/column-transforms.ts"() {
31750
- "use strict";
31751
- ENUM_NORMALIZATIONS = /* @__PURE__ */ new Map([
31752
- // --- task_commits.link_source -------------------------------------------
31753
- // 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
31754
- [
31755
- "tasks_task_commits.link_source",
31756
- (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`
31757
- ],
31758
- // --- architecture_decisions.status (case + date-suffix normalization) ----
31759
- // 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
31760
- // 'Proposed', 'PROPOSED' → 'proposed'
31761
- // 'Superseded', 'SUPERSEDED' → 'superseded'
31762
- [
31763
- "tasks_architecture_decisions.status",
31764
- (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`
31765
- ],
31766
- // --- brain_* enum normalizations REMOVED (T11647) -----------------------
31767
- // The brain memory family now lands in the consolidated cleo.db in its LEGACY
31768
- // RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
31769
- // constraints (the `text({ enum })` unions are enforced only at the
31770
- // application layer, exactly as the runtime `drizzle-brain` tables are). With
31771
- // no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
31772
- // value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
31773
- // 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
31774
- // nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
31775
- // The previous brain entries (brain_observations.{source_type,type},
31776
- // brain_decisions.{confirmation_state,decision_category,confidence,outcome,
31777
- // decided_by}) are therefore deleted. The non-brain entries below still apply
31778
- // because those consolidated tables retain their CHECK constraints.
31779
- // --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
31780
- // NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
31781
- // requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
31782
- // to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
31783
- // 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
31784
- // coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
31785
- // of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
31786
- // (brain_decisions.{decision_category,confidence} normalizations removed —
31787
- // T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
31788
- // --- tasks_commits.conventional_type (T11548 + T11578) -------------------
31789
- // The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
31790
- // perf/revert/breaking. Real git history carries non-conventional subjects:
31791
- // - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
31792
- // - 'merge'/'release' → 'chore' (T11578): merge + release commits are
31793
- // maintenance-class; the precise semantic is preserved by the dedicated
31794
- // `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
31795
- // `conventional_type` to the maintenance catch-all 'chore' is lossless at
31796
- // the row grain. Without this the 'merge'/'release' rows violate the CHECK,
31797
- // `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
31798
- // data-continuity gate aborts the cutover (T11578 CI regression).
31799
- // - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
31800
- // subjects must never re-break the zero-deficit gate; the boolean flags and
31801
- // raw subject text remain the precise provenance).
31802
- [
31803
- "tasks_commits.conventional_type",
31804
- (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`
31805
- ],
31806
- // --- tasks_task_relations.relation_type (T11548) -------------------------
31807
- // 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
31808
- // supersedes/groups). 4 rows.
31809
- [
31810
- "tasks_task_relations.relation_type",
31811
- (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`
31812
- ],
31813
- // --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
31814
- // Legacy camelCase / past-tense values → canonical snake_case stage names.
31815
- // 'implemented' → 'implementation', 'qaPassed' → 'validation',
31816
- // 'testsPassed' → 'testing'. 3 rows.
31817
- [
31818
- "tasks_lifecycle_stages.stage_name",
31819
- (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`
31820
- ],
31821
- // --- tasks_architecture_decisions.gate_status (T11548) ------------------
31822
- // 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
31823
- // (enum: pending/passed/failed/waived). 2 rows.
31824
- [
31825
- "tasks_architecture_decisions.gate_status",
31826
- (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`
31827
- ],
31828
- // --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
31829
- // Values with a 'validator:...' prefix → 'direct'
31830
- // (enum: direct/satisfies/coverage). 3 rows.
31831
- // Strip the namespace prefix introduced before the enum was tightened.
31832
- [
31833
- "tasks_evidence_ac_bindings.binding_type",
31834
- (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`
31835
- ]
31836
- // (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
31837
- // brain target = runtime shape with no CHECK; legacy values like 'accepted',
31838
- // 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
31839
- ]);
31840
- NUMERIC_CLAMPS = /* @__PURE__ */ new Map([
31841
- // --- brain_weight_history.delta_weight (T11782) -------------------------
31842
- // +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
31843
- // depression), NaN → 0.0 (no-op delta). Finite values pass through.
31844
- [
31845
- "brain_weight_history.delta_weight",
31846
- (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`
31847
- ]
31848
- ]);
31849
- ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
31850
- EPOCH_SECONDS_THRESHOLD = 1e11;
31851
- }
31852
- });
31853
-
31854
31780
  // packages/core/src/store/exodus/table-name-map.ts
31855
31781
  function resolveTableTargetScope(sourceName, legacyTable, sourceScope) {
31856
31782
  if (inferSourceKind(sourceName) === "nexus" && NEXUS_GRAPH_PROJECT_TABLES.has(legacyTable)) {
@@ -32179,86 +32105,556 @@ var init_table_name_map = __esm({
32179
32105
  }
32180
32106
  });
32181
32107
 
32182
- // packages/core/src/store/exodus/types.ts
32183
- var EXODUS_TARGET_SCHEMA_VERSION;
32184
- var init_types = __esm({
32185
- "packages/core/src/store/exodus/types.ts"() {
32186
- "use strict";
32187
- EXODUS_TARGET_SCHEMA_VERSION = "drizzle-v1.0.0-rc.3/dual-scope/2026-05";
32188
- }
32189
- });
32190
-
32191
- // packages/core/src/store/exodus/migrate.ts
32192
- import {
32193
- copyFileSync as copyFileSync3,
32194
- existsSync as existsSync7,
32195
- mkdirSync as mkdirSync2,
32196
- readFileSync as readFileSync3,
32197
- renameSync as renameSync2,
32198
- unlinkSync as unlinkSync2,
32199
- writeFileSync as writeFileSync3
32200
- } from "node:fs";
32201
- import { join as join9 } from "node:path";
32202
- function getSqliteVersion(db) {
32203
- try {
32204
- const row = db.prepare("SELECT sqlite_version() AS v").get();
32205
- return row?.v ?? "unknown";
32206
- } catch {
32207
- return "unknown";
32208
- }
32209
- }
32108
+ // packages/core/src/store/exodus/count-parity.ts
32109
+ import { existsSync as existsSync7 } from "node:fs";
32210
32110
  function listTables(db) {
32211
- const rows = db.prepare(
32111
+ return db.prepare(
32212
32112
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
32213
- ).all();
32214
- return rows.map((r) => r.name);
32113
+ ).all().map((r) => r.name);
32215
32114
  }
32216
- function writeJournal(stagingDir, journal) {
32217
- const journalPath = join9(stagingDir, JOURNAL_FILENAME);
32218
- const tmpPath = `${journalPath}.tmp`;
32219
- writeFileSync3(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
32220
- 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;
32221
32118
  }
32222
- function readJournal(stagingDir) {
32223
- const journalPath = join9(stagingDir, JOURNAL_FILENAME);
32224
- if (!existsSync7(journalPath)) return null;
32119
+ function rowCount(db, tableName) {
32225
32120
  try {
32226
- return JSON.parse(readFileSync3(journalPath, "utf8"));
32121
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
32122
+ return Number(row?.c ?? 0);
32227
32123
  } catch {
32228
32124
  return null;
32229
32125
  }
32230
32126
  }
32231
- function clearExodusJournal(stagingDir) {
32232
- 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 });
32233
32135
  try {
32234
- if (!existsSync7(journalPath)) return false;
32235
- unlinkSync2(journalPath);
32236
- log2.info(
32237
- { stagingDir },
32238
- "exodus: cleared migrate journal after abort/rollback \u2014 a retry will RE-COPY all tables"
32239
- );
32240
- return true;
32241
- } 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) {
32242
32176
  log2.warn(
32243
- { err, stagingDir },
32244
- '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`
32245
32179
  );
32246
- return false;
32247
32180
  }
32181
+ return { ok: deficits.length === 0, entries, deficits, checked: entries.length, skipped };
32182
+ }
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;
32199
+ }
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
+ ];
32237
+ }
32238
+ function safeFileBytes(filePath) {
32239
+ try {
32240
+ return statSync2(filePath).size;
32241
+ } catch {
32242
+ return 0;
32243
+ }
32244
+ }
32245
+ function getAvailableBytes(dir) {
32246
+ try {
32247
+ const result = statfsSync(dir);
32248
+ return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
32249
+ } catch {
32250
+ return 0;
32251
+ }
32252
+ }
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
+ };
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
32416
+ function typeDefaultLiteral(colType) {
32417
+ const upper = colType.toUpperCase();
32418
+ if (upper.includes("INT")) return "0";
32419
+ if (upper.includes("REAL") || upper.includes("FLOAT") || upper.includes("DOUBLE")) return "0.0";
32420
+ if (upper.includes("BLOB")) return "x''";
32421
+ return "''";
32422
+ }
32423
+ function enumNormExpr(targetTableName, col, srcRef) {
32424
+ const key = `${targetTableName}.${col}`;
32425
+ const fn = ENUM_NORMALIZATIONS.get(key);
32426
+ return fn ? fn(srcRef) : null;
32427
+ }
32428
+ function numericClampExpr(targetTableName, col, srcRef) {
32429
+ const key = `${targetTableName}.${col}`;
32430
+ const fn = NUMERIC_CLAMPS.get(key);
32431
+ return fn ? fn(srcRef) : null;
32432
+ }
32433
+ function buildEpochToIsoExpr(srcRef) {
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`;
32435
+ }
32436
+ function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
32437
+ const escapedTable = tableName.replace(/'/g, "''");
32438
+ const row = db.prepare(
32439
+ `SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`
32440
+ ).get();
32441
+ if (!row?.sql) return /* @__PURE__ */ new Set();
32442
+ const isoColumns = /* @__PURE__ */ new Set();
32443
+ ISO_CHECK_REGEX.lastIndex = 0;
32444
+ for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) {
32445
+ isoColumns.add(match[1]);
32446
+ }
32447
+ return isoColumns;
32448
+ }
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
+ };
32248
32657
  }
32249
- function initJournal(sqliteVersion) {
32250
- const now = (/* @__PURE__ */ new Date()).toISOString();
32251
- return {
32252
- version: 1,
32253
- cleoVersion: getCleoVersion(),
32254
- targetSchemaVersion: EXODUS_TARGET_SCHEMA_VERSION,
32255
- nodeVersion: process.version,
32256
- sqliteVersion,
32257
- startedAt: now,
32258
- updatedAt: now,
32259
- tables: []
32260
- };
32261
- }
32262
32658
  function lockPath(dbPath) {
32263
32659
  return `${dbPath}${LOCK_SENTINEL_SUFFIX}`;
32264
32660
  }
@@ -32272,6 +32668,13 @@ function releaseAdvisoryLock(dbPath) {
32272
32668
  } catch {
32273
32669
  }
32274
32670
  }
32671
+ function safeStatBytes(filePath) {
32672
+ try {
32673
+ return statSync4(filePath).size;
32674
+ } catch {
32675
+ return 0;
32676
+ }
32677
+ }
32275
32678
  function makeAttachAlias(name2, index2) {
32276
32679
  const safe = name2.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").slice(0, 20);
32277
32680
  return `_src_${safe}_${index2}`;
@@ -32320,7 +32723,7 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
32320
32723
  function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyTableName, sourceName, targetSchema = "main") {
32321
32724
  const resolution = resolveConsolidatedTableName(sourceName, legacyTableName);
32322
32725
  if (resolution.kind === "skip") {
32323
- log2.warn(
32726
+ log3.warn(
32324
32727
  { legacyTableName, sourceName, reason: resolution.reason },
32325
32728
  `Exodus: explicitly skipping table \u2014 ${resolution.reason}`
32326
32729
  );
@@ -32339,7 +32742,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32339
32742
  ).get();
32340
32743
  if (!existsRow) {
32341
32744
  const reason = `consolidated target '${targetTableName}' not found (mapped from legacy '${legacyTableName}')`;
32342
- log2.warn(
32745
+ log3.warn(
32343
32746
  { legacyTableName, targetTableName, sourceName, attachAlias, targetSchema },
32344
32747
  `Exodus: ${reason}`
32345
32748
  );
@@ -32350,13 +32753,13 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32350
32753
  const sharedColumns = srcPragma.map((r) => r.name).filter((col) => tgtColMap.has(col));
32351
32754
  if (sharedColumns.length === 0) {
32352
32755
  const reason = `no overlapping columns between source '${legacyTableName}' and target '${targetTableName}'`;
32353
- log2.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32756
+ log3.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32354
32757
  return { rowsCopied: 0, skipped: true, reason };
32355
32758
  }
32356
32759
  const srcOnlyColumns = srcPragma.map((r) => r.name).filter((c) => !tgtColMap.has(c));
32357
32760
  const tgtOnlyColumns = tgtPragma.map((r) => r.name).filter((c) => !srcColumns.has(c));
32358
32761
  if (srcOnlyColumns.length > 0 || tgtOnlyColumns.length > 0) {
32359
- log2.info(
32762
+ log3.info(
32360
32763
  { legacyTableName, targetTableName, sourceName, srcOnlyColumns, tgtOnlyColumns },
32361
32764
  "Exodus: column drift detected \u2014 copying intersection, dropping src-only cols, using defaults for tgt-only cols"
32362
32765
  );
@@ -32371,7 +32774,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32371
32774
  return isoGlobCols.has(col) && (upper.includes("INT") || upper === "" || upper === "NUMERIC");
32372
32775
  });
32373
32776
  if (coercedCols.length > 0) {
32374
- log2.info(
32777
+ log3.info(
32375
32778
  {
32376
32779
  legacyTableName,
32377
32780
  targetTableName,
@@ -32387,7 +32790,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32387
32790
  (col) => ENUM_NORMALIZATIONS.has(`${targetTableName}.${col}`)
32388
32791
  );
32389
32792
  if (normalizedCols.length > 0) {
32390
- log2.info(
32793
+ log3.info(
32391
32794
  { legacyTableName, targetTableName, sourceName, normalizedCols },
32392
32795
  `Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`
32393
32796
  );
@@ -32396,7 +32799,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32396
32799
  (col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`)
32397
32800
  );
32398
32801
  if (clampedCols.length > 0) {
32399
- log2.info(
32802
+ log3.info(
32400
32803
  { legacyTableName, targetTableName, sourceName, clampedCols },
32401
32804
  `Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`
32402
32805
  );
@@ -32428,25 +32831,37 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32428
32831
  ];
32429
32832
  const colList = allInsertCols.map((c) => `"${c}"`).join(", ");
32430
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);
32431
32836
  const stmt = targetNativeDb.prepare(
32432
32837
  `INSERT OR IGNORE INTO "${targetSchema}"."${targetTableName}" (${colList}) SELECT ${selectList} FROM "${attachAlias}"."${legacyTableName}"`
32433
32838
  );
32434
32839
  const result = stmt.run();
32435
32840
  const rowsCopied = result.changes ?? 0;
32436
32841
  if (rowsCopied < sourceCount) {
32437
- const dropped = sourceCount - rowsCopied;
32438
- if (rowsCopied === 0) {
32439
- 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.`;
32440
- log2.error(
32441
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied },
32442
- `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`
32443
32847
  );
32444
- return { rowsCopied: 0, skipped: false, reason };
32848
+ return { rowsCopied, skipped: false };
32445
32849
  }
32446
- log2.warn(
32447
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, dropped },
32448
- `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}`
32449
32863
  );
32864
+ return { rowsCopied, skipped: false, reason };
32450
32865
  }
32451
32866
  return { rowsCopied, skipped: false };
32452
32867
  }
@@ -32454,10 +32869,10 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32454
32869
  if (journal.targetSchemaVersion !== EXODUS_TARGET_SCHEMA_VERSION) {
32455
32870
  const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
32456
32871
  if (forceCrossVersion) {
32457
- log2.warn(msg + " (--force-cross-version: continuing anyway)");
32872
+ log3.warn(msg + " (--force-cross-version: continuing anyway)");
32458
32873
  return true;
32459
32874
  }
32460
- log2.error(msg + " \u2014 pass --force-cross-version to override");
32875
+ log3.error(msg + " \u2014 pass --force-cross-version to override");
32461
32876
  return false;
32462
32877
  }
32463
32878
  return true;
@@ -32465,18 +32880,19 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32465
32880
  async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32466
32881
  const { sources, stagingDir, diskPreflight, projectDbPath, globalDbPath } = plan;
32467
32882
  if (!diskPreflight) {
32883
+ const shortfall = Math.max(0, plan.requiredBytes - plan.availableBytes);
32468
32884
  return {
32469
32885
  ok: false,
32470
32886
  tables: [],
32471
32887
  stagingDir,
32472
32888
  backupPaths: [],
32473
- 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.`
32474
32890
  };
32475
32891
  }
32476
32892
  mkdirSync2(stagingDir, { recursive: true });
32477
32893
  let sqliteVersion = "unknown";
32478
32894
  for (const src of sources) {
32479
- if (existsSync7(src.path)) {
32895
+ if (existsSync10(src.path)) {
32480
32896
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32481
32897
  sqliteVersion = getSqliteVersion(snap.db);
32482
32898
  snap.close();
@@ -32517,9 +32933,15 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32517
32933
  };
32518
32934
  var extractNativeDb = extractNativeDb2;
32519
32935
  for (const src of sources) {
32520
- if (!existsSync7(src.path)) continue;
32521
- const backupDest = join9(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
32522
- 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)) {
32523
32945
  onProgress?.(`Backing up ${src.name} \u2192 staging dir\u2026`);
32524
32946
  copyFileSync3(src.path, backupDest);
32525
32947
  backupPaths.push(backupDest);
@@ -32537,8 +32959,8 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32537
32959
  });
32538
32960
  const projectNative = extractNativeDb2(projectHandle);
32539
32961
  const globalNative = extractNativeDb2(globalHandle);
32540
- const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync7(s.path));
32541
- 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));
32542
32964
  await migrateScope(
32543
32965
  "project",
32544
32966
  projectSources,
@@ -32563,7 +32985,7 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32563
32985
  return { ok: true, tables: allTableResults, stagingDir, backupPaths };
32564
32986
  } catch (err) {
32565
32987
  const error = err instanceof Error ? err.message : String(err);
32566
- log2.error({ err }, "Exodus migration failed");
32988
+ log3.error({ err }, "Exodus migration failed");
32567
32989
  return { ok: false, tables: allTableResults, stagingDir, backupPaths, error };
32568
32990
  } finally {
32569
32991
  try {
@@ -32583,7 +33005,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32583
33005
  if (sources.length === 0) return;
32584
33006
  onProgress?.(`Migrating ${scope}-scope sources\u2026`);
32585
33007
  targetNativeDb.exec("PRAGMA foreign_keys = OFF");
32586
- 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)");
32587
33009
  try {
32588
33010
  for (let i = 0; i < sources.length; i++) {
32589
33011
  const src = sources[i];
@@ -32600,7 +33022,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32600
33022
  }
32601
33023
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32602
33024
  try {
32603
- const tables = listTables(snap.db);
33025
+ const tables = listTables2(snap.db);
32604
33026
  targetNativeDb.exec("BEGIN");
32605
33027
  let txOpen = true;
32606
33028
  try {
@@ -32650,7 +33072,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32650
33072
  }
32651
33073
  } catch (err) {
32652
33074
  const msg = err instanceof Error ? err.message : String(err);
32653
- 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");
32654
33076
  status = "skipped";
32655
33077
  errorMsg = msg;
32656
33078
  skipped = true;
@@ -32698,7 +33120,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32698
33120
  targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
32699
33121
  onProgress?.(` [${src.name}] Detached "${attachAlias}"`);
32700
33122
  } catch (detachErr) {
32701
- log2.warn(
33123
+ log3.warn(
32702
33124
  { attachAlias, sourceDb: src.name, err: detachErr },
32703
33125
  "DETACH failed \u2014 alias will be released on DB close"
32704
33126
  );
@@ -32708,7 +33130,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32708
33130
  targetNativeDb.exec(`DETACH DATABASE "${crossAlias}"`);
32709
33131
  onProgress?.(` [${src.name}] Cross-scope target detached "${crossAlias}"`);
32710
33132
  } catch (detachErr) {
32711
- log2.warn(
33133
+ log3.warn(
32712
33134
  { crossAlias, sourceDb: src.name, err: detachErr },
32713
33135
  "Cross-scope DETACH failed \u2014 alias will be released on DB close"
32714
33136
  );
@@ -32720,28 +33142,28 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32720
33142
  try {
32721
33143
  const orphans = targetNativeDb.prepare("PRAGMA foreign_key_check").all();
32722
33144
  if (orphans.length > 0) {
32723
- log2.warn(
33145
+ log3.warn(
32724
33146
  { scope, orphanCount: orphans.length, sample: orphans.slice(0, 5) },
32725
33147
  `Exodus: PRAGMA foreign_key_check found ${orphans.length} orphan row(s) after bulk copy \u2014 these are genuine data orphans (not ordering artifacts)`
32726
33148
  );
32727
33149
  } else {
32728
- 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");
32729
33151
  }
32730
33152
  } catch (checkErr) {
32731
- log2.warn(
33153
+ log3.warn(
32732
33154
  { scope, err: checkErr },
32733
33155
  "Exodus: PRAGMA foreign_key_check failed (non-fatal) \u2014 target schema may not have FK constraints enabled"
32734
33156
  );
32735
33157
  }
32736
33158
  try {
32737
33159
  targetNativeDb.exec("PRAGMA foreign_keys = ON");
32738
- log2.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
33160
+ log3.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
32739
33161
  } catch (fkErr) {
32740
- 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)");
32741
33163
  }
32742
33164
  }
32743
33165
  }
32744
- var log2, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
33166
+ var log3, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
32745
33167
  var init_migrate = __esm({
32746
33168
  "packages/core/src/store/exodus/migrate.ts"() {
32747
33169
  "use strict";
@@ -32750,9 +33172,10 @@ var init_migrate = __esm({
32750
33172
  init_dual_scope_db();
32751
33173
  init_open_cleo_db();
32752
33174
  init_column_transforms();
33175
+ init_plan2();
32753
33176
  init_table_name_map();
32754
33177
  init_types();
32755
- log2 = getLogger("exodus-migrate");
33178
+ log3 = getLogger("exodus-migrate");
32756
33179
  LOCK_SENTINEL_SUFFIX = ".exodus-lock";
32757
33180
  JOURNAL_FILENAME = "exodus-journal.json";
32758
33181
  SOURCE_EPOCH_UNITS = /* @__PURE__ */ new Map([
@@ -32768,116 +33191,55 @@ var init_migrate = __esm({
32768
33191
  }
32769
33192
  });
32770
33193
 
32771
- // packages/core/src/store/exodus/plan.ts
32772
- import { existsSync as existsSync8, readdirSync as readdirSync2, statfsSync, statSync as statSync2 } from "node:fs";
32773
- import { join as join10 } from "node:path";
32774
- function buildSourceDescriptors(cwd) {
32775
- const cleoDir = resolveCleoDir(cwd);
32776
- const cleoHome = getCleoHome();
32777
- return [
32778
- // Project-tier — go into consolidated project-scope cleo.db
32779
- {
32780
- name: "tasks",
32781
- path: join10(cleoDir, "tasks.db"),
32782
- targetScope: "project"
32783
- },
32784
- {
32785
- name: "brain (project)",
32786
- path: join10(cleoDir, "brain.db"),
32787
- targetScope: "project"
32788
- },
32789
- {
32790
- name: "conduit",
32791
- path: join10(cleoDir, "conduit.db"),
32792
- targetScope: "project"
32793
- },
32794
- // Global-tier — go into consolidated global-scope cleo.db
32795
- {
32796
- name: "nexus",
32797
- path: join10(cleoHome, "nexus.db"),
32798
- targetScope: "global"
32799
- },
32800
- {
32801
- name: "signaldock",
32802
- path: join10(cleoHome, "signaldock.db"),
32803
- targetScope: "global"
32804
- },
32805
- {
32806
- name: "skills",
32807
- path: join10(cleoHome, "skills.db"),
32808
- targetScope: "global"
32809
- }
32810
- ];
32811
- }
32812
- function safeFileBytes(filePath) {
32813
- try {
32814
- return statSync2(filePath).size;
32815
- } catch {
32816
- return 0;
32817
- }
33194
+ // packages/core/src/store/exodus/seal.ts
33195
+ function resolveScopes(arg) {
33196
+ return arg === "both" ? ["project", "global"] : [arg];
32818
33197
  }
32819
- function getAvailableBytes(dir) {
32820
- try {
32821
- const result = statfsSync(dir);
32822
- return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
32823
- } catch {
32824
- 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: [] };
32825
33204
  }
32826
- }
32827
- function deriveStagingDirName() {
32828
- const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z");
32829
- return `exodus-staging-${iso}`;
32830
- }
32831
- function findExistingStaging(cleoDir) {
32832
- try {
32833
- const entries = readdirSync2(cleoDir, { withFileTypes: true });
32834
- const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
32835
- if (stagingDirs.length > 0) {
32836
- return join10(cleoDir, stagingDirs[0]);
32837
- }
32838
- } 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 });
32839
33223
  }
32840
- return null;
32841
- }
32842
- function buildExodusPlan(cwd) {
32843
- const cleoDir = resolveCleoDir(cwd);
32844
- const sources = buildSourceDescriptors(cwd);
32845
- const totalSourceBytes = sources.reduce((sum, s) => sum + safeFileBytes(s.path), 0);
32846
- const availableBytes = getAvailableBytes(cleoDir);
32847
- const diskPreflight = totalSourceBytes === 0 || availableBytes >= 3 * totalSourceBytes;
32848
- const existingStaging = findExistingStaging(cleoDir);
32849
- const stagingDir = existingStaging ?? join10(cleoDir, deriveStagingDirName());
32850
- const resumeFromStaging = existingStaging !== null;
32851
- const projectDbPath = resolveDualScopeDbPath("project", cwd);
32852
- const globalDbPath = resolveDualScopeDbPath("global");
32853
- return {
32854
- sources,
32855
- totalSourceBytes,
32856
- availableBytes,
32857
- diskPreflight,
32858
- stagingDir,
32859
- resumeFromStaging,
32860
- projectDbPath,
32861
- globalDbPath
32862
- };
33224
+ return { ok: true, parity, scopes: outcomes };
32863
33225
  }
32864
- function sourcesPresent(sources) {
32865
- return sources.some((s) => existsSync8(s.path));
32866
- }
32867
- var init_plan2 = __esm({
32868
- "packages/core/src/store/exodus/plan.ts"() {
33226
+ var log4;
33227
+ var init_seal = __esm({
33228
+ "packages/core/src/store/exodus/seal.ts"() {
32869
33229
  "use strict";
32870
- init_paths();
32871
- init_dual_scope_db();
33230
+ init_logger2();
33231
+ init_archive2();
33232
+ init_count_parity();
33233
+ log4 = getLogger("exodus-seal");
32872
33234
  }
32873
33235
  });
32874
33236
 
32875
33237
  // packages/core/src/store/exodus/status.ts
32876
- 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";
32877
33239
  import { join as join11 } from "node:path";
32878
33240
  function readJournal2(stagingDir) {
32879
33241
  const p = join11(stagingDir, JOURNAL_FILENAME2);
32880
- if (!existsSync9(p)) return null;
33242
+ if (!existsSync11(p)) return null;
32881
33243
  try {
32882
33244
  return JSON.parse(readFileSync4(p, "utf8"));
32883
33245
  } catch {
@@ -32893,7 +33255,7 @@ function findStagingDirs(cleoDir) {
32893
33255
  }
32894
33256
  function safeBytes(p) {
32895
33257
  try {
32896
- return statSync3(p).size;
33258
+ return statSync5(p).size;
32897
33259
  } catch {
32898
33260
  return 0;
32899
33261
  }
@@ -32909,15 +33271,15 @@ function runExodusStatus(cwd) {
32909
33271
  const sourcesInfo = plan.sources.map((s) => ({
32910
33272
  name: s.name,
32911
33273
  path: s.path,
32912
- exists: existsSync9(s.path),
33274
+ exists: existsSync11(s.path),
32913
33275
  bytes: safeBytes(s.path)
32914
33276
  }));
32915
33277
  return {
32916
33278
  hasStaging: latestStaging !== null,
32917
33279
  stagingDir: latestStaging,
32918
33280
  journal,
32919
- projectDbExists: existsSync9(projectDbPath),
32920
- globalDbExists: existsSync9(globalDbPath),
33281
+ projectDbExists: existsSync11(projectDbPath),
33282
+ globalDbExists: existsSync11(globalDbPath),
32921
33283
  sourcesPresent: sourcesInfo.some((s) => s.exists),
32922
33284
  sources: sourcesInfo
32923
33285
  };
@@ -32934,7 +33296,7 @@ var init_status = __esm({
32934
33296
  });
32935
33297
 
32936
33298
  // packages/core/src/store/exodus/verify-migration.ts
32937
- import { existsSync as existsSync10 } from "node:fs";
33299
+ import { existsSync as existsSync12 } from "node:fs";
32938
33300
  import { createRequire as createRequire3 } from "node:module";
32939
33301
  function orderByClause(db, tableName) {
32940
33302
  try {
@@ -32948,6 +33310,18 @@ function orderByClause(db, tableName) {
32948
33310
  return "rowid";
32949
33311
  }
32950
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
+ }
32951
33325
  const { createHash: createHash3 } = _require("node:crypto");
32952
33326
  const hasher = createHash3("sha256");
32953
33327
  const orderBy = orderByClause(db, tableName);
@@ -32956,29 +33330,35 @@ function computeTableDigest(db, tableName, columns, transform) {
32956
33330
  selectClause = columns.map((c) => {
32957
33331
  if (transform === void 0) return `"${c}"`;
32958
33332
  const srcType = transform.srcTypeByCol.get(c) ?? "";
32959
- const expr = buildDigestExpr(transform.targetTableName, c, srcType, transform.isoGlobCols);
33333
+ const tgtCol = transform.tgtColByCol.get(c);
33334
+ const expr = buildDigestExpr(
33335
+ transform.targetTableName,
33336
+ c,
33337
+ srcType,
33338
+ transform.isoGlobCols,
33339
+ tgtCol
33340
+ );
32960
33341
  return `${expr} AS "${c}"`;
32961
33342
  }).join(", ");
32962
33343
  } else {
32963
33344
  selectClause = "*";
32964
33345
  }
32965
- let rows;
32966
33346
  try {
32967
- 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
+ }
32968
33352
  } catch (err) {
32969
33353
  const msg = err instanceof Error ? err.message : String(err);
32970
- log3.warn(
33354
+ log5.warn(
32971
33355
  { tableName, err: msg },
32972
- "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)"
32973
33357
  );
32974
- return { count: 0, hash: "" };
32975
- }
32976
- for (const row of rows) {
32977
- const rowObj = row;
32978
- hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
33358
+ return { count, hash: "" };
32979
33359
  }
32980
33360
  return {
32981
- count: rows.length,
33361
+ count,
32982
33362
  hash: hasher.digest("hex").slice(0, 32)
32983
33363
  };
32984
33364
  }
@@ -33002,18 +33382,24 @@ function buildSourceDigestTransform(srcDb, srcTable, tgtDb, targetTableName) {
33002
33382
  srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => [r.name, r.type])
33003
33383
  );
33004
33384
  const isoGlobCols = detectIsoGlobColumns(tgtDb, targetTableName);
33005
- return { targetTableName, srcTypeByCol, isoGlobCols };
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 };
33006
33392
  } catch {
33007
33393
  return void 0;
33008
33394
  }
33009
33395
  }
33010
- function listTables2(db) {
33396
+ function listTables3(db) {
33011
33397
  const rows = db.prepare(
33012
33398
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
33013
33399
  ).all();
33014
33400
  return rows.map((r) => r.name);
33015
33401
  }
33016
- function tableExists2(db, tableName) {
33402
+ function tableExists3(db, tableName) {
33017
33403
  try {
33018
33404
  const escaped = tableName.replace(/'/g, "''");
33019
33405
  return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
@@ -33091,7 +33477,7 @@ function foreignKeyCheck(db, scope) {
33091
33477
  try {
33092
33478
  const rows = db.prepare("PRAGMA foreign_key_check").all();
33093
33479
  if (rows.length > 0) {
33094
- log3.warn(
33480
+ log5.warn(
33095
33481
  { scope, count: rows.length, sample: rows.slice(0, 5) },
33096
33482
  `verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`
33097
33483
  );
@@ -33103,7 +33489,7 @@ function foreignKeyCheck(db, scope) {
33103
33489
  fkid: r.fkid
33104
33490
  }));
33105
33491
  } catch (err) {
33106
- 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)");
33107
33493
  return [];
33108
33494
  }
33109
33495
  }
@@ -33140,13 +33526,13 @@ function sourceOrphanSignatures(db, sourceName, scope) {
33140
33526
  const rows = db.prepare("PRAGMA foreign_key_check").all();
33141
33527
  for (const r of rows) sigs.add(orphanSignature(db, r, sourceName));
33142
33528
  if (rows.length > 0) {
33143
- log3.warn(
33529
+ log5.warn(
33144
33530
  { scope, count: rows.length, sample: rows.slice(0, 5) },
33145
33531
  `verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) \u2014 these are tolerated (carried forward losslessly, flagged for data-hygiene)`
33146
33532
  );
33147
33533
  }
33148
33534
  } catch (err) {
33149
- 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)");
33150
33536
  }
33151
33537
  return sigs;
33152
33538
  }
@@ -33158,7 +33544,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33158
33544
  const preExistingForeignKeyViolations = [];
33159
33545
  const failureLines = [];
33160
33546
  const sourceOrphanSigs = /* @__PURE__ */ new Set();
33161
- if (!existsSync10(projectDbPath)) {
33547
+ if (!existsSync12(projectDbPath)) {
33162
33548
  return {
33163
33549
  ok: false,
33164
33550
  tables: [],
@@ -33169,7 +33555,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33169
33555
  error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`
33170
33556
  };
33171
33557
  }
33172
- if (!existsSync10(globalDbPath)) {
33558
+ if (!existsSync12(globalDbPath)) {
33173
33559
  return {
33174
33560
  ok: false,
33175
33561
  tables: [],
@@ -33184,7 +33570,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33184
33570
  const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
33185
33571
  try {
33186
33572
  for (const src of sources) {
33187
- if (!existsSync10(src.path)) {
33573
+ if (!existsSync12(src.path)) {
33188
33574
  onProgress?.(`Skipping ${src.name} (not present)`);
33189
33575
  continue;
33190
33576
  }
@@ -33193,9 +33579,9 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33193
33579
  for (const sig of sourceOrphanSignatures(srcSnap.db, src.name, `source:${src.name}`)) {
33194
33580
  sourceOrphanSigs.add(sig);
33195
33581
  }
33196
- const sourceTables = listTables2(srcSnap.db);
33197
- const projectTables = new Set(listTables2(projectSnap.db));
33198
- 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));
33199
33585
  for (const legacyTableName of sourceTables) {
33200
33586
  onProgress?.(`Verifying ${src.name}.${legacyTableName}\u2026`);
33201
33587
  const resolution = resolveConsolidatedTableName(src.name, legacyTableName);
@@ -33269,7 +33655,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33269
33655
  `[${scope}] ${src.name}.${legacyTableName} \u2192 ${targetTableName}: DEFICIT \u2014 source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`
33270
33656
  );
33271
33657
  } else if (tgtDigest.count > srcDigest.count) {
33272
- log3.warn(
33658
+ log5.warn(
33273
33659
  {
33274
33660
  scope,
33275
33661
  source: src.name,
@@ -33306,7 +33692,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33306
33692
  targetOrphans.push(...foreignKeyCheck(globalSnap.db, "global"));
33307
33693
  foreignKeyViolations.push(...targetOrphans);
33308
33694
  for (const fk of targetOrphans) {
33309
- const orphanDb = tableExists2(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33695
+ const orphanDb = tableExists3(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33310
33696
  const sig = orphanSignature(orphanDb, fk);
33311
33697
  const preExisting = sourceOrphanSigs.has(sig);
33312
33698
  if (preExisting) {
@@ -33319,7 +33705,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33319
33705
  }
33320
33706
  }
33321
33707
  if (preExistingForeignKeyViolations.length > 0) {
33322
- log3.warn(
33708
+ log5.warn(
33323
33709
  {
33324
33710
  count: preExistingForeignKeyViolations.length,
33325
33711
  sample: preExistingForeignKeyViolations.slice(0, 5)
@@ -33329,7 +33715,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33329
33715
  }
33330
33716
  } catch (err) {
33331
33717
  const error = err instanceof Error ? err.message : String(err);
33332
- log3.error({ err }, "verifyMigration failed");
33718
+ log5.error({ err }, "verifyMigration failed");
33333
33719
  return {
33334
33720
  ok: false,
33335
33721
  tables,
@@ -33346,7 +33732,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33346
33732
  if (failureLines.length > 0) {
33347
33733
  const error = `verifyMigration FAILED: ${failureLines.length} issue(s):
33348
33734
  ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33349
- log3.error({ failureCount: failureLines.length }, error);
33735
+ log5.error({ failureCount: failureLines.length }, error);
33350
33736
  return {
33351
33737
  ok: false,
33352
33738
  tables,
@@ -33366,7 +33752,7 @@ ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33366
33752
  enumDrift
33367
33753
  };
33368
33754
  }
33369
- var log3, _require, CHECK_ENUM_REGEX;
33755
+ var log5, _require, CHECK_ENUM_REGEX;
33370
33756
  var init_verify_migration = __esm({
33371
33757
  "packages/core/src/store/exodus/verify-migration.ts"() {
33372
33758
  "use strict";
@@ -33375,7 +33761,7 @@ var init_verify_migration = __esm({
33375
33761
  init_open_cleo_db();
33376
33762
  init_column_transforms();
33377
33763
  init_table_name_map();
33378
- log3 = getLogger("verify-migration");
33764
+ log5 = getLogger("verify-migration");
33379
33765
  _require = createRequire3(import.meta.url);
33380
33766
  CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
33381
33767
  }
@@ -33418,8 +33804,10 @@ __export(exodus_exports, {
33418
33804
  archiveMigratedSources: () => archiveMigratedSources,
33419
33805
  archiveSourceDb: () => archiveSourceDb,
33420
33806
  archiveStrandedResidue: () => archiveStrandedResidue,
33807
+ buildExodusHealth: () => buildExodusHealth,
33421
33808
  buildExodusPlan: () => buildExodusPlan,
33422
33809
  clearExodusJournal: () => clearExodusJournal,
33810
+ computeCountParity: () => computeCountParity,
33423
33811
  deriveStagingDirName: () => deriveStagingDirName,
33424
33812
  detectStrandedResidue: () => detectStrandedResidue,
33425
33813
  exodusArchiveDir: () => exodusArchiveDir,
@@ -33431,6 +33819,7 @@ __export(exodus_exports, {
33431
33819
  runExodusMigrate: () => runExodusMigrate,
33432
33820
  runExodusStatus: () => runExodusStatus,
33433
33821
  runExodusVerify: () => runExodusVerify,
33822
+ sealExodus: () => sealExodus,
33434
33823
  sourcesPresent: () => sourcesPresent,
33435
33824
  verifyMigration: () => verifyMigration,
33436
33825
  writeExodusCompleteMarker: () => writeExodusCompleteMarker
@@ -33439,8 +33828,11 @@ var init_exodus = __esm({
33439
33828
  "packages/core/src/store/exodus/index.ts"() {
33440
33829
  "use strict";
33441
33830
  init_archive2();
33831
+ init_count_parity();
33832
+ init_health2();
33442
33833
  init_migrate();
33443
33834
  init_plan2();
33835
+ init_seal();
33444
33836
  init_status();
33445
33837
  init_table_name_map();
33446
33838
  init_types();
@@ -33456,7 +33848,7 @@ __export(on_open_exports, {
33456
33848
  isDataContinuityOk: () => isDataContinuityOk,
33457
33849
  maybeRunExodusOnOpen: () => maybeRunExodusOnOpen
33458
33850
  });
33459
- import { existsSync as existsSync11 } from "node:fs";
33851
+ import { existsSync as existsSync13 } from "node:fs";
33460
33852
  function isDisabledByEnv() {
33461
33853
  const v = process.env.CLEO_DISABLE_EXODUS_ON_OPEN;
33462
33854
  return v === "1" || v === "true";
@@ -33482,7 +33874,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33482
33874
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%'"
33483
33875
  ).all().map((r) => r.name);
33484
33876
  } catch (err) {
33485
- 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");
33486
33878
  return;
33487
33879
  }
33488
33880
  try {
@@ -33492,7 +33884,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33492
33884
  try {
33493
33885
  nativeDb.exec(`DELETE FROM "${table}"`);
33494
33886
  } catch (err) {
33495
- 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");
33496
33888
  }
33497
33889
  }
33498
33890
  nativeDb.exec("COMMIT");
@@ -33501,7 +33893,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33501
33893
  nativeDb.exec("ROLLBACK");
33502
33894
  } catch {
33503
33895
  }
33504
- log4.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33896
+ log6.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33505
33897
  } finally {
33506
33898
  try {
33507
33899
  nativeDb.exec("PRAGMA foreign_keys = ON");
@@ -33521,7 +33913,7 @@ async function rollbackBothScopes(scope, projectDbPath, globalDbPath) {
33521
33913
  rollbackConsolidatedToEmpty(native, s);
33522
33914
  }
33523
33915
  } catch (err) {
33524
- log4.warn(
33916
+ log6.warn(
33525
33917
  { err, scope: s, openingScope: scope },
33526
33918
  "exodus-on-open: could not roll back scope (best-effort)"
33527
33919
  );
@@ -33537,7 +33929,7 @@ function isDataContinuityOk(result) {
33537
33929
  const deficits = result.tables.filter((t) => t.targetCount < t.sourceCount);
33538
33930
  const surpluses = result.tables.filter((t) => t.targetCount > t.sourceCount);
33539
33931
  if (surpluses.length > 0) {
33540
- log4.warn(
33932
+ log6.warn(
33541
33933
  {
33542
33934
  surpluses: surpluses.map((t) => ({
33543
33935
  table: t.targetTable,
@@ -33571,7 +33963,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33571
33963
  const { buildExodusPlan: buildExodusPlan2, runExodusMigrate: runExodusMigrate2, verifyMigration: verifyMigration2, clearExodusJournal: clearExodusJournal2 } = await Promise.resolve().then(() => (init_exodus(), exodus_exports));
33572
33964
  const plan = buildExodusPlan2(cwd);
33573
33965
  const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
33574
- if (!scopeSources.some((s) => existsSync11(s.path))) {
33966
+ if (!scopeSources.some((s) => existsSync13(s.path))) {
33575
33967
  return {
33576
33968
  outcome: "skipped",
33577
33969
  reason: `no legacy ${scope}-scope source DBs present (fresh install or cross-scope-only)`
@@ -33584,11 +33976,11 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33584
33976
  if (!consolidatedIsEmpty(nativeDb, scope)) {
33585
33977
  return { outcome: "skipped", reason: "migrated by a concurrent process (lock winner)" };
33586
33978
  }
33587
- log4.info(
33979
+ log6.info(
33588
33980
  {
33589
33981
  scope,
33590
33982
  dbPath,
33591
- 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)
33592
33984
  },
33593
33985
  "exodus-on-open: consolidated cleo.db is empty and legacy data present \u2014 auto-migrating"
33594
33986
  );
@@ -33597,23 +33989,23 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33597
33989
  const migrateResult = await runExodusMigrate2(
33598
33990
  plan,
33599
33991
  false,
33600
- (msg) => log4.debug({ scope }, `exodus-on-open: ${msg}`)
33992
+ (msg) => log6.debug({ scope }, `exodus-on-open: ${msg}`)
33601
33993
  );
33602
33994
  if (!migrateResult.ok) {
33603
33995
  await rollbackBothScopes(scope, plan.projectDbPath, plan.globalDbPath);
33604
33996
  clearExodusJournal2(migrateResult.stagingDir);
33605
33997
  const reason = `migration failed: ${migrateResult.error ?? "unknown error"} \u2014 legacy DBs kept as source`;
33606
- log4.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33998
+ log6.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33607
33999
  return { outcome: "aborted", reason };
33608
34000
  }
33609
34001
  const verifyResult = verifyMigration2(
33610
34002
  plan.sources,
33611
34003
  plan.projectDbPath,
33612
34004
  plan.globalDbPath,
33613
- (msg) => log4.debug({ scope }, `exodus-on-open verify: ${msg}`)
34005
+ (msg) => log6.debug({ scope }, `exodus-on-open verify: ${msg}`)
33614
34006
  );
33615
34007
  if (!verifyResult.ok) {
33616
- log4.warn(
34008
+ log6.warn(
33617
34009
  {
33618
34010
  scope,
33619
34011
  enumDrift: verifyResult.enumDrift.length,
@@ -33627,7 +34019,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33627
34019
  clearExodusJournal2(plan.stagingDir);
33628
34020
  const deficits = verifyResult.tables.filter((t) => t.targetCount < t.sourceCount).map((t) => `${t.targetTable}(${t.sourceCount}\u2192${t.targetCount})`);
33629
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();
33630
- log4.error(
34022
+ log6.error(
33631
34023
  {
33632
34024
  scope,
33633
34025
  countDeficits: deficits,
@@ -33639,14 +34031,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33639
34031
  return { outcome: "aborted", reason };
33640
34032
  }
33641
34033
  const rowsCopied = migrateResult.tables.filter((t) => !t.skipped).reduce((n, t) => n + t.rowsCopied, 0);
33642
- log4.info(
34034
+ log6.info(
33643
34035
  { scope, rowsCopied, tables: migrateResult.tables.length },
33644
34036
  "exodus-on-open: parity verified \u2014 legacy data migrated into consolidated cleo.db"
33645
34037
  );
33646
34038
  try {
33647
- const consumed = plan.sources.filter((s) => existsSync11(s.path));
34039
+ const consumed = plan.sources.filter((s) => existsSync13(s.path));
33648
34040
  const archiveResult = archiveMigratedSources(consumed, cwd);
33649
- log4.info(
34041
+ log6.info(
33650
34042
  {
33651
34043
  scope,
33652
34044
  archived: archiveResult.sources.filter((s) => s.action === "archived").length,
@@ -33655,7 +34047,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33655
34047
  "exodus-on-open: archived legacy sources + sealed completion marker(s)"
33656
34048
  );
33657
34049
  } catch (err) {
33658
- log4.error(
34050
+ log6.error(
33659
34051
  { err, scope },
33660
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`)"
33661
34053
  );
@@ -33677,14 +34069,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33677
34069
  function _isExodusInProgress() {
33678
34070
  return _exodusInProgress;
33679
34071
  }
33680
- var log4, _exodusInProgress;
34072
+ var log6, _exodusInProgress;
33681
34073
  var init_on_open = __esm({
33682
34074
  "packages/core/src/store/exodus/on-open.ts"() {
33683
34075
  "use strict";
33684
34076
  init_logger2();
33685
34077
  init_lock();
33686
34078
  init_archive2();
33687
- log4 = getLogger("exodus-on-open");
34079
+ log6 = getLogger("exodus-on-open");
33688
34080
  _exodusInProgress = false;
33689
34081
  }
33690
34082
  });
@@ -33699,7 +34091,7 @@ __export(dual_scope_db_exports, {
33699
34091
  resolveDualScopeDbPath: () => resolveDualScopeDbPath,
33700
34092
  upsertIdempotent: () => upsertIdempotent
33701
34093
  });
33702
- import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
34094
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3 } from "node:fs";
33703
34095
  import { createRequire as createRequire4 } from "node:module";
33704
34096
  import { dirname as dirname5, join as join12 } from "node:path";
33705
34097
  function cacheKey(scope, dbPath) {
@@ -33747,10 +34139,10 @@ async function openDualScopeDb(scope, cwd) {
33747
34139
  const dbPath = resolveDualScopeDbPath(scope, cwd);
33748
34140
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath, cwd) : openDualScopeDbAtPath("global", dbPath, cwd);
33749
34141
  }
33750
- async function openDedicatedDualScopeDb(scope, dbPath, log8) {
33751
- 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)");
33752
34144
  const dir = dirname5(dbPath);
33753
- if (!existsSync12(dir)) {
34145
+ if (!existsSync14(dir)) {
33754
34146
  mkdirSync3(dir, { recursive: true });
33755
34147
  }
33756
34148
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33768,7 +34160,7 @@ async function openDedicatedDualScopeDb(scope, dbPath, log8) {
33768
34160
  existenceTable(scope),
33769
34161
  `dual-scope-db[${scope}]`
33770
34162
  );
33771
- 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)");
33772
34164
  return {
33773
34165
  db,
33774
34166
  scope,
@@ -33793,14 +34185,14 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33793
34185
  return existing.handle;
33794
34186
  }
33795
34187
  }
33796
- const log8 = getLogger("dual-scope-db");
34188
+ const log10 = getLogger("dual-scope-db");
33797
34189
  if (dedicated) {
33798
- return openDedicatedDualScopeDb(scope, dbPath, log8);
34190
+ return openDedicatedDualScopeDb(scope, dbPath, log10);
33799
34191
  }
33800
34192
  const initPromise = (async () => {
33801
- log8.debug({ scope, dbPath }, "opening dual-scope cleo.db");
34193
+ log10.debug({ scope, dbPath }, "opening dual-scope cleo.db");
33802
34194
  const dir = dirname5(dbPath);
33803
- if (!existsSync12(dir)) {
34195
+ if (!existsSync14(dir)) {
33804
34196
  mkdirSync3(dir, { recursive: true });
33805
34197
  }
33806
34198
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33818,7 +34210,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33818
34210
  existenceTable(scope),
33819
34211
  `dual-scope-db[${scope}]`
33820
34212
  );
33821
- log8.debug({ scope, dbPath }, "dual-scope cleo.db ready");
34213
+ log10.debug({ scope, dbPath }, "dual-scope cleo.db ready");
33822
34214
  const handle = {
33823
34215
  db,
33824
34216
  scope,
@@ -33842,7 +34234,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33842
34234
  const result = await maybeRunExodusOnOpen2(scope, dbPath, nativeDb, exodusCwd);
33843
34235
  if (result.outcome === "migrated" || result.outcome === "aborted") {
33844
34236
  if (result.outcome === "aborted") {
33845
- log8.warn(
34237
+ log10.warn(
33846
34238
  { scope, reason: result.reason },
33847
34239
  "exodus-on-open aborted; consolidated cleo.db left empty, legacy kept as source"
33848
34240
  );
@@ -33850,7 +34242,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33850
34242
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath) : openDualScopeDbAtPath("global", dbPath);
33851
34243
  }
33852
34244
  } catch (err) {
33853
- log8.warn(
34245
+ log10.warn(
33854
34246
  { err, scope },
33855
34247
  "exodus-on-open hook failed (non-fatal); re-opening consolidated handle"
33856
34248
  );
@@ -34434,7 +34826,7 @@ __export(nexus_sqlite_exports, {
34434
34826
  resetNexusDbState: () => resetNexusDbState,
34435
34827
  resolveNexusMigrationsFolder: () => resolveNexusMigrationsFolder
34436
34828
  });
34437
- import { copyFileSync as copyFileSync4, existsSync as existsSync13 } from "node:fs";
34829
+ import { copyFileSync as copyFileSync4, existsSync as existsSync15 } from "node:fs";
34438
34830
  import { join as join13 } from "node:path";
34439
34831
  function getNexusDbPath(cwd) {
34440
34832
  return resolveDualScopeDbPath("project", cwd);
@@ -34476,12 +34868,12 @@ function detectAndWarnOnNestedNexus() {
34476
34868
  } catch {
34477
34869
  return false;
34478
34870
  }
34479
- if (!existsSync13(nestedPath)) return false;
34871
+ if (!existsSync15(nestedPath)) return false;
34480
34872
  if (_warnedNestedPaths.has(nestedPath)) return false;
34481
34873
  _warnedNestedPaths.add(nestedPath);
34482
34874
  const canonicalPath = getNexusDbPath();
34483
- const log8 = getLogger("nexus-sqlite");
34484
- log8.warn(
34875
+ const log10 = getLogger("nexus-sqlite");
34876
+ log10.warn(
34485
34877
  {
34486
34878
  nestedPath,
34487
34879
  canonicalPath,
@@ -34496,7 +34888,7 @@ function detectAndWarnOnNestedNexus() {
34496
34888
  function _resetNestedNexusWarningGate() {
34497
34889
  _warnedNestedPaths.clear();
34498
34890
  }
34499
- function tableExists3(nativeDb, tableName) {
34891
+ function tableExists4(nativeDb, tableName) {
34500
34892
  const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
34501
34893
  return !!result;
34502
34894
  }
@@ -34505,7 +34897,7 @@ function objectExists(nativeDb, type, name2) {
34505
34897
  return !!result;
34506
34898
  }
34507
34899
  function ensureNexusFts5(nativeDb) {
34508
- if (!tableExists3(nativeDb, "nexus_nodes")) return;
34900
+ if (!tableExists4(nativeDb, "nexus_nodes")) return;
34509
34901
  if (!objectExists(nativeDb, "table", "nexus_symbols_fts")) {
34510
34902
  nativeDb.exec(`
34511
34903
  CREATE VIRTUAL TABLE nexus_symbols_fts USING fts5(
@@ -34551,7 +34943,7 @@ function ensureNexusFts5(nativeDb) {
34551
34943
  `);
34552
34944
  }
34553
34945
  function ensureNexusRelationWeights(nativeDb) {
34554
- if (!tableExists3(nativeDb, "nexus_relations")) return;
34946
+ if (!tableExists4(nativeDb, "nexus_relations")) return;
34555
34947
  nativeDb.exec(`
34556
34948
  CREATE TABLE IF NOT EXISTS nexus_relation_weights (
34557
34949
  relation_id TEXT PRIMARY KEY NOT NULL,
@@ -34633,9 +35025,9 @@ function ensureNexusRelationWeights(nativeDb) {
34633
35025
  }
34634
35026
  function runNexusMigrations(nativeDb, db) {
34635
35027
  const migrationsFolder = resolveNexusMigrationsFolder();
34636
- if (tableExists3(nativeDb, "nexus_nodes") && _nexusDbPath) {
35028
+ if (tableExists4(nativeDb, "nexus_nodes") && _nexusDbPath) {
34637
35029
  const backupPath = _nexusDbPath.replace(/\.db$/, "-pre-cleo.db.bak");
34638
- if (!existsSync13(backupPath)) {
35030
+ if (!existsSync15(backupPath)) {
34639
35031
  try {
34640
35032
  copyFileSync4(_nexusDbPath, backupPath);
34641
35033
  } catch {
@@ -34767,7 +35159,7 @@ var init_nexus_sqlite = __esm({
34767
35159
 
34768
35160
  // packages/core/src/paths.ts
34769
35161
  import { AsyncLocalStorage } from "node:async_hooks";
34770
- 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";
34771
35163
  import { createRequire as createRequire5 } from "node:module";
34772
35164
  import { homedir } from "node:os";
34773
35165
  import { basename as basename4, dirname as dirname6, join as join14, resolve as resolve3 } from "node:path";
@@ -34861,7 +35253,7 @@ function _resolveProjectByCwdFromNexus(cwd) {
34861
35253
  try {
34862
35254
  const cleoHome = getCleoHome();
34863
35255
  const globalDbPath = join14(cleoHome, "cleo.db");
34864
- if (!existsSync14(globalDbPath)) return null;
35256
+ if (!existsSync16(globalDbPath)) return null;
34865
35257
  const start = resolve3(cwd ?? process.cwd());
34866
35258
  let current = start;
34867
35259
  const DatabaseSync3 = _getDatabaseSyncCtor();
@@ -34900,7 +35292,7 @@ function _findCleoDirRoot(cwd) {
34900
35292
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
34901
35293
  if (!isDangerousRoot) {
34902
35294
  try {
34903
- if (statSync4(join14(current, ".cleo")).isDirectory()) {
35295
+ if (statSync6(join14(current, ".cleo")).isDirectory()) {
34904
35296
  return current;
34905
35297
  }
34906
35298
  } catch {
@@ -34974,7 +35366,7 @@ function _cwdHasGitAncestor(cwd) {
34974
35366
  while (true) {
34975
35367
  const gitMarker = join14(current, ".git");
34976
35368
  try {
34977
- if (existsSync14(gitMarker)) {
35369
+ if (existsSync16(gitMarker)) {
34978
35370
  return true;
34979
35371
  }
34980
35372
  } catch {
@@ -34987,15 +35379,15 @@ function _cwdHasGitAncestor(cwd) {
34987
35379
  function _resolveMainRepoFromGitlink(gitlinkDir) {
34988
35380
  try {
34989
35381
  const gitLinkPath = join14(gitlinkDir, ".git");
34990
- if (!existsSync14(gitLinkPath)) return null;
34991
- const stat2 = statSync4(gitLinkPath);
35382
+ if (!existsSync16(gitLinkPath)) return null;
35383
+ const stat2 = statSync6(gitLinkPath);
34992
35384
  if (!stat2.isFile()) return null;
34993
35385
  const gitLinkContent = readFileSync5(gitLinkPath, "utf-8").trim();
34994
35386
  const match = gitLinkContent.match(/^gitdir:\s*(.+)$/m);
34995
35387
  if (!match) return null;
34996
35388
  const gitdir = match[1].trim();
34997
35389
  const mainRepo = dirname6(dirname6(dirname6(gitdir)));
34998
- if (existsSync14(join14(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
35390
+ if (existsSync16(join14(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
34999
35391
  return mainRepo;
35000
35392
  }
35001
35393
  } catch {
@@ -35004,19 +35396,19 @@ function _resolveMainRepoFromGitlink(gitlinkDir) {
35004
35396
  }
35005
35397
  function validateProjectRoot(candidate) {
35006
35398
  const cleoDir = join14(candidate, ".cleo");
35007
- if (!existsSync14(cleoDir)) {
35399
+ if (!existsSync16(cleoDir)) {
35008
35400
  return false;
35009
35401
  }
35010
35402
  const projectInfoPath = join14(cleoDir, "project-info.json");
35011
- if (existsSync14(projectInfoPath)) {
35403
+ if (existsSync16(projectInfoPath)) {
35012
35404
  try {
35013
35405
  const raw = readFileSync5(projectInfoPath, "utf-8");
35014
35406
  const parsed = JSON.parse(raw);
35015
35407
  if (typeof parsed === "object" && parsed !== null && "projectId" in parsed && typeof parsed["projectId"] === "string" && parsed["projectId"] !== "") {
35016
35408
  const gitMarker = join14(candidate, ".git");
35017
- if (existsSync14(gitMarker)) {
35409
+ if (existsSync16(gitMarker)) {
35018
35410
  try {
35019
- if (!statSync4(gitMarker).isDirectory()) {
35411
+ if (!statSync6(gitMarker).isDirectory()) {
35020
35412
  return false;
35021
35413
  }
35022
35414
  } catch {
@@ -35029,10 +35421,10 @@ function validateProjectRoot(candidate) {
35029
35421
  }
35030
35422
  }
35031
35423
  const gitDir = join14(candidate, ".git");
35032
- if (existsSync14(gitDir)) {
35424
+ if (existsSync16(gitDir)) {
35033
35425
  let isRealGitDir = false;
35034
35426
  try {
35035
- const stat2 = statSync4(gitDir);
35427
+ const stat2 = statSync6(gitDir);
35036
35428
  isRealGitDir = stat2.isDirectory();
35037
35429
  } catch {
35038
35430
  isRealGitDir = false;
@@ -35079,16 +35471,16 @@ function getProjectRoot(cwd) {
35079
35471
  const cleoDir = join14(current, ".cleo");
35080
35472
  const gitDir = join14(current, ".git");
35081
35473
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
35082
- if (existsSync14(cleoDir) && !isDangerousRoot) {
35474
+ if (existsSync16(cleoDir) && !isDangerousRoot) {
35083
35475
  if (validateProjectRoot(current)) {
35084
35476
  return current;
35085
35477
  }
35086
35478
  skippedCleoDirs.push(current);
35087
35479
  }
35088
- if (existsSync14(gitDir) && !isDangerousRoot) {
35480
+ if (existsSync16(gitDir) && !isDangerousRoot) {
35089
35481
  let isRealGitDir = false;
35090
35482
  try {
35091
- isRealGitDir = statSync4(gitDir).isDirectory();
35483
+ isRealGitDir = statSync6(gitDir).isDirectory();
35092
35484
  } catch {
35093
35485
  isRealGitDir = false;
35094
35486
  }
@@ -35141,7 +35533,7 @@ function isAbsolutePath(path2) {
35141
35533
  function _readProjectNameFromInfo(projectRoot) {
35142
35534
  try {
35143
35535
  const infoPath = join14(projectRoot, ".cleo", "project-info.json");
35144
- if (!existsSync14(infoPath)) return void 0;
35536
+ if (!existsSync16(infoPath)) return void 0;
35145
35537
  const raw = readFileSync5(infoPath, "utf-8");
35146
35538
  const data = JSON.parse(raw);
35147
35539
  return typeof data.name === "string" && data.name.length > 0 ? data.name : void 0;
@@ -35415,7 +35807,7 @@ __export(config_exports, {
35415
35807
  parseConfigValue: () => parseConfigValue,
35416
35808
  setConfigValue: () => setConfigValue
35417
35809
  });
35418
- import { existsSync as existsSync15 } from "node:fs";
35810
+ import { existsSync as existsSync17 } from "node:fs";
35419
35811
  import { mkdir as mkdir3, writeFile } from "node:fs/promises";
35420
35812
  import { dirname as dirname7 } from "node:path";
35421
35813
  function getNestedValue(obj, path2) {
@@ -35539,7 +35931,7 @@ function parseConfigValue(value) {
35539
35931
  }
35540
35932
  async function setConfigValue(key, value, cwd, opts) {
35541
35933
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35542
- if (!existsSync15(configPath)) {
35934
+ if (!existsSync17(configPath)) {
35543
35935
  const dir = dirname7(configPath);
35544
35936
  await mkdir3(dir, { recursive: true });
35545
35937
  await writeFile(configPath, "{}", "utf-8");
@@ -35553,7 +35945,7 @@ async function setConfigValue(key, value, cwd, opts) {
35553
35945
  async function applyStrictnessPreset(preset, cwd, opts) {
35554
35946
  const definition = STRICTNESS_PRESETS[preset];
35555
35947
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35556
- if (!existsSync15(configPath)) {
35948
+ if (!existsSync17(configPath)) {
35557
35949
  const dir = dirname7(configPath);
35558
35950
  await mkdir3(dir, { recursive: true });
35559
35951
  await writeFile(configPath, "{}", "utf-8");
@@ -35899,7 +36291,7 @@ function brainTablesAreConsolidatedShape(nativeDb) {
35899
36291
  return createdAt !== void 0 && createdAt.type.toUpperCase() !== "INTEGER";
35900
36292
  }
35901
36293
  function establishLegacyBrainSchema(nativeDb, db) {
35902
- const log8 = getLogger("brain-schema");
36294
+ const log10 = getLogger("brain-schema");
35903
36295
  if (brainTablesAreConsolidatedShape(nativeDb)) {
35904
36296
  const fkRow = nativeDb.prepare("PRAGMA foreign_keys").get();
35905
36297
  const fkWasOn = fkRow?.foreign_keys === 1;
@@ -35909,7 +36301,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35909
36301
  try {
35910
36302
  nativeDb.exec(`DROP TABLE IF EXISTS \`${table}\``);
35911
36303
  } catch (err) {
35912
- log8.warn(
36304
+ log10.warn(
35913
36305
  { table, err },
35914
36306
  "Failed to drop consolidated brain table during legacy rebuild."
35915
36307
  );
@@ -35918,7 +36310,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35918
36310
  } finally {
35919
36311
  nativeDb.exec(`PRAGMA foreign_keys=${fkWasOn ? "ON" : "OFF"}`);
35920
36312
  }
35921
- log8.debug(
36313
+ log10.debug(
35922
36314
  { count: CONSOLIDATED_BRAIN_TABLES.length },
35923
36315
  "Dropped consolidated (exodus-target) brain tables \u2014 rebuilding in legacy runtime shape."
35924
36316
  );
@@ -35929,7 +36321,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
35929
36321
  nativeDb,
35930
36322
  migrationsFolder
35931
36323
  );
35932
- log8.debug(
36324
+ log10.debug(
35933
36325
  { marked, applied },
35934
36326
  "brain consolidated cleo.db reconcile (T11647) \u2014 marked already-present migrations applied + executed the missing unprefixed-table migrations directly."
35935
36327
  );
@@ -36479,7 +36871,7 @@ async function upsertTask(db, row, archiveFields, allowOrphanParent = false) {
36479
36871
  if (allowOrphanParent) {
36480
36872
  row = { ...row, parentId: null };
36481
36873
  } else {
36482
- log5.warn(
36874
+ log7.warn(
36483
36875
  { taskId: row.id, parentId: row.parentId },
36484
36876
  "upsertTask: parentId references a non-existent task \u2014 parent relationship may be lost"
36485
36877
  );
@@ -36662,13 +37054,13 @@ async function loadRelationsForTasks(db, tasks2) {
36662
37054
  task.relates = relations && relations.length > 0 ? relations : [];
36663
37055
  }
36664
37056
  }
36665
- var log5;
37057
+ var log7;
36666
37058
  var init_db_helpers = __esm({
36667
37059
  "packages/core/src/store/db-helpers.ts"() {
36668
37060
  "use strict";
36669
37061
  init_logger2();
36670
37062
  init_tasks_schema();
36671
- log5 = getLogger("db-helpers");
37063
+ log7 = getLogger("db-helpers");
36672
37064
  }
36673
37065
  });
36674
37066
 
@@ -36810,7 +37202,7 @@ var init_sqlite2 = __esm({
36810
37202
  });
36811
37203
 
36812
37204
  // packages/core/src/store/agent-registry-store.ts
36813
- import { existsSync as existsSync16 } from "node:fs";
37205
+ import { existsSync as existsSync18 } from "node:fs";
36814
37206
  import { join as join16 } from "node:path";
36815
37207
  function getGlobalAgentRegistryDbPath() {
36816
37208
  const cleoHome = getCleoHome();
@@ -36855,7 +37247,7 @@ function writeAgentRegistrySchemaVersionSentinel(db) {
36855
37247
  }
36856
37248
  async function ensureGlobalAgentRegistryDb() {
36857
37249
  const dbPath = getGlobalAgentRegistryDbPath();
36858
- const alreadyExists = existsSync16(dbPath);
37250
+ const alreadyExists = existsSync18(dbPath);
36859
37251
  const dualHandle = await openDualScopeDb("global");
36860
37252
  const nativeDb = dualHandle.db.$client ?? null;
36861
37253
  if (!nativeDb) {
@@ -36904,7 +37296,7 @@ var init_agent_registry_store = __esm({
36904
37296
  });
36905
37297
 
36906
37298
  // packages/core/src/store/conduit-sqlite.ts
36907
- import { existsSync as existsSync17 } from "node:fs";
37299
+ import { existsSync as existsSync19 } from "node:fs";
36908
37300
  import { createRequire as createRequire7 } from "node:module";
36909
37301
  function _getDrizzle2() {
36910
37302
  if (_drizzle3 === null) {
@@ -36935,7 +37327,7 @@ async function ensureConduitDb(projectRoot) {
36935
37327
  }
36936
37328
  if (_initPromise3) return _initPromise3;
36937
37329
  _initPromise3 = (async () => {
36938
- const alreadyExists = existsSync17(dbPath);
37330
+ const alreadyExists = existsSync19(dbPath);
36939
37331
  const dualHandle = await openDualScopeDb("project", projectRoot);
36940
37332
  const nativeDb = dualHandle.db.$client ?? null;
36941
37333
  if (!nativeDb) {
@@ -37229,10 +37621,10 @@ var init_skills_db = __esm({
37229
37621
  import {
37230
37622
  chmodSync,
37231
37623
  copyFileSync as copyFileSync5,
37232
- existsSync as existsSync18,
37624
+ existsSync as existsSync20,
37233
37625
  mkdirSync as mkdirSync5,
37234
37626
  readdirSync as readdirSync4,
37235
- statSync as statSync5,
37627
+ statSync as statSync7,
37236
37628
  unlinkSync as unlinkSync3
37237
37629
  } from "node:fs";
37238
37630
  import { join as join18 } from "node:path";
@@ -37271,7 +37663,7 @@ async function openAgentRegistryDbForSnapshot() {
37271
37663
  async function openTelemetryDbForSnapshot() {
37272
37664
  try {
37273
37665
  const path2 = join18(getCleoHome(), "telemetry.db");
37274
- if (!existsSync18(path2)) return null;
37666
+ if (!existsSync20(path2)) return null;
37275
37667
  } catch {
37276
37668
  return null;
37277
37669
  }
@@ -37286,7 +37678,7 @@ function buildRawFileVacuumOpener(entry) {
37286
37678
  return async (cwd) => {
37287
37679
  const path2 = resolveInventoryPath(entry, cwd);
37288
37680
  if (!path2) return null;
37289
- if (!existsSync18(path2)) return null;
37681
+ if (!existsSync20(path2)) return null;
37290
37682
  try {
37291
37683
  const { DatabaseSync: DatabaseSync3 } = await import("node:sqlite");
37292
37684
  return new DatabaseSync3(path2, { readOnly: true });
@@ -37377,7 +37769,7 @@ function rotateSnapshots(backupDir, prefix) {
37377
37769
  const files = readdirSync4(backupDir).filter((f) => pattern.test(f)).map((f) => ({
37378
37770
  name: f,
37379
37771
  path: join18(backupDir, f),
37380
- mtimeMs: statSync5(join18(backupDir, f)).mtimeMs
37772
+ mtimeMs: statSync7(join18(backupDir, f)).mtimeMs
37381
37773
  })).sort((a, b) => a.mtimeMs - b.mtimeMs);
37382
37774
  while (files.length >= MAX_SNAPSHOTS) {
37383
37775
  const oldest = files.shift();
@@ -37436,12 +37828,12 @@ function listSqliteBackupsForPrefix(prefix, cwd) {
37436
37828
  try {
37437
37829
  const cleoDir = getCleoDir(cwd);
37438
37830
  const backupDir = join18(cleoDir, "backups", "sqlite");
37439
- if (!existsSync18(backupDir)) return [];
37831
+ if (!existsSync20(backupDir)) return [];
37440
37832
  const pattern = snapshotPattern(prefix);
37441
37833
  return readdirSync4(backupDir).filter((f) => pattern.test(f)).map((f) => ({
37442
37834
  name: f,
37443
37835
  path: join18(backupDir, f),
37444
- mtimeMs: statSync5(join18(backupDir, f)).mtimeMs
37836
+ mtimeMs: statSync7(join18(backupDir, f)).mtimeMs
37445
37837
  })).sort((a, b) => b.mtimeMs - a.mtimeMs);
37446
37838
  } catch {
37447
37839
  return [];
@@ -37624,7 +38016,7 @@ __export(sqlite_exports, {
37624
38016
  resolveMigrationsFolder: () => resolveMigrationsFolder,
37625
38017
  schema: () => tasks_schema_exports
37626
38018
  });
37627
- 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";
37628
38020
  import { createRequire as createRequire9 } from "node:module";
37629
38021
  import { eq as eq5 } from "drizzle-orm";
37630
38022
  function _getDrizzle3() {
@@ -37666,7 +38058,7 @@ function countBackupTasks(backupDb) {
37666
38058
  }
37667
38059
  async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37668
38060
  const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
37669
- const log8 = getLogger("sqlite");
38061
+ const log10 = getLogger("sqlite");
37670
38062
  try {
37671
38063
  const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks_tasks").get();
37672
38064
  const taskCount = countResult?.cnt ?? 0;
@@ -37692,13 +38084,13 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37692
38084
  async () => {
37693
38085
  const currentTaskCount = await recountTasksFromDisk(dbPath);
37694
38086
  if (currentTaskCount > 0) {
37695
- log8.info(
38087
+ log10.info(
37696
38088
  { dbPath, currentTaskCount },
37697
38089
  "Auto-recovery skipped: database was populated by a concurrent process while acquiring the first-open lock (T11662 double-checked re-query)."
37698
38090
  );
37699
38091
  return;
37700
38092
  }
37701
- log8.warn(
38093
+ log10.warn(
37702
38094
  { dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
37703
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).`
37704
38096
  );
@@ -37718,7 +38110,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37718
38110
  const tempPath = dbPath + ".recovery-tmp";
37719
38111
  copyFileSync6(newestBackup.path, tempPath);
37720
38112
  renameSync3(tempPath, dbPath);
37721
- log8.info(
38113
+ log10.info(
37722
38114
  { dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
37723
38115
  "Database auto-recovered from backup successfully."
37724
38116
  );
@@ -37735,7 +38127,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
37735
38127
  { stale: 6e5, retries: 30 }
37736
38128
  );
37737
38129
  } catch (err) {
37738
- 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.");
37739
38131
  }
37740
38132
  }
37741
38133
  async function getDb(cwd) {
@@ -37835,7 +38227,7 @@ async function getSchemaVersion(cwd) {
37835
38227
  return result[0]?.value ?? null;
37836
38228
  }
37837
38229
  function dbExists(cwd) {
37838
- return existsSync19(resolveDualScopeDbPath("project", cwd));
38230
+ return existsSync21(resolveDualScopeDbPath("project", cwd));
37839
38231
  }
37840
38232
  function getNativeDb() {
37841
38233
  return _nativeDb3;
@@ -39023,7 +39415,7 @@ var init_sqlite_data_accessor = __esm({
39023
39415
  });
39024
39416
 
39025
39417
  // packages/core/src/sequence/index.ts
39026
- 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";
39027
39419
  import { join as join20 } from "node:path";
39028
39420
  function getLegacySequenceJsonPath(cwd) {
39029
39421
  return join20(resolveOrCwd(cwd), ".cleo", ".sequence.json");
@@ -39040,7 +39432,7 @@ function isSeedSequence(value) {
39040
39432
  return value.counter === 0 && value.lastId === "T000" && value.checksum === "seed";
39041
39433
  }
39042
39434
  function readLegacySequenceFile(path2) {
39043
- if (!existsSync20(path2)) return null;
39435
+ if (!existsSync22(path2)) return null;
39044
39436
  try {
39045
39437
  const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
39046
39438
  return isValidSequenceState(parsed) ? parsed : null;
@@ -39049,10 +39441,10 @@ function readLegacySequenceFile(path2) {
39049
39441
  }
39050
39442
  }
39051
39443
  function renameLegacyFile(path2) {
39052
- if (!existsSync20(path2)) return;
39444
+ if (!existsSync22(path2)) return;
39053
39445
  const migratedPath = `${path2}.migrated`;
39054
39446
  try {
39055
- if (!existsSync20(migratedPath)) {
39447
+ if (!existsSync22(migratedPath)) {
39056
39448
  renameSync4(path2, migratedPath);
39057
39449
  return;
39058
39450
  }
@@ -39215,7 +39607,7 @@ var init_sequence = __esm({
39215
39607
 
39216
39608
  // packages/core/src/store/git-checkpoint.ts
39217
39609
  import { execFile as execFile2 } from "node:child_process";
39218
- import { existsSync as existsSync21 } from "node:fs";
39610
+ import { existsSync as existsSync23 } from "node:fs";
39219
39611
  import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
39220
39612
  import { join as join21, resolve as resolve5 } from "node:path";
39221
39613
  import { promisify as promisify2 } from "node:util";
@@ -39241,7 +39633,7 @@ async function cleoGitCommand(args, cleoDir) {
39241
39633
  }
39242
39634
  }
39243
39635
  function isCleoGitInitialized(cleoDir) {
39244
- return existsSync21(join21(cleoDir, ".git", "HEAD"));
39636
+ return existsSync23(join21(cleoDir, ".git", "HEAD"));
39245
39637
  }
39246
39638
  async function loadStateFileAllowlist(cwd) {
39247
39639
  try {
@@ -39285,14 +39677,14 @@ async function isCleoGitRepo(cleoDir) {
39285
39677
  return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
39286
39678
  }
39287
39679
  function isMergeInProgress(cleoDir) {
39288
- return existsSync21(join21(cleoDir, ".git", "MERGE_HEAD"));
39680
+ return existsSync23(join21(cleoDir, ".git", "MERGE_HEAD"));
39289
39681
  }
39290
39682
  async function isDetachedHead(cleoDir) {
39291
39683
  const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
39292
39684
  return !result.success;
39293
39685
  }
39294
39686
  function isRebaseInProgress(cleoDir) {
39295
- 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"));
39296
39688
  }
39297
39689
  async function recordCheckpointTime(cleoDir) {
39298
39690
  try {
@@ -39316,7 +39708,7 @@ async function getChangedStateFiles(cleoDir, cwd) {
39316
39708
  const allStateFiles = await getAllStateFiles(cwd);
39317
39709
  for (const stateFile of allStateFiles) {
39318
39710
  const fullPath = join21(cleoDir, stateFile);
39319
- if (!existsSync21(fullPath)) continue;
39711
+ if (!existsSync23(fullPath)) continue;
39320
39712
  const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
39321
39713
  const cachedResult = await cleoGitCommand(
39322
39714
  ["diff", "--cached", "--quiet", "--", stateFile],
@@ -39343,7 +39735,7 @@ async function shouldCheckpoint(options) {
39343
39735
  const config = await loadCheckpointConfig(cwd);
39344
39736
  if (!config.enabled) return false;
39345
39737
  const cleoDir = getCleoDir(cwd);
39346
- if (!existsSync21(cleoDir)) return false;
39738
+ if (!existsSync23(cleoDir)) return false;
39347
39739
  if (!isCleoGitInitialized(cleoDir)) return false;
39348
39740
  if (!await isCleoGitRepo(cleoDir)) return false;
39349
39741
  if (isMergeInProgress(cleoDir)) return false;
@@ -39421,7 +39813,7 @@ async function ensureSequenceValid(cwd, options) {
39421
39813
  if (!options?.validateSequence) return;
39422
39814
  const check = await checkSequence(cwd);
39423
39815
  if (!check.valid) {
39424
- log6.warn({ counter: check.counter, maxId: check.maxIdInData }, "Sequence behind, repairing");
39816
+ log8.warn({ counter: check.counter, maxId: check.maxIdInData }, "Sequence behind, repairing");
39425
39817
  const repair = await repairSequence(cwd);
39426
39818
  if (!repair.repaired && options.strict) {
39427
39819
  throw new DataSafetyError(`Sequence repair failed: ${repair.message}`, "SEQUENCE_INVALID", {
@@ -39438,7 +39830,7 @@ async function checkpoint(context, cwd, options) {
39438
39830
  stats.checkpoints++;
39439
39831
  stats.lastCheckpoint = /* @__PURE__ */ new Date();
39440
39832
  } catch (err) {
39441
- log6.warn({ err }, "Checkpoint failed (non-fatal)");
39833
+ log8.warn({ err }, "Checkpoint failed (non-fatal)");
39442
39834
  }
39443
39835
  vacuumIntoBackup({ cwd }).catch(() => {
39444
39836
  });
@@ -39500,7 +39892,7 @@ async function safeAppendLog(accessor, entry, cwd, options) {
39500
39892
  stats.writes++;
39501
39893
  await checkpoint("log entry", cwd, opts);
39502
39894
  }
39503
- var log6, DataSafetyError, DEFAULT_SAFETY, stats;
39895
+ var log8, DataSafetyError, DEFAULT_SAFETY, stats;
39504
39896
  var init_data_safety_central = __esm({
39505
39897
  "packages/core/src/store/data-safety-central.ts"() {
39506
39898
  "use strict";
@@ -39510,7 +39902,7 @@ var init_data_safety_central = __esm({
39510
39902
  init_sqlite3();
39511
39903
  init_sqlite_backup();
39512
39904
  init_tasks_schema();
39513
- log6 = getLogger("data-safety");
39905
+ log8 = getLogger("data-safety");
39514
39906
  DataSafetyError = class extends Error {
39515
39907
  constructor(message, code, context) {
39516
39908
  super(message);
@@ -39550,7 +39942,7 @@ function isSafetyDisabled() {
39550
39942
  }
39551
39943
  function wrapWithSafety(accessor, cwd) {
39552
39944
  if (isSafetyDisabled()) {
39553
- log7.warn(
39945
+ log9.warn(
39554
39946
  "Safety disabled - emergency mode (CLEO_DISABLE_SAFETY=true). Data integrity checks bypassed."
39555
39947
  );
39556
39948
  return accessor;
@@ -39571,13 +39963,13 @@ function getSafetyStatus() {
39571
39963
  enabled: true
39572
39964
  };
39573
39965
  }
39574
- var log7, SafetyDataAccessor;
39966
+ var log9, SafetyDataAccessor;
39575
39967
  var init_safety_data_accessor = __esm({
39576
39968
  "packages/core/src/store/safety-data-accessor.ts"() {
39577
39969
  "use strict";
39578
39970
  init_logger2();
39579
39971
  init_data_safety_central();
39580
- log7 = getLogger("data-safety");
39972
+ log9 = getLogger("data-safety");
39581
39973
  SafetyDataAccessor = class {
39582
39974
  /** The underlying accessor being wrapped. */
39583
39975
  inner;
@@ -39601,7 +39993,7 @@ var init_safety_data_accessor = __esm({
39601
39993
  ...config
39602
39994
  };
39603
39995
  if (this.config.verbose) {
39604
- log7.debug({ engine: inner.engine }, "SafetyDataAccessor initialized");
39996
+ log9.debug({ engine: inner.engine }, "SafetyDataAccessor initialized");
39605
39997
  }
39606
39998
  }
39607
39999
  /** The storage engine backing this accessor. */
@@ -39613,7 +40005,7 @@ var init_safety_data_accessor = __esm({
39613
40005
  */
39614
40006
  logVerbose(message) {
39615
40007
  if (this.config.verbose) {
39616
- log7.debug(message);
40008
+ log9.debug(message);
39617
40009
  }
39618
40010
  }
39619
40011
  /**