@codyswann/lisa 3.69.4 → 3.69.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-gates.mjs +200 -15
- package/dist/cli/doctor-skip-jobs-migration.d.ts.map +1 -1
- package/dist/cli/doctor-skip-jobs-migration.js +6 -0
- package/dist/cli/doctor-skip-jobs-migration.js.map +1 -1
- package/dist/cli/gate-report-sections.d.ts.map +1 -1
- package/dist/cli/gate-report-sections.js +2 -0
- package/dist/cli/gate-report-sections.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +11 -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 +13 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/bdd/contract.mjs +41 -2
- package/expo/copy-overwrite/scripts/bdd/envelope.mjs +18 -68
- package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +135 -206
- package/expo/create-only/bdd/coverage-map.json +1 -7
- package/package.json +1 -1
- 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-learnings-budget.ts +93 -18
- package/typescript/create-only/.github/workflows/required-checks-drift.yml +10 -4
- package/typescript/github-rulesets/quality-checks.json +4 -0
|
@@ -11,20 +11,21 @@
|
|
|
11
11
|
* returned, and what is WAIVED. Traceability coverage is not execution
|
|
12
12
|
* coverage and is never a pass rate.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* ONE ADOPTION CONTROL, and it is not this file's. Whether the property is
|
|
15
|
+
* governed at all is decided by the gate declaration — `required`, `optional`,
|
|
16
|
+
* or `off` — the same declaration every other quality job answers to. This gate
|
|
17
|
+
* used to carry a second, private axis (`BDD_MODE`, plus an `adoption` block in
|
|
18
|
+
* the coverage map) that could disagree with it, and the losing control lost
|
|
19
|
+
* silently. Both are retired; `BDD_MODE` is now refused rather than read.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* below its committed floor, coverage given back, new behavior
|
|
25
|
-
* nobody mapped or waived, a deleted scenario, or a run with no
|
|
26
|
-
* base revision to compare against all fail loudly. Only in
|
|
27
|
-
* this state is the check a required ruleset context.
|
|
21
|
+
* So the prover has exactly one behaviour: it proves. Absence fails. A missing
|
|
22
|
+
* config, a malformed manifest, zero scenarios, zero mappings, any contract
|
|
23
|
+
* defect, a platform below its committed floor, coverage given back, new
|
|
24
|
+
* behavior nobody mapped or waived, a deleted scenario, or a run with no base
|
|
25
|
+
* revision to compare against all fail loudly, and there are no warnings — a
|
|
26
|
+
* defect graded amber is a defect nobody fixes. A project that does not want
|
|
27
|
+
* that declares the gate `off`, which is a decision a reader can find in the
|
|
28
|
+
* settings file, rather than a green check that found five hundred defects.
|
|
28
29
|
*
|
|
29
30
|
* The committed `coverageFloor` is an ABSOLUTE BAR, not a ratchet: it answers
|
|
30
31
|
* "is this platform below the bar right now", and nothing stops a project
|
|
@@ -46,13 +47,13 @@ import * as path from "node:path";
|
|
|
46
47
|
import { fileURLToPath } from "node:url";
|
|
47
48
|
|
|
48
49
|
import {
|
|
49
|
-
|
|
50
|
+
GATE_LEVELS,
|
|
51
|
+
RETIRED_ADOPTION_STATES,
|
|
50
52
|
SUPPORTED_MAP_SCHEMA_VERSIONS,
|
|
51
53
|
declaredPlatforms,
|
|
52
54
|
} from "./bdd/contract.mjs";
|
|
53
55
|
import {
|
|
54
56
|
SUCCESS_STATUSES,
|
|
55
|
-
WARNABLE_DEFECT_CODES,
|
|
56
57
|
buildSummary,
|
|
57
58
|
contractVersion,
|
|
58
59
|
correlationId,
|
|
@@ -80,7 +81,7 @@ import {
|
|
|
80
81
|
validateScenarios,
|
|
81
82
|
validateTrackerTags,
|
|
82
83
|
} from "./bdd/validate.mjs";
|
|
83
|
-
import {
|
|
84
|
+
import { validateWaivers } from "./bdd/waivers.mjs";
|
|
84
85
|
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
85
86
|
|
|
86
87
|
const PACKAGE_ROOT = path.resolve(
|
|
@@ -90,33 +91,44 @@ const PACKAGE_ROOT = path.resolve(
|
|
|
90
91
|
const MAP_REL = "bdd/coverage-map.json";
|
|
91
92
|
const defect = (code, message) => ({ code, message });
|
|
92
93
|
|
|
93
|
-
/** Defect
|
|
94
|
-
const NOT_ADOPTED = "not-adopted";
|
|
95
|
-
|
|
96
|
-
/** Defect / mode code, named once. */
|
|
97
|
-
const BOOTSTRAP_METADATA = "bootstrap-metadata";
|
|
98
|
-
|
|
99
|
-
/** Defect / mode code, named once. */
|
|
94
|
+
/** Defect code, named once. */
|
|
100
95
|
const EXECUTION_RESULTS = "execution-results";
|
|
101
96
|
|
|
102
|
-
/** Defect
|
|
97
|
+
/** Defect code, named once. */
|
|
103
98
|
const EMPTY_CONTRACT = "empty-contract";
|
|
104
99
|
|
|
100
|
+
/** Defect code, named once. */
|
|
101
|
+
const ADOPTION_RETIRED = "adoption-retired";
|
|
102
|
+
|
|
103
|
+
/** How every retirement message ends, so all of them name the same remedy. */
|
|
104
|
+
const DECLARE_INSTEAD =
|
|
105
|
+
`Declare this gate in .lisa.config.json at one of ${GATE_LEVELS.join(", ")}` +
|
|
106
|
+
" — that declaration is now the only control over whether the property is" +
|
|
107
|
+
" governed.";
|
|
108
|
+
|
|
105
109
|
/**
|
|
106
|
-
*
|
|
110
|
+
* Refuse a `BDD_MODE` the gate no longer reads.
|
|
111
|
+
*
|
|
112
|
+
* `BDD_MODE` was a second adoption control alongside the gate declaration, and
|
|
113
|
+
* it is retired. Ignoring a value someone deliberately set would be the worst
|
|
114
|
+
* of the three options: the setting would keep looking like configuration while
|
|
115
|
+
* deciding nothing, which is the exact failure the collapse was done to remove.
|
|
116
|
+
*
|
|
117
|
+
* The refusal names the value. A retired one gets its own sentence saying what
|
|
118
|
+
* it was and why it went, because telling the author of `bootstrap` to check
|
|
119
|
+
* for a typo sends them hunting a mistake they did not make.
|
|
107
120
|
* @param {Record<string, string|undefined>} env - Process environment.
|
|
108
|
-
* @returns {{
|
|
121
|
+
* @returns {{error: string|null}} The refusal, when there is one.
|
|
109
122
|
*/
|
|
110
|
-
export function
|
|
123
|
+
export function refuseRetiredMode(env) {
|
|
111
124
|
const raw = (env.BDD_MODE ?? "").trim();
|
|
112
|
-
if (raw === "") return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
return { mode: raw, error: null };
|
|
125
|
+
if (raw === "") return { error: null };
|
|
126
|
+
const known = RETIRED_ADOPTION_STATES[raw];
|
|
127
|
+
return {
|
|
128
|
+
error: known
|
|
129
|
+
? `BDD_MODE="${raw}" is ${known}`
|
|
130
|
+
: `BDD_MODE is retired and "${raw}" was never one of its values. ${DECLARE_INSTEAD}`,
|
|
131
|
+
};
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
/**
|
|
@@ -152,90 +164,36 @@ export function loadContract(root) {
|
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
/**
|
|
155
|
-
*
|
|
167
|
+
* Refuse an `adoption` block the gate no longer reads.
|
|
168
|
+
*
|
|
169
|
+
* The block mirrored `BDD_MODE` inside the manifest, and `adoption-drift`
|
|
170
|
+
* existed to catch the two disagreeing. With one control left there is nothing
|
|
171
|
+
* for it to mirror, so the block is dead configuration — and dead configuration
|
|
172
|
+
* that still reads like a switch is how a project believes it declared
|
|
173
|
+
* something it did not.
|
|
156
174
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
175
|
+
* Refused rather than ignored, and refused whatever it says: a stale
|
|
176
|
+
* `"state": "enforced"` is exactly as misleading as a stale `"bootstrap"`, and
|
|
177
|
+
* only deleting it makes the settings file true. The message names the value so
|
|
178
|
+
* the author of a `bootstrap` block is told what happened to it rather than
|
|
179
|
+
* left to guess.
|
|
161
180
|
* @param {object} contract - Parsed coverage map.
|
|
162
|
-
* @param {string} mode - Mode declared by CI.
|
|
163
|
-
* @param {string} today - ISO date to evaluate the expiry against.
|
|
164
|
-
* @returns {object[]} Defects found.
|
|
165
|
-
*/
|
|
166
|
-
export function validateAdoption(contract, mode, today) {
|
|
167
|
-
const adoption = contract.adoption ?? {};
|
|
168
|
-
const defects = [];
|
|
169
|
-
if (adoption.state && adoption.state !== mode) {
|
|
170
|
-
defects.push(
|
|
171
|
-
defect(
|
|
172
|
-
"adoption-drift",
|
|
173
|
-
`bdd/coverage-map.json declares adoption.state "${adoption.state}" but CI passed BDD_MODE "${mode}". Adoption is one operation: change both, and the ruleset context, together.`
|
|
174
|
-
)
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
if (mode === "enforced" && !adoption.state) {
|
|
178
|
-
defects.push(
|
|
179
|
-
defect(
|
|
180
|
-
"adoption-drift",
|
|
181
|
-
`enforced mode requires adoption.state "enforced" in ${MAP_REL}`
|
|
182
|
-
)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
if (mode !== "bootstrap") return defects;
|
|
186
|
-
return [...defects, ...bootstrapDefects(adoption, today)];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Bootstrap owes a named owner and a hard expiry, and dies at that expiry.
|
|
191
|
-
* @param {object} adoption - The manifest's adoption block.
|
|
192
|
-
* @param {string} today - ISO date to evaluate against.
|
|
193
181
|
* @returns {object[]} Defects found.
|
|
194
182
|
*/
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
defects.push(
|
|
210
|
-
defect(
|
|
211
|
-
BOOTSTRAP_METADATA,
|
|
212
|
-
"bootstrap requires adoption.owner (a named person, not a team)"
|
|
213
|
-
)
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
if (!adoption.expiresAt) {
|
|
217
|
-
defects.push(
|
|
218
|
-
defect(
|
|
219
|
-
BOOTSTRAP_METADATA,
|
|
220
|
-
"bootstrap requires adoption.expiresAt (an ISO date); a bootstrap with no time-box never ends"
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
} else if (!ISO_DATE.test(adoption.expiresAt)) {
|
|
224
|
-
defects.push(
|
|
225
|
-
defect(
|
|
226
|
-
BOOTSTRAP_METADATA,
|
|
227
|
-
"adoption.expiresAt must be an ISO date (YYYY-MM-DD)"
|
|
228
|
-
)
|
|
229
|
-
);
|
|
230
|
-
} else if (ISO_DATE.test(String(today)) && adoption.expiresAt < today) {
|
|
231
|
-
defects.push(
|
|
232
|
-
defect(
|
|
233
|
-
"bootstrap-expired",
|
|
234
|
-
`the BDD bootstrap expired on ${adoption.expiresAt} (owner: ${adoption.owner ?? "unnamed"}). Advance to enforced or re-authorize the time-box.`
|
|
235
|
-
)
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
return defects;
|
|
183
|
+
export function retiredAdoptionDefects(contract) {
|
|
184
|
+
const adoption = contract.adoption;
|
|
185
|
+
if (adoption === undefined) return [];
|
|
186
|
+
const state = typeof adoption?.state === "string" ? adoption.state : null;
|
|
187
|
+
const known = state ? RETIRED_ADOPTION_STATES[state] : null;
|
|
188
|
+
const preamble = known
|
|
189
|
+
? `${MAP_REL} declares adoption.state "${state}", which is ${known}`
|
|
190
|
+
: `${MAP_REL} carries an "adoption" block, which is retired: the gate no longer reads it`;
|
|
191
|
+
return [
|
|
192
|
+
defect(
|
|
193
|
+
ADOPTION_RETIRED,
|
|
194
|
+
`${preamble} Delete the block. ${DECLARE_INSTEAD}`
|
|
195
|
+
),
|
|
196
|
+
];
|
|
239
197
|
}
|
|
240
198
|
|
|
241
199
|
/**
|
|
@@ -295,7 +253,7 @@ function validateAll({
|
|
|
295
253
|
...discovery.defects,
|
|
296
254
|
...disclosureDefects({ root, contract, discovery }),
|
|
297
255
|
];
|
|
298
|
-
if (!options.baseSha) return [...defects,
|
|
256
|
+
if (!options.baseSha) return [...defects, MISSING_BASE_DEFECT];
|
|
299
257
|
const baseline = loadBaseline(root, options.baseSha, platforms);
|
|
300
258
|
if (!baseline.available) {
|
|
301
259
|
return [
|
|
@@ -323,34 +281,29 @@ function validateAll({
|
|
|
323
281
|
}
|
|
324
282
|
|
|
325
283
|
/**
|
|
326
|
-
*
|
|
284
|
+
* What a run with no base revision reports.
|
|
327
285
|
*
|
|
328
286
|
* Non-regression is the whole of what protects accepted coverage now that the
|
|
329
287
|
* floor is a plain bar rather than a ratchet, and every one of those checks
|
|
330
288
|
* needs a base. Running without one used to skip them in silence, which is a
|
|
331
|
-
* gate reporting a property it never evaluated.
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
289
|
+
* gate reporting a property it never evaluated.
|
|
290
|
+
*
|
|
291
|
+
* Unconditional, and therefore a constant rather than a function. The exemption
|
|
292
|
+
* used to be spelled "not in enforced mode", and with the mode axis retired
|
|
293
|
+
* that phrase has no referent: a run either proves non-regression or admits it
|
|
294
|
+
* could not.
|
|
336
295
|
*/
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"baseline",
|
|
342
|
-
"enforced mode requires BDD_BASE_SHA: without a base revision the gate cannot tell coverage that was given back from coverage that was never there, so it refuses to claim either."
|
|
343
|
-
),
|
|
344
|
-
];
|
|
345
|
-
}
|
|
296
|
+
const MISSING_BASE_DEFECT = defect(
|
|
297
|
+
"baseline",
|
|
298
|
+
"BDD_BASE_SHA is required: without a base revision the gate cannot tell coverage that was given back from coverage that was never there, so it refuses to claim either."
|
|
299
|
+
);
|
|
346
300
|
|
|
347
301
|
/**
|
|
348
|
-
* A malformed coverage floor
|
|
302
|
+
* A malformed coverage floor.
|
|
349
303
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* one file, with no other signal. It is refused rather than ignored.
|
|
304
|
+
* A floor written as `"19"` rather than `19` disables the ratchet AND removes
|
|
305
|
+
* the platform from enforcement, in one character, in one file, with no other
|
|
306
|
+
* signal. It is refused rather than ignored.
|
|
354
307
|
* @param {object} report - The built report.
|
|
355
308
|
* @returns {object[]} Defects found.
|
|
356
309
|
*/
|
|
@@ -364,7 +317,11 @@ function floorIntegrityDefects(report) {
|
|
|
364
317
|
}
|
|
365
318
|
|
|
366
319
|
/**
|
|
367
|
-
*
|
|
320
|
+
* Absence, which must fail.
|
|
321
|
+
*
|
|
322
|
+
* These used to be skipped outside `enforced` — the largest of the carve-outs
|
|
323
|
+
* the adoption axis bought, and the one that made a non-enforced run report a
|
|
324
|
+
* property it had not evaluated. There is one mode now, so they always run.
|
|
368
325
|
*
|
|
369
326
|
* The platform vocabulary is deliberately NOT a parameter: every platform this
|
|
370
327
|
* function cares about already reaches it through `report.floor`, which was
|
|
@@ -374,21 +331,18 @@ function floorIntegrityDefects(report) {
|
|
|
374
331
|
* @param {object} input - Contract, scenarios, report, and discovery.
|
|
375
332
|
* @returns {object[]} Defects found.
|
|
376
333
|
*/
|
|
377
|
-
function
|
|
334
|
+
function completenessDefects({ contract, scenarios, report, discovery }) {
|
|
378
335
|
const defects = [];
|
|
379
336
|
if (scenarios.length === 0) {
|
|
380
337
|
defects.push(
|
|
381
|
-
defect(
|
|
382
|
-
EMPTY_CONTRACT,
|
|
383
|
-
"enforced mode: bdd/features declares zero scenarios"
|
|
384
|
-
)
|
|
338
|
+
defect(EMPTY_CONTRACT, "bdd/features declares zero scenarios")
|
|
385
339
|
);
|
|
386
340
|
}
|
|
387
341
|
if ((contract.mappings ?? []).length === 0) {
|
|
388
342
|
defects.push(
|
|
389
343
|
defect(
|
|
390
344
|
EMPTY_CONTRACT,
|
|
391
|
-
"
|
|
345
|
+
"bdd/coverage-map.json declares zero test mappings"
|
|
392
346
|
)
|
|
393
347
|
);
|
|
394
348
|
}
|
|
@@ -396,7 +350,7 @@ function enforcedDefects({ contract, scenarios, report, discovery }) {
|
|
|
396
350
|
defects.push(
|
|
397
351
|
defect(
|
|
398
352
|
EMPTY_CONTRACT,
|
|
399
|
-
"
|
|
353
|
+
"bdd/coverage-map.json declares no runnerPlatforms"
|
|
400
354
|
)
|
|
401
355
|
);
|
|
402
356
|
}
|
|
@@ -405,7 +359,7 @@ function enforcedDefects({ contract, scenarios, report, discovery }) {
|
|
|
405
359
|
defects.push(
|
|
406
360
|
defect(
|
|
407
361
|
"floor-missing",
|
|
408
|
-
`
|
|
362
|
+
`no coverageFloor declared for platform ${platform}`
|
|
409
363
|
)
|
|
410
364
|
);
|
|
411
365
|
}
|
|
@@ -443,14 +397,9 @@ function measured(value) {
|
|
|
443
397
|
* @returns {object} The result envelope.
|
|
444
398
|
*/
|
|
445
399
|
export function run(root, options) {
|
|
446
|
-
const { mode } = options;
|
|
447
400
|
const loaded = loadContract(root);
|
|
448
|
-
const fatal = configFatals(loaded
|
|
449
|
-
if (fatal)
|
|
450
|
-
return result({ mode, defects: [fatal], report: null, contract: null });
|
|
451
|
-
if (!loaded.present) {
|
|
452
|
-
return result({ mode, defects: [], report: null, contract: null });
|
|
453
|
-
}
|
|
401
|
+
const fatal = configFatals(loaded);
|
|
402
|
+
if (fatal) return result({ defects: [fatal], report: null, contract: null });
|
|
454
403
|
const contract = loaded.contract;
|
|
455
404
|
const versionDefect = schemaDefect(contract);
|
|
456
405
|
const platforms = declaredPlatforms(contract.runnerPlatforms);
|
|
@@ -474,7 +423,7 @@ export function run(root, options) {
|
|
|
474
423
|
});
|
|
475
424
|
const defects = [
|
|
476
425
|
...(versionDefect ? [versionDefect] : []),
|
|
477
|
-
...
|
|
426
|
+
...retiredAdoptionDefects(contract),
|
|
478
427
|
...execution.defects,
|
|
479
428
|
...floorIntegrityDefects(report),
|
|
480
429
|
...validateAll({
|
|
@@ -486,25 +435,27 @@ export function run(root, options) {
|
|
|
486
435
|
cache,
|
|
487
436
|
discovery,
|
|
488
437
|
}),
|
|
489
|
-
...(
|
|
490
|
-
? enforcedDefects({ contract, scenarios, report, discovery })
|
|
491
|
-
: []),
|
|
438
|
+
...completenessDefects({ contract, scenarios, report, discovery }),
|
|
492
439
|
];
|
|
493
|
-
return result({
|
|
440
|
+
return result({ defects, report, contract });
|
|
494
441
|
}
|
|
495
442
|
|
|
496
443
|
/**
|
|
497
444
|
* Configuration problems that stop the gate before it can evaluate anything.
|
|
445
|
+
*
|
|
446
|
+
* A missing manifest is one of them. It used to be tolerated outside
|
|
447
|
+
* `enforced`, which meant the commonest way to switch this gate off was to
|
|
448
|
+
* delete the file it reads — an absence that looks identical to an oversight.
|
|
449
|
+
* The way to switch it off is to declare it `off`.
|
|
498
450
|
* @param {object} loaded - Result of {@link loadContract}.
|
|
499
|
-
* @param {string} mode - Adoption state.
|
|
500
451
|
* @returns {object|null} The fatal defect, or null.
|
|
501
452
|
*/
|
|
502
|
-
function configFatals(loaded
|
|
453
|
+
function configFatals(loaded) {
|
|
503
454
|
if (loaded.error) return defect("config-malformed", loaded.error);
|
|
504
|
-
if (loaded.present
|
|
455
|
+
if (loaded.present) return null;
|
|
505
456
|
return defect(
|
|
506
457
|
"config-absent",
|
|
507
|
-
`${
|
|
458
|
+
`${MAP_REL} does not exist, and its absence is a failure, never a skip. ${DECLARE_INSTEAD}`
|
|
508
459
|
);
|
|
509
460
|
}
|
|
510
461
|
|
|
@@ -535,27 +486,19 @@ const INVALID_CODES = Object.freeze([
|
|
|
535
486
|
]);
|
|
536
487
|
|
|
537
488
|
/**
|
|
538
|
-
* Assemble the gate's internal result
|
|
489
|
+
* Assemble the gate's internal result.
|
|
539
490
|
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
491
|
+
* No severity is resolved here any more, because there is no longer more than
|
|
492
|
+
* one. A defect fails the run; the exit code, the human output and the envelope
|
|
493
|
+
* findings therefore cannot disagree about whether something counted, which is
|
|
494
|
+
* the property the grading step existed to preserve and the reason it can go.
|
|
495
|
+
* @param {object} input - Defects, report, and the parsed contract.
|
|
544
496
|
* @returns {object} The internal result.
|
|
545
497
|
*/
|
|
546
|
-
function result({
|
|
547
|
-
const fatal = hasFatalDefect(mode, defects);
|
|
548
|
-
const graded = defects.map(item => ({
|
|
549
|
-
...item,
|
|
550
|
-
severity:
|
|
551
|
-
mode === "enforced" || !WARNABLE_DEFECT_CODES.includes(item.code)
|
|
552
|
-
? "error"
|
|
553
|
-
: "warning",
|
|
554
|
-
}));
|
|
498
|
+
function result({ defects, report, contract }) {
|
|
555
499
|
return {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
defects: graded,
|
|
500
|
+
status: statusFor({ defects, fatal: hasFatalDefect(defects), report }),
|
|
501
|
+
defects,
|
|
559
502
|
report,
|
|
560
503
|
contract,
|
|
561
504
|
};
|
|
@@ -563,14 +506,13 @@ function result({ mode, defects, report, contract }) {
|
|
|
563
506
|
|
|
564
507
|
/**
|
|
565
508
|
* Map the run onto the standard envelope's status vocabulary.
|
|
566
|
-
* @param {object} input -
|
|
509
|
+
* @param {object} input - Defects, fatality, and the report.
|
|
567
510
|
* @returns {string} An envelope status.
|
|
568
511
|
*/
|
|
569
|
-
function statusFor({
|
|
512
|
+
function statusFor({ defects, fatal, report }) {
|
|
570
513
|
if (defects.some(item => INVALID_CODES.includes(item.code))) return "invalid";
|
|
571
514
|
if (fatal) return "failed";
|
|
572
|
-
|
|
573
|
-
return "completed";
|
|
515
|
+
return report ? "completed" : "no-op";
|
|
574
516
|
}
|
|
575
517
|
|
|
576
518
|
/**
|
|
@@ -579,7 +521,7 @@ function statusFor({ mode, defects, fatal, report }) {
|
|
|
579
521
|
* @returns {string} Summary line.
|
|
580
522
|
*/
|
|
581
523
|
function summaryLine(run) {
|
|
582
|
-
const head = `bdd-coverage
|
|
524
|
+
const head = `bdd-coverage: ${run.status}`;
|
|
583
525
|
if (!run.report) return `${head} (${run.defects.length} findings)`;
|
|
584
526
|
const trace = run.report.traceability.overall;
|
|
585
527
|
const execution = run.report.execution.supplied
|
|
@@ -594,19 +536,19 @@ function summaryLine(run) {
|
|
|
594
536
|
* @returns {string} The reason.
|
|
595
537
|
*/
|
|
596
538
|
function reasonFor(run) {
|
|
597
|
-
const first = run.defects
|
|
539
|
+
const first = run.defects[0];
|
|
598
540
|
return first
|
|
599
541
|
? `${first.code}: ${first.message}`
|
|
600
|
-
:
|
|
542
|
+
: "bdd-coverage did not complete";
|
|
601
543
|
}
|
|
602
544
|
|
|
603
545
|
/**
|
|
604
546
|
* Convert the internal result into Lisa's standard command envelope.
|
|
605
547
|
*
|
|
606
548
|
* `mode` is the ENVELOPE's mode — the gate really runs, so it is always
|
|
607
|
-
* `real`.
|
|
608
|
-
*
|
|
609
|
-
*
|
|
549
|
+
* `real`. There is no second axis to carry: whether the property is governed is
|
|
550
|
+
* the gate declaration's answer, and it is decided before this gate is invoked
|
|
551
|
+
* at all.
|
|
610
552
|
* @param {object} input - The result, the environment, and CLI options.
|
|
611
553
|
* @returns {object} An envelope conforming to lisa-command-envelope.v1.
|
|
612
554
|
*/
|
|
@@ -625,13 +567,11 @@ export function toCommandEnvelope({
|
|
|
625
567
|
dryRun: !options.write,
|
|
626
568
|
status: gateRun.status,
|
|
627
569
|
correlationId: correlationId(env.BDD_CORRELATION_ID, {
|
|
628
|
-
adoptionState: gateRun.adoptionState,
|
|
629
570
|
status: gateRun.status,
|
|
630
571
|
summary: summaryLine(gateRun),
|
|
631
572
|
}),
|
|
632
573
|
summary: {
|
|
633
574
|
...buildSummary({
|
|
634
|
-
adoptionState: gateRun.adoptionState,
|
|
635
575
|
report: gateRun.report,
|
|
636
576
|
defects: gateRun.defects,
|
|
637
577
|
filesWritten,
|
|
@@ -642,7 +582,6 @@ export function toCommandEnvelope({
|
|
|
642
582
|
code: item.code,
|
|
643
583
|
subject: subjectFor(item),
|
|
644
584
|
message: item.message,
|
|
645
|
-
severity: item.severity,
|
|
646
585
|
})),
|
|
647
586
|
};
|
|
648
587
|
return SUCCESS_STATUSES.includes(gateRun.status)
|
|
@@ -690,16 +629,13 @@ export function parseArgs(argv, env) {
|
|
|
690
629
|
*/
|
|
691
630
|
async function main() {
|
|
692
631
|
const root = process.env.BDD_COVERAGE_ROOT || PACKAGE_ROOT;
|
|
693
|
-
const
|
|
694
|
-
if (
|
|
695
|
-
console.error(`[bdd-coverage] ${
|
|
632
|
+
const refused = refuseRetiredMode(process.env);
|
|
633
|
+
if (refused.error) {
|
|
634
|
+
console.error(`[bdd-coverage] ${refused.error}`);
|
|
696
635
|
process.exitCode = 2;
|
|
697
636
|
return;
|
|
698
637
|
}
|
|
699
|
-
const options =
|
|
700
|
-
...parseArgs(process.argv.slice(2), process.env),
|
|
701
|
-
mode: resolved.mode,
|
|
702
|
-
};
|
|
638
|
+
const options = parseArgs(process.argv.slice(2), process.env);
|
|
703
639
|
const gateRun = run(root, options);
|
|
704
640
|
const filesWritten =
|
|
705
641
|
options.write && gateRun.report ? writeArtifacts(root, gateRun.report) : 0;
|
|
@@ -788,14 +724,7 @@ function writeArtifacts(root, report) {
|
|
|
788
724
|
*/
|
|
789
725
|
function printHuman(gateRun, envelope) {
|
|
790
726
|
for (const item of gateRun.defects) {
|
|
791
|
-
console.error(
|
|
792
|
-
`[bdd-coverage] ${item.severity}: ${item.code}: ${item.message}`
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
if (gateRun.adoptionState === "bootstrap" && gateRun.defects.length > 0) {
|
|
796
|
-
console.error(
|
|
797
|
-
"[bdd-coverage] bootstrap: warnings above are visible, not blockers, until this repo advances to enforced. Anything reported as `error` fails even here."
|
|
798
|
-
);
|
|
727
|
+
console.error(`[bdd-coverage] ${item.code}: ${item.message}`);
|
|
799
728
|
}
|
|
800
729
|
console.error(`[bdd-coverage] ${envelope.summary.headline}`);
|
|
801
730
|
}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "BDD behavior contract, machine-readable half. See the Lisa rule `bdd-e2e-coverage` and docs/bdd-coverage-schema.md.
|
|
2
|
+
"_comment": "BDD behavior contract, machine-readable half. See the Lisa rule `bdd-e2e-coverage` and docs/bdd-coverage-schema.md. This file holds the contract, never the adoption decision: whether the gate is governed is declared once, as `behavior-contract` in .lisa.config.json, at `required`, `optional`, or `off`. An `adoption` block here is retired and is refused rather than ignored - a second copy of a decision is a second copy that can disagree.",
|
|
3
3
|
"schemaVersion": 2,
|
|
4
4
|
"asOf": "1970-01-01",
|
|
5
|
-
"adoption": {
|
|
6
|
-
"_comment": "not-adopted | bootstrap | enforced. Must equal the bdd_mode this repo's ci.yml passes to Lisa's quality.yml. bootstrap additionally requires `owner` (a named person) and `expiresAt` (an ISO date) — a bootstrap with no time-box never ends.",
|
|
7
|
-
"state": "not-adopted",
|
|
8
|
-
"owner": null,
|
|
9
|
-
"expiresAt": null
|
|
10
|
-
},
|
|
11
5
|
"runnerPlatforms": {
|
|
12
6
|
"_comment": "The ONLY place a test runner is named authoritatively. Declare each runner this project has and the platforms it covers, e.g. {\"playwright\": [\"web\"], \"maestro\": [\"ios\", \"android\"]}. Platform vocabulary is this project's own.",
|
|
13
7
|
"playwright": ["web"],
|
package/package.json
CHANGED
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"zod-validation-error": "^4.0.0"
|
|
144
144
|
},
|
|
145
145
|
"name": "@codyswann/lisa",
|
|
146
|
-
"version": "3.69.
|
|
146
|
+
"version": "3.69.6",
|
|
147
147
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
148
148
|
"main": "dist/index.js",
|
|
149
149
|
"exports": {
|