spec-controller 0.1.0-alpha.3 → 0.1.0-alpha.31
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/README.md +27 -5
- package/dist/cli-allocate/cli.d.ts +2 -0
- package/dist/cli-allocate/cli.d.ts.map +1 -0
- package/dist/cli-allocate/cli.js +45 -0
- package/dist/cli-allocate/cli.js.map +1 -0
- package/dist/cli-allocate/readHistory.d.ts +21 -0
- package/dist/cli-allocate/readHistory.d.ts.map +1 -0
- package/dist/cli-allocate/readHistory.js +97 -0
- package/dist/cli-allocate/readHistory.js.map +1 -0
- package/dist/cli-allocate/readParts.d.ts +3 -0
- package/dist/cli-allocate/readParts.d.ts.map +1 -0
- package/dist/cli-allocate/readParts.js +25 -0
- package/dist/cli-allocate/readParts.js.map +1 -0
- package/dist/cli-allocate/readTree.d.ts +17 -0
- package/dist/cli-allocate/readTree.d.ts.map +1 -0
- package/dist/cli-allocate/readTree.js +63 -0
- package/dist/cli-allocate/readTree.js.map +1 -0
- package/dist/cli-allocate/request.d.ts +11 -0
- package/dist/cli-allocate/request.d.ts.map +1 -0
- package/dist/cli-allocate/request.js +29 -0
- package/dist/cli-allocate/request.js.map +1 -0
- package/dist/cli-allocate/stderr.d.ts +14 -0
- package/dist/cli-allocate/stderr.d.ts.map +1 -0
- package/dist/cli-allocate/stderr.js +33 -0
- package/dist/cli-allocate/stderr.js.map +1 -0
- package/dist/cli-args.d.ts +70 -14
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +239 -18
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-balance/cli.d.ts +18 -1
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +185 -161
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-balance/emit/writer.d.ts +8 -23
- package/dist/cli-balance/emit/writer.d.ts.map +1 -1
- package/dist/cli-balance/emit/writer.js +24 -32
- package/dist/cli-balance/emit/writer.js.map +1 -1
- package/dist/cli-registry.d.ts +28 -0
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +39 -34
- package/dist/cli-registry.js.map +1 -1
- package/dist/cli.d.ts +4 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -12
- package/dist/cli.js.map +1 -1
- package/dist/deferralTags.d.ts +2 -2
- package/dist/deferralTags.js +2 -2
- package/dist/host.d.ts +25 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +119 -15
- package/dist/host.js.map +1 -1
- package/dist/ingest/gherkinValidation.d.ts +9 -12
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +9 -12
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +3 -49
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +27 -120
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/dist/ingest/ingestScenarios.d.ts +8 -14
- package/dist/ingest/ingestScenarios.d.ts.map +1 -1
- package/dist/ingest/ingestScenarios.js +32 -55
- package/dist/ingest/ingestScenarios.js.map +1 -1
- package/dist/outputLocation.d.ts +15 -0
- package/dist/outputLocation.d.ts.map +1 -0
- package/dist/outputLocation.js +39 -0
- package/dist/outputLocation.js.map +1 -0
- package/dist/run-management/keptRun.d.ts +14 -19
- package/dist/run-management/keptRun.d.ts.map +1 -1
- package/dist/run-management/keptRun.js +15 -20
- package/dist/run-management/keptRun.js.map +1 -1
- package/package.json +2 -7
- package/dist/corpus/cli.d.ts +0 -34
- package/dist/corpus/cli.d.ts.map +0 -1
- package/dist/corpus/cli.js +0 -128
- package/dist/corpus/cli.js.map +0 -1
- package/dist/mutation-ratchet/index.d.ts +0 -46
- package/dist/mutation-ratchet/index.d.ts.map +0 -1
- package/dist/mutation-ratchet/index.js +0 -46
- package/dist/mutation-ratchet/index.js.map +0 -1
- package/dist/mutation-ratchet/record.d.ts +0 -178
- package/dist/mutation-ratchet/record.d.ts.map +0 -1
- package/dist/mutation-ratchet/record.js +0 -314
- package/dist/mutation-ratchet/record.js.map +0 -1
- package/dist/mutation-ratchet/report.d.ts +0 -109
- package/dist/mutation-ratchet/report.d.ts.map +0 -1
- package/dist/mutation-ratchet/report.js +0 -156
- package/dist/mutation-ratchet/report.js.map +0 -1
package/dist/host.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @SCN-RMG-004 — features/run-management.feature.
|
|
14
14
|
*/
|
|
15
|
+
import { equalsFormRefusal, givenTwiceRefusal, isEqualsForm, isMissingValue, missingValueRefusal, valueFlagsOf, } from "./cli-args.js";
|
|
16
|
+
import { cliRegistry } from "./cli-registry.js";
|
|
15
17
|
/**
|
|
16
18
|
* Parse the host argv into its leading global options (`--store`, `--run-id`) and the
|
|
17
19
|
* command + its clean args. Global options are recognised ONLY before the command: the
|
|
@@ -20,26 +22,101 @@
|
|
|
20
22
|
* --features f` yields the store + `balance` + `[--features, f]`, while `balance --store
|
|
21
23
|
* /s` leaves `--store` in the command's args (a `--store` AFTER the command is the
|
|
22
24
|
* command's own arg, never a host store directive) — the "global before command" contract.
|
|
25
|
+
* A modifier with nothing after it is refused (@SCN-CLI-067); an empty argv is the bare help.
|
|
23
26
|
*/
|
|
24
|
-
export function parseHostInvocation(argv) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
export function parseHostInvocation(argv, globalFlags = cliRegistry.global, commands = cliRegistry.commands.map((command) => command.name)) {
|
|
28
|
+
// The modifiers and the values they take are the registry's global scope, never a list kept here
|
|
29
|
+
// (@SCN-CLI-059): a modifier added later is read, and refused without its value, by construction.
|
|
30
|
+
const modifiers = valueFlagsOf(globalFlags);
|
|
31
|
+
const values = new Map();
|
|
27
32
|
let i = 0;
|
|
28
33
|
for (; i < argv.length; i++) {
|
|
29
|
-
const token = argv[i];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const token = argv[i] ?? "";
|
|
35
|
+
const modifier = modifiers.get(token);
|
|
36
|
+
if (modifier === undefined) {
|
|
37
|
+
const refusal = leadingFlagRefusal(token, modifiers, globalFlags);
|
|
38
|
+
if (refusal !== undefined)
|
|
39
|
+
return { commandArgs: [], refusal };
|
|
40
|
+
break; // first non-modifier token is the command
|
|
34
41
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const value = argv[i + 1];
|
|
43
|
+
const refusal = modifierRefusal(token, modifier, argv.slice(i + 1, i + 3), { values, commands });
|
|
44
|
+
if (refusal !== undefined)
|
|
45
|
+
return { commandArgs: [], refusal };
|
|
46
|
+
values.set(modifier.name, value ?? "");
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
if (i === argv.length && values.size > 0)
|
|
50
|
+
return { commandArgs: [], refusal: noCommandRefusal([...values.keys()]) };
|
|
51
|
+
return {
|
|
52
|
+
store: values.get("--store"),
|
|
53
|
+
runId: values.get("--run-id"),
|
|
54
|
+
command: argv[i],
|
|
55
|
+
commandArgs: argv.slice(i + 1),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The refusal a modifier earns where it stands, else undefined; `ahead` is its value and the token
|
|
60
|
+
* after it. Given twice, it is never read as its last value (@SCN-CLI-064): `--store A --store B` used
|
|
61
|
+
* to store under B without a word. Given no value, it is never handed the next token (@SCN-CLI-059):
|
|
62
|
+
* `--store --run-id r1` used to store under a directory called `--run-id`. Given the command as its
|
|
63
|
+
* value with no command after it, it has eaten the command (@SCN-CLI-066): `--store balance
|
|
64
|
+
* --features f` used to blame `--features`, and `--store balance` alone answered help at exit 0. A
|
|
65
|
+
* command name followed by the command is still a value: `--store balance balance …` is read.
|
|
66
|
+
*/
|
|
67
|
+
function modifierRefusal(token, modifier, [value, after], read) {
|
|
68
|
+
if (read.values.has(modifier.name))
|
|
69
|
+
return givenTwiceRefusal(token, "spec-controller");
|
|
70
|
+
if (isMissingValue(value))
|
|
71
|
+
return missingValueRefusal(token, modifier, "spec-controller");
|
|
72
|
+
if (read.commands.includes(value ?? "") && (after === undefined || after.startsWith("-"))) {
|
|
73
|
+
return commandAsValueRefusal(token, modifier, value ?? "");
|
|
41
74
|
}
|
|
42
|
-
return
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The refusal for host modifiers with nothing after them: every modifier given, in one line, and the
|
|
79
|
+
* fix (@SCN-CLI-067). Given last, `--store <root>` used to answer the top-level help at exit 0 having
|
|
80
|
+
* run nothing, a false green for a CI step that lost its command word (3F-3686). Help after a
|
|
81
|
+
* modifier is a command here, and a bare `spec-controller` never reaches this.
|
|
82
|
+
*/
|
|
83
|
+
function noCommandRefusal(given) {
|
|
84
|
+
return (`No command followed the host modifiers given (${given.join(", ")}), so nothing ran: ` +
|
|
85
|
+
`a host modifier needs a command after it, or --help.\n` +
|
|
86
|
+
`Run 'spec-controller --help' to see the commands and host modifiers this version accepts.`);
|
|
87
|
+
}
|
|
88
|
+
/** The refusal for a modifier that took the command as its value: the modifier, the command, and the fix. */
|
|
89
|
+
function commandAsValueRefusal(token, modifier, command) {
|
|
90
|
+
return (`The ${token} host modifier took '${command}', a command, as its value, and left no command to run.\n` +
|
|
91
|
+
`Give ${token} its own value, as in 'spec-controller ${token} ${modifier.arg ?? ""} ${command} …'. ` +
|
|
92
|
+
`Run 'spec-controller --help' to see the commands and host modifiers this version accepts.`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The refusal a leading token that is no modifier earns, else undefined (it is the command). A
|
|
96
|
+
* modifier written `--store=<root>` is a form that is not read (@SCN-CLI-065), never a flag out of
|
|
97
|
+
* place; any other flag here is out of place, never a command (@SCN-CLI-060): `--exit-zero balance`
|
|
98
|
+
* used to answer `unknown command "--exit-zero"`. The top-level help stays where it was.
|
|
99
|
+
*/
|
|
100
|
+
function leadingFlagRefusal(token, modifiers, globalFlags) {
|
|
101
|
+
if (isEqualsForm(token))
|
|
102
|
+
return equalsFormRefusal(token, modifiers, "spec-controller");
|
|
103
|
+
if (isFlagOutOfPlace(token))
|
|
104
|
+
return flagBeforeCommandRefusal(token, globalFlags);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/** The help the host answers in the command's place, which is the one flag it takes there. */
|
|
108
|
+
const TOP_LEVEL_HELP = new Set(["--help", "-h"]);
|
|
109
|
+
/** True for a flag-shaped token that is not the top-level help. */
|
|
110
|
+
function isFlagOutOfPlace(token) {
|
|
111
|
+
return token.length > 1 && token.startsWith("-") && !TOP_LEVEL_HELP.has(token);
|
|
112
|
+
}
|
|
113
|
+
/** The refusal for a flag given before the command: the flag, what goes there, and where it goes. */
|
|
114
|
+
function flagBeforeCommandRefusal(flag, globalFlags) {
|
|
115
|
+
const modifiers = globalFlags.map((modifier) => modifier.name).join(", ");
|
|
116
|
+
return (`The ${flag} flag was given before the command. Before the command, spec-controller takes ` +
|
|
117
|
+
`only its host modifiers (${modifiers}); a command's own flags go after it, as in ` +
|
|
118
|
+
`'spec-controller <command> ${flag}'.\n` +
|
|
119
|
+
`Run 'spec-controller --help' to see the commands and host modifiers this version accepts.`);
|
|
43
120
|
}
|
|
44
121
|
/**
|
|
45
122
|
* Build the wrapped command argv: the command's clean args followed by a repeated
|
|
@@ -66,4 +143,31 @@ export function peekFlag(argv, name) {
|
|
|
66
143
|
}
|
|
67
144
|
return undefined;
|
|
68
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Answer a command the registry does not carry. It is given the command token and the rendered
|
|
148
|
+
* help, never the command's args, so no flag after the command can change the answer.
|
|
149
|
+
*
|
|
150
|
+
* An unknown command is a usage fault, so it exits 2, the published usage code
|
|
151
|
+
* (packages/spec-controller/README.md), which `--exit-zero` never masks (3F-3649).
|
|
152
|
+
*
|
|
153
|
+
* @SCN-CLI-056 @SCN-CLI-057 — features/cli-balance.feature.
|
|
154
|
+
*/
|
|
155
|
+
export function answerUnknownCommand(command, help) {
|
|
156
|
+
return {
|
|
157
|
+
exitCode: 2,
|
|
158
|
+
stderr: `spec-controller: unknown command "${command}".\n\n${help}\n`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** The refusal for host modifiers given before a command that takes none, else undefined. */
|
|
162
|
+
export function modifiersNotTakenRefusal(inv, commands = cliRegistry.commands) {
|
|
163
|
+
const entry = commands.find((command) => command.name === inv.command);
|
|
164
|
+
if (entry?.acceptsHostModifiers !== false)
|
|
165
|
+
return undefined;
|
|
166
|
+
const given = [inv.store === undefined ? [] : ["--store"], inv.runId === undefined ? [] : ["--run-id"]].flat();
|
|
167
|
+
if (given.length === 0)
|
|
168
|
+
return undefined;
|
|
169
|
+
return (`spec-controller: usage error — '${entry.name}' takes no host modifier, and was given ${given.join(", ")}: ` +
|
|
170
|
+
`it answers a query and keeps no run.\n` +
|
|
171
|
+
`Run 'spec-controller ${entry.name} …' without them.`);
|
|
172
|
+
}
|
|
69
173
|
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
* PURE: `string → parse errors`. No fs, no process — the file READ lives beside it at the ingest
|
|
24
24
|
* edge, as it does for every other input.
|
|
25
25
|
*
|
|
26
|
-
* WHY IT LIVES IN THE APP, NOT CORE. Core's barrel is the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* and the
|
|
26
|
+
* WHY IT LIVES IN THE APP, NOT CORE. Core's barrel is the v1 contract surface downstream consumers
|
|
27
|
+
* build on. Since 3F-3449 nothing reds when a symbol is added there — the guard that reconciled it
|
|
28
|
+
* both directions is gone — so keeping it narrow is a discipline rather than a gate. This validator
|
|
29
|
+
* is an INGEST concern (it decides whether an input can be read at all), not part of the
|
|
30
|
+
* reconciliation contract, so it belongs with the other readers at the app edge and the v1 surface
|
|
31
|
+
* stays untouched.
|
|
31
32
|
*/
|
|
32
33
|
/** A Gherkin parse failure: the file could not be read as Gherkin at all. */
|
|
33
34
|
export interface GherkinParseError {
|
|
@@ -73,13 +74,9 @@ export declare class UnreadableFeatureCorpusError extends Error {
|
|
|
73
74
|
* refusal means for it.
|
|
74
75
|
*
|
|
75
76
|
* WHY A TYPED ERROR RATHER THAN AN EMPTY CORPUS. The obvious fix is for `discoverFeatures` to hand
|
|
76
|
-
* back `[]` when the directory is not there
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* `checkInputsExist`, a supplied-but-absent `--features` too — into `The corpus could not be
|
|
80
|
-
* read: …` at exit 2. Tolerance at the reader turns both into `tags: OK …` at exit 0, inside the
|
|
81
|
-
* slice whose whole purpose is killing a vacuous certification. Typed, both survive untouched:
|
|
82
|
-
* this IS an `Error`, so it still lands in that generic catch.
|
|
77
|
+
* back `[]` when the directory is not there, and that would decide for every reader that absence
|
|
78
|
+
* is empty. Typed, each caller decides: `balance` reads it as no corpus (@SCN-CLI-017), and any
|
|
79
|
+
* other reader still meets a refusal. (The second command this guarded, `tags`, left at 3F-3635.)
|
|
83
80
|
*
|
|
84
81
|
* IT CARRIES THE DIRECTORY because the report is owed the name of what it examined — "I found no
|
|
85
82
|
* record" and "I found no regression" must never wear one word (3F-2947), and a consumer told
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gherkinValidation.d.ts","sourceRoot":"","sources":["../../src/ingest/gherkinValidation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gherkinValidation.d.ts","sourceRoot":"","sources":["../../src/ingest/gherkinValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAa/E;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;gBAElC,MAAM,EAAE,SAAS,iBAAiB,EAAE;CAUjD;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM;CAK9B"}
|
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
* PURE: `string → parse errors`. No fs, no process — the file READ lives beside it at the ingest
|
|
24
24
|
* edge, as it does for every other input.
|
|
25
25
|
*
|
|
26
|
-
* WHY IT LIVES IN THE APP, NOT CORE. Core's barrel is the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* and the
|
|
26
|
+
* WHY IT LIVES IN THE APP, NOT CORE. Core's barrel is the v1 contract surface downstream consumers
|
|
27
|
+
* build on. Since 3F-3449 nothing reds when a symbol is added there — the guard that reconciled it
|
|
28
|
+
* both directions is gone — so keeping it narrow is a discipline rather than a gate. This validator
|
|
29
|
+
* is an INGEST concern (it decides whether an input can be read at all), not part of the
|
|
30
|
+
* reconciliation contract, so it belongs with the other readers at the app edge and the v1 surface
|
|
31
|
+
* stays untouched.
|
|
31
32
|
*/
|
|
32
33
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
33
34
|
import { IdGenerator } from "@cucumber/messages";
|
|
@@ -88,13 +89,9 @@ export class UnreadableFeatureCorpusError extends Error {
|
|
|
88
89
|
* refusal means for it.
|
|
89
90
|
*
|
|
90
91
|
* WHY A TYPED ERROR RATHER THAN AN EMPTY CORPUS. The obvious fix is for `discoverFeatures` to hand
|
|
91
|
-
* back `[]` when the directory is not there
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* `checkInputsExist`, a supplied-but-absent `--features` too — into `The corpus could not be
|
|
95
|
-
* read: …` at exit 2. Tolerance at the reader turns both into `tags: OK …` at exit 0, inside the
|
|
96
|
-
* slice whose whole purpose is killing a vacuous certification. Typed, both survive untouched:
|
|
97
|
-
* this IS an `Error`, so it still lands in that generic catch.
|
|
92
|
+
* back `[]` when the directory is not there, and that would decide for every reader that absence
|
|
93
|
+
* is empty. Typed, each caller decides: `balance` reads it as no corpus (@SCN-CLI-017), and any
|
|
94
|
+
* other reader still meets a refusal. (The second command this guarded, `tags`, left at 3F-3635.)
|
|
98
95
|
*
|
|
99
96
|
* IT CARRIES THE DIRECTORY because the report is owed the name of what it examined — "I found no
|
|
100
97
|
* record" and "I found no regression" must never wear one word (3F-2947), and a consumer told
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gherkinValidation.js","sourceRoot":"","sources":["../../src/ingest/gherkinValidation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gherkinValidation.js","sourceRoot":"","sources":["../../src/ingest/gherkinValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAUjD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,MAAc;IACxD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,0BAA0B,EAAE,CAAC,CAAC;IAEhG,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0FAA0F;QAC1F,sFAAsF;QACtF,0FAA0F;QAC1F,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IAC5C,MAAM,CAA+B;IAE9C,YAAY,MAAoC;QAC9C,KAAK,CACH;YACE,yEAAyE;YACzE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC;SAC5D,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,8FAA8F;IACrF,SAAS,CAAS;IAE3B,YAAY,SAAiB;QAC3B,KAAK,CAAC,gDAAgD,SAAS,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -15,25 +15,6 @@
|
|
|
15
15
|
import { type GherkinParseError } from "./gherkinValidation.js";
|
|
16
16
|
import { type EvidenceObligations } from "@3f-consulting/spec-controller-core";
|
|
17
17
|
import type { EvidenceReconciliation } from "@3f-consulting/spec-controller-core";
|
|
18
|
-
/**
|
|
19
|
-
* A reader over ONE resolution pass's records.
|
|
20
|
-
*
|
|
21
|
-
* EACH RECORD IS OPENED ONCE, WHICH IS WHY THIS IS A CLOSURE AND NOT A FUNCTION PER REFERENCE. A
|
|
22
|
-
* manifest may name several gates of one record, and a reader opening it per entry would let two
|
|
23
|
-
* references answer for two different states of the same file — a rewrite landing mid-pass, a gate
|
|
24
|
-
* present for one and gone for the next. One read makes the pass's bars one reading of one record.
|
|
25
|
-
*
|
|
26
|
-
* THE REFUSAL IS REMEMBERED TOO. Cacheing only the successes would re-open an absent or unreadable
|
|
27
|
-
* record per reference, which is the same disagreement by the other door — and the likelier door,
|
|
28
|
-
* since a record being rewritten is unreadable for exactly the window that matters.
|
|
29
|
-
*
|
|
30
|
-
* THE SUBTRACTION IS THE RECORD'S, NOT THIS EDGE'S. `killFloorOf` spends the allowance; an edge
|
|
31
|
-
* computing `detected - slack` itself would be a second place holding a fact about the record.
|
|
32
|
-
*/
|
|
33
|
-
export declare function killFloorReaderFor(root: string): (reference: {
|
|
34
|
-
readonly mutationGate: string;
|
|
35
|
-
readonly recordFile?: string;
|
|
36
|
-
}) => number | null;
|
|
37
18
|
/**
|
|
38
19
|
* The reconciler's resolved input paths — the CLI options threaded to the readers.
|
|
39
20
|
* Every path is optional; a missing result file is tolerated (its evidence is
|
|
@@ -48,12 +29,6 @@ export interface ReconcileOptions {
|
|
|
48
29
|
cucumber?: string;
|
|
49
30
|
ci?: string;
|
|
50
31
|
packageJson?: string;
|
|
51
|
-
/**
|
|
52
|
-
* The measurement artefact (@SCN-CIP-003) — the number each measured check posted, and
|
|
53
|
-
* the credit side of every measured fitness check. Optional like the other result files: absent,
|
|
54
|
-
* it contributes nothing, and each measured obligation it would have answered reads `missing`.
|
|
55
|
-
*/
|
|
56
|
-
measurements?: string;
|
|
57
32
|
/**
|
|
58
33
|
* The CI RUN'S OWN RECORD of what each of its steps did (@SCN-LNT-021, @SCN-LNT-028) — the credit
|
|
59
34
|
* side of every CONVERTED fitness check, written by the consumer's own pipeline and by neither
|
|
@@ -85,26 +60,6 @@ export interface ReconcileOptions {
|
|
|
85
60
|
* refusal is not ours to interpret and rethrows.
|
|
86
61
|
*/
|
|
87
62
|
export declare function featureCorpusParseErrors(featuresDir: string | undefined): GherkinParseError[];
|
|
88
|
-
/**
|
|
89
|
-
* The `@SCN` ids the feature corpus CARRIES MORE THAN ONCE (@SCN-LDG-020's guardrail, 3F-1775).
|
|
90
|
-
*
|
|
91
|
-
* An `@SCN` identifies exactly ONE scenario — that is the whole basis of attributing evidence to
|
|
92
|
-
* it. If two scenarios carry the same id, a test citing it could be proving EITHER, and the tool
|
|
93
|
-
* cannot know which: the corpus is ambiguous, and any verdict over it is a guess.
|
|
94
|
-
*
|
|
95
|
-
* WHY THIS EXISTS. Tag inheritance made this reachable: an `@SCN` hoisted to a `Feature:`/`Rule:`
|
|
96
|
-
* is inherited by every scenario beneath it (cucumber does this, so we do — parity). Left
|
|
97
|
-
* unhandled, ONE passing test then marked EVERY scenario sharing that id `balanced`, including
|
|
98
|
-
* scenarios nothing proves — a BALANCED-WHEN-BROKEN run, exit 0. A new silent false-green,
|
|
99
|
-
* introduced by the very fix that closed the last one.
|
|
100
|
-
*
|
|
101
|
-
* Refused at the edge (exit 2) rather than reconciled, under the Rule already shipped for the
|
|
102
|
-
* observed side and the corrupt corpus (@SCN-CLI-014/015/016): an input the tool cannot read is a
|
|
103
|
-
* usage error, never a verdict. Whether hoisting an `@SCN` is an authoring fault worth a richer
|
|
104
|
-
* diagnostic is the deferred policy question (3F-1774's sibling); refusing to reconcile a corpus we
|
|
105
|
-
* cannot read is not a policy — it is the floor.
|
|
106
|
-
*/
|
|
107
|
-
export declare function featureCorpusDuplicateScnIds(featuresDir: string | undefined): string[];
|
|
108
63
|
/**
|
|
109
64
|
* Read the target's `feature-code → slug` map over the same `discoverFeatures` corpus
|
|
110
65
|
* (@SCN-RPT-006) — each coded feature file's `@<FFF>` header (`featureCode`)
|
|
@@ -113,10 +68,9 @@ export declare function featureCorpusDuplicateScnIds(featuresDir: string | undef
|
|
|
113
68
|
* the balance, threaded to `renderMarkdown` (via `writeOutputs`) as the 5th sibling arg
|
|
114
69
|
* (markdown-only — the by-feature cut is markdown-only, so it is NOT serialised to JSON).
|
|
115
70
|
* Uncoded files (`featureCode === null`) are omitted, so the by-feature emitter falls
|
|
116
|
-
* back to the bare `FFF` for any id whose code no file registers.
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* now, which is the first time a CONSUMER of this reader has it too.
|
|
71
|
+
* back to the bare `FFF` for any id whose code no file registers. When two files carry one
|
|
72
|
+
* code the map keeps the last, and that run lists `duplicate-feature-code` under Scenario code
|
|
73
|
+
* errors and fails (@SCN-STG-010, 3F-3635), so a report whose map is ambiguous never passes.
|
|
120
74
|
*/
|
|
121
75
|
export declare function readFeatureNames(featuresDir?: string): Map<string, string>;
|
|
122
76
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestQualityChecks.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"ingestQualityChecks.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,qCAAqC,CAAC;AAkB7C,OAAO,KAAK,EAIV,sBAAsB,EACvB,MAAM,qCAAqC,CAAC;AA4B7C;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8ED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,EAAE,CAQ7F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAM1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAuB9E;AAyCD,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,sBAAsB,CAoE3E"}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* `package.json` to observe each fitness check the corpus cites.
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync, existsSync } from "node:fs";
|
|
16
|
-
import { basename
|
|
16
|
+
import { basename } from "node:path";
|
|
17
17
|
import { ciSourcePathsOf } from "./ciSourcePaths.js";
|
|
18
18
|
import { discoverFeatures } from "./ingestScenarios.js";
|
|
19
|
-
import { parseScenarios, countScnOccurrences, parseUntaggedScenarios, proseOnlyCodes, } from "@3f-consulting/spec-controller-core";
|
|
19
|
+
import { parseScenarios, countScnOccurrences, parseUntaggedScenarios, proseOnlyCodes, sharedScnCodesOf, } from "@3f-consulting/spec-controller-core";
|
|
20
20
|
import { MissingFeatureCorpusError, UnreadableFeatureCorpusError, } from "./gherkinValidation.js";
|
|
21
21
|
import { DEFAULT_DEFERRAL_TAGS } from "../deferralTags.js";
|
|
22
22
|
import { evidenceObligations, } from "@3f-consulting/spec-controller-core";
|
|
@@ -30,12 +30,6 @@ import { parseCucumberEvidenceObservations, } from "@3f-consulting/spec-controll
|
|
|
30
30
|
// their CALLER. What is left here is the reads: this edge opens the files and hands the bytes over.
|
|
31
31
|
import { evidenceReconciliationFromSources } from "@3f-consulting/spec-controller-core";
|
|
32
32
|
import { resolveTargetRoot } from "../run-management/resolveRunInputs.js";
|
|
33
|
-
// The record reader, reached for the RECORD half of a bar that names a gate (@SCN-MUT-012): its
|
|
34
|
-
// conventional record name, its parser, and the floor a gate's own figures imply. Through the
|
|
35
|
-
// barrel, which is the surface a caller holds. (The directory is still spelled `mutation-ratchet`
|
|
36
|
-
// and the ratchet it was named for is deleted — 3F-3337 left the name standing rather than move a
|
|
37
|
-
// module inside a delete; filed as a finding.)
|
|
38
|
-
import { gateRecordFor, killFloorOf, MalformedRecordError, parseGateRecords, RECORD_FILE, } from "../mutation-ratchet/index.js";
|
|
39
33
|
/**
|
|
40
34
|
* The per-input FALLBACK quality-check for each JS-ecosystem result source — the
|
|
41
35
|
* source→quality-check mapping, SET at the CLI edge rather than hardcoded in the core
|
|
@@ -54,74 +48,12 @@ const CUCUMBER_QUALITY_CHECK = "integration";
|
|
|
54
48
|
* publishes and `balance` is the command that reconciles; the engine holds neither, and a core
|
|
55
49
|
* that assumed them would be asserting something about a command line it never sees.
|
|
56
50
|
*
|
|
57
|
-
* BOTH TOKENS, AND THE SECOND IS NOT DECORATION.
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
* would take that step off the books. The comparison is whole, adjacent shell tokens
|
|
51
|
+
* BOTH TOKENS, AND THE SECOND IS NOT DECORATION. A step running the same binary for anything but
|
|
52
|
+
* `balance` is an ordinary step of the pipeline being reconciled, so an invocation naming the
|
|
53
|
+
* binary alone would take that step off the books. The comparison is whole, adjacent shell tokens
|
|
61
54
|
* (`parse/commandRuns`), so this names the reconciliation and nothing else the tool can do.
|
|
62
55
|
*/
|
|
63
56
|
const RECONCILER_INVOCATION = "spec-controller balance";
|
|
64
|
-
/**
|
|
65
|
-
* A refusal met with an absent bar rather than let out of this edge.
|
|
66
|
-
*
|
|
67
|
-
* NOTHING IS THROWN OUT OF HERE, and that is the stance rather than caution. A refusal that escapes
|
|
68
|
-
* costs the whole run its verdict over one bad character in a file nobody may even be reading — a
|
|
69
|
-
* fault that is loud nowhere, where an absent bar is loud on the ledger. It is `thresholdsOf`'s own
|
|
70
|
-
* stance for a malformed entry, applied to the half of a bar that now lives in a second file.
|
|
71
|
-
*
|
|
72
|
-
* TWO CLASSES AND NO MORE. The record library normalises every fault it can see — the YAML parser's
|
|
73
|
-
* own included — into `MalformedRecordError` precisely so a caller catches all of them by catching
|
|
74
|
-
* one; beside it sits the read that never got as far as a record. A bare catch would swallow a
|
|
75
|
-
* defect in this edge as well, and answer it with the same silent absence.
|
|
76
|
-
*/
|
|
77
|
-
function orNoBar(read) {
|
|
78
|
-
try {
|
|
79
|
-
return read();
|
|
80
|
-
}
|
|
81
|
-
catch (cause) {
|
|
82
|
-
if (cause instanceof MalformedRecordError || isReadRefusal(cause))
|
|
83
|
-
return null;
|
|
84
|
-
throw cause;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/** A filesystem refusal — the record was not there, or could not be opened. */
|
|
88
|
-
function isReadRefusal(cause) {
|
|
89
|
-
return cause instanceof Error && typeof cause.code === "string";
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* A reader over ONE resolution pass's records.
|
|
93
|
-
*
|
|
94
|
-
* EACH RECORD IS OPENED ONCE, WHICH IS WHY THIS IS A CLOSURE AND NOT A FUNCTION PER REFERENCE. A
|
|
95
|
-
* manifest may name several gates of one record, and a reader opening it per entry would let two
|
|
96
|
-
* references answer for two different states of the same file — a rewrite landing mid-pass, a gate
|
|
97
|
-
* present for one and gone for the next. One read makes the pass's bars one reading of one record.
|
|
98
|
-
*
|
|
99
|
-
* THE REFUSAL IS REMEMBERED TOO. Cacheing only the successes would re-open an absent or unreadable
|
|
100
|
-
* record per reference, which is the same disagreement by the other door — and the likelier door,
|
|
101
|
-
* since a record being rewritten is unreadable for exactly the window that matters.
|
|
102
|
-
*
|
|
103
|
-
* THE SUBTRACTION IS THE RECORD'S, NOT THIS EDGE'S. `killFloorOf` spends the allowance; an edge
|
|
104
|
-
* computing `detected - slack` itself would be a second place holding a fact about the record.
|
|
105
|
-
*/
|
|
106
|
-
export function killFloorReaderFor(root
|
|
107
|
-
// The reader's shape is SPELLED here rather than imported, and that is the boundary working
|
|
108
|
-
// rather than a duplication. `KillFloorReader` and the reference it takes are core-internal —
|
|
109
|
-
// the registry-home question (increment D) still governs them — and this module is published, so
|
|
110
|
-
// naming them by their `./internal` types would put an unpublished specifier back in the tarball
|
|
111
|
-
// for a type alone. Structural typing is what makes the closure the engine's parameter accepts.
|
|
112
|
-
) {
|
|
113
|
-
const opened = new Map();
|
|
114
|
-
return (reference) => {
|
|
115
|
-
const recordFile = reference.recordFile ?? RECORD_FILE;
|
|
116
|
-
if (!opened.has(recordFile)) {
|
|
117
|
-
opened.set(recordFile, orNoBar(() => parseGateRecords(readFileSync(join(root, recordFile), "utf8"), recordFile)));
|
|
118
|
-
}
|
|
119
|
-
const records = opened.get(recordFile);
|
|
120
|
-
if (records == null)
|
|
121
|
-
return null;
|
|
122
|
-
return orNoBar(() => killFloorOf(gateRecordFor(reference.mutationGate, records, recordFile)));
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
57
|
/** Read + JSON-parse a file if present, else return undefined. */
|
|
126
58
|
function readJsonIfPresent(path) {
|
|
127
59
|
if (!path || !existsSync(path))
|
|
@@ -142,7 +74,7 @@ function readJsonIfPresent(path) {
|
|
|
142
74
|
* this by hand is five chances for one to stay intolerant, and a run that reports on a corpus for
|
|
143
75
|
* four of its readings and throws on the fifth is worse than one that throws. The translation is
|
|
144
76
|
* also why `discoverFeatures` is not the place to be tolerant — see its own note, and
|
|
145
|
-
* `MissingFeatureCorpusError`'s
|
|
77
|
+
* `MissingFeatureCorpusError`'s.
|
|
146
78
|
*
|
|
147
79
|
* IT IS `[]`, NOT A VERDICT. Zero rows is what @SCN-CLI-019 reads as NOTHING TO RECONCILE, and a
|
|
148
80
|
* corpus-less target that still has uncited credits keeps its suspense row and its exit 1
|
|
@@ -217,35 +149,6 @@ export function featureCorpusParseErrors(featuresDir) {
|
|
|
217
149
|
throw err;
|
|
218
150
|
}
|
|
219
151
|
}
|
|
220
|
-
/**
|
|
221
|
-
* The `@SCN` ids the feature corpus CARRIES MORE THAN ONCE (@SCN-LDG-020's guardrail, 3F-1775).
|
|
222
|
-
*
|
|
223
|
-
* An `@SCN` identifies exactly ONE scenario — that is the whole basis of attributing evidence to
|
|
224
|
-
* it. If two scenarios carry the same id, a test citing it could be proving EITHER, and the tool
|
|
225
|
-
* cannot know which: the corpus is ambiguous, and any verdict over it is a guess.
|
|
226
|
-
*
|
|
227
|
-
* WHY THIS EXISTS. Tag inheritance made this reachable: an `@SCN` hoisted to a `Feature:`/`Rule:`
|
|
228
|
-
* is inherited by every scenario beneath it (cucumber does this, so we do — parity). Left
|
|
229
|
-
* unhandled, ONE passing test then marked EVERY scenario sharing that id `balanced`, including
|
|
230
|
-
* scenarios nothing proves — a BALANCED-WHEN-BROKEN run, exit 0. A new silent false-green,
|
|
231
|
-
* introduced by the very fix that closed the last one.
|
|
232
|
-
*
|
|
233
|
-
* Refused at the edge (exit 2) rather than reconciled, under the Rule already shipped for the
|
|
234
|
-
* observed side and the corrupt corpus (@SCN-CLI-014/015/016): an input the tool cannot read is a
|
|
235
|
-
* usage error, never a verdict. Whether hoisting an `@SCN` is an authoring fault worth a richer
|
|
236
|
-
* diagnostic is the deferred policy question (3F-1774's sibling); refusing to reconcile a corpus we
|
|
237
|
-
* cannot read is not a policy — it is the floor.
|
|
238
|
-
*/
|
|
239
|
-
export function featureCorpusDuplicateScnIds(featuresDir) {
|
|
240
|
-
const seen = new Set();
|
|
241
|
-
const duplicates = new Set();
|
|
242
|
-
for (const scenario of scenariosFromFeatures(featuresDir)) {
|
|
243
|
-
if (seen.has(scenario.scnId))
|
|
244
|
-
duplicates.add(scenario.scnId);
|
|
245
|
-
seen.add(scenario.scnId);
|
|
246
|
-
}
|
|
247
|
-
return [...duplicates].sort();
|
|
248
|
-
}
|
|
249
152
|
/**
|
|
250
153
|
* Read the target's `feature-code → slug` map over the same `discoverFeatures` corpus
|
|
251
154
|
* (@SCN-RPT-006) — each coded feature file's `@<FFF>` header (`featureCode`)
|
|
@@ -254,10 +157,9 @@ export function featureCorpusDuplicateScnIds(featuresDir) {
|
|
|
254
157
|
* the balance, threaded to `renderMarkdown` (via `writeOutputs`) as the 5th sibling arg
|
|
255
158
|
* (markdown-only — the by-feature cut is markdown-only, so it is NOT serialised to JSON).
|
|
256
159
|
* Uncoded files (`featureCode === null`) are omitted, so the by-feature emitter falls
|
|
257
|
-
* back to the bare `FFF` for any id whose code no file registers.
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* now, which is the first time a CONSUMER of this reader has it too.
|
|
160
|
+
* back to the bare `FFF` for any id whose code no file registers. When two files carry one
|
|
161
|
+
* code the map keeps the last, and that run lists `duplicate-feature-code` under Scenario code
|
|
162
|
+
* errors and fails (@SCN-STG-010, 3F-3635), so a report whose map is ambiguous never passes.
|
|
261
163
|
*/
|
|
262
164
|
export function readFeatureNames(featuresDir) {
|
|
263
165
|
const map = new Map();
|
|
@@ -283,9 +185,11 @@ export function readEvidenceObligations(features) {
|
|
|
283
185
|
let scenarioCount = 0;
|
|
284
186
|
const untagged = [];
|
|
285
187
|
const sources = [];
|
|
188
|
+
const files = [];
|
|
286
189
|
for (const f of featureFiles) {
|
|
287
190
|
const source = readFileSync(f.path, "utf8");
|
|
288
191
|
sources.push(source);
|
|
192
|
+
files.push({ feature: `${f.slug}.feature`, source });
|
|
289
193
|
raw += countScnOccurrences(source);
|
|
290
194
|
scenarioCount += parseScenarios(source).length;
|
|
291
195
|
for (const scenario of parseUntaggedScenarios(source)) {
|
|
@@ -295,7 +199,7 @@ export function readEvidenceObligations(features) {
|
|
|
295
199
|
untagged.sort((a, b) => a.feature.localeCompare(b.feature) || a.scenario.localeCompare(b.scenario));
|
|
296
200
|
// The prose-only codes are a CROSS-CORPUS difference (a code tagged in any file isn't
|
|
297
201
|
// dangling), so it reads all sources at once — not per-file like the others.
|
|
298
|
-
return evidenceObligations(raw, scenarioCount, untagged, proseOnlyCodes(sources));
|
|
202
|
+
return evidenceObligations(raw, scenarioCount, untagged, proseOnlyCodes(sources), sharedScnCodesOf(files));
|
|
299
203
|
}
|
|
300
204
|
/**
|
|
301
205
|
* Parse the target's RUNTIME observations ONCE — the tag-derived, per-tier observation set
|
|
@@ -323,9 +227,8 @@ export function runReconcile(opts) {
|
|
|
323
227
|
const pkgPath = opts.packageJson ?? "package.json";
|
|
324
228
|
const packageJson = readJsonIfPresent(pkgPath) ?? {};
|
|
325
229
|
// The TARGET repo root — the credit locators are made relative to it, so a phantom-scenario row's
|
|
326
|
-
// where-to-fix (ruling Q5) stays portable in a banked artefact
|
|
327
|
-
//
|
|
328
|
-
// threaded, which is also what keeps `execFileSync` and `process.cwd()` out of the engine.
|
|
230
|
+
// where-to-fix (ruling Q5) stays portable in a banked artefact. Resolved ONCE here and threaded,
|
|
231
|
+
// which is also what keeps `execFileSync` and `process.cwd()` out of the engine.
|
|
329
232
|
const root = resolveTargetRoot({ features: opts.features });
|
|
330
233
|
// `--ci` names a workflow FILE or a DIRECTORY of them, and defaults to the directory
|
|
331
234
|
// (@SCN-USG-006, 3F-2238): a pipeline split across files enforces its checks just as one
|
|
@@ -340,11 +243,19 @@ export function runReconcile(opts) {
|
|
|
340
243
|
// keeping the core that way is the point. Resolving the list twice would be two chances for the
|
|
341
244
|
// bytes and the names to fall out of the order they are paired by.
|
|
342
245
|
const ciPaths = ciSourcePathsOf(ciPath);
|
|
246
|
+
// The corpus, discovered and read ONCE: its bytes, and — paired by index — which file each came
|
|
247
|
+
// from and the header code it carries, for the scenario code errors (3F-3635).
|
|
248
|
+
const corpusFiles = balanceCorpus(opts.features).map((f) => ({
|
|
249
|
+
slug: f.slug,
|
|
250
|
+
featureCode: f.featureCode,
|
|
251
|
+
source: readFileSync(f.path, "utf8"),
|
|
252
|
+
}));
|
|
343
253
|
return evidenceReconciliationFromSources({
|
|
344
254
|
// The corpus's bytes, read ONCE at this impure edge (3F-3088). The engine composes the three
|
|
345
255
|
// readings a citing run needs from this one array, so they cannot be assembled from two
|
|
346
256
|
// discoveries that disagree about the corpus (@SCN-LNT-009).
|
|
347
|
-
featureSources:
|
|
257
|
+
featureSources: corpusFiles.map((f) => f.source),
|
|
258
|
+
featureFiles: corpusFiles.map(({ slug, featureCode }) => ({ slug, featureCode })),
|
|
348
259
|
// The untagged half by its `<feature> : <scenario>` locator — parsed here because the locator
|
|
349
260
|
// names the FILE, which the discovery knows and a source string does not.
|
|
350
261
|
untaggedScenarios: untaggedScenariosFromFeatures(opts.features),
|
|
@@ -355,26 +266,22 @@ export function runReconcile(opts) {
|
|
|
355
266
|
// half of the entry that does not come out of the workflow's own text. The directory it sits
|
|
356
267
|
// in is the caller's `--ci`, not the consumer's subject, so it is not part of what they write.
|
|
357
268
|
ciSourceNames: ciPaths.map((path) => basename(path)),
|
|
358
|
-
measurements: readJsonIfPresent(opts.measurements) ?? {},
|
|
359
269
|
// The CONVERTED checks' credit stream, read at this edge like every other report. Absent it is
|
|
360
270
|
// undefined rather than empty, and that distinction reaches the reader (@SCN-LNT-027): "no
|
|
361
271
|
// record" is a run nobody recorded, "a record holding nothing" is a run that recorded none of
|
|
362
272
|
// these steps, and those are opposite facts.
|
|
363
|
-
// STRUCTURALLY, NOT BY NAME
|
|
364
|
-
// travels in the tarball and reaches core through the PUBLIC barrel alone (3F-3086), and
|
|
273
|
+
// STRUCTURALLY, NOT BY NAME: this module travels in the tarball and reaches core through the PUBLIC barrel alone (3F-3086), and
|
|
365
274
|
// `CiStepRecord` is not on it. The shape is the document's, which is the consumer's, so what is
|
|
366
275
|
// written here is what the file holds rather than a contract owed to anyone.
|
|
367
276
|
ciStepRecord: readJsonIfPresent(opts.ciSteps),
|
|
368
277
|
runtimeObservations: parseRuntimeObservations(opts, root),
|
|
369
|
-
// The
|
|
370
|
-
// starter deferral set is the application layer's own policy, how this tool is invoked to
|
|
371
|
-
// reconcile is a fact about this product's command line
|
|
372
|
-
// target's tree. THE PREFIX SET USED TO SIT HERE AND IS GONE (@SCN-USG-004, 3F-3343): the
|
|
278
|
+
// The two things the engine's resolvers cannot hold, and each for its own reason: the
|
|
279
|
+
// starter deferral set is the application layer's own policy, and how this tool is invoked to
|
|
280
|
+
// reconcile is a fact about this product's command line. THE PREFIX SET USED TO SIT HERE AND IS GONE (@SCN-USG-004, 3F-3343): the
|
|
373
281
|
// engine no longer decides a check's membership by its name, so there is no convention left
|
|
374
282
|
// for this edge to own.
|
|
375
283
|
deferralTags: DEFAULT_DEFERRAL_TAGS,
|
|
376
284
|
reconcilerInvocation: RECONCILER_INVOCATION,
|
|
377
|
-
killFloor: killFloorReaderFor(root),
|
|
378
285
|
});
|
|
379
286
|
}
|
|
380
287
|
// The static-check kind-counts are no longer read as a parallel census (@SCN-USG-001 / 3F-2118):
|