jackherd 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -19
  3. package/assets/icons/skills.svg +1 -0
  4. package/dist/config.d.ts +9 -1
  5. package/dist/config.js +24 -1
  6. package/dist/plan-format.d.ts +24 -1
  7. package/dist/plan-format.js +25 -5
  8. package/docs/plan-contract.md +84 -0
  9. package/docs/skills-dark.svg +15 -2
  10. package/docs/skills.svg +15 -2
  11. package/herdr-plugin/herdr-plugin.toml +1 -1
  12. package/herdr-plugin/restore-plans.ts +56 -6
  13. package/install.sh +1 -1
  14. package/kit/hooks/hooks/merge-guard/board-settings.ts +5 -2
  15. package/kit/hooks/hooks/merge-guard/pull-request.ts +28 -15
  16. package/kit/hooks/hooks/merge-guard/run.ts +6 -3
  17. package/kit/hooks/hooks/merge-guard/settings.ts +12 -1
  18. package/kit/hooks/hooks/model-log/transcript.ts +4 -7
  19. package/kit/hooks/hooks/open-question/record.ts +4 -3
  20. package/kit/hooks/hooks/shared/dialog-answers.ts +7 -3
  21. package/kit/hooks/hooks/shared/log-vocabulary.ts +13 -0
  22. package/kit/hooks/hooks/shared/transcript.ts +7 -4
  23. package/kit/hooks/hooks/task-hint/decision.ts +3 -2
  24. package/kit/hooks/hooks/task-hint/run.ts +2 -4
  25. package/kit/rules/rules/kit/pull-requests.md +1 -0
  26. package/kit/workflow/skills/orchestrate/SKILL.md +22 -9
  27. package/kit/workflow/skills/orchestrate/handover/index.ts +8 -2
  28. package/kit/workflow/skills/orchestrate/references/adopt-plan.md +51 -0
  29. package/kit/workflow/skills/orchestrate/references/plan-commit.md +5 -5
  30. package/package.json +1 -1
  31. package/schemas/jackherd.schema.json +19 -0
  32. package/src/check/command.ts +1 -1
  33. package/src/check/file-kinds.ts +10 -8
  34. package/src/check/parallel-report.ts +10 -3
  35. package/src/check/settings.ts +4 -0
  36. package/src/check/skill-budget-defaults.ts +17 -5
  37. package/src/check/skill-budgets.ts +49 -19
  38. package/src/claude/paths.ts +12 -0
  39. package/src/cli.ts +1 -0
  40. package/src/config/schema.ts +27 -0
  41. package/src/context/fill.ts +2 -1
  42. package/src/context/log-vocabulary.ts +22 -0
  43. package/src/context/session-log.ts +2 -1
  44. package/src/lane-prompt/build.ts +10 -5
  45. package/src/lane-prompt/command.ts +6 -1
  46. package/src/lane-prompt/input.ts +4 -4
  47. package/src/lane-prompt/texts.ts +18 -10
  48. package/src/plan-contract/command.ts +69 -0
  49. package/src/plan-contract/contract.ts +144 -0
  50. package/src/plan-format/dependencies.ts +1 -4
  51. package/src/plan-format/index.ts +4 -0
  52. package/src/plan-format/lanes.ts +11 -0
  53. package/src/plan-format/markdown.ts +6 -5
  54. package/src/plan-format/model-table.ts +27 -0
  55. package/src/plans/activity-read.ts +98 -0
  56. package/src/plans/ask-coordinator.ts +20 -11
  57. package/src/plans/layout.ts +49 -4
  58. package/src/plans/live.ts +17 -4
  59. package/src/plans/plan-panes.ts +25 -5
  60. package/src/plans/snapshot.ts +18 -8
  61. package/src/plans/subagents-read.ts +4 -3
  62. package/src/plans/subagents.ts +2 -1
  63. package/src/plans/texts.ts +27 -1
  64. package/src/settled/send.ts +80 -4
  65. package/src/shared/parse-json.ts +8 -0
  66. package/src/sync/command.ts +17 -10
  67. package/src/sync/foreign-skills.ts +40 -0
  68. package/src/sync/global-kit.ts +31 -21
  69. package/src/sync/init.ts +2 -2
  70. package/src/sync/kit-exclude.ts +39 -0
  71. package/src/sync/plan-file.ts +1 -1
  72. package/src/sync/render.ts +2 -1
  73. package/src/sync/report.ts +37 -2
  74. package/src/sync/sync-result.ts +5 -2
  75. package/src/sync/sync.ts +13 -5
  76. package/src/task-signals/claude-signals.ts +6 -5
  77. package/src/watch/command.ts +56 -17
  78. package/src/watch/lane-message.ts +1 -1
  79. package/src/watch/lane-progress.ts +51 -2
  80. package/src/watch/lane-report.ts +57 -2
  81. package/src/watch/lane-watch.ts +2 -8
  82. package/src/watch/task-parallel-touched.ts +9 -2
  83. package/src/watch/texts.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.15.0
