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,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Tools
5
+ module Truncator
6
+ DEFAULT_MAX_CHARS = 12_000
7
+ DEFAULT_MAX_LINES = 300
8
+
9
+ def self.truncate(text, max_chars: DEFAULT_MAX_CHARS, max_lines: DEFAULT_MAX_LINES, strategy: :head_tail)
10
+ str = text.to_s
11
+ # byteslice truncation below cuts on byte boundaries and can split a
12
+ # multibyte character, producing invalid UTF-8 that then poisons the
13
+ # whole downstream pipeline (JSON generation, regex work, token
14
+ # estimation). Scrub exactly once, after the last byteslice.
15
+ str = str.scrub("") unless str.valid_encoding?
16
+ lines = str.lines
17
+
18
+ max_c = (max_chars || DEFAULT_MAX_CHARS).to_i
19
+ max_l = (max_lines || DEFAULT_MAX_LINES).to_i
20
+
21
+ return { text: str, truncated: false, omitted_lines: 0, omitted_chars: 0 } if str.bytesize <= max_c && lines.size <= max_l
22
+
23
+ strategy_sym = strategy.to_s.downcase.to_sym
24
+
25
+ case strategy_sym
26
+ when :head_only
27
+ kept_lines = lines.first(max_l)
28
+ kept_str = kept_lines.join
29
+ kept_str = kept_str.byteslice(0, max_c) if kept_str.bytesize > max_c
30
+ omitted_lines = [lines.size - kept_lines.size, 0].max
31
+ omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
32
+ notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
33
+ { text: finalize(kept_str, notice, prefix: true), truncated: true,
34
+ omitted_lines: omitted_lines, omitted_chars: omitted_chars }
35
+ when :tail_only
36
+ kept_lines = lines.last(max_l)
37
+ kept_str = kept_lines.join
38
+ kept_str = kept_str.byteslice(-max_c, max_c) if kept_str.bytesize > max_c
39
+ omitted_lines = [lines.size - kept_lines.size, 0].max
40
+ omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
41
+ notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
42
+ { text: finalize(kept_str, notice, prefix: false), truncated: true,
43
+ omitted_lines: omitted_lines, omitted_chars: omitted_chars }
44
+ else # :head_tail
45
+ half_lines = (max_l / 2).clamp(1, 150)
46
+ half_chars = (max_c / 2).clamp(1, 6000)
47
+
48
+ head_lines = lines.first(half_lines)
49
+ tail_lines = lines.last(half_lines)
50
+
51
+ head_str = head_lines.join
52
+ tail_str = tail_lines.join
53
+
54
+ head_str = head_str.byteslice(0, half_chars) if head_str.bytesize > half_chars
55
+ tail_str = tail_str.byteslice(-half_chars, half_chars) if tail_str.bytesize > half_chars
56
+
57
+ omitted_lines = [lines.size - (head_lines.size + tail_lines.size), 0].max
58
+ omitted_chars = [str.bytesize - (head_str.bytesize + tail_str.bytesize), 0].max
59
+ notice = "\n\n... [Truncated: #{omitted_lines} lines / #{omitted_chars} chars omitted to preserve token budget] ...\n\n"
60
+
61
+ { text: finalize(tail_str, finalize(head_str, notice, prefix: true), prefix: false), truncated: true,
62
+ omitted_lines: omitted_lines, omitted_chars: omitted_chars }
63
+ end
64
+ end
65
+
66
+ # Post-byteslice UTF-8 guard: a byte-boundary cut can split a multibyte
67
+ # character at either edge of the kept region. Dropping the partial
68
+ # sequence keeps the byte budget honest while the rendered output stays
69
+ # valid UTF-8 — the property every consumer below this point (JSON
70
+ # payload envelopes, regex-based token estimation, history joins)
71
+ # silently assumes.
72
+ def self.finalize(str, notice, prefix:)
73
+ str = str.scrub("") unless str.valid_encoding?
74
+ prefix ? "#{str}#{notice}" : "#{notice}#{str}"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require_relative "../uris"
5
+ require_relative "../coeffects"
6
+ require_relative "../orchestrator/subagent/transcript_formatter"
7
+ require_relative "output"
8
+ require_relative "diagnostic_reporter"
9
+ require_relative "../storage"
10
+
11
+ module Rubik
12
+ module Tools
13
+ # rubik:// şema sevk yeri (uri-resolver-seam planı 2026-10-01).
14
+ # read_file ve show_file sanal URI'leri buradan geçirir; dosya
15
+ # düzlemi (file:// ve düz yollar) ReadFileTool'da kalır.
16
+ #
17
+ # Kayıt modeli Sandbox::Driver'ınkinin aynısı (AGENTS.md §4.D):
18
+ # Monitor korumalı şema→adapter haritası; ctx verilirse önceki değer
19
+ # anlık görüntüsü alınır ve kimlik-korumalı geri-yükleme lambda'sı
20
+ # ctx.on_dispose'a LIFO için itilir — dispose yalnızca yuvada HÂLÂ
21
+ # bizim adapter varsa geri yükler, böylece daha yeni bir kayıt eski
22
+ # bağlamın kapanışından sağ çıkar. Varsayılan adapterler dosya sonunda
23
+ # ctx'siz tohumlanır (süreç ömrü) — bare kurulumlu testler için diki
24
+ # kök gerekmez.
25
+ module UriResolver
26
+ @adapters = {}
27
+ @mutex = Monitor.new
28
+
29
+ class << self
30
+ def register(scheme, adapter, ctx: nil)
31
+ key = scheme.to_s.downcase
32
+ restore = @mutex.synchronize do
33
+ prior = @adapters[key] # snapshot BEFORE the overwrite
34
+ @adapters[key] = adapter
35
+ lambda do
36
+ @mutex.synchronize do
37
+ next unless @adapters.key?(key) && @adapters[key].equal?(adapter)
38
+
39
+ prior ? @adapters[key] = prior : @adapters.delete(key)
40
+ end
41
+ end
42
+ end
43
+ ctx&.on_dispose(&restore)
44
+ adapter
45
+ end
46
+
47
+ def unregister(scheme)
48
+ @mutex.synchronize { @adapters.delete(scheme.to_s.downcase) }
49
+ end
50
+
51
+ def registered_schemes
52
+ @mutex.synchronize { @adapters.keys.sort }
53
+ end
54
+
55
+ # Sevk. Adapterden String (başarı) ya da ToolOutput (dürüst hata)
56
+ # döner; bu çözümleyici URI'ye hizmet etmiyorsa nil:
57
+ # - URI rubik:// değil
58
+ # - şemaya kayıtlı adapter yok
59
+ # - adapterın tam grameri (match?) URI'yi reddetmesi
60
+ # - `only:` verilmiş ve URI'nin şemesi farklıysa (show_file
61
+ # yalnız "storage" ister)
62
+ # Çağıranlar nil'i "kendi doğal düzlemime düş" olarak yorumlar —
63
+ # bilinmeyen şemalar buffer düzlemine düşer (bugünkü davranış).
64
+ def resolve(uri, ctx: nil, buffer_manager: nil, only: nil)
65
+ scheme = Rubik::Uris.scheme_of(uri)
66
+ return nil unless scheme
67
+ return nil if only && scheme != only.to_s.downcase
68
+
69
+ adapter = @mutex.synchronize { @adapters[scheme] }
70
+ return nil unless adapter
71
+ return nil unless adapter.match?(uri)
72
+
73
+ result = adapter.read(uri, ctx: ctx, buffer_manager: buffer_manager)
74
+ return result unless result.nil?
75
+
76
+ # Sözleşme ihlali (adapter read ASLA nil döndürmez) — gramer
77
+ # reddinden AYRT edilir: nil'i dosya düzlemine düşürmek yanlış
78
+ # yönlendiren bir dosya-not-found üretirdi; dürüst hata basılır.
79
+ warn "[UriResolver] '#{scheme}' adapter violated the read contract (returned nil); " \
80
+ "refusing to fall through to the file plane"
81
+ ToolOutput.error("rubik://#{scheme} read failed: the '#{scheme}' adapter returned no content " \
82
+ "(contract violation — see runtime log).")
83
+ end
84
+
85
+ # Şema kayıtlı VE adapterın grameri URI'yi tanıyor mu — yalnızca
86
+ # belgelendirme/bekçi sorguları için; sevk yolu resolve'dur.
87
+ def handles?(uri)
88
+ scheme = Rubik::Uris.scheme_of(uri)
89
+ return false unless scheme
90
+
91
+ adapter = @mutex.synchronize { @adapters[scheme] }
92
+ !adapter.nil? && adapter.match?(uri)
93
+ end
94
+ end
95
+
96
+ # Adapter sözleşmesi: durumdan yoksuz strateji sınıfları (repo
97
+ # kuralı: ilgili sınıflar aynı dosyada, files.rb gibi). match? gramer
98
+ # kapısıdır — başarısızlığı sevk yerinin nil düşüşüdür. read ASLA nil
99
+ # döndürmez ve ASLA yükselmez; hata yükü kendi ToolOutput'undadır
100
+ # (kendi rescue StandardError'ıyla sarılır — StorageAdapter'da bu
101
+ # ödev UriReader.read'in içindeki rescue'ya delege edilmiştir).
102
+ # Çağrı anı durumu (ctx, buffer_manager) resolve argümanlarından
103
+ # akar — adapter ctx-provided servis DEĞİLDİR.
104
+
105
+ # rubik://storage/... — FS-DB okuma düzlemi. Alt-gramer (index/
106
+ # proje/tip-liste/varlık/refs, .json eki) UriReader'ındır; hata
107
+ # yükleri de orada yaşar.
108
+ class StorageAdapter
109
+ def match?(uri)
110
+ Rubik::Uris::STORAGE_PREFIX.match?(uri)
111
+ end
112
+
113
+ def read(uri, ctx:, **_)
114
+ Rubik::Storage::UriReader.read(uri, ctx: ctx)
115
+ end
116
+ end
117
+
118
+ # rubik://subagent/<storage-id>/transcript reads the transcript FILE
119
+ # directly — never through the buffer manager. The URL must carry the
120
+ # unique storage dir name: the absolute path advertised by
121
+ # manage_subagents status and Instance#to_h. A bare display id
122
+ # ("subagent-1") resolves to a nonexistent dir and returns an honest
123
+ # not-found — deliberately no glob-newest fallback, which would serve
124
+ # a previous session's transcript while a live unsaved instance may
125
+ # exist (the stale-evidence failure mode of 2026-09-27). One route,
126
+ # one truth. The sandbox workspace gate does not apply: the id is
127
+ # charset-validated and the path is an internal artifact under the
128
+ # transcript root, not a user-supplied filesystem location.
129
+ class SubagentTranscriptAdapter
130
+ def match?(uri)
131
+ Rubik::Uris::SUBAGENT_TRANSCRIPT.match?(uri)
132
+ end
133
+
134
+ def read(uri, ctx:, buffer_manager:)
135
+ sub_id = uri[Rubik::Uris::SUBAGENT_TRANSCRIPT, 1]
136
+ unless sub_id.match?(/\A[a-zA-Z0-9_-]+\z/)
137
+ return ToolOutput.error("Invalid subagent id: #{sub_id} (allowed: letters, digits, _ and -). Use the " \
138
+ "storage-id dir name from the absolute 'transcript' path in manage_subagents status.")
139
+ end
140
+
141
+ storage = Rubik::Coeffects.get(ctx, Rubik::Coeffects::STORAGE_STORE)
142
+ file = Rubik::Orchestrator::Subagent::TranscriptFormatter.resolve(buffer_manager.root_dir, sub_id, storage: storage)
143
+ return File.read(file) if File.exist?(file)
144
+
145
+ ToolOutput.error(DiagnosticReporter.render_file_not_found(file.to_s, candidates: []) +
146
+ "\nHint: for a running subagent the live copy is manage_subagents action 'transcript' " \
147
+ "(in-memory); the on-disk file appears only after the subagent's first save.")
148
+ rescue StandardError => e
149
+ ToolOutput.error("Transcript read failed: #{e.class}: #{e.message}")
150
+ end
151
+ end
152
+
153
+ # rubik://goal/state + rubik://goal/ledger: canlı !goal anlık
154
+ # görüntüleri (GoalState#to_xml / GoalLedger#to_xml) — coeffect
155
+ # üzerinden çözülür; bunlar dosya değildir, buffer manager devre
156
+ # dışıdır. Bağlam/yoksa dürüst not-found (subagent transcript
157
+ # stilinde); glob/fallback yok — yanlış koşunun durumu servis
158
+ # edilmez. goal şeması TEK adapter slotudur: state/ledger ayrımı
159
+ # İÇ dispatch'tir (ikinci kayıt mevcut adapter'ı gölgelerdi).
160
+ class GoalStateAdapter
161
+ def match?(uri)
162
+ Rubik::Uris::GOAL_STATE.match?(uri) || Rubik::Uris::GOAL_LEDGER.match?(uri)
163
+ end
164
+
165
+ def read(uri, ctx:, **_)
166
+ if Rubik::Uris::GOAL_LEDGER.match?(uri)
167
+ ledger = Rubik::Coeffects.get(ctx, Rubik::Coeffects::GOAL_LEDGER)
168
+ return ledger.to_xml if ledger
169
+
170
+ return ToolOutput.error("No goal ledger available: no !goal run is active (or goal.ledger.enabled=false). " \
171
+ "The ledger exists only while a goal loop runs; rubik://goal/state may still " \
172
+ "hold the last run's snapshot.")
173
+ end
174
+
175
+ state = Rubik::Coeffects.get(ctx, Rubik::Coeffects::GOAL_STATE)
176
+ return state.to_xml if state
177
+
178
+ ToolOutput.error("No goal state available: no !goal run has started in this session. " \
179
+ "Goal state exists only while (or after) the operator runs !goal; " \
180
+ "for subagent work use manage_subagents action: 'status'.")
181
+ rescue StandardError => e
182
+ ToolOutput.error("Goal state read failed: #{e.class}: #{e.message}")
183
+ end
184
+ end
185
+
186
+ DEFAULT_STORAGE_ADAPTER = StorageAdapter.new
187
+ DEFAULT_SUBAGENT_TRANSCRIPT_ADAPTER = SubagentTranscriptAdapter.new
188
+ DEFAULT_GOAL_STATE_ADAPTER = GoalStateAdapter.new
189
+
190
+ register(Rubik::Uris::STORAGE, DEFAULT_STORAGE_ADAPTER)
191
+ register(Rubik::Uris::SUBAGENT, DEFAULT_SUBAGENT_TRANSCRIPT_ADAPTER)
192
+ register(Rubik::Uris::GOAL, DEFAULT_GOAL_STATE_ADAPTER)
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tools/base"
4
+ require_relative "tools/output"
5
+ require_relative "tools/result"
6
+ require_relative "tools/truncator"
7
+ require_relative "tools/registry"
8
+ require_relative "tools/builtins"
9
+ require_relative "tools/catalog"
10
+
11
+ module Rubik
12
+ module Tools
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Fast-path approval surfaces (Antigravity-style): a warning card drawn
8
+ # above the prompt plus single-keypress answers.
9
+ #
10
+ # History note: an adaptive sandbox-network approval card
11
+ # (confirm_action/map_action, returning :yes_network/:yes_sandbox) lived
12
+ # here but never had a production caller — a phantom security control
13
+ # (Plan 111, F5). Sandbox network policy is config-only
14
+ # (`sandbox.allow_network`); if an approval gate is ever wired in, its
15
+ # design intent is preserved in _plan/80-sandbox-mimari-refaktoru…md and
16
+ # git history.
17
+ module Approvals
18
+ module_function
19
+
20
+ # Single-key yes/no. Returns the downcased answer String ("y"/"n"/...)
21
+ # exactly like Confirmations#ask_yes_no so call sites keep their word
22
+ # matching. Enter/Esc/EOF/Ctrl+C resolve to the default answer.
23
+ def keypress_yes_no(shell, question, theme:, default: "n")
24
+ default_answer = default.to_s.downcase
25
+ return default_answer unless Capability.interactive?(input: shell.input, output: shell.output)
26
+
27
+ hint = default_answer.start_with?("y") ? "Y/n" : "y/N"
28
+ prompt = build_prompt(shell)
29
+
30
+ answer = prompt.keypress("#{question} #{theme.paint("[#{hint}]", :dim)} ")
31
+ normalize_answer(answer, default_answer)
32
+ rescue Interrupt
33
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
34
+ default_answer
35
+ rescue StandardError
36
+ default_answer
37
+ end
38
+
39
+ def normalize_answer(answer, default_answer)
40
+ answer = answer.is_a?(String) ? answer.strip.downcase : ""
41
+ answer.empty? || answer == "\r" || answer == "\e" ? default_answer : answer
42
+ end
43
+
44
+ # TTY::Prompt must talk to the real terminal: unwrap TeeIO capture the
45
+ # same way Confirmations#interactive_input? does.
46
+ def build_prompt(shell)
47
+ out = Capability.unwrap_tee(shell.output) || $stdout
48
+ TTY::Prompt.new(input: shell.input || $stdin, output: out)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../platform"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Startup banner: brand, version, active model and quick hints inside a
8
+ # rounded frame. Only rendered for interactive TTY sessions honoring
9
+ # `ui.banner`; piped/scripted runs stay silent for clean automation.
10
+ module Banner
11
+ module_function
12
+
13
+ def render(shell, theme)
14
+ return nil unless shell.config.fetch("ui.banner", true)
15
+ return nil unless Capability.interactive?(input: shell.input, output: shell.output)
16
+
17
+ routed = Statusline.current_route(shell)
18
+ model = routed ? Statusline.model_label(routed) : "model yapılandırılmamış"
19
+ project = File.basename(shell.buffer_manager&.root_dir || Dir.pwd)
20
+ branch = shell.detect_git_branch || "no-git"
21
+
22
+ lines = [
23
+ "🎲 Rubik v#{Rubik::VERSION}",
24
+ theme.paint(model, :info),
25
+ theme.paint("#{project} · #{branch}", :dim),
26
+ theme.paint("!help komutlar · ctrl+c kes · !config ayarlar", :dim)
27
+ ]
28
+
29
+ if Rubik::Platform.linux? && Rubik::Platform.which("bwrap") &&
30
+ !shell.config.fetch("sandbox.enabled", false) &&
31
+ shell.config.fetch("sandbox.prompt_on_startup", true)
32
+ lines << theme.paint("💡 Linux & bwrap algılandı: '!sandbox on' ile donanımsal OS izolasyonu açılabilir", :warn)
33
+ end
34
+
35
+ Frame.draw(
36
+ lines,
37
+ theme: theme,
38
+ title: theme.paint("rubik", :accent)
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strings-ansi"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Terminal capability detection: decides whether ANSI color, box-drawing
8
+ # and live spinner surfaces may be used for a given IO target.
9
+ #
10
+ # Resolution order:
11
+ # 1. RUBIK_TEST=1 -> never (deterministic test output)
12
+ # 2. NO_COLOR set -> never (https://no-color.org)
13
+ # 3. CLICOLOR_FORCE=1 -> always (CI with color-capable logs)
14
+ # 4. ui.color config: always|never -> explicit override
15
+ # 5. output not a TTY -> never (pipe/redirect safety)
16
+ # 6. TERM=dumb -> never
17
+ # 7. Windows legacy conhost -> try enabling VT, else never
18
+ module Capability
19
+ module_function
20
+
21
+ # Strips ANSI SGR escape sequences from a string. Used on the recording
22
+ # path (TeeIO -> StringIO) so session files and LLM context stay clean.
23
+ def strip_ansi(str)
24
+ return str unless str.is_a?(String) && str.include?("\e[")
25
+
26
+ Strings::ANSI.sanitize(str)
27
+ end
28
+
29
+ # True when ANSI color output is allowed for `io` under `config`.
30
+ def color?(io, config = nil)
31
+ case resolve_mode(io, config)
32
+ when :always then true
33
+ when :never then false
34
+ else io.respond_to?(:tty?) ? io.tty? : false
35
+ end
36
+ end
37
+
38
+ # True when the target can animate a spinner line (real terminal only).
39
+ def spinner?(io, config = nil)
40
+ color?(io, config) && io.respond_to?(:tty?) && io.tty? && interactive_env?
41
+ end
42
+
43
+ def interactive_env?
44
+ ENV["RUBIK_TEST"] != "1"
45
+ end
46
+
47
+ # True when both the input and the *real* output are interactive TTYs.
48
+ # Single canonical interactivity predicate for UI surfaces: a TeeIO
49
+ # wrapping a real terminal (REPL command capture) still counts as
50
+ # interactive; pure StringIO captures and pipes never do.
51
+ def interactive?(input:, output:)
52
+ return false unless interactive_env?
53
+
54
+ out_io = unwrap_tee(output)
55
+ return false unless out_io
56
+
57
+ input.respond_to?(:tty?) && input.tty? &&
58
+ out_io.respond_to?(:tty?) && out_io.tty?
59
+ end
60
+
61
+ # Resolves the real terminal behind a TeeIO (first target that is a
62
+ # live TTY); returns nil when every target is a capture buffer
63
+ # (StringIO#tty? is defined but false), and the IO itself when it is
64
+ # not a TeeIO.
65
+ def unwrap_tee(io)
66
+ return io unless io.is_a?(Rubik::REPL::TeeIO)
67
+
68
+ io.targets.compact.find(&:tty?)
69
+ end
70
+
71
+ def resolve_mode(io, config)
72
+ return :never if ENV["RUBIK_TEST"] == "1"
73
+ return :never if ENV["NO_COLOR"] && !ENV["NO_COLOR"].to_s.empty?
74
+ return :always if ENV["CLICOLOR_FORCE"] && ENV["CLICOLOR_FORCE"].to_s == "1"
75
+
76
+ cfg_mode = config&.fetch("ui.color", "auto").to_s.downcase if config
77
+ return :always if cfg_mode == "always"
78
+ return :never if cfg_mode == "never"
79
+
80
+ return :never if ENV["TERM"] && ENV["TERM"].downcase == "dumb"
81
+ return :never unless io.respond_to?(:tty?) && io.tty?
82
+
83
+ enable_windows_virtual_terminal_if_needed
84
+ :auto
85
+ end
86
+
87
+ # On legacy Windows consoles ANSI sequences are only rendered after
88
+ # ENABLE_VIRTUAL_TERMINAL_PROCESSING is set on the output handle.
89
+ # Modern terminals (Windows Terminal, WezTerm, Alacritty) already have
90
+ # it on; this is a best-effort, failure-tolerant probe.
91
+ def enable_windows_virtual_terminal_if_needed
92
+ return unless Gem.win_platform?
93
+ return if @vt_enabled
94
+
95
+ @vt_enabled = begin
96
+ require "fiddle"
97
+ kernel32 = Fiddle.dlopen("kernel32.dll")
98
+ get_std_handle = Fiddle::Function.new(kernel32["GetStdHandle"], [Fiddle::TYPE_LONG], Fiddle::TYPE_LONG)
99
+ get_console_mode = Fiddle::Function.new(kernel32["GetConsoleMode"],
100
+ [Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
101
+ set_console_mode = Fiddle::Function.new(kernel32["SetConsoleMode"],
102
+ [Fiddle::TYPE_LONG, Fiddle::TYPE_LONG], Fiddle::TYPE_INT)
103
+ handle = get_std_handle.call(-11) # STD_OUTPUT_HANDLE
104
+ mode_buf = [0].pack("L")
105
+ ok = get_console_mode.call(handle, mode_buf)
106
+ if ok != 0
107
+ mode = mode_buf.unpack1("L")
108
+ vt_flag = 0x0004 # ENABLE_VIRTUAL_TERMINAL_PROCESSING
109
+ set_console_mode.call(handle, mode | vt_flag) != 0 if mode.nobits?(vt_flag)
110
+ end
111
+ true
112
+ rescue StandardError
113
+ false
114
+ end
115
+ end
116
+
117
+ def reset_memoization!
118
+ @vt_enabled = nil
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require_relative "capability"
5
+ require_relative "frame"
6
+ require_relative "theme"
7
+ require_relative "../repl/interrupt_handler"
8
+
9
+ module Rubik
10
+ module UI
11
+ # Interactive question and decision dialog for the human operator (Plan 92).
12
+ # Presents single-choice and multi-choice menus with clean ANSI/VT100 cards
13
+ # and handles keyboard interrupts gracefully.
14
+ class Dialog
15
+ attr_reader :shell, :theme
16
+
17
+ def initialize(shell: nil, theme: nil, prompt: nil)
18
+ @shell = shell
19
+ @theme = theme || Theme.for(shell&.output || $stdout, shell&.config)
20
+ @prompt = prompt
21
+ end
22
+
23
+ # Prompts the user with a single-choice question.
24
+ #
25
+ # @param question [String] Question text
26
+ # @param options [Array<String>] List of selectable options
27
+ # @param allow_custom [Boolean] Whether to provide a free-text write-in option
28
+ # @return [String, Symbol] Selected answer, custom string, or :cancelled
29
+ def ask_single(question, options, allow_custom: true)
30
+ return :non_interactive unless interactive?
31
+
32
+ render_card(question, multi: false)
33
+ p = build_prompt
34
+ choices = options.uniq.dup
35
+ choices << "Other (write-in response)" if allow_custom
36
+
37
+ answer = p.select("Seçiminizi yapın / Select an option:", choices, filter: true, per_page: 10)
38
+ if answer == "Other (write-in response)"
39
+ custom = p.ask("Yanıtınız / Your answer:")
40
+ custom.to_s.strip.empty? ? options.first : "Other (write-in): #{custom.strip}"
41
+ else
42
+ answer
43
+ end
44
+ rescue Interrupt, StandardError
45
+ :cancelled
46
+ ensure
47
+ restore_console
48
+ end
49
+
50
+ # Prompts the user with a multi-choice question.
51
+ #
52
+ # @param question [String] Question text
53
+ # @param options [Array<String>] List of selectable options
54
+ # @return [Array<String>, Symbol] Array of selected answers, or :cancelled
55
+ def ask_multi(question, options)
56
+ return :non_interactive unless interactive?
57
+
58
+ render_card(question, multi: true)
59
+ p = build_prompt
60
+ answers = p.multi_select("Seçenekleri belirleyin / Select options (Space: toggle, Enter: confirm):",
61
+ options.uniq, filter: true, per_page: 10)
62
+ answers.empty? ? [options.first] : answers
63
+ rescue Interrupt, StandardError
64
+ :cancelled
65
+ ensure
66
+ restore_console
67
+ end
68
+
69
+ # True when the input/output streams are interactive TTYs.
70
+ def interactive?
71
+ return true if @prompt # Injected prompt for test mockability
72
+
73
+ inp = @shell&.input || $stdin
74
+ out = @shell&.output || $stdout
75
+ Capability.interactive?(input: inp, output: out)
76
+ end
77
+
78
+ private
79
+
80
+ def render_card(question, multi: false)
81
+ return unless @shell&.output
82
+
83
+ subtitle = multi ? "Çoklu Seçim (Boşluk: Seç/Kaldır · Enter: Onayla)" : "Tekli Seçim (Ok tuşları: Gezin · Enter: Onayla)"
84
+ card = Frame.draw(
85
+ [@theme.paint(question, :bold), @theme.paint(subtitle, :dim)],
86
+ theme: @theme,
87
+ title: @theme.paint("#{@theme.glyph(:action)} AJAN SORUSU", :accent)
88
+ )
89
+ @shell.output.puts(card)
90
+ end
91
+
92
+ def build_prompt
93
+ return @prompt if @prompt
94
+
95
+ out = Capability.unwrap_tee(@shell&.output) || $stdout
96
+ TTY::Prompt.new(input: @shell&.input || $stdin, output: out)
97
+ end
98
+
99
+ def restore_console
100
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
101
+ rescue StandardError
102
+ nil
103
+ end
104
+ end
105
+ end
106
+ end