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,359 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../output"
|
|
5
|
+
require_relative "../../runtime"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Tools
|
|
9
|
+
class ApplyBufferTool < BaseTool
|
|
10
|
+
# maestro_blocked: false is DECLARED policy, not drift (TOOLS.md maestro
|
|
11
|
+
# paragraph): apply_buffer is the manual-mode flush of a coder branch's
|
|
12
|
+
# merge — the root orchestrator persists it directly, and with the gate
|
|
13
|
+
# off the root edits-and-flushes itself.
|
|
14
|
+
DECLARATION = Declaration.new(
|
|
15
|
+
name: "apply_buffer", aliases: ["save_buffer", "commit_changes", "save"],
|
|
16
|
+
description: "Persist all pending in-memory file buffer modifications to disk immediately (skips files with external conflicts unless force: true)",
|
|
17
|
+
risk: :write, archetypes: [:coder], write_grant: true, maestro_blocked: false,
|
|
18
|
+
params_schema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
reason: { type: "string", description: "Optional explanation of why buffer is being applied (e.g. 'prior to running tests')" },
|
|
22
|
+
force: { type: "boolean",
|
|
23
|
+
description: "If true, overwrites disk files even if modified externally by formatters (resolves [CONFLICT])" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
def initialize(buffer_manager = nil, shell = nil)
|
|
28
|
+
super(DECLARATION)
|
|
29
|
+
@buffer_manager = buffer_manager
|
|
30
|
+
@shell = shell
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute(args = {})
|
|
34
|
+
return ToolOutput.error("Buffer manager is not connected.") unless @buffer_manager
|
|
35
|
+
|
|
36
|
+
if @shell&.config && @shell.config.fetch("buffer.model_can_apply", true) == false
|
|
37
|
+
return ToolOutput.error("Model buffer application is disabled by configuration " \
|
|
38
|
+
"(buffer.model_can_apply: false). The human operator will review and apply changes.")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
force = [true, "true"].include?(args["force"]) || [true, "true"].include?(args[:force]) ||
|
|
42
|
+
[true, "true"].include?(args["overwrite"]) || [true, "true"].include?(args[:overwrite])
|
|
43
|
+
policy = force ? "overwrite" : nil
|
|
44
|
+
|
|
45
|
+
if @buffer_manager.dirty?
|
|
46
|
+
applied = @buffer_manager.apply!(policy: policy)
|
|
47
|
+
conflicts = @buffer_manager.last_conflicts || []
|
|
48
|
+
@shell&.sync_context_and_ast(applied) unless applied.empty?
|
|
49
|
+
|
|
50
|
+
messages = []
|
|
51
|
+
messages << "Successfully applied #{applied.size} modified file(s) to disk: #{applied.join(', ')}" unless applied.empty?
|
|
52
|
+
conflicts.each do |c|
|
|
53
|
+
messages << "[CONFLICT] #{c}: disk content changed since last read — skipped to prevent data loss.\n" \
|
|
54
|
+
"To force overwrite with your buffer changes, call: apply_buffer(force: true)\n" \
|
|
55
|
+
"Call read_file(path: \"#{c}\", reload: true) to inspect external changes or resolve."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if applied.empty? && !conflicts.empty?
|
|
59
|
+
ToolOutput.error(messages.join("\n"))
|
|
60
|
+
elsif applied.empty?
|
|
61
|
+
# Inside the dirty? branch with zero applies and zero conflicts,
|
|
62
|
+
# every buffered entry was discarded as an invalid path
|
|
63
|
+
# (apply!'s garbage-skip) — the edits are NOT on disk, and citing
|
|
64
|
+
# an earlier apply here would be a lie.
|
|
65
|
+
"No unapplied buffer changes to write: the buffered entries were discarded as invalid paths and are NOT on disk. " \
|
|
66
|
+
"Re-check the target paths and buffer them again."
|
|
67
|
+
else
|
|
68
|
+
messages.join("\n")
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
clean_buffer_message
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# "Buffer is clean" alone reads as "nothing happened" when an earlier
|
|
78
|
+
# apply (apply_buffer itself or the auto-apply policy) already flushed
|
|
79
|
+
# the edits to disk. Surface the last successful apply so the model can
|
|
80
|
+
# verify against reality instead of re-editing or force-applying
|
|
81
|
+
# (issue #97 finding 6). When the mode cannot be read, the origin stays
|
|
82
|
+
# deliberately two-sided rather than inventing a specific cause.
|
|
83
|
+
def clean_buffer_message
|
|
84
|
+
last = @buffer_manager.last_applied
|
|
85
|
+
return "No unapplied buffer changes to write (buffer is clean)." unless last.is_a?(Hash) && Array(last[:paths]).any?
|
|
86
|
+
|
|
87
|
+
mode = @shell.respond_to?(:config) && @shell.config.respond_to?(:fetch) ? @shell.config.fetch("buffer.auto_apply", "manual") : nil
|
|
88
|
+
origin = if !mode.nil? && !mode.to_s.strip.empty? && mode.to_s.downcase != "manual"
|
|
89
|
+
"the auto-apply policy (buffer.auto_apply: #{mode})"
|
|
90
|
+
else
|
|
91
|
+
"an earlier apply_buffer call or the auto-apply policy"
|
|
92
|
+
end
|
|
93
|
+
"No unapplied buffer changes to write (buffer is clean): #{last[:paths].size} file(s) were already written to disk " \
|
|
94
|
+
"at #{last[:at].utc.iso8601} by #{origin}: #{last[:paths].join(', ')}."
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class RepairRuntimeTool < BaseTool
|
|
99
|
+
DECLARATION = Declaration.new(
|
|
100
|
+
name: "repair_runtime_service", aliases: ["fix_service", "repair_lsp", "repair_runner", "repair_runtime"],
|
|
101
|
+
description: "Diagnose, adapt, or synthesize a replacement command/adapter for a failing runtime component " \
|
|
102
|
+
"(LSP, test runner, linter, or shell command) with pre-flight verification in an isolated sub-context",
|
|
103
|
+
risk: :execute, maestro_blocked: false, # runtime self-healing plane (TOOLS.md maestro policy)
|
|
104
|
+
params_schema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
target: {
|
|
108
|
+
type: "string",
|
|
109
|
+
enum: ["lsp", "test_runner", "linter", "command"],
|
|
110
|
+
description: "Runtime component type to repair or adapt"
|
|
111
|
+
},
|
|
112
|
+
language_or_type: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Language or service identifier (e.g., 'ruby', 'python', 'rust', 'go', 'zig', 'c', 'bash')"
|
|
115
|
+
},
|
|
116
|
+
proposed_command: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "Replacement or custom command string (optional, probed automatically if auto_probe: true)"
|
|
119
|
+
},
|
|
120
|
+
auto_probe: {
|
|
121
|
+
type: "boolean",
|
|
122
|
+
description: "Whether to automatically probe system binaries and candidate commands (default: true)"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ["target", "language_or_type"]
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
def initialize(runtime_manager: nil, lsp_manager: nil, adapter_registry: nil, overlay_storage: nil, config: nil, root_dir: Dir.pwd,
|
|
129
|
+
guard: nil)
|
|
130
|
+
super(DECLARATION)
|
|
131
|
+
@runtime_manager = runtime_manager
|
|
132
|
+
@lsp_manager = lsp_manager
|
|
133
|
+
@adapter_registry = adapter_registry
|
|
134
|
+
@overlay_storage = overlay_storage
|
|
135
|
+
@config = config
|
|
136
|
+
@root_dir = File.expand_path(root_dir)
|
|
137
|
+
@guard = guard
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def execute(args = {})
|
|
141
|
+
target = (args["target"] || args[:target]).to_s.downcase.strip
|
|
142
|
+
lang = (args["language_or_type"] || args[:language_or_type]).to_s.downcase.strip
|
|
143
|
+
proposed_cmd = (args["proposed_command"] || args[:proposed_command]).to_s.strip
|
|
144
|
+
auto_probe = if args["auto_probe"].nil?
|
|
145
|
+
args[:auto_probe].nil? || args[:auto_probe]
|
|
146
|
+
else
|
|
147
|
+
args["auto_probe"]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
return ToolOutput.error("Target and language_or_type must be provided.") if target.empty? || lang.empty?
|
|
151
|
+
|
|
152
|
+
# 1. Automatic Probing if proposed_cmd is not specified or auto_probe is requested
|
|
153
|
+
if proposed_cmd.empty? && auto_probe
|
|
154
|
+
candidate = case target
|
|
155
|
+
when "lsp"
|
|
156
|
+
Rubik::Runtime::Discovery.probe_lsp(lang, root_dir: @root_dir)
|
|
157
|
+
when "test_runner"
|
|
158
|
+
Rubik::Runtime::Discovery.discover_test_runners(root_dir: @root_dir, lang: lang).first
|
|
159
|
+
when "linter"
|
|
160
|
+
Rubik::Runtime::Discovery.discover_formatters(root_dir: @root_dir, lang: lang).first
|
|
161
|
+
when "command"
|
|
162
|
+
Rubik::Runtime::Discovery.binary_available?(lang) ? lang : nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if candidate
|
|
166
|
+
proposed_cmd = candidate
|
|
167
|
+
else
|
|
168
|
+
candidates = case target
|
|
169
|
+
when "lsp" then Rubik::Runtime::Discovery.lsp_candidates(lang)
|
|
170
|
+
when "test_runner" then Rubik::Runtime::Discovery.test_runner_candidates(lang, root_dir: @root_dir)
|
|
171
|
+
when "linter" then Rubik::Runtime::Discovery::FORMATTER_CANDIDATES[lang] || []
|
|
172
|
+
else []
|
|
173
|
+
end
|
|
174
|
+
cand_str = candidates.empty? ? "none" : candidates.join(", ")
|
|
175
|
+
return ToolOutput.error("Auto-probe failed to discover any installed candidate for #{target} (#{lang}). " \
|
|
176
|
+
"Candidates checked: [#{cand_str}]. Please provide a specific `proposed_command`.")
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
return ToolOutput.error("No command provided or discovered for #{target} (#{lang}).") if proposed_cmd.empty?
|
|
181
|
+
|
|
182
|
+
# 2. Pre-flight Verification in Isolated Sub-Context (ctx.extend())
|
|
183
|
+
root_ctx = @runtime_manager&.context || Rubik::Runtime::Context.new
|
|
184
|
+
sub_ctx = root_ctx.extend
|
|
185
|
+
|
|
186
|
+
verified = false
|
|
187
|
+
error_diagnostic = nil
|
|
188
|
+
|
|
189
|
+
begin
|
|
190
|
+
verified, error_diagnostic = verify_binary(target, lang, proposed_cmd, sub_ctx)
|
|
191
|
+
rescue StandardError => e
|
|
192
|
+
error_diagnostic = Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
193
|
+
cmd: proposed_cmd,
|
|
194
|
+
exit_code: 1,
|
|
195
|
+
stderr: "Exception during pre-flight sandbox check: #{e.message}"
|
|
196
|
+
)
|
|
197
|
+
ensure
|
|
198
|
+
sub_ctx.dispose
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
return ToolOutput.error("Verification failed for #{target} (#{lang}):\n#{error_diagnostic&.format_for_llm}") unless verified
|
|
202
|
+
|
|
203
|
+
# 3. Live Context Promotion & Subsystem Configuration (Zero-Downtime Hot Swapping)
|
|
204
|
+
case target
|
|
205
|
+
when "lsp"
|
|
206
|
+
cfg_key = "lsp.servers.#{lang}"
|
|
207
|
+
prev_cmd = @config&.fetch(cfg_key)
|
|
208
|
+
had_client = @lsp_manager ? !@lsp_manager.client_for(lang).nil? : false
|
|
209
|
+
@config&.set(cfg_key, proposed_cmd)
|
|
210
|
+
if @lsp_manager
|
|
211
|
+
@lsp_manager.stop_client(lang)
|
|
212
|
+
start_res = @lsp_manager.start_client(lang)
|
|
213
|
+
unless start_res.is_a?(Hash) && start_res[:ok] == true
|
|
214
|
+
# Failure Reversibility (AGENTS.md §1.A): roll back the in-memory
|
|
215
|
+
# state; disk stays clean because persist happens only after a
|
|
216
|
+
# verified start (atomic promotion).
|
|
217
|
+
@lsp_manager.stop_client(lang)
|
|
218
|
+
@config&.set(cfg_key, prev_cmd)
|
|
219
|
+
@lsp_manager.start_client(lang) if had_client
|
|
220
|
+
cause = start_res.is_a?(Hash) ? start_res[:error] : "unexpected start_client response"
|
|
221
|
+
return ToolOutput.error("Failed to start LSP server for #{lang} ('#{proposed_cmd}'); " \
|
|
222
|
+
"previous configuration restored. Cause: #{cause}")
|
|
223
|
+
end
|
|
224
|
+
active_client = @lsp_manager.client_for(lang)
|
|
225
|
+
root_ctx.provide("lsp:#{lang}", active_client || proposed_cmd)
|
|
226
|
+
else
|
|
227
|
+
root_ctx.provide("lsp:#{lang}", proposed_cmd)
|
|
228
|
+
end
|
|
229
|
+
# Persist to project config only after the live promotion succeeded.
|
|
230
|
+
persist_res = @config&.set(cfg_key, proposed_cmd, persist: :project)
|
|
231
|
+
when "test_runner"
|
|
232
|
+
persist_res = @config&.set("test.runners.#{lang}", proposed_cmd, persist: :project)
|
|
233
|
+
if @adapter_registry
|
|
234
|
+
adapter = @adapter_registry.get(lang)
|
|
235
|
+
if adapter
|
|
236
|
+
adapter.config ||= @config
|
|
237
|
+
adapter.custom_test_cmd = proposed_cmd
|
|
238
|
+
else
|
|
239
|
+
require_relative "../../adapter/generic" unless defined?(Rubik::Adapter::GenericAdapter)
|
|
240
|
+
new_adapter = Rubik::Adapter::GenericAdapter.new(lang, test_cmd: proposed_cmd, guard: @guard)
|
|
241
|
+
new_adapter.config = @config
|
|
242
|
+
@adapter_registry.register(lang, new_adapter, ctx: root_ctx)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
root_ctx.provide("runner:#{lang}", proposed_cmd)
|
|
246
|
+
when "linter"
|
|
247
|
+
persist_res = @config&.set("linter.commands.#{lang}", proposed_cmd, persist: :project)
|
|
248
|
+
if @adapter_registry
|
|
249
|
+
adapter = @adapter_registry.get(lang)
|
|
250
|
+
if adapter
|
|
251
|
+
adapter.config ||= @config
|
|
252
|
+
adapter.custom_format_cmd = proposed_cmd
|
|
253
|
+
else
|
|
254
|
+
require_relative "../../adapter/generic" unless defined?(Rubik::Adapter::GenericAdapter)
|
|
255
|
+
new_adapter = Rubik::Adapter::GenericAdapter.new(lang, format_cmd: proposed_cmd, guard: @guard)
|
|
256
|
+
new_adapter.config = @config
|
|
257
|
+
@adapter_registry.register(lang, new_adapter, ctx: root_ctx)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
root_ctx.provide("linter:#{lang}", proposed_cmd)
|
|
261
|
+
when "command"
|
|
262
|
+
root_ctx.provide("command:#{lang}", proposed_cmd)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
if @config.nil? && ["lsp", "test_runner", "linter"].include?(target)
|
|
266
|
+
return "⚠️ Promoted '#{proposed_cmd}' for #{target} (#{lang}) — no config is attached, " \
|
|
267
|
+
"so the command is active for this session only and was not saved."
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if persist_res.is_a?(Hash) && !persist_res[:ok]
|
|
271
|
+
return "⚠️ Promoted '#{proposed_cmd}' for #{target} (#{lang}) — active in this session, " \
|
|
272
|
+
"but saving to the project config failed: #{persist_res[:error]}"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
"✨ Successfully verified and promoted runtime service for #{target} (#{lang}): '#{proposed_cmd}'. " \
|
|
276
|
+
"Service is now active and hot-swapped in the live context with zero downtime."
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
def verify_binary(target, lang, proposed_cmd, sub_ctx)
|
|
282
|
+
case target
|
|
283
|
+
when "lsp"
|
|
284
|
+
client = Rubik::LSP::Client.new(lang, root_dir: @root_dir, server_cmd: proposed_cmd)
|
|
285
|
+
if client.available?
|
|
286
|
+
sub_ctx.provide("lsp_probe:#{lang}", client)
|
|
287
|
+
[true, nil]
|
|
288
|
+
else
|
|
289
|
+
[false, Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
290
|
+
cmd: proposed_cmd,
|
|
291
|
+
exit_code: 127,
|
|
292
|
+
stderr: "Pre-flight check failed: LSP binary '#{client.binary_name}' not available on system"
|
|
293
|
+
)]
|
|
294
|
+
end
|
|
295
|
+
when "test_runner"
|
|
296
|
+
if Rubik::Runtime::Discovery.binary_available?(proposed_cmd)
|
|
297
|
+
sub_ctx.provide("runner_probe:#{lang}", proposed_cmd)
|
|
298
|
+
[true, nil]
|
|
299
|
+
else
|
|
300
|
+
[false, Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
301
|
+
cmd: proposed_cmd,
|
|
302
|
+
exit_code: 127,
|
|
303
|
+
stderr: "Pre-flight check failed: Test runner executable not found in PATH"
|
|
304
|
+
)]
|
|
305
|
+
end
|
|
306
|
+
when "linter"
|
|
307
|
+
if Rubik::Runtime::Discovery.binary_available?(proposed_cmd)
|
|
308
|
+
sub_ctx.provide("linter_probe:#{lang}", proposed_cmd)
|
|
309
|
+
[true, nil]
|
|
310
|
+
else
|
|
311
|
+
[false, Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
312
|
+
cmd: proposed_cmd,
|
|
313
|
+
exit_code: 127,
|
|
314
|
+
stderr: "Pre-flight check failed: Linter/formatter executable not found in PATH"
|
|
315
|
+
)]
|
|
316
|
+
end
|
|
317
|
+
when "command"
|
|
318
|
+
if Rubik::Runtime::Discovery.binary_available?(proposed_cmd)
|
|
319
|
+
sub_ctx.provide("command_probe:#{lang}", proposed_cmd)
|
|
320
|
+
[true, nil]
|
|
321
|
+
else
|
|
322
|
+
[false, Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
323
|
+
cmd: proposed_cmd,
|
|
324
|
+
exit_code: 127,
|
|
325
|
+
stderr: "Pre-flight check failed: Command executable not found in PATH"
|
|
326
|
+
)]
|
|
327
|
+
end
|
|
328
|
+
else
|
|
329
|
+
[false, Rubik::Runtime::DiagnosticEngine.diagnose(
|
|
330
|
+
cmd: proposed_cmd,
|
|
331
|
+
exit_code: 1,
|
|
332
|
+
stderr: "Unknown target component type: #{target}"
|
|
333
|
+
)]
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
class FinishTool < BaseTool
|
|
339
|
+
DECLARATION = Declaration.new(
|
|
340
|
+
name: "finish", aliases: ["complete", "done", "task_complete", "exit"],
|
|
341
|
+
description: "Signal task completion with final message. Before finishing, re-verify headline claims " \
|
|
342
|
+
"(file lists, counts, command outcomes) against your raw command output — context " \
|
|
343
|
+
"compaction can blur report details, and the final report must match observed evidence.",
|
|
344
|
+
risk: :execute, maestro_blocked: false, # the completion signal — handled before the gate; must stay direct
|
|
345
|
+
params_schema: { type: "object",
|
|
346
|
+
properties: { message: { type: "string",
|
|
347
|
+
description: "Final summary message for the user describing what was accomplished (optional)" } } }
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def initialize
|
|
351
|
+
super(DECLARATION)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def execute(args = {})
|
|
355
|
+
args["message"] || args[:message] || "Task finished"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../coeffects"
|
|
5
|
+
require_relative "../output"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Tools
|
|
9
|
+
class LSPDiagnosticsTool < BaseTool
|
|
10
|
+
attr_reader :lsp_manager, :buffer_manager
|
|
11
|
+
|
|
12
|
+
DECLARATION = Declaration.new(
|
|
13
|
+
name: "lsp_diagnostics", aliases: ["get_diagnostics", "diagnostics"],
|
|
14
|
+
description: "Get real-time compiler and linter diagnostics (errors, warnings) for a file or all active project files via LSP",
|
|
15
|
+
risk: :read, archetypes: [:research, :coder],
|
|
16
|
+
params_schema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
file_path: { type: "string", description: "Optional specific file path to query diagnostics for" }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
def initialize(lsp_manager = nil, buffer_mgr = nil, buffer_manager: nil)
|
|
24
|
+
super(DECLARATION)
|
|
25
|
+
@lsp_manager = lsp_manager
|
|
26
|
+
@buffer_manager = buffer_manager || buffer_mgr
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def effective_buffer_manager
|
|
30
|
+
@buffer_manager || @lsp_manager&.context&.get(Rubik::Coeffects::BUFFER_MANAGER)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute(args = {})
|
|
34
|
+
return ToolOutput.error("LSP manager is not connected.") unless @lsp_manager
|
|
35
|
+
return ToolOutput.error("LSP is currently disabled in configuration (enable via '!lsp on').") unless @lsp_manager.enabled?
|
|
36
|
+
|
|
37
|
+
path = (args["file_path"] || args[:file_path] || "").to_s.strip
|
|
38
|
+
bm = effective_buffer_manager
|
|
39
|
+
@lsp_manager.sync_buffer_file_if_needed(path, bm) if bm && @lsp_manager.respond_to?(:sync_buffer_file_if_needed)
|
|
40
|
+
|
|
41
|
+
if path.empty?
|
|
42
|
+
all = @lsp_manager.all_diagnostics
|
|
43
|
+
total_issues = all.values.flatten.size
|
|
44
|
+
if total_issues.zero?
|
|
45
|
+
"No active LSP diagnostics across workspace (all clean)."
|
|
46
|
+
else
|
|
47
|
+
lines = ["=== Active Workspace LSP Diagnostics (#{total_issues} issue(s)) ==="]
|
|
48
|
+
all.each do |fpath, diags|
|
|
49
|
+
next if diags.empty?
|
|
50
|
+
|
|
51
|
+
lines << "\nFile: #{fpath} (#{diags.size}):"
|
|
52
|
+
diags.each do |d|
|
|
53
|
+
sev = d["severity"] == 1 ? "ERROR" : "WARN"
|
|
54
|
+
start_line = (d.dig("range", "start", "line") || 0) + 1
|
|
55
|
+
lines << " • [#{sev}] Line #{start_line}: #{d['message']}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
lines.join("\n")
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
diags = @lsp_manager.diagnostics_for(path)
|
|
62
|
+
if diags.empty?
|
|
63
|
+
append_buffer_tip("No LSP diagnostics or errors for '#{path}'.", path)
|
|
64
|
+
else
|
|
65
|
+
lines = ["=== LSP Diagnostics for #{path} (#{diags.size} issue(s)) ==="]
|
|
66
|
+
diags.each do |d|
|
|
67
|
+
sev = case d["severity"]
|
|
68
|
+
when 1 then "ERROR"
|
|
69
|
+
when 2 then "WARN"
|
|
70
|
+
when 3 then "INFO"
|
|
71
|
+
else "HINT"
|
|
72
|
+
end
|
|
73
|
+
range = d["range"] || {}
|
|
74
|
+
start_line = (range.dig("start", "line") || 0) + 1
|
|
75
|
+
start_char = (range.dig("start", "character") || 0) + 1
|
|
76
|
+
lines << " • [#{sev}] Line #{start_line}:#{start_char} - #{d['message']}"
|
|
77
|
+
end
|
|
78
|
+
lines.join("\n")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def buffer_dirty?(path)
|
|
86
|
+
bm = effective_buffer_manager
|
|
87
|
+
return false unless bm && !path.to_s.empty?
|
|
88
|
+
|
|
89
|
+
if bm.respond_to?(:dirty?)
|
|
90
|
+
begin
|
|
91
|
+
bm.method(:dirty?).arity.zero? ? bm.dirty? : bm.dirty?(path)
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
bm.dirty?
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def append_buffer_tip(msg, path)
|
|
101
|
+
return msg unless buffer_dirty?(path)
|
|
102
|
+
|
|
103
|
+
tip = "\n\n💡 Tip: '#{path}' has unapplied in-memory buffer modifications. " \
|
|
104
|
+
"Call 'apply_buffer' to persist changes to disk for language servers requiring disk-based indexing."
|
|
105
|
+
"#{msg}#{tip}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class LSPHoverTool < BaseTool
|
|
110
|
+
attr_reader :lsp_manager, :buffer_manager
|
|
111
|
+
|
|
112
|
+
DECLARATION = Declaration.new(
|
|
113
|
+
name: "lsp_hover", aliases: ["get_hover", "hover"],
|
|
114
|
+
description: "Get compiler-level type signature, docstring, and symbol details at a specific line and character position via LSP",
|
|
115
|
+
risk: :read, archetypes: [:research],
|
|
116
|
+
params_schema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
file_path: { type: "string", description: "Path to source file" },
|
|
120
|
+
line: { type: "integer", description: "1-based line number" },
|
|
121
|
+
character: { type: "integer", description: "1-based character position" }
|
|
122
|
+
},
|
|
123
|
+
required: ["file_path", "line", "character"]
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
def initialize(lsp_manager = nil, buffer_mgr = nil, buffer_manager: nil)
|
|
127
|
+
super(DECLARATION)
|
|
128
|
+
@lsp_manager = lsp_manager
|
|
129
|
+
@buffer_manager = buffer_manager || buffer_mgr
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def effective_buffer_manager
|
|
133
|
+
@buffer_manager || @lsp_manager&.context&.get(Rubik::Coeffects::BUFFER_MANAGER)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def execute(args = {})
|
|
137
|
+
return ToolOutput.error("LSP manager is not connected.") unless @lsp_manager
|
|
138
|
+
return ToolOutput.error("LSP is currently disabled in configuration (enable via '!lsp on').") unless @lsp_manager.enabled?
|
|
139
|
+
|
|
140
|
+
path = (args["file_path"] || args[:file_path]).to_s.strip
|
|
141
|
+
line = (args["line"] || args[:line]).to_i
|
|
142
|
+
char = (args["character"] || args[:character] || args["col"] || args[:col]).to_i
|
|
143
|
+
|
|
144
|
+
return ToolOutput.error("File path is required.") if path.empty?
|
|
145
|
+
return ToolOutput.error("Line number must be >= 1.") if line < 1
|
|
146
|
+
|
|
147
|
+
bm = effective_buffer_manager
|
|
148
|
+
@lsp_manager.sync_buffer_file_if_needed(path, bm) if bm && @lsp_manager.respond_to?(:sync_buffer_file_if_needed)
|
|
149
|
+
|
|
150
|
+
res = @lsp_manager.hover(path, [line - 1, 0].max, [char - 1, 0].max)
|
|
151
|
+
if res[:ok]
|
|
152
|
+
contents = res.dig(:result, "contents")
|
|
153
|
+
if contents.nil? || contents.empty?
|
|
154
|
+
append_buffer_tip("No hover information available at #{path}:#{line}:#{char}", path)
|
|
155
|
+
elsif contents.is_a?(Hash) && contents["value"]
|
|
156
|
+
contents["value"]
|
|
157
|
+
elsif contents.is_a?(Array)
|
|
158
|
+
contents.map { |c| c.is_a?(Hash) ? c["value"] : c.to_s }.join("\n")
|
|
159
|
+
else
|
|
160
|
+
contents.to_s
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
ToolOutput.error("LSP Hover failed: #{res[:error]}")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def buffer_dirty?(path)
|
|
170
|
+
bm = effective_buffer_manager
|
|
171
|
+
return false unless bm && !path.to_s.empty?
|
|
172
|
+
|
|
173
|
+
if bm.respond_to?(:dirty?)
|
|
174
|
+
begin
|
|
175
|
+
bm.method(:dirty?).arity.zero? ? bm.dirty? : bm.dirty?(path)
|
|
176
|
+
rescue ArgumentError
|
|
177
|
+
bm.dirty?
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
false
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def append_buffer_tip(msg, path)
|
|
185
|
+
return msg unless buffer_dirty?(path)
|
|
186
|
+
|
|
187
|
+
tip = "\n\n💡 Tip: '#{path}' has unapplied in-memory buffer modifications. " \
|
|
188
|
+
"Call 'apply_buffer' to persist changes to disk for language servers requiring disk-based indexing."
|
|
189
|
+
"#{msg}#{tip}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class LSPDefinitionTool < BaseTool
|
|
194
|
+
attr_reader :lsp_manager, :buffer_manager
|
|
195
|
+
|
|
196
|
+
DECLARATION = Declaration.new(
|
|
197
|
+
name: "lsp_definition", aliases: ["goto_definition", "definition"],
|
|
198
|
+
description: "Jump to exact type-safe declaration and definition location of a symbol at line/character position via LSP",
|
|
199
|
+
risk: :read, archetypes: [:research],
|
|
200
|
+
params_schema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
file_path: { type: "string", description: "Path to source file" },
|
|
204
|
+
line: { type: "integer", description: "1-based line number" },
|
|
205
|
+
character: { type: "integer", description: "1-based character position" }
|
|
206
|
+
},
|
|
207
|
+
required: ["file_path", "line", "character"]
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
def initialize(lsp_manager = nil, buffer_mgr = nil, buffer_manager: nil)
|
|
211
|
+
super(DECLARATION)
|
|
212
|
+
@lsp_manager = lsp_manager
|
|
213
|
+
@buffer_manager = buffer_manager || buffer_mgr
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def effective_buffer_manager
|
|
217
|
+
@buffer_manager || @lsp_manager&.context&.get(Rubik::Coeffects::BUFFER_MANAGER)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def execute(args = {})
|
|
221
|
+
return ToolOutput.error("LSP manager is not connected.") unless @lsp_manager
|
|
222
|
+
return ToolOutput.error("LSP is currently disabled in configuration (enable via '!lsp on').") unless @lsp_manager.enabled?
|
|
223
|
+
|
|
224
|
+
path = (args["file_path"] || args[:file_path]).to_s.strip
|
|
225
|
+
line = (args["line"] || args[:line]).to_i
|
|
226
|
+
char = (args["character"] || args[:character] || args["col"] || args[:col]).to_i
|
|
227
|
+
|
|
228
|
+
return ToolOutput.error("File path is required.") if path.empty?
|
|
229
|
+
return ToolOutput.error("Line number must be >= 1.") if line < 1
|
|
230
|
+
|
|
231
|
+
bm = effective_buffer_manager
|
|
232
|
+
@lsp_manager.sync_buffer_file_if_needed(path, bm) if bm && @lsp_manager.respond_to?(:sync_buffer_file_if_needed)
|
|
233
|
+
|
|
234
|
+
res = @lsp_manager.definition(path, [line - 1, 0].max, [char - 1, 0].max)
|
|
235
|
+
if res[:ok]
|
|
236
|
+
result = res[:result]
|
|
237
|
+
if result.nil? || (result.is_a?(Array) && result.empty?)
|
|
238
|
+
append_buffer_tip("No definition found for symbol at #{path}:#{line}:#{char}", path)
|
|
239
|
+
else
|
|
240
|
+
locs = result.is_a?(Array) ? result : [result]
|
|
241
|
+
lines = ["Found #{locs.size} definition location(s):"]
|
|
242
|
+
locs.each do |loc|
|
|
243
|
+
uri = loc["uri"] || loc["targetUri"]
|
|
244
|
+
clean_file = uri.to_s.sub(%r{^file://}, "")
|
|
245
|
+
range = loc["range"] || loc["targetSelectionRange"] || {}
|
|
246
|
+
s_line = (range.dig("start", "line") || 0) + 1
|
|
247
|
+
s_char = (range.dig("start", "character") || 0) + 1
|
|
248
|
+
lines << " • #{clean_file}:#{s_line}:#{s_char}"
|
|
249
|
+
end
|
|
250
|
+
lines.join("\n")
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
ToolOutput.error("LSP Definition failed: #{res[:error]}")
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def buffer_dirty?(path)
|
|
260
|
+
bm = effective_buffer_manager
|
|
261
|
+
return false unless bm && !path.to_s.empty?
|
|
262
|
+
|
|
263
|
+
if bm.respond_to?(:dirty?)
|
|
264
|
+
begin
|
|
265
|
+
bm.method(:dirty?).arity.zero? ? bm.dirty? : bm.dirty?(path)
|
|
266
|
+
rescue ArgumentError
|
|
267
|
+
bm.dirty?
|
|
268
|
+
end
|
|
269
|
+
else
|
|
270
|
+
false
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def append_buffer_tip(msg, path)
|
|
275
|
+
return msg unless buffer_dirty?(path)
|
|
276
|
+
|
|
277
|
+
tip = "\n\n💡 Tip: '#{path}' has unapplied in-memory buffer modifications. " \
|
|
278
|
+
"Call 'apply_buffer' to persist changes to disk for language servers requiring disk-based indexing."
|
|
279
|
+
"#{msg}#{tip}"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|