jackherd 1.8.1 → 1.10.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 +60 -0
- package/INSTALL.md +3 -1
- package/README.md +75 -64
- package/dist/{chunk-OU2KALXP.js → chunk-5MQBCUQ5.js} +24 -4
- package/dist/config.d.ts +45 -4
- package/dist/config.js +84 -27
- package/dist/plan-format.d.ts +63 -7
- package/dist/plan-format.js +66 -12
- package/docs/workflow-dark.svg +67 -63
- package/docs/workflow.svg +67 -63
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/ask-rules.json +15 -0
- package/kit/hooks/hooks/approval-gate/index.ts +22 -4
- package/kit/hooks/hooks/approval-gate/run.ts +29 -6
- package/kit/hooks/hooks/approval-gate/subagent.ts +18 -0
- package/kit/hooks/hooks/ask-hint/decision.ts +82 -14
- package/kit/hooks/hooks/ask-hint/index.ts +15 -19
- package/kit/hooks/hooks/ask-hint/settings.ts +3 -18
- package/kit/hooks/hooks/git-guard/decision.ts +50 -102
- package/kit/hooks/hooks/merge-guard/always-ask.ts +63 -0
- package/kit/hooks/hooks/merge-guard/{plan-approval-settings.ts → board-settings.ts} +21 -67
- package/kit/hooks/hooks/merge-guard/check-free.ts +20 -0
- package/kit/hooks/hooks/merge-guard/decision.ts +127 -140
- package/kit/hooks/hooks/merge-guard/index.ts +6 -3
- package/kit/hooks/hooks/merge-guard/lane-plan.ts +79 -0
- package/kit/hooks/hooks/merge-guard/run.ts +59 -54
- package/kit/hooks/hooks/merge-guard/settings.ts +13 -1
- package/kit/hooks/hooks/merge-guard/standing-approval.ts +142 -0
- package/kit/hooks/hooks/open-question/index.ts +26 -0
- package/kit/hooks/hooks/open-question/record.ts +135 -0
- package/kit/hooks/hooks/open-question/run.ts +52 -0
- package/kit/hooks/hooks/open-question/store.ts +39 -0
- package/kit/hooks/hooks/sent-file/index.ts +19 -0
- package/kit/hooks/hooks/sent-file/record.ts +128 -0
- package/kit/hooks/hooks/sent-file/run.ts +48 -0
- package/kit/hooks/hooks/sent-file/store.ts +9 -0
- package/kit/hooks/hooks/shared/board-lanes.ts +15 -2
- package/kit/hooks/hooks/shared/config.ts +7 -0
- package/kit/hooks/hooks/shared/dialog-answers.ts +9 -9
- package/kit/hooks/hooks/shared/files.ts +7 -0
- package/kit/hooks/hooks/shared/lane.ts +1 -1
- package/kit/hooks/hooks/shared/newest-verdict.ts +10 -0
- package/kit/hooks/hooks/shared/private-directory.ts +56 -9
- package/kit/hooks/hooks/shared/program-position.ts +2 -75
- package/kit/hooks/hooks/shared/shell-aliases.ts +1 -71
- package/kit/hooks/hooks/{ask-hint → shared}/transcript.ts +37 -6
- package/kit/hooks/hooks/{merge-guard → shared}/xargs-program.ts +10 -8
- package/kit/hooks/hooks/task-hint/decision.ts +49 -0
- package/kit/hooks/hooks/task-hint/index.ts +79 -0
- package/kit/hooks/hooks/task-hint/run.ts +62 -0
- package/kit/hooks/hooks/task-hint/store.ts +51 -0
- package/kit/hooks/hooks.json +21 -4
- package/kit/hooks/statusline.sh +1 -1
- package/kit/review/agents/skill-review.md +34 -0
- package/kit/review/skills/pre-commit/SKILL.md +1 -1
- package/kit/workflow/skills/follow/SKILL.md +1 -8
- package/kit/workflow/skills/implement/SKILL.md +10 -6
- package/kit/workflow/skills/orchestrate/SKILL.md +213 -412
- package/kit/workflow/skills/orchestrate/handover/decision.ts +43 -8
- package/kit/workflow/skills/orchestrate/handover/index.ts +32 -4
- package/kit/workflow/skills/orchestrate/handover/standing-approval.ts +84 -0
- package/kit/workflow/skills/orchestrate/references/acceptance.md +15 -0
- package/kit/workflow/skills/orchestrate/references/close-lane.md +17 -0
- package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +43 -17
- package/kit/workflow/skills/orchestrate/references/draft-review.md +19 -0
- package/kit/workflow/skills/orchestrate/references/events.md +86 -0
- package/kit/workflow/skills/orchestrate/references/herd.md +6 -6
- package/kit/workflow/skills/orchestrate/references/lane-handover.md +18 -0
- package/kit/workflow/skills/orchestrate/references/lane-session.md +25 -0
- package/kit/workflow/skills/orchestrate/references/lane-setup.md +37 -0
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -10
- package/kit/workflow/skills/orchestrate/references/sensitive-input.md +5 -7
- package/kit/workflow/skills/plan/SKILL.md +6 -5
- package/kit/workflow/skills/plan/references/template-English.md +4 -4
- package/kit/workflow/skills/plan/references/template-German.md +4 -4
- package/package.json +4 -1
- package/recipes/README.md +2 -1
- package/recipes/sources.md +1 -1
- package/schemas/jackherd.schema.json +85 -7
- package/src/check/agent-text-reviewers.ts +23 -0
- package/src/check/command.ts +2 -0
- package/src/check/plan-structure-check.ts +49 -2
- package/src/check/plan.ts +8 -1
- package/src/check/schema.ts +9 -0
- package/src/check/skill-budget-defaults.ts +55 -0
- package/src/check/skill-budgets.ts +185 -0
- package/src/cli.ts +4 -0
- package/src/config/auto-window.ts +8 -0
- package/src/config/link-kinds.ts +7 -1
- package/src/config/schema.ts +80 -26
- package/src/context/fill.ts +12 -0
- package/src/context/session-log.ts +43 -1
- package/src/lane/close-board.ts +106 -0
- package/src/lane/close-check.ts +183 -0
- package/src/lane/close-command-line.ts +11 -0
- package/src/lane/close-command.ts +217 -0
- package/src/lane/close-options.ts +43 -0
- package/src/lane/close-survey.ts +83 -0
- package/src/lane/close.ts +141 -0
- package/src/lane/command.ts +13 -0
- package/src/lane-prompt/build.ts +74 -0
- package/src/lane-prompt/command.ts +87 -0
- package/src/lane-prompt/input.ts +116 -0
- package/src/lane-prompt/options.ts +68 -0
- package/src/lane-prompt/texts.ts +101 -0
- package/src/plan-format/clock.ts +12 -0
- package/src/plan-format/dependencies.ts +9 -9
- package/src/plan-format/index.ts +1 -0
- package/src/plan-format/lanes.ts +30 -5
- package/src/plan-format/short-names.ts +71 -0
- package/src/plan-format/terminal-text.ts +4 -0
- package/src/plan-format/waiting.ts +53 -0
- package/src/plans/active-tasks.ts +60 -0
- package/src/plans/appointment-rows.ts +59 -0
- package/src/plans/ask-coordinator.ts +77 -0
- package/src/plans/ask-target.ts +25 -0
- package/src/plans/child-stop.ts +51 -0
- package/src/plans/closable-records.ts +34 -0
- package/src/plans/command.ts +3 -2
- package/src/plans/current-phase.ts +17 -7
- package/src/plans/current-task.ts +63 -5
- package/src/plans/fetch.ts +26 -3
- package/src/plans/git.ts +19 -5
- package/src/plans/herd-claim.ts +173 -0
- package/src/plans/herd-memory.ts +36 -25
- package/src/plans/lane-plans.ts +44 -13
- package/src/plans/lane-standing.ts +9 -4
- package/src/plans/lane-summary.ts +367 -92
- package/src/plans/lane-tickets.ts +15 -0
- package/src/plans/layout-phase.ts +80 -19
- package/src/plans/layout-summary.ts +163 -74
- package/src/plans/layout.ts +472 -114
- package/src/plans/link-chips.ts +1 -0
- package/src/plans/live.ts +119 -10
- package/src/plans/phase-ids.ts +8 -0
- package/src/plans/phase-models.ts +1 -1
- package/src/plans/phase-ranges.ts +45 -0
- package/src/plans/phase-source.ts +15 -0
- package/src/plans/plan-cache.ts +28 -0
- package/src/plans/plan-heads.ts +54 -0
- package/src/plans/pull-requests.ts +23 -2
- package/src/plans/question-records.ts +48 -0
- package/src/plans/questions-read.ts +13 -0
- package/src/plans/read-cache.ts +8 -0
- package/src/plans/record-id.ts +4 -0
- package/src/plans/restart-decision.ts +13 -4
- package/src/plans/selection.ts +117 -34
- package/src/plans/sent-files-read.ts +13 -0
- package/src/plans/sent-files.ts +114 -0
- package/src/plans/settings.ts +2 -1
- package/src/plans/signalled-phase.ts +42 -0
- package/src/plans/signals-read.ts +62 -0
- package/src/plans/snapshot.ts +279 -60
- package/src/plans/sprint-export.ts +11 -11
- package/src/plans/sprint-page.ts +5 -5
- package/src/plans/subagent-phases.ts +95 -0
- package/src/plans/subagent-tasks.ts +51 -0
- package/src/plans/subagents-read.ts +131 -0
- package/src/plans/subagents.ts +190 -0
- package/src/plans/supervisor.ts +14 -4
- package/src/plans/symbols.ts +3 -0
- package/src/plans/task-name.ts +34 -0
- package/src/plans/texts.ts +36 -14
- package/src/plans/unlisted-lanes.ts +10 -5
- package/src/plans/view.ts +20 -12
- package/src/plans/waiting-click.ts +231 -0
- package/src/plans/waiting-records.ts +39 -0
- package/src/plans/waiting-window.ts +8 -0
- package/src/preview/command.ts +105 -0
- package/src/preview/embed.ts +209 -0
- package/src/preview/images.ts +89 -0
- package/src/preview/open.ts +12 -0
- package/src/preview/options.ts +32 -0
- package/src/preview/page.ts +89 -0
- package/src/preview/render.ts +41 -0
- package/src/settled/board-row.ts +1 -2
- package/src/shared/is-record.ts +2 -0
- package/src/shared/private-location.ts +89 -0
- package/src/shared/private-records.ts +48 -0
- package/src/shared/stat-cache.ts +48 -0
- package/src/sync/kit-source.ts +29 -10
- package/src/sync/kit.ts +2 -0
- package/src/sync/manifest.ts +2 -0
- package/src/sync/plan-ask-rules.ts +94 -0
- package/src/sync/plan-hooks.ts +2 -0
- package/src/sync/render.ts +3 -3
- package/src/sync/settings-hooks.ts +4 -1
- package/src/sync/sync-plan.ts +1 -0
- package/src/sync/sync.ts +24 -3
- package/src/task-signals/agent.ts +15 -0
- package/src/task-signals/claude-read.ts +89 -0
- package/src/task-signals/claude-signals.ts +112 -0
- package/src/task-signals/command.ts +71 -0
- package/src/task-signals/fixtures/session.jsonl +7 -0
- package/src/task-signals/matcher.ts +96 -0
- package/src/task-signals/record-store.ts +40 -0
- package/src/task-signals/record.ts +68 -0
- package/src/task-signals/text.ts +13 -0
- package/src/task-signals/types.ts +14 -0
- package/src/terminal/open.ts +13 -6
- package/src/terminal/text.ts +16 -4
- package/src/terminal/width.ts +2 -1
- package/src/version/skill-diff.ts +46 -0
- package/src/vocabulary/index.ts +44 -6
- package/src/waiting/command.ts +50 -0
- package/src/watch/appointments.ts +2 -4
- package/src/watch/approvals.ts +2 -2
- package/src/watch/asking-lanes.ts +40 -0
- package/src/watch/base-tip.ts +11 -0
- package/src/watch/command.ts +401 -90
- package/src/watch/failed-tests.ts +56 -10
- package/src/watch/failure-read.ts +18 -6
- package/src/watch/failures.ts +29 -5
- package/src/watch/feedback.ts +1 -1
- package/src/watch/gh-process.ts +9 -3
- package/src/watch/heartbeat-progress.ts +6 -18
- package/src/watch/json-guards.ts +0 -3
- package/src/watch/kit-update.ts +28 -9
- package/src/watch/lane-fill.ts +1 -1
- package/src/watch/lane-model.ts +1 -1
- package/src/watch/lane-name.ts +6 -0
- package/src/watch/lane-parallel.ts +1 -1
- package/src/watch/lane-progress.ts +58 -14
- package/src/watch/lane-report-file.ts +20 -5
- package/src/watch/lane-report.ts +37 -5
- package/src/watch/lane-rest.ts +28 -0
- package/src/watch/lane-sources.ts +31 -3
- package/src/watch/main-checkout.ts +34 -0
- package/src/watch/plan-badges-read.ts +13 -7
- package/src/watch/plan-badges.ts +9 -2
- package/src/watch/previews.ts +1 -1
- package/src/watch/pull-requests.ts +8 -3
- package/src/watch/reference-paths.ts +87 -0
- package/src/watch/running-subagents.ts +43 -0
- package/src/watch/skill-change.ts +124 -32
- package/src/watch/source-change.ts +48 -0
- package/src/watch/source-path.ts +1 -1
- package/src/watch/sources.ts +7 -4
- package/src/watch/stream-output.ts +14 -8
- package/src/watch/task-parallel-auto.ts +208 -0
- package/src/watch/task-parallel-data-paths.ts +46 -0
- package/src/watch/task-parallel-input.ts +108 -0
- package/src/watch/task-parallel-read.ts +151 -0
- package/src/watch/task-parallel-touched.ts +39 -0
- package/src/watch/task-parallel.ts +235 -0
- package/src/watch/task-signal-missing.ts +69 -0
- package/src/watch/task-signal-read.ts +29 -0
- package/src/watch/texts.ts +10 -0
- package/src/watch/update-available.ts +6 -3
- package/src/watch/waiting-line.ts +19 -0
- package/src/watch/watch-events.ts +36 -1
- package/src/watch/watcher-lock.ts +25 -29
- package/src/watch/workflow-runs.ts +2 -1
- package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +0 -23
- package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +0 -196
- package/kit/hooks/hooks/merge-guard/plan-approval.ts +0 -168
- package/kit/hooks/hooks/merge-guard/plan-growth.ts +0 -40
- package/kit/hooks/hooks/merge-guard/plan-pointers.ts +0 -194
- package/kit/hooks/hooks/shared/command-text-runners.ts +0 -71
- package/kit/hooks/hooks/shared/plan-open-phases.ts +0 -114
package/src/watch/command.ts
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* `watch.deploy` in the config), new feedback on a lane's open pull request (unless `watch.feedback` is false), a deploy preview for a
|
|
5
5
|
* lane's new head commit (only with `watch.previews`), a new or changed entry of a configured source or its failure (only
|
|
6
6
|
* with `watch.sources`), a gh outage, a lane's or the coordinator's own context fill rising to warn or alert, or the
|
|
7
|
-
* heartbeat. A blocked lane adds one duty line, a merge one per lane, a lane at alert one,
|
|
7
|
+
* heartbeat. A blocked lane adds one duty line, a merge one per lane, a lane at alert one, the main checkout behind
|
|
8
|
+
* `origin/<base>` one in every report and heartbeat until it caught up, and every report ends with one status line
|
|
8
9
|
* per lane and one for the coordinator, in English. When the installed kit changes version
|
|
9
|
-
* it prints `=== kit updated <old> → <new>` and ends
|
|
10
|
+
* it prints `=== kit updated <old> → <new>` and ends, and so does a watcher that runs from a checkout's source when
|
|
11
|
+
* that source changes (`… (source changed)`, the version kept); once a day it asks npm and prints `=== update available <old> → <new>`
|
|
10
12
|
* for a newer release, once per version. The coordinator is the agent in the
|
|
11
13
|
* watcher's own herdr pane unless `--coordinator` names it, else `jack-<repository>` (or the old `orchestrator-<repository>`). A new start replaces a running watcher of the same
|
|
12
14
|
* repository; one in another repository keeps running.
|
|
@@ -26,10 +28,11 @@
|
|
|
26
28
|
* jh watch [--stream] [--heartbeat <minutes>] [--board <board.md>] [--coordinator <name>] [<lane> ...]
|
|
27
29
|
* jh watch source <name|draft.json|-> --dry-run
|
|
28
30
|
*/
|
|
29
|
-
import { readFileSync } from 'node:fs'
|
|
31
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
30
32
|
import { homedir } from 'node:os'
|
|
31
33
|
import { basename, dirname, join, relative } from 'node:path'
|
|
32
|
-
import {
|
|
34
|
+
import { writePrivateFile } from '../../kit/hooks/hooks/shared/private-directory'
|
|
35
|
+
import { readConfig, readResolvedConfig } from '../config/read-config'
|
|
33
36
|
import type { JackherdConfig } from '../config/schema'
|
|
34
37
|
import { resolveCoordinatorName } from '../context/coordinator-name'
|
|
35
38
|
import { coordinatorSettings } from '../context/options'
|
|
@@ -40,11 +43,15 @@ import { terminalSafe } from '../plan-format/terminal-text'
|
|
|
40
43
|
import { boardFor, foundLanesOf } from '../plans/board-pick'
|
|
41
44
|
import { ensurePlanPanes } from '../plans/ensure-panes'
|
|
42
45
|
import { errorText } from '../plans/error-text'
|
|
43
|
-
import { mainCheckout, worktreeNames, worktreeRoot } from '../plans/git'
|
|
46
|
+
import { baseReaderOf, mainCheckout, worktreeNames, worktreeRoot } from '../plans/git'
|
|
44
47
|
import { discoverLanes, isUnchanged, laneSetChange, laneSetLines } from '../plans/lane-discovery'
|
|
48
|
+
import { readerOncePerRef } from '../plans/plan-heads'
|
|
45
49
|
import { CLOCK_LOCALE, type PlansSettings, plansSettingsOf, worktreeOf } from '../plans/settings'
|
|
50
|
+
import { readRunningSubagents, subagentSourceOf } from '../plans/subagents-read'
|
|
46
51
|
import { boardTexts } from '../plans/texts'
|
|
52
|
+
import { readWaitingRecords, waitingItemsOf } from '../plans/waiting-records'
|
|
47
53
|
import { defaultUpdateCheck } from '../sync/if-changed'
|
|
54
|
+
import { DEFAULT_SKILL_PREFIX } from '../sync/init'
|
|
48
55
|
import { herdrLaneStatusSource } from '../terminal/herdr'
|
|
49
56
|
import { AGENT_STATUS } from '../terminal/types'
|
|
50
57
|
import { startedKit } from '../version/started-kit'
|
|
@@ -56,6 +63,7 @@ import {
|
|
|
56
63
|
recordAppointments,
|
|
57
64
|
} from './appointments'
|
|
58
65
|
import { type ApprovalRequestFile, approvalEvents, readApprovalRequests, recordApprovals } from './approvals'
|
|
66
|
+
import { askingLanes, questionsFingerprint, recordReportQuestions } from './asking-lanes'
|
|
59
67
|
import {
|
|
60
68
|
type CoordinatorRead,
|
|
61
69
|
contextReadingOf,
|
|
@@ -79,14 +87,14 @@ import {
|
|
|
79
87
|
import { type FeedbackItem } from './feedback-item'
|
|
80
88
|
import { type FeedbackItemsRead, readCommentItems, readFeedbackItems, readOwnLogin } from './feedback-read'
|
|
81
89
|
import { answered, deployTargetOf, type GithubRead, readGithub, targetText } from './github-read'
|
|
82
|
-
import { currentPhaseCountsOf
|
|
90
|
+
import { currentPhaseCountsOf } from './heartbeat-progress'
|
|
83
91
|
import { agentScreen, listAgents } from './herdr'
|
|
84
92
|
import { endedByKitUpdate } from './kit-update'
|
|
85
93
|
import { type LaneReadings, laneFillEvents, recordLaneLevels } from './lane-fill'
|
|
86
94
|
import { laneMessage, rememberedStatusLines, statusBlock } from './lane-message'
|
|
87
95
|
import { type LaneModelReading, laneModelEvents, recordLaneModels } from './lane-model'
|
|
88
96
|
import { laneParallelEvents, recordLaneParallels } from './lane-parallel'
|
|
89
|
-
import { boardCheckoutRows, watchedLaneParallel, watchedLaneProgress } from './lane-progress'
|
|
97
|
+
import { boardCheckoutRows, watchedLaneParallel, watchedLaneProgress, watchedPlanPullRequests } from './lane-progress'
|
|
90
98
|
import {
|
|
91
99
|
appointmentEventLine,
|
|
92
100
|
approvalDutyLines,
|
|
@@ -99,15 +107,18 @@ import {
|
|
|
99
107
|
laneFillDutyLines,
|
|
100
108
|
laneModelDutyLines,
|
|
101
109
|
laneReportLine,
|
|
110
|
+
laneStopHeader,
|
|
102
111
|
mergeDutyLines,
|
|
112
|
+
noTaskSignalDutyLines,
|
|
103
113
|
otherPullRequestsLine,
|
|
104
114
|
parallelDutyLines,
|
|
105
115
|
sourceEventLines,
|
|
106
116
|
sourceFailingStatusLine,
|
|
107
117
|
watchEventLine,
|
|
108
118
|
} from './lane-report'
|
|
109
|
-
import { laneReportPath, readLaneReport, reportMtime } from './lane-report-file'
|
|
110
|
-
import {
|
|
119
|
+
import { hasLaneHandover, laneReportPath, readLaneReport, reportMtime } from './lane-report-file'
|
|
120
|
+
import { restNote } from './lane-rest'
|
|
121
|
+
import { branchOf, fetchBaseWhenMoved, fetchPlanHeadsWhenMoved, readMainCheckout } from './lane-sources'
|
|
111
122
|
import {
|
|
112
123
|
advanceBaseline,
|
|
113
124
|
armBaseline,
|
|
@@ -120,6 +131,7 @@ import {
|
|
|
120
131
|
NO_AGENT,
|
|
121
132
|
toSnapshot,
|
|
122
133
|
} from './lane-watch'
|
|
134
|
+
import { mainCheckoutDutyLine } from './main-checkout'
|
|
123
135
|
import { monitorToolAvailable } from './monitor-tool'
|
|
124
136
|
import { parseWatchOptions, type WatchOptions } from './options'
|
|
125
137
|
import {
|
|
@@ -141,15 +153,40 @@ import {
|
|
|
141
153
|
} from './previews'
|
|
142
154
|
import { latestPullRequestOf, type WatchedPullRequest } from './pull-requests'
|
|
143
155
|
import { recordedReadsOf } from './recorded-reads'
|
|
156
|
+
import {
|
|
157
|
+
annotateFirstLine,
|
|
158
|
+
eventReference,
|
|
159
|
+
REFERENCE,
|
|
160
|
+
type Reference,
|
|
161
|
+
referenceHint,
|
|
162
|
+
type SkillCopy,
|
|
163
|
+
skillCopiesFor,
|
|
164
|
+
skillDirectoryFor,
|
|
165
|
+
} from './reference-paths'
|
|
166
|
+
import { heartbeatNotesOf, runningSubagentCountsOf } from './running-subagents'
|
|
144
167
|
import { withSingleReportNotice } from './single-report'
|
|
145
|
-
import {
|
|
168
|
+
import { readOrchestrateSnapshot, skillLineFor } from './skill-change'
|
|
169
|
+
import { runsFromSource, sourceChangeSettled, watchSourceChanges } from './source-change'
|
|
146
170
|
import { sourceDryRun } from './source-dry-run-command'
|
|
147
171
|
import { runSource, SOURCE_LIMITS } from './source-run'
|
|
148
172
|
import { recordWatchedSources } from './source-tickets'
|
|
149
173
|
import { isSourceDue, type SourceRun, type SourceSettings, type SourceState, sourceEvents } from './sources'
|
|
150
174
|
import { streamHeartbeatLine } from './stream-output'
|
|
175
|
+
import {
|
|
176
|
+
formatTaskParallelLine,
|
|
177
|
+
TASK_PARALLEL_EVENT,
|
|
178
|
+
type TaskParallelEvent,
|
|
179
|
+
type TaskParallelFingerprints,
|
|
180
|
+
taskParallelReport,
|
|
181
|
+
} from './task-parallel'
|
|
182
|
+
import { AUTO_ASK_PATHS, type AutoSettings, autoEndedReport, autoVerdict, localDay } from './task-parallel-auto'
|
|
183
|
+
import { holdsDataPathIn } from './task-parallel-data-paths'
|
|
184
|
+
import { readTaskParallelInputs } from './task-parallel-read'
|
|
185
|
+
import { type NoSignalState, type NoTaskSignalEvent, noTaskSignalEvents } from './task-signal-missing'
|
|
186
|
+
import { signalledLanes } from './task-signal-read'
|
|
151
187
|
import { type WatchTexts, watchTexts } from './texts'
|
|
152
188
|
import { updateAnnouncer } from './update-available'
|
|
189
|
+
import { waitingLine } from './waiting-line'
|
|
153
190
|
import {
|
|
154
191
|
coordinatorWatchEvent,
|
|
155
192
|
deployEvents,
|
|
@@ -163,20 +200,14 @@ import {
|
|
|
163
200
|
pullRequestEvents,
|
|
164
201
|
recordCoordinatorLevel,
|
|
165
202
|
recordWatchState,
|
|
203
|
+
UPDATE_AVAILABLE_EVENT,
|
|
166
204
|
WATCH_EVENT,
|
|
167
205
|
type WatchEvent,
|
|
168
206
|
type WatchState,
|
|
169
207
|
withReportBaselines,
|
|
170
208
|
} from './watch-events'
|
|
171
|
-
import {
|
|
172
|
-
|
|
173
|
-
releaseLock,
|
|
174
|
-
stopNoticeOf,
|
|
175
|
-
takeOverLock,
|
|
176
|
-
WATCHER_FILES,
|
|
177
|
-
writePrivate,
|
|
178
|
-
} from './watcher-lock'
|
|
179
|
-
import type { DeployConfig } from './workflow-runs'
|
|
209
|
+
import { ensureWatcherDirectory, releaseLock, stopNoticeOf, takeOverLock, WATCHER_FILES } from './watcher-lock'
|
|
210
|
+
import type { DeployConfig, WorkflowRun } from './workflow-runs'
|
|
180
211
|
|
|
181
212
|
type Files = { state: string; lock: string }
|
|
182
213
|
|
|
@@ -184,6 +215,7 @@ type Files = { state: string; lock: string }
|
|
|
184
215
|
type FeedbackConfig = JackherdConfig['watch']['feedback']
|
|
185
216
|
|
|
186
217
|
type ChecksConfig = JackherdConfig['watch']['checks']
|
|
218
|
+
type ParallelTasksConfig = JackherdConfig['watch']['parallelTasks']
|
|
187
219
|
|
|
188
220
|
/** Where deploy previews are read from; undefined when the project reads none. */
|
|
189
221
|
type PreviewsConfig = JackherdConfig['watch']['previews']
|
|
@@ -203,9 +235,16 @@ type Watch = {
|
|
|
203
235
|
deploy: DeployConfig | undefined
|
|
204
236
|
feedback: FeedbackConfig
|
|
205
237
|
checks: ChecksConfig
|
|
238
|
+
parallelTasks: ParallelTasksConfig
|
|
239
|
+
/** `watch.taskSignalMinutes`: how long a lane works without a task signal before `no task signal`. */
|
|
240
|
+
taskSignalMinutes: number
|
|
241
|
+
/** The switch `watch.parallelTasks.auto` and the paths that always ask, both from the main checkout's config. */
|
|
242
|
+
parallelAuto: AutoSettings
|
|
206
243
|
previews: PreviewsConfig
|
|
207
244
|
sources: SourceSettings[]
|
|
208
245
|
texts: WatchTexts
|
|
246
|
+
/** Where the orchestrate skill is installed, the project's copy first; the events name a reference inside the one found. */
|
|
247
|
+
skillCopies: SkillCopy[]
|
|
209
248
|
/** Runs inside herdr with the herdr adapter, so missing herd panes can be brought back. */
|
|
210
249
|
herdr: boolean
|
|
211
250
|
}
|
|
@@ -230,8 +269,6 @@ type Watching = {
|
|
|
230
269
|
state: WatchState
|
|
231
270
|
baseline: LaneSnapshot
|
|
232
271
|
current: LaneSnapshot
|
|
233
|
-
/** The phase count of each lane at the last heartbeat, so a heartbeat names only a count that moved. */
|
|
234
|
-
heartbeatCounts: Record<string, PhaseCount>
|
|
235
272
|
/** The last `herdr agent list` answer, kept for finding the coordinator by its pane. */
|
|
236
273
|
agents: HerdrAgent[]
|
|
237
274
|
github: GithubRead | null
|
|
@@ -240,8 +277,10 @@ type Watching = {
|
|
|
240
277
|
coordinator: Coordinator
|
|
241
278
|
herdrDown: boolean
|
|
242
279
|
reporting: boolean
|
|
243
|
-
/**
|
|
244
|
-
|
|
280
|
+
/** The fetch of the base branch under way, null when none runs; the next one waits for it, a report after a merge too. */
|
|
281
|
+
baseFetch: Promise<void> | null
|
|
282
|
+
/** A fetch of the plan pull request heads is under way; the next one waits for it. */
|
|
283
|
+
fetchingHeads: boolean
|
|
245
284
|
/** The account `gh` runs as, read once it answers; feedback is read only once it is known. */
|
|
246
285
|
ownLogin: string | null
|
|
247
286
|
/** The last feedback read that answered; its targets and items are recorded with every state. */
|
|
@@ -252,18 +291,24 @@ type Watching = {
|
|
|
252
291
|
previewRead: PreviewRead | null
|
|
253
292
|
/** The runs of the sources due at the last GitHub poll; recorded with every state like the feedback read. */
|
|
254
293
|
sourceRuns: SourceRun[]
|
|
255
|
-
/** Source name → when it was last started in this process; a source without an entry runs at the next GitHub poll. */
|
|
256
|
-
sourceRanAt: Record<string, number>
|
|
257
294
|
/** The lanes' fills of the last read; a lane without an entry or with null has no value. */
|
|
258
295
|
laneReadings: LaneReadings
|
|
259
296
|
/** The lanes' models against their board rows at the last read; recorded with every state like the fills. */
|
|
260
297
|
laneModelReadings: Record<string, LaneModelReading>
|
|
261
298
|
/** The phases of each lane's plan that can run together at the last read; recorded with every state like the models. */
|
|
262
299
|
laneParallelReadings: Record<string, string[]>
|
|
300
|
+
/** The parallel-task fingerprint of each lane at the last heartbeat; persisted like the lane parallels. */
|
|
301
|
+
taskParallels: TaskParallelFingerprints
|
|
302
|
+
/** The last day of `watch.parallelTasks.auto` whose end was reported; persisted like the fingerprints. */
|
|
303
|
+
parallelAutoEnded: string | null
|
|
304
|
+
/** Since when each lane works without a task signal and the phase last reported; persisted like the fingerprints. */
|
|
305
|
+
noTaskSignals: NoSignalState
|
|
263
306
|
/** The approval requests of the last poll; null before the first read, so the stored ids stay. */
|
|
264
307
|
approvalRequests: ApprovalRequestFile[] | null
|
|
265
308
|
/** The board's appointment table at the last poll and the time it was read at; null before the first read and while the board cannot be read. */
|
|
266
309
|
appointments: { table: AppointmentTable; readAt: number } | null
|
|
310
|
+
/** The questions of each watched lane's report at the last poll (`questionsFingerprint`); null before the first read, so the stored ones stay. */
|
|
311
|
+
reportQuestions: Record<string, string> | null
|
|
267
312
|
planViews: PlanViews
|
|
268
313
|
}
|
|
269
314
|
|
|
@@ -282,6 +327,14 @@ const observationOf = (watching: Watching): Observation => ({
|
|
|
282
327
|
github: answered(watching.github),
|
|
283
328
|
})
|
|
284
329
|
|
|
330
|
+
/** The report questions to remember with the lanes whose report was `shown`; before the first read, the stored ones. */
|
|
331
|
+
const reportQuestionsAfter = (
|
|
332
|
+
watching: Watching,
|
|
333
|
+
remembered: Record<string, string>,
|
|
334
|
+
shown: readonly string[],
|
|
335
|
+
): Record<string, string> =>
|
|
336
|
+
watching.reportQuestions === null ? remembered : recordReportQuestions(remembered, watching.reportQuestions, shown)
|
|
337
|
+
|
|
285
338
|
/** Stamped with the time of the last GitHub read, not of the poll: a run created after it is still unseen. */
|
|
286
339
|
const recordedOf = (watch: Watch, watching: Watching): WatchState => {
|
|
287
340
|
const recorded = recordCoordinatorLevel(
|
|
@@ -299,11 +352,15 @@ const recordedOf = (watch: Watch, watching: Watching): WatchState => {
|
|
|
299
352
|
laneLevels: recordLaneLevels(withReads.laneLevels, watching.laneReadings, watching.lanes),
|
|
300
353
|
laneModels: recordLaneModels(withReads.laneModels, watching.laneModelReadings, watching.lanes),
|
|
301
354
|
laneParallels: recordLaneParallels(withReads.laneParallels, watching.laneParallelReadings, watching.lanes),
|
|
355
|
+
taskParallels: watching.taskParallels,
|
|
356
|
+
parallelAutoEnded: watching.parallelAutoEnded,
|
|
357
|
+
noTaskSignals: watching.noTaskSignals,
|
|
302
358
|
approvals: watching.approvalRequests ? recordApprovals(watching.approvalRequests) : withReads.approvals,
|
|
303
359
|
sources: recordWatchedSources(withReads.sources, watching.sourceRuns, watch.sources, watching.githubReadAt),
|
|
304
360
|
appointments: watching.appointments
|
|
305
361
|
? recordAppointments(withReads.appointments, watching.appointments.table, watching.appointments.readAt)
|
|
306
362
|
: withReads.appointments,
|
|
363
|
+
reportQuestions: reportQuestionsAfter(watching, withReads.reportQuestions, []),
|
|
307
364
|
}
|
|
308
365
|
return withReportBaselines(withLevels, watching.lanes, (lane) => reportMtime(reportPathOf(watch, lane)))
|
|
309
366
|
}
|
|
@@ -340,6 +397,18 @@ const screenOf = (lane: string): string => {
|
|
|
340
397
|
const reportPathOf = (watch: Watch, lane: string): string | null =>
|
|
341
398
|
laneReportPath(worktreeOf(watch.root, watch.settings, lane), lane)
|
|
342
399
|
|
|
400
|
+
/** What a resting lane waits on, from its report file (`restNote`); null while it works or when it asks nothing. */
|
|
401
|
+
const restNoteOf = (watch: Watch, lane: string, status: string | undefined): string | null =>
|
|
402
|
+
status !== undefined && IDLE_STATUSES.has(status)
|
|
403
|
+
? restNote(readLaneReport(reportPathOf(watch, lane))?.text ?? null)
|
|
404
|
+
: null
|
|
405
|
+
|
|
406
|
+
/** The questions of each watched lane's report (`questionsFingerprint`), read at every poll. */
|
|
407
|
+
const readReportQuestions = (watch: Watch, lanes: readonly string[]): Record<string, string> =>
|
|
408
|
+
Object.fromEntries(
|
|
409
|
+
lanes.map((lane) => [lane, questionsFingerprint(readLaneReport(reportPathOf(watch, lane))?.text ?? null)]),
|
|
410
|
+
)
|
|
411
|
+
|
|
343
412
|
/** The lines under a stopped lane's header and its report time to remember (`laneMessage`). */
|
|
344
413
|
const stopMessageOf = (watch: Watch, lane: string, status: string, state: WatchState) =>
|
|
345
414
|
laneMessage({
|
|
@@ -401,6 +470,24 @@ const laneBranchesOf = (watch: Watch, lanes: readonly string[]): Record<string,
|
|
|
401
470
|
}),
|
|
402
471
|
)
|
|
403
472
|
|
|
473
|
+
/** ` · read <path>` of `reference` in the skill copy found now; read per line, so a skill installed meanwhile is found. */
|
|
474
|
+
const hintFor = (watch: Watch, reference: Reference | null): string =>
|
|
475
|
+
reference === null ? '' : referenceHint(reference, skillDirectoryFor(watch.skillCopies, existsSync))
|
|
476
|
+
|
|
477
|
+
/** The lines of one event with its reference hint; a deploy names its target when `watch.deploy` can read it. */
|
|
478
|
+
const linesOfEvent = async (watch: Watch, event: WatchEvent, runs: readonly WorkflowRun[]): Promise<string[]> => {
|
|
479
|
+
const hint = hintFor(watch, eventReference(event))
|
|
480
|
+
if (event.kind === WATCH_EVENT.feedback) return annotateFirstLine(feedbackEventLines(event), hint)
|
|
481
|
+
if (event.kind === WATCH_EVENT.source) return annotateFirstLine(sourceEventLines(event), hint)
|
|
482
|
+
if (isAppointmentEvent(event)) return [`${appointmentEventLine(event)}${hint}`]
|
|
483
|
+
const headSha = event.kind === WATCH_EVENT.deploy ? runs.find((run) => run.id === event.runId)?.headSha : undefined
|
|
484
|
+
const target =
|
|
485
|
+
event.kind === WATCH_EVENT.deploy && headSha && watch.deploy
|
|
486
|
+
? await deployTargetOf(watch.root, watch.deploy, event.runId, headSha)
|
|
487
|
+
: null
|
|
488
|
+
return [`${watchEventLine(event, target ? targetText(target) : null)}${hint}`]
|
|
489
|
+
}
|
|
490
|
+
|
|
404
491
|
const eventLinesOf = async (
|
|
405
492
|
watch: Watch,
|
|
406
493
|
events: readonly WatchEvent[],
|
|
@@ -408,26 +495,7 @@ const eventLinesOf = async (
|
|
|
408
495
|
): Promise<string[]> => {
|
|
409
496
|
const runs = answered(github)?.runs ?? []
|
|
410
497
|
const lines: string[] = []
|
|
411
|
-
for (const event of events)
|
|
412
|
-
if (event.kind === WATCH_EVENT.feedback) {
|
|
413
|
-
lines.push(...feedbackEventLines(event))
|
|
414
|
-
continue
|
|
415
|
-
}
|
|
416
|
-
if (event.kind === WATCH_EVENT.source) {
|
|
417
|
-
lines.push(...sourceEventLines(event))
|
|
418
|
-
continue
|
|
419
|
-
}
|
|
420
|
-
if (isAppointmentEvent(event)) {
|
|
421
|
-
lines.push(appointmentEventLine(event))
|
|
422
|
-
continue
|
|
423
|
-
}
|
|
424
|
-
const headSha = event.kind === WATCH_EVENT.deploy ? runs.find((run) => run.id === event.runId)?.headSha : undefined
|
|
425
|
-
const target =
|
|
426
|
-
event.kind === WATCH_EVENT.deploy && headSha && watch.deploy
|
|
427
|
-
? await deployTargetOf(watch.root, watch.deploy, event.runId, headSha)
|
|
428
|
-
: null
|
|
429
|
-
lines.push(watchEventLine(event, target ? targetText(target) : null))
|
|
430
|
-
}
|
|
498
|
+
for (const event of events) lines.push(...(await linesOfEvent(watch, event, runs)))
|
|
431
499
|
return lines
|
|
432
500
|
}
|
|
433
501
|
|
|
@@ -435,22 +503,23 @@ const eventLinesOf = async (
|
|
|
435
503
|
const boardReadOf = async (
|
|
436
504
|
watch: Watch,
|
|
437
505
|
lanes: readonly string[],
|
|
506
|
+
pullRequests: readonly WatchedPullRequest[],
|
|
438
507
|
): Promise<{ progress: Record<string, LaneProgress>; checkoutRows: number }> => {
|
|
439
508
|
const text = await Bun.file(watch.sprintFile).text()
|
|
440
509
|
const sprintLanes = parseSprintLanes(text, watch.settings.planLanguage)
|
|
441
510
|
const watched = sprintLanes.filter((lane) => lanes.includes(lane.name))
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
)
|
|
511
|
+
const planPulls = watchedPlanPullRequests(pullRequests, watch.settings)
|
|
512
|
+
const headReaderOf = readerOncePerRef((ref) => baseReaderOf(watch.root, ref))
|
|
513
|
+
const progressOf = (lane: SprintLane) =>
|
|
514
|
+
watchedLaneProgress(watch.root, watch.settings, watch.sprintFile, lane, planPulls, headReaderOf)
|
|
515
|
+
const entries = await Promise.all(watched.map(async (lane) => [lane.name, await progressOf(lane)] as const))
|
|
448
516
|
const checkoutRows = boardCheckoutRows(watch.root, watch.settings, watch.sprintFile, text)
|
|
449
517
|
return { progress: Object.fromEntries(entries), checkoutRows }
|
|
450
518
|
}
|
|
451
519
|
|
|
452
520
|
/** What the status line of one lane is made of, from the loop's state. */
|
|
453
521
|
const laneReportOf = (
|
|
522
|
+
watch: Watch,
|
|
454
523
|
lane: string,
|
|
455
524
|
watching: Watching,
|
|
456
525
|
state: WatchState,
|
|
@@ -467,6 +536,7 @@ const laneReportOf = (
|
|
|
467
536
|
pullRequest: pullRequest ? { number: pullRequest.number, checks: pullRequest.checks, feedback } : null,
|
|
468
537
|
progress,
|
|
469
538
|
context: watching.laneReadings[lane]?.level ?? null,
|
|
539
|
+
rest: restNoteOf(watch, lane, watching.current[lane]?.status),
|
|
470
540
|
}
|
|
471
541
|
}
|
|
472
542
|
|
|
@@ -474,9 +544,10 @@ const laneReportOf = (
|
|
|
474
544
|
const boardReadOrNote = async (
|
|
475
545
|
watch: Watch,
|
|
476
546
|
lanes: readonly string[],
|
|
547
|
+
pullRequests: readonly WatchedPullRequest[],
|
|
477
548
|
): Promise<{ progress: Record<string, LaneProgress>; checkoutRows: number; notes: string[] }> => {
|
|
478
549
|
try {
|
|
479
|
-
return { ...(await boardReadOf(watch, lanes)), notes: [] }
|
|
550
|
+
return { ...(await boardReadOf(watch, lanes, pullRequests)), notes: [] }
|
|
480
551
|
} catch (error) {
|
|
481
552
|
return {
|
|
482
553
|
progress: {},
|
|
@@ -498,18 +569,24 @@ const statusLinesOf = async (
|
|
|
498
569
|
const github = answered(watching.github)
|
|
499
570
|
const pullRequests = github?.pullRequests ?? []
|
|
500
571
|
const others = otherPullRequestsLine(pullRequests, github?.own ?? [], watch.texts)
|
|
501
|
-
const { progress, checkoutRows, notes } = await boardReadOrNote(watch, watching.lanes)
|
|
572
|
+
const { progress, checkoutRows, notes } = await boardReadOrNote(watch, watching.lanes, pullRequests)
|
|
573
|
+
const closeLaneHint = hintFor(watch, REFERENCE.closeLane)
|
|
502
574
|
const laneLines = watching.lanes.map((lane) =>
|
|
503
|
-
laneReportLine(
|
|
575
|
+
laneReportLine(
|
|
576
|
+
laneReportOf(watch, lane, watching, state, pullRequests, progress[lane] ?? null),
|
|
577
|
+
watch.texts,
|
|
578
|
+
closeLaneHint,
|
|
579
|
+
),
|
|
504
580
|
)
|
|
505
581
|
const outage = state.ghDownSince === null ? [] : [ghUnavailableStatusLine(clock(state.ghDownSince))]
|
|
582
|
+
const waiting = waitingLineOf(watch)
|
|
506
583
|
const board = { term: watch.settings.board.term, checkoutRows }
|
|
507
584
|
const coordinator = coordinatorStatusLine(watching.coordinator.name, watching.coordinator.read, watch.texts, board)
|
|
508
585
|
return {
|
|
509
586
|
laneLines,
|
|
510
587
|
coordinator,
|
|
511
588
|
others: others === null ? [] : [others],
|
|
512
|
-
rest: [...notes, ...outage, ...failingSourceLines(state.sources)],
|
|
589
|
+
rest: [...notes, ...(waiting === null ? [] : [waiting]), ...outage, ...failingSourceLines(state.sources)],
|
|
513
590
|
}
|
|
514
591
|
}
|
|
515
592
|
|
|
@@ -529,7 +606,10 @@ const stoppedLaneSections = (
|
|
|
529
606
|
const status = watching.current[lane]?.status
|
|
530
607
|
if (!status) return { lane, lines: [`=== ${lane}: ${terminalSafe(LANE_GONE)}`], mtime: undefined }
|
|
531
608
|
const message = stopMessageOf(watch, lane, status, next)
|
|
532
|
-
|
|
609
|
+
const handoverHint = hasLaneHandover(worktreeOf(watch.root, watch.settings, lane), lane)
|
|
610
|
+
? hintFor(watch, REFERENCE.laneHandover)
|
|
611
|
+
: null
|
|
612
|
+
return { lane, lines: [laneStopHeader(lane, status, handoverHint), ...message.lines], mtime: message.reportedMtime }
|
|
533
613
|
})
|
|
534
614
|
const shown = sections.flatMap(({ lane, mtime }) => (mtime === undefined ? [] : [[lane, mtime] as const]))
|
|
535
615
|
return {
|
|
@@ -538,6 +618,20 @@ const stoppedLaneSections = (
|
|
|
538
618
|
}
|
|
539
619
|
}
|
|
540
620
|
|
|
621
|
+
/** The duty line while the main checkout lags behind `origin/<base>`, read from the local refs; null when caught up. */
|
|
622
|
+
const mainCheckoutLineOf = (watch: Watch): string | null =>
|
|
623
|
+
mainCheckoutDutyLine(readMainCheckout(watch.root, watch.settings.baseBranch), watch.settings.baseBranch, watch.texts)
|
|
624
|
+
|
|
625
|
+
/** `mainCheckoutLineOf` once a fetch of the base under way has ended when `events` hold a merge, so the merge already counts. */
|
|
626
|
+
const mainCheckoutLineAfter = async (
|
|
627
|
+
watch: Watch,
|
|
628
|
+
watching: Watching,
|
|
629
|
+
events: readonly WatchEvent[],
|
|
630
|
+
): Promise<string | null> => {
|
|
631
|
+
if (events.some((event) => event.kind === WATCH_EVENT.merged)) await watching.baseFetch
|
|
632
|
+
return mainCheckoutLineOf(watch)
|
|
633
|
+
}
|
|
634
|
+
|
|
541
635
|
/**
|
|
542
636
|
* Every line of one return, gathered before any is printed, so the output is never cut halfway by a slow lookup, and
|
|
543
637
|
* the state with what it showed: the report times of the stopped lanes and the status lines. A state without status
|
|
@@ -552,21 +646,26 @@ const reportOf = async (
|
|
|
552
646
|
): Promise<{ lines: string[]; next: WatchState }> => {
|
|
553
647
|
const stops = stoppedLaneSections(watch, watching, stopped, next)
|
|
554
648
|
const eventLines = await eventLinesOf(watch, events, watching.github)
|
|
649
|
+
const mainCheckout = await mainCheckoutLineAfter(watch, watching, events)
|
|
555
650
|
const dutyLines = [
|
|
556
651
|
...blockedDutyLines(stopped, watching.current, watch.texts),
|
|
557
652
|
...mergeDutyLines(events, watching.lanes, watch.texts),
|
|
653
|
+
...(mainCheckout === null ? [] : [mainCheckout]),
|
|
558
654
|
...laneFillDutyLines(events, watch.texts),
|
|
559
655
|
...laneModelDutyLines(events, watch.texts),
|
|
560
656
|
...parallelDutyLines(events, watch.texts),
|
|
657
|
+
...noTaskSignalDutyLines(events, watch.texts),
|
|
561
658
|
...approvalDutyLines(events, watch.texts),
|
|
562
659
|
]
|
|
563
660
|
const status = await statusLinesOf(watch, watching, next)
|
|
564
661
|
const block = statusBlock(status.laneLines, status.coordinator, next.statusLines, status.others)
|
|
662
|
+
const shownReports = stopped.filter((lane) => stops.reportMtimes[lane] !== next.reportMtimes[lane])
|
|
565
663
|
return {
|
|
566
664
|
lines: [...stops.lines, ...eventLines, ...dutyLines, ...block, ...status.rest],
|
|
567
665
|
next: {
|
|
568
666
|
...next,
|
|
569
667
|
reportMtimes: stops.reportMtimes,
|
|
668
|
+
reportQuestions: reportQuestionsAfter(watching, next.reportQuestions, shownReports),
|
|
570
669
|
statusLines: rememberedStatusLines(status.laneLines, status.others),
|
|
571
670
|
},
|
|
572
671
|
}
|
|
@@ -739,12 +838,14 @@ const recordPreviewRead = (watching: Watching, read: PreviewRead | null): WatchE
|
|
|
739
838
|
}
|
|
740
839
|
|
|
741
840
|
/**
|
|
742
|
-
* Runs the sources whose interval has passed
|
|
743
|
-
* run is a result, never a throw, so no
|
|
841
|
+
* Runs the sources whose interval has passed since the start the saved state holds (`ranAt`, so a restart runs none
|
|
842
|
+
* early), all at once, each without a shell and within `SOURCE_LIMITS`; a failed run is a result, never a throw, so no
|
|
843
|
+
* source stops the watcher. The run's start is recorded with the GitHub read's time, which is `now`.
|
|
744
844
|
*/
|
|
745
845
|
const runDueSources = (watch: Watch, watching: Watching, now: number): Promise<SourceRun[]> => {
|
|
746
|
-
const due = watch.sources.filter((source) =>
|
|
747
|
-
|
|
846
|
+
const due = watch.sources.filter((source) =>
|
|
847
|
+
isSourceDue(watching.state.sources[source.name]?.ranAt, source.every, now),
|
|
848
|
+
)
|
|
748
849
|
return Promise.all(due.map((source) => runSource(source, watch.root, SOURCE_LIMITS)))
|
|
749
850
|
}
|
|
750
851
|
|
|
@@ -755,10 +856,18 @@ const recordSourceRuns = (watch: Watch, watching: Watching, runs: SourceRun[]):
|
|
|
755
856
|
|
|
756
857
|
/** Starts a fetch of the base branch in the background when none runs; the loop does not wait for it. */
|
|
757
858
|
const syncBase = (watch: Watch, watching: Watching) => {
|
|
758
|
-
if (watching.
|
|
759
|
-
watching.
|
|
760
|
-
|
|
761
|
-
|
|
859
|
+
if (watching.baseFetch) return
|
|
860
|
+
watching.baseFetch = fetchBaseWhenMoved(watch.root, watch.settings.baseBranch).finally(() => {
|
|
861
|
+
watching.baseFetch = null
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/** Starts a fetch of the moved plan pull request heads in the background when none runs, like `syncBase`. */
|
|
866
|
+
const syncPlanHeads = (watch: Watch, watching: Watching, pullRequests: readonly WatchedPullRequest[]) => {
|
|
867
|
+
if (watching.fetchingHeads) return
|
|
868
|
+
watching.fetchingHeads = true
|
|
869
|
+
void fetchPlanHeadsWhenMoved(watch.root, watchedPlanPullRequests(pullRequests, watch.settings)).finally(() => {
|
|
870
|
+
watching.fetchingHeads = false
|
|
762
871
|
})
|
|
763
872
|
}
|
|
764
873
|
|
|
@@ -777,6 +886,7 @@ const pollGithubAndCoordinator = async (watch: Watch, watching: Watching, now: n
|
|
|
777
886
|
runDueSources(watch, watching, now),
|
|
778
887
|
])
|
|
779
888
|
const pullRequests = answered(github)?.pullRequests ?? null
|
|
889
|
+
syncPlanHeads(watch, watching, pullRequests ?? [])
|
|
780
890
|
const branches = { ...watching.state.laneBranches, ...laneBranches }
|
|
781
891
|
const failureReadings = await readFailures(watch, watching, github, branches)
|
|
782
892
|
const targets = feedbackTargets(pullRequests, branches, watching.state.feedback)
|
|
@@ -813,6 +923,13 @@ const readAppointments = (watch: Watch, now: number): Watching['appointments'] =
|
|
|
813
923
|
return { table: readAppointmentTable(board, watch.settings.planLanguage, new Date(now)), readAt: now }
|
|
814
924
|
}
|
|
815
925
|
|
|
926
|
+
/** 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. */
|
|
927
|
+
const waitingLineOf = (watch: Watch): string | null => {
|
|
928
|
+
return waitingLine(
|
|
929
|
+
waitingItemsOf(readBoardText(watch), watch.settings.planLanguage, readWaitingRecords(watch.root), Date.now()),
|
|
930
|
+
)
|
|
931
|
+
}
|
|
932
|
+
|
|
816
933
|
/** The board's lanes, null when the board cannot be read. */
|
|
817
934
|
const readSprintLanes = (watch: Watch): SprintLane[] | null => {
|
|
818
935
|
const board = readBoardText(watch)
|
|
@@ -891,23 +1008,169 @@ const report = async (
|
|
|
891
1008
|
console.log(
|
|
892
1009
|
(watch.options.stream ? lines : withSingleReportNotice(lines, monitorToolAvailable(process.env))).join('\n'),
|
|
893
1010
|
)
|
|
894
|
-
|
|
1011
|
+
writePrivateFile(watch.files.state, JSON.stringify(shown))
|
|
895
1012
|
watching.reporting = false
|
|
896
1013
|
return shown
|
|
897
1014
|
}
|
|
898
1015
|
|
|
899
1016
|
/**
|
|
900
|
-
* The
|
|
901
|
-
*
|
|
902
|
-
* next
|
|
1017
|
+
* The parallel-task proposals of this heartbeat, one event per open lane whose situation changed since the last
|
|
1018
|
+
* heartbeat and that has a proposal, marked by `autoVerdict` against the switch, and the fingerprints of every lane
|
|
1019
|
+
* read, kept for the next one. Nothing when herdr or the board cannot be read: a proposal is a hint, and the next
|
|
1020
|
+
* heartbeat reads again.
|
|
1021
|
+
*/
|
|
1022
|
+
const taskParallelProposalsOf = (
|
|
1023
|
+
watch: Watch,
|
|
1024
|
+
watching: Watching,
|
|
1025
|
+
now: number,
|
|
1026
|
+
): { events: TaskParallelEvent[]; fingerprints: TaskParallelFingerprints } => {
|
|
1027
|
+
try {
|
|
1028
|
+
const lanes = (readSprintLanes(watch) ?? []).filter((lane) => !lane.closed && watching.lanes.includes(lane.name))
|
|
1029
|
+
const inputs = readTaskParallelInputs(lanes, herdrLaneStatusSource.listAgents(), {
|
|
1030
|
+
root: watch.root,
|
|
1031
|
+
settings: watch.settings,
|
|
1032
|
+
boardFile: watch.sprintFile,
|
|
1033
|
+
thresholdTokens: watch.parallelTasks.maxTokens,
|
|
1034
|
+
now,
|
|
1035
|
+
})
|
|
1036
|
+
const reports = Object.entries(inputs).map(
|
|
1037
|
+
([lane, input]) => [lane, input, taskParallelReport(watching.taskParallels[lane], input)] as const,
|
|
1038
|
+
)
|
|
1039
|
+
const today = localDay(now)
|
|
1040
|
+
return {
|
|
1041
|
+
events: reports.flatMap(([lane, input, { proposal }]) =>
|
|
1042
|
+
proposal
|
|
1043
|
+
? [
|
|
1044
|
+
{
|
|
1045
|
+
kind: TASK_PARALLEL_EVENT,
|
|
1046
|
+
lane,
|
|
1047
|
+
line: formatTaskParallelLine(lane, proposal, autoVerdict(input, proposal, watch.parallelAuto, today)),
|
|
1048
|
+
},
|
|
1049
|
+
]
|
|
1050
|
+
: [],
|
|
1051
|
+
),
|
|
1052
|
+
fingerprints: Object.fromEntries(reports.map(([lane, , { fingerprint }]) => [lane, fingerprint])),
|
|
1053
|
+
}
|
|
1054
|
+
} catch {
|
|
1055
|
+
return { events: [], fingerprints: {} }
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* This heartbeat's parallel-task events: the line that `watch.parallelTasks.auto` ended, once per last day, then the
|
|
1061
|
+
* proposals. Records the fingerprints and the reported end in `watching`.
|
|
1062
|
+
*/
|
|
1063
|
+
const taskParallelEventsOf = (watch: Watch, watching: Watching, now: number): TaskParallelEvent[] => {
|
|
1064
|
+
const { events, fingerprints } = taskParallelProposalsOf(watch, watching, now)
|
|
1065
|
+
const { ended, line } = autoEndedReport(watching.parallelAutoEnded, watch.parallelAuto.auto, localDay(now))
|
|
1066
|
+
watching.taskParallels = { ...watching.taskParallels, ...fingerprints }
|
|
1067
|
+
watching.parallelAutoEnded = ended
|
|
1068
|
+
return line === null ? events : [{ kind: TASK_PARALLEL_EVENT, lane: null, line }, ...events]
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const workingLanes = (watching: Watching): string[] =>
|
|
1072
|
+
watching.lanes.filter((lane) => watching.current[lane]?.status === AGENT_STATUS.working)
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* This heartbeat's `no task signal` events (D112): each watched lane read as working or not, with an open task signal
|
|
1076
|
+
* or not, and its current phase from `progress`; records the clocks in `watching`. Nothing when herdr or the logs
|
|
1077
|
+
* cannot be read: the event is a reminder, and the next heartbeat reads again.
|
|
1078
|
+
*/
|
|
1079
|
+
const noTaskSignalEventsOf = (
|
|
1080
|
+
watch: Watch,
|
|
1081
|
+
watching: Watching,
|
|
1082
|
+
progress: Readonly<Record<string, LaneProgress>>,
|
|
1083
|
+
now: number,
|
|
1084
|
+
): NoTaskSignalEvent[] => {
|
|
1085
|
+
try {
|
|
1086
|
+
const working = new Set(workingLanes(watching))
|
|
1087
|
+
const signalled = signalledLanes(watching.lanes, {
|
|
1088
|
+
root: watch.root,
|
|
1089
|
+
worktreeDirectory: watch.settings.worktreeDirectory,
|
|
1090
|
+
now,
|
|
1091
|
+
})
|
|
1092
|
+
const counts = currentPhaseCountsOf(progress)
|
|
1093
|
+
const readings = Object.fromEntries(
|
|
1094
|
+
watching.lanes.map((lane) => [
|
|
1095
|
+
lane,
|
|
1096
|
+
{ working: working.has(lane), signalled: signalled.has(lane), phaseId: counts[lane]?.id ?? null },
|
|
1097
|
+
]),
|
|
1098
|
+
)
|
|
1099
|
+
const { events, state } = noTaskSignalEvents(watching.noTaskSignals, readings, now, watch.taskSignalMinutes)
|
|
1100
|
+
watching.noTaskSignals = state
|
|
1101
|
+
return events
|
|
1102
|
+
} catch {
|
|
1103
|
+
return []
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/** The progress of the working lanes, with the pull requests of the last GitHub read; nothing while no lane works. */
|
|
1108
|
+
const workingProgressOf = async (watch: Watch, watching: Watching): Promise<Record<string, LaneProgress>> => {
|
|
1109
|
+
const working = workingLanes(watching)
|
|
1110
|
+
if (working.length === 0) return {}
|
|
1111
|
+
const { progress } = await boardReadOrNote(watch, working, answered(watching.github)?.pullRequests ?? [])
|
|
1112
|
+
return progress
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/** The events a heartbeat of a report brings: the parallel-task proposals, then the lanes without a task signal. */
|
|
1116
|
+
const heartbeatEventsOf = async (watch: Watch, watching: Watching, now: number): Promise<WatchEvent[]> => {
|
|
1117
|
+
const progress = await workingProgressOf(watch, watching)
|
|
1118
|
+
return [...taskParallelEventsOf(watch, watching, now), ...noTaskSignalEventsOf(watch, watching, progress, now)]
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* The subagents running in each worktree, from the chain the herd reads for its own view (`readRunningSubagents`). Nothing
|
|
1123
|
+
* when the logs cannot be read: a note is a hint, and the next heartbeat reads again.
|
|
1124
|
+
*/
|
|
1125
|
+
const subagentCountsOf = (watch: Watch, now: number): Record<string, number> => {
|
|
1126
|
+
try {
|
|
1127
|
+
const worktreeDirectory = join(watch.root, watch.settings.worktreeDirectory)
|
|
1128
|
+
const running = readRunningSubagents(
|
|
1129
|
+
subagentSourceOf(watch.root, worktreeDirectory, watch.settings.skillPrefix),
|
|
1130
|
+
now,
|
|
1131
|
+
)
|
|
1132
|
+
return runningSubagentCountsOf(running, worktreeDirectory)
|
|
1133
|
+
} catch {
|
|
1134
|
+
return {}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* The heartbeat of a stream: one line, a working lane with its phase count at every heartbeat and the subagents running
|
|
1140
|
+
* in its worktree. The plans are read only when a lane works; a board that cannot be read leaves the lines without a
|
|
1141
|
+
* count, and the next report names the failure.
|
|
903
1142
|
*/
|
|
904
1143
|
const printStreamHeartbeat = async (watch: Watch, watching: Watching, now: number): Promise<void> => {
|
|
905
|
-
const working =
|
|
906
|
-
const
|
|
1144
|
+
const working = workingLanes(watching)
|
|
1145
|
+
const progress = await workingProgressOf(watch, watching)
|
|
907
1146
|
const counts = currentPhaseCountsOf(progress)
|
|
908
|
-
const notes =
|
|
909
|
-
|
|
910
|
-
|
|
1147
|
+
const notes = heartbeatNotesOf(working, counts, working.length > 0 ? subagentCountsOf(watch, now) : {})
|
|
1148
|
+
const rests = Object.fromEntries(
|
|
1149
|
+
watching.lanes.flatMap((lane) => {
|
|
1150
|
+
const rest = restNoteOf(watch, lane, watching.current[lane]?.status)
|
|
1151
|
+
return rest === null ? [] : [[lane, rest]]
|
|
1152
|
+
}),
|
|
1153
|
+
)
|
|
1154
|
+
const standing = [waitingLineOf(watch), mainCheckoutLineOf(watch)].filter((line) => line !== null)
|
|
1155
|
+
console.log(streamHeartbeatLine(clock(now), watching.lanes, watching.current, notes, rests, standing))
|
|
1156
|
+
const events = [
|
|
1157
|
+
...taskParallelEventsOf(watch, watching, now),
|
|
1158
|
+
...noTaskSignalEventsOf(watch, watching, progress, now),
|
|
1159
|
+
]
|
|
1160
|
+
for (const event of events) console.log(`${watchEventLine(event, null)}${hintFor(watch, eventReference(event))}`)
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* The lanes to report as stopped: those that stopped or left since the last poll, and the working lanes whose report
|
|
1165
|
+
* asks something new (`askingLanes`): a lane waiting on its background subagents ends no turn. Takes the departed
|
|
1166
|
+
* lanes and records the questions read.
|
|
1167
|
+
*/
|
|
1168
|
+
const takeStoppedLanes = (watch: Watch, watching: Watching): string[] => {
|
|
1169
|
+
const fired = [...firedLanes(watching.baseline, watching.current, watching.lanes), ...watching.departed]
|
|
1170
|
+
watching.departed = []
|
|
1171
|
+
watching.reportQuestions = readReportQuestions(watch, watching.lanes)
|
|
1172
|
+
const working = workingLanes(watching).filter((lane) => !fired.includes(lane))
|
|
1173
|
+
return [...fired, ...askingLanes(working, watching.reportQuestions, watching.state.reportQuestions)]
|
|
911
1174
|
}
|
|
912
1175
|
|
|
913
1176
|
/**
|
|
@@ -932,8 +1195,7 @@ const pollOnce = async (watch: Watch, watching: Watching): Promise<boolean> => {
|
|
|
932
1195
|
...approvalEvents(watching.state.approvals, watching.approvalRequests, now),
|
|
933
1196
|
...appointmentEvents(watching.state.appointments, watching.appointments?.table ?? null, now),
|
|
934
1197
|
]
|
|
935
|
-
const stopped =
|
|
936
|
-
watching.departed = []
|
|
1198
|
+
const stopped = takeStoppedLanes(watch, watching)
|
|
937
1199
|
const heartbeat = isHeartbeatDue(watching.quietSince, now, heartbeatMinutes * MINUTE_MS)
|
|
938
1200
|
let next = recordedOf(watch, watching)
|
|
939
1201
|
const reported = stopped.length > 0 || polledEvents.length > 0
|
|
@@ -941,10 +1203,17 @@ const pollOnce = async (watch: Watch, watching: Watching): Promise<boolean> => {
|
|
|
941
1203
|
next = await report(watch, watching, stopped, polledEvents, next)
|
|
942
1204
|
watching.baseline = { ...watching.current }
|
|
943
1205
|
} else if (heartbeat && !stream) {
|
|
944
|
-
|
|
1206
|
+
const proposals = await heartbeatEventsOf(watch, watching, now)
|
|
1207
|
+
next = await report(watch, watching, [], [{ kind: WATCH_EVENT.heartbeat }, ...proposals], {
|
|
1208
|
+
...recordedOf(watch, watching),
|
|
1209
|
+
statusLines: null,
|
|
1210
|
+
})
|
|
945
1211
|
} else {
|
|
946
|
-
if (heartbeat)
|
|
947
|
-
|
|
1212
|
+
if (heartbeat) {
|
|
1213
|
+
await printStreamHeartbeat(watch, watching, now)
|
|
1214
|
+
next = recordedOf(watch, watching)
|
|
1215
|
+
}
|
|
1216
|
+
if (due) writePrivateFile(watch.files.state, JSON.stringify(next))
|
|
948
1217
|
watching.baseline = advanceBaseline(watching.baseline, watching.current)
|
|
949
1218
|
}
|
|
950
1219
|
watching.state = next
|
|
@@ -968,30 +1237,33 @@ const initialWatching = (
|
|
|
968
1237
|
agents: start.agents,
|
|
969
1238
|
github: null,
|
|
970
1239
|
laneBranches: {},
|
|
971
|
-
heartbeatCounts: {},
|
|
972
1240
|
githubReadAt: 0,
|
|
973
1241
|
coordinator: { name: watch.options.coordinator, read: null },
|
|
974
1242
|
herdrDown: false,
|
|
975
1243
|
reporting: false,
|
|
976
|
-
|
|
1244
|
+
baseFetch: null,
|
|
1245
|
+
fetchingHeads: false,
|
|
977
1246
|
ownLogin: null,
|
|
978
1247
|
feedbackRead: null,
|
|
979
1248
|
failureReadings: [],
|
|
980
1249
|
previewRead: null,
|
|
981
1250
|
sourceRuns: [],
|
|
982
|
-
sourceRanAt: {},
|
|
983
1251
|
laneReadings: {},
|
|
984
1252
|
laneModelReadings: {},
|
|
985
1253
|
laneParallelReadings: {},
|
|
1254
|
+
taskParallels: start.state.taskParallels,
|
|
1255
|
+
parallelAutoEnded: start.state.parallelAutoEnded,
|
|
1256
|
+
noTaskSignals: start.state.noTaskSignals,
|
|
986
1257
|
approvalRequests: null,
|
|
987
1258
|
appointments: null,
|
|
1259
|
+
reportQuestions: null,
|
|
988
1260
|
planViews: { failureReported: false },
|
|
989
1261
|
})
|
|
990
1262
|
|
|
991
1263
|
/** Saves the state a stop leaves behind: the one from before an unfinished report or an unread GitHub answer, else the latest. */
|
|
992
1264
|
const saveState = (watch: Watch, watching: Watching) => {
|
|
993
1265
|
const unread = watching.reporting || watching.github === null
|
|
994
|
-
|
|
1266
|
+
writePrivateFile(watch.files.state, JSON.stringify(unread ? watching.state : recordedOf(watch, watching)))
|
|
995
1267
|
}
|
|
996
1268
|
|
|
997
1269
|
const watchLanes = async (watch: Watch) => {
|
|
@@ -1013,14 +1285,25 @@ const watchLanes = async (watch: Watch) => {
|
|
|
1013
1285
|
})
|
|
1014
1286
|
|
|
1015
1287
|
const kit = await startedKit()
|
|
1016
|
-
const skillSnapshot = kit ?
|
|
1288
|
+
const skillSnapshot = kit ? readOrchestrateSnapshot(kit.packageRoot) : null
|
|
1017
1289
|
const skillLine = () => skillLineFor(kit, skillSnapshot, dirname(watch.files.state))
|
|
1018
|
-
const announceUpdate = kit
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1290
|
+
const announceUpdate = kit
|
|
1291
|
+
? updateAnnouncer(homedir(), kit.version, defaultUpdateCheck(), console.log, () =>
|
|
1292
|
+
hintFor(watch, eventReference({ kind: UPDATE_AVAILABLE_EVENT })),
|
|
1293
|
+
)
|
|
1294
|
+
: null
|
|
1295
|
+
const sourceChanges = kit && runsFromSource(kit.packageRoot) ? watchSourceChanges(kit.packageRoot) : null
|
|
1296
|
+
const sourceChanged = () => sourceChangeSettled(sourceChanges?.lastChangeAt() ?? null, Date.now())
|
|
1297
|
+
const effects = { saveState: () => saveState(watch, watching), log: console.log, skillLine, sourceChanged }
|
|
1298
|
+
try {
|
|
1299
|
+
while (await pollOnce(watch, watching)) {
|
|
1300
|
+
await Bun.sleep(POLL_MS)
|
|
1301
|
+
pollLanes(watch, watching)
|
|
1302
|
+
await announceUpdate?.()
|
|
1303
|
+
if (endedByKitUpdate(kit, effects)) return
|
|
1304
|
+
}
|
|
1305
|
+
} finally {
|
|
1306
|
+
sourceChanges?.close()
|
|
1024
1307
|
}
|
|
1025
1308
|
}
|
|
1026
1309
|
|
|
@@ -1029,10 +1312,34 @@ const filesOf = (root: string): Files => {
|
|
|
1029
1312
|
return { state: join(directory, WATCHER_FILES.state), lock: join(directory, WATCHER_FILES.lock) }
|
|
1030
1313
|
}
|
|
1031
1314
|
|
|
1315
|
+
/**
|
|
1316
|
+
* The switch and the paths that always ask from the main checkout's config only, so a lane's worktree can neither switch
|
|
1317
|
+
* `auto` on, shorten the list nor raise the cost limit; off and the kit's paths when that config cannot be read.
|
|
1318
|
+
*/
|
|
1319
|
+
const autoSettingsOf = (root: string, checkoutRoot: string, config: JackherdConfig) => {
|
|
1320
|
+
try {
|
|
1321
|
+
const main = checkoutRoot === root ? config : readConfig([root])
|
|
1322
|
+
return {
|
|
1323
|
+
auto: main.watch.parallelTasks.auto,
|
|
1324
|
+
askPaths: [...AUTO_ASK_PATHS, ...main.guard.alwaysAsk],
|
|
1325
|
+
maxTokens: main.watch.parallelTasks.maxTokens,
|
|
1326
|
+
holdsDataPath: holdsDataPathIn(root),
|
|
1327
|
+
}
|
|
1328
|
+
} catch {
|
|
1329
|
+
return {
|
|
1330
|
+
auto: undefined,
|
|
1331
|
+
askPaths: AUTO_ASK_PATHS,
|
|
1332
|
+
maxTokens: config.watch.parallelTasks.maxTokens,
|
|
1333
|
+
holdsDataPath: holdsDataPathIn(root),
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1032
1338
|
/** The watch as the command line and the config set it up; throws when the config or the sprint is missing. */
|
|
1033
1339
|
const watchOf = (options: WatchOptions): Watch => {
|
|
1034
1340
|
const root = mainCheckout(process.cwd())
|
|
1035
|
-
const
|
|
1341
|
+
const checkoutRoot = worktreeRoot(process.cwd())
|
|
1342
|
+
const { config, notes } = readResolvedConfig([checkoutRoot, root])
|
|
1036
1343
|
for (const note of notes) console.log(note)
|
|
1037
1344
|
const settings = plansSettingsOf(config)
|
|
1038
1345
|
const picked = boardFor(options.boardPath, [root, worktreeRoot(process.cwd())], settings, () =>
|
|
@@ -1048,9 +1355,13 @@ const watchOf = (options: WatchOptions): Watch => {
|
|
|
1048
1355
|
deploy: config.watch.deploy,
|
|
1049
1356
|
feedback: config.watch.feedback,
|
|
1050
1357
|
checks: config.watch.checks,
|
|
1358
|
+
parallelTasks: config.watch.parallelTasks,
|
|
1359
|
+
taskSignalMinutes: config.watch.taskSignalMinutes,
|
|
1360
|
+
parallelAuto: autoSettingsOf(root, checkoutRoot, config),
|
|
1051
1361
|
previews: config.watch.previews,
|
|
1052
1362
|
sources: config.watch.sources,
|
|
1053
1363
|
texts: watchTexts(settings.responseLanguage),
|
|
1364
|
+
skillCopies: skillCopiesFor(root, homedir(), config.skillPrefix, DEFAULT_SKILL_PREFIX),
|
|
1054
1365
|
herdr: insideHerdr(settings.adapter, process.env),
|
|
1055
1366
|
}
|
|
1056
1367
|
}
|