@codyswann/lisa 4.84.2 → 4.84.3

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 (81) hide show
  1. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  2. package/dist/core/lisa-owned-hash-ledger.js +10 -0
  3. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  4. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  5. package/dist/core/upstream-evidence-manifest.js +1 -1
  6. package/dist/opencode/plugin-templates/lisa-block-managed-file-edits.ts +11 -4
  7. package/dist/opencode/plugin-templates/lisa-block-no-verify.ts +12 -4
  8. package/dist/opencode/plugin-templates/lisa-discharge-work-item-gates.ts +10 -4
  9. package/dist/opencode/plugin-templates/lisa-parity-safety-net.ts +11 -4
  10. package/package.json +4 -4
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-agy/plugin.json +1 -1
  28. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  56. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails-agy/plugin.json +1 -1
  63. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  70. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  77. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  81. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +115 -35
@@ -2129,14 +2129,73 @@ export const REVIEW_GATE_CONDITIONS = Object.freeze({
2129
2129
  pending: "pending",
2130
2130
  unrecognised: "unrecognised",
2131
2131
  undetermined: "undetermined",
2132
- // A SATISFYING condition, and only ever reached when the reviewer posted NO
2133
- // status row at all (`absent`, or `undetermined` while still unreported —
2134
- // never a row still in flight), and a writer posted a local
2135
- // adversarial review for this exact head in its place. Never from
2136
- // `objected` — a substitute does not overrule a review that ran and said no.
2132
+ // A SATISFYING condition: the reviewer could not or did not review this
2133
+ // head, and a writer posted a local adversarial review for it in its place.
2134
+ // See `substitutable` for exactly which readings qualify. Never from
2135
+ // `objected` or a review still in flight.
2137
2136
  substituted: "substituted",
2138
2137
  });
2139
2138
 
2139
+ /**
2140
+ * Review conditions where the reviewer FINISHED without reviewing, so a
2141
+ * writer's substitute may stand in.
2142
+ *
2143
+ * WHY WAIVERS QUALIFY. A rate limit or a vendor skip is a billing or
2144
+ * entitlement state, not a finding, and it must not pull a human into every
2145
+ * merge. Standing in for it is safe because the two things a substitute must
2146
+ * never hide are read from somewhere else: an objection from the review
2147
+ * OBJECTS at this head ({@link readObjectionAtHead}), which outranks every
2148
+ * state in the published verdict; and unresolved review threads, which the
2149
+ * branch ruleset blocks on directly. `unrecognised` qualifies for the same
2150
+ * reason — a green status whose sentence nobody classified carries no
2151
+ * objection the review objects would not also carry.
2152
+ */
2153
+ const SUBSTITUTABLE_REPORTED_CONDITIONS = Object.freeze(
2154
+ new Set([
2155
+ REVIEW_GATE_CONDITIONS.waived,
2156
+ REVIEW_GATE_CONDITIONS.waivedWithReview,
2157
+ REVIEW_GATE_CONDITIONS.unrecognised,
2158
+ ])
2159
+ );
2160
+
2161
+ /**
2162
+ * Whether a substitute may stand in for one reviewer's reading.
2163
+ *
2164
+ * A reviewer that posted NO row qualifies whatever the wait concluded. A
2165
+ * reviewer that posted a row qualifies only when that row is SETTLED and says
2166
+ * it did not review. `undetermined` with a row present is a review STILL
2167
+ * RUNNING once the wait expires, and a substitute must not race a review in
2168
+ * flight whose objection would land after the merge. `objected` and `pending`
2169
+ * never qualify.
2170
+ *
2171
+ * EVERY substitute — for a reviewer that spoke or one that never posted —
2172
+ * also requires the pull request's review surface to have been READ and to
2173
+ * show no objection: no CHANGES_REQUESTED decision and no unresolved thread.
2174
+ * The review objects at head catch only a CHANGES_REQUESTED on this commit; a
2175
+ * COMMENTED review with open threads, or an objection on an earlier push, is
2176
+ * visible only on the surface. A surface that could not be read is not a clean
2177
+ * one, so it substitutes nothing.
2178
+ *
2179
+ * KNOWN DEPENDENCY: GitHub fills `reviewDecision` only where the repository
2180
+ * requires reviews. Without that rule an earlier push's CHANGES_REQUESTED is
2181
+ * caught only if it left an unresolved thread, which reviewers that request
2182
+ * changes ordinarily do.
2183
+ *
2184
+ * @param {object|undefined} found - The reviewer's check row, if it posted one
2185
+ * @param {string} condition - The condition its reading produced
2186
+ * @param {{read: boolean, present: boolean, objected: boolean}|undefined} activity - What the review surface showed, if it was read
2187
+ * @returns {boolean} Whether a substitute may stand in
2188
+ */
2189
+ function substitutable(found, condition, activity) {
2190
+ const surfaceClean =
2191
+ activity !== undefined &&
2192
+ activity.read === true &&
2193
+ activity.objected !== true;
2194
+ if (!surfaceClean) return false;
2195
+ if (found === undefined) return true;
2196
+ return SUBSTITUTABLE_REPORTED_CONDITIONS.has(condition);
2197
+ }
2198
+
2140
2199
  /**
2141
2200
  * What the pull request's OWN review surface says, independent of any check.
2142
2201
  *
@@ -2506,15 +2565,14 @@ export function evaluateReviewGate(declaration, checks, options = {}) {
2506
2565
  },
2507
2566
  vocabulary
2508
2567
  );
2509
- // Only a reviewer that NEVER REPORTED can be stood in for, keyed on the
2510
- // missing row rather than on the condition: `undetermined` is also what a
2511
- // reviewer STILL RUNNING reads as once the wait expires, and a substitute
2512
- // must not race a review in flight whose objection would land after the
2513
- // merge. One that is pending, said something unrecognised, or objected
2514
- // has spoken, and a substitute does not get to overrule it.
2515
- if (found === undefined && options.substitutes?.has(name) === true) {
2568
+ if (
2569
+ options.substitutes?.has(name) === true &&
2570
+ substitutable(found, verdict.condition, options.reviewActivity)
2571
+ ) {
2516
2572
  states[name] = REVIEW_GATE_STATES.satisfied;
2517
2573
  conditions[name] = REVIEW_GATE_CONDITIONS.substituted;
2574
+ if (found !== undefined)
2575
+ descriptions[name] = String(found.description ?? "");
2518
2576
  continue;
2519
2577
  }
2520
2578
  states[name] = verdict.state;
@@ -3204,7 +3262,10 @@ export function reviewGateVerdict(reading = {}) {
3204
3262
  */
