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,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# Öneri 3 (Kademe 1): Shell'den ayrılan girdi döngüsü sorumluluğu.
|
|
6
|
+
# Hem interaktif (Reline) hem stream (pipeline) koşumunun satır düzeyi
|
|
7
|
+
# ayrıştırması: çoklu-satır tamponu, heredoc modu ve EOF/^C davranışı.
|
|
8
|
+
# Durum Shell'de yaşar (@running, @reline, @input, @output); bu modül
|
|
9
|
+
# yalnızca döngü politikasını kodlar.
|
|
10
|
+
module InputLoop
|
|
11
|
+
def run_stream(stream)
|
|
12
|
+
stream.each_line do |line|
|
|
13
|
+
break unless @running
|
|
14
|
+
|
|
15
|
+
process_multiline_or_line(line.chomp)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run_interactive
|
|
20
|
+
@running = true
|
|
21
|
+
multiline_buffer = []
|
|
22
|
+
heredoc_delimiter = nil
|
|
23
|
+
last_line_at = nil
|
|
24
|
+
print_banner
|
|
25
|
+
Rubik::REPL::InterruptHandler.with_handler do
|
|
26
|
+
while @running
|
|
27
|
+
result = read_and_process_line(multiline_buffer, heredoc_delimiter, last_line_at: last_line_at)
|
|
28
|
+
break if result[:stop]
|
|
29
|
+
|
|
30
|
+
multiline_buffer = result[:buffer]
|
|
31
|
+
heredoc_delimiter = result[:delimiter]
|
|
32
|
+
last_line_at = result[:last_line_at]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
ensure
|
|
36
|
+
stop
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Plan 91 — Explicit paste mode: `!paste` starts a line-collector
|
|
40
|
+
# (PasteSupport#read_paste_block); the joined text is dispatched
|
|
41
|
+
# through the normal prompt path. Executed INSIDE run_interactive
|
|
42
|
+
# (not read_and_process_line) so the paste reader fully replaces
|
|
43
|
+
# readline-based collection while the mode is active.
|
|
44
|
+
def run_paste_mode
|
|
45
|
+
text = read_paste_block
|
|
46
|
+
return { stop: false, buffer: [], delimiter: nil } if text.strip.empty?
|
|
47
|
+
|
|
48
|
+
execute_input(text)
|
|
49
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Processes one line of interactive input. Returns a hash with:
|
|
53
|
+
# stop: true if the REPL should exit
|
|
54
|
+
# buffer: updated multiline buffer
|
|
55
|
+
# delimiter: updated heredoc delimiter (nil if not in heredoc mode)
|
|
56
|
+
# last_line_at: monotonic timestamp of this accepted line (Plan 91)
|
|
57
|
+
def read_and_process_line(multiline_buffer, heredoc_delimiter, last_line_at: nil)
|
|
58
|
+
# Plan 91 fix: capture-then-reset BEFORE the early return. Resetting
|
|
59
|
+
# only on the fall-through path leaked the flag when paste mode was
|
|
60
|
+
# taken, so every subsequent prompt re-entered run_paste_mode and a
|
|
61
|
+
# test/piped readline returning nil spun the loop forever.
|
|
62
|
+
paste_mode = @paste_requested
|
|
63
|
+
@paste_requested = false
|
|
64
|
+
return run_paste_mode if paste_mode && heredoc_delimiter.nil? && multiline_buffer.empty?
|
|
65
|
+
|
|
66
|
+
flush_subagent_notices! if respond_to?(:flush_subagent_notices!)
|
|
67
|
+
@terminal_guard&.flush_notices!
|
|
68
|
+
current_prompt, boxed = prepare_prompt_line(!heredoc_delimiter.nil?)
|
|
69
|
+
line = safe_readline(current_prompt, add_history: false)
|
|
70
|
+
@output.puts box_bottom_border if boxed
|
|
71
|
+
return handle_eof(multiline_buffer, heredoc_delimiter) if line.nil? || line == "\x04" || line == "\x1a"
|
|
72
|
+
|
|
73
|
+
# Plan 91 — paste-burst collection: when lines arrive faster than
|
|
74
|
+
# ui.paste_join_window_ms apart (or Reline reports in_pasting?), the
|
|
75
|
+
# pasted lines are buffered and submitted as ONE prompt turn instead
|
|
76
|
+
# of one turn per line. First line still executes immediately if the
|
|
77
|
+
# burst stops; a lone line typed by hand (or Enter pressed in an
|
|
78
|
+
# empty prompt) executes as before.
|
|
79
|
+
#
|
|
80
|
+
# Gated to REAL interactive consoles only: with stubbed/piped readline
|
|
81
|
+
# (tests, RUBIK_TEST=1, non-TTY stdin) every stubbed line arrives
|
|
82
|
+
# "instantly", so an ungated collector would swallow consecutive test
|
|
83
|
+
# lines like "x = a << 1" + "exit" into one turn (seen in test_repl).
|
|
84
|
+
now = paste_clock
|
|
85
|
+
if paste_burst_eligible? && !line.strip.empty? && !heredoc_delimiter &&
|
|
86
|
+
multiline_buffer.empty? &&
|
|
87
|
+
(paste_line_fast?(last_line_at, now) || paste_pending?)
|
|
88
|
+
return collect_paste_burst(line)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Push non-empty command to history
|
|
92
|
+
@reline::HISTORY.push(line) if !line.strip.empty? && line != begin
|
|
93
|
+
@reline::HISTORY.to_a.last
|
|
94
|
+
rescue
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Plan 109 — reactive wake-up: an empty submitted line while
|
|
99
|
+
# wake-worthy notices are pending becomes the autonomous notice
|
|
100
|
+
# turn (both the safe_wakeup!-injected Enter and a user's
|
|
101
|
+
# Enter-on-empty land here). Heredoc body lines are content,
|
|
102
|
+
# never a turn; paste bursts and history already exclude empty
|
|
103
|
+
# lines.
|
|
104
|
+
if heredoc_delimiter.nil? && line.strip.empty? && respond_to?(:pending_wake_notices?) && pending_wake_notices?
|
|
105
|
+
execute_subagent_notice_turn!
|
|
106
|
+
return { stop: false, buffer: [], delimiter: nil, last_line_at: now }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
result = if heredoc_delimiter
|
|
110
|
+
process_heredoc_line(line, multiline_buffer, heredoc_delimiter)
|
|
111
|
+
else
|
|
112
|
+
process_normal_line(line, multiline_buffer)
|
|
113
|
+
end
|
|
114
|
+
result[:last_line_at] = now
|
|
115
|
+
result
|
|
116
|
+
rescue Interrupt
|
|
117
|
+
@output.puts "^C"
|
|
118
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
119
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
120
|
+
rescue StandardError => e
|
|
121
|
+
@output.puts "[REPL Error] #{e.class}: #{e.message}"
|
|
122
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Plan 91 — burst collector: keeps reading lines while they arrive
|
|
126
|
+
# within ui.paste_join_window_ms of each other (or Reline reports
|
|
127
|
+
# pending paste input); submits the joined block as a single prompt
|
|
128
|
+
# turn. EOF/Ctrl+D flushes what was collected. Guarded against slow
|
|
129
|
+
# stdin consumers (run_stream-style piping unaffected — this path
|
|
130
|
+
# only runs for interactive readline input).
|
|
131
|
+
def collect_paste_burst(first_line)
|
|
132
|
+
lines = [first_line]
|
|
133
|
+
last_at = paste_clock
|
|
134
|
+
# Plan 109: collection runs with_collecting so a reactive wake
|
|
135
|
+
# Enter cannot append a blank line inside the pasted block.
|
|
136
|
+
collector = lambda do
|
|
137
|
+
loop do
|
|
138
|
+
line = safe_readline("paste… ", add_history: false)
|
|
139
|
+
now = paste_clock
|
|
140
|
+
break if line.nil? || line == "\x04" || line == "\x1a" # EOF flushes the block
|
|
141
|
+
|
|
142
|
+
in_paste = paste_line_fast?(last_at, now) || paste_pending?
|
|
143
|
+
last_at = now
|
|
144
|
+
break unless in_paste # burst ended -> user is typing again; flush
|
|
145
|
+
|
|
146
|
+
lines << line
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
if @terminal_guard.respond_to?(:with_collecting)
|
|
150
|
+
@terminal_guard.with_collecting(&collector)
|
|
151
|
+
else
|
|
152
|
+
collector.call
|
|
153
|
+
end
|
|
154
|
+
text = lines.join("\n")
|
|
155
|
+
execute_input(text)
|
|
156
|
+
# Return the final line timestamp so a following paste burst chain
|
|
157
|
+
# (copy #2 right after copy #1) is still detected as fast lines.
|
|
158
|
+
{ stop: false, buffer: [], delimiter: nil, last_line_at: last_at }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def safe_readline(prompt, add_history: false)
|
|
162
|
+
if @terminal_guard
|
|
163
|
+
@terminal_guard.with_reading { @reline.readline(prompt, add_history) }
|
|
164
|
+
else
|
|
165
|
+
@reline.readline(prompt, add_history)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def handle_eof(multiline_buffer, heredoc_delimiter)
|
|
170
|
+
if heredoc_delimiter
|
|
171
|
+
@output.puts "\n[Rubik] Multiline input cancelled."
|
|
172
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
173
|
+
else
|
|
174
|
+
stop
|
|
175
|
+
{ stop: true, buffer: multiline_buffer, delimiter: heredoc_delimiter }
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def process_heredoc_line(line, multiline_buffer, heredoc_delimiter)
|
|
180
|
+
if line.strip == heredoc_delimiter
|
|
181
|
+
full_text = multiline_buffer.join("\n")
|
|
182
|
+
execute_input(full_text)
|
|
183
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
184
|
+
else
|
|
185
|
+
multiline_buffer << line
|
|
186
|
+
{ stop: false, buffer: multiline_buffer, delimiter: heredoc_delimiter }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def process_normal_line(line, _multiline_buffer)
|
|
191
|
+
# Only trigger heredoc if line explicitly opens a multiline block (e.g. `<<EOF`, `!plan <<EOF`)
|
|
192
|
+
# NEVER trigger on inline operators like `array << item` or `a << b`
|
|
193
|
+
heredoc_match = line.match(/^\s*(?:[a-zA-Z0-9_!.-]+\s+)?<<[-~]?['"]?([A-Za-z0-9_]+)['"]?\s*$/)
|
|
194
|
+
if heredoc_match && !line.strip.start_with?("#")
|
|
195
|
+
delimiter = heredoc_match[1]
|
|
196
|
+
prefix = line.sub(/<<[-~]?['"]?#{Regexp.escape(delimiter)}['"]?\s*$/, "").strip
|
|
197
|
+
buffer = prefix.empty? ? [] : [prefix]
|
|
198
|
+
{ stop: false, buffer: buffer, delimiter: delimiter }
|
|
199
|
+
elsif ["exit", "quit", "!exit", "!quit"].include?(line.strip.downcase)
|
|
200
|
+
stop
|
|
201
|
+
{ stop: true, buffer: [], delimiter: nil }
|
|
202
|
+
else
|
|
203
|
+
execute_input(line)
|
|
204
|
+
{ stop: false, buffer: [], delimiter: nil }
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def process_multiline_or_line(line)
|
|
209
|
+
execute_input(line)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "reline"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module REPL
|
|
7
|
+
# Encapsulates Ctrl+C (SIGINT) handling for the interactive REPL loop.
|
|
8
|
+
#
|
|
9
|
+
# On Windows, Reline's default interrupt behavior can leave the line editor
|
|
10
|
+
# in a wedged state after a Ctrl+C: the next prompt neither echoes input nor
|
|
11
|
+
# honors EOF (Ctrl+D / Ctrl+Z), so the REPL appears frozen and cannot be
|
|
12
|
+
# exited. This module installs a SIGINT handler that raises Interrupt (so a
|
|
13
|
+
# running command -- orchestrator loop, HTTP call -- aborts promptly and
|
|
14
|
+
# Reline's own teardown restores console mode) and provides a best-effort
|
|
15
|
+
# line-editor reset to recover an unresponsive prompt.
|
|
16
|
+
module InterruptHandler
|
|
17
|
+
# Install a SIGINT handler that raises Interrupt, returning the previous
|
|
18
|
+
# handler so the caller can restore it on exit. Reline captures this trap
|
|
19
|
+
# as its @old_trap and calls it, so the Interrupt propagates out of
|
|
20
|
+
# Reline.readline cleanly.
|
|
21
|
+
def self.install
|
|
22
|
+
Signal.trap("INT") { raise Interrupt }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Restore a previously captured SIGINT handler (value from .install).
|
|
26
|
+
def self.restore(prev_handler)
|
|
27
|
+
Signal.trap("INT", prev_handler) if prev_handler
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Run a block with the Interrupt-raising SIGINT handler installed, always
|
|
31
|
+
# restoring the previous handler and the console afterwards. Usage:
|
|
32
|
+
# Rubik::REPL::InterruptHandler.with_handler { ... loop body ... }
|
|
33
|
+
def self.with_handler
|
|
34
|
+
prev = install
|
|
35
|
+
yield
|
|
36
|
+
ensure
|
|
37
|
+
restore(prev)
|
|
38
|
+
restore_console_after_interrupt
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Best-effort reset of Reline's line editor and console state after an interrupt.
|
|
42
|
+
# On Windows, an interrupted/aborted readline or external command execution can
|
|
43
|
+
# leave the line editor with dirty input buffers or stale console handles.
|
|
44
|
+
# Resetting the editor drops any half-entered buffer, clears IOGate input/output
|
|
45
|
+
# buffers, flushes console input, and refreshes standard handles.
|
|
46
|
+
def self.restore_console_after_interrupt
|
|
47
|
+
return unless defined?(Reline)
|
|
48
|
+
|
|
49
|
+
if Reline.respond_to?(:line_editor) && (le = Reline.line_editor)
|
|
50
|
+
begin
|
|
51
|
+
if le.respond_to?(:reset)
|
|
52
|
+
# reline's released signature varies across versions (reset(prompt='', encoding: ...) vs reset(prompt=''))
|
|
53
|
+
begin
|
|
54
|
+
enc = (Reline.respond_to?(:encoding_system) ? Reline.encoding_system : Encoding.default_external)
|
|
55
|
+
le.reset("", encoding: enc)
|
|
56
|
+
rescue ArgumentError
|
|
57
|
+
begin
|
|
58
|
+
le.reset("")
|
|
59
|
+
rescue ArgumentError
|
|
60
|
+
le.reset
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
elsif le.respond_to?(:reset_line)
|
|
64
|
+
le.reset_line
|
|
65
|
+
end
|
|
66
|
+
rescue StandardError
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if defined?(Reline::IOGate) && (io = Reline::IOGate)
|
|
72
|
+
# Clear internal buffers so unconsumed control characters/interrupt bytes don't bleed into next prompt
|
|
73
|
+
begin
|
|
74
|
+
if io.instance_variable_defined?(:@output_buf) && (buf = io.instance_variable_get(:@output_buf)).is_a?(Array)
|
|
75
|
+
buf.clear
|
|
76
|
+
end
|
|
77
|
+
if io.instance_variable_defined?(:@input_buf) && (buf = io.instance_variable_get(:@input_buf)).is_a?(Array)
|
|
78
|
+
buf.clear
|
|
79
|
+
end
|
|
80
|
+
io.instance_variable_set(:@hsg, nil) if io.instance_variable_defined?(:@hsg)
|
|
81
|
+
rescue StandardError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Refresh Windows console handles and flush console buffer if on Windows
|
|
86
|
+
begin
|
|
87
|
+
if io.is_a?(Reline::Windows) || (defined?(Reline::Windows) && io.class.name.to_s.include?("Windows"))
|
|
88
|
+
get_std_handle = io.instance_variable_get(:@GetStdHandle)
|
|
89
|
+
if get_std_handle
|
|
90
|
+
new_in = begin
|
|
91
|
+
get_std_handle.call(Reline::Windows::STD_INPUT_HANDLE)
|
|
92
|
+
rescue
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
io.instance_variable_set(:@hConsoleInputHandle, new_in) if new_in && new_in != 0
|
|
96
|
+
|
|
97
|
+
new_out = begin
|
|
98
|
+
get_std_handle.call(Reline::Windows::STD_OUTPUT_HANDLE)
|
|
99
|
+
rescue
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
io.instance_variable_set(:@hConsoleHandle, new_out) if new_out && new_out != 0
|
|
103
|
+
|
|
104
|
+
if new_in && new_in != 0 && defined?(Reline::Windows::Win32API)
|
|
105
|
+
flush_func = io.instance_variable_get(:@FlushConsoleInputBuffer) ||
|
|
106
|
+
begin
|
|
107
|
+
Reline::Windows::Win32API.new("kernel32", "FlushConsoleInputBuffer", ["L"], "I")
|
|
108
|
+
rescue
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
if flush_func
|
|
112
|
+
io.instance_variable_set(:@FlushConsoleInputBuffer, flush_func)
|
|
113
|
+
begin
|
|
114
|
+
flush_func.call(new_in)
|
|
115
|
+
rescue
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
rescue StandardError
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
rescue StandardError
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Windows Reline Self-Healing Console Input Patch:
|
|
134
|
+
# If external commands or process interruptions invalidate @hConsoleInputHandle,
|
|
135
|
+
# prevent Reline::Windows#check_input_event from entering an infinite spin loop
|
|
136
|
+
# by refreshing the console input handle on WAIT_FAILED (-1 / 0xFFFFFFFF).
|
|
137
|
+
if defined?(Reline::Windows)
|
|
138
|
+
class Reline::Windows
|
|
139
|
+
unless method_defined?(:rubik_original_check_input_event)
|
|
140
|
+
alias rubik_original_check_input_event check_input_event
|
|
141
|
+
|
|
142
|
+
def check_input_event
|
|
143
|
+
if @hConsoleInputHandle.nil? || @hConsoleInputHandle.zero? || @hConsoleInputHandle == -1
|
|
144
|
+
@hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
|
|
145
|
+
@GetStdHandle.call(STD_INPUT_HANDLE)
|
|
146
|
+
rescue
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
num_of_events = 0.chr * 8
|
|
152
|
+
while @output_buf.empty?
|
|
153
|
+
Reline.core.line_editor.handle_signal
|
|
154
|
+
wait_res = @WaitForSingleObject.call(@hConsoleInputHandle, 100)
|
|
155
|
+
if wait_res != 0
|
|
156
|
+
if [0xFFFFFFFF, 4_294_967_295, -1].include?(wait_res)
|
|
157
|
+
@hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
|
|
158
|
+
@GetStdHandle.call(STD_INPUT_HANDLE)
|
|
159
|
+
rescue
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
@hConsoleHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
|
|
163
|
+
@GetStdHandle.call(STD_OUTPUT_HANDLE)
|
|
164
|
+
rescue
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
@legacy_console = begin
|
|
169
|
+
getconsolemode.nobits?(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
|
170
|
+
rescue
|
|
171
|
+
false
|
|
172
|
+
end
|
|
173
|
+
next
|
|
174
|
+
end
|
|
175
|
+
if @GetNumberOfConsoleInputEvents.call(@hConsoleInputHandle, num_of_events).zero? || num_of_events.unpack1("L").zero?
|
|
176
|
+
@hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
|
|
177
|
+
@GetStdHandle.call(STD_INPUT_HANDLE)
|
|
178
|
+
rescue
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
next
|
|
182
|
+
end
|
|
183
|
+
input_records = 0.chr * 20 * 80
|
|
184
|
+
read_event = 0.chr * 4
|
|
185
|
+
if @ReadConsoleInputW.call(@hConsoleInputHandle, input_records, 80, read_event).zero?
|
|
186
|
+
@hConsoleInputHandle = begin # rubocop:disable Naming/VariableName -- Win32 FFI SDK adlandırması; yalnızca Windows konsol yolu
|
|
187
|
+
@GetStdHandle.call(STD_INPUT_HANDLE)
|
|
188
|
+
rescue
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
else
|
|
192
|
+
read_events = read_event.unpack1("L") || 0
|
|
193
|
+
read_events.times do |idx|
|
|
194
|
+
input_record = input_records[idx * 20, 20]
|
|
195
|
+
next unless input_record && input_record.bytesize >= 20
|
|
196
|
+
|
|
197
|
+
event = input_record[0, 2].unpack1("s*")
|
|
198
|
+
case event
|
|
199
|
+
when WINDOW_BUFFER_SIZE_EVENT
|
|
200
|
+
@winch_handler&.call
|
|
201
|
+
when KEY_EVENT
|
|
202
|
+
key_down = input_record[4, 4].unpack1("l*")
|
|
203
|
+
repeat_count = input_record[8, 2].unpack1("s*")
|
|
204
|
+
virtual_key_code = input_record[10, 2].unpack1("s*")
|
|
205
|
+
virtual_scan_code = input_record[12, 2].unpack1("s*")
|
|
206
|
+
char_code = input_record[14, 2].unpack1("S*")
|
|
207
|
+
control_key_state = input_record[16, 2].unpack1("S*")
|
|
208
|
+
is_key_down = !key_down.zero?
|
|
209
|
+
process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) if is_key_down
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|