tumwater 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { shortSha } from "../text/format.js";
|
|
2
|
-
import { TEST_RUNNER_RULE, dateLine } from "./prompt.js";
|
|
3
|
+
import { KILL_BY_PID_RULE, OLDER_NAMES_SENTENCE, TEST_RUNNER_RULE, dateLine } from "./prompt.js";
|
|
3
4
|
import { NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE } from "../roles/role-guidance.js";
|
|
4
5
|
import { describeCheck } from "../build/build-check-report.js";
|
|
5
6
|
import { formatTimestamp } from "../text/datetime.js";
|
|
6
7
|
import { numberedList } from "../text/markdown.js";
|
|
7
8
|
import { SUMMARY_BLOCK, VERDICT_ENDING, NOTHING_TO_DO } from "../verdict/reply-contract.js";
|
|
8
|
-
/** Prompts for the
|
|
9
|
+
/** Prompts for the merge gate's pi runs — the runs the merge/review pipeline starts, not the
|
|
9
10
|
* role loops' authoring ticks (those live in prompt.ts): conflict resolution after a rebase
|
|
10
|
-
* (
|
|
11
|
+
* (merge-to-main.ts), the adversarial pre-merge review (review.ts), and the notes that carry the
|
|
11
12
|
* gate's verdicts back to the loop that authored the change (loop-tick.ts, tick-prompt.ts,
|
|
12
13
|
* main-red.ts). The machine-detectable half of the gate's reply contract — verdict constants
|
|
13
14
|
* and detection for parsing a reviewer's VERDICT line — lives in reply-contract.ts. */
|
|
@@ -37,38 +38,67 @@ function conflictIntentBlocks(intent) {
|
|
|
37
38
|
lines.push(`(${intent.mainOmitted} more not shown)`);
|
|
38
39
|
return lines.join("\n");
|
|
39
40
|
}
|
|
41
|
+
/** One conflicted file's bullet: its path, then its conflict hunks' line ranges when known
|
|
42
|
+
* (merge-git.ts's conflictedFileHunks). A plain path, or a file with no hunks found, is the bare
|
|
43
|
+
* bullet. */
|
|
44
|
+
function conflictedFileLine(file) {
|
|
45
|
+
if (typeof file === "string")
|
|
46
|
+
return `- ${file}`;
|
|
47
|
+
if (file.hunks.length === 0)
|
|
48
|
+
return `- ${file.path}`;
|
|
49
|
+
const ranges = file.hunks.map((h) => `${h.start}-${h.end}`).join(", ");
|
|
50
|
+
const more = file.more > 0 ? `, and ${file.more} more` : "";
|
|
51
|
+
return `- ${file.path} (conflict hunks at lines ${ranges}${more})`;
|
|
52
|
+
}
|
|
53
|
+
/** True when any conflicted file carries hunk ranges, so the prompt can point at them. */
|
|
54
|
+
function listsHunks(files) {
|
|
55
|
+
return files.some((f) => typeof f !== "string" && f.hunks.length > 0);
|
|
56
|
+
}
|
|
40
57
|
/** The prompt for resolving merge conflicts left in a loop's worktree. `check` — the project's
|
|
41
58
|
* resolved check, as the tick prompt threads it — is named so the resolver knows which command
|
|
42
59
|
* the harness will run (told only to keep the tests passing, the 2026-10-04 resolver guessed
|
|
43
60
|
* `npx vitest run` in a node:test repo, BUGS.md 2026-10-05), and the rules tell it not to run
|
|
44
|
-
* that check itself:
|
|
45
|
-
* anything
|
|
46
|
-
* every queued
|
|
61
|
+
* that check itself: merge-to-main.ts's verifyMerge runs it on the resolved tree before
|
|
62
|
+
* anything merges, and the resolver runs inside the merge slot, so its own copy of the run held
|
|
63
|
+
* every queued merge. On 2026-10-12 the resolvers' own suite runs were 86% of their wall time
|
|
47
64
|
* (951 of 1,104 s over seven runs; one spent 6 min re-testing a tree identical to main).
|
|
48
65
|
* Undefined keeps the generic sentence and adds no such rule: with no declared check the
|
|
49
66
|
* harness verifies nothing. `intent` — this branch's own
|
|
50
67
|
* commit message and the main commits that touched the conflicted files since the merge-base —
|
|
51
68
|
* shows the resolver what both sides meant instead of only the markers; omitted, the prompt is
|
|
52
|
-
* unchanged.
|
|
53
|
-
*
|
|
69
|
+
* unchanged. A file given with its conflict hunks' line ranges (conflictedFileHunks) is listed
|
|
70
|
+
* with them, and the rules then send the resolver to those ranges: told only to grep the
|
|
71
|
+
* markers, the 2026-10-17..19 resolvers still opened the conflicted PLANS.md at line 1, one
|
|
72
|
+
* tool-output-capped read after another, while every queued merge waited. A plain path lists
|
|
73
|
+
* the file alone. `today` pins the date line (prompt.ts's dateLine) for tests; omitted, it is
|
|
74
|
+
* the local day. */
|
|
54
75
|
export function buildConflictPrompt(roleId, files, check, intent, today) {
|
|
55
76
|
const noCheckRunRule = check
|
|
56
77
|
? `
|
|
57
|
-
- Do not run ${describeCheck(check)} (the project's declared check) or the full test suite:
|
|
58
|
-
|
|
78
|
+
- Do not run ${describeCheck(check)} (the project's declared check) or the full test suite:
|
|
79
|
+
tumwater runs that check on the resolved tree before it merges, and every queued merge waits
|
|
59
80
|
on this run. If you combined code from both sides, a compile or the one or two test files
|
|
60
81
|
that cover it is enough.`
|
|
61
82
|
: "";
|
|
62
83
|
const intentBlocks = intent ? conflictIntentBlocks(intent) : "";
|
|
63
84
|
const removalCitation = intent ? ` (see "What main changed in these files" above)` : "";
|
|
64
|
-
|
|
85
|
+
const readRule = listsHunks(files)
|
|
86
|
+
? `- Read only the conflicted files and what they directly reference, not the codebase at large.
|
|
87
|
+
Read each conflict hunk listed above as its line range plus about five lines of context on
|
|
88
|
+
each side; never read a conflicted file from the top, where one read of a long-lined file
|
|
89
|
+
fills the tool-output limit. Editing a hunk shifts the line numbers below it, so resolve the
|
|
90
|
+
last hunk first, or re-run \`grep -n '<<<<<<<' FILE\`, which also finds hunks past the listed
|
|
91
|
+
ones.`
|
|
92
|
+
: `- Read only the conflicted files and what they directly reference (\`grep -n '<<<<<<<' FILE\`
|
|
93
|
+
finds each marker; read around it in ranges) — not the codebase at large.`;
|
|
94
|
+
return `You are the "${roleId}" role on this project's tumwater team — tumwater runs a team of AI roles that develop the project. ${OLDER_NAMES_SENTENCE} A rebase of
|
|
65
95
|
your work branch onto main stopped on conflicts; the conflict markers are sitting in the
|
|
66
96
|
worktree now. Resolve them.
|
|
67
97
|
|
|
68
98
|
${dateLine(today)}
|
|
69
99
|
|
|
70
100
|
Conflicted files:
|
|
71
|
-
${files.map(
|
|
101
|
+
${files.map(conflictedFileLine).join("\n")}
|
|
72
102
|
${intentBlocks ? intentBlocks + "\n" : ""}
|
|
73
103
|
Resolve every conflict marker (<<<<<<<, =======, >>>>>>>) by combining the intent of BOTH sides:
|
|
74
104
|
"ours" is this branch's change, "theirs" is the latest main. Do not simply pick one side unless
|
|
@@ -76,20 +106,20 @@ the two changes are genuinely alternatives. Keep the project building and its te
|
|
|
76
106
|
|
|
77
107
|
Rules for this run:
|
|
78
108
|
- Edit files only. Never run any git command that changes state (no add, commit, merge, rebase,
|
|
79
|
-
reset, checkout) —
|
|
80
|
-
|
|
81
|
-
finds each marker; read around it in ranges) — not the codebase at large.
|
|
109
|
+
reset, checkout) — tumwater concludes the rebase for you. Reading git state is fine.
|
|
110
|
+
${readRule}
|
|
82
111
|
- When main has deliberately removed or replaced what the branch edits (a revert, a rewrite of
|
|
83
112
|
the same code), the branch's edit is dropped: resolve toward main's version — the branch's
|
|
84
113
|
change is re-derived on top of current main by its author if it still matters. Never merge
|
|
85
114
|
the branch's version back in over main's deliberate removal${removalCitation}.
|
|
86
115
|
- Never touch the .tumwater directory or tumwater.json.
|
|
87
116
|
${TEST_RUNNER_RULE}${noCheckRunRule}
|
|
117
|
+
- ${KILL_BY_PID_RULE}
|
|
88
118
|
- When a conflicted file is a markdown backlog file (PLANS.md, BUGS.md, QUESTIONS.md), its
|
|
89
119
|
\`## \` section headings are structure, not text: the result keeps exactly one of each
|
|
90
120
|
\`## \` heading, and every \`### \` entry sits under the section its own side put it in —
|
|
91
121
|
a newly planned feature stays under \`## Planned\` even when the other side moved entries
|
|
92
|
-
around it.
|
|
122
|
+
around it. \`grep -n '^## ' FILE\` lists those headings with their line numbers.
|
|
93
123
|
- When every marker is resolved and the project is consistent, just stop.`;
|
|
94
124
|
}
|
|
95
125
|
/** The prompt for the adversarial pre-merge review gate: a fresh-session pi run that sees only the
|
|
@@ -99,29 +129,29 @@ ${TEST_RUNNER_RULE}${noCheckRunRule}
|
|
|
99
129
|
* green on this exact tree (e.g. "`npm run test` passed"), so the reviewer spends its run on what a
|
|
100
130
|
* green suite cannot show instead of re-running it. The no-re-run instruction is its own line in
|
|
101
131
|
* the rules list, not a clause in the context paragraph: stated there, reviewers read past it and
|
|
102
|
-
* re-ran the suite in scratch copies under /tmp while holding the
|
|
132
|
+
* re-ran the suite in scratch copies under /tmp while holding the merge slot (BUGS.md
|
|
103
133
|
* 2026-09-23). It appears only when a verified result exists: after a timed-out, killed, or skipped
|
|
104
134
|
* pre-check no green run stands behind the tree, and a reviewer running the suite itself is doing
|
|
105
135
|
* its job. `baseRev` is the frozen revision the diff is measured against (changeBaseRev's
|
|
106
136
|
* merge-base, resolved when the gate builds the diff): the prompt names it and tells the reviewer
|
|
107
|
-
* to compare against it, never `main`, because other loops
|
|
137
|
+
* to compare against it, never `main`, because other loops merge to main while the review runs
|
|
108
138
|
* (BUGS.md 2026-10-06). Omitted, the prompt keeps the generic "ahead of main" wording. `today` pins
|
|
109
139
|
* the date line (prompt.ts's dateLine) for tests; omitted, it is the local day. The text must
|
|
110
140
|
* contain the literal "VERDICT:" exactly twice — the two advertised forms — because a prompt
|
|
111
|
-
* test derives the accepted forms from it. `priorReview` is present only when this
|
|
141
|
+
* test derives the accepted forms from it. `priorReview` is present only when this merge is a
|
|
112
142
|
* revision of a previously rejected change (plans/revise-rejected.md part 2/2): it names the round
|
|
113
143
|
* and shows the prior objections plus the interdiff so the re-review checks each one first.
|
|
114
144
|
* Omitted, the prompt is unchanged from a fresh change's. */
|
|
115
145
|
export function buildReviewPrompt(diff, summary, commitBody, principles, highFriction, verifiedByHarness, baseRev, today, priorReview) {
|
|
116
146
|
const parts = [
|
|
117
|
-
`You are an adversarial code reviewer for tumwater,
|
|
118
|
-
|
|
147
|
+
`You are an adversarial code reviewer for tumwater, which runs a team of AI roles on this project. ${OLDER_NAMES_SENTENCE} A
|
|
148
|
+
role's change is about to be merged to main; you decide whether it may merge. You have no context
|
|
119
149
|
from the authoring run — judge only what is in front of you, and assume nothing until you have
|
|
120
150
|
checked it.`,
|
|
121
151
|
dateLine(today),
|
|
122
152
|
];
|
|
123
153
|
if (highFriction)
|
|
124
|
-
parts.push(`This change was flagged HIGH-FRICTION by
|
|
154
|
+
parts.push(`This change was flagged HIGH-FRICTION by tumwater: its authoring run burned far more
|
|
125
155
|
assistant turns or wall-clock time than this project's thresholds. Difficulty is a signal that
|
|
126
156
|
the work may not fit the system — apply extra scrutiny to whether the change should exist at all,
|
|
127
157
|
not just whether it is correct.`);
|
|
@@ -135,24 +165,27 @@ not just whether it is correct.`);
|
|
|
135
165
|
of the prior review's numbered objections FIRST — an unresolved one is a rejection on its own —
|
|
136
166
|
then review the whole diff as usual. List only the still-unresolved objections as your numbered
|
|
137
167
|
reasons; state that a prior objection is resolved in prose, never as a numbered reason, so the
|
|
138
|
-
recorded rejection is not read as the resolution itself.
|
|
168
|
+
recorded rejection is not read as the resolution itself. When a numbered reason restates one of
|
|
169
|
+
the prior objections, start it with the tag (prior N) where N is that objection's number in the
|
|
170
|
+
prior list, and leave a newly raised objection untagged — the tag is how tumwater tells a repeat
|
|
171
|
+
from a new finding. The prior objections were:\n${list}\nThe interdiff
|
|
139
172
|
between the rejected version and this revision (what the revision changed, main's own movement
|
|
140
173
|
excluded):\n<interdiff>\n${priorReview.interdiff}\n</interdiff>`);
|
|
141
174
|
}
|
|
142
175
|
if (verifiedByHarness)
|
|
143
|
-
parts.push(`
|
|
176
|
+
parts.push(`Tumwater already ran the project's own check on this exact tree and it passed:
|
|
144
177
|
${verifiedByHarness}. Spend your run on what a green check cannot show: wrong behavior the tests
|
|
145
178
|
never exercise, claims the diff does not back, work left half-done.`);
|
|
146
179
|
if (principles) {
|
|
147
180
|
parts.push(`Design principles this project holds — your review standard; a violation of one is a finding:\n<principles>\n${principles}\n</principles>`);
|
|
148
181
|
}
|
|
149
|
-
// The base the diff is measured from, named in the prompt: main moves while a
|
|
182
|
+
// The base the diff is measured from, named in the prompt: main moves while a merge is approved,
|
|
150
183
|
// and a reviewer that double-checks scope with `git diff main` reads a newer merge, reversed, as
|
|
151
184
|
// part of the change (BUGS.md 2026-10-06). Sentence only when a base is given; the callers that
|
|
152
185
|
// omit it (unit tests of the prompt's other clauses) keep the generic wording.
|
|
153
186
|
const diffBase = baseRev ? `measured against ${shortSha(baseRev)}` : "everything the branch is ahead of main";
|
|
154
187
|
const baseClause = baseRev
|
|
155
|
-
? ` It is measured against ${shortSha(baseRev)}; other
|
|
188
|
+
? ` It is measured against ${shortSha(baseRev)}; other roles merge on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`, and treat the provided diff as authoritative for which files this change touches.`
|
|
156
189
|
: "";
|
|
157
190
|
const truncationBase = baseRev ? shortSha(baseRev) : "main";
|
|
158
191
|
parts.push(`The full diff this merge will land (${diffBase}):\n<diff>\n${diff}\n</diff>`);
|
|
@@ -160,7 +193,7 @@ never exercise, claims the diff does not back, work left half-done.`);
|
|
|
160
193
|
// something, but a copy made to run the suite is the re-run this line forbids.
|
|
161
194
|
const noRerunRule = verifiedByHarness
|
|
162
195
|
? `
|
|
163
|
-
- Do not re-run the check named above or the project's full test suite:
|
|
196
|
+
- Do not re-run the check named above or the project's full test suite: tumwater's green run
|
|
164
197
|
is the verified result. Copying the tree elsewhere to run it — rsync or cp into a temp
|
|
165
198
|
directory, reinstalling dependencies there (for an npm project, \`npm ci\`; otherwise its
|
|
166
199
|
equivalent) — counts as re-running it. Running one
|
|
@@ -186,7 +219,7 @@ Check, in this order:
|
|
|
186
219
|
1. Does the diff do exactly what the summary and WHY claim — no more, no less? An unclaimed
|
|
187
220
|
change is a finding.
|
|
188
221
|
2. Are the VERIFIED claims consistent with the diff (commands, files, observations)? A claim you
|
|
189
|
-
can disprove is a rejection. Suite counts are
|
|
222
|
+
can disprove is a rejection. Suite counts are tumwater's own attestation — they appear in
|
|
190
223
|
the passed-check line above when the check ran green — so a count missing from VERIFIED is
|
|
191
224
|
not a finding, and neither is an author-stated count that differs from it: judge whether the
|
|
192
225
|
tests exist and exercise the change (check 3), not the author's arithmetic.
|
|
@@ -208,6 +241,7 @@ Rules for this run:
|
|
|
208
241
|
verdict below. Reading files and git history is fine; a scratch copy under the system temp
|
|
209
242
|
directory is fine when you need to run something.${noRerunRule}
|
|
210
243
|
${TEST_RUNNER_RULE}
|
|
244
|
+
- ${KILL_BY_PID_RULE}
|
|
211
245
|
- Weigh the change against its stated purpose; do not approve work you did not actually check.
|
|
212
246
|
- End your reply with exactly one line in this form:
|
|
213
247
|
${VERDICT_ENDING.replace(/\n/g, "\n ")}`);
|
|
@@ -226,7 +260,7 @@ export function buildReviewResumePrompt(baseRev, patchId) {
|
|
|
226
260
|
|
|
227
261
|
You are resuming an adversarial review of a tumwater change: the previous attempt hit its time budget while still making progress, and this run continues that same reviewer session. Everything you already read — the diff and the files you checked — is in your context; do not re-read it.
|
|
228
262
|
|
|
229
|
-
The change is unchanged since that attempt (patch-id ${patchId}) and is still measured against ${shortSha(baseRev)}. Other
|
|
263
|
+
The change is unchanged since that attempt (patch-id ${patchId}) and is still measured against ${shortSha(baseRev)}. Other roles merge on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`.
|
|
230
264
|
|
|
231
265
|
Finish the checks you had not completed, then end your reply with exactly one line in this form:
|
|
232
266
|
${VERDICT_ENDING.replace(/\n/g, "\n ")}`;
|
|
@@ -239,7 +273,7 @@ Finish the checks you had not completed, then end your reply with exactly one li
|
|
|
239
273
|
* too yields nothing (BUGS.md 2026-09-29). It shares the review prompt's closing rule
|
|
240
274
|
* (reply-contract.ts's VERDICT_ENDING), so the accepted forms cannot drift between the prompts. */
|
|
241
275
|
export function buildVerdictRequestPrompt() {
|
|
242
|
-
return `Your review reply above did not include the verdict line
|
|
276
|
+
return `Your review reply above did not include the verdict line tumwater parses, so
|
|
243
277
|
your judgment was lost. Reply now with ONLY the closing block — no tool calls, no other text:
|
|
244
278
|
one line in this form:
|
|
245
279
|
${VERDICT_ENDING}`;
|
|
@@ -249,7 +283,7 @@ one line in this form:
|
|
|
249
283
|
* asks it to finish the review from what it already read. The reviewer's only output channel
|
|
250
284
|
* is the verdict, so the closing rule names both accepted forms like the review prompt does. */
|
|
251
285
|
export function buildNoRerunPrompt(rerun) {
|
|
252
|
-
return `You started a tool call
|
|
286
|
+
return `You started a tool call tumwater's pre-check already covered, so it must not run:
|
|
253
287
|
${rerun}
|
|
254
288
|
The full check passed at this exact tree before your review began. Do not re-run it — not in
|
|
255
289
|
this worktree, not in a scratch copy. Finish the review now from what you have already read:
|
|
@@ -257,20 +291,23 @@ reply with ONLY the closing block — no tool calls, no other text — one line
|
|
|
257
291
|
${VERDICT_ENDING}`;
|
|
258
292
|
}
|
|
259
293
|
/** The one-turn follow-up sent into a tick's OWN session (--continue) when the harness's
|
|
260
|
-
* pre-queue self-check found
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
294
|
+
* pre-queue self-check found problems likely to get the change rejected. Only some findings are
|
|
295
|
+
* the merge gate's own deterministic checks; the rest (path drift, plan dependencies) are
|
|
296
|
+
* heuristics, so the prompt never claims the gate rejects every one — claiming it did made an
|
|
297
|
+
* author sweep a rename through the backlog's history (2026-10-18). The session still holds
|
|
298
|
+
* everything the run did, so the author fixes the faults before the change commits and queues —
|
|
299
|
+
* the gate keeps its final say, and the fix costs one bounded turn instead of a rejection cycle.
|
|
300
|
+
* A finding the author believes is wrong is answered in RISK rather than forced away; the reply
|
|
301
|
+
* re-states the closing block because the harness re-derives the commit message from it.
|
|
302
|
+
* Carries the tick prompt's no-git rule (the harness commits). */
|
|
266
303
|
export function buildStageFixPrompt(findings) {
|
|
267
|
-
return `Your change is about to commit, but
|
|
268
|
-
|
|
304
|
+
return `Your change is about to commit, but tumwater's pre-queue self-check found problems the
|
|
305
|
+
merge gate or its review is likely to reject:
|
|
269
306
|
${numberedList(findings)}
|
|
270
307
|
Fix each one in the worktree now with the smallest edit that resolves it, and keep everything else
|
|
271
308
|
as it is. When a finding is wrong, say why in your RISK line instead of forcing a change that
|
|
272
309
|
does not help. Do not run any git command that changes state (no add, commit, reset, checkout) —
|
|
273
|
-
|
|
310
|
+
tumwater commits for you.
|
|
274
311
|
When done, reply with ONLY the closing block again — no tool calls, no other text, one line each:
|
|
275
312
|
${SUMMARY_BLOCK}`;
|
|
276
313
|
}
|
|
@@ -295,15 +332,15 @@ export function buildRejectedReviewNote(review, role) {
|
|
|
295
332
|
// notebook instead: it records the abandoned purpose and any instances that remain
|
|
296
333
|
// unhandled, then takes a different theme on the next tick. Re-authoring the same purpose
|
|
297
334
|
// from the same objections is what restarted the round and produced the long rejection
|
|
298
|
-
// runs this note exists to end. The markdown-only replan note
|
|
299
|
-
//
|
|
300
|
-
// the instruction does not re-inject once the note has
|
|
335
|
+
// runs this note exists to end. The markdown-only replan note merges review-exempt, but a
|
|
336
|
+
// merged change clears the standing rejection (clearSupersededRejection, tick-apply.ts), so
|
|
337
|
+
// the instruction does not re-inject once the note has merged.
|
|
301
338
|
let finality = "";
|
|
302
339
|
if (review.exhausted) {
|
|
303
340
|
finality =
|
|
304
341
|
role === "feature"
|
|
305
|
-
? ` This was the change's final revision round — the rejected diff and ref are gone. If it implemented a PLANS.md entry, do not re-author it: append ${NEEDS_REPLAN_NOTE} under that entry's heading, with the numbered objections above quoted beneath it, and
|
|
306
|
-
: " This was the change's final revision round — the rejected diff and ref are gone
|
|
342
|
+
? ` This was the change's final revision round — the same objection survived two revisions (or the chain hit its cap), so the rejected diff and ref are gone. If it implemented a PLANS.md entry, do not re-author it: append ${NEEDS_REPLAN_NOTE} under that entry's heading, with the numbered objections above quoted beneath it, and merge that markdown-only change and nothing else so the plan role can rewrite the plan. Otherwise, redo the change against current main.`
|
|
343
|
+
: " This was the change's final revision round — the same objection survived two revisions (or the chain hit its cap), so the rejected diff and ref are gone. Do not re-author it. Record the abandoned purpose and any instances of it that remain unhandled in your notes, then take a different theme on this run: re-deriving the same purpose from the same objections is what produced the long rejection runs this note exists to end.";
|
|
307
344
|
}
|
|
308
345
|
return `Your previous change was rejected in review${context}:\n${list}\nAddress the objections or take a different approach.${finality}`;
|
|
309
346
|
}
|
|
@@ -311,12 +348,13 @@ export function buildRejectedReviewNote(review, role) {
|
|
|
311
348
|
* current main as uncommitted edits (plans/revise-rejected.md, loop/revision.ts's
|
|
312
349
|
* applyRevision): this tick's one task is to revise the change already in the worktree, not
|
|
313
350
|
* author a new one. The prior objections are numbered so the author can work through them; a
|
|
314
|
-
* revision round past the first also reaches here.
|
|
315
|
-
*
|
|
316
|
-
|
|
351
|
+
* revision round past the first also reaches here. The chain keeps revising while each round
|
|
352
|
+
* fixes the prior objection; it ends only on a repeated objection or the backstop (BUGS.md
|
|
353
|
+
* 2026-10-14). A pure function so its shape is pinned in tests. */
|
|
354
|
+
export function buildRevisionNote(review, round) {
|
|
317
355
|
const list = numberedList(review.reasons, NO_REASONS);
|
|
318
356
|
return `Your previously rejected change is already in the worktree as uncommitted edits — this
|
|
319
|
-
is revision ${round}
|
|
357
|
+
is revision ${round}. This run's ONE task is to revise it; do not author anything else.
|
|
320
358
|
The review objections were:\n${list}
|
|
321
359
|
Fix each numbered objection with the smallest edit that resolves it, and keep everything else as it
|
|
322
360
|
is. When an objection is about a build-check failure in a test the change does not touch that you
|
|
@@ -324,22 +362,27 @@ cannot reproduce, say so in RISK and keep the change as it is. Rewrite the closi
|
|
|
324
362
|
describes the WHOLE change as it now stands:
|
|
325
363
|
${SUMMARY_BLOCK}
|
|
326
364
|
When an objection shows the change should not exist (its premise is disproven, it duplicates main,
|
|
327
|
-
or it has no reachable benefit), end your reply with the line ${NOTHING_TO_DO} instead, and
|
|
328
|
-
|
|
365
|
+
or it has no reachable benefit), end your reply with the line ${NOTHING_TO_DO} instead, and
|
|
366
|
+
tumwater drops the change.`;
|
|
329
367
|
}
|
|
330
368
|
/** The note appended to a work instance's tick prompt when it holds a claim (plans/parallel-
|
|
331
369
|
* work-instances.md "Claims", part 4/7): the harness has already picked its entry, so the
|
|
332
370
|
* charter's choosing step is replaced. A pure function so its shape is pinned in tests; the
|
|
333
371
|
* range is optional because a claim whose entry was already released still gets the note. */
|
|
334
|
-
export function buildAssignmentNote(claim, range) {
|
|
372
|
+
export function buildAssignmentNote(claim, range, missingPaths = []) {
|
|
335
373
|
const where = range ? ` (${claim.file} lines ${range.start}-${range.end})` : ` (${claim.file})`;
|
|
336
374
|
const intro = claim.source === "staged"
|
|
337
375
|
? `Your change moved ONE backlog entry, so it is now your task, replacing your charter's "pick one" step:`
|
|
338
|
-
: `
|
|
376
|
+
: `Tumwater has assigned you ONE backlog entry, replacing your charter's "pick one" step:`;
|
|
377
|
+
const shown = missingPaths.slice(0, 10).map((p) => `\`${p}\``).join(", ");
|
|
378
|
+
const rest = missingPaths.length - 10;
|
|
379
|
+
const missingLine = missingPaths.length === 0
|
|
380
|
+
? ""
|
|
381
|
+
: `\nThe entry names paths that do not exist in your checkout: ${shown}${rest > 0 ? `, and ${rest} more` : ""}. Each was renamed or removed after the entry was written (find its successor with \`git log --follow --name-status -- <path>\` or by grepping for the symbols the entry names), or is a file the entry asks you to create.`;
|
|
339
382
|
return `<assigned-entry>
|
|
340
383
|
${intro}
|
|
341
384
|
"${claim.title}"${where}.
|
|
342
|
-
Implement or fix only that entry. If it is too large for one run, append ${NEEDS_REVIEW_NOTE} under its heading and end your reply with that note alone. If it should not be done, append a Refused note under its heading with your objection and change nothing else. Do not edit any other backlog entry
|
|
385
|
+
Implement or fix only that entry. If it is too large for one run, append ${NEEDS_REVIEW_NOTE} under its heading and end your reply with that note alone. If it should not be done, append a Refused note under its heading with your objection and change nothing else. Do not edit any other backlog entry.${missingLine}
|
|
343
386
|
</assigned-entry>`;
|
|
344
387
|
}
|
|
345
388
|
/** The note appended to an unassigned work instance's prompt when siblings hold backlog entries
|
|
@@ -352,38 +395,74 @@ Sibling instances hold these backlog entries; do not pick, edit, or implement th
|
|
|
352
395
|
${titles.map((t) => `- "${t}"`).join("\n")}
|
|
353
396
|
</held-entries>`;
|
|
354
397
|
}
|
|
398
|
+
/** The capped series-part range list shared by the merged-prerequisites and waiting-parts
|
|
399
|
+
* notes: at most five `- "title"` lines, each naming the entry's heading line range, then one
|
|
400
|
+
* `- …and N more` line for the rest, so the two notes list a part's neighbors identically. A
|
|
401
|
+
* pure function so its shape is pinned in tests (test/gate-prompts.test.ts). */
|
|
402
|
+
/** The longest title a compact prompt quotes in an entry list: a PLANS.md heading's tail names
|
|
403
|
+
* the plan's dates and every file it touches, which its range already leads to. */
|
|
404
|
+
const COMPACT_TITLE_CHARS = 100;
|
|
405
|
+
function entryRangeLines(entries, compact = false) {
|
|
406
|
+
// Cut by code point, so an astral character is never split in half.
|
|
407
|
+
const title = (t) => {
|
|
408
|
+
const cps = Array.from(t);
|
|
409
|
+
return compact && cps.length > COMPACT_TITLE_CHARS ? `${cps.slice(0, COMPACT_TITLE_CHARS).join("").trimEnd()}…` : t;
|
|
410
|
+
};
|
|
411
|
+
const lines = entries.slice(0, 5).map((e) => `- "${title(e.title)}" (${e.file ?? "PLANS.md"} lines ${e.start}-${e.end})`);
|
|
412
|
+
if (entries.length > 5)
|
|
413
|
+
lines.push(`- …and ${entries.length - 5} more`);
|
|
414
|
+
return lines;
|
|
415
|
+
}
|
|
416
|
+
/** The note appended to a series part's assignment when parts it requires already merged: each
|
|
417
|
+
* one's Done heading, file (PLANS-DONE.md once archived) and line range, so the implementer reads
|
|
418
|
+
* exactly what that part changed instead of searching a history that runs to thousands of lines.
|
|
419
|
+
* Lists at most five, then a count of the rest. A pure function so its shape is pinned in tests. */
|
|
420
|
+
export function buildMergedPrerequisitesNote(entries,
|
|
421
|
+
/** The compact prompt every run gets: each title cut to COMPACT_TITLE_CHARS. */
|
|
422
|
+
compact = false) {
|
|
423
|
+
const lines = entryRangeLines(entries, compact);
|
|
424
|
+
return `<merged-prerequisites>
|
|
425
|
+
Your assigned entry builds on parts that are already merged. Each one's Done entry records what it actually changed; read one by its range when your entry depends on its details, rather than searching the backlog files:
|
|
426
|
+
${lines.join("\n")}
|
|
427
|
+
</merged-prerequisites>`;
|
|
428
|
+
}
|
|
355
429
|
/** The note injected into a role's prompt after leftover recovery discarded its pinned change
|
|
356
430
|
* at MERGE_CONFLICT_LIMIT (src/loop/leftover.ts): at the cap recovery ordinarily hands the
|
|
357
431
|
* change back to its author with markers instead, so a discard means that lineage already used
|
|
358
|
-
* its hand-back. The
|
|
432
|
+
* its hand-back. The merge failures were harness-level, so without the note the author would
|
|
359
433
|
* never learn its work is gone — or that redoing it from memory of the old diff would conflict
|
|
360
434
|
* again. A pure function so its shape is pinned in tests. */
|
|
361
435
|
export function buildConflictDiscardNote(summary, attempts) {
|
|
362
|
-
return (`Your previous change ("${summary}") was discarded without
|
|
436
|
+
return (`Your previous change ("${summary}") was discarded without merging: it conflicted with main ` +
|
|
363
437
|
`${attempts} times and conflict resolution could not reconcile it. Main has moved on since you ` +
|
|
364
438
|
`wrote it. If the change is still needed, redo it against current main, starting from main's ` +
|
|
365
439
|
`current version of the files it touched rather than your earlier diff.`);
|
|
366
440
|
}
|
|
367
441
|
/** The note appended to a role's tick prompt when a change was handed back to it with the
|
|
368
|
-
* conflict markers left in place (PLANS.md "Robust conflict landing, part 2/2"): the
|
|
442
|
+
* conflict markers left in place (PLANS.md "Robust conflict landing, part 2/2"): the merge
|
|
369
443
|
* resolver could not settle it, so the author — who knows what the change was for — resolves it
|
|
370
|
-
* over current main. `reason` says whether it was an approved change whose
|
|
444
|
+
* over current main. `reason` says whether it was an approved change whose merge kept
|
|
371
445
|
* conflicting or a rejected change whose revision re-apply conflicted. The note names the
|
|
372
446
|
* conflicted files and shows the same "What main changed in these files" intent block part 1/2
|
|
373
|
-
* gives the resolver, so the author sees what main did without re-deriving it.
|
|
374
|
-
*
|
|
447
|
+
* gives the resolver, so the author sees what main did without re-deriving it. Files given with
|
|
448
|
+
* their hunk ranges are listed the way the resolver prompt lists them, with a line pointing the
|
|
449
|
+
* author at those ranges: a hand-back can leave markers in a 1.9 MB PLANS-DONE.md. A pure
|
|
450
|
+
* function so its shape is pinned in tests. */
|
|
375
451
|
export function buildConflictHandbackNote(reason, conflicted, intent) {
|
|
376
|
-
const why = reason === "
|
|
377
|
-
? "Your previously approved change could not be
|
|
452
|
+
const why = reason === "merge"
|
|
453
|
+
? "Your previously approved change could not be merged: conflict resolution could not reconcile it with main"
|
|
378
454
|
: "Your rejected change was being revised, but its diff no longer applies cleanly to main";
|
|
379
|
-
const files = conflicted.length > 0 ? conflicted.map(
|
|
455
|
+
const files = conflicted.length > 0 ? conflicted.map(conflictedFileLine).join("\n") : "- (none recorded)";
|
|
456
|
+
const readHint = listsHunks(conflicted)
|
|
457
|
+
? "\nRead each listed hunk's line range plus a few lines of context, not a conflicted file from the top."
|
|
458
|
+
: "";
|
|
380
459
|
return `<conflict-handback>
|
|
381
460
|
${why}. Main moved under it, and the change is now in your worktree as uncommitted edits with the
|
|
382
461
|
conflict markers left in place.
|
|
383
462
|
Resolve every marker (<<<<<<<, =======, >>>>>>>) by combining the intent of BOTH sides, keeping
|
|
384
463
|
main's deliberate removals, then re-run the project's declared check.
|
|
385
464
|
Conflicted files:
|
|
386
|
-
${files}
|
|
465
|
+
${files}${readHint}
|
|
387
466
|
|
|
388
467
|
${conflictIntentBlocks(intent)}
|
|
389
468
|
</conflict-handback>`;
|
|
@@ -396,14 +475,38 @@ ${conflictIntentBlocks(intent)}
|
|
|
396
475
|
* the suite red, so authoring anything else first is waste. A pure function so its shape is
|
|
397
476
|
* pinned in tests; script/headline are optional because a red carries a failing script, but the
|
|
398
477
|
* note must still read sensibly if it is ever absent. */
|
|
399
|
-
export function buildMainRedNote(sha, script, headline) {
|
|
478
|
+
export function buildMainRedNote(sha, script, headline, tail) {
|
|
400
479
|
const what = script
|
|
401
480
|
? `${script}${headline ? `: ${headline}` : ""}`
|
|
402
481
|
: (headline ?? "the project's declared check");
|
|
482
|
+
// A red baseline already carries the check's bounded clipped tail (clipBuildTail, ≤12 lines:
|
|
483
|
+
// the failing test's name, the error-message line, then the stack). Hoist it into the note so
|
|
484
|
+
// the healer starts from the actual assertion and stack instead of the one-line headline —
|
|
485
|
+
// often just the test name — and must burn its run reproducing the failure first. The tail is
|
|
486
|
+
// fenced and labeled, and never touches the directive lines above, so the failure's own text
|
|
487
|
+
// cannot read as instructions to the role. An absent, empty, or all-blank tail renders today's
|
|
488
|
+
// exact note. */
|
|
489
|
+
const output = (tail ?? []).map((l) => l.trim()).filter((l) => l !== "");
|
|
490
|
+
const tailBlock = output.length > 0
|
|
491
|
+
? `\nFailing check output (${output.length} lines):\n\`\`\`\n${output.join("\n")}\n\`\`\`\n`
|
|
492
|
+
: "";
|
|
403
493
|
return `<main-red>
|
|
404
494
|
main's own suite is red at ${shortSha(sha)} (${what}). Every code-producing role is skipping authoring and no change merges until main is green.
|
|
405
|
-
This
|
|
495
|
+
This run your job is to make main green: reproduce that failure, fix it (add a regression test where one is feasible), and merge the fix.
|
|
406
496
|
The review gate's deterministic pre-check runs main + your change, so a change that leaves the suite red is rejected — do not author anything else first.
|
|
407
497
|
If the failure is environmental — flaky, load-sensitive, or a broken local toolchain — say so plainly in your reply rather than editing unrelated code.
|
|
408
|
-
</main-red>`;
|
|
498
|
+
${tailBlock}</main-red>`;
|
|
499
|
+
}
|
|
500
|
+
/** The note appended to an assigned series part's prompt naming the later planned parts that
|
|
501
|
+
* wait on it, so the implementer sees where its part ends and leaves their share alone. At most
|
|
502
|
+
* five are listed, then a literal `- …and N more` line (the `moreSuffix` phrase renders inline,
|
|
503
|
+
* not as a list line). A pure function so its shape is pinned in tests. */
|
|
504
|
+
export function buildWaitingPartsNote(entries,
|
|
505
|
+
/** The compact prompt every run gets: each title cut to COMPACT_TITLE_CHARS. */
|
|
506
|
+
compact = false) {
|
|
507
|
+
const shown = entryRangeLines(entries, compact);
|
|
508
|
+
return `<waiting-parts>
|
|
509
|
+
Later parts of your series wait for yours to land, and each one plans its own share of the work: do none of it here, and keep in place the names and files they say your part leaves behind. Read one by its range when you need to know what it expects:
|
|
510
|
+
${shown.join("\n")}
|
|
511
|
+
</waiting-parts>`;
|
|
409
512
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { contextBudgetRule } from "./prompt.js";
|
|
2
3
|
import { dialectFor } from "./dialect.js";
|
|
3
4
|
import { NOTHING_TO_DO, REPLY_ENDINGS, SUMMARY_BLOCK } from "../verdict/reply-contract.js";
|
|
@@ -16,39 +17,39 @@ unusable, redo it — but stay on this task rather than picking a new one.`;
|
|
|
16
17
|
* model re-read the whole tree and repeated identical reads of one file nine times. */
|
|
17
18
|
export function buildResumePrompt(roleId, cause = "restart", agent) {
|
|
18
19
|
const opening = cause === "cut-off"
|
|
19
|
-
? `Your previous run as the "${roleId}"
|
|
20
|
-
|
|
20
|
+
? `Your previous run as the "${roleId}" role ran out of context before it could finish, so
|
|
21
|
+
tumwater compacted the session and is continuing it now. Your worktree is exactly as you left it;
|
|
21
22
|
what you did so far is summarized above. Do NOT re-read the codebase: trust the summary and
|
|
22
23
|
re-check only what you must, in ranges — at most ~10 tool calls of re-reading, and never the same
|
|
23
24
|
file twice. Finish the SAME task with the smallest change that completes it. If it cannot be
|
|
24
25
|
finished within a fraction of the window, scope it down to what is already complete and coherent,
|
|
25
26
|
leave the project working, and stop.`
|
|
26
27
|
: cause === "hung-tool"
|
|
27
|
-
? `
|
|
28
|
+
? `Tumwater killed your previous run as the "${roleId}" role because it made no progress long enough to trip its hang watchdog — almost always one tool call that hung (a command waiting on input, or a scan far wider than intended). That tool call is dead: do not re-run it unchanged. Your worktree is exactly as you left it, and this session carries everything you did so far — verify the effect of anything the killed call was supposed to produce before relying on it, and bound any long-running command (a time limit, a scoped path).
|
|
28
29
|
|
|
29
30
|
${CONTINUE_TASK_BRIDGE}`
|
|
30
31
|
: cause === "timeout"
|
|
31
|
-
? `Your previous run as the "${roleId}"
|
|
32
|
-
it was still making progress — a slow run, not a failed one — so
|
|
32
|
+
? `Your previous run as the "${roleId}" role reached tumwater's run time limit while
|
|
33
|
+
it was still making progress — a slow run, not a failed one — so tumwater preserved your
|
|
33
34
|
worktree and this session and is continuing them now. Finish the SAME task, but budget against
|
|
34
35
|
that same limit: make the smallest change that completes the task coherently, verify it, and
|
|
35
36
|
stop. Do not restart broad exploration the first run already finished; trust the work so far
|
|
36
37
|
and build on it.`
|
|
37
38
|
: cause === "budget-resumed"
|
|
38
|
-
? `The
|
|
39
|
-
started on the fallback model —
|
|
39
|
+
? `The team's daily budget has reopened, and your previous run as the "${roleId}" role
|
|
40
|
+
started on the fallback model — tumwater has moved this session back to the primary model and
|
|
40
41
|
is continuing it now. Your worktree is exactly as you left it, and this session carries everything
|
|
41
42
|
you did so far.
|
|
42
43
|
|
|
43
44
|
${CONTINUE_TASK_BRIDGE}`
|
|
44
45
|
: cause === "rate-limit"
|
|
45
46
|
? `The claude.ai subscription usage window rejected your previous run as the "${roleId}"
|
|
46
|
-
|
|
47
|
+
role before it could finish, so tumwater held every subscription claude model job until the window
|
|
47
48
|
reset. Your worktree is exactly as you left it, and this session carries everything you did so
|
|
48
|
-
far. The window has since reset, and
|
|
49
|
+
far. The window has since reset, and tumwater is continuing this session now.
|
|
49
50
|
|
|
50
51
|
${CONTINUE_TASK_BRIDGE}`
|
|
51
|
-
: `
|
|
52
|
+
: `Tumwater was restarted while you (the "${roleId}" role) were mid-run. Your worktree
|
|
52
53
|
is exactly as you left it, and this session carries everything you did so far. A tool call that
|
|
53
54
|
was executing when the restart hit may not have finished — verify its effect before relying on it.
|
|
54
55
|
|
|
@@ -57,7 +58,7 @@ ${CONTINUE_TASK_BRIDGE}`;
|
|
|
57
58
|
still apply, in particular:
|
|
58
59
|
- Do exactly ONE focused task, then stop.
|
|
59
60
|
${contextBudgetRule(dialectFor(agent))}
|
|
60
|
-
- Never create, amend, or revert git commits —
|
|
61
|
+
- Never create, amend, or revert git commits — tumwater handles all git operations.
|
|
61
62
|
- Your last message is plain text — never a tool call or an announcement of a next step.
|
|
62
63
|
${REPLY_ENDINGS}`;
|
|
63
64
|
}
|
|
@@ -66,33 +67,47 @@ ${REPLY_ENDINGS}`;
|
|
|
66
67
|
* the full assembled tick prompt, plus the one fact a fresh session cannot know: an interrupted
|
|
67
68
|
* run's uncommitted edits are sitting in the worktree. The worktree is NOT reset for a handoff,
|
|
68
69
|
* so the bridge must send the new run to `git diff` before it starts its own work. */
|
|
69
|
-
export function buildHandoffPrompt(roleId, tickPrompt, cause = "handoff"
|
|
70
|
+
export function buildHandoffPrompt(roleId, tickPrompt, cause = "handoff",
|
|
71
|
+
/** Named facts an image-reroute handoff note needs that the cause alone cannot carry: the
|
|
72
|
+
* model the blind run was on and the image file it read. */
|
|
73
|
+
imageReroute) {
|
|
70
74
|
const note = cause === "budget-capped"
|
|
71
|
-
? `An earlier API-billed claude run of the "${roleId}"
|
|
75
|
+
? `An earlier API-billed claude run of the "${roleId}" role hit its per-run budget cap before it
|
|
72
76
|
could finish; its uncommitted edits are in your worktree — review \`git diff\`, then finish or
|
|
73
|
-
discard them. This run starts on the
|
|
74
|
-
:
|
|
77
|
+
discard them. This run starts on the model job the daily budget left you.`
|
|
78
|
+
: cause === "image-reroute"
|
|
79
|
+
? `An earlier run of the "${roleId}" role on ${imageReroute?.from ?? "another model"} stopped
|
|
80
|
+
when it read ${imageReroute?.path ?? "an image"}, because that model cannot read images; its
|
|
81
|
+
uncommitted edits are in your worktree — review \`git diff\`, then finish or discard them. This
|
|
82
|
+
run is on a model that CAN read images, so read the image again.`
|
|
83
|
+
: `An earlier run of the "${roleId}" role on another model was interrupted; its
|
|
75
84
|
uncommitted edits are in your worktree — review \`git diff\`, then finish or discard them.`;
|
|
76
85
|
return `${tickPrompt}\n\n${note}`;
|
|
77
86
|
}
|
|
78
87
|
/** The one-turn follow-up sent into a tick's OWN session (--continue) when the run changed files
|
|
79
|
-
* but its reply carried no SUMMARY line — 73 of the first 670 commits
|
|
88
|
+
* but its reply carried no SUMMARY line — 73 of the first 670 commits merged as "<role> tick N"
|
|
80
89
|
* because of that, most of them the largest diffs in the repo. The session still holds everything
|
|
81
90
|
* the run did, so one short reply recovers the subject and body the commit deserves; the caller
|
|
82
91
|
* bounds the run tightly and falls back to a diff-derived subject if this too yields nothing. */
|
|
83
|
-
export function buildSummaryRequestPrompt() {
|
|
92
|
+
export function buildSummaryRequestPrompt(overLength) {
|
|
93
|
+
if (overLength !== undefined) {
|
|
94
|
+
return `Your final reply's SUMMARY line is ${overLength} characters; the commit subject holds at most 72,
|
|
95
|
+
so tumwater would cut it mid-word. Reply now with ONLY the closing block, with the SUMMARY
|
|
96
|
+
restated in at most 72 characters — no tool calls, no other text, one line each:
|
|
97
|
+
${SUMMARY_BLOCK}`;
|
|
98
|
+
}
|
|
84
99
|
return `Your run changed files in the worktree, but your final reply
|
|
85
|
-
did not include the required closing block, so
|
|
100
|
+
did not include the required closing block, so tumwater cannot describe the commit it is about
|
|
86
101
|
to make. Reply now with ONLY that block — no tool calls, no other text, one line each:
|
|
87
102
|
${SUMMARY_BLOCK}`;
|
|
88
103
|
}
|
|
89
104
|
/** The note injected into a role's next FRESH tick prompt after its previous run(s) were cut
|
|
90
|
-
* off at the context ceiling without
|
|
105
|
+
* off at the context ceiling without merging anything (the loop gave up resuming — see
|
|
91
106
|
* tick-apply.ts's CUT_OFF_RESUME_LIMIT — or a cut-off director prompt is re-running). The only
|
|
92
107
|
* cross-tick memory that the last attempt was too big for the window. */
|
|
93
108
|
export function buildCutOffNote(streak) {
|
|
94
109
|
const runs = streak === 1 ? "run" : `${streak} runs`;
|
|
95
|
-
return `Your previous ${runs} as this
|
|
110
|
+
return `Your previous ${runs} as this role ran out of context before merging anything. Pick a
|
|
96
111
|
smaller, more targeted task this time and budget your reading: grep first, read in ranges, cap
|
|
97
112
|
command output. If the smallest useful task still needs most of the codebase in view, reply
|
|
98
113
|
${NOTHING_TO_DO} instead of starting it.`;
|