@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.
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAgCH,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yCAAyC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;IACF;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAaD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCnE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqE9F;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FzF;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAwBtD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAia7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAiB/F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBnF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAgCH,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yCAAyC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;IACF;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAaD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCnE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqE9F;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FzF;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAwBtD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoe7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAiB/F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"brain-sqlite.d.ts","sourceRoot":"","sources":["../../src/store/brain-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAcjD,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAW5C;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAMrD;AAoED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,WAAW,CAAC,CAAC,CA0D9F;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAcnC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAexC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,GAAG,IAAI,CAEtD;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC;AACnC;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"brain-sqlite.d.ts","sourceRoot":"","sources":["../../src/store/brain-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAcjD,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAW5C;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAMrD;AAsED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,WAAW,CAAC,CAAC,CA0D9F;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAcnC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAexC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,GAAG,IAAI,CAEtD;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC;AACnC;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -40,7 +40,11 @@ export declare function createSafetyBackup(dbPath: string): void;
40
40
  * Handles three scenarios:
41
41
  * 1. Tables exist but no __drizzle_migrations — bootstrap baseline as applied
42
42
  * 2. Journal has orphaned hashes (from older CLEO version) — clear and re-mark all as applied
43
- * 3. Journal is healthy no-op
43
+ * 3. Journal exists but is missing entries for migrations whose DDL has already been applied
44
+ * (e.g., ALTER TABLE ADD COLUMN ran but journal entry was never written — happens when
45
+ * migrations are cherry-picked from worktrees or the process crashes mid-migration).
46
+ * Auto-inserts the missing journal entry so Drizzle skips the migration instead of
47
+ * re-running ALTER TABLE and crashing on "duplicate column name".
44
48
  *
45
49
  * @param nativeDb - Native SQLite database handle
46
50
  * @param migrationsFolder - Path to the drizzle migrations folder
@@ -48,13 +52,29 @@ export declare function createSafetyBackup(dbPath: string): void;
48
52
  * @param logSubsystem - Logger subsystem name for reconciliation warnings
49
53
  */
50
54
  export declare function reconcileJournal(nativeDb: DatabaseSync, migrationsFolder: string, existenceTable: string, logSubsystem: string): void;
55
+ /**
56
+ * Check whether an error is a SQLite "duplicate column name" error.
57
+ *
58
+ * These are thrown when an ALTER TABLE ADD COLUMN statement is re-executed
59
+ * after the column was already added (Scenario 3 in reconcileJournal).
60
+ */
61
+ export declare function isDuplicateColumnError(err: unknown): boolean;
51
62
  /**
52
63
  * Run Drizzle migrations with SQLITE_BUSY retry and exponential backoff.
53
64
  *
65
+ * Also handles "duplicate column name" errors (Scenario 3): if Drizzle tries to
66
+ * re-apply a migration whose DDL columns already exist (journal entry missing),
67
+ * this function calls reconcileJournal again to insert the missing entry and
68
+ * retries migrate() once more. This is the belt-and-suspenders safety net for
69
+ * any partial migration that slips through the proactive reconcileJournal check.
70
+ *
54
71
  * @param db - Drizzle database instance
55
72
  * @param migrationsFolder - Path to the drizzle migrations folder
73
+ * @param nativeDb - Optional native SQLite handle for duplicate-column auto-reconcile
74
+ * @param existenceTable - Optional existence-check table name for auto-reconcile
75
+ * @param logSubsystem - Optional logger subsystem name for auto-reconcile warnings
56
76
  */
57
- export declare function migrateWithRetry(db: NodeSQLiteDatabase<any>, migrationsFolder: string): void;
77
+ export declare function migrateWithRetry(db: NodeSQLiteDatabase<any>, migrationsFolder: string, nativeDb?: DatabaseSync, existenceTable?: string, logSubsystem?: string): void;
58
78
  /**
59
79
  * Ensure all required columns exist on a table.
60
80
  *
@@ -1 +1 @@
1
- {"version":3,"file":"migration-manager.d.ts","sourceRoot":"","sources":["../../src/store/migration-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;CACb;AAOD;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIlD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CASvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EACtB,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GACnB,IAAI,CA0CN;AAED;;;;;GAKG;AAEH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAgB5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,cAAc,EAAE,EACjC,YAAY,EAAE,MAAM,GACnB,IAAI,CAmBN"}
1
+ {"version":3,"file":"migration-manager.d.ts","sourceRoot":"","sources":["../../src/store/migration-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;CACb;AAOD;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIlD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CASvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EACtB,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GACnB,IAAI,CA8FN;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAG5D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAE9B,EAAE,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAC3B,gBAAgB,EAAE,MAAM,EACxB,QAAQ,CAAC,EAAE,YAAY,EACvB,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAkCN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,cAAc,EAAE,EACjC,YAAY,EAAE,MAAM,GACnB,IAAI,CAmBN"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,YAAY,IAAI,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrE,KAAK,YAAY,GAAG,iBAAiB,CAAC;AACtC,QAAA,MAAQ,YAAY,wGAC8D,YACjF,CAAC;AAKF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAYlE,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GACA,YAAY,CAiEd;AAKD,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAY7C;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAyHD;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,MAAM,CAAC,CAAC,CA2GpF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAQhD;AAED;;GAEG;AAGH,qDAAqD;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsEtD;;GAEG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAc9B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAcnC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ3E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,YAAY,GAAG,IAAI,CAEjD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,GAAG,IAAI,CAEtD;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC;AACnC;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBvD"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,YAAY,IAAI,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrE,KAAK,YAAY,GAAG,iBAAiB,CAAC;AACtC,QAAA,MAAQ,YAAY,wGAC8D,YACjF,CAAC;AAKF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAYlE,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GACA,YAAY,CAiEd;AAKD,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAY7C;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAyHD;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,MAAM,CAAC,CAAC,CA2GpF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAQhD;AAED;;GAEG;AAGH,qDAAqD;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAwEtD;;GAEG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAc9B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAcnC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ3E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,YAAY,GAAG,IAAI,CAEjD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,GAAG,IAAI,CAEtD;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC;AACnC;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/tasks/add.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/tasks/add.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/core",
3
- "version": "2026.4.18",
3
+ "version": "2026.4.19",
4
4
  "description": "CLEO core business logic kernel — tasks, sessions, memory, orchestration, lifecycle, with bundled SQLite store",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,12 +36,12 @@
36
36
  "write-file-atomic": "^7.0.1",
37
37
  "yaml": "^2.8.3",
38
38
  "zod": "^4.3.6",
39
- "@cleocode/adapters": "2026.4.18",
40
- "@cleocode/agents": "2026.4.18",
41
- "@cleocode/contracts": "2026.4.18",
42
- "@cleocode/lafs": "2026.4.18",
43
- "@cleocode/caamp": "2026.4.18",
44
- "@cleocode/skills": "2026.4.18"
39
+ "@cleocode/adapters": "2026.4.19",
40
+ "@cleocode/caamp": "2026.4.19",
41
+ "@cleocode/contracts": "2026.4.19",
42
+ "@cleocode/agents": "2026.4.19",
43
+ "@cleocode/lafs": "2026.4.19",
44
+ "@cleocode/skills": "2026.4.19"
45
45
  },
46
46
  "optionalDependencies": {
47
47
  "tree-sitter-c": "^0.24.1",
package/src/init.ts CHANGED
@@ -828,6 +828,73 @@ export async function initProject(opts: InitOptions = {}): Promise<InitResult> {
828
828
  );
829
829
  }
830
830
 
831
+ // T441: Deploy starter CANT bundle (team + agents) to project-tier .cleo/cant/
832
+ // This gives the CANT bridge a working team topology on first `cleoos` run.
833
+ // Only deploys if .cleo/cant/ does not already contain .cant files (idempotent).
834
+ try {
835
+ const cantDir = join(cleoDir, 'cant');
836
+ const cantAgentsDir = join(cantDir, 'agents');
837
+ const hasCantFiles =
838
+ existsSync(cantDir) &&
839
+ readdirSync(cantDir, { recursive: true }).some(
840
+ (f) => typeof f === 'string' && f.endsWith('.cant'),
841
+ );
842
+
843
+ if (!hasCantFiles) {
844
+ // Resolve the starter-bundle from @cleocode/cleo-os package
845
+ let starterBundleSrc: string | null = null;
846
+ try {
847
+ const { createRequire } = await import('node:module');
848
+ const req = createRequire(import.meta.url);
849
+ const cleoOsPkgMain = req.resolve('@cleocode/cleo-os/package.json');
850
+ const cleoOsPkgRoot = dirname(cleoOsPkgMain);
851
+ const candidate = join(cleoOsPkgRoot, 'starter-bundle');
852
+ if (existsSync(candidate)) {
853
+ starterBundleSrc = candidate;
854
+ }
855
+ } catch {
856
+ // Not resolvable via require.resolve — try workspace fallbacks
857
+ }
858
+
859
+ if (!starterBundleSrc) {
860
+ const packageRoot = getPackageRoot();
861
+ const fallbacks = [
862
+ join(packageRoot, '..', 'cleo-os', 'starter-bundle'),
863
+ join(packageRoot, '..', '..', 'packages', 'cleo-os', 'starter-bundle'),
864
+ ];
865
+ starterBundleSrc = fallbacks.find((p) => existsSync(p)) ?? null;
866
+ }
867
+
868
+ if (starterBundleSrc) {
869
+ await mkdir(cantDir, { recursive: true });
870
+ await mkdir(cantAgentsDir, { recursive: true });
871
+
872
+ // Copy team.cant
873
+ const teamSrc = join(starterBundleSrc, 'team.cant');
874
+ const teamDst = join(cantDir, 'team.cant');
875
+ if (existsSync(teamSrc) && !existsSync(teamDst)) {
876
+ await copyFile(teamSrc, teamDst);
877
+ }
878
+
879
+ // Copy agent .cant files
880
+ const agentsSrc = join(starterBundleSrc, 'agents');
881
+ if (existsSync(agentsSrc)) {
882
+ const agentFiles = readdirSync(agentsSrc).filter((f) => f.endsWith('.cant'));
883
+ for (const agentFile of agentFiles) {
884
+ const dst = join(cantAgentsDir, agentFile);
885
+ if (!existsSync(dst)) {
886
+ await copyFile(join(agentsSrc, agentFile), dst);
887
+ }
888
+ }
889
+ }
890
+
891
+ created.push('starter-bundle: team + agent .cant files deployed to .cleo/cant/');
892
+ }
893
+ }
894
+ } catch (err) {
895
+ warnings.push(`Starter bundle deploy: ${err instanceof Error ? err.message : String(err)}`);
896
+ }
897
+
831
898
  // T283: Optional install of canonical CleoOS seed agent personas
832
899
  if (opts.installSeedAgents) {
833
900
  try {
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Tests for Scenario 3 in reconcileJournal: auto-reconciling partially-applied
3
+ * migrations whose DDL columns already exist in the database but whose journal
4
+ * entry was never written.
5
+ *
6
+ * Root cause: T417 migration (brain_observations.agent column) was cherry-picked
7
+ * from a worktree. The ALTER TABLE succeeded but the journal INSERT never ran,
8
+ * causing every subsequent `cleo observe` / `cleo memory find` to crash with
9
+ * "duplicate column name".
10
+ *
11
+ * @task T417
12
+ */
13
+
14
+ import { mkdtemp, rm } from 'node:fs/promises';
15
+ import { tmpdir } from 'node:os';
16
+ import { dirname, join } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+
23
+ /** Resolve path to the drizzle-brain migrations folder relative to this test file. */
24
+ function getBrainMigrationsFolder(): string {
25
+ // Test lives at: packages/core/src/store/__tests__/
26
+ // Migrations at: packages/core/migrations/drizzle-brain/
27
+ return join(__dirname, '..', '..', '..', 'migrations', 'drizzle-brain');
28
+ }
29
+
30
+ describe('reconcileJournal — Scenario 3 (T417: partially-applied migration)', () => {
31
+ let tempDir: string;
32
+
33
+ beforeEach(async () => {
34
+ tempDir = await mkdtemp(join(tmpdir(), 'cleo-reconcile-'));
35
+ });
36
+
37
+ afterEach(async () => {
38
+ await rm(tempDir, { recursive: true, force: true });
39
+ });
40
+
41
+ it('inserts a journal entry when the column exists but the entry is missing', async () => {
42
+ const { openNativeDatabase } = await import('../sqlite.js');
43
+ const { reconcileJournal } = await import('../migration-manager.js');
44
+
45
+ const dbPath = join(tempDir, 'brain-partial.db');
46
+ const nativeDb = openNativeDatabase(dbPath);
47
+ const migrationsFolder = getBrainMigrationsFolder();
48
+
49
+ // Simulate a brain.db that had the initial migration applied properly (schema
50
+ // exists, journal has the baseline hash) but the T417 migration was applied
51
+ // outside the journal (column added but journal entry never written).
52
+
53
+ // 1. Create the full initial schema from the initial migration SQL directly
54
+ // (avoids pulling in the full Drizzle bootstrap; we just need the table).
55
+ nativeDb.exec(`
56
+ CREATE TABLE IF NOT EXISTS "brain_decisions" (
57
+ "id" text PRIMARY KEY,
58
+ "type" text NOT NULL,
59
+ "decision" text NOT NULL,
60
+ "rationale" text NOT NULL,
61
+ "confidence" text NOT NULL,
62
+ "outcome" text,
63
+ "alternatives_json" text,
64
+ "context_epic_id" text,
65
+ "context_task_id" text,
66
+ "context_phase" text,
67
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
68
+ "updated_at" text
69
+ );
70
+ CREATE TABLE IF NOT EXISTS "brain_observations" (
71
+ "id" text PRIMARY KEY,
72
+ "type" text NOT NULL,
73
+ "title" text NOT NULL,
74
+ "subtitle" text,
75
+ "narrative" text,
76
+ "facts_json" text,
77
+ "concepts_json" text,
78
+ "project" text,
79
+ "files_read_json" text,
80
+ "files_modified_json" text,
81
+ "source_session_id" text,
82
+ "source_type" text DEFAULT 'agent' NOT NULL,
83
+ "content_hash" text,
84
+ "discovery_tokens" integer,
85
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
86
+ "updated_at" text
87
+ );
88
+ `);
89
+
90
+ // 2. Manually apply the T417 ALTER TABLE (simulates cherry-pick from worktree).
91
+ nativeDb.exec('ALTER TABLE "brain_observations" ADD COLUMN "agent" text');
92
+
93
+ // 3. Create the journal table and insert ONLY the baseline + indexes migration
94
+ // hashes — intentionally omitting the T417 hash.
95
+ nativeDb.exec(`
96
+ CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
97
+ id SERIAL PRIMARY KEY,
98
+ hash text NOT NULL,
99
+ created_at numeric
100
+ )
101
+ `);
102
+ const { readMigrationFiles } = await import('drizzle-orm/migrator');
103
+ const allMigrations = readMigrationFiles({ migrationsFolder });
104
+
105
+ // Find the T417 migration by name
106
+ const t417 = allMigrations.find((m) => m.name.includes('t417'));
107
+ expect(t417).toBeDefined();
108
+
109
+ // Insert all migrations EXCEPT T417 into the journal
110
+ for (const m of allMigrations) {
111
+ if (m.hash === t417!.hash) continue;
112
+ nativeDb.exec(
113
+ `INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES ('${m.hash}', ${m.folderMillis})`,
114
+ );
115
+ }
116
+
117
+ // 4. Verify the journal does NOT contain the T417 entry before reconcile.
118
+ const beforeEntries = nativeDb
119
+ .prepare('SELECT hash FROM "__drizzle_migrations"')
120
+ .all() as Array<{ hash: string }>;
121
+ expect(beforeEntries.some((e) => e.hash === t417!.hash)).toBe(false);
122
+
123
+ // 5. Run reconcileJournal — Scenario 3 must detect and insert the T417 entry.
124
+ reconcileJournal(nativeDb, migrationsFolder, 'brain_decisions', 'brain');
125
+
126
+ // 6. Journal must now contain the T417 entry.
127
+ const afterEntries = nativeDb
128
+ .prepare('SELECT hash FROM "__drizzle_migrations"')
129
+ .all() as Array<{ hash: string }>;
130
+ expect(afterEntries.some((e) => e.hash === t417!.hash)).toBe(true);
131
+
132
+ nativeDb.close();
133
+ });
134
+
135
+ it('does NOT insert a journal entry when the column is genuinely missing', async () => {
136
+ const { openNativeDatabase } = await import('../sqlite.js');
137
+ const { reconcileJournal } = await import('../migration-manager.js');
138
+
139
+ const dbPath = join(tempDir, 'brain-no-col.db');
140
+ const nativeDb = openNativeDatabase(dbPath);
141
+ const migrationsFolder = getBrainMigrationsFolder();
142
+
143
+ // Create the schema WITHOUT the agent column.
144
+ nativeDb.exec(`
145
+ CREATE TABLE IF NOT EXISTS "brain_decisions" (
146
+ "id" text PRIMARY KEY,
147
+ "type" text NOT NULL,
148
+ "decision" text NOT NULL,
149
+ "rationale" text NOT NULL,
150
+ "confidence" text NOT NULL,
151
+ "outcome" text,
152
+ "created_at" text DEFAULT (datetime('now')) NOT NULL
153
+ );
154
+ CREATE TABLE IF NOT EXISTS "brain_observations" (
155
+ "id" text PRIMARY KEY,
156
+ "type" text NOT NULL,
157
+ "title" text NOT NULL,
158
+ "created_at" text DEFAULT (datetime('now')) NOT NULL
159
+ );
160
+ `);
161
+
162
+ // Create the journal with baseline migrations but NOT T417.
163
+ nativeDb.exec(`
164
+ CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
165
+ id SERIAL PRIMARY KEY,
166
+ hash text NOT NULL,
167
+ created_at numeric
168
+ )
169
+ `);
170
+ const { readMigrationFiles } = await import('drizzle-orm/migrator');
171
+ const allMigrations = readMigrationFiles({ migrationsFolder });
172
+ const t417 = allMigrations.find((m) => m.name.includes('t417'));
173
+ expect(t417).toBeDefined();
174
+
175
+ for (const m of allMigrations) {
176
+ if (m.hash === t417!.hash) continue;
177
+ nativeDb.exec(
178
+ `INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES ('${m.hash}', ${m.folderMillis})`,
179
+ );
180
+ }
181
+
182
+ // Run reconcileJournal — since the column does NOT exist, Scenario 3 must
183
+ // NOT insert the T417 entry (leave it for Drizzle to run normally).
184
+ reconcileJournal(nativeDb, migrationsFolder, 'brain_decisions', 'brain');
185
+
186
+ const entries = nativeDb.prepare('SELECT hash FROM "__drizzle_migrations"').all() as Array<{
187
+ hash: string;
188
+ }>;
189
+ expect(entries.some((e) => e.hash === t417!.hash)).toBe(false);
190
+
191
+ nativeDb.close();
192
+ });
193
+
194
+ it('migrateWithRetry does not throw after reconcileJournal inserts the missing entry', async () => {
195
+ const { openNativeDatabase } = await import('../sqlite.js');
196
+ const { reconcileJournal, migrateWithRetry } = await import('../migration-manager.js');
197
+ const { drizzle } = await import('drizzle-orm/node-sqlite');
198
+ const brainSchema = await import('../brain-schema.js');
199
+
200
+ const dbPath = join(tempDir, 'brain-full.db');
201
+ const nativeDb = openNativeDatabase(dbPath);
202
+ const migrationsFolder = getBrainMigrationsFolder();
203
+
204
+ // Build the complete brain schema with the agent column already present.
205
+ // This mirrors a real production brain.db after T417 was cherry-picked.
206
+ nativeDb.exec(`
207
+ CREATE TABLE IF NOT EXISTS "brain_decisions" (
208
+ "id" text PRIMARY KEY,
209
+ "type" text NOT NULL,
210
+ "decision" text NOT NULL,
211
+ "rationale" text NOT NULL,
212
+ "confidence" text NOT NULL,
213
+ "outcome" text,
214
+ "alternatives_json" text,
215
+ "context_epic_id" text,
216
+ "context_task_id" text,
217
+ "context_phase" text,
218
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
219
+ "updated_at" text
220
+ );
221
+ CREATE TABLE IF NOT EXISTS "brain_learnings" (
222
+ "id" text PRIMARY KEY,
223
+ "insight" text NOT NULL,
224
+ "source" text NOT NULL,
225
+ "confidence" real NOT NULL,
226
+ "actionable" integer DEFAULT false NOT NULL,
227
+ "application" text,
228
+ "applicable_types_json" text,
229
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
230
+ "updated_at" text
231
+ );
232
+ CREATE TABLE IF NOT EXISTS "brain_memory_links" (
233
+ "memory_type" text NOT NULL,
234
+ "memory_id" text NOT NULL,
235
+ "task_id" text NOT NULL,
236
+ "link_type" text NOT NULL,
237
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
238
+ CONSTRAINT "brain_memory_links_pk" PRIMARY KEY("memory_type", "memory_id", "task_id", "link_type")
239
+ );
240
+ CREATE TABLE IF NOT EXISTS "brain_observations" (
241
+ "id" text PRIMARY KEY,
242
+ "type" text NOT NULL,
243
+ "title" text NOT NULL,
244
+ "subtitle" text,
245
+ "narrative" text,
246
+ "facts_json" text,
247
+ "concepts_json" text,
248
+ "project" text,
249
+ "files_read_json" text,
250
+ "files_modified_json" text,
251
+ "source_session_id" text,
252
+ "source_type" text DEFAULT 'agent' NOT NULL,
253
+ "content_hash" text,
254
+ "discovery_tokens" integer,
255
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
256
+ "updated_at" text
257
+ );
258
+ CREATE TABLE IF NOT EXISTS "brain_page_edges" (
259
+ "from_id" text NOT NULL,
260
+ "to_id" text NOT NULL,
261
+ "edge_type" text NOT NULL,
262
+ "weight" real DEFAULT 1,
263
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
264
+ CONSTRAINT "brain_page_edges_pk" PRIMARY KEY("from_id", "to_id", "edge_type")
265
+ );
266
+ CREATE TABLE IF NOT EXISTS "brain_page_nodes" (
267
+ "id" text PRIMARY KEY,
268
+ "node_type" text NOT NULL,
269
+ "label" text NOT NULL,
270
+ "metadata_json" text,
271
+ "created_at" text DEFAULT (datetime('now')) NOT NULL
272
+ );
273
+ CREATE TABLE IF NOT EXISTS "brain_patterns" (
274
+ "id" text PRIMARY KEY,
275
+ "type" text NOT NULL,
276
+ "pattern" text NOT NULL,
277
+ "context" text NOT NULL,
278
+ "frequency" integer DEFAULT 1 NOT NULL,
279
+ "success_rate" real,
280
+ "impact" text,
281
+ "anti_pattern" text,
282
+ "mitigation" text,
283
+ "examples_json" text DEFAULT '[]',
284
+ "extracted_at" text DEFAULT (datetime('now')) NOT NULL,
285
+ "updated_at" text
286
+ );
287
+ CREATE TABLE IF NOT EXISTS "brain_schema_meta" (
288
+ "key" text PRIMARY KEY,
289
+ "value" text NOT NULL
290
+ );
291
+ CREATE TABLE IF NOT EXISTS "brain_sticky_notes" (
292
+ "id" text PRIMARY KEY,
293
+ "content" text NOT NULL,
294
+ "created_at" text DEFAULT (datetime('now')) NOT NULL,
295
+ "updated_at" text,
296
+ "tags_json" text,
297
+ "status" text DEFAULT 'active' NOT NULL,
298
+ "converted_to_json" text,
299
+ "color" text,
300
+ "priority" text,
301
+ "source_type" text DEFAULT 'sticky-note'
302
+ );
303
+ `);
304
+
305
+ // T417: agent column already applied out-of-band.
306
+ nativeDb.exec('ALTER TABLE "brain_observations" ADD COLUMN "agent" text');
307
+
308
+ // Journal: all migrations except T417.
309
+ nativeDb.exec(`
310
+ CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
311
+ id SERIAL PRIMARY KEY,
312
+ hash text NOT NULL,
313
+ created_at numeric
314
+ )
315
+ `);
316
+ const { readMigrationFiles } = await import('drizzle-orm/migrator');
317
+ const allMigrations = readMigrationFiles({ migrationsFolder });
318
+ const t417 = allMigrations.find((m) => m.name.includes('t417'));
319
+ expect(t417).toBeDefined();
320
+
321
+ for (const m of allMigrations) {
322
+ if (m.hash === t417!.hash) continue;
323
+ nativeDb.exec(
324
+ `INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES ('${m.hash}', ${m.folderMillis})`,
325
+ );
326
+ }
327
+
328
+ // reconcileJournal fills in the missing T417 entry.
329
+ reconcileJournal(nativeDb, migrationsFolder, 'brain_decisions', 'brain');
330
+
331
+ // migrateWithRetry must NOT throw "duplicate column name".
332
+ const db = drizzle(nativeDb, { schema: brainSchema });
333
+ expect(() =>
334
+ migrateWithRetry(db, migrationsFolder, nativeDb, 'brain_decisions', 'brain'),
335
+ ).not.toThrow();
336
+
337
+ nativeDb.close();
338
+ });
339
+
340
+ it('isDuplicateColumnError correctly identifies duplicate column errors', async () => {
341
+ const { isDuplicateColumnError } = await import('../migration-manager.js');
342
+
343
+ expect(isDuplicateColumnError(new Error('duplicate column name: agent'))).toBe(true);
344
+ expect(isDuplicateColumnError(new Error('DUPLICATE COLUMN NAME: foo'))).toBe(true);
345
+ expect(isDuplicateColumnError(new Error('Duplicate column name in table'))).toBe(true);
346
+ expect(isDuplicateColumnError(new Error('SQLITE_BUSY: database is locked'))).toBe(false);
347
+ expect(isDuplicateColumnError(new Error('no such table: tasks'))).toBe(false);
348
+ expect(isDuplicateColumnError('not an error')).toBe(false);
349
+ expect(isDuplicateColumnError(null)).toBe(false);
350
+ });
351
+ });
@@ -92,8 +92,10 @@ function runBrainMigrations(
92
92
  // Bootstrap baseline + reconcile stale journal entries
93
93
  reconcileJournal(nativeDb, migrationsFolder, 'brain_decisions', 'brain');
94
94
 
95
- // Run pending migrations with SQLITE_BUSY retry
96
- migrateWithRetry(db, migrationsFolder);
95
+ // Run pending migrations with SQLITE_BUSY retry.
96
+ // Pass nativeDb + existenceTable so migrateWithRetry can auto-reconcile any
97
+ // partial migration (Scenario 3) that slips through the proactive check above.
98
+ migrateWithRetry(db, migrationsFolder, nativeDb, 'brain_decisions', 'brain');
97
99
  }
98
100
 
99
101
  /**