@codyswann/lisa 4.34.10 → 4.34.12

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 (68) hide show
  1. package/all/copy-overwrite/scripts/lib/kill-marks.mjs +204 -0
  2. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +29 -2
  3. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  4. package/dist/core/lisa-owned-hash-ledger.js +16 -0
  5. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  6. package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -5
  7. package/dist/core/nightly-e2e-guard-behavior-certificate.js +7 -7
  8. package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
  9. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  10. package/dist/core/upstream-evidence-manifest.js +11 -7
  11. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  12. package/expo/create-only/.github/workflows/nightly-e2e-health.yml +20 -5
  13. package/expo/create-only/.github/workflows/nightly-e2e-report.yml +7 -4
  14. package/expo/create-only/.github/workflows/nightly-e2e-tracking.yml +10 -2
  15. package/package.json +4 -4
  16. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-agy/plugin.json +1 -1
  29. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  49. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-agy/plugin.json +1 -1
  54. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  59. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  64. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  66. package/scripts/check-template-workflow-refs.mjs +112 -9
  67. package/scripts/two-channel-couplings.json +1 -1
  68. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +10 -3
@@ -81,6 +81,31 @@ const TEMPLATE_RE = /^[^/]+\/[^/]+\/\.github\/workflows\/[^/]+\.ya?ml$/;
81
81
  /** `uses: CodySwannGT/lisa/<path>@<ref>` — the reference this gate verifies. */
