jackherd 1.16.0 → 1.17.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 (155) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/INSTALL.md +2 -1
  3. package/README.md +22 -21
  4. package/dist/{chunk-4LUFR7RC.js → chunk-N5FAOQZL.js} +7 -0
  5. package/dist/config.d.ts +33 -1
  6. package/dist/config.js +57 -5
  7. package/dist/plan-format.d.ts +18 -6
  8. package/dist/plan-format.js +8 -1
  9. package/docs/plan-contract.md +1 -0
  10. package/herdr-plugin/herdr-plugin.toml +4 -1
  11. package/herdr-plugin/restore-plans.ts +60 -26
  12. package/herdr-plugin/resume.ts +18 -0
  13. package/install.sh +1 -1
  14. package/kit/hooks/ask-rules.json +11 -0
  15. package/kit/hooks/hooks/approval-gate/checked-marks.ts +59 -0
  16. package/kit/hooks/hooks/approval-gate/index.ts +36 -7
  17. package/kit/hooks/hooks/approval-gate/run.ts +72 -16
  18. package/kit/hooks/hooks/approval-gate/settings.ts +27 -1
  19. package/kit/hooks/hooks/approval-gate/standing-history.ts +90 -0
  20. package/kit/hooks/hooks/approval-gate/standing-sql.ts +58 -0
  21. package/kit/hooks/hooks/approval-gate/transcripts.ts +11 -5
  22. package/kit/hooks/hooks/lane-start-guard/decision.ts +74 -0
  23. package/kit/hooks/hooks/lane-start-guard/index.ts +36 -0
  24. package/kit/hooks/hooks/merge-guard/always-ask.ts +2 -2
  25. package/kit/hooks/hooks/merge-guard/approval.ts +25 -13
  26. package/kit/hooks/hooks/merge-guard/change-id.ts +34 -0
  27. package/kit/hooks/hooks/merge-guard/decision.ts +423 -35
  28. package/kit/hooks/hooks/merge-guard/pane-input.ts +61 -0
  29. package/kit/hooks/hooks/merge-guard/pane-process-info.ts +38 -0
  30. package/kit/hooks/hooks/merge-guard/run.ts +110 -28
  31. package/kit/hooks/hooks/merge-guard/standing-approval.ts +15 -78
  32. package/kit/hooks/hooks/plain-question/form.ts +89 -0
  33. package/kit/hooks/hooks/plain-question/index.ts +44 -4
  34. package/kit/hooks/hooks/plain-question/recommendation.ts +73 -0
  35. package/kit/hooks/hooks/plain-question/run.ts +14 -7
  36. package/kit/hooks/hooks/shared/standing-mark.ts +104 -0
  37. package/kit/hooks/hooks.json +11 -2
  38. package/kit/review/agents/skill-review.md +3 -2
  39. package/kit/review/skills/pre-commit/SKILL.md +1 -1
  40. package/kit/rules/rules/kit/code-style.md +6 -6
  41. package/kit/rules/rules/kit/writing-for-agents.md +3 -0
  42. package/kit/workflow/skills/follow/SKILL.md +2 -3
  43. package/kit/workflow/skills/implement/SKILL.md +1 -1
  44. package/kit/workflow/skills/orchestrate/SKILL.md +120 -299
  45. package/kit/workflow/skills/orchestrate/references/appointments.md +21 -0
  46. package/kit/workflow/skills/orchestrate/references/close-lane.md +8 -7
  47. package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +8 -8
  48. package/kit/workflow/skills/orchestrate/references/events.md +46 -1
  49. package/kit/workflow/skills/orchestrate/references/herd-ask.md +10 -0
  50. package/kit/workflow/skills/orchestrate/references/lane-handover.md +9 -4
  51. package/kit/workflow/skills/orchestrate/references/lane-session.md +5 -4
  52. package/kit/workflow/skills/orchestrate/references/lane-setup.md +2 -1
  53. package/kit/workflow/skills/orchestrate/references/plan-commit.md +9 -2
  54. package/kit/workflow/skills/orchestrate/references/self-merge.md +26 -0
  55. package/kit/workflow/skills/orchestrate/references/stage-approval.md +50 -0
  56. package/kit/workflow/skills/orchestrate/references/standing-approval.md +38 -0
  57. package/kit/workflow/skills/orchestrate/references/start-lane.md +67 -0
  58. package/kit/workflow/skills/orchestrate/references/waiting.md +16 -0
  59. package/kit/workflow/skills/orchestrate/references/watch-without-monitor.md +13 -0
  60. package/kit/workflow/skills/plan/SKILL.md +3 -2
  61. package/package.json +2 -1
  62. package/recipes/README.md +1 -0
  63. package/recipes/jira-cli.md +1 -1
  64. package/recipes/sources/google-calendar-today.ts +8 -2
  65. package/recipes/sources/jira.ts +10 -2
  66. package/recipes/sources/linear.ts +9 -3
  67. package/recipes/sources/sentry.ts +8 -2
  68. package/recipes/sources.md +4 -2
  69. package/schemas/jackherd.schema.json +67 -0
  70. package/schemas/sources.schema.json +9 -0
  71. package/src/approval/checked.ts +60 -0
  72. package/src/approval/command.ts +16 -0
  73. package/src/check/command.ts +51 -6
  74. package/src/check/lint-pointers.ts +79 -0
  75. package/src/check/lint-shaped.ts +196 -0
  76. package/src/check/plan-structure-check.ts +116 -4
  77. package/src/check/report.ts +9 -3
  78. package/src/check/schema.ts +37 -0
  79. package/src/check/settings.ts +4 -0
  80. package/src/check/skill-budget-defaults.ts +77 -26
  81. package/src/check/skill-budgets.ts +58 -10
  82. package/src/cli.ts +2 -0
  83. package/src/config/personal-sources.ts +46 -10
  84. package/src/config/repository-identity.ts +40 -0
  85. package/src/config/schema.ts +53 -1
  86. package/src/context/command.ts +33 -2
  87. package/src/context/options.ts +33 -8
  88. package/src/context/usage-logs.ts +53 -0
  89. package/src/context/usage.ts +110 -0
  90. package/src/lane/base-command.ts +35 -0
  91. package/src/lane/base.ts +43 -0
  92. package/src/lane/close-check.ts +49 -15
  93. package/src/lane/close-command.ts +71 -11
  94. package/src/lane/close-options.ts +29 -8
  95. package/src/lane/close-survey.ts +13 -1
  96. package/src/lane/close.ts +51 -12
  97. package/src/lane/command.ts +21 -3
  98. package/src/lane/send-command.ts +32 -0
  99. package/src/lane/send-herdr.ts +77 -0
  100. package/src/lane/send-options.ts +23 -0
  101. package/src/lane/send.ts +90 -0
  102. package/src/lane/skill-hint.ts +27 -0
  103. package/src/lane/space-command.ts +30 -0
  104. package/src/lane/space.ts +49 -0
  105. package/src/lane/start-lane-hint.ts +18 -0
  106. package/src/plan-contract/base-check-git.ts +33 -0
  107. package/src/plan-contract/base-check.ts +17 -0
  108. package/src/plan-contract/command.ts +25 -4
  109. package/src/plan-format/plan-structure.ts +17 -5
  110. package/src/plans/ask-coordinator.ts +23 -13
  111. package/src/plans/board-rows.ts +9 -4
  112. package/src/plans/click-hint.ts +4 -0
  113. package/src/plans/command.ts +6 -5
  114. package/src/plans/ensure-panes.ts +14 -3
  115. package/src/plans/git.ts +14 -0
  116. package/src/plans/github.ts +19 -10
  117. package/src/plans/layout.ts +82 -33
  118. package/src/plans/live.ts +8 -12
  119. package/src/plans/plan-panes.ts +21 -4
  120. package/src/plans/selection.ts +29 -0
  121. package/src/plans/settings.ts +4 -0
  122. package/src/resume/command.ts +213 -0
  123. package/src/resume/decision.ts +102 -0
  124. package/src/resume/keeper.ts +65 -0
  125. package/src/resume/location.ts +22 -0
  126. package/src/resume/log.ts +27 -0
  127. package/src/resume/ports.ts +102 -0
  128. package/src/resume/record.ts +116 -0
  129. package/src/resume/targets.ts +84 -0
  130. package/src/resume/texts.ts +27 -0
  131. package/src/secrets/command.ts +15 -5
  132. package/src/sync/herdr-plugin.ts +2 -11
  133. package/src/sync/plan-ask-rules.ts +11 -0
  134. package/src/sync/render.ts +3 -1
  135. package/src/vocabulary/index.ts +13 -0
  136. package/src/waiting/command.ts +13 -4
  137. package/src/watch/command.ts +177 -78
  138. package/src/watch/config-reload.ts +119 -0
  139. package/src/watch/lane-model.ts +11 -1
  140. package/src/watch/lane-progress.ts +70 -8
  141. package/src/watch/lane-report.ts +66 -11
  142. package/src/watch/main-checkout.ts +5 -1
  143. package/src/watch/merge-duties.ts +33 -0
  144. package/src/watch/merge-overlap-read.ts +69 -0
  145. package/src/watch/plan-views.ts +16 -4
  146. package/src/watch/reference-paths.ts +46 -5
  147. package/src/watch/report-questions.ts +5 -2
  148. package/src/watch/single-report.ts +3 -2
  149. package/src/watch/source-dry-run-command.ts +22 -4
  150. package/src/watch/source-reason.ts +58 -0
  151. package/src/watch/source-reload.ts +21 -17
  152. package/src/watch/source-run.ts +24 -7
  153. package/src/watch/sources.ts +9 -2
  154. package/src/watch/texts.ts +14 -3
  155. package/src/watch/waiting-line.ts +4 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.17.0
