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