hungry-ghost-hive 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/LICENSE +111 -0
  2. package/README.md +352 -0
  3. package/dist/agents/base-agent.d.ts +63 -0
  4. package/dist/agents/base-agent.d.ts.map +1 -0
  5. package/dist/agents/base-agent.js +189 -0
  6. package/dist/agents/base-agent.js.map +1 -0
  7. package/dist/agents/index.d.ts +7 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/index.js +7 -0
  10. package/dist/agents/index.js.map +1 -0
  11. package/dist/agents/intermediate.d.ts +15 -0
  12. package/dist/agents/intermediate.d.ts.map +1 -0
  13. package/dist/agents/intermediate.js +142 -0
  14. package/dist/agents/intermediate.js.map +1 -0
  15. package/dist/agents/junior.d.ts +15 -0
  16. package/dist/agents/junior.d.ts.map +1 -0
  17. package/dist/agents/junior.js +147 -0
  18. package/dist/agents/junior.js.map +1 -0
  19. package/dist/agents/qa.d.ts +23 -0
  20. package/dist/agents/qa.d.ts.map +1 -0
  21. package/dist/agents/qa.js +238 -0
  22. package/dist/agents/qa.js.map +1 -0
  23. package/dist/agents/senior.d.ts +18 -0
  24. package/dist/agents/senior.d.ts.map +1 -0
  25. package/dist/agents/senior.js +267 -0
  26. package/dist/agents/senior.js.map +1 -0
  27. package/dist/agents/tech-lead.d.ts +17 -0
  28. package/dist/agents/tech-lead.d.ts.map +1 -0
  29. package/dist/agents/tech-lead.js +274 -0
  30. package/dist/agents/tech-lead.js.map +1 -0
  31. package/dist/cli/commands/add-repo.d.ts +3 -0
  32. package/dist/cli/commands/add-repo.d.ts.map +1 -0
  33. package/dist/cli/commands/add-repo.js +84 -0
  34. package/dist/cli/commands/add-repo.js.map +1 -0
  35. package/dist/cli/commands/agents.d.ts +3 -0
  36. package/dist/cli/commands/agents.d.ts.map +1 -0
  37. package/dist/cli/commands/agents.js +214 -0
  38. package/dist/cli/commands/agents.js.map +1 -0
  39. package/dist/cli/commands/assign.d.ts +3 -0
  40. package/dist/cli/commands/assign.d.ts.map +1 -0
  41. package/dist/cli/commands/assign.js +81 -0
  42. package/dist/cli/commands/assign.js.map +1 -0
  43. package/dist/cli/commands/config.d.ts +3 -0
  44. package/dist/cli/commands/config.d.ts.map +1 -0
  45. package/dist/cli/commands/config.js +118 -0
  46. package/dist/cli/commands/config.js.map +1 -0
  47. package/dist/cli/commands/escalations.d.ts +3 -0
  48. package/dist/cli/commands/escalations.d.ts.map +1 -0
  49. package/dist/cli/commands/escalations.js +157 -0
  50. package/dist/cli/commands/escalations.js.map +1 -0
  51. package/dist/cli/commands/index.d.ts +17 -0
  52. package/dist/cli/commands/index.d.ts.map +1 -0
  53. package/dist/cli/commands/index.js +17 -0
  54. package/dist/cli/commands/index.js.map +1 -0
  55. package/dist/cli/commands/init.d.ts +3 -0
  56. package/dist/cli/commands/init.d.ts.map +1 -0
  57. package/dist/cli/commands/init.js +59 -0
  58. package/dist/cli/commands/init.js.map +1 -0
  59. package/dist/cli/commands/manager.d.ts +3 -0
  60. package/dist/cli/commands/manager.d.ts.map +1 -0
  61. package/dist/cli/commands/manager.js +775 -0
  62. package/dist/cli/commands/manager.js.map +1 -0
  63. package/dist/cli/commands/manager.test.d.ts +2 -0
  64. package/dist/cli/commands/manager.test.d.ts.map +1 -0
  65. package/dist/cli/commands/manager.test.js +45 -0
  66. package/dist/cli/commands/manager.test.js.map +1 -0
  67. package/dist/cli/commands/msg.d.ts +3 -0
  68. package/dist/cli/commands/msg.d.ts.map +1 -0
  69. package/dist/cli/commands/msg.js +190 -0
  70. package/dist/cli/commands/msg.js.map +1 -0
  71. package/dist/cli/commands/my-stories.d.ts +3 -0
  72. package/dist/cli/commands/my-stories.d.ts.map +1 -0
  73. package/dist/cli/commands/my-stories.js +174 -0
  74. package/dist/cli/commands/my-stories.js.map +1 -0
  75. package/dist/cli/commands/nuke.d.ts +3 -0
  76. package/dist/cli/commands/nuke.d.ts.map +1 -0
  77. package/dist/cli/commands/nuke.js +189 -0
  78. package/dist/cli/commands/nuke.js.map +1 -0
  79. package/dist/cli/commands/pr.d.ts +3 -0
  80. package/dist/cli/commands/pr.d.ts.map +1 -0
  81. package/dist/cli/commands/pr.js +488 -0
  82. package/dist/cli/commands/pr.js.map +1 -0
  83. package/dist/cli/commands/req.d.ts +3 -0
  84. package/dist/cli/commands/req.d.ts.map +1 -0
  85. package/dist/cli/commands/req.js +212 -0
  86. package/dist/cli/commands/req.js.map +1 -0
  87. package/dist/cli/commands/resume.d.ts +3 -0
  88. package/dist/cli/commands/resume.d.ts.map +1 -0
  89. package/dist/cli/commands/resume.js +114 -0
  90. package/dist/cli/commands/resume.js.map +1 -0
  91. package/dist/cli/commands/status.d.ts +3 -0
  92. package/dist/cli/commands/status.d.ts.map +1 -0
  93. package/dist/cli/commands/status.js +259 -0
  94. package/dist/cli/commands/status.js.map +1 -0
  95. package/dist/cli/commands/stories.d.ts +3 -0
  96. package/dist/cli/commands/stories.d.ts.map +1 -0
  97. package/dist/cli/commands/stories.js +111 -0
  98. package/dist/cli/commands/stories.js.map +1 -0
  99. package/dist/cli/commands/teams.d.ts +3 -0
  100. package/dist/cli/commands/teams.d.ts.map +1 -0
  101. package/dist/cli/commands/teams.js +137 -0
  102. package/dist/cli/commands/teams.js.map +1 -0
  103. package/dist/cli/dashboard/index.d.ts +5 -0
  104. package/dist/cli/dashboard/index.d.ts.map +1 -0
  105. package/dist/cli/dashboard/index.js +128 -0
  106. package/dist/cli/dashboard/index.js.map +1 -0
  107. package/dist/cli/dashboard/panels/activity.d.ts +5 -0
  108. package/dist/cli/dashboard/panels/activity.d.ts.map +1 -0
  109. package/dist/cli/dashboard/panels/activity.js +64 -0
  110. package/dist/cli/dashboard/panels/activity.js.map +1 -0
  111. package/dist/cli/dashboard/panels/agents.d.ts +5 -0
  112. package/dist/cli/dashboard/panels/agents.d.ts.map +1 -0
  113. package/dist/cli/dashboard/panels/agents.js +196 -0
  114. package/dist/cli/dashboard/panels/agents.js.map +1 -0
  115. package/dist/cli/dashboard/panels/escalations.d.ts +5 -0
  116. package/dist/cli/dashboard/panels/escalations.d.ts.map +1 -0
  117. package/dist/cli/dashboard/panels/escalations.js +93 -0
  118. package/dist/cli/dashboard/panels/escalations.js.map +1 -0
  119. package/dist/cli/dashboard/panels/merge-queue.d.ts +5 -0
  120. package/dist/cli/dashboard/panels/merge-queue.d.ts.map +1 -0
  121. package/dist/cli/dashboard/panels/merge-queue.js +57 -0
  122. package/dist/cli/dashboard/panels/merge-queue.js.map +1 -0
  123. package/dist/cli/dashboard/panels/pipeline.d.ts +5 -0
  124. package/dist/cli/dashboard/panels/pipeline.d.ts.map +1 -0
  125. package/dist/cli/dashboard/panels/pipeline.js +54 -0
  126. package/dist/cli/dashboard/panels/pipeline.js.map +1 -0
  127. package/dist/cli/dashboard/panels/stories.d.ts +5 -0
  128. package/dist/cli/dashboard/panels/stories.d.ts.map +1 -0
  129. package/dist/cli/dashboard/panels/stories.js +79 -0
  130. package/dist/cli/dashboard/panels/stories.js.map +1 -0
  131. package/dist/cli-runtimes/claude.d.ts +8 -0
  132. package/dist/cli-runtimes/claude.d.ts.map +1 -0
  133. package/dist/cli-runtimes/claude.js +27 -0
  134. package/dist/cli-runtimes/claude.js.map +1 -0
  135. package/dist/cli-runtimes/codex.d.ts +8 -0
  136. package/dist/cli-runtimes/codex.d.ts.map +1 -0
  137. package/dist/cli-runtimes/codex.js +27 -0
  138. package/dist/cli-runtimes/codex.js.map +1 -0
  139. package/dist/cli-runtimes/gemini.d.ts +8 -0
  140. package/dist/cli-runtimes/gemini.d.ts.map +1 -0
  141. package/dist/cli-runtimes/gemini.js +29 -0
  142. package/dist/cli-runtimes/gemini.js.map +1 -0
  143. package/dist/cli-runtimes/index.d.ts +25 -0
  144. package/dist/cli-runtimes/index.d.ts.map +1 -0
  145. package/dist/cli-runtimes/index.js +48 -0
  146. package/dist/cli-runtimes/index.js.map +1 -0
  147. package/dist/cli-runtimes/index.test.d.ts +2 -0
  148. package/dist/cli-runtimes/index.test.d.ts.map +1 -0
  149. package/dist/cli-runtimes/index.test.js +216 -0
  150. package/dist/cli-runtimes/index.test.js.map +1 -0
  151. package/dist/cli-runtimes/types.d.ts +27 -0
  152. package/dist/cli-runtimes/types.d.ts.map +1 -0
  153. package/dist/cli-runtimes/types.js +2 -0
  154. package/dist/cli-runtimes/types.js.map +1 -0
  155. package/dist/config/index.d.ts +3 -0
  156. package/dist/config/index.d.ts.map +1 -0
  157. package/dist/config/index.js +3 -0
  158. package/dist/config/index.js.map +1 -0
  159. package/dist/config/loader.d.ts +11 -0
  160. package/dist/config/loader.d.ts.map +1 -0
  161. package/dist/config/loader.js +72 -0
  162. package/dist/config/loader.js.map +1 -0
  163. package/dist/config/schema.d.ts +660 -0
  164. package/dist/config/schema.d.ts.map +1 -0
  165. package/dist/config/schema.js +217 -0
  166. package/dist/config/schema.js.map +1 -0
  167. package/dist/config/schema.test.d.ts +2 -0
  168. package/dist/config/schema.test.d.ts.map +1 -0
  169. package/dist/config/schema.test.js +123 -0
  170. package/dist/config/schema.test.js.map +1 -0
  171. package/dist/context-files/generator.d.ts +32 -0
  172. package/dist/context-files/generator.d.ts.map +1 -0
  173. package/dist/context-files/generator.js +120 -0
  174. package/dist/context-files/generator.js.map +1 -0
  175. package/dist/context-files/index.d.ts +38 -0
  176. package/dist/context-files/index.d.ts.map +1 -0
  177. package/dist/context-files/index.js +76 -0
  178. package/dist/context-files/index.js.map +1 -0
  179. package/dist/context-files/index.test.d.ts +2 -0
  180. package/dist/context-files/index.test.d.ts.map +1 -0
  181. package/dist/context-files/index.test.js +265 -0
  182. package/dist/context-files/index.test.js.map +1 -0
  183. package/dist/context-files/templates.d.ts +19 -0
  184. package/dist/context-files/templates.d.ts.map +1 -0
  185. package/dist/context-files/templates.js +266 -0
  186. package/dist/context-files/templates.js.map +1 -0
  187. package/dist/db/client.d.ts +95 -0
  188. package/dist/db/client.d.ts.map +1 -0
  189. package/dist/db/client.js +343 -0
  190. package/dist/db/client.js.map +1 -0
  191. package/dist/db/lock.d.ts +25 -0
  192. package/dist/db/lock.d.ts.map +1 -0
  193. package/dist/db/lock.js +56 -0
  194. package/dist/db/lock.js.map +1 -0
  195. package/dist/db/lock.test.d.ts +2 -0
  196. package/dist/db/lock.test.d.ts.map +1 -0
  197. package/dist/db/lock.test.js +73 -0
  198. package/dist/db/lock.test.js.map +1 -0
  199. package/dist/db/queries/agents.d.ts +31 -0
  200. package/dist/db/queries/agents.d.ts.map +1 -0
  201. package/dist/db/queries/agents.js +76 -0
  202. package/dist/db/queries/agents.js.map +1 -0
  203. package/dist/db/queries/escalations.d.ts +29 -0
  204. package/dist/db/queries/escalations.d.ts.map +1 -0
  205. package/dist/db/queries/escalations.js +105 -0
  206. package/dist/db/queries/escalations.js.map +1 -0
  207. package/dist/db/queries/heartbeat.d.ts +20 -0
  208. package/dist/db/queries/heartbeat.d.ts.map +1 -0
  209. package/dist/db/queries/heartbeat.js +61 -0
  210. package/dist/db/queries/heartbeat.js.map +1 -0
  211. package/dist/db/queries/index.d.ts +8 -0
  212. package/dist/db/queries/index.d.ts.map +1 -0
  213. package/dist/db/queries/index.js +8 -0
  214. package/dist/db/queries/index.js.map +1 -0
  215. package/dist/db/queries/logs.d.ts +21 -0
  216. package/dist/db/queries/logs.d.ts.map +1 -0
  217. package/dist/db/queries/logs.js +72 -0
  218. package/dist/db/queries/logs.js.map +1 -0
  219. package/dist/db/queries/messages.d.ts +17 -0
  220. package/dist/db/queries/messages.d.ts.map +1 -0
  221. package/dist/db/queries/messages.js +22 -0
  222. package/dist/db/queries/messages.js.map +1 -0
  223. package/dist/db/queries/pull-requests.d.ts +33 -0
  224. package/dist/db/queries/pull-requests.d.ts.map +1 -0
  225. package/dist/db/queries/pull-requests.js +130 -0
  226. package/dist/db/queries/pull-requests.js.map +1 -0
  227. package/dist/db/queries/requirements.d.ts +22 -0
  228. package/dist/db/queries/requirements.d.ts.map +1 -0
  229. package/dist/db/queries/requirements.js +53 -0
  230. package/dist/db/queries/requirements.js.map +1 -0
  231. package/dist/db/queries/stories.d.ts +42 -0
  232. package/dist/db/queries/stories.d.ts.map +1 -0
  233. package/dist/db/queries/stories.js +163 -0
  234. package/dist/db/queries/stories.js.map +1 -0
  235. package/dist/db/queries/teams.d.ts +14 -0
  236. package/dist/db/queries/teams.d.ts.map +1 -0
  237. package/dist/db/queries/teams.js +24 -0
  238. package/dist/db/queries/teams.js.map +1 -0
  239. package/dist/git/branches.d.ts +52 -0
  240. package/dist/git/branches.d.ts.map +1 -0
  241. package/dist/git/branches.js +133 -0
  242. package/dist/git/branches.js.map +1 -0
  243. package/dist/git/github.d.ts +75 -0
  244. package/dist/git/github.d.ts.map +1 -0
  245. package/dist/git/github.js +162 -0
  246. package/dist/git/github.js.map +1 -0
  247. package/dist/git/index.d.ts +4 -0
  248. package/dist/git/index.d.ts.map +1 -0
  249. package/dist/git/index.js +4 -0
  250. package/dist/git/index.js.map +1 -0
  251. package/dist/git/submodules.d.ts +47 -0
  252. package/dist/git/submodules.d.ts.map +1 -0
  253. package/dist/git/submodules.js +115 -0
  254. package/dist/git/submodules.js.map +1 -0
  255. package/dist/index.d.ts +3 -0
  256. package/dist/index.d.ts.map +1 -0
  257. package/dist/index.js +62 -0
  258. package/dist/index.js.map +1 -0
  259. package/dist/llm/anthropic.d.ts +18 -0
  260. package/dist/llm/anthropic.d.ts.map +1 -0
  261. package/dist/llm/anthropic.js +111 -0
  262. package/dist/llm/anthropic.js.map +1 -0
  263. package/dist/llm/index.d.ts +6 -0
  264. package/dist/llm/index.d.ts.map +1 -0
  265. package/dist/llm/index.js +24 -0
  266. package/dist/llm/index.js.map +1 -0
  267. package/dist/llm/openai.d.ts +18 -0
  268. package/dist/llm/openai.d.ts.map +1 -0
  269. package/dist/llm/openai.js +103 -0
  270. package/dist/llm/openai.js.map +1 -0
  271. package/dist/llm/provider.d.ts +38 -0
  272. package/dist/llm/provider.d.ts.map +1 -0
  273. package/dist/llm/provider.js +17 -0
  274. package/dist/llm/provider.js.map +1 -0
  275. package/dist/orchestrator/index.d.ts +4 -0
  276. package/dist/orchestrator/index.d.ts.map +1 -0
  277. package/dist/orchestrator/index.js +4 -0
  278. package/dist/orchestrator/index.js.map +1 -0
  279. package/dist/orchestrator/scaler.d.ts +42 -0
  280. package/dist/orchestrator/scaler.d.ts.map +1 -0
  281. package/dist/orchestrator/scaler.js +154 -0
  282. package/dist/orchestrator/scaler.js.map +1 -0
  283. package/dist/orchestrator/scheduler.d.ts +90 -0
  284. package/dist/orchestrator/scheduler.d.ts.map +1 -0
  285. package/dist/orchestrator/scheduler.js +1003 -0
  286. package/dist/orchestrator/scheduler.js.map +1 -0
  287. package/dist/orchestrator/scheduler.test.d.ts +2 -0
  288. package/dist/orchestrator/scheduler.test.d.ts.map +1 -0
  289. package/dist/orchestrator/scheduler.test.js +242 -0
  290. package/dist/orchestrator/scheduler.test.js.map +1 -0
  291. package/dist/orchestrator/workflow.d.ts +18 -0
  292. package/dist/orchestrator/workflow.d.ts.map +1 -0
  293. package/dist/orchestrator/workflow.js +106 -0
  294. package/dist/orchestrator/workflow.js.map +1 -0
  295. package/dist/state-detectors/claude.d.ts +33 -0
  296. package/dist/state-detectors/claude.d.ts.map +1 -0
  297. package/dist/state-detectors/claude.js +237 -0
  298. package/dist/state-detectors/claude.js.map +1 -0
  299. package/dist/state-detectors/claude.test.d.ts +2 -0
  300. package/dist/state-detectors/claude.test.d.ts.map +1 -0
  301. package/dist/state-detectors/claude.test.js +127 -0
  302. package/dist/state-detectors/claude.test.js.map +1 -0
  303. package/dist/state-detectors/codex.d.ts +34 -0
  304. package/dist/state-detectors/codex.d.ts.map +1 -0
  305. package/dist/state-detectors/codex.js +233 -0
  306. package/dist/state-detectors/codex.js.map +1 -0
  307. package/dist/state-detectors/codex.test.d.ts +2 -0
  308. package/dist/state-detectors/codex.test.d.ts.map +1 -0
  309. package/dist/state-detectors/codex.test.js +85 -0
  310. package/dist/state-detectors/codex.test.js.map +1 -0
  311. package/dist/state-detectors/factory.d.ts +22 -0
  312. package/dist/state-detectors/factory.d.ts.map +1 -0
  313. package/dist/state-detectors/factory.js +37 -0
  314. package/dist/state-detectors/factory.js.map +1 -0
  315. package/dist/state-detectors/factory.test.d.ts +2 -0
  316. package/dist/state-detectors/factory.test.d.ts.map +1 -0
  317. package/dist/state-detectors/factory.test.js +44 -0
  318. package/dist/state-detectors/factory.test.js.map +1 -0
  319. package/dist/state-detectors/gemini.d.ts +34 -0
  320. package/dist/state-detectors/gemini.d.ts.map +1 -0
  321. package/dist/state-detectors/gemini.js +236 -0
  322. package/dist/state-detectors/gemini.js.map +1 -0
  323. package/dist/state-detectors/gemini.test.d.ts +2 -0
  324. package/dist/state-detectors/gemini.test.d.ts.map +1 -0
  325. package/dist/state-detectors/gemini.test.js +93 -0
  326. package/dist/state-detectors/gemini.test.js.map +1 -0
  327. package/dist/state-detectors/index.d.ts +20 -0
  328. package/dist/state-detectors/index.d.ts.map +1 -0
  329. package/dist/state-detectors/index.js +21 -0
  330. package/dist/state-detectors/index.js.map +1 -0
  331. package/dist/state-detectors/types.d.ts +67 -0
  332. package/dist/state-detectors/types.d.ts.map +1 -0
  333. package/dist/state-detectors/types.js +28 -0
  334. package/dist/state-detectors/types.js.map +1 -0
  335. package/dist/tmux/index.d.ts +2 -0
  336. package/dist/tmux/index.d.ts.map +1 -0
  337. package/dist/tmux/index.js +2 -0
  338. package/dist/tmux/index.js.map +1 -0
  339. package/dist/tmux/manager.d.ts +45 -0
  340. package/dist/tmux/manager.d.ts.map +1 -0
  341. package/dist/tmux/manager.js +252 -0
  342. package/dist/tmux/manager.js.map +1 -0
  343. package/dist/utils/claude-code-state.d.ts +46 -0
  344. package/dist/utils/claude-code-state.d.ts.map +1 -0
  345. package/dist/utils/claude-code-state.js +252 -0
  346. package/dist/utils/claude-code-state.js.map +1 -0
  347. package/dist/utils/cli-builder.d.ts +19 -0
  348. package/dist/utils/cli-builder.d.ts.map +1 -0
  349. package/dist/utils/cli-builder.js +58 -0
  350. package/dist/utils/cli-builder.js.map +1 -0
  351. package/dist/utils/cli-commands.d.ts +27 -0
  352. package/dist/utils/cli-commands.d.ts.map +1 -0
  353. package/dist/utils/cli-commands.js +69 -0
  354. package/dist/utils/cli-commands.js.map +1 -0
  355. package/dist/utils/index.d.ts +3 -0
  356. package/dist/utils/index.d.ts.map +1 -0
  357. package/dist/utils/index.js +3 -0
  358. package/dist/utils/index.js.map +1 -0
  359. package/dist/utils/logger.d.ts +13 -0
  360. package/dist/utils/logger.d.ts.map +1 -0
  361. package/dist/utils/logger.js +77 -0
  362. package/dist/utils/logger.js.map +1 -0
  363. package/dist/utils/paths.d.ts +17 -0
  364. package/dist/utils/paths.d.ts.map +1 -0
  365. package/dist/utils/paths.js +33 -0
  366. package/dist/utils/paths.js.map +1 -0
  367. package/dist/utils/timeout.d.ts +25 -0
  368. package/dist/utils/timeout.d.ts.map +1 -0
  369. package/dist/utils/timeout.js +57 -0
  370. package/dist/utils/timeout.js.map +1 -0
  371. package/package.json +78 -0
  372. package/src/agents/base-agent.ts +255 -0
  373. package/src/agents/index.ts +6 -0
  374. package/src/agents/intermediate.ts +161 -0
  375. package/src/agents/junior.ts +166 -0
  376. package/src/agents/qa.ts +272 -0
  377. package/src/agents/senior.ts +307 -0
  378. package/src/agents/tech-lead.ts +324 -0
  379. package/src/cli/commands/add-repo.ts +89 -0
  380. package/src/cli/commands/agents.ts +247 -0
  381. package/src/cli/commands/assign.ts +86 -0
  382. package/src/cli/commands/config.ts +121 -0
  383. package/src/cli/commands/escalations.ts +179 -0
  384. package/src/cli/commands/index.ts +16 -0
  385. package/src/cli/commands/init.ts +66 -0
  386. package/src/cli/commands/manager.test.ts +52 -0
  387. package/src/cli/commands/manager.ts +916 -0
  388. package/src/cli/commands/msg.ts +232 -0
  389. package/src/cli/commands/my-stories.ts +198 -0
  390. package/src/cli/commands/nuke.ts +223 -0
  391. package/src/cli/commands/pr.ts +559 -0
  392. package/src/cli/commands/req.ts +231 -0
  393. package/src/cli/commands/resume.ts +129 -0
  394. package/src/cli/commands/status.ts +284 -0
  395. package/src/cli/commands/stories.ts +131 -0
  396. package/src/cli/commands/teams.ts +158 -0
  397. package/src/cli/dashboard/index.ts +141 -0
  398. package/src/cli/dashboard/panels/activity.ts +77 -0
  399. package/src/cli/dashboard/panels/agents.ts +244 -0
  400. package/src/cli/dashboard/panels/escalations.ts +109 -0
  401. package/src/cli/dashboard/panels/merge-queue.ts +65 -0
  402. package/src/cli/dashboard/panels/pipeline.ts +65 -0
  403. package/src/cli/dashboard/panels/stories.ts +87 -0
  404. package/src/cli-runtimes/claude.ts +31 -0
  405. package/src/cli-runtimes/codex.ts +31 -0
  406. package/src/cli-runtimes/gemini.ts +33 -0
  407. package/src/cli-runtimes/index.test.ts +261 -0
  408. package/src/cli-runtimes/index.ts +52 -0
  409. package/src/cli-runtimes/types.ts +30 -0
  410. package/src/config/index.ts +2 -0
  411. package/src/config/loader.ts +89 -0
  412. package/src/config/schema.test.ts +135 -0
  413. package/src/config/schema.ts +238 -0
  414. package/src/context-files/generator.ts +132 -0
  415. package/src/context-files/index.test.ts +323 -0
  416. package/src/context-files/index.ts +102 -0
  417. package/src/context-files/templates.ts +279 -0
  418. package/src/db/client.ts +475 -0
  419. package/src/db/lock.test.ts +93 -0
  420. package/src/db/lock.ts +74 -0
  421. package/src/db/migrations/001-initial.sql +121 -0
  422. package/src/db/migrations/005-add-agent-heartbeat.sql +4 -0
  423. package/src/db/queries/agents.ts +113 -0
  424. package/src/db/queries/escalations.ts +140 -0
  425. package/src/db/queries/heartbeat.ts +92 -0
  426. package/src/db/queries/index.ts +7 -0
  427. package/src/db/queries/logs.ts +136 -0
  428. package/src/db/queries/messages.ts +38 -0
  429. package/src/db/queries/pull-requests.ts +170 -0
  430. package/src/db/queries/requirements.ts +81 -0
  431. package/src/db/queries/stories.ts +223 -0
  432. package/src/db/queries/teams.ts +39 -0
  433. package/src/git/branches.ts +186 -0
  434. package/src/git/github.ts +247 -0
  435. package/src/git/index.ts +3 -0
  436. package/src/git/submodules.ts +141 -0
  437. package/src/index.ts +93 -0
  438. package/src/llm/anthropic.ts +134 -0
  439. package/src/llm/index.ts +26 -0
  440. package/src/llm/openai.ts +125 -0
  441. package/src/llm/provider.ts +60 -0
  442. package/src/orchestrator/index.ts +3 -0
  443. package/src/orchestrator/scaler.ts +201 -0
  444. package/src/orchestrator/scheduler.test.ts +288 -0
  445. package/src/orchestrator/scheduler.ts +1130 -0
  446. package/src/orchestrator/workflow.ts +137 -0
  447. package/src/state-detectors/claude.test.ts +149 -0
  448. package/src/state-detectors/claude.ts +256 -0
  449. package/src/state-detectors/codex.test.ts +100 -0
  450. package/src/state-detectors/codex.ts +252 -0
  451. package/src/state-detectors/factory.test.ts +51 -0
  452. package/src/state-detectors/factory.ts +40 -0
  453. package/src/state-detectors/gemini.test.ts +110 -0
  454. package/src/state-detectors/gemini.ts +255 -0
  455. package/src/state-detectors/index.ts +25 -0
  456. package/src/state-detectors/types.ts +80 -0
  457. package/src/tmux/index.ts +1 -0
  458. package/src/tmux/manager.ts +310 -0
  459. package/src/types/sql.js.d.ts +34 -0
  460. package/src/utils/claude-code-state.ts +281 -0
  461. package/src/utils/cli-builder.ts +78 -0
  462. package/src/utils/cli-commands.ts +84 -0
  463. package/src/utils/index.ts +2 -0
  464. package/src/utils/logger.ts +93 -0
  465. package/src/utils/paths.ts +49 -0
  466. package/src/utils/timeout.ts +84 -0
  467. package/tsconfig.json +25 -0
