mcoda 0.1.2 → 0.1.4

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 (461) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +9 -300
  3. package/dist/bin/McodaEntrypoint.d.ts +5 -0
  4. package/dist/bin/McodaEntrypoint.d.ts.map +1 -0
  5. package/dist/bin/McodaEntrypoint.js +175 -0
  6. package/dist/commands/agents/AgentsCommands.d.ts +4 -0
  7. package/dist/commands/agents/AgentsCommands.d.ts.map +1 -0
  8. package/dist/commands/agents/AgentsCommands.js +376 -0
  9. package/dist/commands/agents/GatewayAgentCommand.d.ts +4 -0
  10. package/dist/commands/agents/GatewayAgentCommand.d.ts.map +1 -0
  11. package/dist/commands/agents/GatewayAgentCommand.js +583 -0
  12. package/dist/commands/agents/TestAgentCommand.d.ts +4 -0
  13. package/dist/commands/agents/TestAgentCommand.d.ts.map +1 -0
  14. package/dist/commands/agents/TestAgentCommand.js +57 -0
  15. package/dist/commands/backlog/BacklogCommands.d.ts +17 -0
  16. package/dist/commands/backlog/BacklogCommands.d.ts.map +1 -0
  17. package/dist/commands/backlog/BacklogCommands.js +260 -0
  18. package/dist/commands/backlog/OrderTasksCommand.d.ts +16 -0
  19. package/dist/commands/backlog/OrderTasksCommand.d.ts.map +1 -0
  20. package/dist/commands/backlog/OrderTasksCommand.js +211 -0
  21. package/dist/commands/backlog/TaskShowCommands.d.ts +16 -0
  22. package/dist/commands/backlog/TaskShowCommands.d.ts.map +1 -0
  23. package/dist/commands/backlog/TaskShowCommands.js +275 -0
  24. package/dist/commands/docs/DocsCommands.d.ts +37 -0
  25. package/dist/commands/docs/DocsCommands.d.ts.map +1 -0
  26. package/dist/commands/docs/DocsCommands.js +381 -0
  27. package/dist/commands/estimate/EstimateCommands.d.ts +24 -0
  28. package/dist/commands/estimate/EstimateCommands.d.ts.map +1 -0
  29. package/dist/commands/estimate/EstimateCommands.js +259 -0
  30. package/dist/commands/jobs/JobsCommands.d.ts +24 -0
  31. package/dist/commands/jobs/JobsCommands.d.ts.map +1 -0
  32. package/dist/commands/jobs/JobsCommands.js +535 -0
  33. package/dist/commands/openapi/OpenapiCommands.d.ts +14 -0
  34. package/dist/commands/openapi/OpenapiCommands.d.ts.map +1 -0
  35. package/dist/commands/openapi/OpenapiCommands.js +157 -0
  36. package/dist/commands/planning/CreateTasksCommand.d.ts +17 -0
  37. package/dist/commands/planning/CreateTasksCommand.d.ts.map +1 -0
  38. package/dist/commands/planning/CreateTasksCommand.js +134 -0
  39. package/dist/commands/planning/MigrateTasksCommand.d.ts +15 -0
  40. package/dist/commands/planning/MigrateTasksCommand.d.ts.map +1 -0
  41. package/dist/commands/planning/MigrateTasksCommand.js +95 -0
  42. package/dist/commands/planning/PlanningCommands.d.ts +3 -0
  43. package/dist/commands/planning/PlanningCommands.d.ts.map +1 -0
  44. package/dist/commands/planning/PlanningCommands.js +2 -0
  45. package/dist/commands/planning/QaTasksCommand.d.ts +30 -0
  46. package/dist/commands/planning/QaTasksCommand.d.ts.map +1 -0
  47. package/dist/commands/planning/QaTasksCommand.js +293 -0
  48. package/dist/commands/planning/RefineTasksCommand.d.ts +30 -0
  49. package/dist/commands/planning/RefineTasksCommand.d.ts.map +1 -0
  50. package/dist/commands/planning/RefineTasksCommand.js +365 -0
  51. package/dist/commands/review/CodeReviewCommand.d.ts +21 -0
  52. package/dist/commands/review/CodeReviewCommand.d.ts.map +1 -0
  53. package/dist/commands/review/CodeReviewCommand.js +236 -0
  54. package/dist/commands/routing/RoutingCommands.d.ts +7 -0
  55. package/dist/commands/routing/RoutingCommands.d.ts.map +1 -0
  56. package/dist/commands/routing/RoutingCommands.js +484 -0
  57. package/dist/commands/telemetry/TelemetryCommands.d.ts +26 -0
  58. package/dist/commands/telemetry/TelemetryCommands.d.ts.map +1 -0
  59. package/dist/commands/telemetry/TelemetryCommands.js +313 -0
  60. package/dist/commands/update/UpdateCommands.d.ts +4 -0
  61. package/dist/commands/update/UpdateCommands.d.ts.map +1 -0
  62. package/dist/commands/update/UpdateCommands.js +280 -0
  63. package/dist/commands/work/WorkOnTasksCommand.d.ts +21 -0
  64. package/dist/commands/work/WorkOnTasksCommand.d.ts.map +1 -0
  65. package/dist/commands/work/WorkOnTasksCommand.js +238 -0
  66. package/dist/commands/workspace/SetWorkspaceCommand.d.ts +9 -0
  67. package/dist/commands/workspace/SetWorkspaceCommand.d.ts.map +1 -0
  68. package/dist/commands/workspace/SetWorkspaceCommand.js +128 -0
  69. package/dist/index.d.ts +19 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/package.json +31 -16
  72. package/.editorconfig +0 -9
  73. package/.eslintrc.cjs +0 -12
  74. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
  75. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  76. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  77. package/.github/workflows/ci.yml +0 -37
  78. package/.github/workflows/nightly.yml +0 -38
  79. package/.github/workflows/release-dry-run.yml +0 -40
  80. package/.github/workflows/release-please.yml +0 -22
  81. package/.github/workflows/release.yml +0 -139
  82. package/.prettierrc +0 -5
  83. package/.release-please-manifest.json +0 -8
  84. package/CLA.md +0 -42
  85. package/CONTRIBUTING.md +0 -38
  86. package/docs/oss_publishing_plan.md +0 -41
  87. package/docs/pdr/.gitkeep +0 -0
  88. package/docs/quality_gates.md +0 -32
  89. package/docs/rfp/.gitkeep +0 -0
  90. package/docs/sds/sds.md +0 -11963
  91. package/docs/usage.md +0 -72
  92. package/openapi/gen-openapi.ts +0 -1
  93. package/openapi/generated/clients/.gitkeep +0 -0
  94. package/openapi/generated/types/.gitkeep +0 -0
  95. package/openapi/generated/types/index.ts +0 -118
  96. package/openapi/mcoda.yaml +0 -2063
  97. package/pack-mcoda.sh +0 -88
  98. package/packages/agents/CHANGELOG.md +0 -7
  99. package/packages/agents/LICENSE +0 -21
  100. package/packages/agents/README.md +0 -9
  101. package/packages/agents/package.json +0 -41
  102. package/packages/agents/src/AgentService/.gitkeep +0 -0
  103. package/packages/agents/src/AgentService/AgentService.d.ts +0 -21
  104. package/packages/agents/src/AgentService/AgentService.d.ts.map +0 -1
  105. package/packages/agents/src/AgentService/AgentService.js +0 -141
  106. package/packages/agents/src/AgentService/AgentService.ts +0 -308
  107. package/packages/agents/src/__tests__/AgentService.test.ts +0 -284
  108. package/packages/agents/src/adapters/AdapterTypes.d.ts +0 -29
  109. package/packages/agents/src/adapters/AdapterTypes.d.ts.map +0 -1
  110. package/packages/agents/src/adapters/AdapterTypes.js +0 -1
  111. package/packages/agents/src/adapters/AdapterTypes.ts +0 -32
  112. package/packages/agents/src/adapters/codex/.gitkeep +0 -0
  113. package/packages/agents/src/adapters/codex/CodexAdapter.d.ts +0 -11
  114. package/packages/agents/src/adapters/codex/CodexAdapter.d.ts.map +0 -1
  115. package/packages/agents/src/adapters/codex/CodexAdapter.js +0 -43
  116. package/packages/agents/src/adapters/codex/CodexAdapter.ts +0 -63
  117. package/packages/agents/src/adapters/codex/CodexCliRunner.ts +0 -154
  118. package/packages/agents/src/adapters/gemini/.gitkeep +0 -0
  119. package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts +0 -11
  120. package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts.map +0 -1
  121. package/packages/agents/src/adapters/gemini/GeminiAdapter.js +0 -42
  122. package/packages/agents/src/adapters/gemini/GeminiAdapter.ts +0 -58
  123. package/packages/agents/src/adapters/gemini/GeminiCliRunner.ts +0 -75
  124. package/packages/agents/src/adapters/local/.gitkeep +0 -0
  125. package/packages/agents/src/adapters/local/LocalAdapter.d.ts +0 -11
  126. package/packages/agents/src/adapters/local/LocalAdapter.d.ts.map +0 -1
  127. package/packages/agents/src/adapters/local/LocalAdapter.js +0 -38
  128. package/packages/agents/src/adapters/local/LocalAdapter.ts +0 -43
  129. package/packages/agents/src/adapters/ollama/OllamaCliAdapter.ts +0 -58
  130. package/packages/agents/src/adapters/ollama/OllamaCliRunner.ts +0 -70
  131. package/packages/agents/src/adapters/ollama/OllamaRemoteAdapter.ts +0 -205
  132. package/packages/agents/src/adapters/openai/.gitkeep +0 -0
  133. package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts +0 -11
  134. package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts.map +0 -1
  135. package/packages/agents/src/adapters/openai/OpenAiAdapter.js +0 -51
  136. package/packages/agents/src/adapters/openai/OpenAiAdapter.ts +0 -56
  137. package/packages/agents/src/adapters/openai/OpenAiCliAdapter.ts +0 -62
  138. package/packages/agents/src/adapters/qa/.gitkeep +0 -0
  139. package/packages/agents/src/adapters/qa/QaAdapter.d.ts +0 -11
  140. package/packages/agents/src/adapters/qa/QaAdapter.d.ts.map +0 -1
  141. package/packages/agents/src/adapters/qa/QaAdapter.js +0 -37
  142. package/packages/agents/src/adapters/qa/QaAdapter.ts +0 -42
  143. package/packages/agents/src/adapters/zhipu/ZhipuApiAdapter.ts +0 -273
  144. package/packages/agents/src/index.d.ts +0 -8
  145. package/packages/agents/src/index.d.ts.map +0 -1
  146. package/packages/agents/src/index.js +0 -7
  147. package/packages/agents/src/index.ts +0 -11
  148. package/packages/agents/tsconfig.json +0 -14
  149. package/packages/cli/CHANGELOG.md +0 -7
  150. package/packages/cli/LICENSE +0 -21
  151. package/packages/cli/README.md +0 -23
  152. package/packages/cli/package.json +0 -61
  153. package/packages/cli/src/__tests__/AgentsCommands.test.ts +0 -137
  154. package/packages/cli/src/__tests__/BacklogCommands.test.ts +0 -40
  155. package/packages/cli/src/__tests__/CodeReviewCommand.test.ts +0 -594
  156. package/packages/cli/src/__tests__/CreateTasksCommand.test.ts +0 -40
  157. package/packages/cli/src/__tests__/DocsCommands.test.ts +0 -41
  158. package/packages/cli/src/__tests__/EstimateCommands.test.ts +0 -54
  159. package/packages/cli/src/__tests__/JobsCommands.behavior.test.ts +0 -311
  160. package/packages/cli/src/__tests__/JobsCommands.test.ts +0 -49
  161. package/packages/cli/src/__tests__/MigrateTasksCommand.test.ts +0 -36
  162. package/packages/cli/src/__tests__/OpenapiCommands.test.ts +0 -34
  163. package/packages/cli/src/__tests__/OrderTasksCommand.test.ts +0 -150
  164. package/packages/cli/src/__tests__/PlanningCommands.test.ts +0 -9
  165. package/packages/cli/src/__tests__/QaTasksCommand.test.ts +0 -58
  166. package/packages/cli/src/__tests__/RefineTasksCommand.test.ts +0 -63
  167. package/packages/cli/src/__tests__/RoutingCommands.test.ts +0 -302
  168. package/packages/cli/src/__tests__/SetWorkspaceCommand.test.ts +0 -18
  169. package/packages/cli/src/__tests__/TaskShowCommands.test.ts +0 -130
  170. package/packages/cli/src/__tests__/TelemetryCommands.test.ts +0 -35
  171. package/packages/cli/src/__tests__/TestAgentCommand.test.ts +0 -41
  172. package/packages/cli/src/__tests__/UpdateCommands.test.ts +0 -292
  173. package/packages/cli/src/__tests__/WorkOnTasksCommand.test.ts +0 -42
  174. package/packages/cli/src/bin/.gitkeep +0 -0
  175. package/packages/cli/src/bin/McodaEntrypoint.ts +0 -180
  176. package/packages/cli/src/commands/agents/.gitkeep +0 -0
  177. package/packages/cli/src/commands/agents/AgentsCommands.ts +0 -374
  178. package/packages/cli/src/commands/agents/GatewayAgentCommand.ts +0 -621
  179. package/packages/cli/src/commands/agents/TestAgentCommand.ts +0 -63
  180. package/packages/cli/src/commands/backlog/.gitkeep +0 -0
  181. package/packages/cli/src/commands/backlog/BacklogCommands.ts +0 -286
  182. package/packages/cli/src/commands/backlog/OrderTasksCommand.ts +0 -237
  183. package/packages/cli/src/commands/backlog/TaskShowCommands.ts +0 -289
  184. package/packages/cli/src/commands/docs/.gitkeep +0 -0
  185. package/packages/cli/src/commands/docs/DocsCommands.ts +0 -413
  186. package/packages/cli/src/commands/estimate/EstimateCommands.ts +0 -290
  187. package/packages/cli/src/commands/jobs/.gitkeep +0 -0
  188. package/packages/cli/src/commands/jobs/JobsCommands.ts +0 -595
  189. package/packages/cli/src/commands/openapi/OpenapiCommands.ts +0 -167
  190. package/packages/cli/src/commands/planning/.gitkeep +0 -0
  191. package/packages/cli/src/commands/planning/CreateTasksCommand.ts +0 -149
  192. package/packages/cli/src/commands/planning/MigrateTasksCommand.ts +0 -105
  193. package/packages/cli/src/commands/planning/PlanningCommands.ts +0 -1
  194. package/packages/cli/src/commands/planning/QaTasksCommand.ts +0 -320
  195. package/packages/cli/src/commands/planning/RefineTasksCommand.ts +0 -408
  196. package/packages/cli/src/commands/review/CodeReviewCommand.ts +0 -262
  197. package/packages/cli/src/commands/routing/.gitkeep +0 -0
  198. package/packages/cli/src/commands/routing/RoutingCommands.ts +0 -554
  199. package/packages/cli/src/commands/telemetry/.gitkeep +0 -0
  200. package/packages/cli/src/commands/telemetry/TelemetryCommands.ts +0 -348
  201. package/packages/cli/src/commands/update/.gitkeep +0 -0
  202. package/packages/cli/src/commands/update/UpdateCommands.ts +0 -301
  203. package/packages/cli/src/commands/work/WorkOnTasksCommand.ts +0 -264
  204. package/packages/cli/src/commands/workspace/SetWorkspaceCommand.ts +0 -132
  205. package/packages/cli/test/packaging_guardrails.test.js +0 -75
  206. package/packages/cli/tsconfig.json +0 -20
  207. package/packages/core/CHANGELOG.md +0 -7
  208. package/packages/core/LICENSE +0 -21
  209. package/packages/core/README.md +0 -9
  210. package/packages/core/package.json +0 -45
  211. package/packages/core/src/__tests__/SmokeClasses.test.ts +0 -32
  212. package/packages/core/src/api/AgentsApi.ts +0 -219
  213. package/packages/core/src/api/QaTasksApi.ts +0 -38
  214. package/packages/core/src/api/TasksApi.ts +0 -35
  215. package/packages/core/src/api/__tests__/AgentsApi.test.ts +0 -203
  216. package/packages/core/src/api/__tests__/QaTasksApi.test.ts +0 -51
  217. package/packages/core/src/api/__tests__/TasksApi.test.ts +0 -56
  218. package/packages/core/src/config/.gitkeep +0 -0
  219. package/packages/core/src/config/ConfigService.ts +0 -1
  220. package/packages/core/src/domain/dependencies/.gitkeep +0 -0
  221. package/packages/core/src/domain/dependencies/Dependency.ts +0 -1
  222. package/packages/core/src/domain/epics/.gitkeep +0 -0
  223. package/packages/core/src/domain/epics/Epic.ts +0 -1
  224. package/packages/core/src/domain/projects/.gitkeep +0 -0
  225. package/packages/core/src/domain/projects/Project.ts +0 -1
  226. package/packages/core/src/domain/tasks/.gitkeep +0 -0
  227. package/packages/core/src/domain/tasks/Task.ts +0 -1
  228. package/packages/core/src/domain/userStories/.gitkeep +0 -0
  229. package/packages/core/src/domain/userStories/UserStory.ts +0 -1
  230. package/packages/core/src/index.ts +0 -27
  231. package/packages/core/src/prompts/.gitkeep +0 -0
  232. package/packages/core/src/prompts/PdrPrompts.ts +0 -23
  233. package/packages/core/src/prompts/PromptLoader.ts +0 -1
  234. package/packages/core/src/prompts/SdsPrompts.ts +0 -47
  235. package/packages/core/src/services/agents/.gitkeep +0 -0
  236. package/packages/core/src/services/agents/AgentManagementService.ts +0 -1
  237. package/packages/core/src/services/agents/GatewayAgentService.ts +0 -956
  238. package/packages/core/src/services/agents/RoutingService.ts +0 -461
  239. package/packages/core/src/services/agents/__tests__/GatewayAgentService.test.ts +0 -72
  240. package/packages/core/src/services/agents/__tests__/RoutingService.test.ts +0 -267
  241. package/packages/core/src/services/agents/generated/RoutingApiClient.ts +0 -89
  242. package/packages/core/src/services/backlog/.gitkeep +0 -0
  243. package/packages/core/src/services/backlog/BacklogService.ts +0 -580
  244. package/packages/core/src/services/backlog/TaskOrderingService.ts +0 -868
  245. package/packages/core/src/services/backlog/__tests__/BacklogService.test.ts +0 -219
  246. package/packages/core/src/services/backlog/__tests__/TaskOrderingService.test.ts +0 -268
  247. package/packages/core/src/services/docs/.gitkeep +0 -0
  248. package/packages/core/src/services/docs/DocsService.ts +0 -1913
  249. package/packages/core/src/services/docs/__tests__/DocsService.test.ts +0 -350
  250. package/packages/core/src/services/estimate/EstimateService.ts +0 -111
  251. package/packages/core/src/services/estimate/VelocityService.ts +0 -272
  252. package/packages/core/src/services/estimate/__tests__/VelocityAndEstimate.test.ts +0 -209
  253. package/packages/core/src/services/estimate/types.ts +0 -41
  254. package/packages/core/src/services/execution/.gitkeep +0 -0
  255. package/packages/core/src/services/execution/ExecutionService.ts +0 -1
  256. package/packages/core/src/services/execution/QaFollowupService.ts +0 -289
  257. package/packages/core/src/services/execution/QaProfileService.ts +0 -160
  258. package/packages/core/src/services/execution/QaTasksService.ts +0 -1303
  259. package/packages/core/src/services/execution/TaskSelectionService.ts +0 -362
  260. package/packages/core/src/services/execution/TaskStateService.ts +0 -64
  261. package/packages/core/src/services/execution/WorkOnTasksService.ts +0 -2023
  262. package/packages/core/src/services/execution/__tests__/QaFollowupService.test.ts +0 -58
  263. package/packages/core/src/services/execution/__tests__/QaProfileService.test.ts +0 -49
  264. package/packages/core/src/services/execution/__tests__/QaTasksService.test.ts +0 -157
  265. package/packages/core/src/services/execution/__tests__/TaskSelectionService.test.ts +0 -179
  266. package/packages/core/src/services/execution/__tests__/TaskStateService.test.ts +0 -51
  267. package/packages/core/src/services/execution/__tests__/WorkOnTasksService.test.ts +0 -285
  268. package/packages/core/src/services/jobs/.gitkeep +0 -0
  269. package/packages/core/src/services/jobs/JobInsightsService.ts +0 -355
  270. package/packages/core/src/services/jobs/JobResumeService.ts +0 -119
  271. package/packages/core/src/services/jobs/JobService.ts +0 -648
  272. package/packages/core/src/services/jobs/JobsApiClient.ts +0 -113
  273. package/packages/core/src/services/jobs/__tests__/JobInsightsService.test.ts +0 -17
  274. package/packages/core/src/services/jobs/__tests__/JobResumeService.test.ts +0 -45
  275. package/packages/core/src/services/jobs/__tests__/JobService.test.ts +0 -44
  276. package/packages/core/src/services/openapi/OpenApiService.ts +0 -558
  277. package/packages/core/src/services/openapi/__tests__/OpenApiService.test.ts +0 -57
  278. package/packages/core/src/services/planning/.gitkeep +0 -0
  279. package/packages/core/src/services/planning/CreateTasksService.ts +0 -1280
  280. package/packages/core/src/services/planning/KeyHelpers.ts +0 -80
  281. package/packages/core/src/services/planning/PlanningService.ts +0 -1
  282. package/packages/core/src/services/planning/RefineTasksService.ts +0 -1552
  283. package/packages/core/src/services/planning/__tests__/CreateTasksService.test.ts +0 -288
  284. package/packages/core/src/services/planning/__tests__/KeyHelpers.test.ts +0 -16
  285. package/packages/core/src/services/planning/__tests__/RefineTasksService.test.ts +0 -172
  286. package/packages/core/src/services/review/CodeReviewService.ts +0 -1386
  287. package/packages/core/src/services/review/__tests__/CodeReviewService.test.ts +0 -89
  288. package/packages/core/src/services/system/SystemUpdateService.ts +0 -177
  289. package/packages/core/src/services/system/__tests__/SystemUpdateService.test.ts +0 -40
  290. package/packages/core/src/services/tasks/TaskApiResolver.ts +0 -37
  291. package/packages/core/src/services/tasks/TaskDetailService.ts +0 -494
  292. package/packages/core/src/services/tasks/__tests__/TaskApiResolver.test.ts +0 -41
  293. package/packages/core/src/services/tasks/__tests__/TaskDetailService.test.ts +0 -178
  294. package/packages/core/src/services/telemetry/.gitkeep +0 -0
  295. package/packages/core/src/services/telemetry/TelemetryService.ts +0 -515
  296. package/packages/core/src/services/telemetry/__tests__/TelemetryService.test.ts +0 -160
  297. package/packages/core/src/workspace/.gitkeep +0 -0
  298. package/packages/core/src/workspace/WorkspaceManager.ts +0 -234
  299. package/packages/core/tsconfig.json +0 -20
  300. package/packages/db/CHANGELOG.md +0 -7
  301. package/packages/db/LICENSE +0 -21
  302. package/packages/db/README.md +0 -9
  303. package/packages/db/package.json +0 -42
  304. package/packages/db/src/__tests__/GlobalRepository.test.ts +0 -109
  305. package/packages/db/src/__tests__/SchemaAlignment.test.ts +0 -80
  306. package/packages/db/src/__tests__/WorkspaceRepository.test.ts +0 -19
  307. package/packages/db/src/index.d.ts +0 -6
  308. package/packages/db/src/index.d.ts.map +0 -1
  309. package/packages/db/src/index.js +0 -5
  310. package/packages/db/src/index.ts +0 -6
  311. package/packages/db/src/migrations/global/.gitkeep +0 -0
  312. package/packages/db/src/migrations/global/GlobalMigrations.d.ts +0 -9
  313. package/packages/db/src/migrations/global/GlobalMigrations.d.ts.map +0 -1
  314. package/packages/db/src/migrations/global/GlobalMigrations.js +0 -68
  315. package/packages/db/src/migrations/global/GlobalMigrations.ts +0 -336
  316. package/packages/db/src/migrations/workspace/.gitkeep +0 -0
  317. package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts +0 -9
  318. package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts.map +0 -1
  319. package/packages/db/src/migrations/workspace/WorkspaceMigrations.js +0 -251
  320. package/packages/db/src/migrations/workspace/WorkspaceMigrations.ts +0 -248
  321. package/packages/db/src/repositories/global/.gitkeep +0 -0
  322. package/packages/db/src/repositories/global/GlobalRepository.d.ts +0 -30
  323. package/packages/db/src/repositories/global/GlobalRepository.d.ts.map +0 -1
  324. package/packages/db/src/repositories/global/GlobalRepository.js +0 -209
  325. package/packages/db/src/repositories/global/GlobalRepository.ts +0 -492
  326. package/packages/db/src/repositories/workspace/.gitkeep +0 -0
  327. package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts +0 -282
  328. package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts.map +0 -1
  329. package/packages/db/src/repositories/workspace/WorkspaceRepository.js +0 -773
  330. package/packages/db/src/repositories/workspace/WorkspaceRepository.ts +0 -1511
  331. package/packages/db/src/sqlite/connection.d.ts +0 -11
  332. package/packages/db/src/sqlite/connection.d.ts.map +0 -1
  333. package/packages/db/src/sqlite/connection.js +0 -31
  334. package/packages/db/src/sqlite/connection.ts +0 -35
  335. package/packages/db/src/sqlite/pragmas.d.ts +0 -5
  336. package/packages/db/src/sqlite/pragmas.d.ts.map +0 -1
  337. package/packages/db/src/sqlite/pragmas.js +0 -6
  338. package/packages/db/src/sqlite/pragmas.ts +0 -10
  339. package/packages/db/tsconfig.json +0 -13
  340. package/packages/generators/package.json +0 -21
  341. package/packages/generators/src/__tests__/Generators.test.ts +0 -19
  342. package/packages/generators/src/index.ts +0 -1
  343. package/packages/generators/src/openapi/generateTypes.ts +0 -1
  344. package/packages/generators/src/openapi/validateSchema.ts +0 -1
  345. package/packages/generators/src/scaffolding/docs/.gitkeep +0 -0
  346. package/packages/generators/src/scaffolding/docs/DocsScaffolder.ts +0 -1
  347. package/packages/generators/src/scaffolding/global/.gitkeep +0 -0
  348. package/packages/generators/src/scaffolding/global/GlobalScaffolder.ts +0 -1
  349. package/packages/generators/src/scaffolding/workspace/.gitkeep +0 -0
  350. package/packages/generators/src/scaffolding/workspace/WorkspaceScaffolder.ts +0 -1
  351. package/packages/generators/tsconfig.json +0 -10
  352. package/packages/integrations/CHANGELOG.md +0 -7
  353. package/packages/integrations/LICENSE +0 -21
  354. package/packages/integrations/README.md +0 -9
  355. package/packages/integrations/package.json +0 -47
  356. package/packages/integrations/src/docdex/.gitkeep +0 -0
  357. package/packages/integrations/src/docdex/DocdexClient.d.ts +0 -50
  358. package/packages/integrations/src/docdex/DocdexClient.d.ts.map +0 -1
  359. package/packages/integrations/src/docdex/DocdexClient.js +0 -216
  360. package/packages/integrations/src/docdex/DocdexClient.ts +0 -261
  361. package/packages/integrations/src/docdex/__tests__/DocdexClient.test.ts +0 -29
  362. package/packages/integrations/src/index.d.ts +0 -2
  363. package/packages/integrations/src/index.d.ts.map +0 -1
  364. package/packages/integrations/src/index.js +0 -4
  365. package/packages/integrations/src/index.ts +0 -5
  366. package/packages/integrations/src/issues/.gitkeep +0 -0
  367. package/packages/integrations/src/issues/IssuesClient.ts +0 -1
  368. package/packages/integrations/src/issues/__tests__/IssuesClient.test.ts +0 -10
  369. package/packages/integrations/src/qa/.gitkeep +0 -0
  370. package/packages/integrations/src/qa/ChromiumQaAdapter.ts +0 -89
  371. package/packages/integrations/src/qa/CliQaAdapter.ts +0 -95
  372. package/packages/integrations/src/qa/MaestroQaAdapter.ts +0 -91
  373. package/packages/integrations/src/qa/QaAdapter.ts +0 -7
  374. package/packages/integrations/src/qa/QaClient.ts +0 -1
  375. package/packages/integrations/src/qa/QaTypes.ts +0 -26
  376. package/packages/integrations/src/qa/__tests__/ChromiumQaAdapter.test.ts +0 -30
  377. package/packages/integrations/src/qa/__tests__/CliQaAdapter.test.ts +0 -33
  378. package/packages/integrations/src/qa/__tests__/MaestroQaAdapter.test.ts +0 -30
  379. package/packages/integrations/src/qa/index.ts +0 -5
  380. package/packages/integrations/src/system/SystemClient.ts +0 -50
  381. package/packages/integrations/src/system/__tests__/SystemClient.test.ts +0 -40
  382. package/packages/integrations/src/telemetry/TelemetryClient.ts +0 -139
  383. package/packages/integrations/src/telemetry/__tests__/TelemetryClient.test.ts +0 -41
  384. package/packages/integrations/src/vcs/.gitkeep +0 -0
  385. package/packages/integrations/src/vcs/VcsClient.ts +0 -211
  386. package/packages/integrations/src/vcs/__tests__/VcsClient.test.ts +0 -26
  387. package/packages/integrations/tsconfig.json +0 -14
  388. package/packages/shared/CHANGELOG.md +0 -7
  389. package/packages/shared/LICENSE +0 -21
  390. package/packages/shared/README.md +0 -9
  391. package/packages/shared/package.json +0 -40
  392. package/packages/shared/src/__tests__/CommandMetadata.test.ts +0 -15
  393. package/packages/shared/src/__tests__/ServiceShells.test.ts +0 -16
  394. package/packages/shared/src/crypto/.gitkeep +0 -0
  395. package/packages/shared/src/crypto/CryptoHelper.d.ts +0 -15
  396. package/packages/shared/src/crypto/CryptoHelper.d.ts.map +0 -1
  397. package/packages/shared/src/crypto/CryptoHelper.js +0 -54
  398. package/packages/shared/src/crypto/CryptoHelper.ts +0 -57
  399. package/packages/shared/src/errors/.gitkeep +0 -0
  400. package/packages/shared/src/errors/ErrorFactory.ts +0 -1
  401. package/packages/shared/src/index.d.ts +0 -6
  402. package/packages/shared/src/index.d.ts.map +0 -1
  403. package/packages/shared/src/index.js +0 -4
  404. package/packages/shared/src/index.ts +0 -35
  405. package/packages/shared/src/logging/.gitkeep +0 -0
  406. package/packages/shared/src/logging/Logger.ts +0 -1
  407. package/packages/shared/src/metadata/CommandMetadata.ts +0 -165
  408. package/packages/shared/src/openapi/.gitkeep +0 -0
  409. package/packages/shared/src/openapi/OpenApiTypes.d.ts +0 -216
  410. package/packages/shared/src/openapi/OpenApiTypes.d.ts.map +0 -1
  411. package/packages/shared/src/openapi/OpenApiTypes.js +0 -1
  412. package/packages/shared/src/openapi/OpenApiTypes.ts +0 -312
  413. package/packages/shared/src/paths/.gitkeep +0 -0
  414. package/packages/shared/src/paths/PathHelper.d.ts +0 -12
  415. package/packages/shared/src/paths/PathHelper.d.ts.map +0 -1
  416. package/packages/shared/src/paths/PathHelper.js +0 -24
  417. package/packages/shared/src/paths/PathHelper.ts +0 -29
  418. package/packages/shared/src/qa/QaProfile.ts +0 -14
  419. package/packages/shared/src/utils/.gitkeep +0 -0
  420. package/packages/shared/src/utils/UtilityService.ts +0 -1
  421. package/packages/shared/tsconfig.json +0 -10
  422. package/packages/testing/package.json +0 -26
  423. package/packages/testing/src/__tests__/TestingFakes.test.ts +0 -15
  424. package/packages/testing/src/cli/e2e/.gitkeep +0 -0
  425. package/packages/testing/src/cli/e2e/E2eSuite.ts +0 -1
  426. package/packages/testing/src/fakes/agents/.gitkeep +0 -0
  427. package/packages/testing/src/fakes/agents/FakeAgents.ts +0 -1
  428. package/packages/testing/src/fakes/docdex/.gitkeep +0 -0
  429. package/packages/testing/src/fakes/docdex/FakeDocdexClient.ts +0 -1
  430. package/packages/testing/src/fakes/qa/.gitkeep +0 -0
  431. package/packages/testing/src/fakes/qa/FakeQaClient.ts +0 -1
  432. package/packages/testing/src/fakes/vcs/.gitkeep +0 -0
  433. package/packages/testing/src/fakes/vcs/FakeVcsClient.ts +0 -1
  434. package/packages/testing/src/fixtures/db/.gitkeep +0 -0
  435. package/packages/testing/src/fixtures/db/DbFixtures.ts +0 -1
  436. package/packages/testing/src/fixtures/workspaces/.gitkeep +0 -0
  437. package/packages/testing/src/fixtures/workspaces/WorkspaceFixtures.ts +0 -1
  438. package/packages/testing/src/index.ts +0 -1
  439. package/packages/testing/tsconfig.json +0 -10
  440. package/pnpm-workspace.yaml +0 -2
  441. package/prompts/README.md +0 -5
  442. package/prompts/code-reviewer.md +0 -23
  443. package/prompts/code-writer.md +0 -35
  444. package/prompts/gateway-agent.md +0 -27
  445. package/prompts/qa-agent.md +0 -21
  446. package/release-please-config.json +0 -39
  447. package/scripts/build-all.ts +0 -1
  448. package/scripts/dev.ts +0 -1
  449. package/scripts/install-local-cli.sh +0 -28
  450. package/scripts/pack-npm-tarballs.js +0 -63
  451. package/scripts/release.ts +0 -1
  452. package/scripts/run-node-tests.js +0 -37
  453. package/tests/all.js +0 -127
  454. package/tests/api/openapi_spec.test.js +0 -21
  455. package/tests/artifacts.md +0 -31
  456. package/tests/component/cli_version.test.js +0 -38
  457. package/tests/integration/workspace_resolver.test.js +0 -44
  458. package/tests/unit/crypto_helper.test.js +0 -36
  459. package/tests/unit/path_helper.test.js +0 -20
  460. package/tsconfig.base.json +0 -32
  461. /package/{packages/cli/src/index.ts → dist/index.js} +0 -0
