@olegkoval/agent-skills 1.42.3 → 1.43.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.
@@ -17,9 +17,16 @@ description: >
17
17
  With --fix (or by accepting the post-review offer) it also APPLIES its own
18
18
  Critical/Important findings as real code in the worktree, verifies each edit,
19
19
  commits them, and pushes to the PR branch only after explicit confirmation.
20
+ Also reviews a LOCAL BRANCH BEFORE IT IS PUSHED (`/lekker-review branch
21
+ --fix`): same agents, same fixes, against the merge-base with the branch the
22
+ PR would target, with no PR and no network round trip. Running it pre-push is
23
+ how you stop paying bot reviewers like Greptile/CodeRabbit to find what this
24
+ skill finds for free, so branch mode defaults to --fix and offers to open the
25
+ PR when it is done.
20
26
  Use when the user says "review this PR", "lekker review", "check this PR",
21
27
  "do a code review on PR #N", "review and fix this PR", "apply the review
22
- fixes", provides a GitHub PR URL, or asks for a pull request review in any
28
+ fixes", "review my branch", "review before push", "check this before I open
29
+ the PR", provides a GitHub PR URL, or asks for a pull request review in any
23
30
  form.
24
31
  license: MIT
25
32
  allowed-tools: Bash, Read, Write, Edit, Agent, Workflow, AskUserQuestion, Artifact
@@ -38,7 +45,7 @@ metadata:
38
45
  - multi-agent
39
46
  - workflow
40
47
  - quality
41
- argument-hint: "<github-pr-url | repo pr-number | repo pr-title> [scan|medium|deep] [--post] [--fix]"
48
+ argument-hint: "<github-pr-url | repo pr-number | repo pr-title | branch [ref]> [scan|medium|deep] [--post] [--fix] [--base <ref>]"
42
49
  ---
43
50
  <!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
44
51
 
@@ -49,6 +56,19 @@ FAANG-grade code review. Isolated worktree checkout, full context gathering
49
56
  you have MCP tools configured for), then 5 parallel specialized review
50
57
  agents, a finding-verification pass, and one unified markdown output.
51
58
 
59
+ Two modes, same review engine:
60
+
61
+ | Mode | Target | Entry |
62
+ |------------|-------------------------------------|-----------------------------------------|
63
+ | **pr** | An open GitHub PR | a PR url / `repo N` / `repo "title"` |
64
+ | **branch** | A local branch, before it is pushed | `branch [ref]`, or `--branch` |
65
+
66
+ Branch mode exists to move the review left: everything a bot reviewer
67
+ (Greptile, CodeRabbit, or similar) would charge for on the PR is found and
68
+ FIXED locally first, so the PR opens clean and there is little left to bill
69
+ for. It reviews the same diff a PR would show - merge-base against the branch
70
+ the PR would target - and it defaults to `--fix`.
71
+
52
72
  Each finding contains: **file + risk** · **bad code verbatim** · **why it's
53
73
  wrong** · **fix with code example** - ready to paste directly into GitHub.
54
74
  Reviews are saved to `~/code-reviews/` for future reference.
@@ -92,6 +112,24 @@ resolve every `references/...` and script path relative to it.
92
112
 
93
113
  ## Step 0 - Parse input
94
114
 
115
+ ### Pick the mode first
116
+
117
+ `MODE=branch` when ANY of these hold:
118
+ - the argument is the literal word `branch` (optionally followed by a ref), or
119
+ the flag `--branch [ref]` is present
120
+ - the user asked for a review "before push", "before the PR", "of my branch",
121
+ "of these commits", or named a local branch that has no open PR
122
+ - no PR reference was given at all and `$PWD` is inside a git repo on a branch
123
+ that is not the base branch
124
+
125
+ Otherwise `MODE=pr`.
126
+
127
+ Never guess between the two. If a PR reference is present, it is `pr` mode
128
+ even in a git repo. If neither a PR reference nor a git repo is available,
129
+ stop and ask - do not review the wrong thing.
130
+
131
+ ### MODE=pr
132
+
95
133
  Accept any of:
96
134
  - Full GitHub URL: `https://github.com/owner/repo/pull/123`
97
135
  - Repo + number: `my-service 42`
@@ -106,10 +144,65 @@ Derive and carry these variables through every subsequent step:
106
144
  - `PR_NUMBER`
107
145
  - `PR_BRANCH` (from `gh pr view`)
108
146
  - `PR_URL` = `https://github.com/<REPO_SLUG>/pull/<PR_NUMBER>`
