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,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Pure fiber ordering for Runtime::Manager (G4 extraction): Kahn's algorithm
|
|
4
|
+
# over provides/injects/requires with legacy-alias canonicalization. Returns
|
|
5
|
+
# [sorted, cycled] — cycle failure REPORTING stays in the Manager (it touches
|
|
6
|
+
# the context's dmesg service); this module only computes the order.
|
|
7
|
+
module Rubik
|
|
8
|
+
module Runtime
|
|
9
|
+
module FiberOrdering
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def sort(fibers)
|
|
13
|
+
return [fibers, []] if fibers.size <= 1
|
|
14
|
+
|
|
15
|
+
provider_map = {}
|
|
16
|
+
fibers.each do |f|
|
|
17
|
+
(f.respond_to?(:provides) ? f.provides : [f.name.downcase]).each do |k|
|
|
18
|
+
(provider_map[k] ||= []) << f
|
|
19
|
+
canon = Rubik::Coeffects.canonical(k)
|
|
20
|
+
(provider_map[canon] ||= []) << f if canon && canon != k
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
dependents = Hash.new { |h, k| h[k] = Set.new }
|
|
25
|
+
in_degree = Hash.new(0)
|
|
26
|
+
fibers.each do |consumer|
|
|
27
|
+
needed = (Array(consumer.inject) + Array(consumer.requires)).map { |d| d.to_s.downcase }
|
|
28
|
+
prereqs = Set.new
|
|
29
|
+
needed.each do |k|
|
|
30
|
+
providers = (provider_map[k] || []) + (provider_map[Rubik::Coeffects.canonical(k)] || [])
|
|
31
|
+
providers.each { |p| prereqs << p if p != consumer && fibers.include?(p) }
|
|
32
|
+
end
|
|
33
|
+
in_degree[consumer] = prereqs.size
|
|
34
|
+
prereqs.each { |p| dependents[p] << consumer }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
queue = fibers.select { |f| in_degree[f].zero? }
|
|
38
|
+
sorted = []
|
|
39
|
+
until queue.empty?
|
|
40
|
+
curr = queue.shift
|
|
41
|
+
sorted << curr
|
|
42
|
+
dependents[curr].each do |dep|
|
|
43
|
+
in_degree[dep] -= 1
|
|
44
|
+
queue << dep if in_degree[dep].zero?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
[sorted, fibers - sorted]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Failed-fiber retry gate (G4 extraction from Runtime::Manager; review
|
|
4
|
+
# 2026-09-17, Finding 3): a permanently-broken factory used to re-run on EVERY
|
|
5
|
+
# reconcile_all (each provide/register). Failures back off exponentially and
|
|
6
|
+
# stop after MAX_RETRIES attempts; a successful activation or a fresh
|
|
7
|
+
# register_fiber clears the bookkeeping.
|
|
8
|
+
#
|
|
9
|
+
# The backoff sleep must NOT run while holding the Manager Monitor: the sleep
|
|
10
|
+
# happens with the monitor fully released (depth 0) so it never serializes
|
|
11
|
+
# other threads; bookkeeping is committed atomically under it first, and the
|
|
12
|
+
# factory activation re-acquires the lock (depth 1) afterwards.
|
|
13
|
+
module Rubik
|
|
14
|
+
module Runtime
|
|
15
|
+
class FiberRetryGate
|
|
16
|
+
MAX_RETRIES = 5
|
|
17
|
+
BACKOFF_BASE_S = 0.05
|
|
18
|
+
|
|
19
|
+
def initialize(monitor, context)
|
|
20
|
+
@monitor = monitor
|
|
21
|
+
@context = context
|
|
22
|
+
@bookkeeping = {} # name -> { attempts:, last_attempt_at:, warned: }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Explicit re-registration / unregistration resets the retry budget.
|
|
26
|
+
def clear(name)
|
|
27
|
+
@monitor.synchronize { @bookkeeping.delete(name) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# No bookkeeping outlives the manager (fresh-eye R2).
|
|
31
|
+
def clear_all
|
|
32
|
+
@bookkeeping.clear
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attempt(fiber)
|
|
36
|
+
delay = 0.0
|
|
37
|
+
elapsed = 0.0
|
|
38
|
+
@monitor.synchronize do
|
|
39
|
+
book = @bookkeeping[fiber.name] ||= { attempts: 0, last_attempt_at: nil }
|
|
40
|
+
if book[:attempts] >= MAX_RETRIES
|
|
41
|
+
warn_budget_exhausted(fiber, book) # atomic, one-shot, stays under the lock
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
prev_attempts = book[:attempts]
|
|
46
|
+
ts_prev = book[:last_attempt_at]
|
|
47
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
48
|
+
delay = ts_prev ? BACKOFF_BASE_S * (2**prev_attempts) : 0.0
|
|
49
|
+
elapsed = ts_prev ? (now - ts_prev) : Float::INFINITY
|
|
50
|
+
book[:attempts] += 1
|
|
51
|
+
book[:last_attempt_at] = now
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Sleep happens with the monitor FULLY released (depth 0) so it never
|
|
55
|
+
# serializes other threads; bookkeeping is already committed above.
|
|
56
|
+
sleep(delay - elapsed) if delay > elapsed
|
|
57
|
+
|
|
58
|
+
# Re-acquire (depth 1) and run the factory: the failing fiber is not in
|
|
59
|
+
# the manager's activation marker, so the reentrancy guard lets this
|
|
60
|
+
# re-activate it. A successful activation clears its bookkeeping.
|
|
61
|
+
@monitor.synchronize do
|
|
62
|
+
yield fiber
|
|
63
|
+
@bookkeeping.delete(fiber.name) if fiber.state == :active
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# One-shot visibility when a fiber's retry budget is exhausted (fresh-eye
|
|
70
|
+
# R3): the old always-retry behavior was noisy but visible; without this
|
|
71
|
+
# warn a permanently-broken factory would sink silently into :failed.
|
|
72
|
+
# Warns exactly once per budget cycle — re-registration resets the flag.
|
|
73
|
+
def warn_budget_exhausted(fiber, book)
|
|
74
|
+
return if book[:warned]
|
|
75
|
+
|
|
76
|
+
book[:warned] = true
|
|
77
|
+
last = fiber.last_error
|
|
78
|
+
Rubik::Log.warn(:runtime, "fiber '#{fiber.name}' retry budget exhausted " \
|
|
79
|
+
"(#{book[:attempts]} attempts, MAX_FIBER_RETRIES=#{MAX_RETRIES}) — " \
|
|
80
|
+
"staying :failed; re-register to retry. Last error: #{last&.class}: #{last&.message}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require_relative "../coeffects"
|
|
5
|
+
require_relative "service_registration"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Runtime
|
|
9
|
+
module IndexService
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def register(context_or_manager, index)
|
|
13
|
+
ServiceRegistration.call(context_or_manager) do |ctx|
|
|
14
|
+
next index unless ctx
|
|
15
|
+
|
|
16
|
+
ctx.provide(Rubik::Coeffects::CONTEXT_INDEX, index)
|
|
17
|
+
index
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sync_roots(context_or_manager, index, additional_roots:, ast_graph: nil, cache_store: nil)
|
|
22
|
+
return unless index
|
|
23
|
+
|
|
24
|
+
index.additional_roots = Array(additional_roots)
|
|
25
|
+
index.full_scan
|
|
26
|
+
|
|
27
|
+
context_or_manager.reconcile_all if context_or_manager.respond_to?(:reconcile_all)
|
|
28
|
+
|
|
29
|
+
if ast_graph
|
|
30
|
+
root_d = index.root_dir
|
|
31
|
+
|
|
32
|
+
# Re-index all supported files known to the context index.
|
|
33
|
+
# Rely on File.exist?(full_path) as the sole guard: a file
|
|
34
|
+
# already in the graph simply re-indexes harmlessly.
|
|
35
|
+
index.files.each_key do |rel_file|
|
|
36
|
+
full_path = File.expand_path(rel_file, root_d)
|
|
37
|
+
ext = File.extname(rel_file).downcase
|
|
38
|
+
if defined?(Rubik::AST::Extractor) && Rubik::AST::Extractor.supported_extensions.include?(ext) &&
|
|
39
|
+
File.exist?(full_path)
|
|
40
|
+
ast_graph.index_file(full_path)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if cache_store && File.directory?(root_d)
|
|
45
|
+
begin
|
|
46
|
+
ast_graph.save_to_cache(cache_store)
|
|
47
|
+
rescue => e
|
|
48
|
+
Rubik::Log.warn(:ast, "AST graph cache save failed (#{e.class}: #{e.message}); stale cache retained")
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
index
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "fiber"
|
|
4
|
+
require_relative "../coeffects"
|
|
5
|
+
require_relative "service_registration"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Runtime
|
|
9
|
+
class AgentLoopFiber < ComponentFiber
|
|
10
|
+
attr_reader :shell
|
|
11
|
+
|
|
12
|
+
def initialize(name: "orchestrator_loop", inject: ["llm_client", "tool_registry", "buffer_manager"], config: {}, shell: nil, &factory)
|
|
13
|
+
@shell = shell
|
|
14
|
+
super(name: name, inject: inject, config: config, &factory)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_instance(sub_context, config)
|
|
18
|
+
LoopService.build_loop(sub_context, shell: @shell, config: config)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module LoopService
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def register(runtime_manager_or_context, shell: nil, name: "orchestrator_loop", inject: ["llm_client", "tool_registry", "buffer_manager"], config: {})
|
|
26
|
+
fiber = AgentLoopFiber.new(name: name, inject: inject, config: config, shell: shell)
|
|
27
|
+
# Fiber registration is not a plain provide: Manager#register_fiber
|
|
28
|
+
# reconciles itself, so the spine's trailing reconcile is suppressed.
|
|
29
|
+
ServiceRegistration.call(runtime_manager_or_context, reconcile: false) do |ctx|
|
|
30
|
+
if runtime_manager_or_context.respond_to?(:register_fiber)
|
|
31
|
+
runtime_manager_or_context.register_fiber(fiber)
|
|
32
|
+
elsif ctx && fiber.satisfiable?(ctx)
|
|
33
|
+
fiber.activate!(ctx)
|
|
34
|
+
end
|
|
35
|
+
fiber
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_loop(context, shell: nil, config: {})
|
|
40
|
+
ctx = context.respond_to?(:context) ? context.context : context
|
|
41
|
+
provider = config[:provider] || (ctx.respond_to?(:get) ? Rubik::Coeffects.get(ctx, Rubik::Coeffects::LLM_CLIENT) : nil)
|
|
42
|
+
tool_registry = config[:tool_registry] || (if ctx.respond_to?(:get)
|
|
43
|
+
Rubik::Coeffects.get(ctx,
|
|
44
|
+
Rubik::Coeffects::TOOL_REGISTRY)
|
|
45
|
+
end) || shell&.build_tool_registry
|
|
46
|
+
app_config = config[:config] || shell&.config ||
|
|
47
|
+
(ctx.respond_to?(:get) ? Rubik::Coeffects.get(ctx, Rubik::Coeffects::CONFIG) : nil)
|
|
48
|
+
|
|
49
|
+
max_steps = config[:max_steps] || (app_config.respond_to?(:fetch) ? app_config.fetch("agent.max_steps", 40).to_i : 40)
|
|
50
|
+
warn_ratio = config[:warn_at_ratio] || (app_config.respond_to?(:fetch) ? app_config.fetch("agent.warn_at_ratio", 0.75).to_f : 0.75)
|
|
51
|
+
model = config[:model]
|
|
52
|
+
on_step = config[:on_step]
|
|
53
|
+
context_limit = config[:context_limit]
|
|
54
|
+
max_output_tokens = config[:max_output_tokens] ||
|
|
55
|
+
(app_config.respond_to?(:fetch) && app_config.fetch("llm.default_max_output_tokens", 4_096)&.to_i) || 4_096
|
|
56
|
+
|
|
57
|
+
require_relative "../orchestrator"
|
|
58
|
+
|
|
59
|
+
Rubik::Orchestrator::Loop.new(
|
|
60
|
+
provider: provider,
|
|
61
|
+
tool_registry: tool_registry,
|
|
62
|
+
max_steps: max_steps,
|
|
63
|
+
warn_at_ratio: warn_ratio,
|
|
64
|
+
model: model,
|
|
65
|
+
shell: shell,
|
|
66
|
+
context_limit: context_limit,
|
|
67
|
+
max_output_tokens: max_output_tokens,
|
|
68
|
+
on_step: on_step
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
require_relative "context"
|
|
5
|
+
require_relative "fiber"
|
|
6
|
+
require_relative "fiber_ordering"
|
|
7
|
+
require_relative "fiber_retry_gate"
|
|
8
|
+
|
|
9
|
+
module Rubik
|
|
10
|
+
module Runtime
|
|
11
|
+
class Manager
|
|
12
|
+
attr_reader :context, :retry_gate
|
|
13
|
+
|
|
14
|
+
def initialize(context = nil)
|
|
15
|
+
@context = context || Context.new
|
|
16
|
+
@fibers = {}
|
|
17
|
+
@monitor = Monitor.new
|
|
18
|
+
# Fiber names with a factory currently in flight (reentrant reconcile
|
|
19
|
+
# guard): a factory that re-provides its own dependency re-enters
|
|
20
|
+
# reconcile_all on the same thread; without this marker the pending/
|
|
21
|
+
# active branches would re-activate the fiber and recurse unbounded
|
|
22
|
+
# (Monitor is reentrant, so the spiral ends in SystemStackError, not
|
|
23
|
+
# a deadlock).
|
|
24
|
+
@activating = {}
|
|
25
|
+
# Failed-fiber retry budget/backoff (G4): the gate owns the
|
|
26
|
+
# bookkeeping; the manager routes clear/attempt through it.
|
|
27
|
+
@retry_gate = FiberRetryGate.new(@monitor, @context)
|
|
28
|
+
# name -> Set of canonical coeffect keys this fiber's factory injects.
|
|
29
|
+
# The reactive surface (provide/unprovide) consults this to decide which
|
|
30
|
+
# fibers are gated on a coeffect that changed, so retracting a service
|
|
31
|
+
# through the reactive path retracts (deactivates) every fiber gated on
|
|
32
|
+
# it — matching provide's on-demand reactivation (AGENTS.md §5, Finding B).
|
|
33
|
+
@fiber_deps = {}
|
|
34
|
+
@disposed = false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Retry-policy constants moved to FiberRetryGate (G4); aliases keep the
|
|
38
|
+
# Manager-facing names (tests, warn-message text) stable.
|
|
39
|
+
MAX_FIBER_RETRIES = FiberRetryGate::MAX_RETRIES
|
|
40
|
+
RETRY_BACKOFF_BASE_S = FiberRetryGate::BACKOFF_BASE_S
|
|
41
|
+
|
|
42
|
+
def fibers
|
|
43
|
+
@monitor.synchronize { @fibers.dup }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def disposed?
|
|
47
|
+
@monitor.synchronize { @disposed }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def register_component(name, inject: [], requires: [], provides: [], config: {}, &factory)
|
|
51
|
+
name_str = name.to_s
|
|
52
|
+
fiber = ComponentFiber.new(name: name_str, inject: inject, requires: requires, provides: provides, config: config, &factory)
|
|
53
|
+
register_fiber(fiber)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Reentrant lock safety: Monitor#synchronize is explicitly reentrant (recursive),
|
|
57
|
+
# allowing register_fiber to store the fiber and activate it. The activation
|
|
58
|
+
# is performed OUTSIDE the lock (via reconcile_fiber) so a failing fiber's
|
|
59
|
+
# backoff sleep never serializes other threads (Finding 3).
|
|
60
|
+
def register_fiber(fiber)
|
|
61
|
+
deps = Array(fiber.inject).map { |n| n.to_s.downcase }
|
|
62
|
+
@monitor.synchronize do
|
|
63
|
+
raise ContextDisposedError, "Cannot register fiber on a disposed Manager" if @disposed
|
|
64
|
+
|
|
65
|
+
@fiber_deps.delete(fiber.name) unless deps.empty?
|
|
66
|
+
@fiber_deps[fiber.name] = deps.to_set unless deps.empty?
|
|
67
|
+
@fibers[fiber.name] = fiber
|
|
68
|
+
@retry_gate.clear(fiber.name) # explicit re-registration resets the retry budget
|
|
69
|
+
end
|
|
70
|
+
reconcile_fiber(fiber)
|
|
71
|
+
fiber
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def unregister_component(name)
|
|
75
|
+
@monitor.synchronize do
|
|
76
|
+
name_str = name.to_s
|
|
77
|
+
fiber = @fibers.delete(name_str)
|
|
78
|
+
@fiber_deps.delete(name_str)
|
|
79
|
+
@retry_gate.clear(name_str) # fresh-eye R2: disposed fibers must not leak their retry budget
|
|
80
|
+
fiber&.dispose!
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Reactive retraction (AGENTS.md §5, Finding B): unlike Context#unprovide
|
|
85
|
+
# (which removes a service from the coeffect registry without touching
|
|
86
|
+
# dependents), retraction here routes through reconcile_all(changed_service:).
|
|
87
|
+
# Any fiber whose factory injects this coeffect therefore transitions
|
|
88
|
+
# Active->Pending and its partial inverse runs (LIFO via its sub-context),
|
|
89
|
+
# instead of orphaning against a now-stale/coeffect-less context. Re-
|
|
90
|
+
# providing the key afterwards is symmetric to provide: the fiber is
|
|
91
|
+
# reactivated on-demand.
|
|
92
|
+
def unprovide(service_name)
|
|
93
|
+
key = service_name.to_s.downcase
|
|
94
|
+
@monitor.synchronize { @context.unprovide(key) }
|
|
95
|
+
reconcile_all(changed_service: key)
|
|
96
|
+
nil # idempotent no-op returns nil (unprovide has no meaningful return)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Reactive provide (AGENTS.md §5): adding a coeffect activates any pending
|
|
100
|
+
# fiber gated on it, and reactivates an active fiber when the SAME service
|
|
101
|
+
# object is re-provided (identity change), so its new instance is injected.
|
|
102
|
+
# Both transitions run reconcile_all(changed_service:) OUTSIDE the monitor.
|
|
103
|
+
def provide(service_name, service)
|
|
104
|
+
@monitor.synchronize { @context.provide(service_name, service) }
|
|
105
|
+
# reconcile_all runs OUTSIDE this lock so a failing fiber's backoff sleep
|
|
106
|
+
# never serializes other threads (Finding 3).
|
|
107
|
+
reconcile_all(changed_service: service_name)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Reentrant lock safety: Monitor#synchronize is explicitly reentrant (recursive),
|
|
111
|
+
# allowing swap_component to safely invoke unregister_component and register_component
|
|
112
|
+
# from within the same thread without deadlocking.
|
|
113
|
+
def swap_component(name, inject: [], requires: [], provides: [], config: {}, &new_factory)
|
|
114
|
+
@monitor.synchronize do
|
|
115
|
+
name_str = name.to_s
|
|
116
|
+
unregister_component(name_str)
|
|
117
|
+
register_component(name_str, inject: inject, requires: requires, provides: provides, config: config, &new_factory)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def reconcile_all(changed_service: nil)
|
|
122
|
+
fibers = @monitor.synchronize { @fibers.values.dup }
|
|
123
|
+
sorted_fibers = topological_sort_fibers(fibers)
|
|
124
|
+
sorted_fibers.each do |fiber|
|
|
125
|
+
reconcile_fiber(fiber, changed_service: changed_service)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def topological_sort_fibers(fibers)
|
|
130
|
+
sorted, cycled = FiberOrdering.sort(fibers)
|
|
131
|
+
handle_fiber_cycle(cycled) unless cycled.empty?
|
|
132
|
+
sorted
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def handle_fiber_cycle(cycle_fibers)
|
|
136
|
+
names = cycle_fibers.map(&:name).join(", ")
|
|
137
|
+
msg = "Dependency cycle detected among fibers: #{names}"
|
|
138
|
+
Rubik::Log.warn(:runtime, msg)
|
|
139
|
+
dmesg = begin
|
|
140
|
+
@context.resolve(Rubik::Coeffects::DMESG)
|
|
141
|
+
rescue StandardError
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
dmesg&.log(:runtime, :err, msg) if dmesg.respond_to?(:log)
|
|
145
|
+
|
|
146
|
+
err = StandardError.new(msg)
|
|
147
|
+
@monitor.synchronize do
|
|
148
|
+
cycle_fibers.each { |cf| cf.mark_failed!(err) if cf.respond_to?(:mark_failed!) }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def reconcile_fiber(fiber, changed_service: nil)
|
|
153
|
+
# pending / active branches activate with the monitor held at depth 1
|
|
154
|
+
# (the factory returns while we still hold it, so nested reconciles see
|
|
155
|
+
# the newest value). The :failed branch defers activation until AFTER
|
|
156
|
+
# the monitor is released so the backoff sleep serializes no one.
|
|
157
|
+
deferred = nil
|
|
158
|
+
@monitor.synchronize do
|
|
159
|
+
# Reentrancy guard: skip fibers whose factory is currently running
|
|
160
|
+
# (nested reconcile from a factory-side provide). The in-flight
|
|
161
|
+
# factory already observes the newest value it provided, so the
|
|
162
|
+
# outer activation converges; a skipped re-run would only recurse.
|
|
163
|
+
return if @activating.key?(fiber.name)
|
|
164
|
+
|
|
165
|
+
case fiber.state
|
|
166
|
+
when :pending
|
|
167
|
+
with_activation_marker(fiber) { fiber.activate!(@context) } if fiber.ready_to_activate?(@context)
|
|
168
|
+
when :active
|
|
169
|
+
if !fiber.ready_to_activate?(@context)
|
|
170
|
+
fiber.deactivate!
|
|
171
|
+
elsif changed_service && fiber_depends_on_service?(fiber, changed_service.to_s.downcase)
|
|
172
|
+
with_activation_marker(fiber) { fiber.reactivate!(@context) }
|
|
173
|
+
end
|
|
174
|
+
when :failed
|
|
175
|
+
deferred = fiber if fiber.ready_to_activate?(@context)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Monitor is now fully released (depth 0); the deferred retry's backoff
|
|
180
|
+
# sleep therefore runs with no lock held.
|
|
181
|
+
return unless deferred
|
|
182
|
+
|
|
183
|
+
@retry_gate.attempt(deferred) do |fiber|
|
|
184
|
+
with_activation_marker(fiber) { fiber.activate!(@context) }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def active_fibers
|
|
189
|
+
@monitor.synchronize { @fibers.values.select { |f| f.state == :active } }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def pending_fibers
|
|
193
|
+
@monitor.synchronize { @fibers.values.select { |f| f.state == :pending } }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def failed_fibers
|
|
197
|
+
@monitor.synchronize { @fibers.values.select { |f| f.state == :failed } }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def dispose!
|
|
201
|
+
@monitor.synchronize do
|
|
202
|
+
return if @disposed
|
|
203
|
+
|
|
204
|
+
@disposed = true
|
|
205
|
+
begin
|
|
206
|
+
@fibers.values.reverse_each do |fiber|
|
|
207
|
+
fiber.dispose!
|
|
208
|
+
rescue StandardError => e
|
|
209
|
+
Rubik::Log.warn(:runtime, "fiber '#{fiber.name}' dispose failed: #{e.class}: #{e.message}")
|
|
210
|
+
end
|
|
211
|
+
ensure
|
|
212
|
+
@fibers.clear
|
|
213
|
+
@retry_gate.clear_all # fresh-eye R2: no bookkeeping outlives the manager
|
|
214
|
+
@context.dispose
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
# Marks the fiber while its factory runs so nested reconciles skip it;
|
|
222
|
+
# ensure-based so a raising factory (→ :failed) never wedges the marker.
|
|
223
|
+
def with_activation_marker(fiber)
|
|
224
|
+
@activating[fiber.name] = true
|
|
225
|
+
begin
|
|
226
|
+
yield
|
|
227
|
+
ensure
|
|
228
|
+
@activating.delete(fiber.name)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# A fiber depends on a changed service when it injects the literal key
|
|
233
|
+
# or a legacy alias of it (Rubik::Coeffects SSOT), so alias-injected
|
|
234
|
+
# fibers react to canonical re-provides instead of holding stale
|
|
235
|
+
# service instances.
|
|
236
|
+
def fiber_depends_on_service?(fiber, changed_key)
|
|
237
|
+
fiber.inject.any? { |dep| dep == changed_key || Rubik::Coeffects.canonical(dep) == changed_key }
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|