@ayushporwal/coja 0.0.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 (455) hide show
  1. package/bin/coja.js +2 -0
  2. package/dist/ai/chat-handler.js +115 -0
  3. package/dist/ai/chats.js +92 -0
  4. package/dist/ai/history.js +70 -0
  5. package/dist/ai/prompt.js +73 -0
  6. package/dist/ai/providers.js +81 -0
  7. package/dist/ai/title.js +77 -0
  8. package/dist/ai/tools.js +495 -0
  9. package/dist/ai/types.js +22 -0
  10. package/dist/app.js +142 -0
  11. package/dist/cli.js +99 -0
  12. package/dist/codex/connection.js +293 -0
  13. package/dist/codex/errors.js +52 -0
  14. package/dist/codex/models.js +97 -0
  15. package/dist/codex/oauth.js +217 -0
  16. package/dist/codex/protocol.js +49 -0
  17. package/dist/codex/provider.js +209 -0
  18. package/dist/context.js +8 -0
  19. package/dist/custom-providers.js +134 -0
  20. package/dist/data-dir.js +25 -0
  21. package/dist/db.js +65 -0
  22. package/dist/forge/forge.js +11 -0
  23. package/dist/forge/gh-cli.js +99 -0
  24. package/dist/forge/github.js +627 -0
  25. package/dist/forge/graphql.js +106 -0
  26. package/dist/forge/queries.js +258 -0
  27. package/dist/git/plumbing.js +519 -0
  28. package/dist/git/remote.js +47 -0
  29. package/dist/git/run.js +256 -0
  30. package/dist/open-browser.js +27 -0
  31. package/dist/paths.js +18 -0
  32. package/dist/pr/fetcher.js +173 -0
  33. package/dist/pr/objects.js +83 -0
  34. package/dist/pr/refs.js +17 -0
  35. package/dist/projects/add.js +111 -0
  36. package/dist/projects/store.js +37 -0
  37. package/dist/routes/chat.js +187 -0
  38. package/dist/routes/fs.js +65 -0
  39. package/dist/routes/git.js +75 -0
  40. package/dist/routes/guard.js +96 -0
  41. package/dist/routes/http.js +35 -0
  42. package/dist/routes/projects.js +51 -0
  43. package/dist/routes/pulls.js +64 -0
  44. package/dist/routes/review.js +113 -0
  45. package/dist/routes/setup.js +285 -0
  46. package/dist/secrets/providers.js +27 -0
  47. package/dist/secrets/store.js +190 -0
  48. package/dist/server.js +51 -0
  49. package/dist/services.js +15 -0
  50. package/dist/shared/api.js +186 -0
  51. package/package.json +35 -0
  52. package/public/assets/abap-CLvhMVsD.js +1 -0
  53. package/public/assets/actionscript-3-Cs6JJXT5.js +1 -0
  54. package/public/assets/ada-C5qYipkI.js +1 -0
  55. package/public/assets/ahk-CA9gdOdF.js +1 -0
  56. package/public/assets/ahk2-_6rzW9BS.js +1 -0
  57. package/public/assets/andromeeda-vGVdxbeo.js +1 -0
  58. package/public/assets/angular-html-BvGMhoM4.js +1 -0
  59. package/public/assets/angular-ts-D4cJlfHk.js +1 -0
  60. package/public/assets/apache-U0d_L8uA.js +1 -0
  61. package/public/assets/apex-KFGFyKwG.js +1 -0
  62. package/public/assets/apl-BAzDJNhF.js +1 -0
  63. package/public/assets/applescript-CCn79oCD.js +1 -0
  64. package/public/assets/ara-4CJ0cIlV.js +1 -0
  65. package/public/assets/asciidoc-SCjQUq34.js +1 -0
  66. package/public/assets/asm-Cmm7eHzH.js +1 -0
  67. package/public/assets/astro-tbl65Kw6.js +1 -0
  68. package/public/assets/aurora-x-CDeNXAV0.js +1 -0
  69. package/public/assets/awk-BWXHIvNe.js +1 -0
  70. package/public/assets/ayu-dark-DluEY0Gj.js +1 -0
  71. package/public/assets/ayu-light-C3h-C4tm.js +1 -0
  72. package/public/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  73. package/public/assets/ballerina-B7ZEbQpA.js +1 -0
  74. package/public/assets/bat-qdxAdBWQ.js +1 -0
  75. package/public/assets/beancount-D-usSTwE.js +1 -0
  76. package/public/assets/berry-DKpUyyne.js +1 -0
  77. package/public/assets/bibtex-Ci_nEsc7.js +1 -0
  78. package/public/assets/bicep-CUHmPFLl.js +1 -0
  79. package/public/assets/bird2-CYIUgYqB.js +1 -0
  80. package/public/assets/blade-Cy5u_P69.js +1 -0
  81. package/public/assets/bsl-_VjlB9mv.js +1 -0
  82. package/public/assets/c-xsYtGME5.js +1 -0
  83. package/public/assets/c3-D0apG41V.js +1 -0
  84. package/public/assets/cadence-CQ2zXKGN.js +1 -0
  85. package/public/assets/cairo-DLTphjLi.js +1 -0
  86. package/public/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  87. package/public/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  88. package/public/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  89. package/public/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  90. package/public/assets/chapel-DlXZg0HF.js +1 -0
  91. package/public/assets/clarity-CtfBBspg.js +1 -0
  92. package/public/assets/clojure-DqKBuwfJ.js +1 -0
  93. package/public/assets/cmake-Bj61d0ZC.js +1 -0
  94. package/public/assets/cobol-NNONwiOX.js +1 -0
  95. package/public/assets/codeowners-C8r90Shi.js +1 -0
  96. package/public/assets/codeql-oeQT6MSM.js +1 -0
  97. package/public/assets/coffee-O853MBTp.js +1 -0
  98. package/public/assets/common-lisp-Cv5bFMCO.js +1 -0
  99. package/public/assets/coq-DuBRPxYg.js +1 -0
  100. package/public/assets/cpp-DtWY3seq.js +1 -0
  101. package/public/assets/crystal-CcZ3zkyx.js +1 -0
  102. package/public/assets/csharp-oqKa8noW.js +1 -0
  103. package/public/assets/css-mSNQLTOb.js +1 -0
  104. package/public/assets/csv-Dx-8-gkx.js +1 -0
  105. package/public/assets/cue-CE9AQfxI.js +1 -0
  106. package/public/assets/cypher-ClKdZ_lG.js +1 -0
  107. package/public/assets/d-qD-0Kul2.js +1 -0
  108. package/public/assets/dark-plus-Cs2F2srj.js +1 -0
  109. package/public/assets/dart-CnvKMtbv.js +1 -0
  110. package/public/assets/dax-BkyTk9wS.js +1 -0
  111. package/public/assets/desktop-Dlh5hvp9.js +1 -0
  112. package/public/assets/diff-woXpYk--.js +1 -0
  113. package/public/assets/docker-IyjqRm3v.js +1 -0
  114. package/public/assets/dotenv-_5a1GRtc.js +1 -0
  115. package/public/assets/dracula-BHWKrbxM.js +1 -0
  116. package/public/assets/dracula-soft-5eyTD99u.js +1 -0
  117. package/public/assets/dream-maker-DW3nJb8Q.js +1 -0
  118. package/public/assets/edge-BA0HOmx0.js +1 -0
  119. package/public/assets/elixir-Buj0VxhK.js +1 -0
  120. package/public/assets/elm-lVH2iZsu.js +1 -0
  121. package/public/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  122. package/public/assets/erb-CbQPls-2.js +1 -0
  123. package/public/assets/erlang-Cphh6RMH.js +1 -0
  124. package/public/assets/everforest-dark-sB-x3p7T.js +1 -0
  125. package/public/assets/everforest-light-Df2xbC6M.js +1 -0
  126. package/public/assets/fennel-DQxkIbk2.js +1 -0
  127. package/public/assets/fish-BJitypiv.js +1 -0
  128. package/public/assets/fluent-C03EYrpw.js +1 -0
  129. package/public/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  130. package/public/assets/fortran-free-form-CYNrtFtB.js +1 -0
  131. package/public/assets/fsharp-D13ZGOAj.js +1 -0
  132. package/public/assets/gdresource-xMC7jr7x.js +1 -0
  133. package/public/assets/gdscript-DYvRD3hg.js +1 -0
  134. package/public/assets/gdshader-CBce3t8t.js +1 -0
  135. package/public/assets/genie-CV2tkWYe.js +1 -0
  136. package/public/assets/gherkin-DExj1W_8.js +1 -0
  137. package/public/assets/git-commit-BSykSTBG.js +1 -0
  138. package/public/assets/git-rebase-DidgtTB0.js +1 -0
  139. package/public/assets/github-dark-C-LZuMrd.js +1 -0
  140. package/public/assets/github-dark-default-DXG-b-1a.js +1 -0
  141. package/public/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  142. package/public/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  143. package/public/assets/github-light-EUqPIrTm.js +1 -0
  144. package/public/assets/github-light-default-BXViO-2h.js +1 -0
  145. package/public/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  146. package/public/assets/gleam-CSRkHgEL.js +1 -0
  147. package/public/assets/glimmer-js-Cck5MdS-.js +1 -0
  148. package/public/assets/glimmer-ts-DMfQxyYr.js +1 -0
  149. package/public/assets/glsl-DPBFrb1h.js +1 -0
  150. package/public/assets/gn-ilITqXS6.js +1 -0
  151. package/public/assets/gnuplot-7GGW24-e.js +1 -0
  152. package/public/assets/go-rLFTqkRN.js +1 -0
  153. package/public/assets/graphql-BvPubeCr.js +1 -0
  154. package/public/assets/groovy-CacY0gHj.js +1 -0
  155. package/public/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  156. package/public/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  157. package/public/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  158. package/public/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  159. package/public/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  160. package/public/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  161. package/public/assets/hack-Drv5y81z.js +1 -0
  162. package/public/assets/haml-DcpINEk-.js +1 -0
  163. package/public/assets/handlebars-sj4UAwHW.js +1 -0
  164. package/public/assets/haskell-D8IpX4py.js +1 -0
  165. package/public/assets/haxe-C8oCnwJJ.js +1 -0
  166. package/public/assets/hcl-Dh228itO.js +1 -0
  167. package/public/assets/hjson-CxZEssPk.js +1 -0
  168. package/public/assets/hlsl-Cvrh5tZx.js +1 -0
  169. package/public/assets/horizon-CE9ld1lL.js +1 -0
  170. package/public/assets/horizon-bright-DSNQnXHK.js +1 -0
  171. package/public/assets/houston-CsvMBhTu.js +1 -0
  172. package/public/assets/html-DQjDPZbg.js +1 -0
  173. package/public/assets/html-derivative-DscSYlel.js +1 -0
  174. package/public/assets/http-CCrfmrUy.js +1 -0
  175. package/public/assets/hurl-DLm6Xdy2.js +1 -0
  176. package/public/assets/hxml-Dcm88srW.js +1 -0
  177. package/public/assets/hy-CZbG8q4J.js +1 -0
  178. package/public/assets/imba-DsUTQ-LC.js +1 -0
  179. package/public/assets/index-BDRMh7Hy.js +2918 -0
  180. package/public/assets/index-Cw-82hD0.css +1 -0
  181. package/public/assets/ini-B5eOa1yu.js +1 -0
  182. package/public/assets/java-CzpiG9dn.js +1 -0
  183. package/public/assets/javascript-2eedadNh.js +1 -0
  184. package/public/assets/jinja-Cch1Cycs.js +1 -0
  185. package/public/assets/jison-C3DQW9A-.js +1 -0
  186. package/public/assets/json-BhBCwuxI.js +1 -0
  187. package/public/assets/json5-BR5RXkoi.js +1 -0
  188. package/public/assets/jsonc-CYpm1nAK.js +1 -0
  189. package/public/assets/jsonl-CmCQp5Yx.js +1 -0
  190. package/public/assets/jsonnet-CJTPZ8u_.js +1 -0
  191. package/public/assets/jssm-DXw9l8Rf.js +1 -0
  192. package/public/assets/jsx-De1vU88o.js +1 -0
  193. package/public/assets/julia-BlJzw_Rq.js +1 -0
  194. package/public/assets/just-A2qsO4cM.js +1 -0
  195. package/public/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  196. package/public/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  197. package/public/assets/kanagawa-wave-BML5mF2J.js +1 -0
  198. package/public/assets/kdl-CsD5j6eV.js +1 -0
  199. package/public/assets/kotlin-DhhofPvG.js +1 -0
  200. package/public/assets/kusto-C7mF5XQf.js +1 -0
  201. package/public/assets/laserwave-C_8bwKvT.js +1 -0
  202. package/public/assets/latex-CDaOJk0y.js +1 -0
  203. package/public/assets/lean-CewbzKMR.js +1 -0
  204. package/public/assets/less-DVTAwKKz.js +1 -0
  205. package/public/assets/light-plus-DVQuIRkW.js +1 -0
  206. package/public/assets/liquid-BOnT7KYX.js +1 -0
  207. package/public/assets/llvm-DqHOMS-s.js +1 -0
  208. package/public/assets/log-BNLmms1o.js +1 -0
  209. package/public/assets/logo-Cluzi2Zq.js +1 -0
  210. package/public/assets/lottie-BT9QSO-G.js +2 -0
  211. package/public/assets/lua-CoLL9qBE.js +1 -0
  212. package/public/assets/luau-C2pBCSgt.js +1 -0
  213. package/public/assets/make-Dixweg8N.js +1 -0
  214. package/public/assets/markdown-BYOwaDjH.js +1 -0
  215. package/public/assets/marko-D8Xuri3Y.js +1 -0
  216. package/public/assets/material-theme-Bm3Qr25_.js +1 -0
  217. package/public/assets/material-theme-darker-2IIEA8gg.js +1 -0
  218. package/public/assets/material-theme-lighter-uhdI0v04.js +1 -0
  219. package/public/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  220. package/public/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  221. package/public/assets/matlab-D7qyCx1q.js +1 -0
  222. package/public/assets/mdc-BhVsbv7E.js +1 -0
  223. package/public/assets/mdx-DQZ5AkYe.js +1 -0
  224. package/public/assets/mermaid-BxpfTIzt.js +1 -0
  225. package/public/assets/min-dark-BSWPekZh.js +1 -0
  226. package/public/assets/min-light-DDpmG2fV.js +1 -0
  227. package/public/assets/mipsasm-BMqwQI7S.js +1 -0
  228. package/public/assets/mojo-fg40_ntQ.js +1 -0
  229. package/public/assets/monokai-B_bV9PQ9.js +1 -0
  230. package/public/assets/moonbit-DgjMoLWr.js +1 -0
  231. package/public/assets/move-BYnGhZoM.js +1 -0
  232. package/public/assets/narrat-_X_XdTYD.js +1 -0
  233. package/public/assets/nextflow-BJtWHP5T.js +1 -0
  234. package/public/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  235. package/public/assets/nginx-CN1bxnHs.js +1 -0
  236. package/public/assets/night-owl-DhmEMT88.js +1 -0
  237. package/public/assets/night-owl-light-eJ-hLW7d.js +1 -0
  238. package/public/assets/nim-BKDc-Eam.js +1 -0
  239. package/public/assets/nix-IvuFDN5E.js +1 -0
  240. package/public/assets/nord-Cb4Vim4T.js +1 -0
  241. package/public/assets/nsis-CjNLYCLg.js +1 -0
  242. package/public/assets/nushell-DHYfO66s.js +1 -0
  243. package/public/assets/objective-c-D1A_Heim.js +1 -0
  244. package/public/assets/objective-cpp-BsSzOQcm.js +1 -0
  245. package/public/assets/ocaml-O90oeIOV.js +1 -0
  246. package/public/assets/odin-B1RWQWA5.js +1 -0
  247. package/public/assets/one-dark-pro-CLwyXe_n.js +1 -0
  248. package/public/assets/one-light-D7Lr4KcI.js +1 -0
  249. package/public/assets/openscad-BUDT5pXO.js +1 -0
  250. package/public/assets/org-CBldiceC.js +1 -0
  251. package/public/assets/pascal-4ZHwLPI5.js +1 -0
  252. package/public/assets/perl-KiFGXvwB.js +1 -0
  253. package/public/assets/php-CjFYgW_Q.js +1 -0
  254. package/public/assets/pierre-dark-CpLgRqie.js +1 -0
  255. package/public/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  256. package/public/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  257. package/public/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  258. package/public/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  259. package/public/assets/pierre-light-CoaEpmwp.js +1 -0
  260. package/public/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  261. package/public/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  262. package/public/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  263. package/public/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  264. package/public/assets/pkl-ot-7Btpt.js +1 -0
  265. package/public/assets/plastic-DQwYfKfQ.js +1 -0
  266. package/public/assets/plsql-DGHpHOYJ.js +1 -0
  267. package/public/assets/po-BiJDBrnU.js +1 -0
  268. package/public/assets/poimandres-DRFjx7u4.js +1 -0
  269. package/public/assets/polar-C7UOKdEL.js +1 -0
  270. package/public/assets/postcss-BXeXVLqQ.js +1 -0
  271. package/public/assets/powerquery-DNMTfnFr.js +1 -0
  272. package/public/assets/powershell-CCSX75yM.js +1 -0
  273. package/public/assets/prisma-DMgNhnYS.js +1 -0
  274. package/public/assets/prolog-iXnhIJG7.js +1 -0
  275. package/public/assets/proto-DB4EqR-F.js +1 -0
  276. package/public/assets/pug-CiQNzZtF.js +1 -0
  277. package/public/assets/puppet-CDv2pdJW.js +1 -0
  278. package/public/assets/purescript-9MfHhQsQ.js +1 -0
  279. package/public/assets/python-gzcpVVnB.js +1 -0
  280. package/public/assets/qml-BynhyZxj.js +1 -0
  281. package/public/assets/qmldir-DCQb3MpD.js +1 -0
  282. package/public/assets/qss-Fe1Jh2GI.js +1 -0
  283. package/public/assets/r-BydfSESc.js +1 -0
  284. package/public/assets/racket-DcIDlBhZ.js +1 -0
  285. package/public/assets/raku-B3gFvitq.js +1 -0
  286. package/public/assets/razor-Bfi-c6ag.js +1 -0
  287. package/public/assets/rbs-C0MpOrj4.js +1 -0
  288. package/public/assets/red-DX1BQOs8.js +1 -0
  289. package/public/assets/reg-CRGYupPL.js +1 -0
  290. package/public/assets/regexp-BFpups_T.js +1 -0
  291. package/public/assets/rel-BtDbiS_P.js +1 -0
  292. package/public/assets/riscv-Ckw8ddFX.js +1 -0
  293. package/public/assets/ron-VUp2lXgN.js +1 -0
  294. package/public/assets/rose-pine-aMsaMD1D.js +1 -0
  295. package/public/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  296. package/public/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  297. package/public/assets/rosmsg-CAekHB0j.js +1 -0
  298. package/public/assets/rst-Z5_5o5cf.js +1 -0
  299. package/public/assets/ruby-DyP6QZ5x.js +1 -0
  300. package/public/assets/rust-Cfkwpbl8.js +1 -0
  301. package/public/assets/sas-DzcEtbFW.js +1 -0
  302. package/public/assets/sass-DXrisJhu.js +1 -0
  303. package/public/assets/scala-BiFt_7mu.js +1 -0
  304. package/public/assets/scheme-DQCgrYNe.js +1 -0
  305. package/public/assets/scss-BVi50TCh.js +1 -0
  306. package/public/assets/sdbl-bTVj8UrX.js +1 -0
  307. package/public/assets/shaderlab-TOUzSsQk.js +1 -0
  308. package/public/assets/shellscript-DwBEbT4l.js +1 -0
  309. package/public/assets/shellsession-DtCkQKX0.js +1 -0
  310. package/public/assets/slack-dark-DnToyrRv.js +1 -0
  311. package/public/assets/slack-ochin-B2OO5cIa.js +1 -0
  312. package/public/assets/smalltalk-BHpQb5nM.js +1 -0
  313. package/public/assets/smithy-Dgaz7aur.js +1 -0
  314. package/public/assets/snazzy-light-4G7pJPwS.js +1 -0
  315. package/public/assets/solarized-dark-UdsmMDCL.js +1 -0
  316. package/public/assets/solarized-light-C1IUL_tW.js +1 -0
  317. package/public/assets/solidity-Bj7E1mRn.js +1 -0
  318. package/public/assets/soy-Col_eH-0.js +1 -0
  319. package/public/assets/sparql-D_iOobhT.js +1 -0
  320. package/public/assets/splunk-BC2Px7Mm.js +1 -0
  321. package/public/assets/sql-CF1geVQJ.js +1 -0
  322. package/public/assets/ssh-config-BgfXC-Er.js +1 -0
  323. package/public/assets/stata-JT7HPvq1.js +1 -0
  324. package/public/assets/stylus-B6D30XZt.js +1 -0
  325. package/public/assets/surrealql-kYIUYZug.js +1 -0
  326. package/public/assets/svelte-CsWZn_FS.js +1 -0
  327. package/public/assets/swift-CyEgAFGc.js +1 -0
  328. package/public/assets/synthwave-84-nFMaYfgc.js +1 -0
  329. package/public/assets/system-verilog-Dh5ihWbf.js +1 -0
  330. package/public/assets/systemd-BxMlprV5.js +1 -0
  331. package/public/assets/talonscript-CohzipZa.js +1 -0
  332. package/public/assets/tasl-DMoTqEGO.js +1 -0
  333. package/public/assets/tcl-CZd0xW_V.js +1 -0
  334. package/public/assets/templ-DCsT8WEJ.js +1 -0
  335. package/public/assets/terraform-DswuEJGm.js +1 -0
  336. package/public/assets/tex-ZyN9TW16.js +1 -0
  337. package/public/assets/tokyo-night-oM2G3aXe.js +1 -0
  338. package/public/assets/toml-CcmNWLt0.js +1 -0
  339. package/public/assets/ts-tags-ArLnwaTi.js +1 -0
  340. package/public/assets/tsv-sltzmVWM.js +1 -0
  341. package/public/assets/tsx-CXwa5__r.js +1 -0
  342. package/public/assets/turtle-ByJddavk.js +1 -0
  343. package/public/assets/twig-BsBKj8BU.js +1 -0
  344. package/public/assets/typescript-Brm8t4pO.js +1 -0
  345. package/public/assets/typespec-BRdr0IET.js +1 -0
  346. package/public/assets/typst-vNjrO6QE.js +1 -0
  347. package/public/assets/v-DBj4K9d_.js +1 -0
  348. package/public/assets/vala-zf12oZj6.js +1 -0
  349. package/public/assets/vb-DOwzaHZ0.js +1 -0
  350. package/public/assets/verilog-B2gwz-sJ.js +1 -0
  351. package/public/assets/vesper-DdrHHSXu.js +1 -0
  352. package/public/assets/vhdl-BroJfC0k.js +1 -0
  353. package/public/assets/viml-DvXPmvsu.js +1 -0
  354. package/public/assets/vitesse-black-fwtXNY1n.js +1 -0
  355. package/public/assets/vitesse-dark-BZCL-v6S.js +1 -0
  356. package/public/assets/vitesse-light-VbXTXTou.js +1 -0
  357. package/public/assets/vue-CcPq89qx.js +1 -0
  358. package/public/assets/vue-html-B5qW2N7E.js +1 -0
  359. package/public/assets/vue-vine-LYElP1mf.js +1 -0
  360. package/public/assets/vyper-CgoNMtux.js +1 -0
  361. package/public/assets/wasm-BnjxR4X6.js +1 -0
  362. package/public/assets/wasm-ByWQv1Qj.js +1 -0
  363. package/public/assets/wenyan-C8pVoKbM.js +1 -0
  364. package/public/assets/wgsl-BsKzXJz4.js +1 -0
  365. package/public/assets/wikitext-ClFFjSW2.js +1 -0
  366. package/public/assets/wit-DdvCle-K.js +1 -0
  367. package/public/assets/wolfram-DLL8P-h_.js +1 -0
  368. package/public/assets/xml-D5hvN78Q.js +1 -0
  369. package/public/assets/xsl-CBSZ35CX.js +1 -0
  370. package/public/assets/yaml-D4FTZq40.js +1 -0
  371. package/public/assets/zenscript-BnlCZFoB.js +1 -0
  372. package/public/assets/zig-CMLA9XwU.js +1 -0
  373. package/public/brand/coja/cj-dark.svg +1 -0
  374. package/public/brand/coja/cj-light.svg +1 -0
  375. package/public/brand/coja/cj-themeable.svg +1 -0
  376. package/public/brand/coja/lottie/cj-huge-dark.json +311 -0
  377. package/public/brand/coja/lottie/cj-huge-light.json +311 -0
  378. package/public/brand/coja/lottie/cj-large-dark.json +311 -0
  379. package/public/brand/coja/lottie/cj-large-light.json +311 -0
  380. package/public/brand/coja/lottie/cj-medium-dark.json +311 -0
  381. package/public/brand/coja/lottie/cj-medium-light.json +311 -0
  382. package/public/brand/coja/lottie/cj-small-dark.json +311 -0
  383. package/public/brand/coja/lottie/cj-small-light.json +311 -0
  384. package/public/brand/coja/manifest.json +172 -0
  385. package/public/brand/coja/themes/ember/dark/cj-huge.json +311 -0
  386. package/public/brand/coja/themes/ember/dark/cj-large.json +311 -0
  387. package/public/brand/coja/themes/ember/dark/cj-medium.json +311 -0
  388. package/public/brand/coja/themes/ember/dark/cj-small.json +311 -0
  389. package/public/brand/coja/themes/ember/dark/cj.svg +1 -0
  390. package/public/brand/coja/themes/ember/light/cj-huge.json +311 -0
  391. package/public/brand/coja/themes/ember/light/cj-large.json +311 -0
  392. package/public/brand/coja/themes/ember/light/cj-medium.json +311 -0
  393. package/public/brand/coja/themes/ember/light/cj-small.json +311 -0
  394. package/public/brand/coja/themes/ember/light/cj.svg +1 -0
  395. package/public/brand/coja/themes/grove/dark/cj-huge.json +311 -0
  396. package/public/brand/coja/themes/grove/dark/cj-large.json +311 -0
  397. package/public/brand/coja/themes/grove/dark/cj-medium.json +311 -0
  398. package/public/brand/coja/themes/grove/dark/cj-small.json +311 -0
  399. package/public/brand/coja/themes/grove/dark/cj.svg +1 -0
  400. package/public/brand/coja/themes/grove/light/cj-huge.json +311 -0
  401. package/public/brand/coja/themes/grove/light/cj-large.json +311 -0
  402. package/public/brand/coja/themes/grove/light/cj-medium.json +311 -0
  403. package/public/brand/coja/themes/grove/light/cj-small.json +311 -0
  404. package/public/brand/coja/themes/grove/light/cj.svg +1 -0
  405. package/public/brand/coja/themes/iris/dark/cj-huge.json +311 -0
  406. package/public/brand/coja/themes/iris/dark/cj-large.json +311 -0
  407. package/public/brand/coja/themes/iris/dark/cj-medium.json +311 -0
  408. package/public/brand/coja/themes/iris/dark/cj-small.json +311 -0
  409. package/public/brand/coja/themes/iris/dark/cj.svg +1 -0
  410. package/public/brand/coja/themes/iris/light/cj-huge.json +311 -0
  411. package/public/brand/coja/themes/iris/light/cj-large.json +311 -0
  412. package/public/brand/coja/themes/iris/light/cj-medium.json +311 -0
  413. package/public/brand/coja/themes/iris/light/cj-small.json +311 -0
  414. package/public/brand/coja/themes/iris/light/cj.svg +1 -0
  415. package/public/brand/coja/themes/mulberry/dark/cj-huge.json +311 -0
  416. package/public/brand/coja/themes/mulberry/dark/cj-large.json +311 -0
  417. package/public/brand/coja/themes/mulberry/dark/cj-medium.json +311 -0
  418. package/public/brand/coja/themes/mulberry/dark/cj-small.json +311 -0
  419. package/public/brand/coja/themes/mulberry/dark/cj.svg +1 -0
  420. package/public/brand/coja/themes/mulberry/light/cj-huge.json +311 -0
  421. package/public/brand/coja/themes/mulberry/light/cj-large.json +311 -0
  422. package/public/brand/coja/themes/mulberry/light/cj-medium.json +311 -0
  423. package/public/brand/coja/themes/mulberry/light/cj-small.json +311 -0
  424. package/public/brand/coja/themes/mulberry/light/cj.svg +1 -0
  425. package/public/brand/coja/themes/ocean/dark/cj-huge.json +311 -0
  426. package/public/brand/coja/themes/ocean/dark/cj-large.json +311 -0
  427. package/public/brand/coja/themes/ocean/dark/cj-medium.json +311 -0
  428. package/public/brand/coja/themes/ocean/dark/cj-small.json +311 -0
  429. package/public/brand/coja/themes/ocean/dark/cj.svg +1 -0
  430. package/public/brand/coja/themes/ocean/light/cj-huge.json +311 -0
  431. package/public/brand/coja/themes/ocean/light/cj-large.json +311 -0
  432. package/public/brand/coja/themes/ocean/light/cj-medium.json +311 -0
  433. package/public/brand/coja/themes/ocean/light/cj-small.json +311 -0
  434. package/public/brand/coja/themes/ocean/light/cj.svg +1 -0
  435. package/public/fonts/ibm-plex-sans/IBMPlexSans-Bold.woff2 +0 -0
  436. package/public/fonts/ibm-plex-sans/IBMPlexSans-BoldItalic.woff2 +0 -0
  437. package/public/fonts/ibm-plex-sans/IBMPlexSans-Italic.woff2 +0 -0
  438. package/public/fonts/ibm-plex-sans/IBMPlexSans-Medium.woff2 +0 -0
  439. package/public/fonts/ibm-plex-sans/IBMPlexSans-MediumItalic.woff2 +0 -0
  440. package/public/fonts/ibm-plex-sans/IBMPlexSans-Regular.woff2 +0 -0
  441. package/public/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.woff2 +0 -0
  442. package/public/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.woff2 +0 -0
  443. package/public/fonts/ibm-plex-sans/LICENSE.txt +93 -0
  444. package/public/fonts/ibm-plex-sans/PROVENANCE.md +10 -0
  445. package/public/fonts/maple-mono-nf/LICENSE.txt +93 -0
  446. package/public/fonts/maple-mono-nf/MapleMono-NF-Bold.woff2 +0 -0
  447. package/public/fonts/maple-mono-nf/MapleMono-NF-BoldItalic.woff2 +0 -0
  448. package/public/fonts/maple-mono-nf/MapleMono-NF-Italic.woff2 +0 -0
  449. package/public/fonts/maple-mono-nf/MapleMono-NF-Medium.woff2 +0 -0
  450. package/public/fonts/maple-mono-nf/MapleMono-NF-MediumItalic.woff2 +0 -0
  451. package/public/fonts/maple-mono-nf/MapleMono-NF-Regular.woff2 +0 -0
  452. package/public/fonts/maple-mono-nf/MapleMono-NF-SemiBold.woff2 +0 -0
  453. package/public/fonts/maple-mono-nf/MapleMono-NF-SemiBoldItalic.woff2 +0 -0
  454. package/public/fonts/maple-mono-nf/PROVENANCE.md +12 -0
  455. package/public/index.html +14 -0