147
+ - `TARGET_LABEL` = `PR #<PR_NUMBER>`
148
+
149
+ ### MODE=branch
150
+
151
+ Resolve everything from the local repo - no `gh pr` call, no network:
152
+
153
+ ```bash
154
+ LOCAL_REPO=$(git -C "$PWD" rev-parse --show-toplevel)
155
+ # Use the ref the user named when present. Otherwise require an attached branch.
156
+ LOCAL_BRANCH="${USER_PROVIDED_REF:-}"
157
+ if [ -z "$LOCAL_BRANCH" ]; then
158
+ LOCAL_BRANCH=$(git -C "$LOCAL_REPO" symbolic-ref --short -q HEAD)
159
+ fi
160
+ if [ -z "$LOCAL_BRANCH" ]; then
161
+ echo "Detached HEAD: name the local ref to review." >&2
162
+ exit 1
163
+ fi
164
+
165
+ ORIGIN_URL=$(git -C "$LOCAL_REPO" remote get-url origin 2>/dev/null || true)
166
+ if [ -n "$ORIGIN_URL" ]; then
167
+ REPO_SLUG=$(printf '%s\n' "$ORIGIN_URL" \
168
+ | sed -E 's#(git@github.com:|https://github.com/)##; s#\.git$##')
169
+ else
170
+ LOCAL_REPO_NAME=$(basename "$LOCAL_REPO" \
171
+ | sed -E 's#[^[:alnum:]_.-]+#-#g; s#^-+|-+$##g')
172
+ REPO_SLUG="local/${LOCAL_REPO_NAME:-repository}"
173
+ fi
174
+ ```
109
175
 
110
- **Depth:** explicit keyword `scan`, `medium`, or `deep` wins. If absent, run
111
- `gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles`
112
- and apply AUTO-DEPTH:
176
+ An explicitly named ref may be reviewed from detached HEAD. Before landing
177
+ fixes, resolve it to a local branch name or stop; never build
178
+ `refs/heads/<LOCAL_BRANCH>` from a commit SHA or another non-branch ref. The
179
+ `local/<directory>` slug is reporting metadata for repositories without an
180
+ `origin`; preserve the origin-derived slug whenever that remote exists.
181
+
182
+ Then:
183
+ - `BASE_REF`: the branch the PR would target. Honour `--base <ref>` when given.
184
+ Otherwise let `scripts/changed-files.sh` detect it (origin's default branch,
185
+ then whatever your convention falls back to) and report which one it picked.
186
+ If detection lands on something that contradicts your team's actual
187
+ convention, say so in the review header rather than reviewing silently
188
+ against the wrong base.
189
+ - `PR_NUMBER` = null, `PR_URL` = null, `TARGET_LABEL` = `branch <LOCAL_BRANCH>`.
190
+ - The reviewed diff is `merge-base(HEAD, BASE_REF)..HEAD` - committed work only.
191
+ **Uncommitted and untracked changes are NOT reviewed.** If `git status
192
+ --porcelain` is non-empty, say so in one line before starting
193
+ (`⚠️ N uncommitted file(s) are not part of this review`) so nobody assumes
194
+ coverage that does not exist.
195
+ - If that diff is empty, stop: `nothing to review - <branch> matches <base>`.
196
+
197
+ **Guard:** never run branch mode on a long-lived integration branch. If
198
+ `LOCAL_BRANCH` is `main`, `master`, `develop`, `staging`, or `production`, stop
199
+ and say so.
200
+
201
+ **Depth:** explicit keyword `scan`, `medium`, or `deep` wins. If absent, get
202
+ the diff stat - in `pr` mode from
203
+ `gh pr view <PR_NUMBER> --repo <REPO_SLUG> --json additions,deletions,changedFiles`,
204
+ in `branch` mode from `git diff --shortstat <MERGE_BASE> HEAD` plus
205
+ `git diff --name-only <MERGE_BASE> HEAD | wc -l` - and apply AUTO-DEPTH:
113
206
  - `scan` if additions+deletions < 150 AND changedFiles <= 5
114
207
  - `deep` if additions+deletions > 800 OR changedFiles > 25 OR diff touches
115
208
  `migrations/` or `*.sql`
@@ -124,11 +217,26 @@ checkout, so `--fix` forces Track A (worktree setup) to run even when
124
217
  depth=scan. If the user did NOT pass `--fix`, leave `FIX_MODE=false` for now -
125
218
  Step 4 offers it after the review is printed.
126
219
 
