opentasks 0.0.5 → 0.0.6

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 (311) hide show
  1. package/README.md +40 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +153 -8
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/schema.d.ts +54 -0
  6. package/dist/config/schema.d.ts.map +1 -1
  7. package/dist/config/schema.js +52 -0
  8. package/dist/config/schema.js.map +1 -1
  9. package/dist/core/merge-driver.d.ts +6 -2
  10. package/dist/core/merge-driver.d.ts.map +1 -1
  11. package/dist/core/merge-driver.js +11 -7
  12. package/dist/core/merge-driver.js.map +1 -1
  13. package/dist/core/worktree.js +1 -1
  14. package/dist/core/worktree.js.map +1 -1
  15. package/dist/daemon/ipc.d.ts.map +1 -1
  16. package/dist/daemon/ipc.js +4 -1
  17. package/dist/daemon/ipc.js.map +1 -1
  18. package/dist/daemon/lifecycle.d.ts.map +1 -1
  19. package/dist/daemon/lifecycle.js +5 -0
  20. package/dist/daemon/lifecycle.js.map +1 -1
  21. package/dist/graph/git-graph-syncer.js +1 -1
  22. package/dist/graph/git-graph-syncer.js.map +1 -1
  23. package/dist/providers/from-config.d.ts +3 -0
  24. package/dist/providers/from-config.d.ts.map +1 -1
  25. package/dist/providers/from-config.js +29 -0
  26. package/dist/providers/from-config.js.map +1 -1
  27. package/dist/providers/index.d.ts +3 -0
  28. package/dist/providers/index.d.ts.map +1 -1
  29. package/dist/providers/index.js +6 -0
  30. package/dist/providers/index.js.map +1 -1
  31. package/dist/providers/map-client-factory.d.ts +55 -0
  32. package/dist/providers/map-client-factory.d.ts.map +1 -0
  33. package/dist/providers/map-client-factory.js +123 -0
  34. package/dist/providers/map-client-factory.js.map +1 -0
  35. package/dist/providers/map-event-bridge.d.ts +146 -0
  36. package/dist/providers/map-event-bridge.d.ts.map +1 -0
  37. package/dist/providers/map-event-bridge.js +208 -0
  38. package/dist/providers/map-event-bridge.js.map +1 -0
  39. package/dist/providers/map.d.ts +115 -0
  40. package/dist/providers/map.d.ts.map +1 -0
  41. package/dist/providers/map.js +376 -0
  42. package/dist/providers/map.js.map +1 -0
  43. package/package.json +1 -1
  44. package/dist/__tests__/cli-tools.test.d.ts +0 -8
  45. package/dist/__tests__/cli-tools.test.d.ts.map +0 -1
  46. package/dist/__tests__/cli-tools.test.js +0 -546
  47. package/dist/__tests__/cli-tools.test.js.map +0 -1
  48. package/dist/__tests__/cli.test.d.ts +0 -5
  49. package/dist/__tests__/cli.test.d.ts.map +0 -1
  50. package/dist/__tests__/cli.test.js +0 -77
  51. package/dist/__tests__/cli.test.js.map +0 -1
  52. package/dist/__tests__/p1-p3-gaps.test.d.ts +0 -2
  53. package/dist/__tests__/p1-p3-gaps.test.d.ts.map +0 -1
  54. package/dist/__tests__/p1-p3-gaps.test.js +0 -463
  55. package/dist/__tests__/p1-p3-gaps.test.js.map +0 -1
  56. package/dist/client/__tests__/client-crud.test.d.ts +0 -7
  57. package/dist/client/__tests__/client-crud.test.d.ts.map +0 -1
  58. package/dist/client/__tests__/client-crud.test.js +0 -404
  59. package/dist/client/__tests__/client-crud.test.js.map +0 -1
  60. package/dist/client/__tests__/client.test.d.ts +0 -5
  61. package/dist/client/__tests__/client.test.d.ts.map +0 -1
  62. package/dist/client/__tests__/client.test.js +0 -518
  63. package/dist/client/__tests__/client.test.js.map +0 -1
  64. package/dist/config/__tests__/defaults.test.d.ts +0 -2
  65. package/dist/config/__tests__/defaults.test.d.ts.map +0 -1
  66. package/dist/config/__tests__/defaults.test.js +0 -57
  67. package/dist/config/__tests__/defaults.test.js.map +0 -1
  68. package/dist/config/__tests__/env.test.d.ts +0 -2
  69. package/dist/config/__tests__/env.test.d.ts.map +0 -1
  70. package/dist/config/__tests__/env.test.js +0 -136
  71. package/dist/config/__tests__/env.test.js.map +0 -1
  72. package/dist/config/__tests__/index.test.d.ts +0 -2
  73. package/dist/config/__tests__/index.test.d.ts.map +0 -1
  74. package/dist/config/__tests__/index.test.js +0 -113
  75. package/dist/config/__tests__/index.test.js.map +0 -1
  76. package/dist/config/__tests__/loader.test.d.ts +0 -2
  77. package/dist/config/__tests__/loader.test.d.ts.map +0 -1
  78. package/dist/config/__tests__/loader.test.js +0 -128
  79. package/dist/config/__tests__/loader.test.js.map +0 -1
  80. package/dist/config/__tests__/merge.test.d.ts +0 -2
  81. package/dist/config/__tests__/merge.test.d.ts.map +0 -1
  82. package/dist/config/__tests__/merge.test.js +0 -79
  83. package/dist/config/__tests__/merge.test.js.map +0 -1
  84. package/dist/config/__tests__/schema.test.d.ts +0 -2
  85. package/dist/config/__tests__/schema.test.d.ts.map +0 -1
  86. package/dist/config/__tests__/schema.test.js +0 -300
  87. package/dist/config/__tests__/schema.test.js.map +0 -1
  88. package/dist/core/__tests__/conditional-redirects.test.d.ts +0 -2
  89. package/dist/core/__tests__/conditional-redirects.test.d.ts.map +0 -1
  90. package/dist/core/__tests__/conditional-redirects.test.js +0 -83
  91. package/dist/core/__tests__/conditional-redirects.test.js.map +0 -1
  92. package/dist/core/__tests__/connections.test.d.ts +0 -2
  93. package/dist/core/__tests__/connections.test.d.ts.map +0 -1
  94. package/dist/core/__tests__/connections.test.js +0 -158
  95. package/dist/core/__tests__/connections.test.js.map +0 -1
  96. package/dist/core/__tests__/hash.test.d.ts +0 -2
  97. package/dist/core/__tests__/hash.test.d.ts.map +0 -1
  98. package/dist/core/__tests__/hash.test.js +0 -139
  99. package/dist/core/__tests__/hash.test.js.map +0 -1
  100. package/dist/core/__tests__/id.test.d.ts +0 -2
  101. package/dist/core/__tests__/id.test.d.ts.map +0 -1
  102. package/dist/core/__tests__/id.test.js +0 -142
  103. package/dist/core/__tests__/id.test.js.map +0 -1
  104. package/dist/core/__tests__/location.test.d.ts +0 -2
  105. package/dist/core/__tests__/location.test.d.ts.map +0 -1
  106. package/dist/core/__tests__/location.test.js +0 -77
  107. package/dist/core/__tests__/location.test.js.map +0 -1
  108. package/dist/core/__tests__/merge-driver.test.d.ts +0 -2
  109. package/dist/core/__tests__/merge-driver.test.d.ts.map +0 -1
  110. package/dist/core/__tests__/merge-driver.test.js +0 -218
  111. package/dist/core/__tests__/merge-driver.test.js.map +0 -1
  112. package/dist/core/__tests__/redirects.test.d.ts +0 -2
  113. package/dist/core/__tests__/redirects.test.d.ts.map +0 -1
  114. package/dist/core/__tests__/redirects.test.js +0 -123
  115. package/dist/core/__tests__/redirects.test.js.map +0 -1
  116. package/dist/core/__tests__/resolve-location-target.test.d.ts +0 -8
  117. package/dist/core/__tests__/resolve-location-target.test.d.ts.map +0 -1
  118. package/dist/core/__tests__/resolve-location-target.test.js +0 -303
  119. package/dist/core/__tests__/resolve-location-target.test.js.map +0 -1
  120. package/dist/core/__tests__/uri.test.d.ts +0 -2
  121. package/dist/core/__tests__/uri.test.d.ts.map +0 -1
  122. package/dist/core/__tests__/uri.test.js +0 -159
  123. package/dist/core/__tests__/uri.test.js.map +0 -1
  124. package/dist/core/__tests__/worktree.test.d.ts +0 -2
  125. package/dist/core/__tests__/worktree.test.d.ts.map +0 -1
  126. package/dist/core/__tests__/worktree.test.js +0 -120
  127. package/dist/core/__tests__/worktree.test.js.map +0 -1
  128. package/dist/daemon/__tests__/flush.test.d.ts +0 -5
  129. package/dist/daemon/__tests__/flush.test.d.ts.map +0 -1
  130. package/dist/daemon/__tests__/flush.test.js +0 -213
  131. package/dist/daemon/__tests__/flush.test.js.map +0 -1
  132. package/dist/daemon/__tests__/integration.test.d.ts +0 -7
  133. package/dist/daemon/__tests__/integration.test.d.ts.map +0 -1
  134. package/dist/daemon/__tests__/integration.test.js +0 -276
  135. package/dist/daemon/__tests__/integration.test.js.map +0 -1
  136. package/dist/daemon/__tests__/ipc.test.d.ts +0 -5
  137. package/dist/daemon/__tests__/ipc.test.d.ts.map +0 -1
  138. package/dist/daemon/__tests__/ipc.test.js +0 -314
  139. package/dist/daemon/__tests__/ipc.test.js.map +0 -1
  140. package/dist/daemon/__tests__/lifecycle.test.d.ts +0 -5
  141. package/dist/daemon/__tests__/lifecycle.test.d.ts.map +0 -1
  142. package/dist/daemon/__tests__/lifecycle.test.js +0 -301
  143. package/dist/daemon/__tests__/lifecycle.test.js.map +0 -1
  144. package/dist/daemon/__tests__/lock.test.d.ts +0 -5
  145. package/dist/daemon/__tests__/lock.test.d.ts.map +0 -1
  146. package/dist/daemon/__tests__/lock.test.js +0 -192
  147. package/dist/daemon/__tests__/lock.test.js.map +0 -1
  148. package/dist/daemon/__tests__/methods/graph.test.d.ts +0 -5
  149. package/dist/daemon/__tests__/methods/graph.test.d.ts.map +0 -1
  150. package/dist/daemon/__tests__/methods/graph.test.js +0 -309
  151. package/dist/daemon/__tests__/methods/graph.test.js.map +0 -1
  152. package/dist/daemon/__tests__/methods/provider.test.d.ts +0 -7
  153. package/dist/daemon/__tests__/methods/provider.test.d.ts.map +0 -1
  154. package/dist/daemon/__tests__/methods/provider.test.js +0 -181
  155. package/dist/daemon/__tests__/methods/provider.test.js.map +0 -1
  156. package/dist/daemon/__tests__/methods/tools.test.d.ts +0 -5
  157. package/dist/daemon/__tests__/methods/tools.test.d.ts.map +0 -1
  158. package/dist/daemon/__tests__/methods/tools.test.js +0 -587
  159. package/dist/daemon/__tests__/methods/tools.test.js.map +0 -1
  160. package/dist/daemon/__tests__/multi-location.test.d.ts +0 -8
  161. package/dist/daemon/__tests__/multi-location.test.d.ts.map +0 -1
  162. package/dist/daemon/__tests__/multi-location.test.js +0 -669
  163. package/dist/daemon/__tests__/multi-location.test.js.map +0 -1
  164. package/dist/daemon/__tests__/registry.test.d.ts +0 -5
  165. package/dist/daemon/__tests__/registry.test.d.ts.map +0 -1
  166. package/dist/daemon/__tests__/registry.test.js +0 -208
  167. package/dist/daemon/__tests__/registry.test.js.map +0 -1
  168. package/dist/daemon/__tests__/watcher.test.d.ts +0 -5
  169. package/dist/daemon/__tests__/watcher.test.d.ts.map +0 -1
  170. package/dist/daemon/__tests__/watcher.test.js +0 -234
  171. package/dist/daemon/__tests__/watcher.test.js.map +0 -1
  172. package/dist/daemon/methods/__tests__/graph.test.d.ts +0 -5
  173. package/dist/daemon/methods/__tests__/graph.test.d.ts.map +0 -1
  174. package/dist/daemon/methods/__tests__/graph.test.js +0 -274
  175. package/dist/daemon/methods/__tests__/graph.test.js.map +0 -1
  176. package/dist/daemon/methods/__tests__/provider.test.d.ts +0 -5
  177. package/dist/daemon/methods/__tests__/provider.test.d.ts.map +0 -1
  178. package/dist/daemon/methods/__tests__/provider.test.js +0 -184
  179. package/dist/daemon/methods/__tests__/provider.test.js.map +0 -1
  180. package/dist/daemon/methods/__tests__/tools.test.d.ts +0 -5
  181. package/dist/daemon/methods/__tests__/tools.test.d.ts.map +0 -1
  182. package/dist/daemon/methods/__tests__/tools.test.js +0 -295
  183. package/dist/daemon/methods/__tests__/tools.test.js.map +0 -1
  184. package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts +0 -2
  185. package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts.map +0 -1
  186. package/dist/graph/__tests__/EdgeTypeRegistry.test.js +0 -212
  187. package/dist/graph/__tests__/EdgeTypeRegistry.test.js.map +0 -1
  188. package/dist/graph/__tests__/FederatedGraph.test.d.ts +0 -2
  189. package/dist/graph/__tests__/FederatedGraph.test.d.ts.map +0 -1
  190. package/dist/graph/__tests__/FederatedGraph.test.js +0 -661
  191. package/dist/graph/__tests__/FederatedGraph.test.js.map +0 -1
  192. package/dist/graph/__tests__/GraphologyAdapter.test.d.ts +0 -2
  193. package/dist/graph/__tests__/GraphologyAdapter.test.d.ts.map +0 -1
  194. package/dist/graph/__tests__/GraphologyAdapter.test.js +0 -326
  195. package/dist/graph/__tests__/GraphologyAdapter.test.js.map +0 -1
  196. package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts +0 -2
  197. package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts.map +0 -1
  198. package/dist/graph/__tests__/HydratingFederatedGraph.test.js +0 -587
  199. package/dist/graph/__tests__/HydratingFederatedGraph.test.js.map +0 -1
  200. package/dist/graph/__tests__/debounce.test.d.ts +0 -5
  201. package/dist/graph/__tests__/debounce.test.d.ts.map +0 -1
  202. package/dist/graph/__tests__/debounce.test.js +0 -195
  203. package/dist/graph/__tests__/debounce.test.js.map +0 -1
  204. package/dist/graph/__tests__/edge-cases.test.d.ts +0 -8
  205. package/dist/graph/__tests__/edge-cases.test.d.ts.map +0 -1
  206. package/dist/graph/__tests__/edge-cases.test.js +0 -472
  207. package/dist/graph/__tests__/edge-cases.test.js.map +0 -1
  208. package/dist/graph/__tests__/expansion.test.d.ts +0 -2
  209. package/dist/graph/__tests__/expansion.test.d.ts.map +0 -1
  210. package/dist/graph/__tests__/expansion.test.js +0 -105
  211. package/dist/graph/__tests__/expansion.test.js.map +0 -1
  212. package/dist/graph/__tests__/provider-store.test.d.ts +0 -5
  213. package/dist/graph/__tests__/provider-store.test.d.ts.map +0 -1
  214. package/dist/graph/__tests__/provider-store.test.js +0 -791
  215. package/dist/graph/__tests__/provider-store.test.js.map +0 -1
  216. package/dist/graph/__tests__/query.test.d.ts +0 -5
  217. package/dist/graph/__tests__/query.test.d.ts.map +0 -1
  218. package/dist/graph/__tests__/query.test.js +0 -774
  219. package/dist/graph/__tests__/query.test.js.map +0 -1
  220. package/dist/graph/__tests__/store.test.d.ts +0 -5
  221. package/dist/graph/__tests__/store.test.d.ts.map +0 -1
  222. package/dist/graph/__tests__/store.test.js +0 -489
  223. package/dist/graph/__tests__/store.test.js.map +0 -1
  224. package/dist/graph/__tests__/sync.test.d.ts +0 -5
  225. package/dist/graph/__tests__/sync.test.d.ts.map +0 -1
  226. package/dist/graph/__tests__/sync.test.js +0 -129
  227. package/dist/graph/__tests__/sync.test.js.map +0 -1
  228. package/dist/graph/__tests__/validation.test.d.ts +0 -2
  229. package/dist/graph/__tests__/validation.test.d.ts.map +0 -1
  230. package/dist/graph/__tests__/validation.test.js +0 -521
  231. package/dist/graph/__tests__/validation.test.js.map +0 -1
  232. package/dist/providers/__tests__/beads.test.d.ts +0 -5
  233. package/dist/providers/__tests__/beads.test.d.ts.map +0 -1
  234. package/dist/providers/__tests__/beads.test.js +0 -591
  235. package/dist/providers/__tests__/beads.test.js.map +0 -1
  236. package/dist/providers/__tests__/claude-tasks.test.d.ts +0 -5
  237. package/dist/providers/__tests__/claude-tasks.test.d.ts.map +0 -1
  238. package/dist/providers/__tests__/claude-tasks.test.js +0 -392
  239. package/dist/providers/__tests__/claude-tasks.test.js.map +0 -1
  240. package/dist/providers/__tests__/from-config.test.d.ts +0 -5
  241. package/dist/providers/__tests__/from-config.test.d.ts.map +0 -1
  242. package/dist/providers/__tests__/from-config.test.js +0 -152
  243. package/dist/providers/__tests__/from-config.test.js.map +0 -1
  244. package/dist/providers/__tests__/materialization.test.d.ts +0 -5
  245. package/dist/providers/__tests__/materialization.test.d.ts.map +0 -1
  246. package/dist/providers/__tests__/materialization.test.js +0 -407
  247. package/dist/providers/__tests__/materialization.test.js.map +0 -1
  248. package/dist/providers/__tests__/native.test.d.ts +0 -5
  249. package/dist/providers/__tests__/native.test.d.ts.map +0 -1
  250. package/dist/providers/__tests__/native.test.js +0 -566
  251. package/dist/providers/__tests__/native.test.js.map +0 -1
  252. package/dist/providers/__tests__/registry.test.d.ts +0 -5
  253. package/dist/providers/__tests__/registry.test.d.ts.map +0 -1
  254. package/dist/providers/__tests__/registry.test.js +0 -183
  255. package/dist/providers/__tests__/registry.test.js.map +0 -1
  256. package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts +0 -2
  257. package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts.map +0 -1
  258. package/dist/providers/traits/__tests__/RelationshipQueryable.test.js +0 -169
  259. package/dist/providers/traits/__tests__/RelationshipQueryable.test.js.map +0 -1
  260. package/dist/providers/traits/__tests__/TaskManageable.test.d.ts +0 -2
  261. package/dist/providers/traits/__tests__/TaskManageable.test.d.ts.map +0 -1
  262. package/dist/providers/traits/__tests__/TaskManageable.test.js +0 -172
  263. package/dist/providers/traits/__tests__/TaskManageable.test.js.map +0 -1
  264. package/dist/schema/__tests__/validation.test.d.ts +0 -2
  265. package/dist/schema/__tests__/validation.test.d.ts.map +0 -1
  266. package/dist/schema/__tests__/validation.test.js +0 -241
  267. package/dist/schema/__tests__/validation.test.js.map +0 -1
  268. package/dist/storage/__tests__/atomic-write.test.d.ts +0 -5
  269. package/dist/storage/__tests__/atomic-write.test.d.ts.map +0 -1
  270. package/dist/storage/__tests__/atomic-write.test.js +0 -170
  271. package/dist/storage/__tests__/atomic-write.test.js.map +0 -1
  272. package/dist/storage/__tests__/file-lock.test.d.ts +0 -2
  273. package/dist/storage/__tests__/file-lock.test.d.ts.map +0 -1
  274. package/dist/storage/__tests__/file-lock.test.js +0 -89
  275. package/dist/storage/__tests__/file-lock.test.js.map +0 -1
  276. package/dist/storage/__tests__/jsonl.test.d.ts +0 -2
  277. package/dist/storage/__tests__/jsonl.test.d.ts.map +0 -1
  278. package/dist/storage/__tests__/jsonl.test.js +0 -228
  279. package/dist/storage/__tests__/jsonl.test.js.map +0 -1
  280. package/dist/storage/__tests__/locked-writer.test.d.ts +0 -2
  281. package/dist/storage/__tests__/locked-writer.test.d.ts.map +0 -1
  282. package/dist/storage/__tests__/locked-writer.test.js +0 -109
  283. package/dist/storage/__tests__/locked-writer.test.js.map +0 -1
  284. package/dist/storage/__tests__/sqlite.test.d.ts +0 -2
  285. package/dist/storage/__tests__/sqlite.test.d.ts.map +0 -1
  286. package/dist/storage/__tests__/sqlite.test.js +0 -470
  287. package/dist/storage/__tests__/sqlite.test.js.map +0 -1
  288. package/dist/tools/__tests__/annotate.test.d.ts +0 -5
  289. package/dist/tools/__tests__/annotate.test.d.ts.map +0 -1
  290. package/dist/tools/__tests__/annotate.test.js +0 -314
  291. package/dist/tools/__tests__/annotate.test.js.map +0 -1
  292. package/dist/tools/__tests__/link.test.d.ts +0 -5
  293. package/dist/tools/__tests__/link.test.d.ts.map +0 -1
  294. package/dist/tools/__tests__/link.test.js +0 -245
  295. package/dist/tools/__tests__/link.test.js.map +0 -1
  296. package/dist/tools/__tests__/query.test.d.ts +0 -5
  297. package/dist/tools/__tests__/query.test.d.ts.map +0 -1
  298. package/dist/tools/__tests__/query.test.js +0 -288
  299. package/dist/tools/__tests__/query.test.js.map +0 -1
  300. package/dist/tools/__tests__/task.test.d.ts +0 -5
  301. package/dist/tools/__tests__/task.test.d.ts.map +0 -1
  302. package/dist/tools/__tests__/task.test.js +0 -178
  303. package/dist/tools/__tests__/task.test.js.map +0 -1
  304. package/dist/tracking/claude-task-reconstructor.d.ts +0 -41
  305. package/dist/tracking/claude-task-reconstructor.d.ts.map +0 -1
  306. package/dist/tracking/claude-task-reconstructor.js +0 -91
  307. package/dist/tracking/claude-task-reconstructor.js.map +0 -1
  308. package/dist/tracking/plan-mode-tracker.d.ts +0 -20
  309. package/dist/tracking/plan-mode-tracker.d.ts.map +0 -1
  310. package/dist/tracking/plan-mode-tracker.js +0 -35
  311. package/dist/tracking/plan-mode-tracker.js.map +0 -1
