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,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../prompt"
4
+ require_relative "provider_recovery"
5
+ require_relative "goal_ledger"
6
+
7
+ module Rubik
8
+ module Orchestrator
9
+ # Step-budget warnings, token accounting and context-compaction decisions
10
+ # (Plan 54 D-6). Stateless collaborator over Loop-held state: counters are
11
+ # read through accessors at call time (tests may poke them between calls)
12
+ # and the total_tokens hash is mutated in place so Loop's reader keeps its
13
+ # object identity.
14
+ class BudgetManager
15
+ # Plan 87 E-1: state is reached through the ShellDeps façade. Accepts a
16
+ # bare loop for compatibility with existing test doubles (wrapped).
17
+ def initialize(loop_or_deps)
18
+ @deps = ShellDeps.wrap(loop_or_deps)
19
+ end
20
+
21
+ def evaluate_step_budget_warning!(warned)
22
+ # Threshold computed at call time: a live `!steps <n>` extension raises
23
+ # max_steps mid-run, and the next evaluation must use the new budget's
24
+ # 75% point instead of a stale pre-extension threshold.
25
+ warn_step = (@deps.max_steps * @deps.warn_at_ratio).ceil
26
+ return warned if warned || @deps.step_count < warn_step || @deps.step_count >= @deps.max_steps
27
+
28
+ remaining = @deps.max_steps - @deps.step_count
29
+ # Plan 61 D-14: send as its own user message — concatenating onto
30
+ # @history.last can weld the XML onto a tool result or a
31
+ # content-less assistant message, which provider formatters mangle.
32
+ warning_xml = "<step_budget current=\"#{@deps.step_count}\" max=\"#{@deps.max_steps}\" remaining=\"#{remaining}\" " \
33
+ "alert=\"Step budget nearing limit (#{remaining} steps left). Please begin concluding your work, " \
34
+ "verify with tests/diagnostics, and call finish. If the task genuinely needs more room, you may " \
35
+ "raise the live step budget with exec_repl_command('!steps <n>') where n is the new total.\" />"
36
+ @deps.history << { role: "user", content: warning_xml }
37
+ @deps.on_step&.call(type: :step_warning, current: @deps.step_count, max: @deps.max_steps, remaining: remaining)
38
+ true
39
+ end
40
+
41
+ def evaluate_context_compaction!
42
+ return unless @deps.context_limit
43
+
44
+ context_cfg = ProviderRecovery.compaction_config(@deps.config)
45
+ needs = Rubik::Prompt::ContextCompactor.needs_compaction?(
46
+ @deps.history, context_limit: @deps.context_limit,
47
+ max_output_tokens: @deps.max_output_tokens, config: context_cfg
48
+ )
49
+ return unless needs
50
+
51
+ # compact_history is pure and returns a new array, so the history
52
+ # reference itself must be rebound on Loop. absorb (plan D): the
53
+ # directive is nil unless goal-mode + knobs + coeffect + rev ≥ 1
54
+ # all hold — otherwise compaction stays byte-identical.
55
+ @deps.replace_history!(Rubik::Prompt::ContextCompactor.compact_history(
56
+ @deps.history, context_limit: @deps.context_limit,
57
+ max_output_tokens: @deps.max_output_tokens, config: context_cfg,
58
+ absorb: GoalLedger::ObservationProjection.directive_for(deps: @deps)
59
+ ))
60
+ end
61
+
62
+ def track_token_usage!(res)
63
+ return unless res.respond_to?(:usage) && res.usage
64
+
65
+ in_tokens = res.usage[:input_tokens] || res.usage["input_tokens"] || 0
66
+ @deps.total_tokens[:input] += in_tokens
67
+ @deps.total_tokens[:output] += res.usage[:output_tokens] || res.usage["output_tokens"] || 0
68
+ warn_context_truncation!(in_tokens)
69
+ end
70
+
71
+ # Plan 61 D-1: usage input_tokens is the server's ground truth. When
72
+ # it approaches the configured window the provider (not the compactor)
73
+ # is clipping the prompt head — surface it once so silent context loss
74
+ # becomes visible instead of reading as "the model forgot everything".
75
+ def warn_context_truncation!(in_tokens)
76
+ return if @deps.context_limit.nil? || in_tokens.to_i <= 0
77
+ return if in_tokens.to_i < (@deps.context_limit * 0.9)
78
+ return if @deps.context_warning_emitted
79
+
80
+ @deps.flag_context_warning!
81
+ @deps.on_step&.call(
82
+ type: :context_truncation_warning,
83
+ input_tokens: in_tokens.to_i,
84
+ context_limit: @deps.context_limit,
85
+ step: @deps.step_count,
86
+ max_steps: @deps.max_steps
87
+ )
88
+ end
89
+
90
+ def estimate_tokens_from_history
91
+ # Use the same estimator as the compaction math (Plan 61 D-1): the
92
+ # usage fallback must agree with the window the request was sized for.
93
+ Rubik::Prompt::TokenEstimator.estimate_history(@deps.history)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ # Manages auto-continuation when an LLM response is truncated due to
6
+ # reaching max_output_tokens (finish_reason == "length" or "max_tokens").
7
+ #
8
+ # Responsible for:
9
+ # 1. Distinguishing between reasoning/thinking exhaustion (empty text)
10
+ # and content truncation (mid-sentence cut-off).
11
+ # 2. Buffering and stitching partial text across continuation turns.
12
+ # 3. Generating targeted, non-repetitive continuation prompts.
13
+ # 4. Enforcing the max_auto_continues safety ceiling to prevent runaways.
14
+ class ContinuationManager
15
+ TRUNCATION_REASONS = ["length", "max_tokens"].freeze
16
+
17
+ attr_reader :auto_continue_count, :continuation_buffer, :max_auto_continues
18
+
19
+ def initialize(config: nil, max_auto_continues: nil)
20
+ @config = config
21
+ @max_auto_continues = (max_auto_continues || safe_config("llm.max_auto_continues", 3)).to_i
22
+ @auto_continue_count = 0
23
+ @continuation_buffer = +""
24
+ end
25
+
26
+ def enabled?
27
+ return true if @config.nil?
28
+
29
+ safe_config("llm.auto_continue_on_length", true) == true
30
+ end
31
+
32
+ def truncation?(finish_reason)
33
+ TRUNCATION_REASONS.include?(finish_reason.to_s.downcase)
34
+ end
35
+
36
+ def ceiling_reached?
37
+ @auto_continue_count >= @max_auto_continues
38
+ end
39
+
40
+ # Classifies the truncation scenario:
41
+ # - :thinking_exhausted when text and tools are empty (budget spent on thought)
42
+ # - :text_truncated when partial text was produced
43
+ # - :tool_truncated when partial tool call was being assembled
44
+ def classify_truncation(text_resp, tool_calls, _res = nil)
45
+ if tool_calls.empty?
46
+ if text_resp.to_s.strip.empty?
47
+ :thinking_exhausted
48
+ else
49
+ :text_truncated
50
+ end
51
+ else
52
+ # Tool call was partially assembled or arguments broken due to cut-off
53
+ :tool_truncated
54
+ end
55
+ end
56
+
57
+ # Handles a truncation event.
58
+ # Returns a Hash:
59
+ # { continue: true, nudge: "...", step_event: {...} }
60
+ # or
61
+ # { continue: false, error: "..." } if ceiling reached or disabled.
62
+ def handle_truncation(finish_reason:, text_resp:, tool_calls:, res: nil, max_tokens: 4096)
63
+ return { continue: false } unless truncation?(finish_reason)
64
+
65
+ unless enabled?
66
+ rec = (max_tokens.to_i * 2).positive? ? max_tokens.to_i * 2 : 8192
67
+ diag = "⚠️ Model çıktısı maksimum token sınırına (max_output_tokens: #{max_tokens}) ulaştığı için tamamlanamadan kesildi.\n" \
68
+ "Çözüm: Çıktı token bütçesini artırmak için şu komutu çalıştırabilirsiniz:\n !config set llm.default_max_output_tokens #{rec}"
69
+ return { continue: false, error: diag }
70
+ end
71
+
72
+ if ceiling_reached?
73
+ rec = (max_tokens.to_i * 2).positive? ? max_tokens.to_i * 2 : 8192
74
+ diag = "⚠️ Model çıktısı maksimum token sınırına (max_output_tokens: #{max_tokens}) ulaştığı için " \
75
+ "ve #{@max_auto_continues} otomatik devam denemesi tükendiği için durduruldu.\n" \
76
+ "Çözüm: Çıktı token bütçesini artırmak için şu komutu çalıştırabilirsiniz:\n !config set llm.default_max_output_tokens #{rec}"
77
+ return { continue: false, error: diag }
78
+ end
79
+
80
+ scenario = classify_truncation(text_resp, tool_calls, res)
81
+ @auto_continue_count += 1
82
+
83
+ case scenario
84
+ when :thinking_exhausted
85
+ {
86
+ continue: true,
87
+ scenario: :thinking_exhausted,
88
+ nudge: "Your thinking process reached the output token limit before producing a response or tool call. " \
89
+ "Do not repeat your reasoning; immediately provide your final response or invoke the necessary tool call concisely.",
90
+ thought: res.respond_to?(:thought) ? res.thought : nil,
91
+ step_event: { type: :auto_continue, reason: :thinking_exhausted, count: @auto_continue_count, max: @max_auto_continues }
92
+ }
93
+ when :text_truncated
94
+ @continuation_buffer << text_resp.to_s
95
+ {
96
+ continue: true,
97
+ scenario: :text_truncated,
98
+ partial_text: text_resp.to_s,
99
+ nudge: "Your previous response was cut off because it reached the output token limit. " \
100
+ "Continue directly and seamlessly from the exact character/code line where you stopped. " \
101
+ "Do not repeat previous text or restart.",
102
+ step_event: { type: :auto_continue, reason: :text_truncated, count: @auto_continue_count, max: @max_auto_continues }
103
+ }
104
+ when :tool_truncated
105
+ {
106
+ continue: true,
107
+ scenario: :tool_truncated,
108
+ nudge: "Your tool call was cut off due to the output token limit. Please re-invoke the tool call directly with concise arguments.",
109
+ step_event: { type: :auto_continue, reason: :tool_truncated, count: @auto_continue_count, max: @max_auto_continues }
110
+ }
111
+ end
112
+ end
113
+
114
+ # Stitches any accumulated continuation buffer with the final response
115
+ # text and resets the continuation state.
116
+ def stitch_and_reset(final_text)
117
+ stitched = if @continuation_buffer.empty?
118
+ final_text.to_s
119
+ else
120
+ @continuation_buffer + final_text.to_s
121
+ end
122
+ reset!
123
+ stitched
124
+ end
125
+
126
+ def reset!
127
+ @auto_continue_count = 0
128
+ @continuation_buffer = +""
129
+ end
130
+
131
+ private
132
+
133
+ def safe_config(key, default)
134
+ return default unless @config
135
+
136
+ if @config.respond_to?(:fetch)
137
+ begin
138
+ val = @config.fetch(key)
139
+ return val unless val.nil?
140
+ rescue KeyError, ArgumentError
141
+ nil
142
+ end
143
+ end
144
+
145
+ if key.include?(".") && @config.respond_to?(:dig)
146
+ val = @config.dig(*key.split("."))
147
+ return val unless val.nil?
148
+ end
149
+
150
+ if @config.respond_to?(:[])
151
+ val = @config[key]
152
+ return val unless val.nil?
153
+ end
154
+
155
+ default
156
+ rescue KeyError, TypeError, NoMethodError
157
+ default
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ # Selects the final message shown to the user when the agent finishes.
6
+ # Extracted from StepExecutor#handle_finish_tool (review Finding 5): the
7
+ # heuristic was inline, undocumented, and its magic substring
8
+ # `!clean_text_resp.include?("tool")` had no explanation of intent.
9
+ #
10
+ # Inputs (both already normalized by the caller):
11
+ # narration — the turn's free text with JSON tool-call blocks stripped
12
+ # (StepExecutor#clean_llm_text), stripped and possibly empty.
13
+ # tool_msg — the finish tool's `message` argument, stripped.
14
+ #
15
+ # Selection (behavior preserved exactly from the inline version):
16
+ # 1. tool_msg non-empty and NOT a generic filler ("completed", "done",
17
+ # "finished", "ok", "task_completed"):
18
+ # - narration and message don't subsume each other -> both, joined.
19
+ # - otherwise keep the richer (longer) one, unless the narration is
20
+ # mere tool-usage narration (see NARRATION_TOOL_HINT below).
21
+ # 2. tool_msg empty or generic filler: prefer the narration if present.
22
+ # 3. Neither has content: fallback constant.
23
+ #
24
+ # The narration-skip heuristic: a narration that merely mentions "tool"
25
+ # ("I'll use the run_tests tool next...") is process talk, not an answer;
26
+ # the finish tool's message is the better user-facing summary, so the
27
+ # message wins even when the narration is longer.
28
+ module FinishMessageSelector
29
+ GENERIC_FILLERS = ["completed", "done", "finished", "ok", "task_completed"].freeze
30
+ NARRATION_TOOL_HINT = "tool"
31
+ DEFAULT_MESSAGE = "Completed"
32
+
33
+ module_function
34
+
35
+ def select(narration, tool_msg)
36
+ narration = narration.to_s
37
+ tool_msg = tool_msg.to_s
38
+
39
+ if !tool_msg.empty? && !GENERIC_FILLERS.include?(tool_msg.downcase)
40
+ if !narration.empty? && !narration.include?(tool_msg) && !tool_msg.include?(narration)
41
+ "#{narration}\n\n#{tool_msg}"
42
+ elsif narration.length > tool_msg.length && !narration.include?(NARRATION_TOOL_HINT)
43
+ narration
44
+ else
45
+ tool_msg
46
+ end
47
+ elsif !narration.empty?
48
+ narration
49
+ else
50
+ tool_msg.empty? ? DEFAULT_MESSAGE : tool_msg
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ module GoalLedger
6
+ # Drift işleme (goal-ledger planı B): failed-doğrulamaların iç mutasyona
7
+ # eşlenmesi (flip + last_check annotation), sessiz :ledger_drift
8
+ # emission'u ve reinjection taşıyıcısı DriftNote.
9
+ #
10
+ # Sessizlik sözleşmesi: :ledger_drift WAKE_KINDS DIŞINDADIR —
11
+ # notice/wake üretmez, vakanüvis abone değildir (yalnız kronik
12
+ # bus'da kaybolur); GoalState#notify_finish mold'ü: post fire-and-
13
+ # forget, rescue → Log.warn. Bus yoksa tamamen sessiz atlanır.
14
+ module Drift
15
+ EVENT = :ledger_drift
16
+ MAX_NOTE_ENTRIES = 8
17
+ MAX_NOTE_CHARS = 300
18
+
19
+ module_function
20
+
21
+ def emit(trigger:, ledger:, flips:, unknowns:, rev:)
22
+ bus = ledger.event_bus
23
+ unless bus.respond_to?(:post)
24
+ # Dürüst degradasyon (review 2026-10-10): bus yokken drift kaydı
25
+ # tamamen kaybolmasın — Log'a tek satır düşer.
26
+ Rubik::Log.warn(:orchestrator, "event bus unavailable — ledger drift dropped " \
27
+ "(#{flips.size} flip(s), #{unknowns.size} unknown(s), rev #{rev})")
28
+ return
29
+ end
30
+
31
+ bus.post(EVENT,
32
+ { trigger: trigger.to_s, rev: rev, flipped: flips.size,
33
+ unknowns: unknowns.map { |u| "unknown #{u[:collection]}[#{u[:index]}] #{u[:id]}: #{u[:reason]}" },
34
+ entries: flips.map { |f| "#{f[:collection]}[#{f[:index]}] #{f[:id]}: #{f[:reason]}" } },
35
+ subsystem: :orchestrator, level: :warn)
36
+ rescue StandardError => e
37
+ Rubik::Log.warn(:orchestrator, "ledger drift event post failed: #{e.class}: #{e.message}")
38
+ end
39
+ end
40
+
41
+ # Reinjection taşıyıcısı: son doğrulamada FAILED olan (o an stale)
42
+ # girdilerden en yeni MAX_NOTE_ENTRIES tanesi, girdi başına
43
+ # MAX_NOTE_CHARS. Boşsa HİÇ render edilmez (OFF/drift'siz koşu
44
+ # byte-özdeş). Tüm metin xml_escape'ten geçer.
45
+ module DriftNote
46
+ module_function
47
+
48
+ def render(snap:)
49
+ entries = []
50
+ Array(snap["tasks"]).each do |task|
51
+ check = task.is_a?(Hash) && task["verify"].is_a?(Hash) ? task["verify"]["last_check"] : nil
52
+ next unless check.is_a?(Hash) && check["state"] == "failed"
53
+
54
+ entries << "task #{task['id']} (was #{check['from']}) -> stale: #{check['reason']} at #{check['at']}"
55
+ end
56
+ Array(snap["environment_notes"]).each do |note|
57
+ check = note.is_a?(Hash) && note["verify"].is_a?(Hash) ? note["verify"]["last_check"] : nil
58
+ next unless check.is_a?(Hash) && check["state"] == "failed"
59
+
60
+ entries << "environment_note #{note['id']} -> verify failed: #{check['reason']} at #{check['at']}"
61
+ end
62
+ return nil if entries.empty?
63
+
64
+ esc = Rubik::Platform.method(:xml_escape)
65
+ body = entries.last(Drift::MAX_NOTE_ENTRIES).map { |line| esc.call(line.to_s.slice(0, Drift::MAX_NOTE_CHARS)) }
66
+ %(<ledger_drift rev="#{snap['rev']}" entries="#{body.size}">\n #{body.join("\n ")}\n</ledger_drift>)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end