220
+ **Branch mode defaults to fix.** In `MODE=branch`, `FIX_MODE=true` and
221
+ `FIX_SCOPE=critical+important` unless the user passed `--no-fix`. That is the
222
+ whole point of reviewing pre-push: findings get applied before the PR exists,
223
+ so a bot reviewer never sees them. Landing those fixes on the local branch
224
+ still needs explicit confirmation (Step 4).
225
+
226
+ **`--base <ref>` flag (branch mode only):** overrides base-ref detection. Passed
227
+ through to the scripts as `LEKKER_BASE_REF`. Ignored in `pr` mode, where the
228
+ base comes from the PR.
229
+
230
+ **`--no-fix` flag:** parse and store as `FIX_MODE=false`, and do not offer fixes
231
+ in Step 4. Use it for a read-only pre-push look.
232
+
127
233
  **`--no-artifact` flag:** parse and store as `ARTIFACT=false` (default true).
128
234
  Skips Step 3.5 (living review artifact) silently.
129
235
 
130
236
  **Re-review detection:** run
131
- `ls ~/code-reviews/*-pr-<PR_NUMBER>-<repo-short-name>.md 2>/dev/null | sort | tail -1`
237
+ `ls ~/code-reviews/*-<TARGET_SLUG>-<repo-short-name>.md 2>/dev/null | sort | tail -1`
238
+ (`TARGET_SLUG` = `pr-<PR_NUMBER>` in pr mode, `branch-<LOCAL_BRANCH sanitized to
239
+ [a-z0-9-]>` in branch mode)
132
240
  to find the newest prior review for this PR (repo-short-name = last segment of
133
241
  REPO_SLUG; keeps PR numbers from colliding across repos). If found, grep it for
134
242
  `\*\*Head:\*\*` and extract the short sha. Set `PREV_SHA=<sha>` and
@@ -159,6 +267,18 @@ For scan: note `⚡ scan - worktree unavailable, static checks skipped` in the
159
267
  review header. When `--fix` forced the worktree at scan depth, drop that note
160
268
  and say `⚡ scan - worktree created for --fix` instead.
161
269
 
270
+ **Branch mode always has a worktree** (fix mode is the default, and there is no
271
+ remote diff to fall back on), so the scan row's "skip worktree" never applies.
272
+ Everything else in the table is unchanged: branch mode is not a shallower
273
+ review, it is the same review earlier. Two rows differ because their inputs do
274
+ not exist yet:
275
+
276
+ | Step | branch mode |
277
+ |--------------------------|--------------------------------------------------------------|
278
+ | CI checks | `N/A - not pushed`; local compiler/linter/tests are the only signal |
279
+ | Existing bot reviews | none by definition - that is the saving |
280
+ | `--post` | unsupported until a PR exists (Step 4.6 can create one) |
281
+
162
282
  ---
163
283
 
164
284
  ## Step 1 - Context + worktree (concurrent)
@@ -170,14 +290,30 @@ Fire both tracks in the same turn.
170
290
  Run via Bash with `run_in_background`:
171
291
 
172
292
  ```bash
293
+ # MODE=pr - checks out origin/<PR_BRANCH>, fetching if needed
173
294
  ${CLAUDE_PLUGIN_ROOT}/scripts/setup-worktree.sh \
174
295
  <REPO_SLUG> <PR_BRANCH> <scratchpad>/worktree.json [PREV_SHA]
175
296
  ```
176
297
 
298
+ ```bash
299
+ # MODE=branch - LOCAL ref only. LEKKER_LOCAL_REPO skips repo discovery, the
300
+ # fetch, and the origin/<branch> checkout entirely, and creates the worktree
301
+ # DETACHED so a branch already checked out in the user's own working copy can
302
+ # still be reviewed. LEKKER_BASE_REF is set only when --base was given.
303
+ LEKKER_LOCAL_REPO=<LOCAL_REPO> [LEKKER_BASE_REF=<BASE_REF>] \
304
+ ${CLAUDE_PLUGIN_ROOT}/scripts/setup-worktree.sh \
305
+ <REPO_SLUG> <LOCAL_BRANCH> <scratchpad>/worktree.json [PREV_SHA]
306
+ ```
307
+
177
308
  On completion, read `worktree.json`. Keys emitted:
178
309
  `worktreePath`, `repoRoot`, `headSha`, `headShaShort`, `tscTail`,
179
310
  `tscChangedTail`, `tscErrorCount`, `eslintTail`, `eslintScope`, `changedFiles`,
