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.
Files changed (261) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/INSTALL.md +3 -1
  3. package/README.md +75 -64
  4. package/dist/{chunk-OU2KALXP.js → chunk-5MQBCUQ5.js} +24 -4
  5. package/dist/config.d.ts +45 -4
  6. package/dist/config.js +84 -27
  7. package/dist/plan-format.d.ts +63 -7
  8. package/dist/plan-format.js +66 -12
  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/approval-gate/index.ts +22 -4
  15. package/kit/hooks/hooks/approval-gate/run.ts +29 -6
  16. package/kit/hooks/hooks/approval-gate/subagent.ts +18 -0
  17. package/kit/hooks/hooks/ask-hint/decision.ts +82 -14
  18. package/kit/hooks/hooks/ask-hint/index.ts +15 -19
  19. package/kit/hooks/hooks/ask-hint/settings.ts +3 -18
  20. package/kit/hooks/hooks/git-guard/decision.ts +50 -102
  21. package/kit/hooks/hooks/merge-guard/always-ask.ts +63 -0
  22. package/kit/hooks/hooks/merge-guard/{plan-approval-settings.ts → board-settings.ts} +21 -67
  23. package/kit/hooks/hooks/merge-guard/check-free.ts +20 -0
  24. package/kit/hooks/hooks/merge-guard/decision.ts +127 -140
  25. package/kit/hooks/hooks/merge-guard/index.ts +6 -3
  26. package/kit/hooks/hooks/merge-guard/lane-plan.ts +79 -0
  27. package/kit/hooks/hooks/merge-guard/run.ts +59 -54
  28. package/kit/hooks/hooks/merge-guard/settings.ts +13 -1
  29. package/kit/hooks/hooks/merge-guard/standing-approval.ts +142 -0
  30. package/kit/hooks/hooks/open-question/index.ts +26 -0
  31. package/kit/hooks/hooks/open-question/record.ts +135 -0
  32. package/kit/hooks/hooks/open-question/run.ts +52 -0
  33. package/kit/hooks/hooks/open-question/store.ts +39 -0
  34. package/kit/hooks/hooks/sent-file/index.ts +19 -0
  35. package/kit/hooks/hooks/sent-file/record.ts +128 -0
  36. package/kit/hooks/hooks/sent-file/run.ts +48 -0
  37. package/kit/hooks/hooks/sent-file/store.ts +9 -0
  38. package/kit/hooks/hooks/shared/board-lanes.ts +15 -2
  39. package/kit/hooks/hooks/shared/config.ts +7 -0
  40. package/kit/hooks/hooks/shared/dialog-answers.ts +9 -9
  41. package/kit/hooks/hooks/shared/files.ts +7 -0
  42. package/kit/hooks/hooks/shared/lane.ts +1 -1
  43. package/kit/hooks/hooks/shared/newest-verdict.ts +10 -0
  44. package/kit/hooks/hooks/shared/private-directory.ts +56 -9
  45. package/kit/hooks/hooks/shared/program-position.ts +2 -75
  46. package/kit/hooks/hooks/shared/shell-aliases.ts +1 -71
  47. package/kit/hooks/hooks/{ask-hint → shared}/transcript.ts +37 -6
  48. package/kit/hooks/hooks/{merge-guard → shared}/xargs-program.ts +10 -8
  49. package/kit/hooks/hooks/task-hint/decision.ts +49 -0
  50. package/kit/hooks/hooks/task-hint/index.ts +79 -0
  51. package/kit/hooks/hooks/task-hint/run.ts +62 -0
  52. package/kit/hooks/hooks/task-hint/store.ts +51 -0
  53. package/kit/hooks/hooks.json +21 -4
  54. package/kit/hooks/statusline.sh +1 -1
  55. package/kit/review/agents/skill-review.md +34 -0
  56. package/kit/review/skills/pre-commit/SKILL.md +1 -1
  57. package/kit/workflow/skills/follow/SKILL.md +1 -8
  58. package/kit/workflow/skills/implement/SKILL.md +10 -6
  59. package/kit/workflow/skills/orchestrate/SKILL.md +213 -412
  60. package/kit/workflow/skills/orchestrate/handover/decision.ts +43 -8
  61. package/kit/workflow/skills/orchestrate/handover/index.ts +32 -4
  62. package/kit/workflow/skills/orchestrate/handover/standing-approval.ts +84 -0
  63. package/kit/workflow/skills/orchestrate/references/acceptance.md +15 -0
  64. package/kit/workflow/skills/orchestrate/references/close-lane.md +17 -0
  65. package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +43 -17
  66. package/kit/workflow/skills/orchestrate/references/draft-review.md +19 -0
  67. package/kit/workflow/skills/orchestrate/references/events.md +86 -0
  68. package/kit/workflow/skills/orchestrate/references/herd.md +6 -6
  69. package/kit/workflow/skills/orchestrate/references/lane-handover.md +18 -0
  70. package/kit/workflow/skills/orchestrate/references/lane-session.md +25 -0
  71. package/kit/workflow/skills/orchestrate/references/lane-setup.md +37 -0
  72. package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -10
  73. package/kit/workflow/skills/orchestrate/references/sensitive-input.md +5 -7
  74. package/kit/workflow/skills/plan/SKILL.md +6 -5
  75. package/kit/workflow/skills/plan/references/template-English.md +4 -4
  76. package/kit/workflow/skills/plan/references/template-German.md +4 -4
  77. package/package.json +4 -1
  78. package/recipes/README.md +2 -1
  79. package/recipes/sources.md +1 -1
  80. package/schemas/jackherd.schema.json +85 -7
  81. package/src/check/agent-text-reviewers.ts +23 -0
  82. package/src/check/command.ts +2 -0
  83. package/src/check/plan-structure-check.ts +49 -2
  84. package/src/check/plan.ts +8 -1
  85. package/src/check/schema.ts +9 -0
  86. package/src/check/skill-budget-defaults.ts +55 -0
  87. package/src/check/skill-budgets.ts +185 -0
  88. package/src/cli.ts +4 -0
  89. package/src/config/auto-window.ts +8 -0
  90. package/src/config/link-kinds.ts +7 -1
  91. package/src/config/schema.ts +80 -26
  92. package/src/context/fill.ts +12 -0
  93. package/src/context/session-log.ts +43 -1
  94. package/src/lane/close-board.ts +106 -0
  95. package/src/lane/close-check.ts +183 -0
  96. package/src/lane/close-command-line.ts +11 -0
  97. package/src/lane/close-command.ts +217 -0
  98. package/src/lane/close-options.ts +43 -0
  99. package/src/lane/close-survey.ts +83 -0
  100. package/src/lane/close.ts +141 -0
  101. package/src/lane/command.ts +13 -0
  102. package/src/lane-prompt/build.ts +74 -0
  103. package/src/lane-prompt/command.ts +87 -0
  104. package/src/lane-prompt/input.ts +116 -0
  105. package/src/lane-prompt/options.ts +68 -0
  106. package/src/lane-prompt/texts.ts +101 -0
  107. package/src/plan-format/clock.ts +12 -0
  108. package/src/plan-format/dependencies.ts +9 -9
  109. package/src/plan-format/index.ts +1 -0
  110. package/src/plan-format/lanes.ts +30 -5
  111. package/src/plan-format/short-names.ts +71 -0
  112. package/src/plan-format/terminal-text.ts +4 -0
  113. package/src/plan-format/waiting.ts +53 -0
  114. package/src/plans/active-tasks.ts +60 -0
  115. package/src/plans/appointment-rows.ts +59 -0
  116. package/src/plans/ask-coordinator.ts +77 -0
  117. package/src/plans/ask-target.ts +25 -0
  118. package/src/plans/child-stop.ts +51 -0
  119. package/src/plans/closable-records.ts +34 -0
  120. package/src/plans/command.ts +3 -2
  121. package/src/plans/current-phase.ts +17 -7
  122. package/src/plans/current-task.ts +63 -5
  123. package/src/plans/fetch.ts +26 -3
  124. package/src/plans/git.ts +19 -5
  125. package/src/plans/herd-claim.ts +173 -0
  126. package/src/plans/herd-memory.ts +36 -25
  127. package/src/plans/lane-plans.ts +44 -13
  128. package/src/plans/lane-standing.ts +9 -4
  129. package/src/plans/lane-summary.ts +367 -92
  130. package/src/plans/lane-tickets.ts +15 -0
  131. package/src/plans/layout-phase.ts +80 -19
  132. package/src/plans/layout-summary.ts +163 -74
  133. package/src/plans/layout.ts +472 -114
  134. package/src/plans/link-chips.ts +1 -0
  135. package/src/plans/live.ts +119 -10
  136. package/src/plans/phase-ids.ts +8 -0
  137. package/src/plans/phase-models.ts +1 -1
  138. package/src/plans/phase-ranges.ts +45 -0
  139. package/src/plans/phase-source.ts +15 -0
  140. package/src/plans/plan-cache.ts +28 -0
  141. package/src/plans/plan-heads.ts +54 -0
  142. package/src/plans/pull-requests.ts +23 -2
  143. package/src/plans/question-records.ts +48 -0
  144. package/src/plans/questions-read.ts +13 -0
  145. package/src/plans/read-cache.ts +8 -0
  146. package/src/plans/record-id.ts +4 -0
  147. package/src/plans/restart-decision.ts +13 -4
  148. package/src/plans/selection.ts +117 -34
  149. package/src/plans/sent-files-read.ts +13 -0
  150. package/src/plans/sent-files.ts +114 -0
  151. package/src/plans/settings.ts +2 -1
  152. package/src/plans/signalled-phase.ts +42 -0
  153. package/src/plans/signals-read.ts +62 -0
  154. package/src/plans/snapshot.ts +279 -60
  155. package/src/plans/sprint-export.ts +11 -11
  156. package/src/plans/sprint-page.ts +5 -5
  157. package/src/plans/subagent-phases.ts +95 -0
  158. package/src/plans/subagent-tasks.ts +51 -0
  159. package/src/plans/subagents-read.ts +131 -0
  160. package/src/plans/subagents.ts +190 -0
  161. package/src/plans/supervisor.ts +14 -4
  162. package/src/plans/symbols.ts +3 -0
  163. package/src/plans/task-name.ts +34 -0
  164. package/src/plans/texts.ts +36 -14
  165. package/src/plans/unlisted-lanes.ts +10 -5
  166. package/src/plans/view.ts +20 -12
  167. package/src/plans/waiting-click.ts +231 -0
  168. package/src/plans/waiting-records.ts +39 -0
  169. package/src/plans/waiting-window.ts +8 -0
  170. package/src/preview/command.ts +105 -0
  171. package/src/preview/embed.ts +209 -0
  172. package/src/preview/images.ts +89 -0
  173. package/src/preview/open.ts +12 -0
  174. package/src/preview/options.ts +32 -0
  175. package/src/preview/page.ts +89 -0
  176. package/src/preview/render.ts +41 -0
  177. package/src/settled/board-row.ts +1 -2
  178. package/src/shared/is-record.ts +2 -0
  179. package/src/shared/private-location.ts +89 -0
  180. package/src/shared/private-records.ts +48 -0
  181. package/src/shared/stat-cache.ts +48 -0
  182. package/src/sync/kit-source.ts +29 -10
  183. package/src/sync/kit.ts +2 -0
  184. package/src/sync/manifest.ts +2 -0
  185. package/src/sync/plan-ask-rules.ts +94 -0
  186. package/src/sync/plan-hooks.ts +2 -0
  187. package/src/sync/render.ts +3 -3
  188. package/src/sync/settings-hooks.ts +4 -1
  189. package/src/sync/sync-plan.ts +1 -0
  190. package/src/sync/sync.ts +24 -3
  191. package/src/task-signals/agent.ts +15 -0
  192. package/src/task-signals/claude-read.ts +89 -0
  193. package/src/task-signals/claude-signals.ts +112 -0
  194. package/src/task-signals/command.ts +71 -0
  195. package/src/task-signals/fixtures/session.jsonl +7 -0
  196. package/src/task-signals/matcher.ts +96 -0
  197. package/src/task-signals/record-store.ts +40 -0
  198. package/src/task-signals/record.ts +68 -0
  199. package/src/task-signals/text.ts +13 -0
  200. package/src/task-signals/types.ts +14 -0
  201. package/src/terminal/open.ts +13 -6
  202. package/src/terminal/text.ts +16 -4
  203. package/src/terminal/width.ts +2 -1
  204. package/src/version/skill-diff.ts +46 -0
  205. package/src/vocabulary/index.ts +44 -6
  206. package/src/waiting/command.ts +50 -0
  207. package/src/watch/appointments.ts +2 -4
  208. package/src/watch/approvals.ts +2 -2
  209. package/src/watch/asking-lanes.ts +40 -0
  210. package/src/watch/base-tip.ts +11 -0
  211. package/src/watch/command.ts +401 -90
  212. package/src/watch/failed-tests.ts +56 -10
  213. package/src/watch/failure-read.ts +18 -6
  214. package/src/watch/failures.ts +29 -5
  215. package/src/watch/feedback.ts +1 -1
  216. package/src/watch/gh-process.ts +9 -3
  217. package/src/watch/heartbeat-progress.ts +6 -18
  218. package/src/watch/json-guards.ts +0 -3
  219. package/src/watch/kit-update.ts +28 -9
  220. package/src/watch/lane-fill.ts +1 -1
  221. package/src/watch/lane-model.ts +1 -1
  222. package/src/watch/lane-name.ts +6 -0
  223. package/src/watch/lane-parallel.ts +1 -1
  224. package/src/watch/lane-progress.ts +58 -14
  225. package/src/watch/lane-report-file.ts +20 -5
  226. package/src/watch/lane-report.ts +37 -5
  227. package/src/watch/lane-rest.ts +28 -0
  228. package/src/watch/lane-sources.ts +31 -3
  229. package/src/watch/main-checkout.ts +34 -0
  230. package/src/watch/plan-badges-read.ts +13 -7
  231. package/src/watch/plan-badges.ts +9 -2
  232. package/src/watch/previews.ts +1 -1
  233. package/src/watch/pull-requests.ts +8 -3
  234. package/src/watch/reference-paths.ts +87 -0
  235. package/src/watch/running-subagents.ts +43 -0
  236. package/src/watch/skill-change.ts +124 -32
  237. package/src/watch/source-change.ts +48 -0
  238. package/src/watch/source-path.ts +1 -1
  239. package/src/watch/sources.ts +7 -4
  240. package/src/watch/stream-output.ts +14 -8
  241. package/src/watch/task-parallel-auto.ts +208 -0
  242. package/src/watch/task-parallel-data-paths.ts +46 -0
  243. package/src/watch/task-parallel-input.ts +108 -0
  244. package/src/watch/task-parallel-read.ts +151 -0
  245. package/src/watch/task-parallel-touched.ts +39 -0
  246. package/src/watch/task-parallel.ts +235 -0
  247. package/src/watch/task-signal-missing.ts +69 -0
  248. package/src/watch/task-signal-read.ts +29 -0
  249. package/src/watch/texts.ts +10 -0
  250. package/src/watch/update-available.ts +6 -3
  251. package/src/watch/waiting-line.ts +19 -0
  252. package/src/watch/watch-events.ts +36 -1
  253. package/src/watch/watcher-lock.ts +25 -29
  254. package/src/watch/workflow-runs.ts +2 -1
  255. package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +0 -23
  256. package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +0 -196
  257. package/kit/hooks/hooks/merge-guard/plan-approval.ts +0 -168
  258. package/kit/hooks/hooks/merge-guard/plan-growth.ts +0 -40
  259. package/kit/hooks/hooks/merge-guard/plan-pointers.ts +0 -194
  260. package/kit/hooks/hooks/shared/command-text-runners.ts +0 -71
  261. 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-OU2KALXP.js";
