spec-controller 0.1.14 → 0.1.16

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 (2) hide show
  1. package/README.md +44 -40
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # spec-controller
2
2
 
3
- **One command, `balance`, that reconciles the behaviour your scenarios name against the evidence that actually ran.**
3
+ **One command, `balance`, that checks the behaviour your scenarios name against the evidence that actually ran.**
4
4
 
5
- You hand it your Gherkin scenarios and the reports your tools already produce — a Vitest report, a Cucumber report, your CI run's own record of what its steps did — and it prints one table: which scenarios have living evidence, which do not, and which evidence ran that no scenario asked for.
5
+ You give it your Gherkin scenarios and the reports your tools already write: a Vitest report, a Cucumber report, and your CI run's own record of what its steps did. It prints one table that shows which scenarios have living evidence, which do not, and which evidence ran that no scenario asked for.
6
6
 
7
- It runs nothing itself. It reads what your pipeline already wrote.
7
+ It runs nothing itself. It only reads what your pipeline already wrote.
8
8
 
9
9
  Requires Node ≥ 22. ESM only.
10
10
 
@@ -14,7 +14,7 @@ Requires Node ≥ 22. ESM only.
14
14
  npm install -g spec-controller
15
15
  ```
16
16
 
17
- Or without installing anything:
17
+ Or run it without installing anything:
18
18
 
19
19
  ```
20
20
  npx spec-controller balance --help
@@ -22,29 +22,32 @@ npx spec-controller balance --help
22
22
 
23
23
  ## Your first run
24
24
 
25
- Point it at your scenarios and give it nothing else:
25
+ Point it at your scenarios and nothing else:
26
26
 
27
27
  ```
28
28
  spec-controller balance --features ./features
29
29
  ```
30
30
 
31
- Every scenario comes back as a row whether or not any evidence exists for it, so on this first run the ones you have supplied no reports for read **missing** and the run comes back out-of-balance. That is the shape of the tool rather than a fault in your repository: the reconciliation starts from what you specified, not from what a tool happened to find, and there is no input for which the right answer is silence.
31
+ Every scenario gets a row, whether or not any evidence exists for it. On this first run you have supplied no reports, so every scenario that names evidence reads **missing** and the run is out-of-balance. That is how the tool works, not a fault in your repository: it starts from what you specified, not from what a tool happened to find, and no input makes silence the right answer.
32
32
 
33
- **`balance` fails your build out of the box.** An out-of-balance run exits `1`, so the first run above reds any pipeline you put it in. That is a deliberate departure from two tools it sits beside: Stryker's `thresholds.break` defaults to `null` (*"never let your build fail"*), and the Sonar scanner exits `0` whatever its quality gate says unless you set `sonar.qualitygate.wait`. Both make you opt in to gating. `balance` makes you opt out.
33
+ **`balance` fails your build by default.** An out-of-balance run exits `1`, so the run above reds any pipeline you put it in. Two tools it sits beside do the opposite: Stryker's `thresholds.break` defaults to `null` (*"never let your build fail"*), and the Sonar scanner exits `0` whatever its quality gate says unless you set `sonar.qualitygate.wait`. Both make you opt in to gating. `balance` makes you opt out.
34
34
 
35
35
  ### Report first, gate later
36
36
 
37
- To bring it into a pipeline you already have without redding it on day one, add `--exit-zero`:
37
+ To add it to an existing pipeline without redding it on day one, add `--exit-zero`:
38
38
 
39
39
  ```
40
40
  spec-controller balance --features ./features --vitest ./vitest-report.json --exit-zero
41
41
  ```
42
42
 
43
- The report is byte for byte the one the gate prints: the same rows, the same out-of-balance count, and the same headline naming the code the gate would give. Only the exit status moves. Out-of-balance and scenario code errors (`1`), and pending under `--strict` (`3`), exit `0`. Usage (`2`), unsound (`4`) and nothing to reconcile (`5`) still fail, because each means the run could not give a verdict at all, and a flag that suppresses verdicts does not suppress that.
43
+ The report is byte for byte the one the gate prints: the same rows, the same out-of-balance count, and the same headline naming the code the gate would give. Only the exit status changes:
44
44
 
45
- Work the out-of-balance rows down, then delete `--exit-zero`, and the gate is on. Don't wrap the step in `continue-on-error` or `|| true` instead: those swallow the codes that mean the run could not be trusted, and they leave nothing in the command to say the gate is off.
45
+ - Out-of-balance and scenario code errors (`1`), and pending under `--strict` (`3`), exit `0`.
46
+ - Usage (`2`), unsound (`4`) and nothing to reconcile (`5`) still fail. Each means the run could not reach a verdict at all, and a flag that suppresses verdicts does not suppress that.
46
47
 
