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,354 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../coeffects"
|
|
5
|
+
require_relative "../uri_resolver"
|
|
6
|
+
require_relative "../output"
|
|
7
|
+
require_relative "../diagnostic_reporter"
|
|
8
|
+
require_relative "../fuzzy_matcher"
|
|
9
|
+
require_relative "../../buffer/path_validator"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module Tools
|
|
13
|
+
class ReadFileTool < BaseTool
|
|
14
|
+
DECLARATION = Declaration.new(
|
|
15
|
+
name: "read_file", aliases: ["view_file", "read"],
|
|
16
|
+
description: "Read content of a file from buffer or disk with 1-based line numbers (optional 1-based offset and line limit for paging)",
|
|
17
|
+
risk: :read, archetypes: [:research, :coder],
|
|
18
|
+
params_schema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
path: { type: "string", description: "File path, or virtual rubik:// URI: 'rubik://storage/<project>/<type>/<id>' " \
|
|
22
|
+
"(browse from 'rubik://storage/index'), 'rubik://goal/state', 'rubik://subagent/<id>/transcript'" },
|
|
23
|
+
offset: { type: "integer", description: "1-based line number to start reading from (optional)" },
|
|
24
|
+
limit: { type: "integer", description: "Maximum number of lines to read (optional)" },
|
|
25
|
+
from_disk: { type: "boolean", description: "Read current disk content without modifying in-memory buffer" },
|
|
26
|
+
reload: { type: "boolean", description: "Discard dirty buffer and reload from disk (resets pending changes)" }
|
|
27
|
+
},
|
|
28
|
+
required: ["path"]
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
def initialize(buffer_manager, runtime_context: nil)
|
|
32
|
+
super(DECLARATION)
|
|
33
|
+
@buffer_manager = buffer_manager
|
|
34
|
+
@runtime_context = runtime_context
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def execute(args = {})
|
|
38
|
+
path = (args["path"] || args[:path]).to_s.strip
|
|
39
|
+
# rubik:// sevk tek yerde: UriResolver (bilinmeyen şema/gramer reddi
|
|
40
|
+
# nil döndürür ve buffer düzlemine düşer — bugünkü düşüş sözleşmesi).
|
|
41
|
+
virt = UriResolver.resolve(path, ctx: @runtime_context, buffer_manager: @buffer_manager)
|
|
42
|
+
return virt unless virt.nil?
|
|
43
|
+
|
|
44
|
+
if path.start_with?("file://")
|
|
45
|
+
clean = path.sub(%r{\Afile://}, "")
|
|
46
|
+
path = clean.sub(%r{\A/([A-Za-z]:)}, '\1')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
content = if args["reload"] == true || args[:reload] == true
|
|
50
|
+
@buffer_manager.read(path, reload: true)
|
|
51
|
+
elsif args["from_disk"] == true || args[:from_disk] == true
|
|
52
|
+
@buffer_manager.read(path, from_disk: true)
|
|
53
|
+
else
|
|
54
|
+
@buffer_manager.read(path)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
unless content
|
|
58
|
+
known_files = @buffer_manager.respond_to?(:all_files) ? @buffer_manager.all_files : []
|
|
59
|
+
candidates = FuzzyMatcher.best_matches(File.basename(path), known_files.map { |f| File.basename(f) })
|
|
60
|
+
return ToolOutput.error(DiagnosticReporter.render_file_not_found(path, candidates: candidates))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
offset = (args["offset"] || args[:offset]).to_i
|
|
64
|
+
limit = (args["limit"] || args[:limit]).to_i
|
|
65
|
+
lines = content.lines
|
|
66
|
+
return "[empty file (0 lines)]" if lines.empty?
|
|
67
|
+
|
|
68
|
+
start_idx = [offset, 1].max - 1
|
|
69
|
+
slice = limit.positive? ? (lines[start_idx, limit] || []) : (lines[start_idx..] || [])
|
|
70
|
+
return ToolOutput.error("No content at offset #{offset} (file has #{lines.length} lines).") if slice.empty?
|
|
71
|
+
|
|
72
|
+
numbered = slice.each_with_index.map { |line, idx| "#{start_idx + idx + 1}: #{line}" }.join
|
|
73
|
+
offset > 1 || limit.positive? ? "[lines #{start_idx + 1}-#{start_idx + slice.length} of #{lines.length} total]\n#{numbered}" : numbered
|
|
74
|
+
rescue Errno::EACCES => e
|
|
75
|
+
ToolOutput.error(DiagnosticReporter.render_permission_denied(path, operation: "read", reason: e.message))
|
|
76
|
+
rescue Rubik::Buffer::InvalidPathError
|
|
77
|
+
ToolOutput.error(DiagnosticReporter.render_traversal_blocked(path))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class WriteFileTool < BaseTool
|
|
82
|
+
DECLARATION = Declaration.new(
|
|
83
|
+
name: "write_file", aliases: ["create_file", "save_file", "overwrite_file"],
|
|
84
|
+
description: "Write full content to the in-memory buffer (does not write to disk until apply_buffer is called). " \
|
|
85
|
+
"For surgical edits to existing files, use replace_file_content instead. " \
|
|
86
|
+
"Use apply_buffer to persist the buffer to disk before running tests or tests will not see your changes.",
|
|
87
|
+
risk: :write, archetypes: [:coder], write_grant: true, maestro_blocked: true,
|
|
88
|
+
params_schema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
path: { type: "string", description: "File path relative to the project root (e.g. 'lib/app.rb', 'Gemfile')" },
|
|
92
|
+
content: { type: "string", description: "Full file content to write (overwrites entire file)" }
|
|
93
|
+
},
|
|
94
|
+
required: ["path", "content"]
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
def initialize(buffer_manager)
|
|
98
|
+
super(DECLARATION)
|
|
99
|
+
@buffer_manager = buffer_manager
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def execute(args = {})
|
|
103
|
+
path = (args["path"] || args[:path]).to_s.strip
|
|
104
|
+
content = args["content"] || args[:content]
|
|
105
|
+
|
|
106
|
+
return ToolOutput.error("Error: `path` parameter is required.") if path.empty?
|
|
107
|
+
unless Rubik::Buffer::PathValidator.valid?(path)
|
|
108
|
+
return ToolOutput.error("Error: Invalid file path '#{path}'. Path must be a valid file path " \
|
|
109
|
+
"relative to the project root (e.g. 'lib/app.rb', 'src/main.go', 'Gemfile').")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
# NB: reset_baseline defaults to false on purpose. The buffer only
|
|
114
|
+
# replaces the tracked baseline for files that have no baseline yet
|
|
115
|
+
# (new files). Passing reset_baseline: true here would overwrite the
|
|
116
|
+
# original baseline of an ALREADY-loaded file, defeating undo and
|
|
117
|
+
# conflict restoration — the buffer could never restore the file's
|
|
118
|
+
# true original state after a full overwrite.
|
|
119
|
+
@buffer_manager.write(path, content)
|
|
120
|
+
rescue ArgumentError
|
|
121
|
+
@buffer_manager.write(path, content)
|
|
122
|
+
end
|
|
123
|
+
"Successfully wrote to in-memory buffer: #{path}"
|
|
124
|
+
rescue Errno::EACCES => e
|
|
125
|
+
ToolOutput.error(DiagnosticReporter.render_permission_denied(path, operation: "write", reason: e.message))
|
|
126
|
+
rescue Rubik::Buffer::InvalidPathError
|
|
127
|
+
ToolOutput.error(DiagnosticReporter.render_traversal_blocked(path))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class ListFilesTool < BaseTool
|
|
132
|
+
# Declared coeffects: hidden from schemas (PENDING) until satisfied (AGENTS.md §2).
|
|
133
|
+
REQUIRES = [Rubik::Coeffects::CONTEXT_INDEX].freeze
|
|
134
|
+
|
|
135
|
+
DECLARATION = Declaration.new(
|
|
136
|
+
name: "list_files", aliases: ["list_directory", "list_dir", "ls"],
|
|
137
|
+
description: "List project files matching an optional path prefix or extension filter. " \
|
|
138
|
+
"Returns at most 50 files; narrow the query with 'path' and 'extension' for larger trees.",
|
|
139
|
+
risk: :read, archetypes: [:research, :coder],
|
|
140
|
+
params_schema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
path: { type: "string",
|
|
144
|
+
description: "Subdirectory or path prefix relative to the project root (e.g. 'lib', 'src'). " \
|
|
145
|
+
"Absolute paths are converted automatically. (optional)" },
|
|
146
|
+
extension: { type: "string", description: "File extension filter, e.g. .rb, .go, .zig (optional)" }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
def initialize(context_index = nil, root_dir: Dir.pwd, runtime_context: nil, context: nil)
|
|
151
|
+
super(DECLARATION)
|
|
152
|
+
@context_index = context_index
|
|
153
|
+
@runtime_context = runtime_context || context
|
|
154
|
+
@root_dir = File.expand_path(root_dir)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def context_index
|
|
158
|
+
Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::CONTEXT_INDEX, @context_index)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def execute(args = {})
|
|
162
|
+
path_filter = args["path"] || args[:path]
|
|
163
|
+
ext_filter = args["extension"] || args[:extension]
|
|
164
|
+
idx = context_index
|
|
165
|
+
return ToolOutput.error("Context index is not available. Run '!context scan' or restart the session to build it.") unless idx
|
|
166
|
+
|
|
167
|
+
filter, error = BaseTool.normalize_path_filter(path_filter, @root_dir)
|
|
168
|
+
if error
|
|
169
|
+
allowed = [{ path: @root_dir, primary: true }]
|
|
170
|
+
idx.additional_roots.each { |r| allowed << { path: r, primary: false } } if idx.respond_to?(:additional_roots)
|
|
171
|
+
return ToolOutput.error(DiagnosticReporter.render_traversal_blocked(path_filter, allowed_roots: allowed))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
files = idx.files.empty? ? idx.full_scan : idx.files
|
|
175
|
+
# Path-filtered list drives the B9 explanation: an extension miss in
|
|
176
|
+
# an INDEXED directory must read "no such files here", not "ignored".
|
|
177
|
+
path_filtered = filter.empty? ? files.keys : files.keys.select { |f| f.start_with?(filter) }
|
|
178
|
+
file_list = ext_filter && !ext_filter.empty? ? path_filtered.select { |f| f.end_with?(ext_filter) } : path_filtered
|
|
179
|
+
|
|
180
|
+
return ignored_directory_message(filter, ext_filter) if path_filtered.empty? && existing_but_unindexed?(@root_dir, filter)
|
|
181
|
+
|
|
182
|
+
return BaseTool.no_match_message(filter) if file_list.empty?
|
|
183
|
+
|
|
184
|
+
"Found #{file_list.size} file(s):\n#{file_list.first(50).map { |f| " - #{f}" }.join("\n")}"
|
|
185
|
+
rescue Errno::EACCES => e
|
|
186
|
+
ToolOutput.error(DiagnosticReporter.render_permission_denied(path_filter || @root_dir, operation: "read", reason: e.message))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
# Session review B9: `list_files(path: 'lib')` answered "No matching
|
|
192
|
+
# files" for a directory that exists — the context index prunes ignore
|
|
193
|
+
# rules (.gitignore, DEFAULT_IGNORES) silently. Say so and hand the
|
|
194
|
+
# escape hatch instead of letting the model doubt the directory.
|
|
195
|
+
def existing_but_unindexed?(root_dir, filter)
|
|
196
|
+
return false if filter.to_s.empty?
|
|
197
|
+
|
|
198
|
+
File.directory?(File.join(root_dir, filter.to_s))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def ignored_directory_message(filter, ext_filter)
|
|
202
|
+
ext_note = ext_filter && !ext_filter.to_s.empty? ? " matching '#{ext_filter}'" : ""
|
|
203
|
+
"No indexed files#{ext_note} under '#{filter}', but the directory EXISTS on disk — its contents are " \
|
|
204
|
+
"excluded by ignore rules (.gitignore / default excludes) and never entered the context index. " \
|
|
205
|
+
"Browse it with run_command (e.g. `ls -la #{filter}`) or read files directly by absolute path."
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Surgical, targeted chunk replacement tool for frontier coding models (Plan 56).
|
|
210
|
+
class ReplaceFileContentTool < BaseTool
|
|
211
|
+
DECLARATION = Declaration.new(
|
|
212
|
+
name: "replace_file_content", aliases: ["edit_file", "edit_chunk", "str_replace", "patch_file"],
|
|
213
|
+
description: "Replace an exact chunk of code within an existing file. Requires exact verbatim matching of target_content " \
|
|
214
|
+
"(including whitespace and indentation). Optional start_line and end_line narrow the search range.",
|
|
215
|
+
risk: :write, archetypes: [:coder], write_grant: true, maestro_blocked: true,
|
|
216
|
+
params_schema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
path: { type: "string", description: "Path to the file to edit" },
|
|
220
|
+
target_content: { type: "string", description: "The exact contiguous block of code/text to replace (must match verbatim)" },
|
|
221
|
+
replacement_content: { type: "string", description: "The new replacement text to insert in place of target_content" },
|
|
222
|
+
start_line: { type: "integer", description: "1-based starting line number to restrict search scope (optional)" },
|
|
223
|
+
end_line: { type: "integer", description: "1-based ending line number to restrict search scope (optional)" },
|
|
224
|
+
allow_multiple: { type: "boolean",
|
|
225
|
+
description: "If true, replaces all occurrences. " \
|
|
226
|
+
"If false, fails safely when multiple matches exist (optional, default: false)" }
|
|
227
|
+
},
|
|
228
|
+
required: ["path", "target_content", "replacement_content"]
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
def initialize(buffer_manager)
|
|
232
|
+
super(DECLARATION)
|
|
233
|
+
@buffer_manager = buffer_manager
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def execute(args = {})
|
|
237
|
+
path = (args["path"] || args[:path]).to_s.strip
|
|
238
|
+
target = args["target_content"] || args[:target_content] ||
|
|
239
|
+
args["old_content"] || args[:old_content] ||
|
|
240
|
+
args["old_str"] || args[:old_str] ||
|
|
241
|
+
args["target"] || args[:target]
|
|
242
|
+
replacement = args["replacement_content"] || args[:replacement_content] ||
|
|
243
|
+
args["new_content"] || args[:new_content] ||
|
|
244
|
+
args["new_str"] || args[:new_str] ||
|
|
245
|
+
args["replacement"] || args[:replacement]
|
|
246
|
+
start_line = args["start_line"] || args[:start_line]
|
|
247
|
+
end_line = args["end_line"] || args[:end_line]
|
|
248
|
+
allow_multiple = args["allow_multiple"] == true || args[:allow_multiple] == true
|
|
249
|
+
|
|
250
|
+
return ToolOutput.error("Error: `path` parameter is required.") if path.empty?
|
|
251
|
+
return ToolOutput.error("Error: `target_content` parameter is required.") if target.nil?
|
|
252
|
+
return ToolOutput.error("Error: `target_content` cannot be empty.") if target.to_s.empty?
|
|
253
|
+
return ToolOutput.error("Error: `replacement_content` parameter is required.") if replacement.nil?
|
|
254
|
+
|
|
255
|
+
content = @buffer_manager.read(path)
|
|
256
|
+
unless content
|
|
257
|
+
known_files = (@buffer_manager.respond_to?(:all_files) ? @buffer_manager.all_files : [])
|
|
258
|
+
candidates = FuzzyMatcher.best_matches(File.basename(path), known_files.map { |f| File.basename(f) })
|
|
259
|
+
return ToolOutput.error(DiagnosticReporter.render_file_not_found(path, candidates: candidates))
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
has_crlf = content.to_s.include?("\r\n")
|
|
263
|
+
norm_file = content.to_s.gsub("\r\n", "\n")
|
|
264
|
+
norm_target = target.to_s.gsub("\r\n", "\n")
|
|
265
|
+
norm_replacement = replacement.to_s.gsub("\r\n", "\n")
|
|
266
|
+
|
|
267
|
+
if start_line || end_line
|
|
268
|
+
perform_scoped_replace(path, norm_file, norm_target, norm_replacement, start_line, end_line, allow_multiple, has_crlf)
|
|
269
|
+
else
|
|
270
|
+
perform_full_replace(path, norm_file, norm_target, norm_replacement, allow_multiple, has_crlf)
|
|
271
|
+
end
|
|
272
|
+
rescue Errno::EACCES => e
|
|
273
|
+
ToolOutput.error(DiagnosticReporter.render_permission_denied(path, operation: "write", reason: e.message))
|
|
274
|
+
rescue Rubik::Buffer::InvalidPathError
|
|
275
|
+
ToolOutput.error(DiagnosticReporter.render_traversal_blocked(path))
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
def perform_scoped_replace(path, norm_file, norm_target, norm_replacement, start_line, end_line, allow_multiple, has_crlf)
|
|
281
|
+
s_line = [start_line.to_i, 1].max
|
|
282
|
+
e_line = end_line ? end_line.to_i : norm_file.lines.size
|
|
283
|
+
s_line, e_line = e_line, s_line if s_line > e_line
|
|
284
|
+
|
|
285
|
+
lines = norm_file.lines
|
|
286
|
+
slice_start_idx = s_line - 1
|
|
287
|
+
slice_len = [e_line - s_line + 1, 0].max
|
|
288
|
+
slice_text = lines[slice_start_idx, slice_len]&.join || ""
|
|
289
|
+
match_count = slice_text.scan(norm_target).size
|
|
290
|
+
|
|
291
|
+
if match_count.zero?
|
|
292
|
+
closest = FuzzyMatcher.find_closest_subsequence(norm_target, slice_text)
|
|
293
|
+
adjusted_range = closest ? ((s_line + closest[:range].begin - 1)..(s_line + closest[:range].end - 1)) : nil
|
|
294
|
+
return ToolOutput.error(DiagnosticReporter.render_target_not_found(
|
|
295
|
+
path, norm_target, closest_snippet: closest&.fetch(:snippet, nil),
|
|
296
|
+
closest_range: adjusted_range, similarity: closest&.fetch(:similarity, nil),
|
|
297
|
+
start_line: s_line, end_line: e_line
|
|
298
|
+
))
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
if match_count > 1 && !allow_multiple
|
|
302
|
+
occurrences = find_line_occurrences(slice_text, norm_target, line_offset: s_line)
|
|
303
|
+
return ToolOutput.error(DiagnosticReporter.render_target_not_unique(path, norm_target, match_count, occurrences))
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
new_slice = allow_multiple ? slice_text.gsub(norm_target) { norm_replacement } : slice_text.sub(norm_target) { norm_replacement }
|
|
307
|
+
pre_text = lines[0...slice_start_idx]&.join || ""
|
|
308
|
+
post_text = lines[(slice_start_idx + slice_len)..]&.join || ""
|
|
309
|
+
final_content = "#{pre_text}#{new_slice}#{post_text}"
|
|
310
|
+
final_content = final_content.gsub("\n", "\r\n") if has_crlf && !final_content.include?("\r\n")
|
|
311
|
+
|
|
312
|
+
@buffer_manager.write(path, final_content)
|
|
313
|
+
"Successfully replaced content in in-memory buffer: #{path} (scoped to lines #{s_line}..#{e_line})"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def perform_full_replace(path, norm_file, norm_target, norm_replacement, allow_multiple, has_crlf)
|
|
317
|
+
match_count = norm_file.scan(norm_target).size
|
|
318
|
+
|
|
319
|
+
if match_count.zero?
|
|
320
|
+
closest = FuzzyMatcher.find_closest_subsequence(norm_target, norm_file)
|
|
321
|
+
return ToolOutput.error(DiagnosticReporter.render_target_not_found(
|
|
322
|
+
path, norm_target, closest_snippet: closest&.fetch(:snippet, nil),
|
|
323
|
+
closest_range: closest&.fetch(:range, nil), similarity: closest&.fetch(:similarity, nil)
|
|
324
|
+
))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
if match_count > 1 && !allow_multiple
|
|
328
|
+
occurrences = find_line_occurrences(norm_file, norm_target, line_offset: 1)
|
|
329
|
+
return ToolOutput.error(DiagnosticReporter.render_target_not_unique(path, norm_target, match_count, occurrences))
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
final_content = allow_multiple ? norm_file.gsub(norm_target) { norm_replacement } : norm_file.sub(norm_target) { norm_replacement }
|
|
333
|
+
first_occ_idx = norm_file.index(norm_target)
|
|
334
|
+
first_occ_line = first_occ_idx ? (norm_file[0...first_occ_idx].count("\n") + 1) : 1
|
|
335
|
+
rep_lines_size = norm_replacement.lines.size
|
|
336
|
+
end_mod_line = [first_occ_line + rep_lines_size - 1, first_occ_line].max
|
|
337
|
+
|
|
338
|
+
final_content = final_content.gsub("\n", "\r\n") if has_crlf && !final_content.include?("\r\n")
|
|
339
|
+
@buffer_manager.write(path, final_content)
|
|
340
|
+
"Successfully replaced content in in-memory buffer: #{path} (modified lines #{first_occ_line}..#{end_mod_line})"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def find_line_occurrences(text, target, line_offset: 1)
|
|
344
|
+
occurrences = []
|
|
345
|
+
current_pos = 0
|
|
346
|
+
while (idx = text.index(target, current_pos))
|
|
347
|
+
occurrences << (text[0...idx].count("\n") + line_offset)
|
|
348
|
+
current_pos = idx + target.length
|
|
349
|
+
end
|
|
350
|
+
occurrences.uniq
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
# Talep düzlemi (goal-ledger planı A): !goal döngüsünün yapılandırılmış
|
|
10
|
+
# ΔΣ defterinin masası. Pruner/vakanüvis emsali REQUIRES-görünürlük:
|
|
11
|
+
# araç HER ZAMAN kayıtlıdır, model yüzünde yalnız GOAL_LEDGER coeffect'i
|
|
12
|
+
# (çalışan !goal + knob ON) tatmininde görünür; Registry#get yolu ise
|
|
13
|
+
# her zaman açık olduğundan kapalıyken dürüst hata verir. Ret
|
|
14
|
+
# geri bildirimi C Validator'ın message+hint çiftinin birebir
|
|
15
|
+
# yankısıdır — bounded-retry protokolünün model-bakan ucu.
|
|
16
|
+
class UpdateGoalLedgerTool < BaseTool
|
|
17
|
+
REQUIRES = [Rubik::Coeffects::GOAL_LEDGER].freeze
|
|
18
|
+
|
|
19
|
+
DECLARATION = Declaration.new(
|
|
20
|
+
name: "update_goal_ledger", aliases: ["ledger"],
|
|
21
|
+
description: "Update the structured goal ledger of the RUNNING !goal loop with a " \
|
|
22
|
+
"JSON patch ({\"rev\": N, \"ops\": [{\"op\": ..., \"path\": ..., \"value\": ...}]}). " \
|
|
23
|
+
"Read the current ledger via read_file(path: 'rubik://goal/ledger') and issue the " \
|
|
24
|
+
"patch against the rev it reports. Rejections carry per-op fix hints; after the " \
|
|
25
|
+
"retry budget the ledger stays unchanged — read it and continue. A task status of " \
|
|
26
|
+
"`stale` means the harness re-checked that entry's verify predicate against the " \
|
|
27
|
+
"filesystem and it FAILED (the world drifted): re-inspect reality, then update the " \
|
|
28
|
+
"entry (including status) with a normal patch — a stale rev also means the ledger " \
|
|
29
|
+
"moved under you.",
|
|
30
|
+
risk: :write, archetypes: [], write_grant: false,
|
|
31
|
+
maestro_blocked: false, # vakanüvis/pruner emsali: kök düzlem aracı
|
|
32
|
+
params_schema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
patch: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "JSON patch text ({\"rev\": N, \"ops\": [...]}) — rev must match the ledger's current rev."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
required: ["patch"]
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def initialize(runtime_context: nil)
|
|
45
|
+
super(DECLARATION)
|
|
46
|
+
@runtime_context = runtime_context
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def execute(args = {})
|
|
50
|
+
patch_text = args["patch"] || args[:patch]
|
|
51
|
+
unless patch_text.is_a?(String) && !patch_text.strip.empty?
|
|
52
|
+
return ToolOutput.error("update_goal_ledger: the 'patch' parameter is required — a JSON string " \
|
|
53
|
+
"{\"rev\": N, \"ops\": [...]}.")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
ledger = Rubik::Coeffects.resolve(@runtime_context, Rubik::Coeffects::GOAL_LEDGER, nil)
|
|
57
|
+
unless ledger
|
|
58
|
+
return ToolOutput.error("Goal ledger is not available: no running !goal loop (or goal.ledger.enabled=false). " \
|
|
59
|
+
"The ledger exists only while a goal loop runs; update it after each meaningful step.")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
render_result(ledger.update(patch_text))
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
ToolOutput.error("update_goal_ledger failed: #{e.class}: #{e.message} — the ledger is unchanged; " \
|
|
65
|
+
"read rubik://goal/ledger and retry.")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def render_result(result)
|
|
71
|
+
case result.status
|
|
72
|
+
when :applied
|
|
73
|
+
row = %(<goal_ledger_applied rev="#{result.rev}" uri="#{result.uri}" />)
|
|
74
|
+
ToolOutput.ok(result.uri ? row : %(<goal_ledger_applied rev="#{result.rev}" storage="unavailable (memory-only)" />))
|
|
75
|
+
when :exhausted
|
|
76
|
+
ToolOutput.error("patch budget exhausted (consecutive rejections); the ledger is unchanged at rev " \
|
|
77
|
+
"#{result.rev}. Goal work may continue — read rubik://goal/ledger when you next " \
|
|
78
|
+
"update state, and issue a valid patch against that rev.")
|
|
79
|
+
else
|
|
80
|
+
lines = result.reasons.map do |reason|
|
|
81
|
+
at = reason[:op_index].nil? ? "" : " (op #{reason[:op_index]})"
|
|
82
|
+
"[#{reason[:class]}]#{at} #{reason[:path]}: #{reason[:message]} — #{reason[:hint]}"
|
|
83
|
+
end
|
|
84
|
+
ToolOutput.error("ledger patch rejected (rev still #{result.rev}):\n#{lines.join("\n")}")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|