toolroll 0.9.92 → 0.9.94

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 (341) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +4 -0
  5. package/dist/agent-fence.js +4 -1
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -3
  12. package/dist/assignment.js +16 -3
  13. package/dist/boot-identity.d.ts +2 -1
  14. package/dist/boot-identity.js +6 -4
  15. package/dist/bounded-spawn.d.ts +57 -0
  16. package/dist/bounded-spawn.js +220 -0
  17. package/dist/browser/terminal.js +42 -42
  18. package/dist/browser/workspace.css +2 -2
  19. package/dist/browser/workspace.js +26 -26
  20. package/dist/browser-crew.js +2 -2
  21. package/dist/browser-shell.d.ts +1 -1
  22. package/dist/browser-shell.js +2 -2
  23. package/dist/browser-workspace.d.ts +48 -7
  24. package/dist/browser-workspace.js +3 -3
  25. package/dist/builder-restart.d.ts +21 -0
  26. package/dist/builder-restart.js +50 -0
  27. package/dist/builder.d.ts +4 -0
  28. package/dist/builder.js +73 -25
  29. package/dist/chat-acceptance.js +1 -1
  30. package/dist/chat-actions.d.ts +12 -2
  31. package/dist/chat-actions.js +25 -2
  32. package/dist/chat-channel.js +11 -0
  33. package/dist/chat-decide.d.ts +7 -2
  34. package/dist/chat-decide.js +37 -9
  35. package/dist/chat-quiet.js +3 -3
  36. package/dist/chat-voice.d.ts +1 -1
  37. package/dist/chat-voice.js +2 -2
  38. package/dist/check-facts.d.ts +126 -0
  39. package/dist/check-facts.js +318 -0
  40. package/dist/check-failure.d.ts +22 -3
  41. package/dist/check-failure.js +57 -13
  42. package/dist/check-levels-ui.d.ts +4 -0
  43. package/dist/check-levels-ui.js +10 -1
  44. package/dist/check-load.d.ts +85 -0
  45. package/dist/check-load.js +147 -0
  46. package/dist/check-only-standing.d.ts +36 -0
  47. package/dist/check-only-standing.js +39 -0
  48. package/dist/check-only.d.ts +15 -0
  49. package/dist/check-only.js +99 -0
  50. package/dist/check-report.d.ts +7 -0
  51. package/dist/check-report.js +4 -3
  52. package/dist/check-stamp.d.ts +46 -0
  53. package/dist/check-stamp.js +93 -0
  54. package/dist/checkout-cleanup.d.ts +36 -5
  55. package/dist/checkout-cleanup.js +166 -14
  56. package/dist/cli-help.d.ts +4 -1
  57. package/dist/cli-help.js +47 -30
  58. package/dist/cli.js +16 -1
  59. package/dist/code-extract.d.ts +44 -0
  60. package/dist/code-extract.js +88 -0
  61. package/dist/code-map-acts.d.ts +24 -0
  62. package/dist/code-map-acts.js +53 -0
  63. package/dist/code-map-admission.d.ts +52 -0
  64. package/dist/code-map-admission.js +295 -0
  65. package/dist/code-map-build.d.ts +63 -0
  66. package/dist/code-map-build.js +415 -0
  67. package/dist/code-map-cli.d.ts +153 -0
  68. package/dist/code-map-cli.js +103 -0
  69. package/dist/code-map-main.d.ts +1 -0
  70. package/dist/code-map-main.js +48 -0
  71. package/dist/code-map-read.d.ts +106 -0
  72. package/dist/code-map-read.js +171 -0
  73. package/dist/code-map-report.d.ts +37 -0
  74. package/dist/code-map-report.js +95 -0
  75. package/dist/code-map-shape.d.ts +172 -0
  76. package/dist/code-map-shape.js +327 -0
  77. package/dist/code-map-store.d.ts +136 -0
  78. package/dist/code-map-store.js +233 -0
  79. package/dist/coding-provider.d.ts +2 -0
  80. package/dist/coding-provider.js +3 -2
  81. package/dist/coding-update.js +3 -2
  82. package/dist/coding-workspace.js +4 -4
  83. package/dist/console-chrome.js +2 -2
  84. package/dist/containment.d.ts +5 -0
  85. package/dist/containment.js +14 -1
  86. package/dist/contracts/chat-actions.d.ts +9 -0
  87. package/dist/contracts/chat-actions.js +2 -0
  88. package/dist/contracts/cli.d.ts +52 -0
  89. package/dist/contracts/cli.js +4 -0
  90. package/dist/contracts/console-api.d.ts +12 -5
  91. package/dist/contracts/console-api.js +12 -5
  92. package/dist/contracts/gateway-tools.d.ts +17 -0
  93. package/dist/contracts/gateway-tools.js +3 -1
  94. package/dist/contracts/lead-tools.d.ts +61 -0
  95. package/dist/contracts/lead-tools.js +10 -1
  96. package/dist/contracts/project-knowledge.d.ts +38 -5
  97. package/dist/contracts/project-knowledge.js +11 -5
  98. package/dist/contracts/project-memory.d.ts +3 -1
  99. package/dist/contracts/project-memory.js +3 -2
  100. package/dist/contracts/release-check.d.ts +25 -1
  101. package/dist/contracts/release-check.js +19 -0
  102. package/dist/contracts/storage.d.ts +14 -0
  103. package/dist/contracts/storage.js +3 -1
  104. package/dist/contracts/store-json.d.ts +2 -0
  105. package/dist/contracts/youiyoux-request.d.ts +1 -1
  106. package/dist/contracts/youiyoux-request.js +1 -1
  107. package/dist/desktop-access.js +6 -2
  108. package/dist/desktop-update.d.ts +2 -2
  109. package/dist/desktop-update.js +13 -9
  110. package/dist/dispatch.js +18 -15
  111. package/dist/dispose.js +8 -2
  112. package/dist/easy-start.js +2 -3
  113. package/dist/eval-hooks.d.ts +55 -0
  114. package/dist/eval-hooks.js +227 -4
  115. package/dist/evals.js +2 -1
  116. package/dist/fast-start.d.ts +136 -0
  117. package/dist/fast-start.js +431 -0
  118. package/dist/fast-terms.d.ts +9 -0
  119. package/dist/fast-terms.js +23 -0
  120. package/dist/fenced-check-coverage.d.ts +62 -0
  121. package/dist/fenced-check-coverage.js +54 -0
  122. package/dist/first-run.js +1 -1
  123. package/dist/flaky-ci.d.ts +31 -0
  124. package/dist/flaky-ci.js +149 -0
  125. package/dist/flaky-cli.d.ts +60 -0
  126. package/dist/flaky-cli.js +60 -0
  127. package/dist/flaky.d.ts +83 -0
  128. package/dist/flaky.js +264 -0
  129. package/dist/flow-card-task.js +3 -0
  130. package/dist/flow-triggers.d.ts +1 -1
  131. package/dist/flow-triggers.js +1 -1
  132. package/dist/github-import.d.ts +62 -0
  133. package/dist/github-import.js +146 -0
  134. package/dist/guides.js +15 -10
  135. package/dist/invoke.d.ts +5 -1
  136. package/dist/invoke.js +49 -14
  137. package/dist/job-object-helper.ps1 +10 -3
  138. package/dist/keymap.d.ts +41 -0
  139. package/dist/keymap.js +51 -0
  140. package/dist/knowledge-cli.js +1 -1
  141. package/dist/knowledge-ui.d.ts +21 -4
  142. package/dist/knowledge-ui.js +75 -7
  143. package/dist/laptop-background.d.ts +16 -0
  144. package/dist/laptop-background.js +108 -2
  145. package/dist/lead-context.js +1 -1
  146. package/dist/lead-contract.d.ts +1 -1
  147. package/dist/lead-contract.js +2 -2
  148. package/dist/lead-progress.js +3 -0
  149. package/dist/lead-status.d.ts +2 -0
  150. package/dist/lead-status.js +6 -2
  151. package/dist/lead-tools.js +51 -2
  152. package/dist/lead.js +1 -1
  153. package/dist/lesson-notes.d.ts +81 -0
  154. package/dist/lesson-notes.js +336 -0
  155. package/dist/limits-ui.js +1 -1
  156. package/dist/local-owner.d.ts +1 -1
  157. package/dist/local-owner.js +11 -0
  158. package/dist/machine-check.d.ts +16 -0
  159. package/dist/machine-check.js +26 -0
  160. package/dist/machine-identity.d.ts +75 -0
  161. package/dist/machine-identity.js +170 -0
  162. package/dist/mcp-core.js +11 -1
  163. package/dist/memory-cli.d.ts +300 -1
  164. package/dist/memory-cli.js +62 -9
  165. package/dist/memory-pass.d.ts +16 -0
  166. package/dist/memory-pass.js +67 -3
  167. package/dist/memory-tidy.d.ts +181 -0
  168. package/dist/memory-tidy.js +523 -0
  169. package/dist/memory-use.d.ts +35 -0
  170. package/dist/memory-use.js +109 -0
  171. package/dist/merge-checklist.d.ts +55 -0
  172. package/dist/merge-checklist.js +182 -0
  173. package/dist/needs-you.d.ts +32 -13
  174. package/dist/needs-you.js +108 -49
  175. package/dist/nightly-github.d.ts +2 -0
  176. package/dist/nightly-github.js +4 -4
  177. package/dist/operate-remote-arguments.js +6 -1
  178. package/dist/operate-remote.js +3 -1
  179. package/dist/operate.d.ts +6 -1
  180. package/dist/operate.js +540 -70
  181. package/dist/phase-routing.js +2 -2
  182. package/dist/plain-schema.d.ts +23 -0
  183. package/dist/plain-schema.js +94 -0
  184. package/dist/plain-words.d.ts +20 -0
  185. package/dist/plain-words.js +66 -0
  186. package/dist/planner.js +2 -1
  187. package/dist/process-liveness.d.ts +2 -0
  188. package/dist/process-liveness.js +61 -17
  189. package/dist/project-delete-ui.d.ts +7 -2
  190. package/dist/project-delete-ui.js +12 -1
  191. package/dist/project-delete.js +13 -2
  192. package/dist/project-knowledge.d.ts +18 -11
  193. package/dist/project-knowledge.js +39 -9
  194. package/dist/project-learning.d.ts +2 -1
  195. package/dist/project-learning.js +15 -45
  196. package/dist/project-memory.d.ts +5 -0
  197. package/dist/project-memory.js +22 -2
  198. package/dist/proof.js +30 -5
  199. package/dist/provider-auth.js +1 -1
  200. package/dist/publish.js +9 -1
  201. package/dist/pull-request-flow.js +5 -0
  202. package/dist/push.d.ts +26 -3
  203. package/dist/push.js +55 -15
  204. package/dist/release-adapters.d.ts +9 -0
  205. package/dist/release-adapters.js +87 -1
  206. package/dist/release-approval.d.ts +191 -0
  207. package/dist/release-approval.js +382 -0
  208. package/dist/release-auto.js +26 -17
  209. package/dist/release-card.d.ts +74 -6
  210. package/dist/release-card.js +179 -23
  211. package/dist/release-carry.d.ts +7 -2
  212. package/dist/release-carry.js +39 -9
  213. package/dist/release-cli.d.ts +4 -0
  214. package/dist/release-cli.js +38 -23
  215. package/dist/release-coverage.js +1 -1
  216. package/dist/release-next.d.ts +48 -0
  217. package/dist/release-next.js +161 -0
  218. package/dist/release.d.ts +50 -1
  219. package/dist/release.js +118 -11
  220. package/dist/remote-destination.d.ts +36 -0
  221. package/dist/remote-destination.js +217 -0
  222. package/dist/remote-main.d.ts +48 -0
  223. package/dist/remote-main.js +84 -5
  224. package/dist/repository-context.d.ts +3 -2
  225. package/dist/repository-context.js +112 -70
  226. package/dist/restart-certification.d.ts +1 -0
  227. package/dist/restart-certification.js +8 -6
  228. package/dist/result-acts.d.ts +29 -21
  229. package/dist/result-acts.js +36 -25
  230. package/dist/result-follow-ups.d.ts +2 -2
  231. package/dist/result-follow-ups.js +18 -4
  232. package/dist/result-review.d.ts +1 -1
  233. package/dist/route-change-check.d.ts +4 -0
  234. package/dist/route-change-check.js +13 -0
  235. package/dist/route-change.d.ts +22 -0
  236. package/dist/route-change.js +89 -0
  237. package/dist/route-hooks.js +3 -2
  238. package/dist/route-results.d.ts +6 -1
  239. package/dist/route-results.js +14 -5
  240. package/dist/run-closeout.d.ts +1 -1
  241. package/dist/run-closeout.js +7 -5
  242. package/dist/runner-process.js +3 -2
  243. package/dist/runner.js +2 -1
  244. package/dist/scope.d.ts +6 -1
  245. package/dist/scope.js +22 -9
  246. package/dist/serve.d.ts +1 -1
  247. package/dist/serve.js +24 -18
  248. package/dist/server/chat.js +14 -12
  249. package/dist/server/chrome.d.ts +1 -1
  250. package/dist/server/chrome.js +26 -35
  251. package/dist/server/console.css +23 -0
  252. package/dist/server/guards.js +4 -0
  253. package/dist/server/home.d.ts +6 -0
  254. package/dist/server/home.js +21 -13
  255. package/dist/server/http.js +1 -1
  256. package/dist/server/kit.js +1 -1
  257. package/dist/server/merge-ready-view.d.ts +10 -0
  258. package/dist/server/merge-ready-view.js +30 -0
  259. package/dist/server/pages-assets.d.ts +1 -0
  260. package/dist/server/pages-assets.js +8 -5
  261. package/dist/server/pages.js +103 -82
  262. package/dist/server/render-chat.js +2 -2
  263. package/dist/server/render-code-map.d.ts +30 -0
  264. package/dist/server/render-code-map.js +137 -0
  265. package/dist/server/render-flaky.d.ts +20 -0
  266. package/dist/server/render-flaky.js +59 -0
  267. package/dist/server/render-history.js +1 -1
  268. package/dist/server/render-new-task.d.ts +26 -1
  269. package/dist/server/render-new-task.js +30 -5
  270. package/dist/server/render-pages.d.ts +1 -175
  271. package/dist/server/render-pages.js +38 -313
  272. package/dist/server/render-projects.d.ts +2 -1
  273. package/dist/server/render-projects.js +9 -7
  274. package/dist/server/render-release.d.ts +19 -0
  275. package/dist/server/render-release.js +84 -0
  276. package/dist/server/render-results.d.ts +2 -2
  277. package/dist/server/render-results.js +71 -73
  278. package/dist/server/render-tasks.d.ts +13 -2
  279. package/dist/server/render-tasks.js +156 -139
  280. package/dist/server/retired-routes.d.ts +17 -0
  281. package/dist/server/retired-routes.js +53 -0
  282. package/dist/server/route-table.d.ts +4 -1
  283. package/dist/server/route-table.js +52 -13
  284. package/dist/server/settings.js +129 -22
  285. package/dist/server/task-views.js +61 -26
  286. package/dist/server/tasks.d.ts +17 -6
  287. package/dist/server/tasks.js +273 -338
  288. package/dist/server/welcome.js +1 -1
  289. package/dist/settings-groups-ui.js +1 -1
  290. package/dist/setup-guide.d.ts +2 -2
  291. package/dist/stop-proof.d.ts +67 -0
  292. package/dist/stop-proof.js +295 -0
  293. package/dist/storage-sweep-process.js +1 -1
  294. package/dist/storage-sweep.d.ts +5 -3
  295. package/dist/storage-sweep.js +30 -9
  296. package/dist/storage-ui.js +28 -17
  297. package/dist/storage.d.ts +27 -2
  298. package/dist/storage.js +39 -2
  299. package/dist/store.d.ts +148 -34
  300. package/dist/store.js +626 -177
  301. package/dist/surface.js +46 -5
  302. package/dist/sweep-quarantine.d.ts +1 -0
  303. package/dist/sweep-quarantine.js +6 -4
  304. package/dist/tamper-scan.d.ts +4 -0
  305. package/dist/tamper-scan.js +90 -0
  306. package/dist/task-aliases.d.ts +5 -0
  307. package/dist/task-aliases.js +9 -0
  308. package/dist/task-control.js +4 -1
  309. package/dist/task-outcome-cli.d.ts +2 -0
  310. package/dist/task-outcome-cli.js +36 -17
  311. package/dist/task-sizing.d.ts +5 -0
  312. package/dist/task-sizing.js +2 -0
  313. package/dist/task-source.d.ts +7 -0
  314. package/dist/task-source.js +14 -0
  315. package/dist/task-status.d.ts +9 -0
  316. package/dist/task-status.js +40 -49
  317. package/dist/teardown-git.d.ts +16 -0
  318. package/dist/teardown-git.js +63 -5
  319. package/dist/telegram-progress.js +5 -3
  320. package/dist/telegram-status.js +6 -4
  321. package/dist/test-infra.d.ts +8 -0
  322. package/dist/test-infra.js +18 -0
  323. package/dist/tidy.js +1 -1
  324. package/dist/toolroll-update-ui.js +2 -2
  325. package/dist/toolroll-update.js +18 -5
  326. package/dist/tui-contract.d.ts +24 -0
  327. package/dist/tui-source.js +67 -8
  328. package/dist/tui.js +51 -51
  329. package/dist/vanished-retry.d.ts +30 -0
  330. package/dist/vanished-retry.js +174 -0
  331. package/dist/work-index.d.ts +2 -0
  332. package/dist/work-index.js +82 -70
  333. package/dist/work-summary.d.ts +2 -2
  334. package/dist/work-summary.js +2 -0
  335. package/dist/workspace-ui.d.ts +4 -2
  336. package/dist/workspace-ui.js +28 -22
  337. package/dist/worktree.d.ts +12 -3
  338. package/dist/worktree.js +68 -13
  339. package/package.json +1 -1
  340. package/dist/server/render-next.d.ts +0 -63
  341. package/dist/server/render-next.js +0 -109