47
- Now give it the evidence:
48
+ Work the out-of-balance rows down, then delete `--exit-zero`, and the gate is on. Do not wrap the step in `continue-on-error` or `|| true` instead. Those swallow the codes that mean the run could not be trusted, and they leave nothing in the command to show the gate is off.
49
+
50
+ ### Give it the evidence
48
51
 
49
52
  ```
50
53
  spec-controller balance \
@@ -53,9 +56,9 @@ spec-controller balance \
53
56
  --cucumber ./cucumber-report.json
54
57
  ```
55
58
 
56
- A scenario tagged `@unit` names an obligation — a unit test must exist and pass — and the evidence posts back by citing the scenario's `@SCN` code in its test name. Where the two agree the row is **balanced**. Where they do not, the row says which way: evidence missing, evidence failing, or evidence that cites a scenario nobody wrote.
59
+ A scenario tagged `@unit` names an obligation: a unit test must exist and pass. The evidence answers it by citing the scenario's `@SCN` code in its test name. Where the two agree, the row is **balanced**. Where they do not, the row says how: evidence missing, evidence failing, or evidence that cites a scenario nobody wrote.
57
60
 
58
- ## What the exit code tells your pipeline
61
+ ## Exit codes
59
62
 
60
63
  | Exit | What it says |
61
64
  | --- | --- |
@@ -66,32 +69,33 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
66
69
  | `4` | unsound — a proving suite failed to collect, so the ledger could not be trusted to disagree |
67
70
  | `5` | nothing to reconcile — no scenario was carried and no evidence was produced, so the run had no basis for a verdict |
68
71
 
69
- The whole report is written before the code is set, so a failing run still tells you why.
72
+ The whole report is written before the exit code is set, so a failing run still tells you why.
73
+
74
+ **Design your pipeline for new codes.** The list is open at the top. A state that is none of the above gets a **new** code rather than being folded into the nearest one; the highest is `5`. So **treat an unrecognised non-zero code as a failure you have not seen before**, not as a synonym for `1`. Matching `1` exactly is not affected when a code is added. Treating any non-zero code as a red build is already correct, only less precise about why.
75
+
76
+ **Node's default exit code is never one of these.** A run that cannot reconcile ends on one of its own codes, never by falling out of the process: a crash that exits `1` would look, by status, exactly like an honest disagreement.
77
+
78
+ **An input it cannot read is a usage error (`2`)**, never a verdict about your code. This covers:
79
+
80
+ - a mistyped path, or a path it is not permitted to read;
81
+ - a file where a directory belongs, or the reverse;
82
+ - an unparseable report, or a report that parses but is not in the shape its runner writes;
83
+ - a saved reconciliation it cannot render;
84
+ - a corpus the Gherkin parser rejects.
85
+
86
+ The refusal names the input, the path, and what was wrong with it. A well-formed report that holds nothing, such as a Vitest run that matched no test file, is read, not refused.
87
+
88
+ **An output location it cannot write is also `2`**: a `--format` path that is read-only, a directory, under a regular file or in a directory that does not exist, or a `--store` root the run directory cannot be created under. It is refused before any report is printed, naming the location and the operating system's reason.
70
89
 
71
- `spec-controller allocate`, which prints the next scenario code for a feature part, reaches no verdict: it exits `0` when it answers and `2` for a usage error or an unknown part. A file or directory it cannot read, whether it reads it itself or through git, is a usage error: it is refused, naming the path and the reason, and no code is given.
90
+ `--exit-zero` changes `1` and `3` to `0` and leaves every other code alone (see *Report first, gate later*).
72
91
 
73
- `--exit-zero` moves `1` and `3` to `0` and leaves every other code alone: see *Report first, gate later*.
92
+ ### Re-rendering a saved report
74
93
 
75
- `balance --render-from-json <saved.json>` re-renders a saved report and exits the code its verdict names, from the same table: a saved run that failed fails again when it is read back. A report saved under `--strict` records it, so its re-render exits `3` too, and `--strict` or `--exit-zero` beside the re-render work as they do live. Any other flag beside it is refused at `2`, because a re-render reads no live input and writes only to stdout.
94
+ `balance --render-from-json <saved.json>` re-renders a saved report from the same table and exits with the code its verdict names, so a saved run that failed fails again when read back. A report saved under `--strict` records that, so its re-render exits `3` too. `--strict` and `--exit-zero` work beside the re-render as they do on a live run. Any other flag beside it is refused with `2`, because a re-render reads no live input and writes only to stdout.
76
95
 
