jackherd 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/INSTALL.md +4 -2
- package/{NOTICE → NOTICE.txt} +1 -1
- package/README.md +94 -1033
- package/assets/icons/bark-dark.svg +1 -1
- package/assets/icons/bark.svg +1 -1
- package/assets/icons/bowl-dark.svg +1 -1
- package/assets/icons/bowl.svg +1 -1
- package/assets/icons/decide-dark.svg +1 -1
- package/assets/icons/decide.svg +1 -1
- package/assets/icons/ear-dark.svg +1 -1
- package/assets/icons/ear.svg +1 -1
- package/assets/icons/lanes-dark.svg +1 -1
- package/assets/icons/lanes.svg +1 -1
- package/assets/icons/plan-dark.svg +1 -1
- package/assets/icons/plan.svg +1 -1
- package/dist/{chunk-OBUWTQ6Z.js → chunk-M6DDZEW4.js} +24 -10
- package/dist/config.d.ts +11 -5
- package/dist/config.js +53 -21
- package/dist/plan-format.d.ts +73 -7
- package/dist/plan-format.js +180 -100
- package/docs/workflow-dark.svg +1 -1
- package/docs/workflow.svg +1 -1
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/hooks/approval-gate/herdr-panes.ts +2 -12
- package/kit/hooks/hooks/approval-gate/index.ts +10 -8
- package/kit/hooks/hooks/approval-gate/run.ts +21 -12
- package/kit/hooks/hooks/ask-hint/decision.ts +3 -3
- package/kit/hooks/hooks/git-guard/decision.ts +189 -15
- package/kit/hooks/hooks/git-guard/index.ts +13 -3
- package/kit/hooks/hooks/git-guard/push-target.ts +85 -0
- package/kit/hooks/hooks/git-guard/repository.ts +63 -0
- package/kit/hooks/hooks/merge-guard/check-free.ts +1 -13
- package/kit/hooks/hooks/merge-guard/decision.ts +342 -102
- package/kit/hooks/hooks/merge-guard/index.ts +6 -4
- package/kit/hooks/hooks/merge-guard/plain-path.ts +15 -0
- package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +23 -0
- package/kit/hooks/hooks/merge-guard/plan-approval-settings.ts +142 -0
- package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +196 -0
- package/kit/hooks/hooks/merge-guard/plan-approval.ts +168 -0
- package/kit/hooks/hooks/merge-guard/plan-growth.ts +40 -0
- package/kit/hooks/hooks/merge-guard/plan-pointers.ts +194 -0
- package/kit/hooks/hooks/merge-guard/pull-request.ts +67 -21
- package/kit/hooks/hooks/merge-guard/run.ts +118 -29
- package/kit/hooks/hooks/merge-guard/settings.ts +8 -8
- package/kit/hooks/hooks/merge-guard/worker.ts +3 -3
- package/kit/hooks/hooks/shared/base-branch.ts +23 -0
- package/kit/hooks/hooks/shared/board-lanes.ts +93 -0
- package/kit/hooks/hooks/shared/command-text-runners.ts +71 -0
- package/kit/hooks/hooks/shared/command.ts +14 -2
- package/kit/hooks/hooks/shared/commit.ts +2 -0
- package/kit/hooks/hooks/shared/dialog-answers.ts +4 -1
- package/kit/hooks/hooks/shared/json.ts +10 -0
- package/kit/hooks/hooks/shared/lane.ts +57 -0
- package/kit/hooks/hooks/shared/once.ts +8 -0
- package/kit/hooks/hooks/shared/own-pane.ts +31 -0
- package/kit/hooks/hooks/shared/pane-agent.ts +24 -0
- package/kit/hooks/hooks/shared/plan-open-phases.ts +114 -0
- package/kit/hooks/hooks/shared/program-position.ts +148 -5
- package/kit/hooks/hooks/shared/shell-aliases.ts +258 -0
- package/kit/hooks/hooks/shared/shell-snapshots.ts +68 -0
- package/kit/hooks/hooks/shared/shell.ts +167 -20
- package/kit/hooks/hooks.json +11 -2
- package/kit/hooks/statusline.sh +29 -16
- package/kit/review/skills/diagnose/SKILL.md +1 -1
- package/kit/review/skills/review/SKILL.md +2 -0
- package/kit/rules/rules/kit/testing.md +2 -5
- package/kit/rules/rules/kit/writing-for-agents.md +3 -1
- package/kit/workflow/agents/spec-review.md +1 -1
- package/kit/workflow/skills/follow/SKILL.md +3 -3
- package/kit/workflow/skills/grill/SKILL.md +1 -1
- package/kit/workflow/skills/implement/SKILL.md +10 -11
- package/kit/workflow/skills/orchestrate/SKILL.md +155 -117
- package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +17 -0
- package/kit/workflow/skills/orchestrate/references/herd.md +27 -0
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -0
- package/kit/workflow/skills/orchestrate/references/sensitive-input.md +23 -0
- package/kit/workflow/skills/plan/SKILL.md +5 -2
- package/kit/workflow/skills/plan/references/template-English.md +6 -0
- package/kit/workflow/skills/plan/references/template-German.md +6 -0
- package/package.json +3 -2
- package/recipes/README.md +21 -0
- package/recipes/github-issues.md +23 -0
- package/recipes/github-projects.md +22 -0
- package/recipes/jira-cli.md +26 -0
- package/recipes/jira-rest.md +24 -0
- package/recipes/linear.md +25 -0
- package/recipes/sentry.md +18 -0
- package/recipes/sources/jira.ts +51 -0
- package/recipes/sources/linear.ts +19 -0
- package/recipes/sources/sentry.ts +12 -0
- package/recipes/sources.md +80 -0
- package/recipes/status-page.md +19 -0
- package/schemas/jackherd.schema.json +42 -18
- package/src/check/command.ts +2 -2
- package/src/check/parallel-report.ts +19 -9
- package/src/cli.ts +1 -0
- package/src/config/context-shares.ts +130 -0
- package/src/config/legacy-names.ts +8 -0
- package/src/config/read-config.ts +12 -4
- package/src/config/schema.ts +75 -20
- package/src/context/auto-compact.ts +68 -0
- package/src/context/command.ts +68 -17
- package/src/context/coordinator-name.ts +27 -9
- package/src/context/index.ts +0 -1
- package/src/context/level.ts +18 -11
- package/src/context/report.ts +6 -3
- package/src/plan-format/dependencies.ts +144 -32
- package/src/plan-format/index.ts +6 -0
- package/src/plan-format/lanes.ts +6 -0
- package/src/plan-format/progress.ts +19 -8
- package/src/plans/command.ts +14 -5
- package/src/plans/current-phase.ts +63 -7
- package/src/plans/current-task.ts +51 -0
- package/src/plans/ensure-panes.ts +25 -8
- package/src/plans/frame.ts +99 -0
- package/src/plans/git.ts +8 -0
- package/src/plans/herd-memory.ts +107 -0
- package/src/plans/lane-plans.ts +5 -3
- package/src/plans/lane-summary.ts +222 -0
- package/src/plans/layout-phase.ts +148 -0
- package/src/plans/layout-summary.ts +140 -0
- package/src/plans/layout.ts +164 -420
- package/src/plans/live.ts +11 -6
- package/src/plans/plan-panes.ts +15 -0
- package/src/plans/planned.ts +9 -42
- package/src/plans/selection.ts +2 -6
- package/src/plans/settings.ts +2 -7
- package/src/plans/snapshot.ts +38 -39
- package/src/plans/symbols.ts +2 -2
- package/src/plans/texts.ts +32 -31
- package/src/plans/view.ts +0 -1
- package/src/plans/worked-phases.ts +2 -2
- package/src/settled/board-row.ts +133 -0
- package/src/settled/check.ts +129 -0
- package/src/settled/command.ts +65 -0
- package/src/settled/evidence-version.ts +14 -0
- package/src/settled/options.ts +69 -0
- package/src/settled/send.ts +27 -0
- package/src/settled/settle.ts +87 -0
- package/src/settled/topics.ts +56 -0
- package/src/sync/command.ts +16 -1
- package/src/sync/render.ts +13 -4
- package/src/sync/sync.ts +25 -5
- package/src/terminal/herdr.ts +46 -10
- package/src/terminal/none.ts +1 -0
- package/src/terminal/types.ts +15 -2
- package/src/update/command.ts +4 -2
- package/src/update/update.ts +33 -9
- package/src/vocabulary/index.ts +47 -11
- package/src/watch/appointments.ts +205 -0
- package/src/watch/command.ts +62 -19
- package/src/watch/lane-report.ts +11 -3
- package/src/watch/texts.ts +35 -59
- package/src/watch/watch-events.ts +21 -28
- package/kit/hooks/hooks/approval-gate/lane.ts +0 -14
package/README.md
CHANGED
|
@@ -1,909 +1,163 @@
|
|
|
1
1
|
<p>
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd
|
|
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
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/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/assets/branding/jackherd-logo.svg" width="340">
|
|
5
5
|
</picture>
|
|
6
6
|
<br>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)
|
|
9
|
+
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Let AI do the work. Stay in control.** Build several features in parallel with Claude Code. Each runs in its own branch; you only answer the questions that need a human. Jack, the coordinator (the Shih Tzu in the logo, who herds anyway), handles the rest. In a project install, hooks hold merges and releases that touch risky paths until you approve, and refuse a push to your base branch.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
you lose track of who waits on you. Jack:<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.7.0/assets/icons/space.svg">
|
|
13
|
+
Install with one command, run `/jh-start` in a repository, and it offers your first plan: [Quickstart](#quickstart).<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
|
|
15
14
|
|
|
16
|
-
<p
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
15
|
+
<p>
|
|
16
|
+
<picture>
|
|
17
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/docs/workflow-dark.svg">
|
|
18
|
+
<img src="https://unpkg.com/jackherd/docs/workflow.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
|
|
19
|
+
</picture>
|
|
20
|
+
<br>
|
|
21
|
+
<sub>Parallel mode in herdr (optional). Planning and building work without it.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
|
|
22
|
+
</p>
|
|
23
23
|
|
|
24
|
-
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd
|
|
24
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/plan-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/plan.svg"></picture><b>Plans live in your repository.</b> Markdown phases, a model each, ticked off as they are built.<br clear="left"></p>
|
|
25
25
|
|
|
26
|
-
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd
|
|
26
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/lanes-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/lanes.svg"></picture><b>Lanes stay apart.</b> A lane is one phase of a plan in its own git worktree and session, side by side in herdr, the terminal that holds the parallel sessions.<br clear="left"></p>
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/decide-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/decide.svg"></picture><b>You decide. Jack takes care of the lanes.</b> He answers what the plan settles and asks you the rest.<br clear="left"></p>
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/bark-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/bark.svg"></picture><b>Jack barks when something moves.</b> One live view of the herd, all your lanes: state, pull request, checks, feedback.<br clear="left"></p>
|
|
31
31
|
|
|
32
|
-
<picture>
|
|
33
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd@1.7.0/docs/workflow-dark.svg">
|
|
34
|
-
<img src="https://unpkg.com/jackherd@1.7.0/docs/workflow.svg" width="720" alt="jackherd in herdr, in five steps: a lane works, hits an open question, the coordinator asks you as a choice, you pick, and the lane works on while the herd counts its progress.">
|
|
35
|
-
</picture>
|
|
32
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/ear-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/ear.svg"></picture><b>Jack listens beyond GitHub.</b> <code>/jh-follow</code> Jira, Linear, Sentry or a status page. Reports need herdr.<br clear="left"></p>
|
|
36
33
|
|
|
37
|
-
<
|
|
34
|
+
<p><picture><source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/assets/icons/bowl-dark.svg"><img align="left" width="28" height="48" alt="" src="https://unpkg.com/jackherd/assets/icons/bowl.svg"></picture><b>Long sessions get a fresh start.</b> At a phase boundary, a lane whose context is filling up moves to a new chat.<br clear="left"></p>
|
|
38
35
|
|
|
39
36
|
## Quickstart
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
Planning and building work without herdr; parallel lanes need it.
|
|
39
|
+
|
|
40
|
+
1. **Install** (Linux and macOS; needs Claude Code and git). It asks a few setup questions and leaves your repositories alone. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)
|
|
42
41
|
|
|
43
42
|
```bash
|
|
44
43
|
curl -fsSL https://unpkg.com/jackherd/install.sh | sh
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
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. → [INSTALL.md: what the installer changes, project or global, updating](https://unpkg.com/jackherd/INSTALL.md)
|
|
48
|
-
|
|
49
46
|
Already in a coding agent? Paste this instead:
|
|
50
47
|
|
|
51
48
|
```text
|
|
52
49
|
Install jackherd by following https://unpkg.com/jackherd/INSTALL.md. Ask me the setup questions it lists; herdr is optional.
|
|
53
50
|
```
|
|
54
|
-
2. **Start Claude Code in a repository and
|
|
51
|
+
2. **Start Claude Code in a repository.** It checks the setup and offers your first plan.
|
|
55
52
|
|
|
56
53
|
```text
|
|
57
54
|
/jh-start
|
|
58
55
|
```
|
|
59
|
-
|
|
60
|
-
It checks the setup, says what is missing (herdr is optional and comes with its install command) and offers to start your first plan.
|
|
61
|
-
3. **Describe a task or point to a spec file:**
|
|
56
|
+
3. **Plan.** Phases, tasks and a model for each, written to `docs/plans/`.
|
|
62
57
|
|
|
63
58
|
```text
|
|
64
59
|
/jh-plan Add product search
|
|
65
|
-
/jh-plan docs/spec.md
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
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.
|
|
69
|
-
4. **Build it.** With herdr, in a Claude Code session started inside herdr, start the board:
|
|
70
|
-
|
|
71
|
-
```text
|
|
72
|
-
/jh-orchestrate docs/plans/board-<name>/board.md
|
|
73
60
|
```
|
|
74
|
-
|
|
75
|
-
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.
|
|
76
|
-
|
|
77
|
-
Without herdr, build a phase in your session:
|
|
61
|
+
4. **Build,** one phase at a time, ticked off in the plan. No herdr needed.
|
|
78
62
|
|
|
79
63
|
```text
|
|
80
64
|
/jh-implement docs/plans/<plan>.md phase 1
|
|
81
65
|
```
|
|
66
|
+
5. **Or let Jack run the herd.** Inside [herdr](https://herdr.dev), one lane per phase, with the live herd view beside him (`jh herd` anywhere else).
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
jh herd
|
|
68
|
+
```text
|
|
69
|
+
/jh-orchestrate docs/plans/board-<name>/board.md
|
|
88
70
|
```
|
|
89
71
|
|
|
90
|
-
|
|
72
|
+
By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)
|
|
91
73
|
|
|
92
|
-
|
|
74
|
+
## Human on the loop
|
|
93
75
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
bun add -g jackherd
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
or with npm:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
npm install -g jackherd
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
then:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
jh init --global
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## With and without herdr
|
|
113
|
-
|
|
114
|
-
Planning, building and reviewing work without herdr. herdr adds parallel lanes and the agents' state in the herd.
|
|
115
|
-
|
|
116
|
-
| | Without herdr | With herdr |
|
|
117
|
-
| --- | --- | --- |
|
|
118
|
-
| `/jh-grill`, `/jh-plan`, `/jh-implement`, `/jh-pre-commit` and the review skills | yes | yes |
|
|
119
|
-
| Independent phases in parallel | as subagents in one checkout | as lanes, one worktree and session each |
|
|
120
|
-
| `/jh-orchestrate` (the coordinator) | – | yes |
|
|
121
|
-
| `jh herd` | board and plan progress | plus the lanes' state, pull requests and checks, context fill |
|
|
122
|
-
| `/jh-follow` (tickets, issues, status pages) | set up a source and dry-run it | the watcher reports changes to the coordinator; tickets show as badges in the herd |
|
|
123
|
-
|
|
124
|
-
Pull requests and checks in the herd need `gh`.
|
|
125
|
-
|
|
126
|
-
## Global or per project
|
|
127
|
-
|
|
128
|
-
The global install prefixes every skill with `jh-`, leaves repositories alone and keeps its config in `~/.jackherd/`; it brings no hooks, so the commit, push and merge approvals rest on the skills alone.
|
|
129
|
-
|
|
130
|
-
To share the kit with your team instead, run `jh init` in the repository. Skills, rules and the kit's hooks (the merge guard among them) then land in `.claude/`, the same for everyone who clones it, and `jh sync` updates them without overwriting your edits (a changed file gets the new version beside it as `<file>.kit-new`); the project's config lives in `.jackherd/`. In a project install the skills drop the prefix by default: `/plan` instead of `/jh-plan`. → [Installation](https://unpkg.com/jackherd/INSTALL.md)
|
|
131
|
-
|
|
132
|
-
## Documentation
|
|
133
|
-
|
|
134
|
-
Each section opens on a click; a link into one opens it.
|
|
135
|
-
|
|
136
|
-
<details>
|
|
137
|
-
<summary><b>Installation and updates</b></summary>
|
|
138
|
-
|
|
139
|
-
### Installation and updates
|
|
140
|
-
|
|
141
|
-
The installer, the setup questions, project or global install, updating and removing are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md), with the steps for a coding agent that installs jackherd for you.
|
|
142
|
-
|
|
143
|
-
</details>
|
|
76
|
+
You decide every open question, commit and push, once per pull request. Jack answers on his own what the plan already settles, never money, auth, secrets, a migration or a release. Once you've approved a plan and a pull request, he merges it on green checks and tells you in one line. The guards catch mistakes, not an agent set on getting around them; only branch protection stops that. A pull request that touches a workflow, a migration, a lockfile or `package.json` still asks, and so does every release. Prefer to see every merge? Approve per pull request instead.
|
|
144
77
|
|
|
145
78
|
<details>
|
|
146
|
-
<summary><b>
|
|
147
|
-
|
|
148
|
-
### Planning: plans, models and boards
|
|
149
|
-
|
|
150
|
-
Skills on this page are written as the quickstart installs them, with the `jh-` prefix of the global install (`/jh-plan`, `/jh-implement`, `/jh-orchestrate`); agents carry it too (`jh-spec-review`). A project install drops it by default (`skillPrefix` in `.jackherd/config.json` sets one).
|
|
151
|
-
|
|
152
|
-
- [Terms](#user-content-terms)
|
|
153
|
-
- [The skills and what they hand on](#user-content-the-skills-and-what-they-hand-on)
|
|
154
|
-
- [What a plan contains](#user-content-what-a-plan-contains)
|
|
155
|
-
- [Model recommendations](#user-content-model-recommendations)
|
|
156
|
-
- [Plan format](#user-content-plan-format)
|
|
157
|
-
- [Boards](#user-content-boards)
|
|
158
|
-
|
|
159
|
-
#### Terms
|
|
160
|
-
|
|
161
|
-
- **Plan:** a Markdown file with phases (`### Phase 1: …`) and checkbox tasks. `/jh-plan` writes it, `/jh-implement` builds one phase and ticks its tasks.
|
|
162
|
-
- **Board:** a Markdown file with a lanes table that says who works on which phases of which plan, and what each lane starts after. `/jh-plan` proposes its rows; by default it is `docs/plans/board-<name>/board.md`.
|
|
163
|
-
- **Lane:** one agent session in its own git worktree, working on the phases its row names.
|
|
164
|
-
- **Coordinator:** one more session, in the main checkout, running `/jh-orchestrate`: it starts the lanes, answers or relays their questions, asks you for approvals and keeps the merge order.
|
|
165
|
-
|
|
166
|
-
#### The skills and what they hand on
|
|
79
|
+
<summary><b>What Jack does on his own, and what waits for you</b></summary>
|
|
167
80
|
|
|
168
81
|
<picture>
|
|
169
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd
|
|
170
|
-
<img src="https://unpkg.com/jackherd
|
|
82
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/docs/lanes-dark.svg">
|
|
83
|
+
<img src="https://unpkg.com/jackherd/docs/lanes.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
|
|
171
84
|
</picture>
|
|
172
85
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- **`/jh-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 `/jh-implement`.
|
|
181
|
-
|
|
182
|
-
#### What a plan contains
|
|
183
|
-
|
|
184
|
-
`/jh-plan` writes every plan from one template. Each part is there because a skill reads it:
|
|
185
|
-
|
|
186
|
-
- **Header (date, status, scope):** what the plan touches and how far it is; the board page of the herd (key `s`) shows each plan with its title and status.
|
|
187
|
-
- **Decisions:** each decision with its rationale, including who decided and when; `/jh-plan` does not invent them, and `/jh-orchestrate` writes a decision of yours into the plan the moment you make it.
|
|
188
|
-
- **Model recommendations per phase:** the model for each phase with the reason ([below](#user-content-model-recommendations)).
|
|
189
|
-
- **Dependencies and parallelization:** per phase what it depends on, the paths it writes and its serial markers (a migration, say). `jh check --parallel <plan>` lists the phases that can start together: every dependency ticked off, no shared path (a directory overlaps everything below it), no shared serial marker; a `User: …` entry counts as an open gate. `/jh-implement` starts them at the same time, the watcher proposes them to the coordinator (`=== parallel <lane> P<a> ∥ P<b>`), and `/jh-orchestrate` checks each lane's `Writes` set against the running lanes before starting it. The table cannot see two phases that change the same content or the same release file (changelog, version bump); that stays a judgement. `jh check` warns about a phase of a changed plan that has no row.
|
|
190
|
-
- **Phases with checkboxes:** the tasks `/jh-implement` ticks as it finishes them, which is what the herd counts.
|
|
191
|
-
- **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.
|
|
192
|
-
- **Tests:** which test files are needed and why, or the reason none are.
|
|
193
|
-
- **Documentation and architecture docs:** what gets documented after the last phase, ending with the architecture documentation derived from the finished code.
|
|
194
|
-
|
|
195
|
-
The section headings stay verbatim (in the plan language the config sets), because the plan parser, `/jh-implement` and `spec-review` find them by name.
|
|
196
|
-
|
|
197
|
-
#### Model recommendations
|
|
198
|
-
|
|
199
|
-
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 `/jh-security-audit` always get the strongest model.
|
|
200
|
-
|
|
201
|
-
The recommendation is applied, not just noted:
|
|
202
|
-
|
|
203
|
-
- `/jh-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.
|
|
204
|
-
- `/jh-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.
|
|
205
|
-
- In a project install the hook `model-gate` checks it once more when `/jh-implement` hands a phase to a subagent: it reads the phase (`Phase P<n>`) and the plan path from the prompt and compares the subagent's model (from the call, else from the agent's definition) with that phase's row. A weaker model is stopped with the one to use, a stronger one passes with a note, and whatever the hook cannot tell (no phase, no row, no model) passes silently. When the subagent ends, `model-log` records the model it actually ran on in `.jackherd/models/`, and the herd shows it at the phase as `Opus ✓`, or `Sonnet ≠ Opus` in amber when it differs.
|
|
206
|
-
|
|
207
|
-
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.
|
|
208
|
-
|
|
209
|
-
#### Plan format
|
|
210
|
-
|
|
211
|
-
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:
|
|
212
|
-
|
|
213
|
-
```markdown
|
|
214
|
-
<!-- docs/plans/2026-10-12-search.md -->
|
|
215
|
-
# Product search
|
|
216
|
-
|
|
217
|
-
**Date:** 2026-10-12
|
|
218
|
-
**Status:** In Progress
|
|
219
|
-
**Scope:** `services/search/`, `apps/shop/src/search/`
|
|
220
|
-
|
|
221
|
-
## Problem / Context
|
|
222
|
-
|
|
223
|
-
Customers scroll the whole catalog to find a product. Support gets the same question every day.
|
|
224
|
-
|
|
225
|
-
## Decisions
|
|
226
|
-
|
|
227
|
-
| Decision | Rationale |
|
|
228
|
-
| -------- | --------- |
|
|
229
|
-
| Index in Postgres full-text search, no external engine | User 2026-10-11: one database to run; 4 000 products fit |
|
|
230
|
-
| The index rebuilds on product events, not on a timer | User 2026-10-11: a changed price must be searchable at once |
|
|
231
|
-
|
|
232
|
-
## Implementation
|
|
233
|
-
|
|
234
|
-
### Model recommendations per phase
|
|
235
|
-
|
|
236
|
-
| Phase | Model | Rationale |
|
|
237
|
-
| ----- | ----- | --------- |
|
|
238
|
-
| 1 — Index | claude-opus-5-5 | Migration on a hot table; a wrong index locks writes |
|
|
239
|
-
| 2 — Search box | claude-sonnet-5-5 | UI after an existing pattern |
|
|
240
|
-
| 3 — Docs | claude-sonnet-5-5 | Text from finished code |
|
|
241
|
-
|
|
242
|
-
### Dependencies and parallelization
|
|
243
|
-
|
|
244
|
-
| Phase | Depends on | Writes | Serial marker |
|
|
245
|
-
| ----- | ---------- | ------ | ------------- |
|
|
246
|
-
| 1 — Index | — | `services/search/`, `db/migrations/` | Migration |
|
|
247
|
-
| 2 — Search box | 1 | `apps/shop/src/search/` | — |
|
|
248
|
-
| 3 — Docs | 1, 2 | `docs/architecture/` | — |
|
|
249
|
-
|
|
250
|
-
### Phase 1: Index
|
|
251
|
-
|
|
252
|
-
- [x] Migration: `tsvector` column and GIN index
|
|
253
|
-
- [x] Rebuild the row on `product.updated`
|
|
254
|
-
- [ ] Backfill the existing products
|
|
255
|
-
|
|
256
|
-
**Review gate Phase 1:**
|
|
257
|
-
|
|
258
|
-
- [ ] Tests written
|
|
259
|
-
- [ ] `/jh-uncle-bob` — Clean Architecture review
|
|
260
|
-
|
|
261
|
-
### Phase 2: Search box
|
|
262
|
-
|
|
263
|
-
- [ ] Search box with results as you type
|
|
264
|
-
- [ ] Empty state when nothing matches
|
|
265
|
-
|
|
266
|
-
**Review gate Phase 2:**
|
|
267
|
-
|
|
268
|
-
- [ ] Tests written
|
|
269
|
-
- [ ] `/jh-uncle-bob` — Clean Architecture review
|
|
270
|
-
|
|
271
|
-
### Phase 3: Docs
|
|
272
|
-
|
|
273
|
-
- [ ] Search flow in the architecture docs
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
**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.
|
|
277
|
-
|
|
278
|
-
#### Boards
|
|
279
|
-
|
|
280
|
-
A board gathers the lanes one coordinator runs; it mixes plans and has no end date of its own. `/jh-plan` proposes the rows from the dependency and model tables:
|
|
281
|
-
|
|
282
|
-
- **Own lane:** a phase that shares no written path and no serial marker with a phase of another lane.
|
|
283
|
-
- **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.
|
|
284
|
-
- **Starts after:** from the phase's dependencies (`P1 merged`, or an external gate such as `User: mockups`); a phase without one starts at once.
|
|
285
|
-
- **Model:** the full model ID from the model table, never an alias.
|
|
286
|
-
- **Plan:** a Markdown link to the plan, relative to the board's directory. The herd finds a lane's plan only through that link.
|
|
287
|
-
|
|
288
|
-
```markdown
|
|
289
|
-
<!-- docs/plans/board-01/board.md -->
|
|
290
|
-
# Board 01
|
|
291
|
-
|
|
292
|
-
## Lanes
|
|
293
|
-
|
|
294
|
-
| Lane | Port | Plan | Phases | Starts after | Model |
|
|
295
|
-
|------|------|------|--------|--------------|-------|
|
|
296
|
-
| `search-index` | 3100 | [Product search](../2026-10-12-search.md) | P1 Index | now | claude-opus-5-5 |
|
|
297
|
-
| `search-ui` | 3101 | [Product search](../2026-10-12-search.md) | P2 Search box | P1 merged | claude-sonnet-5-5 |
|
|
298
|
-
| ↳ `search-ui` | – | [Product search](../2026-10-12-search.md) | P3 Docs | P2 merged | claude-sonnet-5-5 |
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
The columns are read by position. The table heading is `## Lanes` (`## Spuren` with `planLanguage: de`). Nothing is written without your answer: `/jh-plan` writes the rows into the newest board or a new `docs/plans/board-<name>/board.md`, and then names the next command, `/jh-orchestrate <board path>`. Plan and board go into one commit under one approval: yours through `/jh-pre-commit`, or the coordinator's documentation PR when `/jh-orchestrate` finds them missing on the base branch ([Orchestration](#user-content-starting)). No merge is needed before `/jh-orchestrate`.
|
|
302
|
-
|
|
303
|
-
Where boards live and what the project calls them is the `board` block of the config:
|
|
304
|
-
|
|
305
|
-
```json
|
|
306
|
-
{ "board": { "pattern": "board-*", "file": "board.md", "term": "board" } }
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
- `pattern`: the directories under `planDirectory` that hold a board; `jh herd` and `jh watch` take the newest one unless given a path.
|
|
310
|
-
- `file`: the board document in each of them.
|
|
311
|
-
- `term`: the word the herd, its HTML page and `watch` use for it (`board page opened: …`).
|
|
312
|
-
|
|
313
|
-
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`.
|
|
86
|
+
| On his own | After your approval | Never |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| Starting a ready phase in a lane | Commits, push and pull request, once per pull request | Writing lane code himself |
|
|
89
|
+
| Answering what the plan or a rule settles, on record in the board | Every other decision a lane raises | Merging a pull request on a path that always asks, without asking you |
|
|
90
|
+
| Merging a pull request of an approved plan on green checks | A plan approval, once per plan | Storing a permission on your behalf |
|
|
91
|
+
| Rebasing the lanes after a merge | Migrations and writes against a shared environment | |
|
|
92
|
+
| Handing a session over to a fresh chat before its context fills up | Closing a lane | |
|
|
314
93
|
|
|
315
94
|
</details>
|
|
316
95
|
|
|
317
96
|
<details>
|
|
318
|
-
<summary><b>
|
|
319
|
-
|
|
320
|
-
### Orchestration with herdr
|
|
321
|
-
|
|
322
|
-
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; `/jh-orchestrate` (`/orchestrate` in a project install) is the skill the coordinating session runs.
|
|
323
|
-
|
|
324
|
-
- [What runs where](#user-content-what-runs-where)
|
|
325
|
-
- [Starting](#user-content-starting)
|
|
326
|
-
- [What the coordinator does](#user-content-what-the-coordinator-does)
|
|
327
|
-
- [What happens on its own, and what waits for you](#user-content-what-happens-on-its-own-and-what-waits-for-you)
|
|
328
|
-
- [The watcher](#user-content-the-watcher)
|
|
329
|
-
- [Hand-over to a fresh chat](#user-content-hand-over-to-a-fresh-chat)
|
|
330
|
-
- [Lane setup from the config](#user-content-lane-setup-from-the-config)
|
|
331
|
-
|
|
332
|
-
#### What runs where
|
|
97
|
+
<summary><b>Guarded commands and merges</b></summary>
|
|
333
98
|
|
|
334
|
-
|
|
335
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd@1.7.0/docs/lanes-dark.svg">
|
|
336
|
-
<img src="https://unpkg.com/jackherd@1.7.0/docs/lanes.svg" alt="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.">
|
|
337
|
-
</picture>
|
|
338
|
-
|
|
339
|
-
`/jh-orchestrate` needs herdr: it checks that `herdr` is on the path and `HERDR_ENV=1` is set, and stops otherwise. Without herdr, `/jh-implement` still runs independent phases of one plan as parallel subagents in a single checkout, kept apart by their disjoint `Writes` sets.
|
|
340
|
-
|
|
341
|
-
#### Starting
|
|
342
|
-
|
|
343
|
-
1. Set `"terminal": { "adapter": "herdr" }` in `.jackherd/config.json` (or `~/.jackherd/config.json`), so the herd and `jh context` read agent states and context fill from herdr.
|
|
344
|
-
2. Open herdr, run Claude Code in the main checkout and type `/jh-orchestrate docs/plans/<board>/board.md`. That session is the coordinator.
|
|
345
|
-
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.
|
|
346
|
-
|
|
347
|
-
It names itself in herdr (`orchestrator-<repository>` by default, since herdr names are unique across all repositories), opens the herd beside its pane, tells you which lanes start now and which wait, and starts the watcher.
|
|
348
|
-
|
|
349
|
-
#### What the coordinator does
|
|
350
|
-
|
|
351
|
-
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.
|
|
352
|
-
|
|
353
|
-
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.
|
|
354
|
-
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.
|
|
355
|
-
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.
|
|
356
|
-
4. **Vets questions.** A lane never opens a dialog; it ends its turn with a text block. The coordinator checks the premises against the code and the plan and brings the open decisions to you, each as a clickable choice.
|
|
357
|
-
- The block holds the question, the options, the lane's recommendation and the evidence at `file:line`.
|
|
358
|
-
- A question in a lane's report is closed in the same turn, in the lane or with you.
|
|
359
|
-
- 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.
|
|
360
|
-
- Your decision goes into the plan or the board in the same turn, with the date, so it is visible in the herd and survives the chat. The date and time come from `date`, never from the chat or a guess.
|
|
361
|
-
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.
|
|
362
|
-
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. The question has three options: you merge, you approve and let the coordinator merge once the checks are green ([self-merge](#user-content-guarded-commands-and-self-merge)), or you change something first.
|
|
363
|
-
7. **Keeps the merge order.** Merges follow the board's order, the documentation PR of a fresh plan first. After each merge:
|
|
364
|
-
- the remaining lanes fetch and rebase on the base branch and re-run their checks; a lock-file conflict is never resolved by hand.
|
|
365
|
-
- 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.
|
|
366
|
-
- each lane gets its next phase or a named reason to wait.
|
|
367
|
-
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.
|
|
368
|
-
|
|
369
|
-
#### What happens on its own, and what waits for you
|
|
370
|
-
|
|
371
|
-
| On its own | After your approval | Never |
|
|
372
|
-
| --- | --- | --- |
|
|
373
|
-
| 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 |
|
|
374
|
-
| 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 or you picked self-merge for the PR |
|
|
375
|
-
| 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 |
|
|
376
|
-
| 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 |
|
|
99
|
+
In a project install, the `merge-guard` hook asks you before `gh pr merge`, `gh workflow run`, `gh run rerun` and a `gh api` write that merges, writes a file, dispatches or reruns a workflow, or moves a ref, also behind a chain, a wrapper or `sh -c`. The `git-guard` hook refuses a git command that hides a commit, push, rebase, reset, checkout or merge from your permission rules (`git -C … push`, `sh -c "…"`), and every force push, skipped hook and hard reset.
|
|
377
100
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
#### Guarded commands and self-merge
|
|
381
|
-
|
|
382
|
-
A merge or a release is the step nobody takes back.
|
|
383
|
-
|
|
384
|
-
##### What is guarded
|
|
385
|
-
|
|
386
|
-
In a project install the `merge-guard` hook asks you before `gh pr merge` and `gh workflow run` (the usual production release) run. It recognises them when the command spells them out: behind options before the verb (`gh -R x pr merge`), a chain (`a && gh pr merge 12`), a wrapper, known or not (any command whose words contain the guarded words in this order, such as `mise exec -- gh pr merge 12`), `sh -c` and `eval`. It shows the dialog even in auto mode. A quoted text that names the words, such as a commit message, passes, and so does everything else; the hook prints nothing unless it asks.
|
|
387
|
-
|
|
388
|
-
```json
|
|
389
|
-
{
|
|
390
|
-
"guard": {
|
|
391
|
-
"commands": ["gh pr merge", "gh workflow run"],
|
|
392
|
-
"selfMerge": { "offer": true, "approvalMinutes": 180 }
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
- `guard.commands` lists the guarded commands as words, a program and one to three plain words after it: letters, digits and `-`, no `:` and no option. These two are the default; `git push` is not on the list. An empty list switches the guard off.
|
|
398
|
-
- `guard.selfMerge.offer` (default `true`) lets `/jh-orchestrate` offer "approve, and merge it yourself after green checks" in its PR question. With `false` the question has two options and the hook honours no such answer.
|
|
399
|
-
- `guard.selfMerge.approvalMinutes` (default 180) is how long that answer lets `gh pr merge <number>` pass without a second dialog.
|
|
400
|
-
|
|
401
|
-
##### Self-merge
|
|
402
|
-
|
|
403
|
-
You pick the second option of the PR question. The coordinator commits, pushes and opens the PR, waits for the checks in the background, merges with `gh pr merge <number> --squash --delete-branch` when they are green and tells you in one line; on a red check it does not merge and reports. The project supplement can block the option per approval tier.
|
|
404
|
-
|
|
405
|
-
The hook does not take the agent's word for your answer. It reads the session's transcript, which Claude Code writes, and looks for a dialog result where you picked that exact option for a question naming one branch. Then it asks GitHub which branch pull request `<number>` carries and lets the merge pass if the two match, the PR is open and not from a fork, and the command is exactly the plain form: `gh pr merge <number>` with `--squash`, `--merge`, `--rebase` and `--delete-branch` (or `-s`, `-m`, `-r`, `-d`) as the only flags, run on its own in the directory you work in (no `-R`, no `--admin`, no chain, no wrapper). `gh workflow run` and every other guarded command ask every time. The approval covers the branch, not the commits you saw, for the configured minutes.
|
|
406
|
-
|
|
407
|
-
##### Merging without checks
|
|
408
|
-
|
|
409
|
-
A pull request that only changes documents rarely needs its checks. List the paths in `merge.withoutChecks` (default empty, so every merge waits for the checks as before):
|
|
101
|
+
A merge passes without a question only on an answer from your own dialog: self-merge for that pull request, or the approval of its plan, with green checks and no file on a path that always asks (workflows, migrations, lockfiles, `package.json`, env files, agent instructions, and your own list). `merge.withoutChecks` lets a pull request that only touches such paths as docs merge without waiting for checks.
|
|
410
102
|
|
|
411
103
|
```json
|
|
412
104
|
{
|
|
105
|
+
"guard": { "planApproval": { "exceptPaths": ["src/billing/**", "src/auth/**"] } },
|
|
413
106
|
"merge": { "withoutChecks": ["docs/**/*.md"] }
|
|
414
107
|
}
|
|
415
108
|
```
|
|
416
109
|
|
|
417
|
-
A
|
|
418
|
-
|
|
419
|
-
With the list set, `/jh-orchestrate` lists a pull request's files with `gh pr diff <number> --name-only` before it waits for checks and merges at once when every file matches.
|
|
420
|
-
|
|
421
|
-
The merge guard checks the same itself: for a plain `gh pr merge <number>` it asks GitHub for the pull request and then for its files in that repository (`gh api …/pulls/<number>/files`, which also names the old path of a renamed file). It does not ask you when the pull request is open, from this repository, GitHub lists as many files as the pull request changes (fewer than 3000) and every one matches a pattern. This works without a self-merge answer, also when `guard.selfMerge.offer` is `false`. No file, one file outside, a count that differs, a failed `gh` call or a pattern list it cannot read means it asks as before.
|
|
422
|
-
|
|
423
|
-
##### Limits
|
|
424
|
-
|
|
425
|
-
The hook reads the command as written, so it does not see what a command runs behind its back: a script file or a project script (`bash ./release.sh`, `./release.sh`, `bun run release`, `make release`) that calls `gh workflow run`, a shell alias or function, `gh alias` and git aliases, text that is encoded or assembled at run time, and a wrapper that spells the words differently. Add the script runners you use to `guard.commands`, or protect the repository itself.
|
|
426
|
-
|
|
427
|
-
The agent runs with your account, so it can also edit a transcript, drop the hook from `.claude/settings.json` or merge through `gh api`. The guard stops a merge or a release by mistake, by a misread instruction or a mistyped command; it does not stop an agent that sets out to get around it. For that, use branch protection and required reviews on the repository.
|
|
428
|
-
|
|
429
|
-
- The merge-without-checks look is a snapshot: a push between its look and the merge is not seen.
|
|
430
|
-
- A global install ships no hook, so there the skill text alone asks you.
|
|
431
|
-
- If Bun cannot start (a process out of file descriptors, say), the hook cannot run and every Bash call is blocked, on purpose: a guard that cannot run does not wave commands through.
|
|
432
|
-
|
|
433
|
-
##### Dialogs inside a lane
|
|
434
|
-
|
|
435
|
-
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.
|
|
436
|
-
|
|
437
|
-
#### Approvals of a lane
|
|
438
|
-
|
|
439
|
-
In a project install the `approval-gate` hook takes over the permission dialogs of a lane. A call that would ask for your approval inside a lane (an `ask` rule in the permissions, such as a database tool or a project CLI) is held: the hook writes it to `.jackherd/approvals/<id>.json` in the main checkout (ignored by git), the watcher reports `=== approval <lane> <id> <tool> · <call>`, and the coordinator shows you the whole call from that file and asks you in its own pane, with the mark `` `approval <id> <hash>` `` in the question (the hash of the call you were shown, so your answer approves that content and nothing the file says later) and the options *Allow this call* and *Deny this call* (in German *Aufruf erlauben* and *Aufruf ablehnen*). The hook reads your answer only from the coordinator's transcript, never from a file of the lane, and then lets the call run or denies it.
|
|
440
|
-
|
|
441
|
-
- It waits up to `guard.approvals.waitMinutes` (1 to 9, default 8). Without an answer by then it denies the call with a note to retry it once approved; the retry of the same call passes at once when the answer is there.
|
|
442
|
-
- An approval holds once, for exactly that call (lane, directory, tool and input), and for 30 minutes.
|
|
443
|
-
- Outside a lane (no herdr, or not in a worktree under `worktreeDirectory`), for a call whose input exceeds 64 000 characters, and on any failure the hook decides nothing, and the dialog stays.
|
|
444
|
-
- The lane's pane shows the dialog meanwhile; a click there decides instead of your answer to the coordinator. The hook keeps waiting after such a click and spends a later answer to the same request, which then decides nothing; a retry of the call meanwhile may then need a second question.
|
|
445
|
-
- The hook runs from the lane's worktree (`.claude/hooks/approval-gate/`), so an edit to it there takes effect at the lane's next call.
|
|
446
|
-
- The coordinator is any agent in the main checkout; its herdr name differs per repository. The limit is the merge guard's: an agent that runs with your account could write a transcript line itself.
|
|
447
|
-
|
|
448
|
-
#### The watcher
|
|
449
|
-
|
|
450
|
-
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.
|
|
451
|
-
|
|
452
|
-
**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.
|
|
453
|
-
|
|
454
|
-
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:
|
|
455
|
-
|
|
456
|
-
- 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, a shorter head of 8 lines when it has one, plus every `## Questions` section from the first line the head leaves out, wherever it stands, under a `questions: <n>` line, within 20 lines and 1500 characters) 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
|
|
457
|
-
- 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; a red one names its failed checks and tests: `=== checks failed #<n> (<lane>): test (2 tests: search > finds a word, …)`. For that the watcher reads `gh run view <id> --log-failed` once per head commit (30 s, the last 2 MB, after the run has completed; a run still in progress is asked again at the next poll) and picks the lines `watch.checks.failedTestPattern` matches (default: Bun's `(fail) <name>`); without a match only the check names. The line is reported once per head commit and names tests only when the log was readable at that poll; the herd fills them in later
|
|
458
|
-
- 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
|
|
459
|
-
- 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>)`
|
|
460
|
-
- new feedback on a lane's open pull request: a review, an inline comment, a pull request comment, or one edited since (`watch.feedback`)
|
|
461
|
-
- a deploy preview for a lane's new head commit, with its link (`watch.previews`)
|
|
462
|
-
- a new or changed entry of an external service, such as a ticket comment, read by a command the config names (`watch.sources`)
|
|
463
|
-
- `gh` became unreachable
|
|
464
|
-
- npm has a newer jackherd than the one running: `=== update available <old> → <new>`, one line, once per version. The watcher asks npm once a day on the timestamp the `SessionStart` hook uses (`~/.jackherd/update-check.json`), so the two never ask twice a day or report twice; with nothing newer it prints nothing. The coordinator asks you and runs `jh update` only after your yes
|
|
465
|
-
- 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`
|
|
466
|
-
- an idle lane's last answer came from a model its board row's model column does not name (`=== model <name> <id> · board Opus 5.5`, with a duty line: switch it with `/exit` and `claude --resume <session> --model <id>`, not `/model`). Every model the column names fits, written as a name (`Opus 5.5`) or a full id, matched by family and version, notes in brackets ignored; once per deviation, again only when the model changes
|
|
467
|
-
- a lane's call waits for your approval (`=== approval <lane> <id> <tool> · <call>`, with a duty line naming the question's mark and options), see [Approvals of a lane](#user-content-approvals-of-a-lane)
|
|
468
|
-
- the coordinator's own context fill rose to `warn` or `alert`, so it hands itself over in time
|
|
469
|
-
- `lane added` / `lane removed` when a worktree with an agent or a board row appears or goes; a lane's helper agent, named `<lane>-<purpose>`, is watched with it
|
|
470
|
-
- the heartbeat, a status line every 15 minutes (`--heartbeat`)
|
|
471
|
-
|
|
472
|
-
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 herd` chooses the same way and shows that line with its errors.
|
|
473
|
-
|
|
474
|
-
After a herdr server restart, panes come back as shells in their directories and the herd panes are gone. `jh herd --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 `herd --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.
|
|
475
|
-
|
|
476
|
-
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.
|
|
477
|
-
|
|
478
|
-
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.
|
|
479
|
-
|
|
480
|
-
`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):
|
|
481
|
-
|
|
482
|
-
```text
|
|
483
|
-
armed: search=no agent board docs/plans/board-search/board.md
|
|
484
|
-
=== gh unavailable: gh pr list: no git remotes found
|
|
485
|
-
status:
|
|
486
|
-
search no agent · no branch · no PR · open phase 1 Index (starts after: now)
|
|
487
|
-
coordinator coordinator no value (no agent named coordinator)
|
|
488
|
-
gh unavailable since 11:24:31
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
As a stream (`--stream`) it keeps running, and the heartbeat is one line. A working lane shows its plan progress, `<lane> working: P12 7/12` (the lane's current phase and its ticked checkboxes, counted as the herd counts them), but only when the count moved since the last heartbeat; a lane that stays on one number says nothing, and you look for yourself. The plans are read only when a lane works:
|
|
492
|
-
|
|
493
|
-
```text
|
|
494
|
-
armed: search=no agent board docs/plans/board-search/board.md
|
|
495
|
-
=== heartbeat 11:22:09 · search no agent
|
|
496
|
-
=== heartbeat 11:22:19 · search no agent
|
|
497
|
-
stopped by SIGTERM
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
**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.
|
|
501
|
-
|
|
502
|
-
**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.
|
|
503
|
-
|
|
504
|
-
**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.
|
|
505
|
-
|
|
506
|
-
**Deploy reports** need the workflow and the environments that count as targets; without `watch.deploy` no deploy run is read:
|
|
507
|
-
|
|
508
|
-
```json
|
|
509
|
-
{ "watch": { "deploy": { "workflow": "deploy.yml", "environments": ["stage", "production"] } } }
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
**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):
|
|
513
|
-
|
|
514
|
-
```text
|
|
515
|
-
=== feedback #<n> (search) review-bot[bot] inline · 2 new
|
|
516
|
-
src/index.ts:41 The cache key ignores the locale
|
|
517
|
-
src/index.ts:88 Missing test for an empty query
|
|
518
|
-
=== feedback #<n> (search) review-bot[bot] comment edited · 1 new
|
|
519
|
-
No issues found.
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
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:
|
|
523
|
-
|
|
524
|
-
```json
|
|
525
|
-
{ "watch": { "feedback": { "ignore": ["status-bot[bot]"] } } }
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
A bot's comment that only says it found nothing is no feedback: a comment of a login ending `[bot]` whose text, whitespace collapsed, equals one of `watch.feedback.noFindingTemplates`, with no inline comment of the same author within five minutes. The default is the clean-run comment of the CI code review (`## Code review` / `No issues found. Checked for bugs and CLAUDE.md compliance.`); `[]` counts every bot comment. Its inline comments, the real findings, always count.
|
|
529
|
-
|
|
530
|
-
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.
|
|
531
|
-
|
|
532
|
-
**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:
|
|
533
|
-
|
|
534
|
-
```text
|
|
535
|
-
=== preview #<n> (search) https://<host>/ · 3f2c1a9
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
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:
|
|
539
|
-
|
|
540
|
-
```json
|
|
541
|
-
{ "watch": { "previews": { "deployments": true, "authors": ["preview-bot[bot]"], "urlPattern": "Preview: (\\S+)" } } }
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
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.
|
|
545
|
-
|
|
546
|
-
**Source reports**: a ticket, an issue or a status page outside GitHub, set up with `/jh-follow` and reported as `=== source <name> …`. → [Following outside GitHub](#user-content-following-outside-github)
|
|
110
|
+
A global install ships no hooks, and the skills ask on their own.
|
|
547
111
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
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:
|
|
112
|
+
</details>
|
|
551
113
|
|
|
552
|
-
|
|
553
|
-
- **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 `/jh-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.
|
|
114
|
+
## In detail
|
|
554
115
|
|
|
555
|
-
|
|
116
|
+
<details>
|
|
117
|
+
<summary><b>Plans, boards and the skills</b></summary>
|
|
556
118
|
|
|
557
|
-
|
|
119
|
+
A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes: which phases each one works on, on which model, after what. A **lane** is one agent session in its own git worktree, and **Jack** is the session in the main checkout that runs `/jh-orchestrate`.
|
|
558
120
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
- `worktreeDirectory` (default `.claude/worktrees`) is where lane worktrees go: relative, below the repository root, without `..`; `jh sync` refuses anything else.
|
|
121
|
+
<picture>
|
|
122
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://unpkg.com/jackherd/docs/skills-dark.svg">
|
|
123
|
+
<img src="https://unpkg.com/jackherd/docs/skills.svg" alt="/jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
|
|
124
|
+
</picture>
|
|
564
125
|
|
|
565
|
-
|
|
126
|
+
| Skill | Does |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `/jh-start` | checks the setup and changes nothing |
|
|
129
|
+
| `/jh-grill` | questions a loose idea until every decision is settled |
|
|
130
|
+
| `/jh-plan` | writes the plan and proposes the board's rows |
|
|
131
|
+
| `/jh-implement` | builds and tests one phase and ticks it off; independent phases run as parallel subagents |
|
|
132
|
+
| `/jh-orchestrate` | runs a board's lanes in herdr, as Jack |
|
|
133
|
+
| `/jh-pre-commit` | runs `jh check` and the reviewers, proposes the commit message and asks before committing |
|
|
134
|
+
| `/jh-follow` | follows a ticket or a status page outside GitHub |
|
|
135
|
+
| `/jh-diagnose` | turns a bug into a failing command, then hands the fix on |
|
|
566
136
|
|
|
567
|
-
The
|
|
137
|
+
The global install names the skills with a `jh-` prefix; a project install uses none, unless the project already has skills of the same names.
|
|
568
138
|
|
|
569
139
|
</details>
|
|
570
140
|
|
|
571
141
|
<details>
|
|
572
|
-
<summary><b>
|
|
573
|
-
|
|
574
|
-
### Following outside GitHub
|
|
575
|
-
|
|
576
|
-
Not everything that moves lives on GitHub. A product owner answers in the ticket, a vendor's status page turns red. Point Jack at it, and he keeps an ear on it:
|
|
577
|
-
|
|
578
|
-
```text
|
|
579
|
-
/jh-follow PROJ-123
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
`/jh-follow` asks what to follow, checks the tool and its login, shows you a dry run, and writes the source only after your yes.
|
|
583
|
-
|
|
584
|
-
From then on [the watcher](#user-content-the-watcher) reads it every few minutes and barks at the coordinator when something changes; a ticket also shows as a badge beside its lane's plan. That needs herdr. Without it you can still set a source up and dry-run it.
|
|
585
|
-
|
|
586
|
-
- [How a source works](#user-content-how-a-source-works)
|
|
587
|
-
- [Recipes](#user-content-recipes)
|
|
588
|
-
|
|
589
|
-
#### How a source works
|
|
590
|
-
|
|
591
|
-
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. `/jh-follow` fills one of the recipes below for you.
|
|
592
|
-
|
|
593
|
-
```json
|
|
594
|
-
{
|
|
595
|
-
"watch": {
|
|
596
|
-
"sources": [
|
|
597
|
-
{
|
|
598
|
-
"name": "tickets",
|
|
599
|
-
"every": 5,
|
|
600
|
-
"command": ["tickets-cli", "search", "--query", "key in (T-123)", "--json"],
|
|
601
|
-
"items": ".issues",
|
|
602
|
-
"map": {
|
|
603
|
-
"id": ".key",
|
|
604
|
-
"updatedAt": ".fields.updated",
|
|
605
|
-
"title": ".fields.summary",
|
|
606
|
-
"body": ".fields.comment.comments[-1].body",
|
|
607
|
-
"author": ".fields.comment.comments[-1].author.name"
|
|
608
|
-
},
|
|
609
|
-
"ignoreAuthors": ["me"],
|
|
610
|
-
"lane": "search"
|
|
611
|
-
}
|
|
612
|
-
]
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
```text
|
|
618
|
-
=== source tickets (search) T-123 Decide the layout · updated
|
|
619
|
-
Yes, take variant B.
|
|
620
|
-
https://<host>/browse/T-123
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
`items` is the path of the list in the output (without it the output is the list), `map` picks each entry's fields: `id` is required, `updatedAt` too unless a content field (`title`, `author`, `body`, `url`) or `reportOn` can tell a change, and `title`, `url`, `body`, `author` and `status` (a ticket's status, below) are optional; any further field you name (`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.
|
|
624
|
-
|
|
625
|
-
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, and then it is required. 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.
|
|
626
|
-
|
|
627
|
-
**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`.
|
|
628
|
-
|
|
629
|
-
```json
|
|
630
|
-
"map": { "id": ".key", "updatedAt": ".fields.updated", "title": ".fields.summary", "status": ".fields.status.name", "assignee": ".fields.assignee.displayName" },
|
|
631
|
-
"reportOn": ["status", "assignee"]
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
```text
|
|
635
|
-
=== source tickets (search) T-123 Decide the layout · updated: status In progress → Ready for Dev, assignee (empty) → alex
|
|
636
|
-
https://<host>/browse/T-123
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
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.
|
|
640
|
-
|
|
641
|
-
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:
|
|
642
|
-
|
|
643
|
-
```text
|
|
644
|
-
=== source tickets failed: exit 1
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
**Show a ticket beside its plan.** `"ticket": true` marks a source's entries as tickets. The herd then shows each one as a badge in the 📎 line of its lane's plan, right after `📄 Plan`: `📡 T-123 · In Progress`, linked to the ticket. A ticket belongs to the source's `lane`, and also to any lane whose board row, name or plan names its id as a whole word (`T-12` is not found in `T-123`); so one source listing all your open tickets works without `lane`. `map.status` is the status the badge shows; without it the badge shows only the key. A ticket whose last reported change is less than a day old is amber, and a folded lane then shows `📡 <n>` in amber before its bar, linked to the first new ticket. A lane shows at most three tickets, new ones first, then `+<n>`; a link chip of the plan with the same URL gives way to the badge. For this the saved state keeps, of a marked source only, each entry of its last successful run with its URL, its status (stored like a `reportOn` value) and the time its last change was reported. The badges come from the watcher's state like the pull request badges: without a running watcher or with a state older than three minutes there are none, and the header says `watcher off` or `state 4 min old`, also without a pull request. The [GitHub Projects recipe](#user-content-recipes) is a ticket source with a status.
|
|
648
|
-
|
|
649
|
-
**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.
|
|
650
|
-
|
|
651
|
-
**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 `/jh-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`:
|
|
652
|
-
|
|
653
|
-
```text
|
|
654
|
-
dry run source tickets: ["tickets-cli","search","--query","key in (T-123)","--json"]
|
|
655
|
-
2 entries
|
|
656
|
-
T-123 · alex · Decide the layout
|
|
657
|
-
T-124 · me · Pick a colour (ignoreAuthors)
|
|
658
|
-
first run of this source: baseline, nothing reported; later a new or changed entry reports like this:
|
|
659
|
-
=== source tickets (search) T-123 Decide the layout · new
|
|
660
|
-
Yes, take variant B.
|
|
661
|
-
watcher state not read or changed; the watcher takes a new source over at its next start
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
The watcher reads `watch.sources` when it starts: a new or changed source counts from its next start.
|
|
665
|
-
|
|
666
|
-
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.
|
|
667
|
-
|
|
668
|
-
#### Recipes
|
|
669
|
-
|
|
670
|
-
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 TypeScript script run by Bun (which runs wherever `jh` does) puts it from `process.env` straight into the request header. A script fails with an exit code, never with an empty answer the watcher would take for a source with nothing new, and it prints neither the token nor the body of a failed answer. The closing `export {}` makes the file a module, so the type check accepts top-level `await` and the names do not clash between scripts. A recipe that needs no script (`gh …`, `curl` on a public page, `curl --netrc`) stays a direct command. The watcher starts a source without a shell and reads `bun` as the first word to mean the Bun that runs the watcher itself, so the entry needs no absolute path, which would differ from machine to machine, even when Bun is not on the `PATH` (installed in `~/.bun/bin` only, say).
|
|
671
|
-
|
|
672
|
-
**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. `plainText` joins the text and mention nodes of the comment's document tree into plain text; it is a pure function, so you can test it on its own. The script, for example `scripts/sources/jira.ts`, with your project in the query and `<host>` your `<site>.atlassian.net`:
|
|
673
|
-
|
|
674
|
-
```ts
|
|
675
|
-
// scripts/sources/jira.ts
|
|
676
|
-
const jql = 'project = PROJ AND assignee = currentUser() AND statusCategory != Done'
|
|
677
|
-
const host = '<host>'
|
|
678
|
-
const parallel = 6
|
|
679
|
-
|
|
680
|
-
type DocumentNode = { type?: string; text?: string; attrs?: { text?: string }; content?: DocumentNode[] }
|
|
681
|
-
type IssueComment = { author?: { displayName?: string }; body?: DocumentNode }
|
|
682
|
-
type Issue = { key: string; fields: { updated: string; summary: string; comment?: { comments: IssueComment[] } } }
|
|
683
|
-
|
|
684
|
-
const plainText = (node: DocumentNode): string =>
|
|
685
|
-
node.type === 'text' || node.type === 'mention'
|
|
686
|
-
? (node.text ?? node.attrs?.text ?? '')
|
|
687
|
-
: (node.content ?? []).map(plainText).join('')
|
|
688
|
-
|
|
689
|
-
const acli = async (args: string[]): Promise<unknown> => {
|
|
690
|
-
const child = Bun.spawn(['acli', ...args], { stdout: 'pipe', stderr: 'ignore', signal: AbortSignal.timeout(20_000) })
|
|
691
|
-
const output = await new Response(child.stdout).text()
|
|
692
|
-
if ((await child.exited) !== 0) process.exit(1)
|
|
693
|
-
try {
|
|
694
|
-
return JSON.parse(output)
|
|
695
|
-
} catch {
|
|
696
|
-
return process.exit(1)
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const entryOf = ({ key, fields }: Issue) => {
|
|
701
|
-
const latest = fields.comment?.comments.at(-1)
|
|
702
|
-
return {
|
|
703
|
-
key,
|
|
704
|
-
url: `https://${host}/browse/${key}`,
|
|
705
|
-
updated: fields.updated,
|
|
706
|
-
summary: fields.summary,
|
|
707
|
-
author: latest?.author?.displayName ?? null,
|
|
708
|
-
body: latest?.body ? plainText(latest.body) : null,
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const keys = ((await acli(['jira', 'workitem', 'search', '--jql', jql, '--limit', '100', '--json'])) as { key: string }[]).map(
|
|
713
|
-
({ key }) => key,
|
|
714
|
-
)
|
|
715
|
-
const entries = []
|
|
716
|
-
for (let start = 0; start < keys.length; start += parallel) {
|
|
717
|
-
const batch = keys.slice(start, start + parallel)
|
|
718
|
-
const issues = await Promise.all(
|
|
719
|
-
batch.map((key) => acli(['jira', 'workitem', 'view', key, '--fields', 'summary,updated,comment', '--json'])),
|
|
720
|
-
)
|
|
721
|
-
entries.push(...(issues as Issue[]).map(entryOf))
|
|
722
|
-
}
|
|
723
|
-
console.log(JSON.stringify(entries))
|
|
724
|
-
|
|
725
|
-
export {}
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
A failed `acli` call ends the script with exit code 1, which the watcher reports as a failing source; nothing is printed that it could take for an empty list.
|
|
729
|
-
|
|
730
|
-
```json
|
|
731
|
-
{
|
|
732
|
-
"name": "tickets-decisions",
|
|
733
|
-
"command": ["bun", "scripts/sources/jira.ts"],
|
|
734
|
-
"map": {
|
|
735
|
-
"id": ".key",
|
|
736
|
-
"updatedAt": ".updated",
|
|
737
|
-
"title": ".summary",
|
|
738
|
-
"url": ".url",
|
|
739
|
-
"body": ".body",
|
|
740
|
-
"author": ".author"
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
**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:
|
|
746
|
-
|
|
747
|
-
```json
|
|
748
|
-
{
|
|
749
|
-
"name": "tickets-decisions",
|
|
750
|
-
"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"],
|
|
751
|
-
"items": ".issues",
|
|
752
|
-
"map": {
|
|
753
|
-
"id": ".key",
|
|
754
|
-
"updatedAt": ".fields.updated",
|
|
755
|
-
"title": ".fields.summary",
|
|
756
|
-
"body": ".fields.comment.comments[-1].body.content[0].content[0].text",
|
|
757
|
-
"author": ".fields.comment.comments[-1].author.displayName"
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
**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.ts`, with `<host>` the API host `api.linear.app`. Linear answers a bad key or query with HTTP 200 and an `errors` list, so the script checks for it as well:
|
|
763
|
-
|
|
764
|
-
```ts
|
|
765
|
-
// scripts/sources/linear.ts
|
|
766
|
-
const token = process.env.LINEAR_API_KEY
|
|
767
|
-
if (!token) process.exit(1)
|
|
768
|
-
|
|
769
|
-
const query = `{ issues(filter: { team: { key: { eq: "ENG" } }, number: { eq: 123 } }) {
|
|
770
|
-
nodes { identifier title url updatedAt comments { nodes { body user { name } } } } } }`
|
|
771
|
-
|
|
772
|
-
const response = await fetch('https://<host>/graphql', {
|
|
773
|
-
method: 'POST',
|
|
774
|
-
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
|
775
|
-
body: JSON.stringify({ query }),
|
|
776
|
-
signal: AbortSignal.timeout(20_000),
|
|
777
|
-
})
|
|
778
|
-
if (!response.ok) process.exit(1)
|
|
779
|
-
const answer = (await response.json()) as { errors?: unknown }
|
|
780
|
-
if (answer.errors) process.exit(1)
|
|
781
|
-
console.log(JSON.stringify(answer))
|
|
782
|
-
|
|
783
|
-
export {}
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
```json
|
|
787
|
-
{
|
|
788
|
-
"name": "linear-eng-123",
|
|
789
|
-
"command": ["bun", "scripts/sources/linear.ts"],
|
|
790
|
-
"items": ".data.issues.nodes",
|
|
791
|
-
"map": {
|
|
792
|
-
"id": ".identifier",
|
|
793
|
-
"updatedAt": ".updatedAt",
|
|
794
|
-
"title": ".title",
|
|
795
|
-
"url": ".url",
|
|
796
|
-
"body": ".comments.nodes[-1].body",
|
|
797
|
-
"author": ".comments.nodes[-1].user.name"
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
Check with `--raw` which end of `comments.nodes` holds the newest comment; `[0]` instead of `[-1]` if it is the first.
|
|
803
|
-
|
|
804
|
-
**GitHub issues.** Login: `gh auth login`, kept by `gh`:
|
|
805
|
-
|
|
806
|
-
```json
|
|
807
|
-
{
|
|
808
|
-
"name": "decisions",
|
|
809
|
-
"command": ["gh", "issue", "list", "--repo", "<owner>/<repo>", "--label", "decision", "--json", "number,title,url,updatedAt,comments"],
|
|
810
|
-
"map": {
|
|
811
|
-
"id": ".number",
|
|
812
|
-
"updatedAt": ".updatedAt",
|
|
813
|
-
"title": ".title",
|
|
814
|
-
"url": ".url",
|
|
815
|
-
"body": ".comments[-1].body",
|
|
816
|
-
"author": ".comments[-1].author.login"
|
|
817
|
-
},
|
|
818
|
-
"ignoreAuthors": ["<your-login>"]
|
|
819
|
-
}
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
**GitHub Projects.** Reads the cards of a project board as tickets, without a script. Login: `gh` needs the `project` scope, `gh auth refresh -s project`. `<n>` is the project's number, `<owner>` its organization or `@me`; without `--limit` `gh` returns only 30 cards. Each card is `items[]` with its `id`, `title`, `content` (the issue or pull request, `content.url`; a draft card has none, so its badge has no link) and one key per project field, the name with a lower-case first letter, so the `Status` field is `.status` and holds the option's name. A card carries no time of its own, so the entry maps no `updatedAt` and a change of the title or URL is the change. The cards belong to a lane through `lane`, since their ids (`PVTI_…`) are not what a plan names:
|
|
823
|
-
|
|
824
|
-
```json
|
|
825
|
-
{
|
|
826
|
-
"name": "board",
|
|
827
|
-
"command": ["gh", "project", "item-list", "<n>", "--owner", "<owner>", "--format", "json", "--limit", "200"],
|
|
828
|
-
"items": ".items",
|
|
829
|
-
"ticket": true,
|
|
830
|
-
"lane": "<lane>",
|
|
831
|
-
"map": { "id": ".id", "title": ".title", "url": ".content.url", "status": ".status" }
|
|
832
|
-
}
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
jackherd only reads the board; the status moves by the project's own workflows (Settings → Workflows: "Item closed" and "Pull request merged" set Done, "Auto-add" brings new issues in), and a pull request moves its issue's card only when its description links it with `Closes #<n>`; otherwise the status stays where it was. Add `"reportOn": ["status"]` to be told of each move as well.
|
|
836
|
-
|
|
837
|
-
**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.ts`:
|
|
838
|
-
|
|
839
|
-
```ts
|
|
840
|
-
// scripts/sources/sentry.ts
|
|
841
|
-
const token = process.env.SENTRY_AUTH_TOKEN
|
|
842
|
-
if (!token) process.exit(1)
|
|
843
|
-
|
|
844
|
-
const response = await fetch(
|
|
845
|
-
'https://<host>/api/0/projects/<org>/<project>/issues/?query=is:unresolved&statsPeriod=24h',
|
|
846
|
-
{ headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(20_000) },
|
|
847
|
-
)
|
|
848
|
-
if (!response.ok) process.exit(1)
|
|
849
|
-
console.log(JSON.stringify(await response.json()))
|
|
850
|
-
|
|
851
|
-
export {}
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
```json
|
|
855
|
-
{
|
|
856
|
-
"name": "errors",
|
|
857
|
-
"every": 10,
|
|
858
|
-
"command": ["bun", "scripts/sources/sentry.ts"],
|
|
859
|
-
"map": { "id": ".id", "updatedAt": ".lastSeen", "title": ".title", "url": ".permalink", "body": ".culprit" }
|
|
860
|
-
}
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
`lastSeen` moves with every event, but title, culprit and link do not, so only a new issue wakes the coordinator, not each occurrence.
|
|
864
|
-
|
|
865
|
-
**A status page.** A page on the common status page service answers its public API without a token; `<host>` is the page's own:
|
|
866
|
-
|
|
867
|
-
```json
|
|
868
|
-
{
|
|
869
|
-
"name": "provider-status",
|
|
870
|
-
"every": 5,
|
|
871
|
-
"command": ["curl", "-fsS", "--max-time", "20", "https://<host>/api/v2/incidents/unresolved.json"],
|
|
872
|
-
"items": ".incidents",
|
|
873
|
-
"map": { "id": ".id", "updatedAt": ".updated_at", "title": ".name", "url": ".shortlink", "body": ".incident_updates[0].body" }
|
|
874
|
-
}
|
|
875
|
-
```
|
|
142
|
+
<summary><b>Which model runs each phase</b></summary>
|
|
876
143
|
|
|
877
|
-
|
|
144
|
+
Each phase in the plan names its model: the planning model (Opus by default) where a mistake is expensive, such as data, security or a migration, and the cheaper implementation model (Sonnet by default) for everything else. `/jh-implement` and Jack start each phase on its model, and in a project install a hook stops a phase handed to a weaker one. Set the models per role under `models` in the config.
|
|
878
145
|
|
|
879
146
|
</details>
|
|
880
147
|
|
|
881
148
|
<details>
|
|
882
|
-
<summary><b>
|
|
883
|
-
|
|
884
|
-
### Herd and context
|
|
149
|
+
<summary><b>The watcher and tickets outside GitHub</b></summary>
|
|
885
150
|
|
|
886
|
-
|
|
151
|
+
Jack doesn't watch the terminals himself. `jh watch`, which he starts himself, wakes him when a lane is blocked or done, a check turns red, a pull request gets feedback, a lane's context fills up or a lane waits for an approval; deploys and previews too, when configured.
|
|
887
152
|
|
|
888
|
-
-
|
|
889
|
-
- [Attachments](#user-content-attachments)
|
|
890
|
-
- [Where the numbers come from](#user-content-where-the-numbers-come-from)
|
|
891
|
-
- [Keys](#user-content-keys)
|
|
892
|
-
- [Context fill](#user-content-context-fill)
|
|
153
|
+
`/jh-follow PROJ-123` adds a ticket, an issue or a status page: it checks the tool and its login, shows you a dry run and writes the source to `watch.sources` after your yes. The [recipes](https://unpkg.com/jackherd/recipes/README.md) cover Jira, Linear, GitHub issues and Projects, Sentry and status pages. The reports need herdr; a source can be set up and dry-run without it.
|
|
893
154
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
One block per lane of the chosen board, lanes that wait on you first:
|
|
155
|
+
</details>
|
|
897
156
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
- how full the agent's context is (`🥣`), and a bar with the lane's ticked tasks (`◐`)
|
|
901
|
-
- the branch, commits ahead and uncommitted files
|
|
902
|
-
- the lane's open pull request with its checks
|
|
903
|
-
- per plan its attachments as chips (`📎`), the lane's phases with their counts, and the next open task of the current phase (`▶`). A finished phase is a closed row of its own with a green `✓` and a green `n/n`; a phase whose only open box is the documentation box of its review gate reads `docs open` instead of n-1/n.
|
|
904
|
-
- The current phase is the one whose checkboxes moved last: on every re-read the herd compares each lane's ticks with the read before, and when exactly one phase changed, that phase becomes current and stays so until another single phase moves. On the first read, after a read where several phases changed at once (a merge, say), or when the lane does not show the moved phase, the newest board row decides, as follows. The memory lives only in the running `jh herd`, so a restart starts again from the board. The newest board row names the current phase this way: 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
|
|
157
|
+
<details>
|
|
158
|
+
<summary><b>The herd view</b></summary>
|
|
905
159
|
|
|
906
|
-
|
|
160
|
+
`jh herd` shows every lane of a board: agent state, plan progress, pull request, checks and context fill. It only reads.
|
|
907
161
|
|
|
908
162
|
```text
|
|
909
163
|
🐾 Board 01 11:38
|
|
@@ -916,7 +170,7 @@ Four lanes of an example board, three with herdr agents and one without, rendere
|
|
|
916
170
|
│
|
|
917
171
|
├─ ✓ P2 Search box 4/4
|
|
918
172
|
├─ ▸ P3 Docs docs open
|
|
919
|
-
│ └─ ▶
|
|
173
|
+
│ └─ ▶ Docs updated (README)
|
|
920
174
|
└─ ▸ P4 Review 0/2
|
|
921
175
|
────────────────────────────────────────────────────────────────────────────────
|
|
922
176
|
▸ ◉ checkout-fee done ↗ #42 💬 2 🥣 48 % │ ██████████ ◐ 6/6
|
|
@@ -934,237 +188,44 @@ Four lanes of an example board, three with herdr agents and one without, rendere
|
|
|
934
188
|
↑↓ select →← open a show ticked s html q quit ? help
|
|
935
189
|
```
|
|
936
190
|
|
|
937
|
-
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 `herd --once`:
|
|
938
|
-
|
|
939
|
-
```text
|
|
940
|
-
🐾 Board 01 11:38:08
|
|
941
|
-
───────────────────────────────────────────────────────────────────────────────
|
|
942
|
-
───────────────────────────────────────────────────────────────────────────────
|
|
943
|
-
· no agent (2) search-index 2/5 · search-ui 0/5
|
|
944
|
-
───────────────────────────────────────────────────────────────────────────────
|
|
945
|
-
▸ planned (3 phases in 1 plan)
|
|
946
|
-
gh pr list: no git remotes found
|
|
947
|
-
origin/main not found, reading plans from the checkout
|
|
948
|
-
───────────────────────────────────────────────────────────────────────────────
|
|
949
|
-
↑↓ select →← open a show ticked s html q quit ? help
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
#### Attachments
|
|
953
|
-
|
|
954
|
-
Under each plan's title, a `📎` line carries the plan's links as chips; a click opens one in the browser:
|
|
955
|
-
|
|
956
|
-
- `📄 Plan` opens the board's HTML page at that plan: its status, its chips and its phases with their checkboxes. The herd writes the page when it is missing or the plans changed.
|
|
957
|
-
- 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.
|
|
958
|
-
|
|
959
|
-
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`).
|
|
960
|
-
|
|
961
|
-
#### Preview and feedback
|
|
962
|
-
|
|
963
|
-
A pull request row reads `↗ #<n> ✓ checks` (red: the failed checks, `✗ test, lint`, at most three and `+n`; under a lane's pull request up to three failed tests and `+n`), 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 herd 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`.
|
|
964
|
-
|
|
965
|
-
#### Where the numbers come from
|
|
966
|
-
|
|
967
|
-
- **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 herd 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.
|
|
968
|
-
- **Pull requests and checks** come from `gh`. Without `gh` the herd runs and says why the PRs are missing.
|
|
969
|
-
- **Agent state and context fill** come from herdr, with `"terminal": { "adapter": "herdr" }` in the config. Without it, every lane shows no agent.
|
|
970
|
-
- **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.
|
|
971
|
-
|
|
972
|
-
While it runs inside herdr, the herd also sets each lane's sidebar title (`P4 · 7/12`) and clears it on exit.
|
|
973
|
-
|
|
974
|
-
#### Keys
|
|
975
|
-
|
|
976
|
-
| Key | Does |
|
|
977
|
-
| --- | --- |
|
|
978
|
-
| `↑` `↓` | select a lane, phase or task |
|
|
979
|
-
| `→` `←` | open or close the selected row |
|
|
980
|
-
| `a` | list the ticked checkboxes one by one, or sum them up again |
|
|
981
|
-
| `s` | write the board as one HTML page with every lane and plan, which also shows which lane waits for which, and open it |
|
|
982
|
-
| 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 |
|
|
983
|
-
| `?` | the legend |
|
|
984
|
-
| `q` | quit |
|
|
985
|
-
|
|
986
|
-
Options: `--once` prints one frame and exits (for reading the herd from a script or a session), `--ensure` brings back the missing herd panes beside the coordinator and each lane of the board (an empty shell in that tab takes the herd, 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.
|
|
987
|
-
|
|
988
|
-
#### Context fill
|
|
989
|
-
|
|
990
|
-
How full each session's context is, as a number.
|
|
991
|
-
|
|
992
|
-
`jh context [<name>]` prints how full an agent's context is, as one line of JSON, or `null` without a value:
|
|
993
|
-
|
|
994
|
-
```text
|
|
995
|
-
{"tokens":356307,"window":1000000,"share":0.356307,"model":"claude-opus-5-5","level":"warn"}
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
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 %.
|
|
999
|
-
|
|
1000
|
-
| Who | `warn` from | `alert` from | Config |
|
|
1001
|
-
| --- | --- | --- | --- |
|
|
1002
|
-
| Lanes | 60 % | 80 % | `plans.context.warnAt`, `alertAt` |
|
|
1003
|
-
| Coordinator (`--coordinator`) | 50 % | 70 % | `plans.context.coordinator.warnAt`, `alertAt` |
|
|
1004
|
-
|
|
1005
|
-
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)).
|
|
1006
|
-
|
|
1007
191
|
</details>
|
|
1008
192
|
|
|
1009
193
|
<details>
|
|
1010
|
-
<summary><b>
|
|
1011
|
-
|
|
1012
|
-
### Reference
|
|
1013
|
-
|
|
1014
|
-
- [Commands](#user-content-commands)
|
|
1015
|
-
- [Configuration](#user-content-configuration)
|
|
1016
|
-
- [What is included](#user-content-what-is-included)
|
|
1017
|
-
- [Status line](#user-content-status-line)
|
|
1018
|
-
- [A package you publish from the repository](#user-content-a-package-you-publish-from-the-repository)
|
|
194
|
+
<summary><b>Commands and configuration</b></summary>
|
|
1019
195
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
|
1023
|
-
|
|
|
1024
|
-
| `jh
|
|
1025
|
-
| `jh
|
|
1026
|
-
| `jh
|
|
1027
|
-
| `jh
|
|
1028
|
-
| `jh
|
|
1029
|
-
| `jh
|
|
1030
|
-
| `jh herd` | Live view of the board's lanes and plans ([Herd](#user-content-herd-and-context)) | `--once`: one frame and exit; `--lane <name>`: one lane; `--ensure`: bring back the missing herd panes beside the coordinator and each lane, and exit; a board path (default: the newest board) |
|
|
1031
|
-
| `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 |
|
|
1032
|
-
| `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) |
|
|
1033
|
-
|
|
1034
|
-
Without a global install, run any of them as `bunx jackherd <command>`.
|
|
1035
|
-
|
|
1036
|
-
#### Configuration
|
|
1037
|
-
|
|
1038
|
-
`.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.
|
|
1039
|
-
|
|
1040
|
-
| Key | Default | What it decides |
|
|
1041
|
-
| --- | --- | --- |
|
|
1042
|
-
| `responseLanguage` | `en` | the language the skills speak with you, and the watcher's status words |
|
|
1043
|
-
| `responseAddress` | `informal` | `informal` or `formal`: how replies address you where the language has both (German: `du` or `Sie`). `jh sync` writes it once into the kit rule `rules/kit/code-style.md`, which every session loads; it follows `responseLanguage`; the status words of the watcher and the herd use no pronoun and stay as they are |
|
|
1044
|
-
| `planLanguage` | `en` | the language of plans and boards, including their headings |
|
|
1045
|
-
| `planDirectory` | `docs/plans` | where plans and boards live |
|
|
1046
|
-
| `board.pattern`, `board.file`, `board.term` | `board-*`, `board.md`, `board` | [Boards](#user-content-boards) |
|
|
1047
|
-
| `worktreeDirectory` | `.claude/worktrees` | where lane worktrees go; relative, below the repository root |
|
|
1048
|
-
| `baseBranch` | detected, else `main` | the branch lanes start from and plans are read at |
|
|
1049
|
-
| `skillPrefix` | `''` (global: `jh-`) | put before every skill and agent name |
|
|
1050
|
-
| `commands.test`, `typecheck`, `lint`, `format`, `install`, `dev` | detected from the lock file | what the skills and `check` run; `dev` runs as `PORT=<port> <dev>` |
|
|
1051
|
-
| `lanes.copy` | `[]` (then `.worktreeinclude`) | untracked files copied into every new worktree |
|
|
1052
|
-
| `lanes.lockFile` | detected, else `bun.lock` | the lock file a rebase takes from the base branch |
|
|
1053
|
-
| `models.plan`, `implement`, `review`, `explore`, `orchestrate` | written by `init` | full model IDs per role; aliases are refused |
|
|
1054
|
-
| `terminal.adapter` | `none` | `herdr` for agent states and context fill |
|
|
1055
|
-
| `plans.fetchIntervalMinutes` | `5` | background fetch of the base branch while the herd runs |
|
|
1056
|
-
| `plans.linkKinds` | built in | links in a plan the board page shows as chips |
|
|
1057
|
-
| `plans.context` | lanes 60 % / 80 %, coordinator 50 % / 70 %, window 200 000 | [Context fill](#user-content-context-fill) |
|
|
1058
|
-
| `watch.deploy` | none | the deploy workflow and environments the watcher reports |
|
|
1059
|
-
| `watch.feedback` | `{ "ignore": [] }` | `false` turns feedback reports off; `ignore` lists logins whose feedback does not count; `noFindingTemplates`: bot comments that report no finding (default: the CI code review's clean run) |
|
|
1060
|
-
| `watch.checks` | `{}` | `failedTestPattern`: the regular expression for a failed test in a red run's log, first group the name (default: Bun's `(fail) <name>`); no nested quantifiers |
|
|
1061
|
-
| `watch.previews` | none | `deployments` (`false`), `authors` (`[]`) and `urlPattern`: where the watcher reads a lane's deploy preview |
|
|
1062
|
-
| `watch.sources` | `[]` | commands whose JSON output the watcher reports as `source`: `name`, `command`, `every` (`5`), `items`, `map`, `ignoreAuthors` (`[]`), `reportLimit` (`5`), `lane`, `reportOn`, `ticket`; [recipes](#user-content-recipes) |
|
|
1063
|
-
| `check.lintFilesCommand` | detected | lint over the changed files |
|
|
1064
|
-
| `check.checks` | `[]` | project checks (`name`, `command`, optional `globs`) |
|
|
1065
|
-
| `check.reviewerRoutes` | `[]` | which reviewer `/jh-pre-commit` runs for which paths (`reviewer`, `globs`, `why`) |
|
|
1066
|
-
| `check.generatorOwned` | `[]` | files a generator owns (`glob`, `generator`); `check` names the generator when one of them changed |
|
|
1067
|
-
| `check.buildArtifacts` | build output globs | staged files `check` refuses |
|
|
1068
|
-
| `check.publicContent` | none | [published files](#user-content-a-package-you-publish-from-the-repository) |
|
|
1069
|
-
| `check.looseTests` | `false` | also run tests outside any package |
|
|
196
|
+
| Command | Does |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `jh init` | creates `.jackherd/config.json` and installs the kit (`--global` for `~/.claude`) |
|
|
199
|
+
| `jh sync` | updates the kit; a file you edited gets the new version beside it as `.kit-new` |
|
|
200
|
+
| `jh update` | installs the newest release and syncs |
|
|
201
|
+
| `jh check` | types, lint and tests of what you touched, plus the plan and board checks (`--skip-slow`, `--parallel <plan>`) |
|
|
202
|
+
| `jh herd` | the live view (`--once`, `--lane <name>`) |
|
|
203
|
+
| `jh watch` | the watcher Jack runs |
|
|
204
|
+
| `jh context` | how full an agent's context is, as JSON |
|
|
205
|
+
| `jh config` | the commands and the base branch the repository uses |
|
|
1070
206
|
|
|
1071
|
-
|
|
207
|
+
Without a global install, run them as `bunx jackherd <command>`. The config is `.jackherd/config.json` in a project and `~/.jackherd/config.json` globally, checked against [one schema](https://unpkg.com/jackherd/schemas/jackherd.schema.json) your editor completes. What usually needs setting:
|
|
1072
208
|
|
|
1073
209
|
```json
|
|
1074
210
|
{
|
|
1075
211
|
"responseLanguage": "de",
|
|
1076
|
-
"planLanguage": "de",
|
|
1077
|
-
"skillPrefix": "jh-",
|
|
1078
|
-
"lanes": { "copy": ["apps/web/.env"] },
|
|
1079
212
|
"terminal": { "adapter": "herdr" },
|
|
1080
|
-
"
|
|
1081
|
-
|
|
1082
|
-
}
|
|
213
|
+
"lanes": { "copy": ["apps/web/.env"] },
|
|
214
|
+
"check": { "reviewerRoutes": [{ "reviewer": "jh-uncle-bob", "globs": ["src/**/*.ts"], "why": "Code" }] }
|
|
1083
215
|
}
|
|
1084
216
|
```
|
|
1085
217
|
|
|
1086
|
-
#### What is included
|
|
1087
|
-
|
|
1088
|
-
- **Workflow skills:** `start`, `grill`, `plan`, `implement`, `orchestrate`, `follow` ([Planning](#user-content-the-skills-and-what-they-hand-on)).
|
|
1089
|
-
- **Review skills:** `pre-commit`, `review`, `uncle-bob`, `security-audit`, `devops`, `diagnose`.
|
|
1090
|
-
- **Agents:** `plan`, `implement`, `explore`, `spec-review`, `uncle-bob`, `security-audit`, `devops`, `public-content`, each on the model you set per role.
|
|
1091
|
-
- **Rules** in `.claude/rules/kit/`: `code-style`, `testing`, `pull-requests`, `writing-for-agents`, `arc42`, `architecture-files`.
|
|
1092
|
-
- **Hooks** (project install only):
|
|
1093
|
-
- `git-guard` refuses a git command that hides `commit`, `push`, `rebase`, `reset`, `checkout` or `merge` from your permission rules (`git -C … push`, `sh -c "…"`).
|
|
1094
|
-
- `watch-guard` refuses a `jh watch` start whose reports would go unseen.
|
|
1095
|
-
- `merge-guard` asks you before `gh pr merge` and `gh workflow run` run, when they are spelled out ([Guarded commands](#user-content-guarded-commands-and-self-merge)).
|
|
1096
|
-
- `ask-hint` (event `Stop`) tells you when a turn ends in a question asked as running text and used no `AskUserQuestion` dialog. It only hints, never blocks, and stays silent in a lane session, which asks as a text block. Switch it off with `"hints": { "askUserQuestion": false }` in `.jackherd/config.json`.
|
|
1097
|
-
- `format-on-edit` runs the project's own Biome on edited TS, JS and JSON files, when the project has it installed.
|
|
1098
|
-
- **herdr plugin:** a herd pane and an action that opens it ([Installation](https://unpkg.com/jackherd/INSTALL.md)).
|
|
1099
|
-
|
|
1100
|
-
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.
|
|
1101
|
-
|
|
1102
|
-
#### Status line
|
|
1103
|
-
|
|
1104
|
-
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.
|
|
1105
|
-
|
|
1106
|
-
#### A package you publish from the repository
|
|
1107
|
-
|
|
1108
|
-
When the repository publishes a package, `check.publicContent` names its files, and a change to them gets two extra looks before the commit:
|
|
1109
|
-
|
|
1110
|
-
```json
|
|
1111
|
-
{ "check": { "publicContent": { "globs": ["packages/my-kit/**"], "exclude": ["**/*.test.ts"] } } }
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
- **`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.
|
|
1115
|
-
- **The `public-content` agent**, which `/jh-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`.
|
|
1116
|
-
|
|
1117
218
|
</details>
|
|
1118
219
|
|
|
1119
220
|
<details>
|
|
1120
221
|
<summary><b>Troubleshooting</b></summary>
|
|
1121
222
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
**The herd 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.
|
|
1127
|
-
|
|
1128
|
-
**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`.
|
|
1129
|
-
|
|
1130
|
-
**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 `/jh-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.
|
|
1131
|
-
|
|
1132
|
-
**A lane shows no plan.** The herd 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.
|
|
1133
|
-
|
|
1134
|
-
**The herd 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).
|
|
1135
|
-
|
|
1136
|
-
**Pull requests and checks are missing.** They come from `gh`. The herd and the watcher run without it and print why (`gh pr list: no git remotes found`, say); install and authenticate `gh` for them.
|
|
1137
|
-
|
|
1138
|
-
**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.
|
|
1139
|
-
|
|
1140
|
-
**`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`.
|
|
1141
|
-
|
|
1142
|
-
**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.
|
|
1143
|
-
|
|
1144
|
-
**`(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.
|
|
1145
|
-
|
|
1146
|
-
**`=== 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.
|
|
1147
|
-
|
|
1148
|
-
**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.
|
|
1149
|
-
|
|
1150
|
-
**`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.
|
|
1151
|
-
|
|
1152
|
-
**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`.
|
|
1153
|
-
|
|
1154
|
-
**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).
|
|
1155
|
-
|
|
1156
|
-
**`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](https://unpkg.com/jackherd/INSTALL.md).
|
|
1157
|
-
|
|
1158
|
-
**`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`.
|
|
1159
|
-
|
|
1160
|
-
**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.
|
|
1161
|
-
|
|
1162
|
-
**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.
|
|
1163
|
-
|
|
1164
|
-
**`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.
|
|
223
|
+
- **`/jh-orchestrate` stops right away.** It needs herdr: start Claude Code in a herdr pane, or use `/jh-implement`.
|
|
224
|
+
- **Every lane shows `no agent`.** Set `"terminal": { "adapter": "herdr" }`; worktree, board lane and herdr agent need the same name.
|
|
225
|
+
- **Pull requests and checks are missing.** Install `gh` and log in.
|
|
1165
226
|
|
|
1166
227
|
</details>
|
|
1167
228
|
|
|
1168
229
|
## License
|
|
1169
230
|
|
|
1170
|
-
|
|
231
|
+
MIT, see [LICENSE](https://unpkg.com/jackherd/LICENSE). Four kit files adapt [mattpocock/skills](https://github.com/mattpocock/skills) (MIT, Copyright (c) Matt Pocock), see [NOTICE](https://unpkg.com/jackherd/NOTICE.txt). The artwork in [`assets/branding`](https://unpkg.com/jackherd/assets/branding/README.md) is all rights reserved.
|