patchdrill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/.patchdrill.yml +33 -0
  2. package/CHANGELOG.md +150 -0
  3. package/CONTRIBUTING.md +59 -0
  4. package/LICENSE +21 -0
  5. package/README.md +601 -0
  6. package/SECURITY.md +28 -0
  7. package/action.yml +338 -0
  8. package/dist/baseline.d.ts +9 -0
  9. package/dist/baseline.js +38 -0
  10. package/dist/baseline.js.map +1 -0
  11. package/dist/cli.d.ts +19 -0
  12. package/dist/cli.js +662 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/codeowners.d.ts +14 -0
  15. package/dist/codeowners.js +104 -0
  16. package/dist/codeowners.js.map +1 -0
  17. package/dist/command-plan.d.ts +3 -0
  18. package/dist/command-plan.js +26 -0
  19. package/dist/command-plan.js.map +1 -0
  20. package/dist/demo.d.ts +5 -0
  21. package/dist/demo.js +525 -0
  22. package/dist/demo.js.map +1 -0
  23. package/dist/dependency.d.ts +4 -0
  24. package/dist/dependency.js +1424 -0
  25. package/dist/dependency.js.map +1 -0
  26. package/dist/doctor.d.ts +26 -0
  27. package/dist/doctor.js +183 -0
  28. package/dist/doctor.js.map +1 -0
  29. package/dist/evidence.d.ts +64 -0
  30. package/dist/evidence.js +352 -0
  31. package/dist/evidence.js.map +1 -0
  32. package/dist/git.d.ts +16 -0
  33. package/dist/git.js +349 -0
  34. package/dist/git.js.map +1 -0
  35. package/dist/i18n-catalog.d.ts +8 -0
  36. package/dist/i18n-catalog.js +446 -0
  37. package/dist/i18n-catalog.js.map +1 -0
  38. package/dist/i18n.d.ts +20 -0
  39. package/dist/i18n.js +67 -0
  40. package/dist/i18n.js.map +1 -0
  41. package/dist/init.d.ts +13 -0
  42. package/dist/init.js +312 -0
  43. package/dist/init.js.map +1 -0
  44. package/dist/markdown-links.d.ts +18 -0
  45. package/dist/markdown-links.js +180 -0
  46. package/dist/markdown-links.js.map +1 -0
  47. package/dist/package-scripts.d.ts +3 -0
  48. package/dist/package-scripts.js +55 -0
  49. package/dist/package-scripts.js.map +1 -0
  50. package/dist/planner.d.ts +8 -0
  51. package/dist/planner.js +2351 -0
  52. package/dist/planner.js.map +1 -0
  53. package/dist/policy.d.ts +12 -0
  54. package/dist/policy.js +255 -0
  55. package/dist/policy.js.map +1 -0
  56. package/dist/project.d.ts +2 -0
  57. package/dist/project.js +1085 -0
  58. package/dist/project.js.map +1 -0
  59. package/dist/release-readiness.d.ts +25 -0
  60. package/dist/release-readiness.js +426 -0
  61. package/dist/release-readiness.js.map +1 -0
  62. package/dist/report-annotations.d.ts +3 -0
  63. package/dist/report-annotations.js +28 -0
  64. package/dist/report-annotations.js.map +1 -0
  65. package/dist/report-contract.d.ts +2 -0
  66. package/dist/report-contract.js +82 -0
  67. package/dist/report-contract.js.map +1 -0
  68. package/dist/report-html.d.ts +7 -0
  69. package/dist/report-html.js +706 -0
  70. package/dist/report-html.js.map +1 -0
  71. package/dist/report-sarif.d.ts +2 -0
  72. package/dist/report-sarif.js +90 -0
  73. package/dist/report-sarif.js.map +1 -0
  74. package/dist/report.d.ts +14 -0
  75. package/dist/report.js +310 -0
  76. package/dist/report.js.map +1 -0
  77. package/dist/risk.d.ts +19 -0
  78. package/dist/risk.js +1226 -0
  79. package/dist/risk.js.map +1 -0
  80. package/dist/runner.d.ts +8 -0
  81. package/dist/runner.js +113 -0
  82. package/dist/runner.js.map +1 -0
  83. package/dist/scan.d.ts +2 -0
  84. package/dist/scan.js +195 -0
  85. package/dist/scan.js.map +1 -0
  86. package/dist/schema.d.ts +12 -0
  87. package/dist/schema.js +30 -0
  88. package/dist/schema.js.map +1 -0
  89. package/dist/stack-coverage.d.ts +8 -0
  90. package/dist/stack-coverage.js +94 -0
  91. package/dist/stack-coverage.js.map +1 -0
  92. package/dist/types.d.ts +206 -0
  93. package/dist/types.js +2 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/verification.d.ts +11 -0
  96. package/dist/verification.js +108 -0
  97. package/dist/verification.js.map +1 -0
  98. package/docs/ANNOTATIONS.md +34 -0
  99. package/docs/ARCHITECTURE.md +79 -0
  100. package/docs/BASELINES.md +32 -0
  101. package/docs/CASE_STUDIES.md +106 -0
  102. package/docs/CODEOWNERS.md +23 -0
  103. package/docs/DASHBOARD.md +87 -0
  104. package/docs/EVIDENCE.md +55 -0
  105. package/docs/LAUNCH_PLAYBOOK.md +103 -0
  106. package/docs/MONOREPOS.md +74 -0
  107. package/docs/POLICY.md +98 -0
  108. package/docs/PROOF_PACKS.md +57 -0
  109. package/docs/PR_COMMENTS.md +56 -0
  110. package/docs/RELEASE.md +35 -0
  111. package/docs/ROADMAP.md +152 -0
  112. package/docs/RULE_CATALOG.md +90 -0
  113. package/docs/SARIF.md +74 -0
  114. package/docs/SCHEMAS.md +49 -0
  115. package/docs/SECURITY_POSTURE.md +32 -0
  116. package/docs/STACK_COVERAGE.md +20 -0
  117. package/docs/assets/patchdrill-demo.svg +21 -0
  118. package/docs/media/patchdrill-dashboard.png +0 -0
  119. package/docs/media/patchdrill-demo.gif +0 -0
  120. package/examples/case-studies/README.md +20 -0
  121. package/examples/demo/README.md +21 -0
  122. package/examples/demo/patchdrill-demo-summary.md +35 -0
  123. package/examples/demo/patchdrill-demo.html +623 -0
  124. package/examples/demo/patchdrill-demo.json +355 -0
  125. package/examples/demo/patchdrill-demo.md +120 -0
  126. package/examples/demo/patchdrill-demo.sarif +195 -0
  127. package/examples/report.md +128 -0
  128. package/examples/risky-agent-pr/README.md +15 -0
  129. package/examples/risky-agent-pr/patchdrill-demo-summary.md +41 -0
  130. package/examples/risky-agent-pr/patchdrill-demo.html +681 -0
  131. package/examples/risky-agent-pr/patchdrill-demo.json +483 -0
  132. package/examples/risky-agent-pr/patchdrill-demo.md +140 -0
  133. package/examples/risky-agent-pr/patchdrill-demo.sarif +398 -0
  134. package/fixtures/stacks/README.md +4 -0
  135. package/fixtures/stacks/android-gradle/fixture.json +33 -0
  136. package/fixtures/stacks/aspnet-core-service/fixture.json +36 -0
  137. package/fixtures/stacks/bazel-workspace/fixture.json +30 -0
  138. package/fixtures/stacks/buck2-workspace/fixture.json +30 -0
  139. package/fixtures/stacks/cargo-workspace/fixture.json +48 -0
  140. package/fixtures/stacks/django-app/fixture.json +25 -0
  141. package/fixtures/stacks/docker-compose/fixture.json +17 -0
  142. package/fixtures/stacks/dockerfile-service/fixture.json +17 -0
  143. package/fixtures/stacks/dotnet-service/fixture.json +36 -0
  144. package/fixtures/stacks/dotnet-solution-filter/fixture.json +62 -0
  145. package/fixtures/stacks/fastapi-app/fixture.json +29 -0
  146. package/fixtures/stacks/go-workspace/fixture.json +48 -0
  147. package/fixtures/stacks/java-gradle/fixture.json +29 -0
  148. package/fixtures/stacks/java-maven/fixture.json +32 -0
  149. package/fixtures/stacks/kubernetes-helm/fixture.json +25 -0
  150. package/fixtures/stacks/kubernetes-kustomize/fixture.json +21 -0
  151. package/fixtures/stacks/nested-go-workspace/fixture.json +51 -0
  152. package/fixtures/stacks/nextjs-app/fixture.json +34 -0
  153. package/fixtures/stacks/node-turbo-workspace/fixture.json +39 -0
  154. package/fixtures/stacks/pants-python/fixture.json +33 -0
  155. package/fixtures/stacks/php-composer/fixture.json +31 -0
  156. package/fixtures/stacks/python-service/fixture.json +21 -0
  157. package/fixtures/stacks/rails-app/fixture.json +25 -0
  158. package/fixtures/stacks/spring-boot-gradle/fixture.json +29 -0
  159. package/fixtures/stacks/spring-boot-maven/fixture.json +43 -0
  160. package/fixtures/stacks/swift-package/fixture.json +21 -0
  161. package/fixtures/stacks/terraform-module/fixture.json +17 -0
  162. package/fixtures/stacks/uv-python-service/fixture.json +47 -0
  163. package/fixtures/stacks/xcode-app/fixture.json +72 -0
  164. package/package.json +80 -0
  165. package/schemas/patchdrill-doctor.schema.json +171 -0
  166. package/schemas/patchdrill-evidence.schema.json +239 -0
  167. package/schemas/patchdrill-policy.schema.json +170 -0
  168. package/schemas/patchdrill-release-check.schema.json +78 -0
  169. package/schemas/patchdrill-report.schema.json +647 -0
