@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.
- package/all/copy-overwrite/scripts/lib/kill-marks.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +29 -2
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +16 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -5
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +7 -7
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +11 -7
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/create-only/.github/workflows/nightly-e2e-health.yml +20 -5
- package/expo/create-only/.github/workflows/nightly-e2e-report.yml +7 -4
- package/expo/create-only/.github/workflows/nightly-e2e-tracking.yml +10 -2
- 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-template-workflow-refs.mjs +112 -9
- package/scripts/two-channel-couplings.json +1 -1
- 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 (
|
|
255
|
-
|
|
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
|
|
258
|
-
result.verdict === "missing"
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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)) {
|
|
@@ -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,
|