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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+ require_relative "../output"
5
+ require_relative "../../coeffects"
6
+
7
+ module Rubik
8
+ module Tools
9
+ # Tool for controlling long-running asynchronous background processes
10
+ # (servers, test runners, compilers, watch modes) (Plan 93).
11
+ class ManageTaskTool < BaseTool
12
+ REQUIRES = [Rubik::Coeffects::TASK_MANAGER].freeze
13
+
14
+ attr_accessor :task_manager, :runtime_context
15
+
16
+ DECLARATION = Declaration.new(
17
+ name: "manage_task", aliases: ["task", "tasks", "background_task", "bg_task"],
18
+ description: "Manage asynchronous background processes (servers, builders, long test runs). " \
19
+ "Actions: 'start', 'list', 'status', 'logs', 'send_input', 'kill'.",
20
+ risk: :execute, archetypes: [:coder], maestro_blocked: true, # gate scopes to action 'start'; orchestration actions stay direct
21
+ params_schema: {
22
+ type: "object",
23
+ properties: {
24
+ action: {
25
+ type: "string",
26
+ enum: ["start", "list", "status", "logs", "send_input", "kill"],
27
+ description: "Action to perform: 'start', 'list', 'status', 'logs', 'send_input', or 'kill'"
28
+ },
29
+ task_id: { type: "string", description: "The task ID (e.g. 'task-1'). Required for status, logs, send_input, kill." },
30
+ command: { type: "string", description: "The shell command to run. Required for 'start'." },
31
+ input: { type: "string", description: "Input text to send to stdin. Required for 'send_input'." },
32
+ tail: { type: "integer", description: "Number of lines to read for 'logs' (default: 50)." }
33
+ },
34
+ required: ["action"]
35
+ }
36
+ )
37
+ def initialize(task_manager: nil, guard: nil, runtime_context: nil)
38
+ super(DECLARATION)
39
+ @task_manager = task_manager
40
+ @guard = guard
41
+ @runtime_context = runtime_context
42
+ end
43
+
44
+ def execute(args = {})
45
+ action = (args["action"] || args[:action]).to_s.downcase.strip
46
+ task_mgr = resolve_task_manager
47
+ return ToolOutput.error("TaskManager is not available in the runtime context.") unless task_mgr
48
+
49
+ case action
50
+ when "start"
51
+ handle_start(task_mgr, args)
52
+ when "list"
53
+ handle_list(task_mgr)
54
+ when "status"
55
+ handle_status(task_mgr, args)
56
+ when "logs"
57
+ handle_logs(task_mgr, args)
58
+ when "send_input"
59
+ handle_send_input(task_mgr, args)
60
+ when "kill"
61
+ handle_kill(task_mgr, args)
62
+ else
63
+ ToolOutput.error("Unknown action '#{action}'. Allowed actions: start, list, status, logs, send_input, kill.")
64
+ end
65
+ rescue Rubik::Security::BlockedWriteError => e
66
+ require_relative "exec" unless defined?(Rubik::Tools::RunCommandTool)
67
+ ToolOutput.error(Rubik::Tools::RunCommandTool.blocked_guidance(e))
68
+ rescue StandardError => e
69
+ ToolOutput.error("manage_task error (#{action}): #{e.message}")
70
+ end
71
+
72
+ private
73
+
74
+ def resolve_task_manager
75
+ @task_manager || (@runtime_context.respond_to?(:get) ? Rubik::Coeffects.get(@runtime_context, Rubik::Coeffects::TASK_MANAGER) : nil)
76
+ end
77
+
78
+ def extract_task_id(args)
79
+ (args["task_id"] || args[:task_id] || args["taskId"] || args[:taskId]).to_s.strip
80
+ end
81
+
82
+ def extract_command(args)
83
+ (args["command"] || args[:command] || args["cmd"] || args[:cmd]).to_s.strip
84
+ end
85
+
86
+ def handle_start(task_mgr, args)
87
+ cmd = extract_command(args)
88
+ return ToolOutput.error("Missing required parameter 'command' for action 'start'.") if cmd.empty?
89
+
90
+ task = task_mgr.start(cmd, ctx: @runtime_context)
91
+ "Task started: #{task.id} (PID #{task.pid})\nCommand: #{task.command}\nStatus: #{task.status.to_s.upcase}"
92
+ end
93
+
94
+ def handle_list(task_mgr)
95
+ tasks = task_mgr.list.sort_by { |t| t.started_at || Time.at(0) }
96
+ return "No background tasks currently running or tracked." if tasks.empty?
97
+
98
+ lines = ["=== Background Tasks (#{tasks.size}) ==="]
99
+ tasks.each do |t|
100
+ dur = t.running? ? "up #{t.uptime.round(1)}s" : "exit #{t.exit_code}"
101
+ lines << " • [#{t.id}] #{t.status.to_s.upcase} (PID #{t.pid}, #{dur}): #{t.command}"
102
+ end
103
+ lines.join("\n")
104
+ end
105
+
106
+ def handle_status(task_mgr, args)
107
+ task_id = extract_task_id(args)
108
+ return ToolOutput.error("Missing required parameter 'task_id' for action 'status'.") if task_id.empty?
109
+
110
+ task = task_mgr.get(task_id)
111
+ return ToolOutput.error("Task '#{task_id}' not found.") unless task
112
+
113
+ <<~STATUS.chomp
114
+ Task: #{task.id}
115
+ Status: #{task.status.to_s.upcase}
116
+ PID: #{task.pid}
117
+ Uptime: #{task.uptime.round(1)}s
118
+ Exit Code: #{task.exit_code.inspect}
119
+ Command: #{task.command}
120
+ STATUS
121
+ end
122
+
123
+ def handle_logs(task_mgr, args)
124
+ task_id = extract_task_id(args)
125
+ return ToolOutput.error("Missing required parameter 'task_id' for action 'logs'.") if task_id.empty?
126
+
127
+ tail = (args["tail"] || args[:tail] || 50).to_i
128
+ task = task_mgr.get(task_id)
129
+ return ToolOutput.error("Task '#{task_id}' not found.") unless task
130
+
131
+ lines = task.ring_buffer.tail(tail)
132
+ return "No logs recorded for task '#{task_id}' yet." if lines.empty?
133
+
134
+ "=== Logs for #{task.id} (last #{lines.size} lines) ===\n#{lines.join("\n")}"
135
+ end
136
+
137
+ def handle_send_input(task_mgr, args)
138
+ task_id = extract_task_id(args)
139
+ return ToolOutput.error("Missing required parameter 'task_id' for action 'send_input'.") if task_id.empty?
140
+
141
+ input = args["input"] || args[:input]
142
+ return ToolOutput.error("Missing required parameter 'input' for action 'send_input'.") if input.nil?
143
+
144
+ ok = task_mgr.send_input(task_id, input)
145
+ if ok
146
+ "Input sent to task '#{task_id}'."
147
+ else
148
+ ToolOutput.error("Failed to send input to task '#{task_id}'. Task may not be running or stdin is closed.")
149
+ end
150
+ end
151
+
152
+ def handle_kill(task_mgr, args)
153
+ task_id = extract_task_id(args)
154
+ return ToolOutput.error("Missing required parameter 'task_id' for action 'kill'.") if task_id.empty?
155
+
156
+ task = task_mgr.get(task_id)
157
+ return ToolOutput.error("Task '#{task_id}' not found.") unless task
158
+
159
+ if task.status == :running
160
+ task_mgr.kill(task_id)
161
+ "Task '#{task_id}' killed. (PID #{task.pid})"
162
+ else
163
+ "Task '#{task_id}' is already #{task.status.to_s.upcase} (exit code: #{task.exit_code.inspect})."
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # UnsynthesizeTool: the delete twin of the synthesize pair (Plan 54 D-3 kept
4
+ # synthesis.rb as the write side; this extraction keeps every builtin domain
5
+ # file under the 350-line architectural guard while the builtin count grew).
6
+ # Deletes a previously synthesized command or tool from both the live registry
7
+ # and disk (project or global scope), restoring the previous value on context
8
+ # dispose (revertible inverse, AGENTS.md §1.A).
9
+
10
+ require_relative "../base"
11
+ require_relative "../output"
12
+ require_relative "../../runtime"
13
+
14
+ module Rubik
15
+ module Tools
16
+ class UnsynthesizeTool < BaseTool
17
+ DECLARATION = Declaration.new(
18
+ name: "unsynthesize", aliases: ["delete_tool", "delete_command", "remove_tool", "remove_command"],
19
+ description: "Delete a previously synthesized command or tool from both the live registry and disk (project or global scope)",
20
+ risk: :write, maestro_blocked: false, # self-evolution plane (TOOLS.md maestro policy)
21
+ params_schema: {
22
+ type: "object",
23
+ properties: {
24
+ name: { type: "string", description: "Name of the command (without '!') or tool to delete" },
25
+ kind: { type: "string", enum: ["command", "tool"], description: "Whether to delete a command or a tool" },
26
+ scope: { type: "string", enum: ["session", "project", "global"],
27
+ description: "Storage scope: session (live only), project (.rubik/...), global (~/.rubik/...)" }
28
+ },
29
+ required: ["name", "kind"]
30
+ }
31
+ )
32
+ def initialize(shell = nil, tool_registry = nil, overlay_storage = nil, legacy_runtime_manager = nil, runtime_manager: nil)
33
+ super(DECLARATION)
34
+ @shell = shell
35
+ @tool_registry = tool_registry
36
+ @overlay_storage = overlay_storage || Rubik::Runtime::OverlayStorage.new
37
+ @runtime_manager = runtime_manager || legacy_runtime_manager
38
+ end
39
+
40
+ def execute(args = {})
41
+ name = (args["name"] || args[:name]).to_s.strip.delete_prefix("!")
42
+ kind = (args["kind"] || args[:kind]).to_s.downcase.to_sym
43
+ scope = (args["scope"] || args[:scope] || "session").to_s.downcase.to_sym
44
+
45
+ return ToolOutput.error("Name cannot be empty") if name.empty?
46
+ return ToolOutput.error("kind must be 'command' or 'tool'") unless [:command, :tool].include?(kind)
47
+
48
+ kind == :command ? delete_command(name, scope) : delete_tool(name, scope)
49
+ end
50
+
51
+ private
52
+
53
+ def delete_command(name, scope)
54
+ clean = name.to_s.downcase.delete_prefix("!")
55
+ previous_meta = @shell&.commands&.[](clean)
56
+
57
+ # Fail-closed: never unsynthesize a built-in command (custom: false).
58
+ # Symmetric with the synthesize twin's refusal to shadow built-ins.
59
+ return ToolOutput.error("Cannot delete built-in command '!#{clean}'") if previous_meta && previous_meta[:custom] == false
60
+
61
+ @shell&.unregister_command(clean)
62
+
63
+ if [:project, :global].include?(scope)
64
+ res = @overlay_storage.delete_command(name, scope: scope)
65
+ return ToolOutput.error("Failed to delete command from disk: #{res[:error]}") unless res[:ok]
66
+ end
67
+
68
+ # Revertible inverse (AGENTS.md §1.A): restore the previous command on
69
+ # dispose if one existed before deletion.
70
+ if @runtime_manager&.context.is_a?(Rubik::Runtime::Context)
71
+ @runtime_manager.context.on_dispose do
72
+ if previous_meta
73
+ @shell.register_command(clean, description: previous_meta[:description],
74
+ custom: previous_meta[:custom], audience: previous_meta[:audience],
75
+ &previous_meta[:handler])
76
+ end
77
+ end
78
+ end
79
+
80
+ "Successfully deleted command '!#{clean}' [scope: #{scope}]."
81
+ end
82
+
83
+ def delete_tool(name, scope)
84
+ clean = name.to_s.downcase.gsub(/[^a-z0-9_]/, "_")
85
+
86
+ # Fail-closed: never unsynthesize a built-in tool. Symmetric with the
87
+ # synthesize twin's refusal to shadow built-ins.
88
+ return ToolOutput.error("Cannot delete built-in tool '#{clean}'") if @tool_registry&.origin(clean) == :builtin
89
+
90
+ previous_tool = @tool_registry&.get(clean)
91
+ previous_origin = previous_tool ? @tool_registry.origin(clean) : nil
92
+
93
+ @tool_registry&.unregister(clean)
94
+
95
+ if [:project, :global].include?(scope)
96
+ res = @overlay_storage.delete_tool(name, scope: scope)
97
+ return ToolOutput.error("Failed to delete tool from disk: #{res[:error]}") unless res[:ok]
98
+ end
99
+
100
+ # Revertible inverse (AGENTS.md §1.A): restore the previous tool on dispose.
101
+ if @runtime_manager&.context.is_a?(Rubik::Runtime::Context)
102
+ @runtime_manager.context.on_dispose do
103
+ @tool_registry.swap(clean, previous_tool, origin: previous_origin || :overlay) if previous_tool
104
+ end
105
+ end
106
+
107
+ "Successfully deleted tool '#{clean}' [scope: #{scope}]."
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+ require_relative "../output"
5
+ require_relative "../../coeffects"
6
+ require_relative "../../storage/vakanuvis"
7
+
8
+ module Rubik
9
+ module Tools
10
+ # Talep düzlemi (plan 2026-10-04 §4.5, K7): maestro'nun kronikçi
11
+ # masası. Dört eylemin hepsi Vakanüvis::Harness'e delege olur — recall
12
+ # tamamen deterministiktir (LLM'siz), record/forget Düzlem 1 yazma
13
+ # disipliniyle (ref minting, upsert, supersede) yazar, digest asenkron
14
+ # kuyruklanır ve deterministik makbuz döner (maestro turn'ü asla LLM
15
+ # bekletilmez). Görünürlük: yalnız kök/maestro düzlemi (archetypes
16
+ # boş; ScopedRegistry ROOT_ONLY_TOOLS subagent kayıtlarına — self
17
+ # dahil — kopyalamaz).
18
+ class VakanuvisTool < BaseTool
19
+ # Registry#schemas, REQUIRES memnuniyetsizliğinde aracı gizler:
20
+ # kronikçi kapalıysa (subagents.vakanuvis.enabled=false ya da store
21
+ # yok) bu masa model yüzünde görünmez.
22
+ REQUIRES = [Rubik::Coeffects::VAKANUIS].freeze
23
+
24
+ DECLARATION = Declaration.new(
25
+ name: "vakanuvis", aliases: ["chronicler"],
26
+ description: "Ask the background chronicler (compiled knowledge in rubik://storage): " \
27
+ "'recall' (deterministic digest over memory+wiki with source links — ask at task start " \
28
+ "and after compaction), 'record' (persist durable knowledge as a memory card; the ONE " \
29
+ "path for memory writes), 'forget' (supersede/purge a mem.* card or a note), 'digest' (queue an " \
30
+ "async wiki compilation; returns a deterministic receipt, never blocks on the LLM), " \
31
+ "'topics' (list recorded memory topics — orient before the first query), 'digest_status' " \
32
+ "(is my queued digest pending, in flight, or done?). Notes (annal.*) are the chronicler's " \
33
+ "evidence layer; memory cards are the durable conclusions you want recalled by default. " \
34
+ "Hygiene: same topic, same session = one note — update it (or retire it with forget; " \
35
+ "superseded rows keep a trace).",
36
+ risk: :write, archetypes: [], write_grant: false,
37
+ maestro_blocked: false, # storage emsali: kronikçi düzlemi kökte doğrudan
38
+ params_schema: {
39
+ type: "object",
40
+ properties: {
41
+ action: {
42
+ type: "string", enum: ["recall", "record", "forget", "digest", "topics", "digest_status"],
43
+ description: "Operation (default: 'recall')."
44
+ },
45
+ query: { type: "string",
46
+ description: "recall/digest: knowledge query. Recall is keyword-AND — every space-separated " \
47
+ "term must literally appear; retry with other-language synonyms when empty." },
48
+ types: {
49
+ type: "array", items: { type: "string", enum: ["memory", "wiki", "note"] },
50
+ description: "recall: entity scopes to search (default: memory + wiki)."
51
+ },
52
+ topic: { type: "string", description: "record: card topic — mints ref mem.<topic-slug> (upsert by topic)." },
53
+ body: {
54
+ type: "string",
55
+ description: "record: card body (markdown; end it with a Keywords: line of 5-10 TR+EN synonyms so " \
56
+ "keyword-AND recall can find the card — a missing Keywords: line is auto-extracted " \
57
+ "from the body at write time)."
58
+ },
59
+ tags: {
60
+ type: "array", items: { type: "string" },
61
+ description: "record: optional recall tags (searched like body text; merged with the chronicler's vocabulary)."
62
+ },
63
+ links: {
64
+ type: "object", additionalProperties: { type: "string" },
65
+ description: "record: optional cross-references (e.g. {\"goal\": \"goal.1.<hash16>\"})."
66
+ },
67
+ ref: { type: "string", description: "forget: mem.* card or note ref (annal.*) — or id — to retire." }
68
+ }
69
+ }
70
+ )
71
+
72
+ def initialize(harness = nil, runtime_context: nil)
73
+ super(DECLARATION)
74
+ @harness = harness
75
+ @runtime_context = runtime_context
76
+ end
77
+
78
+ def execute(args = {})
79
+ action = (args["action"] || args[:action] || "recall").to_s
80
+ harness = Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::VAKANUIS, @harness)
81
+ unless harness
82
+ return ToolOutput.error("Vakanüvis chronicler is not available: disabled by config " \
83
+ "(subagents.vakanuvis.enabled=false) or no store wired.")
84
+ end
85
+
86
+ begin
87
+ case action
88
+ when "recall" then harness.recall(arg(args, "query"), types: arg(args, "types"))
89
+ when "record"
90
+ receipt = harness.record(topic: arg(args, "topic"), body: arg(args, "body"),
91
+ links: arg(args, "links") || {}, tags: tags_arg(args))
92
+ record_line(harness, receipt)
93
+ when "forget"
94
+ receipt = harness.forget(ref: arg(args, "ref"))
95
+ forget_line(harness, receipt)
96
+ when "digest" then harness.digest(arg(args, "query"))
97
+ when "topics" then harness.topics
98
+ when "digest_status" then harness.digest_status
99
+ else ToolOutput.error("Unknown vakanuvis action '#{action}' " \
100
+ "(valid: recall, record, forget, digest, topics, digest_status).")
101
+ end
102
+ rescue ArgumentError => e
103
+ ToolOutput.error("vakanuvis #{action}: #{e.message}")
104
+ rescue StandardError => e
105
+ Rubik::Storage::Vakanuvis.log_via(harness.dmesg, :warn, "vakanuvis #{action} failed: #{e.class}: #{e.message}")
106
+ ToolOutput.error("vakanuvis #{action} failed: #{e.class}: #{e.message} — the store may be " \
107
+ "unavailable; retry the call or inspect dmesg(subsystem: 'storage').")
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def record_line(harness, receipt)
114
+ verb = receipt["skipped"] ? "unchanged (version #{receipt['version']} kept)" : "stored (version #{receipt['version']})"
115
+ uri = harness.store.uri_for("memory", receipt["id"])
116
+ line = "Recorded '#{receipt['ref']}': #{verb}.\nURI: #{uri}\nRecall it later: vakanuvis(action: 'recall', query: '...')"
117
+ if receipt["keywords_extracted"]
118
+ line += "\nKeywords auto-extracted from the body (no Keywords: line was present)."
119
+ elsif receipt["keywords_state"] == "none"
120
+ line += "\nWarning: no Keywords: line and none could be extracted — recall may miss this card; " \
121
+ "append a Keywords: line manually."
122
+ end
123
+ line
124
+ end
125
+
126
+ # Etiket paramı anahtar-varlığıyla okunur (tags: false sessizce []
127
+ # düşmesin — review L3) ve dizi değilse storage aracıyla aynı virgül
128
+ # bölmesine düşer (review CR#4: "a,b" tek ölü etiket olmasın).
129
+ # Üst-seviye boolean/sayı ret edilir.
130
+ def tags_arg(args)
131
+ raw = if args.key?("tags") || args.key?(:tags)
132
+ args["tags"] || args[:tags]
133
+ else
134
+ []
135
+ end
136
+ unless raw.is_a?(Array) || raw.is_a?(String)
137
+ raise ArgumentError,
138
+ "record tags must be an array of strings or a comma-separated string — " \
139
+ "got #{raw.class} (#{raw.inspect})."
140
+ end
141
+
142
+ raw.is_a?(Array) ? raw : raw.to_s.split(",").map(&:strip)
143
+ end
144
+
145
+ def forget_line(harness, receipt)
146
+ return "Nothing to forget: no memory card or note resolves from '#{receipt['ref']}'." unless receipt["forgot"]
147
+
148
+ if receipt["mode"] == "purge"
149
+ "Forgot '#{receipt['ref']}': row purged (hard delete)."
150
+ else
151
+ uri = harness.store.uri_for(receipt["type"] || "memory", receipt["id"])
152
+ "Forgot '#{receipt['ref']}': superseded (status + trace link kept).\nURI: #{uri}"
153
+ end
154
+ end
155
+
156
+ def arg(args, key, default = nil)
157
+ args[key] || args[key.to_sym] || default
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Domain-grouped home of the 34 built-in agent tools (Plan 54 D-3, Plan 92, Plan 93,
4
+ # storage-show-surface 2026-09-30, vakanuvis-chronicler 2026-10-04, context-pruner
5
+ # 2026-10-09, following the repl/commands/*.rb loader pattern). This file is only the
6
+ # loader; every tool class lives in the file matching its domain (subagent_lifecycle
7
+ # holds the action modules consumed by ManageSubagentsTool, not a tool class).
8
+
9
+ require_relative "uri_resolver"
10
+ require_relative "builtins/files"
11
+ require_relative "builtins/grep"
12
+ require_relative "builtins/symbols"
13
+ require_relative "builtins/lsp"
14
+ require_relative "builtins/skill"
15
+ require_relative "builtins/exec"
16
+ require_relative "builtins/task"
17
+ require_relative "builtins/synthesis"
18
+ require_relative "builtins/unsynthesize"
19
+ require_relative "builtins/runtime"
20
+ require_relative "builtins/lifecycle"
21
+ require_relative "builtins/interactive"
22
+ require_relative "builtins/subagent"
23
+ require_relative "builtins/send_message"
24
+ require_relative "builtins/subagent_lifecycle"
25
+ require_relative "builtins/dmesg"
26
+ require_relative "builtins/storage"
27
+ require_relative "builtins/vakanuvis"
28
+ require_relative "builtins/pruner"
29
+ require_relative "builtins/goal_ledger"
30
+ require_relative "builtins/show_file"
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "declaration"
4
+ require_relative "builtins"
5
+
6
+ module Rubik
7
+ module Tools
8
+ # Frozen load-time projection over the builtin DECLARATIONs: the single
9
+ # place archetype allowlists, the spawn set, the custom-type write gate,
10
+ # the maestro gate and doc tables derive from (architecture review
11
+ # 2026-10-01, candidate 1). Membership pins "a class that exists is
12
+ # registered, and vice versa"; ORDER mirrors Registry.build_default
13
+ # registration order exactly (both directions invariant-tested).
14
+ #
15
+ # This is a load-time constant module — the same class of thing as
16
+ # Rubik::Coeffects, not a registry. Dynamically registered tools (MCP,
17
+ # plugins, synthesis) cannot join it; they use Registry#register's
18
+ # per-instance path unchanged.
19
+ module Catalog
20
+ BUILTINS = [
21
+ ReadFileTool, ReplaceFileContentTool, WriteFileTool, ListFilesTool, GrepSearchTool,
22
+ SearchSymbolsTool, GetOutlineTool, FindReferencesTool, GetSymbolTool,
23
+ LSPDiagnosticsTool, LSPHoverTool, LSPDefinitionTool, RunTestsTool, RunCommandTool,
24
+ ManageTaskTool, ExecReplCommandTool, SynthesizeCommandTool, SynthesizeToolTool,
25
+ UnsynthesizeTool, InspectRuntimeTool, ProvideServiceTool, RepairRuntimeTool,
26
+ ApplyBufferTool, AskQuestionTool, InvokeSubagentTool, ManageSubagentsTool,
27
+ SendMessageTool, DefineSubagentTool, DmesgTool, StorageTool, VakanuvisTool, PruneContextTool,
28
+ UpdateGoalLedgerTool, ShowFileTool, FinishTool
29
+ ].freeze
30
+
31
+ # Tools registered outside build_default (dynamically, each with its own
32
+ # ctx inverse) that still carry a canonical DECLARATION.
33
+ DYNAMIC = [SkillTool].freeze
34
+
35
+ # PERF: Pre-compute and freeze load-time projections to avoid re-filtering,
36
+ # re-sorting, array allocations, and linear scans on hot query paths.
37
+ DECLARATIONS = BUILTINS.filter_map { |k| k.const_defined?(:DECLARATION, false) ? k::DECLARATION : nil }.freeze
38
+ DYNAMIC_DECLARATIONS = DYNAMIC.filter_map { |k| k.const_defined?(:DECLARATION, false) ? k::DECLARATION : nil }.freeze
39
+ NAMES = DECLARATIONS.map(&:name).freeze
40
+ DECLARATIONS_BY_NAME = DECLARATIONS.to_h { |d| [d.name, d] }.freeze
41
+ RESEARCH_TOOLS = DECLARATIONS.select { |d| d.archetypes.include?(:research) }.map(&:name).sort.freeze
42
+ CODER_TOOLS = DECLARATIONS.select { |d| d.archetypes.include?(:coder) }.map(&:name).sort.freeze
43
+ SPAWN_TOOLS = DECLARATIONS.select(&:spawn?).map(&:name).sort.freeze
44
+ WRITE_GRANT_TOOLS = DECLARATIONS.select(&:write_grant?).map(&:name).sort.freeze
45
+ MAESTRO_BLOCKED_TOOLS = DECLARATIONS.select(&:maestro_blocked?).map(&:name).sort.freeze
46
+
47
+ module_function
48
+
49
+ def declarations = DECLARATIONS
50
+
51
+ def dynamic_declarations = DYNAMIC_DECLARATIONS
52
+
53
+ def names = NAMES
54
+
55
+ def [](name) = DECLARATIONS_BY_NAME[name.to_s]
56
+
57
+ def research_tools = RESEARCH_TOOLS
58
+
59
+ def coder_tools = CODER_TOOLS
60
+
61
+ def spawn_tools = SPAWN_TOOLS
62
+
63
+ def write_grant_tools = WRITE_GRANT_TOOLS
64
+
65
+ def maestro_blocked_tools = MAESTRO_BLOCKED_TOOLS
66
+
67
+ def maestro_blocked?(name) = MAESTRO_BLOCKED_TOOLS.include?(name.to_s)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Tools
5
+ # One tool, one declaration (architecture review 2026-10-01, candidate 1):
6
+ # the COMPLETE interface + policy of a tool as a single frozen literal in
7
+ # the tool's domain file. Registry order, archetype allowlists, the
8
+ # maestro gate, the custom-type write gate, TOOLS.md and prompt teachings
9
+ # all derive from this object via Tools::Catalog.
10
+ #
11
+ # Policy fields are orchestrator-plane facts. They NEVER leak into the
12
+ # LLM schema surface: BaseTool#to_h keeps emitting exactly
13
+ # name/description/risk/parameters (invariant-pinned by
14
+ # test_tool_declaration_catalog).
15
+ Declaration = Struct.new(
16
+ :name, :aliases, :description, :risk, :params_schema,
17
+ :allow_additional_properties,
18
+ :archetypes, # Array<Symbol> subset of [:research, :coder] — subagent allowlists
19
+ :subagent_spawn, # bool — member of the spawn set (self-archetype recursion guard)
20
+ :write_grant, # bool — denied to custom types lacking enable_write_tools
21
+ :maestro_blocked, # true | false | nil — gate class; MUST be explicit for :write/:execute
22
+ keyword_init: true
23
+ ) do
24
+ def initialize(name:, description:, risk: :read, params_schema: {}, aliases: [],
25
+ allow_additional_properties: true, archetypes: [], subagent_spawn: false,
26
+ write_grant: false, maestro_blocked: nil)
27
+ clean_name = name.to_s
28
+ # Leading digits are tolerated (not required) for legacy dynamic
29
+ # tools: synthesis/overlay normalize to lowercase snake but do not
30
+ # strip a leading digit, and LLM provider tool-name rules allow
31
+ # digits — rejecting them would drop tools that loaded before the
32
+ # Declaration seam existed.
33
+ unless clean_name.match?(/\A[a-z0-9_]+\z/)
34
+ raise ArgumentError,
35
+ "tool name '#{clean_name}' must be lowercase snake_case (a-z, 0-9, _) — canonical names are the registry keys"
36
+ end
37
+
38
+ risk_key = risk.to_s.to_sym
39
+ unless [:read, :write, :execute].include?(risk_key)
40
+ raise ArgumentError,
41
+ "tool '#{clean_name}' risk must be one of read, write, execute (got #{risk.inspect})"
42
+ end
43
+
44
+ archetype_keys = Array(archetypes).map { |a| a.to_s.to_sym }
45
+ unknown = archetype_keys - [:research, :coder]
46
+ if unknown.any?
47
+ raise ArgumentError,
48
+ "tool '#{clean_name}' archetypes must be a subset of research, coder (got #{unknown.join(', ')}) — " \
49
+ "empty means orchestrator-plane only"
50
+ end
51
+
52
+ # Members are shared by every instance of the class (the DECLARATION
53
+ # constant): freeze aliases and the schema's outer hash so no caller
54
+ # can corrupt the projection source for the whole process.
55
+ # (to_h dups before injecting additionalProperties, so the frozen
56
+ # schema never blocks the LLM payload path.)
57
+ schema = params_schema.is_a?(Hash) ? params_schema.freeze : params_schema
58
+ super(
59
+ name: clean_name,
60
+ description: description.to_s,
61
+ risk: risk_key,
62
+ params_schema: schema,
63
+ aliases: Array(aliases).map(&:to_s).freeze,
64
+ allow_additional_properties: allow_additional_properties,
65
+ archetypes: archetype_keys.freeze,
66
+ subagent_spawn: subagent_spawn ? true : false,
67
+ write_grant: write_grant ? true : false,
68
+ maestro_blocked: if maestro_blocked.nil?
69
+ nil
70
+ else
71
+ (maestro_blocked ? true : false)
72
+ end
73
+ )
74
+ freeze
75
+ end
76
+
77
+ # Derived copy with overrides (e.g. invoke_subagent's supervisor-derived
78
+ # schema) — states a near-declaration instead of splatting to_h by hand.
79
+ def with(**overrides)
80
+ Declaration.new(**to_h, **overrides)
81
+ end
82
+
83
+ def maestro_blocked? = maestro_blocked == true
84
+
85
+ def spawn? = subagent_spawn == true
86
+
87
+ def write_grant? = write_grant == true
88
+ end
89
+ end
90
+ end