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,302 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "monitor"
5
+ require "time"
6
+ require_relative "../platform"
7
+
8
+ module Rubik
9
+ module Runtime
10
+ # Bounded thread-safe ring buffer for capturing child process stdout/stderr
11
+ # without risk of unbounded memory growth (Plan 93).
12
+ class RingBuffer
13
+ attr_reader :capacity
14
+
15
+ def initialize(capacity = 500)
16
+ @capacity = [capacity.to_i, 1].max
17
+ @buffer = []
18
+ @monitor = Monitor.new
19
+ end
20
+
21
+ def push(line)
22
+ @monitor.synchronize do
23
+ @buffer << line.to_s
24
+ @buffer.shift while @buffer.size > @capacity
25
+ end
26
+ self
27
+ end
28
+ alias << push
29
+
30
+ def tail(count = 50)
31
+ @monitor.synchronize do
32
+ limit = [count.to_i, 0].max
33
+ @buffer.last(limit)
34
+ end
35
+ end
36
+
37
+ def lines
38
+ @monitor.synchronize { @buffer.dup }
39
+ end
40
+
41
+ def size
42
+ @monitor.synchronize { @buffer.size }
43
+ end
44
+
45
+ def empty?
46
+ @monitor.synchronize { @buffer.empty? }
47
+ end
48
+
49
+ def clear
50
+ @monitor.synchronize { @buffer.clear }
51
+ end
52
+ end
53
+
54
+ # Managed child process representation with lifecycle status and buffers.
55
+ Task = Struct.new(
56
+ :id, :command, :pid, :started_at, :ended_at, :status, :exit_code,
57
+ :ring_buffer, :stdin_io, :wait_thread, :collector_threads,
58
+ keyword_init: true
59
+ ) do
60
+ def running?
61
+ status == :running
62
+ end
63
+
64
+ def uptime
65
+ ((ended_at || Time.now) - started_at).to_f
66
+ end
67
+
68
+ def to_h
69
+ {
70
+ id: id, command: command, pid: pid, started_at: started_at,
71
+ ended_at: ended_at, status: status, exit_code: exit_code,
72
+ uptime_seconds: uptime.round(2)
73
+ }
74
+ end
75
+ end
76
+
77
+ # Manages asynchronous long-running background processes (servers, builders,
78
+ # watchers) with deadlock-immune streaming, AST guard verification, and
79
+ # context-mediated LIFO rollback (AGENTS.md §1.A/B, Plan 93).
80
+ class TaskManager
81
+ attr_reader :guard, :context, :root_dir, :max_buffer_lines
82
+ attr_accessor :init
83
+
84
+ @instances = []
85
+ @at_exit_installed = false
86
+
87
+ def self.register_instance(mgr)
88
+ @instances << mgr
89
+ return if @at_exit_installed
90
+
91
+ @at_exit_installed = true
92
+ at_exit do
93
+ @instances.each(&:kill_all)
94
+ end
95
+ end
96
+
97
+ def initialize(guard: nil, context: nil, root_dir: Dir.pwd, max_buffer_lines: 500, init: nil)
98
+ @tasks = {}
99
+ @task_counter = 0
100
+ @monitor = Monitor.new
101
+ @guard = guard
102
+ @context = context
103
+ @root_dir = File.expand_path(root_dir)
104
+ @max_buffer_lines = max_buffer_lines
105
+ @init = init
106
+ self.class.register_instance(self)
107
+ bind_context(@context)
108
+ end
109
+
110
+ def context=(ctx)
111
+ @context = ctx
112
+ bind_context(@context)
113
+ end
114
+
115
+ def start(command, chdir: @root_dir, ctx: nil)
116
+ cmd_str = command.to_s.strip
117
+ raise ArgumentError, "Command cannot be empty" if cmd_str.empty?
118
+
119
+ @guard&.verify_command_safety!(cmd_str)
120
+
121
+ # Each task carries its OWN child context, never the shared runtime
122
+ # root: Kernel::Init#reap_dead_units! disposes a dead unit's context
123
+ # by contract, so a root-carried ProcessUnit tears the whole runtime
124
+ # down when the task terminates (services wiped, every subagent
125
+ # cascade-killed) and the REPL then fails every turn with
126
+ # ContextDisposedError on the next root extend. Non-Rubik duck-typed
127
+ # hosts pass through unchanged (their #extend is Module#extend).
128
+ parent_ctx = ctx || @context
129
+ target_ctx = parent_ctx.is_a?(Rubik::Runtime::Context) ? parent_ctx.extend : parent_ctx
130
+
131
+ task_id = nil
132
+ @monitor.synchronize do
133
+ @task_counter += 1
134
+ task_id = "task-#{@task_counter}"
135
+ end
136
+
137
+ ring_buf = RingBuffer.new(@max_buffer_lines)
138
+ begin
139
+ stdin, stdout, stderr, wait_thr = Open3.popen3(cmd_str, chdir: chdir)
140
+ rescue StandardError => e
141
+ rollback_failed_start(target_ctx, nil, e)
142
+ raise e
143
+ end
144
+ stdin.binmode if stdin.respond_to?(:binmode)
145
+ stdin.sync = true if stdin.respond_to?(:sync=)
146
+
147
+ task = Task.new(
148
+ id: task_id,
149
+ command: cmd_str,
150
+ pid: wait_thr.pid,
151
+ started_at: Time.now,
152
+ ended_at: nil,
153
+ status: :running,
154
+ exit_code: nil,
155
+ ring_buffer: ring_buf,
156
+ stdin_io: stdin,
157
+ wait_thread: wait_thr,
158
+ collector_threads: []
159
+ )
160
+
161
+ task.collector_threads = []
162
+
163
+ # Register before the wiring below can fail: once tracked, the task
164
+ # stays reachable by kill/kill_all/at_exit and by the rollback
165
+ # rescue. The registration MUST also precede the on_dispose inverse:
166
+ # an inverse firing against an empty @tasks returns false and leaks
167
+ # the only handle to the spawned process. (Ids are never reused —
168
+ # @task_counter is monotonic — so the id-keyed inverse is exact.)
169
+ @monitor.synchronize { @tasks[task.id] = task }
170
+
171
+ begin
172
+ t_out = Thread.new do
173
+ stdout.each_line do |line|
174
+ ring_buf.push(line.chomp)
175
+ end
176
+ rescue IOError
177
+ nil
178
+ ensure
179
+ begin; stdout.close unless stdout.closed?; rescue IOError; nil; end
180
+ end
181
+
182
+ t_err = Thread.new do
183
+ stderr.each_line do |line|
184
+ ring_buf.push(line.chomp)
185
+ end
186
+ rescue IOError
187
+ nil
188
+ ensure
189
+ begin; stderr.close unless stderr.closed?; rescue IOError; nil; end
190
+ end
191
+
192
+ task.collector_threads = [t_out, t_err]
193
+
194
+ Thread.new do
195
+ status = wait_thr.value
196
+ @monitor.synchronize do
197
+ if task.status == :running
198
+ task.exit_code = status.exitstatus
199
+ task.status = status.success? ? :completed : :failed
200
+ task.ended_at = Time.now
201
+ end
202
+ end
203
+ effective_init = @init || (target_ctx.respond_to?(:get) && Rubik::Coeffects.get(target_ctx, Rubik::Coeffects::INIT))
204
+ effective_init&.get(task.id)&.sync_state!
205
+ begin; stdin.close unless stdin.closed?; rescue IOError; nil; end
206
+ end
207
+
208
+ target_ctx.on_dispose { kill(task.id) } if target_ctx.respond_to?(:on_dispose)
209
+
210
+ effective_init = @init || (target_ctx.respond_to?(:get) && Rubik::Coeffects.get(target_ctx, Rubik::Coeffects::INIT))
211
+ effective_init&.register(Rubik::Kernel::ProcessUnit.new(task, task_manager: self, context: target_ctx))
212
+ rescue StandardError => e
213
+ rollback_failed_start(target_ctx, task.id, e)
214
+ raise e
215
+ end
216
+
217
+ task
218
+ end
219
+
220
+ def list
221
+ @monitor.synchronize { @tasks.values.dup }
222
+ end
223
+
224
+ def get(task_id)
225
+ @monitor.synchronize { @tasks[task_id.to_s] }
226
+ end
227
+ alias [] get
228
+
229
+ def status(task_id)
230
+ task = get(task_id)
231
+ task&.to_h
232
+ end
233
+
234
+ def logs(task_id, tail: 50)
235
+ task = get(task_id)
236
+ task&.ring_buffer&.tail(tail)
237
+ end
238
+
239
+ def send_input(task_id, input)
240
+ @monitor.synchronize do
241
+ task = @tasks[task_id.to_s]
242
+ return false unless task && task.status == :running && task.stdin_io && !task.stdin_io.closed?
243
+
244
+ text = input.to_s
245
+ text += "\n" unless text.end_with?("\n")
246
+ task.stdin_io.write(text)
247
+ task.stdin_io.flush
248
+ true
249
+ rescue IOError, Errno::EPIPE
250
+ false
251
+ end
252
+ end
253
+
254
+ def kill(task_id)
255
+ @monitor.synchronize do
256
+ task = @tasks[task_id.to_s]
257
+ return false unless task
258
+
259
+ if task.status == :running
260
+ task.status = :killed
261
+ task.ended_at = Time.now
262
+ begin
263
+ task.stdin_io&.close unless task.stdin_io&.closed?
264
+ rescue IOError
265
+ nil
266
+ end
267
+ Rubik::Platform.kill_tree(task.pid) if task.pid
268
+ end
269
+ true
270
+ end
271
+ end
272
+
273
+ def kill_all
274
+ @monitor.synchronize do
275
+ @tasks.each_key { |id| kill(id) }
276
+ end
277
+ end
278
+ alias dispose kill_all
279
+
280
+ private
281
+
282
+ # Atomic rollback of a failed start (AGENTS.md §6): stop any process
283
+ # already spawned, dispose the per-task scope, and re-raise the
284
+ # ORIGINAL error — a rollback failure is reported (Log.warn), never
285
+ # allowed to mask what the caller should actually debug.
286
+ def rollback_failed_start(target_ctx, task_id, original)
287
+ kill(task_id) if task_id
288
+ begin
289
+ target_ctx.dispose if target_ctx.respond_to?(:dispose)
290
+ rescue StandardError => e
291
+ Rubik::Log.warn(:runtime, "task spawn rollback failed: #{e.class}: #{e.message} (original error: #{original.class}: #{original.message})")
292
+ end
293
+ end
294
+
295
+ def bind_context(ctx)
296
+ return unless ctx.respond_to?(:on_dispose)
297
+
298
+ ctx.on_dispose { kill_all }
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "runtime/context"
4
+ require_relative "runtime/fiber"
5
+ require_relative "runtime/manager"
6
+ require_relative "runtime/service_definition"
7
+ require_relative "runtime/service_registration"
8
+ require_relative "runtime/overlay_storage"
9
+ require_relative "runtime/discovery"
10
+ require_relative "runtime/diagnostic_engine"
11
+ require_relative "runtime/ast_service"
12
+ require_relative "runtime/index_service"
13
+ require_relative "runtime/loop_service"
14
+ require_relative "runtime/task_manager"
15
+
16
+ module Rubik
17
+ module Runtime
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Sandbox
5
+ module Driver
6
+ class Base
7
+ attr_reader :options
8
+
9
+ def initialize(options = {})
10
+ @options = options || {}
11
+ end
12
+
13
+ # Returns the short driver name derived from the class name.
14
+ #
15
+ # @return [String] the driver name (e.g. "docker" for DockerDriver)
16
+ def name
17
+ self.class.name.split("::").last.sub("Driver", "").downcase
18
+ end
19
+
20
+ def available?
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def run(cmd, timeout: 15, chdir: Dir.pwd, allow_network: false, memory_limit_mb: 512, cpus: "1.0")
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../platform"
5
+ require "open3"
6
+ require "timeout"
7
+
8
+ module Rubik
9
+ module Sandbox
10
+ module Driver
11
+ class BubblewrapDriver < Base
12
+ def name
13
+ "bwrap"
14
+ end
15
+
16
+ def available?
17
+ Rubik::Platform.linux? && !Rubik::Platform.which("bwrap").nil?
18
+ end
19
+
20
+ def run(cmd, timeout: 15, chdir: Dir.pwd, allow_network: false, memory_limit_mb: 512, cpus: "1.0") # rubocop:disable Lint/UnusedMethodArgument
21
+ start_time = Time.now
22
+ workspace = File.expand_path(chdir)
23
+
24
+ bwrap_cmd = [
25
+ "bwrap",
26
+ "--ro-bind", "/", "/",
27
+ "--dev", "/dev",
28
+ "--proc", "/proc",
29
+ "--tmpfs", "/tmp",
30
+ "--bind", workspace, workspace
31
+ ]
32
+
33
+ if @options[:allowed_dirs]
34
+ Array(@options[:allowed_dirs]).each do |dir|
35
+ expanded = File.expand_path(dir.to_s)
36
+ bwrap_cmd.push("--bind", expanded, expanded) if File.directory?(expanded)
37
+ end
38
+ end
39
+
40
+ bwrap_cmd.push("--chdir", workspace)
41
+ bwrap_cmd.push("--unshare-net") unless allow_network
42
+ bwrap_cmd.push("--unshare-pid", "--die-with-parent")
43
+ bwrap_cmd.push("sh", "-c", cmd)
44
+
45
+ stdout_str = ""
46
+ stderr_str = ""
47
+ exit_code = 127
48
+ pid = nil
49
+
50
+ begin
51
+ Timeout.timeout(timeout) do
52
+ Open3.popen3(*bwrap_cmd) do |stdin, stdout, stderr, wait_thr|
53
+ pid = wait_thr.pid
54
+ begin
55
+ stdin.close
56
+ rescue
57
+ nil
58
+ end
59
+ t_out = Thread.new do
60
+ stdout.read
61
+ rescue
62
+ ""
63
+ end
64
+ t_err = Thread.new do
65
+ stderr.read
66
+ rescue
67
+ ""
68
+ end
69
+ begin
70
+ stdout_str = t_out.value.to_s
71
+ stderr_str = t_err.value.to_s
72
+ status = wait_thr.value
73
+ exit_code = status.exitstatus || (status.signaled? ? (128 + (status.termsig || 1)) : 1)
74
+ ensure
75
+ begin
76
+ t_out.kill
77
+ rescue
78
+ nil
79
+ end
80
+ begin
81
+ t_err.kill
82
+ rescue
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+ rescue Timeout::Error
89
+ Rubik::Platform.kill_tree(pid) if pid
90
+ stderr_str = "Bubblewrap execution timed out after #{timeout} seconds"
91
+ exit_code = 124
92
+ rescue StandardError => e
93
+ stderr_str = "Bubblewrap execution error: #{e.message}"
94
+ exit_code = 1
95
+ end
96
+
97
+ duration = Time.now - start_time
98
+ {
99
+ ok: exit_code.zero?,
100
+ exit_code: exit_code,
101
+ stdout: stdout_str,
102
+ stderr: stderr_str,
103
+ output: [stdout_str, stderr_str].reject(&:empty?).join("\n"),
104
+ duration: duration.round(4),
105
+ driver: "bwrap"
106
+ }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../platform"
5
+ require "open3"
6
+ require "timeout"
7
+
8
+ require "securerandom"
9
+
10
+ module Rubik
11
+ module Sandbox
12
+ module Driver
13
+ class DockerDriver < Base
14
+ DEFAULT_IMAGE = "rubik-sandbox:latest"
15
+
16
+ def available?
17
+ !Rubik::Platform.which("docker").nil? && system("docker ps > #{Rubik::Platform.dev_null} 2>&1")
18
+ end
19
+
20
+ def run(cmd, timeout: 15, chdir: Dir.pwd, allow_network: false, memory_limit_mb: 512, cpus: "1.0")
21
+ start_time = Time.now
22
+ image = @options[:image] || DEFAULT_IMAGE
23
+ workspace = File.expand_path(chdir)
24
+ container_name = "rubik-box-#{SecureRandom.hex(6)}"
25
+
26
+ docker_args = [
27
+ "docker", "run", "--rm",
28
+ "--name", container_name,
29
+ "--read-only",
30
+ "--cap-drop", "ALL",
31
+ "--security-opt", "no-new-privileges",
32
+ "--tmpfs", "/tmp:rw,noexec,nosuid",
33
+ "-w", "/workspace",
34
+ "-v", "#{workspace}:/workspace:rw",
35
+ "--memory", "#{memory_limit_mb}m",
36
+ "--cpus", cpus.to_s,
37
+ "--pids-limit", "100"
38
+ ]
39
+
40
+ docker_args.push("--network", "none") unless allow_network
41
+ docker_args.push(image, "sh", "-c", cmd)
42
+
43
+ stdout_str = ""
44
+ stderr_str = ""
45
+ exit_code = 127
46
+ wait_thr_ref = nil
47
+
48
+ begin
49
+ Timeout.timeout(timeout) do
50
+ Open3.popen3(*docker_args) do |stdin, stdout, stderr, wait_thr|
51
+ wait_thr_ref = wait_thr
52
+ begin
53
+ stdin.close
54
+ rescue StandardError
55
+ nil
56
+ end
57
+ t_out = Thread.new do
58
+ stdout.read
59
+ rescue StandardError
60
+ ""
61
+ end
62
+ t_err = Thread.new do
63
+ stderr.read
64
+ rescue StandardError
65
+ ""
66
+ end
67
+ begin
68
+ stdout_str = t_out.value.to_s
69
+ stderr_str = t_err.value.to_s
70
+ status = wait_thr.value
71
+ exit_code = status.exitstatus || (status.signaled? ? (128 + (status.termsig || 1)) : 1)
72
+ ensure
73
+ begin
74
+ t_out.kill
75
+ rescue StandardError
76
+ nil
77
+ end
78
+ begin
79
+ t_err.kill
80
+ rescue StandardError
81
+ nil
82
+ end
83
+ end
84
+ end
85
+ end
86
+ rescue Timeout::Error
87
+ Rubik::Platform.kill_tree(wait_thr_ref.pid) if wait_thr_ref.respond_to?(:pid) && wait_thr_ref.pid
88
+ system("docker rm -f #{container_name} > #{Rubik::Platform.dev_null} 2>&1")
89
+ stderr_str = "Docker sandbox execution timed out after #{timeout} seconds"
90
+ exit_code = 124
91
+ rescue StandardError => e
92
+ stderr_str = "Docker sandbox error: #{e.message}"
93
+ exit_code = 1
94
+ ensure
95
+ Rubik::Platform.kill_tree(wait_thr_ref.pid) if wait_thr_ref.respond_to?(:pid) && wait_thr_ref.pid
96
+ system("docker rm -f #{container_name} > #{Rubik::Platform.dev_null} 2>&1")
97
+ end
98
+
99
+ duration = Time.now - start_time
100
+ {
101
+ ok: exit_code.zero?,
102
+ exit_code: exit_code,
103
+ stdout: stdout_str,
104
+ stderr: stderr_str,
105
+ output: [stdout_str, stderr_str].reject(&:empty?).join("\n"),
106
+ duration: duration.round(4),
107
+ driver: "docker"
108
+ }
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../platform"
5
+ require "open3"
6
+ require "timeout"
7
+
8
+ module Rubik
9
+ module Sandbox
10
+ module Driver
11
+ class PodmanDriver < Base
12
+ DEFAULT_IMAGE = "rubik-sandbox:latest"
13
+
14
+ def available?
15
+ !Rubik::Platform.which("podman").nil?
16
+ end
17
+
18
+ def run(cmd, timeout: 15, chdir: Dir.pwd, allow_network: false, memory_limit_mb: 512, cpus: "1.0")
19
+ start_time = Time.now
20
+ image = @options[:image] || DEFAULT_IMAGE
21
+ workspace = File.expand_path(chdir)
22
+
23
+ podman_args = [
24
+ "podman", "run", "--rm",
25
+ "-w", "/workspace",
26
+ "-v", "#{workspace}:/workspace:rw",
27
+ "--memory", "#{memory_limit_mb}m",
28
+ "--cpus", cpus.to_s,
29
+ "--pids-limit", "100"
30
+ ]
31
+
32
+ podman_args.push("--network", "none") unless allow_network
33
+ podman_args.push(image, "sh", "-c", cmd)
34
+
35
+ stdout_str = ""
36
+ stderr_str = ""
37
+ exit_code = 127
38
+
39
+ begin
40
+ Timeout.timeout(timeout) do
41
+ Open3.popen3(*podman_args) do |stdin, stdout, stderr, wait_thr|
42
+ begin
43
+ stdin.close
44
+ rescue
45
+ nil
46
+ end
47
+ t_out = Thread.new do
48
+ stdout.read
49
+ rescue
50
+ ""
51
+ end
52
+ t_err = Thread.new do
53
+ stderr.read
54
+ rescue
55
+ ""
56
+ end
57
+ begin
58
+ stdout_str = t_out.value.to_s
59
+ stderr_str = t_err.value.to_s
60
+ status = wait_thr.value
61
+ exit_code = status.exitstatus || (status.signaled? ? (128 + (status.termsig || 1)) : 1)
62
+ ensure
63
+ begin
64
+ t_out.kill
65
+ rescue
66
+ nil
67
+ end
68
+ begin
69
+ t_err.kill
70
+ rescue
71
+ nil
72
+ end
73
+ end
74
+ end
75
+ end
76
+ rescue Timeout::Error
77
+ stderr_str = "Podman sandbox execution timed out after #{timeout} seconds"
78
+ exit_code = 124
79
+ rescue StandardError => e
80
+ stderr_str = "Podman sandbox error: #{e.message}"
81
+ exit_code = 1
82
+ end
83
+
84
+ duration = Time.now - start_time
85
+ {
86
+ ok: exit_code.zero?,
87
+ exit_code: exit_code,
88
+ stdout: stdout_str,
89
+ stderr: stderr_str,
90
+ output: [stdout_str, stderr_str].reject(&:empty?).join("\n"),
91
+ duration: duration.round(4),
92
+ driver: "podman"
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end