180
- `baseRef`, `projectRules`, `deltaFile`, `notes`.
311
+ `baseRef`, `mergeBase`, `projectRules`, `deltaFile`, `notes`.
312
+
313
+ `mergeBase` is the commit the file list was scoped against. Branch mode
314
+ generates its diff from that exact sha (below), so the diff the agents read and
315
+ the files the static checks were scoped to can never disagree. `notes` contains
316
+ `local-branch-mode;` when the local path ran.
181
317
 
182
318
  Static checks are scoped so you can tell this PR's errors from the repo's
183
319
  standing debt - do not try to infer that from the raw tail:
@@ -198,7 +334,11 @@ A failing CI build or test = Critical finding input.
198
334
 
199
335
  When depth=scan: set `WORKTREE_PATH=null` without launching the script - unless
200
336
  `FIX_MODE=true`, in which case run the script anyway (fix mode cannot edit code
201
- from a diff).
337
+ from a diff). In `MODE=branch` the script always runs, at every depth.
338
+
339
+ A failing local compiler/linter/test run in branch mode is the direct
340
+ replacement for the failing-CI signal - treat it exactly the same way. Catching
341
+ it here is a CI run the PR never has to burn.
202
342
 
203
343
  ### Track B - metadata and signals (all calls fired in parallel)
204
344
 
@@ -206,6 +346,31 @@ Run ALL of the following in the same message. Full query details are in
206
346
  `references/context-gathering.md` - follow it, do not paste it wholesale into
207
347
  agent contexts.
208
348
 
349
+ **In `MODE=branch`, every `gh pr *` call below is skipped** - there is no PR.
350
+ Wait for Track A's `worktree.json` (you need `mergeBase`), then substitute:
351
+
352
+ ```bash
353
+ # The diff, from the SAME merge-base the file list was scoped to.
354
+ git -C <worktreePath> diff <mergeBase> HEAD > <scratchpad>/pr.diff
355
+
356
+ # Commit log, for ticket ids and for the PR title the branch would need.
357
+ git -C <worktreePath> log --format='%s%n%b' <mergeBase>..HEAD
358
+ ```
359
+
360
+ - The title/ticket-prefix check in branch mode is a PRE-check, not a
361
+ violation: scan the commit subjects for your team's ticket pattern, collect
362
+ the distinct ids, and set `RECOMMENDED_PREFIX`. There is no PR title to be
363
+ wrong yet, so `PR_TITLE_ISSUE=false` - report the prefix as the title to use,
364
+ in the Step 4.6 PR offer.
365
+ - Ticket ids come from the commit log and the branch name instead of the PR
366
+ title/body; the issue-tracker lookup below is otherwise identical.
367
+ - `ciStatus` = `N/A - not pushed`. `existingReviews` = null.
368
+ - Everything else in Track B (issue tracker, chat, docs, framework docs,
369
+ monitoring, prior-review memory) runs unchanged, keyed off the ticket ids
370
+ and the branch name.
371
+
372
+ `MODE=pr` calls:
373
+
209
374
  - `gh pr view <PR_NUMBER> --repo <REPO_SLUG>` with fields: `number`, `title`,
210
375
  `body`, `author`, `headRefName`, `baseRefName`, `labels`, `linkedBranches`,
211
376
  `mergeStateStatus`, `additions`, `deletions`, `changedFiles`, `isDraft`,
@@ -241,10 +406,16 @@ Write `<scratchpad>/context.json` with keys:
241
406
  "ciStatus": "<passing | failing: <names> | pending | N/A>",
242
407
  "existingReviews": "<prior review summaries>",
243
408
  "deltaFile": "<worktree.json deltaFile, or null>",
244
- "houseRulesFile": "${CLAUDE_PLUGIN_ROOT}/references/house-rules.md"
409
+ "houseRulesFile": "${CLAUDE_PLUGIN_ROOT}/references/house-rules.md",
410
+ "reviewTarget": "<TARGET_LABEL, e.g. 'PR #412' or 'branch feat/offline-orders'>",
411
+ "prePush": "<true in MODE=branch, false in MODE=pr>"
245
412
  }
