oc-pilot 0.5.1 → 0.88.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 (481) hide show
  1. package/CHANGELOG.md +683 -0
  2. package/CONTRIBUTING.md +4 -2
  3. package/README.md +588 -20
  4. package/README.ru.md +572 -10
  5. package/dist/cli.js +185 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cli.test.js +1211 -1
  8. package/dist/cli.test.js.map +1 -1
  9. package/dist/commands/anti-regression.js +97 -0
  10. package/dist/commands/anti-regression.js.map +1 -0
  11. package/dist/commands/arch-diff.js +80 -0
  12. package/dist/commands/arch-diff.js.map +1 -0
  13. package/dist/commands/authz.js +167 -0
  14. package/dist/commands/authz.js.map +1 -0
  15. package/dist/commands/autopsy.js +64 -0
  16. package/dist/commands/autopsy.js.map +1 -0
  17. package/dist/commands/bankruptcy.js +60 -0
  18. package/dist/commands/bankruptcy.js.map +1 -0
  19. package/dist/commands/battle.js +119 -0
  20. package/dist/commands/battle.js.map +1 -0
  21. package/dist/commands/boundaries.js +114 -0
  22. package/dist/commands/boundaries.js.map +1 -0
  23. package/dist/commands/bounty.js +56 -0
  24. package/dist/commands/bounty.js.map +1 -0
  25. package/dist/commands/brag.js +62 -0
  26. package/dist/commands/brag.js.map +1 -0
  27. package/dist/commands/cache.js +62 -0
  28. package/dist/commands/cache.js.map +1 -0
  29. package/dist/commands/cinema.js +61 -0
  30. package/dist/commands/cinema.js.map +1 -0
  31. package/dist/commands/cleanup.js +110 -0
  32. package/dist/commands/cleanup.js.map +1 -0
  33. package/dist/commands/commit.js +177 -0
  34. package/dist/commands/commit.js.map +1 -0
  35. package/dist/commands/commit.test.js +160 -0
  36. package/dist/commands/commit.test.js.map +1 -0
  37. package/dist/commands/compliance.js +121 -0
  38. package/dist/commands/compliance.js.map +1 -0
  39. package/dist/commands/contract-drift.js +97 -0
  40. package/dist/commands/contract-drift.js.map +1 -0
  41. package/dist/commands/coverage.js +137 -0
  42. package/dist/commands/coverage.js.map +1 -0
  43. package/dist/commands/day.js +116 -0
  44. package/dist/commands/day.js.map +1 -0
  45. package/dist/commands/day.test.js +45 -0
  46. package/dist/commands/day.test.js.map +1 -0
  47. package/dist/commands/deps.js +245 -0
  48. package/dist/commands/deps.js.map +1 -0
  49. package/dist/commands/docgen.js +4 -2
  50. package/dist/commands/docgen.js.map +1 -1
  51. package/dist/commands/doctor.js +146 -2
  52. package/dist/commands/doctor.js.map +1 -1
  53. package/dist/commands/doctor.test.js +69 -0
  54. package/dist/commands/doctor.test.js.map +1 -0
  55. package/dist/commands/echo.js +78 -0
  56. package/dist/commands/echo.js.map +1 -0
  57. package/dist/commands/ecosystem-verify.js +113 -0
  58. package/dist/commands/ecosystem-verify.js.map +1 -0
  59. package/dist/commands/env-sync.js +102 -0
  60. package/dist/commands/env-sync.js.map +1 -0
  61. package/dist/commands/explain.js +115 -0
  62. package/dist/commands/explain.js.map +1 -0
  63. package/dist/commands/fix.js +145 -3
  64. package/dist/commands/fix.js.map +1 -1
  65. package/dist/commands/flaky.js +111 -0
  66. package/dist/commands/flaky.js.map +1 -0
  67. package/dist/commands/fuzz.js +119 -0
  68. package/dist/commands/fuzz.js.map +1 -0
  69. package/dist/commands/hivemind.js +124 -0
  70. package/dist/commands/hivemind.js.map +1 -0
  71. package/dist/commands/impact.js +73 -0
  72. package/dist/commands/impact.js.map +1 -0
  73. package/dist/commands/init.js +73 -11
  74. package/dist/commands/init.js.map +1 -1
  75. package/dist/commands/init.test.js +138 -0
  76. package/dist/commands/init.test.js.map +1 -0
  77. package/dist/commands/install-hook.js +161 -0
  78. package/dist/commands/install-hook.js.map +1 -0
  79. package/dist/commands/install-hook.test.js +100 -0
  80. package/dist/commands/install-hook.test.js.map +1 -0
  81. package/dist/commands/invariants.js +103 -0
  82. package/dist/commands/invariants.js.map +1 -0
  83. package/dist/commands/mock-leak.js +97 -0
  84. package/dist/commands/mock-leak.js.map +1 -0
  85. package/dist/commands/mutate.js +99 -0
  86. package/dist/commands/mutate.js.map +1 -0
  87. package/dist/commands/pack.js +144 -0
  88. package/dist/commands/pack.js.map +1 -0
  89. package/dist/commands/preflight.js +137 -0
  90. package/dist/commands/preflight.js.map +1 -0
  91. package/dist/commands/query.js +57 -0
  92. package/dist/commands/query.js.map +1 -0
  93. package/dist/commands/ready.js +152 -0
  94. package/dist/commands/ready.js.map +1 -0
  95. package/dist/commands/receipt.js +289 -0
  96. package/dist/commands/receipt.js.map +1 -0
  97. package/dist/commands/replay.js +112 -0
  98. package/dist/commands/replay.js.map +1 -0
  99. package/dist/commands/replay.test.js +125 -0
  100. package/dist/commands/replay.test.js.map +1 -0
  101. package/dist/commands/restore.js +54 -0
  102. package/dist/commands/restore.js.map +1 -0
  103. package/dist/commands/review.js +263 -4
  104. package/dist/commands/review.js.map +1 -1
  105. package/dist/commands/review.test.js +160 -0
  106. package/dist/commands/review.test.js.map +1 -0
  107. package/dist/commands/reviewers.js +86 -0
  108. package/dist/commands/reviewers.js.map +1 -0
  109. package/dist/commands/roast.js +102 -0
  110. package/dist/commands/roast.js.map +1 -0
  111. package/dist/commands/sandbox.js +82 -0
  112. package/dist/commands/sandbox.js.map +1 -0
  113. package/dist/commands/sast.js +162 -0
  114. package/dist/commands/sast.js.map +1 -0
  115. package/dist/commands/scan.js +47 -12
  116. package/dist/commands/scan.js.map +1 -1
  117. package/dist/commands/slop.js +145 -0
  118. package/dist/commands/slop.js.map +1 -0
  119. package/dist/commands/slop.test.js +135 -0
  120. package/dist/commands/slop.test.js.map +1 -0
  121. package/dist/commands/stats.js +61 -0
  122. package/dist/commands/stats.js.map +1 -0
  123. package/dist/commands/test-gen.js +372 -0
  124. package/dist/commands/test-gen.js.map +1 -0
  125. package/dist/commands/test-guard.js +89 -0
  126. package/dist/commands/test-guard.js.map +1 -0
  127. package/dist/commands/test-plan.js +133 -0
  128. package/dist/commands/test-plan.js.map +1 -0
  129. package/dist/commands/zoom-symbol.js +52 -0
  130. package/dist/commands/zoom-symbol.js.map +1 -0
  131. package/dist/core/agent-rules.js +35 -0
  132. package/dist/core/agent-rules.js.map +1 -0
  133. package/dist/core/anthropic.test.js +116 -0
  134. package/dist/core/anthropic.test.js.map +1 -0
  135. package/dist/core/anti-regression.js +161 -0
  136. package/dist/core/anti-regression.js.map +1 -0
  137. package/dist/core/anti-regression.test.js +154 -0
  138. package/dist/core/anti-regression.test.js.map +1 -0
  139. package/dist/core/aql.js +220 -0
  140. package/dist/core/aql.js.map +1 -0
  141. package/dist/core/aql.test.js +64 -0
  142. package/dist/core/aql.test.js.map +1 -0
  143. package/dist/core/arch-diff.js +122 -0
  144. package/dist/core/arch-diff.js.map +1 -0
  145. package/dist/core/arch-diff.test.js +93 -0
  146. package/dist/core/arch-diff.test.js.map +1 -0
  147. package/dist/core/authz.js +292 -0
  148. package/dist/core/authz.js.map +1 -0
  149. package/dist/core/authz.test.js +115 -0
  150. package/dist/core/authz.test.js.map +1 -0
  151. package/dist/core/autopsy.js +230 -0
  152. package/dist/core/autopsy.js.map +1 -0
  153. package/dist/core/autopsy.test.js +106 -0
  154. package/dist/core/autopsy.test.js.map +1 -0
  155. package/dist/core/bankruptcy.js +232 -0
  156. package/dist/core/bankruptcy.js.map +1 -0
  157. package/dist/core/bankruptcy.test.js +91 -0
  158. package/dist/core/bankruptcy.test.js.map +1 -0
  159. package/dist/core/baseline.js +131 -0
  160. package/dist/core/baseline.js.map +1 -0
  161. package/dist/core/baseline.test.js +91 -0
  162. package/dist/core/baseline.test.js.map +1 -0
  163. package/dist/core/battle.js +213 -0
  164. package/dist/core/battle.js.map +1 -0
  165. package/dist/core/battle.test.js +116 -0
  166. package/dist/core/battle.test.js.map +1 -0
  167. package/dist/core/boundaries.js +200 -0
  168. package/dist/core/boundaries.js.map +1 -0
  169. package/dist/core/boundaries.test.js +62 -0
  170. package/dist/core/boundaries.test.js.map +1 -0
  171. package/dist/core/bounty.js +128 -0
  172. package/dist/core/bounty.js.map +1 -0
  173. package/dist/core/bounty.test.js +82 -0
  174. package/dist/core/bounty.test.js.map +1 -0
  175. package/dist/core/brag.js +174 -0
  176. package/dist/core/brag.js.map +1 -0
  177. package/dist/core/brag.test.js +43 -0
  178. package/dist/core/brag.test.js.map +1 -0
  179. package/dist/core/cache.js +189 -0
  180. package/dist/core/cache.js.map +1 -0
  181. package/dist/core/cache.test.js +159 -0
  182. package/dist/core/cache.test.js.map +1 -0
  183. package/dist/core/cinema.js +242 -0
  184. package/dist/core/cinema.js.map +1 -0
  185. package/dist/core/cinema.test.js +73 -0
  186. package/dist/core/cinema.test.js.map +1 -0
  187. package/dist/core/cleanup.js +107 -0
  188. package/dist/core/cleanup.js.map +1 -0
  189. package/dist/core/compliance.js +193 -0
  190. package/dist/core/compliance.js.map +1 -0
  191. package/dist/core/compliance.test.js +123 -0
  192. package/dist/core/compliance.test.js.map +1 -0
  193. package/dist/core/config.js +78 -0
  194. package/dist/core/config.js.map +1 -1
  195. package/dist/core/config.test.js +66 -0
  196. package/dist/core/config.test.js.map +1 -1
  197. package/dist/core/contract-drift.js +296 -0
  198. package/dist/core/contract-drift.js.map +1 -0
  199. package/dist/core/contract-drift.test.js +166 -0
  200. package/dist/core/contract-drift.test.js.map +1 -0
  201. package/dist/core/cost.test.js +103 -0
  202. package/dist/core/cost.test.js.map +1 -0
  203. package/dist/core/coverage.js +335 -0
  204. package/dist/core/coverage.js.map +1 -0
  205. package/dist/core/coverage.test.js +190 -0
  206. package/dist/core/coverage.test.js.map +1 -0
  207. package/dist/core/deps-fix.js +169 -0
  208. package/dist/core/deps-fix.js.map +1 -0
  209. package/dist/core/deps-fix.test.js +176 -0
  210. package/dist/core/deps-fix.test.js.map +1 -0
  211. package/dist/core/deps-health.js +353 -0
  212. package/dist/core/deps-health.js.map +1 -0
  213. package/dist/core/deps-health.test.js +230 -0
  214. package/dist/core/deps-health.test.js.map +1 -0
  215. package/dist/core/deps-why.js +260 -0
  216. package/dist/core/deps-why.js.map +1 -0
  217. package/dist/core/deps-why.test.js +175 -0
  218. package/dist/core/deps-why.test.js.map +1 -0
  219. package/dist/core/deps.js +179 -0
  220. package/dist/core/deps.js.map +1 -0
  221. package/dist/core/deps.test.js +88 -0
  222. package/dist/core/deps.test.js.map +1 -0
  223. package/dist/core/diff-source.js +4 -2
  224. package/dist/core/diff-source.js.map +1 -1
  225. package/dist/core/echo.js +151 -0
  226. package/dist/core/echo.js.map +1 -0
  227. package/dist/core/echo.test.js +90 -0
  228. package/dist/core/echo.test.js.map +1 -0
  229. package/dist/core/ecosystem-verify.js +331 -0
  230. package/dist/core/ecosystem-verify.js.map +1 -0
  231. package/dist/core/ecosystem-verify.test.js +158 -0
  232. package/dist/core/ecosystem-verify.test.js.map +1 -0
  233. package/dist/core/engine-preset.test.js +60 -0
  234. package/dist/core/engine-preset.test.js.map +1 -0
  235. package/dist/core/engine.js +278 -32
  236. package/dist/core/engine.js.map +1 -1
  237. package/dist/core/engine.test.js +63 -0
  238. package/dist/core/engine.test.js.map +1 -1
  239. package/dist/core/env-sync.js +122 -0
  240. package/dist/core/env-sync.js.map +1 -0
  241. package/dist/core/env-sync.test.js +99 -0
  242. package/dist/core/env-sync.test.js.map +1 -0
  243. package/dist/core/file-context.js +4 -2
  244. package/dist/core/file-context.js.map +1 -1
  245. package/dist/core/findings-baseline.js +60 -0
  246. package/dist/core/findings-baseline.js.map +1 -0
  247. package/dist/core/findings-baseline.test.js +77 -0
  248. package/dist/core/findings-baseline.test.js.map +1 -0
  249. package/dist/core/findings.js +68 -21
  250. package/dist/core/findings.js.map +1 -1
  251. package/dist/core/flaky.js +137 -0
  252. package/dist/core/flaky.js.map +1 -0
  253. package/dist/core/flaky.test.js +97 -0
  254. package/dist/core/flaky.test.js.map +1 -0
  255. package/dist/core/format.js +5 -3
  256. package/dist/core/format.js.map +1 -1
  257. package/dist/core/fuzz.js +306 -0
  258. package/dist/core/fuzz.js.map +1 -0
  259. package/dist/core/fuzz.test.js +121 -0
  260. package/dist/core/fuzz.test.js.map +1 -0
  261. package/dist/core/git.js +22 -0
  262. package/dist/core/git.js.map +1 -1
  263. package/dist/core/gitlab.js +176 -0
  264. package/dist/core/gitlab.js.map +1 -0
  265. package/dist/core/gitlab.test.js +199 -0
  266. package/dist/core/gitlab.test.js.map +1 -0
  267. package/dist/core/hivemind.js +250 -0
  268. package/dist/core/hivemind.js.map +1 -0
  269. package/dist/core/hivemind.test.js +79 -0
  270. package/dist/core/hivemind.test.js.map +1 -0
  271. package/dist/core/hologram.js +463 -0
  272. package/dist/core/hologram.js.map +1 -0
  273. package/dist/core/hologram.test.js +124 -0
  274. package/dist/core/hologram.test.js.map +1 -0
  275. package/dist/core/impact.js +240 -0
  276. package/dist/core/impact.js.map +1 -0
  277. package/dist/core/impact.test.js +105 -0
  278. package/dist/core/impact.test.js.map +1 -0
  279. package/dist/core/import-graph.js +170 -0
  280. package/dist/core/import-graph.js.map +1 -0
  281. package/dist/core/import-graph.test.js +52 -0
  282. package/dist/core/import-graph.test.js.map +1 -0
  283. package/dist/core/injection.js +55 -0
  284. package/dist/core/injection.js.map +1 -0
  285. package/dist/core/injection.test.js +45 -0
  286. package/dist/core/injection.test.js.map +1 -0
  287. package/dist/core/invariants.js +286 -0
  288. package/dist/core/invariants.js.map +1 -0
  289. package/dist/core/invariants.test.js +87 -0
  290. package/dist/core/invariants.test.js.map +1 -0
  291. package/dist/core/last-review.js +92 -0
  292. package/dist/core/last-review.js.map +1 -0
  293. package/dist/core/last-review.test.js +97 -0
  294. package/dist/core/last-review.test.js.map +1 -0
  295. package/dist/core/lockfile-support.test.js +112 -0
  296. package/dist/core/lockfile-support.test.js.map +1 -0
  297. package/dist/core/lockfile.js +284 -0
  298. package/dist/core/lockfile.js.map +1 -0
  299. package/dist/core/lockfile.test.js +203 -0
  300. package/dist/core/lockfile.test.js.map +1 -0
  301. package/dist/core/mock-leak.js +120 -0
  302. package/dist/core/mock-leak.js.map +1 -0
  303. package/dist/core/mock-leak.test.js +79 -0
  304. package/dist/core/mock-leak.test.js.map +1 -0
  305. package/dist/core/mutation.js +206 -0
  306. package/dist/core/mutation.js.map +1 -0
  307. package/dist/core/mutation.test.js +104 -0
  308. package/dist/core/mutation.test.js.map +1 -0
  309. package/dist/core/opencode.js +15 -2
  310. package/dist/core/opencode.js.map +1 -1
  311. package/dist/core/opencode.test.js +10 -1
  312. package/dist/core/opencode.test.js.map +1 -1
  313. package/dist/core/options.js +51 -3
  314. package/dist/core/options.js.map +1 -1
  315. package/dist/core/osv.js +202 -0
  316. package/dist/core/osv.js.map +1 -0
  317. package/dist/core/osv.test.js +173 -0
  318. package/dist/core/osv.test.js.map +1 -0
  319. package/dist/core/output-format.test.js +102 -0
  320. package/dist/core/output-format.test.js.map +1 -0
  321. package/dist/core/pack.js +212 -0
  322. package/dist/core/pack.js.map +1 -0
  323. package/dist/core/pack.test.js +129 -0
  324. package/dist/core/pack.test.js.map +1 -0
  325. package/dist/core/phantom.js +155 -0
  326. package/dist/core/phantom.js.map +1 -0
  327. package/dist/core/phantom.test.js +58 -0
  328. package/dist/core/phantom.test.js.map +1 -0
  329. package/dist/core/pr-comment.js +116 -0
  330. package/dist/core/pr-comment.js.map +1 -0
  331. package/dist/core/pr-comment.test.js +53 -0
  332. package/dist/core/pr-comment.test.js.map +1 -0
  333. package/dist/core/pr-review.js +160 -0
  334. package/dist/core/pr-review.js.map +1 -0
  335. package/dist/core/pr-review.test.js +62 -0
  336. package/dist/core/pr-review.test.js.map +1 -0
  337. package/dist/core/preflight-run.js +106 -0
  338. package/dist/core/preflight-run.js.map +1 -0
  339. package/dist/core/preflight.js +173 -0
  340. package/dist/core/preflight.js.map +1 -0
  341. package/dist/core/preflight.test.js +187 -0
  342. package/dist/core/preflight.test.js.map +1 -0
  343. package/dist/core/presets.js +408 -0
  344. package/dist/core/presets.js.map +1 -0
  345. package/dist/core/presets.test.js +79 -0
  346. package/dist/core/presets.test.js.map +1 -0
  347. package/dist/core/prompt.golden.test.js +74 -0
  348. package/dist/core/prompt.golden.test.js.map +1 -0
  349. package/dist/core/prompt.js +43 -4
  350. package/dist/core/prompt.js.map +1 -1
  351. package/dist/core/prompt.test.js +16 -0
  352. package/dist/core/prompt.test.js.map +1 -1
  353. package/dist/core/readiness-fix.js +111 -0
  354. package/dist/core/readiness-fix.js.map +1 -0
  355. package/dist/core/readiness-fix.test.js +99 -0
  356. package/dist/core/readiness-fix.test.js.map +1 -0
  357. package/dist/core/readiness.js +189 -0
  358. package/dist/core/readiness.js.map +1 -0
  359. package/dist/core/readiness.test.js +199 -0
  360. package/dist/core/readiness.test.js.map +1 -0
  361. package/dist/core/receipt.js +193 -0
  362. package/dist/core/receipt.js.map +1 -0
  363. package/dist/core/receipt.test.js +193 -0
  364. package/dist/core/receipt.test.js.map +1 -0
  365. package/dist/core/reviewers.js +179 -0
  366. package/dist/core/reviewers.js.map +1 -0
  367. package/dist/core/reviewers.test.js +105 -0
  368. package/dist/core/reviewers.test.js.map +1 -0
  369. package/dist/core/roast.js +27 -0
  370. package/dist/core/roast.js.map +1 -0
  371. package/dist/core/roast.test.js +19 -0
  372. package/dist/core/roast.test.js.map +1 -0
  373. package/dist/core/router.js +8 -0
  374. package/dist/core/router.js.map +1 -0
  375. package/dist/core/router.test.js +157 -0
  376. package/dist/core/router.test.js.map +1 -0
  377. package/dist/core/rules.js +2 -2
  378. package/dist/core/rules.js.map +1 -1
  379. package/dist/core/run.js +322 -20
  380. package/dist/core/run.js.map +1 -1
  381. package/dist/core/sarif.js +125 -0
  382. package/dist/core/sarif.js.map +1 -0
  383. package/dist/core/sarif.test.js +60 -0
  384. package/dist/core/sarif.test.js.map +1 -0
  385. package/dist/core/sast.js +324 -0
  386. package/dist/core/sast.js.map +1 -0
  387. package/dist/core/sast.test.js +105 -0
  388. package/dist/core/sast.test.js.map +1 -0
  389. package/dist/core/sbom.js +121 -0
  390. package/dist/core/sbom.js.map +1 -0
  391. package/dist/core/sbom.test.js +81 -0
  392. package/dist/core/sbom.test.js.map +1 -0
  393. package/dist/core/second-opinion.js +88 -0
  394. package/dist/core/second-opinion.js.map +1 -0
  395. package/dist/core/second-opinion.test.js +89 -0
  396. package/dist/core/second-opinion.test.js.map +1 -0
  397. package/dist/core/secrets.js +120 -5
  398. package/dist/core/secrets.js.map +1 -1
  399. package/dist/core/secrets.test.js +74 -1
  400. package/dist/core/secrets.test.js.map +1 -1
  401. package/dist/core/shadow.js +94 -0
  402. package/dist/core/shadow.js.map +1 -0
  403. package/dist/core/shadow.test.js +50 -0
  404. package/dist/core/shadow.test.js.map +1 -0
  405. package/dist/core/slicing.js +110 -0
  406. package/dist/core/slicing.js.map +1 -0
  407. package/dist/core/slicing.test.js +287 -0
  408. package/dist/core/slicing.test.js.map +1 -0
  409. package/dist/core/slop/churn.js +49 -0
  410. package/dist/core/slop/churn.js.map +1 -0
  411. package/dist/core/slop/collect.js +186 -0
  412. package/dist/core/slop/collect.js.map +1 -0
  413. package/dist/core/slop/dead-exports.js +101 -0
  414. package/dist/core/slop/dead-exports.js.map +1 -0
  415. package/dist/core/slop/duplicates.js +155 -0
  416. package/dist/core/slop/duplicates.js.map +1 -0
  417. package/dist/core/slop/index.js +72 -0
  418. package/dist/core/slop/index.js.map +1 -0
  419. package/dist/core/slop/model.js +2 -0
  420. package/dist/core/slop/model.js.map +1 -0
  421. package/dist/core/slop/score.js +185 -0
  422. package/dist/core/slop/score.js.map +1 -0
  423. package/dist/core/slop/slop.test.js +160 -0
  424. package/dist/core/slop/slop.test.js.map +1 -0
  425. package/dist/core/slop/source.js +129 -0
  426. package/dist/core/slop/source.js.map +1 -0
  427. package/dist/core/slop/test-debt.js +178 -0
  428. package/dist/core/slop/test-debt.js.map +1 -0
  429. package/dist/core/slop/test-debt.test.js +114 -0
  430. package/dist/core/slop/test-debt.test.js.map +1 -0
  431. package/dist/core/slop/todos.js +117 -0
  432. package/dist/core/slop/todos.js.map +1 -0
  433. package/dist/core/snapshot.js +214 -0
  434. package/dist/core/snapshot.js.map +1 -0
  435. package/dist/core/snapshot.test.js +101 -0
  436. package/dist/core/snapshot.test.js.map +1 -0
  437. package/dist/core/stats.js +119 -0
  438. package/dist/core/stats.js.map +1 -0
  439. package/dist/core/stats.test.js +86 -0
  440. package/dist/core/stats.test.js.map +1 -0
  441. package/dist/core/suppressions.js +96 -0
  442. package/dist/core/suppressions.js.map +1 -0
  443. package/dist/core/suppressions.test.js +90 -0
  444. package/dist/core/suppressions.test.js.map +1 -0
  445. package/dist/core/test-delta.js +53 -0
  446. package/dist/core/test-delta.js.map +1 -0
  447. package/dist/core/test-delta.test.js +41 -0
  448. package/dist/core/test-delta.test.js.map +1 -0
  449. package/dist/core/test-plan.js +212 -0
  450. package/dist/core/test-plan.js.map +1 -0
  451. package/dist/core/test-plan.test.js +94 -0
  452. package/dist/core/test-plan.test.js.map +1 -0
  453. package/dist/core/test-weakening.js +191 -0
  454. package/dist/core/test-weakening.js.map +1 -0
  455. package/dist/core/test-weakening.test.js +114 -0
  456. package/dist/core/test-weakening.test.js.map +1 -0
  457. package/dist/core/tree-scan.js +77 -0
  458. package/dist/core/tree-scan.js.map +1 -0
  459. package/dist/core/tree-scan.test.js +58 -0
  460. package/dist/core/tree-scan.test.js.map +1 -0
  461. package/dist/mcp-prompts.test.js +133 -0
  462. package/dist/mcp-prompts.test.js.map +1 -0
  463. package/dist/mcp.js +3303 -240
  464. package/dist/mcp.js.map +1 -1
  465. package/dist/mcp.test.js +1739 -1
  466. package/dist/mcp.test.js.map +1 -1
  467. package/dist/skills.test.js +221 -0
  468. package/dist/skills.test.js.map +1 -0
  469. package/dist/version.js +1 -1
  470. package/dist/version.js.map +1 -1
  471. package/docs/integrations.md +151 -7
  472. package/package.json +2 -1
  473. package/prompts/oc-pilot.en.md +8 -1
  474. package/prompts/oc-pilot.ru.md +8 -1
  475. package/server.json +2 -2
  476. package/skills/agent-self-review/SKILL.md +87 -0
  477. package/skills/commit-discipline/SKILL.md +86 -0
  478. package/skills/oc-pilot/SKILL.md +86 -0
  479. package/skills/secure-diff-gate/SKILL.md +101 -0
  480. package/skills/stack-review-packs/SKILL.md +76 -0
  481. package/skills/test-failure-triage/SKILL.md +88 -0
