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,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "reaper"
4
+
5
+ module Rubik
6
+ module Orchestrator
7
+ module Subagent
8
+ # Subagent reuse (context preserved redeploy): bitmiş bir subagent'a
9
+ # (completed/failed, thread ölmüş) aynı kimlik ve birikmiş LLM
10
+ # history'si ile YENİ görev verilir — taze spawn + yeniden açıklama
11
+ # maliyeti yok. maestro doktrininin temel taşı.
12
+ module Redeploy
13
+ # Sadece terminal VE thread'i ölmüş stateler: ölü thread, run'ın
14
+ # ensure bloğunun (save_transcript! → cleanup_context →
15
+ # on_complete) TAMAMEN çalıştığını kanıtlar — redeploy kendi
16
+ # tamamlanma işleyişiyle yarışamaz. settled? = :completed/:failed
17
+ # (Instance'ın lifecycle sözlüğü); :disposed bilinçli dışarıda.
18
+ def terminal_and_settled?
19
+ settled? && !@thread&.alive?
20
+ end
21
+
22
+ def redeployable?
23
+ terminal_and_settled? && !@archived
24
+ end
25
+
26
+ # Issue #55 3.2 buffer: a steering message for a finished-but-unreaped
27
+ # instance. Check and push under ONE monitor acquisition — the
28
+ # reaper's archive! shares this monitor, so an archive between a
29
+ # redeployable? check and the push (which would strand the message
30
+ # in a wiped history) cannot interleave. No grant branches here: the
31
+ # redeploy mints a fresh budget, and a settled instance must not
32
+ # resurrect.
33
+ def buffer_for_redeploy(message, sender: nil)
34
+ @monitor.synchronize do
35
+ return false unless redeployable?
36
+
37
+ @inbox.push(Instance::InboxMessage.new(message, sender))
38
+ true
39
+ end
40
+ end
41
+
42
+ # Operator diagnostics for the reap path: queued steering that died
43
+ # with the instance instead of being delivered by a redeploy.
44
+ def pending_redeploy_messages?
45
+ @monitor.synchronize { !@inbox.empty? }
46
+ end
47
+
48
+ # Sadece sıfırlama — @state'i ASLA çevirme; :running flip'i ve
49
+ # thread başlatmayı start_async sahiplenir (flip-sonrası-start
50
+ # hatası penceresi olmasın). Budget taze mint edilir (bitmiş
51
+ # run'ın exhausted bayrağı taşıyabilir), ölü child ctx yerine yenisi
52
+ # ve taze scoped registry bağlanır (eskisinin ask_question'ı
53
+ # dispose'lu ctx'e rebind'lıydı). Yeni görev history'ye user
54
+ # mesajı olarak eklenir; run cycle yalnız BOŞ history seed eder.
55
+ def redeploy_for!(prompt:, max_steps:, context:, tool_registry:)
56
+ @monitor.synchronize do
57
+ return false unless redeployable?
58
+
59
+ drain_inbox_into_history
60
+ @history << { role: "user", content: "[New task from maestro]: #{prompt}" }
61
+ @prompt = prompt.to_s
62
+ @budget = Budget.new(max_steps: max_steps.to_i)
63
+ # @result reset is the ONE sanctioned clearer (terminal-only
64
+ # contract): a new lineage begins. @pause_facts rides along —
65
+ # the old run's wait fact must not narrate the new one.
66
+ @result = @last_error = @ended_at = @transcript_path = nil
67
+ @pause_facts = nil
68
+ @context = context
69
+ @tool_registry = tool_registry
70
+ @context_cleaned_up = false
71
+ true
72
+ end
73
+ end
74
+ end
75
+
76
+ # Supervisor düzlemi: spawn'ın coeffect koreografisini (child ctx,
77
+ # scoped registry, asker provide, SubagentUnit register, start_async)
78
+ # ActivationSupervisor TEK seam'den oynatır — spawn ile birebir aynı
79
+ # düzen: unit kaydı start_async'DEN ÖNCE, derived birth state ile
80
+ # (settled girişte :pending; ölü-doğan unit tuzağı yapısal olarak
81
+ # imkânsız — bkz. activation.rb). provider/buffer/provider_gate/
82
+ # storage_id/kimlik AYNEN KALIR (reuse'un kendisi bu).
83
+ module RedeploySupervisor
84
+ def redeploy(id, prompt:, max_steps: nil)
85
+ prompt_str = prompt.to_s.strip
86
+ raise ArgumentError, "Redeploy prompt cannot be empty" if prompt_str.empty?
87
+
88
+ redeployed = @monitor.synchronize do
89
+ key = id.to_s
90
+ inst = @instances[key] || (@finished || {})[key]
91
+ raise ArgumentError, redeploy_refusal(key, inst) unless inst&.redeployable?
92
+
93
+ validate_concurrency!
94
+ child_ctx = extend_root_context!(:redeploy)
95
+ scoped_registry = build_scoped_registry(@type_registry.get(inst.type_name), child_ctx, inst.buffer_manager)
96
+ return false unless inst.redeploy_for!(prompt: prompt_str, max_steps: (max_steps || @default_max_steps).to_i,
97
+ context: child_ctx, tool_registry: scoped_registry)
98
+
99
+ @finished&.delete(inst.id)
100
+ @instances[inst.id] = inst
101
+ activate_instance!(inst, child_ctx: child_ctx)
102
+ end
103
+ # SS bırakma kancası: redeploy da bir başlangıçtır — taze :running
104
+ # flip'i, bu id'yi SS ile bekleyen kuyruk spesifikasyonlarını
105
+ # bırakır (kilit DIŞINDA — emit kuralı; spawn ile aynı düzen).
106
+ release_ready!
107
+ redeployed
108
+ end
109
+
110
+ private
111
+
112
+ # Reddetme gerekçesi durum-bazlı yönlendirme içerir: maestro doğru
113
+ # aksiyona (steer / wait / taze spawn) yönelsin.
114
+ def redeploy_refusal(id, inst)
115
+ return "Subagent '#{id}' not found." unless inst
116
+ return "Subagent '#{id}' is archived (reaped) — spawn a fresh subagent via invoke_subagent." if inst.archived?
117
+
118
+ if inst.settled?
119
+ "Subagent '#{id}' is still settling (completion handling in flight); retry shortly."
120
+ else
121
+ "Subagent '#{id}' is #{inst.state} with a live thread — steer it (send_message / extend_budget) or kill it first."
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ module Subagent
6
+ # Supervisor'dan çıkarılan araç kapsamlandırıcı (satır sınırı, G-plan
7
+ # hygiene cap'leri): archetype filtresiyle subagent kayıt defterini
8
+ # kurar ve branch-workspace araçlarını yeni buffer'a bağlar.
9
+ module ScopedRegistry
10
+ module_function
11
+
12
+ # K7 (plan 2026-10-04) + context-pruner (2026-10-09): kronikçinin ve
13
+ # budayıcının masaları yalnız kök/maestro düzlemine aittir —
14
+ # subagent kayıt defterlerine ('self' klonu dahil, çünkü onun
15
+ # filtresi yalnız spawn setini dışlar) asla kopyalanmaz. Orkestrasyon
16
+ # belleği tek elde kalır.
17
+ ROOT_ONLY_TOOLS = ["prune_context", "vakanuvis", "update_goal_ledger"].freeze
18
+
19
+ def build(source_registry:, type_def:, child_ctx:, buffer:, parent_buffer:)
20
+ return nil unless source_registry
21
+
22
+ new_reg = Rubik::Tools::Registry.new(coeffect_ctx: child_ctx)
23
+ filter = type_def.tool_filter
24
+
25
+ source_registry.list.each do |tool|
26
+ next if ROOT_ONLY_TOOLS.include?(tool.name)
27
+ next unless filter.nil? || filter.call(tool.name)
28
+
29
+ new_reg.register(bind_tool_buffer(tool, buffer, ctx: child_ctx, parent_buffer: parent_buffer))
30
+ end
31
+ new_reg
32
+ end
33
+
34
+ # Buffer-bound tools (rebuilt only when a branch buffer splits them
35
+ # off the parent); everything else is context-rebind territory.
36
+ BUFFER_BOUND_TOOLS = ["read_file", "write_file", "replace_file_content", "apply_buffer"].freeze
37
+
38
+ # Buffer rebinding must preserve runtime-context coeffect reads
39
+ # (rubik://goal/state, rubik://storage, subagent transcripts resolve
40
+ # through ctx): a branch-workspace coder still shares the session's
41
+ # live state.
42
+ def bind_tool_buffer(tool, buffer, ctx: nil, parent_buffer: nil)
43
+ return rebind_to_context(tool, ctx) unless BUFFER_BOUND_TOOLS.include?(tool.name)
44
+ return tool unless buffer && buffer != parent_buffer
45
+
46
+ case tool.name
47
+ when "read_file" then Rubik::Tools::ReadFileTool.new(buffer, runtime_context: ctx)
48
+ when "write_file" then Rubik::Tools::WriteFileTool.new(buffer)
49
+ when "replace_file_content" then Rubik::Tools::ReplaceFileContentTool.new(buffer)
50
+ else Rubik::Tools::ApplyBufferTool.new(buffer)
51
+ end
52
+ end
53
+
54
+ # Subagent düzlemine geçen araçlar (ask_question + send_message):
55
+ # her ikisinin de coeffect çözümü yalnız child ctx'te sağlanır
56
+ # (supervisor#spawn/redeploy provide). The rebind MUST precede the
57
+ # buffer guard conceptually: research-type subagents inherit
58
+ # workspace buffers, but ask_question still has to swap to the child
59
+ # ctx — otherwise it would keep the ROOT tool (live shell) and pop a
60
+ # HUMAN dialog from the worker thread; send_message swaps so the
61
+ # subagent-plane message carries its REAL sender id to the maestro.
62
+ # Class-matched (not name-matched): a plugin/dynamic tool registered
63
+ # under the same name keeps its own implementation in every plane.
64
+ def rebind_to_context(tool, ctx)
65
+ return tool unless ctx.respond_to?(:get)
66
+
67
+ case tool
68
+ when Rubik::Tools::AskQuestionTool
69
+ Rubik::Tools::AskQuestionTool.new(shell: nil, runtime_context: ctx)
70
+ when Rubik::Tools::SendMessageTool
71
+ Rubik::Tools::SendMessageTool.new(runtime_context: ctx)
72
+ else
73
+ tool
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../prompt"
5
+ require_relative "../../tools/truncator"
6
+ require_relative "../provider_recovery"
7
+ require_relative "../auto_apply"
8
+ require_relative "../tool_call_id"
9
+ require_relative "../tool_history"
10
+ require_relative "../turn_executor"
11
+ require_relative "../turn_executor/subagent_channel"
12
+ require_relative "../../coeffects"
13
+ require_relative "notices"
14
+ require_relative "resilience"
15
+ require_relative "conversation_id"
16
+
17
+ module Rubik
18
+ module Orchestrator
19
+ module Subagent
20
+ # Step execution loop, compaction evaluation, emergency recovery, and tool dispatch
21
+ # extracted from Instance to satisfy module hygiene and maintain vertical cohesion (Plan 95);
22
+ # budget state is reached only through the shared Budget collaborator (Candidate 04).
23
+ # Response processing itself is shared with the user channel through the TurnExecutor
24
+ # spine (agent-turn-executor plan); this module keeps the invocation path, budget
25
+ # mechanics, and the subagent dispatch.
26
+ module StepRunner
27
+ # The shared response-processing spine, built lazily on first use so
28
+ # objects that skip initialize (test doubles) stay safe.
29
+ def turn_executor
30
+ @turn_executor ||= Rubik::Orchestrator::TurnExecutor.new(
31
+ channel: Rubik::Orchestrator::TurnExecutor::SubagentChannel.new(self)
32
+ )
33
+ end
34
+
35
+ def execute_steps_with_provider
36
+ turn_executor.reset!
37
+ loop do
38
+ while !@budget.exhausted? && executing?
39
+ @budget.consume
40
+ drain_inbox_into_history
41
+ inject_step_marker
42
+ evaluate_subagent_context_compaction!
43
+
44
+ opts = { tools: (@tool_registry.respond_to?(:schemas) ? @tool_registry.schemas : []) }
45
+ opts[:model] = @model unless @model.nil? || @model == "inherit"
46
+
47
+ begin
48
+ raw_response = invoke_provider_with_recovery(opts)
49
+ rescue StandardError => e
50
+ if Resilience.transient_error?(e) && auto_pause_on_error?
51
+ break unless pause_on_error!(e)
52
+
53
+ next
54
+ end
55
+ raise e
56
+ end
57
+
58
+ terminal = turn_executor.call(raw_response)
59
+ return if terminal
60
+ end
61
+
62
+ break unless executing? && @budget.exhausted?
63
+ break unless pause_and_await_budget_expansion!
64
+ end
65
+ end
66
+
67
+ # Public seams for the SubagentChannel adapter (a separate object —
68
+ # it cannot reach this module's context). Recording the outcome and
69
+ # transitioning stay paired here so the lifecycle's only-mutation-
70
+ # path doctrine holds.
71
+ def complete_turn!(result)
72
+ @result = result
73
+ transition!(:completed)
74
+ end
75
+
76
+ def fail_turn!(reason)
77
+ @last_error = reason
78
+ transition!(:failed)
79
+ end
80
+
81
+ def evaluate_subagent_context_compaction!
82
+ return unless @context_limit&.positive?
83
+
84
+ context_cfg = ProviderRecovery.compaction_config(@config)
85
+ needs = Rubik::Prompt::ContextCompactor.needs_compaction?(
86
+ @history, context_limit: @context_limit, max_output_tokens: @max_output_tokens, config: context_cfg
87
+ )
88
+ return unless needs
89
+
90
+ compacted = Rubik::Prompt::ContextCompactor.compact_history(
91
+ @history, context_limit: @context_limit, max_output_tokens: @max_output_tokens, config: context_cfg
92
+ )
93
+ @history.replace(compacted)
94
+ end
95
+
96
+ def invoke_provider_with_recovery(opts)
97
+ ProviderRecovery.with_recovery(config: @config, compact: method(:emergency_compact_and_notify!)) do
98
+ Resilience.retry_transient(config: @config, gate: @provider_gate) do
99
+ invoke_provider(opts)
100
+ end
101
+ end
102
+ end
103
+
104
+ def auto_pause_on_error?
105
+ @config.respond_to?(:fetch) ? @config.fetch("subagents.auto_pause_on_network_error", true) != false : true
106
+ end
107
+
108
+ # Compaction actuator for the subagent path (mirrors Loop's public
109
+ # seam of the same name): the compaction body lives in
110
+ # TurnExecutor#emergency_compact! (one home for both channels); this
111
+ # delegates and the channel announces on the bus. Runs on the worker
112
+ # thread between monitor sections — never call it while holding
113
+ # @monitor (the emit stays lock-free).
114
+ def emergency_compact_and_notify!(reason)
115
+ turn_executor.emergency_compact!(reason)
116
+ end
117
+
118
+ # The object that owns the fact emits it — outside the monitor, so
119
+ # subscribers never run under the instance lock.
120
+ def emit_compacted_event(reason, layer)
121
+ Notices.emit(@event_bus, Notices::COMPACTED_EVENT, { id: @id, role: @role, state: @state, reason: reason.to_s, layer: layer })
122
+ end
123
+
124
+ def invoke_provider(opts)
125
+ effective_opts = opts.dup
126
+ effective_opts[:timeout] ||= ProviderRecovery::SUBAGENT_REQUEST_TIMEOUT
127
+ effective_opts[:open_timeout] ||= ProviderRecovery::DEFAULT_OPEN_TIMEOUT
128
+ effective_opts[:context_limit] ||= @context_limit if @context_limit
129
+ effective_opts[:max_tokens] ||= @max_output_tokens if @max_output_tokens&.positive?
130
+
131
+ return @provider.complete(@history, effective_opts) if @provider.respond_to?(:complete)
132
+ return @provider.chat(@history, **effective_opts) if @provider.respond_to?(:chat)
133
+
134
+ raise "Provider #{@provider.class} does not support complete or chat"
135
+ end
136
+
137
+ def drain_inbox_into_history
138
+ until @inbox.empty?
139
+ begin
140
+ entry = @inbox.pop(true)
141
+ @history << { role: "user", content: "[Message from #{sender_label(entry)}]: #{entry.text}" } if entry
142
+ rescue ThreadError
143
+ break
144
+ end
145
+ end
146
+ end
147
+
148
+ # İki-düzlem sözleşme: gönderen yoksa ya da makine adı agent-0'sa
149
+ # model persona'sı maestro ile etiketlenir; subagent gönderenler
150
+ # (sibling mesajları) kendi id'leriyle görünür.
151
+ def sender_label(entry)
152
+ return "maestro" if entry.from.nil? || entry.from == ConversationId::ROOT_ID
153
+
154
+ entry.from
155
+ end
156
+
157
+ # Single-call subagent dispatch (the TurnExecutor's per-call seam):
158
+ # pre-execution auto-apply, timed execution, and the canonical
159
+ # ToolHistory row — which now carries status + duration_ms like every
160
+ # other channel (the row-superset delta of the agent-turn-executor
161
+ # plan; no wire-side formatter reads them, observability only).
162
+ def execute_subagent_tool_call(tool_call)
163
+ name = tool_call["tool"]
164
+ args = tool_call["args"] || {}
165
+ tool = @tool_registry.respond_to?(:get) ? @tool_registry.get(name) : nil
166
+ canonical_name = tool ? tool.name : name.to_s.downcase
167
+
168
+ auto_apply_buffer!(:pre_execution, tool: canonical_name)
169
+
170
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
171
+ output, status = dispatch_subagent_tool(tool, name, args)
172
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(1)
173
+ Rubik::Orchestrator::ToolHistory.write(@history, tool_call_id: tool_call["id"], tool_name: name,
174
+ status: status, duration_ms: duration_ms, content: output.to_s)
175
+ nil
176
+ end
177
+
178
+ # The subagent's own spelling of the shared auto-apply policy (AutoApply
179
+ # owns the decision): decide, apply+sync through the coeffect shell,
180
+ # and REPORT failures instead of swallowing them — a failed pre-test
181
+ # apply used to mean tests silently ran on stale disk. The decision
182
+ # sits OUTSIDE the rescue: a policy bug must fail the subagent as a
183
+ # code error, not masquerade as a runtime apply failure. Runs on the
184
+ # worker thread between monitor sections — never call it while
185
+ # holding @monitor (same discipline as emergency_compact_and_notify!).
186
+ def auto_apply_buffer!(trigger, tool: nil)
187
+ return unless AutoApply.apply?(@config, trigger: trigger, subagent: true, tool: tool)
188
+
189
+ begin
190
+ AutoApply.apply_and_sync(@buffer_manager, coeffect_shell)
191
+ AutoApply.report_conflicts(@event_bus, @buffer_manager,
192
+ id: @id, role: @role, state: @state, trigger: trigger)
193
+ rescue StandardError => e
194
+ AutoApply.report_failure(@event_bus, id: @id, role: @role, state: @state,
195
+ trigger: trigger, reason: "#{e.class}: #{e.message}")
196
+ # The subagent itself is the actor about to trust the disk (its
197
+ # next step is usually run_tests) — a parent notice alone keeps
198
+ # the one participant who could react in the dark.
199
+ @history << { role: "user",
200
+ content: "[AutoApply] buffer auto-apply (#{trigger}) failed: #{e.message}" }
201
+ end
202
+ end
203
+
204
+ # The subagent's own child context walks the parent chain to the
205
+ # root-provided :shell, so its applies sync the AST index exactly
206
+ # like every main-path apply (previously this path never synced).
207
+ def coeffect_shell
208
+ Rubik::Coeffects.get(@context, Rubik::Coeffects::SHELL)
209
+ end
210
+
211
+ # Returns [truncated_output, status] so the dispatch site can build
212
+ # the canonical tool row. Unknown tools and execution errors carry
213
+ # :error; plain results are :ok (Result-backed tools carry their own).
214
+ def dispatch_subagent_tool(tool, name, args)
215
+ return ["Tool '#{name}' is not available in subagent toolset.", :error] unless tool
216
+
217
+ raw = tool.execute(args)
218
+ out_str = raw.respond_to?(:output) ? raw.output.to_s : raw.to_s
219
+ max_chars = (@config.respond_to?(:fetch) ? @config.fetch("tools.max_output_chars", 12_000) : 12_000) || 12_000
220
+ max_lines = (@config.respond_to?(:fetch) ? @config.fetch("tools.max_output_lines", 300) : 300) || 300
221
+ trunc = Rubik::Tools::Truncator.truncate(out_str, max_chars: max_chars.to_i, max_lines: max_lines.to_i)
222
+ status = raw.respond_to?(:status) ? raw.status : :ok
223
+ [trunc[:text], status]
224
+ rescue StandardError => e
225
+ ["Tool execution error: #{e.class}: #{e.message}", :error]
226
+ end
227
+
228
+ private
229
+
230
+ # Plan 109 Faz A: per-step budget transparency replaces the 75%
231
+ # warning latch — Budget owns the marker string under its lock; the
232
+ # step loop owns only the history mutation (a conversation fact),
233
+ # the same split the warning used.
234
+ def inject_step_marker
235
+ @history << { role: "user", content: @budget.step_marker }
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ module Subagent
6
+ # Mints process-unique transcript storage ids (Plan 110, closes #48).
7
+ #
8
+ # Display ids restart at "subagent-1" in every process, so bare display
9
+ # ids collide across sessions sharing a workspace: transcript paths
10
+ # derived from them let one session's save clobber another's, or serve
11
+ # a dead run's file as live evidence (2026-09-27 phantom-subagent
12
+ # incident). A storage id suffixes the display id with three factors:
13
+ # * pid — two live processes can never mint the same id;
14
+ # * epoch seconds — same-process re-runs (test suites) differ;
15
+ # * rand4 — same-second re-mints stay distinct.
16
+ # Unlike ToolCallId — whose doc scopes it to tool-call id spellings and
17
+ # whose bounded session histories make rand-only viable — a transcript
18
+ # filesystem accumulates forever, so uniqueness needs pid + time.
19
+ module StorageId
20
+ def self.mint(display_id)
21
+ "#{display_id}-#{Process.pid}-#{Time.now.to_i}-#{rand(1000..9999)}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end