@codyswann/lisa 4.87.5 → 4.87.6

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 (106) hide show
  1. package/all/copy-overwrite/scripts/check-third-party-review-evidence.mjs +568 -80
  2. package/dist/cli/doctor-seeded-artifacts.d.ts.map +1 -1
  3. package/dist/cli/doctor-seeded-artifacts.js +10 -3
  4. package/dist/cli/doctor-seeded-artifacts.js.map +1 -1
  5. package/dist/cli/doctor-third-party-evidence-seed.d.ts +32 -0
  6. package/dist/cli/doctor-third-party-evidence-seed.d.ts.map +1 -0
  7. package/dist/cli/doctor-third-party-evidence-seed.js +239 -0
  8. package/dist/cli/doctor-third-party-evidence-seed.js.map +1 -0
  9. package/dist/cli/doctor.js +2 -2
  10. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  12. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  13. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  14. package/dist/core/third-party-evidence-seed.d.ts +42 -0
  15. package/dist/core/third-party-evidence-seed.d.ts.map +1 -0
  16. package/dist/core/third-party-evidence-seed.js +233 -0
  17. package/dist/core/third-party-evidence-seed.js.map +1 -0
  18. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  19. package/dist/core/upstream-evidence-manifest.js +21 -4
  20. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  21. package/package.json +4 -4
  22. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  25. package/plugins/lisa/.codex-plugin/skills/lisa-pull-request-review/SKILL.md +11 -5
  26. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  27. package/plugins/lisa/skills/lisa-pull-request-review/SKILL.md +11 -5
  28. package/plugins/lisa-agy/plugin.json +1 -1
  29. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  30. package/plugins/lisa-agy/skills/lisa-pull-request-review/SKILL.md +11 -5
  31. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  34. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  38. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  40. package/plugins/lisa-copilot/skills/lisa-pull-request-review/SKILL.md +11 -5
  41. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  43. package/plugins/lisa-cursor/skills/lisa-pull-request-review/SKILL.md +11 -5
  44. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  46. package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  47. package/plugins/lisa-devin/skills/lisa-pull-request-review/SKILL.md +11 -5
  48. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo-agy/plugin.json +1 -1
  51. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  62. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  65. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  69. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  72. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  76. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  79. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  83. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-rails-agy/plugin.json +1 -1
  86. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  90. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  93. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  100. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  104. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +59 -31
  105. package/plugins/src/base/skills/lisa-pull-request-review/SKILL.md +11 -5
  106. package/typescript/create-only/.github/workflows/third-party-review-evidence.yml +62 -30
package/package.json CHANGED
@@ -189,7 +189,7 @@
189
189
  "qs": "^6.16.0"
190
190
  },
191
191
  "name": "@codyswann/lisa",
192
- "version": "4.87.5",
192
+ "version": "4.87.6",
193
193
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
194
194
  "main": "dist/index.js",
195
195
  "exports": {
@@ -347,7 +347,7 @@
347
347
  "test": "tests"
348
348
  },
349
349
  "types": "./dist/index.d.ts",
350
- "lisaReleaseCommit": "708dc591ad0fb65cd1b5c81af38aac69fbea8b2c",
351
- "gitHead": "708dc591ad0fb65cd1b5c81af38aac69fbea8b2c",
352
- "lisaReleaseTag": "v4.87.5"
350
+ "lisaReleaseCommit": "0eb16cd1048817ae04fbd1cd2a55851a1e2c46d6",
351
+ "gitHead": "0eb16cd1048817ae04fbd1cd2a55851a1e2c46d6",
352
+ "lisaReleaseTag": "v4.87.6"
353
353
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.87.5",
3
+ "version": "4.87.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.87.5",
3
+ "version": "4.87.6",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -955,13 +955,30 @@ resolve every thread via `resolveReviewThread` so the branch-protection
955
955
  thread-resolution gate clears.
956
956
 
957
957
  **A green review check is not proof a review happened.** A third-party review
