jackherd 1.24.0 → 1.26.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 (88) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/INSTALL.md +1 -1
  3. package/README.md +22 -22
  4. package/dist/{chunk-JDO6O3UO.js → chunk-O365KHHP.js} +139 -3
  5. package/dist/config.d.ts +4 -0
  6. package/dist/config.js +4 -1
  7. package/dist/plan-format.d.ts +121 -3
  8. package/dist/plan-format.js +176 -35
  9. package/docs/plan-contract.md +52 -5
  10. package/herdr-plugin/herdr-plugin.toml +1 -1
  11. package/install.sh +1 -1
  12. package/kit/hooks/hooks/approval-gate/index.ts +2 -2
  13. package/kit/hooks/hooks/merge-guard/lane-plan.ts +1 -1
  14. package/kit/hooks/hooks/open-question/record.ts +81 -1
  15. package/kit/hooks/hooks/open-question/run.ts +17 -3
  16. package/kit/hooks/hooks/question-session-end/index.ts +18 -0
  17. package/kit/hooks/hooks/question-session-end/run.ts +37 -0
  18. package/kit/hooks/hooks/sent-file/record.ts +4 -1
  19. package/kit/hooks/hooks/shared/board-lanes.ts +33 -12
  20. package/kit/hooks/hooks/shared/boards.ts +22 -6
  21. package/kit/hooks/hooks.json +8 -0
  22. package/kit/workflow/skills/orchestrate/references/herd.md +3 -3
  23. package/kit/workflow/skills/orchestrate/references/waiting.md +15 -1
  24. package/kit/workflow/skills/plan/SKILL.md +3 -1
  25. package/kit/workflow/skills/plan/references/template-English.md +2 -0
  26. package/kit/workflow/skills/plan/references/template-German.md +2 -0
  27. package/package.json +1 -1
  28. package/schemas/jackherd.schema.json +7 -0
  29. package/src/brief/command.ts +1 -1
  30. package/src/brief/lane-facts.ts +2 -2
  31. package/src/check/command.ts +2 -1
  32. package/src/check/plan-structure-check.ts +68 -14
  33. package/src/check/settings.ts +6 -1
  34. package/src/check/skill-budget-defaults.ts +9 -4
  35. package/src/config/schema.ts +3 -0
  36. package/src/lane/close-board.ts +4 -4
  37. package/src/lane/close-command.ts +2 -2
  38. package/src/lane/dev-command.ts +1 -1
  39. package/src/lane/port-command.ts +1 -1
  40. package/src/plan-contract/command.ts +2 -1
  41. package/src/plan-contract/contract.ts +30 -9
  42. package/src/plan-format/bare-codes.ts +4 -4
  43. package/src/plan-format/dependencies.ts +5 -5
  44. package/src/plan-format/lanes.ts +3 -4
  45. package/src/plan-format/markdown.ts +125 -10
  46. package/src/plan-format/open-status.ts +15 -0
  47. package/src/plan-format/phases.ts +45 -4
  48. package/src/plan-format/plan-head.ts +31 -9
  49. package/src/plan-format/plan-structure.ts +43 -7
  50. package/src/plan-format/progress.ts +3 -0
  51. package/src/plan-format/sprint-document.ts +4 -4
  52. package/src/plan-format/unrecognized-sections.ts +21 -0
  53. package/src/plan-format/waiting.ts +13 -13
  54. package/src/plans/board-close.ts +25 -16
  55. package/src/plans/board-pick.ts +1 -1
  56. package/src/plans/closable-records.ts +11 -10
  57. package/src/plans/close-reason.ts +19 -0
  58. package/src/plans/ensure-panes.ts +2 -2
  59. package/src/plans/layout-summary.ts +8 -1
  60. package/src/plans/layout.ts +14 -5
  61. package/src/plans/live.ts +3 -2
  62. package/src/plans/question-records.ts +23 -2
  63. package/src/plans/questions-read.ts +2 -2
  64. package/src/plans/sent-files-read.ts +2 -2
  65. package/src/plans/sent-files.ts +12 -4
  66. package/src/plans/settings.ts +3 -0
  67. package/src/plans/snapshot.ts +9 -5
  68. package/src/plans/sprint-export.ts +1 -1
  69. package/src/plans/texts.ts +3 -0
  70. package/src/plans/view.ts +1 -0
  71. package/src/resume/coordinator-read.ts +2 -2
  72. package/src/settled/command.ts +1 -1
  73. package/src/settled/settle.ts +5 -5
  74. package/src/sync/render.ts +20 -5
  75. package/src/sync/sync-plan.ts +6 -2
  76. package/src/sync/sync.ts +3 -2
  77. package/src/vocabulary/index.ts +192 -4
  78. package/src/waiting/command.ts +82 -26
  79. package/src/watch/appointments.ts +13 -20
  80. package/src/watch/command.ts +100 -6
  81. package/src/watch/lane-progress.ts +1 -1
  82. package/src/watch/lane-report.ts +4 -0
  83. package/src/watch/merge-overlap-read.ts +2 -2
  84. package/src/watch/obsolete-proposals.ts +175 -0
  85. package/src/watch/obsolete-records.ts +77 -0
  86. package/src/watch/plan-missing-read.ts +3 -1
  87. package/src/watch/reference-paths.ts +2 -0
  88. package/src/watch/watch-events.ts +20 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.26.0