246
413
  ```
247
414
 
415
+ `prePush: true` tells the agents there is no CI verdict and no bot review to
416
+ defer to, and that anything they flag is cheaper to fix now than after the PR
417
+ opens. It does NOT lower the bar: same severities, same no-nitpicking rule.
418
+
248
419
  Agents read keys from this file. Nothing from CONTEXT_FILE is pasted into
249
420
  their prompts wholesale - the workflow script delivers it by path.
250
421
 
@@ -258,8 +429,9 @@ Invoke the Workflow tool:
258
429
  scriptPath: ${CLAUDE_PLUGIN_ROOT}/workflow.js
259
430
  args: {
260
431
  repoSlug,
261
- prNumber,
262
- prUrl,
432
+ prNumber, // null in MODE=branch
433
+ prUrl, // null in MODE=branch
434
+ targetLabel, // TARGET_LABEL - REQUIRED when prNumber is null
263
435
  depth,
264
436
  diffFile: "<scratchpad>/pr.diff",
265
437
  contextFile: "<scratchpad>/context.json",
@@ -269,6 +441,10 @@ args: {
269
441
  }
270
442
  ```
271
443
 
444
+ `targetLabel` is what every agent is told it is reviewing. The workflow falls
445
+ back to `PR #<prNumber>` when it is absent, so pr mode may omit it; branch mode
446
+ must pass it, or the workflow throws on its missing-args guard.
447
+
272
448
  Two optional args tune concurrency; omit both unless a run needs it:
273
449
 
274
450
  - `reviewBatchPlan` - batch sizes for the review dimensions, default `[5]`
@@ -399,12 +575,19 @@ dropped.
399
575
  requirements:
400
576
 
401
577
  - Header must include `**Head:** <headShaShort>` (enables future delta mode).
578
+ - **Branch mode header:** title the review `Pre-push review - <LOCAL_BRANCH>`,
579
+ and include `**Target:** branch <LOCAL_BRANCH> -> <BASE_REF> (not pushed)`,
580
+ `**Base:** <mergeBase short sha>`, and `**CI:** N/A - not pushed`. State the
581
+ uncommitted-file warning here if `git status --porcelain` was non-empty.
582
+ There is no PR link and no bot-review section; do not invent either.
402
583
  - When `isDraft=true`: add `**DRAFT PR** - findings recorded for when this
403
584
  is ready to merge.` after the header block.
404
585
  - When `mergeStateStatus` is not CLEAN: note it (e.g. conflicts, failing
405
586
  required checks).
406
587
  - When `PR_TITLE_ISSUE=true`: insert the `⛔ CANNOT MERGE` block before the
407
- Summary.
588
+ Summary. In branch mode there is no title yet, so instead print one line -
589
+ `**PR title to use:** <RECOMMENDED_PREFIX> <summary>` - and carry it into
590
+ the Step 4.6 offer.
408
591
  - When `sentrySignals` is non-empty: include `## 🔥 Production Signals`.
409
592
  - When `PREV_SHA` is set: include `## 🔁 Since last review` comparing
410
593
  `PREV_REVIEW_FILE` findings against the new head - list each as fixed or
@@ -423,7 +606,9 @@ requirements:
423
606
 
424
607
  ```bash
425
608
  mkdir -p ~/code-reviews
426
- REVIEW_FILE=~/code-reviews/$(date +%Y-%m-%d)-pr-<PR_NUMBER>-<repo-short-name>.md
609
+ # TARGET_SLUG: "pr-<PR_NUMBER>" in pr mode,
610
+ # "branch-<LOCAL_BRANCH sanitized to [a-z0-9-]>" in branch mode
611
+ REVIEW_FILE=~/code-reviews/$(date +%Y-%m-%d)-<TARGET_SLUG>-<repo-short-name>.md
427
612
  # write the review to $REVIEW_FILE
428
613
  ```
429
614
 
@@ -435,6 +620,18 @@ Also write the workflow's `findings` array verbatim to
435
620
  `proof` objects ride along for the Step 5b proof flip), and it is the receipt
436
621
  that what was reported equals what was found.
437
622
 
623
+ **Branch mode also drops a review marker**, so a pre-PR gate you build on top
624
+ of this skill can tell a reviewed branch from an unreviewed one:
625
+
626
+ ```bash
627
+ mkdir -p ~/.cache/lekker-review/reviewed
628
+ printf '%s\n' "$REVIEW_FILE" > ~/.cache/lekker-review/reviewed/<headSha>
629
+ ```
630
+
631
+ Write it against the sha that was actually reviewed. When fix mode later adds
632
+ commits, write a marker for the NEW head sha too - a gate keys on the tip
633
+ that is about to be pushed, and a marker for a superseded sha would be a lie.
634
+
438
635
  Then print the full review as the response.
439
636
 
440
637
  ---
@@ -459,8 +656,8 @@ and print one line: `🔗 Living review: <url>`.
459
656
 
460
657
  ### Trigger
461
658
 
