@codyswann/lisa 3.47.5 โ 3.47.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/lib/gate-failure-diagnosis.mjs +270 -0
- package/all/copy-overwrite/scripts/lisa-gates.mjs +30 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +294 -34
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +83 -26
- package/dist/configs/eslint/expo.d.ts +50 -1
- package/dist/configs/eslint/expo.d.ts.map +1 -1
- package/dist/configs/eslint/expo.js +86 -5
- package/dist/configs/eslint/expo.js.map +1 -1
- package/dist/core/downstream-names.d.ts +83 -0
- package/dist/core/downstream-names.d.ts.map +1 -0
- package/dist/core/downstream-names.js +289 -0
- package/dist/core/downstream-names.js.map +1 -0
- package/dist/core/downstream-references.d.ts +21 -6
- package/dist/core/downstream-references.d.ts.map +1 -1
- package/dist/core/downstream-references.js +19 -16
- package/dist/core/downstream-references.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +14 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +21 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-tsconfig-local-includes.d.ts +1 -1
- package/dist/migrations/ensure-tsconfig-local-includes.js +1 -1
- package/expo/copy-overwrite/eslint.config.ts +9 -0
- package/expo/copy-overwrite/eslint.expo.ts +92 -4
- package/package.json +5 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/rules/reference/design-value-binding.md +4 -0
- 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/rules/reference/design-value-binding.md +4 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +4 -0
- 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/rules/reference/design-value-binding.md +4 -0
- package/scripts/lisa-mutation.mjs +13 -0
- package/typescript/copy-contents/.husky/pre-push +7 -1
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +496 -122
- package/typescript/package-lisa/package.lisa.json +1 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly โ durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Say WHICH thing failed, from a failed gate's own output.
|
|
6
|
+
*
|
|
7
|
+
* ## Why a bare exit code is a defect and not just terse
|
|
8
|
+
*
|
|
9
|
+
* `coverage-adequacy โ bun run test:cov (exit 1)` is the same sentence whether
|
|
10
|
+
* the suite measured 84% against a floor of 86%, or whether four
|
|
11
|
+
* subprocess-heavy tests were starved of CPU and hit their wall-clock budget
|
|
12
|
+
* while every assertion in the run passed. Those are opposite facts: one is a
|
|
13
|
+
* regression that must block, the other is the machine being busy.
|
|
14
|
+
*
|
|
15
|
+
* Measured across six sightings, every one was the second kind โ always a
|
|
16
|
+
* timeout, never a coverage number below threshold โ and every one was
|
|
17
|
+
* answered by a retry, because re-running is genuinely cheaper than
|
|
18
|
+
* investigating a bare exit code. That reflex is rational, and it is also why a
|
|
19
|
+
* real coverage regression would currently be invisible: it renders as the same
|
|
20
|
+
* line the operator has been trained to re-run.
|
|
21
|
+
*
|
|
22
|
+
* ## Precedence: an incomplete run is not a measurement
|
|
23
|
+
*
|
|
24
|
+
* A run whose tests timed out still prints coverage numbers, and those numbers
|
|
25
|
+
* will be low, because the code the dead tests would have exercised went
|
|
26
|
+
* unexercised. Reading a threshold error off such a run reports the *effect* of
|
|
27
|
+
* the timeout as though it were the cause. So a timeout outranks a threshold
|
|
28
|
+
* reading, and a failed assertion outranks it too โ coverage is only a
|
|
29
|
+
* measurement when the suite that produced it finished.
|
|
30
|
+
* @module lib/gate-failure-diagnosis
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** What a failed gate's output was recognised as. */
|
|
34
|
+
export const DIAGNOSIS = Object.freeze({
|
|
35
|
+
/** One or more tests or hooks exceeded their wall-clock budget. */
|
|
36
|
+
TIMEOUT: "timeout",
|
|
37
|
+
/** Tests ran to completion and failed. */
|
|
38
|
+
ASSERTION: "assertion",
|
|
39
|
+
/** Every test finished and passed; a coverage floor was not met. */
|
|
40
|
+
THRESHOLD: "threshold",
|
|
41
|
+
/** Output was read and matched nothing this module knows. */
|
|
42
|
+
UNDIAGNOSED: "undiagnosed",
|
|
43
|
+
/** No output was available to read, so nothing can be said. */
|
|
44
|
+
UNCAPTURED: "uncaptured",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** `Test timed out in 60000ms.` / `Hook timed out in 60000ms.` from vitest. */
|
|
48
|
+
const TIMEOUT_PATTERN = /(Test|Hook) timed out in (\d+)ms/g;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `ERROR: Coverage for statements (85.1%) does not meet global threshold (86%)`
|
|
52
|
+
* โ vitest's own wording, `global` or a quoted glob in the scope position.
|
|
53
|
+
*/
|
|
54
|
+
const THRESHOLD_PATTERN =
|
|
55
|
+
/Coverage for (\S+) \(([\d.]+)%\) does not meet (\S+) threshold \(([\d.]+)%\)/g;
|
|
56
|
+
|
|
57
|
+
/** vitest's tally line: `Tests 4 failed | 14272 passed (14276)`. */
|
|
58
|
+
const TALLY_PATTERN = /Tests\s+(\d+) failed/;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A failing suite header: ` FAIL tests/unit/foo.test.ts > does a thing`.
|
|
62
|
+
*
|
|
63
|
+
* Horizontal whitespace only, never `\s`. Under the `m` flag `^\s*` can consume
|
|
64
|
+
* newline after newline before failing, which is super-linear backtracking on
|
|
65
|
+
* exactly the input this module is fed: a multi-megabyte suite transcript. The
|
|
66
|
+
* shipped ruleset refuses it, and this is a parser reading untrusted-sized
|
|
67
|
+
* output inside a git hook, so the refusal is right.
|
|
68
|
+
*/
|
|
69
|
+
const FAIL_PATTERN = /^[ \t]*FAIL[ \t]+(\S+)/gm;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Which gate's property each kind of failure actually belongs to.
|
|
73
|
+
*
|
|
74
|
+
* Saying WHICH failure it was is half the repair. The other half is saying
|
|
75
|
+
* WHOSE it was, because the two gates involved legitimately share one prover:
|
|
76
|
+
* a coverage-instrumented suite proves `test-correctness` by passing and
|
|
77
|
+
* `coverage-adequacy` by clearing its floor, and one exit code cannot say
|
|
78
|
+
* which of the two it failed on. Reporting it against both is how a starved
|
|
79
|
+
* test suite rendered as a coverage regression โ and it is also why a real
|
|
80
|
+
* coverage regression could not be told from that flake.
|
|
81
|
+
*
|
|
82
|
+
* `undiagnosed` and `uncaptured` are deliberately absent. Nothing was
|
|
83
|
+
* recognised, so nothing may be attributed; the failure stays where it landed.
|
|
84
|
+
*
|
|
85
|
+
* The runner applies this only when the named gate is itself declared on the
|
|
86
|
+
* same command at the same moment, so a phrase in some unrelated tool's output
|
|
87
|
+
* can never invent an attribution.
|
|
88
|
+
*/
|
|
89
|
+
export const ATTRIBUTION = Object.freeze({
|
|
90
|
+
timeout: "test-correctness",
|
|
91
|
+
assertion: "test-correctness",
|
|
92
|
+
threshold: "coverage-adequacy",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** How many named examples a summary carries before it says "and N more". */
|
|
96
|
+
const MAX_EVIDENCE = 5;
|
|
97
|
+
|
|
98
|
+
/** Longest tail line quoted back when nothing else is recognised. */
|
|
99
|
+
const MAX_TAIL = 200;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* How many trailing lines an unrecognised failure quotes back.
|
|
103
|
+
*
|
|
104
|
+
* Three rather than one, measured on this runner's own output: the last line
|
|
105
|
+
* of a failing task-runner chain is the runner's own `exited with code 1`,
|
|
106
|
+
* which says nothing the exit code did not. The line naming the artifact to
|
|
107
|
+
* rebuild was two above it.
|
|
108
|
+
*/
|
|
109
|
+
const TAIL_LINES = 3;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* One classified failure: the kind, a clause an operator can read, and the
|
|
113
|
+
* concrete lines that back it.
|
|
114
|
+
* @typedef {object} Diagnosis
|
|
115
|
+
* @property {string} kind One of `DIAGNOSIS`.
|
|
116
|
+
* @property {string} summary A single operator-readable clause.
|
|
117
|
+
* @property {string[]} evidence Concrete names or lines supporting the summary.
|
|
118
|
+
* @property {string|null} proves The gate whose property this failure belongs
|
|
119
|
+
* to, from `ATTRIBUTION`, or null when nothing was recognised.
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Trim a list to `MAX_EVIDENCE`, saying how many were dropped.
|
|
124
|
+
* @param {string[]} items Every item found.
|
|
125
|
+
* @returns {string[]} At most `MAX_EVIDENCE + 1` lines.
|
|
126
|
+
*/
|
|
127
|
+
function capped(items) {
|
|
128
|
+
const unique = [...new Set(items)];
|
|
129
|
+
if (unique.length <= MAX_EVIDENCE) return unique;
|
|
130
|
+
return [
|
|
131
|
+
...unique.slice(0, MAX_EVIDENCE),
|
|
132
|
+
`โฆand ${unique.length - MAX_EVIDENCE} more`,
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Every timeout the output reports, with the budget each one blew.
|
|
138
|
+
* @param {string} output The gate command's combined output.
|
|
139
|
+
* @returns {{count: number, budgets: number[]}} What was found.
|
|
140
|
+
*/
|
|
141
|
+
function findTimeouts(output) {
|
|
142
|
+
const budgets = [...output.matchAll(TIMEOUT_PATTERN)].map(match =>
|
|
143
|
+
Number(match[2])
|
|
144
|
+
);
|
|
145
|
+
return { count: budgets.length, budgets };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Every coverage floor the output reports as unmet.
|
|
150
|
+
* @param {string} output The gate command's combined output.
|
|
151
|
+
* @returns {string[]} One `metric measured% < required% (scope)` per miss.
|
|
152
|
+
*/
|
|
153
|
+
function findThresholdMisses(output) {
|
|
154
|
+
return [...output.matchAll(THRESHOLD_PATTERN)].map(
|
|
155
|
+
match => `${match[1]} ${match[2]}% < ${match[4]}% (${match[3]})`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The suites the output names as failing, and the tally if one was printed.
|
|
161
|
+
* @param {string} output The gate command's combined output.
|
|
162
|
+
* @returns {{tally: number|null, suites: string[]}} What was found.
|
|
163
|
+
*/
|
|
164
|
+
function findFailures(output) {
|
|
165
|
+
const tally = TALLY_PATTERN.exec(output);
|
|
166
|
+
return {
|
|
167
|
+
tally: tally ? Number(tally[1]) : null,
|
|
168
|
+
suites: [...output.matchAll(FAIL_PATTERN)].map(match => match[1]),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The last lines that carry anything, for a failure nothing else recognised.
|
|
174
|
+
* @param {string} output The gate command's combined output.
|
|
175
|
+
* @returns {string[]} Up to `TAIL_LINES` trimmed lines, oldest first.
|
|
176
|
+
*/
|
|
177
|
+
function tailLines(output) {
|
|
178
|
+
return output
|
|
179
|
+
.split("\n")
|
|
180
|
+
.map(line => line.trim())
|
|
181
|
+
.filter(line => line.length > 0)
|
|
182
|
+
.slice(-TAIL_LINES)
|
|
183
|
+
.map(line => line.slice(0, MAX_TAIL));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* A timeout verdict, worded so it can never be mistaken for a coverage miss.
|
|
188
|
+
* @param {{count: number, budgets: number[]}} timeouts What was found.
|
|
189
|
+
* @param {string[]} suites Suites the output named as failing.
|
|
190
|
+
* @returns {Diagnosis} The verdict.
|
|
191
|
+
*/
|
|
192
|
+
function timeoutVerdict(timeouts, suites) {
|
|
193
|
+
const budget = Math.max(...timeouts.budgets);
|
|
194
|
+
return {
|
|
195
|
+
kind: DIAGNOSIS.TIMEOUT,
|
|
196
|
+
summary:
|
|
197
|
+
`${timeouts.count} test(s)/hook(s) exceeded the ${budget}ms budget, ` +
|
|
198
|
+
`so the suite did not finish โ this is NOT a coverage shortfall`,
|
|
199
|
+
evidence: capped(suites),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Classify why a gate command failed, from the output it produced.
|
|
205
|
+
*
|
|
206
|
+
* Ordered deliberately, and the order is the content of this function: a
|
|
207
|
+
* timeout outranks an assertion failure outranks a threshold miss, because
|
|
208
|
+
* coverage read off a run that did not finish measures the interruption rather
|
|
209
|
+
* than the code. Getting that backwards is the defect being fixed โ it is what
|
|
210
|
+
* printed "coverage-adequacy failed" six times for a machine under load.
|
|
211
|
+
* @param {string|null|undefined} output The command's combined output, or null
|
|
212
|
+
* when the runner could not capture it.
|
|
213
|
+
* @returns {object} What the failure was, before it is attributed.
|
|
214
|
+
*/
|
|
215
|
+
function classify(output) {
|
|
216
|
+
if (typeof output !== "string" || output.length === 0) {
|
|
217
|
+
return {
|
|
218
|
+
kind: DIAGNOSIS.UNCAPTURED,
|
|
219
|
+
summary:
|
|
220
|
+
"no output was captured, so this failure has no diagnosis " +
|
|
221
|
+
"(set LISA_GATES_CAPTURE=1 and re-run to get one)",
|
|
222
|
+
evidence: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const timeouts = findTimeouts(output);
|
|
227
|
+
const failures = findFailures(output);
|
|
228
|
+
const misses = findThresholdMisses(output);
|
|
229
|
+
|
|
230
|
+
if (timeouts.count > 0) return timeoutVerdict(timeouts, failures.suites);
|
|
231
|
+
|
|
232
|
+
if ((failures.tally ?? 0) > 0 || failures.suites.length > 0) {
|
|
233
|
+
const count = failures.tally ?? failures.suites.length;
|
|
234
|
+
return {
|
|
235
|
+
kind: DIAGNOSIS.ASSERTION,
|
|
236
|
+
summary: `${count} test(s) ran and failed`,
|
|
237
|
+
evidence: capped(failures.suites),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (misses.length > 0) {
|
|
242
|
+
return {
|
|
243
|
+
kind: DIAGNOSIS.THRESHOLD,
|
|
244
|
+
summary: `coverage is below the declared floor on ${misses.length} metric(s)`,
|
|
245
|
+
evidence: capped(misses),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
kind: DIAGNOSIS.UNDIAGNOSED,
|
|
251
|
+
summary: "no recognised failure signature; the command's last lines follow",
|
|
252
|
+
evidence: capped(tailLines(output)),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Classify a failure and say whose property it belongs to.
|
|
258
|
+
*
|
|
259
|
+
* Attribution is separated from classification on purpose. Reading a
|
|
260
|
+
* transcript is a fact about a tool's output; deciding which gate that fact
|
|
261
|
+
* indicts is a fact about the registry, and only the caller knows whether the
|
|
262
|
+
* indicted gate is part of the run at all.
|
|
263
|
+
* @param {string|null|undefined} output The command's combined output, or null
|
|
264
|
+
* when the runner could not capture it.
|
|
265
|
+
* @returns {Diagnosis} What the failure was, and whose it was.
|
|
266
|
+
*/
|
|
267
|
+
export function diagnoseFailure(output) {
|
|
268
|
+
const verdict = classify(output);
|
|
269
|
+
return { ...verdict, proves: ATTRIBUTION[verdict.kind] ?? null };
|
|
270
|
+
}
|
|
@@ -134,6 +134,26 @@ export const GATE_FIELDS = new Set(["run", "needs", "task"]);
|
|
|
134
134
|
* whoever remembers to type it, which is how the defect got here.
|
|
135
135
|
*/
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Registry flag: this gate's task costs minutes, not seconds.
|
|
139
|
+
*
|
|
140
|
+
* It exists so that a run which is already blocked can keep going without
|
|
141
|
+
* doubling the cost of a failing push. When a required gate failed, the runner
|
|
142
|
+
* used to stop dead and print every later gate as not-run โ which is how one
|
|
143
|
+
* intermittent test failure took the work-item check and the type check down
|
|
144
|
+
* with it, both of which finish in well under a minute and answer questions
|
|
145
|
+
* that have nothing to do with a test suite. Continuing into those is nearly
|
|
146
|
+
* free and tells the operator everything that is wrong in one attempt instead
|
|
147
|
+
* of one thing per attempt. Continuing into a second full suite is not free,
|
|
148
|
+
* and buys information about a push that cannot land regardless.
|
|
149
|
+
*
|
|
150
|
+
* Registry-only, for the same reason `mayRewrite` is. The safe answer must not
|
|
151
|
+
* depend on whoever remembers to type it, and Lisa knows which of its own
|
|
152
|
+
* canonical gates run a whole suite. A project that points `test-correctness`
|
|
153
|
+
* at something fast loses nothing: the flag only ever suppresses a gate on a
|
|
154
|
+
* run that is already blocked.
|
|
155
|
+
*/
|
|
156
|
+
|
|
137
157
|
/** Prefix marking a gate, or a config key, that this project invented. */
|
|
138
158
|
export const CUSTOM_PREFIX = "x-";
|
|
139
159
|
|
|
@@ -205,6 +225,7 @@ export const REGISTRY = Object.freeze({
|
|
|
205
225
|
task: "test:unit",
|
|
206
226
|
moments: PUSH_ONWARD,
|
|
207
227
|
work: "tests run",
|
|
228
|
+
costly: true,
|
|
208
229
|
},
|
|
209
230
|
"test-node-suites": {
|
|
210
231
|
label: "๐งช Run .mjs Suites",
|
|
@@ -219,6 +240,7 @@ export const REGISTRY = Object.freeze({
|
|
|
219
240
|
task: "test:integration",
|
|
220
241
|
moments: PUSH_ONWARD,
|
|
221
242
|
work: "tests run",
|
|
243
|
+
costly: true,
|
|
222
244
|
},
|
|
223
245
|
"test-meaningfulness": {
|
|
224
246
|
label: "๐งฌ Mutation Testing Gate",
|
|
@@ -226,6 +248,7 @@ export const REGISTRY = Object.freeze({
|
|
|
226
248
|
task: "test:mutation",
|
|
227
249
|
moments: PR_ONWARD,
|
|
228
250
|
work: "mutants generated",
|
|
251
|
+
costly: true,
|
|
229
252
|
},
|
|
230
253
|
"coverage-adequacy": {
|
|
231
254
|
label: "โ
Verification Coverage",
|
|
@@ -233,6 +256,7 @@ export const REGISTRY = Object.freeze({
|
|
|
233
256
|
task: "test:coverage",
|
|
234
257
|
moments: PUSH_ONWARD,
|
|
235
258
|
work: "files measured",
|
|
259
|
+
costly: true,
|
|
236
260
|
},
|
|
237
261
|
"e2e-browser": {
|
|
238
262
|
label: "๐ญ Playwright E2E Tests",
|
|
@@ -240,6 +264,7 @@ export const REGISTRY = Object.freeze({
|
|
|
240
264
|
task: "test:e2e",
|
|
241
265
|
moments: [...PR_ONWARD, CONTINUOUS],
|
|
242
266
|
work: "specs run",
|
|
267
|
+
costly: true,
|
|
243
268
|
},
|
|
244
269
|
"e2e-native": {
|
|
245
270
|
label: "๐ฑ Maestro Native E2E",
|
|
@@ -247,6 +272,7 @@ export const REGISTRY = Object.freeze({
|
|
|
247
272
|
task: "test:e2e:native",
|
|
248
273
|
moments: [...PR_ONWARD, CONTINUOUS],
|
|
249
274
|
work: "flows run",
|
|
275
|
+
costly: true,
|
|
250
276
|
},
|
|
251
277
|
// ---------------------------------------------------------------------
|
|
252
278
|
// Environment facade. Lisa defines and enforces the interface; each project
|
|
@@ -297,6 +323,7 @@ export const REGISTRY = Object.freeze({
|
|
|
297
323
|
// pinned to the per-change gate re-walks the same ground forever.
|
|
298
324
|
moments: [...PR_ONWARD, CONTINUOUS],
|
|
299
325
|
work: "cases generated",
|
|
326
|
+
costly: true,
|
|
300
327
|
},
|
|
301
328
|
"structural-rules": {
|
|
302
329
|
label: "๐ AST Grep Scan",
|
|
@@ -381,6 +408,7 @@ export const REGISTRY = Object.freeze({
|
|
|
381
408
|
task: "perf:load",
|
|
382
409
|
moments: DEPLOY_ONLY,
|
|
383
410
|
work: "requests issued",
|
|
411
|
+
costly: true,
|
|
384
412
|
},
|
|
385
413
|
accessibility: {
|
|
386
414
|
label: "โฟ Accessibility",
|
|
@@ -1400,6 +1428,7 @@ export function resolveMoment({
|
|
|
1400
1428
|
work: null,
|
|
1401
1429
|
evidence: null,
|
|
1402
1430
|
mayRewrite: false,
|
|
1431
|
+
costly: false,
|
|
1403
1432
|
});
|
|
1404
1433
|
}
|
|
1405
1434
|
continue;
|
|
@@ -1432,6 +1461,7 @@ export function resolveMoment({
|
|
|
1432
1461
|
work: definition?.work ?? null,
|
|
1433
1462
|
evidence: entry.await ? mergeEvidence(entry.evidence) : null,
|
|
1434
1463
|
mayRewrite: definition?.mayRewrite === true,
|
|
1464
|
+
costly: definition?.costly === true,
|
|
1435
1465
|
});
|
|
1436
1466
|
}
|
|
1437
1467
|
// Rewriters first, then alphabetical within each group. See `mayRewrite`:
|