@cleocode/core 2026.4.18 → 2026.4.19
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/index.js +103 -5
- package/dist/index.js.map +2 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/store/brain-sqlite.d.ts.map +1 -1
- package/dist/store/migration-manager.d.ts +22 -2
- package/dist/store/migration-manager.d.ts.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/init.ts +67 -0
- package/src/store/__tests__/migration-reconcile.test.ts +351 -0
- package/src/store/brain-sqlite.ts +4 -2
- package/src/store/migration-manager.ts +103 -3
- package/src/store/sqlite.ts +4 -2
- package/src/tasks/add.ts +7 -1
package/dist/index.js
CHANGED
|
@@ -11086,13 +11086,54 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable, logSubsyst
|
|
|
11086
11086
|
}
|
|
11087
11087
|
}
|
|
11088
11088
|
}
|
|
11089
|
+
if (tableExists(nativeDb, "__drizzle_migrations") && tableExists(nativeDb, existenceTable)) {
|
|
11090
|
+
const localMigrations = readMigrationFiles({ migrationsFolder });
|
|
11091
|
+
const journalEntries = nativeDb.prepare('SELECT hash FROM "__drizzle_migrations"').all();
|
|
11092
|
+
const journaledHashes = new Set(journalEntries.map((e) => e.hash));
|
|
11093
|
+
for (const migration of localMigrations) {
|
|
11094
|
+
if (journaledHashes.has(migration.hash)) continue;
|
|
11095
|
+
const alterColumnRegex = /ALTER\s+TABLE\s+[`"]?(\w+)[`"]?\s+ADD\s+COLUMN\s+[`"]?(\w+)[`"]?/gi;
|
|
11096
|
+
const alterMatches = [];
|
|
11097
|
+
const sqlStatements = Array.isArray(migration.sql) ? migration.sql : [migration.sql ?? ""];
|
|
11098
|
+
const fullSql = sqlStatements.join("\n");
|
|
11099
|
+
for (const m of fullSql.matchAll(alterColumnRegex)) {
|
|
11100
|
+
alterMatches.push({ table: m[1], column: m[2] });
|
|
11101
|
+
}
|
|
11102
|
+
if (alterMatches.length === 0) continue;
|
|
11103
|
+
const allColumnsExist = alterMatches.every(({ table, column }) => {
|
|
11104
|
+
if (!tableExists(nativeDb, table)) return false;
|
|
11105
|
+
const cols = nativeDb.prepare(`PRAGMA table_info(${table})`).all();
|
|
11106
|
+
return cols.some((c) => c.name === column);
|
|
11107
|
+
});
|
|
11108
|
+
if (allColumnsExist) {
|
|
11109
|
+
const log9 = getLogger(logSubsystem);
|
|
11110
|
+
log9.warn(
|
|
11111
|
+
{ migration: migration.name, columns: alterMatches },
|
|
11112
|
+
`Detected partially-applied migration ${migration.name} \u2014 columns exist but journal entry missing. Auto-reconciling.`
|
|
11113
|
+
);
|
|
11114
|
+
nativeDb.exec(
|
|
11115
|
+
`INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES ('${migration.hash}', ${migration.folderMillis})`
|
|
11116
|
+
);
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
function isDuplicateColumnError(err) {
|
|
11122
|
+
if (!(err instanceof Error)) return false;
|
|
11123
|
+
return /duplicate column name/i.test(err.message);
|
|
11089
11124
|
}
|
|
11090
|
-
function migrateWithRetry(db, migrationsFolder) {
|
|
11125
|
+
function migrateWithRetry(db, migrationsFolder, nativeDb, existenceTable, logSubsystem) {
|
|
11126
|
+
let duplicateColumnReconciled = false;
|
|
11091
11127
|
for (let attempt = 1; attempt <= MAX_MIGRATION_RETRIES; attempt++) {
|
|
11092
11128
|
try {
|
|
11093
11129
|
migrate(db, { migrationsFolder });
|
|
11094
11130
|
return;
|
|
11095
11131
|
} catch (err) {
|
|
11132
|
+
if (isDuplicateColumnError(err) && !duplicateColumnReconciled && nativeDb !== void 0 && existenceTable !== void 0 && logSubsystem !== void 0) {
|
|
11133
|
+
duplicateColumnReconciled = true;
|
|
11134
|
+
reconcileJournal(nativeDb, migrationsFolder, existenceTable, logSubsystem);
|
|
11135
|
+
continue;
|
|
11136
|
+
}
|
|
11096
11137
|
if (!isSqliteBusy(err) || attempt === MAX_MIGRATION_RETRIES) {
|
|
11097
11138
|
throw err;
|
|
11098
11139
|
}
|
|
@@ -13012,7 +13053,7 @@ function runMigrations(nativeDb, db) {
|
|
|
13012
13053
|
}
|
|
13013
13054
|
reconcileJournal(nativeDb, migrationsFolder, "tasks", "sqlite");
|
|
13014
13055
|
ensureColumns(nativeDb, "sessions", REQUIRED_SESSION_COLUMNS, "sqlite");
|
|
13015
|
-
migrateWithRetry(db, migrationsFolder);
|
|
13056
|
+
migrateWithRetry(db, migrationsFolder, nativeDb, "tasks", "sqlite");
|
|
13016
13057
|
ensureColumns(nativeDb, "tasks", REQUIRED_TASK_COLUMNS, "sqlite");
|
|
13017
13058
|
}
|
|
13018
13059
|
function closeDb() {
|
|
@@ -13138,7 +13179,7 @@ function runBrainMigrations(nativeDb, db) {
|
|
|
13138
13179
|
createSafetyBackup(_dbPath2);
|
|
13139
13180
|
}
|
|
13140
13181
|
reconcileJournal(nativeDb, migrationsFolder, "brain_decisions", "brain");
|
|
13141
|
-
migrateWithRetry(db, migrationsFolder);
|
|
13182
|
+
migrateWithRetry(db, migrationsFolder, nativeDb, "brain_decisions", "brain");
|
|
13142
13183
|
}
|
|
13143
13184
|
function loadBrainVecExtension(nativeDb) {
|
|
13144
13185
|
try {
|
|
@@ -24820,9 +24861,15 @@ async function addTask(options, cwd, accessor) {
|
|
|
24820
24861
|
if (lifecycleMode === "strict") {
|
|
24821
24862
|
throw new CleoError(
|
|
24822
24863
|
6 /* VALIDATION_ERROR */,
|
|
24823
|
-
'Tasks must have a parent (epic or task) in strict mode. Use --parent <epicId
|
|
24864
|
+
'Tasks must have a parent (epic or task) in strict mode. Use --parent <epicId>, --type epic for a root-level epic, or set lifecycle.mode to "advisory".',
|
|
24824
24865
|
{
|
|
24825
|
-
fix: 'cleo add "Task title" --parent T### --acceptance "AC1|AC2|AC3"'
|
|
24866
|
+
fix: 'cleo add "Task title" --parent T### --acceptance "AC1|AC2|AC3"',
|
|
24867
|
+
alternatives: [
|
|
24868
|
+
{
|
|
24869
|
+
action: "Create as epic",
|
|
24870
|
+
command: 'cleo add "Epic title" --type epic --priority high'
|
|
24871
|
+
}
|
|
24872
|
+
]
|
|
24826
24873
|
}
|
|
24827
24874
|
);
|
|
24828
24875
|
}
|
|
@@ -27526,6 +27573,57 @@ async function initProject(opts = {}) {
|
|
|
27526
27573
|
".cleo/ was found in root .gitignore and has been removed. CLEO uses .cleo/.gitignore for selective tracking."
|
|
27527
27574
|
);
|
|
27528
27575
|
}
|
|
27576
|
+
try {
|
|
27577
|
+
const cantDir = join104(cleoDir, "cant");
|
|
27578
|
+
const cantAgentsDir = join104(cantDir, "agents");
|
|
27579
|
+
const hasCantFiles = existsSync104(cantDir) && readdirSync36(cantDir, { recursive: true }).some(
|
|
27580
|
+
(f) => typeof f === "string" && f.endsWith(".cant")
|
|
27581
|
+
);
|
|
27582
|
+
if (!hasCantFiles) {
|
|
27583
|
+
let starterBundleSrc = null;
|
|
27584
|
+
try {
|
|
27585
|
+
const { createRequire: createRequire8 } = await import("node:module");
|
|
27586
|
+
const req = createRequire8(import.meta.url);
|
|
27587
|
+
const cleoOsPkgMain = req.resolve("@cleocode/cleo-os/package.json");
|
|
27588
|
+
const cleoOsPkgRoot = dirname21(cleoOsPkgMain);
|
|
27589
|
+
const candidate = join104(cleoOsPkgRoot, "starter-bundle");
|
|
27590
|
+
if (existsSync104(candidate)) {
|
|
27591
|
+
starterBundleSrc = candidate;
|
|
27592
|
+
}
|
|
27593
|
+
} catch {
|
|
27594
|
+
}
|
|
27595
|
+
if (!starterBundleSrc) {
|
|
27596
|
+
const packageRoot = getPackageRoot();
|
|
27597
|
+
const fallbacks = [
|
|
27598
|
+
join104(packageRoot, "..", "cleo-os", "starter-bundle"),
|
|
27599
|
+
join104(packageRoot, "..", "..", "packages", "cleo-os", "starter-bundle")
|
|
27600
|
+
];
|
|
27601
|
+
starterBundleSrc = fallbacks.find((p) => existsSync104(p)) ?? null;
|
|
27602
|
+
}
|
|
27603
|
+
if (starterBundleSrc) {
|
|
27604
|
+
await mkdir16(cantDir, { recursive: true });
|
|
27605
|
+
await mkdir16(cantAgentsDir, { recursive: true });
|
|
27606
|
+
const teamSrc = join104(starterBundleSrc, "team.cant");
|
|
27607
|
+
const teamDst = join104(cantDir, "team.cant");
|
|
27608
|
+
if (existsSync104(teamSrc) && !existsSync104(teamDst)) {
|
|
27609
|
+
await copyFile4(teamSrc, teamDst);
|
|
27610
|
+
}
|
|
27611
|
+
const agentsSrc = join104(starterBundleSrc, "agents");
|
|
27612
|
+
if (existsSync104(agentsSrc)) {
|
|
27613
|
+
const agentFiles = readdirSync36(agentsSrc).filter((f) => f.endsWith(".cant"));
|
|
27614
|
+
for (const agentFile of agentFiles) {
|
|
27615
|
+
const dst = join104(cantAgentsDir, agentFile);
|
|
27616
|
+
if (!existsSync104(dst)) {
|
|
27617
|
+
await copyFile4(join104(agentsSrc, agentFile), dst);
|
|
27618
|
+
}
|
|
27619
|
+
}
|
|
27620
|
+
}
|
|
27621
|
+
created.push("starter-bundle: team + agent .cant files deployed to .cleo/cant/");
|
|
27622
|
+
}
|
|
27623
|
+
}
|
|
27624
|
+
} catch (err) {
|
|
27625
|
+
warnings.push(`Starter bundle deploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
27626
|
+
}
|
|
27529
27627
|
if (opts.installSeedAgents) {
|
|
27530
27628
|
try {
|
|
27531
27629
|
const seedDir = await resolveSeedAgentsDir();
|