4
+ } from "./chunk-5MQBCUQ5.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
  });
@@ -182,11 +188,23 @@ var isSourcePath = (path) => pathSteps(path) !== null;
182
188
  var CLEAN_REVIEW_TEXT = "## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.";
183
189
  var BUN_FAILED_TEST_PATTERN = "^\\(fail\\) (.+?)(?: \\[[\\d.]+m?s\\])?$";
184
190
 
191
+ // src/plan-format/clock.ts
192
+ var DAY_MS = 864e5;
193
+
194
+ // src/config/auto-window.ts
195
+ var MAX_AUTO_WINDOW_DAYS = 7;
196
+ var windowDays = (since, until) => Math.round((Date.parse(`${until}T00:00:00Z`) - Date.parse(`${since}T00:00:00Z`)) / DAY_MS);
197
+
185
198
  // src/config/link-kinds.ts
199
+ var ARTIFACT_LINK_KIND = {
200
+ pattern: "^https://claude\\.ai/(code/)?artifact/",
201
+ label: "Artifact",
202
+ icon: "\u2726"
203
+ };
186
204
  var DEFAULT_LINK_KINDS = [
187
205
  { pattern: "^https://github\\.com/[^/]+/[^/]+/(pull|issues)/\\d+", label: "GitHub", icon: "\u2442" },
188
206
  { pattern: "^https://(www\\.)?figma\\.com/", label: "Figma", icon: "\u25C8" },
189
- { pattern: "^https://claude\\.ai/(code/)?artifact/", label: "Artifact", icon: "\u2726" }
207
+ ARTIFACT_LINK_KIND
190
208
  ];