3205
3263
  const quote = name => {
3206
3264
  if (conditions[name] === REVIEW_GATE_CONDITIONS.substituted) {
3207
- return `${name} posted NO review status; a writer's ${REVIEW_SUBSTITUTE_MARKER} local adversarial review stands in for it at this head`;
3265
+ const said = Object.hasOwn(descriptions, name)
3266
+ ? `reported ${JSON.stringify(descriptions[name])} without reviewing`
3267
+ : "posted NO review status";
3268
+ return `${name} ${said}; a writer's ${REVIEW_SUBSTITUTE_MARKER} local adversarial review stands in for it at this head`;
3208
3269
  }
3209
3270
  if (conditions[name] === REVIEW_GATE_CONDITIONS.undetermined) {
3210
3271
  return `${name} had not settled when the gate's wait EXPIRED — not observed to be unreviewed`;
@@ -3299,7 +3360,7 @@ export function reviewGateVerdict(reading = {}) {
3299
3360
  verdict: REVIEW_GATE_STATES.waived,
3300
3361
  conclusion: REVIEW_VERDICT_CONCLUSIONS.waived,
3301
3362
  title: fitTitle(
3302
- `CARRIED UNREVIEWED — this pull request's own review ran, but the work it carries was not reviewed with it${suffix}`
3363
+ `CARRIED UNREVIEWED — this pull request's own ${names.some(name => conditions[name] === REVIEW_GATE_CONDITIONS.substituted) ? "head was covered by a local adversarial review standing in for the reviewer" : "review ran"}, but the work it carries was not reviewed with it${suffix}`
3303
3364
  ),
3304
3365
  };
3305
3366
  }
@@ -3315,7 +3376,7 @@ export function reviewGateVerdict(reading = {}) {
3315
3376
  verdict: REVIEW_GATE_STATES.waived,
3316
3377
  conclusion: REVIEW_VERDICT_CONCLUSIONS.waived,
3317
3378
  title: fitTitle(
3318
- `REVIEWED, OBJECTION STANDING UNKNOWN — the review ran, and whether it requested changes at this head could not be read: ${names.map(quote).join("; ")}${suffix}`
3379
+ `${names.some(name => conditions[name] === REVIEW_GATE_CONDITIONS.substituted) ? "SUBSTITUTED, OBJECTION STANDING UNKNOWN — a local adversarial review stood in" : "REVIEWED, OBJECTION STANDING UNKNOWN — the review ran"}, and whether a reviewer requested changes at this head could not be read: ${names.map(quote).join("; ")}${suffix}`
3319
3380
  ),
3320
3381
  };
3321
3382
  }
