tumwater 0.1.2 → 0.2.0
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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/**
|
|
3
|
+
* The dashboard's POST backlog/QUESTIONS command handlers (barrelled into
|
|
4
|
+
* gui/gui-endpoint-commands.ts, which routes to them from src/gui/gui-server.ts):
|
|
5
|
+
* question-answer, backlog-file, backlog-cancel, backlog-release, backlog-top, and
|
|
6
|
+
* backlog-edit — the `checkedBacklogKind` / `checkedEntryPositionAndTitle` validators they share
|
|
7
|
+
* ride with them. Each handler adapts
|
|
8
|
+
* HTTP onto the domain work one layer down (backlog-write, question-answer, backlog-commit)
|
|
9
|
+
* and touches no socket beyond its own `res`.
|
|
10
|
+
*/
|
|
11
|
+
import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
|
|
12
|
+
import { stampedSubject } from "../git/commit-message.js";
|
|
13
|
+
import { fileEntryWithBody, normalizeEntryBody, OPERATOR_FILE_KINDS, tryCancelEntry, tryEditEntry, tryMoveEntryToTop, tryReleaseEntry, bodyStructureProblem } from "../backlog/backlog-write.js";
|
|
14
|
+
import { tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE } from "../backlog/question-answer.js";
|
|
15
|
+
import { promptLengthProblem } from "../inbox/inbox-submit.js";
|
|
16
|
+
import { requestWake, submitRolePromptAndWake, wakeQuestionAsker } from "../operator/operator-intent.js";
|
|
17
|
+
import { badRequest, okGone, sendJson } from "./http-body.js";
|
|
18
|
+
import { collapseWhitespace, errorMessage, gotSuffix } from "../text/text.js";
|
|
19
|
+
import { readPostBody } from "./gui-endpoint-common.js";
|
|
20
|
+
/** Shared 400-validation for an entry-target body's `kind` — "bug" or "plan" — the one field
|
|
21
|
+
* the backlog file/cancel/edit endpoints all ask for in the same words. Sends the 400 and
|
|
22
|
+
* returns null when invalid, else the validated kind. */
|
|
23
|
+
function checkedBacklogKind(res, body) {
|
|
24
|
+
const kind = body.kind;
|
|
25
|
+
if (kind !== "bug" && kind !== "plan") {
|
|
26
|
+
badRequest(res, `kind must be "bug" or "plan"${kind === undefined ? "" : gotSuffix(kind)}`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return kind;
|
|
30
|
+
}
|
|
31
|
+
/** Shared 400-validation for an entry-target body's `position` (a positive integer) and
|
|
32
|
+
* `title` (a non-empty string) — the two fields every backlog/QUESTIONS entry drawer asks
|
|
33
|
+
* for, so the question-answer, backlog-cancel, backlog-release, backlog-top, and
|
|
34
|
+
* backlog-edit endpoints report the same two errors and cannot drift on the messages. Sends the
|
|
35
|
+
* 400 and returns null on the first bad field; the validated pair otherwise. */
|
|
36
|
+
function checkedEntryPositionAndTitle(res, body) {
|
|
37
|
+
const { position, title } = body;
|
|
38
|
+
if (typeof position !== "number" || !Number.isInteger(position) || position < 1) {
|
|
39
|
+
badRequest(res, `position must be a positive integer${position === undefined ? "" : gotSuffix(position)}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof title !== "string" || title === "") {
|
|
43
|
+
badRequest(res, `title must be a non-empty string${title === undefined ? "" : gotSuffix(title)}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return { position, title };
|
|
47
|
+
}
|
|
48
|
+
/** Run a backlog/QUESTIONS domain call, mapping a thrown filesystem/validation error onto the
|
|
49
|
+
* same 400 `{error}` reply the handlers' explicit validation arms send — so a thrown check and
|
|
50
|
+
* an explicit one answer identically and the try/catch no longer opens at every call site, one
|
|
51
|
+
* return each. Every try* outcome type is a union of object shapes (never null), so null is a
|
|
52
|
+
* safe "already answered" sentinel: the caller returns early on it, matching the `return` the
|
|
53
|
+
* inline try/catch used. */
|
|
54
|
+
function outcomeOrBadRequest(res, run) {
|
|
55
|
+
try {
|
|
56
|
+
return run();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
badRequest(res, errorMessage(err));
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Handle POST /api/question-answer: record the operator's answer to the Nth open question
|
|
64
|
+
* exactly as `tumwater questions answer` does, through the non-exiting tryAnswerQuestion core.
|
|
65
|
+
* The body carries the `title` the operator saw at `position`; a different title there, or no
|
|
66
|
+
* such position, answers 200 { ok: true, status: "gone" } (the outbox moved on — data for the
|
|
67
|
+
* flash line, like /api/prompt-cancel). The decision keeps its lines (`questions answer --file`'s
|
|
68
|
+
* rule): a `## `/`### ` heading line or an unclosed fence in it answers 400 (bodyStructureProblem)
|
|
69
|
+
* with nothing written. An unclosed fence in `## Open` answers 409; a blank
|
|
70
|
+
* decision, a non-positive-integer position, or a missing title answers 400, and so does a
|
|
71
|
+
* filesystem failure (handleConfigSet's precedent). */
|
|
72
|
+
export async function handleQuestionAnswer(req, res, root) {
|
|
73
|
+
const body = await readPostBody(req, res, '{"position": 1, "title": "<heading>", "decision": "..."}');
|
|
74
|
+
if (!body)
|
|
75
|
+
return;
|
|
76
|
+
const { decision } = body;
|
|
77
|
+
const pair = checkedEntryPositionAndTitle(res, body);
|
|
78
|
+
if (!pair)
|
|
79
|
+
return;
|
|
80
|
+
const { position, title } = pair;
|
|
81
|
+
if (typeof decision !== "string" || decision.trim() === "") {
|
|
82
|
+
badRequest(res, 'questions answer needs a decision: questions answer <n> "<decision>"');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const keptDecision = normalizeEntryBody(decision);
|
|
86
|
+
const problem = bodyStructureProblem(keptDecision);
|
|
87
|
+
if (problem !== null) {
|
|
88
|
+
badRequest(res, problem);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const outcome = outcomeOrBadRequest(res, () => tryAnswerQuestion(root, position, keptDecision, title, true));
|
|
92
|
+
if (outcome === null)
|
|
93
|
+
return;
|
|
94
|
+
if (outcome.status === "answered") {
|
|
95
|
+
const committed = await commitOperatorBacklogEdit(root, "QUESTIONS.md", stampedSubject("operator", `Answer question: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
96
|
+
const wake = wakeQuestionAsker(root, outcome.title);
|
|
97
|
+
sendJson(res, 200, { ok: true, status: "answered", question: outcome.title, decision: outcome.decision, committed, ...(wake === null ? {} : { wake }) });
|
|
98
|
+
}
|
|
99
|
+
else if (outcome.status === "fenced") {
|
|
100
|
+
sendJson(res, 409, { ok: false, error: UNCLOSED_FENCE_MESSAGE });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
okGone(res);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Handle POST /api/backlog-file: the Plans/Bugs tabs' New control — the same entry and wake
|
|
107
|
+
* `tumwater bug`/`tumwater plan` produce (fileEntryWithBody, then the shared wake text to the
|
|
108
|
+
* bugfix/feature loop). Body `{"kind": "bug"|"plan", "title": "...", "body": "optional
|
|
109
|
+
* markdown"}`. The empty-title and wake-length checks run here, before anything is written:
|
|
110
|
+
* the body is normalized and checked by bodyStructureProblem too, and an over-long wake
|
|
111
|
+
* would throw after the entry merged. */
|
|
112
|
+
export async function handleBacklogFile(req, res, root) {
|
|
113
|
+
const body = await readPostBody(req, res, '{"kind": "plan", "title": "...", "body": "optional markdown"}');
|
|
114
|
+
if (!body)
|
|
115
|
+
return;
|
|
116
|
+
const kind = checkedBacklogKind(res, body);
|
|
117
|
+
if (!kind)
|
|
118
|
+
return;
|
|
119
|
+
const rawTitle = typeof body.title === "string" ? body.title : "";
|
|
120
|
+
if (collapseWhitespace(rawTitle) === "") {
|
|
121
|
+
badRequest(res, "title required");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const { role, wakeText } = OPERATOR_FILE_KINDS[kind];
|
|
125
|
+
const problem = promptLengthProblem(wakeText(collapseWhitespace(rawTitle)), role);
|
|
126
|
+
if (problem) {
|
|
127
|
+
badRequest(res, problem);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const rawBody = body.body;
|
|
131
|
+
if (rawBody !== undefined && typeof rawBody !== "string") {
|
|
132
|
+
badRequest(res, "body must be a string");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const entryBody = normalizeEntryBody(rawBody ?? "");
|
|
136
|
+
const bodyProblem = bodyStructureProblem(entryBody);
|
|
137
|
+
if (bodyProblem) {
|
|
138
|
+
badRequest(res, bodyProblem);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const filed = fileEntryWithBody(root, kind, rawTitle, entryBody);
|
|
142
|
+
const committed = await commitOperatorBacklogEdit(root, filed.file, stampedSubject("operator", `File ${kind}: ${filed.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
143
|
+
submitRolePromptAndWake(root, role, wakeText(filed.title));
|
|
144
|
+
sendJson(res, 200, { ok: true, file: filed.file, title: filed.title, committed });
|
|
145
|
+
}
|
|
146
|
+
/** Handle POST /api/backlog-cancel: the entry drawer's cancel — the same lock and splice as
|
|
147
|
+
* `tumwater plan|bug --cancel <n>` through the non-exiting tryCancelEntry. Body `{"kind":
|
|
148
|
+
* "bug"|"plan", "position": <1-based>, "title": "<heading>"}`; a missing position or a
|
|
149
|
+
* different heading there answers 200 { ok: true, status: "gone" } (the list moved since the
|
|
150
|
+
* poll). No wake and no inbox prompt: removing work must not wake a loop. */
|
|
151
|
+
export async function handleBacklogCancel(req, res, root) {
|
|
152
|
+
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
|
|
153
|
+
if (!body)
|
|
154
|
+
return;
|
|
155
|
+
const kind = checkedBacklogKind(res, body);
|
|
156
|
+
if (!kind)
|
|
157
|
+
return;
|
|
158
|
+
const pair = checkedEntryPositionAndTitle(res, body);
|
|
159
|
+
if (!pair)
|
|
160
|
+
return;
|
|
161
|
+
const { position, title } = pair;
|
|
162
|
+
const outcome = outcomeOrBadRequest(res, () => tryCancelEntry(root, kind, position, title));
|
|
163
|
+
if (outcome === null)
|
|
164
|
+
return;
|
|
165
|
+
if (outcome.status === "cancelled") {
|
|
166
|
+
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Cancel ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
167
|
+
sendJson(res, 200, { ok: true, status: "cancelled", file: outcome.file, title: outcome.title, committed });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
okGone(res);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Handle POST /api/backlog-release: the entry drawer's release — the same lock and splice as
|
|
174
|
+
* `tumwater plan|bug --release <n>` through the non-exiting tryReleaseEntry. Body as
|
|
175
|
+
* backlog-cancel's. A released entry is newly eligible, so the kind's role is woken; `gone`
|
|
176
|
+
* and `moved` answer okGone, and an entry with neither a Refused nor a Waiting-for-the-next-
|
|
177
|
+
* failure note answers status "not_refused" with no write and no wake. */
|
|
178
|
+
export async function handleBacklogRelease(req, res, root) {
|
|
179
|
+
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
|
|
180
|
+
if (!body)
|
|
181
|
+
return;
|
|
182
|
+
const kind = checkedBacklogKind(res, body);
|
|
183
|
+
if (!kind)
|
|
184
|
+
return;
|
|
185
|
+
const pair = checkedEntryPositionAndTitle(res, body);
|
|
186
|
+
if (!pair)
|
|
187
|
+
return;
|
|
188
|
+
const { position, title } = pair;
|
|
189
|
+
const outcome = outcomeOrBadRequest(res, () => tryReleaseEntry(root, kind, position, title));
|
|
190
|
+
if (outcome === null)
|
|
191
|
+
return;
|
|
192
|
+
if (outcome.status === "released") {
|
|
193
|
+
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Release ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
194
|
+
requestWake(root, [OPERATOR_FILE_KINDS[kind].role]);
|
|
195
|
+
sendJson(res, 200, { ok: true, status: "released", file: outcome.file, title: outcome.title, committed });
|
|
196
|
+
}
|
|
197
|
+
else if (outcome.status === "not_refused") {
|
|
198
|
+
sendJson(res, 200, { ok: true, status: "not_refused", title: outcome.title });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
okGone(res);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Handle POST /api/backlog-top: the entry drawer's Move to top — the same splice as
|
|
205
|
+
* `tumwater plan|bug --top <n>` through the non-exiting tryMoveEntryToTop. Body as
|
|
206
|
+
* backlog-cancel's. Reordering makes no entry newly eligible, so no role is woken; position 1
|
|
207
|
+
* answers status "already_first" with no write, and `gone` and `moved` answer okGone. */
|
|
208
|
+
export async function handleBacklogTop(req, res, root) {
|
|
209
|
+
const body = await readPostBody(req, res, '{"kind": "bug", "position": 2, "title": "<heading>"}');
|
|
210
|
+
if (!body)
|
|
211
|
+
return;
|
|
212
|
+
const kind = checkedBacklogKind(res, body);
|
|
213
|
+
if (!kind)
|
|
214
|
+
return;
|
|
215
|
+
const pair = checkedEntryPositionAndTitle(res, body);
|
|
216
|
+
if (!pair)
|
|
217
|
+
return;
|
|
218
|
+
const { position, title } = pair;
|
|
219
|
+
const outcome = outcomeOrBadRequest(res, () => tryMoveEntryToTop(root, kind, position, title));
|
|
220
|
+
if (outcome === null)
|
|
221
|
+
return;
|
|
222
|
+
if (outcome.status === "moved_to_top") {
|
|
223
|
+
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Prioritize ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
224
|
+
sendJson(res, 200, { ok: true, status: "moved_to_top", file: outcome.file, title: outcome.title, committed });
|
|
225
|
+
}
|
|
226
|
+
else if (outcome.status === "already_first") {
|
|
227
|
+
sendJson(res, 200, { ok: true, status: "already_first", title: outcome.title });
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
okGone(res);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Handle POST /api/backlog-edit: the entry drawer's edit — tryEditEntry's title-checked,
|
|
234
|
+
* in-place rewrite under the state lock. Body `{"kind", "position", "title", "newTitle",
|
|
235
|
+
* "newBody"}`; a missing position or a different heading there answers 200 { ok: true, status:
|
|
236
|
+
* "gone" }. The body keeps its line structure. No wake and no inbox prompt: whichever tick picks
|
|
237
|
+
* the entry up refines it. */
|
|
238
|
+
export async function handleBacklogEdit(req, res, root) {
|
|
239
|
+
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>", "newTitle": "...", "newBody": "..."}');
|
|
240
|
+
if (!body)
|
|
241
|
+
return;
|
|
242
|
+
const kind = checkedBacklogKind(res, body);
|
|
243
|
+
if (!kind)
|
|
244
|
+
return;
|
|
245
|
+
const pair = checkedEntryPositionAndTitle(res, body);
|
|
246
|
+
if (!pair)
|
|
247
|
+
return;
|
|
248
|
+
const { position, title } = pair;
|
|
249
|
+
const { newTitle, newBody } = body;
|
|
250
|
+
if (typeof newTitle !== "string" || collapseWhitespace(newTitle) === "") {
|
|
251
|
+
badRequest(res, "newTitle required");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (typeof newBody !== "string") {
|
|
255
|
+
badRequest(res, "newBody must be a string");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const problem = bodyStructureProblem(newBody.replace(/\r\n?/g, "\n"));
|
|
259
|
+
if (problem !== null) {
|
|
260
|
+
badRequest(res, problem);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const outcome = outcomeOrBadRequest(res, () => tryEditEntry(root, kind, position, title, newTitle, newBody));
|
|
264
|
+
if (outcome === null)
|
|
265
|
+
return;
|
|
266
|
+
if (outcome.status === "edited") {
|
|
267
|
+
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Edit ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
268
|
+
sendJson(res, 200, { ok: true, status: "edited", file: outcome.file, title: outcome.title, committed });
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
okGone(res);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -75,7 +75,7 @@ export async function cmdGui(root, args, seams = {}) {
|
|
|
75
75
|
if (allInterfaces) {
|
|
76
76
|
// Name the concrete URLs teammates can open (token included, so they are openable
|
|
77
77
|
// as printed), and say what exposure means: without a token the dashboard has no
|
|
78
|
-
// auth and its prompt box steers the
|
|
78
|
+
// auth and its prompt box steers the team; with one, the token is the gate.
|
|
79
79
|
for (const addr of lan())
|
|
80
80
|
print(` also at http://${addr}:${port}${tokenSuffix}`);
|
|
81
81
|
print(token
|