jackherd 1.8.1 → 1.9.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 (111) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/INSTALL.md +2 -0
  3. package/README.md +37 -44
  4. package/dist/{chunk-OU2KALXP.js → chunk-MCP4XJQH.js} +15 -0
  5. package/dist/config.d.ts +6 -0
  6. package/dist/config.js +19 -3
  7. package/dist/plan-format.d.ts +19 -2
  8. package/dist/plan-format.js +21 -7
  9. package/herdr-plugin/herdr-plugin.toml +1 -1
  10. package/install.sh +1 -1
  11. package/kit/hooks/hooks/approval-gate/index.ts +22 -4
  12. package/kit/hooks/hooks/approval-gate/run.ts +29 -6
  13. package/kit/hooks/hooks/approval-gate/subagent.ts +18 -0
  14. package/kit/hooks/hooks/merge-guard/decision.ts +67 -20
  15. package/kit/hooks/hooks/sent-file/index.ts +19 -0
  16. package/kit/hooks/hooks/sent-file/record.ts +128 -0
  17. package/kit/hooks/hooks/sent-file/run.ts +48 -0
  18. package/kit/hooks/hooks/sent-file/store.ts +18 -0
  19. package/kit/hooks/hooks/shared/board-lanes.ts +15 -2
  20. package/kit/hooks/hooks.json +9 -0
  21. package/kit/hooks/statusline.sh +1 -1
  22. package/kit/workflow/skills/implement/SKILL.md +2 -0
  23. package/kit/workflow/skills/orchestrate/SKILL.md +30 -6
  24. package/package.json +2 -1
  25. package/schemas/jackherd.schema.json +26 -2
  26. package/src/cli.ts +3 -0
  27. package/src/config/schema.ts +22 -2
  28. package/src/context/fill.ts +12 -0
  29. package/src/context/session-log.ts +43 -1
  30. package/src/plan-format/clock.ts +4 -0
  31. package/src/plan-format/dependencies.ts +2 -5
  32. package/src/plan-format/lanes.ts +30 -5
  33. package/src/plan-format/terminal-text.ts +4 -0
  34. package/src/plan-format/waiting.ts +48 -0
  35. package/src/plans/active-tasks.ts +60 -0
  36. package/src/plans/current-phase.ts +17 -7
  37. package/src/plans/current-task.ts +63 -5
  38. package/src/plans/lane-standing.ts +9 -4
  39. package/src/plans/lane-summary.ts +141 -74
  40. package/src/plans/lane-tickets.ts +15 -0
  41. package/src/plans/layout-phase.ts +57 -18
  42. package/src/plans/layout-summary.ts +85 -67
  43. package/src/plans/layout.ts +253 -89
  44. package/src/plans/live.ts +22 -0
  45. package/src/plans/phase-models.ts +1 -1
  46. package/src/plans/phase-ranges.ts +45 -0
  47. package/src/plans/pull-requests.ts +6 -0
  48. package/src/plans/selection.ts +19 -16
  49. package/src/plans/sent-files-read.ts +22 -0
  50. package/src/plans/sent-files.ts +121 -0
  51. package/src/plans/signals-read.ts +54 -0
  52. package/src/plans/snapshot.ts +251 -55
  53. package/src/plans/subagents-read.ts +82 -0
  54. package/src/plans/subagents.ts +132 -0
  55. package/src/plans/texts.ts +16 -11
  56. package/src/plans/unlisted-lanes.ts +10 -5
  57. package/src/plans/view.ts +3 -3
  58. package/src/plans/waiting-click.ts +76 -0
  59. package/src/preview/command.ts +83 -0
  60. package/src/preview/embed.ts +209 -0
  61. package/src/preview/images.ts +89 -0
  62. package/src/preview/open.ts +12 -0
  63. package/src/preview/options.ts +32 -0
  64. package/src/preview/page.ts +89 -0
  65. package/src/preview/render.ts +41 -0
  66. package/src/settled/board-row.ts +1 -2
  67. package/src/shared/is-record.ts +2 -0
  68. package/src/shared/private-records.ts +48 -0
  69. package/src/task-signals/agent.ts +15 -0
  70. package/src/task-signals/claude-read.ts +72 -0
  71. package/src/task-signals/claude-signals.ts +112 -0
  72. package/src/task-signals/command.ts +71 -0
  73. package/src/task-signals/fixtures/session.jsonl +7 -0
  74. package/src/task-signals/matcher.ts +96 -0
  75. package/src/task-signals/record-store.ts +40 -0
  76. package/src/task-signals/record.ts +68 -0
  77. package/src/task-signals/text.ts +13 -0
  78. package/src/task-signals/types.ts +14 -0
  79. package/src/terminal/open.ts +13 -6
  80. package/src/terminal/text.ts +8 -1
  81. package/src/terminal/width.ts +2 -1
  82. package/src/vocabulary/index.ts +28 -0
  83. package/src/waiting/command.ts +35 -0
  84. package/src/watch/appointments.ts +2 -3
  85. package/src/watch/approvals.ts +2 -2
  86. package/src/watch/command.ts +91 -5
  87. package/src/watch/failed-tests.ts +56 -10
  88. package/src/watch/failure-read.ts +18 -6
  89. package/src/watch/failures.ts +29 -5
  90. package/src/watch/feedback.ts +1 -1
  91. package/src/watch/gh-process.ts +9 -3
  92. package/src/watch/json-guards.ts +0 -3
  93. package/src/watch/lane-fill.ts +1 -1
  94. package/src/watch/lane-model.ts +1 -1
  95. package/src/watch/lane-parallel.ts +1 -1
  96. package/src/watch/lane-progress.ts +19 -7
  97. package/src/watch/lane-report.ts +5 -1
  98. package/src/watch/lane-rest.ts +28 -0
  99. package/src/watch/plan-badges.ts +9 -2
  100. package/src/watch/previews.ts +1 -1
  101. package/src/watch/pull-requests.ts +2 -1
  102. package/src/watch/source-path.ts +1 -1
  103. package/src/watch/sources.ts +1 -1
  104. package/src/watch/stream-output.ts +12 -7
  105. package/src/watch/task-parallel-input.ts +118 -0
  106. package/src/watch/task-parallel-read.ts +149 -0
  107. package/src/watch/task-parallel-touched.ts +39 -0
  108. package/src/watch/task-parallel.ts +206 -0
  109. package/src/watch/waiting-line.ts +19 -0
  110. package/src/watch/watch-events.ts +13 -1
  111. package/src/watch/workflow-runs.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.9.0
