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
package/docs/API-V2.md ADDED
@@ -0,0 +1,532 @@
1
+ # API v2 contract (parity build)
2
+
3
+ Additions to the v1 API in `SPEC.md`. Server and client tracks build against this file at the same time. Types go in `src/types.ts` (add fields only) and the client imports them from there through the `@shared` alias.
4
+
5
+ ## Setup readiness
6
+
7
+ `GET /api/setup` returns:
8
+
9
+ ```ts
10
+ interface SetupStatus {
11
+ demo: boolean;
12
+ services: {
13
+ key: 'model' | 'github' | 'jira' | 'linear' | 'slack' | 'push' | 'publicUrl' | 'sandbox' | 'voice';
14
+ label: string; // "Model", "GitHub", ...
15
+ state: 'ready' | 'demo' | 'missing' | 'error';
16
+ detail: string; // "Claude CLI (claude-sonnet-5-5)", "PAT mode", "Not configured"
17
+ docs: string; // relative docs link, e.g. "docs/SETUP.md#github"
18
+ }[];
19
+ }
20
+ ```
21
+
22
+ `docs` names the guide section for the service. The app opens it in the in-app docs at `#/docs/<name>#<anchor>`, for example `docs/SETUP.md#public-url` opens `#/docs/setup#public-url`.
23
+
24
+ ## Test engines
25
+
26
+ `GET /api/setup/engines` returns the workspace default engine and the two engines behind it (KAT-224):
27
+
28
+ ```ts
29
+ interface TestEnginesView {
30
+ default: 'auto' | 'playwright' | 'aivision'; // what a dot without runner.engine uses
31
+ source: 'env' | 'saved' | 'auto' | 'unset'; // env: LOCALDOST_TEST_ENGINE. auto: follows which engines are ready. unset: nothing chosen, AI-Vision only
32
+ envName?: 'LOCALDOST_TEST_ENGINE'; // with source env
33
+ engines: {
34
+ key: 'playwright' | 'aivision';
35
+ label: string; // "Playwright e2e", "AI-Vision"
36
+ runs: string; // what it runs, in one sentence
37
+ state: 'ready' | 'missing';
38
+ detail: string; // "Ready. ..." or "Missing: ..."
39
+ defaultOn: boolean; // the workspace default includes it
40
+ }[];
41
+ }
42
+ ```
43
+
44
+ `PUT /api/setup/engines` saves the default and answers with the new view. The body is `{ "playwright": boolean, "aivision": boolean }` (both on is `auto`) or `{ "auto": true }` to follow which engines are ready again. It answers 400 when both are false or the body is wrong, and 409 while `LOCALDOST_TEST_ENGINE` is set. It follows the rules of the other Setup writes: a token, or this machine only.
45
+
46
+ ## Docs
47
+
48
+ The app shows README.md and the guides in docs/ on the Docs screen (`#/docs`). Both routes are read-only and resolve only names on a fixed list in `src/routes/docs.ts`, so no request reaches another file.
49
+
50
+ | Route | Returns |
51
+ |---|---|
52
+ | `GET /api/docs` | `{ docs: { name, title, group }[] }`, the guides present in this install. `group` is `guide` or `reference` |
53
+ | `GET /api/docs/:name` | `{ name, title, group, markdown, updatedAt }`. `name` is lower case (`setup`, `github`); `SETUP` and `setup.md` work too. 404 for a name off the list or a file missing from the install |
54
+
55
+ ## Dots (Watch additions)
56
+
57
+ `Watch` gains: `role: 'product-area' | 'qa-lead' | 'release-guard' | 'ticket-reviewer' | 'prod-watcher' | 'custom'`, `instructions: string`, `mascot: 'pip' | 'nova' | 'bolt' | 'moss'`, `connections: string[]` (connection ids), `slackChannel?: string`.
58
+ `GET /api/roles` returns role presets `{ key, label, description, defaults: Partial<Watch> }[]`, Product area first. `defaults.stages` holds the six stage switches of the preset ([STAGES.md](STAGES.md#63-stage-presets)).
59
+ `POST /api/dots` and `PATCH /api/dots/:id` accept every Watch field except `archived`, which only the two routes below set. A paired device holds a role (see [Joining a team server](#joining-a-team-server)): a member may call `PATCH` with a body of only `{ "paused": true }` or `{ "paused": false }`, an editor may send any field, and any other body from a member is a 403 `phone_blocked`.
60
+ `DELETE /api/dots/:id?mode=archive|delete` removes a dot, and `POST /api/dots/:id/restore` brings an archived one back. No paired device may call either, whatever its role: both answer 403 with code `phone_blocked` and need the admin token, or a loopback browser on a server with no token. Both modes close the dot's pending approvals as rejected, each with a reason (`closedReason` on the approval and a step on its run).
61
+
62
+ - `mode=archive` (the default) sets `archived` and `paused`, stops the dot (pollers, probes, webhooks, and the digest skip it), drops its queued jobs, cancels its running runs, and keeps every run, case, memory item, and file. The dot leaves `GET /api/dots` unless `?archived=1` is set. Archiving twice is fine. Answers `{ ok, archived: true, approvalsClosed, jobsStopped, runsCancelled }`. Retrying a run of an archived dot answers 409.
63
+ - `mode=delete` also needs `confirmName` set to the dot name (spaces at either end do not count), or it answers 400 and removes nothing. It then removes the dot and its runs, approvals, outbox items, memory, chat, test cases and results, events, jobs, links, probe state, report pages, poll cursors, and the artifact folder of every run. Rows of other dots and shared records (repos, connections) stay. It works on an archived dot too. Answers `{ ok, deleted: true, approvalsClosed, jobsStopped, runsCancelled, rows, artifactFolders }`.
64
+ - `POST /api/dots/:id/restore` answers the dot. It is paused only if it was paused before it was archived. 404 for an unknown dot, 409 for a dot that is not archived.
65
+ `runner.setup` holds install and build steps; `runner.command` is the test command, and both routes answer 400 when it holds a shell operator (`&&`, `|`, `;`, redirects). `POST /api/runner/try` runs the setup and command once and `GET /api/runner/try/:id` returns the result, see [SANDBOX.md](SANDBOX.md#try-it).
66
+ `runner.passEnv?: string[]` names host variables for the test login of the app under test (KAT-361): each starts with `LOCALDOST_TEST_`, at most 20, saved trimmed and without repeats, and both routes answer 400 for any other name. A run sees each one that is set as `TEST_<rest>`. `GET /api/runner/pass-env?names=A,B` answers `{ names: [{ name, as, set }] }` for valid names (`as` is what the test sees, `set` is whether the server has it) and never a value; 400 for a name outside the rule. See [SANDBOX.md](SANDBOX.md#test-login).
67
+ `TestReport` adds `notRun?: { stage: 'setup' | 'command' | 'report'; reason: string; exitCode?: number | null }` and `setup?: { steps, cache: 'hit' | 'miss' | 'none', durationMs }`.
68
+
69
+ ## Dot health
70
+
71
+ `GET /api/dots/:id/health` (KAT-394) answers the one question at the top of a dot's page: does this dot need me? It reads rows the dot already stores in one pass. It calls no model and no tracker. The server keeps the answer 30 s per dot, and a run, an approval, or a status change of the dot drops it. 404 for an unknown dot.
72
+
73
+ `GET /api/dots/health` answers the same for every dot of the list in one request: an object by dot id (`{ [dotId]: DotHealth }`, archived dots only with `?archived=1`). It reads the same kept answers, so a dot has one answer in the list and on its page. The dot list takes its status word and its last-check time from it, and so do the Dots page header count and the sidebar dots; no row asks for its own health.
74
+
75
+ `GET /api/room` answers the Room page (0.15.0, `src/room.ts`): `leads` (each lead with its status, `statusReason`, `digestHour`, `digestEvery` and release call), `dots` (every dot that is not a lead or archived, with `status`, `statusReason` (the health reason in the words of the Board: "needs a person" for the last merge, "drafts to review" for Inbox drafts), `reasonRun` (the latest merge as `{ ref, open }`, only when the reason says the last merge needs a look), `now`, `next`, `nextCount`, `waitsForLimit`, `inbox`, `today` and `probes`), `jobs` (one card per job with `column` set to `queued`, `working`, `waiting` or `done`, its `ref`, the in-app link `open`, and `details` with run, job and draft ids and the stage), and `doneToday`. The same item queued more than once for a dot is one `queued` row (KAT-408): `count` says how many jobs it stands for (only from 2 up), `details.jobIds` lists them, `ref` carries the newest title, `at` the oldest turn, and `held` stays only when every one of them is held. `nextCount` counts those items, and `waitsForLimit` says how many of them wait for the dot's daily model cap (`held: "model_cap"`). A `waiting` card is one run and its `drafts` counts the Inbox drafts of that run, and that run is not in `done` as well; the page counts drafts, in its subtitle and in the Waits for you column alike, so the number it shows is the sum of `drafts` over the `waiting` jobs. Only a PR or a ticket groups, by the repo or tracker in its key (`testops-fe#1292`, `product#4666`, `KAT-408`); a merge, a failed build and a site check have no such key, so each stays its own row. A job a person asked for (`requestedBy`) and a `pr_check` of another head commit never group with another job, since each of them runs and posts. On a server in sandbox mode no site check runs, so every probe reads `unknown` with no check time. It reads the job queue, the runs, the live Inbox drafts and the site check states, makes no model or tracker call, and keeps one answer for 2 seconds.
76
+
77
+ ```
78
+ {
79
+ status: 'failing' | 'needs_you' | 'healthy' | 'not_watching',
80
+ statusReason: string,
81
+ watching: {
82
+ on: boolean,
83
+ state: 'on' | 'paused' | 'archived' | 'sign_in' | 'github' | 'nothing' | 'site_off' | 'site_sandbox',
84
+ note: string | null,
85
+ lastCheck: { kind: JobKind | 'site', ref: string | null, title: string, at: string, runId?: string } | null,
86
+ trial: { day: number, days: number } | null,
87
+ postsWait: boolean
88
+ },
89
+ tiles: { review, bugs, checks, release, site, tests, tickets, ai }, // each: { title, state, line, action, facts }
90
+ suggestion: { rule, text, action } | null,
91
+ team?: TeamHealth, // set on a Lead QA dot only, see Lead QA dot (KAT-405)
92
+ builtAt: string
93
+ }
94
+ ```
95
+
96
+ Status, first match wins:
97
+
98
+ - `not_watching`: the dot cannot watch: it is paused or archived, the AI sign-in of the default model expired, GitHub turned the sign-in down or does not answer, a poll alarm is up, nothing brings a PR to it, or there is nothing to watch (`statusReason` says "Nothing to watch yet." for a dot with no repo, tracker or site). It comes first, so a QA site that is down shows on its own tile and the dot still reads Not watching. No check in 24 hours is not a cause: a quiet repo on a weekend is normal, and `watching.note` carries it. The sentence names the trouble and never an order; `statusFix` (the Open Setup link, for the admin) says where it is repaired.
99
+ - `failing`: a site check is down, or the latest merge into the default branch found a confirmed bug: a verdict of `product_failure` or `test_failure`, or failed tests with no verdict that are not an environment failure or flaky. A merge with a verdict of `needs_attention` is not a confirmed bug: it reads `needs_you`.
100
+ - `needs_you`: a draft waits in the Inbox, a ticket review of the last 7 days left open questions, or the latest merge has a `needs_attention` verdict.
101
+ - `healthy`: none of the above.
102
+
103
+ `watching.state` says whether the dot watches and what stops it: `on`, `paused`, `archived`, `sign_in` (the AI sign-in), `github` (GitHub does not answer, or nothing brings a PR), `nothing`, or, for a dot whose only source is a QA site, `site_off` (the Prod watcher is off) or `site_sandbox` (sandbox mode): no site check runs, so nothing can come in. The Watching strip heading is "Watching" for `on` and "Not watching: <reason>" otherwise, so it agrees with the headline word, and it carries no site health. `watching.note` is one line for a dot that watches but has nothing recent: "Quiet: no check in 24 h", "Checks started, but none of them finished.", "The first check is still running.", or the first-check sentence ("No checks yet. The first site check runs within a minute." only where site checks run; a site-only dot where they do not run is Not watching and has no note). It is `null` while a check under a day old exists and for a dot that does not watch. A check that failed, was interrupted, is still running or could not run its tests is not a check; a site check is.
104
+
105
+ `watching.trial` is the first week of the dot (`shadowWeek`): `{ day, days }` while it runs, `null` after. `postsWait` is true while the PR comment rule asks first.
106
+
107
+ Tiles. `state` is `ready` (the `line` says it), `not_set_up` (nothing feeds the tile: the line is `Not set up` and the action is `{ label: 'Set up', setup }`, never a zero), or `error` (the tile could not be read and the others still answer). `action` holds one of `href` (an app link), `run: 'site_check'`, `setup: 'site' | 'tracker' | 'repo' | 'cap'`, `focus: 'model_usage'`, or `draft` (open the chat with that text). A tile with nothing to open has `action: null`, never a button that goes nowhere.
108
+
109
+ | Tile | Reads | Not set up when |
110
+ |---|---|---|
111
+ | `review` | Drafts waiting and not expired: count and the age of the oldest. | Never. Zero reads "Nothing is waiting". |
112
+ | `bugs` | Bugs the dot filed (executed drafts of `file_defect`, and of `issue_create` with a bug signature, which is how the defect flow drafts a new bug; and bugs a check filed on its own, from runs of any age): open, and new in the last 7 days. A GitHub issue the dot reads as closed is not open; a Jira or Linear bug counts as open. | The dot has no tracker and no repo to file a bug in. |
113
+ | `checks` | Checks of the last 7 days (PR checks, merge checks, CI failures, reruns) by outcome: passed, bug, needs a look (only the latest merge says "1 merge needs a look"; a CI failure or a rerun says "1 needs a look", and an older merge a newer merge replaced is not counted), could not check. A check that skipped its tests on purpose (Run tests off, a docs only change) and ended with a pass counts as passed. Running checks are listed apart. | The dot has no repo whose tests it runs (`setup: 'repo'`) and no check this week. |
114
+ | `release` | The nearest open milestone of the dot's requirements (traceability): tickets tested of all. The nearest is the soonest dated milestone from today on that still has a ticket not passing, else the latest past one, else the first in the tracker order. A GitHub tracker limited to milestones only looks at those. | No requirement has a milestone. |
115
+ | `site` | The dot's site checks (`probes`): up, slow, failing or down, response time and time since the last check. `action.run` is `site_check`, and it is `null` for a paused or archived dot and on a sandbox, where the server refuses the check. A dot whose only source is the site, with site checks off or in a sandbox, gets the reason as the line ("Turn on site checks to start watching this site.") and no action. | The dot has no site check. |
116
+ | `tests` | Tests of the last 7 days: flaky (passed on a retry) and needing a fix (the newest result on the default branch failed). A test is counted once. | The dot has no repo whose tests it runs (`setup: 'repo'`) and no result this week. |
117
+ | `tickets` | Ticket reviews of the last 7 days: tickets reviewed and tickets whose newest review left questions. | The dot has no tracker and no review. |
118
+ | `ai` | AI calls used today against the daily cap (`74 of 150 AI calls used`). | The dot has no daily cap. |
119
+
120
+ Suggestion, one item, from the first rule that matches (`rule` names it):
121
+
122
+ 1. `release_without_cases`: a ticket of the nearest open release with no run, no spec, and no test case. The first one in tracker order. The text names a Jira or Linear ticket by its key (`PROD-1234 is in the release and has no test cases yet.`) and a GitHub ticket by its quoted title, since `owner/repo#115` tells a reader little. The action is `draft`.
123
+ 2. `bug_came_back`: a bug filed earlier was drafted again in the last 7 days (same bug signature, so the tracker no longer showed the first issue as open). Drafts of `file_defect` and `issue_create` both count. The newest draft wins. The text puts the title in quotes (`The bug "Total is wrong" came back after it was fixed.`).
124
+ 3. `flaky_test`: a flaky test with 3 or more flaky or failed results this week. Most results first, then newest, then path and title. The text names the test by its title, with no file path (`The test "cart" was flaky 3 times this week: it failed, then passed on a retry.`; a test with a real failure says `was flaky or failed`).
125
+ 4. `merge_look`: the latest merge ended with a `needs_attention` verdict (`The last merge needs a look.`). The action is `href` to its run.
126
+ 5. `ai_calls_low`: 80 percent or more of the daily calls used. The action is `setup: 'cap'`. A member sees the text and no button, and the card says who can act.
127
+
128
+ `POST /api/dots/:id/site-check` runs the dot's site checks now (the QA site tile's Check now). It answers `{ ok, results: [{ url, ok, latencyMs, error? }] }`. 409 for a paused or archived dot, a dot with no site check, and a sandbox. A paired device may not call it.
129
+
130
+ ## Lead QA dot (KAT-405)
131
+
132
+ A lead is a dot that names member dots and reads the rows they store. The build spec is [LEAD-DOT.md](LEAD-DOT.md), and [SETUP.md](SETUP.md#lead-qa-dot) says how to make one. A lead holds no repos, tracker, site checks, runner, review options, or stage switches of its own.
133
+
134
+ `Watch` gains two fields. `POST /api/dots` and `PATCH /api/dots/:id` take both.
135
+
136
+ - `kind?: 'dot' | 'lead'`: unset means `dot`. It is set on `POST` and never changes: a `PATCH` that names the other kind answers 400 `A dot cannot change kind. Make a new lead.`
137
+ - `memberIds?: string[]`: the dots a lead names, 1 to 8, in the order the team panel shows them. A `PATCH` that leaves it out keeps the members; one that sends it replaces the list.
138
+
139
+ A save of `kind: 'lead'` answers 400 with one of these, and saves nothing:
140
+
141
+ | Refusal | When |
142
+ |---|---|
143
+ | `kind must be dot or lead.` | `kind` is any other value |
144
+ | `Only a lead has members.` | `memberIds` is set on a dot |
145
+ | `A lead has no repos, tracker or site checks of its own. Set them on a member.` | the body holds `repo`, `repos`, `tracker`, `probes`, `statusPages`, `baseUrl`, a `runner` with more than a `mode`, `review`, or `stages` |
146
+ | `A lead names 1 to 8 dots.` | no members, more than 8, an id twice, or an id that is not a string |
147
+ | `No dot with id <id>.` | a member that does not exist |
148
+ | `<name> is a lead. A lead cannot be a member of another lead.` | a member that is a lead |
149
+ | `<name> is archived. Restore it first.` | an archived dot added as a member; a member archived later keeps its place |
150
+
151
+ A dot is in at most one lead, archived leads included, so a restore never gives a dot two leads. A save that adds a dot another lead holds answers 409 `<name> is already in <lead>. Remove it there first.` `GET /api/dots` and `GET /api/dots/:id` return `leadId` on a member (the id of the live lead that names it) and `memberIds` on a lead. A lead matches no webhook event.
152
+
153
+ The chat message of a team digest (`GET /api/messages`, the live `update` event) carries `update.details`: Markdown with the counts and each member's own digest, which the chat draws under a Details fold below the message. The message text and the Slack post do not hold it, since the chat never renders raw HTML and Slack cannot fold.
154
+
155
+ `GET /api/dots/:id/team` answers `TeamHealth` for a lead: the team word and one row per member. It reads the same kept answers as `GET /api/dots/:id/health`, so a member has one answer in its row and on its page. 404 for an unknown dot, 409 for a dot that is not a lead. The team panel reads the team again every minute and when a member publishes. The Open button of a row goes to the member's page, or to the `action.href` of the member's own health answer when that button is a link (an app route such as `#/setup`, or the tracker address of a bug already filed, which opens in a new tab); an action that runs, sets up or drafts needs the member's page.
156
+
157
+ `GET /api/dots/:id/health` for a lead answers a `DotHealth` whose `status` and `statusReason` are the team's, whose `tiles` are all `not_set_up`, whose `suggestion` is `null`, and whose `team` holds the `TeamHealth`. Its `watching.state` is `on`, or `nothing` when the team reads `not_watching`. A lead that a person paused or archived reads `not_watching` with its own reason and `watching.state` `paused` or `archived`. The client draws the team panel whenever `team` is set, and the dot list and the sidebar read the one status word from it.
158
+
159
+ ```
160
+ TeamHealth = {
161
+ status: 'failing' | 'needs_you' | 'healthy' | 'not_watching',
162
+ statusReason: string, // "Regression Runner: the last merge found a bug."
163
+ members: {
164
+ id: string, name: string,
165
+ job: string, // "Ticket review, Test cases": the stages that are on
166
+ status: HealthStatus,
167
+ away?: 'paused' | 'archived', // a member a person stopped; it does not count toward the word
168
+ need: string, // the member's own statusReason, or "Nothing needs you."
169
+ action: HealthAction | null // the member's own button; null when nothing needs a person
170
+ }[],
171
+ inbox: number, // live Inbox items across the members
172
+ builtAt: string
173
+ }
174
+ ```
175
+
176
+ The team word is the worst status among the members that are not away, in this order: `failing`, `needs_you`, `not_watching`, `healthy`. A member a person paused or archived has `away` set, shows `Paused` or `Archived` as its need, and does not count. A team with no member left to count reads `not_watching`: `Every dot of the team is paused or archived.`, or `The team has no dots. Add one in the lead settings.` for an empty list. `statusReason` names the first member in list order that holds the word, with that member's reason after the colon and `(and N more)` when others hold it too (`Regression Runner: the last merge found a bug.`). A team where every counted member is healthy reads `Nothing needs you.` A healthy member that has a suggestion shows the suggestion text as its need.
177
+
178
+ `GET /api/dots/:id/release?milestone=` answers the release call of a lead for one milestone, or for the nearest open one when `milestone` is left out. It reads stored rows and calls no model. 404 for an unknown dot, 409 for a dot that is not a lead.
179
+
180
+ ```
181
+ ReleaseCall = {
182
+ milestone: string,
183
+ answer: 'ready' | 'ready_with_risk' | 'not_ready' | 'no_tickets',
184
+ summary: string, // "14 of 19 requirements passing, 1 failing, 3 not tested"
185
+ counts: ReadinessCounts,
186
+ reasons: { level: 'blocker' | 'risk', text, ref?, href?, memberId, memberName? }[],
187
+ milestones: string[], // every milestone of the members' trackers, for the picker
188
+ rule: string, // the rule that set the answer, in one sentence
189
+ builtAt: string
190
+ }
191
+ ```
192
+
193
+ The answer is the first rule that matches, and `rule` carries its sentence. `no_tickets`: no requirement of the members has this milestone (`reasons` is empty). `not_ready`: a requirement of the milestone is failing, a bug is open against one, or the latest after-merge check of a member that runs them found a bug. `ready_with_risk`: a requirement is not tested, partly tested or out of date, a bug report for a ticket of the milestone waits in a member's Inbox, a test of those tickets was flaky 3 or more times this week, or a member that nobody paused is `not_watching`, so its data may be old. `ready`: none of the above. `reasons` lists the blockers first. The milestone is the nearest open one of the members' trackers when `milestone` is left out. When no milestone is open because every requirement passes, it is the newest milestone of the team's rows (the latest dated one, else the last in natural order), so a team that passes everything reads `ready` with `19 of 19 requirements passing`, not `no_tickets`. `no_tickets` with an empty `milestone` is left for a team whose requirements name no milestone at all. The daily digest asks for the open milestone only, so a milestone that is done never reads "Release 2026.11 is ready" every morning. A reason may carry `openLabel` (`See them` for a group of tickets, `Review` for a bug report that waits in an Inbox): the words of its button in a chat answer, where the page keeps Open. The server keeps an answer 30 s per lead and milestone, and a run, an approval, or a save of a member drops it. The call is read-only: it posts and files nothing, and it never says release or hold, the readiness words only.
194
+
195
+ `POST /api/dots/:id/digest` makes the team digest now. It answers 202 `{ ok: true, jobId }`, or 200 `{ ok: true, duplicate: true }` when a digest of the same lead was already made in the same 10-minute slot. 409 for a dot that is not a lead, and for a paused or archived lead. The digest is text built by code from the members' rows. It makes no model call and counts nothing against a daily cap. It replies in the chat of the person who asked and nowhere else: the daily digest at `digestHour` is the only one that goes to the lead's `slackChannel`. A paired device may call this route.
196
+
197
+ `GET /api/dots/:id/delete-preview` answers `{ otherRunItems, onlyMemberOf }` for any dot: the test run items of other dots that point at this dot's test cases and go with them (`{ dotId, dotName, items }[]`), and the lead that has this dot as its only member (`{ id, name }`, else `null`). The delete dialog reads it before it asks for the name. `DELETE /api/dots/:id?mode=delete` answers 409 for a dot that is the only member of a lead, since a lead always keeps 1 to 8 members. Its summary adds `leadsChanged` (the leads that lost this member) and `otherRunItems`. An archive keeps the member in its lead's list.
198
+
199
+ Other routes that need a repo, tracker, site check, or runner answer 409 `<name> is a lead. Ask a member for this.` for a lead: `PUT` and `POST /api/dots/:id/review`, `GET /api/dots/:id/pulls`, `POST /api/dots/:id/cases`, `POST /api/dots/:id/site-check`, and `POST /api/runner/try` with its `dotId`.
200
+
201
+ Team data. A ticket, its cases, its PR links and its results can sit on different members, so these reads cover every member of the lead:
202
+
203
+ - `casesForTicket` and the case lookups of a PR check, of a merge, and of a test run read the cases of all members, and a result lands on the case's own dot. Traceability (`GET /api/dots/:id/traceability`) and the release call read the members' links, runs, and test results. The ticket reader of a member with no tracker uses the first member that has one.
204
+ - The PR review of a member reads the tickets a PR links when their repo is the dot's own tracker or repo, or the tracker repo of another dot of the team (PR Reviewer has no tracker of its own).
205
+ - A bug report that two members find is one Inbox item. Both runs go to `meta.sources` of the draft (`{ watchId, runId, at, pr?, head?, task?, bugSig? }[]`), and the item sits on the member that found it first. `GET /api/approvals` adds `foundBy` (`Found by PR Reviewer and Regression Runner.`) and `sources` (`{ dotName, runId, at, bugSig }[]`) to an item with more than one source. Rejecting it counts for every source, and a bug already filed or rejected by any member is not drafted again.
206
+
207
+ Chat. A lead's chat takes the questions of the members' dots. `AskOrigin` of a web ask gains `scope: 'lead'` and `leadId`. The read tools take a member (its id or name) or `all`, and `all` is the default. A request that is an action for a member (`rerun_tests`, `review_pr`, `review_ticket`, `design_tests`) goes to the member that does that job, under that member's rules, cap, dedupe, and deploy wait. The answer says so ("Asked PR Reviewer to review PR 412.") and the result replies in the lead's chat with the member's name. One turn sends at most 3 such requests. The `team_status` and `release_call` tools are built by code from stored rows, with no model call, `team_status` answers with a `table` block headed Team health (Dot, Job, Status, Needs a person), and `release_call` with the `release_call` block; see [CHAT-UI.md](CHAT-UI.md#component-registry).
208
+
209
+ Phone. A paired device may call `POST /api/dots/:id/digest`. It may not save or delete a lead, since those need the admin token like any dot save.
210
+
211
+ ## Repos
212
+
213
+ A dot holds a set of repos with a role each, see [MULTI-REPO.md](MULTI-REPO.md#1-data-model-workspace-repos-and-per-dot-repo-sets).
214
+ - `Watch.repos?: { repo: 'owner/name', role: 'app' | 'test' | 'docs', defaultBranch?, pathFilters?, prScope?, runTests?, deployEnvironment?, waitForDeploySec?, deployUrlMatch?, test?, targets? }[]`. `deployUrlMatch` (app repos only) is a host or an origin, with no path, that picks which deployment address the tests of a merge run against; an admin setting. `runTests: false` (app repos only, KAT-361) reviews the repo's pull requests and merges and runs no tests for them; only `false` is stored, and the routes answer 400 for a non-boolean or for a test or docs repo, see [SANDBOX.md](SANDBOX.md#run-tests-per-repo). `repo` carries the switch of the first app repo too. `prScope` (app repos only) is `{ authors?, team?, skipDrafts?, skipBots?, linkedIssueLabels? }`, see [SETUP.md](SETUP.md#pr-scope); `repo` carries the first app repo's scope too. `POST /api/dots` and `PATCH /api/dots/:id` check it: ids are `owner/name`, each repo once, a test repo needs an app repo, `targets` name app repos of the dot. `repo` is rewritten from the first app repo on every save. A body with only `repo` (the single-repo form) replaces the first app repo in its place and keeps the others in their order; `repo: null` drops it. `repo.defaultBranch` on a dot you read is the branch a run reads for the first app repo now, so a form that sends it back changes nothing. A repo the workspace lacks is added on save; with GitHub connected, a repo the connection cannot read is a 400, and a GitHub error while checking it is a 502 (nothing is saved). `nightlyRun` is `{ on, time?, days? }` or `null` (KAT-390): the nightly test run of the dot's Tests repo against its Base URL. `on` is required and a boolean, `time` is `HH:MM` on the server's clock (default `02:00`), `days` is a non-empty list of whole numbers from 0 (Sunday) to 6 (Saturday) (default `[1, 2, 3, 4, 5]`); a save stores all three, with the days sorted, and `null` turns it off. Turning it on needs a Tests repo and a Base URL (a demo dot needs none) or the answer is a 400 with the reason; a Lead QA dot cannot have one. A save that changes `on`, `time` or `days` sets `nightlyLastSlot` (ISO, read-only: a client cannot write it) to the latest slot up to now, so slots before the save never run, and a save that sends the same schedule leaves it alone. A save that takes the last Tests repo away turns a stored schedule off. Each slot queues one job of kind `nightly_run` with the dedupe key `nightly:<dot id>:<slot ISO>`; its run has `kind: "nightly_run"`, `stage: "regression"`, and a trigger of source `schedule` with `nightly: { slot, lateMs }`. See [SETUP.md](SETUP.md#nightly-test-run). `waitForPreviewSec` is a whole number from 0 to 3600: a PR check whose tests will run waits up to that many seconds for the deployment of its head to be reported, then tests the preview or the base URL, see [SETUP.md](SETUP.md#pr-tests-without-a-preview). `testRepoOnPr` and `testRepoOnMerge` are `true`, `false` or `null` (the default, on): whether the dot runs its Tests repo on app PRs and on merges, see [SETUP.md](SETUP.md#tests-repo); any other value is a 400. A repo with `role: "test"` takes `test: { framework, dir?, ref?, command?, setup?, baseUrlEnv?, passEnv?, smokeGrep?, label?, areas?, on? }` (fields in [SANDBOX.md](SANDBOX.md#tests-repo)); the routes answer 400 for a command with a shell operator, a `dir` outside the checkout, a `ref` that is not `default`, `match_branch` or `{ fixed }`, a `passEnv` name that does not start with `LOCALDOST_TEST_`, a `baseUrlEnv` name that looks like a credential, and an `areas` grep text that is not a valid regular expression. The report of a run that ran a Tests repo carries `testRepos: [{ repo, name, label?, ref, refRule, sha, note?, selection, baseUrl?, passed, failed, flaky, skipped, notRun?, notRunOnPurpose? }]`, and the file of each of its cases is `owner/name:path`. `waitForDeploySec` (a whole number from 0 to 3600; `0` and `null` are off) and `deployEnvironment` (one line of up to 100 characters, `null` clears it) make a merge run wait for a successful deploy of the merge commit to that GitHub environment, see [SETUP.md](SETUP.md#waiting-for-the-deploy); a wait above 0 needs an environment, once the save is applied, or the answer is a 400. Both are stored as `null` when off, and an editor who changes either gets a 403 with the code `runner_admin_only` and the error "Ask your localdost admin to change the deploy wait.", as for the runner settings. An app repo takes its own `deployEnvironment` and `waitForDeploySec` in `repos` (KAT-395, role `app` only, `repo` carries those of the first app repo): a field left out uses the dot's, `waitForDeploySec: 0` stops the wait for that repo only, and `null` or an empty `deployEnvironment` puts a field back on the dot's value. The routes answer 400 for seconds outside 0 to 3600, a name that is not text of up to 100 characters on one line, the fields on a test or docs repo, and a repo that waits with no environment of its own or the dot's. They are the admin's like the dot's two fields: an editor who changes one gets the same 403, with `fields` such as `repos[acme/shop].deployEnvironment`. A body with only `repo` that leaves the two fields out keeps those of the first app repo. All the deployments of one commit in the environment are one set: the merge is deployed when every one succeeded, and a deployment that ended in `failure` or `error` ends the wait with no test run, no verdict and no defect (the held run is `cancelled`, `cancelledBy: "the failed deploy"`, with the step `The QA deploy of this merge failed, so the tests did not run.`; the job is `done` with `deployWait.outcome: "failed"`). The tests of a merge whose deployment gave an `environment_url` run against it, kept as `deployWait.url` on the job; a Base URL with a path keeps that path in it (a deployment of the bare site gives `https://web.qa.example.com/app`, not `https://web.qa.example.com`). A run held for the deploy has `status: "interrupted"`, `interruptReason: "deploy_wait"`, and `deployWait: { environment, sha, since, until }`. `prTestTarget` is `base`, `preview-only`, or `none` (default `base`): what a PR check tests against when no preview of the PR is known, see [SETUP.md](SETUP.md#pr-tests-without-a-preview); any other value is a 400. `shadowWeekDays` is save-only: a whole number from 0 to 30 that holds `pr_comment` and `check_run` at `ask` for that many days, and the dot stores `shadowWeek: { until, kinds, saved }`; `0` on a `PATCH` ends the week now, and a `PATCH` of `rules` for a held kind drops that kind from the week.
215
+ - `Watch.runner.copyFiles?: string[]`: relative file paths of the working folder that are copied into the worktree of a run before setup (KAT-293). At most 20; saved trimmed and without repeats. `POST /api/dots` and `PATCH /api/dots/:id` answer 400 for an absolute path, `..`, a path in `.git`, a folder, or a value that is not a list of strings. `TestReport.hint` is a sentence for a person when a worktree run did not pass and untracked dotfiles were left out; it holds no file name. `TestReport.hintFiles` lists up to 5 of those names and is shown on the run page only. A `.git` path is refused in any letter case. See [SANDBOX.md](SANDBOX.md#copy-files).
216
+ - `Watch.tracker.labels.require?: string[]` (GitHub tracker, KAT-353): an issue is reviewed only when it carries one of these labels, in any case; the first one also goes on the defects the dot files. At most 20 labels of 50 characters, saved trimmed and without repeats; an empty list drops the key. `POST /api/dots` and `PATCH /api/dots/:id` answer 400 for a value that is not a list of label names. A GitHub tracker whose `repos` is set files every defect in the first repo, unless the bug was found in another repo the tracker names, and the draft's `meta.project` says which. With `labels.require`, the draft's `meta.labels` also holds the defect label and `meta.issueType` the defect type. See [TRACKERS.md](TRACKERS.md#github-issues).
217
+ - `Watch.testBranches?: string[]`: branches besides the default branch whose merges and pushes run post-merge tests (KAT-292). Plain branch names, at most 20, trimmed and without repeats on save; `[]` or unset means the default branch only. `POST /api/dots` and `PATCH /api/dots/:id` answer 400 for a name with a `refs/` prefix, a wildcard, `..`, or anything else that is not a plain branch name. See [SANDBOX.md](SANDBOX.md#test-branches).
218
+ - `Watch.pollGithub?: boolean`: poll GitHub for this dot (KAT-352). `POST /api/dots` sets it to true when the server has no webhook route and the body names none; both routes answer 400 for a value that is not true or false. `GET /api/dots/:id` and `GET /api/dots` return `listening` (`'webhooks' | 'polling' | 'none'`), `pollSec` when polling, and `listeningMissing` when `none`, see [GITHUB.md](GITHUB.md#polling).
219
+ - `GET /api/dots/:id` and `GET /api/dots` return `repos[]` with `owner`, `name`, `defaultBranch`, `scan`, `intake`, and `lastEventAt` per repo. A dot saved before repo sets shows its `repo` as one app entry.
220
+ - GitHub events match a dot when the event repo is in its set. App repos get `pr_check`, `post_merge`, and `ci_failure`; a test repo gets `ci_failure` only, and a docs repo no job, until the test repo and scan jobs exist. The test repos of a dot run inside the `pr_check` and `post_merge` jobs of its app repos, after the app repo's own tests. Each repo is polled once, whatever the number of dots that hold it.
221
+ - `GET /api/repos` lists workspace repos with their scan state and the dots that use each. `GET /api/repos/options?q=` lists repos the GitHub connection can see, with `added`. `POST /api/repos` `{ repos: ['owner/name'] }` adds repos after the same check (201, or 207 with per-repo `results` carrying `code` 400 or 502). `DELETE /api/repos/:owner/:name` removes one (409 with `dots` while a dot holds it). `POST /api/repos/:owner/:name/scan` with `{ full?: boolean }` queues a `repo_scan` job and answers 202 with `{ repo, scan, queued, jobId?, dot, full? }`: a dot that watches the repo owns the job, and with none the job runs without a dot. `full: true` rescans even when the stored profile is at the same commit. `queued: false` means a scan of the repo was already queued in the last 10 minutes. The repo's `scan.state` follows the job: `queued`, `scanning`, then `ready` (with `sha`, `at`, `mode`) or `error` (with `error`); the repo live event carries each change. The Setup page's GitHub repo picker reads and writes the same list, with the same check; a repo it cannot read drops out of the pick.
222
+ - `GET /api/repos/:owner/:name/profile` returns what the newest scan found, in a short form: `{ repo, sha, scannedAt, summary, apps, routeCount, testFrameworks, specCount, smokeTag? }`. 404 until a scan of the repo has finished. In demo mode, `demo/shop-web` is scanned from `demo/sample-app` at the fixed sha `demo`, with no GitHub.
223
+ - `POST /api/dots/:id/review` and `GET /api/dots/:id/pulls` take an optional `repo` (`owner/name`, an app repo of the dot). Unset: the first app repo.
224
+
225
+ ## Onboarding
226
+
227
+ The first-run flow (`#/welcome`, KAT-257) saves its progress in the settings store, so a reload or another browser resumes at the same step.
228
+
229
+ - `GET /api/onboarding` returns `{ progress, firstRun, dots, demo, sandbox, server, demoRepo? }`. `progress` is null until the flow opens. `firstRun` is true while the flow is open: no dot exists and the flow is neither `done` nor `dismissed`, or the progress is `active` (a reload after Create dot lands back in the flow). The app then opens `#/welcome` instead of the Dots list, and shows the Getting started link. `sandbox` is true under `LOCALDOST_SANDBOX=1`. `server` is `{ token, publicUrl, webhookSecret, webhookRoute, webhookSeen, githubMode, delivery, pollEverySec, remote }`: whether `LOCALDOST_TOKEN` is set, the public URL as an origin only (or null), whether the GitHub webhook secret is set, `webhookRoute` (an https public URL and the secret, so GitHub can send webhooks here), `webhookSeen` (`{ at, event }` of the last signed GitHub delivery, or null when none arrived in the last 7 days), `githubMode` (`app`, `pat`, or `demo`), `delivery` (`webhooks` only when `webhookRoute` is true and `webhookSeen` is set, else `polling`), the poll interval, and whether the caller is off the server's machine.
230
+ - `PUT /api/onboarding` replaces the progress: `{ status, step, steps, demo?, mode?, repos?, repoSetup?, tracker?, githubIssues?, tests?, testsByRepo?, slack?, rules?, dot?, run?, checklistHidden?, startedAt?, finishedAt? }`. `status` is `new`, `active`, `done`, or `dismissed`. `step` and the keys of `steps` are `welcome`, `delivery`, `model`, `github`, `repo`, `tracker`, `tests`, `slack`, `rules`, `dot`, `value`, and each `steps` value is `done` or `skipped`. `mode` is `team` or `laptop`. `repoSetup` maps a lower-case `owner/name` to `{ role: 'app' | 'tests' | 'issues', prScope?: { authors?, skipDrafts?, skipBots? } }`. `githubIssues` is `{ repo, types, labels, milestone? }`, `testsByRepo` maps a repo to `{ setup?, command?, baseUrl?, tried? }`, `slack` is `{ channel?: { id, name }, approvers? }` with Slack ids, and `rules` is `{ shadowWeek }`. An unknown field or a wrong type is a 400 with the reason.
231
+ - `DELETE /api/onboarding` clears the progress.
232
+ - `POST /api/onboarding/github-check` takes `{ repos }` (up to 20 `owner/name`) and answers `{ ok, message, repos: [{ repo, ok, message, issues: 'ok' | 'off' | 'denied' | null, sso? }] }`. It reads each repo and its issues endpoint with the live GitHub connection. `issues: 'off'` means the repo has Issues turned off, which is a note, not a failure. `sso` is set, with the authorization `url` when GitHub sends one, when the token needs SAML SSO. 400 for a bad body, 409 when GitHub is not connected.
233
+ - `POST /api/onboarding/issue-count` takes `{ repo, types?, labels?, milestone? }` and answers `{ repo, count, capped, milestoneMissing? }`: the open issues of the repo that carry one of the labels (the rule of `tracker.labels.require`), sit in the milestone, and fall in scope by type (a typed issue by its type, an untyped one by its labels). It reads at most 500 and sets `capped` when more exist. 400 for a bad body, 403 for SSO or an App that is not installed, 404 for a repo it cannot see, 409 for Issues turned off or GitHub not connected.
234
+ - In demo mode with no GitHub connection, `GET /api/dots/suggest?repo=demo/shop-web` returns a suggestion for the sample shop with `runner: { mode: 'demo' }`.
235
+
236
+ ## Review on demand
237
+
238
+ A person can ask a dot to review a change now, without waiting for the poller or a webhook.
239
+ - `POST /api/dots/:id/review` takes exactly one of `{ pr }`, `{ sha }`, or `{ base, head }`. An open PR queues `pr_check` (sticky comment and check on the PR). A merged PR queues `post_merge` on its merge commit. A sha queues `post_merge` against its first parent, and `{ base, head }` (shas or branch names) against `base`. The request builds the same trigger the webhook path builds, so the job, the flow, and the rules gate are the same. Returns 202 `{ ok, duplicate, jobId, jobKind, title, head, runId? }`. When the same review is already queued or running (an earlier request or the poller's), `duplicate` is true and `jobId` is that job. The same review means the same PR and head for `pr_check`, and the same base and head for `post_merge`, so two ranges that end at one commit both run. A finished review does not block a new one. 400 for a bad body, 404 for an unknown dot, PR, or commit, 409 for a paused, archived, or repo-less dot, a PR closed without a merge, or an empty range, 502 when GitHub fails.
240
+ - `GET /api/dots/:id/pulls?limit=20` lists recent open and merged PRs, newest first: `{ number, title, state: 'open'|'merged', headSha, mergeSha?, author?, url?, updatedAt? }[]`. In demo mode (no GitHub credentials) the list and diffs come from `demo/payloads` for the demo repo only.
241
+ - The run's trigger carries `requestedBy: 'user' | 'chat'`, and its first step is "Review requested".
242
+ - `GET /api/dots/:id/model-usage` (KAT-362) answers `{ cap, today, week, stages, todayFrom, weekFrom }`: model calls and tokens (`calls`, `inputTokens`, `outputTokens`, `tokenCalls`) today and over the last 7 local days, per stage (`stage`, `label`), and `cap` (`{ cap, used, reached, resetsAt }`, or `null` when the dot has none). The dot view carries `modelCap` with the same `cap` while a cap is set, and `PATCH /api/dots/:id` takes `dailyModelCap` (a whole number from 1 to 100000, `0` or `null` for none). See [Daily model cap](SETUP.md#daily-model-cap).
243
+ - `POST /api/dots/:id/review` also takes `post: false` with a PR, a sha, or a range: **review without posting**. The event carries `outboxOnly`, and the PR comment and the check run of that review wait in the Inbox as drafts, whatever the dot's rules say. A rule that is already stricter than `act` stays as it is. A review of the same change that is queued or running and will post is not reused: the answer is 409. `post` must be a boolean.
244
+ - `POST /api/dots/:id/review` also takes `{ ticket }`: a Jira browse URL, a Linear issue URL, a GitHub issue URL, `owner/name#12`, or a key such as `TAD-3` (the dot's tracker reads a bare key, and `#12` on a dot with one GitHub Issues repo). It reads the ticket through the connected tracker and queues `ticket_review` with `requestedBy: 'user'`. Returns 202 `{ ok, duplicate, jobId, jobKind: 'ticket_review', title, ticket: { key, tracker, url }, runId? }`. 400 for a bad body or `ticket` with another target, 404 for a ticket the account cannot see, 409 when the tracker is not connected, the link is on another Jira site or a repo the dot does not watch, the GitHub link is a pull request, or the dot is paused, 502 when the tracker fails.
245
+ - Chat tools `get_ticket { dotId?, ticket }` and `review_ticket { dotId?, ticket }` take the same values. `review_ticket` emits a `run` event (the inline run card), waits up to `LOCALDOST_CHAT_REVIEW_WAIT_SEC` seconds (default 150) for the review, then emits an `approval` event for the comment draft when the rules hold it. `pending_approvals { dotId?, limit? }` lists pending approvals, oldest first, with the wait time (`list_approvals` is the old name and still works). Three more read-only lookups answer "what is the ongoing work": `open_prs { dotId?, limit? }` (open pull requests from GitHub for the dot's repo, or for every dot's repo), `runs_in_progress { dotId? }` (running runs and queued jobs), and `recent_defects { dotId?, sinceLastMerge?, sinceHours?, limit? }` (runs that found failing tests, a product failure, or a high risk, with the failing tests and the suspected cause). `list_runs`, `runs_in_progress`, `open_prs`, `pending_approvals`, `recent_defects`, `probe_status`, `get_memory`, and `list_tickets_reviewed` each answer with a block (the rows, built on the server from the tool result) and take `context?: boolean`: `true` marks a lookup the model makes only to choose its next step, and the chat shows no block for it. A call (`channel: 'call'`) gets no block. See [CHAT-UI.md](CHAT-UI.md#blocks-from-lookup-tools).
246
+ - Chat tools `test_results { dotId?, sinceDays?, branch?, status?, file?, kind?, splitAt? }` and `compose_view { title?, datasets?, vars?, root }` (KAT-366). `test_results` answers the model with the columns, a count, and five rows, and holds every row (at most 2,000) for a view; each lookup result of a turn carries a `resultId`. `compose_view` checks the spec, binds it to those results, and streams a `view` block (see [CHAT-UI.md](CHAT-UI.md#composed-views)). Both only read.
247
+ - On the Anthropic backend, chat reads ticket and PR links and keys in the message and runs `review_ticket`, `get_ticket`, or `review_pr` before the model. The Claude CLI and Codex CLI backends call the tools themselves through a local MCP server and run nothing first, so a message that needs no lookup shows no tool line. A turn runs each tool at most once per input; a repeat returns the first result and shows no second line. A line that refuses or conditions a review ("do not review TAD-3") hides `review_ticket` and `review_pr` for that turn on every backend. `npm run eval:chat` checks this on each backend (docs/CHAT-EVAL.md).
248
+ - Chat tool `review_pr` takes `{ dotId?, pr?, repo?, sha?, base?, head? }` and calls the same function. `repo` comes from a PR link and must be the dot's repo. The dot page has a "Review a PR" action with a PR picker and a commit field.
249
+
250
+ ## Live test runs
251
+
252
+ The runner captures a screenshot after each Playwright step (via a reporter that writes step events) and keeps trace.zip, video, and screenshots under `data/artifacts/<runId>/`.
253
+ - `LiveEvent` adds `{ type: 'run_step'; runId: string; step: RunStep }`, `{ type: 'run_frame'; runId: string; frame: { at: string; test: string; step: string; image: string /* artifact URL */ } }`, and `{ type: 'run_log'; runId: string; line: string }`.
254
+ - `LiveEvent` also has `{ type: 'picked_up'; watchId; jobId; kind: 'pr_check' | 'post_merge'; title; ahead: number; at; pr?: number; sha?: string; url?: string }`. It goes out when a `pr_check` or `post_merge` job is enqueued, before its run exists. `ahead` counts the jobs that start first. The same job keeps its `localdost` check current on GitHub, see [GITHUB.md](GITHUB.md#check-status).
255
+ - `GET /api/runs/:id/artifacts` returns `{ name, kind: 'screenshot'|'trace'|'video'|'log'|'report', url, size }[]`.
256
+ - `GET /artifacts/<runId>/<file>` serves files (auth as `/api`) and answers `Range: bytes=` requests with `206`, so a video can seek.
257
+ - A run's `report.cases[].evidence` is `{ screenshots: { name, step? }[]; video?: string; trace?: string }`, the artifact names of that test. `RunStep.test` names the Playwright test a step belongs to.
258
+ - `POST /api/runs/:id/rerun` and `POST /api/runs/:id/cancel`.
259
+ - `GET /api/runs/:id` adds `evidencePack: boolean` (KAT-392): true when the run names an issue and some test left a screenshot or a video. The run page shows the link to [the evidence pack](#evidence-pack-kat-392) only then.
260
+ - `POST /api/runs/:id/defect {file, title}` drafts a defect for one test that failed or was flaky in the run, through the same draft path as the run flows (`draftDefects`): an earlier draft or filed issue for the same bug is linked instead, and the draft passes the rules gate, so under the default ask rule it waits in the Inbox. Returns 202 `{ ok, state: 'drafted', approvalId? }`, or 200 with `state` `filed` (and `url`) or `rejected`. 400 without a title, 404 for an unknown run or a test that did not fail in it, 422 when the dot has no tracker or repo to file in (KAT-247).
261
+ - Run `status` adds `'cancelled'`. Runs hold a lease. A run whose lease expires becomes `interrupted` with a Retry action.
262
+
263
+ ## Disk space
264
+
265
+ `GET /api/disk` returns how much room the data folder has. It reads memory and one queue count, so it still answers when the disk is full.
266
+
267
+ ```ts
268
+ interface DiskView {
269
+ freeBytes: number | null; // free bytes where the data folder lives; null when unreadable
270
+ thresholdBytes: number; // LOCALDOST_DISK_MIN_FREE_GB in bytes; 0 means the check is off
271
+ low: boolean; // freeBytes is under thresholdBytes
272
+ writeFailed: boolean; // a write failed with "database or disk is full" in the last minute
273
+ lastWriteError?: { at: string; message: string };
274
+ paused: boolean; // low or writeFailed: new test runs wait
275
+ checkedAt: string | null;
276
+ waitingJobs: number; // queued pr_check, post_merge, ci_failure, rerun, repo_scan, and nightly_run jobs
277
+ }
278
+ ```
279
+
280
+ `LiveEvent` adds `{ type: 'disk'; disk: DiskStatus }` (the same shape without `waitingJobs`). It goes out when the status changes and on every check while `paused` is true. A write that finds the disk full answers `507` with `{ error, code: 'disk_full' }`. Setup: [SETUP.md](SETUP.md#disk-space).
281
+
282
+ ## Traceability
283
+
284
+ - `GET /api/links?ref=<ref>` or `?dotId=<id>` lists links, see [TRACKERS.md](TRACKERS.md#github-issues).
285
+ - `GET /api/cards?ref=<link, key, or owner/name#N>&kind=<github_pr|github_issue|jira|linear>&dotId=<id>` returns `{ card: ObjectCard }`, a GitHub PR, GitHub issue, Jira ticket, or Linear ticket built from the dot's connections (KAT-136, [CHAT-UI.md](CHAT-UI.md#object-cards)). `dotId` is optional: the dot that watches the repo or lists the tracker key answers. 400 for text that names no object, 404 for an unknown dot or an object nobody has, 409 when the dot does not watch the repo or the tracker is not connected, 502 when the tracker or GitHub does not answer.
286
+ - `GET /api/cards/status-options?ref=&kind=&dotId=` returns `{ current, options: { id, label, kind }[] }`: the statuses a ticket can move to now (Jira transitions, a Linear team's states, or close and reopen for a GitHub issue). 400 for a PR.
287
+ - `POST /api/cards/comment { ref, kind, dotId?, text }` and `POST /api/cards/status { ref, kind, dotId?, optionId }` send a `pr_comment` (plain) or `ticket_comment` (append) action, or a `ticket_status` action, through the gate and answer `{ result, runId, approvalId?, note? }` (`result` is `executed`, `pending_approval`, `blocked`, or `handed_off`). Sandbox mode and an archived dot refuse them. A PR comment for a repo the dot does not watch answers 409, like the card read. `POST /api/approvals/:id` answers 400 to an approve with an `edit` for a `ticket_status` draft.
288
+ - `GET /api/tickets/status?dotId=<id>` returns `{ statuses: TicketTestStatus[] }` for every issue or ticket the dot tracks and caches each in `issue_state.testStatus`. With `&ref=<owner/name#N or ticket key>` it returns `{ status: TicketTestStatus }` for one. 400 without `dotId`, 404 for an unknown dot.
289
+
290
+ - `GET /api/dots/:id/traceability?milestone=&status=&repo=` returns `{ rows: TraceRow[], milestones, repos, readiness }` for the requirements of a dot. `status` takes a word (`Not tested`) or a code (`not_tested`); an unknown value is a 400. `milestones` and `repos` ignore the filters, so a client can fill its controls. `readiness` counts the rows that pass the milestone and repo filters, and its `summary` is plain QA words, for example `14 of 18 requirements passing, 2 failing, 1 not tested, 1 out of date`.
291
+ - `GET /api/dots/:id/traceability.csv` takes the same filters and returns `text/csv` as an attachment. RFC 4180 quoting, CRLF rows, and a leading single quote on cells that start with `=`, `+`, `-`, or `@`.
292
+ - `GET /api/dots/:id/traceability/issue?ref=` returns the drawer detail of one requirement: summary, acceptance criteria, PRs with the last verdict, tests, defects, and the latest QA review comment. 404 when the ref is not a requirement of the dot.
293
+ - `GET /api/dots/:id/trace/map` returns `{ tree: TraceMapNode, stats, milestones, observed }`: the app under test as the dot's recent runs saw it (app, section, page, state), built from each run's `observations.jsonl`, with status (`covered`, `partly`, `failing`, `needs_tests`, `not_run`) and counts per node. Routes the code, a requirement, or a probe names that no run visited are Needs tests nodes, or Not run yet when a spec covers them; a visited page whose source a merge changed since is Not run yet. A section with one page is that page. `GET /api/dots/:id/trace/map/:nodeId` returns one node's requirements, tests, defects, screenshots (one per test per route, the latest, with its result; up to 12), the coverage `gaps` below it, the source files and API routes from the repo scan (`code`), and skill markdown; 404 for a node not in the tree. `GET /api/dots/:id/trace/map/skill.md` returns the whole map as one agent skill. Section, page and state nodes carry `dots: { id, name, avatar, running?: { runId, label } }[]`, the dots whose runs visited them. `POST /api/dots/:id/trace/map/:nodeId/run` queues a rerun of the spec files that cover the node (409 when none does). Requirement rows with no title get it from the dot's tracker, cached (KAT-190). `GET /api/trace/map`, `GET /api/trace/map/:nodeId`, `GET /api/trace/map/skill.md`, and `POST /api/trace/map/:nodeId/run` are the same for every live dot on one canvas. Rules: [TRACE-MAP.md](TRACE-MAP.md).
294
+ - `GET /api/dots/:id/readiness?milestone=` returns `{ milestone, readiness }`. `POST /api/dots/:id/readiness/report` with `{ milestone }` saves or updates the `readiness` report page of that milestone and returns `{ page, created }`.
295
+
296
+ ```ts
297
+ interface TraceRow {
298
+ ref: string; // lower case: owner/name#12, shop-12
299
+ key: string; // as the tracker writes it
300
+ title: string;
301
+ type: string; // Feature, Story, or Requirement when a label made it one
302
+ milestone: string;
303
+ repo: string;
304
+ prs: { ref: string; number: number; repo: string; url: string; lastVerdict: VerdictOutcome | null }[];
305
+ tests: { passed: number; total: number };
306
+ defects: { open: number; total: number };
307
+ status: string; // Not tested, Passing, Failing, Partly tested, Out of date
308
+ statusCode: 'not_tested' | 'passing' | 'failing' | 'partly_tested' | 'out_of_date';
309
+ }
310
+ ```
311
+
312
+ ```ts
313
+ interface TicketTestStatus {
314
+ ref: string; // lower case
315
+ status: 'not_tested' | 'passing' | 'failing' | 'partly_tested' | 'out_of_date';
316
+ label: string; // Not tested, Passing, Failing, Partly tested, Out of date
317
+ prs: { ref: string; result: 'passed' | 'failed' | 'none'; outdated: boolean; runId?: string; items: { total: number; notRun: number; failed: number } }[];
318
+ specs: { path: string; result: 'passed' | 'failed' | 'not_run' }[];
319
+ defects: { ref: string; state: 'open' | 'closed' | 'unknown' }[];
320
+ reasons: string[];
321
+ computedAt: string;
322
+ }
323
+ ```
324
+
325
+ `ActionKind` gains `issue_create` (default rule `ask`): a defect drafted from a test that failed on every retry. Its `meta` holds `tracker`, `project`, `sig`, `labels`, `issueType`, `milestone`, `relates`, and after approval `filedUrl` and `filedKey`. `file_defect` files through the same creator. A `ticket_comment` with `meta.append: true` adds a plain comment on a GitHub issue, and one with `meta.statusLabel` sets a `qa: passing` or `qa: failing` label.
326
+
327
+ `ActionKind` gains `ticket_status` (default rule `ask`, KAT-136): a status a person picked on a ticket card. Its `target` is `jira:KEY`, `linear:KEY`, or `github:owner/name#N`, and its `meta` holds `tracker`, `key`, `ticketId`, `optionId` (a Jira transition id, a Linear workflow state id, or `open`, `closed`, `not_planned` for a GitHub issue), `optionLabel`, and `from`. It passes the same destination check as a comment. A `pr_comment` with `meta.plain: true` is a new comment on the PR, written by a person on a card: it posts with `addComment`, never edits the sticky QA report, and does not replace a pending report draft for the same PR.
328
+
329
+ ## Test cases
330
+
331
+ The test case store of a dot (KAT-163, [STAGES.md](STAGES.md) section 4). A case has a key such as `TC-12`, unique per dot and never reused, steps with expected results, a kind (`manual`, `playwright`, `aivision`), requirement refs in `LinkRecord` form, a spec path when it is automated, a priority (`must`, `should`, `could`), a status (`draft`, `ready`, `retired`), and a source (`designer`, `user`, `imported`). Results are append only.
332
+
333
+ - `GET /api/dots/:id/cases?status=&kind=&requirement=&q=` returns `{ cases: TestCaseView[], requirements, counts }`. No `status` means draft and ready; `status=all` adds retired cases. `requirements` (every ref any case names, with its source, title, and link) and `counts` (per status) ignore the filters. An unknown status or kind is a 400.
334
+ - `GET /api/dots/:id/cases/:caseId` takes the case id or its key and returns the case with `results`, newest first. 404 for a case of another dot.
335
+ - `POST /api/dots/:id/cases` creates a case (source `user` unless the body names one) and returns 201. `PATCH /api/dots/:id/cases/:caseId` edits one; `specPath: null` clears the spec file. `POST /api/dots/:id/cases/:caseId/retire` retires one; its results stay. A bad field is a 400 in plain words: a manual case with a spec file, a ready case with no steps, a ref that is not a key, a spec path outside the repo.
336
+ - `GET /api/cases/:id/results` lists results, newest first. `POST /api/cases/:id/results` with `{ result, notes?, runId?, evidence? }` adds a manual result (`passed`, `failed`, `skipped`, `blocked`). `runId` must be a run of the same dot.
337
+ - `GET /api/dots/:id/folders` returns `{ folders, unfiled }` with `caseCount` on each folder. The ticket folder of a GitHub issue also has `repo` (KAT-398): the issue's repo as `owner/name` in lower case, which the designer matches with the issue number to find the folder again after a person moved it, so `#12` of one repo never takes the folder of `#12` in another. It is read only: `POST` and `PATCH` ignore a `repo` in the body, a rename or a move keeps it, and a folder of a Jira or Linear ticket has none. The boot migration fills `repo` on older ticket folders from the keys of their cases (`owner/name#12`), and leaves a folder with no such case, or with cases of two repos, without one.
338
+ - `GET /api/dots/:id/cases/export.zip` downloads the draft and ready cases as `tests/cases/<key>.md` files: YAML front matter, then the steps. Nothing is committed.
339
+
340
+ A run that saves a test report writes one result per case whose `specPath` names a file the run executed (`source: run`). The match ignores the testDir prefix, so `e2e/coupon.spec.ts` on the case matches `coupon.spec.ts` in the Playwright report. Any failed test fails the case; a test that passed on a retry passes it with a note. Screenshots, video, and trace of those tests become the result's `evidence`. A second save of the same run (the retry pass) replaces that row. Retired cases get no new results.
341
+
342
+ Each ticket review that writes a review also designs test cases (KAT-164, the test designer in [STAGES.md](STAGES.md) section 3). One model call turns the review's risks, test ideas, and open questions into manual cases, each with steps and expected results, a priority, the ticket as its only requirement ref, status `draft`, and source `designer`. A case that has the same normalized title or the same normalized steps as a case the dot already has for that ticket, in any status, is not stored again. The mock backend designs from the review with a fixed rule instead of a model call. The run gets `designed: { requirement, added, keys, existing, dropped, error? }`. The run page and the chat answer of `review_ticket` show "N test cases designed" with a link to `#/cases?dot=<id>&req=<ticket>`. A person marks each draft ready on the Test cases page.
343
+
344
+ Each ticket review also checks the tests the dot already has (KAT-304, [STAGES.md](STAGES.md) section 3.8). The run gets `impact: { ticket, checked, valid, update, obsolete, specs, leftOut?, error? }`. A check looks at the best 12 cases and 6 specs. `leftOut: { cases, specs }` says how many more matched and were dropped; it is absent when nothing was.
345
+
346
+ - `GET /api/dots/:id/impact?ticket=&state=&case=` returns `{ items: ImpactItem[], open, leftOut? }`. With `ticket`, `leftOut` is the count from the latest check of that ticket (absent when nothing was left out). `ticket` is a key such as `SHOP-12` (a bad one is a 400). `state=open` keeps the rows a person still has to decide; `state=all` is the default. Each item has the verdict (`valid`, `update`, `obsolete`), `confidence`, the `ticketLine` that causes it, a plain `note`, the `changes` (new steps) for an update, the ticket as a titled `requirement`, the case's `currentSteps`, and `specUrl` for a spec. Rows that need a decision come first.
347
+ - `POST /api/dots/:id/impact/:impactId/accept` writes the draft as the case's next version (the old version stays in its history), or marks a spec as updated. `POST .../reject` rejects the draft or dismisses the spec row. Both answer the ticket's list. A row that was already decided is a 409. A case the ticket makes obsolete is decided in the Inbox: its `case_retire` draft always asks, whatever the dot's rules say, and the two routes answer 409 for it. A paired phone may call both routes.
348
+ - `GET /api/dots/:id/cases` and `GET /api/dots/:id/cases/:caseId` add `needsReview: { impactId, ticket, verdict }` to a case with a draft that waits. Run items in `GET /api/dots/:id/test-runs/:runId` carry the same field.
349
+
350
+ ## True Platform sync
351
+
352
+ A dot copies its test cases, automated runs, and manual results into one True Platform project (KAT-406, setup and rules in [SETUP.md](SETUP.md#true-platform)). The setting is the `trueSync` field of the dot, saved with `POST` or `PATCH /api/dots/:id`.
353
+
354
+ `Watch.trueSync?: { enabled, tenantUrl, projectId, repositoryId?, folderPath?, ownerUuid?, blocked? }`. A request sends only `enabled`, `tenantUrl` (an https address on a `katalon.io` host, saved as its origin), and `projectId` (a whole number, a numeric string is taken). Switching on needs both. Anything else a request sends under `trueSync` is ignored: `repositoryId`, `folderPath`, `ownerUuid`, and `blocked` (`{ reason: 'key_missing' | 'key_rejected' | 'project_not_found' | 'unreachable', since }`) are the server's. A save that moves the dot to another address or project drops what it sent, since the True ids belong to the old project, and queues the cases for the new one. A save that switches the dot on queues every draft and ready case, and nothing for runs and test runs that came before.
355
+
356
+ - A bad address is a 400 with `Use your True Platform address, for example https://your-team.katalon.io.`; a switch without a project is a 400.
357
+ - Only the admin token, or a loopback browser on a server with no token, may change `enabled`, `tenantUrl`, or `projectId`. A paired editor who sends a different value gets 403 `{ error: 'Ask your localdost admin.', code: 'true_admin', fields: ['trueSync.projectId'] }`; the same values are fine. A member may only pause (see [Dots](#dots-watch-additions)).
358
+ - `GET /api/dots/:id` and the list add `trueKey: { set: boolean, source?: 'env' | 'file', warning?: string }`. The key is never in an answer.
359
+
360
+ Routes:
361
+
362
+ - `GET /api/dots/:id/true` answers the setting for the page: `{ enabled, tenantUrl, projectId, folderPath, key, blocked?, waiting, notSent, lastSentAt?, chatServer?, sandbox, items }`. `waiting` counts the items still queued, `notSent` those that gave up, `chatServer` is the URL of the dot's Katalon True Platform connection when it has one, and `items` lists up to 50 that gave up or wait with an error (`id, kind, refId, status, attempts, error, code, nextAt`).
363
+ - `GET /api/dots/:id/true/line?kind=run|test_run|case&id=` answers the status line of a dot run, a manual test run, or a case (a case by id or key). With the switch off, or nothing to say yet, it answers `{ on, line: null }`. Otherwise `{ on: true, code, text, error?, nextAt?, since?, projectId?, url?, urlLabel?, itemId?, canResend?, details?, earlierRounds?, notes?, trueName? }`. `code` is one of `sent`, `sending`, `retry`, `unreachable`, `key_missing`, `key_rejected`, `project_not_found`, `no_tests`, `gave_up_24h`, `dependency`, `other_user`, `refused`, `sandbox`. `text` is the sentence with the time in the server's clock; the app draws its own from the same words (`src/true/line.ts`).
364
+ - `POST /api/dots/:id/true/check` with an optional `{ tenantUrl, projectId }` (values the form has not saved) logs in and reads the key owner and the repository, then answers `{ ok: true, project }` or `{ ok: false, message }`. It is a POST so that a paired device, which may call every GET, cannot make the server call out: any paired device gets 403 `phone_blocked`. It answers 400 when no address and project are known, and `{ ok: false }` in sandbox mode or without a key.
365
+ - `POST /api/dots/:id/true/resend` is **Send all again**: every item the dot gave up goes back in the queue in its original order, with a fresh 24 hours. Answers `{ requeued }`.
366
+ - `POST /api/dots/:id/true/items/:itemId/resend` is **Send again** for one item that gave up, and for the items given up because of it. 404 for an item of another dot, 409 for one that is not waiting for a person. Both resend routes are open to a paired member.
367
+
368
+ ## Approvals
369
+
370
+ `ApprovalRecord` gains `expiresAt` (default 24 h) and `executedAt`. Approving twice returns 409. An expired approval returns 410 and shows as expired.
371
+
372
+ ## Connections (MCP)
373
+
374
+ ```ts
375
+ interface Connection { id: string; name: string; transport: 'stdio' | 'http'; command?: string; args?: string[]; url?: string; headersSecretRef?: string; enabled: boolean; tools: { name: string; description: string; readOnly: boolean; enabled: boolean }[]; status: 'connected' | 'error' | 'disabled'; error?: string; createdAt: string }
376
+ ```
377
+ - `GET /api/connections`, `POST /api/connections`, `PATCH /api/connections/:id`, `DELETE /api/connections/:id`, `POST /api/connections/:id/refresh` (lists tools through `@modelcontextprotocol/sdk`).
378
+ - Chat and flows can call enabled tools of connections attached to the dot. Read-only tools run directly. Any other tool goes through `gate()` as ActionKind `mcp_tool` (default rule `ask`), and the chat shows an inline approval card.
379
+ - Secrets live in env vars referenced by name (`headersSecretRef`), never in the database.
380
+
381
+ ## Reports workspace
382
+
383
+ ```ts
384
+ interface ReportPage { id: string; watchId?: string; parentId?: string; title: string; kind: 'test_plan' | 'readiness' | 'incident' | 'digest' | 'note'; markdown: string; revision: number; sourceRunId?: string; createdAt: string; updatedAt: string }
385
+ ```
386
+ - `GET /api/reports?watchId=&q=`, `GET /api/reports/:id`, `POST /api/reports`, `PUT /api/reports/:id` with `{ markdown, title, revision }` (409 when `revision` is stale), `DELETE /api/reports/:id`.
387
+ - `POST /api/runs/:id/save-report` creates a page from a run verdict. Digests save a page automatically.
388
+ - `POST /api/chat` accepts `reportId` to scope the chat to a page. The dot may propose an edit, which arrives as an approval of kind `report_edit` with a markdown diff. A copy of the dot's Team notes (the `instructions` field, 8 words in a row or more) is cut from the page, the title and the note before the card shows the diff and again when the edit is saved, whatever the `report_edit` rule is (KAT-399). A copy that runs across a sentence end or a line break is cut with the sentences it touches and the rest of the page stays. When nothing but a copy is left, the tool answers `{ error }`, no approval is made, and the page is not changed.
389
+
390
+ ## Release notes (KAT-391)
391
+
392
+ `GET /api/v2/release-notes?dot=&milestone=&format=` answers the release-note list: the note line of every merged PR the dots reviewed, grouped by milestone and then by the issue the PR names. It is read-only (no other method exists on the path), uses the same token as the rest of `/api`, and a paired device may call it. It reads stored rows and makes no model call and no tracker call. The page is `#/reports/release-notes` (Release notes in Reports): a table where the page is wide, a list where it is narrow.
393
+
394
+ | Query | Meaning |
395
+ |---|---|
396
+ | `dot` | A dot id or its name (case does not matter). A Lead QA dot reads its members. Empty: every dot that is not archived. 404 `No dot named "<x>".` for an unknown dot. An archived dot is read only when named |
397
+ | `milestone` | An exact milestone title, case does not matter. Empty: every milestone. A title with no note answers an empty list |
398
+ | `format` | `json` (default) or `md`. Any other value is 400 |
399
+
400
+ ```ts
401
+ interface ReleaseNotesList {
402
+ milestones: { // highest milestone name first (numbers compare as numbers), no milestone last
403
+ milestone: string | null; // null: the issue has no milestone, or the PR names no issue
404
+ issues: { // by key, the group "no issue" last
405
+ ref: string | null; // lower case: "owner/name#12" or the ticket key. null: the PR names no issue
406
+ key: string; // "KAT-12" or "owner/name#12". "" when ref is null
407
+ title: string; // "" until a review or a tracker read gave it
408
+ url: string | null;
409
+ tracker: 'github' | 'jira' | 'linear' | null;
410
+ notes: { // newest merge first
411
+ note: string; // one line of plain text
412
+ breaking: boolean;
413
+ audience: 'users' | 'admins' | 'developers';
414
+ testStatus: 'passed' | 'bug_found' | 'partly_tested' | 'not_tested';
415
+ testLabel: 'Passed' | 'Bug found' | 'Partly tested' | 'Not tested';
416
+ pr: { ref: string; number: number; repo: string; title: string; url: string; mergedAt: string };
417
+ alsoNames: { ref: string; key: string; url: string | null; tracker: 'github' | 'jira' | 'linear' | null }[]; // the other issues the PR names
418
+ dot: { id: string; name: string };
419
+ runId: string; // the merge check the note came from
420
+ }[];
421
+ }[];
422
+ }[];
423
+ counts: { notes: number; breaking: number; passed: number; bugFound: number; partlyTested: number; notTested: number }; // the rows of milestones: each merged PR is one
424
+ cut: boolean; // true when a dot in scope has more PR and merge checks than the window reads: older merges may be missing
425
+ allMilestones: string[]; // every milestone that has a note for this dot filter, lowest first, for a picker
426
+ dots: { id: string; name: string }[]; // the dots the filter can name
427
+ filter: { dot: { id: string; name: string } | null; milestone: string | null };
428
+ builtAt: string;
429
+ }
430
+ ```
431
+
432
+ How a note is built, all from rows the dots already hold:
433
+
434
+ - **Which notes.** The note line comes from the newest finished after-merge check of each merged PR (`post_merge` run with a review), the same data as the daily digest's release notes (`releaseNoteSources` in `src/flows/digest.ts`). A change the review calls not user-visible, or one with no note line, has no note. The newest check of a PR decides.
435
+ - **Audience.** The review's `end_users` is `users`, `admins` is `admins`, and `api_consumers` and `internal` are `developers`.
436
+ - **Issues and milestone.** A note is listed once, under one issue: the issue its PR closes, else the first issue it names by a ticket key or a cross-reference (the same links as a ticket's test status). When a PR closes or names several, they are ordered by reference with the number read as a number (`acme/shop#2` before `acme/shop#10`), so the same PR is always listed under the same issue, whenever its links were recorded. The other issues the PR names are in `alsoNames` on the note, and the page and the Markdown show them as "Other issues". The issue's milestone and title come from the ticket catalog that Traceability reads, and from the cached tracker titles. A PR that names no issue is listed under "No issue named", and an issue with no milestone under "No milestone". The milestone filter keeps the notes whose deciding issue has that milestone, so the rows of a filtered list are still the notes `counts` counts.
437
+ - **Test status.** The result of the PR's runs by the rule a ticket's test status uses (`prEvidence` in `src/flows/testStatus.ts`): `bug_found` when the latest run that ran the tests failed, `passed` when it passed, `partly_tested` when it passed but some What to test items of the review did not run, and `not_tested` when no run ran the tests, when the tests ran against the shared QA build, or when the result is for an older commit than the newest check.
438
+ - **Four values.** A reader that knows three (`passed`, `bug_found`, `not_tested`) reads `partly_tested` as `passed` with a gap: the run passed and the review's What to test list has items that did not run. `counts.partlyTested` is the number of such notes, and `counts.passed` does not include them.
439
+ - **How far back.** The newest 1000 PR and merge checks of each dot in scope. Digests, ticket reviews and site checks of the dot do not use up the window, so a busy dot keeps its merges. When a dot has more than 1000 such checks, `cut` is true, the page says "Only the newest checks of each dot are read, so older merges may be missing.", and the Markdown says the same in one line under the counts. The ticket test status keeps its own window of the newest 1000 runs of any kind.
440
+
441
+ `?format=md` answers `text/markdown; charset=utf-8`: a title, the counts line, then `## <milestone>`, `### [<key>](<issue url>): <title>`, and one bullet per note with `**Breaking.**`, `For <audience>.`, the test label and a link to the PR. Every note, title and key is escaped, and a link is written only for an http or https address, since the text comes from PR bodies and tickets. A skill reads it with the token of the server:
442
+
443
+ ```
444
+ curl -s -H "Authorization: Bearer $LOCALDOST_TOKEN" "$LOCALDOST_URL/api/v2/release-notes?milestone=2026.11&format=md"
445
+ ```
446
+
447
+ ## Evidence pack (KAT-392)
448
+
449
+ `GET /api/v2/runs/:id/evidence-pack` answers the evidence of one run that names an issue. It is read-only, uses the same token as `/api`, makes no model call and no tracker call, and reuses the per-test evidence of KAT-222 (`report.cases[].evidence`). The page is `#/runs/:id/evidence` (the **Steps, screenshots and video** link on the run page): the issue, each test with its video and its steps, the verdict, and a **Copy JSON** link that puts this JSON on the clipboard. It fits a 390 px phone. 404 for an unknown run. 409 `This run names no issue, so it has no evidence pack.` for a run that names none.
450
+
451
+ A run names an issue when it is a ticket review (the ticket), or when its PR names one: a closing keyword, a ticket key, or a cross-reference from the PR, the same links as the release-note list. The first issue is `issue`: the ticket of a ticket review, else an issue the PR closes, else one it mentions, in the same order as the release-note list, so a PR's pack and its note name the same issue. The rest are `moreIssues`.
452
+
453
+ ```ts
454
+ interface EvidencePack {
455
+ run: { id: string; kind: JobKind; kindLabel: string; status: RunRecord['status']; dot: { id: string; name: string }; startedAt: string; finishedAt: string | null };
456
+ issue: { ref: string; key: string; title: string; url: string | null; tracker: 'github' | 'jira' | 'linear' | null; milestone: string | null };
457
+ moreIssues: PackIssue[]; // same shape as issue
458
+ pr: { ref: string; number: number; title: string; url: string } | null;
459
+ verdict: { outcome: VerdictOutcome; label: string; headline: string; summary: string; risk: 'low' | 'medium' | 'high' } | null;
460
+ tests: { // tests that ran (passed, failed, flaky, timed out), up to 50, skipped ones are only counted
461
+ title: string; // without the project prefix
462
+ status: 'passed' | 'failed' | 'flaky' | 'timedOut';
463
+ durationMs: number;
464
+ error: string | null; // what went wrong in a line or two, scrubbed: the Expected and Received lines of a failed check, else the first line of the error. Only for a test that did not pass
465
+ errorDetails: string | null; // the whole error text (up to 1200 characters, the call log included), scrubbed, when it says more than `error`. Otherwise null
466
+ steps: { n: number; step: string; screenshot: { name: string; url: string; size: number } | null; failed: boolean }[];
467
+ video: { name: string; url: string; size: number } | null;
468
+ }[];
469
+ counts: { tests: number; passed: number; failed: number; skipped: number; steps: number; screenshots: number; videos: number; shown: number; truncated: boolean }; // tests, passed, failed, skipped count the whole run; tests lists at most 50 (shown), truncated says it was cut; steps, screenshots, videos count the listed tests
470
+ links: { run: string; page: string; api: string }; // in-app and API paths
471
+ scrubbed: number; // how many values the scrub replaced with [redacted]
472
+ builtAt: string;
473
+ }
474
+ ```
475
+
476
+ - **Steps.** The steps are in the order they ran. A test that names its steps (`test.step`, kept for every test that ran in `case.namedSteps`) has one step per named block. The live session takes at most one frame per 500 ms, each labelled with the step that ended just before it, so a named block takes the frame labelled with its own name, and a block with no such frame takes the last frame taken before the next block's frame. The frames of the Playwright calls inside a block fold into it. A frame after the last block (the last screenshot of the test, which the runner labels `Test passed`, `Test failed` or `Test timed out`, and a report attachment `End of test`) follows as a step called `End of test`. A block with no screenshot stays in the list with `screenshot: null`. A test that names no steps has one step per screenshot, in the order taken, with the Playwright call in plain words (`Click the "Pay" button`, `Fill in the "Password" field`, `Open /cart`, `Check the text`); a call the pack cannot put in words keeps its label, scrubbed.
477
+ - **Failed step.** For a failed or timed-out test, `failed` is true on one step: the `test.step` block that threw, else the last step before the `End of test` screenshot. It is false on every step of a test that passed, and on every step when the test left nothing to point at. The page shows it as "Failed here".
478
+ - **Typed values.** Playwright titles a fill step with the value it typed (`Fill "<value>"`, also `Type`, `Insert`, and `Press "<key>"` for one key). The runner hides the value as `[redacted]` in the step list, the live frame, the log line and the label stored in the report, and the pack hides it again in every text it holds, so a password a test typed never reaches the JSON, the page or Copy JSON. The same goes for `fill("<value>")` in a call log or a code frame.
479
+ - **Files.** A screenshot or a video is listed only when the run folder holds the file. `url` is `/artifacts/<runId>/<name>`: it needs the same token as the API, and it supports `Range` requests. The pack holds paths, never file bytes.
480
+ - **Scrub.** Every text of the pack goes through `scrubText` (`src/evidence-pack.ts`): the token shapes of `redactCode` (GitHub, Anthropic, AWS, Slack and Google keys, JWTs, private keys, a password in a URL), the value of an `Authorization`, `Cookie` or `Set-Cookie` header, a Bearer or Basic credential, `document.cookie = ...`, a cookie object (`name` with `value`), a `name=value`, a `name: value` (a YAML, `.env` or log line) or a `"name": "value"` pair whose name says session, cookie, csrf, token, secret, password, auth, key or signature, the value a test typed, and any query value of 32 or more opaque characters. Each is replaced by `[redacted]` and counted in `scrubbed`. The trace, the test command, the output tail, the base URL and the run's trigger are not in the pack, since they can hold cookies and tokens. A screenshot or a video can still show what the page showed: the scrub reads text only.
481
+
482
+ ## Chat events
483
+
484
+ SSE events on `/api/chat`: `delta {text}`, `tool {name, input}`, `tool_result {name, summary, error?}` (`error: true` when the call failed; `summary` then holds the reason), `approval {approval}` (inline card), `run {runId}` (inline TestRunCard), `block {block}` (an answer block, [CHAT-UI.md](CHAT-UI.md)), `reasoning {text}` (one step line of the summary the backend returned of its reasoning, plain text, KAT-374), `work {work}` (once the answer is final: `{ elapsedMs, steps, summary?, tools?, tokens? }`, where `tokens` sums the turn's `model_calls` rows and is left out when none knows its tokens; the stored assistant message keeps it as `work`), `view_node {id, at, node}` (one node of a composed view that is still streaming, [CHAT-UI.md](CHAT-UI.md#composed-views)), `view_drop {id}` (a view that streamed nodes could not be built after all: the client takes the block away), `done {text, replace?}`, `error {error}`. Every answer is rewritten so that each PR, issue, ticket, run, commit, and defect it names shows a short title with its link (`[Queued checks show at once (#30)](url)`; `<url|Queued checks show at once (#30)>` in Slack; plain `Queued checks show at once (#30)` for push and voice). Deltas stream as the model wrote them. When the rewrite changes the text, `done` carries the final text with `replace: true` and the client swaps it in. Titles come from the dot's GitHub data, the connected tracker, and the stored runs, with a 10 minute cache (`src/refs.ts`). A short sha (7 to 40 hex characters with a digit and a letter, or any hex after the word "commit") gets the first line of the commit message as its title. The chat shows the steps and the tool events in the Steps card, see [CHAT-UI.md](CHAT-UI.md#steps-card); tool results are not rewritten. A question about what failed gets the triage view instead of a model answer, see [CHAT-UI.md](CHAT-UI.md#triage). `GET /api/messages` returns stored `blocks` with each triage row's action state read again, and with the drafts, runs, ticket reviews, and notes of the lookup blocks read again.
485
+
486
+ ## Calls
487
+
488
+ `POST /api/call/session` returns `{ mode: 'browser' | 'realtime', token?: string, model?: string }`. Realtime mode is used when `OPENAI_API_KEY` is set (ephemeral token for the OpenAI Realtime API). Otherwise the client uses Web Speech in and `speechSynthesis` out, with the same chat endpoint. `POST /api/call/receipt` stores `{ dotId, startedAt, endedAt, transcript[] }` as a message with kind `call_receipt`. An answer of the transcript may carry `work { elapsedMs, steps, tokens? }`, numbers only, for its folded Reasoning line.
489
+
490
+ ## Slack
491
+
492
+ Socket Mode through `@slack/bolt` when `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN` are set, so no public URL is needed. Allowlists: `SLACK_ALLOWED_TEAM`, `SLACK_ALLOWED_USERS` (comma list). A mention of the bot in a thread routes to the dot named in the message, or else to the dot whose `slackChannel` matches the channel. The bot replies in the thread. Outage and digest notices post to `slackChannel` when that is set, through `gate()` as ActionKind `slack_post` (default `act`).
493
+
494
+ ## Joining a team server
495
+
496
+ A teammate joins with a code an admin made, and the device carries the teammate's name (KAT-356) and a role (KAT-364). The pairing routes are the phone's (`src/routes/phone.ts`).
497
+
498
+ **Roles.** A device is a `member` or an `editor`; a device made before roles existed is a member, and so is the Tailscale owner. The admin token and a loopback browser on a server with no token may do everything. The check runs on every `/api` call (`remoteAccess` in `src/phone/auth.ts`), and anything not listed is refused:
499
+
500
+ - A member: every read, `POST /api/approvals/:id`, `POST /api/chat`, `POST /api/runs/:id/rerun` and `/cancel`, `POST /api/dots/:id/review`, the manual test run results, impact accept and reject, failed alert retry and dismiss, `POST /api/push/subscribe`, `PUT /api/phone/me/updates`, `DELETE /api/phone/me`, and `PATCH /api/dots/:id` with a body of only `paused` (a boolean).
501
+ - An editor adds: `PATCH /api/dots/:id` with any field but the admin-only runner fields below, `POST /api/runner/try` with the dot's saved runner, `POST /api/memory`, `DELETE /api/memory/:id`, `POST /api/cards/comment`, and `POST /api/cards/status`.
502
+ - Admin only, inside the editor's routes: `runner.command`, `runner.setup`, `runner.passEnv`, `runner.copyFiles`, `runner.image`, `runner.mode`, `runner.workingDirectory`, `runner.workdir`, `runner.trustForks`, and `repos[].test.command`, `passEnv`, and `dir` (`RUNNER_ADMIN_FIELDS` in `src/runner/command.ts`). A `PATCH /api/dots/:id` from a device that changes one of them, a field left out of a sent `runner` included, answers 403 `{ error: "Ask your localdost admin to change test commands.", code: "runner_admin_only", fields }` and saves nothing; a `runner` with them as saved is a save. `POST /api/runner/try` from a device answers the same 403 for a runner that differs from the dot's in those fields, or with no `dotId`.
503
+ - Admin only, also inside the editor's routes (KAT-369): which code a run checks out. These are the repos of the dot and their roles, their order (the first app repo is `repo`), `repos[].defaultBranch` and `repo.defaultBranch`, `repos[].targets`, `repos[].test.framework` and `ref`, and `testBranches` (`repoAdminChanges` in `src/repos.ts`). The command, setup, folder and test login of a Tests repo (`repos[].test.command`, `setup`, `dir`, `passEnv`) are admin settings like the runner's. A `PATCH /api/dots/:id` from a device that changes one of them, read as a run reads them, answers 403 `{ error: "Ask your localdost admin to change repositories.", code: "repo_admin_only", fields }` and saves nothing; the saved list, or `repo` with the saved owner, name and branch, is a save, and so are `pathFilters`, `prScope`, and `runTests`. `POST /api/runner/try` from a device answers the same 403 for a `repo` that is not the dot's own repo and branch.
504
+ - Neither role: connections and setup (`/api/connect`, `/api/setup` writes), `POST /api/dots`, `DELETE /api/dots/:id` in any mode (archive and delete), `POST /api/dots/:id/restore`, and the pairing routes below. A phone that archived or restored a dot before roles existed is a member now, so it can no longer.
505
+ - Chat holds the same line: `POST /api/chat` stays open to a member, but the `add_memory` tool answers a member's turn with `Ask your localdost admin.` and saves nothing, as `POST /api/memory` does.
506
+ - A refusal is 403 `{ error: 'Ask your localdost admin.', code: 'phone_blocked' }`. The app shows "Ask your localdost admin" for that code and never takes it for an unpaired device.
507
+ - `GET /api/phone/me` answers `{ kind, role }` for the caller: `kind: 'device'` also carries `device`, and the admin token answers `{ kind: 'admin', admin: true, role: 'admin' }`. The app reads it to show only the controls the role may use.
508
+
509
+ - A request with no valid credential gets 401 `{ error: 'unauthorized', code: 'auth_required' }` from the server-wide check. The page shows the Join screen for that code only. The connection routes (`/api/connect`, `/api/setup/...`) answer a paired device or the Tailscale owner with 403 `phone_blocked`, never 401.
510
+ - `GET /api/phone/join` is open without credentials. It answers `{ name, empty }`: the value of `LOCALDOST_SERVER_NAME`, or null, and whether the server has no dot yet. The Join screen shows the name, and on an empty server it also asks the admin for the server token. A `POST` to this path needs a credential like any other `/api` call.
511
+ - `POST /api/phone/invite` makes an invite code. Admin only (the admin token, or a loopback browser on a server with no token) and same-origin. The optional body `{ ttlSec, role }`: `ttlSec` is 3600, 86400 (the default), or 604800, and `role` is `member` (the default) or `editor`; any other value is a 400. Answers 201 `{ code, display, expiresAt, ttlSec, role }`. A code works once, never cancels another open code, and is stored as a hash. 429 after 20 new invites in 10 minutes.
512
+ - `DELETE /api/phone/invite` cancels every open invite. Answers `{ ok, cancelled }`. `GET /api/phone` carries `invites`, the number still open.
513
+ - `POST /api/phone/pair/exchange` trades a code for a device token and takes `{ code, name }`. It accepts phone codes and invite codes, needs no credential, and is rate limited: 5 wrong codes per client in 10 minutes, and 20 in total cancel the open phone code. Invites stay until their admin cancels them or they expire. A code from an invite needs a `name`: a missing name is a 422 and a name another active device already uses (any case) is a 409, both before the code is spent. No device may be named `admin` (any case or spacing, also with a phone code): that is a 422, because an approval made with the admin token is recorded as `admin` and a teammate under the same name could not be told apart from it. A device made from an invite has `viaInvite: true` and the role of the invite; a phone code gives a member.
514
+ - `PATCH /api/phone/devices/:id` takes `{ name?, role? }` and changes the name (never to `admin`, a 422), the role (`member` or `editor`), or both; the next request of the device uses the new role. Admin only. A page that is open reads its role again from `GET /api/phone/me` when its tab comes back to the front and after a `phone_blocked` refusal, so a change shows without a reload. `GET /api/phone` lists each device with its `role`.
515
+ - `POST /api/phone/pair/self` takes `{ name }` and makes a device in the admin's name, an editor with no `viaInvite`, without spending an invite. Admin only. It answers 201 `{ token, device }` once, so the browser that asked keeps the token; a missing name is a 422, `admin` is a 422, and a name another active device uses is a 409. The audit trail records `paired` with the detail `admin computer`.
516
+ - `PUT /api/phone/me/updates` saves the calling device's own update level and quiet hours, `{ level?, quiet? }` as in `PUT /api/updates/prefs/:id`. The row is `device:<device id>`, it needs a paired device (400 otherwise), and revoking the device removes it. Phone alerts to that device follow it: Off sends nothing, Only what needs me sends decisions, outages, and high urgency, and an alert that arrives in quiet hours is skipped for that device, not held or sent later. A teammate's device gets no digest and no Slack message. `GET /api/updates/people` answers a device with its own row only, labelled `You`.
517
+
518
+ `POST /api/approvals/:id` stores `decidedBy` on the approval when the caller is a paired device (its name), the Tailscale owner (its login), or the admin token (`admin`); a loopback browser on a server with no token adds none. `POST /api/cards/comment` and `POST /api/cards/status` pass the same name: the run step reads `Asked on a card by <name>` and the draft carries `meta.askedByName`. A Slack decision stores `Slack <name>`, the name Slack sends with the button press, or `Slack <user id>` when it sends none.
519
+
520
+ ## Browser probes
521
+
522
+ `ProbeConfig` gains `kind: 'http' | 'browser'`, `selector?: string`, `steps?: string[]`. Browser probes load the page in Playwright, wait for the selector, and capture a screenshot on failure.
523
+
524
+ ## Failed alerts
525
+
526
+ `GET /api/updates/failed` answers `{ items, total }`. `items` lists urgent updates that gave up on Slack or the phone (`?dotId=` for one dot, `?limit=` up to 100), newest first. `total` is how many failed alerts there are in all, so it can be larger than `items`; the app's "Dismiss all N" button shows it. A row whose decision is no longer pending carries `approvalSettled: true`. The app shows them as "Alerts that did not arrive", on the dot page and in the Inbox. Three routes clear them:
527
+
528
+ - `POST /api/updates/:id/retry` sends the alert again now on its failed channels only, with the same send code as the timed retries. Quiet hours do not hold it, and a channel that is turned off is reported and not sent. Answers `{ update, sent, failed, cleared }`. `cleared` is true when no failed channel is left. The attempts stay and the new try is added to them. 404 for an unknown update. 409 with `code` `not_failed`, `settled` (the approval it announced is already decided), or `archived` (the dot is archived).
529
+ - `POST /api/updates/:id/dismiss` takes the alert off the list without sending it and ends its retries. It sets `dismissedBy` and `dismissedAt` on the update. Answers `{ update }`. 404 and 409 `not_failed` as above.
530
+ - `POST /api/updates/failed/dismiss` dismisses many. The optional body `{ dotId?, olderThan? }` limits it to one dot and to alerts created before the timestamp. Dismisses every matching alert, not only the page that `GET` returns. Answers `{ dismissed, ids }`. 400 for a `dotId` that is not a string or an `olderThan` that is not a timestamp.
531
+
532
+ A paired phone may call all three: a retry resends a message the dot already wrote, and a dismissal only hides the entry from the list.