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.
- package/CHANGELOG.md +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -3
- package/dist/assignment.js +16 -3
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +50 -1
- package/dist/release.js +118 -11
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +29 -21
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +18 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/server/pages.js
CHANGED
|
@@ -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,
|
|
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, "/
|
|
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
|
|
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="/
|
|
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
|
|
975
|
-
const { url, who, response
|
|
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
|
-
|
|
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,
|
|
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.", "/
|
|
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
|
|
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&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" ? "
|
|
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 <name></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
|
|
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
|
-
}
|
|
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;
|