jackherd 1.23.3 → 1.25.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 (90) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/INSTALL.md +16 -0
  3. package/README.md +23 -22
  4. package/dist/{chunk-JDO6O3UO.js → chunk-O365KHHP.js} +139 -3
  5. package/dist/config.d.ts +13 -1
  6. package/dist/config.js +178 -132
  7. package/dist/plan-format.d.ts +121 -3
  8. package/dist/plan-format.js +176 -35
  9. package/docs/plan-contract.md +52 -5
  10. package/herdr-plugin/herdr-plugin.toml +1 -1
  11. package/install.sh +1 -1
  12. package/kit/hooks/hooks/approval-gate/index.ts +2 -2
  13. package/kit/hooks/hooks/merge-guard/lane-plan.ts +1 -1
  14. package/kit/hooks/hooks/shared/board-lanes.ts +33 -12
  15. package/kit/hooks/hooks/shared/boards.ts +22 -6
  16. package/kit/workflow/skills/orchestrate/SKILL.md +2 -1
  17. package/kit/workflow/skills/orchestrate/references/backend.md +30 -0
  18. package/kit/workflow/skills/orchestrate/references/close-lane.md +1 -0
  19. package/kit/workflow/skills/orchestrate/references/herd.md +3 -3
  20. package/kit/workflow/skills/orchestrate/references/start-lane.md +2 -2
  21. package/kit/workflow/skills/plan/SKILL.md +3 -1
  22. package/kit/workflow/skills/plan/references/template-English.md +2 -0
  23. package/kit/workflow/skills/plan/references/template-German.md +2 -0
  24. package/package.json +1 -1
  25. package/schemas/jackherd.schema.json +49 -0
  26. package/src/backend/command-line.ts +116 -0
  27. package/src/backend/command.ts +14 -0
  28. package/src/backend/control.ts +194 -0
  29. package/src/backend/handover.ts +28 -0
  30. package/src/backend/herdr.ts +147 -0
  31. package/src/backend/lock.ts +84 -0
  32. package/src/backend/process-cwd.ts +32 -0
  33. package/src/backend/schema.ts +47 -0
  34. package/src/backend/state.ts +57 -0
  35. package/src/backend/step.ts +93 -0
  36. package/src/backend/system.ts +150 -0
  37. package/src/backend/watch.ts +75 -0
  38. package/src/brief/command.ts +1 -1
  39. package/src/brief/lane-facts.ts +2 -2
  40. package/src/check/command.ts +2 -1
  41. package/src/check/plan-structure-check.ts +68 -14
  42. package/src/check/settings.ts +6 -1
  43. package/src/check/skill-budget-defaults.ts +6 -3
  44. package/src/cli.ts +1 -0
  45. package/src/config/schema.ts +6 -0
  46. package/src/lane/close-board.ts +4 -4
  47. package/src/lane/close-command.ts +2 -2
  48. package/src/lane/dev-command.ts +1 -1
  49. package/src/lane/dev.ts +2 -2
  50. package/src/lane/port-command.ts +1 -1
  51. package/src/plan-contract/command.ts +2 -1
  52. package/src/plan-contract/contract.ts +30 -9
  53. package/src/plan-format/bare-codes.ts +4 -4
  54. package/src/plan-format/dependencies.ts +5 -5
  55. package/src/plan-format/lanes.ts +3 -4
  56. package/src/plan-format/markdown.ts +125 -10
  57. package/src/plan-format/open-status.ts +15 -0
  58. package/src/plan-format/phases.ts +45 -4
  59. package/src/plan-format/plan-head.ts +31 -9
  60. package/src/plan-format/plan-structure.ts +43 -7
  61. package/src/plan-format/progress.ts +3 -0
  62. package/src/plan-format/sprint-document.ts +4 -4
  63. package/src/plan-format/unrecognized-sections.ts +21 -0
  64. package/src/plan-format/waiting.ts +13 -13
  65. package/src/plans/board-close.ts +25 -16
  66. package/src/plans/board-pick.ts +1 -1
  67. package/src/plans/ensure-panes.ts +2 -2
  68. package/src/plans/layout-summary.ts +8 -1
  69. package/src/plans/layout.ts +14 -5
  70. package/src/plans/live.ts +1 -1
  71. package/src/plans/plan-panes.ts +1 -1
  72. package/src/plans/settings.ts +6 -0
  73. package/src/plans/snapshot.ts +9 -5
  74. package/src/plans/sprint-export.ts +1 -1
  75. package/src/plans/texts.ts +3 -0
  76. package/src/plans/view.ts +1 -0
  77. package/src/resume/coordinator-read.ts +2 -2
  78. package/src/settled/command.ts +1 -1
  79. package/src/settled/settle.ts +5 -5
  80. package/src/shared/shell-word.ts +2 -0
  81. package/src/sync/render.ts +20 -5
  82. package/src/sync/sync-plan.ts +6 -2
  83. package/src/sync/sync.ts +3 -2
  84. package/src/vocabulary/index.ts +192 -4
  85. package/src/watch/appointments.ts +13 -20
  86. package/src/watch/command.ts +32 -5
  87. package/src/watch/lane-progress.ts +1 -1
  88. package/src/watch/merge-overlap-read.ts +2 -2
  89. package/src/watch/plan-missing-read.ts +3 -1
  90. package/src/watch/reference-paths.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.25.0