958
- app posts a **commit status** on the PR head; when it is throttled or declines,
959
- it still reports `success` and never blocks. Only the status **description**
960
- tells the two apart. Measured over the last 30 merged PRs in one repository: 24
961
- of 30 merges passed a required review gate that had reviewed nothing, every one
962
- of them `success`. A second repository sampled `success` on 50 of 50.
958
+ app reports on the PR head through a **check run** or a **commit status**, and
959
+ one app can move from the second to the first without notice: measured in one
960
+ repository, its reviewer stopped posting statuses between 2026-10-26 and
961
+ 2026-10-27 and has reported only as a check run since. When it is throttled or
962
+ declines it never blocks — a status still reports `success`, and a check run
963
+ concludes `neutral`, which GitHub counts as passing a required check. Only the
964
+ reviewer's own words tell the two apart. Measured over the last 30 merged PRs in
965
+ one repository: 24 of 30 merges passed a required review gate that had reviewed
966
+ nothing. A second repository sampled `success` on 50 of 50.
967
+
968
+ **Read the words from the reviewer's own API object, never from the rollup.**
969
+ A check run's title is often a generic `Finished`; its result is the **first
970
+ line of its summary** — `Result: Success - Review completed` when a review
971
+ completed, `Result: Rate limited - Review rate limited` when it was throttled.
972
+ Filter by name — the `?check_name=<context>` filter, sent as a field so a name
973
+ with spaces is encoded for you — because a head commonly carries more check
974
+ runs than one unfiltered page returns:
963
975
 
964
- **Read the description from the commit status API, never from the rollup.**
976
+ ```bash
977
+ gh api -X GET 'repos/<owner>/<repo>/commits/<head-sha>/check-runs' -f check_name='<context>' \
978
+ --jq '.check_runs[] | {id, name, head_sha, status, conclusion, app: .app.id, title: .output.title, result: ((.output.summary // "") | split("\n")[0])}'
979
+ ```
980
+
981
+ A commit status carries its words in its description, and
965
982
  `gh pr view --json statusCheckRollup` returns a `StatusContext` with **no
966
983
  `description` key at all** — verified — so a hollow green is invisible there:
967
984
 
@@ -971,16 +988,22 @@ gh api repos/<owner>/<repo>/commits/<head-sha>/status \
971
988
  ```
972
989
 
973
990
  For a quick human look at one context, `gh pr checks <pr> --json name,state,description,bucket`
974
- prints the same description — but the prover and CI read the commit-status API,
975
- because `gh pr checks` resolves the rollup through the workflow run and exits
976
- non-zero with EMPTY stdout when it lacks `actions: read`, which reads as a
977
- content problem and never says "permission".
991
+ prints a status's description, but its description column is EMPTY for a check
992
+ run — it shows neither the run's title nor its summary — so it can never tell
993
+ you a check run reviewed anything. The prover and CI read the two APIs above
994
+ instead, because `gh pr checks`
995
+ resolves the rollup through the workflow run and exits non-zero with EMPTY
996
+ stdout when it lacks `actions: read`, which reads as a content problem and
997
+ never says "permission". Reading a check run needs `checks: read`.
978
998
 
979
999
  **Which reviewers to read is configuration, never a vendor name.** Each gate in
980
1000
  `.lisa.config.json` that declares `evidence.reviewer: true` on its
981
- `pull-request` moment names one third-party reviewer: `await` is its status
982
- context, and `evidence.proof` is the exact **reviewed-when** description. The
983
- shipped prover resolves them and returns the whole verdict:
1001
+ `pull-request` moment names one third-party reviewer: `await` is the status
1002
+ context or check-run name it reports under, `posted_by` (when declared) is the
1003
+ app whose check run counts, and `evidence.proof` is the exact **reviewed-when**
1004
+ phrase. The shipped prover reads both representations and returns the whole
1005
+ verdict. Take the verdict from it, not from your own reading of the payloads
1006
+ above:
984
1007
 
985
1008
  ```bash
986
1009
  node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
@@ -988,18 +1011,20 @@ node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
988
1011
 
989
1012
  Three cases, and each is handled differently:
990
1013
 
991
- 1. **`satisfied`** — every configured reviewer's description matched its
992
- reviewed-when phrase. Nothing further; record `reviewed` and the phrase.
1014
+ 1. **`satisfied`** — every configured reviewer showed its reviewed-when
1015
+ evidence: a status whose description is the phrase, or a check run that
1016
+ completed with `success` and whose first summary line is the completed
1017
+ result. Nothing further; record `reviewed` and the words it observed.
993
1018
  2. **`no-reviewer-configured`** — no gate declares one. This is a legitimate
