@codyswann/lisa 4.29.10 → 4.29.12

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 (77) hide show
  1. package/all/copy-overwrite/scripts/check-third-party-review-evidence.mjs +706 -0
  2. package/all/copy-overwrite/scripts/lisa-gates.mjs +55 -1
  3. package/cdk/create-only/.github/workflows/ci.yml +1 -1
  4. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  5. package/dist/core/lisa-owned-hash-ledger.js +10 -0
  6. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  7. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  8. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  9. package/dist/core/upstream-evidence-manifest.js +20 -11
  10. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  11. package/expo/create-only/.github/workflows/deploy.yml +4 -4
  12. package/harper-fabric/create-only/.github/workflows/deploy.yml +1 -1
  13. package/harper-fabric/create-only/.github/workflows/zap-baseline.yml +1 -1
  14. package/nestjs/create-only/.github/workflows/deploy.yml +9 -9
  15. package/npm-package/create-only/.github/workflows/publish-to-npm.yml +1 -1
  16. package/package.json +5 -4
  17. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  20. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  21. package/plugins/lisa-agy/plugin.json +1 -1
  22. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  23. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  26. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  30. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  32. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-agy/plugin.json +1 -1
  35. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  55. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-agy/plugin.json +1 -1
  60. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  65. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  70. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +138 -37
  73. package/rails/create-only/.github/workflows/deploy.yml +3 -3
  74. package/scripts/check-third-party-action-pins.mjs +455 -0
  75. package/scripts/two-channel-couplings.json +1 -1
  76. package/typescript/copy-overwrite/.prettierignore +1 -0
  77. package/typescript/create-only/.github/workflows/third-party-review-evidence.yml +114 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.29.10",
3
+ "version": "4.29.12",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -320,51 +320,123 @@ shipped-verification checks what you pushed. When the root cause is an upstream
320
320
  rather than this project's code, fix it upstream and propagate down rather than
321
321
  patching only here.
322
322
 
323
- ### d. Review comments — human and bot (CodeRabbit, etc.)
323
+ ### d. Review comments — human and bot
324
324
  Delegate to the `pull-request-review` skill with the PR number. It owns the whole
325
325
  comment cycle: fetch every unresolved human + bot thread (with resolution state via
326
326
  GraphQL), implement valid feedback (commit + push), reply to invalid feedback, and
327
327
  resolve every thread via `resolveReviewThread` so the branch-protection
328
328
  thread-resolution gate clears.
329
329
 
330
- **A green review check is not proof a review happened.** That skill's Step 1b
331
- returns a `reviewed` / `NOT REVIEWED` verdict — record it, and repeat it in this
332
- skill's final report. Measured (CodySwannGT/lisa#2497): `CodeRabbit` was a
333
- *required* context and posted `success — "Review rate limited"` on #2483 and
334
- #2484, so branch protection recorded a satisfied review gate for two
335
- security-relevant PRs nothing had read; both merged and shipped in `v3.5.1`.
330
+ **A green review check is not proof a review happened.** A third-party review
331
+ app posts a **commit status** on the PR head; when it is throttled or declines,
332
+ it still reports `success` and never blocks. Only the status **description**
333
+ tells the two apart. Measured over the last 30 merged PRs in one repository: 24
334
+ of 30 merges passed a required review gate that had reviewed nothing, every one
335
+ of them `success`. A second repository sampled `success` on 50 of 50.
336
336
 
