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,6 +47,8 @@ export function createTuiPanes(deps) {
|
|
|
47
47
|
session.dropBrokenPinsArmed = false;
|
|
48
48
|
session.armedRetireLeftovers = session.retireLeftoversArmed;
|
|
49
49
|
session.retireLeftoversArmed = false;
|
|
50
|
+
session.armedArchiveBacklog = session.archiveBacklogArmed;
|
|
51
|
+
session.archiveBacklogArmed = false;
|
|
50
52
|
session.armedRevert = session.revertArmed;
|
|
51
53
|
session.revertArmed = null;
|
|
52
54
|
session.armedQueue = session.queueArmed;
|
|
@@ -114,57 +116,47 @@ export function createTuiPanes(deps) {
|
|
|
114
116
|
}
|
|
115
117
|
session.requestRender();
|
|
116
118
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// The
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
if (key.ctrl && key.name === "g") {
|
|
144
|
-
// Ctrl+G's scope arm is routed by view: Activity on 0, the History exact-role scope on
|
|
145
|
-
// N+4, the Pending overlap scope on N+5, and the report team/base-role scope
|
|
146
|
-
// everywhere else — the priority the original else-branch chain kept, so the
|
|
147
|
-
// report arm never steals the History view's slot.
|
|
148
|
-
if (session.view === 0)
|
|
149
|
-
return activity.handleKey?.(key, ctx) ?? false;
|
|
150
|
-
if (session.view === session.roleIds.length + 4)
|
|
151
|
-
return history.handleKey?.(key, ctx) ?? false;
|
|
152
|
-
if (session.view === session.roleIds.length + 5)
|
|
153
|
-
return pending.handleKey?.(key, ctx) ?? false;
|
|
154
|
-
return report.handleKey?.(key, ctx) ?? false;
|
|
155
|
-
}
|
|
156
|
-
if (key.ctrl && key.name === "e") {
|
|
157
|
-
// Ctrl+E's Health reclaim arm is routed ahead of the report panes' trailing-window
|
|
158
|
-
// cycle; the Health arm's own view guard keeps Usage/Failures on their cycle, and the
|
|
159
|
-
// Backlog/Inbox edit arms (which live in the editors) are untouched.
|
|
160
|
-
if (health.handleKey?.(key, ctx))
|
|
161
|
-
return true;
|
|
162
|
-
return report.handleKey?.(key, ctx) ?? false;
|
|
163
|
-
}
|
|
164
|
-
if (key.ctrl && key.name === "k")
|
|
165
|
-
return health.handleKey?.(key, ctx) ?? false;
|
|
166
|
-
if (key.ctrl && key.name === "w")
|
|
119
|
+
const routeCtrlN = (key, ctx) => {
|
|
120
|
+
// Ctrl+N means four different things. The Health arm is routed first so its notify-test
|
|
121
|
+
// action is reachable on Health; the History trail arm second, which falls through
|
|
122
|
+
// (returns false) unless the History view is selected; the Pending overlap drill-down
|
|
123
|
+
// third, with the same fall-through; the transcript pane's notes/change arm last.
|
|
124
|
+
// The order among them is NOT immaterial: the transcript arm
|
|
125
|
+
// consumes Ctrl+N whenever it is reached — it returns true with a render even off a
|
|
126
|
+
// transcript view — so it must sit last, as the fallback, or it would swallow the key
|
|
127
|
+
// before the Health or History arm ever saw it.
|
|
128
|
+
if (health.handleKey?.(key, ctx))
|
|
129
|
+
return true;
|
|
130
|
+
if (history.handleKey?.(key, ctx))
|
|
131
|
+
return true;
|
|
132
|
+
if (pending.handleKey?.(key, ctx))
|
|
133
|
+
return true;
|
|
134
|
+
return transcript.handleKey?.(key, ctx) ?? false;
|
|
135
|
+
};
|
|
136
|
+
const routeCtrlG = (key, ctx) => {
|
|
137
|
+
// Ctrl+G's scope arm is routed by view: Activity on 0, the History exact-role scope on
|
|
138
|
+
// N+4, the Pending overlap scope on N+5, and the report team/base-role scope
|
|
139
|
+
// everywhere else — the priority the original else-branch chain kept, so the
|
|
140
|
+
// report arm never steals the History view's slot.
|
|
141
|
+
if (session.view === 0)
|
|
142
|
+
return activity.handleKey?.(key, ctx) ?? false;
|
|
143
|
+
if (session.view === session.roleIds.length + 4)
|
|
167
144
|
return history.handleKey?.(key, ctx) ?? false;
|
|
145
|
+
if (session.view === session.roleIds.length + 5)
|
|
146
|
+
return pending.handleKey?.(key, ctx) ?? false;
|
|
147
|
+
return report.handleKey?.(key, ctx) ?? false;
|
|
148
|
+
};
|
|
149
|
+
const routeCtrlE = (key, ctx) => {
|
|
150
|
+
// Ctrl+E's Health reclaim arm is routed ahead of the report panes' trailing-window
|
|
151
|
+
// cycle; the Health arm's own view guard keeps Usage/Failures on their cycle, and the
|
|
152
|
+
// Backlog/Inbox edit arms (which live in the editors) are untouched.
|
|
153
|
+
if (health.handleKey?.(key, ctx))
|
|
154
|
+
return true;
|
|
155
|
+
return report.handleKey?.(key, ctx) ?? false;
|
|
156
|
+
};
|
|
157
|
+
const routeCtrlK = (key, ctx) => health.handleKey?.(key, ctx) ?? false;
|
|
158
|
+
const routeCtrlW = (key, ctx) => history.handleKey?.(key, ctx) ?? false;
|
|
159
|
+
const routeCtrlS = (key, ctx) => {
|
|
168
160
|
// Health owns Ctrl+S (drop broken pins) ahead of the pane router's fallthrough; off the
|
|
169
161
|
// Health view it falls through so the quiet-hours (Activity), trailing-window (History)
|
|
170
162
|
// and min-run-interval (Transcript) arms keep their current meanings.
|
|
@@ -177,15 +169,20 @@ export function createTuiPanes(deps) {
|
|
|
177
169
|
}
|
|
178
170
|
if (key.ctrl && key.name === "s")
|
|
179
171
|
return history.handleKey?.(key, ctx) ?? false;
|
|
172
|
+
return false;
|
|
173
|
+
};
|
|
174
|
+
const routeCtrlP = (key, ctx) => {
|
|
180
175
|
// Ctrl+P on Usage/Failures steps the report window back; every other view is History's.
|
|
181
176
|
if (key.ctrl && key.name === "p" && (session.view === session.roleIds.length + 2 || session.view === session.roleIds.length + 3)) {
|
|
182
177
|
return report.handleKey?.(key, ctx) ?? false;
|
|
183
178
|
}
|
|
184
|
-
// Health owns Ctrl+P
|
|
185
|
-
|
|
186
|
-
return health.handleKey?.(key, ctx) ?? false;
|
|
179
|
+
// Health owns none of the Ctrl+P cycle now that the image-model re-probe is gone; it
|
|
180
|
+
// falls through to History's Ctrl+P below.
|
|
187
181
|
if (key.ctrl && key.name === "p")
|
|
188
182
|
return history.handleKey?.(key, ctx) ?? false;
|
|
183
|
+
return false;
|
|
184
|
+
};
|
|
185
|
+
const routeCtrlA = (key, ctx) => {
|
|
189
186
|
// Health owns Ctrl+A (commit-backlog) ahead of the pane router's fallthrough; the Backlog
|
|
190
187
|
// view's Ctrl+A (the answer editor) is handled by tui-editor-answer.ts, which the key
|
|
191
188
|
// dispatcher consults before this router, so this case never shadows it.
|
|
@@ -195,10 +192,24 @@ export function createTuiPanes(deps) {
|
|
|
195
192
|
// Health's so the Health commit-backlog arm keeps its view's slot.
|
|
196
193
|
if (key.ctrl && key.name === "a" && session.view === session.roleIds.length + 5)
|
|
197
194
|
return pending.handleKey?.(key, ctx) ?? false;
|
|
195
|
+
return false;
|
|
196
|
+
};
|
|
197
|
+
const routeCtrlU = (key, ctx) => {
|
|
198
198
|
// Health owns Ctrl+U (re-sync install) ahead of the pane router's fallthrough; off the
|
|
199
199
|
// Health view it falls through so the prompt-line editor keeps Ctrl+U (kill-to-start).
|
|
200
200
|
if (key.ctrl && key.name === "u" && session.view === session.roleIds.length + 7)
|
|
201
201
|
return health.handleKey?.(key, ctx) ?? false;
|
|
202
|
+
return false;
|
|
203
|
+
};
|
|
204
|
+
const routeCtrlB = (key, ctx) => {
|
|
205
|
+
// Health owns Ctrl+B (archive backlog) ahead of the pane router's fallthrough; the prompt-
|
|
206
|
+
// line editors are consulted first, so the budget editor keeps Ctrl+B where it applies,
|
|
207
|
+
// and off the Health view Ctrl+B is otherwise unbound.
|
|
208
|
+
if (key.ctrl && key.name === "b" && session.view === session.roleIds.length + 7)
|
|
209
|
+
return health.handleKey?.(key, ctx) ?? false;
|
|
210
|
+
return false;
|
|
211
|
+
};
|
|
212
|
+
const routeCtrlL = (key, ctx) => {
|
|
202
213
|
// Health owns Ctrl+L (retire leftovers) ahead of the pane router's fallthrough; off the
|
|
203
214
|
// Health view it falls through so the Activity fallback shift (view 0) and the
|
|
204
215
|
// instructions editor on a role's Transcript keep their current meanings.
|
|
@@ -209,104 +220,153 @@ export function createTuiPanes(deps) {
|
|
|
209
220
|
// (handled in the editors ahead of this router) keep their current meanings.
|
|
210
221
|
if (key.ctrl && key.name === "l")
|
|
211
222
|
return history.handleKey?.(key, ctx) ?? false;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
if (key.ctrl && key.name === "x") {
|
|
236
|
-
// The armed-clear Ctrl+X arms: the transcript notes first, then the History model filter,
|
|
237
|
-
// then the queued cancel, then the Pending merge cancel, then the backlog cancel — each
|
|
238
|
-
// returns false when its view/selection does not apply.
|
|
239
|
-
if (transcript.handleKey?.(key, ctx))
|
|
240
|
-
return true;
|
|
241
|
-
if (history.handleKey?.(key, ctx))
|
|
242
|
-
return true;
|
|
243
|
-
if (queued.handleKey?.(key, ctx))
|
|
244
|
-
return true;
|
|
245
|
-
if (pending.handleKey?.(key, ctx))
|
|
246
|
-
return true;
|
|
247
|
-
return backlog.handleKey?.(key, ctx) ?? false;
|
|
248
|
-
}
|
|
249
|
-
if (key.ctrl && key.name === "r") {
|
|
250
|
-
// The Ctrl+R arms: the Pending pane's queue/roster re-read, the Health pane's doctor
|
|
251
|
-
// re-run, the History trail's transcript cycle, then the backlog's release — each returns
|
|
252
|
-
// false when its view does not apply, so the order among them is immaterial.
|
|
253
|
-
if (pending.handleKey?.(key, ctx))
|
|
254
|
-
return true;
|
|
255
|
-
if (health.handleKey?.(key, ctx))
|
|
256
|
-
return true;
|
|
257
|
-
if (history.handleKey?.(key, ctx))
|
|
258
|
-
return true;
|
|
259
|
-
return backlog.handleKey?.(key, ctx) ?? false;
|
|
260
|
-
}
|
|
261
|
-
if (key.ctrl && key.name === "v") {
|
|
262
|
-
// The Ctrl+V arms, routed like Ctrl+X's: the History trail's revert, the queued
|
|
263
|
-
// prompt's move-to-top, then the Pending pane's merge move-to-top, and the backlog
|
|
264
|
-
// entry's move-to-top last — each returns false when its view/selection does not
|
|
265
|
-
// apply. The Activity attach editor's own Ctrl+V arm is guarded to view 0, so it
|
|
266
|
-
// still fires there ahead of this router.
|
|
267
|
-
if (history.handleKey?.(key, ctx))
|
|
268
|
-
return true;
|
|
269
|
-
if (queued.handleKey?.(key, ctx))
|
|
270
|
-
return true;
|
|
271
|
-
if (pending.handleKey?.(key, ctx))
|
|
272
|
-
return true;
|
|
223
|
+
return false;
|
|
224
|
+
};
|
|
225
|
+
const routeCtrlY = (key, ctx) => {
|
|
226
|
+
// Ctrl+Y's transcript change-step arm (the diffstat toggle) is routed first and falls
|
|
227
|
+
// through off it, so History keeps its Summary cycle on every other view.
|
|
228
|
+
if (transcript.handleKey?.(key, ctx))
|
|
229
|
+
return true;
|
|
230
|
+
return history.handleKey?.(key, ctx) ?? false;
|
|
231
|
+
};
|
|
232
|
+
const routeCtrlO = (key, ctx) => {
|
|
233
|
+
// The Activity type cycle is routed first on its own view (0); on the Health view the
|
|
234
|
+
// problems-only toggle precedes the History outcome cycle; on the Pending view the
|
|
235
|
+
// post-merge review state filter is routed ahead of History's outcome cycle (its own arm
|
|
236
|
+
// consumes the key on Pending); on the Backlog view the Ready-only filter is routed
|
|
237
|
+
// ahead of History's outcome cycle, and every other view keeps the History outcome cycle.
|
|
238
|
+
if (session.view === 0)
|
|
239
|
+
return activity.handleKey?.(key, ctx) ?? false;
|
|
240
|
+
if (session.view === session.roleIds.length + 7)
|
|
241
|
+
return health.handleKey?.(key, ctx) ?? false;
|
|
242
|
+
if (session.view === session.roleIds.length + 5)
|
|
243
|
+
return pending.handleKey?.(key, ctx) ?? false;
|
|
244
|
+
if (session.view === session.roleIds.length + 1)
|
|
273
245
|
return backlog.handleKey?.(key, ctx) ?? false;
|
|
274
|
-
|
|
246
|
+
return history.handleKey?.(key, ctx) ?? false;
|
|
247
|
+
};
|
|
248
|
+
const routeCtrlX = (key, ctx) => {
|
|
249
|
+
// The armed-clear Ctrl+X arms: the transcript notes first, then the History model filter,
|
|
250
|
+
// then the queued cancel, then the Pending merge cancel, then the backlog cancel — each
|
|
251
|
+
// returns false when its view/selection does not apply.
|
|
252
|
+
if (transcript.handleKey?.(key, ctx))
|
|
253
|
+
return true;
|
|
254
|
+
if (history.handleKey?.(key, ctx))
|
|
255
|
+
return true;
|
|
256
|
+
if (queued.handleKey?.(key, ctx))
|
|
257
|
+
return true;
|
|
258
|
+
if (pending.handleKey?.(key, ctx))
|
|
259
|
+
return true;
|
|
260
|
+
return backlog.handleKey?.(key, ctx) ?? false;
|
|
261
|
+
};
|
|
262
|
+
const routeCtrlR = (key, ctx) => {
|
|
263
|
+
// The Ctrl+R arms: the Pending pane's queue/roster re-read, the Health pane's doctor
|
|
264
|
+
// re-run, the History trail's transcript cycle, then the backlog's release — each returns
|
|
265
|
+
// false when its view does not apply, so the order among them is immaterial.
|
|
266
|
+
if (pending.handleKey?.(key, ctx))
|
|
267
|
+
return true;
|
|
268
|
+
if (health.handleKey?.(key, ctx))
|
|
269
|
+
return true;
|
|
270
|
+
if (history.handleKey?.(key, ctx))
|
|
271
|
+
return true;
|
|
272
|
+
return backlog.handleKey?.(key, ctx) ?? false;
|
|
273
|
+
};
|
|
274
|
+
const routeCtrlV = (key, ctx) => {
|
|
275
|
+
// The Ctrl+V arms, routed like Ctrl+X's: the History trail's revert, the queued
|
|
276
|
+
// prompt's move-to-top, then the Pending pane's merge move-to-top, and the backlog
|
|
277
|
+
// entry's move-to-top last — each returns false when its view/selection does not
|
|
278
|
+
// apply. The Activity attach editor's own Ctrl+V arm is guarded to view 0, so it
|
|
279
|
+
// still fires there ahead of this router.
|
|
280
|
+
if (history.handleKey?.(key, ctx))
|
|
281
|
+
return true;
|
|
282
|
+
if (queued.handleKey?.(key, ctx))
|
|
283
|
+
return true;
|
|
284
|
+
if (pending.handleKey?.(key, ctx))
|
|
285
|
+
return true;
|
|
286
|
+
return backlog.handleKey?.(key, ctx) ?? false;
|
|
287
|
+
};
|
|
288
|
+
const routeArrow = (key, ctx) => {
|
|
289
|
+
// Entry-browse arrows: the backlog pane, then the Pending pane (merge-row selection),
|
|
290
|
+
// then the queued pane; every other view falls through so the editors' prompt-history
|
|
291
|
+
// recall keeps the arrows.
|
|
292
|
+
if (backlog.handleKey?.(key, ctx))
|
|
293
|
+
return true;
|
|
294
|
+
if (pending.handleKey?.(key, ctx))
|
|
295
|
+
return true;
|
|
296
|
+
if (queued.handleKey?.(key, ctx))
|
|
297
|
+
return true;
|
|
298
|
+
return false;
|
|
299
|
+
};
|
|
300
|
+
const routePage = (key, ctx) => {
|
|
301
|
+
// Within-body page routing in today's order: the Activity tail, the notes/change
|
|
302
|
+
// (transcript) and run-trail (history) bodies, the Markdown panes from report on, then
|
|
303
|
+
// the backlog entry body.
|
|
304
|
+
if (activity.handleKey?.(key, ctx))
|
|
305
|
+
return true;
|
|
306
|
+
if (transcript.handleKey?.(key, ctx))
|
|
307
|
+
return true;
|
|
308
|
+
if (history.handleKey?.(key, ctx))
|
|
309
|
+
return true;
|
|
310
|
+
if (report.handleKey?.(key, ctx))
|
|
311
|
+
return true;
|
|
312
|
+
if (backlog.handleKey?.(key, ctx))
|
|
313
|
+
return true;
|
|
314
|
+
return false;
|
|
315
|
+
};
|
|
316
|
+
const routeEscape = (key, ctx) => {
|
|
317
|
+
if (pending.handleKey?.(key, ctx))
|
|
318
|
+
return true;
|
|
319
|
+
return queued.handleKey?.(key, ctx) ?? false;
|
|
320
|
+
};
|
|
321
|
+
/** Run the pane arms of the key dispatch; true when one consumed the key. `es` is the
|
|
322
|
+
* editors' state, so the exclusion guards read the flags the editors hold. Each pane module
|
|
323
|
+
* checks its own view/guard and returns true only when it consumed the key; this router keeps
|
|
324
|
+
* the keys that multiple panes serve in today's priority order. */
|
|
325
|
+
const handleKey = (key, es) => {
|
|
326
|
+
// The editor flags below are all false here — the editors consume every key that reads or
|
|
327
|
+
// writes the line and return true whenever one is open — but each module restates its
|
|
328
|
+
// mutual-exclusion guard so it reads on its own instead of leaning on the routing order.
|
|
329
|
+
const ctx = { es };
|
|
330
|
+
if (key.ctrl && key.name === "n")
|
|
331
|
+
return routeCtrlN(key, ctx);
|
|
332
|
+
if (key.ctrl && key.name === "g")
|
|
333
|
+
return routeCtrlG(key, ctx);
|
|
334
|
+
if (key.ctrl && key.name === "e")
|
|
335
|
+
return routeCtrlE(key, ctx);
|
|
336
|
+
if (key.ctrl && key.name === "k")
|
|
337
|
+
return routeCtrlK(key, ctx);
|
|
338
|
+
if (key.ctrl && key.name === "w")
|
|
339
|
+
return routeCtrlW(key, ctx);
|
|
340
|
+
if (key.ctrl && key.name === "s")
|
|
341
|
+
return routeCtrlS(key, ctx);
|
|
342
|
+
if (key.ctrl && key.name === "p")
|
|
343
|
+
return routeCtrlP(key, ctx);
|
|
344
|
+
if (key.ctrl && key.name === "a")
|
|
345
|
+
return routeCtrlA(key, ctx);
|
|
346
|
+
if (key.ctrl && key.name === "u")
|
|
347
|
+
return routeCtrlU(key, ctx);
|
|
348
|
+
if (key.ctrl && key.name === "b")
|
|
349
|
+
return routeCtrlB(key, ctx);
|
|
350
|
+
if (key.ctrl && key.name === "l")
|
|
351
|
+
return routeCtrlL(key, ctx);
|
|
352
|
+
if (key.ctrl && key.name === "y")
|
|
353
|
+
return routeCtrlY(key, ctx);
|
|
354
|
+
if (key.ctrl && key.name === "o")
|
|
355
|
+
return routeCtrlO(key, ctx);
|
|
356
|
+
if (key.ctrl && key.name === "x")
|
|
357
|
+
return routeCtrlX(key, ctx);
|
|
358
|
+
if (key.ctrl && key.name === "r")
|
|
359
|
+
return routeCtrlR(key, ctx);
|
|
360
|
+
if (key.ctrl && key.name === "v")
|
|
361
|
+
return routeCtrlV(key, ctx);
|
|
275
362
|
const arrow = arrowDir(key.name);
|
|
276
|
-
if (arrow !== null)
|
|
277
|
-
|
|
278
|
-
// then the queued pane; every other view falls through so the editors' prompt-history
|
|
279
|
-
// recall keeps the arrows.
|
|
280
|
-
if (backlog.handleKey?.(key, ctx))
|
|
281
|
-
return true;
|
|
282
|
-
if (pending.handleKey?.(key, ctx))
|
|
283
|
-
return true;
|
|
284
|
-
if (queued.handleKey?.(key, ctx))
|
|
285
|
-
return true;
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
363
|
+
if (arrow !== null)
|
|
364
|
+
return routeArrow(key, ctx);
|
|
288
365
|
const page = pageDir(key.name);
|
|
289
|
-
if (page !== null)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (activity.handleKey?.(key, ctx))
|
|
294
|
-
return true;
|
|
295
|
-
if (transcript.handleKey?.(key, ctx))
|
|
296
|
-
return true;
|
|
297
|
-
if (history.handleKey?.(key, ctx))
|
|
298
|
-
return true;
|
|
299
|
-
if (report.handleKey?.(key, ctx))
|
|
300
|
-
return true;
|
|
301
|
-
if (backlog.handleKey?.(key, ctx))
|
|
302
|
-
return true;
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
if (key.name === "escape") {
|
|
306
|
-
if (pending.handleKey?.(key, ctx))
|
|
307
|
-
return true;
|
|
308
|
-
return queued.handleKey?.(key, ctx) ?? false;
|
|
309
|
-
}
|
|
366
|
+
if (page !== null)
|
|
367
|
+
return routePage(key, ctx);
|
|
368
|
+
if (key.name === "escape")
|
|
369
|
+
return routeEscape(key, ctx);
|
|
310
370
|
return false;
|
|
311
371
|
};
|
|
312
372
|
/** Shift, clamp, and prune the pane state against a new roster. */
|
|
@@ -396,6 +456,9 @@ export function createTuiPanes(deps) {
|
|
|
396
456
|
refreshQueued: queued.refreshQueued,
|
|
397
457
|
refreshNotes: (role) => transcript.refreshNotes?.(role),
|
|
398
458
|
refreshBrief: () => brief.refreshBrief?.(),
|
|
459
|
+
refreshReport: () => {
|
|
460
|
+
report.enter?.();
|
|
461
|
+
},
|
|
399
462
|
clearBacklogSelection: backlog.clearBacklogSelection,
|
|
400
463
|
applyHistorySearch: history.applySearch,
|
|
401
464
|
applyActivitySearch: activity.applySearch,
|
package/dist/src/ui/tui/tui.js
CHANGED
|
@@ -2,8 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { render as inkRender } from "ink";
|
|
3
3
|
import { TuiApp } from "./tui-app.js";
|
|
4
4
|
import { openQuestions } from "../../backlog/backlog.js";
|
|
5
|
-
import {
|
|
6
|
-
import { capacityContention,
|
|
5
|
+
import { refusedEntries, stalledEntries } from "../../backlog/backlog-eligibility.js";
|
|
6
|
+
import { capacityContention, instancePoolProblems, planDependencyAlert, providerCapProblems, regressedFixesAlert, staleOpenQuestions, staleWorkspacePins } from "../alert-feeds.js";
|
|
7
7
|
import { strandedPlanEntriesFor } from "../../backlog/backlog-structure.js";
|
|
8
8
|
import { duplicatedBacklogHeadings, fixClaimProblems, liveFinishedEntriesFor, validationGapProblems } from "../../doctor/doctor-backlog-plans.js";
|
|
9
9
|
import { snapshot } from "../../status/status-data.js";
|
|
@@ -93,7 +93,7 @@ export async function runTui(root, seams = {}) {
|
|
|
93
93
|
const view = s.view;
|
|
94
94
|
// What needs the operator — the dashboard's alert banners (team-alerts.ts's teamAlerts), as
|
|
95
95
|
// attention lines under the header. Each consumes one line of the height budget.
|
|
96
|
-
const attention = alertLines(teamAlerts(snap, openQuestions(root), status.roles, Date.now(), refusedEntries(root),
|
|
96
|
+
const attention = alertLines(teamAlerts(snap, openQuestions(root), status.roles, Date.now(), refusedEntries(root), planDependencyAlert(root), stalledEntries(root), strandedPlanEntriesFor(root), duplicatedBacklogHeadings(root), validationGapProblems(root), capacityContention(root), regressedFixesAlert(root, Date.now()), staleClaimEntries(root, status.roles.map((r) => r.role), Date.now()), staleOpenQuestions(root), fixClaimProblems(root), staleWorkspacePins(root, Date.now()), liveFinishedEntriesFor(root), instancePoolProblems(root), providerCapProblems(root)), width);
|
|
97
97
|
const statusLines = [status.lines[0] ?? [], ...attention, ...status.lines.slice(1)];
|
|
98
98
|
// Numbered previews of prompts queued for the director, between the table and the
|
|
99
99
|
// activity pane: what will run next, in execution order. Each line consumes exactly
|
|
@@ -124,7 +124,7 @@ export async function runTui(root, seams = {}) {
|
|
|
124
124
|
// The frame as the ink tree draws it (tui-app.tsx): the same composed lines the
|
|
125
125
|
// hand-painted string frame joined, as clipped StatusLines — one column row per line,
|
|
126
126
|
// one styled Text per span, colors through tui-app.tsx's toneColor.
|
|
127
|
-
const mode = { budget: s.budgetMode, concurrencyMode: s.concurrencyMode, checksMode: s.checksMode, checkMode: s.checkMode, fallbackMode: s.fallbackMode, modelMode: s.modelMode, rolePromptFor: s.rolePromptFor, attachMode: s.attachMode, pauseEditMode: s.pauseEditMode, pauseEditRole: s.pauseEditRole, pauseEditWake: s.pauseEditWake, pauseEditStop: s.pauseEditStop, queuedEditFor: s.queuedEditFor, answerFor: s.answerFor, entryEditFor: s.entryEditFor, notesEditFor: s.notesEditFor, briefEditMode: s.briefEditMode, retireMode: s.retireMode, roleEnabledMode: s.roleEnabledMode, quietMode: s.quietMode, quietTeam: s.quietTeam, providerCapsMode: s.providerCapsMode, claudeReserveMode: s.claudeReserveMode, intervalMode: s.intervalMode, roleModelMode: s.roleModelMode, instructionsEditFor: s.instructionsEditFor, slotsMode: s.slotsMode, images: s.images, notifyMode: s.notifyMode, backendsMode: s.backendsMode, instancesMode: s.instancesMode };
|
|
127
|
+
const mode = { budget: s.budgetMode, concurrencyMode: s.concurrencyMode, checksMode: s.checksMode, checkMode: s.checkMode, fallbackMode: s.fallbackMode, modelMode: s.modelMode, rolePromptFor: s.rolePromptFor, attachMode: s.attachMode, pauseEditMode: s.pauseEditMode, pauseEditRole: s.pauseEditRole, pauseEditWake: s.pauseEditWake, pauseEditStop: s.pauseEditStop, queuedEditFor: s.queuedEditFor, answerFor: s.answerFor, entryEditFor: s.entryEditFor, notesEditFor: s.notesEditFor, briefEditMode: s.briefEditMode, retireMode: s.retireMode, releaseMode: s.releaseMode, roleEnabledMode: s.roleEnabledMode, quietMode: s.quietMode, quietTeam: s.quietTeam, providerCapsMode: s.providerCapsMode, claudeReserveMode: s.claudeReserveMode, intervalMode: s.intervalMode, roleModelMode: s.roleModelMode, instructionsEditFor: s.instructionsEditFor, slotsMode: s.slotsMode, images: s.images, notifyMode: s.notifyMode, backendsMode: s.backendsMode, instancesMode: s.instancesMode };
|
|
128
128
|
const lines = [...statusLines, []]; // a blank row below the header
|
|
129
129
|
for (const [i, preview] of queued.entries())
|
|
130
130
|
lines.push(toneLine(`${i + 1}. ${preview}`, width));
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* - A `path:line` location (`status-roles.ts:66`) was checked as a symbol, and no file is named
|
|
44
44
|
* that. A location now claims its file, a bare line reference (`:368`) claims nothing, and a
|
|
45
45
|
* bare file name is backed by a file of that name anywhere in the walked source dirs. */
|
|
46
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
46
|
+
import { existsSync, lstatSync, readFileSync, statSync } from "node:fs";
|
|
47
47
|
import { extname, join } from "node:path";
|
|
48
48
|
import { readTextOrNull, statOrNull, walkFiles } from "../files/files.js";
|
|
49
49
|
import { fenceTracker, finishedArchiveOf, parseEntryDetails, sectionLines, trailingParenthetical, } from "../backlog/backlog-md.js";
|
|
@@ -307,11 +307,23 @@ export async function falseFixReason(wt, mainBranch, files) {
|
|
|
307
307
|
// is no claim at all. Both sides of the comparison read both files.
|
|
308
308
|
if (!files.includes("BUGS.md") && !files.includes(BUGS_ARCHIVE))
|
|
309
309
|
return undefined;
|
|
310
|
-
// A deleted or
|
|
311
|
-
// so this check passes and lets the gate's sibling structural check
|
|
312
|
-
// report the dropped section. Reading
|
|
313
|
-
//
|
|
314
|
-
|
|
310
|
+
// A deleted, unreadable, or non-regular (symlinked) file reads as empty: there is no Fixed
|
|
311
|
+
// transition to verify, so this check passes and lets the gate's sibling structural check
|
|
312
|
+
// (backlog-structure.ts) report the dropped section. Reading a symlink raw would pull an
|
|
313
|
+
// outside host file's `## Fixed` heading into this gate's verdict (the same lstat policy the
|
|
314
|
+
// backlog-conflict resolver and backlogStructureReason pin under INV-G3), so a head is read
|
|
315
|
+
// only when lstat shows a regular file; ENOENT and symlinks both read as empty.
|
|
316
|
+
const heads = ["BUGS.md", BUGS_ARCHIVE].map((f) => {
|
|
317
|
+
const full = join(wt, f);
|
|
318
|
+
try {
|
|
319
|
+
if (!lstatSync(full).isFile())
|
|
320
|
+
return "";
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
return readTextOrNull(full) ?? "";
|
|
326
|
+
});
|
|
315
327
|
if (!heads.some((head) => head.includes("## Fixed")))
|
|
316
328
|
return undefined;
|
|
317
329
|
// The diff's own base, not main's tip: the gate diffs `mainBranch...HEAD` against the
|
|
@@ -3,7 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { commitPathsAndDiscardRest, fileContentAt } from "../git/git.js";
|
|
4
4
|
import { writeTextAtomic } from "../files/files.js";
|
|
5
5
|
import { parseEntryDetails } from "../backlog/backlog-md.js";
|
|
6
|
-
import { actionableEntryRanges
|
|
6
|
+
import { actionableEntryRanges } from "../backlog/backlog-index-data.js";
|
|
7
|
+
import { stripEntryStamp } from "../backlog/backlog-structure.js";
|
|
7
8
|
import { entryHoldAgainst, refusalNote, withoutRefusalNotes } from "../backlog/backlog-eligibility.js";
|
|
8
9
|
import { entryKey, plannedPartKeys } from "../backlog/backlog-parts.js";
|
|
9
10
|
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
@@ -87,6 +88,15 @@ export function newlyRefusedTitles(baseMd, headMd, section) {
|
|
|
87
88
|
* logs a warning and leaves PLANS.md out of the commit, so the other notes still merge. */
|
|
88
89
|
export async function handleRefusal(ctx, state, wt, pi) {
|
|
89
90
|
const reason = (pi.refusedReason ?? "").trim() || "no reason given";
|
|
91
|
+
const plansUnstripped = await stripBlockedPlanRefusalNotes(ctx, wt);
|
|
92
|
+
const notes = await collectRefusalNotes(wt, plansUnstripped);
|
|
93
|
+
const entries = await collectRefusedEntryTitles(ctx, wt, notes);
|
|
94
|
+
return mergeRefusalNotes(ctx, state, wt, reason, notes, entries);
|
|
95
|
+
}
|
|
96
|
+
/** Strip this run's newly added Refused notes from any `## Planned` entry main still holds as
|
|
97
|
+
* blocked by a planned prerequisite. Returns whether the strip write failed: a throw leaves the
|
|
98
|
+
* flag set and PLANS.md out of the commit, so the other notes still merge. */
|
|
99
|
+
async function stripBlockedPlanRefusalNotes(ctx, wt) {
|
|
90
100
|
const plansFile = path.join(wt, "PLANS.md");
|
|
91
101
|
let plansUnstripped = false;
|
|
92
102
|
if (fs.existsSync(plansFile)) {
|
|
@@ -117,9 +127,17 @@ export async function handleRefusal(ctx, state, wt, pi) {
|
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
|
-
|
|
130
|
+
return plansUnstripped;
|
|
131
|
+
}
|
|
132
|
+
/** The markdown notes this refusal may commit: changed `.md` files, PLANS.md dropped only when
|
|
133
|
+
* the blocked-plan strip write failed. */
|
|
134
|
+
async function collectRefusalNotes(wt, plansUnstripped) {
|
|
135
|
+
return (await changedFiles(wt)).filter((f) => f.toLowerCase().endsWith(".md") && !(plansUnstripped && f === "PLANS.md"));
|
|
136
|
+
}
|
|
137
|
+
/** Best-effort names for the entry_refused event, per changed file's section — it must never
|
|
138
|
+
* fail the refusal, so a read error swallows and leaves that file unnamed. */
|
|
139
|
+
async function collectRefusedEntryTitles(ctx, wt, notes) {
|
|
121
140
|
const entries = [];
|
|
122
|
-
// Best-effort naming for the entry_refused event; it must never fail the refusal.
|
|
123
141
|
for (const { file, section } of [{ file: "PLANS.md", section: "Planned" }, { file: "BUGS.md", section: "Open" }]) {
|
|
124
142
|
if (!notes.includes(file))
|
|
125
143
|
continue;
|
|
@@ -130,10 +148,19 @@ export async function handleRefusal(ctx, state, wt, pi) {
|
|
|
130
148
|
// Unnamed is fine: the event still carries reason and files.
|
|
131
149
|
}
|
|
132
150
|
}
|
|
151
|
+
return entries;
|
|
152
|
+
}
|
|
153
|
+
/** Merge this refusal's notes onto main: commit only the markdown notes (subject + trailer only —
|
|
154
|
+
* a refusal carries no WHY/RISK/VERIFIED body; the reason is the subject, and the trailer's turn
|
|
155
|
+
* count is the same field the friction flag reads), then merge directly. A failed note merge (a
|
|
156
|
+
* throw such as a lock timeout, or a non-"changed" result) is recorded in `state.lastError` and
|
|
157
|
+
* reported through the "refused" result like any other non-merge. A merged refusal note supersedes
|
|
158
|
+
* a stale rejection exactly as applyMergeOutcome's merged branch does (this path merges directly,
|
|
159
|
+
* bypassing that fold), then pages the operator: the entry stays held until a human or the
|
|
160
|
+
* director lifts it. */
|
|
161
|
+
async function mergeRefusalNotes(ctx, state, wt, reason, notes, entries) {
|
|
133
162
|
let commit;
|
|
134
163
|
if (notes.length > 0) {
|
|
135
|
-
// Subject + trailer only — a refusal carries no WHY/RISK/VERIFIED body; the reason is
|
|
136
|
-
// the subject, and the trailer's turn count is the same field the friction flag reads.
|
|
137
164
|
const message = buildCommitMessage(stampedSubject(baseRoleOf(ctx.role), `refuse — ${reason}`), null, commitTrailer(ctx.role, state.runs, ctx.turns, state.peakContextTokens, undefined, undefined, ctx.modelLine));
|
|
138
165
|
commit = (await commitPathsAndDiscardRest(wt, message, notes)) ?? undefined;
|
|
139
166
|
}
|
|
@@ -155,8 +182,6 @@ export async function handleRefusal(ctx, state, wt, pi) {
|
|
|
155
182
|
}
|
|
156
183
|
if (result !== "changed")
|
|
157
184
|
state.lastError = `refusal note merge failed: ${result}`;
|
|
158
|
-
// A merged refusal note is a merged change too: it supersedes a stale rejection exactly as
|
|
159
|
-
// applyMergeOutcome's merged branch does (this path merges directly, bypassing that fold).
|
|
160
185
|
else {
|
|
161
186
|
clearSupersededRejection(state);
|
|
162
187
|
// Page the operator: the entry stays held until a human or the director lifts it. Best-effort —
|
package/package.json
CHANGED