@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
|
@@ -4,10 +4,9 @@ import {
|
|
|
4
4
|
type NotesPart,
|
|
5
5
|
type PlanLine,
|
|
6
6
|
type PlanSection,
|
|
7
|
-
type RowYield,
|
|
8
7
|
type TerminalSize,
|
|
9
|
-
type YieldStage,
|
|
10
8
|
} from './run-board-fit'
|
|
9
|
+
import { run_board_fresh } from './run-board-fresh'
|
|
11
10
|
import { run_board_header, type BoardHeader } from './run-board-header'
|
|
12
11
|
import { run_board_labels } from './run-board-labels'
|
|
13
12
|
import {
|
|
@@ -16,13 +15,17 @@ import {
|
|
|
16
15
|
type BoardRow,
|
|
17
16
|
type WaveEntry,
|
|
18
17
|
} from './run-board-layout'
|
|
18
|
+
import type { Link } from './run-board-link'
|
|
19
19
|
import type { BoardNote } from './run-board-notes'
|
|
20
|
-
import
|
|
20
|
+
import { run_board_render_checks } from './run-board-render-checks'
|
|
21
21
|
import { run_board_render_legend } from './run-board-render-legend'
|
|
22
22
|
import { run_board_render_notes } from './run-board-render-notes'
|
|
23
23
|
import { run_board_render_slot, type UsageSlot } from './run-board-render-slot'
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
24
|
+
import { run_board_render_tail } from './run-board-render-tail'
|
|
25
|
+
import { run_board_render_thanks } from './run-board-render-thanks'
|
|
26
|
+
import { run_board_render_title } from './run-board-render-title'
|
|
27
|
+
import { run_board_row_yield } from './run-board-row-yield'
|
|
28
|
+
import type { Thanks } from './run-board-thanks-csv'
|
|
26
29
|
|
|
27
30
|
// The whole `run:board` screen as lines — pure, so what a person sees is tested apart from the
|
|
28
31
|
// terminal. The header says where the run is; the sections below it follow the plan's order; the
|
|
@@ -31,14 +34,14 @@ import { run_board_track } from './run-board-track'
|
|
|
31
34
|
// what differs between rows. Every word is English whatever the session language: one set of words
|
|
32
35
|
// reads the same on every board.
|
|
33
36
|
|
|
34
|
-
const { FILED_KIND_ICONS, KIND_BLANK, NOTES_ICON, PHASE_ICONS, STATE_ICONS
|
|
37
|
+
const { FILED_KIND_ICONS, KIND_BLANK, NOTES_ICON, NOW_ICONS, PHASE_ICONS, STATE_ICONS } =
|
|
35
38
|
run_board_labels
|
|
39
|
+
const { WAITS_ICON, WORDS } = run_board_labels
|
|
36
40
|
const { clock_of, elapsed_of } = run_board_labels
|
|
37
41
|
const { NOTE_LIMIT } = run_board_render_notes
|
|
38
42
|
const { cell_of, column_width, slot_of, usage_slot } = run_board_render_slot
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const ELLIPSIS = '…'
|
|
43
|
+
const { ELLIPSIS, TITLE_LIMIT, fitted, reason_text, title_of } = run_board_render_title
|
|
44
|
+
const { padded } = run_board_fresh
|
|
42
45
|
const GAP = ' '
|
|
43
46
|
const INDENT = ' '
|
|
44
47
|
const BRANCH = ' ├ '
|
|
@@ -59,6 +62,9 @@ interface BoardView extends FrameBounds {
|
|
|
59
62
|
notes: ReadonlyArray<BoardNote>
|
|
60
63
|
// The session a stopped run resumes from.
|
|
61
64
|
resume?: string | undefined
|
|
65
|
+
// The reviewers thanked above the legend, and the link a name with a page is wrapped in.
|
|
66
|
+
thanks?: Thanks | undefined
|
|
67
|
+
link?: Link | undefined
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
// What every row of one frame is drawn with: the header, how wide the time column is, so a three-digit
|
|
@@ -69,11 +75,10 @@ interface RowFrame {
|
|
|
69
75
|
time_width: number
|
|
70
76
|
usage: UsageSlot | undefined
|
|
71
77
|
slot_width: number
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
return title.length > TITLE_LIMIT ? `${title.slice(0, TITLE_LIMIT - 1)}${ELLIPSIS}` : title
|
|
78
|
+
// The columns a row's title is cut to: the title column's, or fewer where a row gives some up.
|
|
79
|
+
title_limit: number
|
|
80
|
+
// The pane's width where the frame is kept within one.
|
|
81
|
+
columns: number | undefined
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
// A row drawn before the plan is read has no title yet, and says so.
|
|
@@ -105,45 +110,37 @@ function time_of(row: BoardRow, now_ms: number): string | undefined {
|
|
|
105
110
|
return elapsed === undefined ? undefined : elapsed_of(elapsed)
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
// The phases a row's track draws — every one the child passed while running, kept once it settles.
|
|
109
|
-
function drawn_phases(row: BoardRow): ReadonlyArray<Phase> {
|
|
110
|
-
return row.status?.track ?? []
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// A row's phases as its track, drawn last: it grows with every round, so the
|
|
114
|
-
// columns before it stay where they are whatever its length. A settled row's ends on its state icon.
|
|
115
|
-
function track_text(row: BoardRow): string | undefined {
|
|
116
|
-
const phases = drawn_phases(row)
|
|
117
|
-
|
|
118
|
-
return phases.length === 0 ? undefined : run_board_track.track_of(phases, row.state)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
113
|
function waits_of(row: BoardRow, header: BoardHeader): string | undefined {
|
|
122
114
|
if (row.waits.length === 0) return undefined
|
|
123
115
|
|
|
124
116
|
return `${WAITS_ICON} ${row.waits.map((reference) => header.link(reference)).join(', ')}`
|
|
125
117
|
}
|
|
126
118
|
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
function
|
|
119
|
+
// A running row's part that changed at `at_ms`, emphasized while fresh (`run-board-fresh.ts`) — its
|
|
120
|
+
// start and its phase; a settled row's are long past.
|
|
121
|
+
function running_fresh(
|
|
130
122
|
row: BoardRow,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
text: string,
|
|
124
|
+
at_ms: number | undefined,
|
|
125
|
+
header: BoardHeader,
|
|
126
|
+
): string {
|
|
127
|
+
return row.state === 'running' ? run_board_fresh.since(text, at_ms, header) : text
|
|
128
|
+
}
|
|
137
129
|
|
|
138
|
-
|
|
130
|
+
// The title padded to the title column and the time to the time column — blanks where a row has none —
|
|
131
|
+
// so the times form one column, and what follows them starts in one column on every row.
|
|
132
|
+
function timed_parts(row: BoardRow, frame: RowFrame): Array<string> {
|
|
133
|
+
const time = time_of(row, frame.header.now_ms) ?? ''
|
|
134
|
+
const title = title_of(row_title(row, frame.header), frame.title_limit)
|
|
135
|
+
const fresh = running_fresh(row, time, row.status?.started_ms, frame.header)
|
|
139
136
|
|
|
140
|
-
return [title.padEnd(
|
|
137
|
+
return [title.padEnd(frame.title_limit), padded(time, frame.time_width, fresh)]
|
|
141
138
|
}
|
|
142
139
|
|
|
143
|
-
// A running row leads with the icon of the phase it is in now — the
|
|
144
|
-
//
|
|
140
|
+
// A running row leads with the icon of the phase it is in now — the last its track reached — and
|
|
141
|
+
// with 🔄 while it has reached none yet.
|
|
145
142
|
function state_icon(row: BoardRow): string {
|
|
146
|
-
const current = row.state === 'running' ?
|
|
143
|
+
const current = row.state === 'running' ? row.status?.track?.at(-1) : undefined
|
|
147
144
|
|
|
148
145
|
return current === undefined ? STATE_ICONS[row.state] : PHASE_ICONS[current]
|
|
149
146
|
}
|
|
@@ -156,15 +153,24 @@ function indent_of(prefix: string, row: BoardRow, header: BoardHeader): string {
|
|
|
156
153
|
return `${header.spinner}${prefix.slice(1)}`
|
|
157
154
|
}
|
|
158
155
|
|
|
159
|
-
// A row's usage column
|
|
160
|
-
|
|
161
|
-
|
|
156
|
+
// A row's usage column as drawn: a settled row's finish time emphasized while it is fresh.
|
|
157
|
+
function slot_text(row: BoardRow, usage: UsageSlot, header: BoardHeader): string | undefined {
|
|
158
|
+
const slot = slot_of(row, usage, header.now_ms)
|
|
159
|
+
const ended = row.state === 'running' ? undefined : row.status?.ended_ms
|
|
160
|
+
|
|
161
|
+
return slot === undefined ? slot : run_board_fresh.since(slot, ended, header)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// A row's usage column where the frame draws one, padded to one width on every row so every tail
|
|
165
|
+
// starts in one column. A plan row the run has not touched draws blanks in its place, so what it waits
|
|
166
|
+
// on starts in the tail's column.
|
|
162
167
|
function cell_text(row: BoardRow, frame: RowFrame): string | undefined {
|
|
163
168
|
const { usage, slot_width } = frame
|
|
164
169
|
|
|
165
|
-
if (usage === undefined || slot_width === 0
|
|
170
|
+
if (usage === undefined || slot_width === 0) return undefined
|
|
171
|
+
if (row.status === undefined) return ' '.repeat(slot_width)
|
|
166
172
|
|
|
167
|
-
return cell_of(
|
|
173
|
+
return cell_of(slot_text(row, usage, frame.header), slot_width)
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
function joined(parts: ReadonlyArray<string | undefined>): string {
|
|
@@ -175,52 +181,39 @@ function joined(parts: ReadonlyArray<string | undefined>): string {
|
|
|
175
181
|
// ends at its icon, so what follows keeps its usual gap.
|
|
176
182
|
function lead_of(row: BoardRow, header: BoardHeader, title: string): string {
|
|
177
183
|
const kind = row.kind === undefined ? KIND_BLANK : FILED_KIND_ICONS[row.kind]
|
|
178
|
-
const
|
|
184
|
+
const icon = running_fresh(row, state_icon(row), row.status?.phase_ms, header)
|
|
185
|
+
const lead = `${icon} ${header.link(String(row.number))} ${kind}`
|
|
179
186
|
|
|
180
187
|
return title === '' ? lead : `${lead} ${title}`
|
|
181
188
|
}
|
|
182
189
|
|
|
183
|
-
// A row up to its usage column — the columns that stay where they are
|
|
190
|
+
// A row up to its usage column — the columns that stay where they are on every row.
|
|
184
191
|
function head_text(row: BoardRow, frame: RowFrame): string {
|
|
185
|
-
const
|
|
186
|
-
const [title = '', ...time] = timed_parts(row, frame, cell !== undefined)
|
|
192
|
+
const [title = '', time] = timed_parts(row, frame)
|
|
187
193
|
|
|
188
|
-
return joined([lead_of(row, frame.header, title),
|
|
194
|
+
return joined([lead_of(row, frame.header, title), time, cell_text(row, frame)])
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
// Every row carries its state's icon, a waiting one ⏳ too; a row
|
|
192
|
-
// after its padded columns ends where its text does.
|
|
197
|
+
// Every row carries its state's icon, a waiting one ⏳ too; a running row ends on its tail
|
|
198
|
+
// (`run-board-render-tail.ts`), and a row with nothing after its padded columns ends where its text does.
|
|
193
199
|
function row_text(row: BoardRow, frame: RowFrame): string {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// The stage each state's row gives way in when the pane is short.
|
|
198
|
-
const YIELD_STAGES: Readonly<Record<ItemState, YieldStage>> = {
|
|
199
|
-
merged: 'finished',
|
|
200
|
-
done: 'finished',
|
|
201
|
-
waiting: 'pending',
|
|
202
|
-
human: 'pending',
|
|
203
|
-
parked: 'parked',
|
|
204
|
-
stopped: 'parked',
|
|
205
|
-
running: 'running',
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// A running row is as old as its start; a settled one as its end, or its start where it has none.
|
|
209
|
-
function aged_ms(row: BoardRow): number | undefined {
|
|
210
|
-
const started = row.status?.started_ms
|
|
211
|
-
|
|
212
|
-
return row.state === 'running' ? started : (row.status?.ended_ms ?? started)
|
|
213
|
-
}
|
|
200
|
+
const { header } = frame
|
|
201
|
+
const tail = run_board_render_tail.tail_of(row, header)
|
|
202
|
+
const notes = [waits_of(row, header), reason_text(row, header.now_ms)]
|
|
214
203
|
|
|
215
|
-
|
|
216
|
-
return { stage: YIELD_STAGES[row.state], at_ms: aged_ms(row) }
|
|
204
|
+
return joined([head_text(row, frame), ...tail, ...notes]).trimEnd()
|
|
217
205
|
}
|
|
218
206
|
|
|
219
207
|
// A plan line that draws one issue row, and one that draws none — a blank, a rule, an epic's own line.
|
|
220
208
|
function row_line(prefix: string, row: BoardRow, frame: RowFrame): PlanLine {
|
|
221
|
-
const
|
|
209
|
+
const indent = indent_of(prefix, row, frame.header)
|
|
210
|
+
const text = fitted(
|
|
211
|
+
row,
|
|
212
|
+
frame.columns,
|
|
213
|
+
(title_limit) => `${indent}${row_text(row, { ...frame, title_limit })}`,
|
|
214
|
+
)
|
|
222
215
|
|
|
223
|
-
return { text, row: yield_of(row) }
|
|
216
|
+
return { text, row: run_board_row_yield.yield_of(row) }
|
|
224
217
|
}
|
|
225
218
|
|
|
226
219
|
function bare(text: string): PlanLine {
|
|
@@ -281,8 +274,8 @@ function time_width(header: BoardHeader): number {
|
|
|
281
274
|
}
|
|
282
275
|
|
|
283
276
|
// Whether every row's usage column still fits one terminal line — measured as the deepest, an epic's
|
|
284
|
-
// child — where a frame is kept within a terminal. The
|
|
285
|
-
//
|
|
277
|
+
// child — where a frame is kept within a terminal. The tail and the notes after it are left out: a
|
|
278
|
+
// row's reason makes room for itself, so one long reason would otherwise take the column off every row.
|
|
286
279
|
function is_usage_fitting(
|
|
287
280
|
rows: ReadonlyArray<BoardRow>,
|
|
288
281
|
frame: RowFrame,
|
|
@@ -309,6 +302,8 @@ function frame_of(
|
|
|
309
302
|
time_width: time_width(header),
|
|
310
303
|
usage,
|
|
311
304
|
slot_width: usage === undefined ? 0 : column_width(rows, usage, header.now_ms),
|
|
305
|
+
title_limit: TITLE_LIMIT,
|
|
306
|
+
columns: size?.columns,
|
|
312
307
|
}
|
|
313
308
|
|
|
314
309
|
return is_usage_fitting(rows, frame, size) ? frame : { ...frame, usage: undefined }
|
|
@@ -352,6 +347,18 @@ function resume_lines(resume: string | undefined): Array<string> {
|
|
|
352
347
|
return ['', `${STATE_ICONS.human} ${WORDS.resume}${GAP}claude --resume ${resume}`]
|
|
353
348
|
}
|
|
354
349
|
|
|
350
|
+
// The activity kinds the legend names: the header's third line's, 🔧 where a row's tail draws it, and
|
|
351
|
+
// a followup row's checks (joshuafolkken/kit#3770).
|
|
352
|
+
function activity_named(header: BoardHeader, rows: ReadonlyArray<BoardRow>): Array<string> {
|
|
353
|
+
const now = run_board_header.now_legend(header)
|
|
354
|
+
const is_drawn = rows.some((row) => run_board_render_tail.is_operation_drawn(row, header))
|
|
355
|
+
const operation = is_drawn ? [`${NOW_ICONS.operation} ${WORDS.operation}`] : []
|
|
356
|
+
const is_checks = rows.some((row) => run_board_render_tail.is_checks_drawn(row, header))
|
|
357
|
+
const checks = is_checks ? run_board_render_checks.LEGEND : []
|
|
358
|
+
|
|
359
|
+
return [...(now === undefined ? [] : [now]), ...operation, ...checks]
|
|
360
|
+
}
|
|
361
|
+
|
|
355
362
|
// No legend is drawn before a plan is read: there are no rows whose symbols it would name.
|
|
356
363
|
function legend_lines(header: BoardHeader, notes: ReadonlyArray<BoardNote>): Array<string> {
|
|
357
364
|
const { layout } = header
|
|
@@ -360,8 +367,16 @@ function legend_lines(header: BoardHeader, notes: ReadonlyArray<BoardNote>): Arr
|
|
|
360
367
|
|
|
361
368
|
const rows = run_board_layout.rows_of(layout)
|
|
362
369
|
const drawn = new Set(rows.map((row) => state_icon(row)))
|
|
370
|
+
const named = activity_named(header, rows)
|
|
371
|
+
|
|
372
|
+
return ['', ...run_board_render_legend.legend_of(rows, drawn, notes, named)]
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Names with no link where no terminal opens one: a frame with no `link` draws them plain.
|
|
376
|
+
function thanks_part(view: BoardView): Array<string> {
|
|
377
|
+
const link = view.link ?? ((text: string): string => text)
|
|
363
378
|
|
|
364
|
-
return
|
|
379
|
+
return run_board_render_thanks.thanks_lines(view.thanks ?? [], link, view.size?.columns)
|
|
365
380
|
}
|
|
366
381
|
|
|
367
382
|
// A live frame is kept within its terminal; any other is drawn whole.
|
|
@@ -378,6 +393,7 @@ function render(view: BoardView): Array<string> {
|
|
|
378
393
|
above,
|
|
379
394
|
plan: plan_sections(header, view.size),
|
|
380
395
|
notes: notes_part(notes, header),
|
|
396
|
+
thanks: thanks_part(view),
|
|
381
397
|
legend: legend_lines(header, notes),
|
|
382
398
|
footer: view.footer ?? [],
|
|
383
399
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RowYield, YieldStage } from './run-board-fit'
|
|
2
|
+
import type { BoardRow } from './run-board-layout'
|
|
3
|
+
import type { ItemState } from './run-board-status'
|
|
4
|
+
|
|
5
|
+
// When an issue row gives way to a short pane (`run-board-fit.ts`): the stage its state yields in,
|
|
6
|
+
// and how old it is within that stage.
|
|
7
|
+
|
|
8
|
+
// The stage each state's row gives way in when the pane is short.
|
|
9
|
+
const YIELD_STAGES: Readonly<Record<ItemState, YieldStage>> = {
|
|
10
|
+
merged: 'finished',
|
|
11
|
+
done: 'finished',
|
|
12
|
+
waiting: 'pending',
|
|
13
|
+
human: 'pending',
|
|
14
|
+
parked: 'parked',
|
|
15
|
+
stopped: 'parked',
|
|
16
|
+
running: 'running',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// A running row is as old as its start; a settled one as its end, or its start where it has none.
|
|
20
|
+
function aged_ms(row: BoardRow): number | undefined {
|
|
21
|
+
const started = row.status?.started_ms
|
|
22
|
+
|
|
23
|
+
return row.state === 'running' ? started : (row.status?.ended_ms ?? started)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function yield_of(row: BoardRow): RowYield {
|
|
27
|
+
return { stage: YIELD_STAGES[row.state], at_ms: aged_ms(row) }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const run_board_row_yield = { yield_of }
|
|
31
|
+
|
|
32
|
+
export { run_board_row_yield }
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
2
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
3
|
+
import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
|
|
4
|
+
|
|
5
|
+
// What the run's `setup` events (`run-event-setup.ts`) tell `run:board`: the stage the run is at now,
|
|
6
|
+
// and which lanes are not children yet. The events carry it all — nothing here infers a stage from
|
|
7
|
+
// which records exist.
|
|
8
|
+
|
|
9
|
+
const KIND = run_event_stream.EVENT_KIND
|
|
10
|
+
const { STAGE } = run_event_setup
|
|
11
|
+
|
|
12
|
+
interface SetupRead {
|
|
13
|
+
// The stage the run itself is working at, in the stage's own words: any stage while the run is
|
|
14
|
+
// starting; from the first launch — or a drain before one — on, a lane opening or a judgment session.
|
|
15
|
+
// `undefined` where the run is at none.
|
|
16
|
+
setup: string | undefined
|
|
17
|
+
// When the run entered that stage — its event's own time, so how long the run has stood there is
|
|
18
|
+
// counted from a record; `undefined` wherever `setup` is.
|
|
19
|
+
setup_ms: number | undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const NO_SETUP: SetupRead = { setup: undefined, setup_ms: undefined }
|
|
23
|
+
|
|
24
|
+
// The stages of a hand-back to a session. Only the supervisor's next hand-off ends one, whatever the
|
|
25
|
+
// session writes to the stream meanwhile.
|
|
26
|
+
const JUDGMENT_STAGES: ReadonlySet<string> = new Set([STAGE.JUDGING, STAGE.SESSION])
|
|
27
|
+
|
|
28
|
+
function is_setup(event: RunEvent): boolean {
|
|
29
|
+
return event.kind === KIND.SETUP
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function has_launched(events: ReadonlyArray<RunEvent>): boolean {
|
|
33
|
+
return events.some((event) => event.kind === KIND.CHILD_LAUNCH)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The start is over at the first launch, or at a drain that came before one: a run whose backlog
|
|
37
|
+
// emptied with nothing launched is watching, and the hand-off each wake of that watch writes is not a
|
|
38
|
+
// run getting ready.
|
|
39
|
+
function has_started(events: ReadonlyArray<RunEvent>): boolean {
|
|
40
|
+
return has_launched(events) || events.some((event) => event.kind === KIND.DRAIN)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A lane is opening while the stage naming its issue is newer than the run's newest position: the
|
|
44
|
+
// launch it leads to, or whatever the run did instead, ends it. A stage that names no issue — a
|
|
45
|
+
// hand-off between two children — prepares nothing a person waits on.
|
|
46
|
+
function is_opening(events: ReadonlyArray<RunEvent>, stage: RunEvent): boolean {
|
|
47
|
+
if (run_event_scope.issue_named(stage) === undefined) return false
|
|
48
|
+
|
|
49
|
+
const position = events.findLast((event) => !run_event_stream.TRACE_KINDS.has(event.kind))
|
|
50
|
+
|
|
51
|
+
return position === undefined || position.pos < stage.pos
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Whether the run is at `stage` now: at any stage while it is starting; after that only at one it is
|
|
55
|
+
// itself working through.
|
|
56
|
+
function is_standing(events: ReadonlyArray<RunEvent>, stage: RunEvent): boolean {
|
|
57
|
+
if (!has_started(events) || JUDGMENT_STAGES.has(stage.text)) return true
|
|
58
|
+
|
|
59
|
+
return is_opening(events, stage)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function read(events: ReadonlyArray<RunEvent>): SetupRead {
|
|
63
|
+
const stage = events.findLast((event) => is_setup(event))
|
|
64
|
+
|
|
65
|
+
if (stage === undefined || !is_standing(events, stage)) return NO_SETUP
|
|
66
|
+
|
|
67
|
+
return { setup: stage.text, setup_ms: Date.parse(stage.at) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function names(event: RunEvent, issue: number): boolean {
|
|
71
|
+
return Number(run_event_scope.issue_named(event)) === issue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// A lane the stream saw no launch for is not yet this run's child when the run says it is still
|
|
75
|
+
// setting up: it has launched nothing at all, so any lane here is an earlier run's, or it is opening
|
|
76
|
+
// this very lane. A stream with no stage on it says neither, and the lane is read as before.
|
|
77
|
+
function is_lane_pending(events: ReadonlyArray<RunEvent>, issue: number): boolean {
|
|
78
|
+
const stages = events.filter((event) => is_setup(event))
|
|
79
|
+
|
|
80
|
+
if (stages.length === 0) return false
|
|
81
|
+
|
|
82
|
+
return !has_launched(events) || stages.some((stage) => names(stage, issue))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// A stage of one lane's launch, as its event wrote it, and when the lane entered it.
|
|
86
|
+
interface LaneStage {
|
|
87
|
+
text: string
|
|
88
|
+
at_ms: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// What the run did for `issue`: a stage of its lane's launch, or a position naming it.
|
|
92
|
+
function is_step_for(event: RunEvent, issue: number): boolean {
|
|
93
|
+
if (!names(event, issue)) return false
|
|
94
|
+
|
|
95
|
+
return is_setup(event) || !run_event_stream.TRACE_KINDS.has(event.kind)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A stage that names no issue: a hand-off to the driver, a hand-back to a session, or a launch that
|
|
99
|
+
// returned without a child. A lane's launch runs inside one driver pass, so a newer one of these says
|
|
100
|
+
// that launch has returned.
|
|
101
|
+
function is_run_stage(event: RunEvent): boolean {
|
|
102
|
+
return is_setup(event) && run_event_scope.issue_named(event) === undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The stage `issue`'s own lane is at: the newest stage naming it, until the run does anything else for
|
|
106
|
+
// that issue — its launch, or whatever ended the attempt — or writes a stage naming none, which is all
|
|
107
|
+
// a launch that left no child puts on the stream: the driver says so itself, since a refusal it waits
|
|
108
|
+
// out is followed by no hand-off. Read per issue, so two lanes opened side by side each stand at their
|
|
109
|
+
// own stage whatever the other has reached.
|
|
110
|
+
function lane_stage(events: ReadonlyArray<RunEvent>, issue: number): LaneStage | undefined {
|
|
111
|
+
const step = events.findLast((event) => is_run_stage(event) || is_step_for(event, issue))
|
|
112
|
+
|
|
113
|
+
if (step === undefined || !is_setup(step) || !names(step, issue)) return undefined
|
|
114
|
+
|
|
115
|
+
return { text: step.text, at_ms: Date.parse(step.at) }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const run_board_setup = { is_lane_pending, lane_stage, read }
|
|
119
|
+
|
|
120
|
+
export { run_board_setup }
|
|
121
|
+
export type { LaneStage, SetupRead }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PendingLaunch } from '#scripts/backlog/backlogrun-launch'
|
|
1
2
|
import type { CarryRead } from '#scripts/run/carry/run-carry'
|
|
2
3
|
import type { EndedRun } from '#scripts/run/carry/run-carry-ended'
|
|
3
4
|
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
@@ -27,9 +28,40 @@ function ended_span(ended: EndedRun | undefined): BoardSpan | undefined {
|
|
|
27
28
|
return { invocation: ended.invocation, started_ms, ended_ms }
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
// A run `josh backlogrun` launched and its parent has not claimed yet: it has no carry record, so the
|
|
32
|
+
// launch record is the only one that says a run is here and since when.
|
|
33
|
+
function launch_span(launch: PendingLaunch | undefined): BoardSpan | undefined {
|
|
34
|
+
const started_ms = launch === undefined ? undefined : moment_of(launch.started_at)
|
|
35
|
+
|
|
36
|
+
if (launch === undefined || started_ms === undefined) return undefined
|
|
37
|
+
|
|
38
|
+
return { invocation: launch.invocation, started_ms, ended_ms: undefined }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// With no carry record: the run on its way, else the one that last ended. **A launch older than that
|
|
42
|
+
// end is the ended run's own** — its parent outlives `run:carry --end` for a moment — so it never
|
|
43
|
+
// redraws a finished run as one starting.
|
|
44
|
+
function idle_span(
|
|
45
|
+
ended: EndedRun | undefined,
|
|
46
|
+
launch: PendingLaunch | undefined,
|
|
47
|
+
): BoardSpan | undefined {
|
|
48
|
+
const last = ended_span(ended)
|
|
49
|
+
const coming = launch_span(launch)
|
|
50
|
+
|
|
51
|
+
if (coming === undefined) return last
|
|
52
|
+
|
|
53
|
+
return (last?.ended_ms ?? 0) >= coming.started_ms ? last : coming
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
// The running run wins over the ended one, so a run that begins replaces the finished board at once.
|
|
31
|
-
|
|
32
|
-
|
|
57
|
+
// **The launch's start is the carry's**: `run:carry --begin` inherits it for the parent the launch
|
|
58
|
+
// started, so the clock does not restart when the record appears.
|
|
59
|
+
function span_of(
|
|
60
|
+
read: CarryRead,
|
|
61
|
+
ended: EndedRun | undefined,
|
|
62
|
+
launch?: PendingLaunch,
|
|
63
|
+
): BoardSpan | undefined {
|
|
64
|
+
if (read.kind !== 'carried') return read.kind === 'none' ? idle_span(ended, launch) : undefined
|
|
33
65
|
|
|
34
66
|
const started_ms = moment_of(read.carry.started_at)
|
|
35
67
|
|
|
@@ -38,12 +70,22 @@ function span_of(read: CarryRead, ended: EndedRun | undefined): BoardSpan | unde
|
|
|
38
70
|
return { invocation: read.carry.invocation, started_ms, ended_ms: undefined }
|
|
39
71
|
}
|
|
40
72
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
// Whether the span drawn with no carry record is the ended run's, rather than a launch's.
|
|
74
|
+
function is_ended_drawn(ended: EndedRun | undefined, launch: PendingLaunch | undefined): boolean {
|
|
75
|
+
return idle_span(ended, launch)?.ended_ms !== undefined
|
|
76
|
+
}
|
|
45
77
|
|
|
46
|
-
|
|
78
|
+
// The session a person resumes a stopped run from: only while the ended run is
|
|
79
|
+
// the one drawn, and only when it stopped rather than finished. A run that begins — or is on its way —
|
|
80
|
+
// hides it at once.
|
|
81
|
+
function resume_of(
|
|
82
|
+
read: CarryRead,
|
|
83
|
+
ended: EndedRun | undefined,
|
|
84
|
+
launch?: PendingLaunch,
|
|
85
|
+
): string | undefined {
|
|
86
|
+
if (read.kind !== 'none' || !is_ended_drawn(ended, launch)) return undefined
|
|
87
|
+
|
|
88
|
+
return ended?.stopped === undefined ? undefined : ended.session
|
|
47
89
|
}
|
|
48
90
|
|
|
49
91
|
function is_within(event: RunEvent, span: BoardSpan): boolean {
|
|
@@ -9,6 +9,7 @@ import type { Link } from './run-board-link'
|
|
|
9
9
|
import type { MachineGauges, MachineMark } from './run-board-machine'
|
|
10
10
|
import type { LocalRead } from './run-board-read'
|
|
11
11
|
import type { Spot } from './run-board-spin'
|
|
12
|
+
import type { Thanks } from './run-board-thanks-csv'
|
|
12
13
|
import type { LaneUsages, UsageMark } from './run-board-usage'
|
|
13
14
|
|
|
14
15
|
// What a `run:board` redraw reads through and folds forward, shared by the
|
|
@@ -38,6 +39,8 @@ interface BoardPorts {
|
|
|
38
39
|
// Records that a progress report was just given, as `run:progress --mark` does, so the next scheduled
|
|
39
40
|
// one waits a full interval from here.
|
|
40
41
|
mark: () => Promise<void>
|
|
42
|
+
// The reviewers' roster (`run-board-thanks-read.ts`); none where the frame thanks nobody — the chat.
|
|
43
|
+
read_thanks?: (() => Promise<Thanks>) | undefined
|
|
41
44
|
// Sends one chat frame off-screen, for `--every` alone.
|
|
42
45
|
push: (frame: string) => Promise<void>
|
|
43
46
|
// Runs `leave` however the process ends: a normal exit, Ctrl+C or SIGTERM.
|
|
@@ -79,6 +82,10 @@ interface BoardState {
|
|
|
79
82
|
// them.
|
|
80
83
|
usage: UsageMark | undefined
|
|
81
84
|
usages: LaneUsages | undefined
|
|
85
|
+
// The reviewers drawn above the legend, the read of them in flight, and when it was last launched.
|
|
86
|
+
thanks: Thanks
|
|
87
|
+
thanks_fetch: Fetch<Thanks> | undefined
|
|
88
|
+
thanks_attempted_ms: number | undefined
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
const FRESH_STATE: BoardState = {
|
|
@@ -101,6 +108,9 @@ const FRESH_STATE: BoardState = {
|
|
|
101
108
|
spots: [],
|
|
102
109
|
usage: undefined,
|
|
103
110
|
usages: undefined,
|
|
111
|
+
thanks: [],
|
|
112
|
+
thanks_fetch: undefined,
|
|
113
|
+
thanks_attempted_ms: undefined,
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
function is_due(last_ms: number | undefined, interval_ms: number, now_ms: number): boolean {
|