@@ -1,1552 +0,0 @@
1
- import path from "node:path";
2
- import { promises as fs } from "node:fs";
3
- import { AgentService } from "@mcoda/agents";
4
- import { DocdexClient } from "@mcoda/integrations";
5
- import { GlobalRepository, TaskDependencyInsert, TaskInsert, TaskRow, WorkspaceRepository } from "@mcoda/db";
6
- import {
7
- RefineOperation,
8
- RefineStrategy,
9
- RefineTasksPlan,
10
- RefineTasksRequest,
11
- RefineTasksResult,
12
- SplitTaskOp,
13
- UpdateTaskOp,
14
- } from "@mcoda/shared";
15
- import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
16
- import { JobService } from "../jobs/JobService.js";
17
- import { RoutingService } from "../agents/RoutingService.js";
18
- import { createTaskKeyGenerator } from "./KeyHelpers.js";
19
-
20
- interface RefineTasksOptions extends RefineTasksRequest {
21
- workspace: WorkspaceResolution;
22
- storyKey?: string;
23
- agentName?: string;
24
- agentStream?: boolean;
25
- fromDb?: boolean;
26
- planInPath?: string;
27
- planOutPath?: string;
28
- jobId?: string;
29
- apply?: boolean;
30
- excludeAlreadyRefined?: boolean;
31
- allowEmptySelection?: boolean;
32
- outputJson?: boolean;
33
- }
34
-
35
- interface CandidateTask extends TaskRow {
36
- storyKey: string;
37
- epicKey: string;
38
- dependencies: string[];
39
- }
40
-
41
- interface StoryGroup {
42
- epic: {
43
- id: string;
44
- key: string;
45
- title: string;
46
- description?: string;
47
- };
48
- story: {
49
- id: string;
50
- key: string;
51
- title: string;
52
- description?: string;
53
- acceptance?: string[];
54
- };
55
- tasks: CandidateTask[];
56
- docSummary?: string;
57
- historySummary?: string;
58
- }
59
-
60
- const DEFAULT_STRATEGY: RefineStrategy = "auto";
61
- const FORBIDDEN_TARGET_STATUSES = new Set(["ready_to_review", "ready_to_qa", "completed"]);
62
- const DEFAULT_MAX_TASKS = 250;
63
- const MAX_AGENT_OUTPUT_CHARS = 10_000_000;
64
-
65
- const estimateTokens = (text: string): number => Math.max(1, Math.ceil(text.length / 4));
66
-
67
- const extractJson = (raw: string): any | undefined => {
68
- try {
69
- const fenced = raw.match(/```json([\s\S]*?)```/);
70
- const candidate = fenced ? fenced[1] : raw;
71
- const start = candidate.indexOf("{");
72
- const end = candidate.lastIndexOf("}");
73
- if (start === -1 || end === -1 || end <= start) return JSON.parse(raw);
74
- return JSON.parse(candidate.slice(start, end + 1));
75
- } catch {
76
- return undefined;
77
- }
78
- };
79
-
80
- const normalizeOperation = (op: any): RefineOperation => {
81
- if (!op || typeof op !== "object") return op as RefineOperation;
82
- if (op.op !== "update_task") return op as RefineOperation;
83
- const taskKey =
84
- (op as any).taskKey ?? (op as any).key ?? (op as any).task ?? (op as any).targetTaskKey ?? null;
85
- const updates = { ...(op as any).updates };
86
- const inlineFields = ["title", "description", "acceptanceCriteria", "type", "status", "storyPoints", "priority", "dependsOn", "metadata"];
87
- for (const field of inlineFields) {
88
- if (op[field] !== undefined && updates[field] === undefined) {
89
- updates[field] = op[field];
90
- }
91
- }
92
- return {
93
- ...(op as any),
94
- taskKey,
95
- updates,
96
- } as RefineOperation;
97
- };
98
-
99
- const safeParsePlan = (content: string): RefineTasksPlan | undefined => {
100
- try {
101
- const parsed = JSON.parse(content) as RefineTasksPlan;
102
- if (parsed && Array.isArray(parsed.operations)) return parsed;
103
- } catch {
104
- /* ignore */
105
- }
106
- return undefined;
107
- };
108
-
109
- const formatTaskSummary = (task: CandidateTask): string => {
110
- return [
111
- `- ${task.key}: ${task.title} [${task.status}${task.type ? `/${task.type}` : ""}]`,
112
- task.storyPoints !== null && task.storyPoints !== undefined ? ` SP: ${task.storyPoints}` : "",
113
- task.dependencies.length ? ` Depends on: ${task.dependencies.join(", ")}` : "",
114
- ]
115
- .filter(Boolean)
116
- .join("\n");
117
- };
118
-
119
- export class RefineTasksService {
120
- private docdex: DocdexClient;
121
- private jobService: JobService;
122
- private agentService: AgentService;
123
- private repo: GlobalRepository;
124
- private workspaceRepo: WorkspaceRepository;
125
- private routingService: RoutingService;
126
- private workspace: WorkspaceResolution;
127
-
128
- constructor(
129
- workspace: WorkspaceResolution,
130
- deps: {
131
- docdex: DocdexClient;
132
- jobService: JobService;
133
- agentService: AgentService;
134
- repo: GlobalRepository;
135
- workspaceRepo: WorkspaceRepository;
136
- routingService: RoutingService;
137
- },
138
- ) {
139
- this.workspace = workspace;
140
- this.docdex = deps.docdex;
141
- this.jobService = deps.jobService;
142
- this.agentService = deps.agentService;
143
- this.repo = deps.repo;
144
- this.workspaceRepo = deps.workspaceRepo;
145
- this.routingService = deps.routingService;
146
- }
147
-
148
- static async create(workspace: WorkspaceResolution): Promise<RefineTasksService> {
149
- const repo = await GlobalRepository.create();
150
- const agentService = new AgentService(repo);
151
- const routingService = await RoutingService.create();
152
- const docdex = new DocdexClient({
153
- workspaceRoot: workspace.workspaceRoot,
154
- baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
155
- });
156
- const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
157
- const jobService = new JobService(workspace, workspaceRepo);
158
- return new RefineTasksService(workspace, {
159
- docdex,
160
- jobService,
161
- agentService,
162
- repo,
163
- workspaceRepo,
164
- routingService,
165
- });
166
- }
167
-
168
- async close(): Promise<void> {
169
- const tryClose = async (target: unknown) => {
170
- try {
171
- if ((target as any)?.close) {
172
- await (target as any).close();
173
- }
174
- } catch {
175
- // ignore close errors
176
- }
177
- };
178
- await tryClose(this.agentService);
179
- await tryClose(this.repo);
180
- await tryClose(this.jobService);
181
- await tryClose(this.workspaceRepo);
182
- await tryClose(this.routingService);
183
- await tryClose(this.docdex);
184
- }
185
-
186
- private async resolveAgent(agentName?: string) {
187
- const resolved = await this.routingService.resolveAgentForCommand({
188
- workspace: this.workspace,
189
- commandName: "refine-tasks",
190
- overrideAgentSlug: agentName,
191
- });
192
- return resolved.agent;
193
- }
194
-
195
- private async selectTasks(
196
- projectKey: string,
197
- filters: {
198
- epicKey?: string;
199
- storyKey?: string;
200
- taskKeys?: string[];
201
- statusFilter?: string[];
202
- maxTasks?: number;
203
- excludeAlreadyRefined?: boolean;
204
- },
205
- ): Promise<{ projectId: string; groups: StoryGroup[]; warnings: string[] }> {
206
- const db = this.workspaceRepo.getDb();
207
- const warnings: string[] = [];
208
- const project = await this.workspaceRepo.getProjectByKey(projectKey);
209
- if (!project) {
210
- throw new Error(`Unknown project key: ${projectKey}`);
211
- }
212
-
213
- const epicRow = filters.epicKey
214
- ? await db.get<{ id: string; key: string; title: string; description?: string }>(
215
- `SELECT id, key, title, description FROM epics WHERE key = ? AND project_id = ?`,
216
- filters.epicKey,
217
- project.id,
218
- )
219
- : undefined;
220
- if (filters.epicKey && !epicRow) {
221
- throw new Error(`Unknown epic key ${filters.epicKey} under project ${projectKey}`);
222
- }
223
-
224
- const storyRow = filters.storyKey
225
- ? await db.get<{ id: string; key: string; epic_id: string; title: string; description?: string; acceptance_criteria?: string | null }>(
226
- `SELECT id, key, epic_id, title, description, acceptance_criteria FROM user_stories WHERE key = ?`,
227
- filters.storyKey,
228
- )
229
- : undefined;
230
- if (filters.storyKey && !storyRow) {
231
- throw new Error(`Unknown user story key ${filters.storyKey}`);
232
- }
233
- if (filters.storyKey && epicRow && storyRow && storyRow.epic_id !== epicRow.id) {
234
- throw new Error(`Story ${filters.storyKey} is not under epic ${filters.epicKey}`);
235
- }
236
-
237
- const clauses: string[] = ["t.project_id = ?"];
238
- const params: any[] = [project.id];
239
- if (epicRow) {
240
- clauses.push("t.epic_id = ?");
241
- params.push(epicRow.id);
242
- }
243
- if (storyRow) {
244
- clauses.push("t.user_story_id = ?");
245
- params.push(storyRow.id);
246
- }
247
- if (filters.taskKeys && filters.taskKeys.length > 0) {
248
- clauses.push(`t.key IN (${filters.taskKeys.map(() => "?").join(", ")})`);
249
- params.push(...filters.taskKeys);
250
- }
251
- if (filters.statusFilter && filters.statusFilter.length > 0) {
252
- clauses.push(`LOWER(t.status) IN (${filters.statusFilter.map(() => "?").join(", ")})`);
253
- params.push(...filters.statusFilter.map((s) => s.toLowerCase()));
254
- }
255
- if (filters.excludeAlreadyRefined) {
256
- clauses.push(
257
- `NOT EXISTS (
258
- SELECT 1
259
- FROM task_runs tr
260
- WHERE tr.task_id = t.id
261
- AND tr.command = ?
262
- AND LOWER(tr.status) = 'succeeded'
263
- )`,
264
- );
265
- params.push("refine-tasks");
266
- }
267
- const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
268
- const limit = filters.maxTasks ? `LIMIT ${filters.maxTasks}` : "";
269
- const rows = await db.all<any[]>(
270
- `
271
- SELECT
272
- t.id AS task_id,
273
- t.key AS task_key,
274
- t.project_id AS project_id,
275
- t.epic_id AS epic_id,
276
- t.user_story_id AS story_id,
277
- t.title AS task_title,
278
- t.description AS task_description,
279
- t.type AS task_type,
280
- t.status AS task_status,
281
- t.story_points AS task_story_points,
282
- t.priority AS task_priority,
283
- t.metadata_json AS task_metadata,
284
- e.key AS epic_key,
285
- e.title AS epic_title,
286
- e.description AS epic_description,
287
- s.key AS story_key,
288
- s.title AS story_title,
289
- s.description AS story_description,
290
- s.acceptance_criteria AS story_acceptance
291
- FROM tasks t
292
- INNER JOIN epics e ON e.id = t.epic_id
293
- INNER JOIN user_stories s ON s.id = t.user_story_id
294
- ${where}
295
- ORDER BY s.priority IS NULL, s.priority, t.priority IS NULL, t.priority, t.created_at
296
- ${limit}
297
- `,
298
- params,
299
- );
300
-
301
- const taskIds = rows.map((r) => r.task_id);
302
- const depMap = new Map<string, string[]>();
303
- if (taskIds.length > 0) {
304
- const depRows = await db.all<{ task_id: string; dep_key: string }[]>(
305
- `
306
- SELECT td.task_id, dep.key AS dep_key
307
- FROM task_dependencies td
308
- INNER JOIN tasks dep ON dep.id = td.depends_on_task_id
309
- WHERE td.task_id IN (${taskIds.map(() => "?").join(", ")})
310
- `,
311
- taskIds,
312
- );
313
- for (const dep of depRows) {
314
- const list = depMap.get(dep.task_id) ?? [];
315
- list.push(dep.dep_key);
316
- depMap.set(dep.task_id, list);
317
- }
318
- }
319
-
320
- const groups = new Map<string, StoryGroup>();
321
- for (const row of rows) {
322
- const acceptance = row.story_acceptance ? String(row.story_acceptance).split(/\r?\n/).filter(Boolean) : [];
323
- const groupKey = row.story_id;
324
- if (!groups.has(groupKey)) {
325
- groups.set(groupKey, {
326
- epic: { id: row.epic_id, key: row.epic_key, title: row.epic_title, description: row.epic_description ?? undefined },
327
- story: { id: row.story_id, key: row.story_key, title: row.story_title, description: row.story_description ?? undefined, acceptance },
328
- tasks: [],
329
- });
330
- }
331
- const group = groups.get(groupKey)!;
332
- const task: CandidateTask = {
333
- id: row.task_id,
334
- projectId: row.project_id,
335
- epicId: row.epic_id,
336
- userStoryId: row.story_id,
337
- key: row.task_key,
338
- title: row.task_title,
339
- description: row.task_description ?? undefined,
340
- type: row.task_type ?? undefined,
341
- status: row.task_status,
342
- storyPoints: row.task_story_points ?? null,
343
- priority: row.task_priority ?? null,
344
- assignedAgentId: null,
345
- assigneeHuman: null,
346
- vcsBranch: null,
347
- vcsBaseBranch: null,
348
- vcsLastCommitSha: null,
349
- metadata: row.task_metadata ? JSON.parse(row.task_metadata) : undefined,
350
- openapiVersionAtCreation: null,
351
- createdAt: "",
352
- updatedAt: "",
353
- storyKey: row.story_key,
354
- epicKey: row.epic_key,
355
- dependencies: depMap.get(row.task_id) ?? [],
356
- };
357
- group.tasks.push(task);
358
- }
359
-
360
- if (filters.maxTasks && rows.length > filters.maxTasks) {
361
- warnings.push(`max-tasks=${filters.maxTasks} truncated selection to ${filters.maxTasks} tasks.`);
362
- }
363
-
364
- return { projectId: project.id, groups: Array.from(groups.values()), warnings };
365
- }
366
-
367
- private parseTaskKeyParts(taskKey: string): { storyKey: string; epicKey: string } | null {
368
- const match = taskKey.match(/^(.*-us-\d+)-t\d+$/);
369
- if (!match) return null;
370
- const storyKey = match[1];
371
- const epicMatch = storyKey.match(/^(.*)-us-\d+$/);
372
- const epicKey = epicMatch ? epicMatch[1] : storyKey.split("-us-")[0];
373
- return { storyKey, epicKey };
374
- }
375
-
376
- private async ensureTaskExists(
377
- projectId: string,
378
- projectKey: string,
379
- taskKey: string,
380
- createIfMissing: boolean,
381
- seed?: { fields?: Record<string, unknown>; updates?: Record<string, unknown> },
382
- ): Promise<{ task: CandidateTask; epic: StoryGroup["epic"]; story: StoryGroup["story"] } | undefined> {
383
- const parts = this.parseTaskKeyParts(taskKey);
384
- if (!parts) return undefined;
385
- const db = this.workspaceRepo.getDb();
386
- const existing = await this.workspaceRepo.getTaskByKey(taskKey);
387
- const loadDeps = async (taskId: string): Promise<string[]> => {
388
- const depRows = await db.all<{ dep_key: string }[]>(
389
- `SELECT dep.key AS dep_key
390
- FROM task_dependencies td
391
- INNER JOIN tasks dep ON dep.id = td.depends_on_task_id
392
- WHERE td.task_id = ?`,
393
- taskId,
394
- );
395
- return depRows.map((d) => d.dep_key);
396
- };
397
-
398
- const ensureEpic = async (): Promise<StoryGroup["epic"]> => {
399
- const row = await db.get<{ id: string; key: string; title: string; description?: string }>(
400
- `SELECT id, key, title, description FROM epics WHERE key = ? AND project_id = ?`,
401
- parts.epicKey,
402
- projectId,
403
- );
404
- if (row) return { id: row.id, key: row.key, title: row.title, description: row.description ?? undefined };
405
- const [inserted] = await this.workspaceRepo.insertEpics(
406
- [
407
- {
408
- projectId,
409
- key: parts.epicKey,
410
- title: `Epic ${parts.epicKey}`,
411
- description: `Auto-created while applying refine plan for ${projectKey}`,
412
- storyPointsTotal: null,
413
- priority: null,
414
- },
415
- ],
416
- false,
417
- );
418
- return { id: inserted.id, key: inserted.key, title: inserted.title, description: inserted.description ?? undefined };
419
- };
420
-
421
- const ensureStory = async (epicId: string): Promise<StoryGroup["story"]> => {
422
- const row = await db.get<{
423
- id: string;
424
- key: string;
425
- title: string;
426
- description?: string;
427
- acceptance_criteria?: string | null;
428
- }>(`SELECT id, key, title, description, acceptance_criteria FROM user_stories WHERE key = ?`, parts.storyKey);
429
- if (row) {
430
- const acceptance = row.acceptance_criteria ? String(row.acceptance_criteria).split(/\r?\n/).filter(Boolean) : [];
431
- return {
432
- id: row.id,
433
- key: row.key,
434
- title: row.title,
435
- description: row.description ?? undefined,
436
- acceptance,
437
- };
438
- }
439
- const [inserted] = await this.workspaceRepo.insertStories(
440
- [
441
- {
442
- projectId,
443
- epicId,
444
- key: parts.storyKey,
445
- title: `Story ${parts.storyKey}`,
446
- description: `Auto-created while applying refine plan for ${projectKey}`,
447
- acceptanceCriteria: undefined,
448
- storyPointsTotal: null,
449
- priority: null,
450
- },
451
- ],
452
- false,
453
- );
454
- return {
455
- id: inserted.id,
456
- key: inserted.key,
457
- title: inserted.title,
458
- description: inserted.description ?? undefined,
459
- acceptance: [],
460
- };
461
- };
462
-
463
- if (existing) {
464
- const epicRow = await db.get<{ id: string; key: string; title: string; description?: string }>(
465
- `SELECT id, key, title, description FROM epics WHERE id = ?`,
466
- existing.epicId,
467
- );
468
- const storyRow = await db.get<{
469
- id: string;
470
- key: string;
471
- title: string;
472
- description?: string;
473
- acceptance_criteria?: string | null;
474
- }>(`SELECT id, key, title, description, acceptance_criteria FROM user_stories WHERE id = ?`, existing.userStoryId);
475
- const acceptance = storyRow?.acceptance_criteria
476
- ? String(storyRow.acceptance_criteria).split(/\r?\n/).filter(Boolean)
477
- : [];
478
- return {
479
- task: {
480
- ...existing,
481
- storyKey: storyRow?.key ?? parts.storyKey,
482
- epicKey: epicRow?.key ?? parts.epicKey,
483
- dependencies: await loadDeps(existing.id),
484
- },
485
- epic: {
486
- id: epicRow?.id ?? existing.epicId,
487
- key: epicRow?.key ?? parts.epicKey,
488
- title: epicRow?.title ?? `Epic ${parts.epicKey}`,
489
- description: epicRow?.description ?? undefined,
490
- },
491
- story: {
492
- id: storyRow?.id ?? existing.userStoryId,
493
- key: storyRow?.key ?? parts.storyKey,
494
- title: storyRow?.title ?? `Story ${parts.storyKey}`,
495
- description: storyRow?.description ?? undefined,
496
- acceptance,
497
- },
498
- };
499
- }
500
-
501
- if (!createIfMissing) return undefined;
502
-
503
- const updates = (seed?.updates as Record<string, unknown>) ?? (seed?.fields as Record<string, unknown>) ?? {};
504
- const epic = await ensureEpic();
505
- const story = await ensureStory(epic.id);
506
-
507
- const [task] = await this.workspaceRepo.insertTasks(
508
- [
509
- {
510
- projectId,
511
- epicId: epic.id,
512
- userStoryId: story.id,
513
- key: taskKey,
514
- title: (updates.title as string | undefined) ?? `Task ${taskKey}`,
515
- description: (updates.description as string | undefined) ?? "",
516
- type: (updates.type as string | undefined) ?? "feature",
517
- status: (updates.status as string | undefined) ?? "not_started",
518
- storyPoints: (updates.storyPoints as number | undefined) ?? null,
519
- priority: (updates.priority as number | undefined) ?? null,
520
- metadata: (updates.metadata as Record<string, unknown> | undefined) ?? undefined,
521
- },
522
- ],
523
- false,
524
- );
525
-
526
- return {
527
- task: {
528
- ...task,
529
- storyKey: story.key,
530
- epicKey: epic.key,
531
- dependencies: [],
532
- },
533
- epic,
534
- story,
535
- };
536
- }
537
-
538
- private buildStoryPrompt(group: StoryGroup, strategy: RefineStrategy, docSummary?: string): string {
539
- const taskList = group.tasks.map((t) => formatTaskSummary(t)).join("\n");
540
- const constraints = [
541
- "- Immutable: project_id, epic_id, user_story_id, task keys.",
542
- "- Allowed edits: title, description, acceptanceCriteria, metadata/labels, type, priority, storyPoints, status (but NOT ready_to_review/qa/completed).",
543
- "- Splits: children stay under same story; keep parent unless keepParent=false; child dependsOn must reference existing tasks or siblings.",
544
- "- Merges: target and sources must be in same story; prefer cancelling redundant sources (status=cancelled) and preserve useful details in target updates.",
545
- "- Dependencies: maintain DAG; do not introduce cycles or cross-story edges.",
546
- "- Story points: non-negative, keep within typical agile range (0-13).",
547
- "- Do not invent new epics/stories or change parentage.",
548
- ].join("\n");
549
- return [
550
- `You are refining tasks for epic ${group.epic.key} "${group.epic.title}" and story ${group.story.key} "${group.story.title}".`,
551
- `Strategy: ${strategy}`,
552
- "Story acceptance criteria:",
553
- group.story.acceptance?.length ? group.story.acceptance.map((c) => `- ${c}`).join("\n") : "- (none provided)",
554
- "Current tasks:",
555
- taskList || "- (no tasks selected)",
556
- "Doc context (summaries only):",
557
- docSummary || "(none)",
558
- "Recent task history (logs/comments):",
559
- group.historySummary || "(none)",
560
- "Constraints:",
561
- constraints,
562
- "Return JSON ONLY matching: { \"operations\": [UpdateTaskOp | SplitTaskOp | MergeTasksOp | UpdateEstimateOp] } where each item has an `op` discriminator (update_task|split_task|merge_tasks|update_estimate).",
563
- ].join("\n\n");
564
- }
565
-
566
- private async summarizeDocs(projectKey: string, epicKey?: string, storyKey?: string): Promise<{ summary: string; warnings: string[] }> {
567
- const warnings: string[] = [];
568
- const startedAt = Date.now();
569
- try {
570
- const docs = await this.docdex.search({
571
- projectKey,
572
- profile: "sds",
573
- query: [epicKey, storyKey].filter(Boolean).join(" "),
574
- });
575
- if (!docs || docs.length === 0) {
576
- return { summary: "(no relevant docdex entries)", warnings: [] };
577
- }
578
- const top = docs
579
- .filter((doc) => {
580
- const type = (doc.docType ?? "").toLowerCase();
581
- return type.includes("sds") || type.includes("pdr") || type.includes("rfp");
582
- })
583
- .slice(0, 5);
584
- const summary = top
585
- .map((doc) => {
586
- const segments = (doc.segments ?? []).slice(0, 3);
587
- const segText = segments
588
- .map((seg, idx) => {
589
- const snippet = seg.content.length > 180 ? `${seg.content.slice(0, 180)}...` : seg.content;
590
- return ` (${idx + 1}) ${seg.heading ? `${seg.heading}: ` : ""}${snippet}`;
591
- })
592
- .join("\n");
593
- const head = doc.content ? doc.content.split(/\r?\n/).slice(0, 2).join(" ").slice(0, 160) : "";
594
- return [`- [${doc.docType}] ${doc.title ?? doc.path ?? doc.id}${head ? ` — ${head}` : ""}`, segText].filter(Boolean).join("\n");
595
- })
596
- .join("\n");
597
- const durationSeconds = (Date.now() - startedAt) / 1000;
598
- await this.jobService.recordTokenUsage({
599
- workspaceId: this.workspace.workspaceId,
600
- jobId: undefined,
601
- commandRunId: undefined,
602
- agentId: undefined,
603
- modelName: "docdex",
604
- tokensPrompt: null,
605
- tokensCompletion: null,
606
- tokensTotal: null,
607
- durationSeconds,
608
- timestamp: new Date().toISOString(),
609
- metadata: { command: "refine-tasks", action: "docdex_search", projectKey, epicKey, storyKey },
610
- });
611
- return { summary: summary || "(no doc segments found)", warnings };
612
- } catch (error) {
613
- warnings.push(`Docdex lookup failed: ${(error as Error).message}`);
614
- return { summary: "(docdex unavailable)", warnings };
615
- }
616
- }
617
-
618
- private async summarizeHistory(taskIds: string[]): Promise<string> {
619
- if (taskIds.length === 0) return "(none)";
620
- const db = this.workspaceRepo.getDb();
621
- const placeholders = taskIds.map(() => "?").join(", ");
622
- try {
623
- const rows = await db.all<
624
- { task_id: string; timestamp: string; level: string | null; message: string | null; source: string | null }[]
625
- >(
626
- `
627
- SELECT r.task_id, l.timestamp, l.level, l.message, l.source
628
- FROM task_logs l
629
- INNER JOIN task_runs r ON r.id = l.task_run_id
630
- WHERE r.task_id IN (${placeholders})
631
- ORDER BY l.timestamp DESC
632
- LIMIT 15
633
- `,
634
- taskIds,
635
- );
636
- if (!rows || rows.length === 0) return "(none)";
637
- return rows
638
- .map((row) => {
639
- const level = row.level ? row.level.toUpperCase() : "INFO";
640
- const msg = row.message ?? "";
641
- return `- ${row.task_id}: [${level}] ${msg} (${row.source ?? "run"})`;
642
- })
643
- .join("\n");
644
- } catch {
645
- return "(unavailable)";
646
- }
647
- }
648
-
649
- private async logWarningsToTasks(
650
- taskIds: string[],
651
- jobId: string,
652
- commandRunId: string,
653
- message: string,
654
- ): Promise<void> {
655
- const now = new Date().toISOString();
656
- for (const taskId of taskIds) {
657
- try {
658
- const run = await this.workspaceRepo.createTaskRun({
659
- taskId,
660
- command: "refine-tasks",
661
- status: "succeeded",
662
- jobId,
663
- commandRunId,
664
- startedAt: now,
665
- finishedAt: now,
666
- runContext: { warning: true },
667
- });
668
- await this.workspaceRepo.insertTaskLog({
669
- taskRunId: run.id,
670
- sequence: 0,
671
- timestamp: now,
672
- level: "warn",
673
- source: "refine-tasks",
674
- message,
675
- details: { warning: true },
676
- });
677
- } catch {
678
- // Best-effort logging only.
679
- }
680
- }
681
- }
682
-
683
- private mergeMetadata(existing: Record<string, unknown> | undefined, updates?: Record<string, unknown>): Record<string, unknown> | undefined {
684
- if (!updates) return existing;
685
- return { ...(existing ?? {}), ...updates };
686
- }
687
-
688
- private validateOperation(group: StoryGroup, op: RefineOperation): { valid: boolean; reason?: string } {
689
- const allowedOps = new Set(["update_task", "split_task", "merge_tasks", "update_estimate"]);
690
- if (!op || typeof (op as any).op !== "string" || !allowedOps.has((op as any).op)) {
691
- return { valid: false, reason: "Unknown op type" };
692
- }
693
- if (op.op === "update_task") {
694
- if (!op.taskKey || typeof op.updates !== "object") {
695
- return { valid: false, reason: "update_task missing taskKey or updates" };
696
- }
697
- }
698
- if (op.op === "split_task") {
699
- const split = op as SplitTaskOp;
700
- if (!split.taskKey || !Array.isArray(split.children) || split.children.length === 0) {
701
- return { valid: false, reason: "split_task missing taskKey or children" };
702
- }
703
- }
704
- if (op.op === "merge_tasks") {
705
- if (!op.targetTaskKey || !Array.isArray(op.sourceTaskKeys) || op.sourceTaskKeys.length === 0) {
706
- return { valid: false, reason: "merge_tasks missing targets" };
707
- }
708
- }
709
- if (op.op === "update_estimate") {
710
- if (!op.taskKey) return { valid: false, reason: "update_estimate missing taskKey" };
711
- }
712
- const keySet = new Set(group.tasks.map((t) => t.key));
713
- if ((op as any).taskKey && !keySet.has((op as any).taskKey)) {
714
- return { valid: false, reason: `Unknown task key ${(op as any).taskKey} for story ${group.story.key}` };
715
- }
716
- if ((op as any).targetTaskKey && !keySet.has((op as any).targetTaskKey)) {
717
- return { valid: false, reason: `Unknown merge target ${(op as any).targetTaskKey}` };
718
- }
719
- if ((op as any).sourceTaskKeys) {
720
- const missing = (op as any).sourceTaskKeys.filter((k: string) => !keySet.has(k));
721
- if (missing.length) {
722
- return { valid: false, reason: `Merge sources not in story ${group.story.key}: ${missing.join(", ")}` };
723
- }
724
- }
725
- if (op.op === "update_task" && op.updates.status && FORBIDDEN_TARGET_STATUSES.has(op.updates.status.toLowerCase())) {
726
- return { valid: false, reason: `Status ${op.updates.status} not allowed in refine-tasks` };
727
- }
728
- if (op.op === "update_task" && op.updates.storyPoints !== undefined) {
729
- const sp = op.updates.storyPoints;
730
- if (sp !== null && (typeof sp !== "number" || sp < 0 || sp > 13)) {
731
- return { valid: false, reason: `Story points out of bounds for ${op.taskKey}` };
732
- }
733
- }
734
- if (op.op === "split_task") {
735
- const split = op as SplitTaskOp;
736
- const invalidDep = split.children.some((child) => child.dependsOn?.some((dep) => !keySet.has(dep)));
737
- if (invalidDep) {
738
- return { valid: false, reason: "Split child references unknown dependency" };
739
- }
740
- if (split.children.some((child) => child.storyPoints !== undefined && child.storyPoints !== null && (child.storyPoints < 0 || child.storyPoints > 13))) {
741
- return { valid: false, reason: "Child story points out of bounds" };
742
- }
743
- const crossStory = split.children.some((child) => (child as any).storyKey && (child as any).storyKey !== group.story.key);
744
- if (crossStory) {
745
- return { valid: false, reason: "Split children must stay within the same story" };
746
- }
747
- }
748
- if (op.op === "merge_tasks") {
749
- const crossStory =
750
- op.sourceTaskKeys.some((k) => !keySet.has(k)) ||
751
- (op.targetTaskKey && !keySet.has(op.targetTaskKey));
752
- if (crossStory) {
753
- return { valid: false, reason: "Merge must stay within the same story" };
754
- }
755
- const uniqueSources = new Set(op.sourceTaskKeys.filter(Boolean));
756
- if (uniqueSources.size !== op.sourceTaskKeys.length) {
757
- return { valid: false, reason: "Duplicate source task keys in merge" };
758
- }
759
- if (uniqueSources.has(op.targetTaskKey)) {
760
- return { valid: false, reason: "Merge sources cannot include target" };
761
- }
762
- }
763
- return { valid: true };
764
- }
765
-
766
- private detectCycle(edges: Array<{ from: string; to: string }>): boolean {
767
- const adj = new Map<string, string[]>();
768
- for (const edge of edges) {
769
- const list = adj.get(edge.from) ?? [];
770
- list.push(edge.to);
771
- adj.set(edge.from, list);
772
- }
773
- const visiting = new Set<string>();
774
- const visited = new Set<string>();
775
- const dfs = (node: string): boolean => {
776
- if (visiting.has(node)) return true;
777
- if (visited.has(node)) return false;
778
- visiting.add(node);
779
- for (const nxt of adj.get(node) ?? []) {
780
- if (dfs(nxt)) return true;
781
- }
782
- visiting.delete(node);
783
- visited.add(node);
784
- return false;
785
- };
786
- for (const node of adj.keys()) {
787
- if (dfs(node)) return true;
788
- }
789
- return false;
790
- }
791
-
792
- private async applyOperations(
793
- projectId: string,
794
- jobId: string,
795
- commandRunId: string,
796
- group: StoryGroup,
797
- operations: RefineOperation[],
798
- ): Promise<{ created: string[]; updated: string[]; cancelled: string[]; storyPointsDelta: number; warnings: string[] }> {
799
- const created: string[] = [];
800
- const updated: string[] = [];
801
- const cancelled: string[] = [];
802
- let storyPointsDelta = 0;
803
- const warnings: string[] = [];
804
- const taskByKey = new Map(group.tasks.map((t) => [t.key, t]));
805
-
806
- await this.workspaceRepo.withTransaction(async () => {
807
- let stage = "start";
808
- const newTasks: TaskInsert[] = [];
809
- const pendingDeps: { childKey: string; dependsOnId: string; relationType: string }[] = [];
810
- const dependencyEdges: Array<{ from: string; to: string }> = [];
811
-
812
- try {
813
- stage = "load:storyKeys";
814
- const storyKeyRows = await this.workspaceRepo.getDb().all<{ key: string }[]>(
815
- `SELECT key FROM tasks WHERE user_story_id = ?`,
816
- group.story.id,
817
- );
818
- const existingKeys = storyKeyRows.map((r) => r.key);
819
- const keyGen = createTaskKeyGenerator(group.story.key, existingKeys);
820
-
821
- for (const op of operations) {
822
- stage = `op:${op.op}`;
823
- if (op.op === "update_task") {
824
- const target = taskByKey.get(op.taskKey);
825
- if (!target) continue;
826
- const before = { ...target };
827
- const metadata = this.mergeMetadata(target.metadata, op.updates.metadata);
828
- const beforeSp = target.storyPoints ?? 0;
829
- const afterSp = op.updates.storyPoints ?? target.storyPoints ?? null;
830
- storyPointsDelta += (afterSp ?? 0) - (beforeSp ?? 0);
831
- await this.workspaceRepo.updateTask(target.id, {
832
- title: op.updates.title ?? target.title,
833
- description: op.updates.description ?? target.description ?? null,
834
- type: op.updates.type ?? target.type ?? null,
835
- storyPoints: afterSp,
836
- priority: op.updates.priority ?? target.priority ?? null,
837
- status: op.updates.status ?? target.status,
838
- metadata,
839
- });
840
- updated.push(target.key);
841
- await this.workspaceRepo.insertTaskRevision({
842
- taskId: target.id,
843
- jobId,
844
- commandRunId,
845
- snapshotBefore: before,
846
- snapshotAfter: { ...before, ...op.updates, storyPoints: afterSp, metadata },
847
- createdAt: new Date().toISOString(),
848
- });
849
- } else if (op.op === "split_task") {
850
- const target = taskByKey.get(op.taskKey);
851
- if (!target) continue;
852
- if (op.parentUpdates) {
853
- const before = { ...target };
854
- await this.workspaceRepo.updateTask(target.id, {
855
- title: op.parentUpdates.title ?? target.title,
856
- description: op.parentUpdates.description ?? target.description ?? null,
857
- type: op.parentUpdates.type ?? target.type ?? null,
858
- storyPoints: op.parentUpdates.storyPoints ?? target.storyPoints ?? null,
859
- priority: op.parentUpdates.priority ?? target.priority ?? null,
860
- metadata: this.mergeMetadata(target.metadata, op.parentUpdates.metadata),
861
- });
862
- updated.push(target.key);
863
- await this.workspaceRepo.insertTaskRevision({
864
- taskId: target.id,
865
- jobId,
866
- commandRunId,
867
- snapshotBefore: before,
868
- snapshotAfter: {
869
- ...before,
870
- ...op.parentUpdates,
871
- storyPoints: op.parentUpdates.storyPoints ?? before.storyPoints,
872
- metadata: this.mergeMetadata(before.metadata, op.parentUpdates.metadata),
873
- },
874
- createdAt: new Date().toISOString(),
875
- });
876
- }
877
- for (const child of op.children) {
878
- const childKey = keyGen();
879
- const childSp = child.storyPoints ?? null;
880
- if (childSp) {
881
- storyPointsDelta += childSp;
882
- }
883
- const childInsert: TaskInsert = {
884
- projectId,
885
- epicId: target.epicId,
886
- userStoryId: target.userStoryId,
887
- key: childKey,
888
- title: child.title,
889
- description: child.description ?? target.description ?? "",
890
- type: child.type ?? target.type ?? "feature",
891
- status: "not_started",
892
- storyPoints: childSp,
893
- priority: child.priority ?? target.priority ?? null,
894
- metadata: this.mergeMetadata({}, child.metadata),
895
- assignedAgentId: target.assignedAgentId ?? null,
896
- assigneeHuman: target.assigneeHuman ?? null,
897
- vcsBranch: null,
898
- vcsBaseBranch: null,
899
- vcsLastCommitSha: null,
900
- openapiVersionAtCreation: target.openapiVersionAtCreation ?? null,
901
- };
902
- newTasks.push(childInsert);
903
- const dependsOn = child.dependsOn ?? [];
904
- for (const depKey of dependsOn) {
905
- const depTask = taskByKey.get(depKey);
906
- if (depTask) {
907
- pendingDeps.push({ childKey, dependsOnId: depTask.id, relationType: "blocks" });
908
- dependencyEdges.push({ from: childKey, to: depTask.key });
909
- }
910
- }
911
- taskByKey.set(childKey, {
912
- ...childInsert,
913
- id: "",
914
- createdAt: "",
915
- updatedAt: "",
916
- storyKey: group.story.key,
917
- epicKey: group.epic.key,
918
- dependencies: child.dependsOn ?? [],
919
- });
920
- created.push(childKey);
921
- }
922
- } else if (op.op === "merge_tasks") {
923
- const target = taskByKey.get(op.targetTaskKey);
924
- if (!target) continue;
925
- if (op.updates) {
926
- const before = { ...target };
927
- await this.workspaceRepo.updateTask(target.id, {
928
- title: op.updates.title ?? target.title,
929
- description: op.updates.description ?? target.description ?? null,
930
- type: op.updates.type ?? target.type ?? null,
931
- storyPoints: op.updates.storyPoints ?? target.storyPoints ?? null,
932
- priority: op.updates.priority ?? target.priority ?? null,
933
- metadata: this.mergeMetadata(target.metadata, op.updates.metadata),
934
- });
935
- updated.push(target.key);
936
- await this.workspaceRepo.insertTaskRevision({
937
- taskId: target.id,
938
- jobId,
939
- commandRunId,
940
- snapshotBefore: before,
941
- snapshotAfter: {
942
- ...before,
943
- ...op.updates,
944
- storyPoints: op.updates.storyPoints ?? before.storyPoints,
945
- metadata: this.mergeMetadata(before.metadata, op.updates.metadata),
946
- },
947
- createdAt: new Date().toISOString(),
948
- });
949
- }
950
- for (const sourceKey of op.sourceTaskKeys) {
951
- const source = taskByKey.get(sourceKey);
952
- if (!source || source.key === target.key) continue;
953
- const before = { ...source };
954
- const mergedMetadata = this.mergeMetadata(source.metadata, { merged_into: target.key });
955
- await this.workspaceRepo.updateTask(source.id, {
956
- status: source.status, // do not cancel; requirement: no deletes/cancels
957
- metadata: mergedMetadata,
958
- });
959
- updated.push(source.key);
960
- await this.workspaceRepo.insertTaskRevision({
961
- taskId: source.id,
962
- jobId,
963
- commandRunId,
964
- snapshotBefore: before,
965
- snapshotAfter: { ...before, metadata: mergedMetadata },
966
- createdAt: new Date().toISOString(),
967
- });
968
- }
969
- } else if (op.op === "update_estimate") {
970
- const target = taskByKey.get(op.taskKey);
971
- if (!target) continue;
972
- const beforeSp = target.storyPoints ?? 0;
973
- const afterSp = op.storyPoints ?? target.storyPoints ?? null;
974
- storyPointsDelta += (afterSp ?? 0) - (beforeSp ?? 0);
975
- await this.workspaceRepo.updateTask(target.id, {
976
- storyPoints: afterSp,
977
- type: op.type ?? target.type ?? null,
978
- priority: op.priority ?? target.priority ?? null,
979
- });
980
- updated.push(target.key);
981
- await this.workspaceRepo.insertTaskRevision({
982
- taskId: target.id,
983
- jobId,
984
- commandRunId,
985
- snapshotBefore: { ...target },
986
- snapshotAfter: { ...target, storyPoints: afterSp, type: op.type ?? target.type ?? null, priority: op.priority ?? target.priority ?? null },
987
- createdAt: new Date().toISOString(),
988
- });
989
- }
990
- }
991
-
992
- if (newTasks.length > 0) {
993
- stage = "insert:newTasks";
994
- const inserted = await this.workspaceRepo.insertTasks(newTasks, false);
995
- const idByKey = new Map(inserted.map((t) => [t.key, t.id]));
996
- for (const row of inserted) {
997
- const current = taskByKey.get(row.key);
998
- if (current) {
999
- current.id = row.id;
1000
- current.createdAt = row.createdAt;
1001
- current.updatedAt = row.updatedAt;
1002
- }
1003
- }
1004
- const deps: TaskDependencyInsert[] = [];
1005
- for (const dep of pendingDeps) {
1006
- const childId = idByKey.get(dep.childKey);
1007
- if (childId) {
1008
- deps.push({ taskId: childId, dependsOnTaskId: dep.dependsOnId, relationType: dep.relationType });
1009
- }
1010
- }
1011
- if (deps.length > 0) {
1012
- stage = "insert:deps";
1013
- await this.workspaceRepo.insertTaskDependencies(deps, false);
1014
- }
1015
- }
1016
-
1017
- // cycle detection on current + new dependencies (by key)
1018
- const edgeSet: Array<{ from: string; to: string }> = [];
1019
- for (const task of group.tasks) {
1020
- for (const dep of task.dependencies) {
1021
- edgeSet.push({ from: task.key, to: dep });
1022
- }
1023
- }
1024
- edgeSet.push(...dependencyEdges);
1025
- const hasCycle = this.detectCycle(edgeSet);
1026
- if (hasCycle) {
1027
- throw new Error("Dependency cycle detected after refinement; aborting apply.");
1028
- }
1029
-
1030
- stage = "rollup:story";
1031
- const storyTotalRow = await this.workspaceRepo.getDb().get<{ total: number }>(
1032
- `SELECT SUM(story_points) AS total FROM tasks WHERE user_story_id = ?`,
1033
- group.story.id,
1034
- );
1035
- await this.workspaceRepo.updateStoryPointsTotal(group.story.id, storyTotalRow?.total ?? null);
1036
- stage = "rollup:epic";
1037
- const epicTotalRow = await this.workspaceRepo.getDb().get<{ total: number }>(
1038
- `SELECT SUM(story_points_total) AS total FROM user_stories WHERE epic_id = ?`,
1039
- group.epic.id,
1040
- );
1041
- await this.workspaceRepo.updateEpicStoryPointsTotal(group.epic.id, epicTotalRow?.total ?? null);
1042
-
1043
- stage = "task-runs";
1044
- const allTouched = [...new Set([...created, ...updated, ...cancelled])];
1045
- const now = new Date().toISOString();
1046
- for (const key of allTouched) {
1047
- try {
1048
- const task =
1049
- group.tasks.find((t) => t.key === key) ??
1050
- (await this.workspaceRepo.getDb().get<{ id: string }>(`SELECT id FROM tasks WHERE key = ?`, key));
1051
- if (task && task.id) {
1052
- const run = await this.workspaceRepo.createTaskRun({
1053
- taskId: task.id,
1054
- command: "refine-tasks",
1055
- status: "succeeded",
1056
- jobId,
1057
- commandRunId,
1058
- startedAt: now,
1059
- finishedAt: now,
1060
- runContext: { key },
1061
- });
1062
- await this.workspaceRepo.insertTaskLog({
1063
- taskRunId: run.id,
1064
- sequence: 0,
1065
- timestamp: now,
1066
- level: "info",
1067
- source: "refine-tasks",
1068
- message: `Applied refine operation for ${key}`,
1069
- details: { opCount: operations.length },
1070
- });
1071
- }
1072
- } catch (error) {
1073
- warnings.push(`Logging failed for ${key}: ${(error as Error).message}`);
1074
- }
1075
- }
1076
- } catch (error) {
1077
- throw new Error(`refine apply failed at ${stage}: ${(error as Error).message}`);
1078
- }
1079
- });
1080
-
1081
- return { created, updated, cancelled, storyPointsDelta, warnings };
1082
- }
1083
-
1084
- private async invokeAgent(
1085
- agentName: string | undefined,
1086
- prompt: string,
1087
- stream: boolean,
1088
- jobId: string,
1089
- commandRunId: string,
1090
- metadata?: Record<string, unknown>,
1091
- ): Promise<{ raw: string; promptTokens: number; completionTokens: number }> {
1092
- const startedAt = Date.now();
1093
- const agent = await this.resolveAgent(agentName);
1094
- const parts: string[] = [];
1095
- let capturedChars = 0;
1096
- let truncated = false;
1097
-
1098
- const logChunk = async (chunk?: string) => {
1099
- if (!chunk) return;
1100
- await this.jobService.appendLog(jobId, chunk);
1101
- if (stream) process.stdout.write(chunk);
1102
- };
1103
-
1104
- const capture = (chunk?: string) => {
1105
- if (!chunk || truncated) return;
1106
- const next = capturedChars + chunk.length;
1107
- if (next > MAX_AGENT_OUTPUT_CHARS) {
1108
- truncated = true;
1109
- return;
1110
- }
1111
- parts.push(chunk);
1112
- capturedChars = next;
1113
- };
1114
-
1115
- const formatContext = (): string => {
1116
- const meta = metadata as Record<string, unknown> | undefined;
1117
- const epic = typeof meta?.epicKey === "string" && meta.epicKey ? ` epic=${meta.epicKey}` : "";
1118
- const story = typeof meta?.storyKey === "string" && meta.storyKey ? ` story=${meta.storyKey}` : "";
1119
- return `${epic}${story}`;
1120
- };
1121
-
1122
- try {
1123
- if (stream) {
1124
- const gen = await this.agentService.invokeStream(agent.id, { input: prompt, metadata: { jobId, commandRunId } });
1125
- for await (const chunk of gen) {
1126
- const text = chunk.output ?? "";
1127
- capture(text);
1128
- await logChunk(text);
1129
- }
1130
- } else {
1131
- const result = await this.agentService.invoke(agent.id, { input: prompt, metadata: { jobId, commandRunId } });
1132
- const text = result.output ?? "";
1133
- capture(text);
1134
- await logChunk(text);
1135
- }
1136
- } catch (error) {
1137
- const message = (error as Error).message ?? String(error);
1138
- if (message.includes("Invalid string length")) {
1139
- throw new Error(
1140
- `Agent output exceeded runtime limits (Invalid string length) while refining tasks.${formatContext()} ` +
1141
- `Try rerunning with a smaller scope (e.g. --max-tasks 200, or filter by --epic/--story/--status), or disable streaming (--agent-stream false).`,
1142
- );
1143
- }
1144
- throw error;
1145
- }
1146
-
1147
- if (truncated) {
1148
- throw new Error(
1149
- `Agent output exceeded ${MAX_AGENT_OUTPUT_CHARS.toLocaleString()} characters while refining tasks.${formatContext()} ` +
1150
- `Rerun with a smaller scope (e.g. --max-tasks 200, or filter by --epic/--story/--status), or disable streaming (--agent-stream false).`,
1151
- );
1152
- }
1153
-
1154
- const output = parts.join("");
1155
- const promptTokens = estimateTokens(prompt);
1156
- const completionTokens = estimateTokens(output);
1157
- const durationSeconds = (Date.now() - startedAt) / 1000;
1158
- await this.jobService.recordTokenUsage({
1159
- workspaceId: this.workspace.workspaceId,
1160
- agentId: agent.id,
1161
- modelName: agent.defaultModel,
1162
- jobId,
1163
- commandRunId,
1164
- projectId: undefined,
1165
- epicId: undefined,
1166
- userStoryId: undefined,
1167
- tokensPrompt: promptTokens,
1168
- tokensCompletion: completionTokens,
1169
- tokensTotal: promptTokens + completionTokens,
1170
- durationSeconds,
1171
- timestamp: new Date().toISOString(),
1172
- metadata: { command: "refine-tasks", action: "agent_refine", ...(metadata ?? {}) },
1173
- });
1174
- return { raw: output, promptTokens, completionTokens };
1175
- }
1176
-
1177
- async refineTasks(options: RefineTasksOptions): Promise<RefineTasksResult> {
1178
- const strategy = options.strategy ?? DEFAULT_STRATEGY;
1179
- const agentStream = options.agentStream !== false;
1180
- const applyChanges = options.apply === true; // default to no DB writes unless explicitly requested
1181
- const shouldDefaultMaxTasks =
1182
- options.planInPath == null &&
1183
- options.maxTasks == null &&
1184
- !options.epicKey &&
1185
- !(options.userStoryKey ?? options.storyKey) &&
1186
- !(options.taskKeys && options.taskKeys.length) &&
1187
- !(options.statusFilter && options.statusFilter.length);
1188
- await this.workspaceRepo.createProjectIfMissing({
1189
- key: options.projectKey,
1190
- name: options.projectKey,
1191
- description: `Workspace project ${options.projectKey}`,
1192
- });
1193
- const commandRun = await this.jobService.startCommandRun("refine-tasks", options.projectKey, {
1194
- taskIds: options.taskKeys,
1195
- });
1196
- const job = await this.jobService.startJob("task_refinement", commandRun.id, options.projectKey, {
1197
- commandName: "refine-tasks",
1198
- payload: {
1199
- projectKey: options.projectKey,
1200
- epicKey: options.epicKey,
1201
- storyKey: options.userStoryKey ?? options.storyKey,
1202
- taskKeys: options.taskKeys,
1203
- statusFilter: options.statusFilter,
1204
- strategy,
1205
- maxTasks: options.maxTasks,
1206
- dryRun: options.dryRun,
1207
- fromDb: options.fromDb !== false,
1208
- planIn: options.planInPath,
1209
- planOut: options.planOutPath,
1210
- },
1211
- });
1212
-
1213
- try {
1214
- if (options.fromDb === false) {
1215
- throw new Error("refine-tasks currently only supports DB-backed selection; set --from-db true");
1216
- }
1217
- const selection = await this.selectTasks(options.projectKey, {
1218
- epicKey: options.epicKey,
1219
- storyKey: options.userStoryKey ?? options.storyKey,
1220
- taskKeys: options.taskKeys,
1221
- statusFilter: options.statusFilter,
1222
- maxTasks: shouldDefaultMaxTasks ? DEFAULT_MAX_TASKS : options.maxTasks,
1223
- excludeAlreadyRefined: options.excludeAlreadyRefined === true,
1224
- });
1225
-
1226
- const plan: RefineTasksPlan = {
1227
- strategy,
1228
- operations: [],
1229
- warnings: [...selection.warnings],
1230
- metadata: {
1231
- generatedAt: new Date().toISOString(),
1232
- projectKey: options.projectKey,
1233
- epicKeys: selection.groups.map((g) => g.epic.key),
1234
- storyKeys: selection.groups.map((g) => g.story.key),
1235
- strategy,
1236
- jobId: job.id,
1237
- commandRunId: commandRun.id,
1238
- },
1239
- };
1240
-
1241
- if (selection.groups.length === 0 && !options.planInPath) {
1242
- if (!options.allowEmptySelection) {
1243
- throw new Error("No tasks matched the provided filters.");
1244
- }
1245
- plan.warnings?.push("No tasks matched the provided filters.");
1246
- await this.jobService.updateJobStatus(job.id, "completed", {
1247
- payload: {
1248
- dryRun: options.dryRun ?? true,
1249
- operations: 0,
1250
- applied: false,
1251
- emptySelection: true,
1252
- },
1253
- processedItems: 0,
1254
- totalItems: 0,
1255
- lastCheckpoint: "empty_selection",
1256
- });
1257
- await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1258
- return {
1259
- jobId: job.id,
1260
- commandRunId: commandRun.id,
1261
- plan,
1262
- applied: false,
1263
- createdTasks: [],
1264
- updatedTasks: [],
1265
- cancelledTasks: [],
1266
- summary: { tasksProcessed: 0, tasksAffected: 0, storyPointsDelta: 0 },
1267
- };
1268
- }
1269
-
1270
- let planInput: RefineTasksPlan | undefined;
1271
- if (options.planInPath) {
1272
- const raw = await fs.readFile(options.planInPath, "utf8");
1273
- planInput = safeParsePlan(raw);
1274
- if (!planInput) {
1275
- throw new Error(`Failed to parse plan from ${options.planInPath}`);
1276
- }
1277
- if (planInput.metadata?.projectKey && planInput.metadata.projectKey !== options.projectKey) {
1278
- throw new Error(`Plan project mismatch: ${planInput.metadata.projectKey} !== ${options.projectKey}`);
1279
- }
1280
- if (planInput.metadata?.jobId && options.jobId && planInput.metadata.jobId !== options.jobId) {
1281
- throw new Error(`Plan was generated for job ${planInput.metadata.jobId}, mismatch with --job-id ${options.jobId}`);
1282
- }
1283
- const mergedMeta = {
1284
- generatedAt: planInput.metadata?.generatedAt ?? plan.metadata?.generatedAt ?? new Date().toISOString(),
1285
- projectKey: planInput.metadata?.projectKey ?? plan.metadata?.projectKey ?? options.projectKey,
1286
- epicKeys: planInput.metadata?.epicKeys ?? plan.metadata?.epicKeys,
1287
- storyKeys: planInput.metadata?.storyKeys ?? plan.metadata?.storyKeys,
1288
- jobId: planInput.metadata?.jobId ?? plan.metadata?.jobId,
1289
- commandRunId: planInput.metadata?.commandRunId ?? plan.metadata?.commandRunId,
1290
- strategy: planInput.metadata?.strategy ?? plan.metadata?.strategy ?? strategy,
1291
- };
1292
- plan.metadata = mergedMeta;
1293
- if (planInput.warnings) plan.warnings?.push(...planInput.warnings);
1294
- // Validate ops against current selection and group membership.
1295
- const taskToGroup = new Map<string, StoryGroup>();
1296
- selection.groups.forEach((g) => g.tasks.forEach((t) => taskToGroup.set(t.key, g)));
1297
- const allowCreateMissingPlanIn = false;
1298
- for (const rawOp of planInput.operations) {
1299
- const op = normalizeOperation(rawOp);
1300
- const keyCandidate = (op as any).taskKey ?? (op as any).targetTaskKey ?? null;
1301
- let group = keyCandidate ? taskToGroup.get(keyCandidate) : undefined;
1302
- if (!group && allowCreateMissingPlanIn && keyCandidate) {
1303
- const ensured = await this.ensureTaskExists(selection.projectId, options.projectKey, keyCandidate, true, op as any);
1304
- if (ensured) {
1305
- group =
1306
- selection.groups.find((g) => g.story.key === ensured.story.key) ??
1307
- (() => {
1308
- const newGroup: StoryGroup = {
1309
- epic: ensured.epic,
1310
- story: ensured.story,
1311
- tasks: [],
1312
- };
1313
- selection.groups.push(newGroup);
1314
- return newGroup;
1315
- })();
1316
- group.tasks.push(ensured.task);
1317
- taskToGroup.set(keyCandidate, group);
1318
- }
1319
- }
1320
- if (!group) {
1321
- plan.warnings?.push(`Skipped plan-in op because task key not in selection: ${keyCandidate ?? op.op}`);
1322
- continue;
1323
- }
1324
- const { valid, reason } = this.validateOperation(group, op);
1325
- if (!valid) {
1326
- if (reason) plan.warnings?.push(`Skipped plan-in op: ${reason}`);
1327
- continue;
1328
- }
1329
- plan.operations.push(op as RefineOperation);
1330
- }
1331
- }
1332
-
1333
- if (!planInput) {
1334
- if (shouldDefaultMaxTasks) {
1335
- plan.warnings?.push(
1336
- `No filters were provided; defaulted --max-tasks to ${DEFAULT_MAX_TASKS} to keep refinement tractable. Pass --max-tasks explicitly to override.`,
1337
- );
1338
- }
1339
- for (const group of selection.groups) {
1340
- try {
1341
- const { summary: docSummary, warnings: docWarnings } = await this.summarizeDocs(
1342
- options.projectKey,
1343
- group.epic.key,
1344
- group.story.key,
1345
- );
1346
- group.docSummary = docSummary;
1347
- const historySummary = await this.summarizeHistory(group.tasks.map((t) => t.id));
1348
- group.historySummary = historySummary;
1349
- await this.jobService.writeCheckpoint(job.id, {
1350
- stage: "context_built",
1351
- timestamp: new Date().toISOString(),
1352
- details: { epic: group.epic.key, story: group.story.key, tasks: group.tasks.length },
1353
- });
1354
- if (docWarnings.length) {
1355
- plan.warnings?.push(...docWarnings);
1356
- // eslint-disable-next-line no-console
1357
- console.warn(docWarnings.join("; "));
1358
- await this.jobService.appendLog(job.id, docWarnings.join("\n"));
1359
- await this.logWarningsToTasks(
1360
- group.tasks.map((t) => t.id),
1361
- job.id,
1362
- commandRun.id,
1363
- docWarnings.join("; "),
1364
- );
1365
- }
1366
- const prompt = this.buildStoryPrompt(group, strategy, docSummary);
1367
- const { raw } = await this.invokeAgent(
1368
- options.agentName,
1369
- prompt,
1370
- agentStream,
1371
- job.id,
1372
- commandRun.id,
1373
- { epicKey: group.epic.key, storyKey: group.story.key },
1374
- );
1375
- const parsed = extractJson(raw);
1376
- const ops =
1377
- parsed?.operations && Array.isArray(parsed.operations) ? (parsed.operations as RefineOperation[]) : [];
1378
- const normalized = ops.map(normalizeOperation);
1379
- const filtered = normalized.filter((op) => {
1380
- const { valid, reason } = this.validateOperation(group, op);
1381
- if (!valid && reason) {
1382
- plan.warnings?.push(`Skipped op for story ${group.story.key}: ${reason}`);
1383
- }
1384
- return valid;
1385
- });
1386
- plan.operations.push(...filtered);
1387
- } catch (error) {
1388
- throw new Error(
1389
- `Failed while refining epic ${group.epic.key} story ${group.story.key}: ${(error as Error).message}`,
1390
- );
1391
- }
1392
- }
1393
- }
1394
-
1395
- // Always persist the plan to disk in a unique folder (similar to create-tasks)
1396
- const ensureUniquePath = async (candidate: string): Promise<string> => {
1397
- try {
1398
- await fs.access(candidate);
1399
- const dir = path.dirname(candidate);
1400
- const base = path.basename(candidate, path.extname(candidate));
1401
- const ext = path.extname(candidate) || ".json";
1402
- const suffix = new Date().toISOString().replace(/[:.]/g, "-");
1403
- return path.join(dir, `${base}-${suffix}${ext}`);
1404
- } catch {
1405
- return candidate;
1406
- }
1407
- };
1408
-
1409
- const defaultPlanPath = path.join(
1410
- this.workspace.workspaceRoot,
1411
- ".mcoda",
1412
- "tasks",
1413
- options.projectKey,
1414
- "refinements",
1415
- job.id,
1416
- "plan.json",
1417
- );
1418
- const requestedOutPath = options.planOutPath ? path.resolve(options.planOutPath) : defaultPlanPath;
1419
- const outPath = await ensureUniquePath(requestedOutPath);
1420
- await fs.mkdir(path.dirname(outPath), { recursive: true });
1421
- await fs.writeFile(outPath, JSON.stringify(plan, null, 2), "utf8");
1422
- await this.jobService.writeCheckpoint(job.id, {
1423
- stage: "plan_written",
1424
- timestamp: new Date().toISOString(),
1425
- details: { path: outPath, ops: plan.operations.length },
1426
- });
1427
-
1428
- if (plan.operations.length === 0) {
1429
- await this.jobService.updateJobStatus(job.id, "completed", {
1430
- payload: {
1431
- dryRun: options.dryRun ?? !applyChanges,
1432
- operations: 0,
1433
- planPath: outPath,
1434
- applied: false,
1435
- reason: "no_operations",
1436
- },
1437
- processedItems: 0,
1438
- totalItems: 0,
1439
- lastCheckpoint: "no_operations",
1440
- });
1441
- await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1442
- return {
1443
- jobId: job.id,
1444
- commandRunId: commandRun.id,
1445
- plan,
1446
- applied: false,
1447
- createdTasks: [],
1448
- updatedTasks: [],
1449
- cancelledTasks: [],
1450
- summary: { tasksProcessed: selection.groups.reduce((acc, g) => acc + g.tasks.length, 0), tasksAffected: 0, storyPointsDelta: 0 },
1451
- };
1452
- }
1453
-
1454
- if (options.dryRun || !applyChanges) {
1455
- await this.jobService.updateJobStatus(job.id, "completed", {
1456
- payload: {
1457
- dryRun: options.dryRun ?? true,
1458
- operations: plan.operations.length,
1459
- planPath: outPath,
1460
- applied: false,
1461
- },
1462
- processedItems: plan.operations.length,
1463
- totalItems: plan.operations.length,
1464
- lastCheckpoint: "dry_run",
1465
- });
1466
- await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1467
- return {
1468
- jobId: job.id,
1469
- commandRunId: commandRun.id,
1470
- plan,
1471
- applied: false,
1472
- createdTasks: [],
1473
- updatedTasks: [],
1474
- cancelledTasks: [],
1475
- summary: { tasksProcessed: selection.groups.reduce((acc, g) => acc + g.tasks.length, 0), tasksAffected: 0 },
1476
- };
1477
- }
1478
-
1479
- const created: string[] = [];
1480
- const updated: string[] = [];
1481
- const cancelled: string[] = [];
1482
- let storyPointsDelta = 0;
1483
- const operationsByStory = new Map<string, RefineOperation[]>();
1484
- for (const op of plan.operations) {
1485
- const key = (op as any).taskKey ?? (op as any).targetTaskKey ?? null;
1486
- if (!key) continue;
1487
- const group = selection.groups.find((g) => g.tasks.some((t) => t.key === key));
1488
- if (!group) continue;
1489
- const list = operationsByStory.get(group.story.id) ?? [];
1490
- list.push(op);
1491
- operationsByStory.set(group.story.id, list);
1492
- }
1493
-
1494
- for (const group of selection.groups) {
1495
- const ops = operationsByStory.get(group.story.id) ?? [];
1496
- if (ops.length === 0) continue;
1497
- const { created: c, updated: u, cancelled: x, storyPointsDelta: delta, warnings: opWarnings } = await this.applyOperations(
1498
- selection.projectId,
1499
- job.id,
1500
- commandRun.id,
1501
- group,
1502
- ops,
1503
- );
1504
- await this.jobService.writeCheckpoint(job.id, {
1505
- stage: "story_applied",
1506
- timestamp: new Date().toISOString(),
1507
- details: { epic: group.epic.key, story: group.story.key, ops: ops.length, created: c.length, updated: u.length, cancelled: x.length },
1508
- });
1509
- if (opWarnings.length) {
1510
- plan.warnings?.push(...opWarnings);
1511
- }
1512
- created.push(...c);
1513
- updated.push(...u);
1514
- cancelled.push(...x);
1515
- storyPointsDelta += delta;
1516
- }
1517
-
1518
- await this.jobService.updateJobStatus(job.id, "completed", {
1519
- payload: {
1520
- created: created.length,
1521
- updated: updated.length,
1522
- cancelled: cancelled.length,
1523
- storyPointsDelta,
1524
- },
1525
- processedItems: created.length + updated.length + cancelled.length,
1526
- totalItems: plan.operations.length,
1527
- lastCheckpoint: "completed",
1528
- });
1529
- await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1530
-
1531
- return {
1532
- jobId: job.id,
1533
- commandRunId: commandRun.id,
1534
- plan,
1535
- applied: true,
1536
- createdTasks: created,
1537
- updatedTasks: updated,
1538
- cancelledTasks: cancelled,
1539
- summary: {
1540
- tasksProcessed: selection.groups.reduce((acc, g) => acc + g.tasks.length, 0),
1541
- tasksAffected: created.length + updated.length + cancelled.length,
1542
- storyPointsDelta,
1543
- },
1544
- };
1545
- } catch (error) {
1546
- const message = (error as Error).message;
1547
- await this.jobService.updateJobStatus(job.id, "failed", { errorSummary: message });
1548
- await this.jobService.finishCommandRun(commandRun.id, "failed", message);
1549
- throw error;
1550
- }
1551
- }
1552
- }