462
- - `FIX_MODE=true` (the user passed `--fix`) -> go straight to
463
- `references/fix-mode.md`.
659
+ - `FIX_MODE=true` (the user passed `--fix`, or `MODE=branch` defaulted it on)
660
+ -> go straight to `references/fix-mode.md`.
464
661
  - `FIX_MODE=false` and at least one Critical or Important finding has a `fix`
465
662
  field -> ask once, via AskUserQuestion:
466
663
 
@@ -475,22 +672,29 @@ and print one line: `🔗 Living review: <url>`.
475
672
  - No fixable findings, or the review found nothing -> do not ask. Say
476
673
  `nothing to auto-fix` in one line and skip to Step 5.
477
674
  - **Unattended run** (cron, `/loop`, background agent): never ask. Run fix mode
478
- only when `--fix` was passed explicitly, and stop before pushing.
675
+ only when `--fix` was passed explicitly, and stop before pushing (branch mode:
676
+ stop before landing).
479
677
 
480
678
  ### Procedure
481
679
 
482
680
  Read `references/fix-mode.md` and follow it. Shape of the run:
483
681
 
484
- 1. Preconditions: worktree exists + clean, `origin/<PR_BRANCH>` still at
485
- `headSha`, PR open, head repo writable.
682
+ 1. Preconditions.
683
+ - `MODE=pr`: worktree exists + clean, `origin/<PR_BRANCH>` still at
684
+ `headSha`, PR open, head repo writable.
685
+ - `MODE=branch`: worktree exists + clean, and `refs/heads/<LOCAL_BRANCH>` in
686
+ `repoRoot` is still at `headSha`. If the branch moved while the review ran,
687
+ stop - do not land onto a tip you did not review.
486
688
  2. Select eligible findings (Critical/Important with a `fix`, real file,
487
689
  non-generated). Never auto-fix Observation, Idiomatic, or a title/process rule.
488
690
  3. Invoke the fix workflow:
489
691
  ```
490
692
  scriptPath: ${CLAUDE_PLUGIN_ROOT}/fix-workflow.js
491
- args: { repoSlug, prNumber, worktreePath, diffFile, contextFile, promptDir,
492
- findings: [<selected findings verbatim>] }
693
+ args: { repoSlug, prNumber, targetLabel, worktreePath, diffFile, contextFile,
694
+ promptDir, findings: [<selected findings verbatim>] }
493
695
  ```
696
+ `targetLabel` is required whenever `prNumber` is null, same as the review
697
+ workflow.
494
698
  One `sonnet` fix agent per file (never two on the same file), then a
495
699
  read-only `sonnet` fix-verifier per file reading the actual `git diff`. One
496
700
  retry max on a non-`good` verdict.
@@ -503,9 +707,37 @@ Read `references/fix-mode.md` and follow it. Shape of the run:
503
707
  group even if the fix-verifier said `good`. An executed test outranks an
504
708
  agent's opinion. Green -> record `proofFlip: green` in the status table.
505
709
  6. Commit one commit per file with an explicit `git add -- <files>`.
506
- 7. Push ONLY after the user confirms, with `git push origin HEAD:refs/heads/<PR_BRANCH>`
507
- and a re-fetch sha guard. Never force, never rebase, never push to
508
- main/master/staging/develop. Verify via `gh pr view --json headRefOid`.
710
+ 7. Land the commits, ONLY after the user confirms.
711
+ - `MODE=pr`: `git push origin "HEAD:refs/heads/$pr_branch"` with a re-fetch
712
+ sha guard. Never force, never rebase, never push to
713
+ main/master/staging/develop. Verify via `gh pr view --json headRefOid`.
714
+ - `MODE=branch`: nothing is pushed - the fixes move onto the LOCAL branch, so
715
+ the work is one branch again before it ever reaches the remote.
716
+
717
+ ```bash
718
+ # Fast-forward the local branch onto the reviewed-and-fixed worktree tip.
719
+ # --ff-only and the old-value guard together mean this can only ever
720
+ # advance the exact commit the review started from.
721
+ git check-ref-format --branch "$local_branch"
722
+ git -C "$repo_root" update-ref "refs/heads/$local_branch" \
723
+ "$worktree_head_sha" "$head_sha"
724
+ ```
725
+
726
+ Treat every repository path, ref, branch, title, and body as untrusted
727
+ data. Pass each value as a separately shell-quoted argument, never by
728
+ concatenating it into shell source. Put multiline PR bodies in a file and
729
+ pass the quoted path with `--body-file`.
730
+
731
+ If `$local_branch` is the branch checked out in `$repo_root`, `update-ref`
732
+ would leave the user's working tree looking like it had deleted the fixes.
733
+ In that case require a clean `git -C "$repo_root" status --porcelain` and use
734
+ `git -C "$repo_root" merge --ff-only "$worktree_head_sha"` instead. If the tree
735
+ is dirty, stop, keep the worktree, and print the exact command - never
736
+ stash or discard someone's uncommitted work.
737
+
738
+ Verify with `git -C "$repo_root" rev-parse "refs/heads/$local_branch"` and
739
+ confirm it equals `$worktree_head_sha`. Then refresh the review marker for
740
+ the new sha (Step 3).
509
741
  8. Print the per-finding status table and append `## 🔧 Fixes applied` to the
