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,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-spinner"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Live status spinner shown while the orchestrator queries the LLM:
8
+ # cycling Turkish verbs (Claude-Code-style) plus elapsed seconds, drawn
9
+ # on a single terminal line. Owns a background thread whose lifecycle is
10
+ # tied to #stop/#kill; the Runtime RenderService registers `kill` as a
11
+ # context inverse so disposed shells never leak spinner threads.
12
+ class Spinner
13
+ TICK_SECONDS = 0.12
14
+ ELAPSED_STEP = 1.0
15
+
16
+ def initialize(io, theme, config: nil)
17
+ @io = io
18
+ @theme = theme
19
+ @config = config
20
+ @thread = nil
21
+ @mutex = Mutex.new
22
+ @spinner = nil
23
+ @started_at = nil
24
+ @last_elapsed_shown = -1
25
+ end
26
+
27
+ def enabled?
28
+ @config ? @config.fetch("ui.spinner", true) != false : true
29
+ end
30
+
31
+ def spinning?
32
+ @thread&.alive? == true
33
+ end
34
+
35
+ # Starts spinning with the next verb from the theme's rotation.
36
+ # No-op when the surface is disabled (non-TTY, tests, quiet terminals).
37
+ def start(step: nil, max_steps: nil)
38
+ return unless enabled?
39
+ return if spinning?
40
+
41
+ verb = next_verb
42
+ step_part = step && max_steps ? " [#{step}/#{max_steps}]" : ""
43
+ @started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
+ @last_elapsed_shown = -1
45
+
46
+ @mutex.synchronize do
47
+ # The message template carries :spinner and :msg tokens so #update
48
+ # actually substitutes the verb/step/elapsed text; `clear: true`
49
+ # makes tty-spinner erase its own line on stop (no stray frame).
50
+ @spinner = TTY::Spinner.new(":spinner :msg", output: @io, format: :spin,
51
+ hide_cursor: true, clear: true)
52
+ @spinner.update(msg: "#{@theme.paint(verb, :accent)}#{step_part}")
53
+ @spinner.spin
54
+ end
55
+
56
+ @thread = Thread.new do
57
+ loop do
58
+ sleep TICK_SECONDS
59
+ break unless spinning?
60
+
61
+ elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at)
62
+ @mutex.synchronize do
63
+ next unless @spinner && !@spinner.done?
64
+
65
+ if elapsed - @last_elapsed_shown >= ELAPSED_STEP
66
+ @last_elapsed_shown = elapsed
67
+ @spinner.update(msg: current_message(verb, step_part, elapsed))
68
+ end
69
+ @spinner.spin
70
+ rescue StandardError => e
71
+ # A dead tick thread must not take the REPL down, but the
72
+ # failure is never silent (AGENTS.md §5): report, then exit.
73
+ warn "[rubik] spinner tick failed: #{e.class}: #{e.message}"
74
+ Thread.exit
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Stops the spinner and clears its line so the caller can print freely.
81
+ # `final:` optionally leaves a completion note on the cleared line.
82
+ def stop(final: nil)
83
+ thread = @thread
84
+ @thread = nil
85
+ thread&.kill
86
+ thread&.join(0.3)
87
+
88
+ @mutex.synchronize do
89
+ if @spinner
90
+ begin
91
+ @spinner.stop
92
+ @io.print(final) unless final.nil?
93
+ @io.flush if @io.respond_to?(:flush)
94
+ rescue StandardError => e
95
+ # Cleanup must never mask the caller's in-flight exception
96
+ # (AGENTS.md §5): report the IO failure and still nil @spinner
97
+ # so repeated stops stay idempotent.
98
+ warn "[rubik] spinner cleanup failed: #{e.class}: #{e.message}"
99
+ ensure
100
+ @spinner = nil
101
+ end
102
+ end
103
+ end
104
+ end
105
+ alias kill stop
106
+ alias dispose stop
107
+
108
+ def current_message(verb, step_part, elapsed)
109
+ seconds = elapsed.to_i
110
+ suffix = seconds.positive? ? " · #{seconds}s" : ""
111
+ "#{@theme.paint(verb, :accent)}#{step_part}#{suffix} (ctrl+c: kes)"
112
+ end
113
+
114
+ def next_verb
115
+ @verb_index = ((@verb_index || -1) + 1) % @theme.spinner_verbs.size
116
+ @theme.spinner_verbs[@verb_index]
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../prompt/token_estimator"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Compact context strip printed above the prompt line before every read:
8
+ # active model, estimated context-window usage, dirty buffer count and
9
+ # sandbox state. Customizable via `ui.statusline_template` placeholders:
10
+ # {project} {branch} {model} {ctx} {dirty} {sandbox} {verbosity}
11
+ module Statusline
12
+ module_function
13
+
14
+ def render(shell, theme)
15
+ return nil unless shell.config.fetch("ui.statusline", true)
16
+
17
+ template = shell.config.fetch("ui.statusline_template", default_template)
18
+ # `!config set ui.statusline_template "{a} {b}"` can arrive parsed as
19
+ # a YAML flow-mapping Hash; coerce any non-String back to the default.
20
+ template = default_template unless template.is_a?(String)
21
+
22
+ routed = current_route(shell)
23
+ units_str = units_segment(shell)
24
+ values = {
25
+ "project" => File.basename(shell.buffer_manager&.root_dir || Dir.pwd),
26
+ "branch" => shell.detect_git_branch || "-",
27
+ "model" => model_label(routed),
28
+ "ctx" => context_usage(shell, routed),
29
+ "dirty" => dirty_segment(shell, theme),
30
+ "sandbox" => sandbox_segment(shell),
31
+ "verbosity" => shell.config.fetch("ui.verbosity", "normal"),
32
+ "units" => units_str
33
+ }
34
+
35
+ line = template.gsub(/\{(\w+)\}/) { values.fetch(::Regexp.last_match(1)) { "{#{::Regexp.last_match(1)}}" } }
36
+ line += " · [#{units_str}]" if !template.include?("{units}") && active_units_count(shell).positive?
37
+ theme.paint(line, :dim)
38
+ end
39
+
40
+ def default_template
41
+ "{model} · ctx {ctx} · {dirty} · sandbox {sandbox}"
42
+ end
43
+
44
+ def current_route(shell)
45
+ shell.router.route(task_type: :default)
46
+ rescue StandardError => e
47
+ # Edge-triggered warn: render runs before every prompt read, so an
48
+ # unguarded warn would spam one line per redraw on a broken router.
49
+ unless @route_failure_warned
50
+ @route_failure_warned = true
51
+ Rubik::Log.warn(:ui, "statusline route lookup failed (#{e.class}: #{e.message}); model shows '-'")
52
+ end
53
+ nil
54
+ end
55
+
56
+ # "provider/model" label shared with the banner.
57
+ def model_label(routed)
58
+ return "-" unless routed
59
+
60
+ provider = routed[:spec].is_a?(Hash) ? (routed[:spec]["provider"] || routed[:spec][:provider]) : nil
61
+ "#{provider || 'unknown'}/#{routed[:model]}"
62
+ end
63
+
64
+ # Estimated share of the context window consumed by the in-memory
65
+ # dialogue history, e.g. "34%" (or "-" when the limit is unknown).
66
+ def context_usage(shell, routed)
67
+ limit = routed&.fetch(:context_limit, nil).to_i
68
+ return "-" unless limit.positive?
69
+
70
+ tokens = Rubik::Prompt::TokenEstimator.estimate_history(shell.conversation_history || [])
71
+ pct = ((tokens.to_f / limit) * 100).round
72
+ pct = 100 if pct > 100
73
+ "#{pct}%"
74
+ end
75
+
76
+ def dirty_segment(shell, theme)
77
+ count = shell.buffer_manager&.dirty? ? (shell.buffer_manager.dirty_files&.size || 0) : 0
78
+ count.zero? ? "temiz" : "#{count} #{theme.glyph(:dirty)}"
79
+ end
80
+
81
+ def sandbox_segment(shell)
82
+ if shell.config.fetch("sandbox.enabled", false)
83
+ net_str = shell.config.fetch("sandbox.allow_network", false) ? "ağ-açık" : "izole"
84
+ "#{shell.sandbox_runner&.driver&.name || 'sandbox'}:#{net_str}"
85
+ else
86
+ "kapalı"
87
+ end
88
+ end
89
+
90
+ def active_units_count(shell)
91
+ running, paused = active_units_breakdown(shell)
92
+ running + paused
93
+ end
94
+
95
+ def active_units_breakdown(shell)
96
+ if shell.respond_to?(:init) && shell.init
97
+ # Kernel branch (translated unit states): Unit::PAUSED_STATES is
98
+ # the single spelling. :active deliberately covers :waiting_input
99
+ # here — the fallback branch's executing? below does not; the
100
+ # planes disagree on that one state, on purpose.
101
+ running = shell.init.units_by_state(:active).size
102
+ paused = shell.init.units_by_state(*Rubik::Kernel::Unit::PAUSED_STATES).size
103
+ [running, paused]
104
+ elsif shell.respond_to?(:subagent_supervisor) && shell.subagent_supervisor
105
+ insts = shell.subagent_supervisor.instances.values
106
+ running = insts.count(&:executing?)
107
+ paused = insts.count(&:parked?)
108
+ [running, paused]
109
+ else
110
+ [0, 0]
111
+ end
112
+ rescue StandardError => e
113
+ unless @units_failure_warned
114
+ @units_failure_warned = true
115
+ Rubik::Log.warn(:ui, "statusline unit-state lookup failed (#{e.class}: #{e.message}); showing 0 units")
116
+ end
117
+ [0, 0]
118
+ end
119
+
120
+ def units_segment(shell)
121
+ running, paused = active_units_breakdown(shell)
122
+ if running.positive? && paused.positive?
123
+ "⚙️ #{running} running, ⏸️ #{paused} paused"
124
+ elsif running.positive?
125
+ "⚙️ #{running} units running"
126
+ elsif paused.positive?
127
+ "⏸️ #{paused} units paused"
128
+ else
129
+ "0 units"
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+ require_relative "../platform"
5
+ require_relative "capability"
6
+
7
+ module Rubik
8
+ module UI
9
+ # Single source of truth for the Rubik display language: palette, glyph
10
+ # sets (unicode / ascii fallback) and spinner verbs. All visual components
11
+ # decorate through a Theme so `ui.color: never` / NO_COLOR / non-TTY pipes
12
+ # collapse every surface back to plain deterministic text.
13
+ class Theme
14
+ # Color-blind safe default palette: positive states (success/added)
15
+ # live on the blue axis (:cyan) and negative states (error/removed) on
16
+ # :red, so no distinction ever rests on the red-vs-green axis (the
17
+ # classic deuteranopia/protanopia confusion). Structural/meta text uses
18
+ # :blue to keep :cyan unambiguous as the "positive" hue. Named ANSI
19
+ # codes render differently per terminal theme, but the axis separation
20
+ # holds in all of them.
21
+ STYLES = {
22
+ accent: [:bright_red],
23
+ success: [:cyan],
24
+ error: [:red, :bold],
25
+ warn: [:yellow],
26
+ info: [:blue],
27
+ dim: [:bright_black],
28
+ thought: [:bright_black, :italic],
29
+ bold: [:bold],
30
+ added: [:cyan],
31
+ removed: [:red]
32
+ }.freeze
33
+
34
+ GLYPHS = {
35
+ unicode: {
36
+ action: "⏺", branch: "⎿", spinner: "✻", thought: "💭",
37
+ ok: "✔", err: "✖", warn: "⚠", retry: "↻", finish: "🏁",
38
+ bolt: "⚡", clock: "⏳", dirty: "✎", rail: "│", dash: "─",
39
+ corner_tl: "╭", corner_tr: "╮", corner_bl: "╰", corner_br: "╯",
40
+ cursor: "›", preview: "↳", ellipsis: "…"
41
+ }.freeze,
42
+ ascii: {
43
+ action: "*", branch: "`", spinner: "*", thought: "o",
44
+ ok: "+", err: "x", warn: "!", retry: "r", finish: "#",
45
+ bolt: "!", clock: "~", dirty: "*", rail: "|", dash: "-",
46
+ corner_tl: "+", corner_tr: "+", corner_bl: "+", corner_br: "+",
47
+ cursor: ">", preview: "->", ellipsis: "..."
48
+ }.freeze
49
+ }.freeze
50
+
51
+ DEFAULT_SPINNER_VERBS = [
52
+ "Düşünüyor", "Kavuruyor", "Karıştırıyor", "Damıtıyor", "Biçimlendiriyor", "Harmanlıyor", "Tarıyor", "Planlıyor", "Doğruluyor", "Tamamlıyor"
53
+ ].freeze
54
+
55
+ attr_reader :pastel, :glyphs, :spinner_verbs
56
+
57
+ def initialize(enabled: true, glyph_set: :unicode, spinner_verbs: DEFAULT_SPINNER_VERBS)
58
+ @pastel = Pastel.new(enabled: enabled)
59
+ set_name = GLYPHS.key?(glyph_set.to_sym) ? glyph_set.to_sym : :unicode
60
+ @glyphs = GLYPHS[set_name]
61
+ verbs = Array(spinner_verbs)
62
+ @spinner_verbs = verbs.empty? ? DEFAULT_SPINNER_VERBS : verbs
63
+ end
64
+
65
+ # Builds the theme the Shell subsystem should use for a given output IO
66
+ # and config. Called on Shell init and ui.* refresh — not on the
67
+ # per-token hot path. Unicode glyphs are downgraded to the ascii set
68
+ # when the output's external encoding cannot encode them (redirected
69
+ # legacy-codepage consoles on Windows), preventing conversion errors.
70
+ def self.for(io, config = nil)
71
+ enabled = Capability.color?(io, config)
72
+ glyph_set = (config&.fetch("ui.glyphs", "unicode") || "unicode").to_sym
73
+ glyph_set = :ascii if glyph_set == :unicode && !unicode_supported?(io)
74
+ verbs = config&.fetch("ui.spinner_verbs", nil)
75
+ new(enabled: enabled, glyph_set: glyph_set, spinner_verbs: verbs)
76
+ end
77
+
78
+ def self.unicode_supported?(io)
79
+ return true unless io.respond_to?(:external_encoding)
80
+
81
+ enc = io.external_encoding
82
+ return true if enc.nil? # default external (UTF-8 on modern Rubies)
83
+
84
+ "⏺".encode(enc)
85
+ true
86
+ rescue Encoding::UndefinedConversionError, Encoding::ConverterNotFoundError
87
+ false
88
+ end
89
+
90
+ def color?
91
+ pastel.enabled?
92
+ end
93
+
94
+ # Applies a named style. Returns plain text verbatim when color is off.
95
+ def paint(text, style_name)
96
+ return text.to_s unless color?
97
+
98
+ style = STYLES[style_name]
99
+ unless style
100
+ # Unknown styles used to fall through silently (the banner's :yellow
101
+ # hint rendered unstyled for months); surface that in debug mode
102
+ # instead of swallowing it (warned once per style).
103
+ if ENV["RUBIK_DEBUG"]
104
+ @warned_styles ||= Set.new
105
+ Rubik::Platform.debug_warn("UI::Theme#paint: unknown style #{style_name.inspect}, rendering plain") if @warned_styles.add?(style_name)
106
+ end
107
+ return text.to_s
108
+ end
109
+
110
+ pastel.decorate(text.to_s, *style)
111
+ end
112
+
113
+ def glyph(name)
114
+ glyphs.fetch(name, "")
115
+ end
116
+ end
117
+ end
118
+ end
data/lib/rubik/ui.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ui/capability"
4
+ require_relative "ui/theme"
5
+ require_relative "ui/frame"
6
+ require_relative "ui/banner"
7
+ require_relative "ui/statusline"
8
+ require_relative "ui/spinner"
9
+ require_relative "ui/renderer"
10
+ require_relative "ui/markdown_stream"
11
+ require_relative "ui/approvals"
12
+ require_relative "ui/panel"
13
+ require_relative "ui/diff_viewer"
14
+
15
+ require_relative "runtime/service_registration"
16
+
17
+ module Rubik
18
+ # Terminal display layer (Plan 51): theme, capability detection, agent-flow
19
+ # renderer, live spinner, markdown streaming and interactive approval /
20
+ # panel surfaces. Registered into the Runtime as a coeffect service so
21
+ # synthesized tools and plugins can resolve `ui` and `theme` through the
22
+ # context, and so spinner threads are disposed with their context (LIFO
23
+ # inverse), never leaked.
24
+ module UI
25
+ module RenderService
26
+ module_function
27
+
28
+ # Wires the display layer into the runtime context.
29
+ # Returns the Renderer; also provides `ui` (renderer) and `theme`.
30
+ # Idempotent on re-registration (`Shell#refresh_ui!`): service values
31
+ # are swapped, the delete-inverses are not stacked, and the single
32
+ # spinner-kill inverse resolves the CURRENT renderer at dispose time,
33
+ # so repeated refreshes never grow the Omega accumulator.
34
+ # `output:` lets callers bind to the real terminal when the shell's
35
+ # current @output is a transient capture TeeIO.
36
+ def register(context_or_manager, shell:, output: nil)
37
+ Rubik::Runtime::ServiceRegistration.call(context_or_manager) do |ctx|
38
+ next nil unless ctx
39
+
40
+ out = output || shell.output
41
+ theme = Theme.for(out, shell.config)
42
+ renderer = Renderer.new(config: shell.config, output: out, theme: theme)
43
+
44
+ existing_renderer = ctx.get("ui")
45
+ existing_renderer.spinner&.kill if existing_renderer.respond_to?(:spinner) && existing_renderer.spinner
46
+
47
+ first_registration = !ctx.has?("ui")
48
+ ctx.provide("ui", renderer)
49
+ ctx.provide("theme", theme)
50
+ ctx.on_dispose { (ctx.get("ui") || renderer)&.spinner&.kill } if first_registration
51
+
52
+ renderer
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/rubik/uris.rb ADDED
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ # rubik:// sanal URI gramerinin TEK evi (uri-resolver-seam planı
5
+ # 2026-10-01). Şemalar üç alanı kapsar (storage = FS-DB, subagent =
6
+ # orchestrator yapısı, goal = canlı orchestrator anlık görüntüsü) —
7
+ # dolayısıyla gramer storage'a ait değildir; bağımlılıksız yaprak olarak
8
+ # burada yaşar (coeffects.rb/platform.rb öncülü). Desenler, çözümleyiciler
9
+ # ve URI kurucuları yalnızca bu modüldedir; bekçi testi
10
+ # (test_architectural_boundaries.rb) lib/ içinde başka rubik:// regexi
11
+ # yasaklar. Buradaki desenler büyük/küçük harf duyarsızdır (\i) — mevcut
12
+ # çağrı yüzeyleri hiçbir değişiklik olmadan aynen kalır.
13
+ module Uris
14
+ SCHEME = "rubik"
15
+ STORAGE = "storage"
16
+ GOAL = "goal"
17
+ SUBAGENT = "subagent"
18
+
19
+ # Desenler — lib/ içinde izin verilen TEK rubik:// regexleri.
20
+ # Şema segmenti: [a-z][a-z0-9_-]* (çözümleme anahtarları küçük harfe
21
+ # iner; scheme_of). Önek ya da URI sonunda olmalı; yarıda kesilmez.
22
+ SCHEME_PREFIX = %r{\Arubik://([a-z][a-z0-9_-]*)}i
23
+ STORAGE_PREFIX = %r{\Arubik://storage(?:/|\z)}i
24
+ STORAGE_RELATIVE = %r{\Arubik://storage/(.*)\z}i
25
+ GOAL_STATE = %r{\Arubik://goal(?:/state)?(?:\.xml)?\z}i
26
+ # Yapılandırılmış Σ okuma yüzeyi (goal-ledger planı A): /state regexi
27
+ # /ledger'ı EŞLEMEZ — iki ayrı gramer, tek adapter içinde iç dispatch.
28
+ GOAL_LEDGER = %r{\Arubik://goal/ledger(?:\.xml)?\z}i
29
+ SUBAGENT_TRANSCRIPT = %r{\Arubik://subagent/([^/]+)/transcript(?:\.jsonl)?(?:#.*)?\z}i
30
+
31
+ # TranscriptIndexer ref sözleşimi: "subagent-<storage-id>".
32
+ TRANSCRIPT_REF_PREFIX = "subagent-"
33
+
34
+ module_function
35
+
36
+ # -> "storage"|"goal"|"subagent"|... ya da nil (rubik:// değil).
37
+ # to_s: nil/sembol girdiler dürüst nil düşer (NoMethodError yok).
38
+ def scheme_of(uri)
39
+ m = uri.to_s.match(SCHEME_PREFIX)
40
+ m[1].downcase if m
41
+ end
42
+
43
+ # Kanonik soyma (uri_reader.rb'nin tarihsel biçimi): önek + ilk eğik
44
+ # çizgi düşer. -> "uriproj/goal/1" | nil. Çıplak kök ("rubik://storage")
45
+ # boş dize döndürür — UriReader bunu malformed olarak raporlar.
46
+ def storage_path(uri)
47
+ uri.sub(STORAGE_PREFIX, "") if STORAGE_PREFIX.match?(uri)
48
+ end
49
+
50
+ # Kozmetik görüntüleme soyması (show_file stub'ı, ui/renderer
51
+ # kısaltması): zorunlu eğik çizgi, gerisi aynen. -> "proj/type/id" |
52
+ # nil. Kanonik storage_path'ten FARKLIDIR (ör. "rubik://storage" çıplak
53
+ # kökü soymaz) — yalnızca sunumda kullanılır, ayrıştırma için değil.
54
+ def storage_relative(uri)
55
+ m = uri.match(STORAGE_RELATIVE)
56
+ m[1] if m
57
+ end
58
+
59
+ def storage_entity(project:, type:, id:)
60
+ "#{SCHEME}://#{STORAGE}/#{project}/#{type}/#{id}"
61
+ end
62
+
63
+ def storage_project(project:)
64
+ "#{SCHEME}://#{STORAGE}/#{project}"
65
+ end
66
+
67
+ def storage_ref(project:, ref:)
68
+ "#{SCHEME}://#{STORAGE}/#{project}/refs/#{ref}"
69
+ end
70
+
71
+ def storage_listing(project:, type:)
72
+ "#{SCHEME}://#{STORAGE}/#{project}/#{type}"
73
+ end
74
+
75
+ def storage_index
76
+ "#{SCHEME}://#{STORAGE}/index"
77
+ end
78
+
79
+ def goal_state
80
+ "#{SCHEME}://#{GOAL}/state"
81
+ end
82
+
83
+ def goal_ledger
84
+ "#{SCHEME}://#{GOAL}/ledger"
85
+ end
86
+
87
+ def subagent_transcript(storage_id)
88
+ "#{SCHEME}://#{SUBAGENT}/#{storage_id}/transcript"
89
+ end
90
+
91
+ # TranscriptIndexer ref sözleşimi: "subagent-<storage-id>". storage-id
92
+ # kendisi zaten "subagent-" önekli doğar (ör. "subagent-1-12456-...") —
93
+ # öneki körlemesine eklemek çift önekli, çözümlenemez alias üretiyordu
94
+ # (js-tangram olay raporu 2026-10-04, olay #4). Önekli girdi aynen
95
+ # kullanılır.
96
+ def transcript_ref(storage_id)
97
+ id = storage_id.to_s
98
+ id.start_with?(TRANSCRIPT_REF_PREFIX) ? id : "#{TRANSCRIPT_REF_PREFIX}#{id}"
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ VERSION = "0.1.0"
5
+ end
data/lib/rubik.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rubik/version"
4
+ require_relative "rubik/log"
5
+ require_relative "rubik/platform"
6
+ require_relative "rubik/concurrency"
7
+ require_relative "rubik/uris"
8
+ require_relative "rubik/config"
9
+ require_relative "rubik/event_bus"
10
+ require_relative "rubik/repl"
11
+ require_relative "rubik/repl/terminal_guard"
12
+ require_relative "rubik/ui"
13
+ require_relative "rubik/buffer"
14
+ require_relative "rubik/session"
15
+ require_relative "rubik/context"
16
+ require_relative "rubik/storage"
17
+ require_relative "rubik/ast"
18
+ require_relative "rubik/llm"
19
+ require_relative "rubik/adapter"
20
+ require_relative "rubik/diagnostics"
21
+ require_relative "rubik/tools"
22
+ require_relative "rubik/orchestrator"
23
+ require_relative "rubik/repair"
24
+ require_relative "rubik/security"
25
+ require_relative "rubik/sandbox"
26
+ require_relative "rubik/plugin"
27
+ require_relative "rubik/benchmark"
28
+ require_relative "rubik/lsp"
29
+ require_relative "rubik/mcp"
30
+ require_relative "rubik/runtime"
31
+ require_relative "rubik/skills"
32
+ require_relative "rubik/prompt"
33
+ require_relative "rubik/kernel/record"
34
+ require_relative "rubik/kernel/dmesg"
35
+ require_relative "rubik/kernel/dispatcher"
36
+ require_relative "rubik/kernel/unit"
37
+ require_relative "rubik/kernel/init"
38
+
39
+ module Rubik
40
+ class Error < StandardError; end
41
+
42
+ class << self
43
+ attr_accessor :current_shell, :runtime_manager
44
+
45
+ # Deprecated read bridge (review Öneri 4 sunset): resolves the runtime
46
+ # manager from the ambient root. New code must go through the context:
47
+ # the 'shell' coeffect (Rubik::Coeffects::SHELL) via ctx.require or
48
+ # Manager#context. Warns once per process.
49
+ def runtime
50
+ warn_ambient_read_once
51
+ runtime_manager || current_shell&.runtime_manager
52
+ end
53
+
54
+ private
55
+
56
+ def warn_ambient_read_once
57
+ return if defined?(@ambient_warned) && @ambient_warned
58
+
59
+ @ambient_warned = true
60
+ warn "[DEPRECATION] Rubik.runtime is deprecated (sunset). Inject the " \
61
+ "'shell' coeffect (Rubik::Coeffects::SHELL) or read the manager " \
62
+ "from the owning Shell instead."
63
+ end
64
+ end
65
+ end