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.
Files changed (145) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +967 -0
  4. package/dist/.dts/config/define-repo-contract.d.ts +36 -0
  5. package/dist/.dts/config/define-repo-contract.d.ts.map +1 -0
  6. package/dist/.dts/config/tokenize-command.d.ts +33 -0
  7. package/dist/.dts/config/tokenize-command.d.ts.map +1 -0
  8. package/dist/.dts/config/validate-config.d.ts +32 -0
  9. package/dist/.dts/config/validate-config.d.ts.map +1 -0
  10. package/dist/.dts/errors.d.ts +155 -0
  11. package/dist/.dts/errors.d.ts.map +1 -0
  12. package/dist/.dts/evidence/build-evidence.d.ts +26 -0
  13. package/dist/.dts/evidence/build-evidence.d.ts.map +1 -0
  14. package/dist/.dts/execution/abort-signals.d.ts +29 -0
  15. package/dist/.dts/execution/abort-signals.d.ts.map +1 -0
  16. package/dist/.dts/execution/concurrency-pool.d.ts +14 -0
  17. package/dist/.dts/execution/concurrency-pool.d.ts.map +1 -0
  18. package/dist/.dts/execution/dependency-scheduler.d.ts +30 -0
  19. package/dist/.dts/execution/dependency-scheduler.d.ts.map +1 -0
  20. package/dist/.dts/execution/process-tree.d.ts +48 -0
  21. package/dist/.dts/execution/process-tree.d.ts.map +1 -0
  22. package/dist/.dts/execution/run-checks.d.ts +29 -0
  23. package/dist/.dts/execution/run-checks.d.ts.map +1 -0
  24. package/dist/.dts/execution/spawn-check.d.ts +30 -0
  25. package/dist/.dts/execution/spawn-check.d.ts.map +1 -0
  26. package/dist/.dts/index.d.ts +13 -0
  27. package/dist/.dts/index.d.ts.map +1 -0
  28. package/dist/.dts/parsing/parse-json.d.ts +8 -0
  29. package/dist/.dts/parsing/parse-json.d.ts.map +1 -0
  30. package/dist/.dts/parsing/parse-output.d.ts +10 -0
  31. package/dist/.dts/parsing/parse-output.d.ts.map +1 -0
  32. package/dist/.dts/parsing/parse-text.d.ts +8 -0
  33. package/dist/.dts/parsing/parse-text.d.ts.map +1 -0
  34. package/dist/.dts/parsing/parse-yaml.d.ts +21 -0
  35. package/dist/.dts/parsing/parse-yaml.d.ts.map +1 -0
  36. package/dist/.dts/policy/run-policies.d.ts +38 -0
  37. package/dist/.dts/policy/run-policies.d.ts.map +1 -0
  38. package/dist/.dts/presets/arethetypeswrong.d.ts +39 -0
  39. package/dist/.dts/presets/arethetypeswrong.d.ts.map +1 -0
  40. package/dist/.dts/presets/broken-links.d.ts +16 -0
  41. package/dist/.dts/presets/broken-links.d.ts.map +1 -0
  42. package/dist/.dts/presets/commitlint.d.ts +22 -0
  43. package/dist/.dts/presets/commitlint.d.ts.map +1 -0
  44. package/dist/.dts/presets/dead-code.d.ts +23 -0
  45. package/dist/.dts/presets/dead-code.d.ts.map +1 -0
  46. package/dist/.dts/presets/duplication.d.ts +14 -0
  47. package/dist/.dts/presets/duplication.d.ts.map +1 -0
  48. package/dist/.dts/presets/e2e.d.ts +4 -0
  49. package/dist/.dts/presets/e2e.d.ts.map +1 -0
  50. package/dist/.dts/presets/format.d.ts +4 -0
  51. package/dist/.dts/presets/format.d.ts.map +1 -0
  52. package/dist/.dts/presets/index.d.ts +31 -0
  53. package/dist/.dts/presets/index.d.ts.map +1 -0
  54. package/dist/.dts/presets/license.d.ts +4 -0
  55. package/dist/.dts/presets/license.d.ts.map +1 -0
  56. package/dist/.dts/presets/lint.d.ts +20 -0
  57. package/dist/.dts/presets/lint.d.ts.map +1 -0
  58. package/dist/.dts/presets/markdownlint.d.ts +23 -0
  59. package/dist/.dts/presets/markdownlint.d.ts.map +1 -0
  60. package/dist/.dts/presets/publint.d.ts +13 -0
  61. package/dist/.dts/presets/publint.d.ts.map +1 -0
  62. package/dist/.dts/presets/security-deps.d.ts +4 -0
  63. package/dist/.dts/presets/security-deps.d.ts.map +1 -0
  64. package/dist/.dts/presets/security-secrets.d.ts +4 -0
  65. package/dist/.dts/presets/security-secrets.d.ts.map +1 -0
  66. package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts +13 -0
  67. package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts.map +1 -0
  68. package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts +23 -0
  69. package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts.map +1 -0
  70. package/dist/.dts/presets/shared/missing-dependency.d.ts +19 -0
  71. package/dist/.dts/presets/shared/missing-dependency.d.ts.map +1 -0
  72. package/dist/.dts/presets/shared/read-json-report.d.ts +33 -0
  73. package/dist/.dts/presets/shared/read-json-report.d.ts.map +1 -0
  74. package/dist/.dts/presets/shared/terminal-status.d.ts +25 -0
  75. package/dist/.dts/presets/shared/terminal-status.d.ts.map +1 -0
  76. package/dist/.dts/presets/shared/vitest-json-policy.d.ts +20 -0
  77. package/dist/.dts/presets/shared/vitest-json-policy.d.ts.map +1 -0
  78. package/dist/.dts/presets/stylelint.d.ts +17 -0
  79. package/dist/.dts/presets/stylelint.d.ts.map +1 -0
  80. package/dist/.dts/presets/test.d.ts +4 -0
  81. package/dist/.dts/presets/test.d.ts.map +1 -0
  82. package/dist/.dts/presets/typecheck.d.ts +4 -0
  83. package/dist/.dts/presets/typecheck.d.ts.map +1 -0
  84. package/dist/.dts/run-repo-contract.d.ts +38 -0
  85. package/dist/.dts/run-repo-contract.d.ts.map +1 -0
  86. package/dist/.dts/types.d.ts +324 -0
  87. package/dist/.dts/types.d.ts.map +1 -0
  88. package/dist/index.cjs +46 -0
  89. package/dist/index.cjs.map +1 -0
  90. package/dist/index.d.cts +1 -0
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.js +11 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/presets.cjs +30 -0
  95. package/dist/presets.cjs.map +1 -0
  96. package/dist/presets.d.cts +1 -0
  97. package/dist/presets.d.ts +1 -0
  98. package/dist/presets.js +13 -0
  99. package/dist/presets.js.map +1 -0
  100. package/package.json +192 -0
  101. package/presets/package.json +5 -0
  102. package/schemas/evidence.schema.json +253 -0
  103. package/schemas/verdict.schema.json +66 -0
  104. package/src/config/define-repo-contract.ts +38 -0
  105. package/src/config/tokenize-command.ts +214 -0
  106. package/src/config/validate-config.ts +368 -0
  107. package/src/errors.ts +229 -0
  108. package/src/evidence/build-evidence.ts +91 -0
  109. package/src/execution/abort-signals.ts +56 -0
  110. package/src/execution/concurrency-pool.ts +64 -0
  111. package/src/execution/dependency-scheduler.ts +216 -0
  112. package/src/execution/process-tree.ts +107 -0
  113. package/src/execution/run-checks.ts +348 -0
  114. package/src/execution/spawn-check.ts +494 -0
  115. package/src/index.ts +44 -0
  116. package/src/parsing/parse-json.ts +18 -0
  117. package/src/parsing/parse-output.ts +26 -0
  118. package/src/parsing/parse-text.ts +10 -0
  119. package/src/parsing/parse-yaml.ts +40 -0
  120. package/src/policy/run-policies.ts +261 -0
  121. package/src/presets/arethetypeswrong.ts +116 -0
  122. package/src/presets/broken-links.ts +95 -0
  123. package/src/presets/commitlint.ts +77 -0
  124. package/src/presets/dead-code.ts +223 -0
  125. package/src/presets/duplication.ts +137 -0
  126. package/src/presets/e2e.ts +144 -0
  127. package/src/presets/format.ts +25 -0
  128. package/src/presets/index.ts +30 -0
  129. package/src/presets/license.ts +90 -0
  130. package/src/presets/lint.ts +116 -0
  131. package/src/presets/markdownlint.ts +105 -0
  132. package/src/presets/publint.ts +38 -0
  133. package/src/presets/security-deps.ts +142 -0
  134. package/src/presets/security-secrets.ts +93 -0
  135. package/src/presets/shared/error-warning-pass-policy.ts +39 -0
  136. package/src/presets/shared/exit-code-fail-rationale.ts +34 -0
  137. package/src/presets/shared/missing-dependency.ts +31 -0
  138. package/src/presets/shared/read-json-report.ts +46 -0
  139. package/src/presets/shared/terminal-status.ts +70 -0
  140. package/src/presets/shared/vitest-json-policy.ts +95 -0
  141. package/src/presets/stylelint.ts +101 -0
  142. package/src/presets/test.ts +19 -0
  143. package/src/presets/typecheck.ts +25 -0
  144. package/src/run-repo-contract.ts +80 -0
  145. 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
+ }