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 UI
5
+ # Colorized unified-diff viewer for `!diff`: added/removed lines carry a
6
+ # color-blind safe cyan/red pair (never green-vs-red), hunk headers blue.
7
+ # The +/- signs stay in the text, so color is never the only channel.
8
+ # On interactive consoles a follow-up single-keypress menu offers apply.
9
+ # Non-TTY runs keep the plain diff byte-for-byte (tests, pipes, session
10
+ # replay).
11
+ module DiffViewer
12
+ module_function
13
+
14
+ def render(shell, theme, diff_text)
15
+ return if diff_text.to_s.empty?
16
+
17
+ require_relative "../security/redactor" unless defined?(Rubik::Security::Redactor)
18
+ clean_diff = Rubik::Security::Redactor.redact(diff_text.to_s)
19
+
20
+ if theme.color?
21
+ shell.output.puts colorize(clean_diff, theme)
22
+ else
23
+ shell.output.puts clean_diff
24
+ end
25
+
26
+ offer_actions(shell, theme) if interactive?(shell)
27
+ end
28
+
29
+ def colorize(diff_text, theme)
30
+ diff_text.lines.map do |line|
31
+ stripped = line.chomp
32
+ styled =
33
+ if stripped.start_with?("+++", "---", "===")
34
+ theme.paint(stripped, :bold)
35
+ elsif stripped.start_with?("@@")
36
+ theme.paint(stripped, :info)
37
+ elsif stripped.start_with?("+")
38
+ theme.paint(stripped, :added)
39
+ elsif stripped.start_with?("-")
40
+ theme.paint(stripped, :removed)
41
+ else
42
+ theme.paint(stripped, :dim)
43
+ end
44
+ "#{styled}\n"
45
+ end.join
46
+ end
47
+
48
+ # Single-key follow-up. Never raw #gets: mixing plain gets with Reline
49
+ # wedges the prompt on Windows (see Confirmations) — tty-prompt's
50
+ # keypress uses the same raw-mode path as every other UI surface.
51
+ def offer_actions(shell, theme)
52
+ prompt = Approvals.build_prompt(shell)
53
+ key = prompt.keypress(theme.paint(" a: uygula (!apply) · q veya enter: çık ", :dim))
54
+ apply_all(shell) if key.to_s.strip.downcase == "a"
55
+ rescue Interrupt
56
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
57
+ rescue StandardError => e
58
+ # apply! fails loud on containment breaks and IO errors — the user
59
+ # pressed "a", so the verdict must reach them, not vanish here.
60
+ shell.output.puts "\n[Hata] Uygulama başarısız: #{e.message}"
61
+ end
62
+
63
+ def apply_all(shell)
64
+ applied = shell.buffer_manager.apply!
65
+ if applied.empty?
66
+ shell.output.puts "Uygulanacak değişiklik kalmadı."
67
+ else
68
+ shell.sync_context_and_ast(applied)
69
+ shell.output.puts "Uygulandı: #{applied.join(', ')}"
70
+ end
71
+ end
72
+
73
+ def interactive?(shell)
74
+ Capability.interactive?(input: shell.input, output: shell.output)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Minimal rounded frame drawing used by the banner, approval cards and
8
+ # the input-box prompt. The glyphs come from the active Theme (unicode
9
+ # rounded or ascii), so the same code degrades gracefully on legacy
10
+ # consoles.
11
+ module Frame
12
+ # Terminal width of the given output (or the controlling console).
13
+ def self.width(io = nil)
14
+ w = if io.respond_to?(:winsize)
15
+ begin
16
+ io.winsize[1]
17
+ rescue SystemCallError, IOError
18
+ nil
19
+ end
20
+ end
21
+ w = w.to_i
22
+ return w if w.positive?
23
+
24
+ console = begin
25
+ IO.console
26
+ rescue SystemCallError
27
+ nil
28
+ end
29
+ cols = console&.winsize&.[](1).to_i
30
+ cols.positive? ? cols : 80
31
+ end
32
+
33
+ # Standalone top border with an optional title segment; shared by
34
+ # #draw and the input-box prompt (ui.input_box).
35
+ def self.top_border(theme, width:, title: nil)
36
+ g = theme.glyphs
37
+ inner = [width - 4, 8].max
38
+ title_part = title ? "#{g[:dash]} #{title} " : ""
39
+ title_pad = [inner + 1 - display_width(title_part), 0].max
40
+ "#{g[:corner_tl]}#{g[:dash]}#{title_part}#{g[:dash] * title_pad}#{g[:corner_tr]}"
41
+ end
42
+
43
+ # Standalone bottom border matching #top_border.
44
+ def self.bottom_border(theme, width:)
45
+ g = theme.glyphs
46
+ inner = [width - 4, 8].max
47
+ "#{g[:corner_bl]}#{g[:dash] * (inner + 2)}#{g[:corner_br]}"
48
+ end
49
+
50
+ # Draws a one-cell padded frame around content lines.
51
+ # Returns the frame as a single string (with trailing newline).
52
+ def self.draw(lines, theme:, width: 60, title: nil)
53
+ return "" if lines.empty?
54
+
55
+ inner = [width - 4, 8].max
56
+ content = lines.map { |l| truncate_plain(theme, l, inner) }
57
+
58
+ out = +""
59
+ out << "#{top_border(theme, width: width, title: title)}\n"
60
+ content.each { |l| out << "#{theme.glyph(:rail)} #{l}#{' ' * [inner - display_width(l), 0].max} #{theme.glyph(:rail)}\n" }
61
+ out << "#{bottom_border(theme, width: width)}\n"
62
+ out
63
+ end
64
+
65
+ # Truncates to exactly `max` display cells, reserving room for the
66
+ # theme's ellipsis glyph ("…" unicode / "..." ascii) so every row
67
+ # keeps the frame's uniform width.
68
+ def self.truncate_plain(theme, str, max)
69
+ ellipsis = theme.glyph(:ellipsis)
70
+ return str if display_width(str) <= max
71
+
72
+ "#{str[0...(max - display_width(ellipsis))]}#{ellipsis}"
73
+ rescue ArgumentError
74
+ str[0...max]
75
+ end
76
+
77
+ # East-Asian wide + common emoji blocks that render two columns wide
78
+ # in terminals; padding must account for them or the right rail
79
+ # drifts (e.g. the 🎲 banner row).
80
+ WIDE_RANGES = [
81
+ [0x1100, 0x115F], [0x2E80, 0xA4CF], [0xAC00, 0xD7A3],
82
+ [0xF900, 0xFAFF], [0xFE30, 0xFE4F], [0xFF00, 0xFF60],
83
+ [0xFFE0, 0xFFE6], [0x1F300, 0x1F64F], [0x1F680, 0x1F6FF],
84
+ [0x1F900, 0x1F9FF], [0x20000, 0x3FFFD]
85
+ ].freeze
86
+
87
+ def self.display_width(str)
88
+ Capability.strip_ansi(str.to_s).each_char.sum do |chr|
89
+ ord = chr.ord
90
+ WIDE_RANGES.any? { |(a, b)| ord.between?(a, b) } ? 2 : 1
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-markdown"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Line-oriented live markdown renderer for the streaming token flow.
8
+ # Completed lines are styled the moment their newline arrives (bold
9
+ # headings, inline code, fenced code rails); the tail is flushed on
10
+ # chunk_end. When styling is disabled (non-TTY / ui.markdown: off) every
11
+ # segment passes through verbatim so piped output stays byte-identical
12
+ # with the pre-UI streaming behavior.
13
+ #
14
+ # Until a step's first visible content arrives, blank lines are
15
+ # suppressed: the spinner's erased line plus the model's habitual
16
+ # leading "\n\n" would otherwise paint 2-3 empty rows at every step
17
+ # boundary. #finish ends one streamed response (Shell calls it on
18
+ # :chunk_end) and re-arms the suppression for the next step.
19
+ class MarkdownStream
20
+ def initialize(theme, enabled: true)
21
+ @theme = theme
22
+ @enabled = enabled && theme.color?
23
+ @pending = +""
24
+ @in_fence = false
25
+ @content_started = false
26
+ end
27
+
28
+ # Feeds a streaming delta. Yields ready-to-print segments (including
29
+ # their trailing newline) to the block as they complete.
30
+ def push(delta)
31
+ return if delta.nil? || delta.empty?
32
+
33
+ @pending << delta
34
+ while (idx = @pending.index("\n"))
35
+ line = @pending.slice!(0..idx)
36
+ next if suppress_leading_blank?(line)
37
+
38
+ yield render_segment(line)
39
+ end
40
+ end
41
+
42
+ # Flushes the buffered tail at end-of-stream. Yields the final segment
43
+ # (plus a terminating newline when anything was streamed mid-line) so
44
+ # the caller never leaves the cursor hanging. Always re-arms leading
45
+ # blank suppression: one #finish call == one streamed step (:chunk_end).
46
+ def finish
47
+ unless @pending.empty?
48
+ tail = @pending
49
+ @pending = +""
50
+ yield "#{render_segment(tail)}\n" unless suppress_leading_blank?(tail)
51
+ end
52
+ @content_started = false
53
+ end
54
+
55
+ # Renders a complete markdown document in one shot (batch mode /
56
+ # command output). Falls back to the raw text when styling is off.
57
+ def self.render_document(text, theme, enabled: true)
58
+ return text if text.nil? || text.empty?
59
+ return text unless enabled && theme.color?
60
+
61
+ TTY::Markdown.parse(text, color: :always)
62
+ end
63
+
64
+ private
65
+
66
+ # Leading-blank suppression (enabled styling only — piped output keeps
67
+ # its byte-identical contract). Marks the first visible line and lets
68
+ # everything after it through untouched.
69
+ def suppress_leading_blank?(line)
70
+ return false unless @enabled
71
+ return false if @content_started
72
+
73
+ if line.strip.empty?
74
+ true
75
+ else
76
+ @content_started = true
77
+ false
78
+ end
79
+ end
80
+
81
+ def render_segment(segment)
82
+ return segment unless @enabled
83
+
84
+ line = segment.chomp
85
+ rendered =
86
+ if line.start_with?("```")
87
+ @in_fence = !@in_fence
88
+ @theme.paint(line, :dim)
89
+ elsif @in_fence
90
+ "#{@theme.paint("#{@theme.glyph(:rail)} ", :dim)}#{@theme.paint(line, :dim)}"
91
+ elsif line =~ /\A(#+)\s+(.*)\z/
92
+ @theme.paint("#{Regexp.last_match(1)} #{Regexp.last_match(2)}", :bold)
93
+ else
94
+ style_inline(line)
95
+ end
96
+ "#{rendered}#{"\n" if segment.end_with?("\n")}"
97
+ end
98
+
99
+ # Inline styles: **bold**, `code`. Conservative regexes; unmatched
100
+ # markers stay visible rather than swallowing text.
101
+ def style_inline(line)
102
+ out = line.gsub(/\*\*([^*]+)\*\*/) { @theme.paint(Regexp.last_match(1), :bold) }
103
+ out.gsub(/`([^`]+)`/) { |seg| @theme.paint(seg, :info) }
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+
5
+ module Rubik
6
+ module UI
7
+ # Interactive agent/fiber board (`!agents`, the Antigravity /agents
8
+ # counterpart): lists runtime fibers with state and current activity,
9
+ # then offers an arrow-key detail view (state history, coeffect
10
+ # dependencies, orchestrator progress, Omega accumulator depth).
11
+ # Falls back to a static listing for pipes and tests.
12
+ module Panel
13
+ STATE_GLYPHS = {
14
+ active: "🟢",
15
+ pending: "⏳",
16
+ failed: "🔴",
17
+ disposed: "⚫",
18
+ unloaded: "⚪"
19
+ }.freeze
20
+
21
+ module_function
22
+
23
+ def render(shell, theme)
24
+ fibers = shell.runtime_manager&.fibers || {}
25
+ if fibers.empty?
26
+ shell.output.puts "Kayıtlı fiber/ajan yok."
27
+ return
28
+ end
29
+
30
+ shell.output.puts "=== Ajan / Fiber Panosu ==="
31
+ rows = fibers.values.sort_by { |f| STATE_ORDER[f.state] || 99 }
32
+ rows.each do |fiber|
33
+ shell.output.puts detail_line(fiber, theme)
34
+ end
35
+
36
+ return unless interactive?(shell)
37
+
38
+ choice = select_fiber(shell, rows, theme)
39
+ show_fiber_detail(shell, choice, theme) if choice
40
+ end
41
+
42
+ def detail_line(fiber, theme)
43
+ glyph = STATE_GLYPHS[fiber.state] || "❔"
44
+ activity = current_activity(fiber)
45
+ name_col = fiber.name.ljust(24)
46
+ "#{glyph} #{theme.paint(name_col, fiber.state == :active ? :accent : :dim)} #{activity}"
47
+ end
48
+
49
+ def current_activity(fiber)
50
+ inst = fiber.instance
51
+ if inst.respond_to?(:step_count)
52
+ depth = fiber.sub_context ? " · Ω#{fiber.sub_context.disposable_count}" : ""
53
+ "adım #{inst.step_count}/#{inst.max_steps}#{depth}"
54
+ elsif fiber.state == :failed
55
+ "hata: #{fiber.last_error&.message.to_s[0, 50]}"
56
+ else
57
+ deps = fiber.inject
58
+ deps.empty? ? "-" : "coeffects: #{deps.join(', ')}"
59
+ end
60
+ end
61
+
62
+ def select_fiber(shell, rows, _theme)
63
+ prompt = Approvals.build_prompt(shell)
64
+ choices = rows.map { |f| { name: "#{STATE_GLYPHS[f.state]} #{f.name}", value: f } }
65
+ choices << { name: "← çıkış", value: nil }
66
+ prompt.select("Detay için fiber seç:") do |menu|
67
+ choices.each { |c| menu.choice c[:name], c[:value] }
68
+ end
69
+ rescue Interrupt
70
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
71
+ nil
72
+ rescue StandardError
73
+ nil
74
+ end
75
+
76
+ def show_fiber_detail(shell, fiber, theme)
77
+ inst = fiber.instance
78
+ shell.output.puts
79
+ shell.output.puts theme.paint("── #{fiber.name} ──", :bold)
80
+ shell.output.puts " Durum : #{STATE_GLYPHS[fiber.state]} #{fiber.state}"
81
+ shell.output.puts " Coeffects : #{fiber.inject.join(', ')}"
82
+ shell.output.puts " Ω derinliği : #{fiber.sub_context ? fiber.sub_context.disposable_count : 0}"
83
+ if inst.respond_to?(:step_count)
84
+ shell.output.puts " Adım : #{inst.step_count}/#{inst.max_steps}"
85
+ shell.output.puts " Token : #{inst.total_tokens.inspect}" if inst.respond_to?(:total_tokens)
86
+ end
87
+ shell.output.puts " Son hata : #{fiber.last_error&.message}" if fiber.last_error
88
+ shell.output.puts theme.paint(" (enter ile panoya dön)", :dim)
89
+ end
90
+
91
+ def interactive?(shell)
92
+ Capability.interactive?(input: shell.input, output: shell.output)
93
+ end
94
+
95
+ STATE_ORDER = { active: 0, pending: 1, failed: 2, disposed: 3, unloaded: 4 }.freeze
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../platform"
5
+ require_relative "../uris"
6
+ require_relative "../repl/step_formatter"
7
+ require_relative "spinner"
8
+ require_relative "capability"
9
+ require_relative "theme"
10
+
11
+ module Rubik
12
+ module UI
13
+ # Display formatter for orchestrator step events implementing the Rubik
14
+ # agent-flow visual language:
15
+ #
16
+ # ✻ Kavuruyor · 3s (ctrl+c: kes) <- live spinner (UI::Spinner)
17
+ # ⏺ read_file — lib/main.rb <- action line
18
+ # ⎿ 3 lines · 15.2ms <- nested result branch
19
+ #
20
+ # Responsibilities (Plan 51, Decision D2 — StepFormatter stays the
21
+ # semantic layer, this class is the presentation decorator):
22
+ # * owns the Spinner lifecycle (:thinking starts it, any other event
23
+ # stops it before text is printed),
24
+ # * maps the 13 orchestrator event types to the glyph tree at
25
+ # normal/verbose levels, consuming the legacy formatter's public
26
+ # semantic helpers (thinking_parts, primary_arg_value,
27
+ # summarize_result, thought_first_line) instead of re-parsing events,
28
+ # * delegates raw [DEBUG:...] lines verbatim at debug level and
29
+ # re-anchors stable event bodies (warnings, retries, finish) with
30
+ # two leading spaces,
31
+ # * paints through the Theme so disabled color yields plain text with
32
+ # identical glyphs (one visual language, deterministic pipes).
33
+ class Renderer
34
+ LEVEL_QUIET = Rubik::REPL::StepFormatter::LEVEL_QUIET
35
+
36
+ attr_reader :spinner, :theme
37
+
38
+ def initialize(output:, config: nil, verbosity: nil, max_steps: nil, theme: nil)
39
+ @config = config
40
+ @max_steps = max_steps
41
+ @output = output
42
+ @theme = theme || Theme.for(output, config)
43
+ @legacy = Rubik::REPL::StepFormatter.new(config: config, verbosity: verbosity, max_steps: max_steps)
44
+ @spinner = Spinner.new(output, @theme, config: config)
45
+ @spinner_capable = Capability.spinner?(output, config) && level > LEVEL_QUIET
46
+ end
47
+
48
+ def level
49
+ @legacy.level
50
+ end
51
+
52
+ # Renders one orchestrator event to a printable String (or nil to stay
53
+ # silent). Must be called for EVERY event so the spinner is stopped
54
+ # before any other output is printed.
55
+ def render(step_info)
56
+ return nil unless step_info.is_a?(Hash)
57
+
58
+ type = step_info[:type]&.to_sym
59
+
60
+ # Teslimat süslemeden önce gelir: :user_output debug dahil her
61
+ # seviyede doğrudan işlenir — debug delegasyonu onu StepFormatter'a
62
+ # (kolu yok → nil) götürür ve stub yalan olurdu.
63
+ return render_user_output(step_info) if type == :user_output
64
+
65
+ # Raw debug lines come verbatim from the legacy formatter.
66
+ return @legacy.format(step_info) if level >= Rubik::REPL::StepFormatter::LEVEL_DEBUG
67
+
68
+ case type
69
+ when :thinking then render_thinking(step_info)
70
+ when :tool_call then render_tool_call(step_info)
71
+ when :tool_result then render_tool_result(step_info)
72
+ when :user_output then render_user_output(step_info)
73
+ when :llm_thought then render_llm_thought(step_info)
74
+ when :chunk, :chunk_end
75
+ @spinner.stop
76
+ nil
77
+ else
78
+ restyle_legacy(step_info)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def render_thinking(step_info)
85
+ return nil if level <= LEVEL_QUIET
86
+
87
+ if @spinner_capable
88
+ @spinner.start(step: step_info[:step], max_steps: step_info[:max_steps])
89
+ return nil
90
+ end
91
+
92
+ parts = @legacy.thinking_parts(step_info)
93
+ target_part = parts[:target].empty? ? "" : " (#{parts[:target]})"
94
+ "#{@theme.glyph(:clock)} #{@theme.spinner_verbs.first} [#{parts[:step]}/#{parts[:max]}]#{target_part}"
95
+ end
96
+
97
+ def render_tool_call(step_info)
98
+ @spinner.stop
99
+ return nil if level <= LEVEL_QUIET
100
+
101
+ tool = step_info[:tool]
102
+ args = step_info[:args] || {}
103
+ header = @theme.paint("#{@theme.glyph(:action)} #{tool}", :accent)
104
+
105
+ if level >= Rubik::REPL::StepFormatter::LEVEL_VERBOSE
106
+ "#{header} #{@theme.paint(Rubik::Platform.safe_json_generate(args), :dim)}"
107
+ else
108
+ summary = pretty_storage_arg(@legacy.primary_arg_value(args).to_s)
109
+ summary.empty? ? header : "#{header} #{theme_dim_dash(summary)}"
110
+ end
111
+ end
112
+
113
+ # Depo varlıklarını kullanıcıya 'göster' dendiğinde ajan uzun
114
+ # rubik://storage URI'leri kullanır; eylem satırında önek düşer,
115
+ # içerik karması 8 hexe kısaltılır (storage-show-surface planı C).
116
+ # rubik://storage/js-tangram/review/review.1.b54cddc277e87d25
117
+ # → js-tangram/review/review.1.b54cddc2…
118
+ def pretty_storage_arg(value)
119
+ rel = Rubik::Uris.storage_relative(value)
120
+ return value unless rel
121
+
122
+ rel.sub(/\.json\z/i, "").sub(/([0-9a-f]{8})[0-9a-f]{8}\z/i, "\\1…")
123
+ end
124
+
125
+ # :user_output (show_file sözleşmesi): tam içerik kullanıcıya aynen
126
+ # akar — soluklaştırılmaz, okunabilirlik önce gelir; yalnız başlık
127
+ # satırı dim boyanır. Model bu içeriği görmedi (stub aldı). Bu event
128
+ # süsleme değil TESLİMAT'tır: quiet dahil her seviyede basılır
129
+ # (sessiz düşüş, modelin "rendered to terminal" stub'ını yalan yapar).
130
+ def render_user_output(step_info)
131
+ @spinner.stop
132
+
133
+ header = @theme.paint("#{@theme.glyph(:branch)} #{step_info[:label]}", :dim)
134
+ "#{header}\n#{step_info[:content]}\n"
135
+ end
136
+
137
+ def render_tool_result(step_info)
138
+ @spinner.stop
139
+ return nil if level <= LEVEL_QUIET
140
+
141
+ status = (step_info[:status] || :ok).to_sym
142
+ duration = step_info[:duration_ms] || 0.0
143
+ output = step_info[:output].to_s
144
+ branch = "#{@theme.glyph(:branch)} "
145
+
146
+ if status != :ok
147
+ err = step_info[:error]
148
+ err = output.lines.first.to_s.strip if err.nil? || err.to_s.empty?
149
+ err = "Command failed" if err.to_s.empty?
150
+ if err.start_with?("Security blocked:") || output.start_with?("Security blocked:")
151
+ sec_line = output.lines.first.to_s.strip
152
+ hint = output[/!(?:allow-cmd|allow-dir)\s+[^\n]+/]&.strip
153
+ msg = hint ? "#{sec_line} [#{hint}]" : sec_line
154
+ "#{branch}#{@theme.paint("#{@theme.glyph(:warn)} #{msg}", :warn)}"
155
+ else
156
+ "#{branch}#{@theme.paint("#{@theme.glyph(:err)} #{err} (#{duration}ms)", :error)}"
157
+ end
158
+ elsif level >= Rubik::REPL::StepFormatter::LEVEL_VERBOSE
159
+ lines_size = step_info[:line_count] || output.lines.size
160
+ byte_size = step_info[:byte_size] || output.bytesize
161
+ header = "#{branch}#{lines_size} lines (#{byte_size} B, #{duration}ms)"
162
+ return @theme.paint(header, :dim) if output.empty?
163
+
164
+ preview = output.lines.first(5).map { |l| " #{l.chomp}" }.join("\n")
165
+ "#{@theme.paint(header, :dim)}\n#{@theme.paint(" #{@theme.glyph(:preview)} Preview:", :dim)}\n#{@theme.paint(preview, :dim)}"
166
+ else
167
+ summary = @legacy.summarize_result(output, step_info)
168
+ "#{branch}#{@theme.paint("#{summary} · #{duration}ms", :dim)}"
169
+ end
170
+ end
171
+
172
+ def render_llm_thought(step_info)
173
+ @spinner.stop
174
+ return nil if level < Rubik::REPL::StepFormatter::LEVEL_VERBOSE
175
+
176
+ first_line = @legacy.thought_first_line(step_info[:content])
177
+ return nil if first_line.nil? || first_line.empty?
178
+
179
+ " #{@theme.paint("#{@theme.glyph(:thought)} #{first_line}", :thought)}"
180
+ end
181
+
182
+ # Events whose message bodies stay stable (warnings, retries, finish,
183
+ # auto-apply, compaction): keep the legacy text, re-anchor it to the
184
+ # glyph tree with two leading spaces.
185
+ def restyle_legacy(step_info)
186
+ @spinner.stop
187
+ legacy_line = @legacy.format(step_info)
188
+ return nil if legacy_line.nil? || legacy_line.empty?
189
+
190
+ styled = case step_info[:type]&.to_sym
191
+ when :step_warning, :emergency_compact, :llm_retry
192
+ @theme.paint(legacy_line.strip, :warn)
193
+ when :auto_apply, :finish
194
+ @theme.paint(legacy_line.strip, :success)
195
+ else
196
+ legacy_line.strip
197
+ end
198
+ " #{styled}"
199
+ end
200
+
201
+ def theme_dim_dash(text)
202
+ @theme.paint("— #{text}", :dim)
203
+ end
204
+ end
205
+ end
206
+ end