rovecode 0.4.0-beta.3 → 0.4.0

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 (432) hide show
  1. package/README.md +57 -72
  2. package/THIRD_PARTY_NOTICES.md +0 -44
  3. package/bin/rovecode.ts +21 -0
  4. package/package.json +16 -38
  5. package/src/account/keys.ts +97 -0
  6. package/src/account/login.ts +158 -0
  7. package/src/account/provision.ts +47 -0
  8. package/src/account/store.ts +63 -0
  9. package/src/acp/server.ts +373 -0
  10. package/src/cli/account-cmd.ts +116 -0
  11. package/src/cli/connect.ts +244 -0
  12. package/src/cli/context-cmd.ts +199 -0
  13. package/src/cli/dispatch.ts +109 -0
  14. package/src/cli/doctor.ts +324 -0
  15. package/src/cli/export.ts +278 -0
  16. package/src/cli/help.ts +240 -0
  17. package/src/cli/is-tui-invocation.ts +8 -0
  18. package/src/cli/main.ts +599 -0
  19. package/src/cli/market-cmd.ts +658 -0
  20. package/src/cli/mcp-market-cmd.ts +299 -0
  21. package/src/cli/output.ts +382 -0
  22. package/src/cli/repl.ts +172 -0
  23. package/src/cli/resume.ts +32 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +792 -0
  26. package/src/cli/setup.ts +187 -0
  27. package/src/cli/update-cmd.ts +78 -0
  28. package/src/cli/workflow-cmd.ts +100 -0
  29. package/src/coding/checkpoints.ts +270 -0
  30. package/src/coding/diff.ts +136 -0
  31. package/src/coding/files.ts +339 -0
  32. package/src/coding/hashline.ts +319 -0
  33. package/src/coding/lsp.ts +406 -0
  34. package/src/coding/repomap-cache.ts +99 -0
  35. package/src/coding/repomap-files.ts +110 -0
  36. package/src/coding/repomap.ts +392 -0
  37. package/src/core/compaction.ts +399 -0
  38. package/src/core/config.ts +289 -0
  39. package/src/core/context-report.ts +228 -0
  40. package/src/core/context.ts +60 -0
  41. package/src/core/count-remote.ts +107 -0
  42. package/src/core/execpolicy-rules.ts +196 -0
  43. package/src/core/execpolicy.ts +385 -0
  44. package/src/core/executor.ts +397 -0
  45. package/src/core/guardrails.ts +400 -0
  46. package/src/core/hooks.ts +398 -0
  47. package/src/core/images.ts +230 -0
  48. package/src/core/intro.ts +236 -0
  49. package/src/core/loop.ts +621 -0
  50. package/src/core/modes.ts +372 -0
  51. package/src/core/orchestrator.ts +207 -0
  52. package/src/core/reflection.ts +165 -0
  53. package/src/core/sandbox-config.ts +167 -0
  54. package/src/core/session-images.ts +73 -0
  55. package/src/core/session.ts +398 -0
  56. package/src/core/settings.ts +98 -0
  57. package/src/core/stuck-detector.ts +273 -0
  58. package/src/core/tasks.ts +374 -0
  59. package/src/core/token-scale.ts +108 -0
  60. package/src/core/tool-output-budget.ts +166 -0
  61. package/src/core/tools.ts +288 -0
  62. package/src/core/types.ts +330 -0
  63. package/src/core/update-check.ts +171 -0
  64. package/src/core/update.ts +158 -0
  65. package/src/core/usage.ts +204 -0
  66. package/src/core/validate.ts +121 -0
  67. package/src/core/verify-gate.ts +159 -0
  68. package/src/core/verify.ts +237 -0
  69. package/src/core/voice.ts +158 -0
  70. package/src/core/win-job.ts +183 -0
  71. package/src/design/audit.ts +797 -0
  72. package/src/design/direction.ts +190 -0
  73. package/src/design/rules.ts +157 -0
  74. package/src/eval/bench.ts +150 -0
  75. package/src/eval/gauntlet-runner.ts +218 -0
  76. package/src/eval/gauntlet.ts +226 -0
  77. package/src/eval/grader.ts +186 -0
  78. package/src/eval/record.ts +202 -0
  79. package/src/eval/redact.ts +141 -0
  80. package/src/eval/replay.ts +147 -0
  81. package/src/eval/trajectory.ts +373 -0
  82. package/src/index.ts +17 -0
  83. package/src/market/catalogs/mcp-docs.json +111 -0
  84. package/src/market/catalogs/plugins.json +111 -0
  85. package/src/market/catalogs/skills.json +478 -0
  86. package/src/market/clone.ts +72 -0
  87. package/src/market/context-cost.ts +121 -0
  88. package/src/market/digest.ts +106 -0
  89. package/src/market/index.ts +22 -0
  90. package/src/market/install.ts +578 -0
  91. package/src/market/manifest.ts +187 -0
  92. package/src/market/prereq.ts +145 -0
  93. package/src/market/registry.ts +363 -0
  94. package/src/market/resolve.ts +111 -0
  95. package/src/market/types.ts +236 -0
  96. package/src/market/validate.ts +227 -0
  97. package/src/mcp/client.ts +431 -0
  98. package/src/mcp/config.ts +239 -0
  99. package/src/mcp/local-package.ts +211 -0
  100. package/src/mcp/market-catalog.ts +84 -0
  101. package/src/mcp/market-install.ts +289 -0
  102. package/src/mcp/market.ts +0 -0
  103. package/src/mcp/tools.ts +131 -0
  104. package/src/mcp/trust.ts +49 -0
  105. package/src/memory/blocks.ts +175 -0
  106. package/src/memory/recall.ts +355 -0
  107. package/src/memory/store.ts +105 -0
  108. package/src/memory/tools.ts +99 -0
  109. package/src/plugins/cli.ts +123 -0
  110. package/src/plugins/discover.ts +108 -0
  111. package/src/plugins/index.ts +50 -0
  112. package/src/plugins/init.ts +140 -0
  113. package/src/plugins/install.ts +184 -0
  114. package/src/plugins/load.ts +149 -0
  115. package/src/plugins/manifest.ts +106 -0
  116. package/src/plugins/state.ts +83 -0
  117. package/src/providers/auth.ts +293 -0
  118. package/src/providers/cache.ts +223 -0
  119. package/src/providers/catalog-local.ts +160 -0
  120. package/src/providers/catalog.ts +408 -0
  121. package/src/providers/middleware-context.ts +86 -0
  122. package/src/providers/middleware.ts +373 -0
  123. package/src/providers/profile-glm53.ts +111 -0
  124. package/src/providers/profile-sonnet5-persona.ts +65 -0
  125. package/src/providers/profile-sonnet5-voice.ts +23 -0
  126. package/src/providers/profiles.ts +156 -0
  127. package/src/providers/provider-config.ts +311 -0
  128. package/src/providers/registry.ts +302 -0
  129. package/src/providers/response-validation.ts +80 -0
  130. package/src/providers/retry.ts +234 -0
  131. package/src/providers/router.ts +294 -0
  132. package/src/providers/sse.ts +26 -0
  133. package/src/providers/stream-errors.ts +117 -0
  134. package/src/providers/stream.ts +569 -0
  135. package/src/providers/thinking.ts +189 -0
  136. package/src/providers/wire-messages.ts +129 -0
  137. package/src/sdk/client.ts +225 -0
  138. package/src/sdk/index.ts +3 -0
  139. package/src/server/dashboard.ts +144 -0
  140. package/src/server/http.ts +343 -0
  141. package/src/server/openapi.ts +246 -0
  142. package/src/sextant/card-hits.ts +102 -0
  143. package/src/sextant/card-keys.ts +55 -0
  144. package/src/sextant/context-source.ts +157 -0
  145. package/src/sextant/draw-agents.ts +273 -0
  146. package/src/sextant/draw-code.ts +388 -0
  147. package/src/sextant/draw-context.ts +222 -0
  148. package/src/sextant/draw-frame.ts +164 -0
  149. package/src/sextant/draw-market.ts +573 -0
  150. package/src/sextant/draw-messages.ts +386 -0
  151. package/src/sextant/draw-pet.ts +230 -0
  152. package/src/sextant/draw-plan.ts +159 -0
  153. package/src/sextant/draw-tabs.ts +85 -0
  154. package/src/sextant/draw-util.ts +65 -0
  155. package/src/sextant/engine.ts +230 -0
  156. package/src/sextant/frame-hits.ts +25 -0
  157. package/src/sextant/frame.ts +101 -0
  158. package/src/sextant/git-status.ts +197 -0
  159. package/src/sextant/grid.ts +59 -0
  160. package/src/sextant/input.ts +119 -0
  161. package/src/sextant/keys.ts +488 -0
  162. package/src/sextant/layout.ts +86 -0
  163. package/src/sextant/local-commands.ts +156 -0
  164. package/src/sextant/market-source.ts +287 -0
  165. package/src/sextant/mentions.ts +141 -0
  166. package/src/sextant/message-hits.ts +26 -0
  167. package/src/sextant/model.ts +387 -0
  168. package/src/sextant/overlays.ts +451 -0
  169. package/src/sextant/panel-hits.ts +38 -0
  170. package/src/sextant/pet.ts +399 -0
  171. package/src/sextant/screen.ts +324 -0
  172. package/src/sextant/scroll-hits.ts +66 -0
  173. package/src/sextant/scrollbar.ts +82 -0
  174. package/src/sextant/selection.ts +123 -0
  175. package/src/sextant/sextant-bridge.ts +174 -0
  176. package/src/sextant/sextant-cards.ts +142 -0
  177. package/src/sextant/sextant-diff-base.ts +63 -0
  178. package/src/sextant/sextant-files.ts +154 -0
  179. package/src/sextant/sextant-frame-loop.ts +314 -0
  180. package/src/sextant/sextant-renderer.ts +478 -0
  181. package/src/sextant/sextant-repo.ts +131 -0
  182. package/src/sextant/theme.ts +66 -0
  183. package/src/sextant/tool-rows.ts +189 -0
  184. package/src/sextant/types.ts +473 -0
  185. package/src/skills/index.ts +306 -0
  186. package/src/skills/tools.ts +69 -0
  187. package/src/skills/versioned.ts +227 -0
  188. package/src/telemetry/otel.ts +353 -0
  189. package/src/telemetry/otlp.ts +68 -0
  190. package/src/tools/ask-user.ts +156 -0
  191. package/src/tools/design.ts +151 -0
  192. package/src/tools/evalcell.ts +338 -0
  193. package/src/tools/html-text.ts +139 -0
  194. package/src/tools/provider.ts +149 -0
  195. package/src/tools/task.ts +216 -0
  196. package/src/tools/todo.ts +320 -0
  197. package/src/tools/webfetch.ts +331 -0
  198. package/src/tui/app.ts +608 -0
  199. package/src/tui/attach.ts +127 -0
  200. package/src/tui/checkpoints-cmd.ts +70 -0
  201. package/src/tui/clipboard-image.ts +81 -0
  202. package/src/tui/commands.ts +277 -0
  203. package/src/tui/cost.ts +108 -0
  204. package/src/tui/info-cmd.ts +144 -0
  205. package/src/tui/mcp-cmd.ts +128 -0
  206. package/src/tui/modes-cmd.ts +45 -0
  207. package/src/tui/overlays.ts +97 -0
  208. package/src/tui/pi-renderer.ts +424 -0
  209. package/src/tui/providers-cmd.ts +366 -0
  210. package/src/tui/renderer.ts +101 -0
  211. package/src/tui/replay-marker.ts +29 -0
  212. package/src/tui/session-cmd.ts +146 -0
  213. package/src/tui/sextant-attach.ts +68 -0
  214. package/src/tui/sextant-io.ts +184 -0
  215. package/src/tui/sextant-smoke.ts +110 -0
  216. package/src/tui/smoke.ts +72 -0
  217. package/src/tui/theme.ts +59 -0
  218. package/src/tui/todo-label.ts +7 -0
  219. package/src/workflow/engine.ts +266 -0
  220. package/tsconfig.json +30 -0
  221. package/vendor/pi-tui/LICENSE +21 -0
  222. package/vendor/pi-tui/PATCHES.md +12 -0
  223. package/vendor/pi-tui/PROVENANCE.md +12 -0
  224. package/vendor/pi-tui/README.upstream.md +854 -0
  225. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  226. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  227. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  228. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  229. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  230. package/vendor/pi-tui/src/components/box.ts +138 -0
  231. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  232. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  233. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  234. package/vendor/pi-tui/src/components/image.ts +128 -0
  235. package/vendor/pi-tui/src/components/input.ts +448 -0
  236. package/vendor/pi-tui/src/components/loader.ts +93 -0
  237. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  238. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  239. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  240. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  241. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  242. package/vendor/pi-tui/src/components/stack.ts +155 -0
  243. package/vendor/pi-tui/src/components/text.ts +108 -0
  244. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  245. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  246. package/vendor/pi-tui/src/editor-component.ts +75 -0
  247. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  248. package/vendor/pi-tui/src/index.ts +149 -0
  249. package/vendor/pi-tui/src/keybindings.ts +321 -0
  250. package/vendor/pi-tui/src/keys.ts +1402 -0
  251. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  252. package/vendor/pi-tui/src/latex.ts +1381 -0
  253. package/vendor/pi-tui/src/layout-node.ts +52 -0
  254. package/vendor/pi-tui/src/layout.ts +411 -0
  255. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  256. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  257. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  258. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  259. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  260. package/vendor/pi-tui/src/terminal.ts +554 -0
  261. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  262. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  263. package/vendor/pi-tui/src/tui.ts +1264 -0
  264. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  265. package/vendor/pi-tui/src/utils.ts +1327 -0
  266. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  267. package/vendor/pi-tui/test/test-themes.ts +39 -0
  268. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
  269. package/CHANGELOG.md +0 -527
  270. package/bin/rovecode.js +0 -24
  271. package/dist/cli/app-j6gn14w3.js +0 -2
  272. package/dist/cli/ask-user-cwstt8fz.js +0 -2
  273. package/dist/cli/auth-login-9bbp9915.js +0 -2
  274. package/dist/cli/auth-m8p9grty.js +0 -2
  275. package/dist/cli/bench-16zqdms5.js +0 -9
  276. package/dist/cli/catalog-1xchffa4.js +0 -2
  277. package/dist/cli/cli-1n1zb64f.js +0 -2
  278. package/dist/cli/client-2t9gjkck.js +0 -2
  279. package/dist/cli/commands-exafvm2b.js +0 -2
  280. package/dist/cli/connect-6zde0kn3.js +0 -2
  281. package/dist/cli/context-cmd-5t43wgqt.js +0 -2
  282. package/dist/cli/context-report-kt01pw8y.js +0 -2
  283. package/dist/cli/count-remote-ap7x3vh6.js +0 -2
  284. package/dist/cli/design-ne5zszyh.js +0 -2
  285. package/dist/cli/dispatch-2r5myxye.js +0 -2
  286. package/dist/cli/doctor-ws4fh4tn.js +0 -3
  287. package/dist/cli/executor-bdrjn634.js +0 -2
  288. package/dist/cli/export-1mxb9g5p.js +0 -2
  289. package/dist/cli/files-g104xghh.js +0 -2
  290. package/dist/cli/gauntlet-07xrjpj7.js +0 -2
  291. package/dist/cli/gauntlet-runner-xvy64436.js +0 -10
  292. package/dist/cli/gauntlet-wave3-jm91yt5w.js +0 -5
  293. package/dist/cli/gauntlet-wave4-r13py7p1.js +0 -14
  294. package/dist/cli/hashline-znvrat11.js +0 -2
  295. package/dist/cli/http-xafw6fsh.js +0 -143
  296. package/dist/cli/index-1sgjm25y.js +0 -2
  297. package/dist/cli/init-g2m0tn4m.js +0 -51
  298. package/dist/cli/install-avaqjjqq.js +0 -2
  299. package/dist/cli/loop-mmpfft01.js +0 -2
  300. package/dist/cli/main-0904f6ps.js +0 -5
  301. package/dist/cli/main-0ab9fc26.js +0 -9
  302. package/dist/cli/main-0jys2ccn.js +0 -3
  303. package/dist/cli/main-0mtcdbs7.js +0 -3
  304. package/dist/cli/main-0z1w2zsg.js +0 -3
  305. package/dist/cli/main-1dchs7xv.js +0 -18
  306. package/dist/cli/main-1ereejm1.js +0 -3
  307. package/dist/cli/main-1k1kw6b5.js +0 -3
  308. package/dist/cli/main-27y4sm2k.js +0 -38
  309. package/dist/cli/main-2wwjex5j.js +0 -58
  310. package/dist/cli/main-2yeveeve.js +0 -6
  311. package/dist/cli/main-2yfck9b5.js +0 -3
  312. package/dist/cli/main-2zmzgkwh.js +0 -3
  313. package/dist/cli/main-351pz3z7.js +0 -7
  314. package/dist/cli/main-3gjqfh7a.js +0 -6
  315. package/dist/cli/main-3nf3kgve.js +0 -3
  316. package/dist/cli/main-3pjrb2hd.js +0 -3
  317. package/dist/cli/main-3rxcvgna.js +0 -19
  318. package/dist/cli/main-4b3jgy66.js +0 -19
  319. package/dist/cli/main-4wndhjdc.js +0 -7
  320. package/dist/cli/main-4xcmvxnk.js +0 -3
  321. package/dist/cli/main-5tbz0wbz.js +0 -4
  322. package/dist/cli/main-5ywnwthm.js +0 -3
  323. package/dist/cli/main-6b62vkz0.js +0 -14
  324. package/dist/cli/main-6dnk69vp.js +0 -3
  325. package/dist/cli/main-6genrmhs.js +0 -136
  326. package/dist/cli/main-73g7eff4.js +0 -15
  327. package/dist/cli/main-7c5thhjd.js +0 -5
  328. package/dist/cli/main-7rn6bqje.js +0 -3
  329. package/dist/cli/main-80haw7qk.js +0 -4
  330. package/dist/cli/main-875s60s2.js +0 -4
  331. package/dist/cli/main-8kjxbpw4.js +0 -8
  332. package/dist/cli/main-90ds1z4e.js +0 -10
  333. package/dist/cli/main-9etavkew.js +0 -3
  334. package/dist/cli/main-a9njrkk1.js +0 -3
  335. package/dist/cli/main-aecrjq2d.js +0 -12
  336. package/dist/cli/main-ck9asesq.js +0 -9
  337. package/dist/cli/main-cta9racd.js +0 -4
  338. package/dist/cli/main-ddv7j2ag.js +0 -3
  339. package/dist/cli/main-dfreez27.js +0 -10
  340. package/dist/cli/main-f7rw7des.js +0 -3
  341. package/dist/cli/main-ggcn7rd7.js +0 -5
  342. package/dist/cli/main-gzkmycnv.js +0 -3
  343. package/dist/cli/main-hq51jg8v.js +0 -18
  344. package/dist/cli/main-jft389w9.js +0 -8
  345. package/dist/cli/main-k1eqkg83.js +0 -3
  346. package/dist/cli/main-k2y8a2aw.js +0 -9
  347. package/dist/cli/main-kcpbykxz.js +0 -4
  348. package/dist/cli/main-kd488vje.js +0 -22
  349. package/dist/cli/main-kh32yvgk.js +0 -5
  350. package/dist/cli/main-kqxnqjnv.js +0 -25
  351. package/dist/cli/main-kyn0xnsg.js +0 -3
  352. package/dist/cli/main-m1kk6fp5.js +0 -21
  353. package/dist/cli/main-mv40pcr2.js +0 -4
  354. package/dist/cli/main-n0t3973w.js +0 -3
  355. package/dist/cli/main-nqveez48.js +0 -4
  356. package/dist/cli/main-pknhvrmj.js +0 -3
  357. package/dist/cli/main-pn1w7a7j.js +0 -3
  358. package/dist/cli/main-prxxs70n.js +0 -4
  359. package/dist/cli/main-q3vsesf9.js +0 -3
  360. package/dist/cli/main-qsevpgsv.js +0 -3
  361. package/dist/cli/main-rdgdw24b.js +0 -25
  362. package/dist/cli/main-rfth4tbm.js +0 -16
  363. package/dist/cli/main-rg0wn0xf.js +0 -5
  364. package/dist/cli/main-sdmxhtv8.js +0 -4
  365. package/dist/cli/main-skbp13js.js +0 -18
  366. package/dist/cli/main-t4xnd213.js +0 -7
  367. package/dist/cli/main-vqak588n.js +0 -4
  368. package/dist/cli/main-w2n1303f.js +0 -9
  369. package/dist/cli/main-wbrdspr2.js +0 -5
  370. package/dist/cli/main-wsrg79c1.js +0 -7
  371. package/dist/cli/main-x4r0fne4.js +0 -5
  372. package/dist/cli/main-xea2f3tn.js +0 -6
  373. package/dist/cli/main-xg704a3c.js +0 -3
  374. package/dist/cli/main-xvnrabfp.js +0 -16
  375. package/dist/cli/main-xy53xf0r.js +0 -4
  376. package/dist/cli/main-y1fqy60y.js +0 -3
  377. package/dist/cli/main-yn8cd281.js +0 -34
  378. package/dist/cli/main-yr0ksc0h.js +0 -4
  379. package/dist/cli/main-z2ex2vyf.js +0 -4
  380. package/dist/cli/main-z3aayzvq.js +0 -3
  381. package/dist/cli/main-zaqh35jg.js +0 -3
  382. package/dist/cli/main-zc2e8e46.js +0 -4
  383. package/dist/cli/main-zzrfw6cf.js +0 -13
  384. package/dist/cli/main.js +0 -280
  385. package/dist/cli/market-cmd-e14kmx9n.js +0 -5
  386. package/dist/cli/mcp-login-wq7ktdek.js +0 -2
  387. package/dist/cli/mcp-market-cmd-9mg3jecy.js +0 -2
  388. package/dist/cli/notify-b7qc0cjb.js +0 -2
  389. package/dist/cli/oauth-z8whcgfx.js +0 -2
  390. package/dist/cli/output-b3ewj3ps.js +0 -16
  391. package/dist/cli/profiles-6mr5he5e.js +0 -2
  392. package/dist/cli/provider-config-g7j42q8x.js +0 -2
  393. package/dist/cli/provider-jr1y8vvm.js +0 -2
  394. package/dist/cli/registry-s8yk86g0.js +0 -2
  395. package/dist/cli/registry-t6p8d4mn.js +0 -2
  396. package/dist/cli/repl-bajwe1mh.js +0 -11
  397. package/dist/cli/resume-rwn9nz7y.js +0 -2
  398. package/dist/cli/run-flags-nah7ndpt.js +0 -2
  399. package/dist/cli/runtime-n7gafzhb.js +0 -2
  400. package/dist/cli/sandbox-config-emdy18x4.js +0 -2
  401. package/dist/cli/server-b0nvs2bn.js +0 -5
  402. package/dist/cli/session-arg-y75wd4kj.js +0 -2
  403. package/dist/cli/session-j62evmjq.js +0 -2
  404. package/dist/cli/sessions-cmd-tsnwz0ns.js +0 -7
  405. package/dist/cli/settings-df10wfez.js +0 -2
  406. package/dist/cli/setup-jzvv72fg.js +0 -2
  407. package/dist/cli/sextant-smoke-37m81ke6.js +0 -5
  408. package/dist/cli/skills-cmd-gjxnxnhx.js +0 -2
  409. package/dist/cli/smoke-p7748apt.js +0 -8
  410. package/dist/cli/start-chat-s4st3mm0.js +0 -12
  411. package/dist/cli/stream-gmeyewds.js +0 -2
  412. package/dist/cli/task-gh0kkp3n.js +0 -2
  413. package/dist/cli/tasks-z1kfpe8e.js +0 -2
  414. package/dist/cli/thinking-0eqkrz6t.js +0 -2
  415. package/dist/cli/todo-5brcrt9m.js +0 -2
  416. package/dist/cli/tools-7pzm0vj9.js +0 -2
  417. package/dist/cli/tools-s635p6s8.js +0 -2
  418. package/dist/cli/trust-cmd-cjav8zgm.js +0 -2
  419. package/dist/cli/update-check-pt31bm2f.js +0 -2
  420. package/dist/cli/update-cmd-tk131s9t.js +0 -2
  421. package/dist/cli/voice-56nabd8d.js +0 -2
  422. package/dist/cli/webfetch-xd8q596m.js +0 -2
  423. package/dist/cli/websearch-5hkf98k1.js +0 -2
  424. package/dist/cli/workflow-cmd-cy3cvzjp.js +0 -4
  425. package/dist/cli/workspace-q10g5z3e.js +0 -2
  426. package/dist/lib/index.js +0 -62
  427. package/dist/lib/models-index.json +0 -1
  428. package/dist/lib/plugins.js +0 -55
  429. package/dist/lib/providers.js +0 -17
  430. package/dist/lib/public-api.js +0 -20
  431. package/dist/lib/sdk.js +0 -360
  432. /package/{dist/cli → src/providers}/models-index.json +0 -0
