@joshuafolkken/kit 1.1387.0 → 1.1453.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-park.md +2 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
- package/.claude/skills/workflow-commands/backlogrun.md +3 -2
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/kickoff.md +12 -13
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/.claude/skills/workflow-commands/prerequisite.md +6 -0
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/README.md +27 -4
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
- 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 +261 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +95 -19
- package/package.json +4 -3
- package/prompts/collaboration-workflow/issue-template.md +10 -8
- package/prompts/review.md +30 -4
- 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/auto-ok/auto-ok-judge-cli.ts +125 -0
- package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
- package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
- package/scripts/auto-ok/auto-ok-judge.ts +67 -0
- package/scripts/backlog/backlog-drive-cli.ts +14 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -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-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +38 -11
- 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 +24 -0
- 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 +175 -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 +18 -4
- 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-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +13 -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 +7 -4
- package/scripts/init/init-ai-copy.ts +34 -11
- package/scripts/init/init-basic.ts +9 -4
- package/scripts/init/init-bootstrap.ts +6 -2
- package/scripts/init/init-dry-run-report.ts +150 -0
- package/scripts/init/init-dry-run.ts +189 -0
- package/scripts/init/init-install.ts +8 -1
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +62 -42
- package/scripts/init/kit-setup-state.ts +11 -17
- package/scripts/init/own-formatter.ts +21 -0
- package/scripts/init/project-profile.ts +60 -12
- package/scripts/init/start-dry-run.ts +70 -0
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +23 -3
- package/scripts/init/start-setup-pr.ts +13 -24
- package/scripts/init/start-steps.ts +62 -1
- package/scripts/init/start.ts +38 -17
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/init/working-tree.ts +116 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-auto-ok.ts +68 -99
- package/scripts/issue/issue-file-cli.ts +17 -17
- package/scripts/issue/issue-file.ts +1 -6
- package/scripts/issue/issue-labels.ts +46 -10
- package/scripts/issue/issue-release-cli.ts +69 -23
- package/scripts/issue/issue-release.ts +3 -1
- 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-ai.ts +11 -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 +10 -3
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +26 -2
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/josh/process-identity.ts +32 -6
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-broker-server.ts +69 -0
- 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/lane-session-broker.ts +363 -0
- 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/observations/observation-ledger-migrate.ts +3 -15
- package/scripts/release/release-issue-close.ts +133 -0
- package/scripts/release/release-publish.ts +12 -1
- package/scripts/repo/repository-labels.ts +11 -1
- package/scripts/review/comment-ranges.ts +199 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +24 -7
- package/scripts/review/review-code-tree.ts +119 -0
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/review/review-round2-cli.ts +16 -5
- package/scripts/review/review-round2.ts +30 -10
- package/scripts/review/review-stamps.ts +22 -1
- 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 +129 -53
- 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 +49 -3
- 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 +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- 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 +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -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 +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- 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/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 +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- 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/lane/openai-review-broker.ts +0 -262
- 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 }
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
2
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
2
3
|
import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
|
|
3
4
|
|
|
4
|
-
// What the run's `setup` events (`run-event-setup.ts`) tell `run:board`: the stage
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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.
|
|
7
8
|
|
|
8
9
|
const KIND = run_event_stream.EVENT_KIND
|
|
10
|
+
const { STAGE } = run_event_setup
|
|
9
11
|
|
|
10
12
|
interface SetupRead {
|
|
11
|
-
// The
|
|
12
|
-
// drain before one — on,
|
|
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.
|
|
13
16
|
setup: string | undefined
|
|
14
|
-
//
|
|
15
|
-
|
|
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
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const NO_SETUP: SetupRead = { setup: undefined,
|
|
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])
|
|
19
27
|
|
|
20
28
|
function is_setup(event: RunEvent): boolean {
|
|
21
29
|
return event.kind === KIND.SETUP
|
|
@@ -43,13 +51,20 @@ function is_opening(events: ReadonlyArray<RunEvent>, stage: RunEvent): boolean {
|
|
|
43
51
|
return position === undefined || position.pos < stage.pos
|
|
44
52
|
}
|
|
45
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
|
+
|
|
46
62
|
function read(events: ReadonlyArray<RunEvent>): SetupRead {
|
|
47
63
|
const stage = events.findLast((event) => is_setup(event))
|
|
48
64
|
|
|
49
|
-
if (stage === undefined) return NO_SETUP
|
|
50
|
-
if (!has_started(events)) return { setup: stage.text, is_preparing: true }
|
|
65
|
+
if (stage === undefined || !is_standing(events, stage)) return NO_SETUP
|
|
51
66
|
|
|
52
|
-
return { setup:
|
|
67
|
+
return { setup: stage.text, setup_ms: Date.parse(stage.at) }
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
function names(event: RunEvent, issue: number): boolean {
|
|
@@ -67,7 +82,40 @@ function is_lane_pending(events: ReadonlyArray<RunEvent>, issue: number): boolea
|
|
|
67
82
|
return !has_launched(events) || stages.some((stage) => names(stage, issue))
|
|
68
83
|
}
|
|
69
84
|
|
|
70
|
-
|
|
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 }
|
|
71
119
|
|
|
72
120
|
export { run_board_setup }
|
|
73
|
-
export type { SetupRead }
|
|
121
|
+
export type { LaneStage, SetupRead }
|
|
@@ -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 {
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
IN_PROGRESS_LABEL,
|
|
4
4
|
NEEDS_DECISION_LABEL,
|
|
5
5
|
} from '#scripts/issue/issue-labels'
|
|
6
|
+
import type { PrChecks } from '#scripts/run/event/run-event-checks'
|
|
6
7
|
import { run_event_scope } from '#scripts/run/event/run-event-scope'
|
|
7
8
|
import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
|
|
9
|
+
import { run_board_checks } from './run-board-checks'
|
|
8
10
|
import type { ClosedIssue } from './run-board-closed'
|
|
11
|
+
import { run_board_lane_event, type LaneEvent } from './run-board-lane-event'
|
|
9
12
|
import { run_board_phase, type Phase } from './run-board-phase'
|
|
10
13
|
import { run_board_setup, type SetupRead } from './run-board-setup'
|
|
11
14
|
|
|
@@ -25,12 +28,29 @@ interface ItemStatus {
|
|
|
25
28
|
lane?: string | undefined
|
|
26
29
|
// Every phase the child passed while running, in order (`run-board-phase.ts`) — kept once it settles.
|
|
27
30
|
track?: ReadonlyArray<Phase> | undefined
|
|
31
|
+
// When a running child entered the phase its track ends on — the event that moved it there.
|
|
32
|
+
phase_ms?: number | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface LatestEvent {
|
|
36
|
+
text: string
|
|
37
|
+
at_ms: number
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
// The run as a whole, read off the same stream. The setup half is `run-board-setup.ts`'s, absent
|
|
31
41
|
// where a stream carries no stage.
|
|
32
42
|
interface RunActivity extends Partial<SetupRead> {
|
|
33
43
|
last_event_ms: number | undefined
|
|
44
|
+
// The newest thing the run did — its newest position, or its newest event where it has none yet —
|
|
45
|
+
// in the event's own words, and when.
|
|
46
|
+
latest?: LatestEvent | undefined
|
|
47
|
+
// When the run last did anything of its own — its newest position or stage, whichever is newer. A
|
|
48
|
+
// stage that has ended still counts: what follows it is counted from its end, not from before it.
|
|
49
|
+
idle_ms?: number | undefined
|
|
50
|
+
// Each child's newest `lane-phase` or `ship-stage`, by issue (`run-board-lane-event.ts`).
|
|
51
|
+
lane_events?: ReadonlyMap<number, LaneEvent> | undefined
|
|
52
|
+
// Each child's newest `pr-checks`, by issue (`run-board-checks.ts`).
|
|
53
|
+
pr_checks?: ReadonlyMap<number, PrChecks> | undefined
|
|
34
54
|
// The run's last position is a `stop` that names no child — the run itself ended.
|
|
35
55
|
is_stopped: boolean
|
|
36
56
|
}
|
|
@@ -44,13 +64,30 @@ const ENDING_STATES: Readonly<Partial<Record<string, ItemState>>> = {
|
|
|
44
64
|
}
|
|
45
65
|
// A cut or a resume says the child is still running, on whatever phase it had reached.
|
|
46
66
|
const CONTINUING_KINDS: ReadonlySet<string> = new Set([KIND.CUT, KIND.RESUME])
|
|
67
|
+
// A person's restart starts the child over in a fresh session, so its track starts over too.
|
|
68
|
+
const LAUNCH_KINDS: ReadonlySet<string> = new Set([KIND.CHILD_LAUNCH, KIND.RESTART])
|
|
47
69
|
|
|
48
70
|
function moment(event: RunEvent): number {
|
|
49
71
|
return Date.parse(event.at)
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
function launched(event: RunEvent): ItemStatus {
|
|
53
|
-
|
|
75
|
+
const at_ms = moment(event)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
state: 'running',
|
|
79
|
+
started_ms: at_ms,
|
|
80
|
+
track: [run_board_phase.LAUNCHED_PHASE],
|
|
81
|
+
phase_ms: at_ms,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The status with its track moved on by `event`. The phase's start moves only where the phase the
|
|
86
|
+
// track ends on changes, so a second session's first edit does not restart the time of its phase.
|
|
87
|
+
function moved(previous: ItemStatus, track: ReadonlyArray<Phase>, event: RunEvent): ItemStatus {
|
|
88
|
+
const is_same = previous.phase_ms !== undefined && track.at(-1) === previous.track?.at(-1)
|
|
89
|
+
|
|
90
|
+
return { ...previous, track, phase_ms: is_same ? previous.phase_ms : moment(event) }
|
|
54
91
|
}
|
|
55
92
|
|
|
56
93
|
// The status a child settles into: its start and the track it had reached, so a settled row still
|
|
@@ -66,7 +103,9 @@ function ended(previous: ItemStatus | undefined, state: ItemState, event: RunEve
|
|
|
66
103
|
}
|
|
67
104
|
|
|
68
105
|
function continued(previous: ItemStatus | undefined): ItemStatus {
|
|
69
|
-
|
|
106
|
+
const { started_ms, track, phase_ms } = previous ?? {}
|
|
107
|
+
|
|
108
|
+
return { state: 'running', started_ms, track, phase_ms }
|
|
70
109
|
}
|
|
71
110
|
|
|
72
111
|
// A stage line carrying its attempt redraws the track of any child launched on this stream — a settled
|
|
@@ -75,11 +114,12 @@ function continued(previous: ItemStatus | undefined): ItemStatus {
|
|
|
75
114
|
function replayed(
|
|
76
115
|
previous: ItemStatus | undefined,
|
|
77
116
|
attempt: ReadonlyArray<Phase>,
|
|
117
|
+
event: RunEvent,
|
|
78
118
|
): ItemStatus | undefined {
|
|
79
119
|
if (previous === undefined) return previous
|
|
80
120
|
if (previous.track === undefined && previous.state !== 'running') return previous
|
|
81
121
|
|
|
82
|
-
return
|
|
122
|
+
return moved(previous, run_board_phase.replayed(previous.track ?? [], attempt), event)
|
|
83
123
|
}
|
|
84
124
|
|
|
85
125
|
// A phase moves only a running child, so a three-word `report` stage after its merge — or a `plan`
|
|
@@ -89,18 +129,18 @@ function phased(previous: ItemStatus | undefined, event: RunEvent): ItemStatus |
|
|
|
89
129
|
|
|
90
130
|
if (phase === undefined || previous?.state !== 'running') return previous
|
|
91
131
|
|
|
92
|
-
return
|
|
132
|
+
return moved(previous, run_board_phase.history_after(previous.track ?? [], phase), event)
|
|
93
133
|
}
|
|
94
134
|
|
|
95
135
|
function advanced(previous: ItemStatus | undefined, event: RunEvent): ItemStatus | undefined {
|
|
96
136
|
const attempt = run_board_phase.attempt_of(event)
|
|
97
137
|
|
|
98
|
-
return attempt === undefined ? phased(previous, event) : replayed(previous, attempt)
|
|
138
|
+
return attempt === undefined ? phased(previous, event) : replayed(previous, attempt, event)
|
|
99
139
|
}
|
|
100
140
|
|
|
101
141
|
// One event folded into the status of the child it names; an event the board does not track keeps it.
|
|
102
142
|
function next_status(previous: ItemStatus | undefined, event: RunEvent): ItemStatus | undefined {
|
|
103
|
-
if (event.kind
|
|
143
|
+
if (LAUNCH_KINDS.has(event.kind)) return launched(event)
|
|
104
144
|
|
|
105
145
|
const ending = ENDING_STATES[event.kind]
|
|
106
146
|
|
|
@@ -247,6 +287,21 @@ function is_run_stop(event: RunEvent | undefined): boolean {
|
|
|
247
287
|
return event?.kind === KIND.STOP && run_event_scope.issue_named(event) === undefined
|
|
248
288
|
}
|
|
249
289
|
|
|
290
|
+
function latest_of(event: RunEvent | undefined): LatestEvent | undefined {
|
|
291
|
+
return event === undefined ? undefined : { text: event.text, at_ms: moment(event) }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function is_own(event: RunEvent): boolean {
|
|
295
|
+
return event.kind === KIND.SETUP || !run_event_stream.TRACE_KINDS.has(event.kind)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// A stream of traces alone has done nothing of its own yet, so its newest event stands in.
|
|
299
|
+
function idle_of(events: ReadonlyArray<RunEvent>): number | undefined {
|
|
300
|
+
const own = events.findLast((event) => is_own(event)) ?? events.at(-1)
|
|
301
|
+
|
|
302
|
+
return own === undefined ? undefined : moment(own)
|
|
303
|
+
}
|
|
304
|
+
|
|
250
305
|
// `is_ended` is a run `run:carry --end` closed: its record says it stopped, whatever its last event
|
|
251
306
|
// was.
|
|
252
307
|
function activity_of(events: ReadonlyArray<RunEvent>, is_ended = false): RunActivity {
|
|
@@ -256,6 +311,10 @@ function activity_of(events: ReadonlyArray<RunEvent>, is_ended = false): RunActi
|
|
|
256
311
|
return {
|
|
257
312
|
...run_board_setup.read(events),
|
|
258
313
|
last_event_ms: last === undefined ? undefined : moment(last),
|
|
314
|
+
latest: latest_of(position ?? last),
|
|
315
|
+
idle_ms: idle_of(events),
|
|
316
|
+
lane_events: run_board_lane_event.newest_of(events),
|
|
317
|
+
pr_checks: run_board_checks.newest_of(events),
|
|
259
318
|
is_stopped: is_ended || is_run_stop(position),
|
|
260
319
|
}
|
|
261
320
|
}
|
|
@@ -263,4 +322,4 @@ function activity_of(events: ReadonlyArray<RunEvent>, is_ended = false): RunActi
|
|
|
263
322
|
const run_board_status = { activity_of, settle_closed, statuses_of }
|
|
264
323
|
|
|
265
324
|
export { run_board_status }
|
|
266
|
-
export type { ItemState, ItemStatus, OpenRead, RunActivity }
|
|
325
|
+
export type { ItemState, ItemStatus, LatestEvent, OpenRead, RunActivity }
|