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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/define-repo-contract.ts","../src/errors.ts","../src/config/tokenize-command.ts","../src/config/validate-config.ts","../src/parsing/parse-json.ts","../src/parsing/parse-text.ts","../src/parsing/parse-yaml.ts","../src/parsing/parse-output.ts","../src/evidence/build-evidence.ts","../src/execution/abort-signals.ts","../src/execution/concurrency-pool.ts","../src/execution/dependency-scheduler.ts","../src/execution/process-tree.ts","../src/execution/spawn-check.ts","../src/execution/run-checks.ts","../src/policy/run-policies.ts","../src/run-repo-contract.ts"],"names":["escape","command","args"],"mappings":"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","file":"index.js","sourcesContent":["import type { CheckSchema, RepoContractConfig, ValidatedCheckSchema } from \"../types.js\"\n\n/**\n * Identity function whose only job is type inference: authoring a config\n * through `defineRepoContract` lets each check's `output` (present or\n * absent, and which format) flow into that same check's `policy` parameter\n * type, without the consumer writing any type annotations themselves. It\n * also statically validates every check's `dependsOn` against its sibling\n * check ids (see `ValidatedCheckSchema`) -- a typo'd or self-referencing id\n * fails to compile here rather than only failing at runtime. Performs no\n * other validation and no cloning -- `runRepoContract` validates whatever\n * config it is ultimately given, whether or not it passed through this\n * function first.\n *\n * `const TChecks` (TypeScript 5.0's `const` type parameter modifier) keeps\n * each check's own configuration -- notably whether `output` is present at\n * all -- from being widened during inference; without it, TypeScript's\n * inference for a `Record` of heterogeneous generic entries does not\n * reliably preserve that per-check shape once a callback property\n * (`policy`) is also present. See `InferParsedValue` in types.ts for the\n * related limitation this does not fully solve.\n *\n * `TChecks` is inferred from the plain, unwrapped `RepoContractConfig<TChecks>`\n * position -- the `ValidatedCheckSchema<TChecks>` constraint on `checks` is\n * intersected in afterward, computed from that already-inferred `TChecks`,\n * rather than substituted in its place. Inferring `TChecks` directly from a\n * mapped/conditional type over itself (as `ValidatedCheckSchema` is) loses\n * the contextual typing every check's `policy` callback otherwise gets --\n * another real, confirmed TypeScript inference limitation, distinct from\n * the `output`-to-`policy` one above.\n * @param config - the config to type-check and return unchanged.\n * @returns the same `config` object, untouched and uncloned.\n */\nexport function defineRepoContract<const TChecks extends CheckSchema>(\n config: RepoContractConfig<TChecks> & { readonly checks: ValidatedCheckSchema<TChecks> },\n): RepoContractConfig<TChecks> {\n return config\n}\n","/**\n * Error hierarchy for repo-contract. Every concrete error carries a stable\n * `code` string for programmatic handling, and never embeds raw\n * stdout/stderr/env values in its message -- only check ids and field names\n * (see SECURITY.md).\n *\n * Three distinct failure classes are kept deliberately separate (see\n * specs/architecture.md):\n * - Structural config problems throw synchronously, before any process\n * spawns (`InvalidRepoContractConfigError`, `InvalidCheckConfigError`).\n * - Anything only discoverable by attempting execution becomes evidence,\n * never a throw (a bad `cwd`, a missing binary -- recorded as\n * `status: \"spawn_error\"` on that check's `CheckEvidence`).\n * - A policy function throwing or rejecting is a bug in consumer code, not\n * a check failing its contract -- it propagates as a rejected\n * `runRepoContract()` promise (`PolicyThrewError`), never silently\n * turned into a failed verdict entry.\n */\nexport abstract class RepoContractError extends Error {\n /** Stable, machine-readable identifier for this error's specific failure mode. */\n abstract readonly code: string\n}\n\n/** The top-level `RepoContractConfig` itself is structurally invalid -- e.g. `checks` is not an object, or `concurrency` is not a positive integer. (A `checks` object with zero entries is deliberately valid: the run produces an empty, passing `Verdict`.) Thrown synchronously by `runRepoContract`, before anything spawns -- not by `defineRepoContract`, which performs no runtime validation of its own (see its own doc comment). */\nexport class InvalidRepoContractConfigError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_INVALID_CONFIG\"`. */\n readonly code = \"REPO_CONTRACT_INVALID_CONFIG\"\n\n constructor(reason: string) {\n super(`Invalid repo-contract config -- ${reason}`)\n this.name = \"InvalidRepoContractConfigError\"\n }\n}\n\n/** One check's `CheckDefinition` is structurally invalid -- e.g. an empty `run`, a `run` string containing an unquoted shell operator without `shell: true`, or a missing `policy`. Thrown synchronously, before that check (or any other) spawns. */\nexport class InvalidCheckConfigError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_INVALID_CHECK_CONFIG\"`. */\n readonly code = \"REPO_CONTRACT_INVALID_CHECK_CONFIG\"\n /** The id of the check whose configuration was invalid. */\n readonly checkId: string\n\n constructor(checkId: string, reason: string) {\n super(`Invalid check config for \"${checkId}\" -- ${reason}`)\n this.name = \"InvalidCheckConfigError\"\n this.checkId = checkId\n }\n}\n\n/**\n * `RunRepoContractOptions.checks` (a partial-run request) names a check id that doesn't exist in\n * the configured `checks`. Unlike a `dependsOn` id (already validated to exist by\n * `validateRepoContractConfig` before any run starts), `options.checks` is only ever checked once\n * `runChecks` actually resolves it -- there is no earlier structural-validation pass for it.\n */\nexport class UnknownCheckIdError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_UNKNOWN_CHECK_ID\"`. */\n readonly code = \"REPO_CONTRACT_UNKNOWN_CHECK_ID\"\n /** The unrecognized check id named in `options.checks`. */\n readonly checkId: string\n\n constructor(checkId: string) {\n super(\n `options.checks names \"${checkId}\", which is not a check id in this config's checks object.`,\n )\n this.name = \"UnknownCheckIdError\"\n this.checkId = checkId\n }\n}\n\n/**\n * A check's `dependsOn` names a check declared *later* in the same `checks` object.\n * `dependsOn` may only reference a check declared earlier -- see `CheckDefinition.dependsOn`'s own\n * doc comment. Declaration order doubles as the required topological order, so this is the only\n * way an invalid dependency graph can arise; a real cycle is structurally impossible once every\n * edge points backward. Thrown synchronously, before any check spawns.\n */\nexport class DependencyDeclaredLaterError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_DEPENDENCY_DECLARED_LATER\"`. */\n readonly code = \"REPO_CONTRACT_DEPENDENCY_DECLARED_LATER\"\n /** The id of the check whose `dependsOn` names a later-declared check. */\n readonly checkId: string\n /** The later-declared check id named in `checkId`'s `dependsOn`. */\n readonly dependencyId: string\n\n constructor(checkId: string, dependencyId: string) {\n super(\n `Invalid check config for \"${checkId}\" -- dependsOn: [\"${dependencyId}\"], but \"${dependencyId}\" ` +\n `is declared later in the checks object. dependsOn may only reference a check declared ` +\n `earlier -- reorder the checks object so \"${dependencyId}\" is declared before \"${checkId}\".`,\n )\n this.name = \"DependencyDeclaredLaterError\"\n this.checkId = checkId\n this.dependencyId = dependencyId\n }\n}\n\n/** A check requested `output: { format: \"yaml\" }` but the optional `yaml` peer dependency is not installed. Thrown when that check's output is parsed, not at config-validation time (parsing only happens after the process has already run). */\nexport class ParserDependencyMissingError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_PARSER_DEPENDENCY_MISSING\"`. */\n readonly code = \"REPO_CONTRACT_PARSER_DEPENDENCY_MISSING\"\n /** The id of the check whose output could not be parsed. */\n readonly checkId: string\n /** The output format that was requested but whose optional peer dependency is missing. */\n readonly format: OutputFormatForError\n\n constructor(checkId: string, format: OutputFormatForError, cause: unknown) {\n super(\n `Check \"${checkId}\" requested output.format: \"${format}\", but the optional \"${format}\" ` +\n `peer dependency is not installed -- run \\`npm install ${format}\\` to enable it.`,\n { cause },\n )\n this.name = \"ParserDependencyMissingError\"\n this.checkId = checkId\n this.format = format\n }\n}\n\n// Kept narrow and local rather than importing OutputFormat from types.ts --\n// only \"yaml\" can ever produce this error today (json/text have no external\n// dependency to be missing), but the field stays named/typed generically in\n// case a future optional format needs the same treatment.\ntype OutputFormatForError = \"yaml\"\n\n/**\n * A check's `policy` function threw synchronously, or returned a `Promise`\n * that later rejected. Both failure modes are wrapped identically. The\n * original thrown/rejected value is preserved verbatim via the native\n * `Error` `cause` chain -- never stringified, summarized, or discarded --\n * so a consumer catching this can still inspect exactly what their own\n * policy code did wrong.\n *\n * A policy throwing never stops any other check's policy from running --\n * every configured check's policy is invoked exactly once regardless of\n * what any other policy does. If more than one policy fails this way in the\n * same run, `runRepoContract()` rejects with a native `AggregateError` whose\n * `errors` array holds one error per failing check -- `PolicyThrewError`,\n * or one of its two narrower siblings below (`PolicyReadUnrequestedOutputError`,\n * `PolicyReadFailedParseValueError`) when the failure matches one of their\n * more specific shapes -- rather than surfacing only the first one found.\n */\nexport class PolicyThrewError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_POLICY_THREW\"`. */\n readonly code = \"REPO_CONTRACT_POLICY_THREW\"\n /** The id of the check whose policy threw or rejected. */\n readonly checkId: string\n\n constructor(checkId: string, cause: unknown) {\n super(`Policy for check \"${checkId}\" threw instead of returning a PolicyResult`, { cause })\n this.name = \"PolicyThrewError\"\n this.checkId = checkId\n }\n}\n\n/**\n * A narrower `runPolicies` throws instead of `PolicyThrewError` when the\n * thrown `TypeError`'s own message shows it came from reading a\n * `result.output` property (`.success`, `.value`, `.error`, or `.format`),\n * and this check's `CheckEvidence.output` really is `undefined` -- which\n * happens only when its config never requested `output: { format: ... }` in\n * the first place (see `CheckEvidence.output` in types.ts). Detected from\n * the `TypeError`'s exact V8-produced message (\"Cannot read properties of\n * undefined (reading '...')\") cross-checked against that `undefined` fact,\n * so it is thrown only for a policy that plausibly hit this exact mistake --\n * any other `TypeError`, or a check that did request a format, still becomes\n * a plain `PolicyThrewError`. This is best-effort inference from the\n * `TypeError`'s message text, not a verified trace back to `result.output`\n * itself -- V8's message carries only the property name, so an unrelated\n * bug that happens to read the same property name off some other\n * `undefined` value can still be misclassified this way (see\n * `unrequestedOutputProperty` in run-policies.ts). `cause` still holds the\n * original `TypeError` verbatim, exactly as `PolicyThrewError` guarantees\n * for every other policy failure, so the true cause remains recoverable\n * either way.\n */\nexport class PolicyReadUnrequestedOutputError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_POLICY_READ_UNREQUESTED_OUTPUT\"`. */\n readonly code = \"REPO_CONTRACT_POLICY_READ_UNREQUESTED_OUTPUT\"\n /** The id of the check whose policy read `result.output` without requesting a format. */\n readonly checkId: string\n\n constructor(checkId: string, property: string, cause: unknown) {\n super(\n `Policy for check \"${checkId}\" read \\`result.output.${property}\\`, but \"${checkId}\" never ` +\n `requested an output format, so \\`result.output\\` is undefined -- add ` +\n `\\`output: { format: \"json\" }\\` (or \"yaml\"/\"text\") to check \"${checkId}\"'s definition to ` +\n `parse its stdout, then narrow with \\`result.output?.success\\` before reading ` +\n `\\`.value\\`/\\`.error\\`.`,\n { cause },\n )\n this.name = \"PolicyReadUnrequestedOutputError\"\n this.checkId = checkId\n }\n}\n\n/**\n * `PolicyThrewError`'s other special case, alongside\n * `PolicyReadUnrequestedOutputError`: this check *did* request\n * `output: { format: ... }`, but that parse itself failed\n * (`result.output.success === false` -- see `ParsedOutputFailure` in types.ts, which\n * has no `value` field, only `error`), and the policy read a property off\n * `result.output.value` anyway without checking `.success` first. Detected the same\n * way `runPolicies` detects the sibling case -- a thrown `TypeError` whose own\n * message shows a property read off `undefined` -- cross-checked against this\n * check's own evidence having `output.success === false`, so a genuinely unrelated\n * `TypeError`, or a check whose parse actually succeeded, still becomes a plain\n * `PolicyThrewError`. Unlike `PolicyReadUnrequestedOutputError`, the read property\n * isn't restricted to a known list here -- `result.output.value`'s shape is whatever\n * the external tool printed, entirely unknown to repo-contract -- so this match is\n * necessarily a little broader. `cause` still holds the original `TypeError`\n * verbatim; the message never repeats `result.output.error`'s own text, which may\n * contain raw stdout content (see SECURITY.md).\n */\nexport class PolicyReadFailedParseValueError extends RepoContractError {\n /** Always `\"REPO_CONTRACT_POLICY_READ_FAILED_PARSE_VALUE\"`. */\n readonly code = \"REPO_CONTRACT_POLICY_READ_FAILED_PARSE_VALUE\"\n /** The id of the check whose policy read `result.output.value` after a failed parse. */\n readonly checkId: string\n\n constructor(checkId: string, property: string, cause: unknown) {\n super(\n `Policy for check \"${checkId}\" read \\`result.output.value.${property}\\`, but \"${checkId}\"'s ` +\n `output failed to parse -- a failed parse has \\`result.output.error\\`, never \\`.value\\` -- ` +\n `check \\`result.output.success\\` before reading \\`.value\\`.`,\n { cause },\n )\n this.name = \"PolicyReadFailedParseValueError\"\n this.checkId = checkId\n }\n}\n","import { InvalidCheckConfigError } from \"../errors.js\"\n\n/**\n * Consumes a `\\`-escape sequence starting at `run[i]`, if one applies here. A backslash escapes the\n * next character (and is itself dropped) when unquoted or inside a double-quoted span; inside\n * single quotes it is a literal character, exactly as in every POSIX shell -- so a Windows path in\n * single quotes survives intact. A trailing backslash with nothing left to escape is not an escape\n * and is kept literally by the caller.\n * @param run - the full command string being tokenized.\n * @param i - the index of the candidate backslash.\n * @param quote - the current quote context (`\"'\"`, `'\"'`, or `null` for unquoted).\n * @returns the escaped character and the index just past the two-character sequence, or `undefined` when no escape applies at `i`.\n */\nfunction consumeEscape(\n run: string,\n i: number,\n quote: \"'\" | '\"' | null,\n): { readonly value: string; readonly next: number } | undefined {\n if (run[i] !== \"\\\\\" || quote === \"'\") return undefined\n // No explicit `i + 1 < run.length` pre-check: a trailing backslash at\n // end-of-string reads `run[i + 1]` as `undefined` (which `noUncheckedIndexedAccess`\n // already types), and the `escaped === undefined` guard below returns\n // `undefined` for exactly that case -- so the caller keeps the backslash\n // literal. One guard, one behavior, nothing to mutation-suppress.\n const escaped: string | undefined = run[i + 1]\n if (escaped === undefined) return undefined\n return { value: escaped, next: i + 2 }\n}\n\n// Single-character shell/multi-command operators rejected outright when they\n// appear unquoted. A literal newline (`\\n`/`\\r`) and the two-character `$(`\n// are handled separately in `rejectUnquotedOperator` -- every entry here is\n// exactly one character, matched by a single Set lookup rather than a chain\n// of per-character `if`s.\nconst UNQUOTED_SHELL_OPERATORS: ReadonlySet<string> = new Set([\";\", \"&\", \"|\", \"`\", \"<\", \">\"])\n\n/**\n * Throws `InvalidCheckConfigError` when the unquoted character `char` at `run[i]` is a\n * shell/multi-command operator repo-contract never interprets (`;`, `&`, `|`, a backtick, `<`, `>`,\n * `$(`, or a literal newline). Returns normally when `char` is legitimate literal argv content --\n * glob characters and a bare `$` deliberately included (see `tokenizeRunString`'s doc comment).\n * @param char - the character under the cursor, already known defined by the caller.\n * @param run - the full command string, needed only to look one character ahead for `$(`.\n * @param i - the index of `char` within `run`.\n * @param checkId - identifies which check's `run` was invalid, used in the thrown error message.\n */\nfunction rejectUnquotedOperator(char: string, run: string, i: number, checkId: string): void {\n const reject = (operator: string): never => {\n throw new InvalidCheckConfigError(\n checkId,\n `run string contains an unquoted \"${operator}\" -- repo-contract never invokes a shell for ` +\n `string-form \"run\", so shell operators are not interpreted. Use \"run: [...]\" (array ` +\n `form) to pass \"${operator}\" as a literal argument, or set \"shell: true\" to opt into ` +\n `real shell execution.`,\n )\n }\n\n if (char === \"\\n\" || char === \"\\r\") reject(\"newline\")\n if (UNQUOTED_SHELL_OPERATORS.has(char)) reject(char)\n if (char === \"$\" && run[i + 1] === \"(\") reject(\"$(\")\n}\n\n/**\n * Splits a `run` string into argv (executable + arguments) without invoking\n * a shell -- no shell operator is ever executed, no glob is ever expanded by\n * this package, no environment variable is ever substituted. The result is\n * deterministic: the same input string always produces the same argv array.\n *\n * Quoting: `'...'` and `\"...\"` group whitespace into a single argument and\n * are themselves stripped from the resulting token. `\\` escapes the next\n * character (and is itself stripped) when unquoted or inside a double-quoted\n * span; inside single quotes it is a literal character, exactly as in every\n * POSIX shell -- so a Windows path in single quotes survives intact.\n * Unquoted whitespace (space, tab) separates tokens.\n *\n * Rejected outright (throws `InvalidCheckConfigError`, checkId identifies\n * which check's `run` was invalid): any *unquoted* occurrence of a true\n * shell/multi-command operator -- `;`, `&`, `|`, a backtick, `$(`, `<`, `>`,\n * or a literal newline. A string containing one of these almost always\n * reflects a mistaken assumption that shell interpretation is happening;\n * the fix is either `run: [...]` (array form, bypasses tokenization\n * entirely) or explicit `shell: true`.\n *\n * Deliberately NOT rejected: glob characters (`*`, `?`, `~`, `[`, `]`, `{`,\n * `}`) and a bare `$`. These are common, legitimate literal argv content --\n * many CLI tools (eslint, prettier, tsc) accept and internally expand glob\n * patterns themselves, e.g. `eslint \"src/**\\/*.ts\"` -- and since no shell is\n * ever invoked here, they carry zero shell-injection risk regardless of\n * where they appear in the string.\n * @param run - the command string to tokenize.\n * @param checkId - identifies which check's `run` was invalid, used in the thrown error message.\n * @returns the tokenized argv (executable followed by its arguments).\n */\nexport function tokenizeRunString(run: string, checkId: string): readonly string[] {\n const tokens: string[] = []\n let current = \"\"\n let hasCurrent = false\n let quote: \"'\" | '\"' | null = null\n let i = 0\n\n // Loosening the `i < run.length` bound to `i <= run.length` is\n // behaviorally invisible: the one extra iteration it would permit reads\n // `run[run.length]`, which is `undefined`, and is caught immediately below\n // by the (itself unmutatable, for the same `noUncheckedIndexedAccess`\n // reason) `char === undefined` check -- confirmed equivalent by exhaustive\n // differential testing against a wide corpus of inputs, not assumed.\n //\n // `iterations` is a second, independent forward-progress bound: every loop\n // pass that doesn't throw/break advances `i` by exactly 1 or 2, so no\n // correct execution ever needs more than `run.length` passes -- a\n // regression that makes `i` stand still or move backward (a `+=`\n // accidentally becoming `-=`), or that wipes the loop body entirely,\n // would otherwise hang forever instead of failing loudly. It is\n // deliberately tracked in the `for` statement's own update/condition\n // clauses rather than inside the loop body: those clauses sit outside the\n // body's own `{ ... }` block, so they keep running (and keep bounding the\n // loop) even under a mutation that replaces the entire body with `{}`,\n // which a bound placed inside the body could not survive.\n //\n // Every mutation of this line's own clauses (loosening either half of the\n // `&&`, swapping it for `||`, or reversing `iterations`' own direction) is\n // itself equivalent as long as the *body* still advances `i` correctly:\n // `i < run.length` alone already terminates the loop at the right point\n // for correct code, with the `iterations` bound only ever mattering in\n // combination with a genuine body regression -- confirmed empirically:\n // mutating this line in isolation (leaving the body untouched) produces no\n // observable difference. It exists precisely to convert the *body*\n // mutations described above from an unkillable hang into a fast, visible\n // test failure, not to be independently killable itself.\n // Stryker disable next-line ConditionalExpression,EqualityOperator,LogicalOperator,AssignmentOperator,BlockStatement -- loosening i < run.length to i <= run.length is behaviorally invisible since the unmutatable char === undefined check right after already catches it, and the iterations bound is a second, independent forward-progress bound tracked in this line's own clauses (outside the body's braces) specifically so a body-emptying mutation can't produce an unkillable hang; every mutation of this line's own clauses is equivalent as long as the body still advances i correctly, confirmed empirically.\n for (let iterations = 0; i < run.length && iterations <= run.length; iterations += 1) {\n const char = run[i]\n // Unreachable given the loop condition (`i < run.length` already\n // guarantees `run[i]` is defined) -- kept only because\n // `noUncheckedIndexedAccess` can't itself express that invariant.\n // Stryker disable next-line ConditionalExpression -- unreachable given the loop's own i < run.length guard already ensures run[i] is defined; kept only because noUncheckedIndexedAccess can't itself express that invariant.\n if (char === undefined) break\n\n if (quote !== null) {\n const escape = consumeEscape(run, i, quote)\n if (escape !== undefined) {\n current += escape.value\n i = escape.next\n continue\n }\n if (char === quote) {\n quote = null\n i += 1\n continue\n }\n current += char\n i += 1\n continue\n }\n\n if (char === \"'\" || char === '\"') {\n quote = char\n hasCurrent = true\n i += 1\n continue\n }\n\n // An unquoted backslash before a newline is a shell line-continuation.\n // repo-contract never interprets one -- and it must be rejected *here*,\n // before `consumeEscape` below, because `consumeEscape` would otherwise\n // treat `\\<newline>` as an ordinary escape: splice a literal newline into\n // the token and advance the cursor past the newline, so the bare-newline\n // rejection further down never runs.\n const nextChar = run[i + 1]\n if (char === \"\\\\\" && (nextChar === \"\\n\" || nextChar === \"\\r\")) {\n throw new InvalidCheckConfigError(\n checkId,\n `run string contains an unquoted line continuation (a backslash before a newline) -- ` +\n `repo-contract never invokes a shell for string-form \"run\". Use \"run: [...]\" (array ` +\n `form), or set \"shell: true\" to opt into real shell execution.`,\n )\n }\n\n const escape = consumeEscape(run, i, null)\n if (escape !== undefined) {\n current += escape.value\n hasCurrent = true\n i = escape.next\n continue\n }\n\n if (char === \" \" || char === \"\\t\") {\n if (hasCurrent) {\n tokens.push(current)\n current = \"\"\n hasCurrent = false\n }\n i += 1\n continue\n }\n\n rejectUnquotedOperator(char, run, i, checkId)\n\n current += char\n hasCurrent = true\n i += 1\n }\n\n if (quote !== null) {\n throw new InvalidCheckConfigError(checkId, `run string has an unterminated ${quote} quote.`)\n }\n if (hasCurrent) tokens.push(current)\n\n if (tokens.length === 0) {\n throw new InvalidCheckConfigError(checkId, \"run string is empty or contains only whitespace.\")\n }\n\n return tokens\n}\n","import {\n DependencyDeclaredLaterError,\n InvalidCheckConfigError,\n InvalidRepoContractConfigError,\n} from \"../errors.js\"\nimport type { OutputFormat, RepoContractConfig } from \"../types.js\"\nimport { tokenizeRunString } from \"./tokenize-command.js\"\n\nconst OUTPUT_FORMATS: readonly OutputFormat[] = [\"json\", \"yaml\", \"text\"]\n\n/**\n * Validates a `RepoContractConfig` structurally and throws before any\n * process spawns. A config with zero checks is valid (an empty `checks`\n * object vacuously satisfies \"every check passed\"); everything else here is\n * a genuine structural problem. Runtime checks are deliberately defensive\n * about field types rather than trusting the compile-time type, since a\n * config can arrive from plain JavaScript or a widened/cast value.\n * @param config - the config to validate; throws if structurally invalid.\n */\nexport function validateRepoContractConfig(config: RepoContractConfig): void {\n // Typed as `unknown` at this internal boundary (not the declared\n // `RepoContractConfig` parameter type) deliberately -- this function's\n // whole purpose is verifying something whose actual runtime shape isn't\n // trusted (plain JavaScript callers, a widened or cast value), so\n // narrowing from `unknown` keeps every check below genuinely meaningful\n // rather than flagged as redundant against a type that's assumed valid.\n const untrusted: unknown = config\n\n if (untrusted === null || typeof untrusted !== \"object\") {\n throw new InvalidRepoContractConfigError(\"config must be an object.\")\n }\n\n const { checks, concurrency } = untrusted as Record<string, unknown>\n\n if (checks === null || typeof checks !== \"object\" || Array.isArray(checks)) {\n throw new InvalidRepoContractConfigError(\n \"checks must be an object mapping check id to check definition.\",\n )\n }\n\n if (concurrency !== undefined) {\n // `typeof concurrency !== \"number\"` is behaviorally redundant with the\n // clause after it: `Number.isInteger` returns `false` (never throws or\n // coerces) for every non-number value, so `!Number.isInteger(...)`\n // alone already rejects every case the typeof check would -- kept for\n // readability at the call site, not because it changes behavior.\n // Stryker disable next-line ConditionalExpression -- the typeof check is redundant with Number.isInteger, which returns false (never throws) for every non-number value, so it's kept only for readability at the call site.\n if (typeof concurrency !== \"number\" || !Number.isInteger(concurrency) || concurrency < 1) {\n throw new InvalidRepoContractConfigError(\n \"concurrency must be a positive integer when provided.\",\n )\n }\n }\n\n for (const [checkId, check] of Object.entries(checks)) {\n validateCheckDefinition(checkId, check)\n }\n\n validateDependencyGraph(checks as Record<string, { dependsOn?: readonly string[] }>)\n}\n\n/**\n * Orchestrates one check's field-level validators. Deliberately just a flat\n * sequence of calls with no branching of its own -- each field's validation\n * logic (and its own complexity) lives in its own small function below, so\n * this function's own cyclomatic complexity (and CRAP score) stays low\n * regardless of how many fields exist to check.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param check - the check definition to validate.\n */\nfunction validateCheckDefinition(checkId: string, check: unknown): void {\n // An integer-like key (\"0\", \"42\", ...) is enumerated by `Object.keys` in\n // ascending numeric order ahead of every other key, regardless of insertion\n // order -- so `validateDependencyGraph`'s `ids`/`indexById` (and the runtime\n // scheduler) would see a different order than the source declares, silently\n // breaking the \"declaration order is the required topological order\" contract.\n if (/^(?:0|[1-9]\\d*)$/.test(checkId)) {\n throw new InvalidCheckConfigError(\n checkId,\n \"check id must not be an integer-like string -- JavaScript would reorder it ahead of every \" +\n \"other key and break the declaration-order-is-topological-order contract.\",\n )\n }\n\n if (check === null || typeof check !== \"object\") {\n throw new InvalidCheckConfigError(checkId, \"check definition must be an object.\")\n }\n\n const fields = check as Record<string, unknown>\n const usesShell = validateShell(checkId, fields.shell)\n validateRun(checkId, fields.run, usesShell)\n validateCwd(checkId, fields.cwd)\n validateEnv(checkId, fields.env)\n validateInheritEnv(checkId, fields.inheritEnv)\n validateTimeoutMs(checkId, fields.timeoutMs)\n validateOutput(checkId, fields.output)\n validateDependsOn(checkId, fields.dependsOn)\n validateIsolated(checkId, fields.isolated)\n validatePolicy(checkId, fields.policy)\n}\n\n/**\n * Returns whether `shell: true` was set, having already validated `shell`'s own type.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param shell - the check's raw `shell` field to validate.\n * @returns `true` if `shell` was set to `true`, `false` otherwise (including when omitted).\n */\nfunction validateShell(checkId: string, shell: unknown): boolean {\n if (shell !== undefined && typeof shell !== \"boolean\") {\n throw new InvalidCheckConfigError(checkId, \"shell must be a boolean when provided.\")\n }\n return shell === true\n}\n\n/**\n * Validates that `run` is a string or an array of strings, then delegates to the matching shape-specific validator.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param run - the check's raw `run` field to validate.\n * @param usesShell - whether `shell: true` was set, from `validateShell`.\n */\nfunction validateRun(checkId: string, run: unknown, usesShell: boolean): void {\n if (typeof run !== \"string\" && !Array.isArray(run)) {\n throw new InvalidCheckConfigError(checkId, \"run must be a string or an array of strings.\")\n }\n if (typeof run === \"string\") {\n validateStringRun(checkId, run, usesShell)\n } else {\n validateArrayRun(checkId, run as unknown[], usesShell)\n }\n}\n\n/**\n * Validates a string-form `run`: tokenizes it (for its side effect of rejecting unquoted shell operators) when no shell is used, or rejects an empty/whitespace-only string when a shell is used.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param run - the check's raw `run` string to validate.\n * @param usesShell - whether `shell: true` was set, from `validateShell`.\n */\nfunction validateStringRun(checkId: string, run: string, usesShell: boolean): void {\n if (!usesShell) {\n // Tokenized here for its side effect (throws on an empty string or an\n // unquoted shell operator); the result is recomputed at spawn time,\n // since tokenization is cheap and pure. The first token is additionally\n // checked for emptiness: `run: \"''\"` / `run: \"' ' build\"` tokenizes to\n // a non-empty array whose executable is `\"\"`, which would otherwise fail\n // only later as an opaque spawn error rather than a synchronous config\n // error like every other structurally-broken `run`.\n const [executable] = tokenizeRunString(run, checkId)\n // `noUncheckedIndexedAccess` types the destructured `executable` as\n // `string | undefined`, but `tokenizeRunString` throws on any input that\n // would produce an empty token array (it rejects an empty or\n // whitespace-only string outright), so `executable` is always a real\n // string by the time control reaches here. The `?.` exists only to\n // satisfy the compiler; removing it is behaviourally equivalent, so the\n // OptionalChaining mutant here is an equivalent mutant with no test that\n // could ever distinguish it.\n // Stryker disable next-line OptionalChaining -- equivalent mutant: `executable` is provably always a string here (see comment above), so `executable?.trim()` and `executable.trim()` are identical.\n if (executable?.trim().length === 0) {\n throw new InvalidCheckConfigError(\n checkId,\n \"run string's first token (the executable) is empty or contains only whitespace.\",\n )\n }\n return\n }\n if (run.trim().length === 0) {\n throw new InvalidCheckConfigError(checkId, \"run string is empty or contains only whitespace.\")\n }\n}\n\n/**\n * Validates an array-form `run`: rejects it outright when a shell is used (array args can't express shell operators), and otherwise requires a non-empty array of strings.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param items - the check's raw `run` array to validate.\n * @param usesShell - whether `shell: true` was set, from `validateShell`.\n */\nfunction validateArrayRun(checkId: string, items: readonly unknown[], usesShell: boolean): void {\n if (usesShell) {\n throw new InvalidCheckConfigError(\n checkId,\n \"shell: true requires run to be a string -- an array of arguments is individually \" +\n \"escaped for the shell and cannot express shell operators like pipes or redirects, \" +\n \"so combining the two forms would silently do nothing useful.\",\n )\n }\n if (items.length === 0) {\n throw new InvalidCheckConfigError(checkId, \"run array must not be empty.\")\n }\n if (items.some((item) => typeof item !== \"string\")) {\n throw new InvalidCheckConfigError(checkId, \"run array must contain only strings.\")\n }\n // The first element is the executable; an empty or whitespace-only value\n // there fails only later as an opaque spawn error, so reject it here like\n // every other structurally-broken `run`.\n if ((items[0] as string).trim().length === 0) {\n throw new InvalidCheckConfigError(\n checkId,\n \"run array's first element (the executable) is empty or contains only whitespace.\",\n )\n }\n}\n\n/**\n * Validates that `cwd`, if provided, is a string.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param cwd - the check's raw `cwd` field to validate.\n */\nfunction validateCwd(checkId: string, cwd: unknown): void {\n if (cwd !== undefined && typeof cwd !== \"string\") {\n throw new InvalidCheckConfigError(checkId, \"cwd must be a string when provided.\")\n }\n}\n\n/**\n * Validates that `env`, if provided, is an object mapping names to string values.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param env - the check's raw `env` field to validate.\n */\nfunction validateEnv(checkId: string, env: unknown): void {\n if (env === undefined) return\n if (env === null || typeof env !== \"object\" || Array.isArray(env)) {\n throw new InvalidCheckConfigError(\n checkId,\n \"env must be an object mapping name to value when provided.\",\n )\n }\n for (const [key, value] of Object.entries(env)) {\n if (typeof value !== \"string\") {\n throw new InvalidCheckConfigError(checkId, `env[\"${key}\"] must be a string.`)\n }\n }\n}\n\n/**\n * Validates that `inheritEnv`, if provided, is a boolean.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param inheritEnv - the check's raw `inheritEnv` field to validate.\n */\nfunction validateInheritEnv(checkId: string, inheritEnv: unknown): void {\n if (inheritEnv !== undefined && typeof inheritEnv !== \"boolean\") {\n throw new InvalidCheckConfigError(checkId, \"inheritEnv must be a boolean when provided.\")\n }\n}\n\n/**\n * Validates that `timeoutMs`, if provided, is a positive finite number.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param timeoutMs - the check's raw `timeoutMs` field to validate.\n */\nfunction validateTimeoutMs(checkId: string, timeoutMs: unknown): void {\n if (timeoutMs === undefined) return\n // Same redundancy as concurrency's typeof check above: `Number.isFinite`\n // returns `false` (never throws or coerces) for every non-number value.\n // Stryker disable next-line ConditionalExpression -- same redundancy as the concurrency check above: the typeof check is redundant with Number.isFinite, which returns false for every non-number value.\n if (typeof timeoutMs !== \"number\" || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new InvalidCheckConfigError(checkId, \"timeoutMs must be a positive number when provided.\")\n }\n}\n\n/**\n * Validates that `output`, if provided, is an object whose `format` is one of the supported `OUTPUT_FORMATS`.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param output - the check's raw `output` field to validate.\n */\nfunction validateOutput(checkId: string, output: unknown): void {\n if (output === undefined) return\n if (output === null || typeof output !== \"object\") {\n throw new InvalidCheckConfigError(checkId, \"output must be an object when provided.\")\n }\n const { format } = output as Record<string, unknown>\n // `typeof format !== \"string\"` is behaviorally redundant with the clause\n // after it: `Array#includes` uses strict equality, so a non-string\n // `format` can never match an entry of `OUTPUT_FORMATS` (all strings),\n // meaning `!OUTPUT_FORMATS.includes(...)` alone already rejects every\n // case the typeof check would.\n // Stryker disable next-line ConditionalExpression -- the typeof check is redundant with Array#includes' strict equality against OUTPUT_FORMATS (all strings), so a non-string format can never match regardless of the typeof check.\n if (typeof format !== \"string\" || !OUTPUT_FORMATS.includes(format as OutputFormat)) {\n throw new InvalidCheckConfigError(\n checkId,\n `output.format must be one of ${OUTPUT_FORMATS.map((f) => `\"${f}\"`).join(\", \")}.`,\n )\n }\n}\n\n/**\n * Validates that `policy` is a function.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param policy - the check's raw `policy` field to validate.\n */\nfunction validatePolicy(checkId: string, policy: unknown): void {\n if (typeof policy !== \"function\") {\n throw new InvalidCheckConfigError(checkId, \"policy must be a function.\")\n }\n}\n\n/**\n * Only this check's own shape (array of strings) and self-dependency --\n * both need nothing beyond this one check's own id/field. Self-dependency\n * is a degenerate one-node cycle, but is deliberately caught here rather\n * than deferred to `validateDependencyGraph`'s cycle detector below: it\n * needs no other check's data, gives a clearer message, and fails in this\n * same per-check pass instead of waiting for a second one.\n * @param checkId - identifies which check is being validated, used in thrown error messages and to detect self-dependency.\n * @param dependsOn - the check's raw `dependsOn` field to validate.\n */\nfunction validateDependsOn(checkId: string, dependsOn: unknown): void {\n if (dependsOn === undefined) return\n if (!Array.isArray(dependsOn) || dependsOn.some((id) => typeof id !== \"string\")) {\n throw new InvalidCheckConfigError(\n checkId,\n \"dependsOn must be an array of check ids (strings) when provided.\",\n )\n }\n if (dependsOn.includes(checkId)) {\n throw new InvalidCheckConfigError(checkId, \"dependsOn must not include the check's own id.\")\n }\n}\n\n/**\n * Validates that `isolated`, if provided, is a boolean.\n * @param checkId - identifies which check is being validated, used in thrown error messages.\n * @param isolated - the check's raw `isolated` field to validate.\n */\nfunction validateIsolated(checkId: string, isolated: unknown): void {\n if (isolated !== undefined && typeof isolated !== \"boolean\") {\n throw new InvalidCheckConfigError(checkId, \"isolated must be a boolean when provided.\")\n }\n}\n\n/**\n * Whole-graph properties that can't be checked per-check in isolation --\n * run once, after every check's own `dependsOn` shape has already been\n * validated by `validateDependsOn` above, so this can walk every\n * `dependsOn` array without re-checking its shape.\n *\n * Declaration order in the `checks` object doubles as the required topological order (see\n * `CheckDefinition.dependsOn`'s own doc comment): every `dependsOn` id must name a check declared\n * earlier* than the check declaring it. This single backward-reference check subsumes what used\n * to be two separate passes (an unknown-id check, then a DFS cycle detector) -- a cycle is no\n * longer expressible at all once every edge is required to point backward, so there is nothing\n * left for a separate cycle detector to catch. `isolated` needs no validation-time edge computation\n * here: its own implied positional edges (see `run-checks.ts`'s `dependencyIndexesFor`) always\n * point backward (to earlier-declared checks) or are pointed at by later-declared checks, by\n * construction, so they can never introduce a cycle either.\n * @param checks - the full check map, keyed by check id, in declaration order.\n */\nexport function validateDependencyGraph(\n checks: Record<string, { dependsOn?: readonly string[] }>,\n): void {\n const ids = Object.keys(checks)\n const indexById = new Map(ids.map((id, index) => [id, index]))\n\n for (const [index, id] of ids.entries()) {\n const check = checks[id]\n // `id` always comes from `Object.keys(checks)`, so `checks[id]` can\n // never actually be undefined -- kept only because\n // `noUncheckedIndexedAccess` can't itself express that invariant.\n // Stryker disable next-line OptionalChaining -- id always comes from Object.keys(checks), so check can never be undefined here; the optional chaining exists only to satisfy noUncheckedIndexedAccess.\n for (const depId of check?.dependsOn ?? []) {\n const depIndex = indexById.get(depId)\n if (depIndex === undefined) {\n throw new InvalidCheckConfigError(id, `dependsOn references unknown check id \"${depId}\".`)\n }\n if (depIndex >= index) {\n throw new DependencyDeclaredLaterError(id, depId)\n }\n }\n }\n}\n","import type { ParsedOutput } from \"../types.js\"\n\n/**\n * Parses `stdout` as JSON. A malformed-JSON failure preserves the raw stdout on the caller's `CheckEvidence` unchanged and never throws -- the failure is reported as data, per repo-contract's \"parsing failures are deterministic evidence, never a silent reinterpretation\" contract.\n * @param stdout - the check's raw stdout to parse as JSON.\n * @returns the parsed value on success, or the unparsed failure with `stdout` preserved.\n */\nexport function parseJson(stdout: string): ParsedOutput<unknown> {\n try {\n return { format: \"json\", success: true, value: JSON.parse(stdout) }\n } catch (error) {\n return {\n format: \"json\",\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n","import type { ParsedOutput } from \"../types.js\"\n\n/**\n * \"Parses\" `stdout` as text -- a trimmed passthrough that always succeeds. Exists as an explicit, self-documenting alternative to omitting `output` entirely: `format: \"text\"` gives a consumer `result.output.value` as a plain trimmed string, rather than needing to read `result.stdout` and trim it themselves every time.\n * @param stdout - the check's raw stdout to trim.\n * @returns the always-successful result wrapping the trimmed string.\n */\nexport function parseText(stdout: string): ParsedOutput<string> {\n return { format: \"text\", success: true, value: stdout.trim() }\n}\n","import type * as Yaml from \"yaml\"\nimport { ParserDependencyMissingError } from \"../errors.js\"\nimport type { ParsedOutput } from \"../types.js\"\n\n/**\n * Parses `stdout` as YAML using the optional `yaml` peer dependency, loaded\n * via a dynamic `import()` only when a check actually requests\n * `output.format: \"yaml\"` -- consumers who never use YAML never pay for it.\n * If `yaml` cannot be loaded at all (not installed, or any other import-time\n * failure), throws `ParserDependencyMissingError` with the original failure\n * preserved as `cause`, rather than guessing at a specific module-resolution\n * error code that could vary across Node versions and module systems (ESM\n * vs. the package's own CJS build output).\n *\n * A malformed-YAML failure (the dependency loaded fine, but `stdout` isn't\n * valid YAML) is a normal parse failure, not a missing-dependency error --\n * it's returned as `{ success: false }`, following the same \"preserve raw\n * output, report failure as data\" contract as `parseJson`.\n * @param stdout - the check's raw stdout to parse as YAML.\n * @param checkId - identifies which check's output is being parsed, used in the thrown `ParserDependencyMissingError`.\n * @returns the parsed value on success, or the unparsed failure with `stdout` preserved.\n */\nexport async function parseYaml(stdout: string, checkId: string): Promise<ParsedOutput<unknown>> {\n let yamlModule: typeof Yaml\n try {\n yamlModule = await import(\"yaml\")\n } catch (error) {\n throw new ParserDependencyMissingError(checkId, \"yaml\", error)\n }\n\n try {\n return { format: \"yaml\", success: true, value: yamlModule.parse(stdout) }\n } catch (error) {\n return {\n format: \"yaml\",\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n","import type { OutputFormat, ParsedOutput } from \"../types.js\"\nimport { parseJson } from \"./parse-json.js\"\nimport { parseText } from \"./parse-text.js\"\nimport { parseYaml } from \"./parse-yaml.js\"\n\n/**\n * Dispatches to the parser for `format`. Never throws for a malformed-output parse failure -- see each parser's own documentation. May throw `ParserDependencyMissingError` for `format: \"yaml\"` if the optional `yaml` peer dependency is unavailable.\n * @param format - which parser to dispatch to.\n * @param stdout - the check's raw stdout to parse.\n * @param checkId - identifies which check's output is being parsed, used in the `ParserDependencyMissingError` thrown for a missing `yaml` dependency.\n * @returns the parsed output produced by the selected parser.\n */\nexport async function parseOutput(\n format: OutputFormat,\n stdout: string,\n checkId: string,\n): Promise<ParsedOutput<unknown>> {\n switch (format) {\n case \"json\":\n return parseJson(stdout)\n case \"yaml\":\n return parseYaml(stdout, checkId)\n case \"text\":\n return parseText(stdout)\n }\n}\n","import type { CheckExecutionEntry } from \"../execution/run-checks.js\"\nimport type { ParserDependencyMissingError } from \"../errors.js\"\nimport { parseOutput } from \"../parsing/parse-output.js\"\nimport type { CheckDefinition, CheckEvidence, Evidence } from \"../types.js\"\n\n/** One check's id, its original definition, and its final evidence (parsed output attached, if requested) -- the parsed-output counterpart to `CheckExecutionEntry`, consumed directly by the policy phase so it never needs to look a check up by id either. */\nexport type ParsedCheckEntry = readonly [string, CheckDefinition, CheckEvidence]\n\n// Not exported -- nothing outside this file references it by name; callers\n// (run-repo-contract.ts) destructure `{ evidence, entries }` directly.\ninterface BuiltEvidence {\n readonly evidence: Evidence\n readonly entries: readonly ParsedCheckEntry[]\n}\n\n/**\n * Attaches parsed output (if requested) to every check's raw execution\n * evidence and assembles the versioned, immutable `Evidence` object for the\n * run as a whole. Also returns the same information as a flat entries array\n * (see `ParsedCheckEntry`) for the policy phase to consume directly -- by\n * the time this function returns, every check's evidence -- including every\n * sibling check's -- is fully assembled; nothing here is generated lazily\n * or streamed, which is what makes it safe for a policy to read the full\n * `evidence` object, not just its own check's `result` (see\n * specs/architecture.md).\n * @param results - each check's id, definition, and raw execution evidence from the run phase\n * @param startedAt - when the overall run began, recorded on the assembled `Evidence`\n * @param completedAt - when the overall run finished, used with `startedAt` to compute the assembled `Evidence`'s `durationMs`\n * @returns the assembled `Evidence` for the whole run, plus the same checks as a flat `ParsedCheckEntry` array for the policy phase\n */\nexport async function buildEvidence(\n results: readonly CheckExecutionEntry[],\n startedAt: Date,\n completedAt: Date,\n): Promise<BuiltEvidence> {\n // Mirrors src/policy/run-policies.ts's own thrown-error aggregation: each\n // mapped entry catches its own failure and records it rather than letting\n // it reject `Promise.all` directly, so that two checks concurrently\n // requesting an output format whose parser dependency is missing (e.g.\n // \"yaml\" without the optional peer dependency installed) are both\n // reported, not just whichever rejected first.\n const thrown: unknown[] = []\n\n const entries = await Promise.all(\n results.map(async ([checkId, check, raw]): Promise<ParsedCheckEntry> => {\n if (check.output === undefined) return [checkId, check, raw]\n try {\n const output = await parseOutput(check.output.format, raw.stdout, checkId)\n return [checkId, check, { ...raw, output }]\n } catch (error) {\n thrown.push(error)\n // Never actually consumed -- every branch below that follows a\n // non-empty `thrown` throws before `entries` is read.\n // Stryker disable next-line ArrayDeclaration -- this tuple is never read: both branches below that can run when `thrown` is non-empty (thrown.length === 1 or > 1) throw before `entries` -- the array this returns into -- is ever returned to a caller.\n return [checkId, check, raw]\n }\n }),\n )\n\n if (thrown.length === 1) {\n const [only] = thrown as [ParserDependencyMissingError]\n throw only\n }\n // \"> 1\" vs. \">= 1\" are equivalent here for the same reason as the\n // identical comparison in src/policy/run-policies.ts: the `=== 1` early\n // return just above already consumes the length-1 case, so by the time\n // this line runs, thrown.length is never exactly 1 -- either 0 (falls\n // through below either way) or >= 2 (takes this branch either way).\n // Stryker disable next-line EqualityOperator -- \"> 1\" vs. \">= 1\" are equivalent here given the early return just above: by the time this line runs, thrown.length is never exactly 1 (either 0 or >= 2 either way), documented so a future refactor that removes the early return doesn't quietly widen this comparison's real behavior without anyone noticing.\n if (thrown.length > 1) {\n throw new AggregateError(thrown, `${String(thrown.length)} check output(s) failed to parse.`)\n }\n\n // `Evidence[\"checks\"]` is a mapped type over the *specific* CheckSchema a\n // consumer's config declares (see runRepoContract's own generic\n // signature) -- internal pipeline code works with the erased/default\n // CheckSchema instead, whose `keyof` is a plain `string`. The precise\n // generic Evidence<TChecks> is asserted only once, at runRepoContract's\n // own public boundary.\n const evidence = {\n version: 1,\n startedAt: startedAt.toISOString(),\n completedAt: completedAt.toISOString(),\n durationMs: completedAt.getTime() - startedAt.getTime(),\n checks: Object.fromEntries(\n entries.map(([checkId, , checkEvidence]) => [checkId, checkEvidence]),\n ),\n } as Evidence\n\n return { evidence, entries }\n}\n","/** The composed signal, and a `dispose` to release any resources this composition holds once it's no longer needed. */\ninterface ComposedSignal {\n readonly signal: AbortSignal\n readonly dispose: () => void\n}\n\n/**\n * Combines multiple signals into one that aborts as soon as any input does.\n * Prefers the native `AbortSignal.any()` where available; falls back to a\n * manual composition for engines without it. `AbortSignal.any` landed in\n * Node 20.3.0 -- genuinely newer than this package's `engines.node >=20.0.0`\n * floor, so the fallback is live code for a real gap (a 20.0-20.2 patch\n * release), not dead code kept out of caution.\n *\n * The native path's `dispose` is a no-op -- `AbortSignal.any` manages its own\n * source-signal listeners internally and doesn't leak them. The manual\n * fallback's `dispose` removes the \"abort\" listeners it added to each input\n * `signal`; without calling it, a long-lived input (like a whole run's shared\n * `AbortSignal`, composed fresh for every check the run spawns) would\n * accumulate one permanent, never-removed listener per composed signal for\n * the rest of its own lifetime, regardless of whether that particular\n * composition ever actually needed to abort. Callers should call `dispose()`\n * once the composed signal is no longer needed, whether or not it ever\n * aborted.\n * @param signals - the signals to combine; the composed signal aborts as soon as any one of them does\n * @returns the composed signal (aborting with that input's abort reason as soon as any signal in `signals` aborts), and a `dispose` to release this composition's own resources\n */\nexport function composeSignals(signals: readonly AbortSignal[]): ComposedSignal {\n if (typeof AbortSignal.any === \"function\") {\n // Expression-bodied no-op, not `() => {}` (an empty block body trips\n // @typescript-eslint/no-empty-function): the native AbortSignal.any\n // manages its own source-signal listeners internally and doesn't leak\n // them, so there's nothing for this composition's own dispose to\n // release.\n return { signal: AbortSignal.any(signals as AbortSignal[]), dispose: () => undefined }\n }\n const controller = new AbortController()\n const attached: { signal: AbortSignal; listener: () => void }[] = []\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason)\n break\n }\n const listener = (): void => {\n controller.abort(signal.reason)\n }\n signal.addEventListener(\"abort\", listener)\n attached.push({ signal, listener })\n }\n return {\n signal: controller.signal,\n dispose: () => {\n for (const { signal, listener } of attached) signal.removeEventListener(\"abort\", listener)\n },\n }\n}\n","/**\n * Runs `worker` over `items` with at most `concurrency` invocations in\n * flight at once, preserving each result at its original index regardless\n * of completion order. Has no knowledge of aborting, killing, or check\n * evidence -- a pure, reusable bounded-parallelism primitive; abort-\n * awareness lives entirely in the caller's `worker` function (see\n * spawn-check.ts).\n * @param items - the items to process, each passed to `worker` along with its index\n * @param concurrency - the maximum number of `worker` calls allowed in flight at once\n * @param worker - the async function run per item; its resolved value becomes that item's result\n * @returns the results, one per item, in the same order as `items` regardless of completion order\n */\nexport async function runWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n worker: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = []\n const entries = items.map((item, index) => [item, index] as const)\n const iterator = entries[Symbol.iterator]()\n\n // The first worker rejection is recorded and rethrown after every\n // still-running sibling worker has settled, rather than let `Promise.all`\n // reject eagerly while other `runNext` loops keep awaiting workers whose\n // later rejections would then have no handler -- an `unhandledRejection`\n // (fatal under Node's `--unhandled-rejections=throw`). The error itself is\n // still propagated verbatim, matching how `dependency-scheduler.ts`\n // forwards a worker rejection unwrapped.\n let firstRejection: { readonly error: unknown } | undefined\n\n /**\n *\n */\n async function runNext(): Promise<void> {\n for (let next = iterator.next(); !next.done; next = iterator.next()) {\n if (firstRejection !== undefined) return\n const [item, index] = next.value\n try {\n results[index] = await worker(item, index)\n } catch (error) {\n firstRejection ??= { error }\n return\n }\n }\n }\n\n // `Math.max`/`Math.min` both propagate a `NaN` argument straight through\n // (NaN poisons them), so a caller-supplied `concurrency` of `NaN` would\n // otherwise survive as `workerCount`, and `Array.from({ length: NaN }, ...)`\n // spec-clamps a NaN length to zero -- silently returning an empty result\n // array for every item, with no error to explain why. `validateRepoContractConfig`\n // already rejects a non-integer/NaN `concurrency` before it can reach this\n // internal, non-exported function via the public API, but this guard\n // defends the primitive itself rather than relying solely on that upstream promise.\n const effectiveConcurrency = Number.isFinite(concurrency) ? concurrency : 1\n const workerCount = Math.max(1, Math.min(effectiveConcurrency, items.length))\n await Promise.all(Array.from({ length: workerCount }, () => runNext()))\n if (firstRejection !== undefined) {\n // Propagated verbatim, exactly as Promise.all would have surfaced it and\n // as dependency-scheduler.ts forwards a worker rejection unwrapped.\n throw firstRejection.error\n }\n return results\n}\n","/**\n * Runs `worker` over `items` with at most `concurrency` invocations in\n * flight at once, like `concurrency-pool.ts`'s `runWithConcurrency`, but\n * additionally respecting a dependency graph: an item whose\n * `dependencyIndexes` are non-empty is not started until every one of\n * those indexes has settled. Has no knowledge of checks, evidence, or\n * abort semantics -- a pure, reusable bounded-parallelism-plus-ordering\n * primitive, kept in its own file rather than folded into\n * `concurrency-pool.ts` so that file's simpler, more heavily-used flat\n * primitive stays untouched.\n *\n * A reactive, event-driven scheduler (conceptually Kahn's algorithm run\n * incrementally): a completing item immediately re-evaluates and unblocks\n * its own dependents, with no artificial \"wave\" boundary -- a wave/layer\n * design was considered and rejected because it would stall a dependent\n * whose single dependency finished early behind an unrelated slow item\n * sharing its layer, a real regression for a diamond or fan-out shape.\n *\n * The caller is responsible for ensuring `dependencyIndexes` describes an\n * acyclic graph (see `validate-config.ts`'s cycle detection) -- this\n * function's own stall guard below exists only as defense in depth against\n * that invariant being violated, not as the primary means of catching it.\n * @param items - the items to process, each passed to `worker` and `dependencyIndexes` along with its index\n * @param concurrency - the maximum number of `worker` calls allowed in flight at once\n * @param dependencyIndexes - given an item and its index, returns the indexes into `items` that must settle first\n * @param worker - the async function run per item once its dependencies have settled; its resolved value becomes that item's result\n * @returns the results, one per item, in the same order as `items` regardless of completion order\n */\nexport async function runWithConcurrencyGraph<T, R>(\n items: readonly T[],\n concurrency: number,\n dependencyIndexes: (item: T, index: number) => readonly number[],\n worker: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n if (items.length === 0) return []\n\n // `dependencyIndexes` is called exactly once per item and snapshotted --\n // never invoked again below -- so the graph this function schedules\n // against is fixed for the whole run, even if the callback isn't\n // perfectly deterministic or does nontrivial work. Each item's own\n // indexes are deduplicated defensively (a caller-supplied duplicate,\n // e.g. `dependsOn: [\"a\", \"a\"]`, would otherwise double-decrement its\n // dependent's `remaining` count -- harmless by coincidence today, but\n // not a graph shape this primitive should silently rely on staying\n // harmless).\n const dependencies = items.map((item, index) =>\n // An out-of-range index (`< 0`, `>= items.length`, or non-integer) is\n // dropped here, at the single snapshot point, rather than carried into\n // `remaining` below: it can never be decremented (nothing pushes the\n // dependent onto a `dependents[outOfRange]` bucket that doesn't exist),\n // so keeping it would leave `remaining[index]` permanently above zero\n // and stall the whole run with a bogus \"not acyclic\" rejection. Dropping\n // it is what \"tolerates an out-of-range caller-supplied dependency\n // index\" (see dependency-scheduler.test.ts) actually means.\n [...new Set(dependencyIndexes(item, index))].filter(\n (depIndex) => Number.isInteger(depIndex) && depIndex >= 0 && depIndex < items.length,\n ),\n )\n const remaining = dependencies.map((indexes) => indexes.length)\n // Each entry's own initial value is unobservable regardless of its exact\n // contents: confirmed empirically (diamond, fan-out, and no-dependency\n // shapes) that seeding it with garbage produces identical scheduling\n // results either way, since a stray non-index entry only ever causes a\n // harmless, unused `remaining[...]` property to be created when\n // processed, never affecting `ready` or the final results.\n // Stryker disable next-line ArrayDeclaration -- each dependents[] entry's initial contents are unobservable regardless of value, confirmed empirically across diamond, fan-out, and no-dependency shapes that seeding it with garbage produces identical scheduling results.\n const dependents: number[][] = items.map(() => [])\n dependencies.forEach((indexes, index) => {\n for (const depIndex of indexes) {\n // `depIndex` is already guaranteed in-range: the snapshot above\n // filters every out-of-range caller-supplied index out before this\n // loop. The `?.` only satisfies `noUncheckedIndexedAccess` (`dependents`\n // is built with exactly `items.length` entries, one per valid index),\n // matching this function's other index guards -- see \"tolerates an\n // out-of-range caller-supplied dependency index\" in\n // dependency-scheduler.test.ts.\n dependents[depIndex]?.push(index)\n }\n })\n\n const ready: number[] = remaining.flatMap((count, index) => (count === 0 ? [index] : []))\n // `Math.max`/`Math.min` both propagate a `NaN` argument straight through to\n // their result (NaN poisons them), so a caller-supplied `concurrency` of\n // `NaN` would otherwise survive `Math.max(1, concurrency)` below as `NaN`\n // itself, making `active < effectiveConcurrency` always false and hanging\n // this function's returned promise forever, with no worker ever launched\n // and no error to explain why. `validateRepoContractConfig` already rejects\n // a non-integer/NaN `concurrency` before it can reach this internal,\n // non-exported function via the public API, but this guard defends the\n // primitive itself rather than relying solely on that upstream promise.\n const concurrencyIsUsable = Number.isFinite(concurrency)\n // Pre-sizing here only matters while the run is in flight (so `results[index]\n // = result` below never needs to extend the array); by the time this promise\n // resolves, `settled === items.length` guarantees every index has already\n // been assigned exactly once (see the property test \"every item runs\n // exactly once ...\" in dependency-scheduler.property.test.ts), which turns\n // any initially-sparse array just as dense as a pre-sized one -- the two are\n // indistinguishable in the returned result. Confirmed empirically: an\n // unexempted run showed the `ObjectLiteral` replacement (`{}`, i.e. starting\n // from a zero-length array instead) survives.\n // Stryker disable next-line ObjectLiteral -- pre-sizing only matters while the run is in flight; by completion every index is guaranteed filled exactly once, so a sparse vs. dense start is indistinguishable in the returned result, confirmed empirically that the unexempted {} mutant survives.\n const results: R[] = Array.from({ length: items.length })\n const effectiveConcurrency = concurrencyIsUsable ? Math.max(1, concurrency) : 1\n let active = 0\n let settled = 0\n let done = false\n\n return new Promise<R[]>((resolvePromise, rejectPromise) => {\n // `fail` is a hoisted function declaration, usable below before its\n // textual definition.\n // A graph where *every* item has at least one dependency (the whole\n // graph forms a cycle, not just part of it) never launches a single\n // worker -- the post-settlement stall check below can only ever run\n // once at least one worker has settled, so this covers the one stall\n // shape that check cannot: a stall from the very start.\n if (ready.length === 0) {\n fail(\n new Error(\n \"runWithConcurrencyGraph: stalled before starting -- the dependency graph passed \" +\n \"in is not acyclic.\",\n ),\n )\n return\n }\n\n /**\n *\n * @param error - the error to reject the returned promise with, propagated verbatim (unwrapped)\n */\n function fail(error: unknown): void {\n // A second call after the promise has already settled is harmless\n // even without this guard: native Promise resolution is idempotent,\n // so a later rejectPromise call would have no observable effect on\n // the promise's own outcome regardless -- kept for clarity/intent,\n // not because it changes behavior.\n // Stryker disable next-line ConditionalExpression -- native Promise resolution is idempotent, so a second rejectPromise call after settling has no observable effect either way; kept only for clarity/intent, not because it changes behavior.\n if (done) return\n done = true\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- propagated verbatim, not wrapped: matches runWithConcurrency's own existing behavior (a worker rejection passes straight through Promise.all unmodified), which this function is meant to be a drop-in graph-aware replacement for.\n rejectPromise(error)\n }\n\n // launchNext is only ever called (both its initial call below and its\n // own recursive call inside .then()) at a point already known to have\n // done === false -- every call site is guarded by a done check before\n // reaching it -- so an equivalent internal guard here was removed\n // rather than kept only to be marked equivalent.\n /**\n *\n */\n function launchNext(): void {\n // Loosening `ready.length > 0` (e.g. to always-true) is behaviorally\n // invisible: the loop would simply enter once more with `ready`\n // genuinely empty, `ready.shift()` would return `undefined` (a safe\n // no-op on an empty array), and the very next line's `index ===\n // undefined` check -- itself unmutatable for the same reason --\n // immediately breaks out, identically to the condition correctly\n // stopping the loop one iteration earlier. `ready.length > 0` also\n // already guarantees `.shift()` returns a real index, and every\n // index this function ever pushes onto `ready` is a valid index into\n // `items`/`dependents`/`remaining` (all built with exactly\n // `items.length` entries) -- these guards are kept only because\n // `noUncheckedIndexedAccess` can't itself express any of these\n // invariants, confirmed equivalent by exhaustive differential\n // testing, not assumed.\n // Stryker disable ConditionalExpression,EqualityOperator,LogicalOperator,BlockStatement -- loosening the while condition or its guard checks to always-true is behaviorally invisible: the loop just runs one harmless extra iteration where ready.shift() returns undefined and the immediately-following index === undefined check breaks back out, and every index ever pushed onto ready is already guaranteed valid so the equivalent item === undefined guard is confirmed equivalent by exhaustive differential testing, not assumed.\n while (active < effectiveConcurrency && ready.length > 0) {\n const index = ready.shift()\n if (index === undefined) break\n const item = items[index]\n if (item === undefined) continue\n // Stryker restore all\n active += 1\n worker(item, index)\n .then((result) => {\n results[index] = result\n active -= 1\n settled += 1\n // `dependents[index]` can never actually be undefined -- `dependents`\n // is built above with exactly `items.length` entries, one per valid\n // index, same invariant as `launchNext`'s own guards. Confirmed\n // empirically, not just by analogy: un-exempting this mutant and\n // running Stryker scoped to this file showed the `?? []` fallback's\n // own content (`ArrayDeclaration`) is NoCoverage -- the fallback\n // branch never executes at all. The surrounding `BlockStatement`/\n // `LogicalOperator` mutants on this same line are not exempted:\n // confirmed Killed under the same run, so they stay live.\n // Stryker disable next-line ArrayDeclaration -- dependents[index] can never actually be undefined (dependents is built with exactly items.length entries, one per valid index), confirmed empirically -- not just by analogy -- that un-exempting this mutant and running Stryker scoped to this file shows the ?? [] fallback's own ArrayDeclaration content is NoCoverage, i.e. the fallback branch never executes.\n for (const dependentIndex of dependents[index] ?? []) {\n const nextRemaining = (remaining[dependentIndex] ?? 0) - 1\n remaining[dependentIndex] = nextRemaining\n if (nextRemaining === 0) ready.push(dependentIndex)\n }\n if (done) return\n if (settled === items.length) {\n resolvePromise(results)\n return\n }\n if (active === 0 && ready.length === 0) {\n fail(\n new Error(\n \"runWithConcurrencyGraph: stalled with unsettled items remaining -- the \" +\n \"dependency graph passed in is not acyclic.\",\n ),\n )\n return\n }\n launchNext()\n })\n .catch(fail)\n }\n }\n\n launchNext()\n })\n}\n","import { spawnSync } from \"node:child_process\"\n\n/**\n * Whether a check's process should be spawned with `detached: true`. On\n * POSIX this makes the spawned process the leader of a new process group\n * (sharing its own pid as the group id), which is what lets `killTree`\n * target the whole group rather than just the immediate child. On Windows,\n * process groups work differently and `detached: true` would instead launch\n * the process in its own console window -- not what's wanted here, since\n * Windows cleanup goes through `taskkill /t` instead (see `killTree`).\n * @returns true on POSIX (spawn as its own process group leader, killable via `killTree`); false on Windows\n */\nexport function shouldSpawnDetached(): boolean {\n return process.platform !== \"win32\"\n}\n\n/**\n *\n * @param error - the caught value to narrow\n * @returns true if `error` is an `Error` carrying a `code` property, as Node's errno exceptions do\n */\nfunction isErrnoException(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error\n}\n\n/**\n * Best-effort termination of an entire process tree rooted at `pid`, not\n * just the immediate process -- necessary because a check's command is\n * often itself a wrapper (`npm run test` spawns `npm`, which spawns the\n * actual test runner), and killing only the wrapper would orphan its\n * descendants. `spawn`'s own `timeout`/`signal` options only ever affect the\n * directly spawned process, never its descendants, which is why this exists\n * as a separate utility rather than relying on those.\n *\n * On POSIX, sends `signal` to the whole process group via the negative-pid\n * convention (requires the process to have been spawned with\n * `detached: true`, see `shouldSpawnDetached`). On Windows, process groups\n * don't work the same way, so this shells out to `taskkill /pid <pid> /t\n * /f` -- the same technique the `tree-kill` package uses internally -- which\n * walks the system process table for descendants of `pid` regardless of how\n * it was spawned.\n *\n * Swallows the expected best-effort-cleanup failures, but not every failure: a\n * process that has already exited (POSIX `ESRCH`, or `taskkill`'s \"not found\"\n * case) is a no-op, and a POSIX `EPERM` permission error is swallowed the same\n * way -- by the time cleanup runs the process may well have exited on its own,\n * and a failed cleanup should not crash the run. Any *other* failure is\n * rethrown: an unexpected POSIX errno, or (on Windows) a JS-level spawn failure\n * of `taskkill` itself (`result.error`, e.g. the tool missing from PATH), which\n * would otherwise silently leave an orphaned process tree behind.\n *\n * On Windows this always runs `taskkill` with `/f` regardless of which `signal` was requested --\n * not a partial implementation of POSIX's cooperative-SIGTERM-then-SIGKILL escalation, but a\n * reflection of a real platform difference: Windows has no signal-delivery mechanism for an\n * arbitrary process tree by pid at all (this is also why Node's own `ChildProcess.kill()` treats\n * every signal identically on Windows, per Node's own child_process documentation), so there is no\n * more-cooperative alternative to fall back to here the way there is on POSIX.\n * @param pid - the pid of the tree's root process (the process group id on POSIX, since it was spawned detached)\n * @param signal - the POSIX signal to send (on Windows, ignored -- see doc comment above)\n */\nexport function killTree(pid: number, signal: NodeJS.Signals): void {\n // `process.kill(-pid, ...)` signals a process *group*, and `-0` coerces to\n // `0`, which POSIX interprets as \"every process in the caller's own group\"\n // -- i.e. this would signal the repo-contract host itself. `taskkill /pid\n // 0` is likewise not a real target. A pid of `0` (or negative, or\n // non-integer) is never a real child here; spawn-check.ts guards its own\n // kill calls with `child.pid !== undefined`, but that admits `0`. Refuse\n // it rather than turn a best-effort cleanup call into self-harm.\n if (!Number.isInteger(pid) || pid <= 0) {\n return\n }\n\n /* v8 ignore start -- Windows-only; exercised for real by\n * test/unit/cross-platform/windows-taskkill.test.ts on the CI Windows runner\n * (see vitest.config.ts's coverage thresholds for the rationale -- this\n * branch cannot run on the OS any other CI job/local dev uses). Mutation\n * testing only runs in the ubuntu-only `contract` CI job (see\n * .github/workflows/ci.yml), so this branch is never covered there either\n * -- disabled for the same reason, not left to inflate \"no coverage\"\n * counts against a branch that genuinely is tested, just on a different\n * platform than the one that runs Stryker. */\n // Stryker disable ConditionalExpression,EqualityOperator,StringLiteral,ArrayDeclaration,ObjectLiteral,BlockStatement,CallExpression -- this whole branch only runs on Windows, and mutation testing only runs in the ubuntu-only CI job (see the v8-ignore comment above), so every mutator that could apply to it would surface as an unreachable \"no coverage\" survivor rather than a real test gap.\n if (process.platform === \"win32\") {\n // eslint-disable-next-line n/no-sync -- `killTree` is itself synchronous best-effort cleanup, callable from error/signal-handling paths that don't await anything else; an async `spawn` here would need every caller threaded through `await` for what's already a fire-and-forget `taskkill`.\n const result = spawnSync(\"taskkill\", [\"/pid\", String(pid), \"/t\", \"/f\"], { stdio: \"ignore\" })\n // `result.error` is a JS-level spawn failure (e.g. ENOENT if `taskkill.exe` itself is\n // missing from PATH, or EPERM from a policy blocking process creation) -- distinct from\n // `result.status`, taskkill's own process-table-dependent exit code for \"pid not found\" vs.\n // other in-process failures, which isn't documented precisely enough to safely distinguish\n // an expected no-op from a genuine bug the same way the POSIX branch's ESRCH/EPERM check\n // does. Rethrowing only `result.error` still closes the most likely silent-orphan gap (the\n // tool being unavailable at all) without risking a false rethrow on Windows's legitimate\n // \"already exited\" no-op case, which this file's own test (`windows-taskkill.test.ts`, \"is a\n // no-op, not a throw, for a PID taskkill cannot find\") pins as a passing case.\n if (result.error !== undefined) throw result.error\n return\n }\n // Stryker restore all\n /* v8 ignore stop */\n\n try {\n process.kill(-pid, signal)\n } catch (error) {\n if (isErrnoException(error) && (error.code === \"ESRCH\" || error.code === \"EPERM\")) return\n throw error\n }\n}\n","import crossSpawn from \"cross-spawn\"\nimport { StringDecoder } from \"node:string_decoder\"\nimport { tokenizeRunString } from \"../config/tokenize-command.js\"\nimport { InvalidCheckConfigError } from \"../errors.js\"\nimport type { CheckDefinition, CheckEvidence, CheckStatus } from \"../types.js\"\nimport { composeSignals } from \"./abort-signals.js\"\nimport { killTree, shouldSpawnDetached } from \"./process-tree.js\"\n\n// A configured check's command is repository-controlled, not attacker-input\n// in the usual sense, but its OUTPUT is whatever that command chooses to\n// print -- a misbehaving or compromised tool can still write without bound.\n// Every check's stdout/stderr is retained in full for the life of the\n// process (concatenated into a JS string) and then persisted verbatim into\n// evidence/history.json forever, so leaving this uncapped is an unbounded\n// memory and disk sink, not just a cosmetic concern. None of this\n// repository's own checks rely on more than a few hundred KB of stdout\n// content (the ones that produce large structured output write it to a\n// `reports/*.json` file via `--output` instead -- see securitySecrets and\n// arethetypeswrong's own run arrays -- precisely so stdout stays small); 10\n// MiB per stream is generous headroom above that while still bounding a\n// pathological or malicious command.\nconst MAX_CAPTURED_OUTPUT_BYTES = 10 * 1024 * 1024\n\n// SIGTERM asks a process tree to exit cooperatively; nothing obliges it to.\n// A check that traps or ignores SIGTERM (or a detached descendant that\n// never receives it at all) would otherwise hang this promise -- and with\n// it, the whole run -- forever, silently defeating both `timeoutMs` and a\n// host SIGINT/SIGTERM. 2 seconds is generous for the short-lived CLI tools\n// (linters, test runners, formatters) this library spawns to flush output\n// and exit; it is not meant to accommodate a long-running service's graceful\n// shutdown. Exported so run-checks.ts's own host-signal handler can wait out\n// this same window before letting the host process itself exit -- otherwise\n// the escalation this constant times would never get a chance to fire.\nexport const SIGKILL_GRACE_PERIOD_MS = 2000\n\n/**\n * Accumulates a child process's stdout or stderr up to\n * `MAX_CAPTURED_OUTPUT_BYTES`, appending a truncation marker and discarding\n * further chunks once the cap is hit -- the process itself keeps running to\n * its real exit, only the retained text is bounded.\n *\n * Decodes with a stateful `StringDecoder` rather than `chunk.toString(\"utf8\")` per chunk: Node\n * delivers `\"data\"` events at arbitrary byte boundaries that don't respect UTF-8 character\n * boundaries, so a multi-byte character split across two chunks would otherwise decode to a\n * replacement character on both sides of the split -- `StringDecoder` buffers a trailing partial\n * sequence internally and completes it once the rest arrives. The cap itself is tracked in real\n * bytes fed in (`chunk.byteLength`), not `value.length` (UTF-16 code units) -- for 3-byte-per-\n * character UTF-8 text, code-unit length undercounts actual bytes 3:1, which would otherwise let\n * up to ~3x the documented byte cap accumulate before truncation fires.\n * @returns an `append`/`value` pair: feed it chunks as they arrive, read the (possibly truncated) accumulated text once the process ends\n */\nfunction createBoundedCollector(): { append(chunk: Buffer): void; value(): string } {\n // Node's own encoding normalization treats a falsy/unrecognized encoding argument as \"utf8\"\n // (confirmed empirically: `new StringDecoder(\"\").encoding === \"utf8\"`, and it decodes identically\n // to an explicit `new StringDecoder(\"utf8\")`, including for a multi-byte character split across\n // writes) -- this is the only encoding this collector is ever used with, so no other string value\n // is reachable here to distinguish \"utf8\" from any other literal.\n // Stryker disable next-line StringLiteral -- Node's own encoding normalization treats a falsy/unrecognized encoding argument as \"utf8\" (confirmed empirically: `new StringDecoder(\"\").encoding === \"utf8\"`, decoding identically to an explicit \"utf8\", including for a multi-byte character split across writes); this is the only encoding this collector is ever used with, so no other string value is reachable here to distinguish it.\n const decoder = new StringDecoder(\"utf8\")\n let value = \"\"\n let byteCount = 0\n let truncated = false\n return {\n append(chunk) {\n // Once truncated, `value`'s retained prefix (positions 0..CAP-1) can\n // never change again no matter what further chunks arrive -- slicing\n // to the same CAP from a longer string always yields the same prefix\n // it already had. So skipping further chunks here is unobservable\n // from the *content* `value()` ever returns; what it actually saves\n // is the repeated O(cap) slice+concat this function would otherwise\n // redo on every single subsequent chunk for a process that keeps\n // writing past the cap -- real, unbounded-with-chunk-count CPU work a\n // pathological or malicious command could otherwise force. No\n // deterministic, non-flaky test can observe that difference (only\n // timing can, which this project's own mutation policy already\n // refuses to treat as a real kill -- see checks/mutation.ts's\n // \"Timed out\" handling).\n // Stryker disable next-line ConditionalExpression -- removing this guard has no effect on the content `value()` ever returns (see comment above); it only removes an unbounded-with-chunk-count amount of wasted repeated work for a process that keeps writing past the cap, which no deterministic test can observe without relying on flaky timing.\n if (truncated) return\n byteCount += chunk.byteLength\n value += decoder.write(chunk)\n if (byteCount > MAX_CAPTURED_OUTPUT_BYTES) {\n // Same reasoning as the `if (truncated) return` guard above: leaving\n // this `false` never changes any content `value()` returns, only\n // how much repeated, wasted slice+concat work later chunks cause.\n // Stryker disable next-line BooleanLiteral -- leaving this false has no effect on returned content (see comment above and the identical reasoning on the `if (truncated) return` guard); it only removes an unbounded-with-chunk-count amount of wasted repeated work, which no deterministic test can observe without relying on flaky timing.\n truncated = true\n value = `${value.slice(0, MAX_CAPTURED_OUTPUT_BYTES)}\\n...[output truncated at ${String(MAX_CAPTURED_OUTPUT_BYTES)} bytes]`\n }\n },\n value: () => value,\n }\n}\n\n/** Handle a caller can use to forcibly terminate one in-flight check's process tree, independent of that check's own timeout/abort wiring -- used by run-checks.ts to clean up every active check when the host process itself receives SIGINT/SIGTERM. */\nexport interface ActiveCheckHandle {\n kill(signal: NodeJS.Signals): void\n}\n\n/**\n *\n * @param checkId - the check's id, used only to attach context to a thrown `InvalidCheckConfigError`\n * @param check - the check definition whose `run` (and `shell`) determines the command and args to execute\n * @returns the resolved `command` and `args` to spawn, with `args` empty when `run` is a shell string\n */\nfunction resolveCommand(\n checkId: string,\n check: CheckDefinition,\n): { command: string; args: readonly string[] } {\n const run = check.run\n // `typeof run === \"string\"` narrows this two-member union reliably;\n // `Array.isArray` does not narrow a `readonly T[]` union member the same\n // way it narrows a mutable `T[]` one.\n if (typeof run !== \"string\") {\n const [command, ...args] = run\n // validate-config.ts already rejects an empty run array before this\n // function is ever reached in the normal runRepoContract pipeline; this\n // check re-asserts that same invariant defensively at this function's\n // own boundary, since resolveCommand has no compile-time guarantee of\n // it if called some other way (e.g. directly from a test).\n if (command === undefined) {\n throw new InvalidCheckConfigError(checkId, \"run array must not be empty.\")\n }\n return { command, args }\n }\n if (check.shell === true) {\n // Passed to the platform shell as a single command line; cross-spawn's\n // own `shell` option handles the platform-specific invocation. There is\n // no meaningful separate argv to report, so the whole string is\n // recorded as `command` with empty `args`.\n return { command: run, args: [] }\n }\n const [command, ...args] = tokenizeRunString(run, checkId)\n // tokenizeRunString already rejects an empty/whitespace-only string, so\n // this is unreachable in practice -- kept for the same reason as above.\n // Unlike the array-form check above, this one has no way to be exercised\n // directly (tokenizeRunString's own contract guarantees a non-empty\n // result or a throw, with no way to bypass it short of calling this\n // private function directly, which isn't exported).\n // Stryker disable EqualityOperator,ConditionalExpression,BlockStatement,StringLiteral,CallExpression -- unlike the array-form check above, this one has no way to be exercised directly: tokenizeRunString's own contract guarantees a non-empty result or a throw, with no way to bypass it short of calling this private function directly, which isn't exported.\n if (command === undefined) {\n throw new InvalidCheckConfigError(checkId, \"run string is empty or contains only whitespace.\")\n }\n // Stryker restore all\n return { command, args }\n}\n\n/**\n *\n * @param check - the check definition; unless `inheritEnv` is explicitly `false`, this process's own env is inherited and then overlaid with `check.env`\n * @returns the environment variables to pass to the spawned process\n */\nfunction buildEnv(check: CheckDefinition): Record<string, string> {\n const base: Record<string, string> = {}\n if (check.inheritEnv !== false) {\n // eslint-disable-next-line n/no-process-env -- reading the ambient environment isn't a config smell here, it's the feature: `inheritEnv` (on by default) means the spawned check inherits this process's real env, exactly what this loop builds.\n for (const [key, value] of Object.entries(process.env)) {\n // `process.env`'s index signature is typed `string | undefined`, but\n // a real Node process never actually produces an `undefined` value\n // here for an own enumerable key -- assigning `undefined` to an env\n // var coerces it to the literal string `\"undefined\"` (confirmed\n // empirically). Kept only to satisfy that type, not because it\n // changes observed behavior.\n // Stryker disable next-line ConditionalExpression -- process.env's index signature is typed string | undefined, but a real Node process never actually produces an undefined value here for an own enumerable key; kept only to satisfy that type, not because it changes observed behavior.\n if (value !== undefined) base[key] = value\n }\n }\n return { ...base, ...check.env }\n}\n\n/**\n *\n * @param command - the resolved executable that was (or would have been) run\n * @param args - the resolved argument list passed to `command`\n * @param startedAt - when the check began running; used with the current time to compute `durationMs`\n * @param status - the terminal status to record (e.g. \"completed\", \"timed_out\", \"aborted\", \"spawn_error\")\n * @param exitCode - the process's exit code, or null if it never exited normally\n * @param signal - the signal that terminated the process, or null if it exited normally or never spawned\n * @param stdout - the process's captured stdout, if any was produced before it ended\n * @param stderr - the process's captured stderr, if any was produced before it ended\n * @param spawnError - the underlying spawn error message, present only when `status` is \"spawn_error\"\n * @param spawnErrorCode - the underlying spawn error's structured `ErrnoException.code`, present only when `status` is \"spawn_error\" and Node provided one\n * @returns a fully-formed `CheckEvidence`, with `completedAt`/`durationMs` computed from `startedAt` to now\n */\nfunction terminalEvidence(\n command: string,\n args: readonly string[],\n startedAt: Date,\n status: CheckStatus,\n exitCode: number | null,\n signal: NodeJS.Signals | null,\n stdout = \"\",\n stderr = \"\",\n spawnError?: string,\n spawnErrorCode?: string,\n): CheckEvidence {\n const completedAt = new Date()\n return {\n command,\n args,\n startedAt: startedAt.toISOString(),\n completedAt: completedAt.toISOString(),\n durationMs: completedAt.getTime() - startedAt.getTime(),\n exitCode,\n signal,\n stdout,\n stderr,\n status,\n ...(spawnError !== undefined ? { spawnError } : {}),\n ...(spawnErrorCode !== undefined ? { spawnErrorCode } : {}),\n }\n}\n\n/**\n * Runs one configured check end to end: resolves its command, spawns it,\n * enforces its timeout (if any), reacts to the run-level `AbortSignal` (if\n * any), captures stdout/stderr, and resolves to a fully-formed\n * `CheckEvidence` no matter how the process ended -- this function never\n * rejects. `activeHandles` is a shared registry the caller (run-checks.ts)\n * uses to kill every in-flight check on a host-process SIGINT/SIGTERM; this\n * function adds its own handle once the process has a pid and removes it\n * once the process has settled.\n *\n * A check whose `runSignal` is already aborted before this function is even\n * invoked (queued behind the concurrency limit when the run was cancelled)\n * never spawns at all, but still resolves to a well-formed `status:\n * \"aborted\"` evidence entry -- every configured check gets evidence and a\n * policy invocation regardless of whether it ever ran (see\n * specs/architecture.md).\n * @param checkId - the check's id, used for logging and passed through to `resolveCommand`\n * @param check - the check definition to run (command, timeout, env, cwd, shell, etc.)\n * @param runSignal - the whole run's abort signal, if any; already-aborted before this is called means the check never spawns\n * @param activeHandles - the shared registry this check's kill handle is added to while running, so a host-process SIGINT/SIGTERM can terminate it\n * @returns a fully-formed `CheckEvidence` reflecting however the process ended; this function itself never rejects\n */\nexport async function spawnCheck(\n checkId: string,\n check: CheckDefinition,\n runSignal: AbortSignal | undefined,\n activeHandles: Set<ActiveCheckHandle>,\n): Promise<CheckEvidence> {\n const startedAt = new Date()\n const { command, args } = resolveCommand(checkId, check)\n\n if (runSignal?.aborted === true) {\n return terminalEvidence(command, args, startedAt, \"aborted\", null, null)\n }\n\n const env = buildEnv(check)\n\n let terminationReason: \"aborted\" | \"timed_out\" | null = null\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n const timeoutController = new AbortController()\n if (check.timeoutMs !== undefined) {\n // `setTimeout` truncates any delay above the 32-bit signed limit to 1ms\n // (with a `TimeoutOverflowWarning`), so a caller asking for a genuinely\n // long timeout -- e.g. `timeoutMs: 2_592_000_000` (30 days) -- would\n // otherwise fire the abort almost immediately and record `status:\n // \"timed_out\"` milliseconds after spawn. Clamp to the maximum delay\n // `setTimeout` can actually represent; anything at or above it is, in\n // practice, \"effectively no timeout\".\n const MAX_TIMER_DELAY_MS = 2_147_483_647\n const delay = Math.min(check.timeoutMs, MAX_TIMER_DELAY_MS)\n timeoutHandle = setTimeout(() => {\n timeoutController.abort()\n }, delay)\n }\n\n const { signal: effectiveSignal, dispose: disposeEffectiveSignal } = composeSignals(\n runSignal !== undefined ? [runSignal, timeoutController.signal] : [timeoutController.signal],\n )\n\n return new Promise<CheckEvidence>((resolve) => {\n const child = crossSpawn(command, args, {\n cwd: check.cwd,\n env,\n shell: check.shell === true,\n detached: shouldSpawnDetached(),\n // Windows-only cosmetic behavior (suppresses a console window flash)\n // with no effect on stdout/stderr/exitCode/signal on any platform, and\n // no Node API exposes it back for a test to observe either way.\n // Stryker disable next-line BooleanLiteral -- Windows-only cosmetic behavior (suppresses a console window flash) with no effect on stdout/stderr/exitCode/signal on any platform, and no Node API exposes it back for a test to observe either way.\n windowsHide: true,\n })\n\n const stdoutCollector = createBoundedCollector()\n const stderrCollector = createBoundedCollector()\n let handle: ActiveCheckHandle | undefined\n let escalationHandle: ReturnType<typeof setTimeout> | undefined\n let hostTerminated = false\n\n /**\n * Best-effort: swallows whatever `killTree` throws rather than letting it\n * escape. `killTree` deliberately rethrows a genuinely unexpected errno\n * (see process-tree.test.ts) so a real bug there isn't silently hidden\n * from a caller equipped to handle it -- but `killWithEscalation` is\n * invoked from inside an `AbortSignal` \"abort\" listener and, via\n * `ActiveCheckHandle.kill`, a `process.once(signal, ...)` handler in\n * run-checks.ts, and an exception escaping either of those crashes the\n * whole host process instead of merely failing this one check's cleanup.\n * @param pid - the root pid of the process tree to kill\n * @param signal - the signal to send\n */\n const bestEffortKillTree = (pid: number, signal: NodeJS.Signals): void => {\n try {\n killTree(pid, signal)\n } catch {\n // best-effort; see doc comment above\n }\n }\n\n /**\n * Kills the tree with `signal`, then schedules a SIGKILL follow-up\n * unless `signal` already was SIGKILL -- see `SIGKILL_GRACE_PERIOD_MS`.\n * A prior pending escalation is replaced, not stacked, since only the\n * most recent kill's grace period should apply.\n * @param pid - the root pid of the process tree to kill, forwarded to `killTree`\n * @param signal - the signal to send now; anything other than SIGKILL schedules a SIGKILL follow-up if the tree hasn't exited by then\n */\n const killWithEscalation = (pid: number, signal: NodeJS.Signals): void => {\n bestEffortKillTree(pid, signal)\n clearTimeout(escalationHandle)\n if (signal === \"SIGKILL\") return\n escalationHandle = setTimeout(() => {\n bestEffortKillTree(pid, \"SIGKILL\")\n }, SIGKILL_GRACE_PERIOD_MS)\n }\n\n if (child.pid !== undefined) {\n const pid = child.pid\n handle = {\n kill: (signal) => {\n // `ActiveCheckHandle.kill` is invoked only by run-checks.ts's host-process\n // SIGINT/SIGTERM cleanup (see that interface's own doc comment) -- repo-contract itself\n // is requesting this signal, just not via `options.signal`/`timeoutMs`, so the eventual\n // \"close\" status must not fall through to `\"signaled\"` (reserved for a signal\n // repo-contract did *not* request) the way it would if `terminationReason` were left\n // unset here too.\n hostTerminated = true\n killWithEscalation(pid, signal)\n },\n }\n activeHandles.add(handle)\n }\n\n // `child.stdout`/`child.stderr` are typed `Readable | null` (Node is\n // only ever `null` when `stdio` overrides that stream to something\n // other than \"pipe\"), but this call site never passes a `stdio` option,\n // so both are always real streams in practice -- confirmed empirically,\n // including for a spawn that fails outright (ENOENT). Kept only to\n // satisfy the type, not because it changes observed behavior.\n // Stryker disable next-line OptionalChaining -- child.stdout is typed Readable | null (Node is only ever null when stdio overrides that stream to something other than \"pipe\"), but this call site never passes a stdio option, so it's always a real stream in practice, confirmed empirically including for a spawn that fails outright (ENOENT).\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdoutCollector.append(chunk)\n })\n // Same reasoning as the stdout guard above (which already covers\n // \"both\"), confirmed empirically here too: un-exempting this mutant and\n // running Stryker scoped to this file showed `OptionalChaining`'s\n // `child.stderr.on` replacement survives.\n // Stryker disable next-line OptionalChaining -- same reasoning as the stdout guard above (which already covers \"both\"), confirmed empirically here too: un-exempting this mutant and running Stryker scoped to this file showed OptionalChaining's child.stderr.on replacement survives.\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderrCollector.append(chunk)\n })\n\n const onEffectiveAbort = (): void => {\n terminationReason = runSignal?.aborted === true ? \"aborted\" : \"timed_out\"\n // Deliberately NOT simplified to an unconditional `killTree` call: if\n // `child.pid` were ever genuinely undefined here (spawn failed but\n // \"error\" hasn't been reported yet), calling `killTree(undefined,\n // ...)` would throw synchronously inside this AbortSignal listener,\n // which Node reschedules onto `process.nextTick` as an *uncaught*\n // exception -- verified directly, not assumed -- which would crash\n // the whole test process rather than fail one assertion, making this\n // guard's absence unsafe to exercise via a real test.\n // The ConditionalExpression guard is covered by the rationale just\n // above. The \"SIGTERM\" string literal is separately unkillable:\n // confirmed empirically that Node's own process.kill() normalizes a\n // falsy/empty signal argument back to SIGTERM, so the OS-observed\n // signal is identical either way.\n // Stryker disable next-line EqualityOperator,ConditionalExpression,StringLiteral,CallExpression -- if child.pid were ever genuinely undefined here, calling killWithEscalation(undefined, ...) would throw synchronously inside this AbortSignal listener, which Node reschedules onto process.nextTick as an uncaught exception, crashing the whole test process rather than failing one assertion; the \"SIGTERM\" string literal is separately unkillable since Node's own process.kill() normalizes a falsy/empty signal argument back to SIGTERM, so the OS-observed signal is identical either way.\n if (child.pid !== undefined) killWithEscalation(child.pid, \"SIGTERM\")\n }\n // `effectiveSignal` fires \"abort\" at most once in its lifetime (an\n // AbortSignal cannot transition from aborted back to unaborted, so it\n // can never fire \"abort\" a second time) and is discarded once this\n // function resolves, so `{ once: true }` is provably redundant --\n // omitted rather than kept only to be marked equivalent.\n effectiveSignal.addEventListener(\"abort\", onEffectiveAbort)\n\n const cleanup = (): void => {\n // `clearTimeout` silently no-ops for `undefined` (confirmed\n // empirically), so the `timeoutHandle !== undefined` guard that used\n // to wrap this call was provably redundant -- omitted rather than\n // kept only to be marked equivalent. Same reasoning covers\n // `escalationHandle`: a check that exited before any kill was ever\n // issued leaves it `undefined`, and a check that died from the\n // initial signal (the common case) never lets its escalation fire.\n clearTimeout(timeoutHandle)\n clearTimeout(escalationHandle)\n // Removing the wrong event name here has no test-observable effect\n // within a short-lived test (the real listener is attached to an\n // AbortController that's garbage-collected with the test, and\n // `effectiveSignal` only ever fires \"abort\" once in its lifetime\n // regardless) -- it matters for a long-running consumer process\n // avoiding a listener leak, not for anything this suite can directly\n // assert on.\n // Stryker disable next-line StringLiteral,CallExpression -- removing the wrong event name here has no test-observable effect within a short-lived test: the real listener is attached to an AbortController that's garbage-collected with the test, and effectiveSignal only ever fires \"abort\" once in its lifetime regardless; it matters for a long-running consumer process avoiding a listener leak, not for anything this suite can directly assert on.\n effectiveSignal.removeEventListener(\"abort\", onEffectiveAbort)\n // Releases the manual fallback's own listeners on `runSignal`/\n // `timeoutController.signal` (a no-op on the native AbortSignal.any\n // path) -- without this, every check spawned during a run would leave\n // one permanent listener on the run's own long-lived shared signal.\n // See abort-signals.ts's composeSignals doc comment.\n disposeEffectiveSignal()\n // `handle` is `ActiveCheckHandle | undefined` (undefined exactly when\n // spawning itself failed and no pid was ever obtained, see below) --\n // this guard exists to satisfy Set#delete's parameter type, not\n // because calling delete(undefined) would behave differently at\n // runtime (Set#delete on a non-member value is already a silent\n // no-op), so no test can observe a difference either way.\n // Stryker disable next-line ConditionalExpression -- handle is ActiveCheckHandle | undefined exactly when spawning itself failed and no pid was ever obtained (see below); this guard exists to satisfy Set#delete's parameter type, not because calling delete(undefined) would behave differently at runtime -- Set#delete on a non-member value is already a silent no-op.\n if (handle !== undefined) activeHandles.delete(handle)\n }\n\n // A spawn failure (e.g. the executable does not exist) emits \"error\" --\n // Node does not throw synchronously from spawn() for this case.\n child.once(\"error\", (error: NodeJS.ErrnoException) => {\n cleanup()\n resolve(\n terminalEvidence(\n command,\n args,\n startedAt,\n \"spawn_error\",\n null,\n null,\n stdoutCollector.value(),\n stderrCollector.value(),\n error.message,\n error.code,\n ),\n )\n })\n\n // \"close\", not \"exit\": Node's own docs warn that \"exit\" can fire before\n // the child's stdio streams have finished delivering their final\n // buffered data -- resolving on \"exit\" risks silently truncating\n // stdout/stderr for a process that writes a lot of output right before\n // exiting (confirmed for real during implementation against secretlint's\n // own output, not a hypothetical). \"close\" fires only after every stdio\n // stream has ended, and still carries the same (code, signal) pair.\n child.once(\"close\", (code, signal) => {\n cleanup()\n const status: CheckStatus =\n // A kill via ActiveCheckHandle.kill (host SIGINT/SIGTERM cleanup, see that interface's own\n // doc comment) is classified first -- ahead of terminationReason. run-checks.ts's handler\n // aborts `hostAbortController` (composed into `runSignal`) *before* calling handle.kill(),\n // which it must, so the scheduler stops launching queued checks synchronously; that abort\n // fires this check's own effectiveSignal listener, which sets terminationReason to\n // \"aborted\" (runSignal is aborted by then). Checking terminationReason first would\n // therefore misreport every host-Ctrl+C-killed check as \"aborted\" -- indistinguishable\n // from an options.signal cancellation -- and leave \"host_terminated\" unreachable.\n hostTerminated\n ? \"host_terminated\"\n : terminationReason === \"aborted\"\n ? \"aborted\"\n : terminationReason === \"timed_out\"\n ? \"timed_out\"\n : // Node's own child_process contract guarantees exactly one of\n // code/signal is non-null on a normal \"exit\" event, making\n // `signal !== null` here effectively redundant given\n // `code === null` already -- kept as a belt-and-suspenders\n // check against that contract rather than assumed absolute,\n // since it is difficult to construct a real counterexample to\n // test against (both null, or both non-null, simultaneously).\n // Stryker disable next-line ConditionalExpression,EqualityOperator,LogicalOperator -- Node's own child_process contract guarantees exactly one of code/signal is non-null on a normal \"exit\" event, making signal !== null here effectively redundant given code === null already; kept as a belt-and-suspenders check against that contract rather than assumed absolute, since it is difficult to construct a real counterexample to test against (both null, or both non-null, simultaneously).\n code === null && signal !== null\n ? \"signaled\"\n : \"completed\"\n resolve(\n terminalEvidence(\n command,\n args,\n startedAt,\n status,\n code,\n signal,\n stdoutCollector.value(),\n stderrCollector.value(),\n ),\n )\n })\n })\n}\n","import { UnknownCheckIdError } from \"../errors.js\"\nimport type {\n CheckDefinition,\n CheckEvidence,\n CheckSchema,\n RunRepoContractOptions,\n} from \"../types.js\"\nimport { composeSignals } from \"./abort-signals.js\"\nimport { runWithConcurrency } from \"./concurrency-pool.js\"\nimport { runWithConcurrencyGraph } from \"./dependency-scheduler.js\"\nimport type { ActiveCheckHandle } from \"./spawn-check.js\"\nimport { SIGKILL_GRACE_PERIOD_MS, spawnCheck } from \"./spawn-check.js\"\n\n/**\n * One check's id, its original definition, and its raw execution evidence,\n * threaded together as a triple rather than three separately-keyed maps --\n * every later stage (parsing, policy evaluation) consumes this directly\n * instead of re-looking a check up by id, which under `noUncheckedIndexedAccess`\n * would otherwise force handling an \"undefined\" case that can't actually\n * happen (every checkId here comes from the same `Object.entries(checks)`\n * that produced it).\n */\nexport type CheckExecutionEntry = readonly [string, CheckDefinition, CheckEvidence]\n\nconst TERMINATION_SIGNALS: readonly NodeJS.Signals[] = [\"SIGINT\", \"SIGTERM\"]\n\n// Extra headroom over SIGKILL_GRACE_PERIOD_MS itself: killWithEscalation arms its own SIGKILL\n// follow-up timer with that exact delay, in the same synchronous pass as the self-terminate timer\n// below -- waiting only that same delay would race Node's timer ordering rather than reliably\n// outlasting it. This margin ensures every active check's scheduled SIGKILL follow-up has already\n// had the chance to run and observe the process exit before this process re-signals itself.\n// Exported so a unit test can pin its exact value against SIGKILL_GRACE_PERIOD_MS without needing\n// to trigger the real, only-testable-in-a-child-process SIGINT handler this constant feeds (see\n// installTerminationHandlers below).\nexport const SELF_TERMINATE_DELAY_MS = SIGKILL_GRACE_PERIOD_MS + 250\n\n/**\n * While any check is in flight, installs a handler for SIGINT/SIGTERM that\n * kills every currently-active check's process tree before the host process\n * itself terminates -- otherwise a Ctrl+C during `runRepoContract()` would\n * leave every spawned check (and their own descendants) running as orphans.\n * After cleanup, removes its own handler and re-sends the signal to this\n * process so default termination behavior (or any other listener the host\n * application registered) still applies -- this never itself decides to\n * keep the process alive or call `process.exit()`, it only ensures cleanup\n * happens first.\n *\n * Also aborts `hostAbortController` before killing anything: `activeHandles` only ever contains\n * checks that have *already* spawned, so killing every current member does nothing to stop the\n * concurrency pool/scheduler from immediately launching the *next* queued check the instant a\n * killed one's promise settles -- by then this handler has already returned and `uninstall()` has\n * already removed these very listeners, so nothing would be left to kill that newly-spawned\n * process. Aborting `hostAbortController` first means `runChecks`' composed `runSignal` is already\n * `aborted` by the time any such check is considered, so `spawnCheck` takes its documented\n * already-aborted path (see spawn-check.ts's own doc comment) and resolves without spawning at\n * all, instead of racing this cleanup.\n * @param activeHandles - the currently-running checks' kill handles, killed with the received signal on termination\n * @param hostAbortController - aborted before any active check is killed, so a check still queued behind the concurrency limit never spawns instead of racing this cleanup\n * @returns a function that removes the installed signal handlers without killing anything, for cleanup once no check is in flight\n */\nfunction installTerminationHandlers(\n activeHandles: Set<ActiveCheckHandle>,\n hostAbortController: AbortController,\n): () => void {\n const handlers = new Map<NodeJS.Signals, () => void>()\n\n for (const signal of TERMINATION_SIGNALS) {\n /* v8 ignore start -- this body only runs when the host *process* actually\n * receives a real SIGINT/SIGTERM. test/unit/execution/run-checks.test.ts's\n * \"SIGINT while checks are in flight...\" test exercises it for real, but\n * necessarily in a separate child process (sending SIGINT to this test\n * worker's own process would kill the test runner) -- v8 coverage is\n * per-process, so that real exercise is invisible here. Same reasoning\n * applies to Stryker's mutation testing, which only observes this\n * process's own test run. */\n // Stryker disable BlockStatement,CallExpression,ConditionalExpression,EqualityOperator,BooleanLiteral -- this handler body only executes when the host process receives a real SIGINT/SIGTERM; the test exercising it necessarily runs in a separate child process (v8 coverage and Stryker's own instrumentation are both per-process), so mutating this body -- including the hadActiveChecks/!hadActiveChecks branch below -- always reports an uncoverable-looking survivor rather than a real gap.\n const handler = (): void => {\n // Must run before killing anything currently active (see this\n // function's own doc comment): synchronous, so every check the\n // scheduler considers launching from this point forward -- including\n // one whose turn comes only after a check killed below actually exits\n // -- already observes `aborted === true`.\n hostAbortController.abort()\n // Captured before killing anything: `activeHandles` only loses entries\n // once each check's own process actually exits (asynchronously, via\n // spawn-check.ts's cleanup), so it still reads non-empty immediately\n // after calling kill() on every one of them below -- this check must\n // run first to know whether there's anything worth waiting on.\n const hadActiveChecks = activeHandles.size > 0\n for (const handle of activeHandles) handle.kill(signal)\n uninstall()\n if (!hadActiveChecks) {\n process.kill(process.pid, signal)\n return\n }\n // Previously this re-signaled (and thereby terminated, via Node's\n // default disposition once uninstall() removed this process's own\n // listener) the host process immediately after arming the kills above\n // -- starving every check's own SIGKILL-escalation timer\n // (spawn-check.ts's killWithEscalation) of the time it needs to fire,\n // orphaning any check whose command traps or ignores the initial\n // signal. Waiting SELF_TERMINATE_DELAY_MS first gives every one of\n // those timers a real chance to run before this process exits. This\n // delay is sized against `SIGKILL_GRACE_PERIOD_MS` alone -- it is\n // deliberately not tied to whether `runChecks()`'s own promise (or any\n // work a caller does with its result, e.g. persisting evidence) has\n // resolved by then: a real Ctrl+C is a request to stop now, and this\n // process is going to re-terminate itself via the signal re-sent below\n // regardless of what the caller is doing, the same as if this handler\n // did not exist at all -- the only thing this delay buys is letting\n // already-spawned child processes actually die first.\n setTimeout(() => {\n process.kill(process.pid, signal)\n }, SELF_TERMINATE_DELAY_MS)\n }\n // Stryker restore all\n /* v8 ignore stop */\n handlers.set(signal, handler)\n process.once(signal, handler)\n }\n\n /**\n *\n */\n function uninstall(): void {\n for (const [signal, handler] of handlers) process.removeListener(signal, handler)\n }\n\n return uninstall\n}\n\n/**\n * Resolves `requestedChecks` plus every check transitively required to satisfy their own\n * `dependsOn` -- never `isolated`, whose implied positional edges (see `dependencyIndexesFor`\n * below) are scheduling-only and must not pull unrelated checks into a partial run just because\n * one happens to sit after an isolated barrier (see `CheckDefinitionConfig.isolated`'s own doc\n * comment). No cycle guard is needed: `validateRepoContractConfig` already guarantees, before\n * `runChecks` is ever reached, that every `dependsOn` id names a check declared *earlier* than the\n * check declaring it -- a cycle is structurally impossible once every edge points backward.\n * @param checks - the full set of configured checks, keyed by id, in declaration order\n * @param requestedChecks - the check ids explicitly requested for this run\n * @returns the requested checks plus every transitive `dependsOn` dependency, in declaration order\n */\nfunction resolveCheckDependencies(\n checks: Record<string, CheckDefinition>,\n requestedChecks: readonly string[],\n): [string, CheckDefinition][] {\n const required = new Set<string>()\n\n const visit = (checkId: string): void => {\n // Real, tested logic -- \"throws when a requested subset's dependsOn forms a cycle\" in\n // run-checks.test.ts proves this guard fires correctly for the one caller shape that can still\n // reach a cycle here (runChecks invoked directly, bypassing validateRepoContractConfig's own\n // backward-reference check). In a plain, uninstrumented process, disabling it and reproducing\n // the resulting unbounded recursion throws a stack-overflow RangeError in single-digit\n // milliseconds (confirmed empirically, not assumed). Mutation-tested here it is not: disabling\n // this condition turns a would-be 2-node cycle into unbounded mutual recursion between `visit`\n // calls, and Stryker's own per-statement coverage instrumentation adds enough overhead per\n // recursive frame that reaching V8's actual stack limit -- still the only thing that stops it --\n // takes long enough to exceed stryker.config.mjs's timeoutMS. A \"Timeout\" verdict under\n // instrumentation here reflects that mismatch between wall-clock budget and V8's stack limit,\n // not a gap in test coverage -- a known limitation of mutation testing for recursion/loop-guard\n // code generally, not specific to this function.\n // Stryker disable next-line ConditionalExpression -- disabling this in an uninstrumented process empirically produces unbounded mutual recursion that hits V8's stack limit in single-digit milliseconds, but under Stryker's own per-statement instrumentation overhead reaching that same stack limit takes long enough to exceed stryker.config.mjs's timeoutMS, surfacing as a \"Timeout\" verdict rather than a real test gap.\n if (required.has(checkId)) return\n\n const check = checks[checkId]\n if (!check) {\n throw new UnknownCheckIdError(checkId)\n }\n\n required.add(checkId)\n for (const dependency of check.dependsOn ?? []) {\n visit(dependency)\n }\n }\n\n for (const checkId of requestedChecks) visit(checkId)\n\n return Object.entries(checks).filter(([checkId]) => required.has(checkId))\n}\n\n/**\n * Executes every resolved check with at most `concurrency` running at once,\n * returning each check's raw evidence keyed by check id. For a full run\n * (`options.checks` omitted), every configured check id is resolved and\n * therefore appears in the result exactly once, regardless of whether it\n * ever actually spawned (see spawnCheck's documentation for the pre-aborted\n * case). When `options.checks` restricts execution to a subset, only the\n * requested ids and their transitive `dependsOn` are resolved (see\n * `resolveCheckDependencies` above) -- every other configured check id is\n * simply absent from the result, not present with some placeholder value.\n * @param checks - the full set of configured checks, keyed by id\n * @param concurrency - the maximum number of checks to run in parallel at once\n * @param options - run options; `options.checks` restricts execution to those ids (plus their dependencies), `options.signal` cancels the whole run\n * @returns each executed check's id, definition, and raw evidence, one entry per resolved check regardless of whether it actually spawned\n */\nexport async function runChecks(\n checks: CheckSchema,\n concurrency: number,\n options?: RunRepoContractOptions,\n): Promise<readonly CheckExecutionEntry[]> {\n const entries = options?.checks\n ? resolveCheckDependencies(checks, options.checks)\n : Object.entries(checks)\n\n const activeHandles = new Set<ActiveCheckHandle>()\n const hostAbortController = new AbortController()\n const uninstall = installTerminationHandlers(activeHandles, hostAbortController)\n // Composed rather than passing `options?.signal` straight through: a host-process\n // SIGINT/SIGTERM must abort every not-yet-spawned check exactly like an\n // explicit `options.signal` cancellation already does, or a check queued behind the\n // concurrency limit would spawn unsupervised after installTerminationHandlers' own\n // listeners are already removed (see that function's doc comment). The `if` branch below\n // remains fully covered without further exemption: removing `options.signal` from that array\n // breaks \"a global AbortSignal fired mid-run...\" (run-checks.test.ts), which supplies\n // `options.signal` and asserts on its effect directly, in-process.\n const { signal: runSignal, dispose: disposeRunSignal } = composeSignals(\n options?.signal !== undefined\n ? [options.signal, hostAbortController.signal]\n : // The `else` branch's inclusion of `hostAbortController.signal` is only observably\n // meaningful once `hostAbortController.abort()` is actually called, which happens\n // exclusively inside installTerminationHandlers' real-OS-signal handler above -- and per\n // that handler's own doc comment, that handler body only executes when the host\n // *process* actually receives a real SIGINT/SIGTERM, which cannot be triggered from this\n // same in-process test worker without killing it (v8/Stryker coverage is per-process, so\n // the real exercise -- run-checks.test.ts's \"does not spawn a check still queued behind\n // the concurrency limit...\" test, run in a separate child process -- is invisible here\n // for the identical reason already documented on that handler).\n // Stryker disable next-line ArrayDeclaration -- see comment immediately above: only provable via a real signal delivered to a separate child process, invisible to this process's own Stryker instrumentation, the same per-process-invisible situation already documented on installTerminationHandlers' handler.\n [hostAbortController.signal],\n )\n\n const worker = async ([checkId, check]: readonly [\n string,\n CheckDefinition,\n ]): Promise<CheckExecutionEntry> => {\n const evidence = await spawnCheck(checkId, check, runSignal, activeHandles)\n return [checkId, check, evidence]\n }\n\n try {\n // A fully-disconnected graph (no check declares dependsOn or isolated --\n // the default) takes the exact same runWithConcurrency path as before\n // this feature existed, not a \"generalized but behaviorally equivalent\"\n // one -- zero risk to the common case. This choice of code path is\n // provably unobservable either way: dependency-scheduler.test.ts's own\n // \"zero-edges equivalence\" suite confirms runWithConcurrencyGraph\n // behaves identically to runWithConcurrency for a graph with no real\n // edges (order, concurrency clamping, rejection propagation, all\n // matching) -- routing every call through the graph-aware scheduler\n // regardless of hasDependencies would produce the same observable\n // results for every existing no-dependsOn/no-isolated test, making that\n // decision a pure performance optimization, not a behavioral one (an\n // isolated check alone in `entries`, with nothing else to wait on,\n // resolves to zero effective edges too -- the same proven-equivalent\n // case).\n // Stryker disable CallExpression,ArrowFunction,OptionalChaining,LogicalOperator,EqualityOperator,UnaryOperator,ConditionalExpression,BlockStatement -- dependency-scheduler.test.ts's own \"zero-edges equivalence\" suite already proves runWithConcurrencyGraph behaves identically to runWithConcurrency for a graph with no real edges, so this fast path is a pure performance optimization: every existing no-dependsOn/no-isolated test would pass identically either way, making this branch provably unobservable rather than undertested.\n const hasDependencies = entries.some(\n ([, check]) => (check.dependsOn?.length ?? 0) > 0 || check.isolated === true,\n )\n if (!hasDependencies) {\n return await runWithConcurrency(entries, concurrency, worker)\n }\n // Stryker restore all\n\n const indexById = new Map(entries.map(([checkId], index) => [checkId, index]))\n // Precomputed once per run rather than re-scanned inside\n // dependencyIndexesFor's own per-check closure below: every plain\n // (non-isolated) check needs this same list to find which isolated\n // checks it must wait for (see that edge's own comment below).\n //\n // This array's own initial contents are unobservable regardless of what they are: every real\n // isolated index is still pushed on below, and dependencyIndexesFor's own `earlierIsolated`\n // filter (further down) compares each entry against a numeric `index` with `<` -- a stray\n // non-numeric seed value fails that numeric comparison (JavaScript's `<` coerces a non-numeric\n // operand to NaN, and every comparison against NaN is false) and is silently filtered out,\n // never appearing in any real edge list. Confirmed empirically, not assumed: `[\"Stryker was\n // here\", 2, 5].filter((i) => i < 10)` evaluates to `[2, 5]`, dropping the bogus entry with no\n // trace.\n // Stryker disable next-line ArrayDeclaration -- this array's own initial contents are unobservable regardless of what they are: every real isolated index is still pushed on below, and dependencyIndexesFor's own earlierIsolated filter compares each entry against a numeric index with `<`, which coerces a non-numeric seed value to NaN and silently filters it out (every comparison against NaN is false) -- confirmed empirically that a bogus seed entry never appears in any real edge list.\n const isolatedIndexes: number[] = []\n for (const [entryIndex, [, entryCheck]] of entries.entries()) {\n if (entryCheck.isolated === true) isolatedIndexes.push(entryIndex)\n }\n // Declaration order in `entries` doubles as the required scheduling order (see\n // CheckDefinitionConfig.isolated and CheckDefinition.dependsOn's own doc comments,\n // specs/architecture.md, and validate-config.ts's backward-reference validation, which\n // guarantees every declared `dependsOn` id resolves to an index strictly less than this\n // check's own before this function is ever reached): a check runs concurrently with whatever's\n // declared around it, launched in declaration order and bounded by `concurrency`, EXCEPT that\n // (a) an explicit `dependsOn` id must have already reached a terminal status, and (b) an\n // `isolated` check is a full barrier at its own declared position -- it waits for every check\n // declared earlier (nothing \"currently in flight\" when its turn comes can be anything other\n // than an earlier-declared check, since nothing later has been reached in the walk yet), and\n // every check declared *after* it waits for it in turn, so nothing overlaps it either\n // direction. Two isolated checks are therefore always sequential relative to each other (the\n // later one's \"everything declared earlier\" already includes the earlier one).\n const dependencyIndexesFor = (\n [, check]: readonly [string, CheckDefinition],\n index: number,\n ): number[] => {\n const declared = (check.dependsOn ?? []).map((depId) => {\n const depIndex = indexById.get(depId)\n // validate-config.ts has already guaranteed, before runChecks is\n // ever invoked, that every dependsOn id names a check that exists\n // in this same `checks` record -- reaching this would be a bug in\n // that guarantee, not a user-input problem, so it fails loudly\n // rather than silently miscounting the dependency graph. Provably\n // unreachable given that guarantee, same as this file's other\n // upstream-validated invariants.\n /* v8 ignore start */\n // Stryker disable EqualityOperator,ConditionalExpression,BlockStatement,StringLiteral,CallExpression -- validate-config.ts has already guaranteed, before runChecks is ever invoked, that every dependsOn id names a check that exists in this same checks record; reaching this would be a bug in that guarantee, not a user-input problem.\n if (depIndex === undefined) {\n throw new Error(`internal: dependsOn references unknown check id \"${depId}\".`)\n }\n // Stryker restore all\n /* v8 ignore stop */\n return depIndex\n })\n\n if (check.isolated === true) {\n // Every index declared earlier than this one -- see this function's own doc comment above.\n const earlierIndexes = Array.from({ length: index }, (_, earlierIndex) => earlierIndex)\n return [...new Set([...declared, ...earlierIndexes])]\n }\n\n // A plain check waits for every isolated check declared earlier than it (so it never starts\n // while that barrier is still draining or running), but not for every plain check earlier\n // than it -- those remain free to run concurrently, unchanged from the disconnected-graph\n // default.\n //\n // `<` vs `<=` here is provably equivalent, not a coverage gap: this branch only ever runs for\n // a check whose own `isolated !== true` (the `if (check.isolated === true)` branch above\n // already returned otherwise), and `isolatedIndexes` contains only indexes of checks whose\n // `isolated === true` -- so `index` (this check's own) can never itself be a member of\n // `isolatedIndexes`, making `isolatedIndex === index` unconditionally false for every value\n // this filter is ever called with. `<` and `<=` therefore select the identical subset here.\n // Stryker disable next-line EqualityOperator -- provably equivalent: this branch only runs when check.isolated !== true, and isolatedIndexes contains only indexes of checks where isolated === true, so this check's own `index` can never be a member of isolatedIndexes -- isolatedIndex === index is unconditionally false, making `<` and `<=` select the identical subset for every value this filter is ever called with.\n const earlierIsolated = isolatedIndexes.filter((isolatedIndex) => isolatedIndex < index)\n return [...new Set([...declared, ...earlierIsolated])]\n }\n return await runWithConcurrencyGraph(entries, concurrency, dependencyIndexesFor, worker)\n } finally {\n uninstall()\n disposeRunSignal()\n }\n}\n","import type { ParsedCheckEntry } from \"../evidence/build-evidence.js\"\nimport {\n PolicyReadFailedParseValueError,\n PolicyReadUnrequestedOutputError,\n PolicyThrewError,\n} from \"../errors.js\"\nimport type {\n CheckEvidence,\n Evidence,\n ParsedOutput,\n PolicyOutcome,\n PolicyResult,\n Verdict,\n} from \"../types.js\"\n\nconst POLICY_OUTCOMES: readonly PolicyOutcome[] = [\"pass\", \"fail\", \"warn\"]\n\n/** `ParsedOutput`'s own field names -- the only properties reading `result.output` when it's `undefined` can throw on, so only these can trigger `PolicyReadUnrequestedOutputError` below. */\nconst OUTPUT_PROPERTIES: ReadonlySet<string> = new Set([\"success\", \"value\", \"error\", \"format\"])\n\n/**\n * Recognizes the one `TypeError` message shape Node/V8 produces for reading a\n * property off `undefined` (\"Cannot read properties of undefined (reading\n * 'x')\"), and returns `x`. Returns `undefined` for any other error -- a\n * `TypeError` with a different message, or anything that isn't a `TypeError`\n * at all.\n * @param error - the value caught from invoking (or awaiting) a check's `policy`\n * @returns the read property name, or `undefined` if `error` doesn't match this exact shape\n */\nfunction readPropertyOfUndefined(error: unknown): string | undefined {\n if (!(error instanceof TypeError)) return undefined\n const match = /^Cannot read properties of undefined \\(reading '([^']+)'\\)$/.exec(error.message)\n return match?.[1]\n}\n\n/**\n * Narrows `readPropertyOfUndefined` to the specific shape\n * `PolicyReadUnrequestedOutputError` covers: the read property must also be\n * one of `ParsedOutput`'s own fields. This meaningfully narrows false\n * positives -- an unrelated `TypeError` reading some other property name\n * never matches -- but is not proof the read actually came from\n * `result.output` itself: V8's own error message carries only the property\n * name, never the object expression it was read from, so a policy's own\n * unrelated bug that happens to read `.success`/`.value`/`.error`/`.format`\n * off some other `undefined` value (a `Result`-shaped helper's own return\n * value, for instance) can still be misclassified as this specific mistake.\n * The residual risk from that is a misleading message, never a lost error --\n * `cause` always preserves the original `TypeError` verbatim regardless of\n * which class wraps it, so the true cause is still recoverable by a consumer\n * that inspects it.\n * @param error - the value caught from invoking (or awaiting) a check's `policy`\n * @returns the read property name, or `undefined` if `error` doesn't match this exact shape\n */\nfunction unrequestedOutputProperty(error: unknown): string | undefined {\n const property = readPropertyOfUndefined(error)\n // Provably equivalent, not a coverage gap: OUTPUT_PROPERTIES is a\n // ReadonlySet<string>, so `.has(undefined)` is always false regardless of\n // its argument's runtime value -- the `property !== undefined` guard is\n // therefore redundant with the `.has()` call it short-circuits into, and\n // no test can observe a difference between keeping and removing it. Kept\n // anyway for readability (it documents \"must be a recognized property\n // name\" without requiring the reader to already know Set.has's behavior\n // on undefined), not because it changes behavior.\n // Stryker disable next-line ConditionalExpression -- OUTPUT_PROPERTIES is a ReadonlySet<string>, so .has(undefined) is always false regardless of argument, making the property !== undefined guard redundant with the .has() call it short-circuits into; removing it changes no observable behavior.\n return property !== undefined && OUTPUT_PROPERTIES.has(property) ? property : undefined\n}\n\n/**\n * Chooses which error wraps one check's caught policy failure. Defaults to a\n * plain `PolicyThrewError`, upgrading to a more specific, more actionable\n * class for two known mistakes, each recognized by cross-checking the\n * failure's own `TypeError` message shape against this check's own `output`\n * evidence:\n * - `output === undefined` (this check never requested a format) and the\n * read property is one of `ParsedOutput`'s own fields --\n * `PolicyReadUnrequestedOutputError`.\n * - `output.success === false` (this check requested a format, but that\n * parse failed, so `result.output` has no `value` field) and *any*\n * property was read off the resulting `undefined` -- `PolicyReadFailedParseValueError`.\n * A `TypeError` that doesn't match either shape, or a check whose `output`\n * matches neither state, always falls back to the plain `PolicyThrewError`\n * every other policy failure already gets. Both upgrades are best-effort\n * inference from the failure's own message text, not a verified trace back\n * to `result.output` -- see `unrequestedOutputProperty`'s own doc comment\n * for the specific, known false-positive channel this leaves open.\n * @param checkId - the failing check's id\n * @param output - that check's own `CheckEvidence.output`, tested against the two shapes above\n * @param error - the value caught from invoking (or awaiting) the check's `policy`\n * @returns the error to record for this check\n */\nfunction wrapPolicyFailure(\n checkId: string,\n output: ParsedOutput<unknown> | undefined,\n error: unknown,\n): PolicyThrewError | PolicyReadUnrequestedOutputError | PolicyReadFailedParseValueError {\n if (output === undefined) {\n const property = unrequestedOutputProperty(error)\n if (property !== undefined) {\n return new PolicyReadUnrequestedOutputError(checkId, property, error)\n }\n } else if (!output.success) {\n const property = readPropertyOfUndefined(error)\n if (property !== undefined) {\n return new PolicyReadFailedParseValueError(checkId, property, error)\n }\n }\n return new PolicyThrewError(checkId, error)\n}\n\n/**\n * Describes why `value` is not a valid `PolicyResult`, or `undefined` if it\n * is one. A TypeScript-authored policy can never fail this (the type\n * checker already guarantees it), but nothing stops a JavaScript consumer,\n * or a typo'd literal (`\"failed\"` instead of `\"fail\"`), from returning\n * something else at runtime -- and `passed` below is computed by comparing\n * `outcome` against `\"fail\"`, so an unvalidated garbage value would\n * otherwise be silently treated as non-failing.\n * @param value - the raw, `await`-ed return value of a check's `policy` call\n * @returns a human-readable reason `value` is invalid, or `undefined` if it is a valid `PolicyResult`\n */\nfunction invalidPolicyResultReason(value: unknown): string | undefined {\n if (typeof value !== \"object\" || value === null) {\n // `typeof null === \"object\"` in JavaScript, so falling back to `typeof value`\n // here would nonsensically report \"got object\" for a null result.\n return `expected an object with \"outcome\" and \"rationale\", got ${value === null ? \"null\" : typeof value}`\n }\n const outcome = (value as { outcome?: unknown }).outcome\n if (!POLICY_OUTCOMES.includes(outcome as PolicyOutcome)) {\n return `\"outcome\" must be \"pass\", \"fail\", or \"warn\", got ${typeof outcome === \"string\" ? `\"${outcome}\"` : typeof outcome}`\n }\n const rationale = (value as { rationale?: unknown }).rationale\n if (typeof rationale !== \"string\") {\n return `\"rationale\" must be a string, got ${typeof rationale}`\n }\n return undefined\n}\n\n/**\n * Invokes every check's `policy` against the complete, already-assembled\n * `evidence` and aggregates the results into a `Verdict`. Each check's\n * `PolicyResult` is stored verbatim under `checks[checkId]`; `passed` is\n * `true` only if every check's `outcome` is `\"pass\"` or `\"warn\"` -- no\n * check's failure is ever collapsed into another's or into one generic\n * message.\n *\n * Every `policy` call is isolated: the call itself is wrapped in `try/catch`\n * (a policy doesn't have to be `async` to misbehave), and its returned\n * value is `await`-ed inside that same `try`, so a promise that rejects\n * later is caught identically to a synchronous throw. The resolved value is\n * then validated against the `PolicyResult` contract itself (`outcome` is\n * exactly `\"pass\"`, `\"fail\"`, or `\"warn\"`; `rationale` is a string) before\n * it is trusted -- a malformed result is treated exactly like a throw\n * rather than silently coerced into a pass. Every one of these failure\n * modes is wrapped in `PolicyThrewError`, with the original thrown value\n * (or a descriptive validation error, for a malformed result) preserved via\n * `cause` -- never stringified or discarded. Two specific, common mistakes\n * get a more actionable error instead, chosen by `wrapPolicyFailure` (see\n * its own doc comment): reading a `result.output` property on a check that\n * never requested a format (`PolicyReadUnrequestedOutputError`), and reading\n * `result.output.value` on a check whose requested parse actually failed\n * (`PolicyReadFailedParseValueError`) -- both still preserve the original\n * `TypeError` via `cause`, exactly like the plain `PolicyThrewError` case.\n * One policy failing this way never stops any other check's policy from\n * running. If more than one policy fails this way in the same run, this\n * function throws a native `AggregateError` whose `errors` holds one such\n * error per failing check, rather than surfacing only the first one found;\n * if exactly one policy fails, it throws that single error directly.\n * @param entries - each check's id, definition, and final (possibly parsed-output) evidence to invoke its policy against\n * @param evidence - the complete, already-assembled evidence for the whole run, passed to every policy call\n * @returns the aggregated `Verdict` (throws instead if one or more policies failed to produce a valid `PolicyResult`)\n */\nexport async function runPolicies(\n entries: readonly ParsedCheckEntry[],\n evidence: Evidence,\n): Promise<Verdict> {\n const thrown: (\n PolicyThrewError | PolicyReadUnrequestedOutputError | PolicyReadFailedParseValueError\n )[] = []\n // Indexed by each entry's position, not appended on promise resolution: the\n // policies run concurrently and settle in arbitrary order, but `Verdict.checks`\n // must follow declaration order, consistently with `Evidence.checks`.\n const checkResults: (readonly [string, PolicyResult] | undefined)[] = []\n\n await Promise.all(\n entries.map(async ([checkId, check, checkEvidence], entryIndex) => {\n // Derived on the fly from data that already exists (this check's own\n // dependsOn plus the already-fully-assembled evidence.checks) --\n // never persisted, so Evidence's own shape/schema doesn't grow. `{}`\n // for a check with no dependsOn, never undefined.\n const dependencies: Record<string, CheckEvidence> = {}\n // `check.dependsOn` is `undefined` exactly for a check with no\n // declared dependencies, in which case this fallback's own contents\n // are unobservable regardless of what they are: any id it iterated\n // would look up `evidence.checks[depId]`, find nothing (no real\n // check has that id), and be filtered out by the guard below anyway\n // -- confirmed by \"a policy with no dependsOn sees ctx.dependencies\n // as an empty object\" in run-policies.test.ts, which passes\n // regardless of this fallback's specific value.\n // Stryker disable next-line ArrayDeclaration -- check.dependsOn is undefined exactly when a check has no declared dependencies, in which case this fallback's contents are unobservable regardless of value: any id iterated would look up evidence.checks[depId], find nothing, and get filtered out by the guard below anyway, confirmed by the \"sees ctx.dependencies as an empty object\" test in run-policies.test.ts, which passes regardless of the fallback's specific value.\n for (const depId of check.dependsOn ?? []) {\n const depEvidence = evidence.checks[depId]\n // validate-config.ts already guarantees every dependsOn id names a\n // check that exists in this run, and the phasing invariant\n // guarantees its evidence is already assembled by the time any\n // policy runs -- this guard exists only to satisfy\n // noUncheckedIndexedAccess.\n // Stryker disable next-line ConditionalExpression -- validate-config.ts already guarantees every dependsOn id names a check that exists in this run, and the phasing invariant guarantees its evidence is already assembled by the time any policy runs; this guard exists only to satisfy noUncheckedIndexedAccess.\n if (depEvidence !== undefined) dependencies[depId] = depEvidence\n }\n\n let outcome: PolicyResult\n try {\n const rawResult: unknown = await check.policy({\n result: checkEvidence,\n evidence,\n dependencies,\n })\n const invalidReason = invalidPolicyResultReason(rawResult)\n if (invalidReason !== undefined) {\n throw new Error(\n `Policy for check \"${checkId}\" returned an invalid PolicyResult: ${invalidReason}.`,\n )\n }\n outcome = rawResult as PolicyResult\n } catch (error) {\n thrown.push(wrapPolicyFailure(checkId, checkEvidence.output, error))\n return\n }\n checkResults[entryIndex] = [checkId, outcome]\n }),\n )\n\n if (thrown.length === 1) {\n const [only] = thrown as [\n PolicyThrewError | PolicyReadUnrequestedOutputError | PolicyReadFailedParseValueError,\n ]\n throw only\n }\n // `> 1` vs `>= 1` are equivalent here given the early return just above --\n // by the time this line can even run, thrown.length is never 1 (either 0,\n // falling through to the success path below either way, or >= 2, taking\n // this branch either way). Documented rather than silently accepted so a\n // future refactor that removes the early return doesn't quietly widen it.\n // Stryker disable next-line EqualityOperator -- \"> 1\" vs. \">= 1\" are equivalent here given the early return just above: by the time this line runs, thrown.length is never exactly 1 (either 0 or >= 2 either way), documented so a future refactor that removes the early return doesn't quietly widen this silently.\n if (thrown.length > 1) {\n throw new AggregateError(\n thrown,\n `${String(thrown.length)} check policies threw instead of returning a PolicyResult.`,\n )\n }\n\n // Every slot is filled and in declaration order by now: a policy that threw\n // took the early-return path above, so `thrown.length === 0` here means every\n // `entries.map` callback assigned its `checkResults[entryIndex]`. The cast\n // just drops the `| undefined` the sparse-write type carries.\n const orderedResults = checkResults as readonly (readonly [string, PolicyResult])[]\n const checks = Object.fromEntries(orderedResults)\n const passed = orderedResults.every(([, result]) => result.outcome !== \"fail\")\n\n return { version: 2, passed, checks }\n}\n","import * as os from \"node:os\"\nimport { validateRepoContractConfig } from \"./config/validate-config.js\"\nimport { buildEvidence } from \"./evidence/build-evidence.js\"\nimport { runChecks } from \"./execution/run-checks.js\"\nimport { runPolicies } from \"./policy/run-policies.js\"\nimport type {\n CheckSchema,\n Evidence,\n RepoContractConfig,\n RunRepoContractOptions,\n Verdict,\n} from \"./types.js\"\n\n/**\n * Executes every configured check, collects its evidence, optionally parses\n * its output, evaluates every check's repository-owned policy against the\n * complete evidence, and aggregates a verdict. Never calls `process.exit()`\n * -- returns data; the caller decides what to do with `verdict.passed`.\n *\n * Structural configuration problems throw synchronously before any process\n * spawns. Anything only discoverable by attempting execution (a missing\n * binary, a bad `cwd`) becomes evidence on that check (`status:\n * \"spawn_error\"`), never a throw. A policy function throwing or rejecting\n * rejects this function's own returned promise (`PolicyThrewError`, or an\n * `AggregateError` of them if more than one policy failed this way) --\n * distinct from a policy that ran fine and simply returned a failure\n * string. See `src/errors.ts` for the full distinction.\n *\n * Execution and policy evaluation are strictly phased: every check finishes\n * running and every check's evidence is fully assembled before any policy\n * is invoked (see specs/architecture.md) -- a policy can safely read\n * `ctx.evidence` for any sibling check's result.\n *\n * Deliberately not declared `async`: validation runs and can throw before this function returns\n * anything at all, so a config problem is a genuine synchronous exception to the caller (as\n * documented above and on `InvalidRepoContractConfigError`/`InvalidCheckConfigError`/\n * `DependencyDeclaredLaterError`) -- an `async function`'s body runs to its first `await` still\n * inside the caller's own call stack, but any throw before that point is nonetheless converted by\n * the language into a rejected `Promise`, never surfaced as a synchronous exception. Splitting\n * validation out into this synchronous wrapper, with the rest of the work in the `async` function\n * below, keeps the documented synchronous-throw guarantee actually true rather than aspirational.\n * @param config - the repo-contract configuration to run: its checks, concurrency, and their policies\n * @param options - run options; `options.checks` restricts execution to specific check ids, `options.signal` allows cancelling the run\n * @returns the assembled `evidence` for every check together with the aggregated `verdict`\n */\nexport function runRepoContract<const TChecks extends CheckSchema>(\n config: RepoContractConfig<TChecks>,\n options?: RunRepoContractOptions,\n): Promise<{ evidence: Evidence<TChecks>; verdict: Verdict<TChecks> }> {\n validateRepoContractConfig(config)\n return runRepoContractAfterValidation(config, options)\n}\n\n/**\n * The rest of `runRepoContract`'s work, once its config has already been validated -- see that\n * function's own doc comment for why validation itself lives in a separate, non-`async` wrapper.\n * @param config - the already-validated repo-contract configuration to run.\n * @param options - run options; see `runRepoContract`.\n * @returns the assembled `evidence` for every check together with the aggregated `verdict`\n */\nasync function runRepoContractAfterValidation<const TChecks extends CheckSchema>(\n config: RepoContractConfig<TChecks>,\n options: RunRepoContractOptions | undefined,\n): Promise<{ evidence: Evidence<TChecks>; verdict: Verdict<TChecks> }> {\n // validateRepoContractConfig above already rejects any config.concurrency that is not a\n // positive integer (>= 1, see validate-config.ts), so by the time this line runs\n // config.concurrency is always either undefined or already truthy -- `??` and `&&` therefore\n // select the identical branch for every value this parameter can actually hold here.\n // Stryker disable next-line LogicalOperator -- validateRepoContractConfig above already rejects any config.concurrency that is not a positive integer (>= 1), so by the time this line runs config.concurrency is always either undefined or already truthy; `??` and `&&` therefore select the identical branch for every value this parameter can actually hold here, making them equivalent at this exact call site.\n const concurrency = config.concurrency ?? os.availableParallelism()\n const startedAt = new Date()\n\n const results = await runChecks(config.checks, concurrency, options)\n const completedAt = new Date()\n\n const { evidence, entries } = await buildEvidence(results, startedAt, completedAt)\n const verdict = await runPolicies(entries, evidence)\n\n return { evidence, verdict } as { evidence: Evidence<TChecks>; verdict: Verdict<TChecks> }\n}\n"]}
|
package/dist/presets.cjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
|
|
5
|
+
function checkDependencyInstalled(result,packageName){if(result.status!=="spawn_error"||result.spawnErrorCode!=="ENOENT"){return void 0}return {outcome:"fail",rationale:`\`${packageName}\` is required by this preset but was not found. Install \`${packageName}\` as a development dependency and run the contract again.`}}function combinedOutput(result){return [result.stdout.trim(),result.stderr.trim()].filter(value=>Boolean(value)).join("\n")}function exitCodeFailRationale(result,description){const output=combinedOutput(result);return output.length>0?`${description}:
|
|
6
|
+
${output}`:`${description} (exit code ${String(result.exitCode)}).`}function checkTerminatedAbnormally(result,toolName){if(result.status==="completed"){return void 0}const detail=combinedOutput(result);const suffix=detail.length>0?`
|
|
7
|
+
${detail}`:"";switch(result.status){case "timed_out":return {outcome:"fail",rationale:`${toolName} did not finish: its process exceeded the configured timeout and was terminated.${suffix}`};case "aborted":return {outcome:"fail",rationale:`${toolName} did not finish: the run was aborted before its process completed.${suffix}`};case "host_terminated":return {outcome:"fail",rationale:`${toolName} did not finish: the host process received a termination signal and killed it.${suffix}`};case "signaled":return {outcome:"fail",rationale:`${toolName} was terminated by signal ${result.signal??"unknown"} before it completed.${suffix}`};case "spawn_error":return {outcome:"fail",rationale:`${toolName} could not be started: ${result.spawnError??"the process failed to spawn"}.`};default:{const unhandled=result.status;throw new Error(`checkTerminatedAbnormally: unhandled check status ${String(unhandled)}`)}}}function formatProblem(problem){const context=[problem.entrypoint?`entrypoint=${problem.entrypoint}`:void 0,problem.resolutionKind?`resolution=${problem.resolutionKind}`:void 0,problem.typesFileName?`types=${problem.typesFileName}`:void 0,problem.implementationFileName?`impl=${problem.implementationFileName}`:void 0].filter(value=>value!==void 0);return context.length>0?`${problem.kind}: ${context.join(" ")}`:problem.kind}function evaluateAttwReport(report){if(typeof report!=="object"||report===null){return {outcome:"fail",rationale:"@arethetypeswrong/cli produced invalid JSON report data."}}const rawProblems=report.problems;const problemGroups=typeof rawProblems==="object"&&rawProblems!==null?Object.values(rawProblems):[];const problems=problemGroups.flat().filter(problem=>typeof problem==="object"&&problem!==null);if(problems.length===0){return {outcome:"pass",rationale:"@arethetypeswrong/cli found 0 packaged type-resolution problem(s)."}}return {outcome:"fail",rationale:[`@arethetypeswrong/cli found ${String(problems.length)} packaged type-resolution problem(s):`,...problems.map(problem=>`- ${formatProblem(problem)}`)].join("\n")}}var arethetypeswrong={run:["attw","--pack",".","--format","json"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"@arethetypeswrong/cli");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"@arethetypeswrong/cli");if(terminated)return terminated;if(!result.output?.success){return {outcome:"fail",rationale:"@arethetypeswrong/cli output could not be parsed as JSON."}}return evaluateAttwReport(result.output.value)}};function formatBrokenLink(link){const parent=link.parent?` (linked from ${link.parent})`:"";return `${link.url} -- HTTP ${String(link.status)}${parent}`}function brokenLinks(options={}){const{start="."}=options;return {run:["linkinator",start,"--recurse","--format","json","--skip","node_modules"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"linkinator");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"linkinator");if(terminated)return terminated;if(!result.output?.success){return {outcome:"fail",rationale:"linkinator output could not be parsed as JSON."}}const value=result.output.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"linkinator produced invalid JSON report data."}}const report=value;if(!Array.isArray(report.links)){return {outcome:"fail",rationale:"linkinator produced invalid JSON report data."}}const links=report.links;const brokenLinkResults=links.filter(link=>link.state==="BROKEN");if(brokenLinkResults.length===0){return {outcome:"pass",rationale:`linkinator found 0 broken link(s) across ${String(links.length)} checked.`}}return {outcome:"fail",rationale:[`linkinator found ${String(brokenLinkResults.length)} broken link(s):`,...brokenLinkResults.map(link=>`- ${formatBrokenLink(link)}`)].join("\n")}}}}function remediation(from){return ["","Rewrite the offending commit message(s) already on this branch:","",` git rebase -i ${from}`,"","In the editor, for each commit flagged above change `pick` to:"," - `reword` to keep the commit but fix its message (a valid Conventional Commit:"," `type(scope): subject`, lowercase type, no trailing period), or"," - `squash` / `fixup` to fold it into the commit before it, or"," - `drop` to remove the commit entirely.","","Then, if the branch is already pushed:",""," git push --force-with-lease"].join("\n")}function commitlint(options={}){const{from="origin/main",to="HEAD"}=options;return {run:["commitlint","--from",from,"--to",to],policy:({result})=>{const missing=checkDependencyInstalled(result,"@commitlint/cli");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"commitlint");if(terminated)return terminated;if(result.exitCode===0){return {outcome:"pass",rationale:`commitlint found 0 commit message violations between ${from} and ${to}.`}}return {outcome:"fail",rationale:exitCodeFailRationale(result,"commitlint reported commit message violations")+`
|
|
8
|
+
${remediation(from)}`}}}}function readReporterOptions(args){const flagIndex=args.indexOf("--reporter-options");const raw=flagIndex===-1?void 0:args[flagIndex+1];if(raw===void 0){return void 0}try{return JSON.parse(raw)}catch{return void 0}}function formatEntry(entry){if(Array.isArray(entry)){const group=entry;return {name:group.map(member=>member.name).join(", "),location:""}}const single=entry;const location=typeof single.line==="number"?`:${String(single.line)}:${String(single.col??1)}`:"";return {name:single.name,location}}function deadCode(options={}){const{exemptUnusedDevDependencies=[]}=options;return {run:["knip","--reporter","json","--reporter-options",JSON.stringify({exemptUnusedDevDependencies})],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"knip");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"Knip");if(terminated)return terminated;if(!result.output?.success){return {outcome:"fail",rationale:"Knip output could not be parsed as JSON."}}const value=result.output.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"Knip produced invalid JSON report data."}}const report=value;if(!Array.isArray(report.issues)){return {outcome:"fail",rationale:"Knip produced invalid JSON report data."}}const issues=report.issues;const exemptDevDependencies=new Set(readReporterOptions(result.args)?.exemptUnusedDevDependencies??[]);const details=[];for(const issue of issues){const addEntries=(label,entries,ignored=new Set)=>{for(const entry of entries??[]){const{name,location}=formatEntry(entry);if(ignored.has(name)){continue}details.push(`${issue.file}${location} \u2014 ${label}: ${name}`);}};addEntries("unused dependency",issue.dependencies);addEntries("unused devDependency",issue.devDependencies,exemptDevDependencies);addEntries("unused optional peer dependency",issue.optionalPeerDependencies);addEntries("unlisted dependency",issue.unlisted);addEntries("unresolved import",issue.unresolved);addEntries("unused export",issue.exports);addEntries("unused export (namespace)",issue.nsExports);addEntries("unused type",issue.types);addEntries("unused type (namespace)",issue.nsTypes);addEntries("unused namespace member",issue.namespaceMembers);addEntries("unused enum member",issue.enumMembers);addEntries("unlisted binary",issue.binaries);addEntries("duplicate export",issue.duplicates);addEntries("circular dependency",issue.cycles);addEntries("unused file",issue.files);}if(details.length===0){return {outcome:"pass",rationale:"Knip reported 0 issues."}}return {outcome:"fail",rationale:[`Knip reported ${String(details.length)} issue(s):`,...details.map(detail=>`- ${detail}`)].join("\n")}}}}async function readJsonReport(readRaw,onReadFailed,onParseFailed){let raw;try{raw=await readRaw();}catch{return {ok:false,result:{outcome:"fail",rationale:onReadFailed}}}try{return {ok:true,value:JSON.parse(raw)}}catch{return {ok:false,result:{outcome:"fail",rationale:onParseFailed}}}}var REPORT_PATH="reports/jscpd/jscpd-report.json";function duplication(options={}){const{path="."}=options;return {run:["jscpd",path,"--reporters","json","--output","reports/jscpd","--silent"],policy:async({result})=>{const missing=checkDependencyInstalled(result,"jscpd");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"jscpd");if(terminated)return terminated;const parsed=await readJsonReport(()=>promises.readFile(REPORT_PATH,"utf8"),"jscpd did not produce its expected JSON report.","jscpd produced invalid JSON evidence.");if(!parsed.ok)return parsed.result;const value=parsed.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"jscpd produced invalid JSON report data."}}const report=value;const total=report.statistics?.total;if(!Array.isArray(report.duplicates)||!total||typeof total.percentage!=="number"||!Number.isFinite(total.percentage)){return {outcome:"fail",rationale:"jscpd produced invalid JSON report data."}}const duplicates=report.duplicates;if(duplicates.length===0){return {outcome:"pass",rationale:`jscpd found 0 duplicated block(s) across ${String(total.sources)} file(s) (${String(total.lines)} lines).`}}const details=duplicates.map(duplicate=>`${duplicate.firstFile.name}:${String(duplicate.firstFile.start)} duplicates ${duplicate.secondFile.name}:${String(duplicate.secondFile.start)} -- ${String(duplicate.lines)} lines / ${String(duplicate.tokens)} tokens`);return {outcome:"fail",rationale:[`jscpd found ${String(duplicates.length)} duplicated block(s) (${total.percentage.toFixed(2)}% of ${String(total.lines)} lines across ${String(total.sources)} file(s)):`,...details.map(detail=>`- ${detail}`)].join("\n")}}}}function collectFailingSpecs(suites){const details=[];for(const suite of suites){for(const spec of suite.specs??[]){if(spec.ok)continue;const location=spec.line!==void 0?`:${String(spec.line)}`:"";const lastResult=(spec.tests??[]).flatMap(t=>t.results??[]).at(-1);const message=lastResult?.error?.message?.trim();details.push(`${spec.file??suite.title}${location} ${spec.title}${message?` \u2014 ${message}`:""}`);}if(suite.suites)details.push(...collectFailingSpecs(suite.suites));}return details}var e2e={run:["playwright","test","--reporter=json"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"@playwright/test");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"Playwright");if(terminated)return terminated;if(!result.output?.success){return {outcome:"fail",rationale:"Playwright output could not be parsed as JSON."}}const value=result.output.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"Playwright produced invalid JSON report data."}}const report=value;const stats=report.stats;if(!stats||typeof stats.unexpected!=="number"||typeof stats.flaky!=="number"){return {outcome:"fail",rationale:"Playwright produced invalid JSON report data."}}if(stats.unexpected===0&&stats.flaky===0){return {outcome:"pass",rationale:`Playwright completed ${String(stats.expected)} test(s) with 0 unexpected failures.`}}if(stats.unexpected>0){const details=collectFailingSpecs(report.suites??[]);return {outcome:"fail",rationale:[`Playwright reported ${String(stats.unexpected)} unexpected failure(s):`,...details.map(detail=>`- ${detail}`)].join("\n")}}return {outcome:"warn",rationale:`Playwright completed with ${String(stats.flaky)} flaky test(s) that eventually passed on retry.`}}};var format={run:["prettier","--write","."],policy:({result})=>{const missing=checkDependencyInstalled(result,"prettier");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"Prettier");if(terminated)return terminated;if(result.exitCode===0){return {outcome:"pass",rationale:"Prettier reported no formatting failures."}}return {outcome:"fail",rationale:exitCodeFailRationale(result,"Prettier reported formatting failures")}}};var license={run:["licensee","--production","--osi","--errors-only","--ndjson"],policy:({result})=>{const missing=checkDependencyInstalled(result,"licensee");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"licensee");if(terminated)return terminated;const trimmed=result.stdout.trim();if(trimmed.length===0){if(result.status==="completed"&&result.exitCode===0){return {outcome:"pass",rationale:"licensee found 0 production dependencies with a non-OSI-approved license."}}return {outcome:"fail",rationale:exitCodeFailRationale(result,"licensee did not evaluate any dependency licenses (it produced no output)")}}let entries;try{entries=trimmed.split("\n").map(line=>line.trim()).filter(line=>line.length>0).map(line=>JSON.parse(line));}catch{return {outcome:"fail",rationale:"licensee produced invalid JSON evidence."}}const details=entries.map(entry=>`${entry.name}@${entry.version}: ${entry.license??"unknown license"}`).sort();return {outcome:"fail",rationale:[`licensee found ${String(details.length)} production dependency(ies) without an OSI-approved license:`,...details.map(detail=>`- ${detail}`)].join("\n")}}};function errorWarningPassPolicy(toolName,errorDetails,warningDetails){if(errorDetails.length>0){return {outcome:"fail",rationale:[`${toolName} reported ${String(errorDetails.length)} error(s):`,...errorDetails.map(detail=>`- ${detail}`)].join("\n")}}if(warningDetails.length>0){return {outcome:"warn",rationale:[`${toolName} reported 0 errors but ${String(warningDetails.length)} warning(s):`,...warningDetails.map(detail=>`- ${detail}`)].join("\n")}}return {outcome:"pass",rationale:`${toolName} reported 0 errors and 0 warnings.`}}function renderMessages(results,severity){return results.flatMap(file=>file.messages.filter(message=>message.severity===severity).map(message=>{const rule=message.ruleId?` [${message.ruleId}]`:"";return `${file.filePath}:${String(message.line)}:${String(message.column)}${rule}: ${message.message}`}))}function lint(options={}){const{path="."}=options;return {run:["eslint",path,"--format","json"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"eslint");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"ESLint");if(terminated)return terminated;if(!result.output?.success){const printed=combinedOutput(result);return {outcome:"fail",rationale:printed.length>0?`ESLint output could not be parsed as JSON. ESLint printed:
|
|
9
|
+
${printed}`:"ESLint output could not be parsed as JSON."}}const value=result.output.value;if(!Array.isArray(value)||!value.every(file=>typeof file==="object"&&file!==null&&Array.isArray(file.messages))){return {outcome:"fail",rationale:"ESLint output could not be parsed as JSON."}}const results=value;const errorDetails=renderMessages(results,2);const warningDetails=renderMessages(results,1);return errorWarningPassPolicy("ESLint",errorDetails,warningDetails)}}}var REPORT_PATH2="reports/markdownlint.json";function formatFinding(finding){const rule=finding.ruleNames.join("/");const detail=finding.errorDetail?` (${finding.errorDetail})`:"";return `${finding.fileName}:${String(finding.lineNumber)} [${rule}]: ${finding.ruleDescription}${detail}`}function markdownlint(options={}){const{glob="**/*.md"}=options;return {run:["markdownlint-cli2",glob],policy:async({result})=>{const missing=checkDependencyInstalled(result,"markdownlint-cli2");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"markdownlint-cli2");if(terminated)return terminated;const parsed=await readJsonReport(()=>promises.readFile(REPORT_PATH2,"utf8"),`markdownlint-cli2 did not produce its expected JSON report -- confirm your .markdownlint-cli2.jsonc configures outputFormatters to write "${REPORT_PATH2}" (requires the markdownlint-cli2-formatter-json package).`,"markdownlint-cli2 produced invalid JSON evidence.");if(!parsed.ok)return parsed.result;if(!Array.isArray(parsed.value)){return {outcome:"fail",rationale:"markdownlint-cli2 produced invalid JSON report data."}}const findings=parsed.value;if(findings.length===0){return {outcome:"pass",rationale:"markdownlint-cli2 reported 0 issues."}}return {outcome:"fail",rationale:[`markdownlint-cli2 reported ${String(findings.length)} issue(s):`,...findings.map(finding=>`- ${formatFinding(finding)}`)].join("\n")}}}}var publint={run:["publint","run"],policy:({result})=>{const missing=checkDependencyInstalled(result,"publint");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"publint");if(terminated)return terminated;const output=combinedOutput(result);if(result.exitCode===0){return output.includes("Warnings:")||output.includes("Suggestions:")?{outcome:"warn",rationale:`publint reported non-blocking finding(s):
|
|
10
|
+
${output}`}:{outcome:"pass",rationale:"publint reported no packaging errors."}}return {outcome:"fail",rationale:exitCodeFailRationale(result,"publint reported packaging error(s)")}}};var securityDeps={run:["npm","audit","--omit=dev","--json"],output:{format:"json"},policy:({result})=>{const terminated=checkTerminatedAbnormally(result,"npm audit");if(terminated)return terminated;if(!result.output?.success){return {outcome:"fail",rationale:"npm audit output could not be parsed as JSON."}}const value=result.output.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"npm audit produced invalid JSON report data."}}const report=value;const vulnerabilities=report.metadata?.vulnerabilities;if(!vulnerabilities){return {outcome:"fail",rationale:"npm audit produced no vulnerability summary."}}const severities=["info","low","moderate","high","critical"];const missingCount=severities.find(severity=>!Number.isFinite(vulnerabilities[severity]));if(missingCount!==void 0){return {outcome:"fail",rationale:`npm audit's vulnerability summary is missing a numeric "${missingCount}" count -- the report could not be evaluated.`}}const blocking=vulnerabilities.low+vulnerabilities.moderate+vulnerabilities.high+vulnerabilities.critical;if(blocking>0){const details=Object.entries(report.vulnerabilities??{}).map(([name,vulnerability])=>{const severity=vulnerability.severity??"unknown";const dependencyType=vulnerability.isDirect===true?"direct":"transitive";const range=vulnerability.range?` range=${vulnerability.range}`:"";const fix=vulnerability.fixAvailable===true?" fix available":vulnerability.fixAvailable?" remediation available":" no automatic fix available";return `${name}: ${severity} (${dependencyType})${range};${fix}`}).sort();return {outcome:"fail",rationale:[`npm audit found ${String(blocking)} runtime vulnerability(ies):`,...details.map(detail=>`- ${detail}`)].join("\n")}}if(vulnerabilities.info>0){return {outcome:"warn",rationale:`Runtime dependency policy passed. 0 critical, 0 high, 0 moderate, and 0 low vulnerabilities were found. ${String(vulnerabilities.info)} info-severity finding(s) remain and are non-blocking under repository policy.`}}return {outcome:"pass",rationale:"Runtime dependency policy passed. 0 critical, 0 high, 0 moderate, and 0 low vulnerabilities were found."}}};var securitySecrets={run:["secretlint","--format","json","--output","reports/secretlint.json","**/*"],policy:async({result})=>{const missing=checkDependencyInstalled(result,"secretlint");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"Secretlint");if(terminated)return terminated;const parsed=await readJsonReport(()=>promises.readFile("reports/secretlint.json","utf8"),"Secretlint did not produce its expected JSON report.","Secretlint produced invalid JSON evidence.");if(!parsed.ok)return parsed.result;if(!Array.isArray(parsed.value)){return {outcome:"fail",rationale:"Secretlint produced invalid JSON report data."}}const results=parsed.value;const findings=results.flatMap(file=>{if(typeof file!=="object"||file===null||!Array.isArray(file.messages)){return []}const typedFile=file;return typedFile.messages.map(message=>({file:typedFile.filePath,line:message.line,column:message.column,ruleId:message.ruleId}))});if(findings.length===0){return {outcome:"pass",rationale:"Secretlint found 0 potential secrets."}}const details=findings.map(finding=>{const rule=finding.ruleId?` [${finding.ruleId}]`:"";return `${finding.file}:${String(finding.line)}:${String(finding.column)}${rule}`});return {outcome:"fail",rationale:[`Secretlint found ${String(findings.length)} potential secret(s):`,...details.map(detail=>`- ${detail}`),"Potential secret values are intentionally omitted. Remove the secret, replace it with an appropriate secret-management mechanism, and rotate any credential that may already have been exposed."].join("\n")}}};function stylelint(options={}){const{glob="**/*.{css,scss}"}=options;return {run:["stylelint",glob,"--formatter","json"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"stylelint");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"stylelint");if(terminated)return terminated;if(!result.output?.success){const printed=combinedOutput(result);return {outcome:"fail",rationale:printed.length>0?`stylelint output could not be parsed as JSON. stylelint printed:
|
|
11
|
+
${printed}`:"stylelint output could not be parsed as JSON."}}const value=result.output.value;if(!Array.isArray(value)||!value.every(entry=>typeof entry==="object"&&entry!==null&&Array.isArray(entry.warnings))){return {outcome:"fail",rationale:"stylelint output could not be parsed as JSON."}}const results=value;const render=(file,warning)=>`${file.source??"<unknown file>"}:${String(warning.line)}:${String(warning.column)} [${warning.rule}]: ${warning.text}`;const errorDetails=results.flatMap(file=>file.warnings.filter(warning=>warning.severity==="error").map(warning=>render(file,warning)));const warningDetails=results.flatMap(file=>file.warnings.filter(warning=>warning.severity==="warning").map(warning=>render(file,warning)));return errorWarningPassPolicy("stylelint",errorDetails,warningDetails)}}}function evaluateVitestJsonPolicy(output){if(!output?.success){return {outcome:"fail",rationale:"Vitest output could not be parsed as JSON."}}const value=output.value;if(typeof value!=="object"||value===null){return {outcome:"fail",rationale:"Vitest produced invalid JSON report data."}}const report=value;if(!Array.isArray(report.testResults)){return {outcome:"fail",rationale:"Vitest produced invalid JSON report data."}}if(report.numFailedTests===0&&report.numFailedTestSuites===0){return {outcome:"pass",rationale:`Vitest completed ${String(report.numTotalTests)} test(s) with 0 failures across ${String(report.numTotalTestSuites)} suite(s).`}}const failures=report.testResults.flatMap(suite=>(suite.assertionResults??[]).filter(test2=>test2.status==="failed").map(test2=>{const location=test2.location?`:${String(test2.location.line)}:${String(test2.location.column)}`:"";const messages=(test2.failureMessages??[]).map(message=>message.trim()).filter(Boolean).join(" | ");return [`${suite.name}${location}`,test2.fullName,messages].filter(Boolean).join(" \u2014 ")}));return {outcome:"fail",rationale:[`Vitest reported ${String(report.numFailedTests)} failing test(s) across ${String(report.numFailedTestSuites)} failing suite(s):`,...failures.map(failure=>`- ${failure}`)].join("\n")}}var test={run:["vitest","run","--reporter=json"],output:{format:"json"},policy:({result})=>{const missing=checkDependencyInstalled(result,"vitest");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"Vitest");if(terminated)return terminated;return evaluateVitestJsonPolicy(result.output)}};var typecheck={run:["tsc","--noEmit","-p","tsconfig.json"],policy:({result})=>{const missing=checkDependencyInstalled(result,"typescript");if(missing)return missing;const terminated=checkTerminatedAbnormally(result,"TypeScript");if(terminated)return terminated;if(result.exitCode===0){return {outcome:"pass",rationale:"tsc reported no type errors."}}return {outcome:"fail",rationale:exitCodeFailRationale(result,"TypeScript reported type errors")}}};
|
|
12
|
+
|
|
13
|
+
exports.arethetypeswrong = arethetypeswrong;
|
|
14
|
+
exports.brokenLinks = brokenLinks;
|
|
15
|
+
exports.commitlint = commitlint;
|
|
16
|
+
exports.deadCode = deadCode;
|
|
17
|
+
exports.duplication = duplication;
|
|
18
|
+
exports.e2e = e2e;
|
|
19
|
+
exports.format = format;
|
|
20
|
+
exports.license = license;
|
|
21
|
+
exports.lint = lint;
|
|
22
|
+
exports.markdownlint = markdownlint;
|
|
23
|
+
exports.publint = publint;
|
|
24
|
+
exports.securityDeps = securityDeps;
|
|
25
|
+
exports.securitySecrets = securitySecrets;
|
|
26
|
+
exports.stylelint = stylelint;
|
|
27
|
+
exports.test = test;
|
|
28
|
+
exports.typecheck = typecheck;
|
|
29
|
+
//# sourceMappingURL=presets.cjs.map
|
|
30
|
+
//# sourceMappingURL=presets.cjs.map
|