agentic-orchestrator 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 (300) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/CLAUDE.md +126 -0
  3. package/README.md +166 -25
  4. package/agentic/orchestrator/adapters.yaml +3 -0
  5. package/agentic/orchestrator/gates.yaml +47 -0
  6. package/agentic/orchestrator/policy.yaml +89 -0
  7. package/agentic/orchestrator/schemas/adapters.schema.json +12 -0
  8. package/agentic/orchestrator/schemas/gates.schema.json +6 -1
  9. package/agentic/orchestrator/schemas/index.schema.json +14 -0
  10. package/agentic/orchestrator/schemas/multi-project.schema.json +41 -0
  11. package/agentic/orchestrator/schemas/policy.schema.json +449 -52
  12. package/agentic/orchestrator/schemas/state.schema.json +16 -0
  13. package/agentic/orchestrator/tools/catalog.json +68 -0
  14. package/agentic/orchestrator/tools/schemas/input/cost.get.input.schema.json +10 -0
  15. package/agentic/orchestrator/tools/schemas/input/cost.record.input.schema.json +13 -0
  16. package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +11 -0
  17. package/agentic/orchestrator/tools/schemas/input/performance.get_analytics.input.schema.json +10 -0
  18. package/agentic/orchestrator/tools/schemas/input/performance.record_outcome.input.schema.json +18 -0
  19. package/agentic/orchestrator/tools/schemas/output/cost.get.output.schema.json +13 -0
  20. package/agentic/orchestrator/tools/schemas/output/cost.record.output.schema.json +13 -0
  21. package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +7 -0
  22. package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +23 -0
  23. package/agentic/orchestrator/tools/schemas/output/performance.get_analytics.output.schema.json +46 -0
  24. package/agentic/orchestrator/tools/schemas/output/performance.record_outcome.output.schema.json +10 -0
  25. package/agentic/orchestrator/tools.md +5 -0
  26. package/apps/control-plane/scripts/validate-architecture-rules.mjs +28 -2
  27. package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +12 -0
  28. package/apps/control-plane/scripts/validate-mcp-contracts.ts +92 -0
  29. package/apps/control-plane/src/application/adapters/adapter-registry.ts +169 -0
  30. package/apps/control-plane/src/application/multi-project-loader.ts +119 -0
  31. package/apps/control-plane/src/application/services/activity-monitor-service.ts +199 -0
  32. package/apps/control-plane/src/application/services/cost-tracking-service.ts +82 -0
  33. package/apps/control-plane/src/application/services/dependency-scheduler-service.ts +86 -0
  34. package/apps/control-plane/src/application/services/feature-deletion-service.ts +8 -7
  35. package/apps/control-plane/src/application/services/gate-interpolation-service.ts +15 -0
  36. package/apps/control-plane/src/application/services/gate-service.ts +38 -2
  37. package/apps/control-plane/src/application/services/instance-isolation-service.ts +18 -0
  38. package/apps/control-plane/src/application/services/issue-tracker-service.ts +469 -0
  39. package/apps/control-plane/src/application/services/merge-service.ts +67 -3
  40. package/apps/control-plane/src/application/services/notifier-service.ts +295 -0
  41. package/apps/control-plane/src/application/services/performance-analytics-service.ts +122 -0
  42. package/apps/control-plane/src/application/services/plan-service.ts +51 -0
  43. package/apps/control-plane/src/application/services/pr-monitor-service.ts +262 -0
  44. package/apps/control-plane/src/application/services/reactions-service.ts +175 -0
  45. package/apps/control-plane/src/application/services/reporting-service.ts +17 -2
  46. package/apps/control-plane/src/application/services/run-lease-service.ts +16 -38
  47. package/apps/control-plane/src/application/tools/tool-metadata.ts +4 -1
  48. package/apps/control-plane/src/cli/aop.ts +1 -1
  49. package/apps/control-plane/src/cli/attach-command-handler.ts +120 -0
  50. package/apps/control-plane/src/cli/cleanup-command-handler.ts +190 -0
  51. package/apps/control-plane/src/cli/cli-argument-parser.ts +69 -3
  52. package/apps/control-plane/src/cli/dashboard-command-handler.ts +57 -0
  53. package/apps/control-plane/src/cli/help-command-handler.ts +163 -0
  54. package/apps/control-plane/src/cli/init-command-handler.ts +609 -0
  55. package/apps/control-plane/src/cli/resume-command-handler.ts +1 -0
  56. package/apps/control-plane/src/cli/retry-command-handler.ts +138 -0
  57. package/apps/control-plane/src/cli/run-command-handler.ts +115 -3
  58. package/apps/control-plane/src/cli/send-command-handler.ts +65 -0
  59. package/apps/control-plane/src/cli/status-command-handler.ts +102 -2
  60. package/apps/control-plane/src/cli/types.ts +26 -1
  61. package/apps/control-plane/src/core/constants.ts +8 -2
  62. package/apps/control-plane/src/core/error-codes.ts +3 -1
  63. package/apps/control-plane/src/core/gates.ts +170 -50
  64. package/apps/control-plane/src/core/kernel.ts +280 -5
  65. package/apps/control-plane/src/core/path-layout.ts +12 -0
  66. package/apps/control-plane/src/core/tool-caller.ts +36 -0
  67. package/apps/control-plane/src/core/workspace-hooks.ts +87 -0
  68. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +258 -9
  69. package/apps/control-plane/src/providers/providers.ts +235 -14
  70. package/apps/control-plane/src/supervisor/build-wave-executor.ts +129 -8
  71. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +123 -5
  72. package/apps/control-plane/src/supervisor/run-coordinator.ts +143 -6
  73. package/apps/control-plane/src/supervisor/runtime.ts +135 -6
  74. package/apps/control-plane/src/supervisor/types.ts +12 -21
  75. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +8 -0
  76. package/apps/control-plane/test/activity-monitor.spec.ts +294 -0
  77. package/apps/control-plane/test/adapter-registry.spec.ts +132 -0
  78. package/apps/control-plane/test/batch-operations.spec.ts +112 -0
  79. package/apps/control-plane/test/bootstrap-attach.spec.ts +102 -0
  80. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +252 -0
  81. package/apps/control-plane/test/bootstrap.spec.ts +560 -0
  82. package/apps/control-plane/test/cleanup-command.spec.ts +301 -0
  83. package/apps/control-plane/test/cli-helpers.spec.ts +404 -1
  84. package/apps/control-plane/test/cli.unit.spec.ts +182 -1
  85. package/apps/control-plane/test/collision-queue.spec.ts +104 -1
  86. package/apps/control-plane/test/core-utils.spec.ts +175 -2
  87. package/apps/control-plane/test/cost-tracking.spec.ts +143 -0
  88. package/apps/control-plane/test/dashboard-api.integration.spec.ts +247 -0
  89. package/apps/control-plane/test/dashboard-client.spec.ts +116 -0
  90. package/apps/control-plane/test/dashboard-command.spec.ts +103 -0
  91. package/apps/control-plane/test/dependency-scheduler.spec.ts +189 -0
  92. package/apps/control-plane/test/epoch-tracking.spec.ts +4 -4
  93. package/apps/control-plane/test/feature-deletion-service.spec.ts +422 -0
  94. package/apps/control-plane/test/feature-lifecycle.spec.ts +202 -0
  95. package/apps/control-plane/test/git-spawn-error.spec.ts +24 -0
  96. package/apps/control-plane/test/incremental-gates.spec.ts +137 -0
  97. package/apps/control-plane/test/init-wizard.spec.ts +506 -0
  98. package/apps/control-plane/test/instance-isolation.spec.ts +83 -0
  99. package/apps/control-plane/test/issue-tracker.spec.ts +890 -0
  100. package/apps/control-plane/test/kernel.coverage.spec.ts +3 -5
  101. package/apps/control-plane/test/kernel.coverage2.spec.ts +871 -0
  102. package/apps/control-plane/test/kernel.spec.ts +13 -11
  103. package/apps/control-plane/test/lock-service.spec.ts +508 -0
  104. package/apps/control-plane/test/mcp-helpers.spec.ts +176 -0
  105. package/apps/control-plane/test/mcp.spec.ts +50 -15
  106. package/apps/control-plane/test/merge-service.spec.ts +67 -4
  107. package/apps/control-plane/test/multi-project.spec.ts +372 -0
  108. package/apps/control-plane/test/notifier-service.spec.ts +388 -0
  109. package/apps/control-plane/test/parallel-gates.spec.ts +312 -0
  110. package/apps/control-plane/test/patch-service.spec.ts +253 -0
  111. package/apps/control-plane/test/performance-analytics.spec.ts +338 -0
  112. package/apps/control-plane/test/planning-wave-executor.spec.ts +168 -0
  113. package/apps/control-plane/test/pr-monitor.spec.ts +385 -0
  114. package/apps/control-plane/test/providers.spec.ts +344 -1
  115. package/apps/control-plane/test/reactions.spec.ts +392 -0
  116. package/apps/control-plane/test/resume-command.spec.ts +390 -0
  117. package/apps/control-plane/test/run-coordinator.spec.ts +481 -2
  118. package/apps/control-plane/test/schema-date-time.spec.ts +46 -0
  119. package/apps/control-plane/test/service-retry-paths.spec.ts +30 -0
  120. package/apps/control-plane/test/services.spec.ts +95 -2
  121. package/apps/control-plane/test/session-management.spec.ts +450 -0
  122. package/apps/control-plane/test/spec-ingestion.spec.ts +190 -0
  123. package/apps/control-plane/test/supervisor-collaborators.spec.ts +699 -2
  124. package/apps/control-plane/test/supervisor.spec.ts +36 -30
  125. package/apps/control-plane/test/supervisor.unit.spec.ts +405 -0
  126. package/apps/control-plane/test/worker-decision-loop.spec.ts +57 -0
  127. package/apps/control-plane/test/workspace-hooks.spec.ts +177 -0
  128. package/apps/control-plane/vitest.config.ts +21 -5
  129. package/dist/apps/control-plane/application/adapters/adapter-registry.d.ts +44 -0
  130. package/dist/apps/control-plane/application/adapters/adapter-registry.js +76 -0
  131. package/dist/apps/control-plane/application/adapters/adapter-registry.js.map +1 -0
  132. package/dist/apps/control-plane/application/multi-project-loader.d.ts +31 -0
  133. package/dist/apps/control-plane/application/multi-project-loader.js +82 -0
  134. package/dist/apps/control-plane/application/multi-project-loader.js.map +1 -0
  135. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +43 -0
  136. package/dist/apps/control-plane/application/services/activity-monitor-service.js +132 -0
  137. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -0
  138. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +28 -0
  139. package/dist/apps/control-plane/application/services/cost-tracking-service.js +48 -0
  140. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -0
  141. package/dist/apps/control-plane/application/services/dependency-scheduler-service.d.ts +26 -0
  142. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js +75 -0
  143. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js.map +1 -0
  144. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +2 -0
  145. package/dist/apps/control-plane/application/services/feature-deletion-service.js +6 -7
  146. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  147. package/dist/apps/control-plane/application/services/gate-interpolation-service.d.ts +7 -0
  148. package/dist/apps/control-plane/application/services/gate-interpolation-service.js +7 -0
  149. package/dist/apps/control-plane/application/services/gate-interpolation-service.js.map +1 -0
  150. package/dist/apps/control-plane/application/services/gate-service.js +32 -2
  151. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  152. package/dist/apps/control-plane/application/services/instance-isolation-service.d.ts +11 -0
  153. package/dist/apps/control-plane/application/services/instance-isolation-service.js +17 -0
  154. package/dist/apps/control-plane/application/services/instance-isolation-service.js.map +1 -0
  155. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +65 -0
  156. package/dist/apps/control-plane/application/services/issue-tracker-service.js +358 -0
  157. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -0
  158. package/dist/apps/control-plane/application/services/merge-service.d.ts +4 -0
  159. package/dist/apps/control-plane/application/services/merge-service.js +44 -2
  160. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  161. package/dist/apps/control-plane/application/services/notifier-service.d.ts +74 -0
  162. package/dist/apps/control-plane/application/services/notifier-service.js +212 -0
  163. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -0
  164. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +39 -0
  165. package/dist/apps/control-plane/application/services/performance-analytics-service.js +75 -0
  166. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -0
  167. package/dist/apps/control-plane/application/services/plan-service.d.ts +1 -0
  168. package/dist/apps/control-plane/application/services/plan-service.js +53 -0
  169. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  170. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +44 -0
  171. package/dist/apps/control-plane/application/services/pr-monitor-service.js +192 -0
  172. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -0
  173. package/dist/apps/control-plane/application/services/reactions-service.d.ts +67 -0
  174. package/dist/apps/control-plane/application/services/reactions-service.js +114 -0
  175. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -0
  176. package/dist/apps/control-plane/application/services/reporting-service.d.ts +1 -0
  177. package/dist/apps/control-plane/application/services/reporting-service.js +13 -2
  178. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  179. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +2 -0
  180. package/dist/apps/control-plane/application/services/run-lease-service.js +14 -38
  181. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  182. package/dist/apps/control-plane/application/tools/tool-metadata.js +3 -1
  183. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  184. package/dist/apps/control-plane/cli/aop.d.ts +1 -1
  185. package/dist/apps/control-plane/cli/aop.js +1 -1
  186. package/dist/apps/control-plane/cli/attach-command-handler.d.ts +12 -0
  187. package/dist/apps/control-plane/cli/attach-command-handler.js +98 -0
  188. package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -0
  189. package/dist/apps/control-plane/cli/cleanup-command-handler.d.ts +12 -0
  190. package/dist/apps/control-plane/cli/cleanup-command-handler.js +162 -0
  191. package/dist/apps/control-plane/cli/cleanup-command-handler.js.map +1 -0
  192. package/dist/apps/control-plane/cli/cli-argument-parser.js +73 -3
  193. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  194. package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +7 -0
  195. package/dist/apps/control-plane/cli/dashboard-command-handler.js +45 -0
  196. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -0
  197. package/dist/apps/control-plane/cli/help-command-handler.d.ts +8 -0
  198. package/dist/apps/control-plane/cli/help-command-handler.js +146 -0
  199. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -0
  200. package/dist/apps/control-plane/cli/init-command-handler.d.ts +26 -0
  201. package/dist/apps/control-plane/cli/init-command-handler.js +517 -0
  202. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -0
  203. package/dist/apps/control-plane/cli/resume-command-handler.js +1 -1
  204. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  205. package/dist/apps/control-plane/cli/retry-command-handler.d.ts +8 -0
  206. package/dist/apps/control-plane/cli/retry-command-handler.js +111 -0
  207. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -0
  208. package/dist/apps/control-plane/cli/run-command-handler.d.ts +5 -0
  209. package/dist/apps/control-plane/cli/run-command-handler.js +82 -3
  210. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  211. package/dist/apps/control-plane/cli/send-command-handler.d.ts +8 -0
  212. package/dist/apps/control-plane/cli/send-command-handler.js +55 -0
  213. package/dist/apps/control-plane/cli/send-command-handler.js.map +1 -0
  214. package/dist/apps/control-plane/cli/status-command-handler.d.ts +12 -1
  215. package/dist/apps/control-plane/cli/status-command-handler.js +55 -2
  216. package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
  217. package/dist/apps/control-plane/cli/types.d.ts +26 -1
  218. package/dist/apps/control-plane/cli/types.js +15 -1
  219. package/dist/apps/control-plane/cli/types.js.map +1 -1
  220. package/dist/apps/control-plane/core/constants.d.ts +6 -0
  221. package/dist/apps/control-plane/core/constants.js +8 -2
  222. package/dist/apps/control-plane/core/constants.js.map +1 -1
  223. package/dist/apps/control-plane/core/error-codes.d.ts +2 -0
  224. package/dist/apps/control-plane/core/error-codes.js +3 -1
  225. package/dist/apps/control-plane/core/error-codes.js.map +1 -1
  226. package/dist/apps/control-plane/core/gates.d.ts +4 -0
  227. package/dist/apps/control-plane/core/gates.js +140 -43
  228. package/dist/apps/control-plane/core/gates.js.map +1 -1
  229. package/dist/apps/control-plane/core/kernel.d.ts +50 -1
  230. package/dist/apps/control-plane/core/kernel.js +220 -7
  231. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  232. package/dist/apps/control-plane/core/path-layout.d.ts +3 -0
  233. package/dist/apps/control-plane/core/path-layout.js +9 -0
  234. package/dist/apps/control-plane/core/path-layout.js.map +1 -1
  235. package/dist/apps/control-plane/core/tool-caller.d.ts +32 -0
  236. package/dist/apps/control-plane/core/tool-caller.js +2 -0
  237. package/dist/apps/control-plane/core/tool-caller.js.map +1 -0
  238. package/dist/apps/control-plane/core/workspace-hooks.d.ts +20 -0
  239. package/dist/apps/control-plane/core/workspace-hooks.js +69 -0
  240. package/dist/apps/control-plane/core/workspace-hooks.js.map +1 -0
  241. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +245 -9
  242. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  243. package/dist/apps/control-plane/providers/providers.d.ts +42 -3
  244. package/dist/apps/control-plane/providers/providers.js +216 -5
  245. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  246. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  247. package/dist/apps/control-plane/supervisor/build-wave-executor.js +115 -6
  248. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  249. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  250. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +109 -5
  251. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  252. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +15 -0
  253. package/dist/apps/control-plane/supervisor/run-coordinator.js +132 -6
  254. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  255. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  256. package/dist/apps/control-plane/supervisor/runtime.js +110 -6
  257. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  258. package/dist/apps/control-plane/supervisor/types.d.ts +9 -16
  259. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  260. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +3 -0
  261. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +5 -0
  262. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  263. package/eslint.config.mjs +2 -1
  264. package/package.json +12 -2
  265. package/packages/web-dashboard/next-env.d.ts +5 -0
  266. package/packages/web-dashboard/next.config.js +7 -0
  267. package/packages/web-dashboard/package.json +26 -0
  268. package/packages/web-dashboard/src/app/api/actions/route.ts +64 -0
  269. package/packages/web-dashboard/src/app/api/events/route.ts +51 -0
  270. package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +256 -0
  271. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +10 -0
  272. package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +25 -0
  273. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +63 -0
  274. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +16 -0
  275. package/packages/web-dashboard/src/app/api/projects/route.ts +31 -0
  276. package/packages/web-dashboard/src/app/api/status/route.ts +15 -0
  277. package/packages/web-dashboard/src/app/globals.css +2 -0
  278. package/packages/web-dashboard/src/app/layout.tsx +15 -0
  279. package/packages/web-dashboard/src/app/page.tsx +393 -0
  280. package/packages/web-dashboard/src/lib/aop-client.ts +244 -0
  281. package/packages/web-dashboard/src/lib/multi-project-config.ts +116 -0
  282. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +284 -0
  283. package/packages/web-dashboard/src/lib/types.ts +58 -0
  284. package/packages/web-dashboard/tsconfig.json +40 -0
  285. package/packages/web-dashboard/vitest.config.ts +6 -0
  286. package/spec-files/completed/agentic_orchestrator_feature_gaps_closure_spec.md +1764 -0
  287. package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +348 -0
  288. package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +344 -0
  289. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +374 -0
  290. package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +1059 -0
  291. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +466 -0
  292. package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +198 -0
  293. package/spec-files/outstanding/agentic_orchestrator_validator_hardening_spec.md +365 -0
  294. package/spec-files/progress.md +481 -52
  295. /package/spec-files/{agentic_orchestrator_cli_delete_command_spec.md → completed/agentic_orchestrator_cli_delete_command_spec.md} +0 -0
  296. /package/spec-files/{agentic_orchestrator_dot_aop_generated_artifacts_spec.md → completed/agentic_orchestrator_dot_aop_generated_artifacts_spec.md} +0 -0
  297. /package/spec-files/{agentic_orchestrator_mcp_formalization_spec.md → completed/agentic_orchestrator_mcp_formalization_spec.md} +0 -0
  298. /package/spec-files/{agentic_orchestrator_oop_refactor_spec.md → completed/agentic_orchestrator_oop_refactor_spec.md} +0 -0
  299. /package/spec-files/{agentic_orchestrator_single_global_orchestrator_spec.md → completed/agentic_orchestrator_single_global_orchestrator_spec.md} +0 -0
  300. /package/spec-files/{agentic_orchestrator_spec.md → completed/agentic_orchestrator_spec.md} +0 -0
