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
data/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # 🎲 Rubik — Autonomous AI Coding Agent & REPL Harness
2
+
3
+ English | [Türkçe](README.tr.md)
4
+
5
+ > A next-generation autonomous pair-programming and REPL harness written in pure Ruby, built on the **Spatiotemporal Composability & Self-Evolving Runtime** architecture.
6
+
7
+ [![Gem Version](https://img.shields.io/gem/v/rubik.svg)](https://rubygems.org/gems/rubik)
8
+ [![Gem Downloads](https://img.shields.io/gem/dt/rubik.svg)](https://rubygems.org/gems/rubik)
9
+ [![Ruby Version](https://img.shields.io/badge/ruby-3.4%2B-red.svg)](https://www.ruby-lang.org)
10
+ [![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-blue.svg)]()
11
+ [![Protocol](https://img.shields.io/badge/protocol-AGENTS.md-blue.svg)](AGENTS.md)
12
+ [![Commands Guide](https://img.shields.io/badge/docs-COMMANDS.md-yellow.svg)](COMMANDS.md)
13
+ [![Config Guide](https://img.shields.io/badge/docs-CONFIG.md-green.svg)](CONFIG.md)
14
+ [![Test Suite](https://img.shields.io/badge/tests-3202%20runs%20%7C%20100%25%20passed-brightgreen.svg)]()
15
+
16
+ ---
17
+
18
+ ## 👋 What is Rubik?
19
+
20
+ Rubik is a terminal-native autonomous coding agent and interactive REPL in pure Ruby. A **maestro** agent orchestrates specialized subagents (research / coder / self) over a 34-tool belt while you keep the wheel: file edits stage in a reviewable in-memory buffer (`!diff` → `!apply`), every runtime side effect is registered with a LIFO inverse (zero-restart reloads and teardown), and failing services are diagnosed and repaired live. It runs cross-platform (Linux, macOS, Windows), talks to any OpenAI-compatible endpoint, Anthropic, or local Ollama, and the whole runtime is embeddable from Ruby with `require "rubik"`.
21
+
22
+ ---
23
+
24
+ ## 🚀 Quick Start
25
+
26
+ ### From the gem
27
+
28
+ Requires **Ruby 3.4+**:
29
+
30
+ ```bash
31
+ gem install rubik
32
+ ```
33
+
34
+ or with Bundler:
35
+
36
+ ```ruby
37
+ gem "rubik"
38
+ ```
39
+
40
+ #### Platform notes
41
+
42
+ | Platform | Install experience |
43
+ | :--- | :--- |
44
+ | 🐧 Linux / 🍎 macOS | `gem install rubik` — dependencies ship prebuilt. |
45
+ | 🪟 Windows | `tree_sitter_language_pack` has no Windows prebuilt and compiles from source: install Rust with the GNU toolchain and export `RUST_TARGET=x86_64-pc-windows-gnu` before installing. The exact, working recipe is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). |
46
+
47
+ ### From source
48
+
49
+ **Prerequisites**
50
+
51
+ - **Ruby:** 3.4.0 or later (`ruby -v`)
52
+ - **Linux / macOS:** Via the system package manager, `rbenv`, `rvm`, or `asdf`.
53
+ - **Windows:** [Scoop](https://scoop.sh) (`scoop install ruby`) or [RubyInstaller with DevKit](https://rubyinstaller.org).
54
+ - **Git:** Git 2.30+ (`git -v`)
55
+ - **LLM Provider:** Local [Ollama](https://ollama.ai) (`ollama run qwen2.5-coder:7b`) or a DeepSeek, OpenRouter, Anthropic, or OpenAI API key.
56
+
57
+ Clone the project, install the dependencies, and start the REPL:
58
+
59
+ ```bash
60
+ git clone https://github.com/ozgurulukir/rubik.git
61
+ cd rubik
62
+ bundle install
63
+ ```
64
+
65
+ #### 🐧 Linux & 🍎 macOS (POSIX)
66
+
67
+ ```bash
68
+ # Proje dizininden doğrudan çalıştırma
69
+ ./bin/rubik
70
+
71
+ # Veya sistemde global kullanmak için ~/.local/bin dizinine bağlayın:
72
+ ln -sf $(pwd)/bin/rubik ~/.local/bin/rubik
73
+ rubik
74
+ ```
75
+
76
+ #### 🪟 Windows (PowerShell / Command Prompt)
77
+
78
+ Rubik ships with built-in `.cmd`, `.bat`, and `.ps1` wrappers for Windows:
79
+
80
+ ```powershell
81
+ # PowerShell üzerinden çalıştırma
82
+ .\bin\rubik.ps1
83
+
84
+ # veya Command Prompt / Terminal üzerinden
85
+ .\bin\rubik.cmd
86
+
87
+ # veya Ruby üzerinden doğrudan
88
+ ruby bin\rubik
89
+ ```
90
+
91
+ > [!TIP]
92
+ > **The Global `rubik` Command on Windows:**
93
+ > By adding Rubik's `bin` folder to your environment variables (User PATH), you can launch it from any terminal simply by typing `rubik`:
94
+ > ```powershell
95
+ > [Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$PWD\bin", "User")
96
+ > ```
97
+
98
+ ### Adding a Provider & Model
99
+
100
+ You can connect any OpenAI-compatible provider in a single line:
101
+
102
+ ```bash
103
+ # REPL içerisinden sağlayıcı ekleme:
104
+ rubik > !provider add deepseek openai_compatible https://api.deepseek.com/v1/chat/completions --key=sk-... --model=deepseek-coder --context=64000
105
+ rubik > !model deepseek/deepseek-coder
106
+ ```
107
+
108
+ ### First Session Example
109
+
110
+ ```text
111
+ ╭── rubik ─────────────────────────────────────────────────╮
112
+ │ 🎲 Rubik v0.1.0 │
113
+ │ deepseek/deepseek-coder │
114
+ │ my_project · main │
115
+ │ !help komutlar · ctrl+c kes · !config ayarlar │
116
+ ╰──────────────────────────────────────────────────────────╯
117
+ deepseek/deepseek-coder · ctx 3% · temiz · sandbox kapalı
118
+ rubik (my_project) [main] > !status
119
+ Project: my_project (main)
120
+ Default Model: deepseek/deepseek-coder
121
+ Dirty buffer: false
122
+
123
+ rubik (my_project) [main] > Kullanıcı kayıt formunu doğrulayan bir helper fonksiyonu yaz
124
+ ✻ Düşünüyor… · 1s (ctrl+c: kes)
125
+ ⏺ write_file — lib/validator.rb
126
+ ⎿ 3 lines · 18ms
127
+
128
+ [Rubik] In-memory buffer updated. Type `!diff` to view, `!apply` to save.
129
+
130
+ rubik (my_project) [main] [DIRTY: 1 ✎] > !diff
131
+ + def validate_user(params)
132
+ + raise "Invalid email" unless params[:email]&.include?("@")
133
+ + end
134
+
135
+ rubik (my_project) [main] [DIRTY: 1 ✎] > !test
136
+ Applied 1 file(s) before test. All 12 tests passed!
137
+ ```
138
+
139
+ > [!TIP]
140
+ > **Safe Execution (Tree-Sitter AST) & Hardware-Enforced OS Isolation (Bubblewrap / Container):**
141
+ > Before running any command, Rubik always keeps the `Security::Guard` shield active (Safe Execution), which detects dangerous commands, pipeline poisoning, and directory escapes ahead of time via Tree-Sitter syntax-tree (AST) analysis for Bash and PowerShell plus a database of 172 CLI YAML rules. For hardware-enforced OS isolation on Linux, use Bubblewrap (`bwrap`) or OCI (`docker`/`podman`):
142
+ > ```bash
143
+ > !sandbox on # Linux'ta Bubblewrap ile donanımsal OS izolasyonunu açar
144
+ > !config set sandbox.allow_network true -p # Ağ erişimi gerektiren komutlar için izin verir
145
+ > ```
146
+
147
+ ---
148
+
149
+ ## ⚡ Key Features
150
+
151
+ - 🌐 **Full Cross-Platform Support (Linux, macOS, Windows):** Runs with zero friction on Windows 11 / Windows Server environments (PowerShell, Command Prompt, Windows Terminal) in addition to POSIX systems. Ships with a pure-Ruby binary resolution solution, NTFS case-insensitivity handling, the RFC 8089 LSP URI standard, and native Windows CLI (`.cmd`, `.bat`, `.ps1`) wrappers.
152
+ - 🛡️ **Zero-Risk In-Memory Buffer:** When the model writes code, your files are not written to disk immediately; they are staged in an in-memory virtual buffer. You can inspect them with `!diff`, approve them with `!apply`, or revert them within seconds with `!undo`.
153
+ - 🏢 **Multi-Workspace & Extra Root Directories:** In monorepo or sibling-package layouts (`../shared-kernel`, `../proto`), you can safely attach external root directories (`!workspace add`) and include them in the buffer and the AST index in read-only or writable (`--rw`) mode.
154
+ - ⚡ **Live LSP Compiler Diagnostics:** While the model writes code, language servers such as `rust-analyzer`, `typescript-language-server`, `ruff`, `typeprof`, and `gopls` stand guard. As soon as a syntax or type error occurs, the model is alerted before any tests have even run.
155
+ - 🌳 **Multilingual Tree-Sitter & Prism Hybrid AST Engine:** Ships with the built-in **Prism** parser for Ruby and a full **Tree-Sitter** AST engine for Python, Go, Rust, TypeScript, TSX, JavaScript, C, C++, and Zig. It is 100% immune to curly-brace traps inside comments/strings and to modern language patterns; when a library is unavailable it silently falls back to the legacy regex extractors (fail-safe graceful fallback). The symbol and call graph (`!symbols`, `!outline`, `!refs`) is scanned asynchronously in the background; thanks to the persistent on-disk cache (`.rubik/cache/ast_index.json`) it is ready instantly across restarts.
156
+ - 🤖 **Dynamic LLM Providers & Multi-Role Routing:** Light/heavy task routing with support for all OpenAI-compatible endpoints (DeepSeek, Groq, OpenRouter, Mistral, vLLM), Anthropic Claude, OpenAI, and local Ollama.
157
+ - 🧬 **Spatiotemporal Composability & Self-Evolving Runtime:** The model can synthesize new REPL commands (`synthesize_command`) or built-in tools (`synthesize_tool`) at runtime; every side effect is tracked safely by the LIFO inverse-operation accumulator ($\Omega$).
158
+ - 🩹 **Self-Healing Runtime (Service Repair Plane):** Runtime components never require a process restart. `inspect_runtime` exposes the live fiber/coeffect/service state; `repair_runtime_service` diagnoses a failing component (LSP server, test runner, linter, or shell command), probes replacement commands with pre-flight verification in an isolated sub-context, and re-provides the repaired service — session state, caches, and conversations stay intact. `provide_service` registers new services live; every step is LIFO-revertible per the protocol above.
159
+ - 📋 **Structured Goal Ledger (SKILL.state adaptation):** the `!goal` loop keeps a rev-guarded ΔΣ ledger (objective, tasks, findings, decisions, open questions, environment notes) instead of trusting lossy conversation memory. The maestro patches it with the root-only `update_goal_ledger` tool (classified syntax/schema/conflict feedback, bounded retries), the current Σ is pinned through every compaction tier and re-injected each step, every revision is appended to storage (`goal-ledger` ref), declarative `verify` specs are re-checked at step boundaries (a failed check flips the task to `stale` — the world-drift signal), and banked tool results compact into per-row `<absorbed_into_ledger>` tombstones. Default ON (`goal.ledger.*`, operator-only; opt-out per key).
160
+ - 🧹 **Context Pruner (Semi-Deterministic Window Cleanup):** Long sessions accumulate stale exploration in the maestro's conversation window. A Vakanüvis-style two-plane pruner cleans it: a silent LLM subagent proposes a keep/drop/summarize plan, and the harness validates it deterministically (goal pinning, tool-use pairing, head/tail preservation bands, token budget) and applies it atomically at the next turn boundary — never mid-turn. It auto-fires at 60% of the input ceiling (before the blind drop-oldest trimmer would) or on demand via the `prune_context` tool and `!prune`. Default ON since 2026-10-10 (`subagents.pruner.*`, operator-only; opt-out `enabled: false`); the deterministic compaction stack always remains the safety net.
161
+ - 🔌 **Drop-in Plugin System (User Plugins):** `.rb` plugin files (drop-in) or directories under `~/.rubik/plugins` (global) or `.rubik/plugins` (project) are loaded automatically. Plugins are managed dynamically and with zero downtime from the REPL (`!plugin add`, `!plugin reload`); integrity is enforced through SHA-256 `.manifest.json` security verification.
162
+ - 🔌 **Model Context Protocol (MCP) & Spatiotemporal Tool Isolation:** Dynamically attaches external MCP servers (`filesystem`, `fetch`, databases, GitHub, etc.) over the stdio transport (`!mcp connect`, `!mcp add`). Discovered tools join the registry as `Rubik::Tools::BaseTool` proxies; when a server stops or crashes, the `AGENTS.md` LIFO inverse-operation accumulator ($\Omega$) and atomic rollback guarantee zero leftover processes and zero leaks.
163
+ - 📺 **Tiered Live Agent Monitoring (Step Formatter & Verbosity):** With the `quiet`, `normal`, `verbose`, and `debug` levels, step progress, tool calls, execution times, and intermediate thoughts are watched in real time (`!verbosity`, `!verbose`).
164
+ - 🎨 **Hybrid TUI Display Layer:** The `⏺/⎿` agent flow language, a live spinner cycling through Turkish verbs (`✻ Kavuruyor… 4sn`), an above-prompt statusline (model · context percentage · dirty buffer), live markdown rendering, a colored diff viewer, one-key quick approvals, and the interactive `!agents` fiber board. Under `NO_COLOR`/`--no-color` and in pipe mode the output degrades to plain text; session logs never contain ANSI codes (live settings via `!config set ui.*`).
165
+ - ⚡ **Native Tool Acceleration:** When available on the system, `grep_search` is delegated to ripgrep and context scanning to `git ls-files`; AST symbols warm up with lazy body loading. Without rg/git, or with `tools.native: off` or `RUBIK_NATIVE=off`, every path falls back to pure Ruby with identical output — the output format never changes.
166
+ - 🧵 **Quiet Concurrency (Fiber Scheduler Event Loop):** The pure-stdlib `Rubik::Concurrency` layer provides cooperative single-threaded concurrency WITHOUT altering any existing blocking I/O call sites (Net::HTTP, sleep, Open3 pipes, Mutex/CV/Thread#join): thousands of concurrent network requests, the MCP boot fan-out, and the subagent provider pool all run through the `Concurrency.silently`/`.map` boundary (not CPU parallelism, due to the GVL). Kill switch: `RUBIK_FIBER_SCHEDULER=off`.
167
+ - 📜 **Vakanüvis Chronicler (Session Annals):** The deterministic harness records REPL root turns and subagent lifecycle events into the `rubik://storage` annals; a budget-capped LLM distiller mines memory and a wiki skeleton from those annals. All planes are governed by `subagents.vakanuvis.*`; chronicles can be recalled through the Vakanüvis tool (`recall`/`record`/`forget`/`digest`).
168
+
169
+ > 🛡️ **Security note — when cloning unfamiliar repositories:** The `.rubik/commands` and `.rubik/tools` directories contain overlay files that can be interpreted at runtime. SHA-256 manifest verification blocks later modifications; files carrying a sentinel header but no manifest are accepted on the first scan only with the explicit consent of `RUBIK_BOOTSTRAP_OVERLAYS=1`. If you have cloned a repository you do not know, inspect the `.rubik/` directory before the first run.
170
+
171
+ ---
172
+
173
+ ## 🕹️ REPL Bang Commands
174
+
175
+ For detailed parameters, flags, and usage scenarios of all 30+ interactive commands:
176
+ 👉 Check out the **[COMMANDS.md — Comprehensive REPL Command Guide](COMMANDS.md)** document.
177
+
178
+ ### Command Summary Table
179
+
180
+ | Command Group | Key Commands | Description |
181
+ | :--- | :--- | :--- |
182
+ | **Models & Providers** | `!provider`, `!providers`, `!model` | Provider add/remove (CRUD), model role assignment, and smart routing. |
183
+ | **Agent & Planning** | `!plan`, `!goal`, `!steps` | Step-by-step planning, the autonomous goal loop, and step budget management. |
184
+ | **Buffer & Snapshots** | `!diff`, `!apply`, `!autoapply`, `!undo` | Inspect the in-memory virtual file buffer, save to disk, and revert. |
185
+ | **Runtime & Fibers** | `!runtime`, `!fibers`, `!services` | Coeffect services, active/pending fibers, and the $\Omega$ inverse depth. |
186
+ | **Code Intelligence & LSP** | `!lsp`, `!symbols`, `!outline`, `!refs` | Compiler diagnostics and semantic AST graph searches. |
187
+ | **Tests & Execution** | `!test`, `!lint`, `!format`, `!sh`, `!sandbox` | Multilingual test runs, Safe Execution, and a pluggable sandbox (bwrap/docker/podman). |
188
+ | **Session & Settings** | `!session`, `!history`, `!config`, `!verbosity`, `!status` | Session persistence, resuming with `rubik -c`, verbosity, and live settings. |
189
+ | **Workspace & Roots** | `!workspace`, `!roots`, `!allow` | Adding/removing multiple root directories (`add`/`remove`), external permissions (`!allow`), and status listing. |
190
+
191
+ ### Agent Surface Contract
192
+
193
+ Every command is typed with an `audience` at registration time: `:human` (operator only), `:agent`, or `:both`. Agent-visible built-in commands (`!diff`, `!status`, `!undo`, `!rollback`, `!autoapply`, `!context`, `!steps`, `!config`, `!model`, `!providers`, `!runtime`, `!fibers`, `!services`, `!agents`, `!lint`, `!format`, `!history`, `!prune`) are published as `<agent_commands>` in the system prompt and can be invoked via the `exec_repl_command` tool; file, code, test, and shell operations are always performed directly through the tools (`read_file`, `write_file`, `run_tests`, `run_command`). Human-only commands (`!provider`, `!workspace`, `!allow`, `!verbosity`, …) are reported back to the agent as a steering message; session lifecycle commands (`!quit`, `!exit`, `!clear`, `!reset`) are hard-blocked for the agent, while `!plan`/`!goal` are redirected to the "finish" tool behind reentrancy protection. Two notes: a `!model` switch takes effect on the next agent run; and `!undo`/`!rollback` revert buffer changes not yet applied to disk, while in `buffer.auto_apply` = `on_test`/`always` mode they may trigger the pre-run auto-apply.
194
+
195
+ ---
196
+
197
+ ## ⚙️ Configuration
198
+
199
+ For details on all model, LSP, buffer policy, provider, and sandbox settings:
200
+ 👉 Check out the **[CONFIG.md — Rubik Configuration Guide](CONFIG.md)** document.
201
+
202
+ ---
203
+
204
+ ## 🖥️ Agent Shell Behavior
205
+
206
+ `run_command` commands are never left to an implicit interpreter: the Safe Execution infrastructure runs every command with a **resolved shell** and an explicit argv array. `sandbox.shell: auto` (the default) chooses per command via a syntax gate — bash-required syntax (`<(...)`, `/dev/null`, heredocs) and `$` substitutions go to **bash**, the PowerShell fingerprint (`$env:`, `Select-String`, `Get-*` and other cmdlets) goes to **pwsh**, and plain commands take the fast path to **cmd** on Windows (bash elsewhere). The choice is labeled in the result (`[EXIT 1] (bash)`), and the system prompt announces the shell behavior to the model up front via the `<environment>` line — the model now knows which shell it is writing commands for. Use `sandbox.shell: bash|pwsh|cmd|sh` to pin a preference (see [CONFIG.md](CONFIG.md) §7).
207
+
208
+ ---
209
+
210
+ ## 🧪 Test Suite
211
+
212
+ Full suite: `rake` (or `rake test`). Single file: `ruby -Itest test/unit/test_repl.rb`.
213
+
214
+ Windows notes:
215
+ - Tests run sequentially in a single process; multi-file arguments such as `ruby -Itest a.rb b.rb` run only the **first** file — always use `rake` for the full suite.
216
+ - A space-containing `TEST` value such as `rake test TEST="a b"` is interpreted as a single path on Windows; make one invocation per file.
217
+ - Temp-directory removal (`teardown`) can fail with `Errno::EACCES` on Windows while a handle is still open; the `remove_temp_dir` helper in `test_helper.rb` swallows this with a best-effort retry and never muddies test results.
218
+
219
+ Environmental test skips:
220
+ - PowerShell shell execution tests are skipped when the `pwsh` (PowerShell Core) binary is not present on the system.
221
+ - Alternative AST parser tests are skipped when the `prism` gem or its C extension is not present on the system.
222
+ - In non-interactive / headless CI environments, direct TUI/reline terminal tests automatically run in non-blocking mode or are skipped in a controlled way.
223
+ - These skips keep the test suite portable and guarantee a 100% green run on every platform.
224
+
225
+ ---
226
+
227
+ ## 📦 Versioning Policy & SemVer 2.0 (Pre-1.0 Stability Roadmap)
228
+
229
+ Rubik adopts the [Semantic Versioning 2.0.0](https://semver.org/) standard. In the current pre-1.0 release (`0.1.0`), the Spatiotemporal Composability core architecture, the fiber lifecycle, and the agent protocols are being hardened:
230
+ - **0.x Releases:** A phase of rapid architectural evolution and refinement of experimental capabilities. Improvements to the core APIs are managed alongside the plan documents.
231
+ - **1.0.0 Goal:** Reaching production stability by freezing the core SPI interfaces, the Revertible Effects LIFO protocol, and the agent surface contract.
232
+ - **Backward Compatibility:** Before every minor release, comprehensive backward-compatibility and SPI breakage analyses (using GitNexus knowledge-graph & PDG analysis) are mandatory.
233
+
234
+ ---
235
+
236
+ ## 📚 Documentation
237
+
238
+ Every guide ships as a bilingual pair — `X.md` (English, authoritative) alongside `X.tr.md` (Turkish translation).
239
+
240
+ ### Architecture & Internals
241
+
242
+ | Guide | What it covers |
243
+ | :--- | :--- |
244
+ | 🌌 [AGENTS.md](AGENTS.md) | The Spatiotemporal Composability Protocol — theory, invariants, the fiber lifecycle state machine. |
245
+ | 🏗️ [ARCHITECTURE.md](ARCHITECTURE.md) | The 30 core domains and the responsibility matrix. |
246
+ | 🧬 [SELF_EVOLUTION.md](SELF_EVOLUTION.md) | How the LLM synthesizes tools, repairs services, and evolves its own runtime. |
247
+ | 🔌 [API.md](API.md) | Embedding the runtime from Ruby: config, tool registry, provider contract, orchestrator loop, error contracts. |
248
+
249
+ ### User & Operator Guides
250
+
251
+ | Guide | What it covers |
252
+ | :--- | :--- |
253
+ | 🕹️ [COMMANDS.md](COMMANDS.md) | All 30+ REPL bang commands with parameters and usage scenarios. |
254
+ | 🧰 [TOOLS.md](TOOLS.md) | Catalog of the 34 built-in agent tools: parameters, risk classes, diagnostics. |
255
+ | ⚙️ [CONFIG.md](CONFIG.md) | Every configuration knob (models, LSP, buffer policy, sandbox, subagents, pruner…). |
256
+ | 🌍 [ENV.md](ENV.md) | Environment variables — single consolidated reference table. |
257
+ | 🖥️ [LSP.md](LSP.md) | Step-by-step language-server setup and diagnostics walkthrough. |
258
+ | 🔌 [PLUGINS.md](PLUGINS.md) | The drop-in plugin system: layout, manifests, zero-downtime reloads. |
259
+
260
+ ### Project & Policy
261
+
262
+ | Guide | What it covers |
263
+ | :--- | :--- |
264
+ | 🤝 [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, test commands, commit/PR conventions, security reporting. |
265
+ | 🧪 [TESTING.md](TESTING.md) | Running the suite, naming conventions, test templates, flaky-test policy. |
266
+ | 🔒 [SECURITY.md](SECURITY.md) | Command gate, critical-write protection, synthesis gates, sandbox, audit log. |
267
+ | 📜 [ATTRIBUTION.md](ATTRIBUTION.md) | Open-source attributions and third-party notices. |
268
+ | 📜 [CHANGELOG.md](CHANGELOG.md) | Thematic release history; the full record lives in `git log`. |
269
+ | 📄 [LICENSE](LICENSE) | Software license (MIT). |
data/bin/rubik ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+ require "rubik"
6
+ require "optparse"
7
+
8
+ # Global SIGINT safety net. The REPL installs its own "INT" handler while
9
+ # running so a Ctrl+C during a command raises Interrupt (and Reline chains to it
10
+ # as its old handler). This catch-all at the top level guarantees that an
11
+ # interrupt that occurs before the REPL loop starts -- or any situation where an
12
+ # inner handler has been left uninstalled -- always terminates the process, so
13
+ # the user is never stuck unable to exit.
14
+ trap("INT") do
15
+ $stderr.puts "\n[rubik] Interrupted. Exiting."
16
+ exit!(130)
17
+ end
18
+
19
+ options = {}
20
+ OptionParser.new do |opts|
21
+ opts.banner = "Usage: rubik [options]"
22
+
23
+ opts.on("-v", "--version", "Show version") do
24
+ puts "rubik version #{Rubik::VERSION}"
25
+ Kernel.exit(0)
26
+ end
27
+
28
+ opts.on("-h", "--help", "Show help") do
29
+ puts opts
30
+ Kernel.exit(0)
31
+ end
32
+
33
+ opts.on("-c", "--continue [SESSION_ID]", "Resume previous session (default: latest recorded session)") do |sid|
34
+ options[:continue] = sid || :latest
35
+ end
36
+
37
+ opts.on("--list-sessions", "List all past sessions for current workspace") do
38
+ sessions = Rubik::Session::Manager.list_sessions(Dir.pwd)
39
+ if sessions.empty?
40
+ puts "No recorded sessions found for #{Dir.pwd}."
41
+ else
42
+ puts "=== Recorded Sessions for #{File.basename(Dir.pwd)} (#{sessions.size}) ==="
43
+ sessions.each_with_index do |s, idx|
44
+ puts " #{idx + 1}. #{s[:id]} (#{s[:scope]}) - #{s[:event_count]} events - Last: \"#{s[:last_prompt]}\""
45
+ end
46
+ end
47
+ Kernel.exit(0)
48
+ end
49
+
50
+ opts.on("-s", "--script FILE", "Execute script file in non-interactive mode") do |file|
51
+ options[:script] = file
52
+ end
53
+
54
+ opts.on("--no-color", "Disable ANSI colors, banner and spinner") do
55
+ ENV["NO_COLOR"] = "1"
56
+ end
57
+
58
+ opts.on("--color", "Force ANSI colors even when output is piped") do
59
+ ENV["CLICOLOR_FORCE"] = "1"
60
+ end
61
+ end.parse!
62
+
63
+ shell = Rubik::REPL::Shell.new
64
+
65
+ if options[:continue]
66
+ res = shell.resume_session(options[:continue])
67
+ unless res[:ok]
68
+ warn "[Rubik] Warning: #{res[:error]}"
69
+ end
70
+ end
71
+
72
+ if options[:script]
73
+ unless File.exist?(options[:script])
74
+ warn "Script file not found: #{options[:script]}"
75
+ Kernel.exit(1)
76
+ end
77
+ File.open(options[:script]) do |f|
78
+ shell.run(f)
79
+ end
80
+ else
81
+ shell.run
82
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "process"
5
+ require_relative "../platform"
6
+ require_relative "../runtime/discovery"
7
+ require_relative "../runtime/diagnostic_engine"
8
+
9
+ module Rubik
10
+ module Adapter
11
+ class BaseAdapter
12
+ attr_reader :name, :binary_name
13
+ attr_accessor :custom_test_cmd, :custom_format_cmd, :config
14
+
15
+ def initialize(name, binary_name)
16
+ @name = name.to_s.downcase
17
+ @binary_name = binary_name
18
+ @custom_test_cmd = nil
19
+ @custom_format_cmd = nil
20
+ @config = nil
21
+ end
22
+
23
+ def available?
24
+ !Rubik::Platform.which(@binary_name).nil?
25
+ end
26
+
27
+ def check_syntax(_file_path)
28
+ return unavailable_result unless available?
29
+
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # Compilation is an optional capability: interpreted languages ride this
34
+ # no-op default and only compiled-language adapters override it.
35
+ def compile(_root_dir)
36
+ { ok: true, output: "#{@name} requires no compilation", duration: 0.0, exit_code: 0 }
37
+ end
38
+
39
+ # Exit signature of "the selected runner itself is missing" — e.g.
40
+ # `cargo nextest run` on a machine without cargo-nextest (cargo exits
41
+ # 101 with "error: no such command: `nextest`" on STDERR and no test
42
+ # output). Stderr-only + empty stdout keeps a genuine failing suite
43
+ # that merely prints the phrase from triggering the fallback.
44
+ MISSING_RUNNER_PATTERN = /\bno such (?:command|subcommand)\b/i
45
+
46
+ def run_tests(root_dir, path: nil, timeout: nil)
47
+ return unavailable_result unless available?
48
+
49
+ cmd = resolve_test_cmd(root_dir, path: path)
50
+ unless cmd && Runtime::Discovery.binary_available?(cmd)
51
+ candidates = if @config
52
+ Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir, config: @config)
53
+ else
54
+ Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir)
55
+ end
56
+ reason = if candidates.any?
57
+ "No test runner found for #{@name} (tried: #{candidates.join(', ')}). Install one or set custom_test_cmd."
58
+ else
59
+ "No test runner found for #{@name}. Install one or set custom_test_cmd."
60
+ end
61
+ return unavailable_result(reason: reason)
62
+ end
63
+
64
+ eff_timeout = (timeout || (@config.respond_to?(:fetch) ? @config.fetch("test.timeout", 30) : 30) || 30).to_i
65
+ work_dir = test_working_dir(root_dir, path)
66
+ res = ProcessRunner.run(cmd, timeout: eff_timeout, chdir: work_dir)
67
+
68
+ if missing_runner_failure?(res)
69
+ fallback = next_test_runner_candidate(root_dir, exclude: [cmd, resolve_test_cmd(root_dir)])
70
+ if fallback
71
+ fallback_cmd = targeted_test_cmd(fallback, path.to_s.strip) unless path.nil? || path.to_s.strip.empty?
72
+ fallback_cmd ||= fallback
73
+ retry_res = ProcessRunner.run(fallback_cmd, timeout: eff_timeout, chdir: work_dir)
74
+ if missing_runner_failure?(retry_res)
75
+ # The ladder is exhausted: keep the ORIGINAL failure, but record
76
+ # the attempt so the dead end is never silent.
77
+ res[:output] = "[Test runner fallback] '#{fallback_cmd}' also reported a missing runner — " \
78
+ "no candidate left. Original failure follows.\n#{res[:output]}"
79
+ else
80
+ retry_res[:output] = "[Test runner fallback] '#{cmd}' could not run (runner missing) — " \
81
+ "retried with '#{fallback_cmd}'.\n#{retry_res[:output]}"
82
+ res = retry_res
83
+ cmd = fallback_cmd
84
+ end
85
+ end
86
+ end
87
+
88
+ res[:diagnostic] = Runtime::DiagnosticEngine.analyze_process_result(res, cmd: cmd) unless res[:ok]
89
+ res
90
+ end
91
+
92
+ # Working directory for the test run. Default: the session root.
93
+ # Adapters override when the runner must execute from a build root it
94
+ # discovers on disk (Crystal: nearest shard.yml ancestor).
95
+ def test_working_dir(root_dir, _path)
96
+ root_dir
97
+ end
98
+
99
+ def format_code(file_path)
100
+ cmd = resolve_format_cmd(file_path)
101
+ return unavailable_result unless cmd && Runtime::Discovery.binary_available?(cmd)
102
+
103
+ res = ProcessRunner.run(cmd)
104
+ res[:diagnostic] = Runtime::DiagnosticEngine.analyze_process_result(res, cmd: cmd) unless res[:ok]
105
+ res
106
+ end
107
+
108
+ def resolve_test_cmd(root_dir = Dir.pwd, path: nil)
109
+ base = if @custom_test_cmd && !@custom_test_cmd.strip.empty?
110
+ @custom_test_cmd
111
+ else
112
+ discovered = if @config
113
+ Runtime::Discovery.discover_test_runners(root_dir: root_dir, lang: @name, config: @config)
114
+ else
115
+ Runtime::Discovery.discover_test_runners(root_dir: root_dir, lang: @name)
116
+ end
117
+ discovered.first || default_test_cmd
118
+ end
119
+ return nil unless base
120
+ return base if path.nil? || path.to_s.strip.empty?
121
+
122
+ targeted_test_cmd(base, path.to_s.strip)
123
+ end
124
+
125
+ def targeted_test_cmd(base_cmd, path)
126
+ clean_path = path.to_s.tr("\\", "/")
127
+ if base_cmd.include?("%s")
128
+ format(base_cmd, Shellwords.escape(clean_path))
129
+ elsif base_cmd.include?("rake test")
130
+ "#{base_cmd} TEST=#{Shellwords.escape(clean_path)}"
131
+ else
132
+ "#{base_cmd} #{Shellwords.escape(clean_path)}"
133
+ end
134
+ end
135
+
136
+ def resolve_format_cmd(file_path)
137
+ if @custom_format_cmd && !@custom_format_cmd.strip.empty?
138
+ if @custom_format_cmd.include?("%s")
139
+ format(@custom_format_cmd, Shellwords.escape(file_path))
140
+ else
141
+ "#{@custom_format_cmd} #{Shellwords.escape(file_path)}"
142
+ end
143
+ else
144
+ discovered = Runtime::Discovery.discover_formatters(root_dir: File.dirname(file_path), lang: @name)
145
+ if discovered.first
146
+ "#{discovered.first} #{Shellwords.escape(file_path)}"
147
+ else
148
+ default_format_cmd(file_path)
149
+ end
150
+ end
151
+ end
152
+
153
+ protected
154
+
155
+ def default_test_cmd
156
+ nil
157
+ end
158
+
159
+ # Runner-missing shape: the phrase comes from the RUNNER WRAPPER on
160
+ # stderr while the suite produced no output at all — a genuine failing
161
+ # suite that merely prints the phrase (e.g. CLI error-path tests) has
162
+ # stdout content and is never misread as a missing runner.
163
+ def missing_runner_failure?(res)
164
+ return false if res[:ok]
165
+
166
+ res[:stdout].to_s.strip.empty? && res[:stderr].to_s.match?(MISSING_RUNNER_PATTERN)
167
+ end
168
+
169
+ # Next available Discovery candidate for this language, minus the
170
+ # commands already tried — the fallback ladder for a missing runner.
171
+ def next_test_runner_candidate(root_dir, exclude:)
172
+ candidates = if @config
173
+ Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir, config: @config)
174
+ else
175
+ Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir)
176
+ end
177
+ candidates.find do |c|
178
+ next false if exclude.include?(c)
179
+
180
+ Runtime::Discovery.binary_available?(c, root_dir: root_dir)
181
+ end
182
+ end
183
+
184
+ def default_format_cmd(_file_path)
185
+ nil
186
+ end
187
+
188
+ def unavailable_result(binary = nil, reason: nil)
189
+ bin = binary || @binary_name
190
+ msg = reason || "#{bin} is unavailable on system"
191
+ { ok: false, output: msg, duration: 0.0, exit_code: 127 }
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class CAdapter < BaseAdapter
9
+ def initialize
10
+ super("c", "clang")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("clang -fsyntax-only #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ def compile(root_dir)
20
+ return unavailable_result unless available?
21
+
22
+ ProcessRunner.run("make", chdir: root_dir)
23
+ end
24
+
25
+ protected
26
+
27
+ def default_test_cmd
28
+ "ctest"
29
+ end
30
+
31
+ def default_format_cmd(file_path)
32
+ "clang-format -i #{Shellwords.escape(file_path)}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "base"
5
+
6
+ module Rubik
7
+ module Adapter
8
+ class CrystalAdapter < BaseAdapter
9
+ def initialize
10
+ super("crystal", "crystal")
11
+ end
12
+
13
+ def check_syntax(file_path)
14
+ return unavailable_result unless available?
15
+
16
+ ProcessRunner.run("crystal build --no-codegen #{Shellwords.escape(file_path)}")
17
+ end
18
+
19
+ def compile(root_dir)
20
+ return unavailable_result unless available?
21
+
22
+ cmd = File.exist?(File.join(root_dir, "shard.yml")) ? "shards build" : "crystal build"
23
+ ProcessRunner.run(cmd, chdir: root_dir)
24
+ end
25
+
26
+ protected
27
+
28
+ def default_test_cmd
29
+ "crystal spec"
30
+ end
31
+
32
+ def default_format_cmd(file_path)
33
+ "crystal tool format #{Shellwords.escape(file_path)}"
34
+ end
35
+
36
+ private
37
+
38
+ # Nearest ancestor of the target (or the session root) that carries a
39
+ # shard.yml — crystal resolves shard dependencies from that directory,
40
+ # so a repo with a nested shard root (e.g. cli/shard.yml) must run its
41
+ # specs from there, not from the session root. nil = no shard root
42
+ # found; the default session-root behavior applies.
43
+ def shard_root(root_dir, path)
44
+ clean = path.to_s.strip
45
+ start = clean.empty? ? root_dir : File.dirname(File.expand_path(clean, root_dir))
46
+ walk = start.tr("\\", "/")
47
+ until walk.empty? || walk == File.dirname(walk)
48
+ return walk if File.exist?(File.join(walk, "shard.yml"))
49
+
50
+ walk = File.dirname(walk)
51
+ end
52
+ root = root_dir.to_s.tr("\\", "/")
53
+ File.exist?(File.join(root, "shard.yml")) ? root : nil
54
+ end
55
+
56
+ public
57
+
58
+ # Relativize the targeted spec path to the shard root so the command
59
+ # and the working directory (test_working_dir) agree on the same base.
60
+ # Prefix match is case-insensitive (Windows filesystems); the strip is
61
+ # exact-token based on the raw expanded path — Platform.normalize_path
62
+ # downcases on Windows and would corrupt the rendered path.
63
+ def resolve_test_cmd(root_dir = Dir.pwd, path: nil)
64
+ clean = path.to_s.strip
65
+ unless clean.empty? || (root = shard_root(root_dir, clean)).nil?
66
+ abs = File.expand_path(clean, root_dir).tr("\\", "/")
67
+ prefix = root.end_with?("/") ? root : "#{root}/"
68
+ clean = abs.downcase.start_with?(prefix.downcase) ? abs[prefix.length..] : abs
69
+ end
70
+ super(root_dir, path: clean.empty? ? nil : clean)
71
+ end
72
+
73
+ def test_working_dir(root_dir, path)
74
+ shard_root(root_dir, path) || super
75
+ end
76
+ end
77
+ end
78
+ end