jackherd 1.9.0 → 1.11.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 (228) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/INSTALL.md +1 -1
  3. package/README.md +65 -32
  4. package/dist/{chunk-MCP4XJQH.js → chunk-RMMXD4G5.js} +18 -6
  5. package/dist/config.d.ts +99 -4
  6. package/dist/config.js +105 -29
  7. package/dist/plan-format.d.ts +51 -6
  8. package/dist/plan-format.js +49 -7
  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 +57 -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/rules/rules/kit/pull-requests.md +2 -0
  50. package/kit/workflow/skills/follow/SKILL.md +3 -10
  51. package/kit/workflow/skills/implement/SKILL.md +8 -6
  52. package/kit/workflow/skills/orchestrate/SKILL.md +218 -433
  53. package/kit/workflow/skills/orchestrate/handover/decision.ts +43 -8
  54. package/kit/workflow/skills/orchestrate/handover/index.ts +32 -4
  55. package/kit/workflow/skills/orchestrate/handover/standing-approval.ts +84 -0
  56. package/kit/workflow/skills/orchestrate/references/acceptance.md +15 -0
  57. package/kit/workflow/skills/orchestrate/references/close-lane.md +17 -0
  58. package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +43 -17
  59. package/kit/workflow/skills/orchestrate/references/draft-review.md +19 -0
  60. package/kit/workflow/skills/orchestrate/references/events.md +86 -0
  61. package/kit/workflow/skills/orchestrate/references/herd.md +6 -6
  62. package/kit/workflow/skills/orchestrate/references/lane-handover.md +18 -0
  63. package/kit/workflow/skills/orchestrate/references/lane-session.md +25 -0
  64. package/kit/workflow/skills/orchestrate/references/lane-setup.md +37 -0
  65. package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -10
  66. package/kit/workflow/skills/orchestrate/references/sensitive-input.md +5 -7
  67. package/kit/workflow/skills/plan/SKILL.md +6 -5
  68. package/kit/workflow/skills/plan/references/template-English.md +4 -4
  69. package/kit/workflow/skills/plan/references/template-German.md +4 -4
  70. package/package.json +3 -1
  71. package/recipes/README.md +2 -1
  72. package/recipes/sources.md +5 -1
  73. package/schemas/jackherd.schema.json +139 -5
  74. package/src/check/agent-text-reviewers.ts +23 -0
  75. package/src/check/command.ts +2 -0
  76. package/src/check/plan-structure-check.ts +49 -2
  77. package/src/check/plan.ts +8 -1
  78. package/src/check/schema.ts +9 -0
  79. package/src/check/skill-budget-defaults.ts +55 -0
  80. package/src/check/skill-budgets.ts +185 -0
  81. package/src/cli.ts +2 -0
  82. package/src/config/auto-window.ts +8 -0
  83. package/src/config/link-kinds.ts +7 -1
  84. package/src/config/schema.ts +82 -25
  85. package/src/config/tones.ts +27 -0
  86. package/src/lane/close-board.ts +106 -0
  87. package/src/lane/close-check.ts +183 -0
  88. package/src/lane/close-command-line.ts +11 -0
  89. package/src/lane/close-command.ts +217 -0
  90. package/src/lane/close-options.ts +43 -0
  91. package/src/lane/close-survey.ts +83 -0
  92. package/src/lane/close.ts +141 -0
  93. package/src/lane/command.ts +13 -0
  94. package/src/lane-prompt/additions.ts +14 -0
  95. package/src/lane-prompt/build.ts +74 -0
  96. package/src/lane-prompt/command.ts +93 -0
  97. package/src/lane-prompt/input.ts +157 -0
  98. package/src/lane-prompt/options.ts +70 -0
  99. package/src/lane-prompt/texts.ts +101 -0
  100. package/src/mermaid/command.ts +36 -0
  101. package/src/mermaid/problems.ts +48 -0
  102. package/src/plan-format/clock.ts +8 -0
  103. package/src/plan-format/dependencies.ts +9 -6
  104. package/src/plan-format/index.ts +1 -0
  105. package/src/plan-format/short-names.ts +71 -0
  106. package/src/plan-format/waiting.ts +22 -4
  107. package/src/plans/appointment-rows.ts +83 -0
  108. package/src/plans/ask-coordinator.ts +111 -0
  109. package/src/plans/ask-target.ts +36 -0
  110. package/src/plans/board-close.ts +85 -0
  111. package/src/plans/child-stop.ts +51 -0
  112. package/src/plans/closable-records.ts +34 -0
  113. package/src/plans/command.ts +3 -2
  114. package/src/plans/fetch.ts +26 -3
  115. package/src/plans/frame.ts +5 -12
  116. package/src/plans/git.ts +54 -5
  117. package/src/plans/groomed.ts +27 -0
  118. package/src/plans/herd-claim.ts +173 -0
  119. package/src/plans/herd-memory.ts +36 -25
  120. package/src/plans/lane-plans.ts +44 -13
  121. package/src/plans/lane-summary.ts +253 -45
  122. package/src/plans/layout-phase.ts +36 -11
  123. package/src/plans/layout-summary.ts +89 -18
  124. package/src/plans/layout.ts +502 -98
  125. package/src/plans/link-chips.ts +23 -4
  126. package/src/plans/live.ts +178 -23
  127. package/src/plans/next-rows.ts +44 -0
  128. package/src/plans/phase-ids.ts +8 -0
  129. package/src/plans/phase-source.ts +15 -0
  130. package/src/plans/plan-cache.ts +28 -0
  131. package/src/plans/plan-heads.ts +54 -0
  132. package/src/plans/planned.ts +22 -0
  133. package/src/plans/pull-requests.ts +17 -2
  134. package/src/plans/question-records.ts +48 -0
  135. package/src/plans/questions-read.ts +13 -0
  136. package/src/plans/read-cache.ts +8 -0
  137. package/src/plans/record-id.ts +4 -0
  138. package/src/plans/restart-decision.ts +13 -4
  139. package/src/plans/row-actions.ts +52 -0
  140. package/src/plans/row-focus.ts +148 -0
  141. package/src/plans/row-targets.ts +7 -0
  142. package/src/plans/selection.ts +111 -18
  143. package/src/plans/sent-files-read.ts +6 -15
  144. package/src/plans/sent-files.ts +7 -14
  145. package/src/plans/settings.ts +12 -1
  146. package/src/plans/signalled-phase.ts +42 -0
  147. package/src/plans/signals-read.ts +12 -4
  148. package/src/plans/snapshot.ts +162 -81
  149. package/src/plans/source-asked.ts +19 -0
  150. package/src/plans/source-prefix.ts +17 -0
  151. package/src/plans/source-rows.ts +188 -0
  152. package/src/plans/sprint-export.ts +11 -11
  153. package/src/plans/sprint-page.ts +5 -5
  154. package/src/plans/subagent-phases.ts +95 -0
  155. package/src/plans/subagent-tasks.ts +51 -0
  156. package/src/plans/subagents-read.ts +66 -17
  157. package/src/plans/subagents.ts +64 -6
  158. package/src/plans/supervisor.ts +14 -4
  159. package/src/plans/symbols.ts +3 -0
  160. package/src/plans/task-name.ts +34 -0
  161. package/src/plans/texts.ts +48 -6
  162. package/src/plans/tones.ts +30 -0
  163. package/src/plans/view.ts +25 -11
  164. package/src/plans/waiting-click.ts +295 -35
  165. package/src/plans/waiting-records.ts +39 -0
  166. package/src/plans/waiting-window.ts +8 -0
  167. package/src/preview/command.ts +25 -3
  168. package/src/shared/private-location.ts +89 -0
  169. package/src/shared/stat-cache.ts +48 -0
  170. package/src/sync/kit-source.ts +29 -10
  171. package/src/sync/kit.ts +2 -0
  172. package/src/sync/manifest.ts +2 -0
  173. package/src/sync/plan-ask-rules.ts +94 -0
  174. package/src/sync/plan-hooks.ts +2 -0
  175. package/src/sync/render.ts +3 -3
  176. package/src/sync/settings-hooks.ts +4 -1
  177. package/src/sync/sync-plan.ts +1 -0
  178. package/src/sync/sync.ts +24 -3
  179. package/src/task-signals/claude-read.ts +27 -10
  180. package/src/terminal/text.ts +16 -3
  181. package/src/terminal/types.ts +4 -0
  182. package/src/version/skill-diff.ts +46 -0
  183. package/src/vocabulary/index.ts +26 -6
  184. package/src/waiting/command.ts +22 -7
  185. package/src/watch/appointments.ts +1 -2
  186. package/src/watch/asking-lanes.ts +40 -0
  187. package/src/watch/base-tip.ts +11 -0
  188. package/src/watch/command.ts +330 -105
  189. package/src/watch/heartbeat-progress.ts +6 -18
  190. package/src/watch/kit-update.ts +28 -9
  191. package/src/watch/lane-name.ts +6 -0
  192. package/src/watch/lane-progress.ts +40 -8
  193. package/src/watch/lane-report-file.ts +20 -5
  194. package/src/watch/lane-report.ts +32 -4
  195. package/src/watch/lane-sources.ts +31 -3
  196. package/src/watch/main-checkout.ts +34 -0
  197. package/src/watch/plan-badges-read.ts +22 -9
  198. package/src/watch/plan-badges.ts +10 -2
  199. package/src/watch/pull-requests.ts +6 -2
  200. package/src/watch/reference-paths.ts +87 -0
  201. package/src/watch/report-questions.ts +4 -3
  202. package/src/watch/running-subagents.ts +43 -0
  203. package/src/watch/skill-change.ts +124 -32
  204. package/src/watch/source-change.ts +48 -0
  205. package/src/watch/source-entries.ts +55 -0
  206. package/src/watch/source-fields.ts +1 -0
  207. package/src/watch/source-statuses.ts +60 -0
  208. package/src/watch/source-tickets.ts +21 -1
  209. package/src/watch/sources.ts +47 -4
  210. package/src/watch/stream-output.ts +4 -3
  211. package/src/watch/task-parallel-auto.ts +208 -0
  212. package/src/watch/task-parallel-data-paths.ts +46 -0
  213. package/src/watch/task-parallel-input.ts +5 -15
  214. package/src/watch/task-parallel-read.ts +3 -1
  215. package/src/watch/task-parallel.ts +36 -7
  216. package/src/watch/task-signal-missing.ts +69 -0
  217. package/src/watch/task-signal-read.ts +29 -0
  218. package/src/watch/texts.ts +10 -0
  219. package/src/watch/update-available.ts +6 -3
  220. package/src/watch/watch-events.ts +23 -0
  221. package/src/watch/watcher-lock.ts +25 -29
  222. package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +0 -23
  223. package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +0 -196
  224. package/kit/hooks/hooks/merge-guard/plan-approval.ts +0 -168
  225. package/kit/hooks/hooks/merge-guard/plan-growth.ts +0 -40
  226. package/kit/hooks/hooks/merge-guard/plan-pointers.ts +0 -194
  227. package/kit/hooks/hooks/shared/command-text-runners.ts +0 -71
  228. package/kit/hooks/hooks/shared/plan-open-phases.ts +0 -114
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PLAN_LANGUAGE,
3
3
  RESPONSE_ADDRESS