6
+
7
+ - **An open lane in the herd takes fewer lines, and a red pull request says what failed where.** A failed test hangs under its check as `└ ✗ <package> · <test>`, the package read from the log (the monorepo task header, else the test file), the test path dim with `›`; a cut name unfolds on a click and folds on the next. The pull request title stays on one line, a lane's only link chip moves onto its session line, and the finished phases are ranges (`✓ P1–P7, P10–P15`). The label column (SESSION, PR, NOW, NEXT, LATER, DONE, LINKS) is gone: the tree lines and each row's mark (`⎇`, `↗`, `▶`/`▷`, `✓`) say what it is, and every row under a lane gains the 9 columns the labels took. Each running phase is a branch of its own, and the other open phases share one line, `next P18–P25` (ranges, the other plans by name after them). A reported task in other words still finds its plan task, and a lane that reports a task shows no guessed `▷`. The progress column is calmer: no `◐`, the `🥣` cell on a folded lane only from `warn`, and a hidden cell keeps its width, so no bar moves; the checkbox count stands on every lane line. A folded lane's title line in a narrow pane starts in the column of the lane's name, where an open lane's branches start too. A click on a for-you row now also lands right while `now:` lines stand above it.
8
+ - **A lane in the herd shows only the phases its board rows name.** It showed every phase of its plan, so phases another lane had just written stood under the lane's NOW while its own phase waited; the tally (`n/m`, `n/m ph`) counts the named phases too, a sub-phase (`P10c`) belonging to the phase a row names. A board row's range (`P17–P25`) names every phase it spans, where before only its first counted. A phase runs while some of its tasks are ticked and others open, while one of its boxes was ticked in the last 20 minutes, or while an agent reports it; task lines written into a phase, or a new phase without a tick, no longer make it run, and a phase with only its review gate open waits.
9
+ - **The herd's teal is the brand colour.** The teal accents (the working lane's dot, `▶`/`▷`, `↑n`) are exactly `#03B5BD`, as true colour where `COLORTERM` is `truecolor` or `24bit`, and 256-colour 37 elsewhere.
10
+ - **`jh watch` proposes open plan tasks that could run in parallel at a heartbeat** (`parallel tasks <lane>: …`): disjoint paths, none touched or reported by running agents (the coordinator's subagents in the lane's worktree included), extra agents costing less than `watch.parallelTasks.maxTokens` (default 100000, the baseline is the first answer of a session log). One free task beside a running agent counts; a lane with no active work gets no proposal; once per changed situation; nothing starts by itself.
11
+ - **The merge guard no longer asks for an interpreter script whose text merely contains the command words apart** (a path segment `workflow` and a `run` elsewhere): where the program is hidden, the words count only as adjacent whole tokens, with quotes, commas, brackets and gh options between them. An option value that brackets or braces cut into several tokens (`-R "${REPO}"`, `REPOS[0]`, `os.environ["GH_REPO"]`, an f-string) no longer hides the words after it, so those calls ask again.
12
+ - **`jh preview` is hardened.** An image is embedded only when its answer carries a plain `image/<subtype>` type, an image address is escaped before it goes into the page (also the one behind GitHub's image proxy) and read back unescaped before it is fetched, so a badge address with `&` in it fetches the right image, and a local image is read only when it is a regular file whose real path, links followed, lies inside the package. `jh watch` reads the task signals of subagent logs written within the activity window only, and the writes of at most the 20 newest subagent logs of a session.
13
+ - **The herd's arrow follows the task an agent works on.** It stood on the first open task of a phase; now the open task whose words the lane's agent (or a subagent working in its worktree) used most in its last 40 log entries gets the arrow, when it clearly leads (at least 3 of its distinctive words and 40 %); otherwise the first open task, as before.
14
+ - **`jh task start "<text>"` and `jh task done` report the task an agent works on.** One record per agent in `.jackherd/tasks/` (a new `start` replaces the agent's task); the kit skills `/implement` and `/orchestrate` tell every building agent to report. `jh task done` without an open task is silent and exits 0, so a second call is no error; with text that names another task than the open one it exits 1.
15
+ - **The herd's arrow comes from what agents report, in every case.** It reads Claude Code's task list (`TaskCreate`/`TaskUpdate`, `TodoWrite`) from the logs of the sessions and subagents of every worktree and of the coordinator, and the records of `jh task`, and matches them to open plan tasks. Every active task carries its own `▶`, a phase with an active task runs, so a lane may show several active phases; the agent's name stands dim after the title only while more than one agent is active in the lane. A report that matches no open task shows as `now: <text>` under its lane, under the title when no lane fits (also without herdr). A `jh task` record counts only while it is younger than the lane's last tick; agent text is cleaned of control characters. Without any report the word guess stays, drawn as a dim `▷` with a plain title.
16
+ - **The herd's arrow is teal, not yellow.** `▶` on a task is teal with a bold title, as the working lane's dot is; yellow stays for what is blocked or waits on you. The legend (`?`) names `▶` and `▷`.
17
+ - **The open lane's tree names each thing once.** An open lane no longer repeats its phase on the lane line; a folded lane names it compactly, `P3 Search API +1 · 12/20` (`+n` more active phases). The running phases stand by number, never by recency, each with its age, its count and `· gate` while only the review gate is open, without an arrow on the phase row; arrows stand before tasks only, and an agent's name stands dim at the right edge of its task, only while more than one agent is active. An open lane shows per active phase only its active tasks; a click on the phase row shows the other open ones and the next click folds them. Only you change a fold; a new active task adds its own row.
18
+ - **Files sent to you and published artifacts stay in sight.** The new kit hook `sent-file` (PostToolUse on `SendUserFile` and `Artifact`) records each send with time and caption in `.jackherd/sent/`. `jh herd` shows the files as `📎` badges among the link chips of the lane they belong to (linked by their `file:` address; an artifact by its URL), the sends nobody closed wait on you next to the board's rows (top of the herd, heartbeat, status line), and `jh waiting close <id>` (or `--all`) closes them after your answer; `jh waiting` lists them.
19
+ - **A draft for review is published as one Artifact.** `/orchestrate` names the `Artifact` tool: with it, and unless `preview.publish` is `false` in `.jackherd/config.json`, the coordinator publishes the `jh preview` page as a private Artifact and updates the same URL each round; without it, a file card or `jh preview --open`. `jh preview` embeds external images (badges) as data addresses so the page is complete where it may load nothing from outside, and warns once for an image it could not fetch. It fetches only `https` images from public hosts (no loopback, private or link-local address, also after a redirect) and at most 5 MB each.
20
+ - **`jh herd` stays calm.** The lanes keep the order of the board, whatever their states are (the order by urgency is gone; the state shows in the symbol and colour of each line, and what waits on you sits on top). A lane opens by itself only when it first appears; a change of status no longer opens or closes it, so a lane you folded stays folded.
21
+ - **A click on a `📎` row opens it.** In `jh herd` a plain left click on a file or Artifact in the block of what waits on you opens it with the platform's opener (`xdg-open`, `open`); a `?` row opens nothing and a failure shows in the footer. `jh preview --open` shares the opener (`src/terminal/open.ts`).
22
+ - **The herd's lane line names the state only for `blocked`.** The symbol at the row start carries working, idle, done, unknown and no agent (colour unchanged); a blocked lane writes `blocked` in front of its own phase title (no column, so no other row moves). The legend names every symbol; the watcher and the heartbeat keep their words.
23
+ - **Long for-you rows expand by click.** A `📎`/`?` row wider than the pane expands on a click (wrapped over several lines); a question folds on the next click, a file or Artifact opens on it. A row that fits opens its file at once.
24
+ - **The coordinator's hand-over thresholds default to 350 000 / 500 000 tokens** (`plans.context.coordinator`, were 200 000 / 300 000), since a fresh coordinator chat already starts near 200 000; the 60 % / 85 % cap of a smaller window stays. The `statusline.sh` template follows.
25
+ - **`jh preview --open` works on Windows again** through the shared opener (`rundll32 url.dll,FileProtocolHandler`, which hands the target over without a shell).
26
+ - **`jh preview` renders long documents and answers `--help`.** The Markdown went to `gh api /markdown` as an argument and failed with `E2BIG` for a long plan; it goes through standard input now. `jh preview --help` (and `-h`) prints the usage and exits 0.
27
+ - **A question or a waiting agent no longer sinks in the coordinator's pane.** `jh herd` shows a subagent of the coordinator that builds in a worktree as `working` (and as `blocked` while the approval gate holds a request of it), also when the worktree has no row in the board; the row carries the subagent's description and the branch. The approval gate holds a permission request of such a subagent like a lane's, under the name `subagent-<type>-<id>`, so the watcher reports `approval …` instead of the dialog sitting in the coordinator's pane. The heartbeat and each status line name what a resting (`idle`, `done`) lane waits on, from the questions in its report: `a idle: waits on: <first question>, 2 open questions`.
28
+ - **What waits on you stays in sight.** A board section `## Wartet auf dich` (German) or `## Waiting for you` (English), one row `Seit | Punkt | Herkunft | Stand`, lists a preview sent, a question put to you or a report to accept. `jh herd` shows every open row on top as one line per item (a dim `▌`, then `📎 file` or `? question` in bold, `▌ +n` after five), closed by a rule labelled `woof`, and the watcher repeats them in each heartbeat and status until the row is closed in its status cell. `/orchestrate` says when Jack enters and closes a row.
29
+ - **The README is shorter and keeps each point once.** The section "Human on the loop" is gone; what it said sits where it belongs, in the intro (you decide, Jack asks only what the plan does not settle), in "What Jack does on his own, and what waits for you" and in "Guarded commands and merges", which now also names the refusal of a push to the base branch and the limit of the guards. Planning and building are told apart from the parts that need herdr, the quickstart is five bare steps, and the lines on lanes, boards, worktrees, the watcher and the fresh start say what they mean. `jh preview` is in the command list.
30
+ - **The feature icons load from a marked address.** Every icon in the README is `…/assets/icons/<name>.svg?v=2`. GitHub's image proxy keeps an unmarked address for a year, so npm kept showing the 1.8.0 icons, a few pixels too high. A test holds the mark in one place (`assets/icons/version.ts`) and compares the icon files with stored digests, so a changed icon without a new mark turns red.
31
+ - **`jh preview <markdown-file>`** renders a Markdown file through the GitHub API into a page that shows it in GitHub and npm style, light and dark, and at phone width (`--out`, `--open`).
32
+ - **`jh herd` shows a small fix as active.** It listed a lane as "no agent" when the board held a cleaned-up row and a newer open row of the same name, or when a cleaned-up lane had uncommitted changes in its worktree. A newer row without the mark now reopens the lane, and uncommitted changes make it active.
33
+ - **An open lane in `jh herd` keeps its tree.** Its pull requests hang on the tree as `PR`, behind the SESSION line, the chips row is labelled `LINKS`, and a pull request title no longer repeats the lane's ticket key.
34
+
5
35
  ## 1.8.1
6
36
 
7
37
  - **The README reads on npm in a dark browser.** npm wraps every image in a link, so a `<picture>` never switched to its dark source, and npm has no dark theme of its own; a phone in dark mode showed the light logo on black. Every image is now one version that works on white and on black: the logo is the dark one with a light outline around Jack, the letters, the lanes and the nodes (`assets/branding/jackherd-logo-outline.svg`), the terminal diagrams are always the dark ones, and the feature icons are mid-grey and teal. Each of the six points has its icon on the line of the bold title and its text below, so nothing wraps under an icon on a phone or on a desktop and the gaps are equal.
package/INSTALL.md CHANGED
@@ -69,6 +69,8 @@ bunx jackherd sync # later: updates the kit files
69
69
 
70
70
  `init` writes what it [detected](#detected-per-repository) into the new config, so the project sees it. It asks whether to enter the status line (README, *Status line*) in `.claude/settings.json`; `--yes` or `--no` answer every question without asking.
71
71
 
72
+ **Everyone on the team needs Bun.** The project hooks run as `bun .claude/hooks/…`, and without Bun the guard hooks block the commands they check.
73
+
72
74
  **Name clashes.** A project that already has a skill or agent of a kit name (`implement`, `plan`, …): `init` names the clash and offers the prefix `jh-`, so the kit's copies sit beside yours and call each other, not yours. Any project can set `skillPrefix` in `.jackherd/config.json`; `sync` then renames, and removes the unprefixed kit files you never edited, with the directories they leave empty. A directory that holds a file of yours stays, and `sync` names it.
73
75
 
74
76
  **Your edits survive.** `sync` leaves files you have edited alone. When the kit changed one of them too, its version lands beside yours as `<file>.kit-new`: merge it, or take the kit version with `sync --accept <path>`. A kit file you deleted stays deleted. Project-specific additions go into `.claude/project/<skill>.md` (read first by the skill of that name) or a rule beside `.claude/rules/kit/`, so the kit files stay updatable.
package/README.md CHANGED
@@ -3,35 +3,35 @@
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/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/assets/icons/space.svg?v=2">
7
7
 
8
- **Let AI do the work. Stay in control.** Build several features in parallel with Claude Code. Each runs in its own branch; you only answer the questions that need a human. Jack, the coordinator (the Shih Tzu in the logo, who herds anyway), handles the rest. In a project install, hooks hold merges and releases that touch risky paths until you approve, and refuse a push to your base branch.
8
+ # Your agents write code. Jack keeps the work moving.
9
9
 
10
- Install with one command, run `/jh-start` in a repository, and it offers your first plan: [Quickstart](#quickstart).<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
10
+ **Let AI do the work. Stay in control.** jackherd is a coordination layer for your coding agents, built around a plan in your repository. Claude Code alone builds it one phase at a time; with [herdr](https://herdr.dev), a terminal for coding agents, phases run in parallel, each in its own git worktree. Jack, the coordinator, keeps these lanes moving: he answers what the plan already settles and brings you only the decisions that need you.
11
+
12
+ Install with one command, run `/jh-start` in a repository, and it offers your first plan: [Quickstart](#quickstart).<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg?v=2">
11
13
 
12
14
  <p>
13
15
  <img src="https://unpkg.com/jackherd/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.">
14
16
  <br>
15
- <sub>Parallel mode in herdr (optional). Planning and building work without it.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
17
+ <sub>Parallel lanes in herdr. Without herdr, you plan and build one phase at a time.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg?v=2">
16
18
  </p>
17
19
 
18
- <p><img src="https://unpkg.com/jackherd/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Plans live in your repository.</b><br>Markdown phases, a model each, ticked off as they are built.</p>
20
+ <p><img src="https://unpkg.com/jackherd/assets/icons/decide.svg?v=2" width="24" height="34" align="middle" alt=""> <b>You decide. Jack takes care of the lanes.</b><br>Jack answers what the plan already settles, records his decisions on the board, and asks you about the rest. One approval per pull request covers its commits and pushes. Jack merges automatically only when you've allowed it and checks pass.</p>
19
21
 
20
- <p><img src="https://unpkg.com/jackherd/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Lanes stay apart.</b><br>A lane is one phase of a plan in its own git worktree and session, side by side in herdr, the terminal that holds the parallel sessions.</p>
22
+ <p><img src="https://unpkg.com/jackherd/assets/icons/lanes.svg?v=2" width="24" height="34" align="middle" alt=""> <b>Lanes stay apart.</b><br>One phase per lane, on its own branch and worktree. Jack keeps the board's merge order and has the other lanes rebase after each merge.</p>
21
23
 
22
- <p><img src="https://unpkg.com/jackherd/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You decide. Jack takes care of the lanes.</b><br>He answers what the plan settles and asks you the rest.</p>
24
+ <p><img src="https://unpkg.com/jackherd/assets/icons/bark.svg?v=2" width="24" height="34" align="middle" alt=""> <b>Jack barks when something moves.</b><br>One live view of the herd, all your lanes: state, plan progress, pull request, checks, feedback, context fill.</p>
23
25
 
24
- <p><img src="https://unpkg.com/jackherd/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks when something moves.</b><br>One live view of the herd, all your lanes: state, pull request, checks, feedback.</p>
26
+ <p><img src="https://unpkg.com/jackherd/assets/icons/ear.svg?v=2" width="24" height="34" align="middle" alt=""> <b>Jack listens beyond GitHub.</b><br><code>/jh-follow</code> Jira, Linear, Sentry or a status page, and Jack reports changes while he runs in herdr.</p>
25
27
 
26
- <p><img src="https://unpkg.com/jackherd/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens beyond GitHub.</b><br><code>/jh-follow</code> Jira, Linear, Sentry or a status page. Reports need herdr.</p>
28
+ <p><img src="https://unpkg.com/jackherd/assets/icons/plan.svg?v=2" width="24" height="34" align="middle" alt=""> <b>One workflow for the whole team.</b><br>After <code>jh init</code>, plans, skills, rules and guard hooks are files in your repository. Commit them, and everyone who opens it in Claude Code works with the same set; changes to it go through your usual pull request review.</p>
27
29
 
28
- <p><img src="https://unpkg.com/jackherd/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Long sessions get a fresh start.</b><br>At a phase boundary, a lane whose context is filling up moves to a new chat.</p>
30
+ <p><img src="https://unpkg.com/jackherd/assets/icons/bowl.svg?v=2" width="24" height="34" align="middle" alt=""> <b>Long chats get a fresh start.</b><br>A lane whose context fills up continues in a new chat at the end of its phase, from the ticked-off plan and a hand-over note.</p>
29
31
 
30
32
  ## Quickstart
31
33
 
32
- Planning and building work without herdr; parallel lanes need it.
33
-
34
- 1. **Install** (Linux and macOS; needs Claude Code and git). It asks a few setup questions and leaves your repositories alone. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)
34
+ 1. **Install** (Linux and macOS; needs Claude Code and git). → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)
35
35
 
36
36
  ```bash
37
37
  curl -fsSL https://unpkg.com/jackherd/install.sh | sh
@@ -42,22 +42,22 @@ Planning and building work without herdr; parallel lanes need it.
42
42
  ```text
43
43
  Install jackherd by following https://unpkg.com/jackherd/INSTALL.md. Ask me the setup questions it lists; herdr is optional.
44
44
  ```
45
- 2. **Start Claude Code in a repository.** It checks the setup and offers your first plan.
45
+ 2. **Start Claude Code in a repository.**
46
46
 
47
47
  ```text
48
48
  /jh-start
49
49
  ```
50
- 3. **Plan.** Phases, tasks and a model for each, written to `docs/plans/`.
50
+ 3. **Create your first plan.** It lands in `docs/plans/`, and for parallel lanes `/jh-plan` offers to write a board.
51
51
 
52
52
  ```text
53
53
  /jh-plan Add product search
54
54
  ```
55
- 4. **Build,** one phase at a time, ticked off in the plan. No herdr needed.
55
+ 4. **Build the first phase.**
56
56
 
57
57
  ```text
58
58
  /jh-implement docs/plans/<plan>.md phase 1
59
59
  ```
60
- 5. **Or let Jack run the herd.** Inside [herdr](https://herdr.dev), one lane per phase, with the live herd view beside him (`jh herd` anywhere else).
60
+ 5. **Run phases in parallel with herdr (optional).** In Claude Code inside herdr, hand Jack the board:
61
61
 
62
62
  ```text
63
63
  /jh-orchestrate docs/plans/board-<name>/board.md
@@ -65,9 +65,7 @@ Planning and building work without herdr; parallel lanes need it.
65
65
 
66
66
  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)
67
67
 
68
- ## Human on the loop
69
-
70
- You decide every open question, commit and push, once per pull request. Jack answers on his own what the plan already settles, never money, auth, secrets, a migration or a release. Once you've approved a plan and a pull request, he merges it on green checks and tells you in one line. The guards catch mistakes, not an agent set on getting around them; only branch protection stops that. A pull request that touches a workflow, a migration, a lockfile or `package.json` still asks, and so does every release. Prefer to see every merge? Approve per pull request instead.
68
+ ## In detail
71
69
 
72
70
  <details>
73
71
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
@@ -87,7 +85,7 @@ You decide every open question, commit and push, once per pull request. Jack ans
87
85
  <details>
88
86
  <summary><b>Guarded commands and merges</b></summary>
89
87
 
90
- In a project install, the `merge-guard` hook asks you before `gh pr merge`, `gh workflow run`, `gh run rerun` and a `gh api` write that merges, writes a file, dispatches or reruns a workflow, or moves a ref, also behind a chain, a wrapper or `sh -c`. The `git-guard` hook refuses a git command that hides a commit, push, rebase, reset, checkout or merge from your permission rules (`git -C … push`, `sh -c "…"`), and every force push, skipped hook and hard reset.
88
+ In a project install, the `merge-guard` hook asks you before `gh pr merge`, `gh workflow run`, `gh run rerun` and a `gh api` write that merges, writes a file, dispatches or reruns a workflow, or moves a ref, also behind a chain, a wrapper or `sh -c`. The `git-guard` hook refuses a git command that hides a commit, push, rebase, reset, checkout or merge from your permission rules (`git -C … push`, `sh -c "…"`), every push to the base branch, and every force push, skipped hook and hard reset.
91
89
 
92
90
  A merge passes without a question only on an answer from your own dialog: self-merge for that pull request, or the approval of its plan, with green checks and no file on a path that always asks (workflows, migrations, lockfiles, `package.json`, env files, agent instructions, and your own list). `merge.withoutChecks` lets a pull request that only touches such paths as docs merge without waiting for checks.
93
91
 
@@ -98,16 +96,14 @@ A merge passes without a question only on an answer from your own dialog: self-m
98
96
  }
99
97
  ```
100
98
 
101
- A global install ships no hooks, and the skills ask on their own.
99
+ The guards catch an agent's mistakes, not an agent set on getting around them; for that, use branch protection. A global install ships no hooks, and the skills ask on their own.
102
100
 
103
101
  </details>
104
102
 
105
- ## In detail
106
-
107
103
  <details>
108
104
  <summary><b>Plans, boards and the skills</b></summary>
109
105
 
110
- A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes: which phases each one works on, on which model, after what. A **lane** is one agent session in its own git worktree, and **Jack** is the session in the main checkout that runs `/jh-orchestrate`.
106
+ 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.
111
107
 
112
108
  <img src="https://unpkg.com/jackherd/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.">
113
109
 
@@ -136,7 +132,7 @@ Each phase in the plan names its model: the planning model (Opus by default) whe
136
132
  <details>
137
133
  <summary><b>The watcher and tickets outside GitHub</b></summary>
138
134
 
139
- Jack doesn't watch the terminals himself. `jh watch`, which he starts himself, wakes him when a lane is blocked or done, a check turns red, a pull request gets feedback, a lane's context fills up or a lane waits for an approval; deploys and previews too, when configured.
135
+ Jack doesn't watch the terminals himself. `jh watch`, which he starts, wakes him when a lane is blocked or done, a check turns red, a pull request gets feedback, a lane's context fills up or a lane waits for an approval; deploys and previews too, when configured.
140
136
 
141
137
  `/jh-follow PROJ-123` adds a ticket, an issue or a status page: it checks the tool and its login, shows you a dry run and writes the source to `watch.sources` after your yes. The [recipes](https://unpkg.com/jackherd/recipes/README.md) cover Jira, Linear, GitHub issues and Projects, Sentry and status pages. The reports need herdr; a source can be set up and dry-run without it.
142
138
 
@@ -150,26 +146,21 @@ Jack doesn't watch the terminals himself. `jh watch`, which he starts himself, w
150
146
  ```text
151
147
  🐾 Board 01 11:38
152
148
  ────────────────────────────────────────────────────────────────────────────────
153
- ▾ ◆ search-ui blocked 🥣 62 % │ ██████░░░░ ◐ 6/9
154
- ⎇ feat/search-ui ↑1 ±4
155
- │
156
- └─ Product search
157
- ├─ 📎 📄 Plan 📡 PROJ-123 · In Progress ✦ Mockup ◈ Search box
158
- │
159
- ├─ ✓ P2 Search box 4/4
160
- ├─ ▸ P3 Docs docs open
161
- │ └─ ▶ Docs updated (README)
162
- └─ ▸ P4 Review 0/2
149
+ ▾ ◆ search-ui blocked ██████░░░░ 6/9
150
+ ├─ Search box docs ⎇ feat/search-ui ↑1 ±4 🥣 62 %
151
+ ├─ 📄 Plan 📡 PROJ-123 · In Progress ✦ Mockup ◈ Search box
152
+ ├─ P3 Docs · 4 min ago · gate 2/3
153
+ │ ▶ Review gate
154
+ ├─ next P4
155
+ └─ ✓ P2
163
156
  ────────────────────────────────────────────────────────────────────────────────
164
- ▸ ◉ checkout-fee done ↗ #42 💬 2 🥣 48 % │ ██████████ ◐ 6/6
157
+ ▸ ◉ checkout-fee ↗ #42 💬 2 ██████████ 6/6
165
158
  ────────────────────────────────────────────────────────────────────────────────
166
- ▾ ● search-index working 🥣 34 % │ ████████░░ ◐ 4/5
167
- ⎇ feat/search-index ↑3 ±2
168
- ↗ #41 … checks 🌐 preview 💬 1 new Search index
169
- │
170
- └─ Product search
171
- └─ ▸ P1 Index 4/5
172
- └─ ▶ /jh-uncle-bob
159
+ ▾ ● search-index ████████░░ 4/5
160
+ ├─ Search index ⎇ feat/search-index ↑3 ±2 🥣 34 %
161
+ ├─ ↗ #41 … checks 🌐 preview 💬 1 new Search index
162
+ └─ P1 Index · just now 4/5
163
+ ▶ /jh-uncle-bob
173
164
  ────────────────────────────────────────────────────────────────────────────────
174
165
  · no agent (1) docs-site 1/3
175
166
  ────────────────────────────────────────────────────────────────────────────────
@@ -189,6 +180,8 @@ Jack doesn't watch the terminals himself. `jh watch`, which he starts himself, w
189
180
  | `jh check` | types, lint and tests of what you touched, plus the plan and board checks (`--skip-slow`, `--parallel <plan>`) |
190
181
  | `jh herd` | the live view (`--once`, `--lane <name>`) |
191
182
  | `jh watch` | the watcher Jack runs |
183
+ | `jh preview <file>` | renders a Markdown file through the GitHub API into a preview in GitHub and npm style |
184
+ | `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>…` or `close --all` once you have) |
192
185
  | `jh context` | how full an agent's context is, as JSON |
193
186
  | `jh config` | the commands and the base branch the repository uses |
194
187
 
@@ -45,6 +45,20 @@ var APPOINTMENT_VOCABULARY = {
45
45
  open: ["open"]
46
46
  }
47
47
  };
