@stsepelin/checktrail 0.1.0-alpha.1

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 (385) hide show
  1. package/CONTRIBUTING.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +355 -0
  4. package/SECURITY.md +46 -0
  5. package/dist/src/actionlint-evidence.d.ts +2 -0
  6. package/dist/src/actionlint-evidence.js +166 -0
  7. package/dist/src/actionlint-inputs.d.ts +5 -0
  8. package/dist/src/actionlint-inputs.js +123 -0
  9. package/dist/src/actionlint-runner.d.ts +1 -0
  10. package/dist/src/actionlint-runner.js +122 -0
  11. package/dist/src/actionlint.d.ts +19 -0
  12. package/dist/src/actionlint.js +80 -0
  13. package/dist/src/adapters.d.ts +49 -0
  14. package/dist/src/adapters.js +430 -0
  15. package/dist/src/architecture.d.ts +107 -0
  16. package/dist/src/architecture.js +230 -0
  17. package/dist/src/clang-evidence.d.ts +2 -0
  18. package/dist/src/clang-evidence.js +194 -0
  19. package/dist/src/clang-protocol.d.ts +3 -0
  20. package/dist/src/clang-protocol.js +25 -0
  21. package/dist/src/clang-runner.d.ts +1 -0
  22. package/dist/src/clang-runner.js +159 -0
  23. package/dist/src/clang.d.ts +44 -0
  24. package/dist/src/clang.js +197 -0
  25. package/dist/src/cli.d.ts +2 -0
  26. package/dist/src/cli.js +338 -0
  27. package/dist/src/config.d.ts +33 -0
  28. package/dist/src/config.js +140 -0
  29. package/dist/src/contract-schema.d.ts +85 -0
  30. package/dist/src/contract-schema.js +56 -0
  31. package/dist/src/contract-worker.d.ts +1 -0
  32. package/dist/src/contract-worker.js +123 -0
  33. package/dist/src/contracts.d.ts +22 -0
  34. package/dist/src/contracts.js +123 -0
  35. package/dist/src/django-evidence.d.ts +2 -0
  36. package/dist/src/django-evidence.js +100 -0
  37. package/dist/src/django-runner.d.ts +1 -0
  38. package/dist/src/django-runner.js +80 -0
  39. package/dist/src/django.d.ts +9 -0
  40. package/dist/src/django.js +50 -0
  41. package/dist/src/dotnet-compiler.d.ts +1 -0
  42. package/dist/src/dotnet-compiler.js +87 -0
  43. package/dist/src/dotnet-evidence.d.ts +2 -0
  44. package/dist/src/dotnet-evidence.js +127 -0
  45. package/dist/src/dotnet-runner.d.ts +1 -0
  46. package/dist/src/dotnet-runner.js +167 -0
  47. package/dist/src/dotnet.d.ts +65 -0
  48. package/dist/src/dotnet.js +120 -0
  49. package/dist/src/engine.d.ts +19 -0
  50. package/dist/src/engine.js +239 -0
  51. package/dist/src/environment.d.ts +6 -0
  52. package/dist/src/environment.js +36 -0
  53. package/dist/src/eslint-evidence.d.ts +2 -0
  54. package/dist/src/eslint-evidence.js +98 -0
  55. package/dist/src/eslint-runner.d.ts +1 -0
  56. package/dist/src/eslint-runner.js +61 -0
  57. package/dist/src/eslint.d.ts +2 -0
  58. package/dist/src/eslint.js +40 -0
  59. package/dist/src/evidence.d.ts +2 -0
  60. package/dist/src/evidence.js +313 -0
  61. package/dist/src/external-adapter.d.ts +164 -0
  62. package/dist/src/external-adapter.js +265 -0
  63. package/dist/src/external-evidence.d.ts +2 -0
  64. package/dist/src/external-evidence.js +127 -0
  65. package/dist/src/external-runner.d.ts +1 -0
  66. package/dist/src/external-runner.js +122 -0
  67. package/dist/src/fastapi-evidence.d.ts +2 -0
  68. package/dist/src/fastapi-evidence.js +126 -0
  69. package/dist/src/fastapi-runner.d.ts +1 -0
  70. package/dist/src/fastapi-runner.js +84 -0
  71. package/dist/src/fastapi.d.ts +10 -0
  72. package/dist/src/fastapi.js +52 -0
  73. package/dist/src/fetch-pack.d.ts +16 -0
  74. package/dist/src/fetch-pack.js +154 -0
  75. package/dist/src/finding-policy-schema.d.ts +146 -0
  76. package/dist/src/finding-policy-schema.js +70 -0
  77. package/dist/src/finding-policy.d.ts +62 -0
  78. package/dist/src/finding-policy.js +206 -0
  79. package/dist/src/git-selection.d.ts +3 -0
  80. package/dist/src/git-selection.js +229 -0
  81. package/dist/src/go-directives.d.ts +1 -0
  82. package/dist/src/go-directives.js +32 -0
  83. package/dist/src/go-scope.d.ts +11 -0
  84. package/dist/src/go-scope.js +122 -0
  85. package/dist/src/golangci-evidence.d.ts +2 -0
  86. package/dist/src/golangci-evidence.js +92 -0
  87. package/dist/src/golangci-runner.d.ts +1 -0
  88. package/dist/src/golangci-runner.js +124 -0
  89. package/dist/src/golangci.d.ts +2 -0
  90. package/dist/src/golangci.js +35 -0
  91. package/dist/src/guidance.d.ts +98 -0
  92. package/dist/src/guidance.js +247 -0
  93. package/dist/src/index.d.ts +31 -0
  94. package/dist/src/index.js +16 -0
  95. package/dist/src/inventory.d.ts +4 -0
  96. package/dist/src/inventory.js +94 -0
  97. package/dist/src/java-compiler.d.ts +1 -0
  98. package/dist/src/java-compiler.js +110 -0
  99. package/dist/src/java-evidence.d.ts +2 -0
  100. package/dist/src/java-evidence.js +114 -0
  101. package/dist/src/java-runner.d.ts +1 -0
  102. package/dist/src/java-runner.js +79 -0
  103. package/dist/src/java.d.ts +25 -0
  104. package/dist/src/java.js +102 -0
  105. package/dist/src/jest-evidence.d.ts +2 -0
  106. package/dist/src/jest-evidence.js +117 -0
  107. package/dist/src/jest-runner.d.ts +1 -0
  108. package/dist/src/jest-runner.js +46 -0
  109. package/dist/src/jest.d.ts +2 -0
  110. package/dist/src/jest.js +36 -0
  111. package/dist/src/junit.d.ts +18 -0
  112. package/dist/src/junit.js +157 -0
  113. package/dist/src/laravel-evidence.d.ts +2 -0
  114. package/dist/src/laravel-evidence.js +175 -0
  115. package/dist/src/laravel-runner.d.ts +1 -0
  116. package/dist/src/laravel-runner.js +168 -0
  117. package/dist/src/laravel.d.ts +8 -0
  118. package/dist/src/laravel.js +50 -0
  119. package/dist/src/local-tool.d.ts +1 -0
  120. package/dist/src/local-tool.js +19 -0
  121. package/dist/src/mcp.d.ts +14 -0
  122. package/dist/src/mcp.js +362 -0
  123. package/dist/src/mutation.d.ts +118 -0
  124. package/dist/src/mutation.js +370 -0
  125. package/dist/src/mypy-evidence.d.ts +2 -0
  126. package/dist/src/mypy-evidence.js +63 -0
  127. package/dist/src/mypy.d.ts +2 -0
  128. package/dist/src/mypy.js +59 -0
  129. package/dist/src/node-reporter.d.ts +1 -0
  130. package/dist/src/node-reporter.js +28 -0
  131. package/dist/src/nuxt-evidence.d.ts +2 -0
  132. package/dist/src/nuxt-evidence.js +160 -0
  133. package/dist/src/nuxt-protocol.d.ts +52 -0
  134. package/dist/src/nuxt-protocol.js +29 -0
  135. package/dist/src/nuxt-runner.d.ts +1 -0
  136. package/dist/src/nuxt-runner.js +165 -0
  137. package/dist/src/nuxt.d.ts +24 -0
  138. package/dist/src/nuxt.js +105 -0
  139. package/dist/src/output.d.ts +3 -0
  140. package/dist/src/output.js +48 -0
  141. package/dist/src/phpstan-evidence.d.ts +2 -0
  142. package/dist/src/phpstan-evidence.js +81 -0
  143. package/dist/src/phpstan.d.ts +2 -0
  144. package/dist/src/phpstan.js +51 -0
  145. package/dist/src/phpunit-evidence.d.ts +2 -0
  146. package/dist/src/phpunit-evidence.js +53 -0
  147. package/dist/src/phpunit.d.ts +2 -0
  148. package/dist/src/phpunit.js +58 -0
  149. package/dist/src/pint-evidence.d.ts +2 -0
  150. package/dist/src/pint-evidence.js +79 -0
  151. package/dist/src/pint-runner.d.ts +1 -0
  152. package/dist/src/pint-runner.js +53 -0
  153. package/dist/src/pint.d.ts +2 -0
  154. package/dist/src/pint.js +37 -0
  155. package/dist/src/playwright-evidence.d.ts +2 -0
  156. package/dist/src/playwright-evidence.js +127 -0
  157. package/dist/src/playwright-reporter.d.ts +10 -0
  158. package/dist/src/playwright-reporter.js +37 -0
  159. package/dist/src/playwright-runner.d.ts +1 -0
  160. package/dist/src/playwright-runner.js +61 -0
  161. package/dist/src/playwright.d.ts +2 -0
  162. package/dist/src/playwright.js +35 -0
  163. package/dist/src/policy-pack.d.ts +21 -0
  164. package/dist/src/policy-pack.js +41 -0
  165. package/dist/src/pytest-evidence.d.ts +2 -0
  166. package/dist/src/pytest-evidence.js +116 -0
  167. package/dist/src/pytest-runner.d.ts +1 -0
  168. package/dist/src/pytest-runner.js +48 -0
  169. package/dist/src/pytest.d.ts +2 -0
  170. package/dist/src/pytest.js +25 -0
  171. package/dist/src/report-validation.d.ts +129 -0
  172. package/dist/src/report-validation.js +27 -0
  173. package/dist/src/review.d.ts +270 -0
  174. package/dist/src/review.js +349 -0
  175. package/dist/src/ruby.d.ts +2 -0
  176. package/dist/src/ruby.js +25 -0
  177. package/dist/src/ruff-evidence.d.ts +2 -0
  178. package/dist/src/ruff-evidence.js +99 -0
  179. package/dist/src/ruff.d.ts +2 -0
  180. package/dist/src/ruff.js +40 -0
  181. package/dist/src/runner.d.ts +8 -0
  182. package/dist/src/runner.js +117 -0
  183. package/dist/src/runtime-inventory.d.ts +112 -0
  184. package/dist/src/runtime-inventory.js +200 -0
  185. package/dist/src/rust-dep-info.d.ts +1 -0
  186. package/dist/src/rust-dep-info.js +46 -0
  187. package/dist/src/rust-evidence.d.ts +2 -0
  188. package/dist/src/rust-evidence.js +157 -0
  189. package/dist/src/rust-runner.d.ts +1 -0
  190. package/dist/src/rust-runner.js +161 -0
  191. package/dist/src/rust.d.ts +7 -0
  192. package/dist/src/rust.js +39 -0
  193. package/dist/src/sarif.d.ts +86 -0
  194. package/dist/src/sarif.js +113 -0
  195. package/dist/src/schemas.d.ts +393 -0
  196. package/dist/src/schemas.js +222 -0
  197. package/dist/src/staticcheck-evidence.d.ts +2 -0
  198. package/dist/src/staticcheck-evidence.js +75 -0
  199. package/dist/src/swift.d.ts +2 -0
  200. package/dist/src/swift.js +20 -0
  201. package/dist/src/task-store.d.ts +37 -0
  202. package/dist/src/task-store.js +349 -0
  203. package/dist/src/tool-versions.d.ts +3 -0
  204. package/dist/src/tool-versions.js +260 -0
  205. package/dist/src/types.d.ts +157 -0
  206. package/dist/src/types.js +37 -0
  207. package/dist/src/typescript-build-evidence.d.ts +2 -0
  208. package/dist/src/typescript-build-evidence.js +86 -0
  209. package/dist/src/typescript-build-runner.d.ts +1 -0
  210. package/dist/src/typescript-build-runner.js +132 -0
  211. package/dist/src/typescript-build.d.ts +2 -0
  212. package/dist/src/typescript-build.js +41 -0
  213. package/dist/src/typescript.d.ts +2 -0
  214. package/dist/src/typescript.js +54 -0
  215. package/dist/src/validation-task-worker.d.ts +1 -0
  216. package/dist/src/validation-task-worker.js +205 -0
  217. package/dist/src/validation-tasks-protocol.d.ts +69 -0
  218. package/dist/src/validation-tasks-protocol.js +57 -0
  219. package/dist/src/validation-tasks.d.ts +11 -0
  220. package/dist/src/validation-tasks.js +144 -0
  221. package/dist/src/vitest-evidence.d.ts +2 -0
  222. package/dist/src/vitest-evidence.js +112 -0
  223. package/dist/src/vitest-runner.d.ts +1 -0
  224. package/dist/src/vitest-runner.js +48 -0
  225. package/dist/src/vitest.d.ts +2 -0
  226. package/dist/src/vitest.js +36 -0
  227. package/dist/src/vue-router-capture.d.ts +42 -0
  228. package/dist/src/vue-router-capture.js +87 -0
  229. package/dist/src/vue-router-evidence.d.ts +2 -0
  230. package/dist/src/vue-router-evidence.js +126 -0
  231. package/dist/src/vue-router-protocol.d.ts +63 -0
  232. package/dist/src/vue-router-protocol.js +44 -0
  233. package/dist/src/vue-router-runner.d.ts +1 -0
  234. package/dist/src/vue-router-runner.js +88 -0
  235. package/dist/src/vue-router.d.ts +23 -0
  236. package/dist/src/vue-router.js +108 -0
  237. package/dist/src/vue-tsc-runner.d.ts +1 -0
  238. package/dist/src/vue-tsc-runner.js +24 -0
  239. package/dist/src/workspace.d.ts +7 -0
  240. package/dist/src/workspace.js +53 -0
  241. package/docs/ACCEPTANCE.md +56 -0
  242. package/docs/ACTIONLINT.md +112 -0
  243. package/docs/ARCHITECTURE-POLICY.md +82 -0
  244. package/docs/ARCHITECTURE.md +122 -0
  245. package/docs/CLANG.md +113 -0
  246. package/docs/CLIENTS.md +109 -0
  247. package/docs/CONTRACTS.md +73 -0
  248. package/docs/DEPENDENCIES.md +42 -0
  249. package/docs/DJANGO.md +70 -0
  250. package/docs/DOTNET.md +114 -0
  251. package/docs/ENVIRONMENTS.md +52 -0
  252. package/docs/ESLINT.md +68 -0
  253. package/docs/EVALUATION.md +124 -0
  254. package/docs/EXECUTION.md +250 -0
  255. package/docs/EXTERNAL-ADAPTERS.md +170 -0
  256. package/docs/EXTERNAL-EVALUATION.md +155 -0
  257. package/docs/EXTERNAL-RUFF-EVALUATION.md +85 -0
  258. package/docs/FASTAPI.md +73 -0
  259. package/docs/FINDING-POLICY.md +67 -0
  260. package/docs/GO-RACE.md +19 -0
  261. package/docs/GO-SCOPE.md +63 -0
  262. package/docs/GOLANGCI-LINT.md +64 -0
  263. package/docs/GUIDANCE.md +46 -0
  264. package/docs/IMPACT-MEASUREMENT.md +97 -0
  265. package/docs/INSTALLATION.md +102 -0
  266. package/docs/JAVA.md +79 -0
  267. package/docs/JEST.md +36 -0
  268. package/docs/JUNIT.md +31 -0
  269. package/docs/LANGUAGES.md +113 -0
  270. package/docs/LARAVEL.md +111 -0
  271. package/docs/MCP-COMPATIBILITY.md +68 -0
  272. package/docs/MUTATIONS.md +93 -0
  273. package/docs/MYPY.md +37 -0
  274. package/docs/NATIVE-CI.md +96 -0
  275. package/docs/NUXT.md +123 -0
  276. package/docs/PACK-DISTRIBUTION.md +98 -0
  277. package/docs/PERFORMANCE.md +71 -0
  278. package/docs/PEST.md +36 -0
  279. package/docs/PHPSTAN.md +48 -0
  280. package/docs/PHPUNIT.md +32 -0
  281. package/docs/PINT.md +41 -0
  282. package/docs/PLAN.md +265 -0
  283. package/docs/PLAYWRIGHT.md +73 -0
  284. package/docs/POLICY-PACKS.md +106 -0
  285. package/docs/PRIOR-WORKFLOW-EVALUATION.md +116 -0
  286. package/docs/PUBLICATION.md +28 -0
  287. package/docs/PYTEST.md +51 -0
  288. package/docs/RELEASE.md +115 -0
  289. package/docs/RENAMING.md +55 -0
  290. package/docs/REVIEW-EXCHANGE.md +136 -0
  291. package/docs/RUBY.md +50 -0
  292. package/docs/RUFF.md +39 -0
  293. package/docs/RUNTIME-INVENTORY.md +56 -0
  294. package/docs/RUST.md +74 -0
  295. package/docs/SARIF.md +64 -0
  296. package/docs/STATUS.md +530 -0
  297. package/docs/SWIFT.md +48 -0
  298. package/docs/TASK-STORAGE.md +141 -0
  299. package/docs/TOOL-IDENTITY.md +30 -0
  300. package/docs/TYPESCRIPT-BUILD.md +55 -0
  301. package/docs/VALIDATION-TASKS.md +119 -0
  302. package/docs/VITEST.md +36 -0
  303. package/docs/VUE-ROUTER.md +130 -0
  304. package/docs/VUE-TSC.md +26 -0
  305. package/docs/WORKSPACES.md +81 -0
  306. package/docs/licenses/nodable-entities-3.0.0.txt +21 -0
  307. package/docs/measurements/client-claude-before-schema-fix-darwin-arm64-node26.json +60 -0
  308. package/docs/measurements/client-claude-darwin-arm64-node26.json +55 -0
  309. package/docs/measurements/client-codex-darwin-arm64-node26.json +175 -0
  310. package/docs/measurements/evaluation-corpus-repo-verifier.json +332 -0
  311. package/docs/measurements/evaluation-darwin-arm64-node26.json +1236 -0
  312. package/docs/measurements/evaluation-linux-arm64-node22.json +1236 -0
  313. package/docs/measurements/external-eslint-darwin-arm64-node26.json +11866 -0
  314. package/docs/measurements/external-eslint-linux-arm64-node22.json +11866 -0
  315. package/docs/measurements/external-eslint-preservation.json +1527 -0
  316. package/docs/measurements/external-ruff-darwin-arm64-node26.json +1031 -0
  317. package/docs/measurements/external-ruff-linux-arm64-node22.json +1031 -0
  318. package/docs/measurements/hosted-ci-52ba415.json +1005 -0
  319. package/docs/measurements/impact-darwin-arm64-node26.json +6685 -0
  320. package/docs/measurements/impact-linux-arm64-node22.json +6690 -0
  321. package/docs/measurements/performance-darwin-arm64-node26.json +845 -0
  322. package/docs/measurements/performance-linux-arm64-node22.json +845 -0
  323. package/docs/measurements/required-native-profiles.json +500 -0
  324. package/package.json +72 -0
  325. package/packs/actionlint.json +8 -0
  326. package/packs/cpp.json +8 -0
  327. package/packs/dotnet.json +8 -0
  328. package/packs/go.json +7 -0
  329. package/packs/java.json +8 -0
  330. package/packs/javascript-node.json +7 -0
  331. package/packs/nuxt.json +7 -0
  332. package/packs/php-pest.json +7 -0
  333. package/packs/php-phpunit.json +7 -0
  334. package/packs/python-pytest.json +7 -0
  335. package/packs/ruby.json +7 -0
  336. package/packs/rust.json +7 -0
  337. package/packs/swift.json +7 -0
  338. package/packs/vue-router.json +7 -0
  339. package/packs/vue.json +11 -0
  340. package/schemas/actionlint-config.schema.json +19 -0
  341. package/schemas/architecture-policy.schema.json +51 -0
  342. package/schemas/architecture-report.schema.json +123 -0
  343. package/schemas/architecture-summary.schema.json +82 -0
  344. package/schemas/clang-database.schema.json +23 -0
  345. package/schemas/config.schema.json +69 -0
  346. package/schemas/contract-bundle.schema.json +78 -0
  347. package/schemas/contract-report.schema.json +114 -0
  348. package/schemas/contract-summary.schema.json +72 -0
  349. package/schemas/dependency-graph.schema.json +65 -0
  350. package/schemas/django-config.schema.json +16 -0
  351. package/schemas/dotnet-config.schema.json +60 -0
  352. package/schemas/external-manifest.schema.json +97 -0
  353. package/schemas/external-reference.schema.json +10 -0
  354. package/schemas/external-request.schema.json +56 -0
  355. package/schemas/external-result.schema.json +111 -0
  356. package/schemas/fastapi-config.schema.json +27 -0
  357. package/schemas/finding-baseline.schema.json +92 -0
  358. package/schemas/finding-comparison-summary.schema.json +95 -0
  359. package/schemas/finding-comparison.schema.json +121 -0
  360. package/schemas/guidance-context.schema.json +34 -0
  361. package/schemas/guidance-report.schema.json +176 -0
  362. package/schemas/guidance-summary.schema.json +68 -0
  363. package/schemas/java-config.schema.json +24 -0
  364. package/schemas/junit.schema.json +69 -0
  365. package/schemas/laravel-config.schema.json +11 -0
  366. package/schemas/mutation-recipe.schema.json +31 -0
  367. package/schemas/mutation-report.schema.json +238 -0
  368. package/schemas/mutation-summary.schema.json +98 -0
  369. package/schemas/nuxt-config.schema.json +51 -0
  370. package/schemas/plan-summary.schema.json +63 -0
  371. package/schemas/plan.schema.json +244 -0
  372. package/schemas/policy-pack.schema.json +41 -0
  373. package/schemas/report-summary.schema.json +100 -0
  374. package/schemas/report.schema.json +463 -0
  375. package/schemas/review-assessment.schema.json +133 -0
  376. package/schemas/review-context-summary.schema.json +31 -0
  377. package/schemas/review-context.schema.json +254 -0
  378. package/schemas/review-receipt-summary.schema.json +104 -0
  379. package/schemas/review-receipt.schema.json +271 -0
  380. package/schemas/review-selection.schema.json +30 -0
  381. package/schemas/runtime-comparison-summary.schema.json +68 -0
  382. package/schemas/runtime-comparison.schema.json +127 -0
  383. package/schemas/runtime-inventory.schema.json +99 -0
  384. package/schemas/vue-router-config.schema.json +67 -0
  385. package/server.json +47 -0