@@ -1,774 +0,0 @@
1
- /**
2
- * Tests for Query Engine
3
- */
4
- import { describe, it, expect, beforeEach, vi } from 'vitest';
5
- import { createQueryEngine } from '../query.js';
6
- // ============================================================================
7
- // Test Fixtures
8
- // ============================================================================
9
- function createMockStorage() {
10
- return {
11
- createNode: vi.fn(),
12
- getNode: vi.fn(),
13
- updateNode: vi.fn(),
14
- deleteNode: vi.fn(),
15
- queryNodes: vi.fn().mockResolvedValue([]),
16
- createEdge: vi.fn(),
17
- getEdge: vi.fn(),
18
- deleteEdge: vi.fn(),
19
- getEdgesFrom: vi.fn().mockResolvedValue([]),
20
- getEdgesTo: vi.fn().mockResolvedValue([]),
21
- addTag: vi.fn(),
22
- removeTag: vi.fn(),
23
- getTags: vi.fn().mockResolvedValue([]),
24
- getTagsForNodes: vi.fn().mockResolvedValue(new Map()),
25
- getNodesByTag: vi.fn().mockResolvedValue([]),
26
- getReady: vi.fn().mockResolvedValue([]),
27
- runInTransaction: vi.fn(),
28
- markDirty: vi.fn(),
29
- getDirtyNodes: vi.fn().mockResolvedValue([]),
30
- clearDirty: vi.fn(),
31
- close: vi.fn(),
32
- };
33
- }
34
- function createSpec(id, title, overrides = {}) {
35
- return {
36
- id,
37
- uuid: `uuid-${id}`,
38
- type: 'spec',
39
- title,
40
- created_at: '2024-01-01T00:00:00Z',
41
- updated_at: '2024-01-01T00:00:00Z',
42
- archived: false,
43
- ...overrides,
44
- };
45
- }
46
- function createIssue(id, title, status = 'open', overrides = {}) {
47
- return {
48
- id,
49
- uuid: `uuid-${id}`,
50
- type: 'issue',
51
- title,
52
- status,
53
- created_at: '2024-01-01T00:00:00Z',
54
- updated_at: '2024-01-01T00:00:00Z',
55
- archived: false,
56
- ...overrides,
57
- };
58
- }
59
- function createFeedback(id, targetId, overrides = {}) {
60
- return {
61
- id,
62
- uuid: `uuid-${id}`,
63
- type: 'feedback',
64
- title: 'Feedback',
65
- target_id: targetId,
66
- feedback_type: 'comment',
67
- created_at: '2024-01-01T00:00:00Z',
68
- updated_at: '2024-01-01T00:00:00Z',
69
- archived: false,
70
- resolved: false,
71
- dismissed: false,
72
- ...overrides,
73
- };
74
- }
75
- function createEdge(id, fromId, toId, type) {
76
- return {
77
- id,
78
- uuid: `uuid-${id}`,
79
- from_id: fromId,
80
- to_id: toId,
81
- type,
82
- created_at: '2024-01-01T00:00:00Z',
83
- };
84
- }
85
- // ============================================================================
86
- // Test Suites
87
- // ============================================================================
88
- describe('QueryEngine', () => {
89
- let storage;
90
- let engine;
91
- beforeEach(() => {
92
- storage = createMockStorage();
93
- engine = createQueryEngine(storage);
94
- });
95
- describe('nodes()', () => {
96
- it('should query nodes with filter', async () => {
97
- const spec = createSpec('s-abc1', 'Test Spec');
98
- vi.mocked(storage.queryNodes).mockResolvedValue([spec]);
99
- const result = await engine.nodes({ type: 'spec' });
100
- expect(storage.queryNodes).toHaveBeenCalledWith({
101
- type: 'spec',
102
- status: undefined,
103
- tags: undefined,
104
- parent_id: undefined,
105
- archived: undefined,
106
- search: undefined,
107
- limit: undefined,
108
- offset: undefined,
109
- });
110
- expect(result).toHaveLength(1);
111
- expect(result[0].id).toBe('s-abc1');
112
- });
113
- it('should filter out invalid nodes', async () => {
114
- const validSpec = createSpec('s-abc1', 'Test Spec');
115
- const invalidNode = { id: 'invalid', type: 'unknown' };
116
- vi.mocked(storage.queryNodes).mockResolvedValue([validSpec, invalidNode]);
117
- const result = await engine.nodes({});
118
- // Only valid spec should be returned
119
- expect(result).toHaveLength(1);
120
- expect(result[0].id).toBe('s-abc1');
121
- });
122
- it('should handle archived filter', async () => {
123
- vi.mocked(storage.queryNodes).mockResolvedValue([]);
124
- await engine.nodes({ archived: false });
125
- expect(storage.queryNodes).toHaveBeenCalledWith(expect.objectContaining({ archived: false }));
126
- });
127
- it('should convert null archived to undefined', async () => {
128
- vi.mocked(storage.queryNodes).mockResolvedValue([]);
129
- await engine.nodes({ archived: null });
130
- expect(storage.queryNodes).toHaveBeenCalledWith(expect.objectContaining({ archived: undefined }));
131
- });
132
- });
133
- describe('edges()', () => {
134
- it('should query edges from a node', async () => {
135
- const edge = createEdge('x-abc1', 's-a', 's-b', 'references');
136
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([edge]);
137
- const result = await engine.edges({ from_id: 's-a' });
138
- expect(storage.getEdgesFrom).toHaveBeenCalledWith('s-a', undefined);
139
- expect(result).toHaveLength(1);
140
- expect(result[0].id).toBe('x-abc1');
141
- });
142
- it('should query edges to a node', async () => {
143
- const edge = createEdge('x-abc1', 's-a', 's-b', 'references');
144
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
145
- const result = await engine.edges({ to_id: 's-b' });
146
- expect(storage.getEdgesTo).toHaveBeenCalledWith('s-b', undefined);
147
- expect(result).toHaveLength(1);
148
- });
149
- it('should return empty when no filter specified', async () => {
150
- const result = await engine.edges({});
151
- expect(result).toHaveLength(0);
152
- });
153
- it('should apply pagination', async () => {
154
- const edges = [
155
- createEdge('x-1', 's-a', 's-b', 'references'),
156
- createEdge('x-2', 's-a', 's-c', 'references'),
157
- createEdge('x-3', 's-a', 's-d', 'references'),
158
- ];
159
- vi.mocked(storage.getEdgesFrom).mockResolvedValue(edges);
160
- const result = await engine.edges({ from_id: 's-a', offset: 1, limit: 1 });
161
- expect(result).toHaveLength(1);
162
- expect(result[0].id).toBe('x-2');
163
- });
164
- });
165
- describe('edgesFrom()', () => {
166
- it('should get edges from a node', async () => {
167
- const edge = createEdge('x-abc1', 's-a', 's-b', 'blocks');
168
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([edge]);
169
- const result = await engine.edgesFrom('s-a', 'blocks');
170
- expect(storage.getEdgesFrom).toHaveBeenCalledWith('s-a', 'blocks');
171
- expect(result).toHaveLength(1);
172
- });
173
- });
174
- describe('edgesTo()', () => {
175
- it('should get edges to a node', async () => {
176
- const edge = createEdge('x-abc1', 's-a', 's-b', 'blocks');
177
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
178
- const result = await engine.edgesTo('s-b', 'blocks');
179
- expect(storage.getEdgesTo).toHaveBeenCalledWith('s-b', 'blocks');
180
- expect(result).toHaveLength(1);
181
- });
182
- });
183
- describe('edgesFor()', () => {
184
- it('should get all edges for a node (both directions)', async () => {
185
- const fromEdge = createEdge('x-1', 's-a', 's-b', 'blocks');
186
- const toEdge = createEdge('x-2', 's-c', 's-a', 'references');
187
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([fromEdge]);
188
- vi.mocked(storage.getEdgesTo).mockResolvedValue([toEdge]);
189
- const result = await engine.edgesFor('s-a');
190
- expect(result).toHaveLength(2);
191
- expect(result.map((e) => e.id).sort()).toEqual(['x-1', 'x-2']);
192
- });
193
- it('should deduplicate edges', async () => {
194
- const edge = createEdge('x-1', 's-a', 's-a', 'references'); // self-reference
195
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([edge]);
196
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
197
- const result = await engine.edgesFor('s-a');
198
- expect(result).toHaveLength(1);
199
- });
200
- });
201
- describe('blockers()', () => {
202
- it('should get direct blockers', async () => {
203
- const blocker = createIssue('i-blocker', 'Blocking Issue', 'open');
204
- const edge = createEdge('x-1', 'i-blocker', 'i-blocked', 'blocks');
205
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
206
- vi.mocked(storage.getNode).mockResolvedValue(blocker);
207
- const result = await engine.blockers('i-blocked');
208
- expect(result).toHaveLength(1);
209
- expect(result[0].id).toBe('i-blocker');
210
- });
211
- it('should filter out inactive blockers by default', async () => {
212
- const closedBlocker = createIssue('i-blocker', 'Closed Blocker', 'closed');
213
- const edge = createEdge('x-1', 'i-blocker', 'i-blocked', 'blocks');
214
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
215
- vi.mocked(storage.getNode).mockResolvedValue(closedBlocker);
216
- const result = await engine.blockers('i-blocked');
217
- expect(result).toHaveLength(0);
218
- });
219
- it('should include inactive blockers when activeOnly=false', async () => {
220
- const closedBlocker = createIssue('i-blocker', 'Closed Blocker', 'closed');
221
- const edge = createEdge('x-1', 'i-blocker', 'i-blocked', 'blocks');
222
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
223
- vi.mocked(storage.getNode).mockResolvedValue(closedBlocker);
224
- const result = await engine.blockers('i-blocked', { activeOnly: false });
225
- expect(result).toHaveLength(1);
226
- });
227
- it('should get transitive blockers', async () => {
228
- const blockerA = createIssue('i-a', 'Blocker A', 'open');
229
- const blockerB = createIssue('i-b', 'Blocker B', 'open');
230
- vi.mocked(storage.getEdgesTo)
231
- .mockResolvedValueOnce([createEdge('x-1', 'i-a', 'i-target', 'blocks')])
232
- .mockResolvedValueOnce([createEdge('x-2', 'i-b', 'i-a', 'blocks')])
233
- .mockResolvedValueOnce([]);
234
- vi.mocked(storage.getNode)
235
- .mockResolvedValueOnce(blockerA)
236
- .mockResolvedValueOnce(blockerB);
237
- const result = await engine.blockers('i-target', { transitive: true });
238
- expect(result).toHaveLength(2);
239
- });
240
- it('should respect maxDepth', async () => {
241
- // maxDepth=0 allows depth 0 (direct blockers) but no recursion
242
- // maxDepth=1 would allow depth 0 and depth 1 (blockers of blockers)
243
- const directBlocker = createIssue('i-direct', 'Direct Blocker', 'open');
244
- const transitiveBlocker = createIssue('i-transitive', 'Transitive', 'open');
245
- vi.mocked(storage.getEdgesTo)
246
- .mockResolvedValueOnce([createEdge('x-1', 'i-direct', 'i-target', 'blocks')])
247
- .mockResolvedValueOnce([createEdge('x-2', 'i-transitive', 'i-direct', 'blocks')]);
248
- vi.mocked(storage.getNode)
249
- .mockResolvedValueOnce(directBlocker)
250
- .mockResolvedValueOnce(transitiveBlocker);
251
- // With maxDepth=0 and transitive=true, we get direct blocker but NOT its blockers
252
- const result = await engine.blockers('i-target', {
253
- transitive: true,
254
- maxDepth: 0,
255
- });
256
- // Should have 1 result (direct blocker), not 2 (would include transitive)
257
- expect(result).toHaveLength(1);
258
- expect(result[0].id).toBe('i-direct');
259
- });
260
- });
261
- describe('blocking()', () => {
262
- it('should get nodes this blocks', async () => {
263
- const blocked = createIssue('i-blocked', 'Blocked Issue', 'open');
264
- const edge = createEdge('x-1', 'i-blocker', 'i-blocked', 'blocks');
265
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([edge]);
266
- vi.mocked(storage.getNode).mockResolvedValue(blocked);
267
- const result = await engine.blocking('i-blocker');
268
- expect(result).toHaveLength(1);
269
- expect(result[0].id).toBe('i-blocked');
270
- });
271
- });
272
- describe('isBlocking()', () => {
273
- it('should return true for direct blocking', async () => {
274
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([
275
- createEdge('x-1', 'i-a', 'i-b', 'blocks'),
276
- ]);
277
- const result = await engine.isBlocking('i-a', 'i-b');
278
- expect(result).toBe(true);
279
- });
280
- it('should return true for transitive blocking', async () => {
281
- vi.mocked(storage.getEdgesFrom)
282
- .mockResolvedValueOnce([createEdge('x-1', 'i-a', 'i-b', 'blocks')])
283
- .mockResolvedValueOnce([createEdge('x-2', 'i-b', 'i-c', 'blocks')]);
284
- const result = await engine.isBlocking('i-a', 'i-c');
285
- expect(result).toBe(true);
286
- });
287
- it('should return false when not blocking', async () => {
288
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([]);
289
- const result = await engine.isBlocking('i-a', 'i-b');
290
- expect(result).toBe(false);
291
- });
292
- it('should handle cycles in graph without infinite loop', async () => {
293
- vi.mocked(storage.getEdgesFrom)
294
- .mockResolvedValueOnce([createEdge('x-1', 'i-a', 'i-b', 'blocks')])
295
- .mockResolvedValueOnce([createEdge('x-2', 'i-b', 'i-a', 'blocks')]) // cycle back
296
- .mockResolvedValue([]);
297
- // Should terminate without infinite loop
298
- const result = await engine.isBlocking('i-a', 'i-c');
299
- expect(result).toBe(false);
300
- });
301
- });
302
- describe('implementers()', () => {
303
- it('should get issues that implement a spec', async () => {
304
- const issue = createIssue('i-impl', 'Implementation');
305
- const edge = createEdge('x-1', 'i-impl', 's-spec', 'implements');
306
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
307
- vi.mocked(storage.getNode).mockResolvedValue(issue);
308
- const result = await engine.implementers('s-spec');
309
- expect(storage.getEdgesTo).toHaveBeenCalledWith('s-spec', 'implements');
310
- expect(result).toHaveLength(1);
311
- expect(result[0].id).toBe('i-impl');
312
- });
313
- it('should filter out non-issues', async () => {
314
- const spec = createSpec('s-not-issue', 'Not an issue');
315
- const edge = createEdge('x-1', 's-not-issue', 's-spec', 'implements');
316
- vi.mocked(storage.getEdgesTo).mockResolvedValue([edge]);
317
- vi.mocked(storage.getNode).mockResolvedValue(spec);
318
- const result = await engine.implementers('s-spec');
319
- expect(result).toHaveLength(0);
320
- });
321
- });
322
- describe('specs()', () => {
323
- it('should get specs that an issue implements', async () => {
324
- const spec = createSpec('s-spec', 'Spec');
325
- const edge = createEdge('x-1', 'i-issue', 's-spec', 'implements');
326
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([edge]);
327
- vi.mocked(storage.getNode).mockResolvedValue(spec);
328
- const result = await engine.specs('i-issue');
329
- expect(storage.getEdgesFrom).toHaveBeenCalledWith('i-issue', 'implements');
330
- expect(result).toHaveLength(1);
331
- expect(result[0].id).toBe('s-spec');
332
- });
333
- });
334
- describe('children()', () => {
335
- it('should get child nodes', async () => {
336
- const child = createSpec('s-child', 'Child', { parent_id: 's-parent' });
337
- vi.mocked(storage.queryNodes).mockResolvedValue([child]);
338
- const result = await engine.children('s-parent');
339
- expect(storage.queryNodes).toHaveBeenCalledWith({ parent_id: 's-parent' });
340
- expect(result).toHaveLength(1);
341
- expect(result[0].id).toBe('s-child');
342
- });
343
- });
344
- describe('parent()', () => {
345
- it('should get parent node', async () => {
346
- const child = createSpec('s-child', 'Child', { parent_id: 's-parent' });
347
- const parent = createSpec('s-parent', 'Parent');
348
- vi.mocked(storage.getNode)
349
- .mockResolvedValueOnce(child)
350
- .mockResolvedValueOnce(parent);
351
- const result = await engine.parent('s-child');
352
- expect(result).not.toBeNull();
353
- expect(result?.id).toBe('s-parent');
354
- });
355
- it('should return null when no parent', async () => {
356
- const node = createSpec('s-root', 'Root');
357
- vi.mocked(storage.getNode).mockResolvedValue(node);
358
- const result = await engine.parent('s-root');
359
- expect(result).toBeNull();
360
- });
361
- it('should return null when node not found', async () => {
362
- vi.mocked(storage.getNode).mockResolvedValue(null);
363
- const result = await engine.parent('s-missing');
364
- expect(result).toBeNull();
365
- });
366
- });
367
- describe('ancestors()', () => {
368
- it('should get all ancestors', async () => {
369
- const child = createSpec('s-child', 'Child', { parent_id: 's-mid' });
370
- const mid = createSpec('s-mid', 'Mid', { parent_id: 's-root' });
371
- const root = createSpec('s-root', 'Root');
372
- vi.mocked(storage.getNode)
373
- .mockResolvedValueOnce(child)
374
- .mockResolvedValueOnce(mid)
375
- .mockResolvedValueOnce(mid) // gets mid again for parent_id check
376
- .mockResolvedValueOnce(root)
377
- .mockResolvedValueOnce(root); // gets root again for parent_id check
378
- const result = await engine.ancestors('s-child');
379
- expect(result.length).toBeGreaterThanOrEqual(1);
380
- });
381
- it('should return empty for root node', async () => {
382
- const root = createSpec('s-root', 'Root');
383
- vi.mocked(storage.getNode).mockResolvedValue(root);
384
- const result = await engine.ancestors('s-root');
385
- expect(result).toHaveLength(0);
386
- });
387
- });
388
- describe('descendants()', () => {
389
- it('should get all descendants', async () => {
390
- const child1 = createSpec('s-c1', 'Child 1', { parent_id: 's-root' });
391
- const child2 = createSpec('s-c2', 'Child 2', { parent_id: 's-root' });
392
- const grandchild = createSpec('s-gc', 'Grandchild', { parent_id: 's-c1' });
393
- vi.mocked(storage.queryNodes)
394
- .mockResolvedValueOnce([child1, child2]) // children of root
395
- .mockResolvedValueOnce([grandchild]) // children of c1
396
- .mockResolvedValueOnce([]) // children of c2
397
- .mockResolvedValueOnce([]); // children of gc
398
- const result = await engine.descendants('s-root');
399
- expect(result).toHaveLength(3);
400
- });
401
- });
402
- describe('ready()', () => {
403
- it('should return open issues with no active blockers', async () => {
404
- const issue = createIssue('i-ready', 'Ready Issue', 'open');
405
- vi.mocked(storage.queryNodes).mockResolvedValue([issue]);
406
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]); // no blockers
407
- const result = await engine.ready();
408
- expect(storage.queryNodes).toHaveBeenCalledWith({
409
- type: 'issue',
410
- status: 'open',
411
- archived: false,
412
- });
413
- expect(result).toHaveLength(1);
414
- expect(result[0].id).toBe('i-ready');
415
- });
416
- it('should exclude issues with active blockers', async () => {
417
- const issue = createIssue('i-blocked', 'Blocked Issue', 'open');
418
- const blocker = createIssue('i-blocker', 'Blocker', 'open');
419
- vi.mocked(storage.queryNodes).mockResolvedValue([issue]);
420
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
421
- createEdge('x-1', 'i-blocker', 'i-blocked', 'blocks'),
422
- ]);
423
- vi.mocked(storage.getNode).mockResolvedValue(blocker);
424
- const result = await engine.ready();
425
- expect(result).toHaveLength(0);
426
- });
427
- it('should include issues whose blockers are closed', async () => {
428
- const issue = createIssue('i-ready', 'Ready Issue', 'open');
429
- const closedBlocker = createIssue('i-blocker', 'Closed', 'closed');
430
- vi.mocked(storage.queryNodes).mockResolvedValue([issue]);
431
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
432
- createEdge('x-1', 'i-blocker', 'i-ready', 'blocks'),
433
- ]);
434
- vi.mocked(storage.getNode).mockResolvedValue(closedBlocker);
435
- const result = await engine.ready();
436
- expect(result).toHaveLength(1);
437
- });
438
- it('should include issues whose blockers are archived', async () => {
439
- const issue = createIssue('i-ready', 'Ready Issue', 'open');
440
- const archivedBlocker = createIssue('i-blocker', 'Archived', 'open', {
441
- archived: true,
442
- });
443
- vi.mocked(storage.queryNodes).mockResolvedValue([issue]);
444
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
445
- createEdge('x-1', 'i-blocker', 'i-ready', 'blocks'),
446
- ]);
447
- vi.mocked(storage.getNode).mockResolvedValue(archivedBlocker);
448
- const result = await engine.ready();
449
- expect(result).toHaveLength(1);
450
- });
451
- it('should filter by tags', async () => {
452
- const issue1 = createIssue('i-1', 'Issue 1', 'open', { tags: ['urgent'] });
453
- const issue2 = createIssue('i-2', 'Issue 2', 'open', { tags: ['low'] });
454
- vi.mocked(storage.queryNodes).mockResolvedValue([issue1, issue2]);
455
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]);
456
- const result = await engine.ready({ tags: ['urgent'] });
457
- expect(result).toHaveLength(1);
458
- expect(result[0].id).toBe('i-1');
459
- });
460
- it('should filter by priority', async () => {
461
- const highPriority = createIssue('i-high', 'High', 'open', { priority: 0 });
462
- const lowPriority = createIssue('i-low', 'Low', 'open', { priority: 4 });
463
- vi.mocked(storage.queryNodes).mockResolvedValue([highPriority, lowPriority]);
464
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]);
465
- const result = await engine.ready({ priority: 0 });
466
- expect(result).toHaveLength(1);
467
- expect(result[0].id).toBe('i-high');
468
- });
469
- it('should filter by priority range', async () => {
470
- const p0 = createIssue('i-0', 'P0', 'open', { priority: 0 });
471
- const p2 = createIssue('i-2', 'P2', 'open', { priority: 2 });
472
- const p4 = createIssue('i-4', 'P4', 'open', { priority: 4 });
473
- vi.mocked(storage.queryNodes).mockResolvedValue([p0, p2, p4]);
474
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]);
475
- const result = await engine.ready({ priority: { min: 1, max: 3 } });
476
- expect(result).toHaveLength(1);
477
- expect(result[0].id).toBe('i-2');
478
- });
479
- it('should filter by assignee', async () => {
480
- const assigned = createIssue('i-assigned', 'Assigned', 'open', {
481
- assignee: 'alice',
482
- });
483
- const unassigned = createIssue('i-unassigned', 'Unassigned', 'open');
484
- vi.mocked(storage.queryNodes).mockResolvedValue([assigned, unassigned]);
485
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]);
486
- const result = await engine.ready({ assignee: 'alice' });
487
- expect(result).toHaveLength(1);
488
- expect(result[0].id).toBe('i-assigned');
489
- });
490
- it('should respect limit', async () => {
491
- const issues = [
492
- createIssue('i-1', 'Issue 1', 'open'),
493
- createIssue('i-2', 'Issue 2', 'open'),
494
- createIssue('i-3', 'Issue 3', 'open'),
495
- ];
496
- vi.mocked(storage.queryNodes).mockResolvedValue(issues);
497
- vi.mocked(storage.getEdgesTo).mockResolvedValue([]);
498
- const result = await engine.ready({ limit: 2 });
499
- expect(result).toHaveLength(2);
500
- });
501
- });
502
- describe('feedback()', () => {
503
- it('should get feedback for a target', async () => {
504
- const fb = createFeedback('f-1', 's-spec');
505
- vi.mocked(storage.queryNodes).mockResolvedValue([fb]);
506
- const result = await engine.feedback('s-spec');
507
- expect(result).toHaveLength(1);
508
- expect(result[0].id).toBe('f-1');
509
- });
510
- it('should filter by type', async () => {
511
- const comment = createFeedback('f-1', 's-spec', { feedback_type: 'comment' });
512
- const suggestion = createFeedback('f-2', 's-spec', {
513
- feedback_type: 'suggestion',
514
- });
515
- vi.mocked(storage.queryNodes).mockResolvedValue([comment, suggestion]);
516
- const result = await engine.feedback('s-spec', { type: 'comment' });
517
- expect(result).toHaveLength(1);
518
- expect(result[0].id).toBe('f-1');
519
- });
520
- it('should filter by resolved status', async () => {
521
- const resolved = createFeedback('f-1', 's-spec', { resolved: true });
522
- const unresolved = createFeedback('f-2', 's-spec', { resolved: false });
523
- vi.mocked(storage.queryNodes).mockResolvedValue([resolved, unresolved]);
524
- const result = await engine.feedback('s-spec', { resolved: true });
525
- expect(result).toHaveLength(1);
526
- expect(result[0].id).toBe('f-1');
527
- });
528
- it('should exclude dismissed by default', async () => {
529
- const normal = createFeedback('f-1', 's-spec', { dismissed: false });
530
- const dismissed = createFeedback('f-2', 's-spec', { dismissed: true });
531
- vi.mocked(storage.queryNodes).mockResolvedValue([normal, dismissed]);
532
- const result = await engine.feedback('s-spec');
533
- expect(result).toHaveLength(1);
534
- expect(result[0].id).toBe('f-1');
535
- });
536
- it('should include dismissed when requested', async () => {
537
- const normal = createFeedback('f-1', 's-spec', { dismissed: false });
538
- const dismissed = createFeedback('f-2', 's-spec', { dismissed: true });
539
- vi.mocked(storage.queryNodes).mockResolvedValue([normal, dismissed]);
540
- const result = await engine.feedback('s-spec', { includeDismissed: true });
541
- expect(result).toHaveLength(2);
542
- });
543
- });
544
- describe('unresolvedFeedback()', () => {
545
- it('should get unresolved feedback', async () => {
546
- const unresolved = createFeedback('f-1', 's-spec', { resolved: false });
547
- const resolved = createFeedback('f-2', 's-spec', { resolved: true });
548
- vi.mocked(storage.queryNodes).mockResolvedValue([unresolved, resolved]);
549
- const result = await engine.unresolvedFeedback();
550
- expect(result).toHaveLength(1);
551
- expect(result[0].id).toBe('f-1');
552
- });
553
- it('should exclude dismissed feedback', async () => {
554
- const unresolved = createFeedback('f-1', 's-spec', {
555
- resolved: false,
556
- dismissed: false,
557
- });
558
- const dismissed = createFeedback('f-2', 's-spec', {
559
- resolved: false,
560
- dismissed: true,
561
- });
562
- vi.mocked(storage.queryNodes).mockResolvedValue([unresolved, dismissed]);
563
- const result = await engine.unresolvedFeedback();
564
- expect(result).toHaveLength(1);
565
- expect(result[0].id).toBe('f-1');
566
- });
567
- it('should filter by target', async () => {
568
- const fb1 = createFeedback('f-1', 's-spec1', { resolved: false });
569
- const fb2 = createFeedback('f-2', 's-spec2', { resolved: false });
570
- vi.mocked(storage.queryNodes).mockResolvedValue([fb1, fb2]);
571
- const result = await engine.unresolvedFeedback('s-spec1');
572
- expect(result).toHaveLength(1);
573
- expect(result[0].id).toBe('f-1');
574
- });
575
- });
576
- // ============================================================================
577
- // Cross-Provider Resolution Tests
578
- // ============================================================================
579
- describe('Cross-Provider Resolution', () => {
580
- describe('with nodeResolver', () => {
581
- it('blockers() should call resolver for external URIs', async () => {
582
- const externalNode = {
583
- id: 'x-ext1',
584
- uuid: 'ext-uuid-1',
585
- type: 'external',
586
- title: 'External Blocker',
587
- status: 'open',
588
- uri: 'beads://./bd-blocker',
589
- source: 'beads',
590
- created_at: '2024-01-01T00:00:00Z',
591
- updated_at: '2024-01-01T00:00:00Z',
592
- };
593
- // Setup mock resolver
594
- const mockResolver = vi.fn().mockResolvedValue(externalNode);
595
- // Create engine with resolver
596
- const engineWithResolver = createQueryEngine({
597
- storage,
598
- nodeResolver: mockResolver,
599
- });
600
- // Setup storage mocks
601
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
602
- createEdge('e-1', 'beads://./bd-blocker', 'i-local', 'blocks'),
603
- ]);
604
- await engineWithResolver.blockers('i-local');
605
- // Resolver should be called for external URI
606
- expect(mockResolver).toHaveBeenCalledWith('beads://./bd-blocker');
607
- // Note: External nodes don't appear in results because they don't
608
- // parse as valid Node types (spec/issue/feedback). The resolver is
609
- // still called to support ready() blocking checks.
610
- });
611
- it('blockers() should return local issues resolved via resolver', async () => {
612
- // When resolver returns a valid issue type, it should be in results
613
- const localBlocker = createIssue('i-blocker', 'Local Blocker');
614
- const mockResolver = vi.fn().mockResolvedValue(localBlocker);
615
- const engineWithResolver = createQueryEngine({
616
- storage,
617
- nodeResolver: mockResolver,
618
- });
619
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
620
- createEdge('e-1', 'beads://./bd-blocker', 'i-local', 'blocks'),
621
- ]);
622
- const result = await engineWithResolver.blockers('i-local');
623
- expect(mockResolver).toHaveBeenCalledWith('beads://./bd-blocker');
624
- expect(result).toHaveLength(1);
625
- expect(result[0].title).toBe('Local Blocker');
626
- });
627
- it('blockers() should use storage for local IDs even with resolver', async () => {
628
- const localBlocker = createIssue('i-blocker', 'Local Blocker');
629
- const mockResolver = vi.fn();
630
- const engineWithResolver = createQueryEngine({
631
- storage,
632
- nodeResolver: mockResolver,
633
- });
634
- vi.mocked(storage.getNode).mockResolvedValue(localBlocker);
635
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
636
- createEdge('e-1', 'i-blocker', 'i-local', 'blocks'),
637
- ]);
638
- await engineWithResolver.blockers('i-local');
639
- // Resolver should NOT be called for local IDs
640
- expect(mockResolver).not.toHaveBeenCalled();
641
- expect(storage.getNode).toHaveBeenCalledWith('i-blocker');
642
- });
643
- it('blocking() should call resolver for external URIs', async () => {
644
- const externalNode = {
645
- id: 'x-ext1',
646
- uuid: 'ext-uuid-1',
647
- type: 'external',
648
- title: 'External Blocked',
649
- status: 'open',
650
- uri: 'beads://./bd-blocked',
651
- source: 'beads',
652
- created_at: '2024-01-01T00:00:00Z',
653
- updated_at: '2024-01-01T00:00:00Z',
654
- };
655
- const mockResolver = vi.fn().mockResolvedValue(externalNode);
656
- const engineWithResolver = createQueryEngine({
657
- storage,
658
- nodeResolver: mockResolver,
659
- });
660
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([
661
- createEdge('e-1', 'i-blocker', 'beads://./bd-blocked', 'blocks'),
662
- ]);
663
- await engineWithResolver.blocking('i-blocker');
664
- // Resolver should be called for external URI
665
- expect(mockResolver).toHaveBeenCalledWith('beads://./bd-blocked');
666
- // Note: External nodes don't appear in results because they don't
667
- // parse as valid Node types (spec/issue/feedback).
668
- });
669
- it('blocking() should return local issues resolved via resolver', async () => {
670
- const localBlocked = createIssue('i-blocked', 'Local Blocked Issue');
671
- const mockResolver = vi.fn().mockResolvedValue(localBlocked);
672
- const engineWithResolver = createQueryEngine({
673
- storage,
674
- nodeResolver: mockResolver,
675
- });
676
- vi.mocked(storage.getEdgesFrom).mockResolvedValue([
677
- createEdge('e-1', 'i-blocker', 'beads://./bd-blocked', 'blocks'),
678
- ]);
679
- const result = await engineWithResolver.blocking('i-blocker');
680
- expect(mockResolver).toHaveBeenCalledWith('beads://./bd-blocked');
681
- expect(result).toHaveLength(1);
682
- expect(result[0].title).toBe('Local Blocked Issue');
683
- });
684
- it('ready() should consider external blockers when resolver provided', async () => {
685
- const localIssue = createIssue('i-ready', 'Ready Issue');
686
- const externalBlocker = {
687
- id: 'x-ext1',
688
- uuid: 'ext-uuid-1',
689
- type: 'external',
690
- title: 'External Blocker',
691
- status: 'open', // Active blocker
692
- uri: 'beads://./bd-blocker',
693
- source: 'beads',
694
- created_at: '2024-01-01T00:00:00Z',
695
- updated_at: '2024-01-01T00:00:00Z',
696
- };
697
- const mockResolver = vi.fn().mockResolvedValue(externalBlocker);
698
- const engineWithResolver = createQueryEngine({
699
- storage,
700
- nodeResolver: mockResolver,
701
- });
702
- vi.mocked(storage.queryNodes).mockResolvedValue([localIssue]);
703
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
704
- createEdge('e-1', 'beads://./bd-blocker', 'i-ready', 'blocks'),
705
- ]);
706
- const result = await engineWithResolver.ready();
707
- // Issue should NOT be ready because external blocker is active
708
- expect(result).toHaveLength(0);
709
- });
710
- it('ready() should include issue when external blocker is closed', async () => {
711
- const localIssue = createIssue('i-ready', 'Ready Issue');
712
- const closedBlocker = {
713
- id: 'x-ext1',
714
- uuid: 'ext-uuid-1',
715
- type: 'external',
716
- title: 'Closed External Blocker',
717
- status: 'closed', // Closed - not blocking
718
- uri: 'beads://./bd-blocker',
719
- source: 'beads',
720
- created_at: '2024-01-01T00:00:00Z',
721
- updated_at: '2024-01-01T00:00:00Z',
722
- };
723
- const mockResolver = vi.fn().mockResolvedValue(closedBlocker);
724
- const engineWithResolver = createQueryEngine({
725
- storage,
726
- nodeResolver: mockResolver,
727
- });
728
- vi.mocked(storage.queryNodes).mockResolvedValue([localIssue]);
729
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
730
- createEdge('e-1', 'beads://./bd-blocker', 'i-ready', 'blocks'),
731
- ]);
732
- const result = await engineWithResolver.ready();
733
- // Issue should be ready because external blocker is closed
734
- expect(result).toHaveLength(1);
735
- expect(result[0].id).toBe('i-ready');
736
- });
737
- it('resolver returning null should skip the node', async () => {
738
- const mockResolver = vi.fn().mockResolvedValue(null);
739
- const engineWithResolver = createQueryEngine({
740
- storage,
741
- nodeResolver: mockResolver,
742
- });
743
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
744
- createEdge('e-1', 'beads://./bd-missing', 'i-local', 'blocks'),
745
- ]);
746
- const result = await engineWithResolver.blockers('i-local');
747
- expect(mockResolver).toHaveBeenCalledWith('beads://./bd-missing');
748
- expect(result).toHaveLength(0);
749
- });
750
- });
751
- describe('without nodeResolver (backward compatibility)', () => {
752
- it('blockers() should skip external URIs that storage cannot find', async () => {
753
- vi.mocked(storage.getNode).mockResolvedValue(null);
754
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
755
- createEdge('e-1', 'beads://./bd-blocker', 'i-local', 'blocks'),
756
- ]);
757
- const result = await engine.blockers('i-local');
758
- expect(result).toHaveLength(0);
759
- });
760
- it('ready() should not be blocked by unresolvable external nodes', async () => {
761
- const localIssue = createIssue('i-ready', 'Ready Issue');
762
- vi.mocked(storage.queryNodes).mockResolvedValue([localIssue]);
763
- vi.mocked(storage.getEdgesTo).mockResolvedValue([
764
- createEdge('e-1', 'beads://./bd-unresolvable', 'i-ready', 'blocks'),
765
- ]);
766
- vi.mocked(storage.getNode).mockResolvedValue(null);
767
- const result = await engine.ready();
768
- // Issue should be ready because blocker cannot be resolved
769
- expect(result).toHaveLength(1);
770
- });
771
- });
772
- });
773
- });
774
- //# sourceMappingURL=query.test.js.map