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,17 +1,17 @@
|
|
|
1
1
|
import { eventRole, eventUsage, tickSpanMs, tickStartMap } from "../events/event-read.js";
|
|
2
2
|
import { errorTextOrPlaceholder, normalizeClusterKey, poolTimeoutKey, sortedRoles, truncateExample } from "./failure-cluster.js";
|
|
3
|
-
import { resolveQueuedResult,
|
|
3
|
+
import { resolveQueuedResult, bucketMergeEvents } from "../history/history-data.js";
|
|
4
4
|
import { nonNegativeNumber, stringList } from "../files/json-object.js";
|
|
5
5
|
import { getOrCreate, groupBy, rankByCount } from "../collections.js";
|
|
6
6
|
/** How the Outcome table's results collapse for costing:
|
|
7
|
-
* "
|
|
8
|
-
* result is "error-class" — it burned agent time without
|
|
7
|
+
* "merged" made progress, "no_change" spent a tick and merged nothing, and every remaining
|
|
8
|
+
* result is "error-class" — it burned agent time without merge, whether the cause was a
|
|
9
9
|
* hard failure or the review gate. Typed as a full Record so a result added to
|
|
10
10
|
* src/tick/tick-outcome.ts and forgotten here is a compile error, like RESULT_ORDER in
|
|
11
11
|
* src/failure/failure-render.ts. */
|
|
12
|
-
const OUTCOME_CLASS = {
|
|
13
|
-
changed: "
|
|
14
|
-
queued: "
|
|
12
|
+
export const OUTCOME_CLASS = {
|
|
13
|
+
changed: "merged",
|
|
14
|
+
queued: "merged",
|
|
15
15
|
no_change: "no_change",
|
|
16
16
|
refused: "no_change",
|
|
17
17
|
skipped: "no_change",
|
|
@@ -28,29 +28,39 @@ const OUTCOME_CLASS = {
|
|
|
28
28
|
/** A row's summed wall-clock span across its outcome classes — the sort key that puts the
|
|
29
29
|
* burning role first, the way the usage report ranks its cost lines. */
|
|
30
30
|
function sumMs(classes) {
|
|
31
|
-
return classes.
|
|
31
|
+
return classes.merged.ms + classes.no_change.ms + classes.error.ms;
|
|
32
32
|
}
|
|
33
33
|
/** The loss-cause cut, like ERROR_TOP above: the five most expensive causes by time. */
|
|
34
34
|
const LOSS_TOP = 5;
|
|
35
35
|
/** The raw results whose loss attributes to a cluster — an error, an abort, a quiet kill, or
|
|
36
|
-
* a red-main skip, each carrying its own message on the
|
|
36
|
+
* a red-main skip, each carrying its own message on the run_end (main_red's is the
|
|
37
37
|
* redMainMessage the error-clusters section already itemizes, BUGS.md 2026-10-07). A queued
|
|
38
|
-
* tick's
|
|
38
|
+
* tick's merge outcomes are owned by the joins below instead: review_error through its
|
|
39
39
|
* matching `review_failed` message, review-rejected through its review reasons, and
|
|
40
40
|
* merge_conflict/merge_blocked by the result itself (BUGS.md 2026-10-10). (A raw
|
|
41
|
-
* `user_aborted`
|
|
41
|
+
* `user_aborted` run_end is also error-class but is not in this set, so it stays out of the
|
|
42
42
|
* ranking.) */
|
|
43
43
|
const CLUSTERED_RESULTS = new Set(["error", "aborted", "quiet_killed", "main_red"]);
|
|
44
|
-
/** The newest event for `role` inside a
|
|
44
|
+
/** The newest event for `role` inside a merge attempt's pin→outcome window — the attempt no
|
|
45
45
|
* other can occupy. Shared by the review-rejected and review_error cause joins, so the two
|
|
46
46
|
* agree on which event belongs to the attempt (BUGS.md 2026-10-10: the window, not the pin's
|
|
47
|
-
* sha, is the match, since every
|
|
47
|
+
* sha, is the match, since every verification rebases its pin before the gate). */
|
|
48
48
|
function newestAttemptEvent(eventsByLoop, role, attempt) {
|
|
49
49
|
return (eventsByLoop.get(role) ?? [])
|
|
50
50
|
.filter((e) => e.ts >= attempt.pin.ts && e.ts <= attempt.outcome.ts)
|
|
51
51
|
.at(-1);
|
|
52
52
|
}
|
|
53
|
-
/** The
|
|
53
|
+
/** The pooled cluster key and truncated example a failure-message source's span prices under:
|
|
54
|
+
* the shared placeholder when the source carries no message, the pooled render
|
|
55
|
+
* (poolTimeoutKey(normalizeClusterKey)) of the message's cluster — so a review timeout pools
|
|
56
|
+
* into the same `timed out after <dur>` cause the error-cluster section counts — and the
|
|
57
|
+
* truncated example the loss-rank cell shows. One copy of the placeholder/pool/truncate chain
|
|
58
|
+
* so the error-cluster and review-failure causes cannot drift on it (BUGS.md 2026-10-10). */
|
|
59
|
+
function pooledClusterCause(source) {
|
|
60
|
+
const text = errorTextOrPlaceholder(source);
|
|
61
|
+
return { key: poolTimeoutKey(normalizeClusterKey(text)), example: truncateExample(text) };
|
|
62
|
+
}
|
|
63
|
+
/** The time a `run_end` attests: its own `durationMs` when it carries one (every event
|
|
54
64
|
* written since 2026-09-29 does), else the start→end pairing over `starts` — the same
|
|
55
65
|
* fallback history-data.ts renders, so old events read their span the one way it can still be
|
|
56
66
|
* known. 0 when neither source has the start (rotation cut it); the fold prices no time it
|
|
@@ -61,43 +71,46 @@ function tickDurationMs(ev, starts) {
|
|
|
61
71
|
return own;
|
|
62
72
|
return tickSpanMs(ev, starts) ?? 0;
|
|
63
73
|
}
|
|
64
|
-
/** Add one tick to a {
|
|
74
|
+
/** Add one tick to a {runs, ms, costUsd} accumulator — the single home of the fold shared
|
|
65
75
|
* by the time-and-spend table's outcome cells (SpendCell) and the loss-cause drafts
|
|
66
76
|
* (LossDraft), so the two tallies cannot drift apart in what they count or price. The
|
|
67
77
|
* caller passes one event's already-computed durationMs and usage, so folding an event
|
|
68
78
|
* into both a cell and a draft parses the usage and pairs the span exactly once. */
|
|
69
79
|
function tally(target, durationMs, usage) {
|
|
70
|
-
target.
|
|
80
|
+
target.runs++;
|
|
71
81
|
target.ms += durationMs;
|
|
72
82
|
target.costUsd += usage.costUsd;
|
|
73
83
|
}
|
|
74
|
-
/** Fold the window's
|
|
84
|
+
/** Fold the window's run_ends into the time-and-spend table and the loss ranking. Pairing
|
|
75
85
|
* runs over BOTH windows' events (the caller passes the whole read), so a tick that opened
|
|
76
|
-
* in the preceding day and ended in the current one still gets its span. The
|
|
77
|
-
* join runs over that same whole read too (BUGS.md 2026-09-30): a queued
|
|
78
|
-
* through its own
|
|
79
|
-
* loop+sha — history-data.ts's exact join — so an authoring tick whose
|
|
80
|
-
* prices into the error-class cell its contract names instead of the
|
|
81
|
-
* transient queued result suggested. A
|
|
82
|
-
*
|
|
86
|
+
* in the preceding day and ended in the current one still gets its span. The merge-outcome
|
|
87
|
+
* join runs over that same whole read too (BUGS.md 2026-09-30): a queued run_end resolves
|
|
88
|
+
* through its own merge_queued pin to the later merge_done/merge_failed outcome for the same
|
|
89
|
+
* loop+sha — history-data.ts's exact join — so an authoring tick whose merge was rejected
|
|
90
|
+
* prices into the error-class cell its contract names instead of the merged cell its
|
|
91
|
+
* transient queued result suggested. A leftover-recovery run resolves to the failed attempt it
|
|
92
|
+
* re-queued (the history row's rule), so its near-zero span counts under that failure — a
|
|
93
|
+
* review timeout's cluster, say — rather than adding a run to the change's final
|
|
94
|
+
* review-rejected cause once per re-queue. A merge still in the pipeline, or an outcome event
|
|
95
|
+
* matching no pin, keeps the queued→merged reading: the conservative fallback. */
|
|
83
96
|
export function timeAndSpend(tickEvents, allEvents) {
|
|
84
97
|
const starts = tickStartMap(allEvents);
|
|
85
|
-
const emptyCell = () => ({
|
|
98
|
+
const emptyCell = () => ({ runs: 0, ms: 0, costUsd: 0 });
|
|
86
99
|
const byRole = new Map();
|
|
87
100
|
const losses = new Map();
|
|
88
|
-
// The
|
|
101
|
+
// The merge evidence rides history-data.ts's bucketMergeEvents — the same buckets
|
|
89
102
|
// history's rows read — so the digest's join cannot drift from the rows' join.
|
|
90
|
-
const {
|
|
91
|
-
const rejectedByLoop = groupBy(allEvents.filter((e) => e.type === "review_rejected"), (e) => e.
|
|
92
|
-
// A review_error
|
|
103
|
+
const { mergeQueuedByLoop, outcomeByLoop } = bucketMergeEvents(allEvents);
|
|
104
|
+
const rejectedByLoop = groupBy(allEvents.filter((e) => e.type === "review_rejected"), (e) => e.source);
|
|
105
|
+
// A review_error merge kept its pin but left no message on its own merge_failed; the
|
|
93
106
|
// matching review_failed event for the same loop+sha carries it, so the span prices under
|
|
94
107
|
// the same cluster key the digest's review-failure section already counts.
|
|
95
|
-
const reviewFailedByLoop = groupBy(allEvents.filter((e) => e.type === "review_failed"), (e) => e.
|
|
108
|
+
const reviewFailedByLoop = groupBy(allEvents.filter((e) => e.type === "review_failed"), (e) => e.source);
|
|
96
109
|
const deadlineKilledByLoop = groupBy(allEvents.filter((e) => e.type === "build_check" &&
|
|
97
110
|
e.status === "failed" &&
|
|
98
|
-
(e.scope === "
|
|
99
|
-
typeof e.timeoutMs === "number"), (e) => e.
|
|
100
|
-
// Queued
|
|
111
|
+
(e.scope === "merge" || e.scope === "batch") &&
|
|
112
|
+
typeof e.timeoutMs === "number"), (e) => e.source);
|
|
113
|
+
// Queued run_ends resolve newest-first so each claims the newest pin at or before its end
|
|
101
114
|
// that no newer tick has claimed — resolveQueuedResult's exact claim rule, shared with the
|
|
102
115
|
// history rows. tickEvents is oldest-first, hence the backward walk.
|
|
103
116
|
const claimTop = new Map();
|
|
@@ -106,7 +119,7 @@ export function timeAndSpend(tickEvents, allEvents) {
|
|
|
106
119
|
const ev = tickEvents[i];
|
|
107
120
|
if (ev.result !== "queued")
|
|
108
121
|
continue;
|
|
109
|
-
const joined = resolveQueuedResult(ev,
|
|
122
|
+
const joined = resolveQueuedResult(ev, mergeQueuedByLoop, outcomeByLoop, claimTop, new Map(), null);
|
|
110
123
|
if (joined !== null)
|
|
111
124
|
resolved.set(ev, joined);
|
|
112
125
|
}
|
|
@@ -127,37 +140,46 @@ export function timeAndSpend(tickEvents, allEvents) {
|
|
|
127
140
|
const usage = eventUsage(ev);
|
|
128
141
|
const durationMs = tickDurationMs(ev, starts);
|
|
129
142
|
const row = getOrCreate(byRole, role, () => ({
|
|
130
|
-
|
|
143
|
+
merged: emptyCell(),
|
|
131
144
|
no_change: emptyCell(),
|
|
132
145
|
error: emptyCell(),
|
|
133
146
|
}));
|
|
134
147
|
tally(row[cls], durationMs, usage);
|
|
135
148
|
// Loss causes: a clustered failure's cluster owns its time, a no_change's role does, and
|
|
136
|
-
// a
|
|
137
|
-
// unrepresentable as an error cluster, since the
|
|
138
|
-
// the example is the matching review_rejected's first reason, matched by
|
|
149
|
+
// a merge-gate rejection prices its authoring span under a per-role cause of its own —
|
|
150
|
+
// unrepresentable as an error cluster, since the merge_failed event carries no message;
|
|
151
|
+
// the example is the matching review_rejected's first reason, matched by merge attempt.
|
|
139
152
|
let key = null;
|
|
140
153
|
let kind = "error-cluster";
|
|
141
154
|
let example = "";
|
|
142
|
-
if (
|
|
155
|
+
if (ev.result === "refused") {
|
|
156
|
+
// A refusal is a deliberate objection that blocks its entry, not quiet idleness — the
|
|
157
|
+
// reason live in the event + lastSummary (src/verdict/refusal.ts). Rank it as its own
|
|
158
|
+
// per-role cause so the digest surfaces the objection instead of pricing the tick
|
|
159
|
+
// exactly like a silently idle one; the branch sits BEFORE the no_change one because a
|
|
160
|
+
// refused tick is also no_change-class in the outcome table. The example rides the
|
|
161
|
+
// refusal's summary the way a review-rejected cause rides its reason.
|
|
162
|
+
key = `refused\u0000${role}`;
|
|
163
|
+
kind = "refused";
|
|
164
|
+
example = typeof ev.summary === "string" ? truncateExample(ev.summary) : "";
|
|
165
|
+
}
|
|
166
|
+
else if (cls === "no_change") {
|
|
143
167
|
key = `no_change\u0000${role}`;
|
|
144
168
|
kind = "no_change";
|
|
145
169
|
}
|
|
146
170
|
else if (CLUSTERED_RESULTS.has(String(ev.result))) {
|
|
147
|
-
// A clustered result with no error text still owns its time: an abort's
|
|
171
|
+
// A clustered result with no error text still owns its time: an abort's run_end never
|
|
148
172
|
// carries one (planTickStart clears s.lastError, tick-finalize logs error: undefined),
|
|
149
173
|
// and a malformed error tick can lose it too, so both take the same placeholder cause the
|
|
150
174
|
// error-clusters section already itemizes rather than dropping the span itemized nowhere.
|
|
151
|
-
|
|
152
|
-
key = poolTimeoutKey(normalizeClusterKey(text));
|
|
153
|
-
example = truncateExample(text);
|
|
175
|
+
({ key, example } = pooledClusterCause(ev.error));
|
|
154
176
|
}
|
|
155
177
|
else if (ev.result === "queued" && resolvedOutcome?.result === "rejected") {
|
|
156
178
|
key = `review-rejected\u0000${role}`;
|
|
157
179
|
kind = "review-rejected";
|
|
158
|
-
// Match the
|
|
180
|
+
// Match the merge ATTEMPT, not the pin's sha: every verification rebases its pin onto main
|
|
159
181
|
// before the gate (syncPinToMain), so `review_rejected.head` is the rebased commit while
|
|
160
|
-
// `
|
|
182
|
+
// `merge_queued` keeps the authored one — exact-sha equality names no reason for a rebased
|
|
161
183
|
// rejection (BUGS.md 2026-10-10). The rejection for this attempt is the one inside the
|
|
162
184
|
// claimed pin and its outcome, the window no other attempt can occupy.
|
|
163
185
|
const rej = newestAttemptEvent(rejectedByLoop, role, resolvedOutcome);
|
|
@@ -170,34 +192,32 @@ export function timeAndSpend(tickEvents, allEvents) {
|
|
|
170
192
|
// message the review-failure section already clusters. Price the authoring span under
|
|
171
193
|
// that cluster's key — a review timeout pools into the shared `timed out after <dur>`
|
|
172
194
|
// cause via poolTimeoutKey — and fall back to the shared placeholder when no event is
|
|
173
|
-
// visible, so the span is itemized rather than hidden. The match keys on the
|
|
174
|
-
// ATTEMPT, not the pin's sha: every
|
|
175
|
-
// (syncPinToMain), so `review_failed.head` is the rebased commit while `
|
|
176
|
-
// keeps the authored one — exact-sha equality missed every rebased
|
|
195
|
+
// visible, so the span is itemized rather than hidden. The match keys on the merge
|
|
196
|
+
// ATTEMPT, not the pin's sha: every verification rebases its pin onto main before the gate
|
|
197
|
+
// (syncPinToMain), so `review_failed.head` is the rebased commit while `merge_queued`
|
|
198
|
+
// keeps the authored one — exact-sha equality missed every rebased verification (BUGS.md
|
|
177
199
|
// 2026-10-10). The review failure for this attempt is the one that falls inside the
|
|
178
200
|
// claimed pin and its outcome, the window no other attempt can occupy.
|
|
179
201
|
const failed = newestAttemptEvent(reviewFailedByLoop, role, resolvedOutcome);
|
|
180
|
-
|
|
181
|
-
key = poolTimeoutKey(normalizeClusterKey(text));
|
|
182
|
-
example = truncateExample(text);
|
|
202
|
+
({ key, example } = pooledClusterCause(failed?.message));
|
|
183
203
|
}
|
|
184
204
|
else if (ev.result === "queued" &&
|
|
185
205
|
(resolvedOutcome?.result === "merge_conflict" || resolvedOutcome?.result === "merge_blocked")) {
|
|
186
|
-
// The
|
|
187
|
-
// authoring span burned. The `
|
|
206
|
+
// The merge failed to merge (a conflict, or a blocked fast-forward/rebase): this
|
|
207
|
+
// authoring span burned. The `merge_failed` event carries no message — writeMergeOutcome
|
|
188
208
|
// logs only result/commit/durationMs/usage — so the cause is named by its own result and
|
|
189
|
-
// takes the shared no-text placeholder, the same fallback a message-less aborted
|
|
209
|
+
// takes the shared no-text placeholder, the same fallback a message-less aborted run_end
|
|
190
210
|
// uses, rather than dropping a span the error-class cell already priced (BUGS.md
|
|
191
211
|
// 2026-10-10).
|
|
192
212
|
key = `${resolvedOutcome.result}\u0000${role}`;
|
|
193
213
|
kind = resolvedOutcome.result === "merge_conflict" ? "merge-conflict" : "merge-blocked";
|
|
194
214
|
example = errorTextOrPlaceholder(undefined);
|
|
195
|
-
// A merge-scope check the harness killed at its own deadline is the
|
|
215
|
+
// A merge-scope check the harness killed at its own deadline is the team's load, not a
|
|
196
216
|
// verdict on the change: split it into its own cause so the ranking names it.
|
|
197
217
|
const killed = newestAttemptEvent(deadlineKilledByLoop, role, resolvedOutcome);
|
|
198
218
|
if (kind === "merge-blocked" && killed) {
|
|
199
219
|
key = `merge_blocked_deadline\u0000${role}`;
|
|
200
|
-
example = `${String(killed.scope)} build check killed at its ${Number(killed.timeoutMs) / 1000}s deadline (
|
|
220
|
+
example = `${String(killed.scope)} build check killed at its ${Number(killed.timeoutMs) / 1000}s deadline (tumwater's own timeout, not a test failure)`;
|
|
201
221
|
}
|
|
202
222
|
}
|
|
203
223
|
if (key === null)
|
|
@@ -209,7 +229,7 @@ export function timeAndSpend(tickEvents, allEvents) {
|
|
|
209
229
|
roles: new Set(),
|
|
210
230
|
lastSeen: ev.ts,
|
|
211
231
|
example,
|
|
212
|
-
|
|
232
|
+
runs: 0,
|
|
213
233
|
ms: 0,
|
|
214
234
|
costUsd: 0,
|
|
215
235
|
};
|
|
@@ -230,11 +250,11 @@ export function timeAndSpend(tickEvents, allEvents) {
|
|
|
230
250
|
kind: d.kind,
|
|
231
251
|
roles: sortedRoles(d.roles),
|
|
232
252
|
example: d.example,
|
|
233
|
-
|
|
253
|
+
runs: d.runs,
|
|
234
254
|
ms: d.ms,
|
|
235
255
|
costUsd: d.costUsd,
|
|
236
256
|
}))
|
|
237
|
-
.sort((a, b) => b.ms - a.ms || b.
|
|
257
|
+
.sort((a, b) => b.ms - a.ms || b.runs - a.runs || a.example.localeCompare(b.example));
|
|
238
258
|
const lossCauses = rankedLosses.slice(0, LOSS_TOP);
|
|
239
259
|
return { timeSpend, lossCauses, lossCausesHidden: Math.max(0, rankedLosses.length - LOSS_TOP) };
|
|
240
260
|
}
|
|
@@ -2,17 +2,29 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { errCode } from "../errno.js";
|
|
4
4
|
/** The directory-of-timestamped-files queue convention shared by the director's prompt inbox
|
|
5
|
-
* (inbox.ts) and the durable
|
|
5
|
+
* (inbox.ts) and the durable merge queue (merge-queue.ts): one file per entry, ordered by
|
|
6
6
|
* filename across processes and listed by a directory read. Split out because both queues
|
|
7
7
|
* would otherwise carry their own copy of the listing, naming, and ENOENT-tolerant removal
|
|
8
8
|
* rules — exactly the parts that must not drift between the two. */
|
|
9
9
|
/** Every entry file under `dir` whose name ends in `ext`, oldest first by filename sort. A
|
|
10
10
|
* missing directory reads as an empty queue, like every reader in both queues. */
|
|
11
11
|
export function listQueueFiles(dir, ext) {
|
|
12
|
-
|
|
12
|
+
let entries;
|
|
13
|
+
try {
|
|
14
|
+
entries = fs.readdirSync(dir);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
// The "no queue here" cases — a directory removed between the caller's existence check
|
|
18
|
+
// and this read (ENOENT), or a regular file squatting at the path (ENOTDIR) — read the
|
|
19
|
+
// same empty list a missing directory does, per the contract. Any other error (EACCES:
|
|
20
|
+
// a real but unreadable directory) is a genuine failure the caller must see, never a
|
|
21
|
+
// silently empty queue that would hide queued work.
|
|
22
|
+
const code = errCode(err);
|
|
23
|
+
if (code !== "ENOENT" && code !== "ENOTDIR")
|
|
24
|
+
throw err;
|
|
13
25
|
return [];
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
}
|
|
27
|
+
return entries
|
|
16
28
|
.filter((f) => f.endsWith(ext))
|
|
17
29
|
.sort()
|
|
18
30
|
.map((f) => path.join(dir, f));
|
package/dist/src/files/files.js
CHANGED
|
@@ -27,9 +27,9 @@ export function statOrNull(file) {
|
|
|
27
27
|
* plus matching size and, when the caller tracks it, mtime. The one home of the freshness
|
|
28
28
|
* check shared by files/stat-cache.ts's cachedByStat, events/events.ts's appended-shape memo
|
|
29
29
|
* (`mtimeMs` omitted — it decides "our own append left this log clean" from identity and size
|
|
30
|
-
* alone), report/report-
|
|
30
|
+
* alone), report/report-fold.ts's fold cache, gates/maintenance-quota.ts's window counter,
|
|
31
31
|
* and failure/failure-data.ts's digest cache. Callers whose cached field is a byte offset
|
|
32
|
-
* rather than a size (report-
|
|
32
|
+
* rather than a size (report-fold, maintenance-quota) pass that offset as `size`, since a
|
|
33
33
|
* fully-folded log's offset equals its length. */
|
|
34
34
|
export function sameStatIdentity(st, prev) {
|
|
35
35
|
if (st.dev !== prev.dev || st.ino !== prev.ino || st.size !== prev.size)
|
|
@@ -130,6 +130,18 @@ export function unlinkAllMissingTolerant(files) {
|
|
|
130
130
|
/** The PATH a binary lookup searches by default: the process PATH, or the empty string when
|
|
131
131
|
* it is unset (findOnPath then finds no directories). The one home of the default so every
|
|
132
132
|
* caller that exposes a `pathEnv` override falls back the same way. */
|
|
133
|
+
/** Expand a `~` at the start of a directory against the home dir, for an env var that points
|
|
134
|
+
* at a subdirectory of home — e.g. `$PI_CODING_AGENT_DIR` (pi-models.ts's piAgentDir) and
|
|
135
|
+
* `$CLAUDE_CONFIG_DIR` (claude-backend.ts's claudeConfigDir), which share this exact rule. The
|
|
136
|
+
* empty case is the caller's job (each returns its own default subpath); this helper only maps
|
|
137
|
+
* the bare `~` → home and `~/x` → home/x, otherwise the literal dir. */
|
|
138
|
+
export function expandHomeTilde(dir, homeDir) {
|
|
139
|
+
if (dir === "~")
|
|
140
|
+
return homeDir;
|
|
141
|
+
if (dir.startsWith("~/"))
|
|
142
|
+
return path.join(homeDir, dir.slice(2));
|
|
143
|
+
return dir;
|
|
144
|
+
}
|
|
133
145
|
export function envPath() {
|
|
134
146
|
return process.env.PATH ?? "";
|
|
135
147
|
}
|
|
@@ -247,7 +259,7 @@ export function subdirectoryNames(dir, keep) {
|
|
|
247
259
|
* name carries the pid because several processes can write at once (CLI, TUI, GUI, a loop's
|
|
248
260
|
* re-queue); on failure the tmp is removed and the error rethrown, leaving the target untouched.
|
|
249
261
|
* The harness's JSON writer (writeJsonAtomic in src/files/json-files.ts) and the config write-back
|
|
250
|
-
* (writeBytesAtomic's caller,
|
|
262
|
+
* (writeBytesAtomic's caller, merge-git.ts's restoreConfigBytes) write through this, so the
|
|
251
263
|
* pid-tmp and no-torn-file contract lives in exactly one place. */
|
|
252
264
|
function writeAtomic(file, data) {
|
|
253
265
|
ensureParentDir(file);
|
|
@@ -266,8 +278,8 @@ export function writeTextAtomic(file, text) {
|
|
|
266
278
|
writeAtomic(file, text);
|
|
267
279
|
}
|
|
268
280
|
/** Write raw `data` bytes to `file` — the byte entry point to writeAtomic's tmp+rename, for
|
|
269
|
-
* writers that must preserve the source bytes exactly (
|
|
270
|
-
* writes back the live tumwater.json bytes
|
|
281
|
+
* writers that must preserve the source bytes exactly (merge-git.ts's restoreConfigBytes
|
|
282
|
+
* writes back the live tumwater.json bytes the merge deleted). */
|
|
271
283
|
export function writeBytesAtomic(file, data) {
|
|
272
284
|
writeAtomic(file, data);
|
|
273
285
|
}
|
|
@@ -302,7 +314,7 @@ export function removeTree(dir) {
|
|
|
302
314
|
* A subdirectory that cannot be listed — without read permission, or removed by a concurrent
|
|
303
315
|
* pass between the parent's readdir and this one — is skipped, the same vanish-tolerant policy
|
|
304
316
|
* the per-file stat/delete below applies. This cleanup runs inside the orchestrator's poll loop
|
|
305
|
-
* (RetentionPruner.poll), so a readdir throw escaping here would end the whole
|
|
317
|
+
* (RetentionPruner.poll), so a readdir throw escaping here would end the whole team; one
|
|
306
318
|
* unreadable session directory must cost at most its own contents' retention pass.
|
|
307
319
|
*
|
|
308
320
|
* After a subdirectory is walked, an empty one whose own mtime is past the cutoff is removed:
|
|
@@ -16,7 +16,7 @@ export function isJsonObject(value) {
|
|
|
16
16
|
* when it is an array and [] otherwise — the one home of the "an event/record field is the
|
|
17
17
|
* list or it is nothing" read every formatter applies (event-format.ts's review_rejected
|
|
18
18
|
* reasons, dep_install packages, config_changed keys; src/failure/failure-state-change.ts's
|
|
19
|
-
* config_changed keys; time-spend.ts's review-rejection reasons;
|
|
19
|
+
* config_changed keys; time-spend.ts's review-rejection reasons; team-state.ts's paused-roles
|
|
20
20
|
* list). A torn or hand-edited line
|
|
21
21
|
* never throws and never renders a placeholder: the field reads as absent, and the caller's
|
|
22
22
|
* empty-list branch fires. Foreign entries inside an array are dropped for the same reason —
|
|
@@ -38,7 +38,7 @@ export function stringList(value) {
|
|
|
38
38
|
* autoRestartRecord (lastAt → null); build/build-info.ts's readBuildInfo (builtAt → 0);
|
|
39
39
|
* failure/failure-state-change.ts's model_fallback_ended (durationMs → 0) and build_stale
|
|
40
40
|
* renderer (aheadCommits → 0); gates/maintenance-quota.ts's foldLine and seed (ev.ts → null);
|
|
41
|
-
*
|
|
41
|
+
* team/orchestrator-info.ts's isFiniteNumber (v → undefined); team/team-state.ts's
|
|
42
42
|
* standingMarker, pausedRoles, and standingRolesUntil (until → undefined);
|
|
43
43
|
* operator/operator-requests.ts's consumeWakeRequest (notBeforeMs → undefined);
|
|
44
44
|
* git/slots-state.ts's readSlotRecord (pinnedAt, lastReleasedAt → null); git/worktree-use.ts's
|
|
@@ -60,7 +60,7 @@ export function finiteNumber(value, fallback) {
|
|
|
60
60
|
* previous context, output and its peak floor → 0), time-spend.ts's tickDurationMs (an event's
|
|
61
61
|
* durationMs → null, the "no own duration" marker), pi-extension/context-budget.ts's
|
|
62
62
|
* contextNote (usedTokens, usedWindow → null), pi-extension/context-shake.ts's percentOf
|
|
63
|
-
* (tokens, contextWindow → null),
|
|
63
|
+
* (tokens, contextWindow → null), merge/merge-queue.ts's isMergeEntry (tick, enqueuedAt,
|
|
64
64
|
* revisionRound → null), git/slots-state.ts's isSlotLease (since, pid → null); and
|
|
65
65
|
* agent/claude/claude-stream.ts's canonicalUsage (each wire usage
|
|
66
66
|
* number → 0) and its result/retry/rate-limit reads (api_error_status, attempt,
|
|
@@ -92,3 +92,18 @@ export function parseJsonObject(text) {
|
|
|
92
92
|
export function objectOrEmpty(value) {
|
|
93
93
|
return isJsonObject(value) ? value : {};
|
|
94
94
|
}
|
|
95
|
+
/** Assemble a stream translator's canonical usage block from the four already-validated non-
|
|
96
|
+
* negative counts and the USD cost — the one home of the shape both the claude and opencode
|
|
97
|
+
* translators (agent/claude/claude-stream.ts, agent/opencode/opencode-stream.ts) build, so the
|
|
98
|
+
* `totalTokens` sum and the `cost.total` envelope cannot drift between backends. The wire-
|
|
99
|
+
* specific field-name reads stay in each translator; this owns only the identical assembly. */
|
|
100
|
+
export function canonicalUsageBlock(input, cacheRead, cacheWrite, output, costTotal) {
|
|
101
|
+
return {
|
|
102
|
+
input,
|
|
103
|
+
cacheRead,
|
|
104
|
+
cacheWrite,
|
|
105
|
+
output,
|
|
106
|
+
totalTokens: input + cacheRead + cacheWrite + output,
|
|
107
|
+
cost: { total: costTotal },
|
|
108
|
+
};
|
|
109
|
+
}
|
package/dist/src/files/tail.js
CHANGED
|
@@ -17,7 +17,7 @@ import { piLogPath } from "../paths.js";
|
|
|
17
17
|
* re-read next poll once its writer has written the newline instead of being parsed torn
|
|
18
18
|
* or lost. The tail primitives here build on it (withTail, followFile); outside this
|
|
19
19
|
* module it is called directly by src/ui/progress-data.ts, src/ui/transcript-tail.ts, and
|
|
20
|
-
* the report fold in src/report/report-
|
|
20
|
+
* the report fold in src/report/report-fold.ts. */
|
|
21
21
|
export function readCompleteLines(file, offset, size) {
|
|
22
22
|
const len = size - offset;
|
|
23
23
|
if (len <= 0)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** New-project bootstrap (plans/work-ratio.md, "New-project bootstrap"; part 2/2): a fresh
|
|
2
3
|
* project seeds `bootstrap.untilPlansDone` in tumwater.json (part 1/2), and this gate holds
|
|
3
4
|
* every maintenance loop until that many plans have moved to PLANS.md's `## Done`. Only plan,
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
* may let a later poll retry. Removing `bootstrap` from config (live
|
|
14
15
|
* reload) also ends it, without a latch. Both the latch write and the completion event are
|
|
15
16
|
* best-effort, like every sibling gate: the poll runs inside the orchestrator's catch-less
|
|
16
|
-
* loop, so an unwritable repo must degrade (the hold still lifts) rather than end the
|
|
17
|
+
* loop, so an unwritable repo must degrade (the hold still lifts) rather than end the team.
|
|
17
18
|
*
|
|
18
19
|
* Shape, like every gate in the family (src/gates/gate-polls.ts): the read-only verdict (and
|
|
19
20
|
* the pure hold predicate) is separated from the edge-triggered bookkeeping so both the
|
|
@@ -24,7 +25,7 @@ import fs from "node:fs";
|
|
|
24
25
|
import { donePlans, openBugs } from "../backlog/backlog.js";
|
|
25
26
|
import { bootstrapLatchPath } from "../paths.js";
|
|
26
27
|
import { writeJsonAtomic } from "../files/json-files.js";
|
|
27
|
-
import { logEventBestEffort } from "../events/events.js";
|
|
28
|
+
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
28
29
|
import { errorMessage, stripTerminalControls } from "../text/text.js";
|
|
29
30
|
import { BUGFIX_ROLE, DIRECTOR_ROLE } from "../roles/roles.js";
|
|
30
31
|
import { baseRoleOf } from "../roles/loop-ids.js";
|
|
@@ -76,10 +77,10 @@ export function pollBootstrapGate(root, state, runners, config, now) {
|
|
|
76
77
|
catch (err) {
|
|
77
78
|
// A terminal boundary: the error can name a path carrying control characters. Sanitize
|
|
78
79
|
// like cli-output's say()/fail(), so the fallback line cannot drive the operator's shell.
|
|
79
|
-
process.stderr.write(`tumwater:
|
|
80
|
+
process.stderr.write(`tumwater: could not write bootstrap latch: ${stripTerminalControls(errorMessage(err))}\n`);
|
|
80
81
|
}
|
|
81
82
|
logEventBestEffort(root, {
|
|
82
|
-
|
|
83
|
+
source: TUMWATER_SOURCE,
|
|
83
84
|
type: "bootstrap_complete",
|
|
84
85
|
plansDone: status.plansDone,
|
|
85
86
|
untilPlansDone: status.untilPlansDone,
|