jackherd 1.9.0 → 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.
Files changed (204) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/INSTALL.md +1 -1
  3. package/README.md +50 -32
  4. package/dist/{chunk-MCP4XJQH.js → chunk-5MQBCUQ5.js} +9 -4
  5. package/dist/config.d.ts +39 -4
  6. package/dist/config.js +67 -26
  7. package/dist/plan-format.d.ts +45 -6
  8. package/dist/plan-format.js +46 -6
  9. package/docs/workflow-dark.svg +67 -63
  10. package/docs/workflow.svg +67 -63
  11. package/herdr-plugin/herdr-plugin.toml +1 -1
  12. package/install.sh +1 -1
  13. package/kit/hooks/ask-rules.json +15 -0
  14. package/kit/hooks/hooks/ask-hint/decision.ts +82 -14
  15. package/kit/hooks/hooks/ask-hint/index.ts +15 -19
  16. package/kit/hooks/hooks/ask-hint/settings.ts +3 -18
  17. package/kit/hooks/hooks/git-guard/decision.ts +50 -102
  18. package/kit/hooks/hooks/merge-guard/always-ask.ts +63 -0
  19. package/kit/hooks/hooks/merge-guard/{plan-approval-settings.ts → board-settings.ts} +21 -67
  20. package/kit/hooks/hooks/merge-guard/check-free.ts +20 -0
  21. package/kit/hooks/hooks/merge-guard/decision.ts +127 -187
  22. package/kit/hooks/hooks/merge-guard/index.ts +6 -3
  23. package/kit/hooks/hooks/merge-guard/lane-plan.ts +79 -0
  24. package/kit/hooks/hooks/merge-guard/run.ts +59 -54
  25. package/kit/hooks/hooks/merge-guard/settings.ts +13 -1
  26. package/kit/hooks/hooks/merge-guard/standing-approval.ts +142 -0
  27. package/kit/hooks/hooks/open-question/index.ts +26 -0
  28. package/kit/hooks/hooks/open-question/record.ts +135 -0
  29. package/kit/hooks/hooks/open-question/run.ts +52 -0
  30. package/kit/hooks/hooks/open-question/store.ts +39 -0
  31. package/kit/hooks/hooks/sent-file/store.ts +3 -12
  32. package/kit/hooks/hooks/shared/config.ts +7 -0
  33. package/kit/hooks/hooks/shared/dialog-answers.ts +9 -9
  34. package/kit/hooks/hooks/shared/files.ts +7 -0
  35. package/kit/hooks/hooks/shared/lane.ts +1 -1
  36. package/kit/hooks/hooks/shared/newest-verdict.ts +10 -0
  37. package/kit/hooks/hooks/shared/private-directory.ts +56 -9
  38. package/kit/hooks/hooks/shared/program-position.ts +2 -75
  39. package/kit/hooks/hooks/shared/shell-aliases.ts +1 -71
  40. package/kit/hooks/hooks/{ask-hint → shared}/transcript.ts +37 -6
  41. package/kit/hooks/hooks/{merge-guard → shared}/xargs-program.ts +10 -8
  42. package/kit/hooks/hooks/task-hint/decision.ts +49 -0
  43. package/kit/hooks/hooks/task-hint/index.ts +79 -0
  44. package/kit/hooks/hooks/task-hint/run.ts +62 -0
  45. package/kit/hooks/hooks/task-hint/store.ts +51 -0
  46. package/kit/hooks/hooks.json +13 -5
  47. package/kit/review/agents/skill-review.md +34 -0
  48. package/kit/review/skills/pre-commit/SKILL.md +1 -1
  49. package/kit/workflow/skills/follow/SKILL.md +1 -8
  50. package/kit/workflow/skills/implement/SKILL.md +8 -6
  51. package/kit/workflow/skills/orchestrate/SKILL.md +210 -433
  52. package/kit/workflow/skills/orchestrate/handover/decision.ts +43 -8
  53. package/kit/workflow/skills/orchestrate/handover/index.ts +32 -4
  54. package/kit/workflow/skills/orchestrate/handover/standing-approval.ts +84 -0
  55. package/kit/workflow/skills/orchestrate/references/acceptance.md +15 -0
  56. package/kit/workflow/skills/orchestrate/references/close-lane.md +17 -0
  57. package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +43 -17
  58. package/kit/workflow/skills/orchestrate/references/draft-review.md +19 -0
  59. package/kit/workflow/skills/orchestrate/references/events.md +86 -0
  60. package/kit/workflow/skills/orchestrate/references/herd.md +6 -6
  61. package/kit/workflow/skills/orchestrate/references/lane-handover.md +18 -0
  62. package/kit/workflow/skills/orchestrate/references/lane-session.md +25 -0
  63. package/kit/workflow/skills/orchestrate/references/lane-setup.md +37 -0
  64. package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -10
  65. package/kit/workflow/skills/orchestrate/references/sensitive-input.md +5 -7
  66. package/kit/workflow/skills/plan/SKILL.md +6 -5
  67. package/kit/workflow/skills/plan/references/template-English.md +4 -4
  68. package/kit/workflow/skills/plan/references/template-German.md +4 -4
  69. package/package.json +3 -1
  70. package/recipes/README.md +2 -1
  71. package/recipes/sources.md +1 -1
  72. package/schemas/jackherd.schema.json +59 -5
  73. package/src/check/agent-text-reviewers.ts +23 -0
  74. package/src/check/command.ts +2 -0
  75. package/src/check/plan-structure-check.ts +49 -2
  76. package/src/check/plan.ts +8 -1
  77. package/src/check/schema.ts +9 -0
  78. package/src/check/skill-budget-defaults.ts +55 -0
  79. package/src/check/skill-budgets.ts +185 -0
  80. package/src/cli.ts +1 -0
  81. package/src/config/auto-window.ts +8 -0
  82. package/src/config/link-kinds.ts +7 -1
  83. package/src/config/schema.ts +58 -24
  84. package/src/lane/close-board.ts +106 -0
  85. package/src/lane/close-check.ts +183 -0
  86. package/src/lane/close-command-line.ts +11 -0
  87. package/src/lane/close-command.ts +217 -0
  88. package/src/lane/close-options.ts +43 -0
  89. package/src/lane/close-survey.ts +83 -0
  90. package/src/lane/close.ts +141 -0
  91. package/src/lane/command.ts +13 -0
  92. package/src/lane-prompt/build.ts +74 -0
  93. package/src/lane-prompt/command.ts +87 -0
  94. package/src/lane-prompt/input.ts +116 -0
  95. package/src/lane-prompt/options.ts +68 -0
  96. package/src/lane-prompt/texts.ts +101 -0
  97. package/src/plan-format/clock.ts +8 -0
  98. package/src/plan-format/dependencies.ts +7 -4
  99. package/src/plan-format/index.ts +1 -0
  100. package/src/plan-format/short-names.ts +71 -0
  101. package/src/plan-format/waiting.ts +7 -2
  102. package/src/plans/appointment-rows.ts +59 -0
  103. package/src/plans/ask-coordinator.ts +77 -0
  104. package/src/plans/ask-target.ts +25 -0
  105. package/src/plans/child-stop.ts +51 -0
  106. package/src/plans/closable-records.ts +34 -0
  107. package/src/plans/command.ts +3 -2
  108. package/src/plans/fetch.ts +26 -3
  109. package/src/plans/git.ts +19 -5
  110. package/src/plans/herd-claim.ts +173 -0
  111. package/src/plans/herd-memory.ts +36 -25
  112. package/src/plans/lane-plans.ts +44 -13
  113. package/src/plans/lane-summary.ts +253 -45
  114. package/src/plans/layout-phase.ts +29 -7
  115. package/src/plans/layout-summary.ts +89 -18
  116. package/src/plans/layout.ts +262 -68
  117. package/src/plans/link-chips.ts +1 -0
  118. package/src/plans/live.ts +107 -20
  119. package/src/plans/phase-ids.ts +8 -0
  120. package/src/plans/phase-source.ts +15 -0
  121. package/src/plans/plan-cache.ts +28 -0
  122. package/src/plans/plan-heads.ts +54 -0
  123. package/src/plans/pull-requests.ts +17 -2
  124. package/src/plans/question-records.ts +48 -0
  125. package/src/plans/questions-read.ts +13 -0
  126. package/src/plans/read-cache.ts +8 -0
  127. package/src/plans/record-id.ts +4 -0
  128. package/src/plans/restart-decision.ts +13 -4
  129. package/src/plans/selection.ts +98 -18
  130. package/src/plans/sent-files-read.ts +6 -15
  131. package/src/plans/sent-files.ts +7 -14
  132. package/src/plans/settings.ts +2 -1
  133. package/src/plans/signalled-phase.ts +42 -0
  134. package/src/plans/signals-read.ts +12 -4
  135. package/src/plans/snapshot.ts +85 -62
  136. package/src/plans/sprint-export.ts +11 -11
  137. package/src/plans/sprint-page.ts +5 -5
  138. package/src/plans/subagent-phases.ts +95 -0
  139. package/src/plans/subagent-tasks.ts +51 -0
  140. package/src/plans/subagents-read.ts +66 -17
  141. package/src/plans/subagents.ts +64 -6
  142. package/src/plans/supervisor.ts +14 -4
  143. package/src/plans/symbols.ts +3 -0
  144. package/src/plans/task-name.ts +34 -0
  145. package/src/plans/texts.ts +23 -6
  146. package/src/plans/view.ts +17 -9
  147. package/src/plans/waiting-click.ts +192 -37
  148. package/src/plans/waiting-records.ts +39 -0
  149. package/src/plans/waiting-window.ts +8 -0
  150. package/src/preview/command.ts +25 -3
  151. package/src/shared/private-location.ts +89 -0
  152. package/src/shared/stat-cache.ts +48 -0
  153. package/src/sync/kit-source.ts +29 -10
  154. package/src/sync/kit.ts +2 -0
  155. package/src/sync/manifest.ts +2 -0
  156. package/src/sync/plan-ask-rules.ts +94 -0
  157. package/src/sync/plan-hooks.ts +2 -0
  158. package/src/sync/render.ts +3 -3
  159. package/src/sync/settings-hooks.ts +4 -1
  160. package/src/sync/sync-plan.ts +1 -0
  161. package/src/sync/sync.ts +24 -3
  162. package/src/task-signals/claude-read.ts +27 -10
  163. package/src/terminal/text.ts +8 -3
  164. package/src/version/skill-diff.ts +46 -0
  165. package/src/vocabulary/index.ts +16 -6
  166. package/src/waiting/command.ts +22 -7
  167. package/src/watch/appointments.ts +1 -2
  168. package/src/watch/asking-lanes.ts +40 -0
  169. package/src/watch/base-tip.ts +11 -0
  170. package/src/watch/command.ts +330 -105
  171. package/src/watch/heartbeat-progress.ts +6 -18
  172. package/src/watch/kit-update.ts +28 -9
  173. package/src/watch/lane-name.ts +6 -0
  174. package/src/watch/lane-progress.ts +40 -8
  175. package/src/watch/lane-report-file.ts +20 -5
  176. package/src/watch/lane-report.ts +32 -4
  177. package/src/watch/lane-sources.ts +31 -3
  178. package/src/watch/main-checkout.ts +34 -0
  179. package/src/watch/plan-badges-read.ts +13 -7
  180. package/src/watch/pull-requests.ts +6 -2
  181. package/src/watch/reference-paths.ts +87 -0
  182. package/src/watch/running-subagents.ts +43 -0
  183. package/src/watch/skill-change.ts +124 -32
  184. package/src/watch/source-change.ts +48 -0
  185. package/src/watch/sources.ts +6 -3
  186. package/src/watch/stream-output.ts +4 -3
  187. package/src/watch/task-parallel-auto.ts +208 -0
  188. package/src/watch/task-parallel-data-paths.ts +46 -0
  189. package/src/watch/task-parallel-input.ts +5 -15
  190. package/src/watch/task-parallel-read.ts +3 -1
  191. package/src/watch/task-parallel.ts +36 -7
  192. package/src/watch/task-signal-missing.ts +69 -0
  193. package/src/watch/task-signal-read.ts +29 -0
  194. package/src/watch/texts.ts +10 -0
  195. package/src/watch/update-available.ts +6 -3
  196. package/src/watch/watch-events.ts +23 -0
  197. package/src/watch/watcher-lock.ts +25 -29
  198. package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +0 -23
  199. package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +0 -196
  200. package/kit/hooks/hooks/merge-guard/plan-approval.ts +0 -168
  201. package/kit/hooks/hooks/merge-guard/plan-growth.ts +0 -40
  202. package/kit/hooks/hooks/merge-guard/plan-pointers.ts +0 -194
  203. package/kit/hooks/hooks/shared/command-text-runners.ts +0 -71
  204. package/kit/hooks/hooks/shared/plan-open-phases.ts +0 -114