337
- `NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
338
- it as a failing check, and do not try to force the bot to re-run. A hollow
339
- review check is usually an org-wide vendor spending cap, which is a billing
340
- matter no amount of driving will clear, and whether such a check belongs in the
341
- required set at all is an open owner decision. It is a **reporting** obligation:
342
- the PR merged unreviewed, and the report has to say so instead of implying a
343
- review it did not get. If that skill needs to push a commit, leave
344
- auto-merge armed (section 1); when it returns, re-read `headRefOid` and reset
345
- `verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here
346
- — it is the single source of truth for review-thread handling.
347
-
348
- **Merging past a rate-limited CodeRabbit is permitted — but ONLY when CodeRabbit
349
- is the sole gate still blocking the merge.** When the CodeRabbit context reports
350
- `Review rate limited` and it is the *only* thing standing between the PR and
351
- `MERGED`, do not wait it out. Prove that vendor-cap signal from the live check
352
- description; `statusCheckRollup` does not carry enough detail, so read it with:
337
+ **Read the description from the commit status API, never from the rollup.**
338
+ `gh pr view --json statusCheckRollup` returns a `StatusContext` with **no
339
+ `description` key at all** — verified — so a hollow green is invisible there:
340
+
341
+ ```bash
342
+ gh api repos/<owner>/<repo>/commits/<head-sha>/status \
343
+ --jq '.statuses[] | {context, state, description}'
344
+ ```
345
+
346
+ For a quick human look at one context, `gh pr checks <pr> --json name,state,description,bucket`
347
+ prints the same description — but the prover and CI read the commit-status API,
348
+ because `gh pr checks` resolves the rollup through the workflow run and exits
349
+ non-zero with EMPTY stdout when it lacks `actions: read`, which reads as a
350
+ content problem and never says "permission".
351
+
352
+ **Which reviewers to read is configuration, never a vendor name.** Each gate in
353
+ `.lisa.config.json` that declares `evidence.reviewer: true` on its
354
+ `pull-request` moment names one third-party reviewer: `await` is its status
355
+ context, and `evidence.proof` is the exact **reviewed-when** description. The
356
+ shipped prover resolves them and returns the whole verdict:
353
357
 
354
358
  ```bash
355
- gh pr checks <pr> --json name,state,description,bucket \
356
- --jq '.[] | select(.name == "CodeRabbit")'
359
+ node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
357
360
  ```
358
361
 