@@ -4525,23 +4586,38 @@ export function inspectVacuity(argv, declaration, options = {}) {
4525
4586
  ? earlySubstitutes
4526
4587
  : readSubstitutes(read.headSha),
4527
4588
  };
4528
- // TWO PASSES, AND THE SECOND IS PAID FOR ONLY BY A WAIVER (#3706). The review
4529
- // surface is read exactly when a waiver is on the table, which is the only
4530
- // condition whose verdict it can change: `satisfied`, `absent`, `pending`,
4531
- // `unrecognised` and `undetermined` all reach the same answer without it, and
4532
- // making them pay a network call would put a new failure mode on paths that
4533
- // work. Same shape, and the same reason, as the waive-rate sample below.
4534
- const gate = Object.values(
4535
- evaluateReviewGate(declaration, read.checks, gateOptions).states
4536
- ).includes(REVIEW_GATE_STATES.waived)
4537
- ? evaluateReviewGate(declaration, read.checks, {
4538
- ...gateOptions,
4539
- reviewActivity: (options.reviewActivity ?? fetchReviewActivity)(
4540
- pr,
4541
- repo
4542
- ),
4543
- })
4544
- : evaluateReviewGate(declaration, read.checks, gateOptions);
4589
+ // TWO PASSES, AND THE SECOND IS PAID FOR ONLY BY A WAIVER OR A SUBSTITUTE
4590
+ // (#3706, #4532). The review surface is read exactly when one of the two is
4591
+ // on the table, the only inputs whose verdict it can change: `satisfied`,
4592
+ // `absent`, `pending`, `unrecognised` and `undetermined` all reach the same
4593
+ // answer without it when nobody stood in, and making them pay a network call
4594
+ // would put a new failure mode on paths that work. Same shape, and the same
4595
+ // reason, as the waive-rate sample below.
4596
+ //
4597
+ // The trigger is decided WITHOUT substitutes. A substitute turns a waived
4598
+ // row satisfied, and deciding on that would skip the very read that finds an
4599
+ // objection hidden inside a waiver. And ANY substitute pays for the read on
4600
+ // its own, because standing in — for a reviewer that spoke or one that never
4601
+ // posted — requires the surface to show no objection: an unresolved thread,
4602
+ // or a CHANGES_REQUESTED from an earlier push, is visible only there.
4603
+ const unsubstituted = evaluateReviewGate(declaration, read.checks, {
4604
+ ...gateOptions,
4605
+ substitutes: undefined,
4606
+ });
4607
+ const anyStandIn = Object.keys(unsubstituted.conditions).some(name =>
4608
+ gateOptions.substitutes.has(name)
4609
+ );
4610
+ const gate =
4611
+ Object.values(unsubstituted.states).includes(REVIEW_GATE_STATES.waived) ||
4612
+ anyStandIn
4613
+ ? evaluateReviewGate(declaration, read.checks, {
4614
+ ...gateOptions,
4615
+ reviewActivity: (options.reviewActivity ?? fetchReviewActivity)(
4616
+ pr,
4617
+ repo
4618
+ ),
4619
+ })
4620
+ : evaluateReviewGate(declaration, read.checks, gateOptions);
4545
4621
  // #3658. Everything above judged ONE commit — this pull request's head — and
4546
4622
  // that is the one commit a batch integration pull request does not speak for.
4547
4623
  // Armed by the same flag as the gate itself, because this IS the gate,
@@ -4578,12 +4654,16 @@ export function inspectVacuity(argv, declaration, options = {}) {
4578
4654
  headSha: read.headSha,
4579
4655
  checked: evaluated.checked,
4580
4656
  violations: [
4581
- // "Did not report… NOBODY LOOKED" is false once a writer's substitute
4582
- // stands in for that exact check at this head, so it is not repeated.
4657
+ // "Did not report… NOBODY LOOKED" and "reported green having reviewed
4658
+ // nothing" are both answered once a writer's substitute stands in for
4659
+ // that exact check at this head, so neither is repeated — and the
4660
+ // vacuous one would otherwise still fail the job under
4661
+ // `--fail-on-vacuous` while the verdict reads SUBSTITUTED.
4583
4662
  ...evaluated.violations.filter(
4584
4663
  violation =>
4585
4664
  !(
4586
- violation.kind === VIOLATIONS.unproven &&
4665
+ (violation.kind === VIOLATIONS.unproven ||
4666
+ violation.kind === VIOLATIONS.vacuous) &&
4587
4667
  gate.conditions[violation.token] ===
4588
4668
  REVIEW_GATE_CONDITIONS.substituted
4589
4669
  )