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/mcp.test.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtempSync, readFileSync, writeFileSync, rmSync } from "node:fs";
2
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { test } from "node:test";
7
+ import { applyResultOptions, patchSummary } from "./mcp.js";
7
8
  const MCP = resolve("dist/mcp.js");
9
+ const CLI = resolve("dist/cli.js");
8
10
  const AWS_KEY = ["AKIA", "IOSFODNN7EXAMPLE"].join("");
9
11
  function runServer(dir, requests, args = []) {
10
12
  const input = `${requests.map((request) => JSON.stringify(request)).join("\n")}\n`;
@@ -92,6 +94,48 @@ test("MCP is read-only by default and rejects paths outside the project", () =>
92
94
  method: "tools/call",
93
95
  params: { name: "test_fix", arguments: { diff: "diff --git a/a b/a\n" } },
94
96
  },
97
+ {
98
+ jsonrpc: "2.0",
99
+ id: 4,
100
+ method: "tools/call",
101
+ params: { name: "sast", arguments: { paths: ["app.js"], update_baseline: true } },
102
+ },
103
+ {
104
+ jsonrpc: "2.0",
105
+ id: 5,
106
+ method: "tools/call",
107
+ params: { name: "mutate", arguments: { max_mutants: 1 } },
108
+ },
109
+ {
110
+ jsonrpc: "2.0",
111
+ id: 6,
112
+ method: "tools/call",
113
+ params: { name: "deps", arguments: { fix: true } },
114
+ },
115
+ {
116
+ jsonrpc: "2.0",
117
+ id: 7,
118
+ method: "tools/call",
119
+ params: { name: "test_gen", arguments: { apply: true } },
120
+ },
121
+ {
122
+ jsonrpc: "2.0",
123
+ id: 8,
124
+ method: "tools/call",
125
+ params: { name: "battle", arguments: { task: "x", apply: true } },
126
+ },
127
+ {
128
+ jsonrpc: "2.0",
129
+ id: 9,
130
+ method: "tools/call",
131
+ params: { name: "cleanup", arguments: { apply: true } },
132
+ },
133
+ {
134
+ jsonrpc: "2.0",
135
+ id: 10,
136
+ method: "tools/call",
137
+ params: { name: "echo", arguments: { submit: true, endpoint: "https://example.com/echo" } },
138
+ },
95
139
  ], []);
96
140
  assert.equal(status, 0, stderr);
97
141
  const byId = new Map(responses.map((response) => [response.id, response]));
@@ -104,6 +148,27 @@ test("MCP is read-only by default and rejects paths outside the project", () =>
104
148
  const testResult = byId.get(3)?.result;
105
149
  assert.equal(testResult?.isError, true);
106
150
  assert.match(testResult?.content?.[0]?.text ?? "", /read-only/);
151
+ const baselineResult = byId.get(4)?.result;
152
+ assert.equal(baselineResult?.isError, true);
153
+ assert.match(baselineResult?.content?.[0]?.text ?? "", /read-only/);
154
+ const mutateResult = byId.get(5)?.result;
155
+ assert.equal(mutateResult?.isError, true);
156
+ assert.match(mutateResult?.content?.[0]?.text ?? "", /read-only/);
157
+ const depsResult = byId.get(6)?.result;
158
+ assert.equal(depsResult?.isError, true);
159
+ assert.match(depsResult?.content?.[0]?.text ?? "", /read-only/);
160
+ const testGenResult = byId.get(7)?.result;
161
+ assert.equal(testGenResult?.isError, true);
162
+ assert.match(testGenResult?.content?.[0]?.text ?? "", /read-only/);
163
+ const battleResult = byId.get(8)?.result;
164
+ assert.equal(battleResult?.isError, true);
165
+ assert.match(battleResult?.content?.[0]?.text ?? "", /read-only/);
166
+ const cleanupResult = byId.get(9)?.result;
167
+ assert.equal(cleanupResult?.isError, true);
168
+ assert.match(cleanupResult?.content?.[0]?.text ?? "", /read-only/);
169
+ const echoResult = byId.get(10)?.result;
170
+ assert.equal(echoResult?.isError, true);
171
+ assert.match(echoResult?.content?.[0]?.text ?? "", /read-only/);
107
172
  }
108
173
  finally {
109
174
  rmSync(dir, { recursive: true, force: true });
@@ -125,4 +190,1677 @@ test("MCP rejects malformed JSON without crashing", () => {
125
190
  rmSync(dir, { recursive: true, force: true });
126
191
  }
127
192
  });
