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,342 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../runtime"
5
+ require_relative "../../sandbox"
6
+ require_relative "../../orchestrator/auto_apply"
7
+
8
+ module Rubik
9
+ module REPL
10
+ module Commands
11
+ module AgentExecution
12
+ extend Base
13
+
14
+ # Whole-command synopses: shared by the spine registration (usage:)
15
+ # and the empty-args branches that print them, so the two cannot drift.
16
+ USAGE_PLAN = "Kullanım: !plan <görev açıklaması>"
17
+ USAGE_GOAL = "Kullanım: !goal <hedef açıklaması>"
18
+
19
+ def self.register(shell)
20
+ register_plan_command(shell)
21
+ register_goal_command(shell)
22
+ register_test_command(shell)
23
+ register_lint_command(shell)
24
+ register_format_command(shell)
25
+ register_sh_command(shell)
26
+ register_sandbox_command(shell)
27
+ end
28
+
29
+ private_class_method def self.register_plan_command(shell)
30
+ shell.register_command("plan", description: "Formulate step-by-step plan for a task and ask for confirmation",
31
+ usage: USAGE_PLAN) do |args, ctx|
32
+ task = args.join(" ").strip
33
+ if task.empty?
34
+ ctx.out.puts USAGE_PLAN
35
+ next
36
+ end
37
+
38
+ plan_steps = ctx.config.fetch("agent.plan_max_steps", 15).to_i
39
+ routed = ctx.router.route(task_type: :plan, prompt: task)
40
+ ctx.runtime_manager.provide(:llm_client, routed[:provider]) if routed[:provider]
41
+ orchestrator = ctx.orchestrator_loop(
42
+ provider: routed[:provider],
43
+ model: routed[:model],
44
+ max_steps: plan_steps,
45
+ context_limit: routed[:context_limit],
46
+ max_output_tokens: routed[:max_output_tokens],
47
+ on_step: ->(step_info) { ctx.render_step_event(step_info) }
48
+ )
49
+
50
+ ctx.out.puts "[Rubik::Plan] Generating plan using #{routed[:role]} model (#{routed[:model]})..."
51
+ res = orchestrator.run(task, plan_only: true)
52
+
53
+ if res[:ok]
54
+ ctx.out.puts "\n=== Proposed Plan ==="
55
+ ctx.out.puts res[:output]
56
+ ctx.out.puts "=====================\n"
57
+ ans = ctx.ask_yes_no("[Rubik::Plan] Plan uygulansın mı?", default: "y")
58
+ if ["y", "yes", "evet"].include?(ans)
59
+ ctx.out.puts "[Rubik::Act] Executing approved plan..."
60
+ ctx.execute_free_text("Execute the following approved plan step by step:\n#{res[:output]}")
61
+ else
62
+ ctx.out.puts "[Rubik] Plan iptal edildi."
63
+ end
64
+ else
65
+ ctx.out.puts "Plan üretilemedi: #{res[:error]}"
66
+ end
67
+ end
68
+ end
69
+
70
+ private_class_method def self.register_goal_command(shell)
71
+ shell.register_command("goal", description: "Run long-running autonomous goal loop (the maestro supervises a subagent pipeline) until all tests pass",
72
+ usage: USAGE_GOAL) do |args, ctx|
73
+ goal_text = args.join(" ").strip
74
+ if goal_text.empty?
75
+ ctx.out.puts USAGE_GOAL
76
+ next
77
+ end
78
+
79
+ ctx.recorder&.record_user_turn("!goal #{goal_text}")
80
+ goal_steps = ctx.config.fetch("agent.goal_max_steps", 100).to_i
81
+ warn_ratio = ctx.config.fetch("agent.warn_at_ratio", 0.75).to_f
82
+ routed = ctx.router.route(task_type: :goal, prompt: goal_text)
83
+ ctx.runtime_manager.provide(:llm_client, routed[:provider]) if routed[:provider]
84
+
85
+ # Canlı hedef durumu: maestro ve alt ajanlar read_file
86
+ # rubik://goal/state ile aynı anlık görüntüyü okur. Provide swap
87
+ # semantiğine sahiptir — tekrarlanan !goal koşuları Ω'ya inverse
88
+ # yığmaz; session dispose'u tek delete-inverse ile temizler.
89
+ # event_bus: bitiş olayı (GoalState::FINISH_EVENT) Vakanüvis
90
+ # kronikçisinin goal kancasına akar (K3) — notice/wake yok.
91
+ goal_state = Rubik::Orchestrator::GoalState.new(event_bus: ctx.event_bus)
92
+ goal_state.start!(goal_text, goal_steps)
93
+ ctx.runtime_manager.provide(Rubik::Coeffects::GOAL_STATE, goal_state)
94
+
95
+ # Yapılandırılmış Σ defteri (goal-ledger planı A): knob ON iken
96
+ # koşuyla doğar, terminal dallarda finalize olur, provide swap
97
+ # semantiğiyle tekrarlanan !goal koşuları Ω'ya inverse yığmaz.
98
+ # OFF iken hiçbir yüz kurulmaz (araç/URI/prompt bayt-özdeş).
99
+ goal_ledger = nil
100
+ if Rubik::Orchestrator::GoalLedger::Ledger.enabled?(ctx.config)
101
+ store = Rubik::Coeffects.get(ctx.runtime_manager&.context, Rubik::Coeffects::STORAGE_STORE)
102
+ dmesg = Rubik::Coeffects.get(ctx.runtime_manager&.context, Rubik::Coeffects::DMESG)
103
+ goal_ledger = Rubik::Orchestrator::GoalLedger::Ledger.create!(
104
+ goal_text: goal_text, config: ctx.config, store: store, dmesg: dmesg,
105
+ event_bus: ctx.event_bus
106
+ )
107
+ ctx.runtime_manager.provide(Rubik::Coeffects::GOAL_LEDGER, goal_ledger)
108
+ end
109
+ finalize_ledger = lambda do |status|
110
+ next unless goal_ledger
111
+
112
+ goal_ledger.finalize!(status)
113
+ goal_state.ledger_id = goal_ledger.snapshot[:storage_id]
114
+ end
115
+
116
+ ctx.out.puts "[Rubik::Goal] Starting autonomous goal loop (Budget: #{goal_steps} steps) with #{routed[:role]} model (#{routed[:model]})..."
117
+ ctx.out.puts "[Rubik::Goal] Target: #{goal_text}\n"
118
+
119
+ orchestrator = ctx.orchestrator_loop(
120
+ provider: routed[:provider],
121
+ model: routed[:model],
122
+ max_steps: goal_steps,
123
+ warn_at_ratio: warn_ratio,
124
+ context_limit: routed[:context_limit],
125
+ max_output_tokens: routed[:max_output_tokens],
126
+ on_step: lambda { |step_info|
127
+ # Adım sayacı rubik://goal/state'e aksın: olaylar bir adım
128
+ # içinde tekrarlanabilir, GoalState monotoniktir (zararsız).
129
+ goal_state.update_steps(step_info[:step], step_info[:max_steps]) if step_info[:step].is_a?(Integer)
130
+ ctx.render_step_event(step_info)
131
+ }
132
+ )
133
+
134
+ # Plan 53 D-8 yardımcısı: nötr çift + kayıtçı eşlemesi tek yerde.
135
+ # Session restore kayıtçı OLAYLARINDAN hidrate eder — eşlenmiş
136
+ # restore için conversation_history VE record_assistant_response
137
+ # birlikte yazılmalı (başarısızlıkta da).
138
+ record_goal_pair = lambda do |assistant_content, streamed: nil, tokens: nil|
139
+ ctx.conversation_history << { role: "user", content: "Goal: #{goal_text}" }
140
+ ctx.conversation_history << { role: "assistant", content: assistant_content }
141
+ ctx.recorder&.record_assistant_response(assistant_content, tokens: tokens, streamed: streamed)
142
+ ctx.trim_conversation_window!
143
+ end
144
+
145
+ # Pruner apply seam: hedef döngüsü de aynı sınırda tohumlanır —
146
+ # aşamalı plan varsa goal promptundan ÖNCE uygulanır.
147
+ ctx.apply_staged_prune_plan!
148
+ ctx.active_loop = orchestrator
149
+ begin
150
+ res = orchestrator.run(goal_text, goal_mode: true, conversation_history: ctx.conversation_history)
151
+
152
+ # Bütçe tükenince sor (execute_free_text simetrisi; aynı knob,
153
+ # aynı +20). continue_loop boyunca active_loop DOLU kalmalı:
154
+ # notice yönlendirmesi aktif döngüye bakar (lifecycle.rb
155
+ # route_subagent_notice) — soruya beklerken gelen subagent
156
+ # olayları loop inbox'a girer ve devam adımında boşaltılır.
157
+ while !res[:ok] && res[:error]&.include?("Max steps budget exceeded") && ctx.config.fetch("agent.ask_continue_on_limit", true)
158
+ ctx.out.puts "\n⚠️ [Rubik::Goal] #{res[:error]}"
159
+ ans = ctx.ask_yes_no(" [Rubik::Goal] +20 adım ile devam edilsin mi?", default: "n")
160
+ break unless ["y", "yes", "evet"].include?(ans)
161
+
162
+ ctx.out.puts " [Rubik::Goal] Adım bütçesi +20 artırılıyor ve hedef sürdürülüyor..."
163
+ goal_state.extend!(goal_state.max_steps + 20)
164
+ res = orchestrator.continue_loop(20)
165
+ end
166
+ rescue Interrupt
167
+ # ^C hedefi 'running' bırakmasın; eşlenmiş kayıt otursun. Interrupt
168
+ # yeniden yükselir — kullanıcı mesajını execute_command basar.
169
+ goal_state.stop!("Interrupted by user (^C)")
170
+ finalize_ledger.call(:stopped)
171
+ record_goal_pair.call("[Goal attempt interrupted after #{goal_state.step_count} steps]")
172
+ raise
173
+ ensure
174
+ ctx.active_loop = nil
175
+ end
176
+ if res[:ok]
177
+ active = goal_active_subagent_count(ctx)
178
+ if active.positive?
179
+ # Defense behind the loop-level goal guard (GoalWait): a
180
+ # success terminal escaping with live subagents means the
181
+ # pipeline is mid-flight — an honest stop beats a false
182
+ # "Goal Achieved" (field incident 2026-10-05).
183
+ reason = "Goal loop concluded with #{active} active subagent(s) still running — not marked complete"
184
+ goal_state.stop!(reason)
185
+ finalize_ledger.call(:stopped)
186
+ ctx.out.puts "\n⚠️ [Rubik::Goal] #{reason}."
187
+ record_goal_pair.call("[Goal attempt ended after #{goal_state.step_count} steps: #{reason}]", tokens: res[:tokens])
188
+ else
189
+ goal_state.complete!(res[:output].to_s, tokens: res[:tokens])
190
+ finalize_ledger.call(:completed)
191
+ ctx.out.puts "\n🎉 Goal Achieved in #{res[:steps]} steps!"
192
+ ctx.out.puts res[:output]
193
+ # Keep the bang syntax out of the LLM context (Plan 53 D-8): the
194
+ # session recorder already stores the literal "!goal ..." for replay.
195
+ record_goal_pair.call(res[:output].to_s, streamed: res[:streamed] == true, tokens: res[:tokens])
196
+
197
+ ctx.out.puts "\nTip: Use `!diff` to review changes and `!apply` to commit to disk." if ctx.buffer_manager.dirty?
198
+ end
199
+ else
200
+ # Başarısızlık eşlemesi: kayıtçı '!goal ...' user_turn'unu komut
201
+ # girişinde çoktan yazdı — assistant_response olmadan session
202
+ # restore EŞLEŞMEMİŞ bir 'Goal: ...' turu hidrate ederdi.
203
+ # Adım sayısı: loop'un truncation hatası :steps'siz döner —
204
+ # GoalState monotonik sayacı otoriter yedek.
205
+ sanitized = Rubik::Platform.safe_utf8(res[:error].to_s)
206
+ steps_used = res[:steps] || goal_state.step_count
207
+ goal_state.fail!(sanitized, tokens: res[:tokens])
208
+ finalize_ledger.call(:failed)
209
+ ctx.out.puts "\n❌ Goal could not be completed: #{sanitized}"
210
+ record_goal_pair.call("[Goal attempt ended after #{steps_used} steps: #{sanitized}]", tokens: res[:tokens])
211
+ end
212
+ end
213
+ end
214
+
215
+ # Defense-in-depth behind the loop-level goal guard: if a success
216
+ # terminal escapes GoalWait with live subagent units, the goal must
217
+ # stop honestly instead of celebrating a false completion. Fails
218
+ # CONSERVATIVE: coeffect absence is nil-safe (Coeffects.get never
219
+ # raises for a missing subsystem), so a raise here is real breakage
220
+ # — reporting zero would launder it into exactly the false
221
+ # "Goal Achieved" this backstop exists to refuse.
222
+ private_class_method def self.goal_active_subagent_count(ctx)
223
+ supervisor = Rubik::Coeffects.get(ctx.runtime_manager&.context, Rubik::Coeffects::SUBAGENT_SUPERVISOR)
224
+ supervisor&.active_count.to_i
225
+ rescue StandardError => e
226
+ Rubik::Log.warn(:orchestrator, "goal completion defense could not inspect subagents " \
227
+ "(#{e.class}: #{e.message}); refusing completion")
228
+ 1
229
+ end
230
+
231
+ private_class_method def self.register_test_command(shell)
232
+ shell.register_command("test", description: "Run test suite via active language adapter") do |args, ctx|
233
+ if ctx.buffer_manager&.dirty?
234
+ if Rubik::Orchestrator::AutoApply.apply?(ctx.config, trigger: :pre_execution)
235
+ applied = Rubik::Orchestrator::AutoApply.apply_and_sync(ctx.buffer_manager, ctx)
236
+ ctx.out.puts Rubik::Orchestrator::AutoApply.applied_line(applied.size, "test run")
237
+ else
238
+ ctx.out.puts Rubik::Orchestrator::AutoApply.unapplied_warning("Tests")
239
+ end
240
+ end
241
+ lang = args.first || Rubik::Runtime::Discovery.detect_primary_language(Dir.pwd) || "ruby"
242
+ adapter = ctx.adapter_registry.get(lang) || ctx.adapter_registry.get("ruby")
243
+ res = adapter.run_tests(Dir.pwd)
244
+ ctx.out.puts res[:output]
245
+ if !res[:ok] && res[:diagnostic]
246
+ ctx.out.puts "\n🔍 [Diagnostic] #{res[:diagnostic].category.to_s.upcase}: #{res[:diagnostic].suggestion}"
247
+ ctx.out.puts "Tip: Use `repair_runtime_service` (or `fix_service`) to adapt test runners dynamically."
248
+ end
249
+ end
250
+ end
251
+
252
+ private_class_method def self.register_lint_command(shell)
253
+ shell.register_command("lint", description: "Run linter/syntax check", audience: :both) do |args, ctx|
254
+ target = args.first || "lib/rubik.rb"
255
+ lang = Rubik::Runtime::Discovery.detect_primary_language(Dir.pwd) || "ruby"
256
+ adapter = ctx.adapter_registry.get(lang) || ctx.adapter_registry.get("ruby")
257
+ res = adapter.check_syntax(target)
258
+ ctx.out.puts res[:output]
259
+ ctx.out.puts "\n🔍 [Diagnostic] #{res[:diagnostic].category.to_s.upcase}: #{res[:diagnostic].suggestion}" if !res[:ok] && res[:diagnostic]
260
+ end
261
+ end
262
+
263
+ private_class_method def self.register_format_command(shell)
264
+ shell.register_command("format", description: "Format target file", audience: :both) do |args, ctx|
265
+ target = args.first
266
+ if target
267
+ lang = Rubik::Runtime::Discovery.detect_primary_language(Dir.pwd) || "ruby"
268
+ adapter = ctx.adapter_registry.get(lang) || ctx.adapter_registry.get("ruby")
269
+ res = adapter.format_code(target)
270
+ ctx.out.puts res[:output]
271
+ ctx.out.puts "\n🔍 [Diagnostic] #{res[:diagnostic].category.to_s.upcase}: #{res[:diagnostic].suggestion}" if !res[:ok] && res[:diagnostic]
272
+ end
273
+ end
274
+ end
275
+
276
+ private_class_method def self.register_sh_command(shell)
277
+ shell.register_command("sh", description: "Execute shell command via sandbox runner") do |args, ctx|
278
+ if ctx.buffer_manager&.dirty?
279
+ if Rubik::Orchestrator::AutoApply.apply?(ctx.config, trigger: :pre_execution)
280
+ applied = Rubik::Orchestrator::AutoApply.apply_and_sync(ctx.buffer_manager, ctx)
281
+ ctx.out.puts Rubik::Orchestrator::AutoApply.applied_line(applied.size, "command execution")
282
+ else
283
+ ctx.out.puts Rubik::Orchestrator::AutoApply.unapplied_warning("Commands")
284
+ end
285
+ end
286
+
287
+ cmd = args.join(" ")
288
+ ctx.guard.verify_command_safety!(cmd)
289
+ runner = ctx.sandbox_runner || Rubik::Sandbox::Runner.new(root_dir: Dir.pwd, config: ctx.config, guard: ctx.guard)
290
+ res = runner.run(cmd)
291
+ ctx.out.puts res[:output]
292
+ end
293
+ end
294
+
295
+ private_class_method def self.register_sandbox_command(shell)
296
+ shell.register_command("sandbox", description: "Inspect or configure sandbox isolation (bwrap/docker/podman)") do |args, ctx|
297
+ scope, clean_args = Base.parse_scope(args)
298
+ subcmd = clean_args.first.to_s.downcase
299
+
300
+ registered_map = Rubik::Sandbox::Driver.registered_drivers
301
+ registered_keys = registered_map.keys.uniq
302
+
303
+ if clean_args.empty? || subcmd == "status"
304
+ enabled = ctx.config.fetch("sandbox.enabled", false)
305
+ state_str = enabled ? "Açık (enabled)" : "Kapalı (disabled - direct safe execution)"
306
+ driver_name = ctx.sandbox_runner&.driver&.name || ctx.config.fetch("sandbox.driver", "bwrap")
307
+ net_str = ctx.config.fetch("sandbox.allow_network", false) ? "allowed" : "blocked"
308
+
309
+ ctx.out.puts "=== Sandbox Durumu ==="
310
+ ctx.out.puts "Durum: #{state_str}"
311
+ ctx.out.puts "Aktif Motor: #{driver_name}"
312
+ ctx.out.puts "Ağ: #{net_str}"
313
+ ctx.out.puts "Kayıtlı: #{registered_keys.join(', ')}"
314
+ elsif subcmd == "on"
315
+ res = ctx.config.set("sandbox.enabled", true, persist: scope)
316
+ driver_res = ctx.config.set("sandbox.driver", "bwrap", persist: scope) if Rubik::Platform.linux?
317
+ next if Base.report_persist_failure(ctx, res, scope)
318
+ next if Base.report_persist_failure(ctx, driver_res, scope)
319
+
320
+ ctx.out.puts "[Sandbox] Enabled (#{ctx.sandbox_runner&.driver&.name || 'default'})."
321
+ elsif subcmd == "off"
322
+ res = ctx.config.set("sandbox.enabled", false, persist: scope)
323
+ next if Base.report_persist_failure(ctx, res, scope)
324
+
325
+ ctx.out.puts "[Sandbox] Disabled (direct safe execution active)."
326
+ elsif registered_map.key?(subcmd)
327
+ ctx.out.puts "⚠️ [Sandbox] 'bwrap' yalnızca Linux üzerinde desteklenir." if ["bwrap", "bubblewrap"].include?(subcmd) && !Rubik::Platform.linux?
328
+ res = ctx.config.set("sandbox.enabled", true, persist: scope)
329
+ driver_res = ctx.config.set("sandbox.driver", subcmd, persist: scope)
330
+ next if Base.report_persist_failure(ctx, res, scope)
331
+ next if Base.report_persist_failure(ctx, driver_res, scope)
332
+
333
+ ctx.out.puts "[Sandbox] Driver set to '#{subcmd}' (enabled)."
334
+ else
335
+ ctx.out.puts "[Sandbox] Bilinmeyen alt komut veya sürücü: '#{subcmd}'. Kayıtlı sürücüler: #{registered_keys.join(', ')}"
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../llm"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ module AgentModels
10
+ extend Base
11
+
12
+ def self.parse_model_target(target, ctx)
13
+ return ["ollama", ""] if target.nil? || target.empty?
14
+
15
+ raw_target = target.strip
16
+
17
+ # Extract registered providers from registry and config
18
+ registered_providers = []
19
+ registered_providers += ctx.llm_registry.list if ctx.llm_registry
20
+ registered_providers += ctx.config.fetch("providers", {}).keys if ctx.config
21
+ registered_providers = registered_providers.map { |p| p.to_s.downcase }.uniq
22
+
23
+ # 1. Explicit provider prefix with colon: e.g. "openrouter:anthropic/claude-3.5-sonnet"
24
+ if raw_target.include?(":")
25
+ parts = raw_target.split(":", 2)
26
+ first_part = parts.first.downcase
27
+
28
+ if registered_providers.include?(first_part)
29
+ prov = first_part
30
+ mod = parts[1]
31
+ return [prov, mod]
32
+ end
33
+ end
34
+
35
+ # 2. Explicit provider prefix with slash: e.g. "ollama/qwen2.5-coder:7b" or "openrouter/cline-pass/deepseek-v4-flash"
36
+ if raw_target.include?("/")
37
+ parts = raw_target.split("/")
38
+ first_part = parts.first.downcase
39
+
40
+ if registered_providers.include?(first_part)
41
+ prov = first_part
42
+ mod = parts[1..].join("/")
43
+ return [prov, mod]
44
+ end
45
+
46
+ # Check if this exact target is registered in any provider's model catalog
47
+ if ctx.llm_registry&.models_catalog
48
+ catalog_match = ctx.llm_registry.models_catalog.find { |_k, v| v[:name] == raw_target }
49
+ return [catalog_match[1][:provider], raw_target] if catalog_match
50
+ end
51
+
52
+ # First part is not a known provider; treat entire string as model under default provider
53
+ current_default_prov = ctx.config.model_spec(:default)&.dig("provider") || "ollama"
54
+ return [current_default_prov, raw_target]
55
+ end
56
+
57
+ # 3. No slash, exactly registered provider name alone
58
+ if registered_providers.include?(raw_target.downcase)
59
+ default_mod = ctx.llm_registry.get(raw_target.downcase).respond_to?(:model) ? ctx.llm_registry.get(raw_target.downcase).model : raw_target
60
+ return [raw_target.downcase, default_mod]
61
+ end
62
+
63
+ # 4. Fallback: model name under default provider (e.g. "qwen2.5-coder:7b", "llama3")
64
+ current_default_prov = ctx.config.model_spec(:default)&.dig("provider") || "ollama"
65
+ [current_default_prov, raw_target]
66
+ end
67
+
68
+ def self.parse_model_args(clean_args, ctx)
69
+ return ["ollama", ""] if clean_args.nil? || clean_args.empty?
70
+
71
+ clean_array = clean_args.is_a?(Array) ? clean_args : clean_args.to_s.strip.split(/\s+/)
72
+ return ["ollama", ""] if clean_array.empty?
73
+
74
+ registered_providers = []
75
+ registered_providers += ctx.llm_registry.list if ctx.llm_registry
76
+ registered_providers += ctx.config.fetch("providers", {}).keys if ctx.config
77
+ registered_providers = registered_providers.map { |p| p.to_s.downcase }.uniq
78
+
79
+ # 1. If 2 or more space-separated arguments: e.g. ["openrouter", "deepseek/deepseek-v4-flash"]
80
+ if clean_array.length >= 2
81
+ first_candidate = clean_array.first.to_s.strip.downcase
82
+ if registered_providers.include?(first_candidate)
83
+ prov = first_candidate
84
+ mod = clean_array[1..].join(" ").strip
85
+ return [prov, mod]
86
+ end
87
+ end
88
+
89
+ # 2. Otherwise join and parse as single target
90
+ target = clean_array.join(" ").strip
91
+ parse_model_target(target, ctx)
92
+ end
93
+
94
+ def self.register(shell)
95
+ register_model_command(shell)
96
+ end
97
+
98
+ # Shared tail of the three !model context forms: apply the limit
99
+ # through the Config verb, refresh the runtime, report — or refuse
100
+ # honestly (unknown provider vs a disk write that failed).
101
+ private_class_method def self.update_context_limit(ctx, scope, provider, model, limit_val, label)
102
+ res = ctx.config.upsert_provider_model(provider, model, context_limit: limit_val, persist: scope)
103
+ ctx.refresh_llm_registry!
104
+ if res[:ok]
105
+ ctx.out.puts "Updated context limit for #{label} to #{limit_val} tokens [#{Base.scope_label(scope)}]."
106
+ elsif res[:reason] == :unknown_provider
107
+ ctx.out.puts "Provider '#{provider}' not found."
108
+ else
109
+ Base.report_persist_failure(ctx, res, scope)
110
+ end
111
+ end
112
+
113
+ private_class_method def self.register_model_command(shell)
114
+ shell.register_command("model",
115
+ audience: :both,
116
+ description: "Switch or view LLM provider/model roles (default, fast, advanced) with auto-persistence") do |args, ctx|
117
+ scope, clean_args = Base.parse_scope(args)
118
+
119
+ if clean_args.empty? || clean_args.first.downcase == "list"
120
+ mode = ctx.config.fetch("router.mode", "auto")
121
+ def_spec = ctx.config.model_spec(:default)
122
+ fast_spec = ctx.config.model_spec(:fast)
123
+ adv_spec = ctx.config.model_spec(:advanced)
124
+
125
+ def_limit = ctx.llm_registry.context_limit_for(def_spec["provider"], def_spec["name"])
126
+ fast_limit = ctx.llm_registry.context_limit_for(fast_spec["provider"], fast_spec["name"])
127
+ adv_limit = ctx.llm_registry.context_limit_for(adv_spec["provider"], adv_spec["name"])
128
+
129
+ def_ep = def_spec["endpoint"] || def_spec["host"] || "cloud"
130
+ fast_ep = fast_spec["endpoint"] || fast_spec["host"] || "cloud"
131
+ adv_ep = adv_spec["endpoint"] || adv_spec["host"] || "cloud"
132
+
133
+ ctx.out.puts "Model Router Configuration (Mode: #{mode}):"
134
+ ctx.out.puts " • default : #{def_spec['provider']}/#{def_spec['name']} [Context: #{def_limit} tokens] (#{def_ep})"
135
+ ctx.out.puts " • fast : #{fast_spec['provider']}/#{fast_spec['name']} [Context: #{fast_limit} tokens] (#{fast_ep})"
136
+ ctx.out.puts " • advanced : #{adv_spec['provider']}/#{adv_spec['name']} [Context: #{adv_limit} tokens] (#{adv_ep})"
137
+ ctx.out.puts "Global Config : #{ctx.config.global_config_path}"
138
+ ctx.out.puts "Project Config : #{ctx.config.project_config_path}"
139
+ elsif clean_args.first.downcase == "route"
140
+ mode = clean_args[1].to_s.downcase
141
+ if ["auto", "manual"].include?(mode)
142
+ res = ctx.config.set("router.mode", mode, persist: scope)
143
+ next if Base.report_persist_failure(ctx, res, scope)
144
+
145
+ ctx.out.puts "Router mode set to #{mode} [#{Base.scope_label(scope)}]."
146
+ else
147
+ ctx.out.puts "Usage: !model route <auto|manual>"
148
+ end
149
+ elsif clean_args.first.downcase == "add"
150
+ p_name = clean_args[1]&.downcase
151
+ m_name = clean_args[2]
152
+ if p_name.nil? || p_name.empty? || m_name.nil? || m_name.empty?
153
+ ctx.out.puts "Usage: !model add <provider_name> <model_name> [--context=LIMIT] [--project|--global]"
154
+ else
155
+ context_arg = clean_args.find { |a| a.start_with?("--context=") }
156
+ c_limit = context_arg ? context_arg.split("=", 2).last.to_i : 32_768
157
+ res = ctx.config.upsert_provider_model(p_name, m_name, context_limit: c_limit, persist: scope)
158
+ ctx.refresh_llm_registry!
159
+ if res[:ok]
160
+ ctx.out.puts "Added model '#{m_name}' (#{c_limit} tokens) to provider '#{p_name}' [#{Base.scope_label(scope)}]."
161
+ elsif res[:reason] == :unknown_provider
162
+ ctx.out.puts "Provider '#{p_name}' not found. Add provider first via !provider add."
163
+ else
164
+ Base.report_persist_failure(ctx, res, scope)
165
+ end
166
+ end
167
+ elsif ["context", "limit"].include?(clean_args.first.downcase)
168
+ if clean_args.size == 2 && clean_args[1] =~ /^\d+$/
169
+ def_spec = ctx.config.model_spec(:default)
170
+ update_context_limit(ctx, scope, def_spec["provider"], def_spec["name"],
171
+ clean_args[1].to_i, "default model #{def_spec['provider']}/#{def_spec['name']}")
172
+ elsif clean_args.size == 3 && ["default", "fast", "advanced"].include?(clean_args[1].downcase) && clean_args[2] =~ /^\d+$/
173
+ role = clean_args[1].downcase.to_sym
174
+ spec = ctx.config.model_spec(role)
175
+ update_context_limit(ctx, scope, spec["provider"], spec["name"],
176
+ clean_args[2].to_i, "#{role} model #{spec['provider']}/#{spec['name']}")
177
+ elsif clean_args.size == 4 && clean_args[3] =~ /^\d+$/
178
+ update_context_limit(ctx, scope, clean_args[1].downcase, clean_args[2],
179
+ clean_args[3].to_i, "#{clean_args[1].downcase}/#{clean_args[2]}")
180
+ else
181
+ ctx.out.puts "Usage: !model context <limit> | !model context <default|fast|advanced> <limit> | " \
182
+ "!model context <provider> <model> <limit> [--project|--global]"
183
+ end
184
+ elsif clean_args.first.downcase == "set"
185
+ role = clean_args[1].to_s.downcase
186
+ model_args = clean_args[2..]
187
+ if model_args.nil? || model_args.empty?
188
+ ctx.out.puts "Usage: !model set <default|fast|advanced> [<provider>] <model_name> [--project|--global]"
189
+ else
190
+ prov, mod = parse_model_args(model_args, ctx)
191
+ res = ctx.config.set_model_spec(role, provider: prov, name: mod, persist: scope)
192
+ ctx.resync_llm_route!
193
+ next if Base.report_persist_failure(ctx, res, scope)
194
+
195
+ limit = ctx.llm_registry.context_limit_for(prov, mod)
196
+ ctx.out.puts "Set model role '#{role}' to #{prov}/#{mod} [Context: #{limit} tokens] [#{Base.scope_label(scope)}]."
197
+ end
198
+ else
199
+ prov, mod = parse_model_args(clean_args, ctx)
200
+ res = ctx.config.set_model_spec(:default, provider: prov, name: mod, persist: scope)
201
+ ctx.resync_llm_route!
202
+ next if Base.report_persist_failure(ctx, res, scope)
203
+
204
+ limit = ctx.llm_registry.context_limit_for(prov, mod)
205
+ ctx.out.puts "Switched default model to #{prov}/#{mod} [Context: #{limit} tokens] [#{Base.scope_label(scope)}]."
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end