mcoda 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc.cjs +12 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  6. package/.github/workflows/ci.yml +37 -0
  7. package/.github/workflows/nightly.yml +38 -0
  8. package/.github/workflows/release-dry-run.yml +40 -0
  9. package/.github/workflows/release-please.yml +22 -0
  10. package/.github/workflows/release.yml +149 -0
  11. package/.prettierrc +5 -0
  12. package/.release-please-manifest.json +8 -0
  13. package/CHANGELOG.md +7 -0
  14. package/CLA.md +42 -0
  15. package/CONTRIBUTING.md +38 -0
  16. package/LICENSE +21 -0
  17. package/README.md +314 -0
  18. package/docs/oss_publishing_plan.md +41 -0
  19. package/docs/pdr/.gitkeep +0 -0
  20. package/docs/quality_gates.md +32 -0
  21. package/docs/rfp/.gitkeep +0 -0
  22. package/docs/sds/sds.md +11963 -0
  23. package/docs/usage.md +72 -0
  24. package/openapi/gen-openapi.ts +1 -0
  25. package/openapi/generated/clients/.gitkeep +0 -0
  26. package/openapi/generated/types/.gitkeep +0 -0
  27. package/openapi/generated/types/index.ts +118 -0
  28. package/openapi/mcoda.yaml +2063 -0
  29. package/pack-mcoda.sh +88 -0
  30. package/package.json +46 -0
  31. package/packages/agents/CHANGELOG.md +7 -0
  32. package/packages/agents/LICENSE +21 -0
  33. package/packages/agents/README.md +9 -0
  34. package/packages/agents/package.json +41 -0
  35. package/packages/agents/src/AgentService/.gitkeep +0 -0
  36. package/packages/agents/src/AgentService/AgentService.d.ts +21 -0
  37. package/packages/agents/src/AgentService/AgentService.d.ts.map +1 -0
  38. package/packages/agents/src/AgentService/AgentService.js +141 -0
  39. package/packages/agents/src/AgentService/AgentService.ts +308 -0
  40. package/packages/agents/src/__tests__/AgentService.test.ts +284 -0
  41. package/packages/agents/src/adapters/AdapterTypes.d.ts +29 -0
  42. package/packages/agents/src/adapters/AdapterTypes.d.ts.map +1 -0
  43. package/packages/agents/src/adapters/AdapterTypes.js +1 -0
  44. package/packages/agents/src/adapters/AdapterTypes.ts +32 -0
  45. package/packages/agents/src/adapters/codex/.gitkeep +0 -0
  46. package/packages/agents/src/adapters/codex/CodexAdapter.d.ts +11 -0
  47. package/packages/agents/src/adapters/codex/CodexAdapter.d.ts.map +1 -0
  48. package/packages/agents/src/adapters/codex/CodexAdapter.js +43 -0
  49. package/packages/agents/src/adapters/codex/CodexAdapter.ts +63 -0
  50. package/packages/agents/src/adapters/codex/CodexCliRunner.ts +154 -0
  51. package/packages/agents/src/adapters/gemini/.gitkeep +0 -0
  52. package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts +11 -0
  53. package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts.map +1 -0
  54. package/packages/agents/src/adapters/gemini/GeminiAdapter.js +42 -0
  55. package/packages/agents/src/adapters/gemini/GeminiAdapter.ts +58 -0
  56. package/packages/agents/src/adapters/gemini/GeminiCliRunner.ts +75 -0
  57. package/packages/agents/src/adapters/local/.gitkeep +0 -0
  58. package/packages/agents/src/adapters/local/LocalAdapter.d.ts +11 -0
  59. package/packages/agents/src/adapters/local/LocalAdapter.d.ts.map +1 -0
  60. package/packages/agents/src/adapters/local/LocalAdapter.js +38 -0
  61. package/packages/agents/src/adapters/local/LocalAdapter.ts +43 -0
  62. package/packages/agents/src/adapters/ollama/OllamaCliAdapter.ts +58 -0
  63. package/packages/agents/src/adapters/ollama/OllamaCliRunner.ts +70 -0
  64. package/packages/agents/src/adapters/ollama/OllamaRemoteAdapter.ts +205 -0
  65. package/packages/agents/src/adapters/openai/.gitkeep +0 -0
  66. package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts +11 -0
  67. package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts.map +1 -0
  68. package/packages/agents/src/adapters/openai/OpenAiAdapter.js +51 -0
  69. package/packages/agents/src/adapters/openai/OpenAiAdapter.ts +56 -0
  70. package/packages/agents/src/adapters/openai/OpenAiCliAdapter.ts +62 -0
  71. package/packages/agents/src/adapters/qa/.gitkeep +0 -0
  72. package/packages/agents/src/adapters/qa/QaAdapter.d.ts +11 -0
  73. package/packages/agents/src/adapters/qa/QaAdapter.d.ts.map +1 -0
  74. package/packages/agents/src/adapters/qa/QaAdapter.js +37 -0
  75. package/packages/agents/src/adapters/qa/QaAdapter.ts +42 -0
  76. package/packages/agents/src/adapters/zhipu/ZhipuApiAdapter.ts +273 -0
  77. package/packages/agents/src/index.d.ts +8 -0
  78. package/packages/agents/src/index.d.ts.map +1 -0
  79. package/packages/agents/src/index.js +7 -0
  80. package/packages/agents/src/index.ts +11 -0
  81. package/packages/agents/tsconfig.json +14 -0
  82. package/packages/cli/CHANGELOG.md +7 -0
  83. package/packages/cli/LICENSE +21 -0
  84. package/packages/cli/README.md +23 -0
  85. package/packages/cli/package.json +61 -0
  86. package/packages/cli/src/__tests__/AgentsCommands.test.ts +137 -0
  87. package/packages/cli/src/__tests__/BacklogCommands.test.ts +40 -0
  88. package/packages/cli/src/__tests__/CodeReviewCommand.test.ts +594 -0
  89. package/packages/cli/src/__tests__/CreateTasksCommand.test.ts +40 -0
  90. package/packages/cli/src/__tests__/DocsCommands.test.ts +41 -0
  91. package/packages/cli/src/__tests__/EstimateCommands.test.ts +54 -0
  92. package/packages/cli/src/__tests__/JobsCommands.behavior.test.ts +311 -0
  93. package/packages/cli/src/__tests__/JobsCommands.test.ts +49 -0
  94. package/packages/cli/src/__tests__/MigrateTasksCommand.test.ts +36 -0
  95. package/packages/cli/src/__tests__/OpenapiCommands.test.ts +34 -0
  96. package/packages/cli/src/__tests__/OrderTasksCommand.test.ts +150 -0
  97. package/packages/cli/src/__tests__/PlanningCommands.test.ts +9 -0
  98. package/packages/cli/src/__tests__/QaTasksCommand.test.ts +58 -0
  99. package/packages/cli/src/__tests__/RefineTasksCommand.test.ts +63 -0
  100. package/packages/cli/src/__tests__/RoutingCommands.test.ts +302 -0
  101. package/packages/cli/src/__tests__/SetWorkspaceCommand.test.ts +18 -0
  102. package/packages/cli/src/__tests__/TaskShowCommands.test.ts +130 -0
  103. package/packages/cli/src/__tests__/TelemetryCommands.test.ts +35 -0
  104. package/packages/cli/src/__tests__/TestAgentCommand.test.ts +41 -0
  105. package/packages/cli/src/__tests__/UpdateCommands.test.ts +292 -0
  106. package/packages/cli/src/__tests__/WorkOnTasksCommand.test.ts +42 -0
  107. package/packages/cli/src/bin/.gitkeep +0 -0
  108. package/packages/cli/src/bin/McodaEntrypoint.ts +180 -0
  109. package/packages/cli/src/commands/agents/.gitkeep +0 -0
  110. package/packages/cli/src/commands/agents/AgentsCommands.ts +374 -0
  111. package/packages/cli/src/commands/agents/GatewayAgentCommand.ts +621 -0
  112. package/packages/cli/src/commands/agents/TestAgentCommand.ts +63 -0
  113. package/packages/cli/src/commands/backlog/.gitkeep +0 -0
  114. package/packages/cli/src/commands/backlog/BacklogCommands.ts +286 -0
  115. package/packages/cli/src/commands/backlog/OrderTasksCommand.ts +237 -0
  116. package/packages/cli/src/commands/backlog/TaskShowCommands.ts +289 -0
  117. package/packages/cli/src/commands/docs/.gitkeep +0 -0
  118. package/packages/cli/src/commands/docs/DocsCommands.ts +413 -0
  119. package/packages/cli/src/commands/estimate/EstimateCommands.ts +290 -0
  120. package/packages/cli/src/commands/jobs/.gitkeep +0 -0
  121. package/packages/cli/src/commands/jobs/JobsCommands.ts +595 -0
  122. package/packages/cli/src/commands/openapi/OpenapiCommands.ts +167 -0
  123. package/packages/cli/src/commands/planning/.gitkeep +0 -0
  124. package/packages/cli/src/commands/planning/CreateTasksCommand.ts +149 -0
  125. package/packages/cli/src/commands/planning/MigrateTasksCommand.ts +105 -0
  126. package/packages/cli/src/commands/planning/PlanningCommands.ts +1 -0
  127. package/packages/cli/src/commands/planning/QaTasksCommand.ts +320 -0
  128. package/packages/cli/src/commands/planning/RefineTasksCommand.ts +408 -0
  129. package/packages/cli/src/commands/review/CodeReviewCommand.ts +262 -0
  130. package/packages/cli/src/commands/routing/.gitkeep +0 -0
  131. package/packages/cli/src/commands/routing/RoutingCommands.ts +554 -0
  132. package/packages/cli/src/commands/telemetry/.gitkeep +0 -0
  133. package/packages/cli/src/commands/telemetry/TelemetryCommands.ts +348 -0
  134. package/packages/cli/src/commands/update/.gitkeep +0 -0
  135. package/packages/cli/src/commands/update/UpdateCommands.ts +301 -0
  136. package/packages/cli/src/commands/work/WorkOnTasksCommand.ts +264 -0
  137. package/packages/cli/src/commands/workspace/SetWorkspaceCommand.ts +132 -0
  138. package/packages/cli/src/index.ts +18 -0
  139. package/packages/cli/test/packaging_guardrails.test.js +75 -0
  140. package/packages/cli/tsconfig.json +20 -0
  141. package/packages/core/CHANGELOG.md +7 -0
  142. package/packages/core/LICENSE +21 -0
  143. package/packages/core/README.md +9 -0
  144. package/packages/core/package.json +45 -0
  145. package/packages/core/src/__tests__/SmokeClasses.test.ts +32 -0
  146. package/packages/core/src/api/AgentsApi.ts +219 -0
  147. package/packages/core/src/api/QaTasksApi.ts +38 -0
  148. package/packages/core/src/api/TasksApi.ts +35 -0
  149. package/packages/core/src/api/__tests__/AgentsApi.test.ts +203 -0
  150. package/packages/core/src/api/__tests__/QaTasksApi.test.ts +51 -0
  151. package/packages/core/src/api/__tests__/TasksApi.test.ts +56 -0
  152. package/packages/core/src/config/.gitkeep +0 -0
  153. package/packages/core/src/config/ConfigService.ts +1 -0
  154. package/packages/core/src/domain/dependencies/.gitkeep +0 -0
  155. package/packages/core/src/domain/dependencies/Dependency.ts +1 -0
  156. package/packages/core/src/domain/epics/.gitkeep +0 -0
  157. package/packages/core/src/domain/epics/Epic.ts +1 -0
  158. package/packages/core/src/domain/projects/.gitkeep +0 -0
  159. package/packages/core/src/domain/projects/Project.ts +1 -0
  160. package/packages/core/src/domain/tasks/.gitkeep +0 -0
  161. package/packages/core/src/domain/tasks/Task.ts +1 -0
  162. package/packages/core/src/domain/userStories/.gitkeep +0 -0
  163. package/packages/core/src/domain/userStories/UserStory.ts +1 -0
  164. package/packages/core/src/index.ts +27 -0
  165. package/packages/core/src/prompts/.gitkeep +0 -0
  166. package/packages/core/src/prompts/PdrPrompts.ts +23 -0
  167. package/packages/core/src/prompts/PromptLoader.ts +1 -0
  168. package/packages/core/src/prompts/SdsPrompts.ts +47 -0
  169. package/packages/core/src/services/agents/.gitkeep +0 -0
  170. package/packages/core/src/services/agents/AgentManagementService.ts +1 -0
  171. package/packages/core/src/services/agents/GatewayAgentService.ts +956 -0
  172. package/packages/core/src/services/agents/RoutingService.ts +461 -0
  173. package/packages/core/src/services/agents/__tests__/GatewayAgentService.test.ts +72 -0
  174. package/packages/core/src/services/agents/__tests__/RoutingService.test.ts +267 -0
  175. package/packages/core/src/services/agents/generated/RoutingApiClient.ts +89 -0
  176. package/packages/core/src/services/backlog/.gitkeep +0 -0
  177. package/packages/core/src/services/backlog/BacklogService.ts +580 -0
  178. package/packages/core/src/services/backlog/TaskOrderingService.ts +868 -0
  179. package/packages/core/src/services/backlog/__tests__/BacklogService.test.ts +219 -0
  180. package/packages/core/src/services/backlog/__tests__/TaskOrderingService.test.ts +268 -0
  181. package/packages/core/src/services/docs/.gitkeep +0 -0
  182. package/packages/core/src/services/docs/DocsService.ts +1913 -0
  183. package/packages/core/src/services/docs/__tests__/DocsService.test.ts +350 -0
  184. package/packages/core/src/services/estimate/EstimateService.ts +111 -0
  185. package/packages/core/src/services/estimate/VelocityService.ts +272 -0
  186. package/packages/core/src/services/estimate/__tests__/VelocityAndEstimate.test.ts +209 -0
  187. package/packages/core/src/services/estimate/types.ts +41 -0
  188. package/packages/core/src/services/execution/.gitkeep +0 -0
  189. package/packages/core/src/services/execution/ExecutionService.ts +1 -0
  190. package/packages/core/src/services/execution/QaFollowupService.ts +289 -0
  191. package/packages/core/src/services/execution/QaProfileService.ts +160 -0
  192. package/packages/core/src/services/execution/QaTasksService.ts +1303 -0
  193. package/packages/core/src/services/execution/TaskSelectionService.ts +362 -0
  194. package/packages/core/src/services/execution/TaskStateService.ts +64 -0
  195. package/packages/core/src/services/execution/WorkOnTasksService.ts +2023 -0
  196. package/packages/core/src/services/execution/__tests__/QaFollowupService.test.ts +58 -0
  197. package/packages/core/src/services/execution/__tests__/QaProfileService.test.ts +49 -0
  198. package/packages/core/src/services/execution/__tests__/QaTasksService.test.ts +157 -0
  199. package/packages/core/src/services/execution/__tests__/TaskSelectionService.test.ts +179 -0
  200. package/packages/core/src/services/execution/__tests__/TaskStateService.test.ts +51 -0
  201. package/packages/core/src/services/execution/__tests__/WorkOnTasksService.test.ts +285 -0
  202. package/packages/core/src/services/jobs/.gitkeep +0 -0
  203. package/packages/core/src/services/jobs/JobInsightsService.ts +355 -0
  204. package/packages/core/src/services/jobs/JobResumeService.ts +119 -0
  205. package/packages/core/src/services/jobs/JobService.ts +648 -0
  206. package/packages/core/src/services/jobs/JobsApiClient.ts +113 -0
  207. package/packages/core/src/services/jobs/__tests__/JobInsightsService.test.ts +17 -0
  208. package/packages/core/src/services/jobs/__tests__/JobResumeService.test.ts +45 -0
  209. package/packages/core/src/services/jobs/__tests__/JobService.test.ts +44 -0
  210. package/packages/core/src/services/openapi/OpenApiService.ts +558 -0
  211. package/packages/core/src/services/openapi/__tests__/OpenApiService.test.ts +57 -0
  212. package/packages/core/src/services/planning/.gitkeep +0 -0
  213. package/packages/core/src/services/planning/CreateTasksService.ts +1280 -0
  214. package/packages/core/src/services/planning/KeyHelpers.ts +80 -0
  215. package/packages/core/src/services/planning/PlanningService.ts +1 -0
  216. package/packages/core/src/services/planning/RefineTasksService.ts +1552 -0
  217. package/packages/core/src/services/planning/__tests__/CreateTasksService.test.ts +288 -0
  218. package/packages/core/src/services/planning/__tests__/KeyHelpers.test.ts +16 -0
  219. package/packages/core/src/services/planning/__tests__/RefineTasksService.test.ts +172 -0
  220. package/packages/core/src/services/review/CodeReviewService.ts +1386 -0
  221. package/packages/core/src/services/review/__tests__/CodeReviewService.test.ts +89 -0
  222. package/packages/core/src/services/system/SystemUpdateService.ts +177 -0
  223. package/packages/core/src/services/system/__tests__/SystemUpdateService.test.ts +40 -0
  224. package/packages/core/src/services/tasks/TaskApiResolver.ts +37 -0
  225. package/packages/core/src/services/tasks/TaskDetailService.ts +494 -0
  226. package/packages/core/src/services/tasks/__tests__/TaskApiResolver.test.ts +41 -0
  227. package/packages/core/src/services/tasks/__tests__/TaskDetailService.test.ts +178 -0
  228. package/packages/core/src/services/telemetry/.gitkeep +0 -0
  229. package/packages/core/src/services/telemetry/TelemetryService.ts +515 -0
  230. package/packages/core/src/services/telemetry/__tests__/TelemetryService.test.ts +160 -0
  231. package/packages/core/src/workspace/.gitkeep +0 -0
  232. package/packages/core/src/workspace/WorkspaceManager.ts +234 -0
  233. package/packages/core/tsconfig.json +20 -0
  234. package/packages/db/CHANGELOG.md +7 -0
  235. package/packages/db/LICENSE +21 -0
  236. package/packages/db/README.md +9 -0
  237. package/packages/db/package.json +42 -0
  238. package/packages/db/src/__tests__/GlobalRepository.test.ts +109 -0
  239. package/packages/db/src/__tests__/SchemaAlignment.test.ts +80 -0
  240. package/packages/db/src/__tests__/WorkspaceRepository.test.ts +19 -0
  241. package/packages/db/src/index.d.ts +6 -0
  242. package/packages/db/src/index.d.ts.map +1 -0
  243. package/packages/db/src/index.js +5 -0
  244. package/packages/db/src/index.ts +6 -0
  245. package/packages/db/src/migrations/global/.gitkeep +0 -0
  246. package/packages/db/src/migrations/global/GlobalMigrations.d.ts +9 -0
  247. package/packages/db/src/migrations/global/GlobalMigrations.d.ts.map +1 -0
  248. package/packages/db/src/migrations/global/GlobalMigrations.js +68 -0
  249. package/packages/db/src/migrations/global/GlobalMigrations.ts +336 -0
  250. package/packages/db/src/migrations/workspace/.gitkeep +0 -0
  251. package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts +9 -0
  252. package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts.map +1 -0
  253. package/packages/db/src/migrations/workspace/WorkspaceMigrations.js +251 -0
  254. package/packages/db/src/migrations/workspace/WorkspaceMigrations.ts +248 -0
  255. package/packages/db/src/repositories/global/.gitkeep +0 -0
  256. package/packages/db/src/repositories/global/GlobalRepository.d.ts +30 -0
  257. package/packages/db/src/repositories/global/GlobalRepository.d.ts.map +1 -0
  258. package/packages/db/src/repositories/global/GlobalRepository.js +209 -0
  259. package/packages/db/src/repositories/global/GlobalRepository.ts +492 -0
  260. package/packages/db/src/repositories/workspace/.gitkeep +0 -0
  261. package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts +282 -0
  262. package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts.map +1 -0
  263. package/packages/db/src/repositories/workspace/WorkspaceRepository.js +773 -0
  264. package/packages/db/src/repositories/workspace/WorkspaceRepository.ts +1511 -0
  265. package/packages/db/src/sqlite/connection.d.ts +11 -0
  266. package/packages/db/src/sqlite/connection.d.ts.map +1 -0
  267. package/packages/db/src/sqlite/connection.js +31 -0
  268. package/packages/db/src/sqlite/connection.ts +35 -0
  269. package/packages/db/src/sqlite/pragmas.d.ts +5 -0
  270. package/packages/db/src/sqlite/pragmas.d.ts.map +1 -0
  271. package/packages/db/src/sqlite/pragmas.js +6 -0
  272. package/packages/db/src/sqlite/pragmas.ts +10 -0
  273. package/packages/db/tsconfig.json +13 -0
  274. package/packages/generators/package.json +21 -0
  275. package/packages/generators/src/__tests__/Generators.test.ts +19 -0
  276. package/packages/generators/src/index.ts +1 -0
  277. package/packages/generators/src/openapi/generateTypes.ts +1 -0
  278. package/packages/generators/src/openapi/validateSchema.ts +1 -0
  279. package/packages/generators/src/scaffolding/docs/.gitkeep +0 -0
  280. package/packages/generators/src/scaffolding/docs/DocsScaffolder.ts +1 -0
  281. package/packages/generators/src/scaffolding/global/.gitkeep +0 -0
  282. package/packages/generators/src/scaffolding/global/GlobalScaffolder.ts +1 -0
  283. package/packages/generators/src/scaffolding/workspace/.gitkeep +0 -0
  284. package/packages/generators/src/scaffolding/workspace/WorkspaceScaffolder.ts +1 -0
  285. package/packages/generators/tsconfig.json +10 -0
  286. package/packages/integrations/CHANGELOG.md +7 -0
  287. package/packages/integrations/LICENSE +21 -0
  288. package/packages/integrations/README.md +9 -0
  289. package/packages/integrations/package.json +47 -0
  290. package/packages/integrations/src/docdex/.gitkeep +0 -0
  291. package/packages/integrations/src/docdex/DocdexClient.d.ts +50 -0
  292. package/packages/integrations/src/docdex/DocdexClient.d.ts.map +1 -0
  293. package/packages/integrations/src/docdex/DocdexClient.js +216 -0
  294. package/packages/integrations/src/docdex/DocdexClient.ts +261 -0
  295. package/packages/integrations/src/docdex/__tests__/DocdexClient.test.ts +29 -0
  296. package/packages/integrations/src/index.d.ts +2 -0
  297. package/packages/integrations/src/index.d.ts.map +1 -0
  298. package/packages/integrations/src/index.js +4 -0
  299. package/packages/integrations/src/index.ts +5 -0
  300. package/packages/integrations/src/issues/.gitkeep +0 -0
  301. package/packages/integrations/src/issues/IssuesClient.ts +1 -0
  302. package/packages/integrations/src/issues/__tests__/IssuesClient.test.ts +10 -0
  303. package/packages/integrations/src/qa/.gitkeep +0 -0
  304. package/packages/integrations/src/qa/ChromiumQaAdapter.ts +89 -0
  305. package/packages/integrations/src/qa/CliQaAdapter.ts +95 -0
  306. package/packages/integrations/src/qa/MaestroQaAdapter.ts +91 -0
  307. package/packages/integrations/src/qa/QaAdapter.ts +7 -0
  308. package/packages/integrations/src/qa/QaClient.ts +1 -0
  309. package/packages/integrations/src/qa/QaTypes.ts +26 -0
  310. package/packages/integrations/src/qa/__tests__/ChromiumQaAdapter.test.ts +30 -0
  311. package/packages/integrations/src/qa/__tests__/CliQaAdapter.test.ts +33 -0
  312. package/packages/integrations/src/qa/__tests__/MaestroQaAdapter.test.ts +30 -0
  313. package/packages/integrations/src/qa/index.ts +5 -0
  314. package/packages/integrations/src/system/SystemClient.ts +50 -0
  315. package/packages/integrations/src/system/__tests__/SystemClient.test.ts +40 -0
  316. package/packages/integrations/src/telemetry/TelemetryClient.ts +139 -0
  317. package/packages/integrations/src/telemetry/__tests__/TelemetryClient.test.ts +41 -0
  318. package/packages/integrations/src/vcs/.gitkeep +0 -0
  319. package/packages/integrations/src/vcs/VcsClient.ts +211 -0
  320. package/packages/integrations/src/vcs/__tests__/VcsClient.test.ts +26 -0
  321. package/packages/integrations/tsconfig.json +14 -0
  322. package/packages/shared/CHANGELOG.md +7 -0
  323. package/packages/shared/LICENSE +21 -0
  324. package/packages/shared/README.md +9 -0
  325. package/packages/shared/package.json +40 -0
  326. package/packages/shared/src/__tests__/CommandMetadata.test.ts +15 -0
  327. package/packages/shared/src/__tests__/ServiceShells.test.ts +16 -0
  328. package/packages/shared/src/crypto/.gitkeep +0 -0
  329. package/packages/shared/src/crypto/CryptoHelper.d.ts +15 -0
  330. package/packages/shared/src/crypto/CryptoHelper.d.ts.map +1 -0
  331. package/packages/shared/src/crypto/CryptoHelper.js +54 -0
  332. package/packages/shared/src/crypto/CryptoHelper.ts +57 -0
  333. package/packages/shared/src/errors/.gitkeep +0 -0
  334. package/packages/shared/src/errors/ErrorFactory.ts +1 -0
  335. package/packages/shared/src/index.d.ts +6 -0
  336. package/packages/shared/src/index.d.ts.map +1 -0
  337. package/packages/shared/src/index.js +4 -0
  338. package/packages/shared/src/index.ts +35 -0
  339. package/packages/shared/src/logging/.gitkeep +0 -0
  340. package/packages/shared/src/logging/Logger.ts +1 -0
  341. package/packages/shared/src/metadata/CommandMetadata.ts +165 -0
  342. package/packages/shared/src/openapi/.gitkeep +0 -0
  343. package/packages/shared/src/openapi/OpenApiTypes.d.ts +216 -0
  344. package/packages/shared/src/openapi/OpenApiTypes.d.ts.map +1 -0
  345. package/packages/shared/src/openapi/OpenApiTypes.js +1 -0
  346. package/packages/shared/src/openapi/OpenApiTypes.ts +312 -0
  347. package/packages/shared/src/paths/.gitkeep +0 -0
  348. package/packages/shared/src/paths/PathHelper.d.ts +12 -0
  349. package/packages/shared/src/paths/PathHelper.d.ts.map +1 -0
  350. package/packages/shared/src/paths/PathHelper.js +24 -0
  351. package/packages/shared/src/paths/PathHelper.ts +29 -0
  352. package/packages/shared/src/qa/QaProfile.ts +14 -0
  353. package/packages/shared/src/utils/.gitkeep +0 -0
  354. package/packages/shared/src/utils/UtilityService.ts +1 -0
  355. package/packages/shared/tsconfig.json +10 -0
  356. package/packages/testing/package.json +26 -0
  357. package/packages/testing/src/__tests__/TestingFakes.test.ts +15 -0
  358. package/packages/testing/src/cli/e2e/.gitkeep +0 -0
  359. package/packages/testing/src/cli/e2e/E2eSuite.ts +1 -0
  360. package/packages/testing/src/fakes/agents/.gitkeep +0 -0
  361. package/packages/testing/src/fakes/agents/FakeAgents.ts +1 -0
  362. package/packages/testing/src/fakes/docdex/.gitkeep +0 -0
  363. package/packages/testing/src/fakes/docdex/FakeDocdexClient.ts +1 -0
  364. package/packages/testing/src/fakes/qa/.gitkeep +0 -0
  365. package/packages/testing/src/fakes/qa/FakeQaClient.ts +1 -0
  366. package/packages/testing/src/fakes/vcs/.gitkeep +0 -0
  367. package/packages/testing/src/fakes/vcs/FakeVcsClient.ts +1 -0
  368. package/packages/testing/src/fixtures/db/.gitkeep +0 -0
  369. package/packages/testing/src/fixtures/db/DbFixtures.ts +1 -0
  370. package/packages/testing/src/fixtures/workspaces/.gitkeep +0 -0
  371. package/packages/testing/src/fixtures/workspaces/WorkspaceFixtures.ts +1 -0
  372. package/packages/testing/src/index.ts +1 -0
  373. package/packages/testing/tsconfig.json +10 -0
  374. package/pnpm-workspace.yaml +2 -0
  375. package/prompts/README.md +5 -0
  376. package/prompts/code-reviewer.md +23 -0
  377. package/prompts/code-writer.md +35 -0
  378. package/prompts/gateway-agent.md +27 -0
  379. package/prompts/qa-agent.md +21 -0
  380. package/release-please-config.json +39 -0
  381. package/scripts/build-all.ts +1 -0
  382. package/scripts/dev.ts +1 -0
  383. package/scripts/install-local-cli.sh +28 -0
  384. package/scripts/pack-npm-tarballs.js +63 -0
  385. package/scripts/release.ts +1 -0
  386. package/scripts/run-node-tests.js +37 -0
  387. package/tests/all.js +127 -0
  388. package/tests/api/openapi_spec.test.js +21 -0
  389. package/tests/artifacts.md +31 -0
  390. package/tests/component/cli_version.test.js +38 -0
  391. package/tests/integration/workspace_resolver.test.js +44 -0
  392. package/tests/unit/crypto_helper.test.js +36 -0
  393. package/tests/unit/path_helper.test.js +20 -0
  394. package/tsconfig.base.json +32 -0
