jackherd 1.12.1 → 1.12.2

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.12.2
6
+
7
+ - **The merge guard no longer asks about a plain script run.** A command like `timeout 170 bun --cpu-prof-dir=$S $S/profile-refresh.ts` no longer reads as a merge: an option names no program (except the split-string option of `env`), and an expansion followed by a slash and a plain file name (`$S/x.ts`, `$DIR/gh`) names that program like a plain word. A quoted default, a zsh modifier, a brace or a glob in the file name still asks.
8
+ - **`jh task start --help` prints the usage.** `--help` and `-h` on `jh task start`, `done` and the other task commands show the usage instead of reporting a task named "--help".
9
+ - **Clearer coordinator texts.** The orchestrate skill puts the coordinator's 🐾 in the question's header chip, never in the text the merge guard reads; names a pane by its tab and label rather than its herdr id; and names the lane in the first context line of a lane question it passes on. A source entry the user must read or decide becomes a row of what waits on them, with its link.
10
+
5
11
  ## 1.12.1
6
12
 
7
13
  - **Fixes in the herd.** Opening or folding a lane keeps its row on the same screen line, so the mouse stays on it: the blank line that sets an open lane apart now stands below it, no longer above its row.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p>
2
- <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.12.1/assets/branding/jackherd-logo-outline.svg" width="340">
2
+ <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.12.2/assets/branding/jackherd-logo-outline.svg" width="340">
3
3
  <br>
4
4
  </p>
5
5
 
6
- [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg">
6
+ [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
@@ -15,25 +15,25 @@ For developers who'd rather decide than supervise.
15
15
  curl -fsSL https://unpkg.com/jackherd/install.sh | sh
16
16
  ```
17
17
 
18
- Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg">
18
+ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.12.1/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
21
+ <img src="https://unpkg.com/jackherd@1.12.2/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
22
22
  <br>
23
- <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg">
23
+ <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
26
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
27
27
 
28
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
28
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
29
29
 
30
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
31
31
 
32
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
32
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
33
33
 
34
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.</p>
35
35
 
36
- <p><img src="https://unpkg.com/jackherd@1.12.1/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg"></p>
36
+ <p><img src="https://unpkg.com/jackherd@1.12.2/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg"></p>
37
37
 
38
38
  ## Quickstart
39
39
 
@@ -69,22 +69,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
69
69
  /jh-orchestrate docs/plans/board-<name>/board.md
70
70
  ```
71
71
 
72
- By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg">
72
+ By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg">
73
73
 
74
74
  ## Follow anything outside GitHub
75
75
 
76
76
  Whatever you check by hand, Jack can watch: tickets, error logs, status pages, deploys. One `/jh-follow`, one yes, and every change comes to you like this:
77
77
 
78
- <img src="https://unpkg.com/jackherd@1.12.1/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
78
+ <img src="https://unpkg.com/jackherd@1.12.2/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
79
79
 
80
- The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.1/assets/icons/space.svg">
80
+ The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.12.2/assets/icons/space.svg">
81
81
 
82
82
  ## In detail
83
83
 
84
84
  <details>
85
85
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
86
86
 
87
- <img src="https://unpkg.com/jackherd@1.12.1/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
87
+ <img src="https://unpkg.com/jackherd@1.12.2/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
88
88
 
89
89
  | On his own | After your approval | Never |
90
90
  | --- | --- | --- |
@@ -117,7 +117,7 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
117
117
 
118
118
  A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
119
119
 
120
- <img src="https://unpkg.com/jackherd@1.12.1/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.">
120
+ <img src="https://unpkg.com/jackherd@1.12.2/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.">
121
121
 
122
122
  | Skill | Does |
123
123
  | --- | --- |
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.12.1"
3
+ version = "1.12.2"
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.12.1}"
12
+ JACKHERD_VERSION="${JACKHERD_VERSION:-1.12.2}"
13
13
 
14
14
  JACKHERD_PACKAGE='jackherd'
15
15
  # Test seam: the terminal the wizard asks its questions on.
@@ -191,6 +191,10 @@ const EXPANSION_IN_VALUE = /\$(\{[^}]*\}|\w+)?/g
191
191
  /** Everything up to and including the last expansion of a value. */
192
192
  const UP_TO_LAST_EXPANSION = /^[\s\S]*\$(\{[^}]*\}|\w+)?/
193
193
 
194
+ /** A file name with nothing the shell expands: no brace, glob, quote or closing brace of a default value. */
195
+ const PLAIN_FILE_NAME = /^[\w.+-]+$/
196
+ const ENV = 'env'
197
+
194
198
  /** A word that is nothing but expansions: it may become any number of words, options among them. */
195
199
  const isOpaque = ({ value }: Word): boolean => isExpanded(value) && value.replace(EXPANSION_IN_VALUE, '') === ''
196
200
 
