@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
@@ -11331,6 +11331,87 @@ var init_nexus_scope_map = __esm({
11331
11331
  }
11332
11332
  });
11333
11333
 
11334
+ // packages/contracts/src/operations/output-contracts-data.ts
11335
+ var TASK_MUTATION_DATA_SCHEMA, tasksAddOutputContract, tasksAddBatchOutputContract, tasksUpdateOutputContract, tasksCompleteOutputContract;
11336
+ var init_output_contracts_data = __esm({
11337
+ "packages/contracts/src/operations/output-contracts-data.ts"() {
11338
+ "use strict";
11339
+ TASK_MUTATION_DATA_SCHEMA = {
11340
+ type: "object",
11341
+ required: ["count", "created", "updated", "deleted"],
11342
+ additionalProperties: true,
11343
+ properties: {
11344
+ count: { type: "number", description: "Number of records the mutation affected." },
11345
+ created: {
11346
+ type: "array",
11347
+ description: 'Task IDs created by the mutation (bare strings, e.g. "T11692"). Empty for update/delete-only mutations.',
11348
+ items: { type: "string" }
11349
+ },
11350
+ updated: {
11351
+ type: "array",
11352
+ description: "Task IDs updated by the mutation (bare strings). Empty for create/delete-only mutations.",
11353
+ items: { type: "string" }
11354
+ },
11355
+ deleted: {
11356
+ type: "array",
11357
+ description: "Task IDs deleted by the mutation (bare strings). Empty for create/update-only mutations.",
11358
+ items: { type: "string" }
11359
+ },
11360
+ ids: {
11361
+ type: "array",
11362
+ description: "Deprecated alias for the non-empty bucket. Prefer created/updated/deleted.",
11363
+ items: { type: "string" }
11364
+ },
11365
+ dryRun: { type: "boolean", description: "True when this was a preview-only mutation." },
11366
+ status: { type: "string", description: "Post-mutation task status (add/update/complete)." }
11367
+ }
11368
+ };
11369
+ tasksAddOutputContract = {
11370
+ operation: "tasks.add",
11371
+ shapeNote: 'The created task ID (bare string) is at /data/created/0 \u2014 NOT /data/created/0/id. Example: /data/created/0 \u2192 "T11692".',
11372
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11373
+ fieldPointers: ["/data/created/0", "/data/count"]
11374
+ };
11375
+ tasksAddBatchOutputContract = {
11376
+ operation: "tasks.add-batch",
11377
+ 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.",
11378
+ dataSchema: {
11379
+ type: "object",
11380
+ required: ["count", "created", "updated", "deleted"],
11381
+ additionalProperties: true,
11382
+ properties: {
11383
+ ...TASK_MUTATION_DATA_SCHEMA.properties,
11384
+ wouldCreate: {
11385
+ type: "number",
11386
+ description: "Dry-run: predicted write count. Present only when dryRun=true."
11387
+ },
11388
+ insertedCount: {
11389
+ type: "number",
11390
+ description: "Dry-run: always 0 (no DB write). Present only when dryRun=true."
11391
+ },
11392
+ wouldAffect: {
11393
+ type: "number",
11394
+ description: "Dry-run: generic affected count. Present only when dryRun=true."
11395
+ }
11396
+ }
11397
+ },
11398
+ fieldPointers: ["/data/created/0", "/data/count", "/data/wouldCreate", "/data/insertedCount"]
11399
+ };
11400
+ tasksUpdateOutputContract = {
11401
+ operation: "tasks.update",
11402
+ 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.",
11403
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11404
+ fieldPointers: ["/data/updated/0", "/data/status", "/data/count"]
11405
+ };
11406
+ tasksCompleteOutputContract = {
11407
+ operation: "tasks.complete",
11408
+ 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.",
11409
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
11410
+ fieldPointers: ["/data/updated/0", "/data/status", "/data/count"]
11411
+ };
11412
+ }
11413
+ });
11414
+
11334
11415
  // packages/contracts/src/peer.ts
