@shepai/cli 1.116.3 → 1.117.0-pr367.ca5b6ac

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 (460) hide show
  1. package/apis/json-schema/AnalyzeRepoTimeouts.yaml +10 -0
  2. package/apis/json-schema/SdlcLifecycle.yaml +1 -0
  3. package/apis/json-schema/StageTimeouts.yaml +35 -0
  4. package/apis/json-schema/WorkflowConfig.yaml +6 -0
  5. package/dist/packages/core/src/application/ports/output/services/index.d.ts +2 -0
  6. package/dist/packages/core/src/application/ports/output/services/index.d.ts.map +1 -1
  7. package/dist/packages/core/src/application/ports/output/services/tunnel-service.interface.d.ts +41 -0
  8. package/dist/packages/core/src/application/ports/output/services/tunnel-service.interface.d.ts.map +1 -0
  9. package/dist/packages/core/src/application/ports/output/services/tunnel-service.interface.js +14 -0
  10. package/dist/packages/core/src/application/ports/output/services/webhook-service.interface.d.ts +65 -0
  11. package/dist/packages/core/src/application/ports/output/services/webhook-service.interface.d.ts.map +1 -0
  12. package/dist/packages/core/src/application/ports/output/services/webhook-service.interface.js +10 -0
  13. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  14. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +17 -8
  15. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +2 -0
  16. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
  17. package/dist/packages/core/src/application/use-cases/features/start-feature.use-case.d.ts +25 -0
  18. package/dist/packages/core/src/application/use-cases/features/start-feature.use-case.d.ts.map +1 -0
  19. package/dist/packages/core/src/application/use-cases/features/start-feature.use-case.js +116 -0
  20. package/dist/packages/core/src/domain/generated/output.d.ts +47 -0
  21. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  22. package/dist/packages/core/src/domain/generated/output.js +1 -0
  23. package/dist/packages/core/src/domain/lifecycle-gates.d.ts +3 -0
  24. package/dist/packages/core/src/domain/lifecycle-gates.d.ts.map +1 -1
  25. package/dist/packages/core/src/domain/lifecycle-gates.js +3 -0
  26. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  27. package/dist/packages/core/src/infrastructure/di/container.js +19 -0
  28. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +7 -0
  29. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
  30. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.js +44 -0
  31. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/035-add-stage-timeout-ms.d.ts +22 -0
  32. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/035-add-stage-timeout-ms.d.ts.map +1 -0
  33. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations/035-add-stage-timeout-ms.js +53 -0
  34. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  35. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +15 -6
  36. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.d.ts.map +1 -1
  37. package/dist/packages/core/src/infrastructure/repositories/sqlite-settings.repository.js +15 -0
  38. package/dist/packages/core/src/infrastructure/services/agents/analyze-repo/analyze-repository-graph.d.ts.map +1 -1
  39. package/dist/packages/core/src/infrastructure/services/agents/analyze-repo/analyze-repository-graph.js +15 -0
  40. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts +11 -1
  41. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
  42. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +42 -3
  43. package/dist/packages/core/src/infrastructure/services/tunnel/cloudflare-tunnel.service.d.ts +55 -0
  44. package/dist/packages/core/src/infrastructure/services/tunnel/cloudflare-tunnel.service.d.ts.map +1 -0
  45. package/dist/packages/core/src/infrastructure/services/tunnel/cloudflare-tunnel.service.js +203 -0
  46. package/dist/packages/core/src/infrastructure/services/webhook/github-webhook.service.d.ts +75 -0
  47. package/dist/packages/core/src/infrastructure/services/webhook/github-webhook.service.d.ts.map +1 -0
  48. package/dist/packages/core/src/infrastructure/services/webhook/github-webhook.service.js +372 -0
  49. package/dist/packages/core/src/infrastructure/services/webhook/webhook-manager.service.d.ts +82 -0
  50. package/dist/packages/core/src/infrastructure/services/webhook/webhook-manager.service.d.ts.map +1 -0
  51. package/dist/packages/core/src/infrastructure/services/webhook/webhook-manager.service.js +206 -0
  52. package/dist/src/presentation/cli/commands/feat/index.d.ts.map +1 -1
  53. package/dist/src/presentation/cli/commands/feat/index.js +2 -0
  54. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
  55. package/dist/src/presentation/cli/commands/feat/ls.command.js +3 -0
  56. package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
  57. package/dist/src/presentation/cli/commands/feat/new.command.js +9 -1
  58. package/dist/src/presentation/cli/commands/feat/start.command.d.ts +12 -0
  59. package/dist/src/presentation/cli/commands/feat/start.command.d.ts.map +1 -0
  60. package/dist/src/presentation/cli/commands/feat/start.command.js +43 -0
  61. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  62. package/dist/src/presentation/cli/commands/ui.command.js +15 -0
  63. package/dist/src/presentation/web/app/actions/create-feature.d.ts +2 -0
  64. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  65. package/dist/src/presentation/web/app/actions/create-feature.js +3 -1
  66. package/dist/src/presentation/web/app/actions/load-webhook-status.d.ts +3 -0
  67. package/dist/src/presentation/web/app/actions/load-webhook-status.d.ts.map +1 -0
  68. package/dist/src/presentation/web/app/actions/load-webhook-status.js +19 -0
  69. package/dist/src/presentation/web/app/actions/start-feature.d.ts +5 -0
  70. package/dist/src/presentation/web/app/actions/start-feature.d.ts.map +1 -0
  71. package/dist/src/presentation/web/app/actions/start-feature.js +16 -0
  72. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  73. package/dist/src/presentation/web/app/api/agent-events/route.js +1 -0
  74. package/dist/src/presentation/web/app/api/webhooks/deliveries/route.d.ts +9 -0
  75. package/dist/src/presentation/web/app/api/webhooks/deliveries/route.d.ts.map +1 -0
  76. package/dist/src/presentation/web/app/api/webhooks/deliveries/route.js +27 -0
  77. package/dist/src/presentation/web/app/api/webhooks/github/route.d.ts +17 -0
  78. package/dist/src/presentation/web/app/api/webhooks/github/route.d.ts.map +1 -0
  79. package/dist/src/presentation/web/app/api/webhooks/github/route.js +70 -0
  80. package/dist/src/presentation/web/app/api/webhooks/status/route.d.ts +9 -0
  81. package/dist/src/presentation/web/app/api/webhooks/status/route.d.ts.map +1 -0
  82. package/dist/src/presentation/web/app/api/webhooks/status/route.js +26 -0
  83. package/dist/src/presentation/web/app/webhooks/page.d.ts +4 -0
  84. package/dist/src/presentation/web/app/webhooks/page.d.ts.map +1 -0
  85. package/dist/src/presentation/web/app/webhooks/page.js +9 -0
  86. package/dist/src/presentation/web/components/common/base-drawer/base-drawer.js +1 -1
  87. package/dist/src/presentation/web/components/common/base-drawer/base-drawer.stories.js +1 -1
  88. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  89. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +20 -1
  90. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +2 -0
  91. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  92. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +6 -2
  93. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  94. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +6 -0
  95. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +3 -2
  96. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
  97. package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +13 -1
  98. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  99. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +12 -3
  100. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts +2 -0
  101. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
  102. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +36 -2
  103. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  104. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +3 -1
  105. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +1 -0
  106. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  107. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +23 -0
  108. package/dist/src/presentation/web/components/features/settings/settings-page-client.d.ts.map +1 -1
  109. package/dist/src/presentation/web/components/features/settings/settings-page-client.js +75 -2
  110. package/dist/src/presentation/web/components/features/settings/settings-page-client.stories.d.ts +1 -0
  111. package/dist/src/presentation/web/components/features/settings/settings-page-client.stories.d.ts.map +1 -1
  112. package/dist/src/presentation/web/components/features/settings/settings-page-client.stories.js +23 -0
  113. package/dist/src/presentation/web/components/features/settings/timeout-slider.d.ts +11 -0
  114. package/dist/src/presentation/web/components/features/settings/timeout-slider.d.ts.map +1 -0
  115. package/dist/src/presentation/web/components/features/settings/timeout-slider.js +63 -0
  116. package/dist/src/presentation/web/components/features/settings/timeout-slider.stories.d.ts +10 -0
  117. package/dist/src/presentation/web/components/features/settings/timeout-slider.stories.d.ts.map +1 -0
  118. package/dist/src/presentation/web/components/features/settings/timeout-slider.stories.js +24 -0
  119. package/dist/src/presentation/web/components/features/settings/workflow-settings-section.d.ts.map +1 -1
  120. package/dist/src/presentation/web/components/features/settings/workflow-settings-section.js +97 -2
  121. package/dist/src/presentation/web/components/features/settings/workflow-settings-section.stories.d.ts.map +1 -1
  122. package/dist/src/presentation/web/components/features/settings/workflow-settings-section.stories.js +22 -0
  123. package/dist/src/presentation/web/components/features/webhooks/types.d.ts +32 -0
  124. package/dist/src/presentation/web/components/features/webhooks/types.d.ts.map +1 -0
  125. package/dist/src/presentation/web/components/features/webhooks/types.js +1 -0
  126. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.d.ts +6 -0
  127. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.d.ts.map +1 -0
  128. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.js +38 -0
  129. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.stories.d.ts +9 -0
  130. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.stories.d.ts.map +1 -0
  131. package/dist/src/presentation/web/components/features/webhooks/webhook-delivery-table.stories.js +69 -0
  132. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.d.ts +7 -0
  133. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.d.ts.map +1 -0
  134. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.js +11 -0
  135. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.stories.d.ts +9 -0
  136. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.stories.d.ts.map +1 -0
  137. package/dist/src/presentation/web/components/features/webhooks/webhook-repo-list.stories.js +31 -0
  138. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.d.ts +6 -0
  139. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.d.ts.map +1 -0
  140. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.js +21 -0
  141. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.stories.d.ts +9 -0
  142. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.stories.d.ts.map +1 -0
  143. package/dist/src/presentation/web/components/features/webhooks/webhook-status-cards.stories.js +56 -0
  144. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.d.ts +6 -0
  145. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.d.ts.map +1 -0
  146. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.js +39 -0
  147. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.stories.d.ts +8 -0
  148. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.stories.d.ts.map +1 -0
  149. package/dist/src/presentation/web/components/features/webhooks/webhooks-page-client.stories.js +59 -0
  150. package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts.map +1 -1
  151. package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.js +2 -2
  152. package/dist/src/presentation/web/components/ui/slider.d.ts +5 -0
  153. package/dist/src/presentation/web/components/ui/slider.d.ts.map +1 -0
  154. package/dist/src/presentation/web/components/ui/slider.js +10 -0
  155. package/dist/src/presentation/web/components/ui/slider.stories.d.ts +7 -0
  156. package/dist/src/presentation/web/components/ui/slider.stories.d.ts.map +1 -0
  157. package/dist/src/presentation/web/components/ui/slider.stories.js +15 -0
  158. package/dist/src/presentation/web/dev-server.js +20 -0
  159. package/dist/src/presentation/web/hooks/sidebar-features-context.d.ts.map +1 -1
  160. package/dist/src/presentation/web/hooks/sidebar-features-context.js +1 -0
  161. package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -1
  162. package/dist/src/presentation/web/hooks/use-graph-state.js +1 -0
  163. package/dist/src/presentation/web/lib/derive-graph.d.ts +2 -0
  164. package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -1
  165. package/dist/src/presentation/web/lib/derive-graph.js +5 -0
  166. package/dist/tsconfig.build.tsbuildinfo +1 -1
  167. package/package.json +3 -1
  168. package/web/.next/BUILD_ID +1 -1
  169. package/web/.next/app-path-routes-manifest.json +5 -1
  170. package/web/.next/build-manifest.json +6 -6
  171. package/web/.next/fallback-build-manifest.json +2 -2
  172. package/web/.next/prerender-manifest.json +3 -3
  173. package/web/.next/required-server-files.js +3 -3
  174. package/web/.next/required-server-files.json +3 -3
  175. package/web/.next/routes-manifest.json +24 -0
  176. package/web/.next/server/app/(dashboard)/@drawer/create/page/build-manifest.json +4 -4
  177. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +55 -40
  178. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +1 -1
  179. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  180. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  181. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/build-manifest.json +4 -4
  182. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +67 -52
  183. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js +1 -1
  184. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  185. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  186. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/build-manifest.json +4 -4
  187. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +67 -52
  188. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +1 -1
  189. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  190. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  191. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/build-manifest.json +4 -4
  192. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +51 -36
  193. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +1 -1
  194. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  195. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  196. package/web/.next/server/app/(dashboard)/create/page/build-manifest.json +4 -4
  197. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +55 -40
  198. package/web/.next/server/app/(dashboard)/create/page.js +1 -1
  199. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  200. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  201. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/build-manifest.json +4 -4
  202. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +67 -52
  203. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js +1 -1
  204. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  205. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  206. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/build-manifest.json +4 -4
  207. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +67 -52
  208. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +1 -1
  209. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  210. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  211. package/web/.next/server/app/(dashboard)/page/build-manifest.json +4 -4
  212. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +51 -36
  213. package/web/.next/server/app/(dashboard)/page.js +1 -1
  214. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  215. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  216. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/build-manifest.json +4 -4
  217. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +51 -36
  218. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +1 -1
  219. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  220. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  221. package/web/.next/server/app/_global-error/page/build-manifest.json +4 -4
  222. package/web/.next/server/app/_global-error.html +2 -2
  223. package/web/.next/server/app/_global-error.rsc +1 -1
  224. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  225. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  226. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  227. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  228. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  229. package/web/.next/server/app/_not-found/page/build-manifest.json +4 -4
  230. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  231. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  232. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  233. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  234. package/web/.next/server/app/api/evidence/route.js.nft.json +1 -1
  235. package/web/.next/server/app/api/webhooks/deliveries/route/app-paths-manifest.json +3 -0
  236. package/web/.next/server/app/api/webhooks/deliveries/route/build-manifest.json +11 -0
  237. package/web/.next/server/app/api/webhooks/deliveries/route/server-reference-manifest.json +4 -0
  238. package/web/.next/server/app/api/webhooks/deliveries/route.js +6 -0
  239. package/web/.next/server/app/api/webhooks/deliveries/route.js.map +5 -0
  240. package/web/.next/server/app/api/webhooks/deliveries/route.js.nft.json +1 -0
  241. package/web/.next/server/app/api/webhooks/deliveries/route_client-reference-manifest.js +2 -0
  242. package/web/.next/server/app/api/webhooks/github/route/app-paths-manifest.json +3 -0
  243. package/web/.next/server/app/api/webhooks/github/route/build-manifest.json +11 -0
  244. package/web/.next/server/app/api/webhooks/github/route/server-reference-manifest.json +4 -0
  245. package/web/.next/server/app/api/webhooks/github/route.js +6 -0
  246. package/web/.next/server/app/api/webhooks/github/route.js.map +5 -0
  247. package/web/.next/server/app/api/webhooks/github/route.js.nft.json +1 -0
  248. package/web/.next/server/app/api/webhooks/github/route_client-reference-manifest.js +2 -0
  249. package/web/.next/server/app/api/webhooks/status/route/app-paths-manifest.json +3 -0
  250. package/web/.next/server/app/api/webhooks/status/route/build-manifest.json +11 -0
  251. package/web/.next/server/app/api/webhooks/status/route/server-reference-manifest.json +4 -0
  252. package/web/.next/server/app/api/webhooks/status/route.js +6 -0
  253. package/web/.next/server/app/api/webhooks/status/route.js.map +5 -0
  254. package/web/.next/server/app/api/webhooks/status/route.js.nft.json +1 -0
  255. package/web/.next/server/app/api/webhooks/status/route_client-reference-manifest.js +2 -0
  256. package/web/.next/server/app/settings/page/build-manifest.json +4 -4
  257. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  258. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  259. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  260. package/web/.next/server/app/skills/page/build-manifest.json +4 -4
  261. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  262. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  263. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  264. package/web/.next/server/app/tools/page/build-manifest.json +4 -4
  265. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  266. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  267. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  268. package/web/.next/server/app/version/page/build-manifest.json +4 -4
  269. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  270. package/web/.next/server/app/version/page.js.nft.json +1 -1
  271. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  272. package/web/.next/server/app/webhooks/page/app-paths-manifest.json +3 -0
  273. package/web/.next/server/app/webhooks/page/build-manifest.json +18 -0
  274. package/web/.next/server/app/webhooks/page/next-font-manifest.json +6 -0
  275. package/web/.next/server/app/webhooks/page/react-loadable-manifest.json +1 -0
  276. package/web/.next/server/app/webhooks/page/server-reference-manifest.json +35 -0
  277. package/web/.next/server/app/webhooks/page.js +16 -0
  278. package/web/.next/server/app/webhooks/page.js.map +5 -0
  279. package/web/.next/server/app/webhooks/page.js.nft.json +1 -0
  280. package/web/.next/server/app/webhooks/page_client-reference-manifest.js +2 -0
  281. package/web/.next/server/app-paths-manifest.json +5 -1
  282. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js +1 -1
  283. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
  284. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  285. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  286. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_deliveries_route_actions_d9e34c8d.js +3 -0
  287. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_deliveries_route_actions_d9e34c8d.js.map +1 -0
  288. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_github_route_actions_1b0d0c38.js +3 -0
  289. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_github_route_actions_1b0d0c38.js.map +1 -0
  290. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_status_route_actions_1a0d86a1.js +3 -0
  291. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_webhooks_status_route_actions_1a0d86a1.js.map +1 -0
  292. package/web/.next/server/chunks/[root-of-the-server]__15e5d45c._.js +3 -0
  293. package/web/.next/server/chunks/[root-of-the-server]__15e5d45c._.js.map +1 -0
  294. package/web/.next/server/chunks/[root-of-the-server]__23be381e._.js +3 -0
  295. package/web/.next/server/chunks/[root-of-the-server]__23be381e._.js.map +1 -0
  296. package/web/.next/server/chunks/[root-of-the-server]__7d5492a0._.js +1 -1
  297. package/web/.next/server/chunks/[root-of-the-server]__7d5492a0._.js.map +1 -1
  298. package/web/.next/server/chunks/[root-of-the-server]__9fc760c0._.js +3 -0
  299. package/web/.next/server/chunks/[root-of-the-server]__9fc760c0._.js.map +1 -0
  300. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  301. package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js +1 -1
  302. package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js.map +1 -1
  303. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  304. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  305. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_activity_22f9667f.js +3 -0
  306. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_activity_22f9667f.js.map +1 -0
  307. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_ed372306.js +3 -0
  308. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_ed372306.js.map +1 -0
  309. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_32e26434.js +3 -0
  310. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_32e26434.js.map +1 -0
  311. package/web/.next/server/chunks/ssr/[root-of-the-server]__07a39ff2._.js +1 -1
  312. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  313. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  314. package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbd8062c._.js → [root-of-the-server]__172d9576._.js} +2 -2
  315. package/web/.next/server/chunks/ssr/{[root-of-the-server]__e70c5627._.js.map → [root-of-the-server]__172d9576._.js.map} +1 -1
  316. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js +2 -2
  317. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js.map +1 -1
  318. package/web/.next/server/chunks/ssr/{[root-of-the-server]__039967e3._.js → [root-of-the-server]__2a5592d5._.js} +2 -2
  319. package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbd8062c._.js.map → [root-of-the-server]__2a5592d5._.js.map} +1 -1
  320. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d847b7b._.js +3 -0
  321. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d847b7b._.js.map +1 -0
  322. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  323. package/web/.next/server/chunks/ssr/[root-of-the-server]__87a7ae30._.js +3 -0
  324. package/web/.next/server/chunks/ssr/[root-of-the-server]__87a7ae30._.js.map +1 -0
  325. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js +2 -2
  326. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js.map +1 -1
  327. package/web/.next/server/chunks/ssr/[root-of-the-server]__920dccd5._.js +4 -0
  328. package/web/.next/server/chunks/ssr/[root-of-the-server]__920dccd5._.js.map +1 -0
  329. package/web/.next/server/chunks/ssr/[root-of-the-server]__a42f5a88._.js +3 -0
  330. package/web/.next/server/chunks/ssr/[root-of-the-server]__a42f5a88._.js.map +1 -0
  331. package/web/.next/server/chunks/ssr/{[root-of-the-server]__e70c5627._.js → [root-of-the-server]__a93289b0._.js} +2 -2
  332. package/web/.next/server/chunks/ssr/{[root-of-the-server]__2773312c._.js.map → [root-of-the-server]__a93289b0._.js.map} +1 -1
  333. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  334. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  335. package/web/.next/server/chunks/ssr/{[root-of-the-server]__2773312c._.js → [root-of-the-server]__ac0304e7._.js} +2 -2
  336. package/web/.next/server/chunks/ssr/{[root-of-the-server]__039967e3._.js.map → [root-of-the-server]__ac0304e7._.js.map} +1 -1
  337. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  338. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  339. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  340. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  341. package/web/.next/server/chunks/ssr/_250a63ae._.js +1 -1
  342. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -1
  343. package/web/.next/server/chunks/ssr/_26b2c311._.js +3 -0
  344. package/web/.next/server/chunks/ssr/_26b2c311._.js.map +1 -0
  345. package/web/.next/server/chunks/ssr/{_6f0118a0._.js → _719ef347._.js} +2 -2
  346. package/web/.next/server/chunks/ssr/_719ef347._.js.map +1 -0
  347. package/web/.next/server/chunks/ssr/_809c53f6._.js +9 -0
  348. package/web/.next/server/chunks/ssr/_809c53f6._.js.map +1 -0
  349. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  350. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  351. package/web/.next/server/chunks/ssr/_98047ffd._.js +3 -0
  352. package/web/.next/server/chunks/ssr/_98047ffd._.js.map +1 -0
  353. package/web/.next/server/chunks/ssr/_9eea4c51._.js +3 -0
  354. package/web/.next/server/chunks/ssr/_9eea4c51._.js.map +1 -0
  355. package/web/.next/server/chunks/ssr/_a4899a76._.js +3 -0
  356. package/web/.next/server/chunks/ssr/_a4899a76._.js.map +1 -0
  357. package/web/.next/server/chunks/ssr/_b715515b._.js +3 -0
  358. package/web/.next/server/chunks/ssr/{_c5f84841._.js.map → _b715515b._.js.map} +1 -1
  359. package/web/.next/server/chunks/ssr/_b7eb93fc._.js +4 -0
  360. package/web/.next/server/chunks/ssr/_b7eb93fc._.js.map +1 -0
  361. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  362. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  363. package/web/.next/server/chunks/ssr/_e0a01382._.js +3 -0
  364. package/web/.next/server/chunks/ssr/_e0a01382._.js.map +1 -0
  365. package/web/.next/server/chunks/ssr/_f8b36e2c._.js +3 -0
  366. package/web/.next/server/chunks/ssr/_f8b36e2c._.js.map +1 -0
  367. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  368. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  369. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_webhooks_webhooks-page-client_tsx_b2662ea3._.js +3 -0
  370. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_webhooks_webhooks-page-client_tsx_b2662ea3._.js.map +1 -0
  371. package/web/.next/server/chunks/ssr/{_1bb131c4._.js → node_modules__pnpm_747b43ac._.js} +2 -2
  372. package/web/.next/server/chunks/ssr/node_modules__pnpm_747b43ac._.js.map +1 -0
  373. package/web/.next/server/chunks/ssr/src_presentation_web_0835b28e._.js +5 -0
  374. package/web/.next/server/chunks/ssr/src_presentation_web_0835b28e._.js.map +1 -0
  375. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_e99d78dd.js +3 -0
  376. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_e99d78dd.js.map +1 -0
  377. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  378. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  379. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  380. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  381. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  382. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  383. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  384. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  385. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  386. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  387. package/web/.next/server/chunks/ssr/src_presentation_web_e8034a29._.js +5 -0
  388. package/web/.next/server/chunks/ssr/src_presentation_web_e8034a29._.js.map +1 -0
  389. package/web/.next/server/middleware-build-manifest.js +4 -4
  390. package/web/.next/server/pages/500.html +2 -2
  391. package/web/.next/server/server-reference-manifest.js +1 -1
  392. package/web/.next/server/server-reference-manifest.json +323 -230
  393. package/web/.next/static/chunks/06b948216df083c1.js +1 -0
  394. package/web/.next/static/chunks/{52285fdb39925ad5.js → 2ac228556e3d1db4.js} +1 -1
  395. package/web/.next/static/chunks/31f35710932542ef.js +1 -0
  396. package/web/.next/static/chunks/40b6bcf1a2de4a0f.js +1 -0
  397. package/web/.next/static/chunks/4714eb891fa4fd81.js +1 -0
  398. package/web/.next/static/chunks/57094e1cbd326d6c.css +1 -0
  399. package/web/.next/static/chunks/{18de73b2f14204d3.js → 5856c588eb7f21fd.js} +1 -1
  400. package/web/.next/static/chunks/5f6161ceb4263319.js +1 -0
  401. package/web/.next/static/chunks/81f470d21748eafd.js +2 -0
  402. package/web/.next/static/chunks/829305a6e02eff1f.js +1 -0
  403. package/web/.next/static/chunks/97aaeeee72be634b.js +1 -0
  404. package/web/.next/static/chunks/{672e3da3ad26005b.js → 98e3a7cf58fc912a.js} +1 -1
  405. package/web/.next/static/chunks/9a7361e2365b2d8e.js +1 -0
  406. package/web/.next/static/chunks/9af45e885bd4ccef.js +1 -0
  407. package/web/.next/static/chunks/{5de387864b89e64c.js → ab8b8f80cbc8366f.js} +2 -2
  408. package/web/.next/static/chunks/c3c3f2c9eb309920.js +1 -0
  409. package/web/.next/static/chunks/c7e793951b20a67f.js +1 -0
  410. package/web/.next/static/chunks/cb2824954c3975b0.js +1 -0
  411. package/web/.next/static/chunks/{2babc8e3ae9ba6e8.js → d3db43fcdbbd9b68.js} +1 -1
  412. package/web/.next/static/chunks/e9621b096a69e4e8.js +11 -0
  413. package/web/.next/static/chunks/fb99c57e6d28a274.js +1 -0
  414. package/web/.next/static/chunks/{turbopack-426d9bc2450816ba.js → turbopack-b1685b5f87ad1d60.js} +1 -1
  415. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js +0 -3
  416. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js.map +0 -1
  417. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js +0 -3
  418. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js.map +0 -1
  419. package/web/.next/server/chunks/ssr/[root-of-the-server]__6367aea0._.js +0 -3
  420. package/web/.next/server/chunks/ssr/[root-of-the-server]__6367aea0._.js.map +0 -1
  421. package/web/.next/server/chunks/ssr/[root-of-the-server]__b1e68e34._.js +0 -3
  422. package/web/.next/server/chunks/ssr/[root-of-the-server]__b1e68e34._.js.map +0 -1
  423. package/web/.next/server/chunks/ssr/_1bb131c4._.js.map +0 -1
  424. package/web/.next/server/chunks/ssr/_273d4632._.js +0 -3
  425. package/web/.next/server/chunks/ssr/_273d4632._.js.map +0 -1
  426. package/web/.next/server/chunks/ssr/_6d26a08f._.js +0 -3
  427. package/web/.next/server/chunks/ssr/_6d26a08f._.js.map +0 -1
  428. package/web/.next/server/chunks/ssr/_6f0118a0._.js.map +0 -1
  429. package/web/.next/server/chunks/ssr/_a68d7e1c._.js +0 -9
  430. package/web/.next/server/chunks/ssr/_a68d7e1c._.js.map +0 -1
  431. package/web/.next/server/chunks/ssr/_b5fc318a._.js +0 -3
  432. package/web/.next/server/chunks/ssr/_b5fc318a._.js.map +0 -1
  433. package/web/.next/server/chunks/ssr/_c5f84841._.js +0 -3
  434. package/web/.next/server/chunks/ssr/_d2e408a9._.js +0 -3
  435. package/web/.next/server/chunks/ssr/_d2e408a9._.js.map +0 -1
  436. package/web/.next/server/chunks/ssr/_f5316e67._.js +0 -3
  437. package/web/.next/server/chunks/ssr/_f5316e67._.js.map +0 -1
  438. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js +0 -3
  439. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js.map +0 -1
  440. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js +0 -5
  441. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js.map +0 -1
  442. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js +0 -5
  443. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js.map +0 -1
  444. package/web/.next/static/chunks/0b3ab176958a8286.js +0 -1
  445. package/web/.next/static/chunks/242446982e8d196e.js +0 -1
  446. package/web/.next/static/chunks/303a1217006489b9.js +0 -1
  447. package/web/.next/static/chunks/3a806cf059241bc9.js +0 -1
  448. package/web/.next/static/chunks/552e0f129144b54d.js +0 -1
  449. package/web/.next/static/chunks/57b45edf86522369.css +0 -1
  450. package/web/.next/static/chunks/619af41a8d3232e5.js +0 -1
  451. package/web/.next/static/chunks/7b924c47dd2a5a53.js +0 -2
  452. package/web/.next/static/chunks/84a181ff3270fd9f.js +0 -1
  453. package/web/.next/static/chunks/b1f3c0e8e9872ef8.js +0 -1
  454. package/web/.next/static/chunks/c172f4a6010cb5b5.js +0 -1
  455. package/web/.next/static/chunks/e12f41ac3d49a7b5.js +0 -11
  456. package/web/.next/static/chunks/ed3a602003bcb31b.js +0 -1
  457. package/web/.next/static/chunks/fcde0cbb2976b933.js +0 -1
  458. /package/web/.next/static/{CStnC7-QgiTd6U99YUc1N → T6bpRqj4p37ell4NNYIUl}/_buildManifest.js +0 -0
  459. /package/web/.next/static/{CStnC7-QgiTd6U99YUc1N → T6bpRqj4p37ell4NNYIUl}/_clientMiddlewareManifest.json +0 -0
  460. /package/web/.next/static/{CStnC7-QgiTd6U99YUc1N → T6bpRqj4p37ell4NNYIUl}/_ssgManifest.js +0 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Cloudflare Tunnel Service