6
+
7
+ - **Every plan phase says in one sentence what it is about.** The plan template puts a line directly under each `### Phase` heading, `**What it is about:** <sentence>` (German: `**Worum es geht:** <Satz>`): what is different afterwards and for whom, or "nothing visible". `jh check` fails a phase the change adds that has tasks and no such line; phases a plan already had are not read, so older plans stay green. `jh plan check` prints a sixth line, `purpose: present` or `purpose: missing <file>:<line> no purpose line in P2, P3`; like the head it is a note and never changes the exit code. In an opened lane the herd shows the sentence dimmed under the head of each phase it draws; the folded row and the sidebar title stay as they are. The plan skill (§ structure), the herd reference and `docs/plan-contract.md` describe it. Run `jh sync` to get the template and the skill text.
8
+ - **A board and a plan are read in every language, and the language jh writes the board in is its own setting.** A German board next to an English plan (or the other way round) used to leave "Your turn" empty and `jh plan check` red, because every heading, column name and status word was compared with the one language in `planLanguage`. jackherd now reads both languages whatever the config says, and a document in a third language carries a marker, `<!-- jh: <key> -->`, which Markdown does not show: on the line under a heading (`lanes`, `history`, `for-you`, …), in a header cell (`depends-on`, `status`, …), on a head line, in a status cell (`status-open`, `status-done`, …) and on a line that opens with bold words (`purpose`, `no-review-gate`). New `boardLanguage` in `.jackherd/config.json` is the language jh writes the board in; it defaults to `planLanguage`, so a config without it runs unchanged. A plan template renders in the language of its file name (the English file is no longer filled with German headings under a German `planLanguage`). When no section is recognised, `jh plan check` names the forms it looked for, `jh check` warns on a changed file jh reads nothing of, and the herd shows `board: no section recognised` instead of "nothing waits for you". `docs/plan-contract.md` lists the keys. Run `jh sync` to get the template and the skill text.
9
+
10
+ ## 1.24.0
11
+
12
+ - **A shared backend for all lanes: `lanes.backend` and `jh backend`.** With herdr, the services every lane's app talks to run in one pane (its own tab) of the main checkout, which the watcher starts at its start and once a minute, and starts again after it ended, at most three times in ten minutes. `lanes.backend` takes a `command` that contains `{services}`, a list of `services` (`name`, `port`, both unique) and an optional pane `label`; the command line starts with `env -u PORT`. A service port that something listens on prevents a start and is never freed. The pane keeps running when the watcher ends, even after `kit updated`; what was last started is kept in `backend.json` in the watcher's directory, so a restart of the watcher leaves a pane with that command alone. `jh backend` shows pane, command, handed-over services and starts; `jh backend restart` starts again after "gave up"; `jh backend release <service> --lane <lane>` stops the pane, starts it without that service and names the port for `jh lane dev <lane> --filter <service> --port <port>`; `jh backend return <service>` takes it back, and the watcher does it once the lane's worktree is gone. Only the process in the pane with the backend's label, in the main checkout, is ever stopped (SIGTERM to its foreground process group). A service name or label must be one shell word that does not start with a dash, and `jh lane dev --filter` now refuses a leading dash too. The orchestrate skill (§ 3, § 7, the `backend:` lines in its events reference) tells the coordinator when to hand a service over. A jackherd older than this version refuses a config that has `lanes.backend`: run `jh update` on every machine first, then `jh sync` for the skill text.
13
+
5
14
  ## 1.23.3
6
15
 
