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,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Platform
5
+ # Path resolution, normalization, URI conversion and path safety checks.
6
+ # Extracted from lib/rubik/platform.rb (Plan 86 D-4).
7
+ module Path
8
+ def self.included(base)
9
+ base.extend self
10
+ end
11
+
12
+ # Pure-Ruby cross-platform binary resolver
13
+ def which(cmd)
14
+ return nil if cmd.nil? || cmd.to_s.strip.empty?
15
+
16
+ cmd_str = cmd.to_s.strip.split.first
17
+ return nil if cmd_str.nil? || cmd_str.empty?
18
+
19
+ # Windows shell builtins (e.g. echo, dir, cls)
20
+ return cmd_str if windows? && Rubik::Platform::WINDOWS_SHELL_BUILTINS.include?(cmd_str.downcase)
21
+
22
+ # If full/relative path is provided
23
+ if cmd_str.include?("/") || (windows? && cmd_str.include?("\\"))
24
+ return File.expand_path(cmd_str).gsub("\\", "/") if File.file?(cmd_str) && executable_file?(cmd_str)
25
+
26
+ if windows?
27
+ pathext.each do |ext|
28
+ candidate = "#{cmd_str}#{ext}"
29
+ return File.expand_path(candidate).gsub("\\", "/") if File.file?(candidate) && executable_file?(candidate)
30
+ end
31
+ end
32
+ return nil
33
+ end
34
+
35
+ # Search in PATH
36
+ paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR)
37
+ exts = if windows?
38
+ ext = File.extname(cmd_str)
39
+ ext.empty? ? pathext : ([""] + pathext).uniq
40
+ else
41
+ [""]
42
+ end
43
+
44
+ paths.each do |dir|
45
+ next if dir.empty? || !File.directory?(dir)
46
+
47
+ exts.each do |ext|
48
+ target = File.join(dir, "#{cmd_str}#{ext}")
49
+ return File.expand_path(target).gsub("\\", "/") if File.file?(target) && executable_file?(target)
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def pathext
56
+ (ENV["PATHEXT"] || ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC;.PS1").split(";").map(&:downcase)
57
+ end
58
+
59
+ def executable_file?(path)
60
+ return false unless File.file?(path)
61
+
62
+ windows? || File.executable?(path)
63
+ end
64
+
65
+ # RFC 8089 Compliant File URI (e.g. file:///C:/path/file vs file:///path/file)
66
+ def to_file_uri(path)
67
+ normalized = normalize_path_raw(path)
68
+ if windows? && normalized =~ %r{^([a-zA-Z]):/(.*)$}
69
+ "file:///#{::Regexp.last_match(1)}:/#{::Regexp.last_match(2)}"
70
+ else
71
+ normalized.start_with?("/") ? "file://#{normalized}" : "file:///#{normalized}"
72
+ end
73
+ end
74
+
75
+ def from_file_uri(uri)
76
+ str = uri.to_s.sub(%r{^file://}, "")
77
+ str = CGI.unescape(str)
78
+ if windows?
79
+ # Handles file:///C:/... and file://C:/...
80
+ if str =~ %r{^/?([a-zA-Z]:/.*)$}
81
+ ::Regexp.last_match(1)
82
+ else
83
+ str
84
+ end
85
+ else
86
+ str.start_with?("/") ? str : "/#{str}"
87
+ end
88
+ end
89
+
90
+ def normalize_path_raw(path)
91
+ expanded = File.expand_path(path.to_s)
92
+ expanded.gsub("\\", "/")
93
+ end
94
+
95
+ # Normalizes separators and casing for comparison
96
+ def normalize_path(path)
97
+ raw = normalize_path_raw(path)
98
+ windows? ? raw.downcase : raw
99
+ end
100
+
101
+ def path_equal?(lhs, rhs)
102
+ normalize_path(lhs) == normalize_path(rhs)
103
+ end
104
+
105
+ def path_under?(child, parent)
106
+ n_child = normalize_path(child)
107
+ n_parent = normalize_path(parent)
108
+ return true if n_child == n_parent
109
+
110
+ clean_parent = n_parent.chomp("/")
111
+ clean_child = n_child.chomp("/")
112
+ return true if clean_child == clean_parent
113
+
114
+ n_child.start_with?("#{clean_parent}/")
115
+ end
116
+
117
+ # Realpath for security-boundary containment decisions ONLY (stored
118
+ # buffer keys and every other comparison stay lexical). The deepest
119
+ # EXISTING ancestor is resolved and the not-yet-existing tail is
120
+ # re-attached lexically — a plain missing component cannot be a
121
+ # symlink, and File.expand_path has already collapsed ".." lexically.
122
+ # Symlink components (live or dangling) stop the walk: a dangling link
123
+ # must not be reattached as if it could never resolve.
124
+ # If strict: true (used in containment checks), IO resolution failure
125
+ # returns nil so resolved_path_under? fails closed (unprovable containment).
126
+ # If strict: false, falls back to lexical path for display/diagnostics.
127
+ def resolved_path(path, strict: false)
128
+ expanded = File.expand_path(path.to_s)
129
+ begin
130
+ File.realpath(expanded)
131
+ rescue Errno::ENOENT, Errno::ENOTDIR
132
+ begin
133
+ dir = expanded
134
+ tail = []
135
+ # File.symlink? in the walk condition: a DANGLING link reads as
136
+ # "missing" to File.exist?, but the OS write that follows DOES
137
+ # traverse it — stop there and let realpath's ENOENT fail closed
138
+ # (strict) instead of reattaching it lexically as if it could
139
+ # never be a link.
140
+ until dir == File.dirname(dir) || File.exist?(dir) || File.symlink?(dir)
141
+ tail.unshift(File.basename(dir))
142
+ dir = File.dirname(dir)
143
+ end
144
+ base = File.realpath(dir)
145
+ tail.empty? ? base : File.join(base, *tail)
146
+ rescue SystemCallError => e
147
+ debug_warn "resolved_path: ancestor walk failed for #{expanded}: #{e.message}"
148
+ return nil if strict
149
+
150
+ expanded
151
+ end
152
+ rescue SystemCallError => e
153
+ debug_warn "resolved_path: resolution failed for #{expanded}: #{e.message}"
154
+ return nil if strict
155
+
156
+ expanded
157
+ end
158
+ end
159
+
160
+ # Best-effort variant for read-only / display / diagnostics (never fails closed)
161
+ def resolved_path_best_effort(path)
162
+ resolved_path(path, strict: false)
163
+ end
164
+
165
+ # Drop-in twin of path_under? that resolves symlinks/junctions on BOTH
166
+ # sides before the prefix comparison. Security boundaries (Buffer
167
+ # containment, Sandbox chdir checks) must use this variant.
168
+ # Fail-closed invariant (W1): IO/permission failure returns false (containment unprovable).
169
+ def resolved_path_under?(child, parent)
170
+ r_child = resolved_path(child, strict: true)
171
+ return false if r_child.nil?
172
+
173
+ r_parent = resolved_path(parent, strict: true)
174
+ return false if r_parent.nil?
175
+
176
+ path_under?(r_child, r_parent)
177
+ end
178
+
179
+ def dangerous_system_roots
180
+ roots = ["/", "/root", "/home", "/etc", "/usr", "/var", "/bin", "/sbin", "/lib", "/lib64", "/opt", "/boot", "/dev", "/proc", "/sys"]
181
+ if windows?
182
+ # Add expanded Unix root equivalents on current drive (e.g. C:/etc, C:/usr)
183
+ roots += roots.map { |r| File.expand_path(r) }
184
+ roots += ["c:/", "c:/windows", "c:/windows/system32", "c:/program files", "c:/program files (x86)", "c:/users", "c:/programdata"]
185
+ roots << ENV["SystemRoot"] if ENV["SystemRoot"]
186
+ roots << ENV["ProgramFiles"] if ENV["ProgramFiles"]
187
+ roots << ENV["ProgramFiles(x86)"] if ENV["ProgramFiles(x86)"]
188
+ roots << ENV["CommonProgramFiles"] if ENV["CommonProgramFiles"]
189
+ roots << ENV["USERPROFILE"] if ENV["USERPROFILE"]
190
+ roots << "#{ENV['SystemDrive']}/" if ENV["SystemDrive"]
191
+ roots << ENV["SystemDrive"] if ENV["SystemDrive"]
192
+ end
193
+ roots << home_dir
194
+ roots.compact.map { |r| normalize_path(r) }.uniq
195
+ end
196
+
197
+ def drive_root?(path)
198
+ return false unless windows?
199
+
200
+ # Purely lexical: a bare drive-relative "D:" expands to the CURRENT
201
+ # directory on that drive, so expanding first would make "is this a
202
+ # drive root?" depend on the process cwd (which drive the workspace
203
+ # lives on). Security callers pass already-expanded absolute paths;
204
+ # compare the literal form here.
205
+ cleaned = path.to_s.tr("\\", "/").downcase.sub(%r{/+\z}, "")
206
+ cleaned =~ /^[a-z]:$/
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Platform
5
+ # Process and environment management (e.g. kill_tree).
6
+ # Extracted from lib/rubik/platform.rb (Plan 86 D-4).
7
+ module Process
8
+ def self.included(base)
9
+ base.extend self
10
+ end
11
+
12
+ def kill_tree(pid)
13
+ return unless pid&.positive?
14
+
15
+ if windows?
16
+ begin
17
+ # Plan 55 D-1 ile tutarlı dizi-form çağrı: cmd.exe ara katmanı ve kabuk
18
+ # yönlendirmesi ("> NUL 2>&1") yerine out:/err: seçenekleri kullanılır.
19
+ system("taskkill", "/F", "/T", "/PID", pid.to_s, out: File::NULL, err: File::NULL)
20
+ rescue
21
+ nil
22
+ end
23
+ else
24
+ begin
25
+ ::Process.kill("TERM", pid)
26
+ rescue
27
+ nil
28
+ end
29
+ begin
30
+ ::Process.kill("KILL", pid)
31
+ rescue
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Platform
5
+ # Encoding and JSON hygiene (UTF-8 sanitation, safe JSON generation, XML escaping).
6
+ # This is the growth family that drove platform.rb over 300 lines.
7
+ # Extracted from lib/rubik/platform.rb (Plan 86 D-4).
8
+ module Sanitize
9
+ def self.included(base)
10
+ base.extend self
11
+ end
12
+
13
+ # Escapes a value for safe interpolation into XML text nodes and
14
+ # attribute values. Used by tool-result envelopes (<tool_result>) and
15
+ # the system-context XML so untrusted content (file contents, paths,
16
+ # user prompts) can neither break the envelope nor smuggle instructions.
17
+ def xml_escape(value)
18
+ CGI.escapeHTML(value.to_s)
19
+ rescue StandardError
20
+ ""
21
+ end
22
+
23
+ # Deeply sanitizes invalid or malformed UTF-8 byte sequences from a string
24
+ def clean_utf8(str)
25
+ return str unless str.is_a?(String)
26
+ return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
27
+
28
+ if str.valid_encoding?
29
+ begin
30
+ return str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "")
31
+ rescue StandardError => e
32
+ debug_warn("clean_utf8 encode fallback: #{e.class}: #{e.message}")
33
+ # fallback below
34
+ end
35
+ end
36
+
37
+ str.to_s.dup.force_encoding(Encoding::UTF_8).scrub("")
38
+ rescue StandardError => e
39
+ debug_warn("clean_utf8 scrub fallback: #{e.class}: #{e.message}")
40
+ begin
41
+ str.to_s.scrub("")
42
+ rescue
43
+ ""
44
+ end
45
+ end
46
+
47
+ # UTF-8 hygiene for strings whose encoding TAG is untrusted but whose
48
+ # bytes are expected to be UTF-8: HTTP bodies served without a charset
49
+ # arrive from Net::HTTP tagged ASCII-8BIT, and so does process/pipe
50
+ # output. Re-tags the string as UTF-8 and scrubs invalid sequences,
51
+ # PRESERVING valid multibyte content. clean_utf8 is the wrong tool for
52
+ # this: its encode step treats bytes >= 0x80 as undefined when
53
+ # converting FROM binary and silently strips them.
54
+ def safe_utf8(str)
55
+ return str unless str.is_a?(String)
56
+ return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
57
+
58
+ retagged = str.dup.force_encoding(Encoding::UTF_8)
59
+ retagged.valid_encoding? ? retagged : retagged.scrub("")
60
+ end
61
+
62
+ # Recursively sanitizes hashes, arrays, symbols, and strings for safe UTF-8 encoding
63
+ def clean_utf8_deep(obj, seen = nil)
64
+ case obj
65
+ when String
66
+ clean_utf8(obj)
67
+ when Hash
68
+ seen ||= Set.new
69
+ return "{...}" if seen.include?(obj.object_id)
70
+
71
+ seen.add(obj.object_id)
72
+
73
+ obj.each_with_object({}) do |(k, v), acc|
74
+ cleaned_k = if k.is_a?(String)
75
+ clean_utf8(k)
76
+ else
77
+ (k.is_a?(Symbol) ? clean_utf8(k.to_s).to_sym : clean_utf8_deep(k, seen))
78
+ end
79
+ acc[cleaned_k] = clean_utf8_deep(v, seen)
80
+ end
81
+ when Array
82
+ seen ||= Set.new
83
+ return "[...]" if seen.include?(obj.object_id)
84
+
85
+ seen.add(obj.object_id)
86
+
87
+ obj.map { |item| clean_utf8_deep(item, seen) }
88
+ when Set
89
+ seen ||= Set.new
90
+ return Set.new(["[...]"]) if seen.include?(obj.object_id)
91
+
92
+ seen.add(obj.object_id)
93
+
94
+ obj.to_set { |item| clean_utf8_deep(item, seen) }
95
+ when Symbol
96
+ clean_utf8(obj.to_s).to_sym
97
+ else
98
+ obj
99
+ end
100
+ end
101
+
102
+ # Generates JSON safely without raising JSON::GeneratorError on malformed UTF-8
103
+ def safe_json_generate(obj, *)
104
+ cleaned = clean_utf8_deep(obj)
105
+ JSON.generate(cleaned, *)
106
+ rescue StandardError => e
107
+ debug_warn("safe_json_generate fallback: #{e.class}: #{e.message}")
108
+ fallback = sanitize_for_json_fallback(obj)
109
+ begin
110
+ JSON.generate(fallback, *)
111
+ rescue
112
+ "{}"
113
+ end
114
+ end
115
+
116
+ # Dumps JSON safely without raising JSON::GeneratorError on malformed UTF-8
117
+ def safe_json_dump(obj, *)
118
+ cleaned = clean_utf8_deep(obj)
119
+ JSON.dump(cleaned, *)
120
+ rescue StandardError => e
121
+ debug_warn("safe_json_dump fallback: #{e.class}: #{e.message}")
122
+ fallback = sanitize_for_json_fallback(obj)
123
+ begin
124
+ JSON.dump(fallback, *)
125
+ rescue
126
+ "{}"
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def sanitize_for_json_fallback(obj)
133
+ case obj
134
+ when Hash
135
+ obj.each_with_object({}) do |(k, v), acc|
136
+ acc[clean_utf8(k.to_s)] = sanitize_for_json_fallback(v)
137
+ end
138
+ when Array, Set
139
+ obj.map { |item| sanitize_for_json_fallback(item) }
140
+ when String
141
+ clean_utf8(obj)
142
+ when Numeric, TrueClass, FalseClass, NilClass
143
+ obj
144
+ else
145
+ clean_utf8(obj.to_s)
146
+ end
147
+ rescue StandardError => e
148
+ debug_warn("sanitize_for_json_fallback rescue: #{e.class}: #{e.message}")
149
+ begin
150
+ obj.to_s.scrub("")
151
+ rescue
152
+ ""
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "tmpdir"
5
+ require "fileutils"
6
+ require "cgi"
7
+ require "json"
8
+
9
+ require_relative "platform/path"
10
+ require_relative "platform/process"
11
+ require_relative "platform/filesystem"
12
+ require_relative "platform/sanitize"
13
+ require_relative "platform/io_drain"
14
+
15
+ module Rubik
16
+ module Platform
17
+ # Common constants shared across sub-modules
18
+ WINDOWS_SHELL_BUILTINS = [
19
+ "echo", "dir", "cd", "cls", "set", "type", "copy", "move", "del", "md", "rd", "exit", "rem", "ver", "vol"
20
+ ].freeze
21
+
22
+ class << self
23
+ # Audit 1: gated debug logging for the platform layer's many intentional
24
+ # rescue-and-fallback paths (UTF-8 sanitation, atomic writes). Follows
25
+ # the codebase's ENV["RUBIK_DEBUG"] convention (see config/store.rb) —
26
+ # silent in normal operation, diagnosable on demand, never raises.
27
+ #
28
+ # $stderr.puts, not Kernel#warn: warn is silenced when $VERBOSE is nil
29
+ # (bundler/rake -W0) — the exact context these diagnostics must survive.
30
+ # Same ruling as Log.emit.
31
+ def debug_warn(msg)
32
+ $stderr.puts "[WARN] #{msg}" if ENV["RUBIK_DEBUG"] # rubocop:disable Style/StderrPuts
33
+ rescue StandardError
34
+ nil
35
+ end
36
+
37
+ # Boot/warm async-knob resolution with test-armor defaulting (arch
38
+ # 2026-10-08 #12): an explicit true/false config value always wins;
39
+ # nil (unset) falls back to the RUBIK_TEST env default — RUBIK_TEST=1
40
+ # resolves false so tests warm/boot synchronously. Consumers:
41
+ # repl/lifecycle.rb#warm_up! (ast.async_warm, mcp.async_load).
42
+ def async_knob(config, key)
43
+ value = config.fetch(key, nil)
44
+ value = ENV["RUBIK_TEST"] != "1" if value.nil?
45
+ value
46
+ end
47
+
48
+ def host_os
49
+ RbConfig::CONFIG["host_os"] || ""
50
+ end
51
+
52
+ def ruby_platform
53
+ RUBY_PLATFORM
54
+ end
55
+
56
+ def os_family
57
+ if windows?
58
+ :windows
59
+ elsif macos?
60
+ :macos
61
+ elsif linux?
62
+ :linux
63
+ else
64
+ :unknown
65
+ end
66
+ end
67
+
68
+ def windows?
69
+ Gem.win_platform? || !(host_os =~ /mswin|mingw|cygwin|bccwin|wince/i).nil?
70
+ end
71
+
72
+ def macos?
73
+ (host_os =~ /darwin/i) != nil
74
+ end
75
+
76
+ def linux?
77
+ !windows? && !macos?
78
+ end
79
+
80
+ def unix?
81
+ !windows?
82
+ end
83
+
84
+ def dev_null
85
+ File::NULL
86
+ end
87
+
88
+ def home_dir
89
+ Dir.home
90
+ rescue
91
+ (ENV["USERPROFILE"] || (ENV.fetch("HOMEDRIVE",
92
+ nil) && ENV.fetch("HOMEPATH",
93
+ nil) && "#{ENV.fetch('HOMEDRIVE',
94
+ nil)}#{ENV.fetch('HOMEPATH', nil)}") || ENV["HOME"] || ".")
95
+ end
96
+
97
+ def tmp_dir
98
+ Dir.tmpdir
99
+ rescue
100
+ (ENV["TEMP"] || ENV["TMP"] || "/tmp")
101
+ end
102
+ end
103
+
104
+ # Include all sub-modules so the singleton class (class << self) gains
105
+ # their methods via extend in the included hook. Each sub-module
106
+ # `extend`s itself when included, making its methods available as
107
+ # `Rubik::Platform.<method>` for backward compatibility.
108
+ include Platform::Path
109
+ include Platform::Process
110
+ include Platform::FileSystem
111
+ include Platform::Sanitize
112
+ end
113
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Plugin
5
+ class Definition
6
+ attr_reader :name, :version, :description, :inject, :requires, :factory_block
7
+
8
+ def initialize(name, version: "1.0.0", description: "", inject: [], requires: [], &block)
9
+ @name = name
10
+ @version = version
11
+ @description = description
12
+ @inject = Array(inject)
13
+ @requires = Array(requires)
14
+ @factory_block = block
15
+ end
16
+ end
17
+
18
+ class Registry
19
+ @definitions = {}
20
+
21
+ class << self
22
+ attr_reader :definitions
23
+
24
+ def register(definition)
25
+ @definitions[definition.name.to_s] = definition
26
+ end
27
+
28
+ def get(name)
29
+ @definitions[name.to_s]
30
+ end
31
+
32
+ def clear!
33
+ @definitions.clear
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Top level DSL
40
+ def self.plugin(name, version: "1.0.0", description: "", inject: [], requires: [], &)
41
+ definition = Plugin::Definition.new(
42
+ name,
43
+ version: version,
44
+ description: description,
45
+ inject: inject,
46
+ requires: requires,
47
+ &
48
+ )
49
+ Plugin::Registry.register(definition)
50
+ end
51
+ end