jackherd 1.16.0 → 1.17.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/CHANGELOG.md +22 -0
- package/INSTALL.md +2 -1
- package/README.md +22 -21
- package/dist/{chunk-4LUFR7RC.js → chunk-N5FAOQZL.js} +7 -0
- package/dist/config.d.ts +33 -1
- package/dist/config.js +57 -5
- package/dist/plan-format.d.ts +18 -6
- package/dist/plan-format.js +8 -1
- package/docs/plan-contract.md +1 -0
- package/herdr-plugin/herdr-plugin.toml +4 -1
- package/herdr-plugin/restore-plans.ts +60 -26
- package/herdr-plugin/resume.ts +18 -0
- package/install.sh +1 -1
- package/kit/hooks/ask-rules.json +11 -0
- package/kit/hooks/hooks/approval-gate/checked-marks.ts +59 -0
- package/kit/hooks/hooks/approval-gate/index.ts +36 -7
- package/kit/hooks/hooks/approval-gate/run.ts +72 -16
- package/kit/hooks/hooks/approval-gate/settings.ts +27 -1
- package/kit/hooks/hooks/approval-gate/standing-history.ts +90 -0
- package/kit/hooks/hooks/approval-gate/standing-sql.ts +58 -0
- package/kit/hooks/hooks/approval-gate/transcripts.ts +11 -5
- package/kit/hooks/hooks/lane-start-guard/decision.ts +74 -0
- package/kit/hooks/hooks/lane-start-guard/index.ts +36 -0
- package/kit/hooks/hooks/merge-guard/always-ask.ts +2 -2
- package/kit/hooks/hooks/merge-guard/approval.ts +25 -13
- package/kit/hooks/hooks/merge-guard/change-id.ts +34 -0
- package/kit/hooks/hooks/merge-guard/decision.ts +423 -35
- package/kit/hooks/hooks/merge-guard/pane-input.ts +61 -0
- package/kit/hooks/hooks/merge-guard/pane-process-info.ts +38 -0
- package/kit/hooks/hooks/merge-guard/run.ts +110 -28
- package/kit/hooks/hooks/merge-guard/standing-approval.ts +15 -78
- package/kit/hooks/hooks/plain-question/form.ts +89 -0
- package/kit/hooks/hooks/plain-question/index.ts +44 -4
- package/kit/hooks/hooks/plain-question/recommendation.ts +73 -0
- package/kit/hooks/hooks/plain-question/run.ts +14 -7
- package/kit/hooks/hooks/shared/standing-mark.ts +104 -0
- package/kit/hooks/hooks.json +11 -2
- package/kit/review/agents/skill-review.md +3 -2
- package/kit/review/skills/pre-commit/SKILL.md +1 -1
- package/kit/rules/rules/kit/code-style.md +6 -6
- package/kit/rules/rules/kit/writing-for-agents.md +3 -0
- package/kit/workflow/skills/follow/SKILL.md +2 -3
- package/kit/workflow/skills/implement/SKILL.md +1 -1
- package/kit/workflow/skills/orchestrate/SKILL.md +120 -299
- package/kit/workflow/skills/orchestrate/references/appointments.md +21 -0
- package/kit/workflow/skills/orchestrate/references/close-lane.md +8 -7
- package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +8 -8
- package/kit/workflow/skills/orchestrate/references/events.md +46 -1
- package/kit/workflow/skills/orchestrate/references/herd-ask.md +10 -0
- package/kit/workflow/skills/orchestrate/references/lane-handover.md +9 -4
- package/kit/workflow/skills/orchestrate/references/lane-session.md +5 -4
- package/kit/workflow/skills/orchestrate/references/lane-setup.md +2 -1
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +9 -2
- package/kit/workflow/skills/orchestrate/references/self-merge.md +26 -0
- package/kit/workflow/skills/orchestrate/references/stage-approval.md +50 -0
- package/kit/workflow/skills/orchestrate/references/standing-approval.md +38 -0
- package/kit/workflow/skills/orchestrate/references/start-lane.md +67 -0
- package/kit/workflow/skills/orchestrate/references/waiting.md +16 -0
- package/kit/workflow/skills/orchestrate/references/watch-without-monitor.md +13 -0
- package/kit/workflow/skills/plan/SKILL.md +3 -2
- package/package.json +2 -1
- package/recipes/README.md +1 -0
- package/recipes/jira-cli.md +1 -1
- package/recipes/sources/google-calendar-today.ts +8 -2
- package/recipes/sources/jira.ts +10 -2
- package/recipes/sources/linear.ts +9 -3
- package/recipes/sources/sentry.ts +8 -2
- package/recipes/sources.md +4 -2
- package/schemas/jackherd.schema.json +67 -0
- package/schemas/sources.schema.json +9 -0
- package/src/approval/checked.ts +60 -0
- package/src/approval/command.ts +16 -0
- package/src/check/command.ts +51 -6
- package/src/check/lint-pointers.ts +79 -0
- package/src/check/lint-shaped.ts +196 -0
- package/src/check/plan-structure-check.ts +116 -4
- package/src/check/report.ts +9 -3
- package/src/check/schema.ts +37 -0
- package/src/check/settings.ts +4 -0
- package/src/check/skill-budget-defaults.ts +77 -26
- package/src/check/skill-budgets.ts +58 -10
- package/src/cli.ts +2 -0
- package/src/config/personal-sources.ts +46 -10
- package/src/config/repository-identity.ts +40 -0
- package/src/config/schema.ts +53 -1
- package/src/context/command.ts +33 -2
- package/src/context/options.ts +33 -8
- package/src/context/usage-logs.ts +53 -0
- package/src/context/usage.ts +110 -0
- package/src/lane/base-command.ts +35 -0
- package/src/lane/base.ts +43 -0
- package/src/lane/close-check.ts +49 -15
- package/src/lane/close-command.ts +71 -11
- package/src/lane/close-options.ts +29 -8
- package/src/lane/close-survey.ts +13 -1
- package/src/lane/close.ts +51 -12
- package/src/lane/command.ts +21 -3
- package/src/lane/send-command.ts +32 -0
- package/src/lane/send-herdr.ts +77 -0
- package/src/lane/send-options.ts +23 -0
- package/src/lane/send.ts +90 -0
- package/src/lane/skill-hint.ts +27 -0
- package/src/lane/space-command.ts +30 -0
- package/src/lane/space.ts +49 -0
- package/src/lane/start-lane-hint.ts +18 -0
- package/src/plan-contract/base-check-git.ts +33 -0
- package/src/plan-contract/base-check.ts +17 -0
- package/src/plan-contract/command.ts +25 -4
- package/src/plan-format/plan-structure.ts +17 -5
- package/src/plans/ask-coordinator.ts +23 -13
- package/src/plans/board-rows.ts +9 -4
- package/src/plans/click-hint.ts +4 -0
- package/src/plans/command.ts +6 -5
- package/src/plans/ensure-panes.ts +14 -3
- package/src/plans/git.ts +14 -0
- package/src/plans/github.ts +19 -10
- package/src/plans/layout.ts +82 -33
- package/src/plans/live.ts +8 -12
- package/src/plans/plan-panes.ts +21 -4
- package/src/plans/selection.ts +29 -0
- package/src/plans/settings.ts +4 -0
- package/src/resume/command.ts +213 -0
- package/src/resume/decision.ts +102 -0
- package/src/resume/keeper.ts +65 -0
- package/src/resume/location.ts +22 -0
- package/src/resume/log.ts +27 -0
- package/src/resume/ports.ts +102 -0
- package/src/resume/record.ts +116 -0
- package/src/resume/targets.ts +84 -0
- package/src/resume/texts.ts +27 -0
- package/src/secrets/command.ts +15 -5
- package/src/sync/herdr-plugin.ts +2 -11
- package/src/sync/plan-ask-rules.ts +11 -0
- package/src/sync/render.ts +3 -1
- package/src/vocabulary/index.ts +13 -0
- package/src/waiting/command.ts +13 -4
- package/src/watch/command.ts +177 -78
- package/src/watch/config-reload.ts +119 -0
- package/src/watch/lane-model.ts +11 -1
- package/src/watch/lane-progress.ts +70 -8
- package/src/watch/lane-report.ts +66 -11
- package/src/watch/main-checkout.ts +5 -1
- package/src/watch/merge-duties.ts +33 -0
- package/src/watch/merge-overlap-read.ts +69 -0
- package/src/watch/plan-views.ts +16 -4
- package/src/watch/reference-paths.ts +46 -5
- package/src/watch/report-questions.ts +5 -2
- package/src/watch/single-report.ts +3 -2
- package/src/watch/source-dry-run-command.ts +22 -4
- package/src/watch/source-reason.ts +58 -0
- package/src/watch/source-reload.ts +21 -17
- package/src/watch/source-run.ts +24 -7
- package/src/watch/sources.ts +9 -2
- package/src/watch/texts.ts +14 -3
- package/src/watch/waiting-line.ts +4 -3
package/src/watch/command.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { basename, dirname, join, relative } from 'node:path'
|
|
|
34
34
|
import { writePrivateFile } from '../../kit/hooks/hooks/shared/private-directory'
|
|
35
35
|
import { sourcesWithPersonal } from '../config/personal-sources'
|
|
36
36
|
import { readConfig, readResolvedConfig } from '../config/read-config'
|
|
37
|
+
import { readRepositoryIdentity } from '../config/repository-identity'
|
|
37
38
|
import type { JackherdConfig } from '../config/schema'
|
|
38
39
|
import { resolveCoordinatorName } from '../context/coordinator-name'
|
|
39
40
|
import { coordinatorSettings } from '../context/options'
|
|
@@ -45,12 +46,14 @@ import { boardFor, foundLanesOf } from '../plans/board-pick'
|
|
|
45
46
|
import { ensurePlanPanes } from '../plans/ensure-panes'
|
|
46
47
|
import { errorText } from '../plans/error-text'
|
|
47
48
|
import { baseReaderOf, mainCheckout, worktreeNames, worktreeRoot } from '../plans/git'
|
|
48
|
-
import { discoverLanes, isUnchanged, laneSetChange
|
|
49
|
+
import { discoverLanes, isUnchanged, laneSetChange } from '../plans/lane-discovery'
|
|
49
50
|
import { readerOncePerRef } from '../plans/plan-heads'
|
|
50
51
|
import { CLOCK_LOCALE, type PlansSettings, plansSettingsOf, worktreeOf } from '../plans/settings'
|
|
51
52
|
import { readRunningSubagents, subagentSourceOf } from '../plans/subagents-read'
|
|
52
53
|
import { boardTexts } from '../plans/texts'
|
|
53
54
|
import { readWaitingRecords, waitingItemsOf } from '../plans/waiting-records'
|
|
55
|
+
import { keepRecord } from '../resume/keeper'
|
|
56
|
+
import { systemRecordStore } from '../resume/ports'
|
|
54
57
|
import { defaultUpdateCheck } from '../sync/if-changed'
|
|
55
58
|
import { DEFAULT_SKILL_PREFIX } from '../sync/init'
|
|
56
59
|
import { herdrLaneStatusSource } from '../terminal/herdr'
|
|
@@ -65,6 +68,14 @@ import {
|
|
|
65
68
|
} from './appointments'
|
|
66
69
|
import { type ApprovalRequestFile, approvalEvents, readApprovalRequests, recordApprovals } from './approvals'
|
|
67
70
|
import { askingLanes, questionsFingerprint, recordReportQuestions } from './asking-lanes'
|
|
71
|
+
import {
|
|
72
|
+
baselinesOf,
|
|
73
|
+
type LiveValues,
|
|
74
|
+
liveValuesOf,
|
|
75
|
+
nextLoadedConfig,
|
|
76
|
+
readWatchedConfig,
|
|
77
|
+
withoutRunsBefore,
|
|
78
|
+
} from './config-reload'
|
|
68
79
|
import {
|
|
69
80
|
type CoordinatorRead,
|
|
70
81
|
contextReadingOf,
|
|
@@ -94,7 +105,7 @@ import { agentScreen, listAgents } from './herdr'
|
|
|
94
105
|
import { endedByKitUpdate } from './kit-update'
|
|
95
106
|
import { type LaneReadings, laneFillEvents, recordLaneLevels } from './lane-fill'
|
|
96
107
|
import { laneMessage, rememberedStatusLines, statusBlock } from './lane-message'
|
|
97
|
-
import { type LaneModelReading, laneModelEvents, recordLaneModels } from './lane-model'
|
|
108
|
+
import { type LaneModelReading, laneModelEvents, modelColumnOf, recordLaneModels } from './lane-model'
|
|
98
109
|
import { laneParallelEvents, recordLaneParallels } from './lane-parallel'
|
|
99
110
|
import {
|
|
100
111
|
boardCheckoutRows,
|
|
@@ -103,6 +114,7 @@ import {
|
|
|
103
114
|
watchedPlanPullRequests,
|
|
104
115
|
watchedReadyPhases,
|
|
105
116
|
workedPhaseKeys,
|
|
117
|
+
worktreeOpenBoxes,
|
|
106
118
|
} from './lane-progress'
|
|
107
119
|
import {
|
|
108
120
|
appointmentEventLine,
|
|
@@ -119,6 +131,8 @@ import {
|
|
|
119
131
|
laneStopHeader,
|
|
120
132
|
mergeDutyLines,
|
|
121
133
|
noTaskSignalDutyLines,
|
|
134
|
+
type OverlapOf,
|
|
135
|
+
openBoxesDutyLines,
|
|
122
136
|
otherPullRequestsLine,
|
|
123
137
|
parallelDutyLines,
|
|
124
138
|
planMissingDutyLines,
|
|
@@ -144,7 +158,8 @@ import {
|
|
|
144
158
|
NO_AGENT,
|
|
145
159
|
toSnapshot,
|
|
146
160
|
} from './lane-watch'
|
|
147
|
-
import { mainCheckoutDutyLine } from './main-checkout'
|
|
161
|
+
import { isBehindOnBase, mainCheckoutDutyLine } from './main-checkout'
|
|
162
|
+
import { mergeOverlapReader } from './merge-overlap-read'
|
|
148
163
|
import { monitorToolAvailable } from './monitor-tool'
|
|
149
164
|
import { parseWatchOptions, type WatchOptions } from './options'
|
|
150
165
|
import type { PlanMissingEvent, PlanMissingState } from './plan-missing'
|
|
@@ -160,6 +175,7 @@ import {
|
|
|
160
175
|
import { readDeploymentCandidates } from './preview-read'
|
|
161
176
|
import {
|
|
162
177
|
commentCandidates,
|
|
178
|
+
emptyPreviewState,
|
|
163
179
|
feedbackReadCovers,
|
|
164
180
|
type PreviewRead,
|
|
165
181
|
previewEvents,
|
|
@@ -169,27 +185,24 @@ import {
|
|
|
169
185
|
import { latestPullRequestOf, type WatchedPullRequest } from './pull-requests'
|
|
170
186
|
import { recordedReadsOf } from './recorded-reads'
|
|
171
187
|
import {
|
|
188
|
+
annotateEach,
|
|
172
189
|
annotateFirstLine,
|
|
173
190
|
eventReference,
|
|
191
|
+
laneSetEventLines,
|
|
174
192
|
REFERENCE,
|
|
175
193
|
type Reference,
|
|
176
194
|
referenceHint,
|
|
177
195
|
type SkillCopy,
|
|
178
196
|
skillCopiesFor,
|
|
179
197
|
skillDirectoryFor,
|
|
198
|
+
stageToolsOf,
|
|
180
199
|
} from './reference-paths'
|
|
181
200
|
import { heartbeatNotesOf, runningSubagentCountsOf } from './running-subagents'
|
|
182
201
|
import { withSingleReportNotice } from './single-report'
|
|
183
202
|
import { readOrchestrateSnapshot, skillLineFor } from './skill-change'
|
|
184
203
|
import { runsFromSource, sourceChangeSettled, watchSourceChanges } from './source-change'
|
|
185
204
|
import { sourceDryRun } from './source-dry-run-command'
|
|
186
|
-
import {
|
|
187
|
-
type LoadedSources,
|
|
188
|
-
nextLoadedSources,
|
|
189
|
-
readFileStamps,
|
|
190
|
-
readWatchedSources,
|
|
191
|
-
sourceFilePaths,
|
|
192
|
-
} from './source-reload'
|
|
205
|
+
import { type LoadedSources, readFileStamps, sourceFilePaths } from './source-reload'
|
|
193
206
|
import { runSource, SOURCE_LIMITS } from './source-run'
|
|
194
207
|
import { recordedSourceState } from './source-tickets'
|
|
195
208
|
import { isSourceDue, type SourceRun, type SourceState, sourceEvents } from './sources'
|
|
@@ -235,15 +248,8 @@ import type { DeployConfig, WorkflowRun } from './workflow-runs'
|
|
|
235
248
|
|
|
236
249
|
type Files = { state: string; lock: string }
|
|
237
250
|
|
|
238
|
-
/** The logins whose feedback does not count, or false when the project turned feedback off. */
|
|
239
|
-
type FeedbackConfig = JackherdConfig['watch']['feedback']
|
|
240
|
-
|
|
241
|
-
type ChecksConfig = JackherdConfig['watch']['checks']
|
|
242
251
|
type ParallelTasksConfig = JackherdConfig['watch']['parallelTasks']
|
|
243
252
|
|
|
244
|
-
/** Where deploy previews are read from; undefined when the project reads none. */
|
|
245
|
-
type PreviewsConfig = JackherdConfig['watch']['previews']
|
|
246
|
-
|
|
247
253
|
/** One feedback read: what counts as feedback, and every item it read, which the previews take their comments from. */
|
|
248
254
|
type FeedbackPoll = { read: FeedbackRead; items: FeedbackItem[] }
|
|
249
255
|
|
|
@@ -256,15 +262,13 @@ type Watch = {
|
|
|
256
262
|
options: WatchOptions
|
|
257
263
|
files: Files
|
|
258
264
|
settings: PlansSettings
|
|
259
|
-
deploy
|
|
260
|
-
|
|
261
|
-
checks: ChecksConfig
|
|
265
|
+
/** `watch.deploy`, `feedback`, `previews` and `checks` as read at the start; the loop reads them from `Watching.live`. */
|
|
266
|
+
live: LiveValues
|
|
262
267
|
parallelTasks: ParallelTasksConfig
|
|
263
268
|
/** `watch.taskSignalMinutes`: how long a lane works without a task signal before `no task signal`. */
|
|
264
269
|
taskSignalMinutes: number
|
|
265
270
|
/** The switch `watch.parallelTasks.auto` and the paths that always ask, both from the main checkout's config. */
|
|
266
271
|
parallelAuto: AutoSettings
|
|
267
|
-
previews: PreviewsConfig
|
|
268
272
|
/** The sources as read at the start; the loop takes up later changes of their files (`Watching.sources`). */
|
|
269
273
|
sources: LoadedSources
|
|
270
274
|
/** The checkouts whose config the sources are read from, as `readResolvedConfig` takes them. */
|
|
@@ -272,6 +276,8 @@ type Watch = {
|
|
|
272
276
|
texts: WatchTexts
|
|
273
277
|
/** Where the orchestrate skill is installed, the project's copy first; the events name a reference inside the one found. */
|
|
274
278
|
skillCopies: SkillCopy[]
|
|
279
|
+
/** The tools of `guard.approvals.standing`: an approval of one of them points to the stage approval reference. */
|
|
280
|
+
stageTools: readonly string[]
|
|
275
281
|
/** Runs inside herdr with the herdr adapter, so missing herd panes can be brought back. */
|
|
276
282
|
herdr: boolean
|
|
277
283
|
}
|
|
@@ -316,8 +322,12 @@ type Watching = {
|
|
|
316
322
|
failureReadings: FailureReading[]
|
|
317
323
|
/** The last preview read that answered; recorded with every state like the feedback read. */
|
|
318
324
|
previewRead: PreviewRead | null
|
|
319
|
-
/** The sources run now, read again whenever one of their files changes (`
|
|
325
|
+
/** The sources run now, read again whenever one of their files changes (`nextLoadedConfig`). */
|
|
320
326
|
sources: LoadedSources
|
|
327
|
+
/** The deploy, feedback, previews and checks settings in force now, taken up with the sources. */
|
|
328
|
+
live: LiveValues
|
|
329
|
+
/** When a new deploy workflow was taken up, in epoch milliseconds: its runs from before count as the baseline. */
|
|
330
|
+
deploySince: number | null
|
|
321
331
|
/** The runs of the sources due at the last GitHub poll; recorded with every state like the feedback read. */
|
|
322
332
|
sourceRuns: SourceRun[]
|
|
323
333
|
/** The lanes' fills of the last read; a lane without an entry or with null has no value. */
|
|
@@ -347,6 +357,15 @@ type Watching = {
|
|
|
347
357
|
planViews: PlanViews
|
|
348
358
|
}
|
|
349
359
|
|
|
360
|
+
/** What the duty lines of one return are made of. */
|
|
361
|
+
type DutyFacts = {
|
|
362
|
+
stopped: readonly string[]
|
|
363
|
+
events: readonly WatchEvent[]
|
|
364
|
+
ready: Readonly<Record<string, ReadyPhase[]>>
|
|
365
|
+
mainCheckout: string | null
|
|
366
|
+
overlapOf: OverlapOf | undefined
|
|
367
|
+
}
|
|
368
|
+
|
|
350
369
|
const POLL_MS = 5_000
|
|
351
370
|
const GITHUB_POLL_MS = 60_000
|
|
352
371
|
const MINUTE_MS = 60_000
|
|
@@ -512,28 +531,37 @@ const laneBranchesOf = (watch: Watch, lanes: readonly string[]): Record<string,
|
|
|
512
531
|
const hintFor = (watch: Watch, reference: Reference | null): string =>
|
|
513
532
|
reference === null ? '' : referenceHint(reference, skillDirectoryFor(watch.skillCopies, existsSync))
|
|
514
533
|
|
|
515
|
-
/** The lines of one event with its reference hint; a deploy names its target when `watch.deploy` can read it. */
|
|
516
|
-
const linesOfEvent = async (
|
|
517
|
-
|
|
534
|
+
/** The lines of one event with its reference hint; a deploy names its target when `deploy` (`watch.deploy`) can read it. */
|
|
535
|
+
const linesOfEvent = async (
|
|
536
|
+
watch: Watch,
|
|
537
|
+
deploy: DeployConfig | undefined,
|
|
538
|
+
event: WatchEvent,
|
|
539
|
+
runs: readonly WorkflowRun[],
|
|
540
|
+
): Promise<string[]> => {
|
|
541
|
+
const hint = hintFor(
|
|
542
|
+
watch,
|
|
543
|
+
eventReference(event, (tool) => watch.stageTools.includes(tool)),
|
|
544
|
+
)
|
|
518
545
|
if (event.kind === WATCH_EVENT.feedback) return annotateFirstLine(feedbackEventLines(event), hint)
|
|
519
546
|
if (event.kind === WATCH_EVENT.source) return annotateFirstLine(sourceEventLines(event), hint)
|
|
520
547
|
if (isAppointmentEvent(event)) return [`${appointmentEventLine(event)}${hint}`]
|
|
521
548
|
const headSha = event.kind === WATCH_EVENT.deploy ? runs.find((run) => run.id === event.runId)?.headSha : undefined
|
|
522
549
|
const target =
|
|
523
|
-
event.kind === WATCH_EVENT.deploy && headSha &&
|
|
524
|
-
? await deployTargetOf(watch.root,
|
|
550
|
+
event.kind === WATCH_EVENT.deploy && headSha && deploy
|
|
551
|
+
? await deployTargetOf(watch.root, deploy, event.runId, headSha)
|
|
525
552
|
: null
|
|
526
553
|
return [`${watchEventLine(event, target ? targetText(target) : null)}${hint}`]
|
|
527
554
|
}
|
|
528
555
|
|
|
529
556
|
const eventLinesOf = async (
|
|
530
557
|
watch: Watch,
|
|
558
|
+
deploy: DeployConfig | undefined,
|
|
531
559
|
events: readonly WatchEvent[],
|
|
532
560
|
github: GithubRead | null,
|
|
533
561
|
): Promise<string[]> => {
|
|
534
562
|
const runs = answered(github)?.runs ?? []
|
|
535
563
|
const lines: string[] = []
|
|
536
|
-
for (const event of events) lines.push(...(await linesOfEvent(watch, event, runs)))
|
|
564
|
+
for (const event of events) lines.push(...(await linesOfEvent(watch, deploy, event, runs)))
|
|
537
565
|
return lines
|
|
538
566
|
}
|
|
539
567
|
|
|
@@ -619,7 +647,7 @@ const statusLinesOf = (
|
|
|
619
647
|
),
|
|
620
648
|
)
|
|
621
649
|
const outage = state.ghDownSince === null ? [] : [ghUnavailableStatusLine(clock(state.ghDownSince))]
|
|
622
|
-
const waiting = waitingLineOf(watch)
|
|
650
|
+
const waiting = waitingLineOf(watch, hintFor(watch, REFERENCE.waiting))
|
|
623
651
|
const board = { term: watch.settings.board.term, checkoutRows }
|
|
624
652
|
const coordinator = coordinatorStatusLine(watching.coordinator.name, watching.coordinator.read, watch.texts, board)
|
|
625
653
|
return {
|
|
@@ -644,7 +672,12 @@ const stoppedLaneSections = (
|
|
|
644
672
|
): { lines: string[]; reportMtimes: Record<string, number | null> } => {
|
|
645
673
|
const sections = stopped.map((lane) => {
|
|
646
674
|
const status = watching.current[lane]?.status
|
|
647
|
-
if (!status)
|
|
675
|
+
if (!status)
|
|
676
|
+
return {
|
|
677
|
+
lane,
|
|
678
|
+
lines: [`=== ${lane}: ${terminalSafe(LANE_GONE)}${hintFor(watch, REFERENCE.events)}`],
|
|
679
|
+
mtime: undefined,
|
|
680
|
+
}
|
|
648
681
|
const message = stopMessageOf(watch, lane, status, next)
|
|
649
682
|
const handoverHint = hasLaneHandover(worktreeOf(watch.root, watch.settings, lane), lane)
|
|
650
683
|
? hintFor(watch, REFERENCE.laneHandover)
|
|
@@ -662,14 +695,21 @@ const stoppedLaneSections = (
|
|
|
662
695
|
const mainCheckoutLineOf = (watch: Watch): string | null =>
|
|
663
696
|
mainCheckoutDutyLine(readMainCheckout(watch.root, watch.settings.baseBranch), watch.settings.baseBranch, watch.texts)
|
|
664
697
|
|
|
665
|
-
/**
|
|
698
|
+
/**
|
|
699
|
+
* The duty line of the main checkout once a fetch of the base under way has ended when `events` hold a merge, so the
|
|
700
|
+
* merge already counts. While it lags behind, the line names the reference of the board patch that follows a merge.
|
|
701
|
+
*/
|
|
666
702
|
const mainCheckoutLineAfter = async (
|
|
667
703
|
watch: Watch,
|
|
668
704
|
watching: Watching,
|
|
669
705
|
events: readonly WatchEvent[],
|
|
670
706
|
): Promise<string | null> => {
|
|
671
707
|
if (events.some((event) => event.kind === WATCH_EVENT.merged)) await watching.baseFetch
|
|
672
|
-
|
|
708
|
+
const read = readMainCheckout(watch.root, watch.settings.baseBranch)
|
|
709
|
+
const line = mainCheckoutDutyLine(read, watch.settings.baseBranch, watch.texts)
|
|
710
|
+
return line !== null && isBehindOnBase(read, watch.settings.baseBranch)
|
|
711
|
+
? `${line}${hintFor(watch, REFERENCE.events)}`
|
|
712
|
+
: line
|
|
673
713
|
}
|
|
674
714
|
|
|
675
715
|
/** The ready phases of the stopped lanes that rest (`done`, `idle`); only their plans are read. */
|
|
@@ -685,6 +725,35 @@ const readyPhasesOfResting = (
|
|
|
685
725
|
return watchedReadyPhases(watch.root, watch.settings, watch.sprintFile, lanes, workedPhaseKeys(progress))
|
|
686
726
|
}
|
|
687
727
|
|
|
728
|
+
/** The reader of which checks a merge asks of a lane; only a return that holds a merge reads the board for it. */
|
|
729
|
+
const overlapReaderFor = async (watch: Watch, events: readonly WatchEvent[]): Promise<OverlapOf | undefined> => {
|
|
730
|
+
if (!events.some((event) => event.kind === WATCH_EVENT.merged)) return undefined
|
|
731
|
+
try {
|
|
732
|
+
return mergeOverlapReader(watch.root, watch.settings, watch.sprintFile, await Bun.file(watch.sprintFile).text())
|
|
733
|
+
} catch {
|
|
734
|
+
return undefined
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** The duty lines of one return; those that start a lane name `start-lane.md`, a missing plan `plan-commit.md`. */
|
|
739
|
+
const dutyLinesOf = (watch: Watch, watching: Watching, facts: DutyFacts): string[] => {
|
|
740
|
+
const { stopped, events, ready, mainCheckout, overlapOf } = facts
|
|
741
|
+
const startLaneHint = hintFor(watch, REFERENCE.startLane)
|
|
742
|
+
return [
|
|
743
|
+
...blockedDutyLines(stopped, watching.current, watch.texts),
|
|
744
|
+
...annotateEach(readyPhaseDutyLines(stopped, watching.current, ready, watch.texts), startLaneHint),
|
|
745
|
+
...annotateEach(mergeDutyLines(events, watching.lanes, watch.texts, overlapOf), startLaneHint),
|
|
746
|
+
...openBoxesDutyLines(events, worktreeOpenBoxes(watch.root, watch.settings), watch.texts),
|
|
747
|
+
...(mainCheckout === null ? [] : [mainCheckout]),
|
|
748
|
+
...laneFillDutyLines(events, watch.texts),
|
|
749
|
+
...laneModelDutyLines(events, watch.texts),
|
|
750
|
+
...parallelDutyLines(events, watch.texts),
|
|
751
|
+
...noTaskSignalDutyLines(events, watch.texts),
|
|
752
|
+
...annotateEach(planMissingDutyLines(events, watch.texts), hintFor(watch, REFERENCE.planCommit)),
|
|
753
|
+
...approvalDutyLines(events, watch.texts),
|
|
754
|
+
]
|
|
755
|
+
}
|
|
756
|
+
|
|
688
757
|
/**
|
|
689
758
|
* Every line of one return, gathered before any is printed, so the output is never cut halfway by a slow lookup, and
|
|
690
759
|
* the state with what it showed: the report times of the stopped lanes and the status lines. A state without status
|
|
@@ -698,22 +767,12 @@ const reportOf = async (
|
|
|
698
767
|
next: WatchState,
|
|
699
768
|
): Promise<{ lines: string[]; next: WatchState }> => {
|
|
700
769
|
const stops = stoppedLaneSections(watch, watching, stopped, next)
|
|
701
|
-
const eventLines = await eventLinesOf(watch, events, watching.github)
|
|
770
|
+
const eventLines = await eventLinesOf(watch, watching.live.deploy, events, watching.github)
|
|
702
771
|
const mainCheckout = await mainCheckoutLineAfter(watch, watching, events)
|
|
703
772
|
const board = await boardReadOrNote(watch, watching.lanes, answered(watching.github)?.pullRequests ?? [])
|
|
704
773
|
const ready = readyPhasesOfResting(watch, watching, stopped, board.progress)
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
...readyPhaseDutyLines(stopped, watching.current, ready, watch.texts),
|
|
708
|
-
...mergeDutyLines(events, watching.lanes, watch.texts),
|
|
709
|
-
...(mainCheckout === null ? [] : [mainCheckout]),
|
|
710
|
-
...laneFillDutyLines(events, watch.texts),
|
|
711
|
-
...laneModelDutyLines(events, watch.texts),
|
|
712
|
-
...parallelDutyLines(events, watch.texts),
|
|
713
|
-
...noTaskSignalDutyLines(events, watch.texts),
|
|
714
|
-
...planMissingDutyLines(events, watch.texts),
|
|
715
|
-
...approvalDutyLines(events, watch.texts),
|
|
716
|
-
]
|
|
774
|
+
const overlapOf = await overlapReaderFor(watch, events)
|
|
775
|
+
const dutyLines = dutyLinesOf(watch, watching, { stopped, events, ready, mainCheckout, overlapOf })
|
|
717
776
|
const status = statusLinesOf(watch, watching, next, board)
|
|
718
777
|
const block = statusBlock(status.laneLines, status.coordinator, next.statusLines, status.others)
|
|
719
778
|
const shownReports = stopped.filter((lane) => stops.reportMtimes[lane] !== next.reportMtimes[lane])
|
|
@@ -744,7 +803,7 @@ const recordGithub = (
|
|
|
744
803
|
|
|
745
804
|
/**
|
|
746
805
|
* The logs of the lanes' red pull requests, once per head commit (`failureTargets`): `gh run view --log-failed` of each
|
|
747
|
-
* failed run, matched with `
|
|
806
|
+
* failed run, matched with `watching.live.checks.failedTestPattern`, which holds with feedback off too. A read
|
|
748
807
|
* that fails is recorded as not read, so the next GitHub poll asks again, a few times at most.
|
|
749
808
|
*/
|
|
750
809
|
const readFailures = async (
|
|
@@ -755,7 +814,7 @@ const readFailures = async (
|
|
|
755
814
|
): Promise<FailureReading[]> => {
|
|
756
815
|
const answer = answered(github)
|
|
757
816
|
if (!answer) return []
|
|
758
|
-
const pattern =
|
|
817
|
+
const pattern = watching.live.checks.failedTestPattern
|
|
759
818
|
const targets = failureTargets(watching.state.failures, answer.pullRequests, branches)
|
|
760
819
|
return Promise.all(
|
|
761
820
|
targets.map(async (target) => failureReadingOf(target, await readFailedTests(watch.root, target.runIds, pattern))),
|
|
@@ -773,10 +832,13 @@ const recordLaneFills = (watching: Watching, readings: LaneReadings): WatchEvent
|
|
|
773
832
|
return laneFillEvents(watching.state.laneLevels, readings)
|
|
774
833
|
}
|
|
775
834
|
|
|
776
|
-
/** Lane → the model column of its
|
|
777
|
-
const laneModelColumns = (watch: Watch): Record<string, string> =>
|
|
835
|
+
/** Lane → the model column of the row of its current phase; none when the board cannot be read, so no lane reports. */
|
|
836
|
+
const laneModelColumns = (watch: Watch, progress: Readonly<Record<string, LaneProgress>>): Record<string, string> =>
|
|
778
837
|
Object.fromEntries(
|
|
779
|
-
(readSprintLanes(watch) ?? []).flatMap((lane) =>
|
|
838
|
+
(readSprintLanes(watch) ?? []).flatMap((lane) => {
|
|
839
|
+
const column = modelColumnOf(lane, progress[lane.name])
|
|
840
|
+
return column === undefined ? [] : [[lane.name, column]]
|
|
841
|
+
}),
|
|
780
842
|
)
|
|
781
843
|
|
|
782
844
|
/** The agent states a lane can be switched in: it ended its turn and waits for no answer. */
|
|
@@ -787,8 +849,9 @@ const laneModelReadingsOf = (
|
|
|
787
849
|
watch: Watch,
|
|
788
850
|
watching: Watching,
|
|
789
851
|
models: Record<string, string | null>,
|
|
852
|
+
progress: Readonly<Record<string, LaneProgress>>,
|
|
790
853
|
): Record<string, LaneModelReading> => {
|
|
791
|
-
const columns = laneModelColumns(watch)
|
|
854
|
+
const columns = laneModelColumns(watch, progress)
|
|
792
855
|
return Object.fromEntries(
|
|
793
856
|
Object.entries(models).flatMap(([lane, model]) => {
|
|
794
857
|
const column = columns[lane]
|
|
@@ -833,12 +896,13 @@ const readFeedback = async (
|
|
|
833
896
|
targets: FeedbackTarget[],
|
|
834
897
|
now: number,
|
|
835
898
|
): Promise<FeedbackPoll | null> => {
|
|
836
|
-
|
|
899
|
+
const { feedback, previews } = watching.live
|
|
900
|
+
if (feedback === false || targets.length === 0) return null
|
|
837
901
|
const own = await ensureOwnLogin(watch, watching)
|
|
838
902
|
if (own === null) return null
|
|
839
903
|
const items = await readFeedbackItems(watch.root, targets, feedbackSince(watching.state.feedback, now))
|
|
840
904
|
if (!items.ok) return null
|
|
841
|
-
const filter = feedbackFilterOf(own,
|
|
905
|
+
const filter = feedbackFilterOf(own, feedback, previews?.authors ?? [])
|
|
842
906
|
return { read: feedbackReadOf(now, targets, items.items, filter), items: items.items }
|
|
843
907
|
}
|
|
844
908
|
|
|
@@ -854,11 +918,12 @@ const recordFeedbackRead = (watching: Watching, read: FeedbackRead | null): Watc
|
|
|
854
918
|
*/
|
|
855
919
|
const previewComments = async (
|
|
856
920
|
watch: Watch,
|
|
921
|
+
previews: LiveValues['previews'],
|
|
857
922
|
state: WatchState,
|
|
858
923
|
feedbackItems: FeedbackItem[] | null,
|
|
859
924
|
now: number,
|
|
860
925
|
): Promise<FeedbackItemsRead> => {
|
|
861
|
-
if (!
|
|
926
|
+
if (!previews || previews.authors.length === 0) return { ok: true, items: [] }
|
|
862
927
|
if (feedbackItems && feedbackReadCovers(state.feedback.since, state.previews.since))
|
|
863
928
|
return { ok: true, items: feedbackItems }
|
|
864
929
|
return readCommentItems(watch.root, feedbackSince(state.previews, now))
|
|
@@ -876,12 +941,12 @@ const readPreviews = async (
|
|
|
876
941
|
feedbackItems: FeedbackItem[] | null,
|
|
877
942
|
now: number,
|
|
878
943
|
): Promise<PreviewRead | null> => {
|
|
879
|
-
const previews =
|
|
944
|
+
const { previews, deploy } = watching.live
|
|
880
945
|
if (!previews || targets.length === 0) return null
|
|
881
946
|
const unreported = previews.deployments ? unreportedTargets(watching.state.previews, targets) : []
|
|
882
947
|
const [comments, deployments] = await Promise.all([
|
|
883
|
-
previewComments(watch, watching.state, feedbackItems, now),
|
|
884
|
-
readDeploymentCandidates(watch.root, unreported,
|
|
948
|
+
previewComments(watch, previews, watching.state, feedbackItems, now),
|
|
949
|
+
readDeploymentCandidates(watch.root, unreported, deploy?.environments ?? []),
|
|
885
950
|
])
|
|
886
951
|
if (!comments.ok || !deployments.ok) return null
|
|
887
952
|
const candidates = [...commentCandidates(comments.items, previews), ...deployments.candidates]
|
|
@@ -907,16 +972,28 @@ const runDueSources = (watch: Watch, watching: Watching, now: number): Promise<S
|
|
|
907
972
|
}
|
|
908
973
|
|
|
909
974
|
/**
|
|
910
|
-
* Takes up a change of the files the
|
|
911
|
-
*
|
|
975
|
+
* Takes up a change of the files the config comes from before the GitHub reads and the due sources run
|
|
976
|
+
* (`nextLoadedConfig`): the sources and the deploy, feedback, previews and checks settings, read once. A source read
|
|
977
|
+
* anew has no `ranAt` and runs at once, its first run a baseline; an invalid file keeps everything and is said once.
|
|
978
|
+
* A new deploy workflow and previews switched on start counting from now (`baselinesOf`).
|
|
912
979
|
*/
|
|
913
|
-
const
|
|
980
|
+
const reloadConfig = (watch: Watch, watching: Watching) => {
|
|
914
981
|
const stamps = readFileStamps(Object.keys(watching.sources.stamps))
|
|
915
|
-
const next =
|
|
916
|
-
|
|
982
|
+
const next = nextLoadedConfig({ sources: watching.sources, live: watching.live }, stamps, () =>
|
|
983
|
+
readWatchedConfig(watch.configRoots, homedir()),
|
|
984
|
+
)
|
|
985
|
+
const baselines = baselinesOf(watching.live, next.loaded.live)
|
|
986
|
+
watching.sources = next.loaded.sources
|
|
987
|
+
watching.live = next.loaded.live
|
|
988
|
+
if (baselines.deploy) watching.deploySince = Date.now()
|
|
989
|
+
if (baselines.previews) watching.state = { ...watching.state, previews: emptyPreviewState() }
|
|
917
990
|
for (const line of next.lines) console.log(line)
|
|
918
991
|
}
|
|
919
992
|
|
|
993
|
+
/** The GitHub read without the deploy runs from before a newly taken up deploy workflow. */
|
|
994
|
+
const withDeployBaseline = (github: GithubRead, since: number | null): GithubRead =>
|
|
995
|
+
github.ok ? { ...github, runs: withoutRunsBefore(github.runs, since) } : github
|
|
996
|
+
|
|
920
997
|
const recordSourceRuns = (watching: Watching, runs: SourceRun[]): WatchEvent[] => {
|
|
921
998
|
watching.sourceRuns = runs
|
|
922
999
|
return sourceEvents(watching.state.sources, runs, watching.sources.sources)
|
|
@@ -941,8 +1018,8 @@ const syncPlanHeads = (watch: Watch, watching: Watching, pullRequests: readonly
|
|
|
941
1018
|
|
|
942
1019
|
/**
|
|
943
1020
|
* Reads the coordinator's fill, GitHub, the feedback, the previews and the due sources, and stores them only once every read is complete: a SIGTERM
|
|
944
|
-
* while a read is pending saves the state as it was, so no event is taken as reported. Before the
|
|
945
|
-
* change of the files
|
|
1021
|
+
* while a read is pending saves the state as it was, so no event is taken as reported. Before the reads, a
|
|
1022
|
+
* change of the files the config comes from is taken up (`reloadConfig`), which prints its notes.
|
|
946
1023
|
*/
|
|
947
1024
|
const pollGithubAndCoordinator = async (watch: Watch, watching: Watching, now: number): Promise<WatchEvent[]> => {
|
|
948
1025
|
syncBase(watch, watching)
|
|
@@ -950,11 +1027,12 @@ const pollGithubAndCoordinator = async (watch: Watch, watching: Watching, now: n
|
|
|
950
1027
|
const fills = fillReadFor(watch, name, watching.agents, watching.lanes)
|
|
951
1028
|
const laneBranches = laneBranchesOf(watch, watching.lanes)
|
|
952
1029
|
const searched = Object.values({ ...watching.state.laneBranches, ...laneBranches })
|
|
953
|
-
|
|
954
|
-
const [
|
|
955
|
-
readGithub(watch.root,
|
|
1030
|
+
reloadConfig(watch, watching)
|
|
1031
|
+
const [read, sourceRuns] = await Promise.all([
|
|
1032
|
+
readGithub(watch.root, watching.live.deploy, watch.settings.baseBranch, searched),
|
|
956
1033
|
runDueSources(watch, watching, now),
|
|
957
1034
|
])
|
|
1035
|
+
const github = withDeployBaseline(read, watching.deploySince)
|
|
958
1036
|
const pullRequests = answered(github)?.pullRequests ?? null
|
|
959
1037
|
syncPlanHeads(watch, watching, pullRequests ?? [])
|
|
960
1038
|
const branches = { ...watching.state.laneBranches, ...laneBranches }
|
|
@@ -962,6 +1040,7 @@ const pollGithubAndCoordinator = async (watch: Watch, watching: Watching, now: n
|
|
|
962
1040
|
const targets = feedbackTargets(pullRequests, branches, watching.state.feedback)
|
|
963
1041
|
const feedback = await readFeedback(watch, watching, targets, now)
|
|
964
1042
|
const previews = pullRequests ? await readPreviews(watch, watching, targets, feedback?.items ?? null, now) : null
|
|
1043
|
+
const { progress: modelProgress } = await boardReadOrNote(watch, Object.keys(fills.models), pullRequests ?? [])
|
|
965
1044
|
return [
|
|
966
1045
|
...recordGithub(watching, github, laneBranches, now, failureReadings),
|
|
967
1046
|
...recordFeedbackRead(watching, feedback?.read ?? null),
|
|
@@ -969,7 +1048,7 @@ const pollGithubAndCoordinator = async (watch: Watch, watching: Watching, now: n
|
|
|
969
1048
|
...recordSourceRuns(watching, sourceRuns),
|
|
970
1049
|
...recordCoordinator(watching, { name, read: fills.coordinator }),
|
|
971
1050
|
...recordLaneFills(watching, fills.lanes),
|
|
972
|
-
...recordLaneModelReads(watching, laneModelReadingsOf(watch, watching, fills.models)),
|
|
1051
|
+
...recordLaneModelReads(watching, laneModelReadingsOf(watch, watching, fills.models, modelProgress)),
|
|
973
1052
|
...recordLaneParallelReads(watching, laneParallelReadingsOf(watch, watching)),
|
|
974
1053
|
]
|
|
975
1054
|
}
|
|
@@ -994,9 +1073,10 @@ const readAppointments = (watch: Watch, now: number): Watching['appointments'] =
|
|
|
994
1073
|
}
|
|
995
1074
|
|
|
996
1075
|
/** The line of what waits on the user's answer, from the board's own section; null when nothing is open or the board cannot be read. */
|
|
997
|
-
const waitingLineOf = (watch: Watch): string | null => {
|
|
1076
|
+
const waitingLineOf = (watch: Watch, hint = ''): string | null => {
|
|
998
1077
|
return waitingLine(
|
|
999
1078
|
waitingItemsOf(readBoardText(watch), watch.settings.planLanguage, readWaitingRecords(watch.root), Date.now()),
|
|
1079
|
+
hint,
|
|
1000
1080
|
)
|
|
1001
1081
|
}
|
|
1002
1082
|
|
|
@@ -1024,6 +1104,7 @@ const keepViews = (watch: Watch, watching: Watching, trigger: EnsureTrigger) =>
|
|
|
1024
1104
|
watching.planViews = keepPlanViews(trigger, watching.planViews, {
|
|
1025
1105
|
ensure: () => ensurePlanPanes(watch.root, watch.settings, watch.sprintFile),
|
|
1026
1106
|
log: console.log,
|
|
1107
|
+
failureHint: () => hintFor(watch, REFERENCE.events),
|
|
1027
1108
|
})
|
|
1028
1109
|
}
|
|
1029
1110
|
|
|
@@ -1037,7 +1118,7 @@ const followLaneSet = (watch: Watch, watching: Watching): string[] => {
|
|
|
1037
1118
|
watching.departed = [...watching.departed, ...departedLanes(change.removed, watching.baseline, watching.current)]
|
|
1038
1119
|
watching.lanes = lanes
|
|
1039
1120
|
watching.current = toSnapshot(watching.agents, lanes)
|
|
1040
|
-
console.log(
|
|
1121
|
+
console.log(laneSetEventLines(change, hintFor(watch, REFERENCE.events)).join('\n'))
|
|
1041
1122
|
return change.added
|
|
1042
1123
|
}
|
|
1043
1124
|
|
|
@@ -1076,7 +1157,10 @@ const report = async (
|
|
|
1076
1157
|
watching.reporting = true
|
|
1077
1158
|
const { lines, next: shown } = await reportOf(watch, watching, stopped, events, next)
|
|
1078
1159
|
console.log(
|
|
1079
|
-
(watch.options.stream
|
|
1160
|
+
(watch.options.stream
|
|
1161
|
+
? lines
|
|
1162
|
+
: withSingleReportNotice(lines, monitorToolAvailable(process.env), hintFor(watch, REFERENCE.watchWithoutMonitor))
|
|
1163
|
+
).join('\n'),
|
|
1080
1164
|
)
|
|
1081
1165
|
writePrivateFile(watch.files.state, JSON.stringify(shown))
|
|
1082
1166
|
watching.reporting = false
|
|
@@ -1382,6 +1466,8 @@ const initialWatching = (
|
|
|
1382
1466
|
failureReadings: [],
|
|
1383
1467
|
previewRead: null,
|
|
1384
1468
|
sources: watch.sources,
|
|
1469
|
+
live: watch.live,
|
|
1470
|
+
deploySince: null,
|
|
1385
1471
|
sourceRuns: [],
|
|
1386
1472
|
laneReadings: {},
|
|
1387
1473
|
laneModelReadings: {},
|
|
@@ -1413,12 +1499,20 @@ const watchLanes = async (watch: Watch): Promise<boolean> => {
|
|
|
1413
1499
|
const lanes = lanesFor(watch, agents, sprintLanes)
|
|
1414
1500
|
const armed = toSnapshot(agents, lanes)
|
|
1415
1501
|
const arm = armNotice(state.armed, lanes, armed, takeOver, relative(watch.root, watch.sprintFile))
|
|
1416
|
-
if (arm.line !== null)
|
|
1502
|
+
if (arm.line !== null)
|
|
1503
|
+
console.log(`${arm.line}${takeOver.stillRunning === null ? '' : hintFor(watch, REFERENCE.events)}`)
|
|
1417
1504
|
if (watch.boardNotice) console.log(watch.boardNotice)
|
|
1418
1505
|
|
|
1419
1506
|
const watching = initialWatching(watch, { lanes, sprintLanes, state: { ...state, armed: arm.state }, armed, agents })
|
|
1420
1507
|
keepViews(watch, watching, { kind: ENSURE_TRIGGER.start })
|
|
1508
|
+
const resumeRecord = keepRecord(
|
|
1509
|
+
{ root: watch.root, board: watch.sprintFile, coordinator: coordinatorNameFor(watch, agents), pid: process.pid },
|
|
1510
|
+
Date.now(),
|
|
1511
|
+
{ store: systemRecordStore(watch.root), log: console.log },
|
|
1512
|
+
)
|
|
1513
|
+
resumeRecord.follow(armed, Date.now())
|
|
1421
1514
|
process.on('SIGTERM', () => {
|
|
1515
|
+
resumeRecord.stop(Date.now())
|
|
1422
1516
|
saveState(watch, watching)
|
|
1423
1517
|
console.log(stopNoticeOf(watch.files.lock))
|
|
1424
1518
|
process.exit(0)
|
|
@@ -1438,16 +1532,23 @@ const watchLanes = async (watch: Watch): Promise<boolean> => {
|
|
|
1438
1532
|
: null
|
|
1439
1533
|
const sourceChanges = kit && runsFromSource(kit.packageRoot) ? watchSourceChanges(kit.packageRoot) : null
|
|
1440
1534
|
const sourceChanged = () => sourceChangeSettled(sourceChanges?.lastChangeAt() ?? null, Date.now())
|
|
1441
|
-
const effects = {
|
|
1535
|
+
const effects = {
|
|
1536
|
+
saveState: () => saveState(watch, watching),
|
|
1537
|
+
log: console.log,
|
|
1538
|
+
skillLine,
|
|
1539
|
+
sourceChanged,
|
|
1540
|
+
}
|
|
1442
1541
|
try {
|
|
1443
1542
|
while (await pollOnce(watch, watching)) {
|
|
1444
1543
|
await Bun.sleep(POLL_MS)
|
|
1445
1544
|
pollLanes(watch, watching)
|
|
1545
|
+
resumeRecord.follow(watching.current, Date.now())
|
|
1446
1546
|
await announceUpdate?.()
|
|
1447
1547
|
if (endedByKitUpdate(kit, effects)) return true
|
|
1448
1548
|
}
|
|
1449
1549
|
return false
|
|
1450
1550
|
} finally {
|
|
1551
|
+
resumeRecord.stop(Date.now())
|
|
1451
1552
|
sourceChanges?.close()
|
|
1452
1553
|
stopPublisher?.()
|
|
1453
1554
|
}
|
|
@@ -1489,7 +1590,7 @@ const watchOf = (options: WatchOptions): Watch => {
|
|
|
1489
1590
|
// Stamped before the read: a change in between is read again at the first poll, never missed.
|
|
1490
1591
|
const stamps = readFileStamps(sourceFilePaths(configRoots, homedir()))
|
|
1491
1592
|
const { config, notes } = readResolvedConfig(configRoots)
|
|
1492
|
-
const merged = sourcesWithPersonal(config.watch.sources)
|
|
1593
|
+
const merged = sourcesWithPersonal(config.watch.sources, readRepositoryIdentity(root))
|
|
1493
1594
|
for (const note of [...notes, ...merged.notes]) console.log(note)
|
|
1494
1595
|
const settings = plansSettingsOf(config)
|
|
1495
1596
|
const picked = boardFor(options.boardPath, [root, worktreeRoot(process.cwd())], settings, () =>
|
|
@@ -1502,17 +1603,15 @@ const watchOf = (options: WatchOptions): Watch => {
|
|
|
1502
1603
|
options,
|
|
1503
1604
|
files: filesOf(root),
|
|
1504
1605
|
settings,
|
|
1505
|
-
|
|
1506
|
-
feedback: config.watch.feedback,
|
|
1507
|
-
checks: config.watch.checks,
|
|
1606
|
+
live: liveValuesOf(config),
|
|
1508
1607
|
parallelTasks: config.watch.parallelTasks,
|
|
1509
1608
|
taskSignalMinutes: config.watch.taskSignalMinutes,
|
|
1510
1609
|
parallelAuto: autoSettingsOf(root, checkoutRoot, config),
|
|
1511
|
-
previews: config.watch.previews,
|
|
1512
1610
|
sources: { sources: merged.sources, stamps },
|
|
1513
1611
|
configRoots,
|
|
1514
1612
|
texts: watchTexts(settings.responseLanguage),
|
|
1515
1613
|
skillCopies: skillCopiesFor(root, homedir(), config.skillPrefix, DEFAULT_SKILL_PREFIX),
|
|
1614
|
+
stageTools: stageToolsOf(config.guard.approvals.standing),
|
|
1516
1615
|
herdr: insideHerdr(settings.adapter, process.env),
|
|
1517
1616
|
}
|
|
1518
1617
|
}
|