@codyswann/lisa 3.6.2 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/doctor-worktree-hygiene.d.ts +40 -0
- package/dist/cli/doctor-worktree-hygiene.d.ts.map +1 -0
- package/dist/cli/doctor-worktree-hygiene.js +123 -0
- package/dist/cli/doctor-worktree-hygiene.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +6 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +24 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/create-only/.github/required-checks.json +9 -1
- package/nestjs/create-only/.github/required-checks.json +9 -1
- package/package.json +1 -1
- 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 +29 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-pull-request-review/SKILL.md +50 -2
- package/plugins/lisa/rules/eager/falsifiable-checks.md +3 -1
- package/plugins/lisa/rules/reference/derived-branch-plan.md +18 -2
- package/plugins/lisa/rules/reference/falsifiable-checks.md +57 -5
- package/plugins/lisa/scripts/design-source-gate.mjs +11 -2
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +29 -1
- package/plugins/lisa/skills/lisa-pull-request-review/SKILL.md +50 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/design-source-gate.mjs +11 -2
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +29 -1
- package/plugins/lisa-agy/skills/lisa-pull-request-review/SKILL.md +50 -2
- 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/rules/eager/falsifiable-checks.md +3 -1
- package/plugins/lisa-copilot/rules/reference/derived-branch-plan.md +18 -2
- package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +57 -5
- package/plugins/lisa-copilot/scripts/design-source-gate.mjs +11 -2
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +29 -1
- package/plugins/lisa-copilot/skills/lisa-pull-request-review/SKILL.md +50 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/derived-branch-plan-reference.mdc +18 -2
- package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +57 -5
- package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +3 -1
- package/plugins/lisa-cursor/scripts/design-source-gate.mjs +11 -2
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +29 -1
- package/plugins/lisa-cursor/skills/lisa-pull-request-review/SKILL.md +50 -2
- 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/rules/eager/falsifiable-checks.md +3 -1
- package/plugins/src/base/rules/reference/derived-branch-plan.md +18 -2
- package/plugins/src/base/rules/reference/falsifiable-checks.md +57 -5
- package/plugins/src/base/scripts/design-source-gate.mjs +11 -2
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +29 -1
- package/plugins/src/base/skills/lisa-pull-request-review/SKILL.md +50 -2
- package/typescript/copy-overwrite/ast-grep/rules/no-missing-fs-extra-namespace-member-js.yml +71 -0
- package/typescript/copy-overwrite/ast-grep/rules/no-missing-fs-extra-namespace-member.yml +90 -0
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +402 -5
- package/typescript/create-only/.github/required-checks.json +13 -3
- package/typescript/package-lisa/package.lisa.json +2 -1
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
"A transcription expires after 90 days, because a ruleset can be edited with no signal in this repository. The shipped `.github/workflows/required-checks-drift.yml` runs `--remote` weekly to catch that; `--remote` reads the ruleset live and so answers even when the cache is untrusted.",
|
|
8
8
|
"Lisa's quality.yml runs the offline arm on every pull request. This seed ships `\"enforcement\": \"warn\"`, which downgrades findings AND the NOT-CHECKED refusal to reports so a fresh install does not go red on arrival. Delete the key once you have transcribed the list — then it blocks.",
|
|
9
9
|
"`skip_jobs` is matched as an exact comma-delimited token and GitHub Actions expression syntax has no string-replace, so write the list with no spaces: `skip_jobs: 'a,b'`, never `skip_jobs: 'a, b'`. A spaced token matches nothing and the job runs — reported as `whitespace_in_skip_token`.",
|
|
10
|
-
"`_example_required_contexts` below is a STARTING POINT FOR TYPING, never read by the guard. Verify every line against your own ruleset before promoting any of it."
|
|
10
|
+
"`_example_required_contexts` below is a STARTING POINT FOR TYPING, never read by the guard. Verify every line against your own ruleset before promoting any of it.",
|
|
11
|
+
"THE FAMILY: required-and-red is loud; required-and-vacuous is not; advisory-and-stale is invisible. All three are one gate reporting satisfied without proving anything. `required_contexts` + `skip_job_declarations` above cover the SKIPPED variant; `evidence_bearing_checks` below covers the VACUOUS one.",
|
|
12
|
+
"VACUOUS, measured (CodySwannGT/lisa#2497): a required `CodeRabbit` context posted `success` with the description `Review rate limited`, having reviewed nothing, on two security-relevant PRs that then merged and shipped. `gh pr checks` prints `pass` for that exactly as it does for a real review — only the DESCRIPTION tells them apart.",
|
|
13
|
+
"Run it per PR: `npm run check:vacuous-required-checks -- --pr=1234` (or `node scripts/check-skipped-required-checks.mjs --pr=1234`). It reads `gh pr checks --json name,state,bucket,description`, which is the only route that carries the description for a legacy commit status like CodeRabbit's.",
|
|
14
|
+
"`evidence_bearing_checks` names the checks whose GREEN is supposed to mean something reviewed the code. Use `{}` to accept the shipped description vocabulary, or add `proof` / `no_work` arrays to extend it — extensions ADD to the defaults, they do not replace them. Undeclared checks are never examined, because most CI jobs ship an empty description and flagging them all would bury the one finding that matters.",
|
|
15
|
+
"This arm REPORTS AND NEVER BLOCKS, in every enforcement mode. A review bot can go hollow because an org-wide SPENDING CAP was hit, and a gate that reddens every PR on a billing state is worse than the one it criticises. What it changes is what you may CLAIM: a PR carrying a `vacuous_required_check` finding has not been shown to be reviewed, so do not record it as reviewed."
|
|
11
16
|
],
|
|
12
17
|
"enforcement": "warn",
|
|
13
18
|
"ruleset": {
|
|
@@ -54,5 +59,8 @@
|
|
|
54
59
|
"ruleset_required": false,
|
|
55
60
|
"reason": "DAST needs a deployed target and is run against staging, not on every PR. Not a required context."
|
|
56
61
|
}
|
|
62
|
+
},
|
|
63
|
+
"evidence_bearing_checks": {
|
|
64
|
+
"CodeRabbit": {}
|
|
57
65
|
}
|
|
58
66
|
}
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
"A transcription expires after 90 days, because a ruleset can be edited with no signal in this repository. The shipped `.github/workflows/required-checks-drift.yml` runs `--remote` weekly to catch that; `--remote` reads the ruleset live and so answers even when the cache is untrusted.",
|
|
8
8
|
"Lisa's quality.yml runs the offline arm on every pull request. This seed ships `\"enforcement\": \"warn\"`, which downgrades findings AND the NOT-CHECKED refusal to reports so a fresh install does not go red on arrival. Delete the key once you have transcribed the list — then it blocks.",
|
|
9
9
|
"`skip_jobs` is matched as an exact comma-delimited token and GitHub Actions expression syntax has no string-replace, so write the list with no spaces: `skip_jobs: 'a,b'`, never `skip_jobs: 'a, b'`. A spaced token matches nothing and the job runs — reported as `whitespace_in_skip_token`.",
|
|
10
|
-
"`_example_required_contexts` below is a STARTING POINT FOR TYPING, never read by the guard. Verify every line against your own ruleset before promoting any of it."
|
|
10
|
+
"`_example_required_contexts` below is a STARTING POINT FOR TYPING, never read by the guard. Verify every line against your own ruleset before promoting any of it.",
|
|
11
|
+
"THE FAMILY: required-and-red is loud; required-and-vacuous is not; advisory-and-stale is invisible. All three are one gate reporting satisfied without proving anything. `required_contexts` + `skip_job_declarations` above cover the SKIPPED variant; `evidence_bearing_checks` below covers the VACUOUS one.",
|
|
12
|
+
"VACUOUS, measured (CodySwannGT/lisa#2497): a required `CodeRabbit` context posted `success` with the description `Review rate limited`, having reviewed nothing, on two security-relevant PRs that then merged and shipped. `gh pr checks` prints `pass` for that exactly as it does for a real review — only the DESCRIPTION tells them apart.",
|
|
13
|
+
"Run it per PR: `npm run check:vacuous-required-checks -- --pr=1234` (or `node scripts/check-skipped-required-checks.mjs --pr=1234`). It reads `gh pr checks --json name,state,bucket,description`, which is the only route that carries the description for a legacy commit status like CodeRabbit's.",
|
|
14
|
+
"`evidence_bearing_checks` names the checks whose GREEN is supposed to mean something reviewed the code. Use `{}` to accept the shipped description vocabulary, or add `proof` / `no_work` arrays to extend it — extensions ADD to the defaults, they do not replace them. Undeclared checks are never examined, because most CI jobs ship an empty description and flagging them all would bury the one finding that matters.",
|
|
15
|
+
"This arm REPORTS AND NEVER BLOCKS, in every enforcement mode. A review bot can go hollow because an org-wide SPENDING CAP was hit, and a gate that reddens every PR on a billing state is worse than the one it criticises. What it changes is what you may CLAIM: a PR carrying a `vacuous_required_check` finding has not been shown to be reviewed, so do not record it as reviewed."
|
|
11
16
|
],
|
|
12
17
|
"enforcement": "warn",
|
|
13
18
|
"ruleset": {
|
|
@@ -53,5 +58,8 @@
|
|
|
53
58
|
"ruleset_required": false,
|
|
54
59
|
"reason": "The shipped ci.yml runs ZAP as its own `zap` job against a deployed target after quality passes, not inside the quality callee. Not a required context."
|
|
55
60
|
}
|
|
61
|
+
},
|
|
62
|
+
"evidence_bearing_checks": {
|
|
63
|
+
"CodeRabbit": {}
|
|
56
64
|
}
|
|
57
65
|
}
|
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"name": "@codyswann/lisa",
|
|
123
|
-
"version": "3.
|
|
123
|
+
"version": "3.8.0",
|
|
124
124
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
125
125
|
"main": "dist/index.js",
|
|
126
126
|
"exports": {
|
|
@@ -291,7 +291,22 @@ Delegate to the `pull-request-review` skill with the PR number. It owns the whol
|
|
|
291
291
|
comment cycle: fetch every unresolved human + bot thread (with resolution state via
|
|
292
292
|
GraphQL), implement valid feedback (commit + push), reply to invalid feedback, and
|
|
293
293
|
resolve every thread via `resolveReviewThread` so the branch-protection
|
|
294
|
-
thread-resolution gate clears.
|
|
294
|
+
thread-resolution gate clears.
|
|
295
|
+
|
|
296
|
+
**A green review check is not proof a review happened.** That skill's Step 1b
|
|
297
|
+
returns a `reviewed` / `NOT REVIEWED` verdict — record it, and repeat it in this
|
|
298
|
+
skill's final report. Measured (CodySwannGT/lisa#2497): `CodeRabbit` was a
|
|
299
|
+
*required* context and posted `success — "Review rate limited"` on #2483 and
|
|
300
|
+
#2484, so branch protection recorded a satisfied review gate for two
|
|
301
|
+
security-relevant PRs nothing had read; both merged and shipped in `v3.5.1`.
|
|
302
|
+
|
|
303
|
+
`NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
|
|
304
|
+
it as a failing check, and do not try to force the bot to re-run. A hollow
|
|
305
|
+
review check is usually an org-wide vendor spending cap, which is a billing
|
|
306
|
+
matter no amount of driving will clear, and whether such a check belongs in the
|
|
307
|
+
required set at all is an open owner decision. It is a **reporting** obligation:
|
|
308
|
+
the PR merged unreviewed, and the report has to say so instead of implying a
|
|
309
|
+
review it did not get. If that skill needs to push a commit, leave
|
|
295
310
|
auto-merge armed (section 1); when it returns, re-read `headRefOid` and reset
|
|
296
311
|
`verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here
|
|
297
312
|
— it is the single source of truth for review-thread handling.
|
|
@@ -452,3 +467,16 @@ At every terminal state, release the babysitter lease
|
|
|
452
467
|
(`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
|
|
453
468
|
auto-fix workflow can take over as fixer of last resort if the branch goes
|
|
454
469
|
red later with nobody driving it.
|
|
470
|
+
|
|
471
|
+
**Every terminal report carries the step-(d) review verdict**, including a
|
|
472
|
+
successful `MERGED`. "Merged, all checks green" is exactly the sentence that hid
|
|
473
|
+
#2483 and #2484: both were green, both were merged, and neither had been read by
|
|
474
|
+
anything. Green means *no gate objected*; it does not mean *something looked*.
|
|
475
|
+
So state the verdict alongside the outcome:
|
|
476
|
+
|
|
477
|
+
- `MERGED — reviewed (CodeRabbit "Review approved")`
|
|
478
|
+
- `MERGED — NOT REVIEWED: CodeRabbit posted success but "Review rate limited"`
|
|
479
|
+
|
|
480
|
+
This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
|
|
481
|
+
turn a merged PR into a blocked one, and it must never be used to withhold a
|
|
482
|
+
merge — it changes what the record says, not what the loop does.
|
|
@@ -37,8 +37,49 @@ gh api graphql -f query='
|
|
|
37
37
|
-F owner=<owner> -F repo=<repo> -F pr=<pr>
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Keep only threads where `isResolved == false`.
|
|
41
|
-
|
|
40
|
+
Keep only threads where `isResolved == false`.
|
|
41
|
+
|
|
42
|
+
**If there are none, you have not yet learned anything — run Step 1b before
|
|
43
|
+
concluding.** Zero unresolved threads has two completely different causes, and
|
|
44
|
+
this query cannot tell them apart: *a reviewer looked and found nothing*, or
|
|
45
|
+
*nothing ever looked*. Reporting the first when it was the second is the defect
|
|
46
|
+
in CodySwannGT/lisa#2497.
|
|
47
|
+
|
|
48
|
+
## Step 1b: Did any review actually happen? (required before reporting)
|
|
49
|
+
|
|
50
|
+
A required review check can post `success` having reviewed nothing. Measured on
|
|
51
|
+
PRs #2483 and #2484: `CodeRabbit` reported `success — "Review rate limited"`,
|
|
52
|
+
zero reviews, and both merged on that green carrying security-relevant changes.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gh pr checks <pr> --json name,state,bucket,description \
|
|
56
|
+
--jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
|
|
57
|
+
```
|
|
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.
|
|
64
|
+
|
|
65
|
+
Where the project ships Lisa's guard, prefer its machine-readable form, which
|
|
66
|
+
also says whether the check is ruleset-required (so whether branch protection
|
|
67
|
+
recorded a satisfied review gate for a review that did not happen):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node scripts/check-skipped-required-checks.mjs --pr=<pr> --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
It reports and never fails — a hollow check is often a vendor spending cap, not
|
|
74
|
+
a repository defect, and it is not this skill's call to block on one.
|
|
75
|
+
|
|
76
|
+
Carry the finding into your Step 4 report. **Do not write "no unresolved review
|
|
77
|
+
threads" on its own** — it is true of an unreviewed PR too. Say which you
|
|
78
|
+
observed:
|
|
79
|
+
|
|
80
|
+
- `reviewed — CodeRabbit "Review approved", 0 unresolved threads`
|
|
81
|
+
- `NOT REVIEWED — CodeRabbit success but "Review rate limited" (vacuous); 0 threads means nobody looked`
|
|
82
|
+
- `NOT REVIEWED — no review check reported on this PR at all`
|
|
42
83
|
|
|
43
84
|
## Step 2: Triage and act on each unresolved thread
|
|
44
85
|
|
|
@@ -75,6 +116,13 @@ judgment. This skill resolves **threads**; it does not dismiss review-decision
|
|
|
75
116
|
gates (`CHANGES_REQUESTED`) or merge the PR — the caller (`drive-pr-to-merge`)
|
|
76
117
|
owns those.
|
|
77
118
|
|
|
119
|
+
**Open the report with the Step 1b verdict, before the thread counts.** A thread
|
|
120
|
+
summary describes what was done about review findings; it says nothing about
|
|
121
|
+
whether a review produced any. State `reviewed` or `NOT REVIEWED (<why>)` first,
|
|
122
|
+
then the counts. A caller that records "reviews addressed" in evidence must
|
|
123
|
+
carry that verdict through verbatim — a PR whose only review check was vacuous
|
|
124
|
+
has not been reviewed, no matter how clean its thread list is.
|
|
125
|
+
|
|
78
126
|
## Composition
|
|
79
127
|
|
|
80
128
|
- **Standalone**: `/lisa:pull-request:review <pr>`.
|
|
@@ -18,7 +18,9 @@ Each has been observed in real runs; each reported success while asserting nothi
|
|
|
18
18
|
## Mandatory
|
|
19
19
|
|
|
20
20
|
- **Falsify before reporting.** No clean result is reportable until the check has been shown to fail on a deliberate break. **"Mentally reverting" does not count** — reasoning that the assertion *would* fail is precisely the step that lets a non-functional guard ship, because the author already believes it is load-bearing. Run the break.
|
|
21
|
-
- **
|
|
21
|
+
- **Mutation-prove the guard, then read the cardinality.** Neuter the protection **in production code**, run the **whole suite**, count the failures and **read their names**, then revert. **Zero** ⇒ the guard is inert (one pinning a single field shipped with all 50 tests green while the regression walked through). **Many, unrelated** ⇒ over-broad; its next failure will not name the cause. **Exactly one, or several all named for the same regression** ⇒ load-bearing and correctly scoped. A **zero is robust to contention** — load only ever adds failures — so only the non-zero counts need a quiet machine.
|
|
22
|
+
- **Never scope the probe by filename, and never infer absent coverage from a grep.** Suites split at the line cap, so a protection's tests often sit in a sibling file: a filename-scoped probe reported cardinality 0 on a live security fix that a whole-suite run showed failing 4 tests, every one named for it.
|
|
23
|
+
- **Assert that no copy is wrong, not that some copy is right.** An existential assertion over duplicated content is satisfied by the correct copy and cannot see the broken one — rewriting one suite's assertions to universal negatives took the same mutation from **0 to 18** failures. Enumerate the property (*every* marker uses an em-dash), never the one known-bad spelling.
|
|
22
24
|
- **Reconstruct a red state; never narrate one you did not run.** Evidence describing a failing or dirty state must come from a run you actually observed — an evidence file once claimed "working tree clean" while listing two untracked scripts in the same file. To observe a state that no longer exists, check that commit out in a **throwaway detached-HEAD worktree** and run it there. Writing down what the output would have said is fabrication, however confident the reasoning.
|
|
23
25
|
- **Say how it was falsified.** "0 findings" alone is not a result; state what you broke and that the check caught it. A gate whose falsification is untested must be reported as *unvalidated*, not as passing.
|
|
24
26
|
- **Prefer structural over textual checks.** Parse the AST/structure instead of matching source text: text matching cannot distinguish a field from a comment, an alias, or a nested occurrence, and it produces false positives that mask the real ones.
|
|
@@ -85,9 +85,25 @@ Stated plainly, because the session that produced this contract exists to make e
|
|
|
85
85
|
|
|
86
86
|
This is **not** an executable control. `ready-role-filing`'s sibling guard is a hook that exits 2 and physically refuses a tool call; nothing here refuses anything. What enforces this contract is a validator agent reading `SKILL.md` and choosing to apply it — the same rung of the ladder that failed to bind 13 filings out of 13.
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
The regression suite has two halves. **Why they bind differently is `falsifiable-checks`' subject, not this rule's** — read the quantifier guidance there. What belongs here is the measurement, because this contract happens to be where it was taken.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
- `runtime-behavior-change-decidability.test.ts` asserts the wording is *present* across all six generated skill roots. It pins the contract against silent deletion or a lossy regeneration, and proves nothing about any particular copy.
|
|
91
|
+
- `runtime-behavior-change-consistency.test.ts` asserts no copy says the *wrong* thing, over every occurrence in the corpus: every declaration marker uses the em-dash discriminator, every absence-derivation site resolves to *underivable* within its own sentence, and no phrasing maps an absent section to `false` or hands authority back to the caller.
|
|
92
|
+
|
|
93
|
+
This document states its rule **twice** — once in gate S8, once in the execution step — which is what made the gap observable. `lisa-linear-validate-issue` once spelled the discriminator with an ASCII hyphen in its execution step while its own S8 used the em-dash, and 192 assertions stayed green through that mutation twice running. The fix pinned the execution step and left S8's copy bare. Mutating each site separately, whole suite, before and after the second half landed:
|
|
94
|
+
|
|
95
|
+
| mutated site | before | after |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| execution step reads an absent section as `false` | 18 | 36 |
|
|
98
|
+
| **gate S8's prose reads an absent section as `false`** | **0** | **18** |
|
|
99
|
+
| writers stop rendering the section | 36 | 54 |
|
|
100
|
+
| a caller's assertion beats the stored declaration | 18 | 36 |
|
|
101
|
+
|
|
102
|
+
Zero out of 11,270 — same file, same rule, same commit, one site pinned and its twin bare.
|
|
103
|
+
|
|
104
|
+
(The wrong spelling is deliberately not written out above. This section sits inside the corpus the consistency suite scans, and the first draft of it quoted the defect as an illustration and failed its own check.)
|
|
105
|
+
|
|
106
|
+
**The executable half of this contract is nothing.** Both suites read markdown. `SKILL.md` *is* the validators' execution substrate — there is no compiled artifact underneath to attach a hook to, so there is no function to call and no return value to assert. What they prove is that the instruction the agent reads is internally consistent; what neither can prove is that the agent then obeys it. Absence-as-`false` was undecidable, is now *decidable* and *non-contradictory*, and both of those are strictly weaker than *enforced*. Treat a gate verdict resting on this contract as evidence, not proof, until a deterministic checker exists to read the declaration off a live item.
|
|
91
107
|
|
|
92
108
|
This costs nothing in derivation difficulty. A single-environment project — Lisa itself is one, `deploy.branches: { production: main }` — recomputes every applicable item to the same branch. The gate was never hard to satisfy; it was impossible to *audit*, and those are different problems with different fixes. Weakening S19 would have fixed neither.
|
|
93
109
|
|
|
@@ -63,17 +63,69 @@ A clean result is a statement about what the check can perceive, not about the c
|
|
|
63
63
|
|
|
64
64
|
1. Author the check.
|
|
65
65
|
2. Deliberately break the guarded property — specifically, introduce **the exact regression the guard exists to prevent**, not a nearby or convenient break.
|
|
66
|
-
3. Confirm the check **fails
|
|
66
|
+
3. Confirm the check **fails and names the right file/line**, then read the failure count and the failing test *names* per the yardstick below.
|
|
67
67
|
4. Restore, and confirm green again.
|
|
68
68
|
5. Report the falsification alongside the result.
|
|
69
69
|
|
|
70
70
|
### The failure count is part of the evidence
|
|
71
71
|
|
|
72
|
-
Step 3's cardinality is not pedantry; each deviation names a distinct defect.
|
|
72
|
+
Step 3's cardinality is not pedantry; each deviation names a distinct defect. Run it as four steps, in this order:
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
1. **Neuter the protection in production code** — delete the branch, drop the token from the enum, remove the check. Editing a *test* proves nothing about the guard; it only proves the test file is loaded.
|
|
75
|
+
2. **Run the whole suite.** Never a single test file, and never a path filter.
|
|
76
|
+
3. **Count the failures and read their names.** The count alone is ambiguous; the names are what separate "several failures of one regression" from "several unrelated failures."
|
|
77
|
+
4. **Revert, and confirm green again.**
|
|
78
|
+
|
|
79
|
+
Then read the result:
|
|
80
|
+
|
|
81
|
+
- **Zero failures** — the guard is inert. It was authored, it is green, and it asserts nothing about the property it names. Two real instances: a guard pinned one field of a structure and the regression it was written to stop walked through it with all fifty tests green; and removing either `conflicting` or `unreadable` from `design-source-gate.mjs`'s `VIOLATION_STATUSES` failed **0 of 38 tests** while flipping the gate's verdict from FAIL to PASS.
|
|
82
|
+
- **Many failures, unrelated to each other** — the break is too coarse, the guard is over-broad, or unrelated tests share a fixture. Any of the three means the guard's next real failure will not tell the reader what broke, which is most of a guard's value.
|
|
83
|
+
- **Exactly one failure, or several all named for the same regression** — the guard localizes. Both readings are load-bearing and correctly scoped. Removing the two-token `--config-env` check failed **3** tests and pinning an index failed **5**; every failing name in both runs described the removed behaviour, so both guards were correct. This is a reading exercise, not arithmetic — "more than one is bad" is the wrong rule.
|
|
84
|
+
|
|
85
|
+
### A zero is robust to contention; a positive count is not
|
|
86
|
+
|
|
87
|
+
The obvious objection to any cardinality measured on a shared machine — *"your probe ran on a loaded box, so how do you trust the number?"* — has an asymmetric answer, and the asymmetry is worth stating because it decides which numbers you must re-measure.
|
|
88
|
+
|
|
89
|
+
**Load can only add failures, never remove them.** A test that passes under contention would also have passed on a quiet machine; contention causes timeouts and lock races, which turn green into red, never red into green. So:
|
|
90
|
+
|
|
91
|
+
- **A zero stands regardless of what else was running.** If the whole suite reports zero failures attributable to your mutation on a loaded box, a quiet box cannot produce fewer. `0 of 11,270` is `0 of 11,270`.
|
|
92
|
+
- **A non-zero count needs a quiet machine**, because contention inflates it. Failures that are really flakes get miscounted as the guard's, which reads as "over-broad" and gets a correctly-scoped guard deleted.
|
|
93
|
+
|
|
94
|
+
Practical consequence: an inert-guard finding is safe to report from a busy machine, while the "exactly one, or several all named alike" reading is only trustworthy once you have separated your mutation's failures from the load-flake population — which is what reading the *names* is for.
|
|
95
|
+
|
|
96
|
+
### Two reasons a real protection reports zero
|
|
97
|
+
|
|
98
|
+
A cardinality of zero has two distinct innocent causes, and they need different fixes. Rule out both before concluding a guard is inert:
|
|
99
|
+
|
|
100
|
+
1. **Wrong probe scope** — the probe was scoped to a filename or path and is blind to a split suite. Fix: run the whole suite.
|
|
101
|
+
2. **Wrong quantifier** — the assertions are existential over a corpus that contains duplicates, so a correct copy satisfies them on the broken copy's behalf. Fix: assert the universal negative.
|
|
102
|
+
|
|
103
|
+
### Never scope the probe by filename
|
|
104
|
+
|
|
105
|
+
**A cardinality probe scoped to a filename reports zero when a suite splits. Follow the protection, not the path.**
|
|
106
|
+
|
|
107
|
+
Observed live: a reviewer neutered `before_end_of_options` (an end-of-options security fix), grepped `block-direct-issue-create.test.ts` for coverage, found none, and reported **cardinality 0 — the fix ships untested**. It was relayed as fact and used to question the author's discipline. Re-measuring against the whole suite showed neutering the protection fails **4** tests, each named for it: the suite had split at the 300-effective-line lint ceiling, and the declaration arm — including all five end-of-options tests — had moved to `block-direct-issue-create-declarations.test.ts`.
|
|
108
|
+
|
|
109
|
+
The false negative fires exactly where this repo is most likely to split a file: at the line cap, on the suites that have grown *because* someone added protections to them. So:
|
|
110
|
+
|
|
111
|
+
- Never scope the probe by filename or path filter.
|
|
112
|
+
- Never infer absence of coverage from a grep. A grep locates text; it cannot enumerate what a mutation breaks.
|
|
113
|
+
- A prior cardinality-0 finding measured with a path-scoped probe must be **re-measured** with a whole-suite probe before anyone acts on it.
|
|
114
|
+
|
|
115
|
+
### Assert that no copy is wrong, not that some copy is right
|
|
116
|
+
|
|
117
|
+
A test asserting that the **correct** marker is *present* passes trivially when the corpus happens to state it twice — the second statement satisfies the assertion no matter what the first one says, so the test survives the exact edit it exists to catch. The falsifiable form asserts the **wrong** spelling is *absent*: it has no second copy to fall back on, and it goes red the moment the wrong form reappears.
|
|
118
|
+
|
|
119
|
+
Stated as quantifiers, which is the general form:
|
|
120
|
+
|
|
121
|
+
- **Existential** — *"some copy says the right thing"* — is trivially satisfied by any other copy. A wrong copy is undetectable.
|
|
122
|
+
- **Universal negative** — *"no copy says the wrong thing"* — cannot be satisfied by a correct copy elsewhere.
|
|
123
|
+
|
|
124
|
+
Observed: a suite over a corpus containing two copies of the same rule returned **cardinality 0** on a real mutation because every assertion was existential. Rewriting the assertions to universal negatives — same code, same corpus, only the quantifier changed — took the same mutation from **0 to 18** failures. The defect it had been hiding: one PR pinned the *execution-step* copy of a rule and left the *gate* copy entirely unpinned, so reverting the gate's "absent section means underivable — never `false`" back to "means `false`" failed **0 of 11,270 tests** while the validator's documented gate contradicted its own execution step.
|
|
125
|
+
|
|
126
|
+
### Enumerate the property, not the known-bad instance
|
|
127
|
+
|
|
128
|
+
A universal negative against one hard-coded wrong spelling still only sees that spelling. Assert the **property** instead: *every* marker uses an em-dash catches the en-dash and the missing dash too, which `not.toContain("<one wrong spelling>")` never would. When that generalisation was made, it went red immediately on a wrong marker its own author had just written into rule prose — the discipline catching the person applying it, in the same run.
|
|
77
129
|
|
|
78
130
|
### Never narrate a red state you did not run
|
|
79
131
|
|
|
@@ -294,8 +294,17 @@ export function classifyDesignSource(file, config) {
|
|
|
294
294
|
return { ...base, status: "undeclared" };
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
/**
|
|
298
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Per-file statuses that fail the gate.
|
|
299
|
+
*
|
|
300
|
+
* Exported so the suite can pin the set itself, not just its members' behavior.
|
|
301
|
+
* Classifying a file `unreadable` is not a guard; *failing the change* because
|
|
302
|
+
* of it is. Issue #2492 measured the difference: with only classification
|
|
303
|
+
* asserted, deleting `conflicting` or `unreadable` from this list flipped the
|
|
304
|
+
* verdict to PASS with the whole suite still green. The pinning test now fails
|
|
305
|
+
* on any edit to this set — a removal and an addition alike.
|
|
306
|
+
*/
|
|
307
|
+
export const VIOLATION_STATUSES = new Set([
|
|
299
308
|
"undeclared",
|
|
300
309
|
"malformed",
|
|
301
310
|
"conflicting",
|
|
@@ -291,7 +291,22 @@ Delegate to the `pull-request-review` skill with the PR number. It owns the whol
|
|
|
291
291
|
comment cycle: fetch every unresolved human + bot thread (with resolution state via
|
|
292
292
|
GraphQL), implement valid feedback (commit + push), reply to invalid feedback, and
|
|
293
293
|
resolve every thread via `resolveReviewThread` so the branch-protection
|
|
294
|
-
thread-resolution gate clears.
|
|
294
|
+
thread-resolution gate clears.
|
|
295
|
+
|
|
296
|
+
**A green review check is not proof a review happened.** That skill's Step 1b
|
|
297
|
+
returns a `reviewed` / `NOT REVIEWED` verdict — record it, and repeat it in this
|
|
298
|
+
skill's final report. Measured (CodySwannGT/lisa#2497): `CodeRabbit` was a
|
|
299
|
+
*required* context and posted `success — "Review rate limited"` on #2483 and
|
|
300
|
+
#2484, so branch protection recorded a satisfied review gate for two
|
|
301
|
+
security-relevant PRs nothing had read; both merged and shipped in `v3.5.1`.
|
|
302
|
+
|
|
303
|
+
`NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
|
|
304
|
+
it as a failing check, and do not try to force the bot to re-run. A hollow
|
|
305
|
+
review check is usually an org-wide vendor spending cap, which is a billing
|
|
306
|
+
matter no amount of driving will clear, and whether such a check belongs in the
|
|
307
|
+
required set at all is an open owner decision. It is a **reporting** obligation:
|
|
308
|
+
the PR merged unreviewed, and the report has to say so instead of implying a
|
|
309
|
+
review it did not get. If that skill needs to push a commit, leave
|
|
295
310
|
auto-merge armed (section 1); when it returns, re-read `headRefOid` and reset
|
|
296
311
|
`verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here
|
|
297
312
|
— it is the single source of truth for review-thread handling.
|
|
@@ -452,3 +467,16 @@ At every terminal state, release the babysitter lease
|
|
|
452
467
|
(`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
|
|
453
468
|
auto-fix workflow can take over as fixer of last resort if the branch goes
|
|
454
469
|
red later with nobody driving it.
|
|
470
|
+
|
|
471
|
+
**Every terminal report carries the step-(d) review verdict**, including a
|
|
472
|
+
successful `MERGED`. "Merged, all checks green" is exactly the sentence that hid
|
|
473
|
+
#2483 and #2484: both were green, both were merged, and neither had been read by
|
|
474
|
+
anything. Green means *no gate objected*; it does not mean *something looked*.
|
|
475
|
+
So state the verdict alongside the outcome:
|
|
476
|
+
|
|
477
|
+
- `MERGED — reviewed (CodeRabbit "Review approved")`
|
|
478
|
+
- `MERGED — NOT REVIEWED: CodeRabbit posted success but "Review rate limited"`
|
|
479
|
+
|
|
480
|
+
This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
|
|
481
|
+
turn a merged PR into a blocked one, and it must never be used to withhold a
|
|
482
|
+
merge — it changes what the record says, not what the loop does.
|
|
@@ -37,8 +37,49 @@ gh api graphql -f query='
|
|
|
37
37
|
-F owner=<owner> -F repo=<repo> -F pr=<pr>
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Keep only threads where `isResolved == false`.
|
|
41
|
-
|
|
40
|
+
Keep only threads where `isResolved == false`.
|
|
41
|
+
|
|
42
|
+
**If there are none, you have not yet learned anything — run Step 1b before
|
|
43
|
+
concluding.** Zero unresolved threads has two completely different causes, and
|
|
44
|
+
this query cannot tell them apart: *a reviewer looked and found nothing*, or
|
|
45
|
+
*nothing ever looked*. Reporting the first when it was the second is the defect
|
|
46
|
+
in CodySwannGT/lisa#2497.
|
|
47
|
+
|
|
48
|
+
## Step 1b: Did any review actually happen? (required before reporting)
|
|
49
|
+
|
|
50
|
+
A required review check can post `success` having reviewed nothing. Measured on
|
|
51
|
+
PRs #2483 and #2484: `CodeRabbit` reported `success — "Review rate limited"`,
|
|
52
|
+
zero reviews, and both merged on that green carrying security-relevant changes.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gh pr checks <pr> --json name,state,bucket,description \
|
|
56
|
+
--jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
|
|
57
|
+
```
|
|
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.
|
|
64
|
+
|
|
65
|
+
Where the project ships Lisa's guard, prefer its machine-readable form, which
|
|
66
|
+
also says whether the check is ruleset-required (so whether branch protection
|
|
67
|
+
recorded a satisfied review gate for a review that did not happen):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node scripts/check-skipped-required-checks.mjs --pr=<pr> --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
It reports and never fails — a hollow check is often a vendor spending cap, not
|
|
74
|
+
a repository defect, and it is not this skill's call to block on one.
|
|
75
|
+
|
|
76
|
+
Carry the finding into your Step 4 report. **Do not write "no unresolved review
|
|
77
|
+
threads" on its own** — it is true of an unreviewed PR too. Say which you
|
|
78
|
+
observed:
|
|
79
|
+
|
|
80
|
+
- `reviewed — CodeRabbit "Review approved", 0 unresolved threads`
|
|
81
|
+
- `NOT REVIEWED — CodeRabbit success but "Review rate limited" (vacuous); 0 threads means nobody looked`
|
|
82
|
+
- `NOT REVIEWED — no review check reported on this PR at all`
|
|
42
83
|
|
|
43
84
|
## Step 2: Triage and act on each unresolved thread
|
|
44
85
|
|
|
@@ -75,6 +116,13 @@ judgment. This skill resolves **threads**; it does not dismiss review-decision
|
|
|
75
116
|
gates (`CHANGES_REQUESTED`) or merge the PR — the caller (`drive-pr-to-merge`)
|
|
76
117
|
owns those.
|
|
77
118
|
|
|
119
|
+
**Open the report with the Step 1b verdict, before the thread counts.** A thread
|
|
120
|
+
summary describes what was done about review findings; it says nothing about
|
|
121
|
+
whether a review produced any. State `reviewed` or `NOT REVIEWED (<why>)` first,
|
|
122
|
+
then the counts. A caller that records "reviews addressed" in evidence must
|
|
123
|
+
carry that verdict through verbatim — a PR whose only review check was vacuous
|
|
124
|
+
has not been reviewed, no matter how clean its thread list is.
|
|
125
|
+
|
|
78
126
|
## Composition
|
|
79
127
|
|
|
80
128
|
- **Standalone**: `/lisa:pull-request:review <pr>`.
|
|
@@ -294,8 +294,17 @@ export function classifyDesignSource(file, config) {
|
|
|
294
294
|
return { ...base, status: "undeclared" };
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
/**
|
|
298
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Per-file statuses that fail the gate.
|
|
299
|
+
*
|
|
300
|
+
* Exported so the suite can pin the set itself, not just its members' behavior.
|
|
301
|
+
* Classifying a file `unreadable` is not a guard; *failing the change* because
|
|
302
|
+
* of it is. Issue #2492 measured the difference: with only classification
|
|
303
|
+
* asserted, deleting `conflicting` or `unreadable` from this list flipped the
|
|
304
|
+
* verdict to PASS with the whole suite still green. The pinning test now fails
|
|
305
|
+
* on any edit to this set — a removal and an addition alike.
|
|
306
|
+
*/
|
|
307
|
+
export const VIOLATION_STATUSES = new Set([
|
|
299
308
|
"undeclared",
|
|
300
309
|
"malformed",
|
|
301
310
|
"conflicting",
|
|
@@ -291,7 +291,22 @@ Delegate to the `pull-request-review` skill with the PR number. It owns the whol
|
|
|
291
291
|
comment cycle: fetch every unresolved human + bot thread (with resolution state via
|
|
292
292
|
GraphQL), implement valid feedback (commit + push), reply to invalid feedback, and
|
|
293
293
|
resolve every thread via `resolveReviewThread` so the branch-protection
|
|
294
|
-
thread-resolution gate clears.
|
|
294
|
+
thread-resolution gate clears.
|
|
295
|
+
|
|
296
|
+
**A green review check is not proof a review happened.** That skill's Step 1b
|
|
297
|
+
returns a `reviewed` / `NOT REVIEWED` verdict — record it, and repeat it in this
|
|
298
|
+
skill's final report. Measured (CodySwannGT/lisa#2497): `CodeRabbit` was a
|
|
299
|
+
*required* context and posted `success — "Review rate limited"` on #2483 and
|
|
300
|
+
#2484, so branch protection recorded a satisfied review gate for two
|
|
301
|
+
security-relevant PRs nothing had read; both merged and shipped in `v3.5.1`.
|
|
302
|
+
|
|
303
|
+
`NOT REVIEWED` is **not a blocker** — do not hold the merge on it, do not treat
|
|
304
|
+
it as a failing check, and do not try to force the bot to re-run. A hollow
|
|
305
|
+
review check is usually an org-wide vendor spending cap, which is a billing
|
|
306
|
+
matter no amount of driving will clear, and whether such a check belongs in the
|
|
307
|
+
required set at all is an open owner decision. It is a **reporting** obligation:
|
|
308
|
+
the PR merged unreviewed, and the report has to say so instead of implying a
|
|
309
|
+
review it did not get. If that skill needs to push a commit, leave
|
|
295
310
|
auto-merge armed (section 1); when it returns, re-read `headRefOid` and reset
|
|
296
311
|
`verify_commit` to the returned/pushed head, then continue. Do not re-implement review handling here
|
|
297
312
|
— it is the single source of truth for review-thread handling.
|
|
@@ -452,3 +467,16 @@ At every terminal state, release the babysitter lease
|
|
|
452
467
|
(`gh pr edit <pr> --remove-label "lisa:babysitter-on-duty"`) so the CI
|
|
453
468
|
auto-fix workflow can take over as fixer of last resort if the branch goes
|
|
454
469
|
red later with nobody driving it.
|
|
470
|
+
|
|
471
|
+
**Every terminal report carries the step-(d) review verdict**, including a
|
|
472
|
+
successful `MERGED`. "Merged, all checks green" is exactly the sentence that hid
|
|
473
|
+
#2483 and #2484: both were green, both were merged, and neither had been read by
|
|
474
|
+
anything. Green means *no gate objected*; it does not mean *something looked*.
|
|
475
|
+
So state the verdict alongside the outcome:
|
|
476
|
+
|
|
477
|
+
- `MERGED — reviewed (CodeRabbit "Review approved")`
|
|
478
|
+
- `MERGED — NOT REVIEWED: CodeRabbit posted success but "Review rate limited"`
|
|
479
|
+
|
|
480
|
+
This is reporting, never a terminal state of its own. `NOT REVIEWED` does not
|
|
481
|
+
turn a merged PR into a blocked one, and it must never be used to withhold a
|
|
482
|
+
merge — it changes what the record says, not what the loop does.
|
|
@@ -37,8 +37,49 @@ gh api graphql -f query='
|
|
|
37
37
|
-F owner=<owner> -F repo=<repo> -F pr=<pr>
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Keep only threads where `isResolved == false`.
|
|
41
|
-
|
|
40
|
+
Keep only threads where `isResolved == false`.
|
|
41
|
+
|
|
42
|
+
**If there are none, you have not yet learned anything — run Step 1b before
|
|
43
|
+
concluding.** Zero unresolved threads has two completely different causes, and
|
|
44
|
+
this query cannot tell them apart: *a reviewer looked and found nothing*, or
|
|
45
|
+
*nothing ever looked*. Reporting the first when it was the second is the defect
|
|
46
|
+
in CodySwannGT/lisa#2497.
|
|
47
|
+
|
|
48
|
+
## Step 1b: Did any review actually happen? (required before reporting)
|
|
49
|
+
|
|
50
|
+
A required review check can post `success` having reviewed nothing. Measured on
|
|
51
|
+
PRs #2483 and #2484: `CodeRabbit` reported `success — "Review rate limited"`,
|
|
52
|
+
zero reviews, and both merged on that green carrying security-relevant changes.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gh pr checks <pr> --json name,state,bucket,description \
|
|
56
|
+
--jq '.[] | select(.name | test("(?i)coderabbit|review")) | "\(.name)\t\(.state)\t\(.description)"'
|
|
57
|
+
```
|
|
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.
|
|
64
|
+
|
|
65
|
+
Where the project ships Lisa's guard, prefer its machine-readable form, which
|
|
66
|
+
also says whether the check is ruleset-required (so whether branch protection
|
|
67
|
+
recorded a satisfied review gate for a review that did not happen):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node scripts/check-skipped-required-checks.mjs --pr=<pr> --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
It reports and never fails — a hollow check is often a vendor spending cap, not
|
|
74
|
+
a repository defect, and it is not this skill's call to block on one.
|
|
75
|
+
|
|
76
|
+
Carry the finding into your Step 4 report. **Do not write "no unresolved review
|
|
77
|
+
threads" on its own** — it is true of an unreviewed PR too. Say which you
|
|
78
|
+
observed:
|
|
79
|
+
|
|
80
|
+
- `reviewed — CodeRabbit "Review approved", 0 unresolved threads`
|
|
81
|
+
- `NOT REVIEWED — CodeRabbit success but "Review rate limited" (vacuous); 0 threads means nobody looked`
|
|
82
|
+
- `NOT REVIEWED — no review check reported on this PR at all`
|
|
42
83
|
|
|
43
84
|
## Step 2: Triage and act on each unresolved thread
|
|
44
85
|
|
|
@@ -75,6 +116,13 @@ judgment. This skill resolves **threads**; it does not dismiss review-decision
|
|
|
75
116
|
gates (`CHANGES_REQUESTED`) or merge the PR — the caller (`drive-pr-to-merge`)
|
|
76
117
|
owns those.
|
|
77
118
|
|
|
119
|
+
**Open the report with the Step 1b verdict, before the thread counts.** A thread
|
|
120
|
+
summary describes what was done about review findings; it says nothing about
|
|
121
|
+
whether a review produced any. State `reviewed` or `NOT REVIEWED (<why>)` first,
|
|
122
|
+
then the counts. A caller that records "reviews addressed" in evidence must
|
|
123
|
+
carry that verdict through verbatim — a PR whose only review check was vacuous
|
|
124
|
+
has not been reviewed, no matter how clean its thread list is.
|
|
125
|
+
|
|
78
126
|
## Composition
|
|
79
127
|
|
|
80
128
|
- **Standalone**: `/lisa:pull-request:review <pr>`.
|