6
+
7
+ - **"Your turn" clears what is obsolete, and says why.** A closed entry now keeps the reason it was closed (`jh waiting close <id> --reason "<text>"`, `jh waiting --closed` lists the last 24 hours). The watcher closes an older sent file by itself when the same file was sent again (`newer version: <id>`, whoever sent it) or its lane's worktree is gone (`lane removed: <name>`), and reports it as `waiting closed`. What only looks obsolete is proposed once as `waiting obsolete?`: a send whose lane's pull request was merged or closed, a board row naming such a pull request or a removed lane, a question a later dialog answered in nearly the same words (`superseded`) and a question whose session ended (`session ended`). Nothing but a send is ever closed without you. A refusal by another hook is no longer recorded as an open question. New hook `question-session-end` (on `SessionEnd`) marks the ending session's open questions. The orchestrate skill's `waiting.md` and `events.md` say how to work through a proposal. Run `jh sync` to get the hook and the texts.
8
+
9
+ ## 1.25.0
10
+
11
+ - **Every plan phase says in one sentence what it is about.** The plan template puts a line directly under each `### Phase` heading, `**What it is about:** <sentence>` (German: `**Worum es geht:** <Satz>`): what is different afterwards and for whom, or "nothing visible". `jh check` fails a phase the change adds that has tasks and no such line; phases a plan already had are not read, so older plans stay green. `jh plan check` prints a sixth line, `purpose: present` or `purpose: missing <file>:<line> no purpose line in P2, P3`; like the head it is a note and never changes the exit code. In an opened lane the herd shows the sentence dimmed under the head of each phase it draws; the folded row and the sidebar title stay as they are. The plan skill (§ structure), the herd reference and `docs/plan-contract.md` describe it. Run `jh sync` to get the template and the skill text.
12
+ - **A board and a plan are read in every language, and the language jh writes the board in is its own setting.** A German board next to an English plan (or the other way round) used to leave "Your turn" empty and `jh plan check` red, because every heading, column name and status word was compared with the one language in `planLanguage`. jackherd now reads both languages whatever the config says, and a document in a third language carries a marker, `<!-- jh: <key> -->`, which Markdown does not show: on the line under a heading (`lanes`, `history`, `for-you`, …), in a header cell (`depends-on`, `status`, …), on a head line, in a status cell (`status-open`, `status-done`, …) and on a line that opens with bold words (`purpose`, `no-review-gate`). New `boardLanguage` in `.jackherd/config.json` is the language jh writes the board in; it defaults to `planLanguage`, so a config without it runs unchanged. A plan template renders in the language of its file name (the English file is no longer filled with German headings under a German `planLanguage`). When no section is recognised, `jh plan check` names the forms it looked for, `jh check` warns on a changed file jh reads nothing of, and the herd shows `board: no section recognised` instead of "nothing waits for you". `docs/plan-contract.md` lists the keys. Run `jh sync` to get the template and the skill text.
13
+
5
14
  ## 1.24.0
6
15
 
7
16
  - **A shared backend for all lanes: `lanes.backend` and `jh backend`.** With herdr, the services every lane's app talks to run in one pane (its own tab) of the main checkout, which the watcher starts at its start and once a minute, and starts again after it ended, at most three times in ten minutes. `lanes.backend` takes a `command` that contains `{services}`, a list of `services` (`name`, `port`, both unique) and an optional pane `label`; the command line starts with `env -u PORT`. A service port that something listens on prevents a start and is never freed. The pane keeps running when the watcher ends, even after `kit updated`; what was last started is kept in `backend.json` in the watcher's directory, so a restart of the watcher leaves a pane with that command alone. `jh backend` shows pane, command, handed-over services and starts; `jh backend restart` starts again after "gave up"; `jh backend release <service> --lane <lane>` stops the pane, starts it without that service and names the port for `jh lane dev <lane> --filter <service> --port <port>`; `jh backend return <service>` takes it back, and the watcher does it once the lane's worktree is gone. Only the process in the pane with the backend's label, in the main checkout, is ever stopped (SIGTERM to its foreground process group). A service name or label must be one shell word that does not start with a dash, and `jh lane dev --filter` now refuses a leading dash too. The orchestrate skill (§ 3, § 7, the `backend:` lines in its events reference) tells the coordinator when to hand a service over. A jackherd older than this version refuses a config that has `lanes.backend`: run `jh update` on every machine first, then `jh sync` for the skill text.
package/INSTALL.md CHANGED
@@ -55,7 +55,7 @@ When `init` creates a new config, it asks:
55
55
  | Use it when | the team shares the kit through the repository | the repository must stay untouched, not even untracked files |
56
56
  | Lives in | the project's `.claude/` | `~/.claude/` |
57
57
  | Skill names | `/plan`, `/implement`, `/orchestrate`, … | `/jh-plan`, `/jh-implement`, `/jh-orchestrate`, … |
58
- | Hooks | `git-guard`, `watch-guard`, `lane-start-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `plain-question`, `sent-file`, `browser-guard`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
58
+ | Hooks | `git-guard`, `watch-guard`, `lane-start-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `question-session-end`, `plain-question`, `sent-file`, `browser-guard`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
59
59
  | Config | `.jackherd/config.json` | `~/.jackherd/config.json`, used where a repository has none |
60
60
  | Update | `jh sync` | `jh update` |
61
61
 
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.24.0/assets/branding/jackherd-logo-outline.svg" width="340">
2
+ <img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.26.0/assets/branding/jackherd-logo-outline.svg" width="340">
3
3
  <br>
4
4
  </p>
5
5
 
