jackherd 1.17.0 → 1.18.1

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.
@@ -14,27 +14,24 @@ The formatter and linter config is the authority; where it is silent, match the
14
14
  - **All code in English**: file names, identifiers, type and component names, comments. User-facing strings go through the project's i18n system.
15
15
  - **Commit messages in English**, subject and body. The subject is a Conventional Commit (`fix(scope): …`); the body explains cause and consequence in prose.
16
16
  - **Plans and ADRs** are written in {{kit.planLanguage}}.
17
- - **Replies to the user** are in {{kit.responseLanguage}}, addressing the user with {{kit.responseAddress}}. That holds for every answer, every question and every report to the user; it does not change what is written into code, commits or plans.
17
+ - **Replies to the user** are in {{kit.responseLanguage}}, addressing the user with {{kit.responseAddress}}: every answer, question and report. Code, commits and plans keep their own language.
18
18
 
19
19
  ## Naming
20
20
 
21
- - **No abbreviations in variable names**: `scrollElement`, not `scrollEl`; `domObserver`, not `mo`. Established exceptions: `i`/`j` in tight loops, `el` in single-line callbacks, `err` in catch blocks. When in doubt, spell it out.
22
- - Keep naming aligned with the existing conventions and folder structure.
21
+ **No abbreviations in variable names**: `scrollElement`, not `scrollEl`. Established exceptions: `i`/`j` in tight loops, `el` in single-line callbacks, `err` in catch blocks.
23
22
 
24
23
  ## Comments & Documentation
25
24
 
26
25
  - Self-documenting code over comments: clear naming, pure functions, obvious control flow.
27
26
  - **No section-label comments** (`// Types`, `// Imports`): the code order speaks for itself.
28
27
 
29
- ### When to add a doc comment
30
-
31
28
  Add a doc comment (JSDoc, PHPDoc …) to an exported item when **any** of these is true:
32
29
 
33
30
  - Parameters aren't self-evident from their types (a `duration` param: what unit? what happens at 0?)
34
31
  - The function has non-obvious constraints or behaviour (serialization limits, a required context, a side-effect contract)
35
32
  - It has edge cases or a subtle contract a caller could easily violate
36
33
 
37
- A well-named function with typed parameters documents itself; one-liners that state the obvious (`/** Returns the sum */`) are noise.
34
+ A one-liner that states the obvious (`/** Returns the sum */`) is noise.
38
35
 
39
36
  ## Code Organization
40
37
 
@@ -44,51 +41,34 @@ A well-named function with typed parameters documents itself; one-liners that st
44
41
  2. **Pure functions**: no side effects, no I/O, predictable outputs
45
42
  3. **Higher-order functions**: functions that create side effects, orchestrate I/O, or compose other functions
46
43
 
47
- ```typescript
48
- import { foo } from './bar'
49
-
50
- export type Config = { ... }
51
-
52
- const parse = (input: string): Config => { ... }
53
- const validate = (config: Config): boolean => { ... }
54
-
55
- export const processFile = async (path: string): Promise<void> => {
56
- const config = parse(await readFile(path))
57
- if (validate(config)) await saveResult(config)
58
- }
59
- ```
60
-
61
44
  ## Framework as an Adapter, not Domain
62
45
 
63
- The UI framework is a **replaceable adapter layer**, not where domain logic lives. Keep business logic framework-free so the framework stays swappable and the logic stays unit-testable without a component harness.
46
+ The UI framework is a **replaceable adapter layer**, so the logic stays unit-testable without a component harness.
64
47
 
65
48
  - **Domain logic lives in framework-free modules** — calculations, validation, state machines, mapping, formatting. These modules import nothing from the UI framework.
66
- - **Components consume logic, they don't contain it.** A component body wires state to pure functions; a derived value that grows past trivial derivation becomes a pure function the component calls.
49
+ - **Components consume logic, they don't contain it.** A derived value that grows past trivial derivation becomes a pure function the component calls.
67
50
  - **Backend logic never touches the frontend framework.**
68
51
 
69
- **Litmus test:** if swapping the framework would force you to rewrite a source file that has no UI in it, that file has leaked framework coupling — extract the logic.
52
+ **Litmus test:** if swapping the framework would force you to rewrite a source file that has no UI in it, extract the logic.
70
53
 
71
54
  ## Code Reuse
72
55
 
73
- **Before implementing new functionality:**
74
-
75
- 1. **Search the codebase** for existing utilities, helpers, components and services.
56
+ 1. **Search the codebase** for existing utilities, helpers, components and services before implementing.
76
57
  2. **Reuse > reinvent**: if something does 80% of what you need, use it and extend it.
77
- 3. **One copy of each piece of logic**: extract and reuse.
78
- 4. **Shared placement**: generic helpers (formatters, validators) belong in the nearest shared module, not in a feature file. If more than one module could use it, it is shared.
58
+ 3. **One copy of each piece of logic**; a helper more than one module could use belongs in the nearest shared module.
79
59
 
80
- For UI: check the shared component library before building a button, input, modal or similar. Build a custom one only when the shared one is fundamentally incompatible, and write down why.
60
+ For UI: check the shared component library first. Build a custom one only when the shared one is fundamentally incompatible, and write down why.
81
61
 