@@ -0,0 +1,125 @@
1
+ import OpenAI from 'openai';
2
+ import type { LLMProvider, Message, CompletionOptions, CompletionResult } from './provider.js';
3
+ import { withTimeout } from '../utils/timeout.js';
4
+
5
+ export class OpenAIProvider implements LLMProvider {
6
+ name = 'openai';
7
+ private client: OpenAI;
8
+ private model: string;
9
+ private defaultMaxTokens: number;
10
+ private defaultTemperature: number;
11
+
12
+ constructor(options: {
13
+ apiKey?: string;
14
+ model?: string;
15
+ maxTokens?: number;
16
+ temperature?: number;
17
+ } = {}) {
18
+ this.client = new OpenAI({
19
+ apiKey: options.apiKey || process.env.OPENAI_API_KEY,
20
+ });
21
+ this.model = options.model || 'gpt-4o-mini';
22
+ this.defaultMaxTokens = options.maxTokens || 4000;
23
+ this.defaultTemperature = options.temperature ?? 0.2;
24
+ }
25
+
26
+ async complete(messages: Message[], options?: CompletionOptions): Promise<CompletionResult> {
27
+ const apiCall = async () => {
28
+ const response = await this.client.chat.completions.create({
29
+ model: this.model,
30
+ max_tokens: options?.maxTokens ?? this.defaultMaxTokens,
31
+ temperature: options?.temperature ?? this.defaultTemperature,
32
+ messages: messages.map(m => ({
33
+ role: m.role,
34
+ content: m.content,
35
+ })),
36
+ stop: options?.stopSequences,
37
+ });
38
+
39
+ const choice = response.choices[0];
40
+
41
+ return {
42
+ content: choice.message.content || '',
43
+ stopReason: this.mapStopReason(choice.finish_reason),
44
+ usage: {
45
+ inputTokens: response.usage?.prompt_tokens || 0,
46
+ outputTokens: response.usage?.completion_tokens || 0,
47
+ },
48
+ };
49
+ };
50
+
51
+ // Apply timeout if specified
52
+ if (options?.timeoutMs) {
53
+ return withTimeout(
54
+ apiCall(),
55
+ options.timeoutMs,
56
+ `OpenAI API call timed out after ${options.timeoutMs}ms`
57
+ );
58
+ }
59
+
60
+ return apiCall();
61
+ }
62
+
63
+ async *streamComplete(messages: Message[], options?: CompletionOptions): AsyncIterable<string> {
64
+ // Capture instance properties to avoid 'this' binding issues in generator
65
+ const client = this.client;
66
+ const model = this.model;
67
+ const defaultMaxTokens = this.defaultMaxTokens;
68
+ const defaultTemperature = this.defaultTemperature;
69
+
70
+ const streamGenerator = async function* () {
71
+ const stream = await client.chat.completions.create({
72
+ model: model,
73
+ max_tokens: options?.maxTokens ?? defaultMaxTokens,
74
+ temperature: options?.temperature ?? defaultTemperature,
75
+ messages: messages.map(m => ({
76
+ role: m.role,
77
+ content: m.content,
78
+ })),
79
+ stop: options?.stopSequences,
80
+ stream: true,
81
+ });
82
+
83
+ for await (const chunk of stream) {
84
+ const delta = chunk.choices[0]?.delta?.content;
85
+ if (delta) {
86
+ yield delta;
87
+ }
88
+ }
89
+ };
90
+
91
+ // Apply timeout if specified
92
+ if (options?.timeoutMs) {
93
+ const timeoutPromise = new Promise<never>((_, reject) => {
94
+ setTimeout(() => {
95
+ reject(new Error(`OpenAI streaming API call timed out after ${options.timeoutMs}ms`));
96
+ }, options.timeoutMs);
97
+ });
98
+
99
+ const generator = streamGenerator();
100
+
101
+ while (true) {
102
+ const result = await Promise.race([
103
+ generator.next(),
104
+ timeoutPromise
105
+ ]);
106
+
107
+ if (result.done) break;
108
+ yield result.value;
109
+ }
110
+ } else {
111
+ yield* streamGenerator();
112
+ }
113
+ }
114
+
115
+ private mapStopReason(reason: string | null): CompletionResult['stopReason'] {
116
+ switch (reason) {
117
+ case 'stop':
118
+ return 'end_turn';
119
+ case 'length':
120
+ return 'max_tokens';
121
+ default:
122
+ return 'error';
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,60 @@
1
+ export interface Message {
2
+ role: 'user' | 'assistant' | 'system';
3
+ content: string;
4
+ }
5
+
6
+ export interface CompletionOptions {
7
+ maxTokens?: number;
8
+ temperature?: number;
9
+ stopSequences?: string[];
10
+ /**
11
+ * Timeout in milliseconds for this completion request.
12
+ * If the LLM call doesn't complete within this time, it will be cancelled
13
+ * and a TimeoutError will be thrown.
14
+ */
15
+ timeoutMs?: number;
16
+ }
17
+
18
+ export interface CompletionResult {
19
+ content: string;
20
+ stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'error';
21
+ usage: {
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ };
25
+ }
26
+
27
+ export interface LLMProvider {
28
+ name: string;
29
+ complete(messages: Message[], options?: CompletionOptions): Promise<CompletionResult>;
30
+ streamComplete?(messages: Message[], options?: CompletionOptions): AsyncIterable<string>;
31
+ }
32
+
33
+ export type ProviderType = 'anthropic' | 'openai';
34
+
35
+ export interface ProviderConfig {
36
+ provider: ProviderType;
37
+ model: string;
38
+ maxTokens?: number;
39
+ temperature?: number;
40
+ }
41
+
42
+ export function getProviderApiKey(provider: ProviderType): string | undefined {
43
+ switch (provider) {
44
+ case 'anthropic':
45
+ return process.env.ANTHROPIC_API_KEY;
46
+ case 'openai':
47
+ return process.env.OPENAI_API_KEY;
48
+ default:
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ export function validateProviderConfig(config: ProviderConfig): void {
54
+ const apiKey = getProviderApiKey(config.provider);
55
+ if (!apiKey) {
56
+ throw new Error(
57
+ `Missing API key for ${config.provider}. Set ${config.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'} environment variable.`
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,3 @@
1
+ export * from './workflow.js';
2
+ export { Scheduler, type SchedulerConfig } from './scheduler.js';
3
+ export { Scaler, type ScalerConfig, type ScalingRecommendation } from './scaler.js';
@@ -0,0 +1,201 @@
1
+ import type { Database } from 'sql.js';
2
+ import { getStoryPointsByTeam } from '../db/queries/stories.js';
3
+ import { getAgentsByTeam, getTechLead, terminateAgent, type AgentRow } from '../db/queries/agents.js';
4
+ import { getAllTeams, type TeamRow } from '../db/queries/teams.js';
5
+ import { createLog } from '../db/queries/logs.js';
6
+ import { killTmuxSession } from '../tmux/manager.js';
7
+ import type { ScalingConfig } from '../config/schema.js';
8
+
9
+ export interface ScalerConfig {
10
+ scaling: ScalingConfig;
11
+ rootDir: string;
12
+ }
13
+
14
+ export interface ScalingRecommendation {
15
+ teamId: string;
16
+ teamName: string;
17
+ currentSeniors: number;
18
+ recommendedSeniors: number;
19
+ action: 'scale_up' | 'scale_down' | 'none';
20
+ reason: string;
21
+ }
22
+
23
+ export class Scaler {
24
+ private db: Database;
25
+ private config: ScalerConfig;
26
+
27
+ constructor(db: Database, config: ScalerConfig) {
28
+ this.db = db;
29
+ this.config = config;
30
+ }
31
+
32
+ /**
33
+ * Analyze current workload and recommend scaling actions
34
+ */
35
+ analyzeScaling(): ScalingRecommendation[] {
36
+ const teams = getAllTeams(this.db);
37
+ const recommendations: ScalingRecommendation[] = [];
38
+
39
+ for (const team of teams) {
40
+ const recommendation = this.analyzeTeam(team);
41
+ recommendations.push(recommendation);
42
+ }
43
+
44
+ return recommendations;
45
+ }
46
+
47
+ private analyzeTeam(team: TeamRow): ScalingRecommendation {
48
+ const storyPoints = getStoryPointsByTeam(this.db, team.id);
49
+ const agents = getAgentsByTeam(this.db, team.id);
50
+ const activeSeniors = agents.filter(
51
+ a => a.type === 'senior' && a.status !== 'terminated'
52
+ );
53
+
54
+ const seniorCapacity = this.config.scaling.senior_capacity;
55
+ const recommendedSeniors = Math.max(1, Math.ceil(storyPoints / seniorCapacity));
56
+ const currentSeniors = activeSeniors.length;
57
+
58
+ let action: ScalingRecommendation['action'] = 'none';
59
+ let reason = '';
60
+
61
+ if (recommendedSeniors > currentSeniors) {
62
+ action = 'scale_up';
63
+ reason = `${storyPoints} story points exceeds capacity of ${currentSeniors} senior(s) (${currentSeniors * seniorCapacity} points)`;
64
+ } else if (recommendedSeniors < currentSeniors && currentSeniors > 1) {
65
+ action = 'scale_down';
66
+ reason = `Only ${storyPoints} story points, can be handled by ${recommendedSeniors} senior(s)`;
67
+ } else {
68
+ reason = `Current capacity is appropriate for ${storyPoints} story points`;
69
+ }
70
+
71
+ return {
72
+ teamId: team.id,
73
+ teamName: team.name,
74
+ currentSeniors,
75
+ recommendedSeniors,
76
+ action,
77
+ reason,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Scale down idle agents when workload decreases
83
+ */
84
+ async scaleDown(): Promise<number> {
85
+ const teams = getAllTeams(this.db);
86
+ let terminated = 0;
87
+
88
+ for (const team of teams) {
89
+ const storyPoints = getStoryPointsByTeam(this.db, team.id);
90
+ const agents = getAgentsByTeam(this.db, team.id);
91
+
92
+ // Only scale down if no active work
93
+ if (storyPoints > 0) continue;
94
+
95
+ // Find idle agents (except the first Senior)
96
+ const idleAgents = agents.filter(a =>
97
+ a.status === 'idle' &&
98
+ a.type !== 'tech_lead' &&
99
+ !a.current_story_id
100
+ );
101
+
102
+ // Keep at least one Senior
103
+ const seniorsToKeep = agents.filter(a => a.type === 'senior' && a.status !== 'terminated')[0];
104
+
105
+ for (const agent of idleAgents) {
106
+ if (agent.type === 'senior' && agent.id === seniorsToKeep?.id) {
107
+ continue; // Keep the primary Senior
108
+ }
109
+
110
+ await this.terminateAgent(agent);
111
+ terminated++;
112
+ }
113
+ }
114
+
115
+ return terminated;
116
+ }
117
+
118
+ private async terminateAgent(agent: AgentRow): Promise<void> {
119
+ // Kill tmux session if exists
120
+ if (agent.tmux_session) {
121
+ await killTmuxSession(agent.tmux_session);
122
+ }
123
+
124
+ // Remove worktree if exists
125
+ if (agent.worktree_path) {
126
+ try {
127
+ const { execSync } = await import('child_process');
128
+ const fullWorktreePath = `${this.config.rootDir}/${agent.worktree_path}`;
129
+ execSync(`git worktree remove "${fullWorktreePath}" --force`, {
130
+ cwd: this.config.rootDir,
131
+ stdio: 'pipe',
132
+ });
133
+ } catch (err) {
134
+ // Log error but don't throw - worktree might already be removed
135
+ console.error(`Warning: Failed to remove worktree: ${err instanceof Error ? err.message : 'Unknown error'}`);
136
+ }
137
+ }
138
+
139
+ // Mark as terminated
140
+ terminateAgent(this.db, agent.id);
141
+
142
+ // Log the event
143
+ createLog(this.db, {
144
+ agentId: agent.id,
145
+ eventType: 'AGENT_TERMINATED',
146
+ message: 'Scaled down due to reduced workload',
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Get scaling statistics
152
+ */
153
+ getStatistics(): {
154
+ totalAgents: number;
155
+ activeAgents: number;
156
+ agentsByType: Record<string, number>;
157
+ teamCapacity: Record<string, { agents: number; storyPoints: number }>;
158
+ } {
159
+ const teams = getAllTeams(this.db);
160
+ const agentsByType: Record<string, number> = {};
161
+ const teamCapacity: Record<string, { agents: number; storyPoints: number }> = {};
162
+ let totalAgents = 0;
163
+ let activeAgents = 0;
164
+
165
+ for (const team of teams) {
166
+ const agents = getAgentsByTeam(this.db, team.id);
167
+ const storyPoints = getStoryPointsByTeam(this.db, team.id);
168
+
169
+ teamCapacity[team.name] = {
170
+ agents: agents.filter(a => a.status !== 'terminated').length,
171
+ storyPoints,
172
+ };
173
+
174
+ for (const agent of agents) {
175
+ if (agent.status === 'terminated') continue;
176
+ totalAgents++;
177
+ agentsByType[agent.type] = (agentsByType[agent.type] || 0) + 1;
178
+ if (agent.status === 'working') {
179
+ activeAgents++;
180
+ }
181
+ }
182
+ }
183
+
184
+ // Add Tech Lead
185
+ const techLead = getTechLead(this.db);
186
+ if (techLead && techLead.status !== 'terminated') {
187
+ totalAgents++;
188
+ agentsByType['tech_lead'] = 1;
189
+ if (techLead.status === 'working') {
190
+ activeAgents++;
191
+ }
192
+ }
193
+
194
+ return {
195
+ totalAgents,
196
+ activeAgents,
197
+ agentsByType,
198
+ teamCapacity,
199
+ };
200
+ }
201
+ }
@@ -0,0 +1,288 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import type { Database } from 'sql.js';
3
+ import initSqlJs from 'sql.js';
4
+ import { Scheduler } from './scheduler.js';
5
+ import { createStory, addStoryDependency, updateStory } from '../db/queries/stories.js';
6
+ import { createTeam } from '../db/queries/teams.js';
7
+ import type { StoryRow } from '../db/queries/stories.js';
8
+
9
+ let db: Database;
10
+ let scheduler: Scheduler;
11
+
12
+ const mockConfig = {
13
+ scaling: {
14
+ junior_max_complexity: 3,
15
+ intermediate_max_complexity: 5,
16
+ senior_capacity: 50,
17
+ },
18
+ models: {
19
+ tech_lead: { provider: 'anthropic', model: 'claude-opus-4-20250514', max_tokens: 16000, temperature: 0.7, cli_tool: 'claude' },
20
+ senior: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', max_tokens: 8000, temperature: 0.5, cli_tool: 'claude' },
21
+ intermediate: { provider: 'anthropic', model: 'claude-haiku-3-5-20241022', max_tokens: 4000, temperature: 0.3, cli_tool: 'claude' },
22
+ junior: { provider: 'openai', model: 'gpt-4o-mini', max_tokens: 4000, temperature: 0.2, cli_tool: 'claude' },
23
+ qa: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', max_tokens: 8000, temperature: 0.2, cli_tool: 'claude' },
24
+ },
25
+ rootDir: '/tmp',
26
+ };
27
+
28
+ // Migration SQL to initialize database for tests
29
+ const INITIAL_MIGRATION = `
30
+ CREATE TABLE IF NOT EXISTS migrations (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ name TEXT NOT NULL UNIQUE,
33
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
34
+ );
35
+
36
+ CREATE TABLE IF NOT EXISTS teams (
37
+ id TEXT PRIMARY KEY,
38
+ repo_url TEXT NOT NULL,
39
+ repo_path TEXT NOT NULL,
40
+ name TEXT NOT NULL,
41
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS agents (
45
+ id TEXT PRIMARY KEY,
46
+ type TEXT NOT NULL CHECK (type IN ('tech_lead', 'senior', 'intermediate', 'junior', 'qa')),
47
+ team_id TEXT REFERENCES teams(id),
48
+ tmux_session TEXT,
49
+ model TEXT,
50
+ status TEXT DEFAULT 'idle' CHECK (status IN ('idle', 'working', 'blocked', 'terminated')),
51
+ current_story_id TEXT,
52
+ memory_state TEXT,
53
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
54
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS stories (
58
+ id TEXT PRIMARY KEY,
59
+ requirement_id TEXT,
60
+ team_id TEXT REFERENCES teams(id),
61
+ title TEXT NOT NULL,
62
+ description TEXT NOT NULL,
63
+ acceptance_criteria TEXT,
64
+ complexity_score INTEGER CHECK (complexity_score BETWEEN 1 AND 13),
65
+ story_points INTEGER,
66
+ status TEXT DEFAULT 'draft' CHECK (status IN (
67
+ 'draft',
68
+ 'estimated',
69
+ 'planned',
70
+ 'in_progress',
71
+ 'review',
72
+ 'qa',
73
+ 'qa_failed',
74
+ 'pr_submitted',
75
+ 'merged'
76
+ )),
77
+ assigned_agent_id TEXT REFERENCES agents(id),
78
+ branch_name TEXT,
79
+ pr_url TEXT,
80
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
81
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
82
+ );
83
+
84
+ CREATE TABLE IF NOT EXISTS story_dependencies (
85
+ story_id TEXT REFERENCES stories(id),
86
+ depends_on_story_id TEXT REFERENCES stories(id),
87
+ PRIMARY KEY (story_id, depends_on_story_id)
88
+ );
89
+ `;
90
+
91
+ beforeEach(async () => {
92
+ const SQL = await initSqlJs();
93
+ db = new SQL.Database();
94
+ db.run('PRAGMA foreign_keys = ON');
95
+ db.run(INITIAL_MIGRATION);
96
+ db.run("INSERT INTO migrations (name) VALUES ('001-initial.sql')");
97
+
98
+ scheduler = new Scheduler(db, mockConfig as any);
99
+ });
100
+
101
+ describe('Scheduler Topological Sort', () => {
102
+ it('should handle stories with no dependencies', () => {
103
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
104
+ const story1 = createStory(db, { teamId: team.id, title: 'Story 1', description: 'Test' });
105
+ const story2 = createStory(db, { teamId: team.id, title: 'Story 2', description: 'Test' });
106
+
107
+ // Mock the private method by accessing it through reflection
108
+ const sortMethod = (scheduler as any).topologicalSort;
109
+ const sorted = sortMethod.call(scheduler, [story1, story2]);
110
+
111
+ expect(sorted).not.toBeNull();
112
+ expect(sorted).toHaveLength(2);
113
+ });
114
+
115
+ it('should respect linear dependencies (A -> B -> C)', () => {
116
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
117
+ const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
118
+ const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
119
+ const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
120
+
121
+ // B depends on A, C depends on B
122
+ addStoryDependency(db, storyB.id, storyA.id);
123
+ addStoryDependency(db, storyC.id, storyB.id);
124
+
125
+ const sortMethod = (scheduler as any).topologicalSort;
126
+ const sorted = sortMethod.call(scheduler, [storyC, storyA, storyB]);
127
+
128
+ expect(sorted).not.toBeNull();
129
+ expect(sorted).toHaveLength(3);
130
+ // A should come first, then B, then C
131
+ const ids = sorted!.map((s: StoryRow) => s.id);
132
+ expect(ids.indexOf(storyA.id)).toBeLessThan(ids.indexOf(storyB.id));
133
+ expect(ids.indexOf(storyB.id)).toBeLessThan(ids.indexOf(storyC.id));
134
+ });
135
+
136
+ it('should respect diamond dependencies (A -> B, A -> C, B -> D, C -> D)', () => {
137
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
138
+ const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
139
+ const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
140
+ const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
141
+ const storyD = createStory(db, { teamId: team.id, title: 'Story D', description: 'Test' });
142
+
143
+ // B and C depend on A, D depends on both B and C
144
+ addStoryDependency(db, storyB.id, storyA.id);
145
+ addStoryDependency(db, storyC.id, storyA.id);
146
+ addStoryDependency(db, storyD.id, storyB.id);
147
+ addStoryDependency(db, storyD.id, storyC.id);
148
+
149
+ const sortMethod = (scheduler as any).topologicalSort;
150
+ const sorted = sortMethod.call(scheduler, [storyD, storyB, storyA, storyC]);
151
+
152
+ expect(sorted).not.toBeNull();
153
+ expect(sorted).toHaveLength(4);
154
+ const ids = sorted!.map((s: StoryRow) => s.id);
155
+
156
+ // A should come first
157
+ expect(ids.indexOf(storyA.id)).toBe(0);
158
+ // D should come last
159
+ expect(ids.indexOf(storyD.id)).toBe(3);
160
+ // B and C should come between A and D
161
+ expect(ids.indexOf(storyB.id)).toBeGreaterThan(ids.indexOf(storyA.id));
162
+ expect(ids.indexOf(storyC.id)).toBeGreaterThan(ids.indexOf(storyA.id));
163
+ expect(ids.indexOf(storyB.id)).toBeLessThan(ids.indexOf(storyD.id));
164
+ expect(ids.indexOf(storyC.id)).toBeLessThan(ids.indexOf(storyD.id));
165
+ });
166
+
167
+ it('should detect circular dependencies', () => {
168
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
169
+ const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
170
+ const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
171
+
172
+ // Create circular dependency: A -> B -> A
173
+ addStoryDependency(db, storyB.id, storyA.id);
174
+ addStoryDependency(db, storyA.id, storyB.id);
175
+
176
+ const sortMethod = (scheduler as any).topologicalSort;
177
+ const sorted = sortMethod.call(scheduler, [storyA, storyB]);
178
+
179
+ expect(sorted).toBeNull();
180
+ });
181
+ });
182
+
183
+ describe('Scheduler Dependency Satisfaction', () => {
184
+ it('should consider merged stories as satisfying dependencies', () => {
185
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
186
+ const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
187
+ const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
188
+
189
+ addStoryDependency(db, mainStory.id, depStory.id);
190
+
191
+ // Initially, dependencies are not satisfied
192
+ let isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
193
+ expect(isSatisfied).toBe(false);
194
+
195
+ // Mark dependency as merged
196
+ updateStory(db, depStory.id, { status: 'merged' });
197
+ isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
198
+ expect(isSatisfied).toBe(true);
199
+ });
200
+
201
+ it('should consider in-progress stories as satisfying dependencies', () => {
202
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
203
+ const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
204
+ const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
205
+
206
+ addStoryDependency(db, mainStory.id, depStory.id);
207
+
208
+ // Mark dependency as in_progress
209
+ updateStory(db, depStory.id, { status: 'in_progress' });
210
+ const isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
211
+ expect(isSatisfied).toBe(true);
212
+ });
213
+
214
+ it('should not consider planned stories as satisfying dependencies', () => {
215
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
216
+ const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
217
+ const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
218
+
219
+ addStoryDependency(db, mainStory.id, depStory.id);
220
+
221
+ // Update main story status to planned (default)
222
+ updateStory(db, mainStory.id, { status: 'planned' });
223
+
224
+ const isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
225
+ expect(isSatisfied).toBe(false);
226
+ });
227
+
228
+ it('should handle multiple dependencies', () => {
229
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
230
+ const dep1 = createStory(db, { teamId: team.id, title: 'Dep 1', description: 'Test' });
231
+ const dep2 = createStory(db, { teamId: team.id, title: 'Dep 2', description: 'Test' });
232
+ const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
233
+
234
+ addStoryDependency(db, mainStory.id, dep1.id);
235
+ addStoryDependency(db, mainStory.id, dep2.id);
236
+
237
+ // Mark only first dependency as merged
238
+ updateStory(db, dep1.id, { status: 'merged' });
239
+ let isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
240
+ expect(isSatisfied).toBe(false);
241
+
242
+ // Mark second dependency as merged too
243
+ updateStory(db, dep2.id, { status: 'merged' });
244
+ isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
245
+ expect(isSatisfied).toBe(true);
246
+ });
247
+ });
248
+
249
+ describe('Scheduler Build Dependency Graph', () => {
250
+ it('should correctly build a dependency graph', () => {
251
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
252
+ const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
253
+ const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
254
+ const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
255
+
256
+ addStoryDependency(db, storyB.id, storyA.id);
257
+ addStoryDependency(db, storyC.id, storyA.id);
258
+
259
+ const graphMethod = (scheduler as any).buildDependencyGraph;
260
+ const graph = graphMethod.call(scheduler, [storyA, storyB, storyC]);
261
+
262
+ expect(graph.has(storyA.id)).toBe(true);
263
+ expect(graph.has(storyB.id)).toBe(true);
264
+ expect(graph.has(storyC.id)).toBe(true);
265
+
266
+ expect(graph.get(storyA.id)).toEqual(new Set());
267
+ expect(graph.get(storyB.id)).toEqual(new Set([storyA.id]));
268
+ expect(graph.get(storyC.id)).toEqual(new Set([storyA.id]));
269
+ });
270
+
271
+ it('should include only stories in the input list', () => {
272
+ const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
273
+ const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
274
+ const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
275
+ const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
276
+
277
+ // B depends on A (A is not in the filter list)
278
+ addStoryDependency(db, storyB.id, storyA.id);
279
+
280
+ const graphMethod = (scheduler as any).buildDependencyGraph;
281
+ // Only include B and C in the graph
282
+ const graph = graphMethod.call(scheduler, [storyB, storyC]);
283
+
284
+ expect(graph.has(storyB.id)).toBe(true);
285
+ expect(graph.has(storyC.id)).toBe(true);
286
+ expect(graph.has(storyA.id)).toBe(false);
287
+ });
288
+ });