82
82
  const USES_RE = /uses:\s*CodySwannGT\/lisa\/([^\s@]+)@([^\s"'#]+)/g;
83
83
 
84
+ /**
85
+ * `uses:` introducing a YAML block scalar — `>`, `>-`, `|`, `|+` and friends.
86
+ *
87
+ * A folded `uses:` puts the reference on a CONTINUATION line, so a per-line
88
+ * scan for `uses: CodySwannGT/...` finds nothing at all and the template reads
89
+ * as having no references. That is not a cosmetic gap: a shipped template
90
+ * pinned to a SHA sat unseen by this gate precisely because it was written this
91
+ * way, and every sweep run against it — including one done by hand — reported
92
+ * the file clean. A parser that silently sees nothing is the same false green
93
+ * this gate exists to prevent, one level up.
94
+ *
95
+ * The header may also carry an explicit INDENTATION indicator — a single digit
96
+ * `1`-`9` — and YAML permits it on either side of the chomping indicator, so
97
+ * `>2-` and `>-2` are both well-formed. Matching only the chomping indicator
98
+ * reproduces the very blind spot above in a narrower form: the header fails to
99
+ * match, the continuation scan never runs, and the reference is invisible
100
+ * again. The digit class is `1`-`9` rather than `\d` because `0` is not a legal
101
+ * indentation indicator and the indicator is exactly one digit wide.
102
+ */
103
+ const USES_BLOCK_RE =
104
+ /^(\s*)uses:\s*[>|](?:[-+][1-9]?|[1-9][-+]?)?\s*(?:#.*)?$/;
105
+
106
+ /** The reference itself, unanchored, for continuation lines. */
107
+ const BARE_REF_RE = /CodySwannGT\/lisa\/([^\s@]+)@([^\s"'#]+)/g;
108
+
84
109
  /** The moving ref, resolvable against the working tree with no history. */
85
110
  const MOVING_REF = "main";
86
111
 
@@ -107,13 +132,59 @@ export function findWorkflowRefs(content) {
107
132
  // A fresh lastIndex per line: the regex is global and shared.
108
133
  USES_RE.lastIndex = 0;
109
134
  let match;
135
+ let matchedInline = false;
110
136
  while ((match = USES_RE.exec(lines[index])) !== null) {
137
+ matchedInline = true;
111
138
  refs.push({ line: index + 1, ref: match[2], target: match[1] });
112
139
  }
140
+ if (matchedInline) continue;
141
+
142
+ // A folded `uses:` carries its value on the following, more-indented
143
+ // lines. The reported line is where the reference text actually sits, so a
144
+ // reader can jump straight to the token rather than to the key above it.
145
+ const block = USES_BLOCK_RE.exec(lines[index]);
146
+ if (block === null) continue;
147
+ const keyIndent = block[1].length;
148
+ for (let next = index + 1; next < lines.length; next++) {
149
+ const line = lines[next];
150
+ if (line.trim() === "") continue;
151
+ const indent = line.length - line.trimStart().length;
152
+ if (indent <= keyIndent) break;
153
+ BARE_REF_RE.lastIndex = 0;
154
+ let inner;
155
+ while ((inner = BARE_REF_RE.exec(line)) !== null) {
156
+ refs.push({ line: next + 1, ref: inner[2], target: inner[1] });
157
+ }
158
+ }
113
159
  }
114
160
  return refs;
115
161
  }
116
162
 
163
+ /**
164
+ * Whether a reference violates the standing rule that every consumer-facing
165
+ * reference to a Lisa reusable workflow tracks `@main`.
166
+ *
167
+ * The rule is a decision, not a preference, and both alternatives to it fail
168
+ * SILENTLY, which is why this is enforced rather than documented:
169
+ *
170
+ * - a tag pin goes stale without ever failing. Two templates sat on a tag a
171
+ * full major version behind; nothing went red, they simply stopped
172
+ * receiving anything.
173
+ * - a SHA pin is worse, because a history rewrite makes the commit
174
+ * unreachable and GitHub Actions then cannot LOAD the workflow. Zero jobs
175
+ * are created, so the required context is ABSENT rather than red, and
176
+ * pull requests block forever waiting on a verdict that will never arrive.
177
+ * That has already happened here once and needed an admin override.
178
+ *
179
+ * `@main` breaking is the loud failure, and the sanctioned response to it is to
180
+ * fix the reusable workflow upstream rather than to freeze a consumer.
181
+ * @param {{ ref: string }} reference - the parsed reference.
182
+ * @returns {boolean} true when the reference is pinned rather than tracking.
183
+ */
184
+ export function violatesRefPolicy(reference) {
185
+ return reference.ref !== MOVING_REF;
186
+ }
187
+
117
188
  /**
118
189
  * List every tracked file in `root`, relative to it. Throws `UsageError` when
119
190
  * git is unavailable or `root` is not a repository.
@@ -228,6 +299,7 @@ export function buildReport(results, opts) {
228
299
  const unverifiable = results.filter(
229
300
  result => result.verdict === "unverifiable"
230
301
  );
302
+ const pinned = results.filter(result => result.policy === "pinned");
231
303
  return {
232
304
  results,
233
305
  root: opts.root,
@@ -236,6 +308,7 @@ export function buildReport(results, opts) {
236
308
  checked: opts.checked,
237
309
  missing: missing.length,
238
310
  ok: opts.checked - results.length,
311
+ pinned: pinned.length,
239
312
  templates: opts.templates,
240
313
  unverifiable: unverifiable.length,
241
314
  },
@@ -251,15 +324,35 @@ export function buildReport(results, opts) {
251
324
  */
252
325
  function humanReport(report) {
253
326
  const { summary } = report;
254
- if (summary.missing === 0 && summary.unverifiable === 0) {
255
- return `✓ ${summary.checked} workflow reference(s) across ${summary.templates} caller template(s) all resolve`;
327
+ if (
328
+ summary.missing === 0 &&
329
+ summary.unverifiable === 0 &&
330
+ summary.pinned === 0
331
+ ) {
332
+ return `✓ ${summary.checked} workflow reference(s) across ${summary.templates} caller template(s) all resolve and track @${MOVING_REF}`;
256
333
  }
257
- const lines = report.results.map(result =>
258
- result.verdict === "missing"
259
- ? `✗ ${result.file}:${result.line}\n ${result.target}@${result.ref} — target does not exist at that ref`
260
- : `? ${result.file}:${result.line}\n ${result.target}@${result.ref} — ref not present in this clone, cannot verify`
334
+ const describe = result => {
335
+ if (result.verdict === "missing")
336
+ return "target does not exist at that ref";
337
+ if (result.verdict === "unverifiable")
338
+ return "ref not present in this clone, cannot verify";
339
+ return `pinned, but every consumer reference must track @${MOVING_REF}`;
340
+ };
341
+ const lines = report.results.map(
342
+ result =>
343
+ `${result.verdict === "unverifiable" ? "?" : "✗"} ${result.file}:${result.line}\n ${result.target}@${result.ref} — ${describe(result)}`
261
344
  );
262
345
  const tail = [];
346
+ if (summary.pinned > 0) {
347
+ tail.push(
348
+ `${summary.pinned} reference(s) are pinned rather than tracking @${MOVING_REF}.`,
349
+ "Both ways of pinning fail silently: a tag quietly stops receiving",
350
+ "anything, and a SHA becomes unreachable after a history rewrite, so the",
351
+ "workflow cannot load, zero jobs run, and the required check is ABSENT",
352
+ "rather than red — which blocks pull requests on a verdict that never",
353
+ `arrives. Repoint these at @${MOVING_REF}; when @${MOVING_REF} breaks, fix it upstream.`
354
+ );
355
+ }
263
356
  if (summary.missing > 0) {
264
357
  tail.push(
265
358
  `${summary.missing} reference(s) name a target that does not exist at the pinned ref.`,
@@ -347,8 +440,18 @@ export function main(argv, io = {}) {
347
440
  for (const reference of findWorkflowRefs(content)) {
348
441
  checked += 1;
349
442
  const verdict = classifyRef(opts.root, reference);
350
- if (verdict !== "ok") {
351
- results.push({ ...reference, file, verdict });
443
+ const pinned = violatesRefPolicy(reference);
444
+ // Resolution and policy are independent questions, and a pinned ref
445
+ // that resolves today is exactly the case worth reporting: it is
446
+ // healthy right now and will fail silently later. Reporting only
447
+ // unresolvable refs would call it clean.
448
+ if (verdict !== "ok" || pinned) {
449
+ results.push({
450
+ ...reference,
451
+ file,
452
+ policy: pinned ? "pinned" : "tracking",
453
+ verdict,
454
+ });
352
455
  }
353
456
  }
354
457
  }
@@ -368,7 +471,7 @@ export function main(argv, io = {}) {
368
471
  if (report.summary.unverifiable > 0) {
369
472
  return 2;
370
473
  }
371
- return report.summary.missing === 0 ? 0 : 1;
474
+ return report.summary.missing === 0 && report.summary.pinned === 0 ? 0 : 1;
372
475
  }
373
476
 
374
477
  if (invokedAsScript(import.meta.url)) {
@@ -7,7 +7,7 @@
7
7
  "workflows": 22,
8
8
  "steps": 715,
9
9
  "couplings": 33,
10
- "inventory": 69
10
+ "inventory": 70
11
11
  },
12
12
  "counts": {
13
13
  "package-backed": 10,
@@ -313,6 +313,13 @@ export const FLOW_COUNT_ARTIFACT_PATTERN =
313
313
  export const SCOPE_ARTIFACT_PATTERN =
314
314
  /^maestro-(?<platform>[a-z0-9]+)-scope-(?<scope>full|filtered)$/;
315
315
 
316
+ /** Locale-independent code-point ordering required by Sonar's string-sort rule. */
317
+ function compareStrings(left, right) {
318
+ if (left < right) return -1;
319
+ if (left > right) return 1;
320
+ return 0;
321
+ }
322
+
316
323
  /** The label that identifies a tracking issue this reporter owns. */
317
324
  export const TRACKING_ISSUE_LABEL = "nightly-e2e";
318
325
 
@@ -988,11 +995,11 @@ export function readSuiteScope(artifacts) {
988
995
  const platforms = Object.keys(counts);
989
996
  return Object.freeze({
990
997
  readable: true,
991
- filtered: Object.freeze(filtered.sort()),
998
+ filtered: Object.freeze(filtered.sort(compareStrings)),
992
999
  // Tracked as well as `filtered`, because "the run asserted it was UNFILTERED"
993
1000
  // and "the run said nothing" are different facts and only one of them is
994
1001
  // grounds to stop asking. Every pre-adoption run is the second.
995
- full: Object.freeze(full.sort()),
1002
+ full: Object.freeze(full.sort(compareStrings)),
996
1003
  counts: Object.freeze({ ...counts }),
997
1004
  // `null`, not 0, when nothing was published. Zero is a READING — the arm ran
998
1005
  // and tested nothing — and rendering "no evidence" as that reading is the
@@ -1050,7 +1057,7 @@ export function assessSuiteScope(suite, scope) {
1050
1057
  // happened to work.
1051
1058
  const dead = Object.keys(scope.counts)
1052
1059
  .filter(platform => scope.counts[platform] === 0)
1053
- .sort();
1060
+ .sort(compareStrings);
1054
1061
  if (dead.length > 0) {
1055
1062
  return {
1056
1063
  reason: ZERO_FLOWS_REASON,