4
- } from "./chunk-MCP4XJQH.js";
4
+ } from "./chunk-RMMXD4G5.js";
5
5
 
6
6
  // src/config/json-schema.ts
7
7
  import { z as z3 } from "zod";
@@ -129,6 +129,7 @@ var publicContent = z.strictObject({
129
129
  /** The agent that judges such a change; a global install names it with its prefix. */
130
130
  reviewer: nonEmpty.default("public-content")
131
131
  });
132
+ var skillBudgets = z.record(glob, z.number().int().positive());
132
133
  var checkConfigSchema = z.strictObject({
133
134
  /**
134
135
  * Run once over the changed files, which are appended as arguments. Separate from `commands.lint`,
@@ -141,6 +142,11 @@ var checkConfigSchema = z.strictObject({
141
142
  buildArtifacts: z.array(glob).default([...DEFAULT_BUILD_ARTIFACT_GLOBS]),
142
143
  reviewerRoutes: z.array(reviewerRoute).default([]),
143
144
  publicContent: publicContent.optional(),
145
+ /**
146
+ * Word budgets for the files an agent loads again and again. An entry replaces the kit's budget for the same
147
+ * path and sets one for the project's own skills; a file with none is measured against the kit's default.
148
+ */
149
+ skillBudgets: skillBudgets.optional(),
144
150
  /** Also run tests in directories that hold test files but belong to no package. */
145
151
  looseTests: z.boolean().default(false)
146
152
  });
