toolroll 0.9.93 → 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 (328) hide show
  1. package/CHANGELOG.md +68 -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-ui.js +1 -1
  11. package/dist/assignment.js +5 -0
  12. package/dist/boot-identity.d.ts +2 -1
  13. package/dist/boot-identity.js +6 -4
  14. package/dist/bounded-spawn.d.ts +57 -0
  15. package/dist/bounded-spawn.js +220 -0
  16. package/dist/browser/terminal.js +42 -42
  17. package/dist/browser/workspace.css +1 -1
  18. package/dist/browser/workspace.js +32 -32
  19. package/dist/browser-shell.d.ts +1 -1
  20. package/dist/browser-shell.js +2 -2
  21. package/dist/browser-workspace.d.ts +45 -2
  22. package/dist/browser-workspace.js +3 -3
  23. package/dist/builder-restart.d.ts +21 -0
  24. package/dist/builder-restart.js +50 -0
  25. package/dist/builder.d.ts +4 -0
  26. package/dist/builder.js +53 -24
  27. package/dist/chat-acceptance.js +1 -1
  28. package/dist/chat-actions.d.ts +12 -2
  29. package/dist/chat-actions.js +25 -2
  30. package/dist/chat-channel.js +11 -0
  31. package/dist/chat-decide.d.ts +7 -2
  32. package/dist/chat-decide.js +37 -9
  33. package/dist/chat-quiet.js +3 -3
  34. package/dist/chat-voice.d.ts +1 -1
  35. package/dist/chat-voice.js +2 -2
  36. package/dist/check-facts.d.ts +3 -1
  37. package/dist/check-facts.js +57 -11
  38. package/dist/check-failure.d.ts +22 -3
  39. package/dist/check-failure.js +57 -13
  40. package/dist/check-levels-ui.d.ts +4 -0
  41. package/dist/check-levels-ui.js +10 -1
  42. package/dist/check-load.d.ts +85 -0
  43. package/dist/check-load.js +147 -0
  44. package/dist/check-only-standing.d.ts +36 -0
  45. package/dist/check-only-standing.js +39 -0
  46. package/dist/check-only.d.ts +15 -0
  47. package/dist/check-only.js +99 -0
  48. package/dist/check-report.d.ts +2 -0
  49. package/dist/check-report.js +1 -1
  50. package/dist/check-stamp.d.ts +46 -0
  51. package/dist/check-stamp.js +93 -0
  52. package/dist/checkout-cleanup.d.ts +36 -5
  53. package/dist/checkout-cleanup.js +166 -14
  54. package/dist/cli-help.d.ts +4 -1
  55. package/dist/cli-help.js +47 -30
  56. package/dist/cli.js +11 -1
  57. package/dist/code-extract.d.ts +44 -0
  58. package/dist/code-extract.js +88 -0
  59. package/dist/code-map-acts.d.ts +24 -0
  60. package/dist/code-map-acts.js +53 -0
  61. package/dist/code-map-admission.d.ts +52 -0
  62. package/dist/code-map-admission.js +295 -0
  63. package/dist/code-map-build.d.ts +63 -0
  64. package/dist/code-map-build.js +415 -0
  65. package/dist/code-map-cli.d.ts +153 -0
  66. package/dist/code-map-cli.js +103 -0
  67. package/dist/code-map-main.d.ts +1 -0
  68. package/dist/code-map-main.js +48 -0
  69. package/dist/code-map-read.d.ts +106 -0
  70. package/dist/code-map-read.js +171 -0
  71. package/dist/code-map-report.d.ts +37 -0
  72. package/dist/code-map-report.js +95 -0
  73. package/dist/code-map-shape.d.ts +172 -0
  74. package/dist/code-map-shape.js +327 -0
  75. package/dist/code-map-store.d.ts +136 -0
  76. package/dist/code-map-store.js +233 -0
  77. package/dist/coding-provider.d.ts +2 -0
  78. package/dist/coding-provider.js +3 -2
  79. package/dist/coding-update.js +3 -2
  80. package/dist/coding-workspace.js +4 -4
  81. package/dist/console-chrome.js +2 -2
  82. package/dist/containment.d.ts +5 -0
  83. package/dist/containment.js +14 -1
  84. package/dist/contracts/chat-actions.d.ts +9 -0
  85. package/dist/contracts/chat-actions.js +2 -0
  86. package/dist/contracts/cli.d.ts +52 -0
  87. package/dist/contracts/cli.js +4 -0
  88. package/dist/contracts/console-api.d.ts +10 -3
  89. package/dist/contracts/console-api.js +10 -3
  90. package/dist/contracts/gateway-tools.d.ts +17 -0
  91. package/dist/contracts/gateway-tools.js +3 -1
  92. package/dist/contracts/lead-tools.d.ts +61 -0
  93. package/dist/contracts/lead-tools.js +10 -1
  94. package/dist/contracts/project-knowledge.d.ts +38 -5
  95. package/dist/contracts/project-knowledge.js +11 -5
  96. package/dist/contracts/project-memory.d.ts +3 -1
  97. package/dist/contracts/project-memory.js +3 -2
  98. package/dist/contracts/storage.d.ts +14 -0
  99. package/dist/contracts/storage.js +3 -1
  100. package/dist/contracts/store-json.d.ts +2 -0
  101. package/dist/contracts/youiyoux-request.d.ts +1 -1
  102. package/dist/contracts/youiyoux-request.js +1 -1
  103. package/dist/desktop-access.js +6 -2
  104. package/dist/dispatch.js +18 -15
  105. package/dist/dispose.js +8 -2
  106. package/dist/easy-start.js +2 -3
  107. package/dist/evals.js +2 -1
  108. package/dist/fast-start.d.ts +136 -0
  109. package/dist/fast-start.js +431 -0
  110. package/dist/fast-terms.d.ts +9 -0
  111. package/dist/fast-terms.js +23 -0
  112. package/dist/fenced-check-coverage.d.ts +62 -0
  113. package/dist/fenced-check-coverage.js +54 -0
  114. package/dist/first-run.js +1 -1
  115. package/dist/flaky-ci.d.ts +31 -0
  116. package/dist/flaky-ci.js +149 -0
  117. package/dist/flaky-cli.d.ts +60 -0
  118. package/dist/flaky-cli.js +60 -0
  119. package/dist/flaky.d.ts +75 -9
  120. package/dist/flaky.js +255 -0
  121. package/dist/flow-card-task.js +3 -0
  122. package/dist/flow-triggers.d.ts +1 -1
  123. package/dist/flow-triggers.js +1 -1
  124. package/dist/github-import.d.ts +62 -0
  125. package/dist/github-import.js +146 -0
  126. package/dist/guides.js +15 -10
  127. package/dist/invoke.d.ts +5 -1
  128. package/dist/invoke.js +49 -14
  129. package/dist/job-object-helper.ps1 +10 -3
  130. package/dist/keymap.d.ts +41 -0
  131. package/dist/keymap.js +51 -0
  132. package/dist/knowledge-cli.js +1 -1
  133. package/dist/knowledge-ui.d.ts +21 -4
  134. package/dist/knowledge-ui.js +75 -7
  135. package/dist/laptop-background.d.ts +16 -0
  136. package/dist/laptop-background.js +108 -2
  137. package/dist/lead-context.js +1 -1
  138. package/dist/lead-contract.d.ts +1 -1
  139. package/dist/lead-contract.js +2 -2
  140. package/dist/lead-progress.js +3 -0
  141. package/dist/lead-status.d.ts +2 -0
  142. package/dist/lead-status.js +6 -2
  143. package/dist/lead-tools.js +51 -2
  144. package/dist/lead.js +1 -1
  145. package/dist/lesson-notes.d.ts +81 -0
  146. package/dist/lesson-notes.js +336 -0
  147. package/dist/limits-ui.js +1 -1
  148. package/dist/local-owner.d.ts +1 -1
  149. package/dist/local-owner.js +11 -0
  150. package/dist/machine-check.d.ts +16 -0
  151. package/dist/machine-check.js +26 -0
  152. package/dist/machine-identity.d.ts +75 -0
  153. package/dist/machine-identity.js +170 -0
  154. package/dist/mcp-core.js +11 -1
  155. package/dist/memory-cli.d.ts +300 -1
  156. package/dist/memory-cli.js +62 -9
  157. package/dist/memory-pass.d.ts +16 -0
  158. package/dist/memory-pass.js +67 -3
  159. package/dist/memory-tidy.d.ts +181 -0
  160. package/dist/memory-tidy.js +523 -0
  161. package/dist/memory-use.d.ts +35 -0
  162. package/dist/memory-use.js +109 -0
  163. package/dist/merge-checklist.d.ts +55 -0
  164. package/dist/merge-checklist.js +182 -0
  165. package/dist/needs-you.d.ts +27 -8
  166. package/dist/needs-you.js +96 -39
  167. package/dist/nightly-github.d.ts +2 -0
  168. package/dist/nightly-github.js +4 -4
  169. package/dist/operate-remote-arguments.js +6 -1
  170. package/dist/operate-remote.js +3 -1
  171. package/dist/operate.d.ts +6 -1
  172. package/dist/operate.js +402 -52
  173. package/dist/phase-routing.js +2 -2
  174. package/dist/plain-schema.d.ts +23 -0
  175. package/dist/plain-schema.js +94 -0
  176. package/dist/plain-words.d.ts +20 -0
  177. package/dist/plain-words.js +66 -0
  178. package/dist/planner.js +2 -1
  179. package/dist/process-liveness.d.ts +2 -0
  180. package/dist/process-liveness.js +61 -17
  181. package/dist/project-delete-ui.d.ts +7 -2
  182. package/dist/project-delete-ui.js +12 -1
  183. package/dist/project-delete.js +8 -1
  184. package/dist/project-knowledge.d.ts +18 -11
  185. package/dist/project-knowledge.js +39 -9
  186. package/dist/project-learning.d.ts +2 -1
  187. package/dist/project-learning.js +15 -45
  188. package/dist/project-memory.d.ts +5 -0
  189. package/dist/project-memory.js +22 -2
  190. package/dist/proof.js +30 -5
  191. package/dist/provider-auth.js +1 -1
  192. package/dist/publish.js +9 -1
  193. package/dist/pull-request-flow.js +5 -0
  194. package/dist/push.d.ts +18 -2
  195. package/dist/push.js +40 -16
  196. package/dist/release-adapters.d.ts +9 -0
  197. package/dist/release-adapters.js +76 -0
  198. package/dist/release-auto.js +13 -13
  199. package/dist/release-card.d.ts +1 -2
  200. package/dist/release-card.js +11 -0
  201. package/dist/release-carry.d.ts +7 -2
  202. package/dist/release-carry.js +39 -9
  203. package/dist/release-cli.d.ts +4 -0
  204. package/dist/release-cli.js +29 -22
  205. package/dist/release-coverage.js +1 -1
  206. package/dist/release-next.d.ts +48 -0
  207. package/dist/release-next.js +161 -0
  208. package/dist/release.d.ts +20 -1
  209. package/dist/release.js +76 -8
  210. package/dist/remote-destination.d.ts +36 -0
  211. package/dist/remote-destination.js +217 -0
  212. package/dist/remote-main.d.ts +48 -0
  213. package/dist/remote-main.js +84 -5
  214. package/dist/repository-context.d.ts +3 -2
  215. package/dist/repository-context.js +112 -70
  216. package/dist/restart-certification.d.ts +1 -0
  217. package/dist/restart-certification.js +8 -6
  218. package/dist/result-acts.d.ts +27 -19
  219. package/dist/result-acts.js +36 -25
  220. package/dist/result-follow-ups.d.ts +2 -2
  221. package/dist/result-follow-ups.js +18 -4
  222. package/dist/result-review.d.ts +1 -1
  223. package/dist/route-change-check.d.ts +4 -0
  224. package/dist/route-change-check.js +13 -0
  225. package/dist/route-change.d.ts +22 -0
  226. package/dist/route-change.js +89 -0
  227. package/dist/route-hooks.js +3 -2
  228. package/dist/route-results.d.ts +6 -1
  229. package/dist/route-results.js +14 -5
  230. package/dist/run-closeout.d.ts +1 -1
  231. package/dist/run-closeout.js +7 -5
  232. package/dist/runner-process.js +3 -2
  233. package/dist/runner.js +2 -1
  234. package/dist/scope.d.ts +6 -1
  235. package/dist/scope.js +22 -9
  236. package/dist/serve.d.ts +1 -1
  237. package/dist/serve.js +24 -18
  238. package/dist/server/chat.js +14 -12
  239. package/dist/server/chrome.d.ts +1 -1
  240. package/dist/server/chrome.js +26 -35
  241. package/dist/server/console.css +23 -0
  242. package/dist/server/guards.js +3 -0
  243. package/dist/server/home.d.ts +6 -0
  244. package/dist/server/home.js +21 -13
  245. package/dist/server/http.js +1 -1
  246. package/dist/server/kit.js +1 -1
  247. package/dist/server/merge-ready-view.d.ts +10 -0
  248. package/dist/server/merge-ready-view.js +30 -0
  249. package/dist/server/pages-assets.d.ts +1 -0
  250. package/dist/server/pages-assets.js +8 -5
  251. package/dist/server/pages.js +103 -82
  252. package/dist/server/render-chat.js +2 -2
  253. package/dist/server/render-code-map.d.ts +30 -0
  254. package/dist/server/render-code-map.js +137 -0
  255. package/dist/server/render-flaky.d.ts +20 -0
  256. package/dist/server/render-flaky.js +59 -0
  257. package/dist/server/render-history.js +1 -1
  258. package/dist/server/render-new-task.d.ts +26 -1
  259. package/dist/server/render-new-task.js +30 -5
  260. package/dist/server/render-pages.d.ts +1 -175
  261. package/dist/server/render-pages.js +38 -313
  262. package/dist/server/render-projects.d.ts +2 -1
  263. package/dist/server/render-projects.js +9 -7
  264. package/dist/server/render-release.d.ts +2 -0
  265. package/dist/server/render-release.js +3 -3
  266. package/dist/server/render-results.d.ts +2 -0
  267. package/dist/server/render-results.js +67 -67
  268. package/dist/server/render-tasks.d.ts +13 -2
  269. package/dist/server/render-tasks.js +153 -136
  270. package/dist/server/retired-routes.d.ts +17 -0
  271. package/dist/server/retired-routes.js +53 -0
  272. package/dist/server/route-table.d.ts +3 -0
  273. package/dist/server/route-table.js +45 -12
  274. package/dist/server/settings.js +129 -22
  275. package/dist/server/task-views.js +61 -26
  276. package/dist/server/tasks.d.ts +2 -6
  277. package/dist/server/tasks.js +174 -321
  278. package/dist/server/welcome.js +1 -1
  279. package/dist/settings-groups-ui.js +1 -1
  280. package/dist/setup-guide.d.ts +2 -2
  281. package/dist/stop-proof.js +6 -4
  282. package/dist/storage-sweep-process.js +1 -1
  283. package/dist/storage-sweep.d.ts +5 -3
  284. package/dist/storage-sweep.js +30 -9
  285. package/dist/storage-ui.js +28 -17
  286. package/dist/storage.d.ts +27 -2
  287. package/dist/storage.js +39 -2
  288. package/dist/store.d.ts +80 -5
  289. package/dist/store.js +296 -87
  290. package/dist/surface.js +45 -4
  291. package/dist/sweep-quarantine.d.ts +1 -0
  292. package/dist/sweep-quarantine.js +6 -4
  293. package/dist/tamper-scan.d.ts +4 -0
  294. package/dist/tamper-scan.js +90 -0
  295. package/dist/task-aliases.d.ts +5 -0
  296. package/dist/task-aliases.js +9 -0
  297. package/dist/task-control.js +4 -1
  298. package/dist/task-outcome-cli.d.ts +2 -0
  299. package/dist/task-outcome-cli.js +17 -17
  300. package/dist/task-sizing.d.ts +5 -0
  301. package/dist/task-sizing.js +2 -0
  302. package/dist/task-source.d.ts +7 -0
  303. package/dist/task-source.js +14 -0
  304. package/dist/task-status.d.ts +9 -0
  305. package/dist/task-status.js +40 -49
  306. package/dist/teardown-git.d.ts +16 -0
  307. package/dist/teardown-git.js +63 -5
  308. package/dist/telegram-progress.js +5 -3
  309. package/dist/telegram-status.js +6 -4
  310. package/dist/test-infra.d.ts +8 -0
  311. package/dist/test-infra.js +18 -0
  312. package/dist/tidy.js +1 -1
  313. package/dist/toolroll-update.js +17 -5
  314. package/dist/tui-contract.d.ts +24 -0
  315. package/dist/tui-source.js +67 -8
  316. package/dist/tui.js +51 -51
  317. package/dist/vanished-retry.d.ts +30 -0
  318. package/dist/vanished-retry.js +174 -0
  319. package/dist/work-index.d.ts +2 -0
  320. package/dist/work-index.js +76 -62
  321. package/dist/work-summary.js +2 -0
  322. package/dist/workspace-ui.d.ts +4 -2
  323. package/dist/workspace-ui.js +28 -22
  324. package/dist/worktree.d.ts +12 -3
  325. package/dist/worktree.js +68 -13
  326. package/package.json +1 -1
  327. package/dist/server/render-next.d.ts +0 -63
  328. package/dist/server/render-next.js +0 -109