6
+
7
+ - **Jack resumes after a crash.** After a machine or herdr crash, herdr restored the agents but nobody restarted the watcher, and every lane whose command was cut off stood still. The watcher now keeps a record in `~/.jackherd/resume/`, and a second `[[startup]]` entry of the herdr plugin runs `jh resume`: it wakes the coordinator with a fixed `/orchestrate` line and every lane that was working with a fixed resume text, at most once per crash. Nothing resumes after a clean stop, for a record older than 12 hours, while a watcher runs, or when a target is ambiguous; `~/.jackherd/logs/resume.log` says why.
8
+ - **The herd finds its coordinator after a herdr restart.** `herd --ensure` ended with 0 when it found no coordinator, and with several unnamed agents in the main checkout it found none. It now exits with 3 in that case, the startup hook keeps waiting for exactly those repositories, and the coordinator is the one agent whose tab holds a pane labelled `herd`.
9
+ - **A failed source says why.** A source's command may print one stdout line `jackherd-reason: <reason>`; the watcher shows the last such line as `exit 1: <reason>`, with the values of the source's `secrets`, URL credentials and URL query values masked, control characters dropped and the text cut to 160 characters. stderr is still never read. The recipe scripts print such a line for a missing variable, an HTTP status or a failed tool call.
10
+ - **A personal source can name its repositories.** `"repos": ["owner/name"]` (one to twenty) in `~/.jackherd/sources.json` runs the source only where the main checkout's `origin` names one of them; elsewhere the watcher notes `<name>: not for this repo`, or `<name>: no origin`. `jh watch source <name> --dry-run` of a left-out source prints the note and exits 2, and `jh secret list` still names its secrets. The repository's own config refuses `repos`.
11
+ - **`deploy`, `feedback`, `previews` and `checks` change without a restart.** The watcher reads them again with the sources. A change that applies prints `config reloaded: <parts>`; an invalid or deleted config keeps every value and says `config not reloaded: <file>: <reason>` once. A part switched on starts from a baseline, so only what happens after the reload is reported.
12
+ - **Writing SQL against stage under a standing approval.** With `guard.approvals.standing.tools` naming an SQL tool as `stage` (production, any other target and any name saying `prod` are refused), a single insert, update or delete passes without a dialog while you approved the environment (at most 24 hours) and Jack marked that exact call checked with `jh approval checked <id> <hash>`. Each pass leaves a line in the board's history with the lane, the kind of statement and its first table, never a value. A select, a schema change or several statements still ask you. `jh approval show` prints a held call for Jack's reading.
13
+ - **A question without its recommendation is sent back.** The first option of a dialog must end in `(recommended)` (`(Empfohlen)` in German); a single choice marks exactly one option, a multi-select marks its first and may mark more. A question that offers a label a guard reads word for word, such as the self-merge or standing approval, needs no mark and must not carry one.
14
+ - **The coordinator's rules are checked in code.** A dialog needs the paw in its header chip, at most 15 words in the question and 5 in a label; `lane-start-guard` refuses `herdr agent start <lane>` from the main checkout while no board row names the lane; `jh lane send` sends the prompt once and tells a stalled lane from a working one; `jh lane base <plan>` prints the base ref; `jh plan check` prints a `base:` line and exits 3 for a plan missing on the base branch; after `merged #n` the watcher names the checks each rebasing lane runs.
15
+ - **The merge guard closes four more ways around it.** A merge through `gh api graphql` or `curl` against the GitHub API asks; a self-merge approval is bound to the change you saw (the patch id), so a push after the answer asks again, and a pull request that changes the guard itself always asks; `herdr agent send-keys` and `prompt` into the coordinator ask. `herdr pane send-text` and `send-keys` always ask, and `herdr pane run` passes only into a plain pane that is not the herd, with a single-line text. The README says what the guards are: they catch mistakes, not intent.
16
+ - **`jh check` fails on open boxes of the phase a lane builds.** The phase comes from the lane's board row; `check.openBoxes` is `fail` (default), `warn` or `off`. A row marked `(Teil)` is left alone. After `merged #<n>` the watcher says once when a phase merged with open boxes.
17
+ - **`jh context --usage` counts a lane's tokens.** `jh context --usage [--since 24h|7d|<date>] [<name>] [--coordinator]` prints the input, cache and output tokens of a lane's sessions and subagents as one line of JSON.
18
+ - **A half-closed lane closes.** `jh lane close --branch <name>` finishes a lane whose worktree is already gone, and a detached worktree closes when the base branch holds its head. `jh lane space [--min-gb <n>]` refuses a new lane below `lanes.minFreeGb` free disk space (5 GB by default).
19
+ - **Board lines are written at once and reach `main` bundled.** History lines, waiting rows and appointments go into the main checkout's board immediately, where the herd reads them, and travel to the base with the next documentation pull request instead of one of their own. The herd also drops a closed lane without waiting for that merge.
20
+ - **A shorter orchestrate skill.** Rules that only some wake-ups need moved into references, and the watcher appends `· read <skill>/references/<file>` to the event that needs one. Word budgets are targets with ceilings that may only fall, and `jh check` prints `over target` hints. Run `jh sync` to get the new texts.
21
+ - **Fewer false alarms from the watcher.** "Keine Rückfragen." no longer counts as a question, the model alarm measures against the row of the phase the lane works on, and a phase whose box stayed open after its merge is not reported as ready.
22
+ - **The herd draws more calmly.** A source's entries block has a head with a count that folds, a resting lane's badge keeps its padding until the name would be cut, and exactly one line per working lane shimmers: its activity line, else its running task, else its first subagent line.
23
+ - **`jh check` warns on rules that belong in your linter.** A rule block you added or changed in `**/rules/**/*.md`, `CLAUDE.md` or `AGENTS.md` that carries a banned import path, a literal comparison, a comment form or a banned call, and names no lint rule, shows as `Lint pointers`. Name the rule with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where it stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs never read. Files `jh sync` installed are not read.
24
+ - **The kit asks for the linter first.** `/jh-plan` asks of every rule it writes whether your linter can check it, `skill-review` has a seventh lens for rules that could be lint rules, and the kit's code style rule shrinks by about eighty words. Run `jh sync` to get the new texts.
25
+ - **macOS and Linux.** The system requirements are written down in INSTALL.md, and the orchestrate reference names the macOS form of the working-directory check.
26
+
5
27
  ## 1.16.0
