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