@cleocode/core 2026.4.17 → 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 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> or set lifecycle.mode to "advisory".',
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();