@codyswann/lisa 4.50.3 → 4.50.5

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 (64) hide show
  1. package/dist/cli/doctor-coderabbit.d.ts +41 -0
  2. package/dist/cli/doctor-coderabbit.d.ts.map +1 -0
  3. package/dist/cli/doctor-coderabbit.js +96 -0
  4. package/dist/cli/doctor-coderabbit.js.map +1 -0
  5. package/dist/cli/doctor.d.ts +2 -0
  6. package/dist/cli/doctor.d.ts.map +1 -1
  7. package/dist/cli/doctor.js +3 -0
  8. package/dist/cli/doctor.js.map +1 -1
  9. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  10. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  11. package/dist/core/upstream-evidence-manifest.js +3 -1
  12. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  13. package/package.json +4 -4
  14. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-agy/plugin.json +1 -1
  27. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  47. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-agy/plugin.json +1 -1
  52. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  57. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  62. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  64. package/scripts/check-shipped-surface-removals.mjs +179 -12
@@ -280,32 +280,177 @@ export function loadLedger(root) {
280
280
  return { baseline, removals };
281
281
  }
282
282
 
283
+ /** A release tag, as this repository spells one. */
284
+ const RELEASE_TAG = /^v\d+\.\d+\.\d+$/u;
285
+
286
+ /**
287
+ * Whether a commit is an ancestor of HEAD, which is what makes `ref..HEAD` a
288
+ * release history rather than an arbitrary pair of commits.
289
+ *
290
+ * @param {string} root - repository root.
291
+ * @param {string} rev - the revision to test.
292
+ * @returns {boolean} true when `rev` is reachable from HEAD.
293
+ */
294
+ function isAncestorOfHead(root, rev) {
295
+ try {
296
+ boundedExecFileSync(
297
+ "git",
298
+ ["-C", root, "merge-base", "--is-ancestor", rev, "HEAD"],
299
+ { stdio: "ignore" }
300
+ );
301
+ return true;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * The OLDEST release tag reachable from HEAD, or null when there is none.
309
+ *
310
+ * Oldest, not newest, and the difference decides whether this gate proves
311
+ * anything. Measured on this repository the day the configured baseline came
312
+ * detached: 1779 tags exist, 141 are reachable, the newest reachable is the
313
+ * release commit HEAD itself sits on — so a newest-first rule yields a window
314
+ * of ZERO commits and a confident pass over nothing examined. The oldest
315
+ * reachable tag yields 1030. A baseline is an anchor, so when the configured
316
+ * anchor is gone the honest substitute is the earliest one still standing, not
317
+ * the nearest.
318
+ *
319
+ * `--merged HEAD` is the ancestry filter, so every candidate is already a legal
320
+ * baseline; ordering only decides how much history the window covers.
321
+ *
322
+ * @param {string} root - repository root.
323
+ * @returns {string | null} the tag name, or null when no release tag is reachable.
324
+ */
325
+ export function oldestReachableReleaseTag(root) {
326
+ const listed = git(
327
+ root,
328
+ ["tag", "--merged", "HEAD", "--sort=creatordate"],
329
+ "could not list tags reachable from HEAD"
330
+ );
331
+ const tags = listed
332
+ .split("\n")
333
+ .map(line => line.trim())
334
+ .filter(line => RELEASE_TAG.test(line));
335
+ return tags[0] ?? null;
336
+ }
337
+
283
338
  /**
284
339
  * Resolve the baseline ref, refusing anything this gate cannot compare against.
285
340
  *
341
+ * The configured baseline is used verbatim whenever it is reachable, and this
342
+ * function behaves then exactly as it always has. It gained a fallback because
343
+ * a history rewrite detached the pinned tag: `v4.0.0` still resolves to a
344
+ * commit and is no longer an ancestor of `main`, so every invocation threw and
345
+ * `test-correctness` — a required PUSH gate — refused every branch in the
346
+ * repository. The gate was not wrong about anything; it could not run.
347
+ * CodySwannGT/lisa#4047, and CodySwannGT/lisa#3719 records the same rewrite
348
+ * breaking a different ancestry-walking gate.
349
+ *
350
+ * The fallback does not loosen the gate. Three ways it stays strict:
351
+ *
352
+ * - it substitutes only when the configured baseline is UNREACHABLE, never
353
+ * when it merely produces findings;
354
+ * - a substituted baseline is named in the report, with the ref it replaced,
355
+ * so a reader is never told a window they did not ask for is the one they
356
+ * configured; and
357
+ * - an EMPTY window is refused. A baseline equal to HEAD compares nothing,
358
+ * and "nothing examined" must never render as "nothing wrong" — that is
359
+ * the failure this gate exists to prevent, one level up.
360
+ *
361
+ * Re-anchoring the tag is deliberately not the fix. Moving a published tag
362
+ * breaks every consumer pinned at a commit that belonged to it
363
+ * (CodySwannGT/lisa#3488, CodySwannGT/lisa#3893).
364
+ *
286
365
  * @param {string} root - repository root.
287
366
  * @param {string} ref - the requested baseline.
288
- * @returns {string} the resolved commit sha.
367
+ * @param {boolean} [mayFallBack] - true only for the ledger's configured pin.
368
+ * An explicit `--since` never falls back: a stale pin is nobody's decision
369
+ * and a typed argument is somebody's, so answering a different window than
370
+ * the one asked for would be answering a different question. This repository
371
+ * drives the gate to a finding by reaching back past the supported major with
372
+ * `--since`, and a fallback there silently turned the bite arm green — caught
373
+ * by that arm, which is the whole reason it exists.
374
+ * @returns {{ sha: string, ref: string, requested: string, substituted: boolean }}
375
+ * the resolved baseline and whether it is the one that was asked for.
289
376
  */
290
- export function resolveBaseline(root, ref) {
377
+ /**
378
+ * Refuse a baseline whose window holds no commits.
379
+ *
380
+ * Shared by both paths out of `resolveBaseline` on purpose. The condition is
381
+ * one thing — the endpoints are the same commit, so the scan compares nothing —
382
+ * and it is reachable two ways: a `--since` that names HEAD, and a fallback tag
383
+ * that turns out to be HEAD. Guarding only the second left the first reporting
384
+ * `OK ... every removal from a shipped surface is governed` over zero commits.
385
+ *
386
+ * @param {string} root - repository root.
387
+ * @param {string} ref - the baseline as the caller named it, for the message.
388
+ * @param {string} sha - the commit that baseline resolved to.
389
+ * @throws {UsageError} When `sha..HEAD` contains no commits.
390
+ */
391
+ function assertNonEmptyWindow(root, ref, sha) {
392
+ const span = git(
393
+ root,
394
+ ["rev-list", "--count", `${sha}..HEAD`],
395
+ `could not measure the window ${ref}..HEAD`
396
+ ).trim();
397
+ if (span === "0") {
398
+ throw new UsageError(
399
+ `baseline ${ref} is HEAD itself, so the window is empty - refusing ` +
400
+ `rather than reporting a clean scan of nothing`
401
+ );
402
+ }
403
+ }
404
+
405
+ export function resolveBaseline(root, ref, mayFallBack = false) {
291
406
  const sha = git(
292
407
  root,
293
408
  ["rev-parse", "--verify", `${ref}^{commit}`],
294
409
  `baseline ${ref} does not resolve to a commit`
295
410
  ).trim();
296
- try {
297
- boundedExecFileSync(
298
- "git",
299
- ["-C", root, "merge-base", "--is-ancestor", sha, "HEAD"],
300
- { stdio: "ignore" }
301
- );
302
- } catch {
411
+ if (isAncestorOfHead(root, sha)) {
412
+ // A reachable baseline can still be HEAD itself, and then the window holds
413
+ // no commits at all. The scan reads nothing and reports every removal
414
+ // governed, which is a confident pass over nothing examined — the failure
415
+ // this gate exists to prevent, produced by the gate. The fallback branch
416
+ // below already refuses that; an explicit `--since` reached the same state
417
+ // by a different route and did not, so the check belongs on every path out
418
+ // of here rather than on one of them.
419
+ assertNonEmptyWindow(root, ref, sha);
420
+ return { ref, requested: ref, sha, substituted: false };
421
+ }
422
+ if (!mayFallBack) {
303
423
  throw new UsageError(
304
424
  `baseline ${ref} is not an ancestor of HEAD, so the window is not a ` +
305
425
  `release history this gate can read`
306
426
  );
307
427
  }
308
- return sha;
428
+ const fallback = oldestReachableReleaseTag(root);
429
+ if (fallback === null) {
430
+ throw new UsageError(
431
+ `baseline ${ref} is not an ancestor of HEAD, and no release tag is ` +
432
+ `reachable from HEAD either, so there is no window this gate can ` +
433
+ `read - refusing rather than reporting a clean scan`
434
+ );
435
+ }
436
+ const fallbackSha = git(
437
+ root,
438
+ ["rev-parse", "--verify", `${fallback}^{commit}`],
439
+ `fallback baseline ${fallback} does not resolve to a commit`
440
+ ).trim();
441
+ const span = git(
442
+ root,
443
+ ["rev-list", "--count", `${fallbackSha}..HEAD`],
444
+ `could not measure the window ${fallback}..HEAD`
445
+ ).trim();
446
+ if (span === "0") {
447
+ throw new UsageError(
448
+ `baseline ${ref} is not an ancestor of HEAD, and the oldest reachable ` +
449
+ `release tag ${fallback} is HEAD itself, so the window is empty - ` +
450
+ `refusing rather than reporting a clean scan of nothing`
451
+ );
452
+ }
453
+ return { ref: fallback, requested: ref, sha: fallbackSha, substituted: true };
309
454
  }
310
455
 
311
456
  /**
@@ -633,6 +778,9 @@ export function buildReport(findings, opts) {
633
778
  findings.ledger.length;
634
779
  return {
635
780
  baseline: opts.baseline,
781
+ // The ref the ledger asked for, when it is NOT the one that was used. Null
782
+ // on every ordinary run, so a reader who sees it knows the window moved.
783
+ baselineReplaced: opts.replaced ?? null,
636
784
  findings,
637
785
  root: opts.root,
638
786
  schemaVersion: 1,
@@ -658,14 +806,27 @@ export function buildReport(findings, opts) {
658
806
  */
659
807
  export function humanReport(report) {
660
808
  const { findings, summary } = report;
809
+ // Printed on BOTH arms. A window that is not the configured one changes what
810
+ // a pass and a failure each mean, so a reader must not have to reach the JSON
811
+ // to find out which window they are reading.
812
+ const substitution =
813
+ report.baselineReplaced === null || report.baselineReplaced === undefined
814
+ ? []
815
+ : [
816
+ ` NOTE baseline ${report.baselineReplaced} is not an ancestor of ` +
817
+ `HEAD, so the oldest reachable release tag ${report.baseline} was ` +
818
+ `used instead. Re-pin ${LEDGER_PATH} rather than moving the tag.`,
819
+ ];
661
820
  if (summary.violations === 0) {
662
821
  return [
663
822
  `OK ${summary.shippedFiles} shipped file(s) since ${report.baseline}:`,
664
823
  ` every removal from a shipped surface is governed`,
665
824
  ` (${summary.recordedRemovals} removal(s) recorded in ${LEDGER_PATH})`,
825
+ ...substitution,
666
826
  ].join("\n");
667
827
  }
668
828
  return [
829
+ ...substitution,
669
830
  ...findings.paths.map(
670
831
  row =>
671
832
  `FAIL ${row.path}\n` +
@@ -771,7 +932,12 @@ export function countImporters(shipped, read) {
771
932
  */
772
933
  export function scan(opts) {
773
934
  const ledger = loadLedger(opts.root);
774
- const baseline = resolveBaseline(opts.root, opts.since ?? ledger.baseline);
935
+ const resolved = resolveBaseline(
936
+ opts.root,
937
+ opts.since ?? ledger.baseline,
938
+ opts.since === null || opts.since === undefined
939
+ );
940
+ const baseline = resolved.sha;
775
941
  const before = shippedFilesAt(opts.root, baseline);
776
942
  const after = shippedFilesAt(opts.root, "HEAD");
777
943
  if (after.size === 0) {
@@ -807,8 +973,9 @@ export function scan(opts) {
807
973
  }),
808
974
  };
809
975
  return buildReport(findings, {
810
- baseline: opts.since ?? ledger.baseline,
976
+ baseline: resolved.ref,
811
977
  recorded: ledger.removals.length,
978
+ replaced: resolved.substituted ? resolved.requested : null,
812
979
  root: opts.root,
813
980
  shipped: after.size,
814
981
  });