spec-controller 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cli-balance/cli.d.ts +87 -0
- package/dist/cli-balance/cli.d.ts.map +1 -0
- package/dist/cli-balance/cli.js +486 -0
- package/dist/cli-balance/cli.js.map +1 -0
- package/dist/cli-balance/emit/format.d.ts +60 -0
- package/dist/cli-balance/emit/format.d.ts.map +1 -0
- package/dist/cli-balance/emit/format.js +90 -0
- package/dist/cli-balance/emit/format.js.map +1 -0
- package/dist/cli-balance/emit/writer.d.ts +45 -0
- package/dist/cli-balance/emit/writer.d.ts.map +1 -0
- package/dist/cli-balance/emit/writer.js +48 -0
- package/dist/cli-balance/emit/writer.js.map +1 -0
- package/dist/cli-registry.d.ts +68 -0
- package/dist/cli-registry.d.ts.map +1 -0
- package/dist/cli-registry.js +181 -0
- package/dist/cli-registry.js.map +1 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/deferralTags.d.ts +16 -0
- package/dist/deferralTags.d.ts.map +1 -0
- package/dist/deferralTags.js +22 -0
- package/dist/deferralTags.js.map +1 -0
- package/dist/host.d.ts +50 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +69 -0
- package/dist/host.js.map +1 -0
- package/dist/ingest/ciSourcePaths.d.ts +46 -0
- package/dist/ingest/ciSourcePaths.d.ts.map +1 -0
- package/dist/ingest/ciSourcePaths.js +58 -0
- package/dist/ingest/ciSourcePaths.js.map +1 -0
- package/dist/ingest/gherkinValidation.d.ts +70 -0
- package/dist/ingest/gherkinValidation.d.ts.map +1 -0
- package/dist/ingest/gherkinValidation.js +85 -0
- package/dist/ingest/gherkinValidation.js.map +1 -0
- package/dist/ingest/ingestQualityChecks.d.ts +119 -0
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -0
- package/dist/ingest/ingestQualityChecks.js +331 -0
- package/dist/ingest/ingestQualityChecks.js.map +1 -0
- package/dist/ingest/ingestScenarios.d.ts +52 -0
- package/dist/ingest/ingestScenarios.d.ts.map +1 -0
- package/dist/ingest/ingestScenarios.js +119 -0
- package/dist/ingest/ingestScenarios.js.map +1 -0
- package/dist/mutation-ratchet/index.d.ts +48 -0
- package/dist/mutation-ratchet/index.d.ts.map +1 -0
- package/dist/mutation-ratchet/index.js +48 -0
- package/dist/mutation-ratchet/index.js.map +1 -0
- package/dist/mutation-ratchet/ratchet.d.ts +129 -0
- package/dist/mutation-ratchet/ratchet.d.ts.map +1 -0
- package/dist/mutation-ratchet/ratchet.js +222 -0
- package/dist/mutation-ratchet/ratchet.js.map +1 -0
- package/dist/mutation-ratchet/ratchetCli.d.ts +57 -0
- package/dist/mutation-ratchet/ratchetCli.d.ts.map +1 -0
- package/dist/mutation-ratchet/ratchetCli.js +139 -0
- package/dist/mutation-ratchet/ratchetCli.js.map +1 -0
- package/dist/mutation-ratchet/reconcile.d.ts +82 -0
- package/dist/mutation-ratchet/reconcile.d.ts.map +1 -0
- package/dist/mutation-ratchet/reconcile.js +67 -0
- package/dist/mutation-ratchet/reconcile.js.map +1 -0
- package/dist/mutation-ratchet/record.d.ts +210 -0
- package/dist/mutation-ratchet/record.d.ts.map +1 -0
- package/dist/mutation-ratchet/record.js +330 -0
- package/dist/mutation-ratchet/record.js.map +1 -0
- package/dist/mutation-ratchet/report.d.ts +83 -0
- package/dist/mutation-ratchet/report.d.ts.map +1 -0
- package/dist/mutation-ratchet/report.js +148 -0
- package/dist/mutation-ratchet/report.js.map +1 -0
- package/dist/mutation-ratchet/verdict.d.ts +177 -0
- package/dist/mutation-ratchet/verdict.d.ts.map +1 -0
- package/dist/mutation-ratchet/verdict.js +387 -0
- package/dist/mutation-ratchet/verdict.js.map +1 -0
- package/dist/run-management/keptRun.d.ts +156 -0
- package/dist/run-management/keptRun.d.ts.map +1 -0
- package/dist/run-management/keptRun.js +133 -0
- package/dist/run-management/keptRun.js.map +1 -0
- package/dist/run-management/resolveRunInputs.d.ts +70 -0
- package/dist/run-management/resolveRunInputs.d.ts.map +1 -0
- package/dist/run-management/resolveRunInputs.js +144 -0
- package/dist/run-management/resolveRunInputs.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The KEPT-RUN scaffold — the thin app-layer that turns a bare tool invocation into a
|
|
3
|
+
* STORED run (the run-management layer). A kept run is a consultancy / product-dev artefact (NOT CI):
|
|
4
|
+
* it makes a per-run directory under a caller-INJECTED store root and routes the tool's
|
|
5
|
+
* `--format` outputs into it. The tool holds no storage opinion (the `--format` surface); this
|
|
6
|
+
* scaffold adds only the directory + the routing.
|
|
7
|
+
*
|
|
8
|
+
* Split by the three-layer architecture:
|
|
9
|
+
* - PURE planner (`planKeptRun`): given `{ storeRoot, target, runId }`, yields the run
|
|
10
|
+
* dir `<storeRoot>/<target>/<runId>/` and the two file-dest paths — no IO, proven
|
|
11
|
+
* `@unit`.
|
|
12
|
+
* - IMPURE runner (`executeKeptRun`): `mkdir -p` the run dir, then route the tool's
|
|
13
|
+
* `--format` outputs into the two files by handing an injected tool-invoker the two
|
|
14
|
+
* file-dest `--format` specs. The ONLY IO, proven `@integration` (the real on-disk
|
|
15
|
+
* files under a temp root + the repo untouched — never a planner re-run).
|
|
16
|
+
*
|
|
17
|
+
* The store root is an INJECTED parameter — never hardcoded — so the scaffold is
|
|
18
|
+
* location-agnostic (proven against a temp root; the repo is never written). RMG-001 is
|
|
19
|
+
* the dir + the routed outputs; the `run.yaml` stamp (RMG-002) and the `--run-id` mnemonic
|
|
20
|
+
* override (RMG-003) land in later sub-issues.
|
|
21
|
+
*
|
|
22
|
+
* Parent: @SCN-RMG-001 — the run-management layer.
|
|
23
|
+
*/
|
|
24
|
+
/** The two artefact filenames a kept run routes the tool's outputs into. */
|
|
25
|
+
export declare const RECONCILIATION_DOCUMENT = "spec-reconciliation.md";
|
|
26
|
+
/**
|
|
27
|
+
* The machine artefact filename (@SCN-RMD-001) — renamed `ledger.json` →
|
|
28
|
+
* `spec-reconciliation.json` (parity with `spec-reconciliation.md`): it is the machine form
|
|
29
|
+
* of the WHOLE reconciliation document, not just the ledger.
|
|
30
|
+
*/
|
|
31
|
+
export declare const RECONCILIATION_JSON = "spec-reconciliation.json";
|
|
32
|
+
/** The manifest filename a kept run stamps in the run dir (RMG-002). */
|
|
33
|
+
export declare const RUN_MANIFEST = "run.yaml";
|
|
34
|
+
/**
|
|
35
|
+
* The run.yaml schema version this scaffold stamps. Bumped 1 → 2 by 3F-1916, which added
|
|
36
|
+
* the tool identity (`tool_sha` / `tool_version`) — so a reader can tell a run that
|
|
37
|
+
* predates tool-identity recording (schema 1) from one that should carry it (schema 2).
|
|
38
|
+
* run.yaml is write-only (no production reader), so the bump breaks no read path.
|
|
39
|
+
*/
|
|
40
|
+
export declare const RUN_MANIFEST_SCHEMA_VERSION = 2;
|
|
41
|
+
/** The stored-run modes (run.yaml `mode`). */
|
|
42
|
+
export type RunMode = "balance" | "analyse" | "migrate";
|
|
43
|
+
/**
|
|
44
|
+
* The manifest-only metadata a kept run stamps that the path spec (`KeptRunSpec`) does not
|
|
45
|
+
* carry — the source SHA, the run mode, and the ISO date.
|
|
46
|
+
*/
|
|
47
|
+
export interface RunManifestMeta {
|
|
48
|
+
readonly sourceSha: string;
|
|
49
|
+
/**
|
|
50
|
+
* The TOOL's own git SHA — which spec-controller produced this run (3F-1916), distinct
|
|
51
|
+
* from the target's `sourceSha`. A string ("unknown" when the tool is not a git
|
|
52
|
+
* checkout), mirroring `sourceSha`'s own "unknown" fallback — so run.yaml is internally
|
|
53
|
+
* consistent (both facts use the same absent-marker, never a bare null).
|
|
54
|
+
*/
|
|
55
|
+
readonly toolSha: string;
|
|
56
|
+
/** The tool's package version — always present. */
|
|
57
|
+
readonly toolVersion: string;
|
|
58
|
+
readonly mode: RunMode;
|
|
59
|
+
readonly date: string;
|
|
60
|
+
}
|
|
61
|
+
/** The full manifest input: the path spec's run-id + target, plus the manifest-only meta. */
|
|
62
|
+
export interface RunManifestInput extends RunManifestMeta {
|
|
63
|
+
readonly runId: string;
|
|
64
|
+
readonly target: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The run.yaml manifest — the decided light field set. `purpose`/`links` default EMPTY (the ad-hoc default; slugs are
|
|
68
|
+
* added by operator hand-edit afterward). `category`/`use_case` are NOT stamped (`purpose`
|
|
69
|
+
* subsumes them). Keys are listed in the decided stamp order.
|
|
70
|
+
*/
|
|
71
|
+
export interface RunManifest {
|
|
72
|
+
readonly schema_version: number;
|
|
73
|
+
readonly run_id: string;
|
|
74
|
+
readonly target: string;
|
|
75
|
+
readonly source_sha: string;
|
|
76
|
+
/** The TOOL's identity (3F-1916) — which spec-controller produced the run. */
|
|
77
|
+
readonly tool_sha: string;
|
|
78
|
+
readonly tool_version: string;
|
|
79
|
+
readonly mode: RunMode;
|
|
80
|
+
readonly date: string;
|
|
81
|
+
readonly purpose: readonly string[];
|
|
82
|
+
readonly links: readonly string[];
|
|
83
|
+
readonly artefacts: {
|
|
84
|
+
readonly document: string;
|
|
85
|
+
readonly reconciliation: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** The kept-run inputs: the injected store root, the target, and the run-id. */
|
|
89
|
+
export interface KeptRunSpec {
|
|
90
|
+
readonly storeRoot: string;
|
|
91
|
+
readonly target: string;
|
|
92
|
+
readonly runId: string;
|
|
93
|
+
}
|
|
94
|
+
/** The planned run: the run dir plus the two file-dest paths inside it. */
|
|
95
|
+
export interface KeptRunPlan {
|
|
96
|
+
readonly runDir: string;
|
|
97
|
+
readonly documentPath: string;
|
|
98
|
+
readonly reconciliationJsonPath: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Invokes the reconciliation tool with the given `--format <fmt>:<path>` specs. INJECTED so
|
|
102
|
+
* the scaffold stays the tool's black-box caller (the real CLI subprocess in the
|
|
103
|
+
* `@integration` arm) and holds no opinion on HOW the tool is run — only WHERE its outputs
|
|
104
|
+
* land (the two file-dest paths the runner hands it).
|
|
105
|
+
*/
|
|
106
|
+
export type RouteOutputs = (formatSpecs: readonly string[]) => void;
|
|
107
|
+
/**
|
|
108
|
+
* PURE planner: yield the run dir `<storeRoot>/<target>/<runId>/` and the two file-dest
|
|
109
|
+
* paths (`<dir>/spec-reconciliation.md`, `<dir>/spec-reconciliation.json`) from the spec. No
|
|
110
|
+
* IO — the store root is threaded straight through `join`, so it is a parameter, never
|
|
111
|
+
* hardcoded.
|
|
112
|
+
*/
|
|
113
|
+
export declare function planKeptRun(spec: KeptRunSpec): KeptRunPlan;
|
|
114
|
+
/**
|
|
115
|
+
* Options for the pure run-id resolver (RMG-003): the operator's OPTIONAL `--run-id` mnemonic
|
|
116
|
+
* and the INJECTED clock instant the default timestamp derives from.
|
|
117
|
+
*/
|
|
118
|
+
export interface ResolveRunIdOptions {
|
|
119
|
+
/** The operator's `--run-id` mnemonic when supplied; overrides the timestamp default, verbatim. */
|
|
120
|
+
readonly runId?: string;
|
|
121
|
+
/**
|
|
122
|
+
* The injected instant the default ISO-timestamp run-id derives from — the deterministic-@unit
|
|
123
|
+
* seam. Never a bare `new Date()` inside the resolver, so the default run-id is pin-testable.
|
|
124
|
+
*/
|
|
125
|
+
readonly now: Date;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* PURE run-id resolver — the CLI/edge seam that turns an OPTIONAL `--run-id` into the concrete
|
|
129
|
+
* run-id naming the run dir (feeds `KeptRunSpec.runId`). No `--run-id` → an ISO-8601 UTC
|
|
130
|
+
* timestamp derived from the INJECTED `now` (filesystem-safe: no colons, no millis — matching
|
|
131
|
+
* the existing store run dir); `--run-id <mnemonic>` → that mnemonic verbatim (`acme-q3`, `e1`
|
|
132
|
+
* — an earlier precedent), which ignores the clock. No IO — proven `@unit` (RMG-003).
|
|
133
|
+
* The clock is injected (never a bare `new Date()`) so the default timestamp is deterministic
|
|
134
|
+
* under test (the discipline the old `formatRunTimestamp` used). Just default-vs-override — no
|
|
135
|
+
* sanitisation / collision policy (deferred).
|
|
136
|
+
*/
|
|
137
|
+
export declare function resolveRunId(options: ResolveRunIdOptions): string;
|
|
138
|
+
/**
|
|
139
|
+
* PURE: build the run.yaml manifest object from the run metadata (RMG-002). `purpose` and
|
|
140
|
+
* `links` are stamped EMPTY (the ad-hoc default; slugs are added by operator hand-edit of
|
|
141
|
+
* run.yaml afterward — the documented curation convention); `artefacts` carry the decided
|
|
142
|
+
* filenames. No IO — proven `@unit`.
|
|
143
|
+
*/
|
|
144
|
+
export declare function planRunManifest(input: RunManifestInput): RunManifest;
|
|
145
|
+
/** PURE: serialise the manifest to YAML text the impure runner writes verbatim. */
|
|
146
|
+
export declare function serialiseRunManifest(manifest: RunManifest): string;
|
|
147
|
+
/**
|
|
148
|
+
* IMPURE runner: `mkdir -p` the run dir, route the tool's `--format` outputs into the two
|
|
149
|
+
* files via the injected `route` — the document as `md:<documentPath>`, the reconciliation
|
|
150
|
+
* JSON as `json:<reconciliationJsonPath>` — and stamp `run.yaml` with the manifest built from the spec + `meta`
|
|
151
|
+
* (RMG-002). Returns the plan it realised. The only IO. The `mkdir` is load-bearing: the
|
|
152
|
+
* tool's writer uses `writeFileSync`, which needs the parent dir to exist — so the scaffold
|
|
153
|
+
* creates the dir the location-agnostic tool writes into.
|
|
154
|
+
*/
|
|
155
|
+
export declare function executeKeptRun(spec: KeptRunSpec, meta: RunManifestMeta, route: RouteOutputs): KeptRunPlan;
|
|
156
|
+
//# sourceMappingURL=keptRun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keptRun.d.ts","sourceRoot":"","sources":["../../src/run-management/keptRun.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,2BAA2B,CAAC;AAChE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAE9D,wEAAwE;AACxE,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,8CAA8C;AAC9C,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,6FAA6F;AAC7F,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE;QAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;KACjC,CAAC;CACH;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;AAEpE;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAO1D;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAOjE;AAYD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAmBpE;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,YAAY,GAClB,WAAW,CASb"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The KEPT-RUN scaffold — the thin app-layer that turns a bare tool invocation into a
|
|
3
|
+
* STORED run (the run-management layer). A kept run is a consultancy / product-dev artefact (NOT CI):
|
|
4
|
+
* it makes a per-run directory under a caller-INJECTED store root and routes the tool's
|
|
5
|
+
* `--format` outputs into it. The tool holds no storage opinion (the `--format` surface); this
|
|
6
|
+
* scaffold adds only the directory + the routing.
|
|
7
|
+
*
|
|
8
|
+
* Split by the three-layer architecture:
|
|
9
|
+
* - PURE planner (`planKeptRun`): given `{ storeRoot, target, runId }`, yields the run
|
|
10
|
+
* dir `<storeRoot>/<target>/<runId>/` and the two file-dest paths — no IO, proven
|
|
11
|
+
* `@unit`.
|
|
12
|
+
* - IMPURE runner (`executeKeptRun`): `mkdir -p` the run dir, then route the tool's
|
|
13
|
+
* `--format` outputs into the two files by handing an injected tool-invoker the two
|
|
14
|
+
* file-dest `--format` specs. The ONLY IO, proven `@integration` (the real on-disk
|
|
15
|
+
* files under a temp root + the repo untouched — never a planner re-run).
|
|
16
|
+
*
|
|
17
|
+
* The store root is an INJECTED parameter — never hardcoded — so the scaffold is
|
|
18
|
+
* location-agnostic (proven against a temp root; the repo is never written). RMG-001 is
|
|
19
|
+
* the dir + the routed outputs; the `run.yaml` stamp (RMG-002) and the `--run-id` mnemonic
|
|
20
|
+
* override (RMG-003) land in later sub-issues.
|
|
21
|
+
*
|
|
22
|
+
* Parent: @SCN-RMG-001 — the run-management layer.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { stringify } from "yaml";
|
|
27
|
+
/** The two artefact filenames a kept run routes the tool's outputs into. */
|
|
28
|
+
export const RECONCILIATION_DOCUMENT = "spec-reconciliation.md";
|
|
29
|
+
/**
|
|
30
|
+
* The machine artefact filename (@SCN-RMD-001) — renamed `ledger.json` →
|
|
31
|
+
* `spec-reconciliation.json` (parity with `spec-reconciliation.md`): it is the machine form
|
|
32
|
+
* of the WHOLE reconciliation document, not just the ledger.
|
|
33
|
+
*/
|
|
34
|
+
export const RECONCILIATION_JSON = "spec-reconciliation.json";
|
|
35
|
+
/** The manifest filename a kept run stamps in the run dir (RMG-002). */
|
|
36
|
+
export const RUN_MANIFEST = "run.yaml";
|
|
37
|
+
/**
|
|
38
|
+
* The run.yaml schema version this scaffold stamps. Bumped 1 → 2 by 3F-1916, which added
|
|
39
|
+
* the tool identity (`tool_sha` / `tool_version`) — so a reader can tell a run that
|
|
40
|
+
* predates tool-identity recording (schema 1) from one that should carry it (schema 2).
|
|
41
|
+
* run.yaml is write-only (no production reader), so the bump breaks no read path.
|
|
42
|
+
*/
|
|
43
|
+
export const RUN_MANIFEST_SCHEMA_VERSION = 2;
|
|
44
|
+
/**
|
|
45
|
+
* PURE planner: yield the run dir `<storeRoot>/<target>/<runId>/` and the two file-dest
|
|
46
|
+
* paths (`<dir>/spec-reconciliation.md`, `<dir>/spec-reconciliation.json`) from the spec. No
|
|
47
|
+
* IO — the store root is threaded straight through `join`, so it is a parameter, never
|
|
48
|
+
* hardcoded.
|
|
49
|
+
*/
|
|
50
|
+
export function planKeptRun(spec) {
|
|
51
|
+
const runDir = join(spec.storeRoot, spec.target, spec.runId);
|
|
52
|
+
return {
|
|
53
|
+
runDir,
|
|
54
|
+
documentPath: join(runDir, RECONCILIATION_DOCUMENT),
|
|
55
|
+
reconciliationJsonPath: join(runDir, RECONCILIATION_JSON),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* PURE run-id resolver — the CLI/edge seam that turns an OPTIONAL `--run-id` into the concrete
|
|
60
|
+
* run-id naming the run dir (feeds `KeptRunSpec.runId`). No `--run-id` → an ISO-8601 UTC
|
|
61
|
+
* timestamp derived from the INJECTED `now` (filesystem-safe: no colons, no millis — matching
|
|
62
|
+
* the existing store run dir); `--run-id <mnemonic>` → that mnemonic verbatim (`acme-q3`, `e1`
|
|
63
|
+
* — an earlier precedent), which ignores the clock. No IO — proven `@unit` (RMG-003).
|
|
64
|
+
* The clock is injected (never a bare `new Date()`) so the default timestamp is deterministic
|
|
65
|
+
* under test (the discipline the old `formatRunTimestamp` used). Just default-vs-override — no
|
|
66
|
+
* sanitisation / collision policy (deferred).
|
|
67
|
+
*/
|
|
68
|
+
export function resolveRunId(options) {
|
|
69
|
+
// The operator mnemonic, when given, names the run dir verbatim — the clock is ignored.
|
|
70
|
+
if (options.runId !== undefined) {
|
|
71
|
+
return options.runId;
|
|
72
|
+
}
|
|
73
|
+
// Otherwise the run-id defaults to the injected instant as a filesystem-safe timestamp.
|
|
74
|
+
return formatRunTimestamp(options.now);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format an injected instant as a filesystem-safe ISO-8601 UTC timestamp: `toISOString()`
|
|
78
|
+
* yields `2026-06-30T10:04:01.123Z`; drop the milliseconds, then swap the time-separator
|
|
79
|
+
* colons for hyphens so the string is a safe directory name (`2026-06-30T10-04-01Z` — the
|
|
80
|
+
* form the existing store run dir uses).
|
|
81
|
+
*/
|
|
82
|
+
function formatRunTimestamp(instant) {
|
|
83
|
+
return instant.toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* PURE: build the run.yaml manifest object from the run metadata (RMG-002). `purpose` and
|
|
87
|
+
* `links` are stamped EMPTY (the ad-hoc default; slugs are added by operator hand-edit of
|
|
88
|
+
* run.yaml afterward — the documented curation convention); `artefacts` carry the decided
|
|
89
|
+
* filenames. No IO — proven `@unit`.
|
|
90
|
+
*/
|
|
91
|
+
export function planRunManifest(input) {
|
|
92
|
+
return {
|
|
93
|
+
schema_version: RUN_MANIFEST_SCHEMA_VERSION,
|
|
94
|
+
run_id: input.runId,
|
|
95
|
+
target: input.target,
|
|
96
|
+
source_sha: input.sourceSha,
|
|
97
|
+
tool_sha: input.toolSha,
|
|
98
|
+
tool_version: input.toolVersion,
|
|
99
|
+
mode: input.mode,
|
|
100
|
+
date: input.date,
|
|
101
|
+
// EMPTY defaults — the ad-hoc run; slugs/links are added by operator hand-edit of
|
|
102
|
+
// run.yaml afterward (no --purpose flag, no vocab enforcement — both deferred).
|
|
103
|
+
purpose: [],
|
|
104
|
+
links: [],
|
|
105
|
+
artefacts: {
|
|
106
|
+
document: RECONCILIATION_DOCUMENT,
|
|
107
|
+
reconciliation: RECONCILIATION_JSON,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** PURE: serialise the manifest to YAML text the impure runner writes verbatim. */
|
|
112
|
+
export function serialiseRunManifest(manifest) {
|
|
113
|
+
return stringify(manifest);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* IMPURE runner: `mkdir -p` the run dir, route the tool's `--format` outputs into the two
|
|
117
|
+
* files via the injected `route` — the document as `md:<documentPath>`, the reconciliation
|
|
118
|
+
* JSON as `json:<reconciliationJsonPath>` — and stamp `run.yaml` with the manifest built from the spec + `meta`
|
|
119
|
+
* (RMG-002). Returns the plan it realised. The only IO. The `mkdir` is load-bearing: the
|
|
120
|
+
* tool's writer uses `writeFileSync`, which needs the parent dir to exist — so the scaffold
|
|
121
|
+
* creates the dir the location-agnostic tool writes into.
|
|
122
|
+
*/
|
|
123
|
+
export function executeKeptRun(spec, meta, route) {
|
|
124
|
+
const plan = planKeptRun(spec);
|
|
125
|
+
mkdirSync(plan.runDir, { recursive: true });
|
|
126
|
+
route([`md:${plan.documentPath}`, `json:${plan.reconciliationJsonPath}`]);
|
|
127
|
+
// Stamp run.yaml from the spec's run-id/target (so it can't drift from the run dir) + the
|
|
128
|
+
// manifest-only meta. The serialised YAML is valid — the @integration parses it back.
|
|
129
|
+
const manifest = planRunManifest({ runId: spec.runId, target: spec.target, ...meta });
|
|
130
|
+
writeFileSync(join(plan.runDir, RUN_MANIFEST), serialiseRunManifest(manifest));
|
|
131
|
+
return plan;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=keptRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keptRun.js","sourceRoot":"","sources":["../../src/run-management/keptRun.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a run's git-derived inputs — the target's source SHA and repo root. The
|
|
3
|
+
* IMPURE half of the old run/runContext.ts: both shell out to `git` over the
|
|
4
|
+
* target tree, so they live in the app's run-management layer, apart from the pure
|
|
5
|
+
* provenance projection (packages/core/src/provenance/provenance.ts) they feed. The
|
|
6
|
+
* CLI (cli-balance/cli.ts) calls these once to build the RunContext.
|
|
7
|
+
*
|
|
8
|
+
* Scenarios: @SCN-RPT-001 (the run-provenance slice); the --source-sha override;
|
|
9
|
+
* @SCN-RPT-016 (target-root derivation).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the target's source SHA (project design §4). An explicit non-empty
|
|
13
|
+
* `sourceSha` wins — the override for non-git / zip / detached-HEAD targets (the
|
|
14
|
+
* --source-sha flag). Otherwise `git -C <targetDir> rev-parse HEAD` (trimmed) as a
|
|
15
|
+
* READ-ONLY pure read. On any throw —
|
|
16
|
+
* a non-git target, git absent, an empty answer — resolve to `null` ("unknown")
|
|
17
|
+
* rather than crashing. `targetDir` = dirname(--features) when given, else cwd.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveSourceSha(opts: {
|
|
20
|
+
sourceSha?: string;
|
|
21
|
+
targetDir?: string;
|
|
22
|
+
}): string | null;
|
|
23
|
+
/** The tool's own identity — which spec-controller produced a run (3F-1916). */
|
|
24
|
+
export interface ToolIdentity {
|
|
25
|
+
/** The tool's own git HEAD, or null when the tool is not a git checkout (an installed package). */
|
|
26
|
+
toolSha: string | null;
|
|
27
|
+
/** The tool's package version — always present (read from the tool's own package.json). */
|
|
28
|
+
toolVersion: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the TOOL's own identity (3F-1916) — which spec-controller produced this run,
|
|
32
|
+
* distinct from the TARGET's `sourceSha`. Two facts, one call, threaded to both the
|
|
33
|
+
* report provenance (RunContext) and the run.yaml manifest.
|
|
34
|
+
*
|
|
35
|
+
* **The correctness trap this exists to avoid:** at run time the process cwd is the
|
|
36
|
+
* TARGET repo (reconcile runs there), so a bare `git rev-parse HEAD` would re-record the
|
|
37
|
+
* TARGET's SHA — the exact bug this ticket fixes. So the SHA is read `git -C <the tool's
|
|
38
|
+
* OWN dir>`, derived from `import.meta.url`, never cwd.
|
|
39
|
+
*
|
|
40
|
+
* `toolSha` is `git -C <toolDir> rev-parse HEAD` (trimmed), or `null` on any throw — the
|
|
41
|
+
* tool installed as a package with no `.git`, git absent, or an empty answer — mirroring
|
|
42
|
+
* `resolveSourceSha`'s fail-soft. `toolVersion` is read from the tool's own
|
|
43
|
+
* `package.json` (walked up from `toolDir`), or `"unknown"` when none is found — so a run
|
|
44
|
+
* stays attributable by version even when no SHA can be read. `toolDir` /
|
|
45
|
+
* `packageJsonPath` are injectable for the `@unit` teeth (@SCN-RPT-026).
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveToolIdentity(opts?: {
|
|
48
|
+
toolDir?: string;
|
|
49
|
+
packageJsonPath?: string;
|
|
50
|
+
}): ToolIdentity;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the TARGET repo root — the root the recorded input paths (`presentInputs`)
|
|
53
|
+
* and the evidence-observation locators (`countRuntimeEvidenceObservationKinds`) are both made
|
|
54
|
+
* portable against (@SCN-RPT-016). The bug it fixes: the prior CLI relativized
|
|
55
|
+
* against `process.cwd()` — the TOOL's own repo — so a cross-repo reconcile
|
|
56
|
+
* (cwd ≠ target) kept the target's absolute paths (they aren't under cwd → the
|
|
57
|
+
* "absolute-not-under-root" branch of `relativizeToRoot` → leaked verbatim).
|
|
58
|
+
*
|
|
59
|
+
* The rule (mirrors `resolveSourceSha`'s targetDir derivation): the root is derived from
|
|
60
|
+
* the TARGET, never the run cwd — the git top-level of `dirname(--features)` when the
|
|
61
|
+
* target is a git repo (the robust case: `--features` may sit in a nested dir), else
|
|
62
|
+
* `dirname(--features)` itself (a non-git target — a zip / extracted tree), else
|
|
63
|
+
* `process.cwd()` for a self-reconcile with no `--features` (the legacy cwd = target
|
|
64
|
+
* assumption, preserved as the fallback). A READ-ONLY `git -C … rev-parse --show-toplevel`
|
|
65
|
+
* (read-only-target-safe); any throw falls back to `dirname(--features)`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveTargetRoot(opts: {
|
|
68
|
+
features?: string;
|
|
69
|
+
}): string;
|
|
70
|
+
//# sourceMappingURL=resolveRunInputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveRunInputs.d.ts","sourceRoot":"","sources":["../../src/run-management/resolveRunInputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,CAiBhG;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,mGAAmG;IACnG,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,YAAY,CAQ3G;AA2CD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAerE"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a run's git-derived inputs — the target's source SHA and repo root. The
|
|
3
|
+
* IMPURE half of the old run/runContext.ts: both shell out to `git` over the
|
|
4
|
+
* target tree, so they live in the app's run-management layer, apart from the pure
|
|
5
|
+
* provenance projection (packages/core/src/provenance/provenance.ts) they feed. The
|
|
6
|
+
* CLI (cli-balance/cli.ts) calls these once to build the RunContext.
|
|
7
|
+
*
|
|
8
|
+
* Scenarios: @SCN-RPT-001 (the run-provenance slice); the --source-sha override;
|
|
9
|
+
* @SCN-RPT-016 (target-root derivation).
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the target's source SHA (project design §4). An explicit non-empty
|
|
17
|
+
* `sourceSha` wins — the override for non-git / zip / detached-HEAD targets (the
|
|
18
|
+
* --source-sha flag). Otherwise `git -C <targetDir> rev-parse HEAD` (trimmed) as a
|
|
19
|
+
* READ-ONLY pure read. On any throw —
|
|
20
|
+
* a non-git target, git absent, an empty answer — resolve to `null` ("unknown")
|
|
21
|
+
* rather than crashing. `targetDir` = dirname(--features) when given, else cwd.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveSourceSha(opts) {
|
|
24
|
+
if (opts.sourceSha !== undefined && opts.sourceSha.trim() !== "") {
|
|
25
|
+
return opts.sourceSha.trim();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const dir = opts.targetDir !== undefined && opts.targetDir.trim() !== ""
|
|
29
|
+
? opts.targetDir
|
|
30
|
+
: process.cwd();
|
|
31
|
+
const out = execFileSync("git", ["-C", dir, "rev-parse", "HEAD"], {
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
34
|
+
});
|
|
35
|
+
const sha = out.trim();
|
|
36
|
+
return sha === "" ? null : sha;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the TOOL's own identity (3F-1916) — which spec-controller produced this run,
|
|
44
|
+
* distinct from the TARGET's `sourceSha`. Two facts, one call, threaded to both the
|
|
45
|
+
* report provenance (RunContext) and the run.yaml manifest.
|
|
46
|
+
*
|
|
47
|
+
* **The correctness trap this exists to avoid:** at run time the process cwd is the
|
|
48
|
+
* TARGET repo (reconcile runs there), so a bare `git rev-parse HEAD` would re-record the
|
|
49
|
+
* TARGET's SHA — the exact bug this ticket fixes. So the SHA is read `git -C <the tool's
|
|
50
|
+
* OWN dir>`, derived from `import.meta.url`, never cwd.
|
|
51
|
+
*
|
|
52
|
+
* `toolSha` is `git -C <toolDir> rev-parse HEAD` (trimmed), or `null` on any throw — the
|
|
53
|
+
* tool installed as a package with no `.git`, git absent, or an empty answer — mirroring
|
|
54
|
+
* `resolveSourceSha`'s fail-soft. `toolVersion` is read from the tool's own
|
|
55
|
+
* `package.json` (walked up from `toolDir`), or `"unknown"` when none is found — so a run
|
|
56
|
+
* stays attributable by version even when no SHA can be read. `toolDir` /
|
|
57
|
+
* `packageJsonPath` are injectable for the `@unit` teeth (@SCN-RPT-026).
|
|
58
|
+
*/
|
|
59
|
+
export function resolveToolIdentity(opts = {}) {
|
|
60
|
+
const toolDir = opts.toolDir !== undefined && opts.toolDir.trim() !== ""
|
|
61
|
+
? opts.toolDir
|
|
62
|
+
: dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
return {
|
|
64
|
+
toolSha: resolveToolSha(toolDir),
|
|
65
|
+
toolVersion: resolveToolVersion(opts.packageJsonPath ?? findPackageJson(toolDir)),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** The tool's own git HEAD, read `-C <toolDir>` (never cwd), null on any throw. */
|
|
69
|
+
function resolveToolSha(toolDir) {
|
|
70
|
+
try {
|
|
71
|
+
const out = execFileSync("git", ["-C", toolDir, "rev-parse", "HEAD"], {
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
74
|
+
});
|
|
75
|
+
const sha = out.trim();
|
|
76
|
+
return sha === "" ? null : sha;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Read the `version` from a package.json, or "unknown" when the path is null / unreadable / versionless. */
|
|
83
|
+
function resolveToolVersion(packageJsonPath) {
|
|
84
|
+
if (packageJsonPath === null)
|
|
85
|
+
return "unknown";
|
|
86
|
+
try {
|
|
87
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
88
|
+
return typeof pkg.version === "string" && pkg.version !== "" ? pkg.version : "unknown";
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return "unknown";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Walk up from a dir to the nearest package.json, or null when the filesystem root is reached. */
|
|
95
|
+
function findPackageJson(fromDir) {
|
|
96
|
+
let dir = fromDir;
|
|
97
|
+
for (;;) {
|
|
98
|
+
const candidate = join(dir, "package.json");
|
|
99
|
+
try {
|
|
100
|
+
readFileSync(candidate, "utf8");
|
|
101
|
+
return candidate;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
const parent = dirname(dir);
|
|
105
|
+
if (parent === dir)
|
|
106
|
+
return null;
|
|
107
|
+
dir = parent;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the TARGET repo root — the root the recorded input paths (`presentInputs`)
|
|
113
|
+
* and the evidence-observation locators (`countRuntimeEvidenceObservationKinds`) are both made
|
|
114
|
+
* portable against (@SCN-RPT-016). The bug it fixes: the prior CLI relativized
|
|
115
|
+
* against `process.cwd()` — the TOOL's own repo — so a cross-repo reconcile
|
|
116
|
+
* (cwd ≠ target) kept the target's absolute paths (they aren't under cwd → the
|
|
117
|
+
* "absolute-not-under-root" branch of `relativizeToRoot` → leaked verbatim).
|
|
118
|
+
*
|
|
119
|
+
* The rule (mirrors `resolveSourceSha`'s targetDir derivation): the root is derived from
|
|
120
|
+
* the TARGET, never the run cwd — the git top-level of `dirname(--features)` when the
|
|
121
|
+
* target is a git repo (the robust case: `--features` may sit in a nested dir), else
|
|
122
|
+
* `dirname(--features)` itself (a non-git target — a zip / extracted tree), else
|
|
123
|
+
* `process.cwd()` for a self-reconcile with no `--features` (the legacy cwd = target
|
|
124
|
+
* assumption, preserved as the fallback). A READ-ONLY `git -C … rev-parse --show-toplevel`
|
|
125
|
+
* (read-only-target-safe); any throw falls back to `dirname(--features)`.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveTargetRoot(opts) {
|
|
128
|
+
if (opts.features === undefined || opts.features.trim() === "") {
|
|
129
|
+
return process.cwd();
|
|
130
|
+
}
|
|
131
|
+
const targetDir = dirname(opts.features);
|
|
132
|
+
try {
|
|
133
|
+
const out = execFileSync("git", ["-C", targetDir, "rev-parse", "--show-toplevel"], {
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
136
|
+
});
|
|
137
|
+
const root = out.trim();
|
|
138
|
+
return root === "" ? targetDir : root;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return targetDir;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=resolveRunInputs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveRunInputs.js","sourceRoot":"","sources":["../../src/run-management/resolveRunInputs.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spec-controller",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Reconcile the behaviour your scenarios name against the evidence that actually ran — the deterministic `balance` CLI.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"reconciliation",
|
|
7
|
+
"gherkin",
|
|
8
|
+
"bdd",
|
|
9
|
+
"evidence",
|
|
10
|
+
"double-entry",
|
|
11
|
+
"cli"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"spec-controller": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@cucumber/gherkin": "^41.0.0",
|
|
26
|
+
"@cucumber/messages": "^34.0.1",
|
|
27
|
+
"yaml": "^2.9.0",
|
|
28
|
+
"@3f-consulting/spec-controller-core": "0.1.0-alpha.1"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.build.json"
|
|
35
|
+
}
|
|
36
|
+
}
|