@cleocode/core 2026.5.132 → 2026.5.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/output.d.ts +2 -0
  6. package/dist/output.d.ts.map +1 -1
  7. package/dist/output.js +59 -1
  8. package/dist/output.js.map +1 -1
  9. package/dist/sentient/daemon-entry.d.ts +6 -0
  10. package/dist/sentient/daemon-entry.d.ts.map +1 -1
  11. package/dist/sentient/daemon-entry.js +10 -1
  12. package/dist/sentient/daemon-entry.js.map +1 -1
  13. package/dist/sentient/daemon.d.ts +16 -0
  14. package/dist/sentient/daemon.d.ts.map +1 -1
  15. package/dist/sentient/daemon.js +11 -1
  16. package/dist/sentient/daemon.js.map +1 -1
  17. package/dist/sentient/index.d.ts +1 -0
  18. package/dist/sentient/index.d.ts.map +1 -1
  19. package/dist/sentient/index.js +4 -0
  20. package/dist/sentient/index.js.map +1 -1
  21. package/dist/sentient/propose-tick.d.ts +101 -0
  22. package/dist/sentient/propose-tick.d.ts.map +1 -1
  23. package/dist/sentient/propose-tick.js +284 -0
  24. package/dist/sentient/propose-tick.js.map +1 -1
  25. package/dist/sentient/tick.d.ts +23 -0
  26. package/dist/sentient/tick.d.ts.map +1 -1
  27. package/dist/sentient/tick.js +63 -8
  28. package/dist/sentient/tick.js.map +1 -1
  29. package/dist/store/exodus/index.d.ts +16 -0
  30. package/dist/store/exodus/index.d.ts.map +1 -0
  31. package/dist/store/exodus/index.js +16 -0
  32. package/dist/store/exodus/index.js.map +1 -0
  33. package/dist/store/exodus/migrate.d.ts +41 -0
  34. package/dist/store/exodus/migrate.d.ts.map +1 -0
  35. package/dist/store/exodus/migrate.js +416 -0
  36. package/dist/store/exodus/migrate.js.map +1 -0
  37. package/dist/store/exodus/plan.d.ts +44 -0
  38. package/dist/store/exodus/plan.d.ts.map +1 -0
  39. package/dist/store/exodus/plan.js +178 -0
  40. package/dist/store/exodus/plan.js.map +1 -0
  41. package/dist/store/exodus/status.d.ts +22 -0
  42. package/dist/store/exodus/status.d.ts.map +1 -0
  43. package/dist/store/exodus/status.js +88 -0
  44. package/dist/store/exodus/status.js.map +1 -0
  45. package/dist/store/exodus/types.d.ts +169 -0
  46. package/dist/store/exodus/types.d.ts.map +1 -0
  47. package/dist/store/exodus/types.js +21 -0
  48. package/dist/store/exodus/types.js.map +1 -0
  49. package/dist/store/exodus/verify.d.ts +34 -0
  50. package/dist/store/exodus/verify.d.ts.map +1 -0
  51. package/dist/store/exodus/verify.js +168 -0
  52. package/dist/store/exodus/verify.js.map +1 -0
  53. package/dist/store/open-cleo-db.d.ts +51 -4
  54. package/dist/store/open-cleo-db.d.ts.map +1 -1
  55. package/dist/store/open-cleo-db.js +56 -2
  56. package/dist/store/open-cleo-db.js.map +1 -1
  57. package/dist/store/schema/cleo-global/nexus.d.ts +1 -1
  58. package/dist/store/schema/cleo-global/signaldock.d.ts +1 -1
  59. package/dist/store/schema/cleo-shared/brain.d.ts +1 -1
  60. package/package.json +12 -12
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Exodus migration engine.
3
+ *
4
+ * `runExodusMigrate()` performs the actual data migration from legacy DBs
5
+ * to the consolidated dual-scope `cleo.db`. Key invariants:
6
+ *
7
+ * - Source DBs are opened **read-only** via `openCleoDbSnapshot` (AC4).
8
+ * - Source files are backed up to the staging dir before any writes (AC5).
9
+ * - Import is wrapped in `BEGIN … COMMIT` per scope; partial failure leaves
10
+ * the target DB untouched (AC6).
11
+ * - Idempotency keys are propagated where the source row has them; generated
12
+ * where it does not (AC7).
13
+ * - The staging journal is written atomically before each table copy so a
14
+ * crash can be resumed (AC5).
15
+ *
16
+ * ## Advisory file lock (AC4)
17
+ *
18
+ * The source DB files are opened read-only via `openCleoDbSnapshot` which
19
+ * calls `new DatabaseSync(path, { readOnly: true })`. Node's SQLite binding
20
+ * opens with `SQLITE_OPEN_READONLY`, which prevents any writes from this
21
+ * process. We additionally write a `.lock` sentinel file next to each source
22
+ * DB for the duration of the migration so that other CLEO processes can detect
23
+ * an in-progress exodus and refuse to write.
24
+ *
25
+ * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
26
+ * @saga T11242
27
+ */
28
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
29
+ import { join } from 'node:path';
30
+ import { getLogger } from '../../logger.js';
31
+ import { getCleoVersion } from '../../scaffold/ensure-config.js';
32
+ import { openDualScopeDb } from '../dual-scope-db.js';
33
+ import { openCleoDbSnapshot } from '../open-cleo-db.js';
34
+ import { EXODUS_TARGET_SCHEMA_VERSION } from './types.js';
35
+ const log = getLogger('exodus-migrate');
36
+ // ---------------------------------------------------------------------------
37
+ // Advisory lock sentinel filename
38
+ // ---------------------------------------------------------------------------
39
+ const LOCK_SENTINEL_SUFFIX = '.exodus-lock';
40
+ // ---------------------------------------------------------------------------
41
+ // Journal helpers
42
+ // ---------------------------------------------------------------------------
43
+ const JOURNAL_FILENAME = 'exodus-journal.json';
44
+ /**
45
+ * Get the SQLite version string from an open DatabaseSync handle.
46
+ */
47
+ function getSqliteVersion(db) {
48
+ try {
49
+ const row = db.prepare('SELECT sqlite_version() AS v').get();
50
+ return row?.v ?? 'unknown';
51
+ }
52
+ catch {
53
+ return 'unknown';
54
+ }
55
+ }
56
+ /**
57
+ * Read the tables list from a legacy SQLite DB (excluding SQLite internals).
58
+ */
59
+ function listTables(db) {
60
+ const rows = db
61
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name")
62
+ .all();
63
+ return rows.map((r) => r.name);
64
+ }
65
+ /**
66
+ * Write the journal file atomically (write-then-rename pattern).
67
+ */
68
+ function writeJournal(stagingDir, journal) {
69
+ const journalPath = join(stagingDir, JOURNAL_FILENAME);
70
+ const tmpPath = `${journalPath}.tmp`;
71
+ writeFileSync(tmpPath, JSON.stringify(journal, null, 2) + '\n', 'utf8');
72
+ // On POSIX, rename is atomic within the same fs.
73
+ renameSync(tmpPath, journalPath);
74
+ }
75
+ /**
76
+ * Read an existing journal from the staging dir, or return `null`.
77
+ */
78
+ function readJournal(stagingDir) {
79
+ const journalPath = join(stagingDir, JOURNAL_FILENAME);
80
+ if (!existsSync(journalPath))
81
+ return null;
82
+ try {
83
+ return JSON.parse(readFileSync(journalPath, 'utf8'));
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Initialise a fresh journal object.
91
+ */
92
+ function initJournal(sqliteVersion) {
93
+ const now = new Date().toISOString();
94
+ return {
95
+ version: 1,
96
+ cleoVersion: getCleoVersion(),
97
+ targetSchemaVersion: EXODUS_TARGET_SCHEMA_VERSION,
98
+ nodeVersion: process.version,
99
+ sqliteVersion,
100
+ startedAt: now,
101
+ updatedAt: now,
102
+ tables: [],
103
+ };
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Advisory lock sentinel helpers (AC4)
107
+ // ---------------------------------------------------------------------------
108
+ function lockPath(dbPath) {
109
+ return `${dbPath}${LOCK_SENTINEL_SUFFIX}`;
110
+ }
111
+ function acquireAdvisoryLock(dbPath) {
112
+ const lp = lockPath(dbPath);
113
+ writeFileSync(lp, JSON.stringify({ pid: process.pid, ts: new Date().toISOString() }), 'utf8');
114
+ }
115
+ function releaseAdvisoryLock(dbPath) {
116
+ try {
117
+ unlinkSync(lockPath(dbPath));
118
+ }
119
+ catch {
120
+ // Ignore — lock may already be gone
121
+ }
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Core copy function
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Copy all rows from `tableName` in the source DB into the target DB using a
128
+ * raw `INSERT INTO … SELECT * FROM …` after ATTACHing the source file.
129
+ *
130
+ * This approach avoids reading all rows into JS memory — SQLite handles the
131
+ * copy entirely in the engine. The ATTACH is scoped to a single statement
132
+ * sequence and detached immediately after (or on error).
133
+ *
134
+ * Returns the number of rows copied.
135
+ *
136
+ * @param targetNativeDb - The target `DatabaseSync` handle (writable).
137
+ * @param sourceDbPath - Absolute path to the read-only source `.db` file.
138
+ * @param tableName - The table to copy.
139
+ * @param idempotencyKeyCol - Optional column name for idempotency key
140
+ * propagation. When present, rows whose key is already in the target are
141
+ * skipped (INSERT OR IGNORE).
142
+ */
143
+ function copyTableViaAttach(targetNativeDb, sourceDbPath, tableName) {
144
+ const attachAlias = '_exodus_src_';
145
+ // Check source table exists and has rows
146
+ const srcCheck = openCleoDbSnapshot(sourceDbPath, { readOnly: true });
147
+ let sourceCount = 0;
148
+ let columns = [];
149
+ try {
150
+ const countRow = srcCheck.db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
151
+ sourceCount = countRow?.c ?? 0;
152
+ // Get column names for explicit column list
153
+ const pragma = srcCheck.db.prepare(`PRAGMA table_info("${tableName}")`).all();
154
+ columns = pragma.map((r) => r.name);
155
+ }
156
+ finally {
157
+ srcCheck.close();
158
+ }
159
+ if (sourceCount === 0)
160
+ return 0;
161
+ if (columns.length === 0)
162
+ return 0;
163
+ // ATTACH source, copy via INSERT OR IGNORE, DETACH
164
+ // Use INSERT OR IGNORE so idempotent keys prevent duplicates on resume
165
+ const colList = columns.map((c) => `"${c}"`).join(', ');
166
+ targetNativeDb.exec(`ATTACH DATABASE '${sourceDbPath.replace(/'/g, "''")}' AS "${attachAlias}"`);
167
+ try {
168
+ // Check if target table exists
169
+ const existsRow = targetNativeDb
170
+ .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName.replace(/'/g, "''")}'`)
171
+ .get();
172
+ if (!existsRow) {
173
+ // Target table doesn't exist yet — log and skip (E6 will create these)
174
+ log.warn({ tableName }, 'Target table not found in consolidated DB — skipping');
175
+ return 0;
176
+ }
177
+ const stmt = targetNativeDb.prepare(`INSERT OR IGNORE INTO main."${tableName}" (${colList}) SELECT ${colList} FROM "${attachAlias}"."${tableName}" ORDER BY rowid`);
178
+ const result = stmt.run();
179
+ return result.changes ?? 0;
180
+ }
181
+ finally {
182
+ try {
183
+ targetNativeDb.exec(`DETACH DATABASE "${attachAlias}"`);
184
+ }
185
+ catch {
186
+ // Best-effort detach
187
+ }
188
+ }
189
+ }
190
+ // ---------------------------------------------------------------------------
191
+ // Main migration runner
192
+ // ---------------------------------------------------------------------------
193
+ /**
194
+ * Validate that the journal's schema version matches the current target version.
195
+ *
196
+ * When `forceCrossVersion === true`, mismatches are logged but not fatal.
197
+ */
198
+ function checkSchemaVersion(journal, forceCrossVersion) {
199
+ if (journal.targetSchemaVersion !== EXODUS_TARGET_SCHEMA_VERSION) {
200
+ const msg = `Schema version mismatch: journal=${journal.targetSchemaVersion}, expected=${EXODUS_TARGET_SCHEMA_VERSION}`;
201
+ if (forceCrossVersion) {
202
+ log.warn(msg + ' (--force-cross-version: continuing anyway)');
203
+ return true;
204
+ }
205
+ log.error(msg + ' — pass --force-cross-version to override');
206
+ return false;
207
+ }
208
+ return true;
209
+ }
210
+ /**
211
+ * Run the exodus migration.
212
+ *
213
+ * @param plan - Pre-flight plan from `buildExodusPlan()`.
214
+ * @param forceCrossVersion - Skip the schema-version guard (AC9).
215
+ * @param onProgress - Optional progress callback called after each table.
216
+ *
217
+ * @returns {@link ExodusMigrateResult}
218
+ *
219
+ * @task T11248 (AC4, AC5, AC6, AC7, AC9)
220
+ */
221
+ export async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
222
+ const { sources, stagingDir, diskPreflight } = plan;
223
+ // AC8: disk pre-flight
224
+ if (!diskPreflight) {
225
+ return {
226
+ ok: false,
227
+ tables: [],
228
+ stagingDir,
229
+ backupPaths: [],
230
+ error: `Insufficient disk space: need ≥3× source size (${plan.totalSourceBytes} bytes source, ${plan.availableBytes} bytes available). Free up space or use a different storage location.`,
231
+ };
232
+ }
233
+ // Ensure staging directory exists (AC5)
234
+ mkdirSync(stagingDir, { recursive: true });
235
+ // Determine SQLite version from the first available source DB
236
+ let sqliteVersion = 'unknown';
237
+ for (const src of sources) {
238
+ if (existsSync(src.path)) {
239
+ const snap = openCleoDbSnapshot(src.path, { readOnly: true });
240
+ sqliteVersion = getSqliteVersion(snap.db);
241
+ snap.close();
242
+ break;
243
+ }
244
+ }
245
+ // Load or initialise the journal (AC5 — resume from staging)
246
+ let journal = readJournal(stagingDir);
247
+ if (journal === null) {
248
+ journal = initJournal(sqliteVersion);
249
+ }
250
+ else {
251
+ // Existing journal — check schema version (AC9)
252
+ if (!checkSchemaVersion(journal, forceCrossVersion)) {
253
+ return {
254
+ ok: false,
255
+ tables: [],
256
+ stagingDir,
257
+ backupPaths: [],
258
+ error: 'Schema version mismatch. Pass --force-cross-version to override.',
259
+ };
260
+ }
261
+ onProgress?.('Resuming from existing staging journal…');
262
+ }
263
+ const backupPaths = [];
264
+ const allTableResults = [];
265
+ const lockedPaths = [];
266
+ try {
267
+ // 1. Back up existing source DBs into staging dir and acquire advisory locks
268
+ for (const src of sources) {
269
+ if (!existsSync(src.path))
270
+ continue;
271
+ const backupDest = join(stagingDir, `${src.name.replace(/[^a-z0-9-]/g, '_')}-backup.db`);
272
+ if (!existsSync(backupDest)) {
273
+ onProgress?.(`Backing up ${src.name} → staging dir…`);
274
+ copyFileSync(src.path, backupDest);
275
+ backupPaths.push(backupDest);
276
+ }
277
+ // AC4: advisory lock sentinel
278
+ acquireAdvisoryLock(src.path);
279
+ lockedPaths.push(src.path);
280
+ }
281
+ // 2. Open (or create) the consolidated target DBs via the chokepoint.
282
+ // This runs Drizzle migrations to create the target schema.
283
+ onProgress?.('Opening consolidated project-scope cleo.db (running migrations)…');
284
+ // openDualScopeDb takes cwd, not a db path — pass undefined to use process.cwd()
285
+ const projectHandle = await openDualScopeDb('project');
286
+ onProgress?.('Opening consolidated global-scope cleo.db (running migrations)…');
287
+ const globalHandle = await openDualScopeDb('global');
288
+ // Extract the raw DatabaseSync from the Drizzle wrapper ($client pattern).
289
+ function extractNativeDb(handle) {
290
+ const drizzleHandle = handle.db;
291
+ const client = drizzleHandle['$client'];
292
+ if (client && typeof client['prepare'] === 'function') {
293
+ return client;
294
+ }
295
+ // Fallback: the handle itself may be a DatabaseSync (unlikely but safe)
296
+ if (typeof drizzleHandle['prepare'] === 'function') {
297
+ return drizzleHandle;
298
+ }
299
+ throw new Error('Could not extract native DatabaseSync from dual-scope DB handle');
300
+ }
301
+ const projectNative = extractNativeDb(projectHandle);
302
+ const globalNative = extractNativeDb(globalHandle);
303
+ // 3. Per-scope BEGIN/COMMIT transactions (AC6)
304
+ // If any table copy fails, ROLLBACK leaves the target untouched.
305
+ // Process project-scope sources
306
+ const projectSources = sources.filter((s) => s.targetScope === 'project' && existsSync(s.path));
307
+ const globalSources = sources.filter((s) => s.targetScope === 'global' && existsSync(s.path));
308
+ await migrateScope('project', projectSources, projectNative, journal, stagingDir, allTableResults, onProgress);
309
+ await migrateScope('global', globalSources, globalNative, journal, stagingDir, allTableResults, onProgress);
310
+ // Final journal update
311
+ journal.updatedAt = new Date().toISOString();
312
+ writeJournal(stagingDir, journal);
313
+ projectHandle.close();
314
+ globalHandle.close();
315
+ return { ok: true, tables: allTableResults, stagingDir, backupPaths };
316
+ }
317
+ catch (err) {
318
+ const error = err instanceof Error ? err.message : String(err);
319
+ log.error({ err }, 'Exodus migration failed');
320
+ return { ok: false, tables: allTableResults, stagingDir, backupPaths, error };
321
+ }
322
+ finally {
323
+ // Release advisory locks
324
+ for (const p of lockedPaths) {
325
+ releaseAdvisoryLock(p);
326
+ }
327
+ }
328
+ }
329
+ /**
330
+ * Migrate all tables from the given sources into the target native DB,
331
+ * wrapped in a single BEGIN/COMMIT transaction per scope.
332
+ */
333
+ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir, allTableResults, onProgress) {
334
+ if (sources.length === 0)
335
+ return;
336
+ onProgress?.(`Migrating ${scope}-scope sources…`);
337
+ // Wrap entire scope in one transaction (AC6)
338
+ targetNativeDb.exec('BEGIN');
339
+ try {
340
+ for (const src of sources) {
341
+ const snap = openCleoDbSnapshot(src.path, { readOnly: true });
342
+ try {
343
+ const tables = listTables(snap.db);
344
+ for (const tableName of tables) {
345
+ // Check journal for resume (AC5)
346
+ const existing = journal.tables.find((e) => e.sourceDb === src.name && e.tableName === tableName);
347
+ if (existing?.status === 'done') {
348
+ onProgress?.(` ↳ ${src.name}.${tableName} — already done (resuming)`);
349
+ allTableResults.push({
350
+ sourceDb: src.name,
351
+ tableName,
352
+ rowsCopied: existing.rowsCopied,
353
+ skipped: false,
354
+ });
355
+ continue;
356
+ }
357
+ onProgress?.(` ↳ Copying ${src.name}.${tableName}…`);
358
+ let rowsCopied = 0;
359
+ let status = 'done';
360
+ let errorMsg;
361
+ let skipped = false;
362
+ try {
363
+ rowsCopied = copyTableViaAttach(targetNativeDb, src.path, tableName);
364
+ }
365
+ catch (err) {
366
+ const msg = err instanceof Error ? err.message : String(err);
367
+ log.warn({ tableName, sourceDb: src.name, err }, 'Table copy failed — skipping');
368
+ status = 'skipped';
369
+ errorMsg = msg;
370
+ skipped = true;
371
+ }
372
+ // Update journal entry
373
+ const entry = {
374
+ sourceDb: src.name,
375
+ tableName,
376
+ status,
377
+ rowsCopied,
378
+ updatedAt: new Date().toISOString(),
379
+ ...(errorMsg ? { error: errorMsg } : {}),
380
+ };
381
+ const idx = journal.tables.findIndex((e) => e.sourceDb === src.name && e.tableName === tableName);
382
+ if (idx >= 0) {
383
+ journal.tables[idx] = entry;
384
+ }
385
+ else {
386
+ journal.tables.push(entry);
387
+ }
388
+ journal.updatedAt = new Date().toISOString();
389
+ // Atomic journal write after each table (AC5 — crash-resumable)
390
+ writeJournal(stagingDir, journal);
391
+ allTableResults.push({
392
+ sourceDb: src.name,
393
+ tableName,
394
+ rowsCopied,
395
+ skipped,
396
+ reason: errorMsg,
397
+ });
398
+ }
399
+ }
400
+ finally {
401
+ snap.close();
402
+ }
403
+ }
404
+ targetNativeDb.exec('COMMIT');
405
+ }
406
+ catch (err) {
407
+ try {
408
+ targetNativeDb.exec('ROLLBACK');
409
+ }
410
+ catch {
411
+ // ignore rollback errors
412
+ }
413
+ throw err;
414
+ }
415
+ }
416
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/store/exodus/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Exodus pre-flight plan builder.
3
+ *
4
+ * `buildExodusPlan()` computes the full migration plan — source DB paths,
5
+ * combined size, free-disk availability, staging directory — BEFORE any
6
+ * writes occur. A `--dry-run` caller can print the plan and exit early.
7
+ *
8
+ * ## Disk pre-flight (AC8)
9
+ *
10
+ * `availableBytes >= 3 * totalSourceBytes` must hold before migration begins.
11
+ * The check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
12
+ *
13
+ * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
14
+ * @saga T11242
15
+ */
16
+ import type { ExodusPlan, LegacyDbDescriptor } from './types.js';
17
+ /**
18
+ * Derive the staging directory name from the current ISO-8601 timestamp.
19
+ *
20
+ * Pattern: `.cleo/exodus-staging-<YYYYMMDDTHHMMSSZ>`.
21
+ * Colons are replaced with empty string (NTFS + shell-safe).
22
+ */
23
+ export declare function deriveStagingDirName(): string;
24
+ /**
25
+ * Build the complete exodus plan.
26
+ *
27
+ * This is a pure read operation — no files are created or modified.
28
+ * Pass the result to `runExodusMigrate()` to execute the migration.
29
+ *
30
+ * @param cwd - Working directory used to resolve the project root. Defaults to
31
+ * `process.cwd()`.
32
+ * @returns {@link ExodusPlan} describing sources, disk availability, and paths.
33
+ *
34
+ * @task T11248 (AC8 — 3× disk pre-flight)
35
+ */
36
+ export declare function buildExodusPlan(cwd?: string): ExodusPlan;
37
+ /**
38
+ * Check whether all required source DBs exist.
39
+ *
40
+ * Returns `true` if at least one source file is present (partial sets are
41
+ * acceptable — empty tables simply copy zero rows).
42
+ */
43
+ export declare function sourcesPresent(sources: LegacyDbDescriptor[]): boolean;
44
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8EjE;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CA+BxD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAErE"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Exodus pre-flight plan builder.
3
+ *
4
+ * `buildExodusPlan()` computes the full migration plan — source DB paths,
5
+ * combined size, free-disk availability, staging directory — BEFORE any
6
+ * writes occur. A `--dry-run` caller can print the plan and exit early.
7
+ *
8
+ * ## Disk pre-flight (AC8)
9
+ *
10
+ * `availableBytes >= 3 * totalSourceBytes` must hold before migration begins.
11
+ * The check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
12
+ *
13
+ * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
14
+ * @saga T11242
15
+ */
16
+ import { existsSync, readdirSync, statfsSync, statSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { getCleoHome, resolveCleoDir } from '../../paths.js';
19
+ import { resolveDualScopeDbPath } from '../dual-scope-db.js';
20
+ // ---------------------------------------------------------------------------
21
+ // Legacy DB descriptors (AC2 — 6 per-machine source DBs mapped to 2 targets)
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Build the ordered list of legacy source DB descriptors for a given project.
25
+ *
26
+ * Project-scoped sources live under `<project>/.cleo/`.
27
+ * Global-scoped sources live under `getCleoHome()`.
28
+ */
29
+ function buildSourceDescriptors(cwd) {
30
+ const cleoDir = resolveCleoDir(cwd);
31
+ const cleoHome = getCleoHome();
32
+ return [
33
+ // Project-tier — go into consolidated project-scope cleo.db
34
+ {
35
+ name: 'tasks',
36
+ path: join(cleoDir, 'tasks.db'),
37
+ targetScope: 'project',
38
+ },
39
+ {
40
+ name: 'brain (project)',
41
+ path: join(cleoDir, 'brain.db'),
42
+ targetScope: 'project',
43
+ },
44
+ {
45
+ name: 'conduit',
46
+ path: join(cleoDir, 'conduit.db'),
47
+ targetScope: 'project',
48
+ },
49
+ // Global-tier — go into consolidated global-scope cleo.db
50
+ {
51
+ name: 'nexus',
52
+ path: join(cleoHome, 'nexus.db'),
53
+ targetScope: 'global',
54
+ },
55
+ {
56
+ name: 'signaldock',
57
+ path: join(cleoHome, 'signaldock.db'),
58
+ targetScope: 'global',
59
+ },
60
+ {
61
+ name: 'skills',
62
+ path: join(cleoHome, 'skills.db'),
63
+ targetScope: 'global',
64
+ },
65
+ ];
66
+ }
67
+ /**
68
+ * Safely stat a file and return its size in bytes, or 0 if it does not exist.
69
+ */
70
+ function safeFileBytes(filePath) {
71
+ try {
72
+ return statSync(filePath).size;
73
+ }
74
+ catch {
75
+ return 0;
76
+ }
77
+ }
78
+ /**
79
+ * Return available bytes on the filesystem that contains `dir`.
80
+ *
81
+ * Uses `statfsSync` (Node 18+). Falls back to 0 if unavailable.
82
+ */
83
+ function getAvailableBytes(dir) {
84
+ try {
85
+ const result = statfsSync(dir);
86
+ // `bfree` = blocks free for privileged processes; `bavail` = unprivileged.
87
+ return (result.bavail ?? result.bfree ?? 0) * (result.bsize ?? 4096);
88
+ }
89
+ catch {
90
+ return 0;
91
+ }
92
+ }
93
+ /**
94
+ * Derive the staging directory name from the current ISO-8601 timestamp.
95
+ *
96
+ * Pattern: `.cleo/exodus-staging-<YYYYMMDDTHHMMSSZ>`.
97
+ * Colons are replaced with empty string (NTFS + shell-safe).
98
+ */
99
+ export function deriveStagingDirName() {
100
+ const iso = new Date()
101
+ .toISOString()
102
+ .replace(/[:]/g, '')
103
+ .replace(/\..+Z$/, 'Z');
104
+ return `exodus-staging-${iso}`;
105
+ }
106
+ /**
107
+ * Scan for an existing staging directory from a previous (possibly crashed)
108
+ * run inside the given `.cleo/` directory.
109
+ *
110
+ * Returns the absolute path to the most-recent staging dir, or `null` if none
111
+ * exists.
112
+ */
113
+ function findExistingStaging(cleoDir) {
114
+ try {
115
+ const entries = readdirSync(cleoDir, { withFileTypes: true });
116
+ const stagingDirs = entries
117
+ .filter((e) => e.isDirectory() && e.name.startsWith('exodus-staging-'))
118
+ .map((e) => e.name)
119
+ .sort()
120
+ .reverse(); // most recent first
121
+ if (stagingDirs.length > 0) {
122
+ return join(cleoDir, stagingDirs[0]);
123
+ }
124
+ }
125
+ catch {
126
+ // .cleo/ may not exist yet
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Build the complete exodus plan.
132
+ *
133
+ * This is a pure read operation — no files are created or modified.
134
+ * Pass the result to `runExodusMigrate()` to execute the migration.
135
+ *
136
+ * @param cwd - Working directory used to resolve the project root. Defaults to
137
+ * `process.cwd()`.
138
+ * @returns {@link ExodusPlan} describing sources, disk availability, and paths.
139
+ *
140
+ * @task T11248 (AC8 — 3× disk pre-flight)
141
+ */
142
+ export function buildExodusPlan(cwd) {
143
+ const cleoDir = resolveCleoDir(cwd);
144
+ const sources = buildSourceDescriptors(cwd);
145
+ // Compute total source size (only existing files count toward the check)
146
+ const totalSourceBytes = sources.reduce((sum, s) => sum + safeFileBytes(s.path), 0);
147
+ // Disk pre-flight: check against the directory that will hold the staging data
148
+ // (the .cleo/ dir, which is where both the backup and staging live).
149
+ const availableBytes = getAvailableBytes(cleoDir);
150
+ const diskPreflight = totalSourceBytes === 0 || availableBytes >= 3 * totalSourceBytes;
151
+ // Staging directory — resume if a previous one exists
152
+ const existingStaging = findExistingStaging(cleoDir);
153
+ const stagingDir = existingStaging ?? join(cleoDir, deriveStagingDirName());
154
+ const resumeFromStaging = existingStaging !== null;
155
+ // Target paths (consolidated cleo.db)
156
+ const projectDbPath = resolveDualScopeDbPath('project', cwd);
157
+ const globalDbPath = resolveDualScopeDbPath('global');
158
+ return {
159
+ sources,
160
+ totalSourceBytes,
161
+ availableBytes,
162
+ diskPreflight,
163
+ stagingDir,
164
+ resumeFromStaging,
165
+ projectDbPath,
166
+ globalDbPath,
167
+ };
168
+ }
169
+ /**
170
+ * Check whether all required source DBs exist.
171
+ *
172
+ * Returns `true` if at least one source file is present (partial sets are
173
+ * acceptable — empty tables simply copy zero rows).
174
+ */
175
+ export function sourcesPresent(sources) {
176
+ return sources.some((s) => existsSync(s.path));
177
+ }
178
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"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"}