6
28
 
7
29
  - **Sources of your own.** A source that belongs to you alone, such as your calendar, goes into `~/.jackherd/sources.json` instead of the repository's config: same shape as `watch.sources`. Its command runs in `~/.jackherd/`, not in the repository, in every repository where you start the watcher, and for nobody else. The watcher, the herd and `jh watch source <name> --dry-run` take it up after the repository's sources. If the repository has a source of the same name, the repository's wins and yours is skipped with a note. A broken personal file never stops the watcher. The file has a JSON schema, `schemas/sources.schema.json`.
package/INSTALL.md CHANGED
@@ -19,6 +19,7 @@ If the user wants the kit in the repository, shared with the team, run `bunx jac
19
19
 
20
20
  ## Requirements
21
21
 
22
+ - Linux or macOS; on Windows, through WSL. `git`, `sh`, `ps` and `tty` must be there: both systems bring them.
22
23
  - [Bun](https://bun.sh) 1.3 or later, and Claude Code.
23
24
  - Optional: [herdr](https://herdr.dev) for parallel lanes (`/jh-orchestrate`, the watcher, agent states and context fill in the herd), and `gh` for pull requests and checks in the herd and the watcher.
24
25
 
@@ -54,7 +55,7 @@ When `init` creates a new config, it asks:
54
55
  | Use it when | the team shares the kit through the repository | the repository must stay untouched, not even untracked files |
55
56
  | Lives in | the project's `.claude/` | `~/.claude/` |
56
57
  | Skill names | `/plan`, `/implement`, `/orchestrate`, … | `/jh-plan`, `/jh-implement`, `/jh-orchestrate`, … |
57
- | Hooks | `git-guard`, `watch-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `plain-question`, `sent-file`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
58
+ | Hooks | `git-guard`, `watch-guard`, `lane-start-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `plain-question`, `sent-file`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
58
59
  | Config | `.jackherd/config.json` | `~/.jackherd/config.json`, used where a repository has none |
59
60
  | Update | `jh sync` | `jh update` |
60
61
 
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p>
2
- <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.16.0/assets/branding/jackherd-logo-outline.svg" width="340">
2
+ <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.17.0/assets/branding/jackherd-logo-outline.svg" width="340">
3
3
  <br>
4
4
  </p>
5
5
 
6
- [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg">
6
+ [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
@@ -15,27 +15,27 @@ For developers who'd rather decide than supervise.
15
15
  curl -fsSL https://unpkg.com/jackherd/install.sh | sh
16
16
  ```
17
17
 
18
- Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg">
18
+ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.16.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
21
+ <img src="https://unpkg.com/jackherd@1.17.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
22
22
  <br>
23
- <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg">
23
+ <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
26
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
27
27
 
28
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.</p>
28
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Commits, pushes and merges wait for your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours. The guards catch mistakes, not intent; a hard boundary takes branch protection.</p>
29
29
 
30
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
31
31
 
32
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
32
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
33
33
 
34
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
35
35
 
36
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
36
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
37
37
 
38
- <p><img src="https://unpkg.com/jackherd@1.16.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg"></p>
38
+ <p><img src="https://unpkg.com/jackherd@1.17.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg"></p>
39
39
 
40
40
  ## Quickstart
41
41
 
@@ -71,22 +71,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
71
71
  /jh-orchestrate docs/plans/board-<name>/board.md