@@ -0,0 +1,94 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readdir, readFile, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ const excludedDirectories = new Set([
5
+ ".git",
6
+ "node_modules",
7
+ "vendor",
8
+ ".venv",
9
+ "venv",
10
+ "__pycache__",
11
+ "dist",
12
+ "build",
13
+ ".build",
14
+ "target",
15
+ "coverage",
16
+ ".next",
17
+ ".nuxt",
18
+ ".output",
19
+ ".checktrail",
20
+ ".repo-verifier",
21
+ ".terraform",
22
+ "obj",
23
+ ]);
24
+ function sensitive(name) {
25
+ return (name === ".env" ||
26
+ name.startsWith(".env.") ||
27
+ name === ".checktrail.local.json" ||
28
+ name === ".repo-verifier.local.json" ||
29
+ /\.(pem|key|p12|pfx)$/i.test(name));
30
+ }
31
+ export async function withinRoot(root, relative) {
32
+ if (path.isAbsolute(relative) || relative.includes("\0"))
33
+ throw new Error("Expected a relative path");
34
+ const canonicalRoot = await realpath(root);
35
+ const resolved = await realpath(path.resolve(canonicalRoot, relative));
36
+ const relation = path.relative(canonicalRoot, resolved);
37
+ if (relation === ".." ||
38
+ relation.startsWith(`..${path.sep}`) ||
39
+ path.isAbsolute(relation)) {
40
+ throw new Error("Path escapes configured root");
41
+ }
42
+ return resolved;
43
+ }
44
+ export async function readProjectFile(root, relative) {
45
+ const resolved = await withinRoot(root, relative);
46
+ const stat = await lstat(resolved);
47
+ if (!stat.isFile() || stat.size > 8 * 1024 * 1024)
48
+ throw new Error("File exceeds inventory limits");
49
+ return readFile(resolved, "utf8");
50
+ }
51
+ export async function inventory(inputRoot) {
52
+ const root = await realpath(inputRoot);
53
+ const files = [];
54
+ const excluded = [];
55
+ const hash = createHash("sha256");
56
+ let size = 0;
57
+ let entriesSeen = 0;
58
+ async function visit(relative, depth) {
59
+ if (depth > 32)
60
+ throw new Error("Inventory depth limit exceeded");
61
+ const directory = await withinRoot(root, relative);
62
+ const entries = await readdir(directory, { withFileTypes: true });
63
+ entries.sort((a, b) => a.name.localeCompare(b.name, "en"));
64
+ for (const entry of entries) {
65
+ if (++entriesSeen > 20_000)
66
+ throw new Error("Inventory entry limit exceeded");
67
+ const file = relative === "." ? entry.name : `${relative}/${entry.name}`;
68
+ if (entry.name === ".git" ||
69
+ entry.isSymbolicLink() ||
70
+ sensitive(entry.name) ||
71
+ (entry.isDirectory() && excludedDirectories.has(entry.name))) {
72
+ excluded.push(file);
73
+ }
74
+ else if (entry.isDirectory()) {
75
+ await visit(file, depth + 1);
76
+ }
77
+ else if (entry.isFile()) {
78
+ const resolved = await withinRoot(root, file);
79
+ const stat = await lstat(resolved);
80
+ size += stat.size;
81
+ if (stat.size > 8 * 1024 * 1024 || size > 64 * 1024 * 1024) {
82
+ throw new Error("Inventory byte limit exceeded");
83
+ }
84
+ const bytes = await readFile(resolved);
85
+ hash.update(JSON.stringify([file, bytes.length]));
86
+ hash.update(bytes);
87
+ files.push(file);
88
+ }
89
+ }
90
+ }
91
+ await visit(".", 0);
92
+ hash.update(JSON.stringify(excluded));
93
+ return { root, files, excluded, fingerprint: hash.digest("hex") };
94
+ }
@@ -0,0 +1 @@
1
+ export declare const javaCompilerSource: string;
@@ -0,0 +1,110 @@
1
+ export const javaCompilerSource = String.raw `
2
+ import java.io.*;
3
+ import java.net.URI;
4
+ import java.nio.charset.StandardCharsets;
5
+ import java.nio.file.*;
6
+ import java.util.*;
7
+ import java.util.jar.*;
8
+ import javax.tools.*;
9
+ import com.sun.source.tree.*;
10
+ import com.sun.source.util.*;
11
+
12
+ class VerifierCompiler {
13
+ static String quote(String value) {
14
+ if (value == null) return "null";
15
+ StringBuilder out = new StringBuilder("\"");
16
+ for (char c : value.toCharArray()) {
17
+ if (c == '"' || c == '\\') out.append('\\').append(c);
18
+ else if (c < 32 || Character.isSurrogate(c)) out.append(String.format("\\u%04x", (int)c));
19
+ else out.append(c);
20
+ }
21
+ return out.append('"').toString();
22
+ }
23
+ static String strings(Collection<String> values) {
24
+ return "[" + String.join(",", values.stream().map(VerifierCompiler::quote).toList()) + "]";
25
+ }
26
+ public static void main(String[] args) throws Exception {
27
+ List<String> input = Files.readAllLines(Path.of(args[0]), StandardCharsets.UTF_8);
28
+ int release = Integer.parseInt(input.get(0));
29
+ boolean strict = Boolean.parseBoolean(input.get(1));
30
+ int count = Integer.parseInt(input.get(2));
31
+ List<Path> jars = new ArrayList<>();
32
+ for (int i = 0; i < count; i++) {
33
+ Path jar = Path.of(new String(Base64.getDecoder().decode(input.get(3 + i)), StandardCharsets.UTF_8));
34
+ try (JarFile archive = new JarFile(jar.toFile())) {
35
+ if (archive.getManifest() != null && archive.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH) != null)
36
+ throw new IOException("Implicit JAR classpath is unsupported");
37
+ if (archive.stream().anyMatch(entry -> entry.getName().endsWith(".java")))
38
+ throw new IOException("Source-bearing JARs are unsupported");
39
+ }
40
+ jars.add(jar);
41
+ }
42
+ List<Path> sources = new ArrayList<>();
43
+ for (int i = 3 + count; i < input.size(); i++)
44
+ sources.add(Path.of(new String(Base64.getDecoder().decode(input.get(i)), StandardCharsets.UTF_8)));
45
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
46
+ if (compiler == null) throw new IOException("Full JDK required");
47
+ List<Diagnostic<? extends JavaFileObject>> diagnostics = new ArrayList<>();
48
+ DiagnosticListener<JavaFileObject> listener = diagnostic -> {
49
+ if (diagnostics.size() >= 2000) throw new IllegalStateException("Diagnostic limit exceeded");
50
+ diagnostics.add(diagnostic);
51
+ };
52
+ try (StandardJavaFileManager standard = compiler.getStandardFileManager(listener, Locale.ROOT, StandardCharsets.UTF_8)) {
53
+ standard.setLocationFromPaths(StandardLocation.CLASS_PATH, jars);
54
+ standard.setLocationFromPaths(StandardLocation.SOURCE_PATH, List.of());
55
+ standard.setLocationFromPaths(StandardLocation.ANNOTATION_PROCESSOR_PATH, List.of());
56
+ long[] bytes = {0};
57
+ JavaFileManager manager = new ForwardingJavaFileManager<StandardJavaFileManager>(standard) {
58
+ @Override public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
59
+ if (location != StandardLocation.CLASS_OUTPUT || kind != JavaFileObject.Kind.CLASS)
60
+ throw new IOException("Unexpected compiler output");
61
+ return new SimpleJavaFileObject(URI.create("memory:///" + name.replace('.', '/') + kind.extension), kind) {
62
+ @Override public OutputStream openOutputStream() {
63
+ return new OutputStream() {
64
+ public void write(int value) throws IOException {
65
+ if (++bytes[0] > 32 * 1024 * 1024) throw new IOException("Output limit exceeded");
66
+ }
67
+ public void write(byte[] value, int offset, int length) throws IOException {
68
+ bytes[0] += length;
69
+ if (bytes[0] > 32 * 1024 * 1024) throw new IOException("Output limit exceeded");
70
+ }
71
+ };
72
+ }
73
+ };
74
+ }
75
+ };
76
+ List<String> options = new ArrayList<>(List.of("--release", Integer.toString(release), "-encoding", "UTF-8", "-proc:none", "-implicit:none", "-Xlint:all", "-Xmaxerrs", "2000", "-Xmaxwarns", "2000"));
77
+ if (strict) options.add("-Werror");
78
+ StringWriter extra = new StringWriter();
79
+ JavacTask task = (JavacTask)compiler.getTask(extra, manager, listener, options, null, standard.getJavaFileObjectsFromPaths(sources));
80
+ Map<String, Integer> parsed = new TreeMap<>();
81
+ Map<String, Set<String>> declared = new TreeMap<>();
82
+ Map<String, Set<String>> analyzed = new TreeMap<>();
83
+ int[] finished = {0};
84
+ task.addTaskListener(new TaskListener() {
85
+ @Override public void finished(TaskEvent event) {
86
+ if (event.getKind() == TaskEvent.Kind.COMPILATION) finished[0]++;
87
+ if (event.getSourceFile() == null) return;
88
+ String file = Path.of(event.getSourceFile().toUri()).toAbsolutePath().normalize().toString();
89
+ if (event.getKind() == TaskEvent.Kind.PARSE) {
90
+ parsed.merge(file, 1, Integer::sum);
91
+ Set<String> types = declared.computeIfAbsent(file, key -> new TreeSet<>());
92
+ CompilationUnitTree unit = event.getCompilationUnit();
93
+ String prefix = unit.getPackageName() == null ? "" : unit.getPackageName().toString() + ".";
94
+ for (Tree tree : unit.getTypeDecls()) if (tree instanceof ClassTree type)
95
+ types.add(prefix + type.getSimpleName());
96
+ }
97
+ if (event.getKind() == TaskEvent.Kind.ANALYZE && event.getTypeElement() != null)
98
+ analyzed.computeIfAbsent(file, key -> new TreeSet<>()).add(event.getTypeElement().getQualifiedName().toString());
99
+ }
100
+ });
101
+ boolean success = task.call();
102
+ List<String> observations = new ArrayList<>();
103
+ for (String file : parsed.keySet()) observations.add("{\"file\":" + quote(file) + ",\"parsed\":" + parsed.get(file) + ",\"declared\":" + strings(declared.getOrDefault(file, Set.of())) + ",\"analyzed\":" + strings(analyzed.getOrDefault(file, Set.of())) + "}");
104
+ List<String> messages = new ArrayList<>();
105
+ for (Diagnostic<? extends JavaFileObject> item : diagnostics) messages.add("{\"kind\":" + quote(item.getKind().name()) + ",\"code\":" + quote(item.getCode()) + ",\"message\":" + quote(item.getMessage(Locale.ROOT)) + ",\"file\":" + quote(item.getSource() == null ? null : Path.of(item.getSource().toUri()).toAbsolutePath().normalize().toString()) + ",\"line\":" + item.getLineNumber() + "}");
106
+ System.out.print("{\"version\":1,\"runtime\":" + quote(Runtime.version().toString()) + ",\"vendor\":" + quote(System.getProperty("java.vendor")) + ",\"success\":" + success + ",\"finished\":" + finished[0] + ",\"extra\":" + quote(extra.toString()) + ",\"sources\":[" + String.join(",", observations) + "],\"diagnostics\":[" + String.join(",", messages) + "]}");
107
+ }
108
+ }
109
+ }
110
+ `;
@@ -0,0 +1,2 @@
1
+ import type { Check, CheckResult, ProcessResult } from "./types.js";
2
+ export declare function javaEvidence(check: Check, processes: ProcessResult[], root: string | undefined): Pick<CheckResult, "status" | "reason" | "findings" | "findingsComplete">;
@@ -0,0 +1,114 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ import { javaInvocationSchema } from "./java.js";
4
+ const schema = z.strictObject({
5
+ version: z.literal(1),
6
+ runtime: z.literal("25.0.4+7-LTS"),
7
+ vendor: z.literal("Eclipse Adoptium"),
8
+ success: z.boolean(),
9
+ finished: z.number().int().nonnegative(),
10
+ extra: z.string(),
11
+ sources: z
12
+ .array(z.strictObject({
13
+ file: z.string(),
14
+ parsed: z.number().int().nonnegative(),
15
+ declared: z.array(z.string()),
16
+ analyzed: z.array(z.string()),
17
+ }))
18
+ .max(20_000),
19
+ diagnostics: z
20
+ .array(z.strictObject({
21
+ kind: z.enum([
22
+ "ERROR",
23
+ "WARNING",
24
+ "MANDATORY_WARNING",
25
+ "NOTE",
26
+ "OTHER",
27
+ ]),
28
+ code: z.string().min(1),
29
+ message: z.string(),
30
+ file: z.string().nullable(),
31
+ line: z.number().int(),
32
+ }))
33
+ .max(2000),
34
+ });
35
+ export function javaEvidence(check, processes, root) {
36
+ const incomplete = {
37
+ status: "inconclusive",
38
+ reason: "Java evidence is malformed or lacks native source/analysis accounting",
39
+ findingsComplete: false,
40
+ };
41
+ if (!root || processes.length !== 1)
42
+ return incomplete;
43
+ const process = processes[0];
44
+ if (process.exitCode === 3) {
45
+ try {
46
+ z.strictObject({ unavailable: z.literal("java-toolchain") }).parse(JSON.parse(process.stdout));
47
+ return {
48
+ status: "unavailable",
49
+ reason: "The verified Temurin Java compiler is unavailable",
50
+ };
51
+ }
52
+ catch {
53
+ return incomplete;
54
+ }
55
+ }
56
+ if (process.exitCode !== 0)
57
+ return {
58
+ status: "error",
59
+ reason: "Java evidence collection did not complete normally",
60
+ findingsComplete: false,
61
+ };
62
+ try {
63
+ const data = schema.parse(JSON.parse(process.stdout));
64
+ const planned = javaInvocationSchema.parse(JSON.parse(check.commands[0].args[2]));
65
+ if (JSON.stringify(planned.scope) !== JSON.stringify(check.scope) ||
66
+ data.finished !== 1 ||
67
+ data.extra.trim())
68
+ return incomplete;
69
+ const expected = new Set(check.scope.map((file) => path.resolve(root, check.project, file)));
70
+ const findings = data.diagnostics.map((item) => ({
71
+ ruleId: `javac/${item.code}`,
72
+ level: item.kind === "ERROR"
73
+ ? "error"
74
+ : item.kind.endsWith("WARNING")
75
+ ? "warning"
76
+ : "note",
77
+ message: item.message,
78
+ ...(item.file && expected.has(item.file) && item.line > 0
79
+ ? {
80
+ file: path.relative(root, item.file).split(path.sep).join("/"),
81
+ line: item.line,
82
+ }
83
+ : {}),
84
+ }));
85
+ const errors = data.diagnostics.some((item) => item.kind === "ERROR");
86
+ if (errors && !data.success)
87
+ return {
88
+ status: "failed",
89
+ reason: "Java reported compiler errors; failed compilation does not establish complete analysis",
90
+ findings,
91
+ findingsComplete: false,
92
+ };
93
+ if (errors ||
94
+ !data.success ||
95
+ !expected.size ||
96
+ data.sources.length !== expected.size ||
97
+ new Set(data.sources.map((item) => item.file)).size !== expected.size ||
98
+ data.sources.some((item) => !expected.has(item.file) ||
99
+ item.parsed !== 1 ||
100
+ new Set(item.declared).size !== item.declared.length ||
101
+ new Set(item.analyzed).size !== item.analyzed.length ||
102
+ item.declared.some((type) => !item.analyzed.includes(type))))
103
+ return { ...incomplete, findings };
104
+ return {
105
+ status: "passed",
106
+ reason: "The configured Java sources compiled with complete parse/analysis accounting; no application code or tests were executed",
107
+ findings,
108
+ findingsComplete: true,
109
+ };
110
+ }
111
+ catch {
112
+ return incomplete;
113
+ }
114
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { javaCompilerSource } from "./java-compiler.js";
6
+ import { javaDependencies, javaInvocationSchema } from "./java.js";
7
+ import { withinRoot } from "./inventory.js";
8
+ const env = { ...process.env };
9
+ for (const name of [
10
+ "JAVA_TOOL_OPTIONS",
11
+ "JDK_JAVA_OPTIONS",
12
+ "_JAVA_OPTIONS",
13
+ "CLASSPATH",
14
+ ])
15
+ delete env[name];
16
+ function invoke(args) {
17
+ return spawnSync("java", args, {
18
+ env,
19
+ encoding: "utf8",
20
+ maxBuffer: 1024 * 1024,
21
+ });
22
+ }
23
+ async function main() {
24
+ const version = invoke(["--version"]);
25
+ if (process.argv[2] === "--version") {
26
+ process.stdout.write(version.stdout ?? "");
27
+ process.stderr.write(version.stderr ?? "");
28
+ process.exitCode = version.status ?? 2;
29
+ return;
30
+ }
31
+ if (version.status !== 0 ||
32
+ version.stderr ||
33
+ !version.stdout.startsWith("openjdk 25.0.4 2026-07-21 LTS\nOpenJDK Runtime Environment Temurin-25.0.4+7 ")) {
34
+ process.stdout.write(JSON.stringify({ unavailable: "java-toolchain" }));
35
+ process.exitCode = 3;
36
+ return;
37
+ }
38
+ const root = await realpath(process.argv[2]);
39
+ const project = path.relative(root, await realpath(process.cwd())) || ".";
40
+ const invocation = javaInvocationSchema.parse(JSON.parse(process.argv[3]));
41
+ const jars = await javaDependencies(root, project, invocation.config);
42
+ const files = await Promise.all(invocation.scope.map((file) => withinRoot(root, path.join(project, file))));
43
+ const temporary = await mkdtemp(path.join(tmpdir(), "checktrail-java-"));
44
+ try {
45
+ const helper = path.join(temporary, "VerifierCompiler.java");
46
+ const config = path.join(temporary, "inputs.txt");
47
+ await writeFile(helper, javaCompilerSource);
48
+ await writeFile(config, [
49
+ String(invocation.config.release),
50
+ String(invocation.config.warningsAsErrors),
51
+ String(jars.length),
52
+ ...[...jars, ...files].map((file) => Buffer.from(file).toString("base64")),
53
+ ].join("\n"));
54
+ const result = invoke([
55
+ "-Xmx256m",
56
+ "-Dfile.encoding=UTF-8",
57
+ "-Duser.language=en",
58
+ "-Duser.country=US",
59
+ "--class-path",
60
+ temporary,
61
+ helper,
62
+ config,
63
+ ]);
64
+ if (result.error ||
65
+ result.signal ||
66
+ result.status !== 0 ||
67
+ result.stderr.trim())
68
+ throw new Error("Java compiler did not complete native evidence collection");
69
+ await javaDependencies(root, project, invocation.config);
70
+ process.stdout.write(result.stdout);
71
+ }
72
+ finally {
73
+ await rm(temporary, { recursive: true, force: true });
74
+ }
75
+ }
76
+ main().catch(() => {
77
+ process.stderr.write("Java evidence collection could not complete\n");
78
+ process.exitCode = 2;
79
+ });
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import type { Check, Inventory, Project } from "./types.js";
3
+ export declare const javaConfigSchema: z.ZodObject<{
4
+ schemaVersion: z.ZodLiteral<1>;
5
+ release: z.ZodNumber;
6
+ warningsAsErrors: z.ZodBoolean;
7
+ classPath: z.ZodArray<z.ZodObject<{
8
+ path: z.ZodString;
9
+ sha256: z.ZodString;
10
+ }, z.core.$strict>>;
11
+ }, z.core.$strict>;
12
+ export declare const javaInvocationSchema: z.ZodObject<{
13
+ config: z.ZodObject<{
14
+ schemaVersion: z.ZodLiteral<1>;
15
+ release: z.ZodNumber;
16
+ warningsAsErrors: z.ZodBoolean;
17
+ classPath: z.ZodArray<z.ZodObject<{
18
+ path: z.ZodString;
19
+ sha256: z.ZodString;
20
+ }, z.core.$strict>>;
21
+ }, z.core.$strict>;
22
+ scope: z.ZodArray<z.ZodString>;
23
+ }, z.core.$strict>;
24
+ export declare function javaDependencies(root: string, project: string, config: z.infer<typeof javaConfigSchema>): Promise<string[]>;
25
+ export declare function javaCheck(source: Inventory, project: Project): Promise<Check>;
@@ -0,0 +1,102 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { z } from "zod";
6
+ import { readProjectFile, withinRoot } from "./inventory.js";
7
+ export const javaConfigSchema = z.strictObject({
8
+ schemaVersion: z.literal(1),
9
+ release: z.number().int().min(8).max(25),
10
+ warningsAsErrors: z.boolean(),
11
+ classPath: z
12
+ .array(z.strictObject({
13
+ path: z
14
+ .string()
15
+ .min(1)
16
+ .max(4096)
17
+ .refine((value) => !/[\0\r\n]/.test(value)),
18
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
19
+ }))
20
+ .max(128),
21
+ });
22
+ export const javaInvocationSchema = z.strictObject({
23
+ config: javaConfigSchema,
24
+ scope: z.array(z.string().min(1)).min(1).max(20_000),
25
+ });
26
+ export async function javaDependencies(root, project, config) {
27
+ let total = 0;
28
+ const files = [];
29
+ for (const item of config.classPath) {
30
+ const declared = path.resolve(root, project, item.path);
31
+ const file = await withinRoot(root, path.relative(root, declared));
32
+ if (declared !== file)
33
+ throw new Error("Java dependencies must not traverse symbolic links");
34
+ const info = await stat(file);
35
+ total += info.size;
36
+ if (!info.isFile() ||
37
+ !file.endsWith(".jar") ||
38
+ info.size > 32 * 1024 * 1024 ||
39
+ total > 128 * 1024 * 1024)
40
+ throw new Error("Java dependencies require bounded, local JAR files");
41
+ if (files.includes(file))
42
+ throw new Error("Duplicate Java classpath dependency");
43
+ if (createHash("sha256")
44
+ .update(await readFile(file))
45
+ .digest("hex") !== item.sha256)
46
+ throw new Error("Java dependency checksum does not match");
47
+ files.push(file);
48
+ }
49
+ return files;
50
+ }
51
+ export async function javaCheck(source, project) {
52
+ const scope = project.files.filter((file) => file.endsWith(".java"));
53
+ const check = {
54
+ id: "jvm.javac",
55
+ adapter: project.adapter,
56
+ project: project.path,
57
+ scope,
58
+ kind: "analysis",
59
+ parser: "java-json",
60
+ commands: [],
61
+ reason: "Compile explicitly configured Java sources with annotation processing disabled and native parse/analysis accounting.",
62
+ };
63
+ try {
64
+ if (!project.files.includes("checktrail.java.json"))
65
+ throw new Error("Prepare an inventoried checktrail.java.json with a release and pinned classpath; Maven and Gradle are not invoked");
66
+ if (!scope.length)
67
+ throw new Error("No Java source was inventoried");
68
+ if (scope.some((file) => path.posix.basename(file) === "module-info.java"))
69
+ throw new Error("Java modules require a separate verified module-path profile");
70
+ if (project.files.some((file) => /\.(?:kt|kts|scala)$/.test(file) &&
71
+ !["build.gradle.kts", "settings.gradle.kts"].includes(path.posix.basename(file))))
72
+ throw new Error("Mixed JVM language projects require a separate compiler profile");
73
+ const config = javaConfigSchema.parse(JSON.parse(await readProjectFile(source.root, path.posix.join(project.path, "checktrail.java.json"))));
74
+ await javaDependencies(source.root, project.path, config);
75
+ const invocation = JSON.stringify({ config, scope });
76
+ if (Buffer.byteLength(invocation) > 100 * 1024)
77
+ throw new Error("Java invocation exceeds the 100 KiB argument limit");
78
+ check.commands.push({
79
+ executable: process.execPath,
80
+ args: [
81
+ fileURLToPath(new URL("./java-runner.js", import.meta.url)),
82
+ source.root,
83
+ invocation,
84
+ ],
85
+ cwd: project.path,
86
+ env: {
87
+ PATH: process.env.PATH ?? "",
88
+ JAVA_TOOL_OPTIONS: "",
89
+ JDK_JAVA_OPTIONS: "",
90
+ _JAVA_OPTIONS: "",
91
+ CLASSPATH: "",
92
+ },
93
+ });
94
+ }
95
+ catch (error) {
96
+ check.unavailableReason =
97
+ error instanceof Error
98
+ ? error.message
99
+ : "Java configuration could not be prepared";
100
+ }
101
+ return check;
102
+ }
@@ -0,0 +1,2 @@
1
+ import type { Check, CheckResult, ProcessResult } from "./types.js";
2
+ export declare function jestEvidence(check: Check, processes: ProcessResult[], root: string | undefined): Pick<CheckResult, "status" | "reason" | "tests">;
@@ -0,0 +1,117 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ const count = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
4
+ const schema = z.object({
5
+ success: z.boolean(),
6
+ wasInterrupted: z.boolean(),
7
+ numTotalTests: count,
8
+ numPassedTests: count,
9
+ numFailedTests: count,
10
+ numPendingTests: count,
11
+ numTodoTests: count,
12
+ numTotalTestSuites: count,
13
+ numPassedTestSuites: count,
14
+ numFailedTestSuites: count,
15
+ numPendingTestSuites: count,
16
+ numRuntimeErrorTestSuites: count,
17
+ runExecError: z.unknown().optional(),
18
+ testResults: z.array(z.object({
19
+ testFilePath: z.string(),
20
+ numPassingTests: count,
21
+ numFailingTests: count,
22
+ numPendingTests: count,
23
+ numTodoTests: count,
24
+ testExecError: z.unknown().optional(),
25
+ testResults: z.array(z.object({
26
+ status: z.enum(["passed", "failed", "pending", "todo", "disabled"]),
27
+ fullName: z.string(),
28
+ wouldRun: z.unknown().optional(),
29
+ })),
30
+ })),
31
+ });
32
+ export function jestEvidence(check, processes, root) {
33
+ const incomplete = {
34
+ status: "inconclusive",
35
+ reason: "Jest evidence is incomplete, inconsistent, or omits planned files or runnable tests.",
36
+ };
37
+ if (!root || processes.length !== 1 || !check.scope.length)
38
+ return incomplete;
39
+ const process = processes[0];
40
+ if (process.exitCode !== 0 && process.exitCode !== 1)
41
+ return {
42
+ status: "error",
43
+ reason: "Jest could not start or complete normally; inspect the runtime and configuration.",
44
+ };
45
+ let report;
46
+ try {
47
+ report = schema.parse(JSON.parse(process.stdout));
48
+ }
49
+ catch {
50
+ return incomplete;
51
+ }
52
+ if (report.wasInterrupted)
53
+ return incomplete;
54
+ const expected = new Set(check.scope.map((file) => path.resolve(root, check.project, file)));
55
+ const seen = new Set();
56
+ const tests = { total: 0, passed: 0, failed: 0, skipped: 0 };
57
+ let pending = 0;
58
+ let todo = 0;
59
+ let runtimeFailure = Boolean(report.runExecError);
60
+ for (const file of report.testResults) {
61
+ if (!expected.has(file.testFilePath) || seen.has(file.testFilePath))
62
+ return incomplete;
63
+ seen.add(file.testFilePath);
64
+ runtimeFailure ||= Boolean(file.testExecError);
65
+ const counts = { passed: 0, failed: 0, pending: 0, todo: 0 };
66
+ for (const assertion of file.testResults) {
67
+ if (assertion.wouldRun !== undefined)
68
+ return incomplete;
69
+ if (assertion.status === "disabled")
70
+ counts.pending++;
71
+ else
72
+ counts[assertion.status]++;
73
+ }
74
+ if (counts.passed !== file.numPassingTests ||
75
+ counts.failed !== file.numFailingTests ||
76
+ counts.pending !== file.numPendingTests ||
77
+ counts.todo !== file.numTodoTests)
78
+ return incomplete;
79
+ tests.passed += counts.passed;
80
+ tests.failed += counts.failed;
81
+ pending += counts.pending;
82
+ todo += counts.todo;
83
+ }
84
+ tests.skipped = pending + todo;
85
+ tests.total = tests.passed + tests.failed + tests.skipped;
86
+ if (seen.size !== expected.size ||
87
+ tests.total !== report.numTotalTests ||
88
+ tests.passed !== report.numPassedTests ||
89
+ tests.failed !== report.numFailedTests ||
90
+ pending !== report.numPendingTests ||
91
+ todo !== report.numTodoTests ||
92
+ report.numTotalTestSuites !== seen.size ||
93
+ report.numTotalTestSuites !==
94
+ report.numPassedTestSuites +
95
+ report.numFailedTestSuites +
96
+ report.numPendingTestSuites)
97
+ return incomplete;
98
+ if (tests.failed ||
99
+ runtimeFailure ||
100
+ report.numFailedTestSuites ||
101
+ report.numRuntimeErrorTestSuites ||
102
+ !report.success ||
103
+ process.exitCode !== 0)
104
+ return {
105
+ status: "failed",
106
+ reason: "Jest reported a test, suite, or execution failure.",
107
+ tests,
108
+ };
109
+ // Jest uses pending for both deliberate skips and tests omitted by .only.
110
+ if (!tests.passed || pending || report.numPendingTestSuites)
111
+ return { ...incomplete, tests };
112
+ return {
113
+ status: "passed",
114
+ reason: "Jest accounted for every planned file with passing assertions and no pending tests.",
115
+ tests,
116
+ };
117
+ }
@@ -0,0 +1 @@
1
+ export {};