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.
- package/CHANGELOG.md +30 -0
- package/INSTALL.md +2 -0
- package/README.md +37 -44
- package/dist/{chunk-OU2KALXP.js → chunk-MCP4XJQH.js} +15 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +19 -3
- package/dist/plan-format.d.ts +19 -2
- package/dist/plan-format.js +21 -7
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/hooks/approval-gate/index.ts +22 -4
- package/kit/hooks/hooks/approval-gate/run.ts +29 -6
- package/kit/hooks/hooks/approval-gate/subagent.ts +18 -0
- package/kit/hooks/hooks/merge-guard/decision.ts +67 -20
- package/kit/hooks/hooks/sent-file/index.ts +19 -0
- package/kit/hooks/hooks/sent-file/record.ts +128 -0
- package/kit/hooks/hooks/sent-file/run.ts +48 -0
- package/kit/hooks/hooks/sent-file/store.ts +18 -0
- package/kit/hooks/hooks/shared/board-lanes.ts +15 -2
- package/kit/hooks/hooks.json +9 -0
- package/kit/hooks/statusline.sh +1 -1
- package/kit/workflow/skills/implement/SKILL.md +2 -0
- package/kit/workflow/skills/orchestrate/SKILL.md +30 -6
- package/package.json +2 -1
- package/schemas/jackherd.schema.json +26 -2
- package/src/cli.ts +3 -0
- package/src/config/schema.ts +22 -2
- package/src/context/fill.ts +12 -0
- package/src/context/session-log.ts +43 -1
- package/src/plan-format/clock.ts +4 -0
- package/src/plan-format/dependencies.ts +2 -5
- package/src/plan-format/lanes.ts +30 -5
- package/src/plan-format/terminal-text.ts +4 -0
- package/src/plan-format/waiting.ts +48 -0
- package/src/plans/active-tasks.ts +60 -0
- package/src/plans/current-phase.ts +17 -7
- package/src/plans/current-task.ts +63 -5
- package/src/plans/lane-standing.ts +9 -4
- package/src/plans/lane-summary.ts +141 -74
- package/src/plans/lane-tickets.ts +15 -0
- package/src/plans/layout-phase.ts +57 -18
- package/src/plans/layout-summary.ts +85 -67
- package/src/plans/layout.ts +253 -89
- package/src/plans/live.ts +22 -0
- package/src/plans/phase-models.ts +1 -1
- package/src/plans/phase-ranges.ts +45 -0
- package/src/plans/pull-requests.ts +6 -0
- package/src/plans/selection.ts +19 -16
- package/src/plans/sent-files-read.ts +22 -0
- package/src/plans/sent-files.ts +121 -0
- package/src/plans/signals-read.ts +54 -0
- package/src/plans/snapshot.ts +251 -55
- package/src/plans/subagents-read.ts +82 -0
- package/src/plans/subagents.ts +132 -0
- package/src/plans/texts.ts +16 -11
- package/src/plans/unlisted-lanes.ts +10 -5
- package/src/plans/view.ts +3 -3
- package/src/plans/waiting-click.ts +76 -0
- package/src/preview/command.ts +83 -0
- package/src/preview/embed.ts +209 -0
- package/src/preview/images.ts +89 -0
- package/src/preview/open.ts +12 -0
- package/src/preview/options.ts +32 -0
- package/src/preview/page.ts +89 -0
- package/src/preview/render.ts +41 -0
- package/src/settled/board-row.ts +1 -2
- package/src/shared/is-record.ts +2 -0
- package/src/shared/private-records.ts +48 -0
- package/src/task-signals/agent.ts +15 -0
- package/src/task-signals/claude-read.ts +72 -0
- package/src/task-signals/claude-signals.ts +112 -0
- package/src/task-signals/command.ts +71 -0
- package/src/task-signals/fixtures/session.jsonl +7 -0
- package/src/task-signals/matcher.ts +96 -0
- package/src/task-signals/record-store.ts +40 -0
- package/src/task-signals/record.ts +68 -0
- package/src/task-signals/text.ts +13 -0
- package/src/task-signals/types.ts +14 -0
- package/src/terminal/open.ts +13 -6
- package/src/terminal/text.ts +8 -1
- package/src/terminal/width.ts +2 -1
- package/src/vocabulary/index.ts +28 -0
- package/src/waiting/command.ts +35 -0
- package/src/watch/appointments.ts +2 -3
- package/src/watch/approvals.ts +2 -2
- package/src/watch/command.ts +91 -5
- package/src/watch/failed-tests.ts +56 -10
- package/src/watch/failure-read.ts +18 -6
- package/src/watch/failures.ts +29 -5
- package/src/watch/feedback.ts +1 -1
- package/src/watch/gh-process.ts +9 -3
- package/src/watch/json-guards.ts +0 -3
- package/src/watch/lane-fill.ts +1 -1
- package/src/watch/lane-model.ts +1 -1
- package/src/watch/lane-parallel.ts +1 -1
- package/src/watch/lane-progress.ts +19 -7
- package/src/watch/lane-report.ts +5 -1
- package/src/watch/lane-rest.ts +28 -0
- package/src/watch/plan-badges.ts +9 -2
- package/src/watch/previews.ts +1 -1
- package/src/watch/pull-requests.ts +2 -1
- package/src/watch/source-path.ts +1 -1
- package/src/watch/sources.ts +1 -1
- package/src/watch/stream-output.ts +12 -7
- package/src/watch/task-parallel-input.ts +118 -0
- package/src/watch/task-parallel-read.ts +149 -0
- package/src/watch/task-parallel-touched.ts +39 -0
- package/src/watch/task-parallel.ts +206 -0
- package/src/watch/waiting-line.ts +19 -0
- package/src/watch/watch-events.ts +13 -1
- 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
|
-
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg">
|
|
6
|
+
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd/assets/icons/space.svg?v=2">
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# Your agents write code. Jack keeps the work moving.
|
|
9
9
|
|
|
10
|
-
|
|
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
|
|
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/
|
|
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>
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
|
|
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.**
|
|
45
|
+
2. **Start Claude Code in a repository.**
|
|
46
46
|
|
|
47
47
|
```text
|
|
48
48
|
/jh-start
|
|
49
49
|
```
|
|
50
|
-
3. **
|
|
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
|
|
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. **
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
157
|
+
▸ ◉ checkout-fee ↗ #42 💬 2 ██████████ 6/6
|
|
165
158
|
────────────────────────────────────────────────────────────────────────────────
|
|
166
|
-
▾ ● search-index
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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-
|
|
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(
|
|
409
|
+
warnTokens: tokenCount.default(35e4),
|
|
401
410
|
/** Tokens in the coordinator's context from which the watcher reports `coordinator alert`. */
|
|
402
|
-
alertTokens: tokenCount.default(
|
|
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({}),
|
package/dist/plan-format.d.ts
CHANGED
|
@@ -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 };
|
package/dist/plan-format.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
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.
|
|
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;
|
|
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
|
-
/**
|
|
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
|
-
|
|
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),
|