liteagents 2.21.1 → 2.22.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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.22.1] - 2026-09-01
13
+
14
+ ### Fixed
15
+ - **`/docs-builder` — the trailing-newline phantom line is dropped from every line count.**
16
+ `text.split('\n')` returns a trailing empty element for any file ending in a newline, so
17
+ `lines.length` was one over the real count. That phantom line reached the index row's "N
18
+ lines" (every row +1), the last H2's line range (one line past EOF), `scan`'s outline.json
19
+ `s`/`e`/`lines`, the ledger's per-file line count, the cleanup cost estimate, and the PARTIAL
20
+ guard (a page one line short of `MIN_PAGE_LINES` passed as complete). Fixed with a
21
+ `splitLines()` helper applied at the 7 counting/bounding sites, deliberately not at the sites
22
+ that map-and-rejoin file text. `docs/index.md` regenerated. Mirrored across all four kits.
23
+ - **`/docs-builder` — an empty page now reports PARTIAL instead of crashing `plan`.** A
24
+ regression from the fix above: `splitLines()` returns `[]` for a 0-byte file where the raw
25
+ split returned `['']`, so `pageStatus`'s unguarded `lines[0].trim()` threw a `TypeError` and
26
+ took `plan` down with it. An empty `.md` page is reachable (a touched placeholder, or
27
+ page-writing interrupted). Fixed at the indexing site — 0 lines is the correct count for an
28
+ empty file — so `pageStatus` guards on `lines.length` instead. Mirrored across all four kits.
29
+
30
+ ## [2.22.0] - 2026-09-01
31
+
32
+ ### Fixed
33
+ - **`/branch-review` — dirty tree is now a hard stop, not a silent partial review.** The
34
+ staged/working-tree fallback only fired when the earlier step was empty, so a branch with
35
+ both committed and uncommitted changes reviewed the commits and silently skipped the
36
+ uncommitted lines. It now reports the resolved review target and re-checks
37
+ `git status --porcelain` at exit as well as at start, in all four kits.
38
+ - **`/release` — Phase 0 no longer commits a dirty tree for the user.** Committing on the
39
+ user's behalf produced a commit made *after* the review, which Phase 0.5 then had no way to
40
+ accept — a dirty tree is now a stop, with the fix pushed back to the user
41
+ (`git add`/`git commit`, re-run `/branch-review`, then `/release`), in all four kits.
42
+ - **`/release` Phase 0.5 compares SHAs mechanically instead of asking the orchestrator to
43
+ judge it.** The worker now runs `git rev-parse HEAD` itself and compares it against the
44
+ review's recorded SHA — no recorded SHA, or a mismatch, is a stop, never a "looks close
45
+ enough" pass.
46
+
47
+ ### Changed
48
+ - **`/branch-review` — test quality is a stage-1 item.** A test's ability to fail is proven by
49
+ reverting the *source* change outside the repo, not by reverting the test itself; commit
50
+ messages are treated as claims to re-verify, never as evidence on their own.
51
+ - **`/branch-review` — the review worker may not spawn sub-workers**, and the verdict line is
52
+ now printed at both the top and the bottom of the report.
53
+ - **`/release` — records local vs. published version in Phase 0**, and Phase 3 treats a gap
54
+ between them as the ask-if-ambiguous trigger for picking a version. The release commit made
55
+ in Phase 3 is documented as the one commit `/release` is permitted to make post-review.
56
+ Documents that `gh pr merge` requires an explicit merge-method flag (`--squash` /
57
+ `--merge` / `--rebase`) or it will not merge.
58
+ - **`/branch-review`, `/release`, `/stash` — tier guidance no longer self-contradicts.**
59
+ "Balanced default tier" plus "never hardcode a vendor name" resolved, in practice, to
60
+ whatever tier the parent session happened to be running at. All three now say: explicitly
61
+ select your tool's mid tier and state it on the spawn.
62
+
12
63
  ## [2.21.1] - 2026-08-30
13
64
 
14
65
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liteagents",
3
- "version": "2.21.1",
3
+ "version": "2.22.1",
4
4
  "description": "AI development toolkit with 11 specialized agents and 18 commands including live-canvas UI design with click-to-annotate feedback. Simple one-question installer for Claude, Opencode, Ampcode, and Droid.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -14,31 +14,61 @@ Run this **before** `/release`. `/release` will refuse to run without a review
