@joshuafolkken/kit 1.1383.0 → 1.1435.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/.claude/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun.md +1 -0
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/README.md +25 -0
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-LOJTBU5V.js → chunk-2G5F2YCD.js} +3 -3
- package/dist/hooks/{chunk-4MGHBTOM.js → chunk-3LE6VLSR.js} +25 -8
- package/dist/hooks/{chunk-Y5R47QHU.js → chunk-4BHUPFKF.js} +1 -1
- package/dist/hooks/{chunk-KC6LUZZ2.js → chunk-5SJSSCJG.js} +1 -1
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-RYP3TSUJ.js → chunk-7KMN5QB5.js} +3 -3
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-YKUITFT7.js → chunk-EMB55BDF.js} +4 -4
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-6MAW7BCM.js → chunk-JXQBK323.js} +2677 -190
- package/dist/hooks/{chunk-KCF2YE7O.js → chunk-MSYSUSY6.js} +5 -5
- package/dist/hooks/{chunk-LC56R53A.js → chunk-MU4LJFIV.js} +4 -4
- package/dist/hooks/{chunk-7VSZ2N4B.js → chunk-QL6QTPWN.js} +3 -3
- package/dist/hooks/{chunk-TT742SIO.js → chunk-XER6SSIB.js} +92 -19
- package/dist/hooks/{chunk-2X2ART5G.js → chunk-YW452ZAX.js} +500 -29
- package/dist/hooks/{chunk-6AC4C2IO.js → chunk-YZXYOAZ3.js} +187 -443
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +262 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +124 -15
- package/dist/hooks/stop-guard.js +98 -55
- package/dist/josh.js +63 -10
- package/package.json +4 -3
- package/prompts/review.md +22 -0
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/backlog/backlog-drive-cli.ts +2 -4
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +5 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-owner.ts +72 -5
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +16 -8
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +80 -5
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +130 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +13 -2
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +4 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +4 -1
- package/scripts/init/init-ai-copy.ts +33 -10
- package/scripts/init/init-basic.ts +8 -3
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +5 -23
- package/scripts/init/project-profile.ts +36 -7
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +30 -4
- package/scripts/init/start-steps.ts +77 -3
- package/scripts/init/start.ts +21 -13
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-labels.ts +40 -8
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +7 -0
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +25 -1
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/{openai-review-broker.ts → lane-session-broker.ts} +106 -28
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +2 -2
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +139 -37
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +58 -5
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +121 -0
- package/scripts/run/board/run-board-span.ts +49 -7
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +76 -13
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +53 -16
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-added.ts +1 -1
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-change.ts +1 -1
- package/scripts/run/carry/run-carry-cli.ts +39 -52
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry-ended.ts +9 -1
- package/scripts/run/carry/run-carry.ts +24 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +3 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +91 -0
- package/scripts/run/event/run-event-stream.ts +46 -9
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/run-stranded-detect.ts +58 -12
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +108 -74
- package/scripts/run/wake/run-wake-describe.ts +36 -2
- package/scripts/run/wake/run-wake-driver.ts +21 -5
- package/scripts/run/wake/run-wake-handoff.ts +1 -1
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { styleText } from 'node:util'
|
|
2
2
|
import { backlog_budget } from '#scripts/backlog/backlog-budget'
|
|
3
3
|
import { run_board_fit } from './run-board-fit'
|
|
4
|
+
import { run_board_fresh } from './run-board-fresh'
|
|
4
5
|
import { run_board_labels } from './run-board-labels'
|
|
5
6
|
import { run_board_layout, type BoardLayout, type BoardRow } from './run-board-layout'
|
|
6
7
|
import { run_board_machine, type MachineGauges } from './run-board-machine'
|
|
8
|
+
import { run_board_now, type NowLine } from './run-board-now'
|
|
9
|
+
import type { SessionOperations } from './run-board-operation-read'
|
|
7
10
|
import { run_board_render_slot } from './run-board-render-slot'
|
|
8
11
|
import type { RunActivity } from './run-board-status'
|
|
9
12
|
import type { LaneUsages } from './run-board-usage'
|
|
13
|
+
import type { UsualTimes } from './run-board-usual'
|
|
10
14
|
|
|
11
15
|
// The top of `run:board`: whether the run is moving, how long it has run, how fresh its last event is,
|
|
12
16
|
// and how far through the plan it is. Every time comes from a record (the carry's start, the stream's
|
|
13
|
-
// events); nothing here guesses one.
|
|
14
|
-
// second is the proof the board is live, so no `updated` line is drawn. **Always
|
|
15
|
-
// columns**: the run's mark and name over its repository's, the run's age over
|
|
16
|
-
// progress over the machine the run is on — so the first line reads as how far the
|
|
17
|
-
// second as what it runs on, a slow run apart from a stuck one.
|
|
18
|
-
// run
|
|
19
|
-
//
|
|
20
|
-
//
|
|
17
|
+
// events); nothing here guesses one. Two lines of symbols rather than sentences: the `⏰` that moves
|
|
18
|
+
// every second is the proof the board is live, so no `updated` line is drawn. **Always three lines,
|
|
19
|
+
// the first two in three columns**: the run's mark and name over its repository's, the run's age over
|
|
20
|
+
// the `💓`, then the progress over the machine the run is on — so the first line reads as how far the
|
|
21
|
+
// run is and the second as what it runs on, a slow run apart from a stuck one. The third says what the
|
|
22
|
+
// run is doing now (`run-board-now.ts`): the spinner, its time and its kind's icon under the second and
|
|
23
|
+
// third columns' icons and digits, so the mark itself never turns. All three are written the
|
|
24
|
+
// same while the run works and while it waits, so a wait is read off the mark, the `💓` and the third
|
|
25
|
+
// line's words rather than off a line that changes shape. A value not read yet is drawn `-`, never
|
|
26
|
+
// `0`, so a header still loading reads as unknown rather than as an empty run.
|
|
21
27
|
|
|
22
28
|
const { HEADER_ICONS, STATE_ICONS, WORDS, bar_of, minute_of, span_of } = run_board_labels
|
|
23
29
|
const { cell_of } = run_board_render_slot
|
|
30
|
+
const { padded } = run_board_fresh
|
|
31
|
+
const aged_fresh = run_board_fresh.aged
|
|
24
32
|
const UNKNOWN = '-'
|
|
25
33
|
const BLANK = ' '
|
|
26
34
|
const REPO_SEPARATOR = '/'
|
|
@@ -44,6 +52,8 @@ interface BoardHeader {
|
|
|
44
52
|
// Set once the run has ended: the title then says when, and how long it took.
|
|
45
53
|
ended_ms: number | undefined
|
|
46
54
|
activity: RunActivity
|
|
55
|
+
// The newest operation of each session of the run, `undefined` where none was read.
|
|
56
|
+
operations?: SessionOperations | undefined
|
|
47
57
|
layout: BoardLayout | undefined
|
|
48
58
|
// The total the board first saw, so arrivals since then read as `(+N)`.
|
|
49
59
|
baseline_total: number | undefined
|
|
@@ -55,6 +65,8 @@ interface BoardHeader {
|
|
|
55
65
|
machine: MachineGauges | undefined
|
|
56
66
|
// Each running lane's CPU and memory, `undefined` where none could be read.
|
|
57
67
|
usages?: LaneUsages | undefined
|
|
68
|
+
// How long each phase usually takes; a running row's phase time is colored against it.
|
|
69
|
+
usual?: UsualTimes | undefined
|
|
58
70
|
// The spinner's frame a running run and a running row turn, `undefined` where the output is not a
|
|
59
71
|
// terminal and they draw their still icons.
|
|
60
72
|
spinner: string | undefined
|
|
@@ -74,13 +86,15 @@ interface BoardCounts {
|
|
|
74
86
|
remaining: number
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
// `setup` is a run with no child running that is itself at work on a stage (`run-board-setup.ts`).
|
|
90
|
+
type RunMark = 'running' | 'setup' | 'idle' | 'human' | 'stopped'
|
|
78
91
|
|
|
79
92
|
type TextColor = Parameters<typeof styleText>[0]
|
|
80
93
|
|
|
81
94
|
// The run's state as a colored glyph before its name.
|
|
82
95
|
const RUN_MARKS: Readonly<Record<RunMark, { glyph: string; color: TextColor }>> = {
|
|
83
96
|
running: { glyph: '▶', color: 'green' },
|
|
97
|
+
setup: { glyph: '▶', color: 'green' },
|
|
84
98
|
idle: { glyph: '⏸', color: 'yellow' },
|
|
85
99
|
human: { glyph: '✋', color: 'magenta' },
|
|
86
100
|
stopped: { glyph: '■', color: 'gray' },
|
|
@@ -99,9 +113,16 @@ function counts_of(layout: BoardLayout): BoardCounts {
|
|
|
99
113
|
return { total, settled, parked, running, remaining: total - settled - running }
|
|
100
114
|
}
|
|
101
115
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
// The marks of a run that is working: they turn the spinner, and only they can go stale.
|
|
117
|
+
const WORKING_MARKS: ReadonlySet<RunMark> = new Set<RunMark>(['running', 'setup'])
|
|
118
|
+
|
|
119
|
+
// Nothing running: the run is at work on a stage of its own; else it is waiting on a person when all
|
|
120
|
+
// the plan has left is what `needs-decision` holds, or idle.
|
|
121
|
+
function quiet_mark(header: BoardHeader): RunMark {
|
|
122
|
+
const { layout } = header
|
|
123
|
+
|
|
124
|
+
if (header.activity.setup !== undefined) return 'setup'
|
|
125
|
+
|
|
105
126
|
return layout?.waves.length === 0 && layout.people.length > 0 ? 'human' : 'idle'
|
|
106
127
|
}
|
|
107
128
|
|
|
@@ -109,15 +130,14 @@ function quiet_mark(layout: BoardLayout | undefined): RunMark {
|
|
|
109
130
|
function mark_of(header: BoardHeader, running: number): RunMark {
|
|
110
131
|
if (header.activity.is_stopped) return 'stopped'
|
|
111
132
|
|
|
112
|
-
return running > 0 ? 'running' : quiet_mark(header
|
|
133
|
+
return running > 0 ? 'running' : quiet_mark(header)
|
|
113
134
|
}
|
|
114
135
|
|
|
115
|
-
//
|
|
116
|
-
function run_part(mark: RunMark
|
|
136
|
+
// The mark stands still in every state: the spinner turns on the third line.
|
|
137
|
+
function run_part(mark: RunMark): string {
|
|
117
138
|
const { glyph, color } = RUN_MARKS[mark]
|
|
118
|
-
const shown = mark === 'running' ? (spinner ?? glyph) : glyph
|
|
119
139
|
|
|
120
|
-
return styleText(color, `${
|
|
140
|
+
return styleText(color, `${glyph} ${RUN_NAME}`)
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
// The repository's own name — `kit` of `owner/kit` — under the run's, past the mark's columns, so a
|
|
@@ -129,6 +149,8 @@ function repo_part(header: BoardHeader, mark: RunMark): string {
|
|
|
129
149
|
return `${BLANK.repeat(indent)}${name}`
|
|
130
150
|
}
|
|
131
151
|
|
|
152
|
+
// A part aged as the `💓` is: yellow once stale, red once alarming — a row's newest activity too
|
|
153
|
+
// (`run-board-render-tail.ts`).
|
|
132
154
|
function aged(part: string, age_ms: number): string {
|
|
133
155
|
if (age_ms > ALARM_MS) return styleText('red', part)
|
|
134
156
|
|
|
@@ -153,26 +175,76 @@ function ended_part(header: BoardHeader): string {
|
|
|
153
175
|
}
|
|
154
176
|
|
|
155
177
|
// The `💓` under the run's age, in the `⏰`'s own format with its digits right-aligned to `width` —
|
|
156
|
-
// only a
|
|
157
|
-
// the minute it ended.
|
|
178
|
+
// only a working run can be stale, so only it is colored. Only an ended run draws anything else there:
|
|
179
|
+
// the minute it ended. Its digits are emphasized for a few seconds after a new event resets them
|
|
180
|
+
// (`run-board-fresh.ts`).
|
|
158
181
|
function beat_part(header: BoardHeader, mark: RunMark, width: number): string {
|
|
159
182
|
const age = age_of(header)
|
|
160
183
|
|
|
161
184
|
if (age === undefined) return ended_part(header)
|
|
162
185
|
|
|
163
|
-
const
|
|
186
|
+
const span = span_of(age)
|
|
187
|
+
const part = `${HEADER_ICONS.heartbeat} ${padded(span, width, aged_fresh(span, age, header))}`
|
|
164
188
|
|
|
165
|
-
return mark
|
|
189
|
+
return WORKING_MARKS.has(mark) ? aged(part, age) : part
|
|
166
190
|
}
|
|
167
191
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
192
|
+
// How long ago what the third line says happened, blank where no event gave it a time.
|
|
193
|
+
function now_age(now: NowLine | undefined): string {
|
|
194
|
+
const age = now?.age_ms
|
|
195
|
+
|
|
196
|
+
return age === undefined ? '' : span_of(age)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// How long ago what the third line says happened, counted to now: an ended run's age stops at its end,
|
|
200
|
+
// so its last words would otherwise stay fresh on every frame after it.
|
|
201
|
+
function fresh_age(now: NowLine | undefined, header: BoardHeader): number | undefined {
|
|
202
|
+
const age = now?.age_ms
|
|
203
|
+
|
|
204
|
+
return age === undefined ? undefined : age + header.now_ms - (header.ended_ms ?? header.now_ms)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The `💓`'s digits, blank where it draws none.
|
|
208
|
+
function beat_span(header: BoardHeader): string {
|
|
172
209
|
const age = age_of(header)
|
|
173
|
-
const width = Math.max(elapsed.length, age === undefined ? 0 : span_of(age).length)
|
|
174
210
|
|
|
175
|
-
return
|
|
211
|
+
return age === undefined ? '' : span_of(age)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The third line's time: the spinner in the `⏰`'s and `💓`'s column — turning only while the run
|
|
215
|
+
// works, so a watch or an end stands blank — then its age, digits under theirs, emphasized while what
|
|
216
|
+
// the line says is fresh.
|
|
217
|
+
function now_time(
|
|
218
|
+
header: BoardHeader,
|
|
219
|
+
mark: RunMark,
|
|
220
|
+
now: NowLine | undefined,
|
|
221
|
+
width: number,
|
|
222
|
+
): string {
|
|
223
|
+
const spinner = WORKING_MARKS.has(mark) ? header.spinner : undefined
|
|
224
|
+
const icon = cell_of(spinner, run_board_fit.width_of(HEADER_ICONS.heartbeat))
|
|
225
|
+
const age = now_age(now)
|
|
226
|
+
|
|
227
|
+
return `${icon} ${padded(age, width, aged_fresh(age, fresh_age(now, header), header))}`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// What the third line says, emphasized while it is fresh.
|
|
231
|
+
function now_text(now: NowLine, header: BoardHeader): string {
|
|
232
|
+
return `${now.icon} ${aged_fresh(now.text, fresh_age(now, header), header)}`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// A run's age over the `💓` — or over the minute an ended run ended — over the third line's age: the
|
|
236
|
+
// shorter lengths are right-aligned to the longest, so the digits stand in one column when only one has
|
|
237
|
+
// passed ten hours.
|
|
238
|
+
function time_parts(header: BoardHeader, mark: RunMark, now: NowLine | undefined): Array<string> {
|
|
239
|
+
const elapsed = span_of((header.ended_ms ?? header.now_ms) - header.started_ms)
|
|
240
|
+
const age = now_age(now)
|
|
241
|
+
const width = Math.max(...[elapsed, beat_span(header), age].map((span) => span.length))
|
|
242
|
+
|
|
243
|
+
return [
|
|
244
|
+
`${HEADER_ICONS.elapsed} ${elapsed.padStart(width)}`,
|
|
245
|
+
beat_part(header, mark, width),
|
|
246
|
+
now_time(header, mark, now, width),
|
|
247
|
+
]
|
|
176
248
|
}
|
|
177
249
|
|
|
178
250
|
// A plan fetch is said only when it failed — the board otherwise keeps quiet about a routine read.
|
|
@@ -250,6 +322,27 @@ function progress_part(counts: BoardCounts, header: BoardHeader): string {
|
|
|
250
322
|
return parts.filter((part) => part !== undefined).join(GAP)
|
|
251
323
|
}
|
|
252
324
|
|
|
325
|
+
// The children drawn as running: only their operations are the run's newest.
|
|
326
|
+
function running_issues(layout: BoardLayout): ReadonlySet<number> {
|
|
327
|
+
const running = layout.active.filter((row) => row.state === 'running')
|
|
328
|
+
|
|
329
|
+
return new Set(running.map((row) => row.number))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// What the run is doing now (`run-board-now.ts`).
|
|
333
|
+
function now_line(counts: BoardCounts, header: BoardHeader): NowLine | undefined {
|
|
334
|
+
return run_board_now.line_of({
|
|
335
|
+
now_ms: header.now_ms,
|
|
336
|
+
ended_ms: header.ended_ms,
|
|
337
|
+
activity: header.activity,
|
|
338
|
+
operations: header.operations,
|
|
339
|
+
children: running_issues(layout_of(header)),
|
|
340
|
+
running: counts.running,
|
|
341
|
+
waiting: is_plan_read(header) ? counts.remaining : undefined,
|
|
342
|
+
people: layout_of(header).people.length,
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
253
346
|
// The gauges the machine line draws: every one on a screen, the memory alone in a chat.
|
|
254
347
|
function machine_of(header: BoardHeader): MachineGauges | undefined {
|
|
255
348
|
const { machine } = header
|
|
@@ -271,28 +364,37 @@ function machine_part(counts: BoardCounts, header: BoardHeader): string {
|
|
|
271
364
|
return run_board_machine.line_of(machine_of(header), width) ?? MACHINE_UNKNOWN
|
|
272
365
|
}
|
|
273
366
|
|
|
274
|
-
//
|
|
367
|
+
// The lines' cells of one column, the narrower padded to the wider as the terminal counts them.
|
|
275
368
|
function column_of(cells: ReadonlyArray<string>): Array<string> {
|
|
276
369
|
const width = Math.max(...cells.map((cell) => run_board_fit.width_of(cell)))
|
|
277
370
|
|
|
278
371
|
return cells.map((cell) => cell_of(cell, width))
|
|
279
372
|
}
|
|
280
373
|
|
|
281
|
-
// The run's name, age and progress, then its repository, heartbeat and machine under them,
|
|
282
|
-
//
|
|
374
|
+
// The run's name, age and progress, then its repository, heartbeat and machine under them, then what
|
|
375
|
+
// it is doing now — its time under theirs, its kind's icon under the `⚡`, the name's column left blank
|
|
376
|
+
// and no label — in every state the board can be in. A stream with no event yet leaves it blank.
|
|
283
377
|
function header_lines(header: BoardHeader): Array<string> {
|
|
284
378
|
const counts = counts_of(layout_of(header))
|
|
285
379
|
const mark = mark_of(header, counts.running)
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
380
|
+
const now = now_line(counts, header)
|
|
381
|
+
const names = column_of([run_part(mark), repo_part(header, mark), ''])
|
|
382
|
+
const times = column_of(time_parts(header, mark, now))
|
|
383
|
+
const lasts = [progress_part(counts, header), machine_part(counts, header)]
|
|
384
|
+
const third =
|
|
385
|
+
now === undefined ? '' : [names.at(-1), times.at(-1), now_text(now, header)].join(GAP)
|
|
386
|
+
|
|
387
|
+
return [...lasts.map((last, line) => [names[line], times[line], last].join(GAP)), third]
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// The third line's kind as the legend names it, `undefined` where the line's own words name it.
|
|
391
|
+
function now_legend(header: BoardHeader): string | undefined {
|
|
392
|
+
const now = now_line(counts_of(layout_of(header)), header)
|
|
291
393
|
|
|
292
|
-
return
|
|
394
|
+
return now?.legend === undefined ? undefined : `${now.icon} ${now.legend}`
|
|
293
395
|
}
|
|
294
396
|
|
|
295
|
-
const run_board_header = { counts_of, header_lines, is_plan_read }
|
|
397
|
+
const run_board_header = { aged, counts_of, header_lines, is_plan_read, now_legend }
|
|
296
398
|
|
|
297
399
|
export { run_board_header }
|
|
298
400
|
export type { BoardCounts, BoardForm, BoardHeader }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WriteStream } from 'node:tty'
|
|
2
2
|
import { styleText } from 'node:util'
|
|
3
3
|
import type { FiledKind } from '#scripts/run/event/run-event-filed'
|
|
4
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
4
5
|
import cli_spinners from 'cli-spinners'
|
|
5
6
|
import type { BoardNote } from './run-board-notes'
|
|
6
7
|
import type { Phase } from './run-board-phase'
|
|
@@ -67,6 +68,12 @@ const WORDS = {
|
|
|
67
68
|
park: 'park',
|
|
68
69
|
note: 'note',
|
|
69
70
|
found_during: ' (found during {n})',
|
|
71
|
+
// What the run is doing now, where no stage of its own says it.
|
|
72
|
+
watching: 'watching',
|
|
73
|
+
pool_empty: 'pool empty',
|
|
74
|
+
on_person: 'waiting on a person',
|
|
75
|
+
operation: 'operation',
|
|
76
|
+
ended: 'ended',
|
|
70
77
|
// The session a stopped run waits in, and what closing the board leaves.
|
|
71
78
|
resume: 'stopped — resume with',
|
|
72
79
|
keeps_running: 'Closing this screen leaves the run going · reopen with `pnpm josh backlogrun`',
|
|
@@ -175,6 +182,24 @@ const HEADER_ICONS = {
|
|
|
175
182
|
heartbeat: '💓',
|
|
176
183
|
} as const
|
|
177
184
|
|
|
185
|
+
// The kind of what the header's third line says, under the `⚡` (joshuafolkken/kit#3767): a call a
|
|
186
|
+
// session made, the run watching its pool, the run ended, or each of the run's own stages, keyed by the
|
|
187
|
+
// stage's words before any issue. A child's event draws its row's phase icon instead. The run ended
|
|
188
|
+
// draws the header's own end mark — 🏁 is already a done row's state icon.
|
|
189
|
+
const NOW_ICONS = { operation: '🔧', watching: '🔭', ended: HEADER_ICONS.ended } as const
|
|
190
|
+
const { STAGE, LANE_STAGE } = run_event_setup
|
|
191
|
+
const STAGE_ICONS: Readonly<Partial<Record<string, string>>> = {
|
|
192
|
+
[STAGE.STARTING]: '🚀',
|
|
193
|
+
[STAGE.PLANNING]: '📝',
|
|
194
|
+
[STAGE.HANDING_OFF]: '🤝',
|
|
195
|
+
[STAGE.JUDGING]: '🤔',
|
|
196
|
+
[STAGE.SESSION]: '💭',
|
|
197
|
+
[STAGE.NO_CHILD]: '🚫',
|
|
198
|
+
[LANE_STAGE.OPENING]: '🚪',
|
|
199
|
+
[LANE_STAGE.INSTALLING]: '📥',
|
|
200
|
+
[LANE_STAGE.DISPATCHING]: '📤',
|
|
201
|
+
}
|
|
202
|
+
|
|
178
203
|
// **The machine gauges' three colors, decided here alone**: Activity Monitor's green, yellow and red
|
|
179
204
|
// in the system's dark-mode values. A palette's `green` draws blue and its `red` orange in a VSCode
|
|
180
205
|
// terminal, so the normal gauge would read as the cyan plan bar and the busy one as yellow.
|
|
@@ -214,17 +239,32 @@ function elapsed_of(ms: number): string {
|
|
|
214
239
|
return `${two_digits(minutes)}:${two_digits(seconds % SECONDS_PER_MINUTE)}`
|
|
215
240
|
}
|
|
216
241
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
function span_of(ms: number): string {
|
|
242
|
+
// A length as its hours, and its minutes and seconds within the hour; a negative span reads as zero.
|
|
243
|
+
function span_fields(ms: number): [hours: number, minutes: number, seconds: number] {
|
|
220
244
|
const seconds = Math.floor(Math.max(0, ms) / MS_PER_SECOND)
|
|
221
245
|
const minutes = Math.floor(seconds / SECONDS_PER_MINUTE)
|
|
222
246
|
const hours = Math.floor(minutes / MINUTES_PER_HOUR)
|
|
223
|
-
|
|
247
|
+
|
|
248
|
+
return [hours, minutes % MINUTES_PER_HOUR, seconds % SECONDS_PER_MINUTE]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// How long the run has run or its stream has been silent, as `H:MM:SS` with the hours not padded; a negative
|
|
252
|
+
// span reads as zero. Always three fields, so a run under an hour (`0:03:25`) never reads as a time.
|
|
253
|
+
function span_of(ms: number): string {
|
|
254
|
+
const [hours, ...tail] = span_fields(ms)
|
|
224
255
|
|
|
225
256
|
return [String(hours), ...tail.map((value) => two_digits(value))].join(':')
|
|
226
257
|
}
|
|
227
258
|
|
|
259
|
+
// How long the run has stood at its stage, as `M:SS` with the minutes not padded, and as `span_of`'s
|
|
260
|
+
// `H:MM:SS` from an hour on. It stands beside the stage's words, never in the header's time column, so
|
|
261
|
+
// its one separator is not read there as a time of day.
|
|
262
|
+
function stage_span_of(ms: number): string {
|
|
263
|
+
const [hours, minutes, seconds] = span_fields(ms)
|
|
264
|
+
|
|
265
|
+
return hours === 0 ? `${String(minutes)}:${two_digits(seconds)}` : span_of(ms)
|
|
266
|
+
}
|
|
267
|
+
|
|
228
268
|
function is_shade(color: Paint): color is Shade {
|
|
229
269
|
return typeof color === 'object' && 'rgb' in color
|
|
230
270
|
}
|
|
@@ -281,9 +321,11 @@ const run_board_labels = {
|
|
|
281
321
|
KIND_WORDS,
|
|
282
322
|
NOTES_ICON,
|
|
283
323
|
NOTE_ICONS,
|
|
324
|
+
NOW_ICONS,
|
|
284
325
|
PHASE_ICONS,
|
|
285
326
|
PHASE_WORDS,
|
|
286
327
|
SPINNER_INTERVAL_MS,
|
|
328
|
+
STAGE_ICONS,
|
|
287
329
|
STATE_ICONS,
|
|
288
330
|
WAITS_ICON,
|
|
289
331
|
WORDS,
|
|
@@ -294,6 +336,7 @@ const run_board_labels = {
|
|
|
294
336
|
painted,
|
|
295
337
|
span_of,
|
|
296
338
|
spinner_of,
|
|
339
|
+
stage_span_of,
|
|
297
340
|
}
|
|
298
341
|
|
|
299
342
|
export { run_board_labels }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
2
|
+
import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
|
|
3
|
+
import { run_ship_stage } from '#scripts/run/ship/run-ship-stage'
|
|
4
|
+
import { run_board_phase, type Phase } from './run-board-phase'
|
|
5
|
+
|
|
6
|
+
// Each running child's newest event of its own — the `lane-phase` the hook writes or a `ship-stage` its
|
|
7
|
+
// ship writes — in words, with the phase whose icon its row's track draws. A child inside a long
|
|
8
|
+
// `josh ship` makes no call of its own while the ship writes on, so the header's third line reads the
|
|
9
|
+
// newer of this and the child's newest operation (joshuafolkken/kit#3767).
|
|
10
|
+
|
|
11
|
+
interface LaneEvent {
|
|
12
|
+
phase: Phase
|
|
13
|
+
// The event's words after its issue: `implement`, `ship gate`, `ship gate failed`.
|
|
14
|
+
words: string
|
|
15
|
+
at_ms: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const KIND = run_event_stream.EVENT_KIND
|
|
19
|
+
const LANE_KINDS: ReadonlySet<string> = new Set([KIND.LANE_PHASE, KIND.SHIP_STAGE])
|
|
20
|
+
const WORD_SEPARATOR = ' '
|
|
21
|
+
const SHIP = 'ship'
|
|
22
|
+
|
|
23
|
+
// A ship's stage reads as `ship <stage>` once started and names how it ended after that; a lane's phase
|
|
24
|
+
// reads as its own word.
|
|
25
|
+
function words_of(event: RunEvent): string {
|
|
26
|
+
const [, stage = '', phase] = event.text.split(WORD_SEPARATOR)
|
|
27
|
+
|
|
28
|
+
if (event.kind === KIND.LANE_PHASE) return stage
|
|
29
|
+
|
|
30
|
+
const words = [SHIP, stage]
|
|
31
|
+
|
|
32
|
+
return (phase === run_ship_stage.PHASE.START ? words : [...words, phase]).join(WORD_SEPARATOR)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function lane_event_of(event: RunEvent): LaneEvent | undefined {
|
|
36
|
+
const phase = LANE_KINDS.has(event.kind) ? run_board_phase.stage_phase_of(event) : undefined
|
|
37
|
+
|
|
38
|
+
return phase === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: { phase, words: words_of(event), at_ms: Date.parse(event.at) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The stream's bound keeps each issue's newest stage start apart from the rest, so the order of the
|
|
44
|
+
// lines is not their age.
|
|
45
|
+
function is_newer(lane: LaneEvent, held: LaneEvent | undefined): boolean {
|
|
46
|
+
return held === undefined || lane.at_ms >= held.at_ms
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The newest lane event of every issue the stream names one for.
|
|
50
|
+
function newest_of(events: ReadonlyArray<RunEvent>): ReadonlyMap<number, LaneEvent> {
|
|
51
|
+
const newest = new Map<number, LaneEvent>()
|
|
52
|
+
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
const lane = lane_event_of(event)
|
|
55
|
+
const issue = Number(run_event_scope.issue_named(event))
|
|
56
|
+
|
|
57
|
+
if (lane !== undefined && is_newer(lane, newest.get(issue))) newest.set(issue, lane)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return newest
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const run_board_lane_event = { newest_of }
|
|
64
|
+
|
|
65
|
+
export { run_board_lane_event }
|
|
66
|
+
export type { LaneEvent }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Declared } from '#scripts/backlog/backlog-overlap'
|
|
1
2
|
import { backlog_plan, type PlanContext } from '#scripts/backlog/backlog-plan'
|
|
2
3
|
import type { WavePlan } from '#scripts/backlog/backlog-waves'
|
|
3
4
|
import type { EpicChild } from '#scripts/epic/epic-graph'
|
|
@@ -25,8 +26,22 @@ interface BoardPlan {
|
|
|
25
26
|
// Each issue's label names as the open listing read them, so a running row is drawn as its labels
|
|
26
27
|
// say it stopped.
|
|
27
28
|
labels: ReadonlyMap<number, ReadonlyArray<string>>
|
|
29
|
+
// The paths each issue's body declares (`Plan.scope.declared`), which a waiting row is checked
|
|
30
|
+
// against a running one by; a plan read without them checks none.
|
|
31
|
+
declared?: Declared | undefined
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
// Why a waiting row waits, or the stage its lane's launch is at, in the board's own words.
|
|
35
|
+
interface RowReason {
|
|
36
|
+
text: string
|
|
37
|
+
// When the row entered a stage, where the reason is one — how long it has stood there is counted
|
|
38
|
+
// from it.
|
|
39
|
+
since_ms?: number | undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// What a waiting child's row says after its blockers; `undefined` says nothing.
|
|
43
|
+
type ReasonOf = (child: EpicChild) => RowReason | undefined
|
|
44
|
+
|
|
30
45
|
interface BoardRow {
|
|
31
46
|
number: number
|
|
32
47
|
title: string | undefined
|
|
@@ -36,6 +51,8 @@ interface BoardRow {
|
|
|
36
51
|
status: ItemStatus | undefined
|
|
37
52
|
// The standing blockers this row still waits on, as the plan names them.
|
|
38
53
|
waits: ReadonlyArray<string>
|
|
54
|
+
// Why the row waits beyond its blockers (`run-board-reason.ts`); a row the run has touched has none.
|
|
55
|
+
reason?: RowReason | undefined
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
type WaveEntry =
|
|
@@ -60,6 +77,11 @@ interface Overlay {
|
|
|
60
77
|
statuses: ReadonlyMap<number, ItemStatus>
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
// The overlay a layout is drawn from: the plan under the statuses, and what each waiting row says.
|
|
81
|
+
interface Drawn extends Overlay {
|
|
82
|
+
reason_of: ReasonOf | undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
const SETTLED_STATES: ReadonlySet<ItemState> = new Set(['merged', 'parked', 'done'])
|
|
64
86
|
|
|
65
87
|
function is_settled(state: ItemState): boolean {
|
|
@@ -80,13 +102,20 @@ function reference_of(edge: IssueReference, repo: string): string {
|
|
|
80
102
|
return edge.repo === repo ? String(edge.number) : issue_cite.plain(edge.number, edge.repo)
|
|
81
103
|
}
|
|
82
104
|
|
|
83
|
-
|
|
105
|
+
// Whether a blocker the plan names still stands for `child` — the wait its row draws as 🔗.
|
|
106
|
+
function is_blocked(child: EpicChild, overlay: Overlay): boolean {
|
|
107
|
+
return child.blocked_by.some((edge) => is_waiting_on(edge, overlay))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function plan_row(child: EpicChild, state: ItemState, overlay: Drawn): BoardRow {
|
|
84
111
|
const { context } = overlay.plan
|
|
85
112
|
const waits = child.blocked_by
|
|
86
113
|
.filter((edge) => is_waiting_on(edge, overlay))
|
|
87
114
|
.map((edge) => reference_of(edge, context.repo))
|
|
115
|
+
const reason = state === 'waiting' ? overlay.reason_of?.(child) : undefined
|
|
88
116
|
|
|
89
117
|
return {
|
|
118
|
+
...(reason !== undefined && { reason }),
|
|
90
119
|
number: child.number,
|
|
91
120
|
title: context.titles.get(child.number),
|
|
92
121
|
kind: run_board_kind.kind_of(child.labels),
|
|
@@ -119,7 +148,7 @@ function active_rows(statuses: ReadonlyMap<number, ItemStatus>, read: RowRead):
|
|
|
119
148
|
function place_in_epic(
|
|
120
149
|
entries: Array<WaveEntry>,
|
|
121
150
|
epic: number,
|
|
122
|
-
overlay:
|
|
151
|
+
overlay: Drawn,
|
|
123
152
|
row: BoardRow,
|
|
124
153
|
): void {
|
|
125
154
|
const group = entries.find((entry) => entry.kind === 'epic' && entry.epic === epic)
|
|
@@ -144,7 +173,7 @@ function wave_state(child: EpicChild): ItemState {
|
|
|
144
173
|
|
|
145
174
|
// One wave, its rows in the plan's order, an epic's children gathered under the epic where the first
|
|
146
175
|
// of them falls.
|
|
147
|
-
function wave_entries(wave: ReadonlyArray<EpicChild>, overlay:
|
|
176
|
+
function wave_entries(wave: ReadonlyArray<EpicChild>, overlay: Drawn): Array<WaveEntry> {
|
|
148
177
|
const entries: Array<WaveEntry> = []
|
|
149
178
|
|
|
150
179
|
for (const child of wave) {
|
|
@@ -158,7 +187,7 @@ function wave_entries(wave: ReadonlyArray<EpicChild>, overlay: Overlay): Array<W
|
|
|
158
187
|
return entries
|
|
159
188
|
}
|
|
160
189
|
|
|
161
|
-
function unreached_rows(overlay:
|
|
190
|
+
function unreached_rows(overlay: Drawn, is_people: boolean): Array<BoardRow> {
|
|
162
191
|
const state: ItemState = is_people ? 'human' : 'waiting'
|
|
163
192
|
|
|
164
193
|
return overlay.plan.waves.unreached
|
|
@@ -166,8 +195,25 @@ function unreached_rows(overlay: Overlay, is_people: boolean): Array<BoardRow> {
|
|
|
166
195
|
.map((child) => plan_row(child, state, overlay))
|
|
167
196
|
}
|
|
168
197
|
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
// Every issue the plan holds, wave or unreached.
|
|
199
|
+
function children_of(plan: BoardPlan): Array<EpicChild> {
|
|
200
|
+
return [...plan.waves.waves.flat(), ...plan.waves.unreached]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// The children whose rows wait on the run rather than on a person: the run has not touched them, and
|
|
204
|
+
// no decision is asked of anyone.
|
|
205
|
+
function waiting_children(overlay: Overlay): Array<EpicChild> {
|
|
206
|
+
return children_of(overlay.plan).filter(
|
|
207
|
+
(child) => is_untouched(child, overlay) && !is_human(child),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function layout_of(
|
|
212
|
+
plan: BoardPlan,
|
|
213
|
+
statuses: ReadonlyMap<number, ItemStatus>,
|
|
214
|
+
reason_of?: ReasonOf,
|
|
215
|
+
): BoardLayout {
|
|
216
|
+
const overlay = { plan, statuses, reason_of }
|
|
171
217
|
const waves = plan.waves.waves.map((wave) =>
|
|
172
218
|
wave_entries(
|
|
173
219
|
wave.filter((child) => is_untouched(child, overlay)),
|
|
@@ -195,9 +241,9 @@ function local_layout_of(statuses: ReadonlyMap<number, ItemStatus>): BoardLayout
|
|
|
195
241
|
}
|
|
196
242
|
}
|
|
197
243
|
|
|
198
|
-
//
|
|
244
|
+
// The run's scope a lane is checked against.
|
|
199
245
|
function numbers_of(plan: BoardPlan): ReadonlySet<number> {
|
|
200
|
-
return new Set(
|
|
246
|
+
return new Set(children_of(plan).map((child) => child.number))
|
|
201
247
|
}
|
|
202
248
|
|
|
203
249
|
// Every row the layout draws, an epic's children among them — what the header counts and the legend reads.
|
|
@@ -211,12 +257,14 @@ function rows_of(layout: BoardLayout): Array<BoardRow> {
|
|
|
211
257
|
|
|
212
258
|
const run_board_layout = {
|
|
213
259
|
EMPTY_LAYOUT,
|
|
260
|
+
is_blocked,
|
|
214
261
|
is_settled,
|
|
215
262
|
layout_of,
|
|
216
263
|
local_layout_of,
|
|
217
264
|
numbers_of,
|
|
218
265
|
rows_of,
|
|
266
|
+
waiting_children,
|
|
219
267
|
}
|
|
220
268
|
|
|
221
269
|
export { run_board_layout }
|
|
222
|
-
export type { BoardLayout, BoardPlan, BoardRow, WaveEntry }
|
|
270
|
+
export type { BoardLayout, BoardPlan, BoardRow, Overlay, ReasonOf, RowReason, WaveEntry }
|