spec-controller 0.1.0-alpha.3 → 0.1.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +24 -4
  2. package/dist/cli-allocate/cli.d.ts +2 -0
  3. package/dist/cli-allocate/cli.d.ts.map +1 -0
  4. package/dist/cli-allocate/cli.js +45 -0
  5. package/dist/cli-allocate/cli.js.map +1 -0
  6. package/dist/cli-allocate/readHistory.d.ts +21 -0
  7. package/dist/cli-allocate/readHistory.d.ts.map +1 -0
  8. package/dist/cli-allocate/readHistory.js +97 -0
  9. package/dist/cli-allocate/readHistory.js.map +1 -0
  10. package/dist/cli-allocate/readParts.d.ts +3 -0
  11. package/dist/cli-allocate/readParts.d.ts.map +1 -0
  12. package/dist/cli-allocate/readParts.js +25 -0
  13. package/dist/cli-allocate/readParts.js.map +1 -0
  14. package/dist/cli-allocate/readTree.d.ts +17 -0
  15. package/dist/cli-allocate/readTree.d.ts.map +1 -0
  16. package/dist/cli-allocate/readTree.js +63 -0
  17. package/dist/cli-allocate/readTree.js.map +1 -0
  18. package/dist/cli-allocate/request.d.ts +11 -0
  19. package/dist/cli-allocate/request.d.ts.map +1 -0
  20. package/dist/cli-allocate/request.js +29 -0
  21. package/dist/cli-allocate/request.js.map +1 -0
  22. package/dist/cli-allocate/stderr.d.ts +14 -0
  23. package/dist/cli-allocate/stderr.d.ts.map +1 -0
  24. package/dist/cli-allocate/stderr.js +33 -0
  25. package/dist/cli-allocate/stderr.js.map +1 -0
  26. package/dist/cli-args.d.ts +70 -14
  27. package/dist/cli-args.d.ts.map +1 -1
  28. package/dist/cli-args.js +239 -18
  29. package/dist/cli-args.js.map +1 -1
  30. package/dist/cli-balance/cli.d.ts +18 -1
  31. package/dist/cli-balance/cli.d.ts.map +1 -1
  32. package/dist/cli-balance/cli.js +185 -161
  33. package/dist/cli-balance/cli.js.map +1 -1
  34. package/dist/cli-registry.d.ts +28 -0
  35. package/dist/cli-registry.d.ts.map +1 -1
  36. package/dist/cli-registry.js +39 -34
  37. package/dist/cli-registry.js.map +1 -1
  38. package/dist/cli.d.ts +4 -5
  39. package/dist/cli.d.ts.map +1 -1
  40. package/dist/cli.js +26 -12
  41. package/dist/cli.js.map +1 -1
  42. package/dist/deferralTags.d.ts +2 -2
  43. package/dist/deferralTags.js +2 -2
  44. package/dist/host.d.ts +25 -1
  45. package/dist/host.d.ts.map +1 -1
  46. package/dist/host.js +119 -15
  47. package/dist/host.js.map +1 -1
  48. package/dist/ingest/gherkinValidation.d.ts +9 -12
  49. package/dist/ingest/gherkinValidation.d.ts.map +1 -1
  50. package/dist/ingest/gherkinValidation.js +9 -12
  51. package/dist/ingest/gherkinValidation.js.map +1 -1
  52. package/dist/ingest/ingestQualityChecks.d.ts +3 -49
  53. package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
  54. package/dist/ingest/ingestQualityChecks.js +27 -120
  55. package/dist/ingest/ingestQualityChecks.js.map +1 -1
  56. package/dist/ingest/ingestScenarios.d.ts +8 -14
  57. package/dist/ingest/ingestScenarios.d.ts.map +1 -1
  58. package/dist/ingest/ingestScenarios.js +32 -55
  59. package/dist/ingest/ingestScenarios.js.map +1 -1
  60. package/package.json +2 -7
  61. package/dist/corpus/cli.d.ts +0 -34
  62. package/dist/corpus/cli.d.ts.map +0 -1
  63. package/dist/corpus/cli.js +0 -128
  64. package/dist/corpus/cli.js.map +0 -1
  65. package/dist/mutation-ratchet/index.d.ts +0 -46
  66. package/dist/mutation-ratchet/index.d.ts.map +0 -1
  67. package/dist/mutation-ratchet/index.js +0 -46
  68. package/dist/mutation-ratchet/index.js.map +0 -1
  69. package/dist/mutation-ratchet/record.d.ts +0 -178
  70. package/dist/mutation-ratchet/record.d.ts.map +0 -1
  71. package/dist/mutation-ratchet/record.js +0 -314
  72. package/dist/mutation-ratchet/record.js.map +0 -1
  73. package/dist/mutation-ratchet/report.d.ts +0 -109
  74. package/dist/mutation-ratchet/report.d.ts.map +0 -1
  75. package/dist/mutation-ratchet/report.js +0 -156
  76. package/dist/mutation-ratchet/report.js.map +0 -1
