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,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module REPL
|
|
7
|
+
module Pruner
|
|
8
|
+
# Düzlem 1 çekirdeği (Vakanüvis::Harness yapısı): tek worker thread
|
|
9
|
+
# + sınırlı FIFO. ANLIK GÖRÜNTÜ her zaman ÇAĞIRAN thread'de alınır
|
|
10
|
+
# (:turn_finished abonelik bloğu turn thread'inde koşar; araç/komut
|
|
11
|
+
# çağrısı da turn thread'indedir) — worker shell durumuna ASLA
|
|
12
|
+
# dokunmaz; o yalnız brif kurar, doğurur, hasat eder ve planı
|
|
13
|
+
# aşamalar. Sessizlik sözleşmesi: "Context Pruner" SILENT_ROLES'tadır
|
|
14
|
+
# — notice yok, wake yok, yalnız dmesg. Uygulama (pencere takası)
|
|
15
|
+
# yalnız turn thread'inde, apply_staged_prune_plan! üzerinden.
|
|
16
|
+
class Harness
|
|
17
|
+
STOP = :__pruner_stop__
|
|
18
|
+
MAX_QUEUE = 16
|
|
19
|
+
TURN_EVENT = :turn_finished # TurnRecorder::TURN_EVENT_TARGET ile eş pinli
|
|
20
|
+
COMPLETED_EVENT = :subagent_completed
|
|
21
|
+
TYPE_NAME = "pruner"
|
|
22
|
+
PRUNER_ROLE = "Context Pruner"
|
|
23
|
+
|
|
24
|
+
attr_reader :shell, :supervisor, :dmesg, :gate
|
|
25
|
+
|
|
26
|
+
def initialize(shell:, config: nil, event_bus: nil, supervisor: nil, dmesg: nil, context: nil)
|
|
27
|
+
@shell = shell
|
|
28
|
+
@config = config
|
|
29
|
+
@event_bus = event_bus
|
|
30
|
+
@supervisor = supervisor
|
|
31
|
+
@dmesg = dmesg
|
|
32
|
+
@context = context
|
|
33
|
+
@queue = Thread::Queue.new
|
|
34
|
+
@monitor = Monitor.new
|
|
35
|
+
@busy = 0
|
|
36
|
+
@stopped = false
|
|
37
|
+
@worker = nil
|
|
38
|
+
@gate = Gate.new(shell: shell, config: config, supervisor: supervisor, dmesg: dmesg,
|
|
39
|
+
event_bus: event_bus, ceiling_resolver: method(:resolve_ceiling))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# ---- yaşam döngüsü ---------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def start!
|
|
45
|
+
subscribe_events
|
|
46
|
+
@worker = Thread.new { run_worker }
|
|
47
|
+
@worker.name = "rubik-pruner" if @worker.respond_to?(:name=)
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stopped? = @monitor.synchronize { @stopped }
|
|
52
|
+
|
|
53
|
+
# Dispose sözleşmesi (vakanüvis): FIFO drain olur (STOP dahil önceki
|
|
54
|
+
# olaylar işlenir), thread 2 saniyede durmazsa kill + dürüst dmesg.
|
|
55
|
+
def shutdown
|
|
56
|
+
@monitor.synchronize { @stopped = true }
|
|
57
|
+
@monitor.synchronize { @busy += 1 }
|
|
58
|
+
@queue.push(STOP)
|
|
59
|
+
@worker&.join(2)
|
|
60
|
+
if @worker&.alive?
|
|
61
|
+
Pruner.log_via(@dmesg, :warn, "worker did not stop in 2s — killing with #{queue_size} event(s) unprocessed")
|
|
62
|
+
@worker.kill
|
|
63
|
+
end
|
|
64
|
+
@worker = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def wait_idle(timeout: 2.0)
|
|
68
|
+
deadline = Time.now + timeout
|
|
69
|
+
sleep 0.01 while busy? && Time.now < deadline
|
|
70
|
+
!busy?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def busy? = @monitor.synchronize { @busy.positive? }
|
|
74
|
+
|
|
75
|
+
def queue_size = @queue.size
|
|
76
|
+
|
|
77
|
+
# ---- talep düzlemi (prune_context aracı / !prune komutu) ----------
|
|
78
|
+
|
|
79
|
+
# Zorunlu budama isteği: LLM asla bekletilmez — deterministik makbuz
|
|
80
|
+
# döner. Kuyruk reddi (kapanış yarışı) yer tutucuyu temizler ve dürüst
|
|
81
|
+
# ret makbuzu döner (makbuz asla kuyruğa girmeyen işe söz vermez).
|
|
82
|
+
def prune(reason: "manual")
|
|
83
|
+
envelope, skip = @gate.stage_job(@shell.conversation_history, reason: reason, forced: true)
|
|
84
|
+
return decline_receipt(skip) unless envelope
|
|
85
|
+
|
|
86
|
+
if !stopped? && enqueue_job(envelope)
|
|
87
|
+
"Prune queued (async): the pruner will propose a keep/drop/summarize plan for the ROOT window; " \
|
|
88
|
+
"it is validated deterministically and applied at the next turn boundary. " \
|
|
89
|
+
"This call did NOT wait for the LLM. Reason: #{reason}."
|
|
90
|
+
else
|
|
91
|
+
@gate.drop_job(envelope, why: "harness stopped")
|
|
92
|
+
decline_receipt(:stopped)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ---- turn sınırı uygulama kapısı (TurnRecorder çağırır) -----------
|
|
97
|
+
|
|
98
|
+
# Aşamalı planı canlı pencereye uygular; dönüş [yeni_pencere,
|
|
99
|
+
# makbuz] veya nil (plan yok / canlı doğrulama düşürdü).
|
|
100
|
+
def take_applicable_plan!(live_window)
|
|
101
|
+
@gate.take_staged!(live_window, config: compaction_config)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def status_text
|
|
105
|
+
Rules.status_report(window: @shell.conversation_history, ceiling: resolve_ceiling,
|
|
106
|
+
bits: @gate.status_bits)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def subscribe_events
|
|
112
|
+
return unless @event_bus.respond_to?(:subscribe)
|
|
113
|
+
|
|
114
|
+
# Anlık görüntü abonelik bloğunda alınır. DÜRÜST NOT: bus post'u
|
|
115
|
+
# fire-and-forget'tir — blok rubik-kworker thread'inde koşar, turn
|
|
116
|
+
# thread'i DEĞİLDİR; okuma bir sonraki turn'ün pencere mutasyonuyla
|
|
117
|
+
# yarışabilir. Güvenlik apply katmanından gelir: hash8 yeniden
|
|
118
|
+
# doğrulaması yarışan birimleri "bayat op" olarak atlar (sayılır),
|
|
119
|
+
# uygulama-sonu değişmezleri ve kopyala-doğrula-takas pencereyi
|
|
120
|
+
# bozulmadan korur — yarış en fazla bir planın düşmesine mal olur.
|
|
121
|
+
@event_bus.subscribe(TURN_EVENT, context: @context) do |_payload|
|
|
122
|
+
@gate.note_turn!
|
|
123
|
+
envelope, = @gate.stage_job(@shell.conversation_history, reason: "turn", forced: false)
|
|
124
|
+
@gate.drop_job(envelope, why: "harness stopped") if envelope && !enqueue_job(envelope)
|
|
125
|
+
end
|
|
126
|
+
@event_bus.subscribe(COMPLETED_EVENT, context: @context) { |p| enqueue_event(:completed, p) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def enqueue_job(envelope)
|
|
130
|
+
enqueue_event(:job, { envelope: envelope })
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def enqueue_event(kind, payload)
|
|
134
|
+
return false if stopped?
|
|
135
|
+
|
|
136
|
+
unless payload.is_a?(Hash)
|
|
137
|
+
Pruner.log_via(@dmesg, :warn, "discarded non-hash #{kind} event (#{payload.class})")
|
|
138
|
+
return false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
drop_oldest! if @queue.size >= MAX_QUEUE
|
|
142
|
+
@monitor.synchronize { @busy += 1 }
|
|
143
|
+
@queue.push([kind, payload])
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Taşma düşürmesi asla STOP'u yutamaz (vakanüvis kuralı); düşen
|
|
148
|
+
# işin gate'teki izi uzlaştırılır — :job yer tutucusu temizlenir,
|
|
149
|
+
# :completed sicil girdisi silinir (kapı asla "uçuşta" takılı
|
|
150
|
+
# kalamaz) ve taşan tamamlanmanın id'si loglanır.
|
|
151
|
+
def drop_oldest!
|
|
152
|
+
item = @queue.pop(true)
|
|
153
|
+
if item == STOP
|
|
154
|
+
@queue.push(STOP)
|
|
155
|
+
else
|
|
156
|
+
@monitor.synchronize { @busy -= 1 }
|
|
157
|
+
if item[0] == :job
|
|
158
|
+
@gate.drop_job(item[1][:envelope], why: "queue overflow")
|
|
159
|
+
elsif item[0] == :completed
|
|
160
|
+
@gate.discard_completion(item[1])
|
|
161
|
+
end
|
|
162
|
+
Pruner.log_via(@dmesg, :warn, "event queue overflow — dropped oldest #{item[0]} event")
|
|
163
|
+
end
|
|
164
|
+
rescue ThreadError
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def run_worker
|
|
169
|
+
loop do
|
|
170
|
+
kind, payload = @queue.pop
|
|
171
|
+
break if kind == STOP
|
|
172
|
+
|
|
173
|
+
handle_event(kind, payload)
|
|
174
|
+
ensure
|
|
175
|
+
@monitor.synchronize { @busy -= 1 }
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Pruner'in kendi hatası REPL akışını asla bozmaz — uyarı düşer,
|
|
180
|
+
# sonraki olay işlenir (TranscriptIndexer disiplini).
|
|
181
|
+
def handle_event(kind, payload)
|
|
182
|
+
case kind
|
|
183
|
+
when :job then handle_job(payload[:envelope])
|
|
184
|
+
when :completed then @gate.completion_event(payload)
|
|
185
|
+
end
|
|
186
|
+
rescue StandardError => e
|
|
187
|
+
Pruner.log_via(@dmesg, :warn, "event handling failed (#{kind}): #{e.class}: #{e.message}")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def handle_job(envelope)
|
|
191
|
+
brief = Brief.build(units: envelope[:units], ceiling: envelope[:ceiling],
|
|
192
|
+
target_tokens: envelope[:target_tokens], trigger_ratio: envelope[:trigger_ratio],
|
|
193
|
+
max_chars: envelope[:brief_budget_chars])
|
|
194
|
+
return @gate.drop_job(envelope, why: "brief over the hard bound") unless brief
|
|
195
|
+
|
|
196
|
+
envelope[:brief] = brief
|
|
197
|
+
@gate.spawn_job(envelope)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def decline_receipt(skip)
|
|
201
|
+
case skip
|
|
202
|
+
when :busy then "A pruner run is already in flight — this request was not duplicated."
|
|
203
|
+
when :staged then "A validated prune plan is already staged and applies at the next turn boundary."
|
|
204
|
+
when :no_ceiling then "Prune declined: the effective input ceiling is unknown for the current model."
|
|
205
|
+
when :stopped then "Prune declined: the pruner harness is not running (session teardown)."
|
|
206
|
+
else "Prune declined (#{skip})."
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Koruma bantları context.* SSOT'sundan (preserve_first_n/last_n).
|
|
211
|
+
def compaction_config
|
|
212
|
+
{
|
|
213
|
+
"preserve_first_n" => fetch_cfg("context.preserve_first_n", 2),
|
|
214
|
+
"preserve_last_n" => fetch_cfg("context.preserve_last_n", 6)
|
|
215
|
+
}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# trim_conversation_window! ile aynı tavan çözümü (sticky damgalı
|
|
219
|
+
# model spec'i + llm_registry; hata → nil → kapı atlar).
|
|
220
|
+
def resolve_ceiling
|
|
221
|
+
spec = @shell.current_model_spec
|
|
222
|
+
return nil unless spec.is_a?(Hash) && @shell.respond_to?(:llm_registry) && @shell.llm_registry
|
|
223
|
+
|
|
224
|
+
@shell.llm_registry.effective_input_limit_for(spec["provider"], spec["name"])
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
Pruner.log_via(@dmesg, :warn, "input ceiling lookup failed (#{e.class}: #{e.message}); prune gate skipped")
|
|
227
|
+
nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def fetch_cfg(key, default)
|
|
231
|
+
@config.respond_to?(:fetch) ? @config.fetch(key, default) : default
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "digest"
|
|
5
|
+
require_relative "../../platform"
|
|
6
|
+
require_relative "../../prompt/context_compactor"
|
|
7
|
+
|
|
8
|
+
module Rubik
|
|
9
|
+
module REPL
|
|
10
|
+
module Pruner
|
|
11
|
+
# Düzlem 1 kuralları (durumsuz, saf): pencereyi atomik birimlere
|
|
12
|
+
# böler (koruma bantları + hedef sabitlemesi dahil), LLM planını
|
|
13
|
+
# katı şemayla doğrular ve DOĞRULANMIŞ planı canlı pencereye
|
|
14
|
+
# kopyala-doğrula-takas disipliniyle uygular. Mevcut sıkıştırma
|
|
15
|
+
# koduna dokunmaz — ContextCompactor/TokenEstimator/ConversationRows
|
|
16
|
+
# yalniz ÇAĞRILIR (subagent yolu bu dosyadan habersizdir).
|
|
17
|
+
module Rules
|
|
18
|
+
MAX_OPS = 64
|
|
19
|
+
SUMMARY_MIN_CHARS = 40
|
|
20
|
+
SUMMARY_MAX_CHARS = 2000
|
|
21
|
+
KEEP_PREVIEW_CHARS = 400
|
|
22
|
+
MIN_WINDOW_ENTRIES = 4 # trim_history_to_budget min_entries aynası
|
|
23
|
+
UNIT_ID = /\Au\d+:[0-9a-f]{8}\z/
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Pencereyi budanabilir birim anlık görüntüsüne çevirir. Birim =
|
|
28
|
+
# Prompt::ContextCompactor.group_atomic_units çıktısı (assistant+tool
|
|
29
|
+
# eşleşmesi yapısal olarak korunur). Korumalı birimler (KEEP):
|
|
30
|
+
# hedef sabitlemeli (<user_task> çifti), baş/bant (preserve_first_n
|
|
31
|
+
# mesajını kaplayan birimler), kuyruk bandı (preserve_last_n), system
|
|
32
|
+
# satırı taşıyanlar. id = "u<sıra>:<hash8>" — sıra benzersizlik,
|
|
33
|
+
# hash8 bayatlık işaretidir (apply sırasında içerik kimliği).
|
|
34
|
+
def build_units(window, config: {})
|
|
35
|
+
groups = Prompt::ContextCompactor.group_atomic_units(Array(window))
|
|
36
|
+
units = groups.map do |rows|
|
|
37
|
+
{ rows: rows.map { |r| Prompt::ConversationRows.row?(r) ? r.dup : r } }
|
|
38
|
+
end
|
|
39
|
+
units.each { |u| u[:pinned_goal?] = Prompt::ContextCompactor.unit_pins_goal?(u[:rows]) }
|
|
40
|
+
|
|
41
|
+
protect_bands!(units, config)
|
|
42
|
+
units.each_with_index do |u, idx|
|
|
43
|
+
u[:id] = "u#{idx}:#{unit_digest(u[:rows])}"
|
|
44
|
+
u[:tokens] = Prompt::TokenEstimator.estimate_history(u[:rows])
|
|
45
|
+
u[:role] = role_label(u[:rows])
|
|
46
|
+
end
|
|
47
|
+
units.each(&:freeze)
|
|
48
|
+
units.freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unit_digest(rows)
|
|
52
|
+
canonical = rows.map { |r| canonical_row(r) }
|
|
53
|
+
Digest::SHA256.hexdigest(Platform.safe_json_generate(canonical))[0, 8]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# İki satır diyalektini (symbol/string anahtar) tek kanonik şekle
|
|
57
|
+
# indirir — nil okumalar "" olur, anahtar sırası hash'i değiştirmez.
|
|
58
|
+
def canonical_row(row)
|
|
59
|
+
return { "row" => "" } unless Prompt::ConversationRows.row?(row)
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
"role" => Prompt::ConversationRows.role_of(row),
|
|
63
|
+
"content" => Prompt::ConversationRows.content_of(row).to_s,
|
|
64
|
+
"tool_call_id" => Prompt::ConversationRows.value_of(row, :tool_call_id).to_s,
|
|
65
|
+
"tool_name" => Prompt::ConversationRows.value_of(row, :tool_name).to_s,
|
|
66
|
+
"status" => Prompt::ConversationRows.value_of(row, :status).to_s,
|
|
67
|
+
"tool_calls" => tool_calls_json(row)
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# LLM planını ANLIK GÖRÜNTÜYE karşı doğrular (canlı durum yok).
|
|
72
|
+
# Dönüş: [plan, nil] veya [nil, insan-okur ret sebebi]. Beyaz liste
|
|
73
|
+
# grameri: {"ops":[{"op":"drop","id":...},{"op":"summarize","id":...,
|
|
74
|
+
# "text":...}]} — model satır/rol/talimat ENJEKTE EDEMEZ, yalnız
|
|
75
|
+
# önceden doğrulanmış birimler arasından seçer ve ≤2000 karakterlik
|
|
76
|
+
# özet yazar.
|
|
77
|
+
def validate_plan(raw_finish_body, units)
|
|
78
|
+
return [nil, "final message is empty"] if raw_finish_body.to_s.strip.empty?
|
|
79
|
+
|
|
80
|
+
parsed = parse_json_object(raw_finish_body.to_s)
|
|
81
|
+
return [nil, parsed] if parsed.is_a?(String)
|
|
82
|
+
|
|
83
|
+
ops = parsed["ops"]
|
|
84
|
+
return [nil, "'ops' must be an array"] unless ops.is_a?(Array)
|
|
85
|
+
return [nil, "plan needs at least one op"] if ops.empty?
|
|
86
|
+
return [nil, "plan exceeds #{MAX_OPS} ops"] if ops.size > MAX_OPS
|
|
87
|
+
|
|
88
|
+
by_id = units.to_h { |u| [u[:id], u] }
|
|
89
|
+
seen = {}
|
|
90
|
+
normalized = []
|
|
91
|
+
ops.each do |op|
|
|
92
|
+
reason = normalize_op(op, by_id, seen, normalized)
|
|
93
|
+
return [nil, reason] if reason
|
|
94
|
+
end
|
|
95
|
+
[{ "ops" => normalized }, nil]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Doğrulanmış planı CANLI pencereye uygular (yalnız turn thread'i
|
|
99
|
+
# çağırır). Her op'un hash'i yeniden doğrulanır: içerik değişen
|
|
100
|
+
# birimler tek tek atlanır (bayat op), genel değişmezlerden biri
|
|
101
|
+
# bozulursa TÜM plan düşer — giriş dizisi asla mutasyona uğramaz.
|
|
102
|
+
# Dönüş: [yeni_pencere, makbuz] veya [nil, sebep].
|
|
103
|
+
def apply_plan(window, plan, target_tokens:, config: {})
|
|
104
|
+
return [nil, "no plan"] unless plan.is_a?(Hash) && plan["ops"].is_a?(Array)
|
|
105
|
+
|
|
106
|
+
units = build_units(window, config: config)
|
|
107
|
+
outcome = resolve_ops(plan["ops"], units)
|
|
108
|
+
applied = outcome[:applied]
|
|
109
|
+
return [nil, "all ops went stale (window changed since the plan)"] if applied.empty?
|
|
110
|
+
|
|
111
|
+
candidate = assemble(units, applied)
|
|
112
|
+
reason = post_invariant_reason(window, candidate, target_tokens)
|
|
113
|
+
return [nil, reason] if reason
|
|
114
|
+
|
|
115
|
+
after_tokens = Prompt::TokenEstimator.estimate_history(candidate)
|
|
116
|
+
receipt = { dropped: applied.count { |_id, op| op[:kind] == :drop },
|
|
117
|
+
summarized: applied.count { |_id, op| op[:kind] == :summarize },
|
|
118
|
+
skipped_stale: outcome[:skipped_stale],
|
|
119
|
+
before_tokens: Prompt::TokenEstimator.estimate_history(window),
|
|
120
|
+
after_tokens: after_tokens }
|
|
121
|
+
[candidate, receipt]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def status_report(window:, ceiling:, bits: {})
|
|
125
|
+
est = Prompt::TokenEstimator.estimate_history(Array(window))
|
|
126
|
+
pct = ceiling&.positive? ? " (#{(est * 100.0 / ceiling).round}% of ceiling)" : ""
|
|
127
|
+
lines = ["Context pruner:",
|
|
128
|
+
" window: #{Array(window).size} entries, ~#{est} est. tokens#{pct}",
|
|
129
|
+
" ceiling: #{ceiling || 'unknown'}"]
|
|
130
|
+
if bits.empty?
|
|
131
|
+
lines << " gate: idle"
|
|
132
|
+
else
|
|
133
|
+
lines << " pruner in flight: #{bits[:in_flight] ? 'yes' : 'no'}"
|
|
134
|
+
lines << " staged plan: #{bits[:staged] ? 'waiting for the next turn boundary' : 'none'}"
|
|
135
|
+
lines << " last receipt: #{bits[:last_receipt] || 'none yet'}"
|
|
136
|
+
end
|
|
137
|
+
lines.join("\n")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# ---- iç kısım ---------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
# Hash döner veya ret sebebi String'i — validate_plan'ın begin/rescue
|
|
143
|
+
# return dansını izole eder (Lint/NoReturnInBeginEndBlocks).
|
|
144
|
+
def parse_json_object(raw)
|
|
145
|
+
parsed = JSON.parse(raw)
|
|
146
|
+
return "plan must be a JSON object" unless parsed.is_a?(Hash)
|
|
147
|
+
return "plan must have exactly one 'ops' key" unless parsed.keys == ["ops"]
|
|
148
|
+
|
|
149
|
+
parsed
|
|
150
|
+
rescue JSON::ParserError
|
|
151
|
+
"final message is not valid JSON"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Baş/kuyruk koruma bantları apply_sliding_window ile AYNI hesapla
|
|
155
|
+
# (context_compactor.rb:180-199): baş birimleri preserve_first_n
|
|
156
|
+
# mesajı, kuyruk birimleri preserve_last_n mesajı kaplayana kadar;
|
|
157
|
+
# system satırı ve sabitlenmiş hedef birimi her yerde korumalı.
|
|
158
|
+
def protect_bands!(units, config)
|
|
159
|
+
first_n = (config["preserve_first_n"] || config[:preserve_first_n] || 2).to_i
|
|
160
|
+
last_n = (config["preserve_last_n"] || config[:preserve_last_n] || 6).to_i
|
|
161
|
+
|
|
162
|
+
protected_idx = {}
|
|
163
|
+
head_count = 0
|
|
164
|
+
head_idx = 0
|
|
165
|
+
while head_idx < units.size && head_count < first_n
|
|
166
|
+
protected_idx[head_idx] = :head
|
|
167
|
+
head_count += units[head_idx][:rows].size
|
|
168
|
+
head_idx += 1
|
|
169
|
+
end
|
|
170
|
+
tail_count = 0
|
|
171
|
+
tail_idx = units.size - 1
|
|
172
|
+
while tail_idx >= head_idx && tail_count < last_n
|
|
173
|
+
protected_idx[tail_idx] = :tail
|
|
174
|
+
tail_count += units[tail_idx][:rows].size
|
|
175
|
+
tail_idx -= 1
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
units.each_with_index do |u, idx|
|
|
179
|
+
u[:protected?] = protected_idx.key?(idx) || u[:pinned_goal?] ||
|
|
180
|
+
u[:rows].any? { |r| Prompt::ConversationRows.role_of(r) == "system" }
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def role_label(rows)
|
|
185
|
+
first = Prompt::ConversationRows.role_of(rows.first)
|
|
186
|
+
rows.size > 1 ? "#{first}+tool" : first
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def tool_calls_json(row)
|
|
190
|
+
calls = row[:tool_calls] || row["tool_calls"]
|
|
191
|
+
return "" unless calls && !calls.empty?
|
|
192
|
+
|
|
193
|
+
Platform.safe_json_generate(calls)
|
|
194
|
+
rescue StandardError
|
|
195
|
+
calls.to_s
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Tek op normalize eder; ret sebebi döndürür (nil = kabul).
|
|
199
|
+
def normalize_op(entry, by_id, seen, normalized)
|
|
200
|
+
return "each op must be a JSON object" unless entry.is_a?(Hash)
|
|
201
|
+
|
|
202
|
+
kind = entry["op"]
|
|
203
|
+
return "unknown op '#{kind}' (valid: drop, summarize)" unless ["drop", "summarize"].include?(kind)
|
|
204
|
+
|
|
205
|
+
expected = kind == "drop" ? ["id", "op"].freeze : ["id", "op", "text"].freeze
|
|
206
|
+
return "op keys must be exactly #{expected.join(', ')}" unless entry.keys.sort == expected
|
|
207
|
+
|
|
208
|
+
id = entry["id"]
|
|
209
|
+
return "malformed unit id '#{id}'" unless id.is_a?(String) && id.match?(UNIT_ID)
|
|
210
|
+
return "duplicate op for unit '#{id}'" if seen.key?(id)
|
|
211
|
+
|
|
212
|
+
unit = by_id[id]
|
|
213
|
+
return "unknown unit '#{id}'" unless unit
|
|
214
|
+
return "unit '#{id}' is protected (KEEP)" if unit[:protected?]
|
|
215
|
+
|
|
216
|
+
seen[id] = true
|
|
217
|
+
clean_op = { "op" => kind, "id" => id }
|
|
218
|
+
if kind == "summarize"
|
|
219
|
+
text = entry["text"]
|
|
220
|
+
return "summarize text must be a string" unless text.is_a?(String)
|
|
221
|
+
|
|
222
|
+
clean = Platform.safe_utf8(text)
|
|
223
|
+
return "summary must be #{SUMMARY_MIN_CHARS}-#{SUMMARY_MAX_CHARS} chars (got #{clean.length})" unless clean.length.between?(SUMMARY_MIN_CHARS, SUMMARY_MAX_CHARS)
|
|
224
|
+
|
|
225
|
+
# Enjeksiyon kapanışı (review 2026-10-09): özet metni <user_task>
|
|
226
|
+
# etiketi taşayla YENİ sabitlenmiş hedef satırı doğurabilir —
|
|
227
|
+
# deterministik yığın o satırı sonsuza dek korurdu. Etiketler
|
|
228
|
+
# özet içinde asla birebir yaşayamaz.
|
|
229
|
+
# <goal_ledger> kaçışı (goal-ledger planı A): sahte pinnenmiş Σ
|
|
230
|
+
# satırı doğması aynı enjeksiyon sınıfıdır — etiketler özet
|
|
231
|
+
# içinde asla birebir yaşayamaz.
|
|
232
|
+
clean = clean.gsub("<user_task>", "<user_task>").gsub("</user_task>", "</user_task>")
|
|
233
|
+
clean = clean.gsub("<goal_ledger ", "<goal_ledger ").gsub("</goal_ledger>", "</goal_ledger>")
|
|
234
|
+
clean_op["text"] = clean
|
|
235
|
+
end
|
|
236
|
+
normalized << clean_op
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Op'ları canlı birimlere bağlar. Önce tam id eşleşmesi; olmazsa
|
|
241
|
+
# hash8 eşleşmesi (sıra kaydırdıysa içerik hâlâ aynı olabilir).
|
|
242
|
+
# Birden çok hash eşleşmesi belirsizdir → op bayat sayılır.
|
|
243
|
+
def resolve_ops(ops, units)
|
|
244
|
+
applied = {}
|
|
245
|
+
skipped = 0
|
|
246
|
+
units_by_id = units.to_h { |u| [u[:id], u] }
|
|
247
|
+
ops.each do |op|
|
|
248
|
+
hash8 = op["id"].split(":").last
|
|
249
|
+
unit = units_by_id[op["id"]]
|
|
250
|
+
unless unit && !unit[:protected?] && !applied.key?(unit[:id])
|
|
251
|
+
matches = units.select do |u|
|
|
252
|
+
!u[:protected?] && !applied.key?(u[:id]) && u[:id].end_with?(":#{hash8}")
|
|
253
|
+
end
|
|
254
|
+
unit = matches.size == 1 ? matches.first : nil
|
|
255
|
+
end
|
|
256
|
+
if unit.nil?
|
|
257
|
+
skipped += 1
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
applied[unit[:id]] = { kind: op["op"].to_sym, text: op["text"] }
|
|
262
|
+
end
|
|
263
|
+
{ applied: applied, skipped_stale: skipped }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Birimleri özgün sırasında birleştirir: düşürülenler çıkar,
|
|
267
|
+
# özetlenenler tek <pruned_summary> user satırına iner (role "user"
|
|
268
|
+
# — sıkı sağlayıcılar ortada system satırı reddeder; compactor
|
|
269
|
+
# summary_message geleneği).
|
|
270
|
+
def assemble(units, applied)
|
|
271
|
+
candidate = []
|
|
272
|
+
units.each do |u|
|
|
273
|
+
op = applied[u[:id]]
|
|
274
|
+
if op.nil?
|
|
275
|
+
candidate.concat(u[:rows])
|
|
276
|
+
elsif op[:kind] == :drop
|
|
277
|
+
next
|
|
278
|
+
else
|
|
279
|
+
candidate << {
|
|
280
|
+
role: "user",
|
|
281
|
+
content: "<pruned_summary unit=\"#{u[:id]}\" rows=\"#{u[:rows].size}\">#{op[:text]}</pruned_summary>"
|
|
282
|
+
}
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
candidate
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Uygulama-sonu değişmezleri — biri bozulursa plan bütünüyle düşer.
|
|
289
|
+
# nil = ihlal yok; String = insan-okur ihlal sebebi.
|
|
290
|
+
def post_invariant_reason(original, candidate, target_tokens)
|
|
291
|
+
return "pruned window would be empty" if candidate.empty?
|
|
292
|
+
return "pruned window must open with a user turn (strict providers)" unless Prompt::ConversationRows.role_of(candidate.first) == "user"
|
|
293
|
+
return "pruned window would be too small (< #{MIN_WINDOW_ENTRIES} entries)" if candidate.size < MIN_WINDOW_ENTRIES
|
|
294
|
+
if Prompt::TokenEstimator.estimate_history(candidate) > target_tokens
|
|
295
|
+
return "pruned window still over target (#{Prompt::TokenEstimator.estimate_history(candidate)} > #{target_tokens} tokens)"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Hedef satır sayısı BİREBİR kalmalı (review 2026-10-09): yalnız
|
|
299
|
+
# eksilmesi değil ARTMASI da ret — özet metninden enjekte edilen
|
|
300
|
+
# sahte <user_task> satırı sonsuza dek sabitlenmiş hedef olurdu.
|
|
301
|
+
original_goals = original.count { |r| Prompt::ContextCompactor.goal_message?(r) }
|
|
302
|
+
candidate_goals = candidate.count { |r| Prompt::ContextCompactor.goal_message?(r) }
|
|
303
|
+
return "goal-pinned message count changed (#{original_goals} -> #{candidate_goals})" if candidate_goals != original_goals
|
|
304
|
+
|
|
305
|
+
# Σ satırı sayısı değişmezi (goal-ledger planı A): hedef satırıyla
|
|
306
|
+
# aynı savunma — eksilme de artma da ret.
|
|
307
|
+
original_ledger = original.count { |r| Prompt::ContextCompactor.ledger_message?(r) }
|
|
308
|
+
candidate_ledger = candidate.count { |r| Prompt::ContextCompactor.ledger_message?(r) }
|
|
309
|
+
return "goal-ledger message count changed (#{original_ledger} -> #{candidate_ledger})" if candidate_ledger != original_ledger
|
|
310
|
+
|
|
311
|
+
nil
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
module Pruner
|
|
6
|
+
# Kompozisyon kökü kancası (repl/lifecycle.rb Phase 4, Vakanüvis
|
|
7
|
+
# bind'inden hemen sonra — SHELL/SUPERVISOR/DMESG coeffect'leri Faz
|
|
8
|
+
# 2'den çözülebilir olmalı): budayıcı açıksa Harness kurulur, kendi
|
|
9
|
+
# child ctx'inde yaşar (abonelik geri alımları orada), PRUNER
|
|
10
|
+
# coeffect'i köke sağlanır (prune_context aracı buradan çözer;
|
|
11
|
+
# apply_staged_prune_plan! da). Kapalıysa
|
|
12
|
+
# (subagents.pruner.enabled=false — opt-out) nil döner: budayıcı
|
|
13
|
+
# yok, araç REQUIRES karşılanmaz ve model yüzünde görünmez, REPL
|
|
14
|
+
# komutu dürüstçe kapalı der.
|
|
15
|
+
module Service
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Test zırhı (vakanüvis emsali): RUBIK_TEST=1 ortamında budayıcı
|
|
19
|
+
# varsayılan KAPALI bağlanır — scripted shell testleri arka plan
|
|
20
|
+
# doğumlarıyla yarışmasın. RUBIK_PRUNER_FORCE=1 zırhı kaldırır
|
|
21
|
+
# (budayıcının kendi testleri). Knob varsayılanı ON'dur (2026-10-10);
|
|
22
|
+
# zırh yalnızca süit içi davranışı sabitler.
|
|
23
|
+
def enabled?(config = nil)
|
|
24
|
+
return false if ENV["RUBIK_TEST"] == "1" && ENV["RUBIK_PRUNER_FORCE"] != "1"
|
|
25
|
+
|
|
26
|
+
config_get(config, "subagents.pruner.enabled", true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bind(context_or_manager, config: nil, event_bus: nil)
|
|
30
|
+
return nil unless enabled?(config)
|
|
31
|
+
|
|
32
|
+
ctx = context_or_manager.respond_to?(:context) ? context_or_manager.context : context_or_manager
|
|
33
|
+
return nil unless ctx.respond_to?(:provide)
|
|
34
|
+
|
|
35
|
+
shell = Rubik::Coeffects.get(ctx, Rubik::Coeffects::SHELL)
|
|
36
|
+
unless shell
|
|
37
|
+
Rubik::Log.warn(:repl, "pruner not bound: SHELL coeffect unsatisfied (boot-order gap)")
|
|
38
|
+
return nil
|
|
39
|
+
end
|
|
40
|
+
supervisor = Rubik::Coeffects.get(ctx, Rubik::Coeffects::SUBAGENT_SUPERVISOR)
|
|
41
|
+
unless supervisor
|
|
42
|
+
Rubik::Log.warn(:repl, "pruner not bound: SUBAGENT_SUPERVISOR coeffect unsatisfied — no spawn path")
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
Rubik::Log.warn(:repl, "pruner bound without an event bus — the LLM half stays dormant (no harvest path)") unless event_bus.respond_to?(:subscribe)
|
|
46
|
+
|
|
47
|
+
child = ctx.respond_to?(:extend) ? ctx.extend : nil
|
|
48
|
+
unless child
|
|
49
|
+
Rubik::Log.warn(:repl, "pruner bound without a disposable child ctx — worker and " \
|
|
50
|
+
"subscriptions live for the process lifetime")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
harness = Harness.new(
|
|
54
|
+
shell: shell, config: config, event_bus: event_bus,
|
|
55
|
+
supervisor: supervisor, dmesg: Rubik::Coeffects.get(ctx, Rubik::Coeffects::DMESG),
|
|
56
|
+
context: child
|
|
57
|
+
)
|
|
58
|
+
# Ömür (§2.7): child ctx'in cascade inverse'i worker'ı kapatır.
|
|
59
|
+
child&.on_dispose { harness.shutdown }
|
|
60
|
+
harness.start!
|
|
61
|
+
ctx.provide(Rubik::Coeffects::PRUNER, harness)
|
|
62
|
+
context_or_manager.reconcile_all if context_or_manager.respond_to?(:reconcile_all)
|
|
63
|
+
harness
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Noktalı anahtar okuması iki config şeklinde de aynı çalışsın
|
|
67
|
+
# (maestro_gate.rb:38-40 düzeni): Rubik::Config#fetch noktalı yolu
|
|
68
|
+
# böler, düz Hash hepsini tek anahtar sayar → dig'e düş.
|
|
69
|
+
def config_get(config, key, default)
|
|
70
|
+
return default if config.nil?
|
|
71
|
+
|
|
72
|
+
val = if config.is_a?(Hash)
|
|
73
|
+
config.dig(*key.to_s.split("."))
|
|
74
|
+
elsif config.respond_to?(:fetch)
|
|
75
|
+
config.fetch(key, nil)
|
|
76
|
+
end
|
|
77
|
+
val.nil? ? default : val
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# Pruner — yarı deterministik KÖK konuşma penceresi budayıcısı
|
|
6
|
+
# (context-pruner planı 2026-10-09, vakanüvis iki-düzlem modeli):
|
|
7
|
+
#
|
|
8
|
+
# Düzlem 1 (deterministik): tetik kapısı, brif kurulumu, plan
|
|
9
|
+
# doğrulaması ve turn sınırındaki aşamalı uygulama (Rules/Brief/Gate).
|
|
10
|
+
# Düzlem 2 (LLM): sessiz "Context Pruner" subagent'ı keep/drop/
|
|
11
|
+
# summarize planını ÖNERİR — yazma yetkisi yoktur; öneri katı
|
|
12
|
+
# doğrulamadan geçmezse pencere dokunulmadan kalır.
|
|
13
|
+
#
|
|
14
|
+
# Deterministik sıkıştırma yığını (trim_conversation_window! /
|
|
15
|
+
# ContextCompactor / EmergencyCompactor) DEĞİŞMEZ ve her zaman güvenlik
|
|
16
|
+
# ağıdır; subagent kanallarının budama mekanizmaları bu özellikten
|
|
17
|
+
# tamamen bağımsız, tam deterministik kalır. DEFAULT ON
|
|
18
|
+
# (subagents.pruner.enabled; opt-out: false — 2026-10-10).
|
|
19
|
+
module Pruner
|
|
20
|
+
SUBSYSTEM = :repl
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Vakanüvis.log_via emsali: dmesg yoksa Log'a, ikisi de patlarsa
|
|
25
|
+
# stderr'e — günlükleme asla akışı bozmaz.
|
|
26
|
+
def log_via(dmesg, level, message)
|
|
27
|
+
dmesg.respond_to?(:log) ? dmesg.log(SUBSYSTEM, level, message) : Rubik::Log.send(level, SUBSYSTEM, message)
|
|
28
|
+
rescue StandardError
|
|
29
|
+
Rubik::Log.warn(SUBSYSTEM, message)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
require_relative "pruner/rules"
|
|
36
|
+
require_relative "pruner/brief"
|
|
37
|
+
require_relative "pruner/gate"
|
|
38
|
+
require_relative "pruner/harness"
|
|
39
|
+
require_relative "pruner/service"
|