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,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Prompt
5
+ # Frozen-in view of everything the prompt renderer needs (architecture
6
+ # review 2026-10-01, candidate 2). Unlike Tools::Services there is NO
7
+ # fresh tier and NO coeffect lookup here: the prompt layer never
8
+ # constructs services (a Guard/Sandbox built here would be policy the
9
+ # caller never granted) and never resolves coeffects — the orchestrator
10
+ # resolves, the prompt renders. Unsatisfied slots degrade to empty
11
+ # sections, exactly matching the builder's historical nil-tolerant
12
+ # rendering.
13
+ #
14
+ # Owns: the slot list (the Struct members are the SSOT) and its frozen
15
+ # snapshot semantics.
16
+ # Leaves: where values come from — the composition root builds the
17
+ # view (from_shell / explicit kwargs); coeffect resolution (skills
18
+ # index, type registry) stays in the orchestrator.
19
+ Services = Struct.new(
20
+ :config, :commands, :buffer_manager, :snapshot_store, :context_index,
21
+ :lsp_manager, :skills_index, :type_registry, :context_limit,
22
+ :max_output_tokens, :root_dir, :extra_context,
23
+ keyword_init: true
24
+ ) do
25
+ def initialize(config: nil, commands: nil, buffer_manager: nil, snapshot_store: nil, context_index: nil,
26
+ lsp_manager: nil, skills_index: nil, type_registry: nil, context_limit: nil,
27
+ max_output_tokens: nil, root_dir: Dir.pwd, extra_context: "")
28
+ super
29
+ freeze
30
+ end
31
+
32
+ # Reader-only shell view: a public reader contributes its value, a
33
+ # reader-less duck type contributes nothing (graceful degradation,
34
+ # never a raise — an ivar without a reader is invisible here, by
35
+ # design). Explicit non-nil overrides beat shell readers, preserving
36
+ # the historical kwarg-over-shell precedence
37
+ # (test_from_shell_overrides_beat_shell_readers).
38
+ # root_dir/context_limit/max_output_tokens/skills_index/extra_context
39
+ # are per-run values, not shell state — they arrive via overrides.
40
+ def self.from_shell(shell, **overrides)
41
+ base = {
42
+ config: reader(shell, :config),
43
+ commands: reader(shell, :commands),
44
+ buffer_manager: reader(shell, :buffer_manager),
45
+ snapshot_store: reader(shell, :snapshot_store),
46
+ context_index: reader(shell, :context_index),
47
+ lsp_manager: reader(shell, :lsp_manager),
48
+ type_registry: reader(shell, :subagent_supervisor)&.type_registry
49
+ }
50
+ new(**base, **overrides.compact)
51
+ end
52
+
53
+ def self.reader(shell, name)
54
+ shell.respond_to?(name) ? shell.public_send(name) : nil
55
+ end
56
+
57
+ private_class_method :reader
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../platform"
5
+ require_relative "conversation_rows"
6
+
7
+ module Rubik
8
+ module Prompt
9
+ module TokenEstimator
10
+ CHARS_PER_TOKEN = 3.8
11
+
12
+ module_function
13
+
14
+ def estimate_text(text)
15
+ return 0 if text.nil?
16
+
17
+ str = text.to_s
18
+ return 0 if str.empty?
19
+
20
+ (str.length / CHARS_PER_TOKEN).ceil
21
+ end
22
+
23
+ def estimate_message(msg)
24
+ return 0 unless ConversationRows.row?(msg)
25
+
26
+ tokens = 4 # Base message overhead (role delimiter, headers)
27
+ role = ConversationRows.value_of(msg, :role)
28
+ tokens += estimate_text(role) if role
29
+
30
+ content = ConversationRows.value_of(msg, :content)
31
+ tokens += estimate_text(content) if content
32
+
33
+ tool_calls = msg[:tool_calls] || msg["tool_calls"]
34
+ if tool_calls && !tool_calls.empty?
35
+ tool_calls_str = tool_calls.is_a?(String) ? Rubik::Platform.clean_utf8(tool_calls) : Rubik::Platform.safe_json_generate(tool_calls)
36
+ tokens += estimate_text(tool_calls_str)
37
+ end
38
+
39
+ tool_call_id = msg[:tool_call_id] || msg["tool_call_id"]
40
+ tokens += estimate_text(tool_call_id) if tool_call_id
41
+
42
+ tool_name = msg[:tool_name] || msg["tool_name"]
43
+ tokens += estimate_text(tool_name) if tool_name
44
+
45
+ tokens
46
+ end
47
+
48
+ def estimate_history(messages)
49
+ return 0 unless messages.is_a?(Array)
50
+
51
+ messages.sum { |msg| estimate_message(msg) }
52
+ end
53
+
54
+ def estimate_tool_schemas(schemas)
55
+ return 0 if schemas.nil?
56
+ return 0 if schemas.respond_to?(:empty?) && schemas.empty?
57
+
58
+ json_str = schemas.is_a?(String) ? Rubik::Platform.clean_utf8(schemas) : Rubik::Platform.safe_json_generate(schemas)
59
+ estimate_text(json_str)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Prompt
5
+ # Tool teachings keyed by canonical tool name (architecture review
6
+ # 2026-10-01, candidate 1): the sentences that teach HOW a specific tool
7
+ # is used live here, one key per tool, instead of being embedded unnamed
8
+ # in Doctrine.operational_rules_xml's heredoc. Identity (the tool) lives in
9
+ # its Tools::Declaration; prose (the presentation) lives here — a prompt
10
+ # layer concern. test_tool_teachings welds the two: every key must
11
+ # resolve to a declared (Catalog) tool, so a rename that forgets the
12
+ # prompt fails the suite.
13
+ #
14
+ # The strings are interpolated into the operational rules HEREDOC at the
15
+ # same positions they originally occupied — the rendered rules are
16
+ # byte-identical (pinned by the substring tests in
17
+ # test_structured_context_builder, test_agent_command_contract,
18
+ # test_buffer_apply_conflict, test_repl_tool_separation_and_guardrails).
19
+ module ToolTeachings
20
+ TEACHINGS = {
21
+ "replace_file_content" =>
22
+ "For modifying existing files, prefer `replace_file_content` with exact verbatim `target_content` " \
23
+ "(or `old_content`) and `replacement_content` (or `new_content`).",
24
+ "write_file" =>
25
+ "For creating new files or completely rewriting files, use `write_file`.",
26
+ "apply_buffer" =>
27
+ "All edits stage safely into the in-memory buffer. Call `apply_buffer` to persist buffer edits to disk. " \
28
+ "If `apply_buffer` reports `[CONFLICT]` due to external modifications (e.g. from code formatters or " \
29
+ "external processes), inspect latest disk changes with `read_file(path: \"...\", reload: true)` or " \
30
+ "overwrite deliberately with `apply_buffer(force: true)`.",
31
+ "synthesize_tool" =>
32
+ "When you need a specialized capability not available in the built-in toolbelt (such as custom AST " \
33
+ "transformers, data converters, or domain-specific benchmarkers), dynamically create and register your " \
34
+ "own tool at runtime using `synthesize_tool(name: '...', description: '...', code: '->(args) { ... }')`.",
35
+ "storage" =>
36
+ "Persist agent workfiles — goals, plans, memory notes, wiki knowledge, reviews — in the central " \
37
+ "`storage` database (`storage` tool: put/get/list/search/set_ref/delete), NOT as scattered files in " \
38
+ "the repository worktree. Read entities back with `read_file(path: 'rubik://storage/<type>/<id>')`; " \
39
+ "browse with `rubik://storage/index` and `rubik://storage/<type>` listings.",
40
+ "run_command" =>
41
+ "GitHub facts must be verified, not assumed: after `gh pr merge` (which can succeed with EMPTY output) " \
42
+ "confirm the merge with `gh pr view <number> --json state`; `mergeable: UNKNOWN` right after a push means " \
43
+ "GitHub is still recomputing — wait briefly and re-check instead of rebasing; `gh pr checks` exits 1 both " \
44
+ "for failing checks and for repos with no CI — disambiguate with `gh pr view --json statusCheckRollup`. " \
45
+ "In repos that intentionally commit no lockfile, install dependencies with `npm install --no-package-lock` " \
46
+ "(or the package manager's equivalent) so setup does not dirty the worktree with a new lockfile.",
47
+ "show_file" =>
48
+ "When the user asks to SEE a stored entity (review report, plan, ...), use " \
49
+ "`show_file(path: 'rubik://storage/...')` — it renders the full content directly to their terminal " \
50
+ "while your context stays lean; only fall back to read_file when YOU must reason about the content.",
51
+ "vakanuvis" =>
52
+ "The background chronicler keeps compiled knowledge (memory cards, wiki pages) in rubik://storage. " \
53
+ "At task start — and again after any compaction — ask it with `vakanuvis(action: 'recall', query: '...')` " \
54
+ "instead of pre-loading pages: it returns a deterministic digest plus rubik:// source links. When " \
55
+ "dispatching a subagent, paste the relevant recall digest (or its rubik:// link) into the task brief. " \
56
+ "At the end of a task, persist only the durable knowledge with `vakanuvis(action: 'record', topic: '...', " \
57
+ "body: '...')` — memory writes go through record (ref minting, upsert, supersede come free), never " \
58
+ "through raw storage puts. Recall is keyword-AND over card/page text, so END every recorded body and " \
59
+ "digest query with its own vocabulary: include a `Keywords:` line listing 5-10 synonyms in BOTH working " \
60
+ "languages (e.g. TR + EN) — a Turkish query cannot otherwise find an English page. If recall is too " \
61
+ "shallow, queue `vakanuvis(action: 'digest', query: '...')`: it returns a deterministic receipt and " \
62
+ "compiles the wiki page asynchronously. Make the session close a standard digest point: review the " \
63
+ "session's new cards, queue one digest per open topic, and let the home index refresh.",
64
+ "prune_context" =>
65
+ "When long sessions leave stale exploration and superseded tool output weighing on the ROOT window, " \
66
+ "check `prune_context(action: 'status')` (window estimate vs ceiling, staged plan state) and force a " \
67
+ "cleanup with `prune_context(action: 'prune', reason: '...')`: it queues an LLM-proposed, " \
68
+ "harness-validated keep/drop/summarize plan that applies at the NEXT turn boundary — the call returns " \
69
+ "a deterministic receipt and never blocks. Goal/task statements, decisions and recent context are " \
70
+ "always preserved; deterministic compaction remains the emergency safety net."
71
+ }.freeze
72
+
73
+ module_function
74
+
75
+ def teaching_for(tool_name) = TEACHINGS[tool_name.to_s]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "prompt/context_builder"
4
+ require_relative "prompt/doctrine"
5
+ require_relative "prompt/services"
6
+ require_relative "prompt/token_estimator"
7
+ require_relative "prompt/context_compactor"
8
+ require_relative "prompt/emergency_compactor"
9
+
10
+ module Rubik
11
+ module Prompt
12
+ end
13
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "tempfile"
5
+
6
+ module Rubik
7
+ module Repair
8
+ class Loop
9
+ attr_reader :max_attempts, :attempts_made, :config
10
+
11
+ def initialize(provider:, buffer_manager:, snapshot_store:, adapter_registry:, max_attempts: nil, config: nil)
12
+ @provider = provider
13
+ @buffer_manager = buffer_manager
14
+ @snapshot_store = snapshot_store
15
+ @adapter_registry = adapter_registry
16
+ @config = config
17
+ @max_attempts = (max_attempts || config&.fetch("repair.max_attempts", 3) || 3).to_i
18
+ @attempts_made = 0
19
+ end
20
+
21
+ def repair(diagnostics, target_file, lang = :ruby)
22
+ @attempts_made = 0
23
+ current_diag = diagnostics
24
+
25
+ while @attempts_made < @max_attempts
26
+ @attempts_made += 1
27
+
28
+ @snapshot_store.create_snapshot(@buffer_manager)
29
+
30
+ prompt = build_repair_prompt(current_diag, target_file, @attempts_made)
31
+ res = @provider.complete([{ role: "user", content: prompt }])
32
+ proposed_code = extract_code(res.text)
33
+
34
+ @buffer_manager.write(target_file, proposed_code)
35
+
36
+ # Verify syntax against in-memory buffer via tempfile
37
+ check_res = check_buffer_syntax(target_file, lang)
38
+
39
+ return { ok: true, attempts: @attempts_made, code: proposed_code } if check_res[:ok]
40
+
41
+ current_diag = Rubik::Diagnostics::Parser.parse(check_res[:output], lang)
42
+ @snapshot_store.undo(@buffer_manager)
43
+
44
+ end
45
+
46
+ { ok: false, attempts: @attempts_made, last_diagnostics: current_diag }
47
+ end
48
+
49
+ private
50
+
51
+ def check_buffer_syntax(target_file, lang)
52
+ adapter = @adapter_registry.get(lang.to_s)
53
+ return { ok: true } unless adapter
54
+
55
+ content = @buffer_manager.read(target_file)
56
+ ext = File.extname(target_file)
57
+
58
+ Tempfile.create(["rubik_repair_", ext]) do |tmp|
59
+ tmp.write(content)
60
+ tmp.flush
61
+ adapter.check_syntax(tmp.path)
62
+ end
63
+ end
64
+
65
+ def build_repair_prompt(diagnostics, target_file, attempt_num)
66
+ diag_str = diagnostics.map { |d| d.format_prompt_snippet(nil, @buffer_manager.root_dir, @buffer_manager) }.join("\n\n")
67
+ current_content = @buffer_manager.read(target_file) || ""
68
+
69
+ <<~PROMPT
70
+ You are repairing code that failed with compiler/test errors.
71
+ Attempt #{attempt_num} of #{@max_attempts}.
72
+
73
+ Target File: #{target_file}
74
+ Current Content:
75
+ ```
76
+ #{current_content}
77
+ ```
78
+
79
+ Diagnostics:
80
+ #{diag_str}
81
+
82
+ Output ONLY the corrected code for #{target_file} wrapped in ```code``` blocks.
83
+ PROMPT
84
+ end
85
+
86
+ def extract_code(text)
87
+ if text.include?("```")
88
+ text.split("```").at(1).to_s.sub(/^[a-z]+\n/, "").strip
89
+ else
90
+ text.strip
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "repair/loop"
4
+
5
+ module Rubik
6
+ module Repair
7
+ end
8
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ # Öneri 3 (Kademe 4): Shell'den ayrılan AST ısınma bloğu. warm_ast_graph
6
+ # ısınmayı senkron ya da arka plan thread'inde tetikler; stop_background_indexer
7
+ # ve join_indexer_safely indexer thread'inin çökmesiz teardown'unu (Audit 1)
8
+ # sağlar; perform_warm_ast_graph delta tarama → indeksleme → stale temizliği →
9
+ # cache kalıcılığı zincirini yürütür.
10
+ # Durum Shell'de yaşar (@indexer_thread, @buffer_manager, @context_index,
11
+ # @ast_graph, @cache_store, @logger, @event_bus); mixin yalnızca ısınma
12
+ # politikasını kodlar.
13
+ module AstWarmup
14
+ # Öneri 3 (Kademe 4): warm_ast_graph / stop_background_indexer /
15
+ # join_indexer_safely / perform_warm_ast_graph,
16
+ # lib/rubik/repl/ast_warmup.rb mixin'ine taşındı (AST grafiği ısınma ve
17
+ # arka plan indexer yaşam döngüsü).
18
+
19
+ def warm_ast_graph(async: true)
20
+ if async
21
+ stop_background_indexer
22
+ # Reset the cooperative-stop flag so repeated warm cycles behave the
23
+ # same as the first (per-turn scopes, MCP reconnects, tool epochs).
24
+ @indexer_stop_requested = false
25
+ @indexer_thread = Thread.new do
26
+ perform_warm_ast_graph
27
+ rescue StandardError => e
28
+ @logger&.warn("AST graph background indexer thread error: #{e.class}: #{e.message}")
29
+ end
30
+ ctx = @runtime_manager&.context || (respond_to?(:context) ? context : nil)
31
+ if ctx.respond_to?(:register_disposable) && ctx != @indexer_disposable_ctx
32
+ @indexer_disposable_ctx = ctx
33
+ ctx.register_disposable { stop_background_indexer }
34
+ end
35
+ else
36
+ perform_warm_ast_graph
37
+ end
38
+ end
39
+
40
+ # Single-homed "workspace roots changed → resync index → re-warm AST"
41
+ # sequence (formerly hand-wired in !workspace add/remove and reactive
42
+ # config wiring). sync_roots already performs additional_roots= +
43
+ # full_scan + fiber reconcile; the sync re-warm keeps symbols fresh
44
+ # before control returns to the caller.
45
+ def sync_workspace_index!
46
+ return unless @context_index
47
+
48
+ Rubik::Runtime::IndexService.sync_roots(
49
+ @runtime_manager, @context_index,
50
+ additional_roots: @buffer_manager&.additional_roots,
51
+ ast_graph: @ast_graph, cache_store: @cache_store
52
+ )
53
+ warm_ast_graph(async: false)
54
+ end
55
+
56
+ # Audit 1: teardown/join of the background indexer never crashes shutdown
57
+ # or blocks symbol lookups; detail surfaced via debug_warn. The bounded
58
+ # join mechanics live in Concurrency::Join; the failure log channel
59
+ # stays here.
60
+ #
61
+ # Cooperative cancellation, NOT Thread#kill: a one-shot warm cycle may run
62
+ # perform_warm_ast_graph's delta-scan -> index -> purge -> save_to_cache
63
+ # chain, and #kill would abort it between save_to_cache and completion,
64
+ # leaving a partially written / inconsistent cache (AGENTS.md §5 leaked
65
+ # side-effects + partial-write). The worker checks @indexer_stop_requested
66
+ # before committing the cache; here we only request + graceful-join.
67
+ def stop_background_indexer
68
+ return unless @indexer_thread
69
+
70
+ @indexer_stop_requested = true
71
+ join_indexer_safely
72
+ @indexer_thread = nil unless @indexer_thread&.alive?
73
+ rescue StandardError => e
74
+ debug_warn("Background AST indexer teardown failed: #{e.class}: #{e.message}")
75
+ nil
76
+ end
77
+
78
+ def join_indexer_safely(timeout = nil)
79
+ result = Rubik::Concurrency::Join.thread(@indexer_thread, timeout: timeout)
80
+ debug_warn("Background AST indexer join failed: #{result.class}: #{result.message}") if result.is_a?(StandardError)
81
+ nil
82
+ end
83
+
84
+ def perform_warm_ast_graph
85
+ root_d = @buffer_manager&.root_dir || Dir.pwd
86
+ delta = @context_index.incremental_scan
87
+ indexed_files = @ast_graph.file_symbols.keys
88
+
89
+ files_to_index = (delta[:added] + delta[:modified]).dup
90
+
91
+ @context_index.files.each_key do |rel_file|
92
+ break if @indexer_stop_requested
93
+
94
+ full_path = File.join(root_d, rel_file)
95
+ ext = File.extname(rel_file).downcase
96
+ files_to_index << rel_file if Rubik::AST::Extractor.supported_extensions.include?(ext) && !(indexed_files.include?(full_path) || indexed_files.include?(rel_file))
97
+ end
98
+
99
+ files_to_index.uniq.each do |rel_file|
100
+ break if @indexer_stop_requested
101
+
102
+ full_path = File.join(root_d, rel_file)
103
+ ext = File.extname(rel_file).downcase
104
+ @ast_graph.index_file(full_path) if Rubik::AST::Extractor.supported_extensions.include?(ext) && File.exist?(full_path)
105
+ end
106
+
107
+ delta[:deleted].each do |rel_file|
108
+ break if @indexer_stop_requested
109
+
110
+ full_path = File.join(root_d, rel_file)
111
+ @ast_graph.remove_file(full_path)
112
+ end
113
+
114
+ return if @indexer_stop_requested
115
+
116
+ purged = Rubik::Runtime::ASTService.purge_stale_entries!(@ast_graph, @context_index, root_d)
117
+
118
+ if @cache_store && File.directory?(root_d) && !@indexer_stop_requested &&
119
+ (!files_to_index.empty? || !delta[:deleted].empty? || purged.positive?)
120
+ @ast_graph.save_to_cache(@cache_store)
121
+ end
122
+ rescue StandardError => e
123
+ @logger&.warn("AST graph warm failed: #{e.message}")
124
+ @event_bus&.emit(:ast_warm_failed, error: e.message)
125
+ nil
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "service_catalog"
4
+ require_relative "../session/replayer"
5
+
6
+ module Rubik
7
+ module REPL
8
+ # Per-dispatch, closed view of the Shell for bang-command handlers —
9
+ # the §7.1 ShellDeps pattern applied to the command axis. Built inside
10
+ # InputExecution#execute_command, AFTER with_output has swapped the
11
+ # capture TeeIO in, so ctx.out is exactly the stream a handler should
12
+ # print to on both the human and the exec_repl_command path (recorder
13
+ # capture and LLM-history truncation keep working unchanged).
14
+ #
15
+ # Closed surface (frozen by test_command_context):
16
+ # · out / output : the active (capture-aware) IO. `output` exists so
17
+ # the UI collaborators (Panel / DiffViewer / Approvals) can take ctx
18
+ # where they previously took the shell — structural duck, UI files
19
+ # untouched.
20
+ # · services : derived from ServiceCatalog.reader_names — the SSOT.
21
+ # Adding a catalog service extends ctx automatically; optional
22
+ # services read as nil (no respond_to? probing).
23
+ # · rebindable : conversation_history / active_loop (get + set) and
24
+ # conversation_route_stamp (write-only) — the ONLY Shell state a
25
+ # command may rebind. Writers delegate to the Shell accessors; ctx
26
+ # never shadows them.
27
+ # · effects : the named verbs below. Anything else raises
28
+ # NoMethodError — extend deliberately, together with the freeze test
29
+ # and the boundary guard.
30
+ #
31
+ # Handlers are invoked as handler.call(args, ctx). There is no
32
+ # register_command, no ivar access and no method_missing: the interface
33
+ # a command sees is exactly this list.
34
+ class CommandContext
35
+ # Shell-owned readers outside the catalog (lifecycle state, table, IO).
36
+ SHELL_READERS = [:indexer_thread, :input, :commands].freeze
37
+
38
+ # Derived from the SSOT at load time; frozen so the surface cannot
39
+ # grow ad hoc.
40
+ SERVICE_READERS = (ServiceCatalog.reader_names + SHELL_READERS).freeze
41
+
42
+ def initialize(shell)
43
+ @shell = shell
44
+ end
45
+
46
+ # Dynamic, NOT constructor-captured: with_output swaps @output before
47
+ # the handler runs, and refresh_ui! rebinds it afterwards — freezing
48
+ # the IO here would silently detach every command print from the
49
+ # capture/record seam.
50
+ def out
51
+ @shell.output
52
+ end
53
+ alias output out
54
+
55
+ # IO conveniences for stored 2-arity synthesized handlers whose source
56
+ # contract was ->(args, out) { out.puts ... }: they now receive ctx,
57
+ # so ctx itself must print.
58
+ def puts(*)
59
+ out.puts(*)
60
+ end
61
+
62
+ def print(*)
63
+ out.print(*)
64
+ end
65
+
66
+ def flush
67
+ out.flush if out.respond_to?(:flush)
68
+ end
69
+
70
+ SERVICE_READERS.each do |name|
71
+ define_method(name) { @shell.public_send(name) }
72
+ end
73
+
74
+ # Read-only inspection view (overrides the loop-generated reader):
75
+ # handlers may consult the command table (!help / !commands) but
76
+ # never mutate it — registrations belong to the shell's spine.
77
+ def commands
78
+ @shell.commands.dup.freeze
79
+ end
80
+
81
+ # Live presentation theme (re-resolved per call: refresh_ui! may swap
82
+ # the theme between registration and invocation).
83
+ def theme
84
+ @shell.theme
85
+ end
86
+
87
+ # ── Plugin coeffects (catalog entries expose no shell reader) ───────
88
+ def plugin_manager
89
+ coeffect(Rubik::Coeffects::PLUGIN_MANAGER)
90
+ end
91
+
92
+ def plugin_storage
93
+ coeffect(Rubik::Coeffects::PLUGIN_STORAGE)
94
+ end
95
+
96
+ # ── Rebindable conversation state (write-through delegation) ────────
97
+ def conversation_history
98
+ @shell.conversation_history
99
+ end
100
+
101
+ def conversation_history=(history)
102
+ @shell.conversation_history = history
103
+ end
104
+
105
+ def active_loop
106
+ @shell.active_loop
107
+ end
108
+
109
+ def active_loop=(loop_instance)
110
+ @shell.active_loop = loop_instance
111
+ end
112
+
113
+ def conversation_route_stamp=(stamp)
114
+ @shell.conversation_route_stamp = stamp
115
+ end
116
+
117
+ # Additional Shell readers commands legitimately consult (plain reads,
118
+ # no writers — see the rebindable block above for what IS writable).
119
+ def agent_caller
120
+ @shell.agent_caller
121
+ end
122
+
123
+ def current_model_spec
124
+ @shell.current_model_spec
125
+ end
126
+
127
+ # ── Command effects (named, closed; one-line delegations) ───────────
128
+ def stop
129
+ @shell.stop
130
+ end
131
+
132
+ def execute_free_text(text)
133
+ @shell.execute_free_text(text)
134
+ end
135
+
136
+ def resume_session(identifier = :latest)
137
+ @shell.resume_session(identifier)
138
+ end
139
+
140
+ def refresh_ui!
141
+ @shell.refresh_ui!
142
+ end
143
+
144
+ def render_step_event(step_info)
145
+ @shell.render_step_event(step_info)
146
+ end
147
+
148
+ def detect_git_branch
149
+ @shell.detect_git_branch
150
+ end
151
+
152
+ def sync_context_and_ast(files)
153
+ @shell.sync_context_and_ast(files)
154
+ end
155
+
156
+ def sync_workspace_index!
157
+ @shell.sync_workspace_index!
158
+ end
159
+
160
+ def resync_llm_route!
161
+ @shell.resync_llm_route!
162
+ end
163
+
164
+ def refresh_llm_registry!
165
+ @shell.refresh_llm_registry!
166
+ end
167
+
168
+ def trim_conversation_window!
169
+ @shell.trim_conversation_window!
170
+ end
171
+
172
+ # Pruner apply seam (goal yolu): aşamalı budama planını turn başında,
173
+ # orchestrator.run pencereyi tohumlamadan ÖNCE uygular (input_execution
174
+ # yolu Shell'e doğrudan çağırır; !goal buradan geçer).
175
+ def apply_staged_prune_plan!
176
+ @shell.apply_staged_prune_plan!
177
+ end
178
+
179
+ def ask_yes_no(prompt, default: "n")
180
+ @shell.ask_yes_no(prompt, default: default)
181
+ end
182
+
183
+ def orchestrator_loop(opts = {})
184
+ @shell.orchestrator_loop(opts)
185
+ end
186
+
187
+ def warm_ast_graph(async: true)
188
+ @shell.warm_ast_graph(async: async)
189
+ end
190
+
191
+ def build_tool_registry
192
+ @shell.build_tool_registry
193
+ end
194
+
195
+ def request_paste!
196
+ @shell.request_paste!
197
+ end
198
+
199
+ # The one whole-shell handoff a command may still make: the replayer
200
+ # plants @replaying on its receiver and re-enters execute_input, so it
201
+ # must own the real Shell — passing ctx would plant the flag on the
202
+ # wrong object and break replay suppression.
203
+ def session_replayer
204
+ Rubik::Session::Replayer.new(@shell)
205
+ end
206
+
207
+ private
208
+
209
+ def coeffect(key)
210
+ Rubik::Coeffects.get(@shell.runtime_manager&.context, key)
211
+ end
212
+ end
213
+ end
214
+ end