spec-controller 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +24 -4
- 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-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/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
|
@@ -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):
|
|
@@ -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,QAAQ,EAAE,
|
|
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,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAA0B,MAAM,sBAAsB,CAAC;AAChF,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GACjB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,yBAAyB,EACzB,4BAA4B,GAE7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EACL,mBAAmB,GAGpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,+BAA+B,GAEhC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,iCAAiC,GAElC,MAAM,qCAAqC,CAAC;AAC7C,mGAAmG;AACnG,yFAAyF;AACzF,yFAAyF;AACzF,gGAAgG;AAChG,iGAAiG;AACjG,oGAAoG;AACpG,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AAExF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAQ1E;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAwB,MAAM,CAAC;AACzD,MAAM,sBAAsB,GAAwB,aAAa,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AA+BxD,kEAAkE;AAClE,SAAS,iBAAiB,CAAI,IAAwB;IACpD,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;AACrD,CAAC;AACD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAS,aAAa,CAAC,WAA+B;IACpD,IAAI,CAAC;QACH,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACxF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,yBAAyB;YAAE,OAAO,EAAE,CAAC;QACxD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,WAA+B;IACrD,OAAO,aAAa,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,qBAAqB,CAC5B,WAA+B,EAC/B,eAAkC,qBAAqB;IAEvD,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,6BAA6B,CAAC,WAA+B;IACpE,OAAO,aAAa,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9C,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CACtD,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,cAAc,QAAQ,EAAE,CAChD,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAA+B;IACtE,IAAI,CAAC;QACH,aAAa,CAAC,WAAW,CAAC,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,4BAA4B;YAAE,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QACxE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAoB;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,WAAW,KAAK,IAAI;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAAiB;IACvD,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAA0C,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;QACnC,aAAa,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAC/C,KAAK,MAAM,QAAQ,IAAI,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CACX,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CACrF,CAAC;IACF,sFAAsF;IACtF,6EAA6E;IAC7E,OAAO,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7G,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,wBAAwB,CAAC,IAAsB,EAAE,IAAwB;IAChF,MAAM,GAAG,GAA0B,EAAE,CAAC;IACtC,MAAM,YAAY,GAAG,iBAAiB,CAAmB,IAAI,CAAC,MAAM,CAAC,CAAC;IACtE,IAAI,YAAY;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,+BAA+B,CAAC,YAAY,EAAE,oBAAoB,EAAE,IAAI,CAAC,CAAC,CAAC;IACzG,MAAM,cAAc,GAAG,iBAAiB,CAAqB,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5E,IAAI,cAAc;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,iCAAiC,CAAC,cAAc,EAAE,sBAAsB,EAAE,IAAI,CAAC,CAAC,CAAC;IACjH,OAAO,GAAG,CAAC;AACb,CAAC;AAwBD,MAAM,UAAU,YAAY,CAAC,IAAsB;IACjD,gGAAgG;IAChG,gGAAgG;IAChG,yFAAyF;IACzF,iCAAiC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,cAAc,CAAC;IACnD,MAAM,WAAW,GAAG,iBAAiB,CAAiB,OAAO,CAAC,IAAI,EAAE,CAAC;IACrE,kGAAkG;IAClG,iGAAiG;IACjG,iFAAiF;IACjF,MAAM,IAAI,GAAG,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5D,qFAAqF;IACrF,yFAAyF;IACzF,wFAAwF;IACxF,oFAAoF;IACpF,0FAA0F;IAC1F,8BAA8B;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,mBAAmB,CAAC;IAC9C,gGAAgG;IAChG,6FAA6F;IAC7F,+FAA+F;IAC/F,gGAAgG;IAChG,mEAAmE;IACnE,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAExC,gGAAgG;IAChG,+EAA+E;IAC/E,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;KACrC,CAAC,CAAC,CAAC;IAEJ,OAAO,iCAAiC,CAAC;QACvC,6FAA6F;QAC7F,wFAAwF;QACxF,6DAA6D;QAC7D,cAAc,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAChD,YAAY,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACjF,8FAA8F;QAC9F,0EAA0E;QAC1E,iBAAiB,EAAE,6BAA6B,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/D,WAAW;QACX,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5D,6FAA6F;QAC7F,2FAA2F;QAC3F,6FAA6F;QAC7F,+FAA+F;QAC/F,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpD,+FAA+F;QAC/F,2FAA2F;QAC3F,8FAA8F;QAC9F,6CAA6C;QAC7C,gIAAgI;QAChI,gGAAgG;QAChG,6EAA6E;QAC7E,YAAY,EAAE,iBAAiB,CAC7B,IAAI,CAAC,OAAO,CACb;QACD,mBAAmB,EAAE,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC;QACzD,sFAAsF;QACtF,8FAA8F;QAC9F,kIAAkI;QAClI,4FAA4F;QAC5F,wBAAwB;QACxB,YAAY,EAAE,qBAAqB;QACnC,oBAAoB,EAAE,qBAAqB;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,iGAAiG;AACjG,8FAA8F;AAC9F,gGAAgG;AAChG,+FAA+F;AAC/F,oGAAoG;AACpG,yFAAyF"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ingest the target's feature files — the IMPURE reader that walks features/ on disk
|
|
3
|
-
* and
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
3
|
+
* and reads each file's header code into DiscoveredFeature records (split out of the old
|
|
4
|
+
* spec/featureCodes.ts). The pure header parser (extractFeatureCode) lives here with its sole
|
|
5
|
+
* consumer; the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
6
|
* featureCode.ts); the duplicate-code lint is engine-side too since 3F-3313, as @SCN-STG-010 in
|
|
8
|
-
* parse/
|
|
7
|
+
* parse/scenarioCodeErrors.ts, and no longer a private check under tests/checks/.
|
|
8
|
+
*
|
|
9
|
+
* NO PER-FILE LIST OF SCENARIO CODES (3F-3636). One used to ride here, read by a copy of the
|
|
10
|
+
* @SCN grammar of its own, and nothing but its own test read it; a scenario's code is read by the
|
|
11
|
+
* engine's scenario walk, which is the reading the ledger's accounts are minted from.
|
|
9
12
|
*
|
|
10
13
|
* The foundation, folded into the @LDG runtime spine.
|
|
11
14
|
*/
|
|
@@ -16,8 +19,6 @@ export interface DiscoveredFeature {
|
|
|
16
19
|
/** Absolute path to the feature file. */
|
|
17
20
|
path: string;
|
|
18
21
|
featureCode: string | null;
|
|
19
|
-
/** Every @SCN-<FFF>-<NNN> scenario code in the file, in order, duplicates kept. */
|
|
20
|
-
scenarioCodes: string[];
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Read the Feature-level @<FFF> header tag from a feature file's source — the
|
|
@@ -25,13 +26,6 @@ export interface DiscoveredFeature {
|
|
|
25
26
|
* or a recognised evidence/routing tag. Returns null if absent.
|
|
26
27
|
*/
|
|
27
28
|
export declare function extractFeatureCode(source: string): string | null;
|
|
28
|
-
/**
|
|
29
|
-
* Parse every @SCN-<FFF>-<NNN> scenario code in a feature file's source, in
|
|
30
|
-
* document order, duplicates preserved. Only genuine TAG LINES are scanned (a
|
|
31
|
-
* line whose first non-blank character is `@` and whose every whitespace-
|
|
32
|
-
* separated token is a tag) — prose that merely mentions a code is skipped.
|
|
33
|
-
*/
|
|
34
|
-
export declare function parseScenarioCodes(source: string): string[];
|
|
35
29
|
/**
|
|
36
30
|
* Discover every feature file on disk and its header code — REFUSING a corpus the Gherkin
|
|
37
31
|
* parser rejects (@SCN-GPG-001, 3F-2817).
|
|
@@ -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;;;;;;;;;;;;;GAaG;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;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKhE;AA8DD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAsB,GAAG,iBAAiB,EAAE,CAiBjF"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ingest the target's feature files — the IMPURE reader that walks features/ on disk
|
|
3
|
-
* and
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
3
|
+
* and reads each file's header code into DiscoveredFeature records (split out of the old
|
|
4
|
+
* spec/featureCodes.ts). The pure header parser (extractFeatureCode) lives here with its sole
|
|
5
|
+
* consumer; the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
6
|
* featureCode.ts); the duplicate-code lint is engine-side too since 3F-3313, as @SCN-STG-010 in
|
|
8
|
-
* parse/
|
|
7
|
+
* parse/scenarioCodeErrors.ts, and no longer a private check under tests/checks/.
|
|
8
|
+
*
|
|
9
|
+
* NO PER-FILE LIST OF SCENARIO CODES (3F-3636). One used to ride here, read by a copy of the
|
|
10
|
+
* @SCN grammar of its own, and nothing but its own test read it; a scenario's code is read by the
|
|
11
|
+
* engine's scenario walk, which is the reading the ledger's accounts are minted from.
|
|
9
12
|
*
|
|
10
13
|
* The foundation, folded into the @LDG runtime spine.
|
|
11
14
|
*/
|
|
@@ -21,56 +24,32 @@ const CODE_PATTERN = /^[A-Z0-9]{2,5}$/;
|
|
|
21
24
|
*/
|
|
22
25
|
export function extractFeatureCode(source) {
|
|
23
26
|
const lines = source.split(/\r?\n/);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
break;
|
|
35
|
-
const tags = prev.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
36
|
-
for (const tag of tags) {
|
|
37
|
-
const bare = tag.slice(1);
|
|
38
|
-
if (bare.startsWith("SCN-"))
|
|
39
|
-
continue;
|
|
40
|
-
if (["unit", "integration", "e2e", "performance", "smoke"].includes(bare))
|
|
41
|
-
continue;
|
|
42
|
-
if (CODE_PATTERN.test(bare))
|
|
43
|
-
return bare;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
27
|
+
const featureLine = lines.findIndex((line) => line.trim().startsWith("Feature:"));
|
|
28
|
+
if (featureLine < 0)
|
|
29
|
+
return null;
|
|
30
|
+
return headerTagsAbove(lines, featureLine).find(isHeaderCode) ?? null;
|
|
31
|
+
}
|
|
32
|
+
/** The tags a tier or routing concern owns, which are never a file's header code. */
|
|
33
|
+
const NON_CODE_TAGS = ["unit", "integration", "e2e", "performance", "smoke"];
|
|
34
|
+
/** Is this bare tag the file's header code — not an @SCN id, not a tier tag, and code-shaped? */
|
|
35
|
+
function isHeaderCode(bare) {
|
|
36
|
+
return !bare.startsWith("SCN-") && !NON_CODE_TAGS.includes(bare) && CODE_PATTERN.test(bare);
|
|
50
37
|
}
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* line whose first non-blank character is `@` and whose every whitespace-
|
|
55
|
-
* separated token is a tag) — prose that merely mentions a code is skipped.
|
|
39
|
+
* The bare tags on the tag lines directly above the `Feature:` line, nearest line first and each
|
|
40
|
+
* line left to right. Blank lines are passed over; the first line that is not a tag line ends it.
|
|
56
41
|
*/
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!line.startsWith("@"))
|
|
63
|
-
continue;
|
|
64
|
-
const tokens = line.split(/\s+/);
|
|
65
|
-
if (!tokens.every((t) => t.startsWith("@")))
|
|
42
|
+
function headerTagsAbove(lines, featureLine) {
|
|
43
|
+
const tags = [];
|
|
44
|
+
for (let j = featureLine - 1; j >= 0; j--) {
|
|
45
|
+
const prev = lines[j].trim();
|
|
46
|
+
if (prev === "")
|
|
66
47
|
continue;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
codes.push(bare);
|
|
71
|
-
}
|
|
48
|
+
if (!prev.startsWith("@"))
|
|
49
|
+
break;
|
|
50
|
+
tags.push(...prev.split(/\s+/).filter((t) => t.startsWith("@")).map((t) => t.slice(1)));
|
|
72
51
|
}
|
|
73
|
-
return
|
|
52
|
+
return tags;
|
|
74
53
|
}
|
|
75
54
|
/**
|
|
76
55
|
* Locate the default features/ directory — `./features` under the current
|
|
@@ -88,10 +67,9 @@ function featuresDir() {
|
|
|
88
67
|
*
|
|
89
68
|
* Absence stays a REFUSAL AT THE READER — what changed is that it is typed, so a caller can decide.
|
|
90
69
|
* `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)
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* into `tags: OK …` at exit 0.
|
|
70
|
+
* at all, and `new-client` is a supported adoption state) by catching `MissingFeatureCorpusError` by
|
|
71
|
+
* name. Returning `[]` here instead would make that decision for every caller, and one that
|
|
72
|
+
* certifies a corpus would certify an absent one as empty.
|
|
95
73
|
*
|
|
96
74
|
* ONLY `ENOENT`. A `features` path that exists but is a FILE, or one the process may not read, is
|
|
97
75
|
* a different fault and is not ours to reinterpret — it rethrows.
|
|
@@ -142,7 +120,6 @@ export function discoverFeatures(dir = featuresDir()) {
|
|
|
142
120
|
slug: name.replace(/\.feature$/, ""),
|
|
143
121
|
path,
|
|
144
122
|
featureCode: extractFeatureCode(source),
|
|
145
|
-
scenarioCodes: parseScenarioCodes(source),
|
|
146
123
|
}));
|
|
147
124
|
}
|
|
148
125
|
//# sourceMappingURL=ingestScenarios.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestScenarios.js","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ingestScenarios.js","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-controller",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.30",
|
|
4
4
|
"description": "Reconcile the behaviour your scenarios name against the evidence that actually ran — the deterministic `balance` CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reconciliation",
|
|
@@ -10,11 +10,6 @@
|
|
|
10
10
|
"double-entry",
|
|
11
11
|
"cli"
|
|
12
12
|
],
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/3f-consulting/spec-controller.git",
|
|
16
|
-
"directory": "packages/spec-controller"
|
|
17
|
-
},
|
|
18
13
|
"license": "MIT",
|
|
19
14
|
"type": "module",
|
|
20
15
|
"engines": {
|
|
@@ -30,7 +25,7 @@
|
|
|
30
25
|
"@cucumber/gherkin": "^41.0.0",
|
|
31
26
|
"@cucumber/messages": "^34.0.1",
|
|
32
27
|
"yaml": "^2.9.0",
|
|
33
|
-
"@3f-consulting/spec-controller-core": "0.1.0-alpha.
|
|
28
|
+
"@3f-consulting/spec-controller-core": "0.1.0-alpha.30"
|
|
34
29
|
},
|
|
35
30
|
"publishConfig": {
|
|
36
31
|
"access": "public"
|
package/dist/corpus/cli.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `tags` command — `spec-controller tags`, the `@SCN` scheme policing itself over a
|
|
3
|
-
* target's Gherkin corpus.
|
|
4
|
-
*
|
|
5
|
-
* THIS EDGE OWNS ONLY THE READS. The rule is `corpusFaults`, in core, reached through the ROOT
|
|
6
|
-
* barrel and nothing else: `./internal` is dropped by `publishConfig.exports` at pack time, and
|
|
7
|
-
* this module travels in the tarball. That is not a style preference — an installed
|
|
8
|
-
* `balance --help` once threw ERR_PACKAGE_PATH_NOT_EXPORTED behind thirteen green gates because a
|
|
9
|
-
* published module reached that subpath (3F-3086). So what happens here is: discover the corpus,
|
|
10
|
-
* read the manifest, build the mutation-record reader, hand over the bytes.
|
|
11
|
-
*
|
|
12
|
-
* ITS OWN COMMAND RATHER THAN A `balance` FLAG. `balance`'s exit code IS its verdict — 0
|
|
13
|
-
* balanced, 1 out of balance, 3 pending-under-strict, 4 unsound, 5 nothing to reconcile — and a
|
|
14
|
-
* corpus FAULT is not a reconciliation verdict. A flag would either overload one of those codes or
|
|
15
|
-
* add a further meaning to a status a consumer's CI branches on. A second command costs one registry entry and gives
|
|
16
|
-
* the fault its own ladder:
|
|
17
|
-
*
|
|
18
|
-
* 0 — the corpus obeys the scheme.
|
|
19
|
-
* 1 — findings; the corpus was read and it breaks a rule.
|
|
20
|
-
* 2 — the corpus (or the manifest) could not be READ at all. Not a verdict, because nothing was
|
|
21
|
-
* concluded. Sibling to @SCN-CLI-014/015/016 on `balance`, and the same argument: an input
|
|
22
|
-
* the tool cannot read is a usage error, never a verdict.
|
|
23
|
-
*
|
|
24
|
-
* Usage:
|
|
25
|
-
* spec-controller tags [--features <dir>] [--package-json <path>]
|
|
26
|
-
*/
|
|
27
|
-
/**
|
|
28
|
-
* Police a target's corpus against the `@SCN` scheme's own rules.
|
|
29
|
-
*
|
|
30
|
-
* `process.exitCode` RATHER THAN `process.exit`, so whatever has been written is flushed intact
|
|
31
|
-
* rather than truncated mid-write — the convention `balance`'s own gate settled on.
|
|
32
|
-
*/
|
|
33
|
-
export declare function runTags(argv: string[]): void;
|
|
34
|
-
//# sourceMappingURL=cli.d.ts.map
|