@codyswann/lisa 4.34.6 → 4.34.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  3. package/dist/core/upstream-evidence-manifest.js +5 -3
  4. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  5. package/package.json +4 -4
  6. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  9. package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +13 -5
  10. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  11. package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +14 -6
  12. package/plugins/lisa-agy/plugin.json +1 -1
  13. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  14. package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +14 -6
  15. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  18. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  22. package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +14 -6
  23. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  25. package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +14 -6
  26. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-agy/plugin.json +1 -1
  29. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  49. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-agy/plugin.json +1 -1
  54. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  59. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  64. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
  67. package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +14 -6
  68. package/scripts/two-channel-couplings.json +1 -1
@@ -29,8 +29,11 @@ merges" loop. Other skills delegate here instead of re-implementing it. Runs
29
29
  - `verify_commit=<sha>` — the commit that MUST end up in the merged base (for the
30
30
  ancestry check). Default: the PR head at the time this skill starts.
31
31
  - `auto_merge=<true|false>` — whether this skill is allowed to merge the PR at
32
- all. Default `true` (existing behavior, byte-identical for every current
33
- caller). With `auto_merge=false` the PR is deliberately left for a human:
32
+ all. Default `true`. `true` is permission to merge, **not** an instruction to
33
+ arm the latch immediately: arming is gated on the review context having done
34
+ work (section 1's arm gate), and under a vacuous context the merge is made
35
+ here, in the open, or not at all. With `auto_merge=false` the PR is
36
+ deliberately left for a human:
34
37
  skip the **entire** "## 1. Enable auto-merge" step — including its
35
38
  direct-merge capability fallback — and never run any `gh pr merge` variant.
36
39
  Still drive every blocker per `on_blocker` (green checks, resolved reviews,
@@ -44,12 +47,13 @@ merges" loop. Other skills delegate here instead of re-implementing it. Runs
44
47
  resolve review comments, dismiss stale review gates — drive until merged.
45
48
  - **`report`** (diagnose & mechanically nudge only): perform just the safe,
46
49
  idempotent, non-destructive actions — ensure auto-merge is enabled (when
47
- `auto_merge=true`) and, if the
50
+ `auto_merge=true` **and the arm gate cleared**; arming against a review that
51
+ did no work is authorising a merge, which is not diagnosis) and, if the
48
52
  PR is `BEHIND` but otherwise clean, run `gh pr update-branch` only when the
49
53
  base branch requires strict up-to-date checks. For **anything** that would
50
54
  require editing code, resolving threads, or dismissing a review, **do not
51
55
  act** — stop and return a structured blocker classification
52
- (`merged` / `will-merge-after-resync` / `blocked:<conflict|checks|changes_requested|deploy|pending-auto-fix>`)
56
+ (`merged` / `will-merge-after-resync` / `blocked:<conflict|checks|changes_requested|deploy|pending-auto-fix|unreviewed>`)
53
57
  so the caller applies its own policy. This is the mode `repair-intake` and the
54
58
  build-intake skills use to diagnose-and-route without fixing in place.
55
59
 
@@ -120,6 +124,157 @@ If the disarm fails or the re-read still shows an armed `autoMergeRequest`,
120
124
  PR could merge without a human. Once disarmed (or already unarmed), proceed
121
125
  straight to the watch loop (section 2).
122
126
 
127
+ ### The arm gate — never arm against a review that did no work
128
+
129
+ **This gate governs the `auto_merge=true` path, and it decides WHETHER to arm.
130
+ It does not touch the rule below that a latch, once armed, stays armed.** Those
131
+ are two different questions with two different answers: arming prematurely is
132
+ what this gate prevents; turning off a live latch is what the rest of section
133
+ 1 forbids.
134
+
135
+ **Scope: repositories that declare a review check.** The defect is a required
136
+ review context reporting satisfied having read nothing, so where no such
137
+ context exists there is no false green to withhold the latch from, and arming is
138
+ unchanged. A repository with no `evidence_bearing_checks` in
139
+ `.github/required-checks.json` — or none at all — passes this gate immediately.
140
+ Holding those PRs would redden a whole fleet for a gate none of them asked for,
141
+ which is how a guard gets deleted rather than adopted.
142
+
143
+ Arming is a decision to merge, made in advance and executed by GitHub without
144
+ you. Everything this skill knows about the review — step (d)'s `reviewed` /
145
+ `NOT REVIEWED` verdict, the five sole-gate conditions, the mandatory
146
+ `MERGED — NOT REVIEWED` report line — is evaluated in the watch loop, which runs
147
+ AFTER the latch is on. So an armed latch never merges on that reasoning. It
148
+ merges on green checks, and whatever the loop would have concluded is simply
149
+ never consulted.
150
+
151
+ Measured (#3439): `auto_merge` defaults to `true` and `lisa-git-submit-pr` armed
152
+ the latch at submit time, before any review existed. Step (d)'s exception —
153
+ five live-verified conditions and a mandatory report line — could not fire on
154
+ any pull request, because GitHub had already merged it the moment CI went green.
155
+ A guard that cannot be reached is not a guard: the merge happened on the latch,
156
+ not on the reasoning.
157
+
158
+ **So the latch may only ever be armed against a review context that did work.**
159
+ The consequence is the whole point. An unreviewed merge then has exactly one
160
+ route left — step (d), which verifies its five conditions against a live poll
161
+ and is *required* to write `MERGED — NOT REVIEWED`. This does not forbid the
162
+ unreviewed merge; the owner's ruling on #3221 is explicit that a vendor
163
+ entitlement state must not redden every pull request. It makes the unreviewed
164
+ merge VISIBLE, by leaving it only one path that can produce it, and that path
165
+ reports itself.
166
+
167
+ #### Classify before arming
168
+
169
+ **Vacuity is the prover's answer, not a string you match here.** The repository
170
+ already ships the detector; consult it on the merge path instead of re-deriving
171
+ its vocabulary, which is how the two drift apart:
172
+
173
+ ```bash
174
+ node scripts/check-skipped-required-checks.mjs --vacuity --pr=<pr> --json
175
+ ```
176
+
177
+ When `scripts/` carries no installed copy, try the in-repo template at
178
+ `typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs` — the same
179
+ second address `review-evidence.yml` resolves. `--vacuity` is the mode that WAITS
180
+ for the declared checks to settle, which is exactly what "before arming" needs:
181
+ a pull request carries several intermediate statuses on one head SHA, and
182
+ judging the first one seen would classify every PR as vacuous.
183
+
184
+ Read `violations[].kind` and `inspected`, **never the exit code** — a named
185
+ entitlement waiver is report-only under every flag by the #3221 ruling, so a
186
+ zero exit is not evidence that a review happened:
187
+
188
+ | reading | what it means | arm? |
189
+ | --- | --- | --- |
190
+ | `inspected: true`, no review-related violation | the check did work | **arm** |
191
+ | `review_evidence_unsatisfied` | a review RAN AND OBJECTED | no — a real blocker; steps (d)/(e) |
192
+ | `review_evidence_waived` | a named vendor entitlement | no — vacuous |
193
+ | `vacuous_required_check` | success carrying a no-work description | no — vacuous |
194
+ | `unproven_required_check` | a description proving nothing either way, or no status at all | no — vacuous |
195
+ | `inspected: false` with `vacuity_none_declared` | this repository declares no review check | **arm** — out of scope, see above |
196
+ | `inspected: false`, any other refusal | something was supposed to be read and was not | no — an empty inspection is not a pass |
197
+
198
+ The last two rows are the same field and opposite answers, so read the refusal
199
+ kind rather than the `inspected` flag. `vacuity_none_declared` means there is
200
+ nothing to inspect; `vacuity_pr_unresolved`, `vacuity_checks_unreadable` and
201
+ `vacuity_no_checks_reported` mean there is, and it was not inspected — most
202
+ often a missing `actions: read`, which makes `gh` exit non-zero with empty
203
+ stdout and never says the word "permission". Collapsing those into one answer is
204
+ this file's own thesis about false greens, aimed at itself.
205
+
206
+ If the prover cannot be run at all, fall back to reading the check yourself and
207
+ require a POSITIVE reading before arming:
208
+
209
+ ```bash
210
+ gh pr checks <pr> --json name,state,description,bucket \
211
+ --jq '.[] | select(.name == "<review-context>")'
212
+ ```
213
+
214
+ `<review-context>` is whatever `.github/required-checks.json` declares under
215
+ `evidence_bearing_checks` — do not assume `CodeRabbit`, and if nothing is
216
+ declared, arm normally (the scope rule above). Where one IS declared, absent a
217
+ description that positively states a review ran, treat the context as vacuous.
218
+ Fail closed: absence of evidence is not evidence of a review.
219
+
220
+ #### Vacuous and transient are different properties
221
+
222
+ A vacuous context is not automatically something to wait out, and this is the
223
+ distinction a naive wait-and-arm gets wrong — it waits forever on a repository
224
+ where the state never clears. **Only the rate limit has both properties.**
225
+
226
+ | description | vacuous? | clears on its own? |
227
+ | --- | --- | --- |
228
+ | `Review rate limited` | yes | **yes** — a throughput window |
229
+ | `Review skipped: manual review required for this OSS repository` | yes | **no** — a standing vendor policy for public repositories |
230
+ | `Review skipped: N files exceed the limit of M` | yes | **no** — not without splitting the pull request |
231
+ | `Review skipped` (bare, no stated reason) | yes | **unknown — treat as standing** |
232
+
233
+ A bare skip carries no machine-readable reason, so nothing distinguishes it from
234
+ the standing forms. Requiring a stated reason before treating a skip as vacuous
235
+ is how the commonest form gets missed.
236
+
237
+ - **Transient (the rate limit, and only the rate limit)** — do not arm yet. Wait
238
+ the window out and re-request the review **at most once**, then re-classify.
239
+ If it now proves work, arm. If it does not, stop and treat it as standing.
240
+ **Never loop**: a retry loop re-triggers the very limit it is escaping, and
241
+ measured on #3220 a single explicit re-request in this repository was
242
+ acknowledged, went `Review in progress`, and settled back at
243
+ `Review rate limited` two seconds later having reviewed nothing — minting a
244
+ second hollow green. One attempt, then report.
245
+ - **Standing** — do not wait at all, and do not re-request. There is no window
246
+ to wait out, so waiting produces a stall that reads as progress. Go straight
247
+ to the watch loop and let step (d) adjudicate the merge in the open.
248
+
249
+ #### A latch somebody else armed
250
+
251
+ Not arming is not enough. `lisa-git-submit-pr`, an older Lisa version, or a
252
+ previous session may have left the latch on, and an armed latch merges the
253
+ instant checks go green no matter what this gate concluded. So when the gate
254
+ says do not arm, turn any existing latch off and prove it took — the same
255
+ mechanism and the same fail-closed re-read the `auto_merge=false` contract above
256
+ uses:
257
+
258
+ ```bash
259
+ armed=$(gh pr view <pr> --json autoMergeRequest -q .autoMergeRequest)
260
+ if [ "$armed" != "null" ] && [ -n "$armed" ]; then
261
+ gh pr merge <pr> --disable-auto
262
+ fi
263
+ gh pr view <pr> --json autoMergeRequest -q .autoMergeRequest # must print null
264
+ ```
265
+
266
+ If the re-read still shows an armed `autoMergeRequest`, **fail closed**: report a
267
+ hard block (section 4) rather than proceeding into a state where the PR can
268
+ merge with neither a review nor step (d)'s recorded decision.
269
+
270
+ **What this costs, stated plainly.** While the review context is vacuous the PR
271
+ cannot merge unattended, so a run that ends early leaves it open. That is the
272
+ opposite trade from the armed-across-fix-pushes rule below, and it is deliberate:
273
+ there, the thing at risk was a PR sitting unmerged; here, the thing at risk is
274
+ code shipping that nothing read. The cost is bounded — the moment the context
275
+ proves work, the latch is armed and the unattended behaviour returns — and the
276
+ open PR is REPORTED (`blocked:unreviewed`, section 4), not silently abandoned.
277
+
123
278
  Before enabling auto-merge, capture the live PR head and compare it to
124
279
  `verify_commit`:
125
280
 
@@ -176,15 +331,23 @@ is what CATCHES a raced merge — it fails loudly when the fix SHA is not an
176
331
  ancestor of the base branch — so the rare miss is detected rather than silent.
177
332
 
178
333
  **Invariant:** never terminate having left auto-merge OFF on an open PR when
179
- `auto_merge=true`. If some future path does disable it, restoring it is a
180
- terminal obligation on EVERY exit — including give-up, budget-exhausted and error
181
- paths — not a later step in a sequence.
334
+ `auto_merge=true` **and the arm gate cleared**. If some future path does turn it
335
+ off, restoring it is a terminal obligation on EVERY exit — including give-up,
336
+ budget-exhausted and error paths — not a later step in a sequence. A PR the arm
337
+ gate held unarmed is outside this invariant: holding it is the gate's purpose,
338
+ and `blocked:unreviewed` (section 4) is how that exit is reported rather than
339
+ passed off as success.
182
340
 
183
341
  - **Capability fallback** (`auto_merge=true` only): if the repo disallows
184
- auto-merge, do not fail. Keep watching; once checks are green, the review gate
185
- is clear, and `mergeable == MERGEABLE`, run `gh pr merge <pr> --<merge_method>`
186
- directly. This fallback lives inside the gated section above — with
187
- `auto_merge=false` it never fires; the PR remains open awaiting a human.
342
+ auto-merge, do not fail. Keep watching; once checks are green, the arm gate
343
+ clears — a review context that proved work, not merely a green one — and
344
+ `mergeable == MERGEABLE`, run `gh pr merge <pr> --<merge_method>` directly.
345
+ A green review context that did no work does NOT clear the gate here either:
346
+ on a repo without auto-merge this fallback is the merge, so exempting it would
347
+ reinstate the whole defect one layer down. Under a vacuous context the merge
348
+ belongs to step (d), with its conditions and its report line, or to
349
+ `blocked:unreviewed`. This fallback lives inside the gated section above —
350
+ with `auto_merge=false` it never fires; the PR remains open awaiting a human.
188
351
 
189
352
  ## 2. The watch loop
190
353
 
@@ -224,11 +387,17 @@ review gate, and `mergeable == MERGEABLE`. Never enable auto-merge or merge
224
387
  directly in this mode.
225
388
 
226
389
  In **`on_blocker=report`** mode, only the mechanical step (a) and auto-merge enabling
227
- (when `auto_merge=true`) apply; for any of (b)–(f) do not act — classify the blocker
390
+ (when `auto_merge=true` **and the arm gate cleared**) apply; for any of (b)–(f) do
391
+ not act — classify the blocker
228
392
  and return per the input contract above. That includes (f): adjudicating a pending
229
393
  auto-fix PR (merging, closing, or deleting its branch) is destructive work, not
230
394
  diagnosis — return its classification (`blocked:pending-auto-fix`) instead.
231
395
 
396
+ The arm gate binds in report mode too, and this is the mode where forgetting it
397
+ does the most damage: a diagnose-only run that arms the latch has not diagnosed
398
+ anything, it has authorised a merge. Under a vacuous review context, report mode
399
+ neither arms nor takes step (d)'s exception — it returns `blocked:unreviewed`.
400
+
232
401
  ### a. Branch behind base (`mergeStateStatus == BEHIND`)
233
402
  Before proactively syncing a clean `BEHIND` PR, check whether the base branch
234
403
  actually requires up-to-date branches:
@@ -425,14 +594,37 @@ throttle, which is a billing matter no amount of driving will clear. It is a
425
594
  **substitution** obligation followed by a **reporting** obligation: something
426
595
  read the diff, and the report says what did.
427
596
 
428
- **Merging past a hollow review is permitted — but ONLY when that review is the
429
- sole gate still blocking the merge, and only after the substitute is posted.**
430
- Prove the hollow signal from the commit status API as above; a
431
- merely pending or queued check is not proof of anything and must keep polling. Once that signal is explicit,
432
- and with `auto_merge=true`, the PR already has auto-merge enabled (section 1), so
433
- leave the latch armed and merge directly with `gh pr merge <pr> --<merge_method>`
434
- (pass `--admin` only if branch protection lists the hollow context as
435
- required and refuses the plain merge). "Sole gate" means every one of these is
597
+ **Merging past a vacuous review context is permitted — but ONLY when that
598
+ context is the sole gate still blocking the merge, and only after the substitute
599
+ is posted.** This is the *only* route by
600
+ which a PR merges unreviewed: the arm gate (section 1) holds the latch off while
601
+ the context is vacuous precisely so that this decision, with its conditions and
602
+ its mandatory report line, is the one that merges. If you find yourself merging
603
+ unreviewed anywhere else, that is the defect, not a shortcut.
604
+
605
+ **Every vacuity mode qualifies, not just the rate limit.** A size-limit skip, the
606
+ public-repository policy skip, and a bare `Review skipped` are the same vacuity
607
+ wearing different strings; a rule keyed on `Review rate limited` alone would
608
+ pass a repository's rate-limited merges while leaving every other mode
609
+ unconsidered, and would look complete. Use the arm gate's classification — the
610
+ prover's `violations[].kind` — rather than matching one vendor string here. That
611
+ classification and the allowlist above answer the same question from two
612
+ directions and must agree: the prover says whether the context did work, and the
613
+ configured reviewed-when phrase says what "did work" looks like for this
614
+ project.
615
+
616
+ Prove the signal from the commit status API as above; a merely pending or queued
617
+ check is not proof of anything and must keep polling. **A transient vacuity is not adjudicated here until the arm gate's
618
+ single wait-and-re-request has been spent** (section 1): a rate limit has a
619
+ window, and merging past one you never waited out is a choice you did not have
620
+ to make. A standing vacuity has no window, so it arrives here immediately.
621
+
622
+ Once the signal is explicit, merge directly with `gh pr merge <pr>
623
+ --<merge_method>` (pass `--admin` only if branch protection lists the vacuous
624
+ context as required and refuses the plain merge).
625
+ **Do not arm the latch to accomplish this** — arming would hand the decision
626
+ back to GitHub and lose the report, and under the arm gate (section 1) the
627
+ latch is deliberately not armed in the first place. "Sole gate" means every one of these is
436
628
  already true at the moment you merge — verify each against the live poll, never
437
629
  from memory:
438
630
 
@@ -484,11 +676,32 @@ mode. Record the result as `MERGED — NOT REVIEWED (<context> "<observed
484
676
  description>"), local adversarial review substituted and posted` in the terminal
485
677
  report (section 4).
486
678
 
679
+ When a condition does not hold and cannot be cleared, the terminal state is
680
+ `blocked:unreviewed` (section 4), not a silent stop and never an arm-and-hope.
681
+
487
682
  ### e. Review gate stall (`reviewDecision == CHANGES_REQUESTED`)
488
683
  After the requested changes are addressed and threads resolved, the prior
489
684
  `CHANGES_REQUESTED` review still blocks — a later `COMMENTED` review does not clear
490
- it. Dismiss the stale (often bot) review where repo policy permits, else re-request
491
- review:
685
+ it.
686
+
687
+ **The discriminator between a stranded verdict and a live objection is the
688
+ unresolved thread count, not `reviewDecision`.** The same vendor limit produces
689
+ opposite failures, and the two signals diverge in BOTH directions: a green
690
+ review context can sit on a review that read nothing (step d), and a
691
+ `CHANGES_REQUESTED` can outlive every thread that justified it, because the
692
+ verdict does not self-dismiss when a later head fixes the code. Measured: one
693
+ pull request was blocked by two genuine unresolved threads, and two hours later
694
+ still reported `CHANGES_REQUESTED` with ZERO unresolved. Neither signal is a
695
+ proxy for the other, so read them separately:
696
+
697
+ - unresolved threads > 0 → a live objection. Work it through the (d) cycle
698
+ first; the verdict is correct and dismissing it would be the bypass.
699
+ - unresolved threads == 0 and `CHANGES_REQUESTED` at an earlier head → a
700
+ stranded verdict. It is safe to dismiss, and leaving it is what makes a PR sit
701
+ wedged looking abandoned.
702
+
703
+ Never dismiss on `reviewDecision` alone. Dismiss the stale (often bot) review
704
+ where repo policy permits, else re-request review:
492
705
  ```bash
493
706
  gh api -X PUT repos/<owner>/<repo>/pulls/<pr>/reviews/<review_id>/dismissals \
494
707
  -f message="Addressed; threads resolved." -f event=DISMISS
@@ -714,14 +927,25 @@ Loop until one of:
714
927
  (`gh pr view <pr> --json autoMergeRequest` shows `null`). Report the PR URL
715
928
  and state — a human decides whether it merges. This is the intended outcome
716
929
  of auto-merge-off mode, not a stall; do not keep looping for `MERGED`.
930
+ - **`blocked:unreviewed`** — the PR is green and mergeable, but the review
931
+ context did no work and step (d)'s sole-gate conditions were not all met (or
932
+ the mode forbids acting on them: `on_blocker=report`). The latch is
933
+ deliberately not armed, so the PR sits OPEN rather than merging on a review
934
+ nothing performed. Report the PR URL, the review context's settled
935
+ description, whether it is transient or standing, and which sole-gate
936
+ condition failed. This is an outcome that needs an operator, stated in one
937
+ line a non-technical reader can act on — for example: *"PR #123 is ready to
938
+ merge but nobody reviewed it: the review tool says it skips public
939
+ repositories, and that will not change on its own. Merge it yourself or ask
940
+ for a human review."*
717
941
  - **`CLOSED`** → report (PR was closed without merge).
718
942
  - **Hard block needing a human**: an unresolvable conflict, a failing check that
719
943
  needs design input, or genuine unresolved human objection (not a bot gate). Stop
720
944
  and report exactly what is blocking and what was already tried — never force the
721
945
  merge or weaken a gate to get past it. The one sanctioned exception is a
722
- hollow third-party review that is the *sole* remaining gate on an auto-merge
723
- enabled PR, and only once the adversarial-review substitute is posted (step
724
- d); it never extends to any other gate.
946
+ hollow third-party review that is the *sole* remaining gate, and only once the
947
+ adversarial-review substitute is posted (step d); it never extends to any
948
+ other gate.
725
949
 
726
950
  At every terminal state, release the babysitter lease
727
951
  (`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
@@ -738,10 +962,18 @@ So state the verdict alongside the outcome:
738
962
  - `MERGED — NOT REVIEWED (<context> "Review rate limited"), local adversarial review substituted and posted`
739
963
  - `MERGED — NOT REVIEWED (<context> "<unrecognised description>"), local adversarial review substituted and posted`
740
964
  - `MERGED — no third-party reviewer configured; local review not substituted for one that was never declared`
965
+ - `NOT MERGED — NOT REVIEWED: blocked:unreviewed, auto-merge deliberately not armed`
741
966
 
742
967
  Spell the reviewer as the configured context, not a vendor name: a project may
743
968
  declare none, one, or several, and each shows its own evidence.
744
969
 
745
- This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
746
- turn a merged PR into a blocked one, and it must never be used to withhold a
747
- merge — it changes what the record says, not what the loop does.
970
+ Quote the description that was actually read. A report that says "rate limited"
971
+ on a policy skip is a wrong record, and the two call for different operator
972
+ actions: one clears itself, the other never will.
973
+
974
+ `NOT REVIEWED` is reporting, and on a merged PR it is never a terminal state of
975
+ its own: it does not turn a merged PR into a blocked one. What it does gate is
976
+ ARMING — section 1 will not hand GitHub a merge decision on a review that did no
977
+ work, because an armed latch merges without ever writing this line. The
978
+ distinction is exact: `NOT REVIEWED` never withholds a merge this skill decides
979
+ to make, and it always withholds the merge this skill would have delegated.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-git-submit-pr
3
- description: This skill should be used when pushing changes and creating or updating a pull request. It verifies the branch state, pushes to remote, creates or updates a PR with a comprehensive description, optionally coordinates the resulting Pull Request into the configured GitHub ProjectV2, and enables auto-merge.
3
+ description: This skill should be used when pushing changes and creating or updating a pull request. It verifies the branch state, pushes to remote, creates or updates a PR with a comprehensive description, optionally coordinates the resulting Pull Request into the configured GitHub ProjectV2, and hands the PR to drive-pr-to-merge, which decides whether auto-merge may be armed. It never arms auto-merge itself.
4
4
  allowed-tools: ["Bash", "Skill", "mcp__github__create_pull_request", "mcp__github__get_pull_request", "mcp__github__update_pull_request"]
5
5
  ---
6
6
 
@@ -14,7 +14,7 @@ Recognized optional hints:
14
14
  - `target_branch=<branch>` or `base=<branch>` — intended PR base branch.
15
15
  - `tracker_provider=<github|linear|jira|none>` — explicit provider when the ref shape is ambiguous.
16
16
  - `pr_url=<url>` — live pull request URL, only needed when updating tracker backlinks from an existing PR context.
17
- - `auto_merge=<true|false>` — whether the PR should merge automatically. Default `true` (existing behavior for every current caller). With `auto_merge=false`, skip step 5 entirely (never run `gh pr merge --auto`) and pass `auto_merge=false` through to the `drive-pr-to-merge` delegation in step 6 so the PR is driven to a clean, green, OPEN state and then left awaiting a human.
17
+ - `auto_merge=<true|false>` — whether the PR should merge automatically. Default `true` (existing behavior for every current caller). This skill **never arms the latch itself** in either mode; it passes the value through to the `drive-pr-to-merge` delegation in step 6, which owns the arm decision. With `auto_merge=false` the PR is driven to a clean, green, OPEN state and then left awaiting a human.
18
18
 
19
19
  ## Workflow
20
20
 
@@ -35,10 +35,18 @@ Recognized optional hints:
35
35
  - Include native development linkage for the source work item when `work_item_ref` can be inferred from `$ARGUMENTS`, the current branch name, an existing PR body, or the issue/ticket context passed by the caller.
36
36
  - After the PR exists, ensure the source work item has a backlink to the PR: invoke `lisa-tracker-sync` with the work item, milestone `pr-ready`, the live `pr_url`, and `tracker_provider` when known. This makes ticket -> PR linkage mandatory, not just a best-effort milestone comment.
37
37
  - After the PR exists, re-resolve the live Pull Request node id and, when `github.projects.v2` is enabled, invoke `lisa-github-project-v2` with `operation: ensure-item` and `content_node_id: <pull-request-node-id>` so linked pull requests join the configured shared Project without replacing the PR as the durable review/merge surface.
38
- 5. **Auto-merge** (only when `auto_merge=true`, the default — with `auto_merge=false` skip this step entirely): Choose merge strategy by PR type:
39
- - **Promotion PRs** (env → env, e.g. `dev` → `staging`): use `gh pr merge --auto --merge` (never squash). Squashing flattens the constituent `chore(release): X.Y.Z [skip ci]` commits into one commit titled with the PR title, stripping the `[skip ci]` markers and breaking the release workflow's promotion-detection regex — the destination branch then double-bumps its version. `--merge` keeps each `chore(release)` commit (and its `[skip ci]` marker) intact under a clean merge commit subject the workflow can recognize.
40
- - **Feature PRs** (anything → `dev`): use `gh pr merge --auto --merge`.
41
- 6. **Drive to merge**: Opening the PR and enabling auto-merge is not terminal. Delegate the full mergeability loop to the `drive-pr-to-merge` skill — invoke it with the PR number and `merge_method=merge` (and `verify_commit=<pushed head sha>` for the ancestry check). When the caller passed `auto_merge=false`, also pass `auto_merge=false` so the delegated loop drives the PR to green-and-open (`awaiting-human`) instead of merged — never merging it, even on repos that disallow auto-merge. That skill is the single source of truth for clearing every blocker: auto-merge with direct-merge fallback, `BEHIND` re-sync, conflict resolution, failing-check fixes, human + bot (CodeRabbit) review-comment handling with GraphQL thread resolution, stale `CHANGES_REQUESTED` dismissal, and post-merge ancestry verification. It runs inline and uses plain `gh`/`git` so Claude and Codex behave identically. Do not re-implement the loop here.
38
+ 5. **Do not arm auto-merge here.** Never run `gh pr merge --auto` in this skill, in either `auto_merge` mode. Arming is delegated to `drive-pr-to-merge` (step 6), which is the only place that first checks whether the review context did any work.
39
+
40
+ **Why this step is a prohibition rather than a command.** It used to arm the latch unconditionally when `auto_merge=true`. At submit time no review exists yet — that is what "submit" means — so the latch went on before anything could have read the code, and GitHub then merged the PR the moment CI went green. `drive-pr-to-merge` carries a careful exception for merging past a review check that reported success without reviewing anything: five conditions verified against a live poll, and a mandatory `MERGED — NOT REVIEWED` line in the report. **That exception could never fire**, because the merge had already been authorised here (#3439). The merge happened on the latch, not on the reasoning.
41
+
42
+ Removing the arm from this step does not lose auto-merge: step 6 always delegates to `drive-pr-to-merge`, which arms as soon as the review context proves work, and adjudicates in the open when it does not. What is lost is the ability to authorise a merge before anything could have objected.
43
+
44
+ The merge strategy is still decided here and passed through, because it is a property of the PR rather than of the review:
45
+ - **Promotion PRs** (env → env, e.g. `dev` → `staging`): `merge_method=merge` (never squash). Squashing flattens the constituent `chore(release): X.Y.Z [skip ci]` commits into one commit titled with the PR title, stripping the `[skip ci]` markers and breaking the release workflow's promotion-detection regex — the destination branch then double-bumps its version. `--merge` keeps each `chore(release)` commit (and its `[skip ci]` marker) intact under a clean merge commit subject the workflow can recognize.
46
+ - **Feature PRs** (anything → `dev`): `merge_method=merge`.
47
+ 6. **Drive to merge**: Opening the PR is not terminal. Delegate the full mergeability loop to the `drive-pr-to-merge` skill — invoke it with the PR number and `merge_method=merge` (and `verify_commit=<pushed head sha>` for the ancestry check). When the caller passed `auto_merge=false`, also pass `auto_merge=false` so the delegated loop drives the PR to green-and-open (`awaiting-human`) instead of merged — never merging it, even on repos that disallow auto-merge. That skill is the single source of truth for clearing every blocker: the arm gate and auto-merge with direct-merge fallback, `BEHIND` re-sync, conflict resolution, failing-check fixes, human + bot (CodeRabbit) review-comment handling with GraphQL thread resolution, stale `CHANGES_REQUESTED` dismissal, and post-merge ancestry verification. It runs inline and uses plain `gh`/`git` so Claude and Codex behave identically. Do not re-implement the loop here.
48
+
49
+ This delegation is now load-bearing rather than a convenience: it is the only path that arms auto-merge, so skipping it leaves the PR open rather than quietly merging it. Reporting submission as complete without invoking it is a failed submit, not a partial one — say so instead of implying the PR is on its way to merged.
42
50
 
43
51
  ### Native Development Linkage
44
52
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.34.6",
3
+ "version": "4.34.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"