tinybeeai 0.0.0-stage → 0.16.1

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 (1034) hide show
  1. package/.railway/railway.ts +83 -0
  2. package/.railway/tsconfig.json +13 -0
  3. package/AGENTS.md +102 -0
  4. package/Dockerfile +108 -0
  5. package/LICENSE +21 -0
  6. package/README.md +275 -2
  7. package/bin/localdost.js +29 -0
  8. package/bin/tinybeeai.js +28 -0
  9. package/compose.yml +63 -0
  10. package/demo/payloads/ci_failure.json +46 -0
  11. package/demo/payloads/outage.json +18 -0
  12. package/demo/payloads/pr_comment_edited.json +50 -0
  13. package/demo/payloads/pr_merged.json +80 -0
  14. package/demo/payloads/pr_opened.json +69 -0
  15. package/demo/payloads/pr_synchronize.json +69 -0
  16. package/demo/payloads/ticket_created.json +138 -0
  17. package/demo/sample-app/playwright.config.ts +20 -0
  18. package/demo/sample-app/server.ts +163 -0
  19. package/demo/sample-app/tests/checkout.spec.ts +13 -0
  20. package/demo/sample-app/tests/smoke.spec.ts +8 -0
  21. package/dist/agent/approval-retries.js +59 -0
  22. package/dist/agent/approval-retries.js.map +1 -0
  23. package/dist/agent/approvals.js +390 -0
  24. package/dist/agent/approvals.js.map +1 -0
  25. package/dist/agent/chat-bridge.js +154 -0
  26. package/dist/agent/chat-bridge.js.map +1 -0
  27. package/dist/agent/chat-lead.js +475 -0
  28. package/dist/agent/chat-lead.js.map +1 -0
  29. package/dist/agent/chat-loop.js +106 -0
  30. package/dist/agent/chat-loop.js.map +1 -0
  31. package/dist/agent/chat-mcp.js +49 -0
  32. package/dist/agent/chat-mcp.js.map +1 -0
  33. package/dist/agent/chat-tool-blocks.js +453 -0
  34. package/dist/agent/chat-tool-blocks.js.map +1 -0
  35. package/dist/agent/chat-view.js +94 -0
  36. package/dist/agent/chat-view.js.map +1 -0
  37. package/dist/agent/chat-views.js +781 -0
  38. package/dist/agent/chat-views.js.map +1 -0
  39. package/dist/agent/chat.js +1703 -0
  40. package/dist/agent/chat.js.map +1 -0
  41. package/dist/agent/destinations.js +150 -0
  42. package/dist/agent/destinations.js.map +1 -0
  43. package/dist/agent/llm-cli.js +451 -0
  44. package/dist/agent/llm-cli.js.map +1 -0
  45. package/dist/agent/llm-codex.js +493 -0
  46. package/dist/agent/llm-codex.js.map +1 -0
  47. package/dist/agent/llm-openai.js +211 -0
  48. package/dist/agent/llm-openai.js.map +1 -0
  49. package/dist/agent/llm.js +1464 -0
  50. package/dist/agent/llm.js.map +1 -0
  51. package/dist/agent/memory.js +33 -0
  52. package/dist/agent/memory.js.map +1 -0
  53. package/dist/agent/mock-views.js +93 -0
  54. package/dist/agent/mock-views.js.map +1 -0
  55. package/dist/agent/model-availability.js +207 -0
  56. package/dist/agent/model-availability.js.map +1 -0
  57. package/dist/agent/model-errors.js +188 -0
  58. package/dist/agent/model-errors.js.map +1 -0
  59. package/dist/agent/model-guard.js +311 -0
  60. package/dist/agent/model-guard.js.map +1 -0
  61. package/dist/agent/prompts.js +374 -0
  62. package/dist/agent/prompts.js.map +1 -0
  63. package/dist/agent/qa-lenses/qa-auth.md +32 -0
  64. package/dist/agent/qa-lenses/qa-check-then-act.md +33 -0
  65. package/dist/agent/qa-lenses/qa-control-has-no-effect.md +32 -0
  66. package/dist/agent/qa-lenses/qa-dates.md +31 -0
  67. package/dist/agent/qa-lenses/qa-double-submit.md +34 -0
  68. package/dist/agent/qa-lenses/qa-feature-flag.md +29 -0
  69. package/dist/agent/qa-lenses/qa-migration.md +35 -0
  70. package/dist/agent/qa-lenses/qa-money.md +38 -0
  71. package/dist/agent/qa-lenses/qa-response-shape-break.md +33 -0
  72. package/dist/agent/qa-lenses/qa-validation.md +29 -0
  73. package/dist/agent/qa-prompts.js +660 -0
  74. package/dist/agent/qa-prompts.js.map +1 -0
  75. package/dist/agent/reasoning.js +55 -0
  76. package/dist/agent/reasoning.js.map +1 -0
  77. package/dist/agent/redact.js +48 -0
  78. package/dist/agent/redact.js.map +1 -0
  79. package/dist/agent/rules.js +927 -0
  80. package/dist/agent/rules.js.map +1 -0
  81. package/dist/agent/runs.js +440 -0
  82. package/dist/agent/runs.js.map +1 -0
  83. package/dist/agent/send-retry.js +62 -0
  84. package/dist/agent/send-retry.js.map +1 -0
  85. package/dist/agent/team-notes.js +288 -0
  86. package/dist/agent/team-notes.js.map +1 -0
  87. package/dist/agent/token-meter.js +31 -0
  88. package/dist/agent/token-meter.js.map +1 -0
  89. package/dist/agent/triage.js +506 -0
  90. package/dist/agent/triage.js.map +1 -0
  91. package/dist/api.js +1047 -0
  92. package/dist/api.js.map +1 -0
  93. package/dist/approval-targets.js +107 -0
  94. package/dist/approval-targets.js.map +1 -0
  95. package/dist/bee/cloudflared.js +108 -0
  96. package/dist/bee/cloudflared.js.map +1 -0
  97. package/dist/bee/health.js +99 -0
  98. package/dist/bee/health.js.map +1 -0
  99. package/dist/bee/hive-client.js +75 -0
  100. package/dist/bee/hive-client.js.map +1 -0
  101. package/dist/bee/identity.js +39 -0
  102. package/dist/bee/identity.js.map +1 -0
  103. package/dist/bee/models.js +510 -0
  104. package/dist/bee/models.js.map +1 -0
  105. package/dist/bee/pairing.js +351 -0
  106. package/dist/bee/pairing.js.map +1 -0
  107. package/dist/bee/relay-client.js +50 -0
  108. package/dist/bee/relay-client.js.map +1 -0
  109. package/dist/bee/routes.js +214 -0
  110. package/dist/bee/routes.js.map +1 -0
  111. package/dist/bee/setup-page.js +223 -0
  112. package/dist/bee/setup-page.js.map +1 -0
  113. package/dist/bee/state.js +41 -0
  114. package/dist/bee/state.js.map +1 -0
  115. package/dist/bee/status.js +117 -0
  116. package/dist/bee/status.js.map +1 -0
  117. package/dist/bee/tunnel-manager.js +245 -0
  118. package/dist/bee/tunnel-manager.js.map +1 -0
  119. package/dist/bee/updater.js +268 -0
  120. package/dist/bee/updater.js.map +1 -0
  121. package/dist/boot.js +41 -0
  122. package/dist/boot.js.map +1 -0
  123. package/dist/call/index.js +145 -0
  124. package/dist/call/index.js.map +1 -0
  125. package/dist/case-folders.js +294 -0
  126. package/dist/case-folders.js.map +1 -0
  127. package/dist/cases.js +554 -0
  128. package/dist/cases.js.map +1 -0
  129. package/dist/chat-blocks.js +709 -0
  130. package/dist/chat-blocks.js.map +1 -0
  131. package/dist/chat-parse-util.js +38 -0
  132. package/dist/chat-parse-util.js.map +1 -0
  133. package/dist/chat-results.js +96 -0
  134. package/dist/chat-results.js.map +1 -0
  135. package/dist/chat-stats.js +193 -0
  136. package/dist/chat-stats.js.map +1 -0
  137. package/dist/chat-view-specs.js +186 -0
  138. package/dist/chat-view-specs.js.map +1 -0
  139. package/dist/cli/doctor-ops.js +361 -0
  140. package/dist/cli/doctor-ops.js.map +1 -0
  141. package/dist/cli/doctor.js +289 -0
  142. package/dist/cli/doctor.js.map +1 -0
  143. package/dist/cli/dogfood.js +70 -0
  144. package/dist/cli/dogfood.js.map +1 -0
  145. package/dist/cli/env-file.js +34 -0
  146. package/dist/cli/env-file.js.map +1 -0
  147. package/dist/cli/eval-chat-cases.js +359 -0
  148. package/dist/cli/eval-chat-cases.js.map +1 -0
  149. package/dist/cli/eval-chat-results.js +85 -0
  150. package/dist/cli/eval-chat-results.js.map +1 -0
  151. package/dist/cli/eval-chat.js +508 -0
  152. package/dist/cli/eval-chat.js.map +1 -0
  153. package/dist/cli/eval-designer.js +67 -0
  154. package/dist/cli/eval-designer.js.map +1 -0
  155. package/dist/cli/eval-qa-review.js +284 -0
  156. package/dist/cli/eval-qa-review.js.map +1 -0
  157. package/dist/cli/golden-export.js +35 -0
  158. package/dist/cli/golden-export.js.map +1 -0
  159. package/dist/cli/index.js +90 -0
  160. package/dist/cli/index.js.map +1 -0
  161. package/dist/cli/init.js +746 -0
  162. package/dist/cli/init.js.map +1 -0
  163. package/dist/cli/scan-repo.js +199 -0
  164. package/dist/cli/scan-repo.js.map +1 -0
  165. package/dist/cli/seed.js +108 -0
  166. package/dist/cli/seed.js.map +1 -0
  167. package/dist/cli/tinybee.js +247 -0
  168. package/dist/cli/tinybee.js.map +1 -0
  169. package/dist/config.js +138 -0
  170. package/dist/config.js.map +1 -0
  171. package/dist/connect/aivision-local.js +833 -0
  172. package/dist/connect/aivision-local.js.map +1 -0
  173. package/dist/connect/aivision-token.js +84 -0
  174. package/dist/connect/aivision-token.js.map +1 -0
  175. package/dist/connect/crypto.js +65 -0
  176. package/dist/connect/crypto.js.map +1 -0
  177. package/dist/connect/deps.js +110 -0
  178. package/dist/connect/deps.js.map +1 -0
  179. package/dist/connect/engines.js +144 -0
  180. package/dist/connect/engines.js.map +1 -0
  181. package/dist/connect/github-access.js +74 -0
  182. package/dist/connect/github-access.js.map +1 -0
  183. package/dist/connect/github-app.js +129 -0
  184. package/dist/connect/github-app.js.map +1 -0
  185. package/dist/connect/index.js +9 -0
  186. package/dist/connect/index.js.map +1 -0
  187. package/dist/connect/model-choices.js +201 -0
  188. package/dist/connect/model-choices.js.map +1 -0
  189. package/dist/connect/providers.js +900 -0
  190. package/dist/connect/providers.js.map +1 -0
  191. package/dist/connect/readiness.js +232 -0
  192. package/dist/connect/readiness.js.map +1 -0
  193. package/dist/connect/redact.js +16 -0
  194. package/dist/connect/redact.js.map +1 -0
  195. package/dist/connect/registry.js +406 -0
  196. package/dist/connect/registry.js.map +1 -0
  197. package/dist/connect/store.js +283 -0
  198. package/dist/connect/store.js.map +1 -0
  199. package/dist/connect/tunnel.js +134 -0
  200. package/dist/connect/tunnel.js.map +1 -0
  201. package/dist/connect/types.js +8 -0
  202. package/dist/connect/types.js.map +1 -0
  203. package/dist/connections/index.js +5 -0
  204. package/dist/connections/index.js.map +1 -0
  205. package/dist/connections/manager.js +223 -0
  206. package/dist/connections/manager.js.map +1 -0
  207. package/dist/connections/presets.js +264 -0
  208. package/dist/connections/presets.js.map +1 -0
  209. package/dist/connections/store.js +132 -0
  210. package/dist/connections/store.js.map +1 -0
  211. package/dist/connections/tools.js +206 -0
  212. package/dist/connections/tools.js.map +1 -0
  213. package/dist/db-schema.js +355 -0
  214. package/dist/db-schema.js.map +1 -0
  215. package/dist/db.js +379 -0
  216. package/dist/db.js.map +1 -0
  217. package/dist/delivery.js +49 -0
  218. package/dist/delivery.js.map +1 -0
  219. package/dist/deploy-wait.js +701 -0
  220. package/dist/deploy-wait.js.map +1 -0
  221. package/dist/disk.js +243 -0
  222. package/dist/disk.js.map +1 -0
  223. package/dist/dot-health.js +966 -0
  224. package/dist/dot-health.js.map +1 -0
  225. package/dist/dot-remove.js +246 -0
  226. package/dist/dot-remove.js.map +1 -0
  227. package/dist/events.js +38 -0
  228. package/dist/events.js.map +1 -0
  229. package/dist/evidence-pack.js +423 -0
  230. package/dist/evidence-pack.js.map +1 -0
  231. package/dist/flows/answers.js +289 -0
  232. package/dist/flows/answers.js.map +1 -0
  233. package/dist/flows/change.js +811 -0
  234. package/dist/flows/change.js.map +1 -0
  235. package/dist/flows/checkStatus.js +524 -0
  236. package/dist/flows/checkStatus.js.map +1 -0
  237. package/dist/flows/ci.js +236 -0
  238. package/dist/flows/ci.js.map +1 -0
  239. package/dist/flows/ciFailure.js +76 -0
  240. package/dist/flows/ciFailure.js.map +1 -0
  241. package/dist/flows/common.js +522 -0
  242. package/dist/flows/common.js.map +1 -0
  243. package/dist/flows/default-branch.js +29 -0
  244. package/dist/flows/default-branch.js.map +1 -0
  245. package/dist/flows/defectDraft.js +447 -0
  246. package/dist/flows/defectDraft.js.map +1 -0
  247. package/dist/flows/defects.js +623 -0
  248. package/dist/flows/defects.js.map +1 -0
  249. package/dist/flows/digest.js +295 -0
  250. package/dist/flows/digest.js.map +1 -0
  251. package/dist/flows/impact.js +82 -0
  252. package/dist/flows/impact.js.map +1 -0
  253. package/dist/flows/index.js +23 -0
  254. package/dist/flows/index.js.map +1 -0
  255. package/dist/flows/intent-sources.js +72 -0
  256. package/dist/flows/intent-sources.js.map +1 -0
  257. package/dist/flows/mergeCases.js +169 -0
  258. package/dist/flows/mergeCases.js.map +1 -0
  259. package/dist/flows/nightlyRun.js +82 -0
  260. package/dist/flows/nightlyRun.js.map +1 -0
  261. package/dist/flows/outage.js +64 -0
  262. package/dist/flows/outage.js.map +1 -0
  263. package/dist/flows/postMerge.js +5 -0
  264. package/dist/flows/postMerge.js.map +1 -0
  265. package/dist/flows/prCheck.js +5 -0
  266. package/dist/flows/prCheck.js.map +1 -0
  267. package/dist/flows/prTarget.js +66 -0
  268. package/dist/flows/prTarget.js.map +1 -0
  269. package/dist/flows/qa.js +363 -0
  270. package/dist/flows/qa.js.map +1 -0
  271. package/dist/flows/questions.js +160 -0
  272. package/dist/flows/questions.js.map +1 -0
  273. package/dist/flows/recovery.js +64 -0
  274. package/dist/flows/recovery.js.map +1 -0
  275. package/dist/flows/rerun.js +50 -0
  276. package/dist/flows/rerun.js.map +1 -0
  277. package/dist/flows/testDesign.js +294 -0
  278. package/dist/flows/testDesign.js.map +1 -0
  279. package/dist/flows/testDesignJob.js +46 -0
  280. package/dist/flows/testDesignJob.js.map +1 -0
  281. package/dist/flows/testRepo.js +228 -0
  282. package/dist/flows/testRepo.js.map +1 -0
  283. package/dist/flows/testSavings.js +381 -0
  284. package/dist/flows/testSavings.js.map +1 -0
  285. package/dist/flows/testStatus.js +343 -0
  286. package/dist/flows/testStatus.js.map +1 -0
  287. package/dist/flows/ticketContext.js +425 -0
  288. package/dist/flows/ticketContext.js.map +1 -0
  289. package/dist/flows/ticketReview.js +249 -0
  290. package/dist/flows/ticketReview.js.map +1 -0
  291. package/dist/flows/ticks.js +73 -0
  292. package/dist/flows/ticks.js.map +1 -0
  293. package/dist/hive/admin.js +122 -0
  294. package/dist/hive/admin.js.map +1 -0
  295. package/dist/hive/auth.js +130 -0
  296. package/dist/hive/auth.js.map +1 -0
  297. package/dist/hive/billing.js +278 -0
  298. package/dist/hive/billing.js.map +1 -0
  299. package/dist/hive/config.js +63 -0
  300. package/dist/hive/config.js.map +1 -0
  301. package/dist/hive/db.js +186 -0
  302. package/dist/hive/db.js.map +1 -0
  303. package/dist/hive/embed.js +76 -0
  304. package/dist/hive/embed.js.map +1 -0
  305. package/dist/hive/errors.js +17 -0
  306. package/dist/hive/errors.js.map +1 -0
  307. package/dist/hive/google.js +75 -0
  308. package/dist/hive/google.js.map +1 -0
  309. package/dist/hive/index.js +25 -0
  310. package/dist/hive/index.js.map +1 -0
  311. package/dist/hive/jwt.js +57 -0
  312. package/dist/hive/jwt.js.map +1 -0
  313. package/dist/hive/keys.js +61 -0
  314. package/dist/hive/keys.js.map +1 -0
  315. package/dist/hive/mail.js +27 -0
  316. package/dist/hive/mail.js.map +1 -0
  317. package/dist/hive/native-push.js +255 -0
  318. package/dist/hive/native-push.js.map +1 -0
  319. package/dist/hive/pairing.js +342 -0
  320. package/dist/hive/pairing.js.map +1 -0
  321. package/dist/hive/presence.js +121 -0
  322. package/dist/hive/presence.js.map +1 -0
  323. package/dist/hive/push.js +185 -0
  324. package/dist/hive/push.js.map +1 -0
  325. package/dist/hive/relay.js +205 -0
  326. package/dist/hive/relay.js.map +1 -0
  327. package/dist/hive/routes.js +228 -0
  328. package/dist/hive/routes.js.map +1 -0
  329. package/dist/hive/server.js +12 -0
  330. package/dist/hive/server.js.map +1 -0
  331. package/dist/hive/session.js +28 -0
  332. package/dist/hive/session.js.map +1 -0
  333. package/dist/hive/tenants.js +287 -0
  334. package/dist/hive/tenants.js.map +1 -0
  335. package/dist/hive/tunnel/cloudflare.js +160 -0
  336. package/dist/hive/tunnel/cloudflare.js.map +1 -0
  337. package/dist/hive/tunnel/mock.js +52 -0
  338. package/dist/hive/tunnel/mock.js.map +1 -0
  339. package/dist/hive/tunnel/provider.js +19 -0
  340. package/dist/hive/tunnel/provider.js.map +1 -0
  341. package/dist/hive/updates.js +61 -0
  342. package/dist/hive/updates.js.map +1 -0
  343. package/dist/http-error.js +11 -0
  344. package/dist/http-error.js.map +1 -0
  345. package/dist/impact/classify.js +226 -0
  346. package/dist/impact/classify.js.map +1 -0
  347. package/dist/impact/find.js +259 -0
  348. package/dist/impact/find.js.map +1 -0
  349. package/dist/impact/pr.js +54 -0
  350. package/dist/impact/pr.js.map +1 -0
  351. package/dist/impact/store.js +365 -0
  352. package/dist/impact/store.js.map +1 -0
  353. package/dist/impact/text.js +158 -0
  354. package/dist/impact/text.js.map +1 -0
  355. package/dist/index.js +288 -0
  356. package/dist/index.js.map +1 -0
  357. package/dist/integrations/adf.js +397 -0
  358. package/dist/integrations/adf.js.map +1 -0
  359. package/dist/integrations/bot-marker.js +103 -0
  360. package/dist/integrations/bot-marker.js.map +1 -0
  361. package/dist/integrations/github-issues.js +705 -0
  362. package/dist/integrations/github-issues.js.map +1 -0
  363. package/dist/integrations/github.js +1284 -0
  364. package/dist/integrations/github.js.map +1 -0
  365. package/dist/integrations/issue-create.js +60 -0
  366. package/dist/integrations/issue-create.js.map +1 -0
  367. package/dist/integrations/jira.js +279 -0
  368. package/dist/integrations/jira.js.map +1 -0
  369. package/dist/integrations/linear.js +273 -0
  370. package/dist/integrations/linear.js.map +1 -0
  371. package/dist/integrations/own-identity-warm.js +47 -0
  372. package/dist/integrations/own-identity-warm.js.map +1 -0
  373. package/dist/integrations/own-identity.js +39 -0
  374. package/dist/integrations/own-identity.js.map +1 -0
  375. package/dist/integrations/status-change.js +42 -0
  376. package/dist/integrations/status-change.js.map +1 -0
  377. package/dist/lead-defects.js +202 -0
  378. package/dist/lead-defects.js.map +1 -0
  379. package/dist/lead-digest.js +333 -0
  380. package/dist/lead-digest.js.map +1 -0
  381. package/dist/lead-release.js +286 -0
  382. package/dist/lead-release.js.map +1 -0
  383. package/dist/lead.js +328 -0
  384. package/dist/lead.js.map +1 -0
  385. package/dist/legacy-names.js +51 -0
  386. package/dist/legacy-names.js.map +1 -0
  387. package/dist/limits.js +44 -0
  388. package/dist/limits.js.map +1 -0
  389. package/dist/link-trust.js +224 -0
  390. package/dist/link-trust.js.map +1 -0
  391. package/dist/links.js +219 -0
  392. package/dist/links.js.map +1 -0
  393. package/dist/loopback.js +35 -0
  394. package/dist/loopback.js.map +1 -0
  395. package/dist/model-cap.js +60 -0
  396. package/dist/model-cap.js.map +1 -0
  397. package/dist/model-usage.js +173 -0
  398. package/dist/model-usage.js.map +1 -0
  399. package/dist/monitor/backfill.js +39 -0
  400. package/dist/monitor/backfill.js.map +1 -0
  401. package/dist/monitor/browser-probe.js +198 -0
  402. package/dist/monitor/browser-probe.js.map +1 -0
  403. package/dist/monitor/catch-up.js +31 -0
  404. package/dist/monitor/catch-up.js.map +1 -0
  405. package/dist/monitor/pollers.js +1606 -0
  406. package/dist/monitor/pollers.js.map +1 -0
  407. package/dist/monitor/probes.js +178 -0
  408. package/dist/monitor/probes.js.map +1 -0
  409. package/dist/monitor/self-health.js +228 -0
  410. package/dist/monitor/self-health.js.map +1 -0
  411. package/dist/monitor/statuspage.js +105 -0
  412. package/dist/monitor/statuspage.js.map +1 -0
  413. package/dist/nightly-scheduler.js +127 -0
  414. package/dist/nightly-scheduler.js.map +1 -0
  415. package/dist/nightly.js +141 -0
  416. package/dist/nightly.js.map +1 -0
  417. package/dist/notify/headline.js +114 -0
  418. package/dist/notify/headline.js.map +1 -0
  419. package/dist/notify/push.js +174 -0
  420. package/dist/notify/push.js.map +1 -0
  421. package/dist/object-cards.js +585 -0
  422. package/dist/object-cards.js.map +1 -0
  423. package/dist/object-links.js +47 -0
  424. package/dist/object-links.js.map +1 -0
  425. package/dist/onboarding-checks.js +141 -0
  426. package/dist/onboarding-checks.js.map +1 -0
  427. package/dist/onboarding.js +401 -0
  428. package/dist/onboarding.js.map +1 -0
  429. package/dist/path-filters.js +62 -0
  430. package/dist/path-filters.js.map +1 -0
  431. package/dist/phone/alerts.js +67 -0
  432. package/dist/phone/alerts.js.map +1 -0
  433. package/dist/phone/auth.js +240 -0
  434. package/dist/phone/auth.js.map +1 -0
  435. package/dist/phone/pairing.js +442 -0
  436. package/dist/phone/pairing.js.map +1 -0
  437. package/dist/phone/prefs.js +27 -0
  438. package/dist/phone/prefs.js.map +1 -0
  439. package/dist/phone/tailscale.js +161 -0
  440. package/dist/phone/tailscale.js.map +1 -0
  441. package/dist/pr-scope.js +441 -0
  442. package/dist/pr-scope.js.map +1 -0
  443. package/dist/proc.js +156 -0
  444. package/dist/proc.js.map +1 -0
  445. package/dist/profile/cache.js +108 -0
  446. package/dist/profile/cache.js.map +1 -0
  447. package/dist/profile/ci.js +216 -0
  448. package/dist/profile/ci.js.map +1 -0
  449. package/dist/profile/index.js +8 -0
  450. package/dist/profile/index.js.map +1 -0
  451. package/dist/profile/job.js +135 -0
  452. package/dist/profile/job.js.map +1 -0
  453. package/dist/profile/queries/go-tags.scm +47 -0
  454. package/dist/profile/queries/java-tags.scm +25 -0
  455. package/dist/profile/queries/javascript-tags.scm +104 -0
  456. package/dist/profile/queries/python-tags.scm +19 -0
  457. package/dist/profile/queries/typescript-tags.scm +28 -0
  458. package/dist/profile/scan-thread.js +42 -0
  459. package/dist/profile/scan-thread.js.map +1 -0
  460. package/dist/profile/scan-worker.js +16 -0
  461. package/dist/profile/scan-worker.js.map +1 -0
  462. package/dist/profile/scan.js +351 -0
  463. package/dist/profile/scan.js.map +1 -0
  464. package/dist/profile/stack.js +528 -0
  465. package/dist/profile/stack.js.map +1 -0
  466. package/dist/profile/store.js +35 -0
  467. package/dist/profile/store.js.map +1 -0
  468. package/dist/profile/tests.js +399 -0
  469. package/dist/profile/tests.js.map +1 -0
  470. package/dist/queue.js +350 -0
  471. package/dist/queue.js.map +1 -0
  472. package/dist/refs.js +756 -0
  473. package/dist/refs.js.map +1 -0
  474. package/dist/release-notes.js +272 -0
  475. package/dist/release-notes.js.map +1 -0
  476. package/dist/reports/build.js +243 -0
  477. package/dist/reports/build.js.map +1 -0
  478. package/dist/reports/index.js +211 -0
  479. package/dist/reports/index.js.map +1 -0
  480. package/dist/reports/store.js +150 -0
  481. package/dist/reports/store.js.map +1 -0
  482. package/dist/reports/ticket-titles.js +124 -0
  483. package/dist/reports/ticket-titles.js.map +1 -0
  484. package/dist/reports/trace-map.js +1278 -0
  485. package/dist/reports/trace-map.js.map +1 -0
  486. package/dist/reports/traceability.js +422 -0
  487. package/dist/reports/traceability.js.map +1 -0
  488. package/dist/repos/mirror.js +356 -0
  489. package/dist/repos/mirror.js.map +1 -0
  490. package/dist/repos-github.js +108 -0
  491. package/dist/repos-github.js.map +1 -0
  492. package/dist/repos.js +891 -0
  493. package/dist/repos.js.map +1 -0
  494. package/dist/review/backend-anthropic.js +90 -0
  495. package/dist/review/backend-anthropic.js.map +1 -0
  496. package/dist/review/backend-cli.js +248 -0
  497. package/dist/review/backend-cli.js.map +1 -0
  498. package/dist/review/backend-codex.js +81 -0
  499. package/dist/review/backend-codex.js.map +1 -0
  500. package/dist/review/backend-mock.js +686 -0
  501. package/dist/review/backend-mock.js.map +1 -0
  502. package/dist/review/backend-only.js +168 -0
  503. package/dist/review/backend-only.js.map +1 -0
  504. package/dist/review/backend.js +105 -0
  505. package/dist/review/backend.js.map +1 -0
  506. package/dist/review/baseline/render-v1.js +483 -0
  507. package/dist/review/baseline/render-v1.js.map +1 -0
  508. package/dist/review/change-summary.js +197 -0
  509. package/dist/review/change-summary.js.map +1 -0
  510. package/dist/review/changelog.js +40 -0
  511. package/dist/review/changelog.js.map +1 -0
  512. package/dist/review/check.js +494 -0
  513. package/dist/review/check.js.map +1 -0
  514. package/dist/review/checkout.js +185 -0
  515. package/dist/review/checkout.js.map +1 -0
  516. package/dist/review/classify.js +178 -0
  517. package/dist/review/classify.js.map +1 -0
  518. package/dist/review/coverage.js +347 -0
  519. package/dist/review/coverage.js.map +1 -0
  520. package/dist/review/decision-eval.js +121 -0
  521. package/dist/review/decision-eval.js.map +1 -0
  522. package/dist/review/deliver.js +104 -0
  523. package/dist/review/deliver.js.map +1 -0
  524. package/dist/review/designer-eval.js +280 -0
  525. package/dist/review/designer-eval.js.map +1 -0
  526. package/dist/review/dogfood-digest.js +136 -0
  527. package/dist/review/dogfood-digest.js.map +1 -0
  528. package/dist/review/dogfood-eval.js +486 -0
  529. package/dist/review/dogfood-eval.js.map +1 -0
  530. package/dist/review/environment.js +109 -0
  531. package/dist/review/environment.js.map +1 -0
  532. package/dist/review/eval.js +405 -0
  533. package/dist/review/eval.js.map +1 -0
  534. package/dist/review/glob.js +110 -0
  535. package/dist/review/glob.js.map +1 -0
  536. package/dist/review/golden-export.js +219 -0
  537. package/dist/review/golden-export.js.map +1 -0
  538. package/dist/review/impact.js +406 -0
  539. package/dist/review/impact.js.map +1 -0
  540. package/dist/review/imports.js +542 -0
  541. package/dist/review/imports.js.map +1 -0
  542. package/dist/review/index.js +512 -0
  543. package/dist/review/index.js.map +1 -0
  544. package/dist/review/intent.js +128 -0
  545. package/dist/review/intent.js.map +1 -0
  546. package/dist/review/lenses.js +167 -0
  547. package/dist/review/lenses.js.map +1 -0
  548. package/dist/review/linked-tickets.js +293 -0
  549. package/dist/review/linked-tickets.js.map +1 -0
  550. package/dist/review/merge.js +370 -0
  551. package/dist/review/merge.js.map +1 -0
  552. package/dist/review/on-demand.js +218 -0
  553. package/dist/review/on-demand.js.map +1 -0
  554. package/dist/review/prompt-eval.js +299 -0
  555. package/dist/review/prompt-eval.js.map +1 -0
  556. package/dist/review/prompt-variants.js +37 -0
  557. package/dist/review/prompt-variants.js.map +1 -0
  558. package/dist/review/readability-eval.js +217 -0
  559. package/dist/review/readability-eval.js.map +1 -0
  560. package/dist/review/readability.js +295 -0
  561. package/dist/review/readability.js.map +1 -0
  562. package/dist/review/render.js +2409 -0
  563. package/dist/review/render.js.map +1 -0
  564. package/dist/review/repo.js +194 -0
  565. package/dist/review/repo.js.map +1 -0
  566. package/dist/review/report-plan.js +90 -0
  567. package/dist/review/report-plan.js.map +1 -0
  568. package/dist/review/routes.js +195 -0
  569. package/dist/review/routes.js.map +1 -0
  570. package/dist/review/run-summary.js +34 -0
  571. package/dist/review/run-summary.js.map +1 -0
  572. package/dist/review/run.js +245 -0
  573. package/dist/review/run.js.map +1 -0
  574. package/dist/review/scope.js +454 -0
  575. package/dist/review/scope.js.map +1 -0
  576. package/dist/review/security.js +432 -0
  577. package/dist/review/security.js.map +1 -0
  578. package/dist/review/signals.js +550 -0
  579. package/dist/review/signals.js.map +1 -0
  580. package/dist/review/specs.js +150 -0
  581. package/dist/review/specs.js.map +1 -0
  582. package/dist/review/ste.js +133 -0
  583. package/dist/review/ste.js.map +1 -0
  584. package/dist/review/test-repo-file.js +18 -0
  585. package/dist/review/test-repo-file.js.map +1 -0
  586. package/dist/review/test-repo-lines.js +53 -0
  587. package/dist/review/test-repo-lines.js.map +1 -0
  588. package/dist/review/ticket-on-demand.js +232 -0
  589. package/dist/review/ticket-on-demand.js.map +1 -0
  590. package/dist/review/yaml.js +298 -0
  591. package/dist/review/yaml.js.map +1 -0
  592. package/dist/room.js +361 -0
  593. package/dist/room.js.map +1 -0
  594. package/dist/routes/activity.js +219 -0
  595. package/dist/routes/activity.js.map +1 -0
  596. package/dist/routes/call.js +29 -0
  597. package/dist/routes/call.js.map +1 -0
  598. package/dist/routes/cards.js +40 -0
  599. package/dist/routes/cards.js.map +1 -0
  600. package/dist/routes/cases.js +133 -0
  601. package/dist/routes/cases.js.map +1 -0
  602. package/dist/routes/connect.js +268 -0
  603. package/dist/routes/connect.js.map +1 -0
  604. package/dist/routes/connection-presets.js +32 -0
  605. package/dist/routes/connection-presets.js.map +1 -0
  606. package/dist/routes/connections.js +177 -0
  607. package/dist/routes/connections.js.map +1 -0
  608. package/dist/routes/disk.js +19 -0
  609. package/dist/routes/disk.js.map +1 -0
  610. package/dist/routes/docs.js +71 -0
  611. package/dist/routes/docs.js.map +1 -0
  612. package/dist/routes/dot-suggest.js +1002 -0
  613. package/dist/routes/dot-suggest.js.map +1 -0
  614. package/dist/routes/evidence-pack.js +16 -0
  615. package/dist/routes/evidence-pack.js.map +1 -0
  616. package/dist/routes/health.js +121 -0
  617. package/dist/routes/health.js.map +1 -0
  618. package/dist/routes/impact.js +48 -0
  619. package/dist/routes/impact.js.map +1 -0
  620. package/dist/routes/lead.js +50 -0
  621. package/dist/routes/lead.js.map +1 -0
  622. package/dist/routes/links.js +13 -0
  623. package/dist/routes/links.js.map +1 -0
  624. package/dist/routes/markdown.js +62 -0
  625. package/dist/routes/markdown.js.map +1 -0
  626. package/dist/routes/onboarding.js +47 -0
  627. package/dist/routes/onboarding.js.map +1 -0
  628. package/dist/routes/phone.js +231 -0
  629. package/dist/routes/phone.js.map +1 -0
  630. package/dist/routes/release-notes.js +30 -0
  631. package/dist/routes/release-notes.js.map +1 -0
  632. package/dist/routes/reports.js +102 -0
  633. package/dist/routes/reports.js.map +1 -0
  634. package/dist/routes/repos.js +91 -0
  635. package/dist/routes/repos.js.map +1 -0
  636. package/dist/routes/review.js +283 -0
  637. package/dist/routes/review.js.map +1 -0
  638. package/dist/routes/roles.js +114 -0
  639. package/dist/routes/roles.js.map +1 -0
  640. package/dist/routes/room.js +21 -0
  641. package/dist/routes/room.js.map +1 -0
  642. package/dist/routes/runner.js +156 -0
  643. package/dist/routes/runner.js.map +1 -0
  644. package/dist/routes/runs.js +84 -0
  645. package/dist/routes/runs.js.map +1 -0
  646. package/dist/routes/setup.js +18 -0
  647. package/dist/routes/setup.js.map +1 -0
  648. package/dist/routes/test-runs.js +220 -0
  649. package/dist/routes/test-runs.js.map +1 -0
  650. package/dist/routes/ticket-status.js +17 -0
  651. package/dist/routes/ticket-status.js.map +1 -0
  652. package/dist/routes/traceability.js +143 -0
  653. package/dist/routes/traceability.js.map +1 -0
  654. package/dist/routes/true.js +142 -0
  655. package/dist/routes/true.js.map +1 -0
  656. package/dist/routes/updates.js +70 -0
  657. package/dist/routes/updates.js.map +1 -0
  658. package/dist/runner/adapters/index.js +12 -0
  659. package/dist/runner/adapters/index.js.map +1 -0
  660. package/dist/runner/adapters/playwright.js +68 -0
  661. package/dist/runner/adapters/playwright.js.map +1 -0
  662. package/dist/runner/aivision/cases.js +172 -0
  663. package/dist/runner/aivision/cases.js.map +1 -0
  664. package/dist/runner/aivision/cli.js +201 -0
  665. package/dist/runner/aivision/cli.js.map +1 -0
  666. package/dist/runner/aivision/index.js +281 -0
  667. package/dist/runner/aivision/index.js.map +1 -0
  668. package/dist/runner/aivision/server.js +394 -0
  669. package/dist/runner/aivision/server.js.map +1 -0
  670. package/dist/runner/aivision/shared.js +140 -0
  671. package/dist/runner/aivision/shared.js.map +1 -0
  672. package/dist/runner/artifact-cap.js +189 -0
  673. package/dist/runner/artifact-cap.js.map +1 -0
  674. package/dist/runner/artifacts.js +433 -0
  675. package/dist/runner/artifacts.js.map +1 -0
  676. package/dist/runner/command.js +564 -0
  677. package/dist/runner/command.js.map +1 -0
  678. package/dist/runner/copyfiles.js +221 -0
  679. package/dist/runner/copyfiles.js.map +1 -0
  680. package/dist/runner/engines.js +32 -0
  681. package/dist/runner/engines.js.map +1 -0
  682. package/dist/runner/evidence.js +114 -0
  683. package/dist/runner/evidence.js.map +1 -0
  684. package/dist/runner/host-env.js +160 -0
  685. package/dist/runner/host-env.js.map +1 -0
  686. package/dist/runner/index.js +1221 -0
  687. package/dist/runner/index.js.map +1 -0
  688. package/dist/runner/observe.js +603 -0
  689. package/dist/runner/observe.js.map +1 -0
  690. package/dist/runner/playwright-json.js +108 -0
  691. package/dist/runner/playwright-json.js.map +1 -0
  692. package/dist/runner/pwgen/exec.js +192 -0
  693. package/dist/runner/pwgen/exec.js.map +1 -0
  694. package/dist/runner/pwgen/explore.js +111 -0
  695. package/dist/runner/pwgen/explore.js.map +1 -0
  696. package/dist/runner/pwgen/index.js +423 -0
  697. package/dist/runner/pwgen/index.js.map +1 -0
  698. package/dist/runner/pwgen/propose.js +69 -0
  699. package/dist/runner/pwgen/propose.js.map +1 -0
  700. package/dist/runner/pwgen/source.js +632 -0
  701. package/dist/runner/pwgen/source.js.map +1 -0
  702. package/dist/runner/setup.js +222 -0
  703. package/dist/runner/setup.js.map +1 -0
  704. package/dist/runner/step-reporter.js +107 -0
  705. package/dist/runner/step-reporter.js.map +1 -0
  706. package/dist/runner/test-repo.js +326 -0
  707. package/dist/runner/test-repo.js.map +1 -0
  708. package/dist/runner/typed-value.js +46 -0
  709. package/dist/runner/typed-value.js.map +1 -0
  710. package/dist/runner/verify.js +133 -0
  711. package/dist/runner/verify.js.map +1 -0
  712. package/dist/runner/worktree.js +349 -0
  713. package/dist/runner/worktree.js.map +1 -0
  714. package/dist/sandbox.js +61 -0
  715. package/dist/sandbox.js.map +1 -0
  716. package/dist/server.js +262 -0
  717. package/dist/server.js.map +1 -0
  718. package/dist/shadow-week.js +126 -0
  719. package/dist/shadow-week.js.map +1 -0
  720. package/dist/shutdown.js +92 -0
  721. package/dist/shutdown.js.map +1 -0
  722. package/dist/slack/index.js +645 -0
  723. package/dist/slack/index.js.map +1 -0
  724. package/dist/stages/config.js +147 -0
  725. package/dist/stages/config.js.map +1 -0
  726. package/dist/stages/index.js +41 -0
  727. package/dist/stages/index.js.map +1 -0
  728. package/dist/suites.js +293 -0
  729. package/dist/suites.js.map +1 -0
  730. package/dist/target-url.js +116 -0
  731. package/dist/target-url.js.map +1 -0
  732. package/dist/test-runs.js +662 -0
  733. package/dist/test-runs.js.map +1 -0
  734. package/dist/ticket-keys.js +97 -0
  735. package/dist/ticket-keys.js.map +1 -0
  736. package/dist/ticket-supersede.js +105 -0
  737. package/dist/ticket-supersede.js.map +1 -0
  738. package/dist/tinybee/ed25519.js +99 -0
  739. package/dist/tinybee/ed25519.js.map +1 -0
  740. package/dist/tinybee/models-routes.js +19 -0
  741. package/dist/tinybee/models-routes.js.map +1 -0
  742. package/dist/tinybee/pair-payload.js +41 -0
  743. package/dist/tinybee/pair-payload.js.map +1 -0
  744. package/dist/tinybee/update-manifest.js +77 -0
  745. package/dist/tinybee/update-manifest.js.map +1 -0
  746. package/dist/tinybee/well-known.js +64 -0
  747. package/dist/tinybee/well-known.js.map +1 -0
  748. package/dist/tinybee/workflows/activity.js +23 -0
  749. package/dist/tinybee/workflows/activity.js.map +1 -0
  750. package/dist/tinybee/workflows/cron.js +210 -0
  751. package/dist/tinybee/workflows/cron.js.map +1 -0
  752. package/dist/tinybee/workflows/engine.js +371 -0
  753. package/dist/tinybee/workflows/engine.js.map +1 -0
  754. package/dist/tinybee/workflows/from-text.js +203 -0
  755. package/dist/tinybee/workflows/from-text.js.map +1 -0
  756. package/dist/tinybee/workflows/routes.js +189 -0
  757. package/dist/tinybee/workflows/routes.js.map +1 -0
  758. package/dist/tinybee/workflows/scheduler.js +100 -0
  759. package/dist/tinybee/workflows/scheduler.js.map +1 -0
  760. package/dist/tinybee/workflows/skills.js +227 -0
  761. package/dist/tinybee/workflows/skills.js.map +1 -0
  762. package/dist/tinybee/workflows/store.js +205 -0
  763. package/dist/tinybee/workflows/store.js.map +1 -0
  764. package/dist/tinybee/workflows/types.js +180 -0
  765. package/dist/tinybee/workflows/types.js.map +1 -0
  766. package/dist/tinybee/workflows/yaml.js +323 -0
  767. package/dist/tinybee/workflows/yaml.js.map +1 -0
  768. package/dist/true/api.js +495 -0
  769. package/dist/true/api.js.map +1 -0
  770. package/dist/true/client.js +292 -0
  771. package/dist/true/client.js.map +1 -0
  772. package/dist/true/clock.js +36 -0
  773. package/dist/true/clock.js.map +1 -0
  774. package/dist/true/events.js +125 -0
  775. package/dist/true/events.js.map +1 -0
  776. package/dist/true/hosts.js +98 -0
  777. package/dist/true/hosts.js.map +1 -0
  778. package/dist/true/index.js +29 -0
  779. package/dist/true/index.js.map +1 -0
  780. package/dist/true/key.js +54 -0
  781. package/dist/true/key.js.map +1 -0
  782. package/dist/true/line.js +69 -0
  783. package/dist/true/line.js.map +1 -0
  784. package/dist/true/mask.js +31 -0
  785. package/dist/true/mask.js.map +1 -0
  786. package/dist/true/names.js +114 -0
  787. package/dist/true/names.js.map +1 -0
  788. package/dist/true/outbox.js +233 -0
  789. package/dist/true/outbox.js.map +1 -0
  790. package/dist/true/replay.js +164 -0
  791. package/dist/true/replay.js.map +1 -0
  792. package/dist/true/settings.js +123 -0
  793. package/dist/true/settings.js.map +1 -0
  794. package/dist/true/status.js +189 -0
  795. package/dist/true/status.js.map +1 -0
  796. package/dist/true/sync-cases.js +222 -0
  797. package/dist/true/sync-cases.js.map +1 -0
  798. package/dist/true/sync-manual.js +295 -0
  799. package/dist/true/sync-manual.js.map +1 -0
  800. package/dist/true/sync-runs.js +109 -0
  801. package/dist/true/sync-runs.js.map +1 -0
  802. package/dist/true/worker.js +414 -0
  803. package/dist/true/worker.js.map +1 -0
  804. package/dist/types.js +32 -0
  805. package/dist/types.js.map +1 -0
  806. package/dist/updates/asks.js +38 -0
  807. package/dist/updates/asks.js.map +1 -0
  808. package/dist/updates/index.js +835 -0
  809. package/dist/updates/index.js.map +1 -0
  810. package/dist/updates/owner.js +24 -0
  811. package/dist/updates/owner.js.map +1 -0
  812. package/dist/updates/prefs.js +118 -0
  813. package/dist/updates/prefs.js.map +1 -0
  814. package/dist/updates/route.js +78 -0
  815. package/dist/updates/route.js.map +1 -0
  816. package/dist/updates/sources.js +345 -0
  817. package/dist/updates/sources.js.map +1 -0
  818. package/dist/updates/words.js +452 -0
  819. package/dist/updates/words.js.map +1 -0
  820. package/dist/version.js +53 -0
  821. package/dist/version.js.map +1 -0
  822. package/dist/view-compute.js +382 -0
  823. package/dist/view-compute.js.map +1 -0
  824. package/dist/view-spec.js +894 -0
  825. package/dist/view-spec.js.map +1 -0
  826. package/dist/web/apple-touch-icon.png +0 -0
  827. package/dist/web/assets/ApprovalCard-Br_z5z7G.css +1 -0
  828. package/dist/web/assets/ApprovalCard-CfGhGFo_.js +3 -0
  829. package/dist/web/assets/CardFromRef-BaTGmZ9c.js +2 -0
  830. package/dist/web/assets/CardFromRef-DO7i2uDd.css +1 -0
  831. package/dist/web/assets/Cases-8l_6UcLh.js +2 -0
  832. package/dist/web/assets/Cases-CpfED0uB.css +1 -0
  833. package/dist/web/assets/Chat-BKDiR0qt.js +3 -0
  834. package/dist/web/assets/Chat-DxjdyYUu.css +1 -0
  835. package/dist/web/assets/ConfirmDialog-CIMveD-b.js +2 -0
  836. package/dist/web/assets/ConnectSheet-Cm5vbZdR.js +4 -0
  837. package/dist/web/assets/Docs-HKGuY0WE.js +2 -0
  838. package/dist/web/assets/Docs-fodKx8S7.css +1 -0
  839. package/dist/web/assets/DotDetail-DskS3NWe.js +2 -0
  840. package/dist/web/assets/DotDetail-OWRSD_1x.css +1 -0
  841. package/dist/web/assets/DotStage-BZDclfzV.css +1 -0
  842. package/dist/web/assets/DotStage-CtMNzCN3.js +2 -0
  843. package/dist/web/assets/Dots-BqCsS34m.js +2 -0
  844. package/dist/web/assets/EvidencePack-DQ3kCJX4.js +2 -0
  845. package/dist/web/assets/EvidencePack-DV_9ETsA.css +1 -0
  846. package/dist/web/assets/FailedDeliveries-1F2kFcs9.js +2 -0
  847. package/dist/web/assets/FailedDeliveries-DXa2_VvI.css +1 -0
  848. package/dist/web/assets/Feed-5wiHRTFK.js +2 -0
  849. package/dist/web/assets/GfmView-C-NYaduC.js +4 -0
  850. package/dist/web/assets/ImpactList-C-RNuDB0.js +2 -0
  851. package/dist/web/assets/ImpactList-C612dFdw.css +1 -0
  852. package/dist/web/assets/Inbox-kc1bKVL_.js +2 -0
  853. package/dist/web/assets/JoinScreen-C3L4wmP3.js +2 -0
  854. package/dist/web/assets/JoinScreen-COeAUv5v.css +1 -0
  855. package/dist/web/assets/LeadDialog-N3Zul9Rw.js +8 -0
  856. package/dist/web/assets/LeadDialog-xyWjAuAS.css +1 -0
  857. package/dist/web/assets/ListeningLine-BRFNaczY.js +2 -0
  858. package/dist/web/assets/ListeningLine-pN8CgyiK.css +1 -0
  859. package/dist/web/assets/Mascots-pIyGpxtD.js +2 -0
  860. package/dist/web/assets/ModelChoices-BxO1zyyr.js +2 -0
  861. package/dist/web/assets/ModelChoices-DQJnVxfs.css +1 -0
  862. package/dist/web/assets/NewDotDialog-CDPUWmgN.js +2 -0
  863. package/dist/web/assets/NewDotDialog-DrPPqkSZ.css +1 -0
  864. package/dist/web/assets/NewTestRunDialog-BQBthVHE.js +2 -0
  865. package/dist/web/assets/NoBreakNames-Csxdqdt6.js +2 -0
  866. package/dist/web/assets/Pair-omex_K7H.js +2 -0
  867. package/dist/web/assets/PushSection-CmqHlE_2.js +2 -0
  868. package/dist/web/assets/ReleaseNotes-B6VqFewP.js +2 -0
  869. package/dist/web/assets/ReleaseNotes-DBJUL7Vm.css +1 -0
  870. package/dist/web/assets/ReportPage-BHS1xWEt.js +162 -0
  871. package/dist/web/assets/Reports-BEHnDIb1.js +2 -0
  872. package/dist/web/assets/ReviewChangeDialog-BVSt-cw-.js +2 -0
  873. package/dist/web/assets/ReviewChangeDialog-BbIlHRx0.css +1 -0
  874. package/dist/web/assets/Room-BoG_uge4.js +2 -0
  875. package/dist/web/assets/Room-evAfiW_e.css +1 -0
  876. package/dist/web/assets/RunActions-ZY7yf1AF.js +2 -0
  877. package/dist/web/assets/RunDetail-C8St20GQ.css +1 -0
  878. package/dist/web/assets/RunDetail-qADXjBvd.js +9 -0
  879. package/dist/web/assets/RunRow-BKEJv8ib.js +2 -0
  880. package/dist/web/assets/RunnerTry-Dgpp67-s.js +6 -0
  881. package/dist/web/assets/RunnerTry-DsweWOfO.css +1 -0
  882. package/dist/web/assets/Settings-fs-QJwNM.js +2 -0
  883. package/dist/web/assets/Setup-DI6hBEP-.js +2 -0
  884. package/dist/web/assets/TestRows-wh03T5Li.js +4 -0
  885. package/dist/web/assets/TestRun-DTOyF-Pv.js +2 -0
  886. package/dist/web/assets/TestRuns-ggDjm1Q8.js +2 -0
  887. package/dist/web/assets/TinyBeeAddLaptop-bc3pmh3T.js +2 -0
  888. package/dist/web/assets/TinyBeeAi-CkyOzhm5.js +2 -0
  889. package/dist/web/assets/TinyBeeConnections-B2K_P38u.js +2 -0
  890. package/dist/web/assets/TinyBeeHome-DSpxX5KS.js +3 -0
  891. package/dist/web/assets/TinyBeePlan-BamiLziu.js +2 -0
  892. package/dist/web/assets/TinyBeeSignup-vHcz0Wen.js +2 -0
  893. package/dist/web/assets/TinyBeeSkills-By9RLUse.js +2 -0
  894. package/dist/web/assets/TinyBeeWorkflow-DeNO7Jsh.js +2 -0
  895. package/dist/web/assets/TinyBeeWorkflowEditor-B6OGC2OT.js +2 -0
  896. package/dist/web/assets/TinyBeeWorkflows-BN4-h6lt.js +2 -0
  897. package/dist/web/assets/Trace-BBCI1aIB.css +1 -0
  898. package/dist/web/assets/Trace-aF92UWaH.js +2 -0
  899. package/dist/web/assets/UpdatesSection-CCWEbkue.js +2 -0
  900. package/dist/web/assets/Welcome-CCcy558Q.js +2 -0
  901. package/dist/web/assets/activity-C3qilrDm.css +1 -0
  902. package/dist/web/assets/activity-CBfTSxqw.js +2 -0
  903. package/dist/web/assets/authedMedia-BTYt_tyu.js +2 -0
  904. package/dist/web/assets/beeApi-BHW8Tx1s.js +2 -0
  905. package/dist/web/assets/data-CmXvx3Mj.js +2 -0
  906. package/dist/web/assets/engines-CfdJODFj.css +1 -0
  907. package/dist/web/assets/format-BBZ8vFyT.js +2 -0
  908. package/dist/web/assets/forms-Coc7p2UF.css +1 -0
  909. package/dist/web/assets/hivePush-DZJDokj7.js +2 -0
  910. package/dist/web/assets/impactModel-MY1OFuiP.js +2 -0
  911. package/dist/web/assets/index-B6qKm1NW.js +13 -0
  912. package/dist/web/assets/index-BvKyT1X7.css +1 -0
  913. package/dist/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  914. package/dist/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  915. package/dist/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  916. package/dist/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  917. package/dist/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  918. package/dist/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  919. package/dist/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  920. package/dist/web/assets/lead-BL_P_kri.css +1 -0
  921. package/dist/web/assets/model-BIQy5IVG.js +2 -0
  922. package/dist/web/assets/modelsApi-PUSySRQw.js +2 -0
  923. package/dist/web/assets/newDotModel-BaLLyw57.js +2 -0
  924. package/dist/web/assets/onboarding-bfKqnM4Z.css +1 -0
  925. package/dist/web/assets/phone-Rs7FoyG0.css +1 -0
  926. package/dist/web/assets/phoneApi-BG_1-M-D.js +3 -0
  927. package/dist/web/assets/releaseModel-EynYMVOC.js +2 -0
  928. package/dist/web/assets/releaseNotesModel-A1Rtv3cV.js +2 -0
  929. package/dist/web/assets/reports-BWaA1HhM.js +37 -0
  930. package/dist/web/assets/reports-DXwdWQsB.css +1 -0
  931. package/dist/web/assets/router-W6nZsQ33.js +2 -0
  932. package/dist/web/assets/runs-BY0Owv93.css +1 -0
  933. package/dist/web/assets/shadowWeekModel-BUUiww09.js +2 -0
  934. package/dist/web/assets/storage-yvL36AWt.js +2 -0
  935. package/dist/web/assets/store-IqrqHuFR.js +2 -0
  936. package/dist/web/assets/testruns-BzIZ0cR0.css +1 -0
  937. package/dist/web/assets/testruns-DyaqX4ji.js +2 -0
  938. package/dist/web/assets/true-9Q68cs5b.js +2 -0
  939. package/dist/web/assets/true-DPGBpoir.css +1 -0
  940. package/dist/web/assets/ui-DcmhYyYp.js +13 -0
  941. package/dist/web/assets/ui-DxEDgbIx.css +1 -0
  942. package/dist/web/assets/usePauseDot-BV60ZVy1.js +2 -0
  943. package/dist/web/assets/usePauseDot-NrSzMI1C.css +1 -0
  944. package/dist/web/assets/usePush-Dsm-TjlG.js +2 -0
  945. package/dist/web/assets/useRefChips-CbafNx59.js +68 -0
  946. package/dist/web/assets/useRestoreDot-D0B6W82H.js +2 -0
  947. package/dist/web/assets/view-worker-pyeVAhUT.js +2 -0
  948. package/dist/web/assets/workflowUi-QYsd3n9J.js +2 -0
  949. package/dist/web/favicon.png +0 -0
  950. package/dist/web/icon-192.png +0 -0
  951. package/dist/web/icon-512.png +0 -0
  952. package/dist/web/icon-maskable-512.png +0 -0
  953. package/dist/web/index.html +45 -0
  954. package/dist/web/manifest.webmanifest +23 -0
  955. package/dist/web/mascots/argus-attention.svg +7 -0
  956. package/dist/web/mascots/argus-ok.svg +7 -0
  957. package/dist/web/mascots/argus-outage.svg +7 -0
  958. package/dist/web/mascots/argus-running.svg +7 -0
  959. package/dist/web/mascots/bolt-attention.svg +7 -0
  960. package/dist/web/mascots/bolt-ok.svg +7 -0
  961. package/dist/web/mascots/bolt-outage.svg +7 -0
  962. package/dist/web/mascots/bolt-running.svg +7 -0
  963. package/dist/web/mascots/echo-attention.svg +7 -0
  964. package/dist/web/mascots/echo-ok.svg +7 -0
  965. package/dist/web/mascots/echo-outage.svg +7 -0
  966. package/dist/web/mascots/echo-running.svg +7 -0
  967. package/dist/web/mascots/lens-attention.svg +7 -0
  968. package/dist/web/mascots/lens-ok.svg +7 -0
  969. package/dist/web/mascots/lens-outage.svg +7 -0
  970. package/dist/web/mascots/lens-running.svg +7 -0
  971. package/dist/web/mascots/moss-attention.svg +7 -0
  972. package/dist/web/mascots/moss-ok.svg +7 -0
  973. package/dist/web/mascots/moss-outage.svg +7 -0
  974. package/dist/web/mascots/moss-running.svg +7 -0
  975. package/dist/web/mascots/nit-attention.svg +7 -0
  976. package/dist/web/mascots/nit-ok.svg +7 -0
  977. package/dist/web/mascots/nit-outage.svg +7 -0
  978. package/dist/web/mascots/nit-running.svg +7 -0
  979. package/dist/web/mascots/nova-attention.svg +7 -0
  980. package/dist/web/mascots/nova-ok.svg +7 -0
  981. package/dist/web/mascots/nova-outage.svg +7 -0
  982. package/dist/web/mascots/nova-running.svg +7 -0
  983. package/dist/web/mascots/penn-attention.svg +7 -0
  984. package/dist/web/mascots/penn-ok.svg +7 -0
  985. package/dist/web/mascots/penn-outage.svg +7 -0
  986. package/dist/web/mascots/penn-running.svg +7 -0
  987. package/dist/web/mascots/pip-attention.svg +7 -0
  988. package/dist/web/mascots/pip-ok.svg +7 -0
  989. package/dist/web/mascots/pip-outage.svg +7 -0
  990. package/dist/web/mascots/pip-running.svg +7 -0
  991. package/dist/web/mascots/ward-attention.svg +7 -0
  992. package/dist/web/mascots/ward-ok.svg +7 -0
  993. package/dist/web/mascots/ward-outage.svg +7 -0
  994. package/dist/web/mascots/ward-running.svg +7 -0
  995. package/dist/web/sw.js +98 -0
  996. package/dist/webhook-seen.js +53 -0
  997. package/dist/webhook-seen.js.map +1 -0
  998. package/dist/webhooks.js +625 -0
  999. package/dist/webhooks.js.map +1 -0
  1000. package/dist/zip.js +63 -0
  1001. package/dist/zip.js.map +1 -0
  1002. package/docs/API-V2.md +532 -0
  1003. package/docs/CHAT-EVAL.md +130 -0
  1004. package/docs/CHAT-UI.md +416 -0
  1005. package/docs/CONNECTIONS.md +89 -0
  1006. package/docs/DATA-MODEL.md +518 -0
  1007. package/docs/DEPLOY.md +577 -0
  1008. package/docs/DOGFOOD-LEDGER.md +81 -0
  1009. package/docs/GITHUB.md +148 -0
  1010. package/docs/INTEGRATION-MODEL.md +200 -0
  1011. package/docs/LEAD-DOT.md +553 -0
  1012. package/docs/LIST-PATTERNS.md +52 -0
  1013. package/docs/MULTI-REPO.md +766 -0
  1014. package/docs/PARITY.md +276 -0
  1015. package/docs/PR-REVIEW-QA.md +1266 -0
  1016. package/docs/PR-REVIEW.md +357 -0
  1017. package/docs/SANDBOX.md +320 -0
  1018. package/docs/SETUP.md +776 -0
  1019. package/docs/SLACK.md +80 -0
  1020. package/docs/STAGES.md +619 -0
  1021. package/docs/TRACE-MAP.md +89 -0
  1022. package/docs/TRACKERS.md +225 -0
  1023. package/docs/VOICE.md +36 -0
  1024. package/docs/tinybee/00-README.md +45 -0
  1025. package/docs/tinybee/01-PRD.md +216 -0
  1026. package/docs/tinybee/02-architecture-pairing.md +199 -0
  1027. package/docs/tinybee/03-local-agent.md +222 -0
  1028. package/docs/tinybee/04-local-setup-ui.md +128 -0
  1029. package/docs/tinybee/05-mobile-app-ux.md +199 -0
  1030. package/docs/tinybee/06-environments-and-delivery.md +148 -0
  1031. package/docs/tinybee/07-coding-agent-handoff.md +116 -0
  1032. package/docs/tinybee/OPEN-QUESTIONS.md +26 -0
  1033. package/docs/tinybee/PROGRESS.md +134 -0
  1034. package/package.json +111 -4
