jackherd 0.0.1 → 1.0.1

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 (250) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +38 -0
  4. package/README.md +1122 -3
  5. package/assets/branding/README.md +62 -0
  6. package/assets/branding/jackherd-icon-256.png +0 -0
  7. package/assets/branding/jackherd-icon-512.png +0 -0
  8. package/assets/branding/jackherd-icon-64.png +0 -0
  9. package/assets/branding/jackherd-icon-small-32.png +0 -0
  10. package/assets/branding/jackherd-icon-small-64.png +0 -0
  11. package/assets/branding/jackherd-icon-small.svg +1 -0
  12. package/assets/branding/jackherd-icon.svg +1 -0
  13. package/assets/branding/jackherd-logo-1024.png +0 -0
  14. package/assets/branding/jackherd-logo-512.png +0 -0
  15. package/assets/branding/jackherd-logo-dark-1024.png +0 -0
  16. package/assets/branding/jackherd-logo-dark-512.png +0 -0
  17. package/assets/branding/jackherd-logo-dark.svg +1 -0
  18. package/assets/branding/jackherd-logo-mark-1024.png +0 -0
  19. package/assets/branding/jackherd-logo-mark-512.png +0 -0
  20. package/assets/branding/jackherd-logo-mark.svg +1 -0
  21. package/assets/branding/jackherd-logo.svg +1 -0
  22. package/dist/chunk-R6MGHCD7.js +93 -0
  23. package/dist/config.d.ts +167 -0
  24. package/dist/config.js +401 -0
  25. package/dist/plan-format.d.ts +325 -0
  26. package/dist/plan-format.js +636 -0
  27. package/docs/lanes.mmd +9 -0
  28. package/docs/lanes.svg +1 -0
  29. package/docs/skills.mmd +10 -0
  30. package/docs/skills.svg +1 -0
  31. package/docs/workflow.svg +172 -0
  32. package/herdr-plugin/herdr-plugin.toml +22 -0
  33. package/herdr-plugin/open-plans.ts +43 -0
  34. package/herdr-plugin/restore-plans.ts +107 -0
  35. package/install.sh +157 -0
  36. package/kit/hooks/hooks/git-guard/decision.ts +438 -0
  37. package/kit/hooks/hooks/git-guard/index.ts +38 -0
  38. package/kit/hooks/hooks/git-guard/shell.ts +407 -0
  39. package/kit/hooks/hooks/watch-guard/background.ts +36 -0
  40. package/kit/hooks/hooks/watch-guard/decision.ts +53 -0
  41. package/kit/hooks/hooks/watch-guard/index.ts +11 -0
  42. package/kit/hooks/hooks/watch-guard/shell-input.ts +32 -0
  43. package/kit/hooks/hooks/watch-guard/starts.ts +154 -0
  44. package/kit/hooks/hooks/watch-guard/text-fallback.ts +40 -0
  45. package/kit/hooks/hooks.json +30 -0
  46. package/kit/hooks/statusline.sh +72 -0
  47. package/kit/review/agents/devops.md +11 -0
  48. package/kit/review/agents/public-content.md +39 -0
  49. package/kit/review/agents/security-audit.md +11 -0
  50. package/kit/review/agents/uncle-bob.md +11 -0
  51. package/kit/review/skills/devops/SKILL.md +50 -0
  52. package/kit/review/skills/diagnose/SKILL.md +140 -0
  53. package/kit/review/skills/diagnose/references/feedback-loops.md +59 -0
  54. package/kit/review/skills/diagnose/scripts/hitl-loop.template.sh +71 -0
  55. package/kit/review/skills/pre-commit/SKILL.md +100 -0
  56. package/kit/review/skills/review/SKILL.md +43 -0
  57. package/kit/review/skills/security-audit/SKILL.md +65 -0
  58. package/kit/review/skills/uncle-bob/SKILL.md +43 -0
  59. package/kit/rules/rules/kit/arc42.md +89 -0
  60. package/kit/rules/rules/kit/architecture-files.md +87 -0
  61. package/kit/rules/rules/kit/code-style.md +159 -0
  62. package/kit/rules/rules/kit/pull-requests.md +59 -0
  63. package/kit/rules/rules/kit/testing.md +91 -0
  64. package/kit/rules/rules/kit/writing-for-agents.md +110 -0
  65. package/kit/workflow/agents/explore.md +25 -0
  66. package/kit/workflow/agents/implement.md +16 -0
  67. package/kit/workflow/agents/plan.md +13 -0
  68. package/kit/workflow/agents/spec-review.md +96 -0
  69. package/kit/workflow/skills/follow/SKILL.md +48 -0
  70. package/kit/workflow/skills/grill/SKILL.md +60 -0
  71. package/kit/workflow/skills/implement/SKILL.md +107 -0
  72. package/kit/workflow/skills/orchestrate/SKILL.md +565 -0
  73. package/kit/workflow/skills/orchestrate/handover/decision.ts +247 -0
  74. package/kit/workflow/skills/orchestrate/handover/index.ts +171 -0
  75. package/kit/workflow/skills/plan/SKILL.md +118 -0
  76. package/kit/workflow/skills/plan/references/template-English.md +92 -0
  77. package/kit/workflow/skills/plan/references/template-German.md +92 -0
  78. package/kit/workflow/skills/start/SKILL.md +47 -0
  79. package/package.json +63 -5
  80. package/schemas/jackherd.schema.json +601 -0
  81. package/src/check/changes.ts +45 -0
  82. package/src/check/command-line.ts +12 -0
  83. package/src/check/command.ts +136 -0
  84. package/src/check/deferral-arrows.ts +41 -0
  85. package/src/check/execute.ts +29 -0
  86. package/src/check/glob.ts +15 -0
  87. package/src/check/plan-structure-check.ts +135 -0
  88. package/src/check/plan.ts +106 -0
  89. package/src/check/public-content-check.ts +32 -0
  90. package/src/check/public-content-facts.ts +177 -0
  91. package/src/check/public-content.ts +216 -0
  92. package/src/check/report.ts +55 -0
  93. package/src/check/result.ts +42 -0
  94. package/src/check/roots.ts +36 -0
  95. package/src/check/routing.ts +22 -0
  96. package/src/check/schema.ts +83 -0
  97. package/src/check/settings.ts +44 -0
  98. package/src/claude/paths.ts +14 -0
  99. package/src/cli.ts +46 -0
  100. package/src/config/command.ts +45 -0
  101. package/src/config/detect.ts +196 -0
  102. package/src/config/index.ts +2 -0
  103. package/src/config/json-schema.ts +5 -0
  104. package/src/config/legacy-names.ts +56 -0
  105. package/src/config/link-kinds.ts +25 -0
  106. package/src/config/location.ts +46 -0
  107. package/src/config/read-config.ts +42 -0
  108. package/src/config/repository-facts.ts +19 -0
  109. package/src/config/repository-values.ts +46 -0
  110. package/src/config/schema.ts +302 -0
  111. package/src/context/command.ts +39 -0
  112. package/src/context/coordinator-name.ts +16 -0
  113. package/src/context/fill.ts +69 -0
  114. package/src/context/index.ts +10 -0
  115. package/src/context/level.ts +22 -0
  116. package/src/context/options.ts +29 -0
  117. package/src/context/report.ts +42 -0
  118. package/src/context/session-log.ts +91 -0
  119. package/src/plan-format/drift.ts +69 -0
  120. package/src/plan-format/header.ts +27 -0
  121. package/src/plan-format/index.ts +9 -0
  122. package/src/plan-format/lanes.ts +128 -0
  123. package/src/plan-format/markdown.ts +98 -0
  124. package/src/plan-format/phase-tally.ts +55 -0
  125. package/src/plan-format/phases.ts +224 -0
  126. package/src/plan-format/plan-structure.ts +150 -0
  127. package/src/plan-format/progress.ts +202 -0
  128. package/src/plan-format/sprint-document.ts +69 -0
  129. package/src/plan-format/terminal-text.ts +28 -0
  130. package/src/plans/board-choice.ts +60 -0
  131. package/src/plans/board-pick.ts +72 -0
  132. package/src/plans/board-rows.ts +62 -0
  133. package/src/plans/command.ts +60 -0
  134. package/src/plans/ensure-panes.ts +71 -0
  135. package/src/plans/error-text.ts +5 -0
  136. package/src/plans/fetch.ts +96 -0
  137. package/src/plans/git.ts +145 -0
  138. package/src/plans/github.ts +35 -0
  139. package/src/plans/lane-discovery.ts +34 -0
  140. package/src/plans/lane-plans.ts +80 -0
  141. package/src/plans/layout.ts +881 -0
  142. package/src/plans/link-chips.ts +74 -0
  143. package/src/plans/live.ts +336 -0
  144. package/src/plans/markdown-html.ts +140 -0
  145. package/src/plans/options.ts +15 -0
  146. package/src/plans/phase-source.ts +71 -0
  147. package/src/plans/plan-chips.ts +75 -0
  148. package/src/plans/plan-panes.ts +206 -0
  149. package/src/plans/planned.ts +117 -0
  150. package/src/plans/pull-requests.ts +107 -0
  151. package/src/plans/restart-decision.ts +42 -0
  152. package/src/plans/selection.ts +352 -0
  153. package/src/plans/settings.ts +68 -0
  154. package/src/plans/single-flight.ts +25 -0
  155. package/src/plans/snapshot.ts +300 -0
  156. package/src/plans/sprint-export.ts +103 -0
  157. package/src/plans/sprint-html.ts +301 -0
  158. package/src/plans/sprint-page.ts +287 -0
  159. package/src/plans/supervisor.ts +124 -0
  160. package/src/plans/symbols.ts +54 -0
  161. package/src/plans/texts.ts +103 -0
  162. package/src/plans/unlisted-lanes.ts +45 -0
  163. package/src/plans/view.ts +68 -0
  164. package/src/plans/worked-phases.ts +53 -0
  165. package/src/sync/checksum.ts +13 -0
  166. package/src/sync/choice.ts +14 -0
  167. package/src/sync/command-context.ts +113 -0
  168. package/src/sync/command.ts +94 -0
  169. package/src/sync/error-message.ts +1 -0
  170. package/src/sync/gitignore.ts +45 -0
  171. package/src/sync/global-kit.ts +176 -0
  172. package/src/sync/herdr-plugin.ts +175 -0
  173. package/src/sync/if-changed.ts +109 -0
  174. package/src/sync/init-command.ts +239 -0
  175. package/src/sync/init.ts +136 -0
  176. package/src/sync/kit-hooks.ts +18 -0
  177. package/src/sync/kit-path.ts +77 -0
  178. package/src/sync/kit-source.ts +169 -0
  179. package/src/sync/kit.ts +9 -0
  180. package/src/sync/legacy-files.ts +116 -0
  181. package/src/sync/manifest.ts +44 -0
  182. package/src/sync/next-steps.ts +42 -0
  183. package/src/sync/plan-file.ts +154 -0
  184. package/src/sync/plan-hooks.ts +165 -0
  185. package/src/sync/project-fs.ts +132 -0
  186. package/src/sync/render.ts +60 -0
  187. package/src/sync/report.ts +96 -0
  188. package/src/sync/settings-hooks.ts +90 -0
  189. package/src/sync/settings-migration.ts +94 -0
  190. package/src/sync/settings-offer.ts +168 -0
  191. package/src/sync/setup-questions.ts +70 -0
  192. package/src/sync/setup.ts +52 -0
  193. package/src/sync/sync-plan.ts +120 -0
  194. package/src/sync/sync-result.ts +9 -0
  195. package/src/sync/sync.ts +307 -0
  196. package/src/sync/tools.ts +52 -0
  197. package/src/terminal/herdr.ts +91 -0
  198. package/src/terminal/index.ts +13 -0
  199. package/src/terminal/none.ts +7 -0
  200. package/src/terminal/open.ts +39 -0
  201. package/src/terminal/text.ts +159 -0
  202. package/src/terminal/types.ts +30 -0
  203. package/src/terminal/width.ts +90 -0
  204. package/src/update/command.ts +65 -0
  205. package/src/update/package-manager.ts +56 -0
  206. package/src/update/update.ts +105 -0
  207. package/src/version/installed-version.ts +32 -0
  208. package/src/version/release.ts +22 -0
  209. package/src/version/skill-diff.ts +63 -0
  210. package/src/version/started-kit.ts +14 -0
  211. package/src/vocabulary/index.ts +132 -0
  212. package/src/watch/base-tip.ts +18 -0
  213. package/src/watch/command.ts +919 -0
  214. package/src/watch/coordinator-fill.ts +76 -0
  215. package/src/watch/feedback-read.ts +73 -0
  216. package/src/watch/feedback.ts +308 -0
  217. package/src/watch/gh.ts +22 -0
  218. package/src/watch/github-read.ts +175 -0
  219. package/src/watch/herdr.ts +11 -0
  220. package/src/watch/json-guards.ts +9 -0
  221. package/src/watch/kit-update.ts +18 -0
  222. package/src/watch/lane-fill.ts +36 -0
  223. package/src/watch/lane-message.ts +99 -0
  224. package/src/watch/lane-progress.ts +44 -0
  225. package/src/watch/lane-report-file.ts +53 -0
  226. package/src/watch/lane-report.ts +270 -0
  227. package/src/watch/lane-search.ts +41 -0
  228. package/src/watch/lane-sources.ts +46 -0
  229. package/src/watch/lane-watch.ts +121 -0
  230. package/src/watch/monitor-tool.ts +19 -0
  231. package/src/watch/options.ts +79 -0
  232. package/src/watch/plan-badges-read.ts +30 -0
  233. package/src/watch/plan-badges.ts +66 -0
  234. package/src/watch/plan-views.ts +59 -0
  235. package/src/watch/preview-read.ts +60 -0
  236. package/src/watch/previews.ts +234 -0
  237. package/src/watch/pull-requests.ts +117 -0
  238. package/src/watch/single-report.ts +15 -0
  239. package/src/watch/skill-change.ts +61 -0
  240. package/src/watch/source-dry-run-command.ts +35 -0
  241. package/src/watch/source-dry-run.ts +124 -0
  242. package/src/watch/source-fields.ts +9 -0
  243. package/src/watch/source-path.ts +40 -0
  244. package/src/watch/source-run.ts +90 -0
  245. package/src/watch/sources.ts +344 -0
  246. package/src/watch/stream-output.ts +14 -0
  247. package/src/watch/texts.ts +76 -0
  248. package/src/watch/watch-events.ts +399 -0
  249. package/src/watch/watcher-lock.ts +145 -0
  250. package/src/watch/workflow-runs.ts +96 -0
