sqlew 3.2.5 → 3.6.0

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 (291) hide show
  1. package/CHANGELOG.md +288 -1011
  2. package/README.md +80 -263
  3. package/assets/config.example.toml +97 -0
  4. package/assets/schema.sql +6 -1
  5. package/dist/adapters/index.d.ts +11 -0
  6. package/dist/adapters/index.d.ts.map +1 -0
  7. package/dist/adapters/index.js +21 -0
  8. package/dist/adapters/index.js.map +1 -0
  9. package/dist/adapters/mysql-adapter.d.ts +31 -0
  10. package/dist/adapters/mysql-adapter.d.ts.map +1 -0
  11. package/dist/adapters/mysql-adapter.js +63 -0
  12. package/dist/adapters/mysql-adapter.js.map +1 -0
  13. package/dist/adapters/postgresql-adapter.d.ts +31 -0
  14. package/dist/adapters/postgresql-adapter.d.ts.map +1 -0
  15. package/dist/adapters/postgresql-adapter.js +63 -0
  16. package/dist/adapters/postgresql-adapter.js.map +1 -0
  17. package/dist/adapters/sqlite-adapter.d.ts +37 -0
  18. package/dist/adapters/sqlite-adapter.d.ts.map +1 -0
  19. package/dist/adapters/sqlite-adapter.js +129 -0
  20. package/dist/adapters/sqlite-adapter.js.map +1 -0
  21. package/dist/adapters/types.d.ts +33 -0
  22. package/dist/adapters/types.d.ts.map +1 -0
  23. package/dist/adapters/types.js +2 -0
  24. package/dist/adapters/types.js.map +1 -0
  25. package/dist/cli.js +55 -54
  26. package/dist/cli.js.map +1 -1
  27. package/dist/config/example-generator.d.ts +11 -0
  28. package/dist/config/example-generator.d.ts.map +1 -0
  29. package/dist/config/example-generator.js +48 -0
  30. package/dist/config/example-generator.js.map +1 -0
  31. package/dist/config/loader.d.ts +46 -0
  32. package/dist/config/loader.d.ts.map +1 -0
  33. package/dist/config/loader.js +155 -0
  34. package/dist/config/loader.js.map +1 -0
  35. package/dist/config/types.d.ts +86 -0
  36. package/dist/config/types.d.ts.map +1 -0
  37. package/dist/config/types.js +28 -0
  38. package/dist/config/types.js.map +1 -0
  39. package/dist/constants.d.ts +9 -0
  40. package/dist/constants.d.ts.map +1 -1
  41. package/dist/constants.js +10 -0
  42. package/dist/constants.js.map +1 -1
  43. package/dist/database.d.ts +44 -122
  44. package/dist/database.d.ts.map +1 -1
  45. package/dist/database.js +145 -349
  46. package/dist/database.js.map +1 -1
  47. package/dist/index.js +223 -175
  48. package/dist/index.js.map +1 -1
  49. package/dist/knexfile.d.ts +6 -0
  50. package/dist/knexfile.d.ts.map +1 -0
  51. package/dist/knexfile.js +85 -0
  52. package/dist/knexfile.js.map +1 -0
  53. package/dist/migrations/add-help-system-tables.d.ts +35 -0
  54. package/dist/migrations/add-help-system-tables.d.ts.map +1 -0
  55. package/dist/migrations/add-help-system-tables.js +206 -0
  56. package/dist/migrations/add-help-system-tables.js.map +1 -0
  57. package/dist/migrations/add-token-tracking.d.ts +28 -0
  58. package/dist/migrations/add-token-tracking.d.ts.map +1 -0
  59. package/dist/migrations/add-token-tracking.js +108 -0
  60. package/dist/migrations/add-token-tracking.js.map +1 -0
  61. package/dist/migrations/add-v3.5.0-pruned-files.d.ts +26 -0
  62. package/dist/migrations/add-v3.5.0-pruned-files.d.ts.map +1 -0
  63. package/dist/migrations/add-v3.5.0-pruned-files.js +107 -0
  64. package/dist/migrations/add-v3.5.0-pruned-files.js.map +1 -0
  65. package/dist/migrations/index.d.ts +26 -12
  66. package/dist/migrations/index.d.ts.map +1 -1
  67. package/dist/migrations/index.js +162 -20
  68. package/dist/migrations/index.js.map +1 -1
  69. package/dist/migrations/knex/20251025020452_create_master_tables.d.ts +4 -0
  70. package/dist/migrations/knex/20251025020452_create_master_tables.d.ts.map +1 -0
  71. package/dist/migrations/knex/20251025020452_create_master_tables.js +65 -0
  72. package/dist/migrations/knex/20251025020452_create_master_tables.js.map +1 -0
  73. package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts +4 -0
  74. package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts.map +1 -0
  75. package/dist/migrations/knex/20251025021152_create_transaction_tables.js +235 -0
  76. package/dist/migrations/knex/20251025021152_create_transaction_tables.js.map +1 -0
  77. package/dist/migrations/knex/20251025021351_create_indexes.d.ts +4 -0
  78. package/dist/migrations/knex/20251025021351_create_indexes.d.ts.map +1 -0
  79. package/dist/migrations/knex/20251025021351_create_indexes.js +62 -0
  80. package/dist/migrations/knex/20251025021351_create_indexes.js.map +1 -0
  81. package/dist/migrations/knex/20251025021416_seed_master_data.d.ts +4 -0
  82. package/dist/migrations/knex/20251025021416_seed_master_data.d.ts.map +1 -0
  83. package/dist/migrations/knex/20251025021416_seed_master_data.js +58 -0
  84. package/dist/migrations/knex/20251025021416_seed_master_data.js.map +1 -0
  85. package/dist/migrations/knex/20251025070349_create_views.d.ts +4 -0
  86. package/dist/migrations/knex/20251025070349_create_views.d.ts.map +1 -0
  87. package/dist/migrations/knex/20251025070349_create_views.js +143 -0
  88. package/dist/migrations/knex/20251025070349_create_views.js.map +1 -0
  89. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts +4 -0
  90. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts.map +1 -0
  91. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js +15 -0
  92. package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js.map +1 -0
  93. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts +8 -0
  94. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts.map +1 -0
  95. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js +12 -0
  96. package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js.map +1 -0
  97. package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts +19 -0
  98. package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts.map +1 -0
  99. package/dist/migrations/knex/20251025090000_create_help_system_tables.js +115 -0
  100. package/dist/migrations/knex/20251025090000_create_help_system_tables.js.map +1 -0
  101. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts +13 -0
  102. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts.map +1 -0
  103. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js +377 -0
  104. package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js.map +1 -0
  105. package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts +15 -0
  106. package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts.map +1 -0
  107. package/dist/migrations/knex/20251025100000_seed_help_metadata.js +253 -0
  108. package/dist/migrations/knex/20251025100000_seed_help_metadata.js.map +1 -0
  109. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts +16 -0
  110. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts.map +1 -0
  111. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js +276 -0
  112. package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js.map +1 -0
  113. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts +8 -0
  114. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts.map +1 -0
  115. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js +64 -0
  116. package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js.map +1 -0
  117. package/dist/migrations/seed-help-data.d.ts +48 -0
  118. package/dist/migrations/seed-help-data.d.ts.map +1 -0
  119. package/dist/migrations/seed-help-data.js +1466 -0
  120. package/dist/migrations/seed-help-data.js.map +1 -0
  121. package/dist/migrations/seed-tool-metadata.d.ts +24 -0
  122. package/dist/migrations/seed-tool-metadata.d.ts.map +1 -0
  123. package/dist/migrations/seed-tool-metadata.js +392 -0
  124. package/dist/migrations/seed-tool-metadata.js.map +1 -0
  125. package/dist/migrations/v3.6.0-help-system-refactor.d.ts +46 -0
  126. package/dist/migrations/v3.6.0-help-system-refactor.d.ts.map +1 -0
  127. package/dist/migrations/v3.6.0-help-system-refactor.js +223 -0
  128. package/dist/migrations/v3.6.0-help-system-refactor.js.map +1 -0
  129. package/dist/schema.d.ts.map +1 -1
  130. package/dist/schema.js +2 -0
  131. package/dist/schema.js.map +1 -1
  132. package/dist/tests/git-aware-completion.test.d.ts +6 -0
  133. package/dist/tests/git-aware-completion.test.d.ts.map +1 -0
  134. package/dist/tests/git-aware-completion.test.js +160 -0
  135. package/dist/tests/git-aware-completion.test.js.map +1 -0
  136. package/dist/tests/help-system.test.d.ts +23 -0
  137. package/dist/tests/help-system.test.d.ts.map +1 -0
  138. package/dist/tests/help-system.test.js +374 -0
  139. package/dist/tests/help-system.test.js.map +1 -0
  140. package/dist/tests/tasks.auto-pruning-decision-link.test.d.ts +6 -0
  141. package/dist/tests/tasks.auto-pruning-decision-link.test.d.ts.map +1 -0
  142. package/dist/tests/tasks.auto-pruning-decision-link.test.js +264 -0
  143. package/dist/tests/tasks.auto-pruning-decision-link.test.js.map +1 -0
  144. package/dist/tests/tasks.auto-pruning-partial.test.d.ts +6 -0
  145. package/dist/tests/tasks.auto-pruning-partial.test.d.ts.map +1 -0
  146. package/dist/tests/tasks.auto-pruning-partial.test.js +285 -0
  147. package/dist/tests/tasks.auto-pruning-partial.test.js.map +1 -0
  148. package/dist/tests/tasks.auto-pruning-persistence.test.d.ts +6 -0
  149. package/dist/tests/tasks.auto-pruning-persistence.test.d.ts.map +1 -0
  150. package/dist/tests/tasks.auto-pruning-persistence.test.js +250 -0
  151. package/dist/tests/tasks.auto-pruning-persistence.test.js.map +1 -0
  152. package/dist/tests/tasks.auto-pruning-safety.test.d.ts +12 -0
  153. package/dist/tests/tasks.auto-pruning-safety.test.d.ts.map +1 -0
  154. package/dist/tests/tasks.auto-pruning-safety.test.js +217 -0
  155. package/dist/tests/tasks.auto-pruning-safety.test.js.map +1 -0
  156. package/dist/tests/tasks.dependencies.test.js +338 -307
  157. package/dist/tests/tasks.dependencies.test.js.map +1 -1
  158. package/dist/tests/tasks.link-file-backward-compat.test.d.ts +6 -0
  159. package/dist/tests/tasks.link-file-backward-compat.test.d.ts.map +1 -0
  160. package/dist/tests/tasks.link-file-backward-compat.test.js +247 -0
  161. package/dist/tests/tasks.link-file-backward-compat.test.js.map +1 -0
  162. package/dist/tests/tasks.watch-files-action.test.d.ts +6 -0
  163. package/dist/tests/tasks.watch-files-action.test.d.ts.map +1 -0
  164. package/dist/tests/tasks.watch-files-action.test.js +372 -0
  165. package/dist/tests/tasks.watch-files-action.test.js.map +1 -0
  166. package/dist/tests/tasks.watch-files-parameter.test.d.ts +6 -0
  167. package/dist/tests/tasks.watch-files-parameter.test.d.ts.map +1 -0
  168. package/dist/tests/tasks.watch-files-parameter.test.js +260 -0
  169. package/dist/tests/tasks.watch-files-parameter.test.js.map +1 -0
  170. package/dist/tests/two-step-git-completion.test.d.ts +6 -0
  171. package/dist/tests/two-step-git-completion.test.d.ts.map +1 -0
  172. package/dist/tests/two-step-git-completion.test.js +326 -0
  173. package/dist/tests/two-step-git-completion.test.js.map +1 -0
  174. package/dist/tests/vcs-staging.test.d.ts +6 -0
  175. package/dist/tests/vcs-staging.test.d.ts.map +1 -0
  176. package/dist/tests/vcs-staging.test.js +137 -0
  177. package/dist/tests/vcs-staging.test.js.map +1 -0
  178. package/dist/tools/config.d.ts +9 -4
  179. package/dist/tools/config.d.ts.map +1 -1
  180. package/dist/tools/config.js +16 -12
  181. package/dist/tools/config.js.map +1 -1
  182. package/dist/tools/constraints.d.ts +9 -3
  183. package/dist/tools/constraints.d.ts.map +1 -1
  184. package/dist/tools/constraints.js +66 -45
  185. package/dist/tools/constraints.js.map +1 -1
  186. package/dist/tools/context.d.ts +35 -16
  187. package/dist/tools/context.d.ts.map +1 -1
  188. package/dist/tools/context.js +374 -314
  189. package/dist/tools/context.js.map +1 -1
  190. package/dist/tools/files.d.ts +11 -4
  191. package/dist/tools/files.d.ts.map +1 -1
  192. package/dist/tools/files.js +173 -91
  193. package/dist/tools/files.js.map +1 -1
  194. package/dist/tools/help-queries.d.ts +130 -0
  195. package/dist/tools/help-queries.d.ts.map +1 -0
  196. package/dist/tools/help-queries.js +393 -0
  197. package/dist/tools/help-queries.js.map +1 -0
  198. package/dist/tools/messaging.d.ts +13 -6
  199. package/dist/tools/messaging.d.ts.map +1 -1
  200. package/dist/tools/messaging.js +217 -129
  201. package/dist/tools/messaging.js.map +1 -1
  202. package/dist/tools/tasks.d.ts +42 -12
  203. package/dist/tools/tasks.d.ts.map +1 -1
  204. package/dist/tools/tasks.js +809 -347
  205. package/dist/tools/tasks.js.map +1 -1
  206. package/dist/tools/utils.d.ts +13 -5
  207. package/dist/tools/utils.d.ts.map +1 -1
  208. package/dist/tools/utils.js +92 -115
  209. package/dist/tools/utils.js.map +1 -1
  210. package/dist/types.d.ts +4 -0
  211. package/dist/types.d.ts.map +1 -1
  212. package/dist/utils/activity-logging.d.ts +114 -0
  213. package/dist/utils/activity-logging.d.ts.map +1 -0
  214. package/dist/utils/activity-logging.js +162 -0
  215. package/dist/utils/activity-logging.js.map +1 -0
  216. package/dist/utils/batch.d.ts +2 -2
  217. package/dist/utils/batch.d.ts.map +1 -1
  218. package/dist/utils/batch.js +8 -8
  219. package/dist/utils/batch.js.map +1 -1
  220. package/dist/utils/cleanup.d.ts +21 -13
  221. package/dist/utils/cleanup.d.ts.map +1 -1
  222. package/dist/utils/cleanup.js +31 -24
  223. package/dist/utils/cleanup.js.map +1 -1
  224. package/dist/utils/debug-logger.d.ts +44 -0
  225. package/dist/utils/debug-logger.d.ts.map +1 -0
  226. package/dist/utils/debug-logger.js +116 -0
  227. package/dist/utils/debug-logger.js.map +1 -0
  228. package/dist/utils/file-pruning.d.ts +69 -0
  229. package/dist/utils/file-pruning.d.ts.map +1 -0
  230. package/dist/utils/file-pruning.js +185 -0
  231. package/dist/utils/file-pruning.js.map +1 -0
  232. package/dist/utils/help-tracking.d.ts +55 -0
  233. package/dist/utils/help-tracking.d.ts.map +1 -0
  234. package/dist/utils/help-tracking.js +88 -0
  235. package/dist/utils/help-tracking.js.map +1 -0
  236. package/dist/utils/quality-checks.d.ts +60 -0
  237. package/dist/utils/quality-checks.d.ts.map +1 -0
  238. package/dist/utils/quality-checks.js +228 -0
  239. package/dist/utils/quality-checks.js.map +1 -0
  240. package/dist/utils/retention.d.ts +13 -5
  241. package/dist/utils/retention.d.ts.map +1 -1
  242. package/dist/utils/retention.js +20 -8
  243. package/dist/utils/retention.js.map +1 -1
  244. package/dist/utils/task-stale-detection.d.ts +77 -7
  245. package/dist/utils/task-stale-detection.d.ts.map +1 -1
  246. package/dist/utils/task-stale-detection.js +309 -34
  247. package/dist/utils/task-stale-detection.js.map +1 -1
  248. package/dist/utils/token-estimation.d.ts +72 -0
  249. package/dist/utils/token-estimation.d.ts.map +1 -0
  250. package/dist/utils/token-estimation.js +71 -0
  251. package/dist/utils/token-estimation.js.map +1 -0
  252. package/dist/utils/token-logging.d.ts +48 -0
  253. package/dist/utils/token-logging.d.ts.map +1 -0
  254. package/dist/utils/token-logging.js +112 -0
  255. package/dist/utils/token-logging.js.map +1 -0
  256. package/dist/utils/vcs-adapter.d.ts +68 -0
  257. package/dist/utils/vcs-adapter.d.ts.map +1 -0
  258. package/dist/utils/vcs-adapter.js +187 -0
  259. package/dist/utils/vcs-adapter.js.map +1 -0
  260. package/dist/utils/view-queries.d.ts +34 -0
  261. package/dist/utils/view-queries.d.ts.map +1 -0
  262. package/dist/utils/view-queries.js +192 -0
  263. package/dist/utils/view-queries.js.map +1 -0
  264. package/dist/watcher/file-watcher.d.ts +54 -4
  265. package/dist/watcher/file-watcher.d.ts.map +1 -1
  266. package/dist/watcher/file-watcher.js +329 -33
  267. package/dist/watcher/file-watcher.js.map +1 -1
  268. package/dist/watcher/gitignore-parser.d.ts +70 -0
  269. package/dist/watcher/gitignore-parser.d.ts.map +1 -0
  270. package/dist/watcher/gitignore-parser.js +191 -0
  271. package/dist/watcher/gitignore-parser.js.map +1 -0
  272. package/dist/watcher/index.d.ts +1 -0
  273. package/dist/watcher/index.d.ts.map +1 -1
  274. package/dist/watcher/index.js +1 -0
  275. package/dist/watcher/index.js.map +1 -1
  276. package/docs/AI_AGENT_GUIDE.md +1 -1
  277. package/docs/ARCHITECTURE.md +12 -0
  278. package/docs/AUTO_FILE_TRACKING.md +486 -82
  279. package/docs/BEST_PRACTICES.md +56 -448
  280. package/docs/CONFIGURATION.md +908 -0
  281. package/docs/GIT_AWARE_AUTO_COMPLETE.md +645 -0
  282. package/docs/MIGRATION_v3.3.md +602 -0
  283. package/docs/MIGRATION_v3.6.0.md +170 -0
  284. package/docs/SHARED_CONCEPTS.md +65 -209
  285. package/docs/TASK_ACTIONS.md +12 -0
  286. package/docs/TASK_OVERVIEW.md +125 -24
  287. package/docs/TASK_PRUNING.md +589 -0
  288. package/docs/TASK_SYSTEM.md +83 -13
  289. package/docs/TOOL_REFERENCE.md +94 -6
  290. package/docs/TOOL_SELECTION.md +41 -248
  291. package/package.json +21 -7
