repo-contract 0.1.0
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +967 -0
- package/dist/.dts/config/define-repo-contract.d.ts +36 -0
- package/dist/.dts/config/define-repo-contract.d.ts.map +1 -0
- package/dist/.dts/config/tokenize-command.d.ts +33 -0
- package/dist/.dts/config/tokenize-command.d.ts.map +1 -0
- package/dist/.dts/config/validate-config.d.ts +32 -0
- package/dist/.dts/config/validate-config.d.ts.map +1 -0
- package/dist/.dts/errors.d.ts +155 -0
- package/dist/.dts/errors.d.ts.map +1 -0
- package/dist/.dts/evidence/build-evidence.d.ts +26 -0
- package/dist/.dts/evidence/build-evidence.d.ts.map +1 -0
- package/dist/.dts/execution/abort-signals.d.ts +29 -0
- package/dist/.dts/execution/abort-signals.d.ts.map +1 -0
- package/dist/.dts/execution/concurrency-pool.d.ts +14 -0
- package/dist/.dts/execution/concurrency-pool.d.ts.map +1 -0
- package/dist/.dts/execution/dependency-scheduler.d.ts +30 -0
- package/dist/.dts/execution/dependency-scheduler.d.ts.map +1 -0
- package/dist/.dts/execution/process-tree.d.ts +48 -0
- package/dist/.dts/execution/process-tree.d.ts.map +1 -0
- package/dist/.dts/execution/run-checks.d.ts +29 -0
- package/dist/.dts/execution/run-checks.d.ts.map +1 -0
- package/dist/.dts/execution/spawn-check.d.ts +30 -0
- package/dist/.dts/execution/spawn-check.d.ts.map +1 -0
- package/dist/.dts/index.d.ts +13 -0
- package/dist/.dts/index.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-json.d.ts +8 -0
- package/dist/.dts/parsing/parse-json.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-output.d.ts +10 -0
- package/dist/.dts/parsing/parse-output.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-text.d.ts +8 -0
- package/dist/.dts/parsing/parse-text.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-yaml.d.ts +21 -0
- package/dist/.dts/parsing/parse-yaml.d.ts.map +1 -0
- package/dist/.dts/policy/run-policies.d.ts +38 -0
- package/dist/.dts/policy/run-policies.d.ts.map +1 -0
- package/dist/.dts/presets/arethetypeswrong.d.ts +39 -0
- package/dist/.dts/presets/arethetypeswrong.d.ts.map +1 -0
- package/dist/.dts/presets/broken-links.d.ts +16 -0
- package/dist/.dts/presets/broken-links.d.ts.map +1 -0
- package/dist/.dts/presets/commitlint.d.ts +22 -0
- package/dist/.dts/presets/commitlint.d.ts.map +1 -0
- package/dist/.dts/presets/dead-code.d.ts +23 -0
- package/dist/.dts/presets/dead-code.d.ts.map +1 -0
- package/dist/.dts/presets/duplication.d.ts +14 -0
- package/dist/.dts/presets/duplication.d.ts.map +1 -0
- package/dist/.dts/presets/e2e.d.ts +4 -0
- package/dist/.dts/presets/e2e.d.ts.map +1 -0
- package/dist/.dts/presets/format.d.ts +4 -0
- package/dist/.dts/presets/format.d.ts.map +1 -0
- package/dist/.dts/presets/index.d.ts +31 -0
- package/dist/.dts/presets/index.d.ts.map +1 -0
- package/dist/.dts/presets/license.d.ts +4 -0
- package/dist/.dts/presets/license.d.ts.map +1 -0
- package/dist/.dts/presets/lint.d.ts +20 -0
- package/dist/.dts/presets/lint.d.ts.map +1 -0
- package/dist/.dts/presets/markdownlint.d.ts +23 -0
- package/dist/.dts/presets/markdownlint.d.ts.map +1 -0
- package/dist/.dts/presets/publint.d.ts +13 -0
- package/dist/.dts/presets/publint.d.ts.map +1 -0
- package/dist/.dts/presets/security-deps.d.ts +4 -0
- package/dist/.dts/presets/security-deps.d.ts.map +1 -0
- package/dist/.dts/presets/security-secrets.d.ts +4 -0
- package/dist/.dts/presets/security-secrets.d.ts.map +1 -0
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts +13 -0
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts.map +1 -0
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts +23 -0
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts.map +1 -0
- package/dist/.dts/presets/shared/missing-dependency.d.ts +19 -0
- package/dist/.dts/presets/shared/missing-dependency.d.ts.map +1 -0
- package/dist/.dts/presets/shared/read-json-report.d.ts +33 -0
- package/dist/.dts/presets/shared/read-json-report.d.ts.map +1 -0
- package/dist/.dts/presets/shared/terminal-status.d.ts +25 -0
- package/dist/.dts/presets/shared/terminal-status.d.ts.map +1 -0
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts +20 -0
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts.map +1 -0
- package/dist/.dts/presets/stylelint.d.ts +17 -0
- package/dist/.dts/presets/stylelint.d.ts.map +1 -0
- package/dist/.dts/presets/test.d.ts +4 -0
- package/dist/.dts/presets/test.d.ts.map +1 -0
- package/dist/.dts/presets/typecheck.d.ts +4 -0
- package/dist/.dts/presets/typecheck.d.ts.map +1 -0
- package/dist/.dts/run-repo-contract.d.ts +38 -0
- package/dist/.dts/run-repo-contract.d.ts.map +1 -0
- package/dist/.dts/types.d.ts +324 -0
- package/dist/.dts/types.d.ts.map +1 -0
- package/dist/index.cjs +46 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/presets.cjs +30 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +1 -0
- package/dist/presets.d.ts +1 -0
- package/dist/presets.js +13 -0
- package/dist/presets.js.map +1 -0
- package/package.json +192 -0
- package/presets/package.json +5 -0
- package/schemas/evidence.schema.json +253 -0
- package/schemas/verdict.schema.json +66 -0
- package/src/config/define-repo-contract.ts +38 -0
- package/src/config/tokenize-command.ts +214 -0
- package/src/config/validate-config.ts +368 -0
- package/src/errors.ts +229 -0
- package/src/evidence/build-evidence.ts +91 -0
- package/src/execution/abort-signals.ts +56 -0
- package/src/execution/concurrency-pool.ts +64 -0
- package/src/execution/dependency-scheduler.ts +216 -0
- package/src/execution/process-tree.ts +107 -0
- package/src/execution/run-checks.ts +348 -0
- package/src/execution/spawn-check.ts +494 -0
- package/src/index.ts +44 -0
- package/src/parsing/parse-json.ts +18 -0
- package/src/parsing/parse-output.ts +26 -0
- package/src/parsing/parse-text.ts +10 -0
- package/src/parsing/parse-yaml.ts +40 -0
- package/src/policy/run-policies.ts +261 -0
- package/src/presets/arethetypeswrong.ts +116 -0
- package/src/presets/broken-links.ts +95 -0
- package/src/presets/commitlint.ts +77 -0
- package/src/presets/dead-code.ts +223 -0
- package/src/presets/duplication.ts +137 -0
- package/src/presets/e2e.ts +144 -0
- package/src/presets/format.ts +25 -0
- package/src/presets/index.ts +30 -0
- package/src/presets/license.ts +90 -0
- package/src/presets/lint.ts +116 -0
- package/src/presets/markdownlint.ts +105 -0
- package/src/presets/publint.ts +38 -0
- package/src/presets/security-deps.ts +142 -0
- package/src/presets/security-secrets.ts +93 -0
- package/src/presets/shared/error-warning-pass-policy.ts +39 -0
- package/src/presets/shared/exit-code-fail-rationale.ts +34 -0
- package/src/presets/shared/missing-dependency.ts +31 -0
- package/src/presets/shared/read-json-report.ts +46 -0
- package/src/presets/shared/terminal-status.ts +70 -0
- package/src/presets/shared/vitest-json-policy.ts +95 -0
- package/src/presets/stylelint.ts +101 -0
- package/src/presets/test.ts +19 -0
- package/src/presets/typecheck.ts +25 -0
- package/src/run-repo-contract.ts +80 -0
- package/src/types.ts +340 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type surface for repo-contract. Evidence describes what happened;
|
|
3
|
+
* Verdict describes whether it was acceptable. They are deliberately
|
|
4
|
+
* separate concepts (see specs/architecture.md) even though
|
|
5
|
+
* `runRepoContract` returns both together.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Output interpretation a check can explicitly request. No format requested means no parsing -- the consumer gets raw stdout/stderr only. */
|
|
9
|
+
export type OutputFormat = "json" | "yaml" | "text"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Why a check's process ended up in its terminal state. `"completed"` means
|
|
13
|
+
* the process ran to exit on its own -- the exit code may still be
|
|
14
|
+
* non-zero, and that is for the check's policy to interpret, never this
|
|
15
|
+
* package. The other five values all mean the process did not exit on its
|
|
16
|
+
* own; repo-contract terminated it, or it was terminated for a reason
|
|
17
|
+
* repo-contract can observe but did not cause.
|
|
18
|
+
*
|
|
19
|
+
* `"signaled"` specifically means a signal repo-contract did *not* itself request -- an
|
|
20
|
+
* externally-caused termination. A check killed because the *host* process running repo-contract
|
|
21
|
+
* received its own SIGINT/SIGTERM (see `run-checks.ts`'s termination-handler cleanup) is instead
|
|
22
|
+
* `"host_terminated"`: repo-contract did request that signal, just not via `options.signal` or
|
|
23
|
+
* `timeoutMs` (see `"aborted"`/`"timed_out"`), so it must not be conflated with an externally-caused
|
|
24
|
+
* `"signaled"`.
|
|
25
|
+
*/
|
|
26
|
+
export type CheckStatus =
|
|
27
|
+
"completed" | "timed_out" | "signaled" | "host_terminated" | "spawn_error" | "aborted"
|
|
28
|
+
|
|
29
|
+
/** A requested parse of a check's stdout succeeded. */
|
|
30
|
+
export interface ParsedOutputSuccess<T> {
|
|
31
|
+
/** The format that was requested and successfully parsed. */
|
|
32
|
+
readonly format: OutputFormat
|
|
33
|
+
/** Always `true`. */
|
|
34
|
+
readonly success: true
|
|
35
|
+
/** The parsed value. */
|
|
36
|
+
readonly value: T
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A requested parse of a check's stdout failed. The raw stdout on the
|
|
41
|
+
* parent `CheckEvidence` is preserved unchanged -- a parse failure is never
|
|
42
|
+
* silently reinterpreted or discarded.
|
|
43
|
+
*/
|
|
44
|
+
export interface ParsedOutputFailure {
|
|
45
|
+
/** The format that was requested (and failed to parse). */
|
|
46
|
+
readonly format: OutputFormat
|
|
47
|
+
/** Always `false`. */
|
|
48
|
+
readonly success: false
|
|
49
|
+
/** The parse error's message. */
|
|
50
|
+
readonly error: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The result of a check's requested output-format parse: either a successful `ParsedOutputSuccess`, or a `ParsedOutputFailure`. */
|
|
54
|
+
export type ParsedOutput<T> = ParsedOutputSuccess<T> | ParsedOutputFailure
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What actually happened when one configured check ran. `output` is present
|
|
58
|
+
* only if that check's config requested a format, and is otherwise
|
|
59
|
+
* `undefined` -- a policy narrows with `ctx.result.output?.success` (or an
|
|
60
|
+
* `if (ctx.result.output) { ... }` guard) before reading `.value`/`.error`.
|
|
61
|
+
*
|
|
62
|
+
* `output.value` is typed `unknown` for every format, including `"text"`
|
|
63
|
+
* (even though `parseText` always produces a `string` at runtime) --
|
|
64
|
+
* neither repo-contract nor TypeScript's generic inference can reliably
|
|
65
|
+
* carry a specific check's own literal `output.format` through to that
|
|
66
|
+
* same check's `policy` parameter once several checks with heterogeneous
|
|
67
|
+
* formats live together in one `checks` record (a real TypeScript
|
|
68
|
+
* inference limitation hit and confirmed during implementation, not a
|
|
69
|
+
* hypothetical -- see specs/decisions/ for the isolated repro). A policy
|
|
70
|
+
* author narrows or casts `.value` themselves, exactly as they already must
|
|
71
|
+
* for `"json"`/`"yaml"` where no schema knowledge exists either way.
|
|
72
|
+
*/
|
|
73
|
+
export interface CheckEvidence {
|
|
74
|
+
/** The executable that was actually spawned (after tokenization, if `run` was a string). */
|
|
75
|
+
readonly command: string
|
|
76
|
+
/** The arguments passed to `command`, exactly as spawned. */
|
|
77
|
+
readonly args: readonly string[]
|
|
78
|
+
/** ISO 8601 timestamp of when the process was spawned. */
|
|
79
|
+
readonly startedAt: string
|
|
80
|
+
/** ISO 8601 timestamp of when the process reached its terminal state. */
|
|
81
|
+
readonly completedAt: string
|
|
82
|
+
/** Wall-clock time from spawn to termination, in milliseconds. */
|
|
83
|
+
readonly durationMs: number
|
|
84
|
+
/** `null` when the process never exited normally -- see `signal` and `status`. */
|
|
85
|
+
readonly exitCode: number | null
|
|
86
|
+
/** The signal that terminated the process, if any. `null` for a normal exit or a spawn failure. */
|
|
87
|
+
readonly signal: NodeJS.Signals | null
|
|
88
|
+
/** The process's raw standard output, captured verbatim up to an internal size cap (10 MiB); content beyond the cap is replaced with a truncation marker. */
|
|
89
|
+
readonly stdout: string
|
|
90
|
+
/** The process's raw standard error, captured verbatim up to an internal size cap (10 MiB); content beyond the cap is replaced with a truncation marker. */
|
|
91
|
+
readonly stderr: string
|
|
92
|
+
/** Why the process reached its terminal state; see `CheckStatus`. */
|
|
93
|
+
readonly status: CheckStatus
|
|
94
|
+
/** Populated only for `status === "spawn_error"` -- the underlying Node error message (e.g. "spawn foo ENOENT"). Never populated for any other status. */
|
|
95
|
+
readonly spawnError?: string
|
|
96
|
+
/** Populated only for `status === "spawn_error"` -- the underlying Node `ErrnoException`'s structured `.code` (e.g. `"ENOENT"`, `"EACCES"`), when Node provides one. Never populated for any other status. Distinguishes "the executable does not exist" from other spawn failures (permission denied, invalid executable format, etc.) without parsing `spawnError`'s free-text message. */
|
|
97
|
+
readonly spawnErrorCode?: string
|
|
98
|
+
/** The parsed interpretation of `stdout`, present only if this check's config requested a `format`. */
|
|
99
|
+
readonly output?: ParsedOutput<unknown>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Versioned, immutable record of one complete `runRepoContract` execution --
|
|
104
|
+
* every configured check's evidence, plus timing for the run as a whole.
|
|
105
|
+
* Says nothing about whether any of it was acceptable; see `Verdict`.
|
|
106
|
+
* Additive fields are a compatible change; changing or removing an existing
|
|
107
|
+
* field requires bumping this version number (see VERSIONING.md).
|
|
108
|
+
*/
|
|
109
|
+
export interface Evidence<TChecks extends CheckSchema = CheckSchema> {
|
|
110
|
+
/** Schema version of this shape; see VERSIONING.md. */
|
|
111
|
+
readonly version: 1
|
|
112
|
+
/** ISO 8601 timestamp of when the run began. */
|
|
113
|
+
readonly startedAt: string
|
|
114
|
+
/** ISO 8601 timestamp of when the last check finished. */
|
|
115
|
+
readonly completedAt: string
|
|
116
|
+
/** Wall-clock time for the run as a whole, in milliseconds. */
|
|
117
|
+
readonly durationMs: number
|
|
118
|
+
/** Each configured check's own evidence, keyed by check id. */
|
|
119
|
+
readonly checks: { readonly [K in keyof TChecks]: CheckEvidence }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* What a check's `policy` function is called with. `result` is that check's
|
|
124
|
+
* own evidence; `evidence` is the complete run's evidence, including every
|
|
125
|
+
* sibling check -- present so a policy can make cross-check decisions (e.g.
|
|
126
|
+
* "only enforce the mutation-score threshold if the tests check passed").
|
|
127
|
+
* By the time any policy runs, every check has already finished executing
|
|
128
|
+
* and every check's evidence has already been assembled -- no policy ever
|
|
129
|
+
* observes a partially-populated `evidence` (see specs/architecture.md).
|
|
130
|
+
*/
|
|
131
|
+
export interface PolicyContext<TChecks extends CheckSchema = CheckSchema> {
|
|
132
|
+
/** This check's own evidence. */
|
|
133
|
+
readonly result: CheckEvidence
|
|
134
|
+
/** The complete run's evidence, including every sibling check. */
|
|
135
|
+
readonly evidence: Evidence<TChecks>
|
|
136
|
+
/**
|
|
137
|
+
* This check's own declared `dependsOn` dependencies' evidence, keyed by
|
|
138
|
+
* check id -- a convenience view, fully derivable from `evidence.checks`
|
|
139
|
+
* plus this check's own `dependsOn`, provided so no policy has to do that
|
|
140
|
+
* lookup itself. `{}` for a check with no `dependsOn` -- always an
|
|
141
|
+
* object, never `undefined`, matching how `evidence.checks` is already
|
|
142
|
+
* used today (a policy narrows a specific key's presence, never the
|
|
143
|
+
* field itself). Evidence only, not policy outcomes -- a dependency's
|
|
144
|
+
* policy result stays visible only via the top-level `Verdict`, exactly
|
|
145
|
+
* as today; this keeps policy evaluation itself fully parallel and
|
|
146
|
+
* unaffected by `dependsOn`.
|
|
147
|
+
*/
|
|
148
|
+
readonly dependencies: Readonly<Record<string, CheckEvidence>>
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* `"pass"`: the repository-owned policy evaluated the captured evidence as
|
|
153
|
+
* satisfying its configured requirements. `"fail"`: the evidence did not
|
|
154
|
+
* satisfy them. `"warn"`: the evidence does not violate the policy's
|
|
155
|
+
* blocking requirements, but the policy has intentionally decided the
|
|
156
|
+
* condition is materially relevant and wants it surfaced -- not a synonym
|
|
157
|
+
* for "minor failure"; a `warn` never fails `Verdict.passed` (see
|
|
158
|
+
* `runPolicies` in `src/policy/run-policies.ts`).
|
|
159
|
+
*/
|
|
160
|
+
export type PolicyOutcome = "pass" | "fail" | "warn"
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A repository-owned policy's interpretation of one check's captured
|
|
164
|
+
* evidence -- fully JSON-serializable (a plain object of primitives only,
|
|
165
|
+
* never an `Error`, class instance, function, or tool-specific object) so it
|
|
166
|
+
* can be persisted, transmitted, aggregated across parallel checks, and
|
|
167
|
+
* consumed directly by a human or an AI without rerunning anything.
|
|
168
|
+
*
|
|
169
|
+
* `rationale` is mandatory and must contain enough actionable detail --
|
|
170
|
+
* specific file/line locations, rule ids, test names, counts -- for a
|
|
171
|
+
* consumer to understand *why* the policy reached its outcome from this
|
|
172
|
+
* value alone. A rationale like "see output above" or "check the report for
|
|
173
|
+
* details" defeats the purpose: it forces the consumer back to raw,
|
|
174
|
+
* unstructured command output, exactly what this type exists to avoid. See
|
|
175
|
+
* specs/architecture.md for the evidence/rationale/judgment distinction this
|
|
176
|
+
* type is built around: evidence answers "what happened?", `rationale`
|
|
177
|
+
* answers "what does the repository's policy conclude about what
|
|
178
|
+
* happened?", and a policy's `outcome` is not the final word -- a human or
|
|
179
|
+
* AI consumer still makes the final judgment call using both.
|
|
180
|
+
*/
|
|
181
|
+
export interface PolicyResult {
|
|
182
|
+
/** The policy's pass/fail/warn decision. */
|
|
183
|
+
readonly outcome: PolicyOutcome
|
|
184
|
+
/** Why the policy reached `outcome`, in enough detail to act on without rerunning anything. */
|
|
185
|
+
readonly rationale: string
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A repository-owned decision about whether one check's evidence is
|
|
190
|
+
* acceptable. Returns a `PolicyResult` -- never a bare boolean or string --
|
|
191
|
+
* so a policy can communicate more than pass/fail even when the run is
|
|
192
|
+
* otherwise acceptable (see `PolicyResult`/`PolicyOutcome`). May be
|
|
193
|
+
* synchronous or return a `Promise`. repo-contract does not interpret
|
|
194
|
+
* `rationale` beyond storing and surfacing it verbatim; the package has no
|
|
195
|
+
* opinion about what makes a check pass, fail, or warrant a `warn`.
|
|
196
|
+
*/
|
|
197
|
+
export type Policy<TChecks extends CheckSchema = CheckSchema> = (
|
|
198
|
+
ctx: PolicyContext<TChecks>,
|
|
199
|
+
) => PolicyResult | Promise<PolicyResult>
|
|
200
|
+
|
|
201
|
+
/** One partially configured check: how to run it, how (if at all) to interpret its output, and the policy that decides whether its evidence is acceptable. */
|
|
202
|
+
export interface CheckDefinitionConfig {
|
|
203
|
+
/**
|
|
204
|
+
* The command to run. A `string` is tokenized into executable + arguments
|
|
205
|
+
* without invoking a shell -- shell operators (`;`, `&`, `|`, backticks,
|
|
206
|
+
* `$(`, `<`, `>`, newlines) are rejected with a configuration error rather
|
|
207
|
+
* than silently passed through as literal arguments, since a string
|
|
208
|
+
* containing one almost always reflects a mistaken assumption that shell
|
|
209
|
+
* interpretation is happening. Glob characters (`*`, `?`, `~`, `[`, `]`,
|
|
210
|
+
* `{`, `}`) are NOT rejected -- they are common, legitimate literal argv
|
|
211
|
+
* content (e.g. `eslint "src/**\/*.ts"`) that many CLI tools glob-expand
|
|
212
|
+
* internally, and carry no shell-injection risk when no shell is invoked.
|
|
213
|
+
* An array bypasses tokenization entirely and is used as argv verbatim.
|
|
214
|
+
*/
|
|
215
|
+
readonly run: string | readonly string[]
|
|
216
|
+
/**
|
|
217
|
+
* Opt into real shell execution instead of the safe argv-only default.
|
|
218
|
+
* When `true`, `run` must be a `string` and is passed to the platform
|
|
219
|
+
* shell as-is (via cross-spawn's own `shell` option) -- shell metacharacter
|
|
220
|
+
* rejection does not apply. See SECURITY.md before enabling this.
|
|
221
|
+
*/
|
|
222
|
+
readonly shell?: boolean
|
|
223
|
+
/** Working directory for the spawned process. Defaults to the current process's `cwd`. */
|
|
224
|
+
readonly cwd?: string
|
|
225
|
+
/** Additional environment variables for the spawned process, applied on top of (or, if `inheritEnv` is `false`, instead of) the inherited environment. */
|
|
226
|
+
readonly env?: Readonly<Record<string, string>>
|
|
227
|
+
/** Whether the spawned process inherits `process.env`. Defaults to `true` -- most check commands (npm scripts, locally-installed CLIs) need `PATH` and similar to resolve at all. Set to `false` for a minimal environment containing only `env` (plus whatever the OS itself always provides). */
|
|
228
|
+
readonly inheritEnv?: boolean
|
|
229
|
+
/** Maximum time to let this check's process run before it is terminated and recorded with `status: "timed_out"`. No timeout by default. */
|
|
230
|
+
readonly timeoutMs?: number
|
|
231
|
+
/** Request that stdout be parsed as this format. Omit for no parsing -- the consumer gets raw stdout/stderr only. */
|
|
232
|
+
readonly output?: { readonly format: OutputFormat }
|
|
233
|
+
/**
|
|
234
|
+
* A full scheduling barrier at this check's own position in the `checks` object: it does not
|
|
235
|
+
* spawn until every check declared *earlier* has reached a terminal status (nothing "currently in
|
|
236
|
+
* flight" when its turn comes can be anything other than an earlier-declared check, since nothing
|
|
237
|
+
* declared later has even been reached yet), and every check declared *after* it waits for it in
|
|
238
|
+
* turn -- so nothing overlaps it in either direction. Purely a scheduling primitive for a check
|
|
239
|
+
* whose own tooling spawns concurrent workers that would otherwise contend with the rest of the
|
|
240
|
+
* run for machine resources (e.g. Stryker's own worker pool); it expresses no need for any other
|
|
241
|
+
* check's evidence, only for the machine to itself, and never appears in a policy's
|
|
242
|
+
* `ctx.dependencies` or in a partial `options.checks` run's transitive closure on its own (an
|
|
243
|
+
* explicit `dependsOn` alongside it still works exactly as it would on any other check). Defaults
|
|
244
|
+
* to `false` -- unaffected, fully-parallel scheduling in declaration order, unchanged. Two
|
|
245
|
+
* isolated checks are always sequential relative to each other (whichever is declared second
|
|
246
|
+
* waits for the first, as one of the "every check declared earlier" it's barred behind). See
|
|
247
|
+
* specs/decisions/0002-dependson-and-isolated-are-two-scheduling-primitives.md.
|
|
248
|
+
*/
|
|
249
|
+
readonly isolated?: boolean
|
|
250
|
+
/** Decides whether this check's evidence is acceptable, once its process has finished running. */
|
|
251
|
+
readonly policy: Policy
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** One fully configured check: how to run it, how (if at all) to interpret its output, and the policy that decides whether its evidence is acceptable. */
|
|
255
|
+
export interface CheckDefinition extends CheckDefinitionConfig {
|
|
256
|
+
/**
|
|
257
|
+
* Other check ids (from this same `checks` record) that must reach a
|
|
258
|
+
* terminal status -- not necessarily a passing policy -- before this
|
|
259
|
+
* check's own process is spawned. Execution ordering only: whether a
|
|
260
|
+
* dependency's *policy* passed is never consulted by repo-contract to
|
|
261
|
+
* decide whether this check's process spawns -- that is this check's
|
|
262
|
+
* own command or policy's decision (see `ctx.dependencies` on
|
|
263
|
+
* `PolicyContext`). Omit, or an empty array, for no dependencies -- the
|
|
264
|
+
* default, fully-parallel behavior, unchanged. Validated before any
|
|
265
|
+
* process spawns: every named id must exist in `checks`, a check cannot
|
|
266
|
+
* depend on itself, and -- since declaration order in the `checks` object
|
|
267
|
+
* doubles as the required topological order -- every named id must be
|
|
268
|
+
* declared *earlier* than the check declaring `dependsOn` on it (a
|
|
269
|
+
* forward reference throws `DependencyDeclaredLaterError`; a cycle is
|
|
270
|
+
* consequently impossible, since no edge can ever point forward). See
|
|
271
|
+
* specs/decisions/0002-dependson-and-isolated-are-two-scheduling-primitives.md.
|
|
272
|
+
*/
|
|
273
|
+
readonly dependsOn?: readonly string[]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The full set of checks in a `RepoContractConfig`, keyed by check id. */
|
|
277
|
+
export type CheckSchema = Record<string, CheckDefinition>
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Same shape as a check schema `T`, except each check's own `dependsOn` is
|
|
281
|
+
* narrowed from `readonly string[]` to only the *other* keys of that same
|
|
282
|
+
* `T` -- so a typo'd or self-referencing id fails to compile instead of
|
|
283
|
+
* only failing at runtime (`validate-config.ts` still enforces this at
|
|
284
|
+
* runtime too, for any config that reaches `runRepoContract` without having
|
|
285
|
+
* gone through `defineRepoContract`'s static checking first, e.g. one
|
|
286
|
+
* assembled dynamically from untyped data).
|
|
287
|
+
*
|
|
288
|
+
* Used as an additional constraint on `defineRepoContract`'s parameter,
|
|
289
|
+
* intersected with `RepoContractConfig<TChecks>` rather than substituted
|
|
290
|
+
* for it -- inferring `TChecks` from the *unwrapped* `RepoContractConfig<TChecks>`
|
|
291
|
+
* position first is what keeps every check's own `policy` callback
|
|
292
|
+
* contextually typed (a real TypeScript inference limitation: inferring
|
|
293
|
+
* `TChecks` directly from a mapped/conditional type over itself, as this
|
|
294
|
+
* type is, loses that contextual typing -- confirmed during implementation,
|
|
295
|
+
* not a hypothetical).
|
|
296
|
+
*/
|
|
297
|
+
export type ValidatedCheckSchema<T> = {
|
|
298
|
+
readonly [K in keyof T]: T[K] extends CheckDefinitionConfig
|
|
299
|
+
? Omit<T[K], "dependsOn"> & {
|
|
300
|
+
readonly dependsOn?: readonly (Exclude<keyof T, K> & string)[]
|
|
301
|
+
}
|
|
302
|
+
: never
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Top-level configuration passed to `defineRepoContract`/`runRepoContract`. */
|
|
306
|
+
export interface RepoContractConfig<TChecks extends CheckSchema = CheckSchema> {
|
|
307
|
+
/** Every check to run, keyed by check id. */
|
|
308
|
+
readonly checks: TChecks
|
|
309
|
+
/** Maximum number of checks to execute concurrently. Defaults to `os.availableParallelism()`. Must be a positive integer. */
|
|
310
|
+
readonly concurrency?: number
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Optional per-run controls for `runRepoContract`. */
|
|
314
|
+
export interface RunRepoContractOptions {
|
|
315
|
+
/** Abort the entire run. Checks already in flight are terminated; checks not yet started never spawn. Every configured check still receives a well-formed evidence entry (`status: "aborted"`) and still has its policy invoked. */
|
|
316
|
+
readonly signal?: AbortSignal
|
|
317
|
+
/** Restrict this run to only these check ids (and whatever they `dependsOn`, transitively). Every configured check runs when omitted. */
|
|
318
|
+
readonly checks?: string[]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Versioned, immutable aggregate result of evaluating every configured
|
|
323
|
+
* check's policy against its evidence -- each check's own `PolicyResult`,
|
|
324
|
+
* verbatim, keyed by check id. `passed` is `true` only if every check's
|
|
325
|
+
* `outcome` is `"pass"` or `"warn"` -- `"fail"` is the only outcome that
|
|
326
|
+
* fails the run; one failing check never collapses into a single generic
|
|
327
|
+
* message, every check remains individually inspectable under `checks`.
|
|
328
|
+
* Versioned independently of `Evidence` (see VERSIONING.md's
|
|
329
|
+
* schema-versioning policy) -- `version: 2` reflects `checks[id]` changing
|
|
330
|
+
* shape from `{ passed, reason? }` to a full `PolicyResult`
|
|
331
|
+
* (`{ outcome, rationale }`); see ADR 0001.
|
|
332
|
+
*/
|
|
333
|
+
export interface Verdict<TChecks extends CheckSchema = CheckSchema> {
|
|
334
|
+
/** Schema version of this shape; see VERSIONING.md. */
|
|
335
|
+
readonly version: 2
|
|
336
|
+
/** `true` only if every check's `outcome` is `"pass"` or `"warn"`. */
|
|
337
|
+
readonly passed: boolean
|
|
338
|
+
/** Each configured check's own `PolicyResult`, verbatim, keyed by check id. */
|
|
339
|
+
readonly checks: { readonly [K in keyof TChecks]: PolicyResult }
|
|
340
|
+
}
|