@@ -158,7 +164,8 @@ var SOURCE_FIELD = {
158
164
  url: "url",
159
165
  body: "body",
160
166
  author: "author",
161
- status: "status"
167
+ status: "status",
168
+ count: "count"
162
169
  };
163
170
  var CONTENT_FIELDS = [SOURCE_FIELD.title, SOURCE_FIELD.author, SOURCE_FIELD.body, SOURCE_FIELD.url];
164
171
 
@@ -182,11 +189,23 @@ var isSourcePath = (path) => pathSteps(path) !== null;
182
189
  var CLEAN_REVIEW_TEXT = "## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.";
183
190
  var BUN_FAILED_TEST_PATTERN = "^\\(fail\\) (.+?)(?: \\[[\\d.]+m?s\\])?$";
184
191
 
192
+ // src/plan-format/clock.ts
193
+ var DAY_MS = 864e5;
194
+
195
+ // src/config/auto-window.ts
196
+ var MAX_AUTO_WINDOW_DAYS = 7;
197
+ var windowDays = (since, until) => Math.round((Date.parse(`${until}T00:00:00Z`) - Date.parse(`${since}T00:00:00Z`)) / DAY_MS);
198
+
185
199
  // src/config/link-kinds.ts
200
+ var ARTIFACT_LINK_KIND = {
201
+ pattern: "^https://claude\\.ai/(code/)?artifact/",
202
+ label: "Artifact",
203
+ icon: "\u2726"
204
+ };
186
205
  var DEFAULT_LINK_KINDS = [
187
206
  { pattern: "^https://github\\.com/[^/]+/[^/]+/(pull|issues)/\\d+", label: "GitHub", icon: "\u2442" },
188
207
  { pattern: "^https://(www\\.)?figma\\.com/", label: "Figma", icon: "\u25C8" },
189
- { pattern: "^https://claude\\.ai/(code/)?artifact/", label: "Artifact", icon: "\u2726" }
208
+ ARTIFACT_LINK_KIND
190
209
  ];