48
+ var WAITING_VOCABULARY = {
49
+ [PLAN_LANGUAGE.de]: {
50
+ heading: "Wartet auf dich",
51
+ columns: { since: "Seit", item: "Punkt", origin: "Herkunft", status: "Stand" },
52
+ statusAlso: ["Status"],
53
+ open: ["offen"]
54
+ },
55
+ [PLAN_LANGUAGE.en]: {
56
+ heading: "Waiting for you",
57
+ columns: { since: "Since", item: "Item", origin: "Origin", status: "Status" },
58
+ statusAlso: [],
59
+ open: ["open"]
60
+ }
61
+ };
48
62
  var isPlanLanguage = (value) => Object.values(PLAN_LANGUAGE).includes(value);
49
63
  var PLAN_STATUS = {
50
64
  done: "done",
@@ -145,6 +159,7 @@ export {
145
159
  PLAN_APPROVAL_LABELS,
146
160
  APPROVAL_TEXTS,
147
161
  APPOINTMENT_VOCABULARY,
162
+ WAITING_VOCABULARY,
148
163
  isPlanLanguage,
149
164
  PLAN_STATUS,
150
165
  PLAN_VOCABULARY,
package/dist/config.d.ts CHANGED
@@ -90,6 +90,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
90
90
  herdr: "herdr";
91
91
  }>>;
92
92
  }, z.core.$strict>>;
