@cleocode/cleo 2026.3.0 → 2026.3.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/cli/index.js +196 -118
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +173 -94
- package/dist/mcp/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -758,6 +758,65 @@ var init_node_sqlite_adapter = __esm({
|
|
|
758
758
|
}
|
|
759
759
|
});
|
|
760
760
|
|
|
761
|
+
// src/store/sqlite-backup.ts
|
|
762
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
763
|
+
import { join as join3 } from "node:path";
|
|
764
|
+
function formatTimestamp(d) {
|
|
765
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
766
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
767
|
+
}
|
|
768
|
+
function rotateSnapshots(backupDir) {
|
|
769
|
+
try {
|
|
770
|
+
const files = readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join3(backupDir, f), mtimeMs: statSync(join3(backupDir, f)).mtimeMs })).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
771
|
+
while (files.length >= MAX_SNAPSHOTS) {
|
|
772
|
+
const oldest = files.shift();
|
|
773
|
+
unlinkSync(oldest.path);
|
|
774
|
+
}
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function vacuumIntoBackup(opts = {}) {
|
|
779
|
+
const now = Date.now();
|
|
780
|
+
if (!opts.force && now - _lastBackupEpoch < DEBOUNCE_MS) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
const cleoDir = getCleoDir(opts.cwd);
|
|
785
|
+
const backupDir = join3(cleoDir, "backups", "sqlite");
|
|
786
|
+
mkdirSync2(backupDir, { recursive: true });
|
|
787
|
+
const db = getNativeDb();
|
|
788
|
+
if (!db) return;
|
|
789
|
+
const dest = join3(backupDir, `tasks-${formatTimestamp(/* @__PURE__ */ new Date())}.db`);
|
|
790
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
791
|
+
rotateSnapshots(backupDir);
|
|
792
|
+
const safeDest = dest.replace(/'/g, "''");
|
|
793
|
+
db.exec(`VACUUM INTO '${safeDest}'`);
|
|
794
|
+
_lastBackupEpoch = Date.now();
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function listSqliteBackups(cwd) {
|
|
799
|
+
try {
|
|
800
|
+
const cleoDir = getCleoDir(cwd);
|
|
801
|
+
const backupDir = join3(cleoDir, "backups", "sqlite");
|
|
802
|
+
if (!existsSync2(backupDir)) return [];
|
|
803
|
+
return readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join3(backupDir, f), mtimeMs: statSync(join3(backupDir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
804
|
+
} catch {
|
|
805
|
+
return [];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
var MAX_SNAPSHOTS, DEBOUNCE_MS, _lastBackupEpoch;
|
|
809
|
+
var init_sqlite_backup = __esm({
|
|
810
|
+
"src/store/sqlite-backup.ts"() {
|
|
811
|
+
"use strict";
|
|
812
|
+
init_paths();
|
|
813
|
+
init_sqlite();
|
|
814
|
+
MAX_SNAPSHOTS = 10;
|
|
815
|
+
DEBOUNCE_MS = 3e4;
|
|
816
|
+
_lastBackupEpoch = 0;
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
761
820
|
// src/store/sqlite.ts
|
|
762
821
|
var sqlite_exports = {};
|
|
763
822
|
__export(sqlite_exports, {
|
|
@@ -773,16 +832,74 @@ __export(sqlite_exports, {
|
|
|
773
832
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
774
833
|
schema: () => schema_exports
|
|
775
834
|
});
|
|
776
|
-
import { existsSync as
|
|
835
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, copyFileSync, unlinkSync as unlinkSync2, renameSync as renameSync2 } from "node:fs";
|
|
777
836
|
import { createRequire as createRequire2 } from "node:module";
|
|
778
|
-
import { dirname as dirname3, join as
|
|
837
|
+
import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
779
838
|
import { fileURLToPath } from "node:url";
|
|
780
839
|
import { eq } from "drizzle-orm";
|
|
781
840
|
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
782
841
|
import { drizzle } from "drizzle-orm/sqlite-proxy";
|
|
783
842
|
import { migrate } from "drizzle-orm/sqlite-proxy/migrator";
|
|
784
843
|
function getDbPath(cwd) {
|
|
785
|
-
return
|
|
844
|
+
return join4(getCleoDirAbsolute(cwd), DB_FILENAME);
|
|
845
|
+
}
|
|
846
|
+
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
847
|
+
const log5 = getLogger("sqlite");
|
|
848
|
+
try {
|
|
849
|
+
const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks").get();
|
|
850
|
+
const taskCount = countResult?.cnt ?? 0;
|
|
851
|
+
if (taskCount > 0) return;
|
|
852
|
+
const backups = listSqliteBackups(cwd);
|
|
853
|
+
if (backups.length === 0) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const newestBackup = backups[0];
|
|
857
|
+
const backupDb = new DatabaseSync2(newestBackup.path, { readOnly: true });
|
|
858
|
+
let backupTaskCount = 0;
|
|
859
|
+
try {
|
|
860
|
+
const backupCount = backupDb.prepare("SELECT COUNT(*) as cnt FROM tasks").get();
|
|
861
|
+
backupTaskCount = backupCount?.cnt ?? 0;
|
|
862
|
+
} finally {
|
|
863
|
+
backupDb.close();
|
|
864
|
+
}
|
|
865
|
+
if (backupTaskCount < MIN_BACKUP_TASK_COUNT) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
log5.warn(
|
|
869
|
+
{ dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
|
|
870
|
+
`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).`
|
|
871
|
+
);
|
|
872
|
+
nativeDb.close();
|
|
873
|
+
const walPath = dbPath + "-wal";
|
|
874
|
+
const shmPath = dbPath + "-shm";
|
|
875
|
+
try {
|
|
876
|
+
unlinkSync2(walPath);
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
unlinkSync2(shmPath);
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
const tempPath = dbPath + ".recovery-tmp";
|
|
884
|
+
copyFileSync(newestBackup.path, tempPath);
|
|
885
|
+
renameSync2(tempPath, dbPath);
|
|
886
|
+
log5.info(
|
|
887
|
+
{ dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
|
|
888
|
+
"Database auto-recovered from backup successfully."
|
|
889
|
+
);
|
|
890
|
+
const restoredNativeDb = openNativeDatabase(dbPath);
|
|
891
|
+
_nativeDb = restoredNativeDb;
|
|
892
|
+
const callback = createDrizzleCallback(restoredNativeDb);
|
|
893
|
+
const batchCb = createBatchCallback(restoredNativeDb);
|
|
894
|
+
const restoredDb = drizzle(callback, batchCb, { schema: schema_exports });
|
|
895
|
+
await runMigrations(restoredNativeDb, restoredDb);
|
|
896
|
+
_db = restoredDb;
|
|
897
|
+
} catch (err) {
|
|
898
|
+
log5.error(
|
|
899
|
+
{ err, dbPath },
|
|
900
|
+
"Auto-recovery from backup failed. Continuing with empty database."
|
|
901
|
+
);
|
|
902
|
+
}
|
|
786
903
|
}
|
|
787
904
|
async function getDb(cwd) {
|
|
788
905
|
const requestedPath = getDbPath(cwd);
|
|
@@ -794,7 +911,7 @@ async function getDb(cwd) {
|
|
|
794
911
|
_initPromise = (async () => {
|
|
795
912
|
const dbPath = requestedPath;
|
|
796
913
|
_dbPath = dbPath;
|
|
797
|
-
|
|
914
|
+
mkdirSync3(dirname3(dbPath), { recursive: true });
|
|
798
915
|
const nativeDb = openNativeDatabase(dbPath);
|
|
799
916
|
_nativeDb = nativeDb;
|
|
800
917
|
const callback = createDrizzleCallback(nativeDb);
|
|
@@ -807,19 +924,28 @@ async function getDb(cwd) {
|
|
|
807
924
|
nativeDb.exec(
|
|
808
925
|
`INSERT OR IGNORE INTO schema_meta (key, value) VALUES ('task_id_sequence', '{"counter":0,"lastId":"T000","checksum":"seed"}')`
|
|
809
926
|
);
|
|
927
|
+
await autoRecoverFromBackup(nativeDb, dbPath, cwd);
|
|
810
928
|
if (!_gitTrackingChecked) {
|
|
811
929
|
_gitTrackingChecked = true;
|
|
812
930
|
try {
|
|
813
931
|
const { execFileSync: execFileSync9 } = await import("node:child_process");
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
stdio: "pipe"
|
|
817
|
-
});
|
|
932
|
+
const gitCwd = resolve3(dbPath, "..", "..");
|
|
933
|
+
const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
|
|
818
934
|
const log5 = getLogger("sqlite");
|
|
819
|
-
|
|
820
|
-
{
|
|
821
|
-
|
|
822
|
-
|
|
935
|
+
for (const fileToCheck of filesToCheck) {
|
|
936
|
+
try {
|
|
937
|
+
execFileSync9("git", ["ls-files", "--error-unmatch", fileToCheck], {
|
|
938
|
+
cwd: gitCwd,
|
|
939
|
+
stdio: "pipe"
|
|
940
|
+
});
|
|
941
|
+
const basename10 = fileToCheck.split("/").pop();
|
|
942
|
+
log5.warn(
|
|
943
|
+
{ path: fileToCheck },
|
|
944
|
+
`${basename10} is tracked by project git \u2014 this risks data loss on branch switch. Run: git rm --cached ${fileToCheck.replace(gitCwd + "/", "")} (see ADR-013, T5188)`
|
|
945
|
+
);
|
|
946
|
+
} catch {
|
|
947
|
+
}
|
|
948
|
+
}
|
|
823
949
|
} catch {
|
|
824
950
|
}
|
|
825
951
|
}
|
|
@@ -835,7 +961,7 @@ async function getDb(cwd) {
|
|
|
835
961
|
function resolveMigrationsFolder() {
|
|
836
962
|
const __filename = fileURLToPath(import.meta.url);
|
|
837
963
|
const __dirname2 = dirname3(__filename);
|
|
838
|
-
return
|
|
964
|
+
return join4(__dirname2, "..", "..", "drizzle");
|
|
839
965
|
}
|
|
840
966
|
function tableExists(nativeDb, tableName) {
|
|
841
967
|
const result = nativeDb.prepare(
|
|
@@ -929,12 +1055,12 @@ async function getSchemaVersion(cwd) {
|
|
|
929
1055
|
return result[0]?.value ?? null;
|
|
930
1056
|
}
|
|
931
1057
|
function dbExists(cwd) {
|
|
932
|
-
return
|
|
1058
|
+
return existsSync3(getDbPath(cwd));
|
|
933
1059
|
}
|
|
934
1060
|
function getNativeDb() {
|
|
935
1061
|
return _nativeDb;
|
|
936
1062
|
}
|
|
937
|
-
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise, _gitTrackingChecked, MAX_MIGRATION_RETRIES, MIGRATION_RETRY_BASE_DELAY_MS, MIGRATION_RETRY_MAX_DELAY_MS;
|
|
1063
|
+
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise, _gitTrackingChecked, MIN_BACKUP_TASK_COUNT, MAX_MIGRATION_RETRIES, MIGRATION_RETRY_BASE_DELAY_MS, MIGRATION_RETRY_MAX_DELAY_MS;
|
|
938
1064
|
var init_sqlite = __esm({
|
|
939
1065
|
"src/store/sqlite.ts"() {
|
|
940
1066
|
"use strict";
|
|
@@ -942,6 +1068,7 @@ var init_sqlite = __esm({
|
|
|
942
1068
|
init_paths();
|
|
943
1069
|
init_node_sqlite_adapter();
|
|
944
1070
|
init_logger();
|
|
1071
|
+
init_sqlite_backup();
|
|
945
1072
|
_require2 = createRequire2(import.meta.url);
|
|
946
1073
|
({ DatabaseSync: DatabaseSync2 } = _require2("node:sqlite"));
|
|
947
1074
|
DB_FILENAME = "tasks.db";
|
|
@@ -952,6 +1079,7 @@ var init_sqlite = __esm({
|
|
|
952
1079
|
_dbPath = null;
|
|
953
1080
|
_initPromise = null;
|
|
954
1081
|
_gitTrackingChecked = false;
|
|
1082
|
+
MIN_BACKUP_TASK_COUNT = 10;
|
|
955
1083
|
MAX_MIGRATION_RETRIES = 5;
|
|
956
1084
|
MIGRATION_RETRY_BASE_DELAY_MS = 100;
|
|
957
1085
|
MIGRATION_RETRY_MAX_DELAY_MS = 2e3;
|
|
@@ -1182,7 +1310,7 @@ __export(atomic_exports, {
|
|
|
1182
1310
|
});
|
|
1183
1311
|
import writeFileAtomic from "write-file-atomic";
|
|
1184
1312
|
import { readFile, mkdir, rename, unlink } from "node:fs/promises";
|
|
1185
|
-
import { existsSync as
|
|
1313
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1186
1314
|
import { dirname as dirname4 } from "node:path";
|
|
1187
1315
|
async function atomicWrite(filePath, data, options) {
|
|
1188
1316
|
try {
|
|
@@ -1220,37 +1348,37 @@ async function atomicWriteJson(filePath, data, options) {
|
|
|
1220
1348
|
async function atomicDatabaseMigration(dbPath, tempPath, validateFn) {
|
|
1221
1349
|
const backupPath = `${dbPath}.backup`;
|
|
1222
1350
|
try {
|
|
1223
|
-
if (!
|
|
1351
|
+
if (!existsSync4(tempPath)) {
|
|
1224
1352
|
throw new Error(`Temp database not found: ${tempPath}`);
|
|
1225
1353
|
}
|
|
1226
1354
|
const isValid = await validateFn(tempPath);
|
|
1227
1355
|
if (!isValid) {
|
|
1228
1356
|
throw new Error(`Temp database validation failed: ${tempPath}`);
|
|
1229
1357
|
}
|
|
1230
|
-
if (
|
|
1358
|
+
if (existsSync4(dbPath)) {
|
|
1231
1359
|
await rename(dbPath, backupPath);
|
|
1232
1360
|
}
|
|
1233
1361
|
await rename(tempPath, dbPath);
|
|
1234
1362
|
return {
|
|
1235
1363
|
success: true,
|
|
1236
1364
|
tempPath,
|
|
1237
|
-
backupPath:
|
|
1365
|
+
backupPath: existsSync4(backupPath) ? backupPath : void 0
|
|
1238
1366
|
};
|
|
1239
1367
|
} catch (err) {
|
|
1240
1368
|
return {
|
|
1241
1369
|
success: false,
|
|
1242
1370
|
tempPath,
|
|
1243
|
-
backupPath:
|
|
1371
|
+
backupPath: existsSync4(backupPath) ? backupPath : void 0,
|
|
1244
1372
|
error: String(err)
|
|
1245
1373
|
};
|
|
1246
1374
|
}
|
|
1247
1375
|
}
|
|
1248
1376
|
async function restoreDatabaseFromBackup(dbPath, backupPath) {
|
|
1249
1377
|
try {
|
|
1250
|
-
if (!
|
|
1378
|
+
if (!existsSync4(backupPath)) {
|
|
1251
1379
|
return false;
|
|
1252
1380
|
}
|
|
1253
|
-
if (
|
|
1381
|
+
if (existsSync4(dbPath)) {
|
|
1254
1382
|
await unlink(dbPath);
|
|
1255
1383
|
}
|
|
1256
1384
|
await rename(backupPath, dbPath);
|
|
@@ -1261,7 +1389,7 @@ async function restoreDatabaseFromBackup(dbPath, backupPath) {
|
|
|
1261
1389
|
}
|
|
1262
1390
|
async function cleanupMigrationArtifacts(backupPath) {
|
|
1263
1391
|
try {
|
|
1264
|
-
if (
|
|
1392
|
+
if (existsSync4(backupPath)) {
|
|
1265
1393
|
await unlink(backupPath);
|
|
1266
1394
|
}
|
|
1267
1395
|
return true;
|
|
@@ -1295,7 +1423,7 @@ var init_atomic = __esm({
|
|
|
1295
1423
|
|
|
1296
1424
|
// src/store/backup.ts
|
|
1297
1425
|
import { copyFile, rename as fsRename, readdir, unlink as unlink2, stat, mkdir as mkdir2 } from "node:fs/promises";
|
|
1298
|
-
import { join as
|
|
1426
|
+
import { join as join5, basename } from "node:path";
|
|
1299
1427
|
async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUPS) {
|
|
1300
1428
|
try {
|
|
1301
1429
|
await mkdir2(backupDir, { recursive: true });
|
|
@@ -1309,14 +1437,14 @@ async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUP
|
|
|
1309
1437
|
);
|
|
1310
1438
|
}
|
|
1311
1439
|
for (let i = maxBackups; i >= 1; i--) {
|
|
1312
|
-
const current =
|
|
1440
|
+
const current = join5(backupDir, `${fileName}.${i}`);
|
|
1313
1441
|
if (i === maxBackups) {
|
|
1314
1442
|
try {
|
|
1315
1443
|
await unlink2(current);
|
|
1316
1444
|
} catch {
|
|
1317
1445
|
}
|
|
1318
1446
|
} else {
|
|
1319
|
-
const next =
|
|
1447
|
+
const next = join5(backupDir, `${fileName}.${i + 1}`);
|
|
1320
1448
|
try {
|
|
1321
1449
|
await stat(current);
|
|
1322
1450
|
await fsRename(current, next);
|
|
@@ -1324,7 +1452,7 @@ async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUP
|
|
|
1324
1452
|
}
|
|
1325
1453
|
}
|
|
1326
1454
|
}
|
|
1327
|
-
const backupPath =
|
|
1455
|
+
const backupPath = join5(backupDir, `${fileName}.1`);
|
|
1328
1456
|
await copyFile(filePath, backupPath);
|
|
1329
1457
|
return backupPath;
|
|
1330
1458
|
} catch (err) {
|
|
@@ -1344,7 +1472,7 @@ async function listBackups(fileName, backupDir) {
|
|
|
1344
1472
|
const numA = parseInt(a.slice(prefix.length), 10);
|
|
1345
1473
|
const numB = parseInt(b.slice(prefix.length), 10);
|
|
1346
1474
|
return numA - numB;
|
|
1347
|
-
}).map((e) =>
|
|
1475
|
+
}).map((e) => join5(backupDir, e));
|
|
1348
1476
|
} catch {
|
|
1349
1477
|
return [];
|
|
1350
1478
|
}
|
|
@@ -2119,15 +2247,15 @@ var init_sqlite_data_accessor = __esm({
|
|
|
2119
2247
|
|
|
2120
2248
|
// src/store/git-checkpoint.ts
|
|
2121
2249
|
import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
2122
|
-
import { existsSync as
|
|
2250
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2123
2251
|
import { execFile } from "node:child_process";
|
|
2124
2252
|
import { promisify } from "node:util";
|
|
2125
|
-
import { join as
|
|
2253
|
+
import { join as join6, resolve as resolve4 } from "node:path";
|
|
2126
2254
|
function makeCleoGitEnv(cleoDir) {
|
|
2127
2255
|
const abs = resolve4(cleoDir);
|
|
2128
2256
|
return {
|
|
2129
2257
|
...process.env,
|
|
2130
|
-
GIT_DIR:
|
|
2258
|
+
GIT_DIR: join6(abs, ".git"),
|
|
2131
2259
|
GIT_WORK_TREE: abs
|
|
2132
2260
|
};
|
|
2133
2261
|
}
|
|
@@ -2145,7 +2273,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
2145
2273
|
}
|
|
2146
2274
|
}
|
|
2147
2275
|
function isCleoGitInitialized(cleoDir) {
|
|
2148
|
-
return
|
|
2276
|
+
return existsSync5(join6(cleoDir, ".git", "HEAD"));
|
|
2149
2277
|
}
|
|
2150
2278
|
async function loadCheckpointConfig(cwd) {
|
|
2151
2279
|
try {
|
|
@@ -2172,25 +2300,25 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
2172
2300
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
2173
2301
|
}
|
|
2174
2302
|
function isMergeInProgress(cleoDir) {
|
|
2175
|
-
return
|
|
2303
|
+
return existsSync5(join6(cleoDir, ".git", "MERGE_HEAD"));
|
|
2176
2304
|
}
|
|
2177
2305
|
async function isDetachedHead(cleoDir) {
|
|
2178
2306
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
2179
2307
|
return !result.success;
|
|
2180
2308
|
}
|
|
2181
2309
|
function isRebaseInProgress(cleoDir) {
|
|
2182
|
-
return
|
|
2310
|
+
return existsSync5(join6(cleoDir, ".git", "rebase-merge")) || existsSync5(join6(cleoDir, ".git", "rebase-apply"));
|
|
2183
2311
|
}
|
|
2184
2312
|
async function recordCheckpointTime(cleoDir) {
|
|
2185
2313
|
try {
|
|
2186
|
-
const stateFile =
|
|
2314
|
+
const stateFile = join6(cleoDir, CHECKPOINT_STATE_FILE);
|
|
2187
2315
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
2188
2316
|
} catch {
|
|
2189
2317
|
}
|
|
2190
2318
|
}
|
|
2191
2319
|
async function getLastCheckpointTime(cleoDir) {
|
|
2192
2320
|
try {
|
|
2193
|
-
const stateFile =
|
|
2321
|
+
const stateFile = join6(cleoDir, CHECKPOINT_STATE_FILE);
|
|
2194
2322
|
const content = await readFile2(stateFile, "utf-8");
|
|
2195
2323
|
const epoch = parseInt(content.trim(), 10);
|
|
2196
2324
|
return isNaN(epoch) ? 0 : epoch;
|
|
@@ -2201,8 +2329,8 @@ async function getLastCheckpointTime(cleoDir) {
|
|
|
2201
2329
|
async function getChangedStateFiles(cleoDir) {
|
|
2202
2330
|
const changed = [];
|
|
2203
2331
|
for (const stateFile of STATE_FILES) {
|
|
2204
|
-
const fullPath =
|
|
2205
|
-
if (!
|
|
2332
|
+
const fullPath = join6(cleoDir, stateFile);
|
|
2333
|
+
if (!existsSync5(fullPath)) continue;
|
|
2206
2334
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
2207
2335
|
const cachedResult = await cleoGitCommand(["diff", "--cached", "--quiet", "--", stateFile], cleoDir);
|
|
2208
2336
|
const untrackedResult = await cleoGitCommand(
|
|
@@ -2226,7 +2354,7 @@ async function shouldCheckpoint(options) {
|
|
|
2226
2354
|
const config = await loadCheckpointConfig(cwd);
|
|
2227
2355
|
if (!config.enabled) return false;
|
|
2228
2356
|
const cleoDir = getCleoDir(cwd);
|
|
2229
|
-
if (!
|
|
2357
|
+
if (!existsSync5(cleoDir)) return false;
|
|
2230
2358
|
if (!isCleoGitInitialized(cleoDir)) return false;
|
|
2231
2359
|
if (!await isCleoGitRepo(cleoDir)) return false;
|
|
2232
2360
|
if (isMergeInProgress(cleoDir)) return false;
|
|
@@ -2324,55 +2452,6 @@ var init_git_checkpoint = __esm({
|
|
|
2324
2452
|
}
|
|
2325
2453
|
});
|
|
2326
2454
|
|
|
2327
|
-
// src/store/sqlite-backup.ts
|
|
2328
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2329
|
-
import { join as join6 } from "node:path";
|
|
2330
|
-
function formatTimestamp(d) {
|
|
2331
|
-
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
2332
|
-
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
2333
|
-
}
|
|
2334
|
-
function rotateSnapshots(backupDir) {
|
|
2335
|
-
try {
|
|
2336
|
-
const files = readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join6(backupDir, f), mtimeMs: statSync(join6(backupDir, f)).mtimeMs })).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
2337
|
-
while (files.length >= MAX_SNAPSHOTS) {
|
|
2338
|
-
const oldest = files.shift();
|
|
2339
|
-
unlinkSync(oldest.path);
|
|
2340
|
-
}
|
|
2341
|
-
} catch {
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
async function vacuumIntoBackup(opts = {}) {
|
|
2345
|
-
const now = Date.now();
|
|
2346
|
-
if (!opts.force && now - _lastBackupEpoch < DEBOUNCE_MS) {
|
|
2347
|
-
return;
|
|
2348
|
-
}
|
|
2349
|
-
try {
|
|
2350
|
-
const cleoDir = getCleoDir(opts.cwd);
|
|
2351
|
-
const backupDir = join6(cleoDir, "backups", "sqlite");
|
|
2352
|
-
mkdirSync3(backupDir, { recursive: true });
|
|
2353
|
-
const db = getNativeDb();
|
|
2354
|
-
if (!db) return;
|
|
2355
|
-
const dest = join6(backupDir, `tasks-${formatTimestamp(/* @__PURE__ */ new Date())}.db`);
|
|
2356
|
-
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
2357
|
-
rotateSnapshots(backupDir);
|
|
2358
|
-
const safeDest = dest.replace(/'/g, "''");
|
|
2359
|
-
db.exec(`VACUUM INTO '${safeDest}'`);
|
|
2360
|
-
_lastBackupEpoch = Date.now();
|
|
2361
|
-
} catch {
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
var MAX_SNAPSHOTS, DEBOUNCE_MS, _lastBackupEpoch;
|
|
2365
|
-
var init_sqlite_backup = __esm({
|
|
2366
|
-
"src/store/sqlite-backup.ts"() {
|
|
2367
|
-
"use strict";
|
|
2368
|
-
init_paths();
|
|
2369
|
-
init_sqlite();
|
|
2370
|
-
MAX_SNAPSHOTS = 10;
|
|
2371
|
-
DEBOUNCE_MS = 3e4;
|
|
2372
|
-
_lastBackupEpoch = 0;
|
|
2373
|
-
}
|
|
2374
|
-
});
|
|
2375
|
-
|
|
2376
2455
|
// src/core/sequence/index.ts
|
|
2377
2456
|
var sequence_exports = {};
|
|
2378
2457
|
__export(sequence_exports, {
|
|
@@ -2381,7 +2460,7 @@ __export(sequence_exports, {
|
|
|
2381
2460
|
repairSequence: () => repairSequence,
|
|
2382
2461
|
showSequence: () => showSequence
|
|
2383
2462
|
});
|
|
2384
|
-
import { existsSync as existsSync6, readFileSync as readFileSync2, renameSync as
|
|
2463
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2, renameSync as renameSync3 } from "node:fs";
|
|
2385
2464
|
import { join as join7 } from "node:path";
|
|
2386
2465
|
import { eq as eq4 } from "drizzle-orm";
|
|
2387
2466
|
function getLegacySequenceJsonPath(cwd) {
|
|
@@ -2412,10 +2491,10 @@ function renameLegacyFile(path) {
|
|
|
2412
2491
|
const migratedPath = `${path}.migrated`;
|
|
2413
2492
|
try {
|
|
2414
2493
|
if (!existsSync6(migratedPath)) {
|
|
2415
|
-
|
|
2494
|
+
renameSync3(path, migratedPath);
|
|
2416
2495
|
return;
|
|
2417
2496
|
}
|
|
2418
|
-
|
|
2497
|
+
renameSync3(path, `${migratedPath}.${Date.now()}`);
|
|
2419
2498
|
} catch {
|
|
2420
2499
|
}
|
|
2421
2500
|
}
|
|
@@ -5331,7 +5410,7 @@ var init_find = __esm({
|
|
|
5331
5410
|
});
|
|
5332
5411
|
|
|
5333
5412
|
// src/store/file-utils.ts
|
|
5334
|
-
import { readFileSync as readFileSync3, writeFileSync, renameSync as
|
|
5413
|
+
import { readFileSync as readFileSync3, writeFileSync, renameSync as renameSync4, existsSync as existsSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "node:fs";
|
|
5335
5414
|
import { join as join8, dirname as dirname6, basename as basename2 } from "node:path";
|
|
5336
5415
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
5337
5416
|
import * as lockfile2 from "proper-lockfile";
|
|
@@ -5346,13 +5425,13 @@ function rotateBackup(filePath) {
|
|
|
5346
5425
|
const current = join8(backupDir, `${name}.${i}`);
|
|
5347
5426
|
if (i === MAX_BACKUPS) {
|
|
5348
5427
|
try {
|
|
5349
|
-
|
|
5428
|
+
unlinkSync3(current);
|
|
5350
5429
|
} catch {
|
|
5351
5430
|
}
|
|
5352
5431
|
} else {
|
|
5353
5432
|
const next = join8(backupDir, `${name}.${i + 1}`);
|
|
5354
5433
|
try {
|
|
5355
|
-
if (existsSync8(current))
|
|
5434
|
+
if (existsSync8(current)) renameSync4(current, next);
|
|
5356
5435
|
} catch {
|
|
5357
5436
|
}
|
|
5358
5437
|
}
|
|
@@ -5372,10 +5451,10 @@ function writeJsonFileAtomic(filePath, data, indent = 2) {
|
|
|
5372
5451
|
if (existsSync8(filePath)) {
|
|
5373
5452
|
rotateBackup(filePath);
|
|
5374
5453
|
}
|
|
5375
|
-
|
|
5454
|
+
renameSync4(tempPath, filePath);
|
|
5376
5455
|
} catch (error) {
|
|
5377
5456
|
try {
|
|
5378
|
-
|
|
5457
|
+
unlinkSync3(tempPath);
|
|
5379
5458
|
} catch {
|
|
5380
5459
|
}
|
|
5381
5460
|
throw error;
|
|
@@ -6860,7 +6939,7 @@ import {
|
|
|
6860
6939
|
writeFileSync as writeFileSync2,
|
|
6861
6940
|
mkdirSync as mkdirSync7,
|
|
6862
6941
|
readdirSync as readdirSync3,
|
|
6863
|
-
copyFileSync,
|
|
6942
|
+
copyFileSync as copyFileSync2,
|
|
6864
6943
|
statSync as statSync3,
|
|
6865
6944
|
rmSync
|
|
6866
6945
|
} from "node:fs";
|
|
@@ -6936,7 +7015,7 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
6936
7015
|
for (const sf of readdirSync3(srcPath)) {
|
|
6937
7016
|
if (!copiedFiles.has(sf)) {
|
|
6938
7017
|
try {
|
|
6939
|
-
|
|
7018
|
+
copyFileSync2(join13(srcPath, sf), join13(dstPath, sf));
|
|
6940
7019
|
copiedFiles.add(sf);
|
|
6941
7020
|
count2++;
|
|
6942
7021
|
} catch {
|
|
@@ -6944,7 +7023,7 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
6944
7023
|
}
|
|
6945
7024
|
}
|
|
6946
7025
|
} else if (!copiedFiles.has(entry)) {
|
|
6947
|
-
|
|
7026
|
+
copyFileSync2(srcPath, dstPath);
|
|
6948
7027
|
copiedFiles.add(entry);
|
|
6949
7028
|
count2++;
|
|
6950
7029
|
}
|
|
@@ -10474,7 +10553,6 @@ var init_relates = __esm({
|
|
|
10474
10553
|
init_paths();
|
|
10475
10554
|
init_errors();
|
|
10476
10555
|
init_exit_codes();
|
|
10477
|
-
init_data_safety_central();
|
|
10478
10556
|
}
|
|
10479
10557
|
});
|
|
10480
10558
|
|
|
@@ -15330,7 +15408,7 @@ function getMigrationStatus(projectRoot, opts) {
|
|
|
15330
15408
|
// src/core/system/cleanup.ts
|
|
15331
15409
|
init_errors();
|
|
15332
15410
|
init_exit_codes();
|
|
15333
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync17, readdirSync as readdirSync4, unlinkSync as
|
|
15411
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync17, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "node:fs";
|
|
15334
15412
|
import { join as join17 } from "node:path";
|
|
15335
15413
|
function cleanupSystem(projectRoot, params) {
|
|
15336
15414
|
if (!params.target) {
|
|
@@ -15381,11 +15459,11 @@ function cleanupSystem(projectRoot, params) {
|
|
|
15381
15459
|
if (params.olderThan && meta.timestamp < params.olderThan) {
|
|
15382
15460
|
items.push(file.replace(".meta.json", ""));
|
|
15383
15461
|
if (!dryRun) {
|
|
15384
|
-
|
|
15462
|
+
unlinkSync4(metaFilePath);
|
|
15385
15463
|
for (const bf of readdirSync4(fullDir)) {
|
|
15386
15464
|
if (bf.includes(meta.backupId)) {
|
|
15387
15465
|
try {
|
|
15388
|
-
|
|
15466
|
+
unlinkSync4(join17(fullDir, bf));
|
|
15389
15467
|
} catch {
|
|
15390
15468
|
}
|
|
15391
15469
|
}
|
|
@@ -15410,7 +15488,7 @@ function cleanupSystem(projectRoot, params) {
|
|
|
15410
15488
|
items.push(file);
|
|
15411
15489
|
if (!dryRun) {
|
|
15412
15490
|
try {
|
|
15413
|
-
|
|
15491
|
+
unlinkSync4(join17(cleoDir, file));
|
|
15414
15492
|
} catch {
|
|
15415
15493
|
}
|
|
15416
15494
|
}
|
|
@@ -30211,7 +30289,7 @@ function registerPlanCommand(program2) {
|
|
|
30211
30289
|
}
|
|
30212
30290
|
|
|
30213
30291
|
// src/core/otel/index.ts
|
|
30214
|
-
import { readFileSync as readFileSync41, existsSync as existsSync55, writeFileSync as writeFileSync14, copyFileSync as
|
|
30292
|
+
import { readFileSync as readFileSync41, existsSync as existsSync55, writeFileSync as writeFileSync14, copyFileSync as copyFileSync3 } from "node:fs";
|
|
30215
30293
|
import { join as join59 } from "node:path";
|
|
30216
30294
|
function getProjectRoot2() {
|
|
30217
30295
|
let dir = process.cwd();
|
|
@@ -30310,7 +30388,7 @@ async function clearOtelData() {
|
|
|
30310
30388
|
const tokenFile = getTokenFilePath();
|
|
30311
30389
|
if (existsSync55(tokenFile)) {
|
|
30312
30390
|
const backup = `${tokenFile}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
30313
|
-
|
|
30391
|
+
copyFileSync3(tokenFile, backup);
|
|
30314
30392
|
writeFileSync14(tokenFile, "");
|
|
30315
30393
|
return { message: "Token tracking cleared", backup };
|
|
30316
30394
|
}
|
|
@@ -30701,7 +30779,7 @@ init_paths();
|
|
|
30701
30779
|
|
|
30702
30780
|
// src/core/upgrade.ts
|
|
30703
30781
|
init_paths();
|
|
30704
|
-
import { existsSync as existsSync59, readFileSync as readFileSync43, writeFileSync as writeFileSync15, mkdirSync as mkdirSync19, readdirSync as readdirSync18, copyFileSync as
|
|
30782
|
+
import { existsSync as existsSync59, readFileSync as readFileSync43, writeFileSync as writeFileSync15, mkdirSync as mkdirSync19, readdirSync as readdirSync18, copyFileSync as copyFileSync4 } from "node:fs";
|
|
30705
30783
|
import { join as join63 } from "node:path";
|
|
30706
30784
|
init_agent_outputs();
|
|
30707
30785
|
|
|
@@ -30901,7 +30979,7 @@ var MigrationLogger = class {
|
|
|
30901
30979
|
if (!existsSync56(logsDir)) {
|
|
30902
30980
|
return;
|
|
30903
30981
|
}
|
|
30904
|
-
const { readdirSync: readdirSync20, unlinkSync:
|
|
30982
|
+
const { readdirSync: readdirSync20, unlinkSync: unlinkSync5 } = __require("node:fs");
|
|
30905
30983
|
const files = readdirSync20(logsDir).filter((f) => f.startsWith("migration-") && f.endsWith(".jsonl")).map((f) => ({
|
|
30906
30984
|
name: f,
|
|
30907
30985
|
path: join60(logsDir, f),
|
|
@@ -30910,7 +30988,7 @@ var MigrationLogger = class {
|
|
|
30910
30988
|
const filesToRemove = files.slice(this.config.maxLogFiles);
|
|
30911
30989
|
for (const file of filesToRemove) {
|
|
30912
30990
|
try {
|
|
30913
|
-
|
|
30991
|
+
unlinkSync5(file.path);
|
|
30914
30992
|
} catch {
|
|
30915
30993
|
}
|
|
30916
30994
|
}
|
|
@@ -31235,7 +31313,7 @@ async function runUpgrade(options = {}) {
|
|
|
31235
31313
|
if (!existsSync59(backupDir)) {
|
|
31236
31314
|
mkdirSync19(backupDir, { recursive: true });
|
|
31237
31315
|
}
|
|
31238
|
-
|
|
31316
|
+
copyFileSync4(dbPath2, dbBackupPath);
|
|
31239
31317
|
const { createHash: createHash8 } = await import("node:crypto");
|
|
31240
31318
|
const origChecksum = createHash8("sha256").update(readFileSync43(dbPath2)).digest("hex");
|
|
31241
31319
|
const backupChecksum = createHash8("sha256").update(readFileSync43(dbBackupPath)).digest("hex");
|
|
@@ -31254,8 +31332,8 @@ async function runUpgrade(options = {}) {
|
|
|
31254
31332
|
logger.info("backup", "verified", "Backup integrity verified", { checksum: origChecksum });
|
|
31255
31333
|
}
|
|
31256
31334
|
if (existsSync59(dbTempPath)) {
|
|
31257
|
-
const { unlinkSync:
|
|
31258
|
-
|
|
31335
|
+
const { unlinkSync: unlinkSync5 } = await import("node:fs");
|
|
31336
|
+
unlinkSync5(dbTempPath);
|
|
31259
31337
|
}
|
|
31260
31338
|
const configPath = join63(cleoDir2, "config.json");
|
|
31261
31339
|
let configBackup = null;
|
|
@@ -31287,7 +31365,7 @@ async function runUpgrade(options = {}) {
|
|
|
31287
31365
|
if (result.success) {
|
|
31288
31366
|
const totalImported = result.tasksImported + result.archivedImported;
|
|
31289
31367
|
if (totalImported === 0 && existsSync59(dbBackupPath)) {
|
|
31290
|
-
|
|
31368
|
+
copyFileSync4(dbBackupPath, dbPath2);
|
|
31291
31369
|
if (configBackup) {
|
|
31292
31370
|
writeFileSync15(configPath, configBackup);
|
|
31293
31371
|
}
|
|
@@ -31329,7 +31407,7 @@ async function runUpgrade(options = {}) {
|
|
|
31329
31407
|
}
|
|
31330
31408
|
} else {
|
|
31331
31409
|
if (existsSync59(dbBackupPath)) {
|
|
31332
|
-
|
|
31410
|
+
copyFileSync4(dbBackupPath, dbPath2);
|
|
31333
31411
|
}
|
|
31334
31412
|
if (configBackup) {
|
|
31335
31413
|
writeFileSync15(configPath, configBackup);
|
|
@@ -31356,7 +31434,7 @@ async function runUpgrade(options = {}) {
|
|
|
31356
31434
|
if (existsSync59(safetyDir)) {
|
|
31357
31435
|
const backups = readdirSync18(safetyDir).filter((f) => f.startsWith("tasks.db.pre-migration.")).sort().reverse();
|
|
31358
31436
|
if (backups.length > 0 && !existsSync59(dbPath2)) {
|
|
31359
|
-
|
|
31437
|
+
copyFileSync4(join63(safetyDir, backups[0]), dbPath2);
|
|
31360
31438
|
}
|
|
31361
31439
|
}
|
|
31362
31440
|
} catch {
|
|
@@ -31445,11 +31523,11 @@ async function runUpgrade(options = {}) {
|
|
|
31445
31523
|
mkdirSync19(backupDir, { recursive: true });
|
|
31446
31524
|
for (const f of foundStale) {
|
|
31447
31525
|
const src = join63(cleoDir, f);
|
|
31448
|
-
|
|
31526
|
+
copyFileSync4(src, join63(backupDir, f));
|
|
31449
31527
|
}
|
|
31450
|
-
const { unlinkSync:
|
|
31528
|
+
const { unlinkSync: unlinkSync5 } = await import("node:fs");
|
|
31451
31529
|
for (const f of foundStale) {
|
|
31452
|
-
|
|
31530
|
+
unlinkSync5(join63(cleoDir, f));
|
|
31453
31531
|
}
|
|
31454
31532
|
actions.push({
|
|
31455
31533
|
action: "stale_json_cleanup",
|