72
72
  ```
73
73
 
74
- By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg">
74
+ Linux and macOS are supported (Windows through WSL); the requirements are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md). By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
75
75
 
76
76
  ## Follow anything outside GitHub
77
77
 
78
78
  Whatever you check by hand, Jack can watch: tickets, error logs, status pages, deploys. One `/jh-follow`, one yes, and every change comes to you like this:
79
79
 
80
- <img src="https://unpkg.com/jackherd@1.16.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
80
+ <img src="https://unpkg.com/jackherd@1.17.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
81
81
 
82
- The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.16.0/assets/icons/space.svg">
82
+ The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
83
83
 
84
84
  ## In detail
85
85
 
86
86
  <details>
87
87
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
88
88
 
89
- <img src="https://unpkg.com/jackherd@1.16.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
89
+ <img src="https://unpkg.com/jackherd@1.17.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
90
90
 
91
91
  | On his own | After your approval | Never |
92
92
  | --- | --- | --- |
@@ -108,7 +108,7 @@ A project install adds two hooks to Claude Code's [permission rules](https://cod
108
108
 
109
109
  `jh sync` also adds ask rules for check reruns and `gh api` writes.
110
110
 
111
- A merge passes without a question on green checks after self-merge, or under a standing approval: one plan's lanes until a time you pick, at most 24 hours; workflows, migrations, env files and the guards' own files still ask. A pull request whose files all match `merge.withoutChecks`, such as docs only, skips question and checks, unless a file lies on a path that always asks.
111
+ A merge passes without a question on green checks after self-merge, or under a standing approval: one plan's lanes until a time you pick, at most 24 hours; workflows, migrations, env files and the guards' own files still ask. A write against stage works the same way: with `guard.approvals.standing.tools` naming an SQL tool as `stage` (never production), you approve the environment for up to 24 hours, Jack reads each statement and marks it checked with `jh approval checked`, and a single insert, update or delete then runs without a dialog, leaving a line in the board's history that names the table but no value. A select, a schema change or several statements still ask you. A pull request whose files all match `merge.withoutChecks`, such as docs only, skips question and checks, unless a file lies on a path that always asks.
112
112
 
113
113
  `"merge": { "openAsDraft": true }` has lanes open every pull request as a draft, so pushes while it is worked on start no workflow that skips drafts; Jack runs `gh pr ready` before the merge. The guard never merges a draft, and a check in `guard.requiredChecks` counts only with a run that succeeded, not with the skipped run a draft leaves behind; without that list, at least one run must have succeeded.
114
114
 
@@ -121,7 +121,7 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
121
121
 
122
122
  A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
123
123
 
124
- <img src="https://unpkg.com/jackherd@1.16.0/docs/skills-dark.svg" alt="Your own plan or an issue is adopted (Jack adds what is missing, you see the diff) and goes to /jh-orchestrate; /jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement, which your own skill can replace; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
124
+ <img src="https://unpkg.com/jackherd@1.17.0/docs/skills-dark.svg" alt="Your own plan or an issue is adopted (Jack adds what is missing, you see the diff) and goes to /jh-orchestrate; /jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement, which your own skill can replace; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
125
125
 
126
126
  | Skill | Does |
127
127
  | --- | --- |
@@ -205,7 +205,7 @@ Jack doesn't watch the terminals himself. `jh watch`, which he starts, wakes him
205
205
 
206
206
  `/jh-follow PROJ-123` adds anything a command can print as JSON: a ticket, an issue, error logs, a status page. It checks the tool and its login, shows you a dry run and writes the source to `watch.sources` after your yes. A token stays in the environment, read by a small Bun script, never in an argument. Each source runs on its own interval, kept across a restart of the watcher. Recipes for Jira, Linear, GitHub issues and Projects, Sentry, status pages and today's Google Calendar appointments come with it. The reports need herdr; a source can be set up and dry-run without it.
207
207
 
208
- A source that is yours alone, such as your calendar, goes into `~/.jackherd/sources.json` instead: it runs in every repository where you start the watcher, and for nobody else. Its secrets live in the system keychain: `jh secret set <NAME>` asks for the value hidden, and only the command of the source that lists it receives it. The watcher takes up a changed source without a restart, and any source can show its entries in a block of their own under Your turn with `"show": "entries"`.
208
+ A source that is yours alone, such as your calendar, goes into `~/.jackherd/sources.json` instead: it runs for nobody else, in every repository where you start the watcher, or only in the ones its `repos` list (`owner/name`) names. Its secrets live in the system keychain: `jh secret set <NAME>` asks for the value hidden, and only the command of the source that lists it receives it. The watcher takes up a changed source, and a changed `deploy`, `feedback`, `previews` or `checks`, without a restart. A source that fails can say why: a line `jackherd-reason: <reason>` on its stdout shows as `exit 1: <reason>`, with your secrets masked. Any source can show its entries in a block of their own under Your turn with `"show": "entries"`.
209
209
 
210
210
  </details>
211
211
 
@@ -227,12 +227,13 @@ Each phase in the plan names its model: the planning model (Opus by default) whe
227
227
  | `jh check` | types, lint and tests of what you touched, plus the plan and board checks (`--skip-slow`, `--parallel <plan>`) |
228
228
  | `jh herd` | the live view (`--once`, `--lane <name>`) |
229
229
  | `jh watch` | the watcher Jack runs |
230
+ | `jh resume` | after herdr restarts, wakes the coordinator and the lanes that worked when the machine crashed; runs once per crash and only when the watcher's record shows no `stoppedAt`. Stopping the watcher before quitting herdr prevents it. |
230
231
  | `jh secret` | keeps a personal source's secrets in the system keychain (`set <NAME>`, `list`, `delete <NAME>`); `list` shows which are set, never a value |
231
232
  | `jh lane prompt <lane>` | writes a lane's first prompt from its board row and its plan (`--out`, `--handover`); your additions below the marker line at its end stay |
232
233
  | `jh lane close <lane>` | removes a finished lane: agent, worktree, herdr workspace and branch, and marks the board; first checks that nothing is lost, and refuses with every reason; `--dry-run` only checks. Claude Code asks before each call |
233
234
  | `jh preview <file>` | renders a Markdown file through the GitHub API into a preview in GitHub and npm style |
234
235
  | `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>…` or `close --all` once you have) |
235
- | `jh context` | how full an agent's context is, as JSON |
236
+ | `jh context` | how full an agent's context is, as JSON; `--usage [--since 24h\|7d\|<date>]` the tokens a lane used instead (Jack's with `--coordinator`) |
236
237
  | `jh config` | the commands and the base branch the repository uses; a missing value goes to stderr with the line to set, and `jh config <key>` for an unset value exits 3 |
237
238
 
238
239
  Without a global install, run them as `bunx jackherd <command>`. The config is `.jackherd/config.json` in a project and `~/.jackherd/config.json` globally, checked against [one schema](https://unpkg.com/jackherd/schemas/jackherd.schema.json), which your editor completes in the project config. What usually needs setting:
@@ -249,7 +250,7 @@ Without a global install, run them as `bunx jackherd <command>`. The config is `
249
250
  }
