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,1464 @@
1
+ // LLM wrapper. AnthropicLlm talks to Claude; MockLlm derives deterministic verdicts from the
2
+ // inputs so the product demos offline with realistic output.
3
+ import Anthropic from '@anthropic-ai/sdk';
4
+ import { COMPOSE_VIEW_TOOL, modelFacing } from './chat-views.js';
5
+ import { asksForComposedView, mockComposedReply } from './mock-views.js';
6
+ import { callChatTool, chatToolsFor, closeViewStreams, designRequestHeld, plannedFirstTool, reviewHasConditions, reviewIntent, reviewRequestHeld, viewStreamOpener, } from './chat.js';
7
+ import { ClaudeCliLlm } from './llm-cli.js';
8
+ import { liveEnv, log, onSettingsChange } from '../config.js';
9
+ import { update } from '../db.js';
10
+ import { countedCall } from '../model-usage.js';
11
+ import { currentRunId } from '../queue.js';
12
+ import { CodexCliLlm } from './llm-codex.js';
13
+ import { OPENAI_COMPAT_SETTING_KEYS, OpenAiCompatLlm } from './llm-openai.js';
14
+ import { classifyThrown, ModelUnavailableError, rethrowIfUnavailable } from './model-errors.js';
15
+ import { GuardedLlm, startRecheckTimer } from './model-guard.js';
16
+ import { FAMILY_LABEL, FAMILY_OF_METHOD, dueForRecheck, getUnavailability, markUnavailable } from './model-availability.js';
17
+ import { addStep } from './runs.js';
18
+ import { getReport } from '../reports/index.js';
19
+ import { REVIEW_SYSTEM, SCORE_SYSTEM, renderActionForReview, renderAnalyzeInput, renderTicket, renderWatchForChat, systemFor, chatSystemFor, } from './prompts.js';
20
+ import { presetHintsForWatch } from '../connections/presets.js';
21
+ import { redact } from './redact.js';
22
+ import { reportReasoning } from './reasoning.js';
23
+ import { scrubVerdict } from './team-notes.js';
24
+ export const ANALYSIS_MODEL = 'claude-sonnet-5-5';
25
+ export const FAST_MODEL = 'claude-haiku-4-5-20251001';
26
+ /** Main model for the Anthropic API backend: LOCALDOST_MODEL (env or saved in Setup), else ANALYSIS_MODEL. */
27
+ export function mainModel() {
28
+ return liveEnv.LOCALDOST_MODEL?.trim() || ANALYSIS_MODEL;
29
+ }
30
+ /** Fast model for scoring and auto-review: LOCALDOST_FAST_MODEL, else FAST_MODEL. */
31
+ export function fastModel() {
32
+ return liveEnv.LOCALDOST_FAST_MODEL?.trim() || FAST_MODEL;
33
+ }
34
+ /** Settings that pick or configure the model backend. A change drops the cached instance. */
35
+ export const MODEL_SETTING_KEYS = [
36
+ 'LOCALDOST_LLM',
37
+ 'ANTHROPIC_API_KEY',
38
+ 'CLAUDE_CODE_OAUTH_TOKEN',
39
+ 'LOCALDOST_MODEL',
40
+ 'LOCALDOST_FAST_MODEL',
41
+ 'LOCALDOST_CLAUDE_MODEL',
42
+ 'LOCALDOST_CLAUDE_FAST_MODEL',
43
+ 'LOCALDOST_CLAUDE_BIN',
44
+ 'LOCALDOST_CODEX_MODEL',
45
+ 'LOCALDOST_CODEX_BIN',
46
+ ...OPENAI_COMPAT_SETTING_KEYS,
47
+ ];
48
+ let instance;
49
+ /** Backends other than the chosen one, built when a call needs to fall back (KAT-102). */
50
+ const spare = new Map();
51
+ /** True while a test pinned the instance with setLlm(); settings changes then leave it alone. */
52
+ let pinned = false;
53
+ onSettingsChange((keys) => {
54
+ if (keys.some((k) => MODEL_SETTING_KEYS.includes(k) || k.startsWith('LOCALDOST_CODEX_')))
55
+ spare.clear();
56
+ if (!pinned && keys.some((k) => MODEL_SETTING_KEYS.includes(k)))
57
+ instance = undefined;
58
+ });
59
+ /** Backend name for the boot log. */
60
+ export function llmBackend() {
61
+ const l = rawLlm();
62
+ if (l instanceof ClaudeCliLlm)
63
+ return 'claude-cli';
64
+ if (l instanceof CodexCliLlm)
65
+ return 'codex-cli';
66
+ if (l instanceof AnthropicLlm)
67
+ return 'anthropic';
68
+ if (l instanceof OpenAiCompatLlm)
69
+ return 'openai-compat';
70
+ return 'mock';
71
+ }
72
+ /**
73
+ * The backend the settings pick, without the model guard. LOCALDOST_LLM: claude-cli (KAT-30), codex-cli (KAT-68),
74
+ * anthropic, openai-compat (the preset TINYBEE_MODEL_BACKEND names: OpenAI, Gemini, Ollama, LM Studio or a custom
75
+ * endpoint, src/agent/llm-openai.ts), or mock. Unset: Anthropic when ANTHROPIC_API_KEY is set, else the mock.
76
+ * Everything here runs on this laptop with the person's own access; a paired Bee never sends host work to Hive's AI.
77
+ */
78
+ export function rawLlm() {
79
+ if (!instance) {
80
+ const pick = (liveEnv.LOCALDOST_LLM ?? '').trim().toLowerCase();
81
+ if (pick === 'claude-cli')
82
+ instance = new ClaudeCliLlm();
83
+ else if (pick === 'codex-cli')
84
+ instance = new CodexCliLlm();
85
+ else if (pick === 'mock')
86
+ instance = new MockLlm();
87
+ else if (pick === 'openai-compat')
88
+ instance = OpenAiCompatLlm.fromSettings() ?? new MockLlm();
89
+ else if (pick === 'anthropic' || liveEnv.ANTHROPIC_API_KEY)
90
+ instance = new AnthropicLlm();
91
+ else
92
+ instance = new MockLlm();
93
+ }
94
+ return instance;
95
+ }
96
+ export function familyOfLlm(l) {
97
+ if (l instanceof ClaudeCliLlm)
98
+ return 'claude';
99
+ if (l instanceof CodexCliLlm)
100
+ return 'codex';
101
+ if (l instanceof AnthropicLlm)
102
+ return 'anthropic';
103
+ return undefined;
104
+ }
105
+ function spareLlm(family) {
106
+ let l = spare.get(family);
107
+ if (!l) {
108
+ l = family === 'claude' ? new ClaudeCliLlm() : family === 'codex' ? new CodexCliLlm() : new AnthropicLlm();
109
+ spare.set(family, l);
110
+ }
111
+ return l;
112
+ }
113
+ /** Where the guard finds its backends: the chosen one, then the signed-in ones of the model switcher list (KAT-83). */
114
+ const llmSource = {
115
+ preferred: () => familyOfLlm(rawLlm()),
116
+ primary: () => rawLlm(),
117
+ async others(pref) {
118
+ const { workingFamilies } = await import('../connect/model-choices.js');
119
+ return (await workingFamilies()).filter((f) => f !== pref).map((family) => ({ family, backend: spareLlm(family) }));
120
+ },
121
+ };
122
+ /** Test hook: the backend a call falls back to for this family. No backend removes it. */
123
+ export function setSpareLlm(family, llm) {
124
+ if (llm)
125
+ spare.set(family, llm);
126
+ else
127
+ spare.delete(family);
128
+ }
129
+ let guarded;
130
+ /**
131
+ * The model every caller uses. It is the chosen backend behind the model guard (model-guard.ts): a quota,
132
+ * spend cap, rate limit or sign-in error moves the call to the next working model, or throws
133
+ * ModelUnavailableError when none works. Use rawLlm() to look at the backend itself.
134
+ */
135
+ export function getLlm() {
136
+ return (guarded ??= new GuardedLlm(llmSource));
137
+ }
138
+ // ---------- model per stage (KAT-162, docs/STAGES.md section 5.4) ----------
139
+ /** The ids a dot may pick per stage: the switcher list of src/connect/model-choices.ts. */
140
+ export const STAGE_MODEL_IDS = Object.keys(FAMILY_OF_METHOD);
141
+ /** The model a run used, as `backend/model`. The mock names itself. */
142
+ export function modelLabel(family) {
143
+ if (family === 'claude')
144
+ return `claude-cli/${liveEnv.LOCALDOST_CLAUDE_MODEL?.trim() || liveEnv.LOCALDOST_MODEL?.trim() || 'sonnet'}`;
145
+ if (family === 'codex')
146
+ return `codex-cli/${liveEnv.LOCALDOST_CODEX_MODEL?.trim() || 'default'}`;
147
+ if (family === 'anthropic')
148
+ return `anthropic/${mainModel()}`;
149
+ return 'mock';
150
+ }
151
+ /** The label of the model the settings pick for stages with no model of their own. */
152
+ export function defaultModelLabel() {
153
+ return modelLabel(familyOfLlm(rawLlm()));
154
+ }
155
+ /** Writes the model onto the run the calling job is working on. A missing run or store error never fails the call. */
156
+ export function recordRunModel(label) {
157
+ const id = currentRunId();
158
+ if (!id)
159
+ return;
160
+ try {
161
+ update('runs', id, { model: label });
162
+ }
163
+ catch {
164
+ /* the run row is gone */
165
+ }
166
+ }
167
+ const noted = new Set();
168
+ function noteStageFallback(stage, who, why) {
169
+ const detail = `The ${stage} stage is set to ${who}, which ${why}. Using the dot's default model instead.`;
170
+ log.warn(`model: ${detail}`);
171
+ const id = currentRunId();
172
+ if (!id)
173
+ return;
174
+ const key = `${id}:${stage}:${why}`;
175
+ if (noted.has(key))
176
+ return;
177
+ noted.add(key);
178
+ if (noted.size > 500)
179
+ noted.delete(noted.values().next().value);
180
+ try {
181
+ addStep(id, 'Stage model unavailable', detail, 'warn');
182
+ }
183
+ catch {
184
+ /* the run row is gone */
185
+ }
186
+ }
187
+ /**
188
+ * The backend a stage asks for, or undefined when the dot's default model runs the stage: no entry for the
189
+ * stage, the default is the mock (demo mode keeps the mock for every stage), the stage names the default's own
190
+ * backend, or the named backend is not signed in or is waiting after a quota error. The last cases leave a log
191
+ * line and a run step. Never throws.
192
+ */
193
+ export async function pickStageModel(watch, stage) {
194
+ const id = stage ? watch.stageModels?.[stage] : undefined;
195
+ if (!stage || !id)
196
+ return undefined;
197
+ const family = FAMILY_OF_METHOD[id];
198
+ if (!family) {
199
+ noteStageFallback(stage, `the model "${String(id)}"`, 'is not on the model list');
200
+ return undefined;
201
+ }
202
+ const def = familyOfLlm(rawLlm());
203
+ if (!def || def === family)
204
+ return undefined;
205
+ const who = FAMILY_LABEL[family];
206
+ try {
207
+ const { workingFamilies } = await import('../connect/model-choices.js');
208
+ if (!(await workingFamilies()).includes(family)) {
209
+ noteStageFallback(stage, who, 'is not signed in or not available');
210
+ return undefined;
211
+ }
212
+ }
213
+ catch (err) {
214
+ noteStageFallback(stage, who, `could not be checked (${err instanceof Error ? err.message : String(err)})`);
215
+ return undefined;
216
+ }
217
+ const e = getUnavailability(family);
218
+ if (e && !dueForRecheck(family)) {
219
+ noteStageFallback(stage, who, `is unavailable (${e.reason})`);
220
+ return undefined;
221
+ }
222
+ return { family, id };
223
+ }
224
+ /**
225
+ * The backend that answers for a dot: the default model of the workspace. A stage that names another model falls back to it when that
226
+ * model is signed out or unavailable (pickStageModel), so only the default model's sign-in can stop a dot.
227
+ */
228
+ export function defaultFamily() {
229
+ return familyOfLlm(rawLlm());
230
+ }
231
+ /** The backend instance of a family: the chosen one when it matches, else the spare. */
232
+ export function llmOfFamily(family) {
233
+ const raw = rawLlm();
234
+ return familyOfLlm(raw) === family ? raw : spareLlm(family);
235
+ }
236
+ /** Records a stage backend that failed with a quota, spend cap, rate limit or sign-in error, and says so on the run. */
237
+ export function stageModelFailed(stage, family, err) {
238
+ markUnavailable(family, err);
239
+ noteStageFallback(stage, FAMILY_LABEL[family], `failed (${err.reason})`);
240
+ }
241
+ /** The model of one stage. Every call tries the stage's model, then falls back to getLlm(). Chat and auto-review use the default. */
242
+ class StageLlm {
243
+ watch;
244
+ stage;
245
+ constructor(watch, stage) {
246
+ this.watch = watch;
247
+ this.stage = stage;
248
+ }
249
+ /** The backend a free-text check looks at: the stage's own when it has one, else the chosen one. */
250
+ unwrap() {
251
+ const id = this.watch.stageModels?.[this.stage];
252
+ const family = id ? FAMILY_OF_METHOD[id] : undefined;
253
+ const def = familyOfLlm(rawLlm());
254
+ return family && def && family !== def ? llmOfFamily(family) : rawLlm();
255
+ }
256
+ async run(call) {
257
+ const pick = await pickStageModel(this.watch, this.stage);
258
+ if (pick) {
259
+ try {
260
+ const out = await countedCall(pick.family, () => call(llmOfFamily(pick.family)));
261
+ recordRunModel(modelLabel(pick.family));
262
+ return out;
263
+ }
264
+ catch (err) {
265
+ if (!(err instanceof ModelUnavailableError))
266
+ throw err;
267
+ stageModelFailed(this.stage, pick.family, err);
268
+ }
269
+ }
270
+ const out = await call(getLlm());
271
+ recordRunModel(defaultModelLabel());
272
+ return out;
273
+ }
274
+ async analyze(req) {
275
+ // A stage model skips the GuardedLlm, so the team notes guard runs here too (KAT-383).
276
+ return scrubVerdict(req, await this.run((l) => l.analyze(req)));
277
+ }
278
+ score(ticket) {
279
+ return this.run((l) => l.score(ticket));
280
+ }
281
+ write(req) {
282
+ return this.run((l) => (l.write ? l.write(req) : Promise.reject(new Error('this model backend writes no free text'))));
283
+ }
284
+ /** The safety review keeps the default model. */
285
+ review(action, watch) {
286
+ return getLlm().review(action, watch);
287
+ }
288
+ /** Chat keeps the default model. */
289
+ chat(req, onDelta, onTool) {
290
+ return getLlm().chat(req, onDelta, onTool);
291
+ }
292
+ }
293
+ /**
294
+ * The model for one stage of a dot. With `watch.stageModels[stage]` set and that model signed in and ready, calls
295
+ * go to it. Otherwise they go to getLlm(), the dot's default model, with a log line and a run step when a set model
296
+ * could not be used. It never fails the job over the stage model. A call with no stage is getLlm(): chat, the
297
+ * safety review, and anything else that belongs to no stage.
298
+ */
299
+ export function getLlmFor(watch, stage) {
300
+ return stage ? new StageLlm(watch, stage) : getLlm();
301
+ }
302
+ /** True when the backend behind `llm` writes free text. For getLlm() that is the chosen backend; the mock writes none. */
303
+ export function canWriteText(llm = getLlm()) {
304
+ const target = llm instanceof GuardedLlm || llm instanceof StageLlm ? llm.unwrap() : llm;
305
+ return typeof target.write === 'function';
306
+ }
307
+ /**
308
+ * One free-text answer from the main model (KAT-282): the model's raw text, or null when the
309
+ * backend writes no free text (the mock), the reply is empty, or the call failed. Through
310
+ * getLlm() a quota, spend cap, rate limit or expired sign-in falls back to the next working model
311
+ * like every other call, and ModelUnavailableError is thrown when none works.
312
+ */
313
+ export async function writeText(req, opts = {}) {
314
+ const llm = opts.llm ?? getLlm();
315
+ const fail = (reason) => {
316
+ opts.onFailure?.(reason);
317
+ return null;
318
+ };
319
+ if (!canWriteText(llm) || !llm.write)
320
+ return fail('this model backend writes no free text');
321
+ try {
322
+ const text = await llm.write(req);
323
+ return typeof text === 'string' && text.trim() ? text : fail('the model returned no text');
324
+ }
325
+ catch (err) {
326
+ rethrowIfUnavailable(err);
327
+ const reason = redact(err instanceof Error ? err.message : String(err)).slice(0, 300);
328
+ console.warn(`[llm] free-text call failed: ${reason}`);
329
+ return fail(reason);
330
+ }
331
+ }
332
+ const PROBE_TICKET = { key: 'PROBE-1', id: '0', tracker: 'jira', title: 'Check that the model answers', description: 'Reply with a score.' };
333
+ /** Boot hook: tests the chosen model when its wait is over and no call came. Returns the stop function. */
334
+ export function startModelRecheck() {
335
+ return startRecheckTimer(llmSource, (l) => l.score(PROBE_TICKET));
336
+ }
337
+ /** Test hook: swap the implementation. */
338
+ export function setLlm(llm) {
339
+ instance = llm;
340
+ pinned = llm !== undefined;
341
+ }
342
+ // ---------- shared schema and normalization ----------
343
+ const OUTCOMES = ['pass', 'product_failure', 'test_failure', 'environment_failure', 'flaky', 'needs_attention', 'info'];
344
+ const LEVELS = ['low', 'medium', 'high'];
345
+ const VERDICT_TOOL = {
346
+ name: 'report_verdict',
347
+ description: 'Report the QA verdict for this task. Call exactly once.',
348
+ strict: true,
349
+ input_schema: {
350
+ type: 'object',
351
+ additionalProperties: false,
352
+ properties: {
353
+ outcome: { type: 'string', enum: OUTCOMES },
354
+ headline: { type: 'string', description: 'One sentence, under 120 characters, readable on a lock screen.' },
355
+ summary: { type: 'string', description: 'Markdown body for the PR or ticket comment.' },
356
+ risk: { type: 'string', enum: [...LEVELS] },
357
+ findings: {
358
+ type: 'array',
359
+ items: {
360
+ type: 'object',
361
+ additionalProperties: false,
362
+ properties: {
363
+ title: { type: 'string' },
364
+ detail: { type: 'string' },
365
+ severity: { type: 'string', enum: [...LEVELS] },
366
+ file: { type: 'string' },
367
+ line: { type: 'integer' },
368
+ },
369
+ required: ['title', 'detail', 'severity'],
370
+ },
371
+ },
372
+ openQuestions: { type: 'array', items: { type: 'string' } },
373
+ answeredQuestions: {
374
+ type: 'array',
375
+ description: 'Only in a second review of a ticket: the questions of the last review that the new comments answer.',
376
+ items: {
377
+ type: 'object',
378
+ additionalProperties: false,
379
+ properties: { question: { type: 'string' }, by: { type: 'string' }, quote: { type: 'string' } },
380
+ required: ['question', 'by', 'quote'],
381
+ },
382
+ },
383
+ testIdeas: { type: 'array', items: { type: 'string' } },
384
+ testsAtRisk: { type: 'array', items: { type: 'string' } },
385
+ suspectedCause: { type: 'string' },
386
+ confidence: { type: 'number', description: '0 to 1' },
387
+ },
388
+ required: ['outcome', 'headline', 'summary', 'risk', 'findings', 'openQuestions', 'testIdeas', 'testsAtRisk', 'confidence'],
389
+ },
390
+ };
391
+ const SCORE_TOOL = {
392
+ name: 'report_score',
393
+ description: 'Report how actionable the ticket is for QA.',
394
+ input_schema: {
395
+ type: 'object',
396
+ properties: { score: { type: 'integer', description: '0 to 100' }, reason: { type: 'string' } },
397
+ required: ['score', 'reason'],
398
+ },
399
+ };
400
+ const REVIEW_TOOL = {
401
+ name: 'report_review',
402
+ description: 'Report whether the outward action may go out without a human.',
403
+ input_schema: {
404
+ type: 'object',
405
+ properties: { ok: { type: 'boolean' }, escalate: { type: 'boolean' }, reason: { type: 'string' } },
406
+ required: ['ok', 'escalate', 'reason'],
407
+ },
408
+ };
409
+ function strings(v, max = 12) {
410
+ return Array.isArray(v) ? v.filter((x) => typeof x === 'string' && x.trim() !== '').slice(0, max) : [];
411
+ }
412
+ /** The answeredQuestions of a reply, as strings of a safe size. The code that uses them checks them again (src/flows/answers.ts). */
413
+ function answeredList(v) {
414
+ if (!Array.isArray(v))
415
+ return [];
416
+ return v.slice(0, 12).flatMap((x) => {
417
+ if (!x || typeof x !== 'object')
418
+ return [];
419
+ const a = x;
420
+ if (typeof a.question !== 'string' || typeof a.by !== 'string' || !a.question.trim() || !a.by.trim())
421
+ return [];
422
+ return [
423
+ { question: a.question.trim().slice(0, 500), by: a.by.trim().slice(0, 60), quote: typeof a.quote === 'string' ? a.quote.trim().slice(0, 600) : '' },
424
+ ];
425
+ });
426
+ }
427
+ function level(v, fallback = 'medium') {
428
+ return LEVELS.find((l) => l === v) ?? fallback;
429
+ }
430
+ export function normalizeVerdict(raw) {
431
+ const o = (raw && typeof raw === 'object' ? raw : {});
432
+ const findings = Array.isArray(o.findings)
433
+ ? o.findings.slice(0, 20).flatMap((f) => {
434
+ if (!f || typeof f !== 'object')
435
+ return [];
436
+ const x = f;
437
+ const item = { title: String(x.title ?? 'Finding'), detail: String(x.detail ?? ''), severity: level(x.severity) };
438
+ if (typeof x.file === 'string' && x.file)
439
+ item.file = x.file;
440
+ if (typeof x.line === 'number')
441
+ item.line = x.line;
442
+ return [item];
443
+ })
444
+ : [];
445
+ const verdict = {
446
+ outcome: OUTCOMES.find((x) => x === o.outcome) ?? 'needs_attention',
447
+ headline: String(o.headline ?? 'localdost finished a check').slice(0, 200),
448
+ summary: String(o.summary ?? ''),
449
+ risk: level(o.risk),
450
+ findings,
451
+ openQuestions: strings(o.openQuestions),
452
+ testIdeas: strings(o.testIdeas),
453
+ testsAtRisk: strings(o.testsAtRisk, 30),
454
+ confidence: typeof o.confidence === 'number' ? Math.max(0, Math.min(1, o.confidence)) : 0.5,
455
+ };
456
+ if (typeof o.suspectedCause === 'string' && o.suspectedCause)
457
+ verdict.suspectedCause = o.suspectedCause;
458
+ const answered = answeredList(o.answeredQuestions);
459
+ if (answered.length)
460
+ verdict.answeredQuestions = answered;
461
+ return verdict;
462
+ }
463
+ // ---------- Anthropic ----------
464
+ export class AnthropicLlm {
465
+ // The key comes from liveEnv so a key saved in Setup works; undefined lets the SDK read the env.
466
+ client;
467
+ mock = new MockLlm();
468
+ constructor() {
469
+ this.client = new Anthropic({ apiKey: liveEnv.ANTHROPIC_API_KEY });
470
+ }
471
+ async analyze(req) {
472
+ const system = [{ type: 'text', text: systemFor(req.task), cache_control: { type: 'ephemeral' } }];
473
+ const messages = [{ role: 'user', content: renderAnalyzeInput(req) }];
474
+ const effort = req.task === 'ticket_review' || req.task === 'digest' ? 'medium' : 'high';
475
+ // Sonnet 5.5 rejects forced tool_choice, so the verdict tool is strict + auto and the
476
+ // system prompt requires it. One nudge turn covers the rare text-only reply.
477
+ for (let attempt = 0; attempt < 2; attempt++) {
478
+ const res = await classifyThrown(() => this.client.messages.create({
479
+ model: mainModel(),
480
+ max_tokens: 16000,
481
+ system,
482
+ tools: [VERDICT_TOOL],
483
+ tool_choice: { type: 'auto' },
484
+ output_config: { effort },
485
+ messages,
486
+ }));
487
+ if (res.stop_reason === 'refusal')
488
+ break;
489
+ const call = res.content.find((b) => b.type === 'tool_use' && b.name === 'report_verdict');
490
+ if (call && res.stop_reason !== 'max_tokens')
491
+ return normalizeVerdict(call.input);
492
+ messages.push({ role: 'assistant', content: res.content });
493
+ messages.push({ role: 'user', content: 'Call report_verdict now with your verdict. Do not answer in text.' });
494
+ }
495
+ const fallback = await this.mock.analyze(req);
496
+ return {
497
+ ...fallback,
498
+ confidence: Math.min(fallback.confidence, 0.3),
499
+ summary: `${fallback.summary}\n\n_Model verdict unavailable; this is a rule-based fallback._`,
500
+ };
501
+ }
502
+ async score(ticket) {
503
+ try {
504
+ const res = await classifyThrown(() => this.client.messages.create({
505
+ model: fastModel(),
506
+ max_tokens: 512,
507
+ system: SCORE_SYSTEM,
508
+ tools: [SCORE_TOOL],
509
+ tool_choice: { type: 'tool', name: 'report_score' },
510
+ messages: [{ role: 'user', content: renderTicket(ticket) }],
511
+ }));
512
+ const call = res.content.find((b) => b.type === 'tool_use');
513
+ const score = Number(call?.input?.score);
514
+ if (Number.isFinite(score))
515
+ return Math.max(0, Math.min(100, Math.round(score)));
516
+ }
517
+ catch (err) {
518
+ rethrowIfUnavailable(err);
519
+ console.warn('[llm] score failed, using heuristic', err);
520
+ }
521
+ return this.mock.score(ticket);
522
+ }
523
+ async review(action, watch) {
524
+ const res = await classifyThrown(() => this.client.messages.create({
525
+ model: fastModel(),
526
+ max_tokens: 512,
527
+ system: REVIEW_SYSTEM,
528
+ tools: [REVIEW_TOOL],
529
+ tool_choice: { type: 'tool', name: 'report_review' },
530
+ messages: [{ role: 'user', content: renderActionForReview(action, watch) }],
531
+ }));
532
+ const call = res.content.find((b) => b.type === 'tool_use');
533
+ if (!call)
534
+ return { ok: true, escalate: true, reason: 'Reviewer returned no decision.', category: 'reviewer_unavailable' };
535
+ const x = (call.input ?? {});
536
+ // A decision without both booleans is no decision: never let a malformed review send a draft.
537
+ if (typeof x.ok !== 'boolean' || typeof x.escalate !== 'boolean')
538
+ return { ok: true, escalate: true, reason: 'Reviewer returned no decision.', category: 'reviewer_unavailable' };
539
+ return { ok: x.ok, escalate: x.escalate, reason: String(x.reason ?? '') };
540
+ }
541
+ async write(req) {
542
+ const res = await classifyThrown(() => this.client.messages.create({ model: mainModel(), max_tokens: 12000, system: req.system, messages: [{ role: 'user', content: req.prompt }] }, { timeout: req.timeoutMs ?? 180_000 }));
543
+ if (res.stop_reason === 'refusal')
544
+ throw new Error('the model refused');
545
+ const text = res.content
546
+ .filter((b) => b.type === 'text')
547
+ .map((b) => b.text)
548
+ .join('');
549
+ if (!text.trim())
550
+ throw new Error('the model returned no text');
551
+ return text;
552
+ }
553
+ async chat(req, onDelta, onTool) {
554
+ const system = [{ type: 'text', text: chatSystemFor(req.channel), cache_control: { type: 'ephemeral' } }];
555
+ if (req.context)
556
+ system.push({ type: 'text', text: req.context });
557
+ // "review PR 58 without posting a comment" asks for a review the tool cannot run as asked:
558
+ // hide the review tools for this turn so the model explains what a review does instead of queuing
559
+ // one. A refusal ("do not review TAD-3") or a condition ("after CI") hides them the same way.
560
+ const allTools = chatToolsFor(req.watch, req.reportId);
561
+ const held = reviewHasConditions(req.text) || reviewRequestHeld(req.text, req.watch);
562
+ const designHeld = designRequestHeld(req.text, req.watch);
563
+ const tools = held || designHeld
564
+ ? allTools.filter((t) => !(held && (t.name === 'review_pr' || t.name === 'review_ticket')) && !(designHeld && t.name === 'design_tests'))
565
+ : allTools;
566
+ const hints = req.watch ? presetHintsForWatch(req.watch) : '';
567
+ const context = req.watch
568
+ ? `${renderWatchForChat(req.watch)}${hints ? `\n${hints}` : ''}\nSelected dot id: ${req.watch.id}\n\n`
569
+ : 'No dot selected; tools default to all dots.\n\n';
570
+ const messages = [
571
+ ...req.history.map((h) => ({ role: h.role, content: h.content })),
572
+ { role: 'user', content: `${context}${req.text}` },
573
+ ];
574
+ let out = '';
575
+ const emit = (t) => {
576
+ out += t;
577
+ onDelta(t);
578
+ };
579
+ const ctx = { watch: req.watch, reportId: req.reportId };
580
+ // A ticket or PR link names the first lookup (KAT-98): run it now and hand the model the result,
581
+ // so the answer starts from that ticket or PR instead of from unrelated run history.
582
+ const first = plannedFirstTool(req.text, req.watch);
583
+ // The planned call is an assistant tool_use turn with no thinking block, and the API refuses thinking on a
584
+ // request that holds one. So a turn with a planned first call runs without thinking.
585
+ let thinkingOn = true;
586
+ if (first && tools.some((t) => t.name === first.name)) {
587
+ thinkingOn = false;
588
+ const id = 'toolu_localdost_planned_0';
589
+ let content;
590
+ let isError = false;
591
+ try {
592
+ content = JSON.stringify(modelFacing(await callChatTool(first.name, first.input, ctx, onTool)));
593
+ }
594
+ catch (err) {
595
+ content = String(err);
596
+ isError = true;
597
+ }
598
+ messages.push({ role: 'assistant', content: [{ type: 'tool_use', id, name: first.name, input: first.input }] });
599
+ messages.push({ role: 'user', content: [{ type: 'tool_result', tool_use_id: id, content, ...(isError ? { is_error: true } : {}) }] });
600
+ }
601
+ const openViewStream = viewStreamOpener();
602
+ for (let turn = 0; turn < 8; turn++) {
603
+ const stream = this.client.messages.stream({
604
+ model: mainModel(),
605
+ max_tokens: 8000,
606
+ system,
607
+ tools,
608
+ output_config: { effort: 'low' },
609
+ // Summarized thinking is the model's own summary of its reasoning; only its text becomes step lines.
610
+ ...(thinkingOn ? { thinking: { type: 'adaptive', display: 'summarized' } } : {}),
611
+ messages,
612
+ });
613
+ stream.on('text', (delta) => emit(delta));
614
+ const told = new Set();
615
+ const tell = (b) => {
616
+ if (b.type !== 'thinking' || told.has(b.thinking))
617
+ return;
618
+ told.add(b.thinking);
619
+ reportReasoning(b.thinking);
620
+ };
621
+ stream.on('contentBlock', tell);
622
+ // A compose_view call is written as JSON that arrives in pieces. The view goes to the client while it is written: its shell once the
623
+ // first node of the root has closed, then each node as it closes (docs/CHAT-UI.md, Composed views). The finished call replaces it.
624
+ const feeds = new Map();
625
+ stream.on('streamEvent', (ev) => {
626
+ try {
627
+ if (ev.type === 'content_block_start' && ev.content_block.type === 'tool_use' && ev.content_block.name === COMPOSE_VIEW_TOOL) {
628
+ const feed = openViewStream();
629
+ if (feed)
630
+ feeds.set(ev.index, feed);
631
+ }
632
+ else if (ev.type === 'content_block_delta' && ev.delta.type === 'input_json_delta')
633
+ feeds.get(ev.index)?.feed(ev.delta.partial_json);
634
+ else if (ev.type === 'content_block_stop')
635
+ feeds.get(ev.index)?.end();
636
+ }
637
+ catch {
638
+ /* the preview is not the view: the finished call still builds it */
639
+ }
640
+ });
641
+ const msg = await classifyThrown(() => stream.finalMessage());
642
+ msg.content.forEach(tell);
643
+ if (msg.stop_reason === 'refusal') {
644
+ emit(out ? '\n' : "I can't help with that request.");
645
+ break;
646
+ }
647
+ const uses = msg.content.filter((b) => b.type === 'tool_use');
648
+ if (msg.stop_reason !== 'tool_use' || uses.length === 0)
649
+ break;
650
+ messages.push({ role: 'assistant', content: msg.content });
651
+ const results = await Promise.all(uses.map(async (u) => {
652
+ if ((u.name === 'review_pr' || u.name === 'review_ticket' || u.name === 'design_tests') && !tools.some((t) => t.name === u.name)) {
653
+ onTool(u.name, u.input);
654
+ return { type: 'tool_result', tool_use_id: u.id, content: `${u.name} is not available for a request with conditions.`, is_error: true };
655
+ }
656
+ try {
657
+ // A repeat of a call made this turn returns the first result and shows no second card.
658
+ const value = await callChatTool(u.name, u.input, ctx, onTool);
659
+ // The model reads the result with its id, so compose_view can name it (KAT-366).
660
+ return { type: 'tool_result', tool_use_id: u.id, content: JSON.stringify(modelFacing(value)) };
661
+ }
662
+ catch (err) {
663
+ return { type: 'tool_result', tool_use_id: u.id, content: String(err), is_error: true };
664
+ }
665
+ }));
666
+ messages.push({ role: 'user', content: results });
667
+ // A view that streamed and had no call to finish it (a repeat of a call, for one) leaves the client.
668
+ closeViewStreams();
669
+ if (out && !out.endsWith('\n'))
670
+ emit('\n');
671
+ }
672
+ return out.trim();
673
+ }
674
+ }
675
+ // ---------- Mock ----------
676
+ const TEST_FILE = /(^|\/)(tests?|e2e|__tests__)\/|\.(spec|test)\.[cm]?[jt]sx?$/;
677
+ function isTestFile(path) {
678
+ return TEST_FILE.test(path);
679
+ }
680
+ export function areaOf(path) {
681
+ const m = /(?:^|\/)src\/([^/]+)\//.exec(path);
682
+ return m ? m[1].toLowerCase() : null;
683
+ }
684
+ function specArea(file) {
685
+ return (file.split('/').pop() ?? file).replace(/\.(spec|test)\.[cm]?[jt]sx?$/, '').toLowerCase();
686
+ }
687
+ function firstLine(s) {
688
+ return ((s ?? '')
689
+ .split('\n')
690
+ .map((l) => l.trim())
691
+ .find(Boolean) ?? '');
692
+ }
693
+ function expectedReceived(error) {
694
+ if (!error)
695
+ return null;
696
+ const clean = error.replace(/\u001b\[[0-9;]*m/g, '');
697
+ const m = /Expected[^:\n]*:\s*"?([^"\n]+)"?\s*\n\s*Received[^:\n]*:\s*"?([^"\n]+)"?/i.exec(clean);
698
+ return m ? { expected: m[1].trim(), received: m[2].trim() } : null;
699
+ }
700
+ function addedLines(f, max = 3) {
701
+ if (!f?.patch)
702
+ return [];
703
+ return f.patch
704
+ .split('\n')
705
+ .filter((l) => l.startsWith('+') && !l.startsWith('+++') && l.slice(1).trim())
706
+ .slice(0, max)
707
+ .map((l) => l.slice(1).trim());
708
+ }
709
+ function ago(fromIso, to = Date.now()) {
710
+ const min = Math.max(0, Math.round((to - Date.parse(fromIso)) / 60000));
711
+ if (min < 60)
712
+ return `${min} min`;
713
+ const h = Math.round(min / 60);
714
+ return h < 48 ? `${h} h` : `${Math.round(h / 24)} days`;
715
+ }
716
+ function hostOf(url) {
717
+ try {
718
+ return new URL(url).host;
719
+ }
720
+ catch {
721
+ return url;
722
+ }
723
+ }
724
+ const RISK_RULES = [
725
+ {
726
+ match: /discount|promo|coupon|loyalty|voucher/,
727
+ risk: {
728
+ title: 'Discount stacking order is undefined',
729
+ detail: 'Applying the loyalty discount before or after a promo code gives different totals. The ticket does not say which order wins or whether there is a cap.',
730
+ severity: 'high',
731
+ },
732
+ question: 'In what order do loyalty and promo discounts apply, and is there a maximum combined discount?',
733
+ ideas: [
734
+ 'Cart of $120 with promo LOYAL10 and loyalty 5%: assert the total for both orders and confirm which one the spec wants',
735
+ 'Promo code that already gives 100% off plus loyalty: total must not go below $0.00',
736
+ ],
737
+ },
738
+ {
739
+ match: /total|price|checkout|payment|currency|tax/,
740
+ risk: {
741
+ title: 'Rounding on stacked percentages',
742
+ detail: 'Two percentage discounts on the same subtotal produce fractional cents. Rounding per step versus once at the end changes the charged amount.',
743
+ severity: 'medium',
744
+ },
745
+ question: 'Do we round after each discount or once on the final total, and which rounding mode?',
746
+ ideas: [
747
+ 'Subtotal $19.99 with 10% then 5% off: assert the exact cents shown at checkout and charged at payment',
748
+ 'Checkout total shown on the page matches the amount sent to the payment provider',
749
+ ],
750
+ },
751
+ {
752
+ match: /loyalty|member|tier|account|guest|login/,
753
+ risk: {
754
+ title: 'Eligibility rules are unclear',
755
+ detail: 'The ticket does not say who counts as a loyalty member, whether guests qualify, or what happens when membership lapses mid-session.',
756
+ severity: 'medium',
757
+ },
758
+ question: 'Which customers are eligible, and what do guests see?',
759
+ ideas: [
760
+ 'Guest checkout with a promo code: no loyalty discount line appears',
761
+ 'Member whose tier expires between cart and checkout: total recalculates on the checkout page',
762
+ ],
763
+ },
764
+ {
765
+ match: /cart|basket|quantity/,
766
+ risk: {
767
+ title: 'Cart edits after discount',
768
+ detail: 'Changing quantity or removing items after a code is applied must recompute every discount line.',
769
+ severity: 'medium',
770
+ },
771
+ question: 'Should a code stay applied when the cart drops below its minimum?',
772
+ ideas: ['Apply a code, change quantity from 1 to 3: discount and total update without a reload'],
773
+ },
774
+ {
775
+ match: /upload|file|attachment|image/,
776
+ risk: { title: 'File limits not stated', detail: 'No size, type, or count limits are given for uploads.', severity: 'medium' },
777
+ question: 'What are the maximum file size and the allowed file types?',
778
+ ideas: ['Upload a file 1 byte over the limit: clear error, nothing stored'],
779
+ },
780
+ {
781
+ match: /email|notification|notify|sms/,
782
+ risk: { title: 'Notification content and timing', detail: 'The ticket does not define when the message is sent or what it contains.', severity: 'low' },
783
+ question: 'Which events trigger the notification, and is there a template?',
784
+ ideas: ['Trigger the event twice quickly: exactly one notification is sent'],
785
+ },
786
+ {
787
+ match: /permission|role|admin|access/,
788
+ risk: { title: 'Authorization gaps', detail: 'Roles that may perform the action are not listed.', severity: 'high' },
789
+ question: 'Which roles may do this, and what does a forbidden user see?',
790
+ ideas: ['Call the endpoint as a read-only user: 403 and no state change'],
791
+ },
792
+ ];
793
+ const GENERIC_RISKS = [
794
+ {
795
+ title: 'Acceptance criteria are not testable as written',
796
+ detail: 'The criteria describe intent, not observable results, so two testers could pass and fail the same build.',
797
+ severity: 'medium',
798
+ },
799
+ {
800
+ title: 'No error or empty states described',
801
+ detail: 'The ticket covers the happy path only. Invalid input, network failure, and empty data are not covered.',
802
+ severity: 'medium',
803
+ },
804
+ { title: 'No rollout or migration notes', detail: 'Existing data and in-flight sessions may behave differently after release.', severity: 'low' },
805
+ ];
806
+ const VAGUE = [
807
+ 'should work',
808
+ 'works correctly',
809
+ 'correctly',
810
+ 'properly',
811
+ 'as expected',
812
+ 'appropriate',
813
+ 'seamless',
814
+ 'user friendly',
815
+ 'etc',
816
+ 'and so on',
817
+ 'fast',
818
+ ];
819
+ // A comment that states a decision. The mock treats a question as answered when such a comment is about the same topic.
820
+ const DECISION = /\b(decided|agreed|confirmed|the answer is|we will|we'll|the rule is|should be|must be)\b/i;
821
+ /** How many different words of a rule a comment uses. The rule patterns are lists of topic words, so this is the topic overlap. */
822
+ function topicHits(rule, text) {
823
+ const re = new RegExp(rule.match.source, 'g');
824
+ return new Set([...text.matchAll(re)].map((m) => m[0])).size;
825
+ }
826
+ /**
827
+ * The comments that answer a question of a risk rule (mock model only), by rule. A comment states a decision and answers
828
+ * one question at most: the rule whose topic words it uses most. When two rules tie, it answers neither, because asking a
829
+ * question again costs less than dropping one that nobody answered. The newest comment wins for a rule (comments come
830
+ * newest first).
831
+ */
832
+ function answersFor(matched, req) {
833
+ const out = new Map();
834
+ for (const c of req.context?.comments ?? []) {
835
+ // A writer with no role in the repo can say anything, so such a comment never answers a question.
836
+ if (c.outsider)
837
+ continue;
838
+ // Only a sentence that is not a question can state a decision: "Which members should be eligible?" asks, it does not answer.
839
+ const decided = c.body.split(/(?<=[.!?])\s+|\n+/).filter((sentence) => !sentence.includes('?') && DECISION.test(sentence));
840
+ if (!decided.length)
841
+ continue;
842
+ const text = decided.join(' ').toLowerCase();
843
+ const scored = matched.map((rule) => ({ rule, hits: topicHits(rule, text) })).filter((x) => x.hits > 0);
844
+ const top = Math.max(0, ...scored.map((x) => x.hits));
845
+ const best = scored.filter((x) => x.hits === top);
846
+ if (best.length !== 1 || out.has(best[0].rule))
847
+ continue;
848
+ // The quote is the decision sentence that is about the question (the most topic words of its rule), so the line that says
849
+ // "this answers the question" shows the answer. A tie keeps the first.
850
+ const rule = best[0].rule;
851
+ const sentence = decided.reduce((a, b) => (topicHits(rule, b.toLowerCase()) > topicHits(rule, a.toLowerCase()) ? b : a));
852
+ const words = sentence.replace(/\s+/g, ' ').trim().split(' ');
853
+ // At most 15 words; a quote that ends a sentence keeps its full stop inside the quotation marks.
854
+ const quote = words
855
+ .slice(0, 15)
856
+ .join(' ')
857
+ .replace(/[,;:]$/, '');
858
+ const cut = words.length > 15 ? `${quote}...` : /[.!?]$/.test(quote) ? quote : `${quote}.`;
859
+ out.set(best[0].rule, { author: c.author, at: c.at.slice(0, 10), quote: cut });
860
+ }
861
+ return out;
862
+ }
863
+ function mockTicketVerdict(req) {
864
+ const t = req.ticket;
865
+ if (!t)
866
+ return infoVerdict('No ticket content was available to review.');
867
+ const text = `${t.title}\n${t.description}\n${t.acceptanceCriteria ?? ''}`.toLowerCase();
868
+ const matched = RISK_RULES.filter((r) => r.match.test(text));
869
+ const risks = matched.map((r) => r.risk);
870
+ for (const g of GENERIC_RISKS)
871
+ if (risks.length < 3)
872
+ risks.push(g);
873
+ const ac = (t.acceptanceCriteria ?? '').toLowerCase();
874
+ const vague = VAGUE.filter((v) => new RegExp(`\\b${v}\\b`).test(ac || text)).filter((v, _i, all) => !all.some((o) => o !== v && o.includes(v)));
875
+ // A question that a comment of the ticket already answers is not asked again; the summary names the comment (KAT-389).
876
+ const answered = answersFor(matched, req);
877
+ const open = matched.filter((r) => !answered.has(r));
878
+ const questions = [
879
+ ...vague.slice(0, 2).map((v) => `"${v[0].toUpperCase()}${v.slice(1)}": what does a tester see when it works? Give one example with real values.`),
880
+ ...open.map((r) => r.question),
881
+ ];
882
+ if (!t.acceptanceCriteria)
883
+ questions.unshift('There are no acceptance criteria. What must be true for this to be done?');
884
+ const ideas = matched.flatMap((r) => r.ideas).slice(0, 5);
885
+ if (!ideas.length)
886
+ ideas.push(`Happy path for "${t.title}" with valid input, then the same flow with each required field empty`);
887
+ const hits = req.codeHits ?? [];
888
+ const testsAtRisk = [...new Set(hits.filter((h) => isTestFile(h.path)).map((h) => h.path))];
889
+ const codePaths = [...new Set(hits.filter((h) => !isTestFile(h.path)).map((h) => h.path))].slice(0, 5);
890
+ const gaps = vague.length > 0 || !t.acceptanceCriteria;
891
+ const outQuestions = questions.slice(0, 5);
892
+ // The comment lists the questions, risks and test ideas itself. The summary holds only what those lines do not say.
893
+ const md = [];
894
+ // One line per answered question; a comment answers one question at most.
895
+ for (const a of answered.values()) {
896
+ md.push(`From the discussion: @${a.author} wrote on ${a.at}: "${a.quote}" That answers the question, so the review does not ask it again.`);
897
+ }
898
+ if (!t.acceptanceCriteria)
899
+ md.push('The ticket has no acceptance criteria, so two testers could pass and fail the same build.');
900
+ else if (vague.length)
901
+ md.push(`The acceptance criteria use ${vague
902
+ .slice(0, 2)
903
+ .map((v) => `"${v}"`)
904
+ .join(' and ')}. A tester cannot measure that, so each person reads it their own way.`);
905
+ if (testsAtRisk.length || codePaths.length) {
906
+ md.push('', '**Existing code and tests at risk**', ...testsAtRisk.map((p) => `- \`${p}\` (test)`), ...codePaths.map((p) => `- \`${p}\``));
907
+ }
908
+ return {
909
+ outcome: gaps ? 'needs_attention' : 'info',
910
+ headline: `${t.key}: ${risks.slice(0, 3).length} risks and ${outQuestions.length} open questions before this is ready to build`,
911
+ summary: md.join('\n'),
912
+ risk: risks.some((r) => r.severity === 'high') ? 'high' : 'medium',
913
+ findings: risks.slice(0, 3),
914
+ openQuestions: outQuestions,
915
+ testIdeas: ideas,
916
+ testsAtRisk,
917
+ confidence: 0.7,
918
+ };
919
+ }
920
+ function infoVerdict(headline, summary = headline) {
921
+ return { outcome: 'info', headline, summary, risk: 'low', findings: [], openQuestions: [], testIdeas: [], testsAtRisk: [], confidence: 0.5 };
922
+ }
923
+ // Infra problems: unreachable app, missing browser, crashed runner.
924
+ const ENV_ERROR = /ECONNREFUSED|ENOTFOUND|ERR_CONNECTION|net::ERR|socket hang up|\b50[234]\b|Executable doesn't exist|browserType\.launch|playwright install|Browser closed|ENOSPC|out of memory/i;
925
+ function mockTestVerdict(req) {
926
+ const r = req.report;
927
+ const diff = req.diff ?? [];
928
+ if (!r)
929
+ return infoVerdict('No test report was produced for this change.');
930
+ const changed = diff.filter((f) => !isTestFile(f.path));
931
+ const failed = r.cases.filter((c) => c.status === 'failed' || c.status === 'timedOut');
932
+ const flaky = r.cases.filter((c) => c.status === 'flaky');
933
+ const total = r.cases.length;
934
+ const table = `| Passed | Failed | Flaky | Skipped |\n|---|---|---|---|\n| ${r.passed} | ${r.failed} | ${r.flaky} | ${r.skipped} |`;
935
+ const areas = [...new Set(changed.map((f) => areaOf(f.path)).filter((a) => !!a))];
936
+ if (failed.length) {
937
+ const first = failed[0];
938
+ const culprit = changed.find((f) => areaOf(f.path) === specArea(first.file)) ?? changed[0];
939
+ const allEnv = failed.every((c) => ENV_ERROR.test(c.error ?? '') || c.title === 'Test run did not complete');
940
+ const selectorOnly = failed.every((c) => /locator\(|waiting for (locator|selector)|strict mode violation|resolved to 0 elements/i.test(c.error ?? '') && !expectedReceived(c.error));
941
+ const onlyTestsChanged = diff.length > 0 && changed.length === 0;
942
+ const outcome = allEnv ? 'environment_failure' : selectorOnly && onlyTestsChanged ? 'test_failure' : 'product_failure';
943
+ const findings = failed.slice(0, 5).map((c) => {
944
+ const er = expectedReceived(c.error);
945
+ const where = changed.find((f) => areaOf(f.path) === specArea(c.file)) ?? culprit;
946
+ const evidence = addedLines(where);
947
+ return {
948
+ title: `"${c.title}" failed${c.retries ? ` on all ${c.retries + 1} attempts` : ''}`,
949
+ detail: [
950
+ er ? `Expected ${er.expected}, got ${er.received}.` : firstLine(c.error),
951
+ where ? `Changed in this merge: ${where.path} (+${where.additions} -${where.deletions}).` : '',
952
+ evidence.length ? `New lines: ${evidence.map((l) => `\`${l}\``).join(', ')}` : '',
953
+ ]
954
+ .filter(Boolean)
955
+ .join(' '),
956
+ severity: outcome === 'product_failure' ? 'high' : 'medium',
957
+ file: c.file,
958
+ };
959
+ });
960
+ const er = expectedReceived(first.error);
961
+ const label = outcome === 'product_failure' ? 'Product failure' : outcome === 'environment_failure' ? 'Environment failure' : 'Test failure';
962
+ const headline = outcome === 'environment_failure'
963
+ ? `${failed.length} of ${total} tests could not run (environment): ${firstLine(first.error)
964
+ .replace(/^Error:\s*/, '')
965
+ .slice(0, 80)}`
966
+ : // The headline is the page title a tester reads: the file of the suspected change sits in the summary below it.
967
+ `"${first.title}" fails after this change${er ? `: expected ${er.expected}, got ${er.received}` : ''}`;
968
+ const md = [
969
+ `### localdost: ${label.toLowerCase()}`,
970
+ `**${first.title}** (\`${first.file}\`) failed${first.retries ? ` ${first.retries + 1} times in a row, so this is not flakiness` : ''}.`,
971
+ er ? `- Expected \`${er.expected}\`, got \`${er.received}\`.` : `- ${firstLine(first.error) || 'See the run for the error.'}`,
972
+ culprit ? `- Suspected cause: \`${culprit.path}\` (+${culprit.additions} -${culprit.deletions}) in this change.` : '',
973
+ flaky.length ? `- Also flaky (passed on retry): ${flaky.map((c) => `"${c.title}"`).join(', ')}.` : '',
974
+ '',
975
+ table,
976
+ ].filter((l) => l !== '');
977
+ const verdict = {
978
+ outcome,
979
+ headline: headline.slice(0, 160),
980
+ summary: md.join('\n'),
981
+ risk: outcome === 'product_failure' ? 'high' : 'medium',
982
+ findings,
983
+ openQuestions: [],
984
+ testIdeas: culprit ? [`Add a unit test next to ${culprit.path} that covers the case "${first.title}" checks end to end`] : [],
985
+ testsAtRisk: [...new Set(failed.map((c) => c.file))],
986
+ confidence: outcome === 'product_failure' && culprit && areaOf(culprit.path) === specArea(first.file) ? 0.85 : 0.6,
987
+ };
988
+ if (culprit)
989
+ verdict.suspectedCause = `${culprit.path} changed in this merge`;
990
+ return verdict;
991
+ }
992
+ if (flaky.length) {
993
+ return {
994
+ outcome: 'flaky',
995
+ headline: `All tests passed, but ${flaky.map((c) => `"${c.title}"`).join(', ')} needed a retry`,
996
+ summary: [
997
+ `### localdost: passed with flaky tests`,
998
+ ...flaky.map((c) => `- \`${c.file}\` "${c.title}" passed on retry ${c.retries}. Saved to memory as flaky.`),
999
+ '',
1000
+ table,
1001
+ ].join('\n'),
1002
+ risk: 'low',
1003
+ findings: flaky.map((c) => ({
1004
+ title: `Flaky: ${c.title}`,
1005
+ detail: firstLine(c.error) || 'Failed once, passed on retry.',
1006
+ severity: 'low',
1007
+ file: c.file,
1008
+ })),
1009
+ openQuestions: [],
1010
+ testIdeas: [],
1011
+ testsAtRisk: flaky.map((c) => c.file),
1012
+ confidence: 0.75,
1013
+ };
1014
+ }
1015
+ const files = [...new Set(r.cases.map((c) => c.file))];
1016
+ const uncovered = areas.filter((a) => !files.some((f) => specArea(f).includes(a)));
1017
+ return {
1018
+ outcome: 'pass',
1019
+ // Skipped tests did not pass: "All 138 passed" with 53 skipped hid them (dogfood ledger).
1020
+ headline: `${r.skipped ? `${r.passed} passed, ${r.skipped} skipped, 0 failed` : `All ${total} selected tests passed`}${areas.length ? ` for ${areas.join(', ')}` : ''}`,
1021
+ summary: [
1022
+ `### localdost: passed`,
1023
+ // A skipped test did not run (KAT-402): the count is the tests that passed, and the skipped ones are said apart.
1024
+ `Ran ${r.skipped ? r.passed : total} tests in ${files.map((f) => `\`${f}\``).join(', ') || 'the suite'} against ${r.baseUrl ?? 'the base URL'}.${r.skipped ? ` ${r.skipped} ${r.skipped === 1 ? 'was' : 'were'} skipped (${r.skipped === 1 ? 'it does' : 'they do'} not apply to this project).` : ''}`,
1025
+ uncovered.length ? `- No spec covers ${uncovered.map((a) => `\`src/${a}/\``).join(', ')} directly; only smoke tests ran for it.` : '',
1026
+ '',
1027
+ table,
1028
+ ]
1029
+ .filter((l) => l !== '')
1030
+ .join('\n'),
1031
+ risk: uncovered.length ? 'medium' : 'low',
1032
+ findings: uncovered.map((a) => ({
1033
+ title: `No spec for ${a}`,
1034
+ detail: `Changes under src/${a}/ were only covered by smoke tests.`,
1035
+ severity: 'medium',
1036
+ })),
1037
+ openQuestions: [],
1038
+ testIdeas: uncovered.map((a) => `Add a ${a}.spec.ts that covers the main ${a} flow`),
1039
+ testsAtRisk: [],
1040
+ confidence: 0.8,
1041
+ };
1042
+ }
1043
+ function latestMerge(runs) {
1044
+ return [...(runs ?? [])].filter((r) => r.kind === 'post_merge' && r.trigger).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
1045
+ }
1046
+ function mockOutageVerdict(req) {
1047
+ const ev = req.event;
1048
+ const probe = ev?.probe;
1049
+ const target = probe ? hostOf(probe.url) : (ev?.title ?? 'production');
1050
+ const symptom = probe ? (probe.status ? `HTTP ${probe.status}` : (probe.error ?? 'no response')) : 'status page degraded';
1051
+ const merge = latestMerge(req.recentRuns);
1052
+ const pr = merge?.trigger?.pr;
1053
+ const cause = merge ? `${pr ? `PR #${pr.number} ` : ''}"${merge.trigger?.title}" merged ${ago(merge.createdAt)} before the first failed probe` : undefined;
1054
+ const brokeTests = merge?.verdict?.outcome === 'product_failure';
1055
+ const md = [
1056
+ `### Outage: ${target}`,
1057
+ `- Symptom: ${symptom}${probe ? ` on ${probe.url}, ${probe.consecutiveFailures} checks in a row` : ''}.`,
1058
+ cause
1059
+ ? `- Leading suspect: ${cause}.${brokeTests ? ' Its post-merge run already reported a product failure.' : ''}`
1060
+ : '- No merges in the last 24 h. Check infrastructure and third-party status pages first.',
1061
+ '- Next: roll back the suspect or check the deploy logs, then watch for the recovery notice.',
1062
+ ];
1063
+ const verdict = {
1064
+ outcome: 'needs_attention',
1065
+ headline: `${target} is down (${symptom})${pr ? `; suspect PR #${pr.number}` : ''}`,
1066
+ summary: md.join('\n'),
1067
+ risk: 'high',
1068
+ findings: [{ title: `${target} failing`, detail: `${symptom}${probe?.error ? `: ${probe.error}` : ''}`, severity: 'high' }],
1069
+ openQuestions: cause ? [] : ['Was there a deploy or config change outside GitHub in the last hour?'],
1070
+ testIdeas: ['Add a probe with expectText on the page body so a blank 200 also counts as down'],
1071
+ testsAtRisk: [],
1072
+ confidence: cause ? (brokeTests ? 0.7 : 0.55) : 0.25,
1073
+ };
1074
+ if (cause)
1075
+ verdict.suspectedCause = cause;
1076
+ return verdict;
1077
+ }
1078
+ function mockCiVerdict(req) {
1079
+ // drop ANSI colours and the ISO timestamps GitHub prefixes to every log line
1080
+ const log = (req.logTail ?? '').replace(/\u001b\[[0-9;]*m/g, '').replace(/^\d{4}-\d\d-\d\dT[\d:.]+Z ?/gm, '');
1081
+ const lines = log.split('\n');
1082
+ const pick = (re) => lines.find((l) => re.test(l))?.trim();
1083
+ let outcome;
1084
+ let decisive;
1085
+ if ((decisive = pick(/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|rate limit|No space left|out of memory|Killed|exit code 137|npm ERR! network|Could not resolve host|503 Service|Executable doesn't exist|browserType\.launch/i)))
1086
+ outcome = 'environment_failure';
1087
+ else if ((decisive = pick(/\bflaky\b|passed on retry|\d+ flaky/i)))
1088
+ outcome = 'flaky';
1089
+ else if ((decisive = pick(/waiting for (locator|selector)|resolved to 0 elements|strict mode violation|toBeVisible/i)) && !/Expected[^:]*:/.test(log))
1090
+ outcome = 'test_failure';
1091
+ else if ((decisive = pick(/Expected[^:]*:|AssertionError|toHaveText|toEqual|TypeError|ReferenceError|\d+ failed/i)))
1092
+ outcome = 'product_failure';
1093
+ else {
1094
+ outcome = 'needs_attention';
1095
+ decisive = pick(/error/i);
1096
+ }
1097
+ const er = expectedReceived(log);
1098
+ const failingTest = pick(/✘|\[\w+\] ›|\d+\) .*spec\.[jt]s/);
1099
+ const wf = req.event?.ci?.workflow ?? 'CI';
1100
+ const next = {
1101
+ product_failure: 'Fix the code; the test caught a real regression.',
1102
+ test_failure: 'Update the selector or the test; the app looks fine.',
1103
+ environment_failure: 'Re-run the job; nothing in the code needs to change.',
1104
+ flaky: 'Re-run and quarantine the flaky spec.',
1105
+ needs_attention: 'Read the full log; the tail is not conclusive.',
1106
+ pass: '',
1107
+ info: '',
1108
+ };
1109
+ return {
1110
+ outcome,
1111
+ headline: `${wf} failed: ${outcome.replace('_', ' ')}${er ? ` (expected ${er.expected}, got ${er.received})` : ''}`.slice(0, 160),
1112
+ summary: [
1113
+ `### CI triage: ${outcome.replace('_', ' ')}`,
1114
+ decisive ? `Decisive line:\n\n\`\`\`\n${decisive.slice(0, 300)}\n\`\`\`` : 'No decisive line in the log tail.',
1115
+ failingTest ? `- Failing test: ${failingTest.slice(0, 200)}` : '',
1116
+ `- Next: ${next[outcome]}`,
1117
+ ]
1118
+ .filter(Boolean)
1119
+ .join('\n'),
1120
+ risk: outcome === 'product_failure' ? 'high' : 'medium',
1121
+ findings: decisive ? [{ title: 'Decisive log line', detail: decisive.slice(0, 300), severity: outcome === 'product_failure' ? 'high' : 'medium' }] : [],
1122
+ openQuestions: [],
1123
+ testIdeas: [],
1124
+ testsAtRisk: failingTest ? [failingTest.slice(0, 120)] : [],
1125
+ confidence: outcome === 'needs_attention' ? 0.3 : 0.65,
1126
+ };
1127
+ }
1128
+ function mockDigestVerdict(req) {
1129
+ const runs = req.recentRuns ?? [];
1130
+ const tests = runs.filter((r) => r.report);
1131
+ const failing = runs.filter((r) => r.verdict && ['product_failure', 'test_failure', 'environment_failure', 'needs_attention'].includes(r.verdict.outcome));
1132
+ const tickets = runs.filter((r) => r.kind === 'ticket_review');
1133
+ const outages = runs.filter((r) => r.kind === 'outage');
1134
+ const flaky = req.memory.filter((m) => m.kind === 'flaky_spec');
1135
+ const passed = tests.reduce((n, r) => n + (r.report?.passed ?? 0), 0);
1136
+ const failed = tests.reduce((n, r) => n + (r.report?.failed ?? 0), 0);
1137
+ const md = [
1138
+ `### Daily digest: ${req.watch.name}`,
1139
+ failing.length ? '**Needs you today**' : '**Nothing needs you today.**',
1140
+ ...failing.slice(0, 5).map((r) => `- ${r.verdict?.headline}`),
1141
+ '',
1142
+ `- ${tests.length} test runs: ${passed} passed, ${failed} failed.`,
1143
+ `- ${tickets.length} tickets reviewed${tickets.length ? `: ${tickets.map((r) => r.trigger?.ticket?.key ?? r.trigger?.title).join(', ')}` : ''}.`,
1144
+ `- ${outages.length} probe incidents.`,
1145
+ flaky.length
1146
+ ? `- Flaky specs on file: ${flaky
1147
+ .slice(0, 5)
1148
+ .map((m) => m.text)
1149
+ .join('; ')}.`
1150
+ : '- No flaky specs on file.',
1151
+ ];
1152
+ return {
1153
+ outcome: failing.length ? 'needs_attention' : 'info',
1154
+ headline: failing.length
1155
+ ? `${failing.length} open problems; first: ${failing[0].verdict?.headline}`.slice(0, 160)
1156
+ : `Quiet day: ${tests.length} runs, ${tickets.length} tickets, ${outages.length} incidents`,
1157
+ summary: md.join('\n'),
1158
+ risk: failing.length ? 'medium' : 'low',
1159
+ findings: failing.slice(0, 5).map((r) => ({ title: r.verdict.headline, detail: `${r.kind} run ${r.id}`, severity: r.verdict.risk })),
1160
+ openQuestions: [],
1161
+ testIdeas: [],
1162
+ testsAtRisk: flaky.map((m) => m.text.replace(/\s\(.*\)$/, '')),
1163
+ confidence: 0.9,
1164
+ };
1165
+ }
1166
+ const SECRET = /(ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-ant-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|password\s*[:=]\s*\S+)/i;
1167
+ const INJECTION = /ignore (all |any )?(previous|prior|above) instructions|disregard (the|your) (rules|instructions)|you are now/i;
1168
+ const KIND_WORDS = {
1169
+ run_tests: ['run tests', 'rerun'],
1170
+ check_run: ['check run', 'status check'],
1171
+ pr_comment: ['pr comment', 'comment on pr', 'comment on pull'],
1172
+ ticket_comment: ['ticket', 'jira', 'linear', 'issue'],
1173
+ ticket_status: ['ticket status', 'change status', 'move the ticket', 'transition'],
1174
+ push_notify: ['notify', 'page me', 'push', 'wake'],
1175
+ open_pr: ['open pr', 'open a pr', 'pull request', 'open prs'],
1176
+ revert: ['revert', 'roll back', 'rollback'],
1177
+ mcp_tool: ['tool', 'mcp'],
1178
+ slack_post: ['slack', 'post to channel'],
1179
+ report_edit: ['report', 'test plan', 'edit the page'],
1180
+ file_defect: ['defect', 'file a bug', 'open an issue', 'file an issue'],
1181
+ issue_create: ['defect', 'file a bug', 'create an issue', 'open an issue', 'file an issue'],
1182
+ case_retire: ['retire a case', 'retire the case', 'obsolete test'],
1183
+ };
1184
+ function chunks(text) {
1185
+ return text.match(/\S+\s*|\s+/g) ?? [text];
1186
+ }
1187
+ /** Plain reply for a ticket or PR tool result, for the rule-based chat and the CLI fallbacks (KAT-98). */
1188
+ export function replyForPlannedTool(name, value) {
1189
+ const v = (value && typeof value === 'object' ? value : {});
1190
+ if (typeof v.error === 'string')
1191
+ return v.error;
1192
+ const list = (x) => (Array.isArray(x) ? x.map(String) : []);
1193
+ if (name === 'get_ticket') {
1194
+ const last = v.lastReview;
1195
+ return [
1196
+ `${v.key}: ${v.title}${v.type ? ` (${v.type}${v.priority ? `, ${v.priority}` : ''})` : ''}.`,
1197
+ v.acceptanceCriteria ? 'It has acceptance criteria.' : 'It has no acceptance criteria.',
1198
+ last ? `My last review: ${last.headline ?? 'no headline'}.` : 'I have not reviewed it yet. Say "check it" and I will.',
1199
+ ].join(' ');
1200
+ }
1201
+ if (name === 'design_tests') {
1202
+ if (v.duplicate)
1203
+ return `I am already designing tests for ${v.title}. The run card updates when it finishes.`;
1204
+ if (v.status === 'queued' || v.status === 'running')
1205
+ return `I started designing tests for ${v.title}. The run card updates when it finishes.`;
1206
+ return `${v.line ?? 'Test design finished'} for ${v.ticket}. They are drafts: a person marks a case ready on the Test cases page.`;
1207
+ }
1208
+ if (name === 'review_ticket') {
1209
+ if (v.duplicate)
1210
+ return `I am already reviewing ${v.title}. The result lands on the run card.`;
1211
+ if (v.status === 'queued' || v.status === 'running')
1212
+ return `I started a review of ${v.title}. The run card updates when it finishes.`;
1213
+ const risks = list(v.risks);
1214
+ const qs = list(v.openQuestions);
1215
+ return [
1216
+ `${v.headline ?? `Reviewed ${v.title}`}.`.replace(/\.\.$/, '.'),
1217
+ risks.length ? `Top risk: ${risks[0].replace(/^\w+: /, '')}.` : '',
1218
+ qs.length ? `First open question: ${qs[0]}` : '',
1219
+ v.comment === 'pending_approval'
1220
+ ? 'My comment draft waits for your approval.'
1221
+ : v.comment === 'executed'
1222
+ ? 'I posted the review as a comment.'
1223
+ : v.comment === 'none'
1224
+ ? ''
1225
+ : 'Your rules did not let me post the comment.',
1226
+ ]
1227
+ .filter(Boolean)
1228
+ .join(' ');
1229
+ }
1230
+ if (name === 'review_pr') {
1231
+ if (v.duplicate)
1232
+ return `I am already reviewing that: ${v.title}. The result lands here and on the PR when it finishes.`;
1233
+ return `Queued a ${v.kind === 'pr_check' ? 'PR check' : 'post-merge review'} for ${v.title}. The result lands here, and on the PR when there is one.`;
1234
+ }
1235
+ return JSON.stringify(value).slice(0, 300);
1236
+ }
1237
+ export class MockLlm {
1238
+ async analyze(req) {
1239
+ switch (req.task) {
1240
+ case 'post_merge':
1241
+ case 'pr_check':
1242
+ return mockTestVerdict(req);
1243
+ case 'ticket_review':
1244
+ return mockTicketVerdict(req);
1245
+ case 'outage':
1246
+ return mockOutageVerdict(req);
1247
+ case 'ci_failure':
1248
+ return mockCiVerdict(req);
1249
+ case 'digest':
1250
+ return mockDigestVerdict(req);
1251
+ }
1252
+ }
1253
+ async score(t) {
1254
+ const desc = t.description ?? '';
1255
+ const ac = t.acceptanceCriteria ?? '';
1256
+ let s = 15;
1257
+ if (t.title.trim().split(/\s+/).length >= 4)
1258
+ s += 10;
1259
+ if (desc.length > 60)
1260
+ s += 10;
1261
+ if (desc.length > 200)
1262
+ s += 15;
1263
+ if (ac.length > 0)
1264
+ s += 15;
1265
+ if (ac.length > 120)
1266
+ s += 10;
1267
+ if (t.type)
1268
+ s += 5;
1269
+ if (t.priority)
1270
+ s += 5;
1271
+ if (t.labels?.length)
1272
+ s += 5;
1273
+ const vague = VAGUE.filter((v) => new RegExp(`\\b${v}\\b`, 'i').test(`${desc}\n${ac}`)).length;
1274
+ s -= Math.min(20, vague * 7);
1275
+ return Math.max(0, Math.min(100, s));
1276
+ }
1277
+ async review(action, watch) {
1278
+ if (SECRET.test(action.body) || SECRET.test(action.title))
1279
+ return { ok: false, escalate: true, reason: 'The draft contains what looks like a credential.', category: 'secret_or_personal_data' };
1280
+ if (INJECTION.test(action.body))
1281
+ return { ok: true, escalate: true, reason: 'The draft repeats instruction-like text from untrusted content.', category: 'untrusted_instruction' };
1282
+ if (action.kind === 'revert' || action.kind === 'open_pr')
1283
+ return { ok: true, escalate: true, reason: `A ${action.kind.replace('_', ' ')} changes the codebase; a person should confirm.`, category: 'destructive' };
1284
+ const mustNot = (watch.mustNot ?? '').toLowerCase();
1285
+ const hit = KIND_WORDS[action.kind].find((w) => mustNot.includes(w));
1286
+ if (hit)
1287
+ return { ok: true, escalate: true, reason: `The watch says it must not: "${watch.mustNot.trim()}".`, category: 'must_not_rule' };
1288
+ // Length is a code check in gate() (rules.ts deterministicReview), not a judgement of the reviewer.
1289
+ return { ok: true, escalate: false, reason: 'No credentials, no rule conflicts, evidence included.' };
1290
+ }
1291
+ async chat(req, onDelta, onTool) {
1292
+ const text = req.text.toLowerCase();
1293
+ const ctx = { watch: req.watch, reportId: req.reportId };
1294
+ const call = async (name, input = {}) => {
1295
+ const v = await callChatTool(name, input, ctx, onTool);
1296
+ // A lookup the turn already made (the CLI tool loop before a fallback) comes back wrapped.
1297
+ return v && typeof v === 'object' && v.repeated ? v.result : v;
1298
+ };
1299
+ const memoryReply = async () => {
1300
+ const items = (await call('get_memory'));
1301
+ return items.length
1302
+ ? `I remember ${items.length} things. ${items
1303
+ .slice(0, 4)
1304
+ .map((m) => `${m.kind.replace('_', ' ')}: ${m.text}`)
1305
+ .join('. ')}.`
1306
+ : 'My memory for this dot is empty.';
1307
+ };
1308
+ let reply;
1309
+ const planned = plannedFirstTool(req.text, req.watch);
1310
+ // A ticket or PR link answers first (KAT-98), then a review request; both win over a page edit.
1311
+ if (planned) {
1312
+ reply = replyForPlannedTool(planned.name, await call(planned.name, planned.input));
1313
+ }
1314
+ else if (reviewIntent(req.text)) {
1315
+ const target = reviewIntent(req.text);
1316
+ const res = (await call('review_pr', target));
1317
+ // A lead sends the review to the member whose job it is and says so (KAT-405).
1318
+ const asked = res.routedTo && res.ask ? `Asked ${res.routedTo} to ${res.ask}. ` : '';
1319
+ reply = res.error
1320
+ ? res.error
1321
+ : res.choices
1322
+ ? `${res.note ?? 'More than one dot fits.'} ${res.choices.map((c) => c.name ?? c.repo).join(', ')}.`
1323
+ : res.duplicate
1324
+ ? `${asked}I am already reviewing that: ${res.title}. The result lands here and on the PR when it finishes.`
1325
+ : `${asked}Queued a ${res.kind === 'pr_check' ? 'PR check' : 'post-merge review'} for ${res.title}. The result lands here, and on the PR when there is one.`;
1326
+ }
1327
+ else if (req.watch?.kind === 'lead' && /\b(?:team|how are we|how is everyone|does anything need me)\b/.test(text)) {
1328
+ // A lead's question about the whole team: one status word and the member that set it (KAT-405).
1329
+ const res = (await call('team_status'));
1330
+ reply = res.error ?? `The team is ${String(res.status ?? 'unknown').toLowerCase()}. ${res.reason ?? ''}`.trim();
1331
+ }
1332
+ else if (req.reportId && /\b(add|append|edit|update|change|propose|rewrite)\b/.test(text)) {
1333
+ // Rule-based page edit: append the request as an open task so the approval flow can be tried offline.
1334
+ const page = getReport(req.reportId);
1335
+ const item = req.text.replace(/^.*?\b(add|append)\b\s*/i, '').trim() || req.text.trim();
1336
+ const markdown = `${(page?.markdown ?? '').replace(/\s+$/, '')}\n\n- [ ] ${item}\n`;
1337
+ const res = (await call('propose_report_edit', { markdown, note: `Add task: ${item.slice(0, 60)}` }));
1338
+ reply =
1339
+ res.error ??
1340
+ (res.status === 'pending_approval'
1341
+ ? 'I drafted the edit. It adds the item as an open task; approve it on the card to save the page.'
1342
+ : res.status === 'executed'
1343
+ ? 'Done, the page is updated.'
1344
+ : 'Your rules did not let me change the page.');
1345
+ }
1346
+ else if (/^\s*(?:what|which|do you|did you|show|list|tell me)\b[^.!]*\b(?:remember|memory|memories)\b/.test(text)) {
1347
+ // A question about what the dot remembers reads the notes; it is not a note to keep.
1348
+ reply = await memoryReply();
1349
+ }
1350
+ else if (/\b(remember|note that|keep in mind)\b/.test(text)) {
1351
+ const body = req.text.replace(/^.*?\b(remember|note that|keep in mind)\b\s*(that\s*)?/i, '').trim() || req.text;
1352
+ const res = (await call('add_memory', { text: body, kind: /flaky/.test(text) ? 'flaky_spec' : 'note' }));
1353
+ reply = res.saved ? `Saved. I will remember: "${body}".` : (res.error ?? 'I could not save that.');
1354
+ }
1355
+ else if (/\b(re-?run|run (the )?(quick )?(tests|smoke|suite)|test again|run again)\b/.test(text)) {
1356
+ const grep = /\b(smoke|quick)\b/.test(text) ? '@smoke' : /checkout/.test(text) ? 'checkout' : undefined;
1357
+ const res = (await call('rerun_tests', { grep }));
1358
+ const asked = res.routedTo && res.ask ? `Asked ${res.routedTo} to ${res.ask}. ` : '';
1359
+ reply = res.queued
1360
+ ? `${asked}Queued a run of ${grep === '@smoke' ? 'the quick tests' : grep ? `the ${grep} tests` : 'the tests'} for ${res.dot}. I will report back here and on your phone if anything fails.`
1361
+ : (res.error ?? res.reason ?? 'Nothing queued.');
1362
+ }
1363
+ else if (asksForComposedView(req.text)) {
1364
+ // A question that needs several blocks, or numbers no tool gives directly: the rows, then a composed view (KAT-366).
1365
+ reply = (await mockComposedReply(req.text, call)) ?? 'I could not build that view.';
1366
+ }
1367
+ else if (/\bpass(?:ing)?[\s-]?rate\b|\bhow (?:are|is) the tests (?:doing|going)\b/.test(text)) {
1368
+ const res = (await call('pass_rate'));
1369
+ reply = res.error ?? res.finding ?? res.note ?? 'No test run finished in the last 7 days.';
1370
+ }
1371
+ else if (/\bflaky\b.*\bspecs?\b|\bspecs?\b.*\bflaky\b|\bflaky tests?\b.*\bby\b/.test(text)) {
1372
+ const res = (await call('flaky_by_spec'));
1373
+ reply = res.error ?? res.finding ?? res.note ?? 'No test passed only on a retry.';
1374
+ }
1375
+ else if (/\b(test\s+cases?|cases)\b/.test(text)) {
1376
+ const requirement = /\b(?!TC-)[A-Z][A-Z0-9_]{0,19}-\d{1,9}\b/.exec(req.text)?.[0];
1377
+ const res = (await call('list_cases', requirement ? { requirement } : {}));
1378
+ reply = res.error
1379
+ ? res.error
1380
+ : !res.count
1381
+ ? `No test cases${requirement ? ` for ${requirement}` : ''} yet. Add them on the Test cases page.`
1382
+ : `${res.count} test ${res.count === 1 ? 'case' : 'cases'}${requirement ? ` for ${requirement}` : ''}, with the latest result of each.`;
1383
+ }
1384
+ else if (/\b(?:ongoing work|what(?:'s| is) going on|what(?:'s| is) happening)\b/.test(text)) {
1385
+ // The three lookups the chat prompt names for "what is the ongoing work": open PRs, runs working now, drafts that wait.
1386
+ const prs = (await call('open_prs'));
1387
+ const running = (await call('runs_in_progress'));
1388
+ const waiting = (await call('pending_approvals'));
1389
+ const n = (c) => Number(c.count ?? 0);
1390
+ reply = `${n(prs)} pull request${n(prs) === 1 ? ' is' : 's are'} open. ${n(running)} run${n(running) === 1 ? ' is' : 's are'} running now. ${n(waiting)} draft${n(waiting) === 1 ? ' waits' : 's wait'} for your approval.`;
1391
+ }
1392
+ else if (/\bdefects?\b|\bsince the last merge\b/.test(text)) {
1393
+ const res = (await call('recent_defects', /\blast merge\b/.test(text) ? { sinceLastMerge: true } : {}));
1394
+ const first = res.defects?.[0];
1395
+ reply = res.error
1396
+ ? res.error
1397
+ : !res.count
1398
+ ? 'No defects found in that time.'
1399
+ : `${res.count} ${res.count === 1 ? 'problem' : 'problems'} found: ${first?.headline ?? first?.trigger ?? 'a failed run'}${res.count > 1 ? `, and ${res.count - 1} more` : ''}.`;
1400
+ }
1401
+ else if (/\b(approv\w*|waiting|pending|inbox)\b/.test(text)) {
1402
+ const res = (await call('pending_approvals'));
1403
+ reply = res.error
1404
+ ? res.error
1405
+ : !res.count
1406
+ ? 'Nothing waits for your approval.'
1407
+ : `${res.count} waiting for your approval: ${(res.approvals ?? [])
1408
+ .slice(0, 3)
1409
+ .map((a) => a.title)
1410
+ .join('; ')}.`;
1411
+ }
1412
+ else if (/\b(prod|production|up|down|outage|probe|status)\b/.test(text)) {
1413
+ const res = (await call('probe_status'));
1414
+ if (!res.probes.length)
1415
+ reply = res.note ?? 'No probe results yet.';
1416
+ else {
1417
+ const down = res.probes.filter((p) => p.down === true || p.status === 'down' || Number(p.consecutiveFailures ?? 0) >= 3);
1418
+ const describe = (p) => `${hostOf(String(p.url ?? 'probe'))}${(p.lastStatus ?? p.status) ? ` (last ${p.lastStatus ?? p.status})` : ''}`;
1419
+ reply = down.length
1420
+ ? `${down.length} of ${res.probes.length} probes are failing: ${down.map(describe).join(', ')}.`
1421
+ : `Yes, production is up. ${res.probes.length === 1 ? 'The probe' : `All ${res.probes.length} probes`} passed the last check.`;
1422
+ }
1423
+ }
1424
+ else if (/\b(ticket|jira|linear|story|refinement)\b/.test(text)) {
1425
+ const rows = (await call('list_tickets_reviewed', { limit: 3 }));
1426
+ if (!rows.length)
1427
+ reply = 'I have not reviewed any tickets yet.';
1428
+ else {
1429
+ const t = rows[0];
1430
+ reply = [
1431
+ `Latest: ${t.headline ?? t.ticket}.`,
1432
+ t.risks?.length ? `Top risk: ${t.risks[0]}.` : '',
1433
+ t.openQuestions?.length ? `First open question: ${t.openQuestions[0]}` : '',
1434
+ t.commentStatus === 'pending_approval' ? 'My comment is waiting for your approval in the Inbox.' : '',
1435
+ ]
1436
+ .filter(Boolean)
1437
+ .join(' ');
1438
+ }
1439
+ }
1440
+ else if (/\b(memory|flaky|remember\?)\b/.test(text)) {
1441
+ reply = await memoryReply();
1442
+ }
1443
+ else {
1444
+ // default covers "what broke today?" and general status questions
1445
+ const all = (await call('list_runs', { sinceHours: 24, limit: 20 }));
1446
+ const problems = all.runs.filter((r) => r.outcome && !['pass', 'info'].includes(r.outcome));
1447
+ if (!all.count)
1448
+ reply = 'Nothing ran in the last 24 hours.';
1449
+ else if (!problems.length)
1450
+ reply = `Nothing broke today. ${all.count} runs in the last 24 hours, all clean.`;
1451
+ else {
1452
+ const lines = problems.slice(0, 3).map((r) => `${r.headline ?? r.trigger}${r.pendingApprovals ? ' (waiting on your approval)' : ''}`);
1453
+ // A real Markdown list, one item per line: the chat shows it as a list, not as one paragraph with "1." inline.
1454
+ reply = `${problems.length} of ${all.count} runs in the last 24 hours need attention:\n\n${lines.map((l, i) => `${i + 1}. ${l}`).join('\n')}`;
1455
+ }
1456
+ }
1457
+ for (const c of chunks(reply)) {
1458
+ onDelta(c);
1459
+ await new Promise((r) => setTimeout(r, 12));
1460
+ }
1461
+ return reply;
1462
+ }
1463
+ }
1464
+ //# sourceMappingURL=llm.js.map