6
- [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg">
6
+ [![npm](https://img.shields.io/npm/v/jackherd)](https://www.npmjs.com/package/jackherd) [![license: MIT](https://img.shields.io/npm/l/jackherd)](https://www.npmjs.com/package/jackherd) [![2 dependencies](https://img.shields.io/badge/dependencies-2-brightgreen)](https://www.npmjs.com/package/jackherd?activeTab=dependencies) [![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple)](https://unpkg.com/jackherd/INSTALL.md) [![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
@@ -15,29 +15,29 @@ For developers who'd rather decide than supervise.
15
15
  curl -fsSL https://unpkg.com/jackherd/install.sh | sh
16
16
  ```
17
17
 
18
- Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg">
18
+ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.24.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
21
+ <img src="https://unpkg.com/jackherd@1.26.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
22
22
  <br>
23
- <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg">
23
+ <sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
26
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
27
27
 
28
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Commits, pushes and merges wait for your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours. The guards catch mistakes, not intent; a hard boundary takes branch protection.</p>
28
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Commits, pushes and merges wait for your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours. The guards catch mistakes, not intent; a hard boundary takes branch protection.</p>
29
29
 
30
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A lane gets stuck, a check goes red, a decision needs you: Jack steps in and brings you only what's yours to decide. The herd view shows where every lane stands.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A lane gets stuck, a check goes red, a decision needs you: Jack steps in and brings you only what's yours to decide. The herd view shows where every lane stands.</p>
31
31
 
32
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
32
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/skills.svg" width="24" height="34" align="middle" alt=""> <b>Works with your skills.</b><br>Keep your own skills: a lane builds with whichever one you pick. Plans written elsewhere run too, once Jack fills in what they're missing.</p>
33
33
 
34
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments on a lane's pull request go back to that lane, which fixes them with a test. <code>/jh-follow</code> brings in anything a CLI or an API can read as JSON: tickets, error logs, status pages.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments on a lane's pull request go back to that lane, which fixes them with a test. <code>/jh-follow</code> brings in anything a CLI or an API can read as JSON: tickets, error logs, status pages.</p>
35
35
 
36
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/model.svg" width="24" height="34" align="middle" alt=""> <b>The right model for each phase.</b><br>Every phase names its model: the stronger one where a mistake is expensive (data, security, a migration), the cheaper one for the rest. Lanes start on it, and a hook refuses to start a phase's subagent on a weaker one.</p>
36
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/model.svg" width="24" height="34" align="middle" alt=""> <b>The right model for each phase.</b><br>Every phase names its model: the stronger one where a mistake is expensive (data, security, a migration), the cheaper one for the rest. Lanes start on it, and a hook refuses to start a phase's subagent on a weaker one.</p>
37
37
 
38
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>A crash or a full context doesn't cost you the work.</b><br>Plan, board and a hand-over file hold the state, not the chat: the next session picks up where the last one stopped.</p>
38
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>A crash or a full context doesn't cost you the work.</b><br>Plan, board and a hand-over file hold the state, not the chat: the next session picks up where the last one stopped.</p>
39
39
 
40
- <p><img src="https://unpkg.com/jackherd@1.24.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg"></p>
40
+ <p><img src="https://unpkg.com/jackherd@1.26.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg"></p>
41
41
 
42
42
  ## Quickstart
43
43
 
@@ -68,22 +68,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
68
68
  /jh-orchestrate docs/plans/board-<name>/board.md
69
69
  ```
70
70
 
71
- Linux and macOS are supported (Windows through WSL); the requirements are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md). By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg">
71
+ Linux and macOS are supported (Windows through WSL); the requirements are in [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md). By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg">
72
72
 
73
73
  ## Follow anything you can query
74
74
 
75
75
  Tickets, error logs, status pages: if a command or an API call can read it as JSON with your login, Jack can watch it. Recipes cover Jira, Linear, GitHub issues and Projects, Sentry, Google Calendar and status pages; for anything else, `/jh-follow` writes the command with you. One yes, and every change shows up like this:
76
76
 
77
- <img src="https://unpkg.com/jackherd@1.24.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
77
+ <img src="https://unpkg.com/jackherd@1.26.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
78
78
 
79
- The herd sums up your sources in one `Sources` line: new entries per source, or its last run, red while it fails; opened, a click opens an entry. One that needs you lands in Your turn under the source's label, [ask] has Jack look at it, and a ticket shows on the lane that works it.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.24.0/assets/icons/space.svg">
79
+ The herd sums up your sources in one `Sources` line: new entries per source, or its last run, red while it fails; opened, a click opens an entry. One that needs you lands in Your turn under the source's label, [ask] has Jack look at it, and a ticket shows on the lane that works it.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.26.0/assets/icons/space.svg">
80
80
 
81
81
  ## In detail
82
82
 
83
83
  <details>
84
84
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
85
85
 
86
- <img src="https://unpkg.com/jackherd@1.24.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
86
+ <img src="https://unpkg.com/jackherd@1.26.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
87
87
 
88
88
  | On his own | After your approval | Never |
89
89
  | --- | --- | --- |
@@ -116,9 +116,9 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
116
116
  <details>
117
117
  <summary><b>Plans, boards and the skills, yours included</b></summary>
118
118
 
119
- A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
119
+ A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase, and under each phase heading one sentence on what is different afterwards and for whom. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
120
120
 
121
- <img src="https://unpkg.com/jackherd@1.24.0/docs/skills-dark.svg" alt="Your own plan or an issue is adopted (Jack adds what is missing, you see the diff) and goes to /jh-orchestrate; /jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement, which your own skill can replace; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
121
+ <img src="https://unpkg.com/jackherd@1.26.0/docs/skills-dark.svg" alt="Your own plan or an issue is adopted (Jack adds what is missing, you see the diff) and goes to /jh-orchestrate; /jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement, which your own skill can replace; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
122
122
 
123
123
  | Skill | Does |
124
124
  | --- | --- |
@@ -185,7 +185,7 @@ How the herd reads the board:
185
185
  - **Sources** shows, per source that is no ticket, its entries and how many are new, or when it last ran: amber with a new entry that is not in Your turn yet, red while it fails. Opened, its entries stand newest first by the time they show, and a click on an entry opens it; `↑ in your turn` marks one a row of Your turn asks about.
186
186
  - **Lanes** opens at the start. A lane that works or is blocked opens by itself only when it first appears; after that, opening and folding are yours. A lane symbol says the state (`●` working, `◆` blocked, `◉` the agent finished its answer, `○` idle, `(·) no agent` a worktree nobody works in); only blocked wears its word. A folded lane line opens with its status and the age of its last activity: `needs you 3 min` and the first open question of its report (`+2 questions` counts the others), `blocked 3 min` and what it waits on (a row of Your turn, or the lane's starts-after cell), or `running 3 min` and its step. The plan's short title and the phase as `P2/5` with its name follow, and the plan is the first part to give way in a narrow pane. A resting lane without a known reason keeps `idle · no reason`. `:3100 ↗` on the branch line of an open lane opens its dev server, while one listens on the port its board row names.
187
187
  - **Waiting** lists the lanes not started yet: no worktree yet, something left to do, and what each starts after; it starts folded. Finished lanes (cleaned up, or every phase they name ticked) are not shown.
188
- - **An open lane** shows, top down: its branch, the model and how long it idles; its link chips on one line, `+n` for the rest; its pull requests; the phases it works on, each with its bar and its tasks as a checklist in plan order (ticked above, the running one marked, the ones to come below; above seven tasks a window around the running one, with `… n more` above and below to open the whole list); `▸ next` with the phases that wait, as ranges; `▸ done` with the finished ones. A lane shows only the phases its board rows name; a lane without a worktree reads its plan from the main checkout. A folded lane shows a reported task below its line while it has the mouse; behind each pull request badge it shows the checks, also while the lane rests: `✓ checks`, `checks running`, `✗ test +1` with the first red check, `draft` for a draft.
188
+ - **An open lane** shows, top down: its branch, the model and how long it idles; its link chips on one line, `+n` for the rest; its pull requests; the phases it works on, each with its bar, its purpose sentence dimmed under the head, and its tasks as a checklist in plan order (ticked above, the running one marked, the ones to come below; above seven tasks a window around the running one, with `… n more` above and below to open the whole list); `▸ next` with the phases that wait, as ranges; `▸ done` with the finished ones. A lane shows only the phases its board rows name; a lane without a worktree reads its plan from the main checkout. A folded lane shows a reported task below its line while it has the mouse; behind each pull request badge it shows the checks, also while the lane rests: `✓ checks`, `checks running`, `✗ test +1` with the first red check, `draft` for a draft.
189
189
  - **A phase runs** when an agent reports working on it, one of its boxes was ticked in the last 20 minutes, or some of its tasks are ticked. A phase whose run was merged and that the lane's newest board row no longer names waits behind the others in `▸ next`, its open boxes left open on purpose. `n min ago` counts since the last tick or report; `· gate` means only the review gate is open, `docs open` only the documentation box.
190
190
  - **A running task** carries `●` when an agent reported it, from its task list or `jh task`; its text stands in teal and a light band runs over it (`plans.shimmer: false`, `NO_COLOR` and `--once` keep it still). `◌` is a guess: in dim, a task guessed from the words of the lane's log; shimmering like a reported one, the first open task of a phase the lane runs when no agent reported one. `now: …` is a reported task no plan task names, `Subagent: …` a running subagent on no phase; a running review sits on the gate box that names it, in the phase it names or else the lane's current one. With several agents in a lane, each task carries its agent's name, dim, at the right edge.
191
191
  - **What a working lane does right now:** under its lane line, open or folded, a dim `↳` line shows the last action from the agent's session log, a tool with its target (`↳ Read plans/layout.ts`) or the start of its answer, and each running subagent adds one, indented and led by its task (where `Subagent: …` says what it works on, `↳` says what it does); the lines go when the lane rests.
@@ -223,7 +223,7 @@ jackherd sets the model, not the effort and not the advisor: those come from Cla
223
223
  | `jh init` | creates `.jackherd/config.json` and installs the kit (`--global` for `~/.claude`) |
224
224
  | `jh sync` | updates the kit; a file you edited gets the new version beside it as `.kit-new` |
225
225
  | `jh update` | installs the newest release and syncs |
226
- | `jh check` | types, lint and tests of what you touched, plus the plan and board checks (`--skip-slow`, `--parallel <plan>`) |
226
+ | `jh check` | types, lint and tests of what you touched, plus the plan and board checks, among them the purpose line every new plan phase with tasks needs (`--skip-slow`, `--parallel <plan>`); `jh plan check <plan>` reports the plan contract, the purpose line as a note; boards and plans are read in English and German whatever `planLanguage` says, a third language by `<!-- jh: <key> -->` markers, and `boardLanguage` (default `planLanguage`) is the language jh writes the board in (`docs/plan-contract.md`) |
227
227
  | `jh herd` | the live view (`--once`, `--lane <name>`) |
228
228
  | `jh watch` | the watcher Jack runs |
229
229
  | `jh resume` | after herdr restarts, wakes the coordinator and the lanes that worked when the machine crashed; runs once per crash and only when the watcher's record shows no `stoppedAt`. Stopping the watcher before quitting herdr prevents it. With `--coordinator` it lists what a coordinator takes up after `/clear` (lanes, PRs, approvals, what waits on you, appointments, standing approvals), from files and herdr; `--clear-line` prints the first input of that chat. |
@@ -232,7 +232,7 @@ jackherd sets the model, not the effort and not the advisor: those come from Cla
232
232
  | `jh lane close <lane>` | removes a finished lane: agent, worktree, herdr workspace and branch, and marks the board; first checks that nothing is lost, and refuses with every reason; `--dry-run` only checks. Runs without a prompt. |
233
233
  | `jh backend` | the shared backend of the lanes (`lanes.backend`, one pane in the main checkout that the watcher keeps): shows its pane, command and handed-over services; `restart`; `release <service> --lane <lane>` stops it and starts it without that service, naming the port the lane starts it on; `return <service>`. Works from any worktree. A busy service port stops a start, nothing frees a port |
234
234
  | `jh preview <file>` | renders a Markdown file through the GitHub API into a preview in GitHub and npm style |
235
- | `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>…` or `close --all` once you have) |
235
+ | `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>… [--reason "<text>"]` or `close --all` once you have; `--closed` lists what closed in the last 24 hours with its reason). The watcher closes an older send by itself once the same file was sent again or its lane is gone, and proposes the rest (a merged pull request, a question that looks answered, a session that ended) as `waiting obsolete?` |
236
236
  | `jh brief` | three lines for each open lane of the board: its goal, its status and what it needs from you, read from the head of its plan (`--board <board.md>` names the board); after `plans.briefAfterMinutes` (default 60, 0 is off) without a prompt, the `return-brief` hook asks Jack to show it first |
237
237
  | `jh context` | how full an agent's context is, as JSON; `--usage [--since 24h\|7d\|<date>]` the tokens a lane used instead (Jack's with `--coordinator`) |
