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,1386 +0,0 @@
1
- import path from "node:path";
2
- import fs from "node:fs/promises";
3
- import { AgentService } from "@mcoda/agents";
4
- import { DocdexClient, VcsClient } from "@mcoda/integrations";
5
- import { GlobalRepository, WorkspaceRepository, type EpicRow, type StoryRow, type TaskInsert, type TaskRow } from "@mcoda/db";
6
- import { PathHelper } from "@mcoda/shared";
7
- import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
8
- import { JobService } from "../jobs/JobService.js";
9
- import { TaskSelectionFilters, TaskSelectionService } from "../execution/TaskSelectionService.js";
10
- import { TaskStateService } from "../execution/TaskStateService.js";
11
- import { BacklogService } from "../backlog/BacklogService.js";
12
- import yaml from "yaml";
13
- import { createTaskKeyGenerator } from "../planning/KeyHelpers.js";
14
- import { RoutingService } from "../agents/RoutingService.js";
15
-
16
- const DEFAULT_BASE_BRANCH = "mcoda-dev";
17
- const REVIEW_DIR = (workspaceRoot: string, jobId: string) => path.join(workspaceRoot, ".mcoda", "jobs", jobId, "review");
18
- const STATE_PATH = (workspaceRoot: string, jobId: string) => path.join(REVIEW_DIR(workspaceRoot, jobId), "state.json");
19
- const DEFAULT_CODE_REVIEW_PROMPT = [
20
- "You are the code-review agent. Before reviewing, 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.",
21
- "Use docdex snippets to verify contracts (data shapes, offline scope, accessibility/perf guardrails, acceptance criteria). Call out mismatches, missing tests, and undocumented changes.",
22
- ].join("\n");
23
- const DEFAULT_JOB_PROMPT = "You are an mcoda agent that follows workspace runbooks and responds with actionable, concise output.";
24
- const DEFAULT_CHARACTER_PROMPT =
25
- "Write clearly, avoid hallucinations, cite assumptions, and prioritize risk mitigation for the user.";
26
-
27
- export interface CodeReviewRequest extends TaskSelectionFilters {
28
- workspace: WorkspaceResolution;
29
- baseRef?: string;
30
- dryRun?: boolean;
31
- agentName?: string;
32
- agentStream?: boolean;
33
- resumeJobId?: string;
34
- }
35
-
36
- export interface ReviewFinding {
37
- type?: string;
38
- severity?: string;
39
- file?: string;
40
- line?: number;
41
- message: string;
42
- suggestedFix?: string;
43
- }
44
-
45
- export interface ReviewAgentResult {
46
- decision: "approve" | "changes_requested" | "block" | "info_only";
47
- summary?: string;
48
- findings: ReviewFinding[];
49
- testRecommendations?: string[];
50
- raw?: string;
51
- }
52
-
53
- export interface TaskReviewResult {
54
- taskId: string;
55
- taskKey: string;
56
- statusBefore: string;
57
- statusAfter?: string;
58
- decision?: ReviewAgentResult["decision"];
59
- findings: ReviewFinding[];
60
- error?: string;
61
- followupTasks?: { taskId: string; taskKey: string; epicId: string; userStoryId: string; generic?: boolean }[];
62
- }
63
-
64
- export interface CodeReviewResult {
65
- jobId: string;
66
- commandRunId: string;
67
- tasks: TaskReviewResult[];
68
- warnings: string[];
69
- }
70
-
71
- interface ReviewJobState {
72
- baseRef: string;
73
- statusFilter: string[];
74
- selectedTaskIds: string[];
75
- contextBuilt: string[];
76
- reviewed: { taskId: string; decision?: string; error?: string }[];
77
- }
78
-
79
- const estimateTokens = (text: string): number => Math.max(1, Math.ceil((text ?? "").length / 4));
80
-
81
- const parseJsonOutput = (raw: string): ReviewAgentResult | undefined => {
82
- const trimmed = raw.trim();
83
- const fenced = trimmed.replace(/^```(?:json)?/i, "").replace(/```$/, "").trim();
84
- const candidates = [trimmed, fenced];
85
- for (const candidate of candidates) {
86
- const start = candidate.indexOf("{");
87
- const end = candidate.lastIndexOf("}");
88
- if (start === -1 || end === -1 || end <= start) continue;
89
- const slice = candidate.slice(start, end + 1);
90
- try {
91
- const parsed = JSON.parse(slice) as ReviewAgentResult;
92
- return { ...parsed, raw: raw };
93
- } catch {
94
- /* ignore */
95
- }
96
- }
97
- return undefined;
98
- };
99
-
100
- const summarizeComments = (comments: { category?: string; body: string; file?: string; line?: number }[]): string => {
101
- if (!comments.length) return "No prior comments.";
102
- return comments
103
- .map((c) => {
104
- const loc = c.file ? `${c.file}${c.line ? `:${c.line}` : ""}` : "";
105
- return `- [${c.category ?? "general"}] ${loc ? `${loc} ` : ""}${c.body}`;
106
- })
107
- .join("\n");
108
- };
109
-
110
- const JSON_CONTRACT = `{
111
- "decision": "approve | changes_requested | block | info_only",
112
- "summary": "short textual summary",
113
- "findings": [
114
- {
115
- "type": "bug | style | test | docs | contract | security | other",
116
- "severity": "info | low | medium | high | critical",
117
- "file": "relative/path/to/file.ext",
118
- "line": 123,
119
- "message": "Clear reviewer message",
120
- "suggestedFix": "Optional suggested change"
121
- }
122
- ],
123
- "testRecommendations": ["Optional test or QA recommendations per task"]
124
- }`;
125
-
126
- const normalizeSingleLine = (value: string | undefined, fallback: string): string => {
127
- const trimmed = (value ?? "").replace(/\s+/g, " ").trim();
128
- return trimmed || fallback;
129
- };
130
-
131
- const buildStandardReviewComment = (params: {
132
- decision?: string;
133
- statusBefore: string;
134
- statusAfter?: string;
135
- findingsCount: number;
136
- summary?: string;
137
- followupTaskKeys?: string[];
138
- error?: string;
139
- }): string => {
140
- const decision = params.decision ?? (params.error ? "error" : "info_only");
141
- const statusAfter = params.statusAfter ?? params.statusBefore;
142
- const summary = normalizeSingleLine(params.summary, params.error ? "Review failed." : "No summary provided.");
143
- const error = normalizeSingleLine(params.error, "none");
144
- const followups = params.followupTaskKeys && params.followupTaskKeys.length ? params.followupTaskKeys.join(", ") : "none";
145
- return [
146
- "[code-review]",
147
- `decision: ${decision}`,
148
- `status_before: ${params.statusBefore}`,
149
- `status_after: ${statusAfter}`,
150
- `findings: ${params.findingsCount}`,
151
- `summary: ${summary}`,
152
- `followups: ${followups}`,
153
- `error: ${error}`,
154
- ].join("\n");
155
- };
156
-
157
- export class CodeReviewService {
158
- private selectionService: TaskSelectionService;
159
- private stateService: TaskStateService;
160
- private vcs: VcsClient;
161
- private taskLogSeq = new Map<string, number>();
162
- private routingService: RoutingService;
163
-
164
- constructor(
165
- private workspace: WorkspaceResolution,
166
- private deps: {
167
- agentService: AgentService;
168
- docdex: DocdexClient;
169
- jobService: JobService;
170
- workspaceRepo: WorkspaceRepository;
171
- selectionService?: TaskSelectionService;
172
- stateService?: TaskStateService;
173
- repo: GlobalRepository;
174
- vcsClient?: VcsClient;
175
- routingService: RoutingService;
176
- },
177
- ) {
178
- this.selectionService = deps.selectionService ?? new TaskSelectionService(workspace, deps.workspaceRepo);
179
- this.stateService = deps.stateService ?? new TaskStateService(deps.workspaceRepo);
180
- this.vcs = deps.vcsClient ?? new VcsClient();
181
- this.routingService = deps.routingService;
182
- }
183
-
184
- static async create(workspace: WorkspaceResolution): Promise<CodeReviewService> {
185
- const repo = await GlobalRepository.create();
186
- const agentService = new AgentService(repo);
187
- const routingService = await RoutingService.create();
188
- const docdex = new DocdexClient({
189
- workspaceRoot: workspace.workspaceRoot,
190
- baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
191
- });
192
- const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
193
- const jobService = new JobService(workspace, workspaceRepo);
194
- const selectionService = new TaskSelectionService(workspace, workspaceRepo);
195
- const stateService = new TaskStateService(workspaceRepo);
196
- const vcsClient = new VcsClient();
197
- return new CodeReviewService(workspace, {
198
- agentService,
199
- docdex,
200
- jobService,
201
- workspaceRepo,
202
- selectionService,
203
- stateService,
204
- repo,
205
- vcsClient,
206
- routingService,
207
- });
208
- }
209
-
210
- async close(): Promise<void> {
211
- const maybeClose = async (target: unknown) => {
212
- try {
213
- if ((target as any)?.close) await (target as any).close();
214
- } catch {
215
- /* ignore */
216
- }
217
- };
218
- await maybeClose(this.deps.selectionService);
219
- await maybeClose(this.deps.stateService);
220
- await maybeClose(this.deps.agentService);
221
- await maybeClose(this.deps.jobService);
222
- await maybeClose(this.deps.repo);
223
- await maybeClose(this.deps.workspaceRepo);
224
- await maybeClose(this.deps.routingService);
225
- await maybeClose(this.deps.docdex);
226
- }
227
-
228
- private async readPromptFiles(paths: string[]): Promise<string[]> {
229
- const contents: string[] = [];
230
- const seen = new Set<string>();
231
- for (const promptPath of paths) {
232
- try {
233
- const content = await fs.readFile(promptPath, "utf8");
234
- const trimmed = content.trim();
235
- if (trimmed && !seen.has(trimmed)) {
236
- contents.push(trimmed);
237
- seen.add(trimmed);
238
- }
239
- } catch {
240
- /* optional prompt */
241
- }
242
- }
243
- return contents;
244
- }
245
-
246
- private async ensureMcoda(): Promise<void> {
247
- await PathHelper.ensureDir(this.workspace.mcodaDir);
248
- const gitignorePath = path.join(this.workspace.workspaceRoot, ".gitignore");
249
- const entry = ".mcoda/\n";
250
- try {
251
- const content = await fs.readFile(gitignorePath, "utf8");
252
- if (!content.includes(".mcoda/")) {
253
- await fs.writeFile(gitignorePath, `${content.trimEnd()}\n${entry}`, "utf8");
254
- }
255
- } catch {
256
- await fs.writeFile(gitignorePath, entry, "utf8");
257
- }
258
- }
259
-
260
- private async loadPrompts(agentId: string): Promise<{ jobPrompt?: string; characterPrompt?: string; commandPrompt?: string }> {
261
- const mcodaPromptPath = path.join(this.workspace.workspaceRoot, ".mcoda", "prompts", "code-reviewer.md");
262
- const workspacePromptPath = path.join(this.workspace.workspaceRoot, "prompts", "code-reviewer.md");
263
- try {
264
- await fs.mkdir(path.dirname(mcodaPromptPath), { recursive: true });
265
- await fs.access(mcodaPromptPath);
266
- console.info(`[code-review] using existing code-reviewer prompt at ${mcodaPromptPath}`);
267
- } catch {
268
- try {
269
- await fs.access(workspacePromptPath);
270
- await fs.copyFile(workspacePromptPath, mcodaPromptPath);
271
- console.info(`[code-review] copied code-reviewer prompt to ${mcodaPromptPath}`);
272
- } catch {
273
- console.info(`[code-review] no code-reviewer prompt found at ${workspacePromptPath}; writing default prompt to ${mcodaPromptPath}`);
274
- await fs.writeFile(mcodaPromptPath, DEFAULT_CODE_REVIEW_PROMPT, 'utf8');
275
- }
276
- }
277
- const filePrompts = await this.readPromptFiles([mcodaPromptPath, workspacePromptPath]);
278
- const agentPrompts =
279
- "getPrompts" in this.deps.agentService ? await (this.deps.agentService as any).getPrompts(agentId) : undefined;
280
- const mergedCommandPrompt = (() => {
281
- const parts = [...filePrompts];
282
- if (agentPrompts?.commandPrompts?.["code-review"]) {
283
- parts.push(agentPrompts.commandPrompts["code-review"]);
284
- }
285
- if (!parts.length) parts.push(DEFAULT_CODE_REVIEW_PROMPT);
286
- return parts.filter(Boolean).join("\n\n");
287
- })();
288
- return {
289
- jobPrompt: agentPrompts?.jobPrompt ?? DEFAULT_JOB_PROMPT,
290
- characterPrompt: agentPrompts?.characterPrompt ?? DEFAULT_CHARACTER_PROMPT,
291
- commandPrompt: mergedCommandPrompt || undefined,
292
- };
293
- }
294
-
295
- private async loadRunbookAndChecklists(): Promise<string[]> {
296
- const extras: string[] = [];
297
- const runbookPath = path.join(this.workspace.workspaceRoot, ".mcoda", "prompts", "commands", "code-review.md");
298
- try {
299
- const content = await fs.readFile(runbookPath, "utf8");
300
- extras.push(content);
301
- } catch {
302
- /* optional */
303
- }
304
- const checklistDir = path.join(this.workspace.workspaceRoot, ".mcoda", "checklists");
305
- try {
306
- const entries = await fs.readdir(checklistDir);
307
- for (const entry of entries) {
308
- if (!entry.endsWith(".md")) continue;
309
- const content = await fs.readFile(path.join(checklistDir, entry), "utf8");
310
- extras.push(content);
311
- }
312
- } catch {
313
- /* optional */
314
- }
315
- return extras;
316
- }
317
-
318
- private async resolveAgent(agentName?: string) {
319
- const resolved = await this.routingService.resolveAgentForCommand({
320
- workspace: this.workspace,
321
- commandName: "code-review",
322
- overrideAgentSlug: agentName,
323
- });
324
- return resolved.agent;
325
- }
326
-
327
- private async selectTasksViaApi(filters: {
328
- projectKey?: string;
329
- epicKey?: string;
330
- storyKey?: string;
331
- taskKeys?: string[];
332
- statusFilter: string[];
333
- limit?: number;
334
- }): Promise<(TaskRow & { epicKey: string; storyKey: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] })[]> {
335
- // Prefer the backlog/task OpenAPI surface (via BacklogService) to mirror API filtering semantics.
336
- const backlog = await BacklogService.create(this.workspace);
337
- try {
338
- const result = await backlog.getBacklog({
339
- projectKey: filters.projectKey,
340
- epicKey: filters.epicKey,
341
- storyKey: filters.storyKey,
342
- statuses: filters.statusFilter,
343
- verbose: true,
344
- });
345
- let tasks = result.summary.tasks;
346
- if (filters.taskKeys?.length) {
347
- const allowed = new Set(filters.taskKeys);
348
- tasks = tasks.filter((t) => allowed.has(t.task_key));
349
- }
350
- if (filters.limit && filters.limit > 0) {
351
- tasks = tasks.slice(0, filters.limit);
352
- }
353
- const ids = tasks.map((t) => t.task_id);
354
- const detailed = await this.deps.workspaceRepo.getTasksWithRelations(ids);
355
- // Preserve ordering from backlog
356
- const order = new Map(ids.map((id, idx) => [id, idx]));
357
- return detailed.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
358
- } finally {
359
- await backlog.close();
360
- }
361
- }
362
-
363
- private async persistState(jobId: string, state: ReviewJobState): Promise<void> {
364
- const dir = REVIEW_DIR(this.workspace.workspaceRoot, jobId);
365
- await fs.mkdir(dir, { recursive: true });
366
- await fs.writeFile(
367
- STATE_PATH(this.workspace.workspaceRoot, jobId),
368
- JSON.stringify({ schema_version: 1, job_id: jobId, updated_at: new Date().toISOString(), ...state }, null, 2),
369
- "utf8",
370
- );
371
- }
372
-
373
- private async loadState(jobId: string): Promise<ReviewJobState | undefined> {
374
- try {
375
- const raw = await fs.readFile(STATE_PATH(this.workspace.workspaceRoot, jobId), "utf8");
376
- return JSON.parse(raw) as ReviewJobState;
377
- } catch {
378
- return undefined;
379
- }
380
- }
381
-
382
- private async writeCheckpoint(jobId: string, stage: string, details?: Record<string, unknown>): Promise<void> {
383
- await this.deps.jobService.writeCheckpoint(jobId, { stage, timestamp: new Date().toISOString(), details });
384
- }
385
-
386
- private componentHintsFromPaths(paths: string[]): string[] {
387
- const hints = new Set<string>();
388
- for (const p of paths) {
389
- const segments = p.split("/").filter(Boolean);
390
- if (segments.length) {
391
- hints.add(segments[0]);
392
- if (segments.length > 1) hints.add(`${segments[0]}/${segments[1]}`);
393
- }
394
- const file = p.split("/").pop();
395
- if (file) {
396
- const base = file.split(".")[0];
397
- hints.add(base);
398
- }
399
- }
400
- return Array.from(hints).slice(0, 8);
401
- }
402
-
403
- private async gatherDocContext(taskTitle: string, paths: string[], acceptance?: string[]): Promise<{ snippets: string[]; warnings: string[] }> {
404
- const snippets: string[] = [];
405
- const warnings: string[] = [];
406
- const queries = [...new Set([...(paths.length ? this.componentHintsFromPaths(paths) : []), taskTitle, ...(acceptance ?? [])])].slice(0, 8);
407
- for (const query of queries) {
408
- try {
409
- const docs = await this.deps.docdex.search({
410
- query,
411
- profile: "workspace-code",
412
- });
413
- snippets.push(
414
- ...docs.slice(0, 2).map((doc) => {
415
- const content = (doc.segments?.[0]?.content ?? doc.content ?? "").slice(0, 400);
416
- const ref = doc.path ?? doc.id ?? doc.title ?? query;
417
- return `- [${doc.docType ?? "doc"}] ${ref}: ${content}`;
418
- }),
419
- );
420
- } catch (error) {
421
- warnings.push(`docdex search failed for ${query}: ${(error as Error).message}`);
422
- }
423
- }
424
- return { snippets: Array.from(new Set(snippets)), warnings };
425
- }
426
-
427
- private buildReviewPrompt(params: {
428
- systemPrompts: string[];
429
- task: TaskRow & { epicKey?: string; storyKey?: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] };
430
- diff: string;
431
- docContext: string[];
432
- openapiSnippet?: string;
433
- checklists?: string[];
434
- historySummary: string;
435
- baseRef: string;
436
- branch?: string;
437
- }): string {
438
- const parts: string[] = [];
439
- if (params.systemPrompts.length) {
440
- parts.push(params.systemPrompts.join("\n\n"));
441
- }
442
- const acceptance = params.task.acceptanceCriteria && params.task.acceptanceCriteria.length ? params.task.acceptanceCriteria.join(" | ") : "none provided";
443
- parts.push(
444
- [
445
- `Task ${params.task.key}: ${params.task.title}`,
446
- `Epic: ${params.task.epicKey ?? ""} ${params.task.epicTitle ?? ""}`.trim(),
447
- `Epic description: ${params.task.epicDescription ? params.task.epicDescription : "none"}`,
448
- `Story: ${params.task.storyKey ?? ""} ${params.task.storyTitle ?? ""}`.trim(),
449
- `Story description: ${params.task.storyDescription ? params.task.storyDescription : "none"}`,
450
- `Status: ${params.task.status}, Branch: ${params.branch ?? params.task.vcsBranch ?? "n/a"} (base ${params.baseRef})`,
451
- `Task description: ${params.task.description ? params.task.description : "none"}`,
452
- `History:\n${params.historySummary}`,
453
- `Acceptance criteria: ${acceptance}`,
454
- params.docContext.length ? `Doc context (docdex excerpts):\n${params.docContext.join("\n")}` : "Doc context: none",
455
- params.openapiSnippet
456
- ? `OpenAPI (authoritative contract; do not invent endpoints outside this):\n${params.openapiSnippet}`
457
- : "OpenAPI: not provided; avoid inventing endpoints.",
458
- params.checklists && params.checklists.length ? `Review checklists/runbook:\n${params.checklists.join("\n\n")}` : "Checklists: none",
459
- "Diff:\n" + (params.diff || "(no diff)"),
460
- "Respond with STRICT JSON only, matching:\n" + JSON_CONTRACT,
461
- "Rules: honor OpenAPI contracts; cite doc context where relevant; do not add prose outside JSON.",
462
- ].join("\n"),
463
- );
464
- return parts.join("\n\n");
465
- }
466
-
467
- private async buildHistorySummary(taskId: string): Promise<string> {
468
- const comments = await this.deps.workspaceRepo.listTaskComments(taskId, {
469
- sourceCommands: ["work-on-tasks", "code-review", "qa-tasks"],
470
- limit: 10,
471
- });
472
- const lastReview = await this.deps.workspaceRepo.getLatestTaskReview(taskId);
473
- const parts: string[] = [];
474
- if (lastReview) {
475
- parts.push(`Last review decision: ${lastReview.decision}${lastReview.summary ? ` — ${lastReview.summary}` : ""}`);
476
- }
477
- if (comments.length) {
478
- parts.push("Recent comments:");
479
- parts.push(
480
- summarizeComments(
481
- comments.map((c) => ({
482
- category: c.category ?? undefined,
483
- body: c.body,
484
- file: c.file ?? undefined,
485
- line: c.line ?? undefined,
486
- })),
487
- ),
488
- );
489
- const unresolved = comments.filter((c) => !c.resolvedAt);
490
- if (unresolved.length) {
491
- parts.push(`Unresolved items: ${unresolved.length}`);
492
- }
493
- }
494
- if (!parts.length) return "No prior review or QA history.";
495
- return parts.join("\n");
496
- }
497
-
498
- private extractPathsFromDiff(diff: string): string[] {
499
- const regex = /^(?:\+\+\+ b\/|\-\-\- a\/)([^\s]+)$/gm;
500
- const paths = new Set<string>();
501
- let match: RegExpExecArray | null;
502
- while ((match = regex.exec(diff)) !== null) {
503
- const raw = match[1]?.trim();
504
- if (raw && raw !== "/dev/null") paths.add(raw.replace(/^a\//, "").replace(/^b\//, ""));
505
- }
506
- return Array.from(paths);
507
- }
508
-
509
- private async buildOpenApiSlice(changedPaths: string[], acceptance?: string[]): Promise<string | undefined> {
510
- const openapiPath = path.join(this.workspace.workspaceRoot, "openapi", "mcoda.yaml");
511
- try {
512
- const content = await fs.readFile(openapiPath, "utf8");
513
- const parsed = yaml.parse(content) as any;
514
- const pathHints = this.componentHintsFromPaths(changedPaths);
515
- const criteriaHints = (acceptance ?? []).map((c) => c.toLowerCase()).slice(0, 5);
516
- const matches: Record<string, any> = {};
517
- if (parsed?.paths) {
518
- for (const [apiPath, ops] of Object.entries(parsed.paths as Record<string, any>)) {
519
- const lowerPath = apiPath.toLowerCase();
520
- const hit =
521
- pathHints.some((h) => lowerPath.includes(h.toLowerCase())) ||
522
- criteriaHints.some((h) => lowerPath.includes(h)) ||
523
- (!pathHints.length && !criteriaHints.length);
524
- if (hit) {
525
- matches[apiPath] = ops;
526
- }
527
- }
528
- }
529
- const schemaMatches: Record<string, any> = {};
530
- if (parsed?.components?.schemas) {
531
- for (const [name, schema] of Object.entries(parsed.components.schemas as Record<string, any>)) {
532
- const lower = name.toLowerCase();
533
- if (pathHints.some((h) => lower.includes(h.toLowerCase()))) {
534
- schemaMatches[name] = schema;
535
- }
536
- }
537
- }
538
- if (!Object.keys(matches).length && !Object.keys(schemaMatches).length) {
539
- return content.slice(0, 4000);
540
- }
541
- const slice = {
542
- openapi: parsed.openapi ?? "3.0.0",
543
- info: parsed.info,
544
- paths: matches,
545
- components: Object.keys(schemaMatches).length ? { schemas: schemaMatches } : undefined,
546
- };
547
- const rendered = yaml.stringify(slice);
548
- return rendered.slice(0, 8000);
549
- } catch {
550
- return undefined;
551
- }
552
- }
553
-
554
- private async buildDiff(
555
- task: TaskRow,
556
- baseRef: string,
557
- fileScope: string[],
558
- ): Promise<{ diff: string; source: "commit" | "branch"; commitSha?: string; warning?: string }> {
559
- const branch = task.vcsBranch;
560
- await this.vcs.ensureRepo(this.workspace.workspaceRoot);
561
- const paths = fileScope.length ? fileScope : undefined;
562
- const commitSha = task.vcsLastCommitSha;
563
- if (commitSha) {
564
- try {
565
- const diff = await this.vcs.diff(this.workspace.workspaceRoot, `${commitSha}^`, commitSha, paths);
566
- return { diff, source: "commit", commitSha };
567
- } catch (error) {
568
- if (!branch) {
569
- throw new Error(`Task branch missing and commit diff failed: ${(error as Error).message}`);
570
- }
571
- const fallback = await this.vcs.diff(this.workspace.workspaceRoot, baseRef, branch, paths);
572
- return {
573
- diff: fallback,
574
- source: "branch",
575
- commitSha,
576
- warning: `Failed to diff commit ${commitSha}; fell back to branch ${branch}.`,
577
- };
578
- }
579
- }
580
- if (!branch) throw new Error("Task branch missing");
581
- const diff = await this.vcs.diff(this.workspace.workspaceRoot, baseRef, branch, paths);
582
- return { diff, source: "branch" };
583
- }
584
-
585
- private async writeReviewSummaryComment(params: {
586
- task: TaskRow;
587
- taskRunId: string;
588
- jobId: string;
589
- agentId: string;
590
- statusBefore: string;
591
- statusAfter?: string;
592
- decision?: string;
593
- summary?: string;
594
- findingsCount: number;
595
- followupTaskKeys?: string[];
596
- error?: string;
597
- }): Promise<void> {
598
- const body = buildStandardReviewComment({
599
- decision: params.decision,
600
- statusBefore: params.statusBefore,
601
- statusAfter: params.statusAfter,
602
- findingsCount: params.findingsCount,
603
- summary: params.summary,
604
- followupTaskKeys: params.followupTaskKeys,
605
- error: params.error,
606
- });
607
- await this.deps.workspaceRepo.createTaskComment({
608
- taskId: params.task.id,
609
- taskRunId: params.taskRunId,
610
- jobId: params.jobId,
611
- sourceCommand: "code-review",
612
- authorType: "agent",
613
- authorAgentId: params.agentId,
614
- category: "review_summary",
615
- body,
616
- createdAt: new Date().toISOString(),
617
- });
618
- }
619
-
620
- private async persistContext(jobId: string, taskId: string, context: Record<string, unknown>): Promise<void> {
621
- const dir = path.join(REVIEW_DIR(this.workspace.workspaceRoot, jobId), "context");
622
- await fs.mkdir(dir, { recursive: true });
623
- await fs.writeFile(
624
- path.join(dir, `${taskId}.json`),
625
- JSON.stringify({ schema_version: 1, task_id: taskId, created_at: new Date().toISOString(), ...context }, null, 2),
626
- "utf8",
627
- );
628
- }
629
-
630
- private async persistDiff(jobId: string, taskId: string, diff: string): Promise<void> {
631
- const dir = path.join(REVIEW_DIR(this.workspace.workspaceRoot, jobId), "diffs");
632
- await fs.mkdir(dir, { recursive: true });
633
- await fs.writeFile(path.join(dir, `${taskId}.diff`), diff, "utf8");
634
- // structured review diff snapshot
635
- const files: { path: string; hunks: string[] }[] = [];
636
- let current: { path: string; hunks: string[] } | null = null;
637
- for (const line of diff.split(/\r?\n/)) {
638
- if (line.startsWith("diff --git")) {
639
- if (current) {
640
- files.push(current);
641
- current = null;
642
- }
643
- continue;
644
- }
645
- const fileHeader = line.match(/^(\+\+\+|---)\s+[ab]\/(.+)$/);
646
- if (fileHeader) {
647
- if (current) files.push(current);
648
- current = { path: fileHeader[2], hunks: [] };
649
- continue;
650
- }
651
- if (line.startsWith("@@")) {
652
- if (current) current.hunks.push(line);
653
- continue;
654
- }
655
- if (current) current.hunks.push(line);
656
- }
657
- if (current) files.push(current);
658
- await fs.writeFile(path.join(dir, `${taskId}.json`), JSON.stringify({ schema_version: 1, task_id: taskId, files }, null, 2), "utf8");
659
- }
660
-
661
- private severityToPriority(severity?: string): number | null {
662
- if (!severity) return null;
663
- const normalized = severity.toLowerCase();
664
- const order: Record<string, number> = { critical: 1, high: 2, medium: 3, low: 4, info: 5 };
665
- return order[normalized] ?? null;
666
- }
667
-
668
- private shouldCreateFollowupTask(decision: ReviewAgentResult["decision"] | undefined, finding: ReviewFinding): boolean {
669
- // SDS rule: create follow-ups for blocking/changes_requested decisions or critical/high issues,
670
- // and for contract/security/bug types at medium+ severity. Do not create for approve+low/info.
671
- const sev = (finding.severity ?? "").toLowerCase();
672
- const type = (finding.type ?? "").toLowerCase();
673
- const decisionRequestsChange = decision === "changes_requested" || decision === "block";
674
- if (decisionRequestsChange && sev !== "info") return true;
675
- if (["critical", "high"].includes(sev)) return true;
676
- if (["bug", "security", "contract"].includes(type) && !["info", "low"].includes(sev)) return true;
677
- return false;
678
- }
679
-
680
- private buildFollowupTitle(task: TaskRow & { storyKey?: string }, finding: ReviewFinding, generatedKey: string): string {
681
- const base = (finding.message ?? "Review follow-up").split("\n")[0]?.trim() ?? "Review follow-up";
682
- const prefix = finding.file ? `${finding.file}: ` : "";
683
- const raw = `${prefix}${base}`;
684
- const truncated = raw.length > 140 ? `${raw.slice(0, 137)}...` : raw;
685
- return truncated || `Follow-up ${generatedKey} for ${task.key}`;
686
- }
687
-
688
- private buildFollowupDescription(task: TaskRow & { storyKey?: string; epicKey?: string }, finding: ReviewFinding, decision?: ReviewAgentResult["decision"]): string {
689
- const lines = [
690
- `Auto-created from code review of ${task.key}. Decision: ${decision ?? "n/a"}.`,
691
- finding.message ? `Finding: ${finding.message}` : undefined,
692
- finding.file ? `Location: ${finding.file}${finding.line ? `:${finding.line}` : ""}` : undefined,
693
- finding.severity ? `Severity: ${finding.severity}` : undefined,
694
- finding.type ? `Category: ${finding.type}` : undefined,
695
- finding.suggestedFix ? `Suggested fix: ${finding.suggestedFix}` : undefined,
696
- `Story: ${task.storyKey ?? task.userStoryId}, Epic: ${task.epicKey ?? task.epicId}`,
697
- ].filter(Boolean);
698
- return lines.join("\n");
699
- }
700
-
701
- private async ensureGenericContainers(projectId: string): Promise<{ epic: EpicRow; story: StoryRow }> {
702
- const epicCandidates = ["epic-bugs", "epic-issues"];
703
- let epic: EpicRow | undefined;
704
- for (const key of epicCandidates) {
705
- epic = await this.deps.workspaceRepo.getEpicByKey(projectId, key);
706
- if (epic) break;
707
- }
708
- if (!epic) {
709
- const [createdEpic] = await this.deps.workspaceRepo.insertEpics(
710
- [
711
- {
712
- projectId,
713
- key: epicCandidates[0],
714
- title: "Bug Backlog",
715
- description: "Generic epic for code review follow-up issues",
716
- metadata: { source: "code-review", autoGenerated: true },
717
- },
718
- ],
719
- true,
720
- );
721
- epic = createdEpic;
722
- }
723
-
724
- const storyCandidates = ["us-bugs", "us-issues"];
725
- let story: StoryRow | undefined;
726
- for (const key of storyCandidates) {
727
- story = await this.deps.workspaceRepo.getStoryByKey(epic.id, key);
728
- if (story) break;
729
- }
730
- if (!story) {
731
- const [createdStory] = await this.deps.workspaceRepo.insertStories(
732
- [
733
- {
734
- projectId,
735
- epicId: epic.id,
736
- key: storyCandidates[0],
737
- title: "Review issues",
738
- description: "Auto-created story for code review findings",
739
- acceptanceCriteria: "Track, fix, and verify issues found during reviews.",
740
- metadata: { source: "code-review", autoGenerated: true },
741
- },
742
- ],
743
- true,
744
- );
745
- story = createdStory;
746
- }
747
-
748
- return { epic, story };
749
- }
750
-
751
- private async createFollowupTasksForFindings(params: {
752
- task: TaskRow & { storyKey?: string; epicKey?: string };
753
- findings: ReviewFinding[];
754
- decision?: ReviewAgentResult["decision"];
755
- jobId: string;
756
- commandRunId: string;
757
- taskRunId: string;
758
- }): Promise<TaskRow[]> {
759
- const actionable = params.findings.filter((f) => this.shouldCreateFollowupTask(params.decision, f));
760
- if (!actionable.length) return [];
761
-
762
- const useGeneric = actionable.some((f) => !f.file && !f.line);
763
- const genericContainers = useGeneric ? await this.ensureGenericContainers(params.task.projectId) : undefined;
764
-
765
- const inserts: TaskInsert[] = [];
766
- const generators = new Map<
767
- string,
768
- {
769
- gen: () => string;
770
- keys: Set<string>;
771
- storyKey: string;
772
- }
773
- >();
774
- const ensureKey = async (storyId: string, storyKey: string): Promise<string> => {
775
- let entry = generators.get(storyId);
776
- if (!entry) {
777
- const existing = new Set(await this.deps.workspaceRepo.listTaskKeys(storyId));
778
- entry = { gen: createTaskKeyGenerator(storyKey, existing), keys: existing, storyKey };
779
- generators.set(storyId, entry);
780
- }
781
- const key = entry.gen();
782
- entry.keys.add(key);
783
- return key;
784
- };
785
-
786
- for (const finding of actionable) {
787
- const genericTarget = !finding.file && !finding.line && genericContainers;
788
- const storyId = genericTarget ? genericContainers.story.id : params.task.userStoryId;
789
- const storyKey = genericTarget ? genericContainers!.story.key : params.task.storyKey ?? genericContainers?.story.key ?? "US-AUTO";
790
- const epicId = genericTarget ? genericContainers!.epic.id : params.task.epicId;
791
- const taskKey = await ensureKey(storyId, storyKey);
792
- inserts.push({
793
- projectId: params.task.projectId,
794
- epicId,
795
- userStoryId: storyId,
796
- key: taskKey,
797
- title: this.buildFollowupTitle(params.task, finding, taskKey),
798
- description: this.buildFollowupDescription(params.task, finding, params.decision),
799
- type: finding.type ?? (params.decision === "changes_requested" || params.decision === "block" ? "bug" : "issue"),
800
- status: "not_started",
801
- storyPoints: null,
802
- priority: this.severityToPriority(finding.severity),
803
- metadata: {
804
- source: "code-review",
805
- source_task_id: params.task.id,
806
- source_task_key: params.task.key,
807
- source_job_id: params.jobId,
808
- source_command_run_id: params.commandRunId,
809
- source_task_run_id: params.taskRunId,
810
- severity: finding.severity,
811
- type: finding.type,
812
- file: finding.file,
813
- line: finding.line,
814
- suggestedFix: finding.suggestedFix,
815
- generic: genericTarget ? true : false,
816
- decision: params.decision,
817
- },
818
- });
819
- }
820
-
821
- const created = await this.deps.workspaceRepo.insertTasks(inserts, true);
822
- for (let i = 0; i < created.length; i += 1) {
823
- const createdTask = created[i];
824
- const sourceFinding = actionable[i];
825
- await this.deps.workspaceRepo.insertTaskLog({
826
- taskRunId: params.taskRunId,
827
- sequence: this.sequenceForTask(params.taskRunId),
828
- timestamp: new Date().toISOString(),
829
- source: "followup_task",
830
- message: `Created follow-up task ${createdTask.key}`,
831
- details: { targetTaskId: createdTask.id, sourceFinding },
832
- });
833
- }
834
-
835
- return created;
836
- }
837
-
838
- async reviewTasks(request: CodeReviewRequest): Promise<CodeReviewResult> {
839
- await this.ensureMcoda();
840
- const agentStream = request.agentStream !== false;
841
- const baseRef = request.baseRef ?? this.workspace.config?.branch ?? DEFAULT_BASE_BRANCH;
842
- const statusFilter = request.statusFilter && request.statusFilter.length ? request.statusFilter : ["ready_to_review"];
843
- let state: ReviewJobState | undefined;
844
-
845
- const commandRun = await this.deps.jobService.startCommandRun("code-review", request.projectKey, {
846
- taskIds: request.taskKeys,
847
- gitBaseBranch: baseRef,
848
- jobId: request.resumeJobId,
849
- });
850
-
851
- let jobId = request.resumeJobId;
852
- let selectedTaskIds: string[] = [];
853
- let warnings: string[] = [];
854
- let selectedTasks: Array<TaskRow & { epicKey: string; storyKey: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] }> =
855
- [];
856
-
857
- if (request.resumeJobId) {
858
- const job = await this.deps.jobService.getJob(request.resumeJobId);
859
- if (!job) throw new Error(`Job not found: ${request.resumeJobId}`);
860
- if ((job.commandName ?? job.type) !== "code-review" && job.type !== "review") {
861
- throw new Error(`Job ${request.resumeJobId} is not a code-review job`);
862
- }
863
- state = await this.loadState(request.resumeJobId);
864
- selectedTaskIds = state?.selectedTaskIds ?? (Array.isArray((job.payload as any)?.selection) ? ((job.payload as any).selection as string[]) : []);
865
- if (!selectedTaskIds.length) {
866
- throw new Error("Resume requested but no task selection found in job payload");
867
- }
868
- await this.deps.jobService.updateJobStatus(job.id, "running", {
869
- totalItems: job.totalItems ?? selectedTaskIds.length,
870
- processedItems: state?.reviewed.length ?? 0,
871
- });
872
- selectedTasks = await this.deps.workspaceRepo.getTasksWithRelations(selectedTaskIds);
873
- } else {
874
- try {
875
- selectedTasks = await this.selectTasksViaApi({
876
- projectKey: request.projectKey,
877
- epicKey: request.epicKey,
878
- storyKey: request.storyKey,
879
- taskKeys: request.taskKeys,
880
- statusFilter,
881
- limit: request.limit,
882
- });
883
- } catch {
884
- const selection = await this.selectionService.selectTasks({
885
- projectKey: request.projectKey,
886
- epicKey: request.epicKey,
887
- storyKey: request.storyKey,
888
- taskKeys: request.taskKeys,
889
- statusFilter,
890
- limit: request.limit,
891
- });
892
- warnings = [...selection.warnings];
893
- selectedTasks = selection.ordered.map((t) => t.task);
894
- }
895
-
896
- selectedTaskIds = selectedTasks.map((t) => t.id);
897
- const job = await this.deps.jobService.startJob("review", commandRun.id, request.projectKey, {
898
- commandName: "code-review",
899
- payload: {
900
- projectKey: request.projectKey,
901
- epicKey: request.epicKey,
902
- storyKey: request.storyKey,
903
- tasks: request.taskKeys,
904
- statusFilter,
905
- baseRef,
906
- selection: selectedTaskIds,
907
- dryRun: request.dryRun ?? false,
908
- agent: request.agentName,
909
- agentStream,
910
- },
911
- totalItems: selectedTaskIds.length,
912
- processedItems: 0,
913
- });
914
- jobId = job.id;
915
- state = {
916
- baseRef,
917
- statusFilter,
918
- selectedTaskIds,
919
- contextBuilt: [],
920
- reviewed: [],
921
- };
922
- await this.persistState(jobId, state);
923
- await this.writeCheckpoint(jobId, "tasks_selected", { tasks: selectedTaskIds, baseRef, statusFilter });
924
- }
925
-
926
- if (!jobId) {
927
- throw new Error("Failed to resolve job id for code-review");
928
- }
929
-
930
- if (!state) {
931
- state = {
932
- baseRef,
933
- statusFilter,
934
- selectedTaskIds,
935
- contextBuilt: [],
936
- reviewed: [],
937
- };
938
- await this.persistState(jobId, state);
939
- }
940
-
941
- if (selectedTaskIds.length === 0) {
942
- await this.deps.jobService.updateJobStatus(jobId, "completed", { totalItems: 0, processedItems: 0 });
943
- await this.deps.jobService.finishCommandRun(commandRun.id, "succeeded");
944
- return { jobId, commandRunId: commandRun.id, tasks: [], warnings };
945
- }
946
-
947
- const tasks =
948
- selectedTasks.length && selectedTaskIds.length === selectedTasks.length
949
- ? selectedTasks
950
- : await this.deps.workspaceRepo.getTasksWithRelations(selectedTaskIds);
951
- const agent = await this.resolveAgent(request.agentName);
952
- const prompts = await this.loadPrompts(agent.id);
953
- const extras = await this.loadRunbookAndChecklists();
954
- const systemPrompts = [prompts.jobPrompt, prompts.characterPrompt, prompts.commandPrompt, ...extras].filter(Boolean) as string[];
955
-
956
- const results: TaskReviewResult[] = [];
957
-
958
- for (const task of tasks) {
959
- const statusBefore = task.status;
960
- const taskRun = await this.deps.workspaceRepo.createTaskRun({
961
- taskId: task.id,
962
- command: "code-review",
963
- jobId,
964
- commandRunId: commandRun.id,
965
- agentId: agent.id,
966
- status: "running",
967
- startedAt: new Date().toISOString(),
968
- storyPointsAtRun: task.storyPoints ?? null,
969
- gitBranch: task.vcsBranch ?? null,
970
- gitBaseBranch: task.vcsBaseBranch ?? null,
971
- gitCommitSha: task.vcsLastCommitSha ?? null,
972
- });
973
-
974
- const findings: ReviewFinding[] = [];
975
- let decision: ReviewAgentResult["decision"] | undefined;
976
- let statusAfter: string | undefined;
977
- const followupCreated: { taskId: string; taskKey: string; epicId: string; userStoryId: string; generic?: boolean }[] = [];
978
-
979
- // Debug visibility: show prompts/task details for this run
980
- const systemPrompt = systemPrompts.join("\n\n");
981
-
982
- try {
983
- const metadata = (task.metadata as any) ?? {};
984
- const allowedFiles: string[] = Array.isArray(metadata.files) ? metadata.files : [];
985
- const diffResult = await this.buildDiff(task, state?.baseRef ?? baseRef, allowedFiles);
986
- const diff = diffResult.diff;
987
- if (diffResult.warning) warnings.push(diffResult.warning);
988
- await this.persistDiff(jobId, task.id, diff);
989
- await this.deps.workspaceRepo.insertTaskLog({
990
- taskRunId: taskRun.id,
991
- sequence: this.sequenceForTask(taskRun.id),
992
- timestamp: new Date().toISOString(),
993
- source: "context_git_diff",
994
- message: "Git diff computed",
995
- details: {
996
- baseRef: state?.baseRef ?? baseRef,
997
- branch: task.vcsBranch,
998
- commitSha: diffResult.commitSha,
999
- diffSource: diffResult.source,
1000
- allowedFiles,
1001
- },
1002
- });
1003
-
1004
- const historySummary = await this.buildHistorySummary(task.id);
1005
- await this.deps.workspaceRepo.insertTaskLog({
1006
- taskRunId: taskRun.id,
1007
- sequence: this.sequenceForTask(taskRun.id),
1008
- timestamp: new Date().toISOString(),
1009
- source: "context_history",
1010
- message: "Loaded task history",
1011
- });
1012
-
1013
- const changedPaths = this.extractPathsFromDiff(diff);
1014
- const docLinks = await this.gatherDocContext(task.title, changedPaths.length ? changedPaths : allowedFiles, task.acceptanceCriteria);
1015
- if (docLinks.warnings.length) warnings.push(...docLinks.warnings);
1016
- await this.deps.workspaceRepo.insertTaskLog({
1017
- taskRunId: taskRun.id,
1018
- sequence: this.sequenceForTask(taskRun.id),
1019
- timestamp: new Date().toISOString(),
1020
- source: "context_docdex",
1021
- message: "Docdex context gathered",
1022
- details: { snippets: docLinks.snippets },
1023
- });
1024
-
1025
- const openapiSnippet = await this.buildOpenApiSlice(changedPaths, task.acceptanceCriteria);
1026
- if (!openapiSnippet) {
1027
- warnings.push("OpenAPI spec not found; proceeding without snippet");
1028
- }
1029
- await this.deps.workspaceRepo.insertTaskLog({
1030
- taskRunId: taskRun.id,
1031
- sequence: this.sequenceForTask(taskRun.id),
1032
- timestamp: new Date().toISOString(),
1033
- source: "context_openapi",
1034
- message: "OpenAPI snippet loaded",
1035
- });
1036
-
1037
- const prompt = this.buildReviewPrompt({
1038
- systemPrompts,
1039
- task,
1040
- diff,
1041
- docContext: docLinks.snippets,
1042
- openapiSnippet,
1043
- historySummary,
1044
- baseRef: state?.baseRef ?? baseRef,
1045
- branch: task.vcsBranch ?? undefined,
1046
- });
1047
-
1048
- const separator = "============================================================";
1049
- const deps =
1050
- Array.isArray((task as any).dependencyKeys) && (task as any).dependencyKeys.length
1051
- ? (task as any).dependencyKeys
1052
- : Array.isArray((task.metadata as any)?.depends_on)
1053
- ? ((task.metadata as any).depends_on as string[])
1054
- : [];
1055
- console.info(separator);
1056
- console.info("[code-review] START OF TASK");
1057
- console.info(`[code-review] Task key: ${task.key}`);
1058
- console.info(`[code-review] Title: ${task.title ?? "(none)"}`);
1059
- console.info(`[code-review] Description: ${task.description ?? "(none)"}`);
1060
- console.info(
1061
- `[code-review] Story points: ${typeof task.storyPoints === "number" ? task.storyPoints : "(none)"}`,
1062
- );
1063
- console.info(`[code-review] Dependencies: ${deps.length ? deps.join(", ") : "(none available)"}`);
1064
- if (Array.isArray(task.acceptanceCriteria) && task.acceptanceCriteria.length) {
1065
- console.info(`[code-review] Acceptance criteria:\n- ${task.acceptanceCriteria.join("\n- ")}`);
1066
- }
1067
- console.info(`[code-review] System prompt used:\n${systemPrompt || "(none)"}`);
1068
- console.info(`[code-review] Task prompt used:\n${prompt}`);
1069
- console.info(separator);
1070
-
1071
- await this.persistContext(jobId, task.id, {
1072
- historySummary,
1073
- docdex: docLinks.snippets,
1074
- openapiSnippet,
1075
- changedPaths,
1076
- });
1077
- state?.contextBuilt.push(task.id);
1078
- await this.persistState(jobId, state!);
1079
- await this.writeCheckpoint(jobId, "context_built", { contextBuilt: state?.contextBuilt ?? [], schema_version: 1 });
1080
-
1081
- const recordUsage = async (
1082
- phase: string,
1083
- promptText: string,
1084
- outputText: string,
1085
- durationSeconds: number,
1086
- tokenMeta?: { tokensPrompt?: number; tokensCompletion?: number; tokensTotal?: number; model?: string | null },
1087
- ) => {
1088
- const tokensPrompt = tokenMeta?.tokensPrompt ?? estimateTokens(promptText);
1089
- const tokensCompletion = tokenMeta?.tokensCompletion ?? estimateTokens(outputText);
1090
- const tokensTotal = tokenMeta?.tokensTotal ?? tokensPrompt + tokensCompletion;
1091
- await this.deps.jobService.recordTokenUsage({
1092
- workspaceId: this.workspace.workspaceId,
1093
- agentId: agent.id,
1094
- modelName: tokenMeta?.model ?? (agent as any).defaultModel ?? undefined,
1095
- jobId,
1096
- commandRunId: commandRun.id,
1097
- taskRunId: taskRun.id,
1098
- taskId: task.id,
1099
- projectId: task.projectId,
1100
- tokensPrompt,
1101
- tokensCompletion,
1102
- tokensTotal,
1103
- durationSeconds,
1104
- timestamp: new Date().toISOString(),
1105
- metadata: { commandName: "code-review", phase, action: phase },
1106
- });
1107
- };
1108
-
1109
- let agentOutput = "";
1110
- let durationSeconds = 0;
1111
- const started = Date.now();
1112
- let lastStreamMeta: any;
1113
- if (agentStream && this.deps.agentService.invokeStream) {
1114
- const stream = await this.deps.agentService.invokeStream(agent.id, { input: prompt, metadata: { taskKey: task.key } });
1115
- for await (const chunk of stream) {
1116
- agentOutput += chunk.output ?? "";
1117
- lastStreamMeta = chunk.metadata ?? lastStreamMeta;
1118
- await this.deps.workspaceRepo.insertTaskLog({
1119
- taskRunId: taskRun.id,
1120
- sequence: this.sequenceForTask(taskRun.id),
1121
- timestamp: new Date().toISOString(),
1122
- source: "agent",
1123
- message: chunk.output ?? "",
1124
- });
1125
- }
1126
- durationSeconds = Math.round(((Date.now() - started) / 1000) * 1000) / 1000;
1127
- } else {
1128
- const response = await this.deps.agentService.invoke(agent.id, { input: prompt, metadata: { taskKey: task.key } });
1129
- agentOutput = response.output ?? "";
1130
- durationSeconds = Math.round(((Date.now() - started) / 1000) * 1000) / 1000;
1131
- await this.deps.workspaceRepo.insertTaskLog({
1132
- taskRunId: taskRun.id,
1133
- sequence: this.sequenceForTask(taskRun.id),
1134
- timestamp: new Date().toISOString(),
1135
- source: "agent",
1136
- message: agentOutput,
1137
- });
1138
- lastStreamMeta = response.metadata;
1139
- }
1140
- const tokenMetaMain = lastStreamMeta
1141
- ? {
1142
- tokensPrompt: typeof lastStreamMeta.tokensPrompt === "number" ? lastStreamMeta.tokensPrompt : (lastStreamMeta.tokens_prompt as number | undefined),
1143
- tokensCompletion:
1144
- typeof lastStreamMeta.tokensCompletion === "number"
1145
- ? lastStreamMeta.tokensCompletion
1146
- : (lastStreamMeta.tokens_completion as number | undefined),
1147
- tokensTotal: typeof lastStreamMeta.tokensTotal === "number" ? lastStreamMeta.tokensTotal : (lastStreamMeta.tokens_total as number | undefined),
1148
- model: (lastStreamMeta.model ?? lastStreamMeta.model_name ?? null) as string | null,
1149
- }
1150
- : undefined;
1151
- await recordUsage("review_main", prompt, agentOutput, durationSeconds, tokenMetaMain);
1152
-
1153
- let parsed = parseJsonOutput(agentOutput);
1154
- if (!parsed) {
1155
- await this.deps.workspaceRepo.insertTaskLog({
1156
- taskRunId: taskRun.id,
1157
- sequence: this.sequenceForTask(taskRun.id),
1158
- timestamp: new Date().toISOString(),
1159
- source: "agent",
1160
- message: "Invalid JSON from agent; retrying once with stricter instructions.",
1161
- });
1162
- const retryPrompt = `${prompt}\n\nRespond ONLY with valid JSON matching the schema above. Do not include prose or fences.`;
1163
- const retryStarted = Date.now();
1164
- const retryResp = await this.deps.agentService.invoke(agent.id, { input: retryPrompt, metadata: { taskKey: task.key, retry: true } });
1165
- const retryOutput = retryResp.output ?? "";
1166
- const retryDuration = Math.round(((Date.now() - retryStarted) / 1000) * 1000) / 1000;
1167
- await this.deps.workspaceRepo.insertTaskLog({
1168
- taskRunId: taskRun.id,
1169
- sequence: this.sequenceForTask(taskRun.id),
1170
- timestamp: new Date().toISOString(),
1171
- source: "agent_retry",
1172
- message: retryOutput,
1173
- });
1174
- const retryTokenMeta = retryResp.metadata
1175
- ? {
1176
- tokensPrompt:
1177
- typeof retryResp.metadata.tokensPrompt === "number"
1178
- ? retryResp.metadata.tokensPrompt
1179
- : (retryResp.metadata.tokens_prompt as number | undefined),
1180
- tokensCompletion:
1181
- typeof retryResp.metadata.tokensCompletion === "number"
1182
- ? retryResp.metadata.tokensCompletion
1183
- : (retryResp.metadata.tokens_completion as number | undefined),
1184
- tokensTotal:
1185
- typeof retryResp.metadata.tokensTotal === "number"
1186
- ? retryResp.metadata.tokensTotal
1187
- : (retryResp.metadata.tokens_total as number | undefined),
1188
- model: (retryResp.metadata.model ?? retryResp.metadata.model_name ?? null) as string | null,
1189
- }
1190
- : undefined;
1191
- await recordUsage("review_retry", retryPrompt, retryOutput, retryDuration, retryTokenMeta);
1192
- parsed = parseJsonOutput(retryOutput);
1193
- agentOutput = retryOutput;
1194
- }
1195
- if (!parsed) {
1196
- throw new Error("Agent output did not contain valid JSON review result after retry");
1197
- }
1198
- parsed.raw = agentOutput;
1199
- decision = parsed.decision;
1200
- findings.push(...(parsed.findings ?? []));
1201
-
1202
- const followups = await this.createFollowupTasksForFindings({
1203
- task,
1204
- findings: parsed.findings ?? [],
1205
- decision: parsed.decision,
1206
- jobId,
1207
- commandRunId: commandRun.id,
1208
- taskRunId: taskRun.id,
1209
- });
1210
- if (followups.length) {
1211
- followupCreated.push(
1212
- ...followups.map((t) => ({
1213
- taskId: t.id,
1214
- taskKey: t.key,
1215
- epicId: t.epicId,
1216
- userStoryId: t.userStoryId,
1217
- generic: (t as any)?.metadata?.generic ? true : undefined,
1218
- })),
1219
- );
1220
- warnings.push(`Created follow-up tasks for ${task.key}: ${followups.map((t) => t.key).join(", ")}`);
1221
- }
1222
-
1223
- let taskStatusUpdate = statusBefore;
1224
- if (!request.dryRun) {
1225
- if (parsed.decision === "approve") {
1226
- await this.stateService.markReadyToQa(task);
1227
- taskStatusUpdate = "ready_to_qa";
1228
- } else if (parsed.decision === "changes_requested") {
1229
- await this.stateService.returnToInProgress(task);
1230
- taskStatusUpdate = "in_progress";
1231
- } else if (parsed.decision === "block") {
1232
- await this.stateService.markBlocked(task, "review_blocked");
1233
- taskStatusUpdate = "blocked";
1234
- }
1235
- } else {
1236
- await this.deps.workspaceRepo.insertTaskLog({
1237
- taskRunId: taskRun.id,
1238
- sequence: this.sequenceForTask(taskRun.id),
1239
- timestamp: new Date().toISOString(),
1240
- source: "state",
1241
- message: "Dry-run enabled; skipping status transition.",
1242
- details: { requestedDecision: parsed.decision },
1243
- });
1244
- }
1245
- statusAfter = taskStatusUpdate;
1246
-
1247
- for (const finding of parsed.findings ?? []) {
1248
- await this.deps.workspaceRepo.createTaskComment({
1249
- taskId: task.id,
1250
- taskRunId: taskRun.id,
1251
- jobId,
1252
- sourceCommand: "code-review",
1253
- authorType: "agent",
1254
- authorAgentId: agent.id,
1255
- category: finding.type ?? "other",
1256
- file: finding.file,
1257
- line: finding.line,
1258
- pathHint: finding.file,
1259
- body: finding.message + (finding.suggestedFix ? `\n\nSuggested fix: ${finding.suggestedFix}` : ""),
1260
- metadata: {
1261
- severity: finding.severity,
1262
- suggestedFix: finding.suggestedFix,
1263
- },
1264
- createdAt: new Date().toISOString(),
1265
- });
1266
- }
1267
-
1268
- await this.writeReviewSummaryComment({
1269
- task,
1270
- taskRunId: taskRun.id,
1271
- jobId,
1272
- agentId: agent.id,
1273
- statusBefore,
1274
- statusAfter: statusAfter ?? statusBefore,
1275
- decision: parsed.decision,
1276
- summary: parsed.summary,
1277
- findingsCount: parsed.findings?.length ?? 0,
1278
- followupTaskKeys: followupCreated.map((t) => t.taskKey),
1279
- });
1280
-
1281
- await this.deps.workspaceRepo.createTaskReview({
1282
- taskId: task.id,
1283
- jobId,
1284
- agentId: agent.id,
1285
- modelName: (agent as any).defaultModel ?? undefined,
1286
- decision: parsed.decision,
1287
- summary: parsed.summary ?? undefined,
1288
- findingsJson: parsed.findings ?? [],
1289
- testRecommendationsJson: parsed.testRecommendations ?? [],
1290
- createdAt: new Date().toISOString(),
1291
- });
1292
- await this.stateService.recordReviewMetadata(task, {
1293
- decision: parsed.decision,
1294
- agentId: agent.id,
1295
- modelName: (agent as any).defaultModel ?? null,
1296
- jobId,
1297
- });
1298
-
1299
- await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
1300
- status: "succeeded",
1301
- finishedAt: new Date().toISOString(),
1302
- runContext: { decision: parsed.decision },
1303
- });
1304
-
1305
- state?.reviewed.push({ taskId: task.id, decision: parsed.decision });
1306
- await this.persistState(jobId, state!);
1307
- await this.writeCheckpoint(jobId, "review_applied", { reviewed: state?.reviewed ?? [], schema_version: 1 });
1308
- } catch (error) {
1309
- const message = error instanceof Error ? error.message : String(error);
1310
- results.push({ taskId: task.id, taskKey: task.key, statusBefore, findings, error: message, followupTasks: followupCreated });
1311
- await this.deps.workspaceRepo.insertTaskLog({
1312
- taskRunId: taskRun.id,
1313
- sequence: this.sequenceForTask(taskRun.id),
1314
- timestamp: new Date().toISOString(),
1315
- source: "review_error",
1316
- message,
1317
- });
1318
- try {
1319
- await this.writeReviewSummaryComment({
1320
- task,
1321
- taskRunId: taskRun.id,
1322
- jobId,
1323
- agentId: agent.id,
1324
- statusBefore,
1325
- statusAfter: statusBefore,
1326
- findingsCount: findings.length,
1327
- error: message,
1328
- });
1329
- } catch {
1330
- await this.deps.workspaceRepo.insertTaskLog({
1331
- taskRunId: taskRun.id,
1332
- sequence: this.sequenceForTask(taskRun.id),
1333
- timestamp: new Date().toISOString(),
1334
- source: "review_error",
1335
- message: "Failed to write review summary comment.",
1336
- });
1337
- }
1338
- await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
1339
- status: "failed",
1340
- finishedAt: new Date().toISOString(),
1341
- });
1342
- state?.reviewed.push({ taskId: task.id, error: message });
1343
- await this.persistState(jobId, state!);
1344
- await this.writeCheckpoint(jobId, "review_applied", { reviewed: state?.reviewed ?? [], schema_version: 1 });
1345
- await this.deps.jobService.updateJobStatus(jobId, "running", {
1346
- processedItems: state?.reviewed.length ?? 0,
1347
- });
1348
- continue;
1349
- }
1350
-
1351
- results.push({
1352
- taskId: task.id,
1353
- taskKey: task.key,
1354
- statusBefore,
1355
- statusAfter,
1356
- decision,
1357
- findings,
1358
- followupTasks: followupCreated,
1359
- });
1360
-
1361
- await this.deps.jobService.updateJobStatus(jobId, "running", {
1362
- processedItems: state?.reviewed.length ?? 0,
1363
- });
1364
- }
1365
-
1366
- await this.deps.jobService.updateJobStatus(jobId, "completed", {
1367
- processedItems: state?.reviewed.length ?? selectedTaskIds.length,
1368
- totalItems: selectedTaskIds.length,
1369
- });
1370
- await this.deps.jobService.finishCommandRun(commandRun.id, "succeeded");
1371
-
1372
- return {
1373
- jobId,
1374
- commandRunId: commandRun.id,
1375
- tasks: results,
1376
- warnings,
1377
- };
1378
- }
1379
-
1380
- private sequenceForTask(taskRunId: string): number {
1381
- const current = this.taskLogSeq.get(taskRunId) ?? 0;
1382
- const next = current + 1;
1383
- this.taskLogSeq.set(taskRunId, next);
1384
- return next;
1385
- }
1386
- }