@@ -3,36 +3,23 @@
3
3
  * Tests add_dependency, remove_dependency, get_dependencies actions
4
4
  * and enhanced list/get actions with dependency support
5
5
  */
6
- import { describe, it, beforeEach } from 'node:test';
6
+ import { describe, it, beforeEach, afterEach } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
- import Database from 'better-sqlite3';
9
- import { initializeSchema } from '../schema.js';
10
- import { migrateToTaskDependencies } from '../migrations/add-task-dependencies.js';
11
- import { getOrCreateAgent, transaction } from '../database.js';
8
+ import { initializeDatabase, getOrCreateAgent, closeDatabase } from '../database.js';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import os from 'os';
12
12
  /**
13
13
  * Test database instance
14
14
  */
15
15
  let testDb;
16
- /**
17
- * Create an in-memory test database
18
- */
19
- function createTestDatabase() {
20
- const db = new Database(':memory:');
21
- db.pragma('foreign_keys = ON');
22
- // Initialize schema
23
- initializeSchema(db);
24
- // Run task dependencies migration
25
- const migrationResult = migrateToTaskDependencies(db);
26
- if (!migrationResult.success) {
27
- throw new Error(`Migration failed: ${migrationResult.message}`);
28
- }
29
- return db;
30
- }
16
+ let tempDir;
17
+ let tempDbPath;
31
18
  /**
32
19
  * Helper: Create a test task
33
20
  */