994
1019
  state, **not** a pass: say so explicitly in the report ("no third-party
995
1020
  reviewer configured; nothing read this diff on that path"), and do not
996
1021
  invent a substitute for a reviewer the project never asked for.
997
- 3. **anything else** — throttled, skipped, absent, empty, or a description in
998
- no configured list. **Treat every one of these as NOT REVIEWED.** The
999
- allowlist is the only thing that grants credit; a denylist of known-bad
1000
- phrases fails open, and the vocabulary is open-ended — four distinct strings
1001
- are known already, from two repositories. An unrecognised string costs an
1002
- extra local review, never a free pass.
1022
+ 3. **anything else** — throttled, skipped, absent, empty, unreadable, a check
1023
+ run that did not succeed, or words in no configured list. **Treat every one
1024
+ of these as NOT REVIEWED.** The allowlist is the only thing that grants
1025
+ credit; a denylist of known-bad phrases fails open, and the vocabulary is
1026
+ open-ended — four distinct strings are known already, from two repositories.
1027
+ An unrecognised string costs an extra local review, never a free pass.
1003
1028
 
1004
1029
  **In case 3, substitute — do not block, and do not wait.** Before merging, run
1005
1030
  the local adversarial review over the PR diff and **post it on the PR**, so the
@@ -1009,13 +1034,15 @@ code that merges has been read by something. The comment must:
1009
1034
  as its first line, so CI can find it for this reviewer at this head;
1010
1035
  - state plainly that it is a **self-review substitute**, not a third-party
1011
1036
  review;
1012
- - **record the actual description string observed, verbatim**, so the trail says
1013
- *why* it was substituted rather than only that it was;
1037
+ - **record the actual words observed, verbatim** — the prover's `observed`: a
1038
+ status's description, or a check run's title or first summary line — so the
1039
+ trail says *why* it was substituted rather than only that it was;
1014
1040
  - **name the observed condition, not a guessed cause.** The prover returns a
1015
1041
  `reading` per reviewer and a sentence for it; copy that sentence in. In
1016
1042
  particular `absent` must read as *no third-party status was present at this
1017
1043
  head at all* — an operator seeing an empty pair of quotes cannot tell that
1018
- apart from a status whose description was blank.
1044
+ apart from a status whose description was blank — and `unreadable` must read
1045
+ as a read that failed, never as an absence.
1019
1046
 
1020
1047
  **Several different causes produce the same reading, and you cannot tell them
1021
1048
  apart from here.** A quota throttle, a per-repository decline, and **auto-review
@@ -1071,8 +1098,8 @@ directions and must agree: the prover says whether the context did work, and the
1071
1098
  configured reviewed-when phrase says what "did work" looks like for this
1072
1099
  project.
1073
1100
 
1074
- Prove the signal from the commit status API as above; a merely pending or queued
1075
- check is not proof of anything and must keep polling. **A settled vacuity, the
1101
+ Prove the signal from the check-run or commit-status API as above; a
1102
+ merely pending or queued check is not proof of anything and must keep polling. **A settled vacuity, the
1076
1103
  rate limit included, arrives here immediately.** There is no wait to spend first
1077
1104
  and no re-request to try: a rate limit's window is of unknown length, and
1078
1105
  waiting it out stalls the pull request without reading a line of the diff. The
@@ -1103,11 +1130,12 @@ from memory:
1103
1130
  - `mergeable == MERGEABLE` and `mergeStateStatus` is not `BEHIND`/`DIRTY`;
1104
1131
  - no pending auto-fix PR into this branch (step f).
1105
1132
 
1106
- **A review OBJECT is not a status CONTEXT.** Everything above is about status
1107
- descriptions. An **empty-bodied `APPROVED` review is an ordinary approval** and
1108
- is never hollow — do not let empty-description reasoning leak onto review
1109
- objects. A `CHANGES_REQUESTED` is a blocking objection **whatever its body**,
1110
- because its content commonly lives entirely in inline threads.
1133
+ **A review OBJECT is not a status CONTEXT.** Everything above is about the
1134
+ words a status or check run reports. An **empty-bodied `APPROVED` review is an
1135
+ ordinary approval** and is never hollow — do not let empty-description
1136
+ reasoning leak onto review objects. A `CHANGES_REQUESTED` is a blocking
1137
+ objection **whatever its body**, because its content commonly lives entirely in
1138
+ inline threads.
1111
1139
 
1112
1140
  **Filter review objects on `commit_id == head`, in BOTH directions.** Writing the
1113
1141
  filter for one direction only is the easy bug, and each direction fails a
@@ -58,11 +58,17 @@ gh pr checks <pr> --json name,state,bucket,description \
58
58
  --jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
59
59
  ```
60
60
 
61
- **The state column reads `SUCCESS` whether the review was real or hollow — only
62
- the description distinguishes them.** `Review approved` / `Review completed` is
63
- a real review; `Review rate limited`, `Review queued`, or a missing context is
64
- not. Never read `gh pr view --json statusCheckRollup` for this: CodeRabbit posts
65
- a legacy commit status, which that route returns *without* the description.
61
+ **The state column passes whether the review was real or hollow — only the
62
+ reviewer's words distinguish them.** For a commit status those words are the
63
+ description: `Review approved` / `Review completed` is a real review;
64
+ `Review rate limited`, `Review queued`, or a missing context is not. A reviewer
65
+ can also report through a **check run** instead, and then this command cannot
66
+ tell you anything: the description column is empty for a check run, which
67
+ reads `SUCCESS` when it reviewed and `NEUTRAL` — still passing a required
68
+ check — when it was throttled. The run's result is the first line of its
69
+ summary, so use `node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json`,
70
+ which reads both representations. Never read `gh pr view --json statusCheckRollup`
71
+ for this: it returns a commit status *without* its description.
66
72
 
67
73
  Where the project ships Lisa's guard, prefer its machine-readable form, which
68
74
  also says whether the check is ruleset-required (so whether branch protection
@@ -953,13 +953,30 @@ resolve every thread via `resolveReviewThread` so the branch-protection
953
953
  thread-resolution gate clears.
954
954
 
955
955
  **A green review check is not proof a review happened.** A third-party review
956
- app posts a **commit status** on the PR head; when it is throttled or declines,
957
- it still reports `success` and never blocks. Only the status **description**
958
- tells the two apart. Measured over the last 30 merged PRs in one repository: 24
959
- of 30 merges passed a required review gate that had reviewed nothing, every one
960
- of them `success`. A second repository sampled `success` on 50 of 50.
956
+ app reports on the PR head through a **check run** or a **commit status**, and
957
+ one app can move from the second to the first without notice: measured in one
958
+ repository, its reviewer stopped posting statuses between 2026-10-26 and
959
+ 2026-10-27 and has reported only as a check run since. When it is throttled or
960
+ declines it never blocks — a status still reports `success`, and a check run
961
+ concludes `neutral`, which GitHub counts as passing a required check. Only the
962
+ reviewer's own words tell the two apart. Measured over the last 30 merged PRs in
963
+ one repository: 24 of 30 merges passed a required review gate that had reviewed
964
+ nothing. A second repository sampled `success` on 50 of 50.
965
+
966
+ **Read the words from the reviewer's own API object, never from the rollup.**
967
+ A check run's title is often a generic `Finished`; its result is the **first
968
+ line of its summary** — `Result: Success - Review completed` when a review
969
+ completed, `Result: Rate limited - Review rate limited` when it was throttled.
970
+ Filter by name — the `?check_name=<context>` filter, sent as a field so a name
971
+ with spaces is encoded for you — because a head commonly carries more check
972
+ runs than one unfiltered page returns:
961
973
 
962
- **Read the description from the commit status API, never from the rollup.**
974
+ ```bash
975
+ gh api -X GET 'repos/<owner>/<repo>/commits/<head-sha>/check-runs' -f check_name='<context>' \
976
+ --jq '.check_runs[] | {id, name, head_sha, status, conclusion, app: .app.id, title: .output.title, result: ((.output.summary // "") | split("\n")[0])}'
977
+ ```
978
+
979
+ A commit status carries its words in its description, and
963
980
  `gh pr view --json statusCheckRollup` returns a `StatusContext` with **no
964
981
  `description` key at all** — verified — so a hollow green is invisible there:
965
982
 
@@ -969,16 +986,22 @@ gh api repos/<owner>/<repo>/commits/<head-sha>/status \
969
986
  ```
970
987
 
971
988
  For a quick human look at one context, `gh pr checks <pr> --json name,state,description,bucket`
972
- prints the same description — but the prover and CI read the commit-status API,
973
- because `gh pr checks` resolves the rollup through the workflow run and exits
974
- non-zero with EMPTY stdout when it lacks `actions: read`, which reads as a
975
- content problem and never says "permission".
989
+ prints a status's description, but its description column is EMPTY for a check
990
+ run — it shows neither the run's title nor its summary — so it can never tell
991
+ you a check run reviewed anything. The prover and CI read the two APIs above
992
+ instead, because `gh pr checks`
993
+ resolves the rollup through the workflow run and exits non-zero with EMPTY
994
+ stdout when it lacks `actions: read`, which reads as a content problem and
995
+ never says "permission". Reading a check run needs `checks: read`.
976
996
 
977
997
  **Which reviewers to read is configuration, never a vendor name.** Each gate in
978
998
  `.lisa.config.json` that declares `evidence.reviewer: true` on its
979
- `pull-request` moment names one third-party reviewer: `await` is its status
980
- context, and `evidence.proof` is the exact **reviewed-when** description. The
981
- shipped prover resolves them and returns the whole verdict:
999
+ `pull-request` moment names one third-party reviewer: `await` is the status
1000
+ context or check-run name it reports under, `posted_by` (when declared) is the
1001
+ app whose check run counts, and `evidence.proof` is the exact **reviewed-when**
1002
+ phrase. The shipped prover reads both representations and returns the whole
1003
+ verdict. Take the verdict from it, not from your own reading of the payloads
1004
+ above:
982
1005
 
983
1006
  ```bash
984
1007
  node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
@@ -986,18 +1009,20 @@ node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
986
1009
 
987
1010
  Three cases, and each is handled differently:
988
1011
 
989
- 1. **`satisfied`** — every configured reviewer's description matched its
990
- reviewed-when phrase. Nothing further; record `reviewed` and the phrase.
1012
+ 1. **`satisfied`** — every configured reviewer showed its reviewed-when
1013
+ evidence: a status whose description is the phrase, or a check run that
1014
+ completed with `success` and whose first summary line is the completed
1015
+ result. Nothing further; record `reviewed` and the words it observed.
991
1016
  2. **`no-reviewer-configured`** — no gate declares one. This is a legitimate
992
1017
  state, **not** a pass: say so explicitly in the report ("no third-party
993
1018
  reviewer configured; nothing read this diff on that path"), and do not
994
1019
  invent a substitute for a reviewer the project never asked for.
995
- 3. **anything else** — throttled, skipped, absent, empty, or a description in
996
- no configured list. **Treat every one of these as NOT REVIEWED.** The
997
- allowlist is the only thing that grants credit; a denylist of known-bad
998
- phrases fails open, and the vocabulary is open-ended — four distinct strings
999
- are known already, from two repositories. An unrecognised string costs an
1000
- extra local review, never a free pass.
1020
+ 3. **anything else** — throttled, skipped, absent, empty, unreadable, a check
1021
+ run that did not succeed, or words in no configured list. **Treat every one
1022
+ of these as NOT REVIEWED.** The allowlist is the only thing that grants
1023
+ credit; a denylist of known-bad phrases fails open, and the vocabulary is
1024
+ open-ended — four distinct strings are known already, from two repositories.
1025
+ An unrecognised string costs an extra local review, never a free pass.
1001
1026
 
1002
1027
  **In case 3, substitute — do not block, and do not wait.** Before merging, run
1003
1028
  the local adversarial review over the PR diff and **post it on the PR**, so the
@@ -1007,13 +1032,15 @@ code that merges has been read by something. The comment must:
1007
1032
  as its first line, so CI can find it for this reviewer at this head;
1008
1033
  - state plainly that it is a **self-review substitute**, not a third-party
1009
1034
  review;
1010
- - **record the actual description string observed, verbatim**, so the trail says
1011
- *why* it was substituted rather than only that it was;
1035
+ - **record the actual words observed, verbatim** — the prover's `observed`: a
1036
+ status's description, or a check run's title or first summary line — so the
1037
+ trail says *why* it was substituted rather than only that it was;
1012
1038
  - **name the observed condition, not a guessed cause.** The prover returns a
1013
1039
  `reading` per reviewer and a sentence for it; copy that sentence in. In
1014
1040
  particular `absent` must read as *no third-party status was present at this
1015
1041
  head at all* — an operator seeing an empty pair of quotes cannot tell that
1016
- apart from a status whose description was blank.
1042
+ apart from a status whose description was blank — and `unreadable` must read
1043
+ as a read that failed, never as an absence.
1017
1044
 
1018
1045
  **Several different causes produce the same reading, and you cannot tell them
1019
1046
  apart from here.** A quota throttle, a per-repository decline, and **auto-review
@@ -1069,8 +1096,8 @@ directions and must agree: the prover says whether the context did work, and the
1069
1096
  configured reviewed-when phrase says what "did work" looks like for this
1070
1097
  project.
1071
1098
 
1072
- Prove the signal from the commit status API as above; a merely pending or queued
1073
- check is not proof of anything and must keep polling. **A settled vacuity, the
1099
+ Prove the signal from the check-run or commit-status API as above; a
1100
+ merely pending or queued check is not proof of anything and must keep polling. **A settled vacuity, the
1074
1101
  rate limit included, arrives here immediately.** There is no wait to spend first
1075
1102
  and no re-request to try: a rate limit's window is of unknown length, and
1076
1103
  waiting it out stalls the pull request without reading a line of the diff. The
@@ -1101,11 +1128,12 @@ from memory:
1101
1128
  - `mergeable == MERGEABLE` and `mergeStateStatus` is not `BEHIND`/`DIRTY`;
1102
1129
  - no pending auto-fix PR into this branch (step f).
1103
1130
 
1104
- **A review OBJECT is not a status CONTEXT.** Everything above is about status
1105
- descriptions. An **empty-bodied `APPROVED` review is an ordinary approval** and
1106
- is never hollow — do not let empty-description reasoning leak onto review
1107
- objects. A `CHANGES_REQUESTED` is a blocking objection **whatever its body**,
1108
- because its content commonly lives entirely in inline threads.
1131
+ **A review OBJECT is not a status CONTEXT.** Everything above is about the
1132
+ words a status or check run reports. An **empty-bodied `APPROVED` review is an
1133
+ ordinary approval** and is never hollow — do not let empty-description
1134
+ reasoning leak onto review objects. A `CHANGES_REQUESTED` is a blocking
1135
+ objection **whatever its body**, because its content commonly lives entirely in
1136
+ inline threads.
1109
1137
 
1110
1138
  **Filter review objects on `commit_id == head`, in BOTH directions.** Writing the
1111
1139
  filter for one direction only is the easy bug, and each direction fails a
@@ -56,11 +56,17 @@ gh pr checks <pr> --json name,state,bucket,description \
56
56
  --jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
57
57
  ```
58
58
 
59
- **The state column reads `SUCCESS` whether the review was real or hollow — only
60
- the description distinguishes them.** `Review approved` / `Review completed` is
61
- a real review; `Review rate limited`, `Review queued`, or a missing context is
62
- not. Never read `gh pr view --json statusCheckRollup` for this: CodeRabbit posts
63
- a legacy commit status, which that route returns *without* the description.
59
+ **The state column passes whether the review was real or hollow — only the
60
+ reviewer's words distinguish them.** For a commit status those words are the
61
+ description: `Review approved` / `Review completed` is a real review;
62
+ `Review rate limited`, `Review queued`, or a missing context is not. A reviewer
63
+ can also report through a **check run** instead, and then this command cannot
64
+ tell you anything: the description column is empty for a check run, which
65
+ reads `SUCCESS` when it reviewed and `NEUTRAL` — still passing a required
66
+ check — when it was throttled. The run's result is the first line of its
67
+ summary, so use `node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json`,
68
+ which reads both representations. Never read `gh pr view --json statusCheckRollup`
69
+ for this: it returns a commit status *without* its description.
64
70
 
65
71
  Where the project ships Lisa's guard, prefer its machine-readable form, which
66
72
  also says whether the check is ruleset-required (so whether branch protection
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.87.5",
3
+ "version": "4.87.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -953,13 +953,30 @@ resolve every thread via `resolveReviewThread` so the branch-protection
953
953
  thread-resolution gate clears.
954
954
 
955
955
  **A green review check is not proof a review happened.** A third-party review
956
- app posts a **commit status** on the PR head; when it is throttled or declines,
957
- it still reports `success` and never blocks. Only the status **description**
958
- tells the two apart. Measured over the last 30 merged PRs in one repository: 24
959
- of 30 merges passed a required review gate that had reviewed nothing, every one
960
- of them `success`. A second repository sampled `success` on 50 of 50.
956
+ app reports on the PR head through a **check run** or a **commit status**, and
957
+ one app can move from the second to the first without notice: measured in one
958
+ repository, its reviewer stopped posting statuses between 2026-10-26 and
959
+ 2026-10-27 and has reported only as a check run since. When it is throttled or
960
+ declines it never blocks — a status still reports `success`, and a check run
961
+ concludes `neutral`, which GitHub counts as passing a required check. Only the
962
+ reviewer's own words tell the two apart. Measured over the last 30 merged PRs in
963
+ one repository: 24 of 30 merges passed a required review gate that had reviewed
964
+ nothing. A second repository sampled `success` on 50 of 50.
965
+
966
+ **Read the words from the reviewer's own API object, never from the rollup.**
967
+ A check run's title is often a generic `Finished`; its result is the **first
968
+ line of its summary** — `Result: Success - Review completed` when a review
969
+ completed, `Result: Rate limited - Review rate limited` when it was throttled.
970
+ Filter by name — the `?check_name=<context>` filter, sent as a field so a name
971
+ with spaces is encoded for you — because a head commonly carries more check
972
+ runs than one unfiltered page returns:
961
973
 
962
- **Read the description from the commit status API, never from the rollup.**
974
+ ```bash
975
+ gh api -X GET 'repos/<owner>/<repo>/commits/<head-sha>/check-runs' -f check_name='<context>' \
976
+ --jq '.check_runs[] | {id, name, head_sha, status, conclusion, app: .app.id, title: .output.title, result: ((.output.summary // "") | split("\n")[0])}'
977
+ ```
978
+
979
+ A commit status carries its words in its description, and
963
980
  `gh pr view --json statusCheckRollup` returns a `StatusContext` with **no
964
981
  `description` key at all** — verified — so a hollow green is invisible there:
965
982
 
@@ -969,16 +986,22 @@ gh api repos/<owner>/<repo>/commits/<head-sha>/status \
969
986
  ```
970
987
 
971
988
  For a quick human look at one context, `gh pr checks <pr> --json name,state,description,bucket`
972
- prints the same description — but the prover and CI read the commit-status API,
973
- because `gh pr checks` resolves the rollup through the workflow run and exits
974
- non-zero with EMPTY stdout when it lacks `actions: read`, which reads as a
975
- content problem and never says "permission".
989
+ prints a status's description, but its description column is EMPTY for a check
990
+ run — it shows neither the run's title nor its summary — so it can never tell
991
+ you a check run reviewed anything. The prover and CI read the two APIs above
992
+ instead, because `gh pr checks`
993
+ resolves the rollup through the workflow run and exits non-zero with EMPTY
994
+ stdout when it lacks `actions: read`, which reads as a content problem and
995
+ never says "permission". Reading a check run needs `checks: read`.
976
996
 
977
997
  **Which reviewers to read is configuration, never a vendor name.** Each gate in
978
998
  `.lisa.config.json` that declares `evidence.reviewer: true` on its
979
- `pull-request` moment names one third-party reviewer: `await` is its status
980
- context, and `evidence.proof` is the exact **reviewed-when** description. The
981
- shipped prover resolves them and returns the whole verdict:
999
+ `pull-request` moment names one third-party reviewer: `await` is the status
1000
+ context or check-run name it reports under, `posted_by` (when declared) is the
1001
+ app whose check run counts, and `evidence.proof` is the exact **reviewed-when**
1002
+ phrase. The shipped prover reads both representations and returns the whole
1003
+ verdict. Take the verdict from it, not from your own reading of the payloads
1004
+ above:
982
1005
 
983
1006
  ```bash
984
1007
  node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
@@ -986,18 +1009,20 @@ node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json
986
1009
 
987
1010
  Three cases, and each is handled differently:
988
1011
 
989
- 1. **`satisfied`** — every configured reviewer's description matched its
990
- reviewed-when phrase. Nothing further; record `reviewed` and the phrase.
1012
+ 1. **`satisfied`** — every configured reviewer showed its reviewed-when
1013
+ evidence: a status whose description is the phrase, or a check run that
1014
+ completed with `success` and whose first summary line is the completed
1015
+ result. Nothing further; record `reviewed` and the words it observed.
991
1016
  2. **`no-reviewer-configured`** — no gate declares one. This is a legitimate
992
1017
  state, **not** a pass: say so explicitly in the report ("no third-party
993
1018
  reviewer configured; nothing read this diff on that path"), and do not
994
1019
  invent a substitute for a reviewer the project never asked for.
995
- 3. **anything else** — throttled, skipped, absent, empty, or a description in
996
- no configured list. **Treat every one of these as NOT REVIEWED.** The
997
- allowlist is the only thing that grants credit; a denylist of known-bad
998
- phrases fails open, and the vocabulary is open-ended — four distinct strings
999
- are known already, from two repositories. An unrecognised string costs an
1000
- extra local review, never a free pass.
1020
+ 3. **anything else** — throttled, skipped, absent, empty, unreadable, a check
1021
+ run that did not succeed, or words in no configured list. **Treat every one
1022
+ of these as NOT REVIEWED.** The allowlist is the only thing that grants
1023
+ credit; a denylist of known-bad phrases fails open, and the vocabulary is
1024
+ open-ended — four distinct strings are known already, from two repositories.
1025
+ An unrecognised string costs an extra local review, never a free pass.
1001
1026
 
1002
1027
  **In case 3, substitute — do not block, and do not wait.** Before merging, run
1003
1028
  the local adversarial review over the PR diff and **post it on the PR**, so the
@@ -1007,13 +1032,15 @@ code that merges has been read by something. The comment must:
1007
1032
  as its first line, so CI can find it for this reviewer at this head;
1008
1033
  - state plainly that it is a **self-review substitute**, not a third-party
1009
1034
  review;
1010
- - **record the actual description string observed, verbatim**, so the trail says
1011
- *why* it was substituted rather than only that it was;
1035
+ - **record the actual words observed, verbatim** — the prover's `observed`: a
1036
+ status's description, or a check run's title or first summary line — so the
1037
+ trail says *why* it was substituted rather than only that it was;
1012
1038
  - **name the observed condition, not a guessed cause.** The prover returns a
1013
1039
  `reading` per reviewer and a sentence for it; copy that sentence in. In
1014
1040
  particular `absent` must read as *no third-party status was present at this
1015
1041
  head at all* — an operator seeing an empty pair of quotes cannot tell that
1016
- apart from a status whose description was blank.
1042
+ apart from a status whose description was blank — and `unreadable` must read
1043
+ as a read that failed, never as an absence.
1017
1044
 
1018
1045
  **Several different causes produce the same reading, and you cannot tell them
1019
1046
  apart from here.** A quota throttle, a per-repository decline, and **auto-review
@@ -1069,8 +1096,8 @@ directions and must agree: the prover says whether the context did work, and the
1069
1096
  configured reviewed-when phrase says what "did work" looks like for this
1070
1097
  project.
1071
1098
 
1072
- Prove the signal from the commit status API as above; a merely pending or queued
1073
- check is not proof of anything and must keep polling. **A settled vacuity, the
1099
+ Prove the signal from the check-run or commit-status API as above; a
1100
+ merely pending or queued check is not proof of anything and must keep polling. **A settled vacuity, the
1074
1101
  rate limit included, arrives here immediately.** There is no wait to spend first
1075
1102
  and no re-request to try: a rate limit's window is of unknown length, and
1076
1103
  waiting it out stalls the pull request without reading a line of the diff. The
@@ -1101,11 +1128,12 @@ from memory:
1101
1128
  - `mergeable == MERGEABLE` and `mergeStateStatus` is not `BEHIND`/`DIRTY`;
1102
1129
  - no pending auto-fix PR into this branch (step f).
1103
1130
 
1104
- **A review OBJECT is not a status CONTEXT.** Everything above is about status
1105
- descriptions. An **empty-bodied `APPROVED` review is an ordinary approval** and
1106
- is never hollow — do not let empty-description reasoning leak onto review
1107
- objects. A `CHANGES_REQUESTED` is a blocking objection **whatever its body**,
1108
- because its content commonly lives entirely in inline threads.
1131
+ **A review OBJECT is not a status CONTEXT.** Everything above is about the
1132
+ words a status or check run reports. An **empty-bodied `APPROVED` review is an
1133
+ ordinary approval** and is never hollow — do not let empty-description
1134
+ reasoning leak onto review objects. A `CHANGES_REQUESTED` is a blocking
1135
+ objection **whatever its body**, because its content commonly lives entirely in
1136
+ inline threads.
1109
1137
 
1110
1138
  **Filter review objects on `commit_id == head`, in BOTH directions.** Writing the
1111
1139
  filter for one direction only is the easy bug, and each direction fails a
@@ -56,11 +56,17 @@ gh pr checks <pr> --json name,state,bucket,description \
56
56
  --jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
57
57
  ```
58
58
 
59
- **The state column reads `SUCCESS` whether the review was real or hollow — only
60
- the description distinguishes them.** `Review approved` / `Review completed` is
61
- a real review; `Review rate limited`, `Review queued`, or a missing context is
62
- not. Never read `gh pr view --json statusCheckRollup` for this: CodeRabbit posts
63
- a legacy commit status, which that route returns *without* the description.
59
+ **The state column passes whether the review was real or hollow — only the
60
+ reviewer's words distinguish them.** For a commit status those words are the
61
+ description: `Review approved` / `Review completed` is a real review;
62
+ `Review rate limited`, `Review queued`, or a missing context is not. A reviewer
63
+ can also report through a **check run** instead, and then this command cannot
64
+ tell you anything: the description column is empty for a check run, which
65
+ reads `SUCCESS` when it reviewed and `NEUTRAL` — still passing a required
66
+ check — when it was throttled. The run's result is the first line of its
67
+ summary, so use `node scripts/check-third-party-review-evidence.mjs --sha <head-sha> --json`,
68
+ which reads both representations. Never read `gh pr view --json statusCheckRollup`
69
+ for this: it returns a commit status *without* its description.
64
70
 
65
71
  Where the project ships Lisa's guard, prefer its machine-readable form, which
66
72
  also says whether the check is ruleset-required (so whether branch protection
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.87.5",
3
+ "version": "4.87.6",
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.87.5",
3
+ "version": "4.87.6",
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.87.5",
3
+ "version": "4.87.6",
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.87.5",
3
+ "version": "4.87.6",
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.87.5",
3
+ "version": "4.87.6",
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.87.5",
3
+ "version": "4.87.6",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"