77
- **Node's default is never one of these.** A run that cannot reconcile ends on an enumerated code of
78
- its own rather than falling out of the process — a crash that exits `1` would be indistinguishable,
79
- by status, from an honest disagreement. **An input the tool cannot read is part of `2`**: a mistyped
80
- path, a path it is not permitted to read, a file where a directory belongs or the reverse, an
81
- unparseable report, a report that parses but is not in the shape its runner writes, a saved
82
- reconciliation it cannot render, or a corpus the Gherkin parser rejects are all the invocation
83
- being wrong, never a verdict about your code. The refusal names the input, the path, and what was
84
- wrong with it. A well-formed report that holds nothing, such as a Vitest run that matched no test
85
- file, is read, not refused. **So is an output location it cannot write**: a `--format` path that is
86
- read-only, a directory, under a regular file or in a directory that does not exist, and a
87
- `--store` root the run directory cannot be created under, are refused at `2`, naming the location and the operating system's reason, before any report is printed.
96
+ ### `allocate`
88
97
 
89
- **The enumeration is open at the top, and that is the one thing to design your pipeline around.** A
90
- state that is genuinely none of the above gets a **new** code rather than being folded into the
91
- nearest one — `5` is the most recent, added when a run over a target that carried no scenario and
92
- produced nothing was found reporting `0`. So **treat an unrecognised non-zero code as a failure you
93
- have not seen before**, not as a synonym for `1`. Matching `1` exactly is unaffected by a code being
94
- added; treating *non-zero* as a red build is already correct, and only imprecise about why.
98
+ `spec-controller allocate` prints the next scenario code for a feature part. It reaches no verdict: it exits `0` when it answers, and `2` for a usage error or an unknown part. A file or directory it cannot read, directly or through git, is a usage error: it is refused, naming the path and the reason, and no code is given.
95
99
 
96
100
  ## On CI
97
101
 
@@ -102,18 +106,18 @@ spec-controller balance --features ./features \
102
106
  --format json:reconciliation.json
103
107
  ```
104
108
 
105
- `--format` is repeatable and takes an optional `:<path>`. On GitHub Actions, writing Markdown to `$GITHUB_STEP_SUMMARY` puts the reconciliation on the run's summary page — the report is a table and that is the channel GitHub provides for one. No action, no plugin, no permission.
109
+ `--format` can be repeated and takes an optional `:<path>`. On GitHub Actions, writing Markdown to `$GITHUB_STEP_SUMMARY` puts the reconciliation on the run's summary page. The report is a table, and that is the channel GitHub provides for one. No action, no plugin, no permission.
106
110
 
107
- `spec-controller balance --help` lists every flag with a line each.
111
+ `spec-controller balance --help` lists every flag, one line each.
108
112
 
109
113
  ## Where to go next
110
114
 
111
- - **Full flags, worked examples and CI wiring** — [`doc/how_to_use.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/how_to_use.md)
112
- - **Why `0.x` promises nothing, and what changes at `1.0`** — [`doc/compatibility.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/compatibility.md)
113
- - **Building your own tooling on the engine instead of running a command** — [`@3f-consulting/spec-controller-core`](https://www.npmjs.com/package/@3f-consulting/spec-controller-core), the same reconciliation logic as a pure library with no CLI and no I/O.
115
+ - **Full flags, worked examples and CI wiring**: [`doc/how_to_use.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/how_to_use.md)
116
+ - **Why `0.x` promises nothing, and what changes at `1.0`**: [`doc/compatibility.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/compatibility.md)
117
+ - **Building your own tooling on the engine instead of running a command**: [`@3f-consulting/spec-controller-core`](https://www.npmjs.com/package/@3f-consulting/spec-controller-core), the same reconciliation logic as a pure library with no CLI and no I/O.
114
118
 
115
119
  Source, issues and history: [github.com/3f-consulting/spec-controller](https://github.com/3f-consulting/spec-controller).
116
120
 
117
121
  ## Licence
118
122
 
119
- MIT — see `LICENSE`, which travels in this package. © 2026 3F Consulting Ltd.
123
+ MIT — see `LICENSE` in the repository. © 2026 3F Consulting Ltd.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-controller",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Reconcile the behaviour your scenarios name against the evidence that actually ran — the deterministic `balance` CLI.",
5
5
  "keywords": [
6
6
  "reconciliation",
@@ -26,7 +26,7 @@
26
26
  "@cucumber/messages": "^34.0.1",
27
27
  "fast-xml-parser": "^5.11.2",
28
28
  "yaml": "^2.9.0",
29
- "@3f-consulting/spec-controller-core": "0.1.14"
29
+ "@3f-consulting/spec-controller-core": "0.1.16"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"