package/dist/cli.test.js CHANGED
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { delimiter, join, resolve } from "node:path";
5
- import { spawnSync } from "node:child_process";
5
+ import { spawn, spawnSync } from "node:child_process";
6
6
  import { test } from "node:test";
7
7
  const CLI = resolve("dist/cli.js");
8
8
  const AWS_KEY = ["AKIA", "IOSFODNN7EXAMPLE"].join("");
@@ -331,6 +331,28 @@ test("--fail-on critical exits 3 and exposes structured findings", () => {
331
331
  rmSync(dir, { recursive: true, force: true });
332
332
  }
333
333
  });
334
+ test("an inline oc-pilot:ignore marker removes the finding from exit code and meta", () => {
335
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-"));
336
+ try {
337
+ const diffPath = join(dir, "changes.diff");
338
+ writeFileSync(diffPath, fixtureDiff());
339
+ const lines = Array.from({ length: 12 }, (_, index) => `// line ${index + 1}`);
340
+ lines[9] = "func pay() {} // oc-pilot:ignore: nil is impossible for a legacy wallet";
341
+ writeFileSync(join(dir, "wallet.go"), `${lines.join("\n")}\n`);
342
+ const env = installFakeOpenCode(dir);
343
+ const result = runCli(["review", "--diff", diffPath, "--cwd", dir, "--json", "--fail-on", "critical"], dir, { ...env, FAKE_CRITICAL: "1" });
344
+ assert.equal(result.status, 0, result.stderr);
345
+ const payload = JSON.parse(result.stdout);
346
+ assert.equal(payload.findings.length, 0);
347
+ assert.equal(payload.meta.findings.critical, 0);
348
+ assert.equal(payload.meta.suppressed?.count, 1);
349
+ assert.equal(payload.meta.suppressed?.entries[0]?.reason, "nil is impossible for a legacy wallet");
350
+ assert.match(result.stderr, /oc-pilot:ignore: подавлено находок: 1/);
351
+ }
352
+ finally {
353
+ rmSync(dir, { recursive: true, force: true });
354
+ }
355
+ });
334
356
  test("--fail-on warning does not fail on style-only output", () => {
335
357
  const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-"));
336
358
  try {
@@ -556,4 +578,1192 @@ test("unknown commands return a usage error", () => {
556
578
  assert.equal(result.status, 2);
557
579
  assert.match(result.stderr, /Неизвестная команда/);
558
580
  });
581
+ test("test-gen applies a generated test file", () => {
582
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-"));
583
+ try {
584
+ spawnSync("git", ["init", "-q"], { cwd: dir });
585
+ writeFileSync(join(dir, "wallet.go"), "package wallet\n", "utf8");
586
+ spawnSync("git", ["add", "wallet.go"], { cwd: dir });
587
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
588
+ const patch = [
589
+ "diff --git a/src/a.test.ts b/src/a.test.ts",
590
+ "new file mode 100644",
591
+ "--- /dev/null",
592
+ "+++ b/src/a.test.ts",
593
+ "@@ -0,0 +1,2 @@",
594
+ "+test(\"a\", () => {});",
595
+ "+",
596
+ "",
597
+ ].join("\n");
598
+ const diffPath = join(dir, "changes.diff");
599
+ writeFileSync(diffPath, patch);
600
+ const fake = installFakeOpenCode(dir);
601
+ const result = runCli(["test-gen", "--diff", diffPath, "--apply", "--no-run", "--rounds", "1", "--cwd", dir, "--json"], dir, { ...fake, FAKE_PATCH_TEXT: patch });
602
+ assert.equal(result.status, 0, result.stderr);
603
+ const payload = JSON.parse(result.stdout);
604
+ assert.equal(payload.applied, true);
605
+ assert.equal(payload.status, "not-run");
606
+ assert.deepEqual(payload.files, ["src/a.test.ts"]);
607
+ assert.match(readFileSync(join(dir, "src", "a.test.ts"), "utf8"), /test\("a"/);
608
+ }
609
+ finally {
610
+ rmSync(dir, { recursive: true, force: true });
611
+ }
612
+ });
613
+ test("test-gen refuses a patch that changes production code", () => {
614
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-skip-"));
615
+ try {
616
+ spawnSync("git", ["init", "-q"], { cwd: dir });
617
+ writeFileSync(join(dir, "wallet.go"), "package wallet\n", "utf8");
618
+ spawnSync("git", ["add", "wallet.go"], { cwd: dir });
619
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
620
+ const patch = [
621
+ "diff --git a/wallet.go b/wallet.go",
622
+ "--- a/wallet.go",
623
+ "+++ b/wallet.go",
624
+ "@@ -1 +1,2 @@",
625
+ " package wallet",
626
+ "+// sneaky production change",
627
+ "",
628
+ ].join("\n");
629
+ const diffPath = join(dir, "changes.diff");
630
+ writeFileSync(diffPath, patch);
631
+ const fake = installFakeOpenCode(dir);
632
+ const result = runCli(["test-gen", "--diff", diffPath, "--apply", "--no-run", "--rounds", "1", "--cwd", dir, "--json"], dir, { ...fake, FAKE_PATCH_TEXT: patch });
633
+ assert.equal(result.status, 1, result.stderr);
634
+ const payload = JSON.parse(result.stdout);
635
+ assert.equal(payload.applied, false);
636
+ assert.equal(payload.patch, null);
637
+ assert.equal(payload.status, "fail");
638
+ assert.equal(readFileSync(join(dir, "wallet.go"), "utf8").replace(/\r\n/g, "\n"), "package wallet\n");
639
+ }
640
+ finally {
641
+ rmSync(dir, { recursive: true, force: true });
642
+ }
643
+ });
644
+ test("test-gen coverage narrows the request to uncovered lines", () => {
645
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-cov-"));
646
+ try {
647
+ spawnSync("git", ["init", "-q"], { cwd: dir });
648
+ writeFileSync(join(dir, "a.ts"), "one\ntwo\n", "utf8");
649
+ spawnSync("git", ["add", "a.ts"], { cwd: dir });
650
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
651
+ const patch = [
652
+ "diff --git a/a.ts b/a.ts",
653
+ "--- a/a.ts",
654
+ "+++ b/a.ts",
655
+ "@@ -1,2 +1,3 @@",
656
+ " one",
657
+ "+added",
658
+ " two",
659
+ "",
660
+ ].join("\n");
661
+ const diffPath = join(dir, "changes.diff");
662
+ writeFileSync(diffPath, patch);
663
+ const lcovPath = join(dir, "lcov.info");
664
+ writeFileSync(lcovPath, ["SF:a.ts", "DA:2,0", "end_of_record", ""].join("\n"));
665
+ const testPatch = [
666
+ "diff --git a/a.test.ts b/a.test.ts",
667
+ "new file mode 100644",
668
+ "--- /dev/null",
669
+ "+++ b/a.test.ts",
670
+ "@@ -0,0 +1 @@",
671
+ "+test(\"added\", () => {});",
672
+ "",
673
+ ].join("\n");
674
+ const fake = installFakeOpenCode(dir);
675
+ const result = runCli([
676
+ "test-gen",
677
+ "--diff",
678
+ diffPath,
679
+ "--coverage-file",
680
+ lcovPath,
681
+ "--apply",
682
+ "--no-run",
683
+ "--rounds",
684
+ "1",
685
+ "--cwd",
686
+ dir,
687
+ "--json",
688
+ ], dir, { ...fake, FAKE_PATCH_TEXT: testPatch });
689
+ assert.equal(result.status, 0, result.stderr);
690
+ const payload = JSON.parse(result.stdout);
691
+ assert.equal(payload.applied, true);
692
+ assert.deepEqual(payload.files, ["a.test.ts"]);
693
+ assert.equal(payload.coverage.format, "lcov");
694
+ assert.equal(payload.coverage.uncoveredLines, 1);
695
+ assert.deepEqual(payload.coverage.gaps, [{ file: "a.ts", uncovered: [2] }]);
696
+ }
697
+ finally {
698
+ rmSync(dir, { recursive: true, force: true });
699
+ }
700
+ });
701
+ test("test-gen coverage skips generation when changed lines are covered", () => {
702
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-covered-"));
703
+ try {
704
+ spawnSync("git", ["init", "-q"], { cwd: dir });
705
+ writeFileSync(join(dir, "a.ts"), "one\ntwo\n", "utf8");
706
+ spawnSync("git", ["add", "a.ts"], { cwd: dir });
707
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
708
+ const patch = [
709
+ "diff --git a/a.ts b/a.ts",
710
+ "--- a/a.ts",
711
+ "+++ b/a.ts",
712
+ "@@ -1,2 +1,3 @@",
713
+ " one",
714
+ "+added",
715
+ " two",
716
+ "",
717
+ ].join("\n");
718
+ const diffPath = join(dir, "changes.diff");
719
+ writeFileSync(diffPath, patch);
720
+ const lcovPath = join(dir, "lcov.info");
721
+ writeFileSync(lcovPath, ["SF:a.ts", "DA:2,1", "end_of_record", ""].join("\n"));
722
+ const fake = installFakeOpenCode(dir);
723
+ const result = runCli(["test-gen", "--diff", diffPath, "--coverage-file", lcovPath, "--json"], dir, fake);
724
+ assert.equal(result.status, 0, result.stderr);
725
+ const payload = JSON.parse(result.stdout);
726
+ assert.equal(payload.applied, false);
727
+ assert.equal(payload.status, "not-run");
728
+ assert.equal(payload.coverage.uncoveredLines, 0);
729
+ }
730
+ finally {
731
+ rmSync(dir, { recursive: true, force: true });
732
+ }
733
+ });
734
+ test("test-gen --from-slop picks an untested file and writes its test", () => {
735
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-slop-"));
736
+ try {
737
+ spawnSync("git", ["init", "-q"], { cwd: dir });
738
+ const source = Array.from({ length: 120 }, (_, index) => `export const value${String(index)} = ${String(index)};`);
739
+ writeFileSync(join(dir, "calc.ts"), `${source.join("\n")}\n`, "utf8");
740
+ spawnSync("git", ["add", "calc.ts"], { cwd: dir });
741
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
742
+ const patch = [
743
+ "diff --git a/calc.test.ts b/calc.test.ts",
744
+ "new file mode 100644",
745
+ "--- /dev/null",
746
+ "+++ b/calc.test.ts",
747
+ "@@ -0,0 +1,2 @@",
748
+ "+test(\"value0\", () => {});",
749
+ "+",
750
+ "",
751
+ ].join("\n");
752
+ const fake = installFakeOpenCode(dir);
753
+ const result = runCli(["test-gen", "--from-slop", "--slop-files", "1", "--apply", "--no-run", "--cwd", dir, "--json"], dir, { ...fake, FAKE_PATCH_TEXT: patch });
754
+ assert.equal(result.status, 0, result.stderr);
755
+ const payload = JSON.parse(result.stdout);
756
+ assert.equal(payload.applied, true);
757
+ assert.deepEqual(payload.files, ["calc.test.ts"]);
758
+ assert.deepEqual(payload.targets.map((target) => target.file), ["calc.ts"]);
759
+ assert.match(readFileSync(join(dir, "calc.test.ts"), "utf8"), /test\("value0"/);
760
+ }
761
+ finally {
762
+ rmSync(dir, { recursive: true, force: true });
763
+ }
764
+ });
765
+ test("test-gen --from-slop rejects diff sources", () => {
766
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-slop-cx-"));
767
+ try {
768
+ const result = runCli(["test-gen", "--from-slop", "--diff", "x.diff", "--cwd", dir, "--json"], dir);
769
+ assert.equal(result.status, 2, result.stderr);
770
+ assert.match(result.stderr, /не сочетается/);
771
+ }
772
+ finally {
773
+ rmSync(dir, { recursive: true, force: true });
774
+ }
775
+ });
776
+ test("test-gen --from-slop exits early when every file has a test", () => {
777
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-test-gen-slop-none-"));
778
+ try {
779
+ spawnSync("git", ["init", "-q"], { cwd: dir });
780
+ writeFileSync(join(dir, "a.ts"), "export const a = 1;\n", "utf8");
781
+ writeFileSync(join(dir, "a.test.ts"), "test(\"a\", () => {});\n", "utf8");
782
+ spawnSync("git", ["add", "a.ts", "a.test.ts"], { cwd: dir });
783
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
784
+ const result = runCli(["test-gen", "--from-slop", "--cwd", dir, "--json"], dir);
785
+ assert.equal(result.status, 0, result.stderr);
786
+ const payload = JSON.parse(result.stdout);
787
+ assert.equal(payload.applied, false);
788
+ assert.equal(payload.status, "not-run");
789
+ assert.deepEqual(payload.targets, []);
790
+ }
791
+ finally {
792
+ rmSync(dir, { recursive: true, force: true });
793
+ }
794
+ });
795
+ test("deps fails the gate on a phantom import", () => {
796
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-phantom-"));
797
+ try {
798
+ spawnSync("git", ["init", "-q"], { cwd: dir });
799
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { "left-pad": "^1.3.0" } }, null, 2), "utf8");
800
+ writeFileSync(join(dir, "app.ts"), 'import axios from "axios";\n', "utf8");
801
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({ lockfileVersion: 3, packages: { "": {}, "node_modules/left-pad": {} } }), "utf8");
802
+ const result = runCli(["deps", "--cwd", dir, "--json", "--fail-on-issue", "high"], dir);
803
+ assert.equal(result.status, 1, result.stderr);
804
+ const payload = JSON.parse(result.stdout);
805
+ assert.equal(payload.packageName, "demo");
806
+ const phantom = payload.issues.find((issue) => issue.kind === "phantom");
807
+ assert.equal(phantom?.name, "axios");
808
+ assert.equal(phantom?.severity, "high");
809
+ }
810
+ finally {
811
+ rmSync(dir, { recursive: true, force: true });
812
+ }
813
+ });
814
+ test("deps reports a clean project", () => {
815
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-clean-"));
816
+ try {
817
+ spawnSync("git", ["init", "-q"], { cwd: dir });
818
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo" }, null, 2), "utf8");
819
+ writeFileSync(join(dir, "local.ts"), "export const value = 1;\n", "utf8");
820
+ writeFileSync(join(dir, "app.ts"), 'import { value } from "./local";\n', "utf8");
821
+ const result = runCli(["deps", "--cwd", dir, "--json"], dir);
822
+ assert.equal(result.status, 0, result.stderr);
823
+ const payload = JSON.parse(result.stdout);
824
+ assert.deepEqual(payload.issues, []);
825
+ }
826
+ finally {
827
+ rmSync(dir, { recursive: true, force: true });
828
+ }
829
+ });
830
+ test("deps --fix writes a phantom import from the lockfile", () => {
831
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-fix-"));
832
+ try {
833
+ spawnSync("git", ["init", "-q"], { cwd: dir });
834
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { "left-pad": "^1.3.0" } }, null, 2), "utf8");
835
+ writeFileSync(join(dir, "app.ts"), 'import axios from "axios";\n', "utf8");
836
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({
837
+ lockfileVersion: 3,
838
+ packages: {
839
+ "": {},
840
+ "node_modules/left-pad": { version: "1.3.0" },
841
+ "node_modules/axios": { version: "1.7.2" },
842
+ },
843
+ }), "utf8");
844
+ const result = runCli(["deps", "--cwd", dir, "--fix", "--json", "--fail-on-issue", "high"], dir);
845
+ assert.equal(result.status, 0, result.stderr);
846
+ const payload = JSON.parse(result.stdout);
847
+ assert.equal(payload.fix.applied, true);
848
+ assert.deepEqual(payload.fix.fixes.map((item) => [item.name, item.action]), [["axios", "add-prod"]]);
849
+ assert.equal(payload.issues.some((issue) => issue.kind === "phantom"), false);
850
+ const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
851
+ assert.equal(manifest.dependencies.axios, "^1.7.2");
852
+ }
853
+ finally {
854
+ rmSync(dir, { recursive: true, force: true });
855
+ }
856
+ });
857
+ test("deps --fix --dry-run leaves package.json untouched", () => {
858
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-plan-"));
859
+ try {
860
+ spawnSync("git", ["init", "-q"], { cwd: dir });
861
+ const original = JSON.stringify({ name: "demo", dependencies: { zod: "^3.23.0" } }, null, 2);
862
+ writeFileSync(join(dir, "package.json"), original, "utf8");
863
+ writeFileSync(join(dir, "app.test.ts"), 'import { z } from "zod";\n', "utf8");
864
+ const result = runCli(["deps", "--cwd", dir, "--fix", "--dry-run", "--json"], dir);
865
+ assert.equal(result.status, 0, result.stderr);
866
+ const payload = JSON.parse(result.stdout);
867
+ assert.equal(payload.fix.applied, false);
868
+ assert.equal(payload.fix.dryRun, true);
869
+ assert.deepEqual(payload.fix.fixes.map((item) => item.action), ["move-to-dev"]);
870
+ assert.equal(readFileSync(join(dir, "package.json"), "utf8"), original);
871
+ }
872
+ finally {
873
+ rmSync(dir, { recursive: true, force: true });
874
+ }
875
+ });
876
+ test("deps --why shows the transitive chain from the lockfile", () => {
877
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-why-"));
878
+ try {
879
+ spawnSync("git", ["init", "-q"], { cwd: dir });
880
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { express: "^4.18.0" } }, null, 2), "utf8");
881
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({
882
+ name: "demo",
883
+ lockfileVersion: 3,
884
+ packages: {
885
+ "": { name: "demo", dependencies: { express: "^4.18.0" } },
886
+ "node_modules/express": { version: "4.18.2", dependencies: { "body-parser": "1.20.1" } },
887
+ "node_modules/body-parser": { version: "1.20.1", dependencies: { qs: "6.11.0" } },
888
+ "node_modules/qs": { version: "6.11.0" },
889
+ },
890
+ }), "utf8");
891
+ writeFileSync(join(dir, "app.ts"), 'import express from "express";\n', "utf8");
892
+ const result = runCli(["deps", "--cwd", dir, "--why", "qs", "--json"], dir);
893
+ assert.equal(result.status, 0, result.stderr);
894
+ const payload = JSON.parse(result.stdout);
895
+ assert.equal(payload.name, "qs");
896
+ assert.deepEqual(payload.versions, ["6.11.0"]);
897
+ assert.deepEqual(payload.dependents, ["body-parser"]);
898
+ assert.deepEqual(payload.chains, [["express", "body-parser", "qs"]]);
899
+ }
900
+ finally {
901
+ rmSync(dir, { recursive: true, force: true });
902
+ }
903
+ });
904
+ test("deps --why fails cleanly for an unknown package", () => {
905
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-deps-why-missing-"));
906
+ try {
907
+ spawnSync("git", ["init", "-q"], { cwd: dir });
908
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo" }, null, 2), "utf8");
909
+ const result = runCli(["deps", "--cwd", dir, "--why", "left-pad", "--json"], dir);
910
+ assert.equal(result.status, 1);
911
+ assert.match(result.stderr, /не найден/);
912
+ }
913
+ finally {
914
+ rmSync(dir, { recursive: true, force: true });
915
+ }
916
+ });
917
+ test("preflight fails on a critical secret in the diff", () => {
918
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-secret-"));
919
+ try {
920
+ spawnSync("git", ["init", "-q"], { cwd: dir });
921
+ const diff = [
922
+ "diff --git a/src/app.ts b/src/app.ts",
923
+ "new file mode 100644",
924
+ "--- /dev/null",
925
+ "+++ b/src/app.ts",
926
+ "@@ -0,0 +1,2 @@",
927
+ '+const key = "AKIAIOSFODNN7EXAMPLE";',
928
+ "+",
929
+ "",
930
+ ].join("\n");
931
+ const diffPath = join(dir, "changes.diff");
932
+ writeFileSync(diffPath, diff);
933
+ const result = runCli(["preflight", "--diff", diffPath, "--no-deps", "--no-slop", "--cwd", dir, "--json"], dir);
934
+ assert.equal(result.status, 1, result.stderr);
935
+ const payload = JSON.parse(result.stdout);
936
+ assert.equal(payload.status, "fail");
937
+ assert.equal(payload.checks.find((check) => check.id === "secrets")?.status, "fail");
938
+ }
939
+ finally {
940
+ rmSync(dir, { recursive: true, force: true });
941
+ }
942
+ });
943
+ test("preflight passes on an empty diff", () => {
944
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-clean-"));
945
+ try {
946
+ spawnSync("git", ["init", "-q"], { cwd: dir });
947
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
948
+ spawnSync("git", ["add", "app.ts"], { cwd: dir });
949
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], {
950
+ cwd: dir,
951
+ });
952
+ const result = runCli(["preflight", "--cwd", dir, "--json", "--no-deps", "--no-slop"], dir);
953
+ assert.equal(result.status, 0, result.stderr);
954
+ const payload = JSON.parse(result.stdout);
955
+ assert.equal(payload.status, "pass");
956
+ }
957
+ finally {
958
+ rmSync(dir, { recursive: true, force: true });
959
+ }
960
+ });
961
+ test("preflight fails when the diff weakens tests", () => {
962
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-tests-"));
963
+ try {
964
+ spawnSync("git", ["init", "-q"], { cwd: dir });
965
+ const diff = [
966
+ "diff --git a/src/app.test.ts b/src/app.test.ts",
967
+ "new file mode 100644",
968
+ "--- /dev/null",
969
+ "+++ b/src/app.test.ts",
970
+ "@@ -0,0 +1,2 @@",
971
+ '+it.skip("works", () => {});',
972
+ "+",
973
+ "",
974
+ ].join("\n");
975
+ const diffPath = join(dir, "changes.diff");
976
+ writeFileSync(diffPath, diff);
977
+ const result = runCli(["preflight", "--diff", diffPath, "--no-deps", "--no-slop", "--cwd", dir, "--json"], dir);
978
+ assert.equal(result.status, 1, result.stderr);
979
+ const payload = JSON.parse(result.stdout);
980
+ assert.equal(payload.checks.find((check) => check.id === "tests")?.status, "fail");
981
+ }
982
+ finally {
983
+ rmSync(dir, { recursive: true, force: true });
984
+ }
985
+ });
986
+ test("preflight can skip every check", () => {
987
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-skip-"));
988
+ try {
989
+ spawnSync("git", ["init", "-q"], { cwd: dir });
990
+ const diff = [
991
+ "diff --git a/src/app.ts b/src/app.ts",
992
+ "new file mode 100644",
993
+ "--- /dev/null",
994
+ "+++ b/src/app.ts",
995
+ "@@ -0,0 +1,2 @@",
996
+ '+const key = "AKIAIOSFODNN7EXAMPLE";',
997
+ "+",
998
+ "",
999
+ ].join("\n");
1000
+ const diffPath = join(dir, "changes.diff");
1001
+ writeFileSync(diffPath, diff);
1002
+ const result = runCli([
1003
+ "preflight",
1004
+ "--diff",
1005
+ diffPath,
1006
+ "--no-secret-scan",
1007
+ "--no-deps",
1008
+ "--no-tests",
1009
+ "--no-slop",
1010
+ "--cwd",
1011
+ dir,
1012
+ "--json",
1013
+ ], dir);
1014
+ assert.equal(result.status, 0, result.stderr);
1015
+ const payload = JSON.parse(result.stdout);
1016
+ assert.equal(payload.status, "pass");
1017
+ assert.deepEqual(payload.checks.map((check) => check.status), ["skip", "skip", "skip", "skip", "skip", "skip"]);
1018
+ }
1019
+ finally {
1020
+ rmSync(dir, { recursive: true, force: true });
1021
+ }
1022
+ });
1023
+ test("receipt records a run and verifies against HEAD", () => {
1024
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-receipt-"));
1025
+ try {
1026
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1027
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", scripts: { test: 'node -e "process.exit(0)"' } }, null, 2), "utf8");
1028
+ writeFileSync(join(dir, "app.ts"), "export const a = 1;\n", "utf8");
1029
+ spawnSync("git", ["add", "-A"], { cwd: dir });
1030
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], {
1031
+ cwd: dir,
1032
+ });
1033
+ const created = runCli(["receipt", "--cwd", dir, "--json", "--no-slop"], dir);
1034
+ assert.equal(created.status, 0, created.stderr);
1035
+ const receipt = JSON.parse(created.stdout);
1036
+ assert.equal(receipt.tests?.passed, true);
1037
+ assert.equal(receipt.preflight.status, "pass");
1038
+ assert.match(receipt.hash, /^[a-f0-9]{64}$/);
1039
+ const saved = join(dir, ".git", "oc-pilot", "receipt.json");
1040
+ assert.equal(existsSync(saved), true);
1041
+ const verified = runCli(["receipt", "--verify", "--cwd", dir, "--json"], dir);
1042
+ assert.equal(verified.status, 0, verified.stderr);
1043
+ const verification = JSON.parse(verified.stdout);
1044
+ assert.equal(verification.ok, true);
1045
+ assert.equal(verification.hashOk, true);
1046
+ assert.equal(verification.stale, false);
1047
+ const tampered = { ...receipt, tests: { ...receipt.tests, passed: false } };
1048
+ writeFileSync(saved, JSON.stringify(tampered, null, 2), "utf8");
1049
+ const rejected = runCli(["receipt", "--verify", "--cwd", dir, "--json"], dir);
1050
+ assert.equal(rejected.status, 1, rejected.stderr);
1051
+ const rejection = JSON.parse(rejected.stdout);
1052
+ assert.equal(rejection.ok, false);
1053
+ assert.equal(rejection.hashOk, false);
1054
+ }
1055
+ finally {
1056
+ rmSync(dir, { recursive: true, force: true });
1057
+ }
1058
+ });
1059
+ test("receipt --sign produces a verifiable Ed25519 signature", () => {
1060
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-receipt-sign-"));
1061
+ try {
1062
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1063
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
1064
+ spawnSync("git", ["add", "app.ts"], { cwd: dir });
1065
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1066
+ const key = join(dir, "receipt.key");
1067
+ const keygen = runCli(["receipt", "--cwd", dir, "--keygen", key], dir);
1068
+ assert.equal(keygen.status, 0, keygen.stderr);
1069
+ assert.equal(existsSync(`${key}.pub`), true);
1070
+ const signed = runCli(["receipt", "--cwd", dir, "--sign", "--key", key, "--no-run", "--json"], dir);
1071
+ assert.equal(signed.status, 0, signed.stderr);
1072
+ const payload = JSON.parse(signed.stdout);
1073
+ assert.equal(typeof payload.signature, "string");
1074
+ const valid = runCli(["receipt", "--cwd", dir, "--verify", "--public-key", `${key}.pub`, "--json"], dir);
1075
+ assert.equal(valid.status, 0, valid.stderr);
1076
+ const verdict = JSON.parse(valid.stdout);
1077
+ assert.equal(verdict.ok, true);
1078
+ assert.equal(verdict.signature, "valid");
1079
+ const otherKey = join(dir, "other.key");
1080
+ assert.equal(runCli(["receipt", "--cwd", dir, "--keygen", otherKey], dir).status, 0);
1081
+ const rejected = runCli(["receipt", "--cwd", dir, "--verify", "--public-key", `${otherKey}.pub`, "--json"], dir);
1082
+ assert.equal(rejected.status, 1, rejected.stderr);
1083
+ const failed = JSON.parse(rejected.stdout);
1084
+ assert.equal(failed.ok, false);
1085
+ assert.equal(failed.signature, "invalid");
1086
+ }
1087
+ finally {
1088
+ rmSync(dir, { recursive: true, force: true });
1089
+ }
1090
+ });
1091
+ test("receipt reports failing tests", () => {
1092
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-receipt-fail-"));
1093
+ try {
1094
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1095
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", scripts: { test: 'node -e "process.exit(1)"' } }, null, 2), "utf8");
1096
+ writeFileSync(join(dir, "app.ts"), "export const a = 1;\n", "utf8");
1097
+ spawnSync("git", ["add", "-A"], { cwd: dir });
1098
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], {
1099
+ cwd: dir,
1100
+ });
1101
+ const result = runCli(["receipt", "--cwd", dir, "--json", "--no-slop"], dir);
1102
+ assert.equal(result.status, 1, result.stderr);
1103
+ const payload = JSON.parse(result.stdout);
1104
+ assert.equal(payload.tests?.passed, false);
1105
+ assert.equal(payload.tests?.exitCode, 1);
1106
+ }
1107
+ finally {
1108
+ rmSync(dir, { recursive: true, force: true });
1109
+ }
1110
+ });
1111
+ test("deps reads a pnpm lockfile and fixes a phantom import", () => {
1112
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-pnpm-"));
1113
+ try {
1114
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1115
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { "left-pad": "^1.3.0" } }, null, 2), "utf8");
1116
+ writeFileSync(join(dir, "app.ts"), 'import axios from "axios";\n', "utf8");
1117
+ writeFileSync(join(dir, "pnpm-lock.yaml"), [
1118
+ "lockfileVersion: '6.0'",
1119
+ "",
1120
+ "packages:",
1121
+ "",
1122
+ " /axios@1.7.2:",
1123
+ " resolution: {integrity: sha512-x}",
1124
+ "",
1125
+ " /left-pad@1.3.0:",
1126
+ " resolution: {integrity: sha512-y}",
1127
+ "",
1128
+ ].join("\n"), "utf8");
1129
+ const audit = runCli(["deps", "--cwd", dir, "--json", "--fail-on-issue", "high"], dir);
1130
+ assert.equal(audit.status, 1, audit.stderr);
1131
+ const report = JSON.parse(audit.stdout);
1132
+ assert.equal(report.lockfile, "pnpm-lock.yaml");
1133
+ assert.ok(report.issues.some((issue) => issue.kind === "phantom" && issue.name === "axios"));
1134
+ const fixed = runCli(["deps", "--cwd", dir, "--fix", "--json"], dir);
1135
+ assert.equal(fixed.status, 0, fixed.stderr);
1136
+ const payload = JSON.parse(fixed.stdout);
1137
+ assert.equal(payload.fix.applied, true);
1138
+ assert.deepEqual(payload.fix.fixes.map((fix) => [fix.name, fix.action]), [["axios", "add-prod"]]);
1139
+ const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
1140
+ assert.equal(manifest.dependencies.axios, "^1.7.2");
1141
+ }
1142
+ finally {
1143
+ rmSync(dir, { recursive: true, force: true });
1144
+ }
1145
+ });
1146
+ test("flaky finds a test that flips between runs", () => {
1147
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-flaky-"));
1148
+ try {
1149
+ writeFileSync(join(dir, "check.js"), [
1150
+ 'const fs = require("node:fs");',
1151
+ 'const count = fs.existsSync("count.txt") ? Number(fs.readFileSync("count.txt", "utf8")) : 0;',
1152
+ 'fs.writeFileSync("count.txt", String(count + 1));',
1153
+ 'if (count % 2 === 0) { console.log("\\u2714 flaky test"); process.exit(0); }',
1154
+ 'console.log("\\u2716 flaky test"); process.exit(1);',
1155
+ "",
1156
+ ].join("\n"), "utf8");
1157
+ const result = runCli(["flaky", "--cwd", dir, "--command", "node check.js", "--runs", "3", "--json"], dir);
1158
+ assert.equal(result.status, 0, result.stderr);
1159
+ const payload = JSON.parse(result.stdout);
1160
+ assert.equal(payload.runs, 3);
1161
+ assert.equal(payload.parsed, true);
1162
+ assert.equal(payload.flaky[0]?.name, "flaky test");
1163
+ assert.equal(payload.flaky[0]?.status, "flaky");
1164
+ const gated = runCli(["flaky", "--cwd", dir, "--command", "node check.js", "--runs", "3", "--json", "--fail-on-flaky"], dir);
1165
+ assert.equal(gated.status, 1, gated.stderr);
1166
+ }
1167
+ finally {
1168
+ rmSync(dir, { recursive: true, force: true });
1169
+ }
1170
+ });
1171
+ test("flaky --shuffle runs test files in a seeded order", () => {
1172
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-flaky-shuffle-"));
1173
+ try {
1174
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1175
+ writeFileSync(join(dir, "a.test.js"), "// a\n", "utf8");
1176
+ writeFileSync(join(dir, "b.test.js"), "// b\n", "utf8");
1177
+ writeFileSync(join(dir, "runner.js"), 'for (const file of process.argv.slice(2)) console.log("\\u2714 " + file.replace(/\\.test\\.js$/, ""));\n', "utf8");
1178
+ const result = runCli([
1179
+ "flaky",
1180
+ "--cwd",
1181
+ dir,
1182
+ "--shuffle",
1183
+ "--seed",
1184
+ "demo",
1185
+ "--command",
1186
+ "node runner.js {files}",
1187
+ "--runs",
1188
+ "2",
1189
+ "--json",
1190
+ ], dir);
1191
+ assert.equal(result.status, 0, result.stderr);
1192
+ const payload = JSON.parse(result.stdout);
1193
+ assert.deepEqual(payload.shuffle, { seed: "demo", files: 2 });
1194
+ assert.equal(payload.parsed, true);
1195
+ assert.deepEqual(payload.tests.map((test) => test.name).sort(), ["a", "b"]);
1196
+ const missingPlaceholder = runCli(["flaky", "--cwd", dir, "--shuffle", "--command", "node runner.js a.test.js", "--runs", "1", "--json"], dir);
1197
+ assert.equal(missingPlaceholder.status, 2);
1198
+ assert.match(missingPlaceholder.stderr, /\{files\}/);
1199
+ }
1200
+ finally {
1201
+ rmSync(dir, { recursive: true, force: true });
1202
+ }
1203
+ });
1204
+ test("ready --fix installs the hook, config and instructions", () => {
1205
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-ready-fix-"));
1206
+ try {
1207
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1208
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true, scripts: { test: 'node -e "process.exit(0)"' } }), "utf8");
1209
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({ name: "demo", lockfileVersion: 3, packages: { "": { name: "demo" } } }), "utf8");
1210
+ const before = runCli(["ready", "--cwd", dir, "--json"], dir);
1211
+ assert.equal(before.status, 0, before.stderr);
1212
+ const baseline = JSON.parse(before.stdout);
1213
+ const fixed = runCli(["ready", "--cwd", dir, "--fix", "--json"], dir);
1214
+ assert.equal(fixed.status, 0, fixed.stderr);
1215
+ const payload = JSON.parse(fixed.stdout);
1216
+ assert.equal(payload.fix.applied, true);
1217
+ assert.equal(payload.fix.dryRun, false);
1218
+ assert.deepEqual(payload.fix.fixes.map((fix) => fix.action).sort(), ["install-hook", "write-agents", "write-config"]);
1219
+ assert.ok(payload.score > baseline.score);
1220
+ assert.ok(existsSync(join(dir, ".git", "hooks", "pre-commit")));
1221
+ assert.ok(existsSync(join(dir, ".oc-pilot.json")));
1222
+ assert.ok(existsSync(join(dir, "AGENTS.md")));
1223
+ const after = runCli(["ready", "--cwd", dir, "--json"], dir);
1224
+ const checks = JSON.parse(after.stdout).checks;
1225
+ const status = new Map(checks.map((check) => [check.id, check.status]));
1226
+ assert.equal(status.get("hooks"), "pass");
1227
+ assert.equal(status.get("docs"), "pass");
1228
+ assert.equal(status.get("config"), "pass");
1229
+ }
1230
+ finally {
1231
+ rmSync(dir, { recursive: true, force: true });
1232
+ }
1233
+ });
1234
+ test("ready --fix --dry-run leaves the repository untouched", () => {
1235
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-ready-dry-"));
1236
+ try {
1237
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1238
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true }), "utf8");
1239
+ const result = runCli(["ready", "--cwd", dir, "--fix", "--dry-run", "--json"], dir);
1240
+ assert.equal(result.status, 0, result.stderr);
1241
+ const payload = JSON.parse(result.stdout);
1242
+ assert.equal(payload.fix.applied, false);
1243
+ assert.equal(payload.fix.dryRun, true);
1244
+ assert.equal(payload.fix.fixes.length, 3);
1245
+ assert.equal(existsSync(join(dir, ".git", "hooks", "pre-commit")), false);
1246
+ assert.equal(existsSync(join(dir, ".oc-pilot.json")), false);
1247
+ assert.equal(existsSync(join(dir, "AGENTS.md")), false);
1248
+ }
1249
+ finally {
1250
+ rmSync(dir, { recursive: true, force: true });
1251
+ }
1252
+ });
1253
+ test("ready --fix --receipt records a verified receipt", () => {
1254
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-ready-receipt-"));
1255
+ try {
1256
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1257
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true, scripts: { test: 'node -e "process.exit(0)"' } }), "utf8");
1258
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({ name: "demo", lockfileVersion: 3, packages: { "": { name: "demo" } } }), "utf8");
1259
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
1260
+ spawnSync("git", ["add", "-A"], { cwd: dir });
1261
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1262
+ const result = runCli(["ready", "--cwd", dir, "--fix", "--receipt", "--json"], dir);
1263
+ assert.equal(result.status, 0, result.stderr);
1264
+ const payload = JSON.parse(result.stdout);
1265
+ assert.equal(payload.fix.applied, true);
1266
+ assert.equal(payload.fix.receipt?.testsPassed, true);
1267
+ assert.equal(payload.fix.receipt?.preflight, "pass");
1268
+ assert.ok(payload.fix.receipt?.path.endsWith("receipt.json"));
1269
+ assert.ok(existsSync(join(dir, ".git", "oc-pilot", "receipt.json")));
1270
+ assert.ok(payload.score >= 90);
1271
+ const after = runCli(["ready", "--cwd", dir, "--json"], dir);
1272
+ const checks = JSON.parse(after.stdout).checks;
1273
+ assert.equal(checks.find((check) => check.id === "receipt")?.status, "pass");
1274
+ const invalid = runCli(["ready", "--cwd", dir, "--receipt", "--json"], dir);
1275
+ assert.equal(invalid.status, 2);
1276
+ }
1277
+ finally {
1278
+ rmSync(dir, { recursive: true, force: true });
1279
+ }
1280
+ });
1281
+ test("mutate reports a killed and a surviving mutation", () => {
1282
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-mutate-"));
1283
+ try {
1284
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1285
+ const calc = [
1286
+ "function same(a, b) { return a === b; }",
1287
+ "const flag = true;",
1288
+ "module.exports = { same };",
1289
+ "",
1290
+ ].join("\n");
1291
+ writeFileSync(join(dir, "calc.js"), calc, "utf8");
1292
+ writeFileSync(join(dir, "check.js"), ['const { same } = require("./calc.js");', "if (!same(1, 1)) process.exit(1);", ""].join("\n"), "utf8");
1293
+ const patch = [
1294
+ "diff --git a/calc.js b/calc.js",
1295
+ "new file mode 100644",
1296
+ "--- /dev/null",
1297
+ "+++ b/calc.js",
1298
+ "@@ -0,0 +1,3 @@",
1299
+ "+function same(a, b) { return a === b; }",
1300
+ "+const flag = true;",
1301
+ "+module.exports = { same };",
1302
+ "",
1303
+ ].join("\n");
1304
+ const diffPath = join(dir, "changes.diff");
1305
+ writeFileSync(diffPath, patch, "utf8");
1306
+ const result = runCli(["mutate", "--diff", diffPath, "--command", "node check.js", "--max-mutants", "5", "--cwd", dir, "--json"], dir);
1307
+ assert.equal(result.status, 0, result.stderr);
1308
+ const payload = JSON.parse(result.stdout);
1309
+ assert.equal(payload.sites, 2);
1310
+ assert.equal(payload.killed, 1);
1311
+ assert.equal(payload.survived, 1);
1312
+ assert.equal(payload.score, 50);
1313
+ assert.deepEqual(payload.mutants.map((mutant) => [mutant.operator, mutant.status]), [
1314
+ ["===", "killed"],
1315
+ ["true", "survived"],
1316
+ ]);
1317
+ assert.equal(readFileSync(join(dir, "calc.js"), "utf8"), calc);
1318
+ const gated = runCli(["mutate", "--diff", diffPath, "--command", "node check.js", "--max-mutants", "5", "--min-score", "100", "--cwd", dir, "--json"], dir);
1319
+ assert.equal(gated.status, 1, gated.stderr);
1320
+ }
1321
+ finally {
1322
+ rmSync(dir, { recursive: true, force: true });
1323
+ }
1324
+ });
1325
+ test("deps --audit fails on a known vulnerability from OSV", async () => {
1326
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-osv-"));
1327
+ let server;
1328
+ try {
1329
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1330
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true, dependencies: { "left-pad": "1.3.0" } }), "utf8");
1331
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({
1332
+ name: "demo",
1333
+ lockfileVersion: 3,
1334
+ packages: { "": { name: "demo" }, "node_modules/left-pad": { version: "1.3.0" } },
1335
+ }), "utf8");
1336
+ writeFileSync(join(dir, "app.ts"), 'import leftPad from "left-pad";\n', "utf8");
1337
+ const serverPath = join(dir, "osv-server.js");
1338
+ writeFileSync(serverPath, [
1339
+ 'const http = require("node:http");',
1340
+ "const server = http.createServer((req, res) => {",
1341
+ ' req.on("data", () => {});',
1342
+ ' req.on("end", () => {',
1343
+ ' res.writeHead(200, { "content-type": "application/json" });',
1344
+ " res.end(JSON.stringify({ results: [{ vulns: [{",
1345
+ ' id: "GHSA-test-1234",',
1346
+ ' summary: "test vulnerability",',
1347
+ ' database_specific: { severity: "high" },',
1348
+ ' affected: [{ ranges: [{ events: [{ fixed: "1.3.1" }] }] }],',
1349
+ " }] }] }));",
1350
+ " });",
1351
+ "});",
1352
+ 'server.listen(0, "127.0.0.1", () => {',
1353
+ " process.stdout.write(String(server.address().port) + \"\\n\");",
1354
+ "});",
1355
+ "",
1356
+ ].join("\n"), "utf8");
1357
+ server = spawn(process.execPath, [serverPath], { cwd: dir, stdio: ["ignore", "pipe", "pipe"] });
1358
+ const port = await new Promise((resolvePort, reject) => {
1359
+ let buffer = "";
1360
+ const timer = setTimeout(() => reject(new Error("OSV server did not start")), 10_000);
1361
+ server?.stdout?.on("data", (chunk) => {
1362
+ buffer += chunk.toString();
1363
+ const match = buffer.match(/(\d+)/);
1364
+ if (match) {
1365
+ clearTimeout(timer);
1366
+ resolvePort(Number(match[1]));
1367
+ }
1368
+ });
1369
+ server?.on("error", (error) => {
1370
+ clearTimeout(timer);
1371
+ reject(error);
1372
+ });
1373
+ });
1374
+ const result = runCli([
1375
+ "deps",
1376
+ "--cwd",
1377
+ dir,
1378
+ "--audit",
1379
+ "--osv-url",
1380
+ `http://127.0.0.1:${String(port)}/v1/querybatch`,
1381
+ "--fail-on-vuln",
1382
+ "high",
1383
+ "--json",
1384
+ ], dir);
1385
+ assert.equal(result.status, 1, result.stderr);
1386
+ const payload = JSON.parse(result.stdout);
1387
+ const vulnerability = payload.audit?.vulnerabilities?.[0];
1388
+ assert.equal(vulnerability?.id, "GHSA-test-1234");
1389
+ assert.equal(vulnerability?.severity, "high");
1390
+ assert.deepEqual(vulnerability?.fixed, ["1.3.1"]);
1391
+ }
1392
+ finally {
1393
+ const child = server;
1394
+ if (child && child.exitCode === null) {
1395
+ child.kill();
1396
+ await Promise.race([
1397
+ new Promise((resolveExit) => child.once("exit", () => resolveExit())),
1398
+ new Promise((resolveTimeout) => setTimeout(resolveTimeout, 5_000)),
1399
+ ]);
1400
+ }
1401
+ rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
1402
+ }
1403
+ });
1404
+ test("deps --sbom writes a CycloneDX file", () => {
1405
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-sbom-"));
1406
+ try {
1407
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1408
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true, dependencies: { "left-pad": "1.3.0" } }), "utf8");
1409
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({
1410
+ name: "demo",
1411
+ lockfileVersion: 3,
1412
+ packages: { "": { name: "demo" }, "node_modules/left-pad": { version: "1.3.0" } },
1413
+ }), "utf8");
1414
+ writeFileSync(join(dir, "app.ts"), 'import leftPad from "left-pad";\n', "utf8");
1415
+ const result = runCli(["deps", "--cwd", dir, "--sbom", "out/sbom.json", "--json"], dir);
1416
+ assert.equal(result.status, 0, result.stderr);
1417
+ const payload = JSON.parse(result.stdout);
1418
+ assert.match(payload.sbom ?? "", /out[\\/]sbom\.json$/);
1419
+ const sbom = JSON.parse(readFileSync(join(dir, "out", "sbom.json"), "utf8"));
1420
+ assert.equal(sbom.bomFormat, "CycloneDX");
1421
+ assert.ok(sbom.components.some((component) => component.name === "left-pad"));
1422
+ }
1423
+ finally {
1424
+ rmSync(dir, { recursive: true, force: true });
1425
+ }
1426
+ });
1427
+ test("sast flags request data in shell and SQL sinks", () => {
1428
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-sast-"));
1429
+ try {
1430
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1431
+ writeFileSync(join(dir, "app.js"), [
1432
+ 'const { exec } = require("node:child_process");',
1433
+ "function handler(req, db) {",
1434
+ " exec(`rm -rf ${req.query.dir}`);",
1435
+ ' db.query("SELECT * FROM users WHERE id = " + req.query.id);',
1436
+ "}",
1437
+ "",
1438
+ ].join("\n"), "utf8");
1439
+ spawnSync("git", ["add", "app.js"], { cwd: dir });
1440
+ const result = runCli(["sast", "--cwd", dir, "--staged", "--fail-on-issue", "high", "--json"], dir);
1441
+ assert.equal(result.status, 1, result.stderr);
1442
+ const payload = JSON.parse(result.stdout);
1443
+ assert.ok(payload.findings.some((finding) => finding.rule === "command-injection"));
1444
+ assert.ok(payload.findings.some((finding) => finding.rule === "sql-injection"));
1445
+ assert.ok(payload.summary.high >= 2);
1446
+ }
1447
+ finally {
1448
+ rmSync(dir, { recursive: true, force: true });
1449
+ }
1450
+ });
1451
+ test("sast passes clean code and emits SARIF", () => {
1452
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-sast-clean-"));
1453
+ try {
1454
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1455
+ writeFileSync(join(dir, "safe.js"), [
1456
+ 'const fs = require("node:fs");',
1457
+ 'const path = require("node:path");',
1458
+ 'function handler(req) { return fs.readFileSync(path.join("/data", path.basename(req.query.file))); }',
1459
+ "",
1460
+ ].join("\n"), "utf8");
1461
+ spawnSync("git", ["add", "safe.js"], { cwd: dir });
1462
+ const clean = runCli(["sast", "--cwd", dir, "--staged", "--json"], dir);
1463
+ assert.equal(clean.status, 0, clean.stderr);
1464
+ const payload = JSON.parse(clean.stdout);
1465
+ assert.deepEqual(payload.findings, []);
1466
+ const sarif = runCli(["sast", "--cwd", dir, "--staged", "--format", "sarif"], dir);
1467
+ assert.equal(sarif.status, 0, sarif.stderr);
1468
+ const report = JSON.parse(sarif.stdout);
1469
+ assert.equal(report.version, "2.1.0");
1470
+ assert.deepEqual(report.runs[0]?.results, []);
1471
+ }
1472
+ finally {
1473
+ rmSync(dir, { recursive: true, force: true });
1474
+ }
1475
+ });
1476
+ test("preflight runs SAST on the changed files", () => {
1477
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-sast-"));
1478
+ try {
1479
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1480
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
1481
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1482
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1483
+ const patch = [
1484
+ "diff --git a/src/app.js b/src/app.js",
1485
+ "new file mode 100644",
1486
+ "--- /dev/null",
1487
+ "+++ b/src/app.js",
1488
+ "@@ -0,0 +1,2 @@",
1489
+ '+const { exec } = require("child_process");',
1490
+ "+exec(`rm -rf ${req.query.dir}`);",
1491
+ "",
1492
+ ].join("\n");
1493
+ mkdirSync(join(dir, "src"), { recursive: true });
1494
+ writeFileSync(join(dir, "src", "app.js"), 'const { exec } = require("child_process");\nexec(`rm -rf ${req.query.dir}`);\n', "utf8");
1495
+ const diffPath = join(dir, "changes.diff");
1496
+ writeFileSync(diffPath, patch, "utf8");
1497
+ const result = runCli(["preflight", "--diff", diffPath, "--no-deps", "--no-slop", "--json", "--cwd", dir], dir);
1498
+ assert.equal(result.status, 1, result.stderr);
1499
+ const payload = JSON.parse(result.stdout);
1500
+ const sast = payload.checks.find((check) => check.id === "sast");
1501
+ assert.equal(sast?.status, "fail");
1502
+ assert.match(sast?.summary ?? "", /command-injection|1 находок/);
1503
+ const skipped = runCli(["preflight", "--diff", diffPath, "--no-deps", "--no-slop", "--no-sast", "--json", "--cwd", dir], dir);
1504
+ assert.equal(skipped.status, 0, skipped.stderr);
1505
+ const skippedPayload = JSON.parse(skipped.stdout);
1506
+ assert.equal(skippedPayload.checks.find((check) => check.id === "sast")?.status, "skip");
1507
+ }
1508
+ finally {
1509
+ rmSync(dir, { recursive: true, force: true });
1510
+ }
1511
+ });
1512
+ test("preflight runs authz on changed routes", () => {
1513
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-preflight-authz-"));
1514
+ try {
1515
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1516
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
1517
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1518
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1519
+ const route = 'router.get("/users/:id", async (req, res) => { const user = await db.user.findUnique({ where: { id: req.params.id } }); res.json(user); });\n';
1520
+ const patch = [
1521
+ "diff --git a/src/routes.js b/src/routes.js",
1522
+ "new file mode 100644",
1523
+ "--- /dev/null",
1524
+ "+++ b/src/routes.js",
1525
+ "@@ -0,0 +1 @@",
1526
+ `+${route.trimEnd()}`,
1527
+ "",
1528
+ ].join("\n");
1529
+ mkdirSync(join(dir, "src"), { recursive: true });
1530
+ writeFileSync(join(dir, "src", "routes.js"), route, "utf8");
1531
+ const diffPath = join(dir, "changes.diff");
1532
+ writeFileSync(diffPath, patch, "utf8");
1533
+ const result = runCli(["preflight", "--diff", diffPath, "--no-deps", "--no-slop", "--no-sast", "--json", "--cwd", dir], dir);
1534
+ assert.equal(result.status, 1, result.stderr);
1535
+ const payload = JSON.parse(result.stdout);
1536
+ const authz = payload.checks.find((check) => check.id === "authz");
1537
+ assert.equal(authz?.status, "fail");
1538
+ assert.match(authz?.summary ?? "", /idor|route-no-auth|2 находок/);
1539
+ const skipped = runCli([
1540
+ "preflight",
1541
+ "--diff",
1542
+ diffPath,
1543
+ "--no-deps",
1544
+ "--no-slop",
1545
+ "--no-sast",
1546
+ "--no-authz",
1547
+ "--json",
1548
+ "--cwd",
1549
+ dir,
1550
+ ], dir);
1551
+ assert.equal(skipped.status, 0, skipped.stderr);
1552
+ const skippedPayload = JSON.parse(skipped.stdout);
1553
+ assert.equal(skippedPayload.checks.find((check) => check.id === "authz")?.status, "skip");
1554
+ }
1555
+ finally {
1556
+ rmSync(dir, { recursive: true, force: true });
1557
+ }
1558
+ });
1559
+ test("authz flags an unauthenticated IDOR route", () => {
1560
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-authz-"));
1561
+ try {
1562
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1563
+ writeFileSync(join(dir, "base.txt"), "base\n", "utf8");
1564
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1565
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1566
+ const source = [
1567
+ 'router.get("/users/:id", async (req, res) => {',
1568
+ " const user = await db.user.findUnique({ where: { id: req.params.id } });",
1569
+ " res.json(user);",
1570
+ "});",
1571
+ "",
1572
+ ].join("\n");
1573
+ const patch = [
1574
+ "diff --git a/src/routes.js b/src/routes.js",
1575
+ "new file mode 100644",
1576
+ "--- /dev/null",
1577
+ "+++ b/src/routes.js",
1578
+ "@@ -0,0 +1,4 @@",
1579
+ ...source.trimEnd().split("\n").map((line) => `+${line}`),
1580
+ "",
1581
+ ].join("\n");
1582
+ const diffPath = join(dir, "changes.diff");
1583
+ writeFileSync(diffPath, patch);
1584
+ mkdirSync(join(dir, "src"), { recursive: true });
1585
+ writeFileSync(join(dir, "src", "routes.js"), source, "utf8");
1586
+ const result = runCli(["authz", "--diff", diffPath, "--json", "--cwd", dir], dir);
1587
+ assert.equal(result.status, 1, result.stderr);
1588
+ const payload = JSON.parse(result.stdout);
1589
+ assert.equal(payload.routes.length, 1);
1590
+ assert.deepEqual(payload.findings.map((finding) => finding.rule).sort(), ["idor", "route-no-auth"]);
1591
+ assert.equal(payload.summary.high, 2);
1592
+ const publicOnly = runCli(["authz", "--public", "/users", "--diff", diffPath, "--json", "--cwd", dir], dir);
1593
+ const publicPayload = JSON.parse(publicOnly.stdout);
1594
+ assert.deepEqual(publicPayload.findings.map((finding) => finding.rule), ["idor"]);
1595
+ const sarif = runCli(["authz", "--diff", diffPath, "--format", "sarif", "--cwd", dir], dir);
1596
+ assert.equal(sarif.status, 1, sarif.stderr);
1597
+ const doc = JSON.parse(sarif.stdout);
1598
+ assert.equal(doc.version, "2.1.0");
1599
+ assert.ok(doc.runs[0]?.results.some((entry) => entry.ruleId === "oc-pilot/idor"));
1600
+ }
1601
+ finally {
1602
+ rmSync(dir, { recursive: true, force: true });
1603
+ }
1604
+ });
1605
+ test("sast baseline accepts known findings and catches new ones", () => {
1606
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-baseline-"));
1607
+ try {
1608
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1609
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
1610
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1611
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1612
+ mkdirSync(join(dir, "src"), { recursive: true });
1613
+ writeFileSync(join(dir, "src", "app.js"), 'const { exec } = require("child_process");\nexec(`rm -rf ${req.query.dir}`);\n', "utf8");
1614
+ const firstDiff = join(dir, "first.diff");
1615
+ writeFileSync(firstDiff, [
1616
+ "diff --git a/src/app.js b/src/app.js",
1617
+ "new file mode 100644",
1618
+ "--- /dev/null",
1619
+ "+++ b/src/app.js",
1620
+ "@@ -0,0 +1,2 @@",
1621
+ '+const { exec } = require("child_process");',
1622
+ "+exec(`rm -rf ${req.query.dir}`);",
1623
+ "",
1624
+ ].join("\n"), "utf8");
1625
+ const updated = runCli(["sast", "--diff", firstDiff, "--update-baseline", "--baseline", ".oc-pilot/baseline.json", "--cwd", dir, "--json"], dir);
1626
+ assert.equal(updated.status, 0, updated.stderr);
1627
+ const updatePayload = JSON.parse(updated.stdout);
1628
+ assert.equal(updatePayload.baseline?.updated, true);
1629
+ assert.equal(updatePayload.baseline?.entries, 1);
1630
+ assert.ok(existsSync(join(dir, ".oc-pilot", "baseline.json")));
1631
+ const filtered = runCli(["sast", "--diff", firstDiff, "--baseline", ".oc-pilot/baseline.json", "--cwd", dir, "--json"], dir);
1632
+ assert.equal(filtered.status, 0, filtered.stderr);
1633
+ const filteredPayload = JSON.parse(filtered.stdout);
1634
+ assert.deepEqual(filteredPayload.findings, []);
1635
+ assert.equal(filteredPayload.baselineSuppressed, 1);
1636
+ writeFileSync(join(dir, "src", "other.js"), "eval(req.query.code);\n", "utf8");
1637
+ const secondDiff = join(dir, "second.diff");
1638
+ writeFileSync(secondDiff, [
1639
+ "diff --git a/src/app.js b/src/app.js",
1640
+ "new file mode 100644",
1641
+ "--- /dev/null",
1642
+ "+++ b/src/app.js",
1643
+ "@@ -0,0 +1,2 @@",
1644
+ '+const { exec } = require("child_process");',
1645
+ "+exec(`rm -rf ${req.query.dir}`);",
1646
+ "diff --git a/src/other.js b/src/other.js",
1647
+ "new file mode 100644",
1648
+ "--- /dev/null",
1649
+ "+++ b/src/other.js",
1650
+ "@@ -0,0 +1 @@",
1651
+ "+eval(req.query.code);",
1652
+ "",
1653
+ ].join("\n"), "utf8");
1654
+ const fresh = runCli(["sast", "--diff", secondDiff, "--baseline", ".oc-pilot/baseline.json", "--cwd", dir, "--json"], dir);
1655
+ assert.equal(fresh.status, 1, fresh.stderr);
1656
+ const freshPayload = JSON.parse(fresh.stdout);
1657
+ assert.equal(freshPayload.baselineSuppressed, 1);
1658
+ assert.deepEqual(freshPayload.findings.map((finding) => finding.rule), ["code-injection"]);
1659
+ }
1660
+ finally {
1661
+ rmSync(dir, { recursive: true, force: true });
1662
+ }
1663
+ });
1664
+ test("fix --from-sast repairs a vulnerable line", () => {
1665
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-fix-sast-"));
1666
+ try {
1667
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1668
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
1669
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1670
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1671
+ mkdirSync(join(dir, "src"), { recursive: true });
1672
+ writeFileSync(join(dir, "src", "app.js"), 'const { exec } = require("child_process");\nexec(req.query.cmd);\n', "utf8");
1673
+ const diff = [
1674
+ "diff --git a/src/app.js b/src/app.js",
1675
+ "new file mode 100644",
1676
+ "--- /dev/null",
1677
+ "+++ b/src/app.js",
1678
+ "@@ -0,0 +1,2 @@",
1679
+ '+const { exec } = require("child_process");',
1680
+ "+exec(req.query.cmd);",
1681
+ "",
1682
+ ].join("\n");
1683
+ const diffPath = join(dir, "changes.diff");
1684
+ writeFileSync(diffPath, diff, "utf8");
1685
+ const patch = [
1686
+ "diff --git a/src/app.js b/src/app.js",
1687
+ "--- a/src/app.js",
1688
+ "+++ b/src/app.js",
1689
+ "@@ -1,2 +1 @@",
1690
+ '-const { exec } = require("child_process");',
1691
+ "-exec(req.query.cmd);",
1692
+ "+console.log(req.query.cmd);",
1693
+ "",
1694
+ ].join("\n");
1695
+ const fake = installFakeOpenCode(dir);
1696
+ const result = runCli(["fix", "--from-sast", "--diff", diffPath, "--apply", "--no-tests", "--cwd", dir, "--json"], dir, { ...fake, FAKE_PATCH_TEXT: patch });
1697
+ assert.equal(result.status, 0, result.stderr);
1698
+ const payload = JSON.parse(result.stdout);
1699
+ assert.equal(payload.applied, true);
1700
+ assert.deepEqual(payload.sast, { before: 1, after: 0, remaining: 0 });
1701
+ assert.match(readFileSync(join(dir, "src", "app.js"), "utf8"), /console\.log/);
1702
+ }
1703
+ finally {
1704
+ rmSync(dir, { recursive: true, force: true });
1705
+ }
1706
+ });
1707
+ test("fix --from-authz repairs an IDOR route", () => {
1708
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-cli-fix-authz-"));
1709
+ try {
1710
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1711
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
1712
+ spawnSync("git", ["add", "base.txt"], { cwd: dir });
1713
+ spawnSync("git", ["-c", "user.email=test@example.com", "-c", "user.name=Test", "commit", "-qm", "init"], { cwd: dir });
1714
+ const source = [
1715
+ 'const express = require("express");',
1716
+ "const router = express.Router();",
1717
+ 'router.get("/users/:id", async (req, res) => {',
1718
+ " const user = await db.user.findUnique({ where: { id: req.params.id } });",
1719
+ " res.json(user);",
1720
+ "});",
1721
+ "module.exports = router;",
1722
+ "",
1723
+ ].join("\n");
1724
+ mkdirSync(join(dir, "src"), { recursive: true });
1725
+ writeFileSync(join(dir, "src", "routes.js"), source, "utf8");
1726
+ const diffPath = join(dir, "changes.diff");
1727
+ writeFileSync(diffPath, [
1728
+ "diff --git a/src/routes.js b/src/routes.js",
1729
+ "new file mode 100644",
1730
+ "--- /dev/null",
1731
+ "+++ b/src/routes.js",
1732
+ "@@ -0,0 +1,7 @@",
1733
+ '+const express = require("express");',
1734
+ "+const router = express.Router();",
1735
+ '+router.get("/users/:id", async (req, res) => {',
1736
+ "+ const user = await db.user.findUnique({ where: { id: req.params.id } });",
1737
+ "+ res.json(user);",
1738
+ "+});",
1739
+ "+module.exports = router;",
1740
+ "",
1741
+ ].join("\n"), "utf8");
1742
+ const patch = [
1743
+ "diff --git a/src/routes.js b/src/routes.js",
1744
+ "--- a/src/routes.js",
1745
+ "+++ b/src/routes.js",
1746
+ "@@ -3,4 +3,6 @@",
1747
+ ' router.get("/users/:id", async (req, res) => {',
1748
+ " const user = await db.user.findUnique({ where: { id: req.params.id } });",
1749
+ "+ if (!req.user) return res.status(401).end();",
1750
+ "+ if (user.id !== req.user.id) return res.status(403).end();",
1751
+ " res.json(user);",
1752
+ " });",
1753
+ "",
1754
+ ].join("\n");
1755
+ const fake = installFakeOpenCode(dir);
1756
+ const result = runCli(["fix", "--from-authz", "--diff", diffPath, "--apply", "--no-tests", "--cwd", dir, "--json"], dir, { ...fake, FAKE_PATCH_TEXT: patch });
1757
+ assert.equal(result.status, 0, result.stderr);
1758
+ const payload = JSON.parse(result.stdout);
1759
+ assert.equal(payload.applied, true);
1760
+ assert.ok((payload.authz?.before ?? 0) >= 1);
1761
+ assert.equal(payload.authz?.after, 0);
1762
+ assert.equal(payload.authz?.remaining, 0);
1763
+ assert.match(readFileSync(join(dir, "src", "routes.js"), "utf8"), /req\.user\.id/);
1764
+ }
1765
+ finally {
1766
+ rmSync(dir, { recursive: true, force: true });
1767
+ }
1768
+ });
559
1769
  //# sourceMappingURL=cli.test.js.map