@codyswann/lisa 4.50.2 → 4.50.4
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/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/reusable-workflow-pin.d.ts +3 -1
- package/dist/core/reusable-workflow-pin.d.ts.map +1 -1
- package/dist/core/reusable-workflow-pin.js +10 -1
- package/dist/core/reusable-workflow-pin.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +3 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts +21 -0
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.d.ts.map +1 -1
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.js +57 -5
- package/dist/migrations/ensure-pinned-reusable-workflow-refs.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-agy/plugin.json +1 -1
- 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-cursor/.claude-plugin/plugin.json +1 -1
- 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/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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
});
|