tumwater 0.4.4 → 0.4.6
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 +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-run.js +1 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +11 -8
- package/dist/src/agent/subscription-account.js +68 -3
- package/dist/src/agent/write-guard.js +50 -13
- package/dist/src/backlog/backlog-eligibility.js +8 -4
- package/dist/src/backlog/backlog-index.js +2 -2
- package/dist/src/backlog/backlog-md.js +45 -19
- package/dist/src/backlog/backlog-release.js +3 -11
- package/dist/src/backlog/backlog-write.js +19 -11
- package/dist/src/baseline/main-baseline.js +47 -72
- package/dist/src/baseline/main-guardrails.js +11 -7
- package/dist/src/baseline/main-red.js +171 -19
- package/dist/src/baseline/main-watch.js +71 -24
- package/dist/src/budget/budget-fallback-view.js +96 -28
- package/dist/src/build/build-check-detect.js +22 -3
- package/dist/src/build/build-check-events.js +25 -5
- package/dist/src/build/build-check-report.js +40 -4
- package/dist/src/build/build-check-rerun.js +62 -0
- package/dist/src/build/build-check-scoped.js +13 -2
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +4 -0
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/build/host-sleep.js +9 -0
- package/dist/src/change/change-render.js +2 -1
- package/dist/src/cli/cli-args.js +48 -3
- package/dist/src/cli/cli-command-args.js +12 -4
- package/dist/src/cli/cli-flag-specs.js +5 -3
- package/dist/src/cli/cli-init.js +5 -1
- package/dist/src/cli/cli-log-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +31 -15
- package/dist/src/cli/cli-review-commands.js +3 -3
- package/dist/src/cli/cli-role-command.js +338 -244
- package/dist/src/cli/cli-start.js +10 -2
- package/dist/src/cli/completion.js +6 -2
- package/dist/src/cli/help.js +6 -3
- package/dist/src/cli.js +112 -140
- package/dist/src/collections.js +21 -0
- package/dist/src/concurrency/check-permit.js +30 -3
- package/dist/src/concurrency/lock.js +73 -14
- package/dist/src/config/config-editable-keys.js +9 -0
- package/dist/src/config/config-fallbacks.js +16 -5
- package/dist/src/config/config-schema.js +3 -3
- package/dist/src/config/config-validation.js +2 -0
- package/dist/src/config/config-views.js +55 -33
- package/dist/src/config/config-write.js +76 -16
- package/dist/src/config/config.js +12 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +33 -1
- package/dist/src/doctor/doctor-backlog.js +6 -8
- package/dist/src/doctor/doctor-check-names.js +2 -0
- package/dist/src/doctor/doctor-checks.js +13 -6
- package/dist/src/doctor/doctor-claude-checks.js +6 -13
- package/dist/src/doctor/doctor-codex-checks.js +2 -2
- package/dist/src/doctor/doctor-contention-checks.js +7 -6
- package/dist/src/doctor/doctor-fallback-checks.js +37 -2
- package/dist/src/doctor/doctor-holds.js +7 -10
- package/dist/src/doctor/doctor-model-checks.js +16 -1
- package/dist/src/doctor/doctor-opencode-checks.js +2 -2
- package/dist/src/doctor/doctor-orphans.js +103 -27
- package/dist/src/doctor/doctor-project-checks.js +43 -26
- package/dist/src/doctor/doctor.js +5 -3
- package/dist/src/events/event-format.js +113 -136
- package/dist/src/events/event-labels.js +3 -1
- package/dist/src/events/events.js +3 -3
- package/dist/src/failure/failure-data.js +6 -4
- package/dist/src/failure/failure-render.js +1 -0
- package/dist/src/failure/failure-state-change.js +14 -4
- package/dist/src/failure/failure-time-spend.js +1 -0
- package/dist/src/files/file-queue.js +32 -19
- package/dist/src/files/files.js +2 -2
- package/dist/src/files/json-object.js +3 -2
- package/dist/src/gates/budget-gates.js +37 -2
- package/dist/src/gates/gate-polls.js +8 -7
- package/dist/src/gates/{maintenance-quota.js → maintenance-quota-gates.js} +1 -1
- package/dist/src/gates/startup-gates.js +18 -1
- package/dist/src/git/git.js +8 -0
- package/dist/src/git/slot-salvage.js +6 -0
- package/dist/src/git/worktree-pool.js +18 -5
- package/dist/src/gui/gui-admin-endpoints.js +11 -1
- package/dist/src/gui/gui-args.js +2 -2
- package/dist/src/gui/gui-backlog-endpoints.js +2 -1
- package/dist/src/gui/gui-history-endpoints.js +7 -7
- package/dist/src/gui/gui-operator-commands.js +2 -3
- package/dist/src/history/history.js +12 -8
- package/dist/src/{run → history}/run-detail-data.js +2 -2
- package/dist/src/{run → history}/run-detail-render.js +2 -2
- package/dist/src/{run → history}/run-detail.js +2 -2
- package/dist/src/inbox/director-request.js +92 -0
- package/dist/src/inbox/inbox-top.js +2 -1
- package/dist/src/merge/merge-backlog-conflicts.js +33 -11
- package/dist/src/merge/merge-batch.js +22 -235
- package/dist/src/merge/merge-cancel.js +2 -1
- package/dist/src/merge/merge-core.js +26 -22
- package/dist/src/merge/merge-drain.js +116 -101
- package/dist/src/merge/merge-pipeline.js +11 -8
- package/dist/src/merge/merge-post-review-drain.js +8 -2
- package/dist/src/merge/merge-questions.js +3 -3
- package/dist/src/merge/merge-queue-render.js +2 -2
- package/dist/src/merge/merge-queue.js +9 -1
- package/dist/src/merge/merge-requeue.js +67 -26
- package/dist/src/merge/merge-slot.js +41 -10
- package/dist/src/merge/merge-to-main.js +20 -165
- package/dist/src/merge/merge-top.js +2 -1
- package/dist/src/merge/merge-verify.js +18 -5
- package/dist/src/merge/merge-withdrawn.js +71 -0
- package/dist/src/model-check/model-check-gate.js +206 -0
- package/dist/src/model-check/model-check-probe.js +109 -0
- package/dist/src/model-check/model-check.js +101 -0
- package/dist/src/{run → observer}/ci-parity.js +15 -2
- package/dist/src/operator/operator-commands.js +8 -25
- package/dist/src/operator/operator-revert.js +1 -1
- package/dist/src/orchestrator/orchestrator-launch.js +6 -1
- package/dist/src/orchestrator/orchestrator-poll.js +32 -5
- package/dist/src/orchestrator/orchestrator-scheduling.js +7 -7
- package/dist/src/orchestrator/orchestrator.js +74 -13
- package/dist/src/paths.js +41 -3
- package/dist/src/pi/pi-run-result.js +16 -0
- package/dist/src/pi/pi-watchdogs.js +15 -4
- package/dist/src/process/process.js +24 -0
- package/dist/src/prompt/gate-notes.js +10 -4
- package/dist/src/prompt/gate-prompts.js +19 -15
- package/dist/src/prompt/principles.js +13 -3
- package/dist/src/prompt/prompt-followup.js +14 -5
- package/dist/src/prompt/prompt.js +11 -0
- package/dist/src/redeploy/redeploy-probes.js +53 -18
- package/dist/src/redeploy/redeploy-target.js +100 -0
- package/dist/src/redeploy/redeployer.js +135 -70
- package/dist/src/report/report-main-red.js +2 -2
- package/dist/src/report/report-render.js +2 -2
- package/dist/src/report/report.js +2 -2
- package/dist/src/review/known-flakes.js +105 -42
- package/dist/src/review/post-merge-review-accept.js +2 -1
- package/dist/src/review/post-merge-review.js +46 -34
- package/dist/src/review/review-precheck.js +72 -28
- package/dist/src/review/review-verdict.js +25 -17
- package/dist/src/roles/role-catalog.js +23 -7
- package/dist/src/roles/role-render.js +158 -83
- package/dist/src/roles/role-view.js +297 -75
- package/dist/src/roles/roles.js +1 -1
- package/dist/src/run/run-apply.js +3 -8
- package/dist/src/run/run-dismiss.js +2 -2
- package/dist/src/run/run-finalize.js +4 -0
- package/dist/src/run/run-prompt.js +4 -4
- package/dist/src/run/run-stage.js +7 -1
- package/dist/src/run/run-timing.js +2 -2
- package/dist/src/run/run-usage.js +9 -1
- package/dist/src/run/run-verdict.js +80 -52
- package/dist/src/runner/runner-leftover.js +48 -1
- package/dist/src/runner/runner-outcome.js +1 -2
- package/dist/src/runner/runner-pi.js +6 -0
- package/dist/src/runner/runner-recovery.js +5 -4
- package/dist/src/runner/runner-revision.js +2 -1
- package/dist/src/runner/runner-run.js +165 -110
- package/dist/src/runner/runner-state-load.js +115 -53
- package/dist/src/runner/runner-state.js +3 -2
- package/dist/src/runner/runner.js +39 -20
- package/dist/src/{orchestrator/scheduling-admit.js → scheduling/admit.js} +2 -2
- package/dist/src/scheduling/backoff.js +6 -6
- package/dist/src/scheduling/claims.js +103 -26
- package/dist/src/{orchestrator/scheduling-scope.js → scheduling/scope.js} +24 -60
- package/dist/src/{orchestrator/scheduling-skip.js → scheduling/skip.js} +112 -78
- package/dist/src/{run → stage}/stage-path-checks.js +3 -3
- package/dist/src/status/status-budget.js +38 -4
- package/dist/src/status/status-data.js +23 -14
- package/dist/src/status/status-polls.js +83 -26
- package/dist/src/status/status-role-notes.js +25 -7
- package/dist/src/status/status-roles.js +56 -25
- package/dist/src/team/team-orchestrator-info.js +51 -0
- package/dist/src/team/team-state.js +25 -9
- package/dist/src/text/format.js +20 -2
- package/dist/src/text/phrases.js +59 -12
- package/dist/src/text/suggest.js +14 -1
- package/dist/src/text/text.js +31 -2
- package/dist/src/ui/badges.js +102 -17
- package/dist/src/ui/gui/gui-client-drawer-config.js +12 -6
- package/dist/src/ui/gui/gui-client-model.js +5 -0
- package/dist/src/ui/gui/gui-client-operator.js +11 -14
- package/dist/src/ui/gui/gui-client-pending.js +10 -3
- package/dist/src/ui/gui/gui-client-report-chart.js +22 -15
- package/dist/src/ui/gui/gui-client-settings.js +25 -11
- package/dist/src/ui/gui/gui-client-team.js +7 -0
- package/dist/src/ui/status-model.js +21 -6
- package/dist/src/ui/status-payload.js +19 -5
- package/dist/src/ui/status-render.js +14 -8
- package/dist/src/ui/team-alert-shared.js +2 -2
- package/dist/src/ui/team-alert-standings.js +96 -0
- package/dist/src/ui/team-alert-status.js +39 -12
- package/dist/src/ui/team-alerts.js +8 -4
- package/dist/src/ui/tone.js +2 -1
- package/dist/src/ui/tui/tui-backlog.js +12 -0
- package/dist/src/ui/tui/tui-editor-attach.js +2 -6
- package/dist/src/ui/tui/tui-editor-brief.js +2 -6
- package/dist/src/ui/tui/tui-editor-interval.js +2 -2
- package/dist/src/ui/tui/tui-editor-release.js +2 -6
- package/dist/src/ui/tui/tui-editor-retire.js +2 -6
- package/dist/src/ui/tui/tui-editor-role-enabled.js +2 -6
- package/dist/src/ui/tui/tui-editor-role-model.js +5 -1
- package/dist/src/ui/tui/tui-editor-session.js +14 -0
- package/dist/src/ui/tui/tui-guard.js +16 -0
- package/dist/src/ui/tui/tui-input.js +2 -2
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-backlog.js +2 -2
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +29 -35
- package/dist/src/ui/tui/tui-pane-history.js +158 -479
- package/dist/src/ui/tui/tui-pane-pending-selection.js +346 -0
- package/dist/src/ui/tui/tui-pane-pending.js +54 -344
- package/dist/src/ui/tui/tui-pane-queued.js +2 -2
- package/dist/src/ui/tui/tui-pane-trail.js +415 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +2 -7
- package/dist/src/ui/tui/tui-pane.js +3 -3
- package/dist/src/ui/tui/tui.js +2 -1
- package/dist/src/verdict/fix-claim.js +10 -4
- package/dist/src/verdict/reply-contract.js +14 -5
- package/package.json +9 -7
- package/dist/src/merge/merge-check-failures.js +0 -212
- package/dist/src/merge/merge-stack.js +0 -235
- /package/dist/src/{run → observer}/feedback-filings.js +0 -0
- /package/dist/src/{run → observer}/qa-coverage.js +0 -0
- /package/dist/src/{run → observer}/selfcheck-digest.js +0 -0
- /package/dist/src/{run → stage}/acceptance-greps.js +0 -0
- /package/dist/src/{run → stage}/stage-backlog-checks.js +0 -0
- /package/dist/src/{run → stage}/stage-check.js +0 -0
- /package/dist/src/{run → stage}/stage-checks-shared.js +0 -0
- /package/dist/src/{run → stage}/stage-name-checks.js +0 -0
- /package/dist/src/{run → stage}/stage-prose-checks.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BUILD_CHECK_TIMEOUT_MS } from "./build-check-detect.js";
|
|
2
2
|
import { EXEC_MAX_BUFFER, execFileAsync } from "../process/process.js";
|
|
3
3
|
import { KILL_GRACE_MS, runScriptGroup } from "../process/process-group.js";
|
|
4
|
-
import { clipBuildTail, parseFailingTests } from "./build-check-report.js";
|
|
4
|
+
import { clipBuildTail, parseFailingTestFiles, parseFailingTests } from "./build-check-report.js";
|
|
5
5
|
import { parseTestCounts } from "./build-check-counts.js";
|
|
6
6
|
import { sampleSleepClock, sleptMsBetween } from "./host-sleep.js";
|
|
7
7
|
import { npmInstall, syncInstall } from "./dep-install.js";
|
|
@@ -177,6 +177,7 @@ async function runDeclaredCheck(wt, check, script, effectiveMs, killGraceMs, sam
|
|
|
177
177
|
script,
|
|
178
178
|
outputTail: clipBuildTail(output),
|
|
179
179
|
failingTests: parseFailingTests(output),
|
|
180
|
+
failingTestFiles: parseFailingTestFiles(output),
|
|
180
181
|
run,
|
|
181
182
|
counts: parseTestCounts(output),
|
|
182
183
|
};
|
|
@@ -54,6 +54,10 @@ export async function isSelfHosted(root, info) {
|
|
|
54
54
|
return false;
|
|
55
55
|
return commitExists(root, info.sha);
|
|
56
56
|
}
|
|
57
|
+
/** The steps a self-redeploy holds new runs through, in order: main's check of the target head
|
|
58
|
+
* has not answered yet, the staged build of it is compiling, then the built update waits while
|
|
59
|
+
* the runs already in flight finish. */
|
|
60
|
+
export const RESTART_HOLD_STAGES = ["checking", "compiling", "finishing"];
|
|
57
61
|
/** Compare a build's commit against main's head over the build inputs only: commits touching
|
|
58
62
|
* just tests or markdown leave the running code unchanged and never count as stale. Null when
|
|
59
63
|
* `buildSha` is not a commit of this repo (the build came from somewhere else — see
|
|
@@ -106,8 +106,7 @@ export async function syncRootInstall(root, role, install = npmInstall) {
|
|
|
106
106
|
if (!r)
|
|
107
107
|
return;
|
|
108
108
|
// Best-effort: the root install already ran and the ff that prompted it already moved main, so an
|
|
109
|
-
// unwritable feed must not reject the merge that awaited this (merge-to-main.ts's tryMerge
|
|
110
|
-
// merge-stack.ts's ffStackToMain).
|
|
109
|
+
// unwritable feed must not reject the merge that awaited this (merge-to-main.ts's tryMerge).
|
|
111
110
|
logEventBestEffort(root, {
|
|
112
111
|
source: role,
|
|
113
112
|
type: "dep_install",
|
|
@@ -85,4 +85,13 @@ export function sleptMsBetween(a, b) {
|
|
|
85
85
|
}
|
|
86
86
|
return undefined;
|
|
87
87
|
}
|
|
88
|
+
/** Is the host starved — its one-minute load average above the core count? The one bar for
|
|
89
|
+
* "the team's own load, not the work": the merge-scope deadline stretch and its on-time-kill
|
|
90
|
+
* note (starvedDeadlineMs / loadedHostPhrase in build-check-events.ts) and the doctor's
|
|
91
|
+
* check-slot contention advice (doctor-contention-checks.ts) all read the host through this,
|
|
92
|
+
* so the surfaces cannot drift on what "starved" means. A core count of zero or below is not
|
|
93
|
+
* starved: there is no denominator for the ratio to exceed. */
|
|
94
|
+
export function hostLoadStarved(load) {
|
|
95
|
+
return load.cpus > 0 && load.load1 > load.cpus;
|
|
96
|
+
}
|
|
88
97
|
export const sampleHostLoad = () => ({ load1: os.loadavg()[0] ?? 0, cpus: os.cpus().length });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* collector (change-data.ts, beside src/report/report-data.ts and history-data.ts) gathers: one
|
|
3
3
|
* role's pending work (`--role <id>`) and the team-wide roster (no --role). Pure function of the
|
|
4
4
|
* collected view — no I/O, no clock reads. */
|
|
5
|
+
import { joinOr } from "../text/format.js";
|
|
5
6
|
import { plural } from "../text/phrases.js";
|
|
6
7
|
/** The work-summary phrase both change views open their work lines with — `N commits ahead
|
|
7
8
|
* of <main>` plus `, K commits behind` when the branch lags main, plus `, M uncommitted
|
|
@@ -35,7 +36,7 @@ export function renderTeamChange(view) {
|
|
|
35
36
|
const lines = view.roles
|
|
36
37
|
.filter((r) => r.state !== "no-base" && (r.ahead > 0 || r.dirtyFiles.length > 0))
|
|
37
38
|
.map((r) => `${r.role}: ${workSummary(r.ahead, r.behind, view.mainBranch, r.dirtyFiles)}`);
|
|
38
|
-
return lines
|
|
39
|
+
return joinOr(lines, "\n", "no pending changes");
|
|
39
40
|
}
|
|
40
41
|
/** Render the view as the operator-facing text `tumwater diff --role <id>` prints. */
|
|
41
42
|
export function renderRoleChange(view) {
|
package/dist/src/cli/cli-args.js
CHANGED
|
@@ -4,6 +4,7 @@ import { knownRoleIdsCached } from "../config/config.js";
|
|
|
4
4
|
import { allRoleIds, unknownRoleMessage } from "../roles/roles.js";
|
|
5
5
|
import { gotSuffix, parsePositiveInt, splitCommaList } from "../text/text.js";
|
|
6
6
|
import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
7
|
+
import { HOUR_MS, DAY_MS } from "../text/datetime.js";
|
|
7
8
|
/** CLI argument parsing and validation, shared by every command in cli.ts. The execution
|
|
8
9
|
* layer calls these before running a command, so a bad flag fails fast with an actionable
|
|
9
10
|
* message instead of the command silently running with default behavior. The plain-decimal
|
|
@@ -150,6 +151,12 @@ export function parseModelFlag(args, missingValueError) {
|
|
|
150
151
|
export function parseCountFlag(flag, raw, max) {
|
|
151
152
|
if (raw === undefined)
|
|
152
153
|
fail(`${flag} needs a value`);
|
|
154
|
+
// A `--`-token in the count's slot is the operator's slipped value (`logs -n --json`, a
|
|
155
|
+
// `--cancel --json`), never a count — counts never start with `-`, so refuse it up front
|
|
156
|
+
// with rejectFlagLookingValue's shared wording instead of reporting the dropped flag as a
|
|
157
|
+
// malformed count ("got \"--json\""). A single-dash token like `-5` is a malformed count
|
|
158
|
+
// and still reads as one. The sibling parsePositionFlag applies the same guard.
|
|
159
|
+
rejectFlagLookingValue(flag, raw);
|
|
153
160
|
const n = parsePositiveInt(raw);
|
|
154
161
|
if (n === null)
|
|
155
162
|
fail(`${flag} needs a positive integer${gotSuffix(raw)}`);
|
|
@@ -168,6 +175,10 @@ export function parseCountFlag(flag, raw, max) {
|
|
|
168
175
|
export function parsePositionFlag(flag, raw, example) {
|
|
169
176
|
if (raw === undefined)
|
|
170
177
|
fail(`${flag} needs a position number (e.g. \`${example}\`)`);
|
|
178
|
+
// The same slipped-`--`-flag guard parseCountFlag applies: a position never starts with
|
|
179
|
+
// `-`, so `--cancel --json` (or any `--`-token) names its own slipped flag rather than
|
|
180
|
+
// reporting "(got \"--json\")" as a malformed position number.
|
|
181
|
+
rejectFlagLookingValue(flag, raw);
|
|
171
182
|
const n = parsePositiveInt(raw);
|
|
172
183
|
if (n === null)
|
|
173
184
|
fail(`${flag} needs a positive integer${gotSuffix(raw)}`);
|
|
@@ -240,6 +251,7 @@ export function parseRoleListFlag(args, validIds) {
|
|
|
240
251
|
const typed = flagValue(args, "--role");
|
|
241
252
|
if (typed === null)
|
|
242
253
|
return null;
|
|
254
|
+
rejectFlagLookingValue("--role", typed);
|
|
243
255
|
const pieces = splitCommaList(typed ?? "");
|
|
244
256
|
if (pieces.length === 0)
|
|
245
257
|
fail(ROLE_VALUE_ERROR);
|
|
@@ -260,13 +272,22 @@ export function parseRoleListFlag(args, validIds) {
|
|
|
260
272
|
export function parsePortFlag(raw) {
|
|
261
273
|
if (raw === undefined)
|
|
262
274
|
fail("--port needs a value");
|
|
275
|
+
// A `--`-token in the slot is the operator's slipped next flag (`gui --port --json`),
|
|
276
|
+
// never a port number — a port never starts with `-` — so refuse it up front with
|
|
277
|
+
// rejectFlagLookingValue's shared wording, the same guard history's --result/--model/--by
|
|
278
|
+
// and the count/position/duration/role flag homes apply, instead of reporting the next
|
|
279
|
+
// flag as a malformed number.
|
|
280
|
+
rejectFlagLookingValue("--port", raw);
|
|
263
281
|
const n = parsePositiveInt(raw);
|
|
264
282
|
if (n === null || n > 65535)
|
|
265
283
|
fail(`--port must be an integer between 1 and 65535${gotSuffix(raw)}`);
|
|
266
284
|
return n;
|
|
267
285
|
}
|
|
268
|
-
/** Milliseconds per unit, the multiplier parseDurationFlag's regex group is applied to.
|
|
269
|
-
|
|
286
|
+
/** Milliseconds per unit, the multiplier parseDurationFlag's regex group is applied to.
|
|
287
|
+
* The hour and day multipliers are HOUR_MS / DAY_MS (text/datetime.ts's shared units, the
|
|
288
|
+
* convention that records why no two sites may spell them differently); second and minute
|
|
289
|
+
* keep their own literals as there is no shared MINUTE_MS/SECOND_MS constant to fold onto. */
|
|
290
|
+
const UNIT_MS = { s: 1_000, m: 60_000, h: HOUR_MS, d: DAY_MS };
|
|
270
291
|
/** The position of each unit in the descending order a composite duration must follow
|
|
271
292
|
* (d → h → m → s), so `30m1h` and `1h30m1h` fail instead of parsing to a surprising total. */
|
|
272
293
|
const UNIT_ORDER = { d: 0, h: 1, m: 2, s: 3 };
|
|
@@ -310,6 +331,12 @@ export function parseDurationMs(raw) {
|
|
|
310
331
|
export function parseDurationFlag(flag, raw) {
|
|
311
332
|
if (raw === undefined)
|
|
312
333
|
fail(`${flag} needs a value`);
|
|
334
|
+
// A `--`-token in the value slot is the operator's slipped value (`start --for --json`),
|
|
335
|
+
// never a duration — durations never start with `-` — so refuse it up front with
|
|
336
|
+
// rejectFlagLookingValue's shared wording, the same guard history's --result/--model/--by,
|
|
337
|
+
// reviews --status and role-command's write flags apply, instead of misreading the next
|
|
338
|
+
// flag as a duration and reporting it as malformed ("got \"--json\"").
|
|
339
|
+
rejectFlagLookingValue(flag, raw);
|
|
313
340
|
const ms = parseDurationMs(raw);
|
|
314
341
|
if (ms === null)
|
|
315
342
|
fail(`${flag} needs a duration like 45s, 90m, 1h30m, or 2d${gotSuffix(raw)}`);
|
|
@@ -371,6 +398,14 @@ export function failOverDurationCap(flag, ms, maxMs, hint) {
|
|
|
371
398
|
export function failRivalShapes(subject, rival, why) {
|
|
372
399
|
fail(`${subject} cannot be combined with ${rival} (${why})`);
|
|
373
400
|
}
|
|
401
|
+
/** The shared why-clause for a trailing-window view's `-f/--follow` rival: follow is an
|
|
402
|
+
* unbounded "from now on" read, while `--since` opens a bounded past window, so the two are
|
|
403
|
+
* mutually exclusive on the same grounds in all three windowed views (logs, history, report).
|
|
404
|
+
* One clause so the three sites cannot drift on what follow means and why it excludes
|
|
405
|
+
* `--since`; the subject keeps the command's own name. */
|
|
406
|
+
export function failFollowSinceRival(command) {
|
|
407
|
+
return failRivalShapes(`${command} --since`, "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
|
|
408
|
+
}
|
|
374
409
|
/** Guard a trailing-window command's `--until <window>` against its `--since <window>` pair —
|
|
375
410
|
* the shared fail sentences the three windowed views (logs, history, report) print, so the
|
|
376
411
|
* wording cannot drift per command. When `--until` is present but `--since` is not, it fails
|
|
@@ -417,6 +452,11 @@ export function parseRoleFlag(args, validIds, flag = "--role", missingValue = RO
|
|
|
417
452
|
const typed = flagValue(args, flag);
|
|
418
453
|
if (typed === null)
|
|
419
454
|
return null;
|
|
455
|
+
// A `--`-token in the id's slot is the operator's slipped value (`--role --json`), never a
|
|
456
|
+
// role id — role ids are bare words that never start with `-` — so refuse it up front with
|
|
457
|
+
// rejectFlagLookingValue's shared wording, the same guard history's --result/--model/--by
|
|
458
|
+
// and reviews --status apply, instead of reporting the next flag as an "unknown role".
|
|
459
|
+
rejectFlagLookingValue(flag, typed);
|
|
420
460
|
if (!typed)
|
|
421
461
|
fail(missingValue);
|
|
422
462
|
const role = canonicalRoleId(typed);
|
|
@@ -427,11 +467,16 @@ export function parseRoleFlag(args, validIds, flag = "--role", missingValue = RO
|
|
|
427
467
|
}
|
|
428
468
|
/** Parse an optional `--branch <name>` flag: the named branch, or null when absent — the same
|
|
429
469
|
* shape as parseRoleFlag, so every command that scopes to one branch validates identically.
|
|
430
|
-
* A missing value fails fast instead of silently falling back to the checked-out branch.
|
|
470
|
+
* A missing value fails fast instead of silently falling back to the checked-out branch. A
|
|
471
|
+
* `--`-token in the slot is the operator's slipped next flag (`run --branch --json`), never a
|
|
472
|
+
* branch name — a branch name never starts with `-` — so refuse it up front with
|
|
473
|
+
* rejectFlagLookingValue's shared wording, the same guard every other value slot applies,
|
|
474
|
+
* instead of reporting the next flag as a missing name. */
|
|
431
475
|
export function parseBranchFlag(args) {
|
|
432
476
|
const branch = flagValue(args, "--branch");
|
|
433
477
|
if (branch === null)
|
|
434
478
|
return null;
|
|
479
|
+
rejectFlagLookingValue("--branch", branch);
|
|
435
480
|
if (!branch || branch.startsWith("-"))
|
|
436
481
|
fail(BRANCH_VALUE_ERROR);
|
|
437
482
|
return branch;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* apart from cli/cli-args.ts's generic machinery — which also keeps that module, minus these
|
|
9
9
|
* two, free of I/O: --file's readFileSync is the one read in the CLI's arg layer. */
|
|
10
10
|
import fs from "node:fs";
|
|
11
|
-
import { failOverDurationCap, parseBranchFlag, parseDurationFlag, parsePositionFlag, rejectRepeatFlag } from "./cli-args.js";
|
|
11
|
+
import { failOverDurationCap, parseBranchFlag, parseDurationFlag, parsePositionFlag, rejectFlagLookingValue, rejectRepeatFlag } from "./cli-args.js";
|
|
12
12
|
import { PAUSE_FOR_MAX_MS } from "../operator/operator-intent.js";
|
|
13
13
|
import { JSON_FLAG, ROLE_FLAG, ROLE_VALUE_ERROR, rejectUnknownDoubleDash, } from "./cli-flag-specs.js";
|
|
14
14
|
import { fail } from "./cli-output.js";
|
|
@@ -99,8 +99,11 @@ function readPromptFile(file) {
|
|
|
99
99
|
* shared by every `--file`-taking parser — init, prompt's edit and enqueue branches, plan/bug
|
|
100
100
|
* add and edit (fileAndAnnounce / editAndAnnounce), and the questions command — so the wording
|
|
101
101
|
* cannot drift between them. The flag token itself is claimed by each parser's stray-argument
|
|
102
|
-
* accounting; this helper only owns the value.
|
|
102
|
+
* accounting; this helper only owns the value. A flag-shaped token (`--file --json`) is
|
|
103
|
+
* refused up front via rejectFlagLookingValue instead of being read as a path that fails deep
|
|
104
|
+
* with a misleading "cannot read prompt file" about a flag nobody intended as a file. */
|
|
103
105
|
export function promptFileValue(file) {
|
|
106
|
+
rejectFlagLookingValue("--file", file);
|
|
104
107
|
if (!file)
|
|
105
108
|
fail("--file needs a path");
|
|
106
109
|
return file;
|
|
@@ -386,13 +389,18 @@ function assertPromptQueueOnlyFlags(scan) {
|
|
|
386
389
|
* with the missing-value refusal, verbatim. */
|
|
387
390
|
function parsePromptAttachments(args, attachFlags) {
|
|
388
391
|
// Each --attach pair claims the flag and its value; a trailing flag with no value names the
|
|
389
|
-
// flag here, like --cancel's missing-value wording, before any queue write.
|
|
390
|
-
|
|
392
|
+
// flag here, like --cancel's missing-value wording, before any queue write. A `--`-shaped
|
|
393
|
+
// token in the value slot is the operator's slip (`--attach --role qa`), so it is refused up
|
|
394
|
+
// front with rejectFlagLookingValue's shared wording — the same guard history's
|
|
395
|
+
// --result/--model and role <id>'s write flags apply — instead of failing deep in image
|
|
396
|
+
// reading with a misleading message about a path nobody intended.
|
|
391
397
|
const attachClaims = attachFlags.flatMap((i) => [i, i + 1]);
|
|
392
398
|
for (const i of attachFlags) {
|
|
393
399
|
if (args[i + 1] === undefined)
|
|
394
400
|
fail(`--attach needs a path (e.g. \`--attach shot.png\`)`);
|
|
401
|
+
rejectFlagLookingValue("--attach", args[i + 1]);
|
|
395
402
|
}
|
|
403
|
+
const attachPaths = attachFlags.map((i) => args[i + 1]);
|
|
396
404
|
return { attachPaths, attachClaims };
|
|
397
405
|
}
|
|
398
406
|
/** The `--at <duration>` parse and its 90-day cap, verbatim. */
|
|
@@ -240,7 +240,8 @@ export const N_FLAG = {
|
|
|
240
240
|
* whole spec, and the render-vs-JSON switch stays in each command body beside its render. */
|
|
241
241
|
export const JSON_FLAG = { names: ["--json"] };
|
|
242
242
|
/** The `-f/--follow` flag spec, shared by every command with a follow view (logs, history,
|
|
243
|
-
* status, queue, report, backlog and
|
|
243
|
+
* status, queue, report, backlog, diff and quarantine — each snapshot command's `-f/--follow`
|
|
244
|
+
* twin): one
|
|
244
245
|
* definition of the flag's spelling, beside ROLE_FLAG and JSON_FLAG, so the gate's accepted
|
|
245
246
|
* vocabulary cannot drift apart. It takes no value, like JSON_FLAG; each body renders its
|
|
246
247
|
* own live view, and detects the flag through hasFollowFlag, so the spelling lives in exactly
|
|
@@ -248,8 +249,9 @@ export const JSON_FLAG = { names: ["--json"] };
|
|
|
248
249
|
export const FOLLOW_FLAG = { names: ["-f", "--follow"] };
|
|
249
250
|
/** Whether an arg array carries the `-f/--follow` flag — the detection every follow-capable
|
|
250
251
|
* command body spelled out as an `includes("-f") || includes("--follow")` one-liner over
|
|
251
|
-
* its own arg array (`args` for status, queue, report and
|
|
252
|
-
* backlog). Now derived from FOLLOW_FLAG's own spelling, so the detection cannot
|
|
252
|
+
* its own arg array (`args` for status, queue, report, diff and quarantine; `rest` for logs,
|
|
253
|
+
* history and backlog). Now derived from FOLLOW_FLAG's own spelling, so the detection cannot
|
|
254
|
+
* drift from
|
|
253
255
|
* the gate's accepted vocabulary. */
|
|
254
256
|
export function hasFollowFlag(args) {
|
|
255
257
|
return FOLLOW_FLAG.names.some((name) => args.includes(name));
|
package/dist/src/cli/cli-init.js
CHANGED
|
@@ -37,7 +37,11 @@ function printInitIntro(result) {
|
|
|
37
37
|
if (result.template !== "blank")
|
|
38
38
|
say(`template: ${result.template}`);
|
|
39
39
|
if (result.adopted) {
|
|
40
|
-
|
|
40
|
+
// Adoption preserves the project's own README.md; the "existing repo" claim holds only when
|
|
41
|
+
// the directory already was a repo — when init seeds one itself, naming it "existing" would
|
|
42
|
+
// contradict the "initialized a new git repository" line the report prints right after.
|
|
43
|
+
const target = result.repoInitialized ? "the existing README.md" : "an existing repo";
|
|
44
|
+
say(`${result.dryRun ? "would adopt" : "adopting"} ${target}: the project brief goes in TUMWATER.md and README.md is left untouched`);
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
/** Print the dry-run preview: repo line, the would-create/would-leave-alone lists, and the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fail, say, sayJsonLine } from "./cli-output.js";
|
|
2
2
|
import { hasFollowFlag, LOGS_TRANSCRIPT_VALUE_ERROR } from "./cli-flag-specs.js";
|
|
3
|
-
import { betweenWindow, durationLabel, failRivalShapes, failUntilShorterThanSince, flagValue, parseCountFlag, parseGrepFlag, parseRoleListScope, parseRoleScope, parseSinceFlag, parseTypeFlag } from "./cli-args.js";
|
|
3
|
+
import { betweenWindow, durationLabel, failFollowSinceRival, failRivalShapes, failUntilShorterThanSince, flagValue, parseCountFlag, parseGrepFlag, parseRoleListScope, parseRoleScope, parseSinceFlag, parseTypeFlag } from "./cli-args.js";
|
|
4
4
|
import { LOGS_SINCE_MAX_MS, readEventsSince, SPARSE_WINDOW_NOTE, } from "../events/event-window.js";
|
|
5
5
|
import { compareCodeUnits, increment } from "../collections.js";
|
|
6
6
|
import { eventTypeTally } from "../events/event-tally.js";
|
|
@@ -229,7 +229,7 @@ function parseLogsFeed(root, args) {
|
|
|
229
229
|
* prose, the `--count` tally, the per-event print and the sparse-window note. */
|
|
230
230
|
function sayLogsWindow(root, feed, ms, untilMs) {
|
|
231
231
|
if (feed.follow)
|
|
232
|
-
|
|
232
|
+
failFollowSinceRival("logs");
|
|
233
233
|
if (feed.rest.includes("-n"))
|
|
234
234
|
failRivalShapes("logs --since", "-n", "a count and a window are rival shapes");
|
|
235
235
|
rejectTranscriptViewRival(feed.rest, "--since", true);
|
|
@@ -7,12 +7,13 @@ import { fail, say, sayJson, sayJsonOrRender } from "./cli-output.js";
|
|
|
7
7
|
import { cancelQueuedMerge } from "../merge/merge-cancel.js";
|
|
8
8
|
import { moveQueuedMergeToTop } from "../merge/merge-top.js";
|
|
9
9
|
import { followView } from "./follow-view.js";
|
|
10
|
-
import { flagValue, parseGrepFlag, parseRoleListScope, rejectRepeatFlag, scanPositionArgs } from "./cli-args.js";
|
|
10
|
+
import { flagValue, parseGrepFlag, parseRoleListScope, rejectFlagLookingValue, rejectRepeatFlag, scanPositionArgs } from "./cli-args.js";
|
|
11
11
|
import { DIFF_PATH_FLAG, FOLLOW_FLAG, hasFollowFlag, JSON_FLAG, READY_FLAG, grepFlagSpec, rejectUnknownArgs, OVERLAPS_FLAG, ROLE_FLAG, STAT_FLAG } from "./cli-flag-specs.js";
|
|
12
12
|
import { refuseTeamWorkspace, requireReadyRepo } from "./cli-repo-gate.js";
|
|
13
13
|
import { scopeSnapshotToRoles, snapshot } from "../status/status-data.js";
|
|
14
14
|
import { statusPayload } from "../ui/status-payload.js";
|
|
15
15
|
import { renderStatus } from "../ui/status-render.js";
|
|
16
|
+
import { gotSuffix } from "../text/text.js";
|
|
16
17
|
import { backlogPayload } from "../backlog/backlog.js";
|
|
17
18
|
import { backlogHolds, backlogInProgress } from "../backlog/backlog-eligibility.js";
|
|
18
19
|
import { readyBacklog, grepBacklog, renderBacklogMarkdown } from "../backlog/backlog-render.js";
|
|
@@ -22,7 +23,7 @@ import { collectChangeOverlaps, scopeChangeOverlaps, scopeChangeOverlapsToPath,
|
|
|
22
23
|
import { renderChangeOverlaps, renderTeamChange, renderRoleChange, renderRoleChanges } from "../change/change-render.js";
|
|
23
24
|
import { readMergeQueue } from "../merge/merge-queue.js";
|
|
24
25
|
import { renderMergeQueue, scopeMergeQueue } from "../merge/merge-queue-render.js";
|
|
25
|
-
import {
|
|
26
|
+
import { mergeHoldsSha } from "../merge/merge-slot.js";
|
|
26
27
|
import { shortSha } from "../text/format.js";
|
|
27
28
|
import { roleLabel } from "../roles/role-labels.js";
|
|
28
29
|
import { orchestratorAlive } from "../team/team-orchestrator-info.js";
|
|
@@ -135,14 +136,17 @@ export async function cmdDiff(root, args, seams = {}) {
|
|
|
135
136
|
sayJsonOrRender(args, view, () => render());
|
|
136
137
|
}
|
|
137
138
|
/** The normalised `--path` value of `diff --role` (undefined when absent): leading `./` pieces
|
|
138
|
-
* and trailing `/` characters stripped, failing on an empty, absolute or `..`-bearing result.
|
|
139
|
+
* and trailing `/` characters stripped, failing on an empty, absolute or `..`-bearing result.
|
|
140
|
+
* A `--` token in the value slot is refused via rejectFlagLookingValue first, so `diff --role
|
|
141
|
+
* feature --path --json` names the flag instead of failing downstream as a path nobody meant. */
|
|
139
142
|
function diffPathArg(args) {
|
|
140
143
|
const raw = flagValue(args, "--path");
|
|
141
144
|
if (raw === null || raw === undefined)
|
|
142
145
|
return undefined;
|
|
146
|
+
rejectFlagLookingValue("--path", raw);
|
|
143
147
|
const norm = normalizeDiffPath(raw);
|
|
144
148
|
if (norm === null)
|
|
145
|
-
fail(
|
|
149
|
+
fail(`diff --path takes a repo-relative path such as src/cli.ts (as diff --overlaps prints it)${gotSuffix(raw)}`);
|
|
146
150
|
return norm;
|
|
147
151
|
}
|
|
148
152
|
/** `tumwater queue [-f/--follow]`: the durable merge queue in execution order, as text or as
|
|
@@ -216,7 +220,7 @@ async function cmdQueueCancel(root, args) {
|
|
|
216
220
|
const view = readMergeQueue(root);
|
|
217
221
|
const inFlight = queueInFlight(root, view.entries, orchestratorAlive(root));
|
|
218
222
|
const target = view.entries[position - 1];
|
|
219
|
-
if (inFlight && target && (
|
|
223
|
+
if (inFlight && target && mergeHoldsSha(inFlight, target.sha)) {
|
|
220
224
|
fail(`queue entry ${position} is being merged right now — wait for it to land, then \`tumwater revert ${shortSha(target.sha)}\``);
|
|
221
225
|
}
|
|
222
226
|
const outcome = await cancelQueuedMerge(root, position);
|
|
@@ -258,7 +262,7 @@ async function cmdQueueTop(root, args) {
|
|
|
258
262
|
const view = readMergeQueue(root);
|
|
259
263
|
const inFlight = queueInFlight(root, view.entries, orchestratorAlive(root));
|
|
260
264
|
const target = view.entries[position - 1];
|
|
261
|
-
if (inFlight && target && (
|
|
265
|
+
if (inFlight && target && mergeHoldsSha(inFlight, target.sha)) {
|
|
262
266
|
fail(`queue entry ${position} is being merged right now — it's already heading into the merge, so there's nothing to top`);
|
|
263
267
|
}
|
|
264
268
|
const outcome = moveQueuedMergeToTop(root, position);
|
|
@@ -338,22 +342,34 @@ export async function cmdBacklog(root, args, seams = {}) {
|
|
|
338
342
|
const result = readBacklog();
|
|
339
343
|
sayJsonOrRender(rest, result.view, () => renderBacklogText(result));
|
|
340
344
|
}
|
|
341
|
-
// `tumwater quarantine [--release <n>] [--json]` — the view of the review
|
|
342
|
-
// quarantine, read-only except the one destructive `--release <n>` mode that
|
|
343
|
-
// entry the list prints at the operator's request.
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
// the
|
|
348
|
-
|
|
345
|
+
// `tumwater quarantine [-f/--follow] [--release <n>] [--json]` — the view of the review
|
|
346
|
+
// gate's flake quarantine, read-only except the one destructive `--release <n>` mode that
|
|
347
|
+
// removes the Nth entry the list prints at the operator's request. `-f/--follow` repaints the
|
|
348
|
+
// list every couple of seconds until Ctrl+C or the process that started it exits, so a
|
|
349
|
+
// backgrounded follow does not outlive its parent (the same rule `queue -f` uses). The read
|
|
350
|
+
// path keeps its no-gate refuseTeamWorkspace stance: loadQuarantine degrades an absent or
|
|
351
|
+
// malformed file to the empty list, so the command prints the empty line in any directory and
|
|
352
|
+
// the read never writes quarantine.json (a test otherwise leaves the list through
|
|
353
|
+
// releaseQuarantine). --json serves the same positioned rows to scripts.
|
|
354
|
+
export async function cmdQuarantine(root, args, seams = {}) {
|
|
349
355
|
// `--release <n>` is the read-only command's one destructive mode: route it to its own
|
|
350
356
|
// branch before the read gate, exactly as cmdReviews routes `--accept`.
|
|
351
357
|
if (args.includes("--release")) {
|
|
352
358
|
await cmdQuarantineRelease(root, args);
|
|
353
359
|
return;
|
|
354
360
|
}
|
|
355
|
-
rejectUnknownArgs("quarantine", args, [JSON_FLAG]);
|
|
361
|
+
rejectUnknownArgs("quarantine", args, [JSON_FLAG, FOLLOW_FLAG]);
|
|
356
362
|
refuseTeamWorkspace(root);
|
|
363
|
+
const follow = hasFollowFlag(args);
|
|
364
|
+
if (follow && args.includes("--json"))
|
|
365
|
+
fail("quarantine --json prints one document; -f/--follow repaints a live view — drop one of them");
|
|
366
|
+
// Each frame re-reads the quarantine file and reads the clock once, so an entry that
|
|
367
|
+
// appears, ages toward the ceiling, or leaves when its bug closes shows up in the next
|
|
368
|
+
// repaint — the same per-frame read `queue -f` uses.
|
|
369
|
+
if (follow) {
|
|
370
|
+
await followView(() => renderQuarantine(quarantineRows(loadQuarantine(root)), Date.now()), seams);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
357
373
|
const rows = quarantineRows(loadQuarantine(root));
|
|
358
374
|
sayJsonOrRender(args, { tests: rows }, () => renderQuarantine(rows, Date.now()));
|
|
359
375
|
}
|
|
@@ -18,8 +18,8 @@ import { POST_REVIEW_STATES, postReviewState, renderPostReviews, scopePostReview
|
|
|
18
18
|
import { roleInScope } from "../roles/runner-ids.js";
|
|
19
19
|
import { roleLabel } from "../roles/role-labels.js";
|
|
20
20
|
import { shortSha } from "../text/format.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { splitCommaList } from "../text/text.js";
|
|
22
|
+
import { oneOfMessage } from "../text/suggest.js";
|
|
23
23
|
/** The missing-value wording of `reviews --status` (shared by the gate's statusFlagSpec and
|
|
24
24
|
* parseStatusFlag's own fail, so the two cannot drift). */
|
|
25
25
|
const REVIEWS_STATUS_VALUE_ERROR = "reviews --status needs a value";
|
|
@@ -36,7 +36,7 @@ function parseReviewStatusList(raw) {
|
|
|
36
36
|
const out = [];
|
|
37
37
|
for (const piece of pieces) {
|
|
38
38
|
if (!POST_REVIEW_STATES.includes(piece))
|
|
39
|
-
fail(`reviews --status
|
|
39
|
+
fail(`reviews --status ${oneOfMessage(spellings)(piece)}`);
|
|
40
40
|
if (!out.includes(piece))
|
|
41
41
|
out.push(piece);
|
|
42
42
|
}
|