tumwater 0.2.0 → 0.3.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 +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/** The staging self-check's findings about backlog records (PLANS.md, BUGS.md, QUESTIONS.md and
|
|
2
|
+
* the finished-entry archives), split from stage-check.ts, which gathers them with the git-level
|
|
3
|
+
* path checks. Each takes the edited text (`head`) and its merge-base text (`base`), so only a
|
|
4
|
+
* problem this change adds is reported. All are staging-only: the review gate does not run
|
|
5
|
+
* them (see stage-check.ts for why each is left out of the gate). */
|
|
6
|
+
import { finishedTitle, readdedHeadings } from "../backlog/backlog-structure.js";
|
|
7
|
+
import { fenceAwareHeadingLines, headingTitle, parseEntryDetails, sectionBodyLines, } from "../backlog/backlog-md.js";
|
|
8
|
+
import { DONE_DATE_RE, FIXED_DATE_RE } from "../backlog/backlog.js";
|
|
9
|
+
import { refusalNote, waitingNote } from "../backlog/backlog-eligibility.js";
|
|
10
|
+
import { entryKey, partKey, planDependencyProblems, seriesPart } from "../backlog/backlog-parts.js";
|
|
11
|
+
import { WAITING_FOR_FAILURE_NOTE } from "../roles/role-guidance.js";
|
|
12
|
+
import { moreSuffix } from "../text/phrases.js";
|
|
13
|
+
import { collapseWhitespace, truncate } from "../text/text.js";
|
|
14
|
+
/** The finding for the heading `requires` clause problems that `head` (the edited PLANS.md)
|
|
15
|
+
* adds over `base` (its text at the merge-base), or undefined when it adds none; `archivedTitles`
|
|
16
|
+
* (PLANS-DONE.md's headings) count as existing headings on both sides. Only new
|
|
17
|
+
* problems count: one already on main is not this run's fault and stays doctor's to report, so
|
|
18
|
+
* the author is never asked to fix another run's heading. The most serious new problem is named
|
|
19
|
+
* — cycle, then dangling, unparsed, then misworded — with a count of the rest. */
|
|
20
|
+
export function planDependencyFinding(head, base, archivedTitles = []) {
|
|
21
|
+
const problems = (text) => planDependencyProblems(parseEntryDetails(text, "Planned"), [...parseEntryDetails(text, "Done").map((e) => e.title), ...archivedTitles]);
|
|
22
|
+
const was = problems(base);
|
|
23
|
+
const now = problems(head);
|
|
24
|
+
const newCycle = now.cycle !== null && was.cycle === null ? now.cycle : null;
|
|
25
|
+
const newDangling = now.dangling.filter((d) => !was.dangling.some((b) => b.title === d.title && b.ref === d.ref));
|
|
26
|
+
const newUnparsed = now.unparsed.filter((t) => !was.unparsed.includes(t));
|
|
27
|
+
const newMisworded = now.misworded.filter((m) => !was.misworded.some((b) => b.title === m.title && b.ref === m.ref));
|
|
28
|
+
const total = (newCycle !== null ? 1 : 0) + newDangling.length + newUnparsed.length + newMisworded.length;
|
|
29
|
+
if (total === 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
let first;
|
|
32
|
+
if (newCycle !== null) {
|
|
33
|
+
first = `PLANS.md's requires clauses now form a cycle: ${newCycle.join(" → ")} — every part on it stays blocked forever; ` + // vocabulary-ok: PLANS.md heading syntax
|
|
34
|
+
"edit one heading's `requires … landed` clause to break it"; // vocabulary-ok: PLANS.md heading syntax
|
|
35
|
+
}
|
|
36
|
+
else if (newDangling.length > 0) {
|
|
37
|
+
const d = newDangling[0];
|
|
38
|
+
first = `PLANS.md plan "${d.title}" requires ${d.ref}, which no PLANS.md heading carries — the claim allocator ` +
|
|
39
|
+
"ignores the clause and hands the plan out now; fix the series name or part number to match the prerequisite's heading";
|
|
40
|
+
}
|
|
41
|
+
else if (newUnparsed.length > 0) {
|
|
42
|
+
first = `PLANS.md plan "${newUnparsed[0]}" has a \`requires … landed\` clause that names no part the claim allocator can read — ` + // vocabulary-ok: PLANS.md heading syntax
|
|
43
|
+
"it holds nothing; write each ref as `<Series> i/n` or `part i/n` after a `<Series>, part i/n:` heading prefix";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const w = newMisworded[0];
|
|
47
|
+
first = `PLANS.md plan "${w.title}" names ${w.ref} as a prerequisite without a \`requires … landed\` clause — ` + // vocabulary-ok: PLANS.md heading syntax
|
|
48
|
+
"the claim allocator ignores that wording and hands the part out now; write it as `requires part i/n landed` — fix it before this change commits"; // vocabulary-ok: PLANS.md heading syntax
|
|
49
|
+
}
|
|
50
|
+
return `${first}${moreSuffix(total - 1)}`;
|
|
51
|
+
}
|
|
52
|
+
/** A finished heading's identity across done dates: the title without its completion stamp and
|
|
53
|
+
* its `(planned …)`/`(found …)` parenthetical, whitespace-collapsed. */
|
|
54
|
+
export function finishedKey(title, dateRe) {
|
|
55
|
+
return collapseWhitespace(finishedTitle(title, dateRe));
|
|
56
|
+
}
|
|
57
|
+
/** The findings for a finished entry that the change leaves in the archive too: a live
|
|
58
|
+
* `## Done`/`## Fixed` entry the head archive already holds under another stamp (the sweep would
|
|
59
|
+
* move it as a new entry, or leave it beside an edited copy), and an archive entry the change
|
|
60
|
+
* added a second time under a new stamp. At most one finding per rule. An exact copy of an
|
|
61
|
+
* archived entry (same heading and body) is skipped: the sweep drops it. A second copy under an
|
|
62
|
+
* identical heading is skipped too: `backlogStructureReason` reports it. */
|
|
63
|
+
export function finishedDuplicateFindings(spec, live, archive) {
|
|
64
|
+
const dateRe = spec.file === "PLANS.md" ? DONE_DATE_RE : FIXED_DATE_RE;
|
|
65
|
+
const keyed = (md) => parseEntryDetails(md, spec.section).map((e) => ({ ...e, key: finishedKey(e.title, dateRe) }));
|
|
66
|
+
const findings = [];
|
|
67
|
+
const liveBase = new Set(keyed(live.base).map((e) => e.key));
|
|
68
|
+
const archived = keyed(archive.head);
|
|
69
|
+
const copy = keyed(live.head).find((e) => {
|
|
70
|
+
if (liveBase.has(e.key))
|
|
71
|
+
return false;
|
|
72
|
+
const held = archived.filter((a) => a.key === e.key);
|
|
73
|
+
if (held.length === 0)
|
|
74
|
+
return false;
|
|
75
|
+
const heading = collapseWhitespace(e.title);
|
|
76
|
+
return !held.some((a) => collapseWhitespace(a.title) === heading && a.body === e.body);
|
|
77
|
+
});
|
|
78
|
+
if (copy !== undefined) {
|
|
79
|
+
findings.push(`${spec.file}'s "## ${spec.section}" entry "${truncate(copy.title, 160)}" is already archived in ${spec.archive} — ` +
|
|
80
|
+
"the commit-time sweep would leave two copies; edit the archived entry in place (its text and its stamp) and delete this copy");
|
|
81
|
+
}
|
|
82
|
+
if (readdedHeadings(archive.base, archive.head).length === 0) {
|
|
83
|
+
const count = (entries, key) => entries.filter((e) => e.key === key).length;
|
|
84
|
+
const was = keyed(archive.base);
|
|
85
|
+
const twice = archived.find((e) => count(archived, e.key) > 1 && count(archived, e.key) > count(was, e.key));
|
|
86
|
+
if (twice !== undefined) {
|
|
87
|
+
findings.push(`${spec.archive} holds "${truncate(twice.title, 160)}" ${count(archived, twice.key)} times — this change added a copy under a different heading or stamp; ` +
|
|
88
|
+
"keep the archived entry once, edited in place, and delete the other copies");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return findings;
|
|
92
|
+
}
|
|
93
|
+
/** The number of `- **Progress …` lines in an entry body. */
|
|
94
|
+
function progressCount(body) {
|
|
95
|
+
return body.split("\n").filter((l) => /^-\s+\*\*Progress\b/.test(l.trim())).length;
|
|
96
|
+
}
|
|
97
|
+
/** The finding for a BUGS.md bug that `head` (the edited file) shows with more Progress notes
|
|
98
|
+
* than `base` (its text at the merge-base) while it stays under `## Open` with neither a Waiting
|
|
99
|
+
* note nor a Refused note — bugfix's step 2 asks a run that cannot reproduce a bug to write the
|
|
100
|
+
* Waiting note, and runs that skip it leave the bug open to re-investigation. A newly filed bug
|
|
101
|
+
* is not a re-investigation. Names the first such bug in file order, with a count of the rest. */
|
|
102
|
+
export function waitingNoteFinding(head, base) {
|
|
103
|
+
const was = new Map(parseEntryDetails(base, "Open").map((e) => [entryKey(e.title), e.body]));
|
|
104
|
+
const counting = parseEntryDetails(head, "Open").filter((e) => {
|
|
105
|
+
const before = was.get(entryKey(e.title));
|
|
106
|
+
return (before !== undefined &&
|
|
107
|
+
progressCount(e.body) > progressCount(before) &&
|
|
108
|
+
waitingNote(e.body) === null &&
|
|
109
|
+
refusalNote(e.body) === null);
|
|
110
|
+
});
|
|
111
|
+
if (counting.length === 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
return (`BUGS.md bug "${counting[0].title}" gained a Progress note but stays open with no Waiting for the next ` +
|
|
114
|
+
`failure note — if this run could not reproduce it, add ${WAITING_FOR_FAILURE_NOTE} as its own line under ` +
|
|
115
|
+
`the heading so later runs wait for new evidence (bugfix's step 2); if it did reproduce, leave the entry ` +
|
|
116
|
+
`as it is${moreSuffix(counting.length - 1)}`);
|
|
117
|
+
}
|
|
118
|
+
/** The skeleton placeholder that marks an empty backlog section. */
|
|
119
|
+
const PLACEHOLDER_LINE = "_None yet._";
|
|
120
|
+
/** The live backlog files; the archives have no placeholder sections. */
|
|
121
|
+
export const PLACEHOLDER_FILES = ["PLANS.md", "BUGS.md", "QUESTIONS.md"];
|
|
122
|
+
/** Each `## ` section of `md` that holds the placeholder line outside a fence while also holding
|
|
123
|
+
* entries, mapped to its entry count. */
|
|
124
|
+
function placeholderSections(md) {
|
|
125
|
+
const found = new Map();
|
|
126
|
+
for (const title of fenceAwareHeadingLines(md, "## ").map((l) => headingTitle(l, "## "))) {
|
|
127
|
+
if (!sectionBodyLines(md, title).some((l) => l.trim() === PLACEHOLDER_LINE))
|
|
128
|
+
continue;
|
|
129
|
+
const count = parseEntryDetails(md, title).length;
|
|
130
|
+
if (count > 0)
|
|
131
|
+
found.set(title, count);
|
|
132
|
+
}
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
135
|
+
/** The finding for a section of `head` (the edited backlog file `file`) that holds entries and the
|
|
136
|
+
* empty-section placeholder while `base` (its text at the merge-base) did not, so damage already
|
|
137
|
+
* on main is not blamed on this change. Names the first such section in file order, with a count
|
|
138
|
+
* of the rest. */
|
|
139
|
+
export function strayPlaceholderFinding(file, head, base) {
|
|
140
|
+
const was = placeholderSections(base);
|
|
141
|
+
const stray = [...placeholderSections(head)].filter(([title]) => !was.has(title));
|
|
142
|
+
if (stray.length === 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
const [title, n] = stray[0];
|
|
145
|
+
return (`${file}'s "## ${title}" section holds ${n} entr${n === 1 ? "y" : "ies"}, but this change left the ` +
|
|
146
|
+
`empty-section placeholder ${PLACEHOLDER_LINE} in it — delete that line: it marks a section with no ` +
|
|
147
|
+
`entries${moreSuffix(stray.length - 1)}`);
|
|
148
|
+
}
|
|
149
|
+
/** The staging finding for a run that holds a claim but moved a different backlog entry
|
|
150
|
+
* (plans/parallel-work-instances.md "Claims", part 4/7): the assigned entry is the instance's
|
|
151
|
+
* one task, so moving another is the same class of mistake `backlogStructureReason` catches
|
|
152
|
+
* and costs one fix-up turn before the change commits. undefined when nothing moved or the
|
|
153
|
+
* claimed entry itself moved — a merged entry is the expected way to release a claim. */
|
|
154
|
+
export function assignedMovedFinding(claimKey, moved) {
|
|
155
|
+
if (moved.length === 0 || moved.some((m) => m.key === claimKey))
|
|
156
|
+
return undefined;
|
|
157
|
+
return `this run is assigned one backlog entry but moved a different one: "${moved[0].title}"`;
|
|
158
|
+
}
|
|
159
|
+
/** The staging finding for an unassigned run that moved a backlog entry a sibling instance
|
|
160
|
+
* holds — one the poll listed in the run's `<held-entries>` note (RunnerState.heldElsewhere).
|
|
161
|
+
* The note is advice the model can ignore; this is the deterministic backstop at the same
|
|
162
|
+
* point as assignedMovedFinding (BUGS.md 2026-10-12: 24 of 29 staged claims duplicated a
|
|
163
|
+
* sibling's held entry, and each duplicate cost a gate check, a review, and a conflict
|
|
164
|
+
* resolver run holding the merge slot). The fix-up turn is asked to drop the duplicate work.
|
|
165
|
+
* A change that held nothing else then ends with no change to merge. undefined when no moved
|
|
166
|
+
* entry is held. */
|
|
167
|
+
export function heldMovedFinding(heldKeys, moved) {
|
|
168
|
+
const dup = moved.find((m) => heldKeys.has(m.key));
|
|
169
|
+
if (dup === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
return (`this run moved a backlog entry another instance of this role is working on: "${dup.title}" — it is that ` +
|
|
172
|
+
`instance's task (see <held-entries>), so revert every edit made for it, including the ` +
|
|
173
|
+
`entry's move, and leave nothing behind if that was the whole change`);
|
|
174
|
+
}
|
|
175
|
+
/** The reason clause of a Refused note — everything after its leading `Refused … by <role>:` —
|
|
176
|
+
* or the whole note when it has no such prefix. This is what the refusal rule lets a run write
|
|
177
|
+
* freely, and what the ungrounded-refusal finding judges. */
|
|
178
|
+
function refusalReason(note) {
|
|
179
|
+
const i = note.indexOf(": ");
|
|
180
|
+
return i < 0 ? note : note.slice(i + 2);
|
|
181
|
+
}
|
|
182
|
+
/** True when `reason` grounds the refusal in this entry itself — it names no series part other
|
|
183
|
+
* than the entry's own (`own`), and cites neither another entry's Refused or Needs note nor a
|
|
184
|
+
* role's own notes. Any of those means the refusal rests on another entry's hold or on the
|
|
185
|
+
* role's opinion, not on an objection to this entry, so it should skip the entry instead. A
|
|
186
|
+
* reason that names a part outside its own series, or any part at all for an entry with no
|
|
187
|
+
* part (`own === null`), is a different-series reference — the same class as naming 7/9 from a
|
|
188
|
+
* 6/9 refusal. */
|
|
189
|
+
function groundedRefusalReason(reason, own) {
|
|
190
|
+
if (/\brefus(?:al|ed|ing)?\s+of\b/i.test(reason))
|
|
191
|
+
return false; // cites another entry's refusal
|
|
192
|
+
if (/\bNeeds-[A-Za-z]/.test(reason))
|
|
193
|
+
return false; // cites another entry's Needs note
|
|
194
|
+
if (/\b(?:roles?['’]?s?|my|our|own)\s+notes?\b/i.test(reason))
|
|
195
|
+
return false; // rests on a role's own notes
|
|
196
|
+
// A part ref: an optional leading series name before `part N/M`, comma- or space-separated
|
|
197
|
+
// (own series when none is named, or when a space-separated word is not a title-cased series
|
|
198
|
+
// name — bridge words like `until part 7/9` resolve to the entry's own series).
|
|
199
|
+
const re = /(?:([A-Za-z][A-Za-z0-9 ._-]*?)([,\s]\s*))?part[s]?\s+([0-9]+[a-z]?)\/(\d+)\b/gi;
|
|
200
|
+
let m;
|
|
201
|
+
while ((m = re.exec(reason)) !== null) {
|
|
202
|
+
const token = (m[3] ?? "").toLowerCase();
|
|
203
|
+
if (own === null)
|
|
204
|
+
return false;
|
|
205
|
+
let series = own.series;
|
|
206
|
+
if (m[1] !== undefined) {
|
|
207
|
+
const word = m[1].trim();
|
|
208
|
+
// An explicit comma names the series whatever its case; a space-separated word is a
|
|
209
|
+
// series only when it is the entry's own (any case) or title-cased, so a bridge word
|
|
210
|
+
// like `until` or `behind` still resolves to the entry's own series.
|
|
211
|
+
const comma = (m[2] ?? "").includes(",");
|
|
212
|
+
if (comma || word.toLowerCase() === own.series.toLowerCase() || /^[A-Z]/.test(word))
|
|
213
|
+
series = word;
|
|
214
|
+
}
|
|
215
|
+
if (partKey(series, token) !== partKey(own.series, own.part))
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
/** The finding for a Refused note the change adds to `file` (PLANS.md or BUGS.md) whose reason
|
|
221
|
+
* grounds the refusal outside the entry — it quotes another entry's Refused or Needs note, the
|
|
222
|
+
* role's own notes, or a different series part (the `b9664a461` case). `head` is the edited
|
|
223
|
+
* text, `base` its text at the merge-base. A refusal already on main is not this change's and
|
|
224
|
+
* is skipped; only a newly added (or edited) one is judged. Names the first offending entry in
|
|
225
|
+
* file order, with a count of the rest. Stages after `waitingNoteFinding`, sharing its guard.
|
|
226
|
+
* Undefined when the change adds no such refusal. */
|
|
227
|
+
export function ungroundedRefusalFinding(file, head, base) {
|
|
228
|
+
const refusalByKey = (md) => {
|
|
229
|
+
const out = new Map();
|
|
230
|
+
for (const sectionLine of fenceAwareHeadingLines(md, "## ")) {
|
|
231
|
+
const section = headingTitle(sectionLine, "## ");
|
|
232
|
+
for (const e of parseEntryDetails(md, section)) {
|
|
233
|
+
const note = refusalNote(e.body);
|
|
234
|
+
if (note !== null)
|
|
235
|
+
out.set(entryKey(e.title), { title: e.title, note, section });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
};
|
|
240
|
+
const was = refusalByKey(base);
|
|
241
|
+
const newlyAdded = [...refusalByKey(head)].filter(([key, e]) => was.get(key)?.note !== e.note && !groundedRefusalReason(refusalReason(e.note), seriesPart(e.title)));
|
|
242
|
+
if (newlyAdded.length === 0)
|
|
243
|
+
return undefined;
|
|
244
|
+
const first = newlyAdded[0][1];
|
|
245
|
+
return (`${file}'s "${first.section}" entry "${truncate(first.title, 160)}" carries a newly added Refused note ` +
|
|
246
|
+
`whose reason cites another entry's Refused/Needs note, a role's own notes, or a different series part ` +
|
|
247
|
+
`instead of an objection to this entry; implement this entry or pick another, but do not refuse it` +
|
|
248
|
+
moreSuffix(newlyAdded.length - 1));
|
|
249
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** The merge gate's deterministic checks, run BEFORE a changed run commits.
|
|
2
|
+
*
|
|
3
|
+
* The gate runs `backlogStructureReason` and `falseFixReason` at merge time, by which point
|
|
4
|
+
* the author's session is over: a finding costs a queue slot, a verification, and a rejection that
|
|
5
|
+
* discards the work (13 phantom-fix rejections between 2026-09-23 and 2026-10-02). Both checks
|
|
6
|
+
* read the worktree and the change's merge-base, so the same questions can be asked at staging
|
|
7
|
+
* time, while the authoring session can still be continued for one fix-up turn. This module
|
|
8
|
+
* runs them over the run's uncommitted edits; the gate still has the final say.
|
|
9
|
+
*
|
|
10
|
+
* Scope mirrors the gate exactly: `backlogStructureReason` runs on every changed run, while
|
|
11
|
+
* `falseFixReason` runs only for an exempt (doc-only) diff — the condition under which the gate
|
|
12
|
+
* consults it.
|
|
13
|
+
*
|
|
14
|
+
* The git-level checks described next live in stage-path-checks.ts (`gitLevelFindings`), which
|
|
15
|
+
* this module appends.
|
|
16
|
+
*
|
|
17
|
+
* Part 2/2 appends git-level, language-neutral findings (PLANS.md "Pre-queue self-check, part
|
|
18
|
+
* 2/2") for path drift, the organize role's main rejection cause: references left behind by a
|
|
19
|
+
* rename or delete, added lines naming paths that do not exist in the resulting tree, and text
|
|
20
|
+
* files whose final newline the change removed. These stage the change (`git add -A`) to read
|
|
21
|
+
* the index with git only, then restore the index to HEAD before returning: staging is a read
|
|
22
|
+
* aid for the checks, not a change to the run's state. A git hiccup yields no finding rather
|
|
23
|
+
* than failing the run. Unlike the backlog checks above, the gate does not run these: they are
|
|
24
|
+
* heuristics for what a reviewer tends to reject, so neither path check looks inside record
|
|
25
|
+
* prose (isRecordProse), where an old or not-yet-created path is correct as written.
|
|
26
|
+
*
|
|
27
|
+
* A fourth check, `staleNameFindings`, reports a compound identifier the change removed from a
|
|
28
|
+
* JS/TS file that a comment or doc still names (the compiler catches code, not prose). It
|
|
29
|
+
* exempts a name the change re-declares (a move), a name another file still declares, an
|
|
30
|
+
* occurrence inside a string literal (legacy-name tables and serialized keys keep old names on
|
|
31
|
+
* purpose), a line marked `retired-name-ok`, and record prose, where history names old things.
|
|
32
|
+
* A fifth, `detachedDocFindings`, reads the same `-U0` diff for a JSDoc block the change
|
|
33
|
+
* separated from its declaration: code inserted between the two, or a declaration deleted
|
|
34
|
+
* while its doc stayed. A sixth, `movedSourceFindings`, reads that diff for a file that still
|
|
35
|
+
* exists but lost lines found verbatim in another file, and lists the lines that still name the
|
|
36
|
+
* old file's path (outside record prose), which may describe code that now lives elsewhere.
|
|
37
|
+
*
|
|
38
|
+
* A seventh check, `garbledWordingFindings`, reports added lines (outside record prose) that carry
|
|
39
|
+
* the broken participles and doubled "run" wording a word-swap rename leaves (has/have/had … ran,
|
|
40
|
+
* a hyphen before ran, "run" repeated back to back or followed by "running", a possessive run
|
|
41
|
+
* followed by "run", "run" joined to "run" by and/or, including a phrase split across two added
|
|
42
|
+
* lines). It also reports any word doubled back to back in comment or doc prose (case-sensitive,
|
|
43
|
+
* not after `-`, `.` or `/`, "that that" and "had had" excepted). Like the path checks, the gate
|
|
44
|
+
* does not run it.
|
|
45
|
+
*
|
|
46
|
+
* The backlog-record findings named below (`planDependencyFinding`, `waitingNoteFinding`,
|
|
47
|
+
* `finishedDuplicateFindings`, `strayPlaceholderFinding`) live in stage-backlog-checks.ts, which
|
|
48
|
+
* also holds the claim-move findings run-stage.ts uses.
|
|
49
|
+
*
|
|
50
|
+
* `planDependencyFinding` is staging-only: it is not part of the review gate or the in-lock
|
|
51
|
+
* merge re-check, because `tumwater doctor`'s plan-dependency warning is the backstop for
|
|
52
|
+
* anything that gets past the fix-up turn. `waitingNoteFinding` is staging-only and bugfix-only
|
|
53
|
+
* for the same reason: it nudges a bugfix run that left a Progress note without the hold.
|
|
54
|
+
* Like `planDependencyFinding`, `finishedDuplicateFindings` runs only at staging. The gate does
|
|
55
|
+
* not run it, because the sweep that creates the second copy runs after it.
|
|
56
|
+
* `strayPlaceholderFinding` is staging-only too. It flags a `_None yet._` line the change left in a
|
|
57
|
+
* PLANS.md, BUGS.md or QUESTIONS.md section that still holds entries. `acceptanceGrepFindings`
|
|
58
|
+
* runs only at staging and runs the strict "matches nothing" greps of the plan entries the change
|
|
59
|
+
* finished. The gate does not run it. */
|
|
60
|
+
import path from "node:path";
|
|
61
|
+
import { changedFiles } from "../git/git-diff.js";
|
|
62
|
+
import { conflictMarkerReason } from "../merge/merge-git.js";
|
|
63
|
+
import { isExemptDiff } from "../review/exemptions.js";
|
|
64
|
+
import { backlogStructureReason } from "../backlog/backlog-structure.js";
|
|
65
|
+
import { protectedEditReason } from "../review/protected-edits.js";
|
|
66
|
+
import { secretLeakReason } from "../review/secret-leaks.js";
|
|
67
|
+
import { falseFixReason } from "../verdict/fix-claim.js";
|
|
68
|
+
import { changeBaseRev, fileContentAt } from "../git/git.js";
|
|
69
|
+
import { readTextOrNull } from "../files/files.js";
|
|
70
|
+
import { FINISHED_ARCHIVES, finishedArchiveOf, parseEntryDetails, } from "../backlog/backlog-md.js";
|
|
71
|
+
import { DONE_DATE_RE } from "../backlog/backlog.js";
|
|
72
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
73
|
+
import { acceptanceGrepFindings } from "./acceptance-greps.js";
|
|
74
|
+
import { PLACEHOLDER_FILES, finishedDuplicateFindings, finishedKey, planDependencyFinding, strayPlaceholderFinding, ungroundedRefusalFinding, waitingNoteFinding, } from "./stage-backlog-checks.js";
|
|
75
|
+
import { gitLevelFindings } from "./stage-path-checks.js";
|
|
76
|
+
/** Where finished plans are archived (plans/backlog-archive.md). */
|
|
77
|
+
const PLANS_ARCHIVE = finishedArchiveOf("PLANS.md").archive;
|
|
78
|
+
/** `file` as it stands at the change's merge-base with `mainBranch`, or undefined when the
|
|
79
|
+
* merge-base or the read fails. */
|
|
80
|
+
const baseFileText = (wt, mainBranch, file) => changeBaseRev(wt, mainBranch).then((rev) => fileContentAt(wt, rev, file)).catch(() => undefined);
|
|
81
|
+
/** Append the reason `check` resolves to, if any, to `findings`; a check that rejects adds
|
|
82
|
+
* nothing, as staging never fails on a best-effort check. */
|
|
83
|
+
const pushReason = async (findings, check) => {
|
|
84
|
+
const reason = await check.catch(() => undefined);
|
|
85
|
+
if (reason)
|
|
86
|
+
findings.push(reason);
|
|
87
|
+
};
|
|
88
|
+
/** The findings likely to get this uncommitted change rejected at merge, or an empty list when
|
|
89
|
+
* it is clean: the merge gate's own deterministic checks plus the staging-only heuristics (plan
|
|
90
|
+
* dependencies, the bugfix Waiting note, path drift). Checks the worktree at `wt` against
|
|
91
|
+
* `mainBranch`'s merge-base; `exemptPaths` are the config's review-exemption globs that scope
|
|
92
|
+
* the false-fix check.
|
|
93
|
+
*
|
|
94
|
+
* Never throws: a check that fails yields no finding, so a git hiccup at staging time defers to
|
|
95
|
+
* the gate instead of failing the run. */
|
|
96
|
+
export async function stageCheckFindings(wt, mainBranch, exemptPaths, role) {
|
|
97
|
+
const files = await changedFiles(wt).catch(() => null);
|
|
98
|
+
if (files === null || files.length === 0)
|
|
99
|
+
return [];
|
|
100
|
+
const findings = [];
|
|
101
|
+
await pushReason(findings, backlogStructureReason(wt, mainBranch, files));
|
|
102
|
+
if (files.includes("PLANS.md")) {
|
|
103
|
+
const head = readTextOrNull(path.join(wt, "PLANS.md")) ?? "";
|
|
104
|
+
const base = await baseFileText(wt, mainBranch, "PLANS.md");
|
|
105
|
+
// Finished plans are archived in PLANS-DONE.md (plans/backlog-archive.md): a requires clause
|
|
106
|
+
// naming a merged part resolves there, on both sides of the comparison alike.
|
|
107
|
+
const archived = parseEntryDetails(readTextOrNull(path.join(wt, PLANS_ARCHIVE)) ?? "", "Done").map((e) => e.title);
|
|
108
|
+
const dependency = base === undefined ? undefined : planDependencyFinding(head, base, archived);
|
|
109
|
+
if (dependency !== undefined)
|
|
110
|
+
findings.push(dependency);
|
|
111
|
+
const refusal = base === undefined ? undefined : ungroundedRefusalFinding("PLANS.md", head, base);
|
|
112
|
+
if (refusal !== undefined)
|
|
113
|
+
findings.push(refusal);
|
|
114
|
+
}
|
|
115
|
+
const baseRev = FINISHED_ARCHIVES.some((s) => files.includes(s.file) || files.includes(s.archive))
|
|
116
|
+
? await changeBaseRev(wt, mainBranch).catch(() => undefined)
|
|
117
|
+
: undefined;
|
|
118
|
+
for (const spec of FINISHED_ARCHIVES) {
|
|
119
|
+
if (baseRev === undefined || !(files.includes(spec.file) || files.includes(spec.archive)))
|
|
120
|
+
continue;
|
|
121
|
+
const duplicates = await (async () => {
|
|
122
|
+
const read = (f) => readTextOrNull(path.join(wt, f)) ?? "";
|
|
123
|
+
const at = async (f) => (await fileContentAt(wt, baseRev, f)) ?? "";
|
|
124
|
+
return finishedDuplicateFindings(spec, { head: read(spec.file), base: await at(spec.file) }, { head: read(spec.archive), base: await at(spec.archive) });
|
|
125
|
+
})().catch(() => []);
|
|
126
|
+
findings.push(...duplicates);
|
|
127
|
+
if (spec.file !== "PLANS.md")
|
|
128
|
+
continue;
|
|
129
|
+
const greps = await (async () => {
|
|
130
|
+
const finished = async (f, section) => {
|
|
131
|
+
const keyed = (md) => parseEntryDetails(md, section).map((e) => ({ ...e, key: finishedKey(e.title, DONE_DATE_RE) }));
|
|
132
|
+
const seen = new Set(keyed((await fileContentAt(wt, baseRev, f)) ?? "").map((e) => e.key));
|
|
133
|
+
return keyed(readTextOrNull(path.join(wt, f)) ?? "").filter((e) => !seen.has(e.key));
|
|
134
|
+
};
|
|
135
|
+
const entries = [...(await finished(spec.file, spec.section)), ...(await finished(spec.archive, spec.section))];
|
|
136
|
+
return acceptanceGrepFindings(wt, entries);
|
|
137
|
+
})().catch(() => []);
|
|
138
|
+
findings.push(...greps);
|
|
139
|
+
}
|
|
140
|
+
if (baseRoleOf(role) === "bugfix" && files.includes("BUGS.md")) {
|
|
141
|
+
const head = readTextOrNull(path.join(wt, "BUGS.md")) ?? "";
|
|
142
|
+
const base = await baseFileText(wt, mainBranch, "BUGS.md");
|
|
143
|
+
const waiting = base === undefined ? undefined : waitingNoteFinding(head, base);
|
|
144
|
+
if (waiting !== undefined)
|
|
145
|
+
findings.push(waiting);
|
|
146
|
+
}
|
|
147
|
+
if (files.includes("BUGS.md")) {
|
|
148
|
+
const head = readTextOrNull(path.join(wt, "BUGS.md")) ?? "";
|
|
149
|
+
const base = await baseFileText(wt, mainBranch, "BUGS.md");
|
|
150
|
+
const refusal = base === undefined ? undefined : ungroundedRefusalFinding("BUGS.md", head, base);
|
|
151
|
+
if (refusal !== undefined)
|
|
152
|
+
findings.push(refusal);
|
|
153
|
+
}
|
|
154
|
+
for (const file of PLACEHOLDER_FILES) {
|
|
155
|
+
if (!files.includes(file))
|
|
156
|
+
continue;
|
|
157
|
+
const head = readTextOrNull(path.join(wt, file)) ?? "";
|
|
158
|
+
const base = (await baseFileText(wt, mainBranch, file)) ?? "";
|
|
159
|
+
const stray = strayPlaceholderFinding(file, head, base);
|
|
160
|
+
if (stray !== undefined)
|
|
161
|
+
findings.push(stray);
|
|
162
|
+
}
|
|
163
|
+
await pushReason(findings, protectedEditReason(wt, mainBranch, files, role));
|
|
164
|
+
await pushReason(findings, secretLeakReason(wt, mainBranch, files));
|
|
165
|
+
if (isExemptDiff(files, exemptPaths))
|
|
166
|
+
await pushReason(findings, falseFixReason(wt, mainBranch, files));
|
|
167
|
+
findings.push(...(await gitLevelFindings(wt, mainBranch).catch(() => [])));
|
|
168
|
+
const markers = conflictMarkerReason(wt, files);
|
|
169
|
+
if (markers !== undefined)
|
|
170
|
+
findings.push(markers);
|
|
171
|
+
return findings;
|
|
172
|
+
}
|