jackherd 1.23.3 → 1.24.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.
@@ -32,6 +32,14 @@ import { existsSync, readFileSync } from 'node:fs'
32
32
  import { homedir } from 'node:os'
33
33
  import { basename, dirname, join, relative } from 'node:path'
34
34
  import { writePrivateFile } from '../../kit/hooks/hooks/shared/private-directory'
35
+ import {
36
+ type BackendWatch,
37
+ backendCheckOf,
38
+ INITIAL_BACKEND_WATCH,
39
+ keepBackend,
40
+ type MainCheckoutBackend,
41
+ mainCheckoutBackendOf,
42
+ } from '../backend/watch'
35
43
  import { sourcesWithPersonal } from '../config/personal-sources'
36
44
  import { readConfig, readResolvedConfig } from '../config/read-config'
37
45
  import { readRepositoryIdentity } from '../config/repository-identity'
@@ -200,6 +208,7 @@ import { recordedReadsOf } from './recorded-reads'
200
208
  import {
201
209
  annotateEach,
202
210
  annotateFirstLine,
211
+ BACKEND_REFERENCE,
203
212
  eventReference,
204
213
  laneSetEventLines,
205
214
  REFERENCE,
@@ -295,6 +304,8 @@ type Watch = {
295
304
  selfMergeMinutes: number
296
305
  /** Runs inside herdr with the herdr adapter, so missing herd panes can be brought back. */
297
306
  herdr: boolean
307
+ /** `lanes.backend` and the worktree directory of the main checkout's config; undefined without one, then the backend is never touched. */
308
+ backend: MainCheckoutBackend | undefined
298
309
  }
299
310
 
300
311
  /** The coordinator's agent name (null when none is found), its last read (null before the first one) and the tokens of that read (null without a value). */
@@ -372,6 +383,8 @@ type Watching = {
372
383
  /** The questions of each watched lane's report at the last poll (`questionsFingerprint`); null before the first read, so the stored ones stay. */
373
384
  reportQuestions: Record<string, string> | null
374
385
  planViews: PlanViews
386
+ /** When the backend pane was last checked and whether a failure of it was already said. */
387
+ backend: BackendWatch
375
388
  }
376
389
 
377
390
  /** What the duty lines of one return are made of. */
@@ -1134,6 +1147,16 @@ const keepViews = (watch: Watch, watching: Watching, trigger: EnsureTrigger) =>
1134
1147
  })
1135
1148
  }
1136
1149
 
1150
+ /** Keeps the shared backend pane once it is due (`BACKEND_CHECK_MS`); only inside herdr and only with `lanes.backend`. */
1151
+ const keepBackendPane = async (watch: Watch, watching: Watching) => {
1152
+ if (!watch.herdr || !watch.backend) return
1153
+ watching.backend = await keepBackend(watching.backend, Date.now(), {
1154
+ check: backendCheckOf(watch.root, watch.backend),
1155
+ log: console.log,
1156
+ hint: hintFor(watch, BACKEND_REFERENCE),
1157
+ })
1158
+ }
1159
+
1137
1160
  /** Takes up lanes that were found or dropped since the last poll and returns the lanes it added; named lanes never change. */
1138
1161
  const followLaneSet = (watch: Watch, watching: Watching): string[] => {
1139
1162
  if (watch.options.lanes !== null) return []
@@ -1570,6 +1593,7 @@ const initialWatching = (
1570
1593
  appointments: null,
1571
1594
  reportQuestions: null,
1572
1595
  planViews: { failureReported: false },
1596
+ backend: INITIAL_BACKEND_WATCH,
1573
1597
  })
1574
1598
 
1575
1599
  /** Saves the state a stop leaves behind: the one from before an unfinished report or an unread GitHub answer, else the latest. */
@@ -1593,6 +1617,7 @@ const watchLanes = async (watch: Watch): Promise<boolean> => {
1593
1617
 
1594
1618
  const watching = initialWatching(watch, { lanes, sprintLanes, state: { ...state, armed: arm.state }, armed, agents })
1595
1619
  keepViews(watch, watching, { kind: ENSURE_TRIGGER.start })
1620
+ await keepBackendPane(watch, watching)
1596
1621
  const resumeRecord = keepRecord(
1597
1622
  { root: watch.root, board: watch.sprintFile, coordinator: coordinatorNameFor(watch, agents), pid: process.pid },
1598
1623
  Date.now(),
@@ -1638,6 +1663,7 @@ const watchLanes = async (watch: Watch): Promise<boolean> => {
1638
1663
  while (await pollOnce(watch, watching)) {
1639
1664
  await Bun.sleep(POLL_MS)
1640
1665
  pollLanes(watch, watching)
1666
+ await keepBackendPane(watch, watching)
1641
1667
  resumeRecord.follow(watching.current, Date.now(), observed())
1642
1668
  await announceUpdate?.()
1643
1669
  if (endedByKitUpdate(kit, effects)) return true
@@ -1710,6 +1736,7 @@ const watchOf = (options: WatchOptions): Watch => {
1710
1736
  stageTools: stageToolsOf(config.guard.approvals.standing),
1711
1737
  selfMergeMinutes: config.guard.selfMerge.approvalMinutes,
1712
1738
  herdr: insideHerdr(settings.adapter, process.env),
1739
+ backend: mainCheckoutBackendOf(root, checkoutRoot, config),
1713
1740
  }
1714
1741
  }
1715
1742
 
@@ -24,10 +24,14 @@ export const REFERENCE = {
24
24
  watchWithoutMonitor: 'references/watch-without-monitor.md',
25
25
  adoptPlan: 'references/adopt-plan.md',
26
26
  waiting: 'references/waiting.md',
27
+ backend: 'references/backend.md',
27
28
  } as const
28
29
 
29
30
  export type Reference = (typeof REFERENCE)[keyof typeof REFERENCE]
30
31
 
32
+ /** Where the coordinator reads what a `backend:` line of the watcher asks for: the shared backend. */
33
+ export const BACKEND_REFERENCE: Reference = REFERENCE.backend
34
+
31
35
  /** A copy of the orchestrate skill: the directory as the session reads it, and where that is on disk. */
32
36
  export type SkillCopy = { shown: string; path: string }
33
37