@@ -1,7 +1,7 @@
1
1
  import { phaseLabel } from '../plan-format'
2
2
  import { fit, paint, spread, visibleLength } from '../terminal/text'
3
3
  import { deeper, type Frame, joinFrames, nest, rowFrame, textFrame } from './frame'
4
- import type { ActivePhase, LaneSummary } from './lane-summary'
4
+ import type { ActivePhase, LaneSummary, PlacedPhase } from './lane-summary'
5
5
  import {
6
6
  activeArrow,
7
7
  activeTitle,
@@ -14,12 +14,14 @@ import {
14
14
  phaseChildren,
15
15
  phaseCountCell,
16
16
  plain,
17
+ subagentLine,
17
18
  TASK_INDENT,
18
19
  taskFrame,
20
+ taskTitle,
19
21
  } from './layout-phase'
20
22
  import { type PhaseModel, phaseModelKey } from './phase-models'
21
23
  import { phaseRanges } from './phase-ranges'
22
- import { itemKey, phaseKey } from './selection'
24
+ import { finishedKey, itemKey, nextKey, phaseKey } from './selection'
23
25
  import { PHASE_COUNT } from './symbols'
24
26
  import { type HerdTexts, TEXTS } from './texts'
25
27
 
@@ -62,6 +64,46 @@ const activeRow = (active: ActivePhase, current: boolean, context: SummaryContex
62
64
  return spread(head, candidates.find((right) => titleRoom(right) >= MIN_TITLE_WIDTH) ?? count, width)
63
65
  }
64
66
 
67
+ /** What an active task reads as on one row: `Review gate` for a box of the review gate, else its short name or text. */
68
+ const taskText = (item: ActivePhase['phase']['items'][number], labels: HerdTexts): string =>
69
+ item.gate ? labels.reviewGate : taskTitle(item.text)
70
+
71
+ /** The texts of the tasks agents report on the phase, then the descriptions of the subagents that name no phase as `Subagent: …`. */
72
+ const reportedTexts = (
73
+ { phase, tasks }: ActivePhase,
74
+ subagentTasks: readonly string[],
75
+ labels: HerdTexts,
76
+ ): string[] => [
77
+ ...tasks
78
+ .filter(({ guessed }) => !guessed)
79
+ .flatMap(({ index }) => {
80
+ const item = phase.items[index]
81
+ return item ? [taskText(item, labels)] : []
82
+ }),
83
+ ...subagentTasks.map((description) => `${TEXTS.subagent} ${taskTitle(description)}`),
84
+ ]
85
+
86
+ /**
87
+ * `▶ Name +1` for a folded lane, behind `indent`: the first task the phase is on with the arrow of the open lane, `+n`
88
+ * for the others in parallel at the end; no ticks, the phase row shows them; the name cut to the pane's edge. A task
89
+ * an agent reports comes first, the descriptions of the `subagentTasks` after it. Null while neither exists: a task
90
+ * the herd only guesses from words is no sign that the lane works.
91
+ */
92
+ export const foldedTaskLine = (
93
+ active: ActivePhase,
94
+ subagentTasks: readonly string[],
95
+ labels: HerdTexts,
96
+ indent: string,
97
+ width: number,
98
+ ): string | null => {
99
+ const [first, ...others] = reportedTexts(active, subagentTasks, labels)
100
+ if (first === undefined) return null
101
+ const lead = `${activeArrow(false)} `
102
+ const tail = others.length > 0 ? ` +${others.length}` : ''
103
+ const room = Math.max(width - visibleLength(indent) - visibleLength(lead) - visibleLength(tail), 1)
104
+ return `${indent}${lead}${activeTitle(fit(first, room), false)}${tail}`
105
+ }
106
+
65
107
  /**
66
108
  * One row per active task under a closed running phase, each with its own arrow: the title bold and the agent dim
67
109
  * after it where agents reported it, `▷` and a plain title where the herd guesses. Always on one line whatever the
@@ -72,7 +114,7 @@ const taskLinesFrame = (context: SummaryContext, phaseRowKey: string, { phase, t
72
114
  tasks.flatMap((task) => {
73
115
  const item = phase.items[task.index]
74
116
  if (!item) return []
75
- const text = item.gate ? context.labels.reviewGate : plain(item.text)
117
+ const text = taskText(item, context.labels)
76
118
  const line = spread(
77
119
  `${TASK_INDENT}${activeArrow(task.guessed)} ${activeTitle(text, task.guessed)}`,
78
120
  agentCell(task.agent),
@@ -117,30 +159,59 @@ const nextLineText = (summary: LaneSummary, labels: HerdTexts): string => {
117
159
  return text ? `${paint.dim(labels.next)} ${text}` : ''
118
160
  }
119
161
 
120
- /** ` · not merged` once for the line when every finished phase waits for its merge, ` · P1–P2 not merged` when some do. */
121
- const unmergedNote = ({ finished }: LaneSummary, labels: HerdTexts): string => {
122
- const unmerged = finished.filter(({ unmerged }) => unmerged).map(({ id }) => id)
123
- if (unmerged.length === 0) return ''
124
- const which = unmerged.length === finished.length ? '' : `${phaseRanges(unmerged)} `
125
- return ` · ${paint.yellow(`${which}${labels.notMerged}`)}`
126
- }
127
-
128
- /** `✓ P1–P7, P10 · not merged` on one line: one ✓, the finished phases as ranges. */
162
+ /** `done P1–P7, P10` on one line: the word `done` in green, the finished phases as ranges. */
129
163
  const finishedText = (summary: LaneSummary, labels: HerdTexts): string => {
130
164
  const phases = phaseRanges(summary.finished.map(({ id }) => id))
131
- return phases ? `${paint.green(PHASE_COUNT.allTicked)} ${phases}${unmergedNote(summary, labels)}` : ''
165
+ return phases ? `${paint.green(labels.done)} ${phases}` : ''
132
166
  }
133
167
 
134
- /** One line of text cut at the pane's edge; no line when the text is empty. */
135
- const lineFrame = (text: string, width: number): Frame => textFrame(text ? [fit(text, width)] : [])
168
+ /** A phase that does not run as a row, which opens to its tasks like a running one; closed, it has no task line below it. */
169
+ const restingFrame = (context: SummaryContext, { phase, stepIndex }: PlacedPhase): Frame =>
170
+ activeFrame(context, { phase, stepIndex, minutesAgo: null, tasks: [] })
171
+
172
+ /**
173
+ * One line of text cut at the pane's edge, a row under `key`; no line when the text is empty. Open, it continues with
174
+ * a row per phase it names and the `unwritten` lines, each phase opening to its tasks.
175
+ */
176
+ const foldFrame = (
177
+ context: SummaryContext,
178
+ key: string,
179
+ text: string,
180
+ { phases, unwritten }: { phases: readonly PlacedPhase[]; unwritten: string[] },
181
+ ): Frame => {
182
+ if (!text) return textFrame([])
183
+ const row = rowFrame(key, [fit(text, context.width)], [], context)
184
+ if (!context.state.expanded.has(key)) return row
185
+ const inner = deeper(context)
186
+ const children = [
187
+ ...phases.map((placed) => restingFrame(inner, placed)),
188
+ ...unwritten.map((line) => textFrame([fit(line, inner.width)])),
189
+ ]
190
+ return joinFrames([row, nest(children)])
191
+ }
136
192
 
137
193
  /** What an open lane shows above its phases, drawn at the width of its branches: the session, its pull requests and its link chips. */
138
194
  export type LaneHead = { session: Frame; pullRequests: Frame; chips: Frame }
139
195
 
196
+ /** The `next` and `done` rows of an open lane. */
197
+ const restingFrames = (context: SummaryContext, summary: LaneSummary): Frame[] => {
198
+ const laneName = context.progress.lane.name
199
+ return [
200
+ foldFrame(context, nextKey(laneName), nextLineText(summary, context.labels), {
201
+ phases: summary.waitingPhases,
202
+ unwritten: summary.unwritten.map(unwrittenText),
203
+ }),
204
+ foldFrame(context, finishedKey(laneName), finishedText(summary, context.labels), {
205
+ phases: summary.finishedPhases,
206
+ unwritten: [],
207
+ }),
208
+ ]
209
+ }
210
+
140
211
  /**
141
212
  * The children of an open lane, one branch each, told apart by their marks rather than by a label: the session
142
213
  * (`⎇`), the pull requests (`↗`), the link chips, then one branch per running phase (its tasks `▶`/`▷` under it), the
143
- * `now:` lines, `next` with the open phases that wait, and `✓` with the finished ones; a branch without lines is left out.
214
+ * `now:` and `Subagent:` lines, `next` with the open phases that wait, and `done` with the finished ones; a branch without lines is left out.
144
215
  */
145
216
  export const summaryChildren = (context: LaneContext, summary: LaneSummary, head: LaneHead): Frame[] => {
146
217
  const labels = TEXTS.herd
@@ -152,7 +223,7 @@ export const summaryChildren = (context: LaneContext, summary: LaneSummary, head
152
223
  head.chips,
153
224
  ...summary.active.map((active) => activeFrame(inner, active)),
154
225
  ...summary.unplanned.map((task) => textFrame([nowLine(task, width)])),
155
- lineFrame(nextLineText(summary, labels), width),
156
- lineFrame(finishedText(summary, labels), width),
226
+ ...summary.subagentTasks.map((description) => textFrame([subagentLine(description, width)])),
227
+ ...restingFrames(inner, summary),
157
228
  ]
158
229
  }