93
+ preview: z.ZodPrefault<z.ZodObject<{
94
+ publish: z.ZodDefault<z.ZodBoolean>;
95
+ }, z.core.$strict>>;
93
96
  plans: z.ZodPrefault<z.ZodObject<{
94
97
  fetchIntervalMinutes: z.ZodDefault<z.ZodNumber>;
95
98
  linkKinds: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -120,6 +123,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
120
123
  checks: z.ZodPrefault<z.ZodObject<{
121
124
  failedTestPattern: z.ZodDefault<z.ZodString>;
122
125
  }, z.core.$strict>>;
126
+ parallelTasks: z.ZodPrefault<z.ZodObject<{
127
+ maxTokens: z.ZodDefault<z.ZodNumber>;
128
+ }, z.core.$strict>>;
123
129
  previews: z.ZodOptional<z.ZodObject<{
124
130
  deployments: z.ZodDefault<z.ZodBoolean>;
125
131
  authors: z.ZodDefault<z.ZodArray<z.ZodString>>;
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PLAN_LANGUAGE,
3
3
  RESPONSE_ADDRESS
4
- } from "./chunk-OU2KALXP.js";
4
+ } from "./chunk-MCP4XJQH.js";
5
5
 
6
6
  // src/config/json-schema.ts
7
7
  import { z as z3 } from "zod";
