tumwater 0.2.0 → 0.4.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 +55 -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 +6 -4
- 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 +404 -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 +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -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 +264 -0
- package/dist/src/backlog/backlog-release.js +134 -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-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -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 +65 -3
- 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 +181 -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 +183 -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 +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- 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 +16 -1
- package/dist/src/config/config-fallbacks.js +94 -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 +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- 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 +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- 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 +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- 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 +89 -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 +54 -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 +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- 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 +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- 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 +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -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 +28 -17
- 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 +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- 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 +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -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 +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -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 +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -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 +126 -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 +26 -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 +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -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 +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- 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 +180 -401
- 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 +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -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} +122 -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} +170 -83
- 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} +105 -57
- 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 +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -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} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- 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 +49 -17
- 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 +43 -28
- package/dist/src/status/status-roles.js +50 -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 +7 -8
- 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 +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- 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 +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -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 +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- 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 +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -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 +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -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 +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -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 +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- 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
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
|
-
/** The merge gate's deterministic checks, run BEFORE a changed tick commits.
|
|
3
|
-
*
|
|
4
|
-
* The gate runs `backlogStructureReason` and `falseFixReason` at merge time, by which point
|
|
5
|
-
* the author's session is over: a finding costs a queue slot, a verification, and a rejection that
|
|
6
|
-
* discards the work (13 phantom-fix rejections between 2026-09-23 and 2026-10-02). Both checks
|
|
7
|
-
* read the worktree and the change's merge-base, so the same questions can be asked at staging
|
|
8
|
-
* time, while the authoring session can still be continued for one fix-up turn. This module
|
|
9
|
-
* runs them over the tick's uncommitted edits; the gate still has the final say.
|
|
10
|
-
*
|
|
11
|
-
* Scope mirrors the gate exactly: `backlogStructureReason` runs on every changed tick, while
|
|
12
|
-
* `falseFixReason` runs only for an exempt (doc-only) diff — the condition under which the gate
|
|
13
|
-
* consults it.
|
|
14
|
-
*
|
|
15
|
-
* Part 2/2 appends git-level, language-neutral findings (PLANS.md "Pre-queue self-check, part
|
|
16
|
-
* 2/2") for path drift, the organize loop's main rejection cause: references left behind by a
|
|
17
|
-
* rename or delete, added lines naming paths that do not exist in the resulting tree, and text
|
|
18
|
-
* files whose final newline the change removed. These stage the change (`git add -A`) to read
|
|
19
|
-
* the index with git only, then restore the index to HEAD before returning: staging is a read
|
|
20
|
-
* aid for the checks, not a change to the tick's state. A git hiccup yields no finding rather
|
|
21
|
-
* than failing the tick. Unlike the backlog checks above, the gate does not run these: they are
|
|
22
|
-
* heuristics for what a reviewer tends to reject, so neither path check looks inside record
|
|
23
|
-
* prose (isRecordProse), where an old or not-yet-created path is correct as written.
|
|
24
|
-
*
|
|
25
|
-
* `planDependencyFinding` is staging-only: it is not part of the review gate or the in-lock
|
|
26
|
-
* merge re-check, because `tumwater doctor`'s plan-dependency warning is the backstop for
|
|
27
|
-
* anything that gets past the fix-up turn. `waitingNoteFinding` is staging-only and bugfix-only
|
|
28
|
-
* for the same reason: it nudges a bugfix run that left a Progress note without the hold. */
|
|
29
|
-
import path from "node:path";
|
|
30
|
-
import { changedFiles } from "../git/git-diff.js";
|
|
31
|
-
import { conflictMarkerReason } from "../merge/merge-git.js";
|
|
32
|
-
import { isExemptDiff } from "../review/exemptions.js";
|
|
33
|
-
import { backlogStructureReason } from "../backlog/backlog-structure.js";
|
|
34
|
-
import { protectedEditReason } from "../review/protected-edits.js";
|
|
35
|
-
import { secretLeakReason } from "../review/secret-leaks.js";
|
|
36
|
-
import { falseFixReason } from "../verdict/fix-claim.js";
|
|
37
|
-
import { changeBaseRev, fileContentAt } from "../git/git.js";
|
|
38
|
-
import { readTextOrNull } from "../files/files.js";
|
|
39
|
-
import { BACKLOG_FILES, finishedArchiveOf, parseEntryDetails } from "../backlog/backlog-md.js";
|
|
40
|
-
import { entryKey, planDependencyProblems, refusalNote, waitingNote } from "../backlog/backlog-eligibility.js";
|
|
41
|
-
import { baseRoleOf } from "../roles/loop-ids.js";
|
|
42
|
-
import { WAITING_FOR_FAILURE_NOTE } from "../roles/role-guidance.js";
|
|
43
|
-
import { gitTry } from "../git/git-run.js";
|
|
44
|
-
import { moreSuffix } from "../text/phrases.js";
|
|
45
|
-
/** Where finished plans are archived (plans/backlog-archive.md). */
|
|
46
|
-
const PLANS_ARCHIVE = finishedArchiveOf("PLANS.md").archive;
|
|
47
|
-
/** How many `file:line` hits a single stale-reference finding lists before it stops. */
|
|
48
|
-
const MAX_STALE_HITS = 10;
|
|
49
|
-
/** How many renamed/deleted paths each stale-reference finding reports before stopping — the
|
|
50
|
-
* cap that keeps a 40-file rename to a bounded finding list. */
|
|
51
|
-
const MAX_STALE_PATHS = 10;
|
|
52
|
-
/** How many nonexistent added paths one finding names before it stops. */
|
|
53
|
-
const MAX_MISSING_PATHS = 10;
|
|
54
|
-
/** How many files one lost-final-newline finding names before it stops. */
|
|
55
|
-
const MAX_NEWLINE_FILES = 10;
|
|
56
|
-
/** The path a unified diff's `+++ b/<path>` header names. Strips the four-character `+++ `
|
|
57
|
-
* marker and git's `b/` prefix. A deleted file's new side is `+++ /dev/null`, which carries no
|
|
58
|
-
* `b/` prefix and is returned as `/dev/null`. */
|
|
59
|
-
function diffPlusPath(line) {
|
|
60
|
-
return line.slice(4).replace(/^b\//, "");
|
|
61
|
-
}
|
|
62
|
-
/** True for record prose: a backlog file (BACKLOG_FILES: PLANS.md, BUGS.md, QUESTIONS.md and the
|
|
63
|
-
* finished-entry archives) or a markdown design doc under `plans/`. They record history and
|
|
64
|
-
* describe planned work, so a path there is correct as written even when it is absent from the
|
|
65
|
-
* tree: a finished entry names a file as it was, and a plan names the file it will create or
|
|
66
|
-
* both sides of the move it describes. Asking a rename to sweep them rewrote "old → new" into
|
|
67
|
-
* "new → new" and history into the present; that failure took five rejections on
|
|
68
|
-
* one rename on 2026-10-18 and -19. */
|
|
69
|
-
function isRecordProse(file) {
|
|
70
|
-
const base = file.slice(file.lastIndexOf("/") + 1);
|
|
71
|
-
return BACKLOG_FILES.has(base) || (file.startsWith("plans/") && file.endsWith(".md"));
|
|
72
|
-
}
|
|
73
|
-
/** The capped list a finding line appends: at most `max` entries joined by ", ", then
|
|
74
|
-
* `moreSuffix` for the rest. Shared by the missing-path and lost-final-newline findings so both
|
|
75
|
-
* cap and join identically. */
|
|
76
|
-
function cappedList(items, max) {
|
|
77
|
-
const shown = items.slice(0, max);
|
|
78
|
-
return `${shown.join(", ")}${moreSuffix(items.length - shown.length)}`;
|
|
79
|
-
}
|
|
80
|
-
/** The finding for the heading `requires` clause problems that `head` (the edited PLANS.md)
|
|
81
|
-
* adds over `base` (its text at the merge-base), or undefined when it adds none; `archivedTitles`
|
|
82
|
-
* (PLANS-DONE.md's headings) count as existing headings on both sides. Only new
|
|
83
|
-
* problems count: one already on main is not this run's fault and stays doctor's to report, so
|
|
84
|
-
* the author is never asked to fix another run's heading. The most serious new problem is named
|
|
85
|
-
* — cycle, then dangling, unparsed, then misworded — with a count of the rest. */
|
|
86
|
-
export function planDependencyFinding(head, base, archivedTitles = []) {
|
|
87
|
-
const problems = (text) => planDependencyProblems(parseEntryDetails(text, "Planned"), [...parseEntryDetails(text, "Done").map((e) => e.title), ...archivedTitles]);
|
|
88
|
-
const was = problems(base);
|
|
89
|
-
const now = problems(head);
|
|
90
|
-
const newCycle = now.cycle !== null && was.cycle === null ? now.cycle : null;
|
|
91
|
-
const newDangling = now.dangling.filter((d) => !was.dangling.some((b) => b.title === d.title && b.ref === d.ref));
|
|
92
|
-
const newUnparsed = now.unparsed.filter((t) => !was.unparsed.includes(t));
|
|
93
|
-
const newMisworded = now.misworded.filter((m) => !was.misworded.some((b) => b.title === m.title && b.ref === m.ref));
|
|
94
|
-
const total = (newCycle !== null ? 1 : 0) + newDangling.length + newUnparsed.length + newMisworded.length;
|
|
95
|
-
if (total === 0)
|
|
96
|
-
return undefined;
|
|
97
|
-
let first;
|
|
98
|
-
if (newCycle !== null) {
|
|
99
|
-
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
|
|
100
|
-
"edit one heading's `requires … landed` clause to break it"; // vocabulary-ok: PLANS.md heading syntax
|
|
101
|
-
}
|
|
102
|
-
else if (newDangling.length > 0) {
|
|
103
|
-
const d = newDangling[0];
|
|
104
|
-
first = `PLANS.md plan "${d.title}" requires ${d.ref}, which no PLANS.md heading carries — the claim allocator ` +
|
|
105
|
-
"ignores the clause and hands the plan out now; fix the series name or part number to match the prerequisite's heading";
|
|
106
|
-
}
|
|
107
|
-
else if (newUnparsed.length > 0) {
|
|
108
|
-
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
|
|
109
|
-
"it holds nothing; write each ref as `<Series> i/n` or `part i/n` after a `<Series>, part i/n:` heading prefix";
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const w = newMisworded[0];
|
|
113
|
-
first = `PLANS.md plan "${w.title}" names ${w.ref} as a prerequisite without a \`requires … landed\` clause — ` + // vocabulary-ok: PLANS.md heading syntax
|
|
114
|
-
"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
|
|
115
|
-
}
|
|
116
|
-
return `${first}${moreSuffix(total - 1)}`;
|
|
117
|
-
}
|
|
118
|
-
/** The number of `- **Progress …` lines in an entry body. */
|
|
119
|
-
function progressCount(body) {
|
|
120
|
-
return body.split("\n").filter((l) => /^-\s+\*\*Progress\b/.test(l.trim())).length;
|
|
121
|
-
}
|
|
122
|
-
/** The finding for a BUGS.md bug that `head` (the edited file) shows with more Progress notes
|
|
123
|
-
* than `base` (its text at the merge-base) while it stays under `## Open` with neither a Waiting
|
|
124
|
-
* note nor a Refused note — bugfix's step 2 asks a run that cannot reproduce a bug to write the
|
|
125
|
-
* Waiting note, and runs that skip it leave the bug open to re-investigation. A newly filed bug
|
|
126
|
-
* is not a re-investigation. Names the first such bug in file order, with a count of the rest. */
|
|
127
|
-
export function waitingNoteFinding(head, base) {
|
|
128
|
-
const was = new Map(parseEntryDetails(base, "Open").map((e) => [entryKey(e.title), e.body]));
|
|
129
|
-
const counting = parseEntryDetails(head, "Open").filter((e) => {
|
|
130
|
-
const before = was.get(entryKey(e.title));
|
|
131
|
-
return (before !== undefined &&
|
|
132
|
-
progressCount(e.body) > progressCount(before) &&
|
|
133
|
-
waitingNote(e.body) === null &&
|
|
134
|
-
refusalNote(e.body) === null);
|
|
135
|
-
});
|
|
136
|
-
if (counting.length === 0)
|
|
137
|
-
return undefined;
|
|
138
|
-
return (`BUGS.md bug "${counting[0].title}" gained a Progress note but stays open with no Waiting for the next ` +
|
|
139
|
-
`failure note — if this run could not reproduce it, add ${WAITING_FOR_FAILURE_NOTE} as its own line under ` +
|
|
140
|
-
`the heading so later runs wait for new evidence (bugfix's step 2); if it did reproduce, leave the entry ` +
|
|
141
|
-
`as it is${moreSuffix(counting.length - 1)}`);
|
|
142
|
-
}
|
|
143
|
-
/** The findings likely to get this uncommitted change rejected at merge, or an empty list when
|
|
144
|
-
* it is clean: the merge gate's own deterministic checks plus the staging-only heuristics (plan
|
|
145
|
-
* dependencies, the bugfix Waiting note, path drift). Checks the worktree at `wt` against
|
|
146
|
-
* `mainBranch`'s merge-base; `exemptPaths` are the config's review-exemption globs that scope
|
|
147
|
-
* the false-fix check.
|
|
148
|
-
*
|
|
149
|
-
* Never throws: a check that fails yields no finding, so a git hiccup at staging time defers to
|
|
150
|
-
* the gate instead of failing the tick. */
|
|
151
|
-
export async function stageCheckFindings(wt, mainBranch, exemptPaths, role) {
|
|
152
|
-
const files = await changedFiles(wt).catch(() => null);
|
|
153
|
-
if (files === null || files.length === 0)
|
|
154
|
-
return [];
|
|
155
|
-
const findings = [];
|
|
156
|
-
const structure = await backlogStructureReason(wt, mainBranch, files).catch(() => undefined);
|
|
157
|
-
if (structure)
|
|
158
|
-
findings.push(structure);
|
|
159
|
-
if (files.includes("PLANS.md")) {
|
|
160
|
-
const head = readTextOrNull(path.join(wt, "PLANS.md")) ?? "";
|
|
161
|
-
const base = await fileContentAt(wt, await changeBaseRev(wt, mainBranch), "PLANS.md").catch(() => undefined);
|
|
162
|
-
// Finished plans are archived in PLANS-DONE.md (plans/backlog-archive.md): a requires clause
|
|
163
|
-
// naming a merged part resolves there, on both sides of the comparison alike.
|
|
164
|
-
const archived = parseEntryDetails(readTextOrNull(path.join(wt, PLANS_ARCHIVE)) ?? "", "Done").map((e) => e.title);
|
|
165
|
-
const dependency = base === undefined ? undefined : planDependencyFinding(head, base, archived);
|
|
166
|
-
if (dependency !== undefined)
|
|
167
|
-
findings.push(dependency);
|
|
168
|
-
}
|
|
169
|
-
if (baseRoleOf(role) === "bugfix" && files.includes("BUGS.md")) {
|
|
170
|
-
const head = readTextOrNull(path.join(wt, "BUGS.md")) ?? "";
|
|
171
|
-
const base = await fileContentAt(wt, await changeBaseRev(wt, mainBranch), "BUGS.md").catch(() => undefined);
|
|
172
|
-
const waiting = base === undefined ? undefined : waitingNoteFinding(head, base);
|
|
173
|
-
if (waiting !== undefined)
|
|
174
|
-
findings.push(waiting);
|
|
175
|
-
}
|
|
176
|
-
const protectedReason = await protectedEditReason(wt, mainBranch, files, role).catch(() => undefined);
|
|
177
|
-
if (protectedReason)
|
|
178
|
-
findings.push(protectedReason);
|
|
179
|
-
const secretReason = await secretLeakReason(wt, mainBranch, files).catch(() => undefined);
|
|
180
|
-
if (secretReason)
|
|
181
|
-
findings.push(secretReason);
|
|
182
|
-
if (isExemptDiff(files, exemptPaths)) {
|
|
183
|
-
const falseFix = await falseFixReason(wt, mainBranch, files).catch(() => undefined);
|
|
184
|
-
if (falseFix)
|
|
185
|
-
findings.push(falseFix);
|
|
186
|
-
}
|
|
187
|
-
findings.push(...(await gitLevelFindings(wt, mainBranch).catch(() => [])));
|
|
188
|
-
const markers = conflictMarkerReason(wt, files);
|
|
189
|
-
if (markers !== undefined)
|
|
190
|
-
findings.push(markers);
|
|
191
|
-
return findings;
|
|
192
|
-
}
|
|
193
|
-
/** The staging finding for a tick that holds a claim but moved a different backlog entry
|
|
194
|
-
* (plans/parallel-work-instances.md "Claims", part 4/7): the assigned entry is the instance's
|
|
195
|
-
* one task, so moving another is the same class of mistake `backlogStructureReason` catches
|
|
196
|
-
* and costs one fix-up turn before the change commits. undefined when nothing moved or the
|
|
197
|
-
* claimed entry itself moved — a merged entry is the expected way to release a claim. */
|
|
198
|
-
export function assignedMovedFinding(claimKey, moved) {
|
|
199
|
-
if (moved.length === 0 || moved.some((m) => m.key === claimKey))
|
|
200
|
-
return undefined;
|
|
201
|
-
return `this run is assigned one backlog entry but moved a different one: "${moved[0].title}"`;
|
|
202
|
-
}
|
|
203
|
-
/** The staging finding for an unassigned tick that moved a backlog entry a sibling instance
|
|
204
|
-
* holds — one the poll listed in the tick's `<held-entries>` note (LoopState.heldElsewhere).
|
|
205
|
-
* The note is advice the model can ignore; this is the deterministic backstop at the same
|
|
206
|
-
* point as assignedMovedFinding (BUGS.md 2026-10-12: 24 of 29 staged claims duplicated a
|
|
207
|
-
* sibling's held entry, and each duplicate cost a gate check, a review, and a conflict
|
|
208
|
-
* resolver run holding the merge slot). The fix-up turn is asked to drop the duplicate work.
|
|
209
|
-
* A change that held nothing else then ends with no change to merge. undefined when no moved
|
|
210
|
-
* entry is held. */
|
|
211
|
-
export function heldMovedFinding(heldKeys, moved) {
|
|
212
|
-
const dup = moved.find((m) => heldKeys.has(m.key));
|
|
213
|
-
if (dup === undefined)
|
|
214
|
-
return undefined;
|
|
215
|
-
return (`this run moved a backlog entry another instance of this role is working on: "${dup.title}" — it is that ` +
|
|
216
|
-
`instance's task (see <held-entries>), so revert every edit made for it, including the ` +
|
|
217
|
-
`entry's move, and leave nothing behind if that was the whole change`);
|
|
218
|
-
}
|
|
219
|
-
/** Parse `git diff --cached -M --name-status` output: `M\tpath`, `D\tpath`, or
|
|
220
|
-
* `R100\told\tnew`. Malformed lines are skipped. */
|
|
221
|
-
function parseNameStatus(out) {
|
|
222
|
-
const entries = [];
|
|
223
|
-
for (const line of (out ?? "").split("\n")) {
|
|
224
|
-
if (!line)
|
|
225
|
-
continue;
|
|
226
|
-
const parts = line.split("\t");
|
|
227
|
-
const status = (parts[0] ?? "").charAt(0);
|
|
228
|
-
if (!status)
|
|
229
|
-
continue;
|
|
230
|
-
if (status === "R" || status === "C") {
|
|
231
|
-
const oldPath = parts[1];
|
|
232
|
-
const path = parts[2];
|
|
233
|
-
if (oldPath && path)
|
|
234
|
-
entries.push({ status, path, oldPath });
|
|
235
|
-
}
|
|
236
|
-
else if (parts[1]) {
|
|
237
|
-
entries.push({ status, path: parts[1] });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return entries;
|
|
241
|
-
}
|
|
242
|
-
/** Stage the change and run the three git-level path checks over the resulting index, then
|
|
243
|
-
* restore the index to HEAD so the staging is invisible to the tick's own change detection.
|
|
244
|
-
* Each check is best-effort: a failed git command contributes nothing. */
|
|
245
|
-
async function gitLevelFindings(wt, mainBranch) {
|
|
246
|
-
await gitTry(wt, "add", "-A");
|
|
247
|
-
try {
|
|
248
|
-
const base = await changeBaseRev(wt, mainBranch);
|
|
249
|
-
const entries = parseNameStatus(await gitTry(wt, "diff", "--cached", "-M", "--name-status", base));
|
|
250
|
-
const findings = await staleReferenceFindings(wt, entries);
|
|
251
|
-
const diff = (await gitTry(wt, "diff", "--cached", "-U0", base)) ?? "";
|
|
252
|
-
findings.push(...(await missingPathFindings(wt, diff, entries)));
|
|
253
|
-
findings.push(...newlineFindings(diff));
|
|
254
|
-
return findings;
|
|
255
|
-
}
|
|
256
|
-
finally {
|
|
257
|
-
// Restore the index to HEAD. Without this, the tick's change detection (changedFiles and
|
|
258
|
-
// stageTickMerge's "nothing left to merge" guard) reads the stale index and reports a
|
|
259
|
-
// fix-up that reverted the whole change as still changed, so an empty tree would reach
|
|
260
|
-
// commitAll instead of the tick ending no_change.
|
|
261
|
-
await gitTry(wt, "reset");
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
/** (a) References left behind by a rename or delete: for each `D` path and each `R` origin,
|
|
265
|
-
* `git grep --cached` the old path across the staged tree and report up to MAX_STALE_HITS
|
|
266
|
-
* `file:line` hits outside record prose (isRecordProse). Renames report the path they moved to;
|
|
267
|
-
* deletes report the deletion. */
|
|
268
|
-
async function staleReferenceFindings(wt, entries) {
|
|
269
|
-
const findings = [];
|
|
270
|
-
let reported = 0;
|
|
271
|
-
for (const e of entries) {
|
|
272
|
-
if (e.status !== "D" && e.status !== "R")
|
|
273
|
-
continue;
|
|
274
|
-
if (reported >= MAX_STALE_PATHS)
|
|
275
|
-
break;
|
|
276
|
-
const old = e.oldPath ?? e.path;
|
|
277
|
-
// `git grep` exits 1 with no output when nothing matches — the common, clean case — and
|
|
278
|
-
// gitTry maps that to null exactly as it maps a real failure, both of which mean "no hits".
|
|
279
|
-
const out = await gitTry(wt, "grep", "-n", "-F", "-I", "--cached", "-e", old);
|
|
280
|
-
if (!out)
|
|
281
|
-
continue;
|
|
282
|
-
const hits = [];
|
|
283
|
-
for (const line of out.split("\n")) {
|
|
284
|
-
const m = /^([^:]+):(\d+):(.*)$/.exec(line);
|
|
285
|
-
if (!m || isRecordProse(m[1]) || !hasStandalonePathOccurrence(m[3] ?? "", old))
|
|
286
|
-
continue;
|
|
287
|
-
hits.push(`${m[1]}:${m[2]}`);
|
|
288
|
-
if (hits.length >= MAX_STALE_HITS)
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
if (hits.length === 0)
|
|
292
|
-
continue;
|
|
293
|
-
const moved = e.status === "R" ? `was renamed to ${e.path}` : "was deleted";
|
|
294
|
-
findings.push(`${old} ${moved} but is still named at: ${hits.join(", ")}`);
|
|
295
|
-
reported++;
|
|
296
|
-
}
|
|
297
|
-
return findings;
|
|
298
|
-
}
|
|
299
|
-
/** True when `needle` appears in `line` as a repo path of its own — not as one segment of a
|
|
300
|
-
* longer path. `git grep -F` matches substrings, so a line naming a rename's destination
|
|
301
|
-
* (`src/config.ts`) would otherwise read as a stale reference to an old root `config.ts`.
|
|
302
|
-
* A preceding path character (or a following one that continues the token, as a `.` extension
|
|
303
|
-
* or an alphanumeric does) disqualifies the occurrence; trailing sentence punctuation does not. */
|
|
304
|
-
function hasStandalonePathOccurrence(line, needle) {
|
|
305
|
-
const pathChar = /[A-Za-z0-9_./-]/;
|
|
306
|
-
for (let i = line.indexOf(needle); i !== -1; i = line.indexOf(needle, i + 1)) {
|
|
307
|
-
const before = i > 0 ? line[i - 1] : "";
|
|
308
|
-
if (before && pathChar.test(before))
|
|
309
|
-
continue;
|
|
310
|
-
const after = line[i + needle.length] ?? "";
|
|
311
|
-
if (/[A-Za-z0-9_/-]/.test(after))
|
|
312
|
-
continue;
|
|
313
|
-
if (after === "." && /[A-Za-z0-9]/.test(line[i + needle.length + 1] ?? ""))
|
|
314
|
-
continue;
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
/** Each distinct path-shaped token in `text`, in first-appearance order: at least one slash and
|
|
320
|
-
* a final `.<ext>` segment. Scanned inside the text (not whitespace-split) so surrounding
|
|
321
|
-
* backticks, brackets, and sentence punctuation fall away with the match. */
|
|
322
|
-
export function pathTokens(text) {
|
|
323
|
-
const tokenRe = /[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+\.[A-Za-z0-9]+/g;
|
|
324
|
-
return [...new Set([...text.matchAll(tokenRe)].map((m) => m[0]))];
|
|
325
|
-
}
|
|
326
|
-
/** (b) Nonexistent paths: every path-shaped token on an added diff line whose first segment is
|
|
327
|
-
* a tracked top-level directory but which is absent from the staged tree. Rename origins and
|
|
328
|
-
* deleted paths are excluded — "moved from X" prose names them legitimately. */
|
|
329
|
-
async function missingPathFindings(wt, diff, entries) {
|
|
330
|
-
const ls = await gitTry(wt, "ls-files");
|
|
331
|
-
const staged = new Set();
|
|
332
|
-
const topDirs = new Set();
|
|
333
|
-
for (const p of (ls ?? "").split("\n")) {
|
|
334
|
-
if (!p)
|
|
335
|
-
continue;
|
|
336
|
-
staged.add(p);
|
|
337
|
-
const slash = p.indexOf("/");
|
|
338
|
-
if (slash > 0)
|
|
339
|
-
topDirs.add(p.slice(0, slash));
|
|
340
|
-
}
|
|
341
|
-
const oldPaths = new Set();
|
|
342
|
-
for (const e of entries) {
|
|
343
|
-
if (e.oldPath)
|
|
344
|
-
oldPaths.add(e.oldPath);
|
|
345
|
-
if (e.status === "D")
|
|
346
|
-
oldPaths.add(e.path);
|
|
347
|
-
}
|
|
348
|
-
const seen = new Set();
|
|
349
|
-
const missing = [];
|
|
350
|
-
let file = null;
|
|
351
|
-
for (const line of diff.split("\n")) {
|
|
352
|
-
if (line.startsWith("+++ ")) {
|
|
353
|
-
file = diffPlusPath(line);
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (!line.startsWith("+") || line.startsWith("+++"))
|
|
357
|
-
continue;
|
|
358
|
-
// A test file's fixtures routinely name paths from the software under test, which need
|
|
359
|
-
// not exist here, and record prose names the paths a plan proposes to create; the
|
|
360
|
-
// added-nonexistent-path heuristic targets real references, so it spares added lines in
|
|
361
|
-
// test/ and in record prose (isRecordProse).
|
|
362
|
-
if (file !== null && (file.startsWith("test/") || isRecordProse(file)))
|
|
363
|
-
continue;
|
|
364
|
-
for (const p of pathTokens(line.slice(1))) {
|
|
365
|
-
const top = p.slice(0, p.indexOf("/"));
|
|
366
|
-
if (!topDirs.has(top) || oldPaths.has(p) || staged.has(p) || seen.has(p))
|
|
367
|
-
continue;
|
|
368
|
-
seen.add(p);
|
|
369
|
-
missing.push(p);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (missing.length === 0)
|
|
373
|
-
return [];
|
|
374
|
-
return [
|
|
375
|
-
`added lines name paths that do not exist in the tree: ${cappedList(missing, MAX_MISSING_PATHS)}`,
|
|
376
|
-
];
|
|
377
|
-
}
|
|
378
|
-
/** (c) Lost final newlines: a text file whose new diff side carries git's
|
|
379
|
-
* `` marker while its base side does not. Git emits the marker
|
|
380
|
-
* immediately after the line it applies to, so a marker after a `+` line flags the new side and
|
|
381
|
-
* one after a `-` line flags the base side; a new file (`+` only) counts. Binary files have no
|
|
382
|
-
* line hunks and never appear. */
|
|
383
|
-
function newlineFindings(diff) {
|
|
384
|
-
const files = [];
|
|
385
|
-
let path = null;
|
|
386
|
-
let newLacks = false;
|
|
387
|
-
let oldLacks = false;
|
|
388
|
-
let prev = "";
|
|
389
|
-
const flush = () => {
|
|
390
|
-
if (path !== null && newLacks && !oldLacks)
|
|
391
|
-
files.push(path);
|
|
392
|
-
};
|
|
393
|
-
for (const line of diff.split("\n")) {
|
|
394
|
-
if (line.startsWith("diff --git ")) {
|
|
395
|
-
flush();
|
|
396
|
-
path = null;
|
|
397
|
-
newLacks = false;
|
|
398
|
-
oldLacks = false;
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (line.startsWith("+++ ")) {
|
|
402
|
-
path = diffPlusPath(line);
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
if (line.startsWith("\")) {
|
|
406
|
-
if (prev.startsWith("+"))
|
|
407
|
-
newLacks = true;
|
|
408
|
-
else if (prev.startsWith("-"))
|
|
409
|
-
oldLacks = true;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
if (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))
|
|
413
|
-
prev = line;
|
|
414
|
-
}
|
|
415
|
-
flush();
|
|
416
|
-
if (files.length === 0)
|
|
417
|
-
return [];
|
|
418
|
-
return [`the change removes the final newline from: ${cappedList(files, MAX_NEWLINE_FILES)}`];
|
|
419
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** The `telemetry` role's tick-time evidence: the failure digest rendered over the role's own
|
|
2
|
-
* trailing 24 h window. Split out of src/failure/failure-render.ts — which stays the pure
|
|
3
|
-
* renderer — so every observer role's evidence builder has its own module the way
|
|
4
|
-
* qa-coverage.ts and backlog-structure.ts do, and the tick lifecycle (tick-prompt.ts) injects
|
|
5
|
-
* it from there instead of reaching into a report module. */
|
|
6
|
-
import { collectFailureReportSince } from "../failure/failure-data.js";
|
|
7
|
-
import { renderFailureMarkdown } from "../failure/failure-render.js";
|
|
8
|
-
/** The `telemetry` role's own digest window: the trailing 24 h ending now, not the local
|
|
9
|
-
* calendar day. A calendar day starts at midnight, so a run at 06:32 would never see the
|
|
10
|
-
* previous evening. The CLI keeps the usage report's 14-day default; the role reads 24 h so a
|
|
11
|
-
* cluster re-surfaces only while it is live. The collector's equal-length prior window is what
|
|
12
|
-
* makes the delta line meaningful. */
|
|
13
|
-
export const TELEMETRY_DIGEST_MS = 24 * 60 * 60 * 1000;
|
|
14
|
-
/** The `telemetry` role's tick-time evidence: the failure digest rendered over its own
|
|
15
|
-
* trailing 24 h window. A missing or corrupt log omits the block (undefined) and never fails the
|
|
16
|
-
* tick — an observer must not break on bookkeeping (plans/telemetry-role.md). The window and
|
|
17
|
-
* the swallow-errors policy live with the digest, not in the tick lifecycle that injects it. */
|
|
18
|
-
export function telemetryDigest(root) {
|
|
19
|
-
try {
|
|
20
|
-
return renderFailureMarkdown(collectFailureReportSince(root, TELEMETRY_DIGEST_MS));
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
|
-
/** Collection half of `tumwater tick <role> <n>`: one completed tick's full event block,
|
|
3
|
-
* distilled from the event log into a TickDetail payload. The collector-in-core convention
|
|
4
|
-
* beside history-data.ts and src/report/report-data.ts — a pure read over root, so core
|
|
5
|
-
* modules can serve the same payload without reaching into ui/, and the CLI
|
|
6
|
-
* (`tick-detail.ts`) and the GUI History drill-down (`src/gui/gui-endpoints.ts`) cannot
|
|
7
|
-
* drift on what one tick's block contains.
|
|
8
|
-
* Every datum rides the events the harness already writes, so this is a distillation of the
|
|
9
|
-
* existing record, not a new one. The rendering half — the summary header, formatEvent, the
|
|
10
|
-
* CLI command — lives in tick-detail.ts. */
|
|
11
|
-
import { HISTORY_SCAN_MAX_EVENTS, bucketMergeEvents, resolveQueuedResult } from "../history/history-data.js";
|
|
12
|
-
import { eventUsage, readEvents, tickSpanMs, tickStartMap } from "../events/event-read.js";
|
|
13
|
-
import { outcomeText, usageText } from "../events/event-format.js";
|
|
14
|
-
/** The event block of `role`'s tick `n`, or null when the scanned window holds no such tick.
|
|
15
|
-
* The newest `run_start` the log retains for `${role}#${n}` wins (a counter reset can make an
|
|
16
|
-
* old tick number recur; the operator asking by number means the most recent one), and the
|
|
17
|
-
* block closes at the first `run_end` for that tick after the start — or, when that end is
|
|
18
|
-
* missing (in flight, or its tail lost to rotation), at the loop's next `run_start`, so a
|
|
19
|
-
* rotation-cut block never leaks the following tick's events into this one. When even the
|
|
20
|
-
* start is gone but a matching `run_end` survives, the end-alone block still answers with
|
|
21
|
-
* `startTs` null and a null duration (the unpaired rule); nothing at all in the scan is the
|
|
22
|
-
* not-found case the caller renders as a not-found line. */
|
|
23
|
-
export function readTickDetail(root, role, tick) {
|
|
24
|
-
// The scan rides history-data.ts's shared one-ask ceiling (HISTORY_SCAN_MAX_EVENTS): one
|
|
25
|
-
// tick's block is small, but it can sit arbitrarily far back in a long-lived team's log,
|
|
26
|
-
// and readTailText reads bytes proportional to the limit's line count — so the cap is also
|
|
27
|
-
// the largest byte read one command can cost, and every read-only view scans under the
|
|
28
|
-
// same bound.
|
|
29
|
-
const events = readEvents(root, HISTORY_SCAN_MAX_EVENTS).filter((e) => e.source === role);
|
|
30
|
-
// Newest run_start for this loop+tick: the block's lower bound.
|
|
31
|
-
let startIdx = -1;
|
|
32
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
33
|
-
const e = events[i];
|
|
34
|
-
if (e.type === "run_start" && e.run === tick) {
|
|
35
|
-
startIdx = i;
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (startIdx < 0) {
|
|
40
|
-
// The start is gone (rotation) or the tick never ran. A surviving run_end for the same
|
|
41
|
-
// number still answers — unpaired, undated at the front — else there is no such tick here.
|
|
42
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
43
|
-
const e = events[i];
|
|
44
|
-
if (e.type === "run_end" && e.run === tick)
|
|
45
|
-
return detailFrom(role, tick, events, [e], e, null);
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
const start = events[startIdx];
|
|
50
|
-
// The block's upper bound: the first run_end for this tick after the start ends it; a
|
|
51
|
-
// later run_start (this tick's end lost to rotation) closes the block exclusive — the
|
|
52
|
-
// events after it belong to the next tick, not this one. No end and no next start reads as
|
|
53
|
-
// in flight: everything since the start belongs to the open tick.
|
|
54
|
-
let end = null;
|
|
55
|
-
let nextStart = null;
|
|
56
|
-
for (let i = startIdx + 1; i < events.length; i++) {
|
|
57
|
-
const e = events[i];
|
|
58
|
-
if (e.type === "run_end" && e.run === tick) {
|
|
59
|
-
end = e;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
if (e.type === "run_start") {
|
|
63
|
-
nextStart = e;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// The block itself: the start event plus everything this loop logged up to the bound —
|
|
68
|
-
// inclusive at a paired end, exclusive at a next tick's start.
|
|
69
|
-
const block = [];
|
|
70
|
-
for (let i = startIdx; i < events.length; i++) {
|
|
71
|
-
const e = events[i];
|
|
72
|
-
if (end !== null ? e.ts > end.ts : nextStart !== null && e.ts >= nextStart.ts)
|
|
73
|
-
break;
|
|
74
|
-
block.push(e);
|
|
75
|
-
}
|
|
76
|
-
return detailFrom(role, tick, events, block, end, start.ts);
|
|
77
|
-
}
|
|
78
|
-
/** Fill the payload around a collected block: usage from the run_end via eventUsage/usageText,
|
|
79
|
-
* duration via tickSpanMs against the loop's own start map (the shared pairing helper, so this
|
|
80
|
-
* view and the history rows cannot drift on what a span is), events oldest-first. */
|
|
81
|
-
function detailFrom(role, tick, scoped, block, end, startTs) {
|
|
82
|
-
const usageNums = end === null ? { tokens: 0, costUsd: 0 } : eventUsage(end);
|
|
83
|
-
let result = end === null ? null : outcomeText(end);
|
|
84
|
-
let merge = null;
|
|
85
|
-
if (end !== null && result === "queued") {
|
|
86
|
-
// A fresh claim cursor suits one tick: the walk starts at the newest pin at or before this
|
|
87
|
-
// tick's end, which is its own pin, since later ticks' pins sit after it.
|
|
88
|
-
const { mergeQueuedByLoop, outcomeByLoop } = bucketMergeEvents(scoped);
|
|
89
|
-
const resolved = resolveQueuedResult(end, mergeQueuedByLoop, outcomeByLoop, new Map(), tickStartMap(scoped), null);
|
|
90
|
-
if (resolved !== null) {
|
|
91
|
-
result = resolved.result;
|
|
92
|
-
merge = resolved.outcome;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
role,
|
|
97
|
-
run: tick,
|
|
98
|
-
startTs,
|
|
99
|
-
endTs: end === null ? null : end.ts,
|
|
100
|
-
durationMs: end === null ? null : tickSpanMs(end, tickStartMap(scoped)),
|
|
101
|
-
result,
|
|
102
|
-
tokens: usageNums.tokens,
|
|
103
|
-
costUsd: usageNums.costUsd,
|
|
104
|
-
usage: end === null ? "" : usageText(end),
|
|
105
|
-
events: block,
|
|
106
|
-
merge,
|
|
107
|
-
};
|
|
108
|
-
}
|