82
62
  ## Design Review (Clean Code)
83
63
 
84
64
  After implementing, review every changed file through these lenses:
85
65
 
86
- 1. **Single Responsibility**: each function/module does one thing. If a description needs "and", split it.
66
+ 1. **Single Responsibility**: if a description needs "and", split it.
87
67
  2. **DRY**: more than 3 identical lines in two places → shared helper.
88
68
  3. **Dead code**: remove unused exports, unreachable branches, commented-out code.
89
- 4. **Naming**: names reveal intent. If you need a comment to explain what a variable holds, rename it.
90
- 5. **Small functions**: functions over ~20 lines are a smell; extract named sub-steps. Where the linter measures cognitive complexity, every `&&`, `||`, `??`, ternary and optional chain counts; extracting a named helper gives it its own budget, extracting a variable does not.
91
- 6. **Dependency direction**: pure utilities never import side-effectful modules. Arrows point inward.
69
+ 4. **Naming**: if you need a comment to explain what a variable holds, rename it.
70
+ 5. **Small functions**: over ~20 lines is a smell; extract named sub-steps. Where the linter measures cognitive complexity, every `&&`, `||`, `??`, ternary and optional chain counts; a named helper gets its own budget, a variable does not.
71
+ 6. **Dependency direction**: pure utilities never import side-effectful modules.
92
72
  7. **No flag arguments**: a boolean that switches behaviour → two functions.
93
73
  8. **Error surface**: handle only errors you can meaningfully recover from; let the rest propagate.
94
74
 
@@ -96,23 +76,17 @@ Apply these checks to **your own output** before presenting it, and fix violatio
96
76
 
97
77
  ## Generators
98
78
 
99
- Before editing a file that might be generated:
100
-
101
- 1. **Check the project's scripts** (`package.json`, `composer.json`, `Makefile`) for `gen`, `generate`, `codegen`.
102
- 2. **Run the generator** instead of editing by hand; a generated file is overwritten on the next run.
79
+ Before editing a file that might be generated, check the project's scripts (`package.json`, `composer.json`, `Makefile`) for `gen`, `generate`, `codegen`. **Run the generator** instead of editing by hand; the next run overwrites a hand edit.
103
80
 
104
81
  ## Validation & Types
105
82
 
106
83
  - The project's schema library for schemas, with types derived from the schema.
107
- - Validation lives once, at the boundary, not again in every layer.
108
- - Strict typing; an unknown value is parsed at the boundary, never typed as "anything".
84
+ - Validation lives once, at the boundary; an unknown value is parsed there, never typed as "anything".
109
85
  - Read the installed version of the schema library before writing against it.
110
86
 
111
87
  ### A fixed vocabulary is a typed constant
112
88
 
113
- Roles, modes, statuses, channels: every closed set of values has **one** constant next to its type (an `as const` object, an enum, a class constant), and every comparison, option id and schema literal reads from it.
114
-
115
- The constant earns its place at the **seams where a `string` arrives** (a query parameter, a form option id, a database row): there a literal comparison is unchecked, and `mode === 'per_km' ? 'per_km' : 'tiers'` maps every unknown value to a default in silence.
89
+ Roles, modes, statuses, channels: every closed set of values has **one** constant next to its type (an `as const` object, an enum, a class constant), and every comparison, option id and schema literal reads from it. It earns its place at the **seams where a `string` arrives** (a query parameter, a form option id, a database row), where a literal comparison is unchecked:
116
90
 
117
91
  ```ts
118
92
  // ✅ the constant is the vocabulary; a guard narrows the string, an unknown value is refused
@@ -127,31 +101,29 @@ if (isFeeMode(id)) handleChange(switchMode(form, id))
127
101
  mode: id === 'per_km' ? 'per_km' : 'tiers'
128
102
  ```
129
103
 
130
- Derive the schema from the constant, never the other way round, so constant, type and parser cannot disagree. Test fixtures may spell the literal.
104
+ Derive the schema from the constant, never the other way round. Test fixtures may spell the literal.
131
105
 
132
106
  ## Database Writes
133
107
 
134
108
  ### Never `upsert` on a generated natural key
135
109
 
136
- `upsert(row, { onConflict: '<natural key>' })` means "write into whichever row already holds that key" — and when the key was generated rather than supplied by the caller, that row belongs to someone else.
137
-
138
- The lesson behind this rule: order numbers drawn from a truncated clock collided for orders created within the same second, and an upsert on that number merged two customers' orders into one row — one name, both carts, the other order gone. The unique index was in place the whole time; the upsert asked it not to complain.
110
+ `upsert(row, { onConflict: '<natural key>' })` writes into whichever row already holds that key; when the key was generated, that row belongs to someone else. Order numbers drawn from a truncated clock collide within one second, and an upsert on them merges two customers' orders into one row.
139
111
 
140
112
  - **Create and update are separate statements.** `insert` for new rows, `update … where key = ?` for known ones.
141
113
  - **Scope an update by tenant as well as by key**, so a key replayed from a URL cannot reach another tenant's row.
