macro-agent 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Tests for DaemonManager
3
+ *
4
+ * Covers:
5
+ * - connectProject() IPC location.register flow
6
+ * - connectProject() edge cases (missing dir, missing config, no hash, already connected)
7
+ * - getConnectedProjects() tracking
8
+ * - shutdown() cleanup
9
+ *
10
+ * Uses mocked filesystem and IPC client — no real daemon needed.
11
+ *
12
+ * @module task/backend/opentasks/__tests__/daemon-manager.test
13
+ */
14
+
15
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+ import { DaemonManager } from "../daemon-manager.js";
19
+ import { IPCOpenTasksClient } from "../client.js";
20
+
21
+ // Mock opentasks to avoid real daemon operations
22
+ vi.mock("opentasks", () => ({
23
+ checkExistingDaemon: vi.fn(),
24
+ createDaemonWithStore: vi.fn(),
25
+ }));
26
+
27
+ // Mock fs for connectProject tests
28
+ vi.mock("node:fs", async () => {
29
+ const actual = await vi.importActual<typeof fs>("node:fs");
30
+ return {
31
+ ...actual,
32
+ existsSync: vi.fn(),
33
+ readFileSync: vi.fn(),
34
+ mkdirSync: vi.fn(),
35
+ };
36
+ });
37
+
38
+ // =============================================================================
39
+ // Tests
40
+ // =============================================================================
41
+
42
+ describe("DaemonManager", () => {
43
+ let manager: DaemonManager;
44
+
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ });
48
+
49
+ afterEach(async () => {
50
+ if (manager) {
51
+ await manager.shutdown();
52
+ }
53
+ });
54
+
55
+ // ─────────────────────────────────────────────────────────────────
56
+ // connectProject()
57
+ // ─────────────────────────────────────────────────────────────────
58
+
59
+ describe("connectProject()", () => {
60
+ let mockClient: IPCOpenTasksClient;
61
+
62
+ beforeEach(() => {
63
+ manager = new DaemonManager({ connectOnSpawn: true });
64
+
65
+ // Create a mock IPCOpenTasksClient with a call method
66
+ mockClient = {
67
+ call: vi.fn().mockResolvedValue({ success: true }),
68
+ isConnected: vi.fn().mockReturnValue(true),
69
+ connect: vi.fn().mockResolvedValue(undefined),
70
+ disconnect: vi.fn(),
71
+ } as unknown as IPCOpenTasksClient;
72
+
73
+ // Inject the mock client via private field access
74
+ (manager as any).client = mockClient;
75
+ });
76
+
77
+ it("should register project location with daemon via IPC", async () => {
78
+ const projectPath = "/projects/my-app";
79
+ const opentasksDir = path.join(projectPath, ".opentasks");
80
+
81
+ vi.mocked(fs.existsSync).mockReturnValue(true);
82
+ vi.mocked(fs.readFileSync).mockReturnValue(
83
+ JSON.stringify({
84
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
85
+ })
86
+ );
87
+
88
+ await manager.connectProject(projectPath);
89
+
90
+ expect(mockClient.call).toHaveBeenCalledWith("location.register", {
91
+ hash: "abc123",
92
+ opentasksPath: opentasksDir,
93
+ });
94
+ expect(manager.getConnectedProjects()).toContain(opentasksDir);
95
+ });
96
+
97
+ it("should skip if connectOnSpawn is false", async () => {
98
+ manager = new DaemonManager({ connectOnSpawn: false });
99
+ (manager as any).client = mockClient;
100
+
101
+ await manager.connectProject("/projects/my-app");
102
+
103
+ expect(mockClient.call).not.toHaveBeenCalled();
104
+ expect(manager.getConnectedProjects()).toHaveLength(0);
105
+ });
106
+
107
+ it("should skip if client is not set", async () => {
108
+ (manager as any).client = null;
109
+
110
+ await manager.connectProject("/projects/my-app");
111
+
112
+ expect(manager.getConnectedProjects()).toHaveLength(0);
113
+ });
114
+
115
+ it("should skip if .opentasks/ directory does not exist", async () => {
116
+ vi.mocked(fs.existsSync).mockReturnValue(false);
117
+
118
+ await manager.connectProject("/projects/my-app");
119
+
120
+ expect(mockClient.call).not.toHaveBeenCalled();
121
+ expect(manager.getConnectedProjects()).toHaveLength(0);
122
+ });
123
+
124
+ it("should skip if config.json has no location hash", async () => {
125
+ vi.mocked(fs.existsSync).mockReturnValue(true);
126
+ vi.mocked(fs.readFileSync).mockReturnValue(
127
+ JSON.stringify({ location: { uuid: "uuid-1" } }) // no hash
128
+ );
129
+
130
+ await manager.connectProject("/projects/my-app");
131
+
132
+ expect(mockClient.call).not.toHaveBeenCalled();
133
+ expect(manager.getConnectedProjects()).toHaveLength(0);
134
+ });
135
+
136
+ it("should skip if project is already connected", async () => {
137
+ const projectPath = "/projects/my-app";
138
+ const opentasksDir = path.join(projectPath, ".opentasks");
139
+
140
+ vi.mocked(fs.existsSync).mockReturnValue(true);
141
+ vi.mocked(fs.readFileSync).mockReturnValue(
142
+ JSON.stringify({
143
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
144
+ })
145
+ );
146
+
147
+ // Connect once
148
+ await manager.connectProject(projectPath);
149
+ expect(mockClient.call).toHaveBeenCalledTimes(1);
150
+
151
+ // Try to connect again — should be idempotent
152
+ await manager.connectProject(projectPath);
153
+ expect(mockClient.call).toHaveBeenCalledTimes(1);
154
+ });
155
+
156
+ it("should handle IPC errors gracefully (non-fatal)", async () => {
157
+ vi.mocked(fs.existsSync).mockReturnValue(true);
158
+ vi.mocked(fs.readFileSync).mockReturnValue(
159
+ JSON.stringify({
160
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
161
+ })
162
+ );
163
+ (mockClient as any).call.mockRejectedValue(new Error("IPC failed"));
164
+
165
+ // Should not throw
166
+ await manager.connectProject("/projects/my-app");
167
+
168
+ // Should not be tracked as connected since the IPC call failed
169
+ expect(manager.getConnectedProjects()).toHaveLength(0);
170
+ });
171
+
172
+ it("should handle malformed config.json gracefully", async () => {
173
+ vi.mocked(fs.existsSync).mockReturnValue(true);
174
+ vi.mocked(fs.readFileSync).mockReturnValue("not valid json");
175
+
176
+ // Should not throw
177
+ await manager.connectProject("/projects/my-app");
178
+
179
+ expect(mockClient.call).not.toHaveBeenCalled();
180
+ expect(manager.getConnectedProjects()).toHaveLength(0);
181
+ });
182
+
183
+ it("should not call IPC if client is not an IPCOpenTasksClient", async () => {
184
+ // Replace with a plain mock that isn't an instance of IPCOpenTasksClient
185
+ const plainClient = {
186
+ isConnected: vi.fn().mockReturnValue(true),
187
+ connect: vi.fn().mockResolvedValue(undefined),
188
+ disconnect: vi.fn(),
189
+ } as any;
190
+ (manager as any).client = plainClient;
191
+
192
+ vi.mocked(fs.existsSync).mockReturnValue(true);
193
+ vi.mocked(fs.readFileSync).mockReturnValue(
194
+ JSON.stringify({
195
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
196
+ })
197
+ );
198
+
199
+ await manager.connectProject("/projects/my-app");
200
+
201
+ // Should still track as connected (IPC call is skipped, not failed)
202
+ expect(manager.getConnectedProjects()).toContain(
203
+ path.join("/projects/my-app", ".opentasks")
204
+ );
205
+ });
206
+ });
207
+
208
+ // ─────────────────────────────────────────────────────────────────
209
+ // isProjectConnected()
210
+ // ─────────────────────────────────────────────────────────────────
211
+
212
+ describe("isProjectConnected()", () => {
213
+ it("should return false for unconnected project", () => {
214
+ manager = new DaemonManager();
215
+ expect(manager.isProjectConnected("/projects/my-app")).toBe(false);
216
+ });
217
+
218
+ it("should return true after connecting", async () => {
219
+ manager = new DaemonManager({ connectOnSpawn: true });
220
+ const mockClient = {
221
+ call: vi.fn().mockResolvedValue({ success: true }),
222
+ isConnected: vi.fn().mockReturnValue(true),
223
+ connect: vi.fn(),
224
+ disconnect: vi.fn(),
225
+ } as unknown as IPCOpenTasksClient;
226
+ (manager as any).client = mockClient;
227
+
228
+ vi.mocked(fs.existsSync).mockReturnValue(true);
229
+ vi.mocked(fs.readFileSync).mockReturnValue(
230
+ JSON.stringify({
231
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
232
+ })
233
+ );
234
+
235
+ await manager.connectProject("/projects/my-app");
236
+ expect(manager.isProjectConnected("/projects/my-app")).toBe(true);
237
+ });
238
+ });
239
+
240
+ // ─────────────────────────────────────────────────────────────────
241
+ // shutdown()
242
+ // ─────────────────────────────────────────────────────────────────
243
+
244
+ describe("shutdown()", () => {
245
+ it("should clear connected projects on shutdown", async () => {
246
+ manager = new DaemonManager({ connectOnSpawn: true });
247
+ const mockClient = {
248
+ call: vi.fn().mockResolvedValue({ success: true }),
249
+ isConnected: vi.fn().mockReturnValue(true),
250
+ connect: vi.fn(),
251
+ disconnect: vi.fn(),
252
+ } as unknown as IPCOpenTasksClient;
253
+ (manager as any).client = mockClient;
254
+
255
+ vi.mocked(fs.existsSync).mockReturnValue(true);
256
+ vi.mocked(fs.readFileSync).mockReturnValue(
257
+ JSON.stringify({
258
+ location: { hash: "abc123", uuid: "uuid-1", name: "my-app" },
259
+ })
260
+ );
261
+
262
+ await manager.connectProject("/projects/my-app");
263
+ expect(manager.getConnectedProjects()).toHaveLength(1);
264
+
265
+ await manager.shutdown();
266
+ expect(manager.getConnectedProjects()).toHaveLength(0);
267
+ });
268
+
269
+ it("should disconnect client on shutdown", async () => {
270
+ manager = new DaemonManager();
271
+ const mockClient = {
272
+ disconnect: vi.fn(),
273
+ isConnected: vi.fn().mockReturnValue(true),
274
+ } as any;
275
+ (manager as any).client = mockClient;
276
+
277
+ await manager.shutdown();
278
+
279
+ expect(mockClient.disconnect).toHaveBeenCalled();
280
+ });
281
+
282
+ it("should stop daemon only if ownsDaemon is true", async () => {
283
+ manager = new DaemonManager();
284
+ const mockDaemon = { stop: vi.fn().mockResolvedValue(undefined) };
285
+ const mockClient = { disconnect: vi.fn() } as any;
286
+
287
+ (manager as any).client = mockClient;
288
+ (manager as any).daemon = mockDaemon;
289
+ (manager as any).ownsDaemon = true;
290
+
291
+ await manager.shutdown();
292
+
293
+ expect(mockDaemon.stop).toHaveBeenCalled();
294
+ });
295
+
296
+ it("should NOT stop daemon if ownsDaemon is false", async () => {
297
+ manager = new DaemonManager();
298
+ const mockDaemon = { stop: vi.fn().mockResolvedValue(undefined) };
299
+ const mockClient = { disconnect: vi.fn() } as any;
300
+
301
+ (manager as any).client = mockClient;
302
+ (manager as any).daemon = mockDaemon;
303
+ (manager as any).ownsDaemon = false;
304
+
305
+ await manager.shutdown();
306
+
307
+ expect(mockDaemon.stop).not.toHaveBeenCalled();
308
+ });
309
+
310
+ it("should delay daemon.stop() by drain grace period when ownsDaemon", async () => {
311
+ manager = new DaemonManager();
312
+ const stopTimes: number[] = [];
313
+ const mockDaemon = {
314
+ stop: vi.fn().mockImplementation(async () => {
315
+ stopTimes.push(Date.now());
316
+ }),
317
+ };
318
+ const mockClient = { disconnect: vi.fn() } as any;
319
+
320
+ (manager as any).client = mockClient;
321
+ (manager as any).daemon = mockDaemon;
322
+ (manager as any).ownsDaemon = true;
323
+
324
+ const startTime = Date.now();
325
+ await manager.shutdown();
326
+
327
+ expect(mockDaemon.stop).toHaveBeenCalled();
328
+ // Drain delay should be at least ~450ms (500ms target with timing tolerance)
329
+ const elapsed = stopTimes[0] - startTime;
330
+ expect(elapsed).toBeGreaterThanOrEqual(450);
331
+ });
332
+ });
333
+
334
+ // ─────────────────────────────────────────────────────────────────
335
+ // ensureDaemon()
336
+ // ─────────────────────────────────────────────────────────────────
337
+
338
+ describe("ensureDaemon()", () => {
339
+ it("should return socketPath when connecting to existing daemon", async () => {
340
+ const { checkExistingDaemon } = await import("opentasks");
341
+
342
+ vi.mocked(checkExistingDaemon).mockResolvedValue({
343
+ running: true,
344
+ socketPath: "/tmp/existing.sock",
345
+ pid: 1234,
346
+ });
347
+
348
+ // Mock IPCOpenTasksClient constructor and connect
349
+ const connectSpy = vi.fn().mockResolvedValue(undefined);
350
+ vi.spyOn(
351
+ await import("../client.js"),
352
+ "IPCOpenTasksClient"
353
+ ).mockImplementation(
354
+ () =>
355
+ ({
356
+ connect: connectSpy,
357
+ disconnect: vi.fn(),
358
+ isConnected: vi.fn().mockReturnValue(true),
359
+ }) as any
360
+ );
361
+
362
+ manager = new DaemonManager({ centralPath: "/tmp/central" });
363
+ const result = await manager.ensureDaemon();
364
+
365
+ expect(result.socketPath).toBe("/tmp/existing.sock");
366
+ expect(result.ownsDaemon).toBe(false);
367
+ expect(connectSpy).toHaveBeenCalled();
368
+ });
369
+
370
+ it("should return socketPath when starting new daemon", async () => {
371
+ const { checkExistingDaemon, createDaemonWithStore } =
372
+ await import("opentasks");
373
+
374
+ vi.mocked(checkExistingDaemon).mockResolvedValue({
375
+ running: false,
376
+ });
377
+
378
+ const mockDaemon = {
379
+ start: vi.fn().mockResolvedValue(undefined),
380
+ stop: vi.fn().mockResolvedValue(undefined),
381
+ socketPath: "/tmp/new-daemon.sock",
382
+ };
383
+ vi.mocked(createDaemonWithStore).mockResolvedValue(mockDaemon as any);
384
+
385
+ const connectSpy = vi.fn().mockResolvedValue(undefined);
386
+ vi.spyOn(
387
+ await import("../client.js"),
388
+ "IPCOpenTasksClient"
389
+ ).mockImplementation(
390
+ () =>
391
+ ({
392
+ connect: connectSpy,
393
+ disconnect: vi.fn(),
394
+ isConnected: vi.fn().mockReturnValue(true),
395
+ }) as any
396
+ );
397
+
398
+ manager = new DaemonManager({ centralPath: "/tmp/central" });
399
+ const result = await manager.ensureDaemon();
400
+
401
+ expect(result.socketPath).toBe("/tmp/new-daemon.sock");
402
+ expect(result.ownsDaemon).toBe(true);
403
+ expect(mockDaemon.start).toHaveBeenCalled();
404
+ });
405
+ });
406
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Tests for OpenTasks status mapping utilities
3
+ *
4
+ * @module task/backend/opentasks/__tests__/mapping.test
5
+ */
6
+
7
+ import { describe, it, expect } from "vitest";
8
+ import {
9
+ mapOpenTasksStatus,
10
+ mapTaskStatus,
11
+ isIssueComplete,
12
+ isIssueBlocked,
13
+ } from "../mapping.js";
14
+
15
+ describe("OpenTasks Mapping", () => {
16
+ describe("mapOpenTasksStatus", () => {
17
+ it("should map 'open' to 'pending'", () => {
18
+ expect(mapOpenTasksStatus("open")).toBe("pending");
19
+ });
20
+
21
+ it("should map 'in_progress' to 'in_progress'", () => {
22
+ expect(mapOpenTasksStatus("in_progress")).toBe("in_progress");
23
+ });
24
+
25
+ it("should map 'blocked' to 'pending'", () => {
26
+ expect(mapOpenTasksStatus("blocked")).toBe("pending");
27
+ });
28
+
29
+ it("should map 'closed' to 'completed'", () => {
30
+ expect(mapOpenTasksStatus("closed")).toBe("completed");
31
+ });
32
+
33
+ it("should map unknown statuses to 'pending'", () => {
34
+ expect(mapOpenTasksStatus("unknown")).toBe("pending");
35
+ expect(mapOpenTasksStatus("")).toBe("pending");
36
+ });
37
+ });
38
+
39
+ describe("mapTaskStatus", () => {
40
+ it("should map 'pending' to 'open'", () => {
41
+ expect(mapTaskStatus("pending")).toBe("open");
42
+ });
43
+
44
+ it("should map 'assigned' to 'open'", () => {
45
+ expect(mapTaskStatus("assigned")).toBe("open");
46
+ });
47
+
48
+ it("should map 'in_progress' to 'in_progress'", () => {
49
+ expect(mapTaskStatus("in_progress")).toBe("in_progress");
50
+ });
51
+
52
+ it("should map 'completed' to 'closed'", () => {
53
+ expect(mapTaskStatus("completed")).toBe("closed");
54
+ });
55
+
56
+ it("should map 'failed' to 'closed'", () => {
57
+ expect(mapTaskStatus("failed")).toBe("closed");
58
+ });
59
+ });
60
+
61
+ describe("isIssueComplete", () => {
62
+ it("should return true for 'closed'", () => {
63
+ expect(isIssueComplete("closed")).toBe(true);
64
+ });
65
+
66
+ it("should return false for other statuses", () => {
67
+ expect(isIssueComplete("open")).toBe(false);
68
+ expect(isIssueComplete("in_progress")).toBe(false);
69
+ expect(isIssueComplete("blocked")).toBe(false);
70
+ });
71
+ });
72
+
73
+ describe("isIssueBlocked", () => {
74
+ it("should return true for 'blocked'", () => {
75
+ expect(isIssueBlocked("blocked")).toBe(true);
76
+ });
77
+
78
+ it("should return false for other statuses", () => {
79
+ expect(isIssueBlocked("open")).toBe(false);
80
+ expect(isIssueBlocked("in_progress")).toBe(false);
81
+ expect(isIssueBlocked("closed")).toBe(false);
82
+ });
83
+ });
84
+ });