14
14
  at the current HEAD SHA.
15
15
 
16
16
  ## Guardrails
17
- - **Spawn a worker on a mid-tier model, not hardcoded.** The review runs in a
18
- subagent on your tool's balanced default tier judgment-capable, cheaper and
19
- faster than your top reasoning tier. **Not the cheapest/fastest tier**: on
20
- judgment work it measurably degrades (misclassification rates several times
21
- higher). Never hardcode a vendor-specific model name. Fall back to running
22
- inline if your tool has no subagent mechanism.
17
+ - **Spawn a worker and explicitly select your tool's mid tier.** State the
18
+ tier on the spawn do not omit it and rely on a default. An omitted tier
19
+ inherits the *parent's* tier, which is not the same thing as the balanced
20
+ one. Pick the judgment-capable tier that is cheaper and faster than your top
21
+ reasoning tier. **Not the cheapest/fastest tier**: on judgment work it
22
+ measurably degrades (misclassification rates several times higher). Choose by
23
+ tier, not by a vendor model name copied from this file — names drift, and
24
+ this command ships to several tools. Fall back to running inline if your tool
25
+ has no subagent mechanism.
23
26
  - **Escalate, never assume.** Anything you cannot decide, cannot verify, or
24
27
  that this spec does not cover → **stop and report it to the orchestrator**
25
28
  (the main session). Never improvise, never widen scope, never fix a side
26
29
  issue you noticed along the way.
30
+ - **The worker does the work itself — no delegation.** The review subagent
31
+ must **not** spawn subagents of its own. Everything it reports has to be
32
+ something it read, ran, or grepped with its own tool calls: a relayed "I
33
+ executed X" from a sub-worker is hearsay, and replacing hearsay with evidence
34
+ is the entire point of this command. A review that delegates its work is a
35
+ review of a report. (Same rule `/security` carries inside stage 2.)
27
36
  - **No edits.** You have no authorization to change code, even for a finding
28
- you are certain about. Report it.
29
-
30
- ## Target interpret `$ARGUMENTS` in this order
37
+ you are certain about. Report it. Re-run `git status --porcelain` before you
38
+ report and confirm it is still empty — if it is not, say what changed. That
39
+ turns "it never edits" from a claim into a checked fact.
40
+
41
+ ## Target — check the tree first, then interpret `$ARGUMENTS`
42
+
43
+ **Before resolving anything, run `git status --porcelain`.** If it prints any
44
+ line — modified, staged, or untracked — **stop and report it**. Say all three
45
+ things, not just the first: (a) the tree is dirty, listing the uncommitted
46
+ paths; (b) `/branch-review` reviews commits, not the working tree; (c) **commit
47
+ the work to the branch, then re-run `/branch-review`.** A stop that names the
48
+ problem without the remedy invites the orchestrator to stash the changes or
49
+ hand-review the working tree instead. Do not review a subset and do not fall
50
+ back to the staged diff or the working tree. A dirty
51
+ tree is an **error**, never a silent partial review — the most expensive
52
+ failure this command can have is reviewing 800 committed lines while 200
53
+ uncommitted lines of today's actual work go unread.
54
+
55
+ This is forced by the design, not a preference: `/release`'s precondition is a
56
+ review at the current HEAD SHA, and any commit made after the review makes it
57
+ stale. **The only correct order is commit → review → release.**
58
+
59
+ With a clean tree, interpret `$ARGUMENTS` in this order:
31
60
  1. **Empty** → the current branch vs its merge-base with `main`
32
- (`git diff $(git merge-base main HEAD)..HEAD`). If that is empty, the
33
- staged diff; if that is empty too, the working tree.
61
+ (`git diff $(git merge-base main HEAD)..HEAD`). If that is empty there is
62
+ nothing committed to review say so and stop.
34
63
  2. **A range** like `main..HEAD` or `origin/main...HEAD` → `git diff <range>`.
35
64
  3. **A single ref** (branch / tag / SHA — confirm with `git rev-parse
36
65
  --verify`) → that ref's merge-base against `HEAD`.
37
66
  4. **A file or directory path** → that target.
38
67
  5. Otherwise → ask.
39
68
 
40
- Record the **HEAD SHA** you reviewed. `/release` checks it, and any commit
41
- made after the review makes the review stale.
69
+ Record the **HEAD SHA** you reviewed, and **report the target you resolved**
70
+ (the literal range or path) in your output, so the orchestrator can see what
71
+ was actually read rather than assuming.
42
72
 