510
742
  saved review file.
511
743
 
@@ -514,10 +746,55 @@ Fix-agent tokens are additional spend: add a `Fix agents:` line to the
514
746
 
515
747
  ---
516
748
 
749
+ ## Step 4.6 - Open the PR (MODE=branch only)
750
+
751
+ Skip entirely in `MODE=pr`.
752
+
753
+ The branch has now been reviewed and, where it had fixable findings, fixed. The
754
+ PR is the next step, but it is the USER'S call and it is the one irreversible
755
+ thing in this whole flow - once the PR exists a bot reviewer may start running
756
+ and start costing money. **Never create it silently.**
757
+
758
+ Ask once, via AskUserQuestion:
759
+
760
+ > Branch reviewed<, N fixes applied>. Open the PR now?
761
+ > - **Yes, create the PR** - `<RECOMMENDED_PREFIX>` title, base `<BASE_REF>`,
762
+ > pushes `<LOCAL_BRANCH>` first
763
+ > - **Push the branch only** - no PR yet
764
+ > - **No, stop here** - nothing leaves this machine
765
+
766
+ Rules for each answer:
767
+
768
+ - **Yes:** push with `git push -u origin "$local_branch"`, then create the PR
769
+ with `gh pr create --repo "$repo_slug" --base "$base_ref" --head
770
+ "$local_branch" --title "$pr_title" --body-file "$pr_body_file"`. Derive the title
771
+ from the commit log (the recommended prefix plus a summary of the change) and
772
+ the body from a short summary of what the branch does - do not leave either
773
+ as a placeholder. Report the PR url. The review is already saved locally;
774
+ offer `--post` on the new PR number only if the user asks - findings that fix
775
+ mode already applied must never be posted as review comments.
776
+ - **Push only:** `git push -u origin "$local_branch"` and stop. Say in one line
777
+ that no PR was created.
778
+ - **No:** stop. Print the branch name and the review file path.
779
+
780
+ **Unattended runs never ask and never create a PR** (see the operating contract
781
+ for your host: with no human present, "ask for confirmation" means stop and
782
+ report). Stage the branch, print what would have happened, and stop.
783
+
784
+ If the review verdict is `🚫 Needs work` with an unfixed Critical, say so in the
785
+ question - opening a PR on a known-broken branch just moves the finding to
786
+ whoever reviews it next.
787
+
788
+ ---
789
+
517
790
  ## Step 5 - Post-review (after the review and any fixes)
518
791
 
519
792
  ### If POST_REVIEW=true
520
793
 
794
+ In `MODE=branch` there is no PR to post to. If `--post` was passed, say
795
+ `--post ignored - no PR yet` in one line and skip this section, unless Step 4.6
796
+ just created a PR, in which case use that PR number.
797
+
521
798
  Follow `references/github-post.md`:
522
799
  - Build a JSON payload with Critical + Important findings as inline comments
523
800
  (only for lines present in the diff hunks).
@@ -544,10 +821,14 @@ repo-short-name + PR author login, instructed to follow
544
821
 
545
822
  ### Cleanup (when a worktree was created)
546
823
 
547
- **Fix-mode override:** if fix mode produced commits that were NOT pushed, do
548
- NOT clean up. Keep the worktree and the repo clone, print the worktree path and
549
- the exact push command. Deleting it destroys the only copy of the work. Clean up
550
- normally when the push succeeded or nothing was committed.
824
+ **Fix-mode override:** if fix mode produced commits that were NOT pushed (pr
825
+ mode) or NOT landed onto the local branch (branch mode), do NOT clean up. Keep
826
+ the worktree and the repo clone, print the worktree path and the exact
827
+ push/land command. Deleting it destroys the only copy of the work. Clean up
828
+ normally when the push or the land succeeded, or nothing was committed.
829
+
830
+ In branch mode `repoRoot` is the user's own working copy, never a
831
+ `/tmp/lekker-clone-*`. Remove the worktree; NEVER remove `repoRoot`.
551
832
 
