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,7 +1,10 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** Shared instruction fragments role prompts and prompt assembly embed verbatim. Pure
|
|
2
|
-
* data: this module imports
|
|
3
|
+
* data: this module imports only the dependency-free legacy-role-ids, so prompt assembly
|
|
4
|
+
* can depend on it without a cycle.
|
|
3
5
|
* Split from roles.ts, whose catalog (now role-catalog.ts) find texts embed these fragments
|
|
4
6
|
* while prompt.ts embeds them too — the wording is one concern, the catalog another. */
|
|
7
|
+
import { roleCommitGrep } from "./legacy-role-ids.js";
|
|
5
8
|
/** Shared guidance for any loop about to record a plan or bug: split independent parts
|
|
6
9
|
* into their own entries. Defined once so the director and role prompts cannot drift.
|
|
7
10
|
* Lives here (not in prompt.ts) because two role `find` texts embed it — keeping this
|
|
@@ -9,7 +12,7 @@
|
|
|
9
12
|
* and the catalog → here. */
|
|
10
13
|
export const DECOMPOSITION_GUIDANCE = `Before recording a plan or bug, check whether it decomposes
|
|
11
14
|
into independent subparts (separate features, or separate bugs). If it does, record each part as
|
|
12
|
-
its own PLANS.md/BUGS.md entry that cross-references its siblings, so
|
|
15
|
+
its own PLANS.md/BUGS.md entry that cross-references its siblings, so roles can pick them up
|
|
13
16
|
independently; if the parts are not truly independent, keep a single entry.`;
|
|
14
17
|
/** Shared instruction for closing a backlog entry (the feature loop moving a plan to Done,
|
|
15
18
|
* the bugfix loop moving a bug to Fixed). Written against the observed failure (PLANS.md
|
|
@@ -17,9 +20,11 @@ independently; if the parts are not truly independent, keep a single entry.`;
|
|
|
17
20
|
* commits left PLANS.md with two `## Done` headings — so the wording names the file's EXISTING
|
|
18
21
|
* resolved heading, forbids touching `## ` structure, and gives the verifiable invariant
|
|
19
22
|
* (the same headings before and after). Lettered sub-steps, because the callers embed it as one
|
|
20
|
-
* numbered step of their task.
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
+
* numbered step of their task. The pasted entry does not stay there: tumwater moves it to the top
|
|
24
|
+
* of `archive` when the change commits (plans/backlog-archive.md), which the last line says so no
|
|
25
|
+
* run opens the archive to do it by hand. Takes the file, section and archive names so the feature
|
|
26
|
+
* and bugfix texts cannot drift apart; lives here with the other shared fragments. */
|
|
27
|
+
export function backlogMoveGuidance(file, openSection, resolvedSection, archive) {
|
|
23
28
|
return `move the entry, step by step:
|
|
24
29
|
a. cut the entry out of \`${openSection}\`;
|
|
25
30
|
b. add the closing date to its \`### \` heading;
|
|
@@ -27,8 +32,12 @@ export function backlogMoveGuidance(file, openSection, resolvedSection) {
|
|
|
27
32
|
d. if it was the last entry under \`${openSection}\`, leave a \`_None yet._\` placeholder
|
|
28
33
|
there.
|
|
29
34
|
Never add, remove, or rename a \`## \` heading: \`grep -n '^## ' ${file}\` must list the
|
|
30
|
-
same headings before and after the edit
|
|
35
|
+
same headings before and after the edit. When your change commits, tumwater moves the
|
|
36
|
+
pasted entry to the top of ${archive}; do not open or edit ${archive} yourself.`;
|
|
31
37
|
}
|
|
38
|
+
/** The heading clause requiredParts parses, spelled as the parser reads it. Interpolated because
|
|
39
|
+
* a comment inside the template would become prompt text. */
|
|
40
|
+
const REQUIRES_PART_FORM = "requires part N/M landed"; // vocabulary-ok: requiredParts parses this heading syntax
|
|
32
41
|
/** Shared sizing rule for anyone writing a plan (the plan role and the director): the feature
|
|
33
42
|
* loop is one mid-sized local model working alone in one run, and the review gate rejects a
|
|
34
43
|
* change that lands less than its entry promises — so a plan must fit that run. Oversized plans
|
|
@@ -38,13 +47,17 @@ export function backlogMoveGuidance(file, openSection, resolvedSection) {
|
|
|
38
47
|
* and its own fast-forward deleted the config. */
|
|
39
48
|
export const PLAN_SIZING = `Size every plan to ONE implementation run by a mid-sized model working
|
|
40
49
|
alone: a handful of files, at most a few hundred lines of change including tests, and no design
|
|
41
|
-
question left open for the implementer. Anything larger is split into independently
|
|
42
|
-
sub-plans that cross-reference each other, each with its own acceptance criteria.
|
|
50
|
+
question left open for the implementer. Anything larger is split into independently mergeable
|
|
51
|
+
sub-plans that cross-reference each other, each with its own acceptance criteria. A sub-plan that
|
|
52
|
+
depends on a sibling spells that dependency as ${REQUIRES_PART_FORM}, in quotes, in its
|
|
53
|
+
heading's trailing parenthetical; it is the only form the claim allocator reads, so a dependency
|
|
54
|
+
worded any other way (such as blocked by part 1/2, after part 1/2, or follows part 1/2) does not
|
|
55
|
+
hold the part until its prerequisite leaves ## Planned. Anchor plans on
|
|
43
56
|
file paths and symbol names — functions, types, constants, test names — never on line numbers or
|
|
44
|
-
ranges, because line anchors go stale with every
|
|
45
|
-
|
|
46
|
-
change to how
|
|
47
|
-
|
|
57
|
+
ranges, because line anchors go stale with every merge. When tumwater is building itself,
|
|
58
|
+
every commit merges under the build that predates it, so a
|
|
59
|
+
change to how merges behave and any step that depends on it are separate sub-plans, the second
|
|
60
|
+
merge only once the first is the running build.`;
|
|
48
61
|
/** The closed vocabulary for the validation-gap trace (plans/repair-traces.md): what made a bug
|
|
49
62
|
* hard to *confirm*, which is the only evidence of where this project's test infrastructure is
|
|
50
63
|
* weakest. Closed on purpose so the traces aggregate, and `none` is a written value rather than an
|
|
@@ -71,17 +84,28 @@ made the bug hard to CONFIRM — not to fix — as one line, \`**Validation gap:
|
|
|
71
84
|
- real-run-needed — no offline path covered it; a real bounded run was required
|
|
72
85
|
- no-observability — the failure left no trace
|
|
73
86
|
- slow-check — the only verification was slow enough to shape the fix
|
|
74
|
-
- unclear-invariant — had to reconstruct what the code was supposed to guarantee first
|
|
87
|
+
- unclear-invariant — had to reconstruct what the code was supposed to guarantee first; record the reconstructed invariant in docs/invariants.md in the same commit so a later bug in that area does not reconstruct it again
|
|
75
88
|
When nothing fits exactly, use the closest tag and say so in the sentence — never invent a
|
|
76
89
|
tag. \`none\` is written, never omitted, so a tally has an honest denominator.`;
|
|
77
|
-
/** The one query that tallies validation-gap tags across BUGS.md
|
|
78
|
-
* \`**Validation gap:**\` line bugfix writes and
|
|
90
|
+
/** The one query that tallies validation-gap tags across BUGS.md and BUGS-FIXED.md (where fixed
|
|
91
|
+
* bugs are archived), counting both the verbatim \`**Validation gap:**\` line bugfix writes and
|
|
92
|
+
* the \`gap: <tag>\` suffix of the one-line records the steward used to compress older entries to.
|
|
79
93
|
* Split from VALIDATION_GAP_GUIDANCE so only the steward — the one role that aggregates the
|
|
80
94
|
* tags — carries it; a bugfix run writes one line and never needs the tally. */
|
|
81
|
-
export const VALIDATION_GAP_TALLY = `One query counts both the verbatim line and the compressed
|
|
82
|
-
\`gap: <tag>\` suffix: \`grep -
|
|
95
|
+
export const VALIDATION_GAP_TALLY = `One query counts both the verbatim line and the older compressed
|
|
96
|
+
\`gap: <tag>\` suffix across BUGS.md and its archive: \`grep -sohE 'gap:[*]{0,2} ?[a-z-]+' BUGS.md BUGS-FIXED.md | sed -E 's/^gap:[*]{0,2} ?//' | sort | uniq -c\`.`;
|
|
97
|
+
/** The one query a *consumer* of the validation-gap traces — a role like coverage that picks its
|
|
98
|
+
* next target from the record of what made past bugs hard to confirm — runs to pull the
|
|
99
|
+
* recent, still-full-body entries. Split from VALIDATION_GAP_TALLY (the steward's aggregate
|
|
100
|
+
* count) because this one lists the *entries*, newest-first: \`head\` not \`tail\`, since
|
|
101
|
+
* \`## Fixed\` is newest-first, so the recent full-body entries sit at the top, for a role to
|
|
102
|
+
* read each hit's free text. It reads BUGS.md's own section first, then BUGS-FIXED.md, where
|
|
103
|
+
* fixed bugs are archived (plans/backlog-archive.md); \`-s\` keeps a missing archive quiet, and
|
|
104
|
+
* each hit names its file and line. Built from the closed vocabulary (minus \`none\`, which
|
|
105
|
+
* names nothing to build) so it cannot drift. */
|
|
106
|
+
export const VALIDATION_GAP_QUERY = `grep -snE '\\*\\*Validation gap:\\*\\* (${VALIDATION_GAP_TAGS.filter((t) => t !== "none").join("|")})' BUGS.md BUGS-FIXED.md | head -20`;
|
|
83
107
|
/** The literal prefix every Needs-review note starts with. Exported so a reader that only has
|
|
84
|
-
* to recognize one (the backlog index's ` [needs review]` mark) can match on it instead of
|
|
108
|
+
* to recognize one (the backlog index's ` [blocked: needs review]` mark) can match on it instead of
|
|
85
109
|
* parsing the date or the wording out of the full template — the same split as
|
|
86
110
|
* NEEDS_REPLAN_PREFIX. */
|
|
87
111
|
export const NEEDS_REVIEW_PREFIX = `**Needs review `;
|
|
@@ -91,22 +115,31 @@ export const NEEDS_REVIEW_PREFIX = `**Needs review `;
|
|
|
91
115
|
* clause cannot drift. */
|
|
92
116
|
export const NEEDS_REVIEW_NOTE = `${NEEDS_REVIEW_PREFIX}<YYYY-MM-DD> by feature: too large for one run**`;
|
|
93
117
|
/** The literal prefix every Needs-replan note starts with. Exported so a reader that only has to
|
|
94
|
-
* recognize one (the backlog index's ` [needs replan]` mark) can match on it instead of
|
|
95
|
-
* the date or the round count out of the full template. */
|
|
118
|
+
* recognize one (the backlog index's ` [blocked: needs replan]` mark) can match on it instead of
|
|
119
|
+
* parsing the date or the round count out of the full template. */
|
|
96
120
|
export const NEEDS_REPLAN_PREFIX = `**Needs replan `;
|
|
97
121
|
/** The markdown note the feature loop appends under a plan whose change was rejected after its
|
|
98
122
|
* final revision round, carrying the reviewer's objections back to the strong-tier plan loop
|
|
99
123
|
* instead of letting feature re-author the same plan. Same convention as NEEDS_REVIEW_NOTE: a
|
|
100
124
|
* note a later fresh tick reads, with no code parsing it. */
|
|
101
125
|
export const NEEDS_REPLAN_NOTE = `${NEEDS_REPLAN_PREFIX}<YYYY-MM-DD> by feature: rejected after <N> review rounds**`;
|
|
102
|
-
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
126
|
+
/** The literal prefix every Waiting-for-the-next-failure note starts with. Exported so a reader
|
|
127
|
+
* that only has to recognize one (the backlog index's ` [blocked: waiting for the next failure]`
|
|
128
|
+
* mark) can match on it instead of parsing the date or the wording out of the full template. */
|
|
129
|
+
export const WAITING_FOR_FAILURE_PREFIX = `**Waiting for the next failure `;
|
|
130
|
+
/** The markdown note the bugfix loop writes under an open bug it cannot reproduce once it has made
|
|
131
|
+
* the failure explain itself, so bugfix stops spending runs without new evidence. Same convention
|
|
132
|
+
* as NEEDS_REPLAN_NOTE: a note a later fresh tick reads. It lifts when whoever records the next
|
|
133
|
+
* occurrence deletes the line, or by `tumwater bug --release <n>`. */
|
|
134
|
+
export const WAITING_FOR_FAILURE_NOTE = `${WAITING_FOR_FAILURE_PREFIX}<YYYY-MM-DD> by bugfix: <what the next failure's message will show>**`;
|
|
135
|
+
/** How a role with no backlog (organize, clean, dedupe, perf, security, robustness, improve) finds
|
|
136
|
+
* its one task, as numbered steps. Written against the observed failure: with nothing to point
|
|
137
|
+
* at, a local model reads the codebase file by file — thirty whole-file reads, ~300 KB of tool
|
|
138
|
+
* output — and then either fills its window or declares nothing-to-do (improve: 41 of 44 ticks in
|
|
139
|
+
* the first week of September landed nothing). Cheap signals shortlist candidates; a decision
|
|
140
|
+
* deadline ends the search; the harness's own commit subjects (`tumwater(<role>): …`) are the
|
|
141
|
+
* only cross-tick memory of what the role did lately, so the text names them. Takes the role id
|
|
142
|
+
* so the git command is literal, not a placeholder the model has to fill in. */
|
|
110
143
|
export function searchGuidance(roleId) {
|
|
111
144
|
return `How to search — do not read the codebase file by file: a whole-tree survey costs most of
|
|
112
145
|
the run and rarely finds anything a targeted look would not.
|
|
@@ -114,10 +147,10 @@ the run and rarely finds anything a targeted look would not.
|
|
|
114
147
|
the expensive unit: \`git log --stat -15\` (recent churn is where problems accumulate),
|
|
115
148
|
\`wc -l\` over the source files (size outliers), and \`grep -rn\` for the specific pattern
|
|
116
149
|
you are after.
|
|
117
|
-
2. Skip what your own role handled lately: \`git log --oneline -15
|
|
118
|
-
lists it —
|
|
150
|
+
2. Skip what your own role handled lately: \`git log --oneline -15 ${roleCommitGrep(roleId)}\`
|
|
151
|
+
lists it — tumwater's commit subjects say what each role did.
|
|
119
152
|
3. Vary the slice you look at across runs (a different directory, module, or recency window)
|
|
120
|
-
so successive
|
|
153
|
+
so successive runs do not all converge on the same files.
|
|
121
154
|
4. Shortlist at most five candidate files and inspect them with \`grep -n\` and ranged reads;
|
|
122
155
|
open a file whole only when it is under ~300 lines.
|
|
123
156
|
5. Decide within ~15 tool calls, in a handful of turns: if no candidate clearly clears the bar
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** How a role's id shows up in words: an instance id as `feature #2`. Pure, no I/O, so core
|
|
3
|
+
* modules and the UI may both import it. */
|
|
4
|
+
import { baseRoleOf, instanceIndex } from "./loop-ids.js";
|
|
5
|
+
/** An instance id as `feature #2`; any other id (a bare role, `director`, a custom role,
|
|
6
|
+
* `feature-1`) unchanged. */
|
|
7
|
+
export function roleLabel(runnerId) {
|
|
8
|
+
const base = baseRoleOf(runnerId);
|
|
9
|
+
return base === runnerId ? runnerId : `${base} #${instanceIndex(runnerId)}`;
|
|
10
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { agree, shortSpanPhrase } from "../text/phrases.js";
|
|
3
|
+
import { compactTokens, usd } from "../text/format.js";
|
|
4
|
+
import { clockHm, formatTimestamp, humanSeconds, nextRunPhrase, secondsSince, secondsUntil } from "../text/datetime.js";
|
|
3
5
|
import { claudeWindowLabel } from "../agent/claude/claude-account.js";
|
|
6
|
+
import { yieldMultiplierSuffix } from "../scheduling/backoff.js";
|
|
7
|
+
import { quietWindowEnd } from "../scheduling/quiet-hours.js";
|
|
8
|
+
import { isAgentId } from "../config/model-selector.js";
|
|
4
9
|
/** The terminal's view of one loop — what `tumwater role <id>` prints, rendered as Markdown
|
|
5
10
|
* from the payload role-view.ts's rolePayload collects (the same document `--json` prints).
|
|
6
11
|
* Like backlog-render.ts, the renderer owns no collection of its own: the CLI's --json and
|
|
@@ -32,15 +37,21 @@ function fencedOrNone(label, text, noneNote = "none") {
|
|
|
32
37
|
* `renderRoleMarkdown` splices these at the same position, so the extracted function and the
|
|
33
38
|
* full document cannot drift. */
|
|
34
39
|
export function inspectMarkdownLines(p) {
|
|
40
|
+
const running = chainStepLine(p);
|
|
41
|
+
return [...(running ? [running, ""] : []), ...inspectBodyLines(p)];
|
|
42
|
+
}
|
|
43
|
+
/** The notebook, prompt and disclosure blocks alone — what renderRoleMarkdown splices after
|
|
44
|
+
* its own Team lines, which already carry the fallback-step bullet. */
|
|
45
|
+
function inspectBodyLines(p) {
|
|
35
46
|
const lines = [];
|
|
36
|
-
lines.push(...fencedOrNone("
|
|
37
|
-
lines.push(...fencedOrNone("Next
|
|
47
|
+
lines.push(...fencedOrNone("Notes", p.notes, "none yet — the role's own earlier runs write them, and the director has none"));
|
|
48
|
+
lines.push(...fencedOrNone("Next run's prompt", p.nextPrompt, "nothing to do this run"));
|
|
38
49
|
// tick-prompt.ts dequeues exactly one queued prompt per tick, so with more than one
|
|
39
50
|
// waiting the block above shows only the oldest — say so, so the count line and the
|
|
40
51
|
// block never read as a contradiction (the 2026-10-04 BUGS.md entry under ## Fixed).
|
|
41
52
|
if (p.inboxCount > 1 && p.nextPrompt !== null && p.nextPrompt.trim() !== "") {
|
|
42
53
|
const more = p.inboxCount - 1;
|
|
43
|
-
lines.push(`_(the
|
|
54
|
+
lines.push(`_(the next run's prompt embeds the oldest inbox message — one is taken per run; ${more} more ${agree(more, "waits", "wait")})_`);
|
|
44
55
|
}
|
|
45
56
|
return lines;
|
|
46
57
|
}
|
|
@@ -53,49 +64,214 @@ function modelPair(provider, model) {
|
|
|
53
64
|
/** The backend's own-default label when no model resolves: `<agent> default` — pi's
|
|
54
65
|
* historical `pi default` for the pi backend, `claude default` for claude (part 4f/7). */
|
|
55
66
|
function agentDefault(p) {
|
|
56
|
-
return `${p.
|
|
67
|
+
return `${p.backend !== "pi" ? p.backend : "pi"} default`;
|
|
57
68
|
}
|
|
58
69
|
/** The provider/model wiring as one readable token: `provider/model` when both resolve,
|
|
59
70
|
* `model` alone when only the model is named, and the backend-default note when neither is.
|
|
60
71
|
* A non-pi backend is named as the selector's provider namespace (`claude/claude-sonnet-5-5`),
|
|
61
72
|
* matching the string the events and status rows carry; a pi seam is unchanged. */
|
|
62
73
|
function modelLine(p) {
|
|
74
|
+
// pi's own saved default (part 2/2): the formatted selector already carries its thinking
|
|
75
|
+
// level (piDefault is formatted with the effective thinking winning), so the separate
|
|
76
|
+
// ` (thinking: …)` suffix is dropped for this case only.
|
|
77
|
+
if (p.piDefault)
|
|
78
|
+
return `${agentDefault(p)} (${p.piDefault})`;
|
|
63
79
|
const pair = modelPair(p.provider, p.model);
|
|
64
80
|
const thinking = p.thinking ? ` (thinking: ${p.thinking})` : "";
|
|
65
81
|
if (!pair)
|
|
66
82
|
return `${agentDefault(p)}${thinking}`;
|
|
67
|
-
const agent = p.
|
|
83
|
+
const agent = p.backend !== "pi" ? `${p.backend}/` : "";
|
|
68
84
|
return `${agent}${pair}${thinking}`;
|
|
69
85
|
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
86
|
+
/** A fallback pair as one readable token, agent-qualified exactly like modelLine — a non-pi
|
|
87
|
+
* backend (its `agent`, or a provider that names one, `claude`) is named as the selector's
|
|
88
|
+
* namespace; a pi pair keeps `provider/model`; neither half present falls back to the
|
|
89
|
+
* backend-default label. One home for the Model fallback and Budget fallback lines, so a
|
|
90
|
+
* claude fallback reads `claude/claude-sonnet-5-5`, not a bare id (PLANS.md "Mixed claude +
|
|
91
|
+
* pi fleets, part 3/4" gap 7). */
|
|
92
|
+
function fallbackPairLabel(fb, agentDefault) {
|
|
93
|
+
const agentBackend = (fb.backend !== undefined && fb.backend !== "pi" ? fb.backend : undefined) ??
|
|
94
|
+
(fb.provider !== undefined && isAgentId(fb.provider) ? fb.provider : undefined);
|
|
95
|
+
if (fb.model !== undefined) {
|
|
96
|
+
if (agentBackend !== undefined)
|
|
97
|
+
return `${agentBackend}/${fb.model}`;
|
|
98
|
+
return fb.provider !== undefined ? `${fb.provider}/${fb.model}` : fb.model;
|
|
99
|
+
}
|
|
100
|
+
if (fb.provider !== undefined)
|
|
101
|
+
return fb.provider;
|
|
102
|
+
return agentDefault;
|
|
103
|
+
}
|
|
104
|
+
/** Why a tier moved down its fallback chain, as the role view words it. */
|
|
105
|
+
function chainReasonPhrase(reason, windowType) {
|
|
106
|
+
switch (reason) {
|
|
107
|
+
case "window":
|
|
108
|
+
return `${windowType !== undefined ? claudeWindowLabel(windowType) : "Claude"} window used up`;
|
|
109
|
+
case "reserve":
|
|
110
|
+
return "window reserve reached";
|
|
111
|
+
case "cap":
|
|
112
|
+
return "daily cap reached";
|
|
113
|
+
case "step-cap":
|
|
114
|
+
return "previous step's cap reached";
|
|
115
|
+
case "overage":
|
|
116
|
+
return "extra usage refused";
|
|
117
|
+
case "demoted":
|
|
118
|
+
return "previous step demoted";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** The `Running on fallback <step> of <of> (<why>, resets HH:MM)` bullet, or null when the
|
|
122
|
+
* role's tier is on its primary. */
|
|
123
|
+
function chainStepLine(p) {
|
|
124
|
+
const c = p.chainStep;
|
|
125
|
+
if (!c)
|
|
126
|
+
return null;
|
|
127
|
+
const resets = c.returnsAt !== undefined ? `, resets ${clockHm(c.returnsAt)}` : "";
|
|
128
|
+
return `- Running on fallback ${c.step} of ${c.of} (${chainReasonPhrase(c.reason, p.billingWindow)}${resets})`;
|
|
129
|
+
}
|
|
130
|
+
/** One recent tick outcome as a Markdown bullet line — `<time> · <result> · <span> —
|
|
131
|
+
* <summary>` — so `tumwater role <id>` and the TUI notebook view read a loop's discrete
|
|
132
|
+
* outcomes at a glance. `time` is the same local `YYYY-MM-DD HH:MM:SS` stamp the model-
|
|
133
|
+
* fallback `since` prints (formatTimestamp); `span` is the same short-duration phrasing
|
|
134
|
+
* shortSpanPhrase renders for holdPhrase and the event feed's countdowns; the summary is
|
|
135
|
+
* appended when the event carried one. Unset halves are dropped, never printed as a literal
|
|
136
|
+
* `undefined`. */
|
|
137
|
+
function recentOutcomeLine(o) {
|
|
138
|
+
let line = `${formatTimestamp(o.ts)} · ${o.result}`;
|
|
139
|
+
if (o.durationMs !== undefined)
|
|
140
|
+
line += ` · ${shortSpanPhrase(o.durationMs)}`;
|
|
141
|
+
if (o.summary)
|
|
142
|
+
line += ` — ${o.summary}`;
|
|
143
|
+
return line;
|
|
144
|
+
}
|
|
145
|
+
/** One quiet-hours bullet: the window, whose it is, and whether it holds the loop now — with
|
|
146
|
+
* the same `quiet until <end>` text the status table uses. */
|
|
147
|
+
function quietHoursLine(w) {
|
|
148
|
+
const whose = w.scope === "role" ? "own window" : "team window";
|
|
149
|
+
const state = w.inWindow ? `holding now — quiet until ${quietWindowEnd(w.window)}` : "not holding now";
|
|
150
|
+
return `- Quiet hours: ${w.window} (${whose}; ${state})`;
|
|
151
|
+
}
|
|
152
|
+
/** The loop's schedule as Markdown bullet lines — `Next tick`, `Last tick`, then one
|
|
153
|
+
* `Quiet hours` line per governing window (none when the payload carries no windows) — in the
|
|
154
|
+
* same words and wording family as the team surface's `next run` cell (status-render.ts's
|
|
155
|
+
* nextRunCell), so the role view and the status table cannot drift on when a loop acts next:
|
|
156
|
+
* a never-scheduled loop (nextRunAt 0) reads `not scheduled yet`; a due loop reads `due now`;
|
|
157
|
+
* a future one reads `in <Ns>` through humanSeconds, prefixed `backoff ` while backoffSeconds
|
|
158
|
+
* > 0 (the wake row-action clears exactly that) — the shared nextRunPhrase, so the role view
|
|
159
|
+
* and the status `next run` cell cannot drift on the backoff prefix or the humanSeconds
|
|
160
|
+
* bucketing. `Last tick` reuses formatTimestamp and the same `humanSeconds … ago` phrasing the
|
|
161
|
+
* status `last tick` cell ages with, or `never` for a loop that has not ticked. `now` is
|
|
162
|
+
* injected (default Date.now()) so the renderer stays a pure function of the payload and the
|
|
163
|
+
* test can pin a fixed clock. */
|
|
164
|
+
function scheduleLine(p, now) {
|
|
165
|
+
// The yield multiplier rides the line as `×N` exactly like nextRunCell/fmtNextRun: a quiet
|
|
166
|
+
// yield-scaled loop's effective gap is longer than nextRunAt's countdown shows (isEligible
|
|
167
|
+
// gates on the multiplier too), so the Schedule must not understate when the loop acts next.
|
|
168
|
+
const suffix = yieldMultiplierSuffix(p.yieldMultiplier);
|
|
169
|
+
let next;
|
|
170
|
+
if (p.nextRunAt <= 0) {
|
|
171
|
+
next = "not scheduled yet";
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const remain = secondsUntil(p.nextRunAt, now);
|
|
175
|
+
next = nextRunPhrase(remain, p.backoffSeconds > 0, "due now", true) + suffix;
|
|
176
|
+
}
|
|
177
|
+
const last = p.lastRunEndedAt !== undefined
|
|
178
|
+
? `${formatTimestamp(p.lastRunEndedAt)} (${humanSeconds(secondsSince(p.lastRunEndedAt, now))} ago)`
|
|
179
|
+
: "never";
|
|
180
|
+
return [`- Next run: ${next}`, `- Last run: ${last}`, ...(p.quietHours ?? []).map(quietHoursLine)];
|
|
181
|
+
}
|
|
182
|
+
/** Render one loop's inspection payload as Markdown. `now` is injected (default Date.now())
|
|
183
|
+
* so the schedule lines stay a pure function of the payload — the test pins a fixed clock. */
|
|
184
|
+
export function renderRoleMarkdown(p, now = Date.now()) {
|
|
72
185
|
const lines = [`# tumwater role: ${p.id}`, ""];
|
|
73
|
-
lines.push(`-
|
|
186
|
+
lines.push(`- ${p.custom ? "Custom role" : "Role"}: "${p.id}" (${p.title})`);
|
|
74
187
|
const state = [p.enabled ? "enabled" : "disabled", p.paused ? "paused" : "not paused"].join(", ");
|
|
75
188
|
lines.push(`- State: ${state}`);
|
|
76
|
-
lines.push(`-
|
|
189
|
+
lines.push(`- Priority: ${p.tier === 0 ? "work" : "maintenance or observer"}`);
|
|
77
190
|
lines.push(`- Model: ${modelLine(p)} (${p.modelTier} tier)`);
|
|
191
|
+
// Context window: the resolved primary seam's declared window (the one `- Next run:`
|
|
192
|
+
// previews, not the fallback — a run on the model fallback may use a different window, the
|
|
193
|
+
// fallback's own documented state). `unknown` when it cannot be known (a non-pi seam, an
|
|
194
|
+
// unresolvable pair, a model with no declared window).
|
|
195
|
+
lines.push(`- Context window: ${p.contextWindow === null ? "unknown" : `${compactTokens(p.contextWindow)} tokens`}`);
|
|
196
|
+
// Per-provider concurrency cap (plans/provider-concurrency.md §8): name the key whose cap
|
|
197
|
+
// bounds the role's concurrent ticks, plus the cap itself. Present only while
|
|
198
|
+
// `maxConcurrentPerProvider` maps this seam's key — a cap-less default team has no
|
|
199
|
+
// resolvable key entry, so its role view renders no new line and stays byte-identical.
|
|
200
|
+
if (p.providerKey !== undefined && p.providerCap !== undefined) {
|
|
201
|
+
lines.push(`- Provider: ${p.providerKey} (cap ${p.providerCap})`);
|
|
202
|
+
}
|
|
203
|
+
// Image input (plans/image-input.md "Surfaces", part 5/5): whether this loop's own model
|
|
204
|
+
// reads images, and — when it cannot — where a tick that reads an image reroutes. Not just
|
|
205
|
+
// `yes`/`no`: a text-only loop that falls back names the target, so `tumwater role <id>`
|
|
206
|
+
// explains the team alert without running doctor. Unknown (null) renders `unknown` rather
|
|
207
|
+
// than guessing.
|
|
208
|
+
const images = p.imageInput === true
|
|
209
|
+
? "yes"
|
|
210
|
+
: p.imageInput === false
|
|
211
|
+
? p.imageReroute
|
|
212
|
+
? `no — reruns on ${p.imageReroute}`
|
|
213
|
+
: "no"
|
|
214
|
+
: "unknown";
|
|
215
|
+
lines.push(`- Images: ${images}`);
|
|
78
216
|
// Model failure fallback, part 2/2: name the off-model pair, when the episode began, and
|
|
79
217
|
// the provider-class failure that tripped it, so `tumwater role <id>` explains the state.
|
|
80
218
|
if (p.modelFallback) {
|
|
81
|
-
const pair =
|
|
219
|
+
const pair = fallbackPairLabel(p.modelFallback, agentDefault(p));
|
|
82
220
|
lines.push(`- Model fallback: ${pair} — on fallback since ${formatTimestamp(p.modelFallback.since)} (primary failing: ${p.modelFallback.reason})`);
|
|
83
221
|
}
|
|
84
222
|
if (p.fallback) {
|
|
85
|
-
const pair =
|
|
86
|
-
lines.push(`-
|
|
223
|
+
const pair = fallbackPairLabel(p.fallback, agentDefault(p));
|
|
224
|
+
lines.push(`- Over-cap model: ${pair} (${p.fallbackFree ? "free" : "priced"})`);
|
|
87
225
|
}
|
|
226
|
+
const running = chainStepLine(p);
|
|
227
|
+
if (running)
|
|
228
|
+
lines.push(running);
|
|
88
229
|
// The claude seam's billing (plans/agent-backends.md §7/§8, PLANS.md part 6d/7): absent on
|
|
89
230
|
// a pi seam, so its role view is byte-identical to before.
|
|
90
231
|
if (p.billing) {
|
|
91
232
|
const window = p.billing === "subscription" && p.billingWindow ? ` (${claudeWindowLabel(p.billingWindow)} window)` : "";
|
|
92
233
|
lines.push(`- Billing: ${p.billing === "api" ? "API" : p.billing}${window}`);
|
|
93
234
|
}
|
|
94
|
-
lines.push(`- Min
|
|
95
|
-
|
|
235
|
+
lines.push(`- Min run interval: ${p.minRunIntervalSeconds}s`);
|
|
236
|
+
if (p.instances !== undefined)
|
|
237
|
+
lines.push(`- Instances: ${p.instances}`);
|
|
238
|
+
lines.push(`- Inbox: ${p.inboxCount}`);
|
|
239
|
+
// The loop's own spend over its retained event window (role-view.ts's usage, summed from the
|
|
240
|
+
// same run_end events recentOutcomes reads): how much the loop burned on tokens and cost,
|
|
241
|
+
// with a notional suffix only when a claude-subscription seam burned non-zero notional spend
|
|
242
|
+
// (plans/agent-backends.md part 6a). Absent for a never-costly loop, so old `tumwater role`
|
|
243
|
+
// goldens are byte-identical.
|
|
244
|
+
if (p.usage) {
|
|
245
|
+
let spend = `- Spend: ${usd(p.usage.costUsd)} · ${compactTokens(p.usage.tokens)} tokens`;
|
|
246
|
+
if (p.usage.notionalCostUsd !== 0)
|
|
247
|
+
spend += ` · ${usd(p.usage.notionalCostUsd)} notional`;
|
|
248
|
+
lines.push(spend);
|
|
249
|
+
}
|
|
250
|
+
// The per-role daily cap (role-view.ts's dailyCap): today's spend against the cap and whether
|
|
251
|
+
// it holds the loop now. Absent without a cap, so old goldens are unchanged.
|
|
252
|
+
if (p.dailyCap) {
|
|
253
|
+
const dc = p.dailyCap;
|
|
254
|
+
let cap = `- Daily cap: ${usd(dc.spentUsd)} of ${usd(dc.capUsd)} spent today (${dc.over ? "holding now — no new runs until local midnight" : "not holding"})`;
|
|
255
|
+
if (dc.roleIds.length > 1)
|
|
256
|
+
cap += ` · shared by ${dc.roleIds.join(", ")}`;
|
|
257
|
+
lines.push(cap);
|
|
258
|
+
}
|
|
259
|
+
// The loop's schedule (role-view.ts's nextRunAt/backoffSeconds/lastRunEndedAt, read from
|
|
260
|
+
// the same persisted state): when it may tick again, and when it last did — the one
|
|
261
|
+
// place the team's `next run`/`last tick` vocabulary was absent. Rendered from the
|
|
262
|
+
// injected `now`, so the lines stay a pure function of the payload.
|
|
263
|
+
lines.push(...scheduleLine(p, now));
|
|
264
|
+
// The loop's recent tick outcomes (role-view.ts's recentOutcomes, read from the event log):
|
|
265
|
+
// one line per outcome so `tumwater role <id>` shows what the loop has been DOING, not just
|
|
266
|
+
// what it is. Absent (never ticked) renders nothing new, so old goldens are byte-identical.
|
|
267
|
+
if (p.recentOutcomes && p.recentOutcomes.length > 0) {
|
|
268
|
+
lines.push("", "## Recent runs", "");
|
|
269
|
+
for (const o of p.recentOutcomes)
|
|
270
|
+
lines.push(`- ${recentOutcomeLine(o)}`);
|
|
271
|
+
}
|
|
96
272
|
lines.push("");
|
|
97
273
|
lines.push(...fencedOrNone("Instructions override", p.instructions));
|
|
98
|
-
lines.push(...fencedOrNone("Find text", p.find, "none — the director is driven by its
|
|
99
|
-
lines.push(...
|
|
274
|
+
lines.push(...fencedOrNone("Find text", p.find, "none — the director is driven by its inbox, not by instructions"));
|
|
275
|
+
lines.push(...inspectBodyLines(p));
|
|
100
276
|
return lines.join("\n").trimEnd();
|
|
101
277
|
}
|