spec-controller 0.1.0-alpha.45 → 0.1.0-alpha.47
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/dist/cli-allocate/cli.d.ts.map +1 -1
- package/dist/cli-allocate/cli.js +0 -5
- package/dist/cli-allocate/cli.js.map +1 -1
- package/dist/cli-allocate/readHistory.d.ts +0 -13
- package/dist/cli-allocate/readHistory.d.ts.map +1 -1
- package/dist/cli-allocate/readHistory.js +0 -33
- package/dist/cli-allocate/readHistory.js.map +1 -1
- package/dist/cli-allocate/readParts.d.ts +0 -1
- package/dist/cli-allocate/readParts.d.ts.map +1 -1
- package/dist/cli-allocate/readParts.js +0 -6
- package/dist/cli-allocate/readParts.js.map +1 -1
- package/dist/cli-allocate/readTree.d.ts +0 -11
- package/dist/cli-allocate/readTree.d.ts.map +1 -1
- package/dist/cli-allocate/readTree.js +0 -32
- package/dist/cli-allocate/readTree.js.map +1 -1
- package/dist/cli-allocate/refStore.d.ts +0 -1
- package/dist/cli-allocate/refStore.d.ts.map +1 -1
- package/dist/cli-allocate/refStore.js +0 -13
- package/dist/cli-allocate/refStore.js.map +1 -1
- package/dist/cli-allocate/request.d.ts +0 -2
- package/dist/cli-allocate/request.d.ts.map +1 -1
- package/dist/cli-allocate/request.js +0 -7
- package/dist/cli-allocate/request.js.map +1 -1
- package/dist/cli-allocate/stderr.d.ts +0 -7
- package/dist/cli-allocate/stderr.d.ts.map +1 -1
- package/dist/cli-allocate/stderr.js +0 -7
- package/dist/cli-allocate/stderr.js.map +1 -1
- package/dist/cli-allocate/unreadable.d.ts +0 -13
- package/dist/cli-allocate/unreadable.d.ts.map +1 -1
- package/dist/cli-allocate/unreadable.js +0 -13
- package/dist/cli-allocate/unreadable.js.map +1 -1
- package/dist/cli-args.d.ts +0 -113
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +0 -145
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-balance/cli.d.ts +0 -85
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +0 -230
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-balance/emit/format.d.ts +0 -52
- package/dist/cli-balance/emit/format.d.ts.map +1 -1
- package/dist/cli-balance/emit/format.js +0 -70
- package/dist/cli-balance/emit/format.js.map +1 -1
- package/dist/cli-balance/emit/writer.d.ts +0 -17
- package/dist/cli-balance/emit/writer.d.ts.map +1 -1
- package/dist/cli-balance/emit/writer.js +0 -13
- package/dist/cli-balance/emit/writer.js.map +1 -1
- package/dist/cli-balance/exitStatus.d.ts +0 -1
- package/dist/cli-balance/exitStatus.d.ts.map +1 -1
- package/dist/cli-balance/exitStatus.js +0 -7
- package/dist/cli-balance/exitStatus.js.map +1 -1
- package/dist/cli-balance/inputsRead.d.ts +0 -12
- package/dist/cli-balance/inputsRead.d.ts.map +1 -1
- package/dist/cli-balance/inputsRead.js +0 -12
- package/dist/cli-balance/inputsRead.js.map +1 -1
- package/dist/cli-balance/reRender.d.ts +0 -14
- package/dist/cli-balance/reRender.d.ts.map +1 -1
- package/dist/cli-balance/reRender.js +0 -15
- package/dist/cli-balance/reRender.js.map +1 -1
- package/dist/cli-balance/unreadableInputs.d.ts +0 -21
- package/dist/cli-balance/unreadableInputs.d.ts.map +1 -1
- package/dist/cli-balance/unreadableInputs.js +0 -21
- package/dist/cli-balance/unreadableInputs.js.map +1 -1
- package/dist/cli-registry.d.ts +0 -86
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +0 -70
- package/dist/cli-registry.js.map +1 -1
- package/dist/cli.d.ts +0 -20
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +0 -21
- package/dist/cli.js.map +1 -1
- package/dist/deferralTags.d.ts +0 -14
- package/dist/deferralTags.d.ts.map +1 -1
- package/dist/deferralTags.js +0 -14
- package/dist/deferralTags.js.map +1 -1
- package/dist/host.d.ts +0 -61
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +1 -84
- package/dist/host.js.map +1 -1
- package/dist/ingest/ciSourcePaths.d.ts +0 -38
- package/dist/ingest/ciSourcePaths.d.ts.map +1 -1
- package/dist/ingest/ciSourcePaths.js +0 -38
- package/dist/ingest/ciSourcePaths.js.map +1 -1
- package/dist/ingest/gherkinValidation.d.ts +0 -76
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +0 -76
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +0 -81
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +0 -194
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/dist/ingest/ingestScenarios.d.ts +0 -39
- package/dist/ingest/ingestScenarios.d.ts.map +1 -1
- package/dist/ingest/ingestScenarios.js +0 -70
- package/dist/ingest/ingestScenarios.js.map +1 -1
- package/dist/ingest/inputShapes.d.ts +0 -31
- package/dist/ingest/inputShapes.d.ts.map +1 -1
- package/dist/ingest/inputShapes.js +0 -30
- package/dist/ingest/inputShapes.js.map +1 -1
- package/dist/outputLocation.d.ts +0 -48
- package/dist/outputLocation.d.ts.map +1 -1
- package/dist/outputLocation.js +0 -48
- package/dist/outputLocation.js.map +1 -1
- package/dist/run-management/keptRun.d.ts +0 -107
- package/dist/run-management/keptRun.d.ts.map +1 -1
- package/dist/run-management/keptRun.js +0 -75
- package/dist/run-management/keptRun.js.map +1 -1
- package/dist/run-management/resolveRunInputs.d.ts +0 -67
- package/dist/run-management/resolveRunInputs.d.ts.map +1 -1
- package/dist/run-management/resolveRunInputs.js +0 -88
- package/dist/run-management/resolveRunInputs.js.map +1 -1
- package/dist/storedRun.d.ts +0 -21
- package/dist/storedRun.d.ts.map +1 -1
- package/dist/storedRun.js +0 -21
- package/dist/storedRun.js.map +1 -1
- package/package.json +2 -2
package/dist/cli-balance/cli.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The balance CLI — thin wiring over the reconciler seams. The orchestration half of
|
|
3
|
-
* the old cli.ts: arg parsing, the render-from-JSON mode, and the reconcile →
|
|
4
|
-
* census → provenance → plan → write sequence. The impure file reads it calls
|
|
5
|
-
* (runReconcile + the read* siblings) live in ingest/ingestQualityChecks.ts; the output
|
|
6
|
-
* plan/write in ./emit; the git-derived run inputs in run-management/resolveRunInputs.ts.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* pnpm reconcile [--features <dir>] [--vitest <report.json>]
|
|
10
|
-
* [--cucumber <report.json>] [--ci-steps <record.json>]
|
|
11
|
-
* [--ci <ci.yml>]
|
|
12
|
-
* [--format <fmt>[:<path>]]
|
|
13
|
-
*/
|
|
14
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
2
|
import { dirname } from "node:path";
|
|
16
3
|
import { runReconcile, readEvidenceObligations, readFeatureNames, featureCorpusParseErrors, } from "../ingest/ingestQualityChecks.js";
|
|
@@ -24,16 +11,7 @@ import { statusFor } from "./exitStatus.js";
|
|
|
24
11
|
import { verdictCode, } from "@3f-consulting/spec-controller-core";
|
|
25
12
|
import { resolveSourceSha, resolveTargetRoot, resolveToolIdentity } from "../run-management/resolveRunInputs.js";
|
|
26
13
|
import { cliRegistry, renderCommandHelp } from "../cli-registry.js";
|
|
27
|
-
// THE ARGV READING AND ITS REGISTRY BOUNDING, SHARED WITH EVERY OTHER COMMAND. Both
|
|
28
|
-
// lived here while `balance` was the only command; a second command needs them, and a second
|
|
29
|
-
// copy of "what did the operator type" is the one thing this tree least wants two of.
|
|
30
14
|
import { asksForHelp, parseArgs, checkArgvShape, checkUnknownFlags, checkMissingValues, checkMisplacedHostModifiers, } from "../cli-args.js";
|
|
31
|
-
/**
|
|
32
|
-
* Collect the `--format <spec>` values from argv in order (the flag is REPEATABLE, unlike
|
|
33
|
-
* the single-valued flags `parseArgs` records) — the raw specs `planOutput` parses. Empty
|
|
34
|
-
* when no `--format` was given, which `planOutput` reads as the md→stdout default.
|
|
35
|
-
* Several specs route in one invocation.
|
|
36
|
-
*/
|
|
37
15
|
function collectFormatSpecs(argv) {
|
|
38
16
|
const specs = [];
|
|
39
17
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -47,15 +25,6 @@ function collectFormatSpecs(argv) {
|
|
|
47
25
|
}
|
|
48
26
|
return specs;
|
|
49
27
|
}
|
|
50
|
-
/**
|
|
51
|
-
* The legacy-`--json` guard at the CLI edge. The earlier
|
|
52
|
-
* `--json <path>` flag is removed; supplying it must fail fast with a migration hint to
|
|
53
|
-
* `--format json:<path>`, so the removed flag never silently no-ops and drops output. PURE
|
|
54
|
-
* and `@unit`-testable: the removed flag is NOT a `--format` spec (planOutput never sees
|
|
55
|
-
* it), so the guard reads the parsed args directly and returns the typed migration-hint
|
|
56
|
-
* error message when `--json` is present — routed to stderr + a non-zero exit by main(),
|
|
57
|
-
* the same handling planOutput's typed error gets (the shared error surface) — else null.
|
|
58
|
-
*/
|
|
59
28
|
export function checkLegacyJson(args) {
|
|
60
29
|
if (args["json"] !== undefined) {
|
|
61
30
|
return ("The --json <path> flag has been removed. " +
|
|
@@ -64,27 +33,9 @@ export function checkLegacyJson(args) {
|
|
|
64
33
|
}
|
|
65
34
|
return null;
|
|
66
35
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Source the provenance "Target repo:" name for a run. The
|
|
69
|
-
* operator's `--target` is recorded VERBATIM — no resolution — defaulting to
|
|
70
|
-
* `"unknown-target"` when omitted (parallel to `sourceSha`'s `"unknown"`; the facts
|
|
71
|
-
* artefact is self-describing, so the line always renders). PURE and `@unit`-testable at
|
|
72
|
-
* the CLI edge, like `checkLegacyJson`. It never inspects `--features`: the prior
|
|
73
|
-
* `basename(dirname(--features))` resolution (the deleted `resolveTarget`) and its
|
|
74
|
-
* degenerate `.` are impossible now — the target is only ever the given value or the
|
|
75
|
-
* explicit default.
|
|
76
|
-
*/
|
|
77
36
|
export function sourceTarget(args) {
|
|
78
37
|
return args["target"] ?? "unknown-target";
|
|
79
38
|
}
|
|
80
|
-
/**
|
|
81
|
-
* The parsed args, as the reconciler's input paths — the one place a `--flag` name becomes a
|
|
82
|
-
* `ReconcileOptions` key.
|
|
83
|
-
*
|
|
84
|
-
* Its own function rather than an object literal inside `runBalance`, which sits at the
|
|
85
|
-
* `max-lines-per-function` standard: a flag is added here, not by growing the orchestration. The
|
|
86
|
-
* mapping is also the only thing in that function with nothing to do with orchestration.
|
|
87
|
-
*/
|
|
88
39
|
function reconcileInputsOf(args) {
|
|
89
40
|
return {
|
|
90
41
|
features: args["features"],
|
|
@@ -95,27 +46,7 @@ function reconcileInputsOf(args) {
|
|
|
95
46
|
ciSteps: args["ci-steps"],
|
|
96
47
|
};
|
|
97
48
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Guard the supplied input-file/dir flags at the CLI edge. A
|
|
100
|
-
* mistyped `--vitest` / `--cucumber` / `--features` / `--ci` / `--package-json`
|
|
101
|
-
* pointing at a non-existent path used to reconcile silently against ZERO evidence —
|
|
102
|
-
* a terrifying false all-red report meaning "you forgot the reports", not "your code
|
|
103
|
-
* is broken" (the dogfooding fault). These flags are Examples of ONE boundary
|
|
104
|
-
* rule: a supplied-but-absent input path is a hard error, and a flag added to the set
|
|
105
|
-
* inherits it rather than restating it. Scope is existence only
|
|
106
|
-
* (readability / file-vs-dir type-correctness out of scope; `exists` covers the dir
|
|
107
|
-
* flag and the file flags alike).
|
|
108
|
-
*
|
|
109
|
-
* PURE and `@unit`-testable like `checkLegacyJson` / `sourceTarget`: reads the parsed
|
|
110
|
-
* args + an injected `exists` fn (no fs), returns the FIRST supplied-but-absent
|
|
111
|
-
* `(flag, path)` as a typed error naming both — routed to stderr + a non-zero exit by
|
|
112
|
-
* `refuseUnreadableInputs` (the shared typed-error surface) — else null. An OMITTED flag is not an
|
|
113
|
-
* error (legitimately optional: simply no evidence of that kind).
|
|
114
|
-
*/
|
|
115
49
|
export function checkInputsExist(args, exists) {
|
|
116
|
-
// The input flags in a fixed order — the FIRST supplied-but-absent one is the
|
|
117
|
-
// reported error (deterministic; the operator fixes and re-runs). Keys are the
|
|
118
|
-
// parseArgs form (the `--` stripped); the message names the flag WITH its dashes.
|
|
119
50
|
const flags = [
|
|
120
51
|
"vitest", "cucumber", "features", "ci", "package-json", "ci-steps",
|
|
121
52
|
];
|
|
@@ -127,29 +58,9 @@ export function checkInputsExist(args, exists) {
|
|
|
127
58
|
}
|
|
128
59
|
return null;
|
|
129
60
|
}
|
|
130
|
-
/**
|
|
131
|
-
* The JSON-BEARING input flags — the ones whose content is `JSON.parse`d. `--features` and `--ci`
|
|
132
|
-
* are read as Gherkin and YAML files on disk, and whether they can be read is `checkInputPaths`'s.
|
|
133
|
-
*/
|
|
134
61
|
const JSON_INPUT_FLAGS = [
|
|
135
62
|
"vitest", "cucumber", "package-json", "ci-steps",
|
|
136
63
|
];
|
|
137
|
-
/**
|
|
138
|
-
* Guard the READABILITY of the supplied JSON inputs at the CLI edge.
|
|
139
|
-
* `checkInputsExist` already guards their PRESENCE — but a path that is present yet
|
|
140
|
-
* UNPARSEABLE fell straight through it: the reader threw mid-parse, nothing caught it, and
|
|
141
|
-
* the process hit Node's default exit 1 — the code reserved for a genuine OUT-OF-BALANCE
|
|
142
|
-
* verdict. A crashed invocation was therefore indistinguishable, by exit code, from an
|
|
143
|
-
* honest disagreement. An input the tool cannot read is a USAGE fault: exit 2,
|
|
144
|
-
* sibling to a missing path and an unrecognised flag.
|
|
145
|
-
*
|
|
146
|
-
* PURE and `@unit`-testable like its sibling guards: reads the parsed args + an injected
|
|
147
|
-
* `read` fn (no fs), returns the FIRST supplied-but-unparseable `(flag, path, failure)` as
|
|
148
|
-
* a typed error naming all three — routed to stderr + exit 2 by `refuseUnreadableInputs` — else null.
|
|
149
|
-
* An omitted flag is not an error, and a file that cannot be read at all (a race after the
|
|
150
|
-
* existence check, a permission refused) is refused as one that could not be read, never as JSON
|
|
151
|
-
* that did not parse.
|
|
152
|
-
*/
|
|
153
64
|
export function checkInputsParse(args, read) {
|
|
154
65
|
for (const flag of JSON_INPUT_FLAGS) {
|
|
155
66
|
const path = args[flag];
|
|
@@ -166,7 +77,6 @@ export function checkInputsParse(args, read) {
|
|
|
166
77
|
JSON.parse(text);
|
|
167
78
|
}
|
|
168
79
|
catch (err) {
|
|
169
|
-
// Anything that escapes this catch lands on Node's default exit 1 — the collision itself.
|
|
170
80
|
return `The --${flag} report is not valid JSON: ${path} (${failureOf(err)})`;
|
|
171
81
|
}
|
|
172
82
|
}
|
|
@@ -175,22 +85,6 @@ export function checkInputsParse(args, read) {
|
|
|
175
85
|
function failureOf(err) {
|
|
176
86
|
return err instanceof Error ? err.message : String(err);
|
|
177
87
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Guard the readability of `--render-from-json` at the CLI edge. The
|
|
180
|
-
* render-from-saved-JSON branch RETURNS before the input guards ever run, so it honoured
|
|
181
|
-
* NEITHER: a missing path crashed to `ENOENT` and a malformed file to `SyntaxError`, both
|
|
182
|
-
* landing on Node's default exit 1 — the out-of-balance code.
|
|
183
|
-
*
|
|
184
|
-
* The missing-path case was a live violation of the missing-input rule's own shipped principle: a
|
|
185
|
-
* supplied input path that does not exist is a hard error at exit 2, enforced for the five
|
|
186
|
-
* evidence flags and not for this one. The CLI shipped a guard that half-honoured its Rule.
|
|
187
|
-
*
|
|
188
|
-
* MISSING and UNPARSEABLE are two Examples of one boundary rule — an input the tool cannot
|
|
189
|
-
* read is a usage error, never a verdict — so one guard covers both. PURE and
|
|
190
|
-
* `@unit`-testable like its siblings: the parsed args + an injected `read` (no fs), which
|
|
191
|
-
* throws for an absent/unreadable path exactly as `readFileSync` does. An OMITTED flag is
|
|
192
|
-
* not an error (the ordinary reconcile path).
|
|
193
|
-
*/
|
|
194
88
|
export function checkRenderFromJson(args, read) {
|
|
195
89
|
const path = args["render-from-json"];
|
|
196
90
|
if (path === undefined)
|
|
@@ -199,72 +93,36 @@ export function checkRenderFromJson(args, read) {
|
|
|
199
93
|
JSON.parse(read(path));
|
|
200
94
|
}
|
|
201
95
|
catch (err) {
|
|
202
|
-
// MISSING (read throws ENOENT) and UNPARSEABLE (JSON.parse throws) are the same input
|
|
203
|
-
// fault, caught in the same place. Anything escaping here lands on Node's default
|
|
204
|
-
// exit 1 — the out-of-balance code — which is the collision this closes.
|
|
205
96
|
const failure = err instanceof Error ? err.message : String(err);
|
|
206
97
|
return `The --render-from-json saved report could not be read: ${path} (${failure})`;
|
|
207
98
|
}
|
|
208
99
|
return null;
|
|
209
100
|
}
|
|
210
|
-
/**
|
|
211
|
-
* The `balance` command. A refusal leaves on the usage code and answers that no report was written,
|
|
212
|
-
* after everything already written has drained.
|
|
213
|
-
*/
|
|
214
101
|
export function runBalance(argv) {
|
|
215
102
|
return settlingRefusal(() => balance(argv), false);
|
|
216
103
|
}
|
|
217
104
|
function balance(argv) {
|
|
218
|
-
// Answer `--help`/`-h` from the command/flag registry BEFORE any
|
|
219
|
-
// parse or reconcile: render the balance command's per-command scope (a line per
|
|
220
|
-
// registered flag — name + arg + description), leave process.exitCode at its default 0,
|
|
221
|
-
// and return. This intercepts --help so it never falls through parseArgs as a stray flag
|
|
222
|
-
// while balance runs anyway. The registry is the single source, so a flag that exists is
|
|
223
|
-
// a flag that appears in help — no hand-kept list to drift.
|
|
224
105
|
if (asksForHelp(argv)) {
|
|
225
106
|
process.stdout.write(renderCommandHelp(cliRegistry, "balance") + "\n");
|
|
226
107
|
return false;
|
|
227
108
|
}
|
|
228
|
-
// Capture the run-start instant up front — the run provenance's UTC timestamp,
|
|
229
|
-
// threaded through ctx.runStart to the renderers.
|
|
230
109
|
const runStart = new Date();
|
|
231
110
|
const args = parseArgs(argv);
|
|
232
|
-
// Legacy-`--json` guard: the earlier `--json <path>` flag is
|
|
233
|
-
// removed. Reject it fast — before any reconcile or write — with a migration hint to
|
|
234
|
-
// `--format json:<path>`, so the removed flag never silently no-ops and drops output. The
|
|
235
|
-
// guard's typed error is routed to stderr + a non-zero exit, exactly as planOutput's error
|
|
236
|
-
// is below (the shared typed-error surface).
|
|
237
111
|
const legacyJsonError = checkLegacyJson(args);
|
|
238
112
|
if (legacyJsonError)
|
|
239
113
|
refuse(legacyJsonError);
|
|
240
|
-
// A host modifier after the command is out of place, not unknown, so it is
|
|
241
|
-
// refused before the drift advice.
|
|
242
114
|
const misplacedModifierError = checkMisplacedHostModifiers(args, cliRegistry, "balance");
|
|
243
115
|
if (misplacedModifierError)
|
|
244
116
|
refuse(misplacedModifierError);
|
|
245
|
-
// Every token is a flag or the value of the flag before it, refused before the unknown-flag
|
|
246
|
-
// check below.
|
|
247
117
|
const shapeError = checkArgvShape(argv, cliRegistry, "balance");
|
|
248
118
|
if (shapeError)
|
|
249
119
|
refuse(shapeError);
|
|
250
|
-
// Registry-bounded flag NAMES: `balance` accepts ONLY the flags its registry
|
|
251
|
-
// scope LISTS. An unregistered flag — a typo `--strcit`, a stray `--bogus` — is a USAGE error:
|
|
252
|
-
// name it on stderr, set process.exitCode = 2 (the enumerated usage code, the 0/1/2
|
|
253
|
-
// taxonomy — never 1, never 0), and RETURN before any reconcile. It bounds names only; that every
|
|
254
|
-
// other token is read or refused is the argv-shape check above.
|
|
255
|
-
// Runs AFTER the legacy-`--json` guard so `--json` keeps its specific migration hint, after the
|
|
256
|
-
// misplaced-modifier refusal, so a `--store` / `--run-id` written after the command is named as
|
|
257
|
-
// out of place rather than given pin advice, and after the argv-shape check, so `--format=json` is
|
|
258
|
-
// refused as a form and never given pin advice. This is the structural teeth behind "accepted =
|
|
259
|
-
// registry = help" — a flag can't affect behaviour without a registry entry.
|
|
260
120
|
const unknownFlagError = checkUnknownFlags(args, cliRegistry, "balance", resolveToolIdentity().toolVersion);
|
|
261
121
|
if (unknownFlagError) {
|
|
262
122
|
process.stderr.write(unknownFlagError + "\n");
|
|
263
123
|
process.exitCode = 2;
|
|
264
124
|
return false;
|
|
265
125
|
}
|
|
266
|
-
// A flag that takes a value, given none, refused before anything reads
|
|
267
|
-
// the value `parseArgs` made up for it.
|
|
268
126
|
const missingValueError = checkMissingValues(argv, cliRegistry, "balance");
|
|
269
127
|
if (missingValueError)
|
|
270
128
|
refuse(missingValueError);
|
|
@@ -272,53 +130,22 @@ function balance(argv) {
|
|
|
272
130
|
return false;
|
|
273
131
|
refuseUnreadableInputs(args);
|
|
274
132
|
const inputs = reconcileInputsOf(args);
|
|
275
|
-
// Reconcile the obligation-vs-observation evidence into the Ledger. The runtime evidence-observation
|
|
276
|
-
// views (Test volume, the evidence grid) are FOLDS the renderers derive off this Ledger's posted
|
|
277
|
-
// credits (the census is retired), so they cannot drift from the
|
|
278
|
-
// reconciler's verdict: they ARE the same rows. `withRuntimeObservations: true` (below) tells the
|
|
279
|
-
// renderers the axis is in play.
|
|
280
133
|
const balance = runReconcile(inputs);
|
|
281
|
-
// Read the scenario-corpus census alongside the balance —
|
|
282
|
-
// the raw-vs-parsed @SCN-occurrence delta over the same discovered feature corpus.
|
|
283
|
-
// A sibling to the balance, threaded to the renderers via writeOutputs like ctx.
|
|
284
134
|
const evidenceObligations = readEvidenceObligations(args["features"]);
|
|
285
|
-
// The static-check kind-counts are a FOLD OVER THE LEDGER — the
|
|
286
|
-
// renderers count the balance's own posted static-check credits (`staticObservationTally`), so
|
|
287
|
-
// they cannot drift from the reconciler's verdict: they ARE the same rows. The parallel static
|
|
288
|
-
// census is RETIRED; `withStaticChecks: true` (below) tells the renderers the axis is in play.
|
|
289
|
-
// Read the target's feature-code→slug map alongside the balance
|
|
290
|
-
// — the by-feature cut's full-name labels. The 5th sibling, threaded to the MARKDOWN
|
|
291
|
-
// renderer via writeOutputs only (markdown-only; the by-feature cut isn't on JSON).
|
|
292
135
|
const featureNames = readFeatureNames(args["features"]);
|
|
293
136
|
const ctx = runContextOf(args, inputs, runStart);
|
|
294
|
-
// Plan the outputs from the --format specs (no flag → md→stdout) and write them via
|
|
295
|
-
// the writer — the pure plan / impure writer split. The plan
|
|
296
|
-
// parse+validate is pure; the writer is the only IO for the emitted report. This is
|
|
297
|
-
// the SOLE output path: the tool emits via --format and holds no storage opinion — the
|
|
298
|
-
// legacy runs/<target>/<ISO>/ auto-archive was removed, the
|
|
299
|
-
// caller now routes stored runs (spec-controller's run-management layer).
|
|
300
137
|
const planResult = planOutput(collectFormatSpecs(argv), namesStdout);
|
|
301
138
|
if (!planResult.ok)
|
|
302
139
|
refuse(planResult.error);
|
|
303
140
|
const payloads = { balance, ctx, evidenceObligations, withRuntimeObservations: true, withStaticChecks: true, featureNames };
|
|
304
141
|
writeOutputs(planResult.plan, payloads, stdoutIdentity, identifiedInputs(inputsReadBy(args, NODE_INPUT_READER)));
|
|
305
|
-
// Set after the report is written in full, never `process.exit`, so the buffered report is
|
|
306
|
-
// flushed intact rather than truncated mid-write.
|
|
307
142
|
const verdict = verdictCode({ ...balance, strict: ctx.strict === true });
|
|
308
143
|
const code = statusFor(verdict, args["exit-zero"] !== undefined);
|
|
309
|
-
// Assigned only when NON-ZERO, so a balanced run still LEAVES process.exitCode at its default
|
|
310
|
-
// rather than writing a 0 over it — the distinction the balanced-exit scenarios rest on.
|
|
311
144
|
if (code !== 0)
|
|
312
145
|
process.exitCode = code;
|
|
313
146
|
return true;
|
|
314
147
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Render-from-saved-JSON mode: the markdown a banked spec-reconciliation.json renders to, on
|
|
317
|
-
* stdout, and the exit its saved verdict names — no reconcile, no live inputs. True when it
|
|
318
|
-
* answered the run, so `runBalance` returns before any reconcile.
|
|
319
|
-
*/
|
|
320
148
|
function renderSavedReport(args) {
|
|
321
|
-
// Here rather than with the live-input guards, because this mode returns before they run.
|
|
322
149
|
const ignoredFlagError = checkReRenderFlags(args);
|
|
323
150
|
if (ignoredFlagError)
|
|
324
151
|
refuse(ignoredFlagError);
|
|
@@ -341,57 +168,19 @@ function renderSavedReport(args) {
|
|
|
341
168
|
process.exitCode = code;
|
|
342
169
|
return true;
|
|
343
170
|
}
|
|
344
|
-
/**
|
|
345
|
-
* The live-input guards, in the order they run: each halts at exit 2 BEFORE any reconcile,
|
|
346
|
-
* because an input the tool cannot read is a usage error, never a verdict.
|
|
347
|
-
*/
|
|
348
171
|
function refuseUnreadableInputs(args) {
|
|
349
|
-
// Input-existence guard: a supplied input-file/dir flag pointing at a
|
|
350
|
-
// non-existent path is a HARD ERROR — halt BEFORE reconciling, with a typed message
|
|
351
|
-
// naming the flag + path, never the silent zero-evidence all-red report a mistyped
|
|
352
|
-
// path used to produce (the dogfooding fault). Routed to stderr + a non-zero exit,
|
|
353
|
-
// exactly as the legacy-`--json` guard (the shared typed-error surface). An
|
|
354
|
-
// omitted flag is legitimately optional and passes through untouched.
|
|
355
172
|
const missingInputError = checkInputsExist(args, existsSync);
|
|
356
173
|
if (missingInputError)
|
|
357
174
|
refuse(missingInputError);
|
|
358
|
-
// Input-READABILITY guard: existence is not enough. A report that is
|
|
359
|
-
// PRESENT but UNPARSEABLE (`--vitest /dev/null`, truncated JSON, non-JSON) used to throw
|
|
360
|
-
// mid-parse inside reconcileWithCensus, uncaught — and Node's default exit code is 1, the
|
|
361
|
-
// code reserved for a genuine OUT-OF-BALANCE verdict. So a tool that
|
|
362
|
-
// CRASHED before reconciling was indistinguishable, by exit code, from an honest
|
|
363
|
-
// disagreement. Runs immediately after the existence guard and BEFORE any
|
|
364
|
-
// reconcile, routing to the same stderr + exit 2 lane: an input the tool cannot read is a
|
|
365
|
-
// usage fault, sibling to a missing path and an unrecognised flag — never a verdict.
|
|
366
175
|
const unparseableInputError = checkInputsParse(args, (path) => readFileSync(path, "utf8"));
|
|
367
176
|
if (unparseableInputError)
|
|
368
177
|
refuse(unparseableInputError);
|
|
369
|
-
// A corpus or workflow path that is not the kind its flag reads, or may not be read, cannot be read at all.
|
|
370
178
|
const unreadablePathError = checkInputPaths(args);
|
|
371
179
|
if (unreadablePathError)
|
|
372
180
|
refuse(unreadablePathError);
|
|
373
|
-
// Parsing is not reading: a report that parses but is not in the shape its reader expects would
|
|
374
|
-
// be read as nothing, or read until it threw, so it is refused here with where its shape is wrong.
|
|
375
181
|
const misshapenInputError = checkInputShapes(jsonInputsOf(args, existsSync), (path) => readFileSync(path, "utf8"));
|
|
376
182
|
if (misshapenInputError)
|
|
377
183
|
refuse(misshapenInputError);
|
|
378
|
-
// The FEATURE-CORPUS integrity checkpoint. The feature corpus is an
|
|
379
|
-
// input too — the ledger's DEBIT side — and it was read by a hand-rolled TAG-LINE SCAN that
|
|
380
|
-
// never consulted a parser. So a malformed tag line simply stopped being a tag line and its
|
|
381
|
-
// scenario CEASED TO EXIST: one stray character deleted a failing row and turned an
|
|
382
|
-
// out-of-balance run (exit 1) into a clean BALANCED (exit 0). A dropped scenario cannot even
|
|
383
|
-
// reconcile `missing`, because it was never RAISED to be missing — the run read cleaner than
|
|
384
|
-
// reality, the exact false-green this tool exists to prevent.
|
|
385
|
-
//
|
|
386
|
-
// Validated with the REAL Gherkin parser (cucumber's own), so a file the tool would reconcile is
|
|
387
|
-
// at least a file cucumber would RUN. A file it rejects is an input we CANNOT READ: exit 2,
|
|
388
|
-
// halting BEFORE any reconcile, naming the file and the parse failure — the same Rule as
|
|
389
|
-
// the unparseable-report and unreadable-saved-report guards ("an input the tool cannot read is a usage error, never a
|
|
390
|
-
// verdict"), now on the obligation side. Never a verdict, and never a green.
|
|
391
|
-
//
|
|
392
|
-
// The corpus is EXTRACTED from the same real AST, so what the tool reconciles is
|
|
393
|
-
// what cucumber runs — Feature/Rule tag inheritance included. This guard decides whether the file
|
|
394
|
-
// can be read at all; parseScenarios then reads it exactly as cucumber would.
|
|
395
184
|
const featureParseErrors = featureCorpusParseErrors(args["features"]);
|
|
396
185
|
if (featureParseErrors.length > 0) {
|
|
397
186
|
refuse(featureParseErrors
|
|
@@ -399,13 +188,6 @@ function refuseUnreadableInputs(args) {
|
|
|
399
188
|
.join("\n"));
|
|
400
189
|
}
|
|
401
190
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Build the run provenance ONCE (project design §1) — a sibling RunContext, the
|
|
404
|
-
* Ledger staying pure — reusing the existing runStart. The target is the
|
|
405
|
-
* operator's --target VERBATIM (unknown-target on omit; Fork A — no
|
|
406
|
-
* resolution, no basename guessing). Source SHA via the read-only target read, with
|
|
407
|
-
* --source-sha as the override (resolveSourceSha).
|
|
408
|
-
*/
|
|
409
191
|
function runContextOf(args, inputs, runStart) {
|
|
410
192
|
return {
|
|
411
193
|
target: sourceTarget(args),
|
|
@@ -415,23 +197,11 @@ function runContextOf(args, inputs, runStart) {
|
|
|
415
197
|
sourceSha: args["source-sha"],
|
|
416
198
|
targetDir: args["features"] !== undefined ? dirname(args["features"]) : undefined,
|
|
417
199
|
}),
|
|
418
|
-
// The TARGET repo root the recorded input paths are made portable against (fixed to
|
|
419
|
-
// the target root, not the run cwd) — the SAME
|
|
420
|
-
// root readEvidenceObservations passes to countRuntimeEvidenceObservationKinds, derived from
|
|
421
|
-
// --features, so a cross-repo reconcile records inputs relative (`features`) rather
|
|
422
|
-
// than leaking the target's absolute path.
|
|
423
200
|
root: resolveTargetRoot({ features: inputs.features }),
|
|
424
|
-
// The TOOL's own identity — which spec-controller produced
|
|
425
|
-
// this run, distinct from the target's sourceSha. Read from the tool's OWN dir, never
|
|
426
|
-
// cwd (which is the target). One call, spread into the ctx; the same identity feeds
|
|
427
|
-
// the run.yaml manifest.
|
|
428
201
|
...resolveToolIdentity(),
|
|
429
202
|
strict: args["strict"] !== undefined,
|
|
430
203
|
};
|
|
431
204
|
}
|
|
432
|
-
// Only run the balance command when this module is executed directly
|
|
433
|
-
// (`tsx cli-balance/cli.ts`), NOT when imported by the top-level `spec-controller`
|
|
434
|
-
// bin dispatcher or by a test that exercises the pure edge guards.
|
|
435
205
|
const invokedDirectly = process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`;
|
|
436
206
|
if (invokedDirectly)
|
|
437
207
|
runBalance(process.argv.slice(2));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-balance/cli.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-balance/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,47 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The output PLAN — the pure app-layer parse+validate of the `--format <fmt>[:<path>]`
|
|
3
|
-
* grammar (the three-layer split). Given the raw
|
|
4
|
-
* `--format` specs (in argv order; empty when no flag was passed), `planOutput` returns
|
|
5
|
-
* either an `OutputPlan` — an ordered list of `{ fmt, dest }` outputs — or a typed error.
|
|
6
|
-
*
|
|
7
|
-
* PURE: no IO. The plan describes WHAT to emit and WHERE; the writer (`writer.ts`) is the
|
|
8
|
-
* only layer that performs the IO. This split lets `planOutput` be proven in isolation
|
|
9
|
-
* (`@unit`, no subprocess) while the writer's real stdout/file effect is the `@integration`
|
|
10
|
-
* arm — the anti-watermelon rule (each integration arm asserts the real IO, never a
|
|
11
|
-
* planOutput re-run).
|
|
12
|
-
*
|
|
13
|
-
* FMT-001 (`@SCN-FMT-001`) establishes the skeleton: no flag — and an explicit `md` —
|
|
14
|
-
* plan a single markdown output bound for stdout. Later sub-issues extend `planOutput`
|
|
15
|
-
* with json (FMT-002), file dests (`<fmt>:<path>`, FMT-002/003), the at-most-one-to-stdout
|
|
16
|
-
* invariant (FMT-005), the legacy-`--json` rejection (FMT-006), and unknown-format
|
|
17
|
-
* rejection (FMT-007) — each landing on the `OutputPlan` type + typed `PlanResult` error
|
|
18
|
-
* surface designed here.
|
|
19
|
-
*
|
|
20
|
-
* Parent: @SCN-FMT-001 — the output-dispatch rework.
|
|
21
|
-
*/
|
|
22
|
-
/** A supported output format. FMT-001 plans `md`; `json` is wired in FMT-002. */
|
|
23
1
|
export type OutputFormat = "md" | "json";
|
|
24
|
-
/** Where a planned output is written — stdout, or a caller-given file path. */
|
|
25
2
|
export type OutputDest = {
|
|
26
3
|
readonly kind: "stdout";
|
|
27
4
|
} | {
|
|
28
5
|
readonly kind: "file";
|
|
29
6
|
readonly path: string;
|
|
30
7
|
};
|
|
31
|
-
/** One planned output: a format routed to a destination. */
|
|
32
8
|
export interface PlannedOutput {
|
|
33
9
|
readonly fmt: OutputFormat;
|
|
34
10
|
readonly dest: OutputDest;
|
|
35
11
|
}
|
|
36
|
-
/** The plan: the ordered outputs a single invocation will emit. */
|
|
37
12
|
export interface OutputPlan {
|
|
38
13
|
readonly outputs: readonly PlannedOutput[];
|
|
39
14
|
}
|
|
40
|
-
/**
|
|
41
|
-
* The result of planning — either the plan, or a typed error carrying the operator-facing
|
|
42
|
-
* message (later sub-issues raise these for the stdout-collision, legacy-flag, and
|
|
43
|
-
* unknown-format cases). A discriminated union so the CLI branches on `ok`.
|
|
44
|
-
*/
|
|
45
15
|
export type PlanResult = {
|
|
46
16
|
readonly ok: true;
|
|
47
17
|
readonly plan: OutputPlan;
|
|
@@ -49,42 +19,20 @@ export type PlanResult = {
|
|
|
49
19
|
readonly ok: false;
|
|
50
20
|
readonly error: string;
|
|
51
21
|
};
|
|
52
|
-
/**
|
|
53
|
-
* Parse + validate the `--format` specs into an `OutputPlan` (or a typed error).
|
|
54
|
-
*
|
|
55
|
-
* FMT-001: no specs (no `--format` flag) defaults to a single `md`; an explicit `md`
|
|
56
|
-
* plans the same. Each planned output is markdown bound for stdout — the default human
|
|
57
|
-
* report, like vitest/cucumber.
|
|
58
|
-
*/
|
|
59
22
|
export declare function planOutput(formatSpecs: readonly string[], namesStdout?: (path: string) => boolean): PlanResult;
|
|
60
|
-
/** The operating system's identity for a regular file: its device and inode, exact beyond 2^53. */
|
|
61
23
|
export interface FileIdentity {
|
|
62
24
|
readonly dev: bigint;
|
|
63
25
|
readonly ino: bigint;
|
|
64
26
|
}
|
|
65
|
-
/** A planned output beside the identity of the regular file it lands on, when it lands on one. */
|
|
66
27
|
export interface IdentifiedOutput {
|
|
67
28
|
readonly output: PlannedOutput;
|
|
68
29
|
readonly identity: FileIdentity | undefined;
|
|
69
30
|
}
|
|
70
|
-
/**
|
|
71
|
-
* The refusal for the first two outputs that land on one regular file, or undefined when none do.
|
|
72
|
-
* The file is known by its identity rather than its spelling, so a link, a `..`, a case the volume
|
|
73
|
-
* folds or the file standard output is redirected into all count. An output with no identity is
|
|
74
|
-
* not on a regular file, and writing such a destination twice loses nothing, so it is never compared.
|
|
75
|
-
*/
|
|
76
31
|
export declare function sharedFileRefusal(outputs: readonly IdentifiedOutput[]): string | undefined;
|
|
77
|
-
/** A file the run reads, as the refusal names it, beside the identity of the regular file it is, when it is one. */
|
|
78
32
|
export interface IdentifiedInput {
|
|
79
33
|
readonly subject: string;
|
|
80
34
|
readonly path: string;
|
|
81
35
|
readonly identity: FileIdentity | undefined;
|
|
82
36
|
}
|
|
83
|
-
/**
|
|
84
|
-
* The refusal for the first output that lands on a regular file the run reads, or undefined when none
|
|
85
|
-
* does. The file is known by its identity, as two outputs sharing one are, so a link, a `..`, a case
|
|
86
|
-
* the volume folds or standard output appended into an input all count. Something that is not a
|
|
87
|
-
* regular file, on either side, is never compared.
|
|
88
|
-
*/
|
|
89
37
|
export declare function inputOverwriteRefusal(outputs: readonly IdentifiedOutput[], inputs: readonly IdentifiedInput[]): string | undefined;
|
|
90
38
|
//# sourceMappingURL=format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/cli-balance/emit/format.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/cli-balance/emit/format.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,CAAC;AAQzC,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,UAAU,CACxB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,WAAW,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAqB,GACnD,UAAU,CAkCZ;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;CAC7C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,GAAG,SAAS,CAY1F;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;CAC7C;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,GAAG,SAAS,CAYlI"}
|
|
@@ -1,63 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The output PLAN — the pure app-layer parse+validate of the `--format <fmt>[:<path>]`
|
|
3
|
-
* grammar (the three-layer split). Given the raw
|
|
4
|
-
* `--format` specs (in argv order; empty when no flag was passed), `planOutput` returns
|
|
5
|
-
* either an `OutputPlan` — an ordered list of `{ fmt, dest }` outputs — or a typed error.
|
|
6
|
-
*
|
|
7
|
-
* PURE: no IO. The plan describes WHAT to emit and WHERE; the writer (`writer.ts`) is the
|
|
8
|
-
* only layer that performs the IO. This split lets `planOutput` be proven in isolation
|
|
9
|
-
* (`@unit`, no subprocess) while the writer's real stdout/file effect is the `@integration`
|
|
10
|
-
* arm — the anti-watermelon rule (each integration arm asserts the real IO, never a
|
|
11
|
-
* planOutput re-run).
|
|
12
|
-
*
|
|
13
|
-
* FMT-001 (`@SCN-FMT-001`) establishes the skeleton: no flag — and an explicit `md` —
|
|
14
|
-
* plan a single markdown output bound for stdout. Later sub-issues extend `planOutput`
|
|
15
|
-
* with json (FMT-002), file dests (`<fmt>:<path>`, FMT-002/003), the at-most-one-to-stdout
|
|
16
|
-
* invariant (FMT-005), the legacy-`--json` rejection (FMT-006), and unknown-format
|
|
17
|
-
* rejection (FMT-007) — each landing on the `OutputPlan` type + typed `PlanResult` error
|
|
18
|
-
* surface designed here.
|
|
19
|
-
*
|
|
20
|
-
* Parent: @SCN-FMT-001 — the output-dispatch rework.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* The formats `planOutput` accepts; any other `--format` token is rejected (FMT-007 /
|
|
24
|
-
* @SCN-FMT-007). The single source of truth the unknown-format guard checks against AND
|
|
25
|
-
* lists in its hint, so a future format (html, sarif) is added in exactly one place.
|
|
26
|
-
*/
|
|
27
1
|
const SUPPORTED_FORMATS = ["md", "json"];
|
|
28
|
-
/** Runtime narrowing of a parsed `--format` token to a supported `OutputFormat`. */
|
|
29
2
|
function isSupportedFormat(fmt) {
|
|
30
3
|
return SUPPORTED_FORMATS.includes(fmt);
|
|
31
4
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Parse + validate the `--format` specs into an `OutputPlan` (or a typed error).
|
|
34
|
-
*
|
|
35
|
-
* FMT-001: no specs (no `--format` flag) defaults to a single `md`; an explicit `md`
|
|
36
|
-
* plans the same. Each planned output is markdown bound for stdout — the default human
|
|
37
|
-
* report, like vitest/cucumber.
|
|
38
|
-
*/
|
|
39
5
|
export function planOutput(formatSpecs, namesStdout = () => false) {
|
|
40
|
-
// No --format flag defaults to a single "md" spec — the default human report.
|
|
41
6
|
const specs = formatSpecs.length === 0 ? ["md"] : formatSpecs;
|
|
42
7
|
const outputs = specs.map((spec) => {
|
|
43
|
-
// Grammar: "<fmt>[:<path>]". Split on the FIRST colon — the fmt is everything before
|
|
44
|
-
// it; a ":<path>" tail routes that format to the file at <path> (FMT-002/003), where
|
|
45
|
-
// the path is everything after the first colon (so a path may itself contain colons).
|
|
46
|
-
// No colon keeps the bare-fmt → stdout default (FMT-001). The at-most-one-to-stdout /
|
|
47
|
-
// unknown-format validation (FMT-005/007) lands on this same parse and the typed
|
|
48
|
-
// PlanResult error surface.
|
|
49
8
|
const colon = spec.indexOf(":");
|
|
50
9
|
const fmt = (colon === -1 ? spec : spec.slice(0, colon));
|
|
51
10
|
const dest = colon === -1 ? { kind: "stdout" } : { kind: "file", path: spec.slice(colon + 1) };
|
|
52
11
|
return { fmt, dest };
|
|
53
12
|
});
|
|
54
|
-
// The unknown-format guard (FMT-007 / @SCN-FMT-007): a
|
|
55
|
-
// --format naming an unsupported format (e.g. `xml`, or a typo) is rejected fast on the
|
|
56
|
-
// shared typed PlanResult error surface (the same surface FMT-005/006 use) — rather than
|
|
57
|
-
// silently rendering as markdown (the writer's default render branch) and exiting 0. The
|
|
58
|
-
// error LISTS the supported formats so the operator knows what to use — basic public-CLI
|
|
59
|
-
// robustness. Checked BEFORE the stdout-collision count so an unroutable format is
|
|
60
|
-
// reported as such (an unknown format can't meaningfully collide on stdout).
|
|
61
13
|
const unknown = outputs.filter((o) => !isSupportedFormat(o.fmt));
|
|
62
14
|
if (unknown.length > 0) {
|
|
63
15
|
const bad = [...new Set(unknown.map((o) => o.fmt))].join(", ");
|
|
@@ -67,16 +19,6 @@ export function planOutput(formatSpecs, namesStdout = () => false) {
|
|
|
67
19
|
`(e.g. --format md, --format json:report.json).`,
|
|
68
20
|
};
|
|
69
21
|
}
|
|
70
|
-
// The stdout invariant (FMT-005 / @SCN-FMT-005): AT MOST
|
|
71
|
-
// ONE output may route to stdout, so a captured stdout never carries two interleaved
|
|
72
|
-
// documents. This is a COUNT check on the stdout-destined outputs (≤ 1) — NOT a
|
|
73
|
-
// "distinct formats > 1" check — so `--format md --format md` (the same format twice,
|
|
74
|
-
// both stdout) is rejected just as `--format md --format json` is; a distinct-formats
|
|
75
|
-
// check would wrongly pass the former. Rejected with a typed error on the shared
|
|
76
|
-
// PlanResult surface (the same surface FMT-006/007 raise), carrying the hint to give one
|
|
77
|
-
// of the colliding outputs a `:<path>` so it writes to a file instead.
|
|
78
|
-
// A path that names standard output itself (/dev/stdout, /dev/fd/1) routes there as surely as a
|
|
79
|
-
// bare format does, so it is counted with the outputs bound for stdout.
|
|
80
22
|
const stdoutBound = outputs.filter((o) => o.dest.kind === "stdout" || namesStdout(o.dest.path));
|
|
81
23
|
if (stdoutBound.length > 1) {
|
|
82
24
|
const fmts = stdoutBound.map((o) => o.fmt).join(", ");
|
|
@@ -89,12 +31,6 @@ export function planOutput(formatSpecs, namesStdout = () => false) {
|
|
|
89
31
|
}
|
|
90
32
|
return { ok: true, plan: { outputs } };
|
|
91
33
|
}
|
|
92
|
-
/**
|
|
93
|
-
* The refusal for the first two outputs that land on one regular file, or undefined when none do.
|
|
94
|
-
* The file is known by its identity rather than its spelling, so a link, a `..`, a case the volume
|
|
95
|
-
* folds or the file standard output is redirected into all count. An output with no identity is
|
|
96
|
-
* not on a regular file, and writing such a destination twice loses nothing, so it is never compared.
|
|
97
|
-
*/
|
|
98
34
|
export function sharedFileRefusal(outputs) {
|
|
99
35
|
const onFiles = outputs.filter((o) => o.identity !== undefined);
|
|
100
36
|
for (const [index, first] of onFiles.entries()) {
|
|
@@ -106,12 +42,6 @@ export function sharedFileRefusal(outputs) {
|
|
|
106
42
|
}
|
|
107
43
|
return undefined;
|
|
108
44
|
}
|
|
109
|
-
/**
|
|
110
|
-
* The refusal for the first output that lands on a regular file the run reads, or undefined when none
|
|
111
|
-
* does. The file is known by its identity, as two outputs sharing one are, so a link, a `..`, a case
|
|
112
|
-
* the volume folds or standard output appended into an input all count. Something that is not a
|
|
113
|
-
* regular file, on either side, is never compared.
|
|
114
|
-
*/
|
|
115
45
|
export function inputOverwriteRefusal(outputs, inputs) {
|
|
116
46
|
for (const { output, identity } of outputs) {
|
|
117
47
|
if (identity === undefined)
|