6
+
7
+ - **Any plan, once it meets the contract.** `docs/plan-contract.md` writes down what Jack needs from a plan: phases with an id, checkbox tasks, a model per phase, and from two phases on a dependency table. `jh plan check <file>` says per part whether it is there, missing or malformed, and exits 0 when the plan meets it. Without a dependency table, phases run one after another, and `jh check --parallel` says so.
8
+ - **Jack adopts a plan from another planner.** When a plan a lane starts from misses the contract, or lives only in an issue or a `.scratch/` folder, Jack mirrors it into a plan file and adds only what is missing: phase headings, tasks, model rows, the dependency table, short task names and a line saying the source planned no review. No word of the author's goes, which a word diff proves, and you see that diff before the plan is committed.
9
+ - **A lane builds with any skill.** The lane prompt names what every lane does, whatever skill it builds with (`/jh-implement`, someone else's, or your own): report each task with `jh task`, write the report, send questions as text, leave git to Jack. A prompt to a lane that does not start working comes back as an error instead of passing as sent, and Jack's own prompts wait the same way.
10
+ - **Other people's skills sit beside jackherd.** A skill installed with `npx skills add` (listed in `skills-lock.json`) only warns when it exceeds a word budget, it never turns `jh check` red. When one lands on the path of a jackherd skill, `jh sync` names it, also when it kept your file, with the way out: `"skillPrefix": "jh-"` moves jackherd's skills to `/jh-…` and leaves the other skill where it is.
11
+ - **Leave out jackherd skills you don't use.** List them in `kit.exclude` in `.jackherd/config.json`, by name without the prefix (`"exclude": ["devops", "grill"]`); a name takes its skill and its agent. `jh sync` stops writing them, removes a copy you never changed, keeps one you changed, and names what it left out. Where another jackherd skill calls one you left out, it reads `/devops (not installed)`, so the agent knows instead of calling a skill that isn't there. A name jackherd doesn't ship stops the sync before it writes anything; `orchestrate` and `start` cannot be left out.
12
+ - **The herd shows what a working lane does.** Under the line of a working lane, a dim `↳` line shows its agent's last action from the session log, a tool with its target or the start of its answer, and one more for each running subagent. The log is read again only when it grew, never per frame.
13
+ - **Jack starts a ready phase at once.** When a lane reports `done` or `idle` and its plan has a phase whose dependencies are ticked and that nobody works on, the watcher prints `<lane> ready: P9 (after P7): start it now`, so a pull request waiting for your approval no longer holds the next phase back, only the commit.
14
+ - **Word budgets follow the skill prefix.** With a `skillPrefix`, `jh check` measured every jackherd skill against the default 2000 words; it now measures each against its own budget.
15
+ - **Tests by name find a private module's test.** `_result.py` counts as tested by `test_result.py`.
16
+ - **A folded lane shows its checks.** Behind each pull request badge the folded lane line shows `✓ checks`, `checks running`, `✗ test +1` with the first red check, or `draft`, also while the lane rests, so a red check or a draft no longer hides until you open the lane. The open lane says `checks running` too, where it said `… checks`.
17
+ - **Pull requests stay drafts until their merge.** With `"merge": { "openAsDraft": true }` a lane opens every pull request with `gh pr create --draft`, so its pushes start no workflow that skips drafts. Jack runs `gh pr ready` when he reports the pull request to you to merge, or as the first step of his own merge, and waits for the runs that start then. Off by default.
18
+ - **The merge guard never merges a draft, and a required check needs a success.** A draft always asks. A check in `guard.requiredChecks` counts only with a run that succeeded: a skipped run, as a draft leaves it right after `gh pr ready`, no longer lets a merge pass. That holds for a self-merge too, which waited for no required check before; a `guard.requiredChecks` that does not read turns the self-merge answer off. Without required checks, at least one run must have succeeded: a pull request whose checks were all skipped asks.
19
+
5
20
  ## 1.14.0
6
21
 
7
22
  - **Any language, once its commands are set.** PHP and Node are recognised on their own, as before. For any other language, set its test command once, or let `/jh-start` in Claude Code propose the commands from your CI: it writes only the ones you approve. From then on `jh check` runs your tests before a commit. In a language jackherd does not know, a changed file without a test of its name gets a warning, not a block, since some languages keep tests in the source file.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p>
2
- <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.14.0/assets/branding/jackherd-logo-outline.svg" width="340">
2
+ <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.15.0/assets/branding/jackherd-logo-outline.svg" width="340">
3
3
  <br>
4
4
  </p>
5
5
 
6
- [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
6
+ [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
@@ -15,25 +15,27 @@ For developers who'd rather decide than supervise.
15
15
  curl -fsSL https://unpkg.com/jackherd/install.sh | sh
16
16
  ```
17
17
 
18
- Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
18
+ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.14.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
21
+ <img src="https://unpkg.com/jackherd@1.15.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
22
22
  <br>
23
- <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
23
+ <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
26
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
27
27
 
28
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
28
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.</p>
29
29
 
30
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
31
31
 
32
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
32
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
33
33
 
34
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
35
35
 
36
- <p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg"></p>
36
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
37
+
38
+ <p><img src="https://unpkg.com/jackherd@1.15.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg"></p>
37
39
 
38
40
  ## Quickstart
39
41
 
@@ -69,22 +71,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
69
71
  /jh-orchestrate docs/plans/board-<name>/board.md
70
72
  ```
71
73
 
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)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
74
+ By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg">
73
75
 
74
76
  ## Follow anything outside GitHub
75
77
 
76
78
  Whatever you check by hand, Jack can watch: tickets, error logs, status pages, deploys. One `/jh-follow`, one yes, and every change comes to you like this:
77
79
 
78
- <img src="https://unpkg.com/jackherd@1.14.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
80
+ <img src="https://unpkg.com/jackherd@1.15.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
79
81
 
80
- The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
82
+ The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.15.0/assets/icons/space.svg">
81
83
 
82
84
  ## In detail
83
85
 
84
86
  <details>
85
87
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
86
88
 
87
- <img src="https://unpkg.com/jackherd@1.14.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
89
+ <img src="https://unpkg.com/jackherd@1.15.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
88
90
 
89
91
  | On his own | After your approval | Never |
90
92
  | --- | --- | --- |
@@ -108,16 +110,18 @@ A project install adds two hooks to Claude Code's [permission rules](https://cod
108
110
 
109
111
  A merge passes without a question on green checks after self-merge, or under a standing approval: one plan's lanes until a time you pick, at most 24 hours; workflows, migrations, env files and the guards' own files still ask. A pull request whose files all match `merge.withoutChecks`, such as docs only, skips question and checks, unless a file lies on a path that always asks.
110
112
 
113
+ `"merge": { "openAsDraft": true }` has lanes open every pull request as a draft, so pushes while it is worked on start no workflow that skips drafts; Jack runs `gh pr ready` before the merge. The guard never merges a draft, and a check in `guard.requiredChecks` counts only with a run that succeeded, not with the skipped run a draft leaves behind; without that list, at least one run must have succeeded.
114
+
111
115
  The guards catch mistakes, not intent; for that, use branch protection. The hooks need Bun; a global install ships none.
112
116
 
113
117
  </details>
114
118
 
115
119
  <details>
116
- <summary><b>Plans, boards and the skills</b></summary>
120
+ <summary><b>Plans, boards and the skills, yours included</b></summary>
117
121
 
118
122
  A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
119
123
 
120
- <img src="https://unpkg.com/jackherd@1.14.0/docs/skills-dark.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
+ <img src="https://unpkg.com/jackherd@1.15.0/docs/skills-dark.svg" alt="Your own plan or an issue is adopted (Jack adds what is missing, you see the diff) and goes to /jh-orchestrate; /jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement, which your own skill can replace; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
121
125
 
122
126
  | Skill | Does |
123
127
  | --- | --- |
@@ -130,7 +134,10 @@ A **plan** is Markdown with phases, checkbox tasks, a model and a review gate pe
130
134
  | `/jh-follow` | follows something outside GitHub: a ticket, error logs, a status page |
131
135
  | `/jh-diagnose` | turns a bug into a failing command, fixes it with a regression test |
132
136
 
133
- 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.
137
+ - **Your own skills stay.** `jh init` keeps them and gives jackherd's skills a `jh-` prefix where a name is taken, as a global install always does; `jh sync` never writes over a file you changed, it leaves `<file>.kit-new` beside it. If a skill from `npx skills add` later takes the path of one of jackherd's, `jh sync` names it and the way out: `"skillPrefix": "jh-"`. Such skills only warn in `jh check`'s word budgets, never red.
138
+ - **Leave out what you don't use.** `"kit": { "exclude": ["devops", "grill"] }` in `.jackherd/config.json` keeps those skills and agents out on the next `jh sync`: a copy you never changed goes, one you changed stays, and the skills that call one show it as not installed. `/jh-orchestrate` and `/jh-start` always stay.
139
+ - **A lane builds with any skill.** The lane's rules hold over it: report each task with `jh task`, send questions as text, leave git to Jack.
140
+ - **A plan from another planner**, or one in an issue, runs once it meets the [plan contract](https://unpkg.com/jackherd/docs/plan-contract.md): Jack adds what is missing and shows you the diff before committing it.
134
141
 
135
142
  </details>
136
143
 
@@ -181,9 +188,10 @@ How the herd reads the board:
181
188
  - **Sources** shows, per source that is no ticket, its entries and how many are new, or when it last ran: amber with a new entry that is not in Your turn yet, red while it fails. Opened, its entries stand newest first by the time they show, and a click on an entry opens it; `↑ in your turn` marks one a row of Your turn asks about.
182
189
  - **Lanes** opens at the start. A lane that works or is blocked opens by itself only when it first appears; after that, opening and folding are yours. A lane symbol says the state (`●` working, `◆` blocked, `◉` the agent finished its answer, `○` idle, `(·) no agent` a worktree nobody works in); only blocked wears its word. A resting lane names what it waits on: `waits on you` with the open questions of its report, `waits on` a row of Your turn, `after` its board row's starts-after cell, or `idle · no reason`.
183
190
  - **Waiting** lists the lanes not started yet: no worktree yet, something left to do, and what each starts after; it starts folded. Finished lanes (cleaned up, or every phase they name ticked) are not shown.
184
- - **An open lane** shows, top down: its branch, the model and how long it idles; its link chips on one line, `+n` for the rest; its pull requests; the phases it works on, each with its bar and its first four open tasks, the running ones first, `▸ n more open · k done` to list them all; `▸ next` with the phases that wait, as ranges; `▸ done` with the finished ones. A lane shows only the phases its board rows name; a lane without a worktree reads its plan from the main checkout. A folded lane line names the phase moved last, `+n` more active ones and its count, and a reported task below it.
191
+ - **An open lane** shows, top down: its branch, the model and how long it idles; its link chips on one line, `+n` for the rest; its pull requests; the phases it works on, each with its bar and its first four open tasks, the running ones first, `▸ n more open · k done` to list them all; `▸ next` with the phases that wait, as ranges; `▸ done` with the finished ones. A lane shows only the phases its board rows name; a lane without a worktree reads its plan from the main checkout. A folded lane line names the phase moved last, `+n` more active ones and its count, and a reported task below it; behind each pull request badge it shows the checks, also while the lane rests: `✓ checks`, `checks running`, `✗ test +1` with the first red check, `draft` for a draft.
185
192
  - **A phase runs** when an agent reports working on it, one of its boxes was ticked in the last 20 minutes, or some of its tasks are ticked. A phase whose run was merged and that the lane's newest board row no longer names waits behind the others in `▸ next`, its open boxes left open on purpose. `n min ago` counts since the last tick or report; `· gate` means only the review gate is open, `docs open` only the documentation box.
186
193
  - **A running task** carries `●` when an agent reported it, from its task list or `jh task`; its text stands in teal and a light band runs over it (`plans.shimmer: false`, `NO_COLOR` and `--once` keep it still). `◌` in dim is only guessed from the words of the lane's log. `now: …` is a reported task no plan task names, `Subagent: …` a running subagent on no phase; a running review sits on the gate box that names it, in the phase it names or else the lane's current one. With several agents in a lane, each task carries its agent's name, dim, at the right edge.
194
+ - **What a working lane does right now:** under its lane line, open or folded, a dim `↳` line shows the last action from the agent's session log, a tool with its target (`↳ Read plans/layout.ts`) or the start of its answer, and each running subagent adds one, indented and led by its task (where `Subagent: …` says what it works on, `↳` says what it does); the lines go when the lane rests.
187
195
  - **Counts and colours:** the bar is the progress over the lane's plans; `n/m` counts checkboxes, `n/m ph` finished phases, green when all are ticked; `–` is a phase without checkboxes, `?` one not in the plan. `🥣` is the share of the context window used, on a folded lane only once it warns. `Sonnet ≠ Opus` in amber says a phase's subagent ran on another model than the plan's. `stale?` in Backlog marks a plan file unchanged on the base branch for 14 days.
188
196
  - **Chips:** `🔗` an Artifact, `⑂` a pull request or issue, `◈` Figma, `📡` a ticket with its status (`📡 n` in amber on a folded lane when tickets are new), `private` where the plan says so. After the lane: `↗` a pull request with its checks, `💬` new feedback, `✋` changes requested, `🌐` a preview. ctrl+click opens a chip's link.
189
197
  - **Mouse and keys:** `▸` `▾` marks a row that opens and folds, and only such a row has one; it turns teal under the mouse or the arrows. `❯` and a light background mark the row under the mouse or the arrows; the mark leaves with the mouse, while a row the arrows chose keeps it; the footer says what a click or a key does there. A click or enter on a task opens it and nothing else: what it asks, up to its first ` — `, one sentence per line behind a thin `│`, only whole sentences in at most five lines, code in teal and the origin dim at the end; `▸ n more lines` opens the rest. Buttons in square brackets do something, bold under the mouse. `s` opens every lane and plan as one HTML page.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26.18 37.09" width="24" height="34" fill="none" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(-0.822 -0.552) scale(1.019)" stroke-width="1.718"><path stroke="#6F818A" d="M10.35 5.40L10.44 2.12L13.56 2.12L13.65 5.40L15.50 6.17L17.88 3.91L20.09 6.12L17.83 8.50L18.60 10.35L21.88 10.44L21.88 13.56L18.60 13.65L17.83 15.50L20.09 17.88L17.88 20.09L15.50 17.83L13.65 18.60L13.56 21.88L10.44 21.88L10.35 18.60L8.50 17.83L6.12 20.09L3.91 17.88L6.17 15.50L5.40 13.65L2.12 13.56L2.12 10.44L5.40 10.35L6.17 8.50L3.91 6.12L6.12 3.91L8.50 6.17Z"/><circle stroke="#04A5AD" cx="12" cy="12" r="3.3"/></g></svg>
package/dist/config.d.ts CHANGED
@@ -21,6 +21,10 @@ declare const isTerminalAdapter: (value: string) => value is TerminalAdapter;
21
21
  declare const isModelRole: (value: string) => value is ModelRole;
22
22
  /** Lower-case letters and digits ending in a dash, so a prefixed name stays one plain path segment. */
23
23
  declare const SKILL_PREFIX: RegExp;
24
+ /** What a global install, and a project that already has skills of kit names, puts before every kit name. */
25
+ declare const DEFAULT_SKILL_PREFIX = "jh-";
26
+ /** The skills every lane and the coordinator run on; `kit.exclude` cannot take them away. */
27
+ declare const KIT_CORE: readonly string[];
24
28
  /**
25
29
  * The project's own commands. Each is optional: detection fills what the repository's stack decides, and a command
26
30
  * nobody set stays out instead of a guessed one (a kit text then says where to set it).
@@ -101,6 +105,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
101
105
  install: z.ZodOptional<z.ZodString>;
102
106
  dev: z.ZodOptional<z.ZodString>;
103
107
  }, z.core.$strict>>;
108
+ kit: z.ZodPrefault<z.ZodObject<{
109
+ exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
110
+ }, z.core.$strict>>;
104
111
  lanes: z.ZodPrefault<z.ZodObject<{
105
112
  copy: z.ZodDefault<z.ZodArray<z.ZodString>>;
106
113
  lockFile: z.ZodOptional<z.ZodString>;
@@ -288,6 +295,7 @@ declare const jackherdConfigSchema: z.ZodObject<{
288
295
  }>>>;
289
296
  merge: z.ZodPrefault<z.ZodObject<{
290
297
  withoutChecks: z.ZodDefault<z.ZodArray<z.ZodString>>;
298
+ openAsDraft: z.ZodDefault<z.ZodBoolean>;
291
299
  }, z.core.$strict>>;
292
300
  hints: z.ZodPrefault<z.ZodObject<{
293
301
  askUserQuestion: z.ZodDefault<z.ZodBoolean>;
@@ -333,4 +341,4 @@ declare const parseConfigFile: (raw: unknown, label: string) => JackherdConfig;
333
341
  /** One `watch.sources` entry outside the config, such as a draft to try; invalid throws with `label` and the issues. */
334
342
  declare const parseSourceDraft: (raw: unknown, label: string) => z.infer<typeof sourceSchema>;
335
343
 
336
- export { COMMAND_KEYS, type CommandKey, DEFAULT_CONTEXT_WINDOW, DEFAULT_MODELS, type JackherdConfig, MODEL_ROLE, type ModelRole, SKILL_PREFIX, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parseSourceDraft, renderConfigJsonSchema };
344
+ export { COMMAND_KEYS, type CommandKey, DEFAULT_CONTEXT_WINDOW, DEFAULT_MODELS, DEFAULT_SKILL_PREFIX, type JackherdConfig, KIT_CORE, MODEL_ROLE, type ModelRole, SKILL_PREFIX, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parseSourceDraft, renderConfigJsonSchema };
package/dist/config.js CHANGED
@@ -105,7 +105,11 @@ var checkConfigSchema = z.strictObject({
105
105
 
106
106
  // src/claude/paths.ts
107
107
  var CLAUDE_DIRECTORY = ".claude";
108
+ var SKILLS_DIRECTORY = "skills";
109
+ var AGENTS_DIRECTORY = "agents";
108
110
  var WORKTREES_DIRECTORY = "worktrees";
111
+ var SKILL_PATH = new RegExp(`^${SKILLS_DIRECTORY}/([^/]+)/(.+)$`);
112
+ var AGENT_PATH = new RegExp(`^${AGENTS_DIRECTORY}/([^/]+)\\.md$`);
109
113
  var claudePath = (...segments) => [CLAUDE_DIRECTORY, ...segments].join("/");
110
114
 
111
115
  // src/watch/source-fields.ts
@@ -275,10 +279,16 @@ var DEFAULT_MODELS = {
275
279
  var isTerminalAdapter = (value) => Object.values(TERMINAL_ADAPTER).includes(value);
276
280
  var isModelRole = (value) => Object.values(MODEL_ROLE).includes(value);
277
281
  var SKILL_PREFIX = /^[a-z0-9]+-$/;
282
+ var DEFAULT_SKILL_PREFIX = "jh-";
283
+ var KIT_CORE = ["orchestrate", "start"];
284
+ var KIT_ENTRY_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
278
285
  var FULL_MODEL_ID = /^claude-[a-z0-9][a-z0-9.-]*$/;
279
286
  var language = z2.enum(Object.values(PLAN_LANGUAGE));
280
287
  var modelId = z2.string().regex(FULL_MODEL_ID, "Use a full model ID such as claude-opus-5-5, not an alias like opus or sonnet");
281
288
  var command2 = z2.string().min(1);
289
+ var excludedKitEntry = z2.string().regex(KIT_ENTRY_NAME, "a kit skill or agent name as the kit names it, e.g. devops").refine((name) => !KIT_CORE.includes(name), {
290
+ error: (issue) => `${issue.input} is the kit's core and cannot be excluded`
291
+ });
282
292
  var tokenCount = z2.number().int().positive();
283
293
  var commandsSchema = z2.strictObject({
284
294
  test: command2.optional(),
@@ -467,7 +477,13 @@ var mergeSchema = z2.strictObject({
467
477
  * of them, `/orchestrate` merges at once and the merge guard does not ask, for a merge bound to the head commit with
468
478
  * `--match-head-commit`. Empty means every merge waits for checks.
469
479
  */
470
- withoutChecks: z2.array(checkFreePattern).default([])
480
+ withoutChecks: z2.array(checkFreePattern).default([]),
481
+ /**
482
+ * Whether a lane opens every pull request as a draft (`gh pr create --draft`), so pushes while it is worked on start
483
+ * no workflow that skips drafts. `/orchestrate` runs `gh pr ready` before the merge and waits for the checks that
484
+ * start then; the merge guard never lets a draft through. Off opens pull requests ready, as before.
485
+ */
486
+ openAsDraft: z2.boolean().default(false)
471
487
  });
472
488
  var guardFields = z2.strictObject({
473
489
  /**
@@ -561,6 +577,11 @@ var jackherdConfigSchema = z2.strictObject({
561
577
  */
562
578
  skillPrefix: z2.union([z2.literal(""), z2.string().regex(SKILL_PREFIX, "lower-case letters and digits ending in a dash, e.g. kit-")]).default(""),
563
579
  commands: commandsSchema.prefault({}),
580
+ /**
581
+ * Kit skills and agents `jh sync` leaves out, by the kit's name without `skillPrefix`; a name takes its skill and
582
+ * its agent alike. An unchanged copy already installed is removed, a changed one stays untracked.
583
+ */
584
+ kit: z2.strictObject({ exclude: z2.array(excludedKitEntry).default([]) }).prefault({}),
564
585
  /** How `/orchestrate` sets up a lane's worktree. */
565
586
  lanes: z2.strictObject({
566
587
  /** Untracked files copied into every new worktree; empty means the files `.worktreeinclude` lists. */
@@ -636,6 +657,8 @@ export {
636
657
  COMMAND_KEYS,
637
658
  DEFAULT_CONTEXT_WINDOW,
638
659
  DEFAULT_MODELS,
660
+ DEFAULT_SKILL_PREFIX,
661
+ KIT_CORE,
639
662
  MODEL_ROLE,
640
663
  SKILL_PREFIX,
641
664
  TERMINAL_ADAPTER,
@@ -382,6 +382,12 @@ type OpenLaneRow = {
382
382
  };
383
383
  /** Sprint tables name a phase `P4`; the sidebar title prints it the same way. */
384
384
  declare const PHASE_ID_PREFIX = "P";
385
+ /** The phase a table's first cell names (`1 — Title`, `P1`, `10c`), lower-cased; null when it names none. */
386
+ declare const phaseIdOf: (cell: string) => string | null;
387
+ /** What a report says when the plan has no dependency table. */
388
+ declare const NO_DEPENDENCY_TABLE = "no dependency table: phases run one after another";
389
+ /** `1 phase`, `3 phases`. */
390
+ declare const phaseCount: (count: number) => string;
385
391
  /** A phase as plans and reports name it: `P4`. */
386
392
  declare const phaseLabel: (id: string) => string;
387
393
  /** Phases as plans and reports name them, joined by `separator`: `P3 P4`, `P19 ∥ P20`. */
@@ -418,6 +424,23 @@ declare const columnModels: (column: string) => ColumnModel[];
418
424
  */
419
425
  declare const differsFromColumn: (column: string, modelId: string) => boolean;
420
426
 
427
+ /**
428
+ * A row of the model table: the phase it names, the models its model cell names (none for a dash), the model cell as
429
+ * written, and the row's 1-based line.
430
+ */
431
+ type ModelRow = {
432
+ id: string;
433
+ models: ColumnModel[];
434
+ cell: string;
435
+ line: number;
436
+ };
437
+ /**
438
+ * The rows of the plan's model table (`### Model-Empfehlungen pro Phase`): the phase from the first cell (`1 — Title`,
439
+ * `P1`, `10c`), the models from the second, read like a board's model column. A row whose first cell names no phase is
440
+ * skipped; null when the plan has no such section or the section holds no table.
441
+ */
442
+ declare const parseModelTable: (markdown: string, language: PlanLanguage) => ModelRow[] | null;
443
+
421
444
  /** Per heading that carries checkboxes or table status cells: how many are done and open. */
422
445
  type PhaseTally = {
423
446
  title: string;
@@ -591,4 +614,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
591
614
  */
592
615
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
593
616
 
594
- export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANE_PROMPT_OPENING, LANGUAGE_NAME, type LaneProgress, type LongPhaseTitle, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, NEXT_VOCABULARY, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
617
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANE_PROMPT_OPENING, LANGUAGE_NAME, type LaneProgress, type LongPhaseTitle, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -69,9 +69,10 @@ var headerField = (markdown, name) => {
69
69
  };
70
70
  var firstHeading = (markdown, fallback) => terminalSafe(markdown.match(/^#\s+(.+)$/m)?.[1] ?? fallback).trim();
71
71
  var HEADING = /^(#{2,4})\s+(.*?)\s*$/;
72
+ var subsectionStart = (markdown, headingText) => markdown.split("\n").findIndex((line) => HEADING.exec(line)?.[2].startsWith(headingText));
72
73
  var subsectionLines = (markdown, headingText) => {
73
74
  const lines = markdown.split("\n");
74
- const start = lines.findIndex((line) => HEADING.exec(line)?.[2].startsWith(headingText));
75
+ const start = subsectionStart(markdown, headingText);
75
76
  if (start === -1) return [];
76
77
  const level = HEADING.exec(lines[start])?.[1].length ?? 2;
77
78
  const rest = lines.slice(start + 1);
@@ -102,6 +103,10 @@ var LANE_NAME = /`([^`]+)`/;
102
103
  var CONTINUATION = "\u21B3";
103
104
  var PLAN_LINK = /\[([^\]]+)\]\(([^)]+)\)/;
104
105
  var PHASE_ID_PREFIX = "P";
106
+ var LEADING_PHASE_ID = new RegExp(`^\\s*${PHASE_ID_PREFIX}?(\\d+[a-z]?)\\b`, "i");
107
+ var phaseIdOf = (cell) => LEADING_PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
108
+ var NO_DEPENDENCY_TABLE = "no dependency table: phases run one after another";
109
+ var phaseCount = (count) => `${count} ${count === 1 ? "phase" : "phases"}`;
105
110
  var phaseLabel = (id) => `${PHASE_ID_PREFIX}${id}`;
106
111
  var MAX_RANGE = 50;
107
112
  var spannedPhaseIds = (first, last) => first > last || last - first >= MAX_RANGE ? [] : Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
@@ -467,7 +472,6 @@ var shortTitle = (progress) => {
467
472
 
468
473
  // src/plan-format/dependencies.ts
469
474
  var ID = "(\\d+[a-z]?)";
470
- var PHASE_ID = new RegExp(`^\\s*${PHASE_ID_PREFIX}?${ID}\\b`, "i");
471
475
  var PHASE_RANGE = new RegExp(`^${PHASE_ID_PREFIX}?(\\d+)\\s*[\u2013-]\\s*${PHASE_ID_PREFIX}?(\\d+)$`, "i");
472
476
  var BACKTICKED = /`([^`]+)`/g;
473
477
  var NO_VALUE = /^[—–-]?$/;
@@ -476,7 +480,6 @@ var GLOB_CHARACTER = /[*?{[]/;
476
480
  var VERSION = /^v?\d+(?:[.\-+][\w.-]*)?$/i;
477
481
  var PACKAGE_SCOPE = /^@[^/]+\//;
478
482
  var RELEASE_FILE = /(?:^|\/)(?:CHANGELOG\.md|package\.json)$/;
479
- var phaseIdOf = (cell) => PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
480
483
  var splitOutsideParentheses = (cell, separator) => {
481
484
  const entries = [];
482
485
  let depth = 0;
@@ -749,11 +752,24 @@ var differsFromColumn = (column, modelId) => {
749
752
  return models.length > 0 && !models.some((model) => fits(tokens, model));
750
753
  };
751
754
 
755
+ // src/plan-format/model-table.ts
756
+ var parseModelTable = (markdown, language) => {
757
+ const heading = PLAN_VOCABULARY[language].sections.modelRecommendations;
758
+ const rows = firstTableRows(subsectionLines(markdown, heading));
759
+ if (rows.length === 0) return null;
760
+ const firstBodyLine = subsectionStart(markdown, heading) + 2;
761
+ return rows.flatMap(({ index, cells }) => {
762
+ const id = phaseIdOf(cells[0] ?? "");
763
+ const cell = cells[1] ?? "";
764
+ return id === null ? [] : [{ id, models: columnModels(cell), cell, line: firstBodyLine + index }];
765
+ });
766
+ };
767
+
752
768
  // src/plan-format/phase-tally.ts
753
769
  var DONE_MARK = /^\s*[-*]\s\[x\]|\|\s*✅\s*\|/gim;
754
770
  var OPEN_MARK = /^\s*[-*]\s\[ \]|\|\s*⬜\s*\|/gm;
755
771
  var TRAILING_SEPARATOR = /[:—–-]\s*$/;
756
- var PHASE_ID2 = /^(\d+[a-z]?)/;
772
+ var PHASE_ID = /^(\d+[a-z]?)/;
757
773
  var modelFor = (markdown, phaseWord, phaseTitle) => {
758
774
  const prefix = new RegExp(`^${escapeRegExp(phaseWord)}\\s*`, "i");
759
775
  const number = /^(\d+)/.exec(phaseTitle.replace(prefix, ""))?.[1];
@@ -765,7 +781,7 @@ var sectionOf = (section, heading, markdown, phaseWord) => {
765
781
  const match = heading.exec(section);
766
782
  if (!match) return null;
767
783
  const title = match[1].replace(TRAILING_SEPARATOR, "").trim();
768
- const phaseId = PHASE_ID2.exec(title)?.[1]?.toLowerCase() ?? "";
784
+ const phaseId = PHASE_ID.exec(title)?.[1]?.toLowerCase() ?? "";
769
785
  const targets = section.split("\n").flatMap((line) => deferralTarget(line, phaseId) ?? []);
770
786
  const done = (section.match(DONE_MARK) ?? []).length;
771
787
  const open = (section.match(OPEN_MARK) ?? []).length - targets.length;
@@ -911,6 +927,7 @@ export {
911
927
  LANE_PROMPT_OPENING,
912
928
  LANGUAGE_NAME,
913
929
  NEXT_VOCABULARY,
930
+ NO_DEPENDENCY_TABLE,
914
931
  PHASE_CELL_LIMIT,
915
932
  PHASE_ID_PREFIX,
916
933
  PHASE_TITLE_LIMIT,
@@ -957,11 +974,14 @@ export {
957
974
  parallelGroups,
958
975
  parallelGroupsOfPlan,
959
976
  parseDependencyTable,
977
+ parseModelTable,
960
978
  parsePlanPhases,
961
979
  parseSprintLanes,
980
+ phaseCount,
962
981
  phaseHeading,
963
982
  phaseHeadingPattern,
964
983
  phaseHeadings,
984
+ phaseIdOf,
965
985
  phaseIdsOfCell,
966
986
  phaseIdsOfPlan,
967
987
  phaseLabel,
@@ -0,0 +1,84 @@
1
+ # The plan contract
2
+
3
+ jackherd reads a plan from its Markdown. This is what a plan has to hold for the readers to find it, and how to check that with one command.
4
+
5
+ ## The four parts
6
+
7
+ | Part | Form | Required | Read by |
8
+ | ---- | ---- | -------- | ------- |
9
+ | Phases | a heading per phase: `### Phase 1: Title` (also `## 1 — Title` or `## P1 — Title`) | yes | herd, watcher, `jh check`, lane start |
10
+ | Tasks | at least one checkbox (`- [ ]`, `- [x]`) or a ✅/⬜ table row in every phase; a phase that points at a section (``See `## Documentation` ``) counts that section's checkboxes | yes | herd (progress), watcher, `jh check` |
11
+ | Model | a table under `### Model recommendations per phase`: the phase in the first cell (`1 — Title`, `P1`, `10c`), the model in the second (`claude-opus-5-5`, `Opus 5.5`); a dash marks a phase that needs no model | yes | lane start (the model a lane runs on), `/implement` |
12
+ | Dependencies | a table under `### Dependencies and parallelization` with the columns `Depends on`, `Writes`, `Serial marker` and one row per phase | from two phases on, optional | `jh check --parallel`, `/implement` |
13
+
14
+ Details that decide a verdict:
15
+
16
+ - A sub-phase (`3b`) is covered by the row of its base phase (`3`) in the model and the dependency table, or by a row of its own.
17
+ - A model cell that names no model and is no dash is a gap: the lane would start on a guess.
18
+ - With fewer than two phases the dependency part is reported `present` ("not needed"), never missing.
19
+ - With two or more phases and no dependency table the plan is still valid: the phases run one after another. A table that is there but lacks a row for a phase is a gap.
20
+ - The headings come from the plan language in `.jackherd/config.json` (`planLanguage`, `de` or `en`); the English ones are shown here.
21
+
22
+ ## Minimal valid plan
23
+
24
+ ```markdown
25
+ # Search filter
26
+
27
+ **Date:** YYYY-MM-DD
28
+ **Status:** Draft
29
+
30
+ ## Implementation
31
+
32
+ ### Model recommendations per phase
33
+
34
+ | Phase | Model | Rationale |
35
+ | ----- | ----- | --------- |
36
+ | 1 — Query parser | claude-sonnet-5-5 | pure functions, cheap to review |
37
+ | 2 — Filter bar | claude-opus-5-5 | touches the shared state |
38
+
39
+ ### Dependencies and parallelization
40
+
41
+ | Phase | Depends on | Writes | Serial marker |
42
+ | ----- | ---------- | ------ | ------------- |
43
+ | 1 | — | `src/query/` | — |
44
+ | 2 | 1 | `src/ui/filter-bar/` | — |
45
+
46
+ ### Phase 1: Query parser
47
+
48
+ - [ ] **Parse filters**: `parseQuery(text)` returns the filter list
49
+ - [ ] **Test it**: every operator, one malformed input
50
+
51
+ ### Phase 2: Filter bar
52
+
53
+ - [ ] **Render the bar**: one chip per filter
54
+ ```
55
+
56
+ ## Checking a plan
57
+
58
+ ```
59
+ jh plan check <plan.md>
60
+ ```
61
+
62
+ Prints one line per part, `file:line` where the part is incomplete. For the plan above:
63
+
64
+ ```
65
+ phases: present plan.md:22 2 phases
66
+ tasks: present plan.md:22 every phase has tasks
67
+ model: present plan.md:12 every phase has a model
68
+ dependencies: present every phase has a row
69
+ ```
70
+
71
+ If the model table lacked the row for phase 2, and the dependency table were left out, the lines would read:
72
+
73
+ ```
74
+ phases: present plan.md:22 2 phases
75
+ tasks: present plan.md:22 every phase has tasks
76
+ model: malformed plan.md:22 no model for P2
77
+ dependencies: missing no dependency table: phases run one after another
78
+ ```
79
+
80
+ | Exit | Meaning |
81
+ | ---- | ------- |
82
+ | 0 | the contract is met (a missing dependency table is a note, not a gap) |
83
+ | 1 | at least one part is missing or malformed |
84
+ | 2 | wrong command line: no subcommand, unknown subcommand, no file, unreadable file, unreadable config |
@@ -1,5 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="960" height="530" viewBox="0 0 960 530" role="img" aria-labelledby="t d"><title id="t">The skills and what they hand on</title><desc id="d">/jh-grill hands decision rows to /jh-plan. /jh-plan feeds /jh-orchestrate with phases, Writes sets and a model per phase, or a single phase to /jh-implement; under /jh-orchestrate each lane runs /jh-implement. /jh-implement ends in /jh-pre-commit, which runs jh check, /jh-review, the jh-spec-review agent against the plan, and the reviewers routed by path: /jh-uncle-bob, /jh-security-audit, /jh-devops. /jh-diagnose hands a red command to /jh-implement.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}</style></defs>
2
- <rect x="1" y="1" width="958" height="528" rx="18" fill="#151D22" stroke="#34434B" stroke-width="1.5"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="600" viewBox="0 0 960 600" role="img" aria-labelledby="t d"><title id="t">The skills and what they hand on</title><desc id="d">A plan of your own or an issue is adopted: Jack adds what is missing, shows you the diff and hands it to /jh-orchestrate. /jh-grill hands decision rows to /jh-plan. /jh-plan feeds /jh-orchestrate with phases, Writes sets and a model per phase, or a single phase to /jh-implement, which your own skill can replace; under /jh-orchestrate each lane runs /jh-implement or your own skill. /jh-implement ends in /jh-pre-commit, which runs jh check, /jh-review, the jh-spec-review agent against the plan, and the reviewers routed by path: /jh-uncle-bob, /jh-security-audit, /jh-devops. /jh-diagnose hands a red command to /jh-implement.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}</style></defs>
2
+ <rect x="1" y="1" width="958" height="598" rx="18" fill="#151D22" stroke="#34434B" stroke-width="1.5"/>
3
+ <g transform="translate(0 70)">
3
4
  <path d="M120 103.5 125.3 103.4 130.4 103.3 135.4 103.3 140.3 103.2 145.1 103.1 149.8 103 154.5 103 159 102.9 163.5 102.8 168 102.7 172.5 102.6 176.9 102.6 181.3 102.5 185.8 102.4 190.2 102.3 194.7 102.2 199.2 102.2 203.9 102.1 208.5 102 213.3 101.9 218.2 101.8 223.1 101.8 228.2 101.7 233.5 101.6 238.8 101.5 244.4 101.5 250.1 101.4 256 101.3 A1.3 1.3 0 0 1 256 98.7 L256 98.7 250.1 98.6 244.4 98.5 238.8 98.5 233.5 98.4 228.2 98.3 223.1 98.2 218.2 98.2 213.3 98.1 208.5 98 203.9 97.9 199.2 97.8 194.7 97.8 190.2 97.7 185.8 97.6 181.3 97.5 176.9 97.4 172.5 97.4 168 97.3 163.5 97.2 159 97.1 154.5 97 149.8 97 145.1 96.9 140.3 96.8 135.4 96.7 130.4 96.7 125.3 96.6 120 96.5 A3.5 3.5 0 0 1 120 103.5Z" fill="#21BEC5"/>
4
5
  <path d="M249.2 106L257.2 100L249.2 94" fill="none" stroke="#21BEC5" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
5
6
  <path d="M320 103.5 325.3 103.4 330.6 103.3 335.8 103.3 340.9 103.2 346 103.1 351 103 356 103 361 102.9 366 102.8 370.9 102.7 375.9 102.6 380.8 102.6 385.8 102.5 390.8 102.4 395.7 102.3 400.8 102.2 405.8 102.2 410.9 102.1 416.1 102 421.3 101.9 426.6 101.8 431.9 101.8 437.4 101.7 442.9 101.6 448.5 101.5 454.2 101.5 460.1 101.4 466 101.3 A1.3 1.3 0 0 1 466 98.7 L466 98.7 460.1 98.6 454.2 98.5 448.5 98.5 442.9 98.4 437.4 98.3 431.9 98.2 426.6 98.2 421.3 98.1 416.1 98 410.9 97.9 405.8 97.8 400.8 97.8 395.7 97.7 390.8 97.6 385.8 97.5 380.8 97.4 375.9 97.4 370.9 97.3 366 97.2 361 97.1 356 97 351 97 346 96.9 340.9 96.8 335.8 96.7 330.6 96.7 325.3 96.6 320 96.5 A3.5 3.5 0 0 1 320 103.5Z" fill="#21BEC5"/>
@@ -44,6 +45,7 @@
44
45
  <text x="532" y="114" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="start">lanes in herdr</text>
45
46
  <text x="500" y="340" class="m" font-size="14" fill="#E6EDF3" font-weight="700" text-anchor="middle">/jh-implement</text>
46
47
  <text x="500" y="358" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">builds, ticks</text>
48
+ <text x="500" y="382" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle" font-style="italic">or your own skill</text>
47
49
  <text x="700" y="340" class="m" font-size="14" fill="#E6EDF3" font-weight="700" text-anchor="middle">/jh-pre-commit</text>
48
50
  <text x="700" y="358" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">jh check</text>
49
51
  <text x="880" y="200" class="m" font-size="14" fill="#E6EDF3" font-weight="700" text-anchor="middle">/jh-review</text>
@@ -62,4 +64,15 @@
62
64
  <text x="788" y="392" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end" font-style="italic">routed</text>
63
65
  <text x="788" y="408" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end" font-style="italic">by path</text>
64
66
  <text x="420" y="420" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="start" font-style="italic">red command</text>
67
+ </g>
68
+ <text x="290" y="48" class="s" font-size="13" fill="#E6EDF3" font-weight="600" text-anchor="middle">your own plan</text>
69
+ <text x="290" y="66" class="s" font-size="13" fill="#E6EDF3" font-weight="600" text-anchor="middle">or an issue</text>
70
+ <path d="M350 52C400 52 440 52 466 52" fill="none" stroke="#7E8A90" stroke-width="2.6" stroke-linecap="round" stroke-dasharray="0.1 7"/>
71
+ <path d="M459.2 58L467.2 52L459.2 46" fill="none" stroke="#7E8A90" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
72
+ <path d="M496.5 82 496.6 84.9 496.7 87.7 496.7 90.4 496.8 93 496.9 95.4 497 97.8 497.1 100 497.1 102.2 497.2 104.2 497.3 106.2 497.4 108.1 497.4 110 497.5 111.8 497.6 113.5 497.7 115.2 497.8 116.8 497.8 118.5 497.9 120.1 498 121.6 498.1 123.2 498.1 124.8 498.2 126.3 498.3 127.9 498.4 129.4 498.5 131 498.5 132.7 498.6 134.3 498.7 136 A1.3 1.3 0 0 1 501.3 136 L501.3 136 501.4 134.3 501.5 132.7 501.5 131 501.6 129.4 501.7 127.9 501.8 126.3 501.9 124.8 501.9 123.2 502 121.6 502.1 120.1 502.2 118.5 502.2 116.8 502.3 115.2 502.4 113.5 502.5 111.8 502.6 110 502.6 108.1 502.7 106.2 502.8 104.2 502.9 102.2 502.9 100 503 97.8 503.1 95.4 503.2 93 503.3 90.4 503.3 87.7 503.4 84.9 503.5 82 A3.5 3.5 0 0 1 496.5 82Z" fill="#21BEC5"/>
73
+ <path d="M494 129.2L500 137.2L506 129.2" fill="none" stroke="#21BEC5" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
74
+ <ellipse cx="500" cy="82.0" rx="28.0" ry="3.5" fill="#BCC5CA" opacity="0.12"/>
75
+ <rect x="476" y="28" width="48" height="48" rx="9.6" fill="#212E34" stroke="#5B6B73" stroke-width="1.9"/><circle cx="500" cy="52" r="9.1" fill="#21BEC5"/>
76
+ <text x="532" y="48" class="m" font-size="14" fill="#E6EDF3" font-weight="700" text-anchor="start">adopt</text>
77
+ <text x="532" y="66" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="start">adds what is missing, shows you the diff</text>
65
78
  </svg>
package/docs/skills.svg CHANGED
@@ -1,5 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="960" height="530" viewBox="0 0 960 530" role="img" aria-labelledby="t d"><title id="t">The skills and what they hand on</title><desc id="d">/jh-grill hands decision rows to /jh-plan. /jh-plan feeds /jh-orchestrate with phases, Writes sets and a model per phase, or a single phase to /jh-implement; under /jh-orchestrate each lane runs /jh-implement. /jh-implement ends in /jh-pre-commit, which runs jh check, /jh-review, the jh-spec-review agent against the plan, and the reviewers routed by path: /jh-uncle-bob, /jh-security-audit, /jh-devops. /jh-diagnose hands a red command to /jh-implement.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}</style></defs>
2
- <rect x="1" y="1" width="958" height="528" rx="18" fill="#FAF9F8" stroke="#C4CBCE" stroke-width="1.5"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="600" viewBox="0 0 960 600" role="img" aria-labelledby="t d"><title id="t">The skills and what they hand on</title><desc id="d">A plan of your own or an issue is adopted: Jack adds what is missing, shows you the diff and hands it to /jh-orchestrate. /jh-grill hands decision rows to /jh-plan. /jh-plan feeds /jh-orchestrate with phases, Writes sets and a model per phase, or a single phase to /jh-implement, which your own skill can replace; under /jh-orchestrate each lane runs /jh-implement or your own skill. /jh-implement ends in /jh-pre-commit, which runs jh check, /jh-review, the jh-spec-review agent against the plan, and the reviewers routed by path: /jh-uncle-bob, /jh-security-audit, /jh-devops. /jh-diagnose hands a red command to /jh-implement.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}</style></defs>
2
+ <rect x="1" y="1" width="958" height="598" rx="18" fill="#FAF9F8" stroke="#C4CBCE" stroke-width="1.5"/>
3
+ <g transform="translate(0 70)">
3
4
  <path d="M120 103.5 125.3 103.4 130.4 103.3 135.4 103.3 140.3 103.2 145.1 103.1 149.8 103 154.5 103 159 102.9 163.5 102.8 168 102.7 172.5 102.6 176.9 102.6 181.3 102.5 185.8 102.4 190.2 102.3 194.7 102.2 199.2 102.2 203.9 102.1 208.5 102 213.3 101.9 218.2 101.8 223.1 101.8 228.2 101.7 233.5 101.6 238.8 101.5 244.4 101.5 250.1 101.4 256 101.3 A1.3 1.3 0 0 1 256 98.7 L256 98.7 250.1 98.6 244.4 98.5 238.8 98.5 233.5 98.4 228.2 98.3 223.1 98.2 218.2 98.2 213.3 98.1 208.5 98 203.9 97.9 199.2 97.8 194.7 97.8 190.2 97.7 185.8 97.6 181.3 97.5 176.9 97.4 172.5 97.4 168 97.3 163.5 97.2 159 97.1 154.5 97 149.8 97 145.1 96.9 140.3 96.8 135.4 96.7 130.4 96.7 125.3 96.6 120 96.5 A3.5 3.5 0 0 1 120 103.5Z" fill="#03B5BD"/>
4
5
  <path d="M249.2 106L257.2 100L249.2 94" fill="none" stroke="#03B5BD" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
5
6
  <path d="M320 103.5 325.3 103.4 330.6 103.3 335.8 103.3 340.9 103.2 346 103.1 351 103 356 103 361 102.9 366 102.8 370.9 102.7 375.9 102.6 380.8 102.6 385.8 102.5 390.8 102.4 395.7 102.3 400.8 102.2 405.8 102.2 410.9 102.1 416.1 102 421.3 101.9 426.6 101.8 431.9 101.8 437.4 101.7 442.9 101.6 448.5 101.5 454.2 101.5 460.1 101.4 466 101.3 A1.3 1.3 0 0 1 466 98.7 L466 98.7 460.1 98.6 454.2 98.5 448.5 98.5 442.9 98.4 437.4 98.3 431.9 98.2 426.6 98.2 421.3 98.1 416.1 98 410.9 97.9 405.8 97.8 400.8 97.8 395.7 97.7 390.8 97.6 385.8 97.5 380.8 97.4 375.9 97.4 370.9 97.3 366 97.2 361 97.1 356 97 351 97 346 96.9 340.9 96.8 335.8 96.7 330.6 96.7 325.3 96.6 320 96.5 A3.5 3.5 0 0 1 320 103.5Z" fill="#03B5BD"/>
@@ -44,6 +45,7 @@
44
45
  <text x="532" y="114" class="s" font-size="12.5" fill="#4A4E51" text-anchor="start">lanes in herdr</text>
45
46
  <text x="500" y="340" class="m" font-size="14" fill="#212E34" font-weight="700" text-anchor="middle">/jh-implement</text>
46
47
  <text x="500" y="358" class="s" font-size="12.5" fill="#4A4E51" text-anchor="middle">builds, ticks</text>
48
+ <text x="500" y="382" class="s" font-size="12.5" fill="#4A4E51" text-anchor="middle" font-style="italic">or your own skill</text>
47
49
  <text x="700" y="340" class="m" font-size="14" fill="#212E34" font-weight="700" text-anchor="middle">/jh-pre-commit</text>
48
50
  <text x="700" y="358" class="s" font-size="12.5" fill="#4A4E51" text-anchor="middle">jh check</text>
49
51
  <text x="880" y="200" class="m" font-size="14" fill="#212E34" font-weight="700" text-anchor="middle">/jh-review</text>
@@ -62,4 +64,15 @@
62
64
  <text x="788" y="392" class="s" font-size="12.5" fill="#4A4E51" text-anchor="end" font-style="italic">routed</text>
63
65
  <text x="788" y="408" class="s" font-size="12.5" fill="#4A4E51" text-anchor="end" font-style="italic">by path</text>
64
66
  <text x="420" y="420" class="s" font-size="12.5" fill="#4A4E51" text-anchor="start" font-style="italic">red command</text>
67
+ </g>
68
+ <text x="290" y="48" class="s" font-size="13" fill="#212E34" font-weight="600" text-anchor="middle">your own plan</text>
69
+ <text x="290" y="66" class="s" font-size="13" fill="#212E34" font-weight="600" text-anchor="middle">or an issue</text>
70
+ <path d="M350 52C400 52 440 52 466 52" fill="none" stroke="#7E8487" stroke-width="2.6" stroke-linecap="round" stroke-dasharray="0.1 7"/>
71
+ <path d="M459.2 58L467.2 52L459.2 46" fill="none" stroke="#7E8487" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
72
+ <path d="M496.5 82 496.6 84.9 496.7 87.7 496.7 90.4 496.8 93 496.9 95.4 497 97.8 497.1 100 497.1 102.2 497.2 104.2 497.3 106.2 497.4 108.1 497.4 110 497.5 111.8 497.6 113.5 497.7 115.2 497.8 116.8 497.8 118.5 497.9 120.1 498 121.6 498.1 123.2 498.1 124.8 498.2 126.3 498.3 127.9 498.4 129.4 498.5 131 498.5 132.7 498.6 134.3 498.7 136 A1.3 1.3 0 0 1 501.3 136 L501.3 136 501.4 134.3 501.5 132.7 501.5 131 501.6 129.4 501.7 127.9 501.8 126.3 501.9 124.8 501.9 123.2 502 121.6 502.1 120.1 502.2 118.5 502.2 116.8 502.3 115.2 502.4 113.5 502.5 111.8 502.6 110 502.6 108.1 502.7 106.2 502.8 104.2 502.9 102.2 502.9 100 503 97.8 503.1 95.4 503.2 93 503.3 90.4 503.3 87.7 503.4 84.9 503.5 82 A3.5 3.5 0 0 1 496.5 82Z" fill="#03B5BD"/>
73
+ <path d="M494 129.2L500 137.2L506 129.2" fill="none" stroke="#03B5BD" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
74
+ <ellipse cx="500" cy="82.0" rx="28.0" ry="3.5" fill="#BCC5CA" opacity="0.55"/>
75
+ <rect x="476" y="28" width="48" height="48" rx="9.6" fill="#212E34"/><circle cx="500" cy="52" r="9.1" fill="#03B5BD"/>
76
+ <text x="532" y="48" class="m" font-size="14" fill="#212E34" font-weight="700" text-anchor="start">adopt</text>
77
+ <text x="532" y="66" class="s" font-size="12.5" fill="#4A4E51" text-anchor="start">adds what is missing, shows you the diff</text>
65
78
  </svg>
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.14.0"
3
+ version = "1.15.0"
4
4
  min_herdr_version = "0.8.2"
5
5
  description = "Show the herd, the state of a board's lanes, in a herdr pane"
6
6
  platforms = ["linux", "macos"]