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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+ require_relative "../security/guard" unless defined?(Rubik::Security::Guard)
6
+
7
+ module Rubik
8
+ module Adapter
9
+ class GenericAdapter < BaseAdapter
10
+ attr_accessor :custom_syntax_cmd
11
+
12
+ def initialize(name, binary_name: nil, test_cmd: nil, format_cmd: nil, syntax_cmd: nil, guard: nil, validation_shell: nil)
13
+ # Assigned BEFORE the validate_custom_command! calls below so ctor-time
14
+ # validation already consults the shared session guard (operator
15
+ # allowlist) instead of a fresh zero-policy one. validation_shell
16
+ # carries the caller's shell context (e.g. RunTestsTool classifies
17
+ # runner candidates with bash semantics).
18
+ @validation_guard = guard
19
+ @validation_shell = validation_shell
20
+ validate_custom_command!(test_cmd, shell: @validation_shell) if test_cmd
21
+ validate_custom_command!(format_cmd, shell: @validation_shell) if format_cmd
22
+ validate_custom_command!(syntax_cmd, shell: @validation_shell) if syntax_cmd
23
+
24
+ derived_bin = binary_name ||
25
+ (test_cmd ? test_cmd.to_s.strip.split.first : nil) ||
26
+ (format_cmd ? format_cmd.to_s.strip.split.first : nil) ||
27
+ (syntax_cmd ? syntax_cmd.to_s.strip.split.first : nil) ||
28
+ name
29
+ super(name, derived_bin)
30
+ @custom_test_cmd = test_cmd
31
+ @custom_format_cmd = format_cmd
32
+ @custom_syntax_cmd = syntax_cmd
33
+ end
34
+
35
+ # Validate a custom command string through the Security::Guard before it
36
+ # is ever executed. These commands are operator-supplied arbitrary shell
37
+ # commands; running them without classification is the exact RCE surface
38
+ # this adapter used to expose. Fail-closed: an unclassifiable or dangerous
39
+ # string raises BlockedWriteError rather than running. The injected
40
+ # session guard carries operator policy; the fresh fallback (silent
41
+ # audit, zero policy) serves standalone/test constructions only.
42
+ def validate_custom_command!(cmd_str, shell: nil)
43
+ return true if cmd_str.nil? || cmd_str.to_s.strip.empty?
44
+
45
+ (@validation_guard ||= Rubik::Security::Guard.new(audit_log_path: nil)).verify_command_safety!(cmd_str, shell: shell)
46
+ true
47
+ end
48
+
49
+ def available?
50
+ Runtime::Discovery.binary_available?(@binary_name)
51
+ end
52
+
53
+ def check_syntax(file_path)
54
+ return unavailable_result unless available?
55
+ return { ok: true, output: "Syntax check not configured for #{@name}", duration: 0.0, exit_code: 0 } unless @custom_syntax_cmd
56
+
57
+ # Classify + validate the operator-supplied syntax command through the
58
+ # Security::Guard before executing it (no bare process run).
59
+ validate_custom_command!(@custom_syntax_cmd, shell: nil)
60
+
61
+ cmd = if @custom_syntax_cmd.include?("%s")
62
+ format(@custom_syntax_cmd, Shellwords.escape(file_path))
63
+ else
64
+ "#{@custom_syntax_cmd} #{Shellwords.escape(file_path)}"
65
+ end
66
+ ProcessRunner.run(cmd)
67
+ end
68
+
69
+ protected
70
+
71
+ def default_test_cmd
72
+ @custom_test_cmd
73
+ end
74
+
75
+ def default_format_cmd(file_path)
76
+ return nil unless @custom_format_cmd
77
+
78
+ if @custom_format_cmd.include?("%s")
79
+ format(@custom_format_cmd, Shellwords.escape(file_path))
80
+ else
81
+ "#{@custom_format_cmd} #{Shellwords.escape(file_path)}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class GoAdapter < BaseAdapter
9
+ def initialize
10
+ super("go", "go")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("go vet #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ def compile(root_dir)
20
+ return unavailable_result unless available?
21
+
22
+ ProcessRunner.run("go build ./...", chdir: root_dir)
23
+ end
24
+
25
+ protected
26
+
27
+ def default_test_cmd
28
+ "go test ./..."
29
+ end
30
+
31
+ def default_format_cmd(file_path)
32
+ "gofmt -w #{Shellwords.escape(file_path)}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "shellwords"
5
+ require_relative "base"
6
+
7
+ module Rubik
8
+ module Adapter
9
+ class NodeAdapter < BaseAdapter
10
+ def initialize(name = "node", binary_name = "node")
11
+ super
12
+ end
13
+
14
+ def available?
15
+ !Rubik::Platform.which("node").nil? ||
16
+ !Rubik::Platform.which("npm").nil? ||
17
+ !Rubik::Platform.which("bun").nil?
18
+ end
19
+
20
+ def resolve_test_cmd(root_dir = Dir.pwd, path: nil)
21
+ base = if @custom_test_cmd && !@custom_test_cmd.strip.empty?
22
+ @custom_test_cmd
23
+ else
24
+ base_node_test_cmd(root_dir)
25
+ end
26
+ return base if path.nil? || path.to_s.strip.empty?
27
+
28
+ targeted_test_cmd(base, path.to_s.strip)
29
+ end
30
+
31
+ def base_node_test_cmd(root_dir)
32
+ runner = runner_for(root_dir)
33
+ pkg_file = File.join(root_dir, "package.json")
34
+
35
+ if File.exist?(pkg_file)
36
+ begin
37
+ parsed = JSON.parse(File.read(pkg_file))
38
+ scripts = parsed.is_a?(Hash) && parsed["scripts"].is_a?(Hash) ? parsed["scripts"] : {}
39
+
40
+ target_script = if scripts.key?("test:run")
41
+ "test:run"
42
+ elsif scripts.key?("test")
43
+ "test"
44
+ elsif scripts.key?("test:unit")
45
+ "test:unit"
46
+ end
47
+
48
+ if target_script
49
+ return target_script == "test" ? "#{runner} test" : "#{runner} run #{target_script}"
50
+ end
51
+ rescue JSON::ParserError, SystemCallError
52
+ # fallback to default
53
+ end
54
+ end
55
+
56
+ "#{runner} test"
57
+ end
58
+
59
+ def targeted_test_cmd(base_cmd, path)
60
+ clean_path = path.to_s.tr("\\", "/")
61
+ if base_cmd.include?("%s")
62
+ format(base_cmd, Shellwords.escape(clean_path))
63
+ elsif base_cmd =~ /(?:npm|pnpm|yarn)\s+(?:run\s+)?test/
64
+ "#{base_cmd} -- #{Shellwords.escape(clean_path)}"
65
+ else
66
+ "#{base_cmd} #{Shellwords.escape(clean_path)}"
67
+ end
68
+ end
69
+
70
+ def check_syntax(file_path)
71
+ return unavailable_result unless available?
72
+
73
+ ext = File.extname(file_path).downcase
74
+ escaped = Shellwords.escape(file_path)
75
+
76
+ cmd = case ext
77
+ when ".ts", ".tsx"
78
+ if Runtime::Discovery.binary_available?("tsc")
79
+ "tsc --noEmit #{escaped}"
80
+ elsif Runtime::Discovery.binary_available?("npx")
81
+ "npx tsc --noEmit #{escaped}"
82
+ else
83
+ "node --check #{escaped}"
84
+ end
85
+ when ".vue"
86
+ if Runtime::Discovery.binary_available?("vue-tsc")
87
+ "vue-tsc --noEmit"
88
+ elsif Runtime::Discovery.binary_available?("npx")
89
+ "npx vue-tsc --noEmit"
90
+ else
91
+ return unavailable_result(reason: "No syntax checker available for Vue files. Install vue-tsc.")
92
+ end
93
+ else
94
+ "node --check #{escaped}"
95
+ end
96
+
97
+ ProcessRunner.run(cmd)
98
+ end
99
+
100
+ def resolve_format_cmd(file_path)
101
+ if @custom_format_cmd && !@custom_format_cmd.strip.empty?
102
+ if @custom_format_cmd.include?("%s")
103
+ format(@custom_format_cmd, Shellwords.escape(file_path))
104
+ else
105
+ "#{@custom_format_cmd} #{Shellwords.escape(file_path)}"
106
+ end
107
+ else
108
+ escaped = Shellwords.escape(file_path)
109
+ dir = File.dirname(file_path)
110
+ discovered = Runtime::Discovery.discover_formatters(root_dir: dir, lang: @name)
111
+ if discovered.first
112
+ "#{discovered.first} #{escaped}"
113
+ elsif Runtime::Discovery.binary_available?("biome")
114
+ "biome format --write #{escaped}"
115
+ elsif Runtime::Discovery.binary_available?("prettier")
116
+ "prettier --write #{escaped}"
117
+ else
118
+ default_format_cmd(file_path)
119
+ end
120
+ end
121
+ end
122
+
123
+ protected
124
+
125
+ def runner_for(root_dir)
126
+ if File.exist?(File.join(root_dir, "pnpm-lock.yaml"))
127
+ "pnpm"
128
+ elsif File.exist?(File.join(root_dir, "yarn.lock"))
129
+ "yarn"
130
+ elsif File.exist?(File.join(root_dir, "bun.lockb"))
131
+ "bun"
132
+ else
133
+ "npm"
134
+ end
135
+ end
136
+
137
+ def default_test_cmd
138
+ "npm test"
139
+ end
140
+
141
+ def default_format_cmd(file_path)
142
+ "prettier --write #{Shellwords.escape(file_path)}"
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class ProcessRunner
9
+ def self.run(cmd, timeout: 15, chdir: Dir.pwd)
10
+ start_time = Time.now
11
+ stdout_str = ""
12
+ stderr_str = ""
13
+ exit_code = 127
14
+
15
+ pid = nil
16
+ begin
17
+ Timeout.timeout(timeout) do
18
+ Open3.popen3(cmd, chdir: chdir) do |stdin, stdout, stderr, wait_thr|
19
+ pid = wait_thr.pid
20
+ begin
21
+ stdin.close
22
+ rescue
23
+ nil
24
+ end
25
+ t_out = Thread.new do
26
+ stdout.read
27
+ rescue
28
+ ""
29
+ end
30
+ t_err = Thread.new do
31
+ stderr.read
32
+ rescue
33
+ ""
34
+ end
35
+ begin
36
+ stdout_str = t_out.value.to_s
37
+ stderr_str = t_err.value.to_s
38
+ status = wait_thr.value
39
+ exit_code = status.exitstatus || (status.signaled? ? (128 + (status.termsig || 1)) : 1)
40
+ ensure
41
+ begin
42
+ t_out.kill
43
+ rescue
44
+ nil
45
+ end
46
+ begin
47
+ t_err.kill
48
+ rescue
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+ rescue Timeout::Error
55
+ Rubik::Platform.kill_tree(pid) if pid
56
+ stderr_str = "Execution timed out after #{timeout} seconds"
57
+ exit_code = 124
58
+ rescue Errno::ENOENT => e
59
+ stderr_str = "Binary not found: #{e.message}"
60
+ exit_code = 127
61
+ rescue StandardError => e
62
+ stderr_str = "Execution error: #{e.message}"
63
+ exit_code = 1
64
+ end
65
+
66
+ duration = Time.now - start_time
67
+ {
68
+ ok: exit_code.zero?,
69
+ exit_code: exit_code,
70
+ stdout: stdout_str,
71
+ stderr: stderr_str,
72
+ output: [stdout_str, stderr_str].reject(&:empty?).join("\n"),
73
+ duration: duration.round(4)
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class PythonAdapter < BaseAdapter
9
+ def initialize
10
+ super("python", "python3")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("python3 -m py_compile #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ protected
20
+
21
+ def default_test_cmd
22
+ "pytest"
23
+ end
24
+
25
+ def default_format_cmd(file_path)
26
+ "ruff format #{Shellwords.escape(file_path)}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require_relative "ruby"
5
+ require_relative "python"
6
+ require_relative "go"
7
+ require_relative "rust"
8
+ require_relative "c"
9
+ require_relative "zig"
10
+ require_relative "node"
11
+ require_relative "crystal"
12
+ require_relative "generic"
13
+
14
+ module Rubik
15
+ module Adapter
16
+ class Registry
17
+ def initialize
18
+ @monitor = Monitor.new
19
+ node_adapter = NodeAdapter.new
20
+ @adapters = {
21
+ "ruby" => RubyAdapter.new,
22
+ "python" => PythonAdapter.new,
23
+ "go" => GoAdapter.new,
24
+ "rust" => RustAdapter.new,
25
+ "c" => CAdapter.new,
26
+ "zig" => ZigAdapter.new,
27
+ "crystal" => CrystalAdapter.new,
28
+ "node" => node_adapter,
29
+ "javascript" => node_adapter,
30
+ "typescript" => node_adapter,
31
+ "vue" => node_adapter
32
+ }
33
+ end
34
+
35
+ def get(lang_name)
36
+ @monitor.synchronize do
37
+ @adapters[lang_name.to_s.downcase]
38
+ end
39
+ end
40
+
41
+ def register(lang_name, adapter_instance, ctx: nil)
42
+ key = lang_name.to_s.downcase
43
+ restore = @monitor.synchronize do
44
+ prior = @adapters[key]
45
+ @adapters[key] = adapter_instance
46
+ lambda do
47
+ @monitor.synchronize do
48
+ next unless @adapters.key?(key) && @adapters[key].equal?(adapter_instance)
49
+
50
+ prior ? @adapters[key] = prior : @adapters.delete(key)
51
+ end
52
+ end
53
+ end
54
+ ctx&.on_dispose(&restore)
55
+ adapter_instance
56
+ end
57
+
58
+ def unregister(lang_name)
59
+ @monitor.synchronize do
60
+ @adapters.delete(lang_name.to_s.downcase)
61
+ end
62
+ end
63
+
64
+ # Atomic hot-swap of an adapter.
65
+ # Reentrant lock safety: Monitor#synchronize is explicitly reentrant (recursive),
66
+ # allowing safe unregister and register invocation within the same critical section.
67
+ def swap(lang_name, new_adapter)
68
+ @monitor.synchronize do
69
+ unregister(lang_name)
70
+ register(lang_name, new_adapter) if new_adapter
71
+ end
72
+ end
73
+
74
+ def list
75
+ @monitor.synchronize do
76
+ @adapters.keys.dup
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class RubyAdapter < BaseAdapter
9
+ def initialize
10
+ super("ruby", "ruby")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("ruby -c #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ protected
20
+
21
+ def default_test_cmd
22
+ "rake test"
23
+ end
24
+
25
+ def default_format_cmd(file_path)
26
+ "rubocop -a #{Shellwords.escape(file_path)}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class RustAdapter < BaseAdapter
9
+ def initialize
10
+ super("rust", "cargo")
11
+ end
12
+
13
+ def check_syntax(_file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("cargo check")
17
+ end
18
+
19
+ def compile(root_dir)
20
+ return unavailable_result unless available?
21
+
22
+ ProcessRunner.run("cargo build", chdir: root_dir)
23
+ end
24
+
25
+ protected
26
+
27
+ def default_test_cmd
28
+ "cargo test"
29
+ end
30
+
31
+ def default_format_cmd(file_path)
32
+ "rustfmt #{Shellwords.escape(file_path)}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class ZigAdapter < BaseAdapter
9
+ def initialize
10
+ super("zig", "zig")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("zig build-exe -fno-emit-bin #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ def compile(root_dir)
20
+ return unavailable_result unless available?
21
+
22
+ ProcessRunner.run("zig build", chdir: root_dir)
23
+ end
24
+
25
+ protected
26
+
27
+ def default_test_cmd
28
+ "zig test"
29
+ end
30
+
31
+ def default_format_cmd(file_path)
32
+ "zig fmt #{Shellwords.escape(file_path)}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "adapter/process"
4
+ require_relative "adapter/base"
5
+ require_relative "adapter/ruby"
6
+ require_relative "adapter/python"
7
+ require_relative "adapter/go"
8
+ require_relative "adapter/rust"
9
+ require_relative "adapter/c"
10
+ require_relative "adapter/zig"
11
+ require_relative "adapter/crystal"
12
+ require_relative "adapter/generic"
13
+ require_relative "adapter/registry"
14
+
15
+ module Rubik
16
+ module Adapter
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module AST
5
+ # Shared helper for extractors that defer a symbol's body slice.
6
+ #
7
+ # The factory MUST be a method: invoking it creates a fresh binding, so
8
+ # the returned lambda captures THIS call's argument values forever. A
9
+ # `->(s = line_num) {}` written inline in a `while` loop does NOT work —
10
+ # lambda default args are evaluated at CALL time, and a while body has no
11
+ # scope of its own, so every loader would resolve the loop's LAST
12
+ # line_num/end_line (regression: test_lazy_hash_and_lazy_body.rb).
13
+ module BodyLoader
14
+ def body_loader_for(start_line, end_line)
15
+ -> { extract_lines(start_line, end_line) }
16
+ end
17
+ end
18
+ end
19
+ end