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
data/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# 🎲 Rubik — Autonomous AI Coding Agent & REPL Harness
|
|
2
|
+
|
|
3
|
+
English | [Türkçe](README.tr.md)
|
|
4
|
+
|
|
5
|
+
> A next-generation autonomous pair-programming and REPL harness written in pure Ruby, built on the **Spatiotemporal Composability & Self-Evolving Runtime** architecture.
|
|
6
|
+
|
|
7
|
+
[](https://rubygems.org/gems/rubik)
|
|
8
|
+
[](https://rubygems.org/gems/rubik)
|
|
9
|
+
[](https://www.ruby-lang.org)
|
|
10
|
+
[]()
|
|
11
|
+
[](AGENTS.md)
|
|
12
|
+
[](COMMANDS.md)
|
|
13
|
+
[](CONFIG.md)
|
|
14
|
+
[]()
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 👋 What is Rubik?
|
|
19
|
+
|
|
20
|
+
Rubik is a terminal-native autonomous coding agent and interactive REPL in pure Ruby. A **maestro** agent orchestrates specialized subagents (research / coder / self) over a 34-tool belt while you keep the wheel: file edits stage in a reviewable in-memory buffer (`!diff` → `!apply`), every runtime side effect is registered with a LIFO inverse (zero-restart reloads and teardown), and failing services are diagnosed and repaired live. It runs cross-platform (Linux, macOS, Windows), talks to any OpenAI-compatible endpoint, Anthropic, or local Ollama, and the whole runtime is embeddable from Ruby with `require "rubik"`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🚀 Quick Start
|
|
25
|
+
|
|
26
|
+
### From the gem
|
|
27
|
+
|
|
28
|
+
Requires **Ruby 3.4+**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gem install rubik
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
or with Bundler:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
gem "rubik"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Platform notes
|
|
41
|
+
|
|
42
|
+
| Platform | Install experience |
|
|
43
|
+
| :--- | :--- |
|
|
44
|
+
| 🐧 Linux / 🍎 macOS | `gem install rubik` — dependencies ship prebuilt. |
|
|
45
|
+
| 🪟 Windows | `tree_sitter_language_pack` has no Windows prebuilt and compiles from source: install Rust with the GNU toolchain and export `RUST_TARGET=x86_64-pc-windows-gnu` before installing. The exact, working recipe is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). |
|
|
46
|
+
|
|
47
|
+
### From source
|
|
48
|
+
|
|
49
|
+
**Prerequisites**
|
|
50
|
+
|
|
51
|
+
- **Ruby:** 3.4.0 or later (`ruby -v`)
|
|
52
|
+
- **Linux / macOS:** Via the system package manager, `rbenv`, `rvm`, or `asdf`.
|
|
53
|
+
- **Windows:** [Scoop](https://scoop.sh) (`scoop install ruby`) or [RubyInstaller with DevKit](https://rubyinstaller.org).
|
|
54
|
+
- **Git:** Git 2.30+ (`git -v`)
|
|
55
|
+
- **LLM Provider:** Local [Ollama](https://ollama.ai) (`ollama run qwen2.5-coder:7b`) or a DeepSeek, OpenRouter, Anthropic, or OpenAI API key.
|
|
56
|
+
|
|
57
|
+
Clone the project, install the dependencies, and start the REPL:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/ozgurulukir/rubik.git
|
|
61
|
+
cd rubik
|
|
62
|
+
bundle install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 🐧 Linux & 🍎 macOS (POSIX)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Proje dizininden doğrudan çalıştırma
|
|
69
|
+
./bin/rubik
|
|
70
|
+
|
|
71
|
+
# Veya sistemde global kullanmak için ~/.local/bin dizinine bağlayın:
|
|
72
|
+
ln -sf $(pwd)/bin/rubik ~/.local/bin/rubik
|
|
73
|
+
rubik
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 🪟 Windows (PowerShell / Command Prompt)
|
|
77
|
+
|
|
78
|
+
Rubik ships with built-in `.cmd`, `.bat`, and `.ps1` wrappers for Windows:
|
|
79
|
+
|
|
80
|
+
```powershell
|
|
81
|
+
# PowerShell üzerinden çalıştırma
|
|
82
|
+
.\bin\rubik.ps1
|
|
83
|
+
|
|
84
|
+
# veya Command Prompt / Terminal üzerinden
|
|
85
|
+
.\bin\rubik.cmd
|
|
86
|
+
|
|
87
|
+
# veya Ruby üzerinden doğrudan
|
|
88
|
+
ruby bin\rubik
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> [!TIP]
|
|
92
|
+
> **The Global `rubik` Command on Windows:**
|
|
93
|
+
> By adding Rubik's `bin` folder to your environment variables (User PATH), you can launch it from any terminal simply by typing `rubik`:
|
|
94
|
+
> ```powershell
|
|
95
|
+
> [Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$PWD\bin", "User")
|
|
96
|
+
> ```
|
|
97
|
+
|
|
98
|
+
### Adding a Provider & Model
|
|
99
|
+
|
|
100
|
+
You can connect any OpenAI-compatible provider in a single line:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# REPL içerisinden sağlayıcı ekleme:
|
|
104
|
+
rubik > !provider add deepseek openai_compatible https://api.deepseek.com/v1/chat/completions --key=sk-... --model=deepseek-coder --context=64000
|
|
105
|
+
rubik > !model deepseek/deepseek-coder
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### First Session Example
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
╭── rubik ─────────────────────────────────────────────────╮
|
|
112
|
+
│ 🎲 Rubik v0.1.0 │
|
|
113
|
+
│ deepseek/deepseek-coder │
|
|
114
|
+
│ my_project · main │
|
|
115
|
+
│ !help komutlar · ctrl+c kes · !config ayarlar │
|
|
116
|
+
╰──────────────────────────────────────────────────────────╯
|
|
117
|
+
deepseek/deepseek-coder · ctx 3% · temiz · sandbox kapalı
|
|
118
|
+
rubik (my_project) [main] > !status
|
|
119
|
+
Project: my_project (main)
|
|
120
|
+
Default Model: deepseek/deepseek-coder
|
|
121
|
+
Dirty buffer: false
|
|
122
|
+
|
|
123
|
+
rubik (my_project) [main] > Kullanıcı kayıt formunu doğrulayan bir helper fonksiyonu yaz
|
|
124
|
+
✻ Düşünüyor… · 1s (ctrl+c: kes)
|
|
125
|
+
⏺ write_file — lib/validator.rb
|
|
126
|
+
⎿ 3 lines · 18ms
|
|
127
|
+
|
|
128
|
+
[Rubik] In-memory buffer updated. Type `!diff` to view, `!apply` to save.
|
|
129
|
+
|
|
130
|
+
rubik (my_project) [main] [DIRTY: 1 ✎] > !diff
|
|
131
|
+
+ def validate_user(params)
|
|
132
|
+
+ raise "Invalid email" unless params[:email]&.include?("@")
|
|
133
|
+
+ end
|
|
134
|
+
|
|
135
|
+
rubik (my_project) [main] [DIRTY: 1 ✎] > !test
|
|
136
|
+
Applied 1 file(s) before test. All 12 tests passed!
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> [!TIP]
|
|
140
|
+
> **Safe Execution (Tree-Sitter AST) & Hardware-Enforced OS Isolation (Bubblewrap / Container):**
|
|
141
|
+
> Before running any command, Rubik always keeps the `Security::Guard` shield active (Safe Execution), which detects dangerous commands, pipeline poisoning, and directory escapes ahead of time via Tree-Sitter syntax-tree (AST) analysis for Bash and PowerShell plus a database of 172 CLI YAML rules. For hardware-enforced OS isolation on Linux, use Bubblewrap (`bwrap`) or OCI (`docker`/`podman`):
|
|
142
|
+
> ```bash
|
|
143
|
+
> !sandbox on # Linux'ta Bubblewrap ile donanımsal OS izolasyonunu açar
|
|
144
|
+
> !config set sandbox.allow_network true -p # Ağ erişimi gerektiren komutlar için izin verir
|
|
145
|
+
> ```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## ⚡ Key Features
|
|
150
|
+
|
|
151
|
+
- 🌐 **Full Cross-Platform Support (Linux, macOS, Windows):** Runs with zero friction on Windows 11 / Windows Server environments (PowerShell, Command Prompt, Windows Terminal) in addition to POSIX systems. Ships with a pure-Ruby binary resolution solution, NTFS case-insensitivity handling, the RFC 8089 LSP URI standard, and native Windows CLI (`.cmd`, `.bat`, `.ps1`) wrappers.
|
|
152
|
+
- 🛡️ **Zero-Risk In-Memory Buffer:** When the model writes code, your files are not written to disk immediately; they are staged in an in-memory virtual buffer. You can inspect them with `!diff`, approve them with `!apply`, or revert them within seconds with `!undo`.
|
|
153
|
+
- 🏢 **Multi-Workspace & Extra Root Directories:** In monorepo or sibling-package layouts (`../shared-kernel`, `../proto`), you can safely attach external root directories (`!workspace add`) and include them in the buffer and the AST index in read-only or writable (`--rw`) mode.
|
|
154
|
+
- ⚡ **Live LSP Compiler Diagnostics:** While the model writes code, language servers such as `rust-analyzer`, `typescript-language-server`, `ruff`, `typeprof`, and `gopls` stand guard. As soon as a syntax or type error occurs, the model is alerted before any tests have even run.
|
|
155
|
+
- 🌳 **Multilingual Tree-Sitter & Prism Hybrid AST Engine:** Ships with the built-in **Prism** parser for Ruby and a full **Tree-Sitter** AST engine for Python, Go, Rust, TypeScript, TSX, JavaScript, C, C++, and Zig. It is 100% immune to curly-brace traps inside comments/strings and to modern language patterns; when a library is unavailable it silently falls back to the legacy regex extractors (fail-safe graceful fallback). The symbol and call graph (`!symbols`, `!outline`, `!refs`) is scanned asynchronously in the background; thanks to the persistent on-disk cache (`.rubik/cache/ast_index.json`) it is ready instantly across restarts.
|
|
156
|
+
- 🤖 **Dynamic LLM Providers & Multi-Role Routing:** Light/heavy task routing with support for all OpenAI-compatible endpoints (DeepSeek, Groq, OpenRouter, Mistral, vLLM), Anthropic Claude, OpenAI, and local Ollama.
|
|
157
|
+
- 🧬 **Spatiotemporal Composability & Self-Evolving Runtime:** The model can synthesize new REPL commands (`synthesize_command`) or built-in tools (`synthesize_tool`) at runtime; every side effect is tracked safely by the LIFO inverse-operation accumulator ($\Omega$).
|
|
158
|
+
- 🩹 **Self-Healing Runtime (Service Repair Plane):** Runtime components never require a process restart. `inspect_runtime` exposes the live fiber/coeffect/service state; `repair_runtime_service` diagnoses a failing component (LSP server, test runner, linter, or shell command), probes replacement commands with pre-flight verification in an isolated sub-context, and re-provides the repaired service — session state, caches, and conversations stay intact. `provide_service` registers new services live; every step is LIFO-revertible per the protocol above.
|
|
159
|
+
- 📋 **Structured Goal Ledger (SKILL.state adaptation):** the `!goal` loop keeps a rev-guarded ΔΣ ledger (objective, tasks, findings, decisions, open questions, environment notes) instead of trusting lossy conversation memory. The maestro patches it with the root-only `update_goal_ledger` tool (classified syntax/schema/conflict feedback, bounded retries), the current Σ is pinned through every compaction tier and re-injected each step, every revision is appended to storage (`goal-ledger` ref), declarative `verify` specs are re-checked at step boundaries (a failed check flips the task to `stale` — the world-drift signal), and banked tool results compact into per-row `<absorbed_into_ledger>` tombstones. Default ON (`goal.ledger.*`, operator-only; opt-out per key).
|
|
160
|
+
- 🧹 **Context Pruner (Semi-Deterministic Window Cleanup):** Long sessions accumulate stale exploration in the maestro's conversation window. A Vakanüvis-style two-plane pruner cleans it: a silent LLM subagent proposes a keep/drop/summarize plan, and the harness validates it deterministically (goal pinning, tool-use pairing, head/tail preservation bands, token budget) and applies it atomically at the next turn boundary — never mid-turn. It auto-fires at 60% of the input ceiling (before the blind drop-oldest trimmer would) or on demand via the `prune_context` tool and `!prune`. Default ON since 2026-10-10 (`subagents.pruner.*`, operator-only; opt-out `enabled: false`); the deterministic compaction stack always remains the safety net.
|
|
161
|
+
- 🔌 **Drop-in Plugin System (User Plugins):** `.rb` plugin files (drop-in) or directories under `~/.rubik/plugins` (global) or `.rubik/plugins` (project) are loaded automatically. Plugins are managed dynamically and with zero downtime from the REPL (`!plugin add`, `!plugin reload`); integrity is enforced through SHA-256 `.manifest.json` security verification.
|
|
162
|
+
- 🔌 **Model Context Protocol (MCP) & Spatiotemporal Tool Isolation:** Dynamically attaches external MCP servers (`filesystem`, `fetch`, databases, GitHub, etc.) over the stdio transport (`!mcp connect`, `!mcp add`). Discovered tools join the registry as `Rubik::Tools::BaseTool` proxies; when a server stops or crashes, the `AGENTS.md` LIFO inverse-operation accumulator ($\Omega$) and atomic rollback guarantee zero leftover processes and zero leaks.
|
|
163
|
+
- 📺 **Tiered Live Agent Monitoring (Step Formatter & Verbosity):** With the `quiet`, `normal`, `verbose`, and `debug` levels, step progress, tool calls, execution times, and intermediate thoughts are watched in real time (`!verbosity`, `!verbose`).
|
|
164
|
+
- 🎨 **Hybrid TUI Display Layer:** The `⏺/⎿` agent flow language, a live spinner cycling through Turkish verbs (`✻ Kavuruyor… 4sn`), an above-prompt statusline (model · context percentage · dirty buffer), live markdown rendering, a colored diff viewer, one-key quick approvals, and the interactive `!agents` fiber board. Under `NO_COLOR`/`--no-color` and in pipe mode the output degrades to plain text; session logs never contain ANSI codes (live settings via `!config set ui.*`).
|
|
165
|
+
- ⚡ **Native Tool Acceleration:** When available on the system, `grep_search` is delegated to ripgrep and context scanning to `git ls-files`; AST symbols warm up with lazy body loading. Without rg/git, or with `tools.native: off` or `RUBIK_NATIVE=off`, every path falls back to pure Ruby with identical output — the output format never changes.
|
|
166
|
+
- 🧵 **Quiet Concurrency (Fiber Scheduler Event Loop):** The pure-stdlib `Rubik::Concurrency` layer provides cooperative single-threaded concurrency WITHOUT altering any existing blocking I/O call sites (Net::HTTP, sleep, Open3 pipes, Mutex/CV/Thread#join): thousands of concurrent network requests, the MCP boot fan-out, and the subagent provider pool all run through the `Concurrency.silently`/`.map` boundary (not CPU parallelism, due to the GVL). Kill switch: `RUBIK_FIBER_SCHEDULER=off`.
|
|
167
|
+
- 📜 **Vakanüvis Chronicler (Session Annals):** The deterministic harness records REPL root turns and subagent lifecycle events into the `rubik://storage` annals; a budget-capped LLM distiller mines memory and a wiki skeleton from those annals. All planes are governed by `subagents.vakanuvis.*`; chronicles can be recalled through the Vakanüvis tool (`recall`/`record`/`forget`/`digest`).
|
|
168
|
+
|
|
169
|
+
> 🛡️ **Security note — when cloning unfamiliar repositories:** The `.rubik/commands` and `.rubik/tools` directories contain overlay files that can be interpreted at runtime. SHA-256 manifest verification blocks later modifications; files carrying a sentinel header but no manifest are accepted on the first scan only with the explicit consent of `RUBIK_BOOTSTRAP_OVERLAYS=1`. If you have cloned a repository you do not know, inspect the `.rubik/` directory before the first run.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🕹️ REPL Bang Commands
|
|
174
|
+
|
|
175
|
+
For detailed parameters, flags, and usage scenarios of all 30+ interactive commands:
|
|
176
|
+
👉 Check out the **[COMMANDS.md — Comprehensive REPL Command Guide](COMMANDS.md)** document.
|
|
177
|
+
|
|
178
|
+
### Command Summary Table
|
|
179
|
+
|
|
180
|
+
| Command Group | Key Commands | Description |
|
|
181
|
+
| :--- | :--- | :--- |
|
|
182
|
+
| **Models & Providers** | `!provider`, `!providers`, `!model` | Provider add/remove (CRUD), model role assignment, and smart routing. |
|
|
183
|
+
| **Agent & Planning** | `!plan`, `!goal`, `!steps` | Step-by-step planning, the autonomous goal loop, and step budget management. |
|
|
184
|
+
| **Buffer & Snapshots** | `!diff`, `!apply`, `!autoapply`, `!undo` | Inspect the in-memory virtual file buffer, save to disk, and revert. |
|
|
185
|
+
| **Runtime & Fibers** | `!runtime`, `!fibers`, `!services` | Coeffect services, active/pending fibers, and the $\Omega$ inverse depth. |
|
|
186
|
+
| **Code Intelligence & LSP** | `!lsp`, `!symbols`, `!outline`, `!refs` | Compiler diagnostics and semantic AST graph searches. |
|
|
187
|
+
| **Tests & Execution** | `!test`, `!lint`, `!format`, `!sh`, `!sandbox` | Multilingual test runs, Safe Execution, and a pluggable sandbox (bwrap/docker/podman). |
|
|
188
|
+
| **Session & Settings** | `!session`, `!history`, `!config`, `!verbosity`, `!status` | Session persistence, resuming with `rubik -c`, verbosity, and live settings. |
|
|
189
|
+
| **Workspace & Roots** | `!workspace`, `!roots`, `!allow` | Adding/removing multiple root directories (`add`/`remove`), external permissions (`!allow`), and status listing. |
|
|
190
|
+
|
|
191
|
+
### Agent Surface Contract
|
|
192
|
+
|
|
193
|
+
Every command is typed with an `audience` at registration time: `:human` (operator only), `:agent`, or `:both`. Agent-visible built-in commands (`!diff`, `!status`, `!undo`, `!rollback`, `!autoapply`, `!context`, `!steps`, `!config`, `!model`, `!providers`, `!runtime`, `!fibers`, `!services`, `!agents`, `!lint`, `!format`, `!history`, `!prune`) are published as `<agent_commands>` in the system prompt and can be invoked via the `exec_repl_command` tool; file, code, test, and shell operations are always performed directly through the tools (`read_file`, `write_file`, `run_tests`, `run_command`). Human-only commands (`!provider`, `!workspace`, `!allow`, `!verbosity`, …) are reported back to the agent as a steering message; session lifecycle commands (`!quit`, `!exit`, `!clear`, `!reset`) are hard-blocked for the agent, while `!plan`/`!goal` are redirected to the "finish" tool behind reentrancy protection. Two notes: a `!model` switch takes effect on the next agent run; and `!undo`/`!rollback` revert buffer changes not yet applied to disk, while in `buffer.auto_apply` = `on_test`/`always` mode they may trigger the pre-run auto-apply.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## ⚙️ Configuration
|
|
198
|
+
|
|
199
|
+
For details on all model, LSP, buffer policy, provider, and sandbox settings:
|
|
200
|
+
👉 Check out the **[CONFIG.md — Rubik Configuration Guide](CONFIG.md)** document.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 🖥️ Agent Shell Behavior
|
|
205
|
+
|
|
206
|
+
`run_command` commands are never left to an implicit interpreter: the Safe Execution infrastructure runs every command with a **resolved shell** and an explicit argv array. `sandbox.shell: auto` (the default) chooses per command via a syntax gate — bash-required syntax (`<(...)`, `/dev/null`, heredocs) and `$` substitutions go to **bash**, the PowerShell fingerprint (`$env:`, `Select-String`, `Get-*` and other cmdlets) goes to **pwsh**, and plain commands take the fast path to **cmd** on Windows (bash elsewhere). The choice is labeled in the result (`[EXIT 1] (bash)`), and the system prompt announces the shell behavior to the model up front via the `<environment>` line — the model now knows which shell it is writing commands for. Use `sandbox.shell: bash|pwsh|cmd|sh` to pin a preference (see [CONFIG.md](CONFIG.md) §7).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 🧪 Test Suite
|
|
211
|
+
|
|
212
|
+
Full suite: `rake` (or `rake test`). Single file: `ruby -Itest test/unit/test_repl.rb`.
|
|
213
|
+
|
|
214
|
+
Windows notes:
|
|
215
|
+
- Tests run sequentially in a single process; multi-file arguments such as `ruby -Itest a.rb b.rb` run only the **first** file — always use `rake` for the full suite.
|
|
216
|
+
- A space-containing `TEST` value such as `rake test TEST="a b"` is interpreted as a single path on Windows; make one invocation per file.
|
|
217
|
+
- Temp-directory removal (`teardown`) can fail with `Errno::EACCES` on Windows while a handle is still open; the `remove_temp_dir` helper in `test_helper.rb` swallows this with a best-effort retry and never muddies test results.
|
|
218
|
+
|
|
219
|
+
Environmental test skips:
|
|
220
|
+
- PowerShell shell execution tests are skipped when the `pwsh` (PowerShell Core) binary is not present on the system.
|
|
221
|
+
- Alternative AST parser tests are skipped when the `prism` gem or its C extension is not present on the system.
|
|
222
|
+
- In non-interactive / headless CI environments, direct TUI/reline terminal tests automatically run in non-blocking mode or are skipped in a controlled way.
|
|
223
|
+
- These skips keep the test suite portable and guarantee a 100% green run on every platform.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 📦 Versioning Policy & SemVer 2.0 (Pre-1.0 Stability Roadmap)
|
|
228
|
+
|
|
229
|
+
Rubik adopts the [Semantic Versioning 2.0.0](https://semver.org/) standard. In the current pre-1.0 release (`0.1.0`), the Spatiotemporal Composability core architecture, the fiber lifecycle, and the agent protocols are being hardened:
|
|
230
|
+
- **0.x Releases:** A phase of rapid architectural evolution and refinement of experimental capabilities. Improvements to the core APIs are managed alongside the plan documents.
|
|
231
|
+
- **1.0.0 Goal:** Reaching production stability by freezing the core SPI interfaces, the Revertible Effects LIFO protocol, and the agent surface contract.
|
|
232
|
+
- **Backward Compatibility:** Before every minor release, comprehensive backward-compatibility and SPI breakage analyses (using GitNexus knowledge-graph & PDG analysis) are mandatory.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 📚 Documentation
|
|
237
|
+
|
|
238
|
+
Every guide ships as a bilingual pair — `X.md` (English, authoritative) alongside `X.tr.md` (Turkish translation).
|
|
239
|
+
|
|
240
|
+
### Architecture & Internals
|
|
241
|
+
|
|
242
|
+
| Guide | What it covers |
|
|
243
|
+
| :--- | :--- |
|
|
244
|
+
| 🌌 [AGENTS.md](AGENTS.md) | The Spatiotemporal Composability Protocol — theory, invariants, the fiber lifecycle state machine. |
|
|
245
|
+
| 🏗️ [ARCHITECTURE.md](ARCHITECTURE.md) | The 30 core domains and the responsibility matrix. |
|
|
246
|
+
| 🧬 [SELF_EVOLUTION.md](SELF_EVOLUTION.md) | How the LLM synthesizes tools, repairs services, and evolves its own runtime. |
|
|
247
|
+
| 🔌 [API.md](API.md) | Embedding the runtime from Ruby: config, tool registry, provider contract, orchestrator loop, error contracts. |
|
|
248
|
+
|
|
249
|
+
### User & Operator Guides
|
|
250
|
+
|
|
251
|
+
| Guide | What it covers |
|
|
252
|
+
| :--- | :--- |
|
|
253
|
+
| 🕹️ [COMMANDS.md](COMMANDS.md) | All 30+ REPL bang commands with parameters and usage scenarios. |
|
|
254
|
+
| 🧰 [TOOLS.md](TOOLS.md) | Catalog of the 34 built-in agent tools: parameters, risk classes, diagnostics. |
|
|
255
|
+
| ⚙️ [CONFIG.md](CONFIG.md) | Every configuration knob (models, LSP, buffer policy, sandbox, subagents, pruner…). |
|
|
256
|
+
| 🌍 [ENV.md](ENV.md) | Environment variables — single consolidated reference table. |
|
|
257
|
+
| 🖥️ [LSP.md](LSP.md) | Step-by-step language-server setup and diagnostics walkthrough. |
|
|
258
|
+
| 🔌 [PLUGINS.md](PLUGINS.md) | The drop-in plugin system: layout, manifests, zero-downtime reloads. |
|
|
259
|
+
|
|
260
|
+
### Project & Policy
|
|
261
|
+
|
|
262
|
+
| Guide | What it covers |
|
|
263
|
+
| :--- | :--- |
|
|
264
|
+
| 🤝 [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, test commands, commit/PR conventions, security reporting. |
|
|
265
|
+
| 🧪 [TESTING.md](TESTING.md) | Running the suite, naming conventions, test templates, flaky-test policy. |
|
|
266
|
+
| 🔒 [SECURITY.md](SECURITY.md) | Command gate, critical-write protection, synthesis gates, sandbox, audit log. |
|
|
267
|
+
| 📜 [ATTRIBUTION.md](ATTRIBUTION.md) | Open-source attributions and third-party notices. |
|
|
268
|
+
| 📜 [CHANGELOG.md](CHANGELOG.md) | Thematic release history; the full record lives in `git log`. |
|
|
269
|
+
| 📄 [LICENSE](LICENSE) | Software license (MIT). |
|
data/bin/rubik
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
5
|
+
require "rubik"
|
|
6
|
+
require "optparse"
|
|
7
|
+
|
|
8
|
+
# Global SIGINT safety net. The REPL installs its own "INT" handler while
|
|
9
|
+
# running so a Ctrl+C during a command raises Interrupt (and Reline chains to it
|
|
10
|
+
# as its old handler). This catch-all at the top level guarantees that an
|
|
11
|
+
# interrupt that occurs before the REPL loop starts -- or any situation where an
|
|
12
|
+
# inner handler has been left uninstalled -- always terminates the process, so
|
|
13
|
+
# the user is never stuck unable to exit.
|
|
14
|
+
trap("INT") do
|
|
15
|
+
$stderr.puts "\n[rubik] Interrupted. Exiting."
|
|
16
|
+
exit!(130)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
options = {}
|
|
20
|
+
OptionParser.new do |opts|
|
|
21
|
+
opts.banner = "Usage: rubik [options]"
|
|
22
|
+
|
|
23
|
+
opts.on("-v", "--version", "Show version") do
|
|
24
|
+
puts "rubik version #{Rubik::VERSION}"
|
|
25
|
+
Kernel.exit(0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on("-h", "--help", "Show help") do
|
|
29
|
+
puts opts
|
|
30
|
+
Kernel.exit(0)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
opts.on("-c", "--continue [SESSION_ID]", "Resume previous session (default: latest recorded session)") do |sid|
|
|
34
|
+
options[:continue] = sid || :latest
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
opts.on("--list-sessions", "List all past sessions for current workspace") do
|
|
38
|
+
sessions = Rubik::Session::Manager.list_sessions(Dir.pwd)
|
|
39
|
+
if sessions.empty?
|
|
40
|
+
puts "No recorded sessions found for #{Dir.pwd}."
|
|
41
|
+
else
|
|
42
|
+
puts "=== Recorded Sessions for #{File.basename(Dir.pwd)} (#{sessions.size}) ==="
|
|
43
|
+
sessions.each_with_index do |s, idx|
|
|
44
|
+
puts " #{idx + 1}. #{s[:id]} (#{s[:scope]}) - #{s[:event_count]} events - Last: \"#{s[:last_prompt]}\""
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
Kernel.exit(0)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
opts.on("-s", "--script FILE", "Execute script file in non-interactive mode") do |file|
|
|
51
|
+
options[:script] = file
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
opts.on("--no-color", "Disable ANSI colors, banner and spinner") do
|
|
55
|
+
ENV["NO_COLOR"] = "1"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
opts.on("--color", "Force ANSI colors even when output is piped") do
|
|
59
|
+
ENV["CLICOLOR_FORCE"] = "1"
|
|
60
|
+
end
|
|
61
|
+
end.parse!
|
|
62
|
+
|
|
63
|
+
shell = Rubik::REPL::Shell.new
|
|
64
|
+
|
|
65
|
+
if options[:continue]
|
|
66
|
+
res = shell.resume_session(options[:continue])
|
|
67
|
+
unless res[:ok]
|
|
68
|
+
warn "[Rubik] Warning: #{res[:error]}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if options[:script]
|
|
73
|
+
unless File.exist?(options[:script])
|
|
74
|
+
warn "Script file not found: #{options[:script]}"
|
|
75
|
+
Kernel.exit(1)
|
|
76
|
+
end
|
|
77
|
+
File.open(options[:script]) do |f|
|
|
78
|
+
shell.run(f)
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
shell.run
|
|
82
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require_relative "process"
|
|
5
|
+
require_relative "../platform"
|
|
6
|
+
require_relative "../runtime/discovery"
|
|
7
|
+
require_relative "../runtime/diagnostic_engine"
|
|
8
|
+
|
|
9
|
+
module Rubik
|
|
10
|
+
module Adapter
|
|
11
|
+
class BaseAdapter
|
|
12
|
+
attr_reader :name, :binary_name
|
|
13
|
+
attr_accessor :custom_test_cmd, :custom_format_cmd, :config
|
|
14
|
+
|
|
15
|
+
def initialize(name, binary_name)
|
|
16
|
+
@name = name.to_s.downcase
|
|
17
|
+
@binary_name = binary_name
|
|
18
|
+
@custom_test_cmd = nil
|
|
19
|
+
@custom_format_cmd = nil
|
|
20
|
+
@config = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def available?
|
|
24
|
+
!Rubik::Platform.which(@binary_name).nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def check_syntax(_file_path)
|
|
28
|
+
return unavailable_result unless available?
|
|
29
|
+
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Compilation is an optional capability: interpreted languages ride this
|
|
34
|
+
# no-op default and only compiled-language adapters override it.
|
|
35
|
+
def compile(_root_dir)
|
|
36
|
+
{ ok: true, output: "#{@name} requires no compilation", duration: 0.0, exit_code: 0 }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Exit signature of "the selected runner itself is missing" — e.g.
|
|
40
|
+
# `cargo nextest run` on a machine without cargo-nextest (cargo exits
|
|
41
|
+
# 101 with "error: no such command: `nextest`" on STDERR and no test
|
|
42
|
+
# output). Stderr-only + empty stdout keeps a genuine failing suite
|
|
43
|
+
# that merely prints the phrase from triggering the fallback.
|
|
44
|
+
MISSING_RUNNER_PATTERN = /\bno such (?:command|subcommand)\b/i
|
|
45
|
+
|
|
46
|
+
def run_tests(root_dir, path: nil, timeout: nil)
|
|
47
|
+
return unavailable_result unless available?
|
|
48
|
+
|
|
49
|
+
cmd = resolve_test_cmd(root_dir, path: path)
|
|
50
|
+
unless cmd && Runtime::Discovery.binary_available?(cmd)
|
|
51
|
+
candidates = if @config
|
|
52
|
+
Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir, config: @config)
|
|
53
|
+
else
|
|
54
|
+
Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir)
|
|
55
|
+
end
|
|
56
|
+
reason = if candidates.any?
|
|
57
|
+
"No test runner found for #{@name} (tried: #{candidates.join(', ')}). Install one or set custom_test_cmd."
|
|
58
|
+
else
|
|
59
|
+
"No test runner found for #{@name}. Install one or set custom_test_cmd."
|
|
60
|
+
end
|
|
61
|
+
return unavailable_result(reason: reason)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
eff_timeout = (timeout || (@config.respond_to?(:fetch) ? @config.fetch("test.timeout", 30) : 30) || 30).to_i
|
|
65
|
+
work_dir = test_working_dir(root_dir, path)
|
|
66
|
+
res = ProcessRunner.run(cmd, timeout: eff_timeout, chdir: work_dir)
|
|
67
|
+
|
|
68
|
+
if missing_runner_failure?(res)
|
|
69
|
+
fallback = next_test_runner_candidate(root_dir, exclude: [cmd, resolve_test_cmd(root_dir)])
|
|
70
|
+
if fallback
|
|
71
|
+
fallback_cmd = targeted_test_cmd(fallback, path.to_s.strip) unless path.nil? || path.to_s.strip.empty?
|
|
72
|
+
fallback_cmd ||= fallback
|
|
73
|
+
retry_res = ProcessRunner.run(fallback_cmd, timeout: eff_timeout, chdir: work_dir)
|
|
74
|
+
if missing_runner_failure?(retry_res)
|
|
75
|
+
# The ladder is exhausted: keep the ORIGINAL failure, but record
|
|
76
|
+
# the attempt so the dead end is never silent.
|
|
77
|
+
res[:output] = "[Test runner fallback] '#{fallback_cmd}' also reported a missing runner — " \
|
|
78
|
+
"no candidate left. Original failure follows.\n#{res[:output]}"
|
|
79
|
+
else
|
|
80
|
+
retry_res[:output] = "[Test runner fallback] '#{cmd}' could not run (runner missing) — " \
|
|
81
|
+
"retried with '#{fallback_cmd}'.\n#{retry_res[:output]}"
|
|
82
|
+
res = retry_res
|
|
83
|
+
cmd = fallback_cmd
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
res[:diagnostic] = Runtime::DiagnosticEngine.analyze_process_result(res, cmd: cmd) unless res[:ok]
|
|
89
|
+
res
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Working directory for the test run. Default: the session root.
|
|
93
|
+
# Adapters override when the runner must execute from a build root it
|
|
94
|
+
# discovers on disk (Crystal: nearest shard.yml ancestor).
|
|
95
|
+
def test_working_dir(root_dir, _path)
|
|
96
|
+
root_dir
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def format_code(file_path)
|
|
100
|
+
cmd = resolve_format_cmd(file_path)
|
|
101
|
+
return unavailable_result unless cmd && Runtime::Discovery.binary_available?(cmd)
|
|
102
|
+
|
|
103
|
+
res = ProcessRunner.run(cmd)
|
|
104
|
+
res[:diagnostic] = Runtime::DiagnosticEngine.analyze_process_result(res, cmd: cmd) unless res[:ok]
|
|
105
|
+
res
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def resolve_test_cmd(root_dir = Dir.pwd, path: nil)
|
|
109
|
+
base = if @custom_test_cmd && !@custom_test_cmd.strip.empty?
|
|
110
|
+
@custom_test_cmd
|
|
111
|
+
else
|
|
112
|
+
discovered = if @config
|
|
113
|
+
Runtime::Discovery.discover_test_runners(root_dir: root_dir, lang: @name, config: @config)
|
|
114
|
+
else
|
|
115
|
+
Runtime::Discovery.discover_test_runners(root_dir: root_dir, lang: @name)
|
|
116
|
+
end
|
|
117
|
+
discovered.first || default_test_cmd
|
|
118
|
+
end
|
|
119
|
+
return nil unless base
|
|
120
|
+
return base if path.nil? || path.to_s.strip.empty?
|
|
121
|
+
|
|
122
|
+
targeted_test_cmd(base, path.to_s.strip)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def targeted_test_cmd(base_cmd, path)
|
|
126
|
+
clean_path = path.to_s.tr("\\", "/")
|
|
127
|
+
if base_cmd.include?("%s")
|
|
128
|
+
format(base_cmd, Shellwords.escape(clean_path))
|
|
129
|
+
elsif base_cmd.include?("rake test")
|
|
130
|
+
"#{base_cmd} TEST=#{Shellwords.escape(clean_path)}"
|
|
131
|
+
else
|
|
132
|
+
"#{base_cmd} #{Shellwords.escape(clean_path)}"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def resolve_format_cmd(file_path)
|
|
137
|
+
if @custom_format_cmd && !@custom_format_cmd.strip.empty?
|
|
138
|
+
if @custom_format_cmd.include?("%s")
|
|
139
|
+
format(@custom_format_cmd, Shellwords.escape(file_path))
|
|
140
|
+
else
|
|
141
|
+
"#{@custom_format_cmd} #{Shellwords.escape(file_path)}"
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
discovered = Runtime::Discovery.discover_formatters(root_dir: File.dirname(file_path), lang: @name)
|
|
145
|
+
if discovered.first
|
|
146
|
+
"#{discovered.first} #{Shellwords.escape(file_path)}"
|
|
147
|
+
else
|
|
148
|
+
default_format_cmd(file_path)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
protected
|
|
154
|
+
|
|
155
|
+
def default_test_cmd
|
|
156
|
+
nil
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Runner-missing shape: the phrase comes from the RUNNER WRAPPER on
|
|
160
|
+
# stderr while the suite produced no output at all — a genuine failing
|
|
161
|
+
# suite that merely prints the phrase (e.g. CLI error-path tests) has
|
|
162
|
+
# stdout content and is never misread as a missing runner.
|
|
163
|
+
def missing_runner_failure?(res)
|
|
164
|
+
return false if res[:ok]
|
|
165
|
+
|
|
166
|
+
res[:stdout].to_s.strip.empty? && res[:stderr].to_s.match?(MISSING_RUNNER_PATTERN)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Next available Discovery candidate for this language, minus the
|
|
170
|
+
# commands already tried — the fallback ladder for a missing runner.
|
|
171
|
+
def next_test_runner_candidate(root_dir, exclude:)
|
|
172
|
+
candidates = if @config
|
|
173
|
+
Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir, config: @config)
|
|
174
|
+
else
|
|
175
|
+
Runtime::Discovery.test_runner_candidates(@name, root_dir: root_dir)
|
|
176
|
+
end
|
|
177
|
+
candidates.find do |c|
|
|
178
|
+
next false if exclude.include?(c)
|
|
179
|
+
|
|
180
|
+
Runtime::Discovery.binary_available?(c, root_dir: root_dir)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def default_format_cmd(_file_path)
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def unavailable_result(binary = nil, reason: nil)
|
|
189
|
+
bin = binary || @binary_name
|
|
190
|
+
msg = reason || "#{bin} is unavailable on system"
|
|
191
|
+
{ ok: false, output: msg, duration: 0.0, exit_code: 127 }
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
module Rubik
|
|
7
|
+
module Adapter
|
|
8
|
+
class CAdapter < BaseAdapter
|
|
9
|
+
def initialize
|
|
10
|
+
super("c", "clang")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def check_syntax(file_path)
|
|
14
|
+
return unavailable_result unless available?
|
|
15
|
+
|
|
16
|
+
ProcessRunner.run("clang -fsyntax-only #{Shellwords.escape(file_path)}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def compile(root_dir)
|
|
20
|
+
return unavailable_result unless available?
|
|
21
|
+
|
|
22
|
+
ProcessRunner.run("make", chdir: root_dir)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def default_test_cmd
|
|
28
|
+
"ctest"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default_format_cmd(file_path)
|
|
32
|
+
"clang-format -i #{Shellwords.escape(file_path)}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
module Rubik
|
|
7
|
+
module Adapter
|
|
8
|
+
class CrystalAdapter < BaseAdapter
|
|
9
|
+
def initialize
|
|
10
|
+
super("crystal", "crystal")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def check_syntax(file_path)
|
|
14
|
+
return unavailable_result unless available?
|
|
15
|
+
|
|
16
|
+
ProcessRunner.run("crystal build --no-codegen #{Shellwords.escape(file_path)}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def compile(root_dir)
|
|
20
|
+
return unavailable_result unless available?
|
|
21
|
+
|
|
22
|
+
cmd = File.exist?(File.join(root_dir, "shard.yml")) ? "shards build" : "crystal build"
|
|
23
|
+
ProcessRunner.run(cmd, chdir: root_dir)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def default_test_cmd
|
|
29
|
+
"crystal spec"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def default_format_cmd(file_path)
|
|
33
|
+
"crystal tool format #{Shellwords.escape(file_path)}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Nearest ancestor of the target (or the session root) that carries a
|
|
39
|
+
# shard.yml — crystal resolves shard dependencies from that directory,
|
|
40
|
+
# so a repo with a nested shard root (e.g. cli/shard.yml) must run its
|
|
41
|
+
# specs from there, not from the session root. nil = no shard root
|
|
42
|
+
# found; the default session-root behavior applies.
|
|
43
|
+
def shard_root(root_dir, path)
|
|
44
|
+
clean = path.to_s.strip
|
|
45
|
+
start = clean.empty? ? root_dir : File.dirname(File.expand_path(clean, root_dir))
|
|
46
|
+
walk = start.tr("\\", "/")
|
|
47
|
+
until walk.empty? || walk == File.dirname(walk)
|
|
48
|
+
return walk if File.exist?(File.join(walk, "shard.yml"))
|
|
49
|
+
|
|
50
|
+
walk = File.dirname(walk)
|
|
51
|
+
end
|
|
52
|
+
root = root_dir.to_s.tr("\\", "/")
|
|
53
|
+
File.exist?(File.join(root, "shard.yml")) ? root : nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
public
|
|
57
|
+
|
|
58
|
+
# Relativize the targeted spec path to the shard root so the command
|
|
59
|
+
# and the working directory (test_working_dir) agree on the same base.
|
|
60
|
+
# Prefix match is case-insensitive (Windows filesystems); the strip is
|
|
61
|
+
# exact-token based on the raw expanded path — Platform.normalize_path
|
|
62
|
+
# downcases on Windows and would corrupt the rendered path.
|
|
63
|
+
def resolve_test_cmd(root_dir = Dir.pwd, path: nil)
|
|
64
|
+
clean = path.to_s.strip
|
|
65
|
+
unless clean.empty? || (root = shard_root(root_dir, clean)).nil?
|
|
66
|
+
abs = File.expand_path(clean, root_dir).tr("\\", "/")
|
|
67
|
+
prefix = root.end_with?("/") ? root : "#{root}/"
|
|
68
|
+
clean = abs.downcase.start_with?(prefix.downcase) ? abs[prefix.length..] : abs
|
|
69
|
+
end
|
|
70
|
+
super(root_dir, path: clean.empty? ? nil : clean)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_working_dir(root_dir, path)
|
|
74
|
+
shard_root(root_dir, path) || super
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|