package/README.md CHANGED
@@ -1,5 +1,1124 @@
1
- # jackherd
1
+ <p>
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd@1.0.0/assets/branding/jackherd-logo-dark.svg">
4
+ <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.0.0/assets/branding/jackherd-logo.svg" width="340">
5
+ </picture>
6
+ <br>
7
+ </p>
2
8
 
3
- Coming soon: the successor of [`@crossdelta/agent-kit`](https://www.npmjs.com/package/@crossdelta/agent-kit), with the command `jh`.
9
+ **Plan. Delegate. Ship.** Your agents do the work. Jack keeps them together.
4
10
 
5
- This version only reserves the name and installs nothing.
11
+ Large tasks outgrow one chat, the context fills up, parallel sessions edit the same files, and
12
+ you lose track of who waits on you. Jack:
13
+
14
+ - 📄 **Keeps the plan in your repository.** Your agent writes it as Markdown, phases with a model each, and ticks it off as it builds.
15
+ - ⚡ **Herds the lanes.** Each lane works in its own git worktree, so sessions never share a checkout.
16
+ - ❓ **Brings you only the decisions.** A coordinating session answers what the plan settles, vets review feedback on its pull requests, asks you the rest as a choice, and asks you before every commit and push.
17
+ - 💬 **Barks when something moves.** One live view of every lane: state, progress, pull request, checks, with the preview link and new feedback as badges; `/follow` brings in tickets, issues and status pages outside GitHub.
18
+ - 🧠 **Watches the context.** It measures each agent session's fill and, before it is full, offers a fresh chat that takes over the state.
19
+
20
+ Built for Claude Code today; other coding agents are planned.
21
+
22
+ jackherd runs its lanes and plan view in [herdr](https://herdr.dev).
23
+
24
+ <img src="https://unpkg.com/jackherd@1.0.0/docs/workflow.svg" width="720" alt="jackherd in herdr: a coordinator and its lanes, a blocked lane's question answered as a choice, and the plan view beside them.">
25
+
26
+ <sub>Schematic of one herdr window; the plan view pane is drawn by jackherd itself from example data.</sub>
27
+
28
+ ## Quickstart
29
+
30
+ Plans and boards are Markdown files in your repository, nothing is hidden. [herdr](https://herdr.dev), a terminal multiplexer for AI coding agents, is needed only for parallel lanes.
31
+
32
+ 1. **Install** (Linux and macOS):
33
+
34
+ ```bash
35
+ curl -fsSL https://unpkg.com/jackherd/install.sh | sh
36
+ ```
37
+
38
+ It installs jackherd and asks a few setup questions, including whether to use herdr (and offers to install it if it is missing); your repositories stay untouched. → [What the installer changes](#user-content-the-installer)
39
+ 2. **Start Claude Code in a repository and run:**
40
+
41
+ ```text
42
+ /jh-start
43
+ ```
44
+
45
+ It checks the setup, says what is missing (herdr is optional and comes with its install command) and offers to start your first plan.
46
+ 3. **Describe a task or point to a spec file:**
47
+
48
+ ```text
49
+ /jh-plan Add product search
50
+ /jh-plan docs/spec.md
51
+ ```
52
+
53
+ It writes the plan to `docs/plans/`: phases with tasks and a recommended model each. At the end it proposes lanes for a board, `docs/plans/board-<name>/board.md`, and asks where they go; it starts nothing. A **board** is the Markdown table of lanes.
54
+ 4. **Build it.** With herdr, in a Claude Code session started inside herdr, start the board:
55
+
56
+ ```text
57
+ /jh-orchestrate docs/plans/board-<name>/board.md
58
+ ```
59
+
60
+ It starts every lane whose prerequisites are done; the others wait. A **lane** is one or more phases of a plan in their own git worktree with their own agent session; the session in which you ran `/jh-orchestrate` is the **coordinator**. It answers what the plan settles, brings you the open questions and asks once per pull request before commit and push.
61
+
62
+ Without herdr, build a phase in your session:
63
+
64
+ ```text
65
+ /jh-implement docs/plans/<plan>.md phase 1
66
+ ```
67
+
68
+ It builds one phase; ask for several phases and the independent ones run in parallel as subagents in the same checkout.
69
+ 5. **Follow progress.** `/jh-orchestrate` opens the plan view in a herdr pane beside the coordinator and one beside each lane. Anywhere else, once a board exists:
70
+
71
+ ```bash
72
+ jh plans
73
+ ```
74
+
75
+ With herdr, the plan view also shows each session's context fill. Once a lane or the coordinator reaches its threshold, it offers you a fresh chat at a quiet moment and hands its state over in a file ([Hand-over to a fresh chat](#user-content-hand-over-to-a-fresh-chat)).
76
+
77
+ → [Orchestration](#user-content-orchestration-with-herdr), [Plan view](#user-content-plan-view-and-context)
78
+
79
+ Installing by hand (the CLI runs on Bun 1.3+), with Bun:
80
+
81
+ ```bash
82
+ bun add -g jackherd
83
+ ```
84
+
85
+ or with npm:
86
+
87
+ ```bash
88
+ npm install -g jackherd
89
+ ```
90
+
91
+ then:
92
+
93
+ ```bash
94
+ jh init --global
95
+ ```
96
+
97
+ ## With and without herdr
98
+
99
+ Planning, building and reviewing work without herdr. herdr adds parallel lanes and the agents' state in the plan view. The table names the skills without prefix; with the global install they start with `jh-` (`/jh-plan`).
100
+
101
+ | | Without herdr | With herdr |
102
+ | --- | --- | --- |
103
+ | `/grill`, `/plan`, `/implement`, `/pre-commit` and the review skills | yes | yes |
104
+ | Independent phases in parallel | as subagents in one checkout | as lanes, one worktree and session each |
105
+ | `/orchestrate` (the coordinator) | – | yes |
106
+ | `jh plans` | board and plan progress | plus the lanes' state, pull requests and checks, context fill |
107
+
108
+ Pull requests and checks in the plan view need `gh`.
109
+
110
+ ## Global or per project
111
+
112
+ The global install prefixes every skill with `jh-`, leaves repositories alone and keeps its config in `~/.jackherd/`. To share the kit with your team instead, run `bunx jackherd init` in the repository. Skills then land in `.claude/` without prefix (`/plan`), with the kit's hooks, and `jh sync` updates them without overwriting your edits; the project's config lives in `.jackherd/`. → [Installation](#user-content-installation-and-updates)
113
+
114
+ ## Documentation
115
+
116
+ Each section opens on a click; a link into one opens it.
117
+
118
+ <details>
119
+ <summary><b>Installation and updates</b></summary>
120
+
121
+ ### Installation and updates
122
+
123
+ - [Requirements](#user-content-requirements)
124
+ - [The installer](#user-content-the-installer)
125
+ - [Setup questions](#user-content-setup-questions)
126
+ - [Project or global](#user-content-project-or-global)
127
+ - [Project install](#user-content-project-install)
128
+ - [Global install](#user-content-global-install)
129
+ - [Detected per repository](#user-content-detected-per-repository)
130
+ - [Updating](#user-content-updating)
131
+ - [Keeping the global kit current](#user-content-keeping-the-global-kit-current)
132
+ - [Removing the global install](#user-content-removing-the-global-install)
133
+
134
+ #### Requirements
135
+
136
+ - [Bun](https://bun.sh) 1.3 or later, and Claude Code.
137
+ - Optional: [herdr](https://herdr.dev) for parallel lanes (`/orchestrate`, the watcher, agent states and context fill in the plan view), and `gh` for pull requests and checks in the plan view and the watcher.
138
+
139
+ #### The installer
140
+
141
+ ```bash
142
+ curl -fsSL https://unpkg.com/jackherd/install.sh | sh
143
+ ```
144
+
145
+ To read the script before it runs, open [install.sh](https://unpkg.com/jackherd/install.sh), the file the command above runs, or download it with `curl -fsSL <that address> -o install.sh` and start it with `sh install.sh` after reading. The address without a version leads to the newest release; for a fixed one use `https://unpkg.com/jackherd@<version>/install.sh`.
146
+
147
+ For Linux and macOS, without root: the script refuses to run as root. It announces each step on one line before it runs:
148
+
149
+ 1. If no Bun is found (on the `PATH` or in `~/.bun/bin`), it runs Bun's official installer from `bun.com/install`, which needs `curl`, `unzip` and `bash`; a Bun older than 1.3 stops the script with a hint to run `bun upgrade`. That installer puts Bun into `~/.bun` and names the shell file it changed; the script itself edits none.
150
+ 2. `bun add -g --ignore-scripts jackherd@<version>`, the version the script ships with. `curl … | JACKHERD_VERSION=1.2.3 sh` picks another release.
151
+ 3. `jh init --global` on your terminal, so the [setup questions](#user-content-setup-questions) work under `curl … | sh`. Without a terminal it runs `init --global --yes` and says so.
152
+
153
+ The same by hand: `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global`. The CLI runs on Bun in both cases.
154
+
155
+ #### Setup questions
156
+
157
+ When `init` creates a new config, it asks:
158
+
159
+ - the language the skills answer you in, and the language plans and boards are written in (English or German; it sets their headings),
160
+ - whether to use herdr as the terminal adapter. If herdr is missing on Linux or macOS, it first offers herdr's official installer, shows the command and runs it only after a yes.
161
+
162
+ `--yes`, `--no` or a run without a terminal ask nothing and install nothing: replies and plans in English, herdr as the adapter only if herdr is found, and one line names what was taken. An existing config is never changed by `init`; edit `responseLanguage`, `planLanguage` and `terminal.adapter` in it, then run `sync`. At the end `init` prints the next steps: the start skill, `/plan` and what herdr or `gh` still need.
163
+
164
+ #### Project or global
165
+
166
+ | | Project install | Global install |
167
+ | --- | --- | --- |
168
+ | Use it when | the team shares the kit through the repository | the repository must stay untouched, not even untracked files |
169
+ | Lives in | the project's `.claude/` | `~/.claude/` |
170
+ | Skill names | `/plan`, `/implement`, `/orchestrate`, … | `/jh-plan`, `/jh-implement`, `/jh-orchestrate`, … |
171
+ | Hooks | `git-guard`, `watch-guard`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
172
+ | Config | `.jackherd/config.json` | `~/.jackherd/config.json`, used where a repository has none |
173
+ | Update | `jh sync` | `jh update` |
174
+
175
+ A project config always wins over the global one. Both can exist side by side: in a project with its own kit, the project's skills are the ones in use.
176
+
177
+ #### Project install
178
+
179
+ ```bash
180
+ bunx jackherd init # writes the kit into .claude/ and creates .jackherd/config.json
181
+ bunx jackherd sync # later: updates the kit files
182
+ ```
183
+
184
+ `init` writes what it [detected](#user-content-detected-per-repository) into the new config, so the project sees it. It asks whether to enter the [status line](#user-content-status-line) in `.claude/settings.json`; `--yes` or `--no` answer every question without asking.
185
+
186
+ **Name clashes.** A project that already has a skill or agent of a kit name (`implement`, `plan`, …): `init` names the clash and offers the prefix `jh-`, so the kit's copies sit beside yours and call each other, not yours. Any project can set `skillPrefix` in `.jackherd/config.json`; `sync` then renames, and removes the unprefixed kit files you never edited.
187
+
188
+ **Your edits survive.** `sync` leaves files you have edited alone. When the kit changed one of them too, its version lands beside yours as `<file>.kit-new`: merge it, or take the kit version with `sync --accept <path>`. A kit file you deleted stays deleted. Project-specific additions go into `.claude/project/<skill>.md` (read first by the skill of that name) or a rule beside `.claude/rules/kit/`, so the kit files stay updatable.
189
+
190
+ **herdr plugin.** Run inside herdr (`HERDR_ENV=1`), `init` and `sync` link the kit's herdr plugin when herdr does not list `crossdelta.jackherd` yet, and link it again when herdr's link points at another package or herdr cannot read its manifest (a deleted checkout); one line names the old and the new path. An installed package (global, `bunx`, a project's dependency) replaces only a link herdr cannot read, so a checkout that develops the kit keeps its link across `jh update`. From a lane's worktree they never link, since that path goes away with the lane; a broken link is named there instead. It adds a plans pane, an action that opens it, and the startup hook that brings back the plans views. If herdr refuses the link, the command prints herdr's message and carries on. The link belongs to herdr, not to the project; `herdr plugin unlink crossdelta.jackherd` removes it.
191
+
192
+ #### Global install
193
+
194
+ On a new machine these two commands are the whole setup:
195
+
196
+ ```bash
197
+ bun add -g jackherd
198
+ jh init --global # writes the kit into ~/.claude, creates ~/.jackherd/config.json, asks about the hook
199
+ ```
200
+
201
+ `init --global` asks once whether to add a `SessionStart` hook that [keeps the global kit current](#user-content-keeping-the-global-kit-current), and whether to enter the status line; `--yes` or `--no` answer without asking. Nothing in the config needs editing: each repository's package manager and base branch are [detected](#user-content-detected-per-repository) where the kit runs. Run `jh sync --global` again after editing the config.
202
+
203
+ **Why the prefix.** Claude Code lets a personal skill in `~/.claude/skills` win over a project skill of the same name, so a global install renames everything with `skillPrefix` (`init --global` sets `jh-`; `sync --global` refuses to run without one): `/jh-implement`, `/jh-pre-commit`, the agent `jh-spec-review`, with every reference inside the files renamed to match. Each description starts with `Global jackherd copy. In a project that has its own implement skill, use that one instead.`, so in a project with its own kit the project's skills stay the ones in use.
204
+
205
+ **What lands where:** `~/.claude/skills/jh-*/` and `~/.claude/agents/jh-*.md`, which Claude Code reads, and under `~/.jackherd/` the config, the lock, the rules in `rules/` (where Claude Code does not load them in every project; the skills read them when they need them) and the status line as `statusline.sh`. Hooks are not installed globally, and `sync --global` never reads or writes `~/.claude/settings.json`; only `init --global` does, after asking. Other files in `~/.claude` are left alone; a file of your own at a kit path becomes a conflict with a `.kit-new` beside it, as in a project.
206
+
207
+ `check`, `plans`, `context` and `watch` read `~/.jackherd/config.json` when the repository has no `.jackherd/config.json`. `check` then runs from the repository's top level. Reviewer names in `check.reviewerRoutes` carry the prefix, since `/jh-pre-commit` dispatches exactly the names it prints.
208
+
209
+ Checked with a packed tarball and a throwaway `HOME` (shortened to `<home>`), in a throwaway Yarn 4 repository with `yarn.lock`, `origin/master` and no config of its own:
210
+
211
+ ```text
212
+ $ jh init --global --yes
213
+ Created <home>/.jackherd/config.json with defaults.
214
+ Installed into ~/.claude; the paths below are relative to <home>.
215
+ jh sync: 30 created
216
+ Added the SessionStart hook to ~/.claude/settings.json.
217
+ Added the status line to ~/.claude/settings.json.
218
+
219
+ $ jh check --skip-slow
220
+ Changed files (8, 0 staged):
221
+ .editorconfig
222
+ .gitattributes
223
+ .gitignore
224
+ .yarnrc.yml
225
+ README.md
226
+ package.json
227
+ src/probe.ts
228
+ yarn.lock
229
+
230
+ Package roots: none
231
+ Reviewers: none
232
+
233
+ ✅ Staged build artifacts (git status --porcelain --untracked-files=all, 0ms)
234
+ ✅ Lint & unused imports (yarn biome check --no-errors-on-unmatched (8 files), 368ms)
235
+ ⏭️ Types & tests (--skip-slow, 0ms)
236
+ skipped on request
237
+
238
+ ✅ 2 passed, 0 failed, 1 skipped
239
+ ```
240
+
241
+ #### Detected per repository
242
+
243
+ What a config leaves out of `commands`, `lanes.lockFile`, `check.lintFilesCommand` and `baseBranch`, the kit reads from the repository it runs in, each time:
244
+
245
+ - **Package manager** from the lock file in the repository root: `bun.lock` → `bun test`, `bun install`, `bunx @biomejs/biome …`; `yarn.lock` → `yarn test`, `yarn tsc --noEmit`, `yarn biome …`; `pnpm-lock.yaml` → `pnpm test`, `pnpm exec …`; `package-lock.json` → `npm test`, `npx …`. The lock file becomes `lanes.lockFile`. Lock files of two managers decide nothing: the Bun defaults hold, with one line saying so.
246
+ - **Base branch** from the branches on `origin`: `main` or `master`, when exactly one of them exists. With both or neither, `main` holds, with one line saying so; `origin/HEAD` is not asked, since it can point at a branch long deleted.
247
+
248
+ Detection only reads the lock files and `refs/remotes/origin/*` as the last fetch left them; it never fetches. A value you set always wins, key by key. `jh config` prints what applies in the repository you are in.
249
+
250
+ The global skills cannot carry one repository's values, so `sync --global` writes them as `<test>`, `<install>`, `<lockFile>`, `<baseBranch>` and so on, and every global skill starts with a sentence that sends the model to `jh config` for them; a project install keeps the real values in its files. `init --global` writes none of these keys, so one global config serves a Yarn repository on `master` and a Bun repository on `main` alike. A global config from 0.10 or 0.11 spells out the Bun defaults and `main`, which count as set: delete `commands`, `baseBranch` and `lanes.lockFile` from it to get detection.
251
+
252
+ #### Updating
253
+
254
+ `jh update` asks npm for the newest release and, when there is one, installs exactly that version, from your home directory and without install scripts, with the package manager that installed the kit globally (bun, npm, pnpm or yarn, read from where the package lies), then syncs `~/.claude` and, inside a project, the project with the freshly installed code. It prints the old and the new version. A kit that is a project dependency or a source checkout is not a global install, and `update` says so and changes nothing; there, update the dependency and run `jh sync`.
255
+
256
+ A running `plans` view restarts itself with the new code, and a running `watch` prints `=== kit updated <old> → <new>` and ends, so the coordinator reads its skill again and starts the stream again.
257
+
258
+ #### Keeping the global kit current
259
+
260
+ `jh sync --global --if-changed` is meant to run at the start of every session. It syncs `~/.claude` only when the installed package is not the version the global lock was written by, so after `bun add -g jackherd@<version>` the next session brings the skills up to date. Once a day it asks npm for the newest release and, when there is one, prints a single line of this form:
261
+
262
+ ```text
263
+ jh 1.1.0 is on npm, 1.0.0 is installed. Ask the user with AskUserQuestion whether to run `jh update` now (update now / not now), and run it only after a yes.
264
+ ```
265
+
266
+ It never installs anything itself: the session asks you, and `jh update` runs only after your yes. With nothing to do it prints nothing and exits 0; without a global install it syncs nothing. The time of the last npm question is kept in `~/.jackherd/update-check.json`.
267
+
268
+ `init --global` asks once whether to add this hook to `~/.claude/settings.json`, appends it as a group of its own beside your other hooks, and does nothing when it is there already. A file it cannot read or merge into, or a symlink, stays as it is; the entry to add by hand:
269
+
270
+ ```json
271
+ {
272
+ "hooks": {
273
+ "SessionStart": [
274
+ { "matcher": "startup", "hooks": [{ "type": "command", "command": "jh sync --global --if-changed" }] }
275
+ ]
276
+ }
277
+ }
278
+ ```
279
+
280
+ Claude Code adds a `SessionStart` hook's output to the session's context, so the update line reaches Claude, not only the terminal; a failing hook does not block the session.
281
+
282
+ #### Removing the global install
283
+
284
+ ```bash
285
+ rm -rf ~/.claude/skills/jh-* ~/.claude/agents/jh-*.md ~/.jackherd
286
+ ```
287
+
288
+ Remove `~/.jackherd/config.json` with it if you no longer need the config, and the `SessionStart` hook and status line entries from `~/.claude/settings.json` if `init --global` added them.
289
+
290
+ </details>
291
+
292
+ <details>
293
+ <summary><b>Planning: plans, models and boards</b></summary>
294
+
295
+ ### Planning: plans, models and boards
296
+
297
+ Names on this page are those of a project install. A global install puts `jh-` before every skill and agent: `/jh-plan`, `/jh-implement`, `/jh-orchestrate`, the agent `jh-spec-review`.
298
+
299
+ - [Terms](#user-content-terms)
300
+ - [The skills and what they hand on](#user-content-the-skills-and-what-they-hand-on)
301
+ - [What a plan contains](#user-content-what-a-plan-contains)
302
+ - [Model recommendations](#user-content-model-recommendations)
303
+ - [Plan format](#user-content-plan-format)
304
+ - [Boards](#user-content-boards)
305
+
306
+ #### Terms
307
+
308
+ - **Plan:** a Markdown file with phases (`### Phase 1: …`) and checkbox tasks. `/plan` writes it, `/implement` builds one phase and ticks its tasks.
309
+ - **Board:** a Markdown file with a lanes table that says who works on which phases of which plan, and what each lane starts after. `/plan` proposes its rows; by default it is `docs/plans/board-<name>/board.md`.
310
+ - **Lane:** one agent session in its own git worktree, working on the phases its row names.
311
+ - **Coordinator:** one more session, in the main checkout, running `/orchestrate`: it starts the lanes, answers or relays their questions, asks you for approvals and keeps the merge order.
312
+
313
+ #### The skills and what they hand on
314
+
315
+ ![/grill hands decision rows to /plan; /plan feeds /orchestrate or a single /implement; /implement ends in /pre-commit, which runs /review and spec-review and the reviewers routed by path; /diagnose hands a red command to /implement.](https://unpkg.com/jackherd@1.0.0/docs/skills.svg)
316
+
317
+ - **`/start`** is the first command after the install. It checks the config, herdr and `gh` with read-only commands, explains plan → board → `/orchestrate` or `/implement` in a few lines and offers to begin with `/plan`. It changes no file.
318
+ - **`/grill`** questions a loose idea in rounds until every decision is settled, before there is a plan. It writes no document; it hands over decision rows that go into the plan's decisions table unchanged, and names what it left open.
319
+ - **`/plan`** writes the plan file from one template ([what it contains](#user-content-what-a-plan-contains)). Its dependency table and its model per phase are what `/orchestrate` reads to tell which phase is ready to start, which lanes would collide, and which model a lane runs on; `/implement` reads the same table to start phases in parallel without `/orchestrate`. At the end `/plan` proposes the board's rows and asks whether they go into the newest board, a new one it names with you, or nowhere yet. If the decisions table would hold its own guesses, `/plan` stops and recommends `/grill` instead.
320
+ - **`/orchestrate`** runs the lanes of a board in herdr from the main checkout ([Orchestration](#user-content-orchestration-with-herdr)). Each lane builds its phases with `/implement`.
321
+ - **`/implement`** builds one phase: explore, build, test, look at it in the running app when a user sees the change, tick the plan's tasks as they finish. It hands a phase recommended for another model to a subagent on that model, starts independent phases as parallel subagents in one checkout, and ends in `/pre-commit`.
322
+ - **`/pre-commit`** runs `jh check` and quotes its numbers, then `/review` for the judgement calls and the `spec-review` agent against the plan, plus the reviewers `check.reviewerRoutes` routes the changed paths to (`/uncle-bob`, `/security-audit`, `/devops`) and, for files a package publishes, `public-content`. It proposes the commit message and asks before committing.
323
+ - **`/follow`** is beside the chain too: for a ticket, an issue or a status page outside GitHub it sets up a watcher source from a [recipe](#user-content-recipes), dry-runs it and writes it to `watch.sources` after you agreed ([Source reports](#user-content-the-watcher)).
324
+ - **`/diagnose`** is beside the chain: for a bug, a flaky failure or a regression it first builds a command that goes red on it, then hands the fix to `/implement`.
325
+
326
+ #### What a plan contains
327
+
328
+ `/plan` writes every plan from one template. Each part is there because a skill reads it:
329
+
330
+ - **Header (date, status, scope):** what the plan touches and how far it is; the board page of `plans` (key `s`) shows each plan with its title and status.
331
+ - **Decisions:** each decision with its rationale, including who decided and when; `/plan` does not invent them, and `/orchestrate` writes a decision of yours into the plan the moment you make it.
332
+ - **Model recommendations per phase:** the model for each phase with the reason ([below](#user-content-model-recommendations)).
333
+ - **Dependencies and parallelization:** per phase what it depends on, the paths it writes and its serial markers (a migration, say). `/implement` starts phases with no shared path at the same time, and `/orchestrate` checks each lane's `Writes` set against the running lanes before starting it.
334
+ - **Phases with checkboxes:** the tasks `/implement` ticks as it finishes them, which is what `plans` counts.
335
+ - **Review gate per phase:** pre-scan, tests, the reviewers to run, documentation. A phase is finished only when its gate is ticked, and a gate item naming a skill is ticked only after the skill ran. A phase that needs none says why (`**No review gate:** <reason>`); `jh check` refuses a new phase with neither.
336
+ - **Tests:** which test files are needed and why, or the reason none are.
337
+ - **Documentation and architecture docs:** what gets documented after the last phase, ending with the architecture documentation derived from the finished code.
338
+
339
+ The section headings stay verbatim (in the plan language the config sets), because the plan parser, `/implement` and `spec-review` find them by name.
340
+
341
+ #### Model recommendations
342
+
343
+ Each phase gets one row, judged by **error cost × complexity**. The project's implementation model (`models.implement`) is the default of every row. A phase moves up to the planning model (`models.plan`) when its rationale names a concrete error cost: a breakable invariant, possible data loss, security or tenant isolation, a migration without a way back, distributed state across systems. "Complex" or "many files" alone keeps the default. Review gates with `/security-audit` always get the strongest model.
344
+
345
+ The recommendation is applied, not just noted:
346
+
347
+ - `/implement` compares the phase's model with the one it runs on and, when they differ in either direction, hands the phase to a subagent on the recommended model.
348
+ - `/orchestrate` starts each lane's agent with the full model ID from the board row and checks the banner before any work happens; an alias would resolve through `ANTHROPIC_DEFAULT_*_MODEL` and can land on another model.
349
+
350
+ The defaults a fresh `init` writes are `claude-opus-5-5` for `plan` and `orchestrate`, and `claude-sonnet-5-5` for `implement`, `review` and `explore`. Change them under `models` in the config.
351
+
352
+ #### Plan format
353
+
354
+ A shortened example, with the parts that matter for the lanes; Current State, Target State, Tests, Risks and the architecture section are left out here:
355
+
356
+ ```markdown
357
+ <!-- docs/plans/2026-10-12-search.md -->
358
+ # Product search
359
+
360
+ **Date:** 2026-10-12
361
+ **Status:** In Progress
362
+ **Scope:** `services/search/`, `apps/shop/src/search/`
363
+
364
+ ## Problem / Context
365
+
366
+ Customers scroll the whole catalog to find a product. Support gets the same question every day.
367
+
368
+ ## Decisions
369
+
370
+ | Decision | Rationale |
371
+ | -------- | --------- |
372
+ | Index in Postgres full-text search, no external engine | User 2026-10-11: one database to run; 4 000 products fit |
373
+ | The index rebuilds on product events, not on a timer | User 2026-10-11: a changed price must be searchable at once |
374
+
375
+ ## Implementation
376
+
377
+ ### Model recommendations per phase
378
+
379
+ | Phase | Model | Rationale |
380
+ | ----- | ----- | --------- |
381
+ | 1 — Index | claude-opus-5-5 | Migration on a hot table; a wrong index locks writes |
382
+ | 2 — Search box | claude-sonnet-5-5 | UI after an existing pattern |
383
+ | 3 — Docs | claude-sonnet-5-5 | Text from finished code |
384
+
385
+ ### Dependencies and parallelization
386
+
387
+ | Phase | Depends on | Writes | Serial marker |
388
+ | ----- | ---------- | ------ | ------------- |
389
+ | 1 — Index | — | `services/search/`, `db/migrations/` | Migration |
390
+ | 2 — Search box | 1 | `apps/shop/src/search/` | — |
391
+ | 3 — Docs | 1, 2 | `docs/architecture/` | — |
392
+
393
+ ### Phase 1: Index
394
+
395
+ - [x] Migration: `tsvector` column and GIN index
396
+ - [x] Rebuild the row on `product.updated`
397
+ - [ ] Backfill the existing products
398
+
399
+ **Review gate Phase 1:**
400
+
401
+ - [ ] Tests written
402
+ - [ ] `/uncle-bob` — Clean Architecture review
403
+
404
+ ### Phase 2: Search box
405
+
406
+ - [ ] Search box with results as you type
407
+ - [ ] Empty state when nothing matches
408
+
409
+ **Review gate Phase 2:**
410
+
411
+ - [ ] Tests written
412
+ - [ ] `/uncle-bob` — Clean Architecture review
413
+
414
+ ### Phase 3: Docs
415
+
416
+ - [ ] Search flow in the architecture docs
417
+ ```
418
+
419
+ **Deferred tasks.** A checkbox moved on purpose to a later phase stays unticked and ends in the target, `- [ ] Update the documentation → P3`: it counts in phase 3, not in its own. The arrow counts only at the end of the line; `jh check` refuses an open box in a changed plan that carries it anywhere else.
420
+
421
+ #### Boards
422
+
423
+ A board gathers the lanes one coordinator runs; it mixes plans and has no end date of its own. `/plan` proposes the rows from the dependency and model tables:
424
+
425
+ - **Own lane:** a phase that shares no written path and no serial marker with a phase of another lane.
426
+ - **Continuation (`↳`):** a phase that shares a written path or a serial marker with an earlier phase, or depends on it, runs in the same worktree after it.
427
+ - **Starts after:** from the phase's dependencies (`P1 merged`, or an external gate such as `User: mockups`); a phase without one starts at once.
428
+ - **Model:** the full model ID from the model table, never an alias.
429
+ - **Plan:** a Markdown link to the plan, relative to the board's directory. The plan view finds a lane's plan only through that link.
430
+
431
+ ```markdown
432
+ <!-- docs/plans/board-01/board.md -->
433
+ # Board 01
434
+
435
+ ## Lanes
436
+
437
+ | Lane | Port | Plan | Phases | Starts after | Model |
438
+ |------|------|------|--------|--------------|-------|
439
+ | `search-index` | 3100 | [Product search](../2026-10-12-search.md) | P1 Index | now | claude-opus-5-5 |
440
+ | `search-ui` | 3101 | [Product search](../2026-10-12-search.md) | P2 Search box | P1 merged | claude-sonnet-5-5 |
441
+ | ↳ `search-ui` | – | [Product search](../2026-10-12-search.md) | P3 Docs | P2 merged | claude-sonnet-5-5 |
442
+ ```
443
+
444
+ The columns are read by position. The table heading is `## Lanes` (`## Spuren` with `planLanguage: de`). Nothing is written without your answer: `/plan` writes the rows into the newest board or a new `docs/plans/board-<name>/board.md`, and then names the next command, `/orchestrate <board path>`. Plan and board go into one commit under one approval: yours through `/pre-commit`, or the coordinator's documentation PR when `/orchestrate` finds them missing on the base branch ([Orchestration](#user-content-starting)). No merge is needed before `/orchestrate`.
445
+
446
+ Where boards live and what the project calls them is the `board` block of the config:
447
+
448
+ ```json
449
+ { "board": { "pattern": "board-*", "file": "board.md", "term": "board" } }
450
+ ```
451
+
452
+ - `pattern`: the directories under `planDirectory` that hold a board; `plans` and `watch` take the newest one unless given a path.
453
+ - `file`: the board document in each of them.
454
+ - `term`: the word `plans`, its HTML page and `watch` print for it (`board page opened: …`).
455
+
456
+ Up to 0.12 the key was `sprintPattern` with a fixed `sprint.md`. 0.13 refuses that key and names its replacement; a project that keeps its sprint documents sets `"board": { "pattern": "sprint-*", "file": "sprint.md", "term": "sprint" }` and renames nothing. `watch --sprint` is now `watch --board`.
457
+
458
+ </details>
459
+
460
+ <details>
461
+ <summary><b>Orchestration with herdr</b></summary>
462
+
463
+ ### Orchestration with herdr
464
+
465
+ For work that splits into independent parts, one agent session per part runs in its own git worktree, and Jack sits between them, keeping every lane on its path. One more session coordinates them. [herdr](https://herdr.dev) is the terminal multiplexer that holds the panes and reports each agent's state; `/orchestrate` (global: `/jh-orchestrate`) is the skill the coordinating session runs.
466
+
467
+ - [What runs where](#user-content-what-runs-where)
468
+ - [Starting](#user-content-starting)
469
+ - [What the coordinator does](#user-content-what-the-coordinator-does)
470
+ - [What happens on its own, and what waits for you](#user-content-what-happens-on-its-own-and-what-waits-for-you)
471
+ - [The watcher](#user-content-the-watcher)
472
+ - [Hand-over to a fresh chat](#user-content-hand-over-to-a-fresh-chat)
473
+ - [Lane setup from the config](#user-content-lane-setup-from-the-config)
474
+
475
+ #### What runs where
476
+
477
+ ![The coordinator prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, PRs and checks and wakes the coordinator.](https://unpkg.com/jackherd@1.0.0/docs/lanes.svg)
478
+
479
+ `/orchestrate` needs herdr: it checks that `herdr` is on the path and `HERDR_ENV=1` is set, and stops otherwise. Without herdr, `/implement` still runs independent phases of one plan as parallel subagents in a single checkout, kept apart by their disjoint `Writes` sets.
480
+
481
+ #### Starting
482
+
483
+ 1. Set `"terminal": { "adapter": "herdr" }` in `.jackherd/config.json` (or `~/.jackherd/config.json`), so the plan view and `jh context` read agent states and context fill from herdr.
484
+ 2. Open herdr, run Claude Code in the main checkout and type `/orchestrate docs/plans/<board>/board.md`. That session is the coordinator.
485
+ 3. **Plan and board on the base branch.** Before the first lane starts, the coordinator checks that the board and every plan it links are on `origin/<baseBranch>`. A fresh plan is not, and a lane cut from the base branch would not find it. No merge is needed first: the coordinator asks once, then commits the missing files on a branch `docs/plan-<topic>` from a temporary worktree, pushes it and opens a documentation PR. The lanes of that plan start from that branch until the files are on the base branch. Say no and no lane of these plans starts.
486
+
487
+ It names itself in herdr (`orchestrator-<repository>` by default, since herdr names are unique across all repositories), opens the plan view beside its pane, tells you which lanes start now and which wait, and starts the watcher.
488
+
489
+ #### What the coordinator does
490
+
491
+ Lanes ask Jack, not you: the coordinator checks their questions against the plan and the code, answers what is settled, and brings you only what needs your decision.
492
+
493
+ 1. **Reads the board.** The `## Lanes` table names each lane, its port, the plan and phases it works on, what it starts after, and the model. It is the only place that says which lane runs which phase. The coordinator adds a row itself when a lane starts without one.
494
+ 2. **Starts ready lanes.** A phase is ready when its starts-after entry and the plan's dependencies are met and its `Writes` set and serial markers collide with no running lane; shared paths run one after the other, migrations strictly in order. For each lane it creates a worktree from the base branch, copies the untracked files every worktree needs, installs dependencies, starts the agent on the board's model, checks the banner shows that model, opens a plan pane beside it, and sends the lane prompt: plan, phases, the paths it may write, its port.
495
+ 3. **Runs dev servers.** A lane whose phase changes something a user sees gets its dev server in a pane of its own when the lane starts, never in the lane's shell.
496
+ 4. **Vets questions.** A lane never opens a dialog. It ends its turn with a text block: the question, the options, its recommendation, the evidence at `file:line`. The coordinator checks the premises against the code and the plan, answers what the plan or a rule already settles, and brings only the open decisions to you, each as a clickable choice. A question in a lane's report is closed in the same turn, in the lane or with you. A draft you review (a README, a page) goes in rounds: you pick the remarks, the lane changes it, you see it again, and the push waits for your approval. Your decision goes into the plan or the board in the same turn, with the date, so it is visible in the plan view and survives the chat.
497
+ 5. **Reviews results.** When a lane is `done` or `idle`, it checks the diff against the lane's `Writes` set and that checks are quoted with numbers, not claimed. Then it starts the lane's next ready phase or names the reason to wait.
498
+ 6. **Asks once per PR.** A lane works uncommitted until its PR is ready. The coordinator then asks you once, with the diff summary, the checks with numbers, open points and the commit message; for a visible change it first walks the changed path in the browser. That approval covers the commits, the push and the PR.
499
+ 7. **Keeps the merge order.** Merge order is the board's, and the documentation PR of a fresh plan merges first. Afterwards the lanes rebase as after any merge, and the coordinator removes the copies of the plan files in the main checkout that equal the merged ones, so the next update of the checkout does not stop on them. After each merge the remaining lanes fetch and rebase on the base branch and re-run their checks; a lock-file conflict is never resolved by hand. Each lane gets its next phase or a named reason to wait.
500
+ 8. **Closes lanes.** After a lane's last merge and your agreement, it ends the agent, proves the worktree holds nothing unmerged, removes the worktree and the branch, and notes the end in the board.
501
+
502
+ #### What happens on its own, and what waits for you
503
+
504
+ | On its own | After your approval | Never |
505
+ | --- | --- | --- |
506
+ | Starting a ready phase in a new or running lane (one line to you afterwards) | Commits, push and PR: one approval per PR, the documentation PR for a fresh plan included | Implementing lane work itself |
507
+ | Answering a question the plan, the board or a rule settles (reported in one line) | Every open decision a lane raises | Merging, unless a project supplement names PRs it may merge |
508
+ | Rebasing lanes after a merge, with their checks re-run | Database migrations and writes against a shared environment | Confirming a chained git command or storing a permission |
509
+ | Writing your decisions into the plan or the board | Ending an agent, closing a lane, a hand-over to a fresh chat | Steering herdr from outside herdr |
510
+
511
+ Who merges follows the project's own rules: by default you, unless the project supplement (`.claude/project/orchestrate.md`) names PRs the coordinator may merge itself and under which conditions.
512
+
513
+ A permission dialog inside a lane the coordinator confirms itself only for the commit and push of the lane's own branch under its PR approval, and for the fetch, rebase and lock-file steps after a merge, always with the one-time option. Every other dialog goes to you.
514
+
515
+ #### The watcher
516
+
517
+ Jack does not sit and stare at every pane. He waits, and barks when something moves: a lane is blocked or done, a check turns red, a pull request gets feedback.
518
+
519
+ **You do not start the watcher.** The coordinator starts `jh watch --stream` once under Claude Code's Monitor tool, which turns every line it prints into a message to the coordinator, and starts it again when the monitor ends after 30 minutes.
520
+
521
+ Why it exists: the coordinator is a chat, and it only acts when something arrives in it. Without the watcher it would have to read every lane's pane in turn, or you would have to tell it that a lane is waiting. `jh watch` does the waiting instead and prints a report when there is something to do:
522
+
523
+ - a lane's agent turned `blocked`, `done` or `idle`, or left herdr's list; a blocked lane adds the duty to vet its question. Under the lane's line stands its report file (`.claude/handover/<lane>-report.md` in its worktree: `report <path>` and its first 15 lines) when the lane wrote one since the last shown, else its screen without Claude Code's input box and status bar; a blocked lane always shows its whole screen, since the dialog is the news
524
+ - a lane's pull request was merged (with a duty line per lane: rebase, next phase, or a reason), or its checks turned red or green
525
+ - one of your own open pull requests on a branch no lane has, such as a documentation or `chore` PR the coordinator opened itself, was merged or its checks turned red or green: `=== checks failed #<n> (branch <name>)`; a merge asks every lane to rebase. The watcher finds them with one search for the pull requests of the account `gh` runs as (`--author @me`); the first sighting is a baseline
526
+ - a deploy run ended, with its target, when the config sets `watch.deploy`; the run of a merged pull request without a lane names its number, `(push, #<n>)`
527
+ - new feedback on a lane's open pull request: a review, an inline comment, a pull request comment, or one edited since (`watch.feedback`)
528
+ - a deploy preview for a lane's new head commit, with its link (`watch.previews`)
529
+ - a new or changed entry of an external service, such as a ticket comment, read by a command the config names (`watch.sources`)
530
+ - `gh` became unreachable
531
+ - a lane's context fill rose to `warn` or `alert` (`=== lane <name> alert 47 %`, with a duty line at `alert`: request the lane's hand-over at its next phase boundary), against the lanes' thresholds in `plans.context`
532
+ - the coordinator's own context fill rose to `warn` or `alert`, so it hands itself over in time
533
+ - `lane added` / `lane removed` when a worktree with an agent or a board row appears or goes
534
+ - the heartbeat, a status line every 15 minutes (`--heartbeat`)
535
+
536
+ Without `--board` the watcher chooses the board by the lanes it finds: the board whose table names the most found lanes (a worktree with a herdr agent of its name), then the most recently changed (last commit on the base branch, else the file's change time). The first line ends with the board it took (`armed: … board docs/plans/board-x/board.md`); when several boards were in question, the next line names the reason and the others, and `--board <path>` overrides. `jh plans` chooses the same way and shows that line with its errors.
537
+
538
+ After a herdr server restart, panes come back as shells in their directories and the plans views are gone. `jh plans --ensure` brings them back beside the coordinator and each lane. Nobody has to run it by hand: inside herdr the watcher runs it at its start, for every `lane added` and whenever a lane's agent appears, so a new lane gets its view too (a lane without its agent waits, so its shell stays free for the agent), and the kit's herdr plugin runs it after a server start (herdr's `[[startup]]` hook), once herdr answers for every pane (up to 10 s; the terminals take about a second) and with up to four tries 2 s apart. A pane herdr lists an agent for is never taken as a free shell, also in the seconds after a restart before the agent's process is back. The watcher prints a line per view it started; a failure prints `plans --ensure failed, still watching: <reason>` once and does not stop the watch. It finds the coordinator by its herdr name, else by the calling pane, else as the only agent in the main checkout.
539
+
540
+ If the coordinator's herdr name is gone (a resumed session keeps the pane, not the name), the watcher reads the fill over its own pane (`HERDR_PANE_ID`) and the status line says `pane without a name, herdr agent rename "$HERDR_PANE_ID" <name>`. Without such an agent it says `no agent named <name>` instead of `null`. The watcher never renames an agent; `jh context --coordinator` reports the same on stderr.
541
+
542
+ Lanes are found, not listed: without lane names the watcher watches every directory under the worktree directory whose name a herdr agent carries or a board row lists. Every report ends with `status:`, one line per lane (status, `context warn` or `context alert` once its fill passed a threshold, branch, PR with checks and the feedback reported since its last push, open phase with its starts-after entry) and one for the coordinator, which ends with `· board: <n> rows only in the checkout` while the board file of the main checkout has rows the base branch does not, a reminder to get them merged. While you have open pull requests without a lane, one more line follows, `other PRs: #<n> passed · #<m> failed`, newest first, at most five and `+<k>` for the rest. When neither a lane line nor that line changed since the last report it is the single line `status: unchanged · <coordinator line>`, and a heartbeat report always shows the whole block. When the installed kit changes version, the line after `=== kit updated <old> → <new>` names the changed sections of the orchestrate skill and the diff file (`orchestrate skill: § 4 changed · diff <path>`), so the coordinator reads the change, not the whole skill; `diff unavailable` sends it back to the whole file. Without `--stream` the watcher ends after one report, and its last line says so.
543
+
544
+ `jh watch --heartbeat 0.05 search` in an example repository, without herdr agents and without a remote, so `gh` fails (the coordinator's name `coordinator` has no herdr agent either):
545
+
546
+ ```text
547
+ armed: search=no agent board docs/plans/board-search/board.md
548
+ === gh unavailable: gh pr list: no git remotes found
549
+ status:
550
+ search no agent · no branch · no PR · open phase 1 Index (starts after: now)
551
+ coordinator coordinator no value (no agent named coordinator)
552
+ gh unavailable since 11:24:31
553
+ ```
554
+
555
+ As a stream (`--stream`) it keeps running, and the heartbeat is one line:
556
+
557
+ ```text
558
+ armed: search=no agent board docs/plans/board-search/board.md
559
+ === heartbeat 11:22:09 · search no agent
560
+ === heartbeat 11:22:19 · search no agent
561
+ stopped by SIGTERM
562
+ ```
563
+
564
+ **Monitor fallback.** Claude Code does not offer the Monitor tool on Amazon Bedrock, Google Cloud's Agent Platform (Vertex) or Microsoft Foundry (`CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`), nor with `DISABLE_TELEMETRY` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` set. There the coordinator runs the watcher without `--stream` as a background Bash call and starts it again after every report; the last line of each report says so when the environment shows one of those settings.
565
+
566
+ **Start guard.** In a project install, the kit's `watch-guard` hook refuses a stream start in a plain Bash call, a one-shot start outside a background task and one sent off with `&`, where the reports would go unseen; a command that only names the watcher, such as a search or a commit message, passes. A global install ships no hook; there the skill text and the last line of a single report carry the rule.
567
+
568
+ **State.** The words of the status line follow `responseLanguage`. Lock and state live in `$TMPDIR/jackherd-watch-<uid>/<repository>-<hash>/`: a new start replaces the watcher of the same repository, and coordinators in two repositories run side by side. The state is saved after every GitHub read and survives a restart, so a lane that stopped while no watcher ran fires at once.
569
+
570
+ **Deploy reports** need the workflow and the environments that count as targets; without `watch.deploy` no deploy run is read:
571
+
572
+ ```json
573
+ { "watch": { "deploy": { "workflow": "deploy.yml", "environments": ["stage", "production"] } } }
574
+ ```
575
+
576
+ **Feedback reports** are on unless the config sets `"watch": { "feedback": false }`. With every GitHub read the watcher asks GitHub's REST API for the pull request comments and inline comments of the repository updated since its last read, and for the reviews of each open lane pull request, then reports what it has not seen yet, one event per pull request, author and form, with one line per entry (its file and line for an inline comment, the first line of text that is no Markdown heading):
577
+
578
+ ```text
579
+ === feedback #<n> (search) review-bot[bot] inline · 2 new
580
+ src/index.ts:41 The cache key ignores the locale
581
+ src/index.ts:88 Missing test for an empty query
582
+ === feedback #<n> (search) review-bot[bot] comment edited · 1 new
583
+ No issues found.
584
+ ```
585
+
586
+ The form is `review <state>` (`approved`, `changes requested`, `commented`), `inline`, `comment`, or either of the last two with `edited`; a review without text that only carries inline comments is left out, its comments report it. The account `gh` runs as is never reported, so the lanes' own answers do not wake the coordinator; neither are the logins in `watch.feedback.ignore`, for bots that comment on every push such as a status bot (case is ignored); a bot that links deploy previews belongs in `watch.previews.authors` instead, below:
587
+
588
+ ```json
589
+ { "watch": { "feedback": { "ignore": ["status-bot[bot]"] } } }
590
+ ```
591
+
592
+ A pull request seen for the first time, and every pull request on a first start without saved state, is a baseline: what it already carries is not reported. Seen entries and their last change are part of the saved state, so a restart reports nothing twice and an edited comment is reported again. A REST read that fails reports nothing and the next one reaches back over the gap. The cost is two calls per GitHub read plus one per open lane pull request, against GitHub's 5000 REST calls per hour.
593
+
594
+ **Preview reports** are off unless the config sets `watch.previews`. Once a lane pushes a new head commit and its deploy preview is updated after it, the watcher reports the link, once per head commit:
595
+
596
+ ```text
597
+ === preview #<n> (search) https://<host>/ · 3f2c1a9
598
+ ```
599
+
600
+ Two sources, either or both. `deployments: true` reads GitHub's deployments of each head commit that has no preview reported yet: its newest deployment outside production and outside `watch.deploy.environments` counts, and once its latest status is `success` it names its `environment_url`. `authors` lists the logins whose pull request comments link a preview; their comments, reviews and inline comments are never feedback, also with `watch.feedback` off, so list a bot that only posts previews, not one that also reviews. The URL is the first group of `urlPattern` (or its whole match), else the first `https://` link in the comment; nothing else of the comment is passed on, and only an `https:` URL of at most 200 characters counts:
601
+
602
+ ```json
603
+ { "watch": { "previews": { "deployments": true, "authors": ["preview-bot[bot]"], "urlPattern": "Preview: (\\S+)" } } }
604
+ ```
605
+
606
+ An edit without a new head commit reports nothing, so a bot that updates one comment per push wakes the coordinator once per push. A pull request seen for the first time is a baseline, as with feedback, and the reported head is part of the saved state. Where a bot first edits its comment to say the build is running and the link is already in it, that edit is the one reported, before the preview is ready. An edit read before GitHub's list shows the new head counts for the old head, and the new head is reported only at the next edit. A head costs one deployment list and at most one status call per GitHub read, and after a report no deployment is read for that pull request until its next push; the comments come from the feedback read, so they cost no call of their own while feedback is on.
607
+
608
+ **Source reports** are off unless the config lists `watch.sources`. The watcher cannot use the MCP servers of a Claude Code session: their connections and logins (often OAuth in a browser) belong to the session. A source is a command instead, which prints JSON on stdout. It runs without a shell, in the main checkout, with your environment, so its token stays where it is. `/follow` sets one up for you (`/follow T-123`): it asks what to follow, checks the tool and its login, fills a recipe from below, shows a dry run and writes the source only after you agreed.
609
+
610
+ ```json
611
+ {
612
+ "watch": {
613
+ "sources": [
614
+ {
615
+ "name": "tickets",
616
+ "every": 5,
617
+ "command": ["tickets-cli", "search", "--query", "key in (T-123)", "--json"],
618
+ "items": ".issues",
619
+ "map": {
620
+ "id": ".key",
621
+ "updatedAt": ".fields.updated",
622
+ "title": ".fields.summary",
623
+ "body": ".fields.comment.comments[-1].body",
624
+ "author": ".fields.comment.comments[-1].author.name"
625
+ },
626
+ "ignoreAuthors": ["me"],
627
+ "lane": "search"
628
+ }
629
+ ]
630
+ }
631
+ }
632
+ ```
633
+
634
+ ```text
635
+ === source tickets (search) T-123 Decide the layout · updated
636
+ Yes, take variant B.
637
+ https://<host>/browse/T-123
638
+ ```
639
+
640
+ `items` is the path of the list in the output (without it the output is the list), `map` picks each entry's fields: `id` and `updatedAt` are required, `title`, `url`, `body` and `author` optional, and any further field you name (`status`, `priority`, `assignee`; a letter, then letters, digits and `_`) is read for `reportOn`. A path is `.` for the whole value or a chain of `.key`, `[n]` and `[-n]` (counted from the end), nothing more; for anything else, put a script in between.
641
+
642
+ What counts as news: an unknown `id` reports `new`; `updated` only when what you read changed, a short hash over `title`, `author`, `body` and `url`. A change of the time alone (a label, a sprint, a status) wakes nobody; where none of the four is mapped, `updatedAt` is the content. An entry whose `author` is in `ignoreAuthors` (case ignored) is recorded but wakes nobody, so your own comment does not. The first successful run of a source is a baseline, and the saved state holds per source only each id with its hash (with `reportOn` also the values of those fields, which may name people, such as an assignee), the 1000 seen most recently, so a restart repeats nothing and an entry that falls out of a time window and comes back is not new. Everything one GitHub read finds goes into one report, at most `reportLimit` entries per source (`5`), the rest as one line `=== source tickets +3 more`. The body shows its first line with text that is no Markdown heading (a bot's `## Code review` is skipped; a body of headings only shows the first), at most 160 characters, and the URL only as an `https:` URL without user name or password. `every` is in minutes, at least 1, `5` by default; the sources run with the GitHub read, so at most once a minute, and after a restart each runs at once.
643
+
644
+ **Report a status change.** `reportOn` names the mapped fields whose change reports, in place of the content hash: with `"reportOn": ["status"]` a ticket that moves from one column to the next wakes the coordinator, and a new comment alone does not. The report names each changed field with its old and new value, cut to 60 characters, `(empty)` for none, in one line; the body follows only when `body` is one of the changed fields. An unknown `id` still reports `new`, and `ignoreAuthors`, `reportLimit` and the one report per read stay as they are. `status` is just a field name: any mapped field but `id` may be listed, including `title` or `body`.
645
+
646
+ ```json
647
+ "map": { "id": ".key", "updatedAt": ".fields.updated", "title": ".fields.summary", "status": ".fields.status.name", "assignee": ".fields.assignee.displayName" },
648
+ "reportOn": ["status", "assignee"]
649
+ ```
650
+
651
+ ```text
652
+ === source tickets (search) T-123 Decide the layout · updated: status In progress → Ready for Dev, assignee (empty) → alex
653
+ https://<host>/browse/T-123
654
+ ```
655
+
656
+ At most 10 fields. For this the saved state keeps the value of each `reportOn` field on one line, as the report shows it (up to 200 characters, a longer one as its first 180, `…` and a hash of the whole, so any change shows). A field added to `reportOn` later, or a source that gains `reportOn`, starts from what it reads then and reports nothing for that first read; a state from an earlier version is read as it is, and dropping `reportOn` reports nothing either.
657
+
658
+ A run that ends with an exit code other than 0, runs past 30 seconds (then its whole process group is killed, a `curl` behind a script included), prints more than 1 MB, cannot be read, prints no JSON or not the configured shape reports once per streak, with the reason only and never stderr, which may carry a token. While it fails, the status block ends with `source tickets failing since 10:15:00`; the watcher and the other sources go on:
659
+
660
+ ```text
661
+ === source tickets failed: exit 1
662
+ ```
663
+
664
+ **How you hear about it.** The report goes to the coordinator, as a `=== source …` event of its watcher. It passes you one line with the entry, its gist and the link. A decision inside it, such as a product owner's answer, it takes over only after you confirmed it, since the text comes from outside. You hear of it only while the coordinator's session and its watcher run; what happened in between is reported at the next start. There is no desktop notification of its own: you get the one herdr shows for any lane that needs you. What a source reports is a change in the mapped content fields (`title`, `author`, `body`, `url`), or with `reportOn` a change of the fields it names, such as a status.
665
+
666
+ **Try a source before the watcher runs it.** `jh watch source <name> --dry-run` runs a configured source once; a draft not yet in the config comes as a file (`jh watch source draft.json --dry-run`, one source object) or on stdin (`-`). It lists the entries (`<id> · <author> · <title>`), says that the watcher's first run of the source is a baseline and reports nothing, and shows one entry as a later report would print it; it neither reads nor changes the watcher's state. A field of `map` that is empty in every entry is named with its path (`map.updatedAt: in 23 of 23 entries empty (.fields.updated)`), read from the raw output, so a run that failed on a missing `id` or `updatedAt` names it too; the dry run then ends with exit code 1, as for a failed run, and `/follow` does not write the source. Without entries it says `0 entries: map not checked`. `--raw` adds the command's output, for finding the paths of `items` and `map`:
667
+
668
+ ```text
669
+ dry run source tickets: ["tickets-cli","search","--query","key in (T-123)","--json"]
670
+ 2 entries
671
+ T-123 · alex · Decide the layout
672
+ T-124 · me · Pick a colour (ignoreAuthors)
673
+ first run of this source: baseline, nothing reported; later a new or changed entry reports like this:
674
+ === source tickets (search) T-123 Decide the layout · new
675
+ Yes, take variant B.
676
+ watcher state not read or changed; the watcher takes a new source over at its next start
677
+ ```
678
+
679
+ The watcher reads `watch.sources` when it starts: a new or changed source counts from its next start.
680
+
681
+ Three limits stand: the watcher cannot make a command read only, so name only commands that read. A source in `.jackherd/config.json` runs for everyone who starts the watcher in the repository, at the same level of trust as `check.checks` and hooks. The watcher reads the config of the main checkout, never the one of a lane's worktree; a dry run started inside a worktree reads that worktree's config first. And `jh watch source … --dry-run` runs whatever command its draft names, so it never belongs in a permission allowlist.
682
+
683
+ ##### Recipes
684
+
685
+ Each recipe is one `watch.sources` entry; put in your key, project or query, then try it with `--dry-run` (and `--raw` where the shape differs from what is shown). A token never goes into an argument: an argument shows in the process list. Where a tool keeps its own login, it is used; where a token sits in an environment variable, a small script hands it to `curl` on stdin (`-H @-`), because the watcher passes `$VARIABLE` in an argument on as it is.
686
+
687
+ **Jira with the Atlassian CLI.** Login: `acli jira auth login`, kept by `acli`. `acli jira workitem search` cannot return `updated` or `comment`, so a script searches for the keys and reads each one with `view`, a few at a time to stay well under the 30 seconds a source may run. It joins the text and mention nodes of the comment's document tree into plain text. The script, for example `scripts/sources/jira.sh`, with your project in the query and `<host>` your `<site>.atlassian.net`:
688
+
689
+ ```bash
690
+ #!/usr/bin/env bash
691
+ set -euo pipefail
692
+ jql='project = PROJ AND assignee = currentUser() AND statusCategory != Done'
693
+ acli jira workitem search --jql "$jql" --limit 100 --json |
694
+ jq -r '.[].key' |
695
+ xargs -r -P 6 -I{} acli jira workitem view {} --fields summary,updated,comment --json |
696
+ jq -s 'map({
697
+ key,
698
+ url: ("https://<host>/browse/" + .key),
699
+ updated: .fields.updated,
700
+ summary: .fields.summary,
701
+ author: (.fields.comment.comments[-1].author.displayName // null),
702
+ body: (.fields.comment.comments[-1].body
703
+ | if . == null then null
704
+ else [.. | objects | select(.type == "text" or .type == "mention") | (.text // .attrs.text)] | join("")
705
+ end)
706
+ })'
707
+ ```
708
+
709
+ `set -euo pipefail` is what makes a failed `acli` call arrive as an error: without it the pipe ends in an empty list, and the watcher would take a lost login for a source with nothing new.
710
+
711
+ ```json
712
+ {
713
+ "name": "tickets-decisions",
714
+ "command": ["bash", "scripts/sources/jira.sh"],
715
+ "map": {
716
+ "id": ".key",
717
+ "updatedAt": ".updated",
718
+ "title": ".summary",
719
+ "url": ".url",
720
+ "body": ".body",
721
+ "author": ".author"
722
+ }
723
+ }
724
+ ```
725
+
726
+ **Jira over REST with `curl`.** One call returns everything, where the CLI needs one call per item, but it needs an API token. Token: an API token of your Atlassian account in `~/.netrc` (`machine <site>.atlassian.net login <email> password <api-token>`, mode 600), which `curl --netrc` reads without a shell; `<host>` is the same `<site>.atlassian.net`. Version 3 of the API returns a comment body as a document tree, so the path reaches into its first paragraph:
727
+
728
+ ```json
729
+ {
730
+ "name": "tickets-decisions",
731
+ "command": ["curl", "-fsS", "--max-time", "20", "--netrc", "-G", "https://<host>/rest/api/3/search/jql", "--data-urlencode", "jql=key in (PROJ-123)", "--data-urlencode", "fields=summary,updated,comment"],
732
+ "items": ".issues",
733
+ "map": {
734
+ "id": ".key",
735
+ "updatedAt": ".fields.updated",
736
+ "title": ".fields.summary",
737
+ "body": ".fields.comment.comments[-1].body.content[0].content[0].text",
738
+ "author": ".fields.comment.comments[-1].author.displayName"
739
+ }
740
+ }
741
+ ```
742
+
743
+ **Linear over GraphQL.** Token: a personal API key in `LINEAR_API_KEY`, sent as `Authorization: <key>` without `Bearer`. The script, for example `scripts/sources/linear.sh`, with `<host>` the API host `api.linear.app`:
744
+
745
+ ```sh
746
+ #!/bin/sh
747
+ query='{"query":"{ issues(filter: { team: { key: { eq: \"ENG\" } }, number: { eq: 123 } }) { nodes { identifier title url updatedAt comments { nodes { body user { name } } } } } }"}'
748
+ printf 'Authorization: %s\n' "$LINEAR_API_KEY" |
749
+ curl -fsS --max-time 20 -H @- -H 'Content-Type: application/json' --data "$query" "https://<host>/graphql"
750
+ ```
751
+
752
+ ```json
753
+ {
754
+ "name": "linear-eng-123",
755
+ "command": ["sh", "scripts/sources/linear.sh"],
756
+ "items": ".data.issues.nodes",
757
+ "map": {
758
+ "id": ".identifier",
759
+ "updatedAt": ".updatedAt",
760
+ "title": ".title",
761
+ "url": ".url",
762
+ "body": ".comments.nodes[-1].body",
763
+ "author": ".comments.nodes[-1].user.name"
764
+ }
765
+ }
766
+ ```
767
+
768
+ Check with `--raw` which end of `comments.nodes` holds the newest comment; `[0]` instead of `[-1]` if it is the first.
769
+
770
+ **GitHub issues.** Login: `gh auth login`, kept by `gh`:
771
+
772
+ ```json
773
+ {
774
+ "name": "decisions",
775
+ "command": ["gh", "issue", "list", "--repo", "<owner>/<repo>", "--label", "decision", "--json", "number,title,url,updatedAt,comments"],
776
+ "map": {
777
+ "id": ".number",
778
+ "updatedAt": ".updatedAt",
779
+ "title": ".title",
780
+ "url": ".url",
781
+ "body": ".comments[-1].body",
782
+ "author": ".comments[-1].author.login"
783
+ },
784
+ "ignoreAuthors": ["<your-login>"]
785
+ }
786
+ ```
787
+
788
+ **Sentry.** Token: an auth token with `event:read` in `SENTRY_AUTH_TOKEN`; `<host>` is `sentry.io` or your own. The script, for example `scripts/sources/sentry.sh`:
789
+
790
+ ```sh
791
+ #!/bin/sh
792
+ printf 'Authorization: Bearer %s\n' "$SENTRY_AUTH_TOKEN" |
793
+ curl -fsS --max-time 20 -H @- "https://<host>/api/0/projects/<org>/<project>/issues/?query=is:unresolved&statsPeriod=24h"
794
+ ```
795
+
796
+ ```json
797
+ {
798
+ "name": "errors",
799
+ "every": 10,
800
+ "command": ["sh", "scripts/sources/sentry.sh"],
801
+ "map": { "id": ".id", "updatedAt": ".lastSeen", "title": ".title", "url": ".permalink", "body": ".culprit" }
802
+ }
803
+ ```
804
+
805
+ `lastSeen` moves with every event, but title, culprit and link do not, so only a new issue wakes the coordinator, not each occurrence.
806
+
807
+ **A status page.** A page on the common status page service answers its public API without a token; `<host>` is the page's own:
808
+
809
+ ```json
810
+ {
811
+ "name": "provider-status",
812
+ "every": 5,
813
+ "command": ["curl", "-fsS", "--max-time", "20", "https://<host>/api/v2/incidents/unresolved.json"],
814
+ "items": ".incidents",
815
+ "map": { "id": ".id", "updatedAt": ".updated_at", "title": ".name", "url": ".shortlink", "body": ".incident_updates[0].body" }
816
+ }
817
+ ```
818
+
819
+ The newest update stands first in `incident_updates`, so each new update of an open incident reports `updated` with its text.
820
+
821
+ #### Hand-over to a fresh chat
822
+
823
+ A long chat answers worse and gets cut off. Jack hands over to a fresh one before that, with a note of the state. Both sides hand over at a quiet moment, never in the middle of a task:
824
+
825
+ - **A lane**, at a phase boundary: `/implement` reads its own fill with `jh context`. At `warn` or `alert`, and with a next phase to go, it asks whether that phase runs in a new chat. The watcher reads the lanes' fills too, once a minute, and reports a rise; at `alert` the coordinator asks the lane for its hand-over at the end of its current phase, so a long phase cannot run past the threshold unnoticed, and the lane then writes it without asking. On yes it writes `.claude/handover/<lane>.md` (state, open points, next step, traps, links; ignored by git). The coordinator asks you, ends the agent, starts a new one with the same model in the same worktree, and gives it the hand-over file as its first prompt.
826
+ - **The coordinator**, when the watcher reports `coordinator warn`: it records the state of every lane in the board's history, writes `.claude/handover/coordinator.md` and asks you. On yes it runs a script that waits until the coordinator is idle, sends `/clear`, and types `/orchestrate <board>` with a pointer to the hand-over only once herdr shows a new session id for the pane. If any step fails, the script stops, logs the reason and the two inputs to type by hand to `.claude/handover/coordinator.log`, and shows a herdr notice.
827
+
828
+ Decisions never live only in a hand-over note: they are in the plan or the board first.
829
+
830
+ #### Lane setup from the config
831
+
832
+ - `commands.install` (default `bun install`, or what the lock file implies) installs a new lane's dependencies.
833
+ - `commands.dev` (default `bun run dev`) runs as `PORT=<port> <dev>` in the lane's dev-server pane.
834
+ - `lanes.copy` lists the untracked files every worktree needs (env files, say); with `lanes.copy` empty, the files `.worktreeinclude` lists are copied. Each file keeps its mode.
835
+ - `lanes.lockFile` (default `bun.lock`) is the file a rebase takes from the base branch and regenerates.
836
+ - `worktreeDirectory` (default `.claude/worktrees`) is where lane worktrees go.
837
+
838
+ In a repository that ignores `docs/`, plan and board exist only in the main checkout and the plan view reads them there; list the plan in `lanes.copy` so the lane has it to work from and to tick off its own progress.
839
+
840
+ The herdr plugin `zerodice0.worktree-bootstrap`, when enabled and configured in the main checkout, can copy files and run setup commands for new worktrees; the coordinator checks its result and does the step itself when it is missing.
841
+
842
+ </details>
843
+
844
+ <details>
845
+ <summary><b>Plan view and context</b></summary>
846
+
847
+ ### Plan view and context
848
+
849
+ `jh plans` is the plan files read for you: one live view of every lane of a board, kept open in a terminal or a herdr pane beside your sessions. The coordinator opens it beside its own pane and one per lane (`--lane <name>`).
850
+
851
+ - [What it shows](#user-content-what-it-shows)
852
+ - [Attachments](#user-content-attachments)
853
+ - [Where the numbers come from](#user-content-where-the-numbers-come-from)
854
+ - [Keys](#user-content-keys)
855
+ - [Context fill](#user-content-context-fill)
856
+
857
+ #### What it shows
858
+
859
+ One block per lane of the chosen board, lanes that wait on you first:
860
+
861
+ - the agent's state: `❓ blocked` (waits for you), `📨 done` (the agent finished its answer, not the lane), `⏸ idle`, `⚡ working`, or `·` no agent
862
+ - how full the agent's context is, and a bar with the lane's ticked tasks
863
+ - the branch, commits ahead and uncommitted files
864
+ - the lane's open pull request with its checks
865
+ - per plan its attachments as chips (`📎`), the lane's phases with their counts, and the next open task of the current phase. The current phase is the one the lane's newest board row names: an open phase whose ticks in the worktree differ from the base branch, else one it ticked to the end there, shown as `ticked, not merged` until its ticks reach the base branch (in the sidebar title and the watcher's status line too), else its first open phase. A phase the newest row repeats from an earlier row counts as named. Ticks in older phases, say from tidying the plan, decide only when the newest row names no phase or only merged ones: then the same order runs over all rows, and without a touched phase the open phase of its newest row counts
866
+
867
+ Three lanes of the example board in [Planning](#user-content-boards), with herdr agents, a pull request each for two of them and a mockup and a Figma link in the search plan, rendered by the package's own layout from example data at 80 columns:
868
+
869
+ ```text
870
+ Board 01 11:38
871
+ ────────────────────────────────────────────────────────────────────────────────
872
+ ▾ ❓ search-ui blocked 🧠 62 % │ ██░░░░░░░░ ✅ 1/5
873
+ ⎇ feat/search-ui ↑1 ±4
874
+ │
875
+ └─ Product search
876
+ ├─ 📎 📄 Plan ✦ Mockup ◈ Search box
877
+ │
878
+ ├─ ▸ P2 Search box 1/4
879
+ └─ ▸ P3 Docs 0/1
880
+ └─ ▶ Search flow
881
+ ────────────────────────────────────────────────────────────────────────────────
882
+ ▸ 📨 checkout-fee done ↗ #42 💬 2 🧠 48 % │ ██████████ ✅ 6/6
883
+ ────────────────────────────────────────────────────────────────────────────────
884
+ ▾ ⚡ search-index working 🧠 34 % │ ████████░░ ✅ 4/5
885
+ ⎇ feat/search-index ↑3 ±2
886
+ ↗ #41 … checks 🌐 preview 💬 1 new Search index
887
+ │
888
+ └─ Product search
889
+ └─ ▸ P1 Index 4/5
890
+ └─ ▶ /uncle-bob
891
+ ────────────────────────────────────────────────────────────────────────────────
892
+ ↑↓ select →← open a all n notes s html q quit ? help
893
+ ```
894
+
895
+ Right after `init`, on the example plan and board, without herdr and without a remote, both lanes have no agent yet and land in one group; `search-index` has 2 of its 5 boxes ticked (tasks and review gate), `search-ui` none. This is `plans --once`:
896
+
897
+ ```text
898
+ Board 01 11:38:08
899
+ ───────────────────────────────────────────────────────────────────────────────
900
+ ───────────────────────────────────────────────────────────────────────────────
901
+ · no agent (2) search-index 2/5 · search-ui 0/5
902
+ ───────────────────────────────────────────────────────────────────────────────
903
+ ▸ planned (3 phases in 1 plan)
904
+ gh pr list: no git remotes found
905
+ origin/main not found, reading plans from the checkout
906
+ ───────────────────────────────────────────────────────────────────────────────
907
+ ↑↓ select →← open a all n notes s html q quit ? help
908
+ ```
909
+
910
+ #### Attachments
911
+
912
+ Under each plan's title, a `📎` line carries the plan's links as chips; a click opens one in the browser:
913
+
914
+ - `📄 Plan` opens the board's HTML page at that plan: its status, its chips and its phases with their checkboxes. The view writes the page when it is missing or the plans changed.
915
+ - Links of a known kind become chips of their own: `✦` a Claude artifact (a mockup, say), `◈` Figma, `⑂` a GitHub pull request or issue. The chip's text is the link text, else the bold label before the link (`**Mockup:** <url>`), else the kind's name. A link on a line that says it is private carries that mark.
916
+
917
+ Nothing is fetched to find them; the kinds are URL patterns, and `plans.linkKinds` in the config replaces them with your own (`pattern`, `label`, `icon`).
918
+
919
+ #### Preview and feedback
920
+
921
+ A pull request row reads `↗ #<n> ✓ checks`, then the watcher's badges, then the title, cut with `…` beside the badges and wrapped under itself without them; the number is a grey badge linked to the pull request. While the watcher runs (`jh watch`), `🌐 preview` opens the deploy preview the watcher reported for the pull request's current head (`watch.previews`), `💬 <n> new` in amber opens the pull request and counts the feedback since its last push that `watch.feedback` does not ignore, and `✋ changes requested` in dark red opens it while a reviewer's latest decisive review requests changes, which a push does not end; an ignored author counts there, since their review blocks the merge all the same. Only when number, checks and badges leave no room for a title do the badges move to a line below, starting in the column of the number. A folded lane shows each pull request as a badge, `↗ #42 💬 2` (amber while there is feedback, the count only above zero; `↗ #42 💬 2 ✋` in dark red while changes are requested), in one column right before the bar, the same for every lane. The words follow `responseLanguage` (`💬 3 neu`, `✋ Änderungen verlangt` in German). Everything comes from the watcher's saved state; the view makes no GitHub call for it. Without a running watcher, with a state older than three minutes (three missed GitHub reads), after a push until the watcher reports the new head, or for a preview recorded before 0.26.0 there is no such badge; while a lane has an open pull request, the header then says why beside the clock: `watcher off` or `state 4 min old`.
922
+
923
+ #### Where the numbers come from
924
+
925
+ - **Plans and board** are read at `origin/<baseBranch>`, and a lane's own phases from its worktree once it has changes there. A phase no row of the lane names comes from the worktree too when it is new there or ticked further than on the base branch; one with fewer ticks there stays at the base, since the lane holds an old copy. Lanes and `↳` steps that only the checkout's board has are added to what the base branch says, so a row written a minute ago shows at once, without a mark: that they still wait for a merge is the coordinator's to track, and its watcher reminds it (`board: 2 rows only in the checkout` after its coordinator line). Rows both boards have, and a board missing on the base branch (read whole from the checkout, with its notice), are unchanged. The view fetches the base branch in the background every 5 minutes (`plans.fetchIntervalMinutes`, `0` turns it off) and redraws when a plan file changes or a fetch lands.
926
+ - **Pull requests and checks** come from `gh`. Without `gh` the view runs and says why the PRs are missing.
927
+ - **Agent state and context fill** come from herdr, with `"terminal": { "adapter": "herdr" }` in the config. Without it, every lane shows no agent.
928
+ - **A running lane without a board row**, neither at the base branch nor in the checkout, shows as `no row in lanes`, without plan and phases.
929
+
930
+ While it runs inside herdr, the view also sets each lane's sidebar title (`P4 · 7/12`) and clears it on exit.
931
+
932
+ #### Keys
933
+
934
+ | Key | Does |
935
+ | --- | --- |
936
+ | `↑` `↓` | select a lane, phase or task |
937
+ | `→` `←` | open or close the selected row |
938
+ | `a` | show or hide what is already ticked |
939
+ | `n` | show or hide a lane's notes |
940
+ | `s` | write the board as one HTML page with every lane and plan, which also shows which lane waits for which, and open it |
941
+ | click, `enter`, `space` | on a lane or phase: open or close it; on a task: show its whole text; on a chip, a badge or a `↗` pull request: open it in the browser |
942
+ | `?` | the legend |
943
+ | `q` | quit |
944
+
945
+ Options: `--once` prints one frame and exits (for reading the view from a script or a session), `--ensure` brings back the missing views beside the coordinator and each lane of the board (an empty shell in that tab takes the view, else the agent's pane is split) and prints what it did; the watcher does the same at its start, for every new lane and when a lane's agent appears (a lane without its agent waits), so a view is never started by hand, `--lane <name>` shows one lane with its current phase open, and a board path picks another board. Without one, the board is chosen as the watcher chooses it: the one that names the most lanes with a worktree and a herdr agent, then the most recently changed; the reason shows with the errors when several boards were in question.
946
+
947
+ #### Context fill
948
+
949
+ How full each session's context is, as a number.
950
+
951
+ `jh context [<name>]` prints how full an agent's context is, as one line of JSON, or `null` without a value:
952
+
953
+ ```text
954
+ {"tokens":356307,"window":1000000,"share":0.356307,"model":"claude-opus-5-5","level":"warn"}
955
+ ```
956
+
957
+ The value is read from Claude Code's session log of the agent herdr reports, only from the last answer of the main chain; sub-agent answers do not count. When that log has no answer yet, the newest log of the agent's working directory is used. Session logs do not name the context window, so set `plans.context.windows` per model ID; otherwise 200 000 tokens are assumed (`plans.context.defaultWindow`). A fill above the assumed window moves to the next larger known window, so a session on a larger window is not shown as 100 %.
958
+
959
+ | Who | `warn` from | `alert` from | Config |
960
+ | --- | --- | --- | --- |
961
+ | Lanes | 60 % | 80 % | `plans.context.warnAt`, `alertAt` |
962
+ | Coordinator (`--coordinator`) | 50 % | 70 % | `plans.context.coordinator.warnAt`, `alertAt` |
963
+
964
+ At `warn` a lane offers a fresh chat at its next phase boundary, and the watcher wakes the coordinator to hand itself over ([Hand-over](#user-content-hand-over-to-a-fresh-chat)).
965
+
966
+ </details>
967
+
968
+ <details>
969
+ <summary><b>Reference</b></summary>
970
+
971
+ ### Reference
972
+
973
+ - [Commands](#user-content-commands)
974
+ - [Configuration](#user-content-configuration)
975
+ - [What is included](#user-content-what-is-included)
976
+ - [Status line](#user-content-status-line)
977
+ - [A package you publish from the repository](#user-content-a-package-you-publish-from-the-repository)
978
+
979
+ #### Commands
980
+
981
+ | Command | What it does | Options |
982
+ | ------- | ------------ | ------- |
983
+ | `jh init` | Creates `.jackherd/config.json` and installs the kit | `--global`: into `~/.claude` and `~/.jackherd`; `--yes`/`--no`: answer all its questions (globally the hook and the status line, in a project the prefix and the status line) and take the default [setup](#user-content-setup-questions); a status line of your own is replaced only by a yes in the dialog |
984
+ | `jh sync` | Updates the kit files; edited files get a `.kit-new` beside them | `--global`; `--accept <path>` takes the kit's version; `--global --if-changed` syncs only after a package update and names a newer npm release once a day |
985
+ | `jh update` | Installs the newest release with the package manager that holds the kit globally, then syncs `~/.claude` and the project with the new code | none; does nothing when the installed version is the latest |
986
+ | `jh config` | The values the current repository decides (`test`, `typecheck`, `lint`, `format`, `install`, `dev`, `lockFile`, `baseBranch`), as set or detected | `<name>`: one bare value |
987
+ | `jh check` | Types, lint and tests of the packages you touched, plus the project's own checks; in changed plans, an open checkbox whose `→ P<n>` is not at the end of the line fails, and so does a phase the change adds with tasks but without a review gate (or a `**No review gate:** <reason>` line); in a changed board, a lane row the change adds or edits whose phases cell names no `P<n>` warns; in a changed plan, a phase whose tasks are all ticked on `origin/<baseBranch>`, that the change does not tick further and that still has open boxes without `→ P<n>` warns | `--skip-slow`: lint and fast checks only |
988
+ | `jh plans` | Live view of the board's lanes and plans ([Plan view](#user-content-plan-view-and-context)) | `--once`: one frame and exit; `--lane <name>`: one lane; `--ensure`: bring back the missing views beside the coordinator and each lane, and exit; a board path (default: the newest board) |
989
+ | `jh context` | How full an agent's context is, as one line of JSON, or `null` | `<name>`: the agent (default: this worktree's); `--coordinator`: the coordinator's thresholds |
990
+ | `jh watch` | Waits for the lanes and reports what the coordinator has to do ([The watcher](#user-content-the-watcher)) | `--stream`, `--heartbeat <minutes>`, `--board <path>`, `--coordinator <name>`, lane names (default: found) |
991
+
992
+ Without a global install, run any of them as `bunx jackherd <command>`.
993
+
994
+ #### Configuration
995
+
996
+ `.jackherd/config.json` in a project, `~/.jackherd/config.json` for a global install; a project config always wins. Every command validates it against one schema, and a typo fails every command alike. A project `init` points `$schema` at `schemas/jackherd.schema.json` of the installed package, so an editor completes the keys.
997
+
998
+ | Key | Default | What it decides |
999
+ | --- | --- | --- |
1000
+ | `responseLanguage` | `en` | the language the skills speak with you, and the watcher's status words |
1001
+ | `planLanguage` | `en` | the language of plans and boards, including their headings |
1002
+ | `planDirectory` | `docs/plans` | where plans and boards live |
1003
+ | `board.pattern`, `board.file`, `board.term` | `board-*`, `board.md`, `board` | [Boards](#user-content-boards) |
1004
+ | `worktreeDirectory` | `.claude/worktrees` | where lane worktrees go |
1005
+ | `baseBranch` | detected, else `main` | the branch lanes start from and plans are read at |
1006
+ | `skillPrefix` | `''` (global: `jh-`) | put before every skill and agent name |
1007
+ | `commands.test`, `typecheck`, `lint`, `format`, `install`, `dev` | detected from the lock file | what the skills and `check` run; `dev` runs as `PORT=<port> <dev>` |
1008
+ | `lanes.copy` | `[]` (then `.worktreeinclude`) | untracked files copied into every new worktree |
1009
+ | `lanes.lockFile` | detected, else `bun.lock` | the lock file a rebase takes from the base branch |
1010
+ | `models.plan`, `implement`, `review`, `explore`, `orchestrate` | written by `init` | full model IDs per role; aliases are refused |
1011
+ | `terminal.adapter` | `none` | `herdr` for agent states and context fill |
1012
+ | `plans.fetchIntervalMinutes` | `5` | background fetch of the base branch while `plans` runs |
1013
+ | `plans.linkKinds` | built in | links in a plan the board page shows as chips |
1014
+ | `plans.context` | lanes 60 % / 80 %, coordinator 50 % / 70 %, window 200 000 | [Context fill](#user-content-context-fill) |
1015
+ | `watch.deploy` | none | the deploy workflow and environments the watcher reports |
1016
+ | `watch.feedback` | `{ "ignore": [] }` | `false` turns feedback reports off; `ignore` lists logins whose feedback does not count |
1017
+ | `watch.previews` | none | `deployments` (`false`), `authors` (`[]`) and `urlPattern`: where the watcher reads a lane's deploy preview |
1018
+ | `watch.sources` | `[]` | commands whose JSON output the watcher reports as `source`: `name`, `command`, `every` (`5`), `items`, `map`, `ignoreAuthors` (`[]`), `reportLimit` (`5`), `lane`, `reportOn`; [recipes](#user-content-recipes) |
1019
+ | `check.lintFilesCommand` | detected | lint over the changed files |
1020
+ | `check.checks` | `[]` | project checks (`name`, `command`, optional `globs`) |
1021
+ | `check.reviewerRoutes` | `[]` | which reviewer `/pre-commit` runs for which paths (`reviewer`, `globs`, `why`) |
1022
+ | `check.generatorOwned` | `[]` | files a generator owns (`glob`, `generator`); `check` names the generator when one of them changed |
1023
+ | `check.buildArtifacts` | build output globs | staged files `check` refuses |
1024
+ | `check.publicContent` | none | [published files](#user-content-a-package-you-publish-from-the-repository) |
1025
+ | `check.looseTests` | `false` | also run tests outside any package |
1026
+
1027
+ What usually stays to configure is what no file in the repository says, for example:
1028
+
1029
+ ```json
1030
+ {
1031
+ "responseLanguage": "de",
1032
+ "planLanguage": "de",
1033
+ "skillPrefix": "jh-",
1034
+ "lanes": { "copy": ["apps/web/.env"] },
1035
+ "terminal": { "adapter": "herdr" },
1036
+ "check": {
1037
+ "reviewerRoutes": [{ "reviewer": "jh-uncle-bob", "globs": ["apps/**/*.ts", "apps/**/*.tsx"], "why": "Code" }]
1038
+ }
1039
+ }
1040
+ ```
1041
+
1042
+ #### What is included
1043
+
1044
+ - **Workflow skills:** `start`, `grill`, `plan`, `implement`, `orchestrate`, `follow` ([Planning](#user-content-the-skills-and-what-they-hand-on)).
1045
+ - **Review skills:** `pre-commit`, `review`, `uncle-bob`, `security-audit`, `devops`, `diagnose`.
1046
+ - **Agents:** `plan`, `implement`, `explore`, `spec-review`, `uncle-bob`, `security-audit`, `devops`, `public-content`, each on the model you set per role.
1047
+ - **Rules** in `.claude/rules/kit/`: `code-style`, `testing`, `pull-requests`, `writing-for-agents`, `arc42`, `architecture-files`.
1048
+ - **Hooks** (project install only):
1049
+ - `git-guard` refuses a git command that hides `commit`, `push`, `rebase`, `reset`, `checkout` or `merge` from your permission rules (`git -C … push`, `sh -c "…"`).
1050
+ - `watch-guard` refuses a `jh watch` start whose reports would go unseen.
1051
+ - `format-on-edit` runs the project's own Biome on edited TS, JS and JSON files, when the project has it installed.
1052
+ - **herdr plugin:** a plans pane and an action that opens it ([Installation](#user-content-project-install)).
1053
+
1054
+ Each kit skill reads `.claude/project/<skill>.md` first when it exists; that file extends the skill for the project and wins where the two disagree. A project rule beside `.claude/rules/kit/` extends the kit rule of the same name.
1055
+
1056
+ #### Status line
1057
+
1058
+ Model, context fill and session cost. `init` asks whether to enter `.claude/statusline.sh` in `.claude/settings.json` (`init --global`: `~/.jackherd/statusline.sh` in `~/.claude/settings.json`). A status line of your own is named and kept; only a yes to the question that names it replaces it, `--yes` does not. Its colours follow `plans.context` of the config it finds above the working directory (else `~/.jackherd/config.json`): the lane thresholds inside a worktree, the coordinator's elsewhere, yellow from 60 % and red with a `/compact` hint from 85 % without a config.
1059
+
1060
+ #### A package you publish from the repository
1061
+
1062
+ When the repository publishes a package, `check.publicContent` names its files, and a change to them gets two extra looks before the commit:
1063
+
1064
+ ```json
1065
+ { "check": { "publicContent": { "globs": ["packages/my-kit/**"], "exclude": ["**/*.test.ts"] } } }
1066
+ ```
1067
+
1068
+ - **`jh check`** reads the added lines against names it gathers on every run and never stores: the repositories beside this one (their names, their organisations from the remotes, the ticket keys in their branch names), this project's app folders, package scopes and the domains in its `.env*.example` files. A name the published files already use counts as their own word. A hit names file, line and kind (`names a neighbour repository`), never the name. More work directories and terms can come from `~/.jackherd/public-content.json` (`{ "workDirectories": ["~/work"], "terms": ["…"] }`), which lives outside every repository.
1069
+ - **The `public-content` agent**, which `/pre-commit` starts for such a change, reads only the diff, as a stranger reads the package, and reports what gives away a project, a client, a team or a person. A global install names it `jh-public-content` in `check.publicContent.reviewer`.
1070
+
1071
+ </details>
1072
+
1073
+ <details>
1074
+ <summary><b>Troubleshooting</b></summary>
1075
+
1076
+ ### Troubleshooting
1077
+
1078
+ **`/orchestrate` stops right away.** It runs only inside herdr: `command -v herdr` must find the binary and `HERDR_ENV` must be `1`. Start Claude Code in a herdr pane. For a single session, use `/implement` instead; it still runs independent phases in parallel as subagents.
1079
+
1080
+ **The plan view shows every lane as `no agent`, and no context fill.** Set `"terminal": { "adapter": "herdr" }` in the config. A lane is matched to its herdr agent by name: the worktree directory, the board's lane name and the herdr agent must carry the same name.
1081
+
1082
+ **A lane shows `no row in lanes`.** Neither the board at `origin/<baseBranch>` nor the checkout's board has a row for it. Add the row (the coordinator does this itself when it starts a lane). A row that only the checkout has is shown like any other, not as `no row in lanes`.
1083
+
1084
+ **A lane cannot find its plan.** The plan is not on the base branch yet, and the lane's worktree was cut from there. A lane `/orchestrate` starts gets it: before the first lane, the coordinator checks board and plans against `origin/<baseBranch>` and, after your yes, starts the lanes from a documentation branch that carries them. A worktree made by hand needs the plan merged first.
1085
+
1086
+ **A lane shows no plan.** The view finds a lane's plan only through the Markdown link in the row's plan column. Link the plan once it exists, in the row of every lane that implements one of its phases.
1087
+
1088
+ **The view says `origin/main not found, reading plans from the checkout`.** The remote has no such branch yet, or the base branch is another one. Check `jh config baseBranch`, and set `baseBranch` if detection could not decide (it falls back to `main` when `origin` has both `main` and `master`, or neither).
1089
+
1090
+ **Pull requests and checks are missing.** They come from `gh`. The view and the watcher run without it and print why (`gh pr list: no git remotes found`, say); install and authenticate `gh` for them.
1091
+
1092
+ **A context fill of 100 %, or one that looks too high.** Session logs do not name the context window. Set `plans.context.windows` for your model IDs; otherwise 200 000 tokens are assumed.
1093
+
1094
+ **`jh context` prints `null`.** No terminal adapter, no agent of that name in herdr, or no session log with an answer yet. `null` is no value, never `ok`.
1095
+
1096
+ **The watcher's reports go unseen.** It must run under Claude Code's Monitor tool as `--stream`, or, where the session has no Monitor tool (Bedrock, Vertex, Foundry, or telemetry turned off), without `--stream` as a background call that the coordinator starts again after every report. The first line, `armed: …`, has to arrive as a Monitor event; a stream started in a plain Bash call never ends and is refused by the `watch-guard` hook in a project install.
1097
+
1098
+ **`(watcher <pid> did not stop)`.** The old watcher survived 5 s and two run. Check with `ps -o command= -p <pid>` that it is the watcher, stop it, and start the stream again.
1099
+
1100
+ **`=== kit updated <old> → <new>`, and the watcher ended.** The kit was updated under it. The coordinator reads its skill again and starts the stream again; nothing is lost, the state is on disk.
1101
+
1102
+ **A `<file>.kit-new` appeared after `sync`.** You edited that kit file and the kit changed it too. Merge the two, or take the kit's version with `jh sync --accept <path>`; delete the `.kit-new` after merging by hand.
1103
+
1104
+ **`sync --global` refuses to run.** A global install needs a `skillPrefix` (lower-case letters and digits ending in a dash, such as `jh-`), because a personal skill would otherwise shadow a project skill of the same name.
1105
+
1106
+ **A project `init` or `sync` refuses the directory.** It refuses the home directory, which holds the global install. Run it in the project, or use `--global`.
1107
+
1108
+ **The config is refused.** Every command validates the whole config and names the key. A model alias (`opus`) is refused in favour of a full ID (`claude-opus-5-5`); `sprintPattern` is refused since 0.13 in favour of [`board`](#user-content-boards).
1109
+
1110
+ **`init --global` could not add the hook.** A `settings.json` it cannot read or merge into, or a symlink, stays as it is. The entry to add by hand is in [Installation](#user-content-keeping-the-global-kit-current).
1111
+
1112
+ **`update` changes nothing.** The installed version is the latest, or the kit is a project dependency or a source checkout, which `update` leaves alone. Update the dependency and run `jh sync`.
1113
+
1114
+ **The coordinator's hand-over did not continue.** The script aborts rather than type into the old chat. `.claude/handover/coordinator.log` names the reason and the two inputs to type by hand: `/clear` in the pane, then the line it logged.
1115
+
1116
+ **A lane has no plan to tick in a repository that ignores `docs/`.** Plan and board then exist only in the main checkout. List the plan in `lanes.copy`, so each new worktree gets a copy to work from and tick.
1117
+
1118
+ **`jh check` reports `Nothing changed` after a rebase.** The tree has nothing uncommitted. Run the type check and tests from each package root and the linter on `git diff --name-only origin/<baseBranch>...HEAD` instead; never `git reset --soft` to make the runner see the diff.
1119
+
1120
+ </details>
1121
+
1122
+ ## License
1123
+
1124
+ The code is MIT. See [LICENSE](https://unpkg.com/jackherd@1.0.0/LICENSE); [NOTICE](https://unpkg.com/jackherd@1.0.0/NOTICE) lists the third-party material the kit files adapt. All rights to the artwork in [`assets/branding`](https://unpkg.com/jackherd@1.0.0/assets/branding/README.md) are reserved.