@@ -0,0 +1,495 @@
1
+ import { tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { blame, blobInfo, countLines, diff, grep, log, lsTree, showFile } from '../git/plumbing.js';
4
+ export const REVIEW_TOOL_NAMES = [
5
+ 'read_file',
6
+ 'list_files',
7
+ 'grep',
8
+ 'get_diff',
9
+ 'git_log',
10
+ 'git_blame',
11
+ ];
12
+ // Bounded results keep one turn's context predictable.
13
+ export const READ_DEFAULT_LINES = 300;
14
+ export const READ_MAX_LINES = 400;
15
+ export const LIST_MAX_ENTRIES = 500;
16
+ export const GREP_MAX_MATCHES = 100;
17
+ export const GREP_MAX_TEXT_CHARS = 300;
18
+ export const DIFF_FILE_MAX_CHARS = 60_000;
19
+ export const DIFF_ALL_MAX_CHARS = 40_000;
20
+ export const LOG_MAX_COUNT = 50;
21
+ export const BLAME_MAX_LINES = 200;
22
+ const MiB = 1024 * 1024;
23
+ /** Bytes of a blob read_file and git_blame read; lines beyond are still counted, just not shown. */
24
+ export const MAX_BLOB_BYTES = 5 * MiB;
25
+ const TOOL_DESCRIPTIONS = {
26
+ read_file: `Read a file at the PR head ("head") or at the merge base ("base": the old version the diff's left side shows). ` +
27
+ `Returns at most ${READ_MAX_LINES} lines per call (the first ${READ_DEFAULT_LINES} by default), each prefixed with its line number so you can cite path:line. ` +
28
+ 'Use startLine/endLine to read further.',
29
+ list_files: 'List the files (path and size in bytes) in the repository tree at head or base, recursively, optionally only under a directory such as "src/". ' +
30
+ `At most ${LIST_MAX_ENTRIES} entries.`,
31
+ grep: 'Search file contents at head or base with an extended regular expression (git grep -E), optionally limited to a path or glob such as "src/*.ts". ' +
32
+ `Returns at most ${GREP_MAX_MATCHES} matches (path, line, text). Cheaper than reading whole files.`,
33
+ get_diff: 'The pull request diff (merge base to head). Without arguments: the changed files with per-file additions and deletions, plus the whole patch when it is small. ' +
34
+ 'With a file path: the unified patch for that one file.',
35
+ git_log: 'The commits this pull request adds (merge base to head) with their messages, newest first, optionally only those touching a path.',
36
+ git_blame: 'Per-line authorship of a file at head or base: which commit (author, date, summary) last changed each line. ' +
37
+ `At most ${BLAME_MAX_LINES} lines per call; use startLine/endLine.`,
38
+ };
39
+ /** Names and one-line descriptions for the "what does the AI see?" affordance. */
40
+ export function describeTools() {
41
+ return REVIEW_TOOL_NAMES.map((name) => ({ name, description: TOOL_DESCRIPTIONS[name] }));
42
+ }
43
+ const refSchema = z
44
+ .enum(['base', 'head'])
45
+ .describe('"head": the pull request\'s code. "base": the merge base, i.e. the old version.');
46
+ const pathSchema = z
47
+ .string()
48
+ .min(1)
49
+ .describe('Repository-relative path, exactly as in the changed-files list or a list_files result.');
50
+ const lineSchema = z.number().int().min(1);
51
+ /** Build the six tools bound to one pull request. The returned object has exactly the REVIEW_TOOL_NAMES keys. */
52
+ export function createReviewTools(ctx, opts = {}) {
53
+ const maxBlobBytes = opts.maxBlobBytes ?? MAX_BLOB_BYTES;
54
+ const oidOf = (ref) => (ref === 'head' ? ctx.headOid : ctx.baseOid);
55
+ return {
56
+ read_file: tool({
57
+ description: TOOL_DESCRIPTIONS.read_file,
58
+ inputSchema: z.object({
59
+ ref: refSchema,
60
+ path: pathSchema,
61
+ startLine: lineSchema
62
+ .optional()
63
+ .describe('First line to return (1-based, inclusive). Default 1.'),
64
+ endLine: lineSchema
65
+ .optional()
66
+ .describe(`Last line to return (inclusive). Default startLine + ${READ_DEFAULT_LINES - 1}; at most ${READ_MAX_LINES} lines are returned per call.`),
67
+ }),
68
+ execute: async ({ ref, path, startLine, endLine }) => {
69
+ const oid = oidOf(ref);
70
+ const file = await readTextFile(ctx, oid, ref, path, maxBlobBytes);
71
+ const window = resolveWindow(startLine, endLine, file.lineCount, READ_DEFAULT_LINES, READ_MAX_LINES);
72
+ // Past the byte cap the lines exist (lineCount counts them) but were not read.
73
+ const shownEnd = Math.min(window.end, file.lines.length);
74
+ const notes = [];
75
+ if (file.truncated)
76
+ notes.push(truncationNote(file));
77
+ if (shownEnd < window.end) {
78
+ notes.push(`Lines ${Math.max(window.start, shownEnd + 1)}-${window.end} lie beyond what was read and are not shown; git_blame returns the content of any line range.`);
79
+ }
80
+ else if (window.end < file.lineCount) {
81
+ notes.push(`Showing lines ${window.start}-${window.end} of ${file.lineCount}. Call read_file again with startLine=${window.end + 1} to continue.`);
82
+ }
83
+ return {
84
+ path,
85
+ ref,
86
+ oid,
87
+ startLine: window.start,
88
+ endLine: window.end,
89
+ lineCount: file.lineCount,
90
+ truncated: window.truncated || shownEnd < window.end,
91
+ ...(notes.length > 0 ? { note: notes.join(' ') } : {}),
92
+ content: numberLines(file.lines.slice(window.start - 1, shownEnd), window.start),
93
+ };
94
+ },
95
+ }),
96
+ list_files: tool({
97
+ description: TOOL_DESCRIPTIONS.list_files,
98
+ inputSchema: z.object({
99
+ ref: refSchema,
100
+ path: z
101
+ .string()
102
+ .min(1)
103
+ .optional()
104
+ .describe('Only list files under this directory, e.g. "src/" or "src/util". Default: the whole tree.'),
105
+ }),
106
+ execute: async ({ ref, path }) => {
107
+ const oid = oidOf(ref);
108
+ const prefix = path?.replace(/\/+$/, '');
109
+ const entries = (await lsTree(ctx.repo, oid, { recursive: true, ...(prefix ? { path: prefix } : {}) })).filter((e) => e.type === 'blob');
110
+ const truncated = entries.length > LIST_MAX_ENTRIES;
111
+ let note;
112
+ if (truncated) {
113
+ note = `${entries.length} files; showing the first ${LIST_MAX_ENTRIES}. Narrow with path, or use grep.`;
114
+ }
115
+ else if (entries.length === 0) {
116
+ note = prefix
117
+ ? `Nothing under ${prefix} at ${ref}. Check the path with list_files on a parent directory.`
118
+ : `The tree at ${ref} is empty.`;
119
+ }
120
+ return {
121
+ ref,
122
+ entries: entries
123
+ .slice(0, LIST_MAX_ENTRIES)
124
+ .map((e) => ({ path: e.path, size: e.size ?? 0 })),
125
+ truncated,
126
+ ...(note ? { note } : {}),
127
+ };
128
+ },
129
+ }),
130
+ grep: tool({
131
+ description: TOOL_DESCRIPTIONS.grep,
132
+ inputSchema: z.object({
133
+ ref: refSchema,
134
+ pattern: z
135
+ .string()
136
+ .min(1)
137
+ .describe('Extended regular expression (POSIX ERE, as in git grep -E).'),
138
+ pathspec: z
139
+ .string()
140
+ .min(1)
141
+ .optional()
142
+ .describe('Limit the search to a path or glob, e.g. "src/" or "*.ts".'),
143
+ ignoreCase: z.boolean().optional(),
144
+ }),
145
+ execute: async ({ ref, pattern, pathspec, ignoreCase }) => {
146
+ // Glob pathspecs ("src/*.ts") are meant to work here, so they pass through as given;
147
+ // the plumbing's assertPathspec still blocks pathspec magic (a leading ":"), option-like
148
+ // values (a leading "-"), ".." and absolute paths.
149
+ const { matches, truncated } = await grep(ctx.repo, oidOf(ref), pattern, {
150
+ ...(pathspec !== undefined ? { pathspec } : {}),
151
+ ...(ignoreCase !== undefined ? { ignoreCase } : {}),
152
+ maxMatches: GREP_MAX_MATCHES,
153
+ });
154
+ return {
155
+ matches: matches.map((m) => ({
156
+ path: m.path,
157
+ line: m.line,
158
+ text: clip(m.text, GREP_MAX_TEXT_CHARS),
159
+ })),
160
+ truncated,
161
+ ...(truncated
162
+ ? {
163
+ note: `More than ${GREP_MAX_MATCHES} matches; showing the first ${GREP_MAX_MATCHES}. Narrow the pattern or pathspec.`,
164
+ }
165
+ : {}),
166
+ };
167
+ },
168
+ }),
169
+ get_diff: tool({
170
+ description: TOOL_DESCRIPTIONS.get_diff,
171
+ inputSchema: z.object({
172
+ file: pathSchema
173
+ .optional()
174
+ .describe('A changed file (its new path for renames). Omit for the whole-PR overview with per-file stats.'),
175
+ }),
176
+ execute: async ({ file }) => (file === undefined ? wholeDiff(ctx) : fileDiff(ctx, file)),
177
+ }),
178
+ git_log: tool({
179
+ description: TOOL_DESCRIPTIONS.git_log,
180
+ inputSchema: z.object({
181
+ path: pathSchema.optional().describe('Only commits that touch this path.'),
182
+ maxCount: lineSchema
183
+ .optional()
184
+ .describe(`How many commits at most (default and maximum ${LOG_MAX_COUNT}).`),
185
+ }),
186
+ execute: async ({ path, maxCount }) => {
187
+ const limit = Math.min(maxCount ?? LOG_MAX_COUNT, LOG_MAX_COUNT);
188
+ const entries = await log(ctx.repo, {
189
+ range: `${ctx.baseOid}..${ctx.headOid}`,
190
+ ...(path !== undefined ? { path } : {}),
191
+ // One extra tells us whether the list was cut.
192
+ maxCount: limit + 1,
193
+ });
194
+ const truncated = entries.length > limit;
195
+ return {
196
+ range: `base..head (${short(ctx.baseOid)}..${short(ctx.headOid)})`,
197
+ commits: entries.slice(0, limit).map((c) => ({
198
+ oid: c.oid,
199
+ abbreviatedOid: c.abbreviatedOid,
200
+ author: c.authorName,
201
+ authoredAt: c.authoredAt,
202
+ subject: c.subject,
203
+ body: c.body,
204
+ })),
205
+ truncated,
206
+ ...(truncated
207
+ ? { note: `More than ${limit} commits; showing the newest ${limit}.` }
208
+ : {}),
209
+ };
210
+ },
211
+ }),
212
+ git_blame: tool({
213
+ description: TOOL_DESCRIPTIONS.git_blame,
214
+ inputSchema: z.object({
215
+ ref: refSchema,
216
+ path: pathSchema,
217
+ startLine: lineSchema.optional().describe('First line (1-based, inclusive). Default 1.'),
218
+ endLine: lineSchema
219
+ .optional()
220
+ .describe(`Last line (inclusive). At most ${BLAME_MAX_LINES} lines per call.`),
221
+ }),
222
+ execute: async ({ ref, path, startLine, endLine }) => {
223
+ const oid = oidOf(ref);
224
+ const file = await readTextFile(ctx, oid, ref, path, maxBlobBytes);
225
+ const window = resolveWindow(startLine, endLine, file.lineCount, BLAME_MAX_LINES, BLAME_MAX_LINES);
226
+ // git blame reads the whole blob itself, so lines past the read cap are still attributed.
227
+ const lines = file.lineCount === 0
228
+ ? []
229
+ : await blame(ctx.repo, oid, path, { startLine: window.start, endLine: window.end });
230
+ const commits = {};
231
+ for (const l of lines) {
232
+ commits[l.abbreviatedOid] ??= {
233
+ oid: l.oid,
234
+ author: l.author,
235
+ authoredAt: l.authorTime,
236
+ summary: l.summary,
237
+ };
238
+ }
239
+ const notes = [];
240
+ if (file.truncated)
241
+ notes.push(truncationNote(file));
242
+ if (window.end < file.lineCount) {
243
+ notes.push(`Lines ${window.start}-${window.end} of ${file.lineCount}. Continue with startLine=${window.end + 1}.`);
244
+ }
245
+ return {
246
+ ref,
247
+ path,
248
+ oid,
249
+ startLine: window.start,
250
+ endLine: window.end,
251
+ lineCount: file.lineCount,
252
+ truncated: window.truncated,
253
+ ...(notes.length > 0 ? { note: notes.join(' ') } : {}),
254
+ /** Keyed by abbreviated commit oid, as referenced by `lines[].commit`. */
255
+ commits,
256
+ lines: lines.map((l) => ({ line: l.line, commit: l.abbreviatedOid, content: l.content })),
257
+ };
258
+ },
259
+ }),
260
+ };
261
+ }
262
+ // ---------------------------------------------------------------------------
263
+ // Diff helpers
264
+ // ---------------------------------------------------------------------------
265
+ async function fileDiff(ctx, file) {
266
+ const entry = ctx.files.find((f) => f.path === file) ?? ctx.files.find((f) => f.previousPath === file);
267
+ if (!entry) {
268
+ throw new Error(`${file} is not changed in this pull request. Call get_diff without arguments to list the changed files.`);
269
+ }
270
+ const paths = entry.previousPath !== undefined && entry.previousPath !== entry.path
271
+ ? [entry.path, entry.previousPath]
272
+ : [entry.path];
273
+ const { patch, truncated } = await diff(ctx.repo, ctx.baseOid, ctx.headOid, { paths });
274
+ const binary = !truncated && isBinaryPatch(patch);
275
+ let text = patch;
276
+ let cut = false;
277
+ let note;
278
+ if (truncated) {
279
+ text = '';
280
+ cut = true;
281
+ note =
282
+ 'The patch is larger than 2 MiB and was withheld. Read the file at base and head with read_file instead.';
283
+ }
284
+ else if (patch.length > DIFF_FILE_MAX_CHARS) {
285
+ text = clipAtLine(patch, DIFF_FILE_MAX_CHARS);
286
+ cut = true;
287
+ note = `Patch truncated at ${DIFF_FILE_MAX_CHARS} of ${patch.length} characters. Use read_file with line windows at head and base for the rest.`;
288
+ }
289
+ else if (binary) {
290
+ note = 'Binary file: there is no text patch.';
291
+ }
292
+ return {
293
+ file: entry.path,
294
+ ...(entry.previousPath !== undefined ? { previousPath: entry.previousPath } : {}),
295
+ status: entry.status,
296
+ binary,
297
+ patch: text,
298
+ truncated: cut,
299
+ ...(note ? { note } : {}),
300
+ };
301
+ }
302
+ async function wholeDiff(ctx) {
303
+ const full = await diff(ctx.repo, ctx.baseOid, ctx.headOid);
304
+ let stats;
305
+ if (!full.truncated) {
306
+ stats = patchStats(full.patch);
307
+ }
308
+ else {
309
+ // Too big to hold with context lines; stats still come from a context-free patch.
310
+ const bare = await diff(ctx.repo, ctx.baseOid, ctx.headOid, { unified: 0, maxBytes: 32 * MiB });
311
+ stats = bare.truncated ? new Map() : patchStats(bare.patch);
312
+ }
313
+ const files = ctx.files.map((f) => {
314
+ const s = stats.get(f.path);
315
+ return {
316
+ ...f,
317
+ ...(s ? { additions: s.additions, deletions: s.deletions } : {}),
318
+ ...(s?.binary ? { binary: true } : {}),
319
+ };
320
+ });
321
+ const includePatch = !full.truncated && full.patch.length <= DIFF_ALL_MAX_CHARS;
322
+ return {
323
+ fileCount: files.length,
324
+ files,
325
+ ...(includePatch
326
+ ? { patch: full.patch }
327
+ : {
328
+ note: `The whole patch is ${full.truncated ? 'over 2 MiB' : `${full.patch.length} characters`}, too large to include. Call get_diff with a file path to see one file's patch.`,
329
+ }),
330
+ };
331
+ }
332
+ /**
333
+ * Per-file added/removed line counts from a unified patch, keyed by the new
334
+ * path (the old path for deletions). Header lines (`---`/`+++`) are only
335
+ * recognised before a section's first hunk, so removed lines that happen to
336
+ * start with `--` are counted correctly. Sections without headers (binaries,
337
+ * mode changes) fall back to the `diff --git a/P b/P` line. Paths git quotes
338
+ * (non-ASCII, control characters) are left out rather than guessed.
339
+ */
340
+ export function patchStats(patch) {
341
+ const stats = new Map();
342
+ let current = null;
343
+ let inHunk = false;
344
+ let oldPath = null;
345
+ let newPath = null;
346
+ let samePath = null;
347
+ const real = (p) => (p !== null && p !== '/dev/null' ? p : null);
348
+ const flush = () => {
349
+ if (!current)
350
+ return;
351
+ const key = real(newPath) ?? real(oldPath) ?? samePath;
352
+ if (key !== null)
353
+ stats.set(key, current);
354
+ };
355
+ for (const line of patch.split('\n')) {
356
+ if (line.startsWith('diff --git ')) {
357
+ flush();
358
+ current = { additions: 0, deletions: 0, binary: false };
359
+ inHunk = false;
360
+ oldPath = null;
361
+ newPath = null;
362
+ samePath = parseSamePath(line.slice('diff --git '.length));
363
+ continue;
364
+ }
365
+ if (!current)
366
+ continue;
367
+ if (!inHunk) {
368
+ if (line.startsWith('@@'))
369
+ inHunk = true;
370
+ else if (line.startsWith('--- '))
371
+ oldPath = stripPrefix(line.slice(4), 'a/');
372
+ else if (line.startsWith('+++ '))
373
+ newPath = stripPrefix(line.slice(4), 'b/');
374
+ else if (line.startsWith('rename from '))
375
+ oldPath = line.slice('rename from '.length);
376
+ else if (line.startsWith('rename to '))
377
+ newPath = line.slice('rename to '.length);
378
+ else if (line.startsWith('Binary files ') || line === 'GIT binary patch')
379
+ current.binary = true;
380
+ continue;
381
+ }
382
+ if (line.startsWith('@@'))
383
+ continue;
384
+ if (line.startsWith('+'))
385
+ current.additions++;
386
+ else if (line.startsWith('-'))
387
+ current.deletions++;
388
+ }
389
+ flush();
390
+ return stats;
391
+ }
392
+ /** `a/P b/P` → `P` when both sides name the same path (spaces included); null otherwise. */
393
+ function parseSamePath(rest) {
394
+ if (!rest.startsWith('a/') || rest.startsWith('"'))
395
+ return null;
396
+ const n = (rest.length - 5) / 2;
397
+ if (!Number.isInteger(n) || n <= 0)
398
+ return null;
399
+ const a = rest.slice(2, 2 + n);
400
+ return rest.slice(2 + n, 5 + n) === ' b/' && rest.slice(5 + n) === a ? a : null;
401
+ }
402
+ const stripPrefix = (s, prefix) => s.startsWith(prefix) ? s.slice(prefix.length) : s;
403
+ const isBinaryPatch = (patch) => /^Binary files .* differ$/m.test(patch) || /^GIT binary patch$/m.test(patch);
404
+ /**
405
+ * The text of `path` at `oid`, with clear errors for missing paths, directories
406
+ * and binaries. A blob over `maxBytes` is read up to the cap: `lines` then
407
+ * holds the complete lines that fit while `lineCount` is still the true count,
408
+ * so a window past the cap is "not shown" rather than a false "past the end".
409
+ */
410
+ async function readTextFile(ctx, oid, ref, path, maxBytes) {
411
+ const info = await blobInfo(ctx.repo, oid, path);
412
+ if (!info.exists) {
413
+ throw new Error(`${path} does not exist at ${ref} (${short(oid)})${missingHint(ctx, ref, path)}`);
414
+ }
415
+ if (info.type === 'tree') {
416
+ throw new Error(`${path} is a directory at ${ref}; use list_files to see the files in it`);
417
+ }
418
+ if (info.type !== 'blob')
419
+ throw new Error(`${path} is a ${info.type} at ${ref}, not a file`);
420
+ const file = await showFile(ctx.repo, oid, path, { maxBytes });
421
+ if (file.binary) {
422
+ throw new Error(`binary file: ${path} at ${ref} (${file.size} bytes) has no text to show`);
423
+ }
424
+ const lines = splitLines(file.text);
425
+ if (!file.truncated) {
426
+ return { lines, lineCount: lines.length, size: file.size, truncated: false, maxBytes };
427
+ }
428
+ // The cut lands mid-line unless the last byte read was a newline; a partial line is worse than none.
429
+ if (!file.text.endsWith('\n'))
430
+ lines.pop();
431
+ const lineCount = await countLines(ctx.repo, oid, path);
432
+ return { lines, lineCount, size: file.size, truncated: true, maxBytes };
433
+ }
434
+ /** Shared by read_file and git_blame, so the model hears one story about a capped blob. */
435
+ function truncationNote(file) {
436
+ return `The file is ${file.size} bytes; only its first ${describeBytes(file.maxBytes)} (${file.lines.length} complete lines of ${file.lineCount}) were read.`;
437
+ }
438
+ const describeBytes = (n) => (n % MiB === 0 ? `${n / MiB} MiB` : `${n} bytes`);
439
+ /** Why a path may be missing at one side of the PR, from the changed-files list. */
440
+ function missingHint(ctx, ref, path) {
441
+ const entry = ctx.files.find((f) => f.path === path || f.previousPath === path);
442
+ if (!entry)
443
+ return '';
444
+ if (ref === 'head') {
445
+ if (entry.status === 'D')
446
+ return '; this PR deletes it, read it with ref "base"';
447
+ if (entry.previousPath === path)
448
+ return `; this PR renames it to ${entry.path}`;
449
+ }
450
+ else {
451
+ if (entry.status === 'A')
452
+ return '; this PR adds it, read it with ref "head"';
453
+ if (entry.previousPath !== undefined && entry.path === path) {
454
+ return `; at base it is ${entry.previousPath}`;
455
+ }
456
+ }
457
+ return '';
458
+ }
459
+ /** Clamp a requested 1-based inclusive line range to the file and to `maxLines`. */
460
+ function resolveWindow(startLine, endLine, lineCount, defaultLines, maxLines) {
461
+ const start = startLine ?? 1;
462
+ if (endLine !== undefined && endLine < start) {
463
+ throw new Error(`endLine (${endLine}) must not be smaller than startLine (${start})`);
464
+ }
465
+ if (lineCount === 0)
466
+ return { start: 1, end: 0, truncated: false };
467
+ if (start > lineCount) {
468
+ throw new Error(`startLine ${start} is past the end of the file, which has ${lineCount} lines`);
469
+ }
470
+ const requestedEnd = Math.min(endLine ?? start + defaultLines - 1, lineCount);
471
+ const cappedEnd = Math.min(requestedEnd, start + maxLines - 1);
472
+ return { start, end: cappedEnd, truncated: cappedEnd < requestedEnd };
473
+ }
474
+ /** Lines of `text`; a trailing newline does not start an extra empty line. */
475
+ function splitLines(text) {
476
+ if (text === '')
477
+ return [];
478
+ const lines = text.split('\n');
479
+ if (lines[lines.length - 1] === '')
480
+ lines.pop();
481
+ return lines;
482
+ }
483
+ /** ` 42│ …` — right-aligned line numbers so the model can cite `path:line`. */
484
+ function numberLines(lines, start) {
485
+ const width = Math.max(4, String(start + lines.length - 1).length);
486
+ return lines.map((line, i) => `${String(start + i).padStart(width)}│ ${line}`).join('\n');
487
+ }
488
+ const short = (oid) => oid.slice(0, 7);
489
+ const clip = (s, max) => (s.length > max ? `${s.slice(0, max)}…` : s);
490
+ /** Cut `text` to at most `max` characters, at a line boundary when possible. */
491
+ function clipAtLine(text, max) {
492
+ const head = text.slice(0, max);
493
+ const nl = head.lastIndexOf('\n');
494
+ return nl > max / 2 ? head.slice(0, nl + 1) : head;
495
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ /** A selection the user attached to a message (`{ type: 'data-chip', data: ContextChip }`). */
3
+ export const contextChipSchema = z.object({
4
+ id: z.string().min(1),
5
+ kind: z.literal('selection'),
6
+ path: z.string().min(1),
7
+ ref: z.enum(['base', 'head']),
8
+ side: z.enum(['LEFT', 'RIGHT']),
9
+ startLine: z.number().int().min(1),
10
+ endLine: z.number().int().min(1),
11
+ text: z.string(),
12
+ });
13
+ /**
14
+ * `.optional()` matters: `validateUIMessages` checks every message's metadata
15
+ * against this, including messages the client sends without any.
16
+ */
17
+ export const chatMessageMetadataSchema = z
18
+ .object({
19
+ model: z.string().optional(),
20
+ createdAt: z.string().optional(),
21
+ })
22
+ .optional();
package/dist/app.js ADDED
@@ -0,0 +1,142 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { Hono } from 'hono';
4
+ import { getCustomProvider } from './custom-providers.js';
5
+ import { packageVersion } from './paths.js';
6
+ import { registerChatRoutes } from './routes/chat.js';
7
+ import { registerFsRoutes } from './routes/fs.js';
8
+ import { registerGitRoutes } from './routes/git.js';
9
+ import { registerRequestGuard } from './routes/guard.js';
10
+ import { onApiError } from './routes/http.js';
11
+ import { registerProjectRoutes } from './routes/projects.js';
12
+ import { registerPullRoutes } from './routes/pulls.js';
13
+ import { registerReviewRoutes } from './routes/review.js';
14
+ import { registerSetupRoutes } from './routes/setup.js';
15
+ import { API_ROUTES } from './shared/api.js';
16
+ const MIME_TYPES = {
17
+ '.html': 'text/html; charset=utf-8',
18
+ '.js': 'text/javascript; charset=utf-8',
19
+ '.mjs': 'text/javascript; charset=utf-8',
20
+ '.css': 'text/css; charset=utf-8',
21
+ '.json': 'application/json; charset=utf-8',
22
+ '.map': 'application/json; charset=utf-8',
23
+ '.txt': 'text/plain; charset=utf-8',
24
+ '.svg': 'image/svg+xml',
25
+ '.png': 'image/png',
26
+ '.ico': 'image/x-icon',
27
+ '.woff2': 'font/woff2',
28
+ '.woff': 'font/woff',
29
+ '.wasm': 'application/wasm',
30
+ '.webmanifest': 'application/manifest+json',
31
+ };
32
+ const NO_CACHE = 'no-cache';
33
+ const IMMUTABLE = 'public, max-age=31536000, immutable';
34
+ /**
35
+ * Policy for the HTML document. Model output is rendered as React elements,
36
+ * never raw HTML; GitHub's `bodyHTML` is the one `dangerouslySetInnerHTML`, so
37
+ * this is the second line of defence (docs/decisions.md). Scripts and styles
38
+ * come from the Vite build; `'unsafe-inline'` is for styles only, because Shiki
39
+ * tokens carry `style` attributes (@pierre/diffs uses constructed stylesheets,
40
+ * which style-src does not govern, and its JS regex engine, so no wasm). Images
41
+ * are avatars and GitHub's camo-proxied `bodyHTML` images, nothing else.
42
+ */
43
+ export const CONTENT_SECURITY_POLICY = [
44
+ "default-src 'self'",
45
+ "script-src 'self'",
46
+ "style-src 'self' 'unsafe-inline'",
47
+ "img-src 'self' data: https://avatars.githubusercontent.com https://*.githubusercontent.com",
48
+ "font-src 'self' data:",
49
+ "connect-src 'self'",
50
+ "worker-src 'self' blob:",
51
+ "frame-ancestors 'none'",
52
+ "base-uri 'none'",
53
+ "form-action 'none'",
54
+ "object-src 'none'",
55
+ ].join('; ');
56
+ export function createApp(opts) {
57
+ const publicDir = path.resolve(opts.publicDir);
58
+ const assetsDir = path.join(publicDir, 'assets') + path.sep;
59
+ const version = packageVersion();
60
+ const app = new Hono();
61
+ // First, so it wraps every response there is: the guard's refusals, error
62
+ // bodies, API JSON and static files alike.
63
+ app.use('*', async (c, next) => {
64
+ await next();
65
+ c.header('x-content-type-options', 'nosniff');
66
+ c.header('referrer-policy', 'no-referrer');
67
+ });
68
+ // Host allow-list + cross-site request refusal: a web page on another origin
69
+ // must never be able to drive this loopback API (see docs/decisions.md).
70
+ registerRequestGuard(app, { allowedHosts: opts.allowedHosts });
71
+ app.onError(onApiError);
72
+ app.get(API_ROUTES.health, (c) => {
73
+ const body = { ok: true, version };
74
+ return c.json(body);
75
+ });
76
+ if (opts.services) {
77
+ const { ctx, forge, fetcher, secrets, ghAuthStatus, chatgpt } = opts.services;
78
+ registerSetupRoutes(app, ctx, { secrets, ghAuthStatus, chatgpt });
79
+ registerFsRoutes(app);
80
+ registerProjectRoutes(app, ctx);
81
+ registerGitRoutes(app, ctx, { forge, fetcher });
82
+ registerPullRoutes(app, ctx, { forge });
83
+ registerReviewRoutes(app, ctx, { forge });
84
+ // The AI layer gets a read-only PR reader, never the Forge (design: the AI drafts, the human sends).
85
+ registerChatRoutes(app, ctx, {
86
+ secrets,
87
+ fetcher,
88
+ subscription: chatgpt,
89
+ customProviders: (id) => getCustomProvider(ctx.db, id),
90
+ readPullRequest: (p, n) => forge.getPullRequest({ owner: p.owner, repo: p.repo }, n),
91
+ });
92
+ }
93
+ // Unknown API routes are JSON 404s, never the SPA shell.
94
+ app.all('/api/*', (c) => c.json({ error: 'not found' }, 404));
95
+ // Static UI with SPA fallback: a real file wins; otherwise index.html, except
96
+ // under /assets/ where a miss is a genuine 404 (hashed files never fall back).
97
+ app.get('*', async (c) => {
98
+ const file = resolveUnder(publicDir, c.req.path);
99
+ if (file) {
100
+ const res = await serveFile(file, file.startsWith(assetsDir) ? IMMUTABLE : NO_CACHE);
101
+ if (res)
102
+ return res;
103
+ }
104
+ if (c.req.path.startsWith('/assets/'))
105
+ return c.text('not found', 404);
106
+ const index = await serveFile(path.join(publicDir, 'index.html'), NO_CACHE);
107
+ return (index ?? c.text('coja UI is not built. Run `pnpm build` (or use the Vite dev server).', 503));
108
+ });
109
+ return app;
110
+ }
111
+ /** Map a URL pathname onto a file inside `root`; null when malformed or escaping the root. */
112
+ function resolveUnder(root, pathname) {
113
+ // Hono already percent-decodes `c.req.path`; decoding again would turn an
114
+ // encoded `%2F` into a separator. Containment below is the real guarantee.
115
+ if (pathname.includes('\0'))
116
+ return null;
117
+ const resolved = path.resolve(root, `.${pathname}`);
118
+ const prefix = root.endsWith(path.sep) ? root : root + path.sep;
119
+ if (resolved !== root && !resolved.startsWith(prefix))
120
+ return null;
121
+ return resolved;
122
+ }
123
+ /** Read `file` into a Response; null when it does not exist or is not a regular file. */
124
+ async function serveFile(file, cacheControl) {
125
+ let body;
126
+ try {
127
+ body = await readFile(file);
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ const type = MIME_TYPES[path.extname(file).toLowerCase()] ?? 'application/octet-stream';
133
+ const headers = {
134
+ 'content-type': type,
135
+ 'content-length': String(body.byteLength),
136
+ 'cache-control': cacheControl,
137
+ };
138
+ // The document (served directly or as the SPA fallback) carries the policy; assets need none.
139
+ if (type.startsWith('text/html'))
140
+ headers['content-security-policy'] = CONTENT_SECURITY_POLICY;
141
+ return new Response(body, { headers });
142
+ }