@@ -52,14 +52,21 @@ import { refuse, regionScript, relativeAge as relativeAgeOf, screen, settingsRow
52
52
  import { redirect, respond, safeReturn, SAFETY, taskHref } from "./http.js";
53
53
  import { assignmentOf } from "../assignment.js";
54
54
  import { assignmentTaskStatusOf } from "../assignment-presentation.js";
55
- import { githubIdentityOf, homePage, portfolioOverview, startBuilderHtml, workbenchRail } from "./render-pages.js";
55
+ import { githubIdentityOf, homePage, startBuilderHtml } from "./render-pages.js";
56
+ import { retiredRedirect } from "./retired-routes.js";
57
+ import { codeMapStatus, openMap } from "../code-map-read.js";
58
+ import { requestRefresh } from "../code-map-store.js";
59
+ import { codeMapReport } from "../code-map-report.js";
60
+ import { codeMapPage, codeMapReportBlock } from "./render-code-map.js";
61
+ import { FLAKY_WINDOW_DAYS, flakyList, flakyShow } from "../flaky.js";
62
+ import { flakyPage, flakyRowPage } from "./render-flaky.js";
56
63
  import { browsePage, githubReposPage, projectHref, projectPage, projectsPage, unknownProject } from "./render-projects.js";
57
64
  import { notFound as kitNotFound } from "./kit.js";
58
65
  import {} from "./render-pages.js";
59
66
  import {} from "../project-outcomes.js";
60
67
  import { CHECK_LEVEL_WORDS } from "../check-levels.js";
61
68
  import { capsPage } from "./render-settings.js";
62
- import { decisionAnswerScript, taskBody } from "./render-tasks.js";
69
+ import { consentDoorOf, decisionAnswerScript, taskBody } from "./render-tasks.js";
63
70
  import {} from "./session.js";
64
71
  import { rereadBrowser } from "./live.js";
65
72
  import { homeView } from "./home.js";
@@ -185,16 +192,21 @@ export function createPagesHandlers(runtime) {
185
192
  async function homeRedirect(ctx) {
186
193
  const { who, response, now, project } = ctx;
187
194
  if (who.via !== "cookie" || restricted())
188
- return redirect(response, "/work");
195
+ return redirect(response, "/tasks");
189
196
  if (firstRunStepsNow(now) !== null && !store.hasAnyWork())
190
197
  return redirect(response, "/chat");
191
198
  // One scope for the page and the chrome around it: the project its address names (proved by the router), else all.
192
199
  const chrome = chromeFor(project, "none");
193
200
  const projects = chrome.projects ?? [];
194
- const view = { ...homeView({ store, now, access: workAccess(), evidenceRoot, project, projects, chat: chrome.chat === true, canAnswer: who.role === "approver" }),
201
+ const closedDoor = (taskId) => {
202
+ const scope = store.getScope(taskId);
203
+ const door = consentDoorOf(scope, runtime.routeViewOf(taskId, store.lookupRef(taskId), scope, now, who));
204
+ return door.open ? null : door;
205
+ };
206
+ const view = { ...homeView({ store, now, access: workAccess(), evidenceRoot, project, projects, chat: chrome.chat === true, canAnswer: who.role === "approver", closedDoor }),
195
207
  // Until the first result: the welcome's three steps stay one link away.
196
208
  setup: firstRunStepsNow(now) !== null ? "/welcome" : null };
197
- return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/work">Tasks</a></p></div>`, { chrome, workspace: { view } }));
209
+ return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/tasks">Tasks</a></p></div>`, { chrome, workspace: { view } }));
198
210
  }
199
211
  // Search (UI revamp wave B): the ⌘K menu's "See all results". The places come from the page; the tasks here are the
200
212
  // most recent ones in view (live and archived), matched on their title and id. Reads only.
@@ -227,76 +239,6 @@ export function createPagesHandlers(runtime) {
227
239
  response.end();
228
240
  return;
229
241
  }
230
- async function workbench(ctx) {
231
- const { url, who, response, now, project } = ctx;
232
- // The fleet under one gaze (attended A1): the rail rolls up the whole
233
- // ceiling regardless of the open project — watching is cross-project
234
- // by nature; acting happens on task screens that re-prove everything.
235
- const admission = admissionList();
236
- const snapshot = store.boardScoped(null, now, 200, admission);
237
- const rows = snapshot.tasks.filter(facts => facts.repo === null || visible(facts.repo));
238
- const cards = rows.map(facts => classify(facts.blockerRepo !== null && !visible(facts.blockerRepo) ? { ...facts, blockerState: null } : facts, now));
239
- const attention = cards.filter(card => card.lane === "attention").slice(0, 100);
240
- const building = cards.filter(card => card.lane === "building");
241
- const waiting = cards.filter(card => card.lane === "waiting");
242
- const queued = cards.filter(card => card.lane === "queued");
243
- const done = snapshot.done
244
- .filter(one => one.repo === null || visible(one.repo))
245
- .slice(0, 5)
246
- .map(one => ({ taskId: one.taskId, title: one.title, outcome: one.outcome, repo: one.repo }));
247
- const selected = url.searchParams.get("t");
248
- const rail = workbenchRail({ attention, building, waiting, queued, done, selected, saturated: snapshot.saturated });
249
- if (url.searchParams.get("fragment") === "rail") {
250
- // The rail alone: same auth, same ceiling, no shell, no scripts.
251
- return respond(response, 200, "text/html; charset=utf-8", rail);
252
- }
253
- // The portfolio overview (arc slice 1a). All-scope hygiene throughout:
254
- // admission binds inside every bounded SQL read, and every unlimited
255
- // feed's rows pass visible() BEFORE they render or tally — a hidden
256
- // project must not leak into a row, a count, or a dollar.
257
- const sinceIso = new Date(now.getTime() - 24 * 3_600_000).toISOString();
258
- const decisions = store
259
- .listDecisionsScoped(null)
260
- .filter(one => visible(one.repo ?? null))
261
- .slice(0, 10);
262
- const approvals = store.scopesAwaitingApproval(null, 10, admission).filter(one => visible(one.repo ?? null));
263
- const requeueables = store.listRequeueablesScoped(null, now, 10, admission).filter(one => visible(one.repo ?? null));
264
- const cancelledBlockers = store
265
- .listCancelledBlockersScoped(null, 10, admission)
266
- .filter(one => visible(one.repo ?? null) && visible(one.blockerRepo ?? null));
267
- const runs24 = store.runsSinceScoped(sinceIso, null).filter(run => visible(taskRepoOf(run.taskRef)));
268
- const live = store.liveClaims(null, now).filter(one => visible(one.repo));
269
- const ledger = store
270
- .portfolioLedgerScoped(null, sinceIso, 30, admission)
271
- .filter(row => visible(row.repo));
272
- // Gaps stay project-relative (the roll-up inbox's rule): computed for
273
- // the OPEN project only — there is no scope-safe road to another
274
- // project's /caps.
275
- const gaps = project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10);
276
- const csrf = who.via === "cookie" ? who.session.csrf : "";
277
- let detail = html `${portfolioOverview({
278
- attention, building, waiting, queued, done, saturated: snapshot.saturated,
279
- decisions, approvals, requeueables, cancelledBlockers, gaps,
280
- gapsProject: project, runs24, live, ledger, csrf, now,
281
- })}<div class="workbench-mobile-rail">${rail}</div>`;
282
- if (selected !== null) {
283
- const view = taskViewData(selected, who, null);
284
- detail = view === null
285
- ? html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p><div class="card"><p class="meta">No such task — it may have been outside this console's view</p></div>`
286
- : html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p>${taskBody({ ...view, degraded: "pane" })}`;
287
- }
288
- return sendScreen(response, 200, screen("portfolio", detail, {
289
- chrome: chromeFor(project, "workbench", html `<div id="wb-rail">${rail}</div><p class="meta" id="wb-rail-stamp"></p>`, "all"),
290
- functional: {
291
- // The decision enhancement rides ONLY the overview: a selected
292
- // task's pane may carry a password ceremony, and sensitive
293
- // pages gain no new scripts (commit-1 review, finding 1).
294
- script: regionScript("wb-rail", "rail") +
295
- (selected === null && decisions.length > 0 ? decisionAnswerScript() : ""),
296
- fetches: true,
297
- },
298
- }));
299
- }
300
242
  async function ledgerPage(ctx) {
301
243
  const { url, who, response, now } = ctx;
302
244
  const admitted = admissionList();
@@ -971,11 +913,12 @@ export function createPagesHandlers(runtime) {
971
913
  * switches the session's project; its Open project button posts to /projects/select, which proves it again. The
972
914
  * name is found among the projects this person may open (projectsSelect's rule: visible here and inside the
973
915
  * ceiling); a name it can't see answers exactly like one that doesn't exist. */
974
- async function projectPageHandler(ctx) {
975
- const { url, who, response, now } = ctx;
916
+ async function projectFound(ctx, page) {
917
+ const { url, who, response } = ctx;
976
918
  let name = "";
919
+ const tail = page === "" ? "" : `/${page}`;
977
920
  try {
978
- name = decodeURIComponent(url.pathname.slice("/projects/".length));
921
+ name = decodeURIComponent(url.pathname.slice("/projects/".length, url.pathname.length - tail.length));
979
922
  }
980
923
  catch {
981
924
  name = "";
@@ -988,8 +931,37 @@ export function createPagesHandlers(runtime) {
988
931
  const open = who.via === "cookie" ? who.session.project : null;
989
932
  if (found === null || !(await authorizedProject(liveCeiling(), found.path))) {
990
933
  const words = unknownProject(name === "" ? "by that name" : name);
991
- return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, words), { chrome: chromeFor(open, "projects") }));
934
+ await sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, words), { chrome: chromeFor(open, "projects") }));
935
+ return null;
992
936
  }
937
+ return { ...found, ambiguous: named.length > 1 };
938
+ }
939
+ /** A project's flaky tests list and one row's history. Reads only; found exactly as the project's page is. */
940
+ async function projectFlakyHandler(ctx) {
941
+ const { url, who, response, now } = ctx;
942
+ const found = await projectFound(ctx, "flaky");
943
+ if (found === null)
944
+ return;
945
+ const open = who.via === "cookie" ? who.session.project : null;
946
+ const chrome = chromeFor(open, "projects");
947
+ const back = projectHref(found.name, found.path, found.ambiguous);
948
+ const selfHref = projectHref(found.name, found.path, found.ambiguous, "flaky");
949
+ const key = url.searchParams.get("row");
950
+ if (key !== null) {
951
+ const shown = flakyShow(store, found.path, key, now);
952
+ if (shown === null)
953
+ return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, { title: "Nothing is recorded for that test in this project." }), { chrome }));
954
+ return sendScreen(response, 200, flakyRowPage(chrome, { name: found.name, repo: found.path, back: selfHref, row: shown.row, history: shown.history, now }));
955
+ }
956
+ const days = Number(url.searchParams.get("days") ?? FLAKY_WINDOW_DAYS);
957
+ return sendScreen(response, 200, flakyPage(chrome, { name: found.name, repo: found.path, back, selfHref, list: flakyList(store, found.path, now, days), now }));
958
+ }
959
+ async function projectPageHandler(ctx) {
960
+ const { who, response, now } = ctx;
961
+ const found = await projectFound(ctx, "");
962
+ if (found === null)
963
+ return;
964
+ const open = who.via === "cookie" ? who.session.project : null;
993
965
  const path = found.path;
994
966
  // The tasks that wait on this person or run, from the board's own classification, scoped to this one project.
995
967
  const snapshot = store.boardScoped(path, now, 200, null);
@@ -1009,15 +981,61 @@ export function createPagesHandlers(runtime) {
1009
981
  }
1010
982
  }
1011
983
  return sendScreen(response, 200, projectPage(chromeFor(open, "projects"), {
1012
- name: found.name, path, selfHref: projectHref(found.name, path, named.length > 1), open: open === path, openedAt: found.openedAt,
984
+ name: found.name, path, selfHref: projectHref(found.name, path, found.ambiguous), open: open === path, openedAt: found.openedAt,
1013
985
  peek: peekOf(path, now), tasks, moreTasks: Math.max(0, cards.length - tasks.length),
1014
986
  checks: cookie ? { href: `/settings/checks?repo=${encodeURIComponent(path)}`, level: CHECK_LEVEL_WORDS[projectCheckLevel(store, path).level] } : null,
1015
987
  pullRequests: cookie ? { href: `/settings/pull-requests?repo=${encodeURIComponent(path)}`, on: publishingOf(store, path).on } : null,
1016
988
  knowledgeHref: `/settings/knowledge?repo=${encodeURIComponent(path)}`,
989
+ flakyHref: projectHref(found.name, path, found.ambiguous, "flaky"),
1017
990
  settingsHref: restricted() ? null : `/settings/project?repo=${encodeURIComponent(path)}`,
1018
991
  activity, now,
1019
992
  }));
1020
993
  }
994
+ /** The named project's code map address, for this account; null when it isn't one they can see. */
995
+ async function mapProject(ctx, suffix) {
996
+ let name = "";
997
+ try {
998
+ name = decodeURIComponent(ctx.url.pathname.slice("/projects/".length, -suffix.length));
999
+ }
1000
+ catch {
1001
+ return null;
1002
+ }
1003
+ const { recent, candidates } = visibleProjects();
1004
+ const named = [...recent.map(one => ({ path: one.path, name: one.name })), ...candidates.map(path => ({ path, name: projectName(path) }))].filter(one => one.name === name);
1005
+ const asked = ctx.url.searchParams.get("path") ?? ctx.posted.get("repo");
1006
+ const found = asked !== null ? named.find(one => one.path === asked) ?? null : named.length === 1 ? named[0] : null;
1007
+ if (found === null || !(await authorizedProject(liveCeiling(), found.path)))
1008
+ return null;
1009
+ return { name: found.name, path: found.path, base: `/projects/${encodeURIComponent(found.name)}/map`, selfHref: projectHref(found.name, found.path, named.length > 1) };
1010
+ }
1011
+ async function codeMapHandler(ctx) {
1012
+ const { url, who, response, now } = ctx;
1013
+ const open = who.via === "cookie" ? who.session.project : null;
1014
+ const found = await mapProject(ctx, "/map");
1015
+ if (found === null)
1016
+ return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, unknownProject("by that name")), { chrome: chromeFor(open, "projects") }));
1017
+ const asked = url.searchParams.get("v");
1018
+ const pinned = asked !== null && /^[1-9][0-9]{0,14}$/.test(asked) ? Number(asked) : undefined;
1019
+ const status = codeMapStatus(store, found.path, now, iso => new Date(iso).toISOString().slice(11, 16));
1020
+ const opened = status.state === "off" || status.state === "not-available" ? null : openMap(store, found.path, pinned);
1021
+ const held = opened !== null && opened.ok ? opened : null;
1022
+ const repoed = opened !== null && !opened.ok && pinned !== undefined ? openMap(store, found.path) : null;
1023
+ const map = held ?? (repoed !== null && repoed.ok ? repoed : null);
1024
+ const canRefresh = who.via === "cookie" && who.authority !== "local-owner" && store.accountOf(who.name)?.role === "approver" && store.accountCanAccess(who.name, found.path);
1025
+ return sendScreen(response, 200, codeMapPage(chromeFor(open, "projects"), {
1026
+ name: found.name, repo: found.path, base: found.base, selfHref: found.selfHref, status, map: map?.map ?? null, versionId: map?.version.id ?? null, remoteUrl: store.projectRemote(found.path),
1027
+ q: url.searchParams.get("q"), area: url.searchParams.get("area"), file: url.searchParams.get("file"), canRefresh, now, report: codeMapReportBlock(codeMapReport(store, found.path, now)),
1028
+ said: url.searchParams.get("said"), problem: repoed !== null ? "The map changed. Showing the current one." : url.searchParams.get("problem"),
1029
+ }));
1030
+ }
1031
+ async function codeMapRefreshHandler(ctx) {
1032
+ const { response, who, now } = ctx;
1033
+ const found = await mapProject(ctx, "/map/refresh");
1034
+ if (found === null || !store.accountCanAccess(who.name, found.path))
1035
+ return refuse(response, who, 403, "That project is outside your access.", "/projects");
1036
+ const said = requestRefresh(store, found.path, who.name, now);
1037
+ return redirect(response, `${found.base}?said=${encodeURIComponent(said === "queued" ? "Queued. Main is checked on the next pass." : "Already queued.")}`);
1038
+ }
1021
1039
  async function instanceEdge(ctx) {
1022
1040
  const { request, response, url } = ctx;
1023
1041
  const secret = options.instanceSecret?.() ?? null;
@@ -1135,7 +1153,7 @@ export function createPagesHandlers(runtime) {
1135
1153
  const taskId = url.searchParams.get("task");
1136
1154
  const ref = taskId === null ? null : store.lookupRef(taskId.slice(0, 200));
1137
1155
  if (taskId !== null && (ref === null || ref.repo === null || !visible(ref.repo)))
1138
- return refuse(response, who, 404, "That task is not available.", "/work");
1156
+ return refuse(response, who, 404, "That task is not available.", "/tasks");
1139
1157
  const shown = ref !== null ? ref.repo : project !== null && visible(project) ? project : null;
1140
1158
  const connected = shown === null ? null : buildersFor(store, shown, now, ref?.assignedRunner ?? null).alive.length > 0;
1141
1159
  return sendScreen(response, 200, screen("Start a builder", startBuilderHtml({
@@ -1148,10 +1166,13 @@ export function createPagesHandlers(runtime) {
1148
1166
  "projects.github-list": projectsGithubList,
1149
1167
  "projects.page": projectsIndex,
1150
1168
  "projects.project": projectPageHandler,
1169
+ "projects.map": codeMapHandler,
1170
+ "projects.map-refresh": codeMapRefreshHandler,
1171
+ "projects.flaky": projectFlakyHandler,
1151
1172
  "home": homeRedirect,
1152
1173
  "search": searchPage,
1153
1174
  "morning": morningAlias,
1154
- "workbench": workbench,
1175
+ "workbench": async (ctx) => retiredRedirect("workbench", ctx.url, ctx.response),
1155
1176
  "ledger": ledgerPage,
1156
1177
  "activity": activityPage,
1157
1178
  "system": systemScreen,
@@ -209,7 +209,7 @@ export function chatFleetOverview(snapshot, projects, interactive) {
209
209
  const briefing = "Brief me on what needs my attention, what is building, and the highest-leverage next action across every project.";
210
210
  return html `<section class="card chat-overview" aria-label="live portfolio overview" data-card-kind="fleet-overview"><div class="chat-overview-head"><h2>${projects.length} project${projects.length === 1 ? "" : "s"}</h2>${interactive
211
211
  ? postForm("/chat", html `<button type="submit" name="message" value="${briefing}" class="quiet">Brief me</button>`, { attrs: { class: "inline" } })
212
- : html `<a href="/board?scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/work?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&amp;view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/work" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/work">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
212
+ : html `<a href="/tasks?view=board&scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/tasks?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&amp;view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/tasks" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/tasks">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
213
213
  }
214
214
  /**
215
215
  * The lead's project rail: one bounded pulse per admitted project, plus
@@ -560,7 +560,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
560
560
  icon: html `<path d="M14.7 6.3a4 4 0 0 0-5 5L4 17l3 3 5.7-5.7a4 4 0 0 0 5-5l-2.4 2.4-3-3z"/>`,
561
561
  },
562
562
  agents: { label: "Agents change", action: "change agents", icon: html `<circle cx="12" cy="12" r="3"/><path d="M12 2v3"/><path d="M12 19v3"/><path d="m4.9 4.9 2.2 2.2"/><path d="m16.9 16.9 2.2 2.2"/><path d="M2 12h3"/><path d="M19 12h3"/><path d="m4.9 19.1 2.2-2.2"/><path d="m16.9 7.1 2.2-2.2"/>` },
563
- review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "Request changes" : "Save for later", icon: html `<path d="M4 5h16v12H8l-4 3z"/>` },
563
+ review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "Send back" : "Save for later", icon: html `<path d="M4 5h16v12H8l-4 3z"/>` },
564
564
  control: { label: "Open control", action: "Open", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
565
565
  action: { label: text("title") || "Review action", action: sharedActionPayload(payload) ? CHAT_ACTIONS[sharedActionPayload(payload).operation].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
566
566
  task_action: { label: payload["operation"] === "stop" ? "Stop task?" : payload["operation"] === "resume" ? "Resume task?" : "Task update", action: isChatTaskAction(payload["operation"]) ? CHAT_TASK_ACTIONS[payload["operation"]].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
@@ -0,0 +1,30 @@
1
+ import { type MapStatus } from "../code-map-read.js";
2
+ import { type CodeMapReport } from "../code-map-report.js";
3
+ import type { CodeMap } from "../code-map-shape.js";
4
+ import { type Html } from "../html.js";
5
+ import { type Chrome, type Screen } from "./chrome.js";
6
+ export declare const LIMITS_WORDS: {
7
+ readonly system: "Limits enforced by the system.";
8
+ readonly checked: "Limits checked by Toolroll every 250 ms.";
9
+ };
10
+ export type CodeMapPage = {
11
+ name: string;
12
+ repo: string;
13
+ base: string;
14
+ selfHref: string;
15
+ status: MapStatus;
16
+ map: CodeMap | null;
17
+ versionId: number | null;
18
+ remoteUrl: string | null;
19
+ q: string | null;
20
+ area: string | null;
21
+ file: string | null;
22
+ canRefresh: boolean;
23
+ said: string | null;
24
+ problem: string | null;
25
+ now: Date;
26
+ report: Html | null;
27
+ };
28
+ /** "Builds with and without the map": read by people; nothing switches on it. */
29
+ export declare function codeMapReportBlock(report: CodeMapReport): Html | null;
30
+ export declare function codeMapPage(chrome: Chrome, data: CodeMapPage): Screen;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The Code map page (docs/plans/code-map.md §6): read-only, from the shared reader. Every name on it came from the
3
+ * repository: `html` escapes it, links carry it through encodeURIComponent, and the handler looks it up exactly.
4
+ */
5
+ import { githubRepoOf } from "../pull-request-flow.js";
6
+ import { areaRows, areaView, fileView, searchMap } from "../code-map-read.js";
7
+ import { NOT_ENOUGH, reportRows } from "../code-map-report.js";
8
+ import { html, joinHtml, postForm } from "../html.js";
9
+ import { relativeAge, screen } from "./chrome.js";
10
+ import { btn, disclose, doc, docHeader, empty, facts, infoLine, input, note, notice, phoneHeader } from "./kit.js";
11
+ const WARNING_WORDS = {
12
+ "symbols-truncated": "Some files have more exported names than the map keeps.",
13
+ "edges-truncated": "This repository has more imports than the map keeps.",
14
+ "read-limit": "Past the read limit, the remaining code files are listed by path only.",
15
+ "areas-folded": "The smallest areas are folded into “other areas”.",
16
+ "tsconfig-extends": "Settings inherited through tsconfig “extends” aren't read, so some imports aren't connected.",
17
+ "tsconfig-unreadable": "tsconfig.json couldn't be read; relative imports still work.",
18
+ };
19
+ export const LIMITS_WORDS = { system: "Limits enforced by the system.", checked: "Limits checked by Toolroll every 250 ms." };
20
+ const mono = (text) => html `<code class="k-mono cm-path">${text}</code>`;
21
+ const short = (commit) => commit.slice(0, 7);
22
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
23
+ const hrefOf = (data, extra) => {
24
+ const params = new URLSearchParams(data.versionId === null ? extra : { v: String(data.versionId), ...extra });
25
+ return `${data.base}?${params.toString()}`;
26
+ };
27
+ function areaBlock(data, one, map) {
28
+ const named = (id) => map.areas.find(area => area.id === id)?.name ?? id;
29
+ const links = (list) => joinHtml(list.slice(0, 6).map(use => html `<a class="k-link" href="${hrefOf(data, { area: use.id })}">${named(use.id)}</a>`), ", ");
30
+ return html `<li class="cm-item"><a class="k-link cm-title" href="${hrefOf(data, { area: one.id })}">${one.name}</a>
31
+ <span class="cm-meta">${plural(one.files, "file")}${one.languages.length === 0 ? "" : ` · ${one.languages.join(", ")}`}${one.tests === 0 ? "" : ` · ${plural(one.tests, "test")}`}</span>
32
+ ${one.uses.length === 0 ? "" : html `<span class="cm-meta">Uses ${links(one.uses)}</span>`}
33
+ ${one.usedBy.length === 0 ? "" : html `<span class="cm-meta">Used by ${links(one.usedBy)}</span>`}</li>`;
34
+ }
35
+ const fileBlock = (data, file) => html `<li class="cm-item"><a class="k-link cm-title" href="${hrefOf(data, { file: file.path })}">${file.path}</a>
36
+ <span class="cm-meta">${file.lang} · ${plural(file.bytes, "byte")}${file.test === null ? "" : html ` · Tests: ${file.test}`}</span>
37
+ ${file.symbols.length === 0 ? "" : html `<span class="cm-meta">Exported names: ${file.symbols.slice(0, 12).map(one => one.name).join(", ")}${file.symbols.length > 12 ? ` and ${file.symbols.length - 12} more` : ""}</span>`}</li>`;
38
+ function overview(data, map) {
39
+ return html `<section class="k-sec" aria-labelledby="cm-areas"><h3 id="cm-areas">Areas</h3>
40
+ ${map.areas.length === 0 ? note("This repository is empty.") : html `<ul class="cm-list">${areaRows(map).map(one => areaBlock(data, one, map))}</ul>`}</section>`;
41
+ }
42
+ function areaPage(data, map, id) {
43
+ const view = areaView(map, id);
44
+ if (view === null)
45
+ return notice("That area isn't in this version of the map.");
46
+ return html `<section class="k-sec" aria-labelledby="cm-area"><h3 id="cm-area">${view.area.name}</h3>
47
+ ${facts([
48
+ ["Files", String(view.area.files)], ["Tests", String(view.area.tests)],
49
+ ...(view.entryPoints.length === 0 ? [] : [["Entry points", joinHtml(view.entryPoints.map(one => html `<a class="k-link" href="${hrefOf(data, { file: one })}">${one}</a>`), ", ")]]),
50
+ ...(view.external.length === 0 ? [] : [["Packages", view.external.join(", ")]]),
51
+ ...(view.area.uses.length === 0 ? [] : [["Uses", joinHtml(view.area.uses.map(one => html `<a class="k-link" href="${hrefOf(data, { area: one.id })}">${one.id}</a> (${String(one.count)})`), ", ")]]),
52
+ ...(view.area.usedBy.length === 0 ? [] : [["Used by", joinHtml(view.area.usedBy.map(one => html `<a class="k-link" href="${hrefOf(data, { area: one.id })}">${one.id}</a> (${String(one.count)})`), ", ")]]),
53
+ ])}</section>
54
+ <section class="k-sec" aria-labelledby="cm-files"><h3 id="cm-files">Files</h3><ul class="cm-list">${view.files.slice(0, 500).map(one => fileBlock(data, one))}</ul>
55
+ ${view.files.length > 500 ? note(`${view.files.length - 500} more. Search to find them.`) : ""}</section>`;
56
+ }
57
+ function filePage(data, map, path) {
58
+ const view = fileView(map, path);
59
+ if (view === null)
60
+ return notice("That file isn't in this version of the map.");
61
+ const github = data.remoteUrl === null ? null : githubRepoOf(data.remoteUrl);
62
+ const list = (rows) => rows.length === 0 ? note("None.") : html `<ul class="cm-list">${rows.slice(0, 200).map(one => html `<li class="cm-item"><a class="k-link cm-title" href="${hrefOf(data, { file: one.path })}">${one.path}</a><span class="cm-meta">line ${String(one.line)}</span></li>`)}</ul>`;
63
+ return html `<section class="k-sec" aria-labelledby="cm-file"><h3 id="cm-file">${mono(view.file.path)}</h3>
64
+ ${github === null ? "" : html `<p><a class="k-link" rel="noopener noreferrer" target="_blank" href="${`https://github.com/${github}/blob/${map.commit}/${view.file.path.split("/").map(encodeURIComponent).join("/")}`}">Open on GitHub at ${short(map.commit)}</a></p>`}
65
+ ${facts([["Area", html `<a class="k-link" href="${hrefOf(data, { area: view.file.area })}">${view.file.area}</a>`], ["Language", view.file.lang], ...(view.file.test === null ? [] : [["Tests", html `<a class="k-link" href="${hrefOf(data, { file: view.file.test })}">${view.file.test}</a>`]])])}</section>
66
+ <section class="k-sec" aria-labelledby="cm-names"><h3 id="cm-names">Exported names</h3>${view.file.symbols.length === 0 ? note(view.file.state === "read" ? "None." : "Names and imports aren't read for this file.") : html `<ul class="cm-list">${view.file.symbols.map(one => html `<li class="cm-item"><span class="cm-title">${one.name}</span><span class="cm-meta">${one.kind} · line ${String(one.line)}</span></li>`)}</ul>`}</section>
67
+ <section class="k-sec" aria-labelledby="cm-imports"><h3 id="cm-imports">Imports</h3>${list(view.imports)}</section>
68
+ <section class="k-sec" aria-labelledby="cm-by"><h3 id="cm-by">Imported by</h3>${list(view.importedBy)}</section>`;
69
+ }
70
+ function searchPage(data, map, query) {
71
+ const hits = searchMap(map, query, 200);
72
+ return html `<section class="k-sec" aria-labelledby="cm-found"><h3 id="cm-found">${plural(hits.length, "match", "matches")}</h3>
73
+ ${hits.length === 0 ? note("Nothing in this version matches.") : html `<ul class="cm-list">${hits.map(one => html `<li class="cm-item"><a class="k-link cm-title" href="${hrefOf(data, { file: one.path })}">${one.name ?? one.path}</a>${one.name === null ? "" : html `<span class="cm-meta">${one.path} · line ${String(one.line)}</span>`}</li>`)}</ul>`}</section>`;
74
+ }
75
+ function provenance(data, map) {
76
+ const report = (() => { try {
77
+ return JSON.parse(data.status.version?.report ?? "{}");
78
+ }
79
+ catch {
80
+ return {};
81
+ } })();
82
+ const c = map.counts;
83
+ return disclose("Where this comes from", joinHtml([
84
+ note(map.label),
85
+ facts([
86
+ ["Remote", data.remoteUrl === null ? "None" : mono(data.remoteUrl)],
87
+ ["Files", `${c.files} (${c.read} read, ${c.pathOnly} by path only, ${c.tooLarge} too large to read)`],
88
+ ...(c.parseErrors > 0 ? [["Unreadable", plural(c.parseErrors, "file")]] : []),
89
+ ...(c.withheld > 0 ? [["Withheld", `${plural(c.withheld, "name")} Toolroll won't show`]] : []),
90
+ ...(c.links + c.submodules > 0 ? [["Not followed", `${plural(c.links, "link")}, ${plural(c.submodules, "submodule")}`]] : []),
91
+ ...(map.vendored.length === 0 ? [] : [["Left out", map.vendored.map(one => `${one.path} (${plural(one.files, "file")})`).join(", ")]]),
92
+ ]),
93
+ note("require() calls aren't counted. Other languages show paths and sizes only."),
94
+ ...map.warnings.map(code => note(WARNING_WORDS[code])),
95
+ ...(report.proxy ? [note(`Read through your proxy at ${report.proxy}.`)] : []),
96
+ ...(report.enforced === undefined ? [] : [note(LIMITS_WORDS[report.enforced])]),
97
+ ]));
98
+ }
99
+ /** "Builds with and without the map": read by people; nothing switches on it. */
100
+ export function codeMapReportBlock(report) {
101
+ if (report.arms.map.families + report.arms.holdout.families === 0)
102
+ return null;
103
+ return html `<section class="k-sec" aria-labelledby="cm-report"><h3 id="cm-report">Builds with and without the map</h3>
104
+ ${report.enough ? "" : note(NOT_ENOUGH)}
105
+ ${facts(reportRows(report).map(row => [row.label, `With ${row.map} · without ${row.holdout}`]))}</section>`;
106
+ }
107
+ export function codeMapPage(chrome, data) {
108
+ const { status, map } = data;
109
+ const age = status.version === null ? "" : ` · ${relativeAge(status.version.builtAt, data.now)}`;
110
+ const meta = map === null ? status.words : `main at ${short(map.commit)}${age}`;
111
+ const check = data.canRefresh && status.state !== "not-available" && status.state !== "off"
112
+ ? postForm(`${data.base}/refresh`, btn("Check now", { sm: true, type: "submit" }), { returnTo: data.selfHref, hidden: { repo: data.repo } }) : "";
113
+ const stated = data.problem !== null ? notice(data.problem) : data.said !== null ? infoLine(data.said) : "";
114
+ const warn = status.state === "couldnt-check" || status.state === "too-large" || status.state === "remote-changed" ? notice(status.words) : status.state === "stale" ? infoLine(status.words) : "";
115
+ let content;
116
+ if (map === null) {
117
+ content = status.state === "making" ? empty({ title: "Making the first map.", text: "It appears here when it's ready." })
118
+ : status.state === "off" ? empty({ title: "The code map is off.", text: "Turn it on in this project's settings." })
119
+ : status.state === "not-available" ? empty({ title: "No code map.", text: status.words })
120
+ : status.state === "no-main" ? empty({ title: "No main yet.", text: status.words })
121
+ : "";
122
+ }
123
+ else {
124
+ const search = html `<form class="cm-search" method="get" action="${data.base}" role="search">
125
+ ${data.versionId === null ? "" : html `<input type="hidden" name="v" value="${String(data.versionId)}">`}
126
+ ${input({ id: "cm-q", name: "q", type: "search", value: data.q ?? "", attrs: { "aria-label": "Search files and exported names", placeholder: "Search files and exported names" } })}
127
+ ${btn("Search", { sm: true, type: "submit" })}</form>`;
128
+ const view = data.q !== null && data.q.trim() !== "" ? searchPage(data, map, data.q) : data.file !== null ? filePage(data, map, data.file) : data.area !== null ? areaPage(data, map, data.area) : overview(data, map);
129
+ const atTop = data.q === null && data.file === null && data.area === null;
130
+ content = joinHtml([search, provenance(data, map), atTop ? "" : html `<p><a class="k-link" href="${hrefOf(data, {})}">All areas</a></p>`, view, atTop ? data.report ?? "" : ""]);
131
+ }
132
+ const body = joinHtml([
133
+ phoneHeader("Code map", { back: data.selfHref.replace(/\/map(?:\?.*)?$/, "") }),
134
+ doc(docHeader({ back: [data.base.replace(/\/map$/, ""), data.name], title: "Code map", meta, ...(check === "" ? {} : { right: check }), same: true }), stated, warn, content),
135
+ ]);
136
+ return screen("Code map", body, { chrome });
137
+ }
@@ -0,0 +1,20 @@
1
+ import { type FlakyList, type FlakyObservation, type FlakyRow } from "../flaky.js";
2
+ import { type Chrome, type Screen } from "./chrome.js";
3
+ /** The list. */
4
+ export declare function flakyPage(chrome: Chrome, data: {
5
+ name: string;
6
+ repo: string;
7
+ back: string;
8
+ selfHref: string;
9
+ list: FlakyList;
10
+ now: Date;
11
+ }): Screen;
12
+ /** One row's history. */
13
+ export declare function flakyRowPage(chrome: Chrome, data: {
14
+ name: string;
15
+ repo: string;
16
+ back: string;
17
+ row: FlakyRow;
18
+ history: readonly FlakyObservation[];
19
+ now: Date;
20
+ }): Screen;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The flaky tests list (docs/plans/learning-loops.md, "The list"): a project's tests with mixed outcomes under recorded
3
+ * conditions, on the kit. Reads only; the one action is "Make a task to fix it", the ordinary /tasks/add form, and a
4
+ * person approves that task as usual. Test names are candidate-written text, escaped at the sink like everything else.
5
+ */
6
+ import { flakyCoverageWords, flakyStatusWords } from "../flaky-cli.js";
7
+ import { fixTaskOf } from "../flaky.js";
8
+ import { html, joinHtml, postForm } from "../html.js";
9
+ import { relativeAge, screen } from "./chrome.js";
10
+ import { btn, chip, disclose, doc, docHeader, empty, facts, note, phoneHeader, row } from "./kit.js";
11
+ const KIND_WORDS = { regate: "Checks run again on the same commit", "same-tree": "Two checks of the same code", "in-check-retry": "Passed on a retry inside one check", github: "GitHub re-run" };
12
+ /** "Make a task to fix it": the ordinary task form, filled in. */
13
+ function fixForm(repo, data, returnTo) {
14
+ const task = fixTaskOf(data);
15
+ return postForm("/tasks/add", btn("Make a task to fix it", { sm: true, type: "submit" }), { returnTo, hidden: { title: task.title, goal: task.goal, repo } });
16
+ }
17
+ const statusChip = (data) => chip(flakyStatusWords(data), "", { dot: false });
18
+ /** The list. */
19
+ export function flakyPage(chrome, data) {
20
+ const { list } = data;
21
+ const rowHref = (key) => `${data.selfHref}${data.selfHref.includes("?") ? "&" : "?"}row=${encodeURIComponent(key)}`;
22
+ const rows = list.rows.map(one => row({
23
+ href: rowHref(one.key), chip: statusChip(one), title: one.name,
24
+ desc: `${one.observations} in ${list.days} days · ${one.conditions.join(", ") || "conditions not recorded"} · ${one.hardFailures} hard failure${one.hardFailures === 1 ? "" : "s"} · ${one.sources.join(", ") || "no source recorded"}`,
25
+ meta: html `<span class="k-num">${relativeAge(one.lastSeen, data.now)}</span>`,
26
+ action: fixForm(data.repo, one, data.selfHref),
27
+ }));
28
+ const shortHistory = list.complete ? "" : note(`Not enough recorded history: test results are kept for 180 days, so ${list.days} days can't be counted in full.`);
29
+ const body = list.rows.length === 0
30
+ ? (list.complete ? empty({ title: "No mixed outcomes recorded" }) : "")
31
+ : html `<div class="flaky-rows">${joinHtml(rows)}</div>`;
32
+ return screen("Flaky tests", joinHtml([
33
+ phoneHeader("Flaky tests", { back: data.back }),
34
+ doc(docHeader({ back: [data.back, data.name], title: "Flaky tests", meta: "Mixed outcomes under recorded conditions" }), shortHistory, body, disclose("What this covers", joinHtml([
35
+ note(flakyCoverageWords(list)),
36
+ list.notPaired === 0 ? "" : note(`${list.notPaired} GitHub re-run${list.notPaired === 1 ? "" : "s"} not paired.`),
37
+ note("Not recorded: network, other processes, time of day."),
38
+ ]))),
39
+ ], "\n"), { chrome });
40
+ }
41
+ /** One row's history. */
42
+ export function flakyRowPage(chrome, data) {
43
+ const one = data.row;
44
+ const history = data.history.length === 0 ? note("No history recorded.") : joinHtml(data.history.map(entry => row({
45
+ chip: html ``, title: KIND_WORDS[entry.kind],
46
+ desc: `${entry.failed} failed · ${entry.passed} passed · ${entry.commit ?? "commit not recorded"} · ${entry.conditions}`,
47
+ meta: html `<span class="k-num">${relativeAge(entry.at, data.now)}</span>`,
48
+ })));
49
+ return screen(one.name, joinHtml([
50
+ phoneHeader("Flaky test", { back: data.back }),
51
+ doc(docHeader({ back: [data.back, "Flaky tests"], title: one.name, meta: flakyStatusWords(one), right: fixForm(data.repo, one, data.back) }), facts([
52
+ ["Seen", `${one.observations} time${one.observations === 1 ? "" : "s"}, last ${relativeAge(one.lastSeen, data.now)}`],
53
+ ["Hard failures", String(one.hardFailures)],
54
+ ["Conditions", one.conditions.join(", ") || "not recorded"],
55
+ ["Recorded by", one.sources.join(", ") || "not recorded"],
56
+ ["Test file last changed", one.fileChanged === null ? "not recorded" : relativeAge(one.fileChanged, data.now)],
57
+ ]), html `<section class="k-sec" aria-labelledby="f-history"><h3 id="f-history">History</h3>${history}</section>`),
58
+ ], "\n"), { chrome });
59
+ }
@@ -22,7 +22,7 @@ export function runPage(chrome, run, taskId, artifacts, terminal = null, notes =
22
22
  ? note(html `The live transcript is off. Start serve with <span class="k-mono">--runner &lt;name&gt;</span> naming this machine's worker, and it appears here.`)
23
23
  : run.provider !== "claude"
24
24
  ? note(`The live transcript needs the claude harness for now; this build runs on ${run.provider}.`)
25
- : html `${note("Display only: this is not evidence, and the machine running the agent could alter it.")}<pre id="live-transcript" class="k-mono h-pre h-live"></pre><p class="k-note" id="live-transcript-state"></p>`);
25
+ : html `<pre id="live-transcript" class="k-mono h-pre h-live"></pre><p class="k-note" id="live-transcript-state"></p>`);
26
26
  // The live peek region (A2): the poller fills it only on a serve that asserted its runner; otherwise the section
27
27
  // says why it is empty, so the click never lands on silence.
28
28
  const peek = !running ? "" : sec("What is changing right now", peekable
@@ -1,6 +1,8 @@
1
1
  import type { QualityMode } from "../quality.js";
2
2
  import type { UnattendedPermissionMode } from "../scope.js";
3
3
  import { type Chrome, type Screen } from "./chrome.js";
4
+ import type { Scope } from "../scope.js";
5
+ import type { Store } from "../store.js";
4
6
  export type TaskComposerPrefill = {
5
7
  title: string;
6
8
  goal: string;
@@ -26,4 +28,27 @@ draft?: string | null,
26
28
  suggestions?: ReadonlyArray<{
27
29
  label: string;
28
30
  draft: string;
29
- }>): Screen;
31
+ }>,
32
+ /** A link the lead or a person put in the address (GET ?from=). */
33
+ fromLink?: string | null): Screen;
34
+ /** The terms as label and words: the lines every approval surface reads (fast-terms.ts), with nothing hidden. */
35
+ export declare function fastTermsRows(store: Store, scope: Scope, repo: string): ReadonlyArray<readonly [string, string]>;
36
+ /** Build (docs/plans/plain-toolroll.md P2.4): the exact terms this small task would be approved on, and the one button
37
+ * that approves them. The fields of "Plan it first" are the person's own words, on the ordinary road. */
38
+ export declare function fastStartPage(chrome: Chrome, input: {
39
+ title: string;
40
+ repo: string;
41
+ preview: string;
42
+ digest: string;
43
+ terms: ReadonlyArray<readonly [string, string]>;
44
+ }): Screen;
45
+ /** An issue or pull request, read and quoted before anything is filed. Add task files it for the ordinary approval. */
46
+ export declare function importPage(chrome: Chrome, input: {
47
+ repo: string;
48
+ url: string;
49
+ title: string;
50
+ body: string;
51
+ kind: "issue" | "pull";
52
+ number: number;
53
+ head: string | null;
54
+ }): Screen;