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