package/README.md CHANGED
@@ -11,15 +11,17 @@ Requires Node ≥ 22. ESM only.
11
11
  ## Install
12
12
 
13
13
  ```
14
- npm install -g spec-controller
14
+ npm install -g spec-controller@alpha
15
15
  ```
16
16
 
17
17
  Or without installing anything:
18
18
 
19
19
  ```
20
- npx spec-controller balance --help
20
+ npx spec-controller@alpha balance --help
21
21
  ```
22
22
 
23
+ `@alpha` is not optional until `0.1.0` publishes: a bare install resolves `latest`, which npm set to `0.1.0-alpha.1` on the first publish, and that version has no `--ci-steps` and no scenario code errors.
24
+
23
25
  ## Your first run
24
26
 
25
27
  Point it at your scenarios and give it nothing else:
@@ -30,6 +32,20 @@ spec-controller balance --features ./features
30
32
 
31
33
  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.
32
34
 
35
+ **`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.
36
+
37
+ ### Report first, gate later
38
+
39
+ To bring it into a pipeline you already have without redding it on day one, add `--exit-zero`:
40
+
41
+ ```
42
+ spec-controller balance --features ./features --vitest ./vitest-report.json --exit-zero
43
+ ```
44
+
45
+ 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.
46
+
47
+ 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.
48
+
33
49
  Now give it the evidence:
34
50
 
35
51
  ```
@@ -46,7 +62,7 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
46
62
  | Exit | What it says |
47
63
  | --- | --- |
48
64
  | `0` | balanced — every obligation has evidence, and it holds |
49
- | `1` | out-of-balance — the two records disagree, and the report says where |
65
+ | `1` | out-of-balance — the two records disagree, and the report says where; or a scenario code is misfiled, listed under `## Scenario code errors` |
50
66
  | `2` | usage — the invocation was wrong |
51
67
  | `3` | balanced, but carrying Pending Items, and you asked for `--strict` |
52
68
  | `4` | unsound — a proving suite failed to collect, so the ledger could not be trusted to disagree |
@@ -54,6 +70,10 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
54
70
 
55
71
  The whole report is written before the code is set, so a failing run still tells you why.
56
72
 
