spec-controller 0.1.0-alpha.1
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/LICENSE +21 -0
- package/dist/cli-balance/cli.d.ts +87 -0
- package/dist/cli-balance/cli.d.ts.map +1 -0
- package/dist/cli-balance/cli.js +486 -0
- package/dist/cli-balance/cli.js.map +1 -0
- package/dist/cli-balance/emit/format.d.ts +60 -0
- package/dist/cli-balance/emit/format.d.ts.map +1 -0
- package/dist/cli-balance/emit/format.js +90 -0
- package/dist/cli-balance/emit/format.js.map +1 -0
- package/dist/cli-balance/emit/writer.d.ts +45 -0
- package/dist/cli-balance/emit/writer.d.ts.map +1 -0
- package/dist/cli-balance/emit/writer.js +48 -0
- package/dist/cli-balance/emit/writer.js.map +1 -0
- package/dist/cli-registry.d.ts +68 -0
- package/dist/cli-registry.d.ts.map +1 -0
- package/dist/cli-registry.js +181 -0
- package/dist/cli-registry.js.map +1 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/deferralTags.d.ts +16 -0
- package/dist/deferralTags.d.ts.map +1 -0
- package/dist/deferralTags.js +22 -0
- package/dist/deferralTags.js.map +1 -0
- package/dist/host.d.ts +50 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +69 -0
- package/dist/host.js.map +1 -0
- package/dist/ingest/ciSourcePaths.d.ts +46 -0
- package/dist/ingest/ciSourcePaths.d.ts.map +1 -0
- package/dist/ingest/ciSourcePaths.js +58 -0
- package/dist/ingest/ciSourcePaths.js.map +1 -0
- package/dist/ingest/gherkinValidation.d.ts +70 -0
- package/dist/ingest/gherkinValidation.d.ts.map +1 -0
- package/dist/ingest/gherkinValidation.js +85 -0
- package/dist/ingest/gherkinValidation.js.map +1 -0
- package/dist/ingest/ingestQualityChecks.d.ts +119 -0
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -0
- package/dist/ingest/ingestQualityChecks.js +331 -0
- package/dist/ingest/ingestQualityChecks.js.map +1 -0
- package/dist/ingest/ingestScenarios.d.ts +52 -0
- package/dist/ingest/ingestScenarios.d.ts.map +1 -0
- package/dist/ingest/ingestScenarios.js +119 -0
- package/dist/ingest/ingestScenarios.js.map +1 -0
- package/dist/mutation-ratchet/index.d.ts +48 -0
- package/dist/mutation-ratchet/index.d.ts.map +1 -0
- package/dist/mutation-ratchet/index.js +48 -0
- package/dist/mutation-ratchet/index.js.map +1 -0
- package/dist/mutation-ratchet/ratchet.d.ts +129 -0
- package/dist/mutation-ratchet/ratchet.d.ts.map +1 -0
- package/dist/mutation-ratchet/ratchet.js +222 -0
- package/dist/mutation-ratchet/ratchet.js.map +1 -0
- package/dist/mutation-ratchet/ratchetCli.d.ts +57 -0
- package/dist/mutation-ratchet/ratchetCli.d.ts.map +1 -0
- package/dist/mutation-ratchet/ratchetCli.js +139 -0
- package/dist/mutation-ratchet/ratchetCli.js.map +1 -0
- package/dist/mutation-ratchet/reconcile.d.ts +82 -0
- package/dist/mutation-ratchet/reconcile.d.ts.map +1 -0
- package/dist/mutation-ratchet/reconcile.js +67 -0
- package/dist/mutation-ratchet/reconcile.js.map +1 -0
- package/dist/mutation-ratchet/record.d.ts +210 -0
- package/dist/mutation-ratchet/record.d.ts.map +1 -0
- package/dist/mutation-ratchet/record.js +330 -0
- package/dist/mutation-ratchet/record.js.map +1 -0
- package/dist/mutation-ratchet/report.d.ts +83 -0
- package/dist/mutation-ratchet/report.d.ts.map +1 -0
- package/dist/mutation-ratchet/report.js +148 -0
- package/dist/mutation-ratchet/report.js.map +1 -0
- package/dist/mutation-ratchet/verdict.d.ts +177 -0
- package/dist/mutation-ratchet/verdict.d.ts.map +1 -0
- package/dist/mutation-ratchet/verdict.js +387 -0
- package/dist/mutation-ratchet/verdict.js.map +1 -0
- package/dist/run-management/keptRun.d.ts +156 -0
- package/dist/run-management/keptRun.d.ts.map +1 -0
- package/dist/run-management/keptRun.js +133 -0
- package/dist/run-management/keptRun.js.map +1 -0
- package/dist/run-management/resolveRunInputs.d.ts +70 -0
- package/dist/run-management/resolveRunInputs.d.ts.map +1 -0
- package/dist/run-management/resolveRunInputs.js +144 -0
- package/dist/run-management/resolveRunInputs.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingest the target's quality-check inputs — the IMPURE reads behind the balance
|
|
3
|
+
* command (split out of the old cli.ts). Reads evidence
|
|
4
|
+
* obligations from the repo's feature files, observed evidence from the Vitest +
|
|
5
|
+
* Cucumber result JSON, and the static-check evidence from the CI config + package.json,
|
|
6
|
+
* turning each into the engine's in-memory model. The orchestration (arg parsing, the
|
|
7
|
+
* output plan/write, main) stays in cli-balance/cli.ts; these are the file reads it calls.
|
|
8
|
+
*
|
|
9
|
+
* Configurable input paths (no adapter registry in v1 — the readers are named, not
|
|
10
|
+
* registered). Missing result files are tolerated: their evidence is simply absent
|
|
11
|
+
* (the scenario reconciles as missing for that level). The static check kind reads
|
|
12
|
+
* `--ci` (a workflow file or a directory of them, default `.github/workflows`) +
|
|
13
|
+
* `package.json` to observe each fitness check the corpus cites.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { ciSourcePathsOf } from "./ciSourcePaths.js";
|
|
18
|
+
import { discoverFeatures } from "./ingestScenarios.js";
|
|
19
|
+
import { parseScenarios, countScnOccurrences, parseUntaggedScenarios, proseOnlyCodes, } from "@3f-consulting/spec-controller-core";
|
|
20
|
+
import { UnreadableFeatureCorpusError } from "./gherkinValidation.js";
|
|
21
|
+
import { DEFAULT_DEFERRAL_TAGS } from "../deferralTags.js";
|
|
22
|
+
import { evidenceObligations, } from "@3f-consulting/spec-controller-core";
|
|
23
|
+
import { parseVitestEvidenceObservations, } from "@3f-consulting/spec-controller-core";
|
|
24
|
+
import { parseCucumberEvidenceObservations, } from "@3f-consulting/spec-controller-core";
|
|
25
|
+
// THE WHOLE COMPOSITION, THROUGH THE PUBLIC BARREL AND NOTHING ELSE (3F-3086). This module travels
|
|
26
|
+
// in the tarball, and until 3F-3089 it reached nine core symbols through the UNPUBLISHED
|
|
27
|
+
// `./internal` subpath to assemble the reconciliation itself — which is why an installed
|
|
28
|
+
// `spec-controller balance --help` threw ERR_PACKAGE_PATH_NOT_EXPORTED while every gate in this
|
|
29
|
+
// workspace stayed green. The nine are still internal (3F-2837 stands, unamended); what moved is
|
|
30
|
+
// their CALLER. What is left here is the reads: this edge opens the files and hands the bytes over.
|
|
31
|
+
import { evidenceReconciliationFromSources } from "@3f-consulting/spec-controller-core";
|
|
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
|
+
/**
|
|
38
|
+
* The per-input FALLBACK quality-check for each JS-ecosystem result source — the
|
|
39
|
+
* source→quality-check mapping, SET at the CLI edge rather than hardcoded in the core
|
|
40
|
+
* parsers / countRuntimeEvidenceObservationKinds. A second ecosystem (pytest / JUnit) sets its own here (or
|
|
41
|
+
* via an adapter); the core never assumes a mapping. No CLI override flag yet — that
|
|
42
|
+
* earns its place at the second ecosystem, not now.
|
|
43
|
+
*/
|
|
44
|
+
const VITEST_QUALITY_CHECK = "unit";
|
|
45
|
+
const CUCUMBER_QUALITY_CHECK = "integration";
|
|
46
|
+
/**
|
|
47
|
+
* The static-check script-prefix convention set — which package.json script names the
|
|
48
|
+
* running-guard observer (`runningStaticChecks`) recognises as static checks, SET at the
|
|
49
|
+
* CLI edge rather than hardcoded in the core parser. `lint:*` is spec-runner's own convention;
|
|
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.
|
|
55
|
+
*
|
|
56
|
+
* A HEURISTIC FOR FINDING CHECKS, NEVER THE DEFINITION OF ONE (@SCN-USG-009, 3F-2495). Membership
|
|
57
|
+
* is a property of the CHECK — does it analyse source without executing it — so a target whose
|
|
58
|
+
* gate this set misses configures it by name via `specController.staticChecks` (the engine's
|
|
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.
|
|
65
|
+
*
|
|
66
|
+
* NOTHING IS THROWN OUT OF HERE, and that is the stance rather than caution. A refusal that escapes
|
|
67
|
+
* costs the whole run its verdict over one bad character in a file nobody may even be reading — a
|
|
68
|
+
* fault that is loud nowhere, where an absent bar is loud on the ledger. It is `thresholdsOf`'s own
|
|
69
|
+
* stance for a malformed entry, applied to the half of a bar that now lives in a second file.
|
|
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.
|
|
75
|
+
*/
|
|
76
|
+
function orNoBar(read) {
|
|
77
|
+
try {
|
|
78
|
+
return read();
|
|
79
|
+
}
|
|
80
|
+
catch (cause) {
|
|
81
|
+
if (cause instanceof MalformedRecordError || isReadRefusal(cause))
|
|
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";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A reader over ONE resolution pass's records.
|
|
92
|
+
*
|
|
93
|
+
* EACH RECORD IS OPENED ONCE, WHICH IS WHY THIS IS A CLOSURE AND NOT A FUNCTION PER REFERENCE. A
|
|
94
|
+
* manifest may name several gates of one record, and a reader opening it per entry would let two
|
|
95
|
+
* references answer for two different states of the same file — a rewrite landing mid-pass, a gate
|
|
96
|
+
* present for one and gone for the next. One read makes the pass's bars one reading of one record.
|
|
97
|
+
*
|
|
98
|
+
* THE REFUSAL IS REMEMBERED TOO. Cacheing only the successes would re-open an absent or unreadable
|
|
99
|
+
* record per reference, which is the same disagreement by the other door — and the likelier door,
|
|
100
|
+
* since a record being rewritten is unreadable for exactly the window that matters.
|
|
101
|
+
*
|
|
102
|
+
* THE SUBTRACTION IS THE RECORD'S, NOT THIS EDGE'S. `killFloorOf` spends the allowance; an edge
|
|
103
|
+
* computing `detected - slack` itself would be a second place holding a fact about the record.
|
|
104
|
+
*/
|
|
105
|
+
export function killFloorReaderFor(root
|
|
106
|
+
// The reader's shape is SPELLED here rather than imported, and that is the boundary working
|
|
107
|
+
// rather than a duplication. `KillFloorReader` and the reference it takes are core-internal —
|
|
108
|
+
// the registry-home question (increment D) still governs them — and this module is published, so
|
|
109
|
+
// naming them by their `./internal` types would put an unpublished specifier back in the tarball
|
|
110
|
+
// for a type alone. Structural typing is what makes the closure the engine's parameter accepts.
|
|
111
|
+
) {
|
|
112
|
+
const opened = new Map();
|
|
113
|
+
return (reference) => {
|
|
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"));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The discovered feature corpus's SOURCES — read once so the two readings a citing run needs (the
|
|
132
|
+
* obligations, and the corpus hop's `check tag → @SCN`) are taken from the same bytes. Two
|
|
133
|
+
* discoveries could disagree about the corpus, and a citation resolved against a corpus the ledger
|
|
134
|
+
* was not built from is exactly the phantom-row fault (@SCN-LNT-009).
|
|
135
|
+
*/
|
|
136
|
+
function featureSources(featuresDir) {
|
|
137
|
+
const features = featuresDir !== undefined ? discoverFeatures(featuresDir) : discoverFeatures();
|
|
138
|
+
return features.map((f) => readFileSync(f.path, "utf8"));
|
|
139
|
+
}
|
|
140
|
+
function scenariosFromFeatures(featuresDir, deferralTags = DEFAULT_DEFERRAL_TAGS) {
|
|
141
|
+
return featureSources(featuresDir).flatMap((source) => parseScenarios(source, deferralTags));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The feature corpus's UNTAGGED half (@SCN-LDG-021, 3F-1774) — the scenarios `parseScenarios`
|
|
145
|
+
* discards because they carry no `@SCN`. They are REAL scenarios: cucumber runs them, so each
|
|
146
|
+
* owns a real Evidence Obligation — but the obligation has no code for an Evidence Observation to
|
|
147
|
+
* link to, so it can never be discharged. `reconcile` gives each its own out-of-balance row
|
|
148
|
+
* (`no-scn-code`); before this read they were invisible to the ledger, and a run whose only fault
|
|
149
|
+
* was an untagged scenario printed "N scenario(s) carry no @SCN tag" and exited 0 BALANCED.
|
|
150
|
+
*
|
|
151
|
+
* Each is handled by its `<feature> : <scenario>` locator — the SAME locator the untagged warning
|
|
152
|
+
* prints — because a bare scenario name is not unique across the corpus, and the row must say
|
|
153
|
+
* where to go.
|
|
154
|
+
*/
|
|
155
|
+
function untaggedScenariosFromFeatures(featuresDir) {
|
|
156
|
+
const features = featuresDir !== undefined ? discoverFeatures(featuresDir) : discoverFeatures();
|
|
157
|
+
return features.flatMap((f) => parseUntaggedScenarios(readFileSync(f.path, "utf8")).map((scenario) => `${f.slug}.feature : ${scenario}`));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The feature corpus's INTEGRITY CHECKPOINT (@SCN-CLI-016, 3F-1767) — validate every discovered
|
|
161
|
+
* `.feature` with the REAL Gherkin parser and report the files it cannot read.
|
|
162
|
+
*
|
|
163
|
+
* The feature corpus is the ledger's DEBIT side, and it used to be read by a tag-line scan that
|
|
164
|
+
* never consulted a parser — so a malformed tag line silently deleted its scenario, and a corrupt
|
|
165
|
+
* spec could read BALANCED. A file the parser rejects is one cucumber would refuse to run: the
|
|
166
|
+
* CLI halts on it (exit 2) rather than reconciling a corpus that is a fiction.
|
|
167
|
+
*
|
|
168
|
+
* THIS IS NOW THE CATCH SITE, NOT THE CALL SITE (@SCN-GPG-001, 3F-2817). The validation moved
|
|
169
|
+
* into `discoverFeatures`, which REFUSES an unreadable corpus rather than returning one, so every
|
|
170
|
+
* corpus reader inherits the refusal instead of each restating it. What is left here is the
|
|
171
|
+
* translation back into the list this CLI edge already prints — same files, same order, same
|
|
172
|
+
* verbatim parser messages, so `balance`'s behaviour is unchanged. Anything that is not a corpus
|
|
173
|
+
* refusal is not ours to interpret and rethrows.
|
|
174
|
+
*/
|
|
175
|
+
export function featureCorpusParseErrors(featuresDir) {
|
|
176
|
+
try {
|
|
177
|
+
if (featuresDir !== undefined)
|
|
178
|
+
discoverFeatures(featuresDir);
|
|
179
|
+
else
|
|
180
|
+
discoverFeatures();
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
if (err instanceof UnreadableFeatureCorpusError)
|
|
185
|
+
return [...err.errors];
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The `@SCN` ids the feature corpus CARRIES MORE THAN ONCE (@SCN-LDG-020's guardrail, 3F-1775).
|
|
191
|
+
*
|
|
192
|
+
* An `@SCN` identifies exactly ONE scenario — that is the whole basis of attributing evidence to
|
|
193
|
+
* it. If two scenarios carry the same id, a test citing it could be proving EITHER, and the tool
|
|
194
|
+
* cannot know which: the corpus is ambiguous, and any verdict over it is a guess.
|
|
195
|
+
*
|
|
196
|
+
* WHY THIS EXISTS. Tag inheritance made this reachable: an `@SCN` hoisted to a `Feature:`/`Rule:`
|
|
197
|
+
* is inherited by every scenario beneath it (cucumber does this, so we do — parity). Left
|
|
198
|
+
* unhandled, ONE passing test then marked EVERY scenario sharing that id `balanced`, including
|
|
199
|
+
* scenarios nothing proves — a BALANCED-WHEN-BROKEN run, exit 0. A new silent false-green,
|
|
200
|
+
* introduced by the very fix that closed the last one.
|
|
201
|
+
*
|
|
202
|
+
* Refused at the edge (exit 2) rather than reconciled, under the Rule already shipped for the
|
|
203
|
+
* observed side and the corrupt corpus (@SCN-CLI-014/015/016): an input the tool cannot read is a
|
|
204
|
+
* usage error, never a verdict. Whether hoisting an `@SCN` is an authoring fault worth a richer
|
|
205
|
+
* diagnostic is the deferred policy question (3F-1774's sibling); refusing to reconcile a corpus we
|
|
206
|
+
* cannot read is not a policy — it is the floor.
|
|
207
|
+
*/
|
|
208
|
+
export function featureCorpusDuplicateScnIds(featuresDir) {
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
const duplicates = new Set();
|
|
211
|
+
for (const scenario of scenariosFromFeatures(featuresDir)) {
|
|
212
|
+
if (seen.has(scenario.scnId))
|
|
213
|
+
duplicates.add(scenario.scnId);
|
|
214
|
+
seen.add(scenario.scnId);
|
|
215
|
+
}
|
|
216
|
+
return [...duplicates].sort();
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Read the target's `feature-code → slug` map over the same `discoverFeatures` corpus
|
|
220
|
+
* (@SCN-RPT-006) — each coded feature file's `@<FFF>` header (`featureCode`)
|
|
221
|
+
* mapped to its filename `slug`, the full feature name. This is the mapping
|
|
222
|
+
* `scenariosFromFeatures` discards (it keeps only the parsed scenarios). A sibling read to
|
|
223
|
+
* the balance, threaded to `renderMarkdown` (via `writeOutputs`) as the 5th sibling arg
|
|
224
|
+
* (markdown-only — the by-feature cut is markdown-only, so it is NOT serialised to JSON).
|
|
225
|
+
* Uncoded files (`featureCode === null`) are omitted, so the by-feature emitter falls
|
|
226
|
+
* back to the bare `FFF` for any id whose code no file registers. One-prefix-one-file holds
|
|
227
|
+
* by invariant (`lintFeatureCodes` flags a duplicate code), so the map is unambiguous.
|
|
228
|
+
*/
|
|
229
|
+
export function readFeatureNames(featuresDir) {
|
|
230
|
+
const features = featuresDir !== undefined ? discoverFeatures(featuresDir) : discoverFeatures();
|
|
231
|
+
const map = new Map();
|
|
232
|
+
for (const f of features) {
|
|
233
|
+
if (f.featureCode !== null)
|
|
234
|
+
map.set(f.featureCode, f.slug);
|
|
235
|
+
}
|
|
236
|
+
return map;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Read the scenario-corpus census over the discovered feature files (the same
|
|
240
|
+
* `discoverFeatures` corpus `scenariosFromFeatures` reads): sum the raw @SCN
|
|
241
|
+
* occurrences (`countScnOccurrences`) and the scenario count
|
|
242
|
+
* (`parseScenarios(...).length`) across every file, and build the `EvidenceObligations`
|
|
243
|
+
* sibling (@SCN-RPT-008). Also collects the UNTAGGED scenarios
|
|
244
|
+
* (@SCN-RPT-014) — `parseUntaggedScenarios` per file, each paired with its feature-file
|
|
245
|
+
* locator, sorted by (feature, scenario). Read alongside the balance in `main()` and
|
|
246
|
+
* threaded to the renderers via `writeOutputs`; `runReconcile` stays unchanged.
|
|
247
|
+
*/
|
|
248
|
+
export function readEvidenceObligations(features) {
|
|
249
|
+
const featureFiles = features !== undefined ? discoverFeatures(features) : discoverFeatures();
|
|
250
|
+
let raw = 0;
|
|
251
|
+
let scenarioCount = 0;
|
|
252
|
+
const untagged = [];
|
|
253
|
+
const sources = [];
|
|
254
|
+
for (const f of featureFiles) {
|
|
255
|
+
const source = readFileSync(f.path, "utf8");
|
|
256
|
+
sources.push(source);
|
|
257
|
+
raw += countScnOccurrences(source);
|
|
258
|
+
scenarioCount += parseScenarios(source).length;
|
|
259
|
+
for (const scenario of parseUntaggedScenarios(source)) {
|
|
260
|
+
untagged.push({ feature: `${f.slug}.feature`, scenario });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
untagged.sort((a, b) => a.feature.localeCompare(b.feature) || a.scenario.localeCompare(b.scenario));
|
|
264
|
+
// The prose-only codes are a CROSS-CORPUS difference (a code tagged in any file isn't
|
|
265
|
+
// dangling), so it reads all sources at once — not per-file like the others.
|
|
266
|
+
return evidenceObligations(raw, scenarioCount, untagged, proseOnlyCodes(sources));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Parse the target's RUNTIME observations ONCE — the tag-derived, per-tier observation set
|
|
270
|
+
* (@SCN-RPT-017 / 3F-1452 FULL UNIFY) fed to BOTH the reconciler and the runtime census, so the
|
|
271
|
+
* two can never disagree. Each source's FALLBACK quality-check is set at the CLI edge
|
|
272
|
+
* (vitest⇒unit, cucumber⇒integration); the parsers DERIVE the level from the cited scenario's tier
|
|
273
|
+
* tag (@SCN-LDG-011 / 3F-1434), using the fallback only for an untagged scenario. A missing report
|
|
274
|
+
* contributes nothing. Root-relative so a phantom-scenario row's where-to-fix stays portable.
|
|
275
|
+
*/
|
|
276
|
+
function parseRuntimeObservations(opts, root) {
|
|
277
|
+
const out = [];
|
|
278
|
+
const vitestReport = readJsonIfPresent(opts.vitest);
|
|
279
|
+
if (vitestReport)
|
|
280
|
+
out.push(...parseVitestEvidenceObservations(vitestReport, VITEST_QUALITY_CHECK, root));
|
|
281
|
+
const cucumberReport = readJsonIfPresent(opts.cucumber);
|
|
282
|
+
if (cucumberReport)
|
|
283
|
+
out.push(...parseCucumberEvidenceObservations(cucumberReport, CUCUMBER_QUALITY_CHECK, root));
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
export function runReconcile(opts) {
|
|
287
|
+
// Read the target's package.json ONCE — its `specController.*` config supplies the deferral-tag
|
|
288
|
+
// extension (@SCN-PND-021), the check registry (@SCN-LNT-005) and the configured check names
|
|
289
|
+
// (@SCN-USG-009), and its `scripts` back the `exists` read. The disk read stays at this impure
|
|
290
|
+
// edge; the engine takes the resolved values as injected params, staying pure.
|
|
291
|
+
const pkgPath = opts.packageJson ?? "package.json";
|
|
292
|
+
const packageJson = readJsonIfPresent(pkgPath) ?? {};
|
|
293
|
+
// 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, and a bar naming a mutation gate
|
|
295
|
+
// finds that target's record rather than this tool's own (@SCN-MUT-012). Resolved ONCE here and
|
|
296
|
+
// threaded, which is also what keeps `execFileSync` and `process.cwd()` out of the engine.
|
|
297
|
+
const root = resolveTargetRoot({ features: opts.features });
|
|
298
|
+
// `--ci` names a workflow FILE or a DIRECTORY of them, and defaults to the directory
|
|
299
|
+
// (@SCN-USG-006, 3F-2238): a pipeline split across files enforces its checks just as one
|
|
300
|
+
// file does, and reading only ci.yml both invents watermelons for named checks enforced
|
|
301
|
+
// elsewhere and drops uncited ones off the books entirely. Path semantics carry the
|
|
302
|
+
// file-vs-directory distinction, so one flag stays one flag and an explicit file is still
|
|
303
|
+
// a narrowing (@SCN-USG-008).
|
|
304
|
+
const ciPath = opts.ci ?? ".github/workflows";
|
|
305
|
+
return evidenceReconciliationFromSources({
|
|
306
|
+
// The corpus's bytes, read ONCE at this impure edge (3F-3088). The engine composes the three
|
|
307
|
+
// readings a citing run needs from this one array, so they cannot be assembled from two
|
|
308
|
+
// discoveries that disagree about the corpus (@SCN-LNT-009).
|
|
309
|
+
featureSources: featureSources(opts.features),
|
|
310
|
+
// The untagged half by its `<feature> : <scenario>` locator — parsed here because the locator
|
|
311
|
+
// names the FILE, which the discovery knows and a source string does not.
|
|
312
|
+
untaggedScenarios: untaggedScenariosFromFeatures(opts.features),
|
|
313
|
+
packageJson,
|
|
314
|
+
ciSources: ciSourcePathsOf(ciPath).map((path) => readFileSync(path, "utf8")),
|
|
315
|
+
measurements: readJsonIfPresent(opts.measurements) ?? {},
|
|
316
|
+
runtimeObservations: parseRuntimeObservations(opts, root),
|
|
317
|
+
// The three things the engine's resolvers cannot hold, and each for its own reason: the starter
|
|
318
|
+
// deferral set and the guard script prefixes are the application layer's own policy, and the
|
|
319
|
+
// reader opens a file on the target's tree.
|
|
320
|
+
deferralTags: DEFAULT_DEFERRAL_TAGS,
|
|
321
|
+
guardScriptPrefixes: GUARD_SCRIPT_PREFIXES,
|
|
322
|
+
killFloor: killFloorReaderFor(root),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
// The static-check kind-counts are no longer read as a parallel census (@SCN-USG-001 / 3F-2118):
|
|
326
|
+
// the former `readStaticEvidenceObservationCounts` re-built the static check observations and
|
|
327
|
+
// re-derived the running / uncited split from a second read of the CI config. That counting now
|
|
328
|
+
// FOLDS onto the Ledger the reconciler already produced (`staticObservationTally`, an internal
|
|
329
|
+
// domain fold the renderers apply), so it cannot drift from the verdict — it counts the SAME posted
|
|
330
|
+
// static-check credits `runReconcile` banked. The census read is deleted, not relocated.
|
|
331
|
+
//# sourceMappingURL=ingestQualityChecks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestQualityChecks.js","sourceRoot":"","sources":["../../src/ingest/ingestQualityChecks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingest the target's feature files — the IMPURE reader that walks features/ on disk
|
|
3
|
+
* and parses each file's feature code + scenario codes into DiscoveredFeature
|
|
4
|
+
* records (split out of the old spec/featureCodes.ts). The pure per-source
|
|
5
|
+
* parsers (extractFeatureCode / parseScenarioCodes) live here with their sole consumer;
|
|
6
|
+
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
|
+
* featureCode.ts); the duplicate-code lint is product-side (static-checks/featureCodes.ts).
|
|
8
|
+
*
|
|
9
|
+
* The foundation, folded into the @LDG runtime spine.
|
|
10
|
+
*/
|
|
11
|
+
/** A feature file discovered on disk, with its `@<FFF>` header code (if any). */
|
|
12
|
+
export interface DiscoveredFeature {
|
|
13
|
+
/** The feature-file basename without the .feature extension. */
|
|
14
|
+
slug: string;
|
|
15
|
+
/** Absolute path to the feature file. */
|
|
16
|
+
path: string;
|
|
17
|
+
featureCode: string | null;
|
|
18
|
+
/** Every @SCN-<FFF>-<NNN> scenario code in the file, in order, duplicates kept. */
|
|
19
|
+
scenarioCodes: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read the Feature-level @<FFF> header tag from a feature file's source — the
|
|
23
|
+
* first tag immediately preceding the `Feature:` keyword that is not an @SCN id
|
|
24
|
+
* or a recognised evidence/routing tag. Returns null if absent.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractFeatureCode(source: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Parse every @SCN-<FFF>-<NNN> scenario code in a feature file's source, in
|
|
29
|
+
* document order, duplicates preserved. Only genuine TAG LINES are scanned (a
|
|
30
|
+
* line whose first non-blank character is `@` and whose every whitespace-
|
|
31
|
+
* separated token is a tag) — prose that merely mentions a code is skipped.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseScenarioCodes(source: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Discover every feature file on disk and its header code — REFUSING a corpus the Gherkin
|
|
36
|
+
* parser rejects (@SCN-GPG-001, 3F-2817).
|
|
37
|
+
*
|
|
38
|
+
* The refusal lives HERE, in the one reader every corpus consumer already goes through, so a
|
|
39
|
+
* consumer INHERITS it rather than restating it. It is not a second parser: it is the existing
|
|
40
|
+
* `parseErrorsIn` — cucumber's own — reached from the shared discovery instead of only from the
|
|
41
|
+
* balance CLI's edge. One answer to "what is a feature file".
|
|
42
|
+
*
|
|
43
|
+
* WHY THE WHOLE CORPUS, NOT THE FIRST BAD FILE. `featureCorpusParseErrors` reports every
|
|
44
|
+
* unreadable file and the balance CLI prints them all (@SCN-CLI-016). Refusing at the first
|
|
45
|
+
* failure would silently narrow that message, so every file is parsed and the refusal is raised
|
|
46
|
+
* once, carrying the lot in discovery order.
|
|
47
|
+
*
|
|
48
|
+
* And NO PARTIAL CORPUS is ever returned: a caller that could scan the readable half of a
|
|
49
|
+
* rejected tree would be back to reporting a verdict over a corpus it cannot read.
|
|
50
|
+
*/
|
|
51
|
+
export declare function discoverFeatures(dir?: string): DiscoveredFeature[];
|
|
52
|
+
//# sourceMappingURL=ingestScenarios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestScenarios.d.ts","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,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;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAsB,GAAG,iBAAiB,EAAE,CAkBjF"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingest the target's feature files — the IMPURE reader that walks features/ on disk
|
|
3
|
+
* and parses each file's feature code + scenario codes into DiscoveredFeature
|
|
4
|
+
* records (split out of the old spec/featureCodes.ts). The pure per-source
|
|
5
|
+
* parsers (extractFeatureCode / parseScenarioCodes) live here with their sole consumer;
|
|
6
|
+
* the `scnId → feature-code` grammar helper is engine-side (packages/core/src/lib/
|
|
7
|
+
* featureCode.ts); the duplicate-code lint is product-side (static-checks/featureCodes.ts).
|
|
8
|
+
*
|
|
9
|
+
* The foundation, folded into the @LDG runtime spine.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { UnreadableFeatureCorpusError, parseErrorsIn } from "./gherkinValidation.js";
|
|
14
|
+
/** A code is valid when it is 2–5 uppercase alphanumerics. */
|
|
15
|
+
const CODE_PATTERN = /^[A-Z0-9]{2,5}$/;
|
|
16
|
+
/**
|
|
17
|
+
* Read the Feature-level @<FFF> header tag from a feature file's source — the
|
|
18
|
+
* first tag immediately preceding the `Feature:` keyword that is not an @SCN id
|
|
19
|
+
* or a recognised evidence/routing tag. Returns null if absent.
|
|
20
|
+
*/
|
|
21
|
+
export function extractFeatureCode(source) {
|
|
22
|
+
const lines = source.split(/\r?\n/);
|
|
23
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24
|
+
const line = lines[i].trim();
|
|
25
|
+
if (line === "" || line.startsWith("#"))
|
|
26
|
+
continue;
|
|
27
|
+
if (line.startsWith("Feature:")) {
|
|
28
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
29
|
+
const prev = lines[j].trim();
|
|
30
|
+
if (prev === "")
|
|
31
|
+
continue;
|
|
32
|
+
if (!prev.startsWith("@"))
|
|
33
|
+
break;
|
|
34
|
+
const tags = prev.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
35
|
+
for (const tag of tags) {
|
|
36
|
+
const bare = tag.slice(1);
|
|
37
|
+
if (bare.startsWith("SCN-"))
|
|
38
|
+
continue;
|
|
39
|
+
if (["unit", "integration", "e2e", "performance", "smoke"].includes(bare))
|
|
40
|
+
continue;
|
|
41
|
+
if (CODE_PATTERN.test(bare))
|
|
42
|
+
return bare;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse every @SCN-<FFF>-<NNN> scenario code in a feature file's source, in
|
|
52
|
+
* document order, duplicates preserved. Only genuine TAG LINES are scanned (a
|
|
53
|
+
* line whose first non-blank character is `@` and whose every whitespace-
|
|
54
|
+
* separated token is a tag) — prose that merely mentions a code is skipped.
|
|
55
|
+
*/
|
|
56
|
+
export function parseScenarioCodes(source) {
|
|
57
|
+
const codes = [];
|
|
58
|
+
const codePattern = /^SCN-[A-Z0-9]+-\d+$/;
|
|
59
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
60
|
+
const line = rawLine.trim();
|
|
61
|
+
if (!line.startsWith("@"))
|
|
62
|
+
continue;
|
|
63
|
+
const tokens = line.split(/\s+/);
|
|
64
|
+
if (!tokens.every((t) => t.startsWith("@")))
|
|
65
|
+
continue;
|
|
66
|
+
for (const token of tokens) {
|
|
67
|
+
const bare = token.slice(1);
|
|
68
|
+
if (codePattern.test(bare))
|
|
69
|
+
codes.push(bare);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return codes;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Locate the default features/ directory — `./features` under the current
|
|
76
|
+
* working directory. This is the target repo's own features dir: the directory
|
|
77
|
+
* a user runs `spec-controller balance` from, and the workspace root when the
|
|
78
|
+
* feature-code lint runs in CI. Callers that reconcile a different tree pass an
|
|
79
|
+
* explicit `--features <dir>`, which overrides this default.
|
|
80
|
+
*/
|
|
81
|
+
function featuresDir() {
|
|
82
|
+
return join(process.cwd(), "features");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Discover every feature file on disk and its header code — REFUSING a corpus the Gherkin
|
|
86
|
+
* parser rejects (@SCN-GPG-001, 3F-2817).
|
|
87
|
+
*
|
|
88
|
+
* The refusal lives HERE, in the one reader every corpus consumer already goes through, so a
|
|
89
|
+
* consumer INHERITS it rather than restating it. It is not a second parser: it is the existing
|
|
90
|
+
* `parseErrorsIn` — cucumber's own — reached from the shared discovery instead of only from the
|
|
91
|
+
* balance CLI's edge. One answer to "what is a feature file".
|
|
92
|
+
*
|
|
93
|
+
* WHY THE WHOLE CORPUS, NOT THE FIRST BAD FILE. `featureCorpusParseErrors` reports every
|
|
94
|
+
* unreadable file and the balance CLI prints them all (@SCN-CLI-016). Refusing at the first
|
|
95
|
+
* failure would silently narrow that message, so every file is parsed and the refusal is raised
|
|
96
|
+
* once, carrying the lot in discovery order.
|
|
97
|
+
*
|
|
98
|
+
* And NO PARTIAL CORPUS is ever returned: a caller that could scan the readable half of a
|
|
99
|
+
* rejected tree would be back to reporting a verdict over a corpus it cannot read.
|
|
100
|
+
*/
|
|
101
|
+
export function discoverFeatures(dir = featuresDir()) {
|
|
102
|
+
const files = readdirSync(dir)
|
|
103
|
+
.filter((name) => name.endsWith(".feature"))
|
|
104
|
+
.sort()
|
|
105
|
+
.map((name) => {
|
|
106
|
+
const path = join(dir, name);
|
|
107
|
+
return { name, path, source: readFileSync(path, "utf8") };
|
|
108
|
+
});
|
|
109
|
+
const parseErrors = files.flatMap(({ path, source }) => parseErrorsIn(path, source));
|
|
110
|
+
if (parseErrors.length > 0)
|
|
111
|
+
throw new UnreadableFeatureCorpusError(parseErrors);
|
|
112
|
+
return files.map(({ name, path, source }) => ({
|
|
113
|
+
slug: name.replace(/\.feature$/, ""),
|
|
114
|
+
path,
|
|
115
|
+
featureCode: extractFeatureCode(source),
|
|
116
|
+
scenarioCodes: parseScenarioCodes(source),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=ingestScenarios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestScenarios.js","sourceRoot":"","sources":["../../src/ingest/ingestScenarios.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEpD,OAAO,EAAW,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,4BAA4B,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAErF,8DAA8D;AAC9D,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAavC;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,IAAI,KAAK,EAAE;oBAAE,SAAS;gBAC1B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,MAAM;gBACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;gBAChE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC1B,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;wBAAE,SAAS;oBACtC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAAE,SAAS;oBACpF,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;wBAAE,OAAO,IAAI,CAAC;gBAC3C,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,WAAW,GAAG,qBAAqB,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAE,SAAS;QACtD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,WAAW,EAAE;IAC1D,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC;SAC3B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;SAC3C,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEL,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACrF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,4BAA4B,CAAC,WAAW,CAAC,CAAC;IAEhF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAC5C,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;QACpC,IAAI;QACJ,WAAW,EAAE,kBAAkB,CAAC,MAAM,CAAC;QACvC,aAAa,EAAE,kBAAkB,CAAC,MAAM,CAAC;KAC1C,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MODULE'S OWN SURFACE — curated symbol by symbol, never a re-export of the tree behind it
|
|
3
|
+
* (@SCN-RAT-014, 3F-2804).
|
|
4
|
+
*
|
|
5
|
+
* A BARREL THAT RE-EXPORTED EVERY MODULE WOULD BE A CONTRACT NOBODY WROTE. Each module below
|
|
6
|
+
* exports what its own siblings need from it, and sibling-visibility is a much wider set than
|
|
7
|
+
* consumer-visibility: the gate-block span finder, the field rewriter, the per-verdict message
|
|
8
|
+
* builders, the status sets and the loader's per-field readers are all exported so their
|
|
9
|
+
* neighbours can reach them without a second copy. Passing that set on wholesale would freeze
|
|
10
|
+
* every one of them as something a consumer may depend on, and the first refactor that moved one
|
|
11
|
+
* would be a breaking change nobody meant to make. So this file names what a caller may hold and
|
|
12
|
+
* the omissions are decisions.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS HERE IS THIS LIBRARY'S OWN ENTRY, AND WHAT ITS FIRST CONSUMER NEEDS. The entry is
|
|
15
|
+
* `reconcileGate` — hand it the five terms a project owns and it answers with a verdict. Around it
|
|
16
|
+
* sit the pieces a consumer that wants to compose its own way needs: the verdict over counts
|
|
17
|
+
* already in hand, the two readers that turn a report into counts, the recorder, the loader, the
|
|
18
|
+
* three score functions a consumer rendering its own view needs, the kill floor those same figures
|
|
19
|
+
* imply in mutants, the default record name, and both error types the functions above raise — the
|
|
20
|
+
* record fault and the up-only refusal — because a refusal a caller cannot name in a `catch` is a
|
|
21
|
+
* refusal that caller cannot handle.
|
|
22
|
+
*
|
|
23
|
+
* THE KILL FLOOR CROSSES FOR A SECOND KIND OF CONSUMER, AND THAT IS WHY IT IS NOT A SCORE (3F-2788).
|
|
24
|
+
* Everything above answers a reader that wants this library's verdict. `killFloorOf` answers one
|
|
25
|
+
* that wants only the BAR — a ledger holding its own measured row to the same figure this library
|
|
26
|
+
* reconciles against, so the two cannot disagree by construction. A consumer that derived it
|
|
27
|
+
* instead would be the second place knowing how an allowance is spent.
|
|
28
|
+
*
|
|
29
|
+
* THE TYPES ARE SPELLED FOR A CONSUMER'S NAMESPACE RATHER THAN FOR THIS MODULE'S. `GateRecord` and
|
|
30
|
+
* `GateAllowance` are unambiguous inside a directory about nothing else; imported into a repository
|
|
31
|
+
* that has gates of several kinds they are not, so they cross this line under the fuller names.
|
|
32
|
+
* The aliasing is deliberate and one-directional: the internal spellings stay internal.
|
|
33
|
+
*
|
|
34
|
+
* AND NO DRIFT GUARD STANDS OVER IT, WHICH IS A DECISION NOW RATHER THAN AN OPEN QUESTION. The
|
|
35
|
+
* sibling core package's barrel is reflected and compared against a frozen list in both directions,
|
|
36
|
+
* so a symbol quietly added or dropped reds. That guard is what a PUBLISHED surface is owed, and
|
|
37
|
+
* 3F-2859 stopped publishing this one: no manifest key resolves here, and no caller outside this
|
|
38
|
+
* repository can spell the specifier at all. Freezing the list would make every refactor behind
|
|
39
|
+
* this barrel a change to a contract nobody holds. What the curation above is still for is the
|
|
40
|
+
* reader of this repository's own gate, and @SCN-RAT-014 reads the list back beside it — so a
|
|
41
|
+
* symbol added here is a symbol somebody wrote down twice, which is the guard this surface earns.
|
|
42
|
+
*/
|
|
43
|
+
export { RECORD_FILE, reconcileGate, type RatchetRequest } from "./reconcile.js";
|
|
44
|
+
export { floorOf, gateRecordFor, killFloorOf, MalformedRecordError, parseGateRecords, recordedScoreOf, scoreOf, type GateAllowance as MutationGateAllowance, type GateRecord as MutationGateRecord, } from "./record.js";
|
|
45
|
+
export { FallRefusedError, raisedRecordText } from "./ratchet.js";
|
|
46
|
+
export { measurementFromReport, readMutationReport, type MutationReportRead, } from "./report.js";
|
|
47
|
+
export { verdictFor, type MutationMeasurement, type MutationVerdict, type MutationVerdictKind, } from "./verdict.js";
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mutation-ratchet/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EACL,OAAO,EACP,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,OAAO,EACP,KAAK,aAAa,IAAI,qBAAqB,EAC3C,KAAK,UAAU,IAAI,kBAAkB,GACtC,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MODULE'S OWN SURFACE — curated symbol by symbol, never a re-export of the tree behind it
|
|
3
|
+
* (@SCN-RAT-014, 3F-2804).
|
|
4
|
+
*
|
|
5
|
+
* A BARREL THAT RE-EXPORTED EVERY MODULE WOULD BE A CONTRACT NOBODY WROTE. Each module below
|
|
6
|
+
* exports what its own siblings need from it, and sibling-visibility is a much wider set than
|
|
7
|
+
* consumer-visibility: the gate-block span finder, the field rewriter, the per-verdict message
|
|
8
|
+
* builders, the status sets and the loader's per-field readers are all exported so their
|
|
9
|
+
* neighbours can reach them without a second copy. Passing that set on wholesale would freeze
|
|
10
|
+
* every one of them as something a consumer may depend on, and the first refactor that moved one
|
|
11
|
+
* would be a breaking change nobody meant to make. So this file names what a caller may hold and
|
|
12
|
+
* the omissions are decisions.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS HERE IS THIS LIBRARY'S OWN ENTRY, AND WHAT ITS FIRST CONSUMER NEEDS. The entry is
|
|
15
|
+
* `reconcileGate` — hand it the five terms a project owns and it answers with a verdict. Around it
|
|
16
|
+
* sit the pieces a consumer that wants to compose its own way needs: the verdict over counts
|
|
17
|
+
* already in hand, the two readers that turn a report into counts, the recorder, the loader, the
|
|
18
|
+
* three score functions a consumer rendering its own view needs, the kill floor those same figures
|
|
19
|
+
* imply in mutants, the default record name, and both error types the functions above raise — the
|
|
20
|
+
* record fault and the up-only refusal — because a refusal a caller cannot name in a `catch` is a
|
|
21
|
+
* refusal that caller cannot handle.
|
|
22
|
+
*
|
|
23
|
+
* THE KILL FLOOR CROSSES FOR A SECOND KIND OF CONSUMER, AND THAT IS WHY IT IS NOT A SCORE (3F-2788).
|
|
24
|
+
* Everything above answers a reader that wants this library's verdict. `killFloorOf` answers one
|
|
25
|
+
* that wants only the BAR — a ledger holding its own measured row to the same figure this library
|
|
26
|
+
* reconciles against, so the two cannot disagree by construction. A consumer that derived it
|
|
27
|
+
* instead would be the second place knowing how an allowance is spent.
|
|
28
|
+
*
|
|
29
|
+
* THE TYPES ARE SPELLED FOR A CONSUMER'S NAMESPACE RATHER THAN FOR THIS MODULE'S. `GateRecord` and
|
|
30
|
+
* `GateAllowance` are unambiguous inside a directory about nothing else; imported into a repository
|
|
31
|
+
* that has gates of several kinds they are not, so they cross this line under the fuller names.
|
|
32
|
+
* The aliasing is deliberate and one-directional: the internal spellings stay internal.
|
|
33
|
+
*
|
|
34
|
+
* AND NO DRIFT GUARD STANDS OVER IT, WHICH IS A DECISION NOW RATHER THAN AN OPEN QUESTION. The
|
|
35
|
+
* sibling core package's barrel is reflected and compared against a frozen list in both directions,
|
|
36
|
+
* so a symbol quietly added or dropped reds. That guard is what a PUBLISHED surface is owed, and
|
|
37
|
+
* 3F-2859 stopped publishing this one: no manifest key resolves here, and no caller outside this
|
|
38
|
+
* repository can spell the specifier at all. Freezing the list would make every refactor behind
|
|
39
|
+
* this barrel a change to a contract nobody holds. What the curation above is still for is the
|
|
40
|
+
* reader of this repository's own gate, and @SCN-RAT-014 reads the list back beside it — so a
|
|
41
|
+
* symbol added here is a symbol somebody wrote down twice, which is the guard this surface earns.
|
|
42
|
+
*/
|
|
43
|
+
export { RECORD_FILE, reconcileGate } from "./reconcile.js";
|
|
44
|
+
export { floorOf, gateRecordFor, killFloorOf, MalformedRecordError, parseGateRecords, recordedScoreOf, scoreOf, } from "./record.js";
|
|
45
|
+
export { FallRefusedError, raisedRecordText } from "./ratchet.js";
|
|
46
|
+
export { measurementFromReport, readMutationReport, } from "./report.js";
|
|
47
|
+
export { verdictFor, } from "./verdict.js";
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mutation-ratchet/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAuB,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EACL,OAAO,EACP,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,OAAO,GAGR,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EACL,qBAAqB,EACrB,kBAAkB,GAEnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,GAIX,MAAM,cAAc,CAAC"}
|