@@ -313,6 +313,15 @@ var feedbackSchema = z2.strictObject({
313
313
  */
314
314
  noFindingTemplates: z2.array(z2.string().min(1)).default([CLEAN_REVIEW_TEXT])
315
315
  });
316
+ var DEFAULT_PARALLEL_TASKS_MAX_TOKENS = 1e5;
317
+ var parallelTasksSchema = z2.strictObject({
318
+ /**
319
+ * The most the extra agents of a parallel proposal may cost before they do anything: the token baseline of a fresh
320
+ * agent times the agents to add. Above it the watcher does not propose parallel tasks. The baseline is the context
321
+ * the first answer of a lane's session carried.
322
+ */
323
+ maxTokens: z2.number().int().positive().default(DEFAULT_PARALLEL_TASKS_MAX_TOKENS)
324
+ });
316
325
  var checksSchema = z2.strictObject({
317
326
  /**
318
327
  * Finds a failed test in a line of a red run's log (`gh run view --log-failed`) after its job, step and timestamp
@@ -397,9 +406,9 @@ var sourceSchema = z2.strictObject({
397
406
  var hasUniqueNames = (sources) => new Set(sources.map(({ name }) => name)).size === sources.length;
398
407
  var coordinatorSchema = z2.strictObject({
399
408
  /** Tokens in the coordinator's context from which the watcher reports `coordinator warn`. */
400
- warnTokens: tokenCount.default(2e5),
409
+ warnTokens: tokenCount.default(35e4),
401
410
  /** Tokens in the coordinator's context from which the watcher reports `coordinator alert`. */
402
- alertTokens: tokenCount.default(3e5)
411
+ alertTokens: tokenCount.default(5e5)
403
412
  }).refine(warnBelowAlert, WARN_BELOW_ALERT);