7
16
  - **The coordinator restarts a lane's dev server without a question.** The orchestrate skill (§ 3) and the pane-input denial now name the way: stop the server by its PID once `/proc/<pid>/cwd` shows the lane's worktree, then `herdr pane run` the same command in its pane; both pass the merge guard, keys typed into the pane ask the user. Run `jh sync` to get the skill text and the hook.
package/INSTALL.md CHANGED
@@ -138,6 +138,22 @@ Detection only reads the lock files and `refs/remotes/origin/*` as the last fetc
138
138
 
139
139
  The global skills cannot carry one repository's values, so `sync --global` writes them as `<test>`, `<install>`, `<lockFile>`, `<baseBranch>` and so on, and every global skill starts with a sentence that sends the model to `jh config` for them; a project install keeps the real values in its files. `init --global` writes none of these keys, so one global config serves a Yarn repository on `master` and a Bun repository on `main` alike. A global config from 0.10 or 0.11 spells out the Bun defaults and `main`, which count as set: delete `commands`, `baseBranch` and `lanes.lockFile` from it to get detection.
140
140
 
141
+ ## A shared backend for the lanes (optional)
142
+
143
+ With herdr, `lanes.backend` in the project's `.jackherd/config.json` runs the services every lane's app talks to in one pane of the main checkout, started and kept by the watcher (`jh watch`). It is never detected; without it nothing starts. The command must contain `{services}`, which becomes the names of the services not handed to a lane, and each service names its port:
144
+
145
+ ```json
146
+ "lanes": {
147
+ "backend": {
148
+ "command": "pnpm dev --filter {services}",
149
+ "services": [{ "name": "api", "port": 4000 }, { "name": "orders", "port": 4001 }],
150
+ "label": "backend"
151
+ }
152
+ }
153
+ ```
154
+
155
+ The line starts with `env -u PORT`, so a lane's `PORT` is not inherited. A service port that something listens on stops a start; nothing frees a port. `jh backend` shows the pane, `jh backend release <service> --lane <lane>` hands a service to a lane that changes it, and `jh backend return <service>` takes it back. A jackherd older than this setting refuses a config that has it, so update every machine first (`jh update`).
156
+
141
157
  ## Updating
142
158
 
143
159
  `jh update` asks npm for the newest release and, when there is one, installs exactly that version, from your home directory and without install scripts, with the package manager that installed the kit globally (bun, npm, pnpm or yarn, read from where the package lies), then syncs `~/.claude` and, inside a project with its own install (`.jackherd/lock.json`), the project with the freshly installed code. A project that has only a `.jackherd/config.json` for the global install is not synced; one line says so, and `jh init` installs the kit there. It prints the old and the new version. A kit that is a project dependency or a source checkout is not a global install, and `update` says so and changes nothing; there, update the dependency and run `jh sync`.
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.23.3/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.25.0/assets/branding/jackherd-logo-outline.svg" width="340">
3
3
  <br>
4
4
  </p>
5
5
 