package/dist/cli.js ADDED
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createDemoReport, demoScenarioNames, isDemoScenario } from "./demo.js";
6
+ import { formatEvidenceVerification, renderEvidenceManifest, verifyEvidenceManifest } from "./evidence.js";
7
+ import { gitRoot } from "./git.js";
8
+ import { isLocale, LOCALES, resolveLocale, t } from "./i18n.js";
9
+ import { isPolicyPackName, policyPackNames, writeGitHubWorkflow, writeOnboardingGuide, writePolicyFile } from "./init.js";
10
+ import { inspectDoctor, renderDoctor } from "./doctor.js";
11
+ import { checkReleaseReadiness, createReleaseReadinessReport, renderReleaseReadiness, summarizeReleaseReadiness } from "./release-readiness.js";
12
+ import { reportContractFailures } from "./report-contract.js";
13
+ import { renderGitHubAnnotations, renderHtml, renderMarkdown, renderSarif, renderSummaryMarkdown, shouldFail, verificationEvidencePhrase } from "./report.js";
14
+ import { isSchemaName, readSchema, schemaNames } from "./schema.js";
15
+ import { scan } from "./scan.js";
16
+ import { verificationSummary } from "./verification.js";
17
+ const severities = ["info", "low", "medium", "high", "critical"];
18
+ async function main() {
19
+ const parsed = parseArgs(process.argv.slice(2));
20
+ const command = parsed.command;
21
+ if (flagBoolean(parsed, "help") || command === "help") {
22
+ printHelp();
23
+ return;
24
+ }
25
+ if (flagBoolean(parsed, "version")) {
26
+ console.log(readVersion());
27
+ return;
28
+ }
29
+ if (command === "scan") {
30
+ await scanCommand(parsed);
31
+ return;
32
+ }
33
+ if (command === "dashboard") {
34
+ dashboardCommand(parsed);
35
+ return;
36
+ }
37
+ if (command === "demo") {
38
+ demoCommand(parsed);
39
+ return;
40
+ }
41
+ if (command === "doctor") {
42
+ doctorCommand(parsed);
43
+ return;
44
+ }
45
+ if (command === "evidence") {
46
+ evidenceCommand(parsed);
47
+ return;
48
+ }
49
+ if (command === "init") {
50
+ initCommand(parsed);
51
+ return;
52
+ }
53
+ if (command === "explain") {
54
+ explainCommand();
55
+ return;
56
+ }
57
+ if (command === "schema") {
58
+ schemaCommand(parsed);
59
+ return;
60
+ }
61
+ if (command === "release-check") {
62
+ releaseCheckCommand(parsed);
63
+ return;
64
+ }
65
+ if (command === "verify") {
66
+ verifyCommand(parsed);
67
+ return;
68
+ }
69
+ throw new Error(`Unknown command "${command}". Run patchdrill --help.`);
70
+ }
71
+ export async function scanCommand(parsed) {
72
+ const cliFailOnValue = flagString(parsed, "fail-on");
73
+ const cliMaxRiskValue = flagString(parsed, "max-risk");
74
+ const cliMaxRiskDeltaValue = flagString(parsed, "max-risk-delta");
75
+ const cliMaxOutputCharsValue = flagString(parsed, "max-output-chars");
76
+ const cliCommandTimeoutMsValue = flagString(parsed, "command-timeout-ms");
77
+ const base = flagString(parsed, "base");
78
+ const head = flagString(parsed, "head");
79
+ const configPath = flagString(parsed, "config");
80
+ const baselinePath = flagString(parsed, "baseline");
81
+ const evidencePath = flagString(parsed, "evidence");
82
+ const summaryMarkdownPath = flagString(parsed, "summary-markdown");
83
+ const markdownPath = flagString(parsed, "markdown");
84
+ const jsonPath = flagString(parsed, "json");
85
+ const sarifPath = flagString(parsed, "sarif");
86
+ const htmlPath = flagString(parsed, "html");
87
+ const run = flagBoolean(parsed, "run");
88
+ const runOptional = flagBoolean(parsed, "run-optional");
89
+ const cliFailOn = cliFailOnValue ? readSeverity(cliFailOnValue, "critical") : undefined;
90
+ const cliMaxRisk = cliMaxRiskValue ? readMaxRisk(cliMaxRiskValue) : undefined;
91
+ const cliMaxRiskDelta = cliMaxRiskDeltaValue ? readMaxRiskDelta(cliMaxRiskDeltaValue) : undefined;
92
+ const cliMaxOutputChars = cliMaxOutputCharsValue ? readPositiveInteger(cliMaxOutputCharsValue, "max output chars") : undefined;
93
+ const cliCommandTimeoutMs = cliCommandTimeoutMsValue ? readPositiveInteger(cliCommandTimeoutMsValue, "command timeout ms") : undefined;
94
+ if (runOptional && !run) {
95
+ throw new Error("--run-optional requires --run.");
96
+ }
97
+ if (evidencePath && !jsonPath) {
98
+ throw new Error("--evidence requires --json so the evidence manifest can verify the JSON report contract.");
99
+ }
100
+ if (cliMaxRiskDelta !== undefined && !baselinePath) {
101
+ throw new Error("--max-risk-delta requires --baseline so the risk delta gate has a previous report to compare against.");
102
+ }
103
+ const locale = readLocale(parsed, true);
104
+ const report = await scan({
105
+ cwd: process.cwd(),
106
+ ...(base ? { base } : {}),
107
+ ...(head ? { head } : {}),
108
+ ...(configPath ? { configPath } : {}),
109
+ ...(baselinePath ? { baselinePath } : {}),
110
+ ...(evidencePath ? { evidencePath } : {}),
111
+ locale,
112
+ run,
113
+ ...(runOptional ? { runOptional: true } : {}),
114
+ ...(cliFailOn ? { failOn: cliFailOn } : {}),
115
+ ...(summaryMarkdownPath ? { summaryMarkdownPath } : {}),
116
+ ...(markdownPath ? { markdownPath } : {}),
117
+ ...(jsonPath ? { jsonPath } : {}),
118
+ ...(sarifPath ? { sarifPath } : {}),
119
+ ...(htmlPath ? { htmlPath } : {}),
120
+ ...(cliMaxOutputChars !== undefined ? { maxOutputChars: cliMaxOutputChars } : {}),
121
+ ...(cliCommandTimeoutMs !== undefined ? { commandTimeoutMs: cliCommandTimeoutMs } : {})
122
+ });
123
+ const gateOptions = {
124
+ failOn: cliFailOn ?? report.policy?.failOn ?? "critical",
125
+ maxRisk: cliMaxRisk ?? report.policy?.maxRisk ?? 69,
126
+ ...(cliMaxRiskDelta !== undefined ? { maxRiskDelta: cliMaxRiskDelta } : {})
127
+ };
128
+ if (!flagBoolean(parsed, "quiet")) {
129
+ console.log(renderConsoleSummary(report, gateOptions, locale));
130
+ if (!markdownPath) {
131
+ console.log("");
132
+ console.log(renderMarkdown(report, locale));
133
+ }
134
+ }
135
+ if (flagBoolean(parsed, "github-annotations")) {
136
+ const annotations = renderGitHubAnnotations(report, locale).trimEnd();
137
+ if (annotations)
138
+ console.log(annotations);
139
+ }
140
+ if (shouldFail(report, gateOptions)) {
141
+ process.exitCode = 1;
142
+ }
143
+ }
144
+ export function dashboardCommand(parsed) {
145
+ const jsonPaths = flagStrings(parsed, "json");
146
+ if (jsonPaths.length === 0) {
147
+ throw new Error("dashboard requires --json <report.json>.");
148
+ }
149
+ const output = flagString(parsed, "output") ?? "patchdrill-dashboard.html";
150
+ const locale = readLocale(parsed);
151
+ const reports = jsonPaths.map((path) => readSavedReport(path).report);
152
+ const report = reports[reports.length - 1];
153
+ if (!report)
154
+ throw new Error("dashboard requires at least one JSON report.");
155
+ const resolved = resolve(process.cwd(), output);
156
+ mkdirSync(dirname(resolved), { recursive: true });
157
+ writeFileSync(resolved, renderHtml(report, { ...(reports.length > 1 ? { history: reports } : {}), locale }), "utf8");
158
+ console.log(`Wrote ${output}`);
159
+ }
160
+ export function demoCommand(parsed) {
161
+ const scenario = readDemoScenario(flagString(parsed, "scenario"));
162
+ const report = createDemoReport(scenario);
163
+ const output = flagString(parsed, "output");
164
+ const locale = readLocale(parsed);
165
+ if (!output) {
166
+ console.log(renderMarkdown(report, locale).trimEnd());
167
+ return;
168
+ }
169
+ const outputDir = resolve(process.cwd(), output);
170
+ mkdirSync(outputDir, { recursive: true });
171
+ const files = {
172
+ summaryMarkdown: join(outputDir, "patchdrill-demo-summary.md"),
173
+ markdown: join(outputDir, "patchdrill-demo.md"),
174
+ json: join(outputDir, "patchdrill-demo.json"),
175
+ sarif: join(outputDir, "patchdrill-demo.sarif"),
176
+ html: join(outputDir, "patchdrill-demo.html")
177
+ };
178
+ writeFileSync(files.summaryMarkdown, renderSummaryMarkdown(report, locale), "utf8");
179
+ writeFileSync(files.markdown, renderMarkdown(report, locale), "utf8");
180
+ writeFileSync(files.json, `${JSON.stringify(report, null, 2)}\n`, "utf8");
181
+ writeFileSync(files.sarif, renderSarif(report), "utf8");
182
+ writeFileSync(files.html, renderHtml(report, { locale }), "utf8");
183
+ console.log(`Wrote demo artifacts to ${output}`);
184
+ console.log(`- ${files.summaryMarkdown}`);
185
+ console.log(`- ${files.markdown}`);
186
+ console.log(`- ${files.json}`);
187
+ console.log(`- ${files.sarif}`);
188
+ console.log(`- ${files.html}`);
189
+ }
190
+ export function doctorCommand(parsed = { command: "doctor", flags: {}, positionals: [] }) {
191
+ const root = gitRoot(process.cwd());
192
+ const report = inspectDoctor(root);
193
+ if (readOutputFormat(parsed) === "json") {
194
+ console.log(JSON.stringify(report, null, 2));
195
+ return;
196
+ }
197
+ console.log(renderDoctor(report).trimEnd());
198
+ }
199
+ export function evidenceCommand(parsed) {
200
+ const reportPath = flagString(parsed, "json");
201
+ const evidencePath = flagString(parsed, "evidence") ?? flagString(parsed, "output");
202
+ if (!reportPath) {
203
+ throw new Error("evidence requires --json <patchdrill-report.json>.");
204
+ }
205
+ if (!evidencePath) {
206
+ throw new Error("evidence requires --evidence <patchdrill-evidence.json>.");
207
+ }
208
+ const { report, contents: reportJson } = readSavedReport(reportPath);
209
+ const artifacts = [
210
+ ...optionalEvidenceArtifact("summary-markdown", flagString(parsed, "summary-markdown")),
211
+ ...optionalEvidenceArtifact("markdown", flagString(parsed, "markdown")),
212
+ { kind: "json", path: reportPath, contents: reportJson },
213
+ ...optionalEvidenceArtifact("sarif", flagString(parsed, "sarif")),
214
+ ...optionalEvidenceArtifact("html", flagString(parsed, "html"))
215
+ ];
216
+ const resolved = resolve(process.cwd(), evidencePath);
217
+ mkdirSync(dirname(resolved), { recursive: true });
218
+ writeFileSync(resolved, renderEvidenceManifest(report, artifacts, report.root || process.cwd(), reportJson), "utf8");
219
+ console.log(`Wrote ${evidencePath}`);
220
+ }
221
+ function readSavedReport(path) {
222
+ const contents = readFileSync(path, "utf8");
223
+ let parsed;
224
+ try {
225
+ parsed = JSON.parse(contents);
226
+ }
227
+ catch (error) {
228
+ const detail = error instanceof Error ? error.message : String(error);
229
+ throw new Error(`Could not parse JSON report at ${path}: ${detail}`, { cause: error });
230
+ }
231
+ if (!isRecord(parsed)) {
232
+ throw new Error(`JSON report at ${path} must be an object; got ${describeJsonValue(parsed)}.`);
233
+ }
234
+ const failures = reportContractFailures(parsed);
235
+ if (failures.length > 0) {
236
+ throw new Error(`JSON report contract failed for ${path}: ${failures.join("; ")}`);
237
+ }
238
+ return { report: parsed, contents };
239
+ }
240
+ function isRecord(value) {
241
+ return typeof value === "object" && value !== null && !Array.isArray(value);
242
+ }
243
+ function describeJsonValue(value) {
244
+ if (Array.isArray(value))
245
+ return "array";
246
+ if (value === null)
247
+ return "null";
248
+ return typeof value;
249
+ }
250
+ function initCommand(parsed) {
251
+ const root = gitRoot(process.cwd());
252
+ const policyPack = readPolicyPack(flagString(parsed, "policy-pack"));
253
+ const force = flagBoolean(parsed, "force");
254
+ const policyRequested = flagBoolean(parsed, "policy") || parsed.flags["policy-pack"] !== undefined;
255
+ const path = writeGitHubWorkflow(root, force);
256
+ console.log(`Created ${path}`);
257
+ if (policyRequested) {
258
+ const policyPath = writePolicyFile(root, force, policyPack);
259
+ console.log(`Created ${policyPath}`);
260
+ }
261
+ const guidePath = writeOnboardingGuide(root, force, { policyPack, policyCreated: policyRequested });
262
+ console.log(`Created ${guidePath}`);
263
+ }
264
+ export function explainCommand() {
265
+ console.log(renderExplainText());
266
+ }
267
+ export function renderExplainText() {
268
+ return `PatchDrill is the deterministic proof layer between code review and CI.
269
+
270
+ PatchDrill is not an AI PR reviewer.
271
+
272
+ AI reviewers answer: "Does this diff look right?"
273
+ PatchDrill answers: "What deterministic proof should exist before merge?"
274
+
275
+ What PatchDrill does:
276
+ 1. Reads changed files and added lines from git.
277
+ 2. Detects repository ecosystems, workspaces, owners, dependencies, package scripts, and workflow trust boundaries.
278
+ 3. Infers required and optional verification commands from the patch.
279
+ 4. Scores risk with human-readable findings where every score increase maps to a report row.
280
+ 5. Emits a Proof Pack: Markdown, JSON, SARIF, static HTML, PR-comment summaries, and verifiable evidence manifests.
281
+
282
+ What makes it different:
283
+ - No model call is required; the same diff produces the same plan and findings.
284
+ - scan does not mutate the repository or run commands unless --run is set.
285
+ - --run executes inferred required checks; --run-optional explicitly opts into optional checks.
286
+ - Proof Pack artifacts are meant for CI gates, bots, auditors, reviewers, and model-assisted review.
287
+ - You can run PatchDrill before handing the report to a human or a frontier model.
288
+
289
+ Try it without a repository:
290
+ patchdrill demo --scenario risky-agent-pr --output patchdrill-risky-demo
291
+
292
+ Typical CI/local use:
293
+ patchdrill scan --base origin/main --run --evidence patchdrill-evidence.json --summary-markdown patchdrill-summary.md --markdown patchdrill-report.md --json patchdrill-report.json --sarif patchdrill.sarif --html patchdrill-dashboard.html --fail-on high --max-risk 69`;
294
+ }
295
+ function schemaCommand(parsed) {
296
+ const requested = parsed.positionals[0];
297
+ if (flagBoolean(parsed, "list") || requested === undefined) {
298
+ console.log(schemaNames.join("\n"));
299
+ return;
300
+ }
301
+ if (!isSchemaName(requested)) {
302
+ throw new Error(`Unknown schema "${requested}". Expected one of ${schemaNames.join(", ")}.`);
303
+ }
304
+ const schema = readSchema(requested);
305
+ const output = flagString(parsed, "output");
306
+ if (output) {
307
+ writeFileSync(output, schema, "utf8");
308
+ console.log(`Wrote ${output}`);
309
+ return;
310
+ }
311
+ console.log(schema.trimEnd());
312
+ }
313
+ function verifyCommand(parsed) {
314
+ const evidencePath = flagString(parsed, "evidence") ?? parsed.positionals[0];
315
+ if (!evidencePath) {
316
+ throw new Error("verify requires --evidence <patchdrill-evidence.json>.");
317
+ }
318
+ const result = verifyEvidenceManifest(evidencePath, process.cwd());
319
+ if (!flagBoolean(parsed, "quiet")) {
320
+ console.log(formatEvidenceVerification(result));
321
+ }
322
+ if (!result.ok) {
323
+ process.exitCode = 1;
324
+ }
325
+ }
326
+ export function releaseCheckCommand(parsed = { command: "release-check", flags: {}, positionals: [] }) {
327
+ const root = gitRoot(process.cwd());
328
+ const checks = checkReleaseReadiness(root);
329
+ const summary = summarizeReleaseReadiness(checks);
330
+ if (readOutputFormat(parsed) === "json") {
331
+ console.log(JSON.stringify(createReleaseReadinessReport(checks), null, 2));
332
+ }
333
+ else {
334
+ console.log(renderReleaseReadiness(checks).trimEnd());
335
+ }
336
+ if (!summary.ok) {
337
+ process.exitCode = 1;
338
+ }
339
+ }
340
+ export function renderConsoleSummary(report, gateOptions, locale = "en") {
341
+ const tr = (text) => t(locale, text);
342
+ const required = report.commandPlan.filter((command) => command.required);
343
+ const optional = report.commandPlan.filter((command) => !command.required);
344
+ const verification = verificationSummary(report);
345
+ const gateStatus = shouldFail(report, gateOptions) ? "FAIL" : "PASS";
346
+ const lines = [
347
+ `${tr("PatchDrill Gate")} ${tr(gateStatus)} - ${tr("assessment")} ${tr(report.summary.status.toUpperCase())}, ${tr("risk")} ${report.summary.riskScore}/100, ${tr("confidence")} ${report.summary.confidenceScore}/100`,
348
+ `${tr("Gate policy")}: ${tr("fail-on")} ${gateOptions.failOn}, ${tr("max-risk")} ${gateOptions.maxRisk}${gateOptions.maxRiskDelta !== undefined ? `, ${tr("max-risk-delta")} ${gateOptions.maxRiskDelta}` : ""}`,
349
+ `${tr("Changed files")}: ${report.summary.changedFileCount}, +${report.summary.additions}/-${report.summary.deletions}`,
350
+ `${tr("Required commands")}: ${required.length}, ${tr("optional commands")}: ${optional.length}`,
351
+ `${tr("Verification evidence")}: ${verificationEvidencePhrase(verification, locale)}`,
352
+ `${tr("Added lines inspected")}: ${report.addedLines}`
353
+ ];
354
+ if (report.findings.length > 0) {
355
+ lines.push(`${tr("Top findings")}:`);
356
+ for (const finding of report.findings.slice(0, 5)) {
357
+ lines.push(`- [${tr(finding.severity)}] ${tr(finding.title)}${finding.file ? ` (${finding.file})` : ""}`);
358
+ }
359
+ }
360
+ if (verification.missingRequired > 0) {
361
+ lines.push(tr("Run with --run to execute required verification commands. Add --run-optional to include optional checks."));
362
+ }
363
+ return lines.join("\n");
364
+ }
365
+ export function parseArgs(args) {
366
+ const flags = {};
367
+ const positionals = [];
368
+ let command = "scan";
369
+ for (let index = 0; index < args.length; index += 1) {
370
+ const arg = args[index];
371
+ if (!arg)
372
+ continue;
373
+ if (arg.startsWith("--")) {
374
+ const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
375
+ const key = rawKey ?? "";
376
+ if (!key)
377
+ continue;
378
+ if (!isKnownFlag(key)) {
379
+ throw new Error(`Unknown flag "--${key}". Run patchdrill --help.`);
380
+ }
381
+ if (inlineValue !== undefined) {
382
+ addFlag(flags, key, isBooleanFlag(key) ? readBooleanFlag(inlineValue, key) : inlineValue);
383
+ }
384
+ else if (takesValue(key)) {
385
+ const next = args[index + 1];
386
+ if (next === undefined || next.startsWith("-")) {
387
+ throw new Error(`Flag "--${key}" requires a value. Run patchdrill --help.`);
388
+ }
389
+ addFlag(flags, key, next);
390
+ index += 1;
391
+ }
392
+ else {
393
+ const next = args[index + 1];
394
+ if (next && !next.startsWith("-") && isBooleanFlag(key) && isBooleanLiteral(next)) {
395
+ addFlag(flags, key, readBooleanFlag(next, key));
396
+ index += 1;
397
+ }
398
+ else {
399
+ addFlag(flags, key, true);
400
+ }
401
+ }
402
+ continue;
403
+ }
404
+ if (!arg.startsWith("-") &&
405
+ command === "scan" &&
406
+ ["scan", "dashboard", "demo", "doctor", "evidence", "init", "explain", "release-check", "schema", "verify", "help"].includes(arg)) {
407
+ command = arg;
408
+ continue;
409
+ }
410
+ positionals.push(arg);
411
+ }
412
+ return { command, flags, positionals };
413
+ }
414
+ function addFlag(flags, key, value) {
415
+ const existing = flags[key];
416
+ if (existing === undefined) {
417
+ flags[key] = value;
418
+ return;
419
+ }
420
+ const next = typeof value === "string" ? value : String(value);
421
+ if (Array.isArray(existing)) {
422
+ existing.push(next);
423
+ return;
424
+ }
425
+ flags[key] = [typeof existing === "string" ? existing : String(existing), next];
426
+ }
427
+ function flagString(parsed, key) {
428
+ const value = parsed.flags[key];
429
+ if (typeof value === "string")
430
+ return value;
431
+ if (Array.isArray(value))
432
+ return value[value.length - 1];
433
+ return undefined;
434
+ }
435
+ function flagStrings(parsed, key) {
436
+ const value = parsed.flags[key];
437
+ if (typeof value === "string")
438
+ return [value];
439
+ if (Array.isArray(value))
440
+ return value;
441
+ return [];
442
+ }
443
+ function flagBoolean(parsed, key) {
444
+ const value = parsed.flags[key];
445
+ if (value === undefined)
446
+ return false;
447
+ if (typeof value === "boolean")
448
+ return value;
449
+ if (typeof value === "string")
450
+ return readBooleanFlag(value, key);
451
+ const last = value.at(-1);
452
+ return last === undefined ? false : readBooleanFlag(last, key);
453
+ }
454
+ function takesValue(flag) {
455
+ return [
456
+ "base",
457
+ "head",
458
+ "config",
459
+ "baseline",
460
+ "evidence",
461
+ "summary-markdown",
462
+ "markdown",
463
+ "json",
464
+ "sarif",
465
+ "html",
466
+ "fail-on",
467
+ "max-risk",
468
+ "max-risk-delta",
469
+ "max-output-chars",
470
+ "command-timeout-ms",
471
+ "policy-pack",
472
+ "scenario",
473
+ "format",
474
+ "output",
475
+ "locale"
476
+ ].includes(flag);
477
+ }
478
+ function isBooleanFlag(flag) {
479
+ return ["help", "version", "quiet", "run", "run-optional", "github-annotations", "force", "policy", "list"].includes(flag);
480
+ }
481
+ function isKnownFlag(flag) {
482
+ return takesValue(flag) || isBooleanFlag(flag);
483
+ }
484
+ function isBooleanLiteral(value) {
485
+ return /^(true|false|1|0|yes|no|on|off)$/i.test(value);
486
+ }
487
+ function readBooleanFlag(value, flag) {
488
+ if (/^(true|1|yes|on)$/i.test(value))
489
+ return true;
490
+ if (/^(false|0|no|off)$/i.test(value))
491
+ return false;
492
+ throw new Error(`Invalid boolean value "${value}" for --${flag}. Expected true or false.`);
493
+ }
494
+ function readSeverity(value, fallback) {
495
+ if (typeof value !== "string")
496
+ return fallback;
497
+ if (!severities.includes(value)) {
498
+ throw new Error(`Invalid severity "${value}". Expected one of ${severities.join(", ")}.`);
499
+ }
500
+ return value;
501
+ }
502
+ function readMaxRisk(value) {
503
+ if (!/^\d+$/.test(value)) {
504
+ throw new Error(`Invalid max risk "${value}". Expected an integer from 0 to 100.`);
505
+ }
506
+ const parsed = Number.parseInt(value, 10);
507
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
508
+ throw new Error(`Invalid max risk "${value}". Expected an integer from 0 to 100.`);
509
+ }
510
+ return parsed;
511
+ }
512
+ function readMaxRiskDelta(value) {
513
+ if (!/^\d+$/.test(value)) {
514
+ throw new Error(`Invalid max risk delta "${value}". Expected an integer from 0 to 100.`);
515
+ }
516
+ const parsed = Number.parseInt(value, 10);
517
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
518
+ throw new Error(`Invalid max risk delta "${value}". Expected an integer from 0 to 100.`);
519
+ }
520
+ return parsed;
521
+ }
522
+ function readPositiveInteger(value, label) {
523
+ if (!/^\d+$/.test(value)) {
524
+ throw new Error(`Invalid ${label} "${value}". Expected a positive integer.`);
525
+ }
526
+ const parsed = Number.parseInt(value, 10);
527
+ if (!Number.isFinite(parsed) || parsed <= 0) {
528
+ throw new Error(`Invalid ${label} "${value}". Expected a positive integer.`);
529
+ }
530
+ return parsed;
531
+ }
532
+ function readLocale(parsed, detectEnv = false) {
533
+ const explicit = flagString(parsed, "locale");
534
+ if (explicit !== undefined && !isLocale(explicit)) {
535
+ throw new Error(`Invalid locale "${explicit}". Expected one of ${LOCALES.join(", ")}.`);
536
+ }
537
+ // Only `scan` auto-detects the system locale (it analyzes the user's own repo).
538
+ // `demo`/`dashboard` render fixed or saved artifacts and stay English unless an
539
+ // explicit --locale is given, so sample output and fixtures are deterministic.
540
+ return resolveLocale(detectEnv ? { explicit, env: process.env } : { explicit });
541
+ }
542
+ function readPolicyPack(value) {
543
+ if (value === undefined || value === false)
544
+ return "default";
545
+ if (typeof value !== "string") {
546
+ throw new Error(`Invalid policy pack. Expected one of ${policyPackNames.join(", ")}.`);
547
+ }
548
+ if (!isPolicyPackName(value)) {
549
+ throw new Error(`Invalid policy pack "${value}". Expected one of ${policyPackNames.join(", ")}.`);
550
+ }
551
+ return value;
552
+ }
553
+ function readOutputFormat(parsed) {
554
+ const format = flagString(parsed, "format") ?? "text";
555
+ if (format !== "text" && format !== "json") {
556
+ throw new Error(`Invalid output format "${format}". Expected text or json.`);
557
+ }
558
+ return format;
559
+ }
560
+ function optionalEvidenceArtifact(kind, path) {
561
+ return path ? [{ kind, path, contents: readFileSync(path, "utf8") }] : [];
562
+ }
563
+ function readDemoScenario(value) {
564
+ if (value === undefined)
565
+ return "review-ready";
566
+ if (!isDemoScenario(value)) {
567
+ throw new Error(`Invalid demo scenario "${value}". Expected one of ${demoScenarioNames.join(", ")}.`);
568
+ }
569
+ return value;
570
+ }
571
+ function readVersion() {
572
+ const packagePath = join(new URL("..", import.meta.url).pathname, "package.json");
573
+ if (!existsSync(packagePath))
574
+ return "0.1.0";
575
+ try {
576
+ const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
577
+ return parsed.version ?? "0.1.0";
578
+ }
579
+ catch {
580
+ return "0.1.0";
581
+ }
582
+ }
583
+ function printHelp() {
584
+ console.log(`PatchDrill - deterministic proof layer for AI-era patches
585
+
586
+ Usage:
587
+ patchdrill scan [options]
588
+ patchdrill dashboard --json <report.json> [--json <report.json>...] [--output <dashboard.html>]
589
+ patchdrill demo [--scenario <name>] [--output <directory>]
590
+ patchdrill doctor [--format text|json]
591
+ patchdrill evidence --json <report.json> --evidence <evidence.json> [artifact options]
592
+ patchdrill init [--force] [--policy] [--policy-pack <name>]
593
+ patchdrill explain
594
+ patchdrill release-check [--format text|json]
595
+ patchdrill schema [policy|report|evidence|doctor|release-check] [--output <path>]
596
+ patchdrill verify --evidence <patchdrill-evidence.json>
597
+
598
+ First run:
599
+ patchdrill explain
600
+ patchdrill demo --scenario risky-agent-pr --output patchdrill-risky-demo
601
+ patchdrill doctor
602
+ patchdrill scan --base origin/main
603
+
604
+ Options:
605
+ --base <ref> Compare against a base ref, for example origin/main
606
+ --head <ref> Head ref when using --base, default HEAD
607
+ --config <path> Read policy from .patchdrill.yml/json or a specific path
608
+ --baseline <path> Compare against a previous PatchDrill JSON report
609
+ --evidence <path> Write a Proof Pack evidence manifest during scan/evidence, or select one for verify. scan --evidence requires --json
610
+ --run Execute required inferred verification commands
611
+ --run-optional With --run, also execute optional verification commands
612
+ --github-annotations
613
+ Emit GitHub Actions log annotations for findings
614
+ --summary-markdown <path>
615
+ Write a compact Markdown summary for PR comments or step summaries
616
+ --markdown <path> Write a Markdown report
617
+ --json <path> Write a JSON report
618
+ --sarif <path> Write a SARIF report for GitHub code scanning
619
+ --html <path> Write a self-contained static HTML dashboard
620
+ --fail-on <level> Fail when findings meet severity: info, low, medium, high, critical
621
+ --max-risk <score> Fail when risk score is above 0-100 threshold, default 69
622
+ --max-risk-delta <score>
623
+ Fail when baseline risk increase is above this threshold
624
+ --max-output-chars <n>
625
+ Keep the last n characters of each command output stream, default 20000
626
+ --command-timeout-ms <n>
627
+ Stop each verification command after n milliseconds
628
+ --quiet Only use exit code, no console report
629
+ --policy Create .patchdrill.yml when used with init
630
+ --policy-pack <name>
631
+ Starter policy pack for init --policy: ${policyPackNames.join(", ")}
632
+ --scenario <name> Demo scenario: ${demoScenarioNames.join(", ")}
633
+ --format <format> Output format for doctor and release-check: text, json
634
+ --locale <lang> Language for human-facing reports: ${LOCALES.join(", ")} (default: system locale, else en)
635
+ --list List schemas when used with schema
636
+ --output <path> Write a schema/dashboard file or demo artifact directory
637
+ --version Print version
638
+ --help Print help
639
+
640
+ Boolean flags accept explicit values: --run=false, --quiet=true, --github-annotations=off.
641
+ `);
642
+ }
643
+ if (isCliEntrypoint()) {
644
+ main().catch((error) => {
645
+ const message = error instanceof Error ? error.message : String(error);
646
+ console.error(`patchdrill: ${message}`);
647
+ process.exitCode = 1;
648
+ });
649
+ }
650
+ function isCliEntrypoint() {
651
+ const entry = process.argv[1];
652
+ if (!entry)
653
+ return false;
654
+ const modulePath = fileURLToPath(import.meta.url);
655
+ try {
656
+ return realpathSync(entry) === realpathSync(modulePath);
657
+ }
658
+ catch {
659
+ return resolve(entry) === modulePath;
660
+ }
661
+ }
662
+ //# sourceMappingURL=cli.js.map