34
- function createTestTask(db, title, status = 'todo') {
35
- const agentId = getOrCreateAgent(db, 'test-agent');
21
+ async function createTestTask(db, title, status = 'todo') {
22
+ const agentId = await getOrCreateAgent(db, 'test-agent');
36
23
  const statusIdMap = {
37
24
  'todo': 1,
38
25
  'in_progress': 2,
@@ -42,181 +29,206 @@ function createTestTask(db, title, status = 'todo') {
42
29
  'archived': 6
43
30
  };
44
31
  const statusId = statusIdMap[status];
45
- const result = db.prepare(`
46
- INSERT INTO t_tasks (title, status_id, priority, created_by_agent_id, assigned_agent_id)
47
- VALUES (?, ?, 2, ?, ?)
48
- `).run(title, statusId, agentId, agentId);
49
- return result.lastInsertRowid;
32
+ const knex = db.getKnex();
33
+ const now = Math.floor(Date.now() / 1000);
34
+ const [taskId] = await knex('t_tasks').insert({
35
+ title,
36
+ status_id: statusId,
37
+ priority: 2,
38
+ created_by_agent_id: agentId,
39
+ assigned_agent_id: agentId,
40
+ created_ts: now,
41
+ updated_ts: now
42
+ });
43
+ return taskId;
50
44
  }
51
45
  /**
52
46
  * Inline implementation of addDependency for testing (avoiding module dependency)
53
47
  */
54
- function addDependencyTest(db, params) {
55
- if (!params.blocker_task_id) {
56
- throw new Error('Parameter "blocker_task_id" is required');
48
+ async function addDependencyTest(db, params) {
49
+ if (!params.depends_on_task_id) {
50
+ throw new Error('Parameter "depends_on_task_id" is required');
57
51
  }
58
- if (!params.blocked_task_id) {
59
- throw new Error('Parameter "blocked_task_id" is required');
52
+ if (!params.task_id) {
53
+ throw new Error('Parameter "task_id" is required');
60
54
  }
61
- return transaction(db, () => {
55
+ const knex = db.getKnex();
56
+ return await knex.transaction(async (trx) => {
62
57
  const TASK_STATUS_ARCHIVED = 6;
63
58
  // Validation 1: No self-dependencies
64
- if (params.blocker_task_id === params.blocked_task_id) {
59
+ if (params.depends_on_task_id === params.task_id) {
65
60
  throw new Error('Self-dependency not allowed');
66
61
  }
67
62
  // Validation 2: Both tasks must exist and check if archived
68
- const blockerTask = db.prepare('SELECT id, status_id FROM t_tasks WHERE id = ?').get(params.blocker_task_id);
69
- const blockedTask = db.prepare('SELECT id, status_id FROM t_tasks WHERE id = ?').get(params.blocked_task_id);
70
- if (!blockerTask) {
71
- throw new Error(`Blocker task #${params.blocker_task_id} not found`);
63
+ const dependsOnTask = await trx('t_tasks')
64
+ .where({ id: params.depends_on_task_id })
65
+ .select('id', 'status_id')
66
+ .first();
67
+ const task = await trx('t_tasks')
68
+ .where({ id: params.task_id })
69
+ .select('id', 'status_id')
70
+ .first();
71
+ if (!dependsOnTask) {
72
+ throw new Error(`Task #${params.depends_on_task_id} not found`);
72
73
  }
73
- if (!blockedTask) {
74
- throw new Error(`Blocked task #${params.blocked_task_id} not found`);
74
+ if (!task) {
75
+ throw new Error(`Task #${params.task_id} not found`);
75
76
  }
76
77
  // Validation 3: Neither task is archived
77
- if (blockerTask.status_id === TASK_STATUS_ARCHIVED) {
78
- throw new Error(`Cannot add dependency: Task #${params.blocker_task_id} is archived`);
78
+ if (dependsOnTask.status_id === TASK_STATUS_ARCHIVED) {
79
+ throw new Error(`Cannot add dependency: Task #${params.depends_on_task_id} is archived`);
79
80
  }
80
- if (blockedTask.status_id === TASK_STATUS_ARCHIVED) {
81
- throw new Error(`Cannot add dependency: Task #${params.blocked_task_id} is archived`);
81
+ if (task.status_id === TASK_STATUS_ARCHIVED) {
82
+ throw new Error(`Cannot add dependency: Task #${params.task_id} is archived`);
82
83
  }
83
84
  // Validation 4: No direct circular (reverse relationship)
84
- const reverseExists = db.prepare(`
85
- SELECT 1 FROM t_task_dependencies
86
- WHERE blocker_task_id = ? AND blocked_task_id = ?
87
- `).get(params.blocked_task_id, params.blocker_task_id);
85
+ const reverseExists = await trx('t_task_dependencies')
86
+ .where({
87
+ depends_on_task_id: params.task_id,
88
+ task_id: params.depends_on_task_id
89
+ })
90
+ .first();
88
91
  if (reverseExists) {
89
- throw new Error(`Circular dependency detected: Task #${params.blocked_task_id} already blocks Task #${params.blocker_task_id}`);
92
+ throw new Error(`Circular dependency detected: Task #${params.task_id} already depends on Task #${params.depends_on_task_id}`);
90
93
  }
91
94
  // Validation 5: No transitive circular (check if adding this would create a cycle)
92
- const cycleCheck = db.prepare(`
95
+ const cycleCheck = await trx.raw(`
93
96
  WITH RECURSIVE dependency_chain AS (
94
- -- Start from the task that would be blocked
95
- SELECT blocked_task_id as task_id, 1 as depth
97
+ -- Start from the task that would have the dependency
98
+ SELECT task_id, 1 as depth
96
99
  FROM t_task_dependencies
97
- WHERE blocker_task_id = ?
100
+ WHERE depends_on_task_id = ?
98
101
 
99
102
  UNION ALL
100
103
 
101
104
  -- Follow the chain of dependencies
102
- SELECT d.blocked_task_id, dc.depth + 1
105
+ SELECT d.task_id, dc.depth + 1
103
106
  FROM t_task_dependencies d
104
- JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
107
+ JOIN dependency_chain dc ON d.depends_on_task_id = dc.task_id
105
108
  WHERE dc.depth < 100
106
109
  )
107
110
  SELECT task_id FROM dependency_chain WHERE task_id = ?
108
- `).get(params.blocked_task_id, params.blocker_task_id);
109
- if (cycleCheck) {
111
+ `, [params.task_id, params.depends_on_task_id]);
112
+ const cycleResult = Array.isArray(cycleCheck) ? cycleCheck[0] : cycleCheck;
113
+ if (cycleResult && cycleResult.task_id) {
110
114
  // Build cycle path for error message
111
- const cyclePathResult = db.prepare(`
115
+ const cyclePathResult = await trx.raw(`
112
116
  WITH RECURSIVE dependency_chain AS (
113
- SELECT blocked_task_id as task_id, 1 as depth,
114
- CAST(blocked_task_id AS TEXT) as path
117
+ SELECT task_id, 1 as depth,
118
+ CAST(task_id AS TEXT) as path
115
119
  FROM t_task_dependencies
116
- WHERE blocker_task_id = ?
120
+ WHERE depends_on_task_id = ?
117
121
 
118
122
  UNION ALL
119
123
 
120
- SELECT d.blocked_task_id, dc.depth + 1,
121
- dc.path || ' → ' || d.blocked_task_id
124
+ SELECT d.task_id, dc.depth + 1,
125
+ dc.path || ' → ' || d.task_id
122
126
  FROM t_task_dependencies d
123
- JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
127
+ JOIN dependency_chain dc ON d.depends_on_task_id = dc.task_id
124
128
  WHERE dc.depth < 100
125
129
  )
126
130
  SELECT path FROM dependency_chain WHERE task_id = ? ORDER BY depth DESC LIMIT 1
127
- `).get(params.blocked_task_id, params.blocker_task_id);
128
- const cyclePath = cyclePathResult?.path || `#${params.blocked_task_id} ... → #${params.blocker_task_id}`;
129
- throw new Error(`Circular dependency detected: Task #${params.blocker_task_id} → #${cyclePath} → #${params.blocker_task_id}`);
131
+ `, [params.task_id, params.depends_on_task_id]);
132
+ const pathResult = Array.isArray(cyclePathResult) ? cyclePathResult[0] : cyclePathResult;
133
+ const cyclePath = pathResult?.path || `#${params.task_id} → ... → #${params.depends_on_task_id}`;
134
+ throw new Error(`Circular dependency detected: Task #${params.depends_on_task_id} → #${cyclePath} → #${params.depends_on_task_id}`);
130
135
  }
131
136
  // All validations passed - insert dependency
132
- const insertStmt = db.prepare(`
133
- INSERT INTO t_task_dependencies (blocker_task_id, blocked_task_id)
134
- VALUES (?, ?)
135
- `);
136
- insertStmt.run(params.blocker_task_id, params.blocked_task_id);
137
+ const now = Math.floor(Date.now() / 1000);
138
+ await trx('t_task_dependencies').insert({
139
+ depends_on_task_id: params.depends_on_task_id,
140
+ task_id: params.task_id,
141
+ created_ts: now
142
+ });
137
143
  return {
138
144
  success: true,
139
- message: `Dependency added: Task #${params.blocker_task_id} blocks Task #${params.blocked_task_id}`
145
+ message: `Dependency added: Task #${params.task_id} depends on Task #${params.depends_on_task_id}`
140
146
  };
141
147
  });
142
148
  }
143
149
  /**
144
150
  * Inline implementation of removeDependency for testing
145
151
  */
146
- function removeDependencyTest(db, params) {
147
- if (!params.blocker_task_id) {
148
- throw new Error('Parameter "blocker_task_id" is required');
152
+ async function removeDependencyTest(db, params) {
153
+ if (!params.depends_on_task_id) {
154
+ throw new Error('Parameter "depends_on_task_id" is required');
149
155
  }
150
- if (!params.blocked_task_id) {
151
- throw new Error('Parameter "blocked_task_id" is required');
156
+ if (!params.task_id) {
157
+ throw new Error('Parameter "task_id" is required');
152
158
  }
153
- const deleteStmt = db.prepare(`
154
- DELETE FROM t_task_dependencies
155
- WHERE blocker_task_id = ? AND blocked_task_id = ?
156
- `);
157
- deleteStmt.run(params.blocker_task_id, params.blocked_task_id);
159
+ const knex = db.getKnex();
160
+ await knex('t_task_dependencies')
161
+ .where({
162
+ depends_on_task_id: params.depends_on_task_id,
163
+ task_id: params.task_id
164
+ })
165
+ .delete();
158
166
  return {
159
167
  success: true,
160
- message: `Dependency removed: Task #${params.blocker_task_id} no longer blocks Task #${params.blocked_task_id}`
168
+ message: `Dependency removed: Task #${params.task_id} no longer depends on Task #${params.depends_on_task_id}`
161
169
  };
162
170
  }
163
171
  /**
164
172
  * Inline implementation of getDependencies for testing
165
173
  */
166
- function getDependenciesTest(db, params) {
174
+ async function getDependenciesTest(db, params) {
167
175
  if (!params.task_id) {
168
176
  throw new Error('Parameter "task_id" is required');
169
177
  }
170
178
  const includeDetails = params.include_details || false;
179
+ const knex = db.getKnex();
171
180
  // Check if task exists
172
- const taskExists = db.prepare('SELECT id FROM t_tasks WHERE id = ?').get(params.task_id);
181
+ const taskExists = await knex('t_tasks')
182
+ .where({ id: params.task_id })
183
+ .select('id')
184
+ .first();
173
185
  if (!taskExists) {
174
186
  throw new Error(`Task with id ${params.task_id} not found`);
175
187
  }
176
188
  // Build query based on include_details flag
177
189
  let selectFields;
178
190
  if (includeDetails) {
179
- selectFields = `
180
- t.id,
181
- t.title,
182
- s.name as status,
183
- t.priority,
184
- aa.name as assigned_to,
185
- t.created_ts,
186
- t.updated_ts,
187
- td.description
188
- `;
191
+ selectFields = [
192
+ 't.id',
193
+ 't.title',
194
+ 's.name as status',
195
+ 't.priority',
196
+ 'aa.name as assigned_to',
197
+ 't.created_ts',
198
+ 't.updated_ts',
199
+ 'td.description'
200
+ ];
189
201
  }
190
202
  else {
191
- selectFields = `
192
- t.id,
193
- t.title,
194
- s.name as status,
195
- t.priority
196
- `;
203
+ selectFields = [
204
+ 't.id',
205
+ 't.title',
206
+ 's.name as status',
207
+ 't.priority'
208
+ ];
197
209
  }
198
- // Get blockers (tasks that block this task)
199
- const blockersQuery = `
200
- SELECT ${selectFields}
201
- FROM t_tasks t
202
- JOIN t_task_dependencies d ON t.id = d.blocker_task_id
203
- LEFT JOIN m_task_statuses s ON t.status_id = s.id
204
- LEFT JOIN m_agents aa ON t.assigned_agent_id = aa.id
205
- ${includeDetails ? 'LEFT JOIN t_task_details td ON t.id = td.task_id' : ''}
206
- WHERE d.blocked_task_id = ?
207
- `;
208
- const blockers = db.prepare(blockersQuery).all(params.task_id);
209
- // Get blocking (tasks this task blocks)
210
- const blockingQuery = `
211
- SELECT ${selectFields}
212
- FROM t_tasks t
213
- JOIN t_task_dependencies d ON t.id = d.blocked_task_id
214
- LEFT JOIN m_task_statuses s ON t.status_id = s.id
215
- LEFT JOIN m_agents aa ON t.assigned_agent_id = aa.id
216
- ${includeDetails ? 'LEFT JOIN t_task_details td ON t.id = td.task_id' : ''}
217
- WHERE d.blocker_task_id = ?
218
- `;
219
- const blocking = db.prepare(blockingQuery).all(params.task_id);
210
+ // Get blockers (tasks that this task depends on)
211
+ let blockersQuery = knex('t_tasks as t')
212
+ .join('t_task_dependencies as d', 't.id', 'd.depends_on_task_id')
213
+ .leftJoin('m_task_statuses as s', 't.status_id', 's.id')
214
+ .leftJoin('m_agents as aa', 't.assigned_agent_id', 'aa.id')
215
+ .where('d.task_id', params.task_id)
216
+ .select(selectFields);
217
+ if (includeDetails) {
218
+ blockersQuery = blockersQuery.leftJoin('t_task_details as td', 't.id', 'td.task_id');
219
+ }
220
+ const blockers = await blockersQuery;
221
+ // Get blocking (tasks that depend on this task)
222
+ let blockingQuery = knex('t_tasks as t')
223
+ .join('t_task_dependencies as d', 't.id', 'd.task_id')
224
+ .leftJoin('m_task_statuses as s', 't.status_id', 's.id')
225
+ .leftJoin('m_agents as aa', 't.assigned_agent_id', 'aa.id')
226
+ .where('d.depends_on_task_id', params.task_id)
227
+ .select(selectFields);
228
+ if (includeDetails) {
229
+ blockingQuery = blockingQuery.leftJoin('t_task_details as td', 't.id', 'td.task_id');
230
+ }
231
+ const blocking = await blockingQuery;
220
232
  return {
221
233
  task_id: params.task_id,
222
234
  blockers,
@@ -226,43 +238,61 @@ function getDependenciesTest(db, params) {
226
238
  /**
227
239
  * Setup before each test
228
240
  */
229
- beforeEach(() => {
230
- // Create fresh database for each test
231
- testDb = createTestDatabase();
241
+ beforeEach(async () => {
242
+ // Create temp directory for test files and database
243
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlew-test-'));
244
+ tempDbPath = path.join(tempDir, 'test.db');
245
+ // Initialize database with Knex adapter
246
+ testDb = await initializeDatabase({
247
+ databaseType: 'sqlite',
248
+ connection: {
249
+ filename: tempDbPath,
250
+ },
251
+ });
252
+ });
253
+ afterEach(async () => {
254
+ await closeDatabase();
255
+ // Cleanup temp directory
256
+ if (fs.existsSync(tempDir)) {
257
+ fs.rmSync(tempDir, { recursive: true, force: true });
258
+ }
232
259
  });
233
260
  // ============================================================================
234
261
  // Task #66: Unit Tests for add_dependency Validation
235
262
  // ============================================================================
236
263
  describe('add_dependency - Success Cases', () => {
237
- it('should add valid dependency', () => {
264
+ it('should add valid dependency', async () => {
238
265
  // Arrange
239
- const task1 = createTestTask(testDb, 'Task 1');
240
- const task2 = createTestTask(testDb, 'Task 2');
266
+ const task1 = await createTestTask(testDb, 'Task 1');
267
+ const task2 = await createTestTask(testDb, 'Task 2');
241
268
  // Act
242
- const result = addDependencyTest(testDb, {
243
- blocker_task_id: task1,
244
- blocked_task_id: task2
269
+ const result = await addDependencyTest(testDb, {
270
+ depends_on_task_id: task1,
271
+ task_id: task2
245
272
  });
246
273
  // Assert
247
274
  assert.strictEqual(result.success, true);
248
275
  assert.match(result.message, /Dependency added/);
249
276
  // Verify in database
250
- const deps = testDb.prepare(`
251
- SELECT * FROM t_task_dependencies
252
- WHERE blocker_task_id = ? AND blocked_task_id = ?
253
- `).get(task1, task2);
277
+ const knex = testDb.getKnex();
278
+ const deps = await knex('t_task_dependencies')
279
+ .where({
280
+ depends_on_task_id: task1,
281
+ task_id: task2
282
+ })
283
+ .first();
254
284
  assert.ok(deps);
255
285
  });
256
- it('should add valid dependency and verify via get_dependencies', () => {
286
+ it('should add valid dependency and verify via get_dependencies', async () => {
257
287
  // Arrange
258
- const task1 = createTestTask(testDb, 'Task 1');
259
- const task2 = createTestTask(testDb, 'Task 2');
288
+ const task1 = await createTestTask(testDb, 'Task 1');
289
+ const task2 = await createTestTask(testDb, 'Task 2');
260
290
  // Act
261
- addDependencyTest(testDb, {
262
- blocker_task_id: task1,
263
- blocked_task_id: task2
291
+ await addDependencyTest(testDb, {
292
+ depends_on_task_id: task1,
293
+ task_id: task2
264
294
  });
265
- const result = getDependenciesTest(testDb, { task_id: task2 });
295
+ const result = await getDependenciesTest(testDb, { task_id: task2 });
266
296
  // Assert
267
297
  assert.strictEqual(result.task_id, task2);
268
298
  assert.strictEqual(result.blockers.length, 1);
@@ -271,14 +301,14 @@ describe('add_dependency - Success Cases', () => {
271
301
  });
272
302
  });
273
303
  describe('add_dependency - Validation: Self-Dependency', () => {
274
- it('should reject self-dependency', () => {
304
+ it('should reject self-dependency', async () => {
275
305
  // Arrange
276
- const task1 = createTestTask(testDb, 'Task 1');
306
+ const task1 = await createTestTask(testDb, 'Task 1');
277
307
  // Act & Assert
278
- assert.throws(() => {
279
- addDependencyTest(testDb, {
280
- blocker_task_id: task1,
281
- blocked_task_id: task1
308
+ await assert.rejects(async () => {
309
+ await addDependencyTest(testDb, {
310
+ depends_on_task_id: task1,
311
+ task_id: task1
282
312
  });
283
313
  }, {
284
314
  message: /Self-dependency not allowed/
@@ -286,20 +316,20 @@ describe('add_dependency - Validation: Self-Dependency', () => {
286
316
  });
287
317
  });
288
318
  describe('add_dependency - Validation: Direct Circular', () => {
289
- it('should reject direct circular dependency', () => {
319
+ it('should reject direct circular dependency', async () => {
290
320
  // Arrange
291
- const taskA = createTestTask(testDb, 'Task A');
292
- const taskB = createTestTask(testDb, 'Task B');
293
- // Add A blocks B
294
- addDependencyTest(testDb, {
295
- blocker_task_id: taskA,
296
- blocked_task_id: taskB
321
+ const taskA = await createTestTask(testDb, 'Task A');
322
+ const taskB = await createTestTask(testDb, 'Task B');
323
+ // Add A blocks B (B depends on A)
324
+ await addDependencyTest(testDb, {
325
+ depends_on_task_id: taskA,
326
+ task_id: taskB
297
327
  });
298
- // Act & Assert - Try to add B blocks A
299
- assert.throws(() => {
300
- addDependencyTest(testDb, {
301
- blocker_task_id: taskB,
302
- blocked_task_id: taskA
328
+ // Act & Assert - Try to add B blocks A (A depends on B)
329
+ await assert.rejects(async () => {
330
+ await addDependencyTest(testDb, {
331
+ depends_on_task_id: taskB,
332
+ task_id: taskA
303
333
  });
304
334
  }, {
305
335
  message: /Circular dependency detected/
@@ -307,51 +337,51 @@ describe('add_dependency - Validation: Direct Circular', () => {
307
337
  });
308
338
  });
309
339
  describe('add_dependency - Validation: Transitive Circular', () => {
310
- it('should reject transitive circular dependency (A→B→C→A)', () => {
340
+ it('should reject transitive circular dependency (A→B→C→A)', async () => {
311
341
  // Arrange
312
- const taskA = createTestTask(testDb, 'Task A');
313
- const taskB = createTestTask(testDb, 'Task B');
314
- const taskC = createTestTask(testDb, 'Task C');
315
- // Add A blocks B
316
- addDependencyTest(testDb, {
317
- blocker_task_id: taskA,
318
- blocked_task_id: taskB
342
+ const taskA = await createTestTask(testDb, 'Task A');
343
+ const taskB = await createTestTask(testDb, 'Task B');
344
+ const taskC = await createTestTask(testDb, 'Task C');
345
+ // Add A blocks B (B depends on A)
346
+ await addDependencyTest(testDb, {
347
+ depends_on_task_id: taskA,
348
+ task_id: taskB
319
349
  });
320
- // Add B blocks C
321
- addDependencyTest(testDb, {
322
- blocker_task_id: taskB,
323
- blocked_task_id: taskC
350
+ // Add B blocks C (C depends on B)
351
+ await addDependencyTest(testDb, {
352
+ depends_on_task_id: taskB,
353
+ task_id: taskC
324
354
  });
325
- // Act & Assert - Try to add C blocks A (would create cycle)
326
- assert.throws(() => {
327
- addDependencyTest(testDb, {
328
- blocker_task_id: taskC,
329
- blocked_task_id: taskA
355
+ // Act & Assert - Try to add C blocks A (A depends on C, would create cycle)
356
+ await assert.rejects(async () => {
357
+ await addDependencyTest(testDb, {
358
+ depends_on_task_id: taskC,
359
+ task_id: taskA
330
360
  });
331
361
  }, {
332
362
  message: /Circular dependency detected/
333
363
  });
334
364
  });
335
- it('should reject transitive circular with cycle path in error message', () => {
365
+ it('should reject transitive circular with cycle path in error message', async () => {
336
366
  // Arrange
337
- const task1 = createTestTask(testDb, 'Task 1');
338
- const task2 = createTestTask(testDb, 'Task 2');
339
- const task3 = createTestTask(testDb, 'Task 3');
340
- // Create chain: 1 → 2 → 3
341
- addDependencyTest(testDb, {
342
- blocker_task_id: task1,
343
- blocked_task_id: task2
367
+ const task1 = await createTestTask(testDb, 'Task 1');
368
+ const task2 = await createTestTask(testDb, 'Task 2');
369
+ const task3 = await createTestTask(testDb, 'Task 3');
370
+ // Create chain: 1 → 2 → 3 (2 depends on 1, 3 depends on 2)
371
+ await addDependencyTest(testDb, {
372
+ depends_on_task_id: task1,
373
+ task_id: task2
344
374
  });
345
- addDependencyTest(testDb, {
346
- blocker_task_id: task2,
347
- blocked_task_id: task3
375
+ await addDependencyTest(testDb, {
376
+ depends_on_task_id: task2,
377
+ task_id: task3
348
378
  });
349
- // Act & Assert - Try to add 3 → 1
379
+ // Act & Assert - Try to add 3 → 1 (1 depends on 3)
350
380
  let errorMessage = '';
351
381
  try {
352
- addDependencyTest(testDb, {
353
- blocker_task_id: task3,
354
- blocked_task_id: task1
382
+ await addDependencyTest(testDb, {
383
+ depends_on_task_id: task3,
384
+ task_id: task1
355
385
  });
356
386
  }
357
387
  catch (error) {
@@ -366,57 +396,57 @@ describe('add_dependency - Validation: Transitive Circular', () => {
366
396
  });
367
397
  });
368
398
  describe('add_dependency - Validation: Non-Existent Tasks', () => {
369
- it('should reject dependency with non-existent blocker', () => {
399
+ it('should reject dependency with non-existent blocker', async () => {
370
400
  // Arrange
371
- const task1 = createTestTask(testDb, 'Task 1');
401
+ const task1 = await createTestTask(testDb, 'Task 1');
372
402
  // Act & Assert
373
- assert.throws(() => {
374
- addDependencyTest(testDb, {
375
- blocker_task_id: 999,
376
- blocked_task_id: task1
403
+ await assert.rejects(async () => {
404
+ await addDependencyTest(testDb, {
405
+ depends_on_task_id: 999,
406
+ task_id: task1
377
407
  });
378
408
  }, {
379
- message: /Blocker task #999 not found/
409
+ message: /Task #999 not found/
380
410
  });
381
411
  });
382
- it('should reject dependency with non-existent blocked', () => {
412
+ it('should reject dependency with non-existent blocked', async () => {
383
413
  // Arrange
384
- const task1 = createTestTask(testDb, 'Task 1');
414
+ const task1 = await createTestTask(testDb, 'Task 1');
385
415
  // Act & Assert
386
- assert.throws(() => {
387
- addDependencyTest(testDb, {
388
- blocker_task_id: task1,
389
- blocked_task_id: 999
416
+ await assert.rejects(async () => {
417
+ await addDependencyTest(testDb, {
418
+ depends_on_task_id: task1,
419
+ task_id: 999
390
420
  });
391
421
  }, {
392
- message: /Blocked task #999 not found/
422
+ message: /Task #999 not found/
393
423
  });
394
424
  });
395
425
  });
396
426
  describe('add_dependency - Validation: Archived Tasks', () => {
397
- it('should reject dependency with archived blocker', () => {
427
+ it('should reject dependency with archived blocker', async () => {
398
428
  // Arrange
399
- const task1 = createTestTask(testDb, 'Task 1', 'archived');
400
- const task2 = createTestTask(testDb, 'Task 2');
429
+ const task1 = await createTestTask(testDb, 'Task 1', 'archived');
430
+ const task2 = await createTestTask(testDb, 'Task 2');
401
431
  // Act & Assert
402
- assert.throws(() => {
403
- addDependencyTest(testDb, {
404
- blocker_task_id: task1,
405
- blocked_task_id: task2
432
+ await assert.rejects(async () => {
433
+ await addDependencyTest(testDb, {
434
+ depends_on_task_id: task1,
435
+ task_id: task2
406
436
  });
407
437
  }, {
408
438
  message: /Cannot add dependency: Task #\d+ is archived/
409
439
  });
410
440
  });
411
- it('should reject dependency with archived blocked', () => {
441
+ it('should reject dependency with archived blocked', async () => {
412
442
  // Arrange
413
- const task1 = createTestTask(testDb, 'Task 1');
414
- const task2 = createTestTask(testDb, 'Task 2', 'archived');
443
+ const task1 = await createTestTask(testDb, 'Task 1');
444
+ const task2 = await createTestTask(testDb, 'Task 2', 'archived');
415
445
  // Act & Assert
416
- assert.throws(() => {
417
- addDependencyTest(testDb, {
418
- blocker_task_id: task1,
419
- blocked_task_id: task2
446
+ await assert.rejects(async () => {
447
+ await addDependencyTest(testDb, {
448
+ depends_on_task_id: task1,
449
+ task_id: task2
420
450
  });
421
451
  }, {
422
452
  message: /Cannot add dependency: Task #\d+ is archived/
@@ -427,37 +457,37 @@ describe('add_dependency - Validation: Archived Tasks', () => {
427
457
  // Task #67: Unit Tests for remove_dependency and get_dependencies
428
458
  // ============================================================================
429
459
  describe('remove_dependency', () => {
430
- it('should remove existing dependency', () => {
460
+ it('should remove existing dependency', async () => {
431
461
  // Arrange
432
- const task1 = createTestTask(testDb, 'Task 1');
433
- const task2 = createTestTask(testDb, 'Task 2');
434
- addDependencyTest(testDb, {
435
- blocker_task_id: task1,
436
- blocked_task_id: task2
462
+ const task1 = await createTestTask(testDb, 'Task 1');
463
+ const task2 = await createTestTask(testDb, 'Task 2');
464
+ await addDependencyTest(testDb, {
465
+ depends_on_task_id: task1,
466
+ task_id: task2
437
467
  });
438
468
  // Verify it exists
439
- const beforeDeps = getDependenciesTest(testDb, { task_id: task2 });
469
+ const beforeDeps = await getDependenciesTest(testDb, { task_id: task2 });
440
470
  assert.strictEqual(beforeDeps.blockers.length, 1);
441
471
  // Act
442
- const result = removeDependencyTest(testDb, {
443
- blocker_task_id: task1,
444
- blocked_task_id: task2
472
+ const result = await removeDependencyTest(testDb, {
473
+ depends_on_task_id: task1,
474
+ task_id: task2
445
475
  });
446
476
  // Assert
447
477
  assert.strictEqual(result.success, true);
448
478
  assert.match(result.message, /Dependency removed/);
449
479
  // Verify it no longer exists
450
- const afterDeps = getDependenciesTest(testDb, { task_id: task2 });
480
+ const afterDeps = await getDependenciesTest(testDb, { task_id: task2 });
451
481
  assert.strictEqual(afterDeps.blockers.length, 0);
452
482
  });
453
- it('should succeed silently when removing non-existent dependency (idempotent)', () => {
483
+ it('should succeed silently when removing non-existent dependency (idempotent)', async () => {
454
484
  // Arrange
455
- const task1 = createTestTask(testDb, 'Task 1');
456
- const task2 = createTestTask(testDb, 'Task 2');
485
+ const task1 = await createTestTask(testDb, 'Task 1');
486
+ const task2 = await createTestTask(testDb, 'Task 2');
457
487
  // Act - Remove dependency that doesn't exist
458
- const result = removeDependencyTest(testDb, {
459
- blocker_task_id: task1,
460
- blocked_task_id: task2
488
+ const result = await removeDependencyTest(testDb, {
489
+ depends_on_task_id: task1,
490
+ task_id: task2
461
491
  });
462
492
  // Assert
463
493
  assert.strictEqual(result.success, true);
@@ -465,22 +495,22 @@ describe('remove_dependency', () => {
465
495
  });
466
496
  });
467
497
  describe('get_dependencies - Metadata Only', () => {
468
- it('should return blockers and blocking (metadata-only)', () => {
498
+ it('should return blockers and blocking (metadata-only)', async () => {
469
499
  // Arrange
470
- const taskA = createTestTask(testDb, 'Task A');
471
- const taskB = createTestTask(testDb, 'Task B');
472
- const taskC = createTestTask(testDb, 'Task C');
473
- // A blocks B, B blocks C
474
- addDependencyTest(testDb, {
475
- blocker_task_id: taskA,
476
- blocked_task_id: taskB
500
+ const taskA = await createTestTask(testDb, 'Task A');
501
+ const taskB = await createTestTask(testDb, 'Task B');
502
+ const taskC = await createTestTask(testDb, 'Task C');
503
+ // A blocks B, B blocks C (B depends on A, C depends on B)
504
+ await addDependencyTest(testDb, {
505
+ depends_on_task_id: taskA,
506
+ task_id: taskB
477
507
  });
478
- addDependencyTest(testDb, {
479
- blocker_task_id: taskB,
480
- blocked_task_id: taskC
508
+ await addDependencyTest(testDb, {
509
+ depends_on_task_id: taskB,
510
+ task_id: taskC
481
511
  });
482
512
  // Act - Get dependencies for B
483
- const result = getDependenciesTest(testDb, { task_id: taskB });
513
+ const result = await getDependenciesTest(testDb, { task_id: taskB });
484
514
  // Assert
485
515
  assert.strictEqual(result.task_id, taskB);
486
516
  // B is blocked by A
@@ -495,11 +525,11 @@ describe('get_dependencies - Metadata Only', () => {
495
525
  assert.strictEqual(result.blockers[0].description, undefined);
496
526
  assert.strictEqual(result.blocking[0].description, undefined);
497
527
  });
498
- it('should return empty arrays for task with no dependencies', () => {
528
+ it('should return empty arrays for task with no dependencies', async () => {
499
529
  // Arrange
500
- const task1 = createTestTask(testDb, 'Task 1');
530
+ const task1 = await createTestTask(testDb, 'Task 1');
501
531
  // Act
502
- const result = getDependenciesTest(testDb, { task_id: task1 });
532
+ const result = await getDependenciesTest(testDb, { task_id: task1 });
503
533
  // Assert
504
534
  assert.strictEqual(result.task_id, task1);
505
535
  assert.strictEqual(result.blockers.length, 0);
@@ -507,22 +537,23 @@ describe('get_dependencies - Metadata Only', () => {
507
537
  });
508
538
  });
509
539
  describe('get_dependencies - With Details', () => {
510
- it('should return full details when include_details=true', () => {
540
+ it('should return full details when include_details=true', async () => {
511
541
  // Arrange
512
- const task1 = createTestTask(testDb, 'Task 1');
513
- const task2 = createTestTask(testDb, 'Task 2');
542
+ const task1 = await createTestTask(testDb, 'Task 1');
543
+ const task2 = await createTestTask(testDb, 'Task 2');
514
544
  // Add description to task1
515
- testDb.prepare(`
516
- INSERT INTO t_task_details (task_id, description)
517
- VALUES (?, ?)
518
- `).run(task1, 'This is task 1 description');
519
- // Add dependency
520
- addDependencyTest(testDb, {
521
- blocker_task_id: task1,
522
- blocked_task_id: task2
545
+ const knex = testDb.getKnex();
546
+ await knex('t_task_details').insert({
547
+ task_id: task1,
548
+ description: 'This is task 1 description'
549
+ });
550
+ // Add dependency (task2 depends on task1)
551
+ await addDependencyTest(testDb, {
552
+ depends_on_task_id: task1,
553
+ task_id: task2
523
554
  });
524
555
  // Act
525
- const result = getDependenciesTest(testDb, {
556
+ const result = await getDependenciesTest(testDb, {
526
557
  task_id: task2,
527
558
  include_details: true
528
559
  });
@@ -531,81 +562,81 @@ describe('get_dependencies - With Details', () => {
531
562
  assert.strictEqual(result.blockers[0].id, task1);
532
563
  assert.strictEqual(result.blockers[0].description, 'This is task 1 description');
533
564
  });
534
- it('should not include description by default', () => {
565
+ it('should not include description by default', async () => {
535
566
  // Arrange
536
- const task1 = createTestTask(testDb, 'Task 1');
537
- const task2 = createTestTask(testDb, 'Task 2');
567
+ const task1 = await createTestTask(testDb, 'Task 1');
568
+ const task2 = await createTestTask(testDb, 'Task 2');
538
569
  // Add description
539
- testDb.prepare(`
540
- INSERT INTO t_task_details (task_id, description)
541
- VALUES (?, ?)
542
- `).run(task1, 'This is task 1 description');
543
- addDependencyTest(testDb, {
544
- blocker_task_id: task1,
545
- blocked_task_id: task2
570
+ const knex = testDb.getKnex();
571
+ await knex('t_task_details').insert({
572
+ task_id: task1,
573
+ description: 'This is task 1 description'
574
+ });
575
+ await addDependencyTest(testDb, {
576
+ depends_on_task_id: task1,
577
+ task_id: task2
546
578
  });
547
579
  // Act
548
- const result = getDependenciesTest(testDb, { task_id: task2 });
580
+ const result = await getDependenciesTest(testDb, { task_id: task2 });
549
581
  // Assert
550
582
  assert.strictEqual(result.blockers[0].description, undefined);
551
583
  });
552
584
  });
553
585
  describe('get_dependencies - Error Handling', () => {
554
- it('should throw error for non-existent task', () => {
586
+ it('should throw error for non-existent task', async () => {
555
587
  // Act & Assert
556
- assert.throws(() => {
557
- getDependenciesTest(testDb, { task_id: 999 });
588
+ await assert.rejects(async () => {
589
+ await getDependenciesTest(testDb, { task_id: 999 });
558
590
  }, {
559
591
  message: /Task with id 999 not found/
560
592
  });
561
593
  });
562
594
  });
563
595
  describe('CASCADE Deletion', () => {
564
- it('should cascade delete dependencies when task deleted', () => {
596
+ it('should cascade delete dependencies when task deleted', async () => {
565
597
  // Arrange
566
- const taskA = createTestTask(testDb, 'Task A');
567
- const taskB = createTestTask(testDb, 'Task B');
568
- // Add A blocks B
569
- addDependencyTest(testDb, {
570
- blocker_task_id: taskA,
571
- blocked_task_id: taskB
598
+ const taskA = await createTestTask(testDb, 'Task A');
599
+ const taskB = await createTestTask(testDb, 'Task B');
600
+ // Add A blocks B (B depends on A)
601
+ await addDependencyTest(testDb, {
602
+ depends_on_task_id: taskA,
603
+ task_id: taskB
572
604
  });
573
605
  // Verify dependency exists
574
- const beforeDeps = getDependenciesTest(testDb, { task_id: taskB });
606
+ const beforeDeps = await getDependenciesTest(testDb, { task_id: taskB });
575
607
  assert.strictEqual(beforeDeps.blockers.length, 1);
576
608
  // Act - Delete task A
577
- testDb.prepare('DELETE FROM t_tasks WHERE id = ?').run(taskA);
609
+ const knex = testDb.getKnex();
610
+ await knex('t_tasks').where({ id: taskA }).delete();
578
611
  // Assert - Dependency should be deleted
579
- const depsInDb = testDb.prepare(`
580
- SELECT * FROM t_task_dependencies
581
- WHERE blocker_task_id = ? OR blocked_task_id = ?
582
- `).all(taskA, taskA);
612
+ const depsInDb = await knex('t_task_dependencies')
613
+ .where('depends_on_task_id', taskA)
614
+ .orWhere('task_id', taskA);
583
615
  assert.strictEqual(depsInDb.length, 0);
584
616
  // Verify B still exists
585
- const taskBExists = testDb.prepare('SELECT id FROM t_tasks WHERE id = ?').get(taskB);
617
+ const taskBExists = await knex('t_tasks').where({ id: taskB }).first();
586
618
  assert.ok(taskBExists);
587
619
  // Verify B has no dependencies anymore
588
- const afterDeps = getDependenciesTest(testDb, { task_id: taskB });
620
+ const afterDeps = await getDependenciesTest(testDb, { task_id: taskB });
589
621
  assert.strictEqual(afterDeps.blockers.length, 0);
590
622
  });
591
- it('should cascade delete when blocked task is deleted', () => {
623
+ it('should cascade delete when blocked task is deleted', async () => {
592
624
  // Arrange
593
- const taskA = createTestTask(testDb, 'Task A');
594
- const taskB = createTestTask(testDb, 'Task B');
595
- addDependencyTest(testDb, {
596
- blocker_task_id: taskA,
597
- blocked_task_id: taskB
625
+ const taskA = await createTestTask(testDb, 'Task A');
626
+ const taskB = await createTestTask(testDb, 'Task B');
627
+ await addDependencyTest(testDb, {
628
+ depends_on_task_id: taskA,
629
+ task_id: taskB
598
630
  });
599
631
  // Act - Delete task B
600
- testDb.prepare('DELETE FROM t_tasks WHERE id = ?').run(taskB);
632
+ const knex = testDb.getKnex();
633
+ await knex('t_tasks').where({ id: taskB }).delete();
601
634
  // Assert - Dependency should be deleted
602
- const depsInDb = testDb.prepare(`
603
- SELECT * FROM t_task_dependencies
604
- WHERE blocked_task_id = ?
605
- `).all(taskB);
635
+ const depsInDb = await knex('t_task_dependencies')
636
+ .where('task_id', taskB);
606
637
  assert.strictEqual(depsInDb.length, 0);
607
638
  // Verify A still exists with no dependencies
608
- const afterDeps = getDependenciesTest(testDb, { task_id: taskA });
639
+ const afterDeps = await getDependenciesTest(testDb, { task_id: taskA });
609
640
  assert.strictEqual(afterDeps.blocking.length, 0);
610
641
  });
611
642
  });