@@ -0,0 +1,17 @@
1
+ /** Retired addresses (docs/plans/plain-toolroll.md P1.4): Home (`/`) and Tasks (`/tasks`) are the pages; every old page
2
+ * answers with a permanent redirect to its place there, for good, so a bookmark, an old push or an installed phone's old
3
+ * service worker still lands right. A redirect keeps only the query keys its target knows, and decides nothing: the
4
+ * target admits the person and filters what they may see, exactly as if they had opened it. */
5
+ import type { ServerResponse } from "node:http";
6
+ /** The Tasks page's own address for a view, a project and the other keys it knows. */
7
+ export declare function tasksHref(over?: {
8
+ view?: string | null;
9
+ project?: string | null;
10
+ scope?: "all" | null;
11
+ state?: string | null;
12
+ cursor?: string | null;
13
+ }): string;
14
+ /** Each retired GET row (route-table.ts `retired`), and where it now lives. */
15
+ export declare const RETIRED_ROUTES: Readonly<Record<string, (url: URL) => string>>;
16
+ /** A retired GET's answer: 301 to where it lives now. Permanent: the old address never comes back. */
17
+ export declare function retiredRedirect(id: string, url: URL, response: ServerResponse): void;
@@ -0,0 +1,53 @@
1
+ import { SAFETY } from "./http.js";
2
+ /** The Tasks page's own address for a view, a project and the other keys it knows. */
3
+ export function tasksHref(over = {}) {
4
+ const q = new URLSearchParams();
5
+ if (over.scope === "all")
6
+ q.set("scope", "all");
7
+ else if (over.project != null)
8
+ q.set("project", over.project);
9
+ if (over.view != null && over.view !== "all")
10
+ q.set("view", over.view);
11
+ if (over.state != null)
12
+ q.set("state", over.state);
13
+ if (over.cursor != null)
14
+ q.set("cursor", over.cursor);
15
+ return `/tasks${q.size === 0 ? "" : `?${q}`}`;
16
+ }
17
+ /** The one project a link names (one `project` key, never two), or null. */
18
+ const projectOf = (url) => url.searchParams.getAll("project").length === 1 ? url.searchParams.get("project") : null;
19
+ /** The Tasks list views an old link may name, as /tasks names them. */
20
+ const LIST_VIEWS = { all: "all", "needs-you": "needs-you", running: "running", queued: "queued", completed: "completed", done: "completed", archived: "archived", ready: "ready" };
21
+ /** Each retired GET row (route-table.ts `retired`), and where it now lives. */
22
+ export const RETIRED_ROUTES = {
23
+ // The old Tasks list: its view, project, stored-state filter and page; layout=board is the Board view; all=1 every project.
24
+ work: url => {
25
+ const board = url.searchParams.get("layout") === "board";
26
+ const view = board ? "board" : LIST_VIEWS[url.searchParams.get("view") ?? ""] ?? null;
27
+ const all = url.searchParams.get("all") === "1";
28
+ return tasksHref({ view, scope: all ? "all" : null, project: all ? null : projectOf(url), state: board ? null : url.searchParams.get("state"), cursor: board ? null : url.searchParams.get("cursor") });
29
+ },
30
+ // The old inbox and its triage flow: Home holds what needs you.
31
+ inbox: url => { const project = projectOf(url); return project === null ? "/" : `/?project=${encodeURIComponent(project)}`; },
32
+ next: url => { const project = projectOf(url); return project === null ? "/" : `/?project=${encodeURIComponent(project)}`; },
33
+ // The portfolio: Tasks across every project.
34
+ workbench: () => tasksHref({ scope: "all" }),
35
+ // The old board: the Board view, or its order view.
36
+ board: url => url.searchParams.get("view") === "order" ? tasksHref({ view: "order", project: projectOf(url) })
37
+ : url.searchParams.get("scope") === "all" ? tasksHref({ view: "board", scope: "all" }) : tasksHref({ view: "board", project: projectOf(url) }),
38
+ queue: url => tasksHref({ view: "order", project: projectOf(url) }),
39
+ done: url => tasksHref({ view: "done", project: projectOf(url) }),
40
+ // The old review list (a result keeps its own page: /review?result=…).
41
+ review: url => tasksHref({ view: "ready", project: projectOf(url) }),
42
+ // Archive old tasks' preview moved beside Tasks; its POST keeps /work/put-away exactly.
43
+ "work.put-away": url => { const days = url.searchParams.get("days"); return days !== null && /^[0-9]{1,3}$/.test(days) ? `/tasks/archive?days=${days}` : "/tasks/archive"; },
44
+ };
45
+ /** A retired GET's answer: 301 to where it lives now. Permanent: the old address never comes back. */
46
+ export function retiredRedirect(id, url, response) {
47
+ const to = RETIRED_ROUTES[id];
48
+ if (to === undefined)
49
+ throw new Error(`no retired route ${id}`);
50
+ response.setHeader("Location", to(url));
51
+ response.writeHead(301, { ...SAFETY });
52
+ response.end();
53
+ }
@@ -94,6 +94,9 @@ export interface RouteDeclaration {
94
94
  */
95
95
  readonly local: "allow" | "deny";
96
96
  readonly localAct: LocalOwnerAct | null;
97
+ /** A retired page (docs/plans/plain-toolroll.md P1.4): a permanent GET redirect to where it lives now
98
+ * (retired-routes.ts), admitted exactly as that target is, and no page handler of its own. */
99
+ readonly retired?: true;
97
100
  }
