tumwater 0.4.0 → 0.4.2
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/README.md +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -1,35 +1,25 @@
|
|
|
1
1
|
import { answeredQuestionSummaries } from "../backlog/question-answer.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { completionDate, donePlanEntries, DONE_DATE_RE, FIXED_DATE_RE, fixedBugEntries, openBugs, openQuestions, plannedPlans } from "../backlog/backlog.js";
|
|
3
|
+
import { backlogHolds, backlogInProgress, backlogMarks, refusedEntries, stalledEntries } from "../backlog/backlog-eligibility.js";
|
|
4
|
+
import { finishedTitle, strandedPlanEntriesFor } from "../backlog/backlog-structure.js";
|
|
5
|
+
import { groupBlockedSeries, partsLabel } from "../backlog/backlog-index-data.js";
|
|
6
6
|
import { fixClaimProblems, validationGapProblems } from "../doctor/doctor-backlog-plans.js";
|
|
7
|
-
import { checkWaitContention, providerWaitContention, slotWaitContention, staleWorkspacePins, CHECK_WAIT_CHECK_WINDOW_MS, PROVIDER_WAIT_CHECK_WINDOW_MS, SLOT_WAIT_CHECK_WINDOW_MS } from "../doctor/doctor-contention-checks.js";
|
|
8
7
|
import { readEvents } from "../events/event-read.js";
|
|
9
|
-
import { eventsSince } from "../events/event-window.js";
|
|
10
|
-
import { isNotifyFailure } from "../events/notify.js";
|
|
11
8
|
import { eventFeedItem, formatEvent } from "../events/event-format.js";
|
|
12
9
|
import { projectName } from "../project-name.js";
|
|
13
10
|
import { dailyCost } from "../budget/budget.js";
|
|
11
|
+
import { contextShareLabel } from "../text/format.js";
|
|
14
12
|
import { queueEntryMergeLabel } from "../merge/merge-queue-render.js";
|
|
13
|
+
import { piModelsPath } from "../pi/pi-models.js";
|
|
15
14
|
import { scopeSnapshotToRoles, snapshot } from "../status/status-data.js";
|
|
16
15
|
import { bootstrapBadge, buildBadge, budgetBadge, diskBadge, mergeBadge, mainCheckBadge, mainCountsFragment, ciParityBadge, ciParityTone, permitsBadge, checksBadge, quietBadge, runWindowBadge, workspacesBadge } from "./badges.js";
|
|
17
|
-
import { readSlotsState } from "../git/slots-state.js";
|
|
18
|
-
import { configForStatus } from "../status/status-polls.js";
|
|
19
16
|
import { isActivePhase, roleRowCells, onErrorStreak } from "./status-model.js";
|
|
20
|
-
import { byDaysThenIndex } from "../collections.js";
|
|
21
17
|
import { yieldMultiplierFor } from "../scheduling/backoff.js";
|
|
22
18
|
import { staleClaimEntries } from "../scheduling/claim-entries.js";
|
|
23
|
-
import { readQaCoverage, qaCoverageStalled } from "../run/qa-coverage.js";
|
|
24
19
|
import { teamAlerts } from "./team-alerts.js";
|
|
25
|
-
import { duplicatedBacklogHeadings, liveFinishedEntriesFor
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { flappedChecksPhrase } from "../doctor/doctor-project-checks.js";
|
|
29
|
-
import { readPostReviews, postMergeChurn, POST_MERGE_REJECT_WINDOW_MS } from "../review/post-merge-review.js";
|
|
30
|
-
import { staleQueuedMergeRows, STALE_BUG_DAYS, STALE_QUESTION_DAYS, staleQueuedPromptRows, skippedMergeQueueFiles, atRiskPostReviews, atRiskPostReviewsPhrase } from "../doctor/doctor-backlog.js";
|
|
31
|
-
import { NOTIFY_CHECK_WINDOW_MS } from "../doctor/doctor-notify.js";
|
|
32
|
-
import { matchedRegressedFixes, regressedFixCandidates, regressedFixesPhrase, staleWaitingBugRows } from "../doctor/doctor-backlog-bugs.js";
|
|
20
|
+
import { duplicatedBacklogHeadings, liveFinishedEntriesFor } from "../doctor/doctor-backlog-plans.js";
|
|
21
|
+
import { readPostReviews } from "../review/post-merge-review.js";
|
|
22
|
+
import { capacityContention, instancePoolProblems, planDependencyAlert, providerCapProblems, regressedFixesAlert, staleOpenQuestions, staleWorkspacePins } from "./alert-feeds.js";
|
|
33
23
|
/** How many recent events the payload carries — enough for the dashboard's activity feed to
|
|
34
24
|
* still show a screenful of notable events (merges, failures, questions) after it filters
|
|
35
25
|
* out the routine ones (run starts, wakes, passing checks). */
|
|
@@ -42,311 +32,11 @@ export const DASHBOARD_RECENT_FINISHED = 5;
|
|
|
42
32
|
export function recentFinished(entries, dateRe) {
|
|
43
33
|
return entries.slice(0, DASHBOARD_RECENT_FINISHED).map((e) => ({ title: finishedTitle(e.title, dateRe), completedOn: completionDate(e.title, dateRe) }));
|
|
44
34
|
}
|
|
45
|
-
/** The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* waiting alerts name those), and so is an undated heading (like the doctor's check). The
|
|
51
|
-
* returned objects carry the 0-based index (for the drawer address `bugs:<index>`), the
|
|
52
|
-
* 1-based position (for `bug --cancel <position>`), the stamp-stripped title, and the whole-
|
|
53
|
-
* day age. Returned oldest-first — most days waited, ties in file order — so a caller naming
|
|
54
|
-
* the first element names the most-stale bug. */
|
|
55
|
-
export function staleOpenBugs(root, now = Date.now()) {
|
|
56
|
-
return openBugEntries(root)
|
|
57
|
-
.flatMap((entry, index) => {
|
|
58
|
-
if (refusalNote(entry.body) !== null || waitingNote(entry.body) !== null)
|
|
59
|
-
return [];
|
|
60
|
-
const stamp = filingDate(entry.title);
|
|
61
|
-
if (stamp === null)
|
|
62
|
-
return [];
|
|
63
|
-
const days = ageInDays(stamp, now);
|
|
64
|
-
return days >= STALE_BUG_DAYS ? [{ index, position: index + 1, title: stripEntryStamp(entry.title), days }] : [];
|
|
65
|
-
})
|
|
66
|
-
.sort(byDaysThenIndex);
|
|
67
|
-
}
|
|
68
|
-
/** The open questions the doctor's 7-day rule (src/doctor/doctor-backlog-bugs.ts
|
|
69
|
-
* checkStaleQuestions, STALE_QUESTION_DAYS) calls stale: an open QUESTIONS.md entry whose
|
|
70
|
-
* `(asked by …)` stamp is at least 7 days old. This is the always-on feed for the stale-questions
|
|
71
|
-
* team alert — the shared rule's twin on every observer surface, computed from the same threshold
|
|
72
|
-
* and the same questionFilingDate parser the doctor's check uses, so the two cannot drift on which
|
|
73
|
-
* questions are stale. An undated heading is skipped (like the doctor's check); questions carry no
|
|
74
|
-
* refused or waiting note to filter. The returned objects carry the 1-based position (for
|
|
75
|
-
* `tumwater questions answer <n>`), the 0-based index (kept only as the file-order tiebreak), the
|
|
76
|
-
* stamp-stripped title, and the whole-day age. Returned oldest-first — most days waited, ties in
|
|
77
|
-
* file order — so a caller naming the first element names the most-stale question. */
|
|
78
|
-
export function staleOpenQuestions(root, now = Date.now()) {
|
|
79
|
-
return openQuestionEntries(root)
|
|
80
|
-
.flatMap((entry, index) => {
|
|
81
|
-
const stamp = questionFilingDate(entry.title);
|
|
82
|
-
if (stamp === null)
|
|
83
|
-
return [];
|
|
84
|
-
const days = ageInDays(stamp, now);
|
|
85
|
-
return days >= STALE_QUESTION_DAYS ? [{ index, position: index + 1, title: stripEntryStamp(entry.title), days }] : [];
|
|
86
|
-
})
|
|
87
|
-
.sort(byDaysThenIndex);
|
|
88
|
-
}
|
|
89
|
-
/** The notify pages the doctor's 1-day rule (src/doctor/doctor-notify.ts checkNotify,
|
|
90
|
-
* NOTIFY_CHECK_WINDOW_MS) calls failed — warning events within the last day that isNotifyFailure
|
|
91
|
-
* recognizes (a nonzero exit, a kill, or a failed start). This is the always-on feed for the
|
|
92
|
-
* notify team alert, sharing the doctor's threshold, window and filter, so the alert and the
|
|
93
|
-
* check cannot drift. null when the window holds no failed page. */
|
|
94
|
-
export function notifyFailures(root, now = Date.now()) {
|
|
95
|
-
const failures = eventsSince(root, NOTIFY_CHECK_WINDOW_MS, now, isNotifyFailure);
|
|
96
|
-
const latest = failures[failures.length - 1];
|
|
97
|
-
return latest ? { count: failures.length, latestTs: latest.ts, message: String(latest.message) } : null;
|
|
98
|
-
}
|
|
99
|
-
/** The queued inbox prompts the doctor's stale-prompt rule (src/doctor/doctor-backlog.ts
|
|
100
|
-
* `staleQueuedPromptRows`, STALE_PROMPT_MS) calls stale: a prompt due-but-undelivered for more
|
|
101
|
-
* than a day. This is the always-on feed for the stale-prompts team alert — the doctor's check
|
|
102
|
-
* reads the same shared traversal and threshold, so the two cannot drift. A deferred prompt
|
|
103
|
-
* (its not-before time still in the future) is never stale, and neither is a prompt at or under
|
|
104
|
-
* the threshold (strictly past STALE_PROMPT_MS). Each returned object carries the role, the
|
|
105
|
-
* 1-based position (`prompt --cancel <n> --role <id>`), and the wait in ms. Returned
|
|
106
|
-
* oldest-first (queuedAtMs ascending), so a caller naming the first element names the
|
|
107
|
-
* longest-waiting prompt. */
|
|
108
|
-
export function stalePrompts(root, now = Date.now()) {
|
|
109
|
-
return staleQueuedPromptRows(root, now).map((p) => ({ role: p.role, position: p.position, waitedMs: now - p.queuedAtMs }));
|
|
110
|
-
}
|
|
111
|
-
/** The queued merges the doctor's stale-merge rule (src/doctor/doctor-backlog.ts
|
|
112
|
-
* `staleQueuedMergeRows`, MERGE_QUEUE_STALE_MS) calls stale: a change that has sat in the
|
|
113
|
-
* merge queue more than 6 h without starting. This is the always-on feed for the stale-merges
|
|
114
|
-
* team alert — the doctor's check reads the same shared traversal and threshold, so the two
|
|
115
|
-
* cannot drift. Each returned object carries the role, the summary, and the wait in ms.
|
|
116
|
-
* Returned oldest-first (queue-file order), so a caller naming the first element names the
|
|
117
|
-
* longest-waiting merge. */
|
|
118
|
-
export function staleMerges(root, now = Date.now()) {
|
|
119
|
-
return staleQueuedMergeRows(root, now).map((m) => ({ role: m.role, summary: m.summary, waitedMs: m.waitedMs }));
|
|
120
|
-
}
|
|
121
|
-
/** A broken plan-dependency clause, the always-on twin of the doctor's `plan dependencies` row
|
|
122
|
-
* (src/doctor/doctor-backlog-plans.ts checkPlanDependencies): the same rule's problem set,
|
|
123
|
-
* severity order, and exact message, so the alert and the doctor cannot drift. The reader owns
|
|
124
|
-
* the rule — planDependencyAlert passes the doctor's planDependencyProblem through unchanged,
|
|
125
|
-
* like staleOpenBugs and the other stale feeds do their doctor's rules. null when PLANS.md's
|
|
126
|
-
* planned entries have no cycle, dangling ref, unparsed clause, or misworded dependency. */
|
|
127
|
-
export function planDependencyAlert(root) {
|
|
128
|
-
return planDependencyProblem(root);
|
|
129
|
-
}
|
|
130
|
-
/** The open bugs a Waiting-for-the-next-failure note has held for STALE_WAIT_DAYS (7) or more —
|
|
131
|
-
* the always-on feed for the waiting-bugs team alert, sharing the doctor's threshold, held-bug
|
|
132
|
-
* selection and anchor through staleWaitingBugRows so the alert and `checkWaitingBugs` cannot
|
|
133
|
-
* drift. The returned objects carry the 0-based index (drawer `bugs:<index>`), the 1-based
|
|
134
|
-
* position (`bug --release <position>`), the stamp-stripped title, and the whole-day age.
|
|
135
|
-
* Returned most-days-waited-first — ties in file order — so a caller naming the first element
|
|
136
|
-
* names the longest-held bug. */
|
|
137
|
-
export function staleWaitingBugs(root, now = Date.now()) {
|
|
138
|
-
return staleWaitingBugRows(root, now);
|
|
139
|
-
}
|
|
140
|
-
/** The merge-queue files `readMergeQueue` skips as checkout-relative paths — the always-on feed
|
|
141
|
-
* for the `skipped-merges` team alert (src/ui/team-alerts.ts), sharing the doctor's one list
|
|
142
|
-
* (doctor-backlog.ts skippedMergeQueueFiles), so the alert and the doctor's `merge-queue files`
|
|
143
|
-
* row cannot drift on the selection or the named paths. */
|
|
144
|
-
export function skippedMergeFiles(root) {
|
|
145
|
-
return skippedMergeQueueFiles(root);
|
|
146
|
-
}
|
|
147
|
-
/** @see CapacityBottleneck */
|
|
148
|
-
export function capacityContention(root, now = Date.now()) {
|
|
149
|
-
const out = [];
|
|
150
|
-
/** Resolve one capacity verdict and push a "capacity bottleneck" row when it warns. Shared
|
|
151
|
-
* by the three capacity checks (workspace, provider, check slots) so the try/catch that keeps
|
|
152
|
-
* each one from crying wolf on an unreadable event log lives in one place, and the row's
|
|
153
|
-
* `name`/`detail` shape cannot drift between them. */
|
|
154
|
-
const maybeWarn = (name, windowMs, keep, resolve) => {
|
|
155
|
-
try {
|
|
156
|
-
const verdict = resolve(eventsSince(root, windowMs, now, keep));
|
|
157
|
-
if (verdict.warn)
|
|
158
|
-
out.push({ name, detail: verdict.detail });
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// A read error skips this row, so the alert never cries wolf on an unreadable log.
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
maybeWarn("workspace slots", SLOT_WAIT_CHECK_WINDOW_MS, (ev) => ev.type === "slot_wait", (waits) => slotWaitContention(waits, readSlotsState(root).slots.length));
|
|
165
|
-
maybeWarn("provider slots", PROVIDER_WAIT_CHECK_WINDOW_MS, (ev) => ev.type === "provider_wait", (waits) => providerWaitContention(waits, configForStatus(root)));
|
|
166
|
-
maybeWarn("check slots", CHECK_WAIT_CHECK_WINDOW_MS, (e) => e.type === "check_wait", (waits) => checkWaitContention(waits, configForStatus(root)));
|
|
167
|
-
return out;
|
|
168
|
-
}
|
|
169
|
-
/** How far back the primary-overwrite alert looks — one day, so a team stuck retrying one
|
|
170
|
-
* dirty primary file is a standing amber banner until the file is committed and the refusals
|
|
171
|
-
* age out. */
|
|
172
|
-
export const PRIMARY_OVERWRITE_WINDOW_MS = 24 * 60 * 60_000;
|
|
173
|
-
/** How far back the dropped-post-review alert looks — one day, the 24 h event-window convention
|
|
174
|
-
* every event-reading alert uses (PRIMARY_OVERWRITE_WINDOW_MS sorts beside it), so a review the
|
|
175
|
-
* drain dropped stays a standing amber notice until a day passes. */
|
|
176
|
-
export const DROPPED_POST_REVIEW_WINDOW_MS = 24 * 60 * 60_000;
|
|
177
|
-
/** How far back the regressed-fixes alert looks — one day, so a recently Fixed test that failed
|
|
178
|
-
* again is a standing amber alert until a day passes (or the failure is filed). The doctor's
|
|
179
|
-
* own `regressed fixes` row keeps its since-the-fix read; this is the alert's daily trigger, the
|
|
180
|
-
* same trailing day the primary-overwrite and notify alerts read. */
|
|
181
|
-
export const REGRESSED_FIX_WINDOW_MS = 24 * 60 * 60_000;
|
|
182
|
-
/** The always-on twin of the doctor's `regressed fixes` row (src/doctor/doctor-backlog-bugs.ts
|
|
183
|
-
* checkRegressedFixes): the same candidate selection (regressedFixCandidates) and the same
|
|
184
|
-
* failureMentions match (matchedRegressedFixes) and sentence (regressedFixesPhrase), read off the
|
|
185
|
-
* trailing REGRESSED_FIX_WINDOW_MS of events (the doctor, whose window is since each fix, still
|
|
186
|
-
* warns on an older recurrence). An unreadable event log yields none. */
|
|
187
|
-
export function regressedFixesAlert(root, now = Date.now()) {
|
|
188
|
-
const candidates = regressedFixCandidates(root);
|
|
189
|
-
const rows = matchedRegressedFixes(candidates, eventsSince(root, REGRESSED_FIX_WINDOW_MS, now));
|
|
190
|
-
const detail = regressedFixesPhrase(rows);
|
|
191
|
-
return detail === null ? null : { count: rows.length, detail };
|
|
192
|
-
}
|
|
193
|
-
/** The exact substring merge-git.ts's overwrite blocker logs in its `warning` event, so this
|
|
194
|
-
* feed matches the refusal and nothing the merge path does not say (see merge-git.ts
|
|
195
|
-
* `overwriteBlockedReason`). */
|
|
196
|
-
const PRIMARY_OVERWRITE_MARKER = "would overwrite files in the primary checkout";
|
|
197
|
-
/** The merges the primary checkout refused in the trailing day (src/merge/merge-git.ts
|
|
198
|
-
* `primaryCheckoutBlockers`, `overwriteBlockedReason`) — the always-on feed for the
|
|
199
|
-
* `primary-overwrite` team alert (src/ui/team-alerts.ts). It reads only the `warning` event the
|
|
200
|
-
* merge path already writes, so it reaches the status poll without a git read (the doctor's
|
|
201
|
-
* `uncommitted backlog` check's git read is async and never gets there) and cannot drift from
|
|
202
|
-
* the refusals. null when the window holds no such warning. */
|
|
203
|
-
export function primaryOverwriteBlocks(root, now = Date.now()) {
|
|
204
|
-
const hits = eventsSince(root, PRIMARY_OVERWRITE_WINDOW_MS, now, (e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(PRIMARY_OVERWRITE_MARKER));
|
|
205
|
-
const latest = hits[hits.length - 1];
|
|
206
|
-
return latest ? { count: hits.length, latestTs: latest.ts, message: String(latest.message) } : null;
|
|
207
|
-
}
|
|
208
|
-
/** Re-export so the always-on alert's threshold and window stay in their one home
|
|
209
|
-
* (src/review/post-merge-review.ts) while existing imports (test/status-payload.test.ts) keep
|
|
210
|
-
* working. */
|
|
211
|
-
export { POST_MERGE_REJECT_WINDOW_MS };
|
|
212
|
-
/** Re-export so the always-on alert's stale-pin selector stays in its one home
|
|
213
|
-
* (src/doctor/doctor-contention-checks.ts) while tui.tsx keeps importing every alert feed from
|
|
214
|
-
* this one module. */
|
|
215
|
-
export { staleWorkspacePins };
|
|
216
|
-
/** The always-on feed for the `post-merge-churn` team alert (src/ui/team-alerts.ts): a day in
|
|
217
|
-
* which at least `POST_MERGE_REJECT_WARN` already-merged commits were turned back by post-merge
|
|
218
|
-
* review, each rejection written by reviewMergedCommit as a `review_rejected` event with
|
|
219
|
-
* `afterMerge: true`. Post-merge is the costly kind — the commit already reached main, so a
|
|
220
|
-
* rejection is answered only by the reviewer's own fix — and it is the reviewer-fix signal for
|
|
221
|
-
* the `review.afterMerge` tiering. Reads the same
|
|
222
|
-
* events the report's `postMergeRejectionsByRole` fills, so the two cannot disagree; null when
|
|
223
|
-
* the window holds fewer than `POST_MERGE_REJECT_WARN` post-merge rejections, or none. A read
|
|
224
|
-
* error skips the row (the capacityContention precedent). */
|
|
225
|
-
export function postMergeChurnAlert(root, now = Date.now()) {
|
|
226
|
-
try {
|
|
227
|
-
const events = eventsSince(root, POST_MERGE_REJECT_WINDOW_MS, now, (e) => e.type === "review_rejected");
|
|
228
|
-
return postMergeChurn(events);
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
// A read error skips the row, so the alert never cries wolf on an unreadable log.
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/** A stale or oversized flake quarantine, the always-on twin of the doctor's `flaky checks` row
|
|
236
|
-
* (src/doctor/doctor-project-checks.ts checkFlakyChecks): the same quarantineWarning the doctor
|
|
237
|
-
* calls over the same loadQuarantine list, so the alert and the doctor cannot drift on when the
|
|
238
|
-
* list is a problem. null when the list is fresh and small (or absent) — the rule fires only
|
|
239
|
-
* over QUARANTINE_WARN_COUNT tests or one first seen more than QUARANTINE_AGE_MS ago. */
|
|
240
|
-
export function quarantineAlert(root, now = Date.now()) {
|
|
241
|
-
const entries = loadQuarantine(root);
|
|
242
|
-
const warning = quarantineWarning(entries, now);
|
|
243
|
-
if (warning === null)
|
|
244
|
-
return null;
|
|
245
|
-
const oldest = entries.reduce((a, b) => (b.firstSeen < a.firstSeen ? b : a));
|
|
246
|
-
return { count: entries.length, oldestTest: oldest.test, oldestFirstSeen: oldest.firstSeen, warning };
|
|
247
|
-
}
|
|
248
|
-
/** Checks that flapped more than once in the doctor's window, the always-on twin of the doctor's
|
|
249
|
-
* `flaky checks` row (src/doctor/doctor-project-checks.ts checkFlakyChecks): the same
|
|
250
|
-
* repeatedFlakeCounts over the same KNOWN_FLAKE_WINDOW_MS event window and the same
|
|
251
|
-
* flappedChecksPhrase the doctor shows, so the alert and the doctor cannot drift on the count or
|
|
252
|
-
* the wording. null when no check flapped twice in the window (or the log is unreadable). */
|
|
253
|
-
export function flappedChecksAlert(root, now = Date.now()) {
|
|
254
|
-
const events = eventsSince(root, KNOWN_FLAKE_WINDOW_MS, now, (e) => e.type === "warning");
|
|
255
|
-
const flapped = repeatedFlakeCounts(events);
|
|
256
|
-
const detail = flappedChecksPhrase(flapped);
|
|
257
|
-
return detail === null ? null : { count: flapped.length, detail };
|
|
258
|
-
}
|
|
259
|
-
export function qaCoverageAlert(root, now = Date.now()) {
|
|
260
|
-
const coverage = readQaCoverage(root);
|
|
261
|
-
if (!qaCoverageStalled(coverage, now))
|
|
262
|
-
return null;
|
|
263
|
-
const entries = Object.entries(coverage);
|
|
264
|
-
let newestFlow = entries[0][0];
|
|
265
|
-
let newestAt = entries[0][1].lastRunAt;
|
|
266
|
-
for (const [name, entry] of entries) {
|
|
267
|
-
if (entry.lastRunAt > newestAt) {
|
|
268
|
-
newestAt = entry.lastRunAt;
|
|
269
|
-
newestFlow = name;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return { newestFlow, newestAt, count: entries.length };
|
|
273
|
-
}
|
|
274
|
-
/** @see PostReviewRisk */
|
|
275
|
-
export function postReviewRiskAlert(root) {
|
|
276
|
-
const rows = atRiskPostReviews(root);
|
|
277
|
-
const detail = atRiskPostReviewsPhrase(rows);
|
|
278
|
-
return detail === null ? null : { count: rows.length, detail };
|
|
279
|
-
}
|
|
280
|
-
/** The exact substring the drain's drop branch logs in its `warning` event (src/merge/
|
|
281
|
-
* merge-post-review.ts at POST_REVIEW_FAILURE_LIMIT), so this feed matches a real drop and not
|
|
282
|
-
* the per-run `the post-merge review of <sha> failed (…)` warning the at-risk alert already
|
|
283
|
-
* covers — status-payload.ts's DROPPED_POST_REVIEW_MARKER is the same string, so the feed and
|
|
284
|
-
* the drain cannot drift on what a drop is. */
|
|
285
|
-
const DROPPED_POST_REVIEW_MARKER = "dropping the post-merge review of";
|
|
286
|
-
/** The always-on feed for the `dropped-post-reviews` team alert (src/ui/team-alerts.ts): reads
|
|
287
|
-
* only the `warning` event the drain's drop branch already writes, so it reaches the status
|
|
288
|
-
* poll without touching the review state and cannot drift from the drop. null when the day
|
|
289
|
-
* holds no such warning. eventsSince degrades an unreadable log, so one bounded event scan
|
|
290
|
-
* needs no try/catch (the primaryOverwriteBlocks precedent). */
|
|
291
|
-
export function droppedPostReviewsAlert(root, now = Date.now()) {
|
|
292
|
-
const hits = eventsSince(root, DROPPED_POST_REVIEW_WINDOW_MS, now, (e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(DROPPED_POST_REVIEW_MARKER));
|
|
293
|
-
const latest = hits[hits.length - 1];
|
|
294
|
-
return latest ? { count: hits.length, latestTs: latest.ts, latestMessage: String(latest.message) } : null;
|
|
295
|
-
}
|
|
296
|
-
/** A main watch tip failing the review gate's five diff rules can sit on main for a day — this
|
|
297
|
-
* is how far back the main-diff-rules alert looks, the same trailing-day convention every
|
|
298
|
-
* event-reading alert uses. */
|
|
299
|
-
export const MAIN_RULE_FAILURE_WINDOW_MS = 24 * 60 * 60_000;
|
|
300
|
-
/** The exact substring main-watch.ts's `watchRules` logs in its warning, so this feed matches a
|
|
301
|
-
* real rule failure and cannot drift from the wording the watch writes. */
|
|
302
|
-
const MAIN_RULE_FAILURE_MARKER = "fails the diff rules";
|
|
303
|
-
/** The always-on feed for the `main-diff-rules` team alert (src/ui/team-alerts.ts): reads only
|
|
304
|
-
* the `warning` event the main watch already writes, so it reaches the status poll without a
|
|
305
|
-
* git read or a rule re-run (the watch paid for the check) and cannot drift from the watch's
|
|
306
|
-
* wording. null when the day holds no such warning. eventsSince degrades an unreadable log, so
|
|
307
|
-
* one bounded event scan needs no try/catch (the primaryOverwriteBlocks precedent). */
|
|
308
|
-
export function mainRuleFailures(root, now = Date.now()) {
|
|
309
|
-
const hits = eventsSince(root, MAIN_RULE_FAILURE_WINDOW_MS, now, (e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(MAIN_RULE_FAILURE_MARKER));
|
|
310
|
-
const latest = hits[hits.length - 1];
|
|
311
|
-
return latest ? { count: hits.length, latestTs: latest.ts, latestMessage: String(latest.message) } : null;
|
|
312
|
-
}
|
|
313
|
-
/** The one team-state document both observer surfaces carry: `GET /api/status` (gui/gui-server.ts)
|
|
314
|
-
* spreads it and adds the serving process's own `serverBuildSha` (the page's cue to notice a
|
|
315
|
-
* newer build and reload), while `tumwater status --json` (cli.ts) prints it verbatim — every
|
|
316
|
-
* other field is shared, so the dashboard and the CLI can never drift apart. Assembled here —
|
|
317
|
-
* not in gui/gui-server.ts — because it is shared data collection for observers, not part of
|
|
318
|
-
* serving HTTP: snapshot() supplies the core state, and the per-role phase/metrics fields come
|
|
319
|
-
* from the same status-model helpers the TUI table uses.
|
|
320
|
-
*
|
|
321
|
-
* `now` pins the poll's clock for the time-derived fields (see snapshot's seam of the same
|
|
322
|
-
* name) — spend stamp-check, burn-rate projection, disk badge, and the pause countdown — so a
|
|
323
|
-
* test's expected value cannot race the payload's own Date.now(). */
|
|
324
|
-
export function statusPayload(root, now = Date.now(), scope = null) {
|
|
325
|
-
const full = snapshot(root, undefined, now);
|
|
326
|
-
// `--role` scopes the role rows (and the alerts computed from them); budget stays team-wide.
|
|
327
|
-
const snap = scope ? scopeSnapshotToRoles(full, scope) : full;
|
|
328
|
-
// One event read feeds both feed shapes below, so `events[i]` and `eventItems[i]` always
|
|
329
|
-
// describe the same event.
|
|
330
|
-
const recent = readEvents(root, RECENT_EVENTS);
|
|
331
|
-
const questions = openQuestions(root);
|
|
332
|
-
// Why each plan/bug is held (src/backlog/backlog-eligibility.ts): the same hold decision
|
|
333
|
-
// the prompt's backlog index and `tumwater backlog` render, projected to the mark each
|
|
334
|
-
// surface appends. Read once here, consumed as the parallel planHolds/bugHolds arrays below.
|
|
335
|
-
const marks = backlogMarks(root);
|
|
336
|
-
const holds = backlogHolds(root);
|
|
337
|
-
const plans = plannedPlans(root);
|
|
338
|
-
// Which blocked plans fold into a series row: the grouping `plan --list` and the prompt's
|
|
339
|
-
// backlog index use, as a slot per plan (null for a plan that stays an ordinary row).
|
|
340
|
-
const working = backlogInProgress(root);
|
|
341
|
-
const planSeries = plans.map(() => null);
|
|
342
|
-
for (const g of groupBlockedSeries(holds.plans.map((hold, i) => ({ i, title: plans[i], hold, claimer: working.plans[i] ?? undefined })))) {
|
|
343
|
-
if (g.kind !== "series")
|
|
344
|
-
continue;
|
|
345
|
-
const slot = { name: g.series, label: `${g.series}, ${g.rows.length} blocked parts (${partsLabel(g.rows)})` };
|
|
346
|
-
for (const r of g.rows)
|
|
347
|
-
planSeries[r.i] = slot;
|
|
348
|
-
}
|
|
349
|
-
const roles = snap.roles.map((s) => {
|
|
35
|
+
/** The per-role rows (status-payload.ts's field-group split): roleRows assembles each role's
|
|
36
|
+
* fields from `snap.roles` — the same single-home roleRowCells/onErrorStreak/metrics reads the
|
|
37
|
+
* TUI table (status-render.ts's renderStatus) repeats, so the two dashboards cannot drift apart. */
|
|
38
|
+
function roleRows(snap, root) {
|
|
39
|
+
return snap.roles.map((s) => {
|
|
350
40
|
// roleRowCells single-homes the per-role tail read + metrics + phase derivation the
|
|
351
41
|
// TUI table (status-render.ts's renderStatus) repeats, so the two dashboards cannot
|
|
352
42
|
// drift apart.
|
|
@@ -406,6 +96,11 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
406
96
|
commits: s.commits,
|
|
407
97
|
generated,
|
|
408
98
|
peakCtx,
|
|
99
|
+
// The preformatted peak-ctx share of the role's declared window (part 3c): shipped
|
|
100
|
+
// beside `peakCtx` so `status --json` and the GUI carry the label the TUI prints without
|
|
101
|
+
// re-resolving the window. Present only where a window is declared, so a window-less
|
|
102
|
+
// role's payload shape is unchanged.
|
|
103
|
+
...(s.contextWindow !== undefined ? { peakCtxLabel: contextShareLabel(peakCtx, s.contextWindow) } : {}),
|
|
409
104
|
costUsd: s.totalCostUsd,
|
|
410
105
|
// The role's spend for the local day (the daily budget window): 0 while its stamp
|
|
411
106
|
// is stale or missing — same helper and semantics as the TUI's `today` column.
|
|
@@ -435,8 +130,33 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
435
130
|
// change ×N` suffix from this number (its row and drawer share noChangeStreak) so its
|
|
436
131
|
// JS twin applies the same display rule without re-parsing the phase text.
|
|
437
132
|
noChangeStreak: s.noChangeStreak ?? 0,
|
|
133
|
+
// The consecutive cut-off streak (run-apply.ts applyRunOutcome's cutOffStreak, an
|
|
134
|
+
// observational counter parallel to noChangeStreak): how many completed runs in a row
|
|
135
|
+
// hit the context ceiling. The GUI's phaseDetailBase re-derives the ` · cut off ×N`
|
|
136
|
+
// suffix from this number (preferred over noChangeStreak, since a cut-off run is also a
|
|
137
|
+
// no_change and its prose names the costly cause) without re-parsing the phase text.
|
|
138
|
+
cutOffStreak: s.cutOffStreak ?? 0,
|
|
438
139
|
};
|
|
439
140
|
});
|
|
141
|
+
}
|
|
142
|
+
/** Which blocked plans fold into a series row (status-payload.ts's field-group split): the
|
|
143
|
+
* grouping `plan --list` and the prompt's backlog index use, as a slot per plan (null for a
|
|
144
|
+
* plan that stays an ordinary row). */
|
|
145
|
+
function planSeriesSlots(plans, holds, working) {
|
|
146
|
+
const planSeries = plans.map(() => null);
|
|
147
|
+
for (const g of groupBlockedSeries(holds.plans.map((hold, i) => ({ i, title: plans[i], hold, claimer: working.plans[i] ?? undefined })))) {
|
|
148
|
+
if (g.kind !== "series")
|
|
149
|
+
continue;
|
|
150
|
+
const slot = { name: g.series, label: `${g.series}, ${g.rows.length} blocked parts (${partsLabel(g.rows)})` };
|
|
151
|
+
for (const r of g.rows)
|
|
152
|
+
planSeries[r.i] = slot;
|
|
153
|
+
}
|
|
154
|
+
return planSeries;
|
|
155
|
+
}
|
|
156
|
+
/** The header row (status-payload.ts's field-group split): the project identity, the running
|
|
157
|
+
* harness and its build/budget/disk/permit-pool/workspace badges, plus the `run --for`
|
|
158
|
+
* deadline and its badge. */
|
|
159
|
+
function headerFields(root, snap, now) {
|
|
440
160
|
return {
|
|
441
161
|
// The project's display name (project-name.ts) — the dashboards' title.
|
|
442
162
|
project: projectName(root),
|
|
@@ -473,6 +193,14 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
473
193
|
...(snap.runStopsAt !== undefined ? { runStopsAt: snap.runStopsAt } : {}),
|
|
474
194
|
...(snap.scheduledStopAt !== undefined ? { scheduledStopAt: snap.scheduledStopAt } : {}),
|
|
475
195
|
runWindowBadge: runWindowBadge(snap.runStopsAt, now),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** Merge-queue and main-check state (status-payload.ts's field-group split): the merge queue
|
|
199
|
+
* and its labels, the post-merge review queue, main's newest merge-scope check and its badge,
|
|
200
|
+
* the ci-parity badge/tone, and the raw twins of the no-check/no-brief/brief-truncated/
|
|
201
|
+
* role-notes/principles alerts for `status --json` scripts. */
|
|
202
|
+
function mergeAndCheckFields(root, snap, now) {
|
|
203
|
+
return {
|
|
476
204
|
// The merge queue (plans/merge-queue.md 4/5): depth always (machine-readable for
|
|
477
205
|
// `status --json`; 0 when idle) plus the in-flight merge's identity only while one is
|
|
478
206
|
// actually running. Raw data here, like budget — and the display-ready header badge
|
|
@@ -506,12 +234,39 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
506
234
|
// state before the dashboard/TUI render it as an alert). Omitted (absent) when a check is
|
|
507
235
|
// declared, the same omit-undefined idiom as ciParity.
|
|
508
236
|
...(snap.noDeclaredCheck ? { noDeclaredCheck: true } : {}),
|
|
237
|
+
// The raw machine-readable twin of the amber `no-brief` alert (present only when a brief
|
|
238
|
+
// candidate exists without a resolvable prompt block, so `tumwater status --json` scripts
|
|
239
|
+
// can read the same state the alert renders). Omitted (absent) when the brief resolves or
|
|
240
|
+
// the repo is the bare pre-init state, the same omit-undefined idiom as noDeclaredCheck.
|
|
241
|
+
...(snap.noBrief ? { noBrief: true } : {}),
|
|
242
|
+
// The raw machine-readable twin of the amber `brief-truncated` alert (present only when
|
|
243
|
+
// the resolved brief block exceeds the read cap, the state the doctor `brief` row's
|
|
244
|
+
// over-cap warn covers, so `tumwater status --json` scripts can read the same state the
|
|
245
|
+
// alert renders). Omitted (absent) at or under the cap, the same omit-undefined idiom as
|
|
246
|
+
// noBrief.
|
|
247
|
+
...(snap.briefTruncated ? { briefTruncated: true } : {}),
|
|
248
|
+
// The raw machine-readable twin of the amber `role-notes-truncated` alert (present only
|
|
249
|
+
// when at least one role's notes file runs past the read bound — the array of `{ role,
|
|
250
|
+
// chars }`, the same array collectRoleNotesOverCap returns and the alert names). Omitted
|
|
251
|
+
// (absent) when every note is within the bound, the same omit-undefined idiom as noBrief.
|
|
252
|
+
...(snap.roleNotesOverCap && snap.roleNotesOverCap.length > 0 ? { roleNotesOverCap: snap.roleNotesOverCap } : {}),
|
|
253
|
+
// The raw machine-readable twin of the amber `principles-truncated` alert (present only
|
|
254
|
+
// when PRINCIPLES.md is over its prompt cap, so `tumwater status --json` scripts can read
|
|
255
|
+
// the same state the alert renders). Omitted (absent) when the file is at or under the cap
|
|
256
|
+
// or missing, the same omit-undefined idiom as noDeclaredCheck.
|
|
257
|
+
...(snap.principlesTruncated ? { principlesTruncated: true } : {}),
|
|
509
258
|
ciParityTone: ciParityTone(snap.ciParity),
|
|
510
259
|
// The check's counts fragment, preformatted through badges.ts's mainCountsFragment so
|
|
511
260
|
// the page renders the pass/skip/fail breakdown verbatim and cannot re-derive (and
|
|
512
261
|
// drift from) it — the sidebar's "Main green · …" row reads this field, not the raw
|
|
513
262
|
// counts. Empty when no check has run or the check carries no counts.
|
|
514
263
|
mainCounts: mainCountsFragment(snap.mainCheck?.counts),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/** The inbox (status-payload.ts's field-group split): the queued director prompts and the
|
|
267
|
+
* per-role queues, raw for the queued-prompts UI. */
|
|
268
|
+
function inboxFields(snap) {
|
|
269
|
+
return {
|
|
515
270
|
inbox: snap.inbox,
|
|
516
271
|
// Previews of the queued director prompts in execution order (truncated server-side —
|
|
517
272
|
// see StatusSnapshot.inboxPrompts); the page lists them in its project status panel.
|
|
@@ -536,6 +291,13 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
536
291
|
// row with a cancel link, replacing the bare "r: N queued" count line. Raw data, like
|
|
537
292
|
// inboxFiles.
|
|
538
293
|
roleInboxPrompts: snap.roleInboxPrompts,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/** Budget and pause/scheduling state (status-payload.ts's field-group split): the daily
|
|
297
|
+
* budget, its subscription windows, the fallback chain, operator/per-role pauses, the
|
|
298
|
+
* cap/maintenance holds, scheduled wakes, quiet hours, and the quiet badge. */
|
|
299
|
+
function budgetAndPauseFields(snap) {
|
|
300
|
+
return {
|
|
539
301
|
// The raw daily-cost-budget data, unconditionally (capUsd 0 = disabled; spend still
|
|
540
302
|
// reported) — machine-readable for `status --json`; the display-ready header badge is
|
|
541
303
|
// the preformatted `budgetBadge`.
|
|
@@ -604,13 +366,25 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
604
366
|
...(snap.quietHours ? { quietHours: snap.quietHours } : {}),
|
|
605
367
|
inQuietHours: snap.inQuietHours,
|
|
606
368
|
quietBadge: quietBadge(snap.quietHours, snap.inQuietHours),
|
|
607
|
-
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/** The event feed (status-payload.ts's field-group split): recent events as the one-line text
|
|
372
|
+
* `tumwater logs` prints and as data for the dashboard's activity feed. */
|
|
373
|
+
function feedFields(recent) {
|
|
374
|
+
return {
|
|
608
375
|
// The recent event feed as the one-line text `tumwater logs` prints…
|
|
609
376
|
events: recent.map((e) => formatEvent(e)),
|
|
610
377
|
// …and the same events as data for the dashboard's activity feed: when, which role, what
|
|
611
378
|
// kind (the event type plus its result or check status, which decide the row's tone), and
|
|
612
379
|
// the message without formatEvent's time/role columns — the page renders those itself.
|
|
613
380
|
eventItems: recent.map(eventFeedItem),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/** Project-status backlog (status-payload.ts's field-group split): planned features, open
|
|
384
|
+
* bugs and questions, their holds and marks, the blocked-plan series, the newest answered
|
|
385
|
+
* questions, and the newest done plans / fixed bugs. */
|
|
386
|
+
function backlogFields(root, plans, marks, holds, planSeries, questions) {
|
|
387
|
+
return {
|
|
614
388
|
// Project status (planned features + open bugs + open questions), fresh per poll like
|
|
615
389
|
// events — roles edit these files constantly, so there is no cache to go stale. The page
|
|
616
390
|
// derives the header badge count from this list's length.
|
|
@@ -637,8 +411,57 @@ export function statusPayload(root, now = Date.now(), scope = null) {
|
|
|
637
411
|
// fresh each poll. Badges and alerts keep counting open entries only.
|
|
638
412
|
donePlans: recentFinished(donePlanEntries(root), DONE_DATE_RE),
|
|
639
413
|
fixedBugs: recentFinished(fixedBugEntries(root), FIXED_DATE_RE),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/** What needs the operator (status-payload.ts's field-group split): team-alerts.ts's
|
|
417
|
+
* teamAlerts, phrased once for the dashboard's alert banners and the TUI's attention lines. */
|
|
418
|
+
function alertFields(root, snap, questions, roles, now) {
|
|
419
|
+
return {
|
|
640
420
|
// What needs the operator (team-alerts.ts's teamAlerts): the dashboard's alert banners and
|
|
641
421
|
// the TUI's attention lines, phrased once.
|
|
642
|
-
alerts: teamAlerts(snap, questions, roles, now, refusedEntries(root),
|
|
422
|
+
alerts: teamAlerts(snap, questions, roles, now, refusedEntries(root), planDependencyAlert(root), stalledEntries(root), strandedPlanEntriesFor(root), duplicatedBacklogHeadings(root), validationGapProblems(root), capacityContention(root, now), regressedFixesAlert(root, now), staleClaimEntries(root, roles.map((r) => r.role), now), staleOpenQuestions(root, now), fixClaimProblems(root), staleWorkspacePins(root, now), liveFinishedEntriesFor(root), instancePoolProblems(root), providerCapProblems(root)),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** The one team-state document both observer surfaces carry: `GET /api/status` (gui/gui-server.ts)
|
|
426
|
+
* spreads it and adds the serving process's own `serverBuildSha` (the page's cue to notice a
|
|
427
|
+
* newer build and reload), while `tumwater status --json` (cli.ts) prints it verbatim — every
|
|
428
|
+
* other field is shared, so the dashboard and the CLI can never drift apart. Assembled here —
|
|
429
|
+
* not in gui/gui-server.ts — because it is shared data collection for observers, not part of
|
|
430
|
+
* serving HTTP: snapshot() supplies the core state, and the per-role phase/metrics fields come
|
|
431
|
+
* from the same status-model helpers the TUI table uses.
|
|
432
|
+
*
|
|
433
|
+
* `now` pins the poll's clock for the time-derived fields (see snapshot's seam of the same
|
|
434
|
+
* name) — spend stamp-check, burn-rate projection, disk badge, and the pause countdown — so a
|
|
435
|
+
* test's expected value cannot race the payload's own Date.now(). `modelsPath` mirrors
|
|
436
|
+
* snapshot's seam of the same name, so a test can resolve a role's declared context window
|
|
437
|
+
* against a fixture models catalog instead of the live ~/.pi one. */
|
|
438
|
+
export function statusPayload(root, now = Date.now(), scope = null, modelsPath = piModelsPath()) {
|
|
439
|
+
const full = snapshot(root, modelsPath, now);
|
|
440
|
+
// `--role` scopes the role rows (and the alerts computed from them); budget stays team-wide.
|
|
441
|
+
const snap = scope ? scopeSnapshotToRoles(full, scope) : full;
|
|
442
|
+
// One event read feeds both feed shapes below, so `events[i]` and `eventItems[i]` always
|
|
443
|
+
// describe the same event.
|
|
444
|
+
const recent = readEvents(root, RECENT_EVENTS);
|
|
445
|
+
const questions = openQuestions(root);
|
|
446
|
+
// Why each plan/bug is held (src/backlog/backlog-eligibility.ts): the same hold decision
|
|
447
|
+
// the prompt's backlog index and `tumwater backlog` render, projected to the mark each
|
|
448
|
+
// surface appends. Read once here, consumed as the parallel planHolds/bugHolds arrays below.
|
|
449
|
+
const marks = backlogMarks(root);
|
|
450
|
+
const holds = backlogHolds(root);
|
|
451
|
+
const plans = plannedPlans(root);
|
|
452
|
+
// Which blocked plans fold into a series row: the grouping `plan --list` and the prompt's
|
|
453
|
+
// backlog index use, as a slot per plan (null for a plan that stays an ordinary row).
|
|
454
|
+
const working = backlogInProgress(root);
|
|
455
|
+
const planSeries = planSeriesSlots(plans, holds, working);
|
|
456
|
+
const roles = roleRows(snap, root);
|
|
457
|
+
return {
|
|
458
|
+
...headerFields(root, snap, now),
|
|
459
|
+
...mergeAndCheckFields(root, snap, now),
|
|
460
|
+
...inboxFields(snap),
|
|
461
|
+
...budgetAndPauseFields(snap),
|
|
462
|
+
roles,
|
|
463
|
+
...feedFields(recent),
|
|
464
|
+
...backlogFields(root, plans, marks, holds, planSeries, questions),
|
|
465
|
+
...alertFields(root, snap, questions, roles, now),
|
|
643
466
|
};
|
|
644
467
|
}
|