@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.
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +5 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +13 -5
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +260 -28
- package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +14 -6
- package/scripts/two-channel-couplings.json +1 -1
package/package.json
CHANGED
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
"zod-validation-error": "^4.0.0"
|
|
167
167
|
},
|
|
168
168
|
"name": "@codyswann/lisa",
|
|
169
|
-
"version": "4.34.
|
|
169
|
+
"version": "4.34.7",
|
|
170
170
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
171
171
|
"main": "dist/index.js",
|
|
172
172
|
"exports": {
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
"test": "tests"
|
|
315
315
|
},
|
|
316
316
|
"types": "./dist/index.d.ts",
|
|
317
|
-
"lisaReleaseCommit": "
|
|
318
|
-
"gitHead": "
|
|
319
|
-
"lisaReleaseTag": "v4.34.
|
|
317
|
+
"lisaReleaseCommit": "f8ef00f1ee6983cc64b50c18461d52e70f5ae887",
|
|
318
|
+
"gitHead": "f8ef00f1ee6983cc64b50c18461d52e70f5ae887",
|
|
319
|
+
"lisaReleaseTag": "v4.34.7"
|
|
320
320
|
}
|
|
@@ -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`
|
|
33
|
-
|
|
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`
|
|
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
|
|
180
|
-
terminal obligation on EVERY exit — including give-up,
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
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
|
|
429
|
-
sole gate still blocking the merge, and only after the substitute
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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.
|
|
491
|
-
|
|
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
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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.
|
|
@@ -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).
|
|
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. **
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|