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,481 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "digest"
6
+ require "time"
7
+ # save_manifest atomik yazımı Platform.atomic_write kullanır; dosyanın tek başına
8
+ # require edilmesi durumunda da çözümlenebilsin diye açık yüklenir.
9
+ require_relative "../platform"
10
+
11
+ module Rubik
12
+ module Runtime
13
+ class OverlayStorage
14
+ attr_reader :root_dir, :home_dir
15
+
16
+ COMMAND_SENTINEL = "# Rubik Custom Command:"
17
+ TOOL_SENTINEL = "# Rubik Custom Tool:"
18
+
19
+ def initialize(root_dir: Dir.pwd, home_dir: ENV.fetch("HOME", nil), guard: nil)
20
+ @root_dir = File.expand_path(root_dir)
21
+ @home_dir = File.expand_path(home_dir || ENV["HOME"] || ".")
22
+ @guard = guard
23
+ ensure_directories
24
+ end
25
+
26
+ def project_commands_dir
27
+ File.join(@root_dir, ".rubik", "commands")
28
+ end
29
+
30
+ def global_commands_dir
31
+ File.join(@home_dir, ".rubik", "commands")
32
+ end
33
+
34
+ def project_tools_dir
35
+ File.join(@root_dir, ".rubik", "tools")
36
+ end
37
+
38
+ def global_tools_dir
39
+ File.join(@home_dir, ".rubik", "tools")
40
+ end
41
+
42
+ def ensure_directories
43
+ [project_commands_dir, global_commands_dir, project_tools_dir, global_tools_dir].each do |dir|
44
+ FileUtils.mkdir_p(dir)
45
+ rescue SystemCallError => e
46
+ warn "[WARN] Failed to create overlay directory #{dir}: #{e.message}" if ENV["RUBIK_DEBUG"]
47
+ end
48
+ end
49
+
50
+ def manifest_path(dir)
51
+ File.join(dir, ".manifest.json")
52
+ end
53
+
54
+ def load_manifest(dir)
55
+ path = manifest_path(dir)
56
+ return {} unless File.exist?(path)
57
+
58
+ JSON.parse(File.read(path))
59
+ rescue JSON::ParserError
60
+ {}
61
+ end
62
+
63
+ def save_manifest(dir, manifest)
64
+ # Atomik yazım (tmp + rename): kilitsiz okuyucuların (load_manifest, manifest_state)
65
+ # yarım manifest görmesini engeller; yarım okuma, kayıtlı overlay'lerin "unknown"
66
+ # sayılıp kalıcı olarak reddedilmesine yol açabiliyordu. Platform.atomic_write,
67
+ # Windows için unlink/rename yedeğiyle bu düzeni sağlar.
68
+ Platform.atomic_write(manifest_path(dir), JSON.pretty_generate(manifest))
69
+ end
70
+
71
+ def normalize_content(raw)
72
+ raw.delete("\r")
73
+ end
74
+
75
+ def record_hash(dir, filename, scope)
76
+ if manifest_state(dir) == :corrupt
77
+ # Bozuk manifesti sessizce "heal" etmek (yalnızca yeni kaydı yazmak) diğer
78
+ # kayıtlı overlay'leri tahliye eder; bunun yerine kaydı reddet.
79
+ raise "cannot record '#{filename}': .manifest.json in #{dir} is corrupt; delete it and re-record overlays"
80
+ end
81
+
82
+ path = File.join(dir, filename)
83
+ hash = Digest::SHA256.hexdigest(normalize_content(File.binread(path)))
84
+ manifest = load_manifest(dir)
85
+ manifest[filename] = {
86
+ "sha256" => hash,
87
+ "scope" => scope.to_s,
88
+ "created_at" => Time.now.iso8601
89
+ }
90
+ save_manifest(dir, manifest)
91
+ end
92
+
93
+ def verify_integrity(file_path, dir, bootstrap_eligible: false)
94
+ return :trusted if trust_mode?
95
+
96
+ basename = File.basename(file_path)
97
+ manifest = load_manifest(dir)
98
+ current_content = File.exist?(file_path) ? File.binread(file_path) : ""
99
+ current_hash = Digest::SHA256.hexdigest(normalize_content(current_content))
100
+
101
+ if manifest.key?(basename)
102
+ # Known file — verify hash
103
+ expected_hash = manifest[basename]["sha256"]
104
+ if current_hash == expected_hash
105
+ :verified
106
+ else
107
+ warn "[SECURITY] Integrity check failed for overlay '#{basename}': file modified " \
108
+ "since synthesis. Skipping. (expected: #{expected_hash}, got: #{current_hash})"
109
+ :rejected
110
+ end
111
+ elsif bootstrap_eligible && sentinel_content?(current_content)
112
+ # Bootstrap (tek seferlik): yalnızca manifest dosyasının hiç var olmadığı ilk taramada,
113
+ # sentinel başlıklı dosya kabul edilip hash'i manifest'e yazılır; uygunluk kararını
114
+ # verified_dir_entries taramanın başında verir. Manifest varken bootstrap asla çalışmaz.
115
+ # Kabul ÖNCE synthesis guard'ından geçer (Aday 07 ertelemesi): klonlanan depodaki
116
+ # dosya bu ortamda sentezlenmemişti; reddedilen dosya manifest'e yazılmaz.
117
+ verdict = screen_bootstrap_content(current_content, basename)
118
+ return verdict unless verdict == :ok
119
+
120
+ scope_match = current_content.match(/# Scope:\s*(.*)/)
121
+ scope = scope_match ? scope_match[1].strip : "unknown"
122
+
123
+ manifest[basename] = {
124
+ "sha256" => current_hash,
125
+ "scope" => scope,
126
+ "created_at" => Time.now.iso8601
127
+ }
128
+ save_manifest(dir, manifest)
129
+ :bootstrapped
130
+ else
131
+ warn "[SECURITY] Unknown overlay file '#{basename}' not in manifest. Skipping."
132
+ :rejected
133
+ end
134
+ end
135
+
136
+ def save_command(name, code, scope: :project, description: "Custom command")
137
+ clean_name = name.to_s.downcase.delete_prefix("!").gsub(/[^a-z0-9_:]/, "_")
138
+ target_dir = scope.to_sym == :global ? global_commands_dir : project_commands_dir
139
+ FileUtils.mkdir_p(target_dir)
140
+
141
+ filename = "#{clean_name.gsub(':', '_')}.rb"
142
+ file_path = File.join(target_dir, filename)
143
+ header = <<~RUBY
144
+ # frozen_string_literal: true
145
+ #{COMMAND_SENTINEL} !#{clean_name}
146
+ # Scope: #{scope}
147
+ # Description: #{description}
148
+
149
+ RUBY
150
+
151
+ File.write(file_path, "#{header}#{code.strip}\n")
152
+ record_hash(target_dir, filename, scope)
153
+ { ok: true, path: file_path, name: clean_name, scope: scope }
154
+ rescue StandardError => e
155
+ { ok: false, error: e.message }
156
+ end
157
+
158
+ # Delete a synthesized command from disk and manifest. Symmetric to
159
+ # save_command: same clean_name normalization, scope→dir selection and
160
+ # { ok:, error: } shape. Fail-closed on a corrupt manifest — never wipe
161
+ # it with {} (mirror the record_hash guard on the save path).
162
+ def delete_command(name, scope: :project)
163
+ clean_name = name.to_s.downcase.delete_prefix("!").gsub(/[^a-z0-9_:]/, "_")
164
+ target_dir = scope.to_sym == :global ? global_commands_dir : project_commands_dir
165
+ filename = "#{clean_name.gsub(':', '_')}.rb"
166
+ file_path = File.join(target_dir, filename)
167
+
168
+ raise "cannot delete '#{filename}': .manifest.json in #{target_dir} is corrupt; delete it and re-record overlays" if manifest_state(target_dir) == :corrupt
169
+
170
+ manifest = load_manifest(target_dir)
171
+ manifest.delete(filename)
172
+ save_manifest(target_dir, manifest)
173
+
174
+ FileUtils.rm_f(file_path)
175
+ { ok: true, name: clean_name, scope: scope, path: file_path }
176
+ rescue StandardError => e
177
+ { ok: false, error: e.message }
178
+ end
179
+
180
+ def list_commands
181
+ # Global commands; project commands (can override global) — sıra ve dönüş şekli korunur.
182
+ verified_dir_entries(global_commands_dir, :global) +
183
+ verified_dir_entries(project_commands_dir, :project)
184
+ end
185
+
186
+ def load_all(shell, _manager = nil)
187
+ loaded = []
188
+ list_commands.each do |cmd_meta|
189
+ next if cmd_meta[:integrity] == :rejected
190
+
191
+ file_path = cmd_meta[:path]
192
+ cmd_name = cmd_meta[:name]
193
+
194
+ begin
195
+ code = File.read(file_path)
196
+ eval_command(shell, cmd_name, code, cmd_meta[:scope])
197
+ loaded << cmd_name
198
+ rescue StandardError => e
199
+ warn "[OVERLAY] Failed to load custom command '#{cmd_name}' from #{file_path}: #{e.class}: #{e.message}"
200
+ end
201
+ end
202
+ loaded
203
+ end
204
+
205
+ def eval_command(shell, cmd_name, code, scope)
206
+ desc_match = code.match(/# Description:\s*(.*)/)
207
+ description = desc_match ? desc_match[1].strip : "Custom #{scope} command"
208
+
209
+ proc_handler = if code.include?("->") || code.include?("lambda") || code.include?("proc")
210
+ eval(code, clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
211
+ else
212
+ eval("->(args, output, shell) {\n#{code}\n}", clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
213
+ end
214
+
215
+ registered = shell.register_command(cmd_name, description: "#{description} [#{scope}]", custom: true) do |args, ctx|
216
+ # Arity contract mirrored from SynthesizeCommandTool: 2-arity
217
+ # handlers receive the per-dispatch CommandContext (its IO
218
+ # methods delegate to the active capture stream), 3-arity keeps
219
+ # the raw shell for trusted persisted code. Direct invocation
220
+ # outside dispatch has no ctx: use the shell's factory, or its
221
+ # raw output for shell doubles predating the contract. The warn
222
+ # keeps a ctx-forgetting dispatch site visible instead of
223
+ # silently auto-healed.
224
+ out_ctx = if ctx
225
+ ctx
226
+ elsif shell.respond_to?(:command_context)
227
+ warn "[rubik] overlay command invoked without a dispatch CommandContext" unless ENV["RUBIK_TEST"]
228
+ shell.command_context
229
+ else
230
+ shell.output
231
+ end
232
+ if proc_handler.arity == 1
233
+ proc_handler.call(args)
234
+ elsif proc_handler.arity == 2
235
+ proc_handler.call(args, out_ctx)
236
+ else
237
+ proc_handler.call(args, out_ctx.respond_to?(:out) ? out_ctx.out : out_ctx, shell)
238
+ end
239
+ end
240
+ # register_command, built-in çakışmasında sessizce false döner; red,
241
+ # load_all'un [OVERLAY] yoluna düşsün ve loaded listesi doğru kalsın.
242
+ raise "registration refused for '#{cmd_name}' (name collides with a built-in)" if registered == false
243
+ end
244
+
245
+ def save_tool(name, code, scope: :project, description: "Custom native tool", params_schema: nil, risk: :execute)
246
+ clean_name = name.to_s.downcase.gsub(/[^a-z0-9_]/, "_")
247
+ target_dir = scope.to_sym == :global ? global_tools_dir : project_tools_dir
248
+ FileUtils.mkdir_p(target_dir)
249
+
250
+ filename = "#{clean_name}.rb"
251
+ file_path = File.join(target_dir, filename)
252
+ schema_json = params_schema ? JSON.dump(params_schema) : "{}"
253
+ header = <<~RUBY
254
+ # frozen_string_literal: true
255
+ #{TOOL_SENTINEL} #{clean_name}
256
+ # Scope: #{scope}
257
+ # Risk: #{risk}
258
+ # Description: #{description}
259
+ # Schema: #{schema_json}
260
+
261
+ RUBY
262
+
263
+ File.write(file_path, "#{header}#{code.strip}\n")
264
+ record_hash(target_dir, filename, scope)
265
+ { ok: true, path: file_path, name: clean_name, scope: scope }
266
+ rescue StandardError => e
267
+ # Audit 1: surfaced to the caller as ok:false and logged under RUBIK_DEBUG.
268
+ warn "[WARN] [OVERLAY] Failed to save tool '#{name}' to #{file_path}: #{e.class}: #{e.message}" if ENV["RUBIK_DEBUG"]
269
+ { ok: false, error: e.message }
270
+ end
271
+
272
+ # Delete a synthesized tool from disk and manifest. Symmetric to
273
+ # save_tool: same clean_name normalization, scope→dir selection and
274
+ # { ok:, error: } shape. Fail-closed on a corrupt manifest — never wipe
275
+ # it with {} (mirror the record_hash guard on the save path).
276
+ def delete_tool(name, scope: :project)
277
+ clean_name = name.to_s.downcase.gsub(/[^a-z0-9_]/, "_")
278
+ target_dir = scope.to_sym == :global ? global_tools_dir : project_tools_dir
279
+ filename = "#{clean_name}.rb"
280
+ file_path = File.join(target_dir, filename)
281
+
282
+ raise "cannot delete '#{filename}': .manifest.json in #{target_dir} is corrupt; delete it and re-record overlays" if manifest_state(target_dir) == :corrupt
283
+
284
+ manifest = load_manifest(target_dir)
285
+ manifest.delete(filename)
286
+ save_manifest(target_dir, manifest)
287
+
288
+ FileUtils.rm_f(file_path)
289
+ { ok: true, name: clean_name, scope: scope, path: file_path }
290
+ rescue StandardError => e
291
+ { ok: false, error: e.message }
292
+ end
293
+
294
+ def list_tools
295
+ verified_dir_entries(global_tools_dir, :global) +
296
+ verified_dir_entries(project_tools_dir, :project)
297
+ end
298
+
299
+ def load_tools(tool_registry)
300
+ loaded = []
301
+ list_tools.each do |t_meta|
302
+ next if t_meta[:integrity] == :rejected
303
+
304
+ code = File.read(t_meta[:path])
305
+ tool_inst = eval_tool(t_meta[:name], code, t_meta[:scope])
306
+ if tool_inst
307
+ tool_registry.register(tool_inst, origin: :overlay)
308
+ loaded << t_meta[:name]
309
+ else
310
+ warn "[OVERLAY] Custom tool '#{t_meta[:name]}' from #{t_meta[:path]} did not evaluate to a usable tool."
311
+ end
312
+ rescue StandardError => e
313
+ warn "[OVERLAY] Failed to load custom tool '#{t_meta[:name]}' from #{t_meta[:path]}: #{e.class}: #{e.message}"
314
+ end
315
+ loaded
316
+ end
317
+
318
+ def eval_tool(name, code, scope)
319
+ desc_match = code.match(/# Description:\s*(.*)/)
320
+ description = desc_match ? desc_match[1].strip : "Custom #{scope} tool"
321
+
322
+ schema_match = code.match(/# Schema:\s*(.*)/)
323
+ schema = if schema_match
324
+ begin
325
+ JSON.parse(schema_match[1])
326
+ rescue JSON::ParserError
327
+ {}
328
+ end
329
+ else
330
+ {}
331
+ end
332
+
333
+ risk_match = code.match(/# Risk:\s*(.*)/)
334
+ risk = risk_match ? risk_match[1].strip.to_sym : :execute
335
+ # Hand-authored overlays may carry non-enum risk words; the
336
+ # Declaration seam validates read/write/execute, so clamp
337
+ # conservatively (execute = the most-guarded class) instead of
338
+ # dropping the tool at load.
339
+ risk = :execute unless [:read, :write, :execute].include?(risk)
340
+
341
+ evaluated = eval(code, clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
342
+ if evaluated.is_a?(Class) && evaluated < Rubik::Tools::BaseTool
343
+ evaluated.new
344
+ elsif evaluated.respond_to?(:call)
345
+ Class.new(Rubik::Tools::BaseTool) do
346
+ define_method(:initialize) do
347
+ super(name: name, description: "#{description} [#{scope}]", risk: risk, params_schema: schema)
348
+ @handler = evaluated
349
+ end
350
+
351
+ define_method(:execute) do |args = {}|
352
+ @handler.call(args)
353
+ end
354
+ end.new
355
+ end
356
+ end
357
+
358
+ private
359
+
360
+ # Bootstrap kabul taramasında kullanılacak guard: kwarg'la geçilen örnek
361
+ # (üretimde service catalog'un tek operatör-policy guard'ı) ya da lazy
362
+ # default. Lazy çözüm bilinçli: overlay_storage.rb standalone require
363
+ # edilebilen bir dosyadır; security/guard yalnızca tarama gerçekten
364
+ # gerektiğinde yüklenir, load-order denetimi gerekmez.
365
+ def bootstrap_guard
366
+ @bootstrap_guard ||= begin
367
+ require_relative "../security/guard"
368
+ @guard || Rubik::Security::Guard.new
369
+ end
370
+ end
371
+
372
+ # Aday 07'den ertelenen güvenlik kalemi: manifestsız dizindeki sentinel
373
+ # dosya manifest'e yazılmadan önce synthesis guard'ından geçirilir.
374
+ # Reddedilen (veya taranamayan) dosya manifest'e YAZILMAZ — sonraki tarama
375
+ # yeniden tarar ve reddeder (sessiz heal, bozuk-manifest doktriniyle
376
+ # çelişir); load_all/load_tools'un mevcut :rejected atlaması devreye girer.
377
+ # Parse hatası guard içinde yutulur (çalıştırılamaz kod güvenlik olayı
378
+ # değil); guard'ın kendisi patlarsa da dosya fail-closed reddedilir —
379
+ # SystemStackError dahil: rakip içerikte derin yuvalama AST walk'ı
380
+ # taşırebilir, tek dosyanın reddiyesi tüm taramayı çökertmemeli.
381
+ # Bilinçli asimetri: dosyanın TAMAMI (Description/Schema başlığı dahil)
382
+ # taranır — sentez anında yalnızca kod taranıyordu. Kabul kapısı daha
383
+ # sıkı olmak güvenli yöndür; başlık metnine denk gelen nadir yanlış
384
+ # pozitif dosyayı makinede yeniden kayıtla çözülür.
385
+ # Emit doktrini (guard.rb run_synthesis_gate hükmü): güvenlik kararının
386
+ # tek kullanıcı-yüzü izi $VERBOSE=nil altında da yaşamalıdır — $stderr.puts.
387
+ def screen_bootstrap_content(content, basename)
388
+ bootstrap_guard.verify_synthesis_safety!(content)
389
+ :ok
390
+ rescue Rubik::Security::BlockedWriteError => e
391
+ $stderr.puts "[SECURITY] Bootstrap overlay '#{basename}' refused by synthesis guard: #{e.message}" # rubocop:disable Style/StderrPuts
392
+ :rejected
393
+ rescue StandardError, ScriptError, SystemStackError => e
394
+ $stderr.puts "[SECURITY] Bootstrap overlay '#{basename}' could not be screened (#{e.class}: #{e.message}); refusing." # rubocop:disable Style/StderrPuts
395
+ :rejected
396
+ end
397
+
398
+ # Ortak kaçış kapısı predicate'i: iki güvenlik kapısının (verify_integrity,
399
+ # verified_dir_entries) kopya ENV kontrolüyle sürünerek ayrışmasını engeller.
400
+ def trust_mode?
401
+ ENV["RUBIK_TRUST_OVERLAYS"] == "1"
402
+ end
403
+
404
+ # Üç durumlu manifest yoklaması: :missing / :valid / :corrupt.
405
+ # load_manifest bilinçli olarak değiştirilmedi; kayıt yolu (record_hash) ve
406
+ # save_command/save_tool akışları bozuk manifestte {} davranışına bağlı kalıyor.
407
+ def manifest_state(dir)
408
+ path = manifest_path(dir)
409
+ return :missing unless File.exist?(path)
410
+
411
+ parsed = JSON.parse(File.read(path))
412
+ parsed.is_a?(Hash) ? :valid : :corrupt
413
+ rescue JSON::ParserError
414
+ :corrupt
415
+ end
416
+
417
+ # Dizin taraması başına bootstrap uygunluğu: manifest dosyası hiç yokken sentinel
418
+ # başlıklı dosyalar bir kez kabul edilir; manifest varken kayıtsız dosyalar her
419
+ # koşulda reddedilir. Bozuk manifest, o taramada dizini tamamen dışlar.
420
+ def verified_dir_entries(dir, scope)
421
+ files = Dir.glob(File.join(dir, "*.rb"))
422
+
423
+ # Kaçış kapısı: trust modu manifest durumundan bağımsız olarak tüm dosyaları güvenilir sayar.
424
+ return files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: :trusted } } if trust_mode?
425
+
426
+ case manifest_state(dir)
427
+ when :corrupt
428
+ warn "[SECURITY] Corrupt manifest in #{dir}; skipping #{files.size} overlay(s)."
429
+ []
430
+ when :missing
431
+ if bootstrap_opt_in?
432
+ bootstrapped = 0
433
+ entries = files.map do |f|
434
+ integrity = verify_integrity(f, dir, bootstrap_eligible: true)
435
+ bootstrapped += 1 if integrity == :bootstrapped
436
+ { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: integrity }
437
+ end
438
+ if bootstrapped.positive?
439
+ warn "[BOOTSTRAP] First scan of #{dir}: recorded #{bootstrapped} overlay(s) into .manifest.json; " \
440
+ "unknown files will be rejected from now on."
441
+ end
442
+ entries
443
+ else
444
+ # Opt-in yokken ilk tarama bile bootstrap yapmaz (yabancı depo klonu senaryosu);
445
+ # sentinel dosyalar görünür tek satırlık bildirimle atlanır.
446
+ gated = files.count { |f| sentinel_file?(f) }
447
+ if gated.positive?
448
+ warn "[BOOTSTRAP] #{gated} unrecorded sentinel overlay(s) in #{dir} were skipped; " \
449
+ "first-time acceptance requires RUBIK_BOOTSTRAP_OVERLAYS=1."
450
+ end
451
+ files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: verify_integrity(f, dir) } }
452
+ end
453
+ else
454
+ files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: verify_integrity(f, dir) } }
455
+ end
456
+ end
457
+
458
+ # İlk-bootstrap açık rızası: manifest'siz dizindeki sentinel dosyaların ilk taramada
459
+ # kabul edilmesi ancak bu env ile mümkündür (kayıt kalıcıdır; sonraki açılışlar env'siz çalışır).
460
+ def bootstrap_opt_in?
461
+ ENV["RUBIK_BOOTSTRAP_OVERLAYS"] == "1"
462
+ end
463
+
464
+ def sentinel_content?(content)
465
+ content.include?(COMMAND_SENTINEL) || content.include?(TOOL_SENTINEL)
466
+ end
467
+
468
+ def sentinel_file?(path)
469
+ File.file?(path) && sentinel_content?(File.binread(path))
470
+ rescue StandardError => e
471
+ # Audit 1: unreadable => fail-closed (not a sentinel), but traceable.
472
+ warn "[WARN] [OVERLAY] sentinel check failed for #{path}: #{e.class}: #{e.message}" if ENV["RUBIK_DEBUG"]
473
+ false
474
+ end
475
+
476
+ def clean_eval_binding
477
+ Object.new.instance_eval { binding }
478
+ end
479
+ end
480
+ end
481
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Runtime
5
+ # Declarative vocabulary entry for one composed service (architecture-
6
+ # review Candidate 05). A ServiceDefinition is pure data: it names a
7
+ # service, binds it to the coeffect key it is published under, and flags
8
+ # how the composition root should carry it (Parts member, shell ivar,
9
+ # shell reader, wire-time provide). It never builds anything and never
10
+ # touches a context.
11
+ #
12
+ # owns : the field semantics of a service declaration
13
+ # leaves : entry ORDER and the entries themselves (REPL::ServiceCatalog
14
+ # — the composition root's table); key STRINGS and legacy
15
+ # aliases (Rubik::Coeffects SSOT); how tool-layer defaults
16
+ # resolve (Tools::Services)
17
+ #
18
+ # Kinship: ComponentFiber declares `inject:` coeffect contracts for
19
+ # runtime consumers; ServiceDefinition declares the provider-side mirror
20
+ # for the composition root. TypeDefinition
21
+ # (orchestrator/subagent/type_registry.rb) is the structural precedent.
22
+ #
23
+ # Builders receive the catalog Scope (root_dir, home_dir, and hash access
24
+ # to already-constructed entries: scope[:guard]). Referencing an entry
25
+ # ordered later raises immediately — the construction-order invariant
26
+ # (guard before its consumers; runtime_manager before task_manager,
27
+ # skills and mcp; buffer before the subagent supervisor) is structural,
28
+ # not editorial.
29
+ ServiceDefinition = Struct.new(
30
+ :name, # Symbol — Parts member, Shell ivar, build_default override slot
31
+ :provide, # coeffect key published at wire time (Coeffects::* constant where
32
+ # one exists), nil when never provided (cache_store, recorder, ...)
33
+ :provide_from, # :ivar (default) | :parts | :self — where wire reads the value
34
+ :builder, # ->(scope) { instance }; nil only for the shell pseudo-entry
35
+ :input, # true for config-derived wiring values (extra_roots, ...): Parts
36
+ # member only — no ivar, no reader, no provide
37
+ :parts_member, # false ONLY for the shell pseudo-entry (provided, never built)
38
+ :optional, # true -> inject uses a respond_to? nil-guard (older parts fixtures)
39
+ :assign_ivar, # false -> Parts member + provide only, no Shell ivar (plugin_storage)
40
+ :reader, # false -> no derived Shell attr_reader (plugin_manager today)
41
+ :after_build, # ->(scope) { } runs right after this entry's construction
42
+ :after_inject, # ->(values, parts, injected_buffer) { } runs after ALL ivars are set
43
+ keyword_init: true
44
+ ) do
45
+ def initialize(**)
46
+ super(parts_member: true, provide_from: :ivar, **)
47
+ end
48
+
49
+ def provided? = !provide.nil?
50
+ def injectable? = !input && assign_ivar != false && parts_member != false
51
+ def expose_reader? = injectable? && reader != false
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Runtime
5
+ # Shared spine of the service register-shims (Runtime::ASTService,
6
+ # Runtime::IndexService, Runtime::LoopService, UI::RenderService —
7
+ # architecture-review Candidate 05): the extraction -> body -> reconcile
8
+ # sequence each shim carried as a private, slightly divergent copy.
9
+ #
10
+ # owns : carrier->context extraction (a Manager hands over its
11
+ # #context; anything else IS the context; nil stays nil), the
12
+ # trailing #reconcile_all trigger, nil-carrier tolerance
13
+ # leaves : the provided value and the local inverses (the yield body —
14
+ # each shim keeps its policy local: ASTService's
15
+ # error-suppressed cache save, RenderService's
16
+ # first-registration spinner latch), and fiber-shaped
17
+ # registration (LoopService passes reconcile: false because
18
+ # Manager#register_fiber reconciles itself)
19
+ #
20
+ # The yield receives the extracted ctx and its return value becomes the
21
+ # spine's return value, so shims keep their exact signatures and returns.
22
+ module ServiceRegistration
23
+ module_function
24
+
25
+ def call(carrier, reconcile: true)
26
+ ctx = carrier.respond_to?(:context) ? carrier.context : carrier
27
+ result = yield ctx
28
+ carrier.reconcile_all if reconcile && ctx && carrier.respond_to?(:reconcile_all)
29
+ result
30
+ end
31
+ end
32
+ end
33
+ end