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,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module Rubik
6
+ module REPL
7
+ module Pruner
8
+ # Düzlem 1 çekirdeği (Vakanüvis::Harness yapısı): tek worker thread
9
+ # + sınırlı FIFO. ANLIK GÖRÜNTÜ her zaman ÇAĞIRAN thread'de alınır
10
+ # (:turn_finished abonelik bloğu turn thread'inde koşar; araç/komut
11
+ # çağrısı da turn thread'indedir) — worker shell durumuna ASLA
12
+ # dokunmaz; o yalnız brif kurar, doğurur, hasat eder ve planı
13
+ # aşamalar. Sessizlik sözleşmesi: "Context Pruner" SILENT_ROLES'tadır
14
+ # — notice yok, wake yok, yalnız dmesg. Uygulama (pencere takası)
15
+ # yalnız turn thread'inde, apply_staged_prune_plan! üzerinden.
16
+ class Harness
17
+ STOP = :__pruner_stop__
18
+ MAX_QUEUE = 16
19
+ TURN_EVENT = :turn_finished # TurnRecorder::TURN_EVENT_TARGET ile eş pinli
20
+ COMPLETED_EVENT = :subagent_completed
21
+ TYPE_NAME = "pruner"
22
+ PRUNER_ROLE = "Context Pruner"
23
+
24
+ attr_reader :shell, :supervisor, :dmesg, :gate
25
+
26
+ def initialize(shell:, config: nil, event_bus: nil, supervisor: nil, dmesg: nil, context: nil)
27
+ @shell = shell
28
+ @config = config
29
+ @event_bus = event_bus
30
+ @supervisor = supervisor
31
+ @dmesg = dmesg
32
+ @context = context
33
+ @queue = Thread::Queue.new
34
+ @monitor = Monitor.new
35
+ @busy = 0
36
+ @stopped = false
37
+ @worker = nil
38
+ @gate = Gate.new(shell: shell, config: config, supervisor: supervisor, dmesg: dmesg,
39
+ event_bus: event_bus, ceiling_resolver: method(:resolve_ceiling))
40
+ end
41
+
42
+ # ---- yaşam döngüsü ---------------------------------------------------
43
+
44
+ def start!
45
+ subscribe_events
46
+ @worker = Thread.new { run_worker }
47
+ @worker.name = "rubik-pruner" if @worker.respond_to?(:name=)
48
+ self
49
+ end
50
+
51
+ def stopped? = @monitor.synchronize { @stopped }
52
+
53
+ # Dispose sözleşmesi (vakanüvis): FIFO drain olur (STOP dahil önceki
54
+ # olaylar işlenir), thread 2 saniyede durmazsa kill + dürüst dmesg.
55
+ def shutdown
56
+ @monitor.synchronize { @stopped = true }
57
+ @monitor.synchronize { @busy += 1 }
58
+ @queue.push(STOP)
59
+ @worker&.join(2)
60
+ if @worker&.alive?
61
+ Pruner.log_via(@dmesg, :warn, "worker did not stop in 2s — killing with #{queue_size} event(s) unprocessed")
62
+ @worker.kill
63
+ end
64
+ @worker = nil
65
+ end
66
+
67
+ def wait_idle(timeout: 2.0)
68
+ deadline = Time.now + timeout
69
+ sleep 0.01 while busy? && Time.now < deadline
70
+ !busy?
71
+ end
72
+
73
+ def busy? = @monitor.synchronize { @busy.positive? }
74
+
75
+ def queue_size = @queue.size
76
+
77
+ # ---- talep düzlemi (prune_context aracı / !prune komutu) ----------
78
+
79
+ # Zorunlu budama isteği: LLM asla bekletilmez — deterministik makbuz
80
+ # döner. Kuyruk reddi (kapanış yarışı) yer tutucuyu temizler ve dürüst
81
+ # ret makbuzu döner (makbuz asla kuyruğa girmeyen işe söz vermez).
82
+ def prune(reason: "manual")
83
+ envelope, skip = @gate.stage_job(@shell.conversation_history, reason: reason, forced: true)
84
+ return decline_receipt(skip) unless envelope
85
+
86
+ if !stopped? && enqueue_job(envelope)
87
+ "Prune queued (async): the pruner will propose a keep/drop/summarize plan for the ROOT window; " \
88
+ "it is validated deterministically and applied at the next turn boundary. " \
89
+ "This call did NOT wait for the LLM. Reason: #{reason}."
90
+ else
91
+ @gate.drop_job(envelope, why: "harness stopped")
92
+ decline_receipt(:stopped)
93
+ end
94
+ end
95
+
96
+ # ---- turn sınırı uygulama kapısı (TurnRecorder çağırır) -----------
97
+
98
+ # Aşamalı planı canlı pencereye uygular; dönüş [yeni_pencere,
99
+ # makbuz] veya nil (plan yok / canlı doğrulama düşürdü).
100
+ def take_applicable_plan!(live_window)
101
+ @gate.take_staged!(live_window, config: compaction_config)
102
+ end
103
+
104
+ def status_text
105
+ Rules.status_report(window: @shell.conversation_history, ceiling: resolve_ceiling,
106
+ bits: @gate.status_bits)
107
+ end
108
+
109
+ private
110
+
111
+ def subscribe_events
112
+ return unless @event_bus.respond_to?(:subscribe)
113
+
114
+ # Anlık görüntü abonelik bloğunda alınır. DÜRÜST NOT: bus post'u
115
+ # fire-and-forget'tir — blok rubik-kworker thread'inde koşar, turn
116
+ # thread'i DEĞİLDİR; okuma bir sonraki turn'ün pencere mutasyonuyla
117
+ # yarışabilir. Güvenlik apply katmanından gelir: hash8 yeniden
118
+ # doğrulaması yarışan birimleri "bayat op" olarak atlar (sayılır),
119
+ # uygulama-sonu değişmezleri ve kopyala-doğrula-takas pencereyi
120
+ # bozulmadan korur — yarış en fazla bir planın düşmesine mal olur.
121
+ @event_bus.subscribe(TURN_EVENT, context: @context) do |_payload|
122
+ @gate.note_turn!
123
+ envelope, = @gate.stage_job(@shell.conversation_history, reason: "turn", forced: false)
124
+ @gate.drop_job(envelope, why: "harness stopped") if envelope && !enqueue_job(envelope)
125
+ end
126
+ @event_bus.subscribe(COMPLETED_EVENT, context: @context) { |p| enqueue_event(:completed, p) }
127
+ end
128
+
129
+ def enqueue_job(envelope)
130
+ enqueue_event(:job, { envelope: envelope })
131
+ end
132
+
133
+ def enqueue_event(kind, payload)
134
+ return false if stopped?
135
+
136
+ unless payload.is_a?(Hash)
137
+ Pruner.log_via(@dmesg, :warn, "discarded non-hash #{kind} event (#{payload.class})")
138
+ return false
139
+ end
140
+
141
+ drop_oldest! if @queue.size >= MAX_QUEUE
142
+ @monitor.synchronize { @busy += 1 }
143
+ @queue.push([kind, payload])
144
+ true
145
+ end
146
+
147
+ # Taşma düşürmesi asla STOP'u yutamaz (vakanüvis kuralı); düşen
148
+ # işin gate'teki izi uzlaştırılır — :job yer tutucusu temizlenir,
149
+ # :completed sicil girdisi silinir (kapı asla "uçuşta" takılı
150
+ # kalamaz) ve taşan tamamlanmanın id'si loglanır.
151
+ def drop_oldest!
152
+ item = @queue.pop(true)
153
+ if item == STOP
154
+ @queue.push(STOP)
155
+ else
156
+ @monitor.synchronize { @busy -= 1 }
157
+ if item[0] == :job
158
+ @gate.drop_job(item[1][:envelope], why: "queue overflow")
159
+ elsif item[0] == :completed
160
+ @gate.discard_completion(item[1])
161
+ end
162
+ Pruner.log_via(@dmesg, :warn, "event queue overflow — dropped oldest #{item[0]} event")
163
+ end
164
+ rescue ThreadError
165
+ nil
166
+ end
167
+
168
+ def run_worker
169
+ loop do
170
+ kind, payload = @queue.pop
171
+ break if kind == STOP
172
+
173
+ handle_event(kind, payload)
174
+ ensure
175
+ @monitor.synchronize { @busy -= 1 }
176
+ end
177
+ end
178
+
179
+ # Pruner'in kendi hatası REPL akışını asla bozmaz — uyarı düşer,
180
+ # sonraki olay işlenir (TranscriptIndexer disiplini).
181
+ def handle_event(kind, payload)
182
+ case kind
183
+ when :job then handle_job(payload[:envelope])
184
+ when :completed then @gate.completion_event(payload)
185
+ end
186
+ rescue StandardError => e
187
+ Pruner.log_via(@dmesg, :warn, "event handling failed (#{kind}): #{e.class}: #{e.message}")
188
+ end
189
+
190
+ def handle_job(envelope)
191
+ brief = Brief.build(units: envelope[:units], ceiling: envelope[:ceiling],
192
+ target_tokens: envelope[:target_tokens], trigger_ratio: envelope[:trigger_ratio],
193
+ max_chars: envelope[:brief_budget_chars])
194
+ return @gate.drop_job(envelope, why: "brief over the hard bound") unless brief
195
+
196
+ envelope[:brief] = brief
197
+ @gate.spawn_job(envelope)
198
+ end
199
+
200
+ def decline_receipt(skip)
201
+ case skip
202
+ when :busy then "A pruner run is already in flight — this request was not duplicated."
203
+ when :staged then "A validated prune plan is already staged and applies at the next turn boundary."
204
+ when :no_ceiling then "Prune declined: the effective input ceiling is unknown for the current model."
205
+ when :stopped then "Prune declined: the pruner harness is not running (session teardown)."
206
+ else "Prune declined (#{skip})."
207
+ end
208
+ end
209
+
210
+ # Koruma bantları context.* SSOT'sundan (preserve_first_n/last_n).
211
+ def compaction_config
212
+ {
213
+ "preserve_first_n" => fetch_cfg("context.preserve_first_n", 2),
214
+ "preserve_last_n" => fetch_cfg("context.preserve_last_n", 6)
215
+ }
216
+ end
217
+
218
+ # trim_conversation_window! ile aynı tavan çözümü (sticky damgalı
219
+ # model spec'i + llm_registry; hata → nil → kapı atlar).
220
+ def resolve_ceiling
221
+ spec = @shell.current_model_spec
222
+ return nil unless spec.is_a?(Hash) && @shell.respond_to?(:llm_registry) && @shell.llm_registry
223
+
224
+ @shell.llm_registry.effective_input_limit_for(spec["provider"], spec["name"])
225
+ rescue StandardError => e
226
+ Pruner.log_via(@dmesg, :warn, "input ceiling lookup failed (#{e.class}: #{e.message}); prune gate skipped")
227
+ nil
228
+ end
229
+
230
+ def fetch_cfg(key, default)
231
+ @config.respond_to?(:fetch) ? @config.fetch(key, default) : default
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "digest"
5
+ require_relative "../../platform"
6
+ require_relative "../../prompt/context_compactor"
7
+
8
+ module Rubik
9
+ module REPL
10
+ module Pruner
11
+ # Düzlem 1 kuralları (durumsuz, saf): pencereyi atomik birimlere
12
+ # böler (koruma bantları + hedef sabitlemesi dahil), LLM planını
13
+ # katı şemayla doğrular ve DOĞRULANMIŞ planı canlı pencereye
14
+ # kopyala-doğrula-takas disipliniyle uygular. Mevcut sıkıştırma
15
+ # koduna dokunmaz — ContextCompactor/TokenEstimator/ConversationRows
16
+ # yalniz ÇAĞRILIR (subagent yolu bu dosyadan habersizdir).
17
+ module Rules
18
+ MAX_OPS = 64
19
+ SUMMARY_MIN_CHARS = 40
20
+ SUMMARY_MAX_CHARS = 2000
21
+ KEEP_PREVIEW_CHARS = 400
22
+ MIN_WINDOW_ENTRIES = 4 # trim_history_to_budget min_entries aynası
23
+ UNIT_ID = /\Au\d+:[0-9a-f]{8}\z/
24
+
25
+ module_function
26
+
27
+ # Pencereyi budanabilir birim anlık görüntüsüne çevirir. Birim =
28
+ # Prompt::ContextCompactor.group_atomic_units çıktısı (assistant+tool
29
+ # eşleşmesi yapısal olarak korunur). Korumalı birimler (KEEP):
30
+ # hedef sabitlemeli (<user_task> çifti), baş/bant (preserve_first_n
31
+ # mesajını kaplayan birimler), kuyruk bandı (preserve_last_n), system
32
+ # satırı taşıyanlar. id = "u<sıra>:<hash8>" — sıra benzersizlik,
33
+ # hash8 bayatlık işaretidir (apply sırasında içerik kimliği).
34
+ def build_units(window, config: {})
35
+ groups = Prompt::ContextCompactor.group_atomic_units(Array(window))
36
+ units = groups.map do |rows|
37
+ { rows: rows.map { |r| Prompt::ConversationRows.row?(r) ? r.dup : r } }
38
+ end
39
+ units.each { |u| u[:pinned_goal?] = Prompt::ContextCompactor.unit_pins_goal?(u[:rows]) }
40
+
41
+ protect_bands!(units, config)
42
+ units.each_with_index do |u, idx|
43
+ u[:id] = "u#{idx}:#{unit_digest(u[:rows])}"
44
+ u[:tokens] = Prompt::TokenEstimator.estimate_history(u[:rows])
45
+ u[:role] = role_label(u[:rows])
46
+ end
47
+ units.each(&:freeze)
48
+ units.freeze
49
+ end
50
+
51
+ def unit_digest(rows)
52
+ canonical = rows.map { |r| canonical_row(r) }
53
+ Digest::SHA256.hexdigest(Platform.safe_json_generate(canonical))[0, 8]
54
+ end
55
+
56
+ # İki satır diyalektini (symbol/string anahtar) tek kanonik şekle
57
+ # indirir — nil okumalar "" olur, anahtar sırası hash'i değiştirmez.
58
+ def canonical_row(row)
59
+ return { "row" => "" } unless Prompt::ConversationRows.row?(row)
60
+
61
+ {
62
+ "role" => Prompt::ConversationRows.role_of(row),
63
+ "content" => Prompt::ConversationRows.content_of(row).to_s,
64
+ "tool_call_id" => Prompt::ConversationRows.value_of(row, :tool_call_id).to_s,
65
+ "tool_name" => Prompt::ConversationRows.value_of(row, :tool_name).to_s,
66
+ "status" => Prompt::ConversationRows.value_of(row, :status).to_s,
67
+ "tool_calls" => tool_calls_json(row)
68
+ }
69
+ end
70
+
71
+ # LLM planını ANLIK GÖRÜNTÜYE karşı doğrular (canlı durum yok).
72
+ # Dönüş: [plan, nil] veya [nil, insan-okur ret sebebi]. Beyaz liste
73
+ # grameri: {"ops":[{"op":"drop","id":...},{"op":"summarize","id":...,
74
+ # "text":...}]} — model satır/rol/talimat ENJEKTE EDEMEZ, yalnız
75
+ # önceden doğrulanmış birimler arasından seçer ve ≤2000 karakterlik
76
+ # özet yazar.
77
+ def validate_plan(raw_finish_body, units)
78
+ return [nil, "final message is empty"] if raw_finish_body.to_s.strip.empty?
79
+
80
+ parsed = parse_json_object(raw_finish_body.to_s)
81
+ return [nil, parsed] if parsed.is_a?(String)
82
+
83
+ ops = parsed["ops"]
84
+ return [nil, "'ops' must be an array"] unless ops.is_a?(Array)
85
+ return [nil, "plan needs at least one op"] if ops.empty?
86
+ return [nil, "plan exceeds #{MAX_OPS} ops"] if ops.size > MAX_OPS
87
+
88
+ by_id = units.to_h { |u| [u[:id], u] }
89
+ seen = {}
90
+ normalized = []
91
+ ops.each do |op|
92
+ reason = normalize_op(op, by_id, seen, normalized)
93
+ return [nil, reason] if reason
94
+ end
95
+ [{ "ops" => normalized }, nil]
96
+ end
97
+
98
+ # Doğrulanmış planı CANLI pencereye uygular (yalnız turn thread'i
99
+ # çağırır). Her op'un hash'i yeniden doğrulanır: içerik değişen
100
+ # birimler tek tek atlanır (bayat op), genel değişmezlerden biri
101
+ # bozulursa TÜM plan düşer — giriş dizisi asla mutasyona uğramaz.
102
+ # Dönüş: [yeni_pencere, makbuz] veya [nil, sebep].
103
+ def apply_plan(window, plan, target_tokens:, config: {})
104
+ return [nil, "no plan"] unless plan.is_a?(Hash) && plan["ops"].is_a?(Array)
105
+
106
+ units = build_units(window, config: config)
107
+ outcome = resolve_ops(plan["ops"], units)
108
+ applied = outcome[:applied]
109
+ return [nil, "all ops went stale (window changed since the plan)"] if applied.empty?
110
+
111
+ candidate = assemble(units, applied)
112
+ reason = post_invariant_reason(window, candidate, target_tokens)
113
+ return [nil, reason] if reason
114
+
115
+ after_tokens = Prompt::TokenEstimator.estimate_history(candidate)
116
+ receipt = { dropped: applied.count { |_id, op| op[:kind] == :drop },
117
+ summarized: applied.count { |_id, op| op[:kind] == :summarize },
118
+ skipped_stale: outcome[:skipped_stale],
119
+ before_tokens: Prompt::TokenEstimator.estimate_history(window),
120
+ after_tokens: after_tokens }
121
+ [candidate, receipt]
122
+ end
123
+
124
+ def status_report(window:, ceiling:, bits: {})
125
+ est = Prompt::TokenEstimator.estimate_history(Array(window))
126
+ pct = ceiling&.positive? ? " (#{(est * 100.0 / ceiling).round}% of ceiling)" : ""
127
+ lines = ["Context pruner:",
128
+ " window: #{Array(window).size} entries, ~#{est} est. tokens#{pct}",
129
+ " ceiling: #{ceiling || 'unknown'}"]
130
+ if bits.empty?
131
+ lines << " gate: idle"
132
+ else
133
+ lines << " pruner in flight: #{bits[:in_flight] ? 'yes' : 'no'}"
134
+ lines << " staged plan: #{bits[:staged] ? 'waiting for the next turn boundary' : 'none'}"
135
+ lines << " last receipt: #{bits[:last_receipt] || 'none yet'}"
136
+ end
137
+ lines.join("\n")
138
+ end
139
+
140
+ # ---- iç kısım ---------------------------------------------------------
141
+
142
+ # Hash döner veya ret sebebi String'i — validate_plan'ın begin/rescue
143
+ # return dansını izole eder (Lint/NoReturnInBeginEndBlocks).
144
+ def parse_json_object(raw)
145
+ parsed = JSON.parse(raw)
146
+ return "plan must be a JSON object" unless parsed.is_a?(Hash)
147
+ return "plan must have exactly one 'ops' key" unless parsed.keys == ["ops"]
148
+
149
+ parsed
150
+ rescue JSON::ParserError
151
+ "final message is not valid JSON"
152
+ end
153
+
154
+ # Baş/kuyruk koruma bantları apply_sliding_window ile AYNI hesapla
155
+ # (context_compactor.rb:180-199): baş birimleri preserve_first_n
156
+ # mesajı, kuyruk birimleri preserve_last_n mesajı kaplayana kadar;
157
+ # system satırı ve sabitlenmiş hedef birimi her yerde korumalı.
158
+ def protect_bands!(units, config)
159
+ first_n = (config["preserve_first_n"] || config[:preserve_first_n] || 2).to_i
160
+ last_n = (config["preserve_last_n"] || config[:preserve_last_n] || 6).to_i
161
+
162
+ protected_idx = {}
163
+ head_count = 0
164
+ head_idx = 0
165
+ while head_idx < units.size && head_count < first_n
166
+ protected_idx[head_idx] = :head
167
+ head_count += units[head_idx][:rows].size
168
+ head_idx += 1
169
+ end
170
+ tail_count = 0
171
+ tail_idx = units.size - 1
172
+ while tail_idx >= head_idx && tail_count < last_n
173
+ protected_idx[tail_idx] = :tail
174
+ tail_count += units[tail_idx][:rows].size
175
+ tail_idx -= 1
176
+ end
177
+
178
+ units.each_with_index do |u, idx|
179
+ u[:protected?] = protected_idx.key?(idx) || u[:pinned_goal?] ||
180
+ u[:rows].any? { |r| Prompt::ConversationRows.role_of(r) == "system" }
181
+ end
182
+ end
183
+
184
+ def role_label(rows)
185
+ first = Prompt::ConversationRows.role_of(rows.first)
186
+ rows.size > 1 ? "#{first}+tool" : first
187
+ end
188
+
189
+ def tool_calls_json(row)
190
+ calls = row[:tool_calls] || row["tool_calls"]
191
+ return "" unless calls && !calls.empty?
192
+
193
+ Platform.safe_json_generate(calls)
194
+ rescue StandardError
195
+ calls.to_s
196
+ end
197
+
198
+ # Tek op normalize eder; ret sebebi döndürür (nil = kabul).
199
+ def normalize_op(entry, by_id, seen, normalized)
200
+ return "each op must be a JSON object" unless entry.is_a?(Hash)
201
+
202
+ kind = entry["op"]
203
+ return "unknown op '#{kind}' (valid: drop, summarize)" unless ["drop", "summarize"].include?(kind)
204
+
205
+ expected = kind == "drop" ? ["id", "op"].freeze : ["id", "op", "text"].freeze
206
+ return "op keys must be exactly #{expected.join(', ')}" unless entry.keys.sort == expected
207
+
208
+ id = entry["id"]
209
+ return "malformed unit id '#{id}'" unless id.is_a?(String) && id.match?(UNIT_ID)
210
+ return "duplicate op for unit '#{id}'" if seen.key?(id)
211
+
212
+ unit = by_id[id]
213
+ return "unknown unit '#{id}'" unless unit
214
+ return "unit '#{id}' is protected (KEEP)" if unit[:protected?]
215
+
216
+ seen[id] = true
217
+ clean_op = { "op" => kind, "id" => id }
218
+ if kind == "summarize"
219
+ text = entry["text"]
220
+ return "summarize text must be a string" unless text.is_a?(String)
221
+
222
+ clean = Platform.safe_utf8(text)
223
+ return "summary must be #{SUMMARY_MIN_CHARS}-#{SUMMARY_MAX_CHARS} chars (got #{clean.length})" unless clean.length.between?(SUMMARY_MIN_CHARS, SUMMARY_MAX_CHARS)
224
+
225
+ # Enjeksiyon kapanışı (review 2026-10-09): özet metni <user_task>
226
+ # etiketi taşayla YENİ sabitlenmiş hedef satırı doğurabilir —
227
+ # deterministik yığın o satırı sonsuza dek korurdu. Etiketler
228
+ # özet içinde asla birebir yaşayamaz.
229
+ # <goal_ledger> kaçışı (goal-ledger planı A): sahte pinnenmiş Σ
230
+ # satırı doğması aynı enjeksiyon sınıfıdır — etiketler özet
231
+ # içinde asla birebir yaşayamaz.
232
+ clean = clean.gsub("<user_task>", "&lt;user_task&gt;").gsub("</user_task>", "&lt;/user_task&gt;")
233
+ clean = clean.gsub("<goal_ledger ", "&lt;goal_ledger ").gsub("</goal_ledger>", "&lt;/goal_ledger>")
234
+ clean_op["text"] = clean
235
+ end
236
+ normalized << clean_op
237
+ nil
238
+ end
239
+
240
+ # Op'ları canlı birimlere bağlar. Önce tam id eşleşmesi; olmazsa
241
+ # hash8 eşleşmesi (sıra kaydırdıysa içerik hâlâ aynı olabilir).
242
+ # Birden çok hash eşleşmesi belirsizdir → op bayat sayılır.
243
+ def resolve_ops(ops, units)
244
+ applied = {}
245
+ skipped = 0
246
+ units_by_id = units.to_h { |u| [u[:id], u] }
247
+ ops.each do |op|
248
+ hash8 = op["id"].split(":").last
249
+ unit = units_by_id[op["id"]]
250
+ unless unit && !unit[:protected?] && !applied.key?(unit[:id])
251
+ matches = units.select do |u|
252
+ !u[:protected?] && !applied.key?(u[:id]) && u[:id].end_with?(":#{hash8}")
253
+ end
254
+ unit = matches.size == 1 ? matches.first : nil
255
+ end
256
+ if unit.nil?
257
+ skipped += 1
258
+ next
259
+ end
260
+
261
+ applied[unit[:id]] = { kind: op["op"].to_sym, text: op["text"] }
262
+ end
263
+ { applied: applied, skipped_stale: skipped }
264
+ end
265
+
266
+ # Birimleri özgün sırasında birleştirir: düşürülenler çıkar,
267
+ # özetlenenler tek <pruned_summary> user satırına iner (role "user"
268
+ # — sıkı sağlayıcılar ortada system satırı reddeder; compactor
269
+ # summary_message geleneği).
270
+ def assemble(units, applied)
271
+ candidate = []
272
+ units.each do |u|
273
+ op = applied[u[:id]]
274
+ if op.nil?
275
+ candidate.concat(u[:rows])
276
+ elsif op[:kind] == :drop
277
+ next
278
+ else
279
+ candidate << {
280
+ role: "user",
281
+ content: "<pruned_summary unit=\"#{u[:id]}\" rows=\"#{u[:rows].size}\">#{op[:text]}</pruned_summary>"
282
+ }
283
+ end
284
+ end
285
+ candidate
286
+ end
287
+
288
+ # Uygulama-sonu değişmezleri — biri bozulursa plan bütünüyle düşer.
289
+ # nil = ihlal yok; String = insan-okur ihlal sebebi.
290
+ def post_invariant_reason(original, candidate, target_tokens)
291
+ return "pruned window would be empty" if candidate.empty?
292
+ return "pruned window must open with a user turn (strict providers)" unless Prompt::ConversationRows.role_of(candidate.first) == "user"
293
+ return "pruned window would be too small (< #{MIN_WINDOW_ENTRIES} entries)" if candidate.size < MIN_WINDOW_ENTRIES
294
+ if Prompt::TokenEstimator.estimate_history(candidate) > target_tokens
295
+ return "pruned window still over target (#{Prompt::TokenEstimator.estimate_history(candidate)} > #{target_tokens} tokens)"
296
+ end
297
+
298
+ # Hedef satır sayısı BİREBİR kalmalı (review 2026-10-09): yalnız
299
+ # eksilmesi değil ARTMASI da ret — özet metninden enjekte edilen
300
+ # sahte <user_task> satırı sonsuza dek sabitlenmiş hedef olurdu.
301
+ original_goals = original.count { |r| Prompt::ContextCompactor.goal_message?(r) }
302
+ candidate_goals = candidate.count { |r| Prompt::ContextCompactor.goal_message?(r) }
303
+ return "goal-pinned message count changed (#{original_goals} -> #{candidate_goals})" if candidate_goals != original_goals
304
+
305
+ # Σ satırı sayısı değişmezi (goal-ledger planı A): hedef satırıyla
306
+ # aynı savunma — eksilme de artma da ret.
307
+ original_ledger = original.count { |r| Prompt::ContextCompactor.ledger_message?(r) }
308
+ candidate_ledger = candidate.count { |r| Prompt::ContextCompactor.ledger_message?(r) }
309
+ return "goal-ledger message count changed (#{original_ledger} -> #{candidate_ledger})" if candidate_ledger != original_ledger
310
+
311
+ nil
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ module Pruner
6
+ # Kompozisyon kökü kancası (repl/lifecycle.rb Phase 4, Vakanüvis
7
+ # bind'inden hemen sonra — SHELL/SUPERVISOR/DMESG coeffect'leri Faz
8
+ # 2'den çözülebilir olmalı): budayıcı açıksa Harness kurulur, kendi
9
+ # child ctx'inde yaşar (abonelik geri alımları orada), PRUNER
10
+ # coeffect'i köke sağlanır (prune_context aracı buradan çözer;
11
+ # apply_staged_prune_plan! da). Kapalıysa
12
+ # (subagents.pruner.enabled=false — opt-out) nil döner: budayıcı
13
+ # yok, araç REQUIRES karşılanmaz ve model yüzünde görünmez, REPL
14
+ # komutu dürüstçe kapalı der.
15
+ module Service
16
+ module_function
17
+
18
+ # Test zırhı (vakanüvis emsali): RUBIK_TEST=1 ortamında budayıcı
19
+ # varsayılan KAPALI bağlanır — scripted shell testleri arka plan
20
+ # doğumlarıyla yarışmasın. RUBIK_PRUNER_FORCE=1 zırhı kaldırır
21
+ # (budayıcının kendi testleri). Knob varsayılanı ON'dur (2026-10-10);
22
+ # zırh yalnızca süit içi davranışı sabitler.
23
+ def enabled?(config = nil)
24
+ return false if ENV["RUBIK_TEST"] == "1" && ENV["RUBIK_PRUNER_FORCE"] != "1"
25
+
26
+ config_get(config, "subagents.pruner.enabled", true)
27
+ end
28
+
29
+ def bind(context_or_manager, config: nil, event_bus: nil)
30
+ return nil unless enabled?(config)
31
+
32
+ ctx = context_or_manager.respond_to?(:context) ? context_or_manager.context : context_or_manager
33
+ return nil unless ctx.respond_to?(:provide)
34
+
35
+ shell = Rubik::Coeffects.get(ctx, Rubik::Coeffects::SHELL)
36
+ unless shell
37
+ Rubik::Log.warn(:repl, "pruner not bound: SHELL coeffect unsatisfied (boot-order gap)")
38
+ return nil
39
+ end
40
+ supervisor = Rubik::Coeffects.get(ctx, Rubik::Coeffects::SUBAGENT_SUPERVISOR)
41
+ unless supervisor
42
+ Rubik::Log.warn(:repl, "pruner not bound: SUBAGENT_SUPERVISOR coeffect unsatisfied — no spawn path")
43
+ return nil
44
+ end
45
+ Rubik::Log.warn(:repl, "pruner bound without an event bus — the LLM half stays dormant (no harvest path)") unless event_bus.respond_to?(:subscribe)
46
+
47
+ child = ctx.respond_to?(:extend) ? ctx.extend : nil
48
+ unless child
49
+ Rubik::Log.warn(:repl, "pruner bound without a disposable child ctx — worker and " \
50
+ "subscriptions live for the process lifetime")
51
+ end
52
+
53
+ harness = Harness.new(
54
+ shell: shell, config: config, event_bus: event_bus,
55
+ supervisor: supervisor, dmesg: Rubik::Coeffects.get(ctx, Rubik::Coeffects::DMESG),
56
+ context: child
57
+ )
58
+ # Ömür (§2.7): child ctx'in cascade inverse'i worker'ı kapatır.
59
+ child&.on_dispose { harness.shutdown }
60
+ harness.start!
61
+ ctx.provide(Rubik::Coeffects::PRUNER, harness)
62
+ context_or_manager.reconcile_all if context_or_manager.respond_to?(:reconcile_all)
63
+ harness
64
+ end
65
+
66
+ # Noktalı anahtar okuması iki config şeklinde de aynı çalışsın
67
+ # (maestro_gate.rb:38-40 düzeni): Rubik::Config#fetch noktalı yolu
68
+ # böler, düz Hash hepsini tek anahtar sayar → dig'e düş.
69
+ def config_get(config, key, default)
70
+ return default if config.nil?
71
+
72
+ val = if config.is_a?(Hash)
73
+ config.dig(*key.to_s.split("."))
74
+ elsif config.respond_to?(:fetch)
75
+ config.fetch(key, nil)
76
+ end
77
+ val.nil? ? default : val
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ # Pruner — yarı deterministik KÖK konuşma penceresi budayıcısı
6
+ # (context-pruner planı 2026-10-09, vakanüvis iki-düzlem modeli):
7
+ #
8
+ # Düzlem 1 (deterministik): tetik kapısı, brif kurulumu, plan
9
+ # doğrulaması ve turn sınırındaki aşamalı uygulama (Rules/Brief/Gate).
10
+ # Düzlem 2 (LLM): sessiz "Context Pruner" subagent'ı keep/drop/
11
+ # summarize planını ÖNERİR — yazma yetkisi yoktur; öneri katı
12
+ # doğrulamadan geçmezse pencere dokunulmadan kalır.
13
+ #
14
+ # Deterministik sıkıştırma yığını (trim_conversation_window! /
15
+ # ContextCompactor / EmergencyCompactor) DEĞİŞMEZ ve her zaman güvenlik
16
+ # ağıdır; subagent kanallarının budama mekanizmaları bu özellikten
17
+ # tamamen bağımsız, tam deterministik kalır. DEFAULT ON
18
+ # (subagents.pruner.enabled; opt-out: false — 2026-10-10).
19
+ module Pruner
20
+ SUBSYSTEM = :repl
21
+
22
+ module_function
23
+
24
+ # Vakanüvis.log_via emsali: dmesg yoksa Log'a, ikisi de patlarsa
25
+ # stderr'e — günlükleme asla akışı bozmaz.
26
+ def log_via(dmesg, level, message)
27
+ dmesg.respond_to?(:log) ? dmesg.log(SUBSYSTEM, level, message) : Rubik::Log.send(level, SUBSYSTEM, message)
28
+ rescue StandardError
29
+ Rubik::Log.warn(SUBSYSTEM, message)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ require_relative "pruner/rules"
36
+ require_relative "pruner/brief"
37
+ require_relative "pruner/gate"
38
+ require_relative "pruner/harness"
39
+ require_relative "pruner/service"