@@ -0,0 +1,1284 @@
1
+ import { runTracked } from '../proc.js';
2
+ import { httpError } from '../http-error.js';
3
+ import { targetUrlOf } from '../target-url.js';
4
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
5
+ import { cp, mkdir, readdir, readFile, stat } from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import { config, liveEnv, onSettingsChange } from '../config.js';
8
+ import { githubArtifactSource } from '../review/coverage.js';
9
+ import { carryTicks } from '../review/render.js';
10
+ import { withBotMarker } from './bot-marker.js';
11
+ import { forgetOwnId, rememberOwnId } from './own-identity.js';
12
+ import { isFullSha, isSafeRef } from '../runner/verify.js';
13
+ import { getWorkspaceRepo, parseRepoId, primaryRepo, repoId, targetRepo } from '../repos.js';
14
+ export const STICKY_MARKER = '<!-- localdost:pr-summary -->';
15
+ /** Matches the marker as written now and as QA Dots wrote it, so old sticky comments are still found and updated. */
16
+ export const STICKY_MARKER_RE = /<!-- (?:localdost|qadots):pr-summary -->/;
17
+ export function hasStickyMarker(body) {
18
+ return typeof body === 'string' && STICKY_MARKER_RE.test(body);
19
+ }
20
+ /** The label every defect the dot files carries. */
21
+ export const DEFECT_LABEL = 'localdost';
22
+ /** The label QA Dots put on defects. Still recognised, never applied. */
23
+ export const LEGACY_DEFECT_LABEL = 'qa-dots';
24
+ export const DEFECT_LABELS = [DEFECT_LABEL, LEGACY_DEFECT_LABEL];
25
+ export function isDefectLabel(name) {
26
+ return typeof name === 'string' && DEFECT_LABELS.includes(name.toLowerCase());
27
+ }
28
+ /** Largest job log jobLog returns, in bytes. A longer log is cut to this size. */
29
+ export const JOB_LOG_CAP = 5 * 1024 * 1024;
30
+ /** What the statuses of a deployment said at the look where it first succeeded, by repository and deployment id; later statuses are not read. Bounded: the oldest entry goes first. */
31
+ const succeededDeployments = new Map();
32
+ const SUCCEEDED_DEPLOYMENTS_MAX = 2000;
33
+ /** Forgets the deployments that were read as succeeded. Tests call it between cases. */
34
+ export function forgetSucceededDeployments() {
35
+ succeededDeployments.clear();
36
+ }
37
+ /**
38
+ * The service a deployment is for, as its own fields say: `payload.service` (or name, app, component, project), else a `task` other
39
+ * than GitHub's default "deploy", else the description. Lower case; undefined when none of them says. Two deployments of one commit
40
+ * with the same service are two attempts at one deployment, and with different services they are two deployments.
41
+ */
42
+ export function deploymentService(d) {
43
+ return serviceInfo(d)?.name;
44
+ }
45
+ /** True when the service of the deployment comes from its description only, which is a guess (see DeploymentInfo.serviceGuessed). */
46
+ export function deploymentServiceGuessed(d) {
47
+ return !!serviceInfo(d)?.guessed;
48
+ }
49
+ function serviceInfo(d) {
50
+ if (!d)
51
+ return undefined;
52
+ let payload = d.payload;
53
+ if (typeof payload === 'string') {
54
+ try {
55
+ payload = JSON.parse(payload);
56
+ }
57
+ catch {
58
+ payload = undefined;
59
+ }
60
+ }
61
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
62
+ for (const key of ['service', 'name', 'app', 'component', 'project']) {
63
+ const v = payload[key];
64
+ if (typeof v === 'string' && v.trim())
65
+ return { name: v.trim().toLowerCase().slice(0, 100), guessed: false };
66
+ }
67
+ }
68
+ if (typeof d.task === 'string' && d.task.trim() && d.task.trim().toLowerCase() !== 'deploy')
69
+ return { name: d.task.trim().toLowerCase().slice(0, 100), guessed: false };
70
+ if (typeof d.description === 'string') {
71
+ const name = serviceOfDescription(d.description);
72
+ return name ? { name, guessed: true } : undefined;
73
+ }
74
+ return undefined;
75
+ }
76
+ /**
77
+ * The service a description names (KAT-403). A pipeline often writes its run into the description ("Deploy web (run 8841)", "web, attempt 2"),
78
+ * and a retry then has another description, so each retry would look like a new service. What names a try and not a service is cut:
79
+ * run, attempt, build and job numbers, #numbers, long numbers, commit shas, uuids, and timestamps. A description with nothing else left names none.
80
+ */
81
+ export function serviceOfDescription(text) {
82
+ const t = text
83
+ .toLowerCase()
84
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g, ' ')
85
+ .replace(/\b\d{4}-\d{2}-\d{2}[t ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?z?\b/g, ' ')
86
+ .replace(/\b(?:run|attempt|try|retry|build|job|workflow|pipeline|deployment|deploy|release|execution|id)[\s_-]*(?:id|number|no|#)?[\s#:=_-]*\d+\b/g, ' ')
87
+ .replace(/#\d+\b/g, ' ')
88
+ .replace(/\b[0-9a-f]{7,40}\b/g, (m) => (/\d/.test(m) && /[a-f]/.test(m) ? ' ' : m))
89
+ .replace(/\b\d{5,}\b/g, ' ')
90
+ .replace(/\(\s*\)|\[\s*\]/g, ' ')
91
+ .replace(/\s+/g, ' ')
92
+ .replace(/^[\s,;:|/-]+|[\s,;:|/-]+$/g, '')
93
+ .trim();
94
+ return t ? t.slice(0, 100) : undefined;
95
+ }
96
+ export function pullSummary(p) {
97
+ const merged = Boolean(p.merged || p.merged_at);
98
+ return {
99
+ number: Number(p.number),
100
+ title: String(p.title ?? ''),
101
+ state: merged ? 'merged' : p.state === 'open' ? 'open' : 'closed',
102
+ headSha: String(p.head?.sha ?? ''),
103
+ ...(merged && p.merge_commit_sha ? { mergeSha: String(p.merge_commit_sha) } : {}),
104
+ ...(p.user?.login ? { author: String(p.user.login) } : {}),
105
+ ...(p.html_url ? { url: String(p.html_url) } : {}),
106
+ ...(p.updated_at ? { updatedAt: String(p.updated_at) } : {}),
107
+ };
108
+ }
109
+ // deployment_status previews, keyed by repo and sha
110
+ const previews = new Map();
111
+ const PREVIEW_CAP = 500;
112
+ /** Closed-PR pages (100 each) the review picker reads at most while it looks for merged PRs. */
113
+ const CLOSED_PULL_PAGES = 3;
114
+ function repoKey(repo) {
115
+ return (typeof repo === 'string' ? repo : `${repo.owner}/${repo.name}`).toLowerCase();
116
+ }
117
+ /**
118
+ * Keeps the preview address of a commit. The address comes from a deployment_status, which anyone who can write one chooses, so it is
119
+ * cleaned like the address of a deploy (targetUrlOf): https, or http on this machine, no sign-in, query or fragment, at most 300
120
+ * characters. Any other address is not kept.
121
+ */
122
+ export function rememberPreview(repo, sha, rawUrl) {
123
+ const url = targetUrlOf(rawUrl);
124
+ if (!url)
125
+ return;
126
+ const key = `${repoKey(repo)}@${sha}`;
127
+ previews.delete(key);
128
+ previews.set(key, url);
129
+ if (previews.size > PREVIEW_CAP) {
130
+ const oldest = previews.keys().next().value;
131
+ if (oldest !== undefined)
132
+ previews.delete(oldest);
133
+ }
134
+ }
135
+ export function previewFor(repo, sha) {
136
+ return previews.get(`${repoKey(repo)}@${sha}`);
137
+ }
138
+ const deploymentListeners = new Set();
139
+ /**
140
+ * Calls `fn` for every deployment_status the server reads that ends a deployment (a webhook delivery, or a simulated one): success,
141
+ * failure or error. A listener that throws is logged and never stops the delivery. Returns a function that removes the listener.
142
+ */
143
+ export function onDeploymentEnd(fn) {
144
+ deploymentListeners.add(fn);
145
+ return () => {
146
+ deploymentListeners.delete(fn);
147
+ };
148
+ }
149
+ export function normalizeGithub(eventName, payload, deliveryId, opts = {}) {
150
+ if (!payload || typeof payload !== 'object')
151
+ return null;
152
+ const r = payload.repository;
153
+ const repo = r ? { owner: String(r.owner?.login ?? r.owner?.name ?? ''), name: String(r.name ?? '') } : undefined;
154
+ const full = r?.full_name ?? (repo ? `${repo.owner}/${repo.name}` : 'unknown');
155
+ const key = (semantic) => (deliveryId ? `github:${deliveryId}` : `github:${semantic}`);
156
+ const extra = {};
157
+ if (Array.isArray(payload.inlineDiff))
158
+ extra.inlineDiff = payload.inlineDiff;
159
+ if (payload.inlineTicket)
160
+ extra.inlineTicket = payload.inlineTicket;
161
+ switch (eventName) {
162
+ case 'pull_request': {
163
+ const pr = payload.pull_request;
164
+ if (!pr)
165
+ return null;
166
+ const action = String(payload.action ?? '');
167
+ const base = {
168
+ number: Number(pr.number ?? payload.number),
169
+ headSha: String(pr.head?.sha ?? ''),
170
+ baseRef: String(pr.base?.ref ?? ''),
171
+ headRef: String(pr.head?.ref ?? ''),
172
+ author: pr.user?.login,
173
+ // A head repository that is gone (null: a deleted fork) is a fork too. The same-repo path runs code on this machine.
174
+ fork: Boolean(pr.head?.repo === null || (pr.head?.repo && r?.full_name && String(pr.head.repo.full_name).toLowerCase() !== String(r.full_name).toLowerCase())),
175
+ ...(pr.base?.sha ? { baseSha: String(pr.base.sha) } : {}),
176
+ ...(typeof pr.body === 'string' && pr.body ? { body: pr.body.slice(0, 20000) } : {}),
177
+ // What a dot's PR scope reads (KAT-354). Set only when true, so other events keep their shape.
178
+ ...(pr.draft === true ? { draft: true } : {}),
179
+ ...(pr.user?.type === 'Bot' || /\[bot\]$/.test(String(pr.user?.login ?? '')) ? { authorIsBot: true } : {}),
180
+ };
181
+ // ready_for_review: a draft left draft state, so the PR is now one to review (KAT-354). Its head did not change,
182
+ // so the dedupe key names the action, and a dot that already reviewed this head skips it (src/webhooks.ts).
183
+ if (action === 'opened' || action === 'synchronize' || action === 'reopened' || action === 'ready_for_review') {
184
+ return {
185
+ source: 'github',
186
+ kind: 'pr_opened',
187
+ dedupeKey: key(`${full}#${base.number}:${action}:${base.headSha}`),
188
+ title: `PR #${base.number}: ${pr.title ?? ''}`.trim(),
189
+ url: pr.html_url,
190
+ repo,
191
+ pr: action === 'ready_for_review' ? { ...base, readyForReview: true } : base,
192
+ ...extra,
193
+ };
194
+ }
195
+ if (action === 'closed' && pr.merged) {
196
+ const mergeSha = String(pr.merge_commit_sha ?? '');
197
+ return {
198
+ source: 'github',
199
+ kind: 'pr_merged',
200
+ dedupeKey: key(`${full}#${base.number}:merged:${mergeSha}`),
201
+ title: `Merged PR #${base.number}: ${pr.title ?? ''}`.trim(),
202
+ url: pr.html_url,
203
+ repo,
204
+ pr: { ...base, mergeSha },
205
+ ...extra,
206
+ };
207
+ }
208
+ return null;
209
+ }
210
+ case 'push': {
211
+ const defaultBranch = r?.default_branch ?? r?.master_branch;
212
+ const pushed = String(payload.ref ?? '').replace(/^refs\/heads\//, '');
213
+ if (!defaultBranch || !String(payload.ref ?? '').startsWith('refs/heads/'))
214
+ return null;
215
+ if (payload.ref !== `refs/heads/${defaultBranch}` && !(repo && opts.isTestBranch?.(repo, pushed)))
216
+ return null;
217
+ if (payload.deleted || /^0+$/.test(String(payload.after ?? '')))
218
+ return null;
219
+ const msg = String(payload.head_commit?.message ?? '').split('\n')[0] ?? '';
220
+ // GitHub lists added, modified and removed paths per commit, and only the first 20 commits.
221
+ const commits = Array.isArray(payload.commits) ? payload.commits : [];
222
+ const pushedFiles = new Set();
223
+ for (const c of commits)
224
+ for (const k of ['added', 'modified', 'removed'])
225
+ for (const f of Array.isArray(c?.[k]) ? c[k] : [])
226
+ pushedFiles.add(String(f));
227
+ const filesKnown = pushedFiles.size > 0 && commits.length < 20;
228
+ return {
229
+ source: 'github',
230
+ kind: 'push',
231
+ dedupeKey: key(`${full}:push:${payload.after}`),
232
+ title: `Push to ${pushed}${msg ? `: ${msg}` : ''}`,
233
+ url: payload.compare,
234
+ repo,
235
+ push: { before: String(payload.before ?? ''), after: String(payload.after ?? ''), ref: String(payload.ref) },
236
+ ...(filesKnown
237
+ ? { changedFiles: [...pushedFiles] }
238
+ : { pathFilterNote: commits.length >= 20 ? 'The push payload lists only the first 20 commits.' : 'The push payload lists no changed files.' }),
239
+ ...extra,
240
+ };
241
+ }
242
+ case 'workflow_run': {
243
+ const wr = payload.workflow_run;
244
+ if (!wr || payload.action !== 'completed')
245
+ return null;
246
+ if (wr.conclusion !== 'failure' && wr.conclusion !== 'timed_out')
247
+ return null;
248
+ return {
249
+ source: 'github',
250
+ kind: 'ci_failure',
251
+ dedupeKey: key(`${full}:ci:${wr.id}:${wr.run_attempt ?? 1}`),
252
+ title: `CI ${wr.conclusion === 'timed_out' ? 'timed out' : 'failed'}: ${wr.name ?? 'workflow'} on ${wr.head_branch ?? wr.head_sha}`,
253
+ url: wr.html_url,
254
+ repo,
255
+ ci: { runId: Number(wr.id), workflow: String(wr.name ?? ''), conclusion: String(wr.conclusion), headSha: String(wr.head_sha ?? '') },
256
+ ...extra,
257
+ };
258
+ }
259
+ case 'deployment_status': {
260
+ const ds = payload.deployment_status;
261
+ const sha = payload.deployment?.sha;
262
+ const url = ds?.environment_url || ds?.target_url;
263
+ if (ds?.state === 'success' && sha && url && repo)
264
+ rememberPreview(repo, sha, String(url));
265
+ // A merge run that waits for the deploy of its commit looks again when this arrives (src/deploy-wait.ts): a success may end the
266
+ // wait, and a failure or error ends it too.
267
+ if ((ds?.state === 'success' || ds?.state === 'failure' || ds?.state === 'error') && sha && repo) {
268
+ const environment = String(ds.environment ?? payload.deployment?.environment ?? '');
269
+ for (const fn of [...deploymentListeners]) {
270
+ try {
271
+ const dep = payload.deployment ?? {};
272
+ fn({
273
+ repo,
274
+ environment,
275
+ sha: String(sha),
276
+ state: ds.state,
277
+ ...(Number.isFinite(Number(dep.id)) && dep.id !== undefined && dep.id !== null ? { id: Number(dep.id) } : {}),
278
+ ...(dep.created_at ? { createdAt: String(dep.created_at) } : {}),
279
+ ...(ds.created_at ? { at: String(ds.created_at) } : {}),
280
+ ...(deploymentService(dep) ? { service: deploymentService(dep) } : {}),
281
+ ...(deploymentService(dep) && deploymentServiceGuessed(dep) ? { serviceGuessed: true } : {}),
282
+ });
283
+ }
284
+ catch (err) {
285
+ console.warn(`[github] a deployment listener failed: ${String(err)}`);
286
+ }
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+ default:
292
+ return null;
293
+ }
294
+ }
295
+ /** Edits of the localdost sticky comment on a pull request; null for anything else. */
296
+ export function normalizeCommentEdit(eventName, payload) {
297
+ if (eventName !== 'issue_comment' || !payload || typeof payload !== 'object')
298
+ return null;
299
+ if (payload.action !== 'edited')
300
+ return null;
301
+ const issue = payload.issue;
302
+ const comment = payload.comment;
303
+ const r = payload.repository;
304
+ if (!issue?.pull_request || !comment || !r)
305
+ return null;
306
+ const body = typeof comment.body === 'string' ? comment.body : '';
307
+ if (!hasStickyMarker(body))
308
+ return null;
309
+ const sender = payload.sender ?? {};
310
+ const login = String(sender.login ?? '');
311
+ return {
312
+ repo: { owner: String(r.owner?.login ?? r.owner?.name ?? ''), name: String(r.name ?? '') },
313
+ pr: Number(issue.number),
314
+ commentId: Number(comment.id ?? 0),
315
+ body,
316
+ sender: login,
317
+ senderIsBot: sender.type === 'Bot' || /\[bot\]$/.test(login),
318
+ association: String(comment.author_association ?? 'NONE').toUpperCase(),
319
+ };
320
+ }
321
+ // Client
322
+ export function githubMode() {
323
+ if (liveEnv.GITHUB_APP_ID && liveEnv.GITHUB_PRIVATE_KEY)
324
+ return 'app';
325
+ if (liveEnv.GITHUB_TOKEN)
326
+ return 'pat';
327
+ return 'demo';
328
+ }
329
+ let clientOverride;
330
+ /** Test seam: githubFor returns what `fn` returns, or its normal client when `fn` returns undefined. Pass undefined to reset. */
331
+ export function overrideGithubClient(fn) {
332
+ clientOverride = fn;
333
+ }
334
+ /**
335
+ * Client for the repo an event belongs to (docs/MULTI-REPO.md section 1.2): the event's repo when
336
+ * the dot holds it, else the dot's first app repo. `event` is optional; with none the client is for
337
+ * the first app repo. In demo mode the event's inlineDiff is what prFiles and compare return.
338
+ */
339
+ export function githubFor(watch, event) {
340
+ const fake = clientOverride?.(watch, event);
341
+ if (fake)
342
+ return fake;
343
+ const target = targetRepo(watch, event);
344
+ const mode = target ? githubMode() : 'demo';
345
+ if (mode === 'demo' || !target)
346
+ return demoClient(watch, event);
347
+ return liveClient(target, mode);
348
+ }
349
+ /**
350
+ * Client for the repo a defect is filed in. That is the tracker repo of a team dot, which can be none of the dot's own
351
+ * repos (`katalon-studio/product` holds no app code), so githubFor would answer with the first app repo instead (KAT-355).
352
+ * The test seam decides first; with no connection it answers like githubFor does, as the demo client.
353
+ */
354
+ export function githubForIssues(watch, project) {
355
+ const ref = parseRepoId(project);
356
+ const fake = clientOverride?.(watch, ref ? { repo: ref } : undefined);
357
+ if (fake)
358
+ return fake;
359
+ const mode = githubMode();
360
+ if (!ref || mode === 'demo')
361
+ return demoClient(watch, undefined);
362
+ const ws = getWorkspaceRepo(repoId(ref.owner, ref.name));
363
+ return liveClient({ owner: ws?.owner ?? ref.owner, name: ws?.name ?? ref.name, defaultBranch: ws?.defaultBranch ?? 'main' }, mode);
364
+ }
365
+ /** Client for a workspace repo with no dot (scans, profile reads, repo info). null when GitHub is not connected. */
366
+ export function githubForRepo(id, defaultBranch) {
367
+ const ref = parseRepoId(id);
368
+ const mode = githubMode();
369
+ if (!ref || mode === 'demo')
370
+ return null;
371
+ const ws = getWorkspaceRepo(repoId(ref.owner, ref.name));
372
+ return liveClient({ owner: ws?.owner ?? ref.owner, name: ws?.name ?? ref.name, defaultBranch: defaultBranch ?? ws?.defaultBranch ?? 'main' }, mode);
373
+ }
374
+ /** A git command ran past its time limit and was stopped. */
375
+ export class GitTimeoutError extends Error {
376
+ }
377
+ /** The connection cannot read the repo: it does not exist, the token has no access, or the App is not installed on it. */
378
+ export class RepoAccessError extends Error {
379
+ }
380
+ /**
381
+ * GET /repos/{owner}/{name} with the live credentials: GitHub's casing, default branch, and private flag.
382
+ * Throws RepoAccessError when the connection cannot read the repo, and a plain Error for anything
383
+ * else (not connected, network, rate limit).
384
+ */
385
+ export async function fetchRepoInfo(ref) {
386
+ const mode = githubMode();
387
+ if (mode === 'demo')
388
+ throw new Error('GitHub is not connected. Connect it on the Setup page first.');
389
+ const full = `${ref.owner}/${ref.name}`;
390
+ let tok;
391
+ try {
392
+ tok = mode === 'app' ? await installationToken(full) : liveEnv.GITHUB_TOKEN;
393
+ }
394
+ catch (err) {
395
+ const msg = err instanceof Error ? err.message : String(err);
396
+ throw /not installed/.test(msg) ? new RepoAccessError(`The GitHub App is not installed on ${full}.`) : err;
397
+ }
398
+ const res = await fetch(`${apiBase()}/repos/${full}`, { headers: headers(tok) });
399
+ if (res.status === 404 || res.status === 403)
400
+ throw new RepoAccessError(`${full} was not found, or the GitHub connection cannot read it.`);
401
+ if (!res.ok)
402
+ throw new Error(`GitHub GET /repos/${full} failed: HTTP ${res.status}`);
403
+ const r = (await res.json());
404
+ return { owner: r.owner?.login || ref.owner, name: r.name || ref.name, defaultBranch: r.default_branch || 'main', private: !!r.private };
405
+ }
406
+ export const apiBase = () => (liveEnv.GITHUB_API_URL ?? 'https://api.github.com').replace(/\/$/, '');
407
+ /** Token for git over HTTPS and API reads on one repo: the App installation token, or the PAT. Null without GitHub credentials. */
408
+ export async function repoGitToken(repo) {
409
+ const mode = githubMode();
410
+ if (mode === 'app')
411
+ return installationToken(`${repo.owner}/${repo.name}`);
412
+ if (mode === 'pat')
413
+ return liveEnv.GITHUB_TOKEN ?? null;
414
+ return null;
415
+ }
416
+ /** Bytes per language from GET /repos/{owner}/{repo}/languages, or null when unavailable. */
417
+ export async function repoLanguages(repo) {
418
+ try {
419
+ const tok = await repoGitToken(repo);
420
+ if (!tok)
421
+ return null;
422
+ const res = await fetch(`${apiBase()}/repos/${repo.owner}/${repo.name}/languages`, { headers: headers(tok) });
423
+ if (!res.ok)
424
+ return null;
425
+ return (await res.json());
426
+ }
427
+ catch {
428
+ return null;
429
+ }
430
+ }
431
+ const serverBase = () => (liveEnv.GITHUB_SERVER_URL ?? 'https://github.com').replace(/\/$/, '');
432
+ /** GitHub web base for file and commit links in comments (GitHub Enterprise sets GITHUB_SERVER_URL). */
433
+ export const githubWebUrl = serverBase;
434
+ function loadPrivateKey() {
435
+ const raw = liveEnv.GITHUB_PRIVATE_KEY ?? '';
436
+ if (!raw.includes('-----BEGIN') && existsSync(raw))
437
+ return readFileSync(raw, 'utf8');
438
+ return raw.replace(/\\n/g, '\n');
439
+ }
440
+ let appAuth = null;
441
+ const installationIds = new Map();
442
+ // Credentials saved in the app take effect without a restart: drop cached App auth and lookups.
443
+ onSettingsChange((keys) => {
444
+ if (!keys.some((k) => k.startsWith('GITHUB_')))
445
+ return;
446
+ appAuth = null;
447
+ installationIds.clear();
448
+ selfLogins.clear();
449
+ forgetOwnId('github');
450
+ });
451
+ async function getAppAuth() {
452
+ appAuth ??= (async () => {
453
+ const { createAppAuth } = await import('@octokit/auth-app');
454
+ return createAppAuth({ appId: liveEnv.GITHUB_APP_ID, privateKey: loadPrivateKey() });
455
+ })();
456
+ return appAuth;
457
+ }
458
+ /** Boot: loads the GitHub App auth module and checks the private key, so the first job does not pay for it. Returns the mode. */
459
+ export async function warmGithub() {
460
+ const mode = githubMode();
461
+ if (mode === 'app')
462
+ await getAppAuth();
463
+ return mode;
464
+ }
465
+ /** Installation token for the repo. @octokit/auth-app caches it until shortly before expiry. */
466
+ async function installationToken(full) {
467
+ const auth = await getAppAuth();
468
+ let id = installationIds.get(full);
469
+ if (!id) {
470
+ const { token: jwt } = await auth({ type: 'app' });
471
+ const res = await ghFetch(`${apiBase()}/repos/${full}/installation`, { headers: headers(jwt) });
472
+ if (!res.ok)
473
+ throw new Error(`GitHub App is not installed on ${full} (HTTP ${res.status})`);
474
+ id = (await res.json()).id;
475
+ installationIds.set(full, id);
476
+ }
477
+ return (await auth({ type: 'installation', installationId: id })).token;
478
+ }
479
+ /**
480
+ * GitHub could not be reached or answered with an outage-class status (5xx, 429, rate limit), so
481
+ * nothing is known about the thing being read. It is never an empty result: the flows stop the run
482
+ * as an environment failure and the job retries with its backoff.
483
+ */
484
+ export class GithubUnreachableError extends Error {
485
+ what;
486
+ constructor(message, what = 'api', options) {
487
+ super(message, options);
488
+ this.what = what;
489
+ this.name = 'GithubUnreachableError';
490
+ }
491
+ }
492
+ /**
493
+ * The real reason behind a failed fetch. Node's fetch throws `TypeError: fetch failed` and keeps the
494
+ * reason in `cause`: a system error with a code (ENOTFOUND, ETIMEDOUT, ECONNRESET), a TLS error, or an
495
+ * AggregateError of several addresses. Returns e.g. `fetch failed (ECONNRESET: read ECONNRESET)`.
496
+ */
497
+ export function describeFetchError(err) {
498
+ if (!(err instanceof Error))
499
+ return String(err);
500
+ const parts = [];
501
+ const seen = new Set();
502
+ const walk = (e, depth) => {
503
+ if (!(e instanceof Error) || seen.has(e) || depth > 4)
504
+ return;
505
+ seen.add(e);
506
+ const x = e;
507
+ const code = typeof x.code === 'string' ? x.code : '';
508
+ const msg = x.message && x.message !== err.message ? x.message : '';
509
+ if (depth > 0 || code)
510
+ parts.push(code && msg && !msg.includes(code) ? `${code}: ${msg}` : code || msg);
511
+ if (x.cause)
512
+ walk(x.cause, depth + 1);
513
+ for (const inner of x.errors ?? [])
514
+ walk(inner, depth + 1);
515
+ };
516
+ walk(err, 0);
517
+ const why = [...new Set(parts.filter(Boolean))].join('; ');
518
+ return why ? `${err.message} (${why})` : err.message;
519
+ }
520
+ /** fetch for GitHub calls: a network failure becomes a GithubUnreachableError that carries the real cause and is logged with it. */
521
+ async function ghFetch(url, init) {
522
+ try {
523
+ return await fetch(url, init);
524
+ }
525
+ catch (err) {
526
+ if (err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError'))
527
+ throw err;
528
+ throw unreachable(`${init?.method ?? 'GET'} ${url.replace(apiBase(), '').split('?')[0]}`, err);
529
+ }
530
+ }
531
+ function unreachable(what, err, kind = 'api') {
532
+ const why = describeFetchError(err);
533
+ console.warn(`[github] could not reach GitHub for ${what}: ${why}`);
534
+ return new GithubUnreachableError(`Could not reach GitHub (${what}): ${why}`, kind, { cause: err });
535
+ }
536
+ /** A status that says GitHub could not serve the read right now: server errors, 429, and a spent rate limit. */
537
+ function isOutageStatus(res) {
538
+ if (res.status >= 500 || res.status === 429)
539
+ return true;
540
+ return res.status === 403 && (res.headers.get('x-ratelimit-remaining') === '0' || res.headers.has('retry-after'));
541
+ }
542
+ export function headers(token, accept = 'application/vnd.github+json') {
543
+ return {
544
+ Authorization: `Bearer ${token}`,
545
+ Accept: accept,
546
+ 'X-GitHub-Api-Version': '2022-11-28',
547
+ 'User-Agent': 'localdost',
548
+ };
549
+ }
550
+ const truncate = (s, n) => (s.length > n ? s.slice(0, n - 20) + '\n\n(truncated)' : s);
551
+ function mapFile(f) {
552
+ const status = f.status === 'added' || f.status === 'removed' || f.status === 'renamed' ? f.status : 'modified';
553
+ const out = { path: String(f.filename), status, additions: Number(f.additions ?? 0), deletions: Number(f.deletions ?? 0) };
554
+ if (typeof f.patch === 'string')
555
+ out.patch = f.patch;
556
+ return out;
557
+ }
558
+ const selfLogins = new Map();
559
+ /** A token for `owner/name`: the PAT, or an App installation token. Throws when GitHub is not connected. */
560
+ export async function githubTokenFor(full) {
561
+ const mode = githubMode();
562
+ if (mode === 'app')
563
+ return installationToken(full);
564
+ if (mode === 'pat')
565
+ return liveEnv.GITHUB_TOKEN;
566
+ throw new Error('GitHub is not connected: set GITHUB_TOKEN or a GitHub App in Setup');
567
+ }
568
+ /** Login of the user a PAT belongs to, read once per token. Lowercase; null when GET /user fails. */
569
+ export async function selfLogin(tok) {
570
+ if (selfLogins.has(tok))
571
+ return selfLogins.get(tok) ?? null;
572
+ let login = null;
573
+ try {
574
+ const res = await fetch(`${apiBase()}/user`, { headers: headers(tok) });
575
+ if (res.ok)
576
+ login = String((await res.json()).login ?? '').toLowerCase() || null;
577
+ }
578
+ catch {
579
+ /* unknown: the caller falls back to the first marked comment */
580
+ }
581
+ if (login) {
582
+ selfLogins.set(tok, login);
583
+ // The PAT of the dot is the dot's own identity; other tokens passed in are not.
584
+ if (tok === liveEnv.GITHUB_TOKEN)
585
+ rememberOwnId('github', login);
586
+ }
587
+ return login;
588
+ }
589
+ function liveClient(repo, mode) {
590
+ const full = `${repo.owner}/${repo.name}`;
591
+ const token = async () => {
592
+ if (mode !== 'app')
593
+ return liveEnv.GITHUB_TOKEN;
594
+ try {
595
+ return await installationToken(full);
596
+ }
597
+ catch (err) {
598
+ // The App auth library calls fetch itself: its network failure surfaces as a bare "fetch failed".
599
+ throw err instanceof TypeError ? unreachable('installation token', err) : err;
600
+ }
601
+ };
602
+ /** Runs a read whose failure to reach GitHub must stop the run, and tags the error with what was being read. */
603
+ async function reading(kind, fn) {
604
+ try {
605
+ return await fn();
606
+ }
607
+ catch (err) {
608
+ if (err instanceof GithubUnreachableError)
609
+ err.what = kind;
610
+ throw err;
611
+ }
612
+ }
613
+ async function call(method, p, body, accept) {
614
+ const res = await ghFetch(`${apiBase()}${p}`, {
615
+ method,
616
+ headers: { ...headers(await token(), accept), ...(body ? { 'Content-Type': 'application/json' } : {}) },
617
+ body: body ? JSON.stringify(body) : undefined,
618
+ });
619
+ if (!res.ok) {
620
+ const text = await res.text().catch(() => '');
621
+ if (isOutageStatus(res))
622
+ throw new GithubUnreachableError(`Could not reach GitHub (${method} ${p.split('?')[0]}): HTTP ${res.status} ${text.slice(0, 200)}`.trim());
623
+ throw httpError(`GitHub ${method} ${p.split('?')[0]} failed: HTTP ${res.status} ${text.slice(0, 300)}`, res);
624
+ }
625
+ if (res.status === 204)
626
+ return undefined;
627
+ const type = res.headers.get('content-type') ?? '';
628
+ return (type.includes('json') ? await res.json() : await res.text());
629
+ }
630
+ async function paginate(p, maxPages) {
631
+ const out = [];
632
+ const sep = p.includes('?') ? '&' : '?';
633
+ for (let page = 1; page <= maxPages; page++) {
634
+ const batch = await call('GET', `${p}${sep}per_page=100&page=${page}`);
635
+ out.push(...batch);
636
+ if (batch.length < 100)
637
+ break;
638
+ }
639
+ return out;
640
+ }
641
+ /** Creates a check run (no id) or patches one (id). Returns the check run id in App mode. */
642
+ async function writeCheck(id, sha, r) {
643
+ const name = r.name ?? 'localdost';
644
+ if (mode === 'pat') {
645
+ // Check runs are App-only; a PAT gets a commit status instead. A later status with the same context replaces the earlier one.
646
+ // Pending: still running, a report that waits for a person, or a commit nobody reviewed. Failure: only a real failure.
647
+ // A check closed without a verdict (cancelled, neutral, skipped) is a success style status, so it never blocks a merge rule.
648
+ // A run that ended without a result is an error: a commit status has no neutral, and green would hide it (KAT-290).
649
+ const state = r.runFailed
650
+ ? 'error'
651
+ : !r.conclusion || r.superseded || r.conclusion === 'action_required'
652
+ ? 'pending'
653
+ : r.conclusion === 'failure' || r.conclusion === 'timed_out'
654
+ ? 'failure'
655
+ : 'success';
656
+ await call('POST', `/repos/${full}/statuses/${sha}`, {
657
+ state,
658
+ context: name,
659
+ description: (r.statusDescription ?? r.title).slice(0, 140),
660
+ ...(r.detailsUrl ? { target_url: r.detailsUrl } : {}),
661
+ });
662
+ return undefined;
663
+ }
664
+ const annotations = (r.annotations ?? []).map((a) => ({
665
+ path: a.path,
666
+ start_line: a.startLine,
667
+ end_line: a.endLine ?? a.startLine,
668
+ annotation_level: a.level,
669
+ message: a.message.slice(0, 64000),
670
+ ...(a.title ? { title: a.title.slice(0, 255) } : {}),
671
+ }));
672
+ const output = (batch) => ({
673
+ title: r.title.slice(0, 255),
674
+ summary: truncate(r.summary, 65000),
675
+ ...(r.text ? { text: truncate(r.text, 65000) } : {}),
676
+ annotations: batch,
677
+ });
678
+ const completed = Boolean(r.conclusion);
679
+ const fields = {
680
+ status: completed ? 'completed' : (r.status ?? 'in_progress'),
681
+ ...(completed ? { conclusion: r.conclusion, completed_at: new Date().toISOString() } : {}),
682
+ ...(r.detailsUrl ? { details_url: r.detailsUrl } : {}),
683
+ output: output(annotations.slice(0, 50)),
684
+ };
685
+ let checkId = id;
686
+ if (checkId === undefined)
687
+ checkId = (await call('POST', `/repos/${full}/check-runs`, { name, head_sha: sha, ...fields })).id;
688
+ else
689
+ await call('PATCH', `/repos/${full}/check-runs/${checkId}`, { name, ...fields });
690
+ // GitHub takes at most 50 annotations per request; the rest go in follow-up PATCHes.
691
+ for (let i = 50; i < annotations.length; i += 50) {
692
+ await call('PATCH', `/repos/${full}/check-runs/${checkId}`, { output: output(annotations.slice(i, i + 50)) });
693
+ }
694
+ return checkId;
695
+ }
696
+ /**
697
+ * Our sticky comment: it carries the marker and localdost wrote it. Anyone who can comment can paste
698
+ * the marker. Without the author check that comment would be read for ticks and patched, and the
699
+ * PATCH of someone else's comment fails, so the real comment would never be posted.
700
+ */
701
+ async function findSticky(pr) {
702
+ const comments = await paginate(`/repos/${full}/issues/${pr}/comments`, 10);
703
+ const marked = comments.filter((c) => hasStickyMarker(c.body));
704
+ if (!marked.length)
705
+ return undefined;
706
+ const mine = await ownCommentTest();
707
+ return mine ? marked.find(mine) : marked[0];
708
+ }
709
+ /** A test for comments this client wrote; null when the identity cannot be read (then the first marked comment counts). */
710
+ async function ownCommentTest() {
711
+ if (mode === 'app') {
712
+ const appId = Number(liveEnv.GITHUB_APP_ID);
713
+ return (c) => c.user?.type === 'Bot' && (!appId || Number(c.performed_via_github_app?.id ?? appId) === appId);
714
+ }
715
+ const login = await selfLogin(await token());
716
+ return login ? (c) => String(c.user?.login ?? '').toLowerCase() === login : null;
717
+ }
718
+ return {
719
+ mode,
720
+ // A failed page is an error, never a short list: the file list is whole or the read throws.
721
+ async prFiles(pr) {
722
+ return reading('diff', async () => (await paginate(`/repos/${full}/pulls/${pr}/files`, 30)).map(mapFile));
723
+ },
724
+ async compare(base, head) {
725
+ return reading('diff', async () => {
726
+ const data = await call('GET', `/repos/${full}/compare/${base}...${head}`);
727
+ return (data.files ?? []).map(mapFile);
728
+ });
729
+ },
730
+ async upsertStickyComment(pr, body) {
731
+ const existing = await findSticky(pr);
732
+ // Ticks a tester made since the flow read the comment are carried into the new body.
733
+ const merged = existing ? carryTicks(body, String(existing.body)) : body;
734
+ const text = truncate(hasStickyMarker(merged) ? merged.replace(STICKY_MARKER_RE, STICKY_MARKER) : `${STICKY_MARKER}\n${merged}`, 65000);
735
+ if (existing)
736
+ await call('PATCH', `/repos/${full}/issues/comments/${existing.id}`, { body: text });
737
+ else
738
+ await call('POST', `/repos/${full}/issues/${pr}/comments`, { body: text });
739
+ },
740
+ async getStickyComment(pr) {
741
+ const existing = await findSticky(pr);
742
+ return existing ? String(existing.body) : null;
743
+ },
744
+ async readFile(p, ref) {
745
+ const res = await ghFetch(`${apiBase()}/repos/${full}/contents/${p.split('/').map(encodeURIComponent).join('/')}?ref=${encodeURIComponent(ref)}`, {
746
+ headers: headers(await token(), 'application/vnd.github.raw+json'),
747
+ });
748
+ if (res.status === 404)
749
+ return null;
750
+ if (isOutageStatus(res))
751
+ throw new GithubUnreachableError(`Could not reach GitHub (contents ${p}): HTTP ${res.status}`);
752
+ if (!res.ok)
753
+ throw new Error(`GitHub contents ${p} failed: HTTP ${res.status}`);
754
+ return res.text();
755
+ },
756
+ async addComment(issue, body) {
757
+ await call('POST', `/repos/${full}/issues/${issue}/comments`, { body: withBotMarker(truncate(body, 64000)) });
758
+ },
759
+ async listOpenDefects() {
760
+ // GitHub ANDs a comma list of labels, so the current and the legacy label are two reads.
761
+ const pages = await Promise.all(DEFECT_LABELS.map((label) => call('GET', `/repos/${full}/issues?state=open&labels=${label}&sort=updated&direction=desc&per_page=100`)));
762
+ const seen = new Set();
763
+ const rows = pages.flat().filter((i) => (seen.has(Number(i.number)) ? false : (seen.add(Number(i.number)), true)));
764
+ return rows
765
+ .filter((i) => !i.pull_request)
766
+ .map((i) => ({
767
+ key: `${full}#${i.number}`,
768
+ title: String(i.title ?? ''),
769
+ body: String(i.body ?? ''),
770
+ ...(i.html_url ? { url: String(i.html_url) } : {}),
771
+ labels: (i.labels ?? []).map((l) => String(typeof l === 'string' ? l : l.name)),
772
+ }));
773
+ },
774
+ async checkRunsFor(sha) {
775
+ const out = [];
776
+ for (let page = 1; page <= 3; page++) {
777
+ const data = await call('GET', `/repos/${full}/commits/${encodeURIComponent(sha)}/check-runs?per_page=100&page=${page}`);
778
+ const runs = (data.check_runs ?? []);
779
+ for (const r of runs)
780
+ out.push({
781
+ id: Number(r.id),
782
+ name: String(r.name ?? ''),
783
+ status: String(r.status ?? ''),
784
+ conclusion: r.conclusion ? String(r.conclusion) : null,
785
+ ...(r.html_url ? { htmlUrl: String(r.html_url) } : {}),
786
+ headSha: String(r.head_sha ?? ''),
787
+ ...(r.app?.slug ? { app: String(r.app.slug) } : {}),
788
+ });
789
+ if (runs.length < 100)
790
+ break;
791
+ }
792
+ return out;
793
+ },
794
+ async deployments({ environment, sha, limit = 5 }) {
795
+ const q = new URLSearchParams({ environment, per_page: String(Math.min(Math.max(1, limit), 30)) });
796
+ if (sha)
797
+ q.set('sha', sha);
798
+ const rows = await call('GET', `/repos/${full}/deployments?${q}`);
799
+ const out = [];
800
+ for (const d of rows.slice(0, limit)) {
801
+ const id = Number(d.id);
802
+ // A deployment that succeeded keeps its success for good, so a later look reads it from memory (KAT-395): a push that deploys
803
+ // many services would otherwise cost one statuses call for each of them on every look of every waiting merge.
804
+ const key = `${full.toLowerCase()}#${id}`;
805
+ let seen = succeededDeployments.get(key);
806
+ if (!seen) {
807
+ const statuses = await call('GET', `/repos/${full}/deployments/${id}/statuses?per_page=30`);
808
+ const newest = statuses[0];
809
+ seen = {
810
+ state: String(newest?.state ?? 'pending'),
811
+ succeeded: statuses.some((st) => st.state === 'success'),
812
+ ...(newest?.created_at ? { at: String(newest.created_at) } : {}),
813
+ ...(newest?.environment_url ? { url: String(newest.environment_url) } : {}),
814
+ };
815
+ if (seen.succeeded) {
816
+ succeededDeployments.set(key, seen);
817
+ if (succeededDeployments.size > SUCCEEDED_DEPLOYMENTS_MAX)
818
+ succeededDeployments.delete(succeededDeployments.keys().next().value);
819
+ }
820
+ }
821
+ const service = deploymentService(d);
822
+ out.push({
823
+ id,
824
+ sha: String(d.sha ?? ''),
825
+ environment: String(d.environment ?? environment),
826
+ ...seen,
827
+ ...(d.created_at ? { createdAt: String(d.created_at) } : {}),
828
+ ...(service ? { service } : {}),
829
+ ...(service && deploymentServiceGuessed(d) ? { serviceGuessed: true } : {}),
830
+ });
831
+ }
832
+ return out;
833
+ },
834
+ async pullReviews(pr) {
835
+ const rows = await paginate(`/repos/${full}/pulls/${pr}/reviews`, 3);
836
+ const states = ['APPROVED', 'CHANGES_REQUESTED', 'COMMENTED', 'DISMISSED', 'PENDING'];
837
+ return rows.map((r) => ({
838
+ user: String(r.user?.login ?? ''),
839
+ state: (states.includes(String(r.state).toUpperCase()) ? String(r.state).toUpperCase() : 'COMMENTED'),
840
+ ...(r.submitted_at ? { submittedAt: String(r.submitted_at) } : {}),
841
+ }));
842
+ },
843
+ async commitStatuses(sha) {
844
+ const data = await call('GET', `/repos/${full}/commits/${encodeURIComponent(sha)}/status?per_page=100`);
845
+ return (data.statuses ?? []).map((st) => ({
846
+ context: String(st.context ?? ''),
847
+ state: (['success', 'failure', 'error', 'pending'].includes(String(st.state)) ? String(st.state) : 'pending'),
848
+ ...(st.target_url ? { url: String(st.target_url) } : {}),
849
+ }));
850
+ },
851
+ async jobLog(jobId) {
852
+ // Redirects to a signed URL; fetch drops the Authorization header on the cross-origin hop.
853
+ const res = await ghFetch(`${apiBase()}/repos/${full}/actions/jobs/${jobId}/logs`, { headers: headers(await token()) });
854
+ if (!res.ok)
855
+ throw new Error(`GitHub GET /repos/${full}/actions/jobs/${jobId}/logs failed: HTTP ${res.status}`);
856
+ return readCapped(res, JOB_LOG_CAP);
857
+ },
858
+ async renderMarkdown(text) {
859
+ const res = await ghFetch(`${apiBase()}/markdown`, {
860
+ method: 'POST',
861
+ headers: { ...headers(await token()), 'Content-Type': 'application/json' },
862
+ body: JSON.stringify({ text, mode: 'gfm', context: full }),
863
+ });
864
+ if (!res.ok)
865
+ throw new Error(`GitHub POST /markdown failed: HTTP ${res.status}`);
866
+ return res.text();
867
+ },
868
+ async rawDiff(target) {
869
+ const p = 'pr' in target ? `/repos/${full}/pulls/${target.pr}` : `/repos/${full}/compare/${target.base}...${target.head}`;
870
+ const res = await ghFetch(`${apiBase()}${p}`, { headers: headers(await token(), 'application/vnd.github.diff') });
871
+ if (!res.ok)
872
+ return null;
873
+ return res.text();
874
+ },
875
+ artifactSource() {
876
+ return {
877
+ async coverageArtifacts(sha) {
878
+ return githubArtifactSource({ owner: repo.owner, repo: repo.name, token: await token(), apiBase: apiBase() }).coverageArtifacts(sha);
879
+ },
880
+ };
881
+ },
882
+ async teamMembers(slug) {
883
+ const [org, team] = slug.split('/');
884
+ if (!org || !team)
885
+ throw new Error(`"${slug}" is not a team slug like org/team`);
886
+ const rows = await paginate(`/orgs/${encodeURIComponent(org)}/teams/${encodeURIComponent(team)}/members`, 10);
887
+ return rows.map((m) => String(m.login ?? '').toLowerCase()).filter(Boolean);
888
+ },
889
+ async pullsOfCommit(sha) {
890
+ if (!/^[0-9a-f]{7,64}$/i.test(sha))
891
+ return [];
892
+ return call('GET', `/repos/${full}/commits/${sha}/pulls?per_page=10`);
893
+ },
894
+ async writePermission(login) {
895
+ if (!login)
896
+ return false;
897
+ const res = await ghFetch(`${apiBase()}/repos/${full}/collaborators/${encodeURIComponent(login)}/permission`, { headers: headers(await token()) });
898
+ if (res.status === 404)
899
+ return false;
900
+ if (!res.ok)
901
+ return null;
902
+ const perm = String((await res.json()).permission ?? '');
903
+ return perm === 'admin' || perm === 'maintain' || perm === 'write';
904
+ },
905
+ async getPull(pr) {
906
+ const res = await ghFetch(`${apiBase()}/repos/${full}/pulls/${pr}`, { headers: headers(await token()) });
907
+ if (res.status === 404)
908
+ return null;
909
+ if (!res.ok)
910
+ throw new Error(`GitHub GET /repos/${full}/pulls/${pr} failed: HTTP ${res.status}`);
911
+ const payload = (await res.json());
912
+ return { summary: pullSummary(payload), payload };
913
+ },
914
+ async listPulls(limit = 20) {
915
+ const n = Math.max(1, Math.min(50, limit));
916
+ // Open PRs, then recently closed ones of which only the merged count. Closed PRs come a full
917
+ // page at a time, and the next page is read while the merged ones are short of the limit, so a
918
+ // run of PRs closed without a merge does not push the merged ones out. Pages stop at
919
+ // CLOSED_PULL_PAGES to bound GitHub calls per dialog open; an older PR is reviewed by number.
920
+ const closedPage = (page) => call('GET', `/repos/${full}/pulls?state=closed&sort=updated&direction=desc&per_page=100&page=${page}`);
921
+ const [open, firstClosed] = await Promise.all([
922
+ call('GET', `/repos/${full}/pulls?state=open&sort=updated&direction=desc&per_page=${n}`),
923
+ closedPage(1),
924
+ ]);
925
+ const merged = firstClosed.filter((p) => p.merged_at);
926
+ let last = firstClosed;
927
+ for (let page = 2; merged.length < n && last.length === 100 && page <= CLOSED_PULL_PAGES; page++) {
928
+ last = await closedPage(page);
929
+ merged.push(...last.filter((p) => p.merged_at));
930
+ }
931
+ merged.splice(n);
932
+ return [...open, ...merged]
933
+ .map(pullSummary)
934
+ .sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? ''))
935
+ .slice(0, n);
936
+ },
937
+ async getCommit(ref) {
938
+ const res = await ghFetch(`${apiBase()}/repos/${full}/commits/${encodeURIComponent(ref)}`, { headers: headers(await token()) });
939
+ if (res.status === 404 || res.status === 422)
940
+ return null;
941
+ if (!res.ok)
942
+ throw new Error(`GitHub GET /repos/${full}/commits failed: HTTP ${res.status}`);
943
+ const c = (await res.json());
944
+ return {
945
+ sha: String(c.sha),
946
+ parents: (c.parents ?? []).map((p) => String(p.sha)),
947
+ message: String(c.commit?.message ?? ''),
948
+ ...(c.commit?.tree?.sha ? { treeSha: String(c.commit.tree.sha) } : {}),
949
+ ...(c.html_url ? { url: String(c.html_url) } : {}),
950
+ };
951
+ },
952
+ async defaultBranchName() {
953
+ const data = await call('GET', `/repos/${full}`);
954
+ const name = String(data.default_branch ?? '');
955
+ if (!name)
956
+ throw new Error(`GitHub did not report a default branch for ${full}`);
957
+ return name;
958
+ },
959
+ async refSha(kind, name) {
960
+ if (!isSafeRef(name))
961
+ return null;
962
+ const enc = (x) => x.split('/').map(encodeURIComponent).join('/');
963
+ const res = await ghFetch(`${apiBase()}/repos/${full}/git/ref/${kind === 'branch' ? 'heads' : 'tags'}/${enc(name)}`, { headers: headers(await token()) });
964
+ if (res.status === 404)
965
+ return null;
966
+ if (!res.ok) {
967
+ if (isOutageStatus(res))
968
+ throw new GithubUnreachableError(`Could not reach GitHub (GET /repos/${full}/git/ref): HTTP ${res.status}`);
969
+ throw httpError(`GitHub GET /repos/${full}/git/ref failed: HTTP ${res.status}`, res);
970
+ }
971
+ const ref = (await res.json());
972
+ let sha = String(ref.object?.sha ?? '');
973
+ // An annotated tag points at a tag object, which points at the commit.
974
+ if (ref.object?.type === 'tag')
975
+ sha = String((await call('GET', `/repos/${full}/git/tags/${sha}`)).object?.sha ?? '');
976
+ return isFullSha(sha) ? sha.toLowerCase() : null;
977
+ },
978
+ async fetchCommit(dir, sha, ref, timeoutMs) {
979
+ if (!isFullSha(sha))
980
+ throw new Error(`Refusing to fetch "${sha.slice(0, 60)}": a commit must be 40 hex characters.`);
981
+ if (!/^refs\/localdost\/[A-Za-z0-9._/-]+$/.test(ref) || ref.includes('..'))
982
+ throw new Error('Refusing to fetch into a ref outside refs/localdost/.');
983
+ await reading('checkout', async () => {
984
+ const tok = await token();
985
+ const authed = `${serverBase().replace(/^https:\/\//, `https://x-access-token:${encodeURIComponent(tok)}@`)}/${full}.git`;
986
+ // By URL, so the token never lands in the config of the folder the worktrees are made from.
987
+ await git(['fetch', '-q', '--depth', '1', '--no-tags', authed, `+${sha}:${ref}`], dir, tok, timeoutMs);
988
+ });
989
+ },
990
+ async compareStatus(base, head) {
991
+ const enc = (x) => x.split('/').map(encodeURIComponent).join('/');
992
+ const data = await call('GET', `/repos/${full}/compare/${enc(base)}...${encodeURIComponent(head)}?per_page=1`);
993
+ const status = String(data.status ?? '');
994
+ if (status !== 'ahead' && status !== 'behind' && status !== 'identical' && status !== 'diverged')
995
+ throw new Error(`GitHub compare of ${base}...${head} returned status "${status}"`);
996
+ return status;
997
+ },
998
+ async openPullWithFiles({ base, branch, title, body, files }) {
999
+ const enc = (x) => x.split('/').map(encodeURIComponent).join('/');
1000
+ const ref = await call('GET', `/repos/${full}/git/ref/heads/${enc(base)}`);
1001
+ const parent = String(ref.object?.sha ?? '');
1002
+ const commit = await call('GET', `/repos/${full}/git/commits/${parent}`);
1003
+ const tree = await call('POST', `/repos/${full}/git/trees`, {
1004
+ base_tree: String(commit.tree?.sha ?? ''),
1005
+ tree: files.map((f) => ({ path: f.path, mode: '100644', type: 'blob', content: f.content })),
1006
+ });
1007
+ const created = await call('POST', `/repos/${full}/git/commits`, { message: title, tree: String(tree.sha), parents: [parent] });
1008
+ // A retry after a failed send finds the branch, and maybe the PR, from the first attempt: the
1009
+ // branch moves to the new commit and an open PR for it is reused.
1010
+ const existing = await ghFetch(`${apiBase()}/repos/${full}/git/ref/heads/${enc(branch)}`, { headers: headers(await token()) });
1011
+ if (existing.ok)
1012
+ await call('PATCH', `/repos/${full}/git/refs/heads/${enc(branch)}`, { sha: String(created.sha), force: true });
1013
+ else
1014
+ await call('POST', `/repos/${full}/git/refs`, { ref: `refs/heads/${branch}`, sha: String(created.sha) });
1015
+ const open = await call('GET', `/repos/${full}/pulls?state=open&head=${encodeURIComponent(`${repo.owner}:${branch}`)}`);
1016
+ if (open.length)
1017
+ return String(open[0].html_url ?? '');
1018
+ const pull = await call('POST', `/repos/${full}/pulls`, { title, head: branch, base, body: truncate(body, 65000), draft: true });
1019
+ return String(pull.html_url ?? '');
1020
+ },
1021
+ async createCheckRun(sha, r) {
1022
+ return writeCheck(undefined, sha, r);
1023
+ },
1024
+ async updateCheckRun(id, sha, r) {
1025
+ await writeCheck(id, sha, r);
1026
+ },
1027
+ async searchCode(q) {
1028
+ try {
1029
+ const data = await call('GET', `/search/code?q=${encodeURIComponent(`${q} repo:${full}`)}&per_page=10`, undefined, 'application/vnd.github.text-match+json');
1030
+ return (data.items ?? []).map((it) => ({
1031
+ path: String(it.path),
1032
+ snippet: String(it.text_matches?.[0]?.fragment ?? ''),
1033
+ }));
1034
+ }
1035
+ catch (err) {
1036
+ console.warn(`[github] code search failed for ${full}: ${err.message}`);
1037
+ return [];
1038
+ }
1039
+ },
1040
+ async jobLogTail(runId) {
1041
+ const data = await call('GET', `/repos/${full}/actions/runs/${runId}/jobs?filter=latest&per_page=100`);
1042
+ const jobs = (data.jobs ?? []);
1043
+ const job = jobs.find((j) => j.conclusion === 'failure' || j.conclusion === 'timed_out') ?? jobs[0];
1044
+ if (!job)
1045
+ return '';
1046
+ const step = (job.steps ?? []).find((s) => s.conclusion === 'failure');
1047
+ // Redirects to a signed URL; fetch drops the Authorization header on the cross-origin hop.
1048
+ const res = await ghFetch(`${apiBase()}/repos/${full}/actions/jobs/${job.id}/logs`, { headers: headers(await token()) });
1049
+ const raw = res.ok ? await res.text() : '';
1050
+ const lines = raw
1051
+ .replace(/\u001b\[[0-9;]*m/g, '')
1052
+ .replace(/^\d{4}-\d\d-\d\dT[\d:.]+Z /gm, '')
1053
+ .split('\n');
1054
+ const tail = lines.slice(-200).join('\n').slice(-15000);
1055
+ return [`Job: ${job.name}`, step ? `Failed step: ${step.name}` : '', tail].filter(Boolean).join('\n');
1056
+ },
1057
+ async cloneTo(dir, sha) {
1058
+ // Both go to git as arguments: a full hex sha, or a ref that starts with a letter or digit (never an option).
1059
+ if (sha && !isFullSha(sha))
1060
+ throw new Error(`Refusing to fetch "${sha.slice(0, 60)}": a commit must be 40 hex characters.`);
1061
+ if (!sha && !isSafeRef(repo.defaultBranch))
1062
+ throw new Error(`Refusing to fetch the branch "${repo.defaultBranch.slice(0, 60)}": not a safe branch name.`);
1063
+ await reading('checkout', async () => {
1064
+ const tok = await token();
1065
+ const authed = `${serverBase().replace(/^https:\/\//, `https://x-access-token:${encodeURIComponent(tok)}@`)}/${full}.git`;
1066
+ await mkdir(dir, { recursive: true });
1067
+ // Fetch by URL so the token never lands in .git/config of the checkout we later mount.
1068
+ await git(['init', '-q'], dir, tok);
1069
+ await git(['fetch', '-q', '--depth', '1', '--no-tags', authed, sha || repo.defaultBranch], dir, tok);
1070
+ await git(['checkout', '-q', '--detach', 'FETCH_HEAD'], dir, tok);
1071
+ // A checkout with no files must not reach the setup step: it would run in a folder with no package.json or lockfile.
1072
+ if (!(await readdir(dir)).some((name) => name !== '.git'))
1073
+ throw new GithubUnreachableError(`The checkout of ${full} at ${sha || repo.defaultBranch} has no files.`, 'checkout');
1074
+ });
1075
+ },
1076
+ };
1077
+ }
1078
+ /** The body of a response as UTF-8 text, cut at `cap` bytes; the rest of the stream is not read. */
1079
+ async function readCapped(res, cap) {
1080
+ if (!res.body)
1081
+ return (await res.text()).slice(0, cap);
1082
+ const reader = res.body.getReader();
1083
+ const chunks = [];
1084
+ let size = 0;
1085
+ while (size < cap) {
1086
+ const { done, value } = await reader.read();
1087
+ if (done)
1088
+ break;
1089
+ chunks.push(Buffer.from(value));
1090
+ size += value.byteLength;
1091
+ }
1092
+ await reader.cancel().catch(() => { });
1093
+ return Buffer.concat(chunks).subarray(0, cap).toString('utf8');
1094
+ }
1095
+ /** One git command in a tracked process group, so shutdown and cancel can stop a clone mid-flight. */
1096
+ async function git(args, cwd, secret, timeoutMs) {
1097
+ const r = await runTracked('git', args, { cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, maxBuffer: 4000, ...(timeoutMs ? { timeoutMs } : {}) });
1098
+ if (r.error)
1099
+ throw new Error(`git ${args[0]} could not start: ${scrub(r.error, secret)}`);
1100
+ if (r.timedOut)
1101
+ throw new GitTimeoutError(`git ${args[0]} was stopped after ${Math.round((timeoutMs ?? 0) / 1000)} s`);
1102
+ if (r.code === 0)
1103
+ return;
1104
+ const how = r.signal ? `stopped by ${r.signal}` : `failed (exit ${r.code})`;
1105
+ const stderr = scrub(r.stderr, secret).trim();
1106
+ const message = `git ${args[0]} ${how}: ${stderr}`.replace(/: $/, '');
1107
+ // The network, not the repo: GitHub could not be reached, so the checkout is unknown rather than wrong.
1108
+ if (GIT_NETWORK_FAILURE.test(stderr)) {
1109
+ console.warn(`[github] could not reach GitHub for ${message}`);
1110
+ throw new GithubUnreachableError(`Could not reach GitHub (${message})`, 'checkout');
1111
+ }
1112
+ throw new Error(message);
1113
+ }
1114
+ const GIT_NETWORK_FAILURE = /Could not resolve host|Failed to connect|Connection (?:timed out|reset|refused)|Operation timed out|Network is unreachable|early EOF|RPC failed|remote end hung up|TLS|SSL|gnutls|returned error: (?:429|5\d\d)/i;
1115
+ function scrub(text, secret) {
1116
+ return secret ? text.split(secret).join('***').split(encodeURIComponent(secret)).join('***') : text;
1117
+ }
1118
+ /**
1119
+ * PRs from demo/payloads for the demo repo, one per number (the file read last wins), so the
1120
+ * PR picker and review on demand work with no GitHub. A watch on any other repo gets none.
1121
+ */
1122
+ function demoPulls(watch) {
1123
+ const dir = path.join(config.demoDir, 'payloads');
1124
+ const primary = primaryRepo(watch);
1125
+ if (!primary || !existsSync(dir))
1126
+ return [];
1127
+ const want = primary.id;
1128
+ const byNumber = new Map();
1129
+ let files;
1130
+ try {
1131
+ files = readdirSync(dir)
1132
+ .filter((f) => f.startsWith('pr_') && f.endsWith('.json'))
1133
+ .sort();
1134
+ }
1135
+ catch {
1136
+ return [];
1137
+ }
1138
+ for (const f of files) {
1139
+ try {
1140
+ const body = JSON.parse(readFileSync(path.join(dir, f), 'utf8')).body;
1141
+ const pull = body?.pull_request;
1142
+ if (!pull?.number || String(body?.repository?.full_name ?? '').toLowerCase() !== want)
1143
+ continue;
1144
+ byNumber.set(Number(pull.number), { pull, ...(Array.isArray(body?.inlineDiff) ? { diff: body.inlineDiff } : {}) });
1145
+ }
1146
+ catch {
1147
+ /* a broken demo payload is skipped */
1148
+ }
1149
+ }
1150
+ return [...byNumber.values()].sort((a, b) => Number(b.pull.number) - Number(a.pull.number));
1151
+ }
1152
+ // Demo client: reads only local data and writes nothing. The rules gate records outward actions in the outbox.
1153
+ function demoClient(watch, event) {
1154
+ const root = watch.runner?.workdir ?? path.join(config.demoDir, 'sample-app');
1155
+ return {
1156
+ mode: 'demo',
1157
+ prFiles: async () => event?.inlineDiff ?? [],
1158
+ compare: async () => event?.inlineDiff ?? [],
1159
+ upsertStickyComment: async () => { },
1160
+ createCheckRun: async () => undefined,
1161
+ updateCheckRun: async () => { },
1162
+ renderMarkdown: async () => null,
1163
+ addComment: async () => { },
1164
+ listOpenDefects: async () => [],
1165
+ checkRunsFor: async () => [],
1166
+ pullReviews: async () => [],
1167
+ commitStatuses: async () => [],
1168
+ teamMembers: async () => null,
1169
+ pullsOfCommit: async () => null,
1170
+ jobLog: async () => '',
1171
+ searchCode: (q) => localSearch(root, q),
1172
+ jobLogTail: async () => '',
1173
+ async getStickyComment(pr) {
1174
+ // The rules gate sends demo comments to the outbox; the newest one for this PR is the sticky.
1175
+ const { list } = await import('../db.js');
1176
+ const rows = list('outbox', { watchId: watch.id }, { orderBy: 'id DESC', limit: 200 });
1177
+ const hit = rows.find((o) => o.action.kind === 'pr_comment' && Number(o.action.meta?.pr) === pr && hasStickyMarker(o.action.body));
1178
+ return hit ? hit.action.body : null;
1179
+ },
1180
+ async readFile(p) {
1181
+ const full = path.resolve(root, p);
1182
+ if (!full.startsWith(path.resolve(root) + path.sep) || !existsSync(full))
1183
+ return null;
1184
+ return readFile(full, 'utf8');
1185
+ },
1186
+ rawDiff: async () => null,
1187
+ artifactSource: () => undefined,
1188
+ writePermission: async () => null,
1189
+ async getPull(pr) {
1190
+ const hit = demoPulls(watch).find((d) => Number(d.pull.number) === pr);
1191
+ return hit ? { summary: pullSummary(hit.pull), payload: hit.pull, ...(hit.diff ? { inlineDiff: hit.diff } : {}) } : null;
1192
+ },
1193
+ async defaultBranchName() {
1194
+ throw new Error('GitHub is not connected, so the default branch is unknown.');
1195
+ },
1196
+ async compareStatus() {
1197
+ throw new Error('GitHub is not connected, so commits cannot be compared.');
1198
+ },
1199
+ async listPulls(limit = 20) {
1200
+ return demoPulls(watch)
1201
+ .map((d) => pullSummary(d.pull))
1202
+ .filter((p) => p.state !== 'closed')
1203
+ .slice(0, limit);
1204
+ },
1205
+ async getCommit(ref) {
1206
+ const r = ref.toLowerCase();
1207
+ if (!/^[0-9a-f]{7,40}$/.test(r))
1208
+ return null;
1209
+ for (const d of demoPulls(watch)) {
1210
+ const p = d.pull;
1211
+ const merge = String(p.merge_commit_sha ?? '');
1212
+ const head = String(p.head?.sha ?? '');
1213
+ const parent = String(p.base?.sha ?? '');
1214
+ const sha = merge.startsWith(r) ? merge : head.startsWith(r) ? head : '';
1215
+ if (sha)
1216
+ return { sha, parents: parent ? [parent] : [], message: String(p.title ?? ''), ...(d.diff ? { inlineDiff: d.diff } : {}) };
1217
+ }
1218
+ return null;
1219
+ },
1220
+ async cloneTo(dir) {
1221
+ if (!existsSync(root))
1222
+ return;
1223
+ await cp(root, dir, {
1224
+ recursive: true,
1225
+ filter: (src) => !/[\\/](node_modules|\.git|test-results|playwright-report)([\\/]|$)/.test(src),
1226
+ });
1227
+ },
1228
+ };
1229
+ }
1230
+ const SEARCH_SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'test-results', 'playwright-report', 'coverage']);
1231
+ const SEARCH_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|html|css|md|json|py|go|rb|java|kt|vue|svelte)$/i;
1232
+ /** Keyword search over a local checkout, one best hit per file. */
1233
+ export async function localSearch(root, q, limit = 10) {
1234
+ const terms = q
1235
+ .toLowerCase()
1236
+ .split(/[^a-z0-9_-]+/)
1237
+ .filter((t) => t.length > 2);
1238
+ if (!terms.length || !existsSync(root))
1239
+ return [];
1240
+ const hits = [];
1241
+ let seen = 0;
1242
+ async function walk(dir) {
1243
+ if (seen > 2000)
1244
+ return;
1245
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
1246
+ if (SEARCH_SKIP.has(entry.name) || entry.name.startsWith('.'))
1247
+ continue;
1248
+ const full = path.join(dir, entry.name);
1249
+ if (entry.isDirectory()) {
1250
+ await walk(full);
1251
+ continue;
1252
+ }
1253
+ if (!SEARCH_EXT.test(entry.name) || ++seen > 2000)
1254
+ continue;
1255
+ if ((await stat(full)).size > 200_000)
1256
+ continue;
1257
+ const lines = (await readFile(full, 'utf8')).split('\n');
1258
+ let best = -1;
1259
+ let bestScore = 0;
1260
+ lines.forEach((line, i) => {
1261
+ const lower = line.toLowerCase();
1262
+ const score = terms.filter((t) => lower.includes(t)).length;
1263
+ if (score > bestScore) {
1264
+ bestScore = score;
1265
+ best = i;
1266
+ }
1267
+ });
1268
+ if (best >= 0) {
1269
+ const snippet = lines
1270
+ .slice(Math.max(0, best - 1), best + 2)
1271
+ .join('\n')
1272
+ .trim()
1273
+ .slice(0, 400);
1274
+ hits.push({ path: path.relative(root, full), snippet, score: bestScore });
1275
+ }
1276
+ }
1277
+ }
1278
+ await walk(root);
1279
+ return hits
1280
+ .sort((a, b) => b.score - a.score)
1281
+ .slice(0, limit)
1282
+ .map(({ path: p, snippet }) => ({ path: p, snippet }));
1283
+ }
1284
+ //# sourceMappingURL=github.js.map