@comity-dev/validate 0.1.1 → 0.2.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.
@@ -0,0 +1,48 @@
1
+ import { comityConfigSchema, createAjvInstance, packageMetadataSchema, } from "@comity-dev/schemas";
2
+ export function runSchema(repo) {
3
+ const start = Date.now();
4
+ const findings = [];
5
+ const ajv = createAjvInstance();
6
+ // Per-package metadata
7
+ const validateMeta = ajv.compile(packageMetadataSchema);
8
+ for (const pkg of repo.packages) {
9
+ const valid = validateMeta(pkg.manifest);
10
+ if (!valid) {
11
+ for (const err of validateMeta.errors ?? []) {
12
+ findings.push({
13
+ tool: "schema",
14
+ rule: "package-metadata",
15
+ package: pkg.name,
16
+ path: err.instancePath,
17
+ message: err.message ?? "schema violation",
18
+ keyword: err.keyword,
19
+ severity: "error",
20
+ });
21
+ }
22
+ }
23
+ }
24
+ // Repository config
25
+ const validateConfig = ajv.compile(comityConfigSchema);
26
+ const configValid = validateConfig(repo.config);
27
+ if (!configValid) {
28
+ for (const err of validateConfig.errors ?? []) {
29
+ findings.push({
30
+ tool: "schema",
31
+ rule: "comity-config",
32
+ path: err.instancePath,
33
+ message: err.message ?? "schema violation",
34
+ keyword: err.keyword,
35
+ severity: "error",
36
+ });
37
+ }
38
+ }
39
+ return {
40
+ executed: true,
41
+ exitCode: 0,
42
+ passed: findings.length === 0,
43
+ findings,
44
+ duration: Date.now() - start,
45
+ status: findings.length === 0 ? "PASS" : "FAIL",
46
+ tool: "schema",
47
+ };
48
+ }
@@ -0,0 +1,18 @@
1
+ import type { Repository } from "../discover.js";
2
+ import type { EngineResult } from "./types.js";
3
+ /**
4
+ * Discover the Python interpreter that ships the `semgrep` module.
5
+ *
6
+ * Selection order:
7
+ * 1. `SEMGREP_PYTHON` when set to an existing path — explicit operator
8
+ * configuration always wins and is never silently overridden.
9
+ * 2. Well-known absolute paths, then `python3` on PATH (historic default).
10
+ *
11
+ * @param env - Environment to read. Defaults to the process environment.
12
+ *
13
+ * @returns The selected interpreter path, or `null` when none is available.
14
+ * A set-but-missing `SEMGREP_PYTHON` also yields `null` so the caller can
15
+ * report it as invalid configuration rather than falling back silently.
16
+ */
17
+ export declare function resolvePythonForSemgrep(env?: NodeJS.ProcessEnv): string | null;
18
+ export declare function runSemgrep(repo: Repository): Promise<EngineResult>;
@@ -0,0 +1,281 @@
1
+ import { buildSemgrepConfig } from "@comity-dev/semgrep-rules";
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { relative, resolve } from "node:path";
5
+ /**
6
+ * Discover the Python interpreter that ships the `semgrep` module.
7
+ *
8
+ * Selection order:
9
+ * 1. `SEMGREP_PYTHON` when set to an existing path — explicit operator
10
+ * configuration always wins and is never silently overridden.
11
+ * 2. Well-known absolute paths, then `python3` on PATH (historic default).
12
+ *
13
+ * @param env - Environment to read. Defaults to the process environment.
14
+ *
15
+ * @returns The selected interpreter path, or `null` when none is available.
16
+ * A set-but-missing `SEMGREP_PYTHON` also yields `null` so the caller can
17
+ * report it as invalid configuration rather than falling back silently.
18
+ */
19
+ export function resolvePythonForSemgrep(env = process.env) {
20
+ const explicit = env["SEMGREP_PYTHON"];
21
+ if (explicit) {
22
+ return existsSync(explicit) ? explicit : null;
23
+ }
24
+ const candidates = [
25
+ "/usr/bin/python3",
26
+ "/usr/local/bin/python3",
27
+ "/opt/homebrew/bin/python3",
28
+ ];
29
+ for (const c of candidates) {
30
+ if (existsSync(c))
31
+ return c;
32
+ }
33
+ const probe = spawnSync("which", ["python3"], {
34
+ encoding: "utf8",
35
+ env: { ...env, PATH: env["PATH"] ?? "" },
36
+ });
37
+ if (probe.status === 0 && probe.stdout?.trim())
38
+ return probe.stdout.trim();
39
+ return null;
40
+ }
41
+ function findSemgrepBin(fallbackDirs) {
42
+ const explicitBin = process.env["SEMGREP_BIN"];
43
+ if (explicitBin && existsSync(explicitBin))
44
+ return explicitBin;
45
+ const probe = spawnSync("which", ["semgrep"], {
46
+ encoding: "utf8",
47
+ env: {
48
+ ...process.env,
49
+ PATH: `${fallbackDirs.join(":")}:${process.env["PATH"] ?? ""}`,
50
+ },
51
+ });
52
+ if (probe.status === 0 && probe.stdout?.trim()) {
53
+ return probe.stdout.trim();
54
+ }
55
+ const absoluteCandidates = [
56
+ "/usr/bin/semgrep",
57
+ "/usr/local/bin/semgrep",
58
+ "/opt/homebrew/bin/semgrep",
59
+ ];
60
+ for (const c of absoluteCandidates) {
61
+ if (existsSync(c))
62
+ return c;
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Returns the set of package directory names that should be excluded
68
+ * from Semgrep scanning. The Development-owned runtime rule set
69
+ * (`@comity-dev/semgrep-rules`) targets Comity Core Modules and
70
+ * Adapters; it MUST NOT fire on Development tooling packages
71
+ * (`comity.layer: "dev-tooling"`) because those packages legitimately
72
+ * use Node APIs, `process.env`, and other patterns the rules forbid
73
+ * for runtime Core Modules.
74
+ */
75
+ function devToolingPackageDirs(repo) {
76
+ const dirs = [];
77
+ for (const pkg of repo.packages) {
78
+ if (pkg.layer === "dev-tooling") {
79
+ const rel = relative(repo.root, pkg.path);
80
+ const top = rel.split("/")[0];
81
+ if (top)
82
+ dirs.push(top);
83
+ }
84
+ }
85
+ return [...new Set(dirs)];
86
+ }
87
+ function buildSemgrepArgs(configPath, target, excludedDirs) {
88
+ const args = [
89
+ "-c",
90
+ "from semgrep.console_scripts.entrypoint import main; main()",
91
+ "scan",
92
+ "--config",
93
+ configPath,
94
+ "--json",
95
+ "--quiet",
96
+ "--error",
97
+ target,
98
+ ];
99
+ // Exclude dev-tooling package directories by both path-pattern
100
+ // (via --exclude) and directory name (via --exclude-dir) so semgrep
101
+ // does not scan them at all. The runtime rule set is not designed
102
+ // for development-tooling source.
103
+ for (const d of excludedDirs) {
104
+ args.push("--exclude", `**/packages/${d}/**`);
105
+ args.push("--exclude-dir", d);
106
+ }
107
+ return args;
108
+ }
109
+ export async function runSemgrep(repo) {
110
+ const start = Date.now();
111
+ // Build the combined Semgrep config. Semgrep's CLI accepts a single
112
+ // --config path, so we concatenate all canonical rule files into one
113
+ // synthesized config.
114
+ const configYaml = await buildSemgrepConfig();
115
+ const tmpDir = mkdtempSync("/tmp/comity-validate-semgrep-");
116
+ const configPath = `${tmpDir}/semgrep-config.yaml`;
117
+ writeFileSync(configPath, configYaml);
118
+ const pathDirs = (process.env["PATH"] ?? "").split(":").filter(Boolean);
119
+ const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
120
+ const candidateDirs = [...new Set([...pathDirs, ...fallbackDirs])];
121
+ const cleanup = () => {
122
+ try {
123
+ rmSync(tmpDir, { recursive: true, force: true });
124
+ }
125
+ catch {
126
+ // ignore
127
+ }
128
+ };
129
+ const semgrepBin = findSemgrepBin(fallbackDirs);
130
+ if (!semgrepBin) {
131
+ cleanup();
132
+ return {
133
+ executed: false,
134
+ exitCode: null,
135
+ passed: false,
136
+ findings: [],
137
+ duration: Date.now() - start,
138
+ status: "TOOL-UNAVAILABLE",
139
+ tool: "semgrep",
140
+ reason: "semgrep binary not found on PATH; install semgrep or set SEMGREP_BIN",
141
+ };
142
+ }
143
+ // Locate the Python interpreter that ships the semgrep module.
144
+ // An explicit SEMGREP_PYTHON is honored as-is; a set-but-missing value
145
+ // is invalid configuration, never a reason to silently fall back.
146
+ const pythonBin = resolvePythonForSemgrep();
147
+ if (!pythonBin) {
148
+ cleanup();
149
+ const explicitPython = process.env["SEMGREP_PYTHON"];
150
+ return {
151
+ executed: false,
152
+ exitCode: null,
153
+ passed: false,
154
+ findings: [],
155
+ duration: Date.now() - start,
156
+ status: "TOOL-UNAVAILABLE",
157
+ tool: "semgrep",
158
+ reason: explicitPython
159
+ ? `SEMGREP_PYTHON is set to "${explicitPython}" but that path does not exist; set SEMGREP_PYTHON to a Python interpreter that ships the semgrep module`
160
+ : "Python interpreter not found; semgrep requires Python on PATH",
161
+ };
162
+ }
163
+ // Augment PATH so the inner Python process can find semgrep's
164
+ // sibling `pysemgrep` script (semgrep's `subprocess.run` does an
165
+ // `execvp("pysemgrep")` lookup against this PATH). Directories of the
166
+ // explicitly configured SEMGREP_BIN and resolved interpreter come first
167
+ // so a locally installed Semgrep resolves its own sibling scripts
168
+ // instead of whatever happens to be on PATH.
169
+ const configuredDirs = [semgrepBin, pythonBin]
170
+ .map((p) => p.slice(0, Math.max(0, p.lastIndexOf("/"))))
171
+ .filter((d) => d.length > 0);
172
+ const envPath = [
173
+ ...new Set(configuredDirs),
174
+ ...candidateDirs,
175
+ "/Users/filippo/Library/Python/3.14/bin",
176
+ "/Users/filippo/Library/Python/3.13/bin",
177
+ "/Users/filippo/Library/Python/3.12/bin",
178
+ process.env["PATH"] ?? "",
179
+ "/usr/bin",
180
+ "/bin",
181
+ ].join(":");
182
+ const target = resolve(repo.root, "packages");
183
+ const excludedDirs = devToolingPackageDirs(repo);
184
+ // If every discovered package is dev-tooling, the runtime rule
185
+ // set is not applicable to this repository. Report NOT-APPLICABLE
186
+ // rather than running semgrep against an empty target.
187
+ const runtimePackages = repo.packages.filter((p) => p.layer !== "dev-tooling");
188
+ if (runtimePackages.length === 0) {
189
+ cleanup();
190
+ return {
191
+ executed: false,
192
+ exitCode: null,
193
+ passed: true,
194
+ findings: [],
195
+ duration: Date.now() - start,
196
+ status: "NOT-APPLICABLE",
197
+ tool: "semgrep",
198
+ reason: "Repository contains no runtime packages; the @comity-dev/semgrep-rules rule set targets Comity Core Modules and Adapters and is not applicable to development-tooling-only repositories.",
199
+ };
200
+ }
201
+ const args = buildSemgrepArgs(configPath, target, excludedDirs);
202
+ const proc = spawnSync(pythonBin, args, {
203
+ cwd: repo.root,
204
+ env: { ...process.env, PATH: envPath },
205
+ maxBuffer: 16 * 1024 * 1024,
206
+ encoding: "utf8",
207
+ });
208
+ cleanup();
209
+ const stdout = proc.stdout ?? "";
210
+ const stderr = proc.stderr ?? "";
211
+ const findings = parseSemgrepOutput(stdout, stderr);
212
+ // If semgrep exited non-zero but produced no parseable findings, this
213
+ // is an EXECUTION-ERROR (configuration, rule-parse, internal crash),
214
+ // not a policy violation.
215
+ if (proc.status !== null && proc.status !== 0 && findings.length === 0) {
216
+ const stderrHead = stderr.split("\n")[0] ?? "";
217
+ const stdoutHead = stdout.slice(0, 500);
218
+ findings.push({
219
+ tool: "semgrep",
220
+ rule: "SEM-CRASH",
221
+ message: `semgrep exited with code ${proc.status}; stderr: ${stderrHead}; stdout (first 500): ${stdoutHead}`,
222
+ severity: "error",
223
+ });
224
+ return {
225
+ executed: true,
226
+ exitCode: proc.status,
227
+ passed: false,
228
+ findings,
229
+ duration: Date.now() - start,
230
+ status: "EXECUTION-ERROR",
231
+ tool: "semgrep",
232
+ };
233
+ }
234
+ return {
235
+ executed: true,
236
+ exitCode: proc.status,
237
+ passed: proc.status === 0 && findings.length === 0,
238
+ findings,
239
+ duration: Date.now() - start,
240
+ status: proc.status === 0 ? "PASS" : "FAIL",
241
+ tool: "semgrep",
242
+ };
243
+ }
244
+ function parseSemgrepOutput(stdout, stderr) {
245
+ const out = stdout ?? stderr ?? "";
246
+ if (!out.trim())
247
+ return [];
248
+ let parsed = null;
249
+ try {
250
+ parsed = JSON.parse(out);
251
+ }
252
+ catch {
253
+ return [];
254
+ }
255
+ const findings = [];
256
+ for (const e of parsed?.errors ?? []) {
257
+ if (e.level === "error") {
258
+ findings.push({
259
+ tool: "semgrep",
260
+ rule: `SEM-PARSE-${e.rule_id ?? "UNKNOWN"}`,
261
+ message: e.message ?? "semgrep rule parse error",
262
+ severity: "error",
263
+ });
264
+ }
265
+ }
266
+ const results = parsed?.results ?? [];
267
+ for (const r of results) {
268
+ const start = r["start"];
269
+ const extra = r["extra"];
270
+ findings.push({
271
+ tool: "semgrep",
272
+ rule: `SEM-${String(r["check_id"] ?? "UNKNOWN")}`,
273
+ message: extra?.message ?? "Semgrep rule violation",
274
+ file: typeof r["path"] === "string" ? r["path"] : undefined,
275
+ line: start?.line,
276
+ column: start?.col,
277
+ severity: extra?.severity === "ERROR" ? "error" : "warning",
278
+ });
279
+ }
280
+ return findings;
281
+ }
@@ -0,0 +1,25 @@
1
+ export type EngineStatus = "PASS" | "FAIL" | "NOT-RUN" | "TOOL-UNAVAILABLE" | "CONFIGURATION-ERROR" | "EXECUTION-ERROR" | "NOT-APPLICABLE";
2
+ export interface Finding {
3
+ tool: string;
4
+ rule: string;
5
+ package?: string | undefined;
6
+ path?: string | undefined;
7
+ message: string;
8
+ severity: "error" | "warning";
9
+ file?: string | undefined;
10
+ line?: number | undefined;
11
+ column?: number | undefined;
12
+ keyword?: string | undefined;
13
+ source?: string | undefined;
14
+ remediation?: string | undefined;
15
+ }
16
+ export interface EngineResult {
17
+ executed: boolean;
18
+ exitCode: number | null;
19
+ passed: boolean;
20
+ findings: Finding[];
21
+ duration: number;
22
+ status: EngineStatus;
23
+ tool?: string | undefined;
24
+ reason?: string | undefined;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Canonical exit codes for `comity validate`.
3
+ *
4
+ * 0 = validation passed
5
+ * 1 = validation failed (violations found)
6
+ * 2 = configuration / discovery error (cannot run)
7
+ * 3 = tool not installed (e.g. dependency-cruiser missing)
8
+ */
9
+ export declare const ExitCode: Readonly<{
10
+ readonly PASS: 0;
11
+ readonly FAIL: 1;
12
+ readonly CONFIG_ERROR: 2;
13
+ readonly TOOL_MISSING: 3;
14
+ }>;
15
+ export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Canonical exit codes for `comity validate`.
3
+ *
4
+ * 0 = validation passed
5
+ * 1 = validation failed (violations found)
6
+ * 2 = configuration / discovery error (cannot run)
7
+ * 3 = tool not installed (e.g. dependency-cruiser missing)
8
+ */
9
+ export const ExitCode = Object.freeze({
10
+ PASS: 0,
11
+ FAIL: 1,
12
+ CONFIG_ERROR: 2,
13
+ TOOL_MISSING: 3,
14
+ });
@@ -0,0 +1,33 @@
1
+ import type { Finding } from "./engines/types.js";
2
+ import type { CategoryResult } from "./run.js";
3
+ export interface FormatInput {
4
+ /** Whether the overall validation passed or failed */
5
+ passed: boolean;
6
+ /** The list of all findings across all categories */
7
+ violations: Finding[];
8
+ /** The results of each validation category */
9
+ categories: {
10
+ /** Schema validation results */
11
+ schema: CategoryResult;
12
+ /** Package metadata validation results */
13
+ metadata: CategoryResult;
14
+ /** Comity config validation results */
15
+ config: CategoryResult;
16
+ /** Dependency validation results */
17
+ dependencies: CategoryResult;
18
+ /** ESLint validation results */
19
+ eslint: CategoryResult;
20
+ /** Semgrep validation results */
21
+ semgrep: CategoryResult;
22
+ /** Adapter peers validation results */
23
+ adapterPeers: CategoryResult;
24
+ };
25
+ }
26
+ /**
27
+ * Format a unified summary for the terminal.
28
+ */
29
+ export declare function formatSummary(result: FormatInput): string;
30
+ /**
31
+ * Format detailed violations for debugging.
32
+ */
33
+ export declare function formatViolations(violations: Finding[]): string;
package/dist/format.js ADDED
@@ -0,0 +1,76 @@
1
+ const CATEGORY_LABELS = {
2
+ schema: "Schema",
3
+ metadata: "Package metadata",
4
+ config: "Comity config",
5
+ dependencies: "Dependencies (depcruise)",
6
+ eslint: "Source rules (ESLint)",
7
+ semgrep: "Structural patterns (Semgrep)",
8
+ adapterPeers: "Adapter peers",
9
+ };
10
+ function statusBadge(cat) {
11
+ switch (cat.status) {
12
+ case "PASS":
13
+ return "PASS";
14
+ case "FAIL":
15
+ return "FAIL";
16
+ case "NOT-RUN":
17
+ return "NOT-RUN";
18
+ case "TOOL-UNAVAILABLE":
19
+ return "TOOL-UNAVAILABLE";
20
+ case "CONFIGURATION-ERROR":
21
+ return "CONFIG-ERROR";
22
+ case "EXECUTION-ERROR":
23
+ return "EXEC-ERROR";
24
+ case "NOT-APPLICABLE":
25
+ return "NOT-APPLICABLE";
26
+ default:
27
+ return cat.executed ? (cat.passed ? "PASS" : "FAIL") : "NOT-RUN";
28
+ }
29
+ }
30
+ /**
31
+ * Format a unified summary for the terminal.
32
+ */
33
+ export function formatSummary(result) {
34
+ const lines = ["Comity validation", ""];
35
+ for (const [key, label] of Object.entries(CATEGORY_LABELS)) {
36
+ const cat = result.categories[key];
37
+ if (!cat)
38
+ continue;
39
+ const badge = statusBadge(cat);
40
+ const count = cat.findings > 0 ? ` (${cat.findings} findings)` : "";
41
+ const exit = cat.exitCode !== null && cat.exitCode !== 0
42
+ ? ` exit=${cat.exitCode}`
43
+ : "";
44
+ lines.push(`${badge.padEnd(18)} ${label}${count}${exit}`);
45
+ }
46
+ lines.push("");
47
+ for (const [key, label] of Object.entries(CATEGORY_LABELS)) {
48
+ const cat = result.categories[key];
49
+ if (!cat)
50
+ continue;
51
+ if (cat.status === "TOOL-UNAVAILABLE" ||
52
+ cat.status === "CONFIGURATION-ERROR" ||
53
+ cat.status === "EXECUTION-ERROR" ||
54
+ cat.status === "NOT-APPLICABLE") {
55
+ lines.push(` ${label}: ${cat.reason ?? cat.status}`);
56
+ }
57
+ }
58
+ lines.push("");
59
+ lines.push(`Result: ${result.passed ? "PASS" : "FAIL"}`);
60
+ return lines.join("\n");
61
+ }
62
+ /**
63
+ * Format detailed violations for debugging.
64
+ */
65
+ export function formatViolations(violations) {
66
+ if (violations.length === 0)
67
+ return "";
68
+ const lines = [`${violations.length} violation(s):\n`];
69
+ for (const v of violations) {
70
+ const loc = v.file
71
+ ? ` ${v.file}${v.line ? `:${v.line}` : ""}${v.column ? `:${v.column}` : ""}`
72
+ : "";
73
+ lines.push(`[${v.tool}] ${v.rule} ${v.package ?? ""}${loc} — ${v.message}`);
74
+ }
75
+ return lines.join("\n");
76
+ }
@@ -0,0 +1,9 @@
1
+ export type { DiscoverOptions, PackageRecord, Repository } from "./discover.js";
2
+ export type { EngineResult, EngineStatus, Finding } from "./engines/types.js";
3
+ export type { ExitCodeValue } from "./exit-codes.js";
4
+ export type { FormatInput } from "./format.js";
5
+ export type { CategoryResult, RunOptions, RunResult } from "./run.js";
6
+ export { discoverRepository } from "./discover.js";
7
+ export { ExitCode } from "./exit-codes.js";
8
+ export { formatSummary, formatViolations } from "./format.js";
9
+ export { runValidation } from "./run.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { discoverRepository } from "./discover.js";
2
+ export { ExitCode } from "./exit-codes.js";
3
+ export { formatSummary, formatViolations } from "./format.js";
4
+ export { runValidation } from "./run.js";
package/dist/run.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { EngineResult, Finding } from "./engines/types.js";
2
+ export interface RunOptions {
3
+ /** The root directory of the repository to validate */
4
+ root: string;
5
+ /** If specified, only run the given category of validation */
6
+ only?: string | null;
7
+ /** If true, print verbose output to stdout */
8
+ verbose?: boolean;
9
+ }
10
+ export interface CategoryResult {
11
+ /** The status of the engine for this category */
12
+ status: EngineResult["status"];
13
+ /** Whether the engine was executed or skipped */
14
+ executed: boolean;
15
+ /** The exit code of the engine process, if applicable */
16
+ exitCode: number | null;
17
+ /** Whether the engine passed or failed */
18
+ passed: boolean;
19
+ /** The number of findings produced by the engine */
20
+ findings: number;
21
+ /** The tool that produced this result, if applicable */
22
+ tool?: string | undefined;
23
+ /** The reason for the engine's status, if applicable */
24
+ reason?: string | undefined;
25
+ }
26
+ export interface RunResult {
27
+ /** Whether the overall validation passed or failed */
28
+ passed: boolean;
29
+ /** The list of all findings across all categories */
30
+ violations: Finding[];
31
+ /** The results of each validation category */
32
+ categories: {
33
+ /** Schema validation results */
34
+ schema: CategoryResult;
35
+ /** Package metadata validation results */
36
+ metadata: CategoryResult;
37
+ /** Comity config validation results */
38
+ config: CategoryResult;
39
+ /** Dependency validation results */
40
+ dependencies: CategoryResult;
41
+ /** ESLint validation results */
42
+ eslint: CategoryResult;
43
+ /** Semgrep validation results */
44
+ semgrep: CategoryResult;
45
+ /** Adapter peers validation results */
46
+ adapterPeers: CategoryResult;
47
+ };
48
+ }
49
+ /**
50
+ * Run validation against a repository. Returns a normalized result.
51
+ *
52
+ * Every category reports either an executed engine with its real exit
53
+ * code, or an explicit NOT-RUN / TOOL-UNAVAILABLE / CONFIGURATION-ERROR
54
+ * state. PASS without execution is impossible.
55
+ */
56
+ export declare function runValidation(options: RunOptions): Promise<RunResult>;