@@ -146,6 +146,8 @@ export interface FeatureDeletionServicePort {
146
146
  withIndexLock<T>(operation: () => Promise<T>): Promise<T>;
147
147
  readIndex(): Promise<AnyRecord>;
148
148
  writeIndex(index: AnyRecord): Promise<void>;
149
+ readRunLease(): Promise<RuntimeSessionsSnapshot>;
150
+ writeRunLease(data: RuntimeSessionsSnapshot): Promise<void>;
149
151
  normalizeRuntimeSessions(value: unknown, at?: string): RuntimeSessionsSnapshot;
150
152
  isRunLeaseFresh(runtimeSessions: RuntimeSessionsSnapshot): boolean;
151
153
  locksRelease(resource: string, featureId: string): Promise<unknown>;
@@ -222,7 +224,7 @@ export class FeatureDeletionService {
222
224
  const stateHeldLocks = readHeldLocksFromState(stateFrontMatter);
223
225
 
224
226
  const indexSnapshot = await this.port.readIndex();
225
- const runtimeSessions = this.port.normalizeRuntimeSessions(indexSnapshot.runtime_sessions);
227
+ const runtimeSessions = await this.port.readRunLease();
226
228
  if (this.hasFreshRunLease(runtimeSessions)) {
227
229
  throw {
228
230
  normalizedResponse: fail(ERROR_CODES.RUN_ALREADY_ACTIVE, 'Cannot delete feature while a run lease is active', {
@@ -293,7 +295,6 @@ export class FeatureDeletionService {
293
295
  const indexUpdate = await this.port.withIndexLock(async () => {
294
296
  const index = await this.port.readIndex();
295
297
  let changed = false;
296
- let blockedQueueRemoved: number;
297
298
  let runtimeSessionRemoved = false;
298
299
 
299
300
  const active = asStringArray(index.active);
@@ -322,7 +323,7 @@ export class FeatureDeletionService {
322
323
  const record = asRecord(entry);
323
324
  return record.feature_id !== featureId;
324
325
  });
325
- blockedQueueRemoved = blockedQueue.length - nextBlockedQueue.length;
326
+ const blockedQueueRemoved = blockedQueue.length - nextBlockedQueue.length;
326
327
  if (blockedQueueRemoved > 0) {
327
328
  index.blocked_queue = nextBlockedQueue;
328
329
  changed = true;
@@ -347,15 +348,15 @@ export class FeatureDeletionService {
347
348
  }
348
349
  index.lock_leases = lockLeases;
349
350
 
350
- const nextRuntimeSessions = this.port.normalizeRuntimeSessions(index.runtime_sessions);
351
- const featureSessions = normalizeFeatureSessions(nextRuntimeSessions);
351
+ const currentRunLease = await this.port.readRunLease();
352
+ const featureSessions = normalizeFeatureSessions(currentRunLease);
352
353
  if (Object.prototype.hasOwnProperty.call(featureSessions, featureId)) {
353
354
  delete featureSessions[featureId];
354
- nextRuntimeSessions.feature_sessions = featureSessions as RuntimeSessionsSnapshot['feature_sessions'];
355
+ currentRunLease.feature_sessions = featureSessions as RuntimeSessionsSnapshot['feature_sessions'];
356
+ await this.port.writeRunLease(currentRunLease);
355
357
  runtimeSessionRemoved = true;
356
358
  changed = true;
357
359
  }
358
- index.runtime_sessions = nextRuntimeSessions;
359
360
 
360
361
  if (changed) {
361
362
  const version = typeof index.version === 'number' ? index.version : 0;
@@ -0,0 +1,15 @@
1
+ export interface InterpolationContext {
2
+ base_branch: string;
3
+ feature_id: string;
4
+ worktree_path?: string;
5
+ }
6
+
7
+ export function interpolateGateCommands(commands: string[], context: InterpolationContext): string[] {
8
+ return commands.map((token) =>
9
+ token.replaceAll('{base_branch}', context.base_branch).replaceAll('{feature_id}', context.feature_id)
10
+ );
11
+ }
12
+
13
+ export function isIncrementalMode(mode: string): boolean {
14
+ return mode === 'fast';
15
+ }
@@ -2,9 +2,11 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { pathExists } from '../../core/fs.js';
4
4
  import { runGateMode } from '../../core/gates.js';
5
+ import type { GateProfile, GateStep } from '../../core/gates.js';
5
6
  import { ERROR_CODES } from '../../core/error-codes.js';
6
7
  import { fail } from '../../core/response.js';
7
8
  import { GATE_RESULT, STATUS } from '../../core/constants.js';
9
+ import { interpolateGateCommands, isIncrementalMode } from './gate-interpolation-service.js';
8
10
 
9
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
12
  type AnyRecord = Record<string, any>;
@@ -118,12 +120,37 @@ export class GateService {
118
120
  const profile = this.gateProfileAndMode(effectiveProfileName, mode);
119
121
  const repoRoot = this.port.getRepoRoot();
120
122
 
123
+ const policySnapshot = this.port.getPolicySnapshot();
124
+ const worktreeConfig = policySnapshot['worktree'] as { base_branch?: string } | undefined;
125
+ const baseBranch = worktreeConfig?.base_branch ?? 'main';
126
+
127
+ const profileAsGate = profile as unknown as GateProfile;
128
+ const originalModeSteps = profileAsGate.modes?.[mode] ?? [];
129
+ const hasBaseBranchTemplate =
130
+ isIncrementalMode(mode) &&
131
+ originalModeSteps.some((step) => step.cmd.some((token) => token.includes('{base_branch}')));
132
+
133
+ let effectiveProfile: AnyRecord = profile;
134
+ if (isIncrementalMode(mode)) {
135
+ const interpolatedSteps: GateStep[] = originalModeSteps.map((step) => ({
136
+ ...step,
137
+ cmd: interpolateGateCommands(step.cmd, { base_branch: baseBranch, feature_id: featureId })
138
+ }));
139
+ effectiveProfile = {
140
+ ...profile,
141
+ modes: {
142
+ ...(profileAsGate.modes ?? {}),
143
+ [mode]: interpolatedSteps
144
+ }
145
+ };
146
+ }
147
+
121
148
  const runResult = (await runGateMode({
122
149
  featureId,
123
150
  mode,
124
151
  profileName: effectiveProfileName,
125
- profile,
126
- policy: this.port.getPolicySnapshot() as Parameters<typeof runGateMode>[0]['policy'],
152
+ profile: effectiveProfile,
153
+ policy: policySnapshot as Parameters<typeof runGateMode>[0]['policy'],
127
154
  worktreePath: this.port.worktreePath(featureId),
128
155
  logDirectory: this.port.logsPath(featureId),
129
156
  evidenceDirectory: this.port.evidencePath(featureId)
@@ -150,6 +177,15 @@ export class GateService {
150
177
  })
151
178
  );
152
179
 
180
+ if (hasBaseBranchTemplate) {
181
+ const skippedInfo = { skipped_reason: 'incremental_mode' as const, base_branch: baseBranch };
182
+ for (const evidenceFile of [runResult.evidence_path, runResult.latest_path]) {
183
+ const raw = await fs.readFile(evidenceFile, 'utf8');
184
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
185
+ await fs.writeFile(evidenceFile, `${JSON.stringify({ ...parsed, ...skippedInfo }, null, 2)}\n`, 'utf8');
186
+ }
187
+ }
188
+
153
189
  if (runResult.overall === 'fail' && runResult.coverage_status === 'fail') {
154
190
  throw {
155
191
  normalizedResponse: fail(
@@ -0,0 +1,18 @@
1
+ import { stableHash } from '../../core/fs.js';
2
+
3
+ /**
4
+ * Computes a deterministic 12-character instance ID from a config file path.
5
+ * This ensures two orchestrator checkouts with different config paths get
6
+ * independent run-lease files and dashboard ports.
7
+ */
8
+ export function computeInstanceId(configPath: string): string {
9
+ return stableHash(configPath).substring(0, 12);
10
+ }
11
+
12
+ /**
13
+ * Returns the canonical config path for the default single-instance case.
14
+ * Used when no explicit --config flag is provided.
15
+ */
16
+ export function defaultConfigPath(repoRoot: string): string {
17
+ return `${repoRoot}/agentic/orchestrator/policy.yaml`;
18
+ }
@@ -0,0 +1,469 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+
4
+ const execFileAsync = promisify(execFile);
5
+
6
+ export interface Issue {
7
+ id: string;
8
+ title: string;
9
+ body: string;
10
+ status: string;
11
+ url: string;
12
+ }
13
+
14
+ export interface IssueTracker {
15
+ getIssue(issueId: string): Promise<Issue>;
16
+ updateIssueStatus(issueId: string, status: string): Promise<void>;
17
+ addComment(issueId: string, comment: string): Promise<void>;
18
+ }
19
+
20
+ export interface IssueTrackerConfig {
21
+ enabled?: boolean;
22
+ type: string;
23
+ config?: Record<string, string>;
24
+ }
25
+
26
+ // --- GitHub adapter (via gh CLI) ---
27
+
28
+ export type GhRunner = (args: string[]) => Promise<{ stdout: string; exitCode: number }>;
29
+ export type HttpRunner = (url: string, init: RequestInit) => Promise<{ status: number; ok: boolean; body: string }>;
30
+
31
+ export function createGhRunner(fn?: GhRunner): GhRunner {
32
+ if (fn !== undefined) {
33
+ return fn;
34
+ }
35
+ return async (args: string[]) => {
36
+ try {
37
+ const { stdout } = await execFileAsync('gh', args, { timeout: 15_000 });
38
+ return { stdout, exitCode: 0 };
39
+ } catch (err: unknown) {
40
+ const e = err as Record<string, unknown>;
41
+ if (e['code'] === 'ENOENT' || e['code'] === 127) {
42
+ return { stdout: '', exitCode: 127 };
43
+ }
44
+ const exitCode = typeof e['code'] === 'number' ? e['code'] : 1;
45
+ return { stdout: '', exitCode };
46
+ }
47
+ };
48
+ }
49
+
50
+ export function createHttpRunner(fn?: HttpRunner): HttpRunner {
51
+ if (fn !== undefined) {
52
+ return fn;
53
+ }
54
+ return async (url: string, init: RequestInit) => {
55
+ try {
56
+ const response = await fetch(url, init);
57
+ return {
58
+ status: response.status,
59
+ ok: response.ok,
60
+ body: await response.text()
61
+ };
62
+ } catch {
63
+ return { status: 0, ok: false, body: '' };
64
+ }
65
+ };
66
+ }
67
+
68
+ function tryParseJson(value: string): Record<string, unknown> | null {
69
+ if (!value) {
70
+ return null;
71
+ }
72
+ try {
73
+ const parsed = JSON.parse(value) as unknown;
74
+ if (parsed && typeof parsed === 'object') {
75
+ return parsed as Record<string, unknown>;
76
+ }
77
+ return null;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ export class GitHubIssueTracker implements IssueTracker {
84
+ private readonly repo: string | undefined;
85
+ private readonly ghRunner: GhRunner;
86
+
87
+ constructor(config: Record<string, string> = {}, ghRunner?: GhRunner) {
88
+ this.repo = config['repo'];
89
+ this.ghRunner = ghRunner ?? createGhRunner();
90
+ }
91
+
92
+ async getIssue(issueId: string): Promise<Issue> {
93
+ const repoArgs = this.repo ? ['--repo', this.repo] : [];
94
+ const result = await this.ghRunner([
95
+ 'issue', 'view', issueId,
96
+ ...repoArgs,
97
+ '--json', 'number,title,body,state,url'
98
+ ]);
99
+ if (result.exitCode !== 0 || !result.stdout) {
100
+ return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
101
+ }
102
+ const parsed = JSON.parse(result.stdout) as {
103
+ number: number;
104
+ title: string;
105
+ body: string;
106
+ state: string;
107
+ url: string;
108
+ };
109
+ return {
110
+ id: String(parsed.number),
111
+ title: parsed.title,
112
+ body: parsed.body,
113
+ status: parsed.state.toLowerCase(),
114
+ url: parsed.url
115
+ };
116
+ }
117
+
118
+ async updateIssueStatus(issueId: string, status: string): Promise<void> {
119
+ const repoArgs = this.repo ? ['--repo', this.repo] : [];
120
+ const stateArg = status === 'closed' || status === 'merged' ? 'closed' : 'open';
121
+ await this.ghRunner(['issue', 'edit', issueId, ...repoArgs, '--state', stateArg]);
122
+ }
123
+
124
+ async addComment(issueId: string, comment: string): Promise<void> {
125
+ const repoArgs = this.repo ? ['--repo', this.repo] : [];
126
+ await this.ghRunner(['issue', 'comment', issueId, ...repoArgs, '--body', comment]);
127
+ }
128
+ }
129
+
130
+ interface LinearIssueNode {
131
+ id: string;
132
+ identifier: string;
133
+ title: string;
134
+ description: string;
135
+ url: string;
136
+ state: {
137
+ name: string;
138
+ } | null;
139
+ }
140
+
141
+ function readString(value: unknown): string {
142
+ return typeof value === 'string' ? value : '';
143
+ }
144
+
145
+ function lowerCaseStatus(value: string): string {
146
+ return value ? value.toLowerCase() : 'unknown';
147
+ }
148
+
149
+ function normalizeAopStatus(status: string): string {
150
+ return status.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_');
151
+ }
152
+
153
+ function mapDefaultLinearStatus(status: string): string | null {
154
+ const normalized = normalizeAopStatus(status);
155
+ if (normalized === 'merged' || normalized === 'closed') {
156
+ return 'done';
157
+ }
158
+ if (normalized === 'blocked' || normalized === 'failed') {
159
+ return 'blocked';
160
+ }
161
+ if (normalized === 'planning') {
162
+ return 'backlog';
163
+ }
164
+ if (normalized === 'qa' || normalized === 'ready_to_merge') {
165
+ return 'in_review';
166
+ }
167
+ if (normalized === 'building') {
168
+ return 'in_progress';
169
+ }
170
+ return 'in_progress';
171
+ }
172
+
173
+ function mapDefaultJiraTransition(status: string): string {
174
+ const normalized = normalizeAopStatus(status);
175
+ if (normalized === 'merged' || normalized === 'closed') {
176
+ return 'done';
177
+ }
178
+ if (normalized === 'planning') {
179
+ return 'to do';
180
+ }
181
+ if (normalized === 'blocked' || normalized === 'failed') {
182
+ return 'blocked';
183
+ }
184
+ return 'in progress';
185
+ }
186
+
187
+ // --- Linear adapter (GraphQL HTTP) ---
188
+ export class LinearIssueTracker implements IssueTracker {
189
+ private readonly token: string;
190
+ private readonly baseUrl: string;
191
+ private readonly config: Record<string, string>;
192
+ private readonly httpRunner: HttpRunner;
193
+
194
+ constructor(config: Record<string, string> = {}, httpRunner?: HttpRunner) {
195
+ this.token = config['token'] ?? '';
196
+ this.baseUrl = config['base_url'] ?? 'https://api.linear.app/graphql';
197
+ this.config = config;
198
+ this.httpRunner = createHttpRunner(httpRunner);
199
+ }
200
+
201
+ private async graphQl<TData>(query: string, variables: Record<string, unknown>): Promise<TData | null> {
202
+ const headers: Record<string, string> = {
203
+ 'Content-Type': 'application/json'
204
+ };
205
+ if (this.token) {
206
+ headers['Authorization'] = `Bearer ${this.token}`;
207
+ }
208
+ const result = await this.httpRunner(this.baseUrl, {
209
+ method: 'POST',
210
+ headers,
211
+ body: JSON.stringify({ query, variables })
212
+ });
213
+ if (!result.ok || !result.body) {
214
+ return null;
215
+ }
216
+ const parsed = tryParseJson(result.body);
217
+ if (!parsed) {
218
+ return null;
219
+ }
220
+ if (Array.isArray(parsed['errors']) && parsed['errors'].length > 0) {
221
+ return null;
222
+ }
223
+ const data = parsed['data'];
224
+ if (data && typeof data === 'object') {
225
+ return data as TData;
226
+ }
227
+ return null;
228
+ }
229
+
230
+ private static parseIssueNode(value: unknown): LinearIssueNode | null {
231
+ if (!value || typeof value !== 'object') {
232
+ return null;
233
+ }
234
+ const raw = value as Record<string, unknown>;
235
+ const stateRaw = raw['state'];
236
+ return {
237
+ id: readString(raw['id']),
238
+ identifier: readString(raw['identifier']),
239
+ title: readString(raw['title']),
240
+ description: readString(raw['description']),
241
+ url: readString(raw['url']),
242
+ state:
243
+ stateRaw && typeof stateRaw === 'object'
244
+ ? {
245
+ name: readString((stateRaw as Record<string, unknown>)['name'])
246
+ }
247
+ : null
248
+ };
249
+ }
250
+
251
+ private async resolveIssueNode(issueId: string): Promise<LinearIssueNode | null> {
252
+ const byIdentifier = await this.graphQl<{ issueByIdentifier?: unknown }>(
253
+ 'query AopIssueByIdentifier($identifier: String!) { issueByIdentifier(identifier: $identifier) { id identifier title description url state { name } } }',
254
+ { identifier: issueId }
255
+ );
256
+ const identifierIssue = LinearIssueTracker.parseIssueNode(byIdentifier?.issueByIdentifier);
257
+ if (identifierIssue) {
258
+ return identifierIssue;
259
+ }
260
+
261
+ const byId = await this.graphQl<{ issue?: unknown }>(
262
+ 'query AopIssueById($id: String!) { issue(id: $id) { id identifier title description url state { name } } }',
263
+ { id: issueId }
264
+ );
265
+ return LinearIssueTracker.parseIssueNode(byId?.issue);
266
+ }
267
+
268
+ private resolveStateId(status: string): string | null {
269
+ const normalized = normalizeAopStatus(status);
270
+ const explicit = this.config[`state_id_${normalized}`];
271
+ if (explicit && explicit.length > 0) {
272
+ return explicit;
273
+ }
274
+ const defaultKey = mapDefaultLinearStatus(status);
275
+ if (!defaultKey) {
276
+ return null;
277
+ }
278
+ const defaultMapped = this.config[`state_id_${defaultKey}`];
279
+ return defaultMapped && defaultMapped.length > 0 ? defaultMapped : null;
280
+ }
281
+
282
+ async getIssue(issueId: string): Promise<Issue> {
283
+ const issue = await this.resolveIssueNode(issueId).catch(() => null);
284
+ if (!issue) {
285
+ return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
286
+ }
287
+ return {
288
+ id: issue.identifier || issue.id || issueId,
289
+ title: issue.title,
290
+ body: issue.description,
291
+ status: lowerCaseStatus(issue.state?.name ?? ''),
292
+ url: issue.url
293
+ };
294
+ }
295
+
296
+ async updateIssueStatus(issueId: string, status: string): Promise<void> {
297
+ const issue = await this.resolveIssueNode(issueId).catch(() => null);
298
+ if (!issue?.id) {
299
+ return;
300
+ }
301
+ const stateId = this.resolveStateId(status);
302
+ if (!stateId) {
303
+ return;
304
+ }
305
+ await this.graphQl(
306
+ 'mutation AopIssueUpdate($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success } }',
307
+ { id: issue.id, stateId }
308
+ );
309
+ }
310
+
311
+ async addComment(issueId: string, comment: string): Promise<void> {
312
+ const issue = await this.resolveIssueNode(issueId).catch(() => null);
313
+ if (!issue?.id) {
314
+ return;
315
+ }
316
+ await this.graphQl(
317
+ 'mutation AopCommentCreate($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success } }',
318
+ { issueId: issue.id, body: comment }
319
+ );
320
+ }
321
+ }
322
+
323
+ function toJiraDescription(value: unknown): string {
324
+ if (typeof value === 'string') {
325
+ return value;
326
+ }
327
+ if (!value || typeof value !== 'object') {
328
+ return '';
329
+ }
330
+ return JSON.stringify(value);
331
+ }
332
+
333
+ // --- Jira adapter (REST HTTP) ---
334
+ export class JiraIssueTracker implements IssueTracker {
335
+ private readonly baseUrl: string;
336
+ private readonly token: string;
337
+ private readonly email: string;
338
+ private readonly config: Record<string, string>;
339
+ private readonly httpRunner: HttpRunner;
340
+
341
+ constructor(config: Record<string, string> = {}, httpRunner?: HttpRunner) {
342
+ this.baseUrl = (config['base_url'] ?? config['url'] ?? '').replace(/\/+$/, '');
343
+ this.token = config['token'] ?? '';
344
+ this.email = config['email'] ?? config['user'] ?? '';
345
+ this.config = config;
346
+ this.httpRunner = createHttpRunner(httpRunner);
347
+ }
348
+
349
+ private authHeader(): string | null {
350
+ if (this.email && this.token) {
351
+ return `Basic ${Buffer.from(`${this.email}:${this.token}`, 'utf8').toString('base64')}`;
352
+ }
353
+ if (this.token) {
354
+ return `Bearer ${this.token}`;
355
+ }
356
+ return null;
357
+ }
358
+
359
+ private async requestJson<T>(
360
+ endpoint: string,
361
+ method: 'GET' | 'POST',
362
+ body?: Record<string, unknown>
363
+ ): Promise<T | null> {
364
+ if (!this.baseUrl) {
365
+ return null;
366
+ }
367
+ const headers: Record<string, string> = {
368
+ Accept: 'application/json'
369
+ };
370
+ const authHeader = this.authHeader();
371
+ if (authHeader) {
372
+ headers['Authorization'] = authHeader;
373
+ }
374
+ if (body !== undefined) {
375
+ headers['Content-Type'] = 'application/json';
376
+ }
377
+ const result = await this.httpRunner(`${this.baseUrl}${endpoint}`, {
378
+ method,
379
+ headers,
380
+ body: body === undefined ? undefined : JSON.stringify(body)
381
+ });
382
+ if (!result.ok || !result.body) {
383
+ return null;
384
+ }
385
+ const parsed = tryParseJson(result.body);
386
+ return parsed as T | null;
387
+ }
388
+
389
+ private resolveTransitionName(status: string): string {
390
+ const normalized = normalizeAopStatus(status);
391
+ const explicit = this.config[`transition_${normalized}`];
392
+ if (explicit && explicit.length > 0) {
393
+ return explicit.toLowerCase();
394
+ }
395
+ return mapDefaultJiraTransition(status);
396
+ }
397
+
398
+ async getIssue(issueId: string): Promise<Issue> {
399
+ const payload = await this.requestJson<{
400
+ key?: string;
401
+ fields?: {
402
+ summary?: unknown;
403
+ description?: unknown;
404
+ status?: {
405
+ name?: unknown;
406
+ };
407
+ };
408
+ }>(`/rest/api/2/issue/${encodeURIComponent(issueId)}?fields=summary,description,status`, 'GET');
409
+ if (!payload) {
410
+ return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
411
+ }
412
+ const fields = payload.fields ?? {};
413
+ return {
414
+ id: readString(payload.key) || issueId,
415
+ title: readString(fields.summary),
416
+ body: toJiraDescription(fields.description),
417
+ status: lowerCaseStatus(readString(fields.status?.name)),
418
+ url: this.baseUrl ? `${this.baseUrl}/browse/${issueId}` : ''
419
+ };
420
+ }
421
+
422
+ async updateIssueStatus(issueId: string, status: string): Promise<void> {
423
+ const transitionsPayload = await this.requestJson<{ transitions?: Array<{ id?: string; name?: string }> }>(
424
+ `/rest/api/2/issue/${encodeURIComponent(issueId)}/transitions`,
425
+ 'GET'
426
+ );
427
+ const transitions = transitionsPayload?.transitions ?? [];
428
+ const targetName = this.resolveTransitionName(status);
429
+ const transition = transitions.find(
430
+ (item) => typeof item.name === 'string' && item.name.toLowerCase() === targetName
431
+ );
432
+ if (!transition?.id) {
433
+ return;
434
+ }
435
+ await this.requestJson(
436
+ `/rest/api/2/issue/${encodeURIComponent(issueId)}/transitions`,
437
+ 'POST',
438
+ { transition: { id: transition.id } }
439
+ );
440
+ }
441
+
442
+ async addComment(issueId: string, comment: string): Promise<void> {
443
+ await this.requestJson(
444
+ `/rest/api/2/issue/${encodeURIComponent(issueId)}/comment`,
445
+ 'POST',
446
+ { body: comment }
447
+ );
448
+ }
449
+ }
450
+
451
+ // --- Factory ---
452
+ export function createIssueTracker(
453
+ config: IssueTrackerConfig | undefined,
454
+ ghRunner?: GhRunner,
455
+ httpRunner?: HttpRunner
456
+ ): IssueTracker | undefined {
457
+ if (!config) {return undefined;}
458
+ if (config.enabled === false) {return undefined;}
459
+ switch (config.type) {
460
+ case 'github':
461
+ return new GitHubIssueTracker(config.config ?? {}, ghRunner);
462
+ case 'linear':
463
+ return new LinearIssueTracker(config.config ?? {}, httpRunner);
464
+ case 'jira':
465
+ return new JiraIssueTracker(config.config ?? {}, httpRunner);
466
+ default:
467
+ return undefined;
468
+ }
469
+ }