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
@@ -1,623 +0,0 @@
1
- /**
2
- * StandaloneClient - SudocodeClient implementation for standalone mode
3
- *
4
- * Accesses sudocode data directly via CLI operations and file watching.
5
- * This is used when macro-agent runs independently without a sudocode server.
6
- *
7
- * @module task/backend/sudocode/standalone-client
8
- * @see s-8472 Pluggable Task Backend Integration with Sudocode
9
- * @see i-1ju3 7A.3: Implement StandaloneClient (standalone mode)
10
- */
11
-
12
- import { existsSync, mkdirSync } from "fs";
13
- import { join } from "path";
14
-
15
- import type {
16
- SudocodeClient,
17
- StandaloneClientConfig,
18
- ListIssuesOptions,
19
- ListSpecsOptions,
20
- UpdateIssueInput,
21
- FeedbackInput,
22
- IssueChangeCallback,
23
- IssueChangeEvent,
24
- Unsubscribe,
25
- } from "./client.js";
26
-
27
- import type {
28
- Issue,
29
- Spec,
30
- RelationshipType,
31
- EntityType,
32
- } from "@sudocode-ai/types";
33
-
34
- import type { Database } from "better-sqlite3";
35
-
36
- // Import CLI operations - these may throw if not available
37
- // We use dynamic import to handle this gracefully
38
- type SudocodeCliModule = typeof import("@sudocode-ai/cli");
39
-
40
- /**
41
- * StandaloneClient - CLI-based client for standalone mode
42
- *
43
- * Uses @sudocode-ai/cli operations directly against a SQLite database.
44
- * Supports event subscriptions via polling since we don't have file watchers here.
45
- */
46
- export class StandaloneClient implements SudocodeClient {
47
- private config: StandaloneClientConfig;
48
- private ready: boolean = false;
49
- private db: Database | null = null;
50
- private cli: SudocodeCliModule | null = null;
51
- private changeCallbacks: Map<string | "*", Set<IssueChangeCallback>> =
52
- new Map();
53
- private pollTimer: ReturnType<typeof setInterval> | null = null;
54
- private lastKnownIssues: Map<string, Issue> = new Map();
55
-
56
- constructor(config: StandaloneClientConfig) {
57
- this.config = {
58
- projectPath: config.projectPath,
59
- pollInterval: config.pollInterval ?? 5000,
60
- };
61
- }
62
-
63
- /**
64
- * Initialize the client - must be called before other operations
65
- */
66
- async init(): Promise<void> {
67
- if (this.ready) return;
68
-
69
- // Dynamically import CLI module
70
- try {
71
- this.cli = await import("@sudocode-ai/cli");
72
- } catch (error) {
73
- throw new Error(
74
- `Failed to load @sudocode-ai/cli package: ${error instanceof Error ? error.message : String(error)}`
75
- );
76
- }
77
-
78
- // Initialize database
79
- const sudocodeDir = join(this.config.projectPath, ".sudocode");
80
- const dbPath = join(sudocodeDir, "cache.db");
81
-
82
- // Create .sudocode directory if it doesn't exist
83
- if (!existsSync(sudocodeDir)) {
84
- mkdirSync(sudocodeDir, { recursive: true });
85
- }
86
-
87
- // Initialize database (creates schema if needed)
88
- this.db = this.cli.initDatabase({ path: dbPath });
89
-
90
- this.ready = true;
91
-
92
- // Load initial state for change detection
93
- await this.refreshIssueCache();
94
-
95
- // Start polling for changes if we have subscribers
96
- this.startPollingIfNeeded();
97
- }
98
-
99
- /**
100
- * Internal: ensure client is initialized
101
- */
102
- private ensureReady(): void {
103
- if (!this.ready || !this.db || !this.cli) {
104
- throw new Error(
105
- "StandaloneClient not initialized. Call init() before using."
106
- );
107
- }
108
- }
109
-
110
- /**
111
- * Internal: get the CLI module (type-safe helper)
112
- */
113
- private getCli(): SudocodeCliModule {
114
- this.ensureReady();
115
- return this.cli!;
116
- }
117
-
118
- /**
119
- * Internal: get the database (type-safe helper)
120
- */
121
- private getDb(): Database {
122
- this.ensureReady();
123
- return this.db!;
124
- }
125
-
126
- // ─── Issue Operations ────────────────────────────────────────────────────────
127
-
128
- async getIssue(id: string): Promise<Issue | null> {
129
- this.ensureReady();
130
- const issue = this.getCli().getIssue(this.getDb(), id);
131
- return issue ?? null;
132
- }
133
-
134
- async listIssues(filter?: ListIssuesOptions): Promise<Issue[]> {
135
- this.ensureReady();
136
- const cli = this.getCli();
137
- const db = this.getDb();
138
-
139
- if (filter?.search) {
140
- // Use search function for text search
141
- return cli.searchIssues(db, filter.search, {
142
- status: filter.status,
143
- priority: filter.priority,
144
- archived: filter.archived,
145
- limit: filter.limit,
146
- }) as Issue[];
147
- }
148
-
149
- return cli.listIssues(db, {
150
- status: filter?.status,
151
- priority: filter?.priority,
152
- archived: filter?.archived,
153
- limit: filter?.limit,
154
- }) as Issue[];
155
- }
156
-
157
- async getReadyIssues(): Promise<Issue[]> {
158
- this.ensureReady();
159
- return this.getCli().getReadyIssues(this.getDb()) as Issue[];
160
- }
161
-
162
- async updateIssue(id: string, updates: UpdateIssueInput): Promise<Issue> {
163
- this.ensureReady();
164
- const cli = this.getCli();
165
- const db = this.getDb();
166
-
167
- // Transform updates to match CLI's expected type (convert null to undefined)
168
- const cliUpdates = {
169
- ...updates,
170
- assignee: updates.assignee === null ? undefined : updates.assignee,
171
- };
172
-
173
- const previousIssue = cli.getIssue(db, id) as Issue | undefined;
174
- const updated = cli.updateIssue(db, id, cliUpdates) as Issue;
175
-
176
- // Emit change event
177
- if (previousIssue) {
178
- this.emitChangeEvent({
179
- type: updates.status !== previousIssue.status ? "status_changed" : "updated",
180
- issueId: id,
181
- issue: updated,
182
- previousIssue,
183
- });
184
- }
185
-
186
- return updated;
187
- }
188
-
189
- // ─── Relationship Operations ─────────────────────────────────────────────────
190
-
191
- async createLink(
192
- from: string,
193
- to: string,
194
- type: RelationshipType
195
- ): Promise<void> {
196
- this.ensureReady();
197
- const cli = this.getCli();
198
- const db = this.getDb();
199
-
200
- const fromType = this.inferEntityType(from);
201
- const toType = this.inferEntityType(to);
202
-
203
- cli.addRelationship(db, {
204
- from_id: from,
205
- from_type: fromType,
206
- to_id: to,
207
- to_type: toType,
208
- relationship_type: type,
209
- });
210
-
211
- // If this was a 'blocks' relationship between issues, emit events
212
- if (type === "blocks" && fromType === "issue" && toType === "issue") {
213
- const toIssue = cli.getIssue(db, to) as Issue | undefined;
214
- if (toIssue && toIssue.status === "blocked") {
215
- this.emitChangeEvent({
216
- type: "blocked",
217
- issueId: to,
218
- issue: toIssue,
219
- metadata: { blockerId: from },
220
- });
221
- }
222
- }
223
- }
224
-
225
- async removeLink(
226
- from: string,
227
- to: string,
228
- type: RelationshipType
229
- ): Promise<void> {
230
- this.ensureReady();
231
- const cli = this.getCli();
232
- const db = this.getDb();
233
-
234
- const fromType = this.inferEntityType(from);
235
- const toType = this.inferEntityType(to);
236
-
237
- cli.removeRelationship(db, from, fromType, to, toType, type);
238
-
239
- // If this was a 'blocks' relationship, check if issue was unblocked
240
- if (type === "blocks" && fromType === "issue" && toType === "issue") {
241
- const toIssue = cli.getIssue(db, to) as Issue | undefined;
242
- if (toIssue && toIssue.status !== "blocked") {
243
- this.emitChangeEvent({
244
- type: "unblocked",
245
- issueId: to,
246
- issue: toIssue,
247
- metadata: { formerBlockerId: from },
248
- });
249
- }
250
- }
251
- }
252
-
253
- async getBlockers(issueId: string): Promise<Issue[]> {
254
- this.ensureReady();
255
- const cli = this.getCli();
256
- const db = this.getDb();
257
-
258
- // Get incoming 'blocks' relationships (other issues that block this one)
259
- const incomingBlocks = cli.getIncomingRelationships(
260
- db,
261
- issueId,
262
- "issue",
263
- "blocks"
264
- );
265
-
266
- // Get outgoing 'depends-on' relationships (this issue depends on others)
267
- const outgoingDependsOn = cli.getOutgoingRelationships(
268
- db,
269
- issueId,
270
- "issue",
271
- "depends-on"
272
- );
273
-
274
- const blockerIds = new Set<string>();
275
-
276
- // For 'blocks': from_id blocks issueId
277
- for (const rel of incomingBlocks) {
278
- if (rel.from_type === "issue") {
279
- blockerIds.add(rel.from_id);
280
- }
281
- }
282
-
283
- // For 'depends-on': issueId depends-on to_id
284
- for (const rel of outgoingDependsOn) {
285
- if (rel.to_type === "issue") {
286
- blockerIds.add(rel.to_id);
287
- }
288
- }
289
-
290
- const blockers: Issue[] = [];
291
- for (const blockerId of blockerIds) {
292
- const blocker = cli.getIssue(db, blockerId) as Issue | undefined;
293
- if (blocker) {
294
- blockers.push(blocker);
295
- }
296
- }
297
-
298
- return blockers;
299
- }
300
-
301
- async getBlocking(issueId: string): Promise<Issue[]> {
302
- this.ensureReady();
303
- const cli = this.getCli();
304
- const db = this.getDb();
305
-
306
- // Get outgoing 'blocks' relationships (this issue blocks others)
307
- const outgoingBlocks = cli.getOutgoingRelationships(
308
- db,
309
- issueId,
310
- "issue",
311
- "blocks"
312
- );
313
-
314
- // Get incoming 'depends-on' relationships (others depend on this issue)
315
- const incomingDependsOn = cli.getIncomingRelationships(
316
- db,
317
- issueId,
318
- "issue",
319
- "depends-on"
320
- );
321
-
322
- const blockedIds = new Set<string>();
323
-
324
- // For 'blocks': issueId blocks to_id
325
- for (const rel of outgoingBlocks) {
326
- if (rel.to_type === "issue") {
327
- blockedIds.add(rel.to_id);
328
- }
329
- }
330
-
331
- // For 'depends-on': from_id depends-on issueId
332
- for (const rel of incomingDependsOn) {
333
- if (rel.from_type === "issue") {
334
- blockedIds.add(rel.from_id);
335
- }
336
- }
337
-
338
- const blocked: Issue[] = [];
339
- for (const blockedId of blockedIds) {
340
- const issue = cli.getIssue(db, blockedId) as Issue | undefined;
341
- if (issue) {
342
- blocked.push(issue);
343
- }
344
- }
345
-
346
- return blocked;
347
- }
348
-
349
- // ─── Spec Operations ─────────────────────────────────────────────────────────
350
-
351
- async getSpec(id: string): Promise<Spec | null> {
352
- this.ensureReady();
353
- const spec = this.getCli().getSpec(this.getDb(), id);
354
- return (spec as Spec) ?? null;
355
- }
356
-
357
- async listSpecs(filter?: ListSpecsOptions): Promise<Spec[]> {
358
- this.ensureReady();
359
- const cli = this.getCli();
360
- const db = this.getDb();
361
-
362
- if (filter?.search) {
363
- return cli.searchSpecs(db, filter.search, {
364
- limit: filter.limit,
365
- }) as Spec[];
366
- }
367
-
368
- return cli.listSpecs(db, {
369
- limit: filter?.limit,
370
- }) as Spec[];
371
- }
372
-
373
- // ─── Feedback Operations ─────────────────────────────────────────────────────
374
-
375
- async addFeedback(
376
- fromIssueId: string | undefined,
377
- toId: string,
378
- feedback: FeedbackInput
379
- ): Promise<void> {
380
- this.ensureReady();
381
- const cli = this.getCli();
382
- const db = this.getDb();
383
-
384
- // Check if addFeedback is available
385
- if (typeof (cli as unknown as Record<string, unknown>).addFeedback !== "function") {
386
- // Fallback: feedback not supported in this CLI version
387
- console.warn("Feedback operations not supported in this @sudocode-ai/cli version");
388
- return;
389
- }
390
-
391
- // Call addFeedback if available
392
- (cli as unknown as { addFeedback: (db: Database, input: unknown) => void }).addFeedback(db, {
393
- from_id: fromIssueId,
394
- to_id: toId,
395
- type: feedback.type,
396
- content: feedback.content,
397
- agent: feedback.agent,
398
- line: feedback.anchor?.line,
399
- text: feedback.anchor?.text,
400
- });
401
- }
402
-
403
- // ─── Event Subscription ──────────────────────────────────────────────────────
404
-
405
- onIssueChange(callback: IssueChangeCallback): Unsubscribe;
406
- onIssueChange(issueId: string, callback: IssueChangeCallback): Unsubscribe;
407
- onIssueChange(
408
- callbackOrId: IssueChangeCallback | string,
409
- maybeCallback?: IssueChangeCallback
410
- ): Unsubscribe {
411
- let key: string;
412
- let callback: IssueChangeCallback;
413
-
414
- if (typeof callbackOrId === "function") {
415
- key = "*";
416
- callback = callbackOrId;
417
- } else {
418
- key = callbackOrId;
419
- callback = maybeCallback!;
420
- }
421
-
422
- if (!this.changeCallbacks.has(key)) {
423
- this.changeCallbacks.set(key, new Set());
424
- }
425
- this.changeCallbacks.get(key)!.add(callback);
426
-
427
- // Start polling if we have subscribers and not already polling
428
- this.startPollingIfNeeded();
429
-
430
- return () => {
431
- const callbacks = this.changeCallbacks.get(key);
432
- if (callbacks) {
433
- callbacks.delete(callback);
434
- if (callbacks.size === 0) {
435
- this.changeCallbacks.delete(key);
436
- }
437
- }
438
-
439
- // Stop polling if no more subscribers
440
- this.stopPollingIfNotNeeded();
441
- };
442
- }
443
-
444
- // ─── Lifecycle ───────────────────────────────────────────────────────────────
445
-
446
- isReady(): boolean {
447
- return this.ready;
448
- }
449
-
450
- close(): void {
451
- this.ready = false;
452
-
453
- // Stop polling
454
- if (this.pollTimer) {
455
- clearInterval(this.pollTimer);
456
- this.pollTimer = null;
457
- }
458
-
459
- // Close database
460
- if (this.db) {
461
- this.db.close();
462
- this.db = null;
463
- }
464
-
465
- // Clear callbacks
466
- this.changeCallbacks.clear();
467
- this.lastKnownIssues.clear();
468
- }
469
-
470
- // ─── Internal Helpers ────────────────────────────────────────────────────────
471
-
472
- /**
473
- * Infer entity type from ID prefix
474
- */
475
- private inferEntityType(id: string): EntityType {
476
- if (id.startsWith("i-")) return "issue";
477
- if (id.startsWith("s-")) return "spec";
478
- throw new Error(`Cannot infer entity type from ID: ${id}`);
479
- }
480
-
481
- /**
482
- * Emit a change event to subscribers
483
- */
484
- private emitChangeEvent(event: IssueChangeEvent): void {
485
- // Notify specific subscribers
486
- const specificCallbacks = this.changeCallbacks.get(event.issueId);
487
- if (specificCallbacks) {
488
- for (const callback of specificCallbacks) {
489
- try {
490
- callback(event);
491
- } catch (error) {
492
- console.error("Error in issue change callback:", error);
493
- }
494
- }
495
- }
496
-
497
- // Notify global subscribers
498
- const globalCallbacks = this.changeCallbacks.get("*");
499
- if (globalCallbacks) {
500
- for (const callback of globalCallbacks) {
501
- try {
502
- callback(event);
503
- } catch (error) {
504
- console.error("Error in issue change callback:", error);
505
- }
506
- }
507
- }
508
- }
509
-
510
- /**
511
- * Refresh the internal issue cache for change detection
512
- */
513
- private async refreshIssueCache(): Promise<void> {
514
- if (!this.ready || !this.db || !this.cli) return;
515
-
516
- const issues = this.cli.listIssues(this.db, { archived: false }) as Issue[];
517
- this.lastKnownIssues.clear();
518
- for (const issue of issues) {
519
- this.lastKnownIssues.set(issue.id, issue);
520
- }
521
- }
522
-
523
- /**
524
- * Poll for changes and emit events
525
- */
526
- private async pollForChanges(): Promise<void> {
527
- if (!this.ready || !this.db || !this.cli) return;
528
-
529
- const currentIssues = this.cli.listIssues(this.db, { archived: false }) as Issue[];
530
- const currentMap = new Map<string, Issue>();
531
- for (const issue of currentIssues) {
532
- currentMap.set(issue.id, issue);
533
- }
534
-
535
- // Check for deleted issues
536
- for (const [id, previousIssue] of this.lastKnownIssues) {
537
- if (!currentMap.has(id)) {
538
- this.emitChangeEvent({
539
- type: "deleted",
540
- issueId: id,
541
- previousIssue,
542
- });
543
- }
544
- }
545
-
546
- // Check for new or changed issues
547
- for (const [id, currentIssue] of currentMap) {
548
- const previousIssue = this.lastKnownIssues.get(id);
549
-
550
- if (!previousIssue) {
551
- // New issue
552
- this.emitChangeEvent({
553
- type: "created",
554
- issueId: id,
555
- issue: currentIssue,
556
- });
557
- } else if (currentIssue.updated_at !== previousIssue.updated_at) {
558
- // Changed issue - determine the type of change
559
- let changeType: IssueChangeEvent["type"] = "updated";
560
-
561
- if (currentIssue.status !== previousIssue.status) {
562
- if (currentIssue.status === "blocked") {
563
- changeType = "blocked";
564
- } else if (previousIssue.status === "blocked") {
565
- changeType = "unblocked";
566
- } else {
567
- changeType = "status_changed";
568
- }
569
- }
570
-
571
- this.emitChangeEvent({
572
- type: changeType,
573
- issueId: id,
574
- issue: currentIssue,
575
- previousIssue,
576
- });
577
- }
578
- }
579
-
580
- // Update cache
581
- this.lastKnownIssues = currentMap;
582
- }
583
-
584
- /**
585
- * Start polling if needed
586
- */
587
- private startPollingIfNeeded(): void {
588
- if (this.pollTimer) return; // Already polling
589
- if (this.changeCallbacks.size === 0) return; // No subscribers
590
- if (!this.ready) return; // Not initialized
591
-
592
- this.pollTimer = setInterval(
593
- () => this.pollForChanges(),
594
- this.config.pollInterval
595
- );
596
- }
597
-
598
- /**
599
- * Stop polling if not needed
600
- */
601
- private stopPollingIfNotNeeded(): void {
602
- if (this.changeCallbacks.size > 0) return; // Still have subscribers
603
-
604
- if (this.pollTimer) {
605
- clearInterval(this.pollTimer);
606
- this.pollTimer = null;
607
- }
608
- }
609
- }
610
-
611
- /**
612
- * Create and initialize a StandaloneClient
613
- *
614
- * @param config Client configuration
615
- * @returns Initialized StandaloneClient
616
- */
617
- export async function createStandaloneClient(
618
- config: StandaloneClientConfig
619
- ): Promise<StandaloneClient> {
620
- const client = new StandaloneClient(config);
621
- await client.init();
622
- return client;
623
- }