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,1303 +0,0 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs/promises';
3
- import { TaskRow, WorkspaceRepository, TaskRunRow, TaskRunStatus, TaskQaRunRow } from '@mcoda/db';
4
- import { PathHelper } from '@mcoda/shared';
5
- import { QaProfile } from '@mcoda/shared/qa/QaProfile.js';
6
- import { WorkspaceResolution } from '../../workspace/WorkspaceManager.js';
7
- import { JobService, JobState } from '../jobs/JobService.js';
8
- import { TaskSelectionFilters, TaskSelectionPlan, TaskSelectionService } from './TaskSelectionService.js';
9
- import { TaskStateService } from './TaskStateService.js';
10
- import { QaProfileService } from './QaProfileService.js';
11
- import { QaFollowupService, FollowupSuggestion } from './QaFollowupService.js';
12
- import { QaAdapter } from '@mcoda/integrations/qa/QaAdapter.js';
13
- import { CliQaAdapter } from '@mcoda/integrations/qa/CliQaAdapter.js';
14
- import { ChromiumQaAdapter } from '@mcoda/integrations/qa/ChromiumQaAdapter.js';
15
- import { MaestroQaAdapter } from '@mcoda/integrations/qa/MaestroQaAdapter.js';
16
- import { QaContext, QaRunResult } from '@mcoda/integrations/qa/QaTypes.js';
17
- import { VcsClient } from '@mcoda/integrations';
18
- import readline from 'node:readline/promises';
19
- import { stdin as input, stdout as output } from 'node:process';
20
- import { AgentService } from '@mcoda/agents';
21
- import { GlobalRepository } from '@mcoda/db';
22
- import { DocdexClient } from '@mcoda/integrations';
23
- import { RoutingService } from '../agents/RoutingService.js';
24
- const DEFAULT_QA_PROMPT = [
25
- 'You are the QA agent. Before testing, query docdex with the task key and feature keywords (MCP `docdex_search` limit 4–8 or CLI `docdexd query --repo <repo> --query \"<term>\" --limit 6 --snippets=false`). If results look stale, reindex (`docdex_index` or `docdexd index --repo <repo>`) then re-run. Fetch snippets via `docdex_open` or `/snippet/:doc_id?text_only=true` only for specific hits.',
26
- 'Use docdex snippets to derive acceptance criteria, data contracts, edge cases, and non-functional requirements (performance, accessibility, offline/online assumptions). Note if docdex is unavailable and fall back to local docs.',
27
- ].join('\n');
28
- const DEFAULT_JOB_PROMPT = 'You are an mcoda agent that follows workspace runbooks and responds with actionable, concise output.';
29
- const DEFAULT_CHARACTER_PROMPT =
30
- 'Write clearly, avoid hallucinations, cite assumptions, and prioritize risk mitigation for the user.';
31
-
32
- export interface QaTasksRequest extends TaskSelectionFilters {
33
- workspace: WorkspaceResolution;
34
- mode?: 'auto' | 'manual';
35
- resumeJobId?: string;
36
- profileName?: string;
37
- level?: string;
38
- testCommand?: string;
39
- agentName?: string;
40
- agentStream?: boolean;
41
- createFollowupTasks?: 'auto' | 'none' | 'prompt';
42
- dryRun?: boolean;
43
- result?: 'pass' | 'fail' | 'blocked';
44
- notes?: string;
45
- evidenceUrl?: string;
46
- }
47
-
48
- export interface QaTaskResult {
49
- taskKey: string;
50
- outcome: 'pass' | 'fix_required' | 'infra_issue' | 'unclear';
51
- profile?: string;
52
- runner?: string;
53
- artifacts?: string[];
54
- followups?: string[];
55
- commentId?: string;
56
- notes?: string;
57
- }
58
-
59
- export interface QaTasksResponse {
60
- jobId: string;
61
- commandRunId: string;
62
- selection: TaskSelectionPlan;
63
- results: QaTaskResult[];
64
- warnings: string[];
65
- }
66
-
67
- const MCODA_GITIGNORE_ENTRY = '.mcoda/\n';
68
-
69
- type AgentFailure = { kind?: string; message: string; evidence?: string };
70
- type AgentFollowUp = {
71
- title?: string;
72
- description?: string;
73
- type?: string;
74
- priority?: number;
75
- story_points?: number;
76
- tags?: string[];
77
- related_task_key?: string;
78
- epic_key?: string;
79
- story_key?: string;
80
- components?: string[];
81
- doc_links?: string[];
82
- evidence_url?: string;
83
- artifacts?: string[];
84
- };
85
-
86
- type PromptBundle = { jobPrompt?: string; characterPrompt?: string; commandPrompt?: string };
87
-
88
- interface AgentInterpretation {
89
- recommendation: 'pass' | 'fix_required' | 'infra_issue' | 'unclear';
90
- testedScope?: string;
91
- coverageSummary?: string;
92
- failures?: AgentFailure[];
93
- followUps?: AgentFollowUp[];
94
- rawOutput?: string;
95
- tokensPrompt?: number;
96
- tokensCompletion?: number;
97
- agentId?: string;
98
- modelName?: string;
99
- }
100
-
101
- export class QaTasksService {
102
- private profileService: QaProfileService;
103
- private selectionService: TaskSelectionService;
104
- private stateService: TaskStateService;
105
- private followupService: QaFollowupService;
106
- private jobService: JobService;
107
- private vcs: VcsClient;
108
- private agentService?: AgentService;
109
- private docdex?: DocdexClient;
110
- private repo?: GlobalRepository;
111
- private routingService?: RoutingService;
112
- private dryRunGuard = false;
113
-
114
- constructor(
115
- private workspace: WorkspaceResolution,
116
- private deps: {
117
- workspaceRepo: WorkspaceRepository;
118
- jobService: JobService;
119
- selectionService?: TaskSelectionService;
120
- stateService?: TaskStateService;
121
- profileService?: QaProfileService;
122
- followupService?: QaFollowupService;
123
- vcsClient?: VcsClient;
124
- agentService?: AgentService;
125
- docdex?: DocdexClient;
126
- repo?: GlobalRepository;
127
- routingService?: RoutingService;
128
- },
129
- ) {
130
- this.selectionService = deps.selectionService ?? new TaskSelectionService(workspace, deps.workspaceRepo);
131
- this.stateService = deps.stateService ?? new TaskStateService(deps.workspaceRepo);
132
- this.profileService = deps.profileService ?? new QaProfileService(workspace.workspaceRoot);
133
- this.followupService = deps.followupService ?? new QaFollowupService(deps.workspaceRepo, workspace.workspaceRoot);
134
- this.jobService = deps.jobService;
135
- this.vcs = deps.vcsClient ?? new VcsClient();
136
- this.agentService = deps.agentService;
137
- this.docdex = deps.docdex;
138
- this.repo = deps.repo;
139
- this.routingService = deps.routingService;
140
- }
141
-
142
- static async create(workspace: WorkspaceResolution, options: { noTelemetry?: boolean } = {}): Promise<QaTasksService> {
143
- const repo = await GlobalRepository.create();
144
- const agentService = new AgentService(repo);
145
- const docdex = new DocdexClient({
146
- workspaceRoot: workspace.workspaceRoot,
147
- baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
148
- });
149
- const routingService = await RoutingService.create();
150
- const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
151
- const jobService = new JobService(workspace, workspaceRepo, {
152
- noTelemetry: options.noTelemetry ?? false,
153
- });
154
- const selectionService = new TaskSelectionService(workspace, workspaceRepo);
155
- const stateService = new TaskStateService(workspaceRepo);
156
- const profileService = new QaProfileService(workspace.workspaceRoot);
157
- const followupService = new QaFollowupService(workspaceRepo, workspace.workspaceRoot);
158
- const vcsClient = new VcsClient();
159
- return new QaTasksService(workspace, {
160
- workspaceRepo,
161
- jobService,
162
- selectionService,
163
- stateService,
164
- profileService,
165
- followupService,
166
- vcsClient,
167
- agentService,
168
- docdex,
169
- repo,
170
- routingService,
171
- });
172
- }
173
-
174
- async close(): Promise<void> {
175
- const maybeClose = async (target: unknown) => {
176
- try {
177
- if ((target as any)?.close) await (target as any).close();
178
- } catch {
179
- /* ignore */
180
- }
181
- };
182
- await maybeClose(this.deps.selectionService);
183
- await maybeClose(this.deps.stateService);
184
- await maybeClose(this.deps.jobService);
185
- await maybeClose(this.deps.workspaceRepo);
186
- await maybeClose(this.agentService);
187
- await maybeClose(this.repo);
188
- await maybeClose(this.docdex);
189
- await maybeClose(this.deps.routingService);
190
- }
191
-
192
- private async readPromptFiles(paths: string[]): Promise<string[]> {
193
- const contents: string[] = [];
194
- const seen = new Set<string>();
195
- for (const promptPath of paths) {
196
- try {
197
- const content = await fs.readFile(promptPath, 'utf8');
198
- const trimmed = content.trim();
199
- if (trimmed && !seen.has(trimmed)) {
200
- contents.push(trimmed);
201
- seen.add(trimmed);
202
- }
203
- } catch {
204
- /* optional prompt */
205
- }
206
- }
207
- return contents;
208
- }
209
-
210
- private async loadPrompts(agentId: string): Promise<PromptBundle> {
211
- const mcodaPromptPath = path.join(this.workspace.workspaceRoot, '.mcoda', 'prompts', 'qa-agent.md');
212
- const workspacePromptPath = path.join(this.workspace.workspaceRoot, 'prompts', 'qa-agent.md');
213
- try {
214
- await fs.mkdir(path.dirname(mcodaPromptPath), { recursive: true });
215
- await fs.access(mcodaPromptPath);
216
- console.info(`[qa-tasks] using existing QA prompt at ${mcodaPromptPath}`);
217
- } catch {
218
- try {
219
- await fs.access(workspacePromptPath);
220
- await fs.copyFile(workspacePromptPath, mcodaPromptPath);
221
- console.info(`[qa-tasks] copied QA prompt to ${mcodaPromptPath}`);
222
- } catch {
223
- console.info(`[qa-tasks] no QA prompt found at ${workspacePromptPath}; writing default prompt to ${mcodaPromptPath}`);
224
- await fs.writeFile(mcodaPromptPath, DEFAULT_QA_PROMPT, 'utf8');
225
- }
226
- }
227
- const commandPromptFiles = await this.readPromptFiles([mcodaPromptPath, workspacePromptPath]);
228
- const agentPrompts =
229
- this.agentService && 'getPrompts' in this.agentService ? await (this.agentService as any).getPrompts(agentId) : undefined;
230
- const mergedCommandPrompt = (() => {
231
- const parts = [...commandPromptFiles];
232
- if (agentPrompts?.commandPrompts?.['qa-tasks']) {
233
- parts.push(agentPrompts.commandPrompts['qa-tasks']);
234
- }
235
- if (!parts.length) parts.push(DEFAULT_QA_PROMPT);
236
- return parts.filter(Boolean).join('\n\n');
237
- })();
238
- return {
239
- jobPrompt: agentPrompts?.jobPrompt ?? DEFAULT_JOB_PROMPT,
240
- characterPrompt: agentPrompts?.characterPrompt ?? DEFAULT_CHARACTER_PROMPT,
241
- commandPrompt: mergedCommandPrompt || undefined,
242
- };
243
- }
244
-
245
- private async checkpoint(jobId: string, stage: string, details?: Record<string, unknown>): Promise<void> {
246
- await this.jobService.writeCheckpoint(jobId, {
247
- stage,
248
- timestamp: new Date().toISOString(),
249
- details,
250
- });
251
- }
252
-
253
- private async ensureTaskBranch(task: TaskSelectionPlan['ordered'][number], taskRunId: string): Promise<{ ok: boolean; message?: string }> {
254
- try {
255
- await this.vcs.ensureRepo(this.workspace.workspaceRoot);
256
- await this.vcs.ensureClean(this.workspace.workspaceRoot, true);
257
- if (task.task.vcsBranch) {
258
- const exists = await this.vcs.branchExists(this.workspace.workspaceRoot, task.task.vcsBranch);
259
- if (!exists) {
260
- return { ok: false, message: `Task branch ${task.task.vcsBranch} not found` };
261
- }
262
- await this.vcs.checkoutBranch(this.workspace.workspaceRoot, task.task.vcsBranch);
263
- } else {
264
- const base = this.workspace.config?.branch ?? 'mcoda-dev';
265
- await this.vcs.ensureBaseBranch(this.workspace.workspaceRoot, base);
266
- }
267
- return { ok: true };
268
- } catch (error: any) {
269
- await this.logTask(taskRunId, `VCS check failed: ${error?.message ?? error}`, 'vcs');
270
- return { ok: false, message: error?.message ?? String(error) };
271
- }
272
- }
273
-
274
- private async ensureMcoda(): Promise<void> {
275
- await PathHelper.ensureDir(this.workspace.mcodaDir);
276
- const gitignorePath = `${this.workspace.workspaceRoot}/.gitignore`;
277
- try {
278
- const content = await fs.readFile(gitignorePath, 'utf8');
279
- if (!content.includes('.mcoda/')) {
280
- await fs.writeFile(gitignorePath, `${content.trimEnd()}\n${MCODA_GITIGNORE_ENTRY}`, 'utf8');
281
- }
282
- } catch {
283
- await fs.writeFile(gitignorePath, MCODA_GITIGNORE_ENTRY, 'utf8');
284
- }
285
- }
286
-
287
- private adapterForProfile(profile?: QaProfile): QaAdapter | undefined {
288
- const runner = profile?.runner ?? 'cli';
289
- if (runner === 'cli') return new CliQaAdapter();
290
- if (runner === 'chromium') return new ChromiumQaAdapter();
291
- if (runner === 'maestro') return new MaestroQaAdapter();
292
- return new CliQaAdapter();
293
- }
294
-
295
- private mapOutcome(result: QaRunResult): 'pass' | 'fix_required' | 'infra_issue' {
296
- if (result.outcome === 'pass') return 'pass';
297
- if (result.outcome === 'infra_issue') return 'infra_issue';
298
- return 'fix_required';
299
- }
300
-
301
- private combineOutcome(
302
- result: QaRunResult,
303
- recommendation?: AgentInterpretation['recommendation'],
304
- ): 'pass' | 'fix_required' | 'infra_issue' | 'unclear' {
305
- const base = this.mapOutcome(result);
306
- if (!recommendation) return base;
307
- if (base === 'infra_issue' || recommendation === 'infra_issue') return 'infra_issue';
308
- if (base === 'fix_required') return 'fix_required';
309
- if (recommendation === 'fix_required') return 'fix_required';
310
- if (recommendation === 'unclear') return 'unclear';
311
- return 'pass';
312
- }
313
-
314
- private async gatherDocContext(
315
- task: TaskSelectionPlan['ordered'][number]['task'],
316
- taskRunId?: string,
317
- ): Promise<string> {
318
- if (!this.docdex) return '';
319
- try {
320
- const querySeeds = [task.key, task.title, ...(task.acceptanceCriteria ?? [])]
321
- .filter(Boolean)
322
- .join(' ')
323
- .slice(0, 200);
324
- const docs = await this.docdex.search({
325
- projectKey: task.projectId,
326
- profile: 'qa',
327
- query: querySeeds,
328
- });
329
- const snippets: string[] = [];
330
- for (const doc of docs.slice(0, 5)) {
331
- const segments = (doc.segments ?? []).slice(0, 2);
332
- const body = segments.length
333
- ? segments
334
- .map((seg, idx) => ` (${idx + 1}) ${seg.heading ? `${seg.heading}: ` : ''}${seg.content.slice(0, 400)}`)
335
- .join('\n')
336
- : doc.content
337
- ? doc.content.slice(0, 600)
338
- : '';
339
- snippets.push(`- [${doc.docType}] ${doc.title ?? doc.path ?? doc.id}\n${body}`.trim());
340
- }
341
- return snippets.join('\n\n');
342
- } catch (error: any) {
343
- if (taskRunId) {
344
- await this.logTask(taskRunId, `Docdex search failed: ${error?.message ?? error}`, 'docdex');
345
- }
346
- return '';
347
- }
348
- }
349
-
350
- private async resolveAgent(agentName?: string) {
351
- if (!this.routingService || !this.agentService) {
352
- throw new Error('RoutingService not available for QA routing');
353
- }
354
- const resolved = await this.routingService.resolveAgentForCommand({
355
- workspace: this.workspace,
356
- commandName: 'qa-tasks',
357
- overrideAgentSlug: agentName,
358
- });
359
- return resolved.agent;
360
- }
361
-
362
- private estimateTokens(text: string): number {
363
- return Math.max(1, Math.ceil((text?.length ?? 0) / 4));
364
- }
365
-
366
- private extractJsonCandidate(raw: string): any | undefined {
367
- const fenced = raw.match(/```json([\s\S]*?)```/i);
368
- const candidate = fenced ? fenced[1] : raw;
369
- const start = candidate.indexOf('{');
370
- const end = candidate.lastIndexOf('}');
371
- if (start === -1 || end === -1 || end <= start) return undefined;
372
- try {
373
- return JSON.parse(candidate.slice(start, end + 1));
374
- } catch {
375
- return undefined;
376
- }
377
- }
378
-
379
- private normalizeAgentOutput(parsed: any): AgentInterpretation | undefined {
380
- if (!parsed || typeof parsed !== 'object') return undefined;
381
- const recommendation = parsed.recommendation as AgentInterpretation['recommendation'];
382
- if (!recommendation || !['pass', 'fix_required', 'infra_issue', 'unclear'].includes(recommendation)) return undefined;
383
- const followUps: AgentFollowUp[] | undefined = Array.isArray(parsed.follow_up_tasks)
384
- ? parsed.follow_up_tasks
385
- : Array.isArray(parsed.follow_ups)
386
- ? parsed.follow_ups
387
- : undefined;
388
- const failures: AgentFailure[] | undefined = Array.isArray(parsed.failures)
389
- ? parsed.failures.map((f: any) => ({ kind: f.kind, message: f.message ?? String(f), evidence: f.evidence }))
390
- : undefined;
391
- return {
392
- recommendation,
393
- testedScope: parsed.tested_scope ?? parsed.scope,
394
- coverageSummary: parsed.coverage_summary ?? parsed.coverage,
395
- failures,
396
- followUps,
397
- };
398
- }
399
-
400
- private async interpretResult(
401
- task: TaskSelectionPlan['ordered'][number],
402
- profile: QaProfile,
403
- result: QaRunResult,
404
- agentName: string | undefined,
405
- stream: boolean,
406
- jobId: string,
407
- commandRunId: string,
408
- taskRunId?: string,
409
- ): Promise<AgentInterpretation> {
410
- if (!this.agentService) {
411
- return { recommendation: this.mapOutcome(result) };
412
- }
413
- try {
414
- const agent = await this.resolveAgent(agentName);
415
- const prompts = await this.loadPrompts(agent.id);
416
- const systemPrompt = [prompts.jobPrompt, prompts.characterPrompt, prompts.commandPrompt].filter(Boolean).join('\n\n');
417
- const docCtx = await this.gatherDocContext(task.task, taskRunId);
418
- const acceptance = (task.task.acceptanceCriteria ?? []).map((line) => `- ${line}`).join('\n');
419
- const prompt = [
420
- systemPrompt,
421
- 'You are the mcoda QA agent. Interpret the QA execution results and return structured JSON.',
422
- `Task: ${task.task.key} ${task.task.title}`,
423
- `Task type: ${task.task.type ?? 'n/a'}, status: ${task.task.status}`,
424
- task.task.description ? `Task description:\n${task.task.description}` : '',
425
- `Epic/Story: ${task.task.epicKey ?? task.task.epicId} / ${task.task.storyKey ?? task.task.userStoryId}`,
426
- acceptance ? `Acceptance criteria:\n${acceptance}` : 'Acceptance criteria: (not provided)',
427
- `QA profile: ${profile.name} (${profile.runner ?? 'cli'})`,
428
- `Test command / runner outcome: exit=${result.exitCode} outcome=${result.outcome}`,
429
- result.stdout ? `Stdout (truncated):\n${result.stdout.slice(0, 3000)}` : '',
430
- result.stderr ? `Stderr (truncated):\n${result.stderr.slice(0, 3000)}` : '',
431
- result.artifacts?.length ? `Artifacts:\n${result.artifacts.join('\n')}` : '',
432
- docCtx ? `Relevant docs (SDS/RFP/OpenAPI):\n${docCtx}` : '',
433
- [
434
- 'Return strict JSON with keys:',
435
- '{',
436
- ' "tested_scope": string,',
437
- ' "coverage_summary": string,',
438
- ' "failures": [{ "kind": "functional|contract|perf|security|infra", "message": string, "evidence": string }],',
439
- ' "recommendation": "pass|fix_required|infra_issue|unclear",',
440
- ' "follow_up_tasks": [{ "title": string, "description": string, "type": "bug|qa_followup|chore", "priority": number, "story_points": number, "tags": string[], "related_task_key": string, "epic_key": string, "story_key": string, "doc_links": string[], "evidence_url": string, "artifacts": string[] }]',
441
- '}',
442
- 'Do not include prose outside the JSON.',
443
- ].join('\n'),
444
- ]
445
- .filter(Boolean)
446
- .join('\n\n');
447
- const separator = "============================================================";
448
- console.info(separator);
449
- console.info("[qa-tasks] START OF TASK");
450
- console.info(`[qa-tasks] Task key: ${task.task.key}`);
451
- console.info(`[qa-tasks] Title: ${task.task.title ?? '(none)'}`);
452
- console.info(`[qa-tasks] Description: ${task.task.description ?? '(none)'}`);
453
- console.info(
454
- `[qa-tasks] Story points: ${typeof task.task.storyPoints === 'number' ? task.task.storyPoints : '(none)'}`,
455
- );
456
- console.info(
457
- `[qa-tasks] Dependencies: ${
458
- task.dependencies.keys.length ? task.dependencies.keys.join(', ') : '(none available)'
459
- }`,
460
- );
461
- if (acceptance) console.info(`[qa-tasks] Acceptance criteria:\n${acceptance}`);
462
- console.info(`[qa-tasks] System prompt used:\n${systemPrompt || '(none)'}`);
463
- console.info(`[qa-tasks] Task prompt used:\n${prompt}`);
464
- console.info(separator);
465
- let output = '';
466
- let chunkCount = 0;
467
- if (stream && this.agentService.invokeStream) {
468
- const gen = await this.agentService.invokeStream(agent.id, { input: prompt, metadata: { command: 'qa-tasks' } });
469
- for await (const chunk of gen) {
470
- output += chunk.output ?? '';
471
- chunkCount += 1;
472
- }
473
- } else {
474
- const res = await this.agentService.invoke(agent.id, { input: prompt, metadata: { command: 'qa-tasks' } });
475
- output = res.output ?? '';
476
- }
477
- const tokensPrompt = this.estimateTokens(prompt);
478
- const tokensCompletion = this.estimateTokens(output);
479
- if (!this.dryRunGuard) {
480
- await this.jobService.recordTokenUsage({
481
- workspaceId: this.workspace.workspaceId,
482
- agentId: agent.id,
483
- modelName: agent.defaultModel,
484
- jobId,
485
- taskId: task.task.id,
486
- commandRunId,
487
- taskRunId,
488
- tokensPrompt,
489
- tokensCompletion,
490
- tokensTotal: tokensPrompt + tokensCompletion,
491
- timestamp: new Date().toISOString(),
492
- metadata: {
493
- commandName: 'qa-tasks',
494
- action: 'qa-interpret-results',
495
- taskKey: task.task.key,
496
- streaming: stream,
497
- streamChunks: chunkCount || undefined,
498
- },
499
- });
500
- }
501
- const parsed = this.extractJsonCandidate(output);
502
- const normalized = this.normalizeAgentOutput(parsed);
503
- if (normalized) {
504
- return {
505
- ...normalized,
506
- rawOutput: output,
507
- tokensPrompt,
508
- tokensCompletion,
509
- agentId: agent.id,
510
- modelName: agent.defaultModel,
511
- };
512
- }
513
- return { recommendation: this.mapOutcome(result), rawOutput: output, tokensPrompt, tokensCompletion, agentId: agent.id, modelName: agent.defaultModel };
514
- } catch (error: any) {
515
- if (taskRunId) {
516
- await this.logTask(taskRunId, `QA agent failed: ${error?.message ?? error}`, 'qa-agent');
517
- }
518
- return { recommendation: this.mapOutcome(result) };
519
- }
520
- }
521
-
522
- private async createTaskRun(
523
- task: TaskRow & { storyPoints?: number | null },
524
- jobId: string,
525
- commandRunId: string,
526
- ): Promise<TaskRunRow> {
527
- const startedAt = new Date().toISOString();
528
- return this.deps.workspaceRepo.createTaskRun({
529
- taskId: task.id,
530
- command: 'qa-tasks',
531
- jobId,
532
- commandRunId,
533
- status: 'running',
534
- startedAt,
535
- storyPointsAtRun: task.storyPoints ?? null,
536
- gitBranch: task.vcsBranch ?? null,
537
- gitBaseBranch: task.vcsBaseBranch ?? null,
538
- gitCommitSha: task.vcsLastCommitSha ?? null,
539
- });
540
- }
541
-
542
- private async finishTaskRun(taskRun: TaskRunRow, status: TaskRunStatus, extra?: Partial<TaskRunRow>): Promise<void> {
543
- await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
544
- status,
545
- finishedAt: new Date().toISOString(),
546
- gitBranch: extra?.gitBranch ?? taskRun.gitBranch,
547
- gitBaseBranch: extra?.gitBaseBranch ?? taskRun.gitBaseBranch,
548
- gitCommitSha: extra?.gitCommitSha ?? taskRun.gitCommitSha,
549
- spPerHourEffective: extra?.spPerHourEffective ?? null,
550
- });
551
- }
552
-
553
- private async logTask(taskRunId: string, message: string, source?: string, details?: Record<string, unknown>): Promise<void> {
554
- await this.deps.workspaceRepo.insertTaskLog({
555
- taskRunId,
556
- sequence: Math.floor(Math.random() * 1000000),
557
- timestamp: new Date().toISOString(),
558
- source: source ?? 'qa-tasks',
559
- message,
560
- details: details ?? undefined,
561
- });
562
- }
563
-
564
- private async applyStateTransition(
565
- task: TaskRow,
566
- outcome: 'pass' | 'fix_required' | 'infra_issue' | 'unclear',
567
- ): Promise<void> {
568
- const timestamp = { last_qa: new Date().toISOString() };
569
- if (outcome === 'pass') {
570
- await this.stateService.markCompleted(task, timestamp);
571
- } else if (outcome === 'fix_required') {
572
- await this.stateService.returnToInProgress(task, timestamp);
573
- } else if (outcome === 'infra_issue') {
574
- await this.stateService.markBlocked(task, 'qa_infra_issue');
575
- }
576
- }
577
-
578
- private buildFollowupSuggestion(task: TaskRow, result: QaRunResult, notes?: string): FollowupSuggestion {
579
- const summary = notes || result.stderr || result.stdout || 'QA failure detected';
580
- const components = Array.isArray((task.metadata as any)?.components) ? (task.metadata as any).components : [];
581
- const docLinks = Array.isArray((task.metadata as any)?.doc_links) ? (task.metadata as any).doc_links : [];
582
- const tests = Array.isArray((task.metadata as any)?.tests) ? (task.metadata as any).tests : [];
583
- return {
584
- title: `QA follow-up for ${task.key}`,
585
- description: `Follow-up created from QA run on ${task.key}.\n\nDetails:\n${summary}`.slice(0, 2000),
586
- type: 'bug',
587
- storyPoints: 1,
588
- priority: 90,
589
- tags: ['qa', 'qa-followup', ...components],
590
- components,
591
- docLinks,
592
- testName: tests[0],
593
- };
594
- }
595
-
596
- private toFollowupSuggestion(
597
- task: TaskRow & { storyKey?: string; epicKey?: string },
598
- agentFollow: AgentFollowUp,
599
- artifacts: string[],
600
- ): FollowupSuggestion {
601
- const taskComponents = Array.isArray((task.metadata as any)?.components) ? (task.metadata as any).components : [];
602
- const taskDocLinks = Array.isArray((task.metadata as any)?.doc_links) ? (task.metadata as any).doc_links : [];
603
- return {
604
- title: agentFollow.title ?? `QA follow-up for ${task.key}`,
605
- description: agentFollow.description,
606
- type: agentFollow.type ?? 'bug',
607
- priority: agentFollow.priority ?? 90,
608
- storyPoints: agentFollow.story_points ?? 1,
609
- tags: agentFollow.tags,
610
- relatedTaskKey: agentFollow.related_task_key,
611
- epicKeyHint: agentFollow.epic_key,
612
- storyKeyHint: agentFollow.story_key,
613
- components: agentFollow.components ?? taskComponents,
614
- docLinks: agentFollow.doc_links ?? taskDocLinks,
615
- evidenceUrl: agentFollow.evidence_url,
616
- artifacts: agentFollow.artifacts ?? artifacts,
617
- };
618
- }
619
-
620
- private async suggestFollowupsFromAgent(
621
- task: TaskSelectionPlan['ordered'][number],
622
- notes: string | undefined,
623
- evidenceUrl: string | undefined,
624
- mode: 'auto' | 'manual',
625
- jobId: string,
626
- commandRunId: string,
627
- taskRunId?: string,
628
- agentStream = true,
629
- ): Promise<FollowupSuggestion[]> {
630
- if (!this.agentService) return [];
631
- const agent = await this.resolveAgent(undefined);
632
- const prompts = await this.loadPrompts(agent.id);
633
- const systemPrompt = [prompts.jobPrompt, prompts.characterPrompt, prompts.commandPrompt].filter(Boolean).join('\n\n');
634
- const docCtx = await this.gatherDocContext(task.task, taskRunId);
635
- const prompt = [
636
- systemPrompt,
637
- 'You are the mcoda QA agent. Given QA notes/evidence, propose structured follow-up tasks as JSON.',
638
- `Task: ${task.task.key} ${task.task.title}`,
639
- task.task.description ? `Task description:\n${task.task.description}` : '',
640
- notes ? `QA notes:\n${notes}` : '',
641
- evidenceUrl ? `Evidence URL: ${evidenceUrl}` : '',
642
- docCtx ? `Relevant docs:\n${docCtx}` : '',
643
- [
644
- 'Return JSON: { "follow_up_tasks": [ { "title": "...", "description": "...", "type": "bug|qa_followup|chore", "priority": number, "story_points": number, "tags": [], "related_task_key": string, "epic_key": string, "story_key": string, "doc_links": [], "evidence_url": string } ] }',
645
- 'No prose outside JSON.',
646
- ].join('\n'),
647
- ]
648
- .filter(Boolean)
649
- .join('\n\n');
650
- let output = '';
651
- let chunkCount = 0;
652
- const useStream = agentStream && Boolean(this.agentService?.invokeStream);
653
- try {
654
- if (useStream && this.agentService.invokeStream) {
655
- const gen = await this.agentService.invokeStream(agent.id, { input: prompt, metadata: { command: 'qa-tasks' } });
656
- for await (const chunk of gen) {
657
- output += chunk.output ?? '';
658
- chunkCount += 1;
659
- }
660
- } else {
661
- const res = await this.agentService.invoke(agent.id, { input: prompt, metadata: { command: 'qa-tasks' } });
662
- output = res.output ?? '';
663
- }
664
- } catch {
665
- return [];
666
- }
667
- const tokensPrompt = this.estimateTokens(prompt);
668
- const tokensCompletion = this.estimateTokens(output);
669
- if (!this.dryRunGuard) {
670
- await this.jobService.recordTokenUsage({
671
- workspaceId: this.workspace.workspaceId,
672
- agentId: agent.id,
673
- modelName: agent.defaultModel,
674
- jobId,
675
- taskId: task.task.id,
676
- commandRunId,
677
- taskRunId,
678
- tokensPrompt,
679
- tokensCompletion,
680
- tokensTotal: tokensPrompt + tokensCompletion,
681
- timestamp: new Date().toISOString(),
682
- metadata: {
683
- commandName: 'qa-tasks',
684
- action: 'qa-manual-followups',
685
- taskKey: task.task.key,
686
- streaming: useStream || undefined,
687
- streamChunks: chunkCount || undefined,
688
- },
689
- });
690
- }
691
- const parsed = this.extractJsonCandidate(output);
692
- const followUps: AgentFollowUp[] = Array.isArray(parsed?.follow_up_tasks)
693
- ? parsed.follow_up_tasks
694
- : Array.isArray(parsed?.followUps)
695
- ? parsed.followUps
696
- : [];
697
- return followUps.map((f) => this.toFollowupSuggestion(task.task, f, []));
698
- }
699
-
700
- private async runAuto(
701
- task: TaskSelectionPlan['ordered'][number],
702
- ctx: {
703
- jobId: string;
704
- commandRunId: string;
705
- request: QaTasksRequest;
706
- },
707
- ): Promise<QaTaskResult> {
708
- const taskRun = await this.createTaskRun(task.task, ctx.jobId, ctx.commandRunId);
709
- await this.logTask(taskRun.id, 'Starting QA', 'qa-start');
710
- const allowedStatuses = new Set(ctx.request.statusFilter ?? ['ready_to_qa']);
711
- if (task.task.status && !allowedStatuses.has(task.task.status)) {
712
- const message = `Task status ${task.task.status} not allowed for QA`;
713
- await this.logTask(taskRun.id, message, 'status-gate');
714
- await this.finishTaskRun(taskRun, 'failed');
715
- if (!this.dryRunGuard) {
716
- await this.deps.workspaceRepo.createTaskQaRun({
717
- taskId: task.task.id,
718
- taskRunId: taskRun.id,
719
- jobId: ctx.jobId,
720
- commandRunId: ctx.commandRunId,
721
- source: 'auto',
722
- mode: 'auto',
723
- rawOutcome: 'infra_issue',
724
- recommendation: 'infra_issue',
725
- profileName: undefined,
726
- runner: undefined,
727
- metadata: { reason: 'status_gating' },
728
- });
729
- }
730
- return { taskKey: task.task.key, outcome: 'infra_issue', notes: 'status_gating' };
731
- }
732
-
733
- const branchCheck = await this.ensureTaskBranch(task, taskRun.id);
734
- if (!branchCheck.ok) {
735
- if (!this.dryRunGuard) {
736
- await this.applyStateTransition(task.task, 'infra_issue');
737
- await this.finishTaskRun(taskRun, 'failed');
738
- await this.deps.workspaceRepo.createTaskQaRun({
739
- taskId: task.task.id,
740
- taskRunId: taskRun.id,
741
- jobId: ctx.jobId,
742
- commandRunId: ctx.commandRunId,
743
- source: 'auto',
744
- mode: 'auto',
745
- rawOutcome: 'infra_issue',
746
- recommendation: 'infra_issue',
747
- metadata: { reason: 'vcs_branch_missing', detail: branchCheck.message },
748
- });
749
- await this.deps.workspaceRepo.createTaskComment({
750
- taskId: task.task.id,
751
- taskRunId: taskRun.id,
752
- jobId: ctx.jobId,
753
- sourceCommand: 'qa-tasks',
754
- authorType: 'agent',
755
- category: 'qa_issue',
756
- body: `VCS validation failed: ${branchCheck.message ?? 'unknown error'}`,
757
- createdAt: new Date().toISOString(),
758
- });
759
- }
760
- return { taskKey: task.task.key, outcome: 'infra_issue', notes: 'vcs_branch_missing' };
761
- }
762
- let profile: QaProfile | undefined;
763
- try {
764
- profile = await this.profileService.resolveProfileForTask(task.task, {
765
- profileName: ctx.request.profileName,
766
- level: ctx.request.level,
767
- });
768
- } catch (error: any) {
769
- await this.logTask(taskRun.id, `Profile resolution failed: ${error?.message ?? error}`, 'qa-profile');
770
- await this.finishTaskRun(taskRun, 'failed');
771
- if (!this.dryRunGuard) {
772
- await this.deps.workspaceRepo.createTaskQaRun({
773
- taskId: task.task.id,
774
- taskRunId: taskRun.id,
775
- jobId: ctx.jobId,
776
- commandRunId: ctx.commandRunId,
777
- source: 'auto',
778
- mode: 'auto',
779
- rawOutcome: 'infra_issue',
780
- recommendation: 'infra_issue',
781
- metadata: { reason: 'profile_resolution_failed', message: error?.message ?? String(error) },
782
- });
783
- }
784
- return { taskKey: task.task.key, outcome: 'infra_issue', notes: 'profile_resolution_failed' };
785
- }
786
- if (!profile) {
787
- await this.logTask(taskRun.id, 'No QA profile available', 'qa-profile');
788
- await this.finishTaskRun(taskRun, 'failed');
789
- if (!this.dryRunGuard) {
790
- await this.deps.workspaceRepo.createTaskQaRun({
791
- taskId: task.task.id,
792
- taskRunId: taskRun.id,
793
- jobId: ctx.jobId,
794
- commandRunId: ctx.commandRunId,
795
- source: 'auto',
796
- mode: 'auto',
797
- rawOutcome: 'infra_issue',
798
- recommendation: 'infra_issue',
799
- metadata: { reason: 'no_profile' },
800
- });
801
- }
802
- return { taskKey: task.task.key, outcome: 'infra_issue', notes: 'no_profile' };
803
- }
804
- const adapter = this.adapterForProfile(profile);
805
- if (!adapter) {
806
- await this.logTask(taskRun.id, 'No QA adapter for profile', 'qa-adapter');
807
- await this.finishTaskRun(taskRun, 'failed');
808
- if (!this.dryRunGuard) {
809
- await this.deps.workspaceRepo.createTaskQaRun({
810
- taskId: task.task.id,
811
- taskRunId: taskRun.id,
812
- jobId: ctx.jobId,
813
- commandRunId: ctx.commandRunId,
814
- source: 'auto',
815
- mode: 'auto',
816
- profileName: profile.name,
817
- runner: profile.runner,
818
- rawOutcome: 'infra_issue',
819
- recommendation: 'infra_issue',
820
- metadata: { reason: 'no_adapter' },
821
- });
822
- }
823
- return { taskKey: task.task.key, outcome: 'infra_issue', profile: profile.name, runner: profile.runner, notes: 'no_adapter' };
824
- }
825
-
826
- const qaCtx: QaContext = {
827
- workspaceRoot: this.workspace.workspaceRoot,
828
- jobId: ctx.jobId,
829
- taskKey: task.task.key,
830
- env: process.env,
831
- testCommandOverride: ctx.request.testCommand,
832
- };
833
-
834
- const ensure = await adapter.ensureInstalled(profile, qaCtx);
835
- if (!ensure.ok) {
836
- await this.logTask(taskRun.id, ensure.message ?? 'QA install failed', 'qa-install');
837
- if (!this.dryRunGuard) {
838
- await this.applyStateTransition(task.task, 'infra_issue');
839
- await this.finishTaskRun(taskRun, 'failed');
840
- await this.deps.workspaceRepo.createTaskQaRun({
841
- taskId: task.task.id,
842
- taskRunId: taskRun.id,
843
- jobId: ctx.jobId,
844
- commandRunId: ctx.commandRunId,
845
- source: 'auto',
846
- mode: 'auto',
847
- profileName: profile.name,
848
- runner: profile.runner,
849
- rawOutcome: 'infra_issue',
850
- recommendation: 'infra_issue',
851
- metadata: { install: ensure.message, adapter: profile.runner },
852
- });
853
- }
854
- return {
855
- taskKey: task.task.key,
856
- outcome: 'infra_issue',
857
- profile: profile.name,
858
- runner: profile.runner,
859
- notes: ensure.message,
860
- };
861
- }
862
-
863
- const artifactDir = path.join(this.workspace.workspaceRoot, '.mcoda', 'jobs', ctx.jobId, 'qa', task.task.key);
864
- await PathHelper.ensureDir(artifactDir);
865
- const result = await adapter.invoke(profile, { ...qaCtx, artifactDir });
866
- await this.logTask(taskRun.id, `QA run completed with outcome ${result.outcome}`, 'qa-exec', {
867
- exitCode: result.exitCode,
868
- });
869
- const interpretation = await this.interpretResult(
870
- task,
871
- profile,
872
- result,
873
- ctx.request.agentName,
874
- ctx.request.agentStream ?? true,
875
- ctx.jobId,
876
- ctx.commandRunId,
877
- taskRun.id,
878
- );
879
- const outcome = this.combineOutcome(result, interpretation.recommendation);
880
- const artifacts = result.artifacts ?? [];
881
-
882
- let qaRun: TaskQaRunRow | undefined;
883
- if (!this.dryRunGuard) {
884
- qaRun = await this.deps.workspaceRepo.createTaskQaRun({
885
- taskId: task.task.id,
886
- taskRunId: taskRun.id,
887
- jobId: ctx.jobId,
888
- commandRunId: ctx.commandRunId,
889
- agentId: interpretation.agentId,
890
- modelName: interpretation.modelName,
891
- source: 'auto',
892
- mode: 'auto',
893
- profileName: profile.name,
894
- runner: profile.runner,
895
- rawOutcome: result.outcome,
896
- recommendation: interpretation.recommendation,
897
- artifacts,
898
- rawResult: {
899
- adapter: result,
900
- agent: interpretation.rawOutput,
901
- },
902
- startedAt: result.startedAt,
903
- finishedAt: result.finishedAt,
904
- metadata: {
905
- tokensPrompt: interpretation.tokensPrompt,
906
- tokensCompletion: interpretation.tokensCompletion,
907
- testedScope: interpretation.testedScope,
908
- coverageSummary: interpretation.coverageSummary,
909
- failures: interpretation.failures,
910
- },
911
- });
912
- }
913
-
914
- if (!this.dryRunGuard) {
915
- await this.applyStateTransition(task.task, outcome);
916
- await this.finishTaskRun(taskRun, outcome === 'pass' ? 'succeeded' : 'failed');
917
- }
918
-
919
- const followups: string[] = [];
920
- if (outcome === 'fix_required' && ctx.request.createFollowupTasks !== 'none') {
921
- const suggestions: FollowupSuggestion[] = interpretation.followUps?.map((f) => this.toFollowupSuggestion(task.task, f, artifacts)) ?? [];
922
- if (suggestions.length === 0) {
923
- suggestions.push(this.buildFollowupSuggestion(task.task, result, ctx.request.notes));
924
- }
925
- const interactive = ctx.request.createFollowupTasks === 'prompt' && process.stdout.isTTY;
926
- for (const suggestion of suggestions) {
927
- let proceed = ctx.request.createFollowupTasks !== 'prompt';
928
- if (interactive) {
929
- const rl = readline.createInterface({ input, output });
930
- const answer = await rl.question(`Create follow-up task "${suggestion.title}" for ${task.task.key}? [y/N]: `);
931
- rl.close();
932
- proceed = answer.trim().toLowerCase().startsWith('y');
933
- }
934
- if (!proceed) continue;
935
- try {
936
- if (!this.dryRunGuard) {
937
- const created = await this.followupService.createFollowupTask({ ...task.task, storyKey: task.task.storyKey, epicKey: task.task.epicKey }, suggestion);
938
- followups.push(created.task.key);
939
- await this.logTask(taskRun.id, `Created follow-up ${created.task.key}`, 'qa-followup');
940
- }
941
- } catch (error: any) {
942
- await this.logTask(taskRun.id, `Failed to create follow-up task: ${error?.message ?? error}`, 'qa-followup');
943
- }
944
- }
945
- }
946
-
947
- const bodyLines = [
948
- `QA outcome: ${outcome}`,
949
- profile ? `Profile: ${profile.name} (${profile.runner ?? 'cli'})` : '',
950
- interpretation.coverageSummary ? `Coverage: ${interpretation.coverageSummary}` : '',
951
- interpretation.failures && interpretation.failures.length
952
- ? `Failures:\n${interpretation.failures.map((f) => `- [${f.kind ?? 'issue'}] ${f.message}${f.evidence ? ` (${f.evidence})` : ''}`).join('\n')}`
953
- : '',
954
- result.stdout ? `Stdout:\n${result.stdout.slice(0, 4000)}` : '',
955
- result.stderr ? `Stderr:\n${result.stderr.slice(0, 4000)}` : '',
956
- artifacts.length ? `Artifacts:\n${artifacts.join('\n')}` : '',
957
- followups.length ? `Follow-ups: ${followups.join(', ')}` : '',
958
- ].filter(Boolean);
959
- if (!this.dryRunGuard) {
960
- await this.deps.workspaceRepo.createTaskComment({
961
- taskId: task.task.id,
962
- taskRunId: taskRun.id,
963
- jobId: ctx.jobId,
964
- sourceCommand: 'qa-tasks',
965
- authorType: 'agent',
966
- category: outcome === 'pass' ? 'qa_result' : 'qa_issue',
967
- body: bodyLines.join('\n\n'),
968
- createdAt: new Date().toISOString(),
969
- metadata: {
970
- ...(artifacts.length ? { artifacts } : {}),
971
- ...(qaRun?.id ? { qaRunId: qaRun.id } : {}),
972
- },
973
- });
974
- }
975
-
976
- return {
977
- taskKey: task.task.key,
978
- outcome,
979
- profile: profile.name,
980
- runner: profile.runner,
981
- artifacts,
982
- followups,
983
- };
984
- }
985
-
986
- private async runManual(
987
- task: TaskSelectionPlan['ordered'][number],
988
- ctx: {
989
- jobId: string;
990
- commandRunId: string;
991
- request: QaTasksRequest;
992
- },
993
- ): Promise<QaTaskResult> {
994
- const taskRun = await this.createTaskRun(task.task, ctx.jobId, ctx.commandRunId);
995
- const result = ctx.request.result ?? 'pass';
996
- const notes = ctx.request.notes;
997
- const outcome: 'pass' | 'fix_required' | 'infra_issue' =
998
- result === 'pass' ? 'pass' : result === 'blocked' ? 'infra_issue' : 'fix_required';
999
- const allowedStatuses = new Set(ctx.request.statusFilter ?? ['ready_to_qa']);
1000
- if (task.task.status && !allowedStatuses.has(task.task.status)) {
1001
- const message = `Task status ${task.task.status} not allowed for manual QA`;
1002
- await this.logTask(taskRun.id, message, 'status-gate');
1003
- await this.finishTaskRun(taskRun, 'failed');
1004
- return { taskKey: task.task.key, outcome: 'infra_issue', notes: 'status_gating' };
1005
- }
1006
-
1007
- if (!ctx.request.dryRun) {
1008
- await this.applyStateTransition(task.task, outcome);
1009
- await this.finishTaskRun(taskRun, outcome === 'pass' ? 'succeeded' : 'failed');
1010
- }
1011
- const followups: string[] = [];
1012
- const artifacts: string[] = [];
1013
- if (!ctx.request.dryRun) {
1014
- await this.deps.workspaceRepo.createTaskQaRun({
1015
- taskId: task.task.id,
1016
- taskRunId: taskRun.id,
1017
- jobId: ctx.jobId,
1018
- commandRunId: ctx.commandRunId,
1019
- source: 'manual',
1020
- mode: 'manual',
1021
- rawOutcome: result,
1022
- recommendation: outcome,
1023
- evidenceUrl: ctx.request.evidenceUrl,
1024
- artifacts,
1025
- rawResult: { notes },
1026
- metadata: { notes, evidenceUrl: ctx.request.evidenceUrl },
1027
- });
1028
- }
1029
- if (!ctx.request.dryRun && ctx.request.createFollowupTasks !== 'none' && outcome === 'fix_required') {
1030
- const suggestions: FollowupSuggestion[] = [
1031
- {
1032
- title: `Manual QA follow-up for ${task.task.key}`,
1033
- description: notes ?? 'Manual QA reported failure. Please investigate.',
1034
- type: 'bug',
1035
- storyPoints: 1,
1036
- priority: 90,
1037
- tags: ['qa', 'manual'],
1038
- evidenceUrl: ctx.request.evidenceUrl,
1039
- },
1040
- ];
1041
- const agentSuggestions = await this.suggestFollowupsFromAgent(
1042
- task,
1043
- notes,
1044
- ctx.request.evidenceUrl,
1045
- 'manual',
1046
- ctx.jobId,
1047
- ctx.commandRunId,
1048
- taskRun.id,
1049
- );
1050
- if (agentSuggestions.length) {
1051
- suggestions.unshift(...agentSuggestions);
1052
- }
1053
- const interactive = ctx.request.createFollowupTasks === 'prompt' && process.stdout.isTTY;
1054
- for (const suggestion of suggestions) {
1055
- let proceed = ctx.request.createFollowupTasks === 'auto' || ctx.request.createFollowupTasks === undefined;
1056
- if (interactive) {
1057
- const rl = readline.createInterface({ input, output });
1058
- const answer = await rl.question(`Create follow-up task "${suggestion.title}" for ${task.task.key}? [y/N]: `);
1059
- rl.close();
1060
- proceed = answer.trim().toLowerCase().startsWith('y');
1061
- }
1062
- if (!proceed) continue;
1063
- try {
1064
- const created = await this.followupService.createFollowupTask(
1065
- { ...task.task, storyKey: task.task.storyKey, epicKey: task.task.epicKey },
1066
- suggestion,
1067
- );
1068
- followups.push(created.task.key);
1069
- } catch (error: any) {
1070
- await this.logTask(taskRun.id, `Follow-up creation failed: ${error?.message ?? error}`, 'qa-followup');
1071
- }
1072
- }
1073
- }
1074
-
1075
- const body = [
1076
- `Manual QA outcome: ${result}`,
1077
- notes ? `Notes: ${notes}` : '',
1078
- ctx.request.evidenceUrl ? `Evidence: ${ctx.request.evidenceUrl}` : '',
1079
- artifacts.length ? `Artifacts:\n${artifacts.join('\n')}` : '',
1080
- followups.length ? `Follow-ups: ${followups.join(', ')}` : '',
1081
- ]
1082
- .filter(Boolean)
1083
- .join('\n');
1084
- if (!ctx.request.dryRun) {
1085
- await this.deps.workspaceRepo.createTaskComment({
1086
- taskId: task.task.id,
1087
- taskRunId: taskRun.id,
1088
- jobId: ctx.jobId,
1089
- sourceCommand: 'qa-tasks',
1090
- authorType: 'human',
1091
- category: result === 'pass' ? 'qa_result' : 'qa_issue',
1092
- body,
1093
- createdAt: new Date().toISOString(),
1094
- metadata: {
1095
- ...(ctx.request.evidenceUrl ? { evidence: ctx.request.evidenceUrl } : {}),
1096
- ...(artifacts.length ? { artifacts } : {}),
1097
- },
1098
- });
1099
- }
1100
-
1101
- return {
1102
- taskKey: task.task.key,
1103
- outcome,
1104
- artifacts,
1105
- followups,
1106
- notes,
1107
- };
1108
- }
1109
-
1110
- async run(request: QaTasksRequest): Promise<QaTasksResponse> {
1111
- const resume = request.resumeJobId ? await this.deps.jobService.getJob(request.resumeJobId) : undefined;
1112
- if (request.resumeJobId && !resume) {
1113
- throw new Error(`Resume requested but job ${request.resumeJobId} not found`);
1114
- }
1115
- const effectiveProject = request.projectKey ?? (resume?.payload as any)?.projectKey;
1116
- const effectiveEpic = request.epicKey ?? (resume?.payload as any)?.epicKey;
1117
- const effectiveStory = request.storyKey ?? (resume?.payload as any)?.storyKey;
1118
- const effectiveTasks = request.taskKeys?.length ? request.taskKeys : (resume?.payload as any)?.tasks;
1119
- const effectiveStatus = request.statusFilter ?? (resume?.payload as any)?.statusFilter ?? ['ready_to_qa'];
1120
-
1121
- const selection = await this.selectionService.selectTasks({
1122
- projectKey: effectiveProject,
1123
- epicKey: effectiveEpic,
1124
- storyKey: effectiveStory,
1125
- taskKeys: effectiveTasks,
1126
- statusFilter: effectiveStatus,
1127
- });
1128
-
1129
- this.dryRunGuard = request.dryRun ?? false;
1130
- if (request.dryRun) {
1131
- const dryResults: QaTaskResult[] = [];
1132
- for (const task of selection.ordered) {
1133
- let profile: QaProfile | undefined;
1134
- try {
1135
- profile = await this.profileService.resolveProfileForTask(task.task, {
1136
- profileName: request.profileName,
1137
- level: request.level,
1138
- });
1139
- } catch {
1140
- profile = undefined;
1141
- }
1142
- dryResults.push({
1143
- taskKey: task.task.key,
1144
- outcome: profile ? 'unclear' : 'infra_issue',
1145
- profile: profile?.name,
1146
- runner: profile?.runner,
1147
- notes: profile ? 'Dry-run: QA planned' : 'Dry-run: no profile available',
1148
- });
1149
- }
1150
- return {
1151
- jobId: 'dry-run',
1152
- commandRunId: 'dry-run',
1153
- selection,
1154
- results: dryResults,
1155
- warnings: selection.warnings,
1156
- };
1157
- }
1158
-
1159
- await this.ensureMcoda();
1160
-
1161
- const completedKeys = new Set<string>();
1162
- const checkpoints = request.resumeJobId ? await this.deps.jobService.readCheckpoints(request.resumeJobId) : [];
1163
- const priorResults = new Map<string, QaTaskResult>();
1164
- for (const ckpt of checkpoints) {
1165
- if (ckpt.stage?.startsWith('task:')) {
1166
- const parts = ckpt.stage.split(':');
1167
- if (parts[1]) completedKeys.add(parts[1]);
1168
- }
1169
- if (Array.isArray(ckpt.details?.completedTaskKeys)) {
1170
- for (const key of ckpt.details.completedTaskKeys as string[]) {
1171
- completedKeys.add(key);
1172
- }
1173
- }
1174
- if (ckpt.details?.taskResult && (ckpt.details as any).taskResult.taskKey) {
1175
- priorResults.set((ckpt.details as any).taskResult.taskKey, (ckpt.details as any).taskResult as QaTaskResult);
1176
- }
1177
- }
1178
- const commandRun = await this.deps.jobService.startCommandRun('qa-tasks', effectiveProject, {
1179
- taskIds: selection.ordered.map((t) => t.task.key),
1180
- jobId: resume?.id,
1181
- });
1182
- const agentStream = request.agentStream !== false;
1183
- const job =
1184
- resume && resume.id
1185
- ? resume
1186
- : await this.deps.jobService.startJob('qa', commandRun.id, effectiveProject, {
1187
- commandName: 'qa-tasks',
1188
- payload: {
1189
- projectKey: effectiveProject,
1190
- epicKey: effectiveEpic,
1191
- storyKey: effectiveStory,
1192
- tasks: effectiveTasks,
1193
- statusFilter: effectiveStatus,
1194
- mode: request.mode ?? 'auto',
1195
- profile: request.profileName,
1196
- level: request.level,
1197
- agent: request.agentName,
1198
- agentStream,
1199
- createFollowups: request.createFollowupTasks ?? 'auto',
1200
- dryRun: request.dryRun ?? false,
1201
- },
1202
- totalItems: selection.ordered.length,
1203
- processedItems: completedKeys.size,
1204
- });
1205
- if (resume?.id) {
1206
- try {
1207
- const qaRuns = await this.deps.workspaceRepo.listTaskQaRunsForJob(
1208
- selection.ordered.map((t) => t.task.id),
1209
- resume.id,
1210
- );
1211
- for (const run of qaRuns) {
1212
- const task = selection.ordered.find((t) => t.task.id === run.taskId);
1213
- if (task && run.recommendation) {
1214
- completedKeys.add(task.task.key);
1215
- }
1216
- }
1217
- } catch {
1218
- // ignore resume enrichment failures
1219
- }
1220
- }
1221
- const remaining = selection.ordered.filter((t) => !completedKeys.has(t.task.key));
1222
-
1223
- // Skip tasks that are already in a terminal QA state for this job (ready_to_qa -> completed/in_progress/blocked)
1224
- const terminalStatuses = new Set(['completed', 'in_progress', 'blocked']);
1225
- const skippedTerminal: QaTaskResult[] = [];
1226
- for (const t of remaining) {
1227
- if (terminalStatuses.has(t.task.status?.toLowerCase?.() ?? '')) {
1228
- completedKeys.add(t.task.key);
1229
- skippedTerminal.push({
1230
- taskKey: t.task.key,
1231
- outcome: 'pass',
1232
- notes: `skipped (terminal status ${t.task.status})`,
1233
- });
1234
- }
1235
- }
1236
- const filteredRemaining = remaining.filter((t) => !terminalStatuses.has(t.task.status?.toLowerCase?.() ?? ''));
1237
-
1238
- await this.deps.jobService.updateJobStatus(job.id, 'running', {
1239
- totalItems: selection.ordered.length,
1240
- processedItems: completedKeys.size,
1241
- });
1242
-
1243
- await this.checkpoint(job.id, 'selection', {
1244
- ordered: selection.ordered.map((t) => t.task.key),
1245
- blocked: selection.blocked.map((t) => t.task.key),
1246
- completedTaskKeys: Array.from(completedKeys),
1247
- });
1248
-
1249
- const results: QaTaskResult[] = [];
1250
- for (const task of selection.ordered) {
1251
- if (completedKeys.has(task.task.key)) {
1252
- results.push(
1253
- priorResults.get(task.task.key) ?? { taskKey: task.task.key, outcome: 'pass', notes: 'skipped (resume)' },
1254
- );
1255
- }
1256
- }
1257
- results.push(...skippedTerminal);
1258
- try {
1259
- let processedCount = completedKeys.size;
1260
- for (const [index, task] of filteredRemaining.entries()) {
1261
- const mode = request.mode ?? 'auto';
1262
- if (mode === 'manual') {
1263
- results.push(await this.runManual(task, { jobId: job.id, commandRunId: commandRun.id, request }));
1264
- } else {
1265
- results.push(await this.runAuto(task, { jobId: job.id, commandRunId: commandRun.id, request }));
1266
- }
1267
- completedKeys.add(task.task.key);
1268
- processedCount = completedKeys.size;
1269
- await this.deps.jobService.updateJobStatus(job.id, 'running', { processedItems: processedCount });
1270
- await this.checkpoint(job.id, `task:${task.task.key}:completed`, {
1271
- processed: processedCount,
1272
- completedTaskKeys: Array.from(completedKeys),
1273
- taskResult: results[results.length - 1],
1274
- });
1275
- }
1276
- const failureCount = results.filter((r) => r.outcome !== 'pass').length;
1277
- const state: JobState =
1278
- failureCount === 0 ? 'completed' : failureCount === results.length ? 'failed' : ('partial' as JobState);
1279
- const errorSummary = failureCount ? `${failureCount} task(s) not passed QA` : undefined;
1280
- await this.deps.jobService.updateJobStatus(job.id, state, { errorSummary });
1281
- await this.deps.jobService.finishCommandRun(commandRun.id, state === 'completed' ? 'succeeded' : 'failed', errorSummary);
1282
- await this.checkpoint(job.id, 'completed', {
1283
- state,
1284
- processed: results.length,
1285
- failures: failureCount,
1286
- taskResults: results,
1287
- });
1288
- } catch (error: any) {
1289
- const message = error instanceof Error ? error.message : String(error);
1290
- await this.deps.jobService.updateJobStatus(job.id, 'failed', { errorSummary: message });
1291
- await this.deps.jobService.finishCommandRun(commandRun.id, 'failed', message);
1292
- throw error;
1293
- }
1294
-
1295
- return {
1296
- jobId: job.id,
1297
- commandRunId: commandRun.id,
1298
- selection,
1299
- results,
1300
- warnings: selection.warnings,
1301
- };
1302
- }
1303
- }