250
251
  ```
251
252
 
252
- `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
253
+ `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. It also reads the rule blocks you added or changed in `**/rules/**/*.md`, `CLAUDE.md` and `AGENTS.md`, and warns (`Lint pointers`) where a block carries a rule your linter could check (a banned import path, a literal comparison, a comment form, a banned call) and names no lint rule. Name it with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where the rule stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs that are never read. `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
253
254
 
254
255
  </details>
255
256
 
@@ -23,6 +23,10 @@ var STANDING_APPROVAL_TEXTS = {
23
23
  [PLAN_LANGUAGE.en]: { approve: "Approve standing", until: "until" },
24
24
  [PLAN_LANGUAGE.de]: { approve: "Stehend freigeben", until: "bis" }
25
25
  };
26
+ var RECOMMENDED_MARK = {
27
+ [PLAN_LANGUAGE.en]: "(recommended)",
28
+ [PLAN_LANGUAGE.de]: "(Empfohlen)"
29
+ };
26
30
  var LANE_PROMPT_OPENING = {
27
31
  [PLAN_LANGUAGE.en]: "You are the lane",
28
32
  [PLAN_LANGUAGE.de]: "Du bist die Spur"
@@ -96,6 +100,7 @@ var PLAN_VOCABULARY = {
96
100
  phase: "Phase",
97
101
  noReviewGate: "Kein Review-Gate",
98
102
  laneClosed: "abger\xE4umt",
103
+ partialPhase: "(Teil)",
99
104
  openSubagents: "offene Sub-Agenten",
100
105
  sideOrderMarker: "dazu (User",
101
106
  startsNow: ["sofort"],
@@ -130,6 +135,7 @@ var PLAN_VOCABULARY = {
130
135
  phase: "Phase",
131
136
  noReviewGate: "No review gate",
132
137
  laneClosed: "cleaned up",
138
+ partialPhase: "(part)",
133
139
  openSubagents: "open subagents",
134
140
  sideOrderMarker: "also (User",
135
141
  startsNow: ["now", "immediately"],
@@ -184,6 +190,7 @@ export {
184
190
  ADDRESS_FORM,
185
191
  SELF_MERGE_LABEL,
186
192
  STANDING_APPROVAL_TEXTS,
193
+ RECOMMENDED_MARK,
187
194
  LANE_PROMPT_OPENING,
188
195
  APPROVAL_TEXTS,
189
196
  APPOINTMENT_VOCABULARY,
package/dist/config.d.ts CHANGED
@@ -8,6 +8,10 @@ declare const TERMINAL_ADAPTER: {
8
8
  readonly herdr: "herdr";
9
9
  };
10
10
  type TerminalAdapter = (typeof TERMINAL_ADAPTER)[keyof typeof TERMINAL_ADAPTER];
11
+ /** The environments a standing approval may cover; production is never one. */
12
+ declare const STANDING_ENVIRONMENT: {
13
+ readonly stage: "stage";
14
+ };
11
15
  declare const MODEL_ROLE: {
12
16
  readonly plan: "plan";
13
17
  readonly implement: "implement";
@@ -75,6 +79,7 @@ declare const sourceSchema: z.ZodObject<{
75
79
  reportOn: z.ZodOptional<z.ZodArray<z.ZodString>>;
76
80
  secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
81
  show: z.ZodOptional<z.ZodLiteral<"entries">>;
82
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
78
83
  }, z.core.$strict>;
79
84
  declare const jackherdConfigSchema: z.ZodObject<{
80
85
  $schema: z.ZodOptional<z.ZodString>;
@@ -114,6 +119,7 @@ declare const jackherdConfigSchema: z.ZodObject<{
114
119
  lanes: z.ZodPrefault<z.ZodObject<{
115
120
  copy: z.ZodDefault<z.ZodArray<z.ZodString>>;
116
121
  lockFile: z.ZodOptional<z.ZodString>;
122
+ minFreeGb: z.ZodOptional<z.ZodNumber>;
117
123
  }, z.core.$strict>>;
118
124
  models: z.ZodDefault<z.ZodRecord<z.ZodEnum<{
119
125
  review: "review";
@@ -254,6 +260,7 @@ declare const jackherdConfigSchema: z.ZodObject<{
254
260
  reportOn: z.ZodOptional<z.ZodArray<z.ZodString>>;
255
261
  secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
256
262
  show: z.ZodOptional<z.ZodLiteral<"entries">>;
263
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
257
264
  }, z.core.$strict>>>;
258
265
  }, z.core.$strict>>;
259
266
  guard: z.ZodPrefault<z.ZodPipe<z.ZodObject<{
@@ -264,6 +271,11 @@ declare const jackherdConfigSchema: z.ZodObject<{
264
271
  }, z.core.$strict>>;
265
272
  approvals: z.ZodPrefault<z.ZodObject<{
266
273
  waitMinutes: z.ZodDefault<z.ZodNumber>;
274
+ standing: z.ZodOptional<z.ZodObject<{
275
+ tools: z.ZodRecord<z.ZodString, z.ZodEnum<{
276
+ stage: "stage";
277
+ }>>;
278
+ }, z.core.$strict>>;
267
279
  }, z.core.$strict>>;
268
280
  alwaysAsk: z.ZodDefault<z.ZodArray<z.ZodString>>;
269
281
  requiredChecks: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -281,6 +293,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
281
293
  };
282
294
  approvals: {
283
295
  waitMinutes: number;
296
+ standing?: {
297
+ tools: Record<string, "stage">;
298
+ } | undefined;
284
299
  };
285
300
  }, {
286
301
  commands: string[];
@@ -290,6 +305,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
290
305
  };
291
306
  approvals: {
292
307
  waitMinutes: number;
308
+ standing?: {
309
+ tools: Record<string, "stage">;
310
+ } | undefined;
293
311
  };
294
312
  alwaysAsk: string[];
295
313
  requiredChecks?: string[] | undefined;
@@ -333,6 +351,19 @@ declare const jackherdConfigSchema: z.ZodObject<{
333
351
  reviewer: z.ZodDefault<z.ZodString>;
334
352
  }, z.core.$strict>>;
335
353
  skillBudgets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
354
+ openBoxes: z.ZodDefault<z.ZodEnum<{
355
+ fail: "fail";
356
+ warn: "warn";
357
+ off: "off";
358
+ }>>;
359
+ lintPointers: z.ZodOptional<z.ZodObject<{
360
+ level: z.ZodDefault<z.ZodEnum<{
361
+ fail: "fail";
362
+ warn: "warn";
363
+ off: "off";
364
+ }>>;
365
+ exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
366
+ }, z.core.$strict>>;
336
367
  looseTests: z.ZodDefault<z.ZodBoolean>;
337
368
  roots: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
338
369
  sourceFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -378,6 +409,7 @@ declare const personalSourcesSchema: z.ZodObject<{
378
409
  reportOn: z.ZodOptional<z.ZodArray<z.ZodString>>;
379
410
  secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
380
411
  show: z.ZodOptional<z.ZodLiteral<"entries">>;
412
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
381
413
  }, z.core.$strict>>>;
382
414
  }, z.core.$strict>;
383
415
  type PersonalSourcesFile = z.infer<typeof personalSourcesSchema>;
@@ -386,4 +418,4 @@ declare const parsePersonalSourcesFile: (raw: unknown, label: string) => Persona
386
418
  /** One `watch.sources` entry outside the config, such as a draft to try; invalid throws with `label` and the issues. */
387
419
  declare const parseSourceDraft: (raw: unknown, label: string) => z.infer<typeof sourceSchema>;
388
420
 
389
- export { COMMAND_KEYS, type CommandKey, DEFAULT_CONTEXT_WINDOW, DEFAULT_MODELS, DEFAULT_SKILL_PREFIX, type JackherdConfig, KIT_CORE, MODEL_ROLE, type ModelRole, type PersonalSourcesFile, SKILL_PREFIX, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parsePersonalSourcesFile, parseSourceDraft, personalSourcesSchema, renderConfigJsonSchema, renderSourcesJsonSchema };
421
+ export { COMMAND_KEYS, type CommandKey, DEFAULT_CONTEXT_WINDOW, DEFAULT_MODELS, DEFAULT_SKILL_PREFIX, type JackherdConfig, KIT_CORE, MODEL_ROLE, type ModelRole, type PersonalSourcesFile, SKILL_PREFIX, STANDING_ENVIRONMENT, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parsePersonalSourcesFile, parseSourceDraft, personalSourcesSchema, renderConfigJsonSchema, renderSourcesJsonSchema };
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PLAN_LANGUAGE,
3
3
  RESPONSE_ADDRESS
4
- } from "./chunk-4LUFR7RC.js";
4
+ } from "./chunk-N5FAOQZL.js";
5
5
 
6
6
  // src/config/json-schema.ts
7
7
  import { z as z3 } from "zod";
@@ -73,6 +73,15 @@ var publicContent = z.strictObject({
73
73
  reviewer: nonEmpty.default("public-content")
74
74
  });
75
75
  var skillBudgets = z.record(glob, z.number().int().positive());
76
+ var OPEN_BOXES_LEVEL = { fail: "fail", warn: "warn", off: "off" };
77
+ var LINT_POINTERS_LEVEL = { warn: "warn", fail: "fail", off: "off" };
78
+ var lintPointers = z.strictObject({
79
+ /** `warn` reports a block and lets the commit through; `fail` stops it; `off` skips the check. */
80
+ level: z.enum([LINT_POINTERS_LEVEL.warn, LINT_POINTERS_LEVEL.fail, LINT_POINTERS_LEVEL.off]).default(LINT_POINTERS_LEVEL.warn),
81
+ /** Rule files left unread, as globs from the project root; the kit copies in the lock file are always left out. */
82
+ exclude: z.array(glob).default([])
83
+ });
84
+ var DEFAULT_LINT_POINTERS = { level: LINT_POINTERS_LEVEL.warn, exclude: [] };
76
85
  var checkConfigSchema = z.strictObject({
77
86
  /**
78
87
  * Run once over the changed files, which are appended as arguments. Separate from `commands.lint`,
@@ -90,6 +99,17 @@ var checkConfigSchema = z.strictObject({
90
99
  * path and sets one for the project's own skills; a file with none is measured against the kit's default.
91
100
  */
92
101
  skillBudgets: skillBudgets.optional(),
102
+ /**
103
+ * What a lane's check does when the phase its board row names still has open boxes without `→ P<n>`: `fail` (the
104
+ * default) stops the commit, `warn` reports, `off` skips. A row carrying the `partialPhase` word (`(Teil)` in German) builds part of the phase and is
105
+ * left alone; a branch whose directory is no lane's is not checked.
106
+ */
107
+ openBoxes: z.enum([OPEN_BOXES_LEVEL.fail, OPEN_BOXES_LEVEL.warn, OPEN_BOXES_LEVEL.off]).default(OPEN_BOXES_LEVEL.fail),
108
+ /**
109
+ * Whether a new or changed block of a rule file (or `CLAUDE.md`, `AGENTS.md`) that a linter could check, such
110
+ * as a forbidden import path or call, must name its lint rule with `` `lint:<rule id>` `` (or `` `lint:none` `` and a reason).
111
+ */
112
+ lintPointers: lintPointers.optional(),
93
113
  /** Also run tests in directories that hold test files but belong to no package. */
94
114
  looseTests: z.boolean().default(false),
95
115
  /**
@@ -275,6 +295,9 @@ import { isAbsolute } from "path";
275
295
  var segmentsOf = (path) => path.split(/[\\/]/).filter((segment) => segment !== "" && segment !== ".");
276
296
  var isRepositoryDirectory = (value) => !isAbsolute(value) && segmentsOf(value).length > 0 && !segmentsOf(value).includes("..");
277
297
 
298
+ // src/config/repository-identity.ts
299
+ var REPOSITORY_NAME = /^[\w.-]+\/[\w.-]+$/;
300
+
278
301
  // src/config/tones.ts
279
302
  var TONE = {
280
303
  amber: "amber",
@@ -303,6 +326,7 @@ var DEFAULT_TONES = {
303
326
 
304
327
  // src/config/schema.ts
305
328
  var TERMINAL_ADAPTER = { none: "none", herdr: "herdr" };
329
+ var STANDING_ENVIRONMENT = { stage: "stage" };
306
330
  var MODEL_ROLE = {
307
331
  plan: "plan",
308
332
  implement: "implement",
@@ -482,7 +506,12 @@ var sourceSchema = z2.strictObject({
482
506
  */
483
507
  secrets: z2.array(z2.string().regex(SECRET_NAME, "capital letters, digits and `_`, starting with a letter")).max(10).refine((names) => new Set(names).size === names.length, "secret names must be unique").optional(),
484
508
  /** `entries` shows the source's entries open, in a herd block of its own; not for a ticket source, whose entries are badges. */
485
- show: z2.literal(SOURCE_SHOW.entries).optional()
509
+ show: z2.literal(SOURCE_SHOW.entries).optional(),
510
+ /**
511
+ * `owner/name` of the repositories a personal source runs in (matched without regard to case); without it every
512
+ * repository. Only in the personal sources file: the repository's own config is for that repository already.
513
+ */
514
+ repos: z2.array(z2.string().regex(REPOSITORY_NAME, "owner/name, such as acme/tool")).min(1).max(20).refine((repos) => new Set(repos.map((repo) => repo.toLowerCase())).size === repos.length, "repos must be unique").optional()
486
515
  }).refine(canChange, {
487
516
  message: "map needs updatedAt, a content field (title, author, body, url) or reportOn",
488
517
  path: ["map"]
@@ -500,12 +529,26 @@ var sourceSchema = z2.strictObject({
500
529
  path: ["reportOn"]
501
530
  });
502
531
  var hasUniqueNames = (sources) => new Set(sources.map(({ name }) => name)).size === sources.length;
532
+ var repositorySources = z2.array(sourceSchema).refine(hasUniqueNames, "source names must be unique").superRefine((sources, context) => {
533
+ sources.forEach((source, index) => {
534
+ if (source.repos === void 0) return;
535
+ context.addIssue({
536
+ code: "custom",
537
+ path: [index, "repos"],
538
+ message: "repos belongs in the personal sources file (~/.jackherd/sources.json): this config is for one repository"
539
+ });
540
+ });
541
+ });
503
542
  var coordinatorSchema = z2.strictObject({
504
543
  /** Tokens in the coordinator's context from which the watcher reports `coordinator warn`. */
505
544
  warnTokens: tokenCount.default(35e4),
506
545
  /** Tokens in the coordinator's context from which the watcher reports `coordinator alert`. */
507
546
  alertTokens: tokenCount.default(5e5)
508
547
  }).refine(warnBelowAlert, WARN_BELOW_ALERT);
548
+ var standingTool = z2.string().regex(
549
+ /^mcp__[A-Za-z0-9_-]+__execute_sql$/,
550
+ "a full MCP tool name ending in execute_sql, such as mcp__<server>__execute_sql"
551
+ ).refine((name) => !/prod/i.test(name), "a tool whose name says prod never runs under a standing approval");
509
552
  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");
510
553
  var checkFreePattern = z2.string().regex(/^[A-Za-z0-9_.*/-]+$/, "path words with * and ** only, such as docs/**/*.md").refine(
511
554
  (pattern) => !pattern.startsWith("/") && !pattern.split("/").includes(".."),
@@ -551,7 +594,13 @@ var guardFields = z2.strictObject({
551
594
  }).prefault({}),
552
595
  approvals: z2.strictObject({
553
596
  /** 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. */
554
- waitMinutes: z2.number().int().min(1).max(9).default(8)
597
+ waitMinutes: z2.number().int().min(1).max(9).default(8),
598
+ /**
599
+ * The calls a standing approval of the user may let through once the coordinator marked them checked: each tool
600
+ * by its full name and the environment it writes to, only `stage`. A tool whose name says `prod` is refused, as the
601
+ * hook refuses it under a broken config; which project a server reaches is the project's start script's to say.
602
+ */
603
+ standing: z2.strictObject({ tools: z2.record(standingTool, z2.enum(Object.values(STANDING_ENVIRONMENT))) }).optional()
555
604
  }).prefault({}),
556
605
  /**
557
606
  * Paths that always ask, on top of the ones jackherd brings (`.github/**`, `**\/migrations/**`, `**\/.env*`,
@@ -638,7 +687,9 @@ var jackherdConfigSchema = z2.strictObject({
638
687
  /** Untracked files copied into every new worktree; empty means the files `.worktreeinclude` lists. */
639
688
  copy: z2.array(laneCopyPath).default([]),
640
689
  /** The dependency lock file a rebase takes from the base branch and regenerates, never merges by hand; detected from the repository when left out. */
641
- lockFile: lockFilePath.optional()
690
+ lockFile: lockFilePath.optional(),
691
+ /** Free gigabytes the disk must hold before `jh lane space` lets a new lane install its dependencies; 5 when left out. */
692
+ minFreeGb: z2.number().positive().optional()
642
693
  }).prefault({}),
643
694
  models: z2.record(z2.enum(Object.values(MODEL_ROLE)), modelId).default(DEFAULT_MODELS),
644
695
  terminal: z2.strictObject({ adapter: z2.enum(Object.values(TERMINAL_ADAPTER)).default(TERMINAL_ADAPTER.none) }).prefault({}),
@@ -680,7 +731,7 @@ var jackherdConfigSchema = z2.strictObject({
680
731
  parallelTasks: parallelTasksSchema.prefault({}),
681
732
  taskSignalMinutes: z2.number().int().positive().default(DEFAULT_TASK_SIGNAL_MINUTES),
682
733
  previews: previewsSchema.optional(),
683
- sources: z2.array(sourceSchema).refine(hasUniqueNames, "source names must be unique").default([])
734
+ sources: repositorySources.default([])
684
735
  }).prefault({}),
685
736
  /** Read by the merge guard hook, which asks before the listed commands run, and by `/orchestrate`. */
686
737
  guard: guardSchema.prefault({}),
@@ -719,6 +770,7 @@ export {
719
770
  KIT_CORE,
720
771
  MODEL_ROLE,
721
772
  SKILL_PREFIX,
773
+ STANDING_ENVIRONMENT,
722
774
  TERMINAL_ADAPTER,
723
775
  isModelRole,
724
776
  isTerminalAdapter,
@@ -32,6 +32,11 @@ declare const STANDING_APPROVAL_TEXTS: Record<PlanLanguage, {
32
32
  approve: string;
33
33
  until: string;
34
34
  }>;
35
+ /**
36
+ * The mark at the end of the first option's label that says it is the recommendation. The plain question hook sends
37
+ * a dialog back whose first option lacks it, so it is fixed per language and not a config value.
38
+ */
39
+ declare const RECOMMENDED_MARK: Record<PlanLanguage, string>;
35
40
  /**
36
41
  * The words a lane prompt opens with, followed by the lane's name. The ask hint hook and the project's lane exception
37
42
  * recognise a lane by them, so they are fixed per language and not a config value.
@@ -133,6 +138,8 @@ type PlanVocabulary = {
133
138
  noReviewGate: string;
134
139
  /** Marks a lane as closed when its name cell contains it. */
135
140
  laneClosed: string;
141
+ /** Stands in a board row's phases cell when that pull request builds only part of the phase. */
142
+ partialPhase: string;
136
143
  /** Opens the line a lane's hand-over file must hold as `<word>: 0`, so the coordinator hands over only after a wave. */
137
144
  openSubagents: string;
138
145
  /** Words a starts-after cell begins with when the lane may start at once. */
@@ -460,13 +467,12 @@ declare const tallyPhases: (markdown: string, language: PlanLanguage) => PhaseTa
460
467
  type PhaseWithoutGate = PhaseHeading;
461
468
  /** A row of the lanes table (1-based line) whose phases cell names no phase id. */
462
469
  type RowWithoutPhase = OpenLaneRow;
463
- /**
464
- * A `### Phase` heading (1-based line) whose tasks are all ticked on the base branch while its own section still
465
- * has open checkboxes not moved on with `→ P<n>` (`openLines`, 1-based).
466
- */
467
- type MergedPhaseWithOpenBoxes = PhaseWithoutGate & {
470
+ /** A `### Phase` heading (1-based line) whose own section has open checkboxes not moved on with `→ P<n>` (`openLines`, 1-based). */
471
+ type PhaseWithOpenBoxes = PhaseWithoutGate & {
468
472
  openLines: number[];
469
473
  };
474
+ /** A `PhaseWithOpenBoxes` whose tasks are all ticked on the base branch. */
475
+ type MergedPhaseWithOpenBoxes = PhaseWithOpenBoxes;
470
476
  /** The ids of the `### Phase` headings of a plan. */
471
477
  declare const phaseIdsOfPlan: (markdown: string, language: PlanLanguage) => Set<string>;
472
478
  /**
@@ -485,6 +491,12 @@ declare const hasLanesTable: (markdown: string, language: PlanLanguage) => boole
485
491
  * with an open task there, is still being worked on.
486
492
  */
487
493
  declare const mergedPhasesWithOpenBoxes: (markdown: string, base: string, language: PlanLanguage) => MergedPhaseWithOpenBoxes[];
494
+ /**
495
+ * The phase `id` of `markdown` when its own section still has open checkboxes, gate boxes included, that no `→ P<n>`
496
+ * at their end moves on; null when it has none or the plan lacks the phase. Unlike `mergedPhasesWithOpenBoxes` it
497
+ * does not ask whether the phase counts as merged: the caller knows which phase a branch builds.
498
+ */
499
+ declare const openBoxesOfPhase: (markdown: string, language: PlanLanguage, id: string) => PhaseWithOpenBoxes | null;
488
500
  /**
489
501
  * The changed rows of open lanes whose phases cell names no phase id (`P10c`), so neither the view nor the watcher
490
502
  * can tie that step to a phase.
@@ -614,4 +626,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
614
626
  */
615
627
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
616
628
 
617
- 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, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, 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, parseModelTable, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
629
+ 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, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithOpenBoxes, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RECOMMENDED_MARK, 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, openBoxesOfPhase, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -8,6 +8,7 @@ import {
8
8
  PLAN_LANGUAGE,
9
9
  PLAN_STATUS,
10
10
  PLAN_VOCABULARY,
11
+ RECOMMENDED_MARK,
11
12
  RESPONSE_ADDRESS,
12
13
  SELF_MERGE_LABEL,
13
14
  STANDING_APPROVAL_TEXTS,
@@ -17,7 +18,7 @@ import {
17
18
  phaseHeading,
18
19
  sectionHeading,
19
20
  terminalSafe
20
- } from "./chunk-4LUFR7RC.js";
21
+ } from "./chunk-N5FAOQZL.js";
21
22
 
22
23
  // src/plan-format/lanes.ts
23
24
  import { posix } from "path";
@@ -830,6 +831,10 @@ var mergedPhasesWithOpenBoxes = (markdown, base, language) => {
830
831
  };
831
832
  return phaseSections(markdown, language).filter(({ id, openLines }) => openLines.length > 0 && isMerged(id)).map(({ line, id, title, openLines }) => ({ line, id, title, openLines }));
832
833
  };
834
+ var openBoxesOfPhase = (markdown, language, id) => {
835
+ const section = phaseSections(markdown, language).find((candidate) => candidate.id === id);
836
+ return section && section.openLines.length > 0 ? { line: section.line, id: section.id, title: section.title, openLines: section.openLines } : null;
837
+ };
833
838
  var boardRowsWithoutPhase = (markdown, language, previousLines) => changedOpenLaneRows(markdown, language, previousLines).filter(
834
839
  ({ phases }) => phaseIdsOfCell(phases, language).length === 0
835
840
  );
@@ -920,6 +925,7 @@ export {
920
925
  PLAN_STATUS,
921
926
  PLAN_VOCABULARY,
922
927
  PROSE_REASON,
928
+ RECOMMENDED_MARK,
923
929
  RESPONSE_ADDRESS,
924
930
  SELF_MERGE_LABEL,
925
931
  SHORT_NAME_LIMIT,
@@ -955,6 +961,7 @@ export {
955
961
  misplacedDeferrals,
956
962
  newPhasesWithoutReviewGate,
957
963
  onlyDocumentationOpen,
964
+ openBoxesOfPhase,
958
965
  parallelGroups,
959
966
  parallelGroupsOfPlan,
960
967
  parseDependencyTable,
@@ -82,3 +82,4 @@ dependencies: missing no dependency table: phases run one after another
82
82
  | 0 | the contract is met (a missing dependency table is a note, not a gap) |
83
83
  | 1 | at least one part is missing or malformed |
84
84
  | 2 | wrong command line: no subcommand, unknown subcommand, no file, unreadable file, unreadable config |
85
+ | 3 | the contract is met, but the plan or a board that links it is not on the base branch (a `base:` line names each path); the coordinator commits it first (`/orchestrate` § 2 step 0), after which the check counts as done |
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.16.0"
3
+ version = "1.17.0"
4
4
  min_herdr_version = "0.8.2"
5
5
  description = "Show the herd, the state of a board's lanes, in a herdr pane"
6
6
  platforms = ["linux", "macos"]
@@ -20,3 +20,6 @@ command = ["bun", "open-plans.ts"]
20
20
 
21
21
  [[startup]]
22
22
  command = ["bun", "restore-plans.ts"]
23
+
24
+ [[startup]]
25
+ command = ["bun", "resume.ts"]