191
210
  var isRegularExpression = (source) => {
192
211
  try {
@@ -256,6 +275,25 @@ import { isAbsolute } from "path";
256
275
  var segmentsOf = (path) => path.split(/[\\/]/).filter((segment) => segment !== "" && segment !== ".");
257
276
  var isRepositoryDirectory = (value) => !isAbsolute(value) && segmentsOf(value).length > 0 && !segmentsOf(value).includes("..");
258
277
 
278
+ // src/config/tones.ts
279
+ var TONE = {
280
+ amber: "amber",
281
+ teal: "teal",
282
+ grey: "grey",
283
+ red: "red",
284
+ green: "green",
285
+ blue: "blue",
286
+ magenta: "magenta"
287
+ };
288
+ var TONE_NAMES = Object.values(TONE);
289
+ var TONE_ROLE = { waiting: "waiting", new: "new", running: "running", resting: "resting" };
290
+ var DEFAULT_TONES = {
291
+ [TONE_ROLE.waiting]: TONE.amber,
292
+ [TONE_ROLE.new]: TONE.amber,
293
+ [TONE_ROLE.running]: TONE.teal,
294
+ [TONE_ROLE.resting]: TONE.grey
295
+ };
296
+
259
297
  // src/config/schema.ts
260
298
  var TERMINAL_ADAPTER = { none: "none", herdr: "herdr" };
261
299
  var MODEL_ROLE = {
@@ -320,7 +358,17 @@ var parallelTasksSchema = z2.strictObject({
320
358
  * agent times the agents to add. Above it the watcher does not propose parallel tasks. The baseline is the context
321
359
  * the first answer of a lane's session carried.
322
360
  */
323
- maxTokens: z2.number().int().positive().default(DEFAULT_PARALLEL_TASKS_MAX_TOKENS)
361
+ maxTokens: z2.number().int().positive().default(DEFAULT_PARALLEL_TASKS_MAX_TOKENS),
362
+ /**
363
+ * The days, both included, on which a proposal that keeps every limit is marked `auto` and the coordinator starts it
364
+ * without asking: subagents beside running work in a lane, at most two extra agents, no path that always asks.
365
+ * `since` is the day it was switched on, `until` at most seven days later; outside the window it is off. Unset by
366
+ * default, so every proposal asks.
367
+ */
368
+ auto: z2.strictObject({ since: z2.iso.date(), until: z2.iso.date() }).refine(({ since, until }) => until >= since, "until must not be before since").refine(
369
+ ({ since, until }) => windowDays(since, until) <= MAX_AUTO_WINDOW_DAYS,
370
+ `until must be at most ${MAX_AUTO_WINDOW_DAYS} days after since`
371
+ ).optional()
324
372
  });
325
373
  var checksSchema = z2.strictObject({
326
374
  /**
@@ -359,11 +407,14 @@ var sourceMapSchema = z2.object({
359
407
  [SOURCE_FIELD.body]: sourcePath.optional(),
360
408
  [SOURCE_FIELD.author]: sourcePath.optional(),
361
409
  /** A ticket source's status, shown on its badge in the herd; also a field `reportOn` can name. */
362
- [SOURCE_FIELD.status]: sourcePath.optional()
410
+ [SOURCE_FIELD.status]: sourcePath.optional(),
411
+ /** How often the entry happened (an error count), shown as `113×` beside the entry in the herd's `Sources` block. */
412
+ [SOURCE_FIELD.count]: sourcePath.optional()
363
413
  }).catchall(sourcePath).refine((map) => Object.keys(map).every((field) => SOURCE_FIELD_NAME.test(field)), {
364
414
  message: "a map field is a letter, then letters, digits and `_`"
365
415
  });
366
416
  var canChange = ({ map, reportOn }) => map.updatedAt !== void 0 || CONTENT_FIELDS.some((field) => map[field] !== void 0) || reportOn !== void 0;
417
+ var toneName = z2.enum(TONE_NAMES);
367
418
  var sourceSchema = z2.strictObject({
368
419
  /** Printed in every report of the source and the key of its saved state. */
369
420
  name: z2.string().regex(/^[A-Za-z0-9][\w.-]*$/, "letters, digits, `_`, `.` and `-`"),
@@ -378,6 +429,8 @@ var sourceSchema = z2.strictObject({
378
429
  ignoreAuthors: z2.array(z2.string().min(1)).default([]),
379
430
  /** The most new or changed entries one report shows of this source; the rest is one line `+<n> more`. */
380
431
  reportLimit: z2.number().int().min(1).default(5),
432
+ /** Shown before a row of "Waiting for you" that came from this source (`Error log · Checkout: …`); without it the source's name. */
433
+ label: z2.string().min(1).max(40).regex(/^[^\r\n]+$/, "one line").optional(),
381
434
  /** The lane the source's reports name. */
382
435
  lane: z2.string().min(1).optional(),
383
436
  /**
@@ -385,6 +438,8 @@ var sourceSchema = z2.strictObject({
385
438
  * the lane above or the lane whose board row or plan names its id.
386
439
  */
387
440
  ticket: z2.boolean().optional(),
441
+ /** The tone of the source's mark in the herd's `Sources` block while its newest report is new; without it the tone of the role `new`. */
442
+ tone: toneName.optional(),
388
443
  /**
389
444
  * The mapped fields whose change reports, with the old and the new value, in place of the content of `title`,
390
445
  * `author`, `body` and `url`; without it a change of that content reports.
@@ -434,13 +489,13 @@ var mergeSchema = z2.strictObject({
434
489
  */
435
490
  withoutChecks: z2.array(checkFreePattern).default([])
436
491
  });
437
- var guardSchema = z2.strictObject({
492
+ var guardFields = z2.strictObject({
438
493
  /**
439
- * The commands the merge guard hook asks the user about before they run, also behind options before the verb, a chain,
440
- * a wrapper or `sh -c`. `gh api` asks only for a write to a merge, a file, a dispatch, a rerun or a ref. An empty list switches the
441
- * guard off.
494
+ * The commands the merge guard hook asks the user about before they run, where they stand at a program position, also
495
+ * behind options before the verb, a chain, a wrapper or `sh -c`. `gh api`, when listed, asks only for a write to a
496
+ * merge, a file, a dispatch, a rerun or a ref. An empty list switches the guard off.
442
497
  */
443
- commands: z2.array(guardedCommand).default(["gh pr merge", "gh workflow run", "gh run rerun", "gh api"]),
498
+ commands: z2.array(guardedCommand).default(["gh pr merge", "gh workflow run"]),
444
499
  selfMerge: z2.strictObject({
445
500
  /** Whether `/orchestrate` offers "merge it yourself after green checks" in the pull request question; off also stops the hook honouring such an answer. */
446
501
  offer: z2.boolean().default(true),
@@ -451,27 +506,44 @@ var guardSchema = z2.strictObject({
451
506
  /** How long the approval gate hook holds a lane's call for the coordinator's dialog before it denies it; below the hook's 10 minutes. */
452
507
  waitMinutes: z2.number().int().min(1).max(9).default(8)
453
508
  }).prefault({}),
454
- planApproval: z2.strictObject({
455
- /**
456
- * Paths that always ask, on top of the ones jackherd brings (`.github/**`, `**\/migrations/**`, `**\/.env*`,
457
- * `.claude/**`, `.jackherd/**`, `bunfig.toml`, `**\/CLAUDE.md`, `**\/AGENTS.md`, `.agents/**`, `.mcp.json`,
458
- * `.husky/**`, `lefthook.yml`, the lockfiles of bun, npm, pnpm and yarn, `**\/.npmrc`, `**\/CODEOWNERS`, every
459
- * package.json, and the CHANGELOG.md of every package without `"private": true`):
460
- * a pull request that changes one is never merged on a plan approval alone. They add to that list, never replace
461
- * it; an invalid entry turns plan approval off, so every merge asks.
462
- */
463
- exceptPaths: z2.array(alwaysAskPattern).default([]),
464
- /**
465
- * Checks that must be among the head commit's and green before a plan approval lets a merge pass, by their names
466
- * on GitHub. Unset, the merge needs at least one check and every one green; set, also each of these. Either way
467
- * GitHub must be able to merge it and the head must contain the base branch.
468
- */
469
- requiredChecks: z2.array(checkName).optional()
470
- }).prefault({})
509
+ /**
510
+ * Paths that always ask, on top of the ones jackherd brings (`.github/**`, `**\/migrations/**`, `**\/.env*`,
511
+ * `.claude/**`, `.jackherd/**`, `bunfig.toml`, `**\/CLAUDE.md`, `**\/AGENTS.md`, `.agents/**`, `.mcp.json`,
512
+ * `.husky/**`, `lefthook.yml`, the lockfiles of bun, npm, pnpm and yarn, `**\/.npmrc`, `**\/CODEOWNERS`): a
513
+ * `merge.withoutChecks` pattern never opens them, and the watcher never starts parallel work on them by itself. They
514
+ * add to that list, never replace it. A standing approval covers them; only workflows, migrations and env files ask.
515
+ */
516
+ alwaysAsk: z2.array(alwaysAskPattern).default([]),
517
+ /**
518
+ * Checks that must be among the head commit's and green before a standing approval lets a merge pass, by their names
519
+ * on GitHub. Unset, the merge needs at least one check and every one green; set, also each of these. Either way
520
+ * GitHub must be able to merge it.
521
+ */
522
+ requiredChecks: z2.array(checkName).optional(),
523
+ /** Read until 2.0.0 as `alwaysAsk` (`exceptPaths`) and `requiredChecks`; the plan approval it was for is gone. */
524
+ planApproval: z2.strictObject({ exceptPaths: z2.array(alwaysAskPattern).optional(), requiredChecks: z2.array(checkName).optional() }).optional().meta({ deprecated: true })
471
525
  });
526
+ var guardWithoutPlanApproval = ({ planApproval, ...guard }) => {
527
+ const requiredChecks = guard.requiredChecks ?? planApproval?.requiredChecks;
528
+ return {
529
+ ...guard,
530
+ alwaysAsk: [...guard.alwaysAsk, ...planApproval?.exceptPaths ?? []],
531
+ ...requiredChecks === void 0 ? {} : { requiredChecks }
532
+ };
533
+ };
534
+ var guardSchema = guardFields.transform(guardWithoutPlanApproval);
472
535
  var hintsSchema = z2.strictObject({
473
536
  /** Whether the `ask-hint` hook points out a last message that ends in a question while the turn used no `AskUserQuestion`; a lane session is never hinted. */
474
- askUserQuestion: z2.boolean().default(true)
537
+ askUserQuestion: z2.boolean().default(true),
538
+ /** Whether the `task-hint` hook points a lane that edits a file without an open task signal to `jh task start`, once per task. */
539
+ taskSignal: z2.boolean().default(true)
540
+ });
541
+ var DEFAULT_TASK_SIGNAL_MINUTES = 10;
542
+ var tonesSchema = z2.strictObject({
543
+ waiting: toneName.default(DEFAULT_TONES.waiting),
544
+ new: toneName.default(DEFAULT_TONES.new),
545
+ running: toneName.default(DEFAULT_TONES.running),
546
+ resting: toneName.default(DEFAULT_TONES.resting)
475
547
  });
476
548
  var contextSchema = z2.strictObject({
477
549
  /** Tokens in a lane's context from which its fill shows yellow and `/implement` offers a new chat. */
@@ -542,12 +614,15 @@ var jackherdConfigSchema = z2.strictObject({
542
614
  icon: z2.string().min(1)
543
615
  })
544
616
  ).default(DEFAULT_LINK_KINDS.map((kind) => ({ ...kind }))),
545
- context: contextSchema.prefault({})
617
+ context: contextSchema.prefault({}),
618
+ /** The colour of each role in the herd, by name: what waits for you, what is new, what runs, what rests. */
619
+ tones: tonesSchema.prefault({})
546
620
  }).prefault({}),
547
621
  /**
548
622
  * Read by `jh watch`; without `deploy` it reports no deploy runs. `feedback` is on unless set to `false`:
549
623
  * it reports new feedback on the lanes' pull requests except from the account the watcher runs as and `ignore`.
550
624
  * `parallelTasks.maxTokens` caps the cost of the extra agents in a proposal of parallel tasks at the heartbeat.
625
+ * `taskSignalMinutes` is how long a lane works without a task signal before it reports `no task signal`, once per phase.
551
626
  * `checks` reads the failed tests of a red lane pull request (`failedTestPattern`), also with `feedback` off.
552
627
  * Without `previews` it reads no deploy preview; with it, one `preview` per new head commit. Each of `sources` is a
553
628
  * command whose JSON output reports `source` for a new or changed entry.
@@ -557,6 +632,7 @@ var jackherdConfigSchema = z2.strictObject({
557
632
  feedback: z2.union([z2.literal(false), feedbackSchema]).prefault({}),
558
633
  checks: checksSchema.prefault({}),
559
634
  parallelTasks: parallelTasksSchema.prefault({}),
635
+ taskSignalMinutes: z2.number().int().positive().default(DEFAULT_TASK_SIGNAL_MINUTES),
560
636
  previews: previewsSchema.optional(),
561
637
  sources: z2.array(sourceSchema).refine(hasUniqueNames, "source names must be unique").default([])
562
638
  }).prefault({}),
@@ -23,14 +23,20 @@ declare const ADDRESS_FORM: Record<PlanLanguage, Record<ResponseAddress, string>
23
23
  */
24
24
  declare const SELF_MERGE_LABEL: Record<PlanLanguage, string>;
25
25
  /**
26
- * The options of the question that approves a plan for all its lanes (``Plan `<path>` ``) and of the one that revokes
27
- * that approval. The merge guard hook lifts its question for a plain merge of such a lane only when the newest answer
28
- * to a question naming the plan is exactly `approve`, so the labels are fixed per language and not a config value.
26
+ * The standing approval: the option that approves (`approve`) and the word between the plan and the end in its
27
+ * question (``Plan `<path>` <until> <YYYY-MM-DD HH:MM>``). The merge guard hook lifts its question for a plain merge of
28
+ * that plan's lanes only when the newest answer to such a question is exactly `approve`, so both are fixed per
29
+ * language and not a config value.
29
30
  */
30
- declare const PLAN_APPROVAL_LABELS: Record<PlanLanguage, {
31
+ declare const STANDING_APPROVAL_TEXTS: Record<PlanLanguage, {
31
32
  approve: string;
32
- revoke: string;
33
+ until: string;
33
34
  }>;
35
+ /**
36
+ * The words a lane prompt opens with, followed by the lane's name. The ask hint hook and the project's lane exception
37
+ * recognise a lane by them, so they are fixed per language and not a config value.
38
+ */
39
+ declare const LANE_PROMPT_OPENING: Record<PlanLanguage, string>;
34
40
  /**
35
41
  * The words of an approval: the two option labels of the coordinator's question about a lane's call, which the
36
42
  * approval gate hook matches exactly and which follow the response language, and what the hook tells the lane when it
@@ -58,6 +64,12 @@ declare const APPOINTMENT_VOCABULARY: Record<PlanLanguage, {
58
64
  /** Further header names that count as the status column. */
59
65
  statusAlso: readonly string[];
60
66
  open: readonly string[];
67
+ /** The lower-case word an action starts with when it is the weekly board grooming, which the herd's header counts from. */
68
+ grooming: string;
69
+ }>;
70
+ /** The heading of the board section of what the coordinator does next, a numbered list of at most three lines. */
71
+ declare const NEXT_VOCABULARY: Record<PlanLanguage, {
72
+ heading: string;
61
73
  }>;
62
74
  /**
63
75
  * The board section of what waits on the user's answer (a sent preview, an open question, a report to accept): its
@@ -500,6 +512,39 @@ declare const activeItem: (phase: Pick<PhaseProgress, "items">) => ChecklistItem
500
512
  /** The sidebar title of a lane, e.g. `P4 · 7/12` or `P4 · 12/12` while ticked and not merged; null when nothing of the lane could be counted. */
501
513
  declare const shortTitle: (progress: LaneProgress) => string | null;
502
514
 
515
+ /** Characters a task's short name may have, so the herd shows it whole. */
516
+ declare const SHORT_NAME_LIMIT = 40;
517
+ /** Characters a phase title may have; the title is the phase's name. */
518
+ declare const PHASE_TITLE_LIMIT = 50;
519
+ declare const SHORT_NAME_PROBLEM: {
520
+ readonly missing: "missing";
521
+ readonly long: "long";
522
+ };
523
+ /** What is wrong with an open task's short name: none at all, or one of `length` characters over the limit. */
524
+ type ShortNameProblem = {
525
+ problem: typeof SHORT_NAME_PROBLEM.missing;
526
+ } | {
527
+ problem: typeof SHORT_NAME_PROBLEM.long;
528
+ length: number;
529
+ };
530
+ /** An open task (1-based line) of phase `phase` whose short name is missing or too long. */
531
+ type ShortNameFinding = {
532
+ line: number;
533
+ phase: string;
534
+ } & ShortNameProblem;
535
+ /** A phase heading whose title has `length` characters, over the limit. */
536
+ type LongPhaseTitle = PhaseHeading & {
537
+ length: number;
538
+ };
539
+ /**
540
+ * The open tasks a change wrote (a line not in `previousLines`) that do not start with `**Name**:` or whose name is
541
+ * longer than `SHORT_NAME_LIMIT`. Only tasks of a phase section before its review gate count: a gate item keeps its
542
+ * own form.
543
+ */
544
+ declare const shortNameFindings: (markdown: string, language: PlanLanguage, previousLines: ReadonlySet<string>) => ShortNameFinding[];
545
+ /** The phase headings a change wrote whose title is longer than `PHASE_TITLE_LIMIT`. */
546
+ declare const longPhaseTitles: (markdown: string, language: PlanLanguage, previousLines: ReadonlySet<string>) => LongPhaseTitle[];
547
+
503
548
  /** What a sprint document says around its lanes table: its goal and its history. */
504
549
  type SprintPlanRow = {
505
550
  cells: string[];
@@ -529,4 +574,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
529
574
  */
530
575
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
531
576
 
532
- export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANGUAGE_NAME, type LaneProgress, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PLAN_APPROVAL_LABELS, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
577
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANE_PROMPT_OPENING, LANGUAGE_NAME, type LaneProgress, type LongPhaseTitle, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, NEXT_VOCABULARY, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -2,19 +2,21 @@ import {
2
2
  ADDRESS_FORM,
3
3
  APPOINTMENT_VOCABULARY,
4
4
  APPROVAL_TEXTS,
5
+ LANE_PROMPT_OPENING,
5
6
  LANGUAGE_NAME,
6
- PLAN_APPROVAL_LABELS,
7
+ NEXT_VOCABULARY,
7
8
  PLAN_LANGUAGE,
8
9
  PLAN_STATUS,
9
10
  PLAN_VOCABULARY,
10
11
  RESPONSE_ADDRESS,
11
12
  SELF_MERGE_LABEL,
13
+ STANDING_APPROVAL_TEXTS,
12
14
  WAITING_VOCABULARY,
13
15
  classifyStatus,
14
16
  isPlanLanguage,
15
17
  phaseHeading,
16
18
  sectionHeading
17
- } from "./chunk-MCP4XJQH.js";
19
+ } from "./chunk-RMMXD4G5.js";
18
20
 
19
21
  // src/plan-format/lanes.ts
20
22
  import { posix } from "path";
@@ -498,7 +500,7 @@ var dependsOf = (cell, language) => {
498
500
  return { after, external };
499
501
  };
500
502
  var pathOf = (entry) => {
501
- if (/\s/.test(entry)) return null;
503
+ if (/\s/.test(entry) || entry.includes("[]")) return null;
502
504
  const glob = entry.search(GLOB_CHARACTER);
503
505
  const literal = glob === -1 ? entry : entry.slice(0, Math.max(entry.lastIndexOf("/", glob), 0));
504
506
  const path = literal.replace(/^\.\//, "").replace(/\/+$/, "");
@@ -573,12 +575,13 @@ var phasesWithoutDependencyRow = (markdown, language) => {
573
575
  };
574
576
  var longestChain = (chains) => chains.reduce((longest, chain) => chain.length > longest.length ? chain : longest, []);
575
577
  var mergeSets = (sets) => sets.some((set) => set === null) ? null : sets.flatMap((set) => set ?? []);
576
- var writeSetOf = (row, byId, chain) => {
578
+ var writeSetWithin = (row, byId, chain) => {
577
579
  if (chain.includes(row.id)) return null;
578
580
  const targets = row.writesLike.map((id) => byId.get(id));
579
- const like = mergeSets(targets.map((target) => target ? writeSetOf(target, byId, [...chain, row.id]) : null));
581
+ const like = mergeSets(targets.map((target) => target ? writeSetWithin(target, byId, [...chain, row.id]) : null));
580
582
  return like === null ? null : [...row.writes, ...like];
581
583
  };
584
+ var writeSetOf = (row, byId) => writeSetWithin(row, byId, []);
582
585
  var overlaps = (left, right) => left === "" || right === "" || left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
583
586
  var sharesEntry = (first, second) => {
584
587
  const entries = new Set(second.map((entry) => entry.trim().toLowerCase()));
@@ -587,7 +590,7 @@ var sharesEntry = (first, second) => {
587
590
  var sharesMarker = (first, second) => sharesEntry(first.serialMarkers, second.serialMarkers) || sharesEntry(first.releases, second.releases);
588
591
  var collides = (first, second) => sharesMarker(first, second) || first.writes.some((path) => second.writes.some((other) => overlaps(path, other)));
589
592
  var candidateOf = (row, byId) => {
590
- const writes = writeSetOf(row, byId, []);
593
+ const writes = writeSetOf(row, byId);
591
594
  if (writes === null || writes.length === 0) return [];
592
595
  const releases = [.../* @__PURE__ */ new Set([...row.releases, ...releasedByFiles(writes)])];
593
596
  return [{ id: row.id, writes, serialMarkers: row.serialMarkers, releases }];
@@ -824,6 +827,38 @@ var boardRowsWithoutPhase = (markdown, language, previousLines) => changedOpenLa
824
827
  ({ phases }) => phaseIdsOfCell(phases, language).length === 0
825
828
  );
826
829
 
830
+ // src/plan-format/short-names.ts
831
+ var SHORT_NAME_LIMIT = 40;
832
+ var PHASE_TITLE_LIMIT = 50;
833
+ var SHORT_NAME_PROBLEM = { missing: "missing", long: "long" };
834
+ var SHORT_NAME = /^\s*[-*]\s+\[ \]\s+\*\*(.+?)\*\*:/;
835
+ var characterCount = (text) => [...text].length;
836
+ var shortNameProblem = (line) => {
837
+ const name = line.match(SHORT_NAME)?.[1];
838
+ if (name === void 0) return { problem: SHORT_NAME_PROBLEM.missing };
839
+ const length = characterCount(name);
840
+ return length > SHORT_NAME_LIMIT ? { problem: SHORT_NAME_PROBLEM.long, length } : null;
841
+ };
842
+ var shortNameFindings = (markdown, language, previousLines) => {
843
+ const headings = new Map(phaseHeadings(markdown, language).map((heading) => [heading.line, heading.id]));
844
+ const found = [];
845
+ let phase = null;
846
+ for (const [index, line] of markdown.split("\n").entries()) {
847
+ const heading = headings.get(index + 1);
848
+ if (heading !== void 0) phase = heading;
849
+ else if (endsPhase(line) || isReviewGateLine(line)) phase = null;
850
+ else if (phase !== null && isOpenCheckboxLine(line) && !previousLines.has(line)) {
851
+ const problem = shortNameProblem(line);
852
+ if (problem) found.push({ line: index + 1, phase, ...problem });
853
+ }
854
+ }
855
+ return found;
856
+ };
857
+ var longPhaseTitles = (markdown, language, previousLines) => {
858
+ const lines = markdown.split("\n");
859
+ return phaseHeadings(markdown, language).filter(({ line }) => !previousLines.has(lines[line - 1] ?? "")).map((heading) => ({ ...heading, length: characterCount(heading.title) })).filter(({ length }) => length > PHASE_TITLE_LIMIT);
860
+ };
861
+
827
862
  // src/plan-format/sprint-document.ts
828
863
  var PHASE_ID_IN_CELL = /^(\d+[a-z]?)\b/;
829
864
  var paragraphs = (lines) => lines.join("\n").split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
@@ -866,16 +901,21 @@ export {
866
901
  ADDRESS_FORM,
867
902
  APPOINTMENT_VOCABULARY,
868
903
  APPROVAL_TEXTS,
904
+ LANE_PROMPT_OPENING,
869
905
  LANGUAGE_NAME,
906
+ NEXT_VOCABULARY,
870
907
  PHASE_CELL_LIMIT,
871
908
  PHASE_ID_PREFIX,
872
- PLAN_APPROVAL_LABELS,
909
+ PHASE_TITLE_LIMIT,
873
910
  PLAN_LANGUAGE,
874
911
  PLAN_STATUS,
875
912
  PLAN_VOCABULARY,
876
913
  PROSE_REASON,
877
914
  RESPONSE_ADDRESS,
878
915
  SELF_MERGE_LABEL,
916
+ SHORT_NAME_LIMIT,
917
+ SHORT_NAME_PROBLEM,
918
+ STANDING_APPROVAL_TEXTS,
879
919
  WAITING_VOCABULARY,
880
920
  activeItem,
881
921
  boardRowsWithProse,
@@ -901,6 +941,7 @@ export {
901
941
  isTickedUnmerged,
902
942
  laneNameOf,
903
943
  laneProgress,
944
+ longPhaseTitles,
904
945
  mergedPhasesWithOpenBoxes,
905
946
  misplacedDeferrals,
906
947
  newPhasesWithoutReviewGate,
@@ -926,6 +967,7 @@ export {
926
967
  readSprintDocument,
927
968
  resolvePlanPath,
928
969
  sectionHeading,
970
+ shortNameFindings,
929
971
  shortTitle,
930
972
  sprintTitleOf,
931
973
  sumPhases,