@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,349 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { closeSync, constants, lstatSync, mkdirSync, openSync, readdirSync, realpathSync, } from "node:fs";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ import { projectReport } from "./output.js";
6
+ import { reportSchema, reportSummarySchema } from "./schemas.js";
7
+ const MAX_TASKS = 32;
8
+ const MAX_RESULT_BYTES = 256 * 1024;
9
+ const MAX_DATABASE_BYTES = 16 * 1024 * 1024;
10
+ const MAX_TTL_MS = 7 * 24 * 60 * 60 * 1000;
11
+ const APPLICATION_ID = 0x52565453;
12
+ const interruptedResult = {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: "Validation interrupted before a durable result was stored. Execution was not resumed.",
17
+ },
18
+ ],
19
+ isError: true,
20
+ };
21
+ const idSchema = z.string().uuid();
22
+ const fingerprintSchema = z.string().regex(/^[a-f0-9]{64}$/);
23
+ const rowSchema = z.strictObject({
24
+ id: idSchema,
25
+ source: fingerprintSchema,
26
+ created: z.number().int().nonnegative(),
27
+ updated: z.number().int().nonnegative(),
28
+ expires: z.number().int().nonnegative(),
29
+ status: z.enum(["working", "completed", "cancelled"]),
30
+ cancelling: z.union([z.literal(0), z.literal(1)]),
31
+ result: z.string().nullable(),
32
+ });
33
+ function privateFile(filename, maximum) {
34
+ let stat;
35
+ try {
36
+ stat = lstatSync(filename);
37
+ }
38
+ catch (error) {
39
+ if (error.code !== "ENOENT")
40
+ throw error;
41
+ const fd = openSync(filename, constants.O_CREAT |
42
+ constants.O_EXCL |
43
+ constants.O_RDWR |
44
+ constants.O_NOFOLLOW, 0o600);
45
+ closeSync(fd);
46
+ stat = lstatSync(filename);
47
+ }
48
+ // Closing another descriptor for an existing SQLite file releases this process's POSIX locks.
49
+ if (!stat.isFile() ||
50
+ stat.nlink !== 1 ||
51
+ stat.uid !== process.getuid() ||
52
+ (stat.mode & 0o777) !== 0o600 ||
53
+ stat.size > maximum)
54
+ throw new Error("Task store requires bounded, private, singly linked regular files");
55
+ }
56
+ function applicationId(db) {
57
+ return Number(db.prepare("PRAGMA application_id").get().application_id);
58
+ }
59
+ function transaction(db, action) {
60
+ db.exec("BEGIN IMMEDIATE");
61
+ try {
62
+ const result = action();
63
+ db.exec("COMMIT");
64
+ return result;
65
+ }
66
+ catch (error) {
67
+ // SQLite can roll back the transaction itself on SQLITE_FULL or an I/O error.
68
+ try {
69
+ db.exec("ROLLBACK");
70
+ }
71
+ catch {
72
+ /* Already rolled back. */
73
+ }
74
+ throw error;
75
+ }
76
+ }
77
+ function decodeTask(row, detailed) {
78
+ const task = {
79
+ taskId: row.id,
80
+ status: row.status,
81
+ createdAt: new Date(row.created).toISOString(),
82
+ lastUpdatedAt: new Date(row.updated).toISOString(),
83
+ ttlMs: row.expires - row.created,
84
+ };
85
+ if (row.result !== null) {
86
+ if (Buffer.byteLength(row.result) > MAX_RESULT_BYTES)
87
+ throw new Error("Stored task result exceeds the byte limit");
88
+ const value = JSON.parse(row.result);
89
+ if (JSON.stringify(value) === JSON.stringify(interruptedResult)) {
90
+ task.result = structuredClone(interruptedResult);
91
+ }
92
+ else {
93
+ const envelope = z
94
+ .strictObject({
95
+ content: z.tuple([
96
+ z.strictObject({ type: z.literal("text"), text: z.string() }),
97
+ ]),
98
+ structuredContent: z.record(z.string(), z.unknown()),
99
+ })
100
+ .parse(value);
101
+ const schema = detailed ? reportSchema : reportSummarySchema;
102
+ schema.parse(envelope.structuredContent);
103
+ if (envelope.content[0].text !== JSON.stringify(envelope.structuredContent))
104
+ throw new Error("Stored task result representations disagree");
105
+ task.result = envelope;
106
+ }
107
+ }
108
+ if ((row.status === "completed") !== (row.result !== null) ||
109
+ row.expires <= row.created ||
110
+ row.expires - row.created > MAX_TTL_MS ||
111
+ row.updated < row.created)
112
+ throw new Error("Invalid stored task state");
113
+ return task;
114
+ }
115
+ /** Bounded validation-result persistence; this component does not execute or stop workers. */
116
+ export class ValidationTaskStore {
117
+ database;
118
+ ownership;
119
+ detailed;
120
+ closed = false;
121
+ constructor(database, ownership, detailed) {
122
+ this.database = database;
123
+ this.ownership = ownership;
124
+ this.detailed = detailed;
125
+ }
126
+ active() {
127
+ if (this.closed)
128
+ throw new Error("Task store is closed");
129
+ }
130
+ prune() {
131
+ this.database
132
+ .prepare("DELETE FROM tasks WHERE expires <= ?")
133
+ .run(Date.now());
134
+ }
135
+ row(taskId) {
136
+ this.active();
137
+ idSchema.parse(taskId);
138
+ const row = this.database
139
+ .prepare("SELECT * FROM tasks WHERE id = ? AND expires > ?")
140
+ .get(taskId, Date.now());
141
+ if (!row)
142
+ throw new Error("Unknown or expired validation task");
143
+ return rowSchema.parse(row);
144
+ }
145
+ create(sourceFingerprint, ttlMs = 24 * 60 * 60 * 1000) {
146
+ this.active();
147
+ fingerprintSchema.parse(sourceFingerprint);
148
+ z.number().int().min(1).max(MAX_TTL_MS).parse(ttlMs);
149
+ return transaction(this.database, () => {
150
+ this.prune();
151
+ const count = Number(this.database.prepare("SELECT count(*) AS count FROM tasks").get()
152
+ .count);
153
+ if (count >= MAX_TASKS)
154
+ throw new Error("Task store capacity reached");
155
+ const taskId = randomUUID();
156
+ const now = Date.now();
157
+ this.database
158
+ .prepare("INSERT INTO tasks VALUES (?, ?, ?, ?, ?, 'working', 0, NULL)")
159
+ .run(taskId, sourceFingerprint, now, now, now + ttlMs);
160
+ return decodeTask(rowSchema.parse(this.database.prepare("SELECT * FROM tasks WHERE id = ?").get(taskId)), this.detailed);
161
+ });
162
+ }
163
+ get(taskId) {
164
+ return decodeTask(this.row(taskId), this.detailed);
165
+ }
166
+ complete(taskId, report) {
167
+ this.active();
168
+ return transaction(this.database, () => {
169
+ const row = this.row(taskId);
170
+ if (row.status !== "working")
171
+ return "terminal";
172
+ if (row.cancelling)
173
+ return "cancellation-requested";
174
+ reportSchema.parse(report);
175
+ if (report.sourceFingerprint !== row.source)
176
+ throw new Error("Task and report source fingerprints differ");
177
+ const projected = projectReport(report, this.detailed);
178
+ const result = JSON.stringify({
179
+ content: [{ type: "text", text: JSON.stringify(projected) }],
180
+ structuredContent: projected,
181
+ });
182
+ if (Buffer.byteLength(result) > MAX_RESULT_BYTES)
183
+ throw new Error("Task result exceeds the byte limit");
184
+ this.database
185
+ .prepare("UPDATE tasks SET status = 'completed', result = ?, updated = ? WHERE id = ? AND status = 'working'")
186
+ .run(result, Math.max(row.updated, Date.now()), taskId);
187
+ return "updated";
188
+ });
189
+ }
190
+ interrupt(taskId) {
191
+ this.active();
192
+ return transaction(this.database, () => {
193
+ const row = this.row(taskId);
194
+ if (row.status !== "working")
195
+ return "terminal";
196
+ if (row.cancelling)
197
+ return "cancellation-requested";
198
+ this.database
199
+ .prepare("UPDATE tasks SET status = 'completed', result = ?, updated = ? WHERE id = ?")
200
+ .run(JSON.stringify(interruptedResult), Math.max(row.updated, Date.now()), taskId);
201
+ return "updated";
202
+ });
203
+ }
204
+ requestCancellation(taskId) {
205
+ this.active();
206
+ return transaction(this.database, () => {
207
+ const row = this.row(taskId);
208
+ if (row.status !== "working")
209
+ return "terminal";
210
+ if (row.cancelling)
211
+ return "cancellation-requested";
212
+ this.database
213
+ .prepare("UPDATE tasks SET cancelling = 1, updated = ? WHERE id = ?")
214
+ .run(Math.max(row.updated, Date.now()), taskId);
215
+ return "updated";
216
+ });
217
+ }
218
+ /** Call only after the executor has confirmed its workers have stopped. */
219
+ finishCancellation(taskId) {
220
+ this.active();
221
+ return transaction(this.database, () => {
222
+ const row = this.row(taskId);
223
+ if (row.status !== "working")
224
+ return "terminal";
225
+ if (!row.cancelling)
226
+ throw new Error("Cancellation was not requested");
227
+ this.database
228
+ .prepare("UPDATE tasks SET status = 'cancelled', updated = ? WHERE id = ?")
229
+ .run(Math.max(row.updated, Date.now()), taskId);
230
+ return "updated";
231
+ });
232
+ }
233
+ close() {
234
+ if (this.closed)
235
+ return;
236
+ this.closed = true;
237
+ try {
238
+ this.database.close();
239
+ }
240
+ finally {
241
+ this.ownership.close();
242
+ }
243
+ }
244
+ }
245
+ /** Open an exclusively owned, root- and disclosure-bound local validation store. */
246
+ export async function openTaskStore(options) {
247
+ if (!["darwin", "linux"].includes(process.platform))
248
+ throw new Error("Task storage is supported only on macOS and Linux");
249
+ const root = realpathSync(options.root);
250
+ if (!lstatSync(root).isDirectory())
251
+ throw new Error("Task root must be a directory");
252
+ const directory = path.resolve(options.directory);
253
+ if (realpathSync(path.dirname(directory)) !== path.dirname(directory))
254
+ throw new Error("Task store parent must be canonical");
255
+ try {
256
+ mkdirSync(directory, { mode: 0o700 });
257
+ }
258
+ catch (error) {
259
+ if (error.code !== "EEXIST")
260
+ throw error;
261
+ }
262
+ const stat = lstatSync(directory);
263
+ if (!stat.isDirectory() ||
264
+ stat.isSymbolicLink() ||
265
+ stat.uid !== process.getuid() ||
266
+ (stat.mode & 0o777) !== 0o700)
267
+ throw new Error("Task store directory must be private and owned by this user");
268
+ const limits = {
269
+ "owner.sqlite": 4096,
270
+ "owner.sqlite-journal": 16384,
271
+ "tasks.sqlite": MAX_DATABASE_BYTES,
272
+ "tasks.sqlite-journal": MAX_DATABASE_BYTES + 1024 * 1024,
273
+ };
274
+ for (const entry of readdirSync(directory)) {
275
+ const limit = limits[entry];
276
+ if (typeof limit !== "number")
277
+ throw new Error("Unexpected file in task store directory");
278
+ privateFile(path.join(directory, entry), limit);
279
+ }
280
+ privateFile(path.join(directory, "owner.sqlite"), 4096);
281
+ const { DatabaseSync } = await import("node:sqlite");
282
+ const ownership = new DatabaseSync(path.join(directory, "owner.sqlite"), {
283
+ allowExtension: false,
284
+ });
285
+ let database;
286
+ try {
287
+ ownership.exec("PRAGMA busy_timeout = 0; BEGIN EXCLUSIVE");
288
+ if (applicationId(ownership) === 0 &&
289
+ !ownership.prepare("SELECT name FROM sqlite_schema LIMIT 1").get()) {
290
+ ownership.exec(`PRAGMA application_id = ${APPLICATION_ID}; PRAGMA user_version = 1; COMMIT; BEGIN EXCLUSIVE`);
291
+ }
292
+ if (applicationId(ownership) !== APPLICATION_ID ||
293
+ ownership.prepare("PRAGMA user_version").get().user_version !== 1)
294
+ throw new Error("Unrecognized task ownership database");
295
+ privateFile(path.join(directory, "tasks.sqlite"), MAX_DATABASE_BYTES);
296
+ database = new DatabaseSync(path.join(directory, "tasks.sqlite"), {
297
+ allowExtension: false,
298
+ });
299
+ const db = database;
300
+ const detailed = options.detailed === true;
301
+ const scope = createHash("sha256")
302
+ .update(JSON.stringify([root, detailed]))
303
+ .digest("hex");
304
+ const id = applicationId(db);
305
+ if (id !== APPLICATION_ID &&
306
+ !(id === 0 && !db.prepare("SELECT name FROM sqlite_schema LIMIT 1").get()))
307
+ throw new Error("Unrecognized validation task database");
308
+ if (db.prepare("PRAGMA page_size").get().page_size !== 4096)
309
+ throw new Error("Unsupported task database page size");
310
+ db.exec("PRAGMA journal_mode = DELETE; PRAGMA synchronous = FULL; PRAGMA trusted_schema = OFF; PRAGMA secure_delete = ON; PRAGMA max_page_count = 4096");
311
+ if (id === 0)
312
+ transaction(db, () => {
313
+ db.exec(`PRAGMA application_id = ${APPLICATION_ID}; PRAGMA user_version = 1;
314
+ CREATE TABLE metadata (scope TEXT NOT NULL);
315
+ CREATE TABLE tasks (id TEXT PRIMARY KEY, source TEXT NOT NULL, created INTEGER NOT NULL, updated INTEGER NOT NULL, expires INTEGER NOT NULL, status TEXT NOT NULL, cancelling INTEGER NOT NULL, result TEXT);
316
+ `);
317
+ db.prepare("INSERT INTO metadata VALUES (?)").run(scope);
318
+ });
319
+ const metadata = db.prepare("SELECT scope FROM metadata").all();
320
+ if (db.prepare("PRAGMA user_version").get().user_version !== 1 ||
321
+ metadata.length !== 1 ||
322
+ metadata[0].scope !== scope)
323
+ throw new Error("Task store version, root or disclosure mode does not match");
324
+ if (db.prepare("PRAGMA quick_check").get().quick_check !== "ok")
325
+ throw new Error("Task database integrity check failed");
326
+ const store = new ValidationTaskStore(db, ownership, detailed);
327
+ transaction(db, () => {
328
+ const rows = db.prepare("SELECT * FROM tasks LIMIT 33").all();
329
+ if (rows.length > MAX_TASKS)
330
+ throw new Error("Task store exceeds task capacity");
331
+ for (const row of rows) {
332
+ const parsed = rowSchema.parse(row);
333
+ decodeTask(parsed, detailed);
334
+ }
335
+ db.prepare("DELETE FROM tasks WHERE expires <= ?").run(Date.now());
336
+ db.prepare("UPDATE tasks SET status = 'completed', result = ?, updated = max(updated, ?) WHERE status = 'working'").run(JSON.stringify(interruptedResult), Date.now());
337
+ });
338
+ return store;
339
+ }
340
+ catch (error) {
341
+ try {
342
+ database?.close();
343
+ }
344
+ finally {
345
+ ownership.close();
346
+ }
347
+ throw error;
348
+ }
349
+ }
@@ -0,0 +1,3 @@
1
+ import type { Check, Command, ProcessResult, ToolEvidence, ToolSpec } from "./types.js";
2
+ export declare function toolsFor(root: string, check: Check): Promise<ToolSpec[]>;
3
+ export declare function identifyTool(root: string, tool: ToolSpec, execute: (command: Command) => Promise<ProcessResult | undefined>): Promise<ToolEvidence>;
@@ -0,0 +1,260 @@
1
+ import { externalInvocationSchema } from "./external-adapter.js";
2
+ import { clangVersion } from "./clang-protocol.js";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { readProjectFile } from "./inventory.js";
6
+ import { localTool } from "./local-tool.js";
7
+ const packages = {
8
+ "javascript.typescript": "typescript",
9
+ "javascript.typescript-build": "typescript",
10
+ "javascript.vue-tsc": "vue-tsc",
11
+ "javascript.vue-router": "vue-router",
12
+ "javascript.eslint": "eslint",
13
+ "javascript.vitest": "vitest",
14
+ "javascript.jest": "jest",
15
+ "javascript.playwright": "playwright",
16
+ };
17
+ export async function toolsFor(root, check) {
18
+ const command = (name, executable, args) => ({
19
+ name,
20
+ source: "version-command",
21
+ command: {
22
+ executable,
23
+ args,
24
+ cwd: check.project,
25
+ ...(check.commands[0]?.env ? { env: check.commands[0].env } : {}),
26
+ },
27
+ });
28
+ if (check.external) {
29
+ const result = [{ name: "node", source: "engine-runtime" }];
30
+ if (check.commands[0]) {
31
+ const invocation = externalInvocationSchema.parse(JSON.parse(check.commands[0].args[2]));
32
+ result.push(command(invocation.identity.id, process.execPath, [
33
+ fileURLToPath(new URL("./external-runner.js", import.meta.url)),
34
+ "--version",
35
+ JSON.stringify(invocation.reference),
36
+ ]));
37
+ if (invocation.runtime === "python3")
38
+ result.push(command("python", "python3", ["-I", "--version"]));
39
+ if (invocation.runtime === "php")
40
+ result.push(command("php", "php", ["-n", "--version"]));
41
+ }
42
+ return result;
43
+ }
44
+ if (check.adapter === "javascript") {
45
+ const tools = [{ name: "node", source: "engine-runtime" }];
46
+ if (check.id === "javascript.nuxt-runtime" && check.commands[0]) {
47
+ const metadata = JSON.parse(check.commands[0].args[2]);
48
+ tools.push(...Object.entries(metadata).map(([name, path]) => ({
49
+ name,
50
+ path,
51
+ source: "package-metadata",
52
+ })));
53
+ }
54
+ if (check.id === "javascript.vue-router")
55
+ tools.push({
56
+ name: "vue",
57
+ source: "package-metadata",
58
+ path: check.commands[0]?.args[2] ?? null,
59
+ });
60
+ const name = packages[check.id];
61
+ if (name)
62
+ tools.push({
63
+ name,
64
+ source: "package-metadata",
65
+ path: (await localTool(root, check.project, `${name}/package.json`)) ??
66
+ null,
67
+ });
68
+ return tools;
69
+ }
70
+ if (check.adapter === "python") {
71
+ const tools = [command("python", "python3", ["--version"])];
72
+ if (check.id === "python.pytest" || check.id === "python.mypy") {
73
+ const name = check.id.slice("python.".length);
74
+ tools.push(command(name, "python3", [
75
+ "-c",
76
+ `from importlib.metadata import version; print(version(${JSON.stringify(name)}))`,
77
+ ]));
78
+ }
79
+ if (check.id === "python.ruff")
80
+ tools.push(command("ruff", "ruff", ["--version"]));
81
+ if (check.id === "python.fastapi-routes")
82
+ for (const name of ["fastapi", "starlette"])
83
+ tools.push(command(name, "python3", [
84
+ "-c",
85
+ `from importlib.metadata import version; print(version(${JSON.stringify(name)}))`,
86
+ ]));
87
+ if (check.id === "python.django-routes")
88
+ tools.push(command("django", "python3", [
89
+ "-c",
90
+ "from importlib.metadata import version; print(version('Django'))",
91
+ ]));
92
+ return tools;
93
+ }
94
+ if (check.adapter === "go")
95
+ return [
96
+ command("go", "go", ["version"]),
97
+ ...(check.id === "go.golangci-lint"
98
+ ? [
99
+ { name: "node", source: "engine-runtime" },
100
+ command("golangci-lint", "golangci-lint", ["version", "--short"]),
101
+ ]
102
+ : []),
103
+ ...(check.id === "go.staticcheck"
104
+ ? [command("staticcheck", "staticcheck", ["-version"])]
105
+ : []),
106
+ ];
107
+ if (check.adapter === "rust")
108
+ return [
109
+ { name: "node", source: "engine-runtime" },
110
+ command("rustc", "rustc", ["--version"]),
111
+ command("cargo", "cargo", ["--version"]),
112
+ ];
113
+ if (check.adapter === "cpp") {
114
+ const tools = [{ name: "node", source: "engine-runtime" }];
115
+ if (check.commands[0]?.args[2]) {
116
+ const invocation = JSON.parse(check.commands[0].args[2]);
117
+ for (const name of new Set(invocation.units.map((unit) => unit.compiler)))
118
+ tools.push(command(name, name, ["--no-default-config", "--version"]));
119
+ }
120
+ return tools;
121
+ }
122
+ if (check.adapter === "swift")
123
+ return [command("swift", "swiftc", ["--version"])];
124
+ if (check.adapter === "jvm")
125
+ return [
126
+ { name: "node", source: "engine-runtime" },
127
+ command("java", process.execPath, [
128
+ fileURLToPath(new URL("./java-runner.js", import.meta.url)),
129
+ "--version",
130
+ ]),
131
+ ];
132
+ if (check.adapter === "ruby")
133
+ return [command("ruby", "ruby", ["--disable-gems", "--version"])];
134
+ if (check.id === "infrastructure.actionlint")
135
+ return [
136
+ { name: "node", source: "engine-runtime" },
137
+ command("actionlint", process.execPath, [
138
+ fileURLToPath(new URL("./actionlint-runner.js", import.meta.url)),
139
+ "--version",
140
+ ]),
141
+ ];
142
+ if (check.adapter === "dotnet")
143
+ return [
144
+ { name: "node", source: "engine-runtime" },
145
+ command("dotnet", process.execPath, [
146
+ fileURLToPath(new URL("./dotnet-runner.js", import.meta.url)),
147
+ "--version",
148
+ ]),
149
+ ];
150
+ if (check.adapter === "php") {
151
+ const tools = [command("php", "php", ["-n", "--version"])];
152
+ if (check.id === "php.laravel-runtime" && check.commands[0]?.args[2])
153
+ tools.push(command("laravel", "php", [
154
+ "-r",
155
+ "require $argv[1]; echo Illuminate\\Foundation\\Application::VERSION;",
156
+ check.commands[0].args[2],
157
+ ]));
158
+ if (check.id === "php.pint" && check.commands[0]?.args[3])
159
+ tools.push(command("pint", "php", [
160
+ check.commands[0].args[3],
161
+ "--version",
162
+ "--no-ansi",
163
+ ]));
164
+ if (["php.phpstan", "php.phpunit", "php.pest"].includes(check.id) &&
165
+ check.commands[0]?.args[0])
166
+ tools.push(command(check.id.slice("php.".length), "php", [
167
+ check.commands[0].args[0],
168
+ ...(check.id === "php.pest" ? ["--colors=never"] : []),
169
+ "--version",
170
+ ]));
171
+ return tools;
172
+ }
173
+ return [];
174
+ }
175
+ const versionPattern = /^\d+\.\d+\.\d+(?:[-+a-zA-Z0-9.]+)?$/;
176
+ export async function identifyTool(root, tool, execute) {
177
+ const result = {
178
+ name: tool.name,
179
+ source: tool.source,
180
+ version: null,
181
+ status: "inconclusive",
182
+ };
183
+ if (tool.source === "engine-runtime")
184
+ return { ...result, version: process.versions.node, status: "identified" };
185
+ if (tool.source === "package-metadata") {
186
+ if (!tool.path)
187
+ return { ...result, status: "unavailable" };
188
+ result.path = tool.path;
189
+ try {
190
+ const value = JSON.parse(await readProjectFile(root, path.relative(root, tool.path)));
191
+ if (typeof value === "object" &&
192
+ value !== null &&
193
+ "name" in value &&
194
+ value.name === tool.name &&
195
+ "version" in value &&
196
+ typeof value.version === "string" &&
197
+ value.version.length <= 128 &&
198
+ versionPattern.test(value.version))
199
+ return { ...result, version: value.version, status: "identified" };
200
+ }
201
+ catch {
202
+ return result;
203
+ }
204
+ return result;
205
+ }
206
+ const execution = await execute(tool.command);
207
+ if (!execution)
208
+ return result;
209
+ result.process = execution;
210
+ if (execution.errorCode === "ENOENT")
211
+ return { ...result, status: "unavailable" };
212
+ if (execution.exitCode !== 0 ||
213
+ execution.signal ||
214
+ execution.errorCode ||
215
+ execution.cancelled ||
216
+ execution.timedOut ||
217
+ execution.truncated)
218
+ return result;
219
+ const output = execution.stdout.trim();
220
+ const version = tool.name === "java"
221
+ ? /^openjdk (\d+\.\d+\.\d+) \d{4}-\d{2}-\d{2} LTS\nOpenJDK Runtime Environment [^\n]+\nOpenJDK 64-Bit Server VM [^\n]+$/.exec(output)?.[1]
222
+ : tool.name === "clang" || tool.name === "clang++"
223
+ ? clangVersion(output)
224
+ : tool.name === "swift"
225
+ ? /^(?:Apple )?Swift version (\d+\.\d+(?:\.\d+)?) \([^\r\n]+\)\r?\nTarget: [a-zA-Z0-9_.-]+$/.exec(output)?.[1]
226
+ : tool.name === "ruby"
227
+ ? /^ruby (\d+\.\d+\.\d+)(?:p\d+)? /.exec(output)?.[1]
228
+ : tool.name === "cargo" || tool.name === "rustc"
229
+ ? output.startsWith(`${tool.name} `)
230
+ ? /^(?:cargo|rustc) (\S+) \([a-f0-9]+ [0-9-]+\)$/.exec(output)?.[1]
231
+ : undefined
232
+ : tool.name === "pint"
233
+ ? /^Pint (\S+)$/.exec(output)?.[1]
234
+ : tool.name === "pest"
235
+ ? /^Pest Testing Framework (\S+)\.$/.exec(output)?.[1]
236
+ : tool.name === "phpunit"
237
+ ? /^PHPUnit (\S+) by .+$/.exec(output)?.[1]
238
+ : tool.name === "phpstan"
239
+ ? /^PHPStan - PHP Static Analysis Tool (\S+)$/.exec(output)?.[1]
240
+ : tool.name === "staticcheck"
241
+ ? /^staticcheck \S+ \((\d+\.\d+\.\d+)\)$/.exec(output)?.[1]
242
+ : tool.name === "go"
243
+ ? /^go version go(\S+) \S+$/.exec(output)?.[1]
244
+ : tool.name === "php"
245
+ ? /^PHP (\S+) /.exec(output)?.[1]
246
+ : tool.name === "python"
247
+ ? /^Python (\S+)$/.exec(output)?.[1]
248
+ : tool.name === "ruff"
249
+ ? /^ruff (\S+)$/.exec(output)?.[1]
250
+ : output;
251
+ if (version &&
252
+ (tool.name === "swift"
253
+ ? /^\d+\.\d+(?:\.\d+)?$/.test(version)
254
+ : versionPattern.test(version)) &&
255
+ (!execution.stderr.trim() ||
256
+ (tool.name === "swift" &&
257
+ /^swift-driver version: \d+\.\d+\.\d+$/.test(execution.stderr.trim()))))
258
+ return { ...result, version, status: "identified" };
259
+ return result;
260
+ }