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