@codyswann/lisa 4.34.5 → 4.34.6
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/lisa-hooks/block-managed-file-edits.sh +436 -9
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +636 -101
- package/all/copy-overwrite/scripts/lisa-test-node.mjs +44 -1
- package/dist/codex/scripts/block-no-verify.sh +608 -99
- package/dist/configs/vitest/scratch-global-setup.d.ts +29 -0
- package/dist/configs/vitest/scratch-global-setup.d.ts.map +1 -1
- package/dist/configs/vitest/scratch-global-setup.js +77 -12
- package/dist/configs/vitest/scratch-global-setup.js.map +1 -1
- package/dist/core/config-shadowing.d.ts +72 -0
- package/dist/core/config-shadowing.d.ts.map +1 -0
- package/dist/core/config-shadowing.js +152 -0
- package/dist/core/config-shadowing.js.map +1 -0
- package/dist/core/config.d.ts +16 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +1 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts +21 -0
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +46 -0
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +17 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +636 -101
- package/dist/strategies/copy-overwrite.d.ts +42 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -1
- package/dist/strategies/copy-overwrite.js +69 -5
- package/dist/strategies/copy-overwrite.js.map +1 -1
- package/expo/copy-overwrite/scripts/check-lighthouse-details.mjs +138 -11
- package/expo/create-only/.github/workflows/deploy.yml +51 -4
- 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/hooks/block-managed-file-edits.sh +436 -9
- package/plugins/lisa/hooks/block-no-verify.agy.sh +608 -99
- package/plugins/lisa/hooks/block-no-verify.sh +636 -101
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +608 -99
- 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-copilot/hooks/block-managed-file-edits.sh +436 -9
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +636 -101
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +436 -9
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +636 -101
- 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/plugins/src/base/hooks/block-managed-file-edits.sh +436 -9
- package/plugins/src/base/hooks/block-no-verify.agy.sh +608 -99
- package/plugins/src/base/hooks/block-no-verify.sh +636 -101
- package/rails/create-only/.github/workflows/deploy.yml +45 -1
|
@@ -68,6 +68,48 @@ export declare class CopyOverwriteStrategy implements ICopyStrategy {
|
|
|
68
68
|
* @returns A preserved result when the copy is kept, else undefined
|
|
69
69
|
*/
|
|
70
70
|
private preserveOwnedHostCopy;
|
|
71
|
+
/**
|
|
72
|
+
* Create a managed file the destination does not have.
|
|
73
|
+
*
|
|
74
|
+
* Its own method because this branch is where CodySwannGT/lisa#3501 lives.
|
|
75
|
+
* Every guard in {@link apply} asks whether THIS path exists, and each is
|
|
76
|
+
* blind to a differently-spelled file that outranks it: a repo configuring
|
|
77
|
+
* knip as `knip.ts` has no `knip.json`, so the template lands as a NEW file
|
|
78
|
+
* that knip then prefers, silently replacing the repo's own settings with
|
|
79
|
+
* stack defaults. The question has to be asked here, before the write,
|
|
80
|
+
* because there is no later branch — the create path is the whole defect.
|
|
81
|
+
* @param sourcePath - Packaged template path
|
|
82
|
+
* @param destPath - Installed file path, which does not yet exist
|
|
83
|
+
* @param relativePath - Repo-relative path for reporting
|
|
84
|
+
* @param context - Strategy context with config
|
|
85
|
+
* @returns The `copied` result, or `shadowed` when Lisa stands down
|
|
86
|
+
*/
|
|
87
|
+
private createMissingFile;
|
|
88
|
+
/**
|
|
89
|
+
* The existing config file this template would outrank, if any.
|
|
90
|
+
*
|
|
91
|
+
* **Seed templates only, and the exclusion is the safety argument.** A
|
|
92
|
+
* template that declares `IS replaced on each` is a governance artifact —
|
|
93
|
+
* `eslint.config.ts`, `vitest.config.ts`, `commitlint.config.cjs`,
|
|
94
|
+
* `sgconfig.yml` — where writing the file IS the guardrail. Standing down
|
|
95
|
+
* because a same-family file exists would let any host silently disable
|
|
96
|
+
* Lisa's enforcement by dropping in an `eslint.config.js`, with nothing
|
|
97
|
+
* reporting it: #2374's undeliverable-fix incident re-entering through the
|
|
98
|
+
* door opened to fix #3501. For those, a same-family host file is a conflict
|
|
99
|
+
* to surface, never a reason not to write.
|
|
100
|
+
*
|
|
101
|
+
* Reuses `declaresReplacedEveryRun` rather than introducing a second notion
|
|
102
|
+
* of who owns a file. Two overlapping ownership definitions is how the next
|
|
103
|
+
* defect of this shape gets built.
|
|
104
|
+
*
|
|
105
|
+
* Returns a result only when Lisa is standing down, so the caller falls
|
|
106
|
+
* through to its normal create path in every other case.
|
|
107
|
+
* @param sourcePath - Packaged template path, read for its own declaration
|
|
108
|
+
* @param destPath - Installed file path, whose directory is the search root
|
|
109
|
+
* @param relativePath - Repo-relative path of the managed template
|
|
110
|
+
* @returns A `shadowed` result when the template is withheld, else undefined
|
|
111
|
+
*/
|
|
112
|
+
private standDownForHostConfig;
|
|
71
113
|
/**
|
|
72
114
|
* Hold back a Lisa-owned refresh that cannot be proved to be an upgrade.
|
|
73
115
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-overwrite.d.ts","sourceRoot":"","sources":["../../src/strategies/copy-overwrite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"copy-overwrite.d.ts","sourceRoot":"","sources":["../../src/strategies/copy-overwrite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgB7D,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG9E;;;;;;GAMG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAE1C;;;;;;;OAOG;IACG,KAAK,CACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;IA+H/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,mBAAmB;IAwBjC;;;;;;;;;;;;;;;;;OAiBG;YACW,qBAAqB;IAkBnC;;;;;;;;;;;;;;;OAeG;YACW,iBAAiB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,sBAAsB;IA2BpC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;YACW,mBAAmB;CAkClC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fse from "fs-extra";
|
|
2
2
|
import { copyFile, readFile } from "node:fs/promises";
|
|
3
3
|
import { mayRefreshTemplate } from "../core/config.js";
|
|
4
|
+
import { shadowedConfigNote, shadowedConfigSibling, } from "../core/config-shadowing.js";
|
|
4
5
|
import { declaresReplacedEveryRun, isLisaOwnedTemplate, } from "../core/lisa-owned-templates.js";
|
|
5
6
|
import { classifyHostCopy, describePreserved, describeUnclassifiable, mayRefreshLisaOwned, } from "../core/lisa-owned-provenance.js";
|
|
6
7
|
import { filesIdentical, ensureParentDir } from "../utils/file-operations.js";
|
|
@@ -25,11 +26,7 @@ export class CopyOverwriteStrategy {
|
|
|
25
26
|
const { config, backupFile, promptOverwrite } = context;
|
|
26
27
|
const destExists = await fse.pathExists(destPath);
|
|
27
28
|
if (!destExists) {
|
|
28
|
-
|
|
29
|
-
await ensureParentDir(destPath);
|
|
30
|
-
await copyFile(sourcePath, destPath);
|
|
31
|
-
}
|
|
32
|
-
return { relativePath, strategy: this.name, action: "copied" };
|
|
29
|
+
return this.createMissingFile(sourcePath, destPath, relativePath, context);
|
|
33
30
|
}
|
|
34
31
|
if (await filesIdentical(sourcePath, destPath)) {
|
|
35
32
|
return { relativePath, strategy: this.name, action: "skipped" };
|
|
@@ -192,6 +189,73 @@ export class CopyOverwriteStrategy {
|
|
|
192
189
|
}
|
|
193
190
|
return this.preserveIfHostAhead(sourcePath, destPath, relativePath, context.hashLedger);
|
|
194
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Create a managed file the destination does not have.
|
|
194
|
+
*
|
|
195
|
+
* Its own method because this branch is where CodySwannGT/lisa#3501 lives.
|
|
196
|
+
* Every guard in {@link apply} asks whether THIS path exists, and each is
|
|
197
|
+
* blind to a differently-spelled file that outranks it: a repo configuring
|
|
198
|
+
* knip as `knip.ts` has no `knip.json`, so the template lands as a NEW file
|
|
199
|
+
* that knip then prefers, silently replacing the repo's own settings with
|
|
200
|
+
* stack defaults. The question has to be asked here, before the write,
|
|
201
|
+
* because there is no later branch — the create path is the whole defect.
|
|
202
|
+
* @param sourcePath - Packaged template path
|
|
203
|
+
* @param destPath - Installed file path, which does not yet exist
|
|
204
|
+
* @param relativePath - Repo-relative path for reporting
|
|
205
|
+
* @param context - Strategy context with config
|
|
206
|
+
* @returns The `copied` result, or `shadowed` when Lisa stands down
|
|
207
|
+
*/
|
|
208
|
+
async createMissingFile(sourcePath, destPath, relativePath, context) {
|
|
209
|
+
const shadowed = await this.standDownForHostConfig(sourcePath, destPath, relativePath);
|
|
210
|
+
if (shadowed !== undefined)
|
|
211
|
+
return shadowed;
|
|
212
|
+
if (!context.config.dryRun) {
|
|
213
|
+
await ensureParentDir(destPath);
|
|
214
|
+
await copyFile(sourcePath, destPath);
|
|
215
|
+
}
|
|
216
|
+
return { relativePath, strategy: this.name, action: "copied" };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The existing config file this template would outrank, if any.
|
|
220
|
+
*
|
|
221
|
+
* **Seed templates only, and the exclusion is the safety argument.** A
|
|
222
|
+
* template that declares `IS replaced on each` is a governance artifact —
|
|
223
|
+
* `eslint.config.ts`, `vitest.config.ts`, `commitlint.config.cjs`,
|
|
224
|
+
* `sgconfig.yml` — where writing the file IS the guardrail. Standing down
|
|
225
|
+
* because a same-family file exists would let any host silently disable
|
|
226
|
+
* Lisa's enforcement by dropping in an `eslint.config.js`, with nothing
|
|
227
|
+
* reporting it: #2374's undeliverable-fix incident re-entering through the
|
|
228
|
+
* door opened to fix #3501. For those, a same-family host file is a conflict
|
|
229
|
+
* to surface, never a reason not to write.
|
|
230
|
+
*
|
|
231
|
+
* Reuses `declaresReplacedEveryRun` rather than introducing a second notion
|
|
232
|
+
* of who owns a file. Two overlapping ownership definitions is how the next
|
|
233
|
+
* defect of this shape gets built.
|
|
234
|
+
*
|
|
235
|
+
* Returns a result only when Lisa is standing down, so the caller falls
|
|
236
|
+
* through to its normal create path in every other case.
|
|
237
|
+
* @param sourcePath - Packaged template path, read for its own declaration
|
|
238
|
+
* @param destPath - Installed file path, whose directory is the search root
|
|
239
|
+
* @param relativePath - Repo-relative path of the managed template
|
|
240
|
+
* @returns A `shadowed` result when the template is withheld, else undefined
|
|
241
|
+
*/
|
|
242
|
+
async standDownForHostConfig(sourcePath, destPath, relativePath) {
|
|
243
|
+
// Read once, ahead of any probe: the declaration is a property of the
|
|
244
|
+
// template, not of the candidate being tested.
|
|
245
|
+
if (declaresReplacedEveryRun(await readFile(sourcePath, "utf8"))) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
const projectRoot = destPath.slice(0, destPath.length - relativePath.length);
|
|
249
|
+
const shadowed = await shadowedConfigSibling(relativePath, candidate => fse.pathExists(`${projectRoot}${candidate}`));
|
|
250
|
+
if (shadowed === undefined)
|
|
251
|
+
return undefined;
|
|
252
|
+
return {
|
|
253
|
+
relativePath,
|
|
254
|
+
strategy: this.name,
|
|
255
|
+
action: "shadowed",
|
|
256
|
+
note: shadowedConfigNote(relativePath, shadowed.tool, shadowed.sibling),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
195
259
|
/**
|
|
196
260
|
* Hold back a Lisa-owned refresh that cannot be proved to be an upgrade.
|
|
197
261
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-overwrite.js","sourceRoot":"","sources":["../../src/strategies/copy-overwrite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EACL,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,OAAO,qBAAqB;IACvB,IAAI,GAAG,gBAAyB,CAAC;IAE1C;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,CACT,UAAkB,EAClB,QAAgB,EAChB,YAAoB,EACpB,OAAwB;QAExB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;QACxD,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAElD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"copy-overwrite.js","sourceRoot":"","sources":["../../src/strategies/copy-overwrite.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
* table. Its score is effectively binary, so a score floor makes unchanged
|
|
10
10
|
* pages alternate between green and red as runner timing changes. LHCI writes
|
|
11
11
|
* the measurement into each LHR; this checker applies a real millisecond
|
|
12
|
-
* ceiling to
|
|
12
|
+
* ceiling to that evidence.
|
|
13
|
+
*
|
|
14
|
+
* The ceiling is asserted against the **median of a URL's runs**, not against
|
|
15
|
+
* any single run. A forced-reflow regression is deterministic — code that
|
|
16
|
+
* reads layout inside a loop does so on every load — so it moves the median.
|
|
17
|
+
* A single cold run does not: the first Lighthouse run of a URL, immediately
|
|
18
|
+
* after the runs of the previous URL, routinely reports a warmup spike that no
|
|
19
|
+
* later run of the same URL reproduces. Asserting a maximum made that spike
|
|
20
|
+
* indistinguishable from a regression, so the gate's verdict was uncorrelated
|
|
21
|
+
* with the change under review.
|
|
13
22
|
* @module scripts/check-lighthouse-details
|
|
14
23
|
*/
|
|
15
24
|
|
|
@@ -21,6 +30,29 @@ const CONFIG_FILE = "lighthouserc-config.json";
|
|
|
21
30
|
const REPORT_DIRECTORY = ".lighthouseci";
|
|
22
31
|
const REPORT_PATTERN = /^lhr-.*\.json$/;
|
|
23
32
|
const DEFAULT_MAX_FORCED_REFLOW_MS = 100;
|
|
33
|
+
const RUN_COUNT_SETTING = "collect.numberOfRuns";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fewest runs per URL that a median can absorb a warmup spike from.
|
|
37
|
+
*
|
|
38
|
+
* With two samples the median is their mean, so one cold spike still drags the
|
|
39
|
+
* statistic halfway to itself — the defect survives, attenuated. Three is the
|
|
40
|
+
* smallest count at which a single outlier cannot move the median at all.
|
|
41
|
+
*/
|
|
42
|
+
const MINIMUM_SAMPLES_PER_URL = 3;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* LHR fields naming the audited page, best first.
|
|
46
|
+
*
|
|
47
|
+
* Lighthouse renamed `finalUrl` to `finalDisplayedUrl` and reports differ by
|
|
48
|
+
* version, so grouping cannot depend on any single one of them.
|
|
49
|
+
*/
|
|
50
|
+
const URL_FIELDS = [
|
|
51
|
+
"finalUrl",
|
|
52
|
+
"finalDisplayedUrl",
|
|
53
|
+
"requestedUrl",
|
|
54
|
+
"mainDocumentUrl",
|
|
55
|
+
];
|
|
24
56
|
|
|
25
57
|
/**
|
|
26
58
|
* Read the bottom-up aggregate table from one forced-reflow audit.
|
|
@@ -66,10 +98,43 @@ function readAggregateMeasurements(details) {
|
|
|
66
98
|
});
|
|
67
99
|
}
|
|
68
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Identify which page one Lighthouse result measured.
|
|
103
|
+
* Samples are grouped by this value, so an unidentifiable report is a hard
|
|
104
|
+
* failure rather than a group of its own — a report grouped alone would be
|
|
105
|
+
* reported as a run-count shortfall, which names the wrong remedy.
|
|
106
|
+
* @param {Record<string, unknown>} report - Parsed LHR
|
|
107
|
+
* @returns {string} Audited URL
|
|
108
|
+
*/
|
|
109
|
+
function readReportUrl(report) {
|
|
110
|
+
for (const field of URL_FIELDS) {
|
|
111
|
+
const value = report?.[field];
|
|
112
|
+
if (typeof value === "string" && value.length > 0) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw new Error(
|
|
117
|
+
`no URL to group runs by; expected one of ${URL_FIELDS.join(", ")}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Middle value of a sample set, averaging the two middles when even.
|
|
123
|
+
* @param {readonly number[]} values - Non-empty sample set
|
|
124
|
+
* @returns {number} Median
|
|
125
|
+
*/
|
|
126
|
+
function median(values) {
|
|
127
|
+
const sorted = [...values].sort((first, second) => first - second);
|
|
128
|
+
const middle = Math.floor(sorted.length / 2);
|
|
129
|
+
return sorted.length % 2 === 0
|
|
130
|
+
? (sorted[middle - 1] + sorted[middle]) / 2
|
|
131
|
+
: sorted[middle];
|
|
132
|
+
}
|
|
133
|
+
|
|
69
134
|
/**
|
|
70
135
|
* Read the host threshold, falling back to Lisa's shipped default.
|
|
71
136
|
* @param {string} projectRoot - Host project root
|
|
72
|
-
* @returns {number} Maximum forced-reflow duration per
|
|
137
|
+
* @returns {number} Maximum median forced-reflow duration per URL
|
|
73
138
|
*/
|
|
74
139
|
function readMaximum(projectRoot) {
|
|
75
140
|
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
@@ -116,10 +181,12 @@ function readReport(reportPath) {
|
|
|
116
181
|
}
|
|
117
182
|
}
|
|
118
183
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
184
|
+
/**
|
|
185
|
+
* List the Lighthouse result files, refusing to pass on absent evidence.
|
|
186
|
+
* @param {string} projectRoot - Host project root
|
|
187
|
+
* @returns {string[]} LHR paths, sorted
|
|
188
|
+
*/
|
|
189
|
+
function readReportPaths(projectRoot) {
|
|
123
190
|
const reportDirectory = path.join(projectRoot, REPORT_DIRECTORY);
|
|
124
191
|
if (!fs.existsSync(reportDirectory)) {
|
|
125
192
|
throw new Error(
|
|
@@ -134,9 +201,19 @@ function main() {
|
|
|
134
201
|
if (reportPaths.length === 0) {
|
|
135
202
|
throw new Error(`${REPORT_DIRECTORY} contains no Lighthouse result JSON`);
|
|
136
203
|
}
|
|
204
|
+
return reportPaths;
|
|
205
|
+
}
|
|
137
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Total the forced-reflow evidence in every report, grouped by audited URL.
|
|
209
|
+
* Evidence-integrity problems are collected rather than thrown so that one
|
|
210
|
+
* malformed report names itself instead of masking the rest.
|
|
211
|
+
* @param {readonly string[]} reportPaths - LHR paths
|
|
212
|
+
* @returns {{ samplesByUrl: Map<string, number[]>, failures: string[] }} Totals and integrity failures
|
|
213
|
+
*/
|
|
214
|
+
function collectSamples(reportPaths) {
|
|
215
|
+
const samplesByUrl = new Map();
|
|
138
216
|
const failures = [];
|
|
139
|
-
let observedMaximum = 0;
|
|
140
217
|
for (const reportPath of reportPaths) {
|
|
141
218
|
const report = readReport(reportPath);
|
|
142
219
|
const audit = report?.audits?.[AUDIT_ID];
|
|
@@ -145,8 +222,10 @@ function main() {
|
|
|
145
222
|
continue;
|
|
146
223
|
}
|
|
147
224
|
let measurements;
|
|
225
|
+
let url;
|
|
148
226
|
try {
|
|
149
227
|
measurements = readAggregateMeasurements(audit.details);
|
|
228
|
+
url = readReportUrl(report);
|
|
150
229
|
} catch (error) {
|
|
151
230
|
failures.push(
|
|
152
231
|
`${path.basename(reportPath)}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -163,21 +242,69 @@ function main() {
|
|
|
163
242
|
(sum, measurement) => sum + measurement,
|
|
164
243
|
0
|
|
165
244
|
);
|
|
166
|
-
|
|
167
|
-
if (
|
|
245
|
+
const samples = samplesByUrl.get(url);
|
|
246
|
+
if (samples) {
|
|
247
|
+
samples.push(total);
|
|
248
|
+
} else {
|
|
249
|
+
samplesByUrl.set(url, [total]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { samplesByUrl, failures };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Assert the configured ceiling against each URL's median.
|
|
257
|
+
* A URL with too few runs fails rather than falling back to a maximum, because
|
|
258
|
+
* the median of one sample is that sample: without this, `numberOfRuns: 1`
|
|
259
|
+
* would silently restore a per-run maximum under the name of a median.
|
|
260
|
+
* @param {Map<string, number[]>} samplesByUrl - Per-URL run totals
|
|
261
|
+
* @param {number} maximum - Configured millisecond ceiling
|
|
262
|
+
* @returns {{ failures: string[], largestMedian: number }} Budget failures and the largest median seen
|
|
263
|
+
*/
|
|
264
|
+
function assertMedians(samplesByUrl, maximum) {
|
|
265
|
+
const failures = [];
|
|
266
|
+
let largestMedian = 0;
|
|
267
|
+
for (const [url, samples] of samplesByUrl) {
|
|
268
|
+
if (samples.length < MINIMUM_SAMPLES_PER_URL) {
|
|
269
|
+
failures.push(
|
|
270
|
+
`${url}: ${samples.length} run${samples.length === 1 ? "" : "s"} is fewer than the ${MINIMUM_SAMPLES_PER_URL} needed for a median; raise ${RUN_COUNT_SETTING} in ${CONFIG_FILE} to at least ${MINIMUM_SAMPLES_PER_URL}`
|
|
271
|
+
);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const middle = median(samples);
|
|
275
|
+
largestMedian = Math.max(largestMedian, middle);
|
|
276
|
+
if (middle > maximum) {
|
|
168
277
|
failures.push(
|
|
169
|
-
`${
|
|
278
|
+
`${url}: median ${middle.toFixed(1)} ms of ${samples.length} runs exceeds ${maximum} ms`
|
|
170
279
|
);
|
|
171
280
|
}
|
|
172
281
|
}
|
|
282
|
+
return { failures, largestMedian };
|
|
283
|
+
}
|
|
173
284
|
|
|
285
|
+
/** Run the detail budget against the current project. */
|
|
286
|
+
function main() {
|
|
287
|
+
const projectRoot = process.cwd();
|
|
288
|
+
const maximum = readMaximum(projectRoot);
|
|
289
|
+
const reportPaths = readReportPaths(projectRoot);
|
|
290
|
+
|
|
291
|
+
const { samplesByUrl, failures: evidenceFailures } =
|
|
292
|
+
collectSamples(reportPaths);
|
|
293
|
+
if (evidenceFailures.length > 0) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`forced-reflow detail budget failed:\n${evidenceFailures.map(failure => `- ${failure}`).join("\n")}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { failures, largestMedian } = assertMedians(samplesByUrl, maximum);
|
|
174
300
|
if (failures.length > 0) {
|
|
175
301
|
throw new Error(
|
|
176
302
|
`forced-reflow detail budget failed:\n${failures.map(failure => `- ${failure}`).join("\n")}`
|
|
177
303
|
);
|
|
178
304
|
}
|
|
305
|
+
|
|
179
306
|
console.log(
|
|
180
|
-
`lighthouse detail budget passed: ${reportPaths.length} report(s), forced reflow <= ${maximum} ms (
|
|
307
|
+
`lighthouse detail budget passed: ${reportPaths.length} report(s) across ${samplesByUrl.size} URL(s), forced reflow median <= ${maximum} ms (largest median ${largestMedian.toFixed(1)} ms)`
|
|
181
308
|
);
|
|
182
309
|
}
|
|
183
310
|
|
|
@@ -211,14 +211,61 @@ jobs:
|
|
|
211
211
|
release,
|
|
212
212
|
check_app_config_changes,
|
|
213
213
|
]
|
|
214
|
-
#
|
|
214
|
+
# Runs when EAS is set up, regardless of whether trigger_eas_build ran or
|
|
215
|
+
# was skipped — and, deliberately, EVEN WHEN THE RELEASE DID NOT SUCCEED.
|
|
216
|
+
#
|
|
217
|
+
# This condition used to end in `needs.release.result == 'success'`, which
|
|
218
|
+
# made a failed release SKIP this job. GitHub renders a skipped job as
|
|
219
|
+
# neutral and treats a skipped required check as satisfied, so a deploy that
|
|
220
|
+
# never happened left no signal anywhere: the branch still had its release
|
|
221
|
+
# commit, versions still matched across branches, and nothing prompted
|
|
222
|
+
# anyone to go read CI history for skipped Deploy jobs. The release result
|
|
223
|
+
# is now read by the guard STEP below, which fails this job loudly (#3467).
|
|
224
|
+
#
|
|
225
|
+
# `!cancelled()` rather than `always()`: it still suppresses the implicit
|
|
226
|
+
# `success()`, so the job runs on a failed release, while a run an operator
|
|
227
|
+
# cancelled does not get a verdict it never earned. EAS not being set up is
|
|
228
|
+
# a genuine "not applicable" and still skips.
|
|
215
229
|
if: |
|
|
216
|
-
|
|
217
|
-
needs.check_eas_setup.outputs.has_eas_setup == 'true'
|
|
218
|
-
needs.release.result == 'success'
|
|
230
|
+
!cancelled() &&
|
|
231
|
+
needs.check_eas_setup.outputs.has_eas_setup == 'true'
|
|
219
232
|
permissions:
|
|
220
233
|
contents: read
|
|
221
234
|
steps:
|
|
235
|
+
# Deploy-outcome guard. Deliberately the FIRST step: it decides whether the
|
|
236
|
+
# rest of this job may proceed, and it reports a verdict on EVERY run, so
|
|
237
|
+
# this check is green or red and never the third, silent state.
|
|
238
|
+
#
|
|
239
|
+
# Keep the `run:` body byte-identical to the guard in every other Lisa
|
|
240
|
+
# deploy workflow — an operator reads the same words whichever stack they
|
|
241
|
+
# are on, and a test asserts the copies have not drifted apart.
|
|
242
|
+
- name: 🚨 Confirm the release shipped
|
|
243
|
+
env:
|
|
244
|
+
DEPLOY_ENVIRONMENT: ${{ needs.determine_environment.outputs.environment }}
|
|
245
|
+
RELEASE_RESULT: ${{ needs.release.result }}
|
|
246
|
+
run: |
|
|
247
|
+
if [ "${RELEASE_RESULT}" = "success" ]; then
|
|
248
|
+
echo "✅ The release succeeded. Deploying to ${DEPLOY_ENVIRONMENT}."
|
|
249
|
+
exit 0
|
|
250
|
+
fi
|
|
251
|
+
{
|
|
252
|
+
echo "### 🚨 Nothing new was deployed to ${DEPLOY_ENVIRONMENT}"
|
|
253
|
+
echo ""
|
|
254
|
+
echo "The **Release** step did not succeed (it reported \`${RELEASE_RESULT}\`), so no"
|
|
255
|
+
echo "new version reached \`${DEPLOY_ENVIRONMENT}\`. Whatever was already running"
|
|
256
|
+
echo "there is untouched and still working — this change simply has not shipped."
|
|
257
|
+
echo ""
|
|
258
|
+
echo "**What to do next**"
|
|
259
|
+
echo ""
|
|
260
|
+
echo "1. Open the **Release** job in this run and read the step marked in red."
|
|
261
|
+
echo "2. The most common cause is that this version number was already released from"
|
|
262
|
+
echo " another branch. The fix is to raise \`version\` in \`package.json\` above every"
|
|
263
|
+
echo " version already released, then merge again."
|
|
264
|
+
echo "3. If anything here is unclear, send this run's link to whoever looks after"
|
|
265
|
+
echo " deployments. Do not merge anything else into this branch until it is fixed."
|
|
266
|
+
} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
|
|
267
|
+
echo "::error title=Nothing new was deployed to ${DEPLOY_ENVIRONMENT}::The release did not succeed (${RELEASE_RESULT}), so this change did not ship and the previous version is still running. Open the Release job in this run to see why, fix it, then merge again."
|
|
268
|
+
exit 1
|
|
222
269
|
- name: Checkout repository
|
|
223
270
|
uses: actions/checkout@v6
|
|
224
271
|
with:
|
package/package.json
CHANGED
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
"zod-validation-error": "^4.0.0"
|
|
167
167
|
},
|
|
168
168
|
"name": "@codyswann/lisa",
|
|
169
|
-
"version": "4.34.
|
|
169
|
+
"version": "4.34.6",
|
|
170
170
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
171
171
|
"main": "dist/index.js",
|
|
172
172
|
"exports": {
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
"test": "tests"
|
|
315
315
|
},
|
|
316
316
|
"types": "./dist/index.d.ts",
|
|
317
|
-
"lisaReleaseCommit": "
|
|
318
|
-
"gitHead": "
|
|
319
|
-
"lisaReleaseTag": "v4.34.
|
|
317
|
+
"lisaReleaseCommit": "825a67c494db413e3fb3305e1db7c2048d8ecdfa",
|
|
318
|
+
"gitHead": "825a67c494db413e3fb3305e1db7c2048d8ecdfa",
|
|
319
|
+
"lisaReleaseTag": "v4.34.6"
|
|
320
320
|
}
|