3
+ *
4
+ * Manages a `cloudflared` quick-tunnel that exposes ONLY the webhook
5
+ * API routes to the public internet via a random *.trycloudflare.com
6
+ * subdomain.
7
+ *
8
+ * Uses the `cloudflared` npm package which handles binary installation
9
+ * and provides a typed Tunnel API — no need for the user to install
10
+ * cloudflared separately.
11
+ *
12
+ * Security: A lightweight proxy HTTP server is started on a random port
13
+ * that only forwards requests matching the allowed path prefix
14
+ * (/api/webhooks) to the main app. The tunnel connects to this proxy,
15
+ * not the main app server. All other paths return 404.
16
+ */
17
+ import { createServer, request as httpRequest } from 'node:http';
18
+ const TAG = '[CloudflareTunnel]';
19
+ const STARTUP_TIMEOUT_MS = 30_000;
20
+ const ALLOWED_PATH_PREFIX = '/api/webhooks';
21
+ async function defaultCreateTunnel(origin) {
22
+ const { Tunnel } = await import('cloudflared');
23
+ return Tunnel.quick(origin);
24
+ }
25
+ const defaultDeps = {
26
+ createTunnel: defaultCreateTunnel,
27
+ };
28
+ export class CloudflareTunnelService {
29
+ tunnel = null;
30
+ proxyServer = null;
31
+ publicUrl = null;
32
+ urlChangeHandlers = [];
33
+ deps;
34
+ constructor(deps = {}) {
35
+ this.deps = { ...defaultDeps, ...deps };
36
+ }
37
+ async start(localPort) {
38
+ if (this.tunnel) {
39
+ throw new Error(`${TAG} Tunnel already running at ${this.publicUrl}`);
40
+ }
41
+ // Start a proxy server that only forwards webhook routes
42
+ const proxyPort = await this.startProxyServer(localPort);
43
+ let tunnel;
44
+ try {
45
+ tunnel = await Promise.resolve(this.deps.createTunnel(`http://localhost:${proxyPort}`));
46
+ }
47
+ catch (err) {
48
+ this.stopProxyServer();
49
+ throw new Error(`${TAG} Failed to create tunnel: ${err.message}`);
50
+ }
51
+ this.tunnel = tunnel;
52
+ return new Promise((resolve, reject) => {
53
+ const timeout = setTimeout(() => {
54
+ this.stopTunnel();
55
+ reject(new Error(`${TAG} Timed out waiting for tunnel URL (${STARTUP_TIMEOUT_MS}ms)`));
56
+ }, STARTUP_TIMEOUT_MS);
57
+ tunnel.on('url', (url) => {
58
+ if (!this.publicUrl) {
59
+ // First URL — startup complete
60
+ this.publicUrl = url;
61
+ clearTimeout(timeout);
62
+ // eslint-disable-next-line no-console
63
+ console.log(`${TAG} Tunnel ready: ${url} (proxying ${ALLOWED_PATH_PREFIX}/* to port ${localPort})`);
64
+ resolve(url);
65
+ }
66
+ else if (url !== this.publicUrl) {
67
+ // URL changed — reconnection with new subdomain
68
+ const oldUrl = this.publicUrl;
69
+ this.publicUrl = url;
70
+ // eslint-disable-next-line no-console
71
+ console.log(`${TAG} Tunnel URL changed: ${oldUrl} -> ${url}`);
72
+ this.notifyUrlChange(url);
73
+ }
74
+ });
75
+ tunnel.on('error', (err) => {
76
+ clearTimeout(timeout);
77
+ this.tunnel = null;
78
+ this.publicUrl = null;
79
+ this.stopProxyServer();
80
+ reject(new Error(`${TAG} Failed to start tunnel: ${err.message}`));
81
+ });
82
+ tunnel.on('exit', (code) => {
83
+ clearTimeout(timeout);
84
+ if (this.tunnel === tunnel) {
85
+ this.tunnel = null;
86
+ const wasRunning = this.publicUrl !== null;
87
+ this.publicUrl = null;
88
+ this.stopProxyServer();
89
+ if (wasRunning) {
90
+ // eslint-disable-next-line no-console
91
+ console.log(`${TAG} Tunnel process exited (code ${code})`);
92
+ }
93
+ else {
94
+ reject(new Error(`${TAG} cloudflared exited with code ${code} before producing a URL`));
95
+ }
96
+ }
97
+ });
98
+ });
99
+ }
100
+ async stop() {
101
+ this.stopTunnel();
102
+ this.publicUrl = null;
103
+ }
104
+ getPublicUrl() {
105
+ return this.publicUrl;
106
+ }
107
+ onUrlChange(handler) {
108
+ this.urlChangeHandlers.push(handler);
109
+ }
110
+ isRunning() {
111
+ return this.tunnel !== null && this.publicUrl !== null;
112
+ }
113
+ /**
114
+ * Start a lightweight HTTP proxy on a random port that only forwards
115
+ * requests with paths starting with ALLOWED_PATH_PREFIX to the main app.
116
+ * Everything else gets a 404.
117
+ */
118
+ startProxyServer(targetPort) {
119
+ return new Promise((resolve, reject) => {
120
+ const server = createServer((req, res) => {
121
+ const url = req.url ?? '';
122
+ if (!url.startsWith(ALLOWED_PATH_PREFIX)) {
123
+ res.writeHead(404, { 'Content-Type': 'application/json' });
124
+ res.end(JSON.stringify({ error: 'Not found' }));
125
+ return;
126
+ }
127
+ // Proxy the request to the main app
128
+ const proxyReq = httpRequest({
129
+ hostname: 'localhost',
130
+ port: targetPort,
131
+ path: url,
132
+ method: req.method,
133
+ headers: req.headers,
134
+ }, (proxyRes) => {
135
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
136
+ proxyRes.pipe(res);
137
+ });
138
+ proxyReq.on('error', (err) => {
139
+ // eslint-disable-next-line no-console
140
+ console.warn(`${TAG} Proxy error: ${err.message}`);
141
+ if (!res.headersSent) {
142
+ res.writeHead(502, { 'Content-Type': 'application/json' });
143
+ }
144
+ res.end(JSON.stringify({ error: 'Bad gateway' }));
145
+ });
146
+ req.pipe(proxyReq);
147
+ });
148
+ // Listen on port 0 = OS assigns a random available port
149
+ server.listen(0, '127.0.0.1', () => {
150
+ const addr = server.address();
151
+ if (addr && typeof addr === 'object') {
152
+ this.proxyServer = server;
153
+ // eslint-disable-next-line no-console
154
+ console.log(`${TAG} Webhook proxy listening on port ${addr.port}`);
155
+ resolve(addr.port);
156
+ }
157
+ else {
158
+ server.close();
159
+ reject(new Error(`${TAG} Failed to start proxy server`));
160
+ }
161
+ });
162
+ server.on('error', (err) => {
163
+ reject(new Error(`${TAG} Proxy server error: ${err.message}`));
164
+ });
165
+ });
166
+ }
167
+ stopProxyServer() {
168
+ if (this.proxyServer) {
169
+ this.proxyServer.close();
170
+ this.proxyServer = null;
171
+ }
172
+ }
173
+ stopTunnel() {
174
+ if (this.tunnel) {
175
+ const tunnel = this.tunnel;
176
+ this.tunnel = null;
177
+ try {
178
+ tunnel.stop();
179
+ }
180
+ catch {
181
+ // Tunnel already stopped
182
+ }
183
+ }
184
+ this.stopProxyServer();
185
+ }
186
+ notifyUrlChange(newUrl) {
187
+ for (const handler of this.urlChangeHandlers) {
188
+ try {
189
+ const result = handler(newUrl);
190
+ if (result instanceof Promise) {
191
+ result.catch((err) => {
192
+ // eslint-disable-next-line no-console
193
+ console.warn(`${TAG} URL change handler failed:`, err);
194
+ });
195
+ }
196
+ }
197
+ catch (err) {
198
+ // eslint-disable-next-line no-console
199
+ console.warn(`${TAG} URL change handler threw:`, err);
200
+ }
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * GitHub Webhook Service
3
+ *
4
+ * Manages GitHub webhook registration and incoming event processing.
5
+ * Uses the GitHub CLI (`gh api`) for webhook CRUD operations.
6
+ *
7
+ * Webhook events supplement the existing PR sync polling:
8
+ * - Webhooks provide near-instant event delivery
9
+ * - Polling remains as a fallback for missed events
10
+ *
11
+ * Registered events: pull_request, check_suite, check_run
12
+ */
13
+ import type { IWebhookService, WebhookEvent, WebhookValidationResult } from '../../../application/ports/output/services/webhook-service.interface.js';
14
+ import type { IFeatureRepository } from '../../../application/ports/output/repositories/feature-repository.interface.js';
15
+ import type { IGitPrService } from '../../../application/ports/output/services/git-pr-service.interface.js';
16
+ import type { INotificationService } from '../../../application/ports/output/services/notification-service.interface.js';
17
+ export type ExecFunction = (file: string, args: string[], options?: object) => Promise<{
18
+ stdout: string;
19
+ stderr: string;
20
+ }>;
21
+ export interface RegisteredWebhook {
22
+ repoFullName: string;
23
+ webhookId: number;
24
+ repositoryPath: string;
25
+ }
26
+ export type WebhookDeliveryStatus = 'success' | 'ignored' | 'error';
27
+ export interface WebhookDeliveryRecord {
28
+ deliveryId: string;
29
+ eventType: string;
30
+ source: string;
31
+ receivedAt: string;
32
+ status: WebhookDeliveryStatus;
33
+ statusMessage: string;
34
+ durationMs: number;
35
+ payload: Record<string, unknown>;
36
+ }
37
+ export declare class GitHubWebhookService implements IWebhookService {
38
+ private readonly featureRepo;
39
+ private readonly gitPrService;
40
+ private readonly notificationService;
41
+ private readonly execFn;
42
+ private readonly registeredWebhooks;
43
+ private readonly deliveryHistory;
44
+ private webhookSecret;
45
+ constructor(featureRepo: IFeatureRepository, gitPrService: IGitPrService, notificationService: INotificationService, execFn: ExecFunction);
46
+ /**
47
+ * Get the webhook secret for signature validation.
48
+ * Exposed so the API route can validate incoming requests.
49
+ */
50
+ getSecret(): string;
51
+ /**
52
+ * Get the list of registered webhooks (for dashboard display).
53
+ */
54
+ getRegisteredWebhooks(): readonly RegisteredWebhook[];
55
+ /**
56
+ * Get recent delivery history (newest first).
57
+ */
58
+ getDeliveryHistory(): readonly WebhookDeliveryRecord[];
59
+ registerWebhooks(publicUrl: string): Promise<void>;
60
+ updateWebhookUrl(newUrl: string): Promise<void>;
61
+ removeWebhooks(): Promise<void>;
62
+ validateSignature(payload: string, signature: string, secret: string): WebhookValidationResult;
63
+ handleEvent(event: WebhookEvent): Promise<void>;
64
+ private recordDelivery;
65
+ private handlePullRequestEvent;
66
+ private handleCheckSuiteEvent;
67
+ private handleCheckRunEvent;
68
+ private findFeatureByPrOrBranch;
69
+ private findFeatureByBranch;
70
+ private updateFeaturePrStatus;
71
+ private updateFeatureCiStatus;
72
+ private registerWebhookForRepo;
73
+ private getRepoFullName;
74
+ }
75
+ //# sourceMappingURL=github-webhook.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-webhook.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/webhook/github-webhook.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,uBAAuB,EACxB,MAAM,yEAAyE,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gFAAgF,CAAC;AACzH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wEAAwE,CAAC;AAC5G,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8EAA8E,CAAC;AASzH,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAC7C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuB;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA2B;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA+B;IAC/D,OAAO,CAAC,aAAa,CAAS;gBAG5B,WAAW,EAAE,kBAAkB,EAC/B,YAAY,EAAE,aAAa,EAC3B,mBAAmB,EAAE,oBAAoB,EACzC,MAAM,EAAE,YAAY;IAStB;;;OAGG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,qBAAqB,IAAI,SAAS,iBAAiB,EAAE;IAIrD;;OAEG;IACH,kBAAkB,IAAI,SAAS,qBAAqB,EAAE;IAIhD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlD,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkC/C,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BrC,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,uBAAuB;IAgCxF,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CrD,OAAO,CAAC,cAAc;YAOR,sBAAsB;YAsBtB,qBAAqB;YAiBrB,mBAAmB;YAoBnB,uBAAuB;YAWvB,mBAAmB;YAKnB,qBAAqB;YA6CrB,qBAAqB;YA4BrB,sBAAsB;YAqDtB,eAAe;CAe9B"}
@@ -0,0 +1,372 @@
1
+ /**
2
+ * GitHub Webhook Service
3
+ *
4
+ * Manages GitHub webhook registration and incoming event processing.
5
+ * Uses the GitHub CLI (`gh api`) for webhook CRUD operations.
6
+ *
7
+ * Webhook events supplement the existing PR sync polling:
8
+ * - Webhooks provide near-instant event delivery
9
+ * - Polling remains as a fallback for missed events
10
+ *
11
+ * Registered events: pull_request, check_suite, check_run
12
+ */
13
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
14
+ import { SdlcLifecycle, PrStatus, CiStatus } from '../../../domain/generated/output.js';
15
+ import { NotificationEventType, NotificationSeverity } from '../../../domain/generated/output.js';
16
+ const TAG = '[GitHubWebhook]';
17
+ const WEBHOOK_EVENTS = ['pull_request', 'check_suite', 'check_run'];
18
+ const MAX_EVENT_HISTORY = 200;
19
+ export class GitHubWebhookService {
20
+ featureRepo;
21
+ gitPrService;
22
+ notificationService;
23
+ execFn;
24
+ registeredWebhooks = [];
25
+ deliveryHistory = [];
26
+ webhookSecret;
27
+ constructor(featureRepo, gitPrService, notificationService, execFn) {
28
+ this.featureRepo = featureRepo;
29
+ this.gitPrService = gitPrService;
30
+ this.notificationService = notificationService;
31
+ this.execFn = execFn;
32
+ this.webhookSecret = randomBytes(32).toString('hex');
33
+ }
34
+ /**
35
+ * Get the webhook secret for signature validation.
36
+ * Exposed so the API route can validate incoming requests.
37
+ */
38
+ getSecret() {
39
+ return this.webhookSecret;
40
+ }
41
+ /**
42
+ * Get the list of registered webhooks (for dashboard display).
43
+ */
44
+ getRegisteredWebhooks() {
45
+ return this.registeredWebhooks;
46
+ }
47
+ /**
48
+ * Get recent delivery history (newest first).
49
+ */
50
+ getDeliveryHistory() {
51
+ return this.deliveryHistory;
52
+ }
53
+ async registerWebhooks(publicUrl) {
54
+ const webhookUrl = `${publicUrl}/api/webhooks/github`;
55
+ // Find all repos with features in Review lifecycle
56
+ const features = await this.featureRepo.list({ lifecycle: SdlcLifecycle.Review });
57
+ const repoPaths = new Set();
58
+ for (const feature of features) {
59
+ if (feature.repositoryPath) {
60
+ repoPaths.add(feature.repositoryPath);
61
+ }
62
+ }
63
+ for (const repoPath of repoPaths) {
64
+ await this.registerWebhookForRepo(repoPath, webhookUrl);
65
+ }
66
+ // eslint-disable-next-line no-console
67
+ console.log(`${TAG} Registered webhooks for ${this.registeredWebhooks.length} repositories`);
68
+ }
69
+ async updateWebhookUrl(newUrl) {
70
+ const webhookUrl = `${newUrl}/api/webhooks/github`;
71
+ for (const webhook of this.registeredWebhooks) {
72
+ try {
73
+ await this.execFn('gh', [
74
+ 'api',
75
+ '--method',
76
+ 'PATCH',
77
+ `-H`,
78
+ 'Accept: application/vnd.github+json',
79
+ `/repos/${webhook.repoFullName}/hooks/${webhook.webhookId}`,
80
+ '-f',
81
+ `config[url]=${webhookUrl}`,
82
+ '-f',
83
+ `config[content_type]=json`,
84
+ '-f',
85
+ `config[secret]=${this.webhookSecret}`,
86
+ ], { cwd: webhook.repositoryPath });
87
+ // eslint-disable-next-line no-console
88
+ console.log(`${TAG} Updated webhook URL for ${webhook.repoFullName}`);
89
+ }
90
+ catch (error) {
91
+ const msg = error instanceof Error ? error.message : String(error);
92
+ // eslint-disable-next-line no-console
93
+ console.warn(`${TAG} Failed to update webhook for ${webhook.repoFullName}: ${msg}`);
94
+ }
95
+ }
96
+ }
97
+ async removeWebhooks() {
98
+ for (const webhook of this.registeredWebhooks) {
99
+ try {
100
+ await this.execFn('gh', [
101
+ 'api',
102
+ '--method',
103
+ 'DELETE',
104
+ `-H`,
105
+ 'Accept: application/vnd.github+json',
106
+ `/repos/${webhook.repoFullName}/hooks/${webhook.webhookId}`,
107
+ ], { cwd: webhook.repositoryPath });
108
+ // eslint-disable-next-line no-console
109
+ console.log(`${TAG} Removed webhook for ${webhook.repoFullName}`);
110
+ }
111
+ catch (error) {
112
+ const msg = error instanceof Error ? error.message : String(error);
113
+ // eslint-disable-next-line no-console
114
+ console.warn(`${TAG} Failed to remove webhook for ${webhook.repoFullName}: ${msg}`);
115
+ }
116
+ }
117
+ this.registeredWebhooks.length = 0;
118
+ }
119
+ validateSignature(payload, signature, secret) {
120
+ if (!signature) {
121
+ return { valid: false, error: 'Missing signature header' };
122
+ }
123
+ const parts = signature.split('=');
124
+ if (parts.length !== 2 || parts[0] !== 'sha256') {
125
+ return { valid: false, error: 'Invalid signature format (expected sha256=...)' };
126
+ }
127
+ const expected = createHmac('sha256', secret).update(payload).digest('hex');
128
+ const actual = parts[1];
129
+ // Use timing-safe comparison to prevent timing attacks
130
+ try {
131
+ const expectedBuf = Buffer.from(expected, 'hex');
132
+ const actualBuf = Buffer.from(actual, 'hex');
133
+ if (expectedBuf.length !== actualBuf.length) {
134
+ return { valid: false, error: 'Signature length mismatch' };
135
+ }
136
+ if (!timingSafeEqual(expectedBuf, actualBuf)) {
137
+ return { valid: false, error: 'Signature mismatch' };
138
+ }
139
+ return { valid: true };
140
+ }
141
+ catch {
142
+ return { valid: false, error: 'Invalid signature encoding' };
143
+ }
144
+ }
145
+ async handleEvent(event) {
146
+ const startTime = Date.now();
147
+ // eslint-disable-next-line no-console
148
+ console.log(`${TAG} Received ${event.source}/${event.eventType} (delivery: ${event.deliveryId})`);
149
+ let status = 'success';
150
+ let statusMessage = 'Processed successfully';
151
+ try {
152
+ switch (event.eventType) {
153
+ case 'pull_request':
154
+ await this.handlePullRequestEvent(event.payload);
155
+ break;
156
+ case 'check_suite':
157
+ await this.handleCheckSuiteEvent(event.payload);
158
+ break;
159
+ case 'check_run':
160
+ await this.handleCheckRunEvent(event.payload);
161
+ break;
162
+ default:
163
+ status = 'ignored';
164
+ statusMessage = `Unhandled event type: ${event.eventType}`;
165
+ // eslint-disable-next-line no-console
166
+ console.log(`${TAG} Ignoring unhandled event type: ${event.eventType}`);
167
+ }
168
+ }
169
+ catch (error) {
170
+ status = 'error';
171
+ statusMessage = error instanceof Error ? error.message : String(error);
172
+ }
173
+ this.recordDelivery({
174
+ deliveryId: event.deliveryId,
175
+ eventType: event.eventType,
176
+ source: event.source,
177
+ receivedAt: new Date().toISOString(),
178
+ status,
179
+ statusMessage,
180
+ durationMs: Date.now() - startTime,
181
+ payload: event.payload,
182
+ });
183
+ }
184
+ recordDelivery(record) {
185
+ this.deliveryHistory.unshift(record);
186
+ if (this.deliveryHistory.length > MAX_EVENT_HISTORY) {
187
+ this.deliveryHistory.length = MAX_EVENT_HISTORY;
188
+ }
189
+ }
190
+ async handlePullRequestEvent(payload) {
191
+ const action = payload.action;
192
+ const pr = payload.pull_request;
193
+ if (!pr)
194
+ return;
195
+ const prNumber = pr.number;
196
+ const prUrl = pr.html_url ?? '';
197
+ const headBranch = pr.head?.ref;
198
+ // Find feature by PR number or branch
199
+ const feature = await this.findFeatureByPrOrBranch(prNumber, headBranch);
200
+ if (!feature)
201
+ return;
202
+ if (action === 'closed' && pr.merged === true) {
203
+ await this.updateFeaturePrStatus(feature, PrStatus.Merged, prNumber, prUrl);
204
+ }
205
+ else if (action === 'closed') {
206
+ await this.updateFeaturePrStatus(feature, PrStatus.Closed, prNumber, prUrl);
207
+ }
208
+ else if (action === 'reopened') {
209
+ await this.updateFeaturePrStatus(feature, PrStatus.Open, prNumber, prUrl);
210
+ }
211
+ }
212
+ async handleCheckSuiteEvent(payload) {
213
+ const action = payload.action;
214
+ if (action !== 'completed')
215
+ return;
216
+ const checkSuite = payload.check_suite;
217
+ if (!checkSuite)
218
+ return;
219
+ const conclusion = checkSuite.conclusion;
220
+ const headBranch = checkSuite.head_branch;
221
+ const feature = await this.findFeatureByBranch(headBranch);
222
+ if (!feature)
223
+ return;
224
+ const ciStatus = conclusion === 'success' ? CiStatus.Success : CiStatus.Failure;
225
+ await this.updateFeatureCiStatus(feature, ciStatus);
226
+ }
227
+ async handleCheckRunEvent(payload) {
228
+ const action = payload.action;
229
+ if (action !== 'completed')
230
+ return;
231
+ const checkRun = payload.check_run;
232
+ if (!checkRun)
233
+ return;
234
+ const conclusion = checkRun.conclusion;
235
+ const checkSuite = checkRun.check_suite;
236
+ const headBranch = checkSuite?.head_branch;
237
+ if (!headBranch)
238
+ return;
239
+ const feature = await this.findFeatureByBranch(headBranch);
240
+ if (!feature)
241
+ return;
242
+ const ciStatus = conclusion === 'success' ? CiStatus.Success : CiStatus.Failure;
243
+ await this.updateFeatureCiStatus(feature, ciStatus);
244
+ }
245
+ async findFeatureByPrOrBranch(prNumber, branch) {
246
+ const features = await this.featureRepo.list({ lifecycle: SdlcLifecycle.Review });
247
+ // Try matching by PR number first
248
+ const byPr = features.find((f) => f.pr?.number === prNumber);
249
+ if (byPr)
250
+ return byPr;
251
+ // Fall back to branch matching
252
+ return features.find((f) => f.branch === branch) ?? null;
253
+ }
254
+ async findFeatureByBranch(branch) {
255
+ const features = await this.featureRepo.list({ lifecycle: SdlcLifecycle.Review });
256
+ return features.find((f) => f.branch === branch) ?? null;
257
+ }
258
+ async updateFeaturePrStatus(feature, status, prNumber, prUrl) {
259
+ const oldStatus = feature.pr?.status;
260
+ if (oldStatus === status)
261
+ return;
262
+ feature.pr = { ...feature.pr, status, url: prUrl, number: prNumber };
263
+ if (status === PrStatus.Merged) {
264
+ feature.lifecycle = SdlcLifecycle.Maintain;
265
+ }
266
+ await this.featureRepo.update(feature);
267
+ // Emit notification
268
+ const eventType = status === PrStatus.Merged
269
+ ? NotificationEventType.PrMerged
270
+ : status === PrStatus.Closed
271
+ ? NotificationEventType.PrClosed
272
+ : NotificationEventType.PrChecksPassed; // reopened
273
+ const severity = status === PrStatus.Merged
274
+ ? NotificationSeverity.Success
275
+ : status === PrStatus.Closed
276
+ ? NotificationSeverity.Warning
277
+ : NotificationSeverity.Info;
278
+ const event = {
279
+ eventType,
280
+ featureId: feature.id,
281
+ agentRunId: feature.agentRunId ?? '',
282
+ featureName: feature.name,
283
+ message: `PR #${prNumber} ${status.toLowerCase()} for ${feature.name} (via webhook)`,
284
+ severity,
285
+ timestamp: new Date().toISOString(),
286
+ };
287
+ this.notificationService.notify(event);
288
+ }
289
+ async updateFeatureCiStatus(feature, ciStatus) {
290
+ if (!feature.pr)
291
+ return;
292
+ if (feature.pr.ciStatus === ciStatus)
293
+ return;
294
+ feature.pr = { ...feature.pr, ciStatus };
295
+ await this.featureRepo.update(feature);
296
+ const eventType = ciStatus === CiStatus.Success
297
+ ? NotificationEventType.PrChecksPassed
298
+ : NotificationEventType.PrChecksFailed;
299
+ const severity = ciStatus === CiStatus.Success ? NotificationSeverity.Success : NotificationSeverity.Error;
300
+ const event = {
301
+ eventType,
302
+ featureId: feature.id,
303
+ agentRunId: feature.agentRunId ?? '',
304
+ featureName: feature.name,
305
+ message: `CI ${ciStatus === CiStatus.Success ? 'passed' : 'failed'} for ${feature.name} (via webhook)`,
306
+ severity,
307
+ timestamp: new Date().toISOString(),
308
+ };
309
+ this.notificationService.notify(event);
310
+ }
311
+ async registerWebhookForRepo(repoPath, webhookUrl) {
312
+ try {
313
+ // Get the repo full name (owner/repo) from the remote URL
314
+ const repoFullName = await this.getRepoFullName(repoPath);
315
+ if (!repoFullName) {
316
+ // eslint-disable-next-line no-console
317
+ console.warn(`${TAG} Could not determine repo name for ${repoPath}`);
318
+ return;
319
+ }
320
+ // Create webhook via GitHub API
321
+ const { stdout } = await this.execFn('gh', [
322
+ 'api',
323
+ '--method',
324
+ 'POST',
325
+ `-H`,
326
+ 'Accept: application/vnd.github+json',
327
+ `/repos/${repoFullName}/hooks`,
328
+ '-f',
329
+ 'name=web',
330
+ '-f',
331
+ `config[url]=${webhookUrl}`,
332
+ '-f',
333
+ 'config[content_type]=json',
334
+ '-f',
335
+ `config[secret]=${this.webhookSecret}`,
336
+ ...WEBHOOK_EVENTS.flatMap((e) => ['-f', `events[]=${e}`]),
337
+ '-F',
338
+ 'active=true',
339
+ ], { cwd: repoPath });
340
+ const response = JSON.parse(stdout);
341
+ const webhookId = response.id;
342
+ this.registeredWebhooks.push({
343
+ repoFullName,
344
+ webhookId,
345
+ repositoryPath: repoPath,
346
+ });
347
+ // eslint-disable-next-line no-console
348
+ console.log(`${TAG} Registered webhook #${webhookId} for ${repoFullName}`);
349
+ }
350
+ catch (error) {
351
+ const msg = error instanceof Error ? error.message : String(error);
352
+ // eslint-disable-next-line no-console
353
+ console.warn(`${TAG} Failed to register webhook for ${repoPath}: ${msg}`);
354
+ }
355
+ }
356
+ async getRepoFullName(repoPath) {
357
+ try {
358
+ const remoteUrl = await this.gitPrService.getRemoteUrl(repoPath);
359
+ if (!remoteUrl)
360
+ return null;
361
+ // Parse owner/repo from URL
362
+ // Handles both https://github.com/owner/repo and git@github.com:owner/repo.git
363
+ const match = remoteUrl.match(/github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
364
+ if (!match)
365
+ return null;
366
+ return `${match[1]}/${match[2]}`;
367
+ }
368
+ catch {
369
+ return null;
370
+ }
371
+ }
372
+ }