@cleocode/core 2026.6.18 → 2026.7.1
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/docs/export-document.js +1173 -334
- package/dist/docs/export-document.js.map +3 -3
- package/dist/lifecycle/effective-stage.js +3 -3
- package/dist/lifecycle/rollup.js +3 -3
- package/dist/llm/plugin-facade.js +1218 -383
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
- package/dist/memory/brain-writer-handlers.js +39 -0
- package/dist/memory/brain-writer-handlers.js.map +1 -1
- package/dist/orchestrate/admission.d.ts +65 -0
- package/dist/orchestrate/admission.d.ts.map +1 -0
- package/dist/orchestrate/admission.js +54 -0
- package/dist/orchestrate/admission.js.map +1 -0
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +10 -0
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +37 -8
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +24 -0
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/release/pr-evidence.d.ts +14 -3
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +44 -4
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/resources/governor.d.ts +133 -0
- package/dist/resources/governor.d.ts.map +1 -0
- package/dist/resources/governor.js +377 -0
- package/dist/resources/governor.js.map +1 -0
- package/dist/resources/supervisor-admit.d.ts +42 -0
- package/dist/resources/supervisor-admit.d.ts.map +1 -0
- package/dist/resources/supervisor-admit.js +136 -0
- package/dist/resources/supervisor-admit.js.map +1 -0
- package/dist/selfimprove/fix-gen.js +1216 -381
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/tick.d.ts.map +1 -1
- package/dist/sentient/tick.js +29 -0
- package/dist/sentient/tick.js.map +1 -1
- package/dist/store/agent-registry-store.d.ts.map +1 -1
- package/dist/store/agent-registry-store.js +17 -2
- package/dist/store/agent-registry-store.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +35 -0
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +15 -0
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +2 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +17 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +98 -98
- package/dist/tasks/compute-task-view.js +2 -2
- package/dist/tasks/evidence.d.ts +19 -0
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +32 -2
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/tool-semaphore.d.ts +25 -0
- package/dist/tasks/tool-semaphore.d.ts.map +1 -1
- package/dist/tasks/tool-semaphore.js +68 -1
- package/dist/tasks/tool-semaphore.js.map +1 -1
- package/dist/tasks/update.d.ts +2 -0
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +1 -0
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +33 -7
- package/dist/validation/engine-ops.js.map +1 -1
- package/package.json +12 -12
|
@@ -1890,6 +1890,7 @@ var init_tasks = __esm({
|
|
|
1890
1890
|
severity: { type: "string", enum: ["P0", "P1", "P2", "P3"] },
|
|
1891
1891
|
reason: { type: "string" },
|
|
1892
1892
|
dependsWaiver: { type: "string" },
|
|
1893
|
+
blockedBy: { type: "string" },
|
|
1893
1894
|
clearBlockedBy: { type: "boolean" },
|
|
1894
1895
|
relates: {
|
|
1895
1896
|
type: "array",
|
|
@@ -4589,6 +4590,12 @@ var init_operations_registry = __esm({
|
|
|
4589
4590
|
required: false,
|
|
4590
4591
|
description: "Justification to waive the critical-priority --depends requirement"
|
|
4591
4592
|
},
|
|
4593
|
+
{
|
|
4594
|
+
name: "blockedBy",
|
|
4595
|
+
type: "string",
|
|
4596
|
+
required: false,
|
|
4597
|
+
description: "Set the free-text blockedBy reason (gh#1106)"
|
|
4598
|
+
},
|
|
4592
4599
|
{
|
|
4593
4600
|
name: "clearBlockedBy",
|
|
4594
4601
|
type: "boolean",
|
|
@@ -12412,7 +12419,7 @@ var init_lafs = __esm({
|
|
|
12412
12419
|
|
|
12413
12420
|
// packages/contracts/src/lease-ipc/messages.ts
|
|
12414
12421
|
import { z as z11 } from "zod";
|
|
12415
|
-
var LeaseScopeSchema, LeaseLaneSchema, LeaseAcquireRequestSchema, LeaseReleaseRequestSchema, LeaseRenewRequestSchema, RateCheckRequestSchema, ToolGrantRequestSchema, QueuePriorityClassSchema, QueueAdmitRequestSchema, WorkerHeartbeatRequestSchema, LeaseIpcRequestSchema, LeaseGrantedResponseSchema, LeaseQueuedResponseSchema, LeaseDeniedResponseSchema, RateResultResponseSchema, ToolGrantedResponseSchema, LeaseRevokedResponseSchema, ChildKilledUnresponsiveResponseSchema, QueueAdmitDispositionSchema, QueueAdmitResultResponseSchema, HeartbeatAckResponseSchema, LeaseErrorResponseSchema, LeaseIpcResponseSchema, LeaseIpcRequestEnvelopeSchema, LeaseIpcResponseEnvelopeSchema, LeaseIpcEnvelopeSchema, LEASE_IPC_REQUEST_KINDS, LEASE_IPC_RESPONSE_KINDS, LEASE_IPC_MESSAGE_KINDS;
|
|
12422
|
+
var LeaseScopeSchema, LeaseLaneSchema, LeaseAcquireRequestSchema, LeaseReleaseRequestSchema, LeaseRenewRequestSchema, RateCheckRequestSchema, ToolGrantRequestSchema, QueuePriorityClassSchema, QueueAdmitRequestSchema, WorkerHeartbeatRequestSchema, ResourceAdmitRequestSchema, ResourceReleaseRequestSchema, LeaseIpcRequestSchema, LeaseGrantedResponseSchema, LeaseQueuedResponseSchema, LeaseDeniedResponseSchema, RateResultResponseSchema, ToolGrantedResponseSchema, LeaseRevokedResponseSchema, ChildKilledUnresponsiveResponseSchema, QueueAdmitDispositionSchema, QueueAdmitResultResponseSchema, HeartbeatAckResponseSchema, ResourceAdmitDispositionSchema, ResourceAdmitResultResponseSchema, ResourceReleaseResultResponseSchema, LeaseErrorResponseSchema, LeaseIpcResponseSchema, LeaseIpcRequestEnvelopeSchema, LeaseIpcResponseEnvelopeSchema, LeaseIpcEnvelopeSchema, LEASE_IPC_REQUEST_KINDS, LEASE_IPC_RESPONSE_KINDS, LEASE_IPC_MESSAGE_KINDS;
|
|
12416
12423
|
var init_messages2 = __esm({
|
|
12417
12424
|
"packages/contracts/src/lease-ipc/messages.ts"() {
|
|
12418
12425
|
"use strict";
|
|
@@ -12497,6 +12504,24 @@ var init_messages2 = __esm({
|
|
|
12497
12504
|
/** The worker's own view of whether it is currently inside an LLM call. */
|
|
12498
12505
|
in_flight_llm: z11.boolean()
|
|
12499
12506
|
}).strict();
|
|
12507
|
+
ResourceAdmitRequestSchema = z11.object({
|
|
12508
|
+
/** Tag discriminating this request variant. */
|
|
12509
|
+
kind: z11.literal("resource_admit"),
|
|
12510
|
+
/** The resource class (e.g. `"db-heavy"`, `"full-build"`, `"test-run"`). */
|
|
12511
|
+
class: z11.string().min(1),
|
|
12512
|
+
/** The caller holding the slot — process/worktree-unique; the release key. */
|
|
12513
|
+
holder_id: z11.string().min(1),
|
|
12514
|
+
/** The client-computed budget: the max concurrent holders for this class. */
|
|
12515
|
+
budget: z11.number().int().nonnegative()
|
|
12516
|
+
}).strict();
|
|
12517
|
+
ResourceReleaseRequestSchema = z11.object({
|
|
12518
|
+
/** Tag discriminating this request variant. */
|
|
12519
|
+
kind: z11.literal("resource_release"),
|
|
12520
|
+
/** The resource class the slot belongs to. */
|
|
12521
|
+
class: z11.string().min(1),
|
|
12522
|
+
/** The holder that was admitted (matches the admit `holder_id`). */
|
|
12523
|
+
holder_id: z11.string().min(1)
|
|
12524
|
+
}).strict();
|
|
12500
12525
|
LeaseIpcRequestSchema = z11.discriminatedUnion("kind", [
|
|
12501
12526
|
LeaseAcquireRequestSchema,
|
|
12502
12527
|
LeaseReleaseRequestSchema,
|
|
@@ -12504,7 +12529,9 @@ var init_messages2 = __esm({
|
|
|
12504
12529
|
RateCheckRequestSchema,
|
|
12505
12530
|
ToolGrantRequestSchema,
|
|
12506
12531
|
QueueAdmitRequestSchema,
|
|
12507
|
-
WorkerHeartbeatRequestSchema
|
|
12532
|
+
WorkerHeartbeatRequestSchema,
|
|
12533
|
+
ResourceAdmitRequestSchema,
|
|
12534
|
+
ResourceReleaseRequestSchema
|
|
12508
12535
|
]);
|
|
12509
12536
|
LeaseGrantedResponseSchema = z11.object({
|
|
12510
12537
|
/** Tag discriminating this response variant. */
|
|
@@ -12605,6 +12632,25 @@ var init_messages2 = __esm({
|
|
|
12605
12632
|
/** Tag discriminating this response variant. */
|
|
12606
12633
|
kind: z11.literal("heartbeat_ack")
|
|
12607
12634
|
}).strict();
|
|
12635
|
+
ResourceAdmitDispositionSchema = z11.enum(["admitted", "deferred"]);
|
|
12636
|
+
ResourceAdmitResultResponseSchema = z11.object({
|
|
12637
|
+
/** Tag discriminating this response variant. */
|
|
12638
|
+
kind: z11.literal("resource_admit_result"),
|
|
12639
|
+
/** Whether the heavy op was admitted or deferred. */
|
|
12640
|
+
disposition: ResourceAdmitDispositionSchema,
|
|
12641
|
+
/** Back-off in ms before re-requesting (0 when admitted). */
|
|
12642
|
+
retry_after_ms: z11.number().int().nonnegative(),
|
|
12643
|
+
/** Remaining slots for the class after this decision. */
|
|
12644
|
+
slots_remaining: z11.number().int().nonnegative()
|
|
12645
|
+
}).strict();
|
|
12646
|
+
ResourceReleaseResultResponseSchema = z11.object({
|
|
12647
|
+
/** Tag discriminating this response variant. */
|
|
12648
|
+
kind: z11.literal("resource_release_result"),
|
|
12649
|
+
/** Whether a held slot was actually released (false = no such holder). */
|
|
12650
|
+
released: z11.boolean(),
|
|
12651
|
+
/** Remaining in-flight holders for the class after the release. */
|
|
12652
|
+
slots_remaining: z11.number().int().nonnegative()
|
|
12653
|
+
}).strict();
|
|
12608
12654
|
LeaseErrorResponseSchema = z11.object({
|
|
12609
12655
|
/** Tag discriminating this response variant. */
|
|
12610
12656
|
kind: z11.literal("error"),
|
|
@@ -12623,6 +12669,8 @@ var init_messages2 = __esm({
|
|
|
12623
12669
|
ChildKilledUnresponsiveResponseSchema,
|
|
12624
12670
|
QueueAdmitResultResponseSchema,
|
|
12625
12671
|
HeartbeatAckResponseSchema,
|
|
12672
|
+
ResourceAdmitResultResponseSchema,
|
|
12673
|
+
ResourceReleaseResultResponseSchema,
|
|
12626
12674
|
LeaseErrorResponseSchema
|
|
12627
12675
|
]);
|
|
12628
12676
|
LeaseIpcRequestEnvelopeSchema = z11.object({
|
|
@@ -12656,7 +12704,9 @@ var init_messages2 = __esm({
|
|
|
12656
12704
|
"rate_check",
|
|
12657
12705
|
"tool_grant",
|
|
12658
12706
|
"queue_admit",
|
|
12659
|
-
"worker_heartbeat"
|
|
12707
|
+
"worker_heartbeat",
|
|
12708
|
+
"resource_admit",
|
|
12709
|
+
"resource_release"
|
|
12660
12710
|
];
|
|
12661
12711
|
LEASE_IPC_RESPONSE_KINDS = [
|
|
12662
12712
|
"lease_granted",
|
|
@@ -12668,6 +12718,8 @@ var init_messages2 = __esm({
|
|
|
12668
12718
|
"child_killed_unresponsive",
|
|
12669
12719
|
"queue_admit_result",
|
|
12670
12720
|
"heartbeat_ack",
|
|
12721
|
+
"resource_admit_result",
|
|
12722
|
+
"resource_release_result",
|
|
12671
12723
|
"error"
|
|
12672
12724
|
];
|
|
12673
12725
|
LEASE_IPC_MESSAGE_KINDS = [
|
|
@@ -12678,9 +12730,11 @@ var init_messages2 = __esm({
|
|
|
12678
12730
|
});
|
|
12679
12731
|
|
|
12680
12732
|
// packages/contracts/src/lease-ipc/version.ts
|
|
12733
|
+
var LEASE_IPC_PROTOCOL_VERSION;
|
|
12681
12734
|
var init_version2 = __esm({
|
|
12682
12735
|
"packages/contracts/src/lease-ipc/version.ts"() {
|
|
12683
12736
|
"use strict";
|
|
12737
|
+
LEASE_IPC_PROTOCOL_VERSION = "1.2.0";
|
|
12684
12738
|
}
|
|
12685
12739
|
});
|
|
12686
12740
|
|
|
@@ -13884,6 +13938,25 @@ var init_render = __esm({
|
|
|
13884
13938
|
}
|
|
13885
13939
|
});
|
|
13886
13940
|
|
|
13941
|
+
// packages/contracts/src/resource-governor.ts
|
|
13942
|
+
var RESOURCE_CLASSES, DEFAULT_RESOURCE_RETRY_AFTER_MS;
|
|
13943
|
+
var init_resource_governor = __esm({
|
|
13944
|
+
"packages/contracts/src/resource-governor.ts"() {
|
|
13945
|
+
"use strict";
|
|
13946
|
+
RESOURCE_CLASSES = Object.freeze([
|
|
13947
|
+
"interactive-cli",
|
|
13948
|
+
"agent-session",
|
|
13949
|
+
"llm-call",
|
|
13950
|
+
"test-run",
|
|
13951
|
+
"scoped-build",
|
|
13952
|
+
"full-build",
|
|
13953
|
+
"db-heavy",
|
|
13954
|
+
"background-autonomous"
|
|
13955
|
+
]);
|
|
13956
|
+
DEFAULT_RESOURCE_RETRY_AFTER_MS = 2e3;
|
|
13957
|
+
}
|
|
13958
|
+
});
|
|
13959
|
+
|
|
13887
13960
|
// packages/contracts/src/session.ts
|
|
13888
13961
|
var init_session2 = __esm({
|
|
13889
13962
|
"packages/contracts/src/session.ts"() {
|
|
@@ -15635,6 +15708,7 @@ var init_src = __esm({
|
|
|
15635
15708
|
init_evidence_atoms();
|
|
15636
15709
|
init_plan();
|
|
15637
15710
|
init_render();
|
|
15711
|
+
init_resource_governor();
|
|
15638
15712
|
init_session2();
|
|
15639
15713
|
init_session_journal();
|
|
15640
15714
|
init_status_registry();
|
|
@@ -17548,7 +17622,7 @@ var init_sql = __esm({
|
|
|
17548
17622
|
return new SQL([new StringChunk(str)]);
|
|
17549
17623
|
}
|
|
17550
17624
|
_sql.raw = raw;
|
|
17551
|
-
function
|
|
17625
|
+
function join25(chunks, separator) {
|
|
17552
17626
|
const result = [];
|
|
17553
17627
|
for (const [i, chunk] of chunks.entries()) {
|
|
17554
17628
|
if (i > 0 && separator !== void 0) result.push(separator);
|
|
@@ -17556,7 +17630,7 @@ var init_sql = __esm({
|
|
|
17556
17630
|
}
|
|
17557
17631
|
return new SQL(result);
|
|
17558
17632
|
}
|
|
17559
|
-
_sql.join =
|
|
17633
|
+
_sql.join = join25;
|
|
17560
17634
|
function identifier(value) {
|
|
17561
17635
|
return new Name(value);
|
|
17562
17636
|
}
|
|
@@ -19943,7 +20017,7 @@ var init_select2 = __esm({
|
|
|
19943
20017
|
const baseTableName = this.tableName;
|
|
19944
20018
|
const tableName = getTableLikeName(table);
|
|
19945
20019
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
19946
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
20020
|
+
if (typeof tableName === "string" && this.config.joins?.some((join25) => join25.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
19947
20021
|
if (!this.isPartialSelect) {
|
|
19948
20022
|
if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") this.config.fields = { [baseTableName]: this.config.fields };
|
|
19949
20023
|
if (typeof tableName === "string" && !is(table, SQL)) {
|
|
@@ -23753,7 +23827,7 @@ var init_dialect = __esm({
|
|
|
23753
23827
|
if (!joins2) return;
|
|
23754
23828
|
const withEntries = Object.entries(joins2).filter(([_, v]) => v);
|
|
23755
23829
|
if (!withEntries.length) return;
|
|
23756
|
-
return sql.join(withEntries.map(([k,
|
|
23830
|
+
return sql.join(withEntries.map(([k, join25]) => {
|
|
23757
23831
|
const relation = tableConfig.relations[k];
|
|
23758
23832
|
const isSingle2 = is(relation, One);
|
|
23759
23833
|
const targetTable = aliasedTable(relation.targetTable, `d${currentDepth + 1}`);
|
|
@@ -23764,7 +23838,7 @@ var init_dialect = __esm({
|
|
|
23764
23838
|
table: targetTable,
|
|
23765
23839
|
mode: isSingle2 ? "first" : "many",
|
|
23766
23840
|
schema,
|
|
23767
|
-
queryConfig:
|
|
23841
|
+
queryConfig: join25,
|
|
23768
23842
|
tableConfig: schema[relation.targetTableName],
|
|
23769
23843
|
relationWhere: filter,
|
|
23770
23844
|
isNested: true,
|
|
@@ -23778,7 +23852,7 @@ var init_dialect = __esm({
|
|
|
23778
23852
|
key: k,
|
|
23779
23853
|
selection: innerQuery.selection,
|
|
23780
23854
|
isArray: !isSingle2,
|
|
23781
|
-
isOptional: (relation.optional ?? false) ||
|
|
23855
|
+
isOptional: (relation.optional ?? false) || join25 !== true && !!join25.where
|
|
23782
23856
|
});
|
|
23783
23857
|
const jsonColumns = sql.join(innerQuery.selection.map((s) => {
|
|
23784
23858
|
return sql`${sql.raw(this.escapeString(s.key))}, ${s.selection ? sql`${jsonb3}(${sql.identifier(s.key)})` : sql.identifier(s.key)}`;
|
|
@@ -24599,7 +24673,7 @@ var init_update = __esm({
|
|
|
24599
24673
|
createJoin(joinType) {
|
|
24600
24674
|
return ((table, on) => {
|
|
24601
24675
|
const tableName = getTableLikeName(table);
|
|
24602
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
24676
|
+
if (typeof tableName === "string" && this.config.joins.some((join25) => join25.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
24603
24677
|
if (typeof on === "function") {
|
|
24604
24678
|
const from = this.config.from ? is(table, SQLiteTable) ? table[Table.Symbol.Columns] : is(table, Subquery) ? table._.selectedFields : is(table, SQLiteViewBase) ? table[ViewBaseConfig].selectedFields : void 0 : void 0;
|
|
24605
24679
|
on = on(new Proxy(this.config.table[Table.Symbol.Columns], new SelectionProxyHandler({
|
|
@@ -25751,8 +25825,8 @@ function probeAndMarkApplied(nativeDb, migration, logSubsystem, eliminatedTables
|
|
|
25751
25825
|
});
|
|
25752
25826
|
if (allAltersPresent && allTablesPresent && allIndexesPresent && allTriggersPresent) {
|
|
25753
25827
|
insertJournalEntry(nativeDb, migration.hash, migration.folderMillis, migration.name ?? "");
|
|
25754
|
-
const
|
|
25755
|
-
|
|
25828
|
+
const log12 = getLogger(logSubsystem);
|
|
25829
|
+
log12.debug(
|
|
25756
25830
|
{
|
|
25757
25831
|
migration: migration.name,
|
|
25758
25832
|
alters: alterTargets.length,
|
|
@@ -25821,14 +25895,14 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25821
25895
|
if (hasOrphanedEntries) {
|
|
25822
25896
|
const dbHashes = new Set(dbEntries.map((e) => e.hash));
|
|
25823
25897
|
const allLocalHashesPresentInDb = localMigrations.every((m) => dbHashes.has(m.hash));
|
|
25824
|
-
const
|
|
25898
|
+
const log12 = getLogger(logSubsystem);
|
|
25825
25899
|
if (allLocalHashesPresentInDb) {
|
|
25826
|
-
|
|
25900
|
+
log12.debug(
|
|
25827
25901
|
{ extra: orphanedEntries.length },
|
|
25828
25902
|
`Migration journal has ${orphanedEntries.length} entries for migrations not known to this install (DB is ahead). Skipping reconciliation.`
|
|
25829
25903
|
);
|
|
25830
25904
|
} else {
|
|
25831
|
-
|
|
25905
|
+
log12.warn(
|
|
25832
25906
|
{ orphaned: orphanedEntries.length },
|
|
25833
25907
|
`Detected ${orphanedEntries.length} true-orphan journal entries from a previous CLEO lineage. Reconciling via DDL probe.`
|
|
25834
25908
|
);
|
|
@@ -25864,8 +25938,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25864
25938
|
if (alterMatches.length === 0) {
|
|
25865
25939
|
const stripped = stripSqlComments(fullSql).trim();
|
|
25866
25940
|
if (stripped === "") {
|
|
25867
|
-
const
|
|
25868
|
-
|
|
25941
|
+
const log12 = getLogger(logSubsystem);
|
|
25942
|
+
log12.debug(
|
|
25869
25943
|
{ migration: migration.name },
|
|
25870
25944
|
`Migration ${migration.name} is a comment-only baseline marker \u2014 marking applied on existing DB.`
|
|
25871
25945
|
);
|
|
@@ -25904,8 +25978,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25904
25978
|
}
|
|
25905
25979
|
}
|
|
25906
25980
|
if (missingColumns.length === 0) {
|
|
25907
|
-
const
|
|
25908
|
-
|
|
25981
|
+
const log12 = getLogger(logSubsystem);
|
|
25982
|
+
log12.warn(
|
|
25909
25983
|
{ migration: migration.name, columns: alterMatches },
|
|
25910
25984
|
`Detected partially-applied migration ${migration.name} \u2014 columns exist but journal entry missing. Auto-reconciling.`
|
|
25911
25985
|
);
|
|
@@ -25913,8 +25987,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25913
25987
|
continue;
|
|
25914
25988
|
}
|
|
25915
25989
|
if (existingColumns.length > 0 && missingColumns.length > 0) {
|
|
25916
|
-
const
|
|
25917
|
-
|
|
25990
|
+
const log12 = getLogger(logSubsystem);
|
|
25991
|
+
log12.warn(
|
|
25918
25992
|
{
|
|
25919
25993
|
migration: migration.name,
|
|
25920
25994
|
existingColumns: existingColumns.map((c) => `${c.table}.${c.column}`),
|
|
@@ -25926,12 +26000,12 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25926
26000
|
if (!tableExists(nativeDb, table)) continue;
|
|
25927
26001
|
try {
|
|
25928
26002
|
nativeDb.exec(`ALTER TABLE ${table} ADD COLUMN ${column}${ddl ? ` ${ddl}` : ""}`);
|
|
25929
|
-
|
|
26003
|
+
log12.warn(
|
|
25930
26004
|
{ migration: migration.name, table, column },
|
|
25931
26005
|
`T920: Added missing column ${table}.${column} to complete partial migration.`
|
|
25932
26006
|
);
|
|
25933
26007
|
} catch {
|
|
25934
|
-
|
|
26008
|
+
log12.warn(
|
|
25935
26009
|
{ migration: migration.name, table, column },
|
|
25936
26010
|
`T920: Could not add missing column ${table}.${column} \u2014 will let Drizzle migrate() handle it.`
|
|
25937
26011
|
);
|
|
@@ -25951,8 +26025,8 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable2, logSubsys
|
|
|
25951
26025
|
for (const entry of unnamedEntries) {
|
|
25952
26026
|
const migrationName = hashToName.get(entry.hash);
|
|
25953
26027
|
if (!migrationName) continue;
|
|
25954
|
-
const
|
|
25955
|
-
|
|
26028
|
+
const log12 = getLogger(logSubsystem);
|
|
26029
|
+
log12.debug(
|
|
25956
26030
|
{ id: entry.id, hash: entry.hash, name: migrationName },
|
|
25957
26031
|
`Backfilling missing name on journal entry id=${entry.id} (Drizzle v1 beta legacy compat).`
|
|
25958
26032
|
);
|
|
@@ -26102,13 +26176,13 @@ function ensureColumns(nativeDb, tableName, requiredColumns, logSubsystem, conte
|
|
|
26102
26176
|
const existingCols = new Set(columns.map((c) => c.name));
|
|
26103
26177
|
for (const req of requiredColumns) {
|
|
26104
26178
|
if (!existingCols.has(req.name)) {
|
|
26105
|
-
const
|
|
26179
|
+
const log12 = getLogger(logSubsystem);
|
|
26106
26180
|
const message = `Adding missing column ${tableName}.${req.name} via ALTER TABLE`;
|
|
26107
26181
|
const fields = { column: req.name, context };
|
|
26108
26182
|
if (context === "fresh") {
|
|
26109
|
-
|
|
26183
|
+
log12.error(fields, `${message} \u2014 MIGRATION DEFECT (fresh DB should not need repair)`);
|
|
26110
26184
|
} else {
|
|
26111
|
-
|
|
26185
|
+
log12.warn(fields, message);
|
|
26112
26186
|
}
|
|
26113
26187
|
nativeDb.exec(`ALTER TABLE ${tableName} ADD COLUMN ${req.name} ${req.ddl}`);
|
|
26114
26188
|
}
|
|
@@ -35301,11 +35375,750 @@ var init_cleo_global = __esm({
|
|
|
35301
35375
|
}
|
|
35302
35376
|
});
|
|
35303
35377
|
|
|
35304
|
-
// packages/core/src/
|
|
35378
|
+
// packages/core/src/resources/linux-backend.ts
|
|
35379
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
35380
|
+
function parsePressureLine(line2) {
|
|
35381
|
+
const avg10Match = /avg10=([\d.]+)/.exec(line2);
|
|
35382
|
+
const avg60Match = /avg60=([\d.]+)/.exec(line2);
|
|
35383
|
+
const avg300Match = /avg300=([\d.]+)/.exec(line2);
|
|
35384
|
+
const totalMatch = /total=(\d+)/.exec(line2);
|
|
35385
|
+
if (!avg10Match?.[1] || !avg60Match?.[1] || !avg300Match?.[1] || !totalMatch?.[1]) {
|
|
35386
|
+
return null;
|
|
35387
|
+
}
|
|
35388
|
+
return {
|
|
35389
|
+
avg10: parseFloat(avg10Match[1]),
|
|
35390
|
+
avg60: parseFloat(avg60Match[1]),
|
|
35391
|
+
avg300: parseFloat(avg300Match[1]),
|
|
35392
|
+
totalUs: parseInt(totalMatch[1], 10)
|
|
35393
|
+
};
|
|
35394
|
+
}
|
|
35395
|
+
function parsePsiFile(content) {
|
|
35396
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
35397
|
+
let some = null;
|
|
35398
|
+
let full = null;
|
|
35399
|
+
for (const line2 of lines) {
|
|
35400
|
+
if (line2.startsWith("some ")) {
|
|
35401
|
+
some = parsePressureLine(line2);
|
|
35402
|
+
} else if (line2.startsWith("full ")) {
|
|
35403
|
+
full = parsePressureLine(line2);
|
|
35404
|
+
}
|
|
35405
|
+
}
|
|
35406
|
+
if (!some) {
|
|
35407
|
+
return null;
|
|
35408
|
+
}
|
|
35409
|
+
return { some, full };
|
|
35410
|
+
}
|
|
35411
|
+
function parseMemAvailable(content) {
|
|
35412
|
+
for (const line2 of content.split("\n")) {
|
|
35413
|
+
if (line2.startsWith("MemAvailable:")) {
|
|
35414
|
+
const match = /MemAvailable:\s+(\d+)\s+kB/.exec(line2);
|
|
35415
|
+
if (match?.[1]) {
|
|
35416
|
+
return parseInt(match[1], 10) * 1024;
|
|
35417
|
+
}
|
|
35418
|
+
}
|
|
35419
|
+
}
|
|
35420
|
+
return null;
|
|
35421
|
+
}
|
|
35422
|
+
function parseSmapsRollup(content) {
|
|
35423
|
+
let rssKb = null;
|
|
35424
|
+
let pssKb = null;
|
|
35425
|
+
for (const line2 of content.split("\n")) {
|
|
35426
|
+
if (line2.startsWith("Rss:")) {
|
|
35427
|
+
const m = /Rss:\s+(\d+)\s+kB/.exec(line2);
|
|
35428
|
+
if (m?.[1]) rssKb = parseInt(m[1], 10);
|
|
35429
|
+
} else if (line2.startsWith("Pss:")) {
|
|
35430
|
+
const m = /Pss:\s+(\d+)\s+kB/.exec(line2);
|
|
35431
|
+
if (m?.[1]) pssKb = parseInt(m[1], 10);
|
|
35432
|
+
}
|
|
35433
|
+
}
|
|
35434
|
+
if (rssKb === null || pssKb === null) return null;
|
|
35435
|
+
return { rssBytes: rssKb * 1024, pssBytes: pssKb * 1024 };
|
|
35436
|
+
}
|
|
35437
|
+
var defaultReadFile, defaultStat, LinuxResourceBackend;
|
|
35438
|
+
var init_linux_backend = __esm({
|
|
35439
|
+
"packages/core/src/resources/linux-backend.ts"() {
|
|
35440
|
+
"use strict";
|
|
35441
|
+
defaultReadFile = (path2, encoding) => readFile2(path2, encoding);
|
|
35442
|
+
defaultStat = async (path2) => {
|
|
35443
|
+
try {
|
|
35444
|
+
const s = await stat(path2);
|
|
35445
|
+
return { size: s.size };
|
|
35446
|
+
} catch {
|
|
35447
|
+
return null;
|
|
35448
|
+
}
|
|
35449
|
+
};
|
|
35450
|
+
LinuxResourceBackend = class {
|
|
35451
|
+
globalPressurePath;
|
|
35452
|
+
cgroupSlicePressurePath;
|
|
35453
|
+
walPaths;
|
|
35454
|
+
readFileFn;
|
|
35455
|
+
statFileFn;
|
|
35456
|
+
constructor(opts = {}) {
|
|
35457
|
+
this.globalPressurePath = opts.globalPressurePath ?? "/proc/pressure/memory";
|
|
35458
|
+
this.cgroupSlicePressurePath = opts.cgroupSlicePressurePath ?? null;
|
|
35459
|
+
this.walPaths = opts.walPaths ?? [];
|
|
35460
|
+
this.readFileFn = opts.readFileFn ?? defaultReadFile;
|
|
35461
|
+
this.statFileFn = opts.statFileFn ?? defaultStat;
|
|
35462
|
+
}
|
|
35463
|
+
/**
|
|
35464
|
+
* Take a bounded point-in-time resource sample.
|
|
35465
|
+
*
|
|
35466
|
+
* Read-count (bounded by Amendment 1):
|
|
35467
|
+
* - 1 read: `/proc/pressure/memory`
|
|
35468
|
+
* - 0–1 reads: slice `memory.pressure` (if configured)
|
|
35469
|
+
* - 1 read: `/proc/meminfo`
|
|
35470
|
+
* - N stat calls: one per WAL path in `walPaths`
|
|
35471
|
+
*
|
|
35472
|
+
* Never spawns a child process.
|
|
35473
|
+
*/
|
|
35474
|
+
async sample() {
|
|
35475
|
+
const sampledAtMs = Date.now();
|
|
35476
|
+
const globalPressure = await this._readPsi(this.globalPressurePath);
|
|
35477
|
+
const pressureAvailable = globalPressure !== null;
|
|
35478
|
+
let slicePressure = null;
|
|
35479
|
+
if (this.cgroupSlicePressurePath) {
|
|
35480
|
+
slicePressure = await this._readPsi(this.cgroupSlicePressurePath);
|
|
35481
|
+
}
|
|
35482
|
+
const memAvailableBytes = await this._readMemAvailable();
|
|
35483
|
+
const walObservations = await this._readWalObservations();
|
|
35484
|
+
return {
|
|
35485
|
+
sampledAtMs,
|
|
35486
|
+
pressureAvailable,
|
|
35487
|
+
memAvailableBytes,
|
|
35488
|
+
globalPressure,
|
|
35489
|
+
slicePressure,
|
|
35490
|
+
walObservations
|
|
35491
|
+
};
|
|
35492
|
+
}
|
|
35493
|
+
/**
|
|
35494
|
+
* Sweep per-child RSS via `/proc/<pid>/smaps_rollup`.
|
|
35495
|
+
*
|
|
35496
|
+
* **STRUCTURALLY SEPARATED from sample()** — smaps_rollup reads are
|
|
35497
|
+
* ms-scale per multi-GB process and must never appear in the hot path.
|
|
35498
|
+
* Call on a separate low-frequency cadence (e.g. every 60s or on
|
|
35499
|
+
* backoff→hold state transition).
|
|
35500
|
+
*
|
|
35501
|
+
* @param pids - PIDs to sweep. Dead/unknown PIDs are silently skipped.
|
|
35502
|
+
*/
|
|
35503
|
+
async sweepChildRss(pids) {
|
|
35504
|
+
const sampledAtMs = Date.now();
|
|
35505
|
+
const entries = [];
|
|
35506
|
+
for (const pid of pids) {
|
|
35507
|
+
try {
|
|
35508
|
+
const content = await this.readFileFn(`/proc/${pid}/smaps_rollup`, "utf-8");
|
|
35509
|
+
const parsed = parseSmapsRollup(content);
|
|
35510
|
+
if (parsed) {
|
|
35511
|
+
entries.push({ pid, ...parsed });
|
|
35512
|
+
}
|
|
35513
|
+
} catch {
|
|
35514
|
+
}
|
|
35515
|
+
}
|
|
35516
|
+
return { sampledAtMs, entries };
|
|
35517
|
+
}
|
|
35518
|
+
// -------------------------------------------------------------------------
|
|
35519
|
+
// Private helpers
|
|
35520
|
+
// -------------------------------------------------------------------------
|
|
35521
|
+
async _readPsi(path2) {
|
|
35522
|
+
try {
|
|
35523
|
+
const content = await this.readFileFn(path2, "utf-8");
|
|
35524
|
+
return parsePsiFile(content);
|
|
35525
|
+
} catch {
|
|
35526
|
+
return null;
|
|
35527
|
+
}
|
|
35528
|
+
}
|
|
35529
|
+
async _readMemAvailable() {
|
|
35530
|
+
try {
|
|
35531
|
+
const content = await this.readFileFn("/proc/meminfo", "utf-8");
|
|
35532
|
+
return parseMemAvailable(content);
|
|
35533
|
+
} catch {
|
|
35534
|
+
return null;
|
|
35535
|
+
}
|
|
35536
|
+
}
|
|
35537
|
+
async _readWalObservations() {
|
|
35538
|
+
const results = [];
|
|
35539
|
+
for (const walPath of this.walPaths) {
|
|
35540
|
+
const info = await this.statFileFn(walPath);
|
|
35541
|
+
results.push({ walPath, sizeBytes: info?.size ?? null });
|
|
35542
|
+
}
|
|
35543
|
+
return results;
|
|
35544
|
+
}
|
|
35545
|
+
};
|
|
35546
|
+
}
|
|
35547
|
+
});
|
|
35548
|
+
|
|
35549
|
+
// packages/core/src/resources/monitor.ts
|
|
35550
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
35551
|
+
function resolveThresholds(cfg) {
|
|
35552
|
+
return {
|
|
35553
|
+
holdSomeAvg10: cfg.psi?.holdSomeAvg10 ?? DEFAULTS.holdSomeAvg10,
|
|
35554
|
+
backoffSomeAvg10: cfg.psi?.backoffSomeAvg10 ?? DEFAULTS.backoffSomeAvg10,
|
|
35555
|
+
holdFullAvg10: cfg.psi?.holdFullAvg10 ?? DEFAULTS.holdFullAvg10,
|
|
35556
|
+
backoffFullAvg10: cfg.psi?.backoffFullAvg10 ?? DEFAULTS.backoffFullAvg10,
|
|
35557
|
+
hysteresisPoints: cfg.hysteresisPoints ?? DEFAULTS.hysteresisPoints,
|
|
35558
|
+
headroomBytes: cfg.headroomBytes ?? DEFAULTS.headroomBytes,
|
|
35559
|
+
walWarnThresholdBytes: cfg.walWarnThresholdBytes ?? DEFAULTS.walWarnThresholdBytes,
|
|
35560
|
+
pollIntervalMs: cfg.pollIntervalMs ?? DEFAULTS.pollIntervalMs
|
|
35561
|
+
};
|
|
35562
|
+
}
|
|
35563
|
+
function evaluateState(sample, thresholds, currentState) {
|
|
35564
|
+
if (!sample.pressureAvailable) {
|
|
35565
|
+
if (sample.memAvailableBytes !== null && sample.memAvailableBytes < thresholds.headroomBytes) {
|
|
35566
|
+
return { state: "hold", reason: "degraded: memAvailable below headroom" };
|
|
35567
|
+
}
|
|
35568
|
+
return { state: "ok", reason: "degraded: PSI unavailable, memAvailable sufficient" };
|
|
35569
|
+
}
|
|
35570
|
+
const somePressure = sample.globalPressure?.some ?? sample.slicePressure?.some;
|
|
35571
|
+
const fullPressure = sample.globalPressure?.full ?? sample.slicePressure?.full;
|
|
35572
|
+
const someAvg102 = somePressure?.avg10 ?? 0;
|
|
35573
|
+
const fullAvg10 = fullPressure?.avg10 ?? 0;
|
|
35574
|
+
if (someAvg102 > thresholds.backoffSomeAvg10 || fullAvg10 > thresholds.backoffFullAvg10) {
|
|
35575
|
+
return {
|
|
35576
|
+
state: "backoff",
|
|
35577
|
+
reason: `some avg10=${someAvg102.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} \u2014 above backoff threshold`
|
|
35578
|
+
};
|
|
35579
|
+
}
|
|
35580
|
+
if (someAvg102 > thresholds.holdSomeAvg10 || fullAvg10 > thresholds.holdFullAvg10) {
|
|
35581
|
+
return {
|
|
35582
|
+
state: "hold",
|
|
35583
|
+
reason: `some avg10=${someAvg102.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} \u2014 above hold threshold`
|
|
35584
|
+
};
|
|
35585
|
+
}
|
|
35586
|
+
if (currentState !== "ok") {
|
|
35587
|
+
const hysteresisFloor = thresholds.holdSomeAvg10 - thresholds.hysteresisPoints;
|
|
35588
|
+
const fullHysteresisFloor = thresholds.holdFullAvg10 - thresholds.hysteresisPoints;
|
|
35589
|
+
if (someAvg102 > hysteresisFloor || fullAvg10 > fullHysteresisFloor) {
|
|
35590
|
+
const clamped = currentState === "backoff" ? "hold" : currentState;
|
|
35591
|
+
return {
|
|
35592
|
+
state: clamped,
|
|
35593
|
+
reason: `some avg10=${someAvg102.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} \u2014 hysteresis hold (floor: some ${hysteresisFloor}, full ${fullHysteresisFloor})`
|
|
35594
|
+
};
|
|
35595
|
+
}
|
|
35596
|
+
}
|
|
35597
|
+
return {
|
|
35598
|
+
state: "ok",
|
|
35599
|
+
reason: `some avg10=${someAvg102.toFixed(2)} full avg10=${fullAvg10.toFixed(2)} \u2014 below thresholds`
|
|
35600
|
+
};
|
|
35601
|
+
}
|
|
35602
|
+
var DEFAULTS, ResourceMonitor;
|
|
35603
|
+
var init_monitor = __esm({
|
|
35604
|
+
"packages/core/src/resources/monitor.ts"() {
|
|
35605
|
+
"use strict";
|
|
35606
|
+
init_linux_backend();
|
|
35607
|
+
DEFAULTS = {
|
|
35608
|
+
holdSomeAvg10: 10,
|
|
35609
|
+
backoffSomeAvg10: 20,
|
|
35610
|
+
holdFullAvg10: 5,
|
|
35611
|
+
backoffFullAvg10: 10,
|
|
35612
|
+
hysteresisPoints: 3,
|
|
35613
|
+
headroomBytes: 256 * 1024 * 1024,
|
|
35614
|
+
walWarnThresholdBytes: 256 * 1024 * 1024,
|
|
35615
|
+
pollIntervalMs: 1500
|
|
35616
|
+
};
|
|
35617
|
+
ResourceMonitor = class extends EventEmitter2 {
|
|
35618
|
+
thresholds;
|
|
35619
|
+
backend;
|
|
35620
|
+
_state = "ok";
|
|
35621
|
+
_timer = null;
|
|
35622
|
+
_running = false;
|
|
35623
|
+
constructor(cfg = {}) {
|
|
35624
|
+
super();
|
|
35625
|
+
this.thresholds = resolveThresholds(cfg);
|
|
35626
|
+
this.backend = cfg.backend ?? new LinuxResourceBackend();
|
|
35627
|
+
}
|
|
35628
|
+
// -------------------------------------------------------------------------
|
|
35629
|
+
// Public API
|
|
35630
|
+
// -------------------------------------------------------------------------
|
|
35631
|
+
/**
|
|
35632
|
+
* Current pressure state (only meaningful after at least one sample).
|
|
35633
|
+
*/
|
|
35634
|
+
get state() {
|
|
35635
|
+
return this._state;
|
|
35636
|
+
}
|
|
35637
|
+
/**
|
|
35638
|
+
* `true` while the continuous loop is running.
|
|
35639
|
+
*/
|
|
35640
|
+
get running() {
|
|
35641
|
+
return this._running;
|
|
35642
|
+
}
|
|
35643
|
+
/**
|
|
35644
|
+
* Take a single point-in-time resource sample.
|
|
35645
|
+
*
|
|
35646
|
+
* Safe to call with the daemon off (no child processes, bounded reads).
|
|
35647
|
+
* Does NOT update `this.state` — use {@link startContinuous} for that.
|
|
35648
|
+
*/
|
|
35649
|
+
async sample() {
|
|
35650
|
+
return this.backend.sample();
|
|
35651
|
+
}
|
|
35652
|
+
/**
|
|
35653
|
+
* Start the continuous polling loop.
|
|
35654
|
+
*
|
|
35655
|
+
* Returns a `stop()` function. Calling `stop()` is idempotent.
|
|
35656
|
+
*
|
|
35657
|
+
* Emits:
|
|
35658
|
+
* - `'sample'` on every poll (even if state did not change)
|
|
35659
|
+
* - `'transition'` when `PressureState` changes
|
|
35660
|
+
* - `'error'` if the backend throws (loop continues regardless)
|
|
35661
|
+
*/
|
|
35662
|
+
startContinuous() {
|
|
35663
|
+
if (this._running) {
|
|
35664
|
+
return () => this._stopLoop();
|
|
35665
|
+
}
|
|
35666
|
+
this._running = true;
|
|
35667
|
+
this._scheduleNext();
|
|
35668
|
+
return () => this._stopLoop();
|
|
35669
|
+
}
|
|
35670
|
+
/**
|
|
35671
|
+
* Stop the continuous polling loop. Idempotent.
|
|
35672
|
+
*/
|
|
35673
|
+
stop() {
|
|
35674
|
+
this._stopLoop();
|
|
35675
|
+
}
|
|
35676
|
+
// -------------------------------------------------------------------------
|
|
35677
|
+
// Private loop machinery
|
|
35678
|
+
// -------------------------------------------------------------------------
|
|
35679
|
+
_scheduleNext() {
|
|
35680
|
+
this._timer = setTimeout(() => {
|
|
35681
|
+
void this._tick();
|
|
35682
|
+
}, this.thresholds.pollIntervalMs);
|
|
35683
|
+
if (typeof this._timer.unref === "function") {
|
|
35684
|
+
this._timer.unref();
|
|
35685
|
+
}
|
|
35686
|
+
}
|
|
35687
|
+
async _tick() {
|
|
35688
|
+
if (!this._running) return;
|
|
35689
|
+
try {
|
|
35690
|
+
const sample = await this.backend.sample();
|
|
35691
|
+
this.emit("sample", sample);
|
|
35692
|
+
const { state: newState, reason } = evaluateState(sample, this.thresholds, this._state);
|
|
35693
|
+
if (newState !== this._state) {
|
|
35694
|
+
const transition = {
|
|
35695
|
+
from: this._state,
|
|
35696
|
+
to: newState,
|
|
35697
|
+
sample,
|
|
35698
|
+
reason
|
|
35699
|
+
};
|
|
35700
|
+
this._state = newState;
|
|
35701
|
+
this.emit("transition", transition);
|
|
35702
|
+
}
|
|
35703
|
+
} catch (err) {
|
|
35704
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
35705
|
+
}
|
|
35706
|
+
if (this._running) {
|
|
35707
|
+
this._scheduleNext();
|
|
35708
|
+
}
|
|
35709
|
+
}
|
|
35710
|
+
_stopLoop() {
|
|
35711
|
+
this._running = false;
|
|
35712
|
+
if (this._timer !== null) {
|
|
35713
|
+
clearTimeout(this._timer);
|
|
35714
|
+
this._timer = null;
|
|
35715
|
+
}
|
|
35716
|
+
}
|
|
35717
|
+
};
|
|
35718
|
+
}
|
|
35719
|
+
});
|
|
35720
|
+
|
|
35721
|
+
// packages/core/src/resources/supervisor-admit.ts
|
|
35722
|
+
import { connect } from "node:net";
|
|
35723
|
+
import { join as join5 } from "node:path";
|
|
35724
|
+
import { getCleoHome as getCleoHome2 } from "@cleocode/paths";
|
|
35725
|
+
function resolveSupervisorSocketPath() {
|
|
35726
|
+
return process.env.CLEO_SUPERVISOR_SOCKET ?? join5(getCleoHome2(), SUPERVISOR_SOCKET_NAME);
|
|
35727
|
+
}
|
|
35728
|
+
async function sendResourceAdmit(socketPath, cls, holderId, budget) {
|
|
35729
|
+
const result = await sendLeaseRequest(
|
|
35730
|
+
socketPath,
|
|
35731
|
+
{ kind: "resource_admit", class: cls, holder_id: holderId, budget },
|
|
35732
|
+
"resource_admit_result"
|
|
35733
|
+
);
|
|
35734
|
+
if ("unavailable" in result) return result;
|
|
35735
|
+
if (result.kind === "resource_admit_result") return result;
|
|
35736
|
+
return { unavailable: true, reason: `unexpected response kind: ${result.kind}` };
|
|
35737
|
+
}
|
|
35738
|
+
async function sendResourceRelease(socketPath, cls, holderId) {
|
|
35739
|
+
const result = await sendLeaseRequest(
|
|
35740
|
+
socketPath,
|
|
35741
|
+
{ kind: "resource_release", class: cls, holder_id: holderId },
|
|
35742
|
+
"resource_release_result"
|
|
35743
|
+
);
|
|
35744
|
+
if ("unavailable" in result) return result;
|
|
35745
|
+
if (result.kind === "resource_release_result") return result;
|
|
35746
|
+
return { unavailable: true, reason: `unexpected response kind: ${result.kind}` };
|
|
35747
|
+
}
|
|
35748
|
+
function sendLeaseRequest(socketPath, request, expectedKind) {
|
|
35749
|
+
return new Promise((resolve6) => {
|
|
35750
|
+
const id = `ra-${process.pid}-${request.kind}`;
|
|
35751
|
+
const envelope = LeaseIpcRequestEnvelopeSchema.parse({
|
|
35752
|
+
protocol_version: LEASE_IPC_PROTOCOL_VERSION,
|
|
35753
|
+
id,
|
|
35754
|
+
direction: "request",
|
|
35755
|
+
request
|
|
35756
|
+
});
|
|
35757
|
+
const line2 = `${JSON.stringify(envelope)}
|
|
35758
|
+
`;
|
|
35759
|
+
let settled = false;
|
|
35760
|
+
let buffer = "";
|
|
35761
|
+
const socket = connect(socketPath);
|
|
35762
|
+
const done = (value) => {
|
|
35763
|
+
if (settled) return;
|
|
35764
|
+
settled = true;
|
|
35765
|
+
socket.removeAllListeners();
|
|
35766
|
+
socket.destroy();
|
|
35767
|
+
resolve6(value);
|
|
35768
|
+
};
|
|
35769
|
+
const degrade = (reason) => done({ unavailable: true, reason });
|
|
35770
|
+
socket.setTimeout(REQUEST_TIMEOUT_MS);
|
|
35771
|
+
const connectTimer = setTimeout(
|
|
35772
|
+
() => degrade("supervisor connect timeout"),
|
|
35773
|
+
CONNECT_TIMEOUT_MS
|
|
35774
|
+
);
|
|
35775
|
+
socket.on("connect", () => {
|
|
35776
|
+
clearTimeout(connectTimer);
|
|
35777
|
+
socket.write(line2);
|
|
35778
|
+
});
|
|
35779
|
+
socket.on("timeout", () => degrade("supervisor request timeout"));
|
|
35780
|
+
socket.on("error", (err) => {
|
|
35781
|
+
clearTimeout(connectTimer);
|
|
35782
|
+
degrade(err.message);
|
|
35783
|
+
});
|
|
35784
|
+
socket.on("close", () => {
|
|
35785
|
+
if (!settled) degrade("supervisor closed the connection before responding");
|
|
35786
|
+
});
|
|
35787
|
+
socket.on("data", (chunk) => {
|
|
35788
|
+
buffer += chunk.toString("utf8");
|
|
35789
|
+
const nl = buffer.indexOf("\n");
|
|
35790
|
+
if (nl < 0) return;
|
|
35791
|
+
let json2;
|
|
35792
|
+
try {
|
|
35793
|
+
json2 = JSON.parse(buffer.slice(0, nl));
|
|
35794
|
+
} catch (cause) {
|
|
35795
|
+
degrade(`malformed lease-ipc frame: ${cause instanceof Error ? cause.message : cause}`);
|
|
35796
|
+
return;
|
|
35797
|
+
}
|
|
35798
|
+
const parsed = LeaseIpcResponseEnvelopeSchema.safeParse(json2);
|
|
35799
|
+
if (!parsed.success) {
|
|
35800
|
+
degrade(`off-contract lease-ipc response: ${parsed.error.message}`);
|
|
35801
|
+
return;
|
|
35802
|
+
}
|
|
35803
|
+
const { response } = parsed.data;
|
|
35804
|
+
if (response.kind === expectedKind) {
|
|
35805
|
+
done(response);
|
|
35806
|
+
return;
|
|
35807
|
+
}
|
|
35808
|
+
if (response.kind === "error") {
|
|
35809
|
+
degrade(`${response.code}: ${response.message}`);
|
|
35810
|
+
return;
|
|
35811
|
+
}
|
|
35812
|
+
degrade(`unexpected lease-ipc response kind for ${request.kind}: ${response.kind}`);
|
|
35813
|
+
});
|
|
35814
|
+
});
|
|
35815
|
+
}
|
|
35816
|
+
var SUPERVISOR_SOCKET_NAME, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS;
|
|
35817
|
+
var init_supervisor_admit = __esm({
|
|
35818
|
+
"packages/core/src/resources/supervisor-admit.ts"() {
|
|
35819
|
+
"use strict";
|
|
35820
|
+
init_src();
|
|
35821
|
+
SUPERVISOR_SOCKET_NAME = "cleo-supervisor.sock";
|
|
35822
|
+
CONNECT_TIMEOUT_MS = 250;
|
|
35823
|
+
REQUEST_TIMEOUT_MS = 2e3;
|
|
35824
|
+
}
|
|
35825
|
+
});
|
|
35826
|
+
|
|
35827
|
+
// packages/core/src/resources/governor.ts
|
|
35828
|
+
var governor_exports = {};
|
|
35829
|
+
__export(governor_exports, {
|
|
35830
|
+
ResourceGovernor: () => ResourceGovernor,
|
|
35831
|
+
_resetGovernorStateForTest: () => _resetGovernorStateForTest,
|
|
35832
|
+
computeClassBudget: () => computeClassBudget,
|
|
35833
|
+
governor: () => governor,
|
|
35834
|
+
governorSlotDir: () => governorSlotDir,
|
|
35835
|
+
resolveGovernorMode: () => resolveGovernorMode,
|
|
35836
|
+
slotFileCount: () => slotFileCount
|
|
35837
|
+
});
|
|
35838
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, writeFileSync } from "node:fs";
|
|
35839
|
+
import { availableParallelism, totalmem } from "node:os";
|
|
35840
|
+
import { join as join6 } from "node:path";
|
|
35305
35841
|
import lockfile from "proper-lockfile";
|
|
35842
|
+
function log2() {
|
|
35843
|
+
if (_log2 === null) _log2 = getLogger("resource-governor");
|
|
35844
|
+
return _log2;
|
|
35845
|
+
}
|
|
35846
|
+
function resolveGovernorMode() {
|
|
35847
|
+
if (_cachedMode2 !== null) return _cachedMode2;
|
|
35848
|
+
const raw = process.env.CLEO_RESOURCES_MODE;
|
|
35849
|
+
_cachedMode2 = raw === "supervisor" || raw === "local" || raw === "off" ? raw : "local";
|
|
35850
|
+
return _cachedMode2;
|
|
35851
|
+
}
|
|
35852
|
+
function logSupervisorDegradeOnce(reason) {
|
|
35853
|
+
if (_supervisorDegradeLogged) return;
|
|
35854
|
+
_supervisorDegradeLogged = true;
|
|
35855
|
+
log2().info(
|
|
35856
|
+
`CLEO_RESOURCES_MODE=supervisor but the arbiter is unreachable (${reason}); degrading to local-mode admission for the process lifetime.`
|
|
35857
|
+
);
|
|
35858
|
+
}
|
|
35859
|
+
function _resetGovernorStateForTest() {
|
|
35860
|
+
_cachedMode2 = null;
|
|
35861
|
+
_supervisorDegradeLogged = false;
|
|
35862
|
+
}
|
|
35863
|
+
function someAvg10(sample) {
|
|
35864
|
+
const some = sample.globalPressure?.some ?? sample.slicePressure?.some;
|
|
35865
|
+
return some?.avg10 ?? 0;
|
|
35866
|
+
}
|
|
35867
|
+
function computeClassBudget(cls, sample, opts = {}) {
|
|
35868
|
+
if (cls === "interactive-cli") return Number.POSITIVE_INFINITY;
|
|
35869
|
+
const cpus = Math.max(1, opts.cpuCount ?? availableParallelism());
|
|
35870
|
+
const totalBytes = opts.totalMemBytes ?? totalmem();
|
|
35871
|
+
const headroomBytes = (opts.headroomMb ?? 2048) * MB;
|
|
35872
|
+
const hold = opts.holdSomeAvg10 ?? 10;
|
|
35873
|
+
const floor = opts.floorSomeAvg10 ?? 25;
|
|
35874
|
+
const some = someAvg10(sample);
|
|
35875
|
+
const availBytes = sample.memAvailableBytes ?? totalBytes;
|
|
35876
|
+
const fullStall = sample.globalPressure?.full?.avg10 ?? sample.slicePressure?.full?.avg10 ?? 0;
|
|
35877
|
+
const backoff = some > floor || fullStall > 10;
|
|
35878
|
+
switch (cls) {
|
|
35879
|
+
case "full-build":
|
|
35880
|
+
return 1;
|
|
35881
|
+
case "agent-session": {
|
|
35882
|
+
const estRamBytes = (opts.agentEstRamMb ?? 4096) * MB;
|
|
35883
|
+
const byMem = Math.floor((availBytes - headroomBytes) / estRamBytes);
|
|
35884
|
+
return clamp(1, byMem, Math.max(1, cpus - 2));
|
|
35885
|
+
}
|
|
35886
|
+
case "llm-call":
|
|
35887
|
+
return Math.max(1, cpus - 2);
|
|
35888
|
+
case "test-run":
|
|
35889
|
+
case "scoped-build": {
|
|
35890
|
+
const base = Math.max(1, Math.floor(cpus / 4));
|
|
35891
|
+
if (some > floor) return 1;
|
|
35892
|
+
if (some > hold) return Math.max(1, Math.floor(base / 2));
|
|
35893
|
+
return base;
|
|
35894
|
+
}
|
|
35895
|
+
case "db-heavy":
|
|
35896
|
+
return backoff ? 0 : 1;
|
|
35897
|
+
case "background-autonomous":
|
|
35898
|
+
return some > hold || fullStall > 5 ? 0 : 1;
|
|
35899
|
+
default:
|
|
35900
|
+
return 1;
|
|
35901
|
+
}
|
|
35902
|
+
}
|
|
35903
|
+
function clamp(lo, v, hi) {
|
|
35904
|
+
return Math.max(lo, Math.min(v, hi));
|
|
35905
|
+
}
|
|
35906
|
+
function governorSlotDir(cls) {
|
|
35907
|
+
return join6(getCleoHome(), "locks", `resource-${cls}`);
|
|
35908
|
+
}
|
|
35909
|
+
function ensureSlotFiles(dir, count) {
|
|
35910
|
+
if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
|
|
35911
|
+
const paths = [];
|
|
35912
|
+
for (let i = 0; i < count; i++) {
|
|
35913
|
+
const p = join6(dir, `slot-${i}.lock`);
|
|
35914
|
+
if (!existsSync3(p)) writeFileSync(p, "", { flag: "a" });
|
|
35915
|
+
paths.push(p);
|
|
35916
|
+
}
|
|
35917
|
+
return paths;
|
|
35918
|
+
}
|
|
35919
|
+
function sleep2(ms) {
|
|
35920
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
35921
|
+
}
|
|
35922
|
+
function passThroughGrant(cls) {
|
|
35923
|
+
return {
|
|
35924
|
+
deferred: false,
|
|
35925
|
+
class: cls,
|
|
35926
|
+
slot: -1,
|
|
35927
|
+
acquiredAtMs: Date.now(),
|
|
35928
|
+
release: async () => {
|
|
35929
|
+
}
|
|
35930
|
+
};
|
|
35931
|
+
}
|
|
35932
|
+
function deferral(cls, reason, retryAfterMs) {
|
|
35933
|
+
return { deferred: true, class: cls, retryAfterMs, reason };
|
|
35934
|
+
}
|
|
35935
|
+
function supervisorHolderId(cls) {
|
|
35936
|
+
_supervisorHolderSeq += 1;
|
|
35937
|
+
return `${process.pid}:${cls}:${_supervisorHolderSeq}`;
|
|
35938
|
+
}
|
|
35939
|
+
function shuffledIndices(n) {
|
|
35940
|
+
const order = Array.from({ length: n }, (_, i) => i);
|
|
35941
|
+
for (let i = order.length - 1; i > 0; i--) {
|
|
35942
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
35943
|
+
const a = order[i];
|
|
35944
|
+
const b = order[j];
|
|
35945
|
+
if (a !== void 0 && b !== void 0) {
|
|
35946
|
+
order[i] = b;
|
|
35947
|
+
order[j] = a;
|
|
35948
|
+
}
|
|
35949
|
+
}
|
|
35950
|
+
return order;
|
|
35951
|
+
}
|
|
35952
|
+
async function countHeldSlots(cls, budget) {
|
|
35953
|
+
const dir = governorSlotDir(cls);
|
|
35954
|
+
if (!existsSync3(dir)) return 0;
|
|
35955
|
+
const slots = ensureSlotFiles(dir, budget);
|
|
35956
|
+
let held = 0;
|
|
35957
|
+
for (const path2 of slots) {
|
|
35958
|
+
try {
|
|
35959
|
+
const release = await lockfile.lock(path2, { retries: 0, stale: STALE_MS, realpath: false });
|
|
35960
|
+
await release();
|
|
35961
|
+
} catch {
|
|
35962
|
+
held++;
|
|
35963
|
+
}
|
|
35964
|
+
}
|
|
35965
|
+
return held;
|
|
35966
|
+
}
|
|
35967
|
+
function slotFileCount(cls) {
|
|
35968
|
+
const dir = governorSlotDir(cls);
|
|
35969
|
+
if (!existsSync3(dir)) return 0;
|
|
35970
|
+
return readdirSync2(dir).filter((f) => f.startsWith("slot-") && f.endsWith(".lock")).length;
|
|
35971
|
+
}
|
|
35972
|
+
var _log2, _cachedMode2, _supervisorDegradeLogged, MB, STALE_MS, _supervisorHolderSeq, ResourceGovernor, governor;
|
|
35973
|
+
var init_governor = __esm({
|
|
35974
|
+
"packages/core/src/resources/governor.ts"() {
|
|
35975
|
+
"use strict";
|
|
35976
|
+
init_src();
|
|
35977
|
+
init_logger2();
|
|
35978
|
+
init_paths();
|
|
35979
|
+
init_monitor();
|
|
35980
|
+
init_supervisor_admit();
|
|
35981
|
+
_log2 = null;
|
|
35982
|
+
_cachedMode2 = null;
|
|
35983
|
+
_supervisorDegradeLogged = false;
|
|
35984
|
+
MB = 1024 * 1024;
|
|
35985
|
+
STALE_MS = 6e5;
|
|
35986
|
+
_supervisorHolderSeq = 0;
|
|
35987
|
+
ResourceGovernor = class {
|
|
35988
|
+
/**
|
|
35989
|
+
* Acquire one slot of `cls`. Returns a {@link ResourceGrant} on success or a
|
|
35990
|
+
* {@link ResourceDeferral} on denial. Never throws for admission control;
|
|
35991
|
+
* only genuinely unexpected I/O errors propagate.
|
|
35992
|
+
*/
|
|
35993
|
+
async acquire(cls, opts = {}) {
|
|
35994
|
+
const mode = resolveGovernorMode();
|
|
35995
|
+
if (mode === "off" || cls === "interactive-cli") {
|
|
35996
|
+
return passThroughGrant(cls);
|
|
35997
|
+
}
|
|
35998
|
+
const sample = opts.sample ?? await (opts.monitor ?? new ResourceMonitor()).sample();
|
|
35999
|
+
const budget = computeClassBudget(cls, sample, opts);
|
|
36000
|
+
if (!Number.isFinite(budget)) return passThroughGrant(cls);
|
|
36001
|
+
if (budget <= 0) {
|
|
36002
|
+
return deferral(
|
|
36003
|
+
cls,
|
|
36004
|
+
`class '${cls}' budget is 0 under current pressure (some avg10=${someAvg10(sample).toFixed(1)})`,
|
|
36005
|
+
DEFAULT_RESOURCE_RETRY_AFTER_MS
|
|
36006
|
+
);
|
|
36007
|
+
}
|
|
36008
|
+
if (mode === "supervisor") {
|
|
36009
|
+
const viaSupervisor = await this.acquireViaSupervisor(cls, Math.floor(budget));
|
|
36010
|
+
if (viaSupervisor !== null) return viaSupervisor;
|
|
36011
|
+
}
|
|
36012
|
+
const dir = governorSlotDir(cls);
|
|
36013
|
+
const slots = ensureSlotFiles(dir, budget);
|
|
36014
|
+
const blocking = opts.blocking ?? true;
|
|
36015
|
+
const timeoutMs = opts.timeoutMs ?? 36e5;
|
|
36016
|
+
const pollMs = opts.pollMs ?? 200;
|
|
36017
|
+
const startedAt = Date.now();
|
|
36018
|
+
do {
|
|
36019
|
+
const order = shuffledIndices(slots.length);
|
|
36020
|
+
for (const idx of order) {
|
|
36021
|
+
const path2 = slots[idx];
|
|
36022
|
+
if (!path2) continue;
|
|
36023
|
+
try {
|
|
36024
|
+
const release = await lockfile.lock(path2, {
|
|
36025
|
+
retries: 0,
|
|
36026
|
+
stale: STALE_MS,
|
|
36027
|
+
realpath: false
|
|
36028
|
+
});
|
|
36029
|
+
let released = false;
|
|
36030
|
+
return {
|
|
36031
|
+
deferred: false,
|
|
36032
|
+
class: cls,
|
|
36033
|
+
slot: idx,
|
|
36034
|
+
acquiredAtMs: Date.now(),
|
|
36035
|
+
release: async () => {
|
|
36036
|
+
if (released) return;
|
|
36037
|
+
released = true;
|
|
36038
|
+
try {
|
|
36039
|
+
await release();
|
|
36040
|
+
} catch {
|
|
36041
|
+
}
|
|
36042
|
+
}
|
|
36043
|
+
};
|
|
36044
|
+
} catch {
|
|
36045
|
+
}
|
|
36046
|
+
}
|
|
36047
|
+
if (!blocking) break;
|
|
36048
|
+
await sleep2(pollMs);
|
|
36049
|
+
} while (Date.now() - startedAt < timeoutMs);
|
|
36050
|
+
return deferral(
|
|
36051
|
+
cls,
|
|
36052
|
+
`class '${cls}' is at capacity (${budget} slot(s)); ` + (blocking ? `timed out after ${timeoutMs}ms` : "no slot free"),
|
|
36053
|
+
Math.min(pollMs * 4, DEFAULT_RESOURCE_RETRY_AFTER_MS)
|
|
36054
|
+
);
|
|
36055
|
+
}
|
|
36056
|
+
/**
|
|
36057
|
+
* Route an admission through the supervisor's central `resource_admit` verb.
|
|
36058
|
+
* Returns a grant (whose `release` calls `resource_release`) or a deferral, or
|
|
36059
|
+
* `null` when the supervisor is unreachable so the caller degrades to the
|
|
36060
|
+
* local slot engine. Never throws — a dead arbiter never deadlocks work.
|
|
36061
|
+
*
|
|
36062
|
+
* @task T12001
|
|
36063
|
+
*/
|
|
36064
|
+
async acquireViaSupervisor(cls, budget) {
|
|
36065
|
+
const socketPath = resolveSupervisorSocketPath();
|
|
36066
|
+
const holderId = supervisorHolderId(cls);
|
|
36067
|
+
const reply = await sendResourceAdmit(socketPath, cls, holderId, budget);
|
|
36068
|
+
if ("unavailable" in reply) {
|
|
36069
|
+
logSupervisorDegradeOnce(reply.reason);
|
|
36070
|
+
return null;
|
|
36071
|
+
}
|
|
36072
|
+
if (reply.disposition === "deferred") {
|
|
36073
|
+
return deferral(
|
|
36074
|
+
cls,
|
|
36075
|
+
`class '${cls}' deferred by supervisor (budget ${budget})`,
|
|
36076
|
+
reply.retry_after_ms || DEFAULT_RESOURCE_RETRY_AFTER_MS
|
|
36077
|
+
);
|
|
36078
|
+
}
|
|
36079
|
+
let released = false;
|
|
36080
|
+
return {
|
|
36081
|
+
deferred: false,
|
|
36082
|
+
class: cls,
|
|
36083
|
+
slot: 0,
|
|
36084
|
+
acquiredAtMs: Date.now(),
|
|
36085
|
+
release: async () => {
|
|
36086
|
+
if (released) return;
|
|
36087
|
+
released = true;
|
|
36088
|
+
await sendResourceRelease(socketPath, cls, holderId).catch(() => {
|
|
36089
|
+
});
|
|
36090
|
+
}
|
|
36091
|
+
};
|
|
36092
|
+
}
|
|
36093
|
+
/** Non-blocking single-pass acquire (admission semantics). */
|
|
36094
|
+
async tryAcquire(cls, opts = {}) {
|
|
36095
|
+
return this.acquire(cls, { ...opts, blocking: false });
|
|
36096
|
+
}
|
|
36097
|
+
/**
|
|
36098
|
+
* Currently-grantable slot count for `cls` = budget − held. Held is the count
|
|
36099
|
+
* of slot files currently locked. `Infinity` for ungated classes.
|
|
36100
|
+
*/
|
|
36101
|
+
async available(cls, opts = {}) {
|
|
36102
|
+
if (resolveGovernorMode() === "off" || cls === "interactive-cli") {
|
|
36103
|
+
return Number.POSITIVE_INFINITY;
|
|
36104
|
+
}
|
|
36105
|
+
const sample = opts.sample ?? await (opts.monitor ?? new ResourceMonitor()).sample();
|
|
36106
|
+
const budget = computeClassBudget(cls, sample, opts);
|
|
36107
|
+
if (!Number.isFinite(budget)) return Number.POSITIVE_INFINITY;
|
|
36108
|
+
if (budget <= 0) return 0;
|
|
36109
|
+
const held = await countHeldSlots(cls, budget);
|
|
36110
|
+
return Math.max(0, budget - held);
|
|
36111
|
+
}
|
|
36112
|
+
};
|
|
36113
|
+
governor = new ResourceGovernor();
|
|
36114
|
+
}
|
|
36115
|
+
});
|
|
36116
|
+
|
|
36117
|
+
// packages/core/src/store/lock.ts
|
|
36118
|
+
import lockfile2 from "proper-lockfile";
|
|
35306
36119
|
async function acquireLock(filePath, options) {
|
|
35307
36120
|
try {
|
|
35308
|
-
const release = await
|
|
36121
|
+
const release = await lockfile2.lock(filePath, {
|
|
35309
36122
|
...DEFAULT_LOCK_OPTIONS,
|
|
35310
36123
|
...options?.stale !== void 0 && { stale: options.stale },
|
|
35311
36124
|
...options?.retries !== void 0 && {
|
|
@@ -35351,7 +36164,7 @@ var init_lock = __esm({
|
|
|
35351
36164
|
});
|
|
35352
36165
|
|
|
35353
36166
|
// packages/core/src/store/atomic.ts
|
|
35354
|
-
import { mkdir, readFile as
|
|
36167
|
+
import { mkdir, readFile as readFile3, rename, unlink } from "node:fs/promises";
|
|
35355
36168
|
import { dirname as dirname3 } from "node:path";
|
|
35356
36169
|
import writeFileAtomic from "write-file-atomic";
|
|
35357
36170
|
async function atomicWrite(filePath, data, options) {
|
|
@@ -35367,7 +36180,7 @@ async function atomicWrite(filePath, data, options) {
|
|
|
35367
36180
|
}
|
|
35368
36181
|
async function safeReadFile(filePath) {
|
|
35369
36182
|
try {
|
|
35370
|
-
return await
|
|
36183
|
+
return await readFile3(filePath, "utf8");
|
|
35371
36184
|
} catch (err) {
|
|
35372
36185
|
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
35373
36186
|
return null;
|
|
@@ -35388,34 +36201,34 @@ var init_atomic = __esm({
|
|
|
35388
36201
|
});
|
|
35389
36202
|
|
|
35390
36203
|
// packages/core/src/store/backup.ts
|
|
35391
|
-
import { copyFile, rename as fsRename, mkdir as mkdir2, readdir, stat, unlink as unlink2 } from "node:fs/promises";
|
|
35392
|
-
import { basename, join as
|
|
36204
|
+
import { copyFile, rename as fsRename, mkdir as mkdir2, readdir, stat as stat2, unlink as unlink2 } from "node:fs/promises";
|
|
36205
|
+
import { basename, join as join7 } from "node:path";
|
|
35393
36206
|
async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUPS) {
|
|
35394
36207
|
try {
|
|
35395
36208
|
await mkdir2(backupDir, { recursive: true });
|
|
35396
36209
|
const fileName = basename(filePath);
|
|
35397
36210
|
try {
|
|
35398
|
-
await
|
|
36211
|
+
await stat2(filePath);
|
|
35399
36212
|
} catch {
|
|
35400
36213
|
throw new CleoError(3 /* FILE_ERROR */, `Cannot backup: source file not found: ${filePath}`);
|
|
35401
36214
|
}
|
|
35402
36215
|
for (let i = maxBackups; i >= 1; i--) {
|
|
35403
|
-
const current =
|
|
36216
|
+
const current = join7(backupDir, `${fileName}.${i}`);
|
|
35404
36217
|
if (i === maxBackups) {
|
|
35405
36218
|
try {
|
|
35406
36219
|
await unlink2(current);
|
|
35407
36220
|
} catch {
|
|
35408
36221
|
}
|
|
35409
36222
|
} else {
|
|
35410
|
-
const next =
|
|
36223
|
+
const next = join7(backupDir, `${fileName}.${i + 1}`);
|
|
35411
36224
|
try {
|
|
35412
|
-
await
|
|
36225
|
+
await stat2(current);
|
|
35413
36226
|
await fsRename(current, next);
|
|
35414
36227
|
} catch {
|
|
35415
36228
|
}
|
|
35416
36229
|
}
|
|
35417
36230
|
}
|
|
35418
|
-
const backupPath =
|
|
36231
|
+
const backupPath = join7(backupDir, `${fileName}.1`);
|
|
35419
36232
|
await copyFile(filePath, backupPath);
|
|
35420
36233
|
return backupPath;
|
|
35421
36234
|
} catch (err) {
|
|
@@ -35694,8 +36507,8 @@ var init_json2 = __esm({
|
|
|
35694
36507
|
});
|
|
35695
36508
|
|
|
35696
36509
|
// packages/core/src/scaffold/ensure-config.ts
|
|
35697
|
-
import { existsSync as
|
|
35698
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
36510
|
+
import { existsSync as existsSync4, readFileSync } from "node:fs";
|
|
36511
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve2 } from "node:path";
|
|
35699
36512
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
35700
36513
|
function getPackageRoot() {
|
|
35701
36514
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -35703,7 +36516,7 @@ function getPackageRoot() {
|
|
|
35703
36516
|
}
|
|
35704
36517
|
function getCleoVersion() {
|
|
35705
36518
|
try {
|
|
35706
|
-
const pkgPath =
|
|
36519
|
+
const pkgPath = join8(getPackageRoot(), "package.json");
|
|
35707
36520
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
35708
36521
|
return pkg.version ?? "0.0.0";
|
|
35709
36522
|
} catch {
|
|
@@ -35722,25 +36535,25 @@ var init_ensure_config = __esm({
|
|
|
35722
36535
|
// packages/core/src/store/exodus/archive.ts
|
|
35723
36536
|
import {
|
|
35724
36537
|
copyFileSync as copyFileSync2,
|
|
35725
|
-
existsSync as
|
|
35726
|
-
mkdirSync,
|
|
36538
|
+
existsSync as existsSync5,
|
|
36539
|
+
mkdirSync as mkdirSync2,
|
|
35727
36540
|
renameSync,
|
|
35728
36541
|
unlinkSync,
|
|
35729
|
-
writeFileSync
|
|
36542
|
+
writeFileSync as writeFileSync2
|
|
35730
36543
|
} from "node:fs";
|
|
35731
|
-
import { basename as basename3, join as
|
|
36544
|
+
import { basename as basename3, join as join9 } from "node:path";
|
|
35732
36545
|
function scopeBaseDir(scope, cwd) {
|
|
35733
36546
|
return scope === "project" ? resolveCleoDir(cwd) : getCleoHome();
|
|
35734
36547
|
}
|
|
35735
36548
|
function exodusArchiveDir(scope, cwd) {
|
|
35736
|
-
return
|
|
36549
|
+
return join9(scopeBaseDir(scope, cwd), ARCHIVE_DIR_NAME);
|
|
35737
36550
|
}
|
|
35738
36551
|
function exodusMarkerPath(scope, cwd) {
|
|
35739
|
-
return
|
|
36552
|
+
return join9(scopeBaseDir(scope, cwd), MARKER_FILENAME_BY_SCOPE[scope]);
|
|
35740
36553
|
}
|
|
35741
36554
|
function hasExodusCompleteMarker(scope, cwd) {
|
|
35742
36555
|
try {
|
|
35743
|
-
return
|
|
36556
|
+
return existsSync5(exodusMarkerPath(scope, cwd));
|
|
35744
36557
|
} catch {
|
|
35745
36558
|
return false;
|
|
35746
36559
|
}
|
|
@@ -35748,7 +36561,7 @@ function hasExodusCompleteMarker(scope, cwd) {
|
|
|
35748
36561
|
function writeExodusCompleteMarker(scope, archivedSources, cwd) {
|
|
35749
36562
|
const markerPath = exodusMarkerPath(scope, cwd);
|
|
35750
36563
|
const baseDir = scopeBaseDir(scope, cwd);
|
|
35751
|
-
|
|
36564
|
+
mkdirSync2(baseDir, { recursive: true });
|
|
35752
36565
|
const marker = {
|
|
35753
36566
|
version: 1,
|
|
35754
36567
|
scope,
|
|
@@ -35757,17 +36570,17 @@ function writeExodusCompleteMarker(scope, archivedSources, cwd) {
|
|
|
35757
36570
|
archivedSources: [...archivedSources]
|
|
35758
36571
|
};
|
|
35759
36572
|
const tmpPath = `${markerPath}.tmp`;
|
|
35760
|
-
|
|
36573
|
+
writeFileSync2(tmpPath, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
35761
36574
|
renameSync(tmpPath, markerPath);
|
|
35762
|
-
|
|
36575
|
+
log3.info({ scope, markerPath, archivedSources }, "exodus: wrote completion marker");
|
|
35763
36576
|
return markerPath;
|
|
35764
36577
|
}
|
|
35765
36578
|
function moveFileInto(srcPath, destDir) {
|
|
35766
|
-
|
|
35767
|
-
let dest =
|
|
35768
|
-
if (
|
|
36579
|
+
mkdirSync2(destDir, { recursive: true });
|
|
36580
|
+
let dest = join9(destDir, basename3(srcPath));
|
|
36581
|
+
if (existsSync5(dest)) {
|
|
35769
36582
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/Z$/, "Z");
|
|
35770
|
-
dest =
|
|
36583
|
+
dest = join9(destDir, `${basename3(srcPath)}.${stamp}`);
|
|
35771
36584
|
}
|
|
35772
36585
|
try {
|
|
35773
36586
|
renameSync(srcPath, dest);
|
|
@@ -35778,25 +36591,25 @@ function moveFileInto(srcPath, destDir) {
|
|
|
35778
36591
|
return dest;
|
|
35779
36592
|
}
|
|
35780
36593
|
function archiveSourceDb(source, cwd) {
|
|
35781
|
-
if (!
|
|
36594
|
+
if (!existsSync5(source.path)) {
|
|
35782
36595
|
return { name: source.name, sourcePath: source.path, archivedTo: null, action: "absent" };
|
|
35783
36596
|
}
|
|
35784
36597
|
const destDir = exodusArchiveDir(source.targetScope, cwd);
|
|
35785
36598
|
const archivedTo = moveFileInto(source.path, destDir);
|
|
35786
36599
|
for (const suffix of SIDECAR_SUFFIXES) {
|
|
35787
36600
|
const sidecar = `${source.path}${suffix}`;
|
|
35788
|
-
if (
|
|
36601
|
+
if (existsSync5(sidecar)) {
|
|
35789
36602
|
try {
|
|
35790
36603
|
moveFileInto(sidecar, destDir);
|
|
35791
36604
|
} catch (err) {
|
|
35792
|
-
|
|
36605
|
+
log3.warn(
|
|
35793
36606
|
{ err, sidecar, sourceName: source.name },
|
|
35794
36607
|
"exodus-archive: failed to archive sidecar (non-fatal)"
|
|
35795
36608
|
);
|
|
35796
36609
|
}
|
|
35797
36610
|
}
|
|
35798
36611
|
}
|
|
35799
|
-
|
|
36612
|
+
log3.info(
|
|
35800
36613
|
{ sourceName: source.name, sourcePath: source.path, archivedTo, scope: source.targetScope },
|
|
35801
36614
|
"exodus-archive: archived legacy source DB"
|
|
35802
36615
|
);
|
|
@@ -35831,7 +36644,7 @@ function detectStrandedResidue(sources, cwd) {
|
|
|
35831
36644
|
const stranded = [];
|
|
35832
36645
|
for (const source of sources) {
|
|
35833
36646
|
if (!markedScopes.has(source.targetScope)) continue;
|
|
35834
|
-
if (
|
|
36647
|
+
if (existsSync5(source.path)) {
|
|
35835
36648
|
stranded.push({ name: source.name, path: source.path, scope: source.targetScope });
|
|
35836
36649
|
}
|
|
35837
36650
|
}
|
|
@@ -35847,14 +36660,14 @@ function archiveStrandedResidue(stranded, sources, cwd) {
|
|
|
35847
36660
|
}
|
|
35848
36661
|
return results;
|
|
35849
36662
|
}
|
|
35850
|
-
var
|
|
36663
|
+
var log3, ARCHIVE_DIR_NAME, MARKER_FILENAME_BY_SCOPE, SIDECAR_SUFFIXES;
|
|
35851
36664
|
var init_archive2 = __esm({
|
|
35852
36665
|
"packages/core/src/store/exodus/archive.ts"() {
|
|
35853
36666
|
"use strict";
|
|
35854
36667
|
init_logger2();
|
|
35855
36668
|
init_paths();
|
|
35856
36669
|
init_ensure_config();
|
|
35857
|
-
|
|
36670
|
+
log3 = getLogger("exodus-archive");
|
|
35858
36671
|
ARCHIVE_DIR_NAME = "_archive";
|
|
35859
36672
|
MARKER_FILENAME_BY_SCOPE = {
|
|
35860
36673
|
project: "exodus-complete",
|
|
@@ -35865,13 +36678,13 @@ var init_archive2 = __esm({
|
|
|
35865
36678
|
});
|
|
35866
36679
|
|
|
35867
36680
|
// packages/core/src/project-info.ts
|
|
35868
|
-
import { existsSync as
|
|
35869
|
-
import { join as
|
|
36681
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
36682
|
+
import { join as join10 } from "node:path";
|
|
35870
36683
|
function getProjectInfoSync(cwd) {
|
|
35871
36684
|
const projectRoot = resolveOrCwd(cwd);
|
|
35872
36685
|
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
35873
|
-
const infoPath =
|
|
35874
|
-
if (!
|
|
36686
|
+
const infoPath = join10(cleoDir, "project-info.json");
|
|
36687
|
+
if (!existsSync6(infoPath)) return null;
|
|
35875
36688
|
try {
|
|
35876
36689
|
const raw = readFileSync2(infoPath, "utf-8");
|
|
35877
36690
|
const data = JSON.parse(raw);
|
|
@@ -35898,8 +36711,8 @@ var init_project_info = __esm({
|
|
|
35898
36711
|
});
|
|
35899
36712
|
|
|
35900
36713
|
// packages/core/src/store/worktree-isolation-guard.ts
|
|
35901
|
-
import { existsSync as
|
|
35902
|
-
import { dirname as dirname5, join as
|
|
36714
|
+
import { existsSync as existsSync7, statSync } from "node:fs";
|
|
36715
|
+
import { dirname as dirname5, join as join11 } from "node:path";
|
|
35903
36716
|
function assertDbPathIsNotWorktreeResident(role, cwd) {
|
|
35904
36717
|
let cleoDir;
|
|
35905
36718
|
try {
|
|
@@ -35908,10 +36721,10 @@ function assertDbPathIsNotWorktreeResident(role, cwd) {
|
|
|
35908
36721
|
return;
|
|
35909
36722
|
}
|
|
35910
36723
|
const projectRoot = dirname5(cleoDir);
|
|
35911
|
-
const projectGit =
|
|
36724
|
+
const projectGit = join11(projectRoot, ".git");
|
|
35912
36725
|
let isWorktreeGitlink = false;
|
|
35913
36726
|
try {
|
|
35914
|
-
isWorktreeGitlink =
|
|
36727
|
+
isWorktreeGitlink = existsSync7(projectGit) && statSync(projectGit).isFile();
|
|
35915
36728
|
} catch {
|
|
35916
36729
|
}
|
|
35917
36730
|
if (!isWorktreeGitlink) {
|
|
@@ -36376,7 +37189,7 @@ var init_table_name_map = __esm({
|
|
|
36376
37189
|
});
|
|
36377
37190
|
|
|
36378
37191
|
// packages/core/src/store/exodus/count-parity.ts
|
|
36379
|
-
import { existsSync as
|
|
37192
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
36380
37193
|
function listTables(db) {
|
|
36381
37194
|
return db.prepare(
|
|
36382
37195
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name"
|
|
@@ -36397,14 +37210,14 @@ function rowCount(db, tableName) {
|
|
|
36397
37210
|
function computeCountParity(sources, projectDbPath, globalDbPath) {
|
|
36398
37211
|
const entries = [];
|
|
36399
37212
|
let skipped = 0;
|
|
36400
|
-
if (!
|
|
37213
|
+
if (!existsSync8(projectDbPath) || !existsSync8(globalDbPath)) {
|
|
36401
37214
|
return { ok: false, entries: [], deficits: [], checked: 0, skipped: 0 };
|
|
36402
37215
|
}
|
|
36403
37216
|
const projectSnap = openCleoDbSnapshot(projectDbPath, { readOnly: true });
|
|
36404
37217
|
const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
|
|
36405
37218
|
try {
|
|
36406
37219
|
for (const src of sources) {
|
|
36407
|
-
if (!
|
|
37220
|
+
if (!existsSync8(src.path)) continue;
|
|
36408
37221
|
const srcSnap = openCleoDbSnapshot(src.path, { readOnly: true });
|
|
36409
37222
|
try {
|
|
36410
37223
|
for (const legacyTable of listTables(srcSnap.db)) {
|
|
@@ -36443,27 +37256,27 @@ function computeCountParity(sources, projectDbPath, globalDbPath) {
|
|
|
36443
37256
|
}
|
|
36444
37257
|
const deficits = entries.filter((e) => e.deficit > 0);
|
|
36445
37258
|
if (deficits.length > 0) {
|
|
36446
|
-
|
|
37259
|
+
log4.warn(
|
|
36447
37260
|
{ deficitCount: deficits.length, sample: deficits.slice(0, 5) },
|
|
36448
37261
|
`exodus count-parity: ${deficits.length} table(s) have FEWER rows in the consolidated target than the legacy source`
|
|
36449
37262
|
);
|
|
36450
37263
|
}
|
|
36451
37264
|
return { ok: deficits.length === 0, entries, deficits, checked: entries.length, skipped };
|
|
36452
37265
|
}
|
|
36453
|
-
var
|
|
37266
|
+
var log4;
|
|
36454
37267
|
var init_count_parity = __esm({
|
|
36455
37268
|
"packages/core/src/store/exodus/count-parity.ts"() {
|
|
36456
37269
|
"use strict";
|
|
36457
37270
|
init_logger2();
|
|
36458
37271
|
init_open_cleo_db();
|
|
36459
37272
|
init_table_name_map();
|
|
36460
|
-
|
|
37273
|
+
log4 = getLogger("exodus-count-parity");
|
|
36461
37274
|
}
|
|
36462
37275
|
});
|
|
36463
37276
|
|
|
36464
37277
|
// packages/core/src/store/exodus/plan.ts
|
|
36465
|
-
import { existsSync as
|
|
36466
|
-
import { join as
|
|
37278
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, statfsSync, statSync as statSync2 } from "node:fs";
|
|
37279
|
+
import { join as join12 } from "node:path";
|
|
36467
37280
|
function computeRequiredBytes(totalSourceBytes, largestSourceBytes) {
|
|
36468
37281
|
return Math.ceil(STAGING_HEADROOM_FACTOR * largestSourceBytes) + totalSourceBytes;
|
|
36469
37282
|
}
|
|
@@ -36474,33 +37287,33 @@ function buildSourceDescriptors(cwd) {
|
|
|
36474
37287
|
// Project-tier — go into consolidated project-scope cleo.db
|
|
36475
37288
|
{
|
|
36476
37289
|
name: "tasks",
|
|
36477
|
-
path:
|
|
37290
|
+
path: join12(cleoDir, "tasks.db"),
|
|
36478
37291
|
targetScope: "project"
|
|
36479
37292
|
},
|
|
36480
37293
|
{
|
|
36481
37294
|
name: "brain (project)",
|
|
36482
|
-
path:
|
|
37295
|
+
path: join12(cleoDir, "brain.db"),
|
|
36483
37296
|
targetScope: "project"
|
|
36484
37297
|
},
|
|
36485
37298
|
{
|
|
36486
37299
|
name: "conduit",
|
|
36487
|
-
path:
|
|
37300
|
+
path: join12(cleoDir, "conduit.db"),
|
|
36488
37301
|
targetScope: "project"
|
|
36489
37302
|
},
|
|
36490
37303
|
// Global-tier — go into consolidated global-scope cleo.db
|
|
36491
37304
|
{
|
|
36492
37305
|
name: "nexus",
|
|
36493
|
-
path:
|
|
37306
|
+
path: join12(cleoHome, "nexus.db"),
|
|
36494
37307
|
targetScope: "global"
|
|
36495
37308
|
},
|
|
36496
37309
|
{
|
|
36497
37310
|
name: "signaldock",
|
|
36498
|
-
path:
|
|
37311
|
+
path: join12(cleoHome, "signaldock.db"),
|
|
36499
37312
|
targetScope: "global"
|
|
36500
37313
|
},
|
|
36501
37314
|
{
|
|
36502
37315
|
name: "skills",
|
|
36503
|
-
path:
|
|
37316
|
+
path: join12(cleoHome, "skills.db"),
|
|
36504
37317
|
targetScope: "global"
|
|
36505
37318
|
}
|
|
36506
37319
|
];
|
|
@@ -36526,10 +37339,10 @@ function deriveStagingDirName() {
|
|
|
36526
37339
|
}
|
|
36527
37340
|
function findExistingStaging(cleoDir) {
|
|
36528
37341
|
try {
|
|
36529
|
-
const entries =
|
|
37342
|
+
const entries = readdirSync3(cleoDir, { withFileTypes: true });
|
|
36530
37343
|
const stagingDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => e.name).sort().reverse();
|
|
36531
37344
|
if (stagingDirs.length > 0) {
|
|
36532
|
-
return
|
|
37345
|
+
return join12(cleoDir, stagingDirs[0]);
|
|
36533
37346
|
}
|
|
36534
37347
|
} catch {
|
|
36535
37348
|
}
|
|
@@ -36545,7 +37358,7 @@ function buildExodusPlan(cwd) {
|
|
|
36545
37358
|
const availableBytes = getAvailableBytes(cleoDir);
|
|
36546
37359
|
const diskPreflight = totalSourceBytes === 0 || availableBytes >= requiredBytes;
|
|
36547
37360
|
const existingStaging = findExistingStaging(cleoDir);
|
|
36548
|
-
const stagingDir = existingStaging ??
|
|
37361
|
+
const stagingDir = existingStaging ?? join12(cleoDir, deriveStagingDirName());
|
|
36549
37362
|
const resumeFromStaging = existingStaging !== null;
|
|
36550
37363
|
const projectDbPath = resolveDualScopeDbPath("project", cwd);
|
|
36551
37364
|
const globalDbPath = resolveDualScopeDbPath("global");
|
|
@@ -36564,7 +37377,7 @@ function buildExodusPlan(cwd) {
|
|
|
36564
37377
|
};
|
|
36565
37378
|
}
|
|
36566
37379
|
function sourcesPresent(sources) {
|
|
36567
|
-
return sources.some((s) =>
|
|
37380
|
+
return sources.some((s) => existsSync9(s.path));
|
|
36568
37381
|
}
|
|
36569
37382
|
var STAGING_HEADROOM_FACTOR, STAGING_COPY_SKIP_THRESHOLD_BYTES;
|
|
36570
37383
|
var init_plan2 = __esm({
|
|
@@ -36578,18 +37391,18 @@ var init_plan2 = __esm({
|
|
|
36578
37391
|
});
|
|
36579
37392
|
|
|
36580
37393
|
// packages/core/src/store/exodus/health.ts
|
|
36581
|
-
import { existsSync as
|
|
37394
|
+
import { existsSync as existsSync10, statSync as statSync3 } from "node:fs";
|
|
36582
37395
|
function buildExodusHealth(cwd) {
|
|
36583
37396
|
const plan = buildExodusPlan(cwd);
|
|
36584
|
-
const anyLegacyPresent = plan.sources.some((s) =>
|
|
37397
|
+
const anyLegacyPresent = plan.sources.some((s) => existsSync10(s.path));
|
|
36585
37398
|
const parity = anyLegacyPresent ? computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath) : { ok: true, entries: [], deficits: [], checked: 0, skipped: 0 };
|
|
36586
37399
|
const consolidatedExistsByScope = {
|
|
36587
|
-
project:
|
|
36588
|
-
global:
|
|
37400
|
+
project: existsSync10(plan.projectDbPath),
|
|
37401
|
+
global: existsSync10(plan.globalDbPath)
|
|
36589
37402
|
};
|
|
36590
37403
|
const buildScope = (scope) => {
|
|
36591
37404
|
const sources = plan.sources.filter((s) => s.targetScope === scope).map((s) => {
|
|
36592
|
-
const present =
|
|
37405
|
+
const present = existsSync10(s.path);
|
|
36593
37406
|
let bytes = 0;
|
|
36594
37407
|
if (present) {
|
|
36595
37408
|
try {
|
|
@@ -36856,15 +37669,15 @@ var init_types = __esm({
|
|
|
36856
37669
|
// packages/core/src/store/exodus/migrate.ts
|
|
36857
37670
|
import {
|
|
36858
37671
|
copyFileSync as copyFileSync3,
|
|
36859
|
-
existsSync as
|
|
36860
|
-
mkdirSync as
|
|
37672
|
+
existsSync as existsSync11,
|
|
37673
|
+
mkdirSync as mkdirSync3,
|
|
36861
37674
|
readFileSync as readFileSync3,
|
|
36862
37675
|
renameSync as renameSync2,
|
|
36863
37676
|
statSync as statSync4,
|
|
36864
37677
|
unlinkSync as unlinkSync2,
|
|
36865
|
-
writeFileSync as
|
|
37678
|
+
writeFileSync as writeFileSync4
|
|
36866
37679
|
} from "node:fs";
|
|
36867
|
-
import { join as
|
|
37680
|
+
import { join as join13 } from "node:path";
|
|
36868
37681
|
function getSqliteVersion(db) {
|
|
36869
37682
|
try {
|
|
36870
37683
|
const row = db.prepare("SELECT sqlite_version() AS v").get();
|
|
@@ -36880,14 +37693,14 @@ function listTables2(db) {
|
|
|
36880
37693
|
return rows.map((r) => r.name);
|
|
36881
37694
|
}
|
|
36882
37695
|
function writeJournal(stagingDir, journal) {
|
|
36883
|
-
const journalPath =
|
|
37696
|
+
const journalPath = join13(stagingDir, JOURNAL_FILENAME);
|
|
36884
37697
|
const tmpPath = `${journalPath}.tmp`;
|
|
36885
|
-
|
|
37698
|
+
writeFileSync4(tmpPath, JSON.stringify(journal, null, 2) + "\n", "utf8");
|
|
36886
37699
|
renameSync2(tmpPath, journalPath);
|
|
36887
37700
|
}
|
|
36888
37701
|
function readJournal(stagingDir) {
|
|
36889
|
-
const journalPath =
|
|
36890
|
-
if (!
|
|
37702
|
+
const journalPath = join13(stagingDir, JOURNAL_FILENAME);
|
|
37703
|
+
if (!existsSync11(journalPath)) return null;
|
|
36891
37704
|
try {
|
|
36892
37705
|
return JSON.parse(readFileSync3(journalPath, "utf8"));
|
|
36893
37706
|
} catch {
|
|
@@ -36895,17 +37708,17 @@ function readJournal(stagingDir) {
|
|
|
36895
37708
|
}
|
|
36896
37709
|
}
|
|
36897
37710
|
function clearExodusJournal(stagingDir) {
|
|
36898
|
-
const journalPath =
|
|
37711
|
+
const journalPath = join13(stagingDir, JOURNAL_FILENAME);
|
|
36899
37712
|
try {
|
|
36900
|
-
if (!
|
|
37713
|
+
if (!existsSync11(journalPath)) return false;
|
|
36901
37714
|
unlinkSync2(journalPath);
|
|
36902
|
-
|
|
37715
|
+
log5.info(
|
|
36903
37716
|
{ stagingDir },
|
|
36904
37717
|
"exodus: cleared migrate journal after abort/rollback \u2014 a retry will RE-COPY all tables"
|
|
36905
37718
|
);
|
|
36906
37719
|
return true;
|
|
36907
37720
|
} catch (err) {
|
|
36908
|
-
|
|
37721
|
+
log5.warn(
|
|
36909
37722
|
{ err, stagingDir },
|
|
36910
37723
|
'exodus: failed to clear migrate journal after rollback (a retry may skip already-"done" tables)'
|
|
36911
37724
|
);
|
|
@@ -36930,7 +37743,7 @@ function lockPath(dbPath) {
|
|
|
36930
37743
|
}
|
|
36931
37744
|
function acquireAdvisoryLock(dbPath) {
|
|
36932
37745
|
const lp = lockPath(dbPath);
|
|
36933
|
-
|
|
37746
|
+
writeFileSync4(lp, JSON.stringify({ pid: process.pid, ts: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
36934
37747
|
}
|
|
36935
37748
|
function releaseAdvisoryLock(dbPath) {
|
|
36936
37749
|
try {
|
|
@@ -36993,7 +37806,7 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
|
|
|
36993
37806
|
function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyTableName, sourceName, targetSchema = "main") {
|
|
36994
37807
|
const resolution = resolveConsolidatedTableName(sourceName, legacyTableName);
|
|
36995
37808
|
if (resolution.kind === "skip") {
|
|
36996
|
-
|
|
37809
|
+
log5.warn(
|
|
36997
37810
|
{ legacyTableName, sourceName, reason: resolution.reason },
|
|
36998
37811
|
`Exodus: explicitly skipping table \u2014 ${resolution.reason}`
|
|
36999
37812
|
);
|
|
@@ -37012,7 +37825,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37012
37825
|
).get();
|
|
37013
37826
|
if (!existsRow) {
|
|
37014
37827
|
const reason = `consolidated target '${targetTableName}' not found (mapped from legacy '${legacyTableName}')`;
|
|
37015
|
-
|
|
37828
|
+
log5.warn(
|
|
37016
37829
|
{ legacyTableName, targetTableName, sourceName, attachAlias, targetSchema },
|
|
37017
37830
|
`Exodus: ${reason}`
|
|
37018
37831
|
);
|
|
@@ -37023,13 +37836,13 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37023
37836
|
const sharedColumns = srcPragma.map((r) => r.name).filter((col) => tgtColMap.has(col));
|
|
37024
37837
|
if (sharedColumns.length === 0) {
|
|
37025
37838
|
const reason = `no overlapping columns between source '${legacyTableName}' and target '${targetTableName}'`;
|
|
37026
|
-
|
|
37839
|
+
log5.warn({ legacyTableName, targetTableName, sourceName }, `Exodus: ${reason}`);
|
|
37027
37840
|
return { rowsCopied: 0, skipped: true, reason };
|
|
37028
37841
|
}
|
|
37029
37842
|
const srcOnlyColumns = srcPragma.map((r) => r.name).filter((c) => !tgtColMap.has(c));
|
|
37030
37843
|
const tgtOnlyColumns = tgtPragma.map((r) => r.name).filter((c) => !srcColumns.has(c));
|
|
37031
37844
|
if (srcOnlyColumns.length > 0 || tgtOnlyColumns.length > 0) {
|
|
37032
|
-
|
|
37845
|
+
log5.info(
|
|
37033
37846
|
{ legacyTableName, targetTableName, sourceName, srcOnlyColumns, tgtOnlyColumns },
|
|
37034
37847
|
"Exodus: column drift detected \u2014 copying intersection, dropping src-only cols, using defaults for tgt-only cols"
|
|
37035
37848
|
);
|
|
@@ -37044,7 +37857,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37044
37857
|
return isoGlobCols.has(col) && (upper.includes("INT") || upper === "" || upper === "NUMERIC");
|
|
37045
37858
|
});
|
|
37046
37859
|
if (coercedCols.length > 0) {
|
|
37047
|
-
|
|
37860
|
+
log5.info(
|
|
37048
37861
|
{
|
|
37049
37862
|
legacyTableName,
|
|
37050
37863
|
targetTableName,
|
|
@@ -37060,7 +37873,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37060
37873
|
(col) => ENUM_NORMALIZATIONS.has(`${targetTableName}.${col}`)
|
|
37061
37874
|
);
|
|
37062
37875
|
if (normalizedCols.length > 0) {
|
|
37063
|
-
|
|
37876
|
+
log5.info(
|
|
37064
37877
|
{ legacyTableName, targetTableName, sourceName, normalizedCols },
|
|
37065
37878
|
`Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`
|
|
37066
37879
|
);
|
|
@@ -37069,7 +37882,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37069
37882
|
(col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`)
|
|
37070
37883
|
);
|
|
37071
37884
|
if (clampedCols.length > 0) {
|
|
37072
|
-
|
|
37885
|
+
log5.info(
|
|
37073
37886
|
{ legacyTableName, targetTableName, sourceName, clampedCols },
|
|
37074
37887
|
`Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`
|
|
37075
37888
|
);
|
|
@@ -37111,7 +37924,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37111
37924
|
if (rowsCopied < sourceCount) {
|
|
37112
37925
|
const presentAccountedFor = existingBefore + rowsCopied;
|
|
37113
37926
|
if (presentAccountedFor >= sourceCount) {
|
|
37114
|
-
|
|
37927
|
+
log5.info(
|
|
37115
37928
|
{ legacyTableName, targetTableName, sourceName, sourceCount, rowsCopied, existingBefore },
|
|
37116
37929
|
`Exodus: '${legacyTableName}'\u2192'${targetTableName}' \u2014 ${sourceCount - rowsCopied} of ${sourceCount} row(s) already present in target (idempotent PK dedup), ${rowsCopied} newly copied; no loss`
|
|
37117
37930
|
);
|
|
@@ -37119,7 +37932,7 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
37119
37932
|
}
|
|
37120
37933
|
const missing = sourceCount - presentAccountedFor;
|
|
37121
37934
|
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.`;
|
|
37122
|
-
|
|
37935
|
+
log5.error(
|
|
37123
37936
|
{
|
|
37124
37937
|
legacyTableName,
|
|
37125
37938
|
targetTableName,
|
|
@@ -37139,10 +37952,10 @@ function checkSchemaVersion(journal, forceCrossVersion) {
|
|
|
37139
37952
|
if (journal.targetSchemaVersion !== EXODUS_TARGET_SCHEMA_VERSION) {
|
|
37140
37953
|
const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
|
|
37141
37954
|
if (forceCrossVersion) {
|
|
37142
|
-
|
|
37955
|
+
log5.warn(msg + " (--force-cross-version: continuing anyway)");
|
|
37143
37956
|
return true;
|
|
37144
37957
|
}
|
|
37145
|
-
|
|
37958
|
+
log5.error(msg + " \u2014 pass --force-cross-version to override");
|
|
37146
37959
|
return false;
|
|
37147
37960
|
}
|
|
37148
37961
|
return true;
|
|
@@ -37159,10 +37972,10 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
|
|
|
37159
37972
|
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.`
|
|
37160
37973
|
};
|
|
37161
37974
|
}
|
|
37162
|
-
|
|
37975
|
+
mkdirSync3(stagingDir, { recursive: true });
|
|
37163
37976
|
let sqliteVersion = "unknown";
|
|
37164
37977
|
for (const src of sources) {
|
|
37165
|
-
if (
|
|
37978
|
+
if (existsSync11(src.path)) {
|
|
37166
37979
|
const snap = openCleoDbSnapshot(src.path, { readOnly: true });
|
|
37167
37980
|
sqliteVersion = getSqliteVersion(snap.db);
|
|
37168
37981
|
snap.close();
|
|
@@ -37203,15 +38016,15 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
|
|
|
37203
38016
|
};
|
|
37204
38017
|
var extractNativeDb = extractNativeDb2;
|
|
37205
38018
|
for (const src of sources) {
|
|
37206
|
-
if (!
|
|
37207
|
-
const backupDest =
|
|
38019
|
+
if (!existsSync11(src.path)) continue;
|
|
38020
|
+
const backupDest = join13(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, "_")}-backup.db`);
|
|
37208
38021
|
const srcBytes = safeStatBytes(src.path);
|
|
37209
38022
|
const skipStagingCopy = srcBytes > plan.stagingCopyThresholdBytes;
|
|
37210
38023
|
if (skipStagingCopy) {
|
|
37211
38024
|
onProgress?.(
|
|
37212
38025
|
`Skipping full staging copy of ${src.name} (${srcBytes} bytes > ${plan.stagingCopyThresholdBytes} threshold) \u2014 source is archived, not deleted, on success.`
|
|
37213
38026
|
);
|
|
37214
|
-
} else if (!
|
|
38027
|
+
} else if (!existsSync11(backupDest)) {
|
|
37215
38028
|
onProgress?.(`Backing up ${src.name} \u2192 staging dir\u2026`);
|
|
37216
38029
|
copyFileSync3(src.path, backupDest);
|
|
37217
38030
|
backupPaths.push(backupDest);
|
|
@@ -37229,8 +38042,8 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
|
|
|
37229
38042
|
});
|
|
37230
38043
|
const projectNative = extractNativeDb2(projectHandle);
|
|
37231
38044
|
const globalNative = extractNativeDb2(globalHandle);
|
|
37232
|
-
const projectSources = sources.filter((s) => s.targetScope === "project" &&
|
|
37233
|
-
const globalSources = sources.filter((s) => s.targetScope === "global" &&
|
|
38045
|
+
const projectSources = sources.filter((s) => s.targetScope === "project" && existsSync11(s.path));
|
|
38046
|
+
const globalSources = sources.filter((s) => s.targetScope === "global" && existsSync11(s.path));
|
|
37234
38047
|
await migrateScope(
|
|
37235
38048
|
"project",
|
|
37236
38049
|
projectSources,
|
|
@@ -37255,7 +38068,7 @@ async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
|
|
|
37255
38068
|
return { ok: true, tables: allTableResults, stagingDir, backupPaths };
|
|
37256
38069
|
} catch (err) {
|
|
37257
38070
|
const error = err instanceof Error ? err.message : String(err);
|
|
37258
|
-
|
|
38071
|
+
log5.error({ err }, "Exodus migration failed");
|
|
37259
38072
|
return { ok: false, tables: allTableResults, stagingDir, backupPaths, error };
|
|
37260
38073
|
} finally {
|
|
37261
38074
|
try {
|
|
@@ -37275,7 +38088,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
37275
38088
|
if (sources.length === 0) return;
|
|
37276
38089
|
onProgress?.(`Migrating ${scope}-scope sources\u2026`);
|
|
37277
38090
|
targetNativeDb.exec("PRAGMA foreign_keys = OFF");
|
|
37278
|
-
|
|
38091
|
+
log5.info({ scope }, "Exodus: foreign_keys=OFF for bulk copy (T11533 FK-defer)");
|
|
37279
38092
|
try {
|
|
37280
38093
|
for (let i = 0; i < sources.length; i++) {
|
|
37281
38094
|
const src = sources[i];
|
|
@@ -37342,7 +38155,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
37342
38155
|
}
|
|
37343
38156
|
} catch (err) {
|
|
37344
38157
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37345
|
-
|
|
38158
|
+
log5.warn({ tableName, sourceDb: src.name, err }, "Table copy failed \u2014 skipping");
|
|
37346
38159
|
status = "skipped";
|
|
37347
38160
|
errorMsg = msg;
|
|
37348
38161
|
skipped = true;
|
|
@@ -37390,7 +38203,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
37390
38203
|
targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
|
|
37391
38204
|
onProgress?.(` [${src.name}] Detached "${attachAlias}"`);
|
|
37392
38205
|
} catch (detachErr) {
|
|
37393
|
-
|
|
38206
|
+
log5.warn(
|
|
37394
38207
|
{ attachAlias, sourceDb: src.name, err: detachErr },
|
|
37395
38208
|
"DETACH failed \u2014 alias will be released on DB close"
|
|
37396
38209
|
);
|
|
@@ -37400,7 +38213,7 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
37400
38213
|
targetNativeDb.exec(`DETACH DATABASE "${crossAlias}"`);
|
|
37401
38214
|
onProgress?.(` [${src.name}] Cross-scope target detached "${crossAlias}"`);
|
|
37402
38215
|
} catch (detachErr) {
|
|
37403
|
-
|
|
38216
|
+
log5.warn(
|
|
37404
38217
|
{ crossAlias, sourceDb: src.name, err: detachErr },
|
|
37405
38218
|
"Cross-scope DETACH failed \u2014 alias will be released on DB close"
|
|
37406
38219
|
);
|
|
@@ -37412,28 +38225,28 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
37412
38225
|
try {
|
|
37413
38226
|
const orphans = targetNativeDb.prepare("PRAGMA foreign_key_check").all();
|
|
37414
38227
|
if (orphans.length > 0) {
|
|
37415
|
-
|
|
38228
|
+
log5.warn(
|
|
37416
38229
|
{ scope, orphanCount: orphans.length, sample: orphans.slice(0, 5) },
|
|
37417
38230
|
`Exodus: PRAGMA foreign_key_check found ${orphans.length} orphan row(s) after bulk copy \u2014 these are genuine data orphans (not ordering artifacts)`
|
|
37418
38231
|
);
|
|
37419
38232
|
} else {
|
|
37420
|
-
|
|
38233
|
+
log5.info({ scope }, "Exodus: PRAGMA foreign_key_check PASSED \u2014 no orphan rows");
|
|
37421
38234
|
}
|
|
37422
38235
|
} catch (checkErr) {
|
|
37423
|
-
|
|
38236
|
+
log5.warn(
|
|
37424
38237
|
{ scope, err: checkErr },
|
|
37425
38238
|
"Exodus: PRAGMA foreign_key_check failed (non-fatal) \u2014 target schema may not have FK constraints enabled"
|
|
37426
38239
|
);
|
|
37427
38240
|
}
|
|
37428
38241
|
try {
|
|
37429
38242
|
targetNativeDb.exec("PRAGMA foreign_keys = ON");
|
|
37430
|
-
|
|
38243
|
+
log5.info({ scope }, "Exodus: foreign_keys=ON restored after bulk copy");
|
|
37431
38244
|
} catch (fkErr) {
|
|
37432
|
-
|
|
38245
|
+
log5.warn({ scope, err: fkErr }, "Exodus: could not restore foreign_keys=ON (non-fatal)");
|
|
37433
38246
|
}
|
|
37434
38247
|
}
|
|
37435
38248
|
}
|
|
37436
|
-
var
|
|
38249
|
+
var log5, LOCK_SENTINEL_SUFFIX, JOURNAL_FILENAME, SOURCE_EPOCH_UNITS;
|
|
37437
38250
|
var init_migrate = __esm({
|
|
37438
38251
|
"packages/core/src/store/exodus/migrate.ts"() {
|
|
37439
38252
|
"use strict";
|
|
@@ -37445,7 +38258,7 @@ var init_migrate = __esm({
|
|
|
37445
38258
|
init_plan2();
|
|
37446
38259
|
init_table_name_map();
|
|
37447
38260
|
init_types();
|
|
37448
|
-
|
|
38261
|
+
log5 = getLogger("exodus-migrate");
|
|
37449
38262
|
LOCK_SENTINEL_SUFFIX = ".exodus-lock";
|
|
37450
38263
|
JOURNAL_FILENAME = "exodus-journal.json";
|
|
37451
38264
|
SOURCE_EPOCH_UNITS = /* @__PURE__ */ new Map([
|
|
@@ -37469,7 +38282,7 @@ function sealExodus(plan, scopeArg, cwd) {
|
|
|
37469
38282
|
const parity = computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath);
|
|
37470
38283
|
if (!parity.ok) {
|
|
37471
38284
|
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(", ")}`;
|
|
37472
|
-
|
|
38285
|
+
log6.error({ deficits: parity.deficits.length }, `exodus seal refused \u2014 ${refusedReason}`);
|
|
37473
38286
|
return { ok: false, refusedReason, parity, scopes: [] };
|
|
37474
38287
|
}
|
|
37475
38288
|
const outcomes = [];
|
|
@@ -37485,7 +38298,7 @@ function sealExodus(plan, scopeArg, cwd) {
|
|
|
37485
38298
|
scopeSources.map((s) => s.name),
|
|
37486
38299
|
cwd
|
|
37487
38300
|
);
|
|
37488
|
-
|
|
38301
|
+
log6.info(
|
|
37489
38302
|
{ scope, alreadySealed, archived: archived.filter((a) => a.action === "archived").length },
|
|
37490
38303
|
`exodus seal: scope '${scope}' certified (count-parity verified, ${parity.checked} tables)`
|
|
37491
38304
|
);
|
|
@@ -37493,23 +38306,23 @@ function sealExodus(plan, scopeArg, cwd) {
|
|
|
37493
38306
|
}
|
|
37494
38307
|
return { ok: true, parity, scopes: outcomes };
|
|
37495
38308
|
}
|
|
37496
|
-
var
|
|
38309
|
+
var log6;
|
|
37497
38310
|
var init_seal = __esm({
|
|
37498
38311
|
"packages/core/src/store/exodus/seal.ts"() {
|
|
37499
38312
|
"use strict";
|
|
37500
38313
|
init_logger2();
|
|
37501
38314
|
init_archive2();
|
|
37502
38315
|
init_count_parity();
|
|
37503
|
-
|
|
38316
|
+
log6 = getLogger("exodus-seal");
|
|
37504
38317
|
}
|
|
37505
38318
|
});
|
|
37506
38319
|
|
|
37507
38320
|
// packages/core/src/store/exodus/status.ts
|
|
37508
|
-
import { existsSync as
|
|
37509
|
-
import { join as
|
|
38321
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
|
|
38322
|
+
import { join as join14 } from "node:path";
|
|
37510
38323
|
function readJournal2(stagingDir) {
|
|
37511
|
-
const p =
|
|
37512
|
-
if (!
|
|
38324
|
+
const p = join14(stagingDir, JOURNAL_FILENAME2);
|
|
38325
|
+
if (!existsSync12(p)) return null;
|
|
37513
38326
|
try {
|
|
37514
38327
|
return JSON.parse(readFileSync4(p, "utf8"));
|
|
37515
38328
|
} catch {
|
|
@@ -37518,7 +38331,7 @@ function readJournal2(stagingDir) {
|
|
|
37518
38331
|
}
|
|
37519
38332
|
function findStagingDirs(cleoDir) {
|
|
37520
38333
|
try {
|
|
37521
|
-
return
|
|
38334
|
+
return readdirSync4(cleoDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("exodus-staging-")).map((e) => join14(cleoDir, e.name)).sort().reverse();
|
|
37522
38335
|
} catch {
|
|
37523
38336
|
return [];
|
|
37524
38337
|
}
|
|
@@ -37541,15 +38354,15 @@ function runExodusStatus(cwd) {
|
|
|
37541
38354
|
const sourcesInfo = plan.sources.map((s) => ({
|
|
37542
38355
|
name: s.name,
|
|
37543
38356
|
path: s.path,
|
|
37544
|
-
exists:
|
|
38357
|
+
exists: existsSync12(s.path),
|
|
37545
38358
|
bytes: safeBytes(s.path)
|
|
37546
38359
|
}));
|
|
37547
38360
|
return {
|
|
37548
38361
|
hasStaging: latestStaging !== null,
|
|
37549
38362
|
stagingDir: latestStaging,
|
|
37550
38363
|
journal,
|
|
37551
|
-
projectDbExists:
|
|
37552
|
-
globalDbExists:
|
|
38364
|
+
projectDbExists: existsSync12(projectDbPath),
|
|
38365
|
+
globalDbExists: existsSync12(globalDbPath),
|
|
37553
38366
|
sourcesPresent: sourcesInfo.some((s) => s.exists),
|
|
37554
38367
|
sources: sourcesInfo
|
|
37555
38368
|
};
|
|
@@ -37566,7 +38379,7 @@ var init_status = __esm({
|
|
|
37566
38379
|
});
|
|
37567
38380
|
|
|
37568
38381
|
// packages/core/src/store/exodus/verify-migration.ts
|
|
37569
|
-
import { existsSync as
|
|
38382
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
37570
38383
|
import { createRequire as createRequire3 } from "node:module";
|
|
37571
38384
|
function orderByClause(db, tableName) {
|
|
37572
38385
|
try {
|
|
@@ -37586,7 +38399,7 @@ function computeTableDigest(db, tableName, columns, transform) {
|
|
|
37586
38399
|
count = Number(row?.c ?? 0);
|
|
37587
38400
|
} catch (err) {
|
|
37588
38401
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37589
|
-
|
|
38402
|
+
log7.warn(
|
|
37590
38403
|
{ tableName, err: msg },
|
|
37591
38404
|
"computeTableDigest: COUNT(*) failed (possibly a virtual/FTS table) \u2014 treating as 0 rows"
|
|
37592
38405
|
);
|
|
@@ -37621,7 +38434,7 @@ function computeTableDigest(db, tableName, columns, transform) {
|
|
|
37621
38434
|
}
|
|
37622
38435
|
} catch (err) {
|
|
37623
38436
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37624
|
-
|
|
38437
|
+
log7.warn(
|
|
37625
38438
|
{ tableName, err: msg },
|
|
37626
38439
|
"computeTableDigest: streamed SELECT failed (possibly a virtual/FTS table) \u2014 digest skipped (COUNT(*) parity still enforced)"
|
|
37627
38440
|
);
|
|
@@ -37747,7 +38560,7 @@ function foreignKeyCheck(db, scope) {
|
|
|
37747
38560
|
try {
|
|
37748
38561
|
const rows = db.prepare("PRAGMA foreign_key_check").all();
|
|
37749
38562
|
if (rows.length > 0) {
|
|
37750
|
-
|
|
38563
|
+
log7.warn(
|
|
37751
38564
|
{ scope, count: rows.length, sample: rows.slice(0, 5) },
|
|
37752
38565
|
`verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`
|
|
37753
38566
|
);
|
|
@@ -37759,7 +38572,7 @@ function foreignKeyCheck(db, scope) {
|
|
|
37759
38572
|
fkid: r.fkid
|
|
37760
38573
|
}));
|
|
37761
38574
|
} catch (err) {
|
|
37762
|
-
|
|
38575
|
+
log7.warn({ scope, err }, "verifyMigration: PRAGMA foreign_key_check failed (non-fatal)");
|
|
37763
38576
|
return [];
|
|
37764
38577
|
}
|
|
37765
38578
|
}
|
|
@@ -37796,13 +38609,13 @@ function sourceOrphanSignatures(db, sourceName, scope) {
|
|
|
37796
38609
|
const rows = db.prepare("PRAGMA foreign_key_check").all();
|
|
37797
38610
|
for (const r of rows) sigs.add(orphanSignature(db, r, sourceName));
|
|
37798
38611
|
if (rows.length > 0) {
|
|
37799
|
-
|
|
38612
|
+
log7.warn(
|
|
37800
38613
|
{ scope, count: rows.length, sample: rows.slice(0, 5) },
|
|
37801
38614
|
`verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) \u2014 these are tolerated (carried forward losslessly, flagged for data-hygiene)`
|
|
37802
38615
|
);
|
|
37803
38616
|
}
|
|
37804
38617
|
} catch (err) {
|
|
37805
|
-
|
|
38618
|
+
log7.warn({ scope, err }, "verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)");
|
|
37806
38619
|
}
|
|
37807
38620
|
return sigs;
|
|
37808
38621
|
}
|
|
@@ -37814,7 +38627,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37814
38627
|
const preExistingForeignKeyViolations = [];
|
|
37815
38628
|
const failureLines = [];
|
|
37816
38629
|
const sourceOrphanSigs = /* @__PURE__ */ new Set();
|
|
37817
|
-
if (!
|
|
38630
|
+
if (!existsSync13(projectDbPath)) {
|
|
37818
38631
|
return {
|
|
37819
38632
|
ok: false,
|
|
37820
38633
|
tables: [],
|
|
@@ -37825,7 +38638,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37825
38638
|
error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`
|
|
37826
38639
|
};
|
|
37827
38640
|
}
|
|
37828
|
-
if (!
|
|
38641
|
+
if (!existsSync13(globalDbPath)) {
|
|
37829
38642
|
return {
|
|
37830
38643
|
ok: false,
|
|
37831
38644
|
tables: [],
|
|
@@ -37840,7 +38653,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37840
38653
|
const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
|
|
37841
38654
|
try {
|
|
37842
38655
|
for (const src of sources) {
|
|
37843
|
-
if (!
|
|
38656
|
+
if (!existsSync13(src.path)) {
|
|
37844
38657
|
onProgress?.(`Skipping ${src.name} (not present)`);
|
|
37845
38658
|
continue;
|
|
37846
38659
|
}
|
|
@@ -37925,7 +38738,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37925
38738
|
`[${scope}] ${src.name}.${legacyTableName} \u2192 ${targetTableName}: DEFICIT \u2014 source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`
|
|
37926
38739
|
);
|
|
37927
38740
|
} else if (tgtDigest.count > srcDigest.count) {
|
|
37928
|
-
|
|
38741
|
+
log7.warn(
|
|
37929
38742
|
{
|
|
37930
38743
|
scope,
|
|
37931
38744
|
source: src.name,
|
|
@@ -37975,7 +38788,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37975
38788
|
}
|
|
37976
38789
|
}
|
|
37977
38790
|
if (preExistingForeignKeyViolations.length > 0) {
|
|
37978
|
-
|
|
38791
|
+
log7.warn(
|
|
37979
38792
|
{
|
|
37980
38793
|
count: preExistingForeignKeyViolations.length,
|
|
37981
38794
|
sample: preExistingForeignKeyViolations.slice(0, 5)
|
|
@@ -37985,7 +38798,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
37985
38798
|
}
|
|
37986
38799
|
} catch (err) {
|
|
37987
38800
|
const error = err instanceof Error ? err.message : String(err);
|
|
37988
|
-
|
|
38801
|
+
log7.error({ err }, "verifyMigration failed");
|
|
37989
38802
|
return {
|
|
37990
38803
|
ok: false,
|
|
37991
38804
|
tables,
|
|
@@ -38002,7 +38815,7 @@ function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
|
38002
38815
|
if (failureLines.length > 0) {
|
|
38003
38816
|
const error = `verifyMigration FAILED: ${failureLines.length} issue(s):
|
|
38004
38817
|
${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
|
|
38005
|
-
|
|
38818
|
+
log7.error({ failureCount: failureLines.length }, error);
|
|
38006
38819
|
return {
|
|
38007
38820
|
ok: false,
|
|
38008
38821
|
tables,
|
|
@@ -38022,7 +38835,7 @@ ${failureLines.map((l) => ` \u2022 ${l}`).join("\n")}`;
|
|
|
38022
38835
|
enumDrift
|
|
38023
38836
|
};
|
|
38024
38837
|
}
|
|
38025
|
-
var
|
|
38838
|
+
var log7, _require, CHECK_ENUM_REGEX;
|
|
38026
38839
|
var init_verify_migration = __esm({
|
|
38027
38840
|
"packages/core/src/store/exodus/verify-migration.ts"() {
|
|
38028
38841
|
"use strict";
|
|
@@ -38031,7 +38844,7 @@ var init_verify_migration = __esm({
|
|
|
38031
38844
|
init_open_cleo_db();
|
|
38032
38845
|
init_column_transforms();
|
|
38033
38846
|
init_table_name_map();
|
|
38034
|
-
|
|
38847
|
+
log7 = getLogger("verify-migration");
|
|
38035
38848
|
_require = createRequire3(import.meta.url);
|
|
38036
38849
|
CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
|
|
38037
38850
|
}
|
|
@@ -38123,7 +38936,7 @@ __export(on_open_exports, {
|
|
|
38123
38936
|
isDataContinuityOk: () => isDataContinuityOk,
|
|
38124
38937
|
maybeRunExodusOnOpen: () => maybeRunExodusOnOpen
|
|
38125
38938
|
});
|
|
38126
|
-
import { existsSync as
|
|
38939
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
38127
38940
|
function isDisabledByEnv() {
|
|
38128
38941
|
const v = process.env.CLEO_DISABLE_EXODUS_ON_OPEN;
|
|
38129
38942
|
return v === "1" || v === "true";
|
|
@@ -38149,7 +38962,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
|
|
|
38149
38962
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%'"
|
|
38150
38963
|
).all().map((r) => r.name);
|
|
38151
38964
|
} catch (err) {
|
|
38152
|
-
|
|
38965
|
+
log8.error({ err, scope }, "exodus-on-open: failed to enumerate tables for rollback");
|
|
38153
38966
|
return;
|
|
38154
38967
|
}
|
|
38155
38968
|
try {
|
|
@@ -38159,7 +38972,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
|
|
|
38159
38972
|
try {
|
|
38160
38973
|
nativeDb.exec(`DELETE FROM "${table}"`);
|
|
38161
38974
|
} catch (err) {
|
|
38162
|
-
|
|
38975
|
+
log8.warn({ err, table, scope }, "exodus-on-open: failed to clear table during rollback");
|
|
38163
38976
|
}
|
|
38164
38977
|
}
|
|
38165
38978
|
nativeDb.exec("COMMIT");
|
|
@@ -38168,7 +38981,7 @@ function rollbackConsolidatedToEmpty(nativeDb, scope) {
|
|
|
38168
38981
|
nativeDb.exec("ROLLBACK");
|
|
38169
38982
|
} catch {
|
|
38170
38983
|
}
|
|
38171
|
-
|
|
38984
|
+
log8.error({ err, scope }, "exodus-on-open: rollback transaction failed");
|
|
38172
38985
|
} finally {
|
|
38173
38986
|
try {
|
|
38174
38987
|
nativeDb.exec("PRAGMA foreign_keys = ON");
|
|
@@ -38188,7 +39001,7 @@ async function rollbackBothScopes(scope, projectDbPath, globalDbPath) {
|
|
|
38188
39001
|
rollbackConsolidatedToEmpty(native, s);
|
|
38189
39002
|
}
|
|
38190
39003
|
} catch (err) {
|
|
38191
|
-
|
|
39004
|
+
log8.warn(
|
|
38192
39005
|
{ err, scope: s, openingScope: scope },
|
|
38193
39006
|
"exodus-on-open: could not roll back scope (best-effort)"
|
|
38194
39007
|
);
|
|
@@ -38204,7 +39017,7 @@ function isDataContinuityOk(result) {
|
|
|
38204
39017
|
const deficits = result.tables.filter((t) => t.targetCount < t.sourceCount);
|
|
38205
39018
|
const surpluses = result.tables.filter((t) => t.targetCount > t.sourceCount);
|
|
38206
39019
|
if (surpluses.length > 0) {
|
|
38207
|
-
|
|
39020
|
+
log8.warn(
|
|
38208
39021
|
{
|
|
38209
39022
|
surpluses: surpluses.map((t) => ({
|
|
38210
39023
|
table: t.targetTable,
|
|
@@ -38238,7 +39051,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38238
39051
|
const { buildExodusPlan: buildExodusPlan2, runExodusMigrate: runExodusMigrate2, verifyMigration: verifyMigration2, clearExodusJournal: clearExodusJournal2 } = await Promise.resolve().then(() => (init_exodus(), exodus_exports));
|
|
38239
39052
|
const plan = buildExodusPlan2(cwd);
|
|
38240
39053
|
const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
|
|
38241
|
-
if (!scopeSources.some((s) =>
|
|
39054
|
+
if (!scopeSources.some((s) => existsSync14(s.path))) {
|
|
38242
39055
|
return {
|
|
38243
39056
|
outcome: "skipped",
|
|
38244
39057
|
reason: `no legacy ${scope}-scope source DBs present (fresh install or cross-scope-only)`
|
|
@@ -38251,11 +39064,11 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38251
39064
|
if (!consolidatedIsEmpty(nativeDb, scope)) {
|
|
38252
39065
|
return { outcome: "skipped", reason: "migrated by a concurrent process (lock winner)" };
|
|
38253
39066
|
}
|
|
38254
|
-
|
|
39067
|
+
log8.info(
|
|
38255
39068
|
{
|
|
38256
39069
|
scope,
|
|
38257
39070
|
dbPath,
|
|
38258
|
-
sources: plan.sources.filter((s) =>
|
|
39071
|
+
sources: plan.sources.filter((s) => existsSync14(s.path)).map((s) => s.name)
|
|
38259
39072
|
},
|
|
38260
39073
|
"exodus-on-open: consolidated cleo.db is empty and legacy data present \u2014 auto-migrating"
|
|
38261
39074
|
);
|
|
@@ -38264,23 +39077,23 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38264
39077
|
const migrateResult = await runExodusMigrate2(
|
|
38265
39078
|
plan,
|
|
38266
39079
|
false,
|
|
38267
|
-
(msg) =>
|
|
39080
|
+
(msg) => log8.debug({ scope }, `exodus-on-open: ${msg}`)
|
|
38268
39081
|
);
|
|
38269
39082
|
if (!migrateResult.ok) {
|
|
38270
39083
|
await rollbackBothScopes(scope, plan.projectDbPath, plan.globalDbPath);
|
|
38271
39084
|
clearExodusJournal2(migrateResult.stagingDir);
|
|
38272
39085
|
const reason = `migration failed: ${migrateResult.error ?? "unknown error"} \u2014 legacy DBs kept as source`;
|
|
38273
|
-
|
|
39086
|
+
log8.error({ scope, error: migrateResult.error }, `exodus-on-open: ${reason}`);
|
|
38274
39087
|
return { outcome: "aborted", reason };
|
|
38275
39088
|
}
|
|
38276
39089
|
const verifyResult = verifyMigration2(
|
|
38277
39090
|
plan.sources,
|
|
38278
39091
|
plan.projectDbPath,
|
|
38279
39092
|
plan.globalDbPath,
|
|
38280
|
-
(msg) =>
|
|
39093
|
+
(msg) => log8.debug({ scope }, `exodus-on-open verify: ${msg}`)
|
|
38281
39094
|
);
|
|
38282
39095
|
if (!verifyResult.ok) {
|
|
38283
|
-
|
|
39096
|
+
log8.warn(
|
|
38284
39097
|
{
|
|
38285
39098
|
scope,
|
|
38286
39099
|
enumDrift: verifyResult.enumDrift.length,
|
|
@@ -38294,7 +39107,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38294
39107
|
clearExodusJournal2(plan.stagingDir);
|
|
38295
39108
|
const deficits = verifyResult.tables.filter((t) => t.targetCount < t.sourceCount).map((t) => `${t.targetTable}(${t.sourceCount}\u2192${t.targetCount})`);
|
|
38296
39109
|
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();
|
|
38297
|
-
|
|
39110
|
+
log8.error(
|
|
38298
39111
|
{
|
|
38299
39112
|
scope,
|
|
38300
39113
|
countDeficits: deficits,
|
|
@@ -38306,14 +39119,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38306
39119
|
return { outcome: "aborted", reason };
|
|
38307
39120
|
}
|
|
38308
39121
|
const rowsCopied = migrateResult.tables.filter((t) => !t.skipped).reduce((n, t) => n + t.rowsCopied, 0);
|
|
38309
|
-
|
|
39122
|
+
log8.info(
|
|
38310
39123
|
{ scope, rowsCopied, tables: migrateResult.tables.length },
|
|
38311
39124
|
"exodus-on-open: parity verified \u2014 legacy data migrated into consolidated cleo.db"
|
|
38312
39125
|
);
|
|
38313
39126
|
try {
|
|
38314
|
-
const consumed = plan.sources.filter((s) =>
|
|
39127
|
+
const consumed = plan.sources.filter((s) => existsSync14(s.path));
|
|
38315
39128
|
const archiveResult = archiveMigratedSources(consumed, cwd);
|
|
38316
|
-
|
|
39129
|
+
log8.info(
|
|
38317
39130
|
{
|
|
38318
39131
|
scope,
|
|
38319
39132
|
archived: archiveResult.sources.filter((s) => s.action === "archived").length,
|
|
@@ -38322,7 +39135,7 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38322
39135
|
"exodus-on-open: archived legacy sources + sealed completion marker(s)"
|
|
38323
39136
|
);
|
|
38324
39137
|
} catch (err) {
|
|
38325
|
-
|
|
39138
|
+
log8.error(
|
|
38326
39139
|
{ err, scope },
|
|
38327
39140
|
"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`)"
|
|
38328
39141
|
);
|
|
@@ -38344,14 +39157,14 @@ async function maybeRunExodusOnOpen(scope, dbPath, nativeDb, cwd) {
|
|
|
38344
39157
|
function _isExodusInProgress() {
|
|
38345
39158
|
return _exodusInProgress;
|
|
38346
39159
|
}
|
|
38347
|
-
var
|
|
39160
|
+
var log8, _exodusInProgress;
|
|
38348
39161
|
var init_on_open = __esm({
|
|
38349
39162
|
"packages/core/src/store/exodus/on-open.ts"() {
|
|
38350
39163
|
"use strict";
|
|
38351
39164
|
init_logger2();
|
|
38352
39165
|
init_lock();
|
|
38353
39166
|
init_archive2();
|
|
38354
|
-
|
|
39167
|
+
log8 = getLogger("exodus-on-open");
|
|
38355
39168
|
_exodusInProgress = false;
|
|
38356
39169
|
}
|
|
38357
39170
|
});
|
|
@@ -38368,9 +39181,9 @@ __export(dual_scope_db_exports, {
|
|
|
38368
39181
|
resolveDualScopeDbPath: () => resolveDualScopeDbPath,
|
|
38369
39182
|
upsertIdempotent: () => upsertIdempotent
|
|
38370
39183
|
});
|
|
38371
|
-
import { existsSync as
|
|
39184
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4 } from "node:fs";
|
|
38372
39185
|
import { createRequire as createRequire4 } from "node:module";
|
|
38373
|
-
import { dirname as dirname6, join as
|
|
39186
|
+
import { dirname as dirname6, join as join15 } from "node:path";
|
|
38374
39187
|
function assertWriteDurable(handle) {
|
|
38375
39188
|
if (handle.exodusAbort) {
|
|
38376
39189
|
throw new ExodusAbortWriteUnsafeError(handle.exodusAbort);
|
|
@@ -38387,9 +39200,9 @@ function cacheKey(scope, dbPath) {
|
|
|
38387
39200
|
}
|
|
38388
39201
|
function resolveDualScopeDbPath(scope, cwd) {
|
|
38389
39202
|
if (scope === "project") {
|
|
38390
|
-
return
|
|
39203
|
+
return join15(resolveCleoDir(cwd), "cleo.db");
|
|
38391
39204
|
}
|
|
38392
|
-
return
|
|
39205
|
+
return join15(getCleoHome(), "cleo.db");
|
|
38393
39206
|
}
|
|
38394
39207
|
function migrationsSetName(scope) {
|
|
38395
39208
|
return scope === "project" ? "drizzle-cleo-project" : "drizzle-cleo-global";
|
|
@@ -38432,11 +39245,11 @@ async function openDualScopeDb(scope, cwd) {
|
|
|
38432
39245
|
const dbPath = resolveDualScopeDbPath(scope, cwd);
|
|
38433
39246
|
return scope === "project" ? openDualScopeDbAtPath("project", dbPath, cwd) : openDualScopeDbAtPath("global", dbPath, cwd);
|
|
38434
39247
|
}
|
|
38435
|
-
async function openDedicatedDualScopeDb(scope, dbPath,
|
|
38436
|
-
|
|
39248
|
+
async function openDedicatedDualScopeDb(scope, dbPath, log12) {
|
|
39249
|
+
log12.debug({ scope, dbPath }, "opening DEDICATED (non-cached) dual-scope cleo.db (T11782 FIX D)");
|
|
38437
39250
|
const dir = dirname6(dbPath);
|
|
38438
|
-
if (!
|
|
38439
|
-
|
|
39251
|
+
if (!existsSync15(dir)) {
|
|
39252
|
+
mkdirSync4(dir, { recursive: true });
|
|
38440
39253
|
}
|
|
38441
39254
|
const DatabaseSyncCtor = getDatabaseSyncCtor();
|
|
38442
39255
|
const nativeDb = new DatabaseSyncCtor(dbPath, { allowExtension: true });
|
|
@@ -38459,7 +39272,7 @@ async function openDedicatedDualScopeDb(scope, dbPath, log11) {
|
|
|
38459
39272
|
existenceTable(scope),
|
|
38460
39273
|
`dual-scope-db[${scope}]`
|
|
38461
39274
|
);
|
|
38462
|
-
|
|
39275
|
+
log12.debug({ scope, dbPath }, "DEDICATED dual-scope cleo.db ready (T11782 FIX D)");
|
|
38463
39276
|
return {
|
|
38464
39277
|
db,
|
|
38465
39278
|
scope,
|
|
@@ -38484,15 +39297,15 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
|
|
|
38484
39297
|
return existing.handle;
|
|
38485
39298
|
}
|
|
38486
39299
|
}
|
|
38487
|
-
const
|
|
39300
|
+
const log12 = getLogger("dual-scope-db");
|
|
38488
39301
|
if (dedicated) {
|
|
38489
|
-
return openDedicatedDualScopeDb(scope, dbPath,
|
|
39302
|
+
return openDedicatedDualScopeDb(scope, dbPath, log12);
|
|
38490
39303
|
}
|
|
38491
39304
|
const initPromise = (async () => {
|
|
38492
|
-
|
|
39305
|
+
log12.debug({ scope, dbPath }, "opening dual-scope cleo.db");
|
|
38493
39306
|
const dir = dirname6(dbPath);
|
|
38494
|
-
if (!
|
|
38495
|
-
|
|
39307
|
+
if (!existsSync15(dir)) {
|
|
39308
|
+
mkdirSync4(dir, { recursive: true });
|
|
38496
39309
|
}
|
|
38497
39310
|
const DatabaseSyncCtor = getDatabaseSyncCtor();
|
|
38498
39311
|
const nativeDb = new DatabaseSyncCtor(dbPath, { allowExtension: true });
|
|
@@ -38519,7 +39332,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
|
|
|
38519
39332
|
existenceTable(scope),
|
|
38520
39333
|
`dual-scope-db[${scope}]`
|
|
38521
39334
|
);
|
|
38522
|
-
|
|
39335
|
+
log12.debug({ scope, dbPath }, "dual-scope cleo.db ready");
|
|
38523
39336
|
const built = {
|
|
38524
39337
|
db,
|
|
38525
39338
|
scope,
|
|
@@ -38545,6 +39358,25 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
|
|
|
38545
39358
|
{ dbPath }
|
|
38546
39359
|
);
|
|
38547
39360
|
if (exodusCwd !== void 0 && dbPath === resolveDualScopeDbPath(scope, exodusCwd)) {
|
|
39361
|
+
let releaseDbHeavy = null;
|
|
39362
|
+
let dbHeavyDeferred = false;
|
|
39363
|
+
try {
|
|
39364
|
+
const { governor: governor2 } = await Promise.resolve().then(() => (init_governor(), governor_exports));
|
|
39365
|
+
const admit = await governor2.tryAcquire("db-heavy");
|
|
39366
|
+
if (admit.deferred) {
|
|
39367
|
+
dbHeavyDeferred = true;
|
|
39368
|
+
} else {
|
|
39369
|
+
releaseDbHeavy = admit.release;
|
|
39370
|
+
}
|
|
39371
|
+
} catch {
|
|
39372
|
+
}
|
|
39373
|
+
if (dbHeavyDeferred) {
|
|
39374
|
+
log12.debug(
|
|
39375
|
+
{ scope, dbPath },
|
|
39376
|
+
"exodus-on-open skipped this open \u2014 db-heavy deferred under memory pressure (re-runs idempotently on a calmer open)"
|
|
39377
|
+
);
|
|
39378
|
+
return handle;
|
|
39379
|
+
}
|
|
38548
39380
|
try {
|
|
38549
39381
|
const { maybeRunExodusOnOpen: maybeRunExodusOnOpen2 } = await Promise.resolve().then(() => (init_on_open(), on_open_exports));
|
|
38550
39382
|
const result = await maybeRunExodusOnOpen2(scope, dbPath, nativeDb, exodusCwd);
|
|
@@ -38557,7 +39389,7 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
|
|
|
38557
39389
|
reason: result.reason,
|
|
38558
39390
|
at: Date.now()
|
|
38559
39391
|
};
|
|
38560
|
-
|
|
39392
|
+
log12.warn(
|
|
38561
39393
|
{ scope, reason: result.reason },
|
|
38562
39394
|
"exodus-on-open aborted; consolidated cleo.db left empty, legacy kept as source \u2014 mutating callers must check handle.exodusAbort / call assertWriteDurable (T11828)"
|
|
38563
39395
|
);
|
|
@@ -38570,11 +39402,14 @@ async function openDualScopeDbAtPath(scope, dbPath, exodusCwd, options) {
|
|
|
38570
39402
|
return reopened;
|
|
38571
39403
|
}
|
|
38572
39404
|
} catch (err) {
|
|
38573
|
-
|
|
39405
|
+
log12.warn(
|
|
38574
39406
|
{ err, scope },
|
|
38575
39407
|
"exodus-on-open hook failed (non-fatal); re-opening consolidated handle"
|
|
38576
39408
|
);
|
|
38577
39409
|
return scope === "project" ? openDualScopeDbAtPath("project", dbPath) : openDualScopeDbAtPath("global", dbPath);
|
|
39410
|
+
} finally {
|
|
39411
|
+
if (releaseDbHeavy) await releaseDbHeavy().catch(() => {
|
|
39412
|
+
});
|
|
38578
39413
|
}
|
|
38579
39414
|
}
|
|
38580
39415
|
return handle;
|
|
@@ -39197,8 +40032,8 @@ __export(nexus_sqlite_exports, {
|
|
|
39197
40032
|
resetNexusDbState: () => resetNexusDbState,
|
|
39198
40033
|
resolveNexusMigrationsFolder: () => resolveNexusMigrationsFolder
|
|
39199
40034
|
});
|
|
39200
|
-
import { copyFileSync as copyFileSync4, existsSync as
|
|
39201
|
-
import { join as
|
|
40035
|
+
import { copyFileSync as copyFileSync4, existsSync as existsSync16 } from "node:fs";
|
|
40036
|
+
import { join as join16 } from "node:path";
|
|
39202
40037
|
function getNexusDbPath(cwd) {
|
|
39203
40038
|
return resolveDualScopeDbPath("project", cwd);
|
|
39204
40039
|
}
|
|
@@ -39230,7 +40065,7 @@ function resolveNexusMigrationsFolder() {
|
|
|
39230
40065
|
return resolveCorePackageMigrationsFolder("drizzle-nexus");
|
|
39231
40066
|
}
|
|
39232
40067
|
function getNestedNexusSentinelPath() {
|
|
39233
|
-
return
|
|
40068
|
+
return join16(getCleoHome(), "nexus", NESTED_NEXUS_SENTINEL);
|
|
39234
40069
|
}
|
|
39235
40070
|
function detectAndWarnOnNestedNexus() {
|
|
39236
40071
|
let nestedPath;
|
|
@@ -39239,12 +40074,12 @@ function detectAndWarnOnNestedNexus() {
|
|
|
39239
40074
|
} catch {
|
|
39240
40075
|
return false;
|
|
39241
40076
|
}
|
|
39242
|
-
if (!
|
|
40077
|
+
if (!existsSync16(nestedPath)) return false;
|
|
39243
40078
|
if (_warnedNestedPaths.has(nestedPath)) return false;
|
|
39244
40079
|
_warnedNestedPaths.add(nestedPath);
|
|
39245
40080
|
const canonicalPath = getNexusDbPath();
|
|
39246
|
-
const
|
|
39247
|
-
|
|
40081
|
+
const log12 = getLogger("nexus-sqlite");
|
|
40082
|
+
log12.warn(
|
|
39248
40083
|
{
|
|
39249
40084
|
nestedPath,
|
|
39250
40085
|
canonicalPath,
|
|
@@ -39398,7 +40233,7 @@ function runNexusMigrations(nativeDb, db) {
|
|
|
39398
40233
|
const migrationsFolder = resolveNexusMigrationsFolder();
|
|
39399
40234
|
if (tableExists4(nativeDb, "nexus_nodes") && _nexusDbPath) {
|
|
39400
40235
|
const backupPath = _nexusDbPath.replace(/\.db$/, "-pre-cleo.db.bak");
|
|
39401
|
-
if (!
|
|
40236
|
+
if (!existsSync16(backupPath)) {
|
|
39402
40237
|
try {
|
|
39403
40238
|
copyFileSync4(_nexusDbPath, backupPath);
|
|
39404
40239
|
} catch {
|
|
@@ -39536,10 +40371,10 @@ var init_nexus_sqlite = __esm({
|
|
|
39536
40371
|
|
|
39537
40372
|
// packages/core/src/paths.ts
|
|
39538
40373
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
39539
|
-
import { existsSync as
|
|
40374
|
+
import { existsSync as existsSync17, readFileSync as readFileSync5, statSync as statSync6 } from "node:fs";
|
|
39540
40375
|
import { createRequire as createRequire5 } from "node:module";
|
|
39541
40376
|
import { homedir } from "node:os";
|
|
39542
|
-
import { basename as basename4, dirname as dirname7, join as
|
|
40377
|
+
import { basename as basename4, dirname as dirname7, join as join17, resolve as resolve3 } from "node:path";
|
|
39543
40378
|
import {
|
|
39544
40379
|
computeCanonicalProjectId as _computeCanonicalProjectId,
|
|
39545
40380
|
getCanonicalTemplatesTildePath as _getCanonicalTemplatesTildePath,
|
|
@@ -39629,8 +40464,8 @@ function getCleoDirAbsolute(cwd, opts) {
|
|
|
39629
40464
|
function _resolveProjectByCwdFromNexus(cwd) {
|
|
39630
40465
|
try {
|
|
39631
40466
|
const cleoHome = getCleoHome();
|
|
39632
|
-
const globalDbPath =
|
|
39633
|
-
if (!
|
|
40467
|
+
const globalDbPath = join17(cleoHome, "cleo.db");
|
|
40468
|
+
if (!existsSync17(globalDbPath)) return null;
|
|
39634
40469
|
const start = resolve3(cwd ?? process.cwd());
|
|
39635
40470
|
let current = start;
|
|
39636
40471
|
const DatabaseSync3 = _getDatabaseSyncCtor();
|
|
@@ -39669,7 +40504,7 @@ function _findCleoDirRoot(cwd) {
|
|
|
39669
40504
|
const isDangerousRoot = current === homeRoot || current === "/" || current === "";
|
|
39670
40505
|
if (!isDangerousRoot) {
|
|
39671
40506
|
try {
|
|
39672
|
-
if (statSync6(
|
|
40507
|
+
if (statSync6(join17(current, ".cleo")).isDirectory()) {
|
|
39673
40508
|
return current;
|
|
39674
40509
|
}
|
|
39675
40510
|
} catch {
|
|
@@ -39684,7 +40519,7 @@ function _findCleoDirRoot(cwd) {
|
|
|
39684
40519
|
function resolveCleoDir(cwd) {
|
|
39685
40520
|
const scope = worktreeScope.getStore();
|
|
39686
40521
|
if (scope !== void 0) {
|
|
39687
|
-
return
|
|
40522
|
+
return join17(scope.worktreeRoot, ".cleo");
|
|
39688
40523
|
}
|
|
39689
40524
|
const override = _cleoDirEnvOverride();
|
|
39690
40525
|
if (override !== null) {
|
|
@@ -39692,14 +40527,14 @@ function resolveCleoDir(cwd) {
|
|
|
39692
40527
|
}
|
|
39693
40528
|
const root = _findCleoDirRoot(cwd);
|
|
39694
40529
|
if (root !== null) {
|
|
39695
|
-
return
|
|
40530
|
+
return join17(root, ".cleo");
|
|
39696
40531
|
}
|
|
39697
40532
|
const start = resolve3(cwd ?? process.cwd());
|
|
39698
40533
|
let current = start;
|
|
39699
40534
|
while (true) {
|
|
39700
40535
|
const mainRepo = _resolveMainRepoFromGitlink(current);
|
|
39701
40536
|
if (mainRepo !== null) {
|
|
39702
|
-
return
|
|
40537
|
+
return join17(mainRepo, ".cleo");
|
|
39703
40538
|
}
|
|
39704
40539
|
const parent = dirname7(current);
|
|
39705
40540
|
if (parent === current) {
|
|
@@ -39741,9 +40576,9 @@ function _cwdHasGitAncestor(cwd) {
|
|
|
39741
40576
|
const start = resolve3(cwd ?? process.cwd());
|
|
39742
40577
|
let current = start;
|
|
39743
40578
|
while (true) {
|
|
39744
|
-
const gitMarker =
|
|
40579
|
+
const gitMarker = join17(current, ".git");
|
|
39745
40580
|
try {
|
|
39746
|
-
if (
|
|
40581
|
+
if (existsSync17(gitMarker)) {
|
|
39747
40582
|
return true;
|
|
39748
40583
|
}
|
|
39749
40584
|
} catch {
|
|
@@ -39755,16 +40590,16 @@ function _cwdHasGitAncestor(cwd) {
|
|
|
39755
40590
|
}
|
|
39756
40591
|
function _resolveMainRepoFromGitlink(gitlinkDir) {
|
|
39757
40592
|
try {
|
|
39758
|
-
const gitLinkPath =
|
|
39759
|
-
if (!
|
|
39760
|
-
const
|
|
39761
|
-
if (!
|
|
40593
|
+
const gitLinkPath = join17(gitlinkDir, ".git");
|
|
40594
|
+
if (!existsSync17(gitLinkPath)) return null;
|
|
40595
|
+
const stat3 = statSync6(gitLinkPath);
|
|
40596
|
+
if (!stat3.isFile()) return null;
|
|
39762
40597
|
const gitLinkContent = readFileSync5(gitLinkPath, "utf-8").trim();
|
|
39763
40598
|
const match = gitLinkContent.match(/^gitdir:\s*(.+)$/m);
|
|
39764
40599
|
if (!match) return null;
|
|
39765
40600
|
const gitdir = match[1].trim();
|
|
39766
40601
|
const mainRepo = dirname7(dirname7(dirname7(gitdir)));
|
|
39767
|
-
if (
|
|
40602
|
+
if (existsSync17(join17(mainRepo, ".cleo")) && validateProjectRoot(mainRepo)) {
|
|
39768
40603
|
return mainRepo;
|
|
39769
40604
|
}
|
|
39770
40605
|
} catch {
|
|
@@ -39772,18 +40607,18 @@ function _resolveMainRepoFromGitlink(gitlinkDir) {
|
|
|
39772
40607
|
return null;
|
|
39773
40608
|
}
|
|
39774
40609
|
function validateProjectRoot(candidate) {
|
|
39775
|
-
const cleoDir =
|
|
39776
|
-
if (!
|
|
40610
|
+
const cleoDir = join17(candidate, ".cleo");
|
|
40611
|
+
if (!existsSync17(cleoDir)) {
|
|
39777
40612
|
return false;
|
|
39778
40613
|
}
|
|
39779
|
-
const projectInfoPath =
|
|
39780
|
-
if (
|
|
40614
|
+
const projectInfoPath = join17(cleoDir, "project-info.json");
|
|
40615
|
+
if (existsSync17(projectInfoPath)) {
|
|
39781
40616
|
try {
|
|
39782
40617
|
const raw = readFileSync5(projectInfoPath, "utf-8");
|
|
39783
40618
|
const parsed = JSON.parse(raw);
|
|
39784
40619
|
if (typeof parsed === "object" && parsed !== null && "projectId" in parsed && typeof parsed["projectId"] === "string" && parsed["projectId"] !== "") {
|
|
39785
|
-
const gitMarker =
|
|
39786
|
-
if (
|
|
40620
|
+
const gitMarker = join17(candidate, ".git");
|
|
40621
|
+
if (existsSync17(gitMarker)) {
|
|
39787
40622
|
try {
|
|
39788
40623
|
if (!statSync6(gitMarker).isDirectory()) {
|
|
39789
40624
|
return false;
|
|
@@ -39797,12 +40632,12 @@ function validateProjectRoot(candidate) {
|
|
|
39797
40632
|
} catch {
|
|
39798
40633
|
}
|
|
39799
40634
|
}
|
|
39800
|
-
const gitDir =
|
|
39801
|
-
if (
|
|
40635
|
+
const gitDir = join17(candidate, ".git");
|
|
40636
|
+
if (existsSync17(gitDir)) {
|
|
39802
40637
|
let isRealGitDir = false;
|
|
39803
40638
|
try {
|
|
39804
|
-
const
|
|
39805
|
-
isRealGitDir =
|
|
40639
|
+
const stat3 = statSync6(gitDir);
|
|
40640
|
+
isRealGitDir = stat3.isDirectory();
|
|
39806
40641
|
} catch {
|
|
39807
40642
|
isRealGitDir = false;
|
|
39808
40643
|
}
|
|
@@ -39845,16 +40680,16 @@ function getProjectRoot(cwd) {
|
|
|
39845
40680
|
const homeRoot = homedir();
|
|
39846
40681
|
const skippedCleoDirs = [];
|
|
39847
40682
|
while (true) {
|
|
39848
|
-
const cleoDir =
|
|
39849
|
-
const gitDir =
|
|
40683
|
+
const cleoDir = join17(current, ".cleo");
|
|
40684
|
+
const gitDir = join17(current, ".git");
|
|
39850
40685
|
const isDangerousRoot = current === homeRoot || current === "/" || current === "";
|
|
39851
|
-
if (
|
|
40686
|
+
if (existsSync17(cleoDir) && !isDangerousRoot) {
|
|
39852
40687
|
if (validateProjectRoot(current)) {
|
|
39853
40688
|
return current;
|
|
39854
40689
|
}
|
|
39855
40690
|
skippedCleoDirs.push(current);
|
|
39856
40691
|
}
|
|
39857
|
-
if (
|
|
40692
|
+
if (existsSync17(gitDir) && !isDangerousRoot) {
|
|
39858
40693
|
let isRealGitDir = false;
|
|
39859
40694
|
try {
|
|
39860
40695
|
isRealGitDir = statSync6(gitDir).isDirectory();
|
|
@@ -39899,18 +40734,18 @@ function resolveOrCwd(maybeRoot) {
|
|
|
39899
40734
|
return getProjectRoot();
|
|
39900
40735
|
}
|
|
39901
40736
|
function getConfigPath(cwd) {
|
|
39902
|
-
return
|
|
40737
|
+
return join17(_resolveCleoDir(cwd), "config.json");
|
|
39903
40738
|
}
|
|
39904
40739
|
function getGlobalConfigPath() {
|
|
39905
|
-
return
|
|
40740
|
+
return join17(getCleoHome(), "config.json");
|
|
39906
40741
|
}
|
|
39907
40742
|
function isAbsolutePath(path2) {
|
|
39908
40743
|
return _isAbsolutePath(path2);
|
|
39909
40744
|
}
|
|
39910
40745
|
function _readProjectNameFromInfo(projectRoot) {
|
|
39911
40746
|
try {
|
|
39912
|
-
const infoPath =
|
|
39913
|
-
if (!
|
|
40747
|
+
const infoPath = join17(projectRoot, ".cleo", "project-info.json");
|
|
40748
|
+
if (!existsSync17(infoPath)) return void 0;
|
|
39914
40749
|
const raw = readFileSync5(infoPath, "utf-8");
|
|
39915
40750
|
const data = JSON.parse(raw);
|
|
39916
40751
|
return typeof data.name === "string" && data.name.length > 0 ? data.name : void 0;
|
|
@@ -39936,8 +40771,8 @@ async function registerProjectOnEncounter(projectRoot, infoProjectId) {
|
|
|
39936
40771
|
if (existingRows.length > 0) {
|
|
39937
40772
|
const existingPath = existingRows[0].projectPath;
|
|
39938
40773
|
if (existingPath !== resolvedPath) {
|
|
39939
|
-
const newBrainDbPath =
|
|
39940
|
-
const newTasksDbPath =
|
|
40774
|
+
const newBrainDbPath = join17(resolvedPath, ".cleo", "brain.db");
|
|
40775
|
+
const newTasksDbPath = join17(resolvedPath, ".cleo", "tasks.db");
|
|
39941
40776
|
await db.update(projectRegistry2).set({
|
|
39942
40777
|
projectPath: resolvedPath,
|
|
39943
40778
|
projectHash: generateProjectHash2(resolvedPath),
|
|
@@ -39968,8 +40803,8 @@ async function registerProjectOnEncounter(projectRoot, infoProjectId) {
|
|
|
39968
40803
|
lastSync: now,
|
|
39969
40804
|
taskCount: 0,
|
|
39970
40805
|
labelsJson: "[]",
|
|
39971
|
-
brainDbPath:
|
|
39972
|
-
tasksDbPath:
|
|
40806
|
+
brainDbPath: join17(resolvedPath, ".cleo", "brain.db"),
|
|
40807
|
+
tasksDbPath: join17(resolvedPath, ".cleo", "tasks.db"),
|
|
39973
40808
|
statsJson: "{}"
|
|
39974
40809
|
}).onConflictDoNothing();
|
|
39975
40810
|
const aliases = canonicalResult.legacyAliases ?? [];
|
|
@@ -40186,7 +41021,7 @@ __export(config_exports, {
|
|
|
40186
41021
|
parseConfigValue: () => parseConfigValue,
|
|
40187
41022
|
setConfigValue: () => setConfigValue
|
|
40188
41023
|
});
|
|
40189
|
-
import { existsSync as
|
|
41024
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
40190
41025
|
function getNestedValue(obj, path2) {
|
|
40191
41026
|
const parts = path2.split(".");
|
|
40192
41027
|
let current = obj;
|
|
@@ -40234,7 +41069,7 @@ function parseEnvValue(value) {
|
|
|
40234
41069
|
return value;
|
|
40235
41070
|
}
|
|
40236
41071
|
async function loadConfig(cwd) {
|
|
40237
|
-
let merged = JSON.parse(JSON.stringify(
|
|
41072
|
+
let merged = JSON.parse(JSON.stringify(DEFAULTS2));
|
|
40238
41073
|
const globalConfig = await readJson(getGlobalConfigPath());
|
|
40239
41074
|
if (globalConfig) {
|
|
40240
41075
|
merged = deepMerge(merged, globalConfig);
|
|
@@ -40280,7 +41115,7 @@ async function getConfigValue(path2, cwd) {
|
|
|
40280
41115
|
return { value: val, source: "global" };
|
|
40281
41116
|
}
|
|
40282
41117
|
}
|
|
40283
|
-
const defaultVal = getNestedValue({ ...
|
|
41118
|
+
const defaultVal = getNestedValue({ ...DEFAULTS2 }, path2);
|
|
40284
41119
|
return { value: defaultVal, source: "default" };
|
|
40285
41120
|
}
|
|
40286
41121
|
async function getRawConfigValue(key, cwd) {
|
|
@@ -40308,7 +41143,7 @@ function parseConfigValue(value) {
|
|
|
40308
41143
|
}
|
|
40309
41144
|
async function setConfigValue(key, value, cwd, opts) {
|
|
40310
41145
|
const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
|
|
40311
|
-
if (!
|
|
41146
|
+
if (!existsSync18(configPath)) {
|
|
40312
41147
|
await atomicWriteJson(configPath, {});
|
|
40313
41148
|
}
|
|
40314
41149
|
const config = await readJson(configPath) ?? {};
|
|
@@ -40320,7 +41155,7 @@ async function setConfigValue(key, value, cwd, opts) {
|
|
|
40320
41155
|
async function applyStrictnessPreset(preset, cwd, opts) {
|
|
40321
41156
|
const definition = STRICTNESS_PRESETS[preset];
|
|
40322
41157
|
const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
|
|
40323
|
-
if (!
|
|
41158
|
+
if (!existsSync18(configPath)) {
|
|
40324
41159
|
await atomicWriteJson(configPath, {});
|
|
40325
41160
|
}
|
|
40326
41161
|
const config = await readJson(configPath) ?? {};
|
|
@@ -40344,7 +41179,7 @@ function listStrictnessPresets() {
|
|
|
40344
41179
|
})
|
|
40345
41180
|
);
|
|
40346
41181
|
}
|
|
40347
|
-
var
|
|
41182
|
+
var DEFAULTS2, ENV_MAP, STRICTNESS_PRESETS;
|
|
40348
41183
|
var init_config = __esm({
|
|
40349
41184
|
"packages/core/src/config.ts"() {
|
|
40350
41185
|
"use strict";
|
|
@@ -40352,7 +41187,7 @@ var init_config = __esm({
|
|
|
40352
41187
|
init_paths();
|
|
40353
41188
|
init_atomic();
|
|
40354
41189
|
init_json2();
|
|
40355
|
-
|
|
41190
|
+
DEFAULTS2 = {
|
|
40356
41191
|
version: "2.10.0",
|
|
40357
41192
|
output: {
|
|
40358
41193
|
defaultFormat: "json",
|
|
@@ -40671,7 +41506,7 @@ function brainTablesAreConsolidatedShape(nativeDb) {
|
|
|
40671
41506
|
return createdAt !== void 0 && createdAt.type.toUpperCase() !== "INTEGER";
|
|
40672
41507
|
}
|
|
40673
41508
|
function establishLegacyBrainSchema(nativeDb, db) {
|
|
40674
|
-
const
|
|
41509
|
+
const log12 = getLogger("brain-schema");
|
|
40675
41510
|
if (brainTablesAreConsolidatedShape(nativeDb)) {
|
|
40676
41511
|
const fkRow = nativeDb.prepare("PRAGMA foreign_keys").get();
|
|
40677
41512
|
const fkWasOn = fkRow?.foreign_keys === 1;
|
|
@@ -40681,7 +41516,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
|
|
|
40681
41516
|
try {
|
|
40682
41517
|
nativeDb.exec(`DROP TABLE IF EXISTS \`${table}\``);
|
|
40683
41518
|
} catch (err) {
|
|
40684
|
-
|
|
41519
|
+
log12.warn(
|
|
40685
41520
|
{ table, err },
|
|
40686
41521
|
"Failed to drop consolidated brain table during legacy rebuild."
|
|
40687
41522
|
);
|
|
@@ -40690,7 +41525,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
|
|
|
40690
41525
|
} finally {
|
|
40691
41526
|
nativeDb.exec(`PRAGMA foreign_keys=${fkWasOn ? "ON" : "OFF"}`);
|
|
40692
41527
|
}
|
|
40693
|
-
|
|
41528
|
+
log12.debug(
|
|
40694
41529
|
{ count: CONSOLIDATED_BRAIN_TABLES.length },
|
|
40695
41530
|
"Dropped consolidated (exodus-target) brain tables \u2014 rebuilding in legacy runtime shape."
|
|
40696
41531
|
);
|
|
@@ -40701,7 +41536,7 @@ function establishLegacyBrainSchema(nativeDb, db) {
|
|
|
40701
41536
|
nativeDb,
|
|
40702
41537
|
migrationsFolder
|
|
40703
41538
|
);
|
|
40704
|
-
|
|
41539
|
+
log12.debug(
|
|
40705
41540
|
{ marked, applied },
|
|
40706
41541
|
"brain consolidated cleo.db reconcile (T11647) \u2014 marked already-present migrations applied + executed the missing unprefixed-table migrations directly."
|
|
40707
41542
|
);
|
|
@@ -41199,11 +42034,11 @@ __export(tasks_schema_exports, {
|
|
|
41199
42034
|
isValidStatus: () => isValidStatus,
|
|
41200
42035
|
jsonb: () => jsonb2,
|
|
41201
42036
|
jsonbText: () => jsonbText,
|
|
41202
|
-
lifecycleEvidence: () =>
|
|
41203
|
-
lifecycleGateResults: () =>
|
|
41204
|
-
lifecyclePipelines: () =>
|
|
41205
|
-
lifecycleStages: () =>
|
|
41206
|
-
lifecycleTransitions: () =>
|
|
42037
|
+
lifecycleEvidence: () => tasksLifecycleEvidence,
|
|
42038
|
+
lifecycleGateResults: () => tasksLifecycleGateResults,
|
|
42039
|
+
lifecyclePipelines: () => tasksLifecyclePipelines,
|
|
42040
|
+
lifecycleStages: () => tasksLifecycleStages,
|
|
42041
|
+
lifecycleTransitions: () => tasksLifecycleTransitions,
|
|
41207
42042
|
makeSchemaMetaTable: () => makeSchemaMetaTable,
|
|
41208
42043
|
manifestEntries: () => manifestEntries,
|
|
41209
42044
|
pipelineManifest: () => pipelineManifest,
|
|
@@ -41237,6 +42072,7 @@ var init_tasks_schema = __esm({
|
|
|
41237
42072
|
"use strict";
|
|
41238
42073
|
init_agent_schema();
|
|
41239
42074
|
init_chain_schema();
|
|
42075
|
+
init_lifecycle3();
|
|
41240
42076
|
init_provenance_commits();
|
|
41241
42077
|
init_provenance_orphans();
|
|
41242
42078
|
init_provenance_rest();
|
|
@@ -41256,7 +42092,7 @@ async function upsertTask(db, row, archiveFields, allowOrphanParent = false) {
|
|
|
41256
42092
|
if (allowOrphanParent) {
|
|
41257
42093
|
row = { ...row, parentId: null };
|
|
41258
42094
|
} else {
|
|
41259
|
-
|
|
42095
|
+
log9.warn(
|
|
41260
42096
|
{ taskId: row.id, parentId: row.parentId },
|
|
41261
42097
|
"upsertTask: parentId references a non-existent task \u2014 parent relationship may be lost"
|
|
41262
42098
|
);
|
|
@@ -41441,13 +42277,13 @@ async function loadRelationsForTasks(db, tasks2) {
|
|
|
41441
42277
|
task.relates = relations && relations.length > 0 ? relations : [];
|
|
41442
42278
|
}
|
|
41443
42279
|
}
|
|
41444
|
-
var
|
|
42280
|
+
var log9;
|
|
41445
42281
|
var init_db_helpers = __esm({
|
|
41446
42282
|
"packages/core/src/store/db-helpers.ts"() {
|
|
41447
42283
|
"use strict";
|
|
41448
42284
|
init_logger2();
|
|
41449
42285
|
init_tasks_schema();
|
|
41450
|
-
|
|
42286
|
+
log9 = getLogger("db-helpers");
|
|
41451
42287
|
}
|
|
41452
42288
|
});
|
|
41453
42289
|
|
|
@@ -41548,10 +42384,10 @@ var init_telemetry_schema = __esm({
|
|
|
41548
42384
|
});
|
|
41549
42385
|
|
|
41550
42386
|
// packages/core/src/telemetry/sqlite.ts
|
|
41551
|
-
import { mkdirSync as
|
|
41552
|
-
import { dirname as dirname8, join as
|
|
42387
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
42388
|
+
import { dirname as dirname8, join as join18 } from "node:path";
|
|
41553
42389
|
function getTelemetryDbPath() {
|
|
41554
|
-
return
|
|
42390
|
+
return join18(getCleoHome(), DB_FILENAME);
|
|
41555
42391
|
}
|
|
41556
42392
|
function resolveTelemetryMigrationsFolder() {
|
|
41557
42393
|
return resolveCorePackageMigrationsFolder("drizzle-telemetry");
|
|
@@ -41599,7 +42435,7 @@ async function getTelemetryDb() {
|
|
|
41599
42435
|
_initPromise2 = (async () => {
|
|
41600
42436
|
const dbPath = requestedPath;
|
|
41601
42437
|
_dbPath2 = dbPath;
|
|
41602
|
-
|
|
42438
|
+
mkdirSync5(dirname8(dbPath), { recursive: true });
|
|
41603
42439
|
const nativeDb = openNativeDatabase(dbPath);
|
|
41604
42440
|
_nativeDb2 = nativeDb;
|
|
41605
42441
|
const db = drizzle({ client: nativeDb, schema: telemetry_schema_exports });
|
|
@@ -41632,11 +42468,11 @@ var init_sqlite2 = __esm({
|
|
|
41632
42468
|
});
|
|
41633
42469
|
|
|
41634
42470
|
// packages/core/src/store/agent-registry-store.ts
|
|
41635
|
-
import { existsSync as
|
|
41636
|
-
import { join as
|
|
42471
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
42472
|
+
import { join as join19 } from "node:path";
|
|
41637
42473
|
function getGlobalAgentRegistryDbPath() {
|
|
41638
42474
|
const cleoHome = getCleoHome();
|
|
41639
|
-
const dbPath =
|
|
42475
|
+
const dbPath = join19(cleoHome, "cleo.db");
|
|
41640
42476
|
if (!dbPath.startsWith(cleoHome)) {
|
|
41641
42477
|
throw new Error(
|
|
41642
42478
|
`BUG: getGlobalAgentRegistryDbPath() resolved to "${dbPath}" which is NOT under getCleoHome() ("${cleoHome}"). The Agent Registry is global-only per ADR-037. This indicates a code path that bypasses path resolution \u2014 fix the caller, do not suppress this error.`
|
|
@@ -41683,8 +42519,8 @@ function writeAgentRegistrySchemaVersionSentinel(db) {
|
|
|
41683
42519
|
}
|
|
41684
42520
|
async function ensureGlobalAgentRegistryDb() {
|
|
41685
42521
|
const dbPath = getGlobalAgentRegistryDbPath();
|
|
41686
|
-
const alreadyExists =
|
|
41687
|
-
const dualHandle = await
|
|
42522
|
+
const alreadyExists = existsSync19(dbPath);
|
|
42523
|
+
const dualHandle = await openDualScopeDbAtPath("global", dbPath);
|
|
41688
42524
|
const nativeDb = dualHandle.db.$client ?? null;
|
|
41689
42525
|
if (!nativeDb) {
|
|
41690
42526
|
throw new Error(
|
|
@@ -41732,7 +42568,7 @@ var init_agent_registry_store = __esm({
|
|
|
41732
42568
|
});
|
|
41733
42569
|
|
|
41734
42570
|
// packages/core/src/store/conduit-sqlite.ts
|
|
41735
|
-
import { existsSync as
|
|
42571
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
41736
42572
|
import { createRequire as createRequire7 } from "node:module";
|
|
41737
42573
|
function _getDrizzle2() {
|
|
41738
42574
|
if (_drizzle3 === null) {
|
|
@@ -41769,7 +42605,7 @@ async function ensureConduitDb(projectRoot) {
|
|
|
41769
42605
|
}
|
|
41770
42606
|
if (_initPromise3) return _initPromise3;
|
|
41771
42607
|
_initPromise3 = (async () => {
|
|
41772
|
-
const alreadyExists =
|
|
42608
|
+
const alreadyExists = existsSync20(dbPath);
|
|
41773
42609
|
const dualHandle = await openDualScopeDb("project", projectRoot);
|
|
41774
42610
|
const nativeDb = dualHandle.db.$client ?? null;
|
|
41775
42611
|
if (!nativeDb) {
|
|
@@ -41989,11 +42825,11 @@ var init_skills_schema = __esm({
|
|
|
41989
42825
|
});
|
|
41990
42826
|
|
|
41991
42827
|
// packages/core/src/store/skills-db.ts
|
|
41992
|
-
import { join as
|
|
42828
|
+
import { join as join20 } from "node:path";
|
|
41993
42829
|
import { and as and3, eq as eq4 } from "drizzle-orm";
|
|
41994
42830
|
function getDefaultSkillsDbPath() {
|
|
41995
42831
|
const cleoHome = getCleoHome();
|
|
41996
|
-
const dbPath =
|
|
42832
|
+
const dbPath = join20(cleoHome, "cleo.db");
|
|
41997
42833
|
if (!dbPath.startsWith(cleoHome)) {
|
|
41998
42834
|
throw new Error(
|
|
41999
42835
|
`BUG: getDefaultSkillsDbPath() resolved to "${dbPath}" which is NOT under getCleoHome() ("${cleoHome}"). The skills registry is global-only per SG-CLEO-SKILLS-architecture-v3.md \xA74. Fix the caller, do not suppress.`
|
|
@@ -42063,13 +42899,13 @@ var init_skills_db = __esm({
|
|
|
42063
42899
|
import {
|
|
42064
42900
|
chmodSync,
|
|
42065
42901
|
copyFileSync as copyFileSync5,
|
|
42066
|
-
existsSync as
|
|
42067
|
-
mkdirSync as
|
|
42068
|
-
readdirSync as
|
|
42902
|
+
existsSync as existsSync21,
|
|
42903
|
+
mkdirSync as mkdirSync6,
|
|
42904
|
+
readdirSync as readdirSync5,
|
|
42069
42905
|
statSync as statSync7,
|
|
42070
42906
|
unlinkSync as unlinkSync3
|
|
42071
42907
|
} from "node:fs";
|
|
42072
|
-
import { join as
|
|
42908
|
+
import { join as join21 } from "node:path";
|
|
42073
42909
|
function resolveInventoryPath(entry, cwd) {
|
|
42074
42910
|
try {
|
|
42075
42911
|
if (entry.tier === "global") {
|
|
@@ -42104,8 +42940,8 @@ async function openAgentRegistryDbForSnapshot() {
|
|
|
42104
42940
|
}
|
|
42105
42941
|
async function openTelemetryDbForSnapshot() {
|
|
42106
42942
|
try {
|
|
42107
|
-
const path2 =
|
|
42108
|
-
if (!
|
|
42943
|
+
const path2 = join21(getCleoHome(), "telemetry.db");
|
|
42944
|
+
if (!existsSync21(path2)) return null;
|
|
42109
42945
|
} catch {
|
|
42110
42946
|
return null;
|
|
42111
42947
|
}
|
|
@@ -42120,7 +42956,7 @@ function buildRawFileVacuumOpener(entry) {
|
|
|
42120
42956
|
return async (cwd) => {
|
|
42121
42957
|
const path2 = resolveInventoryPath(entry, cwd);
|
|
42122
42958
|
if (!path2) return null;
|
|
42123
|
-
if (!
|
|
42959
|
+
if (!existsSync21(path2)) return null;
|
|
42124
42960
|
try {
|
|
42125
42961
|
const { DatabaseSync: DatabaseSync3 } = await import("node:sqlite");
|
|
42126
42962
|
return new DatabaseSync3(path2, { readOnly: true });
|
|
@@ -42208,10 +43044,10 @@ function snapshotPattern(prefix) {
|
|
|
42208
43044
|
function rotateSnapshots(backupDir, prefix) {
|
|
42209
43045
|
try {
|
|
42210
43046
|
const pattern = snapshotPattern(prefix);
|
|
42211
|
-
const files =
|
|
43047
|
+
const files = readdirSync5(backupDir).filter((f) => pattern.test(f)).map((f) => ({
|
|
42212
43048
|
name: f,
|
|
42213
|
-
path:
|
|
42214
|
-
mtimeMs: statSync7(
|
|
43049
|
+
path: join21(backupDir, f),
|
|
43050
|
+
mtimeMs: statSync7(join21(backupDir, f)).mtimeMs
|
|
42215
43051
|
})).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
42216
43052
|
while (files.length >= MAX_SNAPSHOTS) {
|
|
42217
43053
|
const oldest = files.shift();
|
|
@@ -42236,7 +43072,7 @@ async function snapshotOne(target, backupDir, now, cwd) {
|
|
|
42236
43072
|
if (!db) return;
|
|
42237
43073
|
opened = db;
|
|
42238
43074
|
}
|
|
42239
|
-
const dest =
|
|
43075
|
+
const dest = join21(backupDir, `${target.prefix}-${formatTimestamp(now)}.db`);
|
|
42240
43076
|
try {
|
|
42241
43077
|
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
42242
43078
|
rotateSnapshots(backupDir, target.prefix);
|
|
@@ -42257,8 +43093,8 @@ async function vacuumIntoBackup(opts = {}) {
|
|
|
42257
43093
|
}
|
|
42258
43094
|
try {
|
|
42259
43095
|
const cleoDir = getCleoDir(opts.cwd);
|
|
42260
|
-
const backupDir =
|
|
42261
|
-
|
|
43096
|
+
const backupDir = join21(cleoDir, "backups", "sqlite");
|
|
43097
|
+
mkdirSync6(backupDir, { recursive: true });
|
|
42262
43098
|
const target = SNAPSHOT_TARGETS.find((t) => t.prefix === prefix);
|
|
42263
43099
|
if (!target) return;
|
|
42264
43100
|
await snapshotOne(target, backupDir, /* @__PURE__ */ new Date(), opts.cwd);
|
|
@@ -42269,13 +43105,13 @@ async function vacuumIntoBackup(opts = {}) {
|
|
|
42269
43105
|
function listSqliteBackupsForPrefix(prefix, cwd) {
|
|
42270
43106
|
try {
|
|
42271
43107
|
const cleoDir = getCleoDir(cwd);
|
|
42272
|
-
const backupDir =
|
|
42273
|
-
if (!
|
|
43108
|
+
const backupDir = join21(cleoDir, "backups", "sqlite");
|
|
43109
|
+
if (!existsSync21(backupDir)) return [];
|
|
42274
43110
|
const pattern = snapshotPattern(prefix);
|
|
42275
|
-
return
|
|
43111
|
+
return readdirSync5(backupDir).filter((f) => pattern.test(f)).map((f) => ({
|
|
42276
43112
|
name: f,
|
|
42277
|
-
path:
|
|
42278
|
-
mtimeMs: statSync7(
|
|
43113
|
+
path: join21(backupDir, f),
|
|
43114
|
+
mtimeMs: statSync7(join21(backupDir, f)).mtimeMs
|
|
42279
43115
|
})).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
42280
43116
|
} catch {
|
|
42281
43117
|
return [];
|
|
@@ -42331,7 +43167,7 @@ __export(sqlite_native_exports, {
|
|
|
42331
43167
|
import { realpathSync } from "node:fs";
|
|
42332
43168
|
import { createRequire as createRequire8 } from "node:module";
|
|
42333
43169
|
import { tmpdir } from "node:os";
|
|
42334
|
-
import { delimiter, dirname as dirname9, isAbsolute, join as
|
|
43170
|
+
import { delimiter, dirname as dirname9, isAbsolute, join as join22, resolve as resolve4, sep } from "node:path";
|
|
42335
43171
|
function getDbSyncConstructor() {
|
|
42336
43172
|
if (_ctor === null) {
|
|
42337
43173
|
const mod = _require5("node:sqlite");
|
|
@@ -42352,7 +43188,7 @@ function resolveAbsoluteSafe(p) {
|
|
|
42352
43188
|
missingParts.unshift(cursor.slice(parent.length + (parent.endsWith(sep) ? 0 : 1)));
|
|
42353
43189
|
cursor = parent;
|
|
42354
43190
|
try {
|
|
42355
|
-
return
|
|
43191
|
+
return join22(realpathSync(cursor), ...missingParts);
|
|
42356
43192
|
} catch {
|
|
42357
43193
|
}
|
|
42358
43194
|
}
|
|
@@ -42458,7 +43294,7 @@ __export(sqlite_exports, {
|
|
|
42458
43294
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
42459
43295
|
schema: () => tasks_schema_exports
|
|
42460
43296
|
});
|
|
42461
|
-
import { copyFileSync as copyFileSync6, existsSync as
|
|
43297
|
+
import { copyFileSync as copyFileSync6, existsSync as existsSync22, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "node:fs";
|
|
42462
43298
|
import { createRequire as createRequire9 } from "node:module";
|
|
42463
43299
|
import { eq as eq5 } from "drizzle-orm";
|
|
42464
43300
|
function _getDrizzle3() {
|
|
@@ -42500,7 +43336,7 @@ function countBackupTasks(backupDb) {
|
|
|
42500
43336
|
}
|
|
42501
43337
|
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
42502
43338
|
const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
|
|
42503
|
-
const
|
|
43339
|
+
const log12 = getLogger("sqlite");
|
|
42504
43340
|
try {
|
|
42505
43341
|
const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks_tasks").get();
|
|
42506
43342
|
const taskCount = countResult?.cnt ?? 0;
|
|
@@ -42526,13 +43362,13 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
42526
43362
|
async () => {
|
|
42527
43363
|
const currentTaskCount = await recountTasksFromDisk(dbPath);
|
|
42528
43364
|
if (currentTaskCount > 0) {
|
|
42529
|
-
|
|
43365
|
+
log12.info(
|
|
42530
43366
|
{ dbPath, currentTaskCount },
|
|
42531
43367
|
"Auto-recovery skipped: database was populated by a concurrent process while acquiring the first-open lock (T11662 double-checked re-query)."
|
|
42532
43368
|
);
|
|
42533
43369
|
return;
|
|
42534
43370
|
}
|
|
42535
|
-
|
|
43371
|
+
log12.warn(
|
|
42536
43372
|
{ dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
|
|
42537
43373
|
`Empty database detected with ${backupTaskCount}-task backup available. Auto-recovering from backup. This likely happened because git-tracked WAL/SHM files were overwritten during a branch switch (T5188).`
|
|
42538
43374
|
);
|
|
@@ -42552,7 +43388,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
42552
43388
|
const tempPath = dbPath + ".recovery-tmp";
|
|
42553
43389
|
copyFileSync6(newestBackup.path, tempPath);
|
|
42554
43390
|
renameSync3(tempPath, dbPath);
|
|
42555
|
-
|
|
43391
|
+
log12.info(
|
|
42556
43392
|
{ dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
|
|
42557
43393
|
"Database auto-recovered from backup successfully."
|
|
42558
43394
|
);
|
|
@@ -42569,7 +43405,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
42569
43405
|
{ stale: 6e5, retries: 30 }
|
|
42570
43406
|
);
|
|
42571
43407
|
} catch (err) {
|
|
42572
|
-
|
|
43408
|
+
log12.error({ err, dbPath }, "Auto-recovery from backup failed. Continuing with empty database.");
|
|
42573
43409
|
}
|
|
42574
43410
|
}
|
|
42575
43411
|
async function getDb(cwd) {
|
|
@@ -42578,6 +43414,9 @@ async function getDb(cwd) {
|
|
|
42578
43414
|
if (_db3 && _dbPath3 !== requestedPath) {
|
|
42579
43415
|
resetDbState();
|
|
42580
43416
|
}
|
|
43417
|
+
if (_db3 && (_nativeDb3 === null || !_nativeDb3.isOpen)) {
|
|
43418
|
+
resetDbState();
|
|
43419
|
+
}
|
|
42581
43420
|
if (_db3) return _db3;
|
|
42582
43421
|
if (_initPromise4) return _initPromise4;
|
|
42583
43422
|
_initPromise4 = (async () => {
|
|
@@ -42675,7 +43514,7 @@ async function getSchemaVersion(cwd) {
|
|
|
42675
43514
|
return result[0]?.value ?? null;
|
|
42676
43515
|
}
|
|
42677
43516
|
function dbExists(cwd) {
|
|
42678
|
-
return
|
|
43517
|
+
return existsSync22(resolveDualScopeDbPath("project", cwd));
|
|
42679
43518
|
}
|
|
42680
43519
|
function getNativeDb() {
|
|
42681
43520
|
return _nativeDb3;
|
|
@@ -43905,13 +44744,13 @@ var init_sqlite_data_accessor = __esm({
|
|
|
43905
44744
|
});
|
|
43906
44745
|
|
|
43907
44746
|
// packages/core/src/sequence/index.ts
|
|
43908
|
-
import { existsSync as
|
|
43909
|
-
import { join as
|
|
44747
|
+
import { existsSync as existsSync23, readFileSync as readFileSync6, renameSync as renameSync4 } from "node:fs";
|
|
44748
|
+
import { join as join23 } from "node:path";
|
|
43910
44749
|
function getLegacySequenceJsonPath(cwd) {
|
|
43911
|
-
return
|
|
44750
|
+
return join23(resolveOrCwd(cwd), ".cleo", ".sequence.json");
|
|
43912
44751
|
}
|
|
43913
44752
|
function getLegacySequencePath(cwd) {
|
|
43914
|
-
return
|
|
44753
|
+
return join23(resolveOrCwd(cwd), ".cleo", ".sequence");
|
|
43915
44754
|
}
|
|
43916
44755
|
function isValidSequenceState(value) {
|
|
43917
44756
|
if (!value || typeof value !== "object") return false;
|
|
@@ -43922,7 +44761,7 @@ function isSeedSequence(value) {
|
|
|
43922
44761
|
return value.counter === 0 && value.lastId === "T000" && value.checksum === "seed";
|
|
43923
44762
|
}
|
|
43924
44763
|
function readLegacySequenceFile(path2) {
|
|
43925
|
-
if (!
|
|
44764
|
+
if (!existsSync23(path2)) return null;
|
|
43926
44765
|
try {
|
|
43927
44766
|
const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
|
|
43928
44767
|
return isValidSequenceState(parsed) ? parsed : null;
|
|
@@ -43931,10 +44770,10 @@ function readLegacySequenceFile(path2) {
|
|
|
43931
44770
|
}
|
|
43932
44771
|
}
|
|
43933
44772
|
function renameLegacyFile(path2) {
|
|
43934
|
-
if (!
|
|
44773
|
+
if (!existsSync23(path2)) return;
|
|
43935
44774
|
const migratedPath = `${path2}.migrated`;
|
|
43936
44775
|
try {
|
|
43937
|
-
if (!
|
|
44776
|
+
if (!existsSync23(migratedPath)) {
|
|
43938
44777
|
renameSync4(path2, migratedPath);
|
|
43939
44778
|
return;
|
|
43940
44779
|
}
|
|
@@ -44097,15 +44936,15 @@ var init_sequence = __esm({
|
|
|
44097
44936
|
|
|
44098
44937
|
// packages/core/src/store/git-checkpoint.ts
|
|
44099
44938
|
import { execFile as execFile2 } from "node:child_process";
|
|
44100
|
-
import { existsSync as
|
|
44101
|
-
import { readFile as
|
|
44102
|
-
import { join as
|
|
44939
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
44940
|
+
import { readFile as readFile4, writeFile } from "node:fs/promises";
|
|
44941
|
+
import { join as join24, resolve as resolve5 } from "node:path";
|
|
44103
44942
|
import { promisify as promisify2 } from "node:util";
|
|
44104
44943
|
function makeCleoGitEnv(cleoDir) {
|
|
44105
44944
|
const abs = resolve5(cleoDir);
|
|
44106
44945
|
return {
|
|
44107
44946
|
...process.env,
|
|
44108
|
-
GIT_DIR:
|
|
44947
|
+
GIT_DIR: join24(abs, ".git"),
|
|
44109
44948
|
GIT_WORK_TREE: abs
|
|
44110
44949
|
};
|
|
44111
44950
|
}
|
|
@@ -44123,7 +44962,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
44123
44962
|
}
|
|
44124
44963
|
}
|
|
44125
44964
|
function isCleoGitInitialized(cleoDir) {
|
|
44126
|
-
return
|
|
44965
|
+
return existsSync24(join24(cleoDir, ".git", "HEAD"));
|
|
44127
44966
|
}
|
|
44128
44967
|
async function loadStateFileAllowlist(cwd) {
|
|
44129
44968
|
try {
|
|
@@ -44167,26 +45006,26 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
44167
45006
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
44168
45007
|
}
|
|
44169
45008
|
function isMergeInProgress(cleoDir) {
|
|
44170
|
-
return
|
|
45009
|
+
return existsSync24(join24(cleoDir, ".git", "MERGE_HEAD"));
|
|
44171
45010
|
}
|
|
44172
45011
|
async function isDetachedHead(cleoDir) {
|
|
44173
45012
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
44174
45013
|
return !result.success;
|
|
44175
45014
|
}
|
|
44176
45015
|
function isRebaseInProgress(cleoDir) {
|
|
44177
|
-
return
|
|
45016
|
+
return existsSync24(join24(cleoDir, ".git", "rebase-merge")) || existsSync24(join24(cleoDir, ".git", "rebase-apply"));
|
|
44178
45017
|
}
|
|
44179
45018
|
async function recordCheckpointTime(cleoDir) {
|
|
44180
45019
|
try {
|
|
44181
|
-
const stateFile =
|
|
45020
|
+
const stateFile = join24(cleoDir, CHECKPOINT_STATE_FILE);
|
|
44182
45021
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
44183
45022
|
} catch {
|
|
44184
45023
|
}
|
|
44185
45024
|
}
|
|
44186
45025
|
async function getLastCheckpointTime(cleoDir) {
|
|
44187
45026
|
try {
|
|
44188
|
-
const stateFile =
|
|
44189
|
-
const content = await
|
|
45027
|
+
const stateFile = join24(cleoDir, CHECKPOINT_STATE_FILE);
|
|
45028
|
+
const content = await readFile4(stateFile, "utf-8");
|
|
44190
45029
|
const epoch = parseInt(content.trim(), 10);
|
|
44191
45030
|
return Number.isNaN(epoch) ? 0 : epoch;
|
|
44192
45031
|
} catch {
|
|
@@ -44197,8 +45036,8 @@ async function getChangedStateFiles(cleoDir, cwd) {
|
|
|
44197
45036
|
const changed = [];
|
|
44198
45037
|
const allStateFiles = await getAllStateFiles(cwd);
|
|
44199
45038
|
for (const stateFile of allStateFiles) {
|
|
44200
|
-
const fullPath =
|
|
44201
|
-
if (!
|
|
45039
|
+
const fullPath = join24(cleoDir, stateFile);
|
|
45040
|
+
if (!existsSync24(fullPath)) continue;
|
|
44202
45041
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
44203
45042
|
const cachedResult = await cleoGitCommand(
|
|
44204
45043
|
["diff", "--cached", "--quiet", "--", stateFile],
|
|
@@ -44225,7 +45064,7 @@ async function shouldCheckpoint(options) {
|
|
|
44225
45064
|
const config = await loadCheckpointConfig(cwd);
|
|
44226
45065
|
if (!config.enabled) return false;
|
|
44227
45066
|
const cleoDir = getCleoDir(cwd);
|
|
44228
|
-
if (!
|
|
45067
|
+
if (!existsSync24(cleoDir)) return false;
|
|
44229
45068
|
if (!isCleoGitInitialized(cleoDir)) return false;
|
|
44230
45069
|
if (!await isCleoGitRepo(cleoDir)) return false;
|
|
44231
45070
|
if (isMergeInProgress(cleoDir)) return false;
|
|
@@ -44303,7 +45142,7 @@ async function ensureSequenceValid(cwd, options) {
|
|
|
44303
45142
|
if (!options?.validateSequence) return;
|
|
44304
45143
|
const check = await checkSequence(cwd);
|
|
44305
45144
|
if (!check.valid) {
|
|
44306
|
-
|
|
45145
|
+
log10.warn({ counter: check.counter, maxId: check.maxIdInData }, "Sequence behind, repairing");
|
|
44307
45146
|
const repair = await repairSequence(cwd);
|
|
44308
45147
|
if (!repair.repaired && options.strict) {
|
|
44309
45148
|
throw new DataSafetyError(`Sequence repair failed: ${repair.message}`, "SEQUENCE_INVALID", {
|
|
@@ -44320,7 +45159,7 @@ async function checkpoint(context, cwd, options) {
|
|
|
44320
45159
|
stats.checkpoints++;
|
|
44321
45160
|
stats.lastCheckpoint = /* @__PURE__ */ new Date();
|
|
44322
45161
|
} catch (err) {
|
|
44323
|
-
|
|
45162
|
+
log10.warn({ err }, "Checkpoint failed (non-fatal)");
|
|
44324
45163
|
}
|
|
44325
45164
|
vacuumIntoBackup({ cwd }).catch(() => {
|
|
44326
45165
|
});
|
|
@@ -44382,7 +45221,7 @@ async function safeAppendLog(accessor, entry, cwd, options) {
|
|
|
44382
45221
|
stats.writes++;
|
|
44383
45222
|
await checkpoint("log entry", cwd, opts);
|
|
44384
45223
|
}
|
|
44385
|
-
var
|
|
45224
|
+
var log10, DataSafetyError, DEFAULT_SAFETY, stats;
|
|
44386
45225
|
var init_data_safety_central = __esm({
|
|
44387
45226
|
"packages/core/src/store/data-safety-central.ts"() {
|
|
44388
45227
|
"use strict";
|
|
@@ -44392,7 +45231,7 @@ var init_data_safety_central = __esm({
|
|
|
44392
45231
|
init_sqlite3();
|
|
44393
45232
|
init_sqlite_backup();
|
|
44394
45233
|
init_tasks_schema();
|
|
44395
|
-
|
|
45234
|
+
log10 = getLogger("data-safety");
|
|
44396
45235
|
DataSafetyError = class extends Error {
|
|
44397
45236
|
constructor(message, code, context) {
|
|
44398
45237
|
super(message);
|
|
@@ -44432,7 +45271,7 @@ function isSafetyDisabled() {
|
|
|
44432
45271
|
}
|
|
44433
45272
|
function wrapWithSafety(accessor, cwd) {
|
|
44434
45273
|
if (isSafetyDisabled()) {
|
|
44435
|
-
|
|
45274
|
+
log11.warn(
|
|
44436
45275
|
"Safety disabled - emergency mode (CLEO_DISABLE_SAFETY=true). Data integrity checks bypassed."
|
|
44437
45276
|
);
|
|
44438
45277
|
return accessor;
|
|
@@ -44453,13 +45292,13 @@ function getSafetyStatus() {
|
|
|
44453
45292
|
enabled: true
|
|
44454
45293
|
};
|
|
44455
45294
|
}
|
|
44456
|
-
var
|
|
45295
|
+
var log11, SafetyDataAccessor;
|
|
44457
45296
|
var init_safety_data_accessor = __esm({
|
|
44458
45297
|
"packages/core/src/store/safety-data-accessor.ts"() {
|
|
44459
45298
|
"use strict";
|
|
44460
45299
|
init_logger2();
|
|
44461
45300
|
init_data_safety_central();
|
|
44462
|
-
|
|
45301
|
+
log11 = getLogger("data-safety");
|
|
44463
45302
|
SafetyDataAccessor = class {
|
|
44464
45303
|
/** The underlying accessor being wrapped. */
|
|
44465
45304
|
inner;
|
|
@@ -44483,7 +45322,7 @@ var init_safety_data_accessor = __esm({
|
|
|
44483
45322
|
...config
|
|
44484
45323
|
};
|
|
44485
45324
|
if (this.config.verbose) {
|
|
44486
|
-
|
|
45325
|
+
log11.debug({ engine: inner.engine }, "SafetyDataAccessor initialized");
|
|
44487
45326
|
}
|
|
44488
45327
|
}
|
|
44489
45328
|
/** The storage engine backing this accessor. */
|
|
@@ -44495,7 +45334,7 @@ var init_safety_data_accessor = __esm({
|
|
|
44495
45334
|
*/
|
|
44496
45335
|
logVerbose(message) {
|
|
44497
45336
|
if (this.config.verbose) {
|
|
44498
|
-
|
|
45337
|
+
log11.debug(message);
|
|
44499
45338
|
}
|
|
44500
45339
|
}
|
|
44501
45340
|
/**
|