191
209
  var isRegularExpression = (source) => {
192
210
  try {
@@ -313,6 +331,25 @@ var feedbackSchema = z2.strictObject({
313
331
  */
314
332
  noFindingTemplates: z2.array(z2.string().min(1)).default([CLEAN_REVIEW_TEXT])
315
333
  });
334
+ var DEFAULT_PARALLEL_TASKS_MAX_TOKENS = 1e5;
335
+ var parallelTasksSchema = z2.strictObject({
336
+ /**
337
+ * The most the extra agents of a parallel proposal may cost before they do anything: the token baseline of a fresh
338
+ * agent times the agents to add. Above it the watcher does not propose parallel tasks. The baseline is the context
339
+ * the first answer of a lane's session carried.
340
+ */
341
+ maxTokens: z2.number().int().positive().default(DEFAULT_PARALLEL_TASKS_MAX_TOKENS),
342
+ /**
343
+ * The days, both included, on which a proposal that keeps every limit is marked `auto` and the coordinator starts it
344
+ * without asking: subagents beside running work in a lane, at most two extra agents, no path that always asks.
345
+ * `since` is the day it was switched on, `until` at most seven days later; outside the window it is off. Unset by
346
+ * default, so every proposal asks.
347
+ */
348
+ auto: z2.strictObject({ since: z2.iso.date(), until: z2.iso.date() }).refine(({ since, until }) => until >= since, "until must not be before since").refine(
349
+ ({ since, until }) => windowDays(since, until) <= MAX_AUTO_WINDOW_DAYS,
350
+ `until must be at most ${MAX_AUTO_WINDOW_DAYS} days after since`
351
+ ).optional()
352
+ });
316
353
  var checksSchema = z2.strictObject({
317
354
  /**
318
355
  * Finds a failed test in a line of a red run's log (`gh run view --log-failed`) after its job, step and timestamp
@@ -397,9 +434,9 @@ var sourceSchema = z2.strictObject({
397
434
  var hasUniqueNames = (sources) => new Set(sources.map(({ name }) => name)).size === sources.length;
398
435
  var coordinatorSchema = z2.strictObject({
399
436
  /** Tokens in the coordinator's context from which the watcher reports `coordinator warn`. */
400
- warnTokens: tokenCount.default(2e5),
437
+ warnTokens: tokenCount.default(35e4),
401
438
  /** Tokens in the coordinator's context from which the watcher reports `coordinator alert`. */
402
- alertTokens: tokenCount.default(3e5)
439
+ alertTokens: tokenCount.default(5e5)
403
440
  }).refine(warnBelowAlert, WARN_BELOW_ALERT);
404
441
  var guardedCommand = z2.string().regex(/^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*){1,3}$/, "a program and one to three plain words, such as gh pr merge");
405
442
  var checkFreePattern = z2.string().regex(/^[A-Za-z0-9_.*/-]+$/, "path words with * and ** only, such as docs/**/*.md").refine(
@@ -425,13 +462,13 @@ var mergeSchema = z2.strictObject({
425
462
  */
426
463
  withoutChecks: z2.array(checkFreePattern).default([])
427
464
  });
428
- var guardSchema = z2.strictObject({
465
+ var guardFields = z2.strictObject({
429
466
  /**
430
- * The commands the merge guard hook asks the user about before they run, also behind options before the verb, a chain,
431
- * 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
432
- * guard off.
467
+ * The commands the merge guard hook asks the user about before they run, where they stand at a program position, also
468
+ * behind options before the verb, a chain, a wrapper or `sh -c`. `gh api`, when listed, asks only for a write to a
469
+ * merge, a file, a dispatch, a rerun or a ref. An empty list switches the guard off.
433
470
  */
434
- commands: z2.array(guardedCommand).default(["gh pr merge", "gh workflow run", "gh run rerun", "gh api"]),
471
+ commands: z2.array(guardedCommand).default(["gh pr merge", "gh workflow run"]),
435
472
  selfMerge: z2.strictObject({
436
473
  /** Whether `/orchestrate` offers "merge it yourself after green checks" in the pull request question; off also stops the hook honouring such an answer. */
437
474
  offer: z2.boolean().default(true),
@@ -442,28 +479,39 @@ var guardSchema = z2.strictObject({
442
479
  /** 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. */
443
480
  waitMinutes: z2.number().int().min(1).max(9).default(8)
444
481
  }).prefault({}),
445
- planApproval: z2.strictObject({
446
- /**
447
- * Paths that always ask, on top of the ones jackherd brings (`.github/**`, `**\/migrations/**`, `**\/.env*`,
448
- * `.claude/**`, `.jackherd/**`, `bunfig.toml`, `**\/CLAUDE.md`, `**\/AGENTS.md`, `.agents/**`, `.mcp.json`,
449
- * `.husky/**`, `lefthook.yml`, the lockfiles of bun, npm, pnpm and yarn, `**\/.npmrc`, `**\/CODEOWNERS`, every
450
- * package.json, and the CHANGELOG.md of every package without `"private": true`):
451
- * a pull request that changes one is never merged on a plan approval alone. They add to that list, never replace
452
- * it; an invalid entry turns plan approval off, so every merge asks.
453
- */
454
- exceptPaths: z2.array(alwaysAskPattern).default([]),
455
- /**
456
- * Checks that must be among the head commit's and green before a plan approval lets a merge pass, by their names
457
- * on GitHub. Unset, the merge needs at least one check and every one green; set, also each of these. Either way
458
- * GitHub must be able to merge it and the head must contain the base branch.
459
- */
460
- requiredChecks: z2.array(checkName).optional()
461
- }).prefault({})
482
+ /**
483
+ * Paths that always ask, on top of the ones jackherd brings (`.github/**`, `**\/migrations/**`, `**\/.env*`,
484
+ * `.claude/**`, `.jackherd/**`, `bunfig.toml`, `**\/CLAUDE.md`, `**\/AGENTS.md`, `.agents/**`, `.mcp.json`,
485
+ * `.husky/**`, `lefthook.yml`, the lockfiles of bun, npm, pnpm and yarn, `**\/.npmrc`, `**\/CODEOWNERS`): a
486
+ * `merge.withoutChecks` pattern never opens them, and the watcher never starts parallel work on them by itself. They
487
+ * add to that list, never replace it. A standing approval covers them; only workflows, migrations and env files ask.
488
+ */
489
+ alwaysAsk: z2.array(alwaysAskPattern).default([]),
490
+ /**
491
+ * Checks that must be among the head commit's and green before a standing approval lets a merge pass, by their names
492
+ * on GitHub. Unset, the merge needs at least one check and every one green; set, also each of these. Either way
493
+ * GitHub must be able to merge it.
494
+ */
495
+ requiredChecks: z2.array(checkName).optional(),
496
+ /** Read until 2.0.0 as `alwaysAsk` (`exceptPaths`) and `requiredChecks`; the plan approval it was for is gone. */
497
+ planApproval: z2.strictObject({ exceptPaths: z2.array(alwaysAskPattern).optional(), requiredChecks: z2.array(checkName).optional() }).optional().meta({ deprecated: true })
462
498
  });
499
+ var guardWithoutPlanApproval = ({ planApproval, ...guard }) => {
500
+ const requiredChecks = guard.requiredChecks ?? planApproval?.requiredChecks;
501
+ return {
502
+ ...guard,
503
+ alwaysAsk: [...guard.alwaysAsk, ...planApproval?.exceptPaths ?? []],
504
+ ...requiredChecks === void 0 ? {} : { requiredChecks }
505
+ };
506
+ };
507
+ var guardSchema = guardFields.transform(guardWithoutPlanApproval);
463
508
  var hintsSchema = z2.strictObject({
464
509
  /** 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. */
465
- askUserQuestion: z2.boolean().default(true)
510
+ askUserQuestion: z2.boolean().default(true),
511
+ /** Whether the `task-hint` hook points a lane that edits a file without an open task signal to `jh task start`, once per task. */
512
+ taskSignal: z2.boolean().default(true)
466
513
  });
514
+ var DEFAULT_TASK_SIGNAL_MINUTES = 10;
467
515
  var contextSchema = z2.strictObject({
468
516
  /** Tokens in a lane's context from which its fill shows yellow and `/implement` offers a new chat. */
469
517
  warnTokens: tokenCount.default(25e4),
@@ -517,6 +565,11 @@ var jackherdConfigSchema = z2.strictObject({
517
565
  }).prefault({}),
518
566
  models: z2.record(z2.enum(Object.values(MODEL_ROLE)), modelId),
519
567
  terminal: z2.strictObject({ adapter: z2.enum(Object.values(TERMINAL_ADAPTER)).default(TERMINAL_ADAPTER.none) }).prefault({}),
568
+ /** How the coordinator shows a draft for review (`orchestrate` § 4). */
569
+ preview: z2.strictObject({
570
+ /** Publish the draft as a private Artifact when the session has the tool; `false` sends a file card or `jh preview --open` instead. */
571
+ publish: z2.boolean().default(true)
572
+ }).prefault({}),
520
573
  plans: z2.strictObject({
521
574
  /** Minutes between background fetches of the base branch while `jh herd` runs; 0 turns them off. */
522
575
  fetchIntervalMinutes: z2.number().min(0).default(5),
@@ -533,6 +586,8 @@ var jackherdConfigSchema = z2.strictObject({
533
586
  /**
534
587
  * Read by `jh watch`; without `deploy` it reports no deploy runs. `feedback` is on unless set to `false`:
535
588
  * it reports new feedback on the lanes' pull requests except from the account the watcher runs as and `ignore`.
589
+ * `parallelTasks.maxTokens` caps the cost of the extra agents in a proposal of parallel tasks at the heartbeat.
590
+ * `taskSignalMinutes` is how long a lane works without a task signal before it reports `no task signal`, once per phase.
536
591
  * `checks` reads the failed tests of a red lane pull request (`failedTestPattern`), also with `feedback` off.
537
592
  * Without `previews` it reads no deploy preview; with it, one `preview` per new head commit. Each of `sources` is a
538
593
  * command whose JSON output reports `source` for a new or changed entry.
@@ -541,6 +596,8 @@ var jackherdConfigSchema = z2.strictObject({
541
596
  deploy: deploySchema.optional(),
542
597
  feedback: z2.union([z2.literal(false), feedbackSchema]).prefault({}),
543
598
  checks: checksSchema.prefault({}),
599
+ parallelTasks: parallelTasksSchema.prefault({}),
600
+ taskSignalMinutes: z2.number().int().positive().default(DEFAULT_TASK_SIGNAL_MINUTES),
544
601
  previews: previewsSchema.optional(),
545
602
  sources: z2.array(sourceSchema).refine(hasUniqueNames, "source names must be unique").default([])
546
603
  }).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
@@ -59,6 +65,22 @@ declare const APPOINTMENT_VOCABULARY: Record<PlanLanguage, {
59
65
  statusAlso: readonly string[];
60
66
  open: readonly string[];
61
67
  }>;
68
+ /**
69
+ * The board section of what waits on the user's answer (a sent preview, an open question, a report to accept): its
70
+ * heading, the header names of its columns (since, item, origin, status, found by name in any order; the status
71
+ * column may also carry a name of `statusAlso`) and the lower-case words a status cell of a still open item starts with.
72
+ */
73
+ declare const WAITING_VOCABULARY: Record<PlanLanguage, {
74
+ heading: string;
75
+ columns: {
76
+ since: string;
77
+ item: string;
78
+ origin: string;
79
+ status: string;
80
+ };
81
+ statusAlso: readonly string[];
82
+ open: readonly string[];
83
+ }>;
62
84
  declare const isPlanLanguage: (value: string) => value is PlanLanguage;
63
85
  declare const PLAN_STATUS: {
64
86
  readonly done: "done";
@@ -348,7 +370,8 @@ declare const withoutRepeats: (lane: SprintLane, step: PlanStep) => PlanStep;
348
370
  /**
349
371
  * The lanes of the sprint document's lanes table (`## Spuren` in `de`) in table order. A row whose
350
372
  * first cell names a lane in backticks belongs to that lane; a `↳` row without a name continues
351
- * the lane above it.
373
+ * the lane above it. A cleaned-up lane stays closed through its `↳` rows, but a later row that names it again
374
+ * without the mark starts the lane anew: the closed rows are history and drop out.
352
375
  */
353
376
  declare const parseSprintLanes: (markdown: string, language: PlanLanguage) => SprintLane[];
354
377
  declare const sprintTitleOf: (markdown: string, fallback: string) => string;
@@ -483,6 +506,39 @@ declare const activeItem: (phase: Pick<PhaseProgress, "items">) => ChecklistItem
483
506
  /** 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. */
484
507
  declare const shortTitle: (progress: LaneProgress) => string | null;
485
508
 
509
+ /** Characters a task's short name may have, so the herd shows it whole. */
510
+ declare const SHORT_NAME_LIMIT = 40;
511
+ /** Characters a phase title may have; the title is the phase's name. */
512
+ declare const PHASE_TITLE_LIMIT = 50;
513
+ declare const SHORT_NAME_PROBLEM: {
514
+ readonly missing: "missing";
515
+ readonly long: "long";
516
+ };
517
+ /** What is wrong with an open task's short name: none at all, or one of `length` characters over the limit. */
518
+ type ShortNameProblem = {
519
+ problem: typeof SHORT_NAME_PROBLEM.missing;
520
+ } | {
521
+ problem: typeof SHORT_NAME_PROBLEM.long;
522
+ length: number;
523
+ };
524
+ /** An open task (1-based line) of phase `phase` whose short name is missing or too long. */
525
+ type ShortNameFinding = {
526
+ line: number;
527
+ phase: string;
528
+ } & ShortNameProblem;
529
+ /** A phase heading whose title has `length` characters, over the limit. */
530
+ type LongPhaseTitle = PhaseHeading & {
531
+ length: number;
532
+ };
533
+ /**
534
+ * The open tasks a change wrote (a line not in `previousLines`) that do not start with `**Name**:` or whose name is
535
+ * longer than `SHORT_NAME_LIMIT`. Only tasks of a phase section before its review gate count: a gate item keeps its
536
+ * own form.
537
+ */
538
+ declare const shortNameFindings: (markdown: string, language: PlanLanguage, previousLines: ReadonlySet<string>) => ShortNameFinding[];
539
+ /** The phase headings a change wrote whose title is longer than `PHASE_TITLE_LIMIT`. */
540
+ declare const longPhaseTitles: (markdown: string, language: PlanLanguage, previousLines: ReadonlySet<string>) => LongPhaseTitle[];
541
+
486
542
  /** What a sprint document says around its lanes table: its goal and its history. */
487
543
  type SprintPlanRow = {
488
544
  cells: string[];
@@ -512,4 +568,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
512
568
  */
513
569
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
514
570
 
515
- 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, 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 };
571
+ 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, 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,18 +2,20 @@ 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
7
  PLAN_LANGUAGE,
8
8
  PLAN_STATUS,
9
9
  PLAN_VOCABULARY,
10
10
  RESPONSE_ADDRESS,
11
11
  SELF_MERGE_LABEL,
12
+ STANDING_APPROVAL_TEXTS,
13
+ WAITING_VOCABULARY,
12
14
  classifyStatus,
13
15
  isPlanLanguage,
14
16
  phaseHeading,
15
17
  sectionHeading
16
- } from "./chunk-OU2KALXP.js";
18
+ } from "./chunk-5MQBCUQ5.js";
17
19
 
18
20
  // src/plan-format/lanes.ts
19
21
  import { posix } from "path";
@@ -100,6 +102,8 @@ var CONTINUATION = "\u21B3";
100
102
  var PLAN_LINK = /\[([^\]]+)\]\(([^)]+)\)/;
101
103
  var PHASE_ID_PREFIX = "P";
102
104
  var phaseLabel = (id) => `${PHASE_ID_PREFIX}${id}`;
105
+ var MAX_RANGE = 50;
106
+ var spannedPhaseIds = (first, last) => first > last || last - first >= MAX_RANGE ? [] : Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
103
107
  var phaseLabels = (ids, separator) => ids.map(phaseLabel).join(separator);
104
108
  var PHASES_COLUMN = 3;
105
109
  var CLAUSE_SEPARATOR = /([;,]|\.(?=\s))\s*/;
@@ -109,10 +113,18 @@ var phaseIdPatterns = (language) => {
109
113
  const id = `(?:\\*\\*)?${PHASE_ID_PREFIX}(\\d+[a-z]?)\\b`;
110
114
  return {
111
115
  leading: new RegExp(`^\\s*(?:(?:${connectors})\\s+)*${id}`),
116
+ rangeEnd: new RegExp(`^\\s*[\u2013-]\\s*(?:\\*\\*)?${PHASE_ID_PREFIX}(\\d+)\\b`),
112
117
  afterConnector: new RegExp(`^\\s*(?:${connectors})\\s`),
113
118
  connector: new RegExp(`\\b(?:${connectors})\\s+${id}`, "g")
114
119
  };
115
120
  };
121
+ var leadingIds = (clause, leading, patterns) => {
122
+ const first = leading[1];
123
+ const end = clause.slice(leading[0].length).match(patterns.rangeEnd);
124
+ if (!end || !/^\d+$/.test(first)) return [first];
125
+ const spanned = spannedPhaseIds(Number(first), Number(end[1]));
126
+ return spanned.length > 0 ? spanned : [first];
127
+ };
116
128
  var phaseIdsOf = (cell, patterns) => {
117
129
  const parts = cell.split(CLAUSE_SEPARATOR);
118
130
  const ids = [];
@@ -125,7 +137,7 @@ var phaseIdsOf = (cell, patterns) => {
125
137
  const leading = clause.match(patterns.leading);
126
138
  const startsEntry = entryStart || listRunning || patterns.afterConnector.test(clause);
127
139
  const counts = leading !== null && startsEntry;
128
- if (leading && counts) ids.push(leading[1]);
140
+ if (leading && counts) ids.push(...leadingIds(clause, leading, patterns));
129
141
  ids.push(...[...clause.matchAll(patterns.connector)].map((match) => match[1]));
130
142
  listRunning ||= counts;
131
143
  }
@@ -162,7 +174,12 @@ var parseSprintLanes = (markdown, language) => {
162
174
  if (!name) continue;
163
175
  const port = Number.parseInt(cells[1] ?? "", 10);
164
176
  const lane = lanes.get(name) ?? { name, port: Number.isNaN(port) ? null : port, steps: [], closed: false };
165
- lane.closed ||= (cells[0] ?? "").includes(laneClosed);
177
+ const marked = (cells[0] ?? "").includes(laneClosed);
178
+ if (lane.closed && !marked && !(cells[0] ?? "").startsWith(CONTINUATION)) {
179
+ lane.closed = false;
180
+ lane.steps = [];
181
+ }
182
+ lane.closed ||= marked;
166
183
  lane.steps.push(withoutRepeats(lane, toStep(cells, phaseId)));
167
184
  lanes.set(name, lane);
168
185
  previous = name;
@@ -451,7 +468,6 @@ var GLOB_CHARACTER = /[*?{[]/;
451
468
  var VERSION = /^v?\d+(?:[.\-+][\w.-]*)?$/i;
452
469
  var PACKAGE_SCOPE = /^@[^/]+\//;
453
470
  var RELEASE_FILE = /(?:^|\/)(?:CHANGELOG\.md|package\.json)$/;
454
- var MAX_RANGE = 50;
455
471
  var phaseIdOf = (cell) => PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
456
472
  var splitOutsideParentheses = (cell, separator) => {
457
473
  const entries = [];
@@ -470,9 +486,7 @@ var splitOutsideParentheses = (cell, separator) => {
470
486
  var rangeOf = (entry) => {
471
487
  const match = PHASE_RANGE.exec(entry);
472
488
  if (!match) return null;
473
- const [first, last] = [Number(match[1]), Number(match[2])];
474
- if (first > last || last - first >= MAX_RANGE) return [];
475
- return Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
489
+ return spannedPhaseIds(Number(match[1]), Number(match[2]));
476
490
  };
477
491
  var dependsOf = (cell, language) => {
478
492
  const noDependency = PLAN_VOCABULARY[language].noDependency;
@@ -560,12 +574,13 @@ var phasesWithoutDependencyRow = (markdown, language) => {
560
574
  };
561
575
  var longestChain = (chains) => chains.reduce((longest, chain) => chain.length > longest.length ? chain : longest, []);
562
576
  var mergeSets = (sets) => sets.some((set) => set === null) ? null : sets.flatMap((set) => set ?? []);
563
- var writeSetOf = (row, byId, chain) => {
577
+ var writeSetWithin = (row, byId, chain) => {
564
578
  if (chain.includes(row.id)) return null;
565
579
  const targets = row.writesLike.map((id) => byId.get(id));
566
- const like = mergeSets(targets.map((target) => target ? writeSetOf(target, byId, [...chain, row.id]) : null));
580
+ const like = mergeSets(targets.map((target) => target ? writeSetWithin(target, byId, [...chain, row.id]) : null));
567
581
  return like === null ? null : [...row.writes, ...like];
568
582
  };
583
+ var writeSetOf = (row, byId) => writeSetWithin(row, byId, []);
569
584
  var overlaps = (left, right) => left === "" || right === "" || left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
570
585
  var sharesEntry = (first, second) => {
571
586
  const entries = new Set(second.map((entry) => entry.trim().toLowerCase()));
@@ -574,7 +589,7 @@ var sharesEntry = (first, second) => {
574
589
  var sharesMarker = (first, second) => sharesEntry(first.serialMarkers, second.serialMarkers) || sharesEntry(first.releases, second.releases);
575
590
  var collides = (first, second) => sharesMarker(first, second) || first.writes.some((path) => second.writes.some((other) => overlaps(path, other)));
576
591
  var candidateOf = (row, byId) => {
577
- const writes = writeSetOf(row, byId, []);
592
+ const writes = writeSetOf(row, byId);
578
593
  if (writes === null || writes.length === 0) return [];
579
594
  const releases = [.../* @__PURE__ */ new Set([...row.releases, ...releasedByFiles(writes)])];
580
595
  return [{ id: row.id, writes, serialMarkers: row.serialMarkers, releases }];
@@ -811,6 +826,38 @@ var boardRowsWithoutPhase = (markdown, language, previousLines) => changedOpenLa
811
826
  ({ phases }) => phaseIdsOfCell(phases, language).length === 0
812
827
  );
813
828
 
829
+ // src/plan-format/short-names.ts
830
+ var SHORT_NAME_LIMIT = 40;
831
+ var PHASE_TITLE_LIMIT = 50;
832
+ var SHORT_NAME_PROBLEM = { missing: "missing", long: "long" };
833
+ var SHORT_NAME = /^\s*[-*]\s+\[ \]\s+\*\*(.+?)\*\*:/;
834
+ var characterCount = (text) => [...text].length;
835
+ var shortNameProblem = (line) => {
836
+ const name = line.match(SHORT_NAME)?.[1];
837
+ if (name === void 0) return { problem: SHORT_NAME_PROBLEM.missing };
838
+ const length = characterCount(name);
839
+ return length > SHORT_NAME_LIMIT ? { problem: SHORT_NAME_PROBLEM.long, length } : null;
840
+ };
841
+ var shortNameFindings = (markdown, language, previousLines) => {
842
+ const headings = new Map(phaseHeadings(markdown, language).map((heading) => [heading.line, heading.id]));
843
+ const found = [];
844
+ let phase = null;
845
+ for (const [index, line] of markdown.split("\n").entries()) {
846
+ const heading = headings.get(index + 1);
847
+ if (heading !== void 0) phase = heading;
848
+ else if (endsPhase(line) || isReviewGateLine(line)) phase = null;
849
+ else if (phase !== null && isOpenCheckboxLine(line) && !previousLines.has(line)) {
850
+ const problem = shortNameProblem(line);
851
+ if (problem) found.push({ line: index + 1, phase, ...problem });
852
+ }
853
+ }
854
+ return found;
855
+ };
856
+ var longPhaseTitles = (markdown, language, previousLines) => {
857
+ const lines = markdown.split("\n");
858
+ return phaseHeadings(markdown, language).filter(({ line }) => !previousLines.has(lines[line - 1] ?? "")).map((heading) => ({ ...heading, length: characterCount(heading.title) })).filter(({ length }) => length > PHASE_TITLE_LIMIT);
859
+ };
860
+
814
861
  // src/plan-format/sprint-document.ts
815
862
  var PHASE_ID_IN_CELL = /^(\d+[a-z]?)\b/;
816
863
  var paragraphs = (lines) => lines.join("\n").split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
@@ -853,16 +900,21 @@ export {
853
900
  ADDRESS_FORM,
854
901
  APPOINTMENT_VOCABULARY,
855
902
  APPROVAL_TEXTS,
903
+ LANE_PROMPT_OPENING,
856
904
  LANGUAGE_NAME,
857
905
  PHASE_CELL_LIMIT,
858
906
  PHASE_ID_PREFIX,
859
- PLAN_APPROVAL_LABELS,
907
+ PHASE_TITLE_LIMIT,
860
908
  PLAN_LANGUAGE,
861
909
  PLAN_STATUS,
862
910
  PLAN_VOCABULARY,
863
911
  PROSE_REASON,
864
912
  RESPONSE_ADDRESS,
865
913
  SELF_MERGE_LABEL,
914
+ SHORT_NAME_LIMIT,
915
+ SHORT_NAME_PROBLEM,
916
+ STANDING_APPROVAL_TEXTS,
917
+ WAITING_VOCABULARY,
866
918
  activeItem,
867
919
  boardRowsWithProse,
868
920
  boardRowsWithoutPhase,
@@ -887,6 +939,7 @@ export {
887
939
  isTickedUnmerged,
888
940
  laneNameOf,
889
941
  laneProgress,
942
+ longPhaseTitles,
890
943
  mergedPhasesWithOpenBoxes,
891
944
  misplacedDeferrals,
892
945
  newPhasesWithoutReviewGate,
@@ -912,6 +965,7 @@ export {
912
965
  readSprintDocument,
913
966
  resolvePlanPath,
914
967
  sectionHeading,
968
+ shortNameFindings,
915
969
  shortTitle,
916
970
  sprintTitleOf,
917
971
  sumPhases,