sqlew 3.5.3 → 3.6.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.
Files changed (257) hide show
  1. package/CHANGELOG.md +247 -1772
  2. package/README.md +70 -304
  3. package/assets/config.example.toml +106 -0
  4. package/dist/adapters/index.d.ts +11 -0
  5. package/dist/adapters/index.d.ts.map +1 -0
  6. package/dist/adapters/index.js +21 -0
  7. package/dist/adapters/index.js.map +1 -0
  8. package/dist/adapters/mysql-adapter.d.ts +31 -0
  9. package/dist/adapters/mysql-adapter.d.ts.map +1 -0
  10. package/dist/adapters/mysql-adapter.js +63 -0
  11. package/dist/adapters/mysql-adapter.js.map +1 -0
  12. package/dist/adapters/postgresql-adapter.d.ts +31 -0
  13. package/dist/adapters/postgresql-adapter.d.ts.map +1 -0
  14. package/dist/adapters/postgresql-adapter.js +63 -0
  15. package/dist/adapters/postgresql-adapter.js.map +1 -0
  16. package/dist/adapters/sqlite-adapter.d.ts +37 -0
  17. package/dist/adapters/sqlite-adapter.d.ts.map +1 -0
  18. package/dist/adapters/sqlite-adapter.js +129 -0
  19. package/dist/adapters/sqlite-adapter.js.map +1 -0
  20. package/dist/adapters/types.d.ts +33 -0
  21. package/dist/adapters/types.d.ts.map +1 -0
  22. package/dist/adapters/types.js +2 -0
  23. package/dist/adapters/types.js.map +1 -0
  24. package/dist/cli.js +55 -54
  25. package/dist/cli.js.map +1 -1
  26. package/dist/config/example-generator.d.ts +11 -0
  27. package/dist/config/example-generator.d.ts.map +1 -0
  28. package/dist/config/example-generator.js +48 -0
  29. package/dist/config/example-generator.js.map +1 -0
  30. package/dist/config/loader.d.ts.map +1 -1
  31. package/dist/config/loader.js +4 -0
  32. package/dist/config/loader.js.map +1 -1
  33. package/dist/config/types.d.ts +11 -0
  34. package/dist/config/types.d.ts.map +1 -1
  35. package/dist/config/types.js.map +1 -1
  36. package/dist/database.d.ts +56 -121
  37. package/dist/database.d.ts.map +1 -1
  38. package/dist/database.js +266 -414
  39. package/dist/database.js.map +1 -1
  40. package/dist/index.js +329 -245
  41. package/dist/index.js.map +1 -1
  42. package/dist/knexfile.d.ts +6 -0
  43. package/dist/knexfile.d.ts.map +1 -0
  44. package/dist/knexfile.js +85 -0
  45. package/dist/knexfile.js.map +1 -0
  46. package/dist/migrations/add-help-system-tables.d.ts +35 -0
  47. package/dist/migrations/add-help-system-tables.d.ts.map +1 -0
  48. package/dist/migrations/add-help-system-tables.js +206 -0
  49. package/dist/migrations/add-help-system-tables.js.map +1 -0
  50. package/dist/migrations/add-token-tracking.d.ts +28 -0
  51. package/dist/migrations/add-token-tracking.d.ts.map +1 -0
  52. package/dist/migrations/add-token-tracking.js +108 -0
  53. package/dist/migrations/add-token-tracking.js.map +1 -0
  54. package/dist/migrations/index.d.ts +25 -12
  55. package/dist/migrations/index.d.ts.map +1 -1
  56. package/dist/migrations/index.js +147 -20
  57. package/dist/migrations/index.js.map +1 -1
  58. package/dist/migrations/knex/20251025020452_create_master_tables.d.ts +4 -0
  59. package/dist/migrations/knex/20251025020452_create_master_tables.d.ts.map +1 -0
  60. package/dist/migrations/knex/20251025020452_create_master_tables.js +65 -0
  61. package/dist/migrations/knex/20251025020452_create_master_tables.js.map +1 -0
  62. package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts +4 -0
  63. package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts.map +1 -0
  64. package/dist/migrations/knex/20251025021152_create_transaction_tables.js +235 -0
  65. package/dist/migrations/knex/20251025021152_create_transaction_tables.js.map +1 -0
  66. package/dist/migrations/knex/20251025021351_create_indexes.d.ts +4 -0
  67. package/dist/migrations/knex/20251025021351_create_indexes.d.ts.map +1 -0
  68. package/dist/migrations/knex/20251025021351_create_indexes.js +62 -0
  69. package/dist/migrations/knex/20251025021351_create_indexes.js.map +1 -0
  70. package/dist/migrations/knex/20251025021416_seed_master_data.d.ts +4 -0
  71. package/dist/migrations/knex/20251025021416_seed_master_data.d.ts.map +1 -0
  72. package/dist/migrations/knex/20251025021416_seed_master_data.js +58 -0
  73. package/dist/migrations/knex/20251025021416_seed_master_data.js.map +1 -0
  74. package/dist/migrations/knex/20251025070349_create_views.d.ts +4 -0
  75. package/dist/migrations/knex/20251025070349_create_views.d.ts.map +1 -0
  76. package/dist/migrations/knex/20251025070349_create_views.js +143 -0
  77. package/dist/migrations/knex/20251025070349_create_views.js.map +1 -0
  78. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts +4 -0
  79. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts.map +1 -0
  80. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js +15 -0
  81. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js.map +1 -0
  82. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts +8 -0
  83. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts.map +1 -0
  84. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js +12 -0
  85. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js.map +1 -0
  86. package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts +19 -0
  87. package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts.map +1 -0
  88. package/dist/migrations/knex/20251025090000_create_help_system_tables.js +115 -0
  89. package/dist/migrations/knex/20251025090000_create_help_system_tables.js.map +1 -0
  90. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts +13 -0
  91. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts.map +1 -0
  92. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js +377 -0
  93. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js.map +1 -0
  94. package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts +15 -0
  95. package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts.map +1 -0
  96. package/dist/migrations/knex/20251025100000_seed_help_metadata.js +253 -0
  97. package/dist/migrations/knex/20251025100000_seed_help_metadata.js.map +1 -0
  98. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts +16 -0
  99. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts.map +1 -0
  100. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js +276 -0
  101. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js.map +1 -0
  102. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts +8 -0
  103. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts.map +1 -0
  104. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js +64 -0
  105. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js.map +1 -0
  106. package/dist/migrations/knex/20251027000000_add_agent_reuse_system.d.ts +14 -0
  107. package/dist/migrations/knex/20251027000000_add_agent_reuse_system.d.ts.map +1 -0
  108. package/dist/migrations/knex/20251027000000_add_agent_reuse_system.js +34 -0
  109. package/dist/migrations/knex/20251027000000_add_agent_reuse_system.js.map +1 -0
  110. package/dist/migrations/knex/20251027010000_add_task_constraint_to_decision_context.d.ts +4 -0
  111. package/dist/migrations/knex/20251027010000_add_task_constraint_to_decision_context.d.ts.map +1 -0
  112. package/dist/migrations/knex/20251027010000_add_task_constraint_to_decision_context.js +24 -0
  113. package/dist/migrations/knex/20251027010000_add_task_constraint_to_decision_context.js.map +1 -0
  114. package/dist/migrations/knex/20251027020000_update_agent_reusability.d.ts +16 -0
  115. package/dist/migrations/knex/20251027020000_update_agent_reusability.d.ts.map +1 -0
  116. package/dist/migrations/knex/20251027020000_update_agent_reusability.js +27 -0
  117. package/dist/migrations/knex/20251027020000_update_agent_reusability.js.map +1 -0
  118. package/dist/migrations/seed-help-data.d.ts +48 -0
  119. package/dist/migrations/seed-help-data.d.ts.map +1 -0
  120. package/dist/migrations/seed-help-data.js +1466 -0
  121. package/dist/migrations/seed-help-data.js.map +1 -0
  122. package/dist/migrations/seed-tool-metadata.d.ts +24 -0
  123. package/dist/migrations/seed-tool-metadata.d.ts.map +1 -0
  124. package/dist/migrations/seed-tool-metadata.js +392 -0
  125. package/dist/migrations/seed-tool-metadata.js.map +1 -0
  126. package/dist/migrations/v3.6.0-help-system-refactor.d.ts +46 -0
  127. package/dist/migrations/v3.6.0-help-system-refactor.d.ts.map +1 -0
  128. package/dist/migrations/v3.6.0-help-system-refactor.js +223 -0
  129. package/dist/migrations/v3.6.0-help-system-refactor.js.map +1 -0
  130. package/dist/schema.d.ts.map +1 -1
  131. package/dist/schema.js +2 -0
  132. package/dist/schema.js.map +1 -1
  133. package/dist/tests/agent-reuse.test.d.ts +6 -0
  134. package/dist/tests/agent-reuse.test.d.ts.map +1 -0
  135. package/dist/tests/agent-reuse.test.js +242 -0
  136. package/dist/tests/agent-reuse.test.js.map +1 -0
  137. package/dist/tests/all-features.test.d.ts +7 -0
  138. package/dist/tests/all-features.test.d.ts.map +1 -0
  139. package/dist/tests/all-features.test.js +514 -0
  140. package/dist/tests/all-features.test.js.map +1 -0
  141. package/dist/tests/git-aware-completion.test.js +89 -70
  142. package/dist/tests/git-aware-completion.test.js.map +1 -1
  143. package/dist/tests/help-system.test.d.ts +23 -0
  144. package/dist/tests/help-system.test.d.ts.map +1 -0
  145. package/dist/tests/help-system.test.js +374 -0
  146. package/dist/tests/help-system.test.js.map +1 -0
  147. package/dist/tests/tasks.auto-pruning-decision-link.test.js +92 -78
  148. package/dist/tests/tasks.auto-pruning-decision-link.test.js.map +1 -1
  149. package/dist/tests/tasks.auto-pruning-partial.test.js +106 -95
  150. package/dist/tests/tasks.auto-pruning-partial.test.js.map +1 -1
  151. package/dist/tests/tasks.auto-pruning-persistence.test.js +115 -97
  152. package/dist/tests/tasks.auto-pruning-persistence.test.js.map +1 -1
  153. package/dist/tests/tasks.auto-pruning-safety.test.js +124 -103
  154. package/dist/tests/tasks.auto-pruning-safety.test.js.map +1 -1
  155. package/dist/tests/tasks.dependencies.test.js +338 -307
  156. package/dist/tests/tasks.dependencies.test.js.map +1 -1
  157. package/dist/tests/tasks.link-file-backward-compat.test.js +116 -104
  158. package/dist/tests/tasks.link-file-backward-compat.test.js.map +1 -1
  159. package/dist/tests/tasks.watch-files-action.test.js +122 -101
  160. package/dist/tests/tasks.watch-files-action.test.js.map +1 -1
  161. package/dist/tests/tasks.watch-files-parameter.test.js +105 -94
  162. package/dist/tests/tasks.watch-files-parameter.test.js.map +1 -1
  163. package/dist/tests/two-step-git-completion.test.js +176 -133
  164. package/dist/tests/two-step-git-completion.test.js.map +1 -1
  165. package/dist/tests/vcs-staging.test.js +1 -1
  166. package/dist/tests/vcs-staging.test.js.map +1 -1
  167. package/dist/tools/config.d.ts +9 -6
  168. package/dist/tools/config.d.ts.map +1 -1
  169. package/dist/tools/config.js +16 -14
  170. package/dist/tools/config.js.map +1 -1
  171. package/dist/tools/constraints.d.ts +10 -7
  172. package/dist/tools/constraints.d.ts.map +1 -1
  173. package/dist/tools/constraints.js +73 -51
  174. package/dist/tools/constraints.js.map +1 -1
  175. package/dist/tools/context.d.ts +36 -33
  176. package/dist/tools/context.d.ts.map +1 -1
  177. package/dist/tools/context.js +441 -340
  178. package/dist/tools/context.js.map +1 -1
  179. package/dist/tools/files.d.ts +12 -9
  180. package/dist/tools/files.d.ts.map +1 -1
  181. package/dist/tools/files.js +173 -95
  182. package/dist/tools/files.js.map +1 -1
  183. package/dist/tools/help-queries.d.ts +130 -0
  184. package/dist/tools/help-queries.d.ts.map +1 -0
  185. package/dist/tools/help-queries.js +393 -0
  186. package/dist/tools/help-queries.js.map +1 -0
  187. package/dist/tools/messaging.d.ts +14 -11
  188. package/dist/tools/messaging.d.ts.map +1 -1
  189. package/dist/tools/messaging.js +239 -133
  190. package/dist/tools/messaging.js.map +1 -1
  191. package/dist/tools/tasks.d.ts +18 -16
  192. package/dist/tools/tasks.d.ts.map +1 -1
  193. package/dist/tools/tasks.js +519 -442
  194. package/dist/tools/tasks.js.map +1 -1
  195. package/dist/tools/utils.d.ts +14 -11
  196. package/dist/tools/utils.d.ts.map +1 -1
  197. package/dist/tools/utils.js +90 -122
  198. package/dist/tools/utils.js.map +1 -1
  199. package/dist/types.d.ts +1 -0
  200. package/dist/types.d.ts.map +1 -1
  201. package/dist/utils/activity-logging.d.ts +114 -0
  202. package/dist/utils/activity-logging.d.ts.map +1 -0
  203. package/dist/utils/activity-logging.js +162 -0
  204. package/dist/utils/activity-logging.js.map +1 -0
  205. package/dist/utils/batch.d.ts +2 -2
  206. package/dist/utils/batch.d.ts.map +1 -1
  207. package/dist/utils/batch.js +8 -8
  208. package/dist/utils/batch.js.map +1 -1
  209. package/dist/utils/cleanup.d.ts +24 -14
  210. package/dist/utils/cleanup.d.ts.map +1 -1
  211. package/dist/utils/cleanup.js +37 -27
  212. package/dist/utils/cleanup.js.map +1 -1
  213. package/dist/utils/debug-logger.d.ts +99 -0
  214. package/dist/utils/debug-logger.d.ts.map +1 -0
  215. package/dist/utils/debug-logger.js +267 -0
  216. package/dist/utils/debug-logger.js.map +1 -0
  217. package/dist/utils/error-handler.d.ts +28 -0
  218. package/dist/utils/error-handler.d.ts.map +1 -0
  219. package/dist/utils/error-handler.js +121 -0
  220. package/dist/utils/error-handler.js.map +1 -0
  221. package/dist/utils/help-tracking.d.ts +55 -0
  222. package/dist/utils/help-tracking.d.ts.map +1 -0
  223. package/dist/utils/help-tracking.js +88 -0
  224. package/dist/utils/help-tracking.js.map +1 -0
  225. package/dist/utils/param-parser.d.ts +23 -0
  226. package/dist/utils/param-parser.d.ts.map +1 -0
  227. package/dist/utils/param-parser.js +52 -0
  228. package/dist/utils/param-parser.js.map +1 -0
  229. package/dist/utils/retention.d.ts +17 -7
  230. package/dist/utils/retention.d.ts.map +1 -1
  231. package/dist/utils/retention.js +31 -12
  232. package/dist/utils/retention.js.map +1 -1
  233. package/dist/utils/task-stale-detection.d.ts +15 -13
  234. package/dist/utils/task-stale-detection.d.ts.map +1 -1
  235. package/dist/utils/task-stale-detection.js +100 -302
  236. package/dist/utils/task-stale-detection.js.map +1 -1
  237. package/dist/utils/token-estimation.d.ts +72 -0
  238. package/dist/utils/token-estimation.d.ts.map +1 -0
  239. package/dist/utils/token-estimation.js +71 -0
  240. package/dist/utils/token-estimation.js.map +1 -0
  241. package/dist/utils/token-logging.d.ts +48 -0
  242. package/dist/utils/token-logging.d.ts.map +1 -0
  243. package/dist/utils/token-logging.js +112 -0
  244. package/dist/utils/token-logging.js.map +1 -0
  245. package/dist/utils/view-queries.d.ts +34 -0
  246. package/dist/utils/view-queries.d.ts.map +1 -0
  247. package/dist/utils/view-queries.js +192 -0
  248. package/dist/utils/view-queries.js.map +1 -0
  249. package/dist/watcher/file-watcher.d.ts.map +1 -1
  250. package/dist/watcher/file-watcher.js +25 -11
  251. package/dist/watcher/file-watcher.js.map +1 -1
  252. package/docs/BEST_PRACTICES.md +56 -448
  253. package/docs/MIGRATION_v3.6.0.md +170 -0
  254. package/docs/SHARED_CONCEPTS.md +63 -208
  255. package/docs/TASK_OVERVIEW.md +2 -2
  256. package/docs/TOOL_SELECTION.md +41 -248
  257. package/package.json +17 -4
