@codyswann/lisa 4.34.7 → 4.34.9
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/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +184 -5
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +11 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +17 -12
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa/rules/eager/rejection-detection.md +14 -1
- package/plugins/lisa/rules/eager/settled-decisions.md +20 -0
- package/plugins/lisa/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa/rules/reference/settled-decisions.md +10 -0
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +64 -7
- 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/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa-copilot/rules/eager/rejection-detection.md +14 -1
- package/plugins/lisa-copilot/rules/eager/settled-decisions.md +20 -0
- package/plugins/lisa-copilot/rules/reference/rejection-detection.md +69 -0
- package/plugins/lisa-copilot/rules/reference/settled-decisions.md +10 -0
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +64 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +69 -0
- package/plugins/lisa-cursor/rules/rejection-detection.mdc +14 -1
- package/plugins/lisa-cursor/rules/settled-decisions-reference.mdc +10 -0
- package/plugins/lisa-cursor/rules/settled-decisions.mdc +20 -0
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +64 -7
- 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/hooks/block-direct-issue-create.sh +184 -5
- package/plugins/src/base/hooks/block-managed-file-edits.sh +11 -5
- package/plugins/src/base/rules/eager/rejection-detection.md +14 -1
- package/plugins/src/base/rules/eager/settled-decisions.md +20 -0
- package/plugins/src/base/rules/reference/rejection-detection.md +69 -0
- package/plugins/src/base/rules/reference/settled-decisions.md +10 -0
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +141 -1
- package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +64 -7
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +69 -3
|
@@ -186,7 +186,9 @@ rule — never hardcode status/label strings. The relevant repair roles:
|
|
|
186
186
|
|
|
187
187
|
In addition to the lifecycle roles above, the build lifecycle defines the **`human_needed` marker** — an additive label (`jira.labels.human_needed` / `github.labels.build.human_needed` / `linear.labels.build.human_needed`, default `Human Needed` / `human-needed`) that rides alongside `blocked` when the block needs human-only input no agent or retry can supply (see `config-resolution` "Build markers"). repair-intake's interaction with the marker is asymmetric and is the whole point of the distinction below:
|
|
188
188
|
|
|
189
|
-
- The blocks repair-intake **itself writes** are the auto-recoverable kind — it files a build-ready fix ticket and moves the item `blocked` *blocked by that ticket*, expecting the next cycle to self-heal. Those are **not** `human_needed`; if such an item arrives
|
|
189
|
+
- The blocks repair-intake **itself writes** are the auto-recoverable kind — it files a build-ready fix ticket and moves the item `blocked` *blocked by that ticket*, expecting the next cycle to self-heal. Those are **not** `human_needed`; if such an item arrives carrying a `human_needed` marker **this skill applied on an earlier cycle**, repair-intake **clears** it (the block is no longer waiting on a human).
|
|
190
|
+
- **Never remove a `human_needed` marker this skill did not apply.** "Stale" is a judgment about the block's kind, not about who applied the marker or when — so without this rule an operator's deliberate hold, applied *after* correcting a wrong transition, is indistinguishable from a leftover the sweep is designed to clear, and gets swept. Establish provenance from the label event's actor (`rejection-detection` **Automation-reversal memory** reads the same surfaces); if provenance is not readable, **leave the marker in place**. Removing a human's hold is unrecoverable within the loop; leaving a stale one costs a cycle and is visible.
|
|
191
|
+
- The marker is consulted **before **any** repair transition**, not only before Class C. Class C's hard stop is the strictest reading of it, but a marker that is honoured on one classification path and ignored on the other three is not a guard — and Class A, dependency clearing, is exactly the path an operator reverting a wrongly-cleared blocker is trying to protect. Match it robustly (hyphen/underscore, case-insensitive, label set and note prose) wherever it is read.
|
|
190
192
|
- The blocks the **vendor agent** writes when repair-intake re-dispatches it (its pre-flight gate) carry `human_needed` already — the agent owns that marker. repair-intake leaves it in place.
|
|
191
193
|
|
|
192
194
|
Resolve with the standard role-read pattern (local overrides global, default fallback):
|
|
@@ -305,6 +307,16 @@ Apply per candidate. Continue through the ordered list until every candidate ins
|
|
|
305
307
|
native close/archive/complete, re-dispatch, or refreshed note), be recorded read-only, or be
|
|
306
308
|
recorded under Errors. Do not stop after the first write; the cap is the batch boundary.
|
|
307
309
|
|
|
310
|
+
**Before any lifecycle write on a candidate, classify it for `automation-reversal`** per
|
|
311
|
+
`rejection-detection` **Automation-reversal memory** — a role change this skill made that a human
|
|
312
|
+
then moved back. This runs once per candidate at the top of the walk, ahead of the branches below,
|
|
313
|
+
because it wraps every repair path rather than belonging to one: a reversed rollup reconciliation
|
|
314
|
+
involves no dependency link at all, and a reversed Class-B re-check is the same shape as a reversed
|
|
315
|
+
Class-A clearing. Detection is a pure read and `unknown` **never blocks the sweep** — an item whose
|
|
316
|
+
history or authorship cannot be read is judged by the normal rules below, treated as neither a
|
|
317
|
+
reversal nor a clearance. A candidate classified `automation-reversal` skips the branches below and
|
|
318
|
+
is handled per Loop prevention.
|
|
319
|
+
|
|
308
320
|
### Build `claimed` (stalled in-progress) → diagnose blocker, else resume in place
|
|
309
321
|
|
|
310
322
|
**First check for an already-merged PR — this check is NOT gated on staleness.** Read the item's
|
|
@@ -478,9 +490,13 @@ re-issued against an unchanged conflicting head.
|
|
|
478
490
|
new fix ticket (vendor-native: JIRA issue link `is blocked by`; GitHub/Linear `Blocked by:` line
|
|
479
491
|
+ label). Post a `[lisa-repair-intake]` note naming what it is blocked by and why. This block is
|
|
480
492
|
**auto-recoverable** — the fix ticket will build and close on its own — so do **not** add the
|
|
481
|
-
`human_needed` marker, and if the item
|
|
482
|
-
here (it is no longer waiting on a human). The `human_needed` marker
|
|
483
|
-
human must clear; this one a later cycle clears automatically.
|
|
493
|
+
`human_needed` marker, and if the item carries a `human_needed` label **this skill applied on an
|
|
494
|
+
earlier cycle**, remove it here (it is no longer waiting on a human). The `human_needed` marker
|
|
495
|
+
is reserved for blocks a human must clear; this one a later cycle clears automatically.
|
|
496
|
+
**A marker this skill did not apply is never removed here** — see the marker's provenance rule
|
|
497
|
+
above. The block's kind says the *block* self-heals; it says nothing about why a human put a
|
|
498
|
+
hold on the item, and an operator's hold applied after correcting a wrong transition looks
|
|
499
|
+
identical to a leftover unless provenance is checked.
|
|
484
500
|
3. **Record it** as a repair write. Do **not** dispatch the vendor agent for this item this cycle.
|
|
485
501
|
|
|
486
502
|
The item now sits in `blocked`; once the fix ticket reaches a terminal state, the **Build
|
|
@@ -1026,6 +1042,14 @@ layer, and test ancestry against the remote.
|
|
|
1026
1042
|
"no PR found" is `no-pr` and stays blocking.
|
|
1027
1043
|
- **Human override** — an explicit human statement on this item that the dependency is satisfied
|
|
1028
1044
|
clears it and outranks a failed containment check. Name it in the run record.
|
|
1045
|
+
- **Human hold** — the override runs in both directions. `human_needed` is checked here as well as
|
|
1046
|
+
in Class C: an item carrying it is left `blocked` and recorded `skipped: human_needed`, without
|
|
1047
|
+
running the containment check. Class A is the path an operator reverting a wrongly-cleared
|
|
1048
|
+
blocker is protecting, so a marker honoured only on Class C is not honoured where it is most
|
|
1049
|
+
often meant. Match it robustly (hyphen/underscore, case-insensitive, label set and note prose).
|
|
1050
|
+
Symmetrically, an explicit human statement that the dependency is **not** satisfied keeps the
|
|
1051
|
+
item blocked and outranks a *passing* containment check — the contract has an input channel for
|
|
1052
|
+
"you may now proceed" and it needs one for "your reasoning was wrong".
|
|
1029
1053
|
|
|
1030
1054
|
**A lifecycle status name is never the test.** `done`, `on-stg`, `on-dev` and `code-review` are
|
|
1031
1055
|
labels that correlate with containment in a single-trunk workflow and come apart from it in a
|
|
@@ -1139,12 +1163,33 @@ cron tick.
|
|
|
1139
1163
|
self-block** (so a human filling in one of several missing sections changes the fingerprint and
|
|
1140
1164
|
triggers a re-check next cycle, rather than being suppressed as a no-op), terminal/open state,
|
|
1141
1165
|
rollup child tally, and a timestamp.
|
|
1166
|
+
- The fingerprint also carries **authorship and an overturn bit**: the actor class of the most
|
|
1167
|
+
recent lifecycle role change (`automation` / `human` / `unknown`), and whether that change
|
|
1168
|
+
**reversed** a transition this skill made — the `automation-reversal` classification from
|
|
1169
|
+
`rejection-detection` **Automation-reversal memory**. Without these the fingerprint records only
|
|
1170
|
+
*what* the state is, never *who* set it or *whether the last automated decision was overturned*,
|
|
1171
|
+
and those are the only fields that separate "the world moved on" from "a human told me I was
|
|
1172
|
+
wrong".
|
|
1142
1173
|
- Before writing a note or re-attempting a `blocked` item, compute the current fingerprint. If
|
|
1143
1174
|
an identical fingerprint was already posted within the **backoff window**, skip the item
|
|
1144
1175
|
silently (record as `still_blocked` / `active`, no write).
|
|
1145
1176
|
- Backoff window default = `stale_after` (2h). `force=true` bypasses backoff for a manual run.
|
|
1146
|
-
- A *changed* fingerprint (new blocker state, new answers, new verdict)
|
|
1147
|
-
|
|
1177
|
+
- A *changed* fingerprint (new blocker state, new answers, new verdict) warrants a fresh note +
|
|
1178
|
+
re-attempt — backoff suppresses only no-op repeats.
|
|
1179
|
+
- **A change whose only cause is a human overturn is not a warrant.** Where the overturn bit is
|
|
1180
|
+
set and no evidence postdates the reversal, the changed fingerprint is **not on its own a
|
|
1181
|
+
warrant** to re-attempt: do not re-apply the reversed classification, leave the item where the
|
|
1182
|
+
human put it, ensure `human_needed`, and record the outcome naming the **reversed transition**
|
|
1183
|
+
and the classification that produced it. Re-attempt only on evidence postdating the reversal —
|
|
1184
|
+
a blocker state change or a human comment supplying what was missing — cited in the run record.
|
|
1185
|
+
Without this qualifier the revert *itself* changes the fingerprint and releases backoff, so the
|
|
1186
|
+
faster a reviewer corrects a wrong transition, the sooner it returns.
|
|
1187
|
+
- **This inversion is signed per path.** It applies to the paths that decide whether to re-make a
|
|
1188
|
+
lifecycle transition — Class A/B/C/D clearing and rollup reconciliation. It does **not** touch
|
|
1189
|
+
the staleness model: there a human comment counts as forward progress and resets the staleness
|
|
1190
|
+
clock, so an objecting comment already makes the item look active and this skill leaves it
|
|
1191
|
+
alone. That reading is protective; a single global "human activity no longer counts" would
|
|
1192
|
+
switch it off while reading like a tightening.
|
|
1148
1193
|
|
|
1149
1194
|
## Lifecycle ownership guard
|
|
1150
1195
|
|
|
@@ -1261,6 +1306,12 @@ Report outcomes in these buckets:
|
|
|
1261
1306
|
- `normalized_ready` — GitHub issues missing official lifecycle labels that were classified and
|
|
1262
1307
|
given the configured PRD/build `ready` label so normal intake can claim them.
|
|
1263
1308
|
- `still_blocked` — examined and intentionally left `blocked`, with the active reason.
|
|
1309
|
+
- `reversal_suppressed` — a repair this skill previously made was reversed by a human, so the
|
|
1310
|
+
classification was **not** re-applied. Name the **reversed transition** (role, direction, and
|
|
1311
|
+
when), the classification that produced it, and that the item now carries `human_needed`. This
|
|
1312
|
+
bucket is never merged into `still_blocked`: "I declined to act because I was overruled" and "I
|
|
1313
|
+
examined it and the blocker stands" are different facts about the loop, and only the first tells
|
|
1314
|
+
an operator their correction was received.
|
|
1264
1315
|
- `active` — skipped because current work is not stale (or within backoff).
|
|
1265
1316
|
- `errors` — items that failed evaluation, with the error.
|
|
1266
1317
|
|
|
@@ -1280,7 +1331,7 @@ to each item* — the two never merge in the one-line summary.
|
|
|
1280
1331
|
| Nothing actionable — the idle case (walk step 5): examined N, all active or in backoff — including a fix ticket suppressed by a prior decline (`rejection-detection` **Proposal rejection memory**), which the summary names in its suppression count | `nothing-needed` |
|
|
1281
1332
|
| Repairs applied **and confirmed** this cycle — `resumed` / `resynced` / `recovered` / `unblocked` / `closed_out` / `rolled_up` / `relinked` / `normalized_ready` | `change-proved` |
|
|
1282
1333
|
| Repair produced new work for a human to pick up — e.g. an unmergeable PR or failed deploy filed as a **build-ready fix ticket** and left `blocked` | `candidate-proposed` |
|
|
1283
|
-
| A repair reached an autonomy boundary needing a human (a protected-deploy approval before it can proceed) | `approval-requested` |
|
|
1334
|
+
| A repair reached an autonomy boundary needing a human (a protected-deploy approval before it can proceed), **or a repair this loop previously made was reversed by a human** and is therefore suppressed and escalated to `human_needed` (`reversal_suppressed`) rather than re-attempted | `approval-requested` |
|
|
1284
1335
|
| The loop itself could not run — the queue is unreadable, tracker credentials are revoked, or an open-and-closed rejection-memory / blocker-marker search is unreadable and therefore must not fall through to `nothing-needed` | `recovery-required` |
|
|
1285
1336
|
| The runbook's **Retirement condition** tripped | `policy-obsolete` — **never reached by design for this loop** (see Retirement evaluation below) |
|
|
1286
1337
|
|
|
@@ -1343,6 +1394,12 @@ stuck work accumulates), or interleaved on a longer cadence.
|
|
|
1343
1394
|
do."
|
|
1344
1395
|
- Never re-dispatch a `blocked` build item unless every parsed blocker is cleared (conservative
|
|
1345
1396
|
dependency clearing).
|
|
1397
|
+
- Never re-make a transition a human reversed. Classify every candidate for `automation-reversal`
|
|
1398
|
+
before any lifecycle write (`rejection-detection`), and on a hit leave the item where the human
|
|
1399
|
+
put it with `human_needed`, naming the reversed transition in the run record. Re-attempt only on
|
|
1400
|
+
evidence postdating the reversal.
|
|
1401
|
+
- Never remove a `human_needed` marker this skill did not apply, and consult the marker before
|
|
1402
|
+
**any** repair transition rather than only before Class C.
|
|
1346
1403
|
- Repair every materially actionable candidate inside the `max_candidates` cap; default cap is 100.
|
|
1347
1404
|
- Default GitHub `intake_mode` is `both` when both PRD and build namespaces exist.
|
|
1348
1405
|
- Honor the backoff window — never re-post an identical `[lisa-repair-intake]` note within it
|
|
@@ -1242,6 +1242,44 @@ export const REVIEW_GATE_STATES = Object.freeze({
|
|
|
1242
1242
|
unsatisfied: "unsatisfied",
|
|
1243
1243
|
});
|
|
1244
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* WHICH condition produced the state, as stable tokens.
|
|
1247
|
+
*
|
|
1248
|
+
* The state is the gate's SEVERITY and stays three-valued; this is what the
|
|
1249
|
+
* gate OBSERVED, and it is six-valued because `unsatisfied` is reached from
|
|
1250
|
+
* four structurally different situations:
|
|
1251
|
+
*
|
|
1252
|
+
* | condition | what actually happened | operator's next move |
|
|
1253
|
+
* |----------------|-----------------------------------|-----------------------|
|
|
1254
|
+
* | `absent` | nobody reviewed | investigate the silence |
|
|
1255
|
+
* | `objected` | a review ran and objected | READ THE OBJECTION |
|
|
1256
|
+
* | `pending` | the reviewer is late | wait, then re-run |
|
|
1257
|
+
* | `unrecognised` | the reviewer said something new | classify the phrase |
|
|
1258
|
+
*
|
|
1259
|
+
* All four published one word, so an operator handed `unsatisfied` had to guess
|
|
1260
|
+
* between four situations whose correct responses have nothing in common — and
|
|
1261
|
+
* three of those guesses are wrong. That collapse is why CodySwannGT/lisa#3706,
|
|
1262
|
+
* #3716 and #3600 each described a different defect and each was partly right:
|
|
1263
|
+
* they are four faces of one impoverished vocabulary.
|
|
1264
|
+
*
|
|
1265
|
+
* The second row is the sharpest. This file states that a review which
|
|
1266
|
+
* "RAN AND OBJECTED is the case this gate exists to let through to a human" —
|
|
1267
|
+
* and then reported it identically to nobody-reviewed. Blocking is correct
|
|
1268
|
+
* there, so no exit code was ever wrong; what was missing is any way to tell
|
|
1269
|
+
* the operator which of the four they were looking at.
|
|
1270
|
+
*
|
|
1271
|
+
* SEVERITY IS DELIBERATELY UNCHANGED HERE. See `reviewGateState` for why
|
|
1272
|
+
* `pending` keeps blocking.
|
|
1273
|
+
*/
|
|
1274
|
+
export const REVIEW_GATE_CONDITIONS = Object.freeze({
|
|
1275
|
+
satisfied: "satisfied",
|
|
1276
|
+
waived: "waived",
|
|
1277
|
+
absent: "absent",
|
|
1278
|
+
objected: "objected",
|
|
1279
|
+
pending: "pending",
|
|
1280
|
+
unrecognised: "unrecognised",
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1245
1283
|
/**
|
|
1246
1284
|
* Normalises a description for whole-string comparison.
|
|
1247
1285
|
*
|
|
@@ -1262,14 +1300,32 @@ function normalizeDescription(description) {
|
|
|
1262
1300
|
* review-ran-and-objected path has no live example here, and a path with no
|
|
1263
1301
|
* example and no test is a path nobody has watched work.
|
|
1264
1302
|
*
|
|
1303
|
+
* SEVERITY IS DELIBERATELY UNCHANGED. CodySwannGT/lisa#3600 reads the gate as
|
|
1304
|
+
* "backwards" — harsh on a late reviewer, lenient on a hollow one — and asks
|
|
1305
|
+
* for the two to be reconciled. Only half of that survives the invariant the
|
|
1306
|
+
* gate exists to enforce, which is THAT A REVIEW ACTUALLY HAPPENED:
|
|
1307
|
+
*
|
|
1308
|
+
* - `pending` means the review has not happened YET, so blocking is correct.
|
|
1309
|
+
* Making it report-only would let a pull request merge unreviewed whenever a
|
|
1310
|
+
* reviewer ran slow, which is the exact outcome this gate exists to prevent.
|
|
1311
|
+
* - The asymmetry #3600 names is real, but it points the other way: the lenient
|
|
1312
|
+
* side is the hollow green, and that leniency is the owner's explicit ruling
|
|
1313
|
+
* on #3221 (a vendor billing state nobody on the pull request can act on).
|
|
1314
|
+
*
|
|
1315
|
+
* So the response stays proportional by making the gate SAY which condition it
|
|
1316
|
+
* observed, not by lowering what it does about lateness. Reversing the severity
|
|
1317
|
+
* would trade a false red for a silent merge, and this file has a whole
|
|
1318
|
+
* vocabulary of evidence that the silent direction is the expensive one.
|
|
1319
|
+
*
|
|
1265
1320
|
* @param {{present: boolean, state?: string, description?: string}} reading - One check
|
|
1266
1321
|
* @param {{waive?: readonly string[], satisfy?: readonly string[]}} [vocabulary] - Per-check extensions
|
|
1267
|
-
* @returns {{state: string, why: string}}
|
|
1322
|
+
* @returns {{state: string, condition: string, why: string}} Severity, the condition observed, and a one-line reason
|
|
1268
1323
|
*/
|
|
1269
1324
|
export function reviewGateState(reading, vocabulary = {}) {
|
|
1270
1325
|
if (!reading.present) {
|
|
1271
1326
|
return {
|
|
1272
1327
|
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1328
|
+
condition: REVIEW_GATE_CONDITIONS.absent,
|
|
1273
1329
|
why: "did not report on this pull request at all. ABSENT is not the same as waived: nothing said it could not review, so nothing accounts for the silence. A guard that read absence as permission would pass forever the first time it looked at the wrong commit.",
|
|
1274
1330
|
};
|
|
1275
1331
|
}
|
|
@@ -1278,19 +1334,22 @@ export function reviewGateState(reading, vocabulary = {}) {
|
|
|
1278
1334
|
if (state === "FAILURE" || state === "ERROR") {
|
|
1279
1335
|
return {
|
|
1280
1336
|
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1281
|
-
|
|
1337
|
+
condition: REVIEW_GATE_CONDITIONS.objected,
|
|
1338
|
+
why: `reported ${state}${text === "" ? "" : ` — ${JSON.stringify(reading.description ?? "")}`}. A review that RAN AND OBJECTED is the case this gate exists to let through to a human, and it is the one thing no waiver covers. READ THE OBJECTION: this is the one condition here where a review did happen.`,
|
|
1282
1339
|
};
|
|
1283
1340
|
}
|
|
1284
1341
|
if (state !== "SUCCESS") {
|
|
1285
1342
|
return {
|
|
1286
1343
|
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1287
|
-
|
|
1344
|
+
condition: REVIEW_GATE_CONDITIONS.pending,
|
|
1345
|
+
why: `is still ${state === "" ? "unreported" : state} after the settle window. An unsettled check has not said anything yet, and the gate does not guess on its behalf. This is LATENESS, not a verdict about the code — wait for the reviewer to settle, then RE-RUN THIS JOB. Do NOT re-request the review: a re-request OVERWRITES the existing commit status rather than adding to it, so under throttle it destroys a real review one-way and replaces a substantive objection with a rate-limit string.`,
|
|
1288
1346
|
};
|
|
1289
1347
|
}
|
|
1290
1348
|
const satisfies = [...REVIEW_SATISFACTIONS, ...(vocabulary.satisfy ?? [])];
|
|
1291
1349
|
if (satisfies.some(phrase => text === normalizeDescription(phrase))) {
|
|
1292
1350
|
return {
|
|
1293
1351
|
state: REVIEW_GATE_STATES.satisfied,
|
|
1352
|
+
condition: REVIEW_GATE_CONDITIONS.satisfied,
|
|
1294
1353
|
why: `reported ${JSON.stringify(reading.description ?? "")}, which is a review that ran.`,
|
|
1295
1354
|
};
|
|
1296
1355
|
}
|
|
@@ -1298,11 +1357,13 @@ export function reviewGateState(reading, vocabulary = {}) {
|
|
|
1298
1357
|
if (waivers.some(phrase => text === normalizeDescription(phrase))) {
|
|
1299
1358
|
return {
|
|
1300
1359
|
state: REVIEW_GATE_STATES.waived,
|
|
1360
|
+
condition: REVIEW_GATE_CONDITIONS.waived,
|
|
1301
1361
|
why: `reported ${JSON.stringify(reading.description ?? "")} — the check saying, in its own words, that it could not review. WAIVED, not satisfied: this pull request is UNREVIEWED and merging it is a decision taken on that basis. The waiver clears the moment the entitlement behind it is fixed, at which point this gate starts biting with no code change.`,
|
|
1302
1362
|
};
|
|
1303
1363
|
}
|
|
1304
1364
|
return {
|
|
1305
1365
|
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1366
|
+
condition: REVIEW_GATE_CONDITIONS.unrecognised,
|
|
1306
1367
|
why: `reported SUCCESS with the description ${JSON.stringify(reading.description ?? "")}, which is neither a review that ran nor one of the named waivers. An UNRECOGNISED description is surfaced rather than waived — a gate that waived on "anything that is not a completed review" would waive a genuine failure and every phrase the vendor has not invented yet. If this string is legitimate, add it to \`evidence_bearing_checks\` under \`satisfy\` or \`waive\`, deliberately and by name.`,
|
|
1307
1368
|
};
|
|
1308
1369
|
}
|
|
@@ -1360,6 +1421,11 @@ export function evaluateReviewGate(declaration, checks, options = {}) {
|
|
|
1360
1421
|
verdict.state === REVIEW_GATE_STATES.waived
|
|
1361
1422
|
? VIOLATIONS.reviewWaived
|
|
1362
1423
|
: VIOLATIONS.reviewUnsatisfied,
|
|
1424
|
+
// WHICH of the four unsatisfying conditions this was. The `kind` carries
|
|
1425
|
+
// severity and cannot distinguish them — it is the same token for a
|
|
1426
|
+
// review that objected and for one that never ran. Anything rendering
|
|
1427
|
+
// this violation can now say which, without re-parsing the prose.
|
|
1428
|
+
condition: verdict.condition,
|
|
1363
1429
|
token: name,
|
|
1364
1430
|
contexts: [name],
|
|
1365
1431
|
message: `\`${name}\` ${verdict.why}${at}`,
|