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,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ # Öneri 3 (Kademe 1): Shell'den ayrılan girdi döngüsü sorumluluğu.
6
+ # Hem interaktif (Reline) hem stream (pipeline) koşumunun satır düzeyi
7
+ # ayrıştırması: çoklu-satır tamponu, heredoc modu ve EOF/^C davranışı.
8
+ # Durum Shell'de yaşar (@running, @reline, @input, @output); bu modül
9
+ # yalnızca döngü politikasını kodlar.
10
+ module InputLoop
11
+ def run_stream(stream)
12
+ stream.each_line do |line|
13
+ break unless @running
14
+
15
+ process_multiline_or_line(line.chomp)
16
+ end
17
+ end
18
+
19
+ def run_interactive
20
+ @running = true
21
+ multiline_buffer = []
22
+ heredoc_delimiter = nil
23
+ last_line_at = nil
24
+ print_banner
25
+ Rubik::REPL::InterruptHandler.with_handler do
26
+ while @running
27
+ result = read_and_process_line(multiline_buffer, heredoc_delimiter, last_line_at: last_line_at)
28
+ break if result[:stop]
29
+
30
+ multiline_buffer = result[:buffer]
31
+ heredoc_delimiter = result[:delimiter]
32
+ last_line_at = result[:last_line_at]
33
+ end
34
+ end
35
+ ensure
36
+ stop
37
+ end
38
+
39
+ # Plan 91 — Explicit paste mode: `!paste` starts a line-collector
40
+ # (PasteSupport#read_paste_block); the joined text is dispatched
41
+ # through the normal prompt path. Executed INSIDE run_interactive
42
+ # (not read_and_process_line) so the paste reader fully replaces
43
+ # readline-based collection while the mode is active.
44
+ def run_paste_mode
45
+ text = read_paste_block
46
+ return { stop: false, buffer: [], delimiter: nil } if text.strip.empty?
47
+
48
+ execute_input(text)
49
+ { stop: false, buffer: [], delimiter: nil }
50
+ end
51
+
52
+ # Processes one line of interactive input. Returns a hash with:
53
+ # stop: true if the REPL should exit
54
+ # buffer: updated multiline buffer
55
+ # delimiter: updated heredoc delimiter (nil if not in heredoc mode)
56
+ # last_line_at: monotonic timestamp of this accepted line (Plan 91)
57
+ def read_and_process_line(multiline_buffer, heredoc_delimiter, last_line_at: nil)
58
+ # Plan 91 fix: capture-then-reset BEFORE the early return. Resetting
59
+ # only on the fall-through path leaked the flag when paste mode was
60
+ # taken, so every subsequent prompt re-entered run_paste_mode and a
61
+ # test/piped readline returning nil spun the loop forever.
62
+ paste_mode = @paste_requested
63
+ @paste_requested = false
64
+ return run_paste_mode if paste_mode && heredoc_delimiter.nil? && multiline_buffer.empty?
65
+
66
+ flush_subagent_notices! if respond_to?(:flush_subagent_notices!)
67
+ @terminal_guard&.flush_notices!
68
+ current_prompt, boxed = prepare_prompt_line(!heredoc_delimiter.nil?)
69
+ line = safe_readline(current_prompt, add_history: false)
70
+ @output.puts box_bottom_border if boxed
71
+ return handle_eof(multiline_buffer, heredoc_delimiter) if line.nil? || line == "\x04" || line == "\x1a"
72
+
73
+ # Plan 91 — paste-burst collection: when lines arrive faster than
74
+ # ui.paste_join_window_ms apart (or Reline reports in_pasting?), the
75
+ # pasted lines are buffered and submitted as ONE prompt turn instead
76
+ # of one turn per line. First line still executes immediately if the
77
+ # burst stops; a lone line typed by hand (or Enter pressed in an
78
+ # empty prompt) executes as before.
79
+ #
80
+ # Gated to REAL interactive consoles only: with stubbed/piped readline
81
+ # (tests, RUBIK_TEST=1, non-TTY stdin) every stubbed line arrives
82
+ # "instantly", so an ungated collector would swallow consecutive test
83
+ # lines like "x = a << 1" + "exit" into one turn (seen in test_repl).
84
+ now = paste_clock
85
+ if paste_burst_eligible? && !line.strip.empty? && !heredoc_delimiter &&
86
+ multiline_buffer.empty? &&
87
+ (paste_line_fast?(last_line_at, now) || paste_pending?)
88
+ return collect_paste_burst(line)
89
+ end
90
+
91
+ # Push non-empty command to history
92
+ @reline::HISTORY.push(line) if !line.strip.empty? && line != begin
93
+ @reline::HISTORY.to_a.last
94
+ rescue
95
+ nil
96
+ end
97
+
98
+ # Plan 109 — reactive wake-up: an empty submitted line while
99
+ # wake-worthy notices are pending becomes the autonomous notice
100
+ # turn (both the safe_wakeup!-injected Enter and a user's
101
+ # Enter-on-empty land here). Heredoc body lines are content,
102
+ # never a turn; paste bursts and history already exclude empty
103
+ # lines.
104
+ if heredoc_delimiter.nil? && line.strip.empty? && respond_to?(:pending_wake_notices?) && pending_wake_notices?
105
+ execute_subagent_notice_turn!
106
+ return { stop: false, buffer: [], delimiter: nil, last_line_at: now }
107
+ end
108
+
109
+ result = if heredoc_delimiter
110
+ process_heredoc_line(line, multiline_buffer, heredoc_delimiter)
111
+ else
112
+ process_normal_line(line, multiline_buffer)
113
+ end
114
+ result[:last_line_at] = now
115
+ result
116
+ rescue Interrupt
117
+ @output.puts "^C"
118
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
119
+ { stop: false, buffer: [], delimiter: nil }
120
+ rescue StandardError => e
121
+ @output.puts "[REPL Error] #{e.class}: #{e.message}"
122
+ { stop: false, buffer: [], delimiter: nil }
123
+ end
124
+
125
+ # Plan 91 — burst collector: keeps reading lines while they arrive
126
+ # within ui.paste_join_window_ms of each other (or Reline reports
127
+ # pending paste input); submits the joined block as a single prompt
128
+ # turn. EOF/Ctrl+D flushes what was collected. Guarded against slow
129
+ # stdin consumers (run_stream-style piping unaffected — this path
130
+ # only runs for interactive readline input).
131
+ def collect_paste_burst(first_line)
132
+ lines = [first_line]
133
+ last_at = paste_clock
134
+ # Plan 109: collection runs with_collecting so a reactive wake
135
+ # Enter cannot append a blank line inside the pasted block.
136
+ collector = lambda do
137
+ loop do
138
+ line = safe_readline("paste… ", add_history: false)
139
+ now = paste_clock
140
+ break if line.nil? || line == "\x04" || line == "\x1a" # EOF flushes the block
141
+
142
+ in_paste = paste_line_fast?(last_at, now) || paste_pending?
143
+ last_at = now
144
+ break unless in_paste # burst ended -> user is typing again; flush
145
+
146
+ lines << line
147
+ end
148
+ end
149
+ if @terminal_guard.respond_to?(:with_collecting)
150
+ @terminal_guard.with_collecting(&collector)
151
+ else
152
+ collector.call
153
+ end
154
+ text = lines.join("\n")
155
+ execute_input(text)
156
+ # Return the final line timestamp so a following paste burst chain
157
+ # (copy #2 right after copy #1) is still detected as fast lines.
158
+ { stop: false, buffer: [], delimiter: nil, last_line_at: last_at }
159
+ end
160
+
161
+ def safe_readline(prompt, add_history: false)
162
+ if @terminal_guard
163
+ @terminal_guard.with_reading { @reline.readline(prompt, add_history) }
164
+ else
165
+ @reline.readline(prompt, add_history)
166
+ end
167
+ end
168
+
169
+ def handle_eof(multiline_buffer, heredoc_delimiter)
170
+ if heredoc_delimiter
171
+ @output.puts "\n[Rubik] Multiline input cancelled."
172
+ { stop: false, buffer: [], delimiter: nil }
173
+ else
174
+ stop
175
+ { stop: true, buffer: multiline_buffer, delimiter: heredoc_delimiter }
176
+ end
177
+ end
178
+
179
+ def process_heredoc_line(line, multiline_buffer, heredoc_delimiter)
180
+ if line.strip == heredoc_delimiter
181
+ full_text = multiline_buffer.join("\n")
182
+ execute_input(full_text)
183
+ { stop: false, buffer: [], delimiter: nil }
184
+ else
185
+ multiline_buffer << line
186
+ { stop: false, buffer: multiline_buffer, delimiter: heredoc_delimiter }
187
+ end
188
+ end
189
+
190
+ def process_normal_line(line, _multiline_buffer)
191
+ # Only trigger heredoc if line explicitly opens a multiline block (e.g. `<<EOF`, `!plan <<EOF`)
192
+ # NEVER trigger on inline operators like `array << item` or `a << b`
193
+ heredoc_match = line.match(/^\s*(?:[a-zA-Z0-9_!.-]+\s+)?<<[-~]?['"]?([A-Za-z0-9_]+)['"]?\s*$/)
194
+ if heredoc_match && !line.strip.start_with?("#")
195
+ delimiter = heredoc_match[1]
196
+ prefix = line.sub(/<<[-~]?['"]?#{Regexp.escape(delimiter)}['"]?\s*$/, "").strip
197
+ buffer = prefix.empty? ? [] : [prefix]
198
+ { stop: false, buffer: buffer, delimiter: delimiter }
199
+ elsif ["exit", "quit", "!exit", "!quit"].include?(line.strip.downcase)
200
+ stop
201
+ { stop: true, buffer: [], delimiter: nil }
202
+ else
203
+ execute_input(line)
204
+ { stop: false, buffer: [], delimiter: nil }
205
+ end
206
+ end
207
+
208
+ def process_multiline_or_line(line)
209
+ execute_input(line)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "reline"
4
+
5
+ module Rubik
6
+ module REPL
7
+ # Encapsulates Ctrl+C (SIGINT) handling for the interactive REPL loop.
8
+ #
9
+ # On Windows, Reline's default interrupt behavior can leave the line editor
10
+ # in a wedged state after a Ctrl+C: the next prompt neither echoes input nor
11
+ # honors EOF (Ctrl+D / Ctrl+Z), so the REPL appears frozen and cannot be
12
+ # exited. This module installs a SIGINT handler that raises Interrupt (so a
13
+ # running command -- orchestrator loop, HTTP call -- aborts promptly and
14
+ # Reline's own teardown restores console mode) and provides a best-effort
15
+ # line-editor reset to recover an unresponsive prompt.
16
+ module InterruptHandler
17
+ # Install a SIGINT handler that raises Interrupt, returning the previous
18
+ # handler so the caller can restore it on exit. Reline captures this trap
19
+ # as its @old_trap and calls it, so the Interrupt propagates out of
20
+ # Reline.readline cleanly.
21
+ def self.install
22
+ Signal.trap("INT") { raise Interrupt }
23
+ end
24
+
25
+ # Restore a previously captured SIGINT handler (value from .install).
26
+ def self.restore(prev_handler)
27
+ Signal.trap("INT", prev_handler) if prev_handler
28
+ end
29
+
30
+ # Run a block with the Interrupt-raising SIGINT handler installed, always
31
+ # restoring the previous handler and the console afterwards. Usage:
32
+ # Rubik::REPL::InterruptHandler.with_handler { ... loop body ... }
33
+ def self.with_handler
34
+ prev = install
35
+ yield
36
+ ensure
37
+ restore(prev)
38
+ restore_console_after_interrupt
39
+ end
40
+
41
+ # Best-effort reset of Reline's line editor and console state after an interrupt.
42
+ # On Windows, an interrupted/aborted readline or external command execution can
43
+ # leave the line editor with dirty input buffers or stale console handles.
44
+ # Resetting the editor drops any half-entered buffer, clears IOGate input/output
45
+ # buffers, flushes console input, and refreshes standard handles.
46
+ def self.restore_console_after_interrupt
47
+ return unless defined?(Reline)
48
+
49
+ if Reline.respond_to?(:line_editor) && (le = Reline.line_editor)
50
+ begin
51
+ if le.respond_to?(:reset)
52
+ # reline's released signature varies across versions (reset(prompt='', encoding: ...) vs reset(prompt=''))
53
+ begin
54
+ enc = (Reline.respond_to?(:encoding_system) ? Reline.encoding_system : Encoding.default_external)
55
+ le.reset("", encoding: enc)
56
+ rescue ArgumentError
57
+ begin
58
+ le.reset("")
59
+ rescue ArgumentError
60
+ le.reset
61
+ end
62
+ end
63
+ elsif le.respond_to?(:reset_line)
64
+ le.reset_line
65
+ end
66
+ rescue StandardError
67
+ nil
68
+ end
69
+ end
70
+
71
+ if defined?(Reline::IOGate) && (io = Reline::IOGate)
72
+ # Clear internal buffers so unconsumed control characters/interrupt bytes don't bleed into next prompt
73
+ begin
74
+ if io.instance_variable_defined?(:@output_buf) && (buf = io.instance_variable_get(:@output_buf)).is_a?(Array)
75
+ buf.clear
76
+ end
77
+ if io.instance_variable_defined?(:@input_buf) && (buf = io.instance_variable_get(:@input_buf)).is_a?(Array)
78
+ buf.clear
79
+ end
80
+ io.instance_variable_set(:@hsg, nil) if io.instance_variable_defined?(:@hsg)
81
+ rescue StandardError
82
+ nil
83
+ end
84
+
85
+ # Refresh Windows console handles and flush console buffer if on Windows
86
+ begin
87
+ if io.is_a?(Reline::Windows) || (defined?(Reline::Windows) && io.class.name.to_s.include?("Windows"))
88
+ get_std_handle = io.instance_variable_get(:@GetStdHandle)
89
+ if get_std_handle
90
+ new_in = begin
91
+ get_std_handle.call(Reline::Windows::STD_INPUT_HANDLE)
92
+ rescue
93
+ nil
94
+ end
95
+ io.instance_variable_set(:@hConsoleInputHandle, new_in) if new_in && new_in != 0
96
+
97
+ new_out = begin
98
+ get_std_handle.call(Reline::Windows::STD_OUTPUT_HANDLE)
99
+ rescue
100
+ nil
101
+ end
102
+ io.instance_variable_set(:@hConsoleHandle, new_out) if new_out && new_out != 0
103
+
104
+ if new_in && new_in != 0 && defined?(Reline::Windows::Win32API)
105
+ flush_func = io.instance_variable_get(:@FlushConsoleInputBuffer) ||
106
+ begin
107
+ Reline::Windows::Win32API.new("kernel32", "FlushConsoleInputBuffer", ["L"], "I")
108
+ rescue
109
+ nil
110
+ end
111
+ if flush_func
112
+ io.instance_variable_set(:@FlushConsoleInputBuffer, flush_func)
113
+ begin
114
+ flush_func.call(new_in)
115
+ rescue
116
+ nil
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ rescue StandardError
123
+ nil
124
+ end
125
+ end
126
+ rescue StandardError
127
+ nil
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ # Windows Reline Self-Healing Console Input Patch:
134
+ # If external commands or process interruptions invalidate @hConsoleInputHandle,
135
+ # prevent Reline::Windows#check_input_event from entering an infinite spin loop
136
+ # by refreshing the console input handle on WAIT_FAILED (-1 / 0xFFFFFFFF).
137
+ if defined?(Reline::Windows)
138
+ class Reline::Windows
139
+ unless method_defined?(:rubik_original_check_input_event)
140
+ alias rubik_original_check_input_event check_input_event
141
+
142
+ def check_input_event
143
+ if @hConsoleInputHandle.nil? || @hConsoleInputHandle.zero? || @hConsoleInputHandle == -1
144
+ @hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
145
+ @GetStdHandle.call(STD_INPUT_HANDLE)
146
+ rescue
147
+ nil
148
+ end
149
+ end
150
+
151
+ num_of_events = 0.chr * 8
152
+ while @output_buf.empty?
153
+ Reline.core.line_editor.handle_signal
154
+ wait_res = @WaitForSingleObject.call(@hConsoleInputHandle, 100)
155
+ if wait_res != 0
156
+ if [0xFFFFFFFF, 4_294_967_295, -1].include?(wait_res)
157
+ @hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
158
+ @GetStdHandle.call(STD_INPUT_HANDLE)
159
+ rescue
160
+ nil
161
+ end
162
+ @hConsoleHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
163
+ @GetStdHandle.call(STD_OUTPUT_HANDLE)
164
+ rescue
165
+ nil
166
+ end
167
+ end
168
+ @legacy_console = begin
169
+ getconsolemode.nobits?(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
170
+ rescue
171
+ false
172
+ end
173
+ next
174
+ end
175
+ if @GetNumberOfConsoleInputEvents.call(@hConsoleInputHandle, num_of_events).zero? || num_of_events.unpack1("L").zero?
176
+ @hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
177
+ @GetStdHandle.call(STD_INPUT_HANDLE)
178
+ rescue
179
+ nil
180
+ end
181
+ next
182
+ end
183
+ input_records = 0.chr * 20 * 80
184
+ read_event = 0.chr * 4
185
+ if @ReadConsoleInputW.call(@hConsoleInputHandle, input_records, 80, read_event).zero?
186
+ @hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
187
+ @GetStdHandle.call(STD_INPUT_HANDLE)
188
+ rescue
189
+ nil
190
+ end
191
+ else
192
+ read_events = read_event.unpack1("L") || 0
193
+ read_events.times do |idx|
194
+ input_record = input_records[idx * 20, 20]
195
+ next unless input_record && input_record.bytesize >= 20
196
+
197
+ event = input_record[0, 2].unpack1("s*")
198
+ case event
199
+ when WINDOW_BUFFER_SIZE_EVENT
200
+ @winch_handler&.call
201
+ when KEY_EVENT
202
+ key_down = input_record[4, 4].unpack1("l*")
203
+ repeat_count = input_record[8, 2].unpack1("s*")
204
+ virtual_key_code = input_record[10, 2].unpack1("s*")
205
+ virtual_scan_code = input_record[12, 2].unpack1("s*")
206
+ char_code = input_record[14, 2].unpack1("S*")
207
+ control_key_state = input_record[16, 2].unpack1("S*")
208
+ is_key_down = !key_down.zero?
209
+ process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) if is_key_down
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end