package/dist/database.js CHANGED
@@ -1,245 +1,201 @@
1
1
  /**
2
2
  * Database connection and initialization module
3
- * Handles SQLite database setup with configurable path
3
+ * Handles database setup with Knex.js and DatabaseAdapter pattern
4
4
  */
5
- import Database from 'better-sqlite3';
6
- import { mkdirSync, existsSync } from 'fs';
7
- import { dirname, resolve, isAbsolute } from 'path';
8
- import { initializeSchema, isSchemaInitialized, verifySchemaIntegrity } from './schema.js';
9
- import { DEFAULT_DB_PATH, DB_BUSY_TIMEOUT } from './constants.js';
10
- import { performAutoCleanup } from './utils/cleanup.js';
11
- import { runAllMigrations, needsAnyMigrations } from './migrations/index.js';
12
- import { detectAndTransitionStaleTasks, autoArchiveOldDoneTasks, detectAndTransitionToReview } from './utils/task-stale-detection.js';
13
- import { loadConfigFile, loadAndFlattenConfig, validateConfig } from './config/loader.js';
14
- let dbInstance = null;
5
+ import knexConfig from './knexfile.js';
6
+ import { createDatabaseAdapter } from './adapters/index.js';
7
+ // Built-in Claude Code agent types that should be normalized/pooled
8
+ const BUILTIN_AGENT_TYPES = [
9
+ 'general-purpose',
10
+ 'statusline-setup',
11
+ 'output-style-setup',
12
+ 'Explore'
13
+ ];
14
+ // Global adapter instance
15
+ let adapterInstance = null;
15
16
  /**
16
- * Initialize database connection
17
- * Creates database file and folder if they don't exist
18
- * Initializes schema on first run
19
- *
20
- * @param dbPath - Optional database path (defaults to .sqlew/sqlew.db)
21
- * @returns SQLite database instance
17
+ * Initialize database with adapter pattern
22
18
  */
