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,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Concurrency
|
|
5
|
+
# Monotonic min-heap of fiber wakeups. Entries are
|
|
6
|
+
# [deadline, fiber, resume_value, generation]; only the EventLoop thread
|
|
7
|
+
# touches this structure (timers are registered from VM hooks, which run
|
|
8
|
+
# on the loop thread), so no locking is needed.
|
|
9
|
+
#
|
|
10
|
+
# Staleness: a timer for a fiber that has since re-parked (new park
|
|
11
|
+
# generation) or been resumed dead is inert. The EventLoop passes a
|
|
12
|
+
# stale? check at construction; entries failing it are pruned lazily by
|
|
13
|
+
# every query, so a dead timer never gates idle-draining — without this
|
|
14
|
+
# an interrupted 30 s sleep would hold the whole loop hostage until its
|
|
15
|
+
# deadline despite the fiber being long gone.
|
|
16
|
+
class Timers
|
|
17
|
+
def initialize(&staleness)
|
|
18
|
+
@staleness = staleness
|
|
19
|
+
@heap = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the stored entry (opaque; useful for diagnostics only).
|
|
23
|
+
def add(deadline, fiber, resume_value, generation)
|
|
24
|
+
entry = [deadline, fiber, resume_value, generation]
|
|
25
|
+
@heap << entry
|
|
26
|
+
sift_up(@heap.size - 1)
|
|
27
|
+
entry
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def empty?
|
|
31
|
+
prune!
|
|
32
|
+
@heap.empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def size
|
|
36
|
+
prune!
|
|
37
|
+
@heap.size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Seconds until the earliest deadline (0.0 when already due), or nil
|
|
41
|
+
# when no timers are pending.
|
|
42
|
+
def next_gap(now)
|
|
43
|
+
return nil if empty?
|
|
44
|
+
|
|
45
|
+
gap = @heap[0][0] - now
|
|
46
|
+
gap.positive? ? gap : 0.0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Calls sink.call(fiber, resume_value, generation) for every due entry,
|
|
50
|
+
# removing it. Returns the number of entries fired. The sink decides
|
|
51
|
+
# whether a stale entry actually resumes its fiber.
|
|
52
|
+
def fire_due!(now, &sink)
|
|
53
|
+
return 0 if empty? || sink.nil?
|
|
54
|
+
|
|
55
|
+
fired = 0
|
|
56
|
+
while !empty? && @heap[0][0] <= now
|
|
57
|
+
entry = @heap[0]
|
|
58
|
+
last = @heap.pop
|
|
59
|
+
if @heap.empty?
|
|
60
|
+
@heap << last unless last.equal?(entry)
|
|
61
|
+
else
|
|
62
|
+
@heap[0] = last
|
|
63
|
+
sift_down(0)
|
|
64
|
+
end
|
|
65
|
+
sink.call(entry[1], entry[2], entry[3])
|
|
66
|
+
fired += 1
|
|
67
|
+
end
|
|
68
|
+
fired
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def prune!
|
|
74
|
+
return unless @staleness
|
|
75
|
+
|
|
76
|
+
@heap.reject! { |entry| @staleness.call(entry[1], entry[3]) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def sift_up(index)
|
|
80
|
+
entry = @heap[index]
|
|
81
|
+
while index.positive?
|
|
82
|
+
parent = (index - 1) / 2
|
|
83
|
+
break if @heap[parent][0] <= entry[0]
|
|
84
|
+
|
|
85
|
+
@heap[index] = @heap[parent]
|
|
86
|
+
index = parent
|
|
87
|
+
end
|
|
88
|
+
@heap[index] = entry
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def sift_down(index)
|
|
92
|
+
size = @heap.size
|
|
93
|
+
entry = @heap[index]
|
|
94
|
+
loop do
|
|
95
|
+
child = (2 * index) + 1
|
|
96
|
+
break if child >= size
|
|
97
|
+
|
|
98
|
+
right = child + 1
|
|
99
|
+
child = right if right < size && @heap[right][0] < @heap[child][0]
|
|
100
|
+
break if @heap[child][0] >= entry[0]
|
|
101
|
+
|
|
102
|
+
@heap[index] = @heap[child]
|
|
103
|
+
index = child
|
|
104
|
+
end
|
|
105
|
+
@heap[index] = entry
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "log"
|
|
4
|
+
require_relative "concurrency/errors"
|
|
5
|
+
require_relative "concurrency/event_loop"
|
|
6
|
+
require_relative "concurrency/scheduler"
|
|
7
|
+
require_relative "concurrency/boundary"
|
|
8
|
+
require_relative "concurrency/join"
|
|
9
|
+
|
|
10
|
+
module Rubik
|
|
11
|
+
# Silent concurrency over Ruby's Fiber Scheduler: plain blocking I/O —
|
|
12
|
+
# Net::HTTP, sleep backoffs, Open3 pipe reads, Mutex/ConditionVariable
|
|
13
|
+
# waits, Thread#join — interleaves cooperatively on a background event
|
|
14
|
+
# loop while a boundary is open, with ZERO changes at the I/O call sites.
|
|
15
|
+
# "Silently" = no async/await-style syntax noise ever reaches callers.
|
|
16
|
+
#
|
|
17
|
+
# Envelope (documented limits, plan §9): this is I/O concurrency, not CPU
|
|
18
|
+
# parallelism (the GVL still serializes computation); regular-file reads
|
|
19
|
+
# never park (read_nonblock on files returns immediately — they simply run
|
|
20
|
+
# serially, which is harmless); Timeout.timeout-backed code paths are not
|
|
21
|
+
# adopted inside boundaries in v1 (Thread.raise cannot be aimed at a
|
|
22
|
+
# specific fiber). Escape hatch: RUBIK_FIBER_SCHEDULER=off degrades every
|
|
23
|
+
# boundary to plain sequential execution.
|
|
24
|
+
module Concurrency
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Runs the block on the current OS thread with a fiber scheduler
|
|
28
|
+
# installed. Fibers spawned inside (via .map or Fiber.schedule) drain
|
|
29
|
+
# before the block returns. Returns the block's value; errors surface
|
|
30
|
+
# per Boundary.open's drain-then-raise policy. Nested calls reuse the
|
|
31
|
+
# outer boundary. The scheduler is always uninstalled on exit.
|
|
32
|
+
def silently(&block)
|
|
33
|
+
disabled? ? block.call : Boundary.open(&block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Ordered fan-out: runs the block for every item concurrently and
|
|
37
|
+
# returns results in INPUT order. concurrency: bounds simultaneously
|
|
38
|
+
# live fibers (fiber stacks cost real memory — pass a bound for very
|
|
39
|
+
# large enumerables). Drain-then-raise: after every item completes, the
|
|
40
|
+
# lowest-INPUT-index error is raised and its siblings are logged.
|
|
41
|
+
def map(enum, concurrency: nil, &block)
|
|
42
|
+
return enum.map(&block) if disabled?
|
|
43
|
+
|
|
44
|
+
items = enum.to_a
|
|
45
|
+
return [] if items.empty?
|
|
46
|
+
|
|
47
|
+
limit = concurrency&.positive? ? concurrency.to_i : nil
|
|
48
|
+
slots = Boundary.open do
|
|
49
|
+
loop = ::Thread.current.thread_variable_get(Boundary::ACTIVE_KEY).loop
|
|
50
|
+
results = ::Array.new(items.size)
|
|
51
|
+
latch = ::Object.new
|
|
52
|
+
waiter = ::Fiber.current
|
|
53
|
+
remaining = items.size
|
|
54
|
+
slot_lock = ::Mutex.new
|
|
55
|
+
slot_free = ::ConditionVariable.new
|
|
56
|
+
used = 0
|
|
57
|
+
|
|
58
|
+
items.each_with_index do |item, index|
|
|
59
|
+
::Fiber.schedule do
|
|
60
|
+
acquired = false
|
|
61
|
+
begin
|
|
62
|
+
slot_lock.synchronize do
|
|
63
|
+
slot_free.wait(slot_lock) while limit && used >= limit
|
|
64
|
+
used += 1
|
|
65
|
+
end
|
|
66
|
+
acquired = true
|
|
67
|
+
results[index] = { ok: true, value: block.call(item) }
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
results[index] = { ok: false, error: e }
|
|
70
|
+
ensure
|
|
71
|
+
# The ensure spans the ACQUISITION too: an Exception-level
|
|
72
|
+
# death while parked on the semaphore (interrupt delivery)
|
|
73
|
+
# must still count this item down or the latch never fires and
|
|
74
|
+
# the boundary hangs. Mutex#synchronize releases on raise, so
|
|
75
|
+
# re-locking here is safe.
|
|
76
|
+
if acquired
|
|
77
|
+
slot_lock.synchronize do
|
|
78
|
+
used -= 1
|
|
79
|
+
slot_free.signal
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
remaining -= 1 # fibers serialize on the loop thread — no race
|
|
83
|
+
loop.unpark(latch, waiter) if remaining.zero?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
loop.park(latch)
|
|
88
|
+
results
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
failures = slots.filter_map.with_index { |slot, i| [i, slot[:error]] if slot && !slot[:ok] }
|
|
92
|
+
failures.each do |(index, error)|
|
|
93
|
+
Rubik::Log.warn(:concurrency, "fan-out item #{index} failed: #{error.class}: #{error.message}")
|
|
94
|
+
end
|
|
95
|
+
raise failures.first[1] if failures.any?
|
|
96
|
+
|
|
97
|
+
slots.map { |slot| slot[:value] }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# True while a boundary is installed on the current OS thread.
|
|
101
|
+
def active?
|
|
102
|
+
!::Thread.current.thread_variable_get(Boundary::ACTIVE_KEY).nil?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# RUBIK_FIBER_SCHEDULER=off degrades boundaries to sequential execution
|
|
106
|
+
# (memoized at first use — the process-wide kill switch, plan K14).
|
|
107
|
+
def disabled?
|
|
108
|
+
return @disabled unless @disabled.nil?
|
|
109
|
+
|
|
110
|
+
@disabled = ::ENV["RUBIK_FIBER_SCHEDULER"].to_s.casecmp("off").zero?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class << self
|
|
114
|
+
# Test seam: reset the memoized kill-switch state after stubbing ENV.
|
|
115
|
+
def reset_disabled_cache!
|
|
116
|
+
@disabled = nil
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Ruby prints "IO::Buffer is experimental" on the first buffer use; our
|
|
124
|
+
# io_read/io_write hooks exercise it on every boundary. Pay that one-shot
|
|
125
|
+
# warning now, silently, so it never surfaces mid-session.
|
|
126
|
+
original_verbose = $VERBOSE
|
|
127
|
+
begin
|
|
128
|
+
$VERBOSE = nil
|
|
129
|
+
IO::Buffer.new(1)
|
|
130
|
+
ensure
|
|
131
|
+
$VERBOSE = original_verbose
|
|
132
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
class Config
|
|
5
|
+
module DeepOps
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def deep_dup(obj)
|
|
9
|
+
case obj
|
|
10
|
+
when Hash
|
|
11
|
+
obj.transform_values { |v| deep_dup(v) }
|
|
12
|
+
when Array
|
|
13
|
+
obj.map { |v| deep_dup(v) }
|
|
14
|
+
else
|
|
15
|
+
begin
|
|
16
|
+
obj.dup
|
|
17
|
+
rescue TypeError
|
|
18
|
+
# nil/true/false are undupable immediates; YAML-derived config
|
|
19
|
+
# values raise nothing else here. A custom #dup raising anything
|
|
20
|
+
# else must fail loud, not silently alias the value.
|
|
21
|
+
obj
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Merges source over target.
|
|
27
|
+
# Invariant (W6): Arrays are replaced as whole units by default so config overrides
|
|
28
|
+
# can completely reset lists. If array_union: true, arrays are merged via union.
|
|
29
|
+
def deep_merge(target, source, array_union: false)
|
|
30
|
+
return deep_dup(target) unless source.is_a?(Hash)
|
|
31
|
+
|
|
32
|
+
result = deep_dup(target)
|
|
33
|
+
source.each do |key, val|
|
|
34
|
+
result[key] = if result[key].is_a?(Hash) && val.is_a?(Hash)
|
|
35
|
+
deep_merge(result[key], val, array_union: array_union)
|
|
36
|
+
elsif array_union && result[key].is_a?(Array) && val.is_a?(Array)
|
|
37
|
+
(result[key] + val).uniq
|
|
38
|
+
else
|
|
39
|
+
deep_dup(val)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def deep_stringify_keys(obj)
|
|
46
|
+
case obj
|
|
47
|
+
when Hash
|
|
48
|
+
obj.each_with_object({}) do |(k, v), result|
|
|
49
|
+
result[k.to_s] = deep_stringify_keys(v)
|
|
50
|
+
end
|
|
51
|
+
when Array
|
|
52
|
+
obj.map { |v| deep_stringify_keys(v) }
|
|
53
|
+
else
|
|
54
|
+
obj
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Deletes the dot-path leaf from nested string-keyed hashes. Returns
|
|
59
|
+
# :deleted when the leaf existed and was removed, :absent when any
|
|
60
|
+
# segment of the path is missing (nothing to delete), :blocked when
|
|
61
|
+
# the path traverses an existing non-Hash (nothing was changed).
|
|
62
|
+
def deep_delete(obj, path)
|
|
63
|
+
keys = path.to_s.split(".")
|
|
64
|
+
return :blocked unless obj.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
node = keys[0...-1].reduce(obj) do |acc, k|
|
|
67
|
+
return :absent unless acc.is_a?(Hash) && acc.key?(k)
|
|
68
|
+
|
|
69
|
+
child = acc[k]
|
|
70
|
+
return :blocked unless child.is_a?(Hash)
|
|
71
|
+
|
|
72
|
+
child
|
|
73
|
+
end
|
|
74
|
+
return :blocked unless node.is_a?(Hash)
|
|
75
|
+
|
|
76
|
+
node.delete(keys.last) ? :deleted : :absent
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../platform"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
class Config
|
|
7
|
+
module Defaults
|
|
8
|
+
DEFAULT_CONFIG = {
|
|
9
|
+
"llm" => {
|
|
10
|
+
"stream" => true,
|
|
11
|
+
"stream_timeout" => 120,
|
|
12
|
+
"open_timeout" => 15,
|
|
13
|
+
"default_max_output_tokens" => 4096,
|
|
14
|
+
"capture_reasoning" => true,
|
|
15
|
+
"auto_continue_on_length" => true,
|
|
16
|
+
"max_auto_continues" => 3,
|
|
17
|
+
# Plan 90 bridge (loop.rb): nil = not sent; non-nil reasoning_effort also flips reasoning_model?.
|
|
18
|
+
"thinking_budget_ratio" => nil, # Anthropic thinking budget share (0.0-0.8) -> opts[:thinking_budget_ratio]
|
|
19
|
+
"reasoning_effort" => nil, # OpenAI reasoning effort "low"|"medium"|"high" -> opts[:reasoning_effort]
|
|
20
|
+
"headers" => {}
|
|
21
|
+
},
|
|
22
|
+
"model" => { "provider" => "ollama", "name" => "qwen2.5-coder:7b", "endpoint" => "http://localhost:11434" },
|
|
23
|
+
"models" => {
|
|
24
|
+
"default" => { "provider" => "ollama", "name" => "qwen2.5-coder:7b", "endpoint" => "http://localhost:11434" },
|
|
25
|
+
"fast" => { "provider" => "ollama", "name" => "qwen2.5-coder:1.5b", "endpoint" => "http://localhost:11434" },
|
|
26
|
+
"advanced" => { "provider" => "ollama", "name" => "qwen2.5-coder:14b", "endpoint" => "http://localhost:11434" }
|
|
27
|
+
},
|
|
28
|
+
"providers" => {},
|
|
29
|
+
"agent" => {
|
|
30
|
+
"max_steps" => 40,
|
|
31
|
+
"goal_max_steps" => 100,
|
|
32
|
+
"plan_max_steps" => 15,
|
|
33
|
+
"warn_at_ratio" => 0.75,
|
|
34
|
+
"ask_continue_on_limit" => true, "goal_wait_timeout" => 300
|
|
35
|
+
},
|
|
36
|
+
# rubik://storage sanal FS-DB (Rubik::Storage): hedef/plan/bellek/wiki/transcript varlıkları
|
|
37
|
+
# + worktree'yi temiz tutan artifact absorb'u. root "~" içerebilir; project nil ise slug türetilir.
|
|
38
|
+
"storage" => {
|
|
39
|
+
"enabled" => true,
|
|
40
|
+
"root" => "~/.rubik/rubikstorage",
|
|
41
|
+
"project" => nil,
|
|
42
|
+
"absorb_artifacts" => true
|
|
43
|
+
},
|
|
44
|
+
"tools" => {
|
|
45
|
+
"circuit_breaker" => { "enabled" => false, "threshold" => 5, "cooldown_seconds" => 60 },
|
|
46
|
+
"max_output_chars" => 12_000,
|
|
47
|
+
"max_output_lines" => 300,
|
|
48
|
+
"truncation_strategy" => "head_tail",
|
|
49
|
+
"timeout_seconds" => 30,
|
|
50
|
+
"native" => "auto"
|
|
51
|
+
},
|
|
52
|
+
"router" => { "mode" => "auto", "sticky" => true },
|
|
53
|
+
"repair" => { "max_attempts" => 3 },
|
|
54
|
+
"sandbox" => {
|
|
55
|
+
"enabled" => false,
|
|
56
|
+
"driver" => "bwrap",
|
|
57
|
+
"image" => "rubik-sandbox:latest",
|
|
58
|
+
"timeout_seconds" => 15,
|
|
59
|
+
"memory_limit_mb" => 512,
|
|
60
|
+
"cpus" => "1.0",
|
|
61
|
+
"allow_network" => false,
|
|
62
|
+
"shell" => "auto", # sandbox shell resolution: auto | bash | powershell (sandbox/runner.rb)
|
|
63
|
+
"prompt_on_startup" => true, "isolation_note" => true # append the 🔍 isolation line to run_command output (sandbox/runner.rb)
|
|
64
|
+
},
|
|
65
|
+
"security" => {
|
|
66
|
+
# NOTE: no audit_log_path default — Guard.default_audit_log_path (security/guard.rb) is the
|
|
67
|
+
# single source so the value resolves at call time; a load-time snapshot would pin require-time HOME.
|
|
68
|
+
"command_allowlist" => [], # operator-managed token-prefix entries (e.g. "node .gitnexus/run.cjs")
|
|
69
|
+
"allow_safe_probes" => true # `bash --version`-style version/help probes bypass the blocklist
|
|
70
|
+
},
|
|
71
|
+
"network" => { "allow" => false },
|
|
72
|
+
"buffer" => {
|
|
73
|
+
"auto_apply" => "manual",
|
|
74
|
+
"model_can_apply" => true,
|
|
75
|
+
"prompt_indicator" => true,
|
|
76
|
+
"exit_guard" => true,
|
|
77
|
+
"reminder_interval" => 3,
|
|
78
|
+
"on_external_modification" => "skip"
|
|
79
|
+
},
|
|
80
|
+
"lsp" => {
|
|
81
|
+
"enabled" => true,
|
|
82
|
+
"auto_start" => true,
|
|
83
|
+
"auto_diagnostics" => true,
|
|
84
|
+
"servers" => {
|
|
85
|
+
"go" => "gopls", "rust" => "rust-analyzer", "zig" => "zls", "c" => "clangd", "cpp" => "clangd",
|
|
86
|
+
"vue" => "vue-language-server --stdio", "python" => "ruff server", "ruby" => "typeprof --lsp --stdio",
|
|
87
|
+
"typescript" => "typescript-language-server --stdio", "javascript" => "typescript-language-server --stdio",
|
|
88
|
+
"bash" => "bash-language-server start", "crystal" => "crystalline", "gleam" => "gleam lsp"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"mcp" => { "enabled" => true, "servers" => {}, "timeout" => 10, "call_timeout" => 30, "async_load" => nil }, # async_load nil = env-aware; per-server "timeout" overrides
|
|
92
|
+
"ast" => { "async_warm" => nil }, # nil = env-aware default (false under RUBIK_TEST); repl/lifecycle.rb warm_up!
|
|
93
|
+
"context" => {
|
|
94
|
+
"include" => [],
|
|
95
|
+
"exclude" => [],
|
|
96
|
+
"max_tokens" => 32_768, # subagent context-window fallback when the routed model spec carries no context_limit (subagent/instance.rb, subagent/supervisor.rb)
|
|
97
|
+
"compaction_strategy" => "hybrid", # "hybrid", "prune_tool_results", "sliding_window", "off"
|
|
98
|
+
"max_tokens_ratio" => 0.85,
|
|
99
|
+
"tool_result_prune_chars" => 1500,
|
|
100
|
+
"preserve_first_n" => 2,
|
|
101
|
+
"preserve_last_n" => 6,
|
|
102
|
+
"safety_margin_tokens" => 1024,
|
|
103
|
+
"auto_recover_on_overflow" => true,
|
|
104
|
+
"log_failed_turns" => true,
|
|
105
|
+
"tool_chronicle_in_history" => true, # Plan 85 D-2/D-3
|
|
106
|
+
"tool_chronicle_max_entries" => 15, # Plan 85 D-2/D-3
|
|
107
|
+
"merge_turn_tool_history" => true, # Plan 85 D-7
|
|
108
|
+
"merge_turn_tool_max_entries" => 20, # Plan 85 D-7
|
|
109
|
+
"emergency_truncate_strategy" => "head_tail" # EmergencyCompactor hard-truncate layer: "head_tail" | "head_only" | "tail_only"
|
|
110
|
+
},
|
|
111
|
+
"ui" => {
|
|
112
|
+
"verbosity" => "normal",
|
|
113
|
+
"color" => "auto",
|
|
114
|
+
"glyphs" => "unicode",
|
|
115
|
+
"banner" => true,
|
|
116
|
+
"spinner" => true,
|
|
117
|
+
"spinner_verbs" => [], # empty = built-in verb set (ui/theme.rb from_config)
|
|
118
|
+
"markdown" => "live",
|
|
119
|
+
"statusline" => true,
|
|
120
|
+
"statusline_template" => "{model} · ctx {ctx} · {dirty} · sandbox {sandbox}",
|
|
121
|
+
"input_box" => false,
|
|
122
|
+
"paste_join_window_ms" => 120, # Plan 91: lines arriving within this gap join into one paste turn
|
|
123
|
+
"quiet_notices" => false # Plan 105: buffer background notices to inbox until prompt/enter
|
|
124
|
+
},
|
|
125
|
+
"workspace" => {
|
|
126
|
+
"additional_roots" => [],
|
|
127
|
+
"allowed_external_dirs" => [],
|
|
128
|
+
"allow_external_writes" => false,
|
|
129
|
+
"auto_index_additional" => true
|
|
130
|
+
},
|
|
131
|
+
"plugins" => {
|
|
132
|
+
"load" => [],
|
|
133
|
+
"aliases" => {},
|
|
134
|
+
"autoload_dropin" => true
|
|
135
|
+
},
|
|
136
|
+
"skills" => {
|
|
137
|
+
"enabled" => true,
|
|
138
|
+
"paths" => [], # extra skill directories (absolute or root-relative)
|
|
139
|
+
"disabled" => [], # skill names excluded from discovery
|
|
140
|
+
"max_body_chars" => 8000 # keep below tools.max_output_chars (head_tail would cut the middle)
|
|
141
|
+
},
|
|
142
|
+
"subagents" => {
|
|
143
|
+
"max_concurrency" => 10,
|
|
144
|
+
"max_depth" => 2,
|
|
145
|
+
"default_max_steps" => 88,
|
|
146
|
+
"provider_concurrency" => 2,
|
|
147
|
+
"budget_wait_timeout" => 180,
|
|
148
|
+
"auto_apply_buffer" => true,
|
|
149
|
+
"network_retry_count" => 3,
|
|
150
|
+
"network_backoff_base" => 0.5,
|
|
151
|
+
"error_wait_timeout" => 300,
|
|
152
|
+
"question_timeout" => 300,
|
|
153
|
+
# Bitmiş subagent bu grace (sn) sonrasında reap edilir (0 = kapalı);
|
|
154
|
+
# tombstone_limit sorgulanabilir mezar taşı sayısını sınırlar:
|
|
155
|
+
"reap_finished_after" => 300,
|
|
156
|
+
"tombstone_limit" => 25,
|
|
157
|
+
# Maestro kapısı: maestro write_file/replace_file_content/run_command/
|
|
158
|
+
# run_tests araçlarını doğrudan ÇALIŞTIRAMAZ — subagent'a devir zorunlu
|
|
159
|
+
# (false = doktrin yalnız prompt düzeyinde, kapı kapalı):
|
|
160
|
+
"maestro_gate" => true,
|
|
161
|
+
"auto_pause_on_network_error" => true,
|
|
162
|
+
"reactive_wakeup" => true,
|
|
163
|
+
# Vakanüvis arka plan kronikçisi (storage/vakanuvis; plan
|
|
164
|
+
# 2026-10-04 K1-K9): deterministik annal + wiki derleyicisi.
|
|
165
|
+
# enabled: Düzlem 1 harness'i kur/kurulama; distiller: Düzlem 2
|
|
166
|
+
# LLM doğumu (token üst sınırı flush_events x max_steps);
|
|
167
|
+
# flush_events: damıtıcı doğumu için olay eşiği; model: nil =
|
|
168
|
+
# root provider; retention: supersede (iz bırak) | purge (sil);
|
|
169
|
+
# root_turns: REPL kök-turn'leri de annal'a yazılsın mı
|
|
170
|
+
# (root-turn planı 2026-10-04; kapalıyken abonelik kurulmaz).
|
|
171
|
+
"vakanuvis" => {
|
|
172
|
+
"enabled" => true,
|
|
173
|
+
"distiller" => true,
|
|
174
|
+
"max_steps" => 8,
|
|
175
|
+
"flush_events" => 10,
|
|
176
|
+
"model" => nil,
|
|
177
|
+
"retention" => "supersede",
|
|
178
|
+
"root_turns" => true
|
|
179
|
+
},
|
|
180
|
+
# Pruner (repl/pruner): yarı deterministik KÖK pencere temizleyicisi.
|
|
181
|
+
# DEFAULT ON (kullanıcı kararı 2026-10-10) — deterministik yığın
|
|
182
|
+
# (trim_conversation_window! / ContextCompactor / EmergencyCompactor)
|
|
183
|
+
# her zaman güvenlik ağıdır; subagent kanalları bu özellikten asla
|
|
184
|
+
# etkilenmez; opt-out: "enabled" => false (kill-switch). enabled:
|
|
185
|
+
# harness'i kur (prune_context aracı + !prune komutu açılır);
|
|
186
|
+
# trigger_ratio: turn sonu pencere tahmini girdi tavanının bu
|
|
187
|
+
# oranına ulaşınca LLM budayıcı doğar (0.85'lik kör trim'den ÖNCE
|
|
188
|
+
# anlamsal sıkıştırma); target_ratio: plan yalnızca sonuç bu
|
|
189
|
+
# orana sığıyorsa uygulanır; max_steps/model: budayıcı bütçesi
|
|
190
|
+
# (nil = root provider); min_turns_between: otomatik doğum oranı
|
|
191
|
+
# sınırlayıcısı (turn sayısı).
|
|
192
|
+
"pruner" => {
|
|
193
|
+
"enabled" => true,
|
|
194
|
+
"trigger_ratio" => 0.6,
|
|
195
|
+
"target_ratio" => 0.35,
|
|
196
|
+
"max_steps" => 16,
|
|
197
|
+
"model" => nil,
|
|
198
|
+
"min_turns_between" => 5
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
# Goal ledger (SKILL.state ailesi, plan A): !goal'nun yapılandırılmış
|
|
202
|
+
# ΔΣ defteri — DEFAULT ON (kullanıcı kararı 2026-10-10; opt-out:
|
|
203
|
+
# enabled=false, o zaman araç/prompt/storage yüzü tamamen sessiz).
|
|
204
|
+
# max_patch_retries: ardışık reddedilen update_goal_ledger bütçesi.
|
|
205
|
+
# B/D anahtarları (verify / observe_projection) aynı hash'e eklenir.
|
|
206
|
+
"goal" => {
|
|
207
|
+
"ledger" => { "enabled" => true, "verify" => true, "observe_projection" => true,
|
|
208
|
+
"observe_window" => 5, "max_patch_retries" => 2 }
|
|
209
|
+
},
|
|
210
|
+
"project" => { "test_command" => nil },
|
|
211
|
+
"test" => { "timeout" => 30, "runners" => {} }, # run_tests timeout (adapter/base.rb); per-language runner overrides (exec.rb)
|
|
212
|
+
"adapter" => {}
|
|
213
|
+
}.freeze
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
def validate_schema(config, schema = DEFAULT_CONFIG, prefix = "", strict: false)
|
|
218
|
+
raise ArgumentError, "Configuration must be a Hash, got #{config.class}" unless config.is_a?(Hash)
|
|
219
|
+
|
|
220
|
+
config.each do |key, val|
|
|
221
|
+
current_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
|
|
222
|
+
if schema.is_a?(Hash) && !schema.key?(key.to_s)
|
|
223
|
+
candidate_keys = schema.keys
|
|
224
|
+
candidates = if defined?(Rubik::Tools::FuzzyMatcher)
|
|
225
|
+
Rubik::Tools::FuzzyMatcher.best_matches(key.to_s, candidate_keys)
|
|
226
|
+
else
|
|
227
|
+
[]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
suggestion_msg = if candidates && !candidates.empty?
|
|
231
|
+
suggested_full = prefix.empty? ? candidates.first : "#{prefix}.#{candidates.first}"
|
|
232
|
+
"Unknown config key '#{current_key}'. Did you mean '#{suggested_full}'?"
|
|
233
|
+
else
|
|
234
|
+
"Unknown config key '#{current_key}'."
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
raise ArgumentError, suggestion_msg if strict
|
|
238
|
+
|
|
239
|
+
@warnings << suggestion_msg
|
|
240
|
+
|
|
241
|
+
elsif val.is_a?(Hash) && schema.is_a?(Hash) && schema[key.to_s].is_a?(Hash) && !schema[key.to_s].empty?
|
|
242
|
+
validate_schema(val, schema[key.to_s], current_key, strict: strict)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Provider/model catalog CRUD (G21 extraction from config.rb): the mutating
|
|
4
|
+
# verbs over the `models.<role>` and `providers.<name>` storage shapes. The
|
|
5
|
+
# read-side `model_spec` stays typed access in config.rb (Plan 66 ruling).
|
|
6
|
+
# Public API is unchanged — these are Config instance methods via include;
|
|
7
|
+
# config never prints, outcome hashes (ok/error/reason) let callers report.
|
|
8
|
+
module Rubik
|
|
9
|
+
class Config
|
|
10
|
+
module ModelCatalog
|
|
11
|
+
def set_model_spec(role, provider:, name:, endpoint: nil, host: nil, persist: nil)
|
|
12
|
+
role_str = role.to_s.downcase
|
|
13
|
+
old_spec = fetch("models.#{role_str}")
|
|
14
|
+
@data["models"] ||= {}
|
|
15
|
+
spec_hash = {
|
|
16
|
+
"provider" => provider.to_s,
|
|
17
|
+
"name" => name.to_s
|
|
18
|
+
}
|
|
19
|
+
target_endpoint = endpoint || host
|
|
20
|
+
spec_hash["endpoint"] = target_endpoint if target_endpoint
|
|
21
|
+
@data["models"][role_str] = spec_hash
|
|
22
|
+
|
|
23
|
+
@data["model"] = spec_hash.dup if role_str == "default"
|
|
24
|
+
|
|
25
|
+
res = persisting?(persist) ? persist_result(save!(persist)) : { ok: true }
|
|
26
|
+
res[:reason] = :persist_failed unless res[:ok]
|
|
27
|
+
notify_change("models.#{role_str}", spec_hash, old_spec)
|
|
28
|
+
notify_change("model", spec_hash, old_spec) if role_str == "default"
|
|
29
|
+
res
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# ── Provider-model catalog (providers.<name> storage shape) ──────────
|
|
33
|
+
# The spec hash and its "models" array (Hash entries with "name" /
|
|
34
|
+
# "context_limit"; legacy bare String names tolerated) are storage
|
|
35
|
+
# shape: commands ask intent here instead of re-encoding it per
|
|
36
|
+
# !model/!provider branch.
|
|
37
|
+
|
|
38
|
+
# Live spec hash for `provider`, or nil when unknown (absent or empty —
|
|
39
|
+
# the historical !provider "not found" semantics).
|
|
40
|
+
def provider_spec(provider)
|
|
41
|
+
spec = fetch("providers.#{provider}")
|
|
42
|
+
spec.is_a?(Hash) && !spec.empty? ? spec : nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Replace (or append) provider's catalog entry for `model` with the
|
|
46
|
+
# given context limit. {ok: false, reason: :unknown_provider} when the
|
|
47
|
+
# provider is unknown.
|
|
48
|
+
def upsert_provider_model(provider, model, context_limit:, persist: nil)
|
|
49
|
+
mutate_provider_models(provider, persist: persist) do |models|
|
|
50
|
+
models.reject! { |m| model_entry_name(m) == model }
|
|
51
|
+
models << { "name" => model, "context_limit" => context_limit }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Drop provider's catalog entry for `model`. {ok: false,
|
|
56
|
+
# reason: :unknown_provider} when the provider is unknown; removing an
|
|
57
|
+
# absent model is a successful no-op (matches the historical
|
|
58
|
+
# !provider remove-model report).
|
|
59
|
+
def remove_provider_model(provider, model, persist: nil)
|
|
60
|
+
mutate_provider_models(provider, persist: persist) do |models|
|
|
61
|
+
models.reject! { |m| model_entry_name(m) == model }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Drop a provider and its catalog entirely. Like the model verbs this
|
|
66
|
+
# notifies no listener — callers refresh the runtime explicitly. Routed
|
|
67
|
+
# through delete so the removal survives the disk merge.
|
|
68
|
+
def remove_provider(provider, persist: nil)
|
|
69
|
+
return { ok: false, error: "Provider '#{provider}' not found", reason: :unknown_provider } unless provider_spec(provider)
|
|
70
|
+
|
|
71
|
+
delete("providers.#{provider}", persist: persist)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def mutate_provider_models(provider, persist:)
|
|
77
|
+
p_spec = provider_spec(provider)
|
|
78
|
+
return { ok: false, error: "Provider '#{provider}' not found", reason: :unknown_provider } unless p_spec
|
|
79
|
+
|
|
80
|
+
models = Array(p_spec["models"]).dup
|
|
81
|
+
yield models
|
|
82
|
+
p_spec["models"] = models
|
|
83
|
+
set("providers.#{provider}", p_spec, persist: persist)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Catalog entries are Hash-shaped today; older configs stored bare model
|
|
87
|
+
# name Strings. One predicate keeps the tolerance from drifting per verb.
|
|
88
|
+
def model_entry_name(entry)
|
|
89
|
+
entry.is_a?(Hash) ? entry["name"] : entry.to_s
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|