404
413
  var guardedCommand = z2.string().regex(/^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*){1,3}$/, "a program and one to three plain words, such as gh pr merge");
405
414
  var checkFreePattern = z2.string().regex(/^[A-Za-z0-9_.*/-]+$/, "path words with * and ** only, such as docs/**/*.md").refine(
@@ -517,6 +526,11 @@ var jackherdConfigSchema = z2.strictObject({
517
526
  }).prefault({}),
518
527
  models: z2.record(z2.enum(Object.values(MODEL_ROLE)), modelId),
519
528
  terminal: z2.strictObject({ adapter: z2.enum(Object.values(TERMINAL_ADAPTER)).default(TERMINAL_ADAPTER.none) }).prefault({}),
529
+ /** How the coordinator shows a draft for review (`orchestrate` § 4). */
530
+ preview: z2.strictObject({
531
+ /** Publish the draft as a private Artifact when the session has the tool; `false` sends a file card or `jh preview --open` instead. */
532
+ publish: z2.boolean().default(true)
533
+ }).prefault({}),
520
534
  plans: z2.strictObject({
521
535
  /** Minutes between background fetches of the base branch while `jh herd` runs; 0 turns them off. */
522
536
  fetchIntervalMinutes: z2.number().min(0).default(5),
@@ -533,6 +547,7 @@ var jackherdConfigSchema = z2.strictObject({
533
547
  /**
534
548
  * Read by `jh watch`; without `deploy` it reports no deploy runs. `feedback` is on unless set to `false`:
535
549
  * it reports new feedback on the lanes' pull requests except from the account the watcher runs as and `ignore`.
550
+ * `parallelTasks.maxTokens` caps the cost of the extra agents in a proposal of parallel tasks at the heartbeat.
536
551
  * `checks` reads the failed tests of a red lane pull request (`failedTestPattern`), also with `feedback` off.
537
552
  * Without `previews` it reads no deploy preview; with it, one `preview` per new head commit. Each of `sources` is a
538
553
  * command whose JSON output reports `source` for a new or changed entry.
@@ -541,6 +556,7 @@ var jackherdConfigSchema = z2.strictObject({
541
556
  deploy: deploySchema.optional(),
542
557
  feedback: z2.union([z2.literal(false), feedbackSchema]).prefault({}),
543
558
  checks: checksSchema.prefault({}),
559
+ parallelTasks: parallelTasksSchema.prefault({}),
544
560
  previews: previewsSchema.optional(),
545
561
  sources: z2.array(sourceSchema).refine(hasUniqueNames, "source names must be unique").default([])
546
562
  }).prefault({}),
@@ -59,6 +59,22 @@ declare const APPOINTMENT_VOCABULARY: Record<PlanLanguage, {
59
59
  statusAlso: readonly string[];
60
60
  open: readonly string[];
61
61
  }>;
62
+ /**
63
+ * The board section of what waits on the user's answer (a sent preview, an open question, a report to accept): its
64
+ * heading, the header names of its columns (since, item, origin, status, found by name in any order; the status
65
+ * column may also carry a name of `statusAlso`) and the lower-case words a status cell of a still open item starts with.
66
+ */
67
+ declare const WAITING_VOCABULARY: Record<PlanLanguage, {
68
+ heading: string;
69
+ columns: {
70
+ since: string;
71
+ item: string;
72
+ origin: string;
73
+ status: string;
74
+ };
75
+ statusAlso: readonly string[];
76
+ open: readonly string[];
77
+ }>;
62
78
  declare const isPlanLanguage: (value: string) => value is PlanLanguage;
63
79
  declare const PLAN_STATUS: {
64
80
  readonly done: "done";
@@ -348,7 +364,8 @@ declare const withoutRepeats: (lane: SprintLane, step: PlanStep) => PlanStep;
348
364
  /**
349
365
  * The lanes of the sprint document's lanes table (`## Spuren` in `de`) in table order. A row whose
350
366
  * first cell names a lane in backticks belongs to that lane; a `↳` row without a name continues
351
- * the lane above it.
367
+ * the lane above it. A cleaned-up lane stays closed through its `↳` rows, but a later row that names it again
368
+ * without the mark starts the lane anew: the closed rows are history and drop out.
352
369
  */
353
370
  declare const parseSprintLanes: (markdown: string, language: PlanLanguage) => SprintLane[];
354
371
  declare const sprintTitleOf: (markdown: string, fallback: string) => string;
@@ -512,4 +529,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
512
529
  */
513
530
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
514
531
 
515
- export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANGUAGE_NAME, type LaneProgress, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PLAN_APPROVAL_LABELS, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, 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, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
532
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANGUAGE_NAME, type LaneProgress, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PLAN_APPROVAL_LABELS, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, 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, 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, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -9,11 +9,12 @@ import {
9
9
  PLAN_VOCABULARY,
10
10
  RESPONSE_ADDRESS,
11
11
  SELF_MERGE_LABEL,
12
+ WAITING_VOCABULARY,
12
13
  classifyStatus,
13
14
  isPlanLanguage,
14
15
  phaseHeading,
15
16
  sectionHeading
16
- } from "./chunk-OU2KALXP.js";
17
+ } from "./chunk-MCP4XJQH.js";
17
18
 
18
19
  // src/plan-format/lanes.ts
19
20
  import { posix } from "path";
@@ -100,6 +101,8 @@ var CONTINUATION = "\u21B3";
100
101
  var PLAN_LINK = /\[([^\]]+)\]\(([^)]+)\)/;
101
102
  var PHASE_ID_PREFIX = "P";
102
103
  var phaseLabel = (id) => `${PHASE_ID_PREFIX}${id}`;
104
+ var MAX_RANGE = 50;
105
+ var spannedPhaseIds = (first, last) => first > last || last - first >= MAX_RANGE ? [] : Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
103
106
  var phaseLabels = (ids, separator) => ids.map(phaseLabel).join(separator);
104
107
  var PHASES_COLUMN = 3;
105
108
  var CLAUSE_SEPARATOR = /([;,]|\.(?=\s))\s*/;
@@ -109,10 +112,18 @@ var phaseIdPatterns = (language) => {
109
112
  const id = `(?:\\*\\*)?${PHASE_ID_PREFIX}(\\d+[a-z]?)\\b`;
110
113
  return {
111
114
  leading: new RegExp(`^\\s*(?:(?:${connectors})\\s+)*${id}`),
115
+ rangeEnd: new RegExp(`^\\s*[\u2013-]\\s*(?:\\*\\*)?${PHASE_ID_PREFIX}(\\d+)\\b`),
112
116
  afterConnector: new RegExp(`^\\s*(?:${connectors})\\s`),
113
117
  connector: new RegExp(`\\b(?:${connectors})\\s+${id}`, "g")
114
118
  };
115
119
  };
120
+ var leadingIds = (clause, leading, patterns) => {
121
+ const first = leading[1];
122
+ const end = clause.slice(leading[0].length).match(patterns.rangeEnd);
123
+ if (!end || !/^\d+$/.test(first)) return [first];
124
+ const spanned = spannedPhaseIds(Number(first), Number(end[1]));
125
+ return spanned.length > 0 ? spanned : [first];
126
+ };
116
127
  var phaseIdsOf = (cell, patterns) => {
117
128
  const parts = cell.split(CLAUSE_SEPARATOR);
118
129
  const ids = [];
@@ -125,7 +136,7 @@ var phaseIdsOf = (cell, patterns) => {
125
136
  const leading = clause.match(patterns.leading);
126
137
  const startsEntry = entryStart || listRunning || patterns.afterConnector.test(clause);
127
138
  const counts = leading !== null && startsEntry;
128
- if (leading && counts) ids.push(leading[1]);
139
+ if (leading && counts) ids.push(...leadingIds(clause, leading, patterns));
129
140
  ids.push(...[...clause.matchAll(patterns.connector)].map((match) => match[1]));
130
141
  listRunning ||= counts;
131
142
  }
@@ -162,7 +173,12 @@ var parseSprintLanes = (markdown, language) => {
162
173
  if (!name) continue;
163
174
  const port = Number.parseInt(cells[1] ?? "", 10);
164
175
  const lane = lanes.get(name) ?? { name, port: Number.isNaN(port) ? null : port, steps: [], closed: false };
165
- lane.closed ||= (cells[0] ?? "").includes(laneClosed);
176
+ const marked = (cells[0] ?? "").includes(laneClosed);
177
+ if (lane.closed && !marked && !(cells[0] ?? "").startsWith(CONTINUATION)) {
178
+ lane.closed = false;
179
+ lane.steps = [];
180
+ }
181
+ lane.closed ||= marked;
166
182
  lane.steps.push(withoutRepeats(lane, toStep(cells, phaseId)));
167
183
  lanes.set(name, lane);
168
184
  previous = name;
@@ -451,7 +467,6 @@ var GLOB_CHARACTER = /[*?{[]/;
451
467
  var VERSION = /^v?\d+(?:[.\-+][\w.-]*)?$/i;
452
468
  var PACKAGE_SCOPE = /^@[^/]+\//;
453
469
  var RELEASE_FILE = /(?:^|\/)(?:CHANGELOG\.md|package\.json)$/;
454
- var MAX_RANGE = 50;
455
470
  var phaseIdOf = (cell) => PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
456
471
  var splitOutsideParentheses = (cell, separator) => {
457
472
  const entries = [];
@@ -470,9 +485,7 @@ var splitOutsideParentheses = (cell, separator) => {
470
485
  var rangeOf = (entry) => {
471
486
  const match = PHASE_RANGE.exec(entry);
472
487
  if (!match) return null;
473
- const [first, last] = [Number(match[1]), Number(match[2])];
474
- if (first > last || last - first >= MAX_RANGE) return [];
475
- return Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
488
+ return spannedPhaseIds(Number(match[1]), Number(match[2]));
476
489
  };
477
490
  var dependsOf = (cell, language) => {
478
491
  const noDependency = PLAN_VOCABULARY[language].noDependency;
@@ -863,6 +876,7 @@ export {
863
876
  PROSE_REASON,
864
877
  RESPONSE_ADDRESS,
865
878
  SELF_MERGE_LABEL,
879
+ WAITING_VOCABULARY,
866
880
  activeItem,
867
881
  boardRowsWithProse,
868
882
  boardRowsWithoutPhase,
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.8.1"
3
+ version = "1.9.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"]
package/install.sh CHANGED
@@ -9,7 +9,7 @@ set -eu
9
9
 
10
10
  # The default is the version this file ships with. The release bump changes it together with
11
11
  # package.json; install.test.ts fails while the two differ. Overridable, but only by a plain release.
12
- JACKHERD_VERSION="${JACKHERD_VERSION:-1.8.1}"
12
+ JACKHERD_VERSION="${JACKHERD_VERSION:-1.9.0}"
13
13
 
14
14
  JACKHERD_PACKAGE='jackherd'
15
15
  # Test seam: the terminal the wizard asks its questions on.
@@ -1,14 +1,17 @@
1
1
  // PermissionRequest hook of Claude Code, shipped by jackherd: the client adapter of the approval gate. In a lane, known
2
2
  // by the herdr agent of the pane and not by the directory of the call, it holds a call that would open a permission
3
- // dialog, records it for the coordinator and answers with the user's decision from the coordinator's dialog; elsewhere,
4
- // and on any failure, it answers nothing and the dialog stays. argv[2..5]: the allow label, the deny label, the text for
3
+ // dialog, records it for the coordinator and answers with the user's decision from the coordinator's dialog; the same for
4
+ // a subagent of the coordinator, under its own name; elsewhere, and on any failure, it answers nothing and the dialog stays. argv[2..5]: the allow label, the deny label, the text for
5
5
  // a call still pending after the wait, the text for a denied call, rendered into the hook entry by `sync`.
6
+ import { existsSync } from 'node:fs'
6
7
  import { homedir } from 'node:os'
8
+ import { join } from 'node:path'
7
9
  import { mainCheckoutOf } from '../shared/checkout'
8
10
  import { commandOutput } from '../shared/command'
9
11
  import { readConfig } from '../shared/config'
10
12
  import { isRecord } from '../shared/json'
11
13
  import { ownPaneAgent } from '../shared/own-pane'
14
+ import { gateSettingsOf } from './settings'
12
15
  import { coordinatorSessions } from './herdr-panes'
13
16
  import { type ClientCall, type GateDependencies, type GateOutcome, runGate } from './run'
14
17
  import { approvalsDirectory, claimRequest, pruneRequests, readRequests, writeRequest } from './store'
@@ -23,17 +26,32 @@ const herdrPanes = (): unknown => {
23
26
  return answer.result
24
27
  }
25
28
 
26
- /** Claude Code's hook input: `cwd`, `session_id`, `tool_name`, `tool_input`. */
29
+ /** The subagent a call comes from: Claude Code adds `agent_id` and `agent_type` only to the hooks of a subagent. */
30
+ const subagentOf = (input: Record<string, unknown>): ClientCall['subagent'] =>
31
+ typeof input.agent_id === 'string'
32
+ ? { id: input.agent_id, type: typeof input.agent_type === 'string' ? input.agent_type : null }
33
+ : undefined
34
+
35
+ /** Claude Code's hook input: `cwd`, `session_id`, `tool_name`, `tool_input`, and `agent_id` inside a subagent. */
27
36
  const clientCallOf = (text: string): ClientCall | null => {
28
37
  const input: unknown = JSON.parse(text)
29
38
  if (!isRecord(input) || typeof input.cwd !== 'string' || typeof input.tool_name !== 'string') return null
30
39
  if (typeof input.session_id !== 'string') return null
31
- return { cwd: input.cwd, session: input.session_id, tool: input.tool_name, input: input.tool_input ?? null }
40
+ const subagent = subagentOf(input)
41
+ return {
42
+ cwd: input.cwd,
43
+ session: input.session_id,
44
+ tool: input.tool_name,
45
+ input: input.tool_input ?? null,
46
+ ...(subagent ? { subagent } : {}),
47
+ }
32
48
  }
33
49
 
34
50
  const dependencies: GateDependencies = {
35
51
  paneAgent: () => ownPaneAgent(),
36
52
  mainCheckoutOf,
53
+ isLaneName: (main, name) =>
54
+ existsSync(join(main, gateSettingsOf(readConfig([main, homedir()])).worktreeDirectory, name)),
37
55
  config: (main) => readConfig([main, homedir()]),
38
56
  readRequests: (main) => readRequests(approvalsDirectory(main)),
39
57
  writeRequest: (main, request) => writeRequest(approvalsDirectory(main), request),