552
833
  ```bash
553
834
  git -C <repoRoot> worktree remove --force <worktreePath> \
@@ -583,3 +864,9 @@ Fix-mode specific:
583
864
  - Never push without explicit confirmation, and never force-push or rebase a
584
865
  PR branch. If the branch moved under you, stop and report - the commits stay
585
866
  local.
867
+ - Branch mode never pushes on its own: landing means moving the reviewed
868
+ commits onto `refs/heads/<LOCAL_BRANCH>` in the user's own `repoRoot`, always
869
+ with a compare-and-swap (`update-ref <new> <old>` or `merge --ff-only`) that
870
+ can only succeed if the branch is still exactly where the review started. If
871
+ it moved, or the working tree is dirty, stop and print the exact command -
872
+ never stash or discard uncommitted work to force the landing through.
@@ -11,9 +11,15 @@
11
11
  # tree + staged) changes -- what a fix agent just wrote, even if it wasn't
12
12
  # part of the original PR diff.
13
13
  #
14
- # Never fails: if no base ref can be resolved (or merge-base fails), prints
15
- # NOTHING to stdout, "base-ref: unknown" to stderr, and exits 0. Callers
16
- # should treat empty stdout as "could not scope -- fall back to repo-wide".
14
+ # LEKKER_BASE_REF=<ref> overrides base-ref detection entirely (pre-push branch
15
+ # mode passes the branch the PR would target). Also prints the resolved
16
+ # "merge-base: <sha>" to stderr so a caller can produce a diff from the exact
17
+ # same point this file list was scoped to.
18
+ #
19
+ # If automatic base-ref detection finds nothing, prints NOTHING to stdout,
20
+ # "base-ref: unknown" to stderr, and exits 0 so callers can fall back to a
21
+ # repo-wide check. An invalid explicit base or a missing merge base is a caller
22
+ # error and exits nonzero without emitting base-ref/merge-base metadata.
17
23
 
18
24
  set -uo pipefail
19
25
 
@@ -25,10 +31,16 @@ INCLUDE_UNCOMMITTED=false
25
31
  # 1. Resolve a base ref: origin's default branch first, then a fixed
26
32
  # fallback order.
27
33
  # ---------------------------------------------------------------------------
28
- BASE_REF=""
34
+ BASE_REF="${LEKKER_BASE_REF:-}"
35
+ if [[ -n "$BASE_REF" ]] && ! git -C "$WORKTREE_PATH" rev-parse --verify --quiet "$BASE_REF" >/dev/null 2>&1; then
36
+ # An override that does not resolve is a caller error, not a reason to
37
+ # silently review against the wrong base.
38
+ printf 'ERROR: LEKKER_BASE_REF does not resolve: %s\n' "$BASE_REF" >&2
39
+ exit 1
40
+ fi
29
41
 
30
42
  SYM_REF="$(git -C "$WORKTREE_PATH" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)"
31
- if [[ -n "$SYM_REF" ]]; then
43
+ if [[ -z "$BASE_REF" ]] && [[ -n "$SYM_REF" ]]; then
32
44
  candidate="origin/${SYM_REF#refs/remotes/origin/}"
33
45
  if git -C "$WORKTREE_PATH" rev-parse --verify --quiet "$candidate" >/dev/null 2>&1; then
34
46
  BASE_REF="$candidate"
@@ -54,11 +66,12 @@ fi
54
66
  # ---------------------------------------------------------------------------
55
67
  MERGE_BASE="$(git -C "$WORKTREE_PATH" merge-base HEAD "$BASE_REF" 2>/dev/null || true)"
56
68
  if [[ -z "$MERGE_BASE" ]]; then
57
- printf 'base-ref: unknown\n' >&2
58
- exit 0
69
+ printf 'ERROR: no merge base between HEAD and %s\n' "$BASE_REF" >&2
70
+ exit 1
59
71
  fi
60
72
 
61
73
  printf 'base-ref: %s\n' "$BASE_REF" >&2
74
+ printf 'merge-base: %s\n' "$MERGE_BASE" >&2
62
75
 
63
76
  CHANGED_TMP="/tmp/lekker-changed-files-tmp-$$.txt"
64
77
  git -C "$WORKTREE_PATH" diff --name-only "$MERGE_BASE" HEAD > "$CHANGED_TMP" 2>/dev/null || true