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,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../coeffects"
|
|
5
|
+
require_relative "../output"
|
|
6
|
+
require_relative "../diagnostic_reporter"
|
|
7
|
+
require_relative "../fuzzy_matcher"
|
|
8
|
+
|
|
9
|
+
module Rubik
|
|
10
|
+
module Tools
|
|
11
|
+
class SearchSymbolsTool < BaseTool
|
|
12
|
+
# Declared coeffects: hidden from schemas (PENDING) until satisfied (AGENTS.md §2).
|
|
13
|
+
REQUIRES = [Rubik::Coeffects::AST_GRAPH].freeze
|
|
14
|
+
|
|
15
|
+
DECLARATION = Declaration.new(
|
|
16
|
+
name: "search_symbols", aliases: ["find_symbols", "symbols"],
|
|
17
|
+
description: "Search function, class, struct definitions and call hierarchies in the AST symbol graph",
|
|
18
|
+
risk: :read, archetypes: [:research],
|
|
19
|
+
params_schema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
query: { type: "string", description: "Symbol name or partial name to search" },
|
|
23
|
+
smart_context: { type: "boolean", description: "Return full smart context with dependencies (optional)" }
|
|
24
|
+
},
|
|
25
|
+
required: ["query"]
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
def initialize(ast_graph = nil, runtime_context: nil, context: nil)
|
|
29
|
+
super(DECLARATION)
|
|
30
|
+
@ast_graph = ast_graph
|
|
31
|
+
@runtime_context = runtime_context || context
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ast_graph
|
|
35
|
+
Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::AST_GRAPH, @ast_graph)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def execute(args = {})
|
|
39
|
+
query = (args["query"] || args[:query]).to_s.strip
|
|
40
|
+
return ToolOutput.error("Query parameter is required.") if query.empty?
|
|
41
|
+
|
|
42
|
+
graph = ast_graph
|
|
43
|
+
return ToolOutput.error("AST graph is not available. Run '!context scan' to build the symbol index.") unless graph
|
|
44
|
+
|
|
45
|
+
if args["smart_context"] || args[:smart_context]
|
|
46
|
+
ctx = graph.smart_context_for(query)
|
|
47
|
+
return ctx.empty? ? "No smart context found for '#{query}'." : ctx
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
matches = if graph.respond_to?(:search_symbols)
|
|
51
|
+
graph.search_symbols(query)
|
|
52
|
+
else
|
|
53
|
+
graph.symbols.values.select { |sym| sym.name.downcase.include?(query.downcase) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if matches.empty?
|
|
57
|
+
all_sym_names = graph.respond_to?(:symbols) ? graph.symbols.keys : []
|
|
58
|
+
candidates = FuzzyMatcher.best_matches(query, all_sym_names)
|
|
59
|
+
DiagnosticReporter.render_symbol_not_found(query, candidate_symbols: candidates)
|
|
60
|
+
else
|
|
61
|
+
matches.map do |sym|
|
|
62
|
+
"• #{sym.kind.to_s.upcase} #{sym.name} (#{sym.file}:#{sym.line_start}-#{sym.line_end})\n " \
|
|
63
|
+
"Signature: #{sym.signature}\n Calls: #{sym.calls.join(', ')}"
|
|
64
|
+
end.join("\n\n")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class GetOutlineTool < BaseTool
|
|
70
|
+
# Declared coeffects: hidden from schemas (PENDING) until satisfied (AGENTS.md §2).
|
|
71
|
+
REQUIRES = [Rubik::Coeffects::AST_GRAPH].freeze
|
|
72
|
+
|
|
73
|
+
DECLARATION = Declaration.new(
|
|
74
|
+
name: "get_outline", aliases: ["file_outline", "outline"],
|
|
75
|
+
description: "Get high-level semantic outline (classes, methods, functions, and line ranges) " \
|
|
76
|
+
"of a source file from the AST without reading entire file content. " \
|
|
77
|
+
"Accepts 'file_path' (or 'path').",
|
|
78
|
+
risk: :read, archetypes: [:research],
|
|
79
|
+
params_schema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
file_path: { type: "string", description: "Path to source file to inspect" }
|
|
83
|
+
},
|
|
84
|
+
required: ["file_path"]
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
def initialize(ast_graph = nil, runtime_context: nil, context: nil)
|
|
88
|
+
super(DECLARATION)
|
|
89
|
+
@ast_graph = ast_graph
|
|
90
|
+
@runtime_context = runtime_context || context
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def ast_graph
|
|
94
|
+
Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::AST_GRAPH, @ast_graph)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def execute(args = {})
|
|
98
|
+
path = (args["file_path"] || args[:file_path] || args["path"] || args[:path]).to_s.strip
|
|
99
|
+
return ToolOutput.error("File path is required.") if path.empty?
|
|
100
|
+
|
|
101
|
+
graph = ast_graph
|
|
102
|
+
return ToolOutput.error("AST graph is not available. Run '!context scan' to build the symbol index.") unless graph
|
|
103
|
+
|
|
104
|
+
graph.outline_for(path)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class FindReferencesTool < BaseTool
|
|
109
|
+
# Declared coeffects: hidden from schemas (PENDING) until satisfied (AGENTS.md §2).
|
|
110
|
+
REQUIRES = [Rubik::Coeffects::AST_GRAPH].freeze
|
|
111
|
+
|
|
112
|
+
DECLARATION = Declaration.new(
|
|
113
|
+
name: "find_references", aliases: ["references", "get_callers"],
|
|
114
|
+
description: "Find all call sites, callers, and references of a symbol/function/class across the workspace for safe refactoring. " \
|
|
115
|
+
"This is based on the AST symbol graph; for a catch-all text search matching names, use grep_search. " \
|
|
116
|
+
"Accepts 'symbol_name' (or 'symbol').",
|
|
117
|
+
risk: :read, archetypes: [:research],
|
|
118
|
+
params_schema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
symbol_name: { type: "string", description: "Symbol name to find callers/references for" }
|
|
122
|
+
},
|
|
123
|
+
required: ["symbol_name"]
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
def initialize(ast_graph = nil, runtime_context: nil, context: nil)
|
|
127
|
+
super(DECLARATION)
|
|
128
|
+
@ast_graph = ast_graph
|
|
129
|
+
@runtime_context = runtime_context || context
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def ast_graph
|
|
133
|
+
Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::AST_GRAPH, @ast_graph)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def execute(args = {})
|
|
137
|
+
sym_name = (args["symbol_name"] || args[:symbol_name] || args["symbol"] || args[:symbol]).to_s.strip
|
|
138
|
+
return ToolOutput.error("Symbol name is required.") if sym_name.empty?
|
|
139
|
+
|
|
140
|
+
graph = ast_graph
|
|
141
|
+
return ToolOutput.error("AST graph is not available. Run '!context scan' to build the symbol index.") unless graph
|
|
142
|
+
|
|
143
|
+
callers = graph.callers_of(sym_name)
|
|
144
|
+
if callers.empty?
|
|
145
|
+
"No references or callers found for '#{sym_name}' in AST graph."
|
|
146
|
+
else
|
|
147
|
+
lines = ["Found #{callers.size} reference(s) for '#{sym_name}':"]
|
|
148
|
+
callers.each do |c|
|
|
149
|
+
lines << " • #{c.name} (#{c.file}:#{c.line_start}-#{c.line_end}) -> #{c.signature}"
|
|
150
|
+
end
|
|
151
|
+
lines.join("\n")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class GetSymbolTool < BaseTool
|
|
157
|
+
# Declared coeffects: hidden from schemas (PENDING) until satisfied (AGENTS.md §2).
|
|
158
|
+
REQUIRES = [Rubik::Coeffects::AST_GRAPH].freeze
|
|
159
|
+
|
|
160
|
+
DECLARATION = Declaration.new(
|
|
161
|
+
name: "get_symbol", aliases: ["get_definition", "symbol_info"],
|
|
162
|
+
description: "Get isolated definition, signature, docstring, and body of a specific class or method from the AST. " \
|
|
163
|
+
"Accepts 'symbol_name' (or 'symbol').",
|
|
164
|
+
risk: :read, archetypes: [:research],
|
|
165
|
+
params_schema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
symbol_name: { type: "string", description: "Symbol name to retrieve definition for" }
|
|
169
|
+
},
|
|
170
|
+
required: ["symbol_name"]
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
def initialize(ast_graph = nil, runtime_context: nil, context: nil)
|
|
174
|
+
super(DECLARATION)
|
|
175
|
+
@ast_graph = ast_graph
|
|
176
|
+
@runtime_context = runtime_context || context
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def ast_graph
|
|
180
|
+
Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::AST_GRAPH, @ast_graph)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def execute(args = {})
|
|
184
|
+
sym_name = (args["symbol_name"] || args[:symbol_name] || args["symbol"] || args[:symbol]).to_s.strip
|
|
185
|
+
return ToolOutput.error("Symbol name is required.") if sym_name.empty?
|
|
186
|
+
|
|
187
|
+
graph = ast_graph
|
|
188
|
+
return ToolOutput.error("AST graph is not available. Run '!context scan' to build the symbol index.") unless graph
|
|
189
|
+
|
|
190
|
+
info = graph.symbol_definition(sym_name)
|
|
191
|
+
unless info
|
|
192
|
+
all_sym_names = graph.respond_to?(:symbols) ? graph.symbols.keys : []
|
|
193
|
+
candidates = FuzzyMatcher.best_matches(sym_name, all_sym_names)
|
|
194
|
+
return ToolOutput.error(DiagnosticReporter.render_symbol_not_found(sym_name, candidate_symbols: candidates))
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
lines = [
|
|
198
|
+
"Symbol: #{info[:name]} [#{info[:kind].to_s.upcase}]",
|
|
199
|
+
"File: #{info[:file]} (lines #{info[:line_start]}-#{info[:line_end]})",
|
|
200
|
+
"Signature: #{info[:signature]}"
|
|
201
|
+
]
|
|
202
|
+
lines << "Docstring: #{info[:docstring]}" if info[:docstring] && !info[:docstring].empty?
|
|
203
|
+
lines << "Calls: #{info[:calls].join(', ')}" unless info[:calls].empty?
|
|
204
|
+
lines << "\n--- Body ---\n#{info[:body]}" if info[:body] && !info[:body].empty?
|
|
205
|
+
lines.join("\n")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "timeout"
|
|
5
|
+
require_relative "../base"
|
|
6
|
+
require_relative "../output"
|
|
7
|
+
require_relative "../preflight_runner"
|
|
8
|
+
require_relative "../../runtime"
|
|
9
|
+
|
|
10
|
+
module Rubik
|
|
11
|
+
module Tools
|
|
12
|
+
class SynthesizeCommandTool < BaseTool
|
|
13
|
+
DECLARATION = Declaration.new(
|
|
14
|
+
name: "synthesize_command", aliases: ["create_command", "add_command"],
|
|
15
|
+
description: "Dynamically create and register a new Rubik REPL bang command or tool at runtime (session, project, or global scope)",
|
|
16
|
+
risk: :write, maestro_blocked: false, # self-evolution plane (TOOLS.md maestro policy)
|
|
17
|
+
params_schema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
name: { type: "string", description: "Name of the new command without '!' (e.g. 'db_seed', 'benchmark_api')" },
|
|
21
|
+
description: { type: "string", description: "Short description of what the command does for !help" },
|
|
22
|
+
scope: { type: "string", enum: ["session", "project", "global"],
|
|
23
|
+
description: "Storage scope: session (in-memory only), project (.rubik/commands), global (~/.rubik/commands)" },
|
|
24
|
+
code: { type: "string", description: "Ruby handler code: ->(args, ctx) { ... }. For 2-arity, ctx is the OUTPUT VIEW " \
|
|
25
|
+
"(ctx.out / ctx.puts / ctx.print / ctx.flush). For config/services/full shell, " \
|
|
26
|
+
"declare 3-arity ->(args, out, shell) instead." },
|
|
27
|
+
test_args: { type: "object", description: "Optional sample arguments to pre-flight verify the handler before registration (recommended)" }
|
|
28
|
+
},
|
|
29
|
+
required: ["name", "code"]
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
def initialize(shell = nil, overlay_storage = nil, guard = nil, legacy_runtime_manager = nil, runtime_manager: nil)
|
|
33
|
+
super(DECLARATION)
|
|
34
|
+
@shell = shell
|
|
35
|
+
# guard, fallback overlay storage'a da geçilir: bootstrap kabul taraması
|
|
36
|
+
# aynı operatör-policy guard örneğini kullanır (bkz. OverlayStorage).
|
|
37
|
+
@guard = guard || Rubik::Security::Guard.new
|
|
38
|
+
@overlay_storage = overlay_storage || Rubik::Runtime::OverlayStorage.new(guard: @guard)
|
|
39
|
+
@runtime_manager = runtime_manager || legacy_runtime_manager
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def execute(args = {})
|
|
43
|
+
name = (args["name"] || args[:name]).to_s.strip.delete_prefix("!")
|
|
44
|
+
description = (args["description"] || args[:description] || "Custom dynamic command").to_s.strip
|
|
45
|
+
scope = (args["scope"] || args[:scope] || "session").to_s.downcase.to_sym
|
|
46
|
+
code = (args["code"] || args[:code]).to_s.strip
|
|
47
|
+
test_args = args["test_args"] || args[:test_args]
|
|
48
|
+
|
|
49
|
+
return ToolOutput.error("Command name cannot be empty") if name.empty?
|
|
50
|
+
return ToolOutput.error("Handler code cannot be empty") if code.empty?
|
|
51
|
+
|
|
52
|
+
clean_name = name.to_s.downcase.delete_prefix("!")
|
|
53
|
+
previous_meta = nil
|
|
54
|
+
if @shell
|
|
55
|
+
previous_meta = @shell.commands[clean_name]
|
|
56
|
+
return ToolOutput.error("Cannot overwrite reserved built-in command '!#{clean_name}'") if previous_meta && !previous_meta[:custom]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 1. Security & Syntax Verification
|
|
60
|
+
begin
|
|
61
|
+
Rubik::Security::Guard.run_synthesis_gate(@guard, code)
|
|
62
|
+
rescue Rubik::Security::BlockedWriteError => e
|
|
63
|
+
return ToolOutput.error("Security blocked: #{e.message}")
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
return ToolOutput.error("Security blocked: Synthesized command code contains dangerous operations (#{e.message})")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
proc_handler = begin
|
|
69
|
+
eval(code, clean_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- sentez çekirdegi; @guard.verify_synthesis_safety! ön kosulu çalisir
|
|
70
|
+
rescue ScriptError, StandardError => e
|
|
71
|
+
return ToolOutput.error("Syntax error in synthesized code: #{e.message}") # rubocop:disable Lint/NoReturnInBeginEndBlocks -- araç hata akisinda bilinçli erken dönüş
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
unless proc_handler.respond_to?(:call)
|
|
75
|
+
return ToolOutput.error("Code must evaluate to a callable Proc or Lambda " \
|
|
76
|
+
"(e.g. ->(args, ctx) { ... } or ->(args, out, shell) { ... })")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 1b. Optional pre-flight execution in an isolated child process
|
|
80
|
+
# (AGENTS.md §3.4): verifies the handler actually runs before it is
|
|
81
|
+
# persisted or registered. A subprocess guarantees the deadline even
|
|
82
|
+
# for C extensions and blocking syscalls that Timeout.timeout cannot
|
|
83
|
+
# preempt; output goes to a throwaway StringIO inside the child so
|
|
84
|
+
# verification never writes to the user's terminal.
|
|
85
|
+
if test_args
|
|
86
|
+
preflight = PreflightRunner.run(code, test_args, mode: :command, guard: @guard)
|
|
87
|
+
return ToolOutput.error("Pre-flight verification failed for command '!#{name}': #{preflight}") if preflight.is_a?(String)
|
|
88
|
+
|
|
89
|
+
if preflight == :spawn_failed
|
|
90
|
+
sub_ctx = @runtime_manager&.context.is_a?(Rubik::Runtime::Context) ? @runtime_manager.context.extend : nil
|
|
91
|
+
begin
|
|
92
|
+
failure = PreflightRunner.verify_in_process { PreflightRunner.call_handler(proc_handler, test_args, mode: :command, shell: @shell) }
|
|
93
|
+
return ToolOutput.error("Pre-flight verification failed for command '!#{name}': #{failure}") if failure
|
|
94
|
+
ensure
|
|
95
|
+
sub_ctx&.dispose
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 2. Persist to storage if scope is project or global
|
|
101
|
+
if [:project, :global].include?(scope)
|
|
102
|
+
save_res = @overlay_storage.save_command(name, code, scope: scope, description: description)
|
|
103
|
+
return ToolOutput.error("Failed to persist command: #{save_res[:error]}") unless save_res[:ok]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 3. Register live in REPL shell
|
|
107
|
+
if @shell
|
|
108
|
+
@shell.register_command(name, description: "#{description} [#{scope}]", custom: true) do |cmd_args, ctx|
|
|
109
|
+
# Arity contract (mirrored by OverlayStorage#eval_command and
|
|
110
|
+
# PreflightRunner): 1 → args; 2 → the CommandContext (its
|
|
111
|
+
# puts/print/flush delegate to the active capture IO); 3 →
|
|
112
|
+
# (args, out, shell) for trusted code needing the raw shell.
|
|
113
|
+
# Direct invocation outside dispatch (tests, replay) has no
|
|
114
|
+
# ctx: use the shell's factory, or its raw output when held
|
|
115
|
+
# shell doubles predate the CommandContext contract. The warn
|
|
116
|
+
# keeps a dispatch site forgetting ctx visible instead of
|
|
117
|
+
# silently auto-healed.
|
|
118
|
+
out_ctx = if ctx
|
|
119
|
+
ctx
|
|
120
|
+
elsif @shell.respond_to?(:command_context)
|
|
121
|
+
warn "[rubik] custom command invoked without a dispatch CommandContext" unless ENV["RUBIK_TEST"]
|
|
122
|
+
@shell.command_context
|
|
123
|
+
else
|
|
124
|
+
@shell.output
|
|
125
|
+
end
|
|
126
|
+
if proc_handler.arity == 1
|
|
127
|
+
proc_handler.call(cmd_args)
|
|
128
|
+
elsif proc_handler.arity == 2
|
|
129
|
+
proc_handler.call(cmd_args, out_ctx)
|
|
130
|
+
else
|
|
131
|
+
proc_handler.call(cmd_args, out_ctx.respond_to?(:out) ? out_ctx.out : out_ctx, @shell)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Register inverse in Runtime Context. Sound rollback (AGENTS.md §1.A):
|
|
136
|
+
# a hot-updated custom command is restored to its previous handler on
|
|
137
|
+
# dispose instead of leaving a hole in the command table.
|
|
138
|
+
ctx = @runtime_manager&.context || (@shell.respond_to?(:runtime_manager) ? @shell.runtime_manager&.context : nil)
|
|
139
|
+
if ctx.respond_to?(:on_dispose) || ctx.respond_to?(:register_disposable)
|
|
140
|
+
hook = ctx.respond_to?(:on_dispose) ? :on_dispose : :register_disposable
|
|
141
|
+
ctx.send(hook) do
|
|
142
|
+
if previous_meta
|
|
143
|
+
@shell.register_command(clean_name, description: previous_meta[:description],
|
|
144
|
+
custom: previous_meta[:custom], audience: previous_meta[:audience],
|
|
145
|
+
&previous_meta[:handler])
|
|
146
|
+
else
|
|
147
|
+
@shell.unregister_command(name)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
"Successfully synthesized and registered !#{name} [scope: #{scope}]#{' (pre-flight verified)' if test_args}. " \
|
|
154
|
+
"You can now run it using !#{name} or via exec_repl_command('!#{name}')."
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def clean_binding
|
|
160
|
+
Object.new.instance_eval { binding }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class SynthesizeToolTool < BaseTool
|
|
165
|
+
DECLARATION = Declaration.new(
|
|
166
|
+
name: "synthesize_tool", aliases: ["create_tool", "add_tool"],
|
|
167
|
+
description: "Synthesize, verify, and register a new native function calling tool into the agent's live toolbelt " \
|
|
168
|
+
"at runtime (session, project, or global scope)",
|
|
169
|
+
risk: :write, maestro_blocked: false, # self-evolution plane (TOOLS.md maestro policy)
|
|
170
|
+
params_schema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
name: { type: "string", description: "Name of the new tool (e.g. 'calc_stats', 'inspect_ast_node')" },
|
|
174
|
+
description: { type: "string", description: "Clear explanation of what the tool does for model selection" },
|
|
175
|
+
scope: { type: "string", enum: ["session", "project", "global"],
|
|
176
|
+
description: "Storage scope: session (in-memory only), project (.rubik/tools), global (~/.rubik/tools)" },
|
|
177
|
+
risk: { type: "string", enum: ["read", "write", "execute"], default: "execute",
|
|
178
|
+
description: "Risk level of tool (optional; read/write/execute, default: execute)" },
|
|
179
|
+
params_schema: { type: "object", description: "JSON Schema definition of the tool parameters" },
|
|
180
|
+
code: { type: "string", description: "Ruby implementation of tool handler: ->(args) { ... } or class extending Rubik::Tools::BaseTool" },
|
|
181
|
+
test_args: { type: "object", description: "Optional test arguments to perform pre-flight verification in an isolated sandbox" }
|
|
182
|
+
},
|
|
183
|
+
required: ["name", "description", "code"]
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
def initialize(tool_registry = nil, overlay_storage = nil, guard = nil, legacy_runtime_manager = nil, runtime_manager: nil)
|
|
187
|
+
super(DECLARATION)
|
|
188
|
+
@tool_registry = tool_registry
|
|
189
|
+
# guard, fallback overlay storage'a da geçilir: bootstrap kabul taraması
|
|
190
|
+
# aynı operatör-policy guard örneğini kullanır (bkz. OverlayStorage).
|
|
191
|
+
@guard = guard || Rubik::Security::Guard.new
|
|
192
|
+
@overlay_storage = overlay_storage || Rubik::Runtime::OverlayStorage.new(guard: @guard)
|
|
193
|
+
@runtime_manager = runtime_manager || legacy_runtime_manager
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def execute(args = {})
|
|
197
|
+
name = (args["name"] || args[:name]).to_s.strip.downcase.gsub(/[^a-z0-9_]/, "_")
|
|
198
|
+
description = (args["description"] || args[:description] || "Custom native tool").to_s.strip
|
|
199
|
+
scope = (args["scope"] || args[:scope] || "session").to_s.downcase.to_sym
|
|
200
|
+
risk = (args["risk"] || args[:risk] || "execute").to_s.downcase.to_sym
|
|
201
|
+
return ToolOutput.error("Invalid risk '#{risk}' — must be one of: read, write, execute.") unless [:read, :write, :execute].include?(risk)
|
|
202
|
+
|
|
203
|
+
schema = args["params_schema"] || args[:params_schema] || { type: "object", properties: {} }
|
|
204
|
+
code = (args["code"] || args[:code]).to_s.strip
|
|
205
|
+
test_args = args["test_args"] || args[:test_args]
|
|
206
|
+
|
|
207
|
+
return ToolOutput.error("Tool name cannot be empty") if name.empty?
|
|
208
|
+
return ToolOutput.error("Tool code cannot be empty") if code.empty?
|
|
209
|
+
|
|
210
|
+
# Guard against shadowing built-in tools. Registry#register silently
|
|
211
|
+
# overwrites, and the dispose inverse only unregisters — a synthesized
|
|
212
|
+
# "read_file" would leave the session without the real read_file after
|
|
213
|
+
# disposal. Built-ins are rejected here; overlay/MCP/synthesized tools
|
|
214
|
+
# may be hot-updated (previous instance is restored on dispose).
|
|
215
|
+
previous_tool = @tool_registry&.get(name)
|
|
216
|
+
if previous_tool && @tool_registry.origin(name) == :builtin
|
|
217
|
+
msg = "Cannot overwrite built-in tool '#{name}'. Choose a distinct name (e.g. 'my_#{name}')."
|
|
218
|
+
return ToolOutput.error(msg)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# 1. Security & Pre-flight Syntax Verification in isolated Context
|
|
222
|
+
begin
|
|
223
|
+
Rubik::Security::Guard.run_synthesis_gate(@guard, code)
|
|
224
|
+
rescue Rubik::Security::BlockedWriteError => e
|
|
225
|
+
return ToolOutput.error("Security blocked: #{e.message}")
|
|
226
|
+
rescue StandardError => e
|
|
227
|
+
return ToolOutput.error("Security blocked: Synthesized tool code contains dangerous operations (#{e.message})")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
tool_instance = nil
|
|
231
|
+
begin
|
|
232
|
+
evaluated = eval(code, clean_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- sentez çekirdegi
|
|
233
|
+
tool_instance = if evaluated.is_a?(Class) && evaluated < Rubik::Tools::BaseTool
|
|
234
|
+
evaluated.new
|
|
235
|
+
elsif evaluated.respond_to?(:call)
|
|
236
|
+
Class.new(Rubik::Tools::BaseTool) do
|
|
237
|
+
define_method(:initialize) do
|
|
238
|
+
super(name: name, description: "#{description} [#{scope}]", risk: risk, params_schema: schema)
|
|
239
|
+
@handler = evaluated
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
define_method(:execute) do |exec_args = {}|
|
|
243
|
+
@handler.call(exec_args)
|
|
244
|
+
end
|
|
245
|
+
end.new
|
|
246
|
+
else
|
|
247
|
+
return ToolOutput.error("Tool code must evaluate to a callable Proc (->(args) { ... }) or a BaseTool subclass.")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
effective_test_args = test_args || {}
|
|
251
|
+
preflight = PreflightRunner.run(code, effective_test_args, mode: :tool, guard: @guard)
|
|
252
|
+
return ToolOutput.error("Pre-flight verification failed for tool '#{name}': #{preflight}") if preflight.is_a?(String)
|
|
253
|
+
|
|
254
|
+
if preflight == :spawn_failed
|
|
255
|
+
# Fallback: in-process Timeout (lambda form via the shared
|
|
256
|
+
# dispatch; BaseTool-subclass form needs the full runtime and
|
|
257
|
+
# always verifies in-process).
|
|
258
|
+
sub_ctx = @runtime_manager&.context.respond_to?(:extend) ? @runtime_manager.context.extend : nil
|
|
259
|
+
begin
|
|
260
|
+
failure = if evaluated.respond_to?(:call)
|
|
261
|
+
PreflightRunner.verify_in_process { PreflightRunner.call_handler(evaluated, effective_test_args, mode: :tool) }
|
|
262
|
+
else
|
|
263
|
+
PreflightRunner.verify_in_process { tool_instance.execute(effective_test_args) }
|
|
264
|
+
end
|
|
265
|
+
return ToolOutput.error("Pre-flight verification failed for tool '#{name}': #{failure}") if failure
|
|
266
|
+
ensure
|
|
267
|
+
sub_ctx&.dispose! if sub_ctx.respond_to?(:dispose!)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
rescue ScriptError, StandardError => e
|
|
271
|
+
return ToolOutput.error("Pre-flight verification failed for tool '#{name}': #{e.message}")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# 2. Persist to storage if scope is project or global
|
|
275
|
+
if [:project, :global].include?(scope)
|
|
276
|
+
save_res = @overlay_storage.save_tool(name, code, scope: scope, description: description, params_schema: schema, risk: risk)
|
|
277
|
+
return ToolOutput.error("Failed to persist tool: #{save_res[:error]}") unless save_res[:ok]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# 3. Register live into active tool registry
|
|
281
|
+
if @tool_registry
|
|
282
|
+
previous_origin = previous_tool ? @tool_registry.origin(name) : nil
|
|
283
|
+
@tool_registry.register(tool_instance, origin: :synthesis)
|
|
284
|
+
|
|
285
|
+
# Register inverse in Runtime Context. When hot-updating an existing
|
|
286
|
+
# overlay/MCP/synthesized tool, the inverse restores the previous
|
|
287
|
+
# instance instead of leaving a hole in the toolbelt.
|
|
288
|
+
ctx = @runtime_manager&.context
|
|
289
|
+
ctx&.on_dispose do
|
|
290
|
+
if previous_tool && @tool_registry.respond_to?(:swap)
|
|
291
|
+
@tool_registry.swap(name, previous_tool, origin: previous_origin || :overlay)
|
|
292
|
+
else
|
|
293
|
+
@tool_registry.unregister(name)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
verification_note = if test_args
|
|
299
|
+
"Pre-flight verified with provided test_args in an isolated sub-context."
|
|
300
|
+
else
|
|
301
|
+
"Pre-flight verified with default arguments in an isolated sub-context."
|
|
302
|
+
end
|
|
303
|
+
"🎉 Successfully synthesized and registered native tool '#{name}' [scope: #{scope}]. " \
|
|
304
|
+
"#{verification_note} It is now immediately available in your active toolbelt for function calling."
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
def clean_binding
|
|
310
|
+
Object.new.instance_eval { binding }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|