@@ -0,0 +1,236 @@
1
+ /** One market, three kinds. `rovecode market …`, the sextant's /market and the site's market page all read
2
+ * the SAME item type: an MCP server, a skill and a plugin differ in what installing them means, not in how
3
+ * they are found, described or shown.
4
+ *
5
+ * Design rules this file exists to hold:
6
+ * - MCP is WRAPPED, never rewritten: `install.kind === "mcp"` carries the existing `MarketEntry`
7
+ * (src/mcp/market.ts) untouched, so the curated shelf, the registry re-typing and the install planner
8
+ * keep working exactly as they do today. A new kind is a new arm of `InstallSpec`, not a new pipeline.
9
+ * - Everything a UI needs to draw a row is on the item itself (title, publisher, one line, tags, version,
10
+ * source). Anything that needs the local filesystem — installed? update available? — is a separate
11
+ * `InstalledState`, because it changes without the catalog changing.
12
+ * - Catalog data is UNTRUSTED. These types describe what a validator PRODUCES (registry.ts), never what a
13
+ * response happens to contain. The caps live here so every source is bounded the same way.
14
+ * - Nothing here reads a file, spawns a process or touches the network: types + pure helpers only, so
15
+ * importing this module costs nothing at boot (the lazy-import rule). */
16
+
17
+ import type { MarketEntry } from "../mcp/market.ts";
18
+
19
+ export type MarketKind = "mcp" | "skill" | "plugin";
20
+ /** where the item was found. "curated" = built into rovecode, hand-checked; "registry" = a live remote
21
+ * index (the MCP registry today); "catalog" = a rovecode-published catalog file for skills and plugins */
22
+ export type MarketSource = "curated" | "registry" | "catalog";
23
+ /** user = ~/.rovecode (every project sees it) · project = this repo (trust-gated, committed with the repo) */
24
+ export type MarketScope = "user" | "project";
25
+
26
+ /** Hard ceilings on anything a source contributes. A field past its cap is truncated (strings) or dropped
27
+ * (list items) — never an error, because one bad row must not blank a whole catalog. */
28
+ export const LIMITS = {
29
+ /** response body accepted from a remote catalog */
30
+ body: 2 * 1024 * 1024,
31
+ /** items accepted from one source */
32
+ items: 200,
33
+ /** any short string (id, title, publisher, version, url) */
34
+ str: 300,
35
+ /** the one-line description a row shows */
36
+ desc: 500,
37
+ /** tags per item, and files per skill */
38
+ list: 32,
39
+ /** environment variables one item may declare */
40
+ env: 24,
41
+ /** an item's documentation body: markdown, not a label. Nothing else here is remotely this big, which is
42
+ * exactly why it needs its own cap — run a 24 KB body through `str()` and it comes back silently cut to
43
+ * 300 characters, still a valid string, and nobody notices. */
44
+ docs: 24 * 1024,
45
+ } as const;
46
+
47
+ /** An item's own documentation, fetched at BUILD time and carried in the catalog.
48
+ *
49
+ * Never fetched at runtime. The market has to work offline and the site's page is static, and pulling
50
+ * third-party text into a UI at display time is not something worth doing for a docs pane. The body is
51
+ * carried as TEXT and nothing more: it is never executed, never evaluated, never parsed as HTML here —
52
+ * each surface renders it safely on its own side.
53
+ *
54
+ * Absent when upstream had no readable doc. A row without docs is a normal row, not a broken one. */
55
+ export interface ItemDocs {
56
+ /** the exact URL it was read from, so a reader can go to the original */
57
+ source: string;
58
+ format: "markdown";
59
+ /** the body's length in bytes BEFORE truncation, so a UI can say how much is missing */
60
+ bytes: number;
61
+ truncated: boolean;
62
+ /** Markdown text, capped at LIMITS.docs. OPTIONAL on purpose: the search path does not carry bodies
63
+ * (they are ~200 KB the search never reads), so a row from `searchMarket` has the metadata and
64
+ * `body === undefined`. `undefined` means "not carried on this call" and is not the same as an empty
65
+ * document — a UI must send the reader to `market docs <id>` rather than say there is nothing. */
66
+ body?: string;
67
+ }
68
+
69
+ /** An environment variable an item needs. Same shape the MCP side already uses (mcp/market.ts EnvSpec):
70
+ * `secret: true` is asked masked on a shell and NEVER written into a project file — it is written as
71
+ * `${NAME}` and read from the environment at launch. */
72
+ export interface MarketEnv {
73
+ name: string;
74
+ description?: string;
75
+ required: boolean;
76
+ secret: boolean;
77
+ /** a value the catalog already knows (a default port, a public URL): not a question */
78
+ default?: string;
79
+ }
80
+
81
+ /** What installing this item actually does. One arm per kind — the arm carries whatever the kind's existing
82
+ * installer needs, so `install.ts` can delegate without re-deriving anything. */
83
+ export type InstallSpec =
84
+ /** the MCP entry as mcp/market.ts produced it; mcp/market-install.ts plans and writes it */
85
+ | { kind: "mcp"; entry: MarketEntry }
86
+ /** a plugin folder or git URL; src/plugins/install.ts addPlugin() clones and copies it.
87
+ * `subfolder` is where the plugin.json lives inside that repo — real plugins live in a monorepo
88
+ * (plugins/safety-net), so a repo root is the exception, not the rule. Same escape rules as a skill's. */
89
+ | { kind: "plugin"; source: string; /** true when `source` is a git URL rather than a local folder */ git: boolean; subfolder?: string }
90
+ /** a skill: a git repo (optionally a subfolder) or a set of files written verbatim.
91
+ * `files` is the offline form — a curated skill ships its SKILL.md in the catalog itself. */
92
+ | { kind: "skill"; source?: { git: string; subfolder?: string }; files?: { path: string; text: string }[] };
93
+
94
+ /** One row in the market, whatever its kind. */
95
+ export interface MarketItem {
96
+ /** what `market install <id>` takes. Unique per kind, not across kinds: `mcp:git` and `skill:git` may
97
+ * both exist, which is why resolve.ts accepts `kind:id` and lists candidates when a bare id is ambiguous. */
98
+ id: string;
99
+ kind: MarketKind;
100
+ /** display name; falls back to `id` when a source has none */
101
+ title: string;
102
+ /** who publishes it — a namespace, an org, or "unknown" */
103
+ publisher: string;
104
+ /** one line, already capped at LIMITS.desc */
105
+ description: string;
106
+ source: MarketSource;
107
+ version?: string;
108
+ repository?: string;
109
+ homepage?: string;
110
+ /** free-form, lowercase, capped; a UI may filter on these but must not depend on any particular tag */
111
+ tags: string[];
112
+ /** the source's own status when it is anything but normal ("deprecated", "experimental") */
113
+ status?: string;
114
+ /** the licence as the publisher states it ("Apache-2.0", "source-available, not open source", "none stated").
115
+ * A field, not a note: a market that installs other people's work has to show this before the yes, and a
116
+ * UI will want to filter on it. Free text — an SPDX id is common but not required. */
117
+ license?: string;
118
+ /** the variables this item needs before it can run. Empty for most skills and plugins. */
119
+ env: MarketEnv[];
120
+ install: InstallSpec;
121
+ /** the item's own documentation, carried in the catalog — see ItemDocs */
122
+ docs?: ItemDocs;
123
+ /** extra lines a catalog wants in the approval preview, in its own words ("copies one SKILL.md, runs
124
+ * nothing"). install.ts writes the standard plan from `install`; these are appended verbatim, capped
125
+ * like every other list. A catalog that has nothing special to say omits them. */
126
+ planNote?: string[];
127
+ /** what a plugin brings, as data rather than as a sentence: "commands", "skills", "agents", "hooks",
128
+ * "mcpServers", "tools". The catalog has carried this since the generator was written, but only the
129
+ * prose form ("contributes: commands, skills") ever reached a reader — so a caller wanting to filter
130
+ * on it had to parse English. The prose stays for the approval preview, which is written for a
131
+ * person; this is the same fact for anything that has to choose between two candidates. It describes
132
+ * what the catalog CLAIMS, exactly like every other field here, and is no more verified than they are. */
133
+ contributes?: string[];
134
+ }
135
+
136
+ /** Local truth about one item, computed from disk (never from a catalog). Kept apart from `MarketItem`
137
+ * because it changes when the user installs something, not when the catalog updates. Its PRESENCE on a
138
+ * row means installed — `row.installed === undefined` is the not-installed case, so a UI draws the badge
139
+ * from one check and never from a boolean that could disagree with the path. */
140
+ export interface InstalledState {
141
+ /** where it landed: the mcp.json, the plugin folder, the skill folder */
142
+ path: string;
143
+ scope: MarketScope;
144
+ /** the version on disk when it is knowable (a plugin manifest, an mcp entry's recorded version) */
145
+ version?: string;
146
+ /** true when the catalog's version is a different string from the installed one. Not a semver compare:
147
+ * a UI says "update available", the human reads both numbers. */
148
+ updateAvailable?: boolean;
149
+ /** project-scope items only: the trust gate's answer. `undefined` for user scope, which is never gated. */
150
+ trusted?: boolean;
151
+ }
152
+
153
+ /** A market row as a UI receives it: the catalog item plus what this machine knows about it.
154
+ * `installed` present = on disk here; absent = not installed. */
155
+ export interface MarketRow extends MarketItem {
156
+ installed?: InstalledState;
157
+ }
158
+
159
+ // ---------------------------------------------------------------- results, never exceptions
160
+
161
+ /** Why a source produced nothing. A UI draws these differently, so they are distinguishable values and not
162
+ * one error string: `offline` and `stale` still carry rows, `failed` carries the reason. */
163
+ export type SourceStatus =
164
+ | { ok: true; from: "live" }
165
+ /** the network was not tried (--offline, no network): rows come from the cache or the curated shelf */
166
+ | { ok: true; from: "cache"; ageMs: number }
167
+ | { ok: true; from: "curated" }
168
+ /** the source was not consulted at all (an empty query never asks the registry): no rows, no failure */
169
+ | { ok: true; from: "skipped"; why: string }
170
+ /** the fetch failed and there was nothing cached: this source contributed no rows */
171
+ | { ok: false; reason: string };
172
+
173
+ /** What every listing call returns. Never throws for a data problem — a caller that must distinguish
174
+ * "nothing matched" from "the registry is down" reads `sources`. */
175
+ export interface MarketResult {
176
+ items: MarketItem[];
177
+ /** one entry per source consulted, keyed by a stable name ("mcp:curated", "mcp:registry", "skills", "plugins") */
178
+ sources: Record<string, SourceStatus>;
179
+ /** human-readable notes worth showing once (a capped field, a dropped row, a stale cache) */
180
+ notes: string[];
181
+ }
182
+
183
+ /** The three-step visibility the MCP market established, generalised: a plan is built and SHOWN, the human
184
+ * says yes, and only then is anything written. `preview` is the whole plan in the human's words. */
185
+ export interface InstallPlanView {
186
+ item: MarketItem;
187
+ /** where the write lands (an mcp.json, a plugins folder, a skills folder) */
188
+ target: string;
189
+ scope: MarketScope;
190
+ /** the lines a CLI prints or a card shows — everything that must be seen before a yes */
191
+ preview: string[];
192
+ /** variables that will be asked (shell: masked for secrets) or written as `${NAME}` */
193
+ asks: MarketEnv[];
194
+ /** required values nobody can fill for the human (a directory to expose, a database URL) */
195
+ pending: string[];
196
+ /** true when this install replaces something already there */
197
+ replaces?: string;
198
+ }
199
+
200
+ export type InstallOutcome =
201
+ | { ok: true; item: MarketItem; target: string; scope: MarketScope;
202
+ /** variable names the written config now refers to; the human must export them before a restart */
203
+ envNames: string[];
204
+ /** project scope only: whether the write was recorded as trusted */
205
+ trusted?: boolean;
206
+ /** MCP only: `<…>` placeholders the written entry still carries because nobody answered for them; the
207
+ * loader skips the server until a hand replaces them, so `next` says that instead of "restart" */
208
+ fillIn?: string[];
209
+ /** MCP install-once only (mcp/local-package.ts): the package npm put on disk, as recorded in
210
+ * installed.json — `missing` names anything the record could not carry (an integrity hash the lockfile
211
+ * did not supply), so a caller can say so rather than print a clean line over a hole */
212
+ package?: { name: string; version: string; prefix: string; bin: string; integrity?: string; missing?: string[] };
213
+ /** what the human must do for it to take effect ("restart rovecode — servers are read once per process") */
214
+ next?: string }
215
+ | { ok: false; error: string };
216
+
217
+ // ---------------------------------------------------------------- pure helpers (no I/O)
218
+
219
+ /** `mcp:filesystem` → {kind, id}; a bare word → {id} with no kind. Anything else → null. */
220
+ export function parseQualifiedId(text: string): { kind?: MarketKind; id: string } | null {
221
+ const t = text.trim();
222
+ if (t.length === 0 || t.length > LIMITS.str) return null;
223
+ const m = /^(mcp|skill|plugin):(.+)$/.exec(t);
224
+ if (m) return { kind: m[1] as MarketKind, id: m[2]!.trim() };
225
+ return { id: t };
226
+ }
227
+
228
+ export const qualify = (item: Pick<MarketItem, "kind" | "id">): string => `${item.kind}:${item.id}`;
229
+
230
+ /** The one-line row every surface shows. Kept here so the CLI, the TUI and the site cannot drift. */
231
+ export function itemLine(item: MarketItem): string {
232
+ const bits = [item.kind.padEnd(6), item.title];
233
+ if (item.version) bits.push(item.version);
234
+ bits.push(`· ${item.publisher}`);
235
+ return `${bits.join(" ")} — ${item.description}`;
236
+ }
@@ -0,0 +1,227 @@
1
+ /** Check a catalog before anyone is asked to trust it.
2
+ *
3
+ * `--check` in CI answers "did OUR generator produce this?", which is the wrong question for someone
4
+ * else's file. A person writing a private catalog for their team, or preparing one to send us, needs the
5
+ * question answered the other way round: *given this file, what would rovecode do with it, and which of my
6
+ * fields would not survive?* That is what this reports, field by field.
7
+ *
8
+ * THE INVARIANT THAT MATTERS: this must never call a row valid that `registry.ts` would drop. A validator
9
+ * that disagrees with the loader is worse than none — it hands out a clean bill for a catalog that shows
10
+ * up empty. So every row is also run through `itemFromCatalog`, the real reader, and a row it refuses
11
+ * while we found nothing to say produces an error anyway. The checks below exist to explain the refusal in
12
+ * the author's terms, not to be the authority on it.
13
+ *
14
+ * Errors and warnings are genuinely different and are not collapsed:
15
+ * error — the row (or the file) is dropped. The market will not show it.
16
+ * warning — the row loads, but not as written: a field was truncated, a list item past the cap was
17
+ * dropped, a key was not recognised. Silent in production, and exactly what an author wants
18
+ * to hear before they publish.
19
+ */
20
+
21
+ import { itemFromCatalog } from "./registry.ts";
22
+ import { LIMITS } from "./types.ts";
23
+
24
+ export type Severity = "error" | "warning";
25
+
26
+ export interface Finding {
27
+ /** where it is, in the author's terms: `items[3].description` */
28
+ path: string;
29
+ message: string;
30
+ severity: Severity;
31
+ }
32
+
33
+ export interface ValidationReport {
34
+ ok: boolean;
35
+ kind: "skill" | "plugin";
36
+ /** rows that would load */
37
+ accepted: number;
38
+ /** rows the market would drop */
39
+ dropped: number;
40
+ findings: Finding[];
41
+ }
42
+
43
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
44
+ const ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
45
+
46
+ /** The keys each kind's rows may carry. An unrecognised key is a warning, not an error, because a catalog
47
+ * may legitimately carry more than we read — but `descriptoin` is a typo that would otherwise cost the
48
+ * author a silent empty description, and that is worth a line. */
49
+ const KNOWN_KEYS: Record<"skill" | "plugin", ReadonlySet<string>> = {
50
+ skill: new Set(["id", "name", "title", "publisher", "description", "version", "license", "tags", "repository",
51
+ "homepage", "source", "files", "bytes", "docs", "env", "status", "planNote"]),
52
+ plugin: new Set(["id", "name", "title", "publisher", "description", "version", "apiVersion", "license", "tags",
53
+ "repository", "homepage", "install", "contributes", "docs", "env", "status", "planNote"]),
54
+ };
55
+
56
+ /** a subfolder that climbs out of its clone, by the same rules the installers apply */
57
+ function escapes(sub: unknown): boolean {
58
+ if (typeof sub !== "string" || sub === "") return false;
59
+ const s = sub.split("\\").join("/");
60
+ return s.startsWith("/") || /^[A-Za-z]:/.test(s) || s.split("/").includes("..");
61
+ }
62
+
63
+ export interface ValidateOptions {
64
+ /** `skill` or `plugin`; when absent it is read from the document's own `kind`, else inferred from a name */
65
+ kind?: "skill" | "plugin";
66
+ /** the file name, used only to infer the kind when nothing else says */
67
+ filename?: string;
68
+ }
69
+
70
+ export function inferKind(doc: unknown, opts: ValidateOptions = {}): "skill" | "plugin" | undefined {
71
+ if (opts.kind !== undefined) return opts.kind;
72
+ if (isRecord(doc) && (doc.kind === "skill" || doc.kind === "plugin")) return doc.kind;
73
+ const n = (opts.filename ?? "").toLowerCase();
74
+ if (n.includes("skill")) return "skill";
75
+ if (n.includes("plugin")) return "plugin";
76
+ return undefined;
77
+ }
78
+
79
+ /** Validate one catalog document. `text` is the raw file — parsing is part of what is being checked. */
80
+ export function validateCatalog(text: string, opts: ValidateOptions = {}): ValidationReport {
81
+ const findings: Finding[] = [];
82
+ const add = (path: string, message: string, severity: Severity = "error") => findings.push({ path, message, severity });
83
+
84
+ if (text.length > LIMITS.body) {
85
+ return { ok: false, kind: opts.kind ?? "skill", accepted: 0, dropped: 0,
86
+ findings: [{ path: "", message: `the file is ${Math.round(text.length / 1024)} KB; rovecode reads at most ${LIMITS.body / 1024} KB`, severity: "error" }] };
87
+ }
88
+
89
+ let doc: unknown;
90
+ try { doc = JSON.parse(text); }
91
+ catch (e) { return { ok: false, kind: opts.kind ?? "skill", accepted: 0, dropped: 0,
92
+ findings: [{ path: "", message: `not valid JSON: ${e instanceof Error ? e.message : String(e)}`, severity: "error" }] }; }
93
+
94
+ const kind = inferKind(doc, opts);
95
+ if (kind === undefined) {
96
+ return { ok: false, kind: "skill", accepted: 0, dropped: 0,
97
+ findings: [{ path: "", message: `cannot tell whether this is a skill or a plugin catalog — pass --kind skill|plugin, or give the document a "kind" field`, severity: "error" }] };
98
+ }
99
+
100
+ if (!isRecord(doc)) {
101
+ return { ok: false, kind, accepted: 0, dropped: 0, findings: [{ path: "", message: "the top level must be an object", severity: "error" }] };
102
+ }
103
+ if (doc.version === undefined) add("version", "missing; rovecode assumes 1", "warning");
104
+ else if (doc.version !== 1) add("version", `is ${JSON.stringify(doc.version)}; this rovecode reads version 1`);
105
+ if (!Array.isArray(doc.items)) {
106
+ add("items", "missing or not an array — a catalog is { version, items: [...] }");
107
+ return { ok: false, kind, accepted: 0, dropped: 0, findings };
108
+ }
109
+
110
+ const rows = doc.items;
111
+ if (rows.length === 0) add("items", "no rows: this catalog would show nothing", "warning");
112
+ if (rows.length > LIMITS.items) add("items", `${rows.length} rows; rovecode reads the first ${LIMITS.items} and ignores the rest`, "warning");
113
+
114
+ const seen = new Map<string, number>();
115
+ let accepted = 0, dropped = 0;
116
+
117
+ rows.forEach((raw: unknown, i: number) => {
118
+ const at = `items[${i}]`;
119
+ const before = findings.length;
120
+
121
+ if (!isRecord(raw)) { add(at, "not an object"); dropped++; return; }
122
+
123
+ const id = typeof raw.id === "string" ? raw.id : typeof raw.name === "string" ? raw.name : undefined;
124
+ if (id === undefined) add(`${at}.id`, "missing (a row needs an id, or a name to use as one)");
125
+ else if (!ID.test(id)) add(`${at}.id`, `"${id}" is not a bare slug — lowercase letters, digits, dot, dash, underscore, at most 64`);
126
+ else {
127
+ const first = seen.get(id);
128
+ if (first !== undefined) add(`${at}.id`, `"${id}" is already used by items[${first}]; ids are unique within a kind`);
129
+ else seen.set(id, i);
130
+ }
131
+
132
+ if (typeof raw.description !== "string" || raw.description.trim() === "") add(`${at}.description`, "missing (a row needs one line describing it)");
133
+ else if (raw.description.length > LIMITS.desc) add(`${at}.description`, `${raw.description.length} characters; will be cut to ${LIMITS.desc}`, "warning");
134
+
135
+ for (const k of ["title", "publisher", "version", "license", "repository", "homepage"] as const) {
136
+ const v = raw[k];
137
+ if (v !== undefined && typeof v !== "string") add(`${at}.${k}`, `must be a string, not ${Array.isArray(v) ? "an array" : typeof v}`, "warning");
138
+ else if (typeof v === "string" && v.length > LIMITS.str) add(`${at}.${k}`, `${v.length} characters; will be cut to ${LIMITS.str}`, "warning");
139
+ }
140
+ if (raw.publisher === undefined) add(`${at}.publisher`, 'missing; the row will say "unknown"', "warning");
141
+
142
+ if (raw.tags !== undefined) {
143
+ if (!Array.isArray(raw.tags)) add(`${at}.tags`, "must be an array of strings", "warning");
144
+ else if (raw.tags.length > LIMITS.list) add(`${at}.tags`, `${raw.tags.length} tags; only the first ${LIMITS.list} are read`, "warning");
145
+ }
146
+
147
+ validateInstall(kind, raw, at, add);
148
+ validateDocs(raw.docs, `${at}.docs`, add);
149
+
150
+ for (const key of Object.keys(raw)) {
151
+ if (!KNOWN_KEYS[kind].has(key)) add(`${at}.${key}`, "not a field rovecode reads — a typo, or something it will ignore", "warning");
152
+ }
153
+
154
+ // The authority, not us: whatever we found or missed, the real reader decides whether this row loads.
155
+ const notes: string[] = [];
156
+ const item = itemFromCatalog(kind, raw, notes);
157
+ const errorsHere = findings.slice(before).some((f) => f.severity === "error");
158
+ if (item === null) {
159
+ dropped++;
160
+ if (!errorsHere) add(at, `rovecode drops this row${notes.length ? `: ${notes[notes.length - 1]}` : ""} — the checks above did not explain why, which is a gap in this validator`);
161
+ } else {
162
+ if (errorsHere) add(at, "the row loads despite the errors above; treat them as the more careful reading", "warning");
163
+ accepted++;
164
+ }
165
+ });
166
+
167
+ return { ok: findings.every((f) => f.severity !== "error"), kind, accepted, dropped, findings };
168
+ }
169
+
170
+ function validateInstall(kind: "skill" | "plugin", raw: Record<string, unknown>, at: string,
171
+ add: (p: string, m: string, s?: Severity) => void): void {
172
+ if (kind === "skill") {
173
+ const source = isRecord(raw.source) ? raw.source : undefined;
174
+ const files = Array.isArray(raw.files) ? raw.files : undefined;
175
+ if (source === undefined && files === undefined) {
176
+ add(`${at}.source`, "missing: a skill needs either source.git (a repository) or files (the text itself)");
177
+ return;
178
+ }
179
+ if (source !== undefined) {
180
+ if (typeof source.git !== "string" || !/^https?:\/\//.test(source.git)) add(`${at}.source.git`, "must be an http(s) repository URL");
181
+ if (escapes(source.subfolder)) add(`${at}.source.subfolder`, `"${String(source.subfolder)}" climbs out of the clone`);
182
+ }
183
+ files?.forEach((f: unknown, j: number) => {
184
+ const p = isRecord(f) ? f.path : undefined;
185
+ if (typeof p !== "string" || p === "") add(`${at}.files[${j}].path`, "missing");
186
+ else if (escapes(p)) add(`${at}.files[${j}].path`, `"${p}" writes outside the skill's folder`);
187
+ if (isRecord(f) && typeof f.text !== "string") add(`${at}.files[${j}].text`, "missing (the file's contents)");
188
+ });
189
+ if (files !== undefined && files.length > LIMITS.list) add(`${at}.files`, `${files.length} files; only the first ${LIMITS.list} are read`, "warning");
190
+ return;
191
+ }
192
+
193
+ const install = isRecord(raw.install) ? raw.install : undefined;
194
+ if (install === undefined) { add(`${at}.install`, "missing: a plugin needs install.source (a repository URL or a folder)"); return; }
195
+ if (typeof install.source !== "string" || install.source === "") { add(`${at}.install.source`, "missing"); return; }
196
+ if (install.git === true && !/^https?:\/\//.test(install.source)) add(`${at}.install.source`, `"${install.source}" is marked git: true but is not an http(s) URL`);
197
+ if (escapes(install.subfolder)) add(`${at}.install.subfolder`, `"${String(install.subfolder)}" climbs out of the clone`);
198
+ // Not an error, and said plainly: rovecode's own three plugins all live in a subfolder, so a row without
199
+ // one is claiming the manifest sits at the repository root — right sometimes, a mistake more often.
200
+ if (install.subfolder === undefined) add(`${at}.install.subfolder`, "absent: the plugin.json must then be at the repository root", "warning");
201
+ }
202
+
203
+ function validateDocs(docs: unknown, at: string, add: (p: string, m: string, s?: Severity) => void): void {
204
+ if (docs === undefined) return; // a row without documentation is a normal row
205
+ if (!isRecord(docs)) { add(at, "must be an object", "warning"); return; }
206
+ if (typeof docs.source !== "string" || !/^https?:\/\//.test(docs.source)) add(`${at}.source`, "must be the http(s) URL the document was read from — the docs are dropped without it", "warning");
207
+ if (docs.format !== "markdown") add(`${at}.format`, `must be "markdown"; the docs are dropped otherwise`, "warning");
208
+ if (typeof docs.body !== "string" || docs.body.trim() === "") add(`${at}.body`, "empty: the docs are dropped and the row keeps no documentation", "warning");
209
+ else if (Buffer.byteLength(docs.body, "utf8") > LIMITS.docs) add(`${at}.body`, `${Math.round(Buffer.byteLength(docs.body, "utf8") / 1024)} KB; will be cut to ${LIMITS.docs / 1024} KB`, "warning");
210
+ if (docs.truncated === true && typeof docs.bytes === "number" && typeof docs.body === "string"
211
+ && docs.bytes <= Buffer.byteLength(docs.body, "utf8")) {
212
+ add(`${at}.bytes`, "marked truncated, but bytes is not larger than the body — bytes should be the size BEFORE truncation", "warning");
213
+ }
214
+ }
215
+
216
+ /** The report as lines, for a terminal. Errors first: an author fixes those, warnings are a second pass. */
217
+ export function reportLines(r: ValidationReport, where: string): string[] {
218
+ const errors = r.findings.filter((f) => f.severity === "error");
219
+ const warnings = r.findings.filter((f) => f.severity === "warning");
220
+ const lines = [`${where}: ${r.kind} catalog — ${r.ok ? "valid" : "INVALID"}`];
221
+ lines.push(` ${r.accepted} row${r.accepted === 1 ? "" : "s"} would load, ${r.dropped} dropped, ${errors.length} error${errors.length === 1 ? "" : "s"}, ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`);
222
+ for (const f of [...errors, ...warnings]) {
223
+ lines.push(` ${f.severity === "error" ? "error " : "warning"} ${f.path === "" ? "(file)" : f.path}: ${f.message}`);
224
+ }
225
+ if (r.ok && warnings.length > 0) lines.push(" warnings do not stop a catalog loading; they are fields that will not survive as written");
226
+ return lines;
227
+ }