98
101
  /** Every edge row, decided: allow where a local owner's cookie may reach it, deny where its adapter refuses one (or no
99
102
  * cookie is ever read there). A new edge row fails the table's load until it is listed here. */
@@ -45,6 +45,7 @@ function row(stage, domain, id, method, pattern, options = {}) {
45
45
  ...(options.roleRefusal === undefined ? {} : { roleRefusal: options.roleRefusal }),
46
46
  ...(options.bodyCap === undefined ? {} : { bodyCap: options.bodyCap }),
47
47
  freshness: options.freshness ?? "never",
48
+ ...(options.retired === true ? { retired: true } : {}),
48
49
  ...localMarkOf(stage, method, id),
49
50
  });
50
51
  }
@@ -54,6 +55,7 @@ function row(stage, domain, id, method, pattern, options = {}) {
54
55
  /** The console POST rows a local owner may use, and the act each is. Everything else is refused. */
55
56
  const LOCAL_POSTS = Object.freeze({
56
57
  "tasks.add": "file-task",
58
+ "tasks.fast": "build-now",
57
59
  "chat.file": "file-task",
58
60
  "chat.send": "lead-send",
59
61
  "chat.queued-remove": "lead-send",
@@ -66,6 +68,7 @@ const LOCAL_POSTS = Object.freeze({
66
68
  "chat.proposal.dismiss": "lead-send",
67
69
  "task.act.steer": "lead-send",
68
70
  "task.act.approve": "approve-plan",
71
+ "task.act.keep-grown": "approve-plan",
69
72
  "decision.answer": "answer-question",
70
73
  "task.act.reply": "answer-question",
71
74
  "task.act.stop": "stop-run",
@@ -91,11 +94,16 @@ const LOCAL_POSTS = Object.freeze({
91
94
  "settings.lead-on": "choose-default-agent",
92
95
  // Choosing which open project the pages show: a view filter, never authorization.
93
96
  "projects.select": "read",
97
+ // On or off for one project: it moves no money, permission or approval (code-map-acts.ts re-checks this exact act).
98
+ "settings.code-map": "code-map-switch",
94
99
  // Results routing: lowering only.
95
100
  "settings.models-turn-off": "routing-turn-off",
96
101
  "settings.models-not-now": "dismiss-suggestion",
97
102
  "settings.models-suggest-off": "dismiss-suggestion",
98
103
  "settings.models-stop": "trial-stop",
104
+ // Learning loops (D3): a person at the laptop decides lesson notes and clean-ups of project memory.
105
+ "settings.knowledge-note": "memory-review",
106
+ "settings.knowledge-tidy": "memory-review",
99
107
  "agents.seen": "mark-seen",
100
108
  "agents.watch": "watch-agent",
101
109
  });
@@ -245,30 +253,43 @@ get("pages", "projects.page", "/projects", { limited: "unscoped", ...free });
245
253
  // project this account can see and never switches the session's project; its Open project button posts to
246
254
  // /projects/select. The handler resolves the name among the account's visible projects; anything else is not found.
247
255
  get("pages", "projects.project", "^\\/projects\\/[^/]+$", { limited: "unscoped", ...free, sample: "/projects/payments-api" });
256
+ // One project's code map (docs/plans/code-map.md §6): reads only. The handler resolves the name among the account's
257
+ // visible projects, and looks areas and files up exactly in the stored version.
258
+ get("pages", "projects.map", "^\\/projects\\/[^/]+\\/map$", { limited: "unscoped", ...free, sample: "/projects/payments-api/map" });
259
+ // A project's flaky tests list (docs/plans/learning-loops.md): reads only, found as the project's page finds its project.
260
+ // Its one action, "Make a task to fix it", posts the ordinary /tasks/add form.
261
+ get("pages", "projects.flaky", "^\\/projects\\/[^/]+\\/flaky$", { limited: "unscoped", ...free, sample: "/projects/payments-api/flaky" });
248
262
  get("pages", "home", "/", { limited: "collection", ...free });
249
263
  get("pages", "search", "/search", { limited: "collection", ...free });
250
- get("tasks", "inbox", "/inbox", { limited: "collection", ...free });
251
- get("tasks", "work", "/work", { limited: "collection", ...free });
252
- // Put away old tasks (task-tidy.md): the preview reads only; putting away is an approver's step-up in a browser session.
253
- get("tasks", "work.put-away", "/work/put-away", { limited: "collection", ...free, callers: ["cookie"], ...approver("Only an approver can put tasks away.", "/work") });
254
- post("tasks", "work.put-away.apply", "/work/put-away", { scope: "step-up", limited: "collection", ...approver("Only an approver can put tasks away.", "/work") });
255
- get("tasks", "next", "/next", { limited: "collection" });
264
+ // Retired pages (docs/plans/plain-toolroll.md P1.4): permanent redirects to Home and Tasks, admitted as their target is.
265
+ const RETIRED = { limited: "collection", ...free, retired: true };
266
+ get("tasks", "inbox", "/inbox", RETIRED);
267
+ get("tasks", "work", "/work", RETIRED);
268
+ // Archive old tasks (task-tidy.md): the preview reads only, at /tasks/archive; archiving is an approver's step-up in a
269
+ // browser session, and keeps its POST /work/put-away exactly.
270
+ get("tasks", "work.put-away", "/work/put-away", { ...RETIRED, callers: ["cookie"], ...approver("Only an approver can archive tasks.", "/tasks") });
271
+ get("tasks", "tasks.archive", "/tasks/archive", { limited: "collection", ...free, callers: ["cookie"], ...approver("Only an approver can archive tasks.", "/tasks") });
272
+ post("tasks", "work.put-away.apply", "/work/put-away", { scope: "step-up", limited: "collection", ...approver("Only an approver can archive tasks.", "/tasks") });
273
+ get("tasks", "next", "/next", RETIRED);
256
274
  get("pages", "morning", "/morning", { limited: "collection", ...free }); // a redirect to / now
257
- get("pages", "workbench", "/workbench", free);
258
- get("tasks", "board", "/board", { limited: "collection" });
259
- get("tasks", "review", "/review", { limited: "collection" });
275
+ get("pages", "workbench", "/workbench", RETIRED);
276
+ get("tasks", "board", "/board", RETIRED);
277
+ // A result keeps its own page here (/review?result=…); the bare list is retired to Tasks' Ready view.
278
+ get("tasks", "review", "/review", { limited: "collection", ...free });
279
+ // A push's task link (P1.4): /n/<the task's stored number>, resolved to its page under the viewer's own access.
280
+ get("tasks", "n.task", "^\\/n\\/[1-9][0-9]{0,14}$", { limited: "unscoped", ...free, project: "none", sample: "/n/1" });
260
281
  get("settings", "spend", "/spend", { ...free, ...anyOperator("An instance operator sees spend.", "/") });
261
282
  get("settings", "settings.spend", "/settings/spend", { ...free, ...anyOperator("An instance operator sets spending limits.", "/settings") });
262
283
  get("pages", "ledger", "/ledger", { limited: "unscoped", ...free });
263
284
  get("pages", "activity", "/activity", { limited: "collection", ...free }); // a redirect to / now (Home has the summary)
264
- get("tasks", "done", "/done", { limited: "collection", ...free }); // a redirect to the Tasks list's Finished view now
285
+ get("tasks", "done", "/done", RETIRED);
265
286
  get("settings", "mode", "/mode");
266
287
  get("people", "people.page", "/people", { limited: "unscoped", ...free });
267
288
  get("pages", "system", "/system", free);
268
289
  // Where every "Connect a builder" leads: read-only, open to a project-limited person (the handler shows only a visible project).
269
290
  get("pages", "start-builder", "/start-builder", { limited: "unscoped", ...free });
270
- get("tasks", "tasks", "/tasks", { limited: "collection", ...free }); // a redirect to /work now: never sent to the project opener first
271
- get("tasks", "queue", "/queue");
291
+ get("tasks", "tasks", "/tasks", { limited: "collection", ...free });
292
+ get("tasks", "queue", "/queue", RETIRED);
272
293
  get("pages", "peek", "/peek", free);
273
294
  get("pages", "agents", "/agents", { limited: "unscoped", ...free });
274
295
  // The Agents view's Live grid (docs/plans/agents-view.md §3): read-only, every request filtered to what the viewer may see.
@@ -500,9 +521,13 @@ for (const one of ["accept", "keep", "suggest-on", "try"])
500
521
  for (const one of ["turn-off", "not-now", "suggest-off", "stop"])
501
522
  post("settings", `settings.models-${one}`, `/settings/models/${one}`, operator("An instance operator changes which models run.", "/settings/models"));
502
523
  // Any other models action answers the models area's own refusal.
524
+ post("settings", "settings.code-map", "/settings/project/code-map", { project: "form", ...approver("An approver turns the code map on or off.", "/settings/project") });
525
+ post("settings", "settings.code-map-holdout", "/settings/project/code-map/holdout", { project: "form", ...approver("A person ends the holdout.", "/settings/project") });
503
526
  post("settings", "settings.models-act", "^\\/settings\\/models\\/.*$", { sample: "/settings/models/unknown", ...MODELS });
504
527
  post("settings", "settings.knowledge-refresh", "/settings/knowledge/refresh", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to refresh project context.", "/settings/knowledge") });
505
528
  post("settings", "settings.knowledge-proposal", "/settings/knowledge/proposal", { project: "form", ...approver("Sign in as an approver to decide memory proposals.", "/settings/knowledge") });
529
+ post("settings", "settings.knowledge-note", "/settings/knowledge/note", { project: "form", ...approver("Sign in as an approver to decide lesson notes.", "/settings/knowledge") });
530
+ post("settings", "settings.knowledge-tidy", "/settings/knowledge/tidy", { project: "form", ...approver("Sign in as an approver to clean up project memory.", "/settings/knowledge") });
506
531
  post("settings", "settings.knowledge-decision", "/settings/knowledge/decision", { project: "form", ...approver("Sign in as an approver to change project decisions.", "/settings/knowledge") });
507
532
  post("settings", "settings.knowledge-change", "/settings/knowledge/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to change project knowledge.", "/settings/knowledge") });
508
533
  post("settings", "settings.learning-change", "/settings/learning/change", { project: "form", limited: "unscoped" });
@@ -525,9 +550,12 @@ for (const one of ["messaging", "permission-default", "quality-default", "notifi
525
550
  }
526
551
  post("pages", "projects.github-list", "/projects/github", { callers: ["cookie"], ...anyApprover("your login can watch — adding projects is an approver's act") });
527
552
  post("pages", "projects.select", "/projects/select", { project: "form-path", limited: "unscoped", viewer: true });
553
+ post("pages", "projects.map-refresh", "^\\/projects\\/[^/]+\\/map\\/refresh$", { project: "form", sample: "/projects/payments-api/map/refresh", ...approver("An approver asks for a check of main.", "/projects") });
528
554
  post("pages", "projects.remove", "/projects/remove", { project: "form", ...operator("An instance operator removes projects.", "/settings/project") });
529
555
  post("pages", "projects.open", "/projects/open", { project: "form-path" });
530
556
  post("tasks", "tasks.add", "/tasks/add", { project: "form", limited: "collection", bodyCap: LARGE_FORM });
557
+ // Build (fast-start.ts): a person's own browser session only. No token, chat, agent or webhook reaches it.
558
+ post("tasks", "tasks.fast", "/tasks/fast", { callers: ["cookie"], project: "none", limited: "collection", ...approver("An approver starts builds.", "/tasks/new") });
531
559
  post("tasks", "queue.move", "/queue/move");
532
560
  post("tasks", "queue.note", "/queue/note");
533
561
  post("pages", "fleet.register", "/fleet/runner/register", { scope: "step-up" });
@@ -541,6 +569,9 @@ post("people", "people.revoke", "/people/revoke", { scope: "step-up" });
541
569
  post("pages", "fleet.retire", "/fleet/runner/retire", { scope: "step-up" });
542
570
  // Answering a parked decision needs act access and irreversible-choice confirmation, not password step-up.
543
571
  post("tasks", "decision.answer", `^\\/d\\/${ID}\\/answer$`, { project: "decision", limited: "resource", scope: "act", sample: "/d/1/answer" });
572
+ // A held automatic release goes on: a person, with their password typed now (release-auto.ts allowAutoPublish).
573
+ post("tasks", "task.release.go-on", `^\\/t\\/${TASK}\\/release\\/go-on$`, { project: "task", limited: "resource", scope: "step-up", sample: "/t/one/release/go-on", ...approver("An approver lets a release go on.", "/tasks") });
574
+ post("tasks", "task.act.keep-grown", `^\\/t\\/${TASK}\\/keep-grown$`, { project: "task", limited: "resource", sample: "/t/one/keep-grown", callers: ["cookie"], ...approver("An approver keeps a change.", "/tasks") });
544
575
  post("tasks", "task.act.hold", `^\\/t\\/${TASK}\\/hold$`, { project: "task", limited: "resource", sample: "/t/one/hold" });
545
576
  post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task", limited: "resource", sample: "/t/one/unhold" });
546
577
  post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
@@ -648,6 +679,8 @@ export function assertRouteTable(routes = ROUTES) {
648
679
  throw new Error(`route table: ${route.id} role and its refusal go together`);
649
680
  if (route.role !== "any" && route.stage !== "console")
650
681
  throw new Error(`route table: ${route.id} edge routes prove roles in their protocol`);
682
+ if (route.retired === true && (route.method !== "GET" || route.stage !== "console"))
683
+ throw new Error(`route table: ${route.id} a retired page is a console GET`);
651
684
  const test = new RegExp(route.pattern);
652
685
  if (!test.test(route.sample))
653
686
  throw new Error(`route table: ${route.id} does not match its sample ${route.sample}`);
@@ -7,6 +7,9 @@ import { finishGoogleConsent, GOOGLE_CALLBACK } from "../google-mail.js";
7
7
  import { kitInstalled, kitOf } from "../kits.js";
8
8
  import { CONNECT_CALLBACK, connectionsOf, finishConnect, localConnectOf, oneClickOf } from "../mcp-connect.js";
9
9
  import { learningView } from "../project-learning.js";
10
+ import { memoryUseOf } from "../memory-use.js";
11
+ import { afterwardsWords, lessonNoteOf, noteAfterwards } from "../lesson-notes.js";
12
+ import { decideTidy, disabledLessons, listTidy, proposeTidy, restoreDecision, restoreLesson, restoreReference, undoableMerges, unmergeDecisions } from "../memory-tidy.js";
10
13
  import { skillTestResult } from "../project-skills.js";
11
14
  import { discoverTools, projectToolsOf } from "../project-tools.js";
12
15
  import { lookProjectMcp } from "../folder-look.js";
@@ -48,6 +51,7 @@ import { releaseApprovalSetting, setReleaseApproval } from "../release-auto.js";
48
51
  import { backupHtml } from "../backup-ui.js";
49
52
  import { BACKUP_EVERY_HOURS, backupFolderOf, backupNow, checkBackupFolder, defaultBackupFolder, MAX_KEEP } from "../backup.js";
50
53
  import { projectBatchChecks, setProjectBatchChecks } from '../batch-policy.js';
54
+ import { fastStartOn, setFastStart } from '../fast-start.js';
51
55
  import { ALL_PROJECTS as CHAT_APPROVAL_ALL, chatApprovalWords, setChatApproval } from "../chat-approval.js";
52
56
  import { ChatState } from "../chat-delivery-state.js";
53
57
  import { checkSettingsHtml } from '../check-levels-ui.js';
@@ -67,13 +71,13 @@ import { disconnectGoogle, googleConnected, googleConsent, readGoogleMail, saveG
67
71
  import { integrationsHtml } from "../integrations-ui.js";
68
72
  import { setYouiyouxIntake } from "../youiyoux-intake.js";
69
73
  import { clearProviderKey, keyStatus, plausibleKey, PROVIDER_KEY_ENV, readAuthMode, readProviderKey, saveProviderKey, setAuthMode, SUBSCRIPTION_CAPABLE, verdictWords, verifyProviderKey } from "../keys.js";
70
- import { decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml } from "../knowledge-ui.js";
74
+ import { cleanUpHtml, decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml, referenceRestoreHtml, restoreHtml } from "../knowledge-ui.js";
71
75
  import { aboutYouOf, checkAboutYou, saveAboutYou } from "../lead-about.js";
72
76
  import { cancelCommitment, conditionWords, openCommitments } from '../lead-commitments.js';
73
77
  import { checkLeadIdentity, leadIdentityOf } from "../lead-identity.js";
74
78
  import { mailboxAccess, readThroughImap } from "../mailbox.js";
75
79
  import { startConnect } from "../mcp-connect.js";
76
- import { decideProposal, listProposals, memoryStatus } from "../memory-pass.js";
80
+ import { decideProposal, defaultTidyAnalyzer, listProposals, memoryStatus } from "../memory-pass.js";
77
81
  import { checkModels, isNewModel, modelOptions, modelWords, RUNTIME_TOOLS, runtimeStates, seenModels, setWatch, updateRuntime, watchState } from "../model-catalog.js";
78
82
  import { modelsHtml, modelsScript } from "../models-ui.js";
79
83
  import { acceptSuggestion, dismissSuggestion, grantWords, keepRule, modelName, ruleWords, rulesOf, setSuggestModels, startTrial, stopTrial, suggestionsFor, suggestionWords, suggestModelsOn, trialOffersFor, trialOfferWords, trialsOf, trialWords, turnOffRule } from "../route-rules.js";
@@ -93,6 +97,7 @@ import { isCheckAccess, signInsUnavailableWords, widerThan } from "../check-acce
93
97
  import { LOCAL_MODE_CONFINES, localModeOn } from "../local-owner.js";
94
98
  import { parseMachLookup, parseReadDeny } from "../project-sandbox.js";
95
99
  import { projectDeleteConfirmHtml, projectSettingsHtml } from "../project-delete-ui.js";
100
+ import { endHoldout, setCodeMapSwitch } from "../code-map-acts.js";
96
101
  import { deleteProject, holdingsWords } from "../project-delete.js";
97
102
  import { applySavedKnowledge, changeKnowledge, knowledgeVersion, knowledgeView } from "../project-knowledge.js";
98
103
  import { changeLearning } from "../project-learning.js";
@@ -128,8 +133,8 @@ import { lastSweep, saveSweep, storageSweepOff } from "../storage-sweep.js";
128
133
  import { sweepSchedule } from "../storage-sweep-schedule.js";
129
134
  import { storageHtml } from "../storage-ui.js";
130
135
  import { landedSummary } from "../landed-cleanup.js";
131
- import { bytesWords, parseCleanup } from "../storage.js";
132
- import { isDigestTime, liveBackupState, localModeOf, RESULT_SCREENSHOTS, setLiveBackupSettings, setLocalModeOff } from "../store.js";
136
+ import { bytesWords, parseCleanup, parseIdle } from "../storage.js";
137
+ import { checkoutIdle, isDigestTime, liveBackupState, setCheckoutIdle, localModeOf, RESULT_SCREENSHOTS, setLiveBackupSettings, setLocalModeOff } from "../store.js";
133
138
  import { defaultLiveFolder, liveFolderOf, liveFolderProblem } from "../live-backup.js";
134
139
  import { LIVE_EVERY } from "../backup-ui.js";
135
140
  import { checkTeamsCredentials, clearTeamsCredentials, loadTeamsCredentials, saveTeamsCredentials, TeamsError } from "../teams-api.js";
@@ -353,9 +358,34 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
353
358
  return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
354
359
  const selector = projectPicker("/settings/project", projects.map(repo => ({ repo, name: projectName(repo) })), chosen);
355
360
  const notice = { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") };
356
- const content = chosen === "" ? html `${notice.said ? infoLine(notice.said) : ""}${empty({ title: "No projects yet.", action: btn("Add a project", { sm: true, href: "/projects" }) })}` : (projectSettingsHtml({ ...projectViewOf(chosen, who), remoteUrl: store.projectRemote(chosen) }, notice));
361
+ const content = chosen === "" ? html `${notice.said ? infoLine(notice.said) : ""}${empty({ title: "No projects yet.", action: btn("Add a project", { sm: true, href: "/projects" }) })}` : (projectSettingsHtml({ ...projectViewOf(chosen, who), remoteUrl: store.projectRemote(chosen), codeMap: codeMapView(chosen, who, ctx.now) }, notice));
357
362
  return sendScreen(response, 200, screen("Project", settingsSubPage("projects", "Project", html `${selector}${content}`), { chrome: chromeFor(chosen || null, "settings") }));
358
363
  }
364
+ /** The project's code map section: the switch, and the holdout while new builds are still being enrolled. */
365
+ function codeMapView(repo, who, now) {
366
+ const row = store.handle.prepare("SELECT code_map, code_map_enroll_until FROM project WHERE path = ?").get(repo);
367
+ const until = typeof row?.["code_map_enroll_until"] === "string" ? row["code_map_enroll_until"] : null;
368
+ return { on: row === undefined || Number(row["code_map"]) === 1, enrollUntil: until !== null && Date.parse(until) > now.getTime() ? until : null,
369
+ canEndHoldout: who.via === "cookie" && who.authority !== "local-owner", href: `/projects/${encodeURIComponent(projectName(repo))}/map` };
370
+ }
371
+ function codeMapAct(kind) {
372
+ return async (ctx) => {
373
+ const { response, now, posted } = ctx;
374
+ const who = browserCaller(ctx.who);
375
+ const repo = posted.get("repo") ?? "";
376
+ const back = (key, words) => redirect(response, `/settings/project?repo=${encodeURIComponent(repo)}&${key}=${encodeURIComponent(words)}#code-map`);
377
+ if (posted.getAll("repo").length !== 1 || posted.getAll("on").length > 1)
378
+ return back("problem", "That form was sent twice over. Nothing changed.");
379
+ if (!consoleProjects().includes(repo) || !store.accountCanAccess(who.name, repo))
380
+ return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
381
+ const door = who.authority === "local-owner" ? { kind: "local-owner", actor: who.name } : { kind: "person", person: modelsAuthority(who, "session") };
382
+ const on = posted.get("on") === "1";
383
+ const done = kind === "switch" ? setCodeMapSwitch(store, door, repo, on, now) : endHoldout(store, door, repo, now);
384
+ if (!done.ok)
385
+ return back("problem", done.problem);
386
+ return back("said", kind === "holdout" ? "The holdout has ended. Builds already enrolled keep their arm." : on ? "The code map is on." : "The code map is off.");
387
+ };
388
+ }
359
389
  // v102: Settings → Approvals and safety: the defaults for new tasks, a project's separation of duties and release
360
390
  // approval, and the way to its operating mode. Anyone who sees the project reads them; each form's own route decides
361
391
  // who may change it (an approver for the defaults and releases, an instance operator for the rules).
@@ -467,9 +497,9 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
467
497
  const measured = lastChecks.known("storage");
468
498
  const check = postForm("/settings/storage/check", btn("Check now", { sm: true, type: "submit" }));
469
499
  if (measured === null) {
470
- return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", settingsSection({ id: "storage", title: "Checkouts not measured yet", lead: "Toolroll measures the checkouts in the background. Check now measures them at once.", body: check })), { chrome: chromeFor(project, "settings") }));
500
+ return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", settingsSection({ id: "storage", title: "Task folders not measured yet", lead: "Toolroll measures the task folders in the background. Check now measures them at once.", body: check })), { chrome: chromeFor(project, "settings") }));
471
501
  }
472
- const plan = { ...measured.value, cleanup: store.checkoutCleanup() };
502
+ const plan = { ...measured.value, cleanup: store.checkoutCleanup(), idle: checkoutIdle(store) };
473
503
  const sweep = { last: lastSweep(store), off: storageSweepOff(), schedule: sweepSchedule(store, now) };
474
504
  return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", html `${(storageHtml({ plan, csrf: who.session.csrf, sweep, landed: landedSummary(store), measured: html `<div class="k-acts"><span class="k-note">Measured ${new Date(measured.at).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })}</span>${check}</div>` }, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }))}`), { chrome: chromeFor(project, "settings") }));
475
505
  }
@@ -499,7 +529,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
499
529
  const quick = liveQuickCommand(store, repo);
500
530
  const body = (checkSettingsHtml({ repo, name: projectName(repo), csrf: who.session.csrf, canChange: who.role === "approver" && !store.isDemo(),
501
531
  level: projectCheckLevel(store, repo).level, full: store.liveVerifyCommand(repo), quick, suggestion: quick === null ? suggestQuickCommand(repo) : null,
502
- review: (({ on, source }) => ({ on, source }))(store.reviewSwitch(repo, clock())), batch: { on: projectBatchChecks(store, repo).on },
532
+ review: (({ on, source }) => ({ on, source }))(store.reviewSwitch(repo, clock())), batch: { on: projectBatchChecks(store, repo).on }, fast: { on: fastStartOn(store, repo) },
503
533
  said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }));
504
534
  return sendScreen(response, 200, screen("Checks", settingsSubPage("projects", "Checks", html `${body}`), { chrome: chromeFor(project, "projects") }));
505
535
  }
@@ -577,21 +607,25 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
577
607
  const revision = Number(url.searchParams.get('version'));
578
608
  const previous = knowledgeVersion(store, chosen, who.name, revision);
579
609
  const restore = csrf && who.role === 'approver' && revision !== view.revision ? postForm('/settings/knowledge/change', html `<p>Replaces knowledge for future tasks. Existing runs keep their saved context.</p><button>Restore version ${revision}</button>`, { attrs: { class: 'knowledge' }, hidden: { repo: chosen, identity: view.identity, revision: String(view.revision), action: 'restore', restore: String(revision) } }) : '';
580
- return sendScreen(response, 200, screen('Knowledge history', settingsSubPage('agents', `Knowledge · version ${revision}`, html `<p class="k-note"><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(chosen)}">Current knowledge</a></p>${knowledgeHtml({ ...view, knowledge: previous, history: [], stale: null }, '', false)}${restore}`), { chrome: chromeFor(chosen, 'settings') }));
610
+ return sendScreen(response, 200, screen('Knowledge history', settingsSubPage('agents', `Knowledge · version ${revision}`, html `<p class="k-note"><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(chosen)}">Current knowledge</a></p>${knowledgeHtml({ ...view, knowledge: previous, history: [], stale: null }, '', false)}${restore}${referenceRestoreHtml(chosen, revision, previous.references.filter(ref => !view.knowledge.references.some(now => now.id === ref.id)), !!csrf && who.role === 'approver')}`), { chrome: chromeFor(chosen, 'settings') }));
581
611
  }
582
612
  const query = (url.searchParams.get('q') ?? '').slice(0, 1000);
583
- const result = query.trim() ? repositoryContextRead({ repo: chosen, query, mode: url.searchParams.get('mode') === 'impact' ? 'impact' : 'search', cacheRoot: join(dirname(evidenceRoot), 'repository-context') }) : null;
613
+ const result = query.trim() ? repositoryContextRead({ repo: chosen, project: chosen, query, mode: url.searchParams.get('mode') === 'impact' ? 'impact' : 'search', cacheRoot: join(dirname(evidenceRoot), 'repository-context') }) : null;
584
614
  const memoryQuery = (url.searchParams.get('memory') ?? '').slice(0, 300);
585
615
  const hits = memoryQuery.trim() ? searchMemory(store, { actor: who.name, repos: [chosen], query: memoryQuery, limit: 20 }) : null;
586
616
  let proposals = html ``;
587
617
  try {
588
- proposals = (proposalsHtml(chosen, listProposals(store, chosen), memoryStatus(store, chosen, who.name), csrf, who.role === 'approver'));
618
+ proposals = (proposalsHtml(chosen, listProposals(store, chosen), memoryStatus(store, chosen, who.name), csrf, who.role === 'approver', who.name));
589
619
  }
590
620
  catch {
591
621
  proposals = html ``;
592
622
  }
593
- content = html `<p class="meta">${MEMORY_INTRO}</p>${memorySearchHtml(chosen, memoryQuery, hits)}${proposals}${(decisionsHtml(chosen, listDecisions(store, chosen, who.name, { limit: 50 }), csrf, who.role === 'approver'))}\
594
- ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf : '')}${knowledgeHtml(view, csrf, who.role === 'approver')}\
623
+ const use = memoryUseOf(store, chosen);
624
+ const manageMemory = who.role === 'approver' && csrf !== '';
625
+ const retired = listDecisions(store, chosen, who.name, { status: 'all', limit: 100 }).filter(d => d.status !== 'active');
626
+ content = html `<p class="meta">${MEMORY_INTRO}</p>${memorySearchHtml(chosen, memoryQuery, hits)}${proposals}${cleanUpHtml(chosen, listTidy(store, chosen), manageMemory)}${(decisionsHtml(chosen, listDecisions(store, chosen, who.name, { limit: 50 }), csrf, who.role === 'approver', undefined, undefined, use, d => { const after = noteAfterwards(store, d); return after === null ? null : afterwardsWords(after); }))}\
627
+ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf : '')}${knowledgeHtml(view, csrf, who.role === 'approver', undefined, undefined, use)}\
628
+ ${restoreHtml(chosen, retired, undoableMerges(store, chosen), disabledLessons(store, chosen), manageMemory)}\
595
629
  <details class="knowledge frame"><summary class="frame-head">Learned lessons</summary><div class="frame-panel">${(learningHtml(learningView(store, evidenceRoot, chosen, who.name), csrf, who.role === 'approver'))}</div></details>`;
596
630
  }
597
631
  catch {
@@ -803,11 +837,11 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
803
837
  const { url, who, response } = ctx;
804
838
  const provider = url.pathname.split("/")[2];
805
839
  if (!isProviderId(provider))
806
- return refuse(response, who, 400, "unknown provider", "/work");
840
+ return refuse(response, who, 400, "unknown provider", "/tasks");
807
841
  if (who.role !== "approver")
808
- return refuse(response, who, 403, "Only an operator can resume an agent's work.", "/work");
842
+ return refuse(response, who, 403, "Only an operator can resume an agent's work.", "/tasks");
809
843
  liftAuthPause(store, provider, "person", who.name, clock());
810
- return redirect(response, "/work");
844
+ return redirect(response, "/tasks");
811
845
  }
812
846
  // The update notice is dismissed per browser and per version: a newer release shows it again.
813
847
  // Cockpit B2: Forget — nothing verified on this session before now counts as fresh (a CSRF-checked post, like every act).
@@ -1235,16 +1269,20 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1235
1269
  // What the page shows next is measured again after the change.
1236
1270
  const said = sayTo(response, "/settings/storage");
1237
1271
  const back = async (kind, words) => { await measureStorage(databaseFile).catch(() => undefined); return said(kind, words); };
1238
- const what = op === "clean" ? "clean up" : op === "discard" ? "discard a checkout" : "change checkout cleanup";
1272
+ const what = op === "clean" ? "clean up" : op === "discard" ? "discard a task folder" : "change automatic cleanup";
1239
1273
  if (!authenticateApprover(who, body.get("password") ?? "").ok)
1240
1274
  return back("problem", `Enter your Toolroll password to ${what}.`);
1241
1275
  if (op === "cleanup") {
1242
1276
  const cleanup = parseCleanup(body.get("cleanup") ?? "");
1243
1277
  if (cleanup === undefined)
1244
- return back("problem", "Choose when a finished task's checkout is removed.");
1245
- const before = store.checkoutCleanup();
1278
+ return back("problem", "Choose when a finished task's folder is removed.");
1279
+ const idle = body.get("idle") === null ? checkoutIdle(store) : parseIdle(body.get("idle") ?? "");
1280
+ if (idle === undefined)
1281
+ return back("problem", "Choose when an idle task's folder is removed.");
1282
+ const same = cleanup === store.checkoutCleanup() && idle === checkoutIdle(store);
1246
1283
  store.setCheckoutCleanup(cleanup, who.name, now);
1247
- return back("said", cleanup === before ? "Nothing changed." : "Saved.");
1284
+ setCheckoutIdle(store, idle, who.name, now);
1285
+ return back("said", same ? "Nothing changed." : "Saved.");
1248
1286
  }
1249
1287
  const pool = storagePool(databaseFile);
1250
1288
  if (op === "clean") {
@@ -1256,7 +1294,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1256
1294
  return back("problem", "What a clean-up would remove changed since you looked. Check the list again.");
1257
1295
  const done = await cleanCheckouts(store, pool, clock, { manual: true, actor: who.name, only: new Set(plan.go.map(one => one.path)) });
1258
1296
  saveSweep(store, { at: clock().toISOString(), source: "manual", actor: who.name, parts: [{ kind: "checkouts", count: done.removed.length, bytes: done.freed, failed: done.kept.filter(one => one.why === "git refused").length, items: done.removed.slice(0, 20).map(one => one.path) }] });
1259
- return back("said", done.removed.length === 0 ? "Nothing was removed: those checkouts are no longer ready to go." : `Removed ${done.removed.length} checkout${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}. Their branches stay.`);
1297
+ return back("said", done.removed.length === 0 ? "Nothing was removed: those task folders are no longer ready to go." : `Removed ${done.removed.length} task folder${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}. Their branches stay.`);
1260
1298
  }
1261
1299
  const done = await discardCheckout(store, pool, body.get("path") ?? "", now, who.name);
1262
1300
  return done.ok ? back("said", `Discarded, about ${bytesWords(done.bytes)}. Its branch stays.`) : back("problem", done.message);
@@ -1331,6 +1369,13 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1331
1369
  const changed = setProjectBatchChecks(store, repo, on, who.name, now);
1332
1370
  return back("said", !changed.changed ? "Saved." : on ? "Batch checks are on." : "Batch checks are off.");
1333
1371
  }
1372
+ // Small tasks start right away: a person's own logged settings act, behind this form's password.
1373
+ if (act === "small-tasks") {
1374
+ const on = body.get("on") === "1";
1375
+ store.upsertProject(repo, projectName(repo), now);
1376
+ setFastStart(store, repo, on, who.name, now);
1377
+ return back("said", on ? "Small tasks start right away." : "Small tasks wait for a plan.");
1378
+ }
1334
1379
  if (act === "level") {
1335
1380
  const level = body.get("level");
1336
1381
  if (!isCheckLevel(level))
@@ -1736,7 +1781,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1736
1781
  const repo = body.get('repo') ?? '';
1737
1782
  if (!visible(repo) || !store.accountCanAccess(who.name, repo) || !consoleProjects().includes(repo))
1738
1783
  return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1739
- const refreshed = await repositoryContext({ repo, query: '', refresh: true, cacheRoot: join(dirname(evidenceRoot), 'repository-context') });
1784
+ const refreshed = await repositoryContext({ repo, project: repo, query: '', refresh: true, cacheRoot: join(dirname(evidenceRoot), 'repository-context') });
1740
1785
  return sendScreen(response, refreshed.index.status === 'ready' ? 200 : 503, screen('Project context', settingsSubPage("agents", "Project context", html `<p>${refreshed.index.status === 'ready' ? 'Code index refreshed.' : 'The index is unavailable. Source search still works.'}</p><a class="button-link" href="/settings/knowledge?repo=${encodeURIComponent(repo)}">Open knowledge</a>`), { chrome: chromeFor(repo, 'settings') }));
1741
1786
  }
1742
1787
  async function knowledgeProposal(ctx) {
@@ -1755,6 +1800,64 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1755
1800
  }
1756
1801
  return redirect(response, `/settings/knowledge?repo=${encodeURIComponent(repo)}&saved=1`);
1757
1802
  }
1803
+ /** A lesson note: a person's own words, accepted as a decision through the one proposal writer, or rejected. */
1804
+ async function knowledgeNote(ctx) {
1805
+ const { who, response, now, posted } = ctx;
1806
+ const body = readForm(posted, CONSOLE_FORMS.knowledgeNote);
1807
+ const repo = body.get('repo') ?? '', decision = body.get('decision') ?? '';
1808
+ if (!visible(repo) || !consoleProjects().includes(repo))
1809
+ return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1810
+ if (!['accept', 'reject'].includes(decision) || !/^[0-9]{1,12}$/.test(body.get('proposal') ?? ''))
1811
+ return refuse(response, who, 400, 'Choose accept or reject for one note.', '/settings/knowledge');
1812
+ const id = Number(body.get('proposal'));
1813
+ try {
1814
+ if (lessonNoteOf(store, id) === null)
1815
+ throw Error('That note is unavailable.');
1816
+ decideProposal(store, { repo, actor: who.name, id, decision: decision, claim: body.get('claim') ?? '' }, now);
1817
+ }
1818
+ catch (error) {
1819
+ return sendScreen(response, 409, screen('Knowledge', settingsSubPage("agents", "Knowledge", html `<p role="alert">${error instanceof Error ? error.message : 'That note could not be decided.'}</p><p><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(repo)}">Back to Knowledge</a></p>`), { chrome: chromeFor(repo, 'settings') }));
1820
+ }
1821
+ return redirect(response, `/settings/knowledge?repo=${encodeURIComponent(repo)}&saved=1`);
1822
+ }
1823
+ /** Project memory's clean-up: a person's request, accepting or rejecting one proposal, and restores. Every writer
1824
+ * proves the person inside its own transaction. */
1825
+ async function knowledgeTidy(ctx) {
1826
+ const { who, response, now, posted } = ctx;
1827
+ const body = readForm(posted, CONSOLE_FORMS.knowledgeTidy);
1828
+ const repo = body.get('repo') ?? '', action = body.get('action') ?? '', target = body.get('target') ?? '';
1829
+ if (!visible(repo) || !consoleProjects().includes(repo))
1830
+ return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1831
+ const back = `/settings/knowledge?repo=${encodeURIComponent(repo)}`;
1832
+ const said = (id) => /^[0-9]{1,12}$/.test(id) ? Number(id) : null;
1833
+ // Only a browser session is a person's proof here; the writers refuse anything else.
1834
+ const proof = who.via === 'cookie' ? 'session' : 'remote-token';
1835
+ try {
1836
+ if (action === 'propose')
1837
+ await proposeTidy(store, { repo, actor: who.name, analyzer: defaultTidyAnalyzer(store, store.databaseFile() === null ? {} : { configDir: dirname(store.databaseFile()) }) }, now);
1838
+ else if (action === 'accept' || action === 'reject') {
1839
+ const id = said(body.get('proposal') ?? '');
1840
+ if (id === null)
1841
+ throw Error('Choose one proposal.');
1842
+ const choice = body.get('choice'), claim = body.get('claim'), why = body.get('why');
1843
+ decideTidy(store, evidenceRoot, { repo, actor: who.name, id, decision: action, proof, ...(choice === null ? {} : { choice }), ...(claim === null ? {} : { claim }), ...(why === null ? {} : { why }) }, now);
1844
+ }
1845
+ else if (action === 'restore' && said(target) !== null)
1846
+ restoreDecision(store, { repo, actor: who.name, id: said(target), proof }, now);
1847
+ else if (action === 'unmerge' && said(target) !== null)
1848
+ unmergeDecisions(store, { repo, actor: who.name, merge: said(target), proof }, now);
1849
+ else if (action === 'readopt' && said(target) !== null)
1850
+ restoreLesson(store, evidenceRoot, { repo, actor: who.name, id: said(target), proof }, now);
1851
+ else if (action === 'reference' && /^[a-f0-9]{20}$/.test(target) && said(body.get('revision') ?? '') !== null)
1852
+ restoreReference(store, { repo, actor: who.name, id: target, fromRevision: said(body.get('revision') ?? ''), proof }, now);
1853
+ else
1854
+ return refuse(response, who, 400, 'Choose one clean-up action.', back);
1855
+ }
1856
+ catch (error) {
1857
+ return sendScreen(response, 409, screen('Knowledge', settingsSubPage("agents", "Knowledge", html `<p role="alert">${error instanceof Error ? error.message : 'That could not be done.'}</p><p><a class="k-link" href="${back}">Back to Knowledge</a></p>`), { chrome: chromeFor(repo, 'settings') }));
1858
+ }
1859
+ return redirect(response, `${back}&saved=1`);
1860
+ }
1758
1861
  async function knowledgeDecision(ctx) {
1759
1862
  const { response, now, posted } = ctx;
1760
1863
  const who = browserCaller(ctx.who);
@@ -2754,6 +2857,8 @@ ${postForm("/settings/chat-approval/save", html `\
2754
2857
  "settings.models-stop": modelsRuleAct("stop"),
2755
2858
  "settings.knowledge-refresh": knowledgeRefresh,
2756
2859
  "settings.knowledge-proposal": knowledgeProposal,
2860
+ "settings.knowledge-note": knowledgeNote,
2861
+ "settings.knowledge-tidy": knowledgeTidy,
2757
2862
  "settings.knowledge-decision": knowledgeDecision,
2758
2863
  "settings.knowledge-change": knowledgeChange,
2759
2864
  "settings.learning-change": learningChange,
@@ -2803,6 +2908,8 @@ ${postForm("/settings/chat-approval/save", html `\
2803
2908
  "settings.lead-about": leadAbout,
2804
2909
  "settings.lead-promise-cancel": leadPromiseCancel,
2805
2910
  "settings.lead-on": leadOn,
2911
+ "settings.code-map": codeMapAct("switch"),
2912
+ "settings.code-map-holdout": codeMapAct("holdout"),
2806
2913
  }, {
2807
2914
  "edge.google-callback": googleCallback,
2808
2915
  "edge.connect-callback": connectCallback,