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,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../tools"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ module System
10
+ extend Base
11
+
12
+ def self.register(shell)
13
+ shell.register_command("help", description: "List available commands or show help for a command") do |args, ctx|
14
+ if args.empty?
15
+ ctx.out.puts "Available commands:"
16
+ ctx.commands.sort.each do |cmd_name, meta|
17
+ ctx.out.puts " !#{cmd_name.ljust(14)} - #{meta[:description]}"
18
+ end
19
+ else
20
+ target = args.first.downcase.delete_prefix("!")
21
+ if (meta = ctx.commands[target])
22
+ ctx.out.puts "!#{target}: #{meta[:description]}"
23
+ ctx.out.puts " #{meta[:usage]}" if meta[:usage]
24
+ else
25
+ ctx.out.puts "No help found for !#{target}"
26
+ end
27
+ end
28
+ end
29
+
30
+ shell.register_command("commands", description: "List all built-in and synthesized overlay commands") do |_args, ctx|
31
+ ctx.out.puts "=== Rubik Registered REPL Commands ==="
32
+ ctx.commands.sort.each do |name, meta|
33
+ ctx.out.puts " !#{name.ljust(16)} : #{meta[:description]}"
34
+ end
35
+ end
36
+
37
+ shell.register_command("runtime", description: "Visually display active coeffects, fibers, toolbelt, and accumulator depth",
38
+ audience: :both) do |args, ctx|
39
+ target = args.first&.downcase || "all"
40
+ ctx.out.puts inspect_tool(ctx).execute(target: target)
41
+ end
42
+
43
+ shell.register_command("fibers", description: "Visually display active, pending, and failed fibers with coeffect dependencies", audience: :both) do |_args, ctx|
44
+ ctx.out.puts inspect_tool(ctx).execute(target: "fibers")
45
+ end
46
+
47
+ shell.register_command("agents", description: "Interactive agent/fiber board: states, progress, Omega depth, detail view", audience: :both) do |_args, ctx|
48
+ Rubik::UI::Panel.render(ctx, ctx.theme)
49
+ end
50
+
51
+ shell.register_command("services", description: "Visually display provided runtime coeffects and service registry", audience: :both) do |_args, ctx|
52
+ ctx.out.puts inspect_tool(ctx).execute(target: "services")
53
+ end
54
+
55
+ shell.register_command("quit", description: "Exit the REPL shell") { |_args, ctx| ctx.stop }
56
+ shell.register_command("exit", description: "Exit the REPL shell") { |_args, ctx| ctx.stop }
57
+
58
+ shell.register_command("clear", description: "Clear conversation history / dialogue turns") do |_args, ctx|
59
+ ctx.conversation_history.clear
60
+ ctx.out.puts "Conversation context cleared. Starting fresh topic."
61
+ end
62
+
63
+ shell.register_command("reset", description: "Reset conversation history and in-memory dialogue turns") do |_args, ctx|
64
+ ctx.conversation_history.clear
65
+ ctx.out.puts "Conversation context reset. Starting fresh topic."
66
+ end
67
+
68
+ shell.register_command("status", description: "Show system status", audience: :both) do |_args, ctx|
69
+ proj = File.basename(Dir.pwd)
70
+ branch = ctx.detect_git_branch || "no-git"
71
+ routed = ctx.router.route(task_type: :default)
72
+ ctx.out.puts "Project: #{proj} (#{branch})"
73
+ ctx.out.puts "Default Model: #{routed[:spec]['provider']}/#{routed[:model]}"
74
+ ctx.out.puts "Dirty buffer: #{ctx.buffer_manager.dirty?}"
75
+ end
76
+ end
77
+
78
+ # The runtime/fibers/services trio share one inspector construction.
79
+ private_class_method def self.inspect_tool(ctx)
80
+ Rubik::Tools::InspectRuntimeTool.new(ctx.runtime_manager, ctx.build_tool_registry, ctx)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Rubik
6
+ module REPL
7
+ module Commands
8
+ # REPL bang command (!tasks) for monitoring and controlling asynchronous
9
+ # background tasks (Plan 93).
10
+ module Tasks
11
+ extend Base
12
+
13
+ # Whole-command synopsis: shared by the spine registration (usage:)
14
+ # and the unknown-subcommand fallback branch, so the two cannot drift.
15
+ USAGE_TASKS = "Usage: !tasks [list | status <id> | logs <id> [--tail N] | kill <id> | kill-all]"
16
+
17
+ def self.register(shell)
18
+ handler = lambda do |args, ctx|
19
+ subcmd = args.first&.downcase
20
+ task_mgr = ctx.task_manager
21
+
22
+ unless task_mgr
23
+ ctx.out.puts "TaskManager is not available."
24
+ return
25
+ end
26
+
27
+ case subcmd
28
+ when nil, "list"
29
+ render_list(ctx, task_mgr)
30
+ when "status"
31
+ render_status(ctx, task_mgr, args[1..])
32
+ when "logs"
33
+ render_logs(ctx, task_mgr, args[1..])
34
+ when "kill"
35
+ task_id = args[1]
36
+ if task_id.to_s.strip.empty?
37
+ ctx.out.puts "Usage: !tasks kill <task_id>"
38
+ elsif task_mgr.kill(task_id)
39
+ ctx.out.puts "Task '#{task_id}' killed."
40
+ else
41
+ ctx.out.puts "Task '#{task_id}' not found."
42
+ end
43
+ when "kill-all", "killall"
44
+ task_mgr.kill_all
45
+ ctx.out.puts "All background tasks terminated."
46
+ else
47
+ ctx.out.puts "Unknown !tasks subcommand: #{subcmd}. #{USAGE_TASKS}"
48
+ end
49
+ end
50
+
51
+ shell.register_command("tasks", description: "Manage background asynchronous tasks (list, status, logs, kill, kill-all)",
52
+ usage: USAGE_TASKS, audience: :both, &handler)
53
+ shell.register_command("task", description: "Alias for !tasks", usage: USAGE_TASKS, audience: :both, &handler)
54
+ end
55
+
56
+ def self.render_list(ctx, task_mgr)
57
+ tasks = task_mgr.list
58
+ if tasks.empty?
59
+ ctx.out.puts "No background tasks currently running or tracked."
60
+ return
61
+ end
62
+
63
+ ctx.out.puts "=== Background Tasks (#{tasks.size}) ==="
64
+ tasks.each do |t|
65
+ dur = t.running? ? "up #{t.uptime.round(1)}s" : "exit #{t.exit_code}"
66
+ ctx.out.puts " • [#{t.id}] #{t.status.to_s.upcase} (PID #{t.pid}, #{dur}): #{t.command}"
67
+ end
68
+ end
69
+
70
+ def self.render_status(ctx, task_mgr, rest)
71
+ task_id = rest&.first
72
+ if task_id.to_s.strip.empty?
73
+ ctx.out.puts "Usage: !tasks status <task_id>"
74
+ return
75
+ end
76
+
77
+ task = task_mgr.get(task_id)
78
+ unless task
79
+ ctx.out.puts "Task '#{task_id}' not found."
80
+ return
81
+ end
82
+
83
+ dur = task.running? ? "running (#{task.uptime.round(1)}s)" : "#{task.status} (exit #{task.exit_code.inspect}, ran #{task.uptime.round(1)}s)"
84
+ ctx.out.puts "Task: #{task.id}"
85
+ ctx.out.puts " Status: #{dur}"
86
+ ctx.out.puts " PID: #{task.pid}"
87
+ ctx.out.puts " Command: #{task.command}"
88
+ ctx.out.puts " Buffered: #{task.ring_buffer.size} lines"
89
+ end
90
+
91
+ def self.render_logs(ctx, task_mgr, rest)
92
+ rest ||= []
93
+ task_id = rest.first
94
+ if task_id.to_s.strip.empty?
95
+ ctx.out.puts "Usage: !tasks logs <task_id> [--tail N]"
96
+ return
97
+ end
98
+
99
+ tail = 50
100
+ tail_idx = rest.index("--tail") || rest.index("-n")
101
+ tail = rest[tail_idx + 1].to_i if tail_idx && rest[tail_idx + 1]
102
+
103
+ task = task_mgr.get(task_id)
104
+ unless task
105
+ ctx.out.puts "Task '#{task_id}' not found."
106
+ return
107
+ end
108
+
109
+ lines = task.ring_buffer.tail(tail)
110
+ if lines.empty?
111
+ ctx.out.puts "No logs recorded for task '#{task_id}' yet."
112
+ else
113
+ ctx.out.puts "=== Logs for #{task.id} (last #{lines.size} lines) ==="
114
+ lines.each { |line| ctx.out.puts line }
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,443 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Rubik
6
+ module REPL
7
+ module Commands
8
+ module Workspace
9
+ extend Base
10
+
11
+ # Top-level command synopses: one constant per command, referenced by
12
+ # both the spine registration (usage:) and the fallback branch that
13
+ # prints it, so the two cannot drift.
14
+ USAGE_WORKSPACE = "Usage: !workspace <list | add <path> [--rw] | remove <path>> [--project|--global]"
15
+ USAGE_ALLOW = "Usage: !%<cmd>s <path> [--rw|--ro] | !%<cmd>s <list|remove <path>> [--project|--global]"
16
+ USAGE_ALLOW_CMD = "Usage: !allow-cmd <add \"<prefix>\" [--force]|list|remove \"<prefix>\"> [--project|--global]"
17
+ USAGE_CONFIG = "Usage: !config <get <key> | set <key> <val> | list [filter] | path> [--project|--global]"
18
+
19
+ def self.register(shell)
20
+ register_workspace_command(shell)
21
+ register_allow_command(shell)
22
+ register_allow_cmd_command(shell)
23
+ register_config_command(shell)
24
+ end
25
+
26
+ # Operator-only command allowlist gate (Plan: komut-allowlist).
27
+ # audience defaults to :human in Shell#register_command, so the agent
28
+ # can never invoke this via exec_repl_command (fail-closed).
29
+ private_class_method def self.register_allow_cmd_command(shell)
30
+ shell.register_command("allow-cmd",
31
+ description: "Operator-only command allowlist: add|list|remove token-prefix entries " \
32
+ '(e.g. add "node .gitnexus/run.cjs" --project)',
33
+ usage: USAGE_ALLOW_CMD) do |args, ctx|
34
+ scope, clean_args = Base.parse_scope(args)
35
+ sub = clean_args.first&.downcase || "list"
36
+
37
+ handle_add = lambda do |tokens_to_add|
38
+ entry_tokens = tokens_to_add.reject { |a| a == "--force" }
39
+ entry = entry_tokens.join(" ").strip
40
+ if entry.empty?
41
+ ctx.out.puts 'Usage: !allow-cmd add "<command prefix>" [--project|--global] [--force]'
42
+ return
43
+ end
44
+
45
+ tokens = entry.split(/\s+/)
46
+ # Fail closed (G3 triage, Priority-1 site): a classifier crash
47
+ # must not silently skip the broad-token RCE check — treat the
48
+ # token as broad and let the operator pass a narrow prefix.
49
+ broad = begin
50
+ base = Rubik::Security::Classifier.classify(tokens.first)
51
+ base.classification == :dangerous && tokens.size == 1
52
+ rescue StandardError => e
53
+ Rubik::Log.warn(:security, "classifier failed for '#{tokens.first}' (#{e.class}: #{e.message}); treating as broad")
54
+ true
55
+ end
56
+
57
+ # Fail-closed against unbounded execution: a broad, single-token
58
+ # dangerous entry (e.g. "node", "python", "bash") grants
59
+ # command execution for EVERY invocation of that binary, so
60
+ # `node` → `node -e 'malicious()'` RCE. Such broad tokens are
61
+ # blocked with or without --force — the operator should use a
62
+ # narrow prefix (e.g. "node .gitnexus/run.cjs").
63
+ if broad
64
+ ctx.out.puts "Blocked: '#{entry}' is a broad, single-token dangerous command. Even with --force it grants " \
65
+ "unbounded command execution (e.g. `#{tokens.first} -e 'malicious(...)'` RCE). " \
66
+ "Use a narrow prefix instead (e.g. \"#{tokens.first} <script>\")."
67
+ return
68
+ end
69
+
70
+ # The catastrophic destructive-pattern fallback always runs:
71
+ # a dangerous multi-token entry may never add `rm -rf /`-style
72
+ # chains to the allowlist, with or without --force.
73
+ catastrophic = false
74
+ begin
75
+ ctx.guard.verify_command_fallback_regex!(entry)
76
+ rescue Rubik::Security::BlockedWriteError => e
77
+ ctx.out.puts "Blocked: '#{entry}' matches a catastrophic destructive pattern and cannot be allowlisted: #{e.message}"
78
+ catastrophic = true
79
+ end
80
+ return if catastrophic
81
+
82
+ current = Array(ctx.config.fetch("security.command_allowlist", [])).dup
83
+ if current.include?(entry)
84
+ ctx.out.puts "[Rubik::AllowCmd] '#{entry}' is already allowlisted."
85
+ return
86
+ end
87
+ current << entry
88
+ res = ctx.config.set("security.command_allowlist", current, persist: scope)
89
+ ctx.guard&.log_audit("allowlist_add", command: entry, scope: scope)
90
+ next if Base.report_persist_failure(ctx, res, scope)
91
+
92
+ ctx.out.puts "[Rubik::AllowCmd] Added command prefix: #{entry} [#{Base.scope_label(scope)}]."
93
+ end
94
+
95
+ case sub
96
+ when "list", "status"
97
+ entries = Array(ctx.config.fetch("security.command_allowlist", []))
98
+ ctx.out.puts "=== Allowed Command Prefixes (operator allowlist) ==="
99
+ if entries.empty?
100
+ ctx.out.puts " (No command prefixes allowlisted — unknown/dangerous commands stay blocked)"
101
+ else
102
+ entries.each_with_index { |e, idx| ctx.out.puts " • [#{idx + 1}] #{e}" }
103
+ end
104
+ when "add"
105
+ handle_add.call(clean_args[1..])
106
+ when "remove", "rm", "delete"
107
+ entry = clean_args[1..].join(" ").strip
108
+ if entry.empty?
109
+ ctx.out.puts "Usage: !allow-cmd remove \"<command prefix>\" [--project|--global]"
110
+ next
111
+ end
112
+
113
+ current = Array(ctx.config.fetch("security.command_allowlist", [])).dup
114
+ unless current.include?(entry)
115
+ ctx.out.puts "[Rubik::AllowCmd] '#{entry}' is not in the allowlist."
116
+ next
117
+ end
118
+ current.delete(entry)
119
+ res = ctx.config.set("security.command_allowlist", current, persist: scope)
120
+ ctx.guard&.log_audit("allowlist_remove", command: entry, scope: scope)
121
+ next if Base.report_persist_failure(ctx, res, scope)
122
+
123
+ ctx.out.puts "[Rubik::AllowCmd] Removed command prefix: #{entry} [#{Base.scope_label(scope)}]."
124
+ when "help", "-h", "--help"
125
+ ctx.out.puts USAGE_ALLOW_CMD
126
+ else
127
+ handle_add.call(clean_args)
128
+ end
129
+ end
130
+ end
131
+
132
+ private_class_method def self.register_workspace_command(shell)
133
+ workspace_handler = lambda do |args, ctx|
134
+ scope, clean_args = Base.parse_scope(args)
135
+ is_rw = clean_args.include?("--rw") || clean_args.include?("-w")
136
+ is_ro = clean_args.include?("--ro") || clean_args.include?("-r")
137
+ clean_args = clean_args.reject { |a| ["--rw", "-w", "--ro", "-r"].include?(a) }
138
+ sub = clean_args.first&.downcase || "list"
139
+
140
+ effective_root = ctx.buffer_manager&.root_dir || Dir.pwd
141
+
142
+ case sub
143
+ when "list", "status"
144
+ extra = ctx.buffer_manager&.additional_roots || []
145
+ rw_set = ctx.buffer_manager&.rw_roots || Set.new
146
+ allowed_ext = ctx.buffer_manager&.allowed_external_dirs || []
147
+ rw_allowed_set = ctx.buffer_manager&.rw_allowed_dirs || Set.new
148
+ glob_rw = ctx.buffer_manager&.allow_external_writes || false
149
+ auto_idx = ctx.config.fetch("workspace.auto_index_additional", true)
150
+
151
+ ctx.out.puts "=== Configured Workspace Roots ==="
152
+ ctx.out.puts " • [PRIMARY] #{effective_root} (Read-Write)"
153
+ if extra.empty?
154
+ ctx.out.puts " (No additional workspace roots configured)"
155
+ else
156
+ extra.each_with_index do |r, idx|
157
+ mode_str = glob_rw || rw_set.include?(r) ? "Read-Write" : "Read-Only"
158
+ ctx.out.puts " • [EXTRA #{idx + 1}] #{r} (#{mode_str})"
159
+ end
160
+ end
161
+
162
+ ctx.out.puts "=== Allowed External Dirs (Zero-AST) ==="
163
+ if allowed_ext.empty?
164
+ ctx.out.puts " (No allowed external directories configured)"
165
+ else
166
+ allowed_ext.each_with_index do |d, idx|
167
+ mode_str = glob_rw || rw_allowed_set.include?(d) ? "Read-Write" : "Read-Only"
168
+ ctx.out.puts " • [ALLOWED #{idx + 1}] #{d} (#{mode_str})"
169
+ end
170
+ end
171
+
172
+ ctx.out.puts "External Writes Allowed Globally : #{glob_rw}"
173
+ ctx.out.puts "Auto-Index Additional Roots : #{auto_idx}"
174
+ when "allow"
175
+ target_path = clean_args[1]
176
+ if target_path.nil? || target_path.empty?
177
+ ctx.out.puts "Usage: !workspace allow <path> [--rw|--ro] [--project|--global]"
178
+ else
179
+ begin
180
+ ctx.guard.verify_root_safety!(target_path)
181
+ rescue Rubik::Security::BlockedWriteError => e
182
+ ctx.out.puts "Security Error: #{e.message}"
183
+ next
184
+ end
185
+
186
+ mode_rw = !is_ro
187
+ expanded = ctx.buffer_manager.add_allowed_dir(target_path, rw: mode_rw)
188
+ ctx.sandbox_runner&.add_allowed_dir(target_path)
189
+
190
+ current_dirs = Array(ctx.config.fetch("workspace.allowed_external_dirs", [])).dup
191
+ current_dirs << target_path unless current_dirs.include?(target_path)
192
+ res = ctx.config.set("workspace.allowed_external_dirs", current_dirs, persist: scope)
193
+ next if Base.report_persist_failure(ctx, res, scope)
194
+
195
+ mode_str = mode_rw ? "Read-Write" : "Read-Only"
196
+ ctx.out.puts "[Rubik::Allow] Added allowed external directory: #{expanded} (Mode: #{mode_str}) [#{Base.scope_label(scope)}]."
197
+ end
198
+ when "add"
199
+ target_path = clean_args[1]
200
+ if target_path.nil? || target_path.empty?
201
+ ctx.out.puts "Usage: !workspace add <path> [--rw] [--project|--global]"
202
+ else
203
+ begin
204
+ ctx.guard.verify_root_safety!(target_path)
205
+ rescue Rubik::Security::BlockedWriteError => e
206
+ ctx.out.puts "Security Error: #{e.message}"
207
+ next
208
+ end
209
+
210
+ expanded = ctx.buffer_manager.add_root(target_path, rw: is_rw)
211
+
212
+ # Sync context index & AST coeffects
213
+ auto_idx = ctx.config.fetch("workspace.auto_index_additional", true)
214
+ ctx.sync_workspace_index! if auto_idx
215
+
216
+ # Persist to config
217
+ current_roots = Array(ctx.config.fetch("workspace.additional_roots", [])).dup
218
+ current_roots << target_path unless current_roots.include?(target_path)
219
+ res = ctx.config.set("workspace.additional_roots", current_roots, persist: scope)
220
+ next if Base.report_persist_failure(ctx, res, scope)
221
+
222
+ mode_str = is_rw ? "Read-Write" : "Read-Only"
223
+ ctx.out.puts "[Rubik::Workspace] Added additional root: #{expanded} (Mode: #{mode_str}) [#{Base.scope_label(scope)}]."
224
+ end
225
+ when "remove", "rm", "delete"
226
+ target_path = clean_args[1]
227
+ if target_path.nil? || target_path.empty?
228
+ ctx.out.puts "Usage: !workspace remove <path> [--project|--global]"
229
+ else
230
+ expanded = ctx.buffer_manager.remove_root(target_path)
231
+
232
+ # Sync context index & AST coeffects
233
+ auto_idx = ctx.config.fetch("workspace.auto_index_additional", true)
234
+ ctx.sync_workspace_index! if auto_idx
235
+
236
+ # Persist to config
237
+ current_roots = Array(ctx.config.fetch("workspace.additional_roots", [])).dup
238
+ current_roots.reject! { |r| File.expand_path(r, effective_root) == expanded || r == target_path }
239
+ res = ctx.config.set("workspace.additional_roots", current_roots, persist: scope)
240
+ next if Base.report_persist_failure(ctx, res, scope)
241
+
242
+ ctx.out.puts "[Rubik::Workspace] Removed additional root: #{expanded} [#{Base.scope_label(scope)}]."
243
+ end
244
+ else
245
+ ctx.out.puts USAGE_WORKSPACE
246
+ end
247
+ end
248
+
249
+ shell.register_command("workspace", description: "Manage multiple workspace roots (list|add <path> [--rw]|allow <path>|remove <path>)",
250
+ usage: USAGE_WORKSPACE, &workspace_handler)
251
+ shell.register_command("roots", description: "Alias for !workspace", usage: USAGE_WORKSPACE, &workspace_handler)
252
+ end
253
+
254
+ private_class_method def self.register_allow_command(shell)
255
+ allow_handler = lambda do |args, ctx, invoked_as: :allow_dir|
256
+ scope, clean_args = Base.parse_scope(args)
257
+ is_ro = clean_args.include?("--ro") || clean_args.include?("-r")
258
+ clean_args = clean_args.reject { |a| ["--rw", "-w", "--ro", "-r"].include?(a) }
259
+
260
+ mode_rw = !is_ro
261
+ sub = clean_args.first&.downcase || "list"
262
+ effective_root = ctx.buffer_manager&.root_dir || Dir.pwd
263
+
264
+ case sub
265
+ when "list", "status"
266
+ allowed = ctx.buffer_manager&.allowed_external_dirs || []
267
+ rw_set = ctx.buffer_manager&.rw_allowed_dirs || Set.new
268
+ glob_rw = ctx.buffer_manager&.allow_external_writes || false
269
+
270
+ ctx.out.puts "=== Allowed External Directories (Zero-AST / Zero-Scanner) ==="
271
+ if allowed.empty?
272
+ ctx.out.puts " (No allowed external directories configured)"
273
+ else
274
+ allowed.each_with_index do |d, idx|
275
+ mode_str = glob_rw || rw_set.include?(d) ? "Read-Write" : "Read-Only"
276
+ ctx.out.puts " • [ALLOWED #{idx + 1}] #{d} (#{mode_str})"
277
+ end
278
+ end
279
+ when "remove", "rm", "delete"
280
+ target_path = clean_args[1]
281
+ if target_path.nil? || target_path.empty?
282
+ ctx.out.puts "Usage: !allow remove <path> [--project|--global]"
283
+ else
284
+ expanded = ctx.buffer_manager&.remove_allowed_dir(target_path)
285
+ ctx.sandbox_runner&.remove_allowed_dir(target_path)
286
+
287
+ # Persist to config
288
+ current_dirs = Array(ctx.config.fetch("workspace.allowed_external_dirs", [])).dup
289
+ current_dirs.reject! { |d| File.expand_path(d, effective_root) == expanded || d == target_path }
290
+ res = ctx.config.set("workspace.allowed_external_dirs", current_dirs, persist: scope)
291
+ next if Base.report_persist_failure(ctx, res, scope)
292
+
293
+ ctx.out.puts "[Rubik::Allow] Removed allowed external directory: #{expanded} [#{Base.scope_label(scope)}]."
294
+ end
295
+ when "add"
296
+ target_path = clean_args[1]
297
+ if target_path.nil? || target_path.empty?
298
+ ctx.out.puts "Usage: !allow add <path> [--rw|--ro] [--project|--global]"
299
+ else
300
+ begin
301
+ ctx.guard.verify_root_safety!(target_path)
302
+ rescue Rubik::Security::BlockedWriteError => e
303
+ ctx.out.puts "Security Error: #{e.message}"
304
+ next
305
+ end
306
+
307
+ expanded = ctx.buffer_manager.add_allowed_dir(target_path, rw: mode_rw)
308
+ ctx.sandbox_runner&.add_allowed_dir(target_path)
309
+
310
+ # Persist to config (Zero-AST / Zero-Scanner: No IndexService or AST warm)
311
+ current_dirs = Array(ctx.config.fetch("workspace.allowed_external_dirs", [])).dup
312
+ current_dirs << target_path unless current_dirs.include?(target_path)
313
+ res = ctx.config.set("workspace.allowed_external_dirs", current_dirs, persist: scope)
314
+ next if Base.report_persist_failure(ctx, res, scope)
315
+
316
+ mode_str = mode_rw ? "Read-Write" : "Read-Only"
317
+ ctx.out.puts "[Rubik::Allow] Added allowed external directory: #{expanded} (Mode: #{mode_str}) [#{Base.scope_label(scope)}]."
318
+ end
319
+ else
320
+ # First argument is target path directly
321
+ target_path = clean_args.first
322
+ if target_path && !target_path.empty?
323
+ begin
324
+ ctx.guard.verify_root_safety!(target_path)
325
+ rescue Rubik::Security::BlockedWriteError => e
326
+ ctx.out.puts "Security Error: #{e.message}"
327
+ next
328
+ end
329
+
330
+ expanded = ctx.buffer_manager.add_allowed_dir(target_path, rw: mode_rw)
331
+ ctx.sandbox_runner&.add_allowed_dir(target_path)
332
+
333
+ current_dirs = Array(ctx.config.fetch("workspace.allowed_external_dirs", [])).dup
334
+ current_dirs << target_path unless current_dirs.include?(target_path)
335
+ res = ctx.config.set("workspace.allowed_external_dirs", current_dirs, persist: scope)
336
+ next if Base.report_persist_failure(ctx, res, scope)
337
+
338
+ mode_str = mode_rw ? "Read-Write" : "Read-Only"
339
+ ctx.out.puts "[Rubik::Allow] Added allowed external directory: #{expanded} (Mode: #{mode_str}) [#{Base.scope_label(scope)}]."
340
+ else
341
+ cmd_name = invoked_as == :allow ? "allow" : "allow-dir"
342
+ ctx.out.puts format(USAGE_ALLOW, cmd: cmd_name)
343
+ end
344
+ end
345
+ end
346
+
347
+ # Plan (komut-allowlist): `!allow-dir` is the primary command name;
348
+ # `!allow` survives as a one-line deprecation wrapper around the same
349
+ # handler (no silent breakage — Plan 48 muscle memory keeps working).
350
+ shell.register_command("allow-dir",
351
+ description: "Allow external directory for Buffer I/O and Sandbox without AST indexing " \
352
+ "(!allow-dir <path> [--rw|--ro]|list|remove)",
353
+ usage: format(USAGE_ALLOW, cmd: "allow-dir"), &allow_handler)
354
+ deprecated_allow_wrapper = lambda do |args, ctx|
355
+ ctx.out.puts "[Deprecation] '!allow' is an alias — use '!allow-dir' (folders) / '!allow-cmd' (commands)."
356
+ allow_handler.call(args, ctx, invoked_as: :allow)
357
+ end
358
+ shell.register_command("allow", description: "Deprecated alias for !allow-dir",
359
+ usage: format(USAGE_ALLOW, cmd: "allow"), &deprecated_allow_wrapper)
360
+ end
361
+
362
+ private_class_method def self.register_config_command(shell)
363
+ shell.register_command("config", description: "Get, set, or view runtime configuration (set|get|list|path)",
364
+ usage: USAGE_CONFIG, audience: :both) do |args, ctx|
365
+ scope, clean_args = Base.parse_scope(args)
366
+ sub = clean_args.first&.downcase
367
+
368
+ if sub.nil? || sub.empty?
369
+ ctx.out.puts USAGE_CONFIG
370
+ elsif ["path", "paths"].include?(sub)
371
+ ctx.out.puts "Global Config : #{ctx.config.global_config_path} (exists: #{File.exist?(ctx.config.global_config_path)})"
372
+ ctx.out.puts "Project Config : #{ctx.config.project_config_path} (exists: #{File.exist?(ctx.config.project_config_path)})"
373
+ elsif sub == "get"
374
+ key = clean_args[1]
375
+ if key.nil? || key.empty?
376
+ ctx.out.puts "Usage: !config get <key>"
377
+ else
378
+ val = ctx.config.fetch(key)
379
+ if val.nil?
380
+ ctx.out.puts "Config '#{key}' is not set or empty."
381
+ else
382
+ ctx.out.puts "#{key} = #{val}"
383
+ end
384
+ end
385
+ elsif sub == "set"
386
+ key = clean_args[1]
387
+ val = clean_args[2..]&.join(" ")
388
+ if key.nil? || key.empty? || val.nil? || val.empty?
389
+ ctx.out.puts "Usage: !config set <key> <val> [--project|--global]"
390
+ elsif ctx.agent_caller && key.start_with?("security.", "sandbox.", "subagents.")
391
+ # Paket D fail-closed: the agent cannot grant itself command
392
+ # allowlist entries or toggle sandbox enforcement. subagents.*
393
+ # da operatör-only: ajan kendi tasmasını (maestro_gate,
394
+ # concurrency, reap grace) exec_repl_command ile çözemez —
395
+ # kapı canlı config okur, self-unlock yolu bu yüzden kapalı.
396
+ ctx.out.puts "Security Error: keys under 'security.*' and 'sandbox.*' are operator-only " \
397
+ "(mutated via !allow-cmd / !allow-dir as the human operator)."
398
+ else
399
+ res = ctx.config.set(key, val, persist: scope)
400
+ next if Base.report_persist_failure(ctx, res, scope)
401
+
402
+ ctx.out.puts "Config '#{key}' updated to '#{val}' [#{Base.scope_label(scope)}]."
403
+ end
404
+ elsif sub == "list"
405
+ filter = clean_args[1]&.downcase
406
+ flattened = {}
407
+ flatten_proc = lambda do |hash, prefix|
408
+ hash.each do |k, v|
409
+ full_k = prefix.empty? ? k.to_s : "#{prefix}.#{k}"
410
+ if v.is_a?(Hash)
411
+ flatten_proc.call(v, full_k)
412
+ else
413
+ flattened[full_k] = v
414
+ end
415
+ end
416
+ end
417
+ flatten_proc.call(ctx.config.data, "")
418
+
419
+ matched = filter ? flattened.select { |k, _| k.downcase.include?(filter) } : flattened
420
+ if matched.empty?
421
+ ctx.out.puts "No configuration entries found#{" matching '#{filter}'" if filter}."
422
+ else
423
+ ctx.out.puts "=== Rubik Configuration (#{matched.size} entries) ==="
424
+ matched.sort.each do |k, v|
425
+ ctx.out.puts " • #{k.ljust(35)} = #{v.inspect}"
426
+ end
427
+ end
428
+ else
429
+ # Smart guidance for verbless input
430
+ ctx.out.puts "[Rubik::Config] Geçersiz kullanım."
431
+ if clean_args.size >= 2
432
+ ctx.out.puts " • Ayar yapmak için : !config set #{clean_args[0]} #{clean_args[1]}"
433
+ else
434
+ ctx.out.puts " • Ayar yapmak için : !config set #{clean_args[0]} <val>"
435
+ end
436
+ ctx.out.puts " • Değer okumak için: !config get #{clean_args[0]}"
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end