73
+ `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.
74
+
75
+ `--exit-zero` moves `1` and `3` to `0` and leaves every other code alone: see *Report first, gate later*.
76
+
57
77
  **Node's default is never one of these.** A run that cannot reconcile ends on an enumerated code of
58
78
  its own rather than falling out of the process — a crash that exits `1` would be indistinguishable,
59
79
  by status, from an honest disagreement. **An input the tool cannot read is part of `2`**: a mistyped
@@ -83,7 +103,7 @@ spec-controller balance --features ./features \
83
103
  ## Where to go next
84
104
 
85
105
  - **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)
86
- - **Bringing a check you already have under the reconciliation** — [`doc/converting-a-fitness-check.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/converting-a-fitness-check.md)
106
+ - **Bringing a check you already have under the reconciliation** — [`doc/adopting-fitness-checks.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/adopting-fitness-checks.md)
87
107
  - **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)
88
108
  - **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.
89
109
 
@@ -0,0 +1,2 @@
1
+ export declare function runAllocate(argv: string[]): void;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"AAaA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyBhD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `spec-controller allocate <FFF>` — the next scenario code for a feature part, alone on stdout.
3
+ */
4
+ import { join } from "node:path";
5
+ import { nextScnCode } from "@3f-consulting/spec-controller-core";
6
+ import { cliRegistry, renderCommandHelp } from "../cli-registry.js";
7
+ import { resolveToolIdentity } from "../run-management/resolveRunInputs.js";
8
+ import { readHeaderCodes } from "./readParts.js";
9
+ import { readRequest } from "./request.js";
10
+ import { historyBoundaryOf, readHistory, repositoryTop } from "./readHistory.js";
11
+ import { readTree, walkTree } from "./readTree.js";
12
+ import { alreadyHeadedNote, highestSeenLine, historyBoundaryLine, unknownPartRefusal } from "./stderr.js";
13
+ export function runAllocate(argv) {
14
+ if (argv.includes("--help") || argv.includes("-h")) {
15
+ process.stdout.write(renderCommandHelp(cliRegistry, "allocate") + "\n");
16
+ return;
17
+ }
18
+ const request = readRequest(argv, cliRegistry, resolveToolIdentity().toolVersion);
19
+ if ("usageError" in request) {
20
+ process.stderr.write(request.usageError + "\n");
21
+ process.exit(2);
22
+ }
23
+ const { part, isNew } = request;
24
+ const cwd = process.cwd();
25
+ const top = repositoryTop(cwd);
26
+ const headerCodes = readHeaderCodes(join(top ?? cwd, "features"));
27
+ const answer = nextScnCode({ part, headerCodes, isNew }, sightingsFrom(cwd, part, top !== null));
28
+ if (answer.kind === "unknown-part") {
29
+ process.stderr.write(unknownPartRefusal(answer) + "\n");
30
+ process.exit(2);
31
+ }
32
+ const boundaryLine = historyBoundaryLine(historyBoundaryOf(cwd), answer);
33
+ if (boundaryLine !== null)
34
+ process.stderr.write(boundaryLine + "\n");
35
+ process.stderr.write(highestSeenLine(answer) + "\n");
36
+ const note = alreadyHeadedNote(answer);
37
+ if (note !== null)
38
+ process.stderr.write(note + "\n");
39
+ process.stdout.write(answer.code + "\n");
40
+ }
41
+ /** The tree and its history through git, or the tree alone by a walk where there is no repository. */
42
+ function sightingsFrom(cwd, part, inRepository) {
43
+ return inRepository ? [...readTree(cwd, part), ...readHistory(cwd, part)] : walkTree(cwd, part);
44
+ }
45
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ScnSighting } from "@3f-consulting/spec-controller-core";
2
+ /** How much history the read could see: all of it, none (no repository), or back to a shallow clone's edge. */
3
+ export type HistoryBoundary = {
4
+ kind: "complete";
5
+ } | {
6
+ kind: "not-a-repository";
7
+ } | {
8
+ kind: "shallow";
9
+ before: string;
10
+ };
11
+ /** The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`. */
12
+ export declare function readHistory(cwd: string, part: string): ScnSighting[];
13
+ /** The top of the repository `cwd` stands in, or null outside one. */
14
+ export declare function repositoryTop(cwd: string): string | null;
15
+ /** Whether `cwd` stands in a git repository. */
16
+ export declare function isGitRepository(cwd: string): boolean;
17
+ /** How far back the history read can see from `cwd`. */
18
+ export declare function historyBoundaryOf(cwd: string): HistoryBoundary;
19
+ /** The added and removed lines of `git log -p` output, each with where it stands. */
20
+ export declare function diffLineSightings(logOutput: string): ScnSighting[];
21
+ //# sourceMappingURL=readHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readHistory.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAEvE,+GAA+G;AAC/G,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAQxH,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAOpE;AAQD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAM9D;AAaD,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAQlE"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The history half of the allocator's read: the added and removed lines of every reachable commit
3
+ * that touched the part, so a code that was deleted is still counted and never reissued.
4
+ *
5
+ * Only diff lines count, never commit messages. History is read so that a deleted scenario's number
6
+ * is not reissued; a code that only ever appeared in a message was never a scenario, and a message
7
+ * cannot be unwritten, so one stray mention would move the answer for good. The format asks git for
8
+ * each commit's abbreviated hash and nothing else, so a sighting can name the commit that carried it.
9
+ *
10
+ * As with the tree read, git is handed a fixed string only.
11
+ */
12
+ import { spawnSync } from "node:child_process";
13
+ import { readFileSync } from "node:fs";
14
+ /** A long history's patches can be large; the default buffer would truncate them. */
15
+ const MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
16
+ /** The marker the format puts before each commit's hash, a byte no patch line starts with. */
17
+ const COMMIT_MARKER = "\u0001";
18
+ /** The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`. */
19
+ export function readHistory(cwd, part) {
20
+ const result = spawnSync("git", ["log", "--all", "-p", "-U0", "--no-color", "--no-ext-diff", "--format=%x01%h", "-G", `SCN-${part}-`], { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT_BYTES });
21
+ return diffLineSightings(result.stdout);
22
+ }
23
+ /** Git's answer in `cwd`, trimmed, or null when git refuses (outside a repository, or no git at all). */
24
+ function gitAnswer(cwd, args) {
25
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
26
+ return result.status === 0 ? result.stdout.trim() : null;
27
+ }
28
+ /** The top of the repository `cwd` stands in, or null outside one. */
29
+ export function repositoryTop(cwd) {
30
+ return gitAnswer(cwd, ["rev-parse", "--show-toplevel"]);
31
+ }
32
+ /** Whether `cwd` stands in a git repository. */
33
+ export function isGitRepository(cwd) {
34
+ return gitAnswer(cwd, ["rev-parse", "--git-dir"]) !== null;
35
+ }
36
+ /** How far back the history read can see from `cwd`. */
37
+ export function historyBoundaryOf(cwd) {
38
+ if (!isGitRepository(cwd))
39
+ return { kind: "not-a-repository" };
40
+ if (gitAnswer(cwd, ["rev-parse", "--is-shallow-repository"]) !== "true")
41
+ return { kind: "complete" };
42
+ const shallowFile = gitAnswer(cwd, ["rev-parse", "--git-path", "shallow"]) ?? "";
43
+ const edge = readFileSync(shallowFile.startsWith("/") ? shallowFile : `${cwd}/${shallowFile}`, "utf8").split("\n")[0] ?? "";
44
+ return { kind: "shallow", before: gitAnswer(cwd, ["rev-parse", "--short", edge]) ?? edge };
45
+ }
46
+ /** The added and removed lines of `git log -p` output, each with where it stands. */
47
+ export function diffLineSightings(logOutput) {
48
+ const at = { inFileHeader: false, oldPath: "", newPath: "", oldLine: 0, newLine: 0 };
49
+ const sightings = [];
50
+ for (const line of logOutput.split("\n")) {
51
+ const sighting = readLine(line, at);
52
+ if (sighting !== undefined)
53
+ sightings.push(sighting);
54
+ }
55
+ return sightings;
56
+ }
57
+ /** Move the position past one line of output, and return the sighting it is, if it is one. */
58
+ function readLine(line, at) {
59
+ if (line.startsWith(COMMIT_MARKER))
60
+ at.commit = line.slice(COMMIT_MARKER.length);
61
+ else if (line.startsWith("diff --git "))
62
+ at.inFileHeader = true;
63
+ else if (at.inFileHeader)
64
+ readFileHeader(line, at);
65
+ else if (line.startsWith("-"))
66
+ return sightingAt(line, at.oldPath, at.oldLine++, at.commit);
67
+ else if (line.startsWith("+"))
68
+ return sightingAt(line, at.newPath, at.newLine++, at.commit);
69
+ else if (line.startsWith("@@ "))
70
+ readHunkHeader(line, at);
71
+ return undefined;
72
+ }
73
+ /** A file header's lines: `---`/`+++` name the two sides, and the first hunk ends the header. */
74
+ function readFileHeader(line, at) {
75
+ if (line.startsWith("--- "))
76
+ at.oldPath = pathOf(line);
77
+ else if (line.startsWith("+++ "))
78
+ at.newPath = pathOf(line);
79
+ else if (line.startsWith("@@ ")) {
80
+ at.inFileHeader = false;
81
+ readHunkHeader(line, at);
82
+ }
83
+ }
84
+ /** The path a `---`/`+++` header names, without git's `a/`/`b/` prefix. */
85
+ function pathOf(header) {
86
+ return header.slice(4).replace(/^[ab]\//, "");
87
+ }
88
+ /** A hunk header, `@@ -<old>[,n] +<new>[,n] @@`, gives each side's first line. */
89
+ function readHunkHeader(line, at) {
90
+ const match = /^@@ -(\d+)(?:,\d+)? \+(\d+)/.exec(line);
91
+ at.oldLine = Number(match?.[1] ?? 0);
92
+ at.newLine = Number(match?.[2] ?? 0);
93
+ }
94
+ function sightingAt(text, path, line, commit) {
95
+ return commit === undefined ? { text, path, line } : { text, path, line, commit };
96
+ }
97
+ //# sourceMappingURL=readHistory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readHistory.js","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,qFAAqF;AACrF,MAAM,gBAAgB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,8FAA8F;AAC9F,MAAM,aAAa,GAAG,QAAQ,CAAC;AAE/B,+FAA+F;AAC/F,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY;IACnD,MAAM,MAAM,GAAG,SAAS,CACtB,KAAK,EACL,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,IAAI,EAAE,OAAO,IAAI,GAAG,CAAC,EACrG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,CACvD,CAAC;IACF,OAAO,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,yGAAyG;AACzG,SAAS,SAAS,CAAC,GAAW,EAAE,IAAuB;IACrD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,KAAK,IAAI,CAAC;AAC7D,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC/D,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,yBAAyB,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IACrG,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5H,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;AAC7F,CAAC;AAaD,qFAAqF;AACrF,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,EAAE,GAAkB,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACpG,MAAM,SAAS,GAAkB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8FAA8F;AAC9F,SAAS,QAAQ,CAAC,IAAY,EAAE,EAAiB;IAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;SAC5E,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,EAAE,CAAC,YAAY,GAAG,IAAI,CAAC;SAC3D,IAAI,EAAE,CAAC,YAAY;QAAE,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;SAC9C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;SACvF,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;SACvF,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iGAAiG;AACjG,SAAS,cAAc,CAAC,IAAY,EAAE,EAAiB;IACrD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;SAClD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;SACvD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,YAAY,GAAG,KAAK,CAAC;QACxB,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,kFAAkF;AAClF,SAAS,cAAc,CAAC,IAAY,EAAE,EAAiB;IACrD,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,IAAY,EAAE,IAAY,EAAE,MAA0B;IACtF,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACpF,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** The header codes of the feature files in `featuresDir`; none when there is no such directory. */
2
+ export declare function readHeaderCodes(featuresDir: string): string[];
3
+ //# sourceMappingURL=readParts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readParts.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readParts.ts"],"names":[],"mappings":"AAQA,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The parts the corpus carries: the header codes of its feature files, read where `balance` reads its
3
+ * corpus by default. A part only history or a test fixture carries is not one of them.
4
+ */
5
+ import { readdirSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { extractFeatureCode } from "../ingest/ingestScenarios.js";
8
+ /** The header codes of the feature files in `featuresDir`; none when there is no such directory. */
9
+ export function readHeaderCodes(featuresDir) {
10
+ return featureFilesIn(featuresDir).flatMap((name) => {
11
+ const code = extractFeatureCode(readFileSync(join(featuresDir, name), "utf8"));
12
+ return code === null ? [] : [code];
13
+ });
14
+ }
15
+ function featureFilesIn(dir) {
16
+ try {
17
+ return readdirSync(dir).filter((name) => name.endsWith(".feature"));
18
+ }
19
+ catch (err) {
20
+ if (err.code === "ENOENT")
21
+ return [];
22
+ throw err;
23
+ }
24
+ }
25
+ //# sourceMappingURL=readParts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readParts.js","sourceRoot":"","sources":["../../src/cli-allocate/readParts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,kBAAkB,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/E,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ScnSighting } from "@3f-consulting/spec-controller-core";
2
+ /**
3
+ * Every line in the whole repository's tree carrying `SCN-<part>-`, with its path from the top, wherever
4
+ * in the repository it is asked from: `:/` is the top, since git grep otherwise reads only the cwd's subtree.
5
+ * Untracked files the repository does not ignore count too: an author allocates because they have just
6
+ * written a scenario, and it is not committed yet.
7
+ */
8
+ export declare function readTree(cwd: string, part: string): ScnSighting[];
9
+ /**
10
+ * Whether a walk without git enters a directory: never a package store, never a hidden directory.
11
+ * An installed package carries its own codes (spec-controller's own dist carries hundreds), which
12
+ * would retire a consumer's numbers with a package's.
13
+ */
14
+ export declare function isWalkedDirectory(name: string): boolean;
15
+ /** Every line carrying `SCN-<part>-` in the text files under `root`, found by walking it without git. */
16
+ export declare function walkTree(root: string, part: string): ScnSighting[];
17
+ //# sourceMappingURL=readTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readTree.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAKvE;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAOjE;AASD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,yGAAyG;AACzG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAElE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The tree half of the allocator's read: every line of every text file that mentions the part.
3
+ *
4
+ * Git is handed a fixed string only. Which words on those lines are scenario codes is core's
5
+ * question, answered with the shared grammar, because git's own regex dialect has no `\d` and
6
+ * matches nothing when given one, silently.
7
+ */
8
+ import { spawnSync } from "node:child_process";
9
+ import { readdirSync, readFileSync } from "node:fs";
10
+ import { join, relative, sep } from "node:path";
11
+ /** Git's output can be large on a big repository; the default buffer would truncate it. */
12
+ const MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
13
+ /**
14
+ * Every line in the whole repository's tree carrying `SCN-<part>-`, with its path from the top, wherever
15
+ * in the repository it is asked from: `:/` is the top, since git grep otherwise reads only the cwd's subtree.
16
+ * Untracked files the repository does not ignore count too: an author allocates because they have just
17
+ * written a scenario, and it is not committed yet.
18
+ */
19
+ export function readTree(cwd, part) {
20
+ const result = spawnSync("git", ["grep", "-z", "-n", "-I", "--full-name", "--untracked", "-F", "-e", `SCN-${part}-`, "--", ":/"], {
21
+ cwd,
22
+ encoding: "utf8",
23
+ maxBuffer: MAX_OUTPUT_BYTES,
24
+ });
25
+ return result.stdout.split("\n").flatMap(sightingOf);
26
+ }
27
+ /** One `path\0line\0text` record of `git grep -z -n`, or nothing for a blank line. */
28
+ function sightingOf(record) {
29
+ const [path, line, text] = record.split("\0");
30
+ if (path === undefined || line === undefined || text === undefined)
31
+ return [];
32
+ return [{ path, line: Number(line), text }];
33
+ }
34
+ /**
35
+ * Whether a walk without git enters a directory: never a package store, never a hidden directory.
36
+ * An installed package carries its own codes (spec-controller's own dist carries hundreds), which
37
+ * would retire a consumer's numbers with a package's.
38
+ */
39
+ export function isWalkedDirectory(name) {
40
+ return name !== "node_modules" && !name.startsWith(".");
41
+ }
42
+ /** Every line carrying `SCN-<part>-` in the text files under `root`, found by walking it without git. */
43
+ export function walkTree(root, part) {
44
+ return filesUnder(root).flatMap((file) => sightingsIn(root, file, `SCN-${part}-`));
45
+ }
46
+ /** Every file under `dir`, entering only the directories a walk enters. */
47
+ function filesUnder(dir) {
48
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
49
+ const path = join(dir, entry.name);
50
+ if (entry.isDirectory())
51
+ return isWalkedDirectory(entry.name) ? filesUnder(path) : [];
52
+ return entry.isFile() ? [path] : [];
53
+ });
54
+ }
55
+ /** The lines of a text file that carry `needle`; a binary file carries none. */
56
+ function sightingsIn(root, file, needle) {
57
+ const text = readFileSync(file, "utf8");
58
+ if (text.includes("\0"))
59
+ return [];
60
+ const path = relative(root, file).split(sep).join("/");
61
+ return text.split("\n").flatMap((line, i) => (line.includes(needle) ? [{ path, line: i + 1, text: line }] : []));
62
+ }
63
+ //# sourceMappingURL=readTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readTree.js","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { CliRegistry } from "../cli-registry.js";
2
+ /** A well-formed request: the part, and whether it is asked for as new. */
3
+ export interface AllocateRequest {
4
+ readonly part: string;
5
+ readonly isNew: boolean;
6
+ }
7
+ /** The request `argv` makes, or the usage error it is, worded for stderr. */
8
+ export declare function readRequest(argv: readonly string[], registry: CliRegistry, toolVersion: string): AllocateRequest | {
9
+ readonly usageError: string;
10
+ };
11
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/request.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,WAAW,EACrB,WAAW,EAAE,MAAM,GAClB,eAAe,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAInD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What an `allocate` invocation asks for, read against the command's registry scope, or the usage
3
+ * error it is. A usage error is worded as one, so it is never taken for an unknown part.
4
+ */
5
+ import { isFeaturePart } from "@3f-consulting/spec-controller-core";
6
+ import { checkArgvShape, checkMisplacedHostModifiers, checkUnknownFlags, parseArgs, positionalArguments, } from "../cli-args.js";
7
+ /** The request `argv` makes, or the usage error it is, worded for stderr. */
8
+ export function readRequest(argv, registry, toolVersion) {
9
+ const why = argvRefusal(argv, registry, toolVersion) ?? partRefusal(positionalArguments(argv, registry, "allocate")[0]);
10
+ if (why !== null)
11
+ return { usageError: `spec-controller allocate: usage error — ${why}` };
12
+ return { part: positionalArguments(argv, registry, "allocate")[0] ?? "", isNew: argv.includes("--new") };
13
+ }
14
+ /** The first refusal the argv earns against the registry, the same checks `balance` makes, in its order. */
15
+ function argvRefusal(argv, registry, toolVersion) {
16
+ const args = parseArgs(argv);
17
+ return (checkMisplacedHostModifiers(args, registry, "allocate") ??
18
+ checkArgvShape(argv, registry, "allocate") ??
19
+ checkUnknownFlags(args, registry, "allocate", toolVersion));
20
+ }
21
+ /** The refusal for the part given, or null when it is a feature part in canon's form. */
22
+ function partRefusal(part) {
23
+ if (part === undefined)
24
+ return "no feature part was given: 'spec-controller allocate <FFF>' takes one, a feature file's header code.";
25
+ if (isFeaturePart(part))
26
+ return null;
27
+ return `'${part}' is not a feature part in canon's form: two to five characters, each A-Z or 0-9.`;
28
+ }
29
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/cli-allocate/request.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,iBAAiB,EACjB,SAAS,EACT,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AASxB,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CACzB,IAAuB,EACvB,QAAqB,EACrB,WAAmB;IAEnB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,IAAI,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxH,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,UAAU,EAAE,2CAA2C,GAAG,EAAE,EAAE,CAAC;IAC1F,OAAO,EAAE,IAAI,EAAE,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;AAC3G,CAAC;AAED,4GAA4G;AAC5G,SAAS,WAAW,CAAC,IAAuB,EAAE,QAAqB,EAAE,WAAmB;IACtF,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CACL,2BAA2B,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC;QACvD,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC;QAC1C,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAC3D,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,WAAW,CAAC,IAAwB;IAC3C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,sGAAsG,CAAC;IACtI,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,IAAI,IAAI,mFAAmF,CAAC;AACrG,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * What the allocator says beside its answer, on stderr, so stdout carries the code alone.
3
+ */
4
+ import { type Allocation, type UnknownPart } from "@3f-consulting/spec-controller-core";
5
+ import type { HistoryBoundary } from "./readHistory.js";
6
+ /** What was left unread, and what that means for the answer; nothing when history was read whole. */
7
+ export declare function historyBoundaryLine(boundary: HistoryBoundary, allocation: Allocation): string | null;
8
+ /** Where the highest code was seen: a file and line, and the commit when only history carries it. */
9
+ export declare function highestSeenLine(allocation: Allocation): string;
10
+ /** The refusal for a part no feature file's header carries: the part, and the parts that are. */
11
+ export declare function unknownPartRefusal(refusal: UnknownPart): string;
12
+ /** The note for a part asked for as new that a header already carries; nothing otherwise. */
13
+ export declare function alreadyHeadedNote(allocation: Allocation): string | null;
14
+ //# sourceMappingURL=stderr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stderr.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/stderr.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAiB,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAKpG;AAED,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAK9D;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAM/D;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGvE"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What the allocator says beside its answer, on stderr, so stdout carries the code alone.
3
+ */
4
+ import { featureCodeOf } from "@3f-consulting/spec-controller-core";
5
+ /** What was left unread, and what that means for the answer; nothing when history was read whole. */
6
+ export function historyBoundaryLine(boundary, allocation) {
7
+ if (boundary.kind === "complete")
8
+ return null;
9
+ const unread = boundary.kind === "shallow" ? `shallow clone — history before ${boundary.before} unread` : "not a git repository — none read";
10
+ const risk = allocation.highest === null ? `a code of ${featureCodeOf(allocation.code) ?? ""}` : `a code above ${allocation.highest.code}`;
11
+ return `history: ${unread}; ${risk} may have been used before`;
12
+ }
13
+ /** Where the highest code was seen: a file and line, and the commit when only history carries it. */
14
+ export function highestSeenLine(allocation) {
15
+ const highest = allocation.highest;
16
+ if (highest === null)
17
+ return "highest seen: none";
18
+ const { path, line, commit } = highest.sighting;
19
+ return `highest seen: ${highest.code}, ${path}:${line}${commit === undefined ? "" : ` in ${commit}`}`;
20
+ }
21
+ /** The refusal for a part no feature file's header carries: the part, and the parts that are. */
22
+ export function unknownPartRefusal(refusal) {
23
+ const parts = refusal.headerCodes.length === 0 ? "none" : refusal.headerCodes.join(", ");
24
+ return (`spec-controller allocate: unknown part — no feature file's header carries ${refusal.part}.\n` +
25
+ `The parts this corpus's feature files carry: ${parts}.`);
26
+ }
27
+ /** The note for a part asked for as new that a header already carries; nothing otherwise. */
28
+ export function alreadyHeadedNote(allocation) {
29
+ if (allocation.alreadyHeaded !== true)
30
+ return null;
31
+ return `note: a feature file's header already carries ${featureCodeOf(allocation.code) ?? ""}, so --new was not needed`;
32
+ }
33
+ //# sourceMappingURL=stderr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stderr.js","sourceRoot":"","sources":["../../src/cli-allocate/stderr.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,aAAa,EAAqC,MAAM,qCAAqC,CAAC;AAGvG,qGAAqG;AACrG,MAAM,UAAU,mBAAmB,CAAC,QAAyB,EAAE,UAAsB;IACnF,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kCAAkC,QAAQ,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,kCAAkC,CAAC;IAC7I,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC3I,OAAO,YAAY,MAAM,KAAK,IAAI,4BAA4B,CAAC;AACjE,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,eAAe,CAAC,UAAsB;IACpD,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;IACnC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,oBAAoB,CAAC;IAClD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChD,OAAO,iBAAiB,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,EAAE,CAAC;AACxG,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,kBAAkB,CAAC,OAAoB;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzF,OAAO,CACL,6EAA6E,OAAO,CAAC,IAAI,KAAK;QAC9F,gDAAgD,KAAK,GAAG,CACzD,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAAC,UAAsB;IACtD,IAAI,UAAU,CAAC,aAAa,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,iDAAiD,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,CAAC;AAC1H,CAAC"}