spec-controller 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +112 -0
- package/dist/cli-args.d.ts +73 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +114 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-balance/cli.d.ts +20 -20
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +221 -202
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-balance/emit/writer.d.ts +1 -1
- package/dist/cli-registry.d.ts +35 -12
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +95 -36
- package/dist/cli-registry.js.map +1 -1
- package/dist/cli.d.ts +5 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -3
- package/dist/cli.js.map +1 -1
- package/dist/corpus/cli.d.ts +34 -0
- package/dist/corpus/cli.d.ts.map +1 -0
- package/dist/corpus/cli.js +125 -0
- package/dist/corpus/cli.js.map +1 -0
- package/dist/deferralTags.d.ts +3 -3
- package/dist/deferralTags.js +3 -3
- package/dist/ingest/gherkinValidation.d.ts +29 -5
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +33 -5
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +15 -25
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +87 -104
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/dist/ingest/ingestScenarios.d.ts +2 -1
- package/dist/ingest/ingestScenarios.d.ts.map +1 -1
- package/dist/ingest/ingestScenarios.js +32 -3
- package/dist/ingest/ingestScenarios.js.map +1 -1
- package/dist/run-management/resolveRunInputs.d.ts +24 -11
- package/dist/run-management/resolveRunInputs.d.ts.map +1 -1
- package/dist/run-management/resolveRunInputs.js +49 -13
- package/dist/run-management/resolveRunInputs.js.map +1 -1
- package/package.json +2 -2
- package/dist/mutation-ratchet/index.d.ts +0 -48
- package/dist/mutation-ratchet/index.d.ts.map +0 -1
- package/dist/mutation-ratchet/index.js +0 -48
- package/dist/mutation-ratchet/index.js.map +0 -1
- package/dist/mutation-ratchet/ratchet.d.ts +0 -129
- package/dist/mutation-ratchet/ratchet.d.ts.map +0 -1
- package/dist/mutation-ratchet/ratchet.js +0 -222
- package/dist/mutation-ratchet/ratchet.js.map +0 -1
- package/dist/mutation-ratchet/ratchetCli.d.ts +0 -57
- package/dist/mutation-ratchet/ratchetCli.d.ts.map +0 -1
- package/dist/mutation-ratchet/ratchetCli.js +0 -139
- package/dist/mutation-ratchet/ratchetCli.js.map +0 -1
- package/dist/mutation-ratchet/reconcile.d.ts +0 -82
- package/dist/mutation-ratchet/reconcile.d.ts.map +0 -1
- package/dist/mutation-ratchet/reconcile.js +0 -67
- package/dist/mutation-ratchet/reconcile.js.map +0 -1
- package/dist/mutation-ratchet/record.d.ts +0 -210
- package/dist/mutation-ratchet/record.d.ts.map +0 -1
- package/dist/mutation-ratchet/record.js +0 -330
- package/dist/mutation-ratchet/record.js.map +0 -1
- package/dist/mutation-ratchet/report.d.ts +0 -83
- package/dist/mutation-ratchet/report.d.ts.map +0 -1
- package/dist/mutation-ratchet/report.js +0 -148
- package/dist/mutation-ratchet/report.js.map +0 -1
- package/dist/mutation-ratchet/verdict.d.ts +0 -177
- package/dist/mutation-ratchet/verdict.d.ts.map +0 -1
- package/dist/mutation-ratchet/verdict.js +0 -387
- package/dist/mutation-ratchet/verdict.js.map +0 -1
|
@@ -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 {
|
|
@@ -67,4 +68,27 @@ export declare class UnreadableFeatureCorpusError extends Error {
|
|
|
67
68
|
readonly errors: readonly GherkinParseError[];
|
|
68
69
|
constructor(errors: readonly GherkinParseError[]);
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The corpus-level ABSENCE refusal (@SCN-CLI-017, 3F-3345) — sibling to the parse refusal above,
|
|
73
|
+
* and deliberately the same shape: the reader refuses ONCE, and each command decides what the
|
|
74
|
+
* refusal means for it.
|
|
75
|
+
*
|
|
76
|
+
* WHY A TYPED ERROR RATHER THAN AN EMPTY CORPUS. The obvious fix is for `discoverFeatures` to hand
|
|
77
|
+
* back `[]` when the directory is not there. Measured, that would ship a NEW false green in a
|
|
78
|
+
* second command: `spec-controller tags` reaches the same reader through `readCorpusInputs`, whose
|
|
79
|
+
* GENERIC catch is the only thing turning an absent corpus — and, since `tags` carries no
|
|
80
|
+
* `checkInputsExist`, a supplied-but-absent `--features` too — into `The corpus could not be
|
|
81
|
+
* read: …` at exit 2. Tolerance at the reader turns both into `tags: OK …` at exit 0, inside the
|
|
82
|
+
* slice whose whole purpose is killing a vacuous certification. Typed, both survive untouched:
|
|
83
|
+
* this IS an `Error`, so it still lands in that generic catch.
|
|
84
|
+
*
|
|
85
|
+
* IT CARRIES THE DIRECTORY because the report is owed the name of what it examined — "I found no
|
|
86
|
+
* record" and "I found no regression" must never wear one word (3F-2947), and a consumer told
|
|
87
|
+
* their corpus is empty deserves to know which directory was read to reach that.
|
|
88
|
+
*/
|
|
89
|
+
export declare class MissingFeatureCorpusError extends Error {
|
|
90
|
+
/** The directory that is not there — as resolved, so a reader can print the path examined. */
|
|
91
|
+
readonly directory: string;
|
|
92
|
+
constructor(directory: string);
|
|
93
|
+
}
|
|
70
94
|
//# sourceMappingURL=gherkinValidation.d.ts.map
|
|
@@ -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;;;;;;;;;;;;;;;;;GAiBG;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";
|
|
@@ -82,4 +83,31 @@ export class UnreadableFeatureCorpusError extends Error {
|
|
|
82
83
|
this.errors = errors;
|
|
83
84
|
}
|
|
84
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The corpus-level ABSENCE refusal (@SCN-CLI-017, 3F-3345) — sibling to the parse refusal above,
|
|
88
|
+
* and deliberately the same shape: the reader refuses ONCE, and each command decides what the
|
|
89
|
+
* refusal means for it.
|
|
90
|
+
*
|
|
91
|
+
* WHY A TYPED ERROR RATHER THAN AN EMPTY CORPUS. The obvious fix is for `discoverFeatures` to hand
|
|
92
|
+
* back `[]` when the directory is not there. Measured, that would ship a NEW false green in a
|
|
93
|
+
* second command: `spec-controller tags` reaches the same reader through `readCorpusInputs`, whose
|
|
94
|
+
* GENERIC catch is the only thing turning an absent corpus — and, since `tags` carries no
|
|
95
|
+
* `checkInputsExist`, a supplied-but-absent `--features` too — into `The corpus could not be
|
|
96
|
+
* read: …` at exit 2. Tolerance at the reader turns both into `tags: OK …` at exit 0, inside the
|
|
97
|
+
* slice whose whole purpose is killing a vacuous certification. Typed, both survive untouched:
|
|
98
|
+
* this IS an `Error`, so it still lands in that generic catch.
|
|
99
|
+
*
|
|
100
|
+
* IT CARRIES THE DIRECTORY because the report is owed the name of what it examined — "I found no
|
|
101
|
+
* record" and "I found no regression" must never wear one word (3F-2947), and a consumer told
|
|
102
|
+
* their corpus is empty deserves to know which directory was read to reach that.
|
|
103
|
+
*/
|
|
104
|
+
export class MissingFeatureCorpusError extends Error {
|
|
105
|
+
/** The directory that is not there — as resolved, so a reader can print the path examined. */
|
|
106
|
+
directory;
|
|
107
|
+
constructor(directory) {
|
|
108
|
+
super(`The feature corpus directory does not exist: ${directory}`);
|
|
109
|
+
this.name = "MissingFeatureCorpusError";
|
|
110
|
+
this.directory = directory;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
85
113
|
//# sourceMappingURL=gherkinValidation.js.map
|
|
@@ -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;;;;;;;;;;;;;;;;;GAiBG;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,30 +15,11 @@
|
|
|
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
|
|
40
21
|
* simply absent). `packageJson` is the EXTERNALLY-SPECIFIED package.json the static
|
|
41
|
-
*
|
|
22
|
+
* static-check reader reads `exists` from (the --package-json seam, @SCN-LNT-005):
|
|
42
23
|
* default the tool's own cwd `package.json`; set it to reconcile ANOTHER repo's
|
|
43
24
|
* static checks — required to render the historical watermelon correctly.
|
|
44
25
|
*/
|
|
@@ -49,11 +30,18 @@ export interface ReconcileOptions {
|
|
|
49
30
|
ci?: string;
|
|
50
31
|
packageJson?: string;
|
|
51
32
|
/**
|
|
52
|
-
* The
|
|
53
|
-
*
|
|
54
|
-
*
|
|
33
|
+
* The CI RUN'S OWN RECORD of what each of its steps did (@SCN-LNT-021, @SCN-LNT-028) — the credit
|
|
34
|
+
* side of every CONVERTED fitness check, written by the consumer's own pipeline and by neither
|
|
35
|
+
* party to this reconciliation.
|
|
36
|
+
*
|
|
37
|
+
* KEPT APART FROM `ci` BECAUSE THE TWO DOCUMENTS ANSWER DIFFERENT QUESTIONS. The workflow FILE
|
|
38
|
+
* says a check is wired into this pipeline and which row its step cites — a fact about a
|
|
39
|
+
* repository, true between runs. This is one RUN's record: did it run this time, and what did it
|
|
40
|
+
* find. Optional like the other result files: a consumer whose pipeline dumps no record, or a
|
|
41
|
+
* local run with no pipeline at all, hands none, and the converted rows it would have answered
|
|
42
|
+
* read `missing`.
|
|
55
43
|
*/
|
|
56
|
-
|
|
44
|
+
ciSteps?: string;
|
|
57
45
|
}
|
|
58
46
|
/**
|
|
59
47
|
* The feature corpus's INTEGRITY CHECKPOINT (@SCN-CLI-016, 3F-1767) — validate every discovered
|
|
@@ -101,7 +89,9 @@ export declare function featureCorpusDuplicateScnIds(featuresDir: string | undef
|
|
|
101
89
|
* (markdown-only — the by-feature cut is markdown-only, so it is NOT serialised to JSON).
|
|
102
90
|
* Uncoded files (`featureCode === null`) are omitted, so the by-feature emitter falls
|
|
103
91
|
* back to the bare `FFF` for any id whose code no file registers. One-prefix-one-file holds
|
|
104
|
-
* by invariant (`
|
|
92
|
+
* by invariant (`corpusFaults` raises `duplicate-feature-code`, @SCN-STG-010), so the map is
|
|
93
|
+
* unambiguous. That invariant was `lintFeatureCodes`' until 3F-3313; it is the shipped command's
|
|
94
|
+
* now, which is the first time a CONSUMER of this reader has it too.
|
|
105
95
|
*/
|
|
106
96
|
export declare function readFeatureNames(featuresDir?: string): Map<string, string>;
|
|
107
97
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestQualityChecks.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,OAAO,
|
|
1
|
+
{"version":3,"file":"ingestQualityChecks.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,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;AA6B7C;;;;;;;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAStF;AAED;;;;;;;;;;;;GAYG;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,CAqB9E;AA0CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,sBAAsB,CA2D3E"}
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* `package.json` to observe each fitness check the corpus cites.
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync, existsSync } from "node:fs";
|
|
16
|
-
import {
|
|
16
|
+
import { basename } from "node:path";
|
|
17
17
|
import { ciSourcePathsOf } from "./ciSourcePaths.js";
|
|
18
18
|
import { discoverFeatures } from "./ingestScenarios.js";
|
|
19
19
|
import { parseScenarios, countScnOccurrences, parseUntaggedScenarios, proseOnlyCodes, } from "@3f-consulting/spec-controller-core";
|
|
20
|
-
import { UnreadableFeatureCorpusError } from "./gherkinValidation.js";
|
|
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";
|
|
23
23
|
import { parseVitestEvidenceObservations, } from "@3f-consulting/spec-controller-core";
|
|
@@ -30,10 +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 mutation-ratchet library, reached for the RECORD half of a bar that names a gate
|
|
34
|
-
// (@SCN-MUT-012): its conventional record name, its reader, and the floor a gate's own figures
|
|
35
|
-
// imply. Through the barrel, which is the surface a consumer holds.
|
|
36
|
-
import { gateRecordFor, killFloorOf, MalformedRecordError, parseGateRecords, RECORD_FILE, } from "../mutation-ratchet/index.js";
|
|
37
33
|
/**
|
|
38
34
|
* The per-input FALLBACK quality-check for each JS-ecosystem result source — the
|
|
39
35
|
* source→quality-check mapping, SET at the CLI edge rather than hardcoded in the core
|
|
@@ -44,88 +40,61 @@ import { gateRecordFor, killFloorOf, MalformedRecordError, parseGateRecords, REC
|
|
|
44
40
|
const VITEST_QUALITY_CHECK = "unit";
|
|
45
41
|
const CUCUMBER_QUALITY_CHECK = "integration";
|
|
46
42
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `guard:*` is AWTY's (four `guard:*` guards run in AWTY's CI). A third convention adds its
|
|
51
|
-
* prefix here; the core never assumes the set. No CLI override flag yet — that earns its place
|
|
52
|
-
* at a third ecosystem, not now (mirrors the VITEST_QUALITY_CHECK stance above).
|
|
53
|
-
* `qualityCheck: "lint"` (the static-check axis name) is unchanged — this is the script prefix,
|
|
54
|
-
* not the axis.
|
|
43
|
+
* THIS TOOL, RUN TO RECONCILE (@SCN-USG-014, 3F-3343) — the invocation whose step is the
|
|
44
|
+
* reconciler rather than one of the reconciled rows, SET at the CLI edge and injected into the
|
|
45
|
+
* core like the prefixes above and the workflow names below.
|
|
55
46
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* `resolveStaticChecks`) rather than renaming its scripts to our convention, which is not a thing
|
|
60
|
-
* the tool can ask of a client.
|
|
61
|
-
*/
|
|
62
|
-
const GUARD_SCRIPT_PREFIXES = ["lint:", "guard:"];
|
|
63
|
-
/**
|
|
64
|
-
* A refusal met with an absent bar rather than let out of this edge.
|
|
47
|
+
* HOW THIS PRODUCT IS INVOKED IS THIS LAYER'S FACT. `spec-controller` is the `bin` this package
|
|
48
|
+
* publishes and `balance` is the command that reconciles; the engine holds neither, and a core
|
|
49
|
+
* that assumed them would be asserting something about a command line it never sees.
|
|
65
50
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* TWO CLASSES AND NO MORE. The record library normalises every fault it can see — the YAML parser's
|
|
72
|
-
* own included — into `MalformedRecordError` precisely so a caller catches all of them by catching
|
|
73
|
-
* one; beside it sits the read that never got as far as a record. A bare catch would swallow a
|
|
74
|
-
* defect in this edge as well, and answer it with the same silent absence.
|
|
51
|
+
* BOTH TOKENS, AND THE SECOND IS NOT DECORATION. This repository's own pipeline runs
|
|
52
|
+
* `pnpm spec-controller tags` beside its balance step — the same binary, doing something that is
|
|
53
|
+
* an ordinary step of the pipeline being reconciled — so an invocation naming the binary alone
|
|
54
|
+
* would take that step off the books. The comparison is whole, adjacent shell tokens
|
|
55
|
+
* (`parse/commandRuns`), so this names the reconciliation and nothing else the tool can do.
|
|
75
56
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
throw cause;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** A filesystem refusal — the record was not there, or could not be opened. */
|
|
87
|
-
function isReadRefusal(cause) {
|
|
88
|
-
return cause instanceof Error && typeof cause.code === "string";
|
|
57
|
+
const RECONCILER_INVOCATION = "spec-controller balance";
|
|
58
|
+
/** Read + JSON-parse a file if present, else return undefined. */
|
|
59
|
+
function readJsonIfPresent(path) {
|
|
60
|
+
if (!path || !existsSync(path))
|
|
61
|
+
return undefined;
|
|
62
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
89
63
|
}
|
|
90
64
|
/**
|
|
91
|
-
*
|
|
65
|
+
* THE CORPUS `balance` READS — the shared discovery, with an ABSENT directory read as NO CORPUS
|
|
66
|
+
* (@SCN-CLI-017, 3F-3345). The single place this command opts in.
|
|
67
|
+
*
|
|
68
|
+
* `new-client` is a supported adoption state, not a degraded one, and most targets have no
|
|
69
|
+
* `features/` directory at all — so an absent DEFAULT corpus is the target's honest shape and
|
|
70
|
+
* `balance` reports on it. Left to escape it was a raw `ENOENT` landing on Node's default exit 1,
|
|
71
|
+
* the OUT-OF-BALANCE code, which is exactly the collision @SCN-CLI-014/015/016 closed on every
|
|
72
|
+
* other input, alive on the one path each new consumer is guaranteed to take.
|
|
92
73
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
74
|
+
* ONE HELPER, NOT FIVE CATCHES, and that is the whole reason it exists: five readers each doing
|
|
75
|
+
* this by hand is five chances for one to stay intolerant, and a run that reports on a corpus for
|
|
76
|
+
* four of its readings and throws on the fifth is worse than one that throws. The translation is
|
|
77
|
+
* also why `discoverFeatures` is not the place to be tolerant — see its own note, and
|
|
78
|
+
* `MissingFeatureCorpusError`'s: `tags` reaches the same reader and keeps refusing.
|
|
97
79
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
80
|
+
* IT IS `[]`, NOT A VERDICT. Zero rows is what @SCN-CLI-019 reads as NOTHING TO RECONCILE, and a
|
|
81
|
+
* corpus-less target that still has uncited credits keeps its suspense row and its exit 1
|
|
82
|
+
* (@SCN-CLI-020) — this reader says what it found, never what the run means.
|
|
101
83
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
84
|
+
* A supplied-but-absent `--features` never arrives here: `checkInputsExist` refuses it at the CLI
|
|
85
|
+
* edge, exit 2 (@SCN-CLI-002). The precedent is `ciSourcePathsOf`, which already answers an absent
|
|
86
|
+
* DEFAULT `.github/workflows` with `[]` for the same reason — the default finding nothing is a fact
|
|
87
|
+
* about the target, not a mistake by its author.
|
|
104
88
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const recordFile = reference.recordFile ?? RECORD_FILE;
|
|
115
|
-
if (!opened.has(recordFile)) {
|
|
116
|
-
opened.set(recordFile, orNoBar(() => parseGateRecords(readFileSync(join(root, recordFile), "utf8"), recordFile)));
|
|
117
|
-
}
|
|
118
|
-
const records = opened.get(recordFile);
|
|
119
|
-
if (records == null)
|
|
120
|
-
return null;
|
|
121
|
-
return orNoBar(() => killFloorOf(gateRecordFor(reference.mutationGate, records, recordFile)));
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
/** Read + JSON-parse a file if present, else return undefined. */
|
|
125
|
-
function readJsonIfPresent(path) {
|
|
126
|
-
if (!path || !existsSync(path))
|
|
127
|
-
return undefined;
|
|
128
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
89
|
+
function balanceCorpus(featuresDir) {
|
|
90
|
+
try {
|
|
91
|
+
return featuresDir !== undefined ? discoverFeatures(featuresDir) : discoverFeatures();
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof MissingFeatureCorpusError)
|
|
95
|
+
return [];
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
129
98
|
}
|
|
130
99
|
/**
|
|
131
100
|
* The discovered feature corpus's SOURCES — read once so the two readings a citing run needs (the
|
|
@@ -134,8 +103,7 @@ function readJsonIfPresent(path) {
|
|
|
134
103
|
* was not built from is exactly the phantom-row fault (@SCN-LNT-009).
|
|
135
104
|
*/
|
|
136
105
|
function featureSources(featuresDir) {
|
|
137
|
-
|
|
138
|
-
return features.map((f) => readFileSync(f.path, "utf8"));
|
|
106
|
+
return balanceCorpus(featuresDir).map((f) => readFileSync(f.path, "utf8"));
|
|
139
107
|
}
|
|
140
108
|
function scenariosFromFeatures(featuresDir, deferralTags = DEFAULT_DEFERRAL_TAGS) {
|
|
141
109
|
return featureSources(featuresDir).flatMap((source) => parseScenarios(source, deferralTags));
|
|
@@ -153,8 +121,7 @@ function scenariosFromFeatures(featuresDir, deferralTags = DEFAULT_DEFERRAL_TAGS
|
|
|
153
121
|
* where to go.
|
|
154
122
|
*/
|
|
155
123
|
function untaggedScenariosFromFeatures(featuresDir) {
|
|
156
|
-
|
|
157
|
-
return features.flatMap((f) => parseUntaggedScenarios(readFileSync(f.path, "utf8")).map((scenario) => `${f.slug}.feature : ${scenario}`));
|
|
124
|
+
return balanceCorpus(featuresDir).flatMap((f) => parseUntaggedScenarios(readFileSync(f.path, "utf8")).map((scenario) => `${f.slug}.feature : ${scenario}`));
|
|
158
125
|
}
|
|
159
126
|
/**
|
|
160
127
|
* The feature corpus's INTEGRITY CHECKPOINT (@SCN-CLI-016, 3F-1767) — validate every discovered
|
|
@@ -174,10 +141,7 @@ function untaggedScenariosFromFeatures(featuresDir) {
|
|
|
174
141
|
*/
|
|
175
142
|
export function featureCorpusParseErrors(featuresDir) {
|
|
176
143
|
try {
|
|
177
|
-
|
|
178
|
-
discoverFeatures(featuresDir);
|
|
179
|
-
else
|
|
180
|
-
discoverFeatures();
|
|
144
|
+
balanceCorpus(featuresDir);
|
|
181
145
|
return [];
|
|
182
146
|
}
|
|
183
147
|
catch (err) {
|
|
@@ -224,12 +188,13 @@ export function featureCorpusDuplicateScnIds(featuresDir) {
|
|
|
224
188
|
* (markdown-only — the by-feature cut is markdown-only, so it is NOT serialised to JSON).
|
|
225
189
|
* Uncoded files (`featureCode === null`) are omitted, so the by-feature emitter falls
|
|
226
190
|
* back to the bare `FFF` for any id whose code no file registers. One-prefix-one-file holds
|
|
227
|
-
* by invariant (`
|
|
191
|
+
* by invariant (`corpusFaults` raises `duplicate-feature-code`, @SCN-STG-010), so the map is
|
|
192
|
+
* unambiguous. That invariant was `lintFeatureCodes`' until 3F-3313; it is the shipped command's
|
|
193
|
+
* now, which is the first time a CONSUMER of this reader has it too.
|
|
228
194
|
*/
|
|
229
195
|
export function readFeatureNames(featuresDir) {
|
|
230
|
-
const features = featuresDir !== undefined ? discoverFeatures(featuresDir) : discoverFeatures();
|
|
231
196
|
const map = new Map();
|
|
232
|
-
for (const f of
|
|
197
|
+
for (const f of balanceCorpus(featuresDir)) {
|
|
233
198
|
if (f.featureCode !== null)
|
|
234
199
|
map.set(f.featureCode, f.slug);
|
|
235
200
|
}
|
|
@@ -246,7 +211,7 @@ export function readFeatureNames(featuresDir) {
|
|
|
246
211
|
* threaded to the renderers via `writeOutputs`; `runReconcile` stays unchanged.
|
|
247
212
|
*/
|
|
248
213
|
export function readEvidenceObligations(features) {
|
|
249
|
-
const featureFiles =
|
|
214
|
+
const featureFiles = balanceCorpus(features);
|
|
250
215
|
let raw = 0;
|
|
251
216
|
let scenarioCount = 0;
|
|
252
217
|
const untagged = [];
|
|
@@ -285,15 +250,14 @@ function parseRuntimeObservations(opts, root) {
|
|
|
285
250
|
}
|
|
286
251
|
export function runReconcile(opts) {
|
|
287
252
|
// Read the target's package.json ONCE — its `specController.*` config supplies the deferral-tag
|
|
288
|
-
// extension (@SCN-PND-021)
|
|
289
|
-
//
|
|
290
|
-
//
|
|
253
|
+
// extension (@SCN-PND-021) and the configured fitness rows, and its `scripts` back the `exists`
|
|
254
|
+
// read. The disk read stays at this impure edge; the engine takes the resolved values as
|
|
255
|
+
// injected params, staying pure.
|
|
291
256
|
const pkgPath = opts.packageJson ?? "package.json";
|
|
292
257
|
const packageJson = readJsonIfPresent(pkgPath) ?? {};
|
|
293
258
|
// The TARGET repo root — the credit locators are made relative to it, so a phantom-scenario row's
|
|
294
|
-
// where-to-fix (ruling Q5) stays portable in a banked artefact
|
|
295
|
-
//
|
|
296
|
-
// threaded, which is also what keeps `execFileSync` and `process.cwd()` out of the engine.
|
|
259
|
+
// where-to-fix (ruling Q5) stays portable in a banked artefact. Resolved ONCE here and threaded,
|
|
260
|
+
// which is also what keeps `execFileSync` and `process.cwd()` out of the engine.
|
|
297
261
|
const root = resolveTargetRoot({ features: opts.features });
|
|
298
262
|
// `--ci` names a workflow FILE or a DIRECTORY of them, and defaults to the directory
|
|
299
263
|
// (@SCN-USG-006, 3F-2238): a pipeline split across files enforces its checks just as one
|
|
@@ -302,6 +266,12 @@ export function runReconcile(opts) {
|
|
|
302
266
|
// file-vs-directory distinction, so one flag stays one flag and an explicit file is still
|
|
303
267
|
// a narrowing (@SCN-USG-008).
|
|
304
268
|
const ciPath = opts.ci ?? ".github/workflows";
|
|
269
|
+
// RESOLVED ONCE, READ TWICE (@SCN-USG-015). The engine takes anonymous source strings and holds
|
|
270
|
+
// no filesystem, so the mapping from a step's source index back to the workflow file it came
|
|
271
|
+
// from is made HERE, where the paths are already in hand — `ciSourcePaths.ts` is explicit that
|
|
272
|
+
// keeping the core that way is the point. Resolving the list twice would be two chances for the
|
|
273
|
+
// bytes and the names to fall out of the order they are paired by.
|
|
274
|
+
const ciPaths = ciSourcePathsOf(ciPath);
|
|
305
275
|
return evidenceReconciliationFromSources({
|
|
306
276
|
// The corpus's bytes, read ONCE at this impure edge (3F-3088). The engine composes the three
|
|
307
277
|
// readings a citing run needs from this one array, so they cannot be assembled from two
|
|
@@ -311,15 +281,28 @@ export function runReconcile(opts) {
|
|
|
311
281
|
// names the FILE, which the discovery knows and a source string does not.
|
|
312
282
|
untaggedScenarios: untaggedScenariosFromFeatures(opts.features),
|
|
313
283
|
packageJson,
|
|
314
|
-
ciSources:
|
|
315
|
-
|
|
284
|
+
ciSources: ciPaths.map((path) => readFileSync(path, "utf8")),
|
|
285
|
+
// THE FILE, NOT THE PATH. A `notChecks` entry opens with the workflow's own name — `ci.yml`,
|
|
286
|
+
// `release.yml` — which is what a consumer reads at the head of their run log and the only
|
|
287
|
+
// half of the entry that does not come out of the workflow's own text. The directory it sits
|
|
288
|
+
// in is the caller's `--ci`, not the consumer's subject, so it is not part of what they write.
|
|
289
|
+
ciSourceNames: ciPaths.map((path) => basename(path)),
|
|
290
|
+
// The CONVERTED checks' credit stream, read at this edge like every other report. Absent it is
|
|
291
|
+
// undefined rather than empty, and that distinction reaches the reader (@SCN-LNT-027): "no
|
|
292
|
+
// record" is a run nobody recorded, "a record holding nothing" is a run that recorded none of
|
|
293
|
+
// these steps, and those are opposite facts.
|
|
294
|
+
// STRUCTURALLY, NOT BY NAME: this module travels in the tarball and reaches core through the PUBLIC barrel alone (3F-3086), and
|
|
295
|
+
// `CiStepRecord` is not on it. The shape is the document's, which is the consumer's, so what is
|
|
296
|
+
// written here is what the file holds rather than a contract owed to anyone.
|
|
297
|
+
ciStepRecord: readJsonIfPresent(opts.ciSteps),
|
|
316
298
|
runtimeObservations: parseRuntimeObservations(opts, root),
|
|
317
|
-
// The
|
|
318
|
-
// deferral set
|
|
319
|
-
//
|
|
299
|
+
// The two things the engine's resolvers cannot hold, and each for its own reason: the
|
|
300
|
+
// starter deferral set is the application layer's own policy, and how this tool is invoked to
|
|
301
|
+
// 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
|
|
302
|
+
// engine no longer decides a check's membership by its name, so there is no convention left
|
|
303
|
+
// for this edge to own.
|
|
320
304
|
deferralTags: DEFAULT_DEFERRAL_TAGS,
|
|
321
|
-
|
|
322
|
-
killFloor: killFloorReaderFor(root),
|
|
305
|
+
reconcilerInvocation: RECONCILER_INVOCATION,
|
|
323
306
|
});
|
|
324
307
|
}
|
|
325
308
|
// The static-check kind-counts are no longer read as a parallel census (@SCN-USG-001 / 3F-2118):
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestQualityChecks.js","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ingestQualityChecks.js","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* records (split out of the old spec/featureCodes.ts). The pure per-source
|
|
5
5
|
* parsers (extractFeatureCode / parseScenarioCodes) live here with their sole consumer;
|
|
6
6
|
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
|
-
* featureCode.ts); the duplicate-code lint is
|
|
7
|
+
* featureCode.ts); the duplicate-code lint is engine-side too since 3F-3313, as @SCN-STG-010 in
|
|
8
|
+
* parse/corpusFaults.ts, and no longer a private check under tests/checks/.
|
|
8
9
|
*
|
|
9
10
|
* The foundation, folded into the @LDG runtime spine.
|
|
10
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestScenarios.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ingestScenarios.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mFAAmF;IACnF,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBhE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAc3D;AAwCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAsB,GAAG,iBAAiB,EAAE,CAkBjF"}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* records (split out of the old spec/featureCodes.ts). The pure per-source
|
|
5
5
|
* parsers (extractFeatureCode / parseScenarioCodes) live here with their sole consumer;
|
|
6
6
|
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
|
-
* featureCode.ts); the duplicate-code lint is
|
|
7
|
+
* featureCode.ts); the duplicate-code lint is engine-side too since 3F-3313, as @SCN-STG-010 in
|
|
8
|
+
* parse/corpusFaults.ts, and no longer a private check under tests/checks/.
|
|
8
9
|
*
|
|
9
10
|
* The foundation, folded into the @LDG runtime spine.
|
|
10
11
|
*/
|
|
11
12
|
import { readFileSync, readdirSync } from "node:fs";
|
|
12
13
|
import { join } from "node:path";
|
|
13
|
-
import { UnreadableFeatureCorpusError, parseErrorsIn } from "./gherkinValidation.js";
|
|
14
|
+
import { MissingFeatureCorpusError, UnreadableFeatureCorpusError, parseErrorsIn, } from "./gherkinValidation.js";
|
|
14
15
|
/** A code is valid when it is 2–5 uppercase alphanumerics. */
|
|
15
16
|
const CODE_PATTERN = /^[A-Z0-9]{2,5}$/;
|
|
16
17
|
/**
|
|
@@ -81,6 +82,34 @@ export function parseScenarioCodes(source) {
|
|
|
81
82
|
function featuresDir() {
|
|
82
83
|
return join(process.cwd(), "features");
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* List the corpus directory, REFUSING an absent one by name rather than by `ENOENT` (@SCN-CLI-017,
|
|
87
|
+
* 3F-3345).
|
|
88
|
+
*
|
|
89
|
+
* Absence stays a REFUSAL AT THE READER — what changed is that it is typed, so a caller can decide.
|
|
90
|
+
* `balance` opts in to reporting on a target that has no corpus (most targets have no `features/`
|
|
91
|
+
* at all, and `new-client` is a supported adoption state); `tags` keeps refusing, unedited, because
|
|
92
|
+
* a `MissingFeatureCorpusError` is still an `Error` and still lands in its generic catch. Returning
|
|
93
|
+
* `[]` here instead would make that decision for both, and measured it turns two named refusals
|
|
94
|
+
* into `tags: OK …` at exit 0.
|
|
95
|
+
*
|
|
96
|
+
* ONLY `ENOENT`. A `features` path that exists but is a FILE, or one the process may not read, is
|
|
97
|
+
* a different fault and is not ours to reinterpret — it rethrows.
|
|
98
|
+
*
|
|
99
|
+
* A SUPPLIED `--features` never reaches here absent: `checkInputsExist` refuses it at the CLI edge
|
|
100
|
+
* with exit 2 (@SCN-CLI-002). Supplied-but-absent is the operator mistyping; absent-by-default is
|
|
101
|
+
* the target's honest shape.
|
|
102
|
+
*/
|
|
103
|
+
function corpusFileNames(dir) {
|
|
104
|
+
try {
|
|
105
|
+
return readdirSync(dir);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (err.code === "ENOENT")
|
|
109
|
+
throw new MissingFeatureCorpusError(dir);
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
84
113
|
/**
|
|
85
114
|
* Discover every feature file on disk and its header code — REFUSING a corpus the Gherkin
|
|
86
115
|
* parser rejects (@SCN-GPG-001, 3F-2817).
|
|
@@ -99,7 +128,7 @@ function featuresDir() {
|
|
|
99
128
|
* rejected tree would be back to reporting a verdict over a corpus it cannot read.
|
|
100
129
|
*/
|
|
101
130
|
export function discoverFeatures(dir = featuresDir()) {
|
|
102
|
-
const files =
|
|
131
|
+
const files = corpusFileNames(dir)
|
|
103
132
|
.filter((name) => name.endsWith(".feature"))
|
|
104
133
|
.sort()
|
|
105
134
|
.map((name) => {
|