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,129 +0,0 @@
1
- /**
2
- * Tests for Sync Manager
3
- */
4
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
5
- import { createSyncManager } from '../sync.js';
6
- describe('SyncManager', () => {
7
- let storage;
8
- let syncManager;
9
- let flushCallback;
10
- const config = {
11
- debounceMs: 100,
12
- maxDelayMs: 500,
13
- };
14
- beforeEach(() => {
15
- vi.useFakeTimers();
16
- // Create mock storage
17
- const dirtyNodes = new Set();
18
- storage = {
19
- markDirty: vi.fn().mockImplementation(async (nodeId) => {
20
- dirtyNodes.add(nodeId);
21
- }),
22
- getDirtyNodes: vi.fn().mockImplementation(async () => {
23
- return Array.from(dirtyNodes);
24
- }),
25
- clearDirty: vi.fn().mockImplementation(async (nodeIds) => {
26
- for (const id of nodeIds) {
27
- dirtyNodes.delete(id);
28
- }
29
- }),
30
- };
31
- flushCallback = vi.fn().mockResolvedValue(undefined);
32
- syncManager = createSyncManager(config, storage, flushCallback);
33
- });
34
- afterEach(() => {
35
- vi.useRealTimers();
36
- syncManager.cancel();
37
- });
38
- describe('markDirty', () => {
39
- it('should track dirty nodes', () => {
40
- syncManager.markDirty('s-abc1');
41
- syncManager.markDirty('s-abc2');
42
- expect(syncManager.hasPendingChanges()).toBe(true);
43
- });
44
- });
45
- describe('scheduleFlush', () => {
46
- it('should flush after debounce delay', async () => {
47
- syncManager.markDirty('s-abc1');
48
- syncManager.scheduleFlush();
49
- expect(flushCallback).not.toHaveBeenCalled();
50
- // Advance past debounce
51
- await vi.advanceTimersByTimeAsync(config.debounceMs + 10);
52
- expect(flushCallback).toHaveBeenCalled();
53
- });
54
- it('should reset debounce timer on multiple calls', async () => {
55
- syncManager.markDirty('s-abc1');
56
- syncManager.scheduleFlush();
57
- // Advance halfway
58
- await vi.advanceTimersByTimeAsync(config.debounceMs / 2);
59
- syncManager.markDirty('s-abc2');
60
- syncManager.scheduleFlush();
61
- // Advance halfway again (would be past original debounce)
62
- await vi.advanceTimersByTimeAsync(config.debounceMs / 2);
63
- // Should not have flushed yet (debounce was reset)
64
- expect(flushCallback).not.toHaveBeenCalled();
65
- // Advance remaining time
66
- await vi.advanceTimersByTimeAsync(config.debounceMs / 2 + 10);
67
- expect(flushCallback).toHaveBeenCalled();
68
- });
69
- it('should force flush at max delay', async () => {
70
- syncManager.markDirty('s-abc1');
71
- syncManager.scheduleFlush();
72
- // Keep resetting debounce but stay under max delay
73
- // debounceMs = 100, maxDelayMs = 500
74
- // Advance 80ms at a time, which is less than debounce
75
- // After 4 iterations: 320ms total (still under 500ms max)
76
- for (let i = 0; i < 4; i++) {
77
- await vi.advanceTimersByTimeAsync(80);
78
- syncManager.scheduleFlush();
79
- }
80
- // Should not have flushed yet (320ms < 500ms maxDelay)
81
- expect(flushCallback).not.toHaveBeenCalled();
82
- // Advance to max delay (need another 180ms to reach 500ms)
83
- await vi.advanceTimersByTimeAsync(200);
84
- expect(flushCallback).toHaveBeenCalled();
85
- });
86
- });
87
- describe('flush', () => {
88
- it('should flush immediately', async () => {
89
- syncManager.markDirty('s-abc1');
90
- await syncManager.flush();
91
- expect(flushCallback).toHaveBeenCalled();
92
- });
93
- it('should clear pending changes after flush', async () => {
94
- syncManager.markDirty('s-abc1');
95
- await syncManager.flush();
96
- expect(syncManager.hasPendingChanges()).toBe(false);
97
- });
98
- it('should not flush when no pending changes', async () => {
99
- await syncManager.flush();
100
- expect(flushCallback).not.toHaveBeenCalled();
101
- });
102
- it('should deduplicate concurrent flush calls', async () => {
103
- syncManager.markDirty('s-abc1');
104
- // Call flush multiple times concurrently
105
- const promise1 = syncManager.flush();
106
- const promise2 = syncManager.flush();
107
- const promise3 = syncManager.flush();
108
- await Promise.all([promise1, promise2, promise3]);
109
- // Should only call flush callback once
110
- expect(flushCallback).toHaveBeenCalledTimes(1);
111
- });
112
- });
113
- describe('cancel', () => {
114
- it('should cancel pending flush', async () => {
115
- syncManager.markDirty('s-abc1');
116
- syncManager.scheduleFlush();
117
- syncManager.cancel();
118
- // Advance past debounce
119
- await vi.advanceTimersByTimeAsync(config.debounceMs + 10);
120
- expect(flushCallback).not.toHaveBeenCalled();
121
- });
122
- it('should clear pending changes', () => {
123
- syncManager.markDirty('s-abc1');
124
- syncManager.cancel();
125
- expect(syncManager.hasPendingChanges()).toBe(false);
126
- });
127
- });
128
- });
129
- //# sourceMappingURL=sync.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../../../src/graph/__tests__/sync.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=validation.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation.test.d.ts","sourceRoot":"","sources":["../../../src/graph/__tests__/validation.test.ts"],"names":[],"mappings":""}
@@ -1,521 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createValidationService } from '../validation.js';
3
- describe('ValidationService', () => {
4
- const service = createValidationService();
5
- // =========================================================================
6
- // Node Creation Validation
7
- // =========================================================================
8
- describe('validateCreateNode', () => {
9
- describe('common fields', () => {
10
- it('requires type', () => {
11
- const input = { title: 'Test' };
12
- const result = service.validateCreateNode(input);
13
- expect(result.valid).toBe(false);
14
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'type' }));
15
- });
16
- it('rejects invalid type', () => {
17
- const input = { type: 'invalid', title: 'Test' };
18
- const result = service.validateCreateNode(input);
19
- expect(result.valid).toBe(false);
20
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'INVALID_TYPE', field: 'type' }));
21
- });
22
- it('requires title', () => {
23
- const input = { type: 'spec' };
24
- const result = service.validateCreateNode(input);
25
- expect(result.valid).toBe(false);
26
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'title' }));
27
- });
28
- it('rejects empty title', () => {
29
- const input = { type: 'spec', title: '' };
30
- const result = service.validateCreateNode(input);
31
- expect(result.valid).toBe(false);
32
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'title' }));
33
- });
34
- it('rejects title exceeding max length', () => {
35
- const input = {
36
- type: 'spec',
37
- title: 'a'.repeat(501),
38
- };
39
- const result = service.validateCreateNode(input);
40
- expect(result.valid).toBe(false);
41
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'MAX_LENGTH', field: 'title' }));
42
- });
43
- it('rejects content exceeding max length', () => {
44
- const input = {
45
- type: 'spec',
46
- title: 'Test',
47
- content: 'a'.repeat(100_001),
48
- };
49
- const result = service.validateCreateNode(input);
50
- expect(result.valid).toBe(false);
51
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'MAX_LENGTH', field: 'content' }));
52
- });
53
- it('rejects invalid priority (out of range)', () => {
54
- const input = {
55
- type: 'spec',
56
- title: 'Test',
57
- priority: 5,
58
- };
59
- const result = service.validateCreateNode(input);
60
- expect(result.valid).toBe(false);
61
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'INVALID_RANGE', field: 'priority' }));
62
- });
63
- it('rejects negative priority', () => {
64
- const input = {
65
- type: 'spec',
66
- title: 'Test',
67
- priority: -1,
68
- };
69
- const result = service.validateCreateNode(input);
70
- expect(result.valid).toBe(false);
71
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'INVALID_RANGE', field: 'priority' }));
72
- });
73
- it('accepts valid priority', () => {
74
- const input = {
75
- type: 'spec',
76
- title: 'Test',
77
- priority: 2,
78
- };
79
- const result = service.validateCreateNode(input);
80
- expect(result.valid).toBe(true);
81
- });
82
- });
83
- describe('spec validation', () => {
84
- it('accepts valid spec', () => {
85
- const input = {
86
- type: 'spec',
87
- title: 'Test Spec',
88
- content: 'Some content',
89
- };
90
- const result = service.validateCreateNode(input);
91
- expect(result.valid).toBe(true);
92
- expect(result.errors).toHaveLength(0);
93
- });
94
- it('accepts spec with optional status', () => {
95
- const input = {
96
- type: 'spec',
97
- title: 'Test Spec',
98
- status: 'draft',
99
- };
100
- const result = service.validateCreateNode(input);
101
- expect(result.valid).toBe(true);
102
- });
103
- });
104
- describe('issue validation', () => {
105
- it('requires status for issues', () => {
106
- const input = {
107
- type: 'issue',
108
- title: 'Test Issue',
109
- };
110
- const result = service.validateCreateNode(input);
111
- expect(result.valid).toBe(false);
112
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'status' }));
113
- });
114
- it('accepts valid issue', () => {
115
- const input = {
116
- type: 'issue',
117
- title: 'Test Issue',
118
- status: 'open',
119
- };
120
- const result = service.validateCreateNode(input);
121
- expect(result.valid).toBe(true);
122
- });
123
- it('warns for non-standard status', () => {
124
- const input = {
125
- type: 'issue',
126
- title: 'Test Issue',
127
- status: 'custom_status',
128
- };
129
- const result = service.validateCreateNode(input);
130
- expect(result.valid).toBe(true); // Warnings don't fail validation
131
- expect(result.warnings).toContainEqual(expect.objectContaining({
132
- code: 'NON_STANDARD_STATUS',
133
- field: 'status',
134
- }));
135
- });
136
- });
137
- describe('feedback validation', () => {
138
- it('requires target_id for feedback', () => {
139
- const input = {
140
- type: 'feedback',
141
- title: 'Test Feedback',
142
- feedback_type: 'comment',
143
- };
144
- const result = service.validateCreateNode(input);
145
- expect(result.valid).toBe(false);
146
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'target_id' }));
147
- });
148
- it('requires feedback_type for feedback', () => {
149
- const input = {
150
- type: 'feedback',
151
- title: 'Test Feedback',
152
- target_id: 's-abc123',
153
- };
154
- const result = service.validateCreateNode(input);
155
- expect(result.valid).toBe(false);
156
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'feedback_type' }));
157
- });
158
- it('accepts valid feedback', () => {
159
- const input = {
160
- type: 'feedback',
161
- title: 'Test Feedback',
162
- target_id: 's-abc123',
163
- feedback_type: 'comment',
164
- };
165
- const result = service.validateCreateNode(input);
166
- expect(result.valid).toBe(true);
167
- });
168
- });
169
- describe('external validation', () => {
170
- it('requires uri for external', () => {
171
- const input = {
172
- type: 'external',
173
- title: 'External Node',
174
- source: 'jira',
175
- };
176
- const result = service.validateCreateNode(input);
177
- expect(result.valid).toBe(false);
178
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'uri' }));
179
- });
180
- it('requires source for external', () => {
181
- const input = {
182
- type: 'external',
183
- title: 'External Node',
184
- uri: 'jira://PROJ-123',
185
- };
186
- const result = service.validateCreateNode(input);
187
- expect(result.valid).toBe(false);
188
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'source' }));
189
- });
190
- it('accepts valid external node', () => {
191
- const input = {
192
- type: 'external',
193
- title: 'External Node',
194
- uri: 'jira://PROJ-123',
195
- source: 'jira',
196
- };
197
- const result = service.validateCreateNode(input);
198
- expect(result.valid).toBe(true);
199
- });
200
- });
201
- });
202
- // =========================================================================
203
- // Node Update Validation
204
- // =========================================================================
205
- describe('validateUpdateNode', () => {
206
- const existingIssue = {
207
- id: 'i-test',
208
- uuid: 'test-uuid',
209
- type: 'issue',
210
- title: 'Existing Issue',
211
- status: 'open',
212
- created_at: '2025-01-26T10:00:00Z',
213
- updated_at: '2025-01-26T10:00:00Z',
214
- };
215
- it('accepts valid update', () => {
216
- const result = service.validateUpdateNode(existingIssue, {
217
- title: 'Updated Title',
218
- });
219
- expect(result.valid).toBe(true);
220
- });
221
- it('rejects title exceeding max length', () => {
222
- const result = service.validateUpdateNode(existingIssue, {
223
- title: 'a'.repeat(501),
224
- });
225
- expect(result.valid).toBe(false);
226
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'MAX_LENGTH', field: 'title' }));
227
- });
228
- it('warns for non-standard status on issues', () => {
229
- const result = service.validateUpdateNode(existingIssue, {
230
- status: 'custom',
231
- });
232
- expect(result.valid).toBe(true);
233
- expect(result.warnings).toContainEqual(expect.objectContaining({ code: 'NON_STANDARD_STATUS' }));
234
- });
235
- });
236
- // =========================================================================
237
- // Edge Validation
238
- // =========================================================================
239
- describe('validateCreateEdge', () => {
240
- const mockGetNode = async (id) => {
241
- const nodes = {
242
- 's-spec1': {
243
- id: 's-spec1',
244
- uuid: 'spec-uuid',
245
- type: 'spec',
246
- title: 'Test Spec',
247
- created_at: '2025-01-26T10:00:00Z',
248
- updated_at: '2025-01-26T10:00:00Z',
249
- },
250
- 'i-issue1': {
251
- id: 'i-issue1',
252
- uuid: 'issue-uuid',
253
- type: 'issue',
254
- title: 'Test Issue',
255
- status: 'open',
256
- created_at: '2025-01-26T10:00:00Z',
257
- updated_at: '2025-01-26T10:00:00Z',
258
- },
259
- };
260
- return nodes[id] || null;
261
- };
262
- it('requires from_id', async () => {
263
- const input = { to_id: 's-spec1', type: 'blocks' };
264
- const result = await service.validateCreateEdge(input, mockGetNode);
265
- expect(result.valid).toBe(false);
266
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'from_id' }));
267
- });
268
- it('requires to_id', async () => {
269
- const input = { from_id: 'i-issue1', type: 'blocks' };
270
- const result = await service.validateCreateEdge(input, mockGetNode);
271
- expect(result.valid).toBe(false);
272
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'to_id' }));
273
- });
274
- it('requires type', async () => {
275
- const input = { from_id: 'i-issue1', to_id: 's-spec1' };
276
- const result = await service.validateCreateEdge(input, mockGetNode);
277
- expect(result.valid).toBe(false);
278
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'REQUIRED', field: 'type' }));
279
- });
280
- it('rejects self-reference', async () => {
281
- const input = {
282
- from_id: 'i-issue1',
283
- to_id: 'i-issue1',
284
- type: 'blocks',
285
- };
286
- const result = await service.validateCreateEdge(input, mockGetNode);
287
- expect(result.valid).toBe(false);
288
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'SELF_REFERENCE' }));
289
- });
290
- it('errors when source node not found', async () => {
291
- const input = {
292
- from_id: 'i-nonexistent',
293
- to_id: 's-spec1',
294
- type: 'implements',
295
- };
296
- const result = await service.validateCreateEdge(input, mockGetNode);
297
- expect(result.valid).toBe(false);
298
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'NOT_FOUND', field: 'from_id' }));
299
- });
300
- it('errors when target node not found', async () => {
301
- const input = {
302
- from_id: 'i-issue1',
303
- to_id: 's-nonexistent',
304
- type: 'implements',
305
- };
306
- const result = await service.validateCreateEdge(input, mockGetNode);
307
- expect(result.valid).toBe(false);
308
- expect(result.errors).toContainEqual(expect.objectContaining({ code: 'NOT_FOUND', field: 'to_id' }));
309
- });
310
- it('accepts valid edge', async () => {
311
- const input = {
312
- from_id: 'i-issue1',
313
- to_id: 's-spec1',
314
- type: 'implements',
315
- };
316
- const result = await service.validateCreateEdge(input, mockGetNode);
317
- expect(result.valid).toBe(true);
318
- });
319
- it('warns when implements edge has non-issue source', async () => {
320
- const input = {
321
- from_id: 's-spec1',
322
- to_id: 'i-issue1',
323
- type: 'implements',
324
- };
325
- const result = await service.validateCreateEdge(input, mockGetNode);
326
- expect(result.valid).toBe(true); // Warning, not error
327
- expect(result.warnings).toContainEqual(expect.objectContaining({ code: 'IMPLEMENTS_FROM_NON_ISSUE' }));
328
- });
329
- it('warns when implements edge has non-spec target', async () => {
330
- const input = {
331
- from_id: 'i-issue1',
332
- to_id: 'i-issue1',
333
- type: 'implements',
334
- };
335
- // Note: This will hit self-reference first, so use different nodes
336
- const getNodeWithTwoIssues = async (id) => {
337
- const nodes = {
338
- 'i-issue1': {
339
- id: 'i-issue1',
340
- uuid: 'issue-uuid-1',
341
- type: 'issue',
342
- title: 'Test Issue 1',
343
- status: 'open',
344
- created_at: '2025-01-26T10:00:00Z',
345
- updated_at: '2025-01-26T10:00:00Z',
346
- },
347
- 'i-issue2': {
348
- id: 'i-issue2',
349
- uuid: 'issue-uuid-2',
350
- type: 'issue',
351
- title: 'Test Issue 2',
352
- status: 'open',
353
- created_at: '2025-01-26T10:00:00Z',
354
- updated_at: '2025-01-26T10:00:00Z',
355
- },
356
- };
357
- return nodes[id] || null;
358
- };
359
- const input2 = {
360
- from_id: 'i-issue1',
361
- to_id: 'i-issue2',
362
- type: 'implements',
363
- };
364
- const result = await service.validateCreateEdge(input2, getNodeWithTwoIssues);
365
- expect(result.valid).toBe(true);
366
- expect(result.warnings).toContainEqual(expect.objectContaining({ code: 'IMPLEMENTS_TO_NON_SPEC' }));
367
- });
368
- it('allows external URIs without node lookup', async () => {
369
- const input = {
370
- from_id: 'i-issue1',
371
- to_id: 'jira://PROJ-123',
372
- type: 'references',
373
- };
374
- const result = await service.validateCreateEdge(input, mockGetNode);
375
- expect(result.valid).toBe(true);
376
- });
377
- });
378
- // =========================================================================
379
- // Cycle Detection
380
- // =========================================================================
381
- describe('detectCycle', () => {
382
- it('detects simple cycle (A→B, adding B→A)', async () => {
383
- // A blocks B already exists
384
- const getBlocksEdges = async (nodeId) => {
385
- if (nodeId === 'i-b') {
386
- return []; // B doesn't block anything yet
387
- }
388
- if (nodeId === 'i-a') {
389
- return [
390
- {
391
- id: 'x-1',
392
- uuid: 'edge-uuid-1',
393
- from_id: 'i-a',
394
- to_id: 'i-b',
395
- type: 'blocks',
396
- created_at: '2025-01-26T10:00:00Z',
397
- },
398
- ];
399
- }
400
- return [];
401
- };
402
- // Adding B→A would create cycle
403
- const result = await service.detectCycle('i-b', 'i-a', getBlocksEdges);
404
- expect(result.hasCycle).toBe(true);
405
- expect(result.cycle).toEqual(['i-b', 'i-a']);
406
- });
407
- it('detects transitive cycle (A→B→C, adding C→A)', async () => {
408
- // A→B, B→C exists
409
- const getBlocksEdges = async (nodeId) => {
410
- const edges = {
411
- 'i-a': [
412
- {
413
- id: 'x-1',
414
- uuid: 'edge-1',
415
- from_id: 'i-a',
416
- to_id: 'i-b',
417
- type: 'blocks',
418
- created_at: '2025-01-26T10:00:00Z',
419
- },
420
- ],
421
- 'i-b': [
422
- {
423
- id: 'x-2',
424
- uuid: 'edge-2',
425
- from_id: 'i-b',
426
- to_id: 'i-c',
427
- type: 'blocks',
428
- created_at: '2025-01-26T10:00:00Z',
429
- },
430
- ],
431
- 'i-c': [],
432
- };
433
- return edges[nodeId] || [];
434
- };
435
- // Adding C→A would create cycle
436
- const result = await service.detectCycle('i-c', 'i-a', getBlocksEdges);
437
- expect(result.hasCycle).toBe(true);
438
- expect(result.cycle).toContain('i-a');
439
- expect(result.cycle).toContain('i-b');
440
- expect(result.cycle).toContain('i-c');
441
- });
442
- it('returns no cycle for valid edge', async () => {
443
- // A→B exists
444
- const getBlocksEdges = async (nodeId) => {
445
- if (nodeId === 'i-a') {
446
- return [
447
- {
448
- id: 'x-1',
449
- uuid: 'edge-uuid-1',
450
- from_id: 'i-a',
451
- to_id: 'i-b',
452
- type: 'blocks',
453
- created_at: '2025-01-26T10:00:00Z',
454
- },
455
- ];
456
- }
457
- return [];
458
- };
459
- // Adding A→C is fine (no cycle)
460
- const result = await service.detectCycle('i-a', 'i-c', getBlocksEdges);
461
- expect(result.hasCycle).toBe(false);
462
- expect(result.cycle).toBeUndefined();
463
- });
464
- it('handles no existing edges', async () => {
465
- const getBlocksEdges = async () => [];
466
- const result = await service.detectCycle('i-a', 'i-b', getBlocksEdges);
467
- expect(result.hasCycle).toBe(false);
468
- });
469
- it('handles complex graph without cycle', async () => {
470
- // Diamond: A→B, A→C, B→D, C→D
471
- const getBlocksEdges = async (nodeId) => {
472
- const edges = {
473
- 'i-a': [
474
- {
475
- id: 'x-1',
476
- uuid: 'e1',
477
- from_id: 'i-a',
478
- to_id: 'i-b',
479
- type: 'blocks',
480
- created_at: '2025-01-26T10:00:00Z',
481
- },
482
- {
483
- id: 'x-2',
484
- uuid: 'e2',
485
- from_id: 'i-a',
486
- to_id: 'i-c',
487
- type: 'blocks',
488
- created_at: '2025-01-26T10:00:00Z',
489
- },
490
- ],
491
- 'i-b': [
492
- {
493
- id: 'x-3',
494
- uuid: 'e3',
495
- from_id: 'i-b',
496
- to_id: 'i-d',
497
- type: 'blocks',
498
- created_at: '2025-01-26T10:00:00Z',
499
- },
500
- ],
501
- 'i-c': [
502
- {
503
- id: 'x-4',
504
- uuid: 'e4',
505
- from_id: 'i-c',
506
- to_id: 'i-d',
507
- type: 'blocks',
508
- created_at: '2025-01-26T10:00:00Z',
509
- },
510
- ],
511
- 'i-d': [],
512
- };
513
- return edges[nodeId] || [];
514
- };
515
- // Adding D→E is fine
516
- const result = await service.detectCycle('i-d', 'i-e', getBlocksEdges);
517
- expect(result.hasCycle).toBe(false);
518
- });
519
- });
520
- });
521
- //# sourceMappingURL=validation.test.js.map