@cleocode/core 2026.4.18 → 2026.4.20
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/init.d.ts.map
CHANGED
|
@@ -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,
|
|
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;
|
|
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
|
|
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
|
|
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;
|
|
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"}
|
package/dist/tasks/add.d.ts.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "2026.4.20",
|
|
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.
|
|
40
|
-
"@cleocode/agents": "2026.4.
|
|
41
|
-
"@cleocode/
|
|
42
|
-
"@cleocode/
|
|
43
|
-
"@cleocode/
|
|
44
|
-
"@cleocode/skills": "2026.4.
|
|
39
|
+
"@cleocode/adapters": "2026.4.20",
|
|
40
|
+
"@cleocode/agents": "2026.4.20",
|
|
41
|
+
"@cleocode/caamp": "2026.4.20",
|
|
42
|
+
"@cleocode/contracts": "2026.4.20",
|
|
43
|
+
"@cleocode/lafs": "2026.4.20",
|
|
44
|
+
"@cleocode/skills": "2026.4.20"
|
|
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
|
|
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
|
/**
|