6
- [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.23.3/assets/icons/space.svg">
6
+ [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.25.0/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
@@ -15,29 +15,29 @@ 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.23.3/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.25.0/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.23.3/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.25.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.23.3/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.25.0/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.23.3/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.25.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.23.3/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Commits, pushes and merges wait for your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours. The guards catch mistakes, not intent; a hard boundary takes branch protection.</p>
28
+ <p><img src="https://unpkg.com/jackherd@1.25.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Commits, pushes and merges wait for your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours. The guards catch mistakes, not intent; a hard boundary takes branch protection.</p>
29
29
 
30
- <p><img src="https://unpkg.com/jackherd@1.23.3/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A lane gets stuck, a check goes red, a decision needs you: Jack steps in and brings you only what's yours to decide. The herd view shows where every lane stands.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.25.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A lane gets stuck, a check goes red, a decision needs you: Jack steps in and brings you only what's yours to decide. The herd view shows where every lane stands.</p>
31
31
 
32
- <p><img src="https://unpkg.com/jackherd@1.23.3/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
32
+ <p><img src="https://unpkg.com/jackherd@1.25.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.23.3/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments on a lane's pull request go back to that lane, which fixes them with a test. <code>/jh-follow</code> brings in anything a CLI or an API can read as JSON: tickets, error logs, status pages.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.25.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments on a lane's pull request go back to that lane, which fixes them with a test. <code>/jh-follow</code> brings in anything a CLI or an API can read as JSON: tickets, error logs, status pages.</p>
35
35
 
36
- <p><img src="https://unpkg.com/jackherd@1.23.3/assets/icons/model.svg" width="24" height="34" align="middle" alt=""> <b>The right model for each phase.</b><br>Every phase names its model: the stronger one where a mistake is expensive (data, security, a migration), the cheaper one for the rest. Lanes start on it, and a hook refuses to start a phase's subagent on a weaker one.</p>
36
+ <p><img src="https://unpkg.com/jackherd@1.25.0/assets/icons/model.svg" width="24" height="34" align="middle" alt=""> <b>The right model for each phase.</b><br>Every phase names its model: the stronger one where a mistake is expensive (data, security, a migration), the cheaper one for the rest. Lanes start on it, and a hook refuses to start a phase's subagent on a weaker one.</p>
37
37
 
38
- <p><img src="https://unpkg.com/jackherd@1.23.3/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>A crash or a full context doesn't cost you the work.</b><br>Plan, board and a hand-over file hold the state, not the chat: the next session picks up where the last one stopped.</p>
38
+ <p><img src="https://unpkg.com/jackherd@1.25.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>A crash or a full context doesn't cost you the work.</b><br>Plan, board and a hand-over file hold the state, not the chat: the next session picks up where the last one stopped.</p>
39
39
 
40
- <p><img src="https://unpkg.com/jackherd@1.23.3/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.23.3/assets/icons/space.svg"></p>
40
+ <p><img src="https://unpkg.com/jackherd@1.25.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.25.0/assets/icons/space.svg"></p>
41
41
 
42
42
  ## Quickstart
43
43
 
@@ -68,22 +68,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
68
68
  /jh-orchestrate docs/plans/board-<name>/board.md
69
69
  ```
70
70
 
71
- Linux and macOS are supported (Windows through WSL); the requirements are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md). By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.23.3/assets/icons/space.svg">
71
+ Linux and macOS are supported (Windows through WSL); the requirements are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md). By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.25.0/assets/icons/space.svg">
72
72
 
73
73
  ## Follow anything you can query
74
74
 
75
75
  Tickets, error logs, status pages: if a command or an API call can read it as JSON with your login, Jack can watch it. Recipes cover Jira, Linear, GitHub issues and Projects, Sentry, Google Calendar and status pages; for anything else, `/jh-follow` writes the command with you. One yes, and every change shows up like this:
76
76
 
77
- <img src="https://unpkg.com/jackherd@1.23.3/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.">
77
+ <img src="https://unpkg.com/jackherd@1.25.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.">
78
78
 
79
- The herd sums up your sources in one `Sources` line: new entries per source, or its last run, red while it fails; opened, a click opens an entry. One that needs you lands in Your turn under the source's label, [ask] has Jack look at it, and a ticket shows on the lane that works it.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.23.3/assets/icons/space.svg">
79
+ The herd sums up your sources in one `Sources` line: new entries per source, or its last run, red while it fails; opened, a click opens an entry. One that needs you lands in Your turn under the source's label, [ask] has Jack look at it, and a ticket shows on the lane that works it.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.25.0/assets/icons/space.svg">
80
80
 
81
81
  ## In detail
82
82
 
83
83
  <details>
84
84
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
85
85
 
86
- <img src="https://unpkg.com/jackherd@1.23.3/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.">
86
+ <img src="https://unpkg.com/jackherd@1.25.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.">
87
87
 
88
88
  | On his own | After your approval | Never |
89
89
  | --- | --- | --- |
@@ -116,9 +116,9 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
116
116
  <details>
117
117
  <summary><b>Plans, boards and the skills, yours included</b></summary>
118
118
 
119
- 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
+ A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase, and under each phase heading one sentence on what is different afterwards and for whom. 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.
120
120
 
121
- <img src="https://unpkg.com/jackherd@1.23.3/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
+ <img src="https://unpkg.com/jackherd@1.25.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.">
122
122
 
123
123
  | Skill | Does |
124
124
  | --- | --- |
@@ -185,7 +185,7 @@ How the herd reads the board:
185
185
  - **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.
186
186
  - **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 folded lane line opens with its status and the age of its last activity: `needs you 3 min` and the first open question of its report (`+2 questions` counts the others), `blocked 3 min` and what it waits on (a row of Your turn, or the lane's starts-after cell), or `running 3 min` and its step. The plan's short title and the phase as `P2/5` with its name follow, and the plan is the first part to give way in a narrow pane. A resting lane without a known reason keeps `idle · no reason`. `:3100 ↗` on the branch line of an open lane opens its dev server, while one listens on the port its board row names.
187
187
  - **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.
188
- - **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 tasks as a checklist in plan order (ticked above, the running one marked, the ones to come below; above seven tasks a window around the running one, with `… n more` above and below to open the whole list); `▸ 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 shows a reported task below its line while it has the mouse; 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.
188
+ - **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, its purpose sentence dimmed under the head, and its tasks as a checklist in plan order (ticked above, the running one marked, the ones to come below; above seven tasks a window around the running one, with `… n more` above and below to open the whole list); `▸ 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 shows a reported task below its line while it has the mouse; 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.
189
189
  - **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.
190
190
  - **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). `◌` is a guess: in dim, a task guessed from the words of the lane's log; shimmering like a reported one, the first open task of a phase the lane runs when no agent reported one. `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.
191
191
  - **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.
@@ -223,13 +223,14 @@ jackherd sets the model, not the effort and not the advisor: those come from Cla
223
223
  | `jh init` | creates `.jackherd/config.json` and installs the kit (`--global` for `~/.claude`) |
224
224
  | `jh sync` | updates the kit; a file you edited gets the new version beside it as `.kit-new` |
225
225
  | `jh update` | installs the newest release and syncs |
226
- | `jh check` | types, lint and tests of what you touched, plus the plan and board checks (`--skip-slow`, `--parallel <plan>`) |
226
+ | `jh check` | types, lint and tests of what you touched, plus the plan and board checks, among them the purpose line every new plan phase with tasks needs (`--skip-slow`, `--parallel <plan>`); `jh plan check <plan>` reports the plan contract, the purpose line as a note; boards and plans are read in English and German whatever `planLanguage` says, a third language by `<!-- jh: <key> -->` markers, and `boardLanguage` (default `planLanguage`) is the language jh writes the board in (`docs/plan-contract.md`) |
227
227
  | `jh herd` | the live view (`--once`, `--lane <name>`) |
228
228
  | `jh watch` | the watcher Jack runs |
229
229
  | `jh resume` | after herdr restarts, wakes the coordinator and the lanes that worked when the machine crashed; runs once per crash and only when the watcher's record shows no `stoppedAt`. Stopping the watcher before quitting herdr prevents it. With `--coordinator` it lists what a coordinator takes up after `/clear` (lanes, PRs, approvals, what waits on you, appointments, standing approvals), from files and herdr; `--clear-line` prints the first input of that chat. |
230
230
  | `jh secret` | keeps a personal source's secrets in the system keychain (`set <NAME>`, `list`, `delete <NAME>`); `list` shows which are set, never a value |
231
231
  | `jh lane prompt <lane>` | writes a lane's first prompt from its board row and its plan (`--out`, `--handover`); your additions below the marker line at its end stay |
232
232
  | `jh lane close <lane>` | removes a finished lane: agent, worktree, herdr workspace and branch, and marks the board; first checks that nothing is lost, and refuses with every reason; `--dry-run` only checks. Runs without a prompt. |
233
+ | `jh backend` | the shared backend of the lanes (`lanes.backend`, one pane in the main checkout that the watcher keeps): shows its pane, command and handed-over services; `restart`; `release <service> --lane <lane>` stops it and starts it without that service, naming the port the lane starts it on; `return <service>`. Works from any worktree. A busy service port stops a start, nothing frees a port |
233
234
  | `jh preview <file>` | renders a Markdown file through the GitHub API into a preview in GitHub and npm style |
234
235
  | `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>…` or `close --all` once you have) |
235
236
  | `jh brief` | three lines for each open lane of the board: its goal, its status and what it needs from you, read from the head of its plan (`--board <board.md>` names the board); after `plans.briefAfterMinutes` (default 60, 0 is off) without a prompt, the `return-brief` hook asks Jack to show it first |
@@ -250,7 +251,7 @@ Without a global install, run them as `bunx jackherd <command>`. The config is `
250
251
  }
251
252
  ```
252
253
 
253
- `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. A file it names by its exact path is measured too, even one `jh check` would not read on its own (`CLAUDE.md`, a rule with `paths:`); a named file that is missing warns. It also reads the rule blocks you added or changed in `**/rules/**/*.md`, `CLAUDE.md` and `AGENTS.md`, and warns (`Lint pointers`) where a block carries a rule your linter could check (a banned import path, a literal comparison, a comment form, a banned call) and names no lint rule. Name it with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where the rule stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs that are never read. `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
254
+ `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. A file it names by its exact path is measured too, even one `jh check` would not read on its own (`CLAUDE.md`, a rule with `paths:`); a named file that is missing warns. It also reads the rule blocks you added or changed in `**/rules/**/*.md`, `CLAUDE.md` and `AGENTS.md`, and warns (`Lint pointers`) where a block carries a rule your linter could check (a banned import path, a literal comparison, a comment form, a banned call) and names no lint rule. Name it with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where the rule stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs that are never read. `lanes.backend` (`command` with `{services}`, `services` as `{ name, port }`, optional `label`) runs the services every lane's app talks to in one pane of the main checkout, started and kept by the watcher: it checks at its start and once a minute, starts at most three times in ten minutes, leaves the pane running when the watcher ends, and a service port that is busy prevents a start (see [INSTALL.md](INSTALL.md)). `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
254
255
 
255
256
  </details>
256
257
 
@@ -1,5 +1,9 @@
1
1
  // src/vocabulary/index.ts
2
2
  var PLAN_LANGUAGE = { de: "de", en: "en" };
3
+ var languagesFrom = (language) => [
4
+ language,
5
+ ...Object.values(PLAN_LANGUAGE).filter((other) => other !== language)
6
+ ];
3
7
  var LANGUAGE_NAME = {
4
8
  [PLAN_LANGUAGE.en]: "English",
5
9
  [PLAN_LANGUAGE.de]: "German"
@@ -87,6 +91,73 @@ var WAITING_VOCABULARY = {
87
91
  open: ["open"]
88
92
  }
89
93
  };
94
+ var SECTION_KEY = {
95
+ waiting: "waiting",
96
+ appointments: "appointments",
97
+ lanes: "lanes",
98
+ goal: "goal",
99
+ history: "history",
100
+ plans: "plans",
101
+ decisions: "decisions",
102
+ implementation: "implementation",
103
+ model: "model",
104
+ dependencies: "dependencies",
105
+ documentation: "documentation",
106
+ forYou: "for-you",
107
+ next: "next"
108
+ };
109
+ var isSectionKey = (value) => Object.values(SECTION_KEY).includes(value);
110
+ var HEAD_KEY = {
111
+ changes: "head-changes",
112
+ status: "head-status",
113
+ decisions: "head-decisions",
114
+ tryNow: "head-try-now",
115
+ next: "head-next"
116
+ };
117
+ var LINE_KEY = { noReviewGate: "no-review-gate", purpose: "purpose" };
118
+ var COLUMN_KEY = {
119
+ since: "since",
120
+ item: "item",
121
+ origin: "origin",
122
+ unblocks: "unblocks",
123
+ status: "status",
124
+ when: "when",
125
+ action: "action",
126
+ dependsOn: "depends-on",
127
+ writes: "writes",
128
+ serialMarker: "serial-marker"
129
+ };
130
+ var columnWords = (key, language) => {
131
+ const waiting = WAITING_VOCABULARY[language];
132
+ const appointments = APPOINTMENT_VOCABULARY[language];
133
+ const { columns } = PLAN_VOCABULARY[language];
134
+ const words = {
135
+ [COLUMN_KEY.since]: [waiting.columns.since],
136
+ [COLUMN_KEY.item]: [waiting.columns.item],
137
+ [COLUMN_KEY.origin]: [waiting.columns.origin],
138
+ [COLUMN_KEY.unblocks]: [waiting.columns.unblocks],
139
+ [COLUMN_KEY.status]: [
140
+ waiting.columns.status,
141
+ ...waiting.statusAlso,
142
+ appointments.columns.status,
143
+ ...appointments.statusAlso
144
+ ],
145
+ [COLUMN_KEY.when]: [appointments.columns.when],
146
+ [COLUMN_KEY.action]: [appointments.columns.action],
147
+ [COLUMN_KEY.dependsOn]: [columns.dependsOn],
148
+ [COLUMN_KEY.writes]: [columns.writes],
149
+ [COLUMN_KEY.serialMarker]: [columns.serialMarker]
150
+ };
151
+ return words[key];
152
+ };
153
+ var STATUS_KEY = {
154
+ open: "status-open",
155
+ done: "status-done",
156
+ abandoned: "status-abandoned",
157
+ inProgress: "status-in-progress",
158
+ draft: "status-draft"
159
+ };
160
+ var isStatusKey = (value) => Object.values(STATUS_KEY).includes(value);
90
161
  var isPlanLanguage = (value) => Object.values(PLAN_LANGUAGE).includes(value);
91
162
  var PLAN_STATUS = {
92
163
  done: "done",
@@ -138,6 +209,7 @@ var PLAN_VOCABULARY = {
138
209
  columns: { dependsOn: "H\xE4ngt ab von", writes: "Schreibt", serialMarker: "Seriell-Marker" },
139
210
  phase: "Phase",
140
211
  noReviewGate: "Kein Review-Gate",
212
+ phasePurpose: "Worum es geht",
141
213
  laneClosed: "abger\xE4umt",
142
214
  partialPhase: "(Teil)",
143
215
  openSubagents: "offene Sub-Agenten",
@@ -181,6 +253,7 @@ var PLAN_VOCABULARY = {
181
253
  columns: { dependsOn: "Depends on", writes: "Writes", serialMarker: "Serial marker" },
182
254
  phase: "Phase",
183
255
  noReviewGate: "No review gate",
256
+ phasePurpose: "What it is about",
184
257
  laneClosed: "cleaned up",
185
258
  partialPhase: "(part)",
186
259
  openSubagents: "open subagents",
@@ -202,15 +275,63 @@ var PLAN_VOCABULARY = {
202
275
  }
203
276
  };
204
277
  var STATUS_PRECEDENCE = [PLAN_STATUS.done, PLAN_STATUS.abandoned, PLAN_STATUS.inProgress, PLAN_STATUS.draft];
205
- var classifyStatus = (language, statusText) => {
278
+ var STATUS_OF_KEY = {
279
+ [STATUS_KEY.done]: PLAN_STATUS.done,
280
+ [STATUS_KEY.abandoned]: PLAN_STATUS.abandoned,
281
+ [STATUS_KEY.inProgress]: PLAN_STATUS.inProgress,
282
+ [STATUS_KEY.draft]: PLAN_STATUS.draft
283
+ };
284
+ var MARKER_IN_TEXT = /<!--\s*jh:\s*([a-z-]+)\s*-->/;
285
+ var statusOfMarker = (statusText) => {
286
+ const key = MARKER_IN_TEXT.exec(statusText)?.[1];
287
+ return key !== void 0 && isStatusKey(key) ? STATUS_OF_KEY[key] : void 0;
288
+ };
289
+ var statusOfWords = (language, statusText) => {
206
290
  const { statusWords } = PLAN_VOCABULARY[language];
207
291
  const lowerStatus = statusText.toLowerCase();
208
292
  const matches = ({ leading, anywhere }) => leading.some((word) => lowerStatus.startsWith(word)) || anywhere.some((fragment) => lowerStatus.includes(fragment));
209
- const matched = STATUS_PRECEDENCE.find((status) => matches(statusWords[status]));
293
+ return STATUS_PRECEDENCE.find((status) => matches(statusWords[status]));
294
+ };
295
+ var classifyStatus = (language, statusText) => {
296
+ const marked = statusOfMarker(statusText);
297
+ if (marked) return marked;
298
+ const text = statusText.replace(MARKER_IN_TEXT, "");
299
+ const matched = languagesFrom(language).map((candidate) => statusOfWords(candidate, text)).find((status) => status !== void 0);
210
300
  if (matched) return matched;
211
- return statusText.trim() ? PLAN_STATUS.other : PLAN_STATUS.none;
301
+ return text.trim() ? PLAN_STATUS.other : PLAN_STATUS.none;
212
302
  };
213
303
  var phaseHeading = (language, id, title) => `### ${PLAN_VOCABULARY[language].phase} ${id}: ${title}`;
304
+ var headingOfSection = (key, language) => {
305
+ const { sections } = PLAN_VOCABULARY[language];
306
+ const headings = {
307
+ [SECTION_KEY.waiting]: WAITING_VOCABULARY[language].heading,
308
+ [SECTION_KEY.appointments]: APPOINTMENT_VOCABULARY[language].heading,
309
+ [SECTION_KEY.lanes]: sections.lanes,
310
+ [SECTION_KEY.goal]: sections.goal,
311
+ [SECTION_KEY.history]: sections.history,
312
+ [SECTION_KEY.plans]: sections.plans,
313
+ [SECTION_KEY.decisions]: sections.decisions,
314
+ [SECTION_KEY.implementation]: sections.implementation,
315
+ [SECTION_KEY.model]: sections.modelRecommendations,
316
+ [SECTION_KEY.dependencies]: sections.dependencies,
317
+ [SECTION_KEY.documentation]: sections.documentation,
318
+ [SECTION_KEY.forYou]: sections.forYou,
319
+ [SECTION_KEY.next]: NEXT_VOCABULARY[language].heading
320
+ };
321
+ return headings[key];
322
+ };
323
+ var sectionOfHeading = (headingText) => {
324
+ for (const language of Object.values(PLAN_LANGUAGE)) {
325
+ const key = Object.values(SECTION_KEY).find((candidate) => headingOfSection(candidate, language) === headingText);
326
+ if (key !== void 0) return { key, language };
327
+ }
328
+ return null;
329
+ };
330
+ var CELL_KEY = { laneClosed: "lane-closed", partialPhase: "partial-phase" };
331
+ var LANE_CLOSED_WORDS = Object.values(PLAN_VOCABULARY).map(({ laneClosed }) => laneClosed);
332
+ var PARTIAL_PHASE_WORDS = Object.values(PLAN_VOCABULARY).map(
333
+ ({ partialPhase }) => partialPhase
334
+ );
214
335
  var sectionHeading = (language, section) => `## ${PLAN_VOCABULARY[language].sections[section]}`;
215
336
 
216
337
  // src/plan-format/terminal-text.ts
@@ -232,6 +353,7 @@ var terminalSafe = (text) => text.replace(UNSAFE_TERMINAL_TEXT, "");
232
353
 
233
354
  export {
234
355
  PLAN_LANGUAGE,
356
+ languagesFrom,
235
357
  LANGUAGE_NAME,
236
358
  RESPONSE_ADDRESS,
237
359
  ADDRESS_FORM,
@@ -244,13 +366,27 @@ export {
244
366
  APPOINTMENT_VOCABULARY,
245
367
  NEXT_VOCABULARY,
246
368
  WAITING_VOCABULARY,
369
+ SECTION_KEY,
370
+ isSectionKey,
371
+ HEAD_KEY,
372
+ LINE_KEY,
373
+ COLUMN_KEY,
374
+ columnWords,
375
+ STATUS_KEY,
376
+ isStatusKey,
247
377
  isPlanLanguage,
248
378
  PLAN_STATUS,
249
379
  REPORT_FIELDS,
250
380
  HEAD_LINE,
251
381
  PLAN_VOCABULARY,
382
+ MARKER_IN_TEXT,
252
383
  classifyStatus,
253
384
  phaseHeading,
385
+ headingOfSection,
386
+ sectionOfHeading,
387
+ CELL_KEY,
388
+ LANE_CLOSED_WORDS,
389
+ PARTIAL_PHASE_WORDS,
254
390
  sectionHeading,
255
391
  terminalSafe
256
392
  };
package/dist/config.d.ts CHANGED
@@ -95,6 +95,10 @@ declare const jackherdConfigSchema: z.ZodObject<{
95
95
  de: "de";
96
96
  en: "en";
97
97
  }>>;
98
+ boardLanguage: z.ZodOptional<z.ZodEnum<{
99
+ de: "de";
100
+ en: "en";
101
+ }>>;
98
102
  planDirectory: z.ZodDefault<z.ZodString>;
99
103
  board: z.ZodPrefault<z.ZodObject<{
100
104
  pattern: z.ZodDefault<z.ZodString>;
@@ -122,10 +126,18 @@ declare const jackherdConfigSchema: z.ZodObject<{
122
126
  minFreeGb: z.ZodOptional<z.ZodNumber>;
123
127
  basePort: z.ZodOptional<z.ZodNumber>;
124
128
  devCommand: z.ZodOptional<z.ZodString>;
129
+ backend: z.ZodOptional<z.ZodObject<{
130
+ command: z.ZodString;
131
+ services: z.ZodArray<z.ZodObject<{
132
+ name: z.ZodString;
133
+ port: z.ZodNumber;
134
+ }, z.core.$strict>>;
135
+ label: z.ZodDefault<z.ZodString>;
136
+ }, z.core.$strict>>;
125
137
  }, z.core.$strict>>;
126
138
  models: z.ZodDefault<z.ZodRecord<z.ZodEnum<{
127
- review: "review";
128
139
  plan: "plan";
140
+ review: "review";
129
141
  implement: "implement";
130
142
  explore: "explore";
131
143
  orchestrate: "orchestrate";