359
- A merely pending or queued CodeRabbit check is not proof of a vendor cap and
360
- must keep polling. Once that signal is explicit, and with `auto_merge=true`, the
361
- PR already has auto-merge enabled (section 1), so
362
+ Three cases, and each is handled differently:
363
+
364
+ 1. **`satisfied`** — every configured reviewer's description matched its
365
+ reviewed-when phrase. Nothing further; record `reviewed` and the phrase.
366
+ 2. **`no-reviewer-configured`** — no gate declares one. This is a legitimate
367
+ state, **not** a pass: say so explicitly in the report ("no third-party
368
+ reviewer configured; nothing read this diff on that path"), and do not
369
+ invent a substitute for a reviewer the project never asked for.
370
+ 3. **anything else** — throttled, skipped, absent, empty, or a description in
371
+ no configured list. **Treat every one of these as NOT REVIEWED.** The
372
+ allowlist is the only thing that grants credit; a denylist of known-bad
373
+ phrases fails open, and the vocabulary is open-ended — four distinct strings
374
+ are known already, from two repositories. An unrecognised string costs an
375
+ extra local review, never a free pass.
376
+
377
+ **In case 3, substitute — do not block, and do not wait.** Before merging, run
378
+ the local adversarial review over the PR diff and **post it on the PR**, so the
379
+ code that merges has been read by something. The comment must:
380
+
381
+ - carry the marker `<!-- lisa:review-substitute context="<context>" head="<head-sha>" -->`
382
+ as its first line, so CI can find it for this reviewer at this head;
383
+ - state plainly that it is a **self-review substitute**, not a third-party
384
+ review;
385
+ - **record the actual description string observed, verbatim**, so the trail says
386
+ *why* it was substituted rather than only that it was;
387
+ - **name the observed condition, not a guessed cause.** The prover returns a
388
+ `reading` per reviewer and a sentence for it; copy that sentence in. In
389
+ particular `absent` must read as *no third-party status was present at this
390
+ head at all* — an operator seeing an empty pair of quotes cannot tell that
391
+ apart from a status whose description was blank.
392
+
393
+ **Several different causes produce the same reading, and you cannot tell them
394
+ apart from here.** A quota throttle, a per-repository decline, and **auto-review
395
+ being disabled for pull requests whose base is not the default branch** all end
396
+ with no evidence at the head. The last one is worth knowing about because it is
397
+ **permanent** for the pull requests it affects rather than transient — a project
398
+ whose PRs target an integration branch would never be reviewed, and from outside
399
+ that looks identical to the healthy case. It gets **no special case**: the
400
+ allowlist already handles it, because it produces no declared proof phrase.
401
+ Report what was observed and let a human read the cause off it.
402
+
403
+ Re-read `headRefOid` first and post against the head that will actually merge —
404
+ review evidence decays on every push, and a substitute written for an earlier
405
+ head reviewed code that is no longer what merges. If the PR gains a commit
406
+ afterwards, substitute again for the new head.
407
+
408
+ **Never re-request a review to "refresh" it.** Re-requesting **overwrites** the
409
+ existing status rather than adding to it, so under a throttle it destroys a real
410
+ review, one-way. Do not post a review-request comment, and do not add a workflow
411
+ that does.
412
+
413
+ Whether the quota is scoped per repository, per organisation, or per account is
414
+ **unmeasured** — one lane reports a rolling window, another says explicitly that
415
+ it did not test it. Do not depend on a quota shape, and do not assert one.
416
+
417
+ If the review skill needs to push a commit, leave auto-merge armed (section 1);
418
+ when it returns, re-read `headRefOid` and
419
+ reset `verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here —
420
+ that skill is the single source of truth for review-thread handling.
421
+
422
+ `NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
423
+ it as a failing check. A hollow review check is usually a vendor entitlement or
424
+ throttle, which is a billing matter no amount of driving will clear. It is a
425
+ **substitution** obligation followed by a **reporting** obligation: something
426
+ read the diff, and the report says what did.
427
+
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
362
433
  leave the latch armed and merge directly with `gh pr merge <pr> --<merge_method>`
363
- (pass `--admin` only if branch protection lists the rate-limited context as
434
+ (pass `--admin` only if branch protection lists the hollow context as
364
435
  required and refuses the plain merge). "Sole gate" means every one of these is
365
436
  already true at the moment you merge — verify each against the live poll, never
366
437
  from memory:
367
438
 
439
+ - the adversarial-review substitute is posted for the current head;
368
440
  - every other required check in `statusCheckRollup` is green (no FAILURE, no
369
441
  other PENDING);
370
442
  - zero unresolved review threads (human or bot);
@@ -380,13 +452,37 @@ from memory:
380
452
  - `mergeable == MERGEABLE` and `mergeStateStatus` is not `BEHIND`/`DIRTY`;
381
453
  - no pending auto-fix PR into this branch (step f).
382
454
 
455
+ **A review OBJECT is not a status CONTEXT.** Everything above is about status
456
+ descriptions. An **empty-bodied `APPROVED` review is an ordinary approval** and
457
+ is never hollow — do not let empty-description reasoning leak onto review
458
+ objects. A `CHANGES_REQUESTED` is a blocking objection **whatever its body**,
459
+ because its content commonly lives entirely in inline threads.
460
+
461
+ **Filter review objects on `commit_id == head`, in BOTH directions.** Writing the
462
+ filter for one direction only is the easy bug, and each direction fails a
463
+ different way:
464
+
465
+ - a stale `APPROVED` from an older commit reads as fresh approval of code it
466
+ never saw;
467
+ - a stale `CHANGES_REQUESTED` reads as a current objection and blocks work a
468
+ newer head already fixed.
469
+
470
+ **And "no review object" is not "unreviewed".** These are different
471
+ propositions, and collapsing them is a measured incident rather than a
472
+ hypothetical: a lane counted the absence of a *third-party* review object across
473
+ 15 PRs, read it as "12 of 15 have no approving review", and froze merging
474
+ fleet-wide. A PR carrying a genuine human approval at head and no third-party
475
+ object **is reviewed**. Answer the two questions separately, from their two
476
+ separate data sources.
477
+
383
478
  If *anything* else is also blocking, the exception does not apply: clear that
384
479
  blocker through its own step first, re-poll, and only then re-evaluate whether
385
- CodeRabbit is the last gate standing. The exception never stacks with another
480
+ the review is the last gate standing. The exception never stacks with another
386
481
  bypass, never fires under `auto_merge=false` (that mode stops at
387
482
  `awaiting-human`, and a human decides), and never fires in `on_blocker=report`
388
- mode. Record the result as `MERGED — NOT REVIEWED: CodeRabbit rate limited
389
- (merged past as sole remaining gate)` in the terminal report (section 4).
483
+ mode. Record the result as `MERGED — NOT REVIEWED (<context> "<observed
484
+ description>"), local adversarial review substituted and posted` in the terminal
485
+ report (section 4).
390
486
 
391
487
  ### e. Review gate stall (`reviewDecision == CHANGES_REQUESTED`)
392
488
  After the requested changes are addressed and threads resolved, the prior
@@ -608,8 +704,9 @@ Loop until one of:
608
704
  needs design input, or genuine unresolved human objection (not a bot gate). Stop
609
705
  and report exactly what is blocking and what was already tried — never force the
610
706
  merge or weaken a gate to get past it. The one sanctioned exception is a
611
- rate-limited CodeRabbit that is the *sole* remaining gate on an auto-merge
612
- enabled PR (step d); it never extends to any other gate.
707
+ hollow third-party review that is the *sole* remaining gate on an auto-merge
708
+ enabled PR, and only once the adversarial-review substitute is posted (step
709
+ d); it never extends to any other gate.
613
710
 
614
711
  At every terminal state, release the babysitter lease
615
712
  (`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
@@ -622,9 +719,13 @@ successful `MERGED`. "Merged, all checks green" is exactly the sentence that hid
622
719
  anything. Green means *no gate objected*; it does not mean *something looked*.
623
720
  So state the verdict alongside the outcome:
624
721
 
625
- - `MERGED — reviewed (CodeRabbit "Review approved")`
626
- - `MERGED — NOT REVIEWED: CodeRabbit posted success but "Review rate limited"`
627
- - `MERGED — NOT REVIEWED: CodeRabbit rate limited (merged past as sole remaining gate)`
722
+ - `MERGED — reviewed (<context> "Review completed")`
723
+ - `MERGED — NOT REVIEWED (<context> "Review rate limited"), local adversarial review substituted and posted`
724
+ - `MERGED — NOT REVIEWED (<context> "<unrecognised description>"), local adversarial review substituted and posted`
725
+ - `MERGED — no third-party reviewer configured; local review not substituted for one that was never declared`
726
+
727
+ Spell the reviewer as the configured context, not a vendor name: a project may
728
+ declare none, one, or several, and each shows its own evidence.
628
729
 
629
730
  This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
630
731
  turn a merged PR into a blocked one, and it must never be used to withhold a
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.29.10",
3
+ "version": "4.29.12",
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.29.10",
3
+ "version": "4.29.12",
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.29.10",
3
+ "version": "4.29.12",
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.29.10",
3
+ "version": "4.29.12",
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.29.10",
3
+ "version": "4.29.12",
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.29.10",
3
+ "version": "4.29.12",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -320,51 +320,123 @@ shipped-verification checks what you pushed. When the root cause is an upstream
320
320
  rather than this project's code, fix it upstream and propagate down rather than
321
321
  patching only here.
322
322
 
323
- ### d. Review comments — human and bot (CodeRabbit, etc.)
323
+ ### d. Review comments — human and bot
324
324
  Delegate to the `pull-request-review` skill with the PR number. It owns the whole
325
325
  comment cycle: fetch every unresolved human + bot thread (with resolution state via
326
326
  GraphQL), implement valid feedback (commit + push), reply to invalid feedback, and
327
327
  resolve every thread via `resolveReviewThread` so the branch-protection
328
328
  thread-resolution gate clears.
329
329
 
330
- **A green review check is not proof a review happened.** That skill's Step 1b
331
- returns a `reviewed` / `NOT REVIEWED` verdict — record it, and repeat it in this
332
- skill's final report. Measured (CodySwannGT/lisa#2497): `CodeRabbit` was a
333
- *required* context and posted `success — "Review rate limited"` on #2483 and
334
- #2484, so branch protection recorded a satisfied review gate for two
335
- security-relevant PRs nothing had read; both merged and shipped in `v3.5.1`.
330
+ **A green review check is not proof a review happened.** A third-party review
331
+ app posts a **commit status** on the PR head; when it is throttled or declines,
332
+ it still reports `success` and never blocks. Only the status **description**
333
+ tells the two apart. Measured over the last 30 merged PRs in one repository: 24
334
+ of 30 merges passed a required review gate that had reviewed nothing, every one
335
+ of them `success`. A second repository sampled `success` on 50 of 50.
336
336
 
337
- `NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
338
- it as a failing check, and do not try to force the bot to re-run. A hollow
339
- review check is usually an org-wide vendor spending cap, which is a billing
340
- matter no amount of driving will clear, and whether such a check belongs in the
341
- required set at all is an open owner decision. It is a **reporting** obligation:
342
- the PR merged unreviewed, and the report has to say so instead of implying a
343
- review it did not get. If that skill needs to push a commit, leave
344
- auto-merge armed (section 1); when it returns, re-read `headRefOid` and reset
345
- `verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here
346
- — it is the single source of truth for review-thread handling.
347
-
348
- **Merging past a rate-limited CodeRabbit is permitted — but ONLY when CodeRabbit
349
- is the sole gate still blocking the merge.** When the CodeRabbit context reports
350
- `Review rate limited` and it is the *only* thing standing between the PR and
351
- `MERGED`, do not wait it out. Prove that vendor-cap signal from the live check
352
- description; `statusCheckRollup` does not carry enough detail, so read it with:
337
+ **Read the description from the commit status API, never from the rollup.**
338
+ `gh pr view --json statusCheckRollup` returns a `StatusContext` with **no
339
+ `description` key at all** — verified — so a hollow green is invisible there:
340
+
341
+ ```bash
342
+ gh api repos/<owner>/<repo>/commits/<head-sha>/status \
343
+ --jq '.statuses[] | {context, state, description}'
344
+ ```
345
+
346
+ For a quick human look at one context, `gh pr checks <pr> --json name,state,description,bucket`
347
+ prints the same description — but the prover and CI read the commit-status API,
348
+ because `gh pr checks` resolves the rollup through the workflow run and exits
349
+ non-zero with EMPTY stdout when it lacks `actions: read`, which reads as a
350
+ content problem and never says "permission".
351
+
352
+ **Which reviewers to read is configuration, never a vendor name.** Each gate in
353
+ `.lisa.config.json` that declares `evidence.reviewer: true` on its
354
+ `pull-request` moment names one third-party reviewer: `await` is its status
355
+ context, and `evidence.proof` is the exact **reviewed-when** description. The
356
+ shipped prover resolves them and returns the whole verdict:
353
357
 
354
358
  ```bash
355
- gh pr checks <pr> --json name,state,description,bucket \
356
- --jq '.[] | select(.name == "CodeRabbit")'
359
+ node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
357
360
  ```
358
361
 
359
- A merely pending or queued CodeRabbit check is not proof of a vendor cap and
360
- must keep polling. Once that signal is explicit, and with `auto_merge=true`, the
361
- PR already has auto-merge enabled (section 1), so
362
+ Three cases, and each is handled differently:
363
+
364
+ 1. **`satisfied`** — every configured reviewer's description matched its
365
+ reviewed-when phrase. Nothing further; record `reviewed` and the phrase.
366
+ 2. **`no-reviewer-configured`** — no gate declares one. This is a legitimate
367
+ state, **not** a pass: say so explicitly in the report ("no third-party
368
+ reviewer configured; nothing read this diff on that path"), and do not
369
+ invent a substitute for a reviewer the project never asked for.
370
+ 3. **anything else** — throttled, skipped, absent, empty, or a description in
371
+ no configured list. **Treat every one of these as NOT REVIEWED.** The
372
+ allowlist is the only thing that grants credit; a denylist of known-bad
373
+ phrases fails open, and the vocabulary is open-ended — four distinct strings
374
+ are known already, from two repositories. An unrecognised string costs an
375
+ extra local review, never a free pass.
376
+
377
+ **In case 3, substitute — do not block, and do not wait.** Before merging, run
378
+ the local adversarial review over the PR diff and **post it on the PR**, so the
379
+ code that merges has been read by something. The comment must:
380
+
381
+ - carry the marker `<!-- lisa:review-substitute context="<context>" head="<head-sha>" -->`
382
+ as its first line, so CI can find it for this reviewer at this head;
383
+ - state plainly that it is a **self-review substitute**, not a third-party
384
+ review;
385
+ - **record the actual description string observed, verbatim**, so the trail says
386
+ *why* it was substituted rather than only that it was;
387
+ - **name the observed condition, not a guessed cause.** The prover returns a
388
+ `reading` per reviewer and a sentence for it; copy that sentence in. In
389
+ particular `absent` must read as *no third-party status was present at this
390
+ head at all* — an operator seeing an empty pair of quotes cannot tell that
391
+ apart from a status whose description was blank.
392
+
393
+ **Several different causes produce the same reading, and you cannot tell them
394
+ apart from here.** A quota throttle, a per-repository decline, and **auto-review
395
+ being disabled for pull requests whose base is not the default branch** all end
396
+ with no evidence at the head. The last one is worth knowing about because it is
397
+ **permanent** for the pull requests it affects rather than transient — a project
398
+ whose PRs target an integration branch would never be reviewed, and from outside
399
+ that looks identical to the healthy case. It gets **no special case**: the
400
+ allowlist already handles it, because it produces no declared proof phrase.
401
+ Report what was observed and let a human read the cause off it.
402
+
403
+ Re-read `headRefOid` first and post against the head that will actually merge —
404
+ review evidence decays on every push, and a substitute written for an earlier
405
+ head reviewed code that is no longer what merges. If the PR gains a commit
406
+ afterwards, substitute again for the new head.
407
+
408
+ **Never re-request a review to "refresh" it.** Re-requesting **overwrites** the
409
+ existing status rather than adding to it, so under a throttle it destroys a real
410
+ review, one-way. Do not post a review-request comment, and do not add a workflow
411
+ that does.
412
+
413
+ Whether the quota is scoped per repository, per organisation, or per account is
414
+ **unmeasured** — one lane reports a rolling window, another says explicitly that
415
+ it did not test it. Do not depend on a quota shape, and do not assert one.
416
+
417
+ If the review skill needs to push a commit, leave auto-merge armed (section 1);
418
+ when it returns, re-read `headRefOid` and
419
+ reset `verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here —
420
+ that skill is the single source of truth for review-thread handling.
421
+
422
+ `NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
423
+ it as a failing check. A hollow review check is usually a vendor entitlement or
424
+ throttle, which is a billing matter no amount of driving will clear. It is a
425
+ **substitution** obligation followed by a **reporting** obligation: something
426
+ read the diff, and the report says what did.
427
+
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
362
433
  leave the latch armed and merge directly with `gh pr merge <pr> --<merge_method>`
363
- (pass `--admin` only if branch protection lists the rate-limited context as
434
+ (pass `--admin` only if branch protection lists the hollow context as
364
435
  required and refuses the plain merge). "Sole gate" means every one of these is
365
436
  already true at the moment you merge — verify each against the live poll, never
366
437
  from memory:
367
438
 
439
+ - the adversarial-review substitute is posted for the current head;
368
440
  - every other required check in `statusCheckRollup` is green (no FAILURE, no
369
441
  other PENDING);
370
442
  - zero unresolved review threads (human or bot);
@@ -380,13 +452,37 @@ from memory:
380
452
  - `mergeable == MERGEABLE` and `mergeStateStatus` is not `BEHIND`/`DIRTY`;
381
453
  - no pending auto-fix PR into this branch (step f).
382
454
 
455
+ **A review OBJECT is not a status CONTEXT.** Everything above is about status
456
+ descriptions. An **empty-bodied `APPROVED` review is an ordinary approval** and
457
+ is never hollow — do not let empty-description reasoning leak onto review
458
+ objects. A `CHANGES_REQUESTED` is a blocking objection **whatever its body**,
459
+ because its content commonly lives entirely in inline threads.
460
+
461
+ **Filter review objects on `commit_id == head`, in BOTH directions.** Writing the
462
+ filter for one direction only is the easy bug, and each direction fails a
463
+ different way:
464
+
465
+ - a stale `APPROVED` from an older commit reads as fresh approval of code it
466
+ never saw;
467
+ - a stale `CHANGES_REQUESTED` reads as a current objection and blocks work a
468
+ newer head already fixed.
469
+
470
+ **And "no review object" is not "unreviewed".** These are different
471
+ propositions, and collapsing them is a measured incident rather than a
472
+ hypothetical: a lane counted the absence of a *third-party* review object across
473
+ 15 PRs, read it as "12 of 15 have no approving review", and froze merging
474
+ fleet-wide. A PR carrying a genuine human approval at head and no third-party
475
+ object **is reviewed**. Answer the two questions separately, from their two
476
+ separate data sources.
477
+
383
478
  If *anything* else is also blocking, the exception does not apply: clear that
384
479
  blocker through its own step first, re-poll, and only then re-evaluate whether
385
- CodeRabbit is the last gate standing. The exception never stacks with another
480
+ the review is the last gate standing. The exception never stacks with another
386
481
  bypass, never fires under `auto_merge=false` (that mode stops at
387
482
  `awaiting-human`, and a human decides), and never fires in `on_blocker=report`
388
- mode. Record the result as `MERGED — NOT REVIEWED: CodeRabbit rate limited
389
- (merged past as sole remaining gate)` in the terminal report (section 4).
483
+ mode. Record the result as `MERGED — NOT REVIEWED (<context> "<observed
484
+ description>"), local adversarial review substituted and posted` in the terminal
485
+ report (section 4).
390
486
 
391
487
  ### e. Review gate stall (`reviewDecision == CHANGES_REQUESTED`)
392
488
  After the requested changes are addressed and threads resolved, the prior
@@ -608,8 +704,9 @@ Loop until one of:
608
704
  needs design input, or genuine unresolved human objection (not a bot gate). Stop
609
705
  and report exactly what is blocking and what was already tried — never force the
610
706
  merge or weaken a gate to get past it. The one sanctioned exception is a
611
- rate-limited CodeRabbit that is the *sole* remaining gate on an auto-merge
612
- enabled PR (step d); it never extends to any other gate.
707
+ hollow third-party review that is the *sole* remaining gate on an auto-merge
708
+ enabled PR, and only once the adversarial-review substitute is posted (step
709
+ d); it never extends to any other gate.
613
710
 
614
711
  At every terminal state, release the babysitter lease
615
712
  (`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
@@ -622,9 +719,13 @@ successful `MERGED`. "Merged, all checks green" is exactly the sentence that hid
622
719
  anything. Green means *no gate objected*; it does not mean *something looked*.
623
720
  So state the verdict alongside the outcome:
624
721
 
625
- - `MERGED — reviewed (CodeRabbit "Review approved")`
626
- - `MERGED — NOT REVIEWED: CodeRabbit posted success but "Review rate limited"`
627
- - `MERGED — NOT REVIEWED: CodeRabbit rate limited (merged past as sole remaining gate)`
722
+ - `MERGED — reviewed (<context> "Review completed")`
723
+ - `MERGED — NOT REVIEWED (<context> "Review rate limited"), local adversarial review substituted and posted`
724
+ - `MERGED — NOT REVIEWED (<context> "<unrecognised description>"), local adversarial review substituted and posted`
725
+ - `MERGED — no third-party reviewer configured; local review not substituted for one that was never declared`
726
+
727
+ Spell the reviewer as the configured context, not a vendor name: a project may
728
+ declare none, one, or several, and each shows its own evidence.
628
729
 
629
730
  This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
630
731
  turn a merged PR into a blocked one, and it must never be used to withhold a
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.29.10",
3
+ "version": "4.29.12",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"