43
73
  ## Effort level
44
74
  `low | medium | high | max` — default **medium** if not given. The level
@@ -57,6 +87,13 @@ hunk-only read cannot see that a caller further down the same file is now
57
87
  wrong. For multi-commit ranges, skim `git log <range>` for intent before
58
88
  judging.
59
89
 
90
+ **Commit messages are claims, not evidence.** A message saying a fix was
91
+ "proven red→green", a bug reproduced, or a test added is something to re-test,
92
+ not a fact to accept. Branches are commonly AI-authored now — including the
93
+ fixes to the fixes — so a review that trusts the message is reviewing prose.
94
+ Run the test suite and the typecheck/build yourself and cite the command and
95
+ its exit code.
96
+
60
97
  - **Bugs needing a fix.** Logic errors, off-by-one, null/undefined paths,
61
98
  races, wrong defaults, broken edge cases.
62
99
  - **Dead code.** Unreferenced functions / vars / imports / params, unreachable
@@ -69,6 +106,16 @@ judging.
69
106
  broken invariants.
70
107
  - **Performance.** N+1, blocking calls in hot paths, unbounded loops, indexes
71
108
  the diff actually touches.
109
+ - **Test quality, not just test presence.** For every test the diff adds or
110
+ changes, establish that it **can actually fail**. Reasoning about
111
+ falsifiability does not work; executing it does. **Revert the source, not the
112
+ test:** take the pre-change version of the file under test with `git show
113
+ <base-sha>:<path>`, run the test against that copy, and watch it go red. Do
114
+ this **without dirtying the branch** — write the old version to a temp
115
+ location outside the repo; the tree must still be clean at exit. A test that
116
+ passes against both the buggy and the fixed source is a tautology and proves
117
+ nothing. Flag every one you find, and say so explicitly when the tests are
118
+ the branch's only evidence for its claims.
72
119
  - **Maintainability.** Complexity, naming, duplication — only when material.
73
120
 
74
121
  ## Stage 2 — Security (always full)
@@ -102,7 +149,12 @@ cannot write that sentence, the finding is not ready — drop it or mark it
102
149
  uncertain. No vibes.
103
150
 
104
151
  ## Report — then escalate
105
- Order findings most severe first.
152
+ **Open with the one-line verdict**, before any section: **Ready to merge? Yes /
153
+ No / Not until these are fixed.** A report that opens with "Critical: none
154
+ found" reads as a pass at a glance even when the verdict is not one — state the
155
+ verdict first, then repeat it at the end.
156
+
157
+ Then the findings, ordered most severe first.
106
158
 
107
159
  ### 🚨 Critical (blocks merge)
108
160
  ### ⚠️ Warnings (should fix)
@@ -117,7 +169,8 @@ with its evidence. A stage you did not actually run is a **✗**, never an
117
169
  assumed pass.
118
170
 
119
171
  End with:
120
- - **Reviewed at HEAD `<sha>` on `<branch>`.**
172
+ - **Reviewed at HEAD `<sha>` on `<branch>`, target `<resolved range or path>`,
173
+ tree clean at start and at exit.**
121
174
  - One-line verdict: **Ready to merge? Yes / No / Not until these are fixed.**
122
175
  - **Escalate to the orchestrator** with the findings. It decides what gets
123
176
  fixed and by whom. Say plainly what you could not verify.