11335
11416
  var init_peer = __esm({
11336
11417
  "packages/contracts/src/peer.ts"() {
@@ -12591,6 +12672,7 @@ var init_src = __esm({
12591
12672
  init_docs();
12592
12673
  init_operations();
12593
12674
  init_nexus_scope_map();
12675
+ init_output_contracts_data();
12594
12676
  init_params();
12595
12677
  init_tasks();
12596
12678
  init_peer();
@@ -22560,8 +22642,8 @@ function probeAndMarkApplied(nativeDb, migration, logSubsystem) {
22560
22642
  });
22561
22643
  if (allAltersPresent && allTablesPresent && allIndexesPresent && allTriggersPresent) {
22562
22644
  insertJournalEntry(nativeDb, migration.hash, migration.folderMillis, migration.name ?? "");
22563
- const log5 = getLogger(logSubsystem);
22564
- log5.debug(
22645
+ const log7 = getLogger(logSubsystem);
22646
+ log7.debug(
22565
22647
  {
22566
22648
  migration: migration.name,
22567
22649
  alters: alterTargets.length,
@@ -22601,14 +22683,14 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22601
22683
  if (hasOrphanedEntries) {
22602
22684
  const dbHashes = new Set(dbEntries.map((e) => e.hash));
22603
22685
  const allLocalHashesPresentInDb = localMigrations.every((m) => dbHashes.has(m.hash));
22604
- const log5 = getLogger(logSubsystem);
22686
+ const log7 = getLogger(logSubsystem);
22605
22687
  if (allLocalHashesPresentInDb) {
22606
- log5.debug(
22688
+ log7.debug(
22607
22689
  { extra: orphanedEntries.length },
22608
22690
  `Migration journal has ${orphanedEntries.length} entries for migrations not known to this install (DB is ahead). Skipping reconciliation.`
22609
22691
  );
22610
22692
  } else {
22611
- log5.warn(
22693
+ log7.warn(
22612
22694
  { orphaned: orphanedEntries.length },
22613
22695
  `Detected ${orphanedEntries.length} true-orphan journal entries from a previous CLEO lineage. Reconciling via DDL probe.`
22614
22696
  );
@@ -22644,8 +22726,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22644
22726
  if (alterMatches.length === 0) {
22645
22727
  const stripped = fullSql.replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
22646
22728
  if (stripped === "") {
22647
- const log5 = getLogger(logSubsystem);
22648
- log5.debug(
22729
+ const log7 = getLogger(logSubsystem);
22730
+ log7.debug(
22649
22731
  { migration: migration.name },
22650
22732
  `Migration ${migration.name} is a comment-only baseline marker \u2014 marking applied on existing DB.`
22651
22733
  );
@@ -22682,8 +22764,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22682
22764
  }
22683
22765
  }
22684
22766
  if (missingColumns.length === 0) {
22685
- const log5 = getLogger(logSubsystem);
22686
- log5.warn(
22767
+ const log7 = getLogger(logSubsystem);
22768
+ log7.warn(
22687
22769
  { migration: migration.name, columns: alterMatches },
22688
22770
  `Detected partially-applied migration ${migration.name} \u2014 columns exist but journal entry missing. Auto-reconciling.`
22689
22771
  );
@@ -22691,8 +22773,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22691
22773
  continue;
22692
22774
  }
22693
22775
  if (existingColumns.length > 0 && missingColumns.length > 0) {
22694
- const log5 = getLogger(logSubsystem);
22695
- log5.warn(
22776
+ const log7 = getLogger(logSubsystem);
22777
+ log7.warn(
22696
22778
  {
22697
22779
  migration: migration.name,
22698
22780
  existingColumns: existingColumns.map((c) => `${c.table}.${c.column}`),
@@ -22704,12 +22786,12 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22704
22786
  if (!tableExists(nativeDb, table)) continue;
22705
22787
  try {
22706
22788
  nativeDb.exec(`ALTER TABLE ${table} ADD COLUMN ${column}${ddl ? ` ${ddl}` : ""}`);
22707
- log5.warn(
22789
+ log7.warn(
22708
22790
  { migration: migration.name, table, column },
22709
22791
  `T920: Added missing column ${table}.${column} to complete partial migration.`
22710
22792
  );
22711
22793
  } catch {
22712
- log5.warn(
22794
+ log7.warn(
22713
22795
  { migration: migration.name, table, column },
22714
22796
  `T920: Could not add missing column ${table}.${column} \u2014 will let Drizzle migrate() handle it.`
22715
22797
  );
@@ -22729,8 +22811,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
22729
22811
  for (const entry of unnamedEntries) {
22730
22812
  const migrationName = hashToName.get(entry.hash);
22731
22813
  if (!migrationName) continue;
22732
- const log5 = getLogger(logSubsystem);
22733
- log5.debug(
22814
+ const log7 = getLogger(logSubsystem);
22815
+ log7.debug(
22734
22816
  { id: entry.id, hash: entry.hash, name: migrationName },
22735
22817
  `Backfilling missing name on journal entry id=${entry.id} (Drizzle v1 beta legacy compat).`
22736
22818
  );
@@ -22805,13 +22887,13 @@ function ensureColumns(nativeDb, tableName, requiredColumns, logSubsystem, conte
22805
22887
  const existingCols = new Set(columns.map((c) => c.name));
22806
22888
  for (const req of requiredColumns) {
22807
22889
  if (!existingCols.has(req.name)) {
22808
- const log5 = getLogger(logSubsystem);
22890
+ const log7 = getLogger(logSubsystem);
22809
22891
  const message = `Adding missing column ${tableName}.${req.name} via ALTER TABLE`;
22810
22892
  const fields = { column: req.name, context };
22811
22893
  if (context === "fresh") {
22812
- log5.error(fields, `${message} \u2014 MIGRATION DEFECT (fresh DB should not need repair)`);
22894
+ log7.error(fields, `${message} \u2014 MIGRATION DEFECT (fresh DB should not need repair)`);
22813
22895
  } else {
22814
- log5.warn(fields, message);
22896
+ log7.warn(fields, message);
22815
22897
  }
22816
22898
  nativeDb.exec(`ALTER TABLE ${tableName} ADD COLUMN ${req.name} ${req.ddl}`);
22817
22899
  }
@@ -31436,162 +31518,6 @@ var init_open_cleo_db = __esm({
31436
31518
  }
31437
31519
  });
31438
31520
 
31439
- // packages/core/src/store/exodus/column-transforms.ts
31440
- function typeDefaultLiteral(colType) {
31441
- const upper = colType.toUpperCase();
31442
- if (upper.includes("INT")) return "0";
31443
- if (upper.includes("REAL") || upper.includes("FLOAT") || upper.includes("DOUBLE")) return "0.0";
31444
- if (upper.includes("BLOB")) return "x''";
31445
- return "''";
31446
- }
31447
- function enumNormExpr(targetTableName, col, srcRef) {
31448
- const key = `${targetTableName}.${col}`;
31449
- const fn = ENUM_NORMALIZATIONS.get(key);
31450
- return fn ? fn(srcRef) : null;
31451
- }
31452
- function numericClampExpr(targetTableName, col, srcRef) {
31453
- const key = `${targetTableName}.${col}`;
31454
- const fn = NUMERIC_CLAMPS.get(key);
31455
- return fn ? fn(srcRef) : null;
31456
- }
31457
- function buildEpochToIsoExpr(srcRef) {
31458
- 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`;
31459
- }
31460
- function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
31461
- const escapedTable = tableName.replace(/'/g, "''");
31462
- const row = db.prepare(
31463
- `SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`
31464
- ).get();
31465
- if (!row?.sql) return /* @__PURE__ */ new Set();
31466
- const isoColumns = /* @__PURE__ */ new Set();
31467
- ISO_CHECK_REGEX.lastIndex = 0;
31468
- for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) {
31469
- isoColumns.add(match[1]);
31470
- }
31471
- return isoColumns;
31472
- }
31473
- function isIntegerSourceType(srcType) {
31474
- const upper = srcType.toUpperCase();
31475
- return upper.includes("INT") || upper === "" || upper === "NUMERIC";
31476
- }
31477
- function buildDigestExpr(targetTableName, col, srcType, isoGlobCols) {
31478
- const srcRef = `"${col}"`;
31479
- if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) {
31480
- return buildEpochToIsoExpr(srcRef);
31481
- }
31482
- const clampExpr = numericClampExpr(targetTableName, col, srcRef);
31483
- if (clampExpr !== null) return clampExpr;
31484
- const normExpr = enumNormExpr(targetTableName, col, srcRef);
31485
- if (normExpr !== null) return normExpr;
31486
- return srcRef;
31487
- }
31488
- var ENUM_NORMALIZATIONS, NUMERIC_CLAMPS, ISO_CHECK_REGEX, EPOCH_SECONDS_THRESHOLD;
31489
- var init_column_transforms = __esm({
31490
- "packages/core/src/store/exodus/column-transforms.ts"() {
31491
- "use strict";
31492
- ENUM_NORMALIZATIONS = /* @__PURE__ */ new Map([
31493
- // --- task_commits.link_source -------------------------------------------
31494
- // 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
31495
- [
31496
- "tasks_task_commits.link_source",
31497
- (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`
31498
- ],
31499
- // --- architecture_decisions.status (case + date-suffix normalization) ----
31500
- // 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
31501
- // 'Proposed', 'PROPOSED' → 'proposed'
31502
- // 'Superseded', 'SUPERSEDED' → 'superseded'
31503
- [
31504
- "tasks_architecture_decisions.status",
31505
- (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`
31506
- ],
31507
- // --- brain_* enum normalizations REMOVED (T11647) -----------------------
31508
- // The brain memory family now lands in the consolidated cleo.db in its LEGACY
31509
- // RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
31510
- // constraints (the `text({ enum })` unions are enforced only at the
31511
- // application layer, exactly as the runtime `drizzle-brain` tables are). With
31512
- // no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
31513
- // value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
31514
- // 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
31515
- // nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
31516
- // The previous brain entries (brain_observations.{source_type,type},
31517
- // brain_decisions.{confirmation_state,decision_category,confidence,outcome,
31518
- // decided_by}) are therefore deleted. The non-brain entries below still apply
31519
- // because those consolidated tables retain their CHECK constraints.
31520
- // --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
31521
- // NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
31522
- // requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
31523
- // to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
31524
- // 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
31525
- // coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
31526
- // of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
31527
- // (brain_decisions.{decision_category,confidence} normalizations removed —
31528
- // T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
31529
- // --- tasks_commits.conventional_type (T11548 + T11578) -------------------
31530
- // The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
31531
- // perf/revert/breaking. Real git history carries non-conventional subjects:
31532
- // - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
31533
- // - 'merge'/'release' → 'chore' (T11578): merge + release commits are
31534
- // maintenance-class; the precise semantic is preserved by the dedicated
31535
- // `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
31536
- // `conventional_type` to the maintenance catch-all 'chore' is lossless at
31537
- // the row grain. Without this the 'merge'/'release' rows violate the CHECK,
31538
- // `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
31539
- // data-continuity gate aborts the cutover (T11578 CI regression).
31540
- // - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
31541
- // subjects must never re-break the zero-deficit gate; the boolean flags and
31542
- // raw subject text remain the precise provenance).
31543
- [
31544
- "tasks_commits.conventional_type",
31545
- (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`
31546
- ],
31547
- // --- tasks_task_relations.relation_type (T11548) -------------------------
31548
- // 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
31549
- // supersedes/groups). 4 rows.
31550
- [
31551
- "tasks_task_relations.relation_type",
31552
- (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`
31553
- ],
31554
- // --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
31555
- // Legacy camelCase / past-tense values → canonical snake_case stage names.
31556
- // 'implemented' → 'implementation', 'qaPassed' → 'validation',
31557
- // 'testsPassed' → 'testing'. 3 rows.
31558
- [
31559
- "tasks_lifecycle_stages.stage_name",
31560
- (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`
31561
- ],
31562
- // --- tasks_architecture_decisions.gate_status (T11548) ------------------
31563
- // 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
31564
- // (enum: pending/passed/failed/waived). 2 rows.
31565
- [
31566
- "tasks_architecture_decisions.gate_status",
31567
- (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`
31568
- ],
31569
- // --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
31570
- // Values with a 'validator:...' prefix → 'direct'
31571
- // (enum: direct/satisfies/coverage). 3 rows.
31572
- // Strip the namespace prefix introduced before the enum was tightened.
31573
- [
31574
- "tasks_evidence_ac_bindings.binding_type",
31575
- (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`
31576
- ]
31577
- // (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
31578
- // brain target = runtime shape with no CHECK; legacy values like 'accepted',
31579
- // 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
31580
- ]);
31581
- NUMERIC_CLAMPS = /* @__PURE__ */ new Map([
31582
- // --- brain_weight_history.delta_weight (T11782) -------------------------
31583
- // +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
31584
- // depression), NaN → 0.0 (no-op delta). Finite values pass through.
31585
- [
31586
- "brain_weight_history.delta_weight",
31587
- (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`
31588
- ]
31589
- ]);
31590
- ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
31591
- EPOCH_SECONDS_THRESHOLD = 1e11;
31592
- }
31593
- });
31594
-
31595
31521
  // packages/core/src/store/exodus/table-name-map.ts
31596
31522
  function resolveTableTargetScope(sourceName, legacyTable, sourceScope) {
31597
31523
  if (inferSourceKind(sourceName) === "nexus" && NEXUS_GRAPH_PROJECT_TABLES.has(legacyTable)) {
@@ -31920,6 +31846,475 @@ var init_table_name_map = __esm({
31920
31846
  }
31921
31847
  });
31922
31848
 
31849
+ // packages/core/src/store/exodus/count-parity.ts
31850
+ import { existsSync as existsSync4 } from "node:fs";
31851
+ function listTables(db) {
31852
+ return db.prepare(
31853
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
31854
+ ).all().map((r) => r.name);
31855
+ }
31856
+ function tableExists2(db, tableName) {
31857
+ const escaped = tableName.replace(/'/g, "''");
31858
+ return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
31859
+ }
31860
+ function rowCount(db, tableName) {
31861
+ try {
31862
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
31863
+ return Number(row?.c ?? 0);
31864
+ } catch {
31865
+ return null;
31866
+ }
31867
+ }
31868
+ function computeCountParity(sources, projectDbPath, globalDbPath) {
31869
+ const entries = [];
31870
+ let skipped = 0;
31871
+ if (!existsSync4(projectDbPath) || !existsSync4(globalDbPath)) {
31872
+ return { ok: false, entries: [], deficits: [], checked: 0, skipped: 0 };
31873
+ }
31874
+ const projectSnap = openCleoDbSnapshot(projectDbPath, { readOnly: true });
31875
+ const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
31876
+ try {
31877
+ for (const src of sources) {
31878
+ if (!existsSync4(src.path)) continue;
31879
+ const srcSnap = openCleoDbSnapshot(src.path, { readOnly: true });
31880
+ try {
31881
+ for (const legacyTable of listTables(srcSnap.db)) {
31882
+ const resolution = resolveConsolidatedTableName(src.name, legacyTable);
31883
+ if (resolution.kind === "skip") {
31884
+ skipped++;
31885
+ continue;
31886
+ }
31887
+ const targetTable = resolution.targetName;
31888
+ const scope = resolveTableTargetScope(src.name, legacyTable, src.targetScope);
31889
+ const targetSnap = scope === "project" ? projectSnap : globalSnap;
31890
+ const sourceCount = rowCount(srcSnap.db, legacyTable);
31891
+ if (sourceCount === null) {
31892
+ skipped++;
31893
+ continue;
31894
+ }
31895
+ const targetCount = tableExists2(targetSnap.db, targetTable) ? rowCount(targetSnap.db, targetTable) ?? 0 : 0;
31896
+ const deficit = targetCount < sourceCount ? sourceCount - targetCount : 0;
31897
+ entries.push({
31898
+ sourceDb: src.name,
31899
+ sourceTable: legacyTable,
31900
+ targetTable,
31901
+ scope,
31902
+ sourceCount,
31903
+ targetCount,
31904
+ deficit
31905
+ });
31906
+ }
31907
+ } finally {
31908
+ srcSnap.close();
31909
+ }
31910
+ }
31911
+ } finally {
31912
+ projectSnap.close();
31913
+ globalSnap.close();
31914
+ }
31915
+ const deficits = entries.filter((e) => e.deficit > 0);
31916
+ if (deficits.length > 0) {
31917
+ log2.warn(
31918
+ { deficitCount: deficits.length, sample: deficits.slice(0, 5) },
31919
+ `exodus count-parity: ${deficits.length} table(s) have FEWER rows in the consolidated target than the legacy source`
31920
+ );
31921
+ }
31922
+ return { ok: deficits.length === 0, entries, deficits, checked: entries.length, skipped };
31923
+ }
31924
+ var log2;
31925
+ var init_count_parity = __esm({
31926
+ "packages/core/src/store/exodus/count-parity.ts"() {
31927
+ "use strict";
31928
+ init_logger2();
31929
+ init_open_cleo_db();
31930
+ init_table_name_map();
31931
+ log2 = getLogger("exodus-count-parity");
31932
+ }
31933
+ });
31934
+
31935
+ // packages/core/src/store/exodus/plan.ts
31936
+ import { existsSync as existsSync5, readdirSync as readdirSync2, statfsSync, statSync } from "node:fs";
31937
+ import { join as join7 } from "node:path";
31938
+ function computeRequiredBytes(totalSourceBytes, largestSourceBytes) {
31939
+ return Math.ceil(STAGING_HEADROOM_FACTOR * largestSourceBytes) + totalSourceBytes;
31940
+ }
31941
+ function buildSourceDescriptors(cwd) {
31942
+ const cleoDir = resolveCleoDir(cwd);
31943
+ const cleoHome = getCleoHome();
31944
+ return [
31945
+ // Project-tier — go into consolidated project-scope cleo.db
31946
+ {
31947
+ name: "tasks",
31948
+ path: join7(cleoDir, "tasks.db"),
31949
+ targetScope: "project"
31950
+ },
31951
+ {
31952
+ name: "brain (project)",
31953
+ path: join7(cleoDir, "brain.db"),
31954
+ targetScope: "project"
31955
+ },
31956
+ {
31957
+ name: "conduit",
31958
+ path: join7(cleoDir, "conduit.db"),
31959
+ targetScope: "project"
31960
+ },
31961
+ // Global-tier — go into consolidated global-scope cleo.db
31962
+ {
31963
+ name: "nexus",
31964
+ path: join7(cleoHome, "nexus.db"),
31965
+ targetScope: "global"
31966
+ },
31967
+ {
31968
+ name: "signaldock",
31969
+ path: join7(cleoHome, "signaldock.db"),
31970
+ targetScope: "global"
31971
+ },
31972
+ {
31973
+ name: "skills",
31974
+ path: join7(cleoHome, "skills.db"),
31975
+ targetScope: "global"
31976
+ }
31977
+ ];
31978
+ }
31979
+ function safeFileBytes(filePath) {
31980
+ try {
31981
+ return statSync(filePath).size;
31982
+ } catch {
31983
+ return 0;
31984
+ }
31985
+ }
31986
+ function getAvailableBytes(dir) {
31987
+ try {
31988
+ const result = statfsSync(dir);
31989
+ return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
31990
+ } catch {
31991
+ return 0;
31992
+ }
31993
+ }
31994
+ function deriveStagingDirName() {
31995
+ const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z");
31996
+ return `exodus-staging-${iso}`;
31997
+ }
31998
+ function findExistingStaging(cleoDir) {
31999
+ try {
32000
+ const entries = readdirSync2(cleoDir, { withFileTypes: true });
32001
+ const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
32002
+ if (stagingDirs.length > 0) {
32003
+ return join7(cleoDir, stagingDirs[0]);
32004
+ }
32005
+ } catch {
32006
+ }
32007
+ return null;
32008
+ }
32009
+ function buildExodusPlan(cwd) {
32010
+ const cleoDir = resolveCleoDir(cwd);
32011
+ const sources = buildSourceDescriptors(cwd);
32012
+ const sourceBytes = sources.map((s) => safeFileBytes(s.path));
32013
+ const totalSourceBytes = sourceBytes.reduce((sum, b) => sum + b, 0);
32014
+ const largestSourceBytes = sourceBytes.reduce((max, b) => Math.max(max, b), 0);
32015
+ const requiredBytes = computeRequiredBytes(totalSourceBytes, largestSourceBytes);
32016
+ const availableBytes = getAvailableBytes(cleoDir);
32017
+ const diskPreflight = totalSourceBytes === 0 || availableBytes >= requiredBytes;
32018
+ const existingStaging = findExistingStaging(cleoDir);
32019
+ const stagingDir = existingStaging ?? join7(cleoDir, deriveStagingDirName());
32020
+ const resumeFromStaging = existingStaging !== null;
32021
+ const projectDbPath = resolveDualScopeDbPath("project", cwd);
32022
+ const globalDbPath = resolveDualScopeDbPath("global");
32023
+ return {
32024
+ sources,
32025
+ totalSourceBytes,
32026
+ largestSourceBytes,
32027
+ requiredBytes,
32028
+ availableBytes,
32029
+ diskPreflight,
32030
+ stagingCopyThresholdBytes: STAGING_COPY_SKIP_THRESHOLD_BYTES,
32031
+ stagingDir,
32032
+ resumeFromStaging,
32033
+ projectDbPath,
32034
+ globalDbPath
32035
+ };
32036
+ }
32037
+ function sourcesPresent(sources) {
32038
+ return sources.some((s) => existsSync5(s.path));
32039
+ }
32040
+ var STAGING_HEADROOM_FACTOR, STAGING_COPY_SKIP_THRESHOLD_BYTES;
32041
+ var init_plan2 = __esm({
32042
+ "packages/core/src/store/exodus/plan.ts"() {
32043
+ "use strict";
32044
+ init_paths();
32045
+ init_dual_scope_db();
32046
+ STAGING_HEADROOM_FACTOR = 1.2;
32047
+ STAGING_COPY_SKIP_THRESHOLD_BYTES = 256 * 1024 * 1024;
32048
+ }
32049
+ });
32050
+
32051
+ // packages/core/src/store/exodus/health.ts
32052
+ import { existsSync as existsSync6, statSync as statSync2 } from "node:fs";
32053
+ function buildExodusHealth(cwd) {
32054
+ const plan = buildExodusPlan(cwd);
32055
+ const anyLegacyPresent = plan.sources.some((s) => existsSync6(s.path));
32056
+ const parity = anyLegacyPresent ? computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath) : { ok: true, entries: [], deficits: [], checked: 0, skipped: 0 };
32057
+ const consolidatedExistsByScope = {
32058
+ project: existsSync6(plan.projectDbPath),
32059
+ global: existsSync6(plan.globalDbPath)
32060
+ };
32061
+ const buildScope = (scope) => {
32062
+ const sources = plan.sources.filter((s) => s.targetScope === scope).map((s) => {
32063
+ const present = existsSync6(s.path);
32064
+ let bytes = 0;
32065
+ if (present) {
32066
+ try {
32067
+ bytes = statSync2(s.path).size;
32068
+ } catch {
32069
+ bytes = 0;
32070
+ }
32071
+ }
32072
+ return { name: s.name, path: s.path, present, bytes, large: bytes >= LARGE_DB_BYTES };
32073
+ });
32074
+ const markerPresent = hasExodusCompleteMarker(scope, cwd);
32075
+ const legacyPresent = sources.some((s) => s.present);
32076
+ const consolidatedExists = consolidatedExistsByScope[scope];
32077
+ const scopeEntries = parity.entries.filter((e) => e.scope === scope);
32078
+ const scopeHasDeficit = scopeEntries.some((e) => e.deficit > 0);
32079
+ const scopeHasData = scopeEntries.some((e) => e.targetCount > 0);
32080
+ let state;
32081
+ if (markerPresent) {
32082
+ state = "sealed";
32083
+ } else if (legacyPresent && consolidatedExists && scopeHasData && !scopeHasDeficit) {
32084
+ state = "migrated-unsealed";
32085
+ } else if (legacyPresent) {
32086
+ state = "needs-migration";
32087
+ } else {
32088
+ state = "no-cleo-data";
32089
+ }
32090
+ const stranded = sources.filter((s) => s.present && markerPresent).map((s) => s.name);
32091
+ return {
32092
+ scope,
32093
+ state,
32094
+ consolidatedExists,
32095
+ markerPresent,
32096
+ legacySources: sources,
32097
+ strandedResidue: stranded
32098
+ };
32099
+ };
32100
+ const project = buildScope("project");
32101
+ const global = buildScope("global");
32102
+ const largeLegacyDbs = [
32103
+ ...project.legacySources.filter((s) => s.large).map((s) => ({ name: s.name, scope: "project", bytes: s.bytes })),
32104
+ ...global.legacySources.filter((s) => s.large).map((s) => ({ name: s.name, scope: "global", bytes: s.bytes }))
32105
+ ];
32106
+ const recommendations = [];
32107
+ for (const sc of [project, global]) {
32108
+ if (sc.state === "migrated-unsealed") {
32109
+ recommendations.push(
32110
+ `${sc.scope}: data is consolidated but unsealed \u2014 run \`cleo exodus seal --scope ${sc.scope}\` to archive legacy DBs + stop on-open re-firing.`
32111
+ );
32112
+ } else if (sc.state === "needs-migration") {
32113
+ recommendations.push(
32114
+ `${sc.scope}: legacy data not yet consolidated \u2014 run \`cleo exodus migrate --scope ${sc.scope}\`.`
32115
+ );
32116
+ } else if (sc.strandedResidue.length > 0) {
32117
+ recommendations.push(
32118
+ `${sc.scope}: ${sc.strandedResidue.length} stranded legacy DB(s) after a sealed cutover \u2014 run \`cleo doctor exodus-residue --fix\`.`
32119
+ );
32120
+ }
32121
+ }
32122
+ if (largeLegacyDbs.length > 0) {
32123
+ recommendations.push(
32124
+ `${largeLegacyDbs.length} large legacy DB(s) (\u2265500 MB) \u2014 migrate these only with the streamed-verify build (T11834) to avoid the verify OOM.`
32125
+ );
32126
+ }
32127
+ if (!parity.ok) {
32128
+ recommendations.push(
32129
+ `${parity.deficits.length} table(s) show a row deficit in cleo.db \u2014 DO NOT seal; run \`cleo exodus migrate\` first.`
32130
+ );
32131
+ }
32132
+ return {
32133
+ project,
32134
+ global,
32135
+ diskHeadroomOk: plan.diskPreflight,
32136
+ availableBytes: plan.availableBytes,
32137
+ requiredBytes: 3 * plan.totalSourceBytes,
32138
+ killSwitchSet: process.env.CLEO_DISABLE_EXODUS_ON_OPEN === "1",
32139
+ dataParityOk: parity.ok,
32140
+ dataDeficits: parity.deficits.length,
32141
+ largeLegacyDbs,
32142
+ recommendations
32143
+ };
32144
+ }
32145
+ var LARGE_DB_BYTES;
32146
+ var init_health2 = __esm({
32147
+ "packages/core/src/store/exodus/health.ts"() {
32148
+ "use strict";
32149
+ init_archive2();
32150
+ init_count_parity();
32151
+ init_plan2();
32152
+ LARGE_DB_BYTES = 500 * 1024 * 1024;
32153
+ }
32154
+ });
32155
+
32156
+ // packages/core/src/store/exodus/column-transforms.ts
32157
+ function typeDefaultLiteral(colType) {
32158
+ const upper = colType.toUpperCase();
32159
+ if (upper.includes("INT")) return "0";
32160
+ if (upper.includes("REAL") || upper.includes("FLOAT") || upper.includes("DOUBLE")) return "0.0";
32161
+ if (upper.includes("BLOB")) return "x''";
32162
+ return "''";
32163
+ }
32164
+ function enumNormExpr(targetTableName, col, srcRef) {
32165
+ const key = `${targetTableName}.${col}`;
32166
+ const fn = ENUM_NORMALIZATIONS.get(key);
32167
+ return fn ? fn(srcRef) : null;
32168
+ }
32169
+ function numericClampExpr(targetTableName, col, srcRef) {
32170
+ const key = `${targetTableName}.${col}`;
32171
+ const fn = NUMERIC_CLAMPS.get(key);
32172
+ return fn ? fn(srcRef) : null;
32173
+ }
32174
+ function buildEpochToIsoExpr(srcRef) {
32175
+ 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`;
32176
+ }
32177
+ function detectIsoGlobColumns(db, tableName, targetSchema = "main") {
32178
+ const escapedTable = tableName.replace(/'/g, "''");
32179
+ const row = db.prepare(
32180
+ `SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`
32181
+ ).get();
32182
+ if (!row?.sql) return /* @__PURE__ */ new Set();
32183
+ const isoColumns = /* @__PURE__ */ new Set();
32184
+ ISO_CHECK_REGEX.lastIndex = 0;
32185
+ for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) {
32186
+ isoColumns.add(match[1]);
32187
+ }
32188
+ return isoColumns;
32189
+ }
32190
+ function isIntegerSourceType(srcType) {
32191
+ const upper = srcType.toUpperCase();
32192
+ return upper.includes("INT") || upper === "" || upper === "NUMERIC";
32193
+ }
32194
+ function maybeCoalesceNotNull(expr, tgtCol) {
32195
+ if (tgtCol === void 0) return expr;
32196
+ const isNotNullWithoutDefault = tgtCol.notnull === 1 && tgtCol.dflt_value === null;
32197
+ if (!isNotNullWithoutDefault) return expr;
32198
+ return `COALESCE(${expr}, ${typeDefaultLiteral(tgtCol.type)})`;
32199
+ }
32200
+ function buildDigestExpr(targetTableName, col, srcType, isoGlobCols, tgtCol) {
32201
+ const srcRef = `"${col}"`;
32202
+ if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) {
32203
+ return maybeCoalesceNotNull(buildEpochToIsoExpr(srcRef), tgtCol);
32204
+ }
32205
+ const clampExpr = numericClampExpr(targetTableName, col, srcRef);
32206
+ if (clampExpr !== null) return maybeCoalesceNotNull(clampExpr, tgtCol);
32207
+ const normExpr = enumNormExpr(targetTableName, col, srcRef);
32208
+ if (normExpr !== null) return maybeCoalesceNotNull(normExpr, tgtCol);
32209
+ return maybeCoalesceNotNull(srcRef, tgtCol);
32210
+ }
32211
+ var ENUM_NORMALIZATIONS, NUMERIC_CLAMPS, ISO_CHECK_REGEX, EPOCH_SECONDS_THRESHOLD;
32212
+ var init_column_transforms = __esm({
32213
+ "packages/core/src/store/exodus/column-transforms.ts"() {
32214
+ "use strict";
32215
+ ENUM_NORMALIZATIONS = /* @__PURE__ */ new Map([
32216
+ // --- task_commits.link_source -------------------------------------------
32217
+ // 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
32218
+ [
32219
+ "tasks_task_commits.link_source",
32220
+ (src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`
32221
+ ],
32222
+ // --- architecture_decisions.status (case + date-suffix normalization) ----
32223
+ // 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
32224
+ // 'Proposed', 'PROPOSED' → 'proposed'
32225
+ // 'Superseded', 'SUPERSEDED' → 'superseded'
32226
+ [
32227
+ "tasks_architecture_decisions.status",
32228
+ (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`
32229
+ ],
32230
+ // --- brain_* enum normalizations REMOVED (T11647) -----------------------
32231
+ // The brain memory family now lands in the consolidated cleo.db in its LEGACY
32232
+ // RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
32233
+ // constraints (the `text({ enum })` unions are enforced only at the
32234
+ // application layer, exactly as the runtime `drizzle-brain` tables are). With
32235
+ // no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
32236
+ // value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
32237
+ // 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
32238
+ // nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
32239
+ // The previous brain entries (brain_observations.{source_type,type},
32240
+ // brain_decisions.{confirmation_state,decision_category,confidence,outcome,
32241
+ // decided_by}) are therefore deleted. The non-brain entries below still apply
32242
+ // because those consolidated tables retain their CHECK constraints.
32243
+ // --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
32244
+ // NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
32245
+ // requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
32246
+ // to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
32247
+ // 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
32248
+ // coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
32249
+ // of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
32250
+ // (brain_decisions.{decision_category,confidence} normalizations removed —
32251
+ // T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
32252
+ // --- tasks_commits.conventional_type (T11548 + T11578) -------------------
32253
+ // The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
32254
+ // perf/revert/breaking. Real git history carries non-conventional subjects:
32255
+ // - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
32256
+ // - 'merge'/'release' → 'chore' (T11578): merge + release commits are
32257
+ // maintenance-class; the precise semantic is preserved by the dedicated
32258
+ // `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
32259
+ // `conventional_type` to the maintenance catch-all 'chore' is lossless at
32260
+ // the row grain. Without this the 'merge'/'release' rows violate the CHECK,
32261
+ // `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
32262
+ // data-continuity gate aborts the cutover (T11578 CI regression).
32263
+ // - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
32264
+ // subjects must never re-break the zero-deficit gate; the boolean flags and
32265
+ // raw subject text remain the precise provenance).
32266
+ [
32267
+ "tasks_commits.conventional_type",
32268
+ (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`
32269
+ ],
32270
+ // --- tasks_task_relations.relation_type (T11548) -------------------------
32271
+ // 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
32272
+ // supersedes/groups). 4 rows.
32273
+ [
32274
+ "tasks_task_relations.relation_type",
32275
+ (src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`
32276
+ ],
32277
+ // --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
32278
+ // Legacy camelCase / past-tense values → canonical snake_case stage names.
32279
+ // 'implemented' → 'implementation', 'qaPassed' → 'validation',
32280
+ // 'testsPassed' → 'testing'. 3 rows.
32281
+ [
32282
+ "tasks_lifecycle_stages.stage_name",
32283
+ (src) => `CASE ${src} WHEN 'implemented' THEN 'implementation' WHEN 'qaPassed' THEN 'validation' WHEN 'testsPassed' THEN 'testing' ELSE ${src} END`
32284
+ ],
32285
+ // --- tasks_architecture_decisions.gate_status (T11548) ------------------
32286
+ // 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
32287
+ // (enum: pending/passed/failed/waived). 2 rows.
32288
+ [
32289
+ "tasks_architecture_decisions.gate_status",
32290
+ (src) => `CASE WHEN ${src} LIKE 'passed%' THEN 'passed' WHEN ${src} = 'approved' THEN 'passed' ELSE ${src} END`
32291
+ ],
32292
+ // --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
32293
+ // Values with a 'validator:...' prefix → 'direct'
32294
+ // (enum: direct/satisfies/coverage). 3 rows.
32295
+ // Strip the namespace prefix introduced before the enum was tightened.
32296
+ [
32297
+ "tasks_evidence_ac_bindings.binding_type",
32298
+ (src) => `CASE WHEN ${src} LIKE 'validator:%' THEN 'direct' ELSE ${src} END`
32299
+ ]
32300
+ // (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
32301
+ // brain target = runtime shape with no CHECK; legacy values like 'accepted',
32302
+ // 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
32303
+ ]);
32304
+ NUMERIC_CLAMPS = /* @__PURE__ */ new Map([
32305
+ // --- brain_weight_history.delta_weight (T11782) -------------------------
32306
+ // +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
32307
+ // depression), NaN → 0.0 (no-op delta). Finite values pass through.
32308
+ [
32309
+ "brain_weight_history.delta_weight",
32310
+ (src) => `CASE WHEN ${src} = 9e999 THEN 1.0 WHEN ${src} = -9e999 THEN -1.0 WHEN ${src} != ${src} THEN 0.0 ELSE ${src} END`
32311
+ ]
32312
+ ]);
32313
+ ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
32314
+ EPOCH_SECONDS_THRESHOLD = 1e11;
32315
+ }
32316
+ });
32317
+
31923
32318
  // packages/core/src/store/exodus/types.ts
31924
32319
  var EXODUS_TARGET_SCHEMA_VERSION;
31925
32320
  var init_types = __esm({
@@ -31932,14 +32327,15 @@ var init_types = __esm({
31932
32327
  // packages/core/src/store/exodus/migrate.ts
31933
32328
  import {
31934
32329
  copyFileSync as copyFileSync2,
31935
- existsSync as existsSync4,
32330
+ existsSync as existsSync7,
31936
32331
  mkdirSync as mkdirSync2,
31937
32332
  readFileSync as readFileSync2,
31938
32333
  renameSync as renameSync2,
32334
+ statSync as statSync3,
31939
32335
  unlinkSync as unlinkSync2,
31940
32336
  writeFileSync as writeFileSync2
31941
32337
  } from "node:fs";
31942
- import { join as join7 } from "node:path";
32338
+ import { join as join8 } from "node:path";
31943
32339
  function getSqliteVersion(db) {
31944
32340
  try {
31945
32341
  const row = db.prepare("SELECT sqlite_version() AS v").get();
@@ -31948,21 +32344,21 @@ function getSqliteVersion(db) {
31948
32344
  return "unknown";
31949
32345
  }
31950
32346
  }
31951
- function listTables(db) {
32347
+ function listTables2(db) {
31952
32348
  const rows = db.prepare(
31953
32349
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
31954
32350
  ).all();
31955
32351
  return rows.map((r) => r.name);
31956
32352
  }
31957
32353
  function writeJournal(stagingDir, journal) {
31958
- const journalPath = join7(stagingDir, JOURNAL_FILENAME);
32354
+ const journalPath = join8(stagingDir, JOURNAL_FILENAME);
31959
32355
  const tmpPath = `${journalPath}.tmp`;
31960
32356
  writeFileSync2(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
31961
32357
  renameSync2(tmpPath, journalPath);
31962
32358
  }
31963
32359
  function readJournal(stagingDir) {
31964
- const journalPath = join7(stagingDir, JOURNAL_FILENAME);
31965
- if (!existsSync4(journalPath)) return null;
32360
+ const journalPath = join8(stagingDir, JOURNAL_FILENAME);
32361
+ if (!existsSync7(journalPath)) return null;
31966
32362
  try {
31967
32363
  return JSON.parse(readFileSync2(journalPath, "utf8"));
31968
32364
  } catch {
@@ -31970,17 +32366,17 @@ function readJournal(stagingDir) {
31970
32366
  }
31971
32367
  }
31972
32368
  function clearExodusJournal(stagingDir) {
31973
- const journalPath = join7(stagingDir, JOURNAL_FILENAME);
32369
+ const journalPath = join8(stagingDir, JOURNAL_FILENAME);
31974
32370
  try {
31975
- if (!existsSync4(journalPath)) return false;
32371
+ if (!existsSync7(journalPath)) return false;
31976
32372
  unlinkSync2(journalPath);
31977
- log2.info(
32373
+ log3.info(
31978
32374
  { stagingDir },
31979
32375
  "exodus: cleared migrate journal after abort/rollback \u2014 a retry will RE-COPY all tables"
31980
32376
  );
31981
32377
  return true;
31982
32378
  } catch (err) {
31983
- log2.warn(
32379
+ log3.warn(
31984
32380
  { err, stagingDir },
31985
32381
  'exodus: failed to clear migrate journal after rollback (a retry may skip already-"done" tables)'
31986
32382
  );
@@ -32013,6 +32409,13 @@ function releaseAdvisoryLock(dbPath) {
32013
32409
  } catch {
32014
32410
  }
32015
32411
  }
32412
+ function safeStatBytes(filePath) {
32413
+ try {
32414
+ return statSync3(filePath).size;
32415
+ } catch {
32416
+ return 0;
32417
+ }
32418
+ }
32016
32419
  function makeAttachAlias(name15, index2) {
32017
32420
  const safe = name15.replace(/[^a-z0-9]/gi, "_").replace(/_+/g, "_").slice(0, 20);
32018
32421
  return `_src_${safe}_${index2}`;
@@ -32061,7 +32464,7 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
32061
32464
  function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyTableName, sourceName, targetSchema = "main") {
32062
32465
  const resolution = resolveConsolidatedTableName(sourceName, legacyTableName);
32063
32466
  if (resolution.kind === "skip") {
32064
- log2.warn(
32467
+ log3.warn(
32065
32468
  { legacyTableName, sourceName, reason: resolution.reason },
32066
32469
  `Exodus: explicitly skipping table \u2014 ${resolution.reason}`
32067
32470
  );
@@ -32080,7 +32483,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32080
32483
  ).get();
32081
32484
  if (!existsRow) {
32082
32485
  const reason = `consolidated target '${targetTableName}' not found (mapped from legacy '${legacyTableName}')`;
32083
- log2.warn(
32486
+ log3.warn(
32084
32487
  { legacyTableName, targetTableName, sourceName, attachAlias, targetSchema },
32085
32488
  `Exodus: ${reason}`
32086
32489
  );
@@ -32091,13 +32494,13 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32091
32494
  const sharedColumns = srcPragma.map((r) => r.name).filter((col) => tgtColMap.has(col));
32092
32495
  if (sharedColumns.length === 0) {
32093
32496
  const reason = `no overlapping columns between source '${legacyTableName}' and target '${targetTableName}'`;
32094
- log2.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32497
+ log3.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
32095
32498
  return { rowsCopied: 0, skipped: true, reason };
32096
32499
  }
32097
32500
  const srcOnlyColumns = srcPragma.map((r) => r.name).filter((c) => !tgtColMap.has(c));
32098
32501
  const tgtOnlyColumns = tgtPragma.map((r) => r.name).filter((c) => !srcColumns.has(c));
32099
32502
  if (srcOnlyColumns.length > 0 || tgtOnlyColumns.length > 0) {
32100
- log2.info(
32503
+ log3.info(
32101
32504
  { legacyTableName, targetTableName, sourceName, srcOnlyColumns, tgtOnlyColumns },
32102
32505
  "Exodus: column drift detected \u2014 copying intersection, dropping src-only cols, using defaults for tgt-only cols"
32103
32506
  );
@@ -32112,7 +32515,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32112
32515
  return isoGlobCols.has(col) && (upper.includes("INT") || upper === "" || upper === "NUMERIC");
32113
32516
  });
32114
32517
  if (coercedCols.length > 0) {
32115
- log2.info(
32518
+ log3.info(
32116
32519
  {
32117
32520
  legacyTableName,
32118
32521
  targetTableName,
@@ -32128,7 +32531,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32128
32531
  (col) => ENUM_NORMALIZATIONS.has(`${targetTableName}.${col}`)
32129
32532
  );
32130
32533
  if (normalizedCols.length > 0) {
32131
- log2.info(
32534
+ log3.info(
32132
32535
  { legacyTableName, targetTableName, sourceName, normalizedCols },
32133
32536
  `Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`
32134
32537
  );
@@ -32137,7 +32540,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32137
32540
  (col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`)
32138
32541
  );
32139
32542
  if (clampedCols.length > 0) {
32140
- log2.info(
32543
+ log3.info(
32141
32544
  { legacyTableName, targetTableName, sourceName, clampedCols },
32142
32545
  `Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`
32143
32546
  );
@@ -32169,25 +32572,37 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
32169
32572
  ];
32170
32573
  const colList = allInsertCols.map((c) => `"${c}"`).join(", ");
32171
32574
  const selectList = allSelectExprs.join(", ");
32575
+ const existingBeforeRow = targetNativeDb.prepare(`SELECT COUNT(*) AS c FROM "${targetSchema}"."${targetTableName}"`).get();
32576
+ const existingBefore = Number(existingBeforeRow?.c ?? 0);
32172
32577
  const stmt = targetNativeDb.prepare(
32173
32578
  `INSERT OR IGNORE INTO "${targetSchema}"."${targetTableName}" (${colList}) SELECT ${selectList} FROM "${attachAlias}"."${legacyTableName}"`
32174
32579
  );
32175
32580
  const result = stmt.run();
32176
32581
  const rowsCopied = result.changes ?? 0;
32177
32582
  if (rowsCopied < sourceCount) {
32178
- const dropped = sourceCount - rowsCopied;
32179
- if (rowsCopied === 0) {
32180
- 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.`;
32181
- log2.error(
32182
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied },
32183
- `Exodus: ${reason}`
32583
+ const presentAccountedFor = existingBefore + rowsCopied;
32584
+ if (presentAccountedFor >= sourceCount) {
32585
+ log3.info(
32586
+ { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, existingBefore },
32587
+ `Exodus: '${legacyTableName}'\u2192'${targetTableName}' \u2014 ${sourceCount - rowsCopied} of ${sourceCount} row(s) already present in target (idempotent PK dedup), ${rowsCopied} newly copied; no loss`
32184
32588
  );
32185
- return { rowsCopied: 0, skipped: false, reason };
32589
+ return { rowsCopied, skipped: false };
32186
32590
  }
32187
- log2.warn(
32188
- { legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, dropped },
32189
- `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`
32591
+ const missing = sourceCount - presentAccountedFor;
32592
+ 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.`;
32593
+ log3.error(
32594
+ {
32595
+ legacyTableName,
32596
+ targetTableName,
32597
+ sourceName,
32598
+ sourceCount,
32599
+ rowsCopied,
32600
+ existingBefore,
32601
+ missing
32602
+ },
32603
+ `Exodus: ${reason}`
32190
32604
  );
32605
+ return { rowsCopied, skipped: false, reason };
32191
32606
  }
32192
32607
  return { rowsCopied, skipped: false };
32193
32608
  }
@@ -32195,10 +32610,10 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32195
32610
  if (journal.targetSchemaVersion !== EXODUS_TARGET_SCHEMA_VERSION) {
32196
32611
  const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
32197
32612
  if (forceCrossVersion) {
32198
- log2.warn(msg + " (--force-cross-version: continuing anyway)");
32613
+ log3.warn(msg + " (--force-cross-version: continuing anyway)");
32199
32614
  return true;
32200
32615
  }
32201
- log2.error(msg + " \u2014 pass --force-cross-version to override");
32616
+ log3.error(msg + " \u2014 pass --force-cross-version to override");
32202
32617
  return false;
32203
32618
  }
32204
32619
  return true;
@@ -32206,18 +32621,19 @@ function checkSchemaVersion(journal, forceCrossVersion) {
32206
32621
  async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32207
32622
  const { sources, stagingDir, diskPreflight, projectDbPath, globalDbPath } = plan;
32208
32623
  if (!diskPreflight) {
32624
+ const shortfall = Math.max(0, plan.requiredBytes - plan.availableBytes);
32209
32625
  return {
32210
32626
  ok: false,
32211
32627
  tables: [],
32212
32628
  stagingDir,
32213
32629
  backupPaths: [],
32214
- 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.`
32630
+ 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.`
32215
32631
  };
32216
32632
  }
32217
32633
  mkdirSync2(stagingDir, { recursive: true });
32218
32634
  let sqliteVersion = "unknown";
32219
32635
  for (const src of sources) {
32220
- if (existsSync4(src.path)) {
32636
+ if (existsSync7(src.path)) {
32221
32637
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32222
32638
  sqliteVersion = getSqliteVersion(snap.db);
32223
32639
  snap.close();
@@ -32258,9 +32674,15 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32258
32674
  };
32259
32675
  var extractNativeDb = extractNativeDb2;
32260
32676
  for (const src of sources) {
32261
- if (!existsSync4(src.path)) continue;
32262
- const backupDest = join7(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
32263
- if (!existsSync4(backupDest)) {
32677
+ if (!existsSync7(src.path)) continue;
32678
+ const backupDest = join8(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
32679
+ const srcBytes = safeStatBytes(src.path);
32680
+ const skipStagingCopy = srcBytes > plan.stagingCopyThresholdBytes;
32681
+ if (skipStagingCopy) {
32682
+ onProgress?.(
32683
+ `Skipping full staging copy of ${src.name} (${srcBytes} bytes > ${plan.stagingCopyThresholdBytes} threshold) \u2014 source is archived, not deleted, on success.`
32684
+ );
32685
+ } else if (!existsSync7(backupDest)) {
32264
32686
  onProgress?.(`Backing up ${src.name} \u2192 staging dir\u2026`);
32265
32687
  copyFileSync2(src.path, backupDest);
32266
32688
  backupPaths.push(backupDest);
@@ -32278,8 +32700,8 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32278
32700
  });
32279
32701
  const projectNative = extractNativeDb2(projectHandle);
32280
32702
  const globalNative = extractNativeDb2(globalHandle);
32281
- const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync4(s.path));
32282
- const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync4(s.path));
32703
+ const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync7(s.path));
32704
+ const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync7(s.path));
32283
32705
  await migrateScope(
32284
32706
  "project",
32285
32707
  projectSources,
@@ -32304,7 +32726,7 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
32304
32726
  return { ok: true, tables: allTableResults, stagingDir, backupPaths };
32305
32727
  } catch (err) {
32306
32728
  const error48 = err instanceof Error ? err.message : String(err);
32307
- log2.error({ err }, "Exodus migration failed");
32729
+ log3.error({ err }, "Exodus migration failed");
32308
32730
  return { ok: false, tables: allTableResults, stagingDir, backupPaths, error: error48 };
32309
32731
  } finally {
32310
32732
  try {
@@ -32324,7 +32746,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32324
32746
  if (sources.length === 0) return;
32325
32747
  onProgress?.(`Migrating ${scope}-scope sources\u2026`);
32326
32748
  targetNativeDb.exec("PRAGMA foreign_keys = OFF");
32327
- log2.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
32749
+ log3.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
32328
32750
  try {
32329
32751
  for (let i = 0; i < sources.length; i++) {
32330
32752
  const src = sources[i];
@@ -32341,7 +32763,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32341
32763
  }
32342
32764
  const snap = openCleoDbSnapshot(src.path, { readOnly: true });
32343
32765
  try {
32344
- const tables = listTables(snap.db);
32766
+ const tables = listTables2(snap.db);
32345
32767
  targetNativeDb.exec("BEGIN");
32346
32768
  let txOpen = true;
32347
32769
  try {
@@ -32391,7 +32813,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32391
32813
  }
32392
32814
  } catch (err) {
32393
32815
  const msg = err instanceof Error ? err.message : String(err);
32394
- log2.warn({ tableName, sourceDb: src.name, err }, "Table copy failed \u2014 skipping");
32816
+ log3.warn({ tableName, sourceDb: src.name, err }, "Table copy failed \u2014 skipping");
32395
32817
  status = "skipped";
32396
32818
  errorMsg = msg;
32397
32819
  skipped = true;
@@ -32439,7 +32861,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32439
32861
  targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
32440
32862
  onProgress?.(` [${src.name}] Detached "${attachAlias}"`);
32441
32863
  } catch (detachErr) {
32442
- log2.warn(
32864
+ log3.warn(
32443
32865
  { attachAlias, sourceDb: src.name, err: detachErr },
32444
32866
  "DETACH failed \u2014 alias will be released on DB close"
32445
32867
  );
@@ -32449,7 +32871,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32449
32871
  targetNativeDb.exec(`DETACH DATABASE "${crossAlias}"`);
32450
32872
  onProgress?.(` [${src.name}] Cross-scope target detached "${crossAlias}"`);
32451
32873
  } catch (detachErr) {
32452
- log2.warn(
32874
+ log3.warn(
32453
32875
  { crossAlias, sourceDb: src.name, err: detachErr },
32454
32876
  "Cross-scope DETACH failed \u2014 alias will be released on DB close"
32455
32877
  );
@@ -32461,28 +32883,28 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
32461
32883
  try {
32462
32884
  const orphans = targetNativeDb.prepare("PRAGMA foreign_key_check").all();
32463
32885
  if (orphans.length > 0) {
32464
- log2.warn(
32886
+ log3.warn(
32465
32887
  { scope, orphanCount: orphans.length, sample: orphans.slice(0, 5) },
32466
32888
  `Exodus: PRAGMA foreign_key_check found ${orphans.length} orphan row(s) after bulk copy \u2014 these are genuine data orphans (not ordering artifacts)`
32467
32889
  );
32468
32890
  } else {
32469
- log2.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED \u2014 no orphan rows");
32891
+ log3.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED \u2014 no orphan rows");
32470
32892
  }
32471
32893
  } catch (checkErr) {
32472
- log2.warn(
32894
+ log3.warn(
32473
32895
  { scope, err: checkErr },
32474
32896
  "Exodus: PRAGMA foreign_key_check failed (non-fatal) \u2014 target schema may not have FK constraints enabled"
32475
32897
  );
32476
32898
  }
32477
32899
  try {
32478
32900
  targetNativeDb.exec("PRAGMA foreign_keys = ON");
32479
- log2.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
32901
+ log3.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
32480
32902
  } catch (fkErr) {
32481
- log2.warn({ scope, err: fkErr }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
32903
+ log3.warn({ scope, err: fkErr }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
32482
32904
  }
32483
32905
  }
32484
32906
  }
32485
- var log2, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
32907
+ var log3, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
32486
32908
  var init_migrate = __esm({
32487
32909
  "packages/core/src/store/exodus/migrate.ts"() {
32488
32910
  "use strict";
@@ -32491,9 +32913,10 @@ var init_migrate = __esm({
32491
32913
  init_dual_scope_db();
32492
32914
  init_open_cleo_db();
32493
32915
  init_column_transforms();
32916
+ init_plan2();
32494
32917
  init_table_name_map();
32495
32918
  init_types();
32496
- log2 = getLogger("exodus-migrate");
32919
+ log3 = getLogger("exodus-migrate");
32497
32920
  LOCK_SENTINEL_SUFFIX = ".exodus-lock";
32498
32921
  JOURNAL_FILENAME = "exodus-journal.json";
32499
32922
  SOURCE_EPOCH_UNITS = /* @__PURE__ */ new Map([
@@ -32509,116 +32932,55 @@ var init_migrate = __esm({
32509
32932
  }
32510
32933
  });
32511
32934
 
32512
- // packages/core/src/store/exodus/plan.ts
32513
- import { existsSync as existsSync5, readdirSync as readdirSync2, statfsSync, statSync } from "node:fs";
32514
- import { join as join8 } from "node:path";
32515
- function buildSourceDescriptors(cwd) {
32516
- const cleoDir = resolveCleoDir(cwd);
32517
- const cleoHome = getCleoHome();
32518
- return [
32519
- // Project-tier go into consolidated project-scope cleo.db
32520
- {
32521
- name: "tasks",
32522
- path: join8(cleoDir, "tasks.db"),
32523
- targetScope: "project"
32524
- },
32525
- {
32526
- name: "brain (project)",
32527
- path: join8(cleoDir, "brain.db"),
32528
- targetScope: "project"
32529
- },
32530
- {
32531
- name: "conduit",
32532
- path: join8(cleoDir, "conduit.db"),
32533
- targetScope: "project"
32534
- },
32535
- // Global-tier — go into consolidated global-scope cleo.db
32536
- {
32537
- name: "nexus",
32538
- path: join8(cleoHome, "nexus.db"),
32539
- targetScope: "global"
32540
- },
32541
- {
32542
- name: "signaldock",
32543
- path: join8(cleoHome, "signaldock.db"),
32544
- targetScope: "global"
32545
- },
32546
- {
32547
- name: "skills",
32548
- path: join8(cleoHome, "skills.db"),
32549
- targetScope: "global"
32550
- }
32551
- ];
32552
- }
32553
- function safeFileBytes(filePath) {
32554
- try {
32555
- return statSync(filePath).size;
32556
- } catch {
32557
- return 0;
32558
- }
32559
- }
32560
- function getAvailableBytes(dir) {
32561
- try {
32562
- const result = statfsSync(dir);
32563
- return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
32564
- } catch {
32565
- return 0;
32566
- }
32567
- }
32568
- function deriveStagingDirName() {
32569
- const iso = (/* @__PURE__ */ new Date()).toISOString().replace(/[:]/g, "").replace(/\..+Z$/, "Z");
32570
- return `exodus-staging-${iso}`;
32571
- }
32572
- function findExistingStaging(cleoDir) {
32573
- try {
32574
- const entries = readdirSync2(cleoDir, { withFileTypes: true });
32575
- const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
32576
- if (stagingDirs.length > 0) {
32577
- return join8(cleoDir, stagingDirs[0]);
32578
- }
32579
- } catch {
32935
+ // packages/core/src/store/exodus/seal.ts
32936
+ function resolveScopes(arg) {
32937
+ return arg === "both" ? ["project", "global"] : [arg];
32938
+ }
32939
+ function sealExodus(plan, scopeArg, cwd) {
32940
+ const parity = computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath);
32941
+ if (!parity.ok) {
32942
+ 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(", ")}`;
32943
+ log4.error({ deficits: parity.deficits.length }, `exodus seal refused \u2014 ${refusedReason}`);
32944
+ return { ok: false, refusedReason, parity, scopes: [] };
32945
+ }
32946
+ const outcomes = [];
32947
+ for (const scope of resolveScopes(scopeArg)) {
32948
+ const alreadySealed = hasExodusCompleteMarker(scope, cwd);
32949
+ const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
32950
+ const archived = scopeSources.map((s) => {
32951
+ const r = archiveSourceDb(s, cwd);
32952
+ return { name: r.name, action: r.action, archivedTo: r.archivedTo };
32953
+ });
32954
+ const markerPath = writeExodusCompleteMarker(
32955
+ scope,
32956
+ scopeSources.map((s) => s.name),
32957
+ cwd
32958
+ );
32959
+ log4.info(
32960
+ { scope, alreadySealed, archived: archived.filter((a) => a.action === "archived").length },
32961
+ `exodus seal: scope '${scope}' certified (count-parity verified, ${parity.checked} tables)`
32962
+ );
32963
+ outcomes.push({ scope, alreadySealed, archived, markerPath });
32580
32964
  }
32581
- return null;
32965
+ return { ok: true, parity, scopes: outcomes };
32582
32966
  }
32583
- function buildExodusPlan(cwd) {
32584
- const cleoDir = resolveCleoDir(cwd);
32585
- const sources = buildSourceDescriptors(cwd);
32586
- const totalSourceBytes = sources.reduce((sum, s) => sum + safeFileBytes(s.path), 0);
32587
- const availableBytes = getAvailableBytes(cleoDir);
32588
- const diskPreflight = totalSourceBytes === 0 || availableBytes >= 3 * totalSourceBytes;
32589
- const existingStaging = findExistingStaging(cleoDir);
32590
- const stagingDir = existingStaging ?? join8(cleoDir, deriveStagingDirName());
32591
- const resumeFromStaging = existingStaging !== null;
32592
- const projectDbPath = resolveDualScopeDbPath("project", cwd);
32593
- const globalDbPath = resolveDualScopeDbPath("global");
32594
- return {
32595
- sources,
32596
- totalSourceBytes,
32597
- availableBytes,
32598
- diskPreflight,
32599
- stagingDir,
32600
- resumeFromStaging,
32601
- projectDbPath,
32602
- globalDbPath
32603
- };
32604
- }
32605
- function sourcesPresent(sources) {
32606
- return sources.some((s) => existsSync5(s.path));
32607
- }
32608
- var init_plan2 = __esm({
32609
- "packages/core/src/store/exodus/plan.ts"() {
32967
+ var log4;
32968
+ var init_seal = __esm({
32969
+ "packages/core/src/store/exodus/seal.ts"() {
32610
32970
  "use strict";
32611
- init_paths();
32612
- init_dual_scope_db();
32971
+ init_logger2();
32972
+ init_archive2();
32973
+ init_count_parity();
32974
+ log4 = getLogger("exodus-seal");
32613
32975
  }
32614
32976
  });
32615
32977
 
32616
32978
  // packages/core/src/store/exodus/status.ts
32617
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
32979
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
32618
32980
  import { join as join9 } from "node:path";
32619
32981
  function readJournal2(stagingDir) {
32620
32982
  const p = join9(stagingDir, JOURNAL_FILENAME2);
32621
- if (!existsSync6(p)) return null;
32983
+ if (!existsSync8(p)) return null;
32622
32984
  try {
32623
32985
  return JSON.parse(readFileSync3(p, "utf8"));
32624
32986
  } catch {
@@ -32634,7 +32996,7 @@ function findStagingDirs(cleoDir) {
32634
32996
  }
32635
32997
  function safeBytes(p) {
32636
32998
  try {
32637
- return statSync2(p).size;
32999
+ return statSync4(p).size;
32638
33000
  } catch {
32639
33001
  return 0;
32640
33002
  }
@@ -32650,15 +33012,15 @@ function runExodusStatus(cwd) {
32650
33012
  const sourcesInfo = plan.sources.map((s) => ({
32651
33013
  name: s.name,
32652
33014
  path: s.path,
32653
- exists: existsSync6(s.path),
33015
+ exists: existsSync8(s.path),
32654
33016
  bytes: safeBytes(s.path)
32655
33017
  }));
32656
33018
  return {
32657
33019
  hasStaging: latestStaging !== null,
32658
33020
  stagingDir: latestStaging,
32659
33021
  journal,
32660
- projectDbExists: existsSync6(projectDbPath),
32661
- globalDbExists: existsSync6(globalDbPath),
33022
+ projectDbExists: existsSync8(projectDbPath),
33023
+ globalDbExists: existsSync8(globalDbPath),
32662
33024
  sourcesPresent: sourcesInfo.some((s) => s.exists),
32663
33025
  sources: sourcesInfo
32664
33026
  };
@@ -32675,7 +33037,7 @@ var init_status = __esm({
32675
33037
  });
32676
33038
 
32677
33039
  // packages/core/src/store/exodus/verify-migration.ts
32678
- import { existsSync as existsSync7 } from "node:fs";
33040
+ import { existsSync as existsSync9 } from "node:fs";
32679
33041
  import { createRequire as createRequire3 } from "node:module";
32680
33042
  function orderByClause(db, tableName) {
32681
33043
  try {
@@ -32689,6 +33051,18 @@ function orderByClause(db, tableName) {
32689
33051
  return "rowid";
32690
33052
  }
32691
33053
  function computeTableDigest(db, tableName, columns, transform2) {
33054
+ let count = 0;
33055
+ try {
33056
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
33057
+ count = Number(row?.c ?? 0);
33058
+ } catch (err) {
33059
+ const msg = err instanceof Error ? err.message : String(err);
33060
+ log5.warn(
33061
+ { tableName, err: msg },
33062
+ "computeTableDigest: COUNT(*) failed (possibly a virtual/FTS table) \u2014 treating as 0 rows"
33063
+ );
33064
+ return { count: 0, hash: "" };
33065
+ }
32692
33066
  const { createHash: createHash4 } = _require("node:crypto");
32693
33067
  const hasher = createHash4("sha256");
32694
33068
  const orderBy = orderByClause(db, tableName);
@@ -32697,29 +33071,35 @@ function computeTableDigest(db, tableName, columns, transform2) {
32697
33071
  selectClause = columns.map((c) => {
32698
33072
  if (transform2 === void 0) return `"${c}"`;
32699
33073
  const srcType = transform2.srcTypeByCol.get(c) ?? "";
32700
- const expr = buildDigestExpr(transform2.targetTableName, c, srcType, transform2.isoGlobCols);
33074
+ const tgtCol = transform2.tgtColByCol.get(c);
33075
+ const expr = buildDigestExpr(
33076
+ transform2.targetTableName,
33077
+ c,
33078
+ srcType,
33079
+ transform2.isoGlobCols,
33080
+ tgtCol
33081
+ );
32701
33082
  return `${expr} AS "${c}"`;
32702
33083
  }).join(", ");
32703
33084
  } else {
32704
33085
  selectClause = "*";
32705
33086
  }
32706
- let rows;
32707
33087
  try {
32708
- rows = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`).all();
33088
+ const stmt = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`);
33089
+ for (const row of stmt.iterate()) {
33090
+ const rowObj = row;
33091
+ hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
33092
+ }
32709
33093
  } catch (err) {
32710
33094
  const msg = err instanceof Error ? err.message : String(err);
32711
- log3.warn(
33095
+ log5.warn(
32712
33096
  { tableName, err: msg },
32713
- "computeTableDigest: SELECT failed (possibly a virtual/FTS table) \u2014 treating as 0 rows"
33097
+ "computeTableDigest: streamed SELECT failed (possibly a virtual/FTS table) \u2014 digest skipped (COUNT(*) parity still enforced)"
32714
33098
  );
32715
- return { count: 0, hash: "" };
32716
- }
32717
- for (const row of rows) {
32718
- const rowObj = row;
32719
- hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
33099
+ return { count, hash: "" };
32720
33100
  }
32721
33101
  return {
32722
- count: rows.length,
33102
+ count,
32723
33103
  hash: hasher.digest("hex").slice(0, 32)
32724
33104
  };
32725
33105
  }
@@ -32743,18 +33123,24 @@ function buildSourceDigestTransform(srcDb, srcTable, tgtDb, targetTableName) {
32743
33123
  srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => [r.name, r.type])
32744
33124
  );
32745
33125
  const isoGlobCols = detectIsoGlobColumns(tgtDb, targetTableName);
32746
- return { targetTableName, srcTypeByCol, isoGlobCols };
33126
+ const tgtColByCol = new Map(
33127
+ tgtDb.prepare(`PRAGMA table_info("${targetTableName}")`).all().map((r) => [
33128
+ r.name,
33129
+ { notnull: r.notnull, dflt_value: r.dflt_value, type: r.type }
33130
+ ])
33131
+ );
33132
+ return { targetTableName, srcTypeByCol, isoGlobCols, tgtColByCol };
32747
33133
  } catch {
32748
33134
  return void 0;
32749
33135
  }
32750
33136
  }
32751
- function listTables2(db) {
33137
+ function listTables3(db) {
32752
33138
  const rows = db.prepare(
32753
33139
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
32754
33140
  ).all();
32755
33141
  return rows.map((r) => r.name);
32756
33142
  }
32757
- function tableExists2(db, tableName) {
33143
+ function tableExists3(db, tableName) {
32758
33144
  try {
32759
33145
  const escaped = tableName.replace(/'/g, "''");
32760
33146
  return db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !== void 0;
@@ -32832,7 +33218,7 @@ function foreignKeyCheck(db, scope) {
32832
33218
  try {
32833
33219
  const rows = db.prepare("PRAGMA foreign_key_check").all();
32834
33220
  if (rows.length > 0) {
32835
- log3.warn(
33221
+ log5.warn(
32836
33222
  { scope, count: rows.length, sample: rows.slice(0, 5) },
32837
33223
  `verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`
32838
33224
  );
@@ -32844,7 +33230,7 @@ function foreignKeyCheck(db, scope) {
32844
33230
  fkid: r.fkid
32845
33231
  }));
32846
33232
  } catch (err) {
32847
- log3.warn({ scope, err }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
33233
+ log5.warn({ scope, err }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
32848
33234
  return [];
32849
33235
  }
32850
33236
  }
@@ -32881,13 +33267,13 @@ function sourceOrphanSignatures(db, sourceName, scope) {
32881
33267
  const rows = db.prepare("PRAGMA foreign_key_check").all();
32882
33268
  for (const r of rows) sigs.add(orphanSignature(db, r, sourceName));
32883
33269
  if (rows.length > 0) {
32884
- log3.warn(
33270
+ log5.warn(
32885
33271
  { scope, count: rows.length, sample: rows.slice(0, 5) },
32886
33272
  `verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) \u2014 these are tolerated (carried forward losslessly, flagged for data-hygiene)`
32887
33273
  );
32888
33274
  }
32889
33275
  } catch (err) {
32890
- log3.warn({ scope, err }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
33276
+ log5.warn({ scope, err }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
32891
33277
  }
32892
33278
  return sigs;
32893
33279
  }
@@ -32899,7 +33285,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
32899
33285
  const preExistingForeignKeyViolations = [];
32900
33286
  const failureLines = [];
32901
33287
  const sourceOrphanSigs = /* @__PURE__ */ new Set();
32902
- if (!existsSync7(projectDbPath)) {
33288
+ if (!existsSync9(projectDbPath)) {
32903
33289
  return {
32904
33290
  ok: false,
32905
33291
  tables: [],
@@ -32910,7 +33296,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
32910
33296
  error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`
32911
33297
  };
32912
33298
  }
32913
- if (!existsSync7(globalDbPath)) {
33299
+ if (!existsSync9(globalDbPath)) {
32914
33300
  return {
32915
33301
  ok: false,
32916
33302
  tables: [],
@@ -32925,7 +33311,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
32925
33311
  const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
32926
33312
  try {
32927
33313
  for (const src of sources) {
32928
- if (!existsSync7(src.path)) {
33314
+ if (!existsSync9(src.path)) {
32929
33315
  onProgress?.(`Skipping ${src.name} (not present)`);
32930
33316
  continue;
32931
33317
  }
@@ -32934,9 +33320,9 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
32934
33320
  for (const sig of sourceOrphanSignatures(srcSnap.db, src.name, `source:${src.name}`)) {
32935
33321
  sourceOrphanSigs.add(sig);
32936
33322
  }
32937
- const sourceTables = listTables2(srcSnap.db);
32938
- const projectTables = new Set(listTables2(projectSnap.db));
32939
- const globalTables = new Set(listTables2(globalSnap.db));
33323
+ const sourceTables = listTables3(srcSnap.db);
33324
+ const projectTables = new Set(listTables3(projectSnap.db));
33325
+ const globalTables = new Set(listTables3(globalSnap.db));
32940
33326
  for (const legacyTableName of sourceTables) {
32941
33327
  onProgress?.(`Verifying ${src.name}.${legacyTableName}\u2026`);
32942
33328
  const resolution = resolveConsolidatedTableName(src.name, legacyTableName);
@@ -33010,7 +33396,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33010
33396
  `[${scope}] ${src.name}.${legacyTableName} \u2192 ${targetTableName}: DEFICIT \u2014 source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`
33011
33397
  );
33012
33398
  } else if (tgtDigest.count > srcDigest.count) {
33013
- log3.warn(
33399
+ log5.warn(
33014
33400
  {
33015
33401
  scope,
33016
33402
  source: src.name,
@@ -33047,7 +33433,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33047
33433
  targetOrphans.push(...foreignKeyCheck(globalSnap.db, "global"));
33048
33434
  foreignKeyViolations.push(...targetOrphans);
33049
33435
  for (const fk of targetOrphans) {
33050
- const orphanDb = tableExists2(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33436
+ const orphanDb = tableExists3(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
33051
33437
  const sig = orphanSignature(orphanDb, fk);
33052
33438
  const preExisting = sourceOrphanSigs.has(sig);
33053
33439
  if (preExisting) {
@@ -33060,7 +33446,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33060
33446
  }
33061
33447
  }
33062
33448
  if (preExistingForeignKeyViolations.length > 0) {
33063
- log3.warn(
33449
+ log5.warn(
33064
33450
  {
33065
33451
  count: preExistingForeignKeyViolations.length,
33066
33452
  sample: preExistingForeignKeyViolations.slice(0, 5)
@@ -33070,7 +33456,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33070
33456
  }
33071
33457
  } catch (err) {
33072
33458
  const error48 = err instanceof Error ? err.message : String(err);
33073
- log3.error({ err }, "verifyMigration failed");
33459
+ log5.error({ err }, "verifyMigration failed");
33074
33460
  return {
33075
33461
  ok: false,
33076
33462
  tables,
@@ -33087,7 +33473,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
33087
33473
  if (failureLines.length > 0) {
33088
33474
  const error48 = `verifyMigration FAILED: ${failureLines.length} issue(s):
33089
33475
  ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33090
- log3.error({ failureCount: failureLines.length }, error48);
33476
+ log5.error({ failureCount: failureLines.length }, error48);
33091
33477
  return {
33092
33478
  ok: false,
33093
33479
  tables,
@@ -33107,7 +33493,7 @@ ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
33107
33493
  enumDrift
33108
33494
  };
33109
33495
  }
33110
- var log3, _require, CHECK_ENUM_REGEX;
33496
+ var log5, _require, CHECK_ENUM_REGEX;
33111
33497
  var init_verify_migration = __esm({
33112
33498
  "packages/core/src/store/exodus/verify-migration.ts"() {
33113
33499
  "use strict";
@@ -33116,7 +33502,7 @@ var init_verify_migration = __esm({
33116
33502
  init_open_cleo_db();
33117
33503
  init_column_transforms();
33118
33504
  init_table_name_map();
33119
- log3 = getLogger("verify-migration");
33505
+ log5 = getLogger("verify-migration");
33120
33506
  _require = createRequire3(import.meta.url);
33121
33507
  CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
33122
33508
  }
@@ -33159,8 +33545,10 @@ __export(exodus_exports, {
33159
33545
  archiveMigratedSources: () => archiveMigratedSources,
33160
33546
  archiveSourceDb: () => archiveSourceDb,
33161
33547
  archiveStrandedResidue: () => archiveStrandedResidue,
33548
+ buildExodusHealth: () => buildExodusHealth,
33162
33549
  buildExodusPlan: () => buildExodusPlan,
33163
33550
  clearExodusJournal: () => clearExodusJournal,
33551
+ computeCountParity: () => computeCountParity,
33164
33552
  deriveStagingDirName: () => deriveStagingDirName,
33165
33553
  detectStrandedResidue: () => detectStrandedResidue,
33166
33554
  exodusArchiveDir: () => exodusArchiveDir,
@@ -33172,6 +33560,7 @@ __export(exodus_exports, {
33172
33560
  runExodusMigrate: () => runExodusMigrate,
33173
33561
  runExodusStatus: () => runExodusStatus,
33174
33562
  runExodusVerify: () => runExodusVerify,
33563
+ sealExodus: () => sealExodus,
33175
33564
  sourcesPresent: () => sourcesPresent,
33176
33565
  verifyMigration: () => verifyMigration,
33177
33566
  writeExodusCompleteMarker: () => writeExodusCompleteMarker
@@ -33180,8 +33569,11 @@ var init_exodus = __esm({
33180
33569
  "packages/core/src/store/exodus/index.ts"() {
33181
33570
  "use strict";
33182
33571
  init_archive2();
33572
+ init_count_parity();
33573
+ init_health2();
33183
33574
  init_migrate();
33184
33575
  init_plan2();
33576
+ init_seal();
33185
33577
  init_status();
33186
33578
  init_table_name_map();
33187
33579
  init_types();
@@ -33197,7 +33589,7 @@ __export(on_open_exports, {
33197
33589
  isDataContinuityOk: () => isDataContinuityOk,
33198
33590
  maybeRunExodusOnOpen: () => maybeRunExodusOnOpen
33199
33591
  });
33200
- import { existsSync as existsSync8 } from "node:fs";
33592
+ import { existsSync as existsSync10 } from "node:fs";
33201
33593
  function isDisabledByEnv() {
33202
33594
  const v = process.env.CLEO_DISABLE_EXODUS_ON_OPEN;
33203
33595
  return v === "1" || v === "true";
@@ -33223,7 +33615,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33223
33615
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%'"
33224
33616
  ).all().map((r) => r.name);
33225
33617
  } catch (err) {
33226
- log4.error({ err, scope }, "exodus-on-open: failed to enumerate tables for rollback");
33618
+ log6.error({ err, scope }, "exodus-on-open: failed to enumerate tables for rollback");
33227
33619
  return;
33228
33620
  }
33229
33621
  try {
@@ -33233,7 +33625,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33233
33625
  try {
33234
33626
  nativeDb.exec(`DELETE FROM "${table}"`);
33235
33627
  } catch (err) {
33236
- log4.warn({ err, table, scope }, "exodus-on-open: failed to clear table during rollback");
33628
+ log6.warn({ err, table, scope }, "exodus-on-open: failed to clear table during rollback");
33237
33629
  }
33238
33630
  }
33239
33631
  nativeDb.exec("COMMIT");
@@ -33242,7 +33634,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
33242
33634
  nativeDb.exec("ROLLBACK");
33243
33635
  } catch {
33244
33636
  }
33245
- log4.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33637
+ log6.error({ err, scope }, "exodus-on-open: rollback transaction failed");
33246
33638
  } finally {
33247
33639
  try {
33248
33640
  nativeDb.exec("PRAGMA foreign_keys = ON");
@@ -33262,7 +33654,7 @@ async function rollbackBothScopes(scope, projectDbPath, globalDbPath) {
33262
33654
  rollbackConsolidatedToEmpty(native, s);
33263
33655
  }
33264
33656
  } catch (err) {
33265
- log4.warn(
33657
+ log6.warn(
33266
33658
  { err, scope: s, openingScope: scope },
33267
33659
  "exodus-on-open: could not roll back scope (best-effort)"
33268
33660
  );
@@ -33278,7 +33670,7 @@ function isDataContinuityOk(result) {
33278
33670
  const deficits = result.tables.filter((t) => t.targetCount < t.sourceCount);
33279
33671
  const surpluses = result.tables.filter((t) => t.targetCount > t.sourceCount);
33280
33672
  if (surpluses.length > 0) {
33281
- log4.warn(
33673
+ log6.warn(
33282
33674
  {
33283
33675
  surpluses: surpluses.map((t) => ({
33284
33676
  table: t.targetTable,
@@ -33312,7 +33704,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33312
33704
  const { buildExodusPlan: buildExodusPlan2, runExodusMigrate: runExodusMigrate2, verifyMigration: verifyMigration2, clearExodusJournal: clearExodusJournal2 } = await Promise.resolve().then(() => (init_exodus(), exodus_exports));
33313
33705
  const plan = buildExodusPlan2(cwd);
33314
33706
  const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
33315
- if (!scopeSources.some((s) => existsSync8(s.path))) {
33707
+ if (!scopeSources.some((s) => existsSync10(s.path))) {
33316
33708
  return {
33317
33709
  outcome: "skipped",
33318
33710
  reason: `no legacy ${scope}-scope source DBs present (fresh install or cross-scope-only)`
@@ -33325,11 +33717,11 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33325
33717
  if (!consolidatedIsEmpty(nativeDb, scope)) {
33326
33718
  return { outcome: "skipped", reason: "migrated by a concurrent process (lock winner)" };
33327
33719
  }
33328
- log4.info(
33720
+ log6.info(
33329
33721
  {
33330
33722
  scope,
33331
33723
  dbPath,
33332
- sources: plan.sources.filter((s) => existsSync8(s.path)).map((s) => s.name)
33724
+ sources: plan.sources.filter((s) => existsSync10(s.path)).map((s) => s.name)
33333
33725
  },
33334
33726
  "exodus-on-open: consolidated cleo.db is empty and legacy data present \u2014 auto-migrating"
33335
33727
  );
@@ -33338,23 +33730,23 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33338
33730
  const migrateResult = await runExodusMigrate2(
33339
33731
  plan,
33340
33732
  false,
33341
- (msg) => log4.debug({ scope }, `exodus-on-open: ${msg}`)
33733
+ (msg) => log6.debug({ scope }, `exodus-on-open: ${msg}`)
33342
33734
  );
33343
33735
  if (!migrateResult.ok) {
33344
33736
  await rollbackBothScopes(scope, plan.projectDbPath, plan.globalDbPath);
33345
33737
  clearExodusJournal2(migrateResult.stagingDir);
33346
33738
  const reason = `migration failed: ${migrateResult.error ?? "unknown error"} \u2014 legacy DBs kept as source`;
33347
- log4.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33739
+ log6.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
33348
33740
  return { outcome: "aborted", reason };
33349
33741
  }
33350
33742
  const verifyResult = verifyMigration2(
33351
33743
  plan.sources,
33352
33744
  plan.projectDbPath,
33353
33745
  plan.globalDbPath,
33354
- (msg) => log4.debug({ scope }, `exodus-on-open verify: ${msg}`)
33746
+ (msg) => log6.debug({ scope }, `exodus-on-open verify: ${msg}`)
33355
33747
  );
33356
33748
  if (!verifyResult.ok) {
33357
- log4.warn(
33749
+ log6.warn(
33358
33750
  {
33359
33751
  scope,
33360
33752
  enumDrift: verifyResult.enumDrift.length,
@@ -33368,7 +33760,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33368
33760
  clearExodusJournal2(plan.stagingDir);
33369
33761
  const deficits = verifyResult.tables.filter((t) => t.targetCount < t.sourceCount).map((t) => `${t.targetTable}(${t.sourceCount}\u2192${t.targetCount})`);
33370
33762
  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();
33371
- log4.error(
33763
+ log6.error(
33372
33764
  {
33373
33765
  scope,
33374
33766
  countDeficits: deficits,
@@ -33380,14 +33772,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33380
33772
  return { outcome: "aborted", reason };
33381
33773
  }
33382
33774
  const rowsCopied = migrateResult.tables.filter((t) => !t.skipped).reduce((n, t) => n + t.rowsCopied, 0);
33383
- log4.info(
33775
+ log6.info(
33384
33776
  { scope, rowsCopied, tables: migrateResult.tables.length },
33385
33777
  "exodus-on-open: parity verified \u2014 legacy data migrated into consolidated cleo.db"
33386
33778
  );
33387
33779
  try {
33388
- const consumed = plan.sources.filter((s) => existsSync8(s.path));
33780
+ const consumed = plan.sources.filter((s) => existsSync10(s.path));
33389
33781
  const archiveResult = archiveMigratedSources(consumed, cwd);
33390
- log4.info(
33782
+ log6.info(
33391
33783
  {
33392
33784
  scope,
33393
33785
  archived: archiveResult.sources.filter((s) => s.action === "archived").length,
@@ -33396,7 +33788,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33396
33788
  "exodus-on-open: archived legacy sources + sealed completion marker(s)"
33397
33789
  );
33398
33790
  } catch (err) {
33399
- log4.error(
33791
+ log6.error(
33400
33792
  { err, scope },
33401
33793
  "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`)"
33402
33794
  );
@@ -33418,14 +33810,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
33418
33810
  function _isExodusInProgress() {
33419
33811
  return _exodusInProgress;
33420
33812
  }
33421
- var log4, _exodusInProgress;
33813
+ var log6, _exodusInProgress;
33422
33814
  var init_on_open = __esm({
33423
33815
  "packages/core/src/store/exodus/on-open.ts"() {
33424
33816
  "use strict";
33425
33817
  init_logger2();
33426
33818
  init_lock();
33427
33819
  init_archive2();
33428
- log4 = getLogger("exodus-on-open");
33820
+ log6 = getLogger("exodus-on-open");
33429
33821
  _exodusInProgress = false;
33430
33822
  }
33431
33823
  });
@@ -33440,7 +33832,7 @@ __export(dual_scope_db_exports, {
33440
33832
  resolveDualScopeDbPath: () => resolveDualScopeDbPath,
33441
33833
  upsertIdempotent: () => upsertIdempotent
33442
33834
  });
33443
- import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
33835
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
33444
33836
  import { createRequire as createRequire4 } from "node:module";
33445
33837
  import { dirname as dirname4, join as join10 } from "node:path";
33446
33838
  function cacheKey(scope, dbPath) {
@@ -33488,10 +33880,10 @@ async function openDualScopeDb(scope, cwd) {
33488
33880
  const dbPath = resolveDualScopeDbPath(scope, cwd);
33489
33881
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath, cwd) : openDualScopeDbAtPath("global", dbPath, cwd);
33490
33882
  }
33491
- async function openDedicatedDualScopeDb(scope, dbPath, log5) {
33492
- log5.debug({ scope, dbPath }, "opening DEDICATED (non-cached) dual-scope cleo.db (T11782 FIX D)");
33883
+ async function openDedicatedDualScopeDb(scope, dbPath, log7) {
33884
+ log7.debug({ scope, dbPath }, "opening DEDICATED (non-cached) dual-scope cleo.db (T11782 FIX D)");
33493
33885
  const dir = dirname4(dbPath);
33494
- if (!existsSync9(dir)) {
33886
+ if (!existsSync11(dir)) {
33495
33887
  mkdirSync3(dir, { recursive: true });
33496
33888
  }
33497
33889
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33509,7 +33901,7 @@ async function openDedicatedDualScopeDb(scope, dbPath, log5) {
33509
33901
  existenceTable(scope),
33510
33902
  `dual-scope-db[${scope}]`
33511
33903
  );
33512
- log5.debug({ scope, dbPath }, "DEDICATED dual-scope cleo.db ready (T11782 FIX D)");
33904
+ log7.debug({ scope, dbPath }, "DEDICATED dual-scope cleo.db ready (T11782 FIX D)");
33513
33905
  return {
33514
33906
  db,
33515
33907
  scope,
@@ -33534,14 +33926,14 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33534
33926
  return existing.handle;
33535
33927
  }
33536
33928
  }
33537
- const log5 = getLogger("dual-scope-db");
33929
+ const log7 = getLogger("dual-scope-db");
33538
33930
  if (dedicated) {
33539
- return openDedicatedDualScopeDb(scope, dbPath, log5);
33931
+ return openDedicatedDualScopeDb(scope, dbPath, log7);
33540
33932
  }
33541
33933
  const initPromise = (async () => {
33542
- log5.debug({ scope, dbPath }, "opening dual-scope cleo.db");
33934
+ log7.debug({ scope, dbPath }, "opening dual-scope cleo.db");
33543
33935
  const dir = dirname4(dbPath);
33544
- if (!existsSync9(dir)) {
33936
+ if (!existsSync11(dir)) {
33545
33937
  mkdirSync3(dir, { recursive: true });
33546
33938
  }
33547
33939
  const DatabaseSyncCtor = getDatabaseSyncCtor();
@@ -33559,7 +33951,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33559
33951
  existenceTable(scope),
33560
33952
  `dual-scope-db[${scope}]`
33561
33953
  );
33562
- log5.debug({ scope, dbPath }, "dual-scope cleo.db ready");
33954
+ log7.debug({ scope, dbPath }, "dual-scope cleo.db ready");
33563
33955
  const handle = {
33564
33956
  db,
33565
33957
  scope,
@@ -33583,7 +33975,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33583
33975
  const result = await maybeRunExodusOnOpen2(scope, dbPath, nativeDb, exodusCwd);
33584
33976
  if (result.outcome === "migrated" || result.outcome === "aborted") {
33585
33977
  if (result.outcome === "aborted") {
33586
- log5.warn(
33978
+ log7.warn(
33587
33979
  { scope, reason: result.reason },
33588
33980
  "exodus-on-open aborted; consolidated cleo.db left empty, legacy kept as source"
33589
33981
  );
@@ -33591,7 +33983,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
33591
33983
  return scope === "project" ? openDualScopeDbAtPath("project", dbPath) : openDualScopeDbAtPath("global", dbPath);
33592
33984
  }
33593
33985
  } catch (err) {
33594
- log5.warn(
33986
+ log7.warn(
33595
33987
  { err, scope },
33596
33988
  "exodus-on-open hook failed (non-fatal); re-opening consolidated handle"
33597
33989
  );
@@ -34175,7 +34567,7 @@ __export(nexus_sqlite_exports, {
34175
34567
  resetNexusDbState: () => resetNexusDbState,
34176
34568
  resolveNexusMigrationsFolder: () => resolveNexusMigrationsFolder
34177
34569
  });
34178
- import { copyFileSync as copyFileSync3, existsSync as existsSync10 } from "node:fs";
34570
+ import { copyFileSync as copyFileSync3, existsSync as existsSync12 } from "node:fs";
34179
34571
  import { join as join11 } from "node:path";
34180
34572
  function getNexusDbPath(cwd) {
34181
34573
  return resolveDualScopeDbPath("project", cwd);
@@ -34217,12 +34609,12 @@ function detectAndWarnOnNestedNexus() {
34217
34609
  } catch {
34218
34610
  return false;
34219
34611
  }
34220
- if (!existsSync10(nestedPath)) return false;
34612
+ if (!existsSync12(nestedPath)) return false;
34221
34613
  if (_warnedNestedPaths.has(nestedPath)) return false;
34222
34614
  _warnedNestedPaths.add(nestedPath);
34223
34615
  const canonicalPath = getNexusDbPath();
34224
- const log5 = getLogger("nexus-sqlite");
34225
- log5.warn(
34616
+ const log7 = getLogger("nexus-sqlite");
34617
+ log7.warn(
34226
34618
  {
34227
34619
  nestedPath,
34228
34620
  canonicalPath,
@@ -34237,7 +34629,7 @@ function detectAndWarnOnNestedNexus() {
34237
34629
  function _resetNestedNexusWarningGate() {
34238
34630
  _warnedNestedPaths.clear();
34239
34631
  }
34240
- function tableExists3(nativeDb, tableName) {
34632
+ function tableExists4(nativeDb, tableName) {
34241
34633
  const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
34242
34634
  return !!result;
34243
34635
  }
@@ -34246,7 +34638,7 @@ function objectExists(nativeDb, type, name15) {
34246
34638
  return !!result;
34247
34639
  }
34248
34640
  function ensureNexusFts5(nativeDb) {
34249
- if (!tableExists3(nativeDb, "nexus_nodes")) return;
34641
+ if (!tableExists4(nativeDb, "nexus_nodes")) return;
34250
34642
  if (!objectExists(nativeDb, "table", "nexus_symbols_fts")) {
34251
34643
  nativeDb.exec(`
34252
34644
  CREATE VIRTUAL TABLE nexus_symbols_fts USING fts5(
@@ -34292,7 +34684,7 @@ function ensureNexusFts5(nativeDb) {
34292
34684
  `);
34293
34685
  }
34294
34686
  function ensureNexusRelationWeights(nativeDb) {
34295
- if (!tableExists3(nativeDb, "nexus_relations")) return;
34687
+ if (!tableExists4(nativeDb, "nexus_relations")) return;
34296
34688
  nativeDb.exec(`
34297
34689
  CREATE TABLE IF NOT EXISTS nexus_relation_weights (
34298
34690
  relation_id TEXT PRIMARY KEY NOT NULL,
@@ -34374,9 +34766,9 @@ function ensureNexusRelationWeights(nativeDb) {
34374
34766
  }
34375
34767
  function runNexusMigrations(nativeDb, db) {
34376
34768
  const migrationsFolder = resolveNexusMigrationsFolder();
34377
- if (tableExists3(nativeDb, "nexus_nodes") && _nexusDbPath) {
34769
+ if (tableExists4(nativeDb, "nexus_nodes") && _nexusDbPath) {
34378
34770
  const backupPath = _nexusDbPath.replace(/\.db$/, "-pre-cleo.db.bak");
34379
- if (!existsSync10(backupPath)) {
34771
+ if (!existsSync12(backupPath)) {
34380
34772
  try {
34381
34773
  copyFileSync3(_nexusDbPath, backupPath);
34382
34774
  } catch {
@@ -34508,7 +34900,7 @@ var init_nexus_sqlite = __esm({
34508
34900
 
34509
34901
  // packages/core/src/paths.ts
34510
34902
  import { AsyncLocalStorage } from "node:async_hooks";
34511
- import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
34903
+ import { existsSync as existsSync13, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
34512
34904
  import { createRequire as createRequire5 } from "node:module";
34513
34905
  import { homedir } from "node:os";
34514
34906
  import { basename as basename4, dirname as dirname5, join as join12, resolve as resolve3 } from "node:path";
@@ -34602,7 +34994,7 @@ function _resolveProjectByCwdFromNexus(cwd) {
34602
34994
  try {
34603
34995
  const cleoHome = getCleoHome();
34604
34996
  const globalDbPath = join12(cleoHome, "cleo.db");
34605
- if (!existsSync11(globalDbPath)) return null;
34997
+ if (!existsSync13(globalDbPath)) return null;
34606
34998
  const start = resolve3(cwd ?? process.cwd());
34607
34999
  let current = start;
34608
35000
  const DatabaseSync2 = _getDatabaseSyncCtor();
@@ -34641,7 +35033,7 @@ function _findCleoDirRoot(cwd) {
34641
35033
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
34642
35034
  if (!isDangerousRoot) {
34643
35035
  try {
34644
- if (statSync3(join12(current, ".cleo")).isDirectory()) {
35036
+ if (statSync5(join12(current, ".cleo")).isDirectory()) {
34645
35037
  return current;
34646
35038
  }
34647
35039
  } catch {
@@ -34715,7 +35107,7 @@ function _cwdHasGitAncestor(cwd) {
34715
35107
  while (true) {
34716
35108
  const gitMarker = join12(current, ".git");
34717
35109
  try {
34718
- if (existsSync11(gitMarker)) {
35110
+ if (existsSync13(gitMarker)) {
34719
35111
  return true;
34720
35112
  }
34721
35113
  } catch {
@@ -34728,15 +35120,15 @@ function _cwdHasGitAncestor(cwd) {
34728
35120
  function _resolveMainRepoFromGitlink(gitlinkDir) {
34729
35121
  try {
34730
35122
  const gitLinkPath = join12(gitlinkDir, ".git");
34731
- if (!existsSync11(gitLinkPath)) return null;
34732
- const stat2 = statSync3(gitLinkPath);
35123
+ if (!existsSync13(gitLinkPath)) return null;
35124
+ const stat2 = statSync5(gitLinkPath);
34733
35125
  if (!stat2.isFile()) return null;
34734
35126
  const gitLinkContent = readFileSync4(gitLinkPath, "utf-8").trim();
34735
35127
  const match = gitLinkContent.match(/^gitdir:\s*(.+)$/m);
34736
35128
  if (!match) return null;
34737
35129
  const gitdir = match[1].trim();
34738
35130
  const mainRepo = dirname5(dirname5(dirname5(gitdir)));
34739
- if (existsSync11(join12(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
35131
+ if (existsSync13(join12(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
34740
35132
  return mainRepo;
34741
35133
  }
34742
35134
  } catch {
@@ -34745,19 +35137,19 @@ function _resolveMainRepoFromGitlink(gitlinkDir) {
34745
35137
  }
34746
35138
  function validateProjectRoot(candidate) {
34747
35139
  const cleoDir = join12(candidate, ".cleo");
34748
- if (!existsSync11(cleoDir)) {
35140
+ if (!existsSync13(cleoDir)) {
34749
35141
  return false;
34750
35142
  }
34751
35143
  const projectInfoPath = join12(cleoDir, "project-info.json");
34752
- if (existsSync11(projectInfoPath)) {
35144
+ if (existsSync13(projectInfoPath)) {
34753
35145
  try {
34754
35146
  const raw = readFileSync4(projectInfoPath, "utf-8");
34755
35147
  const parsed = JSON.parse(raw);
34756
35148
  if (typeof parsed === "object" && parsed !== null && "projectId" in parsed && typeof parsed["projectId"] === "string" && parsed["projectId"] !== "") {
34757
35149
  const gitMarker = join12(candidate, ".git");
34758
- if (existsSync11(gitMarker)) {
35150
+ if (existsSync13(gitMarker)) {
34759
35151
  try {
34760
- if (!statSync3(gitMarker).isDirectory()) {
35152
+ if (!statSync5(gitMarker).isDirectory()) {
34761
35153
  return false;
34762
35154
  }
34763
35155
  } catch {
@@ -34770,10 +35162,10 @@ function validateProjectRoot(candidate) {
34770
35162
  }
34771
35163
  }
34772
35164
  const gitDir = join12(candidate, ".git");
34773
- if (existsSync11(gitDir)) {
35165
+ if (existsSync13(gitDir)) {
34774
35166
  let isRealGitDir = false;
34775
35167
  try {
34776
- const stat2 = statSync3(gitDir);
35168
+ const stat2 = statSync5(gitDir);
34777
35169
  isRealGitDir = stat2.isDirectory();
34778
35170
  } catch {
34779
35171
  isRealGitDir = false;
@@ -34820,16 +35212,16 @@ function getProjectRoot(cwd) {
34820
35212
  const cleoDir = join12(current, ".cleo");
34821
35213
  const gitDir = join12(current, ".git");
34822
35214
  const isDangerousRoot = current === homeRoot || current === "/" || current === "";
34823
- if (existsSync11(cleoDir) && !isDangerousRoot) {
35215
+ if (existsSync13(cleoDir) && !isDangerousRoot) {
34824
35216
  if (validateProjectRoot(current)) {
34825
35217
  return current;
34826
35218
  }
34827
35219
  skippedCleoDirs.push(current);
34828
35220
  }
34829
- if (existsSync11(gitDir) && !isDangerousRoot) {
35221
+ if (existsSync13(gitDir) && !isDangerousRoot) {
34830
35222
  let isRealGitDir = false;
34831
35223
  try {
34832
- isRealGitDir = statSync3(gitDir).isDirectory();
35224
+ isRealGitDir = statSync5(gitDir).isDirectory();
34833
35225
  } catch {
34834
35226
  isRealGitDir = false;
34835
35227
  }
@@ -34882,7 +35274,7 @@ function isAbsolutePath(path) {
34882
35274
  function _readProjectNameFromInfo(projectRoot) {
34883
35275
  try {
34884
35276
  const infoPath = join12(projectRoot, ".cleo", "project-info.json");
34885
- if (!existsSync11(infoPath)) return void 0;
35277
+ if (!existsSync13(infoPath)) return void 0;
34886
35278
  const raw = readFileSync4(infoPath, "utf-8");
34887
35279
  const data = JSON.parse(raw);
34888
35280
  return typeof data.name === "string" && data.name.length > 0 ? data.name : void 0;
@@ -34974,7 +35366,7 @@ var init_paths = __esm({
34974
35366
  // packages/core/src/store/file-utils.ts
34975
35367
  import { randomBytes } from "node:crypto";
34976
35368
  import {
34977
- existsSync as existsSync12,
35369
+ existsSync as existsSync14,
34978
35370
  mkdirSync as mkdirSync4,
34979
35371
  readdirSync as readdirSync4,
34980
35372
  readFileSync as readFileSync5,
@@ -34989,7 +35381,7 @@ function rotateBackup(filePath, mode) {
34989
35381
  const name15 = basename5(filePath);
34990
35382
  const backupDir = join13(dir, ".backups");
34991
35383
  const dirMode = typeof mode === "number" ? modeToDirMode(mode) : void 0;
34992
- if (!existsSync12(backupDir)) {
35384
+ if (!existsSync14(backupDir)) {
34993
35385
  mkdirSync4(backupDir, { recursive: true, mode: dirMode });
34994
35386
  }
34995
35387
  for (let i = MAX_BACKUPS; i >= 1; i--) {
@@ -35002,7 +35394,7 @@ function rotateBackup(filePath, mode) {
35002
35394
  } else {
35003
35395
  const next = join13(backupDir, `${name15}.${i + 1}`);
35004
35396
  try {
35005
- if (existsSync12(current)) renameSync3(current, next);
35397
+ if (existsSync14(current)) renameSync3(current, next);
35006
35398
  } catch {
35007
35399
  }
35008
35400
  }
@@ -35038,7 +35430,7 @@ function writeJsonFileAtomic(filePath, data, optsOrIndent = 2) {
35038
35430
  writeFileSync3(tempPath, content, "utf-8");
35039
35431
  }
35040
35432
  try {
35041
- if (existsSync12(filePath)) {
35433
+ if (existsSync14(filePath)) {
35042
35434
  rotateBackup(filePath, mode);
35043
35435
  }
35044
35436
  renameSync3(tempPath, filePath);
@@ -35064,10 +35456,10 @@ function readJsonFile(filePath) {
35064
35456
  async function withLock2(filePath, transform2, opts = {}) {
35065
35457
  const dir = dirname6(filePath);
35066
35458
  const dirMode = typeof opts.mode === "number" ? modeToDirMode(opts.mode) : void 0;
35067
- if (!existsSync12(dir)) {
35459
+ if (!existsSync14(dir)) {
35068
35460
  mkdirSync4(dir, { recursive: true, mode: dirMode });
35069
35461
  }
35070
- if (!existsSync12(filePath)) {
35462
+ if (!existsSync14(filePath)) {
35071
35463
  if (typeof opts.mode === "number") {
35072
35464
  writeFileSync3(filePath, "", { encoding: "utf-8", mode: opts.mode });
35073
35465
  } else {
@@ -35089,10 +35481,10 @@ async function withLock2(filePath, transform2, opts = {}) {
35089
35481
  }
35090
35482
  async function withFileLock(filePath, operation) {
35091
35483
  const dir = dirname6(filePath);
35092
- if (!existsSync12(dir)) {
35484
+ if (!existsSync14(dir)) {
35093
35485
  mkdirSync4(dir, { recursive: true });
35094
35486
  }
35095
- if (!existsSync12(filePath)) {
35487
+ if (!existsSync14(filePath)) {
35096
35488
  writeFileSync3(filePath, "", "utf-8");
35097
35489
  }
35098
35490
  let release;
@@ -35312,7 +35704,7 @@ __export(config_exports, {
35312
35704
  parseConfigValue: () => parseConfigValue,
35313
35705
  setConfigValue: () => setConfigValue
35314
35706
  });
35315
- import { existsSync as existsSync13 } from "node:fs";
35707
+ import { existsSync as existsSync15 } from "node:fs";
35316
35708
  import { mkdir as mkdir3, writeFile } from "node:fs/promises";
35317
35709
  import { dirname as dirname7 } from "node:path";
35318
35710
  function getNestedValue(obj, path) {
@@ -35436,7 +35828,7 @@ function parseConfigValue(value) {
35436
35828
  }
35437
35829
  async function setConfigValue(key, value, cwd, opts) {
35438
35830
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35439
- if (!existsSync13(configPath)) {
35831
+ if (!existsSync15(configPath)) {
35440
35832
  const dir = dirname7(configPath);
35441
35833
  await mkdir3(dir, { recursive: true });
35442
35834
  await writeFile(configPath, "{}", "utf-8");
@@ -35450,7 +35842,7 @@ async function setConfigValue(key, value, cwd, opts) {
35450
35842
  async function applyStrictnessPreset(preset, cwd, opts) {
35451
35843
  const definition = STRICTNESS_PRESETS[preset];
35452
35844
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
35453
- if (!existsSync13(configPath)) {
35845
+ if (!existsSync15(configPath)) {
35454
35846
  const dir = dirname7(configPath);
35455
35847
  await mkdir3(dir, { recursive: true });
35456
35848
  await writeFile(configPath, "{}", "utf-8");
@@ -35823,7 +36215,7 @@ var init_cleo_pkce_seeder = __esm({
35823
36215
  });
35824
36216
 
35825
36217
  // packages/core/src/llm/credential-seeders/codex-cli-seeder.ts
35826
- import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
36218
+ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "node:fs";
35827
36219
  import { homedir as homedir3 } from "node:os";
35828
36220
  import { join as join17 } from "node:path";
35829
36221
  function getCodexAuthPath() {
@@ -35848,7 +36240,7 @@ var init_codex_cli_seeder = __esm({
35848
36240
  */
35849
36241
  async seed() {
35850
36242
  const authPath = getCodexAuthPath();
35851
- if (!existsSync14(authPath)) {
36243
+ if (!existsSync16(authPath)) {
35852
36244
  return { entries: [] };
35853
36245
  }
35854
36246
  let raw;
@@ -35978,7 +36370,7 @@ var init_google_pkce = __esm({
35978
36370
  });
35979
36371
 
35980
36372
  // packages/core/src/llm/credential-seeders/gemini-cli-seeder.ts
35981
- import { existsSync as existsSync15, readFileSync as readFileSync9 } from "node:fs";
36373
+ import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
35982
36374
  import { join as join18 } from "node:path";
35983
36375
  import { getCleoHome as getCleoHome3 } from "@cleocode/paths";
35984
36376
  function getGoogleOauthPath() {
@@ -36027,7 +36419,7 @@ var init_gemini_cli_seeder = __esm({
36027
36419
  */
36028
36420
  async seed() {
36029
36421
  const path = getGoogleOauthPath();
36030
- if (!existsSync15(path)) {
36422
+ if (!existsSync17(path)) {
36031
36423
  return { entries: [] };
36032
36424
  }
36033
36425
  let raw;
@@ -36252,7 +36644,7 @@ var init_credential_seeders = __esm({
36252
36644
 
36253
36645
  // packages/core/src/llm/global-config-migration.ts
36254
36646
  import {
36255
- existsSync as existsSync16,
36647
+ existsSync as existsSync18,
36256
36648
  mkdirSync as mkdirSync5,
36257
36649
  readFileSync as readFileSync10,
36258
36650
  renameSync as renameSync4,
@@ -36278,12 +36670,12 @@ function migrateGlobalConfigToConfigDir() {
36278
36670
  const source = legacyGlobalConfigPath();
36279
36671
  const target = configDirGlobalConfigPath();
36280
36672
  const marker16 = migrationMarkerPath();
36281
- if (existsSync16(marker16)) return false;
36282
- if (!existsSync16(source)) {
36673
+ if (existsSync18(marker16)) return false;
36674
+ if (!existsSync18(source)) {
36283
36675
  stampMarker(marker16);
36284
36676
  return false;
36285
36677
  }
36286
- if (existsSync16(target)) {
36678
+ if (existsSync18(target)) {
36287
36679
  backupSourceQuiet(source);
36288
36680
  stampMarker(marker16);
36289
36681
  return false;
@@ -36316,7 +36708,7 @@ function migrateGlobalConfigToConfigDir() {
36316
36708
  } catch (err) {
36317
36709
  try {
36318
36710
  const temp = tempTargetPath();
36319
- if (existsSync16(temp)) unlinkSync5(temp);
36711
+ if (existsSync18(temp)) unlinkSync5(temp);
36320
36712
  } catch {
36321
36713
  }
36322
36714
  console.error(
@@ -36336,7 +36728,7 @@ function stampMarker(markerPath) {
36336
36728
  function backupSourceQuiet(source) {
36337
36729
  try {
36338
36730
  const backup = `${source}${BACKUP_SUFFIX}`;
36339
- if (existsSync16(backup)) return;
36731
+ if (existsSync18(backup)) return;
36340
36732
  renameSync4(source, backup);
36341
36733
  } catch {
36342
36734
  }
@@ -36359,7 +36751,7 @@ var init_global_config_migration = __esm({
36359
36751
  });
36360
36752
 
36361
36753
  // packages/core/src/llm/legacy-flat-key-import.ts
36362
- import { existsSync as existsSync17, readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync5 } from "node:fs";
36754
+ import { existsSync as existsSync19, readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync5 } from "node:fs";
36363
36755
  import { join as join20 } from "node:path";
36364
36756
  import { getCleoHome as getCleoHome5 } from "@cleocode/paths";
36365
36757
  function migrationPaths() {
@@ -36388,7 +36780,7 @@ function writeMarker(markerPath) {
36388
36780
  }
36389
36781
  function readFlatKey(flatPath) {
36390
36782
  try {
36391
- if (!existsSync17(flatPath)) return null;
36783
+ if (!existsSync19(flatPath)) return null;
36392
36784
  const raw = readFileSync11(flatPath, "utf-8").trim();
36393
36785
  return raw || null;
36394
36786
  } catch (err) {
@@ -36401,7 +36793,7 @@ function readFlatKey(flatPath) {
36401
36793
  }
36402
36794
  async function importLegacyFlatAnthropicKey() {
36403
36795
  const { flatPath, bakPath, markerPath } = migrationPaths();
36404
- if (existsSync17(markerPath)) {
36796
+ if (existsSync19(markerPath)) {
36405
36797
  return { status: "marker-present", flatPath, bakPath: null, markerPath };
36406
36798
  }
36407
36799
  let existing = null;
@@ -36418,7 +36810,7 @@ async function importLegacyFlatAnthropicKey() {
36418
36810
  writeMarker(markerPath);
36419
36811
  return { status: "already-imported", flatPath, bakPath: null, markerPath };
36420
36812
  }
36421
- if (!existsSync17(flatPath)) {
36813
+ if (!existsSync19(flatPath)) {
36422
36814
  writeMarker(markerPath);
36423
36815
  return { status: "no-flat-file", flatPath, bakPath: null, markerPath };
36424
36816
  }
@@ -36494,7 +36886,7 @@ __export(credentials_exports, {
36494
36886
  resolveProviderStatus: () => resolveProviderStatus,
36495
36887
  storeAnthropicApiKey: () => storeAnthropicApiKey
36496
36888
  });
36497
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
36889
+ import { existsSync as existsSync20, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
36498
36890
  import { join as join21 } from "node:path";
36499
36891
  import { getCleoHome as getCleoHome6 } from "@cleocode/paths";
36500
36892
  function globalConfigPath() {
@@ -36513,7 +36905,7 @@ function readGlobalProviderKey(provider) {
36513
36905
  function readFlatAnthropicKey() {
36514
36906
  try {
36515
36907
  const keyFile = join21(getCleoHome6(), "anthropic-key");
36516
- if (!existsSync18(keyFile)) return null;
36908
+ if (!existsSync20(keyFile)) return null;
36517
36909
  const stored = readFileSync12(keyFile, "utf-8").trim();
36518
36910
  return stored || null;
36519
36911
  } catch {
@@ -36522,7 +36914,7 @@ function readFlatAnthropicKey() {
36522
36914
  }
36523
36915
  function readProviderKeyFromConfig(configFile, provider) {
36524
36916
  try {
36525
- if (!existsSync18(configFile)) return null;
36917
+ if (!existsSync20(configFile)) return null;
36526
36918
  const raw = readFileSync12(configFile, "utf-8");
36527
36919
  const config2 = JSON.parse(raw);
36528
36920
  const llm = config2["llm"];
@@ -36685,7 +37077,7 @@ function resolveProviderStatus(provider) {
36685
37077
  }
36686
37078
  function storeAnthropicApiKey(apiKey) {
36687
37079
  const dir = getCleoHome6();
36688
- if (!existsSync18(dir)) {
37080
+ if (!existsSync20(dir)) {
36689
37081
  mkdirSync6(dir, { recursive: true });
36690
37082
  }
36691
37083
  const keyFile = join21(dir, "anthropic-key");
@@ -36740,16 +37132,16 @@ var init_credentials2 = __esm({
36740
37132
  });
36741
37133
 
36742
37134
  // packages/core/src/llm/credentials-store.ts
36743
- import { chmodSync, existsSync as existsSync19, mkdirSync as mkdirSync7, readFileSync as readFileSync13, statSync as statSync4, writeFileSync as writeFileSync7 } from "node:fs";
37135
+ import { chmodSync, existsSync as existsSync21, mkdirSync as mkdirSync7, readFileSync as readFileSync13, statSync as statSync6, writeFileSync as writeFileSync7 } from "node:fs";
36744
37136
  import { dirname as dirname8, join as join22 } from "node:path";
36745
37137
  import { getCleoHome as getCleoHome7 } from "@cleocode/paths";
36746
37138
  function credentialsStorePath() {
36747
37139
  return join22(getCleoHome7(), "llm-credentials.json");
36748
37140
  }
36749
37141
  function ensureFileInitialized(path) {
36750
- if (existsSync19(path)) return;
37142
+ if (existsSync21(path)) return;
36751
37143
  const dir = dirname8(path);
36752
- if (!existsSync19(dir)) {
37144
+ if (!existsSync21(dir)) {
36753
37145
  mkdirSync7(dir, { recursive: true, mode: 448 });
36754
37146
  } else {
36755
37147
  try {
@@ -36771,7 +37163,7 @@ function ensureFileInitialized(path) {
36771
37163
  function checkPermsOnRead(path) {
36772
37164
  if (_warnedLoosePerms) return;
36773
37165
  try {
36774
- const stats = statSync4(path);
37166
+ const stats = statSync6(path);
36775
37167
  const mode = stats.mode & 511;
36776
37168
  if (mode !== 384 && mode !== 0) {
36777
37169
  logger2.warn(
@@ -37053,6 +37445,10 @@ var init_anthropic = __esm({
37053
37445
  baseUrl: "https://api.anthropic.com",
37054
37446
  defaultModel: "claude-haiku-4-5-20251001",
37055
37447
  aliases: ["claude", "anthropic-api"],
37448
+ // Hermes-parity routing/catalog fields (T11756); catalog-sourced under E8.
37449
+ tier: "frontier",
37450
+ defaultAuxModel: "claude-haiku-4-5-20251001",
37451
+ defaultMaxTokens: 4096,
37056
37452
  defaultHeaders: { "anthropic-version": "2023-06-01" },
37057
37453
  oauth: ANTHROPIC_OAUTH
37058
37454
  };
@@ -37093,6 +37489,10 @@ var init_gemini = __esm({
37093
37489
  baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
37094
37490
  defaultModel: "gemini-2.0-flash",
37095
37491
  aliases: ["google", "google-gemini"],
37492
+ // Hermes-parity routing/catalog fields (T11756); catalog-sourced under E8.
37493
+ tier: "standard",
37494
+ defaultAuxModel: "gemini-2.0-flash",
37495
+ defaultMaxTokens: 4096,
37096
37496
  envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
37097
37497
  /**
37098
37498
  * Inject `thinking_config` for Gemini's extended reasoning support.
@@ -37118,13 +37518,13 @@ var init_gemini = __esm({
37118
37518
 
37119
37519
  // packages/core/src/llm/stable-device-id.ts
37120
37520
  import { randomUUID } from "node:crypto";
37121
- import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "node:fs";
37521
+ import { existsSync as existsSync22, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "node:fs";
37122
37522
  import { dirname as dirname9, join as join23 } from "node:path";
37123
37523
  import { getCleoHome as getCleoHome8 } from "@cleocode/paths";
37124
37524
  function getStableDeviceId() {
37125
37525
  if (_cachedDeviceId !== null) return _cachedDeviceId;
37126
37526
  const path = join23(getCleoHome8(), DEVICE_ID_FILE);
37127
- if (existsSync20(path)) {
37527
+ if (existsSync22(path)) {
37128
37528
  try {
37129
37529
  const raw = readFileSync14(path, "utf-8").trim();
37130
37530
  if (raw.length > 0) {
@@ -37137,7 +37537,7 @@ function getStableDeviceId() {
37137
37537
  const fresh = randomUUID();
37138
37538
  try {
37139
37539
  const dir = dirname9(path);
37140
- if (!existsSync20(dir)) {
37540
+ if (!existsSync22(dir)) {
37141
37541
  mkdirSync8(dir, { recursive: true });
37142
37542
  }
37143
37543
  const tmpPath = `${path}.tmp.${process.pid}`;
@@ -37284,6 +37684,10 @@ var init_ollama = __esm({
37284
37684
  baseUrl: "http://localhost:11434",
37285
37685
  defaultModel: "llama3",
37286
37686
  aliases: ["ollama-local"],
37687
+ // Hermes-parity routing/catalog fields (T11756). Local on-device tier.
37688
+ tier: "local",
37689
+ defaultAuxModel: "llama3",
37690
+ defaultMaxTokens: 2048,
37287
37691
  envVars: ["OLLAMA_API_KEY", "OLLAMA_BASE_URL"],
37288
37692
  supportsThinkingBudget: false
37289
37693
  };
@@ -37322,6 +37726,12 @@ var init_openai = __esm({
37322
37726
  // to the confirmed latest OpenAI model (catalog release_date 2026-04-23).
37323
37727
  defaultModel: "gpt-5.5",
37324
37728
  aliases: ["codex", "chatgpt", "openai-codex"],
37729
+ // Hermes-parity routing/catalog fields (T11756). tier + defaultAuxModel are
37730
+ // catalog-sourced (E8 join, T11773) when a snapshot is present; these are the
37731
+ // static fallbacks used until the catalog populates them.
37732
+ tier: "frontier",
37733
+ defaultAuxModel: "gpt-5-mini",
37734
+ defaultMaxTokens: 4096,
37325
37735
  oauth: OPENAI_CODEX_OAUTH
37326
37736
  };
37327
37737
  }
@@ -37429,7 +37839,7 @@ var init_xai = __esm({
37429
37839
  });
37430
37840
 
37431
37841
  // packages/core/src/llm/provider-registry/loader.ts
37432
- import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
37842
+ import { readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
37433
37843
  import { join as join24 } from "node:path";
37434
37844
  import { pathToFileURL } from "node:url";
37435
37845
  function isPluginFile(filename) {
@@ -37484,7 +37894,7 @@ async function scanAndLoadPlugins(pluginDir, api) {
37484
37894
  }
37485
37895
  const pluginFiles = entries.filter((name15) => !name15.startsWith(".") && isPluginFile(name15)).filter((name15) => {
37486
37896
  try {
37487
- return statSync5(join24(pluginDir, name15)).isFile();
37897
+ return statSync7(join24(pluginDir, name15)).isFile();
37488
37898
  } catch {
37489
37899
  return false;
37490
37900
  }
@@ -37571,7 +37981,7 @@ var init_provider_registry = __esm({
37571
37981
  });
37572
37982
 
37573
37983
  // packages/core/src/llm/rate-limit-guard.ts
37574
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, unlinkSync as unlinkSync6 } from "node:fs";
37984
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, unlinkSync as unlinkSync6 } from "node:fs";
37575
37985
  import { join as join25 } from "node:path";
37576
37986
  import { getCleoHome as getCleoHome9 } from "@cleocode/paths";
37577
37987
  function sanitize(value) {
@@ -37627,7 +38037,7 @@ async function recordRateLimit(provider, label, opts) {
37627
38037
  }
37628
38038
  const filePath = rateLimitStatePath(provider, label);
37629
38039
  const dir = join25(getCleoHome9(), "rate-limit-state");
37630
- if (!existsSync21(dir)) {
38040
+ if (!existsSync23(dir)) {
37631
38041
  mkdirSync9(dir, { recursive: true });
37632
38042
  }
37633
38043
  const state = { resetAt, recordedAt: now, source };
@@ -38832,6 +39242,78 @@ var init_concrete_session = __esm({
38832
39242
  }
38833
39243
  });
38834
39244
 
39245
+ // packages/core/src/llm/transports/codex-oauth-headers.ts
39246
+ function extractChatGptAccountId(accessToken) {
39247
+ if (typeof accessToken !== "string" || !accessToken.trim()) return null;
39248
+ const parts = accessToken.split(".");
39249
+ if (parts.length < 2) return null;
39250
+ const payloadSegment = parts[1];
39251
+ if (!payloadSegment) return null;
39252
+ try {
39253
+ const b64 = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
39254
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
39255
+ const json3 = Buffer.from(padded, "base64").toString("utf8");
39256
+ const claims = JSON.parse(json3);
39257
+ const authClaim = claims["https://api.openai.com/auth"];
39258
+ if (authClaim && typeof authClaim === "object" && !Array.isArray(authClaim)) {
39259
+ const acctId = authClaim["chatgpt_account_id"];
39260
+ if (typeof acctId === "string" && acctId) return acctId;
39261
+ }
39262
+ } catch {
39263
+ }
39264
+ return null;
39265
+ }
39266
+ function buildCodexOAuthHeaders(accessToken) {
39267
+ const headers = {
39268
+ "User-Agent": CODEX_USER_AGENT,
39269
+ originator: "codex_cli_rs"
39270
+ };
39271
+ const acctId = extractChatGptAccountId(accessToken);
39272
+ if (acctId) headers["ChatGPT-Account-ID"] = acctId;
39273
+ return headers;
39274
+ }
39275
+ var CODEX_OAUTH_BASE_URL, CODEX_USER_AGENT;
39276
+ var init_codex_oauth_headers = __esm({
39277
+ "packages/core/src/llm/transports/codex-oauth-headers.ts"() {
39278
+ "use strict";
39279
+ CODEX_OAUTH_BASE_URL = "https://chatgpt.com/backend-api/codex";
39280
+ CODEX_USER_AGENT = "codex_cli_rs/0.0.0 (CLEO)";
39281
+ }
39282
+ });
39283
+
39284
+ // packages/core/src/llm/api-mode.ts
39285
+ var api_mode_exports = {};
39286
+ __export(api_mode_exports, {
39287
+ deriveApiWire: () => deriveApiWire
39288
+ });
39289
+ function deriveApiWire(provider, authType) {
39290
+ if (provider === "anthropic") {
39291
+ return { apiMode: "anthropic_messages", baseUrl: null };
39292
+ }
39293
+ if (provider === "kimi-code") {
39294
+ return { apiMode: "anthropic_messages", baseUrl: null };
39295
+ }
39296
+ if (provider === "bedrock") {
39297
+ return { apiMode: "bedrock_converse", baseUrl: null };
39298
+ }
39299
+ if (provider === "gemini") {
39300
+ return { apiMode: "chat_completions", baseUrl: null };
39301
+ }
39302
+ if (provider === "ollama") {
39303
+ return { apiMode: "ollama_native", baseUrl: null };
39304
+ }
39305
+ if (provider === "openai" && authType === "oauth") {
39306
+ return { apiMode: "codex_responses", baseUrl: CODEX_OAUTH_BASE_URL };
39307
+ }
39308
+ return { apiMode: "chat_completions", baseUrl: null };
39309
+ }
39310
+ var init_api_mode = __esm({
39311
+ "packages/core/src/llm/api-mode.ts"() {
39312
+ "use strict";
39313
+ init_codex_oauth_headers();
39314
+ }
39315
+ });
39316
+
38835
39317
  // packages/core/src/llm/image-routing.ts
38836
39318
  function imageSizeLimitFor(provider) {
38837
39319
  return PROVIDER_IMAGE_SIZE_LIMITS[provider.toLowerCase()] ?? Number.POSITIVE_INFINITY;
@@ -41241,45 +41723,6 @@ var init_chat_completions = __esm({
41241
41723
  }
41242
41724
  });
41243
41725
 
41244
- // packages/core/src/llm/transports/codex-oauth-headers.ts
41245
- function extractChatGptAccountId(accessToken) {
41246
- if (typeof accessToken !== "string" || !accessToken.trim()) return null;
41247
- const parts = accessToken.split(".");
41248
- if (parts.length < 2) return null;
41249
- const payloadSegment = parts[1];
41250
- if (!payloadSegment) return null;
41251
- try {
41252
- const b64 = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
41253
- const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
41254
- const json3 = Buffer.from(padded, "base64").toString("utf8");
41255
- const claims = JSON.parse(json3);
41256
- const authClaim = claims["https://api.openai.com/auth"];
41257
- if (authClaim && typeof authClaim === "object" && !Array.isArray(authClaim)) {
41258
- const acctId = authClaim["chatgpt_account_id"];
41259
- if (typeof acctId === "string" && acctId) return acctId;
41260
- }
41261
- } catch {
41262
- }
41263
- return null;
41264
- }
41265
- function buildCodexOAuthHeaders(accessToken) {
41266
- const headers = {
41267
- "User-Agent": CODEX_USER_AGENT,
41268
- originator: "codex_cli_rs"
41269
- };
41270
- const acctId = extractChatGptAccountId(accessToken);
41271
- if (acctId) headers["ChatGPT-Account-ID"] = acctId;
41272
- return headers;
41273
- }
41274
- var CODEX_OAUTH_BASE_URL, CODEX_USER_AGENT;
41275
- var init_codex_oauth_headers = __esm({
41276
- "packages/core/src/llm/transports/codex-oauth-headers.ts"() {
41277
- "use strict";
41278
- CODEX_OAUTH_BASE_URL = "https://chatgpt.com/backend-api/codex";
41279
- CODEX_USER_AGENT = "codex_cli_rs/0.0.0 (CLEO)";
41280
- }
41281
- });
41282
-
41283
41726
  // packages/core/src/llm/transports/codex-responses.ts
41284
41727
  import { OpenAI as OpenAIClient } from "openai";
41285
41728
  function extractTextContent3(message) {
@@ -70227,6 +70670,62 @@ var model_runner_exports = {};
70227
70670
  __export(model_runner_exports, {
70228
70671
  ModelRunner: () => ModelRunner
70229
70672
  });
70673
+ function buildCodexResponsesTransport(provider, credential) {
70674
+ const defaultHeaders = credential.authType === "oauth" ? { ...credential.extraHeaders, ...buildCodexOAuthHeaders(credential.token) } : { ...credential.extraHeaders };
70675
+ return new CodexResponsesTransport({
70676
+ apiKey: credential.token,
70677
+ baseUrl: credential.baseUrl ?? void 0,
70678
+ defaultHeaders: Object.keys(defaultHeaders).length ? defaultHeaders : void 0,
70679
+ provider
70680
+ });
70681
+ }
70682
+ function buildAnthropicMessagesTransport(provider, credential) {
70683
+ if (provider === "kimi-code") {
70684
+ return new AnthropicTransport({
70685
+ authToken: credential.token,
70686
+ baseUrl: credential.baseUrl ?? KIMI_CODE_BASE_URL2,
70687
+ defaultHeaders: getKimiCodeMshHeaders()
70688
+ });
70689
+ }
70690
+ const opts = credential.authType === "oauth" ? {
70691
+ authToken: credential.token,
70692
+ baseUrl: credential.baseUrl ?? void 0,
70693
+ defaultHeaders: { ...ANTHROPIC_OAUTH_HEADERS, ...credential.extraHeaders }
70694
+ } : {
70695
+ apiKey: credential.token,
70696
+ baseUrl: credential.baseUrl ?? void 0,
70697
+ defaultHeaders: Object.keys(credential.extraHeaders).length ? credential.extraHeaders : void 0
70698
+ };
70699
+ return new AnthropicTransport(opts);
70700
+ }
70701
+ function buildBedrockConverseTransport(_provider, credential) {
70702
+ return new BedrockTransport({ awsProfile: credential.awsProfile ?? void 0 });
70703
+ }
70704
+ function buildOllamaNativeTransport(_provider, credential) {
70705
+ return new OllamaTransport({
70706
+ baseUrl: credential.baseUrl ?? void 0,
70707
+ apiKey: credential.token || void 0,
70708
+ defaultHeaders: Object.keys(credential.extraHeaders).length ? credential.extraHeaders : void 0
70709
+ });
70710
+ }
70711
+ function buildChatCompletionsTransport(provider, credential) {
70712
+ if (provider === "gemini") {
70713
+ return new GeminiTransport({
70714
+ apiKey: credential.token,
70715
+ baseUrl: credential.baseUrl ?? void 0
70716
+ });
70717
+ }
70718
+ const defaultHeaders = { ...credential.extraHeaders };
70719
+ if (credential.authType === "oauth") {
70720
+ defaultHeaders["Authorization"] = `Bearer ${credential.token}`;
70721
+ }
70722
+ return new ChatCompletionsTransport({
70723
+ apiKey: credential.token,
70724
+ baseUrl: credential.baseUrl ?? void 0,
70725
+ defaultHeaders: Object.keys(defaultHeaders).length ? defaultHeaders : void 0,
70726
+ provider
70727
+ });
70728
+ }
70230
70729
  function descriptorToCredential(d) {
70231
70730
  const authType = d.authType === "oauth" ? "oauth" : d.authType === "aws_sdk" ? "aws_sdk" : "api_key";
70232
70731
  return {
@@ -70241,11 +70740,12 @@ function descriptorToCredential(d) {
70241
70740
  awsProfile: null
70242
70741
  };
70243
70742
  }
70244
- var logger3, KIMI_CODE_BASE_URL2, ANTHROPIC_OAUTH_HEADERS, OLLAMA_OPENAI_COMPAT_BASE_URL, ModelRunner;
70743
+ var logger3, KIMI_CODE_BASE_URL2, ANTHROPIC_OAUTH_HEADERS, TRANSPORT_ADAPTERS, OLLAMA_OPENAI_COMPAT_BASE_URL, ModelRunner;
70245
70744
  var init_model_runner = __esm({
70246
70745
  "packages/core/src/llm/model-runner.ts"() {
70247
70746
  "use strict";
70248
70747
  init_logger2();
70748
+ init_api_mode();
70249
70749
  init_concrete_session();
70250
70750
  init_kimi_code();
70251
70751
  init_anthropic2();
@@ -70260,6 +70760,13 @@ var init_model_runner = __esm({
70260
70760
  ANTHROPIC_OAUTH_HEADERS = {
70261
70761
  "anthropic-beta": "oauth-2025-04-20"
70262
70762
  };
70763
+ TRANSPORT_ADAPTERS = {
70764
+ codex_responses: buildCodexResponsesTransport,
70765
+ anthropic_messages: buildAnthropicMessagesTransport,
70766
+ bedrock_converse: buildBedrockConverseTransport,
70767
+ ollama_native: buildOllamaNativeTransport,
70768
+ chat_completions: buildChatCompletionsTransport
70769
+ };
70263
70770
  OLLAMA_OPENAI_COMPAT_BASE_URL = "http://localhost:11434/v1";
70264
70771
  ModelRunner = {
70265
70772
  /**
@@ -70295,64 +70802,8 @@ var init_model_runner = __esm({
70295
70802
  * @returns A wire-level transport.
70296
70803
  */
70297
70804
  buildTransportFromCredential(provider, credential, apiMode) {
70298
- if (apiMode === "codex_responses") {
70299
- const defaultHeaders2 = credential.authType === "oauth" ? { ...credential.extraHeaders, ...buildCodexOAuthHeaders(credential.token) } : { ...credential.extraHeaders };
70300
- return new CodexResponsesTransport({
70301
- apiKey: credential.token,
70302
- baseUrl: credential.baseUrl ?? void 0,
70303
- defaultHeaders: Object.keys(defaultHeaders2).length ? defaultHeaders2 : void 0,
70304
- provider
70305
- });
70306
- }
70307
- if (provider === "anthropic") {
70308
- const opts = credential.authType === "oauth" ? {
70309
- authToken: credential.token,
70310
- baseUrl: credential.baseUrl ?? void 0,
70311
- // OAuth REQUIRES the beta opt-in header. The canonical header is
70312
- // the default; a caller's own extraHeaders override it if set.
70313
- defaultHeaders: { ...ANTHROPIC_OAUTH_HEADERS, ...credential.extraHeaders }
70314
- } : {
70315
- apiKey: credential.token,
70316
- baseUrl: credential.baseUrl ?? void 0,
70317
- defaultHeaders: Object.keys(credential.extraHeaders).length ? credential.extraHeaders : void 0
70318
- };
70319
- return new AnthropicTransport(opts);
70320
- }
70321
- if (provider === "kimi-code") {
70322
- return new AnthropicTransport({
70323
- authToken: credential.token,
70324
- baseUrl: credential.baseUrl ?? KIMI_CODE_BASE_URL2,
70325
- defaultHeaders: getKimiCodeMshHeaders()
70326
- });
70327
- }
70328
- if (provider === "bedrock") {
70329
- return new BedrockTransport({
70330
- awsProfile: credential.awsProfile ?? void 0
70331
- });
70332
- }
70333
- if (provider === "gemini") {
70334
- return new GeminiTransport({
70335
- apiKey: credential.token,
70336
- baseUrl: credential.baseUrl ?? void 0
70337
- });
70338
- }
70339
- if (provider === "ollama") {
70340
- return new OllamaTransport({
70341
- baseUrl: credential.baseUrl ?? void 0,
70342
- apiKey: credential.token || void 0,
70343
- defaultHeaders: Object.keys(credential.extraHeaders).length ? credential.extraHeaders : void 0
70344
- });
70345
- }
70346
- const defaultHeaders = { ...credential.extraHeaders };
70347
- if (credential.authType === "oauth") {
70348
- defaultHeaders["Authorization"] = `Bearer ${credential.token}`;
70349
- }
70350
- return new ChatCompletionsTransport({
70351
- apiKey: credential.token,
70352
- baseUrl: credential.baseUrl ?? void 0,
70353
- defaultHeaders: Object.keys(defaultHeaders).length ? defaultHeaders : void 0,
70354
- provider
70355
- });
70805
+ const mode = apiMode ?? deriveApiWire(provider, null).apiMode;
70806
+ return TRANSPORT_ADAPTERS[mode](provider, credential);
70356
70807
  },
70357
70808
  /**
70358
70809
  * Construct a Vercel AI SDK {@link LanguageModel} for the sentient/adapters
@@ -70422,39 +70873,6 @@ var init_model_runner = __esm({
70422
70873
  }
70423
70874
  });
70424
70875
 
70425
- // packages/core/src/llm/api-mode.ts
70426
- var api_mode_exports = {};
70427
- __export(api_mode_exports, {
70428
- deriveApiWire: () => deriveApiWire
70429
- });
70430
- function deriveApiWire(provider, authType) {
70431
- if (provider === "anthropic") {
70432
- return { apiMode: "anthropic_messages", baseUrl: null };
70433
- }
70434
- if (provider === "kimi-code") {
70435
- return { apiMode: "anthropic_messages", baseUrl: null };
70436
- }
70437
- if (provider === "bedrock") {
70438
- return { apiMode: "bedrock_converse", baseUrl: null };
70439
- }
70440
- if (provider === "gemini") {
70441
- return { apiMode: "chat_completions", baseUrl: null };
70442
- }
70443
- if (provider === "ollama") {
70444
- return { apiMode: "ollama_native", baseUrl: null };
70445
- }
70446
- if (provider === "openai" && authType === "oauth") {
70447
- return { apiMode: "codex_responses", baseUrl: CODEX_OAUTH_BASE_URL };
70448
- }
70449
- return { apiMode: "chat_completions", baseUrl: null };
70450
- }
70451
- var init_api_mode = __esm({
70452
- "packages/core/src/llm/api-mode.ts"() {
70453
- "use strict";
70454
- init_codex_oauth_headers();
70455
- }
70456
- });
70457
-
70458
70876
  // packages/core/src/llm/transports/anthropic-client-factory.ts
70459
70877
  import { Anthropic as Anthropic2 } from "@anthropic-ai/sdk";
70460
70878
  function buildAnthropicClient(credential) {