23
- export function initializeDatabase(dbPath, configPath) {
24
- // If already initialized, return existing instance
25
- if (dbInstance) {
26
- return dbInstance;
19
+ export async function initializeDatabase(config) {
20
+ if (adapterInstance) {
21
+ return adapterInstance;
27
22
  }
28
- try {
29
- // Load config file to get database path and other settings
30
- const config = loadConfigFile(configPath);
31
- // Validate config
32
- const validation = validateConfig(config);
33
- if (!validation.valid) {
34
- console.warn('⚠️ Configuration validation warnings:');
35
- validation.errors.forEach(err => console.warn(` - ${err}`));
36
- }
37
- // Determine database path with priority:
38
- // 1. CLI argument (dbPath parameter)
39
- // 2. Config file (config.database.path)
40
- // 3. Default path (DEFAULT_DB_PATH)
41
- const finalPath = dbPath || config.database?.path || DEFAULT_DB_PATH;
42
- // Convert to absolute path if relative
43
- const absolutePath = isAbsolute(finalPath)
44
- ? finalPath
45
- : resolve(process.cwd(), finalPath);
46
- // Create directory if it doesn't exist
47
- const dbDir = dirname(absolutePath);
48
- if (!existsSync(dbDir)) {
49
- mkdirSync(dbDir, { recursive: true });
50
- console.log(`✓ Created database directory: ${dbDir}`);
51
- }
52
- // Open database connection
53
- const db = new Database(absolutePath, {
54
- verbose: process.env.DEBUG_SQL ? console.log : undefined,
55
- });
56
- // Configure database
57
- db.pragma('journal_mode = WAL'); // Write-Ahead Logging for better concurrency
58
- db.pragma('foreign_keys = ON'); // Enforce foreign key constraints
59
- db.pragma('synchronous = NORMAL'); // Balance between safety and performance
60
- db.pragma(`busy_timeout = ${DB_BUSY_TIMEOUT}`); // Set busy timeout
61
- console.log(`✓ Connected to database: ${absolutePath}`);
62
- // Check if database has existing schema
63
- const schemaExists = isSchemaInitialized(db);
64
- if (schemaExists) {
65
- // Run all pending migrations using orchestrator
66
- // This handles upgrades from any version (v1.0.0, v1.1.x, v2.0.0, v2.1.x) to latest
67
- if (needsAnyMigrations(db)) {
68
- const migrationResults = runAllMigrations(db);
69
- // Check if any migration failed
70
- const failed = migrationResults.find(r => !r.success);
71
- if (failed) {
72
- console.error('\n❌ ERROR: Migration failed!');
73
- console.error(failed.message);
74
- db.close();
75
- process.exit(1);
76
- }
77
- // After table prefix migration, run schema initialization to create views/triggers
78
- // (tables already exist, CREATE TABLE IF NOT EXISTS will skip them)
79
- const hadPrefixMigration = migrationResults.some(r => r.message.toLowerCase().includes('table prefix') ||
80
- r.message.toLowerCase().includes('prefix'));
81
- if (hadPrefixMigration) {
82
- console.log('\n→ Creating views and triggers for new schema...');
83
- initializeSchema(db);
84
- }
85
- }
86
- // Validate existing schema integrity (after migrations)
87
- console.log('→ Validating existing database schema...');
88
- const validation = verifySchemaIntegrity(db);
89
- if (!validation.valid) {
90
- // Schema is invalid - display error and exit
91
- console.error('\n❌ ERROR: Database schema validation failed!');
92
- console.error('\nThe existing database file has an incompatible schema.');
93
- console.error(`Database location: ${absolutePath}`);
94
- if (validation.missing.length > 0) {
95
- console.error('\n📋 Missing components:');
96
- validation.missing.forEach(item => console.error(` - ${item}`));
97
- }
98
- if (validation.errors.length > 0) {
99
- console.error('\n⚠️ Validation errors:');
100
- validation.errors.forEach(error => console.error(` - ${error}`));
101
- }
102
- console.error('\n💡 Possible solutions:');
103
- console.error(' 1. Backup and delete the existing database file to start fresh');
104
- console.error(' 2. Use a different database path with --db-path option');
105
- console.error(' 3. Restore from a backup if available\n');
106
- // Close database and exit
107
- db.close();
108
- process.exit(1);
109
- }
110
- console.log('✓ Database schema validation passed');
111
- }
112
- else {
113
- // Initialize new schema
114
- console.log('→ Initializing database schema...');
115
- initializeSchema(db);
116
- }
117
- // Populate database with config file values (if any)
118
- try {
119
- const flatConfig = loadAndFlattenConfig(configPath);
120
- let configUpdates = 0;
121
- for (const [key, value] of Object.entries(flatConfig)) {
122
- if (value !== undefined) {
123
- const stringValue = typeof value === 'boolean' ? (value ? '1' : '0') : String(value);
124
- db.prepare('INSERT OR REPLACE INTO m_config (key, value) VALUES (?, ?)').run(key, stringValue);
125
- configUpdates++;
126
- }
127
- }
128
- if (configUpdates > 0) {
129
- console.log(`✓ Loaded ${configUpdates} config values from file`);
130
- }
131
- }
132
- catch (error) {
133
- console.warn('⚠️ Failed to load config file values:', error instanceof Error ? error.message : String(error));
134
- }
135
- // Initialize v3.4.0 config keys (git-aware auto-complete)
136
- // Use INSERT OR IGNORE to avoid overwriting existing values
137
- const v340Configs = [
138
- { key: 'git_auto_complete_enabled', value: '1' },
139
- { key: 'require_all_files_committed', value: '1' },
140
- { key: 'stale_review_notification_hours', value: '48' },
141
- ];
142
- const configInsert = db.prepare('INSERT OR IGNORE INTO m_config (key, value) VALUES (?, ?)');
143
- for (const config of v340Configs) {
144
- configInsert.run(config.key, config.value);
145
- }
146
- // Initialize v3.5.2 config keys (two-step git-aware workflow)
147
- // Use INSERT OR IGNORE to avoid overwriting existing values
148
- const v352Configs = [
149
- { key: 'git_auto_complete_on_stage', value: '1' }, // Auto-complete on git add
150
- { key: 'git_auto_archive_on_commit', value: '1' }, // Auto-archive on git commit
151
- { key: 'require_all_files_staged', value: '1' }, // Require ALL files staged (vs ANY)
152
- { key: 'require_all_files_committed_for_archive', value: '1' }, // Require ALL files committed for archiving
153
- ];
154
- for (const config of v352Configs) {
155
- configInsert.run(config.key, config.value);
156
- }
157
- // Store instance
158
- dbInstance = db;
159
- // Perform initial cleanup and task maintenance
160
- // Run synchronous tasks first
161
- const staleTransitions = detectAndTransitionStaleTasks(db);
162
- const archivedTasks = autoArchiveOldDoneTasks(db);
163
- const cleanupResult = performAutoCleanup(db);
164
- // Run async smart review detection in background (non-blocking)
165
- detectAndTransitionToReview(db).then(reviewTransitions => {
166
- const taskMaintenance = [];
167
- if (staleTransitions > 0)
168
- taskMaintenance.push(`${staleTransitions} stale task transitions`);
169
- if (archivedTasks > 0)
170
- taskMaintenance.push(`${archivedTasks} done tasks archived`);
171
- if (reviewTransitions > 0)
172
- taskMaintenance.push(`${reviewTransitions} quality-based review transitions`);
173
- if (cleanupResult.messagesDeleted > 0)
174
- taskMaintenance.push(`${cleanupResult.messagesDeleted} messages`);
175
- if (cleanupResult.fileChangesDeleted > 0)
176
- taskMaintenance.push(`${cleanupResult.fileChangesDeleted} file changes`);
177
- if (taskMaintenance.length > 0) {
178
- console.log(`✓ Cleanup: ${taskMaintenance.join(', ')}`);
179
- }
180
- }).catch(error => {
181
- console.warn('⚠️ Initial cleanup failed:', error instanceof Error ? error.message : String(error));
182
- });
183
- return db;
184
- }
185
- catch (error) {
186
- const message = error instanceof Error ? error.message : String(error);
187
- throw new Error(`Failed to initialize database: ${message}`);
23
+ const dbType = config?.databaseType || 'sqlite';
24
+ const adapter = createDatabaseAdapter(dbType);
25
+ // Determine if running from compiled code (dist/) or source (src/)
26
+ const isCompiledCode = import.meta.url.includes('/dist/');
27
+ const environment = isCompiledCode ? 'production' : 'development';
28
+ // Use config from knexfile or provided config
29
+ const baseConfig = knexConfig[environment] || knexConfig.development;
30
+ const knexConnConfig = config?.connection
31
+ ? { ...baseConfig, connection: config.connection }
32
+ : baseConfig;
33
+ await adapter.connect(knexConnConfig);
34
+ // Run migrations if needed
35
+ const knex = adapter.getKnex();
36
+ await knex.migrate.latest();
37
+ console.log(`✓ Database initialized with Knex adapter (${environment})`);
38
+ adapterInstance = adapter;
39
+ return adapter;
40
+ }
41
+ /**
42
+ * Get current database adapter
43
+ */
44
+ export function getAdapter() {
45
+ if (!adapterInstance) {
46
+ throw new Error('Database not initialized. Call initializeDatabase() first.');
188
47
  }
48
+ return adapterInstance;
189
49
  }
190
50
  /**
191
51
  * Close database connection
192
52
  */
193
- export function closeDatabase() {
194
- if (dbInstance) {
195
- dbInstance.close();
196
- dbInstance = null;
53
+ export async function closeDatabase() {
54
+ if (adapterInstance) {
55
+ await adapterInstance.disconnect();
56
+ adapterInstance = null;
197
57
  console.log('✓ Database connection closed');
198
58
  }
199
59
  }
60
+ // ============================================================================
61
+ // Helper Functions for Master Table Management (Async)
62
+ // ============================================================================
200
63
  /**
201
- * Get current database instance
202
- * Throws error if not initialized
64
+ * Determines if an agent name is user-specified (meaningful) or generic
203
65
  *
204
- * @returns Current database instance
66
+ * Agent Classification:
67
+ * - System agents: 'system', 'migration-manager' (not reusable, permanently protected)
68
+ * - Built-in agents: Claude Code default agents like 'Explore' (reusable, normalized)
69
+ * - User-defined agents: Custom agents like 'rust-architecture-expert' (reusable, exact name preserved)
70
+ * - Generic pool: Empty names or 'generic-N' pattern (reusable, automatically allocated)
71
+ *
72
+ * @returns true if agent should NOT be reusable (system agents only)
205
73
  */
206
- export function getDatabase() {
207
- if (!dbInstance) {
208
- throw new Error('Database not initialized. Call initializeDatabase() first.');
74
+ function isUserSpecifiedAgent(name) {
75
+ // Empty names use generic pool (reusable)
76
+ if (!name || name.trim().length === 0) {
77
+ return false;
78
+ }
79
+ // Core system agents that should NOT be reusable
80
+ const systemAgents = ['system', 'migration-manager'];
81
+ if (systemAgents.includes(name.toLowerCase())) {
82
+ return true;
83
+ }
84
+ // Built-in Claude Code agents (reusable, normalized)
85
+ if (BUILTIN_AGENT_TYPES.includes(name)) {
86
+ return false;
209
87
  }
210
- return dbInstance;
88
+ // Everything else (user-defined agents, generic-N patterns) is reusable
89
+ return false;
211
90
  }
212
- // ============================================================================
213
- // Helper Functions for Master Table Management
214
- // ============================================================================
215
91
  /**
216
- * Get or create agent by name
217
- * Uses INSERT OR IGNORE for idempotent operation
92
+ * Get or create agent by name with reuse logic
218
93
  *
219
- * @param db - Database instance
220
- * @param name - Agent name
221
- * @returns Agent ID
94
+ * - Empty/whitespace names: Allocate reusable slot (find inactive or create new generic-N)
95
+ * - Named generic agents (e.g., 'generic-5', 'agent-123'): Create with exact name, mark reusable
96
+ * - User-specified agents (e.g., 'rust-expert'): Create permanently, non-reusable
222
97
  */
223
- export function getOrCreateAgent(db, name) {
224
- // Try to insert
225
- db.prepare('INSERT OR IGNORE INTO m_agents (name) VALUES (?)').run(name);
98
+ export async function getOrCreateAgent(adapter, name, trx) {
99
+ const knex = trx || adapter.getKnex();
100
+ const now = Math.floor(Date.now() / 1000);
101
+ // Empty name: allocate a reusable generic slot
102
+ if (!name || name.trim().length === 0) {
103
+ // Try to reuse an inactive slot
104
+ const inactiveSlot = await knex('m_agents')
105
+ .where({
106
+ is_reusable: true,
107
+ in_use: false
108
+ })
109
+ .orderBy('id', 'asc')
110
+ .first();
111
+ if (inactiveSlot) {
112
+ // Reuse this slot
113
+ await knex('m_agents')
114
+ .where({ id: inactiveSlot.id })
115
+ .update({
116
+ in_use: true,
117
+ last_active_ts: now
118
+ });
119
+ return inactiveSlot.id;
120
+ }
121
+ else {
122
+ // No inactive slots available, create a new generic agent
123
+ const maxGeneric = await knex('m_agents')
124
+ .where('name', 'like', 'generic-%')
125
+ .orderBy('name', 'desc')
126
+ .first('name');
127
+ let nextNumber = 1;
128
+ if (maxGeneric && maxGeneric.name) {
129
+ const match = maxGeneric.name.match(/^generic-(\d+)$/);
130
+ if (match) {
131
+ nextNumber = parseInt(match[1], 10) + 1;
132
+ }
133
+ }
134
+ const genericName = `generic-${nextNumber}`;
135
+ const [id] = await knex('m_agents').insert({
136
+ name: genericName,
137
+ is_reusable: true,
138
+ in_use: true,
139
+ last_active_ts: now
140
+ });
141
+ return id;
142
+ }
143
+ }
144
+ // Check if this is a user-specified agent or a named generic agent
145
+ const isUserSpecified = isUserSpecifiedAgent(name);
146
+ const isReusable = !isUserSpecified;
147
+ // Named agent: create/get with exact name
148
+ // Try to insert (will be ignored if exists)
149
+ await knex('m_agents')
150
+ .insert({
151
+ name,
152
+ is_reusable: isReusable,
153
+ in_use: true,
154
+ last_active_ts: now
155
+ })
156
+ .onConflict('name')
157
+ .ignore();
158
+ // Update activity timestamp and in_use flag
159
+ await knex('m_agents')
160
+ .where({ name })
161
+ .update({
162
+ in_use: true,
163
+ last_active_ts: now
164
+ });
226
165
  // Get the ID
227
- const result = db.prepare('SELECT id FROM m_agents WHERE name = ?').get(name);
166
+ const result = await knex('m_agents').where({ name }).first('id');
228
167
  if (!result) {
229
168
  throw new Error(`Failed to get or create agent: ${name}`);
230
169
  }
231
170
  return result.id;
232
171
  }
172
+ /**
173
+ * Release an agent slot (mark as inactive)
174
+ * This allows generic agents to be reused
175
+ */
176
+ export async function releaseAgent(adapter, agentId, trx) {
177
+ const knex = trx || adapter.getKnex();
178
+ await knex('m_agents')
179
+ .where({ id: agentId, is_reusable: true })
180
+ .update({ in_use: false });
181
+ }
182
+ /**
183
+ * Update agent activity timestamp
184
+ */
185
+ export async function updateAgentActivity(adapter, agentId, trx) {
186
+ const knex = trx || adapter.getKnex();
187
+ const now = Math.floor(Date.now() / 1000);
188
+ await knex('m_agents')
189
+ .where({ id: agentId })
190
+ .update({ last_active_ts: now });
191
+ }
233
192
  /**
234
193
  * Get or create context key by name
235
- *
236
- * @param db - Database instance
237
- * @param key - Context key name
238
- * @returns Context key ID
239
194
  */
240
- export function getOrCreateContextKey(db, key) {
241
- db.prepare('INSERT OR IGNORE INTO m_context_keys (key) VALUES (?)').run(key);
242
- const result = db.prepare('SELECT id FROM m_context_keys WHERE key = ?').get(key);
195
+ export async function getOrCreateContextKey(adapter, key, trx) {
196
+ const knex = trx || adapter.getKnex();
197
+ await knex('m_context_keys').insert({ key }).onConflict('key').ignore();
198
+ const result = await knex('m_context_keys').where({ key }).first('id');
243
199
  if (!result) {
244
200
  throw new Error(`Failed to get or create context key: ${key}`);
245
201
  }
@@ -247,14 +203,11 @@ export function getOrCreateContextKey(db, key) {
247
203
  }
248
204
  /**
249
205
  * Get or create file by path
250
- *
251
- * @param db - Database instance
252
- * @param path - File path
253
- * @returns File ID
254
206
  */
255
- export function getOrCreateFile(db, path) {
256
- db.prepare('INSERT OR IGNORE INTO m_files (path) VALUES (?)').run(path);
257
- const result = db.prepare('SELECT id FROM m_files WHERE path = ?').get(path);
207
+ export async function getOrCreateFile(adapter, path, trx) {
208
+ const knex = trx || adapter.getKnex();
209
+ await knex('m_files').insert({ path }).onConflict('path').ignore();
210
+ const result = await knex('m_files').where({ path }).first('id');
258
211
  if (!result) {
259
212
  throw new Error(`Failed to get or create file: ${path}`);
260
213
  }
@@ -262,14 +215,11 @@ export function getOrCreateFile(db, path) {
262
215
  }
263
216
  /**
264
217
  * Get or create tag by name
265
- *
266
- * @param db - Database instance
267
- * @param name - Tag name
268
- * @returns Tag ID
269
218
  */
270
- export function getOrCreateTag(db, name) {
271
- db.prepare('INSERT OR IGNORE INTO m_tags (name) VALUES (?)').run(name);
272
- const result = db.prepare('SELECT id FROM m_tags WHERE name = ?').get(name);
219
+ export async function getOrCreateTag(adapter, name, trx) {
220
+ const knex = trx || adapter.getKnex();
221
+ await knex('m_tags').insert({ name }).onConflict('name').ignore();
222
+ const result = await knex('m_tags').where({ name }).first('id');
273
223
  if (!result) {
274
224
  throw new Error(`Failed to get or create tag: ${name}`);
275
225
  }
@@ -277,14 +227,11 @@ export function getOrCreateTag(db, name) {
277
227
  }
278
228
  /**
279
229
  * Get or create scope by name
280
- *
281
- * @param db - Database instance
282
- * @param name - Scope name
283
- * @returns Scope ID
284
230
  */
285
- export function getOrCreateScope(db, name) {
286
- db.prepare('INSERT OR IGNORE INTO m_scopes (name) VALUES (?)').run(name);
287
- const result = db.prepare('SELECT id FROM m_scopes WHERE name = ?').get(name);
231
+ export async function getOrCreateScope(adapter, name, trx) {
232
+ const knex = trx || adapter.getKnex();
233
+ await knex('m_scopes').insert({ name }).onConflict('name').ignore();
234
+ const result = await knex('m_scopes').where({ name }).first('id');
288
235
  if (!result) {
289
236
  throw new Error(`Failed to get or create scope: ${name}`);
290
237
  }
@@ -292,17 +239,11 @@ export function getOrCreateScope(db, name) {
292
239
  }
293
240
  /**
294
241
  * Get or create category ID
295
- * Uses INSERT to create if doesn't exist
296
- *
297
- * @param db - Database instance
298
- * @param category - Category name
299
- * @returns Category ID
300
242
  */
301
- export function getOrCreateCategoryId(db, category) {
302
- // Use INSERT OR IGNORE for idempotent operation
303
- db.prepare('INSERT OR IGNORE INTO m_constraint_categories (name) VALUES (?)').run(category);
304
- // Get the ID
305
- const result = db.prepare('SELECT id FROM m_constraint_categories WHERE name = ?').get(category);
243
+ export async function getOrCreateCategoryId(adapter, category, trx) {
244
+ const knex = trx || adapter.getKnex();
245
+ await knex('m_constraint_categories').insert({ name: category }).onConflict('name').ignore();
246
+ const result = await knex('m_constraint_categories').where({ name: category }).first('id');
306
247
  if (!result) {
307
248
  throw new Error(`Failed to get or create category: ${category}`);
308
249
  }
@@ -310,77 +251,56 @@ export function getOrCreateCategoryId(db, category) {
310
251
  }
311
252
  /**
312
253
  * Get layer ID by name
313
- * Does not auto-create (layers are predefined)
314
- *
315
- * @param db - Database instance
316
- * @param name - Layer name
317
- * @returns Layer ID or null if not found
318
254
  */
319
- export function getLayerId(db, name) {
320
- const result = db.prepare('SELECT id FROM m_layers WHERE name = ?').get(name);
255
+ export async function getLayerId(adapter, name, trx) {
256
+ const knex = trx || adapter.getKnex();
257
+ const result = await knex('m_layers').where({ name }).first('id');
321
258
  return result ? result.id : null;
322
259
  }
323
260
  /**
324
261
  * Get constraint category ID by name
325
- * Does not auto-create (categories are predefined)
326
- *
327
- * @param db - Database instance
328
- * @param name - Category name
329
- * @returns Category ID or null if not found
330
262
  */
331
- export function getCategoryId(db, name) {
332
- const result = db.prepare('SELECT id FROM m_constraint_categories WHERE name = ?').get(name);
263
+ export async function getCategoryId(adapter, name, trx) {
264
+ const knex = trx || adapter.getKnex();
265
+ const result = await knex('m_constraint_categories').where({ name }).first('id');
333
266
  return result ? result.id : null;
334
267
  }
335
268
  // ============================================================================
336
- // Configuration Management
269
+ // Configuration Management (Async)
337
270
  // ============================================================================
338
271
  /**
339
272
  * Get configuration value from m_config table
340
- *
341
- * @param db - Database instance
342
- * @param key - Config key
343
- * @returns Config value as string or null if not found
344
273
  */
345
- export function getConfigValue(db, key) {
346
- const result = db.prepare('SELECT value FROM m_config WHERE key = ?').get(key);
274
+ export async function getConfigValue(adapter, key) {
275
+ const knex = adapter.getKnex();
276
+ const result = await knex('m_config').where({ key }).first('value');
347
277
  return result ? result.value : null;
348
278
  }
349
279
  /**
350
280
  * Set configuration value in m_config table
351
- *
352
- * @param db - Database instance
353
- * @param key - Config key
354
- * @param value - Config value (will be converted to string)
355
281
  */
356
- export function setConfigValue(db, key, value) {
282
+ export async function setConfigValue(adapter, key, value) {
283
+ const knex = adapter.getKnex();
357
284
  const stringValue = String(value);
358
- db.prepare('INSERT OR REPLACE INTO m_config (key, value) VALUES (?, ?)').run(key, stringValue);
285
+ await knex('m_config')
286
+ .insert({ key, value: stringValue })
287
+ .onConflict('key')
288
+ .merge({ value: stringValue });
359
289
  }
360
290
  /**
361
291
  * Get configuration value as boolean
362
- *
363
- * @param db - Database instance
364
- * @param key - Config key
365
- * @param defaultValue - Default value if key not found
366
- * @returns Boolean value
367
292
  */
368
- export function getConfigBool(db, key, defaultValue = false) {
369
- const value = getConfigValue(db, key);
293
+ export async function getConfigBool(adapter, key, defaultValue = false) {
294
+ const value = await getConfigValue(adapter, key);
370
295
  if (value === null)
371
296
  return defaultValue;
372
297
  return value === '1' || value.toLowerCase() === 'true';
373
298
  }
374
299
  /**
375
300
  * Get configuration value as integer
376
- *
377
- * @param db - Database instance
378
- * @param key - Config key
379
- * @param defaultValue - Default value if key not found
380
- * @returns Integer value
381
301
  */
382
- export function getConfigInt(db, key, defaultValue = 0) {
383
- const value = getConfigValue(db, key);
302
+ export async function getConfigInt(adapter, key, defaultValue = 0) {
303
+ const value = await getConfigValue(adapter, key);
384
304
  if (value === null)
385
305
  return defaultValue;
386
306
  const parsed = parseInt(value, 10);
@@ -388,12 +308,10 @@ export function getConfigInt(db, key, defaultValue = 0) {
388
308
  }
389
309
  /**
390
310
  * Get all configuration as an object
391
- *
392
- * @param db - Database instance
393
- * @returns Object with all m_config key-value pairs
394
311
  */
395
- export function getAllConfig(db) {
396
- const rows = db.prepare('SELECT key, value FROM m_config').all();
312
+ export async function getAllConfig(adapter) {
313
+ const knex = adapter.getKnex();
314
+ const rows = await knex('m_config').select('key', 'value');
397
315
  const config = {};
398
316
  for (const row of rows) {
399
317
  config[row.key] = row.value;
@@ -401,35 +319,10 @@ export function getAllConfig(db) {
401
319
  return config;
402
320
  }
403
321
  // ============================================================================
404
- // Transaction Helpers
405
- // ============================================================================
406
- /**
407
- * Execute a function within a transaction
408
- * Automatically handles commit/rollback
409
- *
410
- * @param db - Database instance
411
- * @param fn - Function to execute in transaction
412
- * @returns Result from function
413
- */
414
- export function transaction(db, fn) {
415
- db.exec('BEGIN TRANSACTION');
416
- try {
417
- const result = fn();
418
- db.exec('COMMIT');
419
- return result;
420
- }
421
- catch (error) {
422
- db.exec('ROLLBACK');
423
- throw error;
424
- }
425
- }
426
- // ============================================================================
427
- // Decision Context Management (v3.2.2)
322
+ // Decision Context Management (Async - v3.2.2)
428
323
  // ============================================================================
429
324
  /**
430
325
  * Validate JSON structure for alternatives array
431
- * @param alternatives - JSON string or null
432
- * @throws Error if JSON is invalid or not an array
433
326
  */
434
327
  function validateAlternativesJson(alternatives) {
435
328
  if (alternatives === null || alternatives === undefined)
@@ -449,8 +342,6 @@ function validateAlternativesJson(alternatives) {
449
342
  }
450
343
  /**
451
344
  * Validate JSON structure for tradeoffs object
452
- * @param tradeoffs - JSON string or null
453
- * @throws Error if JSON is invalid or doesn't have pros/cons structure
454
345
  */
455
346
  function validateTradeoffsJson(tradeoffs) {
456
347
  if (tradeoffs === null || tradeoffs === undefined)
@@ -460,7 +351,6 @@ function validateTradeoffsJson(tradeoffs) {
460
351
  if (typeof parsed !== 'object' || parsed === null) {
461
352
  throw new Error('tradeoffs must be a JSON object');
462
353
  }
463
- // Optional: Check for pros/cons keys if provided
464
354
  if (parsed.pros !== undefined && !Array.isArray(parsed.pros)) {
465
355
  throw new Error('tradeoffs.pros must be an array');
466
356
  }
@@ -477,85 +367,55 @@ function validateTradeoffsJson(tradeoffs) {
477
367
  }
478
368
  /**
479
369
  * Add decision context to a decision
480
- *
481
- * @param db - Database instance
482
- * @param decisionKey - Decision key to attach context to
483
- * @param rationale - Rationale for the decision (required)
484
- * @param alternatives - JSON array of alternatives considered (optional)
485
- * @param tradeoffs - JSON object with pros/cons (optional)
486
- * @param decidedBy - Agent name who decided (optional)
487
- * @param relatedTaskId - Related task ID (optional)
488
- * @param relatedConstraintId - Related constraint ID (optional)
489
- * @returns Context ID
490
370
  */
491
- export function addDecisionContext(db, decisionKey, rationale, alternatives = null, tradeoffs = null, decidedBy = null, relatedTaskId = null, relatedConstraintId = null) {
371
+ export async function addDecisionContext(adapter, decisionKey, rationale, alternatives = null, tradeoffs = null, decidedBy = null, relatedTaskId = null, relatedConstraintId = null) {
492
372
  // Validate JSON inputs
493
373
  validateAlternativesJson(alternatives);
494
374
  validateTradeoffsJson(tradeoffs);
375
+ const knex = adapter.getKnex();
495
376
  // Get decision key ID
496
- const keyId = getOrCreateContextKey(db, decisionKey);
377
+ const keyId = await getOrCreateContextKey(adapter, decisionKey);
497
378
  // Get agent ID if provided
498
379
  let agentId = null;
499
380
  if (decidedBy) {
500
- agentId = getOrCreateAgent(db, decidedBy);
381
+ agentId = await getOrCreateAgent(adapter, decidedBy);
501
382
  }
502
383
  // Insert context
503
- const result = db.prepare(`
504
- INSERT INTO t_decision_context (
505
- decision_key_id,
506
- rationale,
507
- alternatives_considered,
508
- tradeoffs,
509
- decided_by_agent_id,
510
- related_task_id,
511
- related_constraint_id
512
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
513
- `).run(keyId, rationale, alternatives, tradeoffs, agentId, relatedTaskId, relatedConstraintId);
514
- return result.lastInsertRowid;
384
+ const [id] = await knex('t_decision_context').insert({
385
+ decision_key_id: keyId,
386
+ rationale,
387
+ alternatives_considered: alternatives,
388
+ tradeoffs,
389
+ agent_id: agentId,
390
+ related_task_id: relatedTaskId,
391
+ related_constraint_id: relatedConstraintId,
392
+ decision_date: Math.floor(Date.now() / 1000),
393
+ ts: Math.floor(Date.now() / 1000),
394
+ });
395
+ return id;
515
396
  }
516
397
  /**
517
398
  * Get decision with context
518
- *
519
- * @param db - Database instance
520
- * @param decisionKey - Decision key
521
- * @returns Decision with context or null if not found
522
399
  */
523
- export function getDecisionWithContext(db, decisionKey) {
400
+ export async function getDecisionWithContext(adapter, decisionKey) {
401
+ const knex = adapter.getKnex();
524
402
  // First get the decision
525
- const decision = db.prepare(`
526
- SELECT
527
- k.key,
528
- d.value,
529
- d.version,
530
- CASE d.status WHEN 1 THEN 'active' WHEN 2 THEN 'deprecated' ELSE 'draft' END as status,
531
- l.name as layer,
532
- a.name as decided_by,
533
- datetime(d.ts, 'unixepoch') as updated
534
- FROM t_decisions d
535
- JOIN m_context_keys k ON d.key_id = k.id
536
- LEFT JOIN m_layers l ON d.layer_id = l.id
537
- LEFT JOIN m_agents a ON d.agent_id = a.id
538
- WHERE k.key = ?
539
- `).get(decisionKey);
403
+ const decision = await knex('t_decisions as d')
404
+ .join('m_context_keys as k', 'd.key_id', 'k.id')
405
+ .leftJoin('m_layers as l', 'd.layer_id', 'l.id')
406
+ .leftJoin('m_agents as a', 'd.agent_id', 'a.id')
407
+ .where('k.key', decisionKey)
408
+ .select('k.key', 'd.value', 'd.version', knex.raw(`CASE d.status WHEN 1 THEN 'active' WHEN 2 THEN 'deprecated' ELSE 'draft' END as status`), 'l.name as layer', 'a.name as decided_by', knex.raw(`datetime(d.ts, 'unixepoch') as updated`))
409
+ .first();
540
410
  if (!decision)
541
411
  return null;
542
412
  // Get all contexts for this decision
543
- const contexts = db.prepare(`
544
- SELECT
545
- dc.id,
546
- dc.rationale,
547
- dc.alternatives_considered,
548
- dc.tradeoffs,
549
- a.name as decided_by,
550
- datetime(dc.decision_date, 'unixepoch') as decision_date,
551
- dc.related_task_id,
552
- dc.related_constraint_id
553
- FROM t_decision_context dc
554
- JOIN m_context_keys k ON dc.decision_key_id = k.id
555
- LEFT JOIN m_agents a ON dc.decided_by_agent_id = a.id
556
- WHERE k.key = ?
557
- ORDER BY dc.decision_date DESC
558
- `).all(decisionKey);
413
+ const contexts = await knex('t_decision_context as dc')
414
+ .join('m_context_keys as k', 'dc.decision_key_id', 'k.id')
415
+ .leftJoin('m_agents as a', 'dc.agent_id', 'a.id')
416
+ .where('k.key', decisionKey)
417
+ .select('dc.id', 'dc.rationale', 'dc.alternatives_considered', 'dc.tradeoffs', 'a.name as decided_by', knex.raw(`datetime(dc.decision_date, 'unixepoch') as decision_date`), 'dc.related_task_id', 'dc.related_constraint_id')
418
+ .orderBy('dc.decision_date', 'desc');
559
419
  return {
560
420
  ...decision,
561
421
  context: contexts,
@@ -563,54 +423,46 @@ export function getDecisionWithContext(db, decisionKey) {
563
423
  }
564
424
  /**
565
425
  * List decision contexts with optional filters
566
- *
567
- * @param db - Database instance
568
- * @param filters - Optional filters
569
- * @returns Array of decision contexts
570
426
  */
571
- export function listDecisionContexts(db, filters) {
572
- let query = `
573
- SELECT
574
- dc.id,
575
- k.key as decision_key,
576
- dc.rationale,
577
- dc.alternatives_considered,
578
- dc.tradeoffs,
579
- a.name as decided_by,
580
- datetime(dc.decision_date, 'unixepoch') as decision_date,
581
- dc.related_task_id,
582
- dc.related_constraint_id
583
- FROM t_decision_context dc
584
- JOIN m_context_keys k ON dc.decision_key_id = k.id
585
- LEFT JOIN m_agents a ON dc.decided_by_agent_id = a.id
586
- WHERE 1=1
587
- `;
588
- const params = [];
427
+ export async function listDecisionContexts(adapter, filters) {
428
+ const knex = adapter.getKnex();
429
+ let query = knex('t_decision_context as dc')
430
+ .join('m_context_keys as k', 'dc.decision_key_id', 'k.id')
431
+ .leftJoin('m_agents as a', 'dc.agent_id', 'a.id')
432
+ .select('dc.id', 'k.key as decision_key', 'dc.rationale', 'dc.alternatives_considered', 'dc.tradeoffs', 'a.name as decided_by', knex.raw(`datetime(dc.decision_date, 'unixepoch') as decision_date`), 'dc.related_task_id', 'dc.related_constraint_id');
589
433
  if (filters?.decisionKey) {
590
- query += ' AND k.key = ?';
591
- params.push(filters.decisionKey);
434
+ query = query.where('k.key', filters.decisionKey);
592
435
  }
593
436
  if (filters?.relatedTaskId !== undefined) {
594
- query += ' AND dc.related_task_id = ?';
595
- params.push(filters.relatedTaskId);
437
+ query = query.where('dc.related_task_id', filters.relatedTaskId);
596
438
  }
597
439
  if (filters?.relatedConstraintId !== undefined) {
598
- query += ' AND dc.related_constraint_id = ?';
599
- params.push(filters.relatedConstraintId);
440
+ query = query.where('dc.related_constraint_id', filters.relatedConstraintId);
600
441
  }
601
442
  if (filters?.decidedBy) {
602
- query += ' AND a.name = ?';
603
- params.push(filters.decidedBy);
443
+ query = query.where('a.name', filters.decidedBy);
604
444
  }
605
- query += ' ORDER BY dc.decision_date DESC';
445
+ query = query.orderBy('dc.decision_date', 'desc');
606
446
  if (filters?.limit) {
607
- query += ' LIMIT ?';
608
- params.push(filters.limit);
447
+ query = query.limit(filters.limit);
609
448
  }
610
449
  if (filters?.offset) {
611
- query += ' OFFSET ?';
612
- params.push(filters.offset);
450
+ query = query.offset(filters.offset);
613
451
  }
614
- return db.prepare(query).all(...params);
452
+ return await query;
453
+ }
454
+ /**
455
+ * Backwards compatibility alias for getAdapter
456
+ */
457
+ export function getDatabase() {
458
+ return getAdapter();
459
+ }
460
+ /**
461
+ * Execute a function within a database transaction
462
+ */
463
+ export async function transaction(callback) {
464
+ const adapter = getAdapter();
465
+ const knex = adapter.getKnex();
466
+ return await knex.transaction(callback);
615
467
  }
616
468
  //# sourceMappingURL=database.js.map