193
+ test("MCP exposes resources and resource templates", () => {
194
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-res-"));
195
+ try {
196
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
197
+ git(["init"]);
198
+ git(["config", "user.email", "test@example.com"]);
199
+ git(["config", "user.name", "Test"]);
200
+ git(["config", "core.autocrlf", "false"]);
201
+ writeFileSync(join(dir, "a.txt"), "v1\n");
202
+ writeFileSync(join(dir, "sample.txt"), `const key = "${AWS_KEY}"\n`);
203
+ git(["add", "."]);
204
+ git(["commit", "-m", "init"]);
205
+ writeFileSync(join(dir, "a.txt"), `v2\nconst key = "${AWS_KEY}"\n`);
206
+ git(["add", "a.txt"]);
207
+ const { status, responses, stderr } = runServer(dir, [
208
+ {
209
+ jsonrpc: "2.0",
210
+ id: 1,
211
+ method: "initialize",
212
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
213
+ },
214
+ { jsonrpc: "2.0", id: 2, method: "resources/list" },
215
+ { jsonrpc: "2.0", id: 3, method: "resources/templates/list" },
216
+ { jsonrpc: "2.0", id: 4, method: "resources/read", params: { uri: "oc-pilot://config" } },
217
+ { jsonrpc: "2.0", id: 5, method: "resources/read", params: { uri: "oc-pilot://prompt/en" } },
218
+ { jsonrpc: "2.0", id: 6, method: "resources/read", params: { uri: "oc-pilot://diff/staged" } },
219
+ { jsonrpc: "2.0", id: 7, method: "resources/read", params: { uri: "oc-pilot://scan/staged" } },
220
+ { jsonrpc: "2.0", id: 8, method: "resources/read", params: { uri: "oc-pilot://file/sample.txt" } },
221
+ { jsonrpc: "2.0", id: 9, method: "resources/read", params: { uri: "oc-pilot://file/..%2Fescape.txt" } },
222
+ { jsonrpc: "2.0", id: 10, method: "resources/read", params: { uri: "oc-pilot://unknown" } },
223
+ ]);
224
+ assert.equal(status, 0, stderr);
225
+ const byId = new Map(responses.map((response) => [response.id, response]));
226
+ const init = byId.get(1)?.result;
227
+ assert.ok(init?.capabilities?.resources, "resources capability missing");
228
+ assert.ok(init?.capabilities?.resourceTemplates, "resourceTemplates capability missing");
229
+ const list = byId.get(2)?.result;
230
+ const uris = new Set(list?.resources?.map((resource) => resource.uri));
231
+ for (const uri of ["oc-pilot://config", "oc-pilot://rules", "oc-pilot://version", "oc-pilot://prompt/en", "oc-pilot://prompt/ru"]) {
232
+ assert.ok(uris.has(uri), uri);
233
+ }
234
+ const templates = byId.get(3)?.result;
235
+ const templateIds = new Set(templates?.resourceTemplates?.map((template) => template.uriTemplate));
236
+ for (const template of ["oc-pilot://diff/{mode}", "oc-pilot://scan/{mode}", "oc-pilot://file/{+path}"]) {
237
+ assert.ok(templateIds.has(template), template);
238
+ }
239
+ const config = byId.get(4)?.result;
240
+ const configJson = JSON.parse(config?.contents?.[0]?.text ?? "{}");
241
+ assert.equal(configJson.config?.engine, "opencode");
242
+ const prompt = byId.get(5)?.result;
243
+ assert.match(prompt?.contents?.[0]?.text ?? "", /ROLE AND GOAL/);
244
+ const diff = byId.get(6)?.result;
245
+ const diffText = diff?.contents?.[0]?.text ?? "";
246
+ assert.match(diffText, /\+v2/);
247
+ assert.ok(!diffText.includes(AWS_KEY), "diff resource must redact secrets");
248
+ const scan = byId.get(7)?.result;
249
+ const scanText = scan?.contents?.[0]?.text ?? "";
250
+ assert.match(scanText, /AWS Access Key ID/);
251
+ assert.ok(!scanText.includes(AWS_KEY), "scan resource must not leak the raw secret");
252
+ const file = byId.get(8)?.result;
253
+ const fileText = file?.contents?.[0]?.text ?? "";
254
+ assert.match(fileText, /REDACTED/);
255
+ assert.ok(!fileText.includes(AWS_KEY), "file resource must redact secrets");
256
+ assert.equal(byId.get(9)?.error?.code, -32002);
257
+ assert.match(byId.get(9)?.error?.message ?? "", /inside the project/);
258
+ assert.equal(byId.get(10)?.error?.code, -32002);
259
+ }
260
+ finally {
261
+ rmSync(dir, { recursive: true, force: true });
262
+ }
263
+ });
264
+ test("MCP advertises slop and result caps in tools/list", () => {
265
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-tools-"));
266
+ try {
267
+ const { responses } = runServer(dir, [
268
+ {
269
+ jsonrpc: "2.0",
270
+ id: 1,
271
+ method: "initialize",
272
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
273
+ },
274
+ { jsonrpc: "2.0", id: 2, method: "tools/list" },
275
+ ]);
276
+ const tools = responses.find((response) => response.id === 2)?.result?.tools ?? [];
277
+ const slop = tools.find((tool) => tool.name === "slop");
278
+ assert.ok(slop, "slop tool missing");
279
+ assert.ok(slop.inputSchema.properties.max_findings, "max_findings missing on slop");
280
+ assert.ok(slop.inputSchema.properties.verbosity, "verbosity missing on slop");
281
+ const review = tools.find((tool) => tool.name === "review");
282
+ assert.ok(review?.inputSchema.properties.max_findings, "max_findings missing on review");
283
+ const preflight = tools.find((tool) => tool.name === "preflight");
284
+ assert.ok(preflight, "preflight tool missing");
285
+ assert.ok(preflight.inputSchema.properties.max_delta, "max_delta missing on preflight");
286
+ assert.ok(preflight.inputSchema.properties.fail_on_issue, "fail_on_issue missing on preflight");
287
+ assert.ok(preflight.inputSchema.properties.no_sast, "no_sast missing on preflight");
288
+ assert.ok(preflight.inputSchema.properties.no_authz, "no_authz missing on preflight");
289
+ const mutate = tools.find((tool) => tool.name === "mutate");
290
+ assert.ok(mutate, "mutate tool missing");
291
+ assert.ok(mutate.inputSchema.properties.max_mutants, "max_mutants missing on mutate");
292
+ assert.ok(mutate.inputSchema.properties.min_score, "min_score missing on mutate");
293
+ const fuzz = tools.find((tool) => tool.name === "fuzz");
294
+ assert.ok(fuzz, "fuzz tool missing");
295
+ assert.ok(fuzz.inputSchema.properties.duration_ms, "duration_ms missing on fuzz");
296
+ assert.ok(fuzz.inputSchema.properties.memory_mb, "memory_mb missing on fuzz");
297
+ assert.ok(fuzz.inputSchema.properties.fail_on_crash, "fail_on_crash missing on fuzz");
298
+ const flaky = tools.find((tool) => tool.name === "flaky");
299
+ assert.ok(flaky, "flaky tool missing");
300
+ assert.ok(flaky.inputSchema.properties.runs, "runs missing on flaky");
301
+ assert.ok(flaky.inputSchema.properties.fail_on_flaky, "fail_on_flaky missing on flaky");
302
+ assert.ok(flaky.inputSchema.properties.shuffle, "shuffle missing on flaky");
303
+ assert.ok(flaky.inputSchema.properties.seed, "seed missing on flaky");
304
+ const impact = tools.find((tool) => tool.name === "impact");
305
+ assert.ok(impact, "impact tool missing");
306
+ assert.ok(impact.inputSchema.properties.base, "base missing on impact");
307
+ assert.ok(impact.inputSchema.properties.staged, "staged missing on impact");
308
+ assert.ok(impact.inputSchema.properties.paths, "paths missing on impact");
309
+ const deps = tools.find((tool) => tool.name === "deps");
310
+ assert.ok(deps, "deps tool missing");
311
+ assert.ok(deps.inputSchema.properties.fail_on_issue, "fail_on_issue missing on deps");
312
+ assert.ok(deps.inputSchema.properties.fix, "fix missing on deps");
313
+ assert.ok(deps.inputSchema.properties.sbom, "sbom missing on deps");
314
+ const coverage = tools.find((tool) => tool.name === "coverage");
315
+ assert.ok(coverage, "coverage tool missing");
316
+ assert.ok(coverage.inputSchema.properties.coverage_file, "coverage_file missing on coverage");
317
+ assert.ok(coverage.inputSchema.properties.fail_under, "fail_under missing on coverage");
318
+ assert.ok(coverage.inputSchema.properties.max_uncovered, "max_uncovered missing on coverage");
319
+ const testGen = tools.find((tool) => tool.name === "test_gen");
320
+ assert.ok(testGen, "test_gen tool missing");
321
+ assert.ok(testGen.inputSchema.properties.apply, "apply missing on test_gen");
322
+ assert.ok(testGen.inputSchema.properties.no_run, "no_run missing on test_gen");
323
+ assert.ok(testGen.inputSchema.properties.coverage, "coverage missing on test_gen");
324
+ assert.ok(testGen.inputSchema.properties.from_slop, "from_slop missing on test_gen");
325
+ assert.ok(testGen.inputSchema.properties.rounds, "rounds missing on test_gen");
326
+ const reviewers = tools.find((tool) => tool.name === "reviewers");
327
+ assert.ok(reviewers, "reviewers tool missing");
328
+ assert.ok(reviewers.inputSchema.properties.limit, "limit missing on reviewers");
329
+ assert.ok(reviewers.inputSchema.properties.depth, "depth missing on reviewers");
330
+ assert.ok(reviewers.inputSchema.properties.exclude, "exclude missing on reviewers");
331
+ const testGuard = tools.find((tool) => tool.name === "test_guard");
332
+ assert.ok(testGuard, "test_guard tool missing");
333
+ assert.ok(testGuard.inputSchema.properties.fail_on_weakening, "fail_on_weakening missing on test_guard");
334
+ assert.ok(testGuard.inputSchema.properties.staged, "staged missing on test_guard");
335
+ const envSync = tools.find((tool) => tool.name === "env_sync");
336
+ assert.ok(envSync, "env_sync tool missing");
337
+ assert.ok(envSync.inputSchema.properties.fail_on_missing, "fail_on_missing missing on env_sync");
338
+ assert.ok(envSync.inputSchema.properties.staged, "staged missing on env_sync");
339
+ const mockLeak = tools.find((tool) => tool.name === "mock_leak");
340
+ assert.ok(mockLeak, "mock_leak tool missing");
341
+ assert.ok(mockLeak.inputSchema.properties.fail_on_leak, "fail_on_leak missing on mock_leak");
342
+ assert.ok(mockLeak.inputSchema.properties.staged, "staged missing on mock_leak");
343
+ const ecosystem = tools.find((tool) => tool.name === "ecosystem_verify");
344
+ assert.ok(ecosystem, "ecosystem_verify tool missing");
345
+ assert.ok(ecosystem.inputSchema.properties.online, "online missing on ecosystem_verify");
346
+ assert.ok(ecosystem.inputSchema.properties.min_age_days, "min_age_days missing on ecosystem_verify");
347
+ assert.ok(ecosystem.inputSchema.properties.fail_on_issue, "fail_on_issue missing on ecosystem_verify");
348
+ const pack = tools.find((tool) => tool.name === "pack");
349
+ assert.ok(pack, "pack tool missing");
350
+ assert.ok(pack.inputSchema.properties.task, "task missing on pack");
351
+ assert.ok(pack.inputSchema.properties.budget, "budget missing on pack");
352
+ assert.ok(pack.inputSchema.properties.test_log, "test_log missing on pack");
353
+ assert.ok(pack.inputSchema.properties.rules, "rules missing on pack");
354
+ const packHologram = tools.find((tool) => tool.name === "pack_hologram");
355
+ assert.ok(packHologram, "pack_hologram tool missing");
356
+ assert.ok(packHologram.inputSchema.properties.max_tokens, "max_tokens missing on pack_hologram");
357
+ const zoomSymbol = tools.find((tool) => tool.name === "zoom_symbol");
358
+ assert.ok(zoomSymbol, "zoom_symbol tool missing");
359
+ assert.ok(zoomSymbol.inputSchema.properties.file, "file missing on zoom_symbol");
360
+ assert.ok(zoomSymbol.inputSchema.properties.symbol, "symbol missing on zoom_symbol");
361
+ const invariants = tools.find((tool) => tool.name === "invariants");
362
+ assert.ok(invariants, "invariants tool missing");
363
+ assert.ok(invariants.inputSchema.properties.strict, "strict missing on invariants");
364
+ assert.ok(invariants.inputSchema.properties.fail_on, "fail_on missing on invariants");
365
+ assert.ok(invariants.inputSchema.properties.no_cache, "no_cache missing on invariants");
366
+ const contractDrift = tools.find((tool) => tool.name === "contract_drift");
367
+ assert.ok(contractDrift, "contract_drift tool missing");
368
+ assert.ok(contractDrift.inputSchema.properties.fail_on_drift, "fail_on_drift missing on contract_drift");
369
+ assert.ok(contractDrift.inputSchema.properties.staged, "staged missing on contract_drift");
370
+ const bankruptcy = tools.find((tool) => tool.name === "bankruptcy");
371
+ assert.ok(bankruptcy, "bankruptcy tool missing");
372
+ assert.ok(bankruptcy.inputSchema.properties.history_days, "history_days missing on bankruptcy");
373
+ assert.ok(bankruptcy.inputSchema.properties.explain, "explain missing on bankruptcy");
374
+ const queryAst = tools.find((tool) => tool.name === "query_ast");
375
+ assert.ok(queryAst, "query_ast tool missing");
376
+ assert.ok(queryAst.inputSchema.properties.query, "query missing on query_ast");
377
+ assert.ok(queryAst.inputSchema.properties.limit, "limit missing on query_ast");
378
+ const sandbox = tools.find((tool) => tool.name === "sandbox");
379
+ assert.ok(sandbox, "sandbox tool missing");
380
+ assert.ok(sandbox.inputSchema.properties.file, "file missing on sandbox");
381
+ assert.ok(sandbox.inputSchema.properties.timeout_ms, "timeout_ms missing on sandbox");
382
+ const hivemind = tools.find((tool) => tool.name === "hivemind_status");
383
+ assert.ok(hivemind, "hivemind_status tool missing");
384
+ const antiRegression = tools.find((tool) => tool.name === "anti_regression");
385
+ assert.ok(antiRegression, "anti_regression tool missing");
386
+ assert.ok(antiRegression.inputSchema.properties.max_commits, "max_commits missing on anti_regression");
387
+ assert.ok(antiRegression.inputSchema.properties.since, "since missing on anti_regression");
388
+ assert.ok(antiRegression.inputSchema.properties.fail_on_regression, "fail_on_regression missing on anti_regression");
389
+ const boundaries = tools.find((tool) => tool.name === "boundaries");
390
+ assert.ok(boundaries, "boundaries tool missing");
391
+ assert.ok(boundaries.inputSchema.properties.config, "config missing on boundaries");
392
+ assert.ok(boundaries.inputSchema.properties.fail_on_violation, "fail_on_violation missing on boundaries");
393
+ const testPlan = tools.find((tool) => tool.name === "test_plan");
394
+ assert.ok(testPlan, "test_plan tool missing");
395
+ assert.ok(testPlan.inputSchema.properties.strategy, "strategy missing on test_plan");
396
+ assert.ok(testPlan.inputSchema.properties.max_tests, "max_tests missing on test_plan");
397
+ assert.ok(testPlan.inputSchema.properties.fail_on_uncovered, "fail_on_uncovered missing on test_plan");
398
+ const complianceVerify = tools.find((tool) => tool.name === "compliance_verify");
399
+ assert.ok(complianceVerify, "compliance_verify tool missing");
400
+ assert.ok(complianceVerify.inputSchema.properties.manifest, "manifest missing on compliance_verify");
401
+ assert.ok(complianceVerify.inputSchema.properties.public_key, "public_key missing on compliance_verify");
402
+ const brag = tools.find((tool) => tool.name === "brag");
403
+ assert.ok(brag, "brag tool missing");
404
+ assert.ok(brag.inputSchema.properties.since_days, "since_days missing on brag");
405
+ assert.ok(brag.inputSchema.properties.no_paths, "no_paths missing on brag");
406
+ assert.ok(brag.inputSchema.properties.svg, "svg missing on brag");
407
+ const battle = tools.find((tool) => tool.name === "battle");
408
+ assert.ok(battle, "battle tool missing");
409
+ assert.ok(battle.inputSchema.properties.task, "task missing on battle");
410
+ assert.ok(battle.inputSchema.properties.rounds, "rounds missing on battle");
411
+ assert.ok(battle.inputSchema.properties.shadow, "shadow missing on battle");
412
+ const cleanup = tools.find((tool) => tool.name === "cleanup");
413
+ assert.ok(cleanup, "cleanup tool missing");
414
+ assert.ok(cleanup.inputSchema.properties.apply, "apply missing on cleanup");
415
+ assert.ok(cleanup.inputSchema.properties.fail_on_task, "fail_on_task missing on cleanup");
416
+ const archDiff = tools.find((tool) => tool.name === "arch_diff");
417
+ assert.ok(archDiff, "arch_diff tool missing");
418
+ assert.ok(archDiff.inputSchema.properties.max_edges, "max_edges missing on arch_diff");
419
+ assert.ok(archDiff.inputSchema.properties.fail_on_removed, "fail_on_removed missing on arch_diff");
420
+ const roast = tools.find((tool) => tool.name === "roast");
421
+ assert.ok(roast, "roast tool missing");
422
+ assert.ok(roast.inputSchema.properties.persona, "persona missing on roast");
423
+ assert.ok(roast.inputSchema.properties.context, "context missing on roast");
424
+ const cinema = tools.find((tool) => tool.name === "cinema");
425
+ assert.ok(cinema, "cinema tool missing");
426
+ assert.ok(cinema.inputSchema.properties.since_days, "since_days missing on cinema");
427
+ assert.ok(cinema.inputSchema.properties.max_frames, "max_frames missing on cinema");
428
+ assert.ok(cinema.inputSchema.properties.cast, "cast missing on cinema");
429
+ const bounty = tools.find((tool) => tool.name === "bounty");
430
+ assert.ok(bounty, "bounty tool missing");
431
+ assert.ok(bounty.inputSchema.properties.limit, "limit missing on bounty");
432
+ assert.ok(bounty.inputSchema.properties.since_days, "since_days missing on bounty");
433
+ const autopsy = tools.find((tool) => tool.name === "autopsy");
434
+ assert.ok(autopsy, "autopsy tool missing");
435
+ assert.ok(autopsy.inputSchema.properties.commit, "commit missing on autopsy");
436
+ assert.ok(autopsy.inputSchema.properties.depth, "depth missing on autopsy");
437
+ assert.ok(autopsy.inputSchema.properties.fail_on_risk, "fail_on_risk missing on autopsy");
438
+ const echo = tools.find((tool) => tool.name === "echo");
439
+ assert.ok(echo, "echo tool missing");
440
+ assert.ok(echo.inputSchema.properties.name, "name missing on echo");
441
+ assert.ok(echo.inputSchema.properties.submit, "submit missing on echo");
442
+ assert.ok(echo.inputSchema.properties.endpoint, "endpoint missing on echo");
443
+ assert.ok(echo.inputSchema.properties.svg, "svg missing on echo");
444
+ const sast = tools.find((tool) => tool.name === "sast");
445
+ assert.ok(sast, "sast tool missing");
446
+ assert.ok(sast.inputSchema.properties.fail_on_issue, "fail_on_issue missing on sast");
447
+ assert.ok(sast.inputSchema.properties.ignore, "ignore missing on sast");
448
+ assert.ok(sast.inputSchema.properties.baseline, "baseline missing on sast");
449
+ assert.ok(sast.inputSchema.properties.update_baseline, "update_baseline missing on sast");
450
+ const authz = tools.find((tool) => tool.name === "authz");
451
+ assert.ok(authz, "authz tool missing");
452
+ assert.ok(authz.inputSchema.properties.public, "public missing on authz");
453
+ assert.ok(authz.inputSchema.properties.fail_on_issue, "fail_on_issue missing on authz");
454
+ assert.ok(authz.inputSchema.properties.baseline, "baseline missing on authz");
455
+ assert.ok(authz.inputSchema.properties.update_baseline, "update_baseline missing on authz");
456
+ const fix = tools.find((tool) => tool.name === "fix");
457
+ assert.ok(fix, "fix tool missing");
458
+ assert.ok(fix.inputSchema.properties.from_sast, "from_sast missing on fix");
459
+ assert.ok(fix.inputSchema.properties.from_authz, "from_authz missing on fix");
460
+ assert.ok(fix.inputSchema.properties.shadow, "shadow missing on fix");
461
+ const ready = tools.find((tool) => tool.name === "ready");
462
+ assert.ok(ready, "ready tool missing");
463
+ assert.ok(ready.inputSchema.properties.fail_under, "fail_under missing on ready");
464
+ const receiptVerify = tools.find((tool) => tool.name === "receipt_verify");
465
+ assert.ok(receiptVerify, "receipt_verify tool missing");
466
+ assert.ok(receiptVerify.inputSchema.properties.receipt, "receipt missing on receipt_verify");
467
+ assert.ok(receiptVerify.inputSchema.properties.public_key, "public_key missing on receipt_verify");
468
+ }
469
+ finally {
470
+ rmSync(dir, { recursive: true, force: true });
471
+ }
472
+ });
473
+ test("MCP slop tool returns the Slop Index offline", () => {
474
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-slop-"));
475
+ try {
476
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
477
+ git(["init"]);
478
+ git(["config", "user.email", "test@example.com"]);
479
+ git(["config", "user.name", "Test"]);
480
+ git(["config", "core.autocrlf", "false"]);
481
+ writeFileSync(join(dir, "wallet.go"), "package main\n\nfunc main() {}\n", "utf8");
482
+ git(["add", "."]);
483
+ git(["commit", "-m", "init"]);
484
+ const { responses, stderr } = runServer(dir, [
485
+ {
486
+ jsonrpc: "2.0",
487
+ id: 1,
488
+ method: "initialize",
489
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
490
+ },
491
+ {
492
+ jsonrpc: "2.0",
493
+ id: 2,
494
+ method: "tools/call",
495
+ params: { name: "slop", arguments: { verbosity: "compact", no_blame: true } },
496
+ },
497
+ ]);
498
+ const result = responses.find((response) => response.id === 2)?.result;
499
+ assert.notEqual(result?.isError, true, stderr);
500
+ assert.match(result?.content?.[0]?.text ?? "", /Slop Index/);
501
+ assert.equal(typeof result?.structuredContent?.index, "number");
502
+ }
503
+ finally {
504
+ rmSync(dir, { recursive: true, force: true });
505
+ }
506
+ });
507
+ test("MCP preflight returns a failing verdict without a tool error", () => {
508
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-preflight-"));
509
+ try {
510
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
511
+ git(["init"]);
512
+ git(["config", "user.email", "test@example.com"]);
513
+ git(["config", "user.name", "Test"]);
514
+ git(["config", "core.autocrlf", "false"]);
515
+ writeFileSync(join(dir, "wallet.go"), "package main\n\nfunc main() {}\n", "utf8");
516
+ git(["add", "."]);
517
+ git(["commit", "-m", "init"]);
518
+ const diff = [
519
+ "diff --git a/src/app.ts b/src/app.ts",
520
+ "new file mode 100644",
521
+ "--- /dev/null",
522
+ "+++ b/src/app.ts",
523
+ "@@ -0,0 +1 @@",
524
+ '+const key = "AKIAIOSFODNN7EXAMPLE";',
525
+ "",
526
+ ].join("\n");
527
+ const { responses, stderr } = runServer(dir, [
528
+ {
529
+ jsonrpc: "2.0",
530
+ id: 1,
531
+ method: "initialize",
532
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
533
+ },
534
+ {
535
+ jsonrpc: "2.0",
536
+ id: 2,
537
+ method: "tools/call",
538
+ params: { name: "preflight", arguments: { diff, no_slop: true } },
539
+ },
540
+ ]);
541
+ const result = responses.find((response) => response.id === 2)?.result;
542
+ assert.notEqual(result?.isError, true, stderr);
543
+ assert.equal(result?._meta?.exitCode, 1);
544
+ assert.equal(result?.structuredContent?.status, "fail");
545
+ const secrets = result?.structuredContent?.checks?.find((check) => check.id === "secrets");
546
+ assert.equal(secrets?.status, "fail");
547
+ assert.match(result?.content?.[0]?.text ?? "", /"status": "fail"/);
548
+ }
549
+ finally {
550
+ rmSync(dir, { recursive: true, force: true });
551
+ }
552
+ });
553
+ test("MCP ready returns the readiness score without a tool error", () => {
554
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-ready-"));
555
+ try {
556
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
557
+ git(["init"]);
558
+ git(["config", "user.email", "test@example.com"]);
559
+ git(["config", "user.name", "Test"]);
560
+ git(["config", "core.autocrlf", "false"]);
561
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", private: true, scripts: { test: 'node -e "process.exit(0)"' } }), "utf8");
562
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({ name: "demo", lockfileVersion: 3, packages: { "": { name: "demo" } } }), "utf8");
563
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
564
+ git(["add", "."]);
565
+ git(["commit", "-m", "init"]);
566
+ const { responses, stderr } = runServer(dir, [
567
+ {
568
+ jsonrpc: "2.0",
569
+ id: 1,
570
+ method: "initialize",
571
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
572
+ },
573
+ {
574
+ jsonrpc: "2.0",
575
+ id: 2,
576
+ method: "tools/call",
577
+ params: { name: "ready", arguments: { fail_under: 85 } },
578
+ },
579
+ ]);
580
+ const result = responses.find((response) => response.id === 2)?.result;
581
+ assert.notEqual(result?.isError, true, stderr);
582
+ assert.equal(result?._meta?.exitCode, 1);
583
+ assert.equal(typeof result?.structuredContent?.score, "number");
584
+ assert.equal(result?.structuredContent?.checks?.find((check) => check.id === "tests")?.status, "pass");
585
+ assert.match(result?.content?.[0]?.text ?? "", /"grade"/);
586
+ }
587
+ finally {
588
+ rmSync(dir, { recursive: true, force: true });
589
+ }
590
+ });
591
+ test("MCP receipt_verify rejects a missing and a tampered receipt", () => {
592
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-receipt-"));
593
+ try {
594
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
595
+ git(["init"]);
596
+ git(["config", "user.email", "test@example.com"]);
597
+ git(["config", "user.name", "Test"]);
598
+ git(["config", "core.autocrlf", "false"]);
599
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
600
+ git(["add", "."]);
601
+ git(["commit", "-m", "init"]);
602
+ const call = () => {
603
+ const { responses, stderr } = runServer(dir, [
604
+ {
605
+ jsonrpc: "2.0",
606
+ id: 1,
607
+ method: "initialize",
608
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
609
+ },
610
+ {
611
+ jsonrpc: "2.0",
612
+ id: 2,
613
+ method: "tools/call",
614
+ params: { name: "receipt_verify", arguments: {} },
615
+ },
616
+ ]);
617
+ const result = responses.find((response) => response.id === 2)?.result;
618
+ return { result, stderr };
619
+ };
620
+ const missing = call();
621
+ assert.notEqual(missing.result?.isError, true, missing.stderr);
622
+ assert.equal(missing.result?._meta?.exitCode, 1);
623
+ assert.equal(missing.result?.structuredContent?.ok, false);
624
+ const created = spawnSync(process.execPath, [CLI, "receipt", "--cwd", dir, "--no-run", "--json"], {
625
+ cwd: dir,
626
+ encoding: "utf8",
627
+ timeout: 60_000,
628
+ });
629
+ assert.equal(created.status, 0, created.stderr);
630
+ const valid = call();
631
+ assert.notEqual(valid.result?.isError, true, valid.stderr);
632
+ assert.equal(valid.result?._meta?.exitCode, 0);
633
+ assert.equal(valid.result?.structuredContent?.ok, true);
634
+ assert.equal(valid.result?.structuredContent?.hashOk, true);
635
+ assert.equal(valid.result?.structuredContent?.stale, false);
636
+ const path = join(dir, ".git", "oc-pilot", "receipt.json");
637
+ const receipt = JSON.parse(readFileSync(path, "utf8"));
638
+ receipt.createdAt = "2020-01-01T00:00:00.000Z";
639
+ writeFileSync(path, JSON.stringify(receipt, null, 2), "utf8");
640
+ const tampered = call();
641
+ assert.equal(tampered.result?._meta?.exitCode, 1);
642
+ assert.equal(tampered.result?.structuredContent?.ok, false);
643
+ assert.equal(tampered.result?.structuredContent?.hashOk, false);
644
+ }
645
+ finally {
646
+ rmSync(dir, { recursive: true, force: true });
647
+ }
648
+ });
649
+ test("MCP sast returns findings without a tool error", () => {
650
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-sast-"));
651
+ try {
652
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
653
+ git(["init"]);
654
+ git(["config", "user.email", "test@example.com"]);
655
+ git(["config", "user.name", "Test"]);
656
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
657
+ git(["add", "."]);
658
+ git(["commit", "-m", "init"]);
659
+ writeFileSync(join(dir, "app.js"), 'const { exec } = require("child_process");\nexec(`rm -rf ${req.query.dir}`);\n', "utf8");
660
+ const { responses, stderr } = runServer(dir, [
661
+ {
662
+ jsonrpc: "2.0",
663
+ id: 1,
664
+ method: "initialize",
665
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
666
+ },
667
+ {
668
+ jsonrpc: "2.0",
669
+ id: 2,
670
+ method: "tools/call",
671
+ params: { name: "sast", arguments: { paths: ["app.js"], fail_on_issue: "high" } },
672
+ },
673
+ ]);
674
+ const result = responses.find((response) => response.id === 2)?.result;
675
+ assert.notEqual(result?.isError, true, stderr);
676
+ assert.equal(result?._meta?.exitCode, 1);
677
+ assert.equal(result?.structuredContent?.findings?.[0]?.rule, "command-injection");
678
+ assert.equal(result?.structuredContent?.findings?.[0]?.severity, "high");
679
+ }
680
+ finally {
681
+ rmSync(dir, { recursive: true, force: true });
682
+ }
683
+ });
684
+ test("MCP authz returns route findings without a tool error", () => {
685
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-authz-"));
686
+ try {
687
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
688
+ git(["init"]);
689
+ git(["config", "user.email", "test@example.com"]);
690
+ git(["config", "user.name", "Test"]);
691
+ writeFileSync(join(dir, "base.txt"), "x\n", "utf8");
692
+ git(["add", "."]);
693
+ git(["commit", "-m", "init"]);
694
+ writeFileSync(join(dir, "app.js"), 'router.get("/users/:id", async (req, res) => { const user = await db.user.findUnique({ where: { id: req.params.id } }); res.json(user); });\n', "utf8");
695
+ const { responses, stderr } = runServer(dir, [
696
+ {
697
+ jsonrpc: "2.0",
698
+ id: 1,
699
+ method: "initialize",
700
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
701
+ },
702
+ {
703
+ jsonrpc: "2.0",
704
+ id: 2,
705
+ method: "tools/call",
706
+ params: { name: "authz", arguments: { paths: ["app.js"], fail_on_issue: "high" } },
707
+ },
708
+ ]);
709
+ const result = responses.find((response) => response.id === 2)?.result;
710
+ assert.notEqual(result?.isError, true, stderr);
711
+ assert.equal(result?._meta?.exitCode, 1);
712
+ assert.equal(result?.structuredContent?.routes?.length, 1);
713
+ const rules = (result?.structuredContent?.findings ?? []).map((finding) => finding.rule);
714
+ assert.ok(rules.includes("route-no-auth"), `rules: ${rules.join(",")}`);
715
+ assert.ok(rules.includes("idor"), `rules: ${rules.join(",")}`);
716
+ }
717
+ finally {
718
+ rmSync(dir, { recursive: true, force: true });
719
+ }
720
+ });
721
+ test("MCP mutate reports surviving mutants and restores the source", () => {
722
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-mutate-"));
723
+ try {
724
+ const source = "export function same(a, b) {\n return a === b;\n}\n";
725
+ writeFileSync(join(dir, "app.js"), source, "utf8");
726
+ const diff = [
727
+ "diff --git a/app.js b/app.js",
728
+ "new file mode 100644",
729
+ "--- /dev/null",
730
+ "+++ b/app.js",
731
+ "@@ -0,0 +1,3 @@",
732
+ "+export function same(a, b) {",
733
+ "+ return a === b;",
734
+ "+}",
735
+ "",
736
+ ].join("\n");
737
+ const { responses, stderr } = runServer(dir, [
738
+ {
739
+ jsonrpc: "2.0",
740
+ id: 1,
741
+ method: "initialize",
742
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
743
+ },
744
+ {
745
+ jsonrpc: "2.0",
746
+ id: 2,
747
+ method: "tools/call",
748
+ params: {
749
+ name: "mutate",
750
+ arguments: {
751
+ diff,
752
+ command: 'node -e "process.exit(0)"',
753
+ max_mutants: 2,
754
+ min_score: 100,
755
+ verbosity: "compact",
756
+ },
757
+ },
758
+ },
759
+ ], ["--allow-write"]);
760
+ const result = responses.find((response) => response.id === 2)?.result;
761
+ assert.notEqual(result?.isError, true, stderr);
762
+ assert.equal(result?._meta?.exitCode, 1);
763
+ assert.equal(result?.structuredContent?.survived, 1);
764
+ assert.equal(result?.structuredContent?.score, 0);
765
+ assert.equal(result?.structuredContent?.mutants?.length, 1);
766
+ assert.match(result?.content?.[0]?.text ?? "", /mutation score 0\/100, 1 survived/);
767
+ assert.equal(readFileSync(join(dir, "app.js"), "utf8"), source);
768
+ }
769
+ finally {
770
+ rmSync(dir, { recursive: true, force: true });
771
+ }
772
+ });
773
+ test("MCP deps --why explains the lockfile chain", () => {
774
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-deps-"));
775
+ try {
776
+ spawnSync("git", ["init", "-q"], { cwd: dir });
777
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { express: "^4.18.0" } }, null, 2), "utf8");
778
+ writeFileSync(join(dir, "package-lock.json"), JSON.stringify({
779
+ name: "demo",
780
+ version: "1.0.0",
781
+ lockfileVersion: 3,
782
+ requires: true,
783
+ packages: {
784
+ "": { name: "demo", version: "1.0.0", dependencies: { express: "^4.18.0" } },
785
+ "node_modules/express": { version: "4.18.2", dependencies: { "body-parser": "1.20.1" } },
786
+ "node_modules/body-parser": { version: "1.20.1", dependencies: { qs: "6.11.0" } },
787
+ "node_modules/qs": { version: "6.11.0" },
788
+ },
789
+ }, null, 2), "utf8");
790
+ writeFileSync(join(dir, "app.ts"), 'import express from "express";\n\nexport const app = express();\n', "utf8");
791
+ const { responses, stderr } = runServer(dir, [
792
+ {
793
+ jsonrpc: "2.0",
794
+ id: 1,
795
+ method: "initialize",
796
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
797
+ },
798
+ {
799
+ jsonrpc: "2.0",
800
+ id: 2,
801
+ method: "tools/call",
802
+ params: { name: "deps", arguments: { why: "qs" } },
803
+ },
804
+ ]);
805
+ const result = responses.find((response) => response.id === 2)?.result;
806
+ assert.notEqual(result?.isError, true, stderr);
807
+ assert.equal(result?._meta?.exitCode, 0);
808
+ assert.equal(result?.structuredContent?.name, "qs");
809
+ assert.deepEqual(result?.structuredContent?.versions, ["6.11.0"]);
810
+ assert.deepEqual(result?.structuredContent?.dependents, ["body-parser"]);
811
+ assert.deepEqual(result?.structuredContent?.chains, [["express", "body-parser", "qs"]]);
812
+ }
813
+ finally {
814
+ rmSync(dir, { recursive: true, force: true });
815
+ }
816
+ });
817
+ test("MCP coverage gates uncovered changed lines", () => {
818
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-coverage-"));
819
+ try {
820
+ mkdirSync(join(dir, "coverage"), { recursive: true });
821
+ writeFileSync(join(dir, "coverage", "lcov.info"), ["SF:src/app.ts", "DA:1,1", "DA:2,0", "end_of_record", ""].join("\n"), "utf8");
822
+ const diff = [
823
+ "diff --git a/src/app.ts b/src/app.ts",
824
+ "new file mode 100644",
825
+ "--- /dev/null",
826
+ "+++ b/src/app.ts",
827
+ "@@ -0,0 +1,3 @@",
828
+ "+export function add(a: number, b: number) {",
829
+ "+ return a + b;",
830
+ "+}",
831
+ "",
832
+ ].join("\n");
833
+ const { responses, stderr } = runServer(dir, [
834
+ {
835
+ jsonrpc: "2.0",
836
+ id: 1,
837
+ method: "initialize",
838
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
839
+ },
840
+ {
841
+ jsonrpc: "2.0",
842
+ id: 2,
843
+ method: "tools/call",
844
+ params: {
845
+ name: "coverage",
846
+ arguments: {
847
+ diff,
848
+ coverage_file: "coverage/lcov.info",
849
+ fail_under: 80,
850
+ verbosity: "compact",
851
+ },
852
+ },
853
+ },
854
+ ]);
855
+ const result = responses.find((response) => response.id === 2)?.result;
856
+ assert.notEqual(result?.isError, true, stderr);
857
+ assert.equal(result?._meta?.exitCode, 1);
858
+ assert.equal(result?.structuredContent?.coveragePercent, 50);
859
+ assert.equal(result?.structuredContent?.uncoveredLines, 1);
860
+ assert.deepEqual(result?.structuredContent?.gaps, [{ file: "src/app.ts", uncovered: [2] }]);
861
+ assert.equal(result?.structuredContent?.gate?.passed, false);
862
+ assert.match(result?.content?.[0]?.text ?? "", /1 uncovered changed line in 1 file \(50%\)/);
863
+ }
864
+ finally {
865
+ rmSync(dir, { recursive: true, force: true });
866
+ }
867
+ });
868
+ test("MCP reviewers ranks authors from git history", () => {
869
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-reviewers-"));
870
+ try {
871
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
872
+ git(["init", "-q"]);
873
+ git(["config", "user.email", "alice@example.com"]);
874
+ git(["config", "user.name", "Alice"]);
875
+ mkdirSync(join(dir, "src"), { recursive: true });
876
+ writeFileSync(join(dir, "src", "app.ts"), "export function a() {}\n", "utf8");
877
+ git(["add", "."]);
878
+ git(["commit", "-m", "init"]);
879
+ const diff = [
880
+ "diff --git a/src/app.ts b/src/app.ts",
881
+ "--- a/src/app.ts",
882
+ "+++ b/src/app.ts",
883
+ "@@ -1 +1,2 @@",
884
+ " export function a() {}",
885
+ "+export function b() {}",
886
+ "",
887
+ ].join("\n");
888
+ const { responses, stderr } = runServer(dir, [
889
+ {
890
+ jsonrpc: "2.0",
891
+ id: 1,
892
+ method: "initialize",
893
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
894
+ },
895
+ {
896
+ jsonrpc: "2.0",
897
+ id: 2,
898
+ method: "tools/call",
899
+ params: { name: "reviewers", arguments: { diff, verbosity: "compact" } },
900
+ },
901
+ ]);
902
+ const result = responses.find((response) => response.id === 2)?.result;
903
+ assert.notEqual(result?.isError, true, stderr);
904
+ assert.equal(result?._meta?.exitCode, 0);
905
+ assert.deepEqual(result?.structuredContent?.files, ["src/app.ts"]);
906
+ assert.equal(result?.structuredContent?.candidates?.length, 1);
907
+ assert.equal(result?.structuredContent?.candidates?.[0]?.name, "Alice");
908
+ assert.equal(result?.structuredContent?.candidates?.[0]?.files, 1);
909
+ assert.equal(result?.structuredContent?.candidates?.[0]?.commits, 1);
910
+ assert.match(result?.content?.[0]?.text ?? "", /1 reviewer candidate, 0 CODEOWNERS owners for 1 file/);
911
+ }
912
+ finally {
913
+ rmSync(dir, { recursive: true, force: true });
914
+ }
915
+ });
916
+ test("MCP test_guard flags a weakened matcher", () => {
917
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-guard-"));
918
+ try {
919
+ const diff = [
920
+ "diff --git a/src/app.test.ts b/src/app.test.ts",
921
+ "--- a/src/app.test.ts",
922
+ "+++ b/src/app.test.ts",
923
+ "@@ -1,3 +1,3 @@",
924
+ ' test("adds", () => {',
925
+ "- expect(add(1, 2)).toBe(3);",
926
+ "+ expect(add(1, 2)).toBeDefined();",
927
+ " });",
928
+ "",
929
+ ].join("\n");
930
+ const { responses, stderr } = runServer(dir, [
931
+ {
932
+ jsonrpc: "2.0",
933
+ id: 1,
934
+ method: "initialize",
935
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
936
+ },
937
+ {
938
+ jsonrpc: "2.0",
939
+ id: 2,
940
+ method: "tools/call",
941
+ params: {
942
+ name: "test_guard",
943
+ arguments: { diff, fail_on_weakening: true, verbosity: "compact" },
944
+ },
945
+ },
946
+ ]);
947
+ const result = responses.find((response) => response.id === 2)?.result;
948
+ assert.notEqual(result?.isError, true, stderr);
949
+ assert.equal(result?._meta?.exitCode, 1);
950
+ assert.equal(result?.structuredContent?.status, "fail");
951
+ assert.equal(result?.structuredContent?.counts?.total, 1);
952
+ assert.equal(result?.structuredContent?.findings?.[0]?.kind, "weakened-matcher");
953
+ assert.equal(result?.structuredContent?.findings?.[0]?.file, "src/app.test.ts");
954
+ assert.match(result?.content?.[0]?.text ?? "", /1 test weakening finding, status fail/);
955
+ }
956
+ finally {
957
+ rmSync(dir, { recursive: true, force: true });
958
+ }
959
+ });
960
+ test("MCP env_sync reports a missing environment variable", () => {
961
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-env-"));
962
+ try {
963
+ writeFileSync(join(dir, ".env"), "EXISTING_KEY=value\n", "utf8");
964
+ writeFileSync(join(dir, ".env.example"), "EXISTING_KEY=\n", "utf8");
965
+ const diff = [
966
+ "diff --git a/src/config.ts b/src/config.ts",
967
+ "--- a/src/config.ts",
968
+ "+++ b/src/config.ts",
969
+ "@@ -1,2 +1,3 @@",
970
+ " const existing = process.env.EXISTING_KEY;",
971
+ "+const missing = process.env.MISSING_KEY;",
972
+ " export { existing, missing };",
973
+ "",
974
+ ].join("\n");
975
+ const { responses, stderr } = runServer(dir, [
976
+ {
977
+ jsonrpc: "2.0",
978
+ id: 1,
979
+ method: "initialize",
980
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
981
+ },
982
+ {
983
+ jsonrpc: "2.0",
984
+ id: 2,
985
+ method: "tools/call",
986
+ params: {
987
+ name: "env_sync",
988
+ arguments: { diff, fail_on_missing: true, verbosity: "compact" },
989
+ },
990
+ },
991
+ ]);
992
+ const result = responses.find((response) => response.id === 2)?.result;
993
+ assert.notEqual(result?.isError, true, stderr);
994
+ assert.equal(result?._meta?.exitCode, 1);
995
+ assert.equal(result?.structuredContent?.status, "fail");
996
+ assert.deepEqual(result?.structuredContent?.missing, ["MISSING_KEY"]);
997
+ assert.deepEqual(result?.structuredContent?.undocumented, []);
998
+ assert.equal(result?.structuredContent?.references?.length, 1);
999
+ assert.equal(result?.structuredContent?.references?.[0]?.name, "MISSING_KEY");
1000
+ assert.equal(result?.structuredContent?.references?.[0]?.line, 2);
1001
+ assert.match(result?.content?.[0]?.text ?? "", /1 env reference, 1 missing, 0 undocumented/);
1002
+ }
1003
+ finally {
1004
+ rmSync(dir, { recursive: true, force: true });
1005
+ }
1006
+ });
1007
+ test("MCP mock_leak finds hardcoded paths and always-true branches", () => {
1008
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-mock-"));
1009
+ try {
1010
+ const diff = [
1011
+ "diff --git a/src/app.ts b/src/app.ts",
1012
+ "--- a/src/app.ts",
1013
+ "+++ b/src/app.ts",
1014
+ "@@ -1,1 +1,4 @@",
1015
+ '+const home = "C:\\\\Users\\\\aleyn\\\\project";',
1016
+ "+if (true) {",
1017
+ "+ load(home);",
1018
+ "+}",
1019
+ "",
1020
+ ].join("\n");
1021
+ const { responses, stderr } = runServer(dir, [
1022
+ {
1023
+ jsonrpc: "2.0",
1024
+ id: 1,
1025
+ method: "initialize",
1026
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1027
+ },
1028
+ {
1029
+ jsonrpc: "2.0",
1030
+ id: 2,
1031
+ method: "tools/call",
1032
+ params: {
1033
+ name: "mock_leak",
1034
+ arguments: { diff, fail_on_leak: "high", verbosity: "compact" },
1035
+ },
1036
+ },
1037
+ ]);
1038
+ const result = responses.find((response) => response.id === 2)?.result;
1039
+ assert.notEqual(result?.isError, true, stderr);
1040
+ assert.equal(result?._meta?.exitCode, 1);
1041
+ assert.equal(result?.structuredContent?.status, "fail");
1042
+ assert.equal(result?.structuredContent?.counts?.total, 2);
1043
+ assert.equal(result?.structuredContent?.counts?.high, 2);
1044
+ const kinds = (result?.structuredContent?.findings ?? []).map((finding) => finding.kind);
1045
+ assert.ok(kinds.includes("local-path"), `kinds: ${kinds.join(", ")}`);
1046
+ assert.ok(kinds.includes("always-true"), `kinds: ${kinds.join(", ")}`);
1047
+ assert.match(result?.content?.[0]?.text ?? "", /2 mock leaks, 2 high, 0 medium/);
1048
+ }
1049
+ finally {
1050
+ rmSync(dir, { recursive: true, force: true });
1051
+ }
1052
+ });
1053
+ test("MCP pack returns a compact context bundle", () => {
1054
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-pack-"));
1055
+ try {
1056
+ mkdirSync(join(dir, "src"), { recursive: true });
1057
+ writeFileSync(join(dir, "src", "wallet.ts"), "export function chargeCard(amount: number) {\n return amount;\n}\n", "utf8");
1058
+ const diff = [
1059
+ "diff --git a/src/wallet.ts b/src/wallet.ts",
1060
+ "new file mode 100644",
1061
+ "--- /dev/null",
1062
+ "+++ b/src/wallet.ts",
1063
+ "@@ -0,0 +1,3 @@",
1064
+ "+export function chargeCard(amount: number) {",
1065
+ "+ return amount;",
1066
+ "+}",
1067
+ "",
1068
+ ].join("\n");
1069
+ const { responses, stderr } = runServer(dir, [
1070
+ {
1071
+ jsonrpc: "2.0",
1072
+ id: 1,
1073
+ method: "initialize",
1074
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1075
+ },
1076
+ {
1077
+ jsonrpc: "2.0",
1078
+ id: 2,
1079
+ method: "tools/call",
1080
+ params: {
1081
+ name: "pack",
1082
+ arguments: { task: "fix login", diff, budget: 4000, verbosity: "compact" },
1083
+ },
1084
+ },
1085
+ ]);
1086
+ const result = responses.find((response) => response.id === 2)?.result;
1087
+ assert.notEqual(result?.isError, true, stderr);
1088
+ assert.equal(result?._meta?.exitCode, 0);
1089
+ assert.equal(result?.structuredContent?.task, "fix login");
1090
+ assert.ok((result?.structuredContent?.files?.length ?? 0) >= 1, "pack files missing");
1091
+ assert.ok((result?.structuredContent?.chars ?? 0) <= 4000, "pack exceeded the budget");
1092
+ assert.equal(result?.structuredContent?.truncated, false);
1093
+ assert.match(result?.structuredContent?.content ?? "", /fix login/);
1094
+ assert.match(result?.structuredContent?.content ?? "", /chargeCard/);
1095
+ assert.match(result?.content?.[0]?.text ?? "", /1 file, \d+ chars \(within budget\)/);
1096
+ }
1097
+ finally {
1098
+ rmSync(dir, { recursive: true, force: true });
1099
+ }
1100
+ });
1101
+ test("MCP contract_drift flags a removed field still used by a consumer", () => {
1102
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-contract-"));
1103
+ try {
1104
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1105
+ mkdirSync(join(dir, "src"), { recursive: true });
1106
+ writeFileSync(join(dir, "src", "types.ts"), "export interface Account {\n id: string;\n role: string;\n}\n", "utf8");
1107
+ writeFileSync(join(dir, "src", "billing.ts"), "export function bill(account: Account) {\n return account.billingEmail;\n}\n", "utf8");
1108
+ const diff = [
1109
+ "diff --git a/src/types.ts b/src/types.ts",
1110
+ "--- a/src/types.ts",
1111
+ "+++ b/src/types.ts",
1112
+ "@@ -1,4 +1,3 @@",
1113
+ " export interface Account {",
1114
+ " id: string;",
1115
+ "- billingEmail: string;",
1116
+ " role: string;",
1117
+ " }",
1118
+ "",
1119
+ ].join("\n");
1120
+ const { responses, stderr } = runServer(dir, [
1121
+ {
1122
+ jsonrpc: "2.0",
1123
+ id: 1,
1124
+ method: "initialize",
1125
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1126
+ },
1127
+ {
1128
+ jsonrpc: "2.0",
1129
+ id: 2,
1130
+ method: "tools/call",
1131
+ params: {
1132
+ name: "contract_drift",
1133
+ arguments: { diff, fail_on_drift: "high", verbosity: "compact" },
1134
+ },
1135
+ },
1136
+ ]);
1137
+ const result = responses.find((response) => response.id === 2)?.result;
1138
+ assert.notEqual(result?.isError, true, stderr);
1139
+ assert.equal(result?._meta?.exitCode, 1);
1140
+ assert.equal(result?.structuredContent?.status, "fail");
1141
+ assert.equal(result?.structuredContent?.counts?.total, 1);
1142
+ const finding = result?.structuredContent?.findings?.[0];
1143
+ assert.equal(finding?.kind, "removed-field");
1144
+ assert.equal(finding?.field, "billingEmail");
1145
+ assert.equal(finding?.severity, "high");
1146
+ assert.equal(finding?.references?.[0]?.file, "src/billing.ts");
1147
+ assert.match(result?.content?.[0]?.text ?? "", /1 contract drift, 1 high, 0 medium/);
1148
+ }
1149
+ finally {
1150
+ rmSync(dir, { recursive: true, force: true });
1151
+ }
1152
+ });
1153
+ test("MCP test_plan selects tests from the import graph", () => {
1154
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-test-plan-"));
1155
+ try {
1156
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1157
+ mkdirSync(join(dir, "src"), { recursive: true });
1158
+ writeFileSync(join(dir, "src", "wallet.ts"), "export function charge(amount: number) {\n return amount;\n}\n", "utf8");
1159
+ writeFileSync(join(dir, "src", "checkout.ts"), 'import { charge } from "./wallet";\n\nexport function pay() {\n return charge(10);\n}\n', "utf8");
1160
+ writeFileSync(join(dir, "src", "wallet.test.ts"), 'import { charge } from "./wallet";\n', "utf8");
1161
+ writeFileSync(join(dir, "src", "checkout.test.ts"), 'import { pay } from "./checkout";\n', "utf8");
1162
+ const diff = [
1163
+ "diff --git a/src/wallet.ts b/src/wallet.ts",
1164
+ "--- a/src/wallet.ts",
1165
+ "+++ b/src/wallet.ts",
1166
+ "@@ -1,3 +1,3 @@",
1167
+ "-export function charge(amount: number) {",
1168
+ "+export function chargeCard(amount: number) {",
1169
+ " return amount;",
1170
+ " }",
1171
+ "",
1172
+ ].join("\n");
1173
+ const { responses, stderr } = runServer(dir, [
1174
+ {
1175
+ jsonrpc: "2.0",
1176
+ id: 1,
1177
+ method: "initialize",
1178
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1179
+ },
1180
+ {
1181
+ jsonrpc: "2.0",
1182
+ id: 2,
1183
+ method: "tools/call",
1184
+ params: {
1185
+ name: "test_plan",
1186
+ arguments: { diff, strategy: "affected", fail_on_uncovered: true, verbosity: "compact" },
1187
+ },
1188
+ },
1189
+ ]);
1190
+ const result = responses.find((response) => response.id === 2)?.result;
1191
+ assert.notEqual(result?.isError, true, stderr);
1192
+ assert.equal(result?._meta?.exitCode, 0);
1193
+ assert.deepEqual(result?.structuredContent?.uncovered, []);
1194
+ const files = (result?.structuredContent?.tests ?? []).map((test) => test.file);
1195
+ assert.ok(files.includes("src/wallet.test.ts"), `tests: ${files.join(", ")}`);
1196
+ assert.ok(files.includes("src/checkout.test.ts"), `tests: ${files.join(", ")}`);
1197
+ assert.match(result?.content?.[0]?.text ?? "", /2 test files selected, 0 uncovered changed files/);
1198
+ }
1199
+ finally {
1200
+ rmSync(dir, { recursive: true, force: true });
1201
+ }
1202
+ });
1203
+ test("MCP pack_hologram layers files and zoom_symbol returns the body", () => {
1204
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-hologram-"));
1205
+ try {
1206
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1207
+ spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: dir });
1208
+ spawnSync("git", ["config", "user.name", "Test"], { cwd: dir });
1209
+ mkdirSync(join(dir, "src"), { recursive: true });
1210
+ writeFileSync(join(dir, "src", "a.ts"), 'import { helper } from "./b";\n\nexport const run = 1;\n', "utf8");
1211
+ writeFileSync(join(dir, "src", "b.ts"), "export function helper(): number {\n return 1;\n}\n", "utf8");
1212
+ writeFileSync(join(dir, "src", "c.ts"), "export const far = 2;\n", "utf8");
1213
+ spawnSync("git", ["add", "."], { cwd: dir });
1214
+ spawnSync("git", ["commit", "-m", "init"], { cwd: dir });
1215
+ const diff = [
1216
+ "diff --git a/src/a.ts b/src/a.ts",
1217
+ "--- a/src/a.ts",
1218
+ "+++ b/src/a.ts",
1219
+ "@@ -1,1 +1,3 @@",
1220
+ '+import { helper } from "./b";',
1221
+ " ",
1222
+ " export const run = 1;",
1223
+ "",
1224
+ ].join("\n");
1225
+ const { responses, stderr } = runServer(dir, [
1226
+ {
1227
+ jsonrpc: "2.0",
1228
+ id: 1,
1229
+ method: "initialize",
1230
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1231
+ },
1232
+ {
1233
+ jsonrpc: "2.0",
1234
+ id: 2,
1235
+ method: "tools/call",
1236
+ params: {
1237
+ name: "pack_hologram",
1238
+ arguments: { diff, max_tokens: 30000, verbosity: "compact" },
1239
+ },
1240
+ },
1241
+ {
1242
+ jsonrpc: "2.0",
1243
+ id: 3,
1244
+ method: "tools/call",
1245
+ params: {
1246
+ name: "zoom_symbol",
1247
+ arguments: { file: "src/b.ts", symbol: "helper", verbosity: "compact" },
1248
+ },
1249
+ },
1250
+ ]);
1251
+ const hologram = responses.find((response) => response.id === 2)?.result;
1252
+ assert.notEqual(hologram?.isError, true, stderr);
1253
+ assert.equal(hologram?.structuredContent?.files?.find((file) => file.file === "src/a.ts")?.lod, 0);
1254
+ assert.equal(hologram?.structuredContent?.files?.find((file) => file.file === "src/b.ts")?.lod, 1);
1255
+ assert.ok((hologram?.structuredContent?.estimatedTokens ?? 0) > 0);
1256
+ assert.match(hologram?.structuredContent?.content ?? "", /body hidden \(LOD 1\)/);
1257
+ assert.match(hologram?.content?.[0]?.text ?? "", /hologram \d+ file\(s\)/);
1258
+ const zoom = responses.find((response) => response.id === 3)?.result;
1259
+ assert.notEqual(zoom?.isError, true, stderr);
1260
+ assert.equal(zoom?.structuredContent?.symbol, "helper");
1261
+ assert.equal(zoom?.structuredContent?.line, 1);
1262
+ assert.match(zoom?.structuredContent?.body ?? "", /return 1/);
1263
+ assert.match(zoom?.content?.[0]?.text ?? "", /symbol src\/b\.ts#helper/);
1264
+ }
1265
+ finally {
1266
+ rmSync(dir, { recursive: true, force: true });
1267
+ }
1268
+ });
1269
+ test("MCP bounty posts a WANTED poster for the worst file", () => {
1270
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-bounty-"));
1271
+ try {
1272
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1273
+ spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: dir });
1274
+ spawnSync("git", ["config", "user.name", "Test"], { cwd: dir });
1275
+ writeFileSync(join(dir, "app.js"), 'const { exec } = require("child_process");\n// TODO: remove the debug command\nexec(req.query.cmd);\n', "utf8");
1276
+ spawnSync("git", ["add", "."], { cwd: dir });
1277
+ spawnSync("git", ["commit", "-m", "init"], { cwd: dir });
1278
+ const { responses, stderr } = runServer(dir, [
1279
+ {
1280
+ jsonrpc: "2.0",
1281
+ id: 1,
1282
+ method: "initialize",
1283
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1284
+ },
1285
+ {
1286
+ jsonrpc: "2.0",
1287
+ id: 2,
1288
+ method: "tools/call",
1289
+ params: {
1290
+ name: "bounty",
1291
+ arguments: { limit: 5, since_days: 30, verbosity: "compact" },
1292
+ },
1293
+ },
1294
+ ]);
1295
+ const result = responses.find((response) => response.id === 2)?.result;
1296
+ assert.notEqual(result?.isError, true, stderr);
1297
+ assert.equal(result?._meta?.exitCode, 0);
1298
+ assert.equal(result?.structuredContent?.file, "app.js");
1299
+ assert.ok((result?.structuredContent?.score ?? 0) > 0);
1300
+ assert.match(result?.structuredContent?.poster ?? "", /W\s*A\s*N\s*T\s*E\s*D/);
1301
+ assert.match(result?.content?.[0]?.text ?? "", /worst file app\.js/);
1302
+ }
1303
+ finally {
1304
+ rmSync(dir, { recursive: true, force: true });
1305
+ }
1306
+ });
1307
+ test("MCP autopsy finds a dangerous commit", () => {
1308
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-autopsy-"));
1309
+ try {
1310
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1311
+ spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: dir });
1312
+ spawnSync("git", ["config", "user.name", "Test"], { cwd: dir });
1313
+ writeFileSync(join(dir, "app.js"), 'const { exec } = require("child_process");\nexec(req.query.cmd);\n', "utf8");
1314
+ spawnSync("git", ["add", "."], { cwd: dir });
1315
+ spawnSync("git", ["commit", "-m", "add the debug command"], { cwd: dir });
1316
+ const { responses, stderr } = runServer(dir, [
1317
+ {
1318
+ jsonrpc: "2.0",
1319
+ id: 1,
1320
+ method: "initialize",
1321
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1322
+ },
1323
+ {
1324
+ jsonrpc: "2.0",
1325
+ id: 2,
1326
+ method: "tools/call",
1327
+ params: {
1328
+ name: "autopsy",
1329
+ arguments: { commit: "HEAD", depth: 5, fail_on_risk: true, verbosity: "compact" },
1330
+ },
1331
+ },
1332
+ ]);
1333
+ const result = responses.find((response) => response.id === 2)?.result;
1334
+ assert.notEqual(result?.isError, true, stderr);
1335
+ assert.equal(result?._meta?.exitCode, 1);
1336
+ assert.equal(result?.structuredContent?.verdict, "dangerous");
1337
+ assert.ok((result?.structuredContent?.signals?.length ?? 0) >= 1);
1338
+ assert.match(result?.content?.[0]?.text ?? "", /autopsy dangerous for/);
1339
+ }
1340
+ finally {
1341
+ rmSync(dir, { recursive: true, force: true });
1342
+ }
1343
+ });
1344
+ test("MCP echo builds badges without submitting", () => {
1345
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-echo-"));
1346
+ try {
1347
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1348
+ spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: dir });
1349
+ spawnSync("git", ["config", "user.name", "Test"], { cwd: dir });
1350
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
1351
+ spawnSync("git", ["add", "."], { cwd: dir });
1352
+ spawnSync("git", ["commit", "-m", "init"], { cwd: dir });
1353
+ const { responses, stderr } = runServer(dir, [
1354
+ {
1355
+ jsonrpc: "2.0",
1356
+ id: 1,
1357
+ method: "initialize",
1358
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1359
+ },
1360
+ {
1361
+ jsonrpc: "2.0",
1362
+ id: 2,
1363
+ method: "tools/call",
1364
+ params: { name: "echo", arguments: { name: "tester", since_days: 30, verbosity: "compact" } },
1365
+ },
1366
+ ]);
1367
+ const result = responses.find((response) => response.id === 2)?.result;
1368
+ assert.notEqual(result?.isError, true, stderr);
1369
+ assert.equal(result?._meta?.exitCode, 0);
1370
+ assert.equal(result?.structuredContent?.name, "tester");
1371
+ assert.equal(result?.structuredContent?.badges?.length, 6);
1372
+ assert.equal(result?.structuredContent?.stats?.commits, 1);
1373
+ assert.equal(result?.structuredContent?.submitted, false);
1374
+ assert.match(result?.structuredContent?.content ?? "", /<svg/);
1375
+ assert.match(result?.content?.[0]?.text ?? "", /6 badge\(s\) for tester, 1 commits, 0 runs/);
1376
+ }
1377
+ finally {
1378
+ rmSync(dir, { recursive: true, force: true });
1379
+ }
1380
+ });
1381
+ test("MCP brag returns a self-contained session report", () => {
1382
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-brag-"));
1383
+ try {
1384
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
1385
+ git(["init", "-q"]);
1386
+ git(["config", "user.email", "test@example.com"]);
1387
+ git(["config", "user.name", "Test"]);
1388
+ writeFileSync(join(dir, "app.ts"), "export const answer = 42;\n", "utf8");
1389
+ git(["add", "."]);
1390
+ git(["commit", "-m", "init"]);
1391
+ const { responses, stderr } = runServer(dir, [
1392
+ {
1393
+ jsonrpc: "2.0",
1394
+ id: 1,
1395
+ method: "initialize",
1396
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1397
+ },
1398
+ {
1399
+ jsonrpc: "2.0",
1400
+ id: 2,
1401
+ method: "tools/call",
1402
+ params: { name: "brag", arguments: { title: "demo", since_days: 30, verbosity: "compact" } },
1403
+ },
1404
+ ]);
1405
+ const result = responses.find((response) => response.id === 2)?.result;
1406
+ assert.notEqual(result?.isError, true, stderr);
1407
+ assert.equal(result?._meta?.exitCode, 0);
1408
+ assert.match(result?.structuredContent?.content ?? "", /<html/i);
1409
+ assert.equal(result?.structuredContent?.stats?.commits, 1);
1410
+ assert.match(result?.content?.[0]?.text ?? "", /1 commits, 0 runs, 0 findings \(HTML\)/);
1411
+ }
1412
+ finally {
1413
+ rmSync(dir, { recursive: true, force: true });
1414
+ }
1415
+ });
1416
+ test("MCP compliance_verify accepts a manifest for HEAD", () => {
1417
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-compliance-"));
1418
+ try {
1419
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1420
+ spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: dir });
1421
+ spawnSync("git", ["config", "user.name", "Test"], { cwd: dir });
1422
+ writeFileSync(join(dir, "app.ts"), "export const value = 1;\n", "utf8");
1423
+ spawnSync("git", ["add", "."], { cwd: dir });
1424
+ spawnSync("git", ["commit", "-m", "init"], { cwd: dir });
1425
+ const created = spawnSync(process.execPath, [CLI, "compliance", "--json"], { cwd: dir, encoding: "utf8" });
1426
+ assert.equal(created.status, 0, created.stderr);
1427
+ const { responses, stderr } = runServer(dir, [
1428
+ {
1429
+ jsonrpc: "2.0",
1430
+ id: 1,
1431
+ method: "initialize",
1432
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1433
+ },
1434
+ {
1435
+ jsonrpc: "2.0",
1436
+ id: 2,
1437
+ method: "tools/call",
1438
+ params: { name: "compliance_verify", arguments: { verbosity: "compact" } },
1439
+ },
1440
+ ]);
1441
+ const result = responses.find((response) => response.id === 2)?.result;
1442
+ assert.notEqual(result?.isError, true, stderr);
1443
+ assert.equal(result?._meta?.exitCode, 0);
1444
+ assert.equal(result?.structuredContent?.verification?.ok, true);
1445
+ assert.equal(result?.structuredContent?.verification?.hashOk, true);
1446
+ assert.equal(result?.structuredContent?.verification?.chainOk, true);
1447
+ assert.match(result?.content?.[0]?.text ?? "", /compliance ok for/);
1448
+ }
1449
+ finally {
1450
+ rmSync(dir, { recursive: true, force: true });
1451
+ }
1452
+ });
1453
+ test("MCP boundaries flags an import that crosses a layer rule", () => {
1454
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-boundaries-"));
1455
+ try {
1456
+ mkdirSync(join(dir, ".oc-pilot"), { recursive: true });
1457
+ writeFileSync(join(dir, ".oc-pilot", "architecture.json"), JSON.stringify({
1458
+ rules: [{ from: "src/ui/**", deny: ["src/db/**"], reason: "UI не ходит в БД" }],
1459
+ }), "utf8");
1460
+ const diff = [
1461
+ "diff --git a/src/ui/app.ts b/src/ui/app.ts",
1462
+ "new file mode 100644",
1463
+ "--- /dev/null",
1464
+ "+++ b/src/ui/app.ts",
1465
+ "@@ -0,0 +1,2 @@",
1466
+ '+import { db } from "../db/user";',
1467
+ "+export const app = db;",
1468
+ "",
1469
+ ].join("\n");
1470
+ const { responses, stderr } = runServer(dir, [
1471
+ {
1472
+ jsonrpc: "2.0",
1473
+ id: 1,
1474
+ method: "initialize",
1475
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1476
+ },
1477
+ {
1478
+ jsonrpc: "2.0",
1479
+ id: 2,
1480
+ method: "tools/call",
1481
+ params: {
1482
+ name: "boundaries",
1483
+ arguments: { diff, fail_on_violation: true, verbosity: "compact" },
1484
+ },
1485
+ },
1486
+ ]);
1487
+ const result = responses.find((response) => response.id === 2)?.result;
1488
+ assert.notEqual(result?.isError, true, stderr);
1489
+ assert.equal(result?._meta?.exitCode, 1);
1490
+ assert.equal(result?.structuredContent?.status, "fail");
1491
+ assert.equal(result?.structuredContent?.violations?.length, 1);
1492
+ assert.equal(result?.structuredContent?.violations?.[0]?.file, "src/ui/app.ts");
1493
+ assert.equal(result?.structuredContent?.violations?.[0]?.line, 1);
1494
+ assert.equal(result?.structuredContent?.violations?.[0]?.imported, "../db/user");
1495
+ assert.match(result?.structuredContent?.violations?.[0]?.rule?.reason ?? "", /UI не ходит в БД/);
1496
+ assert.match(result?.content?.[0]?.text ?? "", /1 boundary violation in 1 file/);
1497
+ }
1498
+ finally {
1499
+ rmSync(dir, { recursive: true, force: true });
1500
+ }
1501
+ });
1502
+ test("MCP anti_regression cites the commit that deleted a re-added line", () => {
1503
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-regression-"));
1504
+ try {
1505
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
1506
+ git(["init", "-q"]);
1507
+ git(["config", "user.email", "test@example.com"]);
1508
+ git(["config", "user.name", "Test"]);
1509
+ mkdirSync(join(dir, "src"), { recursive: true });
1510
+ const file = join(dir, "src", "wallet.ts");
1511
+ writeFileSync(file, "export function charge(amount: number) {\n return amount * 2;\n}\n", "utf8");
1512
+ git(["add", "."]);
1513
+ git(["commit", "-m", "add doubling"]);
1514
+ writeFileSync(file, "export function charge(amount: number) {\n return amount;\n}\n", "utf8");
1515
+ git(["add", "."]);
1516
+ git(["commit", "-m", "stop doubling"]);
1517
+ const diff = [
1518
+ "diff --git a/src/wallet.ts b/src/wallet.ts",
1519
+ "--- a/src/wallet.ts",
1520
+ "+++ b/src/wallet.ts",
1521
+ "@@ -1,3 +1,3 @@",
1522
+ " export function charge(amount: number) {",
1523
+ "- return amount;",
1524
+ "+ return amount * 2;",
1525
+ " }",
1526
+ "",
1527
+ ].join("\n");
1528
+ const { responses, stderr } = runServer(dir, [
1529
+ {
1530
+ jsonrpc: "2.0",
1531
+ id: 1,
1532
+ method: "initialize",
1533
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1534
+ },
1535
+ {
1536
+ jsonrpc: "2.0",
1537
+ id: 2,
1538
+ method: "tools/call",
1539
+ params: {
1540
+ name: "anti_regression",
1541
+ arguments: { diff, fail_on_regression: true, verbosity: "compact" },
1542
+ },
1543
+ },
1544
+ ]);
1545
+ const result = responses.find((response) => response.id === 2)?.result;
1546
+ assert.notEqual(result?.isError, true, stderr);
1547
+ assert.equal(result?._meta?.exitCode, 1);
1548
+ assert.equal(result?.structuredContent?.status, "fail");
1549
+ const finding = result?.structuredContent?.findings?.[0];
1550
+ assert.equal(finding?.kind, "readded-line");
1551
+ assert.equal(finding?.file, "src/wallet.ts");
1552
+ assert.equal(finding?.line, 2);
1553
+ assert.match(finding?.commit?.subject ?? "", /stop doubling/);
1554
+ assert.match(result?.content?.[0]?.text ?? "", /1 re-added line, 1 commit/);
1555
+ }
1556
+ finally {
1557
+ rmSync(dir, { recursive: true, force: true });
1558
+ }
1559
+ });
1560
+ test("MCP ecosystem_verify flags an invented export", () => {
1561
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-ecosystem-"));
1562
+ try {
1563
+ mkdirSync(join(dir, "src"), { recursive: true });
1564
+ mkdirSync(join(dir, "node_modules", "left-pad"), { recursive: true });
1565
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo", dependencies: { "left-pad": "^1.3.0" } }), "utf8");
1566
+ writeFileSync(join(dir, "node_modules", "left-pad", "package.json"), JSON.stringify({ name: "left-pad", version: "1.3.0", types: "index.d.ts" }), "utf8");
1567
+ writeFileSync(join(dir, "node_modules", "left-pad", "index.d.ts"), "export function leftPad(text: string, width: number): string;\n", "utf8");
1568
+ writeFileSync(join(dir, "src", "app.ts"), 'import { leftPad, invented } from "left-pad";\n\nleftPad("x", 3);\ninvented();\n', "utf8");
1569
+ const diff = [
1570
+ "diff --git a/src/app.ts b/src/app.ts",
1571
+ "--- a/src/app.ts",
1572
+ "+++ b/src/app.ts",
1573
+ "@@ -1,1 +1,4 @@",
1574
+ '+import { leftPad, invented } from "left-pad";',
1575
+ "+",
1576
+ '+leftPad("x", 3);',
1577
+ "+invented();",
1578
+ "",
1579
+ ].join("\n");
1580
+ const { responses, stderr } = runServer(dir, [
1581
+ {
1582
+ jsonrpc: "2.0",
1583
+ id: 1,
1584
+ method: "initialize",
1585
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1586
+ },
1587
+ {
1588
+ jsonrpc: "2.0",
1589
+ id: 2,
1590
+ method: "tools/call",
1591
+ params: {
1592
+ name: "ecosystem_verify",
1593
+ arguments: { diff, fail_on_issue: "high", verbosity: "compact" },
1594
+ },
1595
+ },
1596
+ ]);
1597
+ const result = responses.find((response) => response.id === 2)?.result;
1598
+ assert.notEqual(result?.isError, true, stderr);
1599
+ assert.equal(result?._meta?.exitCode, 1);
1600
+ assert.equal(result?.structuredContent?.status, "fail");
1601
+ assert.equal(result?.structuredContent?.counts?.total, 1);
1602
+ assert.equal(result?.structuredContent?.counts?.high, 1);
1603
+ assert.equal(result?.structuredContent?.packages?.[0]?.name, "left-pad");
1604
+ assert.deepEqual(result?.structuredContent?.packages?.[0]?.missingExports, ["invented"]);
1605
+ assert.equal(result?.structuredContent?.issues?.[0]?.kind, "missing-export");
1606
+ assert.match(result?.content?.[0]?.text ?? "", /1 ecosystem issue, 1 high, 0 medium/);
1607
+ }
1608
+ finally {
1609
+ rmSync(dir, { recursive: true, force: true });
1610
+ }
1611
+ });
1612
+ test("MCP arch_diff renders added and removed internal imports", () => {
1613
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-arch-"));
1614
+ try {
1615
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1616
+ mkdirSync(join(dir, "src"), { recursive: true });
1617
+ writeFileSync(join(dir, "src", "a.ts"), 'import { c } from "./c";\n\nexport const a = c;\n', "utf8");
1618
+ writeFileSync(join(dir, "src", "b.ts"), "export const b = 1;\n", "utf8");
1619
+ writeFileSync(join(dir, "src", "c.ts"), "export const c = 1;\n", "utf8");
1620
+ const diff = [
1621
+ "diff --git a/src/a.ts b/src/a.ts",
1622
+ "--- a/src/a.ts",
1623
+ "+++ b/src/a.ts",
1624
+ "@@ -1,3 +1,3 @@",
1625
+ '-import { c } from "./c";',
1626
+ '+import { b } from "./b";',
1627
+ " ",
1628
+ " export const a = c;",
1629
+ "",
1630
+ ].join("\n");
1631
+ const { responses, stderr } = runServer(dir, [
1632
+ {
1633
+ jsonrpc: "2.0",
1634
+ id: 1,
1635
+ method: "initialize",
1636
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1637
+ },
1638
+ {
1639
+ jsonrpc: "2.0",
1640
+ id: 2,
1641
+ method: "tools/call",
1642
+ params: {
1643
+ name: "arch_diff",
1644
+ arguments: { diff, fail_on_removed: true, verbosity: "compact" },
1645
+ },
1646
+ },
1647
+ ]);
1648
+ const result = responses.find((response) => response.id === 2)?.result;
1649
+ assert.notEqual(result?.isError, true, stderr);
1650
+ assert.equal(result?._meta?.exitCode, 1);
1651
+ assert.equal(result?.structuredContent?.added?.[0]?.to, "src/b.ts");
1652
+ assert.equal(result?.structuredContent?.removed?.[0]?.to, "src/c.ts");
1653
+ assert.match(result?.structuredContent?.diagram ?? "", /flowchart LR/);
1654
+ assert.match(result?.content?.[0]?.text ?? "", /1 added edge, 1 removed edge/);
1655
+ }
1656
+ finally {
1657
+ rmSync(dir, { recursive: true, force: true });
1658
+ }
1659
+ });
1660
+ test("MCP impact finds references to changed symbols", () => {
1661
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-impact-"));
1662
+ try {
1663
+ spawnSync("git", ["init", "-q"], { cwd: dir });
1664
+ mkdirSync(join(dir, "src"), { recursive: true });
1665
+ writeFileSync(join(dir, "src", "checkout.ts"), 'import { charge } from "./wallet";\n\nexport function pay() {\n return charge(10);\n}\n', "utf8");
1666
+ const diff = [
1667
+ "diff --git a/src/wallet.ts b/src/wallet.ts",
1668
+ "--- a/src/wallet.ts",
1669
+ "+++ b/src/wallet.ts",
1670
+ "@@ -1,3 +1,3 @@",
1671
+ "-export function charge(amount: number) {",
1672
+ "+export function chargeCard(amount: number) {",
1673
+ " return amount;",
1674
+ " }",
1675
+ "",
1676
+ ].join("\n");
1677
+ const { responses, stderr } = runServer(dir, [
1678
+ {
1679
+ jsonrpc: "2.0",
1680
+ id: 1,
1681
+ method: "initialize",
1682
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1683
+ },
1684
+ {
1685
+ jsonrpc: "2.0",
1686
+ id: 2,
1687
+ method: "tools/call",
1688
+ params: {
1689
+ name: "impact",
1690
+ arguments: { diff, verbosity: "compact" },
1691
+ },
1692
+ },
1693
+ ]);
1694
+ const result = responses.find((response) => response.id === 2)?.result;
1695
+ assert.notEqual(result?.isError, true, stderr);
1696
+ const names = result?.structuredContent?.symbols?.map((symbol) => symbol.name) ?? [];
1697
+ assert.ok(names.includes("charge"), `symbols: ${names.join(", ")}`);
1698
+ assert.ok(names.includes("chargeCard"), `symbols: ${names.join(", ")}`);
1699
+ const reference = result?.structuredContent?.references?.find((item) => item.symbol === "charge");
1700
+ assert.ok(reference, "charge reference missing");
1701
+ assert.equal(reference.file, "src/checkout.ts");
1702
+ assert.match(result?.content?.[0]?.text ?? "", /2 changed symbols, \d+ references? in \d+ scanned files?/);
1703
+ }
1704
+ finally {
1705
+ rmSync(dir, { recursive: true, force: true });
1706
+ }
1707
+ });
1708
+ test("MCP flaky reports a test that passes and fails across runs", () => {
1709
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-flaky-"));
1710
+ try {
1711
+ writeFileSync(join(dir, "flaky.cjs"), [
1712
+ 'const { existsSync, readFileSync, writeFileSync } = require("node:fs");',
1713
+ 'const count = existsSync("runs.txt") ? Number(readFileSync("runs.txt", "utf8")) : 0;',
1714
+ 'writeFileSync("runs.txt", String(count + 1));',
1715
+ "if (count === 0) {",
1716
+ ' console.log("✔ alpha");',
1717
+ "} else {",
1718
+ ' console.log("✖ alpha");',
1719
+ " process.exit(1);",
1720
+ "}",
1721
+ "",
1722
+ ].join("\n"), "utf8");
1723
+ const { responses, stderr } = runServer(dir, [
1724
+ {
1725
+ jsonrpc: "2.0",
1726
+ id: 1,
1727
+ method: "initialize",
1728
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1729
+ },
1730
+ {
1731
+ jsonrpc: "2.0",
1732
+ id: 2,
1733
+ method: "tools/call",
1734
+ params: {
1735
+ name: "flaky",
1736
+ arguments: { runs: 2, command: "node flaky.cjs", verbosity: "compact" },
1737
+ },
1738
+ },
1739
+ ]);
1740
+ const result = responses.find((response) => response.id === 2)?.result;
1741
+ assert.notEqual(result?.isError, true, stderr);
1742
+ assert.equal(result?._meta?.exitCode, 0);
1743
+ assert.equal(result?.structuredContent?.runs, 2);
1744
+ assert.equal(result?.structuredContent?.parsed, true);
1745
+ assert.equal(result?.structuredContent?.flaky?.length, 1);
1746
+ assert.equal(result?.structuredContent?.flaky?.[0]?.name, "alpha");
1747
+ assert.equal(result?.structuredContent?.flaky?.[0]?.passes, 1);
1748
+ assert.equal(result?.structuredContent?.flaky?.[0]?.failures, 1);
1749
+ assert.match(result?.content?.[0]?.text ?? "", /1 flaky, 0 failing across 2 runs/);
1750
+ }
1751
+ finally {
1752
+ rmSync(dir, { recursive: true, force: true });
1753
+ }
1754
+ });
1755
+ test("applyResultOptions caps findings and summarizes compactly", () => {
1756
+ const findings = [1, 2, 3, 4, 5].map((n) => ({
1757
+ severity: n === 1 ? "critical" : "warning",
1758
+ location: `src/a.ts:${n}`,
1759
+ text: `issue ${n}`,
1760
+ }));
1761
+ const base = {
1762
+ content: [{ type: "text", text: "payload" }],
1763
+ structuredContent: { findings },
1764
+ };
1765
+ const capped = applyResultOptions(base, { max_findings: 2 });
1766
+ const payload = capped.structuredContent;
1767
+ assert.equal(payload.findings.length, 2);
1768
+ assert.equal(payload.truncated, true);
1769
+ assert.equal(payload.totalFindings, 5);
1770
+ assert.match(capped.content[0].text, /totalFindings/);
1771
+ const compact = applyResultOptions(base, { verbosity: "compact" });
1772
+ assert.match(compact.content[0].text, /5 findings/);
1773
+ assert.equal(compact.structuredContent.findings.length, 5);
1774
+ });
1775
+ test("MCP handles logging/setLevel and advertises the capability", () => {
1776
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-logging-"));
1777
+ try {
1778
+ const { responses } = runServer(dir, [
1779
+ {
1780
+ jsonrpc: "2.0",
1781
+ id: 1,
1782
+ method: "initialize",
1783
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1784
+ },
1785
+ { jsonrpc: "2.0", id: 2, method: "logging/setLevel", params: { level: "info" } },
1786
+ ]);
1787
+ const init = responses.find((response) => response.id === 1)?.result;
1788
+ assert.deepEqual(init?.capabilities?.logging, {});
1789
+ assert.deepEqual(responses.find((response) => response.id === 2)?.result, {});
1790
+ }
1791
+ finally {
1792
+ rmSync(dir, { recursive: true, force: true });
1793
+ }
1794
+ });
1795
+ test("MCP stops model-backed tools after the call budget", () => {
1796
+ const dir = mkdtempSync(join(tmpdir(), "oc-pilot-mcp-budget-"));
1797
+ try {
1798
+ const git = (args) => spawnSync("git", args, { cwd: dir, encoding: "utf8" });
1799
+ git(["init"]);
1800
+ git(["config", "user.email", "test@example.com"]);
1801
+ git(["config", "user.name", "Test"]);
1802
+ git(["config", "core.autocrlf", "false"]);
1803
+ writeFileSync(join(dir, ".oc-pilot.json"), JSON.stringify({ engine: "openai", baseUrl: "http://127.0.0.1:9/v1", model: "test-model" }), "utf8");
1804
+ writeFileSync(join(dir, "wallet.go"), "package main\n\nfunc main() {}\n", "utf8");
1805
+ git(["add", "."]);
1806
+ git(["commit", "-m", "init"]);
1807
+ const { responses } = runServer(dir, [
1808
+ {
1809
+ jsonrpc: "2.0",
1810
+ id: 1,
1811
+ method: "initialize",
1812
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "test", version: "1" } },
1813
+ },
1814
+ { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "review", arguments: {} } },
1815
+ { jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "review", arguments: {} } },
1816
+ { jsonrpc: "2.0", id: 4, method: "tools/call", params: { name: "stats", arguments: {} } },
1817
+ ], ["--max-calls", "1"]);
1818
+ const blocked = responses.find((response) => response.id === 3)?.result;
1819
+ assert.equal(blocked?.isError, true);
1820
+ assert.match(blocked?.content?.[0]?.text ?? "", /budget/i);
1821
+ const offline = responses.find((response) => response.id === 4)?.result;
1822
+ assert.doesNotMatch(offline?.content?.[0]?.text ?? "", /budget/i);
1823
+ }
1824
+ finally {
1825
+ rmSync(dir, { recursive: true, force: true });
1826
+ }
1827
+ });
1828
+ test("patchSummary describes a patch and applyResultOptions caps its size", () => {
1829
+ const patch = [
1830
+ "diff --git a/src/a.ts b/src/a.ts",
1831
+ "--- a/src/a.ts",
1832
+ "+++ b/src/a.ts",
1833
+ "@@ -1,3 +1,4 @@",
1834
+ " const a = 1;",
1835
+ "-const b = 2;",
1836
+ "+const b = 3;",
1837
+ "+const c = 4;",
1838
+ "diff --git a/src/b.ts b/src/b.ts",
1839
+ "--- a/src/b.ts",
1840
+ "+++ b/src/b.ts",
1841
+ "@@ -1 +1 @@",
1842
+ "-old",
1843
+ "+new",
1844
+ "",
1845
+ ].join("\n");
1846
+ const summary = patchSummary(patch);
1847
+ assert.equal(summary.files.length, 2);
1848
+ assert.equal(summary.additions, 3);
1849
+ assert.equal(summary.deletions, 2);
1850
+ assert.equal(summary.hunks, 2);
1851
+ assert.equal(summary.bytes, patch.length);
1852
+ const result = applyResultOptions({ content: [], structuredContent: { applied: false, patch } }, { verbosity: "compact" });
1853
+ const structured = result.structuredContent;
1854
+ assert.equal(structured.files.length, 2);
1855
+ assert.equal(structured.patchStats.additions, 3);
1856
+ const text = result.content[0]?.text ?? "";
1857
+ assert.match(text, /fix 2 files/);
1858
+ assert.match(text, /not applied/);
1859
+ const huge = "x".repeat(200_001);
1860
+ const capped = applyResultOptions({ content: [], structuredContent: { applied: true, patch: huge } }, {});
1861
+ const payload = capped.structuredContent;
1862
+ assert.equal(payload.patchTruncated, true);
1863
+ assert.equal(payload.patchBytes, 200_001);
1864
+ assert.equal(payload.patch.length, 200_000);
1865
+ });
128
1866
  //# sourceMappingURL=mcp.test.js.map