@codyswann/lisa 4.50.8 → 4.51.0
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-load-adapter.d.ts +78 -0
- package/dist/core/reusable-workflow-load-adapter.d.ts.map +1 -0
- package/dist/core/reusable-workflow-load-adapter.js +125 -0
- package/dist/core/reusable-workflow-load-adapter.js.map +1 -0
- package/dist/core/reusable-workflow-load-failure.d.ts +144 -0
- package/dist/core/reusable-workflow-load-failure.d.ts.map +1 -0
- package/dist/core/reusable-workflow-load-failure.js +184 -0
- package/dist/core/reusable-workflow-load-failure.js.map +1 -0
- package/dist/core/reusable-workflow-load-scan.d.ts +92 -0
- package/dist/core/reusable-workflow-load-scan.d.ts.map +1 -0
- package/dist/core/reusable-workflow-load-scan.js +108 -0
- package/dist/core/reusable-workflow-load-scan.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +10 -0
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +5 -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-workflow-load-failures.ts +171 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan this repository's recent run history for reusable workflows that failed
|
|
3
|
+
* to LOAD (CodySwannGT/lisa#3581).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this runs on a schedule and not at a gate
|
|
6
|
+
*
|
|
7
|
+
* The failure is invisible at the moments a gate covers. On a pull request it
|
|
8
|
+
* is already caught — `check-skipped-required-checks --pr=<n>` raises
|
|
9
|
+
* `absent_required_check`, and a ruleset-required context that never reports
|
|
10
|
+
* blocks the merge anyway. The incident this closes happened somewhere no gate
|
|
11
|
+
* looks: a `push` handler failed five times in a row, ~40 minutes after an
|
|
12
|
+
* upstream commit landed, with nothing changed on the consumer side and no red
|
|
13
|
+
* job to open. A control that only runs where the failure cannot happen is not
|
|
14
|
+
* a control, so the moment is the design decision here, not the mechanism.
|
|
15
|
+
*
|
|
16
|
+
* The sibling precedent is `lifecycle-drift-sweep.yml`, which exists because a
|
|
17
|
+
* report nobody scheduled and nobody read is indistinguishable from no report.
|
|
18
|
+
* This follows it: scheduled, and failing the job on drift so the finding lands
|
|
19
|
+
* in the existing failure-to-issue path rather than a job summary nobody opens.
|
|
20
|
+
*
|
|
21
|
+
* ## Two ways to exit non-zero, and they mean different things
|
|
22
|
+
*
|
|
23
|
+
* Findings are one. **A window this scan could not cover is the other**, and it
|
|
24
|
+
* is the more important of the two: the implementation this replaces read one
|
|
25
|
+
* page, filtered by timestamp, and reported that a hundred runs were inspected
|
|
26
|
+
* across a window that contained four known load failures. "Found nothing" and
|
|
27
|
+
* "could not look" must never render as the same green.
|
|
28
|
+
* @module scripts/check-workflow-load-failures
|
|
29
|
+
*/
|
|
30
|
+
import { execFile } from "node:child_process";
|
|
31
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { promisify } from "node:util";
|
|
34
|
+
|
|
35
|
+
import { callerDeclaresReusableWorkflow } from "../src/core/reusable-workflow-load-failure.js";
|
|
36
|
+
import {
|
|
37
|
+
createRunPageFetcher,
|
|
38
|
+
type GithubRequest,
|
|
39
|
+
} from "../src/core/reusable-workflow-load-adapter.js";
|
|
40
|
+
import {
|
|
41
|
+
scanForLoadFailures,
|
|
42
|
+
type ScanResult,
|
|
43
|
+
} from "../src/core/reusable-workflow-load-scan.js";
|
|
44
|
+
|
|
45
|
+
const run = promisify(execFile);
|
|
46
|
+
|
|
47
|
+
/** Where caller workflows live, relative to the repository root. */
|
|
48
|
+
const WORKFLOW_DIR = ".github/workflows";
|
|
49
|
+
|
|
50
|
+
/** How far back to scan, in hours. */
|
|
51
|
+
const WINDOW_HOURS = 26;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Page cap.
|
|
55
|
+
*
|
|
56
|
+
* Hitting it is NOT coverage — the scan reports the window as uncovered and
|
|
57
|
+
* this script exits non-zero, because a cap is a budget somebody set and not
|
|
58
|
+
* evidence about the runs beyond it.
|
|
59
|
+
*/
|
|
60
|
+
const MAX_PAGES = 20;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build the population test by reading the callers' own source.
|
|
64
|
+
*
|
|
65
|
+
* Derived, never enumerated: there is no list of known callers here, because a
|
|
66
|
+
* roster silently excludes every workflow nobody remembered to add and the
|
|
67
|
+
* exclusion looks exactly like a pass.
|
|
68
|
+
* @param root - Repository root
|
|
69
|
+
* @returns A predicate over a run's caller `path`
|
|
70
|
+
*/
|
|
71
|
+
export async function buildPopulationTest(
|
|
72
|
+
root: string
|
|
73
|
+
): Promise<(runPath: string) => boolean> {
|
|
74
|
+
const dir = path.join(root, WORKFLOW_DIR);
|
|
75
|
+
const names = await readdir(dir).catch(() => [] as string[]);
|
|
76
|
+
const declaring = await Promise.all(
|
|
77
|
+
names
|
|
78
|
+
.filter(name => name.endsWith(".yml") || name.endsWith(".yaml"))
|
|
79
|
+
.map(async name => {
|
|
80
|
+
const source = await readFile(path.join(dir, name), "utf8").catch(
|
|
81
|
+
() => ""
|
|
82
|
+
);
|
|
83
|
+
return callerDeclaresReusableWorkflow(source) ? name : "";
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
const declared = new Set(declaring.filter(name => name !== ""));
|
|
87
|
+
return (runPath: string): boolean => declared.has(path.basename(runPath));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A request function backed by the `gh` CLI.
|
|
92
|
+
* @param repo - Repository in `owner/name` form, used only for error text
|
|
93
|
+
* @returns A request function returning decoded JSON
|
|
94
|
+
*/
|
|
95
|
+
export function ghRequest(repo: string): GithubRequest {
|
|
96
|
+
return async (apiPath: string): Promise<unknown> => {
|
|
97
|
+
const { stdout } = await run("gh", ["api", apiPath], {
|
|
98
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(stdout) as unknown;
|
|
102
|
+
} catch {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`check-workflow-load-failures: \`gh api ${apiPath}\` on ${repo} returned a body that is not JSON. Treating that as "nothing found" would be a pass produced by a broken read, so this is an error.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Render the scan's answer for an operator.
|
|
112
|
+
* @param result - What the scan found, and whether it could see
|
|
113
|
+
* @returns Operator-readable lines
|
|
114
|
+
*/
|
|
115
|
+
export function describe(result: ScanResult): string {
|
|
116
|
+
if (!result.covered) {
|
|
117
|
+
return `check-workflow-load-failures: INCOMPLETE. The scan ${result.reason} It inspected ${result.inspected} run(s), but that is not the same as having covered the window, so this is an error rather than a pass.`;
|
|
118
|
+
}
|
|
119
|
+
if (result.loadFailures.length === 0) {
|
|
120
|
+
return `check-workflow-load-failures: OK. ${result.inspected} run(s) inspected across the last ${WINDOW_HOURS}h; every caller that declared a reusable workflow resolved one.`;
|
|
121
|
+
}
|
|
122
|
+
const lines = result.loadFailures.map(
|
|
123
|
+
finding =>
|
|
124
|
+
` - run ${finding.id} (${finding.path}) declared a reusable workflow and resolved none`
|
|
125
|
+
);
|
|
126
|
+
return [
|
|
127
|
+
`check-workflow-load-failures: ${result.loadFailures.length} run(s) failed to LOAD a reusable workflow.`,
|
|
128
|
+
...lines,
|
|
129
|
+
"",
|
|
130
|
+
"A load failure creates NO jobs, so there is no red job to open and no annotation naming the line. Check the upstream workflow's most recent commit for a syntax or schema error.",
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Scan, report, and choose an exit code.
|
|
136
|
+
* @param repo - Repository in `owner/name` form
|
|
137
|
+
* @param root - Repository root
|
|
138
|
+
* @returns 0 when the window was covered and clean, 1 otherwise
|
|
139
|
+
*/
|
|
140
|
+
export async function main(repo: string, root: string): Promise<number> {
|
|
141
|
+
const windowStart = new Date(
|
|
142
|
+
Date.now() - WINDOW_HOURS * 60 * 60 * 1000
|
|
143
|
+
).toISOString();
|
|
144
|
+
|
|
145
|
+
const result = await scanForLoadFailures({
|
|
146
|
+
fetchPage: createRunPageFetcher({ repo, request: ghRequest(repo) }),
|
|
147
|
+
windowStart,
|
|
148
|
+
inPopulation: await buildPopulationTest(root),
|
|
149
|
+
maxPages: MAX_PAGES,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log(describe(result));
|
|
153
|
+
return result.covered && result.loadFailures.length === 0 ? 0 : 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* c8 ignore start -- entry point, exercised by the workflow rather than a test */
|
|
157
|
+
if (
|
|
158
|
+
process.argv[1] !== undefined &&
|
|
159
|
+
import.meta.url.endsWith(path.basename(process.argv[1]))
|
|
160
|
+
) {
|
|
161
|
+
const repo = process.env["GITHUB_REPOSITORY"] ?? "CodySwannGT/lisa";
|
|
162
|
+
main(repo, process.cwd())
|
|
163
|
+
.then(code => {
|
|
164
|
+
process.exitCode = code;
|
|
165
|
+
})
|
|
166
|
+
.catch((error: unknown) => {
|
|
167
|
+
console.error(String(error));
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/* c8 ignore stop */
|