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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli-balance/cli.d.ts +87 -0
  3. package/dist/cli-balance/cli.d.ts.map +1 -0
  4. package/dist/cli-balance/cli.js +486 -0
  5. package/dist/cli-balance/cli.js.map +1 -0
  6. package/dist/cli-balance/emit/format.d.ts +60 -0
  7. package/dist/cli-balance/emit/format.d.ts.map +1 -0
  8. package/dist/cli-balance/emit/format.js +90 -0
  9. package/dist/cli-balance/emit/format.js.map +1 -0
  10. package/dist/cli-balance/emit/writer.d.ts +45 -0
  11. package/dist/cli-balance/emit/writer.d.ts.map +1 -0
  12. package/dist/cli-balance/emit/writer.js +48 -0
  13. package/dist/cli-balance/emit/writer.js.map +1 -0
  14. package/dist/cli-registry.d.ts +68 -0
  15. package/dist/cli-registry.d.ts.map +1 -0
  16. package/dist/cli-registry.js +181 -0
  17. package/dist/cli-registry.js.map +1 -0
  18. package/dist/cli.d.ts +22 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +82 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/deferralTags.d.ts +16 -0
  23. package/dist/deferralTags.d.ts.map +1 -0
  24. package/dist/deferralTags.js +22 -0
  25. package/dist/deferralTags.js.map +1 -0
  26. package/dist/host.d.ts +50 -0
  27. package/dist/host.d.ts.map +1 -0
  28. package/dist/host.js +69 -0
  29. package/dist/host.js.map +1 -0
  30. package/dist/ingest/ciSourcePaths.d.ts +46 -0
  31. package/dist/ingest/ciSourcePaths.d.ts.map +1 -0
  32. package/dist/ingest/ciSourcePaths.js +58 -0
  33. package/dist/ingest/ciSourcePaths.js.map +1 -0
  34. package/dist/ingest/gherkinValidation.d.ts +70 -0
  35. package/dist/ingest/gherkinValidation.d.ts.map +1 -0
  36. package/dist/ingest/gherkinValidation.js +85 -0
  37. package/dist/ingest/gherkinValidation.js.map +1 -0
  38. package/dist/ingest/ingestQualityChecks.d.ts +119 -0
  39. package/dist/ingest/ingestQualityChecks.d.ts.map +1 -0
  40. package/dist/ingest/ingestQualityChecks.js +331 -0
  41. package/dist/ingest/ingestQualityChecks.js.map +1 -0
  42. package/dist/ingest/ingestScenarios.d.ts +52 -0
  43. package/dist/ingest/ingestScenarios.d.ts.map +1 -0
  44. package/dist/ingest/ingestScenarios.js +119 -0
  45. package/dist/ingest/ingestScenarios.js.map +1 -0
  46. package/dist/mutation-ratchet/index.d.ts +48 -0
  47. package/dist/mutation-ratchet/index.d.ts.map +1 -0
  48. package/dist/mutation-ratchet/index.js +48 -0
  49. package/dist/mutation-ratchet/index.js.map +1 -0
  50. package/dist/mutation-ratchet/ratchet.d.ts +129 -0
  51. package/dist/mutation-ratchet/ratchet.d.ts.map +1 -0
  52. package/dist/mutation-ratchet/ratchet.js +222 -0
  53. package/dist/mutation-ratchet/ratchet.js.map +1 -0
  54. package/dist/mutation-ratchet/ratchetCli.d.ts +57 -0
  55. package/dist/mutation-ratchet/ratchetCli.d.ts.map +1 -0
  56. package/dist/mutation-ratchet/ratchetCli.js +139 -0
  57. package/dist/mutation-ratchet/ratchetCli.js.map +1 -0
  58. package/dist/mutation-ratchet/reconcile.d.ts +82 -0
  59. package/dist/mutation-ratchet/reconcile.d.ts.map +1 -0
  60. package/dist/mutation-ratchet/reconcile.js +67 -0
  61. package/dist/mutation-ratchet/reconcile.js.map +1 -0
  62. package/dist/mutation-ratchet/record.d.ts +210 -0
  63. package/dist/mutation-ratchet/record.d.ts.map +1 -0
  64. package/dist/mutation-ratchet/record.js +330 -0
  65. package/dist/mutation-ratchet/record.js.map +1 -0
  66. package/dist/mutation-ratchet/report.d.ts +83 -0
  67. package/dist/mutation-ratchet/report.d.ts.map +1 -0
  68. package/dist/mutation-ratchet/report.js +148 -0
  69. package/dist/mutation-ratchet/report.js.map +1 -0
  70. package/dist/mutation-ratchet/verdict.d.ts +177 -0
  71. package/dist/mutation-ratchet/verdict.d.ts.map +1 -0
  72. package/dist/mutation-ratchet/verdict.js +387 -0
  73. package/dist/mutation-ratchet/verdict.js.map +1 -0
  74. package/dist/run-management/keptRun.d.ts +156 -0
  75. package/dist/run-management/keptRun.d.ts.map +1 -0
  76. package/dist/run-management/keptRun.js +133 -0
  77. package/dist/run-management/keptRun.js.map +1 -0
  78. package/dist/run-management/resolveRunInputs.d.ts +70 -0
  79. package/dist/run-management/resolveRunInputs.d.ts.map +1 -0
  80. package/dist/run-management/resolveRunInputs.js +144 -0
  81. package/dist/run-management/resolveRunInputs.js.map +1 -0
  82. package/package.json +36 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 3F Consulting Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,87 @@
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 <ci.yml>]
11
+ * [--format <fmt>[:<path>]]
12
+ */
13
+ import type { CliRegistry } from "../cli-registry.js";
14
+ /**
15
+ * The legacy-`--json` guard at the CLI edge (@SCN-FMT-006). The earlier
16
+ * `--json <path>` flag is removed; supplying it must fail fast with a migration hint to
17
+ * `--format json:<path>`, so the removed flag never silently no-ops and drops output. PURE
18
+ * and `@unit`-testable: the removed flag is NOT a `--format` spec (planOutput never sees
19
+ * it), so the guard reads the parsed args directly and returns the typed migration-hint
20
+ * error message when `--json` is present — routed to stderr + a non-zero exit by main(),
21
+ * the same handling planOutput's typed error gets (the shared error surface) — else null.
22
+ */
23
+ export declare function checkLegacyJson(args: Record<string, string>): string | null;
24
+ /**
25
+ * Source the provenance "Target repo:" name for a run (@SCN-RPT-001 Update —
26
+ * Fork A). The operator's `--target` is recorded VERBATIM — no resolution — defaulting to
27
+ * `"unknown-target"` when omitted (parallel to `sourceSha`'s `"unknown"`; the facts
28
+ * artefact is self-describing, so the line always renders). PURE and `@unit`-testable at
29
+ * the CLI edge, like `checkLegacyJson`. It never inspects `--features`: the prior
30
+ * `basename(dirname(--features))` resolution (the deleted `resolveTarget`) and its
31
+ * degenerate `.` are impossible now — the target is only ever the given value or the
32
+ * explicit default.
33
+ */
34
+ export declare function sourceTarget(args: Record<string, string>): string;
35
+ export declare function checkInputsExist(args: Record<string, string>, exists: (path: string) => boolean): string | null;
36
+ /**
37
+ * Guard the READABILITY of the supplied JSON inputs at the CLI edge (@SCN-CLI-014).
38
+ * `checkInputsExist` already guards their PRESENCE — but a path that is present yet
39
+ * UNPARSEABLE fell straight through it: the reader threw mid-parse, nothing caught it, and
40
+ * the process hit Node's default exit 1 — the code reserved for a genuine OUT-OF-BALANCE
41
+ * verdict. A crashed invocation was therefore indistinguishable, by exit code, from an
42
+ * honest disagreement (3F-1742). An input the tool cannot read is a USAGE fault: exit 2,
43
+ * sibling to a missing path (@SCN-CLI-002) and an unrecognised flag (@SCN-CLI-009).
44
+ *
45
+ * PURE and `@unit`-testable like its sibling guards: reads the parsed args + an injected
46
+ * `read` fn (no fs), returns the FIRST supplied-but-unparseable `(flag, path, failure)` as
47
+ * a typed error naming all three — routed to stderr + exit 2 by `runBalance` — else null.
48
+ * An omitted flag is not an error, and an unreadable file (a race after the existence
49
+ * check) reports as the same input fault rather than escaping as a crash.
50
+ */
51
+ export declare function checkInputsParse(args: Record<string, string>, read: (path: string) => string): string | null;
52
+ /**
53
+ * Guard the readability of `--render-from-json` at the CLI edge (@SCN-CLI-015). The
54
+ * render-from-saved-JSON branch RETURNS before the input guards ever run, so it honoured
55
+ * NEITHER: a missing path crashed to `ENOENT` and a malformed file to `SyntaxError`, both
56
+ * landing on Node's default exit 1 — the out-of-balance code (3F-1764).
57
+ *
58
+ * The missing-path case was a live violation of @SCN-CLI-002's own shipped principle: a
59
+ * supplied input path that does not exist is a hard error at exit 2, enforced for the five
60
+ * evidence flags and not for this one. The CLI shipped a guard that half-honoured its Rule.
61
+ *
62
+ * MISSING and UNPARSEABLE are two Examples of one boundary rule — an input the tool cannot
63
+ * read is a usage error, never a verdict — so one guard covers both. PURE and
64
+ * `@unit`-testable like its siblings: the parsed args + an injected `read` (no fs), which
65
+ * throws for an absent/unreadable path exactly as `readFileSync` does. An OMITTED flag is
66
+ * not an error (the ordinary reconcile path).
67
+ */
68
+ export declare function checkRenderFromJson(args: Record<string, string>, read: (path: string) => string): string | null;
69
+ /**
70
+ * The registry-bounded flag check at the CLI edge (@SCN-CLI-009). `balance` accepts ONLY
71
+ * the flags its per-command registry scope LISTS (by `name` or `alias`); an unregistered
72
+ * flag — a typo like `--strcit`, a stray `--bogus` — is a usage error, NOT silently
73
+ * swallowed the way the old permissive `parseArgs` did. This is the structural teeth behind
74
+ * "accepted = registry = help": a flag can't affect behaviour without a registry entry (and
75
+ * so, by CLI-008's guard, without appearing in help).
76
+ *
77
+ * PURE and `@unit`-testable like `checkLegacyJson` / `checkInputsExist`: reads the parsed
78
+ * args (keys are the `--`-stripped flag names) + the registry, returns the FIRST unregistered
79
+ * flag as a typed usage error naming it WITH its dashes — routed to stderr + exit 2 by
80
+ * `runBalance` (per 3F-1414's `0` balanced / `1` out-of-balance / `2` usage taxonomy),
81
+ * never exit 1, never a silent reconcile — else null. The global `--store`/`--run-id`
82
+ * modifiers never reach here: the top-level dispatcher (`parseHostInvocation`) consumes them
83
+ * BEFORE `balance` is dispatched, so the bounded parser sees only the command's own args.
84
+ */
85
+ export declare function checkUnknownFlags(args: Record<string, string>, registry: CliRegistry): string | null;
86
+ export declare function runBalance(argv: string[]): void;
87
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli-balance/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsBH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA0CtD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAS3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEjE;AAqCD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAChC,MAAM,GAAG,IAAI,CAYf;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC7B,MAAM,GAAG,IAAI,CAef;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC7B,MAAM,GAAG,IAAI,CAcf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAmBpG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA8P/C"}
@@ -0,0 +1,486 @@
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 <ci.yml>]
11
+ * [--format <fmt>[:<path>]]
12
+ */
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { dirname } from "node:path";
15
+ import { runReconcile, readEvidenceObligations, readFeatureNames, featureCorpusParseErrors, featureCorpusDuplicateScnIds, } from "../ingest/ingestQualityChecks.js";
16
+ import { planOutput } from "./emit/format.js";
17
+ import { writeOutputs } from "./emit/writer.js";
18
+ import { renderReportFromJson, isBalanced, hasPendingItems, hasBrokenEvidence, } from "@3f-consulting/spec-controller-core";
19
+ import { resolveSourceSha, resolveTargetRoot, resolveToolIdentity } from "../run-management/resolveRunInputs.js";
20
+ import { cliRegistry, renderBalanceHelp } from "../cli-registry.js";
21
+ /** Parse `--flag value` pairs from argv into a record. */
22
+ function parseArgs(argv) {
23
+ const args = {};
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a !== undefined && a.startsWith("--")) {
27
+ const key = a.slice(2);
28
+ const value = argv[i + 1];
29
+ if (value !== undefined && !value.startsWith("--")) {
30
+ args[key] = value;
31
+ i++;
32
+ }
33
+ else {
34
+ args[key] = "true";
35
+ }
36
+ }
37
+ }
38
+ return args;
39
+ }
40
+ /**
41
+ * Collect the `--format <spec>` values from argv in order (the flag is REPEATABLE, unlike
42
+ * the single-valued flags `parseArgs` records) — the raw specs `planOutput` parses. Empty
43
+ * when no `--format` was given, which `planOutput` reads as the md→stdout default
44
+ * (@SCN-FMT-001). Later sub-issues (FMT-004) route several specs in one invocation.
45
+ */
46
+ function collectFormatSpecs(argv) {
47
+ const specs = [];
48
+ for (let i = 0; i < argv.length; i++) {
49
+ if (argv[i] === "--format") {
50
+ const value = argv[i + 1];
51
+ if (value !== undefined && !value.startsWith("--")) {
52
+ specs.push(value);
53
+ i++;
54
+ }
55
+ }
56
+ }
57
+ return specs;
58
+ }
59
+ /**
60
+ * The legacy-`--json` guard at the CLI edge (@SCN-FMT-006). The earlier
61
+ * `--json <path>` flag is removed; supplying it must fail fast with a migration hint to
62
+ * `--format json:<path>`, so the removed flag never silently no-ops and drops output. PURE
63
+ * and `@unit`-testable: the removed flag is NOT a `--format` spec (planOutput never sees
64
+ * it), so the guard reads the parsed args directly and returns the typed migration-hint
65
+ * error message when `--json` is present — routed to stderr + a non-zero exit by main(),
66
+ * the same handling planOutput's typed error gets (the shared error surface) — else null.
67
+ */
68
+ export function checkLegacyJson(args) {
69
+ if (args["json"] !== undefined) {
70
+ return ("The --json <path> flag has been removed. " +
71
+ "Use --format json:<path> to write the JSON report to a file " +
72
+ "(e.g. --format json:spec-reconciliation.json).");
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Source the provenance "Target repo:" name for a run (@SCN-RPT-001 Update —
78
+ * Fork A). The operator's `--target` is recorded VERBATIM — no resolution — defaulting to
79
+ * `"unknown-target"` when omitted (parallel to `sourceSha`'s `"unknown"`; the facts
80
+ * artefact is self-describing, so the line always renders). PURE and `@unit`-testable at
81
+ * the CLI edge, like `checkLegacyJson`. It never inspects `--features`: the prior
82
+ * `basename(dirname(--features))` resolution (the deleted `resolveTarget`) and its
83
+ * degenerate `.` are impossible now — the target is only ever the given value or the
84
+ * explicit default.
85
+ */
86
+ export function sourceTarget(args) {
87
+ return args["target"] ?? "unknown-target";
88
+ }
89
+ /**
90
+ * Guard the supplied input-file/dir flags at the CLI edge (@SCN-CLI-002). A
91
+ * mistyped `--vitest` / `--cucumber` / `--features` / `--ci` / `--package-json`
92
+ * pointing at a non-existent path used to reconcile silently against ZERO evidence —
93
+ * a terrifying false all-red report meaning "you forgot the reports", not "your code
94
+ * is broken" (the dogfooding fault). The five flags are Examples of ONE boundary
95
+ * rule: a supplied-but-absent input path is a hard error. Scope is existence only
96
+ * (readability / file-vs-dir type-correctness out of scope; `exists` covers the dir
97
+ * flag and the file flags alike).
98
+ *
99
+ * PURE and `@unit`-testable like `checkLegacyJson` / `sourceTarget`: reads the parsed
100
+ * args + an injected `exists` fn (no fs), returns the FIRST supplied-but-absent
101
+ * `(flag, path)` as a typed error naming both — routed to stderr + a non-zero exit by
102
+ * `runBalance` (the shared typed-error surface) — else null. An OMITTED flag is not an
103
+ * error (legitimately optional: simply no evidence of that kind).
104
+ */
105
+ /**
106
+ * The parsed args, as the reconciler's input paths — the one place a `--flag` name becomes a
107
+ * `ReconcileOptions` key.
108
+ *
109
+ * Its own function rather than an object literal inside `runBalance`, which sits at the
110
+ * `max-lines-per-function` standard: a flag is added here, not by growing the orchestration. The
111
+ * mapping is also the only thing in that function with nothing to do with orchestration.
112
+ */
113
+ function reconcileInputsOf(args) {
114
+ return {
115
+ features: args["features"],
116
+ vitest: args["vitest"],
117
+ cucumber: args["cucumber"],
118
+ ci: args["ci"],
119
+ packageJson: args["package-json"],
120
+ measurements: args["measurements"],
121
+ };
122
+ }
123
+ export function checkInputsExist(args, exists) {
124
+ // The five input flags in a fixed order — the FIRST supplied-but-absent one is the
125
+ // reported error (deterministic; the operator fixes and re-runs). Keys are the
126
+ // parseArgs form (the `--` stripped); the message names the flag WITH its dashes.
127
+ const flags = ["vitest", "cucumber", "features", "ci", "package-json", "measurements"];
128
+ for (const flag of flags) {
129
+ const path = args[flag];
130
+ if (path !== undefined && !exists(path)) {
131
+ return `The --${flag} path does not exist: ${path}`;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ /**
137
+ * The JSON-BEARING input flags — the ones whose content is `JSON.parse`d, and so the only
138
+ * ones whose content can throw. `--features` is Gherkin (parsed tolerantly) and `--ci` is
139
+ * read as raw TEXT and scanned for `run:` steps — neither is JSON, neither can crash the
140
+ * process, and on garbage both reconcile normally (verified, 3F-1742 Discovery).
141
+ */
142
+ const JSON_INPUT_FLAGS = ["vitest", "cucumber", "package-json", "measurements"];
143
+ /**
144
+ * Guard the READABILITY of the supplied JSON inputs at the CLI edge (@SCN-CLI-014).
145
+ * `checkInputsExist` already guards their PRESENCE — but a path that is present yet
146
+ * UNPARSEABLE fell straight through it: the reader threw mid-parse, nothing caught it, and
147
+ * the process hit Node's default exit 1 — the code reserved for a genuine OUT-OF-BALANCE
148
+ * verdict. A crashed invocation was therefore indistinguishable, by exit code, from an
149
+ * honest disagreement (3F-1742). An input the tool cannot read is a USAGE fault: exit 2,
150
+ * sibling to a missing path (@SCN-CLI-002) and an unrecognised flag (@SCN-CLI-009).
151
+ *
152
+ * PURE and `@unit`-testable like its sibling guards: reads the parsed args + an injected
153
+ * `read` fn (no fs), returns the FIRST supplied-but-unparseable `(flag, path, failure)` as
154
+ * a typed error naming all three — routed to stderr + exit 2 by `runBalance` — else null.
155
+ * An omitted flag is not an error, and an unreadable file (a race after the existence
156
+ * check) reports as the same input fault rather than escaping as a crash.
157
+ */
158
+ export function checkInputsParse(args, read) {
159
+ for (const flag of JSON_INPUT_FLAGS) {
160
+ const path = args[flag];
161
+ if (path === undefined)
162
+ continue;
163
+ try {
164
+ JSON.parse(read(path));
165
+ }
166
+ catch (err) {
167
+ // Both a parse failure and an unreadable file are the SAME input fault. Anything
168
+ // that escapes this catch lands on Node's default exit 1 — the collision itself.
169
+ const failure = err instanceof Error ? err.message : String(err);
170
+ return `The --${flag} report is not valid JSON: ${path} (${failure})`;
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ /**
176
+ * Guard the readability of `--render-from-json` at the CLI edge (@SCN-CLI-015). The
177
+ * render-from-saved-JSON branch RETURNS before the input guards ever run, so it honoured
178
+ * NEITHER: a missing path crashed to `ENOENT` and a malformed file to `SyntaxError`, both
179
+ * landing on Node's default exit 1 — the out-of-balance code (3F-1764).
180
+ *
181
+ * The missing-path case was a live violation of @SCN-CLI-002's own shipped principle: a
182
+ * supplied input path that does not exist is a hard error at exit 2, enforced for the five
183
+ * evidence flags and not for this one. The CLI shipped a guard that half-honoured its Rule.
184
+ *
185
+ * MISSING and UNPARSEABLE are two Examples of one boundary rule — an input the tool cannot
186
+ * read is a usage error, never a verdict — so one guard covers both. PURE and
187
+ * `@unit`-testable like its siblings: the parsed args + an injected `read` (no fs), which
188
+ * throws for an absent/unreadable path exactly as `readFileSync` does. An OMITTED flag is
189
+ * not an error (the ordinary reconcile path).
190
+ */
191
+ export function checkRenderFromJson(args, read) {
192
+ const path = args["render-from-json"];
193
+ if (path === undefined)
194
+ return null;
195
+ try {
196
+ JSON.parse(read(path));
197
+ }
198
+ catch (err) {
199
+ // MISSING (read throws ENOENT) and UNPARSEABLE (JSON.parse throws) are the same input
200
+ // fault, caught in the same place. Anything escaping here lands on Node's default
201
+ // exit 1 — the out-of-balance code — which is the collision this closes.
202
+ const failure = err instanceof Error ? err.message : String(err);
203
+ return `The --render-from-json saved report could not be read: ${path} (${failure})`;
204
+ }
205
+ return null;
206
+ }
207
+ /**
208
+ * The registry-bounded flag check at the CLI edge (@SCN-CLI-009). `balance` accepts ONLY
209
+ * the flags its per-command registry scope LISTS (by `name` or `alias`); an unregistered
210
+ * flag — a typo like `--strcit`, a stray `--bogus` — is a usage error, NOT silently
211
+ * swallowed the way the old permissive `parseArgs` did. This is the structural teeth behind
212
+ * "accepted = registry = help": a flag can't affect behaviour without a registry entry (and
213
+ * so, by CLI-008's guard, without appearing in help).
214
+ *
215
+ * PURE and `@unit`-testable like `checkLegacyJson` / `checkInputsExist`: reads the parsed
216
+ * args (keys are the `--`-stripped flag names) + the registry, returns the FIRST unregistered
217
+ * flag as a typed usage error naming it WITH its dashes — routed to stderr + exit 2 by
218
+ * `runBalance` (per 3F-1414's `0` balanced / `1` out-of-balance / `2` usage taxonomy),
219
+ * never exit 1, never a silent reconcile — else null. The global `--store`/`--run-id`
220
+ * modifiers never reach here: the top-level dispatcher (`parseHostInvocation`) consumes them
221
+ * BEFORE `balance` is dispatched, so the bounded parser sees only the command's own args.
222
+ */
223
+ export function checkUnknownFlags(args, registry) {
224
+ // The accepted set: every balance-scope flag name AND alias, with the leading dashes
225
+ // stripped so it compares against parseArgs's `--`-stripped keys (e.g. "--features" and
226
+ // "-h" become "features" and "h"). Built from the registry alone — the single source — so
227
+ // "accepted = registry" holds by construction.
228
+ const balance = registry.commands.find((command) => command.name === "balance");
229
+ const accepted = new Set();
230
+ for (const flag of balance?.flags ?? []) {
231
+ accepted.add(flag.name.replace(/^-+/, ""));
232
+ for (const alias of flag.aliases ?? [])
233
+ accepted.add(alias.replace(/^-+/, ""));
234
+ }
235
+ // The FIRST parsed flag not in the accepted set is the reported error (Object.keys is
236
+ // argv order, so this is deterministic). The message names the flag WITH its dashes.
237
+ for (const key of Object.keys(args)) {
238
+ if (!accepted.has(key)) {
239
+ return `Unknown flag --${key} for 'spec-controller balance'. Run 'spec-controller balance --help' to see the accepted flags.`;
240
+ }
241
+ }
242
+ return null;
243
+ }
244
+ export function runBalance(argv) {
245
+ // Answer `--help`/`-h` from the command/flag registry (@SCN-CLI-006) BEFORE any
246
+ // parse or reconcile: render the balance command's per-command scope (a line per
247
+ // registered flag — name + arg + description), leave process.exitCode at its default 0,
248
+ // and return. This intercepts --help so it never falls through parseArgs as a stray flag
249
+ // while balance runs anyway. The registry is the single source, so a flag that exists is
250
+ // a flag that appears in help — no hand-kept list to drift.
251
+ if (argv.includes("--help") || argv.includes("-h")) {
252
+ process.stdout.write(renderBalanceHelp(cliRegistry) + "\n");
253
+ return;
254
+ }
255
+ // Capture the run-start instant up front — the run provenance's UTC timestamp,
256
+ // threaded through ctx.runStart to the renderers.
257
+ const runStart = new Date();
258
+ const args = parseArgs(argv);
259
+ // Legacy-`--json` guard (@SCN-FMT-006): the earlier `--json <path>` flag is
260
+ // removed. Reject it fast — before any reconcile or write — with a migration hint to
261
+ // `--format json:<path>`, so the removed flag never silently no-ops and drops output. The
262
+ // guard's typed error is routed to stderr + a non-zero exit, exactly as planOutput's error
263
+ // is below (the shared typed-error surface).
264
+ const legacyJsonError = checkLegacyJson(args);
265
+ if (legacyJsonError) {
266
+ process.stderr.write(legacyJsonError + "\n");
267
+ // Usage/config error → exit 2 (the enumerated usage code, @SCN-CLI-002), distinct
268
+ // from the out-of-balance verdict code 1 (@SCN-CLI-004): an out-of-balance run must
269
+ // never masquerade as a usage error, nor the reverse.
270
+ process.exit(2);
271
+ }
272
+ // Registry-bounded parse (@SCN-CLI-009): `balance` accepts ONLY the flags its registry
273
+ // scope LISTS. An unregistered flag — a typo `--strcit`, a stray `--bogus` — is a
274
+ // USAGE error: name it on stderr, set process.exitCode = 2 (the enumerated usage code,
275
+ // 3F-1414's 0/1/2 taxonomy — never 1, never a silent swallow), and RETURN before any
276
+ // reconcile. Runs AFTER the legacy-`--json` guard so `--json` keeps its specific migration
277
+ // hint; the global `--store`/`--run-id` modifiers were consumed by the dispatcher before
278
+ // balance, so they never reach here. This is the structural teeth behind "accepted =
279
+ // registry = help" — a flag can't affect behaviour without a registry entry.
280
+ const unknownFlagError = checkUnknownFlags(args, cliRegistry);
281
+ if (unknownFlagError) {
282
+ process.stderr.write(unknownFlagError + "\n");
283
+ process.exitCode = 2;
284
+ return;
285
+ }
286
+ // Render-from-saved-JSON mode (@SCN-RMD-007): regenerate the human markdown from a
287
+ // banked spec-reconciliation.json ALONE — no reconcile, no live inputs, no RunContext. Reads
288
+ // the file, JSON.parses it back into the whole-document report model, renders the markdown via
289
+ // renderReport, and writes it to stdout (the same trailing-newline convention as the writer's
290
+ // stdout path). The load-bearing proof that the JSON IS the model — returns before any reconcile.
291
+ // Saved-report READABILITY guard (@SCN-CLI-015). This branch RETURNS before the input
292
+ // guards below ever run, so it honoured NEITHER: a missing path crashed to ENOENT and a
293
+ // malformed file to SyntaxError, both landing on Node's default exit 1 — the
294
+ // out-of-balance code. The missing-path case was a live violation of @SCN-CLI-002's own
295
+ // shipped principle, which the CLI enforced for the five evidence flags and not for this
296
+ // one. The guard therefore runs INSIDE this branch, ahead of the read (3F-1764).
297
+ const unreadableSavedReportError = checkRenderFromJson(args, (path) => readFileSync(path, "utf8"));
298
+ if (unreadableSavedReportError) {
299
+ process.stderr.write(unreadableSavedReportError + "\n");
300
+ process.exit(2);
301
+ }
302
+ const rehydratePath = args["render-from-json"];
303
+ if (rehydratePath !== undefined) {
304
+ const savedReport = readFileSync(rehydratePath, "utf8");
305
+ process.stdout.write(renderReportFromJson(savedReport) + "\n");
306
+ return;
307
+ }
308
+ // Input-existence guard (@SCN-CLI-002): a supplied input-file/dir flag pointing at a
309
+ // non-existent path is a HARD ERROR — halt BEFORE reconciling, with a typed message
310
+ // naming the flag + path, never the silent zero-evidence all-red report a mistyped
311
+ // path used to produce (the dogfooding fault). Routed to stderr + a non-zero exit,
312
+ // exactly as the legacy-`--json` guard above (the shared typed-error surface). An
313
+ // omitted flag is legitimately optional and passes through untouched.
314
+ const missingInputError = checkInputsExist(args, existsSync);
315
+ if (missingInputError) {
316
+ process.stderr.write(missingInputError + "\n");
317
+ // Usage/input error → exit 2 (@SCN-CLI-002), distinct from the out-of-balance
318
+ // verdict code 1 (@SCN-CLI-004).
319
+ process.exit(2);
320
+ }
321
+ // Input-READABILITY guard (@SCN-CLI-014): existence is not enough. A report that is
322
+ // PRESENT but UNPARSEABLE (`--vitest /dev/null`, truncated JSON, non-JSON) used to throw
323
+ // mid-parse inside reconcileWithCensus, uncaught — and Node's default exit code is 1, the
324
+ // code reserved for a genuine OUT-OF-BALANCE verdict (@SCN-CLI-004). So a tool that
325
+ // CRASHED before reconciling was indistinguishable, by exit code, from an honest
326
+ // disagreement (3F-1742). Runs immediately after the existence guard and BEFORE any
327
+ // reconcile, routing to the same stderr + exit 2 lane: an input the tool cannot read is a
328
+ // usage fault, sibling to a missing path and an unrecognised flag — never a verdict.
329
+ const unparseableInputError = checkInputsParse(args, (path) => readFileSync(path, "utf8"));
330
+ if (unparseableInputError) {
331
+ process.stderr.write(unparseableInputError + "\n");
332
+ process.exit(2);
333
+ }
334
+ // The FEATURE-CORPUS integrity checkpoint (@SCN-CLI-016, 3F-1767). The feature corpus is an
335
+ // input too — the ledger's DEBIT side — and it was read by a hand-rolled TAG-LINE SCAN that
336
+ // never consulted a parser. So a malformed tag line simply stopped being a tag line and its
337
+ // scenario CEASED TO EXIST: one stray character deleted a failing row and turned an
338
+ // out-of-balance run (exit 1) into a clean BALANCED (exit 0). A dropped scenario cannot even
339
+ // reconcile `missing`, because it was never RAISED to be missing — the run read cleaner than
340
+ // reality, the exact false-green this tool exists to prevent.
341
+ //
342
+ // Validated with the REAL Gherkin parser (cucumber's own), so a file the tool would reconcile is
343
+ // at least a file cucumber would RUN. A file it rejects is an input we CANNOT READ: exit 2,
344
+ // halting BEFORE any reconcile, naming the file and the parse failure — the same Rule as
345
+ // @SCN-CLI-014 and @SCN-CLI-015 ("an input the tool cannot read is a usage error, never a
346
+ // verdict"), now on the obligation side. Never a verdict, and never a green.
347
+ //
348
+ // The corpus is EXTRACTED from the same real AST (@SCN-LDG-020), so what the tool reconciles is
349
+ // what cucumber runs — Feature/Rule tag inheritance included. This guard decides whether the file
350
+ // can be read at all; parseScenarios then reads it exactly as cucumber would.
351
+ const featureParseErrors = featureCorpusParseErrors(args["features"]);
352
+ if (featureParseErrors.length > 0) {
353
+ for (const { file, message } of featureParseErrors) {
354
+ process.stderr.write(`The --features corpus contains a feature file that could not be parsed: ${file}\n${message}\n`);
355
+ }
356
+ process.exit(2);
357
+ }
358
+ // AMBIGUOUS-CORPUS guard (@SCN-LDG-020's guardrail, 3F-1775). An @SCN identifies exactly ONE
359
+ // scenario — that is the whole basis of attributing evidence to it. Tag inheritance makes a
360
+ // DUPLICATE reachable: an @SCN hoisted to a Feature:/Rule: is inherited by every scenario
361
+ // beneath it (cucumber does this, so we do). One passing test would then mark EVERY scenario
362
+ // sharing that id `balanced` — including scenarios nothing proves — a BALANCED-WHEN-BROKEN run
363
+ // at exit 0. A new silent false-green, introduced by the very fix that closed the last one.
364
+ //
365
+ // So the corpus is REFUSED, not reconciled: the tool cannot know which scenario a citing test
366
+ // proves, and any verdict over it would be a guess. Same Rule as @SCN-CLI-014/015/016 — an input
367
+ // the tool cannot read is a usage error, never a verdict.
368
+ const duplicateScnIds = featureCorpusDuplicateScnIds(args["features"]);
369
+ if (duplicateScnIds.length > 0) {
370
+ process.stderr.write(`The --features corpus names the same @SCN on more than one scenario: ` +
371
+ `${duplicateScnIds.join(", ")}.\n` +
372
+ `An @SCN identifies exactly one scenario — evidence citing a duplicated id cannot be ` +
373
+ `attributed, so no verdict over this corpus would be trustworthy. (An @SCN on a Feature: ` +
374
+ `or Rule: line is inherited by every scenario beneath it — name it on the scenario.)\n`);
375
+ process.exit(2);
376
+ }
377
+ const inputs = reconcileInputsOf(args);
378
+ // Reconcile the obligation-vs-observation evidence into the Ledger. The runtime evidence-observation
379
+ // views (Test volume, the evidence grid) are FOLDS the renderers derive off this Ledger's posted
380
+ // credits (@SCN-RPT-010 / 3F-2103 — the census is retired), so they cannot drift from the
381
+ // reconciler's verdict: they ARE the same rows. `withRuntimeObservations: true` (below) tells the
382
+ // renderers the axis is in play.
383
+ const balance = runReconcile(inputs);
384
+ // Read the scenario-corpus census alongside the balance (@SCN-RPT-008) —
385
+ // the raw-vs-parsed @SCN-occurrence delta over the same discovered feature corpus.
386
+ // A sibling to the balance, threaded to the renderers via writeOutputs like ctx.
387
+ const evidenceObligations = readEvidenceObligations(args["features"]);
388
+ // The static-check kind-counts are a FOLD OVER THE LEDGER (@SCN-USG-001 / 3F-2118) — the
389
+ // renderers count the balance's own posted static-check credits (`staticObservationTally`), so
390
+ // they cannot drift from the reconciler's verdict: they ARE the same rows. The parallel static
391
+ // census is RETIRED; `withStaticChecks: true` (below) tells the renderers the axis is in play.
392
+ // Read the target's feature-code→slug map alongside the balance (@SCN-RPT-006)
393
+ // — the by-feature cut's full-name labels. The 5th sibling, threaded to the MARKDOWN
394
+ // renderer via writeOutputs only (markdown-only; the by-feature cut isn't on JSON).
395
+ const featureNames = readFeatureNames(args["features"]);
396
+ // Build the run provenance ONCE (project design §1) — a sibling RunContext, the
397
+ // Ledger staying pure — reusing the existing runStart. The target is the
398
+ // operator's --target VERBATIM (unknown-target on omit; Fork A — no
399
+ // resolution, no basename guessing). Source SHA via the read-only target read, with
400
+ // --source-sha as the override (resolveSourceSha).
401
+ const ctx = {
402
+ target: sourceTarget(args),
403
+ inputs,
404
+ runStart,
405
+ sourceSha: resolveSourceSha({
406
+ sourceSha: args["source-sha"],
407
+ targetDir: args["features"] !== undefined ? dirname(args["features"]) : undefined,
408
+ }),
409
+ // The TARGET repo root the recorded input paths are made portable against (fixed to
410
+ // the target root, not the run cwd, by @SCN-RPT-016) — the SAME
411
+ // root readEvidenceObservations passes to countRuntimeEvidenceObservationKinds, derived from
412
+ // --features, so a cross-repo reconcile records inputs relative (`features`) rather
413
+ // than leaking the target's absolute path.
414
+ root: resolveTargetRoot({ features: inputs.features }),
415
+ // The TOOL's own identity (@SCN-RPT-026, 3F-1916) — which spec-controller produced
416
+ // this run, distinct from the target's sourceSha. Read from the tool's OWN dir, never
417
+ // cwd (which is the target). One call, spread into the ctx; the same identity feeds
418
+ // the run.yaml manifest.
419
+ ...resolveToolIdentity(),
420
+ };
421
+ // Plan the outputs from the --format specs (no flag → md→stdout) and write them via
422
+ // the writer — the pure plan / impure writer split (@SCN-FMT-001). The plan
423
+ // parse+validate is pure; the writer is the only IO for the emitted report. This is
424
+ // the SOLE output path: the tool emits via --format and holds no storage opinion — the
425
+ // legacy runs/<target>/<ISO>/ auto-archive was removed with @SCN-RUN-001, the
426
+ // caller now routes stored runs (spec-controller's run-management layer).
427
+ const planResult = planOutput(collectFormatSpecs(argv));
428
+ if (!planResult.ok) {
429
+ process.stderr.write(planResult.error + "\n");
430
+ // Usage/config error (bad --format spec) → exit 2 (@SCN-CLI-002), distinct from the
431
+ // out-of-balance verdict code 1 (@SCN-CLI-004).
432
+ process.exit(2);
433
+ }
434
+ writeOutputs(planResult.plan, { balance, ctx, evidenceObligations, withRuntimeObservations: true, withStaticChecks: true, featureNames });
435
+ // The CI gate (@SCN-CLI-004): `balance` is a GATE, not just a reporter. AFTER the report
436
+ // is written in full, map the whole-run verdict to the process exit code. Out-of-balance
437
+ // (ANY reconciling item anywhere: an out-of-balance scenario OR a suspense-row item such
438
+ // as a no-evidence-obligation static-check watermelon) sets process.exitCode = 1 and RETURNS — set,
439
+ // never `process.exit(1)`, so the buffered report is flushed intact rather than truncated
440
+ // mid-write. It exits EXACTLY 1 so it reds a CI build and can't masquerade as the usage
441
+ // error (2, above). An ENUMERATED status, never a count (8-bit wrap → false pass). A
442
+ // balanced run leaves process.exitCode at its default 0 and returns.
443
+ //
444
+ // The --strict escalation (@SCN-CLI-011): Pending Items are run-neutral by DEFAULT
445
+ // (@SCN-PND-008 — they never red a build), but `--strict` is the OPT-IN that fails an
446
+ // OTHERWISE-BALANCED run carrying them, exiting the DISTINCT code 3 (taxonomy 0 balanced /
447
+ // 1 out-of-balance / 2 usage / 3 pending-under-strict — a Pending Item is not out-of-balance,
448
+ // so it must NEVER share code 1). Out-of-balance DOMINATES: the escalation is checked ONLY in
449
+ // the `isBalanced` branch, so an out-of-balance run exits 1 regardless of --strict. Like the
450
+ // CLI-004 gate this SETS process.exitCode (never process.exit) AFTER writeOutputs, so the
451
+ // buffered report is flushed intact. `--strict` is a registry-LISTED boolean, recorded by
452
+ // parseArgs as the string "true" — presence is what matters, so `!== undefined` reads it.
453
+ //
454
+ // The UNSOUND pre-emption (@SCN-CLI-012): a run carrying BROKEN evidence — a proving suite
455
+ // that failed to COLLECT, so the behaviour was never exercised — exits the DISTINCT code 4
456
+ // (taxonomy 0 balanced / 1 out-of-balance / 2 usage / 3 pending-under-strict / 4 unsound).
457
+ // Exit 1 says "the ledger disagrees"; exit 4 says "the ledger could not be TRUSTED to
458
+ // disagree" — you cannot trust a reconciliation whose evidence never materialised, so
459
+ // unsound DOMINATES.
460
+ //
461
+ // THE ORDER IS LOAD-BEARING, NOT COSMETIC. A broken run is ALWAYS out-of-balance: its
462
+ // `broken` item sits on the SUSPENSE row, so `isBalanced` is already false. Check it AFTER
463
+ // `isBalanced` and the branch is UNREACHABLE — every broken run would exit 1 and the
464
+ // collection failure would stay masked as an honest disagreement (the AWTY dogfood fault,
465
+ // 3F-1642). So `hasBrokenEvidence` is checked FIRST, PRE-EMPTING the balance verdict it
466
+ // would otherwise be swallowed by. @SCN-CLI-004 is a BYSTANDER, not an Update: its fixture
467
+ // is a SOUND out-of-balance run (no broken item), so it falls through this branch and still
468
+ // exits exactly 1 — the pre-emption fires only when evidence genuinely failed to collect.
469
+ const strict = args["strict"] !== undefined;
470
+ if (hasBrokenEvidence(balance.ledger)) {
471
+ process.exitCode = 4;
472
+ }
473
+ else if (!isBalanced(balance.ledger)) {
474
+ process.exitCode = 1;
475
+ }
476
+ else if (strict && hasPendingItems(balance.ledger)) {
477
+ process.exitCode = 3;
478
+ }
479
+ }
480
+ // Only run the balance command when this module is executed directly
481
+ // (`tsx cli-balance/cli.ts`), NOT when imported by the top-level `spec-controller`
482
+ // bin dispatcher or by a test that exercises the pure edge guards.
483
+ const invokedDirectly = process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`;
484
+ if (invokedDirectly)
485
+ runBalance(process.argv.slice(2));
486
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-balance/cli.ts"],"names":[],"mappings":"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"}