@@ -103,6 +103,18 @@ function fenceMask(lines) {
103
103
  // prose), sentences()'s own regex strip, and checkCitations/checkLinks doing none at all — so
104
104
  // a page documenting the citation/link syntax INSIDE a fence got its own example flagged as
105
105
  // a real violation. One mechanism: mask with fenceMask(), drop the masked lines.
106
+ // `text.split('\n')` returns a trailing EMPTY element for any file ending in a newline —
107
+ // which is nearly every file — so `lines.length` is real_lines + 1. That phantom line reached
108
+ // the index row's "N lines", the last H2's range (one line past EOF) and scan()'s outline.json.
109
+ // Use this wherever lines are COUNTED or a range is BOUNDED. The raw `.split('\n')` is still
110
+ // correct where the array is mapped and re-joined back into file text (stripFences,
111
+ // replaceOutsideFences): dropping the element there would strip the file's final newline.
112
+ function splitLines(text) {
113
+ const lines = text.split('\n');
114
+ if (lines.length && lines[lines.length - 1] === '') lines.pop();
115
+ return lines;
116
+ }
117
+
106
118
  function stripFences(text) {
107
119
  const lines = text.split('\n');
108
120
  const mask = fenceMask(lines);
@@ -229,7 +241,7 @@ function scan(files) {
229
241
  if (!files.length) die('usage: docs-builder.cjs scan <file.md...>');
230
242
  const records = [];
231
243
  for (const f of files) {
232
- const lines = read(f).split('\n');
244
+ const lines = splitLines(read(f));
233
245
  const mask = fenceMask(lines);
234
246
  const { h1, heads } = headings(lines, mask);
235
247
  const h2s = heads.filter(h => h.lvl === 2);
@@ -522,8 +534,13 @@ const MIN_PAGE_LINES = 10;
522
534
  function pageStatus(file) {
523
535
  if (!fs.existsSync(file)) return 'TODO';
524
536
  const txt = fs.readFileSync(file, 'utf8');
525
- const lines = txt.split('\n');
526
- const hasFrontmatter = lines[0].trim() === '---' && lines.slice(1).some(l => l.trim() === '---');
537
+ const lines = splitLines(txt);
538
+ // splitLines() returns [] for a 0-byte file — 0 lines is the right COUNT, but it means
539
+ // lines[0] can be undefined, where the old raw split('\n') always yielded ['']. An empty
540
+ // page is reachable (a touched placeholder, or page-writing interrupted before it wrote
541
+ // anything) and must read as PARTIAL, not throw and take `plan` down with it.
542
+ const hasFrontmatter = lines.length > 0 && lines[0].trim() === '---'
543
+ && lines.slice(1).some(l => l.trim() === '---');
527
544
  return hasFrontmatter && lines.length >= MIN_PAGE_LINES ? 'done' : 'PARTIAL';
528
545
  }
529
546
 
@@ -732,7 +749,7 @@ const ARCHIVE_WARN_ROWS = 100; // stated default, not measured — see docs-buil
732
749
  // section a reader is being routed into, so its row stays H1 + line count + link only.
733
750
  function indexRow(rel, dest, includeH2) {
734
751
  const text = read(rel);
735
- const lines = text.split('\n');
752
+ const lines = splitLines(text);
736
753
  // Same headings()+fenceMask() path scan() uses -- no second parser -- so an H2 inside a
737
754
  // ``` fence is masked out here exactly as it is there.
738
755
  const mask = fenceMask(lines);
@@ -1398,7 +1415,7 @@ function ledger() {
1398
1415
  const head = git(['rev-parse', 'HEAD'], 'reading HEAD (is this a git repo?)');
1399
1416
  const docs = docFiles().map(f => ({
1400
1417
  path: f,
1401
- lines: read(f).split('\n').length,
1418
+ lines: splitLines(read(f)).length,
1402
1419
  sha256: sha(path.join(REPO, f)).slice(0, 16)
1403
1420
  }));
1404
1421
  const out = { sha: head, at: new Date().toISOString(),
@@ -1492,7 +1509,7 @@ function lint(files) {
1492
1509
  if (!files.length) die('usage: docs-builder.cjs lint <file.md...>');
1493
1510
  const sections = [];
1494
1511
  for (const f of files) {
1495
- const lines = read(f).split('\n');
1512
+ const lines = splitLines(read(f));
1496
1513
  const mask = fenceMask(lines);
1497
1514
  let cur = null;
1498
1515
  const close = i => { if (cur) { cur.e = i; cur.body = lines.slice(cur.s, i).join('\n'); } };
@@ -1665,7 +1682,7 @@ function isIncludeStub(lines) {
1665
1682
  // but its size. Oversized is now orthogonal to sorting: a product doc that's too big is
1666
1683
  // still a product doc.
1667
1684
  function classifyDoc(rel, text) {
1668
- const lines = text.split('\n');
1685
+ const lines = splitLines(text);
1669
1686
  const mask = fenceMask(lines);
1670
1687
  const { h1 } = headings(lines, mask);
1671
1688
  const snip = snippet(lines, mask, 0, lines.length, 200);
@@ -2214,7 +2231,7 @@ function cleanup(files) {
2214
2231
  + `would overwrite that split's still-in-flight outline.json/labels.json. Finish it `
2215
2232
  + `first: write its remaining pages, then re-run \`cleanup-apply ${inFlight} ...\` until `
2216
2233
  + `it archives — THEN run \`cleanup ${file}\`.`);
2217
- const lines = read(file).split('\n').length;
2234
+ const lines = splitLines(read(file)).length;
2218
2235
  const est = writeCostEstimate(1, lines);
2219
2236
  console.log(`${file}: ${lines} lines`);
2220
2237
  console.log(`est. write cost: $${est.toFixed(2)} (mid tier, floor assuming 1 page — the `
@@ -13,12 +13,15 @@ It does not re-implement checks, and it does not review code. Review is a
13
13
  separate command that must have run first.
14
14
 
15
15
  ## Guardrails
16
- - **Spawn a worker on a mid-tier model, not hardcoded.** The run happens in a
17
- subagent on your tool's balanced default tier judgment-capable, cheaper and
18
- faster than your top reasoning tier. **Not the cheapest/fastest tier**: on
19
- judgment work it measurably degrades (misclassification rates several times
20
- higher). Never hardcode a vendor-specific model name. Fall back to running
21
- inline if your tool has no subagent mechanism.
16
+ - **Spawn a worker and explicitly select your tool's mid tier.** State the
17
+ tier on the spawn do not omit it and rely on a default. An omitted tier
18
+ inherits the *parent's* tier, which is not the same thing as the balanced
19
+ one. Pick the judgment-capable tier that is cheaper and faster than your top
20
+ reasoning tier. **Not the cheapest/fastest tier**: on judgment work it
21
+ measurably degrades (misclassification rates several times higher). Choose by
22
+ tier, not by a vendor model name copied from this file — names drift, and
23
+ this command ships to several tools. Fall back to running inline if your tool
24
+ has no subagent mechanism.
22
25
  - **Escalate, never assume.** Anything you cannot decide, cannot verify, or
23
26
  that this spec does not cover → **stop and report it to the orchestrator**
24
27
  (the main session). Never improvise, never widen scope, never fix a finding
@@ -31,24 +34,45 @@ separate command that must have run first.
31
34
  - **Release the branch you are on.** No branch argument, no branch creation.
32
35
  - **On `main` → stop and ask** what should be released. `main` is only ever
33
36
  the merge target; never release it, never commit to it.
34
- - If the working tree still has uncommitted feature changes, commit them to
35
- the branch now the gate must see a real diff, not a dirty tree.
37
+ - **The tree must be clean do not commit for the user.** Run `git status
38
+ --porcelain`. Any line at all modified, staged, or untracked is a
39
+ **stop**: list the paths and say "commit this to the branch, re-run
40
+ `/branch-review`, then re-run `/release`." Committing here would create a
41
+ commit *after* the review and so fail Phase 0.5 by its own rule on the very
42
+ next step; a phase that guarantees the next phase fails is not a phase.
36
43
  - `git fetch origin`; the release diff is `origin/main...HEAD`. Empty →
37
44
  **stop**, nothing to release.
38
- - Print a one-line plan: branch · commit count · files changed · HEAD SHA.
45
+ - **Record both version numbers.** Read the **local** version (`package.json`
46
+ or this project's equivalent) and, if the project has a publish path, the
47
+ **published** one (`npm view <pkg> version`, or the registry equivalent).
48
+ Report them side by side. Local *ahead* of published means a version was cut
49
+ on a branch and never published — Phase 3 cannot see that gap unless you
50
+ record it here, and a worker that cannot see it will re-cut a number that
51
+ already exists.
52
+ - Print a one-line plan: branch · commit count · files changed · HEAD SHA ·
53
+ local version → published version.
39
54
 
40
55
  ## Phase 0.5 — Review precondition (do not skip)
41
- Ask the orchestrator: **has `/branch-review` or `/code-review` run on this
42
- branch at the current HEAD SHA?**
43
-
44
- - **No review** **stop**: "No review at `<sha>`. Run `/branch-review medium`
45
- (or `/code-review medium`) first."
46
- - **Stale** the review ran at an earlier SHA, i.e. commits landed after it
47
- (including fix commits) **stop** and ask for a re-review. This is what
48
- makes "all findings fixed" checkable instead of promised.
56
+ A review must have run on this branch **at the current HEAD SHA**.
57
+
58
+ **Compare the SHAs yourself; do not settle for an answer.** Run `git rev-parse
59
+ HEAD` and compare it against the SHA the review recorded `/branch-review`
60
+ ends with `Reviewed at HEAD <sha>`. Asking the orchestrator "did a review run?"
61
+ puts the question to the one party with an incentive to say yes, so its word is
62
+ not evidence: obtain the review's own recorded SHA and match the two strings.
63
+ **No recorded SHA to compare = no review**, never a pass.
64
+
65
+ - **No review**, or no recorded SHA obtainable → **stop**: "No review at
66
+ `<sha>`. Run `/branch-review medium` (or `/code-review medium`) first."
67
+ - **Stale** — recorded SHA ≠ `git rev-parse HEAD`, i.e. commits landed after
68
+ the review (including fix commits) → **stop** and ask for a re-review. This
69
+ is what makes "all findings fixed" checkable instead of promised.
49
70
  - **Reviewed at this SHA with findings outstanding** → **stop**. Findings are
50
71
  resolved before a release is cut.
51
72
 
73
+ Report the comparison you actually ran: recorded `<sha>` vs HEAD `<sha>`,
74
+ match yes/no.
75
+
52
76
  This is the only thing guaranteeing the branch was reviewed *and* security
53
77
  scanned, so treat a missing answer as a **stop**, never as a pass.
54
78
 
@@ -90,7 +114,11 @@ If a doc needs no change, **say so** rather than editing it for its own sake.
90
114
 
91
115
  ## Phase 3 — Cut (local only)
92
116
  1. **Version bump** — pick the semver level from the change (patch / minor /
93
- major; **ask if ambiguous**) and update `package.json`. The bump must land
117
+ major; **ask if ambiguous**) and update `package.json`. The local-vs-
118
+ published gap you recorded in Phase 0 **is** an ambiguity: if local is ahead
119
+ of published, a version was cut and never published, so ask whether to
120
+ publish that number or bump past it. Never silently re-cut a version that
121
+ already exists locally. The bump must land
94
122
  on the branch, before any merge — a version committed to `main` directly,
95
123
  or added after the merge, breaks the tag/package match.
96
124
  2. **Commit** — `release: vX.Y.Z — <summary>`, including the docs and the
@@ -98,6 +126,12 @@ If a doc needs no change, **say so** rather than editing it for its own sake.
98
126
 
99
127
  Then **stop.** Nothing else.
100
128
 
129
+ This release commit is the **one** commit allowed to land after the review, and
130
+ only because it contains docs and a version number — no code, so it cannot
131
+ invalidate a finding. It does move HEAD past the reviewed SHA, which is why
132
+ `/release` must not be run twice on the same branch without a re-review: the
133
+ second run will correctly stop as stale.
134
+
101
135
  ## Report — the sequence, for a human to authorize
102
136
  Print the evidence, then hand back the exact remaining steps so the
103
137
  orchestrator can run them on the user's named go:
@@ -108,7 +142,9 @@ orchestrator can run them on the user's named go:
108
142
  > 1. `git push -u origin <branch>`
109
143
  > 2. `gh pr create` into `main`
110
144
  > 3. `gh pr merge --admin --squash --delete-branch` (main is PR-protected;
111
- > owner-authorized admin merge on a solo repo)
145
+ > owner-authorized admin merge on a solo repo). **Keep `--squash`** — `gh`
146
+ > requires an explicit merge-method flag (`--squash` / `--merge` /
147
+ > `--rebase`); drop it and the command will not squash-merge.
112
148
  > 4. `git tag vX.Y.Z` on `main` and push the tag
113
149
  > 5. Publish **if this project has a publish path** (e.g.
114
150
  > `gh workflow run publish.yml`) — manual by design
@@ -15,11 +15,12 @@ Save session context for compaction recovery or handoffs.
15
15
  - **Escalate, never assume.** Anything the subagent cannot do, cannot verify, or that this
16
16
  spec does not cover → report it back to the orchestrator (the main session) rather than
17
17
  improvising. Never widen scope beyond writing the file and counting the backlog.
18
- - **Mid-tier model, not hardcoded.** Run the worker on your tool's balanced default tier
19
- judgment-capable, cheaper and faster than your top reasoning tier. **Not the
20
- cheapest/fastest tier**: on judgment work it measurably degrades (misclassification rates
21
- several times higher). Never hardcode a vendor-specific model name; use whatever your tool
22
- designates as that default.
18
+ - **Explicitly select your tool's mid tier.** State the tier on the spawn do not omit it
19
+ and rely on a default. An omitted tier inherits the *parent's* tier, which is not the same
20
+ thing as the balanced one. Pick the judgment-capable tier that is cheaper and faster than
21
+ your top reasoning tier. **Not the cheapest/fastest tier**: on judgment work it measurably
22
+ degrades (misclassification rates several times higher). Choose by tier, not by a vendor
23
+ model name copied from this file — names drift, and this command ships to several tools.
23
24
  - **Background dispatch where supported.** Run the write-up subagent in the background
24
25
  (non-blocking) so the session isn't held up waiting on formatting/file I/O. Fall back to
25
26
  writing inline (today's behavior) if your tool has no subagent or background-dispatch
@@ -14,31 +14,61 @@ Run this **before** `/release`. `/release` will refuse to run without a review
14
14
  at the current HEAD SHA.
15
15
 
16
16
  ## Guardrails
17
- - **Spawn a worker on a mid-tier model, not hardcoded.** The review runs in a
18
- subagent on your tool's balanced default tier judgment-capable, cheaper and
19
- faster than your top reasoning tier. **Not the cheapest/fastest tier**: on
20
- judgment work it measurably degrades (misclassification rates several times
21
- higher). Never hardcode a vendor-specific model name. Fall back to running
22
- inline if your tool has no subagent mechanism.
17
+ - **Spawn a worker and explicitly select your tool's mid tier.** State the
18
+ tier on the spawn do not omit it and rely on a default. An omitted tier
19
+ inherits the *parent's* tier, which is not the same thing as the balanced
20
+ one. Pick the judgment-capable tier that is cheaper and faster than your top
21
+ reasoning tier. **Not the cheapest/fastest tier**: on judgment work it
22
+ measurably degrades (misclassification rates several times higher). Choose by
23
+ tier, not by a vendor model name copied from this file — names drift, and
24
+ this command ships to several tools. Fall back to running inline if your tool
25
+ has no subagent mechanism.
23
26
  - **Escalate, never assume.** Anything you cannot decide, cannot verify, or
24
27
  that this spec does not cover → **stop and report it to the orchestrator**
25
28
  (the main session). Never improvise, never widen scope, never fix a side
26
29
  issue you noticed along the way.
30
+ - **The worker does the work itself — no delegation.** The review subagent
31
+ must **not** spawn subagents of its own. Everything it reports has to be
32
+ something it read, ran, or grepped with its own tool calls: a relayed "I
33
+ executed X" from a sub-worker is hearsay, and replacing hearsay with evidence
34
+ is the entire point of this command. A review that delegates its work is a
35
+ review of a report. (Same rule `/security` carries inside stage 2.)
27
36
  - **No edits.** You have no authorization to change code, even for a finding
28
- you are certain about. Report it.
29
-
30
- ## Target interpret `$ARGUMENTS` in this order
37
+ you are certain about. Report it. Re-run `git status --porcelain` before you
38
+ report and confirm it is still empty — if it is not, say what changed. That
39
+ turns "it never edits" from a claim into a checked fact.
40
+
41
+ ## Target — check the tree first, then interpret `$ARGUMENTS`
42
+
43
+ **Before resolving anything, run `git status --porcelain`.** If it prints any
44
+ line — modified, staged, or untracked — **stop and report it**. Say all three
45
+ things, not just the first: (a) the tree is dirty, listing the uncommitted
46
+ paths; (b) `/branch-review` reviews commits, not the working tree; (c) **commit
47
+ the work to the branch, then re-run `/branch-review`.** A stop that names the
48
+ problem without the remedy invites the orchestrator to stash the changes or
49
+ hand-review the working tree instead. Do not review a subset and do not fall
50
+ back to the staged diff or the working tree. A dirty
51
+ tree is an **error**, never a silent partial review — the most expensive
52
+ failure this command can have is reviewing 800 committed lines while 200
53
+ uncommitted lines of today's actual work go unread.
54
+
55
+ This is forced by the design, not a preference: `/release`'s precondition is a
56
+ review at the current HEAD SHA, and any commit made after the review makes it
57
+ stale. **The only correct order is commit → review → release.**
58
+
59
+ With a clean tree, interpret `$ARGUMENTS` in this order:
31
60
  1. **Empty** → the current branch vs its merge-base with `main`
32
- (`git diff $(git merge-base main HEAD)..HEAD`). If that is empty, the
33
- staged diff; if that is empty too, the working tree.
61
+ (`git diff $(git merge-base main HEAD)..HEAD`). If that is empty there is
62
+ nothing committed to review say so and stop.
34
63
  2. **A range** like `main..HEAD` or `origin/main...HEAD` → `git diff <range>`.
35
64
  3. **A single ref** (branch / tag / SHA — confirm with `git rev-parse
36
65
  --verify`) → that ref's merge-base against `HEAD`.
37
66
  4. **A file or directory path** → that target.
38
67
  5. Otherwise → ask.
39
68
 
40
- Record the **HEAD SHA** you reviewed. `/release` checks it, and any commit
41
- made after the review makes the review stale.
69
+ Record the **HEAD SHA** you reviewed, and **report the target you resolved**
70
+ (the literal range or path) in your output, so the orchestrator can see what
71
+ was actually read rather than assuming.
42
72
 
43
73
  ## Effort level
44
74
  `low | medium | high | max` — default **medium** if not given. The level
@@ -57,6 +87,13 @@ hunk-only read cannot see that a caller further down the same file is now
57
87
  wrong. For multi-commit ranges, skim `git log <range>` for intent before
58
88
  judging.
59
89
 
90
+ **Commit messages are claims, not evidence.** A message saying a fix was
91
+ "proven red→green", a bug reproduced, or a test added is something to re-test,
92
+ not a fact to accept. Branches are commonly AI-authored now — including the
93
+ fixes to the fixes — so a review that trusts the message is reviewing prose.
94
+ Run the test suite and the typecheck/build yourself and cite the command and
95
+ its exit code.
96
+
60
97
  - **Bugs needing a fix.** Logic errors, off-by-one, null/undefined paths,
61
98
  races, wrong defaults, broken edge cases.
62
99
  - **Dead code.** Unreferenced functions / vars / imports / params, unreachable
@@ -69,6 +106,16 @@ judging.
69
106
  broken invariants.
70
107
  - **Performance.** N+1, blocking calls in hot paths, unbounded loops, indexes
71
108
  the diff actually touches.
109
+ - **Test quality, not just test presence.** For every test the diff adds or
110
+ changes, establish that it **can actually fail**. Reasoning about
111
+ falsifiability does not work; executing it does. **Revert the source, not the
112
+ test:** take the pre-change version of the file under test with `git show
113
+ <base-sha>:<path>`, run the test against that copy, and watch it go red. Do
114
+ this **without dirtying the branch** — write the old version to a temp
115
+ location outside the repo; the tree must still be clean at exit. A test that
116
+ passes against both the buggy and the fixed source is a tautology and proves
117
+ nothing. Flag every one you find, and say so explicitly when the tests are
118
+ the branch's only evidence for its claims.
72
119
  - **Maintainability.** Complexity, naming, duplication — only when material.
73
120
 
74
121
  ## Stage 2 — Security (always full)
@@ -102,7 +149,12 @@ cannot write that sentence, the finding is not ready — drop it or mark it
102
149
  uncertain. No vibes.
103
150
 
104
151
  ## Report — then escalate
105
- Order findings most severe first.
152
+ **Open with the one-line verdict**, before any section: **Ready to merge? Yes /
153
+ No / Not until these are fixed.** A report that opens with "Critical: none
154
+ found" reads as a pass at a glance even when the verdict is not one — state the
155
+ verdict first, then repeat it at the end.
156
+
157
+ Then the findings, ordered most severe first.
106
158
 
107
159
  ### 🚨 Critical (blocks merge)
108
160
  ### ⚠️ Warnings (should fix)
@@ -117,7 +169,8 @@ with its evidence. A stage you did not actually run is a **✗**, never an
117
169
  assumed pass.
118
170
 
119
171
  End with:
120
- - **Reviewed at HEAD `<sha>` on `<branch>`.**
172
+ - **Reviewed at HEAD `<sha>` on `<branch>`, target `<resolved range or path>`,
173
+ tree clean at start and at exit.**
121
174
  - One-line verdict: **Ready to merge? Yes / No / Not until these are fixed.**
122
175
  - **Escalate to the orchestrator** with the findings. It decides what gets
123
176
  fixed and by whom. Say plainly what you could not verify.