238
238
  | `jh config` | the commands and the base branch the repository uses; a missing value goes to stderr with the line to set, and `jh config <key>` for an unset value exits 3 |
@@ -1,5 +1,9 @@
1
1
  // src/vocabulary/index.ts
2
2
  var PLAN_LANGUAGE = { de: "de", en: "en" };
3
+ var languagesFrom = (language) => [
4
+ language,
5
+ ...Object.values(PLAN_LANGUAGE).filter((other) => other !== language)
6
+ ];
3
7
  var LANGUAGE_NAME = {
4
8
  [PLAN_LANGUAGE.en]: "English",
5
9
  [PLAN_LANGUAGE.de]: "German"
@@ -87,6 +91,73 @@ var WAITING_VOCABULARY = {
87
91
  open: ["open"]
88
92
  }
89
93
  };
94
+ var SECTION_KEY = {
95
+ waiting: "waiting",
96
+ appointments: "appointments",
97
+ lanes: "lanes",
98
+ goal: "goal",
99
+ history: "history",
100
+ plans: "plans",
101
+ decisions: "decisions",
102
+ implementation: "implementation",
103
+ model: "model",
104
+ dependencies: "dependencies",
105
+ documentation: "documentation",
106
+ forYou: "for-you",
107
+ next: "next"
108
+ };
109
+ var isSectionKey = (value) => Object.values(SECTION_KEY).includes(value);
110
+ var HEAD_KEY = {
111
+ changes: "head-changes",
112
+ status: "head-status",
113
+ decisions: "head-decisions",
114
+ tryNow: "head-try-now",
115
+ next: "head-next"
116
+ };
117
+ var LINE_KEY = { noReviewGate: "no-review-gate", purpose: "purpose" };
118
+ var COLUMN_KEY = {
119
+ since: "since",
120
+ item: "item",
121
+ origin: "origin",
122
+ unblocks: "unblocks",
123
+ status: "status",
124
+ when: "when",
125
+ action: "action",
126
+ dependsOn: "depends-on",
127
+ writes: "writes",
128
+ serialMarker: "serial-marker"
129
+ };
130
+ var columnWords = (key, language) => {
131
+ const waiting = WAITING_VOCABULARY[language];
132
+ const appointments = APPOINTMENT_VOCABULARY[language];
133
+ const { columns } = PLAN_VOCABULARY[language];
134
+ const words = {
135
+ [COLUMN_KEY.since]: [waiting.columns.since],
136
+ [COLUMN_KEY.item]: [waiting.columns.item],
137
+ [COLUMN_KEY.origin]: [waiting.columns.origin],
138
+ [COLUMN_KEY.unblocks]: [waiting.columns.unblocks],
139
+ [COLUMN_KEY.status]: [
140
+ waiting.columns.status,
141
+ ...waiting.statusAlso,
142
+ appointments.columns.status,
143
+ ...appointments.statusAlso
144
+ ],
145
+ [COLUMN_KEY.when]: [appointments.columns.when],
146
+ [COLUMN_KEY.action]: [appointments.columns.action],
147
+ [COLUMN_KEY.dependsOn]: [columns.dependsOn],
148
+ [COLUMN_KEY.writes]: [columns.writes],
149
+ [COLUMN_KEY.serialMarker]: [columns.serialMarker]
150
+ };
151
+ return words[key];
152
+ };
153
+ var STATUS_KEY = {
154
+ open: "status-open",
155
+ done: "status-done",
156
+ abandoned: "status-abandoned",
157
+ inProgress: "status-in-progress",
158
+ draft: "status-draft"
159
+ };
160
+ var isStatusKey = (value) => Object.values(STATUS_KEY).includes(value);
90
161
  var isPlanLanguage = (value) => Object.values(PLAN_LANGUAGE).includes(value);