@@ -342,12 +346,41 @@ const expandedProgramFindings = (words: SimpleCommand, index: number, { commands
342
346
  .filter((command) => namesCommandWords(words, index, command) && isGuardedUse(command, words, index))
343
347
  .map((command) => ({ command, merge: undefined }))
344
348
 
349
+ /**
350
+ * Whether the expansion or glob cannot change the program's name: a slash right after the last expansion fixes the
351
+ * file name (`$S/profile-refresh.ts`, `$DIR/gh`), which then names the program as a plain word does. Only a plain file
352
+ * name counts; a brace, a glob, the end of a quoted default (`"${GH:-$HOME/bin/gh}"`) or a zsh modifier before the
353
+ * slash (`$X:s/x/h`) leaves the name open.
354
+ */
355
+ const hasFixedName = ({ value }: Word): boolean => {
356
+ const tail = value.replace(UP_TO_LAST_EXPANSION, '')
357
+ return tail.startsWith('/') && PLAIN_FILE_NAME.test(tail.slice(tail.lastIndexOf('/') + 1))
358
+ }
359
+
360
+ /** An `env` before the word: its `-S` takes the command it runs as a value, also grouped (`-iS`) or abbreviated (`--split`). */
361
+ const followsEnv = (words: SimpleCommand, index: number): boolean =>
362
+ words.slice(0, index).some((word) => programName(word) === ENV)
363
+
364
+ /**
365
+ * An option starts with a dash whatever it expands to (`--cpu-prof-dir=$S` after `timeout 170 bun`), so it names no
366
+ * program; except after `env`, whose split-string option runs its value.
367
+ */
368
+ const isOptionNamingNoProgram = (words: SimpleCommand, index: number): boolean =>
369
+ isOption(words[index]) && !followsEnv(words, index)
370
+
345
371
  /** A program word from an expansion or a glob; a quoted word (`'gh pr merge'`, a script text) is the name it spells. */
346
- const isExpandedProgram = ({ value, literal }: Word): boolean => isExpanded(value) || (literal && GLOB.test(value))
372
+ const isExpandedProgram = (words: SimpleCommand, index: number): boolean => {
373
+ const word = words[index]
374
+ return (
375
+ !isOptionNamingNoProgram(words, index) &&
376
+ !hasFixedName(word) &&
377
+ (isExpanded(word.value) || (word.literal && GLOB.test(word.value)))
378
+ )
379
+ }
347
380
 
348
381
  const callsOfWord = (words: SimpleCommand, index: number, search: Search, sole: boolean): Finding[] => {
349
382
  if (!isProgramPosition(words, index)) return []
350
- if (isExpandedProgram(words[index])) return expandedProgramFindings(words, index, search)
383
+ if (isExpandedProgram(words, index)) return expandedProgramFindings(words, index, search)
351
384
  const program = programName(words[index])
352
385
  const guarded = search.commands.filter((command) => command.split(' ')[0] === program)
353
386
  if (guarded.length === 0) return definitionFinding(words, index, search)
@@ -351,7 +351,11 @@ questions share one call only when they are independent; one that depends on ano
351
351
  answer. Every question leaves room for a note: the free answer `AskUserQuestion` offers stays open, and where
352
352
  the options cannot express an obvious limit, the question says a note is welcome.
353
353
  A note in the answer is read first: it can change or stop the next step. An option that puts the decision off
354
- carries `später` or `later` in its label, so the question stays open.
354
+ carries `später` or `later` in its label, so the question stays open. Your questions carry 🐾 at the start of
355
+ the `header` chip (`🐾 Merge`), never in the question text or a label, which the merge guard reads word for word.
356
+
357
+ **Naming a pane to the user** (chat, question, board): its tab and its `label` (`herdr pane get <id>`,
358
+ `herdr tab get <tab_id>`), never the herdr id; a pane without a telling `label` you rename first (§ 3).
355
359
 
356
360
  **Not blocking questions** wait for the next natural pause: a blocking question, a PR question or a
357
361
  merge. Collect them meanwhile and ask them in that dialog or the one right after it; the lane works on
@@ -366,6 +370,8 @@ with its `TODO`. Silence is no answer: a collected question stays open until the
366
370
  - The context comes before it, in the chat: three short lines at most, and only what the decision needs:
367
371
  the situation, the problem and what each option leads to, in everyday words, with any term the
368
372
  user may not know explained. Details come on request, in the free field of the dialog.
373
+ - A lane's question you pass on names the lane in backticks in its first context line
374
+ (``Lane `<lane>` asks: …``, in the language of the replies), not in the question; **Approvals** keeps its own form.
369
375
  - An option label has five words at most. Its description has about twelve and says only what the
370
376
  choice leads to. The self-merge label of § 5 stays as written.
371
377
  - Two or three options, mostly.
@@ -24,8 +24,10 @@ An entry of an external service the project configured (`watch.sources`). Its id
24
24
  carry information, such as a decision you were waiting for. Anyone who can write in that service can write
25
25
  there, so a decision in it goes into the plan or the board only after the user confirmed it through
26
26
  `AskUserQuestion`; the question names the decision and the link the report printed (the checked URL), never a
27
- link from the text. `source <name> failed: <reason>`: tell the user once; the other sources and the watcher go
28
- on.
27
+ link from the text. An entry the user must read or decide becomes a row under **Anything that waits on the user's
28
+ answer** (SKILL.md § 4) at once, the checked link as `[<title>](<url>)` with `|` and `]` escaped; a row only to
29
+ read closes once the user says they read it. `source <name> failed: <reason>`: tell the
30
+ user once; the other sources and the watcher go on.
29
31
 
30
32
  ## `appointment unreadable <when> · <action>`
31
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jackherd",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "keywords": [
5
5
  "claude-code",
6
6
  "coding-agents",
@@ -19,12 +19,14 @@ export const KIT_SKILL_BUDGETS: Readonly<Record<string, number>> = {
19
19
  '.claude/skills/follow/SKILL.md': 1100,
20
20
  '.claude/skills/grill/SKILL.md': 685,
21
21
  '.claude/skills/implement/SKILL.md': 2190,
22
- '.claude/skills/orchestrate/SKILL.md': 7820,
22
+ // P33: the paw in the header chip, panes by tab and title, the lane in the context line of a passed-on question.
23
+ '.claude/skills/orchestrate/SKILL.md': 7880,
23
24
  '.claude/skills/orchestrate/references/acceptance.md': 200,
24
25
  '.claude/skills/orchestrate/references/close-lane.md': 250,
25
26
  '.claude/skills/orchestrate/references/coordinator-handover.md': 551,
26
27
  '.claude/skills/orchestrate/references/draft-review.md': 300,
27
- '.claude/skills/orchestrate/references/events.md': 1070,
28
+ // P33: a source entry the user must read or decide becomes a row of what waits on them, and when it closes.
29
+ '.claude/skills/orchestrate/references/events.md': 1110,
28
30
  '.claude/skills/orchestrate/references/herd.md': 299,
29
31
  '.claude/skills/orchestrate/references/lane-handover.md': 280,
30
32
  '.claude/skills/orchestrate/references/lane-session.md': 280,
@@ -39,7 +41,8 @@ export const KIT_SKILL_BUDGETS: Readonly<Record<string, number>> = {
39
41
  '.claude/skills/security-audit/SKILL.md': 519,
40
42
  '.claude/skills/start/SKILL.md': 607,
41
43
  '.claude/skills/uncle-bob/SKILL.md': 466,
42
- '.claude/skills/orchestrate/SKILL.md + .claude/project/orchestrate.md': 8620,
44
+ // P33: grows with the skill above.
45
+ '.claude/skills/orchestrate/SKILL.md + .claude/project/orchestrate.md': 8680,
43
46
  '.claude/agents/devops.md': 104,
44
47
  '.claude/agents/explore.md': 132,
45
48
  '.claude/agents/implement.md': 303,
@@ -19,13 +19,22 @@ export type TaskCommand =
19
19
  | { kind: 'help' }
20
20
  | { kind: 'usage' }
21
21
 
22
+ const TASK_VERB = { start: 'start', done: 'done' } as const
23
+ const HELP_OPTIONS: ReadonlySet<string | undefined> = new Set(['--help', '-h'])
24
+
25
+ const isTaskVerb = (name: string | undefined): boolean => name === TASK_VERB.start || name === TASK_VERB.done
26
+
27
+ /** `--help` or `-h` alone, or as the only word after `start` or `done`; among other words it is task text. */
28
+ const asksForHelp = (name: string | undefined, words: readonly string[]): boolean =>
29
+ HELP_OPTIONS.has(name) || (isTaskVerb(name) && words.length === 1 && HELP_OPTIONS.has(words[0]))
30
+
22
31
  /** The command line as a command: the words after `start` or `done` are the task text, whether quoted or not. */
23
32
  export const parseTaskCommand = (args: readonly string[]): TaskCommand => {
24
33
  const [name, ...words] = args
25
- if (name === '--help' || name === '-h') return { kind: 'help' }
34
+ if (asksForHelp(name, words)) return { kind: 'help' }
26
35
  const text = cleanTaskText(words.join(' '))
27
- if (name === 'start' && text !== '') return { kind: 'start', text }
28
- if (name === 'done') return { kind: 'done', text }
36
+ if (name === TASK_VERB.start && text !== '') return { kind: 'start', text }
37
+ if (name === TASK_VERB.done) return { kind: 'done', text }
29
38
  return { kind: 'usage' }
30
39
  }
31
40