142
- - **An update that matches nothing is an error, not an insert.** Silently creating a row from an unmatched identifier turns a query parameter into a write primitive.
114
+ - **An update that matches nothing is an error, not an insert.**
143
115
  - An upsert stays legitimate where the conflict target is a key the **caller owns** and the merge is the intent (a client-generated id, a per-user settings row).
144
116
 
145
117
  ### A pre-flight existence check is not uniqueness
146
118
 
147
- `SELECT count(*) WHERE key = ?` followed by an insert leaves a gap between two statements, and concurrent writers sit in it. **The unique index is the guarantee: insert against it and retry on the unique-violation error.** When retrying, **name the column** — a table with several unique indexes will otherwise answer a duplicate in another column by burning fresh values until the attempts run out.
119
+ A `SELECT` before an insert leaves a gap concurrent writers sit in. **The unique index is the guarantee: insert against it and retry on the unique-violation error.** When retrying, **name the column**, or a duplicate in another unique column burns fresh values until the attempts run out.
148
120
 
149
121
  ### Generated identifiers carry entropy, never a clock
150
122
 
151
- A value drawn from the clock is a bucket, not an identifier, and truncating it widens the bucket. Draw from the platform's CSPRNG (`crypto.getRandomValues`, `random_bytes`), keep the drawing in a **pure function** that takes the random values as an argument, and unit-test it. A generator whose only seam is a server function with a database call in it is a generator no test reaches.
123
+ Draw from the platform's CSPRNG (`crypto.getRandomValues`, `random_bytes`), keep the drawing in a **pure function** that takes the random values as an argument, and unit-test it.
152
124
 
153
125
  ## External Measurements
154
126
 
155
127
  ### Normalised to whole units at the adapter, like money to cents
156
128
 
157
- A routing provider answers in fractional metres and seconds, a payment provider in decimal currency. The adapter that receives the value rounds it **once**, to the unit the domain counts in (whole metres, whole seconds, whole cents), and nothing past the adapter rounds again. Two callers rounding for themselves round differently, or one forgets — and a billing boundary sits between them. The domain core may keep a defensive rounding as a guarantee against callers that are not the adapter; the adapter is where the rounding is decided, and its contract says so.
129
+ The adapter that receives a fractional measurement (metres, seconds, currency) rounds it **once**, to the unit the domain counts in, and nothing past the adapter rounds again: two callers rounding for themselves round differently, and a billing boundary sits between them. The domain core may keep a defensive rounding as a guarantee against callers that are not the adapter; the adapter's contract says where the rounding is decided.
@@ -1,62 +1,39 @@
1
1
  # Pull Requests
2
2
 
3
- How an agent opens a pull request so every PR reads consistently and is review- and deploy-ready. Applies whenever you run `gh pr create` or edit a PR body.
3
+ Applies whenever you run `gh pr create` or edit a PR body.
4
4
 
5
5
  ## Preconditions
6
6
 
7
7
  - **Open a PR only when the user asks.** Same rule as commit and push.
8
- - **Base = `{{kit.baseBranch}}`** unless the user names another.
9
- - **Work on a branch.** If the working branch is `{{kit.baseBranch}}`, branch first.
10
- - **Push before creating, with the user's go-ahead for the push.** The head branch must be on the remote so `gh pr create` has a target.
8
+ - **Base = `{{kit.baseBranch}}`** unless the user names another; on `{{kit.baseBranch}}` itself, branch first.
9
+ - **Push before creating, with the user's go-ahead for the push.**
11
10
  - **Drafts** (`merge.openAsDraft`: **{{kit.merge.openAsDraft}}**). When `on`, open every PR with `gh pr create --draft`; run `gh pr ready <n>` before handing it to the user to merge, or before waiting on its checks for your own `gh pr merge`.
12
- - **Check for an existing PR first** (`gh pr view <branch>`). If a prior PR for the same branch is already **merged**, the new one is a continuation — mark it in the title.
11
+ - **Check for an existing PR first** (`gh pr view <branch>`). If a prior PR for the same branch is already **merged**, the new one is a continuation.
13
12
 
14
13
  ## Title
15
14
 
16
- Conventional-commit shape, imperative, lower-case summary, no trailing period:
15
+ `type(scope): short imperative summary`, lower case, no trailing period.
17
16
 
18
- ```
19
- type(scope): short imperative summary
20
- ```
21
-
22
- - `type` ∈ `feat | fix | refactor | perf | docs | test | chore | build | ci`. Pick the type of the PR's **dominant** change, not the first commit.
17
+ - `type` ∈ `feat | fix | refactor | perf | docs | test | chore | build | ci`, by the PR's **dominant** change, not the first commit.
23
18
  - `scope` = the primary package, app or service.
24
19
  - **Continuation of a merged PR on the same branch:** append `(PART N)`.
25
20
 
26
21
  ## Body structure
27
22
 
28
- Fixed sections, in this order. Omit a section only when it has no content — never leave a placeholder.
29
-
30
- 1. **Context** — one or two lines: link the plan (`{{kit.planDirectory}}/…`), the predecessor PR, or the issue. State which phase or slice this is.
31
- 2. **Changes** — grouped **by theme**, not commit by commit. Use `###` subheadings or a table when the PR spans several areas. A reviewer must grasp the shape without reading the diff.
32
- 3. **Verification** — the checks you actually ran, with their numbers, and the review gates with their verdicts. Only checks you ran.
33
- 4. **Deploy checklist** — required whenever the PR touches **database migrations, secrets, message or mail templates, or a new or changed service**. Order the steps **causally**: database → secrets → templates → services (consumer before publisher) → apps. Use `- [ ]` so it doubles as a runbook. Flag any migration already applied to an environment and which ones still need it.
34
- 5. **Out of scope / follow-ups** — what was deliberately deferred, so reviewers don't ask.
35
-
36
- ## Diagrams (Mermaid)
37
-
38
- Add a fenced ` ```mermaid ` block **when it removes reader effort**:
39
-
40
- - a **new service** or a multi-hop **data flow** (who reads what, who publishes to whom),
41
- - a **state machine** or transition set,
42
- - a **security or replication mechanism** where the failure mode is the point.
43
-
44
- Keep each diagram to one idea; two small diagrams beat one dense one. Small or single-file PRs need none.
45
-
46
- Write edge labels as `-->|"label"|` (`-- text -->` breaks on a `--` in it); `jh mermaid <body file>` must exit 0 before `gh pr create` or `gh pr edit`.
23
+ Fixed sections, in this order; omit one only when it has no content, never leave a placeholder.
47
24
 
48
- ## Review order (large PRs)
25
+ 1. **Context** — link the plan (`{{kit.planDirectory}}/…`), the predecessor PR, or the issue; name the phase or slice.
26
+ 2. **Changes** — grouped **by theme**, not commit by commit, so a reviewer grasps the shape without the diff.
27
+ 3. **Verification** — the checks with their numbers, and the review gates with their verdicts. Only checks you ran.
28
+ 4. **Deploy checklist** — whenever the PR touches **database migrations, secrets, message or mail templates, or a new or changed service**. Order the steps **causally**: database → secrets → templates → services (consumer before publisher) → apps. Use `- [ ]`, and flag any migration already applied to an environment and which ones still need it.
29
+ 5. **Out of scope / follow-ups** — what was deliberately deferred.
49
30
 
50
- When a PR spans many files, add a short **suggested review order** — **pure/domain logic first, adapters next, UI and wiring last**. A `| # | Area | Start here | Why first |` table works well.
31
+ ## Diagrams and review order
51
32
 
52
- ## Language
33
+ Add a ` ```mermaid ` block **when it removes reader effort**: a new service or a multi-hop data flow, a state machine, a security or replication mechanism whose failure mode is the point. One idea per diagram. Write edge labels as `-->|"label"|`; `jh mermaid <body file>` must exit 0 before `gh pr create` or `gh pr edit`.
53
34
 
54
- PR **title and body are English**, whatever language the conversation runs in, so the history reads the same for every reader.
35
+ A PR across many files adds a **suggested review order**: pure/domain logic first, adapters next, UI and wiring last.
55
36
 
56
- ## Anti-patterns
37
+ ## Language and content
57
38
 
58
- - Pasting the raw commit log as the body — group by theme instead.
59
- - A deploy section out of causal order (a deploy before its migration).
60
- - Claiming tests or reviews that did not run.
61
- - Secrets, tokens or full connection strings anywhere in the body.
62
- - A diagram that restates the title without adding structure.
39
+ PR **title and body are English**, whatever language the conversation runs in. Never: Secrets, tokens or full connection strings anywhere in the body.
@@ -12,9 +12,7 @@ All new or changed code **must** have tests.
12
12
  | File naming | the name and place the runner finds; follow where the project's existing tests live |
13
13
  | E2E runner | Playwright, or the project's own |
14
14
 
15
- ### Run the tests where the runner can see the project's config
16
-
17
- A runner started from the wrong directory can fail before it runs a test: a watcher exhausting file descriptors, a config file or compiler setting resolved from the working directory and not found. **Read the failure before calling it a broken test** — an environment error reads like a missing dependency in code nobody touched, and re-running the same wrong invocation with the changes stashed proves nothing. Run from the package or module directory the config lives in, and leave the user's dev servers running. Where the project has its own testing rule, it names the failures its runner shows.
15
+ A runner started from the wrong directory can fail before it runs a test (file descriptors exhausted, a config not found). **Read the failure before calling it a broken test**: an environment error reads like a missing dependency in code nobody touched, and re-running the same wrong invocation with the changes stashed proves nothing. Run from the package or module directory the config lives in, and leave the user's dev servers running. The project's own testing rule names the failures its runner shows.
18
16
 
19
17
  ## What to Test (decision tree)
20
18
 
@@ -45,23 +43,19 @@ Is it a new user-facing feature?
45
43
 
46
44
  ### Test-first: money, identifiers, state
47
45
 
48
- The coverage target is the same either way. The **order** is what buys something: a test that has to pass before the code exists cannot be satisfied by logic buried inside a server function with a database call in it. Writing it first forces a seam — the random values or the clock arrive as an argument, so the logic is a pure function a unit test reaches directly. See `code-style.md` → Generated identifiers carry entropy, never a clock.
46
+ The **order** buys the seam: a test that must pass before the code exists forces the random values or the clock to arrive as an argument, so the logic is a pure function a unit test reaches. See `code-style.md` → Generated identifiers carry entropy, never a clock.
49
47
 
50
48
  ## Test Priority Order
51
49
 
52
- Write tests in this order — stop when coverage expectations are met:
53
-
54
- 1. **Unit tests** — pure functions, validators, mappers
55
- 2. **Integration tests** — service interactions, database operations
56
- 3. **E2E tests** — user flows via the E2E runner (only when requested)
50
+ Unit tests (pure functions, validators, mappers), then integration tests (service interactions, database operations), then E2E tests (only when requested); stop when coverage expectations are met.
57
51
 
58
52
  ## Mock only the lowest-level dependency
59
53
 
60
- **Mock only the lowest-level dependency** (adapters, repositories). The use-case logic still runs; only I/O is stubbed. Where the runner's mock replaces a whole module, it does so for the entire file, so mocking an intermediate module (a use-case in a handler test) breaks every other test there. Where the project has its own testing rule, it names the mocking call of its runner.
54
+ **Mock only the lowest-level dependency** (adapters, repositories): the use-case logic still runs. Where the runner's mock replaces a whole module for the entire file, mocking an intermediate module (a use-case in a handler test) breaks every other test there.
61
55
 
62
56
  ## Before every commit
63
57
 
64
- `/pre-commit` runs `bunx jackherd check`, which measures types, lint, tests, staged build artifacts and the project's own checks. Beyond that, the judgement points:
58
+ `/pre-commit` runs `bunx jackherd check` (types, lint, tests, staged build artifacts, the project's own checks). Beyond that:
65
59
 
66
60
  1. Version bumped if the change affects a published package (minor = feature, patch = fix)
67
- 2. `code-style.md` holds in every changed file: its code order, and its design review passed, dead code (unused exports, unreachable branches, commented-out code) included
61
+ 2. `code-style.md` holds in every changed file: its code order and its design review, dead code included
@@ -9,81 +9,68 @@ You are the **Implementer**. Your job is to carry tasks through completely: expl
9
9
 
10
10
  ## Golden Rules
11
11
 
12
- 1. **Tool calls before text.** Search, read, write, verify — all via tools. Every plan you state in prose is followed by the tool call that carries it out. Exploration counts as tool use.
12
+ 1. **Tool calls before text.** Every plan you state in prose is followed by the tool call that carries it out.
13
13
  2. **A commit or push waits for the user's explicit confirmation.** `/pre-commit` obtains it via `AskUserQuestion`.
14
14
  3. **Change only what was asked for.** Unasked refactorings and extra abstractions stay out.
15
15
  4. **A bug fix starts from a command that goes red on it.** Before you edit a line, name that command. If you cannot, run `/diagnose` — it builds the command first and hands the fix back here.
16
16
 
17
17
  ## Step 0: Model Gate (plans with a `{{kit.heading.modelRecommendations}}` table)
18
18
 
19
- 1. Read the model recommended for the phase from the table.
20
- 2. Compare it with the model your current run is on:
21
- - **Same model** → continue with step 1.
22
- - **Any other model, stronger or weaker** (the recommendation is a choice, not a floor) → delegate the phase to a subagent with the recommended model: `Agent(subagent_type: 'implement', model: <recommended>, description: …, prompt: …)`, the `description` as in Step 0b item 3. Hand it what you already know (files located, decisions made, the phase's review gate) so it does not repeat the exploration. The prompt names the phase as `Phase P<n>` and the plan by its path from the repository root: then `model-gate` stops a start on a weaker model and `model-log` records the model for the herd. It ticks its phase's tasks itself as it finishes them (see Plan Hygiene). You stay the orchestrator: the review gate, gate verification, the arc42 and architecture checks and `/pre-commit` stay with you.
19
+ Compare the model the table recommends for the phase with the model your run is on:
20
+
21
+ - **Same model** → continue with step 1.
22
+ - **Any other model, stronger or weaker** (the recommendation is a choice, not a floor) → delegate the phase to `Agent(subagent_type: 'implement', model: <recommended>, description: …, prompt: …)`, the `description` as in Step 0b item 3. Hand it what you already know (files located, decisions made, the phase's review gate). The prompt names the phase as `Phase P<n>` and the plan by its path from the repository root, so `model-gate` stops a start on a weaker model and `model-log` records the model for the herd. It ticks its phase's tasks itself (Plan Hygiene). You stay the orchestrator: the review gate, the arc42 and architecture checks and `/pre-commit` stay with you.
23
23
 
24
24
  ## Step 0b: Parallel Gate (plans with a `{{kit.heading.dependencies}}` table)
25
25
 
26
26
  When the user asks for more than one phase:
27
27
 
28
- 1. **Read the computed waves:** run `jh check --parallel <plan>`. It prints one line per wave of phases that can run side by side, in order (`wave 1: P19 ∥ P20`), then `critical path: …` and `in no wave: …`; wave 1 can start now, each later wave once the waves before it are done. A phase is ready once every `{{kit.heading.dependsOn}}` entry is ticked off with its review gate; one with an external gate (`User: …`) never is. Two phases share a wave only with no overlapping backticked path under `{{kit.heading.writes}}` (a `like N` entry adds that phase's set), no shared `{{kit.heading.serialMarker}}`, and no release of the same package: a marker `Release <package> <version>`, or the package's `CHANGELOG.md` or `package.json` under `{{kit.heading.writes}}`. `no dependency table`: the phases run one after the other. `no phase is ready`: every open phase waits, is done, or is in no wave.
29
- 2. **Batch:** the requested phases in wave 1. A requested phase in a later wave waits, naming the phase it collides with. One in no wave is not ready for one of these reasons: it has an external gate (ask whether the gate holds), its `like N` entry names an unknown phase or runs in a cycle, its `{{kit.heading.writes}}` cell has no backticked path, or it depends on such a phase; say which. The command checks paths, markers and releases only: two phases that collide in content stay your judgement, so serialise them yourself.
30
- 3. **Dispatch the batch in one message**, one `Agent(subagent_type: 'implement', model: <from the model table>, description: …, prompt: …)` per phase, in the same checkout: the disjoint `{{kit.heading.writes}}` sets are the isolation. The `description` starts with the short name of the task the subagent works on, the first open one of its phase, then the phase: `<Name> · P<n>`; the herd puts its `●` on that task. Without a short name, the phase alone. They may run in the background; a hand-over (Step 7) waits until all are back. Each prompt names its phase (`Phase P<n>` and the plan's path, as in Step 0) and its `{{kit.heading.writes}}` set as the **only** paths it may edit, besides ticking its own phase's task lines in the plan, and states that it ticks the task checkboxes of its own phase as it finishes each one (Plan Hygiene), and leaves the review gate, commits and `/pre-commit` to you — it reports changed files, test results and the gate items it ran.
31
- 4. **When the batch returns:** check each report against its set (`git diff --stat`; an edit outside the set is a finding; a ticked line outside its own phase is one too), tick what it left open, walk each review gate. Gate items that write outside a phase's set (documentation, a changelog entry, plan synchronisation) are yours, at the wave's end. Then recompute the ready set and dispatch the next batch.
28
+ 1. **Read the computed waves:** `jh check --parallel <plan>` prints one line per wave of phases that can run side by side (`wave 1: P19 ∥ P20`), then `critical path: …` and `in no wave: …`. Wave 1 can start now, each later wave once the waves before it are done. A phase is ready once every `{{kit.heading.dependsOn}}` entry is ticked off with its review gate; one with an external gate (`User: …`) never is. Two phases share a wave only with no overlapping path under `{{kit.heading.writes}}`, no shared `{{kit.heading.serialMarker}}` and no release of the same package: a marker `Release <package> <version>`, or the package's `CHANGELOG.md` or `package.json` under `{{kit.heading.writes}}`. `no dependency table`: the phases run one after the other. `no phase is ready`: every open phase waits, is done, or is in no wave.
29
+ 2. **Batch:** the requested phases in wave 1. A requested phase in a later wave waits, naming the phase it collides with. One in no wave is not ready (an external gate, an unknown or cyclic `like N`, a `{{kit.heading.writes}}` cell without a backticked path, or a dependency on such a phase); say which, and ask whether an external gate holds. The command checks paths, markers and releases only: two phases that collide in content you serialise yourself.
30
+ 3. **Dispatch the batch in one message**, one `Agent(subagent_type: 'implement', model: <from the model table>, description: …, prompt: …)` per phase, in the same checkout. The `description` is `<Name> · P<n>`, with the short name of the first open task of its phase (the herd puts its `●` there), or the phase alone. Each prompt names its phase as in Step 0 and its `{{kit.heading.writes}}` set as the **only** paths it may edit besides its own phase's task lines, and states that it ticks the task checkboxes of its own phase as it finishes each one; the review gate, commits and `/pre-commit` stay with you, and it reports changed files, test results and the gate items it ran. They may run in the background; a hand-over (Step 7) waits until all are back.
31
+ 4. **When the batch returns:** check each report against its set (`git diff --stat`; an edit outside the set, or a tick outside its own phase, is a finding), tick what it left open, walk each review gate. Gate items that write outside a phase's set (documentation, a changelog entry, plan synchronisation) are yours, at the wave's end. Then re-run `jh check --parallel` and dispatch the next batch.
32
32
  5. **Commits stay serial and with you:** one `/pre-commit` per finished phase, in phase order; in a lane under `/orchestrate`, one per pull request (orchestrate skill § 5).
33
33
 
34
34
  ## Plan Hygiene (when implementing from a plan in `{{kit.planDirectory}}/`)
35
35
 
36
- - **Tick off tasks as you go.** As soon as a task is done, set `- [ ]` → `- [x]` in the plan document, right then, so the plan reflects real progress and the herd shows it while the phase runs.
37
- - **A subagent ticks only its own phase,** and only with `Edit` on the exact task line (enough of it to be unique in the file). Never `Write` the plan or rewrite it with a script: parallel subagents share the file, and `Edit` applies its replacement to the file as it is on disk, so ticks in other phases survive, while a whole-file write from an older read erases them. A line that is not unique, and every review-gate item, stays with the caller.
36
+ - **Tick off tasks as you go:** `- [ ]` → `- [x]` the moment a task is done, so the herd shows progress while the phase runs.
37
+ - **A subagent ticks only its own phase,** and only with `Edit` on the exact task line (enough of it to be unique). Never `Write` the plan or rewrite it with a script: a whole-file write from an older read erases the ticks parallel subagents made. A line that is not unique, and every review-gate item, stays with the caller.
38
38
  - **A task you add, or one the user adds later,** takes the form of `/plan` step 2: `- [ ] **Name**: …`.
39
- - **The review gate is binding.** A phase is finished once its review gate has been walked through completely and ticked off; only then comes the next phase or `/pre-commit`. Gate items that name a skill (`/uncle-bob`, `/security-audit`, …) are ticked after the skill has actually run.
39
+ - **The review gate is binding.** A phase is finished once its review gate has been walked through completely and ticked off; only then comes the next phase or `/pre-commit`. A gate item that names a skill is ticked after the skill has actually run.
40
40
 
41
41
  ## Step 1: Explore
42
42
 
43
- Before you write a single line:
44
-
45
- 0. A phase not yet in the plan gets its skeleton there first (heading, origin, tasks, review gate), before any research, so the herd never shows it as missing.
46
- 1. Search with `Grep` / `Glob` / `Read` for existing utilities, components and domain types that cover 80%+ of the need. For deeper research dispatch an `explore` subagent.
43
+ 0. A phase not yet in the plan gets its skeleton there first (heading, origin, tasks, review gate), so the herd never shows it as missing.
44
+ 1. Search for existing utilities, components and domain types that cover 80%+ of the need; for deeper research dispatch an `explore` subagent.
47
45
  2. Run the generator for a generated file instead of editing it (`.claude/rules/kit/code-style.md` → Generators).
48
46
 
49
47
  ## Step 2: Implement
50
48
 
51
- **Report each task before you start it.** Mark it in progress in your own task list; without one, run `jh task start "<task text>"`, and `jh task done` when it is finished. The herd shows the task you work on from that signal.
49
+ **Report each task before you start it**: in your own task list, or without one `jh task start "<task text>"` and `jh task done` when it is finished. The herd shows the task you work on from that signal.
52
50
 
53
- **Money, identifiers, state transitions: the test comes first** — write it, run it, see it red, then implement. `.claude/rules/kit/testing.md` → Test-first carries the reason.
54
-
55
- Follow `.claude/rules/kit/code-style.md`: types → pure functions → higher-order functions, names that reveal intent, no section-label comments.
51
+ **Money, identifiers, state transitions: the test comes first** (`.claude/rules/kit/testing.md` → Test-first).
56
52
 
57
53
  Write code and tests with Write and Edit, never through a here-document or a script in Bash: such a call shows the reviewer no diff, and an unquoted or unreadable one can stop at a guard.
58
54
 
59
55
  ## Step 3: Verify
60
56
 
61
- After each changed file:
62
-
63
- 1. Types and lint clean: `{{kit.command.typecheck}}` in the package, `{{kit.command.lint}}`.
64
- 2. No unused imports, no dead code, no commented-out leftovers.
57
+ After each changed file: `{{kit.command.typecheck}}` in the package and `{{kit.command.lint}}` clean, no dead code.
65
58
 
66
59
  ## Step 4: Write Tests
67
60
 
68
- Follow the decision tree in `.claude/rules/kit/testing.md`. Name and place test files as `.claude/rules/kit/testing.md` → Runner & File Placement says; run them with `{{kit.command.test}}`. For changes in a shared package, also run the tests of the packages that consume it.
61
+ Follow the decision tree in `.claude/rules/kit/testing.md`; run the tests with `{{kit.command.test}}`. For a change in a shared package, also run the tests of the packages that consume it.
69
62
 
70
63
  ## Step 4b: Look at It
71
64
 
72
- A change a user sees — a page, a route, a redirect, a printed document — is looked at in the running app before `/pre-commit`. Unit tests prove the pieces; only the running app proves they meet. Use a temporary end-to-end spec (Playwright or the project's E2E runner) or the browser, take a screenshot per state, and report what each shows. Delete the temporary spec before the commit unless the plan asks for an end-to-end test.
65
+ A change a user sees (a page, a route, a redirect, a printed document) is looked at in the running app before `/pre-commit`: unit tests prove the pieces, only the running app proves they meet. Use a temporary end-to-end spec or the browser, a screenshot per state, and report what each shows; delete the temporary spec before the commit unless the plan asks for one. No visible change → skip, and say so in the report.
73
66
 
74
- No visible change (a domain package, a migration, a service) → skip, and say so in the report.
67
+ ## Step 5: arc42 and Architecture
75
68
 
76
- ## Step 5: arc42 Check
77
-
78
- Before `/pre-commit`: apply the filter in `.claude/rules/kit/arc42.md` and, on a hit, **ask the user concretely** whether the architecture documentation should be extended. No hit (bug fix, styling, small tweak) → skip without asking.
79
-
80
- ## Step 5b: Architecture Check
81
-
82
- If the change touched a service, a domain package or a data layer: apply the filter in `.claude/rules/kit/architecture-files.md` and, on a hit, **ask the user concretely** whether the unit's `ARCHITECTURE.md` needs a new invariant, ownership entry or accepted risk.
69
+ Before `/pre-commit`: apply the filter in `.claude/rules/kit/arc42.md`, and for a service, a domain package or a data layer the one in `.claude/rules/kit/architecture-files.md`. On a hit, **ask the user concretely**; no hit (bug fix, styling, small tweak) → skip without asking.
83
70
 
84
71
  ## Step 6: Pre-Commit
85
72
 
86
- Once all tests are green, types are clean, the change has been looked at (Step 4b) and the arc42 check is done: call `/pre-commit`. It routes to the reviewers and obtains the commit confirmation from the user.
73
+ Once tests are green, types clean, the change looked at and Step 5 done: call `/pre-commit`. It routes to the reviewers and obtains the commit confirmation.
87
74
 
88
75
  Ask every question with a fixed set of answers through the `AskUserQuestion` tool, in {{kit.responseLanguage}}.
89
76
 
@@ -91,20 +78,18 @@ The report at the end is short: one line of diff stat, one line per check with i
91
78
 
92
79
  ### Git commands run one at a time
93
80
 
94
- `git add`, `git commit`, `git fetch`, `git rebase` and `git push` each run as a Bash command of their own, with no check or other step joined to them by `&&`, `||`, `;`, a pipe or a line break. The checks run as separate commands before and after. The order is: checks, commit, fetch, rebase, checks again, push. Push only once the checks after the rebase are green. The checks again run only where the rebase brought in changes to your package roots or plans, all of them when in doubt (orchestrate skill § 6).
95
-
96
- Why: a chained command cannot be approved as one (Claude Code docs, "Configure permissions", section "Compound commands").
81
+ `git add`, `git commit`, `git fetch`, `git rebase` and `git push` each run as a Bash command of their own, with nothing joined by `&&`, `||`, `;`, a pipe or a line break: a chained command cannot be approved as one (Claude Code docs, "Configure permissions", section "Compound commands"). The order is: checks, commit, fetch, rebase, checks again, push. Push only once the checks after the rebase are green. The checks again run where the rebase brought in changes to your package roots or plans, all of them when in doubt (orchestrate skill § 6).
97
82
 
98
83
  ## Step 7: Phase Boundary
99
84
 
100
85
  Runs once the phase's review gate is ticked off, committed or not (a lane under `/orchestrate` commits only when its pull request is ready), and only then — never in the middle of a task.
101
86
 
102
- 1. Read the fill of your own context, from the log and not from memory:
87
+ 1. Read the fill of your own context from the log, not from memory:
103
88
 
104
89
  ```bash
105
90
  bunx jackherd context
106
91
  ```
107
92
 
108
- `null` or `level` `ok` → continue with the next phase in this chat, and say nothing about it. A line on stderr that the auto-compact window is not above `plans.context.alertTokens` means Claude Code compacts before this hand-over: name it in one line of your report.
109
- 2. `warn` or `alert`, and a next phase exists → the next phase runs in a new chat, without a question: go on with step 3, once every subagent you started is back, no dialog of yours is open and no question of yours waits for an answer. The same holds when Jack, the coordinator, asked this lane for its hand-over.
110
- 3. Write the hand-over to `.claude/handover/<lane>.md` (`<lane>` is the name of the worktree directory; the directory is ignored by git and stays out of every commit) and end your turn with its path. The file holds, in this order: the line `{{kit.word.openSubagents}}: 0`, which the coordinator requires, the state (branch, commits or uncommitted changes, what is merged), the open points, the next step (after a wave, the state of each of its phases), the traps you ran into, and links to the plan and the files that matter. Jack ends this agent and starts a fresh one in the same worktree with the hand-over as its first task; without a coordinator, the last line names the fill and tells the user to start a new chat with that file.
93
+ `null` or `level` `ok` → continue with the next phase in this chat, and say nothing about it. A line on stderr that the auto-compact window is not above `plans.context.alertTokens` goes into your report in one line.
94
+ 2. `warn` or `alert`, and a next phase exists → the next phase runs in a new chat, without a question: go on with step 3 once every subagent you started is back, no dialog of yours is open and no question of yours waits for an answer. The same holds when Jack, the coordinator, asked for the hand-over.
95
+ 3. Write the hand-over to `.claude/handover/<lane>.md` (`<lane>` is the worktree directory's name; the directory stays out of every commit) and end your turn with its path. It holds, in this order: the line `{{kit.word.openSubagents}}: 0`, which the coordinator requires, the state (branch, commits or uncommitted changes, what is merged), the open points, the next step (after a wave, the state of each of its phases), the traps you ran into, and links to the plan and the files that matter. Jack starts a fresh agent in the same worktree with it; without a coordinator, the last line names the fill and tells the user to start a new chat with that file.