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
|
@@ -47,207 +47,234 @@ export function roleInspectMarkdown(root, role) {
|
|
|
47
47
|
* the head with the selected entry's full body in entry mode, usage/failures re-window
|
|
48
48
|
* the cached Markdown set on view entry, and the activity pane keeps the event tail. */
|
|
49
49
|
export function paneBody(pane, opts) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Filtered: scan the drawer's 200 entries with the shared keep rule (a lowercased needle).
|
|
75
|
-
return {
|
|
76
|
-
body: readTranscript(root, pane.role, 200, { grep: pane.grep.toLowerCase() }).slice(-eventBudget).map((l) => toneLine(l, width, transcriptTone(l))),
|
|
77
|
-
emptyNote: `(no transcript entries match "${pane.grep}")`,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
50
|
+
if (pane.kind === "transcript")
|
|
51
|
+
return transcriptBody(pane, opts);
|
|
52
|
+
if (pane.kind === "backlog")
|
|
53
|
+
return backlogBody(pane, opts);
|
|
54
|
+
if (pane.kind === "history")
|
|
55
|
+
return historyBody(pane, opts);
|
|
56
|
+
if (pane.kind === "pending")
|
|
57
|
+
return pendingBody(pane, opts);
|
|
58
|
+
if (pane.kind === "health")
|
|
59
|
+
return healthBody(pane, opts);
|
|
60
|
+
if (pane.kind === "brief")
|
|
61
|
+
return briefBody(pane, opts);
|
|
62
|
+
if (pane.kind === "queued")
|
|
63
|
+
return queuedBody(pane, opts);
|
|
64
|
+
if (pane.kind === "usage" || pane.kind === "failures")
|
|
65
|
+
return reportBody(pane, opts);
|
|
66
|
+
return activityBody(pane, opts);
|
|
67
|
+
}
|
|
68
|
+
function transcriptBody(pane, opts) {
|
|
69
|
+
const { root, width, eventBudget, state } = opts;
|
|
70
|
+
// The notes view (Ctrl+N) windows the cached inspection Markdown exactly like the
|
|
71
|
+
// usage/failures panes, headings bold; the live transcript keeps the tail.
|
|
72
|
+
if (pane.note) {
|
|
73
|
+
const win = entryBodyWindow(state.noteCache ?? "", state.paneScroll, eventBudget, width);
|
|
80
74
|
return {
|
|
81
|
-
body:
|
|
82
|
-
emptyNote: "(
|
|
75
|
+
body: win.lines.map((l) => toneLine(l, width, l.startsWith("#") ? "bold" : undefined)),
|
|
76
|
+
emptyNote: "(nothing to inspect yet)",
|
|
83
77
|
};
|
|
84
78
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// then again inside the flat list, so every render paid twice for the same reads and verdicts.
|
|
93
|
-
const flat = browsableBacklogEntries(root);
|
|
94
|
-
// A `/` search narrows the browsable list to the matching open entries (the answered/done/
|
|
95
|
-
// fixed rows are read-only and hidden while a search is applied); entry mode still opens
|
|
96
|
-
// `flat[sel]`, the unfiltered index the selection holds.
|
|
97
|
-
const grep = state.backlogGrep ?? null;
|
|
98
|
-
const shown = flat.filter((e) => {
|
|
99
|
-
if (grep !== null && !(isOpenBacklogLabel(e.label) && entryMatches(e, grep)))
|
|
100
|
-
return false;
|
|
101
|
-
if (state.backlogReadyOnly && isOpenBacklogLabel(e.label) && e.hold !== null)
|
|
102
|
-
return false;
|
|
103
|
-
return true;
|
|
104
|
-
});
|
|
105
|
-
// A stale selection (an entry removed since the last render) clamps to the last; no
|
|
106
|
-
// entries at all falls back to list mode.
|
|
107
|
-
const sel = state.selectedEntry === null || flat.length === 0 ? null : Math.min(state.selectedEntry, flat.length - 1);
|
|
108
|
-
if (sel === null) {
|
|
109
|
-
// List mode: section headings stand out, empty sections read dim. Keeps the HEAD of the
|
|
110
|
-
// list when it overflows — file order is newest-first, unlike events which keep the tail.
|
|
111
|
-
const listed = (label) => shown.filter((e) => e.label === label).map((e) => e.title + e.mark);
|
|
112
|
-
// Plans list a series' blocked parts as one line (as `plan --list` does); the selection
|
|
113
|
-
// below still walks the flat list, one entry per part.
|
|
114
|
-
const plans = shown.filter((e) => e.label === "plan");
|
|
115
|
-
// A `/` search active: the answered/done/fixed sections are hidden, the sections build
|
|
116
|
-
// only from the matching open entries under a `search: "<grep>"` header, and nothing
|
|
117
|
-
// matching paints a self-explanatory line.
|
|
118
|
-
if (grep !== null) {
|
|
119
|
-
const prefix = [`search: "${grep}"`];
|
|
120
|
-
const rest = shown.length === 0
|
|
121
|
-
? [`(no entries match "${grep}")`]
|
|
122
|
-
: backlogLines(planListLines(plans), listed("bug"), listed("question"), plans.length);
|
|
123
|
-
return {
|
|
124
|
-
body: [...prefix, ...rest]
|
|
125
|
-
.slice(0, eventBudget)
|
|
126
|
-
.map((l) => toneLine(l, width, /^(?:plans|open bugs|open questions) \(\d+\):$/.test(l) ? "bold" : l.startsWith("(") ? "dim" : undefined)),
|
|
127
|
-
emptyNote: "(no events yet)",
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// The newest answered questions follow the open ones: they are in `flat` (up/down open
|
|
131
|
-
// their bodies), but no edit key acts on them.
|
|
132
|
-
const answered = answeredQuestionSummaries(root, DASHBOARD_ANSWERED_QUESTIONS).map((q) => q.decision ? `${q.title} — ${q.decision}` : q.title);
|
|
133
|
-
// Done plans and fixed bugs follow, browsable and read-only the same way.
|
|
134
|
-
const finishedRows = (rows, verb) => rows.map((r) => (r.completedOn ? `${r.title} — ${verb} ${r.completedOn}` : r.title));
|
|
135
|
-
const finished = {
|
|
136
|
-
done: finishedRows(recentFinished(donePlanEntries(root), DONE_DATE_RE), "done"),
|
|
137
|
-
fixed: finishedRows(recentFinished(fixedBugEntries(root), FIXED_DATE_RE), "fixed"),
|
|
138
|
-
};
|
|
139
|
-
return {
|
|
140
|
-
body: backlogLines(planListLines(plans), listed("bug"), listed("question"), plans.length, answered, finished)
|
|
141
|
-
.slice(0, eventBudget)
|
|
142
|
-
.map((l) => toneLine(l, width, /^(?:plans|open bugs|open questions|answered recently|done recently|fixed recently) \(\d+\):$/.test(l) ? "bold" : l.startsWith("(") ? "dim" : undefined)),
|
|
143
|
-
emptyNote: "(no events yet)",
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
// Entry browsing: the selected entry's full body under a line naming its section and
|
|
147
|
-
// title.
|
|
148
|
-
const e = flat[sel];
|
|
149
|
-
const win = entryBodyWindow(e.body, state.entryScroll, entryBudget, width);
|
|
79
|
+
// The change view (the second Ctrl+N step) windows the collected unmerged-change text the
|
|
80
|
+
// same way; its async collector leaves the cache null for the first frames, so the pane
|
|
81
|
+
// paints the loading note rather than entryBodyWindow's "no details" placeholder.
|
|
82
|
+
if (pane.change) {
|
|
83
|
+
if (state.changeCache === null)
|
|
84
|
+
return { body: [], emptyNote: "(loading pending change…)" };
|
|
85
|
+
const win = entryBodyWindow(state.changeCache, state.paneScroll, eventBudget, width);
|
|
150
86
|
return {
|
|
151
|
-
body:
|
|
152
|
-
emptyNote: "(
|
|
87
|
+
body: win.lines.map((l) => toneLine(l, width, l.startsWith("#") ? "bold" : undefined)),
|
|
88
|
+
emptyNote: "(loading pending change…)",
|
|
153
89
|
};
|
|
154
90
|
}
|
|
155
|
-
if (pane.
|
|
156
|
-
//
|
|
157
|
-
// read found no run) paints the pane's own note rather than entryBodyWindow's placeholder.
|
|
158
|
-
if (pane.trail) {
|
|
159
|
-
if (state.trailCache === null)
|
|
160
|
-
return { body: [], emptyNote: "(no run trail)" };
|
|
161
|
-
const win = entryBodyWindow(state.trailCache, state.paneScroll, eventBudget, width);
|
|
162
|
-
return {
|
|
163
|
-
body: win.lines.map((l) => toneLine(l, width)),
|
|
164
|
-
emptyNote: "(no run trail)",
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
// historyPaneText returns "" when the log holds no completed runs; return no lines
|
|
168
|
-
// (rather than windowing an empty string, which entryBodyWindow replaces with its
|
|
169
|
-
// "no details" placeholder) so the frame prints the pane's own empty note instead.
|
|
170
|
-
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
|
|
91
|
+
if (pane.grep) {
|
|
92
|
+
// Filtered: scan the drawer's 200 entries with the shared keep rule (a lowercased needle).
|
|
171
93
|
return {
|
|
172
|
-
body:
|
|
173
|
-
emptyNote:
|
|
94
|
+
body: readTranscript(root, pane.role, 200, { grep: pane.grep.toLowerCase() }).slice(-eventBudget).map((l) => toneLine(l, width, transcriptTone(l))),
|
|
95
|
+
emptyNote: `(no transcript entries match "${pane.grep}")`,
|
|
174
96
|
};
|
|
175
97
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
98
|
+
return {
|
|
99
|
+
body: readTranscript(root, pane.role, eventBudget).slice(-eventBudget).map((l) => toneLine(l, width, transcriptTone(l))),
|
|
100
|
+
emptyNote: "(no transcript yet)",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function backlogBody(_pane, opts) {
|
|
104
|
+
const { root, width, eventBudget, entryBudget, state } = opts;
|
|
105
|
+
// The flat, labeled entry list is the one backlog read this frame needs: it carries every
|
|
106
|
+
// planned feature, open bug, and open question (then the newest answered questions, done
|
|
107
|
+
// plans and fixed bugs)
|
|
108
|
+
// from PLANS.md/BUGS.md/QUESTIONS.md with its section label and eligibility mark (the same
|
|
109
|
+
// entryHold verdict `tumwater backlog` renders). List and entry mode share this one pass;
|
|
110
|
+
// entry mode used to read the three sections and derive the holds directly and
|
|
111
|
+
// then again inside the flat list, so every render paid twice for the same reads and verdicts.
|
|
112
|
+
const flat = browsableBacklogEntries(root);
|
|
113
|
+
// A `/` search narrows the browsable list to the matching open entries (the answered/done/
|
|
114
|
+
// fixed rows are read-only and hidden while a search is applied); entry mode still opens
|
|
115
|
+
// `flat[sel]`, the unfiltered index the selection holds.
|
|
116
|
+
const grep = state.backlogGrep ?? null;
|
|
117
|
+
const shown = flat.filter((e) => {
|
|
118
|
+
if (grep !== null && !(isOpenBacklogLabel(e.label) && entryMatches(e, grep)))
|
|
119
|
+
return false;
|
|
120
|
+
if (state.backlogReadyOnly && isOpenBacklogLabel(e.label) && e.hold !== null)
|
|
121
|
+
return false;
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
// A stale selection (an entry removed since the last render) clamps to the last; no
|
|
125
|
+
// entries at all falls back to list mode.
|
|
126
|
+
const sel = state.selectedEntry === null || flat.length === 0 ? null : Math.min(state.selectedEntry, flat.length - 1);
|
|
127
|
+
if (sel === null) {
|
|
128
|
+
// List mode: section headings stand out, empty sections read dim. Keeps the HEAD of the
|
|
129
|
+
// list when it overflows — file order is newest-first, unlike events which keep the tail.
|
|
130
|
+
const listed = (label) => shown.filter((e) => e.label === label).map((e) => e.title + e.mark);
|
|
131
|
+
// Plans list a series' blocked parts as one line (as `plan --list` does); the selection
|
|
132
|
+
// below still walks the flat list, one entry per part.
|
|
133
|
+
const plans = shown.filter((e) => e.label === "plan");
|
|
134
|
+
// A `/` search active: the answered/done/fixed sections are hidden, the sections build
|
|
135
|
+
// only from the matching open entries under a `search: "<grep>"` header, and nothing
|
|
136
|
+
// matching paints a self-explanatory line.
|
|
137
|
+
if (grep !== null) {
|
|
138
|
+
const prefix = [`search: "${grep}"`];
|
|
139
|
+
const rest = shown.length === 0
|
|
140
|
+
? [`(no entries match "${grep}")`]
|
|
141
|
+
: backlogLines(planListLines(plans), listed("bug"), listed("question"), plans.length);
|
|
184
142
|
return {
|
|
185
|
-
body:
|
|
186
|
-
|
|
143
|
+
body: [...prefix, ...rest]
|
|
144
|
+
.slice(0, eventBudget)
|
|
145
|
+
.map((l) => toneLine(l, width, /^(?:plans|open bugs|open questions) \(\d+\):$/.test(l) ? "bold" : l.startsWith("(") ? "dim" : undefined)),
|
|
146
|
+
emptyNote: "(no events yet)",
|
|
187
147
|
};
|
|
188
148
|
}
|
|
189
|
-
|
|
149
|
+
// The newest answered questions follow the open ones: they are in `flat` (up/down open
|
|
150
|
+
// their bodies), but no edit key acts on them.
|
|
151
|
+
const answered = answeredQuestionSummaries(root, DASHBOARD_ANSWERED_QUESTIONS).map((q) => q.decision ? `${q.title} — ${q.decision}` : q.title);
|
|
152
|
+
// Done plans and fixed bugs follow, browsable and read-only the same way.
|
|
153
|
+
const finishedRows = (rows, verb) => rows.map((r) => (r.completedOn ? `${r.title} — ${verb} ${r.completedOn}` : r.title));
|
|
154
|
+
const finished = {
|
|
155
|
+
done: finishedRows(recentFinished(donePlanEntries(root), DONE_DATE_RE), "done"),
|
|
156
|
+
fixed: finishedRows(recentFinished(fixedBugEntries(root), FIXED_DATE_RE), "fixed"),
|
|
157
|
+
};
|
|
190
158
|
return {
|
|
191
|
-
body:
|
|
192
|
-
|
|
159
|
+
body: backlogLines(planListLines(plans), listed("bug"), listed("question"), plans.length, answered, finished)
|
|
160
|
+
.slice(0, eventBudget)
|
|
161
|
+
.map((l) => toneLine(l, width, /^(?:plans|open bugs|open questions|answered recently|done recently|fixed recently) \(\d+\):$/.test(l) ? "bold" : l.startsWith("(") ? "dim" : undefined)),
|
|
162
|
+
emptyNote: "(no events yet)",
|
|
193
163
|
};
|
|
194
164
|
}
|
|
195
|
-
|
|
196
|
-
|
|
165
|
+
// Entry browsing: the selected entry's full body under a line naming its section and
|
|
166
|
+
// title.
|
|
167
|
+
const e = flat[sel];
|
|
168
|
+
const win = entryBodyWindow(e.body, state.entryScroll, entryBudget, width);
|
|
169
|
+
return {
|
|
170
|
+
body: [toneLine(`${e.label}: ${e.title}${e.mark}`, width, "bold"), ...win.lines.map((l) => toneLine(l, width))],
|
|
171
|
+
emptyNote: "(no events yet)",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function historyBody(pane, opts) {
|
|
175
|
+
const { width, eventBudget, state } = opts;
|
|
176
|
+
// The run trail (Ctrl+N) windows the cached renderRunDetail text; a null cache (the
|
|
177
|
+
// read found no run) paints the pane's own note rather than entryBodyWindow's placeholder.
|
|
178
|
+
if (pane.trail) {
|
|
179
|
+
if (state.trailCache === null)
|
|
180
|
+
return { body: [], emptyNote: "(no run trail)" };
|
|
181
|
+
const win = entryBodyWindow(state.trailCache, state.paneScroll, eventBudget, width);
|
|
197
182
|
return {
|
|
198
183
|
body: win.lines.map((l) => toneLine(l, width)),
|
|
199
|
-
emptyNote: "(
|
|
184
|
+
emptyNote: "(no run trail)",
|
|
200
185
|
};
|
|
201
186
|
}
|
|
202
|
-
|
|
203
|
-
|
|
187
|
+
// historyPaneText returns "" when the log holds no completed runs; return no lines
|
|
188
|
+
// (rather than windowing an empty string, which entryBodyWindow replaces with its
|
|
189
|
+
// "no details" placeholder) so the frame prints the pane's own empty note instead.
|
|
190
|
+
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
|
|
191
|
+
return {
|
|
192
|
+
body: win.lines.map((l) => toneLine(l, width)),
|
|
193
|
+
emptyNote: "(no runs yet)",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function pendingBody(_pane, opts) {
|
|
197
|
+
const { width, eventBudget, state } = opts;
|
|
198
|
+
// A selected queued merge shows its detail in place of the queue list; otherwise paneCache
|
|
199
|
+
// stays null until the async collector resolves (the Ctrl+T handler kicks it off
|
|
200
|
+
// fire-and-forget); like history, return no lines while null so the frame prints the
|
|
201
|
+
// loading note rather than windowing an empty string into the "no details" placeholder.
|
|
202
|
+
if (state.queueDetail) {
|
|
203
|
+
const detail = state.queueDetail;
|
|
204
|
+
const sel = entryBodyWindow(detail, state.paneScroll, eventBudget, width);
|
|
204
205
|
return {
|
|
205
|
-
body:
|
|
206
|
-
emptyNote: "(
|
|
206
|
+
body: sel.lines.map((l) => toneLine(l, width)),
|
|
207
|
+
emptyNote: "(loading pending changes…)",
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
210
|
+
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
|
|
211
|
+
return {
|
|
212
|
+
body: win.lines.map((l) => toneLine(l, width)),
|
|
213
|
+
emptyNote: "(loading pending changes…)",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function healthBody(_pane, opts) {
|
|
217
|
+
const { width, eventBudget, state } = opts;
|
|
218
|
+
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
|
|
219
|
+
return {
|
|
220
|
+
body: win.lines.map((l) => toneLine(l, width)),
|
|
221
|
+
emptyNote: "(running health checks…)",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function briefBody(_pane, opts) {
|
|
225
|
+
const { width, eventBudget, state } = opts;
|
|
226
|
+
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
|
|
227
|
+
return {
|
|
228
|
+
body: win.lines.map((l) => toneLine(l, width)),
|
|
229
|
+
emptyNote: "(no project brief with a prompt block — run `tumwater init` first)",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function queuedBody(pane, opts) {
|
|
233
|
+
const { width, eventBudget, entryBudget, state } = opts;
|
|
234
|
+
// A set selection shows the row's full prompt under a cancel header (the numbered list
|
|
235
|
+
// clips long prompts); list mode windows the cached renderQueuedList string, which is
|
|
236
|
+
// computed synchronously on Ctrl+T entry over the same queue `tumwater prompt --list`
|
|
237
|
+
// prints, so paneCache is already set this frame. An empty queue falls to the pane's
|
|
238
|
+
// own note either way.
|
|
239
|
+
const sel = state.selectedQueued === null || state.queuedRows.length === 0
|
|
240
|
+
? null
|
|
241
|
+
: Math.min(state.selectedQueued, state.queuedRows.length - 1);
|
|
242
|
+
if (sel !== null) {
|
|
243
|
+
const row = state.queuedRows[sel];
|
|
244
|
+
const win = entryBodyWindow(row.text, state.paneScroll, entryBudget, width);
|
|
237
245
|
return {
|
|
238
|
-
body: [
|
|
246
|
+
body: [
|
|
247
|
+
toneLine(`${row.role}'s inbox #${row.position} (Ctrl+X to cancel)`, width, "bold"),
|
|
248
|
+
...win.lines.map((l) => toneLine(l, width)),
|
|
249
|
+
],
|
|
239
250
|
emptyNote: "(inbox empty)",
|
|
240
251
|
};
|
|
241
252
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
body: win.lines.map((l) => toneLine(l, width, l.startsWith("#") ? "bold" : undefined)),
|
|
248
|
-
emptyNote: "(no events yet)",
|
|
249
|
-
};
|
|
253
|
+
// An applied search names itself above the list; one that matched nothing says so instead
|
|
254
|
+
// of claiming the inbox is empty.
|
|
255
|
+
const searchLine = pane.grep ? [toneLine(`search: "${pane.grep}"`, width, "bold")] : [];
|
|
256
|
+
if (pane.grep && state.queuedRows.length === 0) {
|
|
257
|
+
return { body: [...searchLine, toneLine(`(no queued prompt matches "${pane.grep}")`, width)], emptyNote: "(inbox empty)" };
|
|
250
258
|
}
|
|
259
|
+
const listBudget = Math.max(1, eventBudget - searchLine.length);
|
|
260
|
+
const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, listBudget, width) : { lines: [] };
|
|
261
|
+
return {
|
|
262
|
+
body: [...searchLine, ...win.lines.map((l) => toneLine(l, width))],
|
|
263
|
+
emptyNote: "(inbox empty)",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function reportBody(_pane, opts) {
|
|
267
|
+
const { width, eventBudget, state } = opts;
|
|
268
|
+
// The Markdown `tumwater report`/`report --failures` prints, windowed like backlog entry
|
|
269
|
+
// mode. The cache is set on view entry by the Ctrl+T handler, so this only re-windows a string.
|
|
270
|
+
const win = entryBodyWindow(state.paneCache ?? "", state.paneScroll, eventBudget, width);
|
|
271
|
+
return {
|
|
272
|
+
body: win.lines.map((l) => toneLine(l, width, l.startsWith("#") ? "bold" : undefined)),
|
|
273
|
+
emptyNote: "(no events yet)",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function activityBody(pane, opts) {
|
|
277
|
+
const { root, width, eventBudget, state } = opts;
|
|
251
278
|
// The Activity pane loads a fixed tail once per read, narrows it by the active role scope
|
|
252
279
|
// and text search, and windows it: null follows the live tail (the newest eventBudget
|
|
253
280
|
// events), a stored offset pages back through the loaded events. Both clamp to
|