rubik 0.1.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 (523) hide show
  1. checksums.yaml +7 -0
  2. data/ATTRIBUTION.md +139 -0
  3. data/CHANGELOG.md +236 -0
  4. data/LICENSE +21 -0
  5. data/README.md +269 -0
  6. data/bin/rubik +82 -0
  7. data/lib/rubik/adapter/base.rb +195 -0
  8. data/lib/rubik/adapter/c.rb +36 -0
  9. data/lib/rubik/adapter/crystal.rb +78 -0
  10. data/lib/rubik/adapter/generic.rb +86 -0
  11. data/lib/rubik/adapter/go.rb +36 -0
  12. data/lib/rubik/adapter/node.rb +146 -0
  13. data/lib/rubik/adapter/process.rb +78 -0
  14. data/lib/rubik/adapter/python.rb +30 -0
  15. data/lib/rubik/adapter/registry.rb +81 -0
  16. data/lib/rubik/adapter/ruby.rb +30 -0
  17. data/lib/rubik/adapter/rust.rb +36 -0
  18. data/lib/rubik/adapter/zig.rb +36 -0
  19. data/lib/rubik/adapter.rb +18 -0
  20. data/lib/rubik/ast/body_loader.rb +19 -0
  21. data/lib/rubik/ast/c_extractor.rb +114 -0
  22. data/lib/rubik/ast/cache_store.rb +191 -0
  23. data/lib/rubik/ast/extractor.rb +59 -0
  24. data/lib/rubik/ast/fallback_cleaner.rb +145 -0
  25. data/lib/rubik/ast/go_extractor.rb +148 -0
  26. data/lib/rubik/ast/graph.rb +272 -0
  27. data/lib/rubik/ast/python_extractor.rb +120 -0
  28. data/lib/rubik/ast/ruby_extractor.rb +260 -0
  29. data/lib/rubik/ast/rust_extractor.rb +127 -0
  30. data/lib/rubik/ast/symbol.rb +103 -0
  31. data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
  32. data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
  33. data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
  34. data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
  35. data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
  36. data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
  37. data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
  38. data/lib/rubik/ast/treesitter_extractor.rb +93 -0
  39. data/lib/rubik/ast/typescript_extractor.rb +283 -0
  40. data/lib/rubik/ast/zig_extractor.rb +116 -0
  41. data/lib/rubik/ast.rb +13 -0
  42. data/lib/rubik/benchmark/runner.rb +224 -0
  43. data/lib/rubik/benchmark/synth_repo.rb +59 -0
  44. data/lib/rubik/benchmark.rb +9 -0
  45. data/lib/rubik/buffer/diff.rb +43 -0
  46. data/lib/rubik/buffer/manager.rb +427 -0
  47. data/lib/rubik/buffer/path_validator.rb +60 -0
  48. data/lib/rubik/buffer/snapshot.rb +99 -0
  49. data/lib/rubik/buffer.rb +10 -0
  50. data/lib/rubik/coeffects.rb +129 -0
  51. data/lib/rubik/concurrency/boundary.rb +74 -0
  52. data/lib/rubik/concurrency/dns_pool.rb +68 -0
  53. data/lib/rubik/concurrency/errors.rb +17 -0
  54. data/lib/rubik/concurrency/event_loop.rb +441 -0
  55. data/lib/rubik/concurrency/join.rb +55 -0
  56. data/lib/rubik/concurrency/process_waiter.rb +55 -0
  57. data/lib/rubik/concurrency/scheduler.rb +84 -0
  58. data/lib/rubik/concurrency/timers.rb +109 -0
  59. data/lib/rubik/concurrency.rb +132 -0
  60. data/lib/rubik/config/deep_ops.rb +80 -0
  61. data/lib/rubik/config/defaults.rb +248 -0
  62. data/lib/rubik/config/model_catalog.rb +93 -0
  63. data/lib/rubik/config/store.rb +155 -0
  64. data/lib/rubik/config.rb +216 -0
  65. data/lib/rubik/context/git_info.rb +84 -0
  66. data/lib/rubik/context/git_lister.rb +37 -0
  67. data/lib/rubik/context/index.rb +134 -0
  68. data/lib/rubik/context/repo_context_reader.rb +121 -0
  69. data/lib/rubik/context/scanner.rb +439 -0
  70. data/lib/rubik/context.rb +11 -0
  71. data/lib/rubik/diagnostics/collector.rb +200 -0
  72. data/lib/rubik/diagnostics/entry.rb +60 -0
  73. data/lib/rubik/diagnostics/parser.rb +107 -0
  74. data/lib/rubik/diagnostics.rb +10 -0
  75. data/lib/rubik/event_bus.rb +56 -0
  76. data/lib/rubik/kernel/dispatcher.rb +171 -0
  77. data/lib/rubik/kernel/dmesg.rb +116 -0
  78. data/lib/rubik/kernel/init.rb +177 -0
  79. data/lib/rubik/kernel/record.rb +85 -0
  80. data/lib/rubik/kernel/unit.rb +160 -0
  81. data/lib/rubik/llm/anthropic.rb +202 -0
  82. data/lib/rubik/llm/chunk_assembler.rb +285 -0
  83. data/lib/rubik/llm/client.rb +526 -0
  84. data/lib/rubik/llm/formatter.rb +450 -0
  85. data/lib/rubik/llm/message_sanitizer.rb +87 -0
  86. data/lib/rubik/llm/ollama.rb +167 -0
  87. data/lib/rubik/llm/openai.rb +21 -0
  88. data/lib/rubik/llm/openai_compatible.rb +216 -0
  89. data/lib/rubik/llm/provider.rb +125 -0
  90. data/lib/rubik/llm/registry.rb +336 -0
  91. data/lib/rubik/llm/router.rb +100 -0
  92. data/lib/rubik/llm.rb +18 -0
  93. data/lib/rubik/log.rb +38 -0
  94. data/lib/rubik/lsp/client.rb +475 -0
  95. data/lib/rubik/lsp/manager.rb +366 -0
  96. data/lib/rubik/lsp.rb +9 -0
  97. data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
  98. data/lib/rubik/mcp/client.rb +169 -0
  99. data/lib/rubik/mcp/errors.rb +11 -0
  100. data/lib/rubik/mcp/manager.rb +619 -0
  101. data/lib/rubik/mcp/proxy_tool.rb +104 -0
  102. data/lib/rubik/mcp/transport/stdio.rb +255 -0
  103. data/lib/rubik/mcp.rb +12 -0
  104. data/lib/rubik/orchestrator/activity_log.rb +75 -0
  105. data/lib/rubik/orchestrator/auto_apply.rb +166 -0
  106. data/lib/rubik/orchestrator/budget_manager.rb +97 -0
  107. data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
  108. data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
  109. data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
  110. data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
  111. data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
  112. data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
  113. data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
  114. data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
  115. data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
  116. data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
  117. data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
  118. data/lib/rubik/orchestrator/goal_state.rb +184 -0
  119. data/lib/rubik/orchestrator/goal_wait.rb +125 -0
  120. data/lib/rubik/orchestrator/loop.rb +388 -0
  121. data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
  122. data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
  123. data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
  124. data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
  125. data/lib/rubik/orchestrator/shell_deps.rb +85 -0
  126. data/lib/rubik/orchestrator/step_executor.rb +328 -0
  127. data/lib/rubik/orchestrator/stream_manager.rb +118 -0
  128. data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
  129. data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
  130. data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
  131. data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
  132. data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
  133. data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
  134. data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
  135. data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
  136. data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
  137. data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
  138. data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
  139. data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
  140. data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
  141. data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
  142. data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
  143. data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
  144. data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
  145. data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
  146. data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
  147. data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
  148. data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
  149. data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
  150. data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
  151. data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
  152. data/lib/rubik/orchestrator/tool_history.rb +39 -0
  153. data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
  154. data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
  155. data/lib/rubik/orchestrator/turn_executor.rb +376 -0
  156. data/lib/rubik/orchestrator.rb +11 -0
  157. data/lib/rubik/platform/filesystem.rb +64 -0
  158. data/lib/rubik/platform/io_drain.rb +53 -0
  159. data/lib/rubik/platform/path.rb +210 -0
  160. data/lib/rubik/platform/process.rb +38 -0
  161. data/lib/rubik/platform/sanitize.rb +157 -0
  162. data/lib/rubik/platform.rb +113 -0
  163. data/lib/rubik/plugin/dsl.rb +51 -0
  164. data/lib/rubik/plugin/manager.rb +247 -0
  165. data/lib/rubik/plugin/storage.rb +195 -0
  166. data/lib/rubik/plugin.rb +10 -0
  167. data/lib/rubik/prompt/context_builder.rb +377 -0
  168. data/lib/rubik/prompt/context_compactor.rb +326 -0
  169. data/lib/rubik/prompt/conversation_rows.rb +74 -0
  170. data/lib/rubik/prompt/doctrine.rb +160 -0
  171. data/lib/rubik/prompt/emergency_compactor.rb +81 -0
  172. data/lib/rubik/prompt/services.rb +60 -0
  173. data/lib/rubik/prompt/token_estimator.rb +63 -0
  174. data/lib/rubik/prompt/tool_teachings.rb +78 -0
  175. data/lib/rubik/prompt.rb +13 -0
  176. data/lib/rubik/repair/loop.rb +95 -0
  177. data/lib/rubik/repair.rb +8 -0
  178. data/lib/rubik/repl/ast_warmup.rb +129 -0
  179. data/lib/rubik/repl/command_context.rb +214 -0
  180. data/lib/rubik/repl/command_parser.rb +48 -0
  181. data/lib/rubik/repl/commands/agent.rb +30 -0
  182. data/lib/rubik/repl/commands/agent_context.rb +289 -0
  183. data/lib/rubik/repl/commands/agent_execution.rb +342 -0
  184. data/lib/rubik/repl/commands/agent_models.rb +212 -0
  185. data/lib/rubik/repl/commands/agent_providers.rb +232 -0
  186. data/lib/rubik/repl/commands/base.rb +55 -0
  187. data/lib/rubik/repl/commands/buffer.rb +124 -0
  188. data/lib/rubik/repl/commands/dmesg.rb +87 -0
  189. data/lib/rubik/repl/commands/lsp.rb +235 -0
  190. data/lib/rubik/repl/commands/mcp.rb +194 -0
  191. data/lib/rubik/repl/commands/plugins.rb +208 -0
  192. data/lib/rubik/repl/commands/pruner.rb +71 -0
  193. data/lib/rubik/repl/commands/session.rb +183 -0
  194. data/lib/rubik/repl/commands/skills.rb +111 -0
  195. data/lib/rubik/repl/commands/storage.rb +85 -0
  196. data/lib/rubik/repl/commands/subagents.rb +195 -0
  197. data/lib/rubik/repl/commands/system.rb +85 -0
  198. data/lib/rubik/repl/commands/tasks.rb +120 -0
  199. data/lib/rubik/repl/commands/workspace.rb +443 -0
  200. data/lib/rubik/repl/commands.rb +44 -0
  201. data/lib/rubik/repl/composition.rb +32 -0
  202. data/lib/rubik/repl/config_wiring.rb +142 -0
  203. data/lib/rubik/repl/confirmations.rb +56 -0
  204. data/lib/rubik/repl/hooks.rb +47 -0
  205. data/lib/rubik/repl/input_execution.rb +323 -0
  206. data/lib/rubik/repl/input_loop.rb +213 -0
  207. data/lib/rubik/repl/interrupt_handler.rb +217 -0
  208. data/lib/rubik/repl/lifecycle.rb +410 -0
  209. data/lib/rubik/repl/paste_support.rb +88 -0
  210. data/lib/rubik/repl/pending_notice_queue.rb +60 -0
  211. data/lib/rubik/repl/presentation.rb +132 -0
  212. data/lib/rubik/repl/pruner/brief.rb +101 -0
  213. data/lib/rubik/repl/pruner/gate.rb +279 -0
  214. data/lib/rubik/repl/pruner/harness.rb +236 -0
  215. data/lib/rubik/repl/pruner/rules.rb +316 -0
  216. data/lib/rubik/repl/pruner/service.rb +82 -0
  217. data/lib/rubik/repl/pruner.rb +39 -0
  218. data/lib/rubik/repl/service_catalog.rb +374 -0
  219. data/lib/rubik/repl/setup_factories.rb +66 -0
  220. data/lib/rubik/repl/shell.rb +231 -0
  221. data/lib/rubik/repl/step_formatter.rb +324 -0
  222. data/lib/rubik/repl/tee_io.rb +105 -0
  223. data/lib/rubik/repl/terminal_guard.rb +157 -0
  224. data/lib/rubik/repl/turn_recorder.rb +234 -0
  225. data/lib/rubik/repl.rb +14 -0
  226. data/lib/rubik/runtime/ast_service.rb +78 -0
  227. data/lib/rubik/runtime/context.rb +335 -0
  228. data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
  229. data/lib/rubik/runtime/discovery.rb +342 -0
  230. data/lib/rubik/runtime/fiber.rb +142 -0
  231. data/lib/rubik/runtime/fiber_ordering.rb +52 -0
  232. data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
  233. data/lib/rubik/runtime/index_service.rb +58 -0
  234. data/lib/rubik/runtime/loop_service.rb +73 -0
  235. data/lib/rubik/runtime/manager.rb +241 -0
  236. data/lib/rubik/runtime/overlay_storage.rb +481 -0
  237. data/lib/rubik/runtime/service_definition.rb +54 -0
  238. data/lib/rubik/runtime/service_registration.rb +33 -0
  239. data/lib/rubik/runtime/task_manager.rb +302 -0
  240. data/lib/rubik/runtime.rb +19 -0
  241. data/lib/rubik/sandbox/driver/base.rb +30 -0
  242. data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
  243. data/lib/rubik/sandbox/driver/docker.rb +113 -0
  244. data/lib/rubik/sandbox/driver/podman.rb +98 -0
  245. data/lib/rubik/sandbox/driver/process.rb +190 -0
  246. data/lib/rubik/sandbox/driver.rb +91 -0
  247. data/lib/rubik/sandbox/runner.rb +218 -0
  248. data/lib/rubik/sandbox/shell_resolver.rb +178 -0
  249. data/lib/rubik/sandbox.rb +8 -0
  250. data/lib/rubik/security/blocked_guidance.rb +187 -0
  251. data/lib/rubik/security/classifier/classifier.rb +440 -0
  252. data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
  253. data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
  254. data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
  255. data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
  256. data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
  257. data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
  258. data/lib/rubik/security/classifier/commands/black.yaml +12 -0
  259. data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
  260. data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
  261. data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
  262. data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
  263. data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
  264. data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
  265. data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
  266. data/lib/rubik/security/classifier/commands/column.yaml +7 -0
  267. data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
  268. data/lib/rubik/security/classifier/commands/command.yaml +12 -0
  269. data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
  270. data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
  271. data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
  272. data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
  273. data/lib/rubik/security/classifier/commands/date.yaml +10 -0
  274. data/lib/rubik/security/classifier/commands/df.yaml +7 -0
  275. data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
  276. data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
  277. data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
  278. data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
  279. data/lib/rubik/security/classifier/commands/du.yaml +7 -0
  280. data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
  281. data/lib/rubik/security/classifier/commands/env.yaml +15 -0
  282. data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
  283. data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
  284. data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
  285. data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
  286. data/lib/rubik/security/classifier/commands/false.yaml +7 -0
  287. data/lib/rubik/security/classifier/commands/file.yaml +7 -0
  288. data/lib/rubik/security/classifier/commands/find.yaml +25 -0
  289. data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
  290. data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
  291. data/lib/rubik/security/classifier/commands/free.yaml +7 -0
  292. data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
  293. data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
  294. data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
  295. data/lib/rubik/security/classifier/commands/git.yaml +524 -0
  296. data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
  297. data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
  298. data/lib/rubik/security/classifier/commands/go.yaml +44 -0
  299. data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
  300. data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
  301. data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
  302. data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
  303. data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
  304. data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
  305. data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
  306. data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
  307. data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
  308. data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
  309. data/lib/rubik/security/classifier/commands/head.yaml +7 -0
  310. data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
  311. data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
  312. data/lib/rubik/security/classifier/commands/id.yaml +7 -0
  313. data/lib/rubik/security/classifier/commands/install.yaml +7 -0
  314. data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
  315. data/lib/rubik/security/classifier/commands/java.yaml +35 -0
  316. data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
  317. data/lib/rubik/security/classifier/commands/join.yaml +7 -0
  318. data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
  319. data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
  320. data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
  321. data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
  322. data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
  323. data/lib/rubik/security/classifier/commands/less.yaml +7 -0
  324. data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
  325. data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
  326. data/lib/rubik/security/classifier/commands/make.yaml +21 -0
  327. data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
  328. data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
  329. data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
  330. data/lib/rubik/security/classifier/commands/more.yaml +7 -0
  331. data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
  332. data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
  333. data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
  334. data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
  335. data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
  336. data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
  337. data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
  338. data/lib/rubik/security/classifier/commands/node.yaml +20 -0
  339. data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
  340. data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
  341. data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
  342. data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
  343. data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
  344. data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
  345. data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
  346. data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
  347. data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
  348. data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
  349. data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
  350. data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
  351. data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
  352. data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
  353. data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
  354. data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
  355. data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
  356. data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
  357. data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
  358. data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
  359. data/lib/rubik/security/classifier/commands/python.yaml +9 -0
  360. data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
  361. data/lib/rubik/security/classifier/commands/read.yaml +7 -0
  362. data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
  363. data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
  364. data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
  365. data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
  366. data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
  367. data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
  368. data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
  369. data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
  370. data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
  371. data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
  372. data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
  373. data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
  374. data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
  375. data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
  376. data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
  377. data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
  378. data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
  379. data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
  380. data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
  381. data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
  382. data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
  383. data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
  384. data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
  385. data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
  386. data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
  387. data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
  388. data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
  389. data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
  390. data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
  391. data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
  392. data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
  393. data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
  394. data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
  395. data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
  396. data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
  397. data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
  398. data/lib/rubik/security/classifier/commands/test.yaml +7 -0
  399. data/lib/rubik/security/classifier/commands/time.yaml +8 -0
  400. data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
  401. data/lib/rubik/security/classifier/commands/top.yaml +7 -0
  402. data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
  403. data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
  404. data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
  405. data/lib/rubik/security/classifier/commands/true.yaml +7 -0
  406. data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
  407. data/lib/rubik/security/classifier/commands/type.yaml +7 -0
  408. data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
  409. data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
  410. data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
  411. data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
  412. data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
  413. data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
  414. data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
  415. data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
  416. data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
  417. data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
  418. data/lib/rubik/security/classifier/commands/which.yaml +7 -0
  419. data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
  420. data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
  421. data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
  422. data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
  423. data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
  424. data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
  425. data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
  426. data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
  427. data/lib/rubik/security/classifier/database.rb +216 -0
  428. data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
  429. data/lib/rubik/security/classifier/matcher/options.rb +245 -0
  430. data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
  431. data/lib/rubik/security/classifier/matcher.rb +249 -0
  432. data/lib/rubik/security/classifier/models.rb +170 -0
  433. data/lib/rubik/security/classifier/parser.rb +23 -0
  434. data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
  435. data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
  436. data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
  437. data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
  438. data/lib/rubik/security/classifier.rb +25 -0
  439. data/lib/rubik/security/errors.rb +39 -0
  440. data/lib/rubik/security/guard.rb +954 -0
  441. data/lib/rubik/security/redactor.rb +89 -0
  442. data/lib/rubik/security.rb +11 -0
  443. data/lib/rubik/session/manager.rb +331 -0
  444. data/lib/rubik/session/recorder.rb +149 -0
  445. data/lib/rubik/session/replayer.rb +65 -0
  446. data/lib/rubik/session.rb +10 -0
  447. data/lib/rubik/skills/frontmatter.rb +41 -0
  448. data/lib/rubik/skills/manager.rb +223 -0
  449. data/lib/rubik/skills/registry.rb +64 -0
  450. data/lib/rubik/skills/skill.rb +33 -0
  451. data/lib/rubik/skills/storage.rb +77 -0
  452. data/lib/rubik/skills/validator.rb +116 -0
  453. data/lib/rubik/skills.rb +20 -0
  454. data/lib/rubik/storage/folding.rb +23 -0
  455. data/lib/rubik/storage/layout.rb +145 -0
  456. data/lib/rubik/storage/render.rb +97 -0
  457. data/lib/rubik/storage/service.rb +42 -0
  458. data/lib/rubik/storage/store.rb +514 -0
  459. data/lib/rubik/storage/transcript_indexer.rb +116 -0
  460. data/lib/rubik/storage/uri_reader.rb +168 -0
  461. data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
  462. data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
  463. data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
  464. data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
  465. data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
  466. data/lib/rubik/storage/vakanuvis/service.rb +69 -0
  467. data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
  468. data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
  469. data/lib/rubik/storage/vakanuvis.rb +77 -0
  470. data/lib/rubik/storage.rb +29 -0
  471. data/lib/rubik/tools/base.rb +107 -0
  472. data/lib/rubik/tools/builtins/dmesg.rb +74 -0
  473. data/lib/rubik/tools/builtins/exec.rb +293 -0
  474. data/lib/rubik/tools/builtins/files.rb +354 -0
  475. data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
  476. data/lib/rubik/tools/builtins/grep.rb +281 -0
  477. data/lib/rubik/tools/builtins/interactive.rb +175 -0
  478. data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
  479. data/lib/rubik/tools/builtins/lsp.rb +283 -0
  480. data/lib/rubik/tools/builtins/pruner.rb +88 -0
  481. data/lib/rubik/tools/builtins/runtime.rb +120 -0
  482. data/lib/rubik/tools/builtins/send_message.rb +109 -0
  483. data/lib/rubik/tools/builtins/show_file.rb +84 -0
  484. data/lib/rubik/tools/builtins/skill.rb +72 -0
  485. data/lib/rubik/tools/builtins/storage.rb +232 -0
  486. data/lib/rubik/tools/builtins/subagent.rb +356 -0
  487. data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
  488. data/lib/rubik/tools/builtins/symbols.rb +209 -0
  489. data/lib/rubik/tools/builtins/synthesis.rb +314 -0
  490. data/lib/rubik/tools/builtins/task.rb +168 -0
  491. data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
  492. data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
  493. data/lib/rubik/tools/builtins.rb +30 -0
  494. data/lib/rubik/tools/catalog.rb +70 -0
  495. data/lib/rubik/tools/declaration.rb +90 -0
  496. data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
  497. data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
  498. data/lib/rubik/tools/native_search.rb +180 -0
  499. data/lib/rubik/tools/output.rb +54 -0
  500. data/lib/rubik/tools/preflight_runner.rb +209 -0
  501. data/lib/rubik/tools/registry.rb +247 -0
  502. data/lib/rubik/tools/result.rb +71 -0
  503. data/lib/rubik/tools/services.rb +96 -0
  504. data/lib/rubik/tools/truncator.rb +78 -0
  505. data/lib/rubik/tools/uri_resolver.rb +195 -0
  506. data/lib/rubik/tools.rb +14 -0
  507. data/lib/rubik/ui/approvals.rb +52 -0
  508. data/lib/rubik/ui/banner.rb +43 -0
  509. data/lib/rubik/ui/capability.rb +122 -0
  510. data/lib/rubik/ui/dialog.rb +106 -0
  511. data/lib/rubik/ui/diff_viewer.rb +78 -0
  512. data/lib/rubik/ui/frame.rb +95 -0
  513. data/lib/rubik/ui/markdown_stream.rb +107 -0
  514. data/lib/rubik/ui/panel.rb +98 -0
  515. data/lib/rubik/ui/renderer.rb +206 -0
  516. data/lib/rubik/ui/spinner.rb +120 -0
  517. data/lib/rubik/ui/statusline.rb +134 -0
  518. data/lib/rubik/ui/theme.rb +118 -0
  519. data/lib/rubik/ui.rb +57 -0
  520. data/lib/rubik/uris.rb +101 -0
  521. data/lib/rubik/version.rb +5 -0
  522. data/lib/rubik.rb +65 -0
  523. metadata +770 -0
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../llm"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ # Domain handler for LLM Providers and Endpoint management (Plan 60 D-6).
10
+ module AgentProviders
11
+ extend Base
12
+
13
+ # Whole-command synopsis: shared by the spine registration (usage:)
14
+ # and the unknown-subcommand fallback branch, so the two cannot drift.
15
+ USAGE_PROVIDER = "Usage: !provider <list | add <name> <type> <endpoint> | add-model <provider> <model> | " \
16
+ "set-context <provider> <model> <limit> | remove-model <provider> <model> | " \
17
+ "set <provider> <field> <val> | remove <name>> [--project|--global]"
18
+
19
+ def self.register(shell)
20
+ register_provider_command(shell)
21
+ register_providers_command(shell)
22
+ end
23
+
24
+ private_class_method def self.register_provider_command(shell)
25
+ shell.register_command("provider",
26
+ description: "Manage LLM providers and models: list | add <name> [type] <url> [--key=...] [--model=...] " \
27
+ "| add-model <provider> <model> [--context=LIMIT] | remove-model <provider> <model> | " \
28
+ "set <provider> <field> <val> | remove <provider>",
29
+ usage: USAGE_PROVIDER) do |args, ctx|
30
+ scope, clean_args = Base.parse_scope(args)
31
+ sub = clean_args.first&.downcase || "list"
32
+
33
+ case sub
34
+ when "list", "ls", "status"
35
+ render_provider_catalog(ctx)
36
+ when "add"
37
+ p_name = clean_args[1]&.downcase
38
+ arg2 = clean_args[2]
39
+ arg3 = clean_args[3]
40
+
41
+ p_type = "openai_compatible"
42
+ p_endpoint = nil
43
+
44
+ if arg2 && (arg2.start_with?("http://") || arg2.start_with?("https://"))
45
+ p_endpoint = arg2
46
+ elsif arg2 && arg3
47
+ p_type = arg2.downcase
48
+ p_endpoint = arg3
49
+ end
50
+
51
+ if p_name.nil? || p_name.empty? || p_endpoint.nil? || p_endpoint.empty?
52
+ ctx.out.puts "Usage: !provider add <name> [type] <endpoint_url> " \
53
+ "[--key=API_KEY | --key-env=ENV_VAR] [--model=NAME] [--context=LIMIT] [--project|--global]"
54
+ elsif !p_endpoint.start_with?("http://") && !p_endpoint.start_with?("https://")
55
+ ctx.out.puts "Error: Endpoint must be a valid HTTP/HTTPS URL (starting with http:// or https://). " \
56
+ "Received: '#{p_endpoint}'\nUsage: !provider add <name> [type] <endpoint_url> " \
57
+ "[--key=API_KEY | --key-env=ENV_VAR] [--model=NAME] [--context=LIMIT] [--project|--global]"
58
+ else
59
+ key_arg = clean_args.find { |a| a.start_with?("--key=") || a.start_with?("--api-key=") }
60
+ key_env_arg = clean_args.find { |a| a.start_with?("--key-env=") }
61
+ model_arg = clean_args.find { |a| a.start_with?("--model=") }
62
+ context_arg = clean_args.find { |a| a.start_with?("--context=") }
63
+
64
+ api_key = key_arg&.split("=", 2)&.last
65
+ key_env = key_env_arg&.split("=", 2)&.last
66
+ m_name = model_arg ? model_arg.split("=", 2).last : "default"
67
+ c_limit = context_arg ? context_arg.split("=", 2).last.to_i : 32_768
68
+
69
+ normalized_endpoint = if ["openai_compatible", "openai"].include?(p_type)
70
+ Rubik::LLM::OpenAICompatibleProvider.normalize_endpoint(p_endpoint)
71
+ else
72
+ p_endpoint
73
+ end
74
+
75
+ p_spec = {
76
+ "type" => p_type,
77
+ "endpoint" => normalized_endpoint,
78
+ "api_key" => api_key,
79
+ "api_key_env" => key_env,
80
+ "models" => [
81
+ { "name" => m_name, "context_limit" => c_limit }
82
+ ]
83
+ }.compact
84
+
85
+ res = ctx.config.set("providers.#{p_name}", p_spec, persist: scope)
86
+ ctx.refresh_llm_registry!
87
+ next if Base.report_persist_failure(ctx, res, scope)
88
+
89
+ ctx.out.puts "Successfully added provider '#{p_name}' (#{p_type} -> #{normalized_endpoint}) " \
90
+ "with model '#{m_name}' [#{Base.scope_label(scope)}]."
91
+ end
92
+ when "add-model", "model-add"
93
+ p_name = clean_args[1]&.downcase
94
+ m_name = clean_args[2]
95
+ if p_name.nil? || p_name.empty? || m_name.nil? || m_name.empty?
96
+ ctx.out.puts "Usage: !provider add-model <provider_name> <model_name> [--context=LIMIT] [--project|--global]"
97
+ else
98
+ context_arg = clean_args.find { |a| a.start_with?("--context=") }
99
+ c_limit = context_arg ? context_arg.split("=", 2).last.to_i : 32_768
100
+
101
+ res = ctx.config.upsert_provider_model(p_name, m_name, context_limit: c_limit, persist: scope)
102
+ ctx.refresh_llm_registry!
103
+ if res[:ok]
104
+ ctx.out.puts "Added model '#{m_name}' (#{c_limit} tokens) to provider '#{p_name}' [#{Base.scope_label(scope)}]."
105
+ elsif res[:reason] == :unknown_provider
106
+ ctx.out.puts "Provider '#{p_name}' not found. Add it first via !provider add."
107
+ else
108
+ Base.report_persist_failure(ctx, res, scope)
109
+ end
110
+ end
111
+ when "remove-model", "model-remove", "model-rm"
112
+ p_name = clean_args[1]&.downcase
113
+ m_name = clean_args[2]
114
+ if p_name.nil? || p_name.empty? || m_name.nil? || m_name.empty?
115
+ ctx.out.puts "Usage: !provider remove-model <provider_name> <model_name> [--project|--global]"
116
+ else
117
+ res = ctx.config.remove_provider_model(p_name, m_name, persist: scope)
118
+ ctx.refresh_llm_registry!
119
+ if res[:ok]
120
+ ctx.out.puts "Removed model '#{m_name}' from provider '#{p_name}' [#{Base.scope_label(scope)}]."
121
+ elsif res[:reason] == :unknown_provider
122
+ ctx.out.puts "Provider '#{p_name}' not found."
123
+ else
124
+ Base.report_persist_failure(ctx, res, scope)
125
+ end
126
+ end
127
+ when "set"
128
+ p_name = clean_args[1]&.downcase
129
+ field = clean_args[2]&.downcase
130
+ val = clean_args[3]
131
+ if p_name.nil? || p_name.empty? || field.nil? || val.nil?
132
+ ctx.out.puts "Usage: !provider set <name> <endpoint|key|key-env|type|stream|stream_timeout|open_timeout|tool_native|num_ctx> <value> [--project|--global]"
133
+ else
134
+ p_spec = ctx.config.provider_spec(p_name)
135
+ if p_spec.nil?
136
+ ctx.out.puts "Provider '#{p_name}' not found."
137
+ else
138
+ case field
139
+ when "key", "api_key"
140
+ p_spec["api_key"] = val
141
+ when "key-env", "key_env", "api_key_env"
142
+ p_spec["api_key_env"] = val
143
+ when "endpoint", "url", "host"
144
+ if !val.start_with?("http://") && !val.start_with?("https://")
145
+ ctx.out.puts "Error: Endpoint must be a valid HTTP/HTTPS URL (starting with http:// or https://). Received: '#{val}'"
146
+ next
147
+ end
148
+ p_spec["endpoint"] = val
149
+ when "type"
150
+ p_spec["type"] = val
151
+ else
152
+ # Free-form fields (stream, tool_native, num_ctx, ...) are
153
+ # typed through parse_scalar_value so "false" becomes a
154
+ # real false — a raw "false" String is truthy in Ruby and
155
+ # would silently re-enable the flag it means to disable.
156
+ p_spec[field] = ctx.config.parse_scalar_value(val)
157
+ end
158
+
159
+ res = ctx.config.set("providers.#{p_name}", p_spec, persist: scope)
160
+ ctx.refresh_llm_registry!
161
+ next if Base.report_persist_failure(ctx, res, scope)
162
+
163
+ ctx.out.puts "Updated provider '#{p_name}' field '#{field}' to '#{val}' [#{Base.scope_label(scope)}]."
164
+ end
165
+ end
166
+ when "remove", "rm", "delete"
167
+ p_name = clean_args[1]&.downcase
168
+ if p_name.nil? || p_name.empty?
169
+ ctx.out.puts "Usage: !provider remove <name> [--project|--global]"
170
+ else
171
+ res = ctx.config.remove_provider(p_name, persist: scope)
172
+ ctx.refresh_llm_registry!
173
+ if res[:ok]
174
+ ctx.out.puts "Removed provider '#{p_name}' [#{Base.scope_label(scope)}]."
175
+ elsif res[:reason] == :unknown_provider
176
+ ctx.out.puts "Provider '#{p_name}' not found."
177
+ else
178
+ Base.report_persist_failure(ctx, res, scope)
179
+ end
180
+ end
181
+ when "set-context", "context", "limit"
182
+ p_name = clean_args[1]&.downcase
183
+ m_name = clean_args[2]
184
+ limit_val = clean_args[3]&.to_i
185
+ if p_name.nil? || p_name.empty? || m_name.nil? || m_name.empty? || limit_val.nil? || limit_val <= 0
186
+ ctx.out.puts "Usage: !provider set-context <provider> <model> <limit_in_tokens> [--project|--global]"
187
+ else
188
+ res = ctx.config.upsert_provider_model(p_name, m_name, context_limit: limit_val, persist: scope)
189
+ ctx.refresh_llm_registry!
190
+ if res[:ok]
191
+ ctx.out.puts "Updated context limit for model '#{m_name}' on provider '#{p_name}' to #{limit_val} tokens [#{Base.scope_label(scope)}]."
192
+ elsif res[:reason] == :unknown_provider
193
+ ctx.out.puts "Provider '#{p_name}' not found."
194
+ else
195
+ Base.report_persist_failure(ctx, res, scope)
196
+ end
197
+ end
198
+ else
199
+ ctx.out.puts USAGE_PROVIDER
200
+ end
201
+ end
202
+ end
203
+
204
+ private_class_method def self.register_providers_command(shell)
205
+ shell.register_command("providers", description: "List all configured LLM providers, endpoints, and models", audience: :both) do |_args, ctx|
206
+ render_provider_catalog(ctx)
207
+ end
208
+ end
209
+
210
+ # Shared rendering for !provider list (human) and !providers (agent):
211
+ # same catalog, same pixels — only the audience differs. Pinned
212
+ # byte-for-byte by TestProviderCatalogRender.
213
+ private_class_method def self.render_provider_catalog(ctx)
214
+ cat = ctx.llm_registry.catalog
215
+ ctx.out.puts "=== Configured LLM Providers & Models ==="
216
+ cat.each do |p_name, info|
217
+ ctx.out.puts "\n• Provider: #{p_name.upcase} (#{info[:type]})"
218
+ ctx.out.puts " Endpoint: #{info[:endpoint]}"
219
+ if info[:models].empty?
220
+ ctx.out.puts " Models : (Default / dynamically requested via model name)"
221
+ else
222
+ ctx.out.puts " Models :"
223
+ info[:models].each do |m|
224
+ ctx.out.puts " - #{m[:name].ljust(35)} [Context: #{m[:context_limit]} tokens]"
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ module Commands
6
+ module Base
7
+ def self.parse_scope(args)
8
+ scope = if args.include?("--project") || args.include?("-p")
9
+ :project
10
+ elsif args.include?("--global") || args.include?("-g")
11
+ :global
12
+ else
13
+ :session
14
+ end
15
+ clean_args = args.reject { |a| ["--project", "-p", "--global", "-g", "--session", "-s"].include?(a) }
16
+ [scope, clean_args]
17
+ end
18
+
19
+ def self.scope_label(scope)
20
+ case scope
21
+ when :project
22
+ "Project level (.rubik/config.yml)"
23
+ when :global
24
+ "System level (~/.rubik/config.yml)"
25
+ else
26
+ "Session only"
27
+ end
28
+ end
29
+
30
+ # One honest line for a failed disk write; returns true when it fired
31
+ # so callers can step past their success report. The in-memory change
32
+ # stands — only persistence failed.
33
+ def self.report_persist_failure(ctx, res, scope)
34
+ return false if res.nil? || res[:ok]
35
+
36
+ ctx.out.puts "Error: could not save to #{scope_label(scope)}: #{res[:error]} " \
37
+ "(change kept for this session only)."
38
+ true
39
+ end
40
+
41
+ def parse_scope(args)
42
+ Base.parse_scope(args)
43
+ end
44
+
45
+ def report_persist_failure(ctx, res, scope)
46
+ Base.report_persist_failure(ctx, res, scope)
47
+ end
48
+
49
+ def scope_label(scope)
50
+ Base.scope_label(scope)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Rubik
6
+ module REPL
7
+ module Commands
8
+ module Buffer
9
+ extend Base
10
+
11
+ # Whole-command synopses: shared by the spine registration (usage:)
12
+ # and the empty-args / fallback branches that print them, so the two
13
+ # cannot drift.
14
+ USAGE_ROLLBACK = "Usage: !rollback <snapshot_id>"
15
+ USAGE_AUTOAPPLY = "Usage: !autoapply <manual | on_test | on_finish | always | status> [--project|--global]"
16
+
17
+ def self.register(shell)
18
+ shell.register_command(
19
+ "diff",
20
+ description: "Preview unapplied buffer changes (staged edits that are NOT yet on disk). " \
21
+ "Scope: buffer only — never shows committed/git history. " \
22
+ "For ground-truth file content, use read_file; for git history, use run_command with git. " \
23
+ "(colorized viewer on terminals)",
24
+ audience: :both
25
+ ) do |args, ctx|
26
+ theme = ctx.theme
27
+ target_path = args.first
28
+ if target_path && !target_path.empty?
29
+ rel = begin
30
+ ctx.buffer_manager.sanitize_path(target_path)
31
+ rescue
32
+ target_path
33
+ end
34
+ if ctx.buffer_manager.dirty_files.key?(rel)
35
+ full_path = File.expand_path(rel, ctx.buffer_manager.root_dir)
36
+ orig_content = File.exist?(full_path) ? File.read(full_path) : ""
37
+ new_content = ctx.buffer_manager.read(rel) || ""
38
+ Rubik::UI::DiffViewer.render(ctx, theme, Rubik::Buffer::Diff.unified_diff(rel, orig_content, new_content))
39
+ else
40
+ ctx.out.puts "No unapplied buffer changes for '#{target_path}'."
41
+ end
42
+ else
43
+ diff_text = ctx.buffer_manager.diff_all
44
+ if diff_text.empty?
45
+ ctx.out.puts "No unapplied buffer changes."
46
+ else
47
+ Rubik::UI::DiffViewer.render(ctx, theme, diff_text)
48
+ end
49
+ end
50
+ end
51
+
52
+ shell.register_command("apply", description: "Apply dirty buffer changes to disk") do |args, ctx|
53
+ target_path = args.first
54
+ if target_path && !target_path.empty?
55
+ applied = ctx.buffer_manager.apply!(target_path)
56
+ conflicts = ctx.buffer_manager.last_conflicts || []
57
+ if applied.empty? && conflicts.empty?
58
+ ctx.out.puts "No unapplied buffer changes for '#{target_path}'."
59
+ else
60
+ ctx.sync_context_and_ast(applied) unless applied.empty?
61
+ ctx.out.puts "Applied changes to file: #{applied.first}" unless applied.empty?
62
+ end
63
+ else
64
+ applied = ctx.buffer_manager.apply!
65
+ conflicts = ctx.buffer_manager.last_conflicts || []
66
+ if applied.empty? && conflicts.empty?
67
+ ctx.out.puts "No unapplied buffer changes."
68
+ else
69
+ ctx.sync_context_and_ast(applied) unless applied.empty?
70
+ ctx.out.puts "Applied changes to #{applied.size} file(s): #{applied.join(', ')}" unless applied.empty?
71
+ end
72
+ end
73
+ conflicts.each do |c|
74
+ ctx.out.puts "[CONFLICT] #{c}: disk content changed since last read — skipped to prevent data loss. Re-read the file to resolve."
75
+ end
76
+ end
77
+
78
+ shell.register_command("undo", description: "Revert the last snapshot", audience: :both) do |_args, ctx|
79
+ if ctx.snapshot_store.undo(ctx.buffer_manager)
80
+ ctx.out.puts "Undo successful."
81
+ else
82
+ ctx.out.puts "No snapshots to undo."
83
+ end
84
+ end
85
+
86
+ shell.register_command("rollback", description: "Rollback to a specific snapshot ID", usage: USAGE_ROLLBACK, audience: :both) do |args, ctx|
87
+ sid = args.first
88
+ if sid.nil? || sid.empty?
89
+ ctx.out.puts USAGE_ROLLBACK
90
+ elsif ctx.snapshot_store.rollback(sid, ctx.buffer_manager)
91
+ ctx.out.puts "Rollback to snapshot #{sid} successful."
92
+ else
93
+ ctx.out.puts "Snapshot #{sid} not found."
94
+ end
95
+ end
96
+
97
+ shell.register_command("autoapply",
98
+ description: "Get or set buffer auto-apply policy (manual|on_test|on_finish|always)",
99
+ usage: USAGE_AUTOAPPLY, audience: :both) do |args, ctx|
100
+ scope, clean_args = Base.parse_scope(args)
101
+ sub = clean_args.first&.downcase
102
+
103
+ if sub.nil? || sub == "status"
104
+ cur = ctx.config.fetch("buffer.auto_apply", "manual")
105
+ can_m = ctx.config.fetch("buffer.model_can_apply", true)
106
+ ctx.out.puts "Buffer Auto-Apply Policy:"
107
+ ctx.out.puts " • Mode : #{cur} (manual | on_test | on_finish | always)"
108
+ ctx.out.puts " • Model Can Apply : #{can_m}"
109
+ ctx.out.puts " • Prompt Indicator : #{ctx.config.fetch('buffer.prompt_indicator', true)}"
110
+ ctx.out.puts " • Reminder Interval: #{ctx.config.fetch('buffer.reminder_interval', 3)} turns"
111
+ elsif ["manual", "on_test", "on_finish", "always"].include?(sub)
112
+ res = ctx.config.set("buffer.auto_apply", sub, persist: scope)
113
+ next if Base.report_persist_failure(ctx, res, scope)
114
+
115
+ ctx.out.puts "Buffer auto-apply policy set to '#{sub}' [#{Base.scope_label(scope)}]."
116
+ else
117
+ ctx.out.puts USAGE_AUTOAPPLY
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Rubik
6
+ module REPL
7
+ module Commands
8
+ # REPL bang command (!dmesg) for inspecting kernel event ring buffer (Plan 102).
9
+ module Dmesg
10
+ extend Base
11
+
12
+ USAGE_DMESG = "Usage: !dmesg [-n <count>] [-l <level>] [-s <subsystem>] [--since <seq>] [clear]"
13
+
14
+ def self.register(shell)
15
+ handler = lambda do |args, ctx|
16
+ dmesg = resolve_dmesg(ctx)
17
+ unless dmesg
18
+ ctx.out.puts "Kernel dmesg ring buffer is not available."
19
+ return
20
+ end
21
+
22
+ if args.include?("clear") || args.include?("-c") || args.include?("--clear")
23
+ dmesg.clear!
24
+ ctx.out.puts "Kernel dmesg buffer cleared."
25
+ return
26
+ end
27
+
28
+ opts = parse_args(args)
29
+ records = dmesg.tail(opts[:limit], level: opts[:level], subsystem: opts[:subsystem], since_seq: opts[:since_seq])
30
+
31
+ if records.empty?
32
+ ctx.out.puts "Kernel dmesg log buffer is empty."
33
+ else
34
+ ctx.out.puts dmesg.formatted(records, color: false)
35
+ end
36
+ end
37
+
38
+ shell.register_command(
39
+ "dmesg",
40
+ description: "Display or filter kernel event ring buffer",
41
+ usage: USAGE_DMESG,
42
+ audience: :both,
43
+ &handler
44
+ )
45
+ end
46
+
47
+ def self.resolve_dmesg(ctx)
48
+ if ctx.respond_to?(:dmesg) && ctx.dmesg
49
+ ctx.dmesg
50
+ elsif ctx.respond_to?(:runtime_manager) && ctx.runtime_manager&.context
51
+ ctx.runtime_manager.context.get(:dmesg)
52
+ end
53
+ end
54
+
55
+ def self.parse_args(args)
56
+ opts = { limit: 50, level: nil, subsystem: nil, since_seq: nil }
57
+ i = 0
58
+ while i < args.size
59
+ arg = args[i]
60
+ case arg
61
+ when "-n", "--limit", "--count"
62
+ i += 1
63
+ opts[:limit] = args[i].to_i if args[i]
64
+ when "-l", "--level"
65
+ i += 1
66
+ opts[:level] = args[i] if args[i]
67
+ when "-s", "--subsystem"
68
+ i += 1
69
+ opts[:subsystem] = args[i] if args[i]
70
+ when "--since"
71
+ i += 1
72
+ opts[:since_seq] = args[i].to_i if args[i]
73
+ when /\A-n(\d+)\z/
74
+ opts[:limit] = ::Regexp.last_match(1).to_i
75
+ when /\A-l(.+)\z/
76
+ opts[:level] = ::Regexp.last_match(1)
77
+ when /\A-s(.+)\z/
78
+ opts[:subsystem] = ::Regexp.last_match(1)
79
+ end
80
+ i += 1
81
+ end
82
+ opts
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end