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,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module UI
|
|
5
|
+
# Colorized unified-diff viewer for `!diff`: added/removed lines carry a
|
|
6
|
+
# color-blind safe cyan/red pair (never green-vs-red), hunk headers blue.
|
|
7
|
+
# The +/- signs stay in the text, so color is never the only channel.
|
|
8
|
+
# On interactive consoles a follow-up single-keypress menu offers apply.
|
|
9
|
+
# Non-TTY runs keep the plain diff byte-for-byte (tests, pipes, session
|
|
10
|
+
# replay).
|
|
11
|
+
module DiffViewer
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def render(shell, theme, diff_text)
|
|
15
|
+
return if diff_text.to_s.empty?
|
|
16
|
+
|
|
17
|
+
require_relative "../security/redactor" unless defined?(Rubik::Security::Redactor)
|
|
18
|
+
clean_diff = Rubik::Security::Redactor.redact(diff_text.to_s)
|
|
19
|
+
|
|
20
|
+
if theme.color?
|
|
21
|
+
shell.output.puts colorize(clean_diff, theme)
|
|
22
|
+
else
|
|
23
|
+
shell.output.puts clean_diff
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
offer_actions(shell, theme) if interactive?(shell)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def colorize(diff_text, theme)
|
|
30
|
+
diff_text.lines.map do |line|
|
|
31
|
+
stripped = line.chomp
|
|
32
|
+
styled =
|
|
33
|
+
if stripped.start_with?("+++", "---", "===")
|
|
34
|
+
theme.paint(stripped, :bold)
|
|
35
|
+
elsif stripped.start_with?("@@")
|
|
36
|
+
theme.paint(stripped, :info)
|
|
37
|
+
elsif stripped.start_with?("+")
|
|
38
|
+
theme.paint(stripped, :added)
|
|
39
|
+
elsif stripped.start_with?("-")
|
|
40
|
+
theme.paint(stripped, :removed)
|
|
41
|
+
else
|
|
42
|
+
theme.paint(stripped, :dim)
|
|
43
|
+
end
|
|
44
|
+
"#{styled}\n"
|
|
45
|
+
end.join
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Single-key follow-up. Never raw #gets: mixing plain gets with Reline
|
|
49
|
+
# wedges the prompt on Windows (see Confirmations) — tty-prompt's
|
|
50
|
+
# keypress uses the same raw-mode path as every other UI surface.
|
|
51
|
+
def offer_actions(shell, theme)
|
|
52
|
+
prompt = Approvals.build_prompt(shell)
|
|
53
|
+
key = prompt.keypress(theme.paint(" a: uygula (!apply) · q veya enter: çık ", :dim))
|
|
54
|
+
apply_all(shell) if key.to_s.strip.downcase == "a"
|
|
55
|
+
rescue Interrupt
|
|
56
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
# apply! fails loud on containment breaks and IO errors — the user
|
|
59
|
+
# pressed "a", so the verdict must reach them, not vanish here.
|
|
60
|
+
shell.output.puts "\n[Hata] Uygulama başarısız: #{e.message}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def apply_all(shell)
|
|
64
|
+
applied = shell.buffer_manager.apply!
|
|
65
|
+
if applied.empty?
|
|
66
|
+
shell.output.puts "Uygulanacak değişiklik kalmadı."
|
|
67
|
+
else
|
|
68
|
+
shell.sync_context_and_ast(applied)
|
|
69
|
+
shell.output.puts "Uygulandı: #{applied.join(', ')}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def interactive?(shell)
|
|
74
|
+
Capability.interactive?(input: shell.input, output: shell.output)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "io/console"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Minimal rounded frame drawing used by the banner, approval cards and
|
|
8
|
+
# the input-box prompt. The glyphs come from the active Theme (unicode
|
|
9
|
+
# rounded or ascii), so the same code degrades gracefully on legacy
|
|
10
|
+
# consoles.
|
|
11
|
+
module Frame
|
|
12
|
+
# Terminal width of the given output (or the controlling console).
|
|
13
|
+
def self.width(io = nil)
|
|
14
|
+
w = if io.respond_to?(:winsize)
|
|
15
|
+
begin
|
|
16
|
+
io.winsize[1]
|
|
17
|
+
rescue SystemCallError, IOError
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
w = w.to_i
|
|
22
|
+
return w if w.positive?
|
|
23
|
+
|
|
24
|
+
console = begin
|
|
25
|
+
IO.console
|
|
26
|
+
rescue SystemCallError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
cols = console&.winsize&.[](1).to_i
|
|
30
|
+
cols.positive? ? cols : 80
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Standalone top border with an optional title segment; shared by
|
|
34
|
+
# #draw and the input-box prompt (ui.input_box).
|
|
35
|
+
def self.top_border(theme, width:, title: nil)
|
|
36
|
+
g = theme.glyphs
|
|
37
|
+
inner = [width - 4, 8].max
|
|
38
|
+
title_part = title ? "#{g[:dash]} #{title} " : ""
|
|
39
|
+
title_pad = [inner + 1 - display_width(title_part), 0].max
|
|
40
|
+
"#{g[:corner_tl]}#{g[:dash]}#{title_part}#{g[:dash] * title_pad}#{g[:corner_tr]}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Standalone bottom border matching #top_border.
|
|
44
|
+
def self.bottom_border(theme, width:)
|
|
45
|
+
g = theme.glyphs
|
|
46
|
+
inner = [width - 4, 8].max
|
|
47
|
+
"#{g[:corner_bl]}#{g[:dash] * (inner + 2)}#{g[:corner_br]}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Draws a one-cell padded frame around content lines.
|
|
51
|
+
# Returns the frame as a single string (with trailing newline).
|
|
52
|
+
def self.draw(lines, theme:, width: 60, title: nil)
|
|
53
|
+
return "" if lines.empty?
|
|
54
|
+
|
|
55
|
+
inner = [width - 4, 8].max
|
|
56
|
+
content = lines.map { |l| truncate_plain(theme, l, inner) }
|
|
57
|
+
|
|
58
|
+
out = +""
|
|
59
|
+
out << "#{top_border(theme, width: width, title: title)}\n"
|
|
60
|
+
content.each { |l| out << "#{theme.glyph(:rail)} #{l}#{' ' * [inner - display_width(l), 0].max} #{theme.glyph(:rail)}\n" }
|
|
61
|
+
out << "#{bottom_border(theme, width: width)}\n"
|
|
62
|
+
out
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Truncates to exactly `max` display cells, reserving room for the
|
|
66
|
+
# theme's ellipsis glyph ("…" unicode / "..." ascii) so every row
|
|
67
|
+
# keeps the frame's uniform width.
|
|
68
|
+
def self.truncate_plain(theme, str, max)
|
|
69
|
+
ellipsis = theme.glyph(:ellipsis)
|
|
70
|
+
return str if display_width(str) <= max
|
|
71
|
+
|
|
72
|
+
"#{str[0...(max - display_width(ellipsis))]}#{ellipsis}"
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
str[0...max]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# East-Asian wide + common emoji blocks that render two columns wide
|
|
78
|
+
# in terminals; padding must account for them or the right rail
|
|
79
|
+
# drifts (e.g. the 🎲 banner row).
|
|
80
|
+
WIDE_RANGES = [
|
|
81
|
+
[0x1100, 0x115F], [0x2E80, 0xA4CF], [0xAC00, 0xD7A3],
|
|
82
|
+
[0xF900, 0xFAFF], [0xFE30, 0xFE4F], [0xFF00, 0xFF60],
|
|
83
|
+
[0xFFE0, 0xFFE6], [0x1F300, 0x1F64F], [0x1F680, 0x1F6FF],
|
|
84
|
+
[0x1F900, 0x1F9FF], [0x20000, 0x3FFFD]
|
|
85
|
+
].freeze
|
|
86
|
+
|
|
87
|
+
def self.display_width(str)
|
|
88
|
+
Capability.strip_ansi(str.to_s).each_char.sum do |chr|
|
|
89
|
+
ord = chr.ord
|
|
90
|
+
WIDE_RANGES.any? { |(a, b)| ord.between?(a, b) } ? 2 : 1
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-markdown"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Line-oriented live markdown renderer for the streaming token flow.
|
|
8
|
+
# Completed lines are styled the moment their newline arrives (bold
|
|
9
|
+
# headings, inline code, fenced code rails); the tail is flushed on
|
|
10
|
+
# chunk_end. When styling is disabled (non-TTY / ui.markdown: off) every
|
|
11
|
+
# segment passes through verbatim so piped output stays byte-identical
|
|
12
|
+
# with the pre-UI streaming behavior.
|
|
13
|
+
#
|
|
14
|
+
# Until a step's first visible content arrives, blank lines are
|
|
15
|
+
# suppressed: the spinner's erased line plus the model's habitual
|
|
16
|
+
# leading "\n\n" would otherwise paint 2-3 empty rows at every step
|
|
17
|
+
# boundary. #finish ends one streamed response (Shell calls it on
|
|
18
|
+
# :chunk_end) and re-arms the suppression for the next step.
|
|
19
|
+
class MarkdownStream
|
|
20
|
+
def initialize(theme, enabled: true)
|
|
21
|
+
@theme = theme
|
|
22
|
+
@enabled = enabled && theme.color?
|
|
23
|
+
@pending = +""
|
|
24
|
+
@in_fence = false
|
|
25
|
+
@content_started = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Feeds a streaming delta. Yields ready-to-print segments (including
|
|
29
|
+
# their trailing newline) to the block as they complete.
|
|
30
|
+
def push(delta)
|
|
31
|
+
return if delta.nil? || delta.empty?
|
|
32
|
+
|
|
33
|
+
@pending << delta
|
|
34
|
+
while (idx = @pending.index("\n"))
|
|
35
|
+
line = @pending.slice!(0..idx)
|
|
36
|
+
next if suppress_leading_blank?(line)
|
|
37
|
+
|
|
38
|
+
yield render_segment(line)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Flushes the buffered tail at end-of-stream. Yields the final segment
|
|
43
|
+
# (plus a terminating newline when anything was streamed mid-line) so
|
|
44
|
+
# the caller never leaves the cursor hanging. Always re-arms leading
|
|
45
|
+
# blank suppression: one #finish call == one streamed step (:chunk_end).
|
|
46
|
+
def finish
|
|
47
|
+
unless @pending.empty?
|
|
48
|
+
tail = @pending
|
|
49
|
+
@pending = +""
|
|
50
|
+
yield "#{render_segment(tail)}\n" unless suppress_leading_blank?(tail)
|
|
51
|
+
end
|
|
52
|
+
@content_started = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Renders a complete markdown document in one shot (batch mode /
|
|
56
|
+
# command output). Falls back to the raw text when styling is off.
|
|
57
|
+
def self.render_document(text, theme, enabled: true)
|
|
58
|
+
return text if text.nil? || text.empty?
|
|
59
|
+
return text unless enabled && theme.color?
|
|
60
|
+
|
|
61
|
+
TTY::Markdown.parse(text, color: :always)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Leading-blank suppression (enabled styling only — piped output keeps
|
|
67
|
+
# its byte-identical contract). Marks the first visible line and lets
|
|
68
|
+
# everything after it through untouched.
|
|
69
|
+
def suppress_leading_blank?(line)
|
|
70
|
+
return false unless @enabled
|
|
71
|
+
return false if @content_started
|
|
72
|
+
|
|
73
|
+
if line.strip.empty?
|
|
74
|
+
true
|
|
75
|
+
else
|
|
76
|
+
@content_started = true
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def render_segment(segment)
|
|
82
|
+
return segment unless @enabled
|
|
83
|
+
|
|
84
|
+
line = segment.chomp
|
|
85
|
+
rendered =
|
|
86
|
+
if line.start_with?("```")
|
|
87
|
+
@in_fence = !@in_fence
|
|
88
|
+
@theme.paint(line, :dim)
|
|
89
|
+
elsif @in_fence
|
|
90
|
+
"#{@theme.paint("#{@theme.glyph(:rail)} ", :dim)}#{@theme.paint(line, :dim)}"
|
|
91
|
+
elsif line =~ /\A(#+)\s+(.*)\z/
|
|
92
|
+
@theme.paint("#{Regexp.last_match(1)} #{Regexp.last_match(2)}", :bold)
|
|
93
|
+
else
|
|
94
|
+
style_inline(line)
|
|
95
|
+
end
|
|
96
|
+
"#{rendered}#{"\n" if segment.end_with?("\n")}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Inline styles: **bold**, `code`. Conservative regexes; unmatched
|
|
100
|
+
# markers stay visible rather than swallowing text.
|
|
101
|
+
def style_inline(line)
|
|
102
|
+
out = line.gsub(/\*\*([^*]+)\*\*/) { @theme.paint(Regexp.last_match(1), :bold) }
|
|
103
|
+
out.gsub(/`([^`]+)`/) { |seg| @theme.paint(seg, :info) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Interactive agent/fiber board (`!agents`, the Antigravity /agents
|
|
8
|
+
# counterpart): lists runtime fibers with state and current activity,
|
|
9
|
+
# then offers an arrow-key detail view (state history, coeffect
|
|
10
|
+
# dependencies, orchestrator progress, Omega accumulator depth).
|
|
11
|
+
# Falls back to a static listing for pipes and tests.
|
|
12
|
+
module Panel
|
|
13
|
+
STATE_GLYPHS = {
|
|
14
|
+
active: "🟢",
|
|
15
|
+
pending: "⏳",
|
|
16
|
+
failed: "🔴",
|
|
17
|
+
disposed: "⚫",
|
|
18
|
+
unloaded: "⚪"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def render(shell, theme)
|
|
24
|
+
fibers = shell.runtime_manager&.fibers || {}
|
|
25
|
+
if fibers.empty?
|
|
26
|
+
shell.output.puts "Kayıtlı fiber/ajan yok."
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
shell.output.puts "=== Ajan / Fiber Panosu ==="
|
|
31
|
+
rows = fibers.values.sort_by { |f| STATE_ORDER[f.state] || 99 }
|
|
32
|
+
rows.each do |fiber|
|
|
33
|
+
shell.output.puts detail_line(fiber, theme)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
return unless interactive?(shell)
|
|
37
|
+
|
|
38
|
+
choice = select_fiber(shell, rows, theme)
|
|
39
|
+
show_fiber_detail(shell, choice, theme) if choice
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def detail_line(fiber, theme)
|
|
43
|
+
glyph = STATE_GLYPHS[fiber.state] || "❔"
|
|
44
|
+
activity = current_activity(fiber)
|
|
45
|
+
name_col = fiber.name.ljust(24)
|
|
46
|
+
"#{glyph} #{theme.paint(name_col, fiber.state == :active ? :accent : :dim)} #{activity}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current_activity(fiber)
|
|
50
|
+
inst = fiber.instance
|
|
51
|
+
if inst.respond_to?(:step_count)
|
|
52
|
+
depth = fiber.sub_context ? " · Ω#{fiber.sub_context.disposable_count}" : ""
|
|
53
|
+
"adım #{inst.step_count}/#{inst.max_steps}#{depth}"
|
|
54
|
+
elsif fiber.state == :failed
|
|
55
|
+
"hata: #{fiber.last_error&.message.to_s[0, 50]}"
|
|
56
|
+
else
|
|
57
|
+
deps = fiber.inject
|
|
58
|
+
deps.empty? ? "-" : "coeffects: #{deps.join(', ')}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def select_fiber(shell, rows, _theme)
|
|
63
|
+
prompt = Approvals.build_prompt(shell)
|
|
64
|
+
choices = rows.map { |f| { name: "#{STATE_GLYPHS[f.state]} #{f.name}", value: f } }
|
|
65
|
+
choices << { name: "← çıkış", value: nil }
|
|
66
|
+
prompt.select("Detay için fiber seç:") do |menu|
|
|
67
|
+
choices.each { |c| menu.choice c[:name], c[:value] }
|
|
68
|
+
end
|
|
69
|
+
rescue Interrupt
|
|
70
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
71
|
+
nil
|
|
72
|
+
rescue StandardError
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def show_fiber_detail(shell, fiber, theme)
|
|
77
|
+
inst = fiber.instance
|
|
78
|
+
shell.output.puts
|
|
79
|
+
shell.output.puts theme.paint("── #{fiber.name} ──", :bold)
|
|
80
|
+
shell.output.puts " Durum : #{STATE_GLYPHS[fiber.state]} #{fiber.state}"
|
|
81
|
+
shell.output.puts " Coeffects : #{fiber.inject.join(', ')}"
|
|
82
|
+
shell.output.puts " Ω derinliği : #{fiber.sub_context ? fiber.sub_context.disposable_count : 0}"
|
|
83
|
+
if inst.respond_to?(:step_count)
|
|
84
|
+
shell.output.puts " Adım : #{inst.step_count}/#{inst.max_steps}"
|
|
85
|
+
shell.output.puts " Token : #{inst.total_tokens.inspect}" if inst.respond_to?(:total_tokens)
|
|
86
|
+
end
|
|
87
|
+
shell.output.puts " Son hata : #{fiber.last_error&.message}" if fiber.last_error
|
|
88
|
+
shell.output.puts theme.paint(" (enter ile panoya dön)", :dim)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def interactive?(shell)
|
|
92
|
+
Capability.interactive?(input: shell.input, output: shell.output)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
STATE_ORDER = { active: 0, pending: 1, failed: 2, disposed: 3, unloaded: 4 }.freeze
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../platform"
|
|
5
|
+
require_relative "../uris"
|
|
6
|
+
require_relative "../repl/step_formatter"
|
|
7
|
+
require_relative "spinner"
|
|
8
|
+
require_relative "capability"
|
|
9
|
+
require_relative "theme"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module UI
|
|
13
|
+
# Display formatter for orchestrator step events implementing the Rubik
|
|
14
|
+
# agent-flow visual language:
|
|
15
|
+
#
|
|
16
|
+
# ✻ Kavuruyor · 3s (ctrl+c: kes) <- live spinner (UI::Spinner)
|
|
17
|
+
# ⏺ read_file — lib/main.rb <- action line
|
|
18
|
+
# ⎿ 3 lines · 15.2ms <- nested result branch
|
|
19
|
+
#
|
|
20
|
+
# Responsibilities (Plan 51, Decision D2 — StepFormatter stays the
|
|
21
|
+
# semantic layer, this class is the presentation decorator):
|
|
22
|
+
# * owns the Spinner lifecycle (:thinking starts it, any other event
|
|
23
|
+
# stops it before text is printed),
|
|
24
|
+
# * maps the 13 orchestrator event types to the glyph tree at
|
|
25
|
+
# normal/verbose levels, consuming the legacy formatter's public
|
|
26
|
+
# semantic helpers (thinking_parts, primary_arg_value,
|
|
27
|
+
# summarize_result, thought_first_line) instead of re-parsing events,
|
|
28
|
+
# * delegates raw [DEBUG:...] lines verbatim at debug level and
|
|
29
|
+
# re-anchors stable event bodies (warnings, retries, finish) with
|
|
30
|
+
# two leading spaces,
|
|
31
|
+
# * paints through the Theme so disabled color yields plain text with
|
|
32
|
+
# identical glyphs (one visual language, deterministic pipes).
|
|
33
|
+
class Renderer
|
|
34
|
+
LEVEL_QUIET = Rubik::REPL::StepFormatter::LEVEL_QUIET
|
|
35
|
+
|
|
36
|
+
attr_reader :spinner, :theme
|
|
37
|
+
|
|
38
|
+
def initialize(output:, config: nil, verbosity: nil, max_steps: nil, theme: nil)
|
|
39
|
+
@config = config
|
|
40
|
+
@max_steps = max_steps
|
|
41
|
+
@output = output
|
|
42
|
+
@theme = theme || Theme.for(output, config)
|
|
43
|
+
@legacy = Rubik::REPL::StepFormatter.new(config: config, verbosity: verbosity, max_steps: max_steps)
|
|
44
|
+
@spinner = Spinner.new(output, @theme, config: config)
|
|
45
|
+
@spinner_capable = Capability.spinner?(output, config) && level > LEVEL_QUIET
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def level
|
|
49
|
+
@legacy.level
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Renders one orchestrator event to a printable String (or nil to stay
|
|
53
|
+
# silent). Must be called for EVERY event so the spinner is stopped
|
|
54
|
+
# before any other output is printed.
|
|
55
|
+
def render(step_info)
|
|
56
|
+
return nil unless step_info.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
type = step_info[:type]&.to_sym
|
|
59
|
+
|
|
60
|
+
# Teslimat süslemeden önce gelir: :user_output debug dahil her
|
|
61
|
+
# seviyede doğrudan işlenir — debug delegasyonu onu StepFormatter'a
|
|
62
|
+
# (kolu yok → nil) götürür ve stub yalan olurdu.
|
|
63
|
+
return render_user_output(step_info) if type == :user_output
|
|
64
|
+
|
|
65
|
+
# Raw debug lines come verbatim from the legacy formatter.
|
|
66
|
+
return @legacy.format(step_info) if level >= Rubik::REPL::StepFormatter::LEVEL_DEBUG
|
|
67
|
+
|
|
68
|
+
case type
|
|
69
|
+
when :thinking then render_thinking(step_info)
|
|
70
|
+
when :tool_call then render_tool_call(step_info)
|
|
71
|
+
when :tool_result then render_tool_result(step_info)
|
|
72
|
+
when :user_output then render_user_output(step_info)
|
|
73
|
+
when :llm_thought then render_llm_thought(step_info)
|
|
74
|
+
when :chunk, :chunk_end
|
|
75
|
+
@spinner.stop
|
|
76
|
+
nil
|
|
77
|
+
else
|
|
78
|
+
restyle_legacy(step_info)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def render_thinking(step_info)
|
|
85
|
+
return nil if level <= LEVEL_QUIET
|
|
86
|
+
|
|
87
|
+
if @spinner_capable
|
|
88
|
+
@spinner.start(step: step_info[:step], max_steps: step_info[:max_steps])
|
|
89
|
+
return nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
parts = @legacy.thinking_parts(step_info)
|
|
93
|
+
target_part = parts[:target].empty? ? "" : " (#{parts[:target]})"
|
|
94
|
+
"#{@theme.glyph(:clock)} #{@theme.spinner_verbs.first} [#{parts[:step]}/#{parts[:max]}]#{target_part}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def render_tool_call(step_info)
|
|
98
|
+
@spinner.stop
|
|
99
|
+
return nil if level <= LEVEL_QUIET
|
|
100
|
+
|
|
101
|
+
tool = step_info[:tool]
|
|
102
|
+
args = step_info[:args] || {}
|
|
103
|
+
header = @theme.paint("#{@theme.glyph(:action)} #{tool}", :accent)
|
|
104
|
+
|
|
105
|
+
if level >= Rubik::REPL::StepFormatter::LEVEL_VERBOSE
|
|
106
|
+
"#{header} #{@theme.paint(Rubik::Platform.safe_json_generate(args), :dim)}"
|
|
107
|
+
else
|
|
108
|
+
summary = pretty_storage_arg(@legacy.primary_arg_value(args).to_s)
|
|
109
|
+
summary.empty? ? header : "#{header} #{theme_dim_dash(summary)}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Depo varlıklarını kullanıcıya 'göster' dendiğinde ajan uzun
|
|
114
|
+
# rubik://storage URI'leri kullanır; eylem satırında önek düşer,
|
|
115
|
+
# içerik karması 8 hexe kısaltılır (storage-show-surface planı C).
|
|
116
|
+
# rubik://storage/js-tangram/review/review.1.b54cddc277e87d25
|
|
117
|
+
# → js-tangram/review/review.1.b54cddc2…
|
|
118
|
+
def pretty_storage_arg(value)
|
|
119
|
+
rel = Rubik::Uris.storage_relative(value)
|
|
120
|
+
return value unless rel
|
|
121
|
+
|
|
122
|
+
rel.sub(/\.json\z/i, "").sub(/([0-9a-f]{8})[0-9a-f]{8}\z/i, "\\1…")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# :user_output (show_file sözleşmesi): tam içerik kullanıcıya aynen
|
|
126
|
+
# akar — soluklaştırılmaz, okunabilirlik önce gelir; yalnız başlık
|
|
127
|
+
# satırı dim boyanır. Model bu içeriği görmedi (stub aldı). Bu event
|
|
128
|
+
# süsleme değil TESLİMAT'tır: quiet dahil her seviyede basılır
|
|
129
|
+
# (sessiz düşüş, modelin "rendered to terminal" stub'ını yalan yapar).
|
|
130
|
+
def render_user_output(step_info)
|
|
131
|
+
@spinner.stop
|
|
132
|
+
|
|
133
|
+
header = @theme.paint("#{@theme.glyph(:branch)} #{step_info[:label]}", :dim)
|
|
134
|
+
"#{header}\n#{step_info[:content]}\n"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_tool_result(step_info)
|
|
138
|
+
@spinner.stop
|
|
139
|
+
return nil if level <= LEVEL_QUIET
|
|
140
|
+
|
|
141
|
+
status = (step_info[:status] || :ok).to_sym
|
|
142
|
+
duration = step_info[:duration_ms] || 0.0
|
|
143
|
+
output = step_info[:output].to_s
|
|
144
|
+
branch = "#{@theme.glyph(:branch)} "
|
|
145
|
+
|
|
146
|
+
if status != :ok
|
|
147
|
+
err = step_info[:error]
|
|
148
|
+
err = output.lines.first.to_s.strip if err.nil? || err.to_s.empty?
|
|
149
|
+
err = "Command failed" if err.to_s.empty?
|
|
150
|
+
if err.start_with?("Security blocked:") || output.start_with?("Security blocked:")
|
|
151
|
+
sec_line = output.lines.first.to_s.strip
|
|
152
|
+
hint = output[/!(?:allow-cmd|allow-dir)\s+[^\n]+/]&.strip
|
|
153
|
+
msg = hint ? "#{sec_line} [#{hint}]" : sec_line
|
|
154
|
+
"#{branch}#{@theme.paint("#{@theme.glyph(:warn)} #{msg}", :warn)}"
|
|
155
|
+
else
|
|
156
|
+
"#{branch}#{@theme.paint("#{@theme.glyph(:err)} #{err} (#{duration}ms)", :error)}"
|
|
157
|
+
end
|
|
158
|
+
elsif level >= Rubik::REPL::StepFormatter::LEVEL_VERBOSE
|
|
159
|
+
lines_size = step_info[:line_count] || output.lines.size
|
|
160
|
+
byte_size = step_info[:byte_size] || output.bytesize
|
|
161
|
+
header = "#{branch}#{lines_size} lines (#{byte_size} B, #{duration}ms)"
|
|
162
|
+
return @theme.paint(header, :dim) if output.empty?
|
|
163
|
+
|
|
164
|
+
preview = output.lines.first(5).map { |l| " #{l.chomp}" }.join("\n")
|
|
165
|
+
"#{@theme.paint(header, :dim)}\n#{@theme.paint(" #{@theme.glyph(:preview)} Preview:", :dim)}\n#{@theme.paint(preview, :dim)}"
|
|
166
|
+
else
|
|
167
|
+
summary = @legacy.summarize_result(output, step_info)
|
|
168
|
+
"#{branch}#{@theme.paint("#{summary} · #{duration}ms", :dim)}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def render_llm_thought(step_info)
|
|
173
|
+
@spinner.stop
|
|
174
|
+
return nil if level < Rubik::REPL::StepFormatter::LEVEL_VERBOSE
|
|
175
|
+
|
|
176
|
+
first_line = @legacy.thought_first_line(step_info[:content])
|
|
177
|
+
return nil if first_line.nil? || first_line.empty?
|
|
178
|
+
|
|
179
|
+
" #{@theme.paint("#{@theme.glyph(:thought)} #{first_line}", :thought)}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Events whose message bodies stay stable (warnings, retries, finish,
|
|
183
|
+
# auto-apply, compaction): keep the legacy text, re-anchor it to the
|
|
184
|
+
# glyph tree with two leading spaces.
|
|
185
|
+
def restyle_legacy(step_info)
|
|
186
|
+
@spinner.stop
|
|
187
|
+
legacy_line = @legacy.format(step_info)
|
|
188
|
+
return nil if legacy_line.nil? || legacy_line.empty?
|
|
189
|
+
|
|
190
|
+
styled = case step_info[:type]&.to_sym
|
|
191
|
+
when :step_warning, :emergency_compact, :llm_retry
|
|
192
|
+
@theme.paint(legacy_line.strip, :warn)
|
|
193
|
+
when :auto_apply, :finish
|
|
194
|
+
@theme.paint(legacy_line.strip, :success)
|
|
195
|
+
else
|
|
196
|
+
legacy_line.strip
|
|
197
|
+
end
|
|
198
|
+
" #{styled}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def theme_dim_dash(text)
|
|
202
|
+
@theme.paint("— #{text}", :dim)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|