91
162
  var PLAN_STATUS = {
92
163
  done: "done",
@@ -138,6 +209,7 @@ var PLAN_VOCABULARY = {
138
209
  columns: { dependsOn: "H\xE4ngt ab von", writes: "Schreibt", serialMarker: "Seriell-Marker" },
139
210
  phase: "Phase",
140
211
  noReviewGate: "Kein Review-Gate",
212
+ phasePurpose: "Worum es geht",
141
213
  laneClosed: "abger\xE4umt",
142
214
  partialPhase: "(Teil)",
143
215
  openSubagents: "offene Sub-Agenten",
@@ -181,6 +253,7 @@ var PLAN_VOCABULARY = {
181
253
  columns: { dependsOn: "Depends on", writes: "Writes", serialMarker: "Serial marker" },
182
254
  phase: "Phase",
183
255
  noReviewGate: "No review gate",
256
+ phasePurpose: "What it is about",
184
257
  laneClosed: "cleaned up",
185
258
  partialPhase: "(part)",
186
259
  openSubagents: "open subagents",
@@ -202,15 +275,63 @@ var PLAN_VOCABULARY = {
202
275
  }
203
276
  };
204
277
  var STATUS_PRECEDENCE = [PLAN_STATUS.done, PLAN_STATUS.abandoned, PLAN_STATUS.inProgress, PLAN_STATUS.draft];
205
- var classifyStatus = (language, statusText) => {
278
+ var STATUS_OF_KEY = {
279
+ [STATUS_KEY.done]: PLAN_STATUS.done,
280
+ [STATUS_KEY.abandoned]: PLAN_STATUS.abandoned,
281
+ [STATUS_KEY.inProgress]: PLAN_STATUS.inProgress,
282
+ [STATUS_KEY.draft]: PLAN_STATUS.draft
283
+ };
284
+ var MARKER_IN_TEXT = /<!--\s*jh:\s*([a-z-]+)\s*-->/;
285
+ var statusOfMarker = (statusText) => {
286
+ const key = MARKER_IN_TEXT.exec(statusText)?.[1];
287
+ return key !== void 0 && isStatusKey(key) ? STATUS_OF_KEY[key] : void 0;
288
+ };
289
+ var statusOfWords = (language, statusText) => {
206
290
  const { statusWords } = PLAN_VOCABULARY[language];
207
291
  const lowerStatus = statusText.toLowerCase();
208
292
  const matches = ({ leading, anywhere }) => leading.some((word) => lowerStatus.startsWith(word)) || anywhere.some((fragment) => lowerStatus.includes(fragment));
209
- const matched = STATUS_PRECEDENCE.find((status) => matches(statusWords[status]));
293
+ return STATUS_PRECEDENCE.find((status) => matches(statusWords[status]));
294
+ };
295
+ var classifyStatus = (language, statusText) => {
296
+ const marked = statusOfMarker(statusText);
297
+ if (marked) return marked;
298
+ const text = statusText.replace(MARKER_IN_TEXT, "");
299
+ const matched = languagesFrom(language).map((candidate) => statusOfWords(candidate, text)).find((status) => status !== void 0);
210
300
  if (matched) return matched;
211
- return statusText.trim() ? PLAN_STATUS.other : PLAN_STATUS.none;
301
+ return text.trim() ? PLAN_STATUS.other : PLAN_STATUS.none;
212
302
  };
213
303
  var phaseHeading = (language, id, title) => `### ${PLAN_VOCABULARY[language].phase} ${id}: ${title}`;
304
+ var headingOfSection = (key, language) => {
305
+ const { sections } = PLAN_VOCABULARY[language];
306
+ const headings = {
307
+ [SECTION_KEY.waiting]: WAITING_VOCABULARY[language].heading,
308
+ [SECTION_KEY.appointments]: APPOINTMENT_VOCABULARY[language].heading,
309
+ [SECTION_KEY.lanes]: sections.lanes,
310
+ [SECTION_KEY.goal]: sections.goal,
311
+ [SECTION_KEY.history]: sections.history,
312
+ [SECTION_KEY.plans]: sections.plans,
313
+ [SECTION_KEY.decisions]: sections.decisions,
314
+ [SECTION_KEY.implementation]: sections.implementation,
315
+ [SECTION_KEY.model]: sections.modelRecommendations,
316
+ [SECTION_KEY.dependencies]: sections.dependencies,
317
+ [SECTION_KEY.documentation]: sections.documentation,
318
+ [SECTION_KEY.forYou]: sections.forYou,
319
+ [SECTION_KEY.next]: NEXT_VOCABULARY[language].heading
320
+ };
321
+ return headings[key];
322
+ };
323
+ var sectionOfHeading = (headingText) => {
324
+ for (const language of Object.values(PLAN_LANGUAGE)) {
325
+ const key = Object.values(SECTION_KEY).find((candidate) => headingOfSection(candidate, language) === headingText);
326
+ if (key !== void 0) return { key, language };
327
+ }
328
+ return null;
329
+ };
330
+ var CELL_KEY = { laneClosed: "lane-closed", partialPhase: "partial-phase" };
331
+ var LANE_CLOSED_WORDS = Object.values(PLAN_VOCABULARY).map(({ laneClosed }) => laneClosed);
332
+ var PARTIAL_PHASE_WORDS = Object.values(PLAN_VOCABULARY).map(
333
+ ({ partialPhase }) => partialPhase
334
+ );
214
335
  var sectionHeading = (language, section) => `## ${PLAN_VOCABULARY[language].sections[section]}`;
215
336
 
216
337
  // src/plan-format/terminal-text.ts
@@ -232,6 +353,7 @@ var terminalSafe = (text) => text.replace(UNSAFE_TERMINAL_TEXT, "");
232
353
 
233
354
  export {
234
355
  PLAN_LANGUAGE,
356
+ languagesFrom,
235
357
  LANGUAGE_NAME,
236
358
  RESPONSE_ADDRESS,
237
359
  ADDRESS_FORM,
@@ -244,13 +366,27 @@ export {
244
366
  APPOINTMENT_VOCABULARY,
245
367
  NEXT_VOCABULARY,
246
368
  WAITING_VOCABULARY,
369
+ SECTION_KEY,
370
+ isSectionKey,
371
+ HEAD_KEY,
372
+ LINE_KEY,
373
+ COLUMN_KEY,
374
+ columnWords,
375
+ STATUS_KEY,
376
+ isStatusKey,
247
377
  isPlanLanguage,
248
378
  PLAN_STATUS,
249
379
  REPORT_FIELDS,
250
380
  HEAD_LINE,
251
381
  PLAN_VOCABULARY,
382
+ MARKER_IN_TEXT,
252
383
  classifyStatus,
253
384
  phaseHeading,
385
+ headingOfSection,
386
+ sectionOfHeading,
387
+ CELL_KEY,
388
+ LANE_CLOSED_WORDS,
389
+ PARTIAL_PHASE_WORDS,
254
390
  sectionHeading,
255
391
  terminalSafe
256
392
  };
package/dist/config.d.ts CHANGED
@@ -95,6 +95,10 @@ declare const jackherdConfigSchema: z.ZodObject<{
95
95
  de: "de";
96
96
  en: "en";
97
97
  }>>;
98
+ boardLanguage: z.ZodOptional<z.ZodEnum<{
99
+ de: "de";
100
+ en: "en";
101
+ }>>;
98
102
  planDirectory: z.ZodDefault<z.ZodString>;
99
103
  board: z.ZodPrefault<z.ZodObject<{
100
104
  pattern: z.ZodDefault<z.ZodString>;
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PLAN_LANGUAGE,
3
3
  RESPONSE_ADDRESS
4
- } from "./chunk-JDO6O3UO.js";
4
+ } from "./chunk-O365KHHP.js";
5
5
 
6
6
  // src/config/json-schema.ts
7
7
  import { z as z4 } from "zod";
@@ -720,7 +720,10 @@ var jackherdConfigSchema = z3.strictObject({
720
720
  responseLanguage: language.default(PLAN_LANGUAGE.en),
721
721
  /** Whether replies address the user with the familiar or the polite form where the language has both (German: du or Sie). */
722
722
  responseAddress: z3.enum(Object.values(RESPONSE_ADDRESS)).default(RESPONSE_ADDRESS.informal),
723
+ /** The language new plans and the plan templates are written in. Boards and plans are read in every known language whatever this says. */
723
724
  planLanguage: language.default(PLAN_LANGUAGE.en),
725
+ /** The language jh writes the board in (history lines, lane rows, waiting rows); `planLanguage` when left out. */
726
+ boardLanguage: language.optional(),
724
727
  planDirectory: z3.string().min(1).default("docs/plans"),
725
728
  /** Where the boards live and what the project calls them. */
726
729
  board: boardSchema.prefault({}),
@@ -3,6 +3,8 @@ declare const PLAN_LANGUAGE: {
3
3
  readonly en: "en";
4
4
  };
5
5
  type PlanLanguage = (typeof PLAN_LANGUAGE)[keyof typeof PLAN_LANGUAGE];
6
+ /** The languages to try when reading: `language` first, then every other known one. */
7
+ declare const languagesFrom: (language: PlanLanguage) => PlanLanguage[];
6
8
  /** The language's name as a reply or a question says it. */
7
9
  declare const LANGUAGE_NAME: Record<PlanLanguage, string>;
8
10
  declare const RESPONSE_ADDRESS: {
@@ -106,6 +108,73 @@ declare const WAITING_VOCABULARY: Record<PlanLanguage, {
106
108
  statusAlso: readonly string[];
107
109
  open: readonly string[];
108
110
  }>;
111
+ /**
112
+ * The closed set of keys a section carries in a marker, `<!-- jh: <key> -->` on the line directly under its heading.
113
+ * The marker names the section whatever language the heading is written in.
114
+ */
115
+ declare const SECTION_KEY: {
116
+ readonly waiting: "waiting";
117
+ readonly appointments: "appointments";
118
+ readonly lanes: "lanes";
119
+ readonly goal: "goal";
120
+ readonly history: "history";
121
+ readonly plans: "plans";
122
+ readonly decisions: "decisions";
123
+ readonly implementation: "implementation";
124
+ readonly model: "model";
125
+ readonly dependencies: "dependencies";
126
+ readonly documentation: "documentation";
127
+ readonly forYou: "for-you";
128
+ readonly next: "next";
129
+ };
130
+ type SectionKey = (typeof SECTION_KEY)[keyof typeof SECTION_KEY];
131
+ declare const isSectionKey: (value: string) => value is SectionKey;
132
+ /**
133
+ * The marker key of each line of the head section (`- **Stand:** <!-- jh: head-status --> …`). The marker names the
134
+ * line whatever language its label is written in.
135
+ */
136
+ declare const HEAD_KEY: Record<HeadLine, string>;
137
+ /**
138
+ * The closed set of keys a line that opens with bold words carries in a marker
139
+ * (`**Kein Review-Gate** <!-- jh: no-review-gate --> reason`). The marker names the line whatever language it is in.
140
+ */
141
+ declare const LINE_KEY: {
142
+ readonly noReviewGate: "no-review-gate";
143
+ readonly purpose: "purpose";
144
+ };
145
+ /**
146
+ * The closed set of keys a table column carries in a marker in its header cell (`Hängt ab von <!-- jh: depends-on -->`).
147
+ * The marker names the column whatever language the header is written in.
148
+ */
149
+ declare const COLUMN_KEY: {
150
+ readonly since: "since";
151
+ readonly item: "item";
152
+ readonly origin: "origin";
153
+ readonly unblocks: "unblocks";
154
+ readonly status: "status";
155
+ readonly when: "when";
156
+ readonly action: "action";
157
+ readonly dependsOn: "depends-on";
158
+ readonly writes: "writes";
159
+ readonly serialMarker: "serial-marker";
160
+ };
161
+ type ColumnKey = (typeof COLUMN_KEY)[keyof typeof COLUMN_KEY];
162
+ /** The header names the column `key` goes by in `language`: its name first, then the names that count as the same column. */
163
+ declare const columnWords: (key: ColumnKey, language: PlanLanguage) => readonly string[];
164
+ /**
165
+ * The closed set of keys a status carries in a marker in its cell (`erledigt <!-- jh: status-done -->`): `open` for a
166
+ * board row that still waits, the others for the status of a plan. The marker names the status whatever language the
167
+ * cell is written in.
168
+ */
169
+ declare const STATUS_KEY: {
170
+ readonly open: "status-open";
171
+ readonly done: "status-done";
172
+ readonly abandoned: "status-abandoned";
173
+ readonly inProgress: "status-in-progress";
174
+ readonly draft: "status-draft";
175
+ };
176
+ type StatusKey = (typeof STATUS_KEY)[keyof typeof STATUS_KEY];
177
+ declare const isStatusKey: (value: string) => value is StatusKey;
109
178
  declare const isPlanLanguage: (value: string) => value is PlanLanguage;
110
179
  declare const PLAN_STATUS: {
111
180
  readonly done: "done";
@@ -173,6 +242,8 @@ type PlanVocabulary = {
173
242
  phase: string;
174
243
  /** Opens the line by which a phase says it has no review gate on purpose: `**Kein Review-Gate:** <reason>`. */
175
244
  noReviewGate: string;
245
+ /** Opens the line directly under a phase heading that says in one sentence what is different afterwards, for whom: `**Worum es geht:** <sentence>`. */
246
+ phasePurpose: string;
176
247
  /** Marks a lane as closed when its name cell contains it. */
177
248
  laneClosed: string;
178
249
  /** Stands in a board row's phases cell when that pull request builds only part of the phase. */
@@ -196,10 +267,35 @@ type PlanVocabulary = {
196
267
  statusWords: Record<PlanStatus, StatusWord>;
197
268
  };
198
269
  declare const PLAN_VOCABULARY: Record<PlanLanguage, PlanVocabulary>;
199
- /** Classifies a free-text `Status:` line; the first matching status in precedence order wins. */
270
+ /** A `<!-- jh: <key> -->` marker anywhere in a text; the key is group 1. */
271
+ declare const MARKER_IN_TEXT: RegExp;
272
+ /**
273
+ * Classifies a free-text `Status:` line. A status marker (`<!-- jh: status-done -->`) decides, any language; else the
274
+ * first matching status in precedence order of the words of `language`, then of any other known language.
275
+ */
200
276
  declare const classifyStatus: (language: PlanLanguage, statusText: string) => PlanStatus;
201
277
  /** `### Phase 3: Title` as the plan spells it in `language`. */
202
278
  declare const phaseHeading: (language: PlanLanguage, id: string, title: string) => string;
279
+ /** The heading text of the section `key` names, as `language` writes it. */
280
+ declare const headingOfSection: (key: SectionKey, language: PlanLanguage) => string;
281
+ /** The key and language of the section a heading text names, read the way a caller spells it; `null` when no language does. */
282
+ declare const sectionOfHeading: (headingText: string) => {
283
+ key: SectionKey;
284
+ language: PlanLanguage;
285
+ } | null;
286
+ /**
287
+ * The closed set of keys a lanes-table cell carries in a marker: `lane-closed` in the name cell of a lane that was
288
+ * cleaned up, `partial-phase` in the phases cell of a row that builds only part of a phase.
289
+ */
290
+ declare const CELL_KEY: {
291
+ readonly laneClosed: "lane-closed";
292
+ readonly partialPhase: "partial-phase";
293
+ };
294
+ type CellKey = (typeof CELL_KEY)[keyof typeof CELL_KEY];
295
+ /** The words of every known language that mark a lane as cleaned up. */
296
+ declare const LANE_CLOSED_WORDS: readonly string[];
297
+ /** The words of every known language that mark a row as building only part of a phase. */
298
+ declare const PARTIAL_PHASE_WORDS: readonly string[];
203
299
  declare const sectionHeading: (language: PlanLanguage, section: keyof PlanVocabulary["sections"]) => string;
204
300
 
205
301
  /**
@@ -260,11 +356,13 @@ type PhaseWorkFields = {
260
356
  /**
261
357
  * `work` is set by the phase source and says nothing for a phase the worktree left as the base has it; a merged phase
262
358
  * is touched as well. `deferred` holds the checkboxes moved to another phase; they are not among `items`, so they
263
- * count neither as open nor as done.
359
+ * count neither as open nor as done. `purpose` is the sentence of the purpose line directly under a `### Phase`
360
+ * heading (`phasePurposeOf`); absent when the phase has none.
264
361
  */
265
362
  type PlanPhase = {
266
363
  id: string;
267
364
  title: string;
365
+ purpose?: string;
268
366
  items: ChecklistItem[];
269
367
  deferred?: DeferredItem[];
270
368
  } & Partial<PhaseWorkFields>;
@@ -283,6 +381,15 @@ declare const endsPhase: (line: string) => boolean;
283
381
  declare const isCheckboxLine: (line: string) => boolean;
284
382
  declare const isOpenCheckboxLine: (line: string) => boolean;
285
383
  declare const phaseHeadingPattern: (language: PlanLanguage) => RegExp;
384
+ /** The bold opening of a line (`**Label:**` or `**Label**:`): the label and the text after it. */
385
+ declare const BOLD_OPENING: RegExp;
386
+ /**
387
+ * The sentence of a purpose line, `**Worum es geht:** <sentence>`, the colon inside or outside the bold; null for any
388
+ * other line and for a purpose line without text after the colon.
389
+ */
390
+ declare const phasePurposeOf: (line: string, language: PlanLanguage) => string | null;
391
+ /** The first non-empty line under a heading decides whether a phase has its purpose line; once `decided`, later lines never do. */
392
+ declare const decidesPurpose: (decided: boolean, line: string) => boolean;
286
393
  /**
287
394
  * The phase an open checkbox line is moved to with `→ P<n>` at its end; null for any other line, a ticked box,
288
395
  * a ✅/⬜ table row, and an arrow to the box's own phase `phaseId`. The one rule the parser and the tally share.
@@ -556,6 +663,8 @@ type PhaseWithOpenBoxes = PhaseWithoutGate & {
556
663
  };
557
664
  /** A `PhaseWithOpenBoxes` whose tasks are all ticked on the base branch. */
558
665
  type MergedPhaseWithOpenBoxes = PhaseWithOpenBoxes;
666
+ /** A `### Phase` heading (1-based line) whose section has tasks and no purpose line directly under the heading. */
667
+ type PhaseWithoutPurpose = PhaseHeading;
559
668
  /** The ids of the `### Phase` headings of a plan. */
560
669
  declare const phaseIdsOfPlan: (markdown: string, language: PlanLanguage) => Set<string>;
561
670
  /**
@@ -565,6 +674,13 @@ declare const phaseIdsOfPlan: (markdown: string, language: PlanLanguage) => Set<
565
674
  * plan does not turn red when it is touched.
566
675
  */
567
676
  declare const newPhasesWithoutReviewGate: (markdown: string, language: PlanLanguage, previousIds: ReadonlySet<string>) => PhaseWithoutGate[];
677
+ /**
678
+ * The phases a plan gained against `previousIds` that have checkboxes and no purpose line as the first non-empty line
679
+ * under the heading (`phasePurposeOf`). Phases the plan already had are not read, like in `newPhasesWithoutReviewGate`.
680
+ */
681
+ declare const newPhasesWithoutPurpose: (markdown: string, language: PlanLanguage, previousIds: ReadonlySet<string>) => PhaseWithoutPurpose[];
682
+ /** Every phase of the plan with tasks and without a purpose line: for a plan read whole, where no phase is older than the change. */
683
+ declare const phasesWithoutPurpose: (markdown: string, language: PlanLanguage) => PhaseWithoutPurpose[];
568
684
  /** Whether the document has a lanes table (`## Spuren`): a board, not a plan. */
569
685
  declare const hasLanesTable: (markdown: string, language: PlanLanguage) => boolean;
570
686
  /**
@@ -593,6 +709,8 @@ declare const boardRowsWithoutPhase: (markdown: string, language: PlanLanguage,
593
709
  type PhaseProgress = {
594
710
  id: string;
595
711
  title: string | null;
712
+ /** The phase's purpose sentence; absent when the plan has none or lacks the phase. */
713
+ purpose?: string;
596
714
  done: number;
597
715
  total: number;
598
716
  items: ChecklistItem[];
@@ -709,4 +827,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
709
827
  */
710
828
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
711
829
 
712
- export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, CARD_FIELDS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, HEAD_LINE, HEAD_STATUS, type HeadLine, type HeadPhase, type HeadStatus, type HeadVerdict, LANE_PROMPT_OPENING, LANGUAGE_NAME, type LaneProgress, type LongPhaseTitle, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithOpenBoxes, type PhaseWithoutGate, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHead, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RECOMMENDED_MARK, REPORT_FIELDS, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, checkPlanHead, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, openBoxesOfPhase, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanHead, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
830
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, BOLD_OPENING, CARD_FIELDS, CELL_KEY, COLUMN_KEY, type CellKey, type ChecklistItem, type ColumnKey, type ColumnModel, type DeferredItem, type DriftFinding, HEAD_KEY, HEAD_LINE, HEAD_STATUS, type HeadLine, type HeadPhase, type HeadStatus, type HeadVerdict, LANE_CLOSED_WORDS, LANE_PROMPT_OPENING, LANGUAGE_NAME, LINE_KEY, type LaneProgress, type LongPhaseTitle, MARKER_IN_TEXT, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, type ModelRow, NEXT_VOCABULARY, NO_DEPENDENCY_TABLE, PARTIAL_PHASE_WORDS, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PHASE_TITLE_LIMIT, PHASE_WORK, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithOpenBoxes, type PhaseWithoutGate, type PhaseWithoutPurpose, type PhaseWithoutRow, type PhaseWork, type PhaseWorkFields, type PlanHead, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RECOMMENDED_MARK, REPORT_FIELDS, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SECTION_KEY, SELF_MERGE_LABEL, SHORT_NAME_LIMIT, SHORT_NAME_PROBLEM, STANDING_APPROVAL_TEXTS, STATUS_KEY, type SectionKey, type ShortNameFinding, type ShortNameProblem, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusKey, type StatusWord, type StepProgress, WAITING_VOCABULARY, activeItem, boardRowsWithProse, boardRowsWithoutPhase, checkPlanHead, classifyStatus, columnModels, columnWords, countDecisions, criticalPath, currentPhase, currentPhaseOf, decidesPurpose, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, headingOfSection, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isSectionKey, isStatusKey, isTickedUnmerged, laneNameOf, laneProgress, languagesFrom, longPhaseTitles, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutPurpose, newPhasesWithoutReviewGate, onlyDocumentationOpen, openBoxesOfPhase, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parseModelTable, parsePlanHead, parsePlanPhases, parseSprintLanes, phaseCount, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdOf, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phasePurposeOf, phaseWaves, phaseWorkFields, phasesText, phasesWithoutDependencyRow, phasesWithoutPurpose, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, sectionOfHeading, shortNameFindings, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };