jackherd 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.18.0
6
+
7
+ - **`jh check` measures every file your budget names.** A path in `check.skillBudgets` (no glob, no sum) is now measured even when `jh check` would not read it on its own, such as `CLAUDE.md` or a rule with `paths:`; a named file that is missing warns, it never fails. The number of measured files in the `Skill budgets` line can rise after the update.
8
+ - **Shorter kit texts.** The code style, testing and pull request rules, `/jh-implement`, `/jh-plan` and `/jh-pre-commit` are cut to the rules an agent must keep, about 1,500 words fewer in total; the orchestrate skill loses about 1,200 words, what a reference, a hook message or a watcher line already says included, and its approval questions moved into `references/approvals.md`, which the `approval` row links. Word budgets and ceilings fall with them. Run `jh sync` to get the new texts.
9
+ - **A clearer README.** The intro, the follow section and the Quickstart say what you decide, what Jack does and what a source needs.
10
+
5
11
  ## 1.17.0
6
12
 
7
13
  - **Jack resumes after a crash.** After a machine or herdr crash, herdr restored the agents but nobody restarted the watcher, and every lane whose command was cut off stood still. The watcher now keeps a record in `~/.jackherd/resume/`, and a second `[[startup]]` entry of the herdr plugin runs `jh resume`: it wakes the coordinator with a fixed `/orchestrate` line and every lane that was working with a fixed resume text, at most once per crash. Nothing resumes after a clean stop, for a record older than 12 hours, while a watcher runs, or when a target is ambiguous; `~/.jackherd/logs/resume.log` says why.
package/README.md CHANGED
@@ -1,13 +1,13 @@
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.17.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.18.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.17.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.18.0/assets/icons/space.svg">
7
7
 
8
8
  # Jack herds your coding agents.
9
9
 
10
- You write the plan. Jack, the coordinator, runs it in parallel lanes, settles what the plan already decides and asks you only what needs you. The herd view shows what's running, what's blocked and what waits for you.
10
+ You decide what gets built. Jack, the coordinator, plans it with you, runs it in parallel lanes, answers what the plan already settles and asks you only what needs you. The herd view shows what's running, what's blocked and what waits for you.
11
11
 
12
12
  For developers who'd rather decide than supervise.
13
13
 
@@ -15,27 +15,27 @@ 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.17.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.18.0/assets/icons/space.svg">
19
19
 
20
20
  <p>
21
- <img src="https://unpkg.com/jackherd@1.17.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.18.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.17.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.18.0/assets/icons/space.svg">
24
24
  </p>
25
25
 
26
- <p><img src="https://unpkg.com/jackherd@1.17.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.18.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.17.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.18.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.17.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
30
+ <p><img src="https://unpkg.com/jackherd@1.18.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.17.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.18.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.17.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
34
+ <p><img src="https://unpkg.com/jackherd@1.18.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.17.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
36
+ <p><img src="https://unpkg.com/jackherd@1.18.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
37
37
 
38
- <p><img src="https://unpkg.com/jackherd@1.17.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.17.0/assets/icons/space.svg"></p>
38
+ <p><img src="https://unpkg.com/jackherd@1.18.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.18.0/assets/icons/space.svg"></p>
39
39
 
40
40
  ## Quickstart
41
41
 
@@ -50,43 +50,38 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
50
50
  ```text
51
51
  Install jackherd by following https://unpkg.com/jackherd/INSTALL.md. Ask me the setup questions it lists; herdr is recommended, not required.
52
52
  ```
53
- 2. **Start Claude Code in a repository.**
53
+ 2. **Start Claude Code in a repository.** `/jh-start` checks your setup and offers your first plan, written to `docs/plans/`.
54
54
 
55
55
  ```text
56
56
  /jh-start
57
57
  ```
58
- 3. **Create your first plan.** It lands in `docs/plans/`, and for parallel lanes `/jh-plan` offers to write a board.
59
-
60
- ```text
61
- /jh-plan Add product search
62
- ```
63
- 4. **Build the first phase.**
58
+ 3. **Build the first phase,** in one session, no herdr needed.
64
59
 
65
60
  ```text
66
61
  /jh-implement docs/plans/<plan>.md phase 1
67
62
  ```
68
- 5. **Run phases in parallel with herdr (recommended).** Lanes, the full herd view, review-comment fixes and ticket reports need it. In Claude Code inside herdr, hand Jack the board:
63
+ 4. **Run phases in parallel with herdr and `gh` (recommended).** Lanes, the full herd view, review-comment fixes and ticket reports need them. In Claude Code inside herdr, hand Jack the board:
69
64
 
70
65
  ```text
71
66
  /jh-orchestrate docs/plans/board-<name>/board.md
72
67
  ```
73
68
 
74
- 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. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
69
+ 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.18.0/assets/icons/space.svg">
75
70
 
76
- ## Follow anything outside GitHub
71
+ ## Follow anything you can query
77
72
 
78
- Whatever you check by hand, Jack can watch: tickets, error logs, status pages, deploys. One `/jh-follow`, one yes, and every change comes to you like this:
73
+ 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:
79
74
 
80
- <img src="https://unpkg.com/jackherd@1.17.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.">
75
+ <img src="https://unpkg.com/jackherd@1.18.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.">
81
76
 
82
- The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.17.0/assets/icons/space.svg">
77
+ 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.18.0/assets/icons/space.svg">
83
78
 
84
79
  ## In detail
85
80
 
86
81
  <details>
87
82
  <summary><b>What Jack does on his own, and what waits for you</b></summary>
88
83
 
89
- <img src="https://unpkg.com/jackherd@1.17.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.">
84
+ <img src="https://unpkg.com/jackherd@1.18.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.">
90
85
 
91
86
  | On his own | After your approval | Never |
92
87
  | --- | --- | --- |
@@ -121,7 +116,7 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
121
116
 
122
117
  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.
123
118
 
124
- <img src="https://unpkg.com/jackherd@1.17.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.">
119
+ <img src="https://unpkg.com/jackherd@1.18.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.">
125
120
 
126
121
  | Skill | Does |
127
122
  | --- | --- |
@@ -131,7 +126,7 @@ A **plan** is Markdown with phases, checkbox tasks, a model and a review gate pe
131
126
  | `/jh-implement` | builds and tests one phase and ticks it off; independent phases run as parallel subagents |
132
127
  | `/jh-orchestrate` | runs a board's lanes in herdr, as Jack |
133
128
  | `/jh-pre-commit` | runs `jh check` and the reviewers, proposes the commit message and asks before committing |
134
- | `/jh-follow` | follows something outside GitHub: a ticket, error logs, a status page |
129
+ | `/jh-follow` | follows a source: a ticket, error logs, a status page |
135
130
  | `/jh-diagnose` | turns a bug into a failing command, fixes it with a regression test |
136
131
 
137
132
  - **Your own skills stay.** `jh init` keeps them and gives jackherd's skills a `jh-` prefix where a name is taken, as a global install always does; `jh sync` never writes over a file you changed, it leaves `<file>.kit-new` beside it. If a skill from `npx skills add` later takes the path of one of jackherd's, `jh sync` names it and the way out: `"skillPrefix": "jh-"`. Such skills only warn in `jh check`'s word budgets, never red.
@@ -250,7 +245,7 @@ Without a global install, run them as `bunx jackherd <command>`. The config is `
250
245
  }
251
246
  ```
252
247
 
253
- `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. It also reads the rule blocks you added or changed in `**/rules/**/*.md`, `CLAUDE.md` and `AGENTS.md`, and warns (`Lint pointers`) where a block carries a rule your linter could check (a banned import path, a literal comparison, a comment form, a banned call) and names no lint rule. Name it with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where the rule stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs that are never read. `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
248
+ `jh check` counts the words of every skill, agent and always-loaded rule an agent reads, and warns above its budget; `check.skillBudgets` sets your own, by path or glob. A file it names by its exact path is measured too, even one `jh check` would not read on its own (`CLAUDE.md`, a rule with `paths:`); a named file that is missing warns. It also reads the rule blocks you added or changed in `**/rules/**/*.md`, `CLAUDE.md` and `AGENTS.md`, and warns (`Lint pointers`) where a block carries a rule your linter could check (a banned import path, a literal comparison, a comment form, a banned call) and names no lint rule. Name it with `` `lint:<rule id>` ``, or write `` `lint:none` `` and a reason where the rule stays prose. `check.lintPointers.level` is `warn` (default), `fail` or `off`; `check.lintPointers.exclude` lists globs that are never read. `watch.parallelTasks.auto` (`since`, `until`, at most seven days) lets Jack start up to two extra subagents in a running lane without asking, under the cost limit and away from paths that always ask; he switches it on only after asking you.
254
249
 
255
250
  </details>
256
251
 
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.17.0"
3
+ version = "1.18.0"
4
4
  min_herdr_version = "0.8.2"
5
5
  description = "Show the herd, the state of a board's lanes, in a herdr pane"
6
6
  platforms = ["linux", "macos"]
package/install.sh CHANGED
@@ -9,7 +9,7 @@ set -eu
9
9
 
10
10
  # The default is the version this file ships with. The release bump changes it together with
11
11
  # package.json; install.test.ts fails while the two differ. Overridable, but only by a plain release.
12
- JACKHERD_VERSION="${JACKHERD_VERSION:-1.17.0}"
12
+ JACKHERD_VERSION="${JACKHERD_VERSION:-1.18.0}"
13
13
 
14
14
  JACKHERD_PACKAGE='jackherd'
15
15
  # Test seam: the terminal the wizard asks its questions on.
@@ -13,42 +13,38 @@ You are the **Pre-Commit Orchestrator**. You measure what a command can measure,
13
13
  bunx jackherd check
14
14
  ```
15
15
 
16
- One command answers everything a command can answer, and **its output is the evidence for steps 1, 3 and 4** — the changed files (including untracked ones), the affected package roots, which reviewers the changed paths call for, which files a generator owns, staged build artifacts, lint over the changed files, types and tests per package root, and the project's own checks. All of it is configured in the `check` block of `.jackherd/config.json`.
16
+ Its output is **the evidence for steps 1, 3 and 4**: the changed files (untracked included), the affected package roots, the reviewers the changed paths call for, the files a generator owns, staged build artifacts, lint, types and tests per package root, and the project's own checks (the `check` block of `.jackherd/config.json`).
17
17
 
18
- **Quote its results; restate nothing from your own reading of the diff.** A verdict about type errors, passing tests or lint that was not measured is a guess, and the failure mode is a confident false PASS. Where the command says `❌`, the summary says FIX FIRST.
18
+ **Quote its results; restate nothing from your own reading of the diff.** An unmeasured verdict is a guess, and its failure mode is a confident false PASS. Where the command says `❌`, the summary says FIX FIRST.
19
19
 
20
20
  `--skip-slow` drops types and tests. Use it only when the user asks for a quick pass, and say so in the summary.
21
21
 
22
22
  ## Step 2: Judgement Review
23
23
 
24
- Run `/review` for what the command cannot decide: dead exports, naming, function size, the design review. The measured points are already settled above.
24
+ Run `/review` for what the command cannot decide: dead exports, naming, function size, the design review.
25
25
 
26
26
  ## Step 3: Specialized Reviewers
27
27
 
28
- **Step 1 printed the reviewer list** (`Reviewers: …`, from `check.reviewerRoutes`, plus `check.publicContent` and `skill-review` for changed agent texts) — dispatch exactly those, each as a subagent via the `Agent` tool with its `subagent_type`:
28
+ **Step 1 printed the reviewer list** (`Reviewers: …`) — dispatch exactly those, each as a subagent via the `Agent` tool with its `subagent_type`:
29
29
 
30
- - Pass in the `prompt`: the `git diff` of the relevant files and the file paths; to `skill-review`, also the `Skill budgets` and `Lint pointers` results of the latest `jh check` run.
30
+ - Pass the `git diff` of the relevant files and their paths; to `skill-review`, also the `Skill budgets` and `Lint pointers` results of the latest `jh check` run.
31
31
  - Start all of them **in parallel**, in a single message, and wait for every result before step 4.
32
32
 
33
- `security-audit` has a judgement half: add it when the diff *does* something security-relevant in a path no route lists — auth middleware, API key handling, a schema at an HTTP endpoint, a raw database query, an access policy.
33
+ Add `security-audit` when the diff *does* something security-relevant in a path no route lists: auth middleware, API key handling, a schema at an HTTP endpoint, a raw database query, an access policy.
34
34
 
35
- **`spec-review` starts in the same message.** It reviews a different axis, not different files, so it applies regardless of which paths changed. Pass it the full `git diff`, the commit list (`git log <base>..HEAD --oneline`, base `{{kit.baseBranch}}`), the base ref and the branch name.
35
+ **`spec-review` starts in the same message.** It applies whichever paths changed. Pass it the full `git diff`, the commit list (`git log <base>..HEAD --oneline`, base `{{kit.baseBranch}}`), the base ref and the branch name.
36
36
 
37
37
  ## Step 4: Generated Files
38
38
 
39
- Step 1 lists the changed files a generator owns and names the generator. A regenerated file lands in the diff the same way as a hand-edited one, so the judgement is whether this one came from the generator. Ask when it looks hand-edited.
39
+ Step 1 lists the changed files a generator owns. Ask when one looks hand-edited rather than regenerated.
40
40
 
41
41
  ## Step 5: Spec Alignment
42
42
 
43
- Take the `spec-review` result. Steps 2 and 3 ask whether the code is written well (**Standards**); this one asks whether it does what was asked (**Spec**). Report the two side by side, each under its own heading, so neither masks the other.
43
+ Steps 2 and 3 judge whether the code is written well (**Standards**), `spec-review` whether it does what was asked (**Spec**). Report the two side by side, each under its own heading.
44
44
 
45
- ## Step 6: arc42 Check
45
+ ## Step 6: arc42 and Architecture
46
46
 
47
- Apply the filter in `.claude/rules/kit/arc42.md` to the changed files. On a hit, **ask the user concretely**. No hit for bug fixes, styling, small UI changes, or refactorings without decision character.
48
-
49
- ## Step 7: Architecture Check
50
-
51
- For changes in a service, a domain package or a data layer, apply the filter in `.claude/rules/kit/architecture-files.md`. On a hit, **ask the user concretely** whether the unit's `ARCHITECTURE.md` needs an update.
47
+ Apply the filter in `.claude/rules/kit/arc42.md` to the changed files; for a service, a domain package or a data layer, also the one in `.claude/rules/kit/architecture-files.md`. On a hit, **ask the user concretely**. No hit for bug fixes, styling or refactorings without decision character.
52
48
 
53
49
  ## Output Format
54
50
 
@@ -75,19 +71,17 @@ Active reviewers: Review + Spec + [routed reviewers]
75
71
  ### Verdict: COMMIT / FIX FIRST
76
72
  ```
77
73
 
78
- On FIX FIRST, apply what a tool fixes on its own — formatting (`{{kit.command.format}}` or the formatter on the file), lint auto-fixes — then **re-run `bunx jackherd check` and quote the new result.** A fix is verified by the command that found the problem.
79
-
80
- A second review round after fixes dispatches only the reviewers whose findings were applied, plus the reviewers of any path the fixes newly touched (the reviewer list of the new `check` run); `jh check` itself always runs in full.
74
+ On FIX FIRST, apply what a tool fixes on its own (`{{kit.command.format}}`, lint auto-fixes), then re-run `bunx jackherd check` and quote the new result. A second review round dispatches only the reviewers whose findings were applied, plus those of any path the fixes newly touched; `jh check` always runs in full.
81
75
 
82
- Architecture problems, missing tests, domain logic errors and **every Spec finding** are reported for the user to decide: whether a requirement was dropped on purpose or the scope grew deliberately is the user's call.
76
+ Architecture problems, missing tests, domain logic errors and **every Spec finding** are reported for the user to decide: a dropped requirement or a grown scope is the user's call.
83
77
 
84
78
  ## Commit
85
79
 
86
80
  On COMMIT, propose a Conventional Commit message (`type(scope): description`, imperative, subject at most 72 characters; the body explains cause and consequence in prose).
87
81
 
88
- Ask the commit question with the `AskUserQuestion` tool, as a clickable choice in {{kit.responseLanguage}}: commit with this message / adjust the message / cancel.
82
+ Ask the commit question with the `AskUserQuestion` tool, in {{kit.responseLanguage}}: commit with this message / adjust the message / cancel.
89
83
 
90
- On confirmation, write the message to a file in your scratchpad with the `Write` tool, stage **the files step 1 listed** by name, and commit from the file:
84
+ On confirmation, write the message to a file in your scratchpad with the `Write` tool, stage **the files step 1 listed** by name, and commit from the file, each as one Bash call:
91
85
 
92
86
  ```bash
93
87
  git add -- "<path from step 1>" "<path from step 1>"
@@ -97,4 +91,4 @@ git add -- "<path from step 1>" "<path from step 1>"
97
91
  git commit -F "<message file>"
98
92
  ```
99
93
 
100
- Each is one Bash call: a chained command cannot be approved as one (Claude Code docs, page "Configure permissions", section "Compound commands"). Staging by name keeps unreviewed files out of the commit. A message file reaches git untouched; passed through the shell (`-m "…"` or a heredoc), the backticks and `$(…)` a commit body quotes could run as commands. Let the repository's own commit hooks run.
94
+ A chained command cannot be approved as one; a message passed through the shell (`-m "…"`, a heredoc) can run the backticks and `$(…)` it quotes. Let the repository's own commit hooks run.
@@ -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