tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -30,10 +30,9 @@ import { commitAuthorArgs, committerIdentArgs, git, gitTry } from "../git/git-ru
|
|
|
30
30
|
import { mergeLockDir } from "../paths.js";
|
|
31
31
|
import { plural } from "../text/phrases.js";
|
|
32
32
|
import { collapseWhitespace, errorFirstLine, errorMessage } from "../text/text.js";
|
|
33
|
-
import { FINISHED_ARCHIVES, fenceTracker, fencedHeadingTitle
|
|
34
|
-
import { actionableEntryRanges
|
|
35
|
-
import {
|
|
36
|
-
import { formatDate } from "../text/datetime.js";
|
|
33
|
+
import { FINISHED_ARCHIVES, fenceTracker, fencedHeadingTitle } from "./backlog-md.js";
|
|
34
|
+
import { actionableEntryRanges } from "./backlog-index-data.js";
|
|
35
|
+
import { strandedPlanEntries } from "./backlog-structure.js";
|
|
37
36
|
/** The placeholder a role leaves under an emptied section: never archived content. */
|
|
38
37
|
const PLACEHOLDER = /^_None yet\._$/;
|
|
39
38
|
/** The text a new archive file starts with: a title, how to read the file, and the section
|
|
@@ -132,6 +131,57 @@ function entryBlocks(lines, entries) {
|
|
|
132
131
|
}
|
|
133
132
|
return out;
|
|
134
133
|
}
|
|
134
|
+
/** The selected entries of `entries`: the keep slice, or the baseKeys/park filter. */
|
|
135
|
+
function selectArchiveEntries(entries, select) {
|
|
136
|
+
return "keep" in select
|
|
137
|
+
? entries.slice(Math.max(0, select.keep))
|
|
138
|
+
: entries.filter((e) => !select.baseKeys.has(archiveKey(e.title)) && !(select.park?.has(archiveKey(e.title)) ?? false));
|
|
139
|
+
}
|
|
140
|
+
/** Add the 0-based live lines of `entries` to `into`. */
|
|
141
|
+
function addEntryLines(entries, into) {
|
|
142
|
+
for (const e of entries)
|
|
143
|
+
for (let i = e.start - 1; i < e.end; i++)
|
|
144
|
+
into.add(i);
|
|
145
|
+
}
|
|
146
|
+
/** The bulk-archive split: the tail past the kept entries moves but for the dropped and staying
|
|
147
|
+
* entries, and a `_None yet._` placeholder stays in the live section, never in the archive.
|
|
148
|
+
* null when no entry sits past the kept ones. */
|
|
149
|
+
function splitByKeep(lines, span, entries, keep, staying, droppedLine) {
|
|
150
|
+
const from = keep <= 0 ? span.heading + 1 : (entries[keep]?.start ?? 0) - 1;
|
|
151
|
+
if (from < 0)
|
|
152
|
+
return null; // no entry past the kept ones
|
|
153
|
+
// The tail moves but for the dropped and staying entries; a `_None yet._` placeholder stays
|
|
154
|
+
// in the live section, never in the archive. Staying entries follow the kept ones.
|
|
155
|
+
const stayLine = new Set();
|
|
156
|
+
addEntryLines(staying, stayLine);
|
|
157
|
+
const tail = [];
|
|
158
|
+
for (let i = from; i < span.end; i++)
|
|
159
|
+
if (!droppedLine.has(i) && !stayLine.has(i))
|
|
160
|
+
tail.push(lines[i] ?? "");
|
|
161
|
+
const moved = trimBlank(tail.filter((l) => !PLACEHOLDER.test(l.trim())));
|
|
162
|
+
const stayingBlocks = entryBlocks(lines, staying);
|
|
163
|
+
const head = keep <= 0 ? [] : trimBlank(lines.slice(span.heading + 1, from));
|
|
164
|
+
const kept = head.length > 0 && stayingBlocks.length > 0 ? [...head, "", ...stayingBlocks] : [...head, ...stayingBlocks];
|
|
165
|
+
return {
|
|
166
|
+
kept: kept.length === 0 ? tail.filter((l) => PLACEHOLDER.test(l.trim())).slice(0, 1) : kept,
|
|
167
|
+
moved,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** The commit-time-sweep split: the kept body drops the moved and dropped lines, refusing when
|
|
171
|
+
* no archive exists and other content would stay in the live section. */
|
|
172
|
+
function splitByBaseKeys(lines, span, archiveMd, moving, droppedLine) {
|
|
173
|
+
const leaves = new Set(droppedLine);
|
|
174
|
+
addEntryLines(moving, leaves);
|
|
175
|
+
let kept = [];
|
|
176
|
+
for (let i = span.heading + 1; i < span.end; i++)
|
|
177
|
+
if (!leaves.has(i))
|
|
178
|
+
kept.push(lines[i] ?? "");
|
|
179
|
+
kept = trimBlank(kept);
|
|
180
|
+
if (archiveMd === null && kept.some((l) => l.trim() !== "" && !PLACEHOLDER.test(l.trim())))
|
|
181
|
+
return null;
|
|
182
|
+
const moved = entryBlocks(lines, moving);
|
|
183
|
+
return { kept, moved };
|
|
184
|
+
}
|
|
135
185
|
/** Move the selected part of `liveMd`'s finished section to the top of `archiveMd`'s (a new
|
|
136
186
|
* archive starts from archiveTemplate), keeping the moved lines' order. A selected entry whose
|
|
137
187
|
* heading the archive already holds never moves (EntryFate): with the same text it is dropped
|
|
@@ -146,9 +196,7 @@ export function archiveFinishedText(liveMd, archiveMd, spec, select) {
|
|
|
146
196
|
if (span === null)
|
|
147
197
|
return null;
|
|
148
198
|
const entries = entriesIn(liveMd, span, spec.section);
|
|
149
|
-
const selected =
|
|
150
|
-
? entries.slice(Math.max(0, select.keep))
|
|
151
|
-
: entries.filter((e) => !select.baseKeys.has(archiveKey(e.title)) && !(select.park?.has(archiveKey(e.title)) ?? false));
|
|
199
|
+
const selected = selectArchiveEntries(entries, select);
|
|
152
200
|
if (!("keep" in select) && selected.length === 0)
|
|
153
201
|
return null;
|
|
154
202
|
const archiveText = archiveMd ?? archiveTemplate(spec);
|
|
@@ -160,46 +208,14 @@ export function archiveFinishedText(liveMd, archiveMd, spec, select) {
|
|
|
160
208
|
const withFate = (fate) => selected.filter((_, i) => fates[i] === fate);
|
|
161
209
|
// The 0-based live lines of the entries that leave the live section without moving.
|
|
162
210
|
const droppedLine = new Set();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return null; // no entry past the kept ones
|
|
172
|
-
// The tail moves but for the dropped and staying entries; a `_None yet._` placeholder stays
|
|
173
|
-
// in the live section, never in the archive. Staying entries follow the kept ones.
|
|
174
|
-
const stayLine = new Set();
|
|
175
|
-
for (const e of withFate("stay"))
|
|
176
|
-
for (let i = e.start - 1; i < e.end; i++)
|
|
177
|
-
stayLine.add(i);
|
|
178
|
-
const tail = [];
|
|
179
|
-
for (let i = from; i < span.end; i++)
|
|
180
|
-
if (!droppedLine.has(i) && !stayLine.has(i))
|
|
181
|
-
tail.push(lines[i] ?? "");
|
|
182
|
-
moved = trimBlank(tail.filter((l) => !PLACEHOLDER.test(l.trim())));
|
|
183
|
-
const staying = entryBlocks(lines, withFate("stay"));
|
|
184
|
-
const head = select.keep <= 0 ? [] : trimBlank(lines.slice(span.heading + 1, from));
|
|
185
|
-
kept = head.length > 0 && staying.length > 0 ? [...head, "", ...staying] : [...head, ...staying];
|
|
186
|
-
if (kept.length === 0)
|
|
187
|
-
kept = tail.filter((l) => PLACEHOLDER.test(l.trim())).slice(0, 1);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
const leaves = new Set(droppedLine);
|
|
191
|
-
for (const e of withFate("move"))
|
|
192
|
-
for (let i = e.start - 1; i < e.end; i++)
|
|
193
|
-
leaves.add(i);
|
|
194
|
-
kept = [];
|
|
195
|
-
for (let i = span.heading + 1; i < span.end; i++)
|
|
196
|
-
if (!leaves.has(i))
|
|
197
|
-
kept.push(lines[i] ?? "");
|
|
198
|
-
kept = trimBlank(kept);
|
|
199
|
-
if (archiveMd === null && kept.some((l) => l.trim() !== "" && !PLACEHOLDER.test(l.trim())))
|
|
200
|
-
return null;
|
|
201
|
-
moved = entryBlocks(lines, withFate("move"));
|
|
202
|
-
}
|
|
211
|
+
addEntryLines(withFate("drop"), droppedLine);
|
|
212
|
+
const split = "keep" in select
|
|
213
|
+
? splitByKeep(lines, span, entries, select.keep, withFate("stay"), droppedLine)
|
|
214
|
+
: splitByBaseKeys(lines, span, archiveMd, withFate("move"), droppedLine);
|
|
215
|
+
if (split === null)
|
|
216
|
+
return null;
|
|
217
|
+
const moved = split.moved;
|
|
218
|
+
const kept = split.kept;
|
|
203
219
|
const dropped = withFate("drop").map((e) => e.title);
|
|
204
220
|
if (moved.length === 0 && dropped.length === 0)
|
|
205
221
|
return null;
|
|
@@ -301,6 +317,15 @@ function archiveCommitWhat(plan) {
|
|
|
301
317
|
const archived = `Archive ${plural(count, "finished entry", "finished entries")} to ${archives.join(" and ")}`;
|
|
302
318
|
return dropped > 0 ? `${archived}; drop ${drop}` : archived;
|
|
303
319
|
}
|
|
320
|
+
/** One live backlog file's finished-history rows as liveFinishedHistory reports them, phrased
|
|
321
|
+
* and joined: `file ## <section> holds N finished entries (X KB)` rows separated by "; " — the
|
|
322
|
+
* shared detail head the doctor's `backlog archive` check (checkLiveFinished) and the always-on
|
|
323
|
+
* `backlog-archive` alert (src/ui/team-alert-backlog.ts) both render, so the row shape, the
|
|
324
|
+
* plural, and the KB rounding cannot drift between the two. Each caller appends its own trailing
|
|
325
|
+
* explanation, so the two texts stay byte-identical up to that suffix. */
|
|
326
|
+
export function liveFinishedRowList(rows) {
|
|
327
|
+
return rows.map((l) => `${l.file} ## ${l.section} holds ${plural(l.entries, "finished entry", "finished entries")} (${Math.round(l.bytes / 1024)} KB)`).join("; ");
|
|
328
|
+
}
|
|
304
329
|
/** One archived file as a line of `tumwater archive`'s output and the team-start event: what
|
|
305
330
|
* moved where, and what was dropped because the archive already held it. */
|
|
306
331
|
export function describeArchivedFile(f) {
|
|
@@ -316,6 +341,17 @@ function planOrSkip(root, keep) {
|
|
|
316
341
|
return { status: "skipped", reason: `a backlog file could not be read: ${errorFirstLine(err)}` };
|
|
317
342
|
}
|
|
318
343
|
}
|
|
344
|
+
/** If the primary checkout is not on the base branch, the reason a main-side write should be
|
|
345
|
+
* skipped, else null. Both `archiveFinishedOnMain` and `fileBugsOnMain` (backlog-file-bugs.ts)
|
|
346
|
+
* guard on this before committing a backlog file. */
|
|
347
|
+
export async function primaryOffBaseBranchReason(root) {
|
|
348
|
+
const primary = await currentBranch(root);
|
|
349
|
+
const base = targetBranch(loadConfigSafe(root).config?.baseBranch, primary);
|
|
350
|
+
if (primary !== base) {
|
|
351
|
+
return `your checkout is on ${primary ?? "a detached HEAD"}, not ${base}`;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
319
355
|
/** The bulk archive, committed: move every live finished section of the primary checkout at
|
|
320
356
|
* `root` (past its newest `keep` entries) to its archive file and commit the touched files on the
|
|
321
357
|
* base branch as one `tumwater(operator):` commit. Holds the team's merge lock, so a merge
|
|
@@ -332,11 +368,9 @@ export async function archiveFinishedOnMain(root, opts = {}) {
|
|
|
332
368
|
if (pre.length === 0)
|
|
333
369
|
return { status: "nothing" };
|
|
334
370
|
return withLock(mergeLockDir(root), () => withPrimaryWorktreeLockAsync(root, async () => {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return { status: "skipped", reason: `your checkout is on ${primary ?? "a detached HEAD"}, not ${base}` };
|
|
339
|
-
}
|
|
371
|
+
const offBase = await primaryOffBaseBranchReason(root);
|
|
372
|
+
if (offBase !== null)
|
|
373
|
+
return { status: "skipped", reason: offBase };
|
|
340
374
|
const files = FINISHED_ARCHIVES.flatMap((a) => [a.file, a.archive]);
|
|
341
375
|
const status = await gitTry(root, "status", "--porcelain", "--", ...files);
|
|
342
376
|
if (status === null)
|
|
@@ -402,121 +436,3 @@ export async function archiveAtTeamStart(root) {
|
|
|
402
436
|
warnEventBestEffort(root, TUMWATER_SOURCE, `finished backlog entries were not archived: ${errorMessage(err)}`);
|
|
403
437
|
}
|
|
404
438
|
}
|
|
405
|
-
/** The start of a quarantine entry's title for `test`, up to the date. appendBugEntryLocked
|
|
406
|
-
* collapses whitespace in the title, so the test part is collapsed the same way; collapsing the
|
|
407
|
-
* whole prefix would trim its trailing space. */
|
|
408
|
-
function quarantineTitlePrefix(test) {
|
|
409
|
-
return collapseWhitespace(`\`${test}\` failed, then passed on the same tree`) + " (quarantined by tumwater ";
|
|
410
|
-
}
|
|
411
|
-
/** Output fenced into a bug body: a fence of its own length cannot be closed by the quoted text. */
|
|
412
|
-
function fencedTail(tail) {
|
|
413
|
-
return ["~~~~", ...tail.replaceAll("~~~~", "~~~").split("\n").slice(-30), "~~~~"].join("\n");
|
|
414
|
-
}
|
|
415
|
-
/** File one BUGS.md `## Open` entry per given entry and commit BUGS.md on the base branch as one
|
|
416
|
-
* `tumwater(operator):` commit (the subject built from the number of entries actually appended is
|
|
417
|
-
* stamped under the operator role), under the merge lock and the primary-worktree lock exactly as
|
|
418
|
-
* archiveFinishedOnMain does. Skipped,
|
|
419
|
-
* writing nothing, when the primary checkout is off the base branch, BUGS.md has uncommitted
|
|
420
|
-
* changes, or BUGS.md exists but cannot be read; a failed append or commit restores BUGS.md.
|
|
421
|
-
* Every entry is appended verbatim as its own `## Open` heading — unless its `ifOpen` dedupe
|
|
422
|
-
* matches an existing heading, which is reused instead — and the returned headings are the
|
|
423
|
-
* entry headings in input order. */
|
|
424
|
-
export async function fileBugsOnMain(root, entries, subject, opts = {}) {
|
|
425
|
-
return withLock(mergeLockDir(root), () => withPrimaryWorktreeLockAsync(root, async () => {
|
|
426
|
-
const primary = await currentBranch(root);
|
|
427
|
-
const base = targetBranch(loadConfigSafe(root).config?.baseBranch, primary);
|
|
428
|
-
if (primary !== base) {
|
|
429
|
-
return { status: "skipped", reason: `your checkout is on ${primary ?? "a detached HEAD"}, not ${base}` };
|
|
430
|
-
}
|
|
431
|
-
const status = await gitTry(root, "status", "--porcelain", "--", "BUGS.md");
|
|
432
|
-
if (status === null)
|
|
433
|
-
return { status: "skipped", reason: "could not read git status" };
|
|
434
|
-
if (status.trim() !== "" && !status.startsWith("??")) {
|
|
435
|
-
return { status: "skipped", reason: "BUGS.md has uncommitted changes" };
|
|
436
|
-
}
|
|
437
|
-
const file = path.join(root, "BUGS.md");
|
|
438
|
-
let before;
|
|
439
|
-
try {
|
|
440
|
-
before = readTextIfExists(file);
|
|
441
|
-
}
|
|
442
|
-
catch (err) {
|
|
443
|
-
return { status: "skipped", reason: `BUGS.md could not be read: ${errorFirstLine(err)}` };
|
|
444
|
-
}
|
|
445
|
-
const open = before === null ? [] : parseEntryDetails(before, "Open").map((e) => e.title);
|
|
446
|
-
const headings = [];
|
|
447
|
-
let appended = 0;
|
|
448
|
-
const restoreBugs = async () => {
|
|
449
|
-
await gitTry(root, "reset", "-q", "--", "BUGS.md");
|
|
450
|
-
if (before === null)
|
|
451
|
-
fs.rmSync(file, { force: true });
|
|
452
|
-
else
|
|
453
|
-
writeTextAtomic(file, before);
|
|
454
|
-
};
|
|
455
|
-
try {
|
|
456
|
-
for (const entry of entries) {
|
|
457
|
-
if (entry.ifOpen !== undefined) {
|
|
458
|
-
const existing = entry.ifOpen(open);
|
|
459
|
-
if (existing !== undefined) {
|
|
460
|
-
headings.push(existing);
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
headings.push(appendBugEntryLocked(root, entry.title, entry.body));
|
|
465
|
-
appended++;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
catch (err) {
|
|
469
|
-
await restoreBugs();
|
|
470
|
-
return { status: "skipped", reason: `a BUGS.md entry could not be written: ${errorFirstLine(err)}` };
|
|
471
|
-
}
|
|
472
|
-
if (appended === 0)
|
|
473
|
-
return { status: "filed", headings };
|
|
474
|
-
const commitSubject = stampedSubject("operator", // vocabulary-ok: the tumwater(operator): commit prefix is a machine string
|
|
475
|
-
subject(appended));
|
|
476
|
-
try {
|
|
477
|
-
await git(root, "add", "--", "BUGS.md");
|
|
478
|
-
await git(root, ...(await committerIdentArgs(root)), "commit", ...commitAuthorArgs(), "--only", "-m", commitSubject, "--", "BUGS.md");
|
|
479
|
-
}
|
|
480
|
-
catch (err) {
|
|
481
|
-
await restoreBugs();
|
|
482
|
-
return { status: "skipped", reason: `the commit failed: ${errorFirstLine(err)}` };
|
|
483
|
-
}
|
|
484
|
-
return { status: "filed", headings };
|
|
485
|
-
}), opts.lockTimeoutMs);
|
|
486
|
-
}
|
|
487
|
-
/** File one BUGS.md `## Open` entry per newly quarantined test and commit BUGS.md on the base
|
|
488
|
-
* branch as one `tumwater(operator):` commit, delegating the locked write/commit to
|
|
489
|
-
* fileBugsOnMain and preserving the existing subject. A test whose quarantine entry is already
|
|
490
|
-
* under `## Open` (a filing whose markQuarantineFiled failed after the commit) gets that heading
|
|
491
|
-
* back and is not filed again — the dedupe runs inside fileBugsOnMain under the merge lock (via
|
|
492
|
-
* each entry's `ifOpen`), so concurrent quarantine filings cannot both append a duplicate, and
|
|
493
|
-
* the real skip on a dirty or off-base BUGS.md is enforced under the same lock. */
|
|
494
|
-
export async function fileQuarantineBugsOnMain(root, tests, opts = {}) {
|
|
495
|
-
const date = formatDate(new Date());
|
|
496
|
-
const toFile = [];
|
|
497
|
-
for (const { test, tail } of tests) {
|
|
498
|
-
const prefix = quarantineTitlePrefix(test);
|
|
499
|
-
const body = [
|
|
500
|
-
`The check failed on this test and then passed on a re-run of the same tree, so tumwater quarantined it: it counts as a known failure until this entry leaves \`## Open\`. Find why it is unstable and fix it.`,
|
|
501
|
-
...(tail ? ["", fencedTail(tail)] : []),
|
|
502
|
-
].join("\n");
|
|
503
|
-
toFile.push({
|
|
504
|
-
title: `${prefix}${date})`,
|
|
505
|
-
body,
|
|
506
|
-
// Dedupe under the lock, against the headings fileBugsOnMain reads: the reused heading
|
|
507
|
-
// (not a duplicate) is returned in headings and zipped back to this test below.
|
|
508
|
-
ifOpen: (open) => open.find((t) => t.startsWith(prefix)),
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
const outcome = await fileBugsOnMain(root, toFile,
|
|
512
|
-
// Size the subject by what was actually appended: tests whose `## Open` entry was already
|
|
513
|
-
// present are reused, not filed, so a mixed batch keeps the original "File N …" wording
|
|
514
|
-
// with N the count of new entries only.
|
|
515
|
-
(appended) => `File ${plural(appended, "quarantined flaky test", "quarantined flaky tests")} in BUGS.md`, opts);
|
|
516
|
-
if (outcome.status === "skipped")
|
|
517
|
-
return { status: "skipped", reason: outcome.reason };
|
|
518
|
-
// fileBugsOnMain's headings align with `tests` in order (reused headings or appended titles).
|
|
519
|
-
const bugs = new Map();
|
|
520
|
-
outcome.headings.forEach((heading, i) => bugs.set(tests[i].test, heading));
|
|
521
|
-
return { status: "filed", bugs };
|
|
522
|
-
}
|
|
@@ -6,7 +6,7 @@ import { fail, say, sayJson } from "../cli/cli-output.js";
|
|
|
6
6
|
import { parseCountFlag, rejectRepeatFlag, scanPositionArgs } from "../cli/cli-args.js";
|
|
7
7
|
import { DONE_DATE_RE, FIXED_DATE_RE, completionDate, donePlanEntries, fixedBugEntries, openBugEntries, plannedPlanEntries, ageInDays, entryAgeSuffix, entryMatches, filingDate, } from "./backlog.js";
|
|
8
8
|
import { backlogHolds, backlogInProgress, holdMark, inProgressMark } from "./backlog-eligibility.js";
|
|
9
|
-
import { groupBlockedSeries, partsLabel } from "./backlog-
|
|
9
|
+
import { groupBlockedSeries, partsLabel } from "./backlog-index-data.js";
|
|
10
10
|
import { rejectUnknownArgs } from "../cli/cli-flag-specs.js";
|
|
11
11
|
import { refuseTeamWorkspace } from "../cli/cli-repo-gate.js";
|
|
12
12
|
import { sectionNoun, showEntry } from "./backlog-write.js";
|
|
@@ -23,31 +23,12 @@ function positionRuns(positions) {
|
|
|
23
23
|
}
|
|
24
24
|
return runs.join(", ");
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
* the two booleans this shell consumes (repeats refused), `--help`/`-h` is tolerated, and any
|
|
33
|
-
* positional or other flag is refused through rejectUnknownArgs.
|
|
34
|
-
*
|
|
35
|
-
* `--grep <text>` searches the active entries too: it keeps the entries whose heading or
|
|
36
|
-
* body contains the text (case-insensitive), each under its unfiltered number so a printed
|
|
37
|
-
* position still reaches `--show`/`--edit`/`--cancel` — the active twin of the finished
|
|
38
|
-
* entries' `--done --grep`.
|
|
39
|
-
*
|
|
40
|
-
* `--ready` keeps the entries with no hold under their unfiltered numbers (composes with
|
|
41
|
-
* `--grep` and `--json`; refused with `--done`, whose entries carry no holds).
|
|
42
|
-
*
|
|
43
|
-
* `--done [-n <count>] [--grep <text>]` (each once; `-n` and `--grep` need `--done`) lists the
|
|
44
|
-
* newest finished entries instead (BUGS.md ## Fixed or PLANS.md ## Done, numbered, default 10)
|
|
45
|
-
* with `older` counting those left out; --json carries each entry's `position` and
|
|
46
|
-
* `completedOn` heading date. `--grep` keeps the entries whose heading or body contains the
|
|
47
|
-
* text (case-insensitive); each keeps its unfiltered number, so it is the `--show <n> --done`
|
|
48
|
-
* number, and `older` counts matches left out. `plan --list` prose
|
|
49
|
-
* groups a series' blocked parts into one line (groupBlockedSeries). */
|
|
50
|
-
export function listAndAnnounce(root, args, command, fileName, sectionTitle) {
|
|
26
|
+
/** Scans the `--list` flag vocabulary (`--list`, `--json`, `--done`, `--ready`, `-n`, `--grep`,
|
|
27
|
+
* each once, plus tolerated `--help`/`-h`) into the parsed locals and returns them with `rest`,
|
|
28
|
+
* the leftover args rejectUnknownArgs will refuse. Reads no entry, filters nothing, prints
|
|
29
|
+
* nothing; the `fail` calls it makes are the flag-usage refusals (a repeat or a `--grep`/`-n`
|
|
30
|
+
* with missing text). */
|
|
31
|
+
function parseListArgs(args, command) {
|
|
51
32
|
let json = false;
|
|
52
33
|
let list = false;
|
|
53
34
|
let done = false;
|
|
@@ -95,52 +76,48 @@ export function listAndAnnounce(root, args, command, fileName, sectionTitle) {
|
|
|
95
76
|
continue;
|
|
96
77
|
rest.push(arg);
|
|
97
78
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
older,
|
|
129
|
-
});
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const what = bugSection ? "fixed bugs" : "done plans";
|
|
133
|
-
if (shown.length === 0) {
|
|
134
|
-
say(pattern === null ? `no ${what}` : `no ${what} match "${pattern}"`);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const lines = shown.map((m) => `${m.position}. ${m.entry.title}`);
|
|
138
|
-
if (older > 0)
|
|
139
|
-
lines.push(`(${older} older; -n ${matches.length} shows more)`);
|
|
140
|
-
const header = pattern === null ? `${what} in ${fileName}, newest first:` : `${what} in ${fileName} matching "${pattern}", newest first:`;
|
|
141
|
-
say(`${header}\n${lines.join("\n")}`);
|
|
79
|
+
return { json, done, ready, count, grep, rest };
|
|
80
|
+
}
|
|
81
|
+
/** The `--done` half of the list shell: the newest finished entries (BUGS.md ## Fixed or
|
|
82
|
+
* PLANS.md ## Done), filtered by `--grep`, limited by `-n`, printed as `--json` or prose. */
|
|
83
|
+
function announceFinishedList(root, fileName, bugSection, grep, count, json) {
|
|
84
|
+
const numbered = (bugSection ? fixedBugEntries(root) : donePlanEntries(root)).map((entry, i) => ({
|
|
85
|
+
entry,
|
|
86
|
+
position: i + 1,
|
|
87
|
+
}));
|
|
88
|
+
const pattern = grep;
|
|
89
|
+
const matches = pattern === null ? numbered : numbered.filter((m) => entryMatches(m.entry, pattern));
|
|
90
|
+
const limit = count ?? 10;
|
|
91
|
+
const shown = matches.slice(0, limit);
|
|
92
|
+
const older = matches.length - shown.length;
|
|
93
|
+
if (json) {
|
|
94
|
+
const dateRe = bugSection ? FIXED_DATE_RE : DONE_DATE_RE;
|
|
95
|
+
sayJson({
|
|
96
|
+
entries: shown.map(({ entry: e, position }) => ({
|
|
97
|
+
position,
|
|
98
|
+
title: e.title,
|
|
99
|
+
body: e.body,
|
|
100
|
+
completedOn: completionDate(e.title, dateRe),
|
|
101
|
+
})),
|
|
102
|
+
older,
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const what = bugSection ? "fixed bugs" : "done plans";
|
|
107
|
+
if (shown.length === 0) {
|
|
108
|
+
say(pattern === null ? `no ${what}` : `no ${what} match "${pattern}"`);
|
|
142
109
|
return;
|
|
143
110
|
}
|
|
111
|
+
const lines = shown.map((m) => `${m.position}. ${m.entry.title}`);
|
|
112
|
+
if (older > 0)
|
|
113
|
+
lines.push(`(${older} older; -n ${matches.length} shows more)`);
|
|
114
|
+
const header = pattern === null ? `${what} in ${fileName}, newest first:` : `${what} in ${fileName} matching "${pattern}", newest first:`;
|
|
115
|
+
say(`${header}\n${lines.join("\n")}`);
|
|
116
|
+
}
|
|
117
|
+
/** The active half of the list shell: the open entries with their holds, in-progress claim
|
|
118
|
+
* marks and age suffixes, optionally reduced to the ready ones, as `--json` or prose, grouping
|
|
119
|
+
* a plan's blocked parts into one line (groupBlockedSeries). */
|
|
120
|
+
function announceActiveList(root, fileName, command, bugSection, sectionTitle, grep, ready, json) {
|
|
144
121
|
const entries = bugSection ? openBugEntries(root) : plannedPlanEntries(root);
|
|
145
122
|
const holds = backlogHolds(root);
|
|
146
123
|
const entryHolds = bugSection ? holds.bugs : holds.plans;
|
|
@@ -214,6 +191,48 @@ export function listAndAnnounce(root, args, command, fileName, sectionTitle) {
|
|
|
214
191
|
lines.push(hiddenNote);
|
|
215
192
|
say(`${noun}s in ${fileName}:\n${lines.join("\n")}`);
|
|
216
193
|
}
|
|
194
|
+
/** The shared shell of the `bug --list` and `plan --list` CLI cases — the read half of the
|
|
195
|
+
* operator queue whose `--edit`/`--cancel` flags address entries by position. Reads the same
|
|
196
|
+
* entries, in the same file order, that locateEntry numbers for `--edit`/`--cancel`, pairs
|
|
197
|
+
* each with its eligibility hold (backlogHolds), and prints them numbered from 1, or the
|
|
198
|
+
* command's `no …` line on an empty or missing file. Like `tumwater backlog` and `tumwater
|
|
199
|
+
* questions` it is read-only: no requireReadyRepo gate and no wake. `--list` and `--json` are
|
|
200
|
+
* the two booleans this shell consumes (repeats refused), `--help`/`-h` is tolerated, and any
|
|
201
|
+
* positional or other flag is refused through rejectUnknownArgs.
|
|
202
|
+
*
|
|
203
|
+
* `--grep <text>` searches the active entries too: it keeps the entries whose heading or
|
|
204
|
+
* body contains the text (case-insensitive), each under its unfiltered number so a printed
|
|
205
|
+
* position still reaches `--show`/`--edit`/`--cancel` — the active twin of the finished
|
|
206
|
+
* entries' `--done --grep`.
|
|
207
|
+
*
|
|
208
|
+
* `--ready` keeps the entries with no hold under their unfiltered numbers (composes with
|
|
209
|
+
* `--grep` and `--json`; refused with `--done`, whose entries carry no holds).
|
|
210
|
+
*
|
|
211
|
+
* `--done [-n <count>] [--grep <text>]` (each once; `-n` and `--grep` need `--done`) lists the
|
|
212
|
+
* newest finished entries instead (BUGS.md ## Fixed or PLANS.md ## Done, numbered, default 10)
|
|
213
|
+
* with `older` counting those left out; --json carries each entry's `position` and
|
|
214
|
+
* `completedOn` heading date. `--grep` keeps the entries whose heading or body contains the
|
|
215
|
+
* text (case-insensitive); each keeps its unfiltered number, so it is the `--show <n> --done`
|
|
216
|
+
* number, and `older` counts matches left out. `plan --list` prose
|
|
217
|
+
* groups a series' blocked parts into one line (groupBlockedSeries). */
|
|
218
|
+
export function listAndAnnounce(root, args, command, fileName, sectionTitle) {
|
|
219
|
+
const { json, done, ready, count, grep, rest } = parseListArgs(args, command);
|
|
220
|
+
// rejectUnknownArgs names a stray positional and an unknown flag with this command's (empty)
|
|
221
|
+
// valid-flag list: the vocabulary a list invocation accepts is exactly --list and --json,
|
|
222
|
+
// both consumed above.
|
|
223
|
+
rejectUnknownArgs(command, rest, []);
|
|
224
|
+
refuseTeamWorkspace(root);
|
|
225
|
+
if (count !== null && !done)
|
|
226
|
+
fail("-n needs --done (it limits the finished entries listed)");
|
|
227
|
+
// BUGS.md's `## Open` is the one section whose noun is "bug"; every other caller is a plan.
|
|
228
|
+
if (ready && done)
|
|
229
|
+
fail("--ready needs the active list (finished entries carry no holds)");
|
|
230
|
+
const bugSection = sectionTitle === "Open";
|
|
231
|
+
if (done)
|
|
232
|
+
announceFinishedList(root, fileName, bugSection, grep, count, json);
|
|
233
|
+
else
|
|
234
|
+
announceActiveList(root, fileName, command, bugSection, sectionTitle, grep, ready, json);
|
|
235
|
+
}
|
|
217
236
|
/** The shell of `bug --show` and `plan --show`: `--show <n>` (once), an optional `--json`
|
|
218
237
|
* and `--done` (each once; `--done` shows the Nth newest finished entry, as `--list --done`
|
|
219
238
|
* numbers them, with its heading stamp kept and `completedOn` in --json),
|