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.
- checksums.yaml +7 -0
- data/ATTRIBUTION.md +139 -0
- data/CHANGELOG.md +236 -0
- data/LICENSE +21 -0
- data/README.md +269 -0
- data/bin/rubik +82 -0
- data/lib/rubik/adapter/base.rb +195 -0
- data/lib/rubik/adapter/c.rb +36 -0
- data/lib/rubik/adapter/crystal.rb +78 -0
- data/lib/rubik/adapter/generic.rb +86 -0
- data/lib/rubik/adapter/go.rb +36 -0
- data/lib/rubik/adapter/node.rb +146 -0
- data/lib/rubik/adapter/process.rb +78 -0
- data/lib/rubik/adapter/python.rb +30 -0
- data/lib/rubik/adapter/registry.rb +81 -0
- data/lib/rubik/adapter/ruby.rb +30 -0
- data/lib/rubik/adapter/rust.rb +36 -0
- data/lib/rubik/adapter/zig.rb +36 -0
- data/lib/rubik/adapter.rb +18 -0
- data/lib/rubik/ast/body_loader.rb +19 -0
- data/lib/rubik/ast/c_extractor.rb +114 -0
- data/lib/rubik/ast/cache_store.rb +191 -0
- data/lib/rubik/ast/extractor.rb +59 -0
- data/lib/rubik/ast/fallback_cleaner.rb +145 -0
- data/lib/rubik/ast/go_extractor.rb +148 -0
- data/lib/rubik/ast/graph.rb +272 -0
- data/lib/rubik/ast/python_extractor.rb +120 -0
- data/lib/rubik/ast/ruby_extractor.rb +260 -0
- data/lib/rubik/ast/rust_extractor.rb +127 -0
- data/lib/rubik/ast/symbol.rb +103 -0
- data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
- data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
- data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
- data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
- data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
- data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
- data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
- data/lib/rubik/ast/treesitter_extractor.rb +93 -0
- data/lib/rubik/ast/typescript_extractor.rb +283 -0
- data/lib/rubik/ast/zig_extractor.rb +116 -0
- data/lib/rubik/ast.rb +13 -0
- data/lib/rubik/benchmark/runner.rb +224 -0
- data/lib/rubik/benchmark/synth_repo.rb +59 -0
- data/lib/rubik/benchmark.rb +9 -0
- data/lib/rubik/buffer/diff.rb +43 -0
- data/lib/rubik/buffer/manager.rb +427 -0
- data/lib/rubik/buffer/path_validator.rb +60 -0
- data/lib/rubik/buffer/snapshot.rb +99 -0
- data/lib/rubik/buffer.rb +10 -0
- data/lib/rubik/coeffects.rb +129 -0
- data/lib/rubik/concurrency/boundary.rb +74 -0
- data/lib/rubik/concurrency/dns_pool.rb +68 -0
- data/lib/rubik/concurrency/errors.rb +17 -0
- data/lib/rubik/concurrency/event_loop.rb +441 -0
- data/lib/rubik/concurrency/join.rb +55 -0
- data/lib/rubik/concurrency/process_waiter.rb +55 -0
- data/lib/rubik/concurrency/scheduler.rb +84 -0
- data/lib/rubik/concurrency/timers.rb +109 -0
- data/lib/rubik/concurrency.rb +132 -0
- data/lib/rubik/config/deep_ops.rb +80 -0
- data/lib/rubik/config/defaults.rb +248 -0
- data/lib/rubik/config/model_catalog.rb +93 -0
- data/lib/rubik/config/store.rb +155 -0
- data/lib/rubik/config.rb +216 -0
- data/lib/rubik/context/git_info.rb +84 -0
- data/lib/rubik/context/git_lister.rb +37 -0
- data/lib/rubik/context/index.rb +134 -0
- data/lib/rubik/context/repo_context_reader.rb +121 -0
- data/lib/rubik/context/scanner.rb +439 -0
- data/lib/rubik/context.rb +11 -0
- data/lib/rubik/diagnostics/collector.rb +200 -0
- data/lib/rubik/diagnostics/entry.rb +60 -0
- data/lib/rubik/diagnostics/parser.rb +107 -0
- data/lib/rubik/diagnostics.rb +10 -0
- data/lib/rubik/event_bus.rb +56 -0
- data/lib/rubik/kernel/dispatcher.rb +171 -0
- data/lib/rubik/kernel/dmesg.rb +116 -0
- data/lib/rubik/kernel/init.rb +177 -0
- data/lib/rubik/kernel/record.rb +85 -0
- data/lib/rubik/kernel/unit.rb +160 -0
- data/lib/rubik/llm/anthropic.rb +202 -0
- data/lib/rubik/llm/chunk_assembler.rb +285 -0
- data/lib/rubik/llm/client.rb +526 -0
- data/lib/rubik/llm/formatter.rb +450 -0
- data/lib/rubik/llm/message_sanitizer.rb +87 -0
- data/lib/rubik/llm/ollama.rb +167 -0
- data/lib/rubik/llm/openai.rb +21 -0
- data/lib/rubik/llm/openai_compatible.rb +216 -0
- data/lib/rubik/llm/provider.rb +125 -0
- data/lib/rubik/llm/registry.rb +336 -0
- data/lib/rubik/llm/router.rb +100 -0
- data/lib/rubik/llm.rb +18 -0
- data/lib/rubik/log.rb +38 -0
- data/lib/rubik/lsp/client.rb +475 -0
- data/lib/rubik/lsp/manager.rb +366 -0
- data/lib/rubik/lsp.rb +9 -0
- data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
- data/lib/rubik/mcp/client.rb +169 -0
- data/lib/rubik/mcp/errors.rb +11 -0
- data/lib/rubik/mcp/manager.rb +619 -0
- data/lib/rubik/mcp/proxy_tool.rb +104 -0
- data/lib/rubik/mcp/transport/stdio.rb +255 -0
- data/lib/rubik/mcp.rb +12 -0
- data/lib/rubik/orchestrator/activity_log.rb +75 -0
- data/lib/rubik/orchestrator/auto_apply.rb +166 -0
- data/lib/rubik/orchestrator/budget_manager.rb +97 -0
- data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
- data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
- data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
- data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
- data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
- data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
- data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
- data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
- data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
- data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
- data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
- data/lib/rubik/orchestrator/goal_state.rb +184 -0
- data/lib/rubik/orchestrator/goal_wait.rb +125 -0
- data/lib/rubik/orchestrator/loop.rb +388 -0
- data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
- data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
- data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
- data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
- data/lib/rubik/orchestrator/shell_deps.rb +85 -0
- data/lib/rubik/orchestrator/step_executor.rb +328 -0
- data/lib/rubik/orchestrator/stream_manager.rb +118 -0
- data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
- data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
- data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
- data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
- data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
- data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
- data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
- data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
- data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
- data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
- data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
- data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
- data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
- data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
- data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
- data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
- data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
- data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
- data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
- data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
- data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
- data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
- data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
- data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
- data/lib/rubik/orchestrator/tool_history.rb +39 -0
- data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
- data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
- data/lib/rubik/orchestrator/turn_executor.rb +376 -0
- data/lib/rubik/orchestrator.rb +11 -0
- data/lib/rubik/platform/filesystem.rb +64 -0
- data/lib/rubik/platform/io_drain.rb +53 -0
- data/lib/rubik/platform/path.rb +210 -0
- data/lib/rubik/platform/process.rb +38 -0
- data/lib/rubik/platform/sanitize.rb +157 -0
- data/lib/rubik/platform.rb +113 -0
- data/lib/rubik/plugin/dsl.rb +51 -0
- data/lib/rubik/plugin/manager.rb +247 -0
- data/lib/rubik/plugin/storage.rb +195 -0
- data/lib/rubik/plugin.rb +10 -0
- data/lib/rubik/prompt/context_builder.rb +377 -0
- data/lib/rubik/prompt/context_compactor.rb +326 -0
- data/lib/rubik/prompt/conversation_rows.rb +74 -0
- data/lib/rubik/prompt/doctrine.rb +160 -0
- data/lib/rubik/prompt/emergency_compactor.rb +81 -0
- data/lib/rubik/prompt/services.rb +60 -0
- data/lib/rubik/prompt/token_estimator.rb +63 -0
- data/lib/rubik/prompt/tool_teachings.rb +78 -0
- data/lib/rubik/prompt.rb +13 -0
- data/lib/rubik/repair/loop.rb +95 -0
- data/lib/rubik/repair.rb +8 -0
- data/lib/rubik/repl/ast_warmup.rb +129 -0
- data/lib/rubik/repl/command_context.rb +214 -0
- data/lib/rubik/repl/command_parser.rb +48 -0
- data/lib/rubik/repl/commands/agent.rb +30 -0
- data/lib/rubik/repl/commands/agent_context.rb +289 -0
- data/lib/rubik/repl/commands/agent_execution.rb +342 -0
- data/lib/rubik/repl/commands/agent_models.rb +212 -0
- data/lib/rubik/repl/commands/agent_providers.rb +232 -0
- data/lib/rubik/repl/commands/base.rb +55 -0
- data/lib/rubik/repl/commands/buffer.rb +124 -0
- data/lib/rubik/repl/commands/dmesg.rb +87 -0
- data/lib/rubik/repl/commands/lsp.rb +235 -0
- data/lib/rubik/repl/commands/mcp.rb +194 -0
- data/lib/rubik/repl/commands/plugins.rb +208 -0
- data/lib/rubik/repl/commands/pruner.rb +71 -0
- data/lib/rubik/repl/commands/session.rb +183 -0
- data/lib/rubik/repl/commands/skills.rb +111 -0
- data/lib/rubik/repl/commands/storage.rb +85 -0
- data/lib/rubik/repl/commands/subagents.rb +195 -0
- data/lib/rubik/repl/commands/system.rb +85 -0
- data/lib/rubik/repl/commands/tasks.rb +120 -0
- data/lib/rubik/repl/commands/workspace.rb +443 -0
- data/lib/rubik/repl/commands.rb +44 -0
- data/lib/rubik/repl/composition.rb +32 -0
- data/lib/rubik/repl/config_wiring.rb +142 -0
- data/lib/rubik/repl/confirmations.rb +56 -0
- data/lib/rubik/repl/hooks.rb +47 -0
- data/lib/rubik/repl/input_execution.rb +323 -0
- data/lib/rubik/repl/input_loop.rb +213 -0
- data/lib/rubik/repl/interrupt_handler.rb +217 -0
- data/lib/rubik/repl/lifecycle.rb +410 -0
- data/lib/rubik/repl/paste_support.rb +88 -0
- data/lib/rubik/repl/pending_notice_queue.rb +60 -0
- data/lib/rubik/repl/presentation.rb +132 -0
- data/lib/rubik/repl/pruner/brief.rb +101 -0
- data/lib/rubik/repl/pruner/gate.rb +279 -0
- data/lib/rubik/repl/pruner/harness.rb +236 -0
- data/lib/rubik/repl/pruner/rules.rb +316 -0
- data/lib/rubik/repl/pruner/service.rb +82 -0
- data/lib/rubik/repl/pruner.rb +39 -0
- data/lib/rubik/repl/service_catalog.rb +374 -0
- data/lib/rubik/repl/setup_factories.rb +66 -0
- data/lib/rubik/repl/shell.rb +231 -0
- data/lib/rubik/repl/step_formatter.rb +324 -0
- data/lib/rubik/repl/tee_io.rb +105 -0
- data/lib/rubik/repl/terminal_guard.rb +157 -0
- data/lib/rubik/repl/turn_recorder.rb +234 -0
- data/lib/rubik/repl.rb +14 -0
- data/lib/rubik/runtime/ast_service.rb +78 -0
- data/lib/rubik/runtime/context.rb +335 -0
- data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
- data/lib/rubik/runtime/discovery.rb +342 -0
- data/lib/rubik/runtime/fiber.rb +142 -0
- data/lib/rubik/runtime/fiber_ordering.rb +52 -0
- data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
- data/lib/rubik/runtime/index_service.rb +58 -0
- data/lib/rubik/runtime/loop_service.rb +73 -0
- data/lib/rubik/runtime/manager.rb +241 -0
- data/lib/rubik/runtime/overlay_storage.rb +481 -0
- data/lib/rubik/runtime/service_definition.rb +54 -0
- data/lib/rubik/runtime/service_registration.rb +33 -0
- data/lib/rubik/runtime/task_manager.rb +302 -0
- data/lib/rubik/runtime.rb +19 -0
- data/lib/rubik/sandbox/driver/base.rb +30 -0
- data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
- data/lib/rubik/sandbox/driver/docker.rb +113 -0
- data/lib/rubik/sandbox/driver/podman.rb +98 -0
- data/lib/rubik/sandbox/driver/process.rb +190 -0
- data/lib/rubik/sandbox/driver.rb +91 -0
- data/lib/rubik/sandbox/runner.rb +218 -0
- data/lib/rubik/sandbox/shell_resolver.rb +178 -0
- data/lib/rubik/sandbox.rb +8 -0
- data/lib/rubik/security/blocked_guidance.rb +187 -0
- data/lib/rubik/security/classifier/classifier.rb +440 -0
- data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
- data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
- data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
- data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
- data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
- data/lib/rubik/security/classifier/commands/black.yaml +12 -0
- data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
- data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
- data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
- data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
- data/lib/rubik/security/classifier/commands/column.yaml +7 -0
- data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
- data/lib/rubik/security/classifier/commands/command.yaml +12 -0
- data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
- data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
- data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
- data/lib/rubik/security/classifier/commands/date.yaml +10 -0
- data/lib/rubik/security/classifier/commands/df.yaml +7 -0
- data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
- data/lib/rubik/security/classifier/commands/du.yaml +7 -0
- data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
- data/lib/rubik/security/classifier/commands/env.yaml +15 -0
- data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
- data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
- data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
- data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/false.yaml +7 -0
- data/lib/rubik/security/classifier/commands/file.yaml +7 -0
- data/lib/rubik/security/classifier/commands/find.yaml +25 -0
- data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
- data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
- data/lib/rubik/security/classifier/commands/free.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
- data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
- data/lib/rubik/security/classifier/commands/git.yaml +524 -0
- data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
- data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
- data/lib/rubik/security/classifier/commands/go.yaml +44 -0
- data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
- data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
- data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
- data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
- data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
- data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
- data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
- data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
- data/lib/rubik/security/classifier/commands/head.yaml +7 -0
- data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
- data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/id.yaml +7 -0
- data/lib/rubik/security/classifier/commands/install.yaml +7 -0
- data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
- data/lib/rubik/security/classifier/commands/java.yaml +35 -0
- data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
- data/lib/rubik/security/classifier/commands/join.yaml +7 -0
- data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
- data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
- data/lib/rubik/security/classifier/commands/less.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
- data/lib/rubik/security/classifier/commands/make.yaml +21 -0
- data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
- data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
- data/lib/rubik/security/classifier/commands/more.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
- data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
- data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
- data/lib/rubik/security/classifier/commands/node.yaml +20 -0
- data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
- data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
- data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
- data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
- data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
- data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
- data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
- data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
- data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
- data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
- data/lib/rubik/security/classifier/commands/python.yaml +9 -0
- data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
- data/lib/rubik/security/classifier/commands/read.yaml +7 -0
- data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
- data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
- data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
- data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
- data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
- data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
- data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
- data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
- data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
- data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
- data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
- data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
- data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
- data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
- data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
- data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
- data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
- data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
- data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
- data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
- data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
- data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
- data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
- data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
- data/lib/rubik/security/classifier/commands/test.yaml +7 -0
- data/lib/rubik/security/classifier/commands/time.yaml +8 -0
- data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
- data/lib/rubik/security/classifier/commands/top.yaml +7 -0
- data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
- data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
- data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
- data/lib/rubik/security/classifier/commands/true.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
- data/lib/rubik/security/classifier/commands/type.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
- data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
- data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
- data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
- data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
- data/lib/rubik/security/classifier/commands/which.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
- data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
- data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
- data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
- data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
- data/lib/rubik/security/classifier/database.rb +216 -0
- data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
- data/lib/rubik/security/classifier/matcher/options.rb +245 -0
- data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
- data/lib/rubik/security/classifier/matcher.rb +249 -0
- data/lib/rubik/security/classifier/models.rb +170 -0
- data/lib/rubik/security/classifier/parser.rb +23 -0
- data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
- data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
- data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
- data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
- data/lib/rubik/security/classifier.rb +25 -0
- data/lib/rubik/security/errors.rb +39 -0
- data/lib/rubik/security/guard.rb +954 -0
- data/lib/rubik/security/redactor.rb +89 -0
- data/lib/rubik/security.rb +11 -0
- data/lib/rubik/session/manager.rb +331 -0
- data/lib/rubik/session/recorder.rb +149 -0
- data/lib/rubik/session/replayer.rb +65 -0
- data/lib/rubik/session.rb +10 -0
- data/lib/rubik/skills/frontmatter.rb +41 -0
- data/lib/rubik/skills/manager.rb +223 -0
- data/lib/rubik/skills/registry.rb +64 -0
- data/lib/rubik/skills/skill.rb +33 -0
- data/lib/rubik/skills/storage.rb +77 -0
- data/lib/rubik/skills/validator.rb +116 -0
- data/lib/rubik/skills.rb +20 -0
- data/lib/rubik/storage/folding.rb +23 -0
- data/lib/rubik/storage/layout.rb +145 -0
- data/lib/rubik/storage/render.rb +97 -0
- data/lib/rubik/storage/service.rb +42 -0
- data/lib/rubik/storage/store.rb +514 -0
- data/lib/rubik/storage/transcript_indexer.rb +116 -0
- data/lib/rubik/storage/uri_reader.rb +168 -0
- data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
- data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
- data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
- data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
- data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
- data/lib/rubik/storage/vakanuvis/service.rb +69 -0
- data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
- data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
- data/lib/rubik/storage/vakanuvis.rb +77 -0
- data/lib/rubik/storage.rb +29 -0
- data/lib/rubik/tools/base.rb +107 -0
- data/lib/rubik/tools/builtins/dmesg.rb +74 -0
- data/lib/rubik/tools/builtins/exec.rb +293 -0
- data/lib/rubik/tools/builtins/files.rb +354 -0
- data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
- data/lib/rubik/tools/builtins/grep.rb +281 -0
- data/lib/rubik/tools/builtins/interactive.rb +175 -0
- data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
- data/lib/rubik/tools/builtins/lsp.rb +283 -0
- data/lib/rubik/tools/builtins/pruner.rb +88 -0
- data/lib/rubik/tools/builtins/runtime.rb +120 -0
- data/lib/rubik/tools/builtins/send_message.rb +109 -0
- data/lib/rubik/tools/builtins/show_file.rb +84 -0
- data/lib/rubik/tools/builtins/skill.rb +72 -0
- data/lib/rubik/tools/builtins/storage.rb +232 -0
- data/lib/rubik/tools/builtins/subagent.rb +356 -0
- data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
- data/lib/rubik/tools/builtins/symbols.rb +209 -0
- data/lib/rubik/tools/builtins/synthesis.rb +314 -0
- data/lib/rubik/tools/builtins/task.rb +168 -0
- data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
- data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
- data/lib/rubik/tools/builtins.rb +30 -0
- data/lib/rubik/tools/catalog.rb +70 -0
- data/lib/rubik/tools/declaration.rb +90 -0
- data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
- data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
- data/lib/rubik/tools/native_search.rb +180 -0
- data/lib/rubik/tools/output.rb +54 -0
- data/lib/rubik/tools/preflight_runner.rb +209 -0
- data/lib/rubik/tools/registry.rb +247 -0
- data/lib/rubik/tools/result.rb +71 -0
- data/lib/rubik/tools/services.rb +96 -0
- data/lib/rubik/tools/truncator.rb +78 -0
- data/lib/rubik/tools/uri_resolver.rb +195 -0
- data/lib/rubik/tools.rb +14 -0
- data/lib/rubik/ui/approvals.rb +52 -0
- data/lib/rubik/ui/banner.rb +43 -0
- data/lib/rubik/ui/capability.rb +122 -0
- data/lib/rubik/ui/dialog.rb +106 -0
- data/lib/rubik/ui/diff_viewer.rb +78 -0
- data/lib/rubik/ui/frame.rb +95 -0
- data/lib/rubik/ui/markdown_stream.rb +107 -0
- data/lib/rubik/ui/panel.rb +98 -0
- data/lib/rubik/ui/renderer.rb +206 -0
- data/lib/rubik/ui/spinner.rb +120 -0
- data/lib/rubik/ui/statusline.rb +134 -0
- data/lib/rubik/ui/theme.rb +118 -0
- data/lib/rubik/ui.rb +57 -0
- data/lib/rubik/uris.rb +101 -0
- data/lib/rubik/version.rb +5 -0
- data/lib/rubik.rb +65 -0
- metadata +770 -0
|
@@ -0,0 +1,74 @@
|
|
|
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 inspecting the kernel monotonic event ring buffer (Plan 102).
|
|
10
|
+
class DmesgTool < BaseTool
|
|
11
|
+
REQUIRES = [Rubik::Coeffects::DMESG].freeze
|
|
12
|
+
|
|
13
|
+
attr_accessor :dmesg, :runtime_context
|
|
14
|
+
|
|
15
|
+
DECLARATION = Declaration.new(
|
|
16
|
+
name: "dmesg", aliases: ["kernel_log", "kernel_dmesg"],
|
|
17
|
+
description: "Read kernel event ring buffer (telemetry, subagent notices, diagnostics, errors). " \
|
|
18
|
+
"Filter by limit, severity level, subsystem, or sequence.",
|
|
19
|
+
risk: :read,
|
|
20
|
+
params_schema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
limit: {
|
|
24
|
+
type: "integer",
|
|
25
|
+
description: "Maximum number of recent events to return (default: 30)."
|
|
26
|
+
},
|
|
27
|
+
level: {
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"],
|
|
30
|
+
description: "Filter by severity level."
|
|
31
|
+
},
|
|
32
|
+
subsystem: {
|
|
33
|
+
type: "string",
|
|
34
|
+
enum: ["init", "subagent", "llm", "net", "buffer", "tool", "sandbox", "kernel", "mcp"],
|
|
35
|
+
description: "Filter by subsystem / facility."
|
|
36
|
+
},
|
|
37
|
+
since_seq: {
|
|
38
|
+
type: "integer",
|
|
39
|
+
description: "Only return events strictly after this sequence number."
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
def initialize(dmesg = nil, runtime_context: nil)
|
|
45
|
+
super(DECLARATION)
|
|
46
|
+
@dmesg = dmesg
|
|
47
|
+
@runtime_context = runtime_context
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def execute(args = {})
|
|
51
|
+
ring = resolve_dmesg
|
|
52
|
+
return ToolOutput.error("Dmesg ring buffer is not available in runtime context.") unless ring
|
|
53
|
+
|
|
54
|
+
limit = (args["limit"] || args[:limit] || 30).to_i
|
|
55
|
+
level = args["level"] || args[:level]
|
|
56
|
+
subsystem = args["subsystem"] || args[:subsystem]
|
|
57
|
+
since_seq = args["since_seq"] || args[:since_seq]
|
|
58
|
+
|
|
59
|
+
records = ring.tail(limit, level: level, subsystem: subsystem, since_seq: since_seq)
|
|
60
|
+
return "No kernel events found matching criteria." if records.empty?
|
|
61
|
+
|
|
62
|
+
ring.formatted(records, color: false)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
ToolOutput.error("dmesg error: #{e.message}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def resolve_dmesg
|
|
70
|
+
@dmesg || (@runtime_context.respond_to?(:get) ? Rubik::Coeffects.get(@runtime_context, Rubik::Coeffects::DMESG) : nil)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "../base"
|
|
5
|
+
require_relative "../output"
|
|
6
|
+
require_relative "../../security/blocked_guidance"
|
|
7
|
+
|
|
8
|
+
module Rubik
|
|
9
|
+
module Tools
|
|
10
|
+
class RunTestsTool < BaseTool
|
|
11
|
+
DECLARATION = Declaration.new(
|
|
12
|
+
name: "run_tests", aliases: ["test", "run_test_suite"],
|
|
13
|
+
description: "Run the project test suite using the appropriate language adapter. " \
|
|
14
|
+
"Specify 'path' to target a specific test file or directory fast. " \
|
|
15
|
+
"Use apply_buffer to persist buffer edits before testing so tests see the latest changes.",
|
|
16
|
+
risk: :execute, archetypes: [:coder], maestro_blocked: true,
|
|
17
|
+
params_schema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
language: { type: "string", description: "Language test runner to use (e.g. ruby, go, rust, zig, python, c, crystal) (optional)" },
|
|
21
|
+
path: { type: "string", description: "Target test file or directory path (e.g. 'test/unit/test_lsp_client.rb') to run fast (optional)" },
|
|
22
|
+
timeout: { type: "integer", description: "Timeout in seconds (optional, default: 30)" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
def initialize(adapter_registry, context_index = nil, root_dir: Dir.pwd, config: nil, guard: nil)
|
|
27
|
+
super(DECLARATION)
|
|
28
|
+
@adapter_registry = adapter_registry
|
|
29
|
+
@context_index = context_index
|
|
30
|
+
@root_dir = File.expand_path(root_dir)
|
|
31
|
+
@config = config
|
|
32
|
+
@guard = guard
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute(args = {})
|
|
36
|
+
lang = args["language"] || args[:language]
|
|
37
|
+
path = args["path"] || args[:path]
|
|
38
|
+
timeout = args["timeout"] || args[:timeout]
|
|
39
|
+
if lang.nil? || lang.to_s.strip.empty?
|
|
40
|
+
langs = @context_index&.languages || []
|
|
41
|
+
lang = langs.first || "ruby"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
lang_key = lang.to_s.downcase.strip
|
|
45
|
+
adapter = @adapter_registry&.get(lang_key)
|
|
46
|
+
adapter ||= synthesize_adapter(lang_key)
|
|
47
|
+
|
|
48
|
+
unless adapter
|
|
49
|
+
return ToolOutput.error(
|
|
50
|
+
<<~ERR.chomp
|
|
51
|
+
-- NO TEST ADAPTER CONFIGURED ---------------------------------------
|
|
52
|
+
No test runner is configured for language '#{lang}'.
|
|
53
|
+
|
|
54
|
+
To configure a test runner automatically, call:
|
|
55
|
+
repair_runtime_service(target: "test_runner", language_or_type: "#{lang}", proposed_command: "<command>")
|
|
56
|
+
|
|
57
|
+
Or execute your test command directly via:
|
|
58
|
+
run_command(command: "<command>")
|
|
59
|
+
ERR
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
clean_path = path.to_s.strip
|
|
64
|
+
clean_path = nil if clean_path.empty?
|
|
65
|
+
eff_timeout = timeout.to_i if timeout&.to_i&.positive?
|
|
66
|
+
|
|
67
|
+
res = if clean_path || eff_timeout
|
|
68
|
+
begin
|
|
69
|
+
adapter.run_tests(@root_dir, path: clean_path, timeout: eff_timeout)
|
|
70
|
+
rescue ArgumentError => e
|
|
71
|
+
raise e unless e.message =~ /wrong number of arguments|unknown keyword/
|
|
72
|
+
|
|
73
|
+
adapter.run_tests(@root_dir)
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
adapter.run_tests(@root_dir)
|
|
77
|
+
end
|
|
78
|
+
status = res[:ok] ? "PASSED" : "FAILED"
|
|
79
|
+
out = "Test run [#{status}] (#{res[:duration]}s):\n#{res[:output]}"
|
|
80
|
+
|
|
81
|
+
out += "\n\n#{res[:diagnostic].format_for_llm}" if !res[:ok] && res[:diagnostic]
|
|
82
|
+
out
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def synthesize_adapter(lang_key)
|
|
88
|
+
configured_cmd = @config&.fetch("test.runners.#{lang_key}")
|
|
89
|
+
candidate_cmd = if configured_cmd && !configured_cmd.to_s.strip.empty?
|
|
90
|
+
configured_cmd
|
|
91
|
+
else
|
|
92
|
+
require_relative "../../runtime/discovery" unless defined?(Rubik::Runtime::Discovery)
|
|
93
|
+
candidates = Rubik::Runtime::Discovery.test_runner_candidates(lang_key, root_dir: @root_dir, config: @config)
|
|
94
|
+
candidates.find { |c| Rubik::Runtime::Discovery.binary_available?(c, root_dir: @root_dir) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return nil unless candidate_cmd
|
|
98
|
+
|
|
99
|
+
# Fail-closed gate: a test-runner command discovered from operator
|
|
100
|
+
# config or heuristics is an arbitrary shell command, verified by the
|
|
101
|
+
# GenericAdapter constructor through the shared session guard BEFORE a
|
|
102
|
+
# runnable adapter exists — otherwise RunTestsTool could fabricate an
|
|
103
|
+
# adapter for any dangerous command (e.g. "rm -rf /") with no
|
|
104
|
+
# classification. validation_shell: "bash" classifies the candidate as
|
|
105
|
+
# the shell command it is (run_tests itself is one), not as Ruby
|
|
106
|
+
# source. The shared guard (registry injects it) carries the operator
|
|
107
|
+
# allowlist, so !allow-cmd add "python3 -m pytest" can rescue a runner
|
|
108
|
+
# here; the lazy fallback (silent audit, zero policy) serves only
|
|
109
|
+
# standalone tool constructions.
|
|
110
|
+
require_relative "../../security/guard" unless defined?(Rubik::Security::Guard)
|
|
111
|
+
security_guard = @guard || Rubik::Security::Guard.new(audit_log_path: nil)
|
|
112
|
+
|
|
113
|
+
require_relative "../../adapter/generic" unless defined?(Rubik::Adapter::GenericAdapter)
|
|
114
|
+
new_adapter = Rubik::Adapter::GenericAdapter.new(lang_key, test_cmd: candidate_cmd,
|
|
115
|
+
guard: security_guard, validation_shell: "bash")
|
|
116
|
+
new_adapter.config = @config
|
|
117
|
+
@adapter_registry&.register(lang_key, new_adapter)
|
|
118
|
+
new_adapter
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class RunCommandTool < BaseTool
|
|
123
|
+
attr_accessor :task_manager, :runtime_context
|
|
124
|
+
|
|
125
|
+
DECLARATION = Declaration.new(
|
|
126
|
+
name: "run_command", aliases: ["bash", "sh", "execute_command", "cmd"],
|
|
127
|
+
description: "Execute a safe shell command inside the sandbox runner in the project working directory (do not use cd). " \
|
|
128
|
+
"Unknown or dangerous commands are blocked fail-closed; the human operator can allowlist specific command " \
|
|
129
|
+
"prefixes via the operator-only !allow-cmd REPL command.",
|
|
130
|
+
risk: :execute, archetypes: [:coder], write_grant: true, maestro_blocked: true, # maestro: command execution belongs to coder subagents
|
|
131
|
+
params_schema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
command: { type: "string", description: "Shell command to run directly in the project directory" },
|
|
135
|
+
timeout: { type: "integer", description: "Timeout in seconds (optional, default: 30)" },
|
|
136
|
+
background: { type: "boolean", description: "Run command asynchronously in the background as a managed task (optional)" }
|
|
137
|
+
},
|
|
138
|
+
required: ["command"]
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
def initialize(guard:, runner:, root_dir: Dir.pwd, task_manager: nil, runtime_context: nil)
|
|
142
|
+
super(DECLARATION)
|
|
143
|
+
@guard = guard
|
|
144
|
+
@runner = runner
|
|
145
|
+
@root_dir = File.expand_path(root_dir)
|
|
146
|
+
@task_manager = task_manager
|
|
147
|
+
@runtime_context = runtime_context
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def execute(args = {})
|
|
151
|
+
cmd = (args["command"] || args[:command]).to_s.strip
|
|
152
|
+
background = args["background"] || args[:background]
|
|
153
|
+
if background
|
|
154
|
+
task_mgr = @task_manager || (@runtime_context.respond_to?(:get) ? Rubik::Coeffects.get(@runtime_context, Rubik::Coeffects::TASK_MANAGER) : nil)
|
|
155
|
+
return ToolOutput.error("TaskManager is not available to run commands in background.") unless task_mgr
|
|
156
|
+
|
|
157
|
+
@guard.verify_command_safety!(cmd)
|
|
158
|
+
task = task_mgr.start(cmd, chdir: @root_dir, ctx: @runtime_context)
|
|
159
|
+
return "Command dispatched to background task [#{task.id}] (PID #{task.pid}): #{cmd}\nUse `manage_task` with action 'status' or 'logs' to inspect."
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Timeout contract: runners expose their config via a public `config`
|
|
163
|
+
# reader (Sandbox::Runner does via attr_reader). Never reach into an
|
|
164
|
+
# arbitrary object with instance_variable_get — that couples us to an
|
|
165
|
+
# ivar name we do not own and silently falls back if it is ever renamed.
|
|
166
|
+
cfg = @runner.config if @runner.respond_to?(:config)
|
|
167
|
+
cfg_timeout = cfg&.fetch("tools.timeout_seconds", 30) || 30
|
|
168
|
+
timeout = (args["timeout"] || args[:timeout] || cfg_timeout).to_i.clamp(1, 600)
|
|
169
|
+
|
|
170
|
+
@guard.verify_command_safety!(cmd)
|
|
171
|
+
res = @runner.run(cmd, timeout: timeout, chdir: @root_dir)
|
|
172
|
+
status = res[:ok] ? "OK" : "EXIT #{res[:exit_code]}"
|
|
173
|
+
# Shell tag (Plan 55 D-5) teaches the model which interpreter ran the
|
|
174
|
+
# command; drivers that resolve no shell simply omit it.
|
|
175
|
+
shell_tag = res[:shell] ? " (#{res[:shell]})" : ""
|
|
176
|
+
out = "[#{status}]#{shell_tag} #{res[:output]}"
|
|
177
|
+
out += "\n#{res[:isolation]}" if res[:isolation]
|
|
178
|
+
|
|
179
|
+
unless res[:ok]
|
|
180
|
+
require_relative "../../runtime/diagnostic_engine" unless defined?(Rubik::Runtime::DiagnosticEngine)
|
|
181
|
+
diag = Rubik::Runtime::DiagnosticEngine.analyze_process_result(res, cmd: cmd)
|
|
182
|
+
out += "\n\n#{diag.format_for_llm}" if diag
|
|
183
|
+
end
|
|
184
|
+
out
|
|
185
|
+
rescue Rubik::Security::BlockedWriteError => e
|
|
186
|
+
# Prefix stays first: StepExecutor::ERROR_PREFIXES classifies by start_with?.
|
|
187
|
+
ToolOutput.error(Rubik::Tools::RunCommandTool.blocked_guidance(e))
|
|
188
|
+
rescue StandardError => e
|
|
189
|
+
ToolOutput.error("Command error: #{e.message}")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Stable public seam (RunCommandTool#execute, ManageTaskTool, tests and
|
|
193
|
+
# dev/review_fix_check call it). The renderer lives in the security
|
|
194
|
+
# domain next to the error it renders: Rubik::Security::BlockedGuidance
|
|
195
|
+
# — also keeps this file under the builtin line budget.
|
|
196
|
+
def self.blocked_guidance(err)
|
|
197
|
+
Rubik::Security::BlockedGuidance.render(err)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class ExecReplCommandTool < BaseTool
|
|
202
|
+
REDIRECT_MAP = {
|
|
203
|
+
"test" => "run_tests",
|
|
204
|
+
"sh" => "run_command",
|
|
205
|
+
"read" => "read_file",
|
|
206
|
+
"write" => "write_file",
|
|
207
|
+
"apply" => "apply_buffer",
|
|
208
|
+
"symbols" => "search_symbols",
|
|
209
|
+
"outline" => "get_outline",
|
|
210
|
+
"refs" => "find_references",
|
|
211
|
+
"ast" => "get_symbol / get_outline",
|
|
212
|
+
"diagnostics" => "lsp_diagnostics"
|
|
213
|
+
}.freeze
|
|
214
|
+
|
|
215
|
+
# Commands that terminate or erase the interactive session. Hard-blocked
|
|
216
|
+
# for the agent regardless of audience metadata (Plan 53 D-3).
|
|
217
|
+
SESSION_CONTROL_COMMANDS = ["quit", "exit", "clear", "reset"].freeze
|
|
218
|
+
|
|
219
|
+
DECLARATION = Declaration.new(
|
|
220
|
+
name: "exec_repl_command", aliases: ["repl_command", "exec_repl", "repl"],
|
|
221
|
+
description: "Execute the session-safe Rubik REPL commands published in <agent_commands> of the system prompt " \
|
|
222
|
+
"(runtime configuration, buffer review and rollback, context/step self-management, " \
|
|
223
|
+
"runtime introspection, code validation). Do NOT use this tool for reading files, writing code, or " \
|
|
224
|
+
"running tests. Use direct tools (read_file, write_file, run_tests, run_command) instead.",
|
|
225
|
+
risk: :execute, maestro_blocked: false, # session-safe REPL plane only — file/test/shell work stays delegated
|
|
226
|
+
params_schema: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
command: { type: "string", description: "The full REPL command string to execute (e.g. '!diff', '!context scan', '!undo')" }
|
|
230
|
+
},
|
|
231
|
+
required: ["command"]
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
def initialize(shell = nil)
|
|
235
|
+
super(DECLARATION)
|
|
236
|
+
@shell = shell
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def execute(args = {})
|
|
240
|
+
cmd = (args["command"] || args[:command]).to_s.strip
|
|
241
|
+
return ToolOutput.error("No command provided.") if cmd.empty?
|
|
242
|
+
return ToolOutput.error("REPL shell is not connected.") unless @shell
|
|
243
|
+
|
|
244
|
+
# Format command with leading ! if missing
|
|
245
|
+
cmd = "!#{cmd}" unless cmd.start_with?("!")
|
|
246
|
+
|
|
247
|
+
base_cmd = cmd.split.first.to_s.downcase.delete_prefix("!")
|
|
248
|
+
|
|
249
|
+
if REDIRECT_MAP.key?(base_cmd)
|
|
250
|
+
return ToolOutput.error("Notice: Please use native tool '#{REDIRECT_MAP[base_cmd]}' directly instead of exec_repl_command('#{cmd}'). " \
|
|
251
|
+
"REPL commands are not suitable for autonomous tool invocation for this action.")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if SESSION_CONTROL_COMMANDS.include?(base_cmd)
|
|
255
|
+
return ToolOutput.error("Blocked: '!#{base_cmd}' would terminate or reset the user's interactive session and is reserved " \
|
|
256
|
+
"for the human operator. Continue your task with native tools and call the 'finish' tool when done.")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Prevent recursive loops of plan/goal inside plan/goal; steer back to the loop contract
|
|
260
|
+
if ["plan", "goal"].include?(base_cmd)
|
|
261
|
+
return ToolOutput.error("Nested #{cmd.split.first} invocation inside tool call is blocked. You are already running inside " \
|
|
262
|
+
"the agent loop: continue the current task with native tools and call the 'finish' tool when the goal is complete.")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
meta = @shell.commands[base_cmd]
|
|
266
|
+
if meta.nil?
|
|
267
|
+
return ToolOutput.error("Unknown REPL command '!#{base_cmd}'. Rubik does not have a '!#{base_cmd}' command. " \
|
|
268
|
+
"To explore files, read code, or perform a review, use direct tools " \
|
|
269
|
+
"('list_files', 'read_file', 'search_symbols', 'get_outline') instead of REPL commands.")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Fail-closed allowlist: any audience value outside this set stays unpublished and un-invokable.
|
|
273
|
+
unless [:agent, :both].include?(meta[:audience])
|
|
274
|
+
return ToolOutput.error("Notice: '!#{base_cmd}' is reserved for the human operator (#{meta[:description].to_s.downcase}). " \
|
|
275
|
+
"For file, code, test, and shell operations use your direct tools instead.")
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Caller-source proof for security-sensitive REPL commands (Plan:
|
|
279
|
+
# komut-allowlist Paket D): commands like `!config set security.*`
|
|
280
|
+
# refuse mutations when the caller is the agent, not the human.
|
|
281
|
+
has_setter = @shell.respond_to?(:agent_caller=)
|
|
282
|
+
prev_caller = @shell.respond_to?(:agent_caller) ? @shell.agent_caller : false
|
|
283
|
+
@shell.agent_caller = true if has_setter
|
|
284
|
+
begin
|
|
285
|
+
output_str = @shell.capture_output { @shell.execute_input(cmd) }.strip
|
|
286
|
+
ensure
|
|
287
|
+
@shell.agent_caller = prev_caller if has_setter
|
|
288
|
+
end
|
|
289
|
+
output_str.empty? ? "Command #{cmd} executed with no output." : output_str
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|