@@ -0,0 +1,1511 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { Database } from "sqlite";
4
+ import { Connection } from "../../sqlite/connection.js";
5
+ import { WorkspaceMigrations } from "../../migrations/workspace/WorkspaceMigrations.js";
6
+
7
+ export type JobStatus = "queued" | "running" | "paused" | "completed" | "failed" | "cancelled" | "partial";
8
+ export type CommandStatus = "running" | "succeeded" | "failed";
9
+ export type TaskRunStatus = "queued" | "running" | "succeeded" | "failed" | "cancelled";
10
+
11
+ export interface ProjectRow {
12
+ id: string;
13
+ key: string;
14
+ name?: string;
15
+ description?: string;
16
+ metadata?: Record<string, unknown>;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ }
20
+
21
+ export interface EpicInsert {
22
+ projectId: string;
23
+ key: string;
24
+ title: string;
25
+ description: string;
26
+ storyPointsTotal?: number | null;
27
+ priority?: number | null;
28
+ metadata?: Record<string, unknown>;
29
+ }
30
+
31
+ export interface EpicRow extends EpicInsert {
32
+ id: string;
33
+ createdAt: string;
34
+ updatedAt: string;
35
+ }
36
+
37
+ export interface StoryInsert {
38
+ projectId: string;
39
+ epicId: string;
40
+ key: string;
41
+ title: string;
42
+ description: string;
43
+ acceptanceCriteria?: string | null;
44
+ storyPointsTotal?: number | null;
45
+ priority?: number | null;
46
+ metadata?: Record<string, unknown>;
47
+ }
48
+
49
+ export interface StoryRow extends StoryInsert {
50
+ id: string;
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ }
54
+
55
+ export interface TaskInsert {
56
+ projectId: string;
57
+ epicId: string;
58
+ userStoryId: string;
59
+ key: string;
60
+ title: string;
61
+ description: string;
62
+ type?: string | null;
63
+ status: string;
64
+ storyPoints?: number | null;
65
+ priority?: number | null;
66
+ assignedAgentId?: string | null;
67
+ assigneeHuman?: string | null;
68
+ vcsBranch?: string | null;
69
+ vcsBaseBranch?: string | null;
70
+ vcsLastCommitSha?: string | null;
71
+ metadata?: Record<string, unknown>;
72
+ openapiVersionAtCreation?: string | null;
73
+ }
74
+
75
+ export interface TaskRow extends TaskInsert {
76
+ id: string;
77
+ createdAt: string;
78
+ updatedAt: string;
79
+ }
80
+
81
+ export interface TaskDependencyInsert {
82
+ taskId: string;
83
+ dependsOnTaskId: string;
84
+ relationType: string;
85
+ }
86
+
87
+ export interface TaskDependencyRow extends TaskDependencyInsert {
88
+ id: string;
89
+ createdAt: string;
90
+ updatedAt: string;
91
+ }
92
+
93
+ export interface JobInsert {
94
+ workspaceId: string;
95
+ type: string;
96
+ state: JobStatus;
97
+ commandName?: string;
98
+ payload?: Record<string, unknown>;
99
+ totalItems?: number | null;
100
+ processedItems?: number | null;
101
+ lastCheckpoint?: string | null;
102
+ }
103
+
104
+ export interface JobRow extends JobInsert {
105
+ id: string;
106
+ createdAt: string;
107
+ updatedAt: string;
108
+ completedAt?: string | null;
109
+ errorSummary?: string | null;
110
+ }
111
+
112
+ export interface CommandRunInsert {
113
+ workspaceId: string;
114
+ commandName: string;
115
+ jobId?: string | null;
116
+ taskIds?: string[];
117
+ gitBranch?: string | null;
118
+ gitBaseBranch?: string | null;
119
+ startedAt: string;
120
+ status: CommandStatus;
121
+ spProcessed?: number | null;
122
+ }
123
+
124
+ export interface CommandRunRow extends CommandRunInsert {
125
+ id: string;
126
+ completedAt?: string | null;
127
+ errorSummary?: string | null;
128
+ durationSeconds?: number | null;
129
+ }
130
+
131
+ export interface TaskRunInsert {
132
+ taskId: string;
133
+ command: string;
134
+ status: TaskRunStatus;
135
+ jobId?: string | null;
136
+ commandRunId?: string | null;
137
+ agentId?: string | null;
138
+ startedAt: string;
139
+ finishedAt?: string | null;
140
+ storyPointsAtRun?: number | null;
141
+ spPerHourEffective?: number | null;
142
+ gitBranch?: string | null;
143
+ gitBaseBranch?: string | null;
144
+ gitCommitSha?: string | null;
145
+ runContext?: Record<string, unknown>;
146
+ }
147
+
148
+ export interface TaskRunRow extends TaskRunInsert {
149
+ id: string;
150
+ }
151
+
152
+ export interface TaskLockRow {
153
+ taskId: string;
154
+ taskRunId: string;
155
+ jobId?: string;
156
+ acquiredAt: string;
157
+ expiresAt: string;
158
+ }
159
+
160
+ export interface TaskQaRunInsert {
161
+ taskId: string;
162
+ taskRunId?: string | null;
163
+ jobId?: string | null;
164
+ commandRunId?: string | null;
165
+ agentId?: string | null;
166
+ modelName?: string | null;
167
+ source: string;
168
+ mode?: string | null;
169
+ profileName?: string | null;
170
+ runner?: string | null;
171
+ rawOutcome?: string | null;
172
+ recommendation?: string | null;
173
+ evidenceUrl?: string | null;
174
+ artifacts?: string[] | null;
175
+ rawResult?: Record<string, unknown> | null;
176
+ startedAt?: string | null;
177
+ finishedAt?: string | null;
178
+ metadata?: Record<string, unknown> | null;
179
+ createdAt?: string;
180
+ }
181
+
182
+ export interface TaskQaRunRow extends TaskQaRunInsert {
183
+ id: string;
184
+ }
185
+
186
+ export interface TokenUsageInsert {
187
+ workspaceId: string;
188
+ agentId?: string | null;
189
+ modelName?: string | null;
190
+ jobId?: string | null;
191
+ commandRunId?: string | null;
192
+ taskRunId?: string | null;
193
+ taskId?: string | null;
194
+ projectId?: string | null;
195
+ epicId?: string | null;
196
+ userStoryId?: string | null;
197
+ tokensPrompt?: number | null;
198
+ tokensCompletion?: number | null;
199
+ tokensTotal?: number | null;
200
+ costEstimate?: number | null;
201
+ durationSeconds?: number | null;
202
+ timestamp: string;
203
+ metadata?: Record<string, unknown>;
204
+ }
205
+
206
+ export interface TaskRevisionInsert {
207
+ taskId: string;
208
+ jobId?: string | null;
209
+ commandRunId?: string | null;
210
+ snapshotBefore?: Record<string, unknown> | null;
211
+ snapshotAfter?: Record<string, unknown> | null;
212
+ createdAt: string;
213
+ }
214
+
215
+ export interface TaskCommentInsert {
216
+ taskId: string;
217
+ taskRunId?: string | null;
218
+ jobId?: string | null;
219
+ sourceCommand: string;
220
+ authorType: "agent" | "human";
221
+ authorAgentId?: string | null;
222
+ category?: string | null;
223
+ file?: string | null;
224
+ line?: number | null;
225
+ pathHint?: string | null;
226
+ body: string;
227
+ metadata?: Record<string, unknown> | null;
228
+ createdAt: string;
229
+ resolvedAt?: string | null;
230
+ resolvedBy?: string | null;
231
+ }
232
+
233
+ export interface TaskCommentRow extends TaskCommentInsert {
234
+ id: string;
235
+ }
236
+
237
+ export interface TaskReviewInsert {
238
+ taskId: string;
239
+ jobId?: string | null;
240
+ agentId?: string | null;
241
+ modelName?: string | null;
242
+ decision: string;
243
+ summary?: string | null;
244
+ findingsJson?: Record<string, unknown> | unknown[] | null;
245
+ testRecommendationsJson?: string[] | null;
246
+ metadata?: Record<string, unknown> | null;
247
+ createdAt: string;
248
+ createdBy?: string | null;
249
+ }
250
+
251
+ export interface TaskReviewRow extends TaskReviewInsert {
252
+ id: string;
253
+ }
254
+
255
+ export class WorkspaceRepository {
256
+ private static txLocks = new Map<string, Promise<void>>();
257
+ private workspaceKey: string;
258
+
259
+ constructor(private db: Database, private connection?: Connection) {
260
+ this.workspaceKey = connection?.dbPath ?? "workspace";
261
+ }
262
+
263
+ static async create(cwd?: string): Promise<WorkspaceRepository> {
264
+ const connection = await Connection.openWorkspace(cwd);
265
+ await WorkspaceMigrations.run(connection.db);
266
+ return new WorkspaceRepository(connection.db, connection);
267
+ }
268
+
269
+ async close(): Promise<void> {
270
+ if (this.connection) {
271
+ await this.connection.close();
272
+ }
273
+ }
274
+
275
+ getDb(): Database {
276
+ return this.db;
277
+ }
278
+
279
+ private async serialize<T>(fn: () => Promise<T>): Promise<T> {
280
+ const key = this.workspaceKey;
281
+ const prev = WorkspaceRepository.txLocks.get(key) ?? Promise.resolve();
282
+ let release!: () => void;
283
+ const next = new Promise<void>((resolve) => {
284
+ release = resolve;
285
+ });
286
+ WorkspaceRepository.txLocks.set(
287
+ key,
288
+ prev
289
+ .catch(() => {
290
+ /* ignore */
291
+ })
292
+ .then(() => next),
293
+ );
294
+ try {
295
+ const result = await fn();
296
+ return result;
297
+ } finally {
298
+ release();
299
+ }
300
+ }
301
+
302
+ async withTransaction<T>(fn: () => Promise<T>): Promise<T> {
303
+ const MAX_RETRIES = 5;
304
+ const BASE_BACKOFF_MS = 200;
305
+ const run = async () => {
306
+ await this.db.exec("BEGIN IMMEDIATE");
307
+ try {
308
+ const result = await fn();
309
+ await this.db.exec("COMMIT");
310
+ return result;
311
+ } catch (error) {
312
+ await this.db.exec("ROLLBACK");
313
+ throw error;
314
+ }
315
+ };
316
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
317
+ try {
318
+ return await this.serialize(run);
319
+ } catch (error) {
320
+ const message = (error as Error).message ?? "";
321
+ const isBusy = message.includes("SQLITE_BUSY") || message.includes("database is locked") || message.includes("busy");
322
+ if (!isBusy || attempt === MAX_RETRIES) {
323
+ if (isBusy && attempt === MAX_RETRIES) {
324
+ console.warn(
325
+ `Workspace DB is busy/locked after ${MAX_RETRIES} attempts for ${this.workspaceKey}. ` +
326
+ `If another mcoda command is running, please wait and retry.`,
327
+ );
328
+ }
329
+ throw error;
330
+ }
331
+ const backoff = BASE_BACKOFF_MS * attempt;
332
+ await delay(backoff);
333
+ }
334
+ }
335
+ // Should never reach here
336
+ return this.serialize(run);
337
+ }
338
+
339
+ async getProjectByKey(key: string): Promise<ProjectRow | undefined> {
340
+ const row = await this.db.get(
341
+ `SELECT id, key, name, description, metadata_json, created_at, updated_at FROM projects WHERE key = ?`,
342
+ key,
343
+ );
344
+ if (!row) return undefined;
345
+ return {
346
+ id: row.id,
347
+ key: row.key,
348
+ name: row.name ?? undefined,
349
+ description: row.description ?? undefined,
350
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
351
+ createdAt: row.created_at,
352
+ updatedAt: row.updated_at,
353
+ };
354
+ }
355
+
356
+ async createProjectIfMissing(input: { key: string; name?: string; description?: string }): Promise<ProjectRow> {
357
+ const existing = await this.getProjectByKey(input.key);
358
+ if (existing) return existing;
359
+ const now = new Date().toISOString();
360
+ const id = randomUUID();
361
+ await this.db.run(
362
+ `INSERT INTO projects (id, key, name, description, created_at, updated_at)
363
+ VALUES (?, ?, ?, ?, ?, ?)`,
364
+ id,
365
+ input.key,
366
+ input.name ?? null,
367
+ input.description ?? null,
368
+ now,
369
+ now,
370
+ );
371
+ return {
372
+ id,
373
+ key: input.key,
374
+ name: input.name,
375
+ description: input.description,
376
+ createdAt: now,
377
+ updatedAt: now,
378
+ };
379
+ }
380
+
381
+ async insertEpics(epics: EpicInsert[], useTransaction = true): Promise<EpicRow[]> {
382
+ const now = new Date().toISOString();
383
+ const rows: EpicRow[] = [];
384
+ const run = async () => {
385
+ for (const epic of epics) {
386
+ const id = randomUUID();
387
+ await this.db.run(
388
+ `INSERT INTO epics (id, project_id, key, title, description, story_points_total, priority, metadata_json, created_at, updated_at)
389
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
390
+ id,
391
+ epic.projectId,
392
+ epic.key,
393
+ epic.title,
394
+ epic.description,
395
+ epic.storyPointsTotal ?? null,
396
+ epic.priority ?? null,
397
+ epic.metadata ? JSON.stringify(epic.metadata) : null,
398
+ now,
399
+ now,
400
+ );
401
+ rows.push({ ...epic, id, createdAt: now, updatedAt: now });
402
+ }
403
+ };
404
+ if (useTransaction) {
405
+ await this.withTransaction(run);
406
+ } else {
407
+ await run();
408
+ }
409
+ return rows;
410
+ }
411
+
412
+ async insertStories(stories: StoryInsert[], useTransaction = true): Promise<StoryRow[]> {
413
+ const now = new Date().toISOString();
414
+ const rows: StoryRow[] = [];
415
+ const run = async () => {
416
+ for (const story of stories) {
417
+ const id = randomUUID();
418
+ await this.db.run(
419
+ `INSERT INTO user_stories (id, project_id, epic_id, key, title, description, acceptance_criteria, story_points_total, priority, metadata_json, created_at, updated_at)
420
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
421
+ id,
422
+ story.projectId,
423
+ story.epicId,
424
+ story.key,
425
+ story.title,
426
+ story.description,
427
+ story.acceptanceCriteria ?? null,
428
+ story.storyPointsTotal ?? null,
429
+ story.priority ?? null,
430
+ story.metadata ? JSON.stringify(story.metadata) : null,
431
+ now,
432
+ now,
433
+ );
434
+ rows.push({ ...story, id, createdAt: now, updatedAt: now });
435
+ }
436
+ };
437
+ if (useTransaction) {
438
+ await this.withTransaction(run);
439
+ } else {
440
+ await run();
441
+ }
442
+ return rows;
443
+ }
444
+
445
+ async insertTasks(tasks: TaskInsert[], useTransaction = true): Promise<TaskRow[]> {
446
+ const now = new Date().toISOString();
447
+ const rows: TaskRow[] = [];
448
+ const run = async () => {
449
+ for (const task of tasks) {
450
+ const id = randomUUID();
451
+ await this.db.run(
452
+ `INSERT INTO tasks (id, project_id, epic_id, user_story_id, key, title, description, type, status, story_points, priority, assigned_agent_id, assignee_human, vcs_branch, vcs_base_branch, vcs_last_commit_sha, metadata_json, openapi_version_at_creation, created_at, updated_at)
453
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
454
+ id,
455
+ task.projectId,
456
+ task.epicId,
457
+ task.userStoryId,
458
+ task.key,
459
+ task.title,
460
+ task.description,
461
+ task.type ?? null,
462
+ task.status,
463
+ task.storyPoints ?? null,
464
+ task.priority ?? null,
465
+ task.assignedAgentId ?? null,
466
+ task.assigneeHuman ?? null,
467
+ task.vcsBranch ?? null,
468
+ task.vcsBaseBranch ?? null,
469
+ task.vcsLastCommitSha ?? null,
470
+ task.metadata ? JSON.stringify(task.metadata) : null,
471
+ task.openapiVersionAtCreation ?? null,
472
+ now,
473
+ now,
474
+ );
475
+ rows.push({ ...task, id, createdAt: now, updatedAt: now });
476
+ }
477
+ };
478
+ if (useTransaction) {
479
+ await this.withTransaction(run);
480
+ } else {
481
+ await run();
482
+ }
483
+ return rows;
484
+ }
485
+
486
+ async updateStoryPointsTotal(storyId: string, total: number | null): Promise<void> {
487
+ await this.db.run(`UPDATE user_stories SET story_points_total = ?, updated_at = ? WHERE id = ?`, total, new Date().toISOString(), storyId);
488
+ }
489
+
490
+ async updateEpicStoryPointsTotal(epicId: string, total: number | null): Promise<void> {
491
+ await this.db.run(`UPDATE epics SET story_points_total = ?, updated_at = ? WHERE id = ?`, total, new Date().toISOString(), epicId);
492
+ }
493
+
494
+ async insertTaskDependencies(deps: TaskDependencyInsert[], useTransaction = true): Promise<TaskDependencyRow[]> {
495
+ const now = new Date().toISOString();
496
+ const rows: TaskDependencyRow[] = [];
497
+ const run = async () => {
498
+ for (const dep of deps) {
499
+ const id = randomUUID();
500
+ await this.db.run(
501
+ `INSERT INTO task_dependencies (id, task_id, depends_on_task_id, relation_type, created_at, updated_at)
502
+ VALUES (?, ?, ?, ?, ?, ?)`,
503
+ id,
504
+ dep.taskId,
505
+ dep.dependsOnTaskId,
506
+ dep.relationType,
507
+ now,
508
+ now,
509
+ );
510
+ rows.push({ ...dep, id, createdAt: now, updatedAt: now });
511
+ }
512
+ };
513
+ if (useTransaction) {
514
+ await this.withTransaction(run);
515
+ } else {
516
+ await run();
517
+ }
518
+ return rows;
519
+ }
520
+
521
+ async deleteTaskDependenciesForTask(taskId: string): Promise<void> {
522
+ await this.db.run(
523
+ `DELETE FROM task_dependencies WHERE task_id = ? OR depends_on_task_id = ?`,
524
+ taskId,
525
+ taskId,
526
+ );
527
+ }
528
+
529
+ async deleteProjectBacklog(projectId: string, useTransaction = true): Promise<void> {
530
+ const run = async () => {
531
+ // Remove task-related rows first to satisfy foreign keys.
532
+ await this.db.run(
533
+ `DELETE FROM task_dependencies WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)
534
+ OR depends_on_task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
535
+ projectId,
536
+ projectId,
537
+ );
538
+ await this.db.run(
539
+ `DELETE FROM task_runs WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
540
+ projectId,
541
+ );
542
+ await this.db.run(
543
+ `DELETE FROM task_qa_runs WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
544
+ projectId,
545
+ );
546
+ await this.db.run(
547
+ `DELETE FROM task_revisions WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
548
+ projectId,
549
+ );
550
+ await this.db.run(
551
+ `DELETE FROM task_comments WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
552
+ projectId,
553
+ );
554
+ await this.db.run(
555
+ `DELETE FROM task_reviews WHERE task_id IN (SELECT id FROM tasks WHERE project_id = ?)`,
556
+ projectId,
557
+ );
558
+ await this.db.run(`DELETE FROM tasks WHERE project_id = ?`, projectId);
559
+ await this.db.run(`DELETE FROM user_stories WHERE project_id = ?`, projectId);
560
+ await this.db.run(`DELETE FROM epics WHERE project_id = ?`, projectId);
561
+ };
562
+ if (useTransaction) {
563
+ await this.withTransaction(run);
564
+ } else {
565
+ await run();
566
+ }
567
+ }
568
+
569
+ async updateTask(
570
+ taskId: string,
571
+ updates: {
572
+ title?: string;
573
+ description?: string | null;
574
+ type?: string | null;
575
+ status?: string;
576
+ storyPoints?: number | null;
577
+ priority?: number | null;
578
+ metadata?: Record<string, unknown> | null;
579
+ assignedAgentId?: string | null;
580
+ assigneeHuman?: string | null;
581
+ vcsBranch?: string | null;
582
+ vcsBaseBranch?: string | null;
583
+ vcsLastCommitSha?: string | null;
584
+ },
585
+ ): Promise<void> {
586
+ const fields: string[] = [];
587
+ const params: any[] = [];
588
+ if (updates.title !== undefined) {
589
+ fields.push("title = ?");
590
+ params.push(updates.title);
591
+ }
592
+ if (updates.description !== undefined) {
593
+ fields.push("description = ?");
594
+ params.push(updates.description);
595
+ }
596
+ if (updates.type !== undefined) {
597
+ fields.push("type = ?");
598
+ params.push(updates.type);
599
+ }
600
+ if (updates.status !== undefined) {
601
+ fields.push("status = ?");
602
+ params.push(updates.status);
603
+ }
604
+ if (updates.storyPoints !== undefined) {
605
+ fields.push("story_points = ?");
606
+ params.push(updates.storyPoints);
607
+ }
608
+ if (updates.priority !== undefined) {
609
+ fields.push("priority = ?");
610
+ params.push(updates.priority);
611
+ }
612
+ if (updates.metadata !== undefined) {
613
+ fields.push("metadata_json = ?");
614
+ params.push(updates.metadata ? JSON.stringify(updates.metadata) : null);
615
+ }
616
+ if (updates.assignedAgentId !== undefined) {
617
+ fields.push("assigned_agent_id = ?");
618
+ params.push(updates.assignedAgentId);
619
+ }
620
+ if (updates.assigneeHuman !== undefined) {
621
+ fields.push("assignee_human = ?");
622
+ params.push(updates.assigneeHuman);
623
+ }
624
+ if (updates.vcsBranch !== undefined) {
625
+ fields.push("vcs_branch = ?");
626
+ params.push(updates.vcsBranch);
627
+ }
628
+ if (updates.vcsBaseBranch !== undefined) {
629
+ fields.push("vcs_base_branch = ?");
630
+ params.push(updates.vcsBaseBranch);
631
+ }
632
+ if (updates.vcsLastCommitSha !== undefined) {
633
+ fields.push("vcs_last_commit_sha = ?");
634
+ params.push(updates.vcsLastCommitSha);
635
+ }
636
+ if (fields.length === 0) return;
637
+ fields.push("updated_at = ?");
638
+ params.push(new Date().toISOString());
639
+ params.push(taskId);
640
+ await this.db.run(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`, ...params);
641
+ }
642
+
643
+ async getTaskById(taskId: string): Promise<TaskRow | undefined> {
644
+ const row = await this.db.get(
645
+ `SELECT id, project_id, epic_id, user_story_id, key, title, description, type, status, story_points, priority, assigned_agent_id, assignee_human, vcs_branch, vcs_base_branch, vcs_last_commit_sha, metadata_json, openapi_version_at_creation, created_at, updated_at
646
+ FROM tasks WHERE id = ?`,
647
+ taskId,
648
+ );
649
+ if (!row) return undefined;
650
+ return {
651
+ id: row.id,
652
+ projectId: row.project_id,
653
+ epicId: row.epic_id,
654
+ userStoryId: row.user_story_id,
655
+ key: row.key,
656
+ title: row.title,
657
+ description: row.description ?? undefined,
658
+ type: row.type ?? undefined,
659
+ status: row.status,
660
+ storyPoints: row.story_points ?? undefined,
661
+ priority: row.priority ?? undefined,
662
+ assignedAgentId: row.assigned_agent_id ?? undefined,
663
+ assigneeHuman: row.assignee_human ?? undefined,
664
+ vcsBranch: row.vcs_branch ?? undefined,
665
+ vcsBaseBranch: row.vcs_base_branch ?? undefined,
666
+ vcsLastCommitSha: row.vcs_last_commit_sha ?? undefined,
667
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
668
+ openapiVersionAtCreation: row.openapi_version_at_creation ?? undefined,
669
+ createdAt: row.created_at,
670
+ updatedAt: row.updated_at,
671
+ };
672
+ }
673
+
674
+ async getTaskByKey(taskKey: string): Promise<TaskRow | undefined> {
675
+ const row = await this.db.get(
676
+ `SELECT id, project_id, epic_id, user_story_id, key, title, description, type, status, story_points, priority, assigned_agent_id, assignee_human, vcs_branch, vcs_base_branch, vcs_last_commit_sha, metadata_json, openapi_version_at_creation, created_at, updated_at
677
+ FROM tasks WHERE key = ?`,
678
+ taskKey,
679
+ );
680
+ if (!row) return undefined;
681
+ return {
682
+ id: row.id,
683
+ projectId: row.project_id,
684
+ epicId: row.epic_id,
685
+ userStoryId: row.user_story_id,
686
+ key: row.key,
687
+ title: row.title,
688
+ description: row.description ?? undefined,
689
+ type: row.type ?? undefined,
690
+ status: row.status,
691
+ storyPoints: row.story_points ?? undefined,
692
+ priority: row.priority ?? undefined,
693
+ assignedAgentId: row.assigned_agent_id ?? undefined,
694
+ assigneeHuman: row.assignee_human ?? undefined,
695
+ vcsBranch: row.vcs_branch ?? undefined,
696
+ vcsBaseBranch: row.vcs_base_branch ?? undefined,
697
+ vcsLastCommitSha: row.vcs_last_commit_sha ?? undefined,
698
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
699
+ openapiVersionAtCreation: row.openapi_version_at_creation ?? undefined,
700
+ createdAt: row.created_at,
701
+ updatedAt: row.updated_at,
702
+ };
703
+ }
704
+
705
+ async getTasksByIds(taskIds: string[]): Promise<TaskRow[]> {
706
+ if (!taskIds.length) return [];
707
+ const placeholders = taskIds.map(() => "?").join(", ");
708
+ const rows = await this.db.all(
709
+ `SELECT id, project_id, epic_id, user_story_id, key, title, description, type, status, story_points, priority, assigned_agent_id, assignee_human, vcs_branch, vcs_base_branch, vcs_last_commit_sha, metadata_json, openapi_version_at_creation, created_at, updated_at
710
+ FROM tasks WHERE id IN (${placeholders})`,
711
+ ...taskIds,
712
+ );
713
+ return rows.map((row: any) => ({
714
+ id: row.id,
715
+ projectId: row.project_id,
716
+ epicId: row.epic_id,
717
+ userStoryId: row.user_story_id,
718
+ key: row.key,
719
+ title: row.title,
720
+ description: row.description ?? undefined,
721
+ type: row.type ?? undefined,
722
+ status: row.status,
723
+ storyPoints: row.story_points ?? undefined,
724
+ priority: row.priority ?? undefined,
725
+ assignedAgentId: row.assigned_agent_id ?? undefined,
726
+ assigneeHuman: row.assignee_human ?? undefined,
727
+ vcsBranch: row.vcs_branch ?? undefined,
728
+ vcsBaseBranch: row.vcs_base_branch ?? undefined,
729
+ vcsLastCommitSha: row.vcs_last_commit_sha ?? undefined,
730
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
731
+ openapiVersionAtCreation: row.openapi_version_at_creation ?? undefined,
732
+ createdAt: row.created_at,
733
+ updatedAt: row.updated_at,
734
+ }));
735
+ }
736
+
737
+ async listEpicKeys(projectId: string): Promise<string[]> {
738
+ const rows = await this.db.all(`SELECT key FROM epics WHERE project_id = ? ORDER BY key`, projectId);
739
+ return rows.map((r: any) => r.key as string);
740
+ }
741
+
742
+ async listStoryKeys(epicId: string): Promise<string[]> {
743
+ const rows = await this.db.all(`SELECT key FROM user_stories WHERE epic_id = ? ORDER BY key`, epicId);
744
+ return rows.map((r: any) => r.key as string);
745
+ }
746
+
747
+ async listTaskKeys(userStoryId: string): Promise<string[]> {
748
+ const rows = await this.db.all(`SELECT key FROM tasks WHERE user_story_id = ? ORDER BY key`, userStoryId);
749
+ return rows.map((r: any) => r.key as string);
750
+ }
751
+
752
+ async createJob(record: JobInsert): Promise<JobRow> {
753
+ const now = new Date().toISOString();
754
+ const id = randomUUID();
755
+ await this.db.run(
756
+ `INSERT INTO jobs (id, workspace_id, type, state, command_name, payload_json, total_items, processed_items, last_checkpoint, created_at, updated_at)
757
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
758
+ id,
759
+ record.workspaceId,
760
+ record.type,
761
+ record.state,
762
+ record.commandName ?? null,
763
+ record.payload ? JSON.stringify(record.payload) : null,
764
+ record.totalItems ?? null,
765
+ record.processedItems ?? null,
766
+ record.lastCheckpoint ?? null,
767
+ now,
768
+ now,
769
+ );
770
+ return {
771
+ id,
772
+ ...record,
773
+ createdAt: now,
774
+ updatedAt: now,
775
+ completedAt: null,
776
+ errorSummary: null,
777
+ };
778
+ }
779
+
780
+ async listJobs(): Promise<JobRow[]> {
781
+ const rows = await this.db.all(
782
+ `SELECT id, workspace_id, type, state, command_name, payload_json, total_items, processed_items, last_checkpoint, created_at, updated_at, completed_at, error_summary
783
+ FROM jobs ORDER BY updated_at DESC`,
784
+ );
785
+ return rows.map((row: any) => ({
786
+ id: row.id,
787
+ workspaceId: row.workspace_id,
788
+ type: row.type,
789
+ state: row.state,
790
+ commandName: row.command_name ?? undefined,
791
+ payload: row.payload_json ? JSON.parse(row.payload_json) : undefined,
792
+ totalItems: row.total_items ?? undefined,
793
+ processedItems: row.processed_items ?? undefined,
794
+ lastCheckpoint: row.last_checkpoint ?? undefined,
795
+ createdAt: row.created_at,
796
+ updatedAt: row.updated_at,
797
+ completedAt: row.completed_at ?? undefined,
798
+ errorSummary: row.error_summary ?? undefined,
799
+ }));
800
+ }
801
+
802
+ async getJob(id: string): Promise<JobRow | undefined> {
803
+ const row = await this.db.get(
804
+ `SELECT id, workspace_id, type, state, command_name, payload_json, total_items, processed_items, last_checkpoint, created_at, updated_at, completed_at, error_summary
805
+ FROM jobs WHERE id = ?`,
806
+ id,
807
+ );
808
+ if (!row) return undefined;
809
+ return {
810
+ id: row.id,
811
+ workspaceId: row.workspace_id,
812
+ type: row.type,
813
+ state: row.state,
814
+ commandName: row.command_name ?? undefined,
815
+ payload: row.payload_json ? JSON.parse(row.payload_json) : undefined,
816
+ totalItems: row.total_items ?? undefined,
817
+ processedItems: row.processed_items ?? undefined,
818
+ lastCheckpoint: row.last_checkpoint ?? undefined,
819
+ createdAt: row.created_at,
820
+ updatedAt: row.updated_at,
821
+ completedAt: row.completed_at ?? undefined,
822
+ errorSummary: row.error_summary ?? undefined,
823
+ };
824
+ }
825
+
826
+ async updateJobState(id: string, update: Partial<JobInsert> & { state?: JobStatus; errorSummary?: string | null; completedAt?: string | null }): Promise<void> {
827
+ const existing = await this.db.get(`SELECT payload_json FROM jobs WHERE id = ?`, id);
828
+ const payload = existing?.payload_json ? JSON.parse(existing.payload_json) : undefined;
829
+ const mergedPayload =
830
+ update.payload !== undefined ? { ...(payload ?? {}), ...(update.payload ?? {}) } : payload;
831
+ const fields: string[] = [];
832
+ const params: any[] = [];
833
+ if (update.state !== undefined) {
834
+ fields.push("state = ?");
835
+ params.push(update.state);
836
+ }
837
+ if (update.commandName !== undefined) {
838
+ fields.push("command_name = ?");
839
+ params.push(update.commandName ?? null);
840
+ }
841
+ if (update.totalItems !== undefined) {
842
+ fields.push("total_items = ?");
843
+ params.push(update.totalItems ?? null);
844
+ }
845
+ if (update.processedItems !== undefined) {
846
+ fields.push("processed_items = ?");
847
+ params.push(update.processedItems ?? null);
848
+ }
849
+ if (update.lastCheckpoint !== undefined) {
850
+ fields.push("last_checkpoint = ?");
851
+ params.push(update.lastCheckpoint ?? null);
852
+ }
853
+ if (update.errorSummary !== undefined) {
854
+ fields.push("error_summary = ?");
855
+ params.push(update.errorSummary ?? null);
856
+ }
857
+ if (update.completedAt !== undefined) {
858
+ fields.push("completed_at = ?");
859
+ params.push(update.completedAt ?? null);
860
+ }
861
+ if (mergedPayload !== undefined) {
862
+ fields.push("payload_json = ?");
863
+ params.push(JSON.stringify(mergedPayload));
864
+ }
865
+ fields.push("updated_at = ?");
866
+ params.push(new Date().toISOString());
867
+ params.push(id);
868
+ await this.db.run(`UPDATE jobs SET ${fields.join(", ")} WHERE id = ?`, ...params);
869
+ }
870
+
871
+ async createCommandRun(record: CommandRunInsert): Promise<CommandRunRow> {
872
+ const id = randomUUID();
873
+ await this.db.run(
874
+ `INSERT INTO command_runs (id, workspace_id, command_name, job_id, task_ids_json, git_branch, git_base_branch, started_at, status, sp_processed)
875
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
876
+ id,
877
+ record.workspaceId,
878
+ record.commandName,
879
+ record.jobId ?? null,
880
+ record.taskIds ? JSON.stringify(record.taskIds) : null,
881
+ record.gitBranch ?? null,
882
+ record.gitBaseBranch ?? null,
883
+ record.startedAt,
884
+ record.status,
885
+ record.spProcessed ?? null,
886
+ );
887
+ return { id, ...record, completedAt: null, errorSummary: null, durationSeconds: null };
888
+ }
889
+
890
+ async setCommandRunJobId(id: string, jobId: string): Promise<void> {
891
+ await this.db.run(`UPDATE command_runs SET job_id = ? WHERE id = ?`, jobId, id);
892
+ }
893
+
894
+ async completeCommandRun(
895
+ id: string,
896
+ update: {
897
+ status: CommandStatus;
898
+ completedAt: string;
899
+ errorSummary?: string | null;
900
+ durationSeconds?: number | null;
901
+ spProcessed?: number | null;
902
+ },
903
+ ): Promise<void> {
904
+ await this.db.run(
905
+ `UPDATE command_runs
906
+ SET status = ?, completed_at = ?, error_summary = ?, duration_seconds = ?, sp_processed = ?
907
+ WHERE id = ?`,
908
+ update.status,
909
+ update.completedAt,
910
+ update.errorSummary ?? null,
911
+ update.durationSeconds ?? null,
912
+ update.spProcessed ?? null,
913
+ id,
914
+ );
915
+ }
916
+
917
+ async getTasksWithRelations(taskIds: string[]): Promise<
918
+ Array<
919
+ TaskRow & {
920
+ epicKey: string;
921
+ storyKey: string;
922
+ epicTitle?: string;
923
+ epicDescription?: string;
924
+ storyTitle?: string;
925
+ storyDescription?: string;
926
+ acceptanceCriteria?: string[];
927
+ }
928
+ >
929
+ > {
930
+ if (!taskIds.length) return [];
931
+ const placeholders = taskIds.map(() => "?").join(", ");
932
+ const rows = await this.db.all<any[]>(
933
+ `
934
+ SELECT
935
+ t.id as task_id,
936
+ t.project_id as project_id,
937
+ t.key as task_key,
938
+ t.status as task_status,
939
+ t.priority as task_priority,
940
+ t.story_points as task_story_points,
941
+ t.created_at as task_created_at,
942
+ t.updated_at as task_updated_at,
943
+ t.description as task_description,
944
+ t.title as task_title,
945
+ t.type as task_type,
946
+ t.metadata_json as task_metadata,
947
+ t.assigned_agent_id as task_assigned_agent_id,
948
+ t.assignee_human as task_assignee_human,
949
+ t.vcs_branch as task_vcs_branch,
950
+ t.vcs_base_branch as task_vcs_base_branch,
951
+ t.vcs_last_commit_sha as task_vcs_last_commit_sha,
952
+ e.id as epic_id,
953
+ e.key as epic_key,
954
+ e.title as epic_title,
955
+ e.description as epic_description,
956
+ us.id as story_id,
957
+ us.key as story_key,
958
+ us.title as story_title,
959
+ us.description as story_description,
960
+ us.acceptance_criteria as story_acceptance
961
+ FROM tasks t
962
+ JOIN epics e ON e.id = t.epic_id
963
+ JOIN user_stories us ON us.id = t.user_story_id
964
+ WHERE t.id IN (${placeholders})
965
+ `,
966
+ ...taskIds,
967
+ );
968
+
969
+ return rows.map((row) => ({
970
+ id: row.task_id,
971
+ projectId: row.project_id,
972
+ epicId: row.epic_id,
973
+ userStoryId: row.story_id,
974
+ key: row.task_key,
975
+ title: row.task_title,
976
+ description: row.task_description ?? "",
977
+ type: row.task_type ?? undefined,
978
+ status: row.task_status,
979
+ storyPoints: row.task_story_points ?? undefined,
980
+ priority: row.task_priority ?? undefined,
981
+ assignedAgentId: row.task_assigned_agent_id ?? undefined,
982
+ assigneeHuman: row.task_assignee_human ?? undefined,
983
+ vcsBranch: row.task_vcs_branch ?? undefined,
984
+ vcsBaseBranch: row.task_vcs_base_branch ?? undefined,
985
+ vcsLastCommitSha: row.task_vcs_last_commit_sha ?? undefined,
986
+ metadata: row.task_metadata ? JSON.parse(row.task_metadata) : undefined,
987
+ openapiVersionAtCreation: undefined,
988
+ createdAt: row.task_created_at,
989
+ updatedAt: row.task_updated_at,
990
+ epicKey: row.epic_key,
991
+ storyKey: row.story_key,
992
+ epicTitle: row.epic_title ?? undefined,
993
+ epicDescription: row.epic_description ?? undefined,
994
+ storyTitle: row.story_title ?? undefined,
995
+ storyDescription: row.story_description ?? undefined,
996
+ acceptanceCriteria: row.story_acceptance
997
+ ? (row.story_acceptance as string)
998
+ .split(/\r?\n/)
999
+ .map((s) => s.trim())
1000
+ .filter(Boolean)
1001
+ : undefined,
1002
+ }));
1003
+ }
1004
+
1005
+ async createTaskRun(record: TaskRunInsert): Promise<TaskRunRow> {
1006
+ const id = randomUUID();
1007
+ await this.db.run(
1008
+ `INSERT INTO task_runs (id, task_id, command, job_id, command_run_id, agent_id, status, started_at, finished_at, story_points_at_run, sp_per_hour_effective, git_branch, git_base_branch, git_commit_sha, run_context_json)
1009
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1010
+ id,
1011
+ record.taskId,
1012
+ record.command,
1013
+ record.jobId ?? null,
1014
+ record.commandRunId ?? null,
1015
+ record.agentId ?? null,
1016
+ record.status,
1017
+ record.startedAt,
1018
+ record.finishedAt ?? null,
1019
+ record.storyPointsAtRun ?? null,
1020
+ record.spPerHourEffective ?? null,
1021
+ record.gitBranch ?? null,
1022
+ record.gitBaseBranch ?? null,
1023
+ record.gitCommitSha ?? null,
1024
+ record.runContext ? JSON.stringify(record.runContext) : null,
1025
+ );
1026
+ return { id, ...record };
1027
+ }
1028
+
1029
+ async getTaskLock(taskId: string): Promise<TaskLockRow | undefined> {
1030
+ const row = await this.db.get(
1031
+ `SELECT task_id, task_run_id, job_id, acquired_at, expires_at FROM task_locks WHERE task_id = ?`,
1032
+ taskId,
1033
+ );
1034
+ if (!row) return undefined;
1035
+ return {
1036
+ taskId: row.task_id,
1037
+ taskRunId: row.task_run_id,
1038
+ jobId: row.job_id ?? undefined,
1039
+ acquiredAt: row.acquired_at,
1040
+ expiresAt: row.expires_at,
1041
+ };
1042
+ }
1043
+
1044
+ async tryAcquireTaskLock(
1045
+ taskId: string,
1046
+ taskRunId: string,
1047
+ jobId?: string | null,
1048
+ ttlSeconds = 3600,
1049
+ ): Promise<{ acquired: boolean; lock?: TaskLockRow }> {
1050
+ const nowIso = new Date().toISOString();
1051
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
1052
+ return this.withTransaction(async () => {
1053
+ const result = await this.db.run(
1054
+ `INSERT INTO task_locks (task_id, task_run_id, job_id, acquired_at, expires_at)
1055
+ VALUES (?, ?, ?, ?, ?)
1056
+ ON CONFLICT(task_id) DO UPDATE SET
1057
+ task_run_id = excluded.task_run_id,
1058
+ job_id = excluded.job_id,
1059
+ acquired_at = excluded.acquired_at,
1060
+ expires_at = excluded.expires_at
1061
+ WHERE task_locks.expires_at < ?`,
1062
+ taskId,
1063
+ taskRunId,
1064
+ jobId ?? null,
1065
+ nowIso,
1066
+ expiresAt,
1067
+ nowIso,
1068
+ );
1069
+ if (result?.changes && result.changes > 0) {
1070
+ return {
1071
+ acquired: true,
1072
+ lock: {
1073
+ taskId,
1074
+ taskRunId,
1075
+ jobId: jobId ?? undefined,
1076
+ acquiredAt: nowIso,
1077
+ expiresAt,
1078
+ },
1079
+ };
1080
+ }
1081
+ const existing = await this.getTaskLock(taskId);
1082
+ return { acquired: false, lock: existing };
1083
+ });
1084
+ }
1085
+
1086
+ async releaseTaskLock(taskId: string, taskRunId: string): Promise<void> {
1087
+ await this.db.run(`DELETE FROM task_locks WHERE task_id = ? AND task_run_id = ?`, taskId, taskRunId);
1088
+ }
1089
+
1090
+ async refreshTaskLock(taskId: string, taskRunId: string, ttlSeconds = 3600): Promise<boolean> {
1091
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
1092
+ const result = await this.db.run(
1093
+ `UPDATE task_locks SET expires_at = ? WHERE task_id = ? AND task_run_id = ?`,
1094
+ expiresAt,
1095
+ taskId,
1096
+ taskRunId,
1097
+ );
1098
+ return Boolean(result?.changes && result.changes > 0);
1099
+ }
1100
+
1101
+ async createTaskQaRun(record: TaskQaRunInsert): Promise<TaskQaRunRow> {
1102
+ const id = randomUUID();
1103
+ const createdAt = record.createdAt ?? new Date().toISOString();
1104
+ await this.db.run(
1105
+ `INSERT INTO task_qa_runs (id, task_id, task_run_id, job_id, command_run_id, agent_id, model_name, source, mode, profile_name, runner, raw_outcome, recommendation, evidence_url, artifacts_json, raw_result_json, started_at, finished_at, metadata_json, created_at)
1106
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1107
+ id,
1108
+ record.taskId,
1109
+ record.taskRunId ?? null,
1110
+ record.jobId ?? null,
1111
+ record.commandRunId ?? null,
1112
+ record.agentId ?? null,
1113
+ record.modelName ?? null,
1114
+ record.source,
1115
+ record.mode ?? null,
1116
+ record.profileName ?? null,
1117
+ record.runner ?? null,
1118
+ record.rawOutcome ?? null,
1119
+ record.recommendation ?? null,
1120
+ record.evidenceUrl ?? null,
1121
+ record.artifacts ? JSON.stringify(record.artifacts) : null,
1122
+ record.rawResult ? JSON.stringify(record.rawResult) : null,
1123
+ record.startedAt ?? null,
1124
+ record.finishedAt ?? null,
1125
+ record.metadata ? JSON.stringify(record.metadata) : null,
1126
+ createdAt,
1127
+ );
1128
+ return { id, ...record, createdAt };
1129
+ }
1130
+
1131
+ async listTaskQaRuns(taskId: string): Promise<TaskQaRunRow[]> {
1132
+ const rows = await this.db.all(
1133
+ `SELECT id, task_id, task_run_id, job_id, command_run_id, agent_id, model_name, source, mode, profile_name, runner, raw_outcome, recommendation, evidence_url, artifacts_json, raw_result_json, started_at, finished_at, metadata_json, created_at
1134
+ FROM task_qa_runs
1135
+ WHERE task_id = ?
1136
+ ORDER BY created_at DESC`,
1137
+ taskId,
1138
+ );
1139
+ return rows.map((row: any) => ({
1140
+ id: row.id,
1141
+ taskId: row.task_id,
1142
+ taskRunId: row.task_run_id ?? undefined,
1143
+ jobId: row.job_id ?? undefined,
1144
+ commandRunId: row.command_run_id ?? undefined,
1145
+ agentId: row.agent_id ?? undefined,
1146
+ modelName: row.model_name ?? undefined,
1147
+ source: row.source,
1148
+ mode: row.mode ?? undefined,
1149
+ profileName: row.profile_name ?? undefined,
1150
+ runner: row.runner ?? undefined,
1151
+ rawOutcome: row.raw_outcome ?? undefined,
1152
+ recommendation: row.recommendation ?? undefined,
1153
+ evidenceUrl: row.evidence_url ?? undefined,
1154
+ artifacts: row.artifacts_json ? JSON.parse(row.artifacts_json) : undefined,
1155
+ rawResult: row.raw_result_json ? JSON.parse(row.raw_result_json) : undefined,
1156
+ startedAt: row.started_at ?? undefined,
1157
+ finishedAt: row.finished_at ?? undefined,
1158
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1159
+ createdAt: row.created_at,
1160
+ }));
1161
+ }
1162
+
1163
+ async listTaskQaRunsForJob(taskIds: string[], jobId: string): Promise<TaskQaRunRow[]> {
1164
+ if (!taskIds.length) return [];
1165
+ const placeholders = taskIds.map(() => '?').join(', ');
1166
+ const rows = await this.db.all(
1167
+ `SELECT id, task_id, task_run_id, job_id, command_run_id, agent_id, model_name, source, mode, profile_name, runner, raw_outcome, recommendation, evidence_url, artifacts_json, raw_result_json, started_at, finished_at, metadata_json, created_at
1168
+ FROM task_qa_runs
1169
+ WHERE job_id = ? AND task_id IN (${placeholders})
1170
+ ORDER BY created_at DESC`,
1171
+ jobId,
1172
+ ...taskIds,
1173
+ );
1174
+ return rows.map((row: any) => ({
1175
+ id: row.id,
1176
+ taskId: row.task_id,
1177
+ taskRunId: row.task_run_id ?? undefined,
1178
+ jobId: row.job_id ?? undefined,
1179
+ commandRunId: row.command_run_id ?? undefined,
1180
+ agentId: row.agent_id ?? undefined,
1181
+ modelName: row.model_name ?? undefined,
1182
+ source: row.source,
1183
+ mode: row.mode ?? undefined,
1184
+ profileName: row.profile_name ?? undefined,
1185
+ runner: row.runner ?? undefined,
1186
+ rawOutcome: row.raw_outcome ?? undefined,
1187
+ recommendation: row.recommendation ?? undefined,
1188
+ evidenceUrl: row.evidence_url ?? undefined,
1189
+ artifacts: row.artifacts_json ? JSON.parse(row.artifacts_json) : undefined,
1190
+ rawResult: row.raw_result_json ? JSON.parse(row.raw_result_json) : undefined,
1191
+ startedAt: row.started_at ?? undefined,
1192
+ finishedAt: row.finished_at ?? undefined,
1193
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1194
+ createdAt: row.created_at,
1195
+ }));
1196
+ }
1197
+
1198
+ async insertTaskLog(entry: {
1199
+ taskRunId: string;
1200
+ sequence: number;
1201
+ timestamp: string;
1202
+ level?: string | null;
1203
+ source?: string | null;
1204
+ message?: string | null;
1205
+ details?: Record<string, unknown> | null;
1206
+ }): Promise<void> {
1207
+ const id = randomUUID();
1208
+ await this.db.run(
1209
+ `INSERT INTO task_logs (id, task_run_id, sequence, timestamp, level, source, message, details_json)
1210
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1211
+ id,
1212
+ entry.taskRunId,
1213
+ entry.sequence,
1214
+ entry.timestamp,
1215
+ entry.level ?? null,
1216
+ entry.source ?? null,
1217
+ entry.message ?? null,
1218
+ entry.details ? JSON.stringify(entry.details) : null,
1219
+ );
1220
+ }
1221
+
1222
+ async updateTaskRun(
1223
+ id: string,
1224
+ update: {
1225
+ status?: TaskRunStatus;
1226
+ finishedAt?: string | null;
1227
+ gitBranch?: string | null;
1228
+ gitBaseBranch?: string | null;
1229
+ gitCommitSha?: string | null;
1230
+ storyPointsAtRun?: number | null;
1231
+ spPerHourEffective?: number | null;
1232
+ runContext?: Record<string, unknown> | null;
1233
+ },
1234
+ ): Promise<void> {
1235
+ const fields: string[] = [];
1236
+ const params: any[] = [];
1237
+ if (update.status !== undefined) {
1238
+ fields.push("status = ?");
1239
+ params.push(update.status);
1240
+ }
1241
+ if (update.finishedAt !== undefined) {
1242
+ fields.push("finished_at = ?");
1243
+ params.push(update.finishedAt);
1244
+ }
1245
+ if (update.gitBranch !== undefined) {
1246
+ fields.push("git_branch = ?");
1247
+ params.push(update.gitBranch);
1248
+ }
1249
+ if (update.gitBaseBranch !== undefined) {
1250
+ fields.push("git_base_branch = ?");
1251
+ params.push(update.gitBaseBranch);
1252
+ }
1253
+ if (update.gitCommitSha !== undefined) {
1254
+ fields.push("git_commit_sha = ?");
1255
+ params.push(update.gitCommitSha);
1256
+ }
1257
+ if (update.storyPointsAtRun !== undefined) {
1258
+ fields.push("story_points_at_run = ?");
1259
+ params.push(update.storyPointsAtRun);
1260
+ }
1261
+ if (update.spPerHourEffective !== undefined) {
1262
+ fields.push("sp_per_hour_effective = ?");
1263
+ params.push(update.spPerHourEffective);
1264
+ }
1265
+ if (update.runContext !== undefined) {
1266
+ fields.push("run_context_json = ?");
1267
+ params.push(update.runContext ? JSON.stringify(update.runContext) : null);
1268
+ }
1269
+ if (!fields.length) return;
1270
+ const clauses = fields.join(", ");
1271
+ params.push(id);
1272
+ await this.db.run(`UPDATE task_runs SET ${clauses} WHERE id = ?`, ...params);
1273
+ }
1274
+
1275
+ async getTaskDependencies(taskIds: string[]): Promise<TaskDependencyRow[]> {
1276
+ if (!taskIds.length) return [];
1277
+ const placeholders = taskIds.map(() => "?").join(", ");
1278
+ const rows = await this.db.all(
1279
+ `SELECT id, task_id, depends_on_task_id, relation_type, created_at, updated_at
1280
+ FROM task_dependencies
1281
+ WHERE task_id IN (${placeholders})`,
1282
+ ...taskIds,
1283
+ );
1284
+ return rows.map((row: any) => ({
1285
+ id: row.id,
1286
+ taskId: row.task_id,
1287
+ dependsOnTaskId: row.depends_on_task_id,
1288
+ relationType: row.relation_type,
1289
+ createdAt: row.created_at,
1290
+ updatedAt: row.updated_at,
1291
+ }));
1292
+ }
1293
+
1294
+ async createTaskComment(record: TaskCommentInsert): Promise<TaskCommentRow> {
1295
+ const id = randomUUID();
1296
+ await this.db.run(
1297
+ `INSERT INTO task_comments (id, task_id, task_run_id, job_id, source_command, author_type, author_agent_id, category, file, line, path_hint, body, metadata_json, created_at, resolved_at, resolved_by)
1298
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1299
+ id,
1300
+ record.taskId,
1301
+ record.taskRunId ?? null,
1302
+ record.jobId ?? null,
1303
+ record.sourceCommand,
1304
+ record.authorType,
1305
+ record.authorAgentId ?? null,
1306
+ record.category ?? null,
1307
+ record.file ?? null,
1308
+ record.line ?? null,
1309
+ record.pathHint ?? null,
1310
+ record.body,
1311
+ record.metadata ? JSON.stringify(record.metadata) : null,
1312
+ record.createdAt,
1313
+ record.resolvedAt ?? null,
1314
+ record.resolvedBy ?? null,
1315
+ );
1316
+ return { ...record, id };
1317
+ }
1318
+
1319
+ async listTaskComments(taskId: string, options: { sourceCommands?: string[]; limit?: number } = {}): Promise<TaskCommentRow[]> {
1320
+ const clauses = ["task_id = ?"];
1321
+ const params: any[] = [taskId];
1322
+ if (options.sourceCommands && options.sourceCommands.length) {
1323
+ clauses.push(`source_command IN (${options.sourceCommands.map(() => "?").join(", ")})`);
1324
+ params.push(...options.sourceCommands);
1325
+ }
1326
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
1327
+ const limitClause = options.limit ? `LIMIT ${options.limit}` : "";
1328
+ const rows = await this.db.all<any[]>(
1329
+ `SELECT id, task_id, task_run_id, job_id, source_command, author_type, author_agent_id, category, file, line, path_hint, body, metadata_json, created_at, resolved_at, resolved_by
1330
+ FROM task_comments
1331
+ ${where}
1332
+ ORDER BY datetime(created_at) DESC
1333
+ ${limitClause}`,
1334
+ ...params,
1335
+ );
1336
+ return rows.map((row) => ({
1337
+ id: row.id,
1338
+ taskId: row.task_id,
1339
+ taskRunId: row.task_run_id ?? undefined,
1340
+ jobId: row.job_id ?? undefined,
1341
+ sourceCommand: row.source_command,
1342
+ authorType: row.author_type,
1343
+ authorAgentId: row.author_agent_id ?? undefined,
1344
+ category: row.category ?? undefined,
1345
+ file: row.file ?? undefined,
1346
+ line: row.line ?? undefined,
1347
+ pathHint: row.path_hint ?? undefined,
1348
+ body: row.body,
1349
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1350
+ createdAt: row.created_at,
1351
+ resolvedAt: row.resolved_at ?? undefined,
1352
+ resolvedBy: row.resolved_by ?? undefined,
1353
+ }));
1354
+ }
1355
+
1356
+ async createTaskReview(record: TaskReviewInsert): Promise<TaskReviewRow> {
1357
+ const id = randomUUID();
1358
+ await this.db.run(
1359
+ `INSERT INTO task_reviews (id, task_id, job_id, agent_id, model_name, decision, summary, findings_json, test_recommendations_json, metadata_json, created_at, created_by)
1360
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1361
+ id,
1362
+ record.taskId,
1363
+ record.jobId ?? null,
1364
+ record.agentId ?? null,
1365
+ record.modelName ?? null,
1366
+ record.decision,
1367
+ record.summary ?? null,
1368
+ record.findingsJson ? JSON.stringify(record.findingsJson) : null,
1369
+ record.testRecommendationsJson ? JSON.stringify(record.testRecommendationsJson) : null,
1370
+ record.metadata ? JSON.stringify(record.metadata) : null,
1371
+ record.createdAt,
1372
+ record.createdBy ?? null,
1373
+ );
1374
+ return { ...record, id };
1375
+ }
1376
+
1377
+ async getLatestTaskReview(taskId: string): Promise<TaskReviewRow | undefined> {
1378
+ const row = await this.db.get<any>(
1379
+ `SELECT id, task_id, job_id, agent_id, model_name, decision, summary, findings_json, test_recommendations_json, metadata_json, created_at, created_by
1380
+ FROM task_reviews
1381
+ WHERE task_id = ?
1382
+ ORDER BY datetime(created_at) DESC
1383
+ LIMIT 1`,
1384
+ taskId,
1385
+ );
1386
+ if (!row) return undefined;
1387
+ return {
1388
+ id: row.id,
1389
+ taskId: row.task_id,
1390
+ jobId: row.job_id ?? undefined,
1391
+ agentId: row.agent_id ?? undefined,
1392
+ modelName: row.model_name ?? undefined,
1393
+ decision: row.decision,
1394
+ summary: row.summary ?? undefined,
1395
+ findingsJson: row.findings_json ? JSON.parse(row.findings_json) : undefined,
1396
+ testRecommendationsJson: row.test_recommendations_json ? JSON.parse(row.test_recommendations_json) : undefined,
1397
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1398
+ createdAt: row.created_at,
1399
+ createdBy: row.created_by ?? undefined,
1400
+ };
1401
+ }
1402
+
1403
+ async recordTokenUsage(entry: TokenUsageInsert): Promise<void> {
1404
+ const id = randomUUID();
1405
+ await this.db.run(
1406
+ `INSERT INTO token_usage (id, workspace_id, agent_id, model_name, job_id, command_run_id, task_run_id, task_id, project_id, epic_id, user_story_id, tokens_prompt, tokens_completion, tokens_total, cost_estimate, duration_seconds, timestamp, metadata_json)
1407
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1408
+ id,
1409
+ entry.workspaceId,
1410
+ entry.agentId ?? null,
1411
+ entry.modelName ?? null,
1412
+ entry.jobId ?? null,
1413
+ entry.commandRunId ?? null,
1414
+ entry.taskRunId ?? null,
1415
+ entry.taskId ?? null,
1416
+ entry.projectId ?? null,
1417
+ entry.epicId ?? null,
1418
+ entry.userStoryId ?? null,
1419
+ entry.tokensPrompt ?? null,
1420
+ entry.tokensCompletion ?? null,
1421
+ entry.tokensTotal ?? null,
1422
+ entry.costEstimate ?? null,
1423
+ entry.durationSeconds ?? null,
1424
+ entry.timestamp,
1425
+ entry.metadata ? JSON.stringify(entry.metadata) : null,
1426
+ );
1427
+ }
1428
+
1429
+ async insertTaskRevision(record: TaskRevisionInsert): Promise<void> {
1430
+ const id = randomUUID();
1431
+ await this.db.run(
1432
+ `INSERT INTO task_revisions (id, task_id, job_id, command_run_id, snapshot_before_json, snapshot_after_json, created_at)
1433
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1434
+ id,
1435
+ record.taskId,
1436
+ record.jobId ?? null,
1437
+ record.commandRunId ?? null,
1438
+ record.snapshotBefore ? JSON.stringify(record.snapshotBefore) : null,
1439
+ record.snapshotAfter ? JSON.stringify(record.snapshotAfter) : null,
1440
+ record.createdAt,
1441
+ );
1442
+ }
1443
+
1444
+ async getEpicByKey(projectId: string, key: string): Promise<EpicRow | undefined> {
1445
+ const row = await this.db.get(
1446
+ `SELECT id, project_id, key, title, description, story_points_total, priority, metadata_json, created_at, updated_at FROM epics WHERE project_id = ? AND key = ?`,
1447
+ projectId,
1448
+ key,
1449
+ );
1450
+ if (!row) return undefined;
1451
+ return {
1452
+ id: row.id,
1453
+ projectId: row.project_id,
1454
+ key: row.key,
1455
+ title: row.title,
1456
+ description: row.description ?? undefined,
1457
+ storyPointsTotal: row.story_points_total ?? undefined,
1458
+ priority: row.priority ?? undefined,
1459
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1460
+ createdAt: row.created_at,
1461
+ updatedAt: row.updated_at,
1462
+ };
1463
+ }
1464
+
1465
+ async getStoryByKey(epicId: string, key: string): Promise<StoryRow | undefined> {
1466
+ const row = await this.db.get(
1467
+ `SELECT id, project_id, epic_id, key, title, description, acceptance_criteria, story_points_total, priority, metadata_json, created_at, updated_at FROM user_stories WHERE epic_id = ? AND key = ?`,
1468
+ epicId,
1469
+ key,
1470
+ );
1471
+ if (!row) return undefined;
1472
+ return {
1473
+ id: row.id,
1474
+ projectId: row.project_id,
1475
+ epicId: row.epic_id,
1476
+ key: row.key,
1477
+ title: row.title,
1478
+ description: row.description ?? undefined,
1479
+ acceptanceCriteria: row.acceptance_criteria ?? undefined,
1480
+ storyPointsTotal: row.story_points_total ?? undefined,
1481
+ priority: row.priority ?? undefined,
1482
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1483
+ createdAt: row.created_at,
1484
+ updatedAt: row.updated_at,
1485
+ };
1486
+ }
1487
+
1488
+ async getStoryByProjectAndKey(projectId: string, key: string): Promise<StoryRow | undefined> {
1489
+ const row = await this.db.get(
1490
+ `SELECT id, project_id, epic_id, key, title, description, acceptance_criteria, story_points_total, priority, metadata_json, created_at, updated_at FROM user_stories WHERE project_id = ? AND key = ?`,
1491
+ projectId,
1492
+ key,
1493
+ );
1494
+ if (!row) return undefined;
1495
+ return {
1496
+ id: row.id,
1497
+ projectId: row.project_id,
1498
+ epicId: row.epic_id,
1499
+ key: row.key,
1500
+ title: row.title,
1501
+ description: row.description ?? undefined,
1502
+ acceptanceCriteria: row.acceptance_criteria ?? undefined,
1503
+ storyPointsTotal: row.story_points_total ?? undefined,
1504
+ priority: row.priority ?? undefined,
1505
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1506
+ createdAt: row.created_at,
1507
+ updatedAt: row.updated_at,
1508
+ };
1509
+ }
1510
+
1511
+ }