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,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../coeffects"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ # !prune — KÖK pencere budayıcısının kullanıcı tarafı (context-pruner
10
+ # planı 2026-10-09). !storage emsali: coeffect üzerinden çözer, kapalıyken
11
+ # dürüstçe kapalı der (knob operator-only: subagents.pruner.*).
12
+ # "now" zorunlu budama kuyruklar (deterministik makbuz; LLM asla
13
+ # bekletilmez — plan turn sınırında uygulanır), "status" durum raporu.
14
+ module Pruner
15
+ extend Base
16
+
17
+ USAGE_PRUNE = "Usage: !prune [now|status]"
18
+
19
+ def self.register(shell)
20
+ handler = lambda do |args, ctx|
21
+ harness = resolve_pruner(ctx)
22
+ unless harness
23
+ ctx.out.puts unbound_message(ctx)
24
+ next
25
+ end
26
+
27
+ sub = args.first.to_s.downcase
28
+ if ["status", ""].include?(sub)
29
+ ctx.out.puts harness.status_text
30
+ elsif ["now", "run", "force"].include?(sub)
31
+ ctx.out.puts harness.prune(reason: "user")
32
+ else
33
+ ctx.out.puts USAGE_PRUNE
34
+ end
35
+ end
36
+
37
+ shell.register_command(
38
+ "prune",
39
+ description: "Clean the ROOT conversation window via the context pruner (!prune status for details)",
40
+ usage: USAGE_PRUNE,
41
+ audience: :both,
42
+ &handler
43
+ )
44
+ end
45
+
46
+ def self.resolve_pruner(ctx)
47
+ rt = ctx.respond_to?(:runtime_manager) ? ctx.runtime_manager : nil
48
+ rt&.context ? Rubik::Coeffects.get(rt.context, Rubik::Coeffects::PRUNER) : nil
49
+ end
50
+
51
+ # Knob kapalı ile AÇIK-AMA-BAĞLANAMAMIŞ ayrımı (review 2026-10-09):
52
+ # ikincisi ayarı zaten açık olan operatörü sonsuz "enable et" döngüsüne
53
+ # sokardı — teşhis (boot warn + dmesg) adı adına söylenir.
54
+ def self.unbound_message(ctx)
55
+ knob = begin
56
+ ctx.config.fetch("subagents.pruner.enabled", true)
57
+ rescue StandardError
58
+ true
59
+ end
60
+ if knob == true
61
+ "Context pruner is ENABLED but did not bind (boot-order gap or missing supervisor — see the " \
62
+ "startup warning / dmesg). Fix the reported cause and restart the session."
63
+ else
64
+ "Context pruner is off (explicit opt-out). An operator can re-enable it with: " \
65
+ "!config set subagents.pruner.enabled true [--project|--global], then restart the session."
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../session"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ module Session
10
+ extend Base
11
+
12
+ # Whole-command synopsis: shared by the spine registration (usage:)
13
+ # and the unknown-subcommand fallback branch, so the two cannot drift.
14
+ USAGE_SESSION = "Usage: !session <list | resume [id] | save <name> | info | prune [days] [--apply] [--all]>"
15
+ PRUNE_DEFAULT_DAYS = 7
16
+
17
+ def self.register(shell)
18
+ shell.register_command("history", description: "List recent session history", audience: :both) do |args, ctx|
19
+ limit = (args.first || 20).to_i
20
+ hist = ctx.recorder.read_history(limit)
21
+ hist.each_with_index do |item, idx|
22
+ ctx.out.puts " #{idx + 1}: #{item['payload']}"
23
+ end
24
+ end
25
+
26
+ # Plan 91 — explicit multi-line paste mode: collects lines until a
27
+ # lone "." line, then dispatches the joined text as one prompt turn.
28
+ shell.register_command("paste", description: "Multi-line paste mode (finish with lone '.')") do |_args, ctx|
29
+ ctx.request_paste!
30
+ ctx.out.puts "[Rubik] Paste modu bir sonraki prompt'ta başlayacak."
31
+ end
32
+
33
+ shell.register_command("replay", description: "Replay session file") do |args, ctx|
34
+ path = args.first
35
+ replayer = ctx.session_replayer
36
+ if replayer.replay_file(path)
37
+ ctx.out.puts "Replay completed."
38
+ else
39
+ ctx.out.puts "Session file not found: #{path}"
40
+ end
41
+ end
42
+
43
+ shell.register_command("session", description: "Manage recorded sessions (list|resume|info|prune)",
44
+ usage: USAGE_SESSION) do |args, ctx|
45
+ sub = args.first&.downcase || "list"
46
+ root_d = ctx.buffer_manager&.root_dir || Dir.pwd
47
+ home_d = ctx.config&.home_dir || ENV.fetch("HOME", nil)
48
+
49
+ case sub
50
+ when "list"
51
+ sessions = Rubik::Session::Manager.list_sessions(root_d, home_d, config: ctx.config)
52
+ if sessions.empty?
53
+ ctx.out.puts "No recorded sessions found for this workspace."
54
+ else
55
+ ctx.out.puts "=== Recorded Sessions (#{sessions.size}) ==="
56
+ sessions.each_with_index do |s, idx|
57
+ mark = s[:path] == ctx.recorder.session_file ? " [CURRENT]" : ""
58
+ ctx.out.puts " #{idx + 1}. #{s[:id]} (#{s[:scope]}) - #{s[:event_count]} events - Last: \"#{s[:last_prompt]}\"#{mark}"
59
+ end
60
+ end
61
+ when "resume", "continue", "load"
62
+ target_id = args[1]
63
+ if target_id.nil?
64
+ picked = interactive_session_pick(ctx)
65
+ if picked == :picker_cancelled
66
+ ctx.out.puts "[Rubik::Session] Resume iptal edildi."
67
+ next
68
+ end
69
+ target_id = picked || :latest
70
+ end
71
+ res = ctx.resume_session(target_id)
72
+ ctx.out.puts "Resume failed: #{res[:error]}" unless res[:ok]
73
+ when "info"
74
+ ctx.out.puts "Active Session File: #{ctx.recorder.session_file}"
75
+ ctx.out.puts "Total Events in Session: #{ctx.recorder.events.size}"
76
+ when "prune", "clean", "cleanup"
77
+ run_prune(args.drop(1), ctx)
78
+ when "save"
79
+ target_name = args[1]
80
+ if target_name.nil? || target_name.strip.empty?
81
+ ctx.out.puts "Usage: !session save <name>"
82
+ else
83
+ safe_name = File.basename(target_name.strip.sub(/\.jsonl$/, ""))
84
+ if safe_name.empty? || safe_name.start_with?(".")
85
+ ctx.out.puts "Error: Invalid session name '#{target_name}'."
86
+ next
87
+ end
88
+
89
+ target_dir = File.join(root_d, ".rubik", "sessions")
90
+ FileUtils.mkdir_p(target_dir)
91
+ target_file = File.join(target_dir, "#{safe_name}.jsonl")
92
+
93
+ if ctx.recorder.session_file && File.exist?(ctx.recorder.session_file)
94
+ FileUtils.cp(ctx.recorder.session_file, target_file)
95
+ else
96
+ File.open(target_file, "w") do |f|
97
+ ctx.recorder.events.each { |ev| f.puts(JSON.dump(ev)) }
98
+ end
99
+ end
100
+ ctx.recorder.switch_session_file(target_file, cleanup_empty_current: false)
101
+ ctx.out.puts "[Rubik::Session] Session saved to #{target_file}"
102
+ end
103
+ else
104
+ ctx.out.puts USAGE_SESSION
105
+ end
106
+ end
107
+ end
108
+
109
+ # Interactive session picker (Antigravity /resume style): arrow-key
110
+ # selection over recorded sessions on capable TTYs. Returns the
111
+ # session id, :picker_cancelled on Ctrl+C (caller aborts the
112
+ # resume), or nil in pipes/tests so `resume` falls back to :latest.
113
+ class << self
114
+ # `!session prune [days] [--apply] [--all]` — dry-run by default:
115
+ # prints the blast radius of files that WOULD be deleted and only
116
+ # unlinks once the operator passes --apply. The active session file
117
+ # is always protected. `--all` widens scope to ~/.rubik/sessions.
118
+ def run_prune(rest, ctx)
119
+ apply = rest.any? { |a| ["--apply", "--yes", "-y", "apply", "delete"].include?(a.downcase) }
120
+ include_global = rest.any? { |a| ["--all", "-a", "all", "global"].include?(a.downcase) }
121
+ day_arg = rest.find { |a| a =~ /\A\d+(\.\d+)?\z/ }
122
+ days = day_arg ? day_arg.to_f : PRUNE_DEFAULT_DAYS
123
+ days = PRUNE_DEFAULT_DAYS unless days.positive?
124
+
125
+ root_d = ctx.buffer_manager&.root_dir || Dir.pwd
126
+ home_d = ctx.config&.home_dir || ENV.fetch("HOME", nil)
127
+ active = ctx.recorder&.session_file
128
+
129
+ report = Rubik::Session::Manager.prune_sessions(
130
+ days: days, root_dir: root_d, home_dir: home_d,
131
+ exclude_file: active, apply: apply, include_global: include_global, config: ctx.config
132
+ )
133
+
134
+ scope_label = include_global ? "project + global" : "project"
135
+ size_mb = report[:bytes] / 1_048_576.0
136
+
137
+ if report[:candidates].zero?
138
+ ctx.out.puts "[Rubik::Session] No #{scope_label} sessions older than #{format_days(days)} to prune."
139
+ return
140
+ end
141
+
142
+ verb = apply ? "Deleted" : "Would delete"
143
+ ctx.out.puts "[Rubik::Session] #{verb} #{report[:candidates]} #{scope_label} session(s) " \
144
+ "older than #{format_days(days)} (#{format('%.1f', size_mb)} MB)."
145
+ unless report[:sample].empty?
146
+ ctx.out.puts " e.g. #{report[:sample].join(', ')}#{', …' if report[:candidates] > report[:sample].size}"
147
+ end
148
+ ctx.out.puts " [CURRENT] active session protected: #{File.basename(active.to_s)}" if report[:protected_skipped].positive?
149
+
150
+ ctx.out.puts " Dry run — nothing removed. Re-run with '--apply' to delete." unless apply
151
+ end
152
+
153
+ def format_days(days)
154
+ days == days.to_i ? "#{days.to_i}d" : "#{days.round(1)}d"
155
+ end
156
+
157
+ def interactive_session_pick(ctx)
158
+ return nil unless Rubik::UI::Capability.interactive?(input: ctx.input, output: ctx.output)
159
+
160
+ root_d = ctx.buffer_manager&.root_dir || Dir.pwd
161
+ home_d = ctx.config&.home_dir || ENV.fetch("HOME", nil)
162
+ sessions = Rubik::Session::Manager.list_sessions(root_d, home_d, config: ctx.config)
163
+ return nil if sessions.empty?
164
+
165
+ prompt = Rubik::UI::Approvals.build_prompt(ctx)
166
+ prompt.select("Devam edilecek oturumu seç:") do |menu|
167
+ sessions.first(15).each do |s|
168
+ menu.choice "#{s[:id]} · #{s[:event_count]} olay · \"#{s[:last_prompt].to_s[0, 40]}\"", s[:id]
169
+ end
170
+ menu.choice "← en son oturum (:latest)", :latest
171
+ end
172
+ rescue Interrupt
173
+ Rubik::REPL::InterruptHandler.restore_console_after_interrupt
174
+ :picker_cancelled
175
+ rescue StandardError => e
176
+ Rubik::Log.warn(:repl, "session picker failed (#{e.class}: #{e.message}); resume selection skipped")
177
+ nil
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module REPL
5
+ module Commands
6
+ module Skills
7
+ # Whole-command synopsis: shared by the spine registration (usage:)
8
+ # and the unknown-subcommand fallback branch, so the two cannot drift.
9
+ USAGE_SKILL = "Usage: !skill [list|show <name>|info <name>|reload]"
10
+
11
+ def self.register(shell)
12
+ handler = proc do |args, ctx|
13
+ manager = ctx.skill_manager
14
+ unless manager
15
+ ctx.out.puts "Skill manager is not initialized."
16
+ next
17
+ end
18
+
19
+ args = args.split if args.is_a?(String)
20
+ subcommand = args[0] || "list"
21
+
22
+ case subcommand
23
+ when "list" then list_skills(ctx, manager)
24
+ when "show" then show_skill(ctx, manager, args[1])
25
+ when "info" then info_skill(ctx, manager, args[1])
26
+ when "reload" then reload_skills(ctx, manager)
27
+ else
28
+ ctx.out.puts "Unknown subcommand: #{subcommand}. #{USAGE_SKILL}"
29
+ end
30
+ end
31
+
32
+ shell.register_command("skill", description: "Agent skills (agentskills.io): list, show <name>, info <name>, reload",
33
+ usage: USAGE_SKILL, audience: :both, &handler)
34
+ shell.register_command("skills", description: "List discovered agent skills", usage: USAGE_SKILL, audience: :human) do |_args, ctx|
35
+ handler.call(["list"], ctx)
36
+ end
37
+ end
38
+
39
+ def self.list_skills(ctx, manager)
40
+ summary = manager.status_summary
41
+ skills = manager.all
42
+ ctx.out.puts "🧠 Agent Skills — #{skills.size} active (#{summary[:skipped]} skipped, #{summary[:notes]} notes)"
43
+ if skills.empty?
44
+ ctx.out.puts "No skills discovered. Add .rubik/skills/<name>/SKILL.md (project), " \
45
+ "~/.rubik/skills/<name>/SKILL.md (user), or extend the skills.paths config."
46
+ else
47
+ skills.each do |skill|
48
+ desc = skill.description.length > 72 ? "#{skill.description[0, 69]}..." : skill.description
49
+ ctx.out.puts format(" %<name>-24s [%<scope>-7s] %<desc>s", name: skill.name, scope: skill.scope, desc: desc)
50
+ end
51
+ end
52
+ print_shadowed(ctx, summary[:shadowed])
53
+ print_issues(ctx, manager)
54
+ end
55
+
56
+ def self.show_skill(ctx, manager, name)
57
+ skill = manager.find(name.to_s)
58
+ return ctx.out.puts "Unknown skill '#{name}'. Use '!skill list'." unless skill
59
+
60
+ ctx.out.puts Rubik::Platform.read_text(skill.path)
61
+ rescue StandardError => e
62
+ ctx.out.puts "Error reading skill '#{name}': #{e.message}"
63
+ end
64
+
65
+ def self.info_skill(ctx, manager, name)
66
+ skill = manager.find(name.to_s)
67
+ return ctx.out.puts "Unknown skill '#{name}'. Use '!skill list'." unless skill
68
+
69
+ ctx.out.puts "=== Skill '#{skill.name}' ==="
70
+ ctx.out.puts " Scope : #{skill.scope}"
71
+ ctx.out.puts " Directory : #{skill.root}"
72
+ ctx.out.puts " Description : #{skill.description}"
73
+ ctx.out.puts " License : #{skill.license || '-'}"
74
+ ctx.out.puts " Compatibility : #{skill.compatibility || '-'}"
75
+ allowed = skill.allowed_tools.empty? ? "-" : "#{skill.allowed_tools.join(' ')} (advisory, not enforced)"
76
+ ctx.out.puts " Allowed tools : #{allowed}"
77
+ meta = skill.metadata.empty? ? "-" : skill.metadata.map { |k, v| "#{k}=#{v}" }.join(", ")
78
+ ctx.out.puts " Metadata : #{meta}"
79
+ aux = skill.auxiliary_files
80
+ resources = aux.empty? ? "none" : aux.map { |dir, files| "#{dir}/ (#{files.size})" }.join(", ")
81
+ ctx.out.puts " Resources : #{resources}"
82
+ skill.warnings.each { |warning| ctx.out.puts " ⚠ #{warning.field} #{warning.message}" }
83
+ end
84
+
85
+ def self.reload_skills(ctx, manager)
86
+ manager.reload!
87
+ summary = manager.status_summary
88
+ scopes = summary[:by_scope].map { |scope, count| "#{scope}: #{count}" }.join(", ")
89
+ ctx.out.puts "🔄 Skills reloaded: #{summary[:total]} active (#{scopes.empty? ? 'none' : scopes}), " \
90
+ "#{summary[:skipped]} skipped, #{summary[:notes]} notes."
91
+ print_shadowed(ctx, summary[:shadowed])
92
+ print_issues(ctx, manager)
93
+ end
94
+
95
+ def self.print_shadowed(ctx, shadowed)
96
+ shadowed.each do |name, entry|
97
+ losers = entry[:loser_scopes].join(", ")
98
+ ctx.out.puts " ⇢ '#{name}' shadowed: #{losers} copy ignored (winner: #{entry[:winner_scope]})"
99
+ end
100
+ end
101
+
102
+ def self.print_issues(ctx, manager)
103
+ manager.issues.each do |issue|
104
+ mark = issue.error? ? "⚠" : "·"
105
+ ctx.out.puts " #{mark} #{File.basename(issue.path)}: #{issue.field} #{issue.message}"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../coeffects"
5
+
6
+ module Rubik
7
+ module REPL
8
+ module Commands
9
+ # !storage — merkezi rubik://storage FS-DB'ye kullanıcı tarafı bakış
10
+ # (storage-show-surface planı B): "raporu göster" isteğini modeli tamamen
11
+ # devre dışı bırakarak karşılar. Depo araç-eylemlerinin aksine bu komut
12
+ # sadece OKUR — yazma düzlemi storage aracında kalır.
13
+ module Storage
14
+ extend Base
15
+
16
+ USAGE_STORAGE = "Usage: !storage [<type|id|type.row|row|hash-prefix|ref>] [refs]"
17
+
18
+ def self.register(shell)
19
+ handler = lambda do |args, ctx|
20
+ store = resolve_store(ctx)
21
+ unless store
22
+ ctx.out.puts "Central storage is not available in this runtime (config storage.enabled=false or standalone construction)."
23
+ return
24
+ end
25
+
26
+ dispatch(args, ctx, store)
27
+ end
28
+
29
+ shell.register_command(
30
+ "storage",
31
+ description: "Browse the central rubik://storage database (entities and refs)",
32
+ usage: USAGE_STORAGE,
33
+ audience: :both,
34
+ &handler
35
+ )
36
+ end
37
+
38
+ def self.dispatch(args, ctx, store)
39
+ return render_refs_table(ctx, store) if args.include?("refs")
40
+ return ctx.out.print(Rubik::Storage::Render.project(store.project, store.project_summary)) if args.empty?
41
+
42
+ selector = args.first.to_s
43
+ return render_type_listing(ctx, store, selector.downcase) if Rubik::Storage::Store::TYPES.include?(selector.downcase)
44
+
45
+ record = resolve_any(store, selector)
46
+ if record
47
+ ctx.out.print(Rubik::Storage::Render.card(record, project: store.project, refs: store.refs))
48
+ else
49
+ ctx.out.puts "Storage entity not found: '#{selector}' in project '#{store.project}'. " \
50
+ "Try '!storage' for counts, '!storage refs' for aliases, or '!storage <type>' for a listing."
51
+ end
52
+ end
53
+
54
+ # Store#get tip ister; kullanıcı seçicisi tipsiz gelir — tüm tiplerde
55
+ # çöz (tam id → type.row → row → hash öneki → alias zinciri meta
56
+ # referanslı olduğu için yalnız eşleşen tipin kayıtlarında tutarlı).
57
+ def self.resolve_any(store, selector)
58
+ Rubik::Storage::Store::TYPES.lazy.map { |t| store.get(type: t, id: selector) }.find(&:itself)
59
+ end
60
+
61
+ def self.render_type_listing(ctx, store, type)
62
+ summaries = store.list(type: type).map(&:to_h)
63
+ ctx.out.print(Rubik::Storage::Render.listing(project: store.project, type: type,
64
+ summaries: summaries, refs: store.refs))
65
+ end
66
+
67
+ def self.render_refs_table(ctx, store)
68
+ refs = store.refs
69
+ if refs.empty?
70
+ ctx.out.puts "No refs defined in project '#{store.project}'."
71
+ return
72
+ end
73
+
74
+ ctx.out.puts "Refs in '#{store.project}':"
75
+ refs.sort.each { |name, id| ctx.out.puts " #{name} → #{id}" }
76
+ end
77
+
78
+ def self.resolve_store(ctx)
79
+ rt = ctx.respond_to?(:runtime_manager) ? ctx.runtime_manager : nil
80
+ rt&.context ? Rubik::Coeffects.get(rt.context, Rubik::Coeffects::STORAGE_STORE) : nil
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../../orchestrator/subagent/budget"
5
+ require_relative "../../orchestrator/subagent/notices"
6
+
7
+ module Rubik
8
+ module REPL
9
+ module Commands
10
+ # REPL bang command (!subagents) for monitoring and supervising autonomous
11
+ # subagents (Plan 94 - Linux Init PID 1 Supervisor Model).
12
+ module Subagents
13
+ extend Base
14
+
15
+ # Whole-command synopsis: shared by the spine registration (usage:)
16
+ # and the unknown-subcommand fallback branch, so the two cannot drift.
17
+ USAGE_SUBAGENTS = "Usage: !subagents [list | status <id> | wait [id] [sec] | extend <id> [steps] | resume [id] | reuse <id> <prompt> | clean | kill <id> | kill-all]"
18
+
19
+ def self.register(shell)
20
+ handler = lambda do |args, ctx|
21
+ subcmd = args.first&.downcase
22
+ sup = resolve_supervisor(ctx)
23
+
24
+ unless sup
25
+ ctx.out.puts "SubagentSupervisor is not available."
26
+ return
27
+ end
28
+
29
+ case subcmd
30
+ when nil, "list" then render_list(ctx, sup)
31
+ when "status" then render_status(ctx, sup, args[1])
32
+ when "types" then render_types(ctx, sup)
33
+ when "extend", "grant", "steps" then handle_extend(ctx, sup, args[1], args[2])
34
+ when "resume", "retry" then handle_resume(ctx, sup, args[1])
35
+ when "reuse", "redeploy" then handle_reuse(ctx, sup, args[1], args[2..])
36
+ when "clean", "reap" then handle_clean(ctx, sup)
37
+ when "wait" then handle_wait(ctx, sup, args[1], args[2])
38
+ when "kill" then handle_kill(ctx, sup, args[1])
39
+ when "kill-all", "killall"
40
+ sup.kill_all
41
+ ctx.out.puts "All subagents terminated."
42
+ else
43
+ ctx.out.puts "Unknown !subagents subcommand: '#{subcmd}'. #{USAGE_SUBAGENTS}"
44
+ end
45
+ end
46
+
47
+ description = "Supervise and manage autonomous subagents (list, status, types, extend, resume, kill, kill-all)"
48
+ shell.register_command("subagents", description: description, usage: USAGE_SUBAGENTS, audience: :both, &handler)
49
+ shell.register_command("subagent", description: "Alias for !subagents", usage: USAGE_SUBAGENTS, audience: :both, &handler)
50
+ # NB: no "agents" alias — System owns !agents (the interactive
51
+ # agent/fiber board); a second registration here would silently
52
+ # overwrite it (register_command only refuses custom-over-builtin).
53
+ end
54
+
55
+ def self.resolve_supervisor(ctx) = ctx.subagent_supervisor
56
+
57
+ def self.render_list(ctx, sup)
58
+ instances = sup.list
59
+ if instances.empty?
60
+ ctx.out.puts "No subagents currently running or tracked."
61
+ return
62
+ end
63
+
64
+ ctx.out.puts "=== Subagent Process Hierarchy (#{instances.size} tracked, #{sup.active_count} active) ==="
65
+ instances.each do |inst|
66
+ dur = if inst.running?
67
+ "up #{inst.uptime.round(1)}s"
68
+ elsif inst.state == :queued
69
+ "queued (waiting on: #{inst.depends_on.join(', ')})"
70
+ else
71
+ "ended (#{inst.uptime.round(1)}s)"
72
+ end
73
+ ctx.out.puts " • [#{inst.id}] #{inst.state.to_s.upcase} (role: #{inst.role}, type: #{inst.type_name}, #{dur})"
74
+ end
75
+ end
76
+
77
+ def self.render_status(ctx, sup, id)
78
+ if id.to_s.strip.empty?
79
+ ctx.out.puts "Usage: !subagents status <subagent_id>"
80
+ return
81
+ end
82
+
83
+ inst = sup.get(id)
84
+ unless inst
85
+ ctx.out.puts "Subagent '#{id}' not found."
86
+ return
87
+ end
88
+
89
+ ctx.out.puts "Subagent: #{inst.id} (Parent: #{inst.parent_id})"
90
+ ctx.out.puts " Role: #{inst.role}"
91
+ ctx.out.puts " Type: #{inst.type_name}"
92
+ ctx.out.puts " State: #{inst.state.to_s.upcase}"
93
+ ctx.out.puts " Steps: #{inst.current_step}/#{inst.max_steps}"
94
+ ctx.out.puts " Uptime: #{inst.uptime.round(2)}s"
95
+ ctx.out.puts " Workspace: #{inst.workspace}"
96
+ ctx.out.puts " Transcript: #{inst.transcript_uri}"
97
+ ctx.out.puts " Last Error: #{inst.last_error}" if inst.last_error
98
+ ctx.out.puts " Result: #{inst.result.to_s.slice(0, 150)}..." if inst.result
99
+ end
100
+
101
+ def self.render_types(ctx, sup)
102
+ types = sup.type_registry.list
103
+ ctx.out.puts "=== Available Subagent Archetypes (#{types.size}) ==="
104
+ types.each do |t|
105
+ flags = []
106
+ flags << "write" if t.enable_write_tools
107
+ flags << "nesting" if t.enable_subagent_tools
108
+ flag_str = flags.empty? ? "read-only" : flags.join(", ")
109
+ ctx.out.puts " • #{t.name.ljust(12)} [#{flag_str}]: #{t.description}"
110
+ end
111
+ end
112
+
113
+ def self.handle_kill(ctx, sup, id)
114
+ if id.to_s.strip.empty?
115
+ ctx.out.puts "Usage: !subagents kill <subagent_id>"
116
+ return
117
+ end
118
+
119
+ if sup.kill(id)
120
+ ctx.out.puts "Subagent '#{id}' and child tree killed."
121
+ else
122
+ ctx.out.puts "Subagent '#{id}' not found."
123
+ end
124
+ end
125
+
126
+ def self.handle_extend(ctx, sup, id, steps_arg)
127
+ if id.to_s.strip.empty?
128
+ ctx.out.puts "Usage: !subagents extend <subagent_id> [additional_steps]"
129
+ return
130
+ end
131
+
132
+ steps = Rubik::Orchestrator::Subagent::Budget.normalize_grant(steps_arg)
133
+ if sup.extend_budget(id, steps)
134
+ inst = sup.get(id)
135
+ ctx.out.puts "Extended budget for #{id} by #{steps} steps (total: #{inst.max_steps}, state: #{inst.state.to_s.upcase})."
136
+ else
137
+ ctx.out.puts "Could not extend budget for #{id} (not found or inactive)."
138
+ end
139
+ end
140
+
141
+ def self.handle_resume(ctx, sup, id)
142
+ if id.to_s.strip.empty?
143
+ resumed = sup.resume_all
144
+ return ctx.out.puts "No paused subagents to resume." if resumed.empty?
145
+
146
+ ctx.out.puts "Resumed #{resumed.size} subagent(s): #{resumed.join(', ')}."
147
+ elsif sup.resume(id)
148
+ inst = sup.get(id)
149
+ ctx.out.puts "Resumed subagent '#{id}' (state: #{inst.state.to_s.upcase})."
150
+ else
151
+ ctx.out.puts "Could not resume subagent '#{id}' (not found or not in pausable state)."
152
+ end
153
+ end
154
+
155
+ # Context-preserving reuse of a finished subagent (maestro persona).
156
+ def self.handle_reuse(ctx, sup, id, words)
157
+ prompt = Array(words).join(" ").strip
158
+ return ctx.out.puts "Usage: !subagents reuse <subagent_id> <new task prompt>" if id.to_s.strip.empty? || prompt.empty?
159
+
160
+ begin
161
+ inst = sup.redeploy(id, prompt: prompt)
162
+ ctx.out.puts "Redeployed '#{id}' with a new task — context preserved (state: #{inst.state.to_s.upcase})."
163
+ rescue ArgumentError => e
164
+ ctx.out.puts e.message
165
+ end
166
+ end
167
+
168
+ def self.handle_clean(ctx, sup)
169
+ reaped = sup.reap_finished!(force: true)
170
+ return ctx.out.puts "No finished subagents to reap." if reaped.empty?
171
+
172
+ ctx.out.puts "Reaped #{reaped.size} finished subagent(s): #{reaped.join(', ')} (transcripts persist)."
173
+ end
174
+
175
+ def self.handle_wait(ctx, sup, arg1, arg2)
176
+ ids = nil
177
+ timeout = Rubik::Orchestrator::Subagent::Notices::DEFAULT_WAIT_TIMEOUT
178
+ if arg1 =~ /^\d+$/
179
+ timeout = arg1.to_i
180
+ elsif arg1 && !arg1.empty?
181
+ ids = [arg1]
182
+ timeout = arg2.to_i if arg2 =~ /^\d+$/
183
+ end
184
+
185
+ ctx.out.puts "Waiting for subagents (timeout: #{timeout}s)..."
186
+ results = sup.wait_for(ids, timeout: timeout)
187
+ return ctx.out.puts "No active subagents tracked." if results.empty?
188
+
189
+ ctx.out.puts "=== Subagents Wait Finished (#{results.size}) ==="
190
+ results.each { |r| ctx.out.puts " • #{r[:id].ljust(12)} [#{r[:state].to_s.upcase}]: #{r[:role]}" }
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end