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,481 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "digest"
|
|
6
|
+
require "time"
|
|
7
|
+
# save_manifest atomik yazımı Platform.atomic_write kullanır; dosyanın tek başına
|
|
8
|
+
# require edilmesi durumunda da çözümlenebilsin diye açık yüklenir.
|
|
9
|
+
require_relative "../platform"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module Runtime
|
|
13
|
+
class OverlayStorage
|
|
14
|
+
attr_reader :root_dir, :home_dir
|
|
15
|
+
|
|
16
|
+
COMMAND_SENTINEL = "# Rubik Custom Command:"
|
|
17
|
+
TOOL_SENTINEL = "# Rubik Custom Tool:"
|
|
18
|
+
|
|
19
|
+
def initialize(root_dir: Dir.pwd, home_dir: ENV.fetch("HOME", nil), guard: nil)
|
|
20
|
+
@root_dir = File.expand_path(root_dir)
|
|
21
|
+
@home_dir = File.expand_path(home_dir || ENV["HOME"] || ".")
|
|
22
|
+
@guard = guard
|
|
23
|
+
ensure_directories
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def project_commands_dir
|
|
27
|
+
File.join(@root_dir, ".rubik", "commands")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def global_commands_dir
|
|
31
|
+
File.join(@home_dir, ".rubik", "commands")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def project_tools_dir
|
|
35
|
+
File.join(@root_dir, ".rubik", "tools")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def global_tools_dir
|
|
39
|
+
File.join(@home_dir, ".rubik", "tools")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ensure_directories
|
|
43
|
+
[project_commands_dir, global_commands_dir, project_tools_dir, global_tools_dir].each do |dir|
|
|
44
|
+
FileUtils.mkdir_p(dir)
|
|
45
|
+
rescue SystemCallError => e
|
|
46
|
+
warn "[WARN] Failed to create overlay directory #{dir}: #{e.message}" if ENV["RUBIK_DEBUG"]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def manifest_path(dir)
|
|
51
|
+
File.join(dir, ".manifest.json")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_manifest(dir)
|
|
55
|
+
path = manifest_path(dir)
|
|
56
|
+
return {} unless File.exist?(path)
|
|
57
|
+
|
|
58
|
+
JSON.parse(File.read(path))
|
|
59
|
+
rescue JSON::ParserError
|
|
60
|
+
{}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def save_manifest(dir, manifest)
|
|
64
|
+
# Atomik yazım (tmp + rename): kilitsiz okuyucuların (load_manifest, manifest_state)
|
|
65
|
+
# yarım manifest görmesini engeller; yarım okuma, kayıtlı overlay'lerin "unknown"
|
|
66
|
+
# sayılıp kalıcı olarak reddedilmesine yol açabiliyordu. Platform.atomic_write,
|
|
67
|
+
# Windows için unlink/rename yedeğiyle bu düzeni sağlar.
|
|
68
|
+
Platform.atomic_write(manifest_path(dir), JSON.pretty_generate(manifest))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalize_content(raw)
|
|
72
|
+
raw.delete("\r")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def record_hash(dir, filename, scope)
|
|
76
|
+
if manifest_state(dir) == :corrupt
|
|
77
|
+
# Bozuk manifesti sessizce "heal" etmek (yalnızca yeni kaydı yazmak) diğer
|
|
78
|
+
# kayıtlı overlay'leri tahliye eder; bunun yerine kaydı reddet.
|
|
79
|
+
raise "cannot record '#{filename}': .manifest.json in #{dir} is corrupt; delete it and re-record overlays"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
path = File.join(dir, filename)
|
|
83
|
+
hash = Digest::SHA256.hexdigest(normalize_content(File.binread(path)))
|
|
84
|
+
manifest = load_manifest(dir)
|
|
85
|
+
manifest[filename] = {
|
|
86
|
+
"sha256" => hash,
|
|
87
|
+
"scope" => scope.to_s,
|
|
88
|
+
"created_at" => Time.now.iso8601
|
|
89
|
+
}
|
|
90
|
+
save_manifest(dir, manifest)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def verify_integrity(file_path, dir, bootstrap_eligible: false)
|
|
94
|
+
return :trusted if trust_mode?
|
|
95
|
+
|
|
96
|
+
basename = File.basename(file_path)
|
|
97
|
+
manifest = load_manifest(dir)
|
|
98
|
+
current_content = File.exist?(file_path) ? File.binread(file_path) : ""
|
|
99
|
+
current_hash = Digest::SHA256.hexdigest(normalize_content(current_content))
|
|
100
|
+
|
|
101
|
+
if manifest.key?(basename)
|
|
102
|
+
# Known file — verify hash
|
|
103
|
+
expected_hash = manifest[basename]["sha256"]
|
|
104
|
+
if current_hash == expected_hash
|
|
105
|
+
:verified
|
|
106
|
+
else
|
|
107
|
+
warn "[SECURITY] Integrity check failed for overlay '#{basename}': file modified " \
|
|
108
|
+
"since synthesis. Skipping. (expected: #{expected_hash}, got: #{current_hash})"
|
|
109
|
+
:rejected
|
|
110
|
+
end
|
|
111
|
+
elsif bootstrap_eligible && sentinel_content?(current_content)
|
|
112
|
+
# Bootstrap (tek seferlik): yalnızca manifest dosyasının hiç var olmadığı ilk taramada,
|
|
113
|
+
# sentinel başlıklı dosya kabul edilip hash'i manifest'e yazılır; uygunluk kararını
|
|
114
|
+
# verified_dir_entries taramanın başında verir. Manifest varken bootstrap asla çalışmaz.
|
|
115
|
+
# Kabul ÖNCE synthesis guard'ından geçer (Aday 07 ertelemesi): klonlanan depodaki
|
|
116
|
+
# dosya bu ortamda sentezlenmemişti; reddedilen dosya manifest'e yazılmaz.
|
|
117
|
+
verdict = screen_bootstrap_content(current_content, basename)
|
|
118
|
+
return verdict unless verdict == :ok
|
|
119
|
+
|
|
120
|
+
scope_match = current_content.match(/# Scope:\s*(.*)/)
|
|
121
|
+
scope = scope_match ? scope_match[1].strip : "unknown"
|
|
122
|
+
|
|
123
|
+
manifest[basename] = {
|
|
124
|
+
"sha256" => current_hash,
|
|
125
|
+
"scope" => scope,
|
|
126
|
+
"created_at" => Time.now.iso8601
|
|
127
|
+
}
|
|
128
|
+
save_manifest(dir, manifest)
|
|
129
|
+
:bootstrapped
|
|
130
|
+
else
|
|
131
|
+
warn "[SECURITY] Unknown overlay file '#{basename}' not in manifest. Skipping."
|
|
132
|
+
:rejected
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def save_command(name, code, scope: :project, description: "Custom command")
|
|
137
|
+
clean_name = name.to_s.downcase.delete_prefix("!").gsub(/[^a-z0-9_:]/, "_")
|
|
138
|
+
target_dir = scope.to_sym == :global ? global_commands_dir : project_commands_dir
|
|
139
|
+
FileUtils.mkdir_p(target_dir)
|
|
140
|
+
|
|
141
|
+
filename = "#{clean_name.gsub(':', '_')}.rb"
|
|
142
|
+
file_path = File.join(target_dir, filename)
|
|
143
|
+
header = <<~RUBY
|
|
144
|
+
# frozen_string_literal: true
|
|
145
|
+
#{COMMAND_SENTINEL} !#{clean_name}
|
|
146
|
+
# Scope: #{scope}
|
|
147
|
+
# Description: #{description}
|
|
148
|
+
|
|
149
|
+
RUBY
|
|
150
|
+
|
|
151
|
+
File.write(file_path, "#{header}#{code.strip}\n")
|
|
152
|
+
record_hash(target_dir, filename, scope)
|
|
153
|
+
{ ok: true, path: file_path, name: clean_name, scope: scope }
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
{ ok: false, error: e.message }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Delete a synthesized command from disk and manifest. Symmetric to
|
|
159
|
+
# save_command: same clean_name normalization, scope→dir selection and
|
|
160
|
+
# { ok:, error: } shape. Fail-closed on a corrupt manifest — never wipe
|
|
161
|
+
# it with {} (mirror the record_hash guard on the save path).
|
|
162
|
+
def delete_command(name, scope: :project)
|
|
163
|
+
clean_name = name.to_s.downcase.delete_prefix("!").gsub(/[^a-z0-9_:]/, "_")
|
|
164
|
+
target_dir = scope.to_sym == :global ? global_commands_dir : project_commands_dir
|
|
165
|
+
filename = "#{clean_name.gsub(':', '_')}.rb"
|
|
166
|
+
file_path = File.join(target_dir, filename)
|
|
167
|
+
|
|
168
|
+
raise "cannot delete '#{filename}': .manifest.json in #{target_dir} is corrupt; delete it and re-record overlays" if manifest_state(target_dir) == :corrupt
|
|
169
|
+
|
|
170
|
+
manifest = load_manifest(target_dir)
|
|
171
|
+
manifest.delete(filename)
|
|
172
|
+
save_manifest(target_dir, manifest)
|
|
173
|
+
|
|
174
|
+
FileUtils.rm_f(file_path)
|
|
175
|
+
{ ok: true, name: clean_name, scope: scope, path: file_path }
|
|
176
|
+
rescue StandardError => e
|
|
177
|
+
{ ok: false, error: e.message }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def list_commands
|
|
181
|
+
# Global commands; project commands (can override global) — sıra ve dönüş şekli korunur.
|
|
182
|
+
verified_dir_entries(global_commands_dir, :global) +
|
|
183
|
+
verified_dir_entries(project_commands_dir, :project)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def load_all(shell, _manager = nil)
|
|
187
|
+
loaded = []
|
|
188
|
+
list_commands.each do |cmd_meta|
|
|
189
|
+
next if cmd_meta[:integrity] == :rejected
|
|
190
|
+
|
|
191
|
+
file_path = cmd_meta[:path]
|
|
192
|
+
cmd_name = cmd_meta[:name]
|
|
193
|
+
|
|
194
|
+
begin
|
|
195
|
+
code = File.read(file_path)
|
|
196
|
+
eval_command(shell, cmd_name, code, cmd_meta[:scope])
|
|
197
|
+
loaded << cmd_name
|
|
198
|
+
rescue StandardError => e
|
|
199
|
+
warn "[OVERLAY] Failed to load custom command '#{cmd_name}' from #{file_path}: #{e.class}: #{e.message}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
loaded
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def eval_command(shell, cmd_name, code, scope)
|
|
206
|
+
desc_match = code.match(/# Description:\s*(.*)/)
|
|
207
|
+
description = desc_match ? desc_match[1].strip : "Custom #{scope} command"
|
|
208
|
+
|
|
209
|
+
proc_handler = if code.include?("->") || code.include?("lambda") || code.include?("proc")
|
|
210
|
+
eval(code, clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
|
|
211
|
+
else
|
|
212
|
+
eval("->(args, output, shell) {\n#{code}\n}", clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
registered = shell.register_command(cmd_name, description: "#{description} [#{scope}]", custom: true) do |args, ctx|
|
|
216
|
+
# Arity contract mirrored from SynthesizeCommandTool: 2-arity
|
|
217
|
+
# handlers receive the per-dispatch CommandContext (its IO
|
|
218
|
+
# methods delegate to the active capture stream), 3-arity keeps
|
|
219
|
+
# the raw shell for trusted persisted code. Direct invocation
|
|
220
|
+
# outside dispatch has no ctx: use the shell's factory, or its
|
|
221
|
+
# raw output for shell doubles predating the contract. The warn
|
|
222
|
+
# keeps a ctx-forgetting dispatch site visible instead of
|
|
223
|
+
# silently auto-healed.
|
|
224
|
+
out_ctx = if ctx
|
|
225
|
+
ctx
|
|
226
|
+
elsif shell.respond_to?(:command_context)
|
|
227
|
+
warn "[rubik] overlay command invoked without a dispatch CommandContext" unless ENV["RUBIK_TEST"]
|
|
228
|
+
shell.command_context
|
|
229
|
+
else
|
|
230
|
+
shell.output
|
|
231
|
+
end
|
|
232
|
+
if proc_handler.arity == 1
|
|
233
|
+
proc_handler.call(args)
|
|
234
|
+
elsif proc_handler.arity == 2
|
|
235
|
+
proc_handler.call(args, out_ctx)
|
|
236
|
+
else
|
|
237
|
+
proc_handler.call(args, out_ctx.respond_to?(:out) ? out_ctx.out : out_ctx, shell)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
# register_command, built-in çakışmasında sessizce false döner; red,
|
|
241
|
+
# load_all'un [OVERLAY] yoluna düşsün ve loaded listesi doğru kalsın.
|
|
242
|
+
raise "registration refused for '#{cmd_name}' (name collides with a built-in)" if registered == false
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def save_tool(name, code, scope: :project, description: "Custom native tool", params_schema: nil, risk: :execute)
|
|
246
|
+
clean_name = name.to_s.downcase.gsub(/[^a-z0-9_]/, "_")
|
|
247
|
+
target_dir = scope.to_sym == :global ? global_tools_dir : project_tools_dir
|
|
248
|
+
FileUtils.mkdir_p(target_dir)
|
|
249
|
+
|
|
250
|
+
filename = "#{clean_name}.rb"
|
|
251
|
+
file_path = File.join(target_dir, filename)
|
|
252
|
+
schema_json = params_schema ? JSON.dump(params_schema) : "{}"
|
|
253
|
+
header = <<~RUBY
|
|
254
|
+
# frozen_string_literal: true
|
|
255
|
+
#{TOOL_SENTINEL} #{clean_name}
|
|
256
|
+
# Scope: #{scope}
|
|
257
|
+
# Risk: #{risk}
|
|
258
|
+
# Description: #{description}
|
|
259
|
+
# Schema: #{schema_json}
|
|
260
|
+
|
|
261
|
+
RUBY
|
|
262
|
+
|
|
263
|
+
File.write(file_path, "#{header}#{code.strip}\n")
|
|
264
|
+
record_hash(target_dir, filename, scope)
|
|
265
|
+
{ ok: true, path: file_path, name: clean_name, scope: scope }
|
|
266
|
+
rescue StandardError => e
|
|
267
|
+
# Audit 1: surfaced to the caller as ok:false and logged under RUBIK_DEBUG.
|
|
268
|
+
warn "[WARN] [OVERLAY] Failed to save tool '#{name}' to #{file_path}: #{e.class}: #{e.message}" if ENV["RUBIK_DEBUG"]
|
|
269
|
+
{ ok: false, error: e.message }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Delete a synthesized tool from disk and manifest. Symmetric to
|
|
273
|
+
# save_tool: same clean_name normalization, scope→dir selection and
|
|
274
|
+
# { ok:, error: } shape. Fail-closed on a corrupt manifest — never wipe
|
|
275
|
+
# it with {} (mirror the record_hash guard on the save path).
|
|
276
|
+
def delete_tool(name, scope: :project)
|
|
277
|
+
clean_name = name.to_s.downcase.gsub(/[^a-z0-9_]/, "_")
|
|
278
|
+
target_dir = scope.to_sym == :global ? global_tools_dir : project_tools_dir
|
|
279
|
+
filename = "#{clean_name}.rb"
|
|
280
|
+
file_path = File.join(target_dir, filename)
|
|
281
|
+
|
|
282
|
+
raise "cannot delete '#{filename}': .manifest.json in #{target_dir} is corrupt; delete it and re-record overlays" if manifest_state(target_dir) == :corrupt
|
|
283
|
+
|
|
284
|
+
manifest = load_manifest(target_dir)
|
|
285
|
+
manifest.delete(filename)
|
|
286
|
+
save_manifest(target_dir, manifest)
|
|
287
|
+
|
|
288
|
+
FileUtils.rm_f(file_path)
|
|
289
|
+
{ ok: true, name: clean_name, scope: scope, path: file_path }
|
|
290
|
+
rescue StandardError => e
|
|
291
|
+
{ ok: false, error: e.message }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def list_tools
|
|
295
|
+
verified_dir_entries(global_tools_dir, :global) +
|
|
296
|
+
verified_dir_entries(project_tools_dir, :project)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def load_tools(tool_registry)
|
|
300
|
+
loaded = []
|
|
301
|
+
list_tools.each do |t_meta|
|
|
302
|
+
next if t_meta[:integrity] == :rejected
|
|
303
|
+
|
|
304
|
+
code = File.read(t_meta[:path])
|
|
305
|
+
tool_inst = eval_tool(t_meta[:name], code, t_meta[:scope])
|
|
306
|
+
if tool_inst
|
|
307
|
+
tool_registry.register(tool_inst, origin: :overlay)
|
|
308
|
+
loaded << t_meta[:name]
|
|
309
|
+
else
|
|
310
|
+
warn "[OVERLAY] Custom tool '#{t_meta[:name]}' from #{t_meta[:path]} did not evaluate to a usable tool."
|
|
311
|
+
end
|
|
312
|
+
rescue StandardError => e
|
|
313
|
+
warn "[OVERLAY] Failed to load custom tool '#{t_meta[:name]}' from #{t_meta[:path]}: #{e.class}: #{e.message}"
|
|
314
|
+
end
|
|
315
|
+
loaded
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def eval_tool(name, code, scope)
|
|
319
|
+
desc_match = code.match(/# Description:\s*(.*)/)
|
|
320
|
+
description = desc_match ? desc_match[1].strip : "Custom #{scope} tool"
|
|
321
|
+
|
|
322
|
+
schema_match = code.match(/# Schema:\s*(.*)/)
|
|
323
|
+
schema = if schema_match
|
|
324
|
+
begin
|
|
325
|
+
JSON.parse(schema_match[1])
|
|
326
|
+
rescue JSON::ParserError
|
|
327
|
+
{}
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
{}
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
risk_match = code.match(/# Risk:\s*(.*)/)
|
|
334
|
+
risk = risk_match ? risk_match[1].strip.to_sym : :execute
|
|
335
|
+
# Hand-authored overlays may carry non-enum risk words; the
|
|
336
|
+
# Declaration seam validates read/write/execute, so clamp
|
|
337
|
+
# conservatively (execute = the most-guarded class) instead of
|
|
338
|
+
# dropping the tool at load.
|
|
339
|
+
risk = :execute unless [:read, :write, :execute].include?(risk)
|
|
340
|
+
|
|
341
|
+
evaluated = eval(code, clean_eval_binding, __FILE__, __LINE__) # rubocop:disable Security/Eval -- overlay çekirdeği; sentez anında (veya bootstrap kabulünde) guard kontrolünden geçti VE disk hash'i manifestle doğrulandı (verify_integrity)
|
|
342
|
+
if evaluated.is_a?(Class) && evaluated < Rubik::Tools::BaseTool
|
|
343
|
+
evaluated.new
|
|
344
|
+
elsif evaluated.respond_to?(:call)
|
|
345
|
+
Class.new(Rubik::Tools::BaseTool) do
|
|
346
|
+
define_method(:initialize) do
|
|
347
|
+
super(name: name, description: "#{description} [#{scope}]", risk: risk, params_schema: schema)
|
|
348
|
+
@handler = evaluated
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
define_method(:execute) do |args = {}|
|
|
352
|
+
@handler.call(args)
|
|
353
|
+
end
|
|
354
|
+
end.new
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
private
|
|
359
|
+
|
|
360
|
+
# Bootstrap kabul taramasında kullanılacak guard: kwarg'la geçilen örnek
|
|
361
|
+
# (üretimde service catalog'un tek operatör-policy guard'ı) ya da lazy
|
|
362
|
+
# default. Lazy çözüm bilinçli: overlay_storage.rb standalone require
|
|
363
|
+
# edilebilen bir dosyadır; security/guard yalnızca tarama gerçekten
|
|
364
|
+
# gerektiğinde yüklenir, load-order denetimi gerekmez.
|
|
365
|
+
def bootstrap_guard
|
|
366
|
+
@bootstrap_guard ||= begin
|
|
367
|
+
require_relative "../security/guard"
|
|
368
|
+
@guard || Rubik::Security::Guard.new
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Aday 07'den ertelenen güvenlik kalemi: manifestsız dizindeki sentinel
|
|
373
|
+
# dosya manifest'e yazılmadan önce synthesis guard'ından geçirilir.
|
|
374
|
+
# Reddedilen (veya taranamayan) dosya manifest'e YAZILMAZ — sonraki tarama
|
|
375
|
+
# yeniden tarar ve reddeder (sessiz heal, bozuk-manifest doktriniyle
|
|
376
|
+
# çelişir); load_all/load_tools'un mevcut :rejected atlaması devreye girer.
|
|
377
|
+
# Parse hatası guard içinde yutulur (çalıştırılamaz kod güvenlik olayı
|
|
378
|
+
# değil); guard'ın kendisi patlarsa da dosya fail-closed reddedilir —
|
|
379
|
+
# SystemStackError dahil: rakip içerikte derin yuvalama AST walk'ı
|
|
380
|
+
# taşırebilir, tek dosyanın reddiyesi tüm taramayı çökertmemeli.
|
|
381
|
+
# Bilinçli asimetri: dosyanın TAMAMI (Description/Schema başlığı dahil)
|
|
382
|
+
# taranır — sentez anında yalnızca kod taranıyordu. Kabul kapısı daha
|
|
383
|
+
# sıkı olmak güvenli yöndür; başlık metnine denk gelen nadir yanlış
|
|
384
|
+
# pozitif dosyayı makinede yeniden kayıtla çözülür.
|
|
385
|
+
# Emit doktrini (guard.rb run_synthesis_gate hükmü): güvenlik kararının
|
|
386
|
+
# tek kullanıcı-yüzü izi $VERBOSE=nil altında da yaşamalıdır — $stderr.puts.
|
|
387
|
+
def screen_bootstrap_content(content, basename)
|
|
388
|
+
bootstrap_guard.verify_synthesis_safety!(content)
|
|
389
|
+
:ok
|
|
390
|
+
rescue Rubik::Security::BlockedWriteError => e
|
|
391
|
+
$stderr.puts "[SECURITY] Bootstrap overlay '#{basename}' refused by synthesis guard: #{e.message}" # rubocop:disable Style/StderrPuts
|
|
392
|
+
:rejected
|
|
393
|
+
rescue StandardError, ScriptError, SystemStackError => e
|
|
394
|
+
$stderr.puts "[SECURITY] Bootstrap overlay '#{basename}' could not be screened (#{e.class}: #{e.message}); refusing." # rubocop:disable Style/StderrPuts
|
|
395
|
+
:rejected
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Ortak kaçış kapısı predicate'i: iki güvenlik kapısının (verify_integrity,
|
|
399
|
+
# verified_dir_entries) kopya ENV kontrolüyle sürünerek ayrışmasını engeller.
|
|
400
|
+
def trust_mode?
|
|
401
|
+
ENV["RUBIK_TRUST_OVERLAYS"] == "1"
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Üç durumlu manifest yoklaması: :missing / :valid / :corrupt.
|
|
405
|
+
# load_manifest bilinçli olarak değiştirilmedi; kayıt yolu (record_hash) ve
|
|
406
|
+
# save_command/save_tool akışları bozuk manifestte {} davranışına bağlı kalıyor.
|
|
407
|
+
def manifest_state(dir)
|
|
408
|
+
path = manifest_path(dir)
|
|
409
|
+
return :missing unless File.exist?(path)
|
|
410
|
+
|
|
411
|
+
parsed = JSON.parse(File.read(path))
|
|
412
|
+
parsed.is_a?(Hash) ? :valid : :corrupt
|
|
413
|
+
rescue JSON::ParserError
|
|
414
|
+
:corrupt
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Dizin taraması başına bootstrap uygunluğu: manifest dosyası hiç yokken sentinel
|
|
418
|
+
# başlıklı dosyalar bir kez kabul edilir; manifest varken kayıtsız dosyalar her
|
|
419
|
+
# koşulda reddedilir. Bozuk manifest, o taramada dizini tamamen dışlar.
|
|
420
|
+
def verified_dir_entries(dir, scope)
|
|
421
|
+
files = Dir.glob(File.join(dir, "*.rb"))
|
|
422
|
+
|
|
423
|
+
# Kaçış kapısı: trust modu manifest durumundan bağımsız olarak tüm dosyaları güvenilir sayar.
|
|
424
|
+
return files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: :trusted } } if trust_mode?
|
|
425
|
+
|
|
426
|
+
case manifest_state(dir)
|
|
427
|
+
when :corrupt
|
|
428
|
+
warn "[SECURITY] Corrupt manifest in #{dir}; skipping #{files.size} overlay(s)."
|
|
429
|
+
[]
|
|
430
|
+
when :missing
|
|
431
|
+
if bootstrap_opt_in?
|
|
432
|
+
bootstrapped = 0
|
|
433
|
+
entries = files.map do |f|
|
|
434
|
+
integrity = verify_integrity(f, dir, bootstrap_eligible: true)
|
|
435
|
+
bootstrapped += 1 if integrity == :bootstrapped
|
|
436
|
+
{ name: File.basename(f, ".rb"), scope: scope, path: f, integrity: integrity }
|
|
437
|
+
end
|
|
438
|
+
if bootstrapped.positive?
|
|
439
|
+
warn "[BOOTSTRAP] First scan of #{dir}: recorded #{bootstrapped} overlay(s) into .manifest.json; " \
|
|
440
|
+
"unknown files will be rejected from now on."
|
|
441
|
+
end
|
|
442
|
+
entries
|
|
443
|
+
else
|
|
444
|
+
# Opt-in yokken ilk tarama bile bootstrap yapmaz (yabancı depo klonu senaryosu);
|
|
445
|
+
# sentinel dosyalar görünür tek satırlık bildirimle atlanır.
|
|
446
|
+
gated = files.count { |f| sentinel_file?(f) }
|
|
447
|
+
if gated.positive?
|
|
448
|
+
warn "[BOOTSTRAP] #{gated} unrecorded sentinel overlay(s) in #{dir} were skipped; " \
|
|
449
|
+
"first-time acceptance requires RUBIK_BOOTSTRAP_OVERLAYS=1."
|
|
450
|
+
end
|
|
451
|
+
files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: verify_integrity(f, dir) } }
|
|
452
|
+
end
|
|
453
|
+
else
|
|
454
|
+
files.map { |f| { name: File.basename(f, ".rb"), scope: scope, path: f, integrity: verify_integrity(f, dir) } }
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# İlk-bootstrap açık rızası: manifest'siz dizindeki sentinel dosyaların ilk taramada
|
|
459
|
+
# kabul edilmesi ancak bu env ile mümkündür (kayıt kalıcıdır; sonraki açılışlar env'siz çalışır).
|
|
460
|
+
def bootstrap_opt_in?
|
|
461
|
+
ENV["RUBIK_BOOTSTRAP_OVERLAYS"] == "1"
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def sentinel_content?(content)
|
|
465
|
+
content.include?(COMMAND_SENTINEL) || content.include?(TOOL_SENTINEL)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def sentinel_file?(path)
|
|
469
|
+
File.file?(path) && sentinel_content?(File.binread(path))
|
|
470
|
+
rescue StandardError => e
|
|
471
|
+
# Audit 1: unreadable => fail-closed (not a sentinel), but traceable.
|
|
472
|
+
warn "[WARN] [OVERLAY] sentinel check failed for #{path}: #{e.class}: #{e.message}" if ENV["RUBIK_DEBUG"]
|
|
473
|
+
false
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def clean_eval_binding
|
|
477
|
+
Object.new.instance_eval { binding }
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Runtime
|
|
5
|
+
# Declarative vocabulary entry for one composed service (architecture-
|
|
6
|
+
# review Candidate 05). A ServiceDefinition is pure data: it names a
|
|
7
|
+
# service, binds it to the coeffect key it is published under, and flags
|
|
8
|
+
# how the composition root should carry it (Parts member, shell ivar,
|
|
9
|
+
# shell reader, wire-time provide). It never builds anything and never
|
|
10
|
+
# touches a context.
|
|
11
|
+
#
|
|
12
|
+
# owns : the field semantics of a service declaration
|
|
13
|
+
# leaves : entry ORDER and the entries themselves (REPL::ServiceCatalog
|
|
14
|
+
# — the composition root's table); key STRINGS and legacy
|
|
15
|
+
# aliases (Rubik::Coeffects SSOT); how tool-layer defaults
|
|
16
|
+
# resolve (Tools::Services)
|
|
17
|
+
#
|
|
18
|
+
# Kinship: ComponentFiber declares `inject:` coeffect contracts for
|
|
19
|
+
# runtime consumers; ServiceDefinition declares the provider-side mirror
|
|
20
|
+
# for the composition root. TypeDefinition
|
|
21
|
+
# (orchestrator/subagent/type_registry.rb) is the structural precedent.
|
|
22
|
+
#
|
|
23
|
+
# Builders receive the catalog Scope (root_dir, home_dir, and hash access
|
|
24
|
+
# to already-constructed entries: scope[:guard]). Referencing an entry
|
|
25
|
+
# ordered later raises immediately — the construction-order invariant
|
|
26
|
+
# (guard before its consumers; runtime_manager before task_manager,
|
|
27
|
+
# skills and mcp; buffer before the subagent supervisor) is structural,
|
|
28
|
+
# not editorial.
|
|
29
|
+
ServiceDefinition = Struct.new(
|
|
30
|
+
:name, # Symbol — Parts member, Shell ivar, build_default override slot
|
|
31
|
+
:provide, # coeffect key published at wire time (Coeffects::* constant where
|
|
32
|
+
# one exists), nil when never provided (cache_store, recorder, ...)
|
|
33
|
+
:provide_from, # :ivar (default) | :parts | :self — where wire reads the value
|
|
34
|
+
:builder, # ->(scope) { instance }; nil only for the shell pseudo-entry
|
|
35
|
+
:input, # true for config-derived wiring values (extra_roots, ...): Parts
|
|
36
|
+
# member only — no ivar, no reader, no provide
|
|
37
|
+
:parts_member, # false ONLY for the shell pseudo-entry (provided, never built)
|
|
38
|
+
:optional, # true -> inject uses a respond_to? nil-guard (older parts fixtures)
|
|
39
|
+
:assign_ivar, # false -> Parts member + provide only, no Shell ivar (plugin_storage)
|
|
40
|
+
:reader, # false -> no derived Shell attr_reader (plugin_manager today)
|
|
41
|
+
:after_build, # ->(scope) { } runs right after this entry's construction
|
|
42
|
+
:after_inject, # ->(values, parts, injected_buffer) { } runs after ALL ivars are set
|
|
43
|
+
keyword_init: true
|
|
44
|
+
) do
|
|
45
|
+
def initialize(**)
|
|
46
|
+
super(parts_member: true, provide_from: :ivar, **)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def provided? = !provide.nil?
|
|
50
|
+
def injectable? = !input && assign_ivar != false && parts_member != false
|
|
51
|
+
def expose_reader? = injectable? && reader != false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Runtime
|
|
5
|
+
# Shared spine of the service register-shims (Runtime::ASTService,
|
|
6
|
+
# Runtime::IndexService, Runtime::LoopService, UI::RenderService —
|
|
7
|
+
# architecture-review Candidate 05): the extraction -> body -> reconcile
|
|
8
|
+
# sequence each shim carried as a private, slightly divergent copy.
|
|
9
|
+
#
|
|
10
|
+
# owns : carrier->context extraction (a Manager hands over its
|
|
11
|
+
# #context; anything else IS the context; nil stays nil), the
|
|
12
|
+
# trailing #reconcile_all trigger, nil-carrier tolerance
|
|
13
|
+
# leaves : the provided value and the local inverses (the yield body —
|
|
14
|
+
# each shim keeps its policy local: ASTService's
|
|
15
|
+
# error-suppressed cache save, RenderService's
|
|
16
|
+
# first-registration spinner latch), and fiber-shaped
|
|
17
|
+
# registration (LoopService passes reconcile: false because
|
|
18
|
+
# Manager#register_fiber reconciles itself)
|
|
19
|
+
#
|
|
20
|
+
# The yield receives the extracted ctx and its return value becomes the
|
|
21
|
+
# spine's return value, so shims keep their exact signatures and returns.
|
|
22
|
+
module ServiceRegistration
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def call(carrier, reconcile: true)
|
|
26
|
+
ctx = carrier.respond_to?(:context) ? carrier.context : carrier
|
|
27
|
+
result = yield ctx
|
|
28
|
+
carrier.reconcile_all if reconcile && ctx && carrier.respond_to?(:reconcile_all)
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|