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,514 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "monitor"
|
|
6
|
+
require "time"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
require_relative "../platform"
|
|
9
|
+
require_relative "../uris"
|
|
10
|
+
require_relative "layout"
|
|
11
|
+
require_relative "folding"
|
|
12
|
+
|
|
13
|
+
module Rubik
|
|
14
|
+
module Storage
|
|
15
|
+
# Merkezi sanal FS-DB deposu: rubik://storage/<project>/<type>/<id>
|
|
16
|
+
# adreslenebilir varlıklar (goal/plan/memory/wiki/transcript/review/note).
|
|
17
|
+
#
|
|
18
|
+
# id => "plan.3.9f86d081884c7d21" — tip + satır (row) + OLUŞTURMA
|
|
19
|
+
# anındaki içerik karmasının ilk 16 hexasi. Güncelleme id'yi
|
|
20
|
+
# korur (refs/linkler kopmaz), "hash" alanı güncel içeriği
|
|
21
|
+
# izler; row asla yeniden kullanılmaz.
|
|
22
|
+
# fiziki => <root>/<project>/store/{meta.json,<type>.jsonl}
|
|
23
|
+
# indeks => <root>/index.json (merkezi; proje sayıları + zaman damgaları)
|
|
24
|
+
#
|
|
25
|
+
# Tek kullanıcı CLI ölçeği: süreç içi Monitor ile thread-güvenli; çapraz
|
|
26
|
+
# süreç yazımlarında meta/index last-writer-wins (belgelenmiş sınırlama).
|
|
27
|
+
# Yazmalar Platform.atomic_write ile tmp+rename; kurulum tembeldir —
|
|
28
|
+
# ilk mutasyona kadar hiçbir dizin oluşturulmaz (standalone/test
|
|
29
|
+
# kurulumu dosya sistemine dokunmaz).
|
|
30
|
+
class Store
|
|
31
|
+
TYPES = ["goal", "plan", "memory", "wiki", "transcript", "review", "note"].freeze
|
|
32
|
+
ID_PATTERN = /\A([a-z]+)\.(\d+)\.([0-9a-f]{16})\z/
|
|
33
|
+
HASH_PREFIX_MIN = 8
|
|
34
|
+
REF_PATTERN = /\A[a-zA-Z0-9._-]+\z/
|
|
35
|
+
# BM25-lite (vakanüvis-v2 §4-V8): klasik Okapi değerleri — k1 terim
|
|
36
|
+
# doygunluğu, b uzunluk normalizasyonu ağırlığı.
|
|
37
|
+
BM25_K1 = 1.2
|
|
38
|
+
BM25_B = 0.75
|
|
39
|
+
# Alan kademeli bonus (issue #99 P3): BM25 birincil kalır; başlıkta
|
|
40
|
+
# geçen sorgu terimi etikette geçene, o da yalnız gövdede geçene
|
|
41
|
+
# üstünlük kurar. Terim başına birimdir ve tipik idf katkısının
|
|
42
|
+
# altındadır — maddesel BM25 farklarını ezmez, sadece eşit-yakın
|
|
43
|
+
# skorları alan bilgisiyle kırar.
|
|
44
|
+
TITLE_TIER_BONUS = 1.0
|
|
45
|
+
TAGS_TIER_BONUS = 0.5
|
|
46
|
+
|
|
47
|
+
Summary = Struct.new(:id, :row, :title, :status, :updated_at, :tags, keyword_init: true)
|
|
48
|
+
|
|
49
|
+
attr_reader :root, :project
|
|
50
|
+
|
|
51
|
+
def initialize(project:, root: Layout.storage_root)
|
|
52
|
+
@root = File.expand_path(root.to_s)
|
|
53
|
+
@project = Layout.slugify(project)
|
|
54
|
+
@monitor = Monitor.new
|
|
55
|
+
# Parse cache (vakanuvis-v2 §4-V8): tip => {mtime:, records:}. mtime
|
|
56
|
+
# doğrulaması hem yazı sonrası tazeliği hem dış müdahaleyi kapatır.
|
|
57
|
+
# Dönen dizi PAYLAŞILIR ve FROZEN (arch-deepening 2026-10-08 #10):
|
|
58
|
+
# dondurma cache dolumunda yapılır — "arayan mutasyon yapmaz" sözü
|
|
59
|
+
# artık yorum değil, FrozenError (put/list zaten kopya üzerinde
|
|
60
|
+
# çalışır).
|
|
61
|
+
@records_cache = {}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def project_dir
|
|
65
|
+
Layout.project_dir(@root, @project)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def uri_for(type, id)
|
|
69
|
+
Rubik::Uris.storage_entity(project: @project, type: type, id: id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Oluşturur veya günceller. id verilirse o kayıt (satır adresli)
|
|
73
|
+
# güncellenir — version artar, hash yeniden hesaplanır, id sabit kalır.
|
|
74
|
+
# ref verilirse alias o kaydın id'sine bağlanır.
|
|
75
|
+
def put(type:, title:, body: "", tags: [], links: {}, status: "active", id: nil, ref: nil)
|
|
76
|
+
type = type.to_s.downcase
|
|
77
|
+
raise ArgumentError, "unknown entity type '#{type}' (valid: #{TYPES.join(', ')})" unless TYPES.include?(type)
|
|
78
|
+
raise ArgumentError, "title is required" if title.to_s.strip.empty?
|
|
79
|
+
|
|
80
|
+
now = Time.now.utc.iso8601
|
|
81
|
+
@monitor.synchronize do
|
|
82
|
+
meta = load_meta
|
|
83
|
+
records = load_records(type)
|
|
84
|
+
existing = id && resolve_record(records, id.to_s, meta)
|
|
85
|
+
raise ArgumentError, "entity '#{id}' not found in #{type}" if id && !existing
|
|
86
|
+
|
|
87
|
+
record =
|
|
88
|
+
if existing
|
|
89
|
+
existing.merge(
|
|
90
|
+
"title" => title.to_s, "body" => body.to_s, "tags" => normalize_tags(tags),
|
|
91
|
+
"links" => normalize_links(links), "status" => status.to_s,
|
|
92
|
+
"hash" => content_hash(type, title, body, tags, links),
|
|
93
|
+
"version" => existing["version"].to_i + 1, "updated_at" => now
|
|
94
|
+
)
|
|
95
|
+
else
|
|
96
|
+
row = meta_next_row(meta, type)
|
|
97
|
+
hash = content_hash(type, title, body, tags, links)
|
|
98
|
+
{ "id" => "#{type}.#{row}.#{hash[0, 16]}", "type" => type, "project" => @project,
|
|
99
|
+
"row" => row, "hash" => hash, "title" => title.to_s, "body" => body.to_s,
|
|
100
|
+
"tags" => normalize_tags(tags), "links" => normalize_links(links),
|
|
101
|
+
"status" => status.to_s, "version" => 1, "created_at" => now, "updated_at" => now }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
records = records.reject { |r| r["row"] == record["row"] } << record
|
|
105
|
+
ensure_project_dirs
|
|
106
|
+
write_records(type, records.sort_by { |r| r["row"].to_i })
|
|
107
|
+
meta_bump_counts(meta, type) unless existing
|
|
108
|
+
meta_set_ref(meta, ref, record["id"]) if ref
|
|
109
|
+
save_meta(meta)
|
|
110
|
+
touch_index(meta)
|
|
111
|
+
record.dup
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Çözüm sırası: tam id ("plan.3.<hash16>") → "plan.3" (tip.satır) →
|
|
116
|
+
# salt satır ("3") → karma öneki (>= 8 hex) → alias (refs). Bulunamazsa
|
|
117
|
+
# nil — dürüst not-found arayan katmanda.
|
|
118
|
+
def get(type:, id:)
|
|
119
|
+
type = type.to_s.downcase
|
|
120
|
+
return nil unless TYPES.include?(type)
|
|
121
|
+
|
|
122
|
+
@monitor.synchronize do
|
|
123
|
+
resolve_record(load_records(type), id.to_s, load_meta)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def list(type: nil)
|
|
128
|
+
@monitor.synchronize do
|
|
129
|
+
resolve_types(type).flat_map { |t| load_records(t) }
|
|
130
|
+
.sort_by { |r| [r["type"].to_s, r["row"].to_i] }
|
|
131
|
+
.map { |r| summary_of(r) }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def search(query:, type: nil)
|
|
136
|
+
# Token-AND: her boşlukla ayrılmış terim, kaydın title/body/tags
|
|
137
|
+
# alanlarından en az birinde geçmeli. Tüm sorgu tek substring olarak
|
|
138
|
+
# arandığında "ratelimit-lock regression 11/11" gibi doğal çok
|
|
139
|
+
# kelimeli sorgular kelimesi kelimesine eşleşmediği için 0 sonuç
|
|
140
|
+
# dönüyordu (js-tangram olay raporu 2026-10-04, olay #2 — "indeks
|
|
141
|
+
# bayatlığı" değil, eşleştirme semantiğiydi). Tek terimli sorgular
|
|
142
|
+
# eski davranışın birebir aynısı.
|
|
143
|
+
tokens = Folding.fold(query).split
|
|
144
|
+
return [] if tokens.empty?
|
|
145
|
+
|
|
146
|
+
@monitor.synchronize do
|
|
147
|
+
matched = resolve_types(type).flat_map do |t|
|
|
148
|
+
records = load_records(t)
|
|
149
|
+
haystacks = haystacks_for(t, records)
|
|
150
|
+
records.each_index.select { |i| tokens.all? { |token| haystacks[i].include?(token) } }
|
|
151
|
+
.map { |i| records[i] }
|
|
152
|
+
end
|
|
153
|
+
matched.sort_by { |r| [r["type"].to_s, r["row"].to_i] }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# BM25-lite ilgi sıralaması (vakanuvis-v2 §4-V8, Aşama 1): aday üretim
|
|
158
|
+
# search ile AYNI token-AND eşiğidir; eşleşenler Okapi skoruna göre
|
|
159
|
+
# sıralanır. Ek maliyet disiplini: df TEK include? geçişinden (AND
|
|
160
|
+
# filtresinin kendisinden) gelir, uzunluk normalizasyonu bytesize (O(1))
|
|
161
|
+
# üzerinden, tf yalnız eşleşen kayıtlarda scan edilir. Eşitlik bozma
|
|
162
|
+
# updated_at desc → [type,row] ⇒ aynı depo ⇒ aynı sıra (replay/pin).
|
|
163
|
+
# Alan kademeli bonus (issue #99 P3): skora terim başına +1.0 başlık
|
|
164
|
+
# / +0.5 etiket izi eklenir — BM25 birincil kalmaya devam eder, bonus
|
|
165
|
+
# yalnız alan-tierini kırar (belgelenmiş sözleşme değişikliği).
|
|
166
|
+
# `search`'ün [type,row] sözleşmesi bilinçli olarak değişmez —
|
|
167
|
+
# sıralama politikası yalnız recall sunum katmanının talebidir.
|
|
168
|
+
# require_all (issue #99 P2): false iken aday eşiği OR'a gevşer
|
|
169
|
+
# (herhangi bir terimi taşıyan kayıt) — fallback recall içindir;
|
|
170
|
+
# skorlama iki ducte de aynıdır, default davranış birebir korunur.
|
|
171
|
+
def search_ranked(query:, type: nil, require_all: true)
|
|
172
|
+
tokens = Folding.fold(query).split
|
|
173
|
+
return [] if tokens.empty?
|
|
174
|
+
|
|
175
|
+
@monitor.synchronize do
|
|
176
|
+
corpus = resolve_types(type).filter_map do |t|
|
|
177
|
+
records = load_records(t)
|
|
178
|
+
next if records.empty?
|
|
179
|
+
|
|
180
|
+
haystacks = haystacks_for(t, records)
|
|
181
|
+
{ records: records, haystacks: haystacks,
|
|
182
|
+
has: tokens.map { |token| haystacks.map { |h| h.include?(token) } } }
|
|
183
|
+
end
|
|
184
|
+
return [] if corpus.empty?
|
|
185
|
+
|
|
186
|
+
total = corpus.sum { |c| c[:records].size }
|
|
187
|
+
avgdl = corpus.sum { |c| c[:haystacks].sum(&:bytesize) }.fdiv(total)
|
|
188
|
+
dfs = tokens.each_index.map { |ti| corpus.sum { |c| c[:has][ti].count(true) } }
|
|
189
|
+
|
|
190
|
+
gate = ranked_gate(require_all)
|
|
191
|
+
scored = corpus.flat_map do |c|
|
|
192
|
+
c[:records].each_index.select { |i| gate.call(c[:has], i) }.map do |i|
|
|
193
|
+
score = tokens.each_with_index.sum do |token, ti|
|
|
194
|
+
df = dfs[ti]
|
|
195
|
+
next 0.0 if df.zero?
|
|
196
|
+
|
|
197
|
+
tf = c[:haystacks][i].scan(token).size
|
|
198
|
+
next 0.0 if tf.zero?
|
|
199
|
+
|
|
200
|
+
idf = Math.log(((total - df + 0.5) / (df + 0.5)) + 1)
|
|
201
|
+
dl = c[:haystacks][i].bytesize
|
|
202
|
+
idf * (tf * (BM25_K1 + 1)) / (tf + (BM25_K1 * (1 - BM25_B + (BM25_B * dl / avgdl))))
|
|
203
|
+
end
|
|
204
|
+
[score + field_tier_bonus(tokens, c[:records][i]), c[:records][i]]
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
ordered = scored.sort do |(score_a, rec_a), (score_b, rec_b)|
|
|
208
|
+
(score_b <=> score_a).nonzero? ||
|
|
209
|
+
(rec_b["updated_at"].to_s <=> rec_a["updated_at"].to_s).nonzero? ||
|
|
210
|
+
(rec_a["type"].to_s <=> rec_b["type"].to_s).nonzero? ||
|
|
211
|
+
(rec_a["row"].to_i <=> rec_b["row"].to_i)
|
|
212
|
+
end
|
|
213
|
+
ordered.map { |_score, record| record }
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Aday eşiği: require_all (AND — default, recall) ya da OR
|
|
218
|
+
# gevşetmesi (fallback — issue #99 P2).
|
|
219
|
+
def ranked_gate(require_all)
|
|
220
|
+
if require_all
|
|
221
|
+
->(has, i) { has.all? { |row| row[i] } }
|
|
222
|
+
else
|
|
223
|
+
->(has, i) { has.any? { |row| row[i] } }
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Terim başına alan izi: katlanmış başlıkta geçen terim +1.0,
|
|
228
|
+
# yalnız katlanmış etiketlerde geçen +0.5, gövde izi 0 — bkz.
|
|
229
|
+
# TITLE_TIER_BONUS/TAGS_TIER_BONUS. Yalnız AND eşiğini geçen
|
|
230
|
+
# kayıtlar için hesaplanır (ek tarama yok).
|
|
231
|
+
def field_tier_bonus(tokens, record)
|
|
232
|
+
title = Folding.fold(record["title"])
|
|
233
|
+
tags = Folding.fold(Array(record["tags"]).join("\n"))
|
|
234
|
+
tokens.sum do |token|
|
|
235
|
+
if title.include?(token)
|
|
236
|
+
TITLE_TIER_BONUS
|
|
237
|
+
elsif tags.include?(token)
|
|
238
|
+
TAGS_TIER_BONUS
|
|
239
|
+
else
|
|
240
|
+
0.0
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def set_ref(name, id)
|
|
246
|
+
name = name.to_s
|
|
247
|
+
raise ArgumentError, "invalid ref name '#{name}'" unless name.match?(REF_PATTERN)
|
|
248
|
+
|
|
249
|
+
@monitor.synchronize do
|
|
250
|
+
meta = load_meta
|
|
251
|
+
target = TYPES.lazy.map { |t| resolve_record(load_records(t), id.to_s, meta) }
|
|
252
|
+
.find(&:itself)
|
|
253
|
+
raise ArgumentError, "entity '#{id}' not found in any type" unless target
|
|
254
|
+
|
|
255
|
+
meta_set_ref(meta, name, target["id"])
|
|
256
|
+
ensure_project_dirs
|
|
257
|
+
save_meta(meta)
|
|
258
|
+
{ "ref" => name, "id" => target["id"], "uri" => uri_for(target["type"], target["id"]) }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def refs
|
|
263
|
+
@monitor.synchronize do
|
|
264
|
+
load_meta.fetch("refs", {}).dup
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def delete(type:, id:)
|
|
269
|
+
type = type.to_s.downcase
|
|
270
|
+
return nil unless TYPES.include?(type)
|
|
271
|
+
|
|
272
|
+
@monitor.synchronize do
|
|
273
|
+
meta = load_meta
|
|
274
|
+
records = load_records(type)
|
|
275
|
+
target = resolve_record(records, id.to_s, meta)
|
|
276
|
+
return nil unless target
|
|
277
|
+
|
|
278
|
+
write_records(type, records.reject { |r| r["row"] == target["row"] })
|
|
279
|
+
meta["counts"] = (meta["counts"] || {}).then { |c| c.merge(type => [c[type].to_i - 1, 0].max) }
|
|
280
|
+
meta["refs"] = meta.fetch("refs", {}).reject { |_k, v| v == target["id"] }
|
|
281
|
+
save_meta(meta)
|
|
282
|
+
touch_index(meta)
|
|
283
|
+
target
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Proje özeti: tip başına sayı + son yazım zamanı.
|
|
288
|
+
def project_summary
|
|
289
|
+
@monitor.synchronize do
|
|
290
|
+
meta = load_meta
|
|
291
|
+
{ "project" => @project, "path" => project_dir,
|
|
292
|
+
"entity_counts" => meta.fetch("counts", {}).dup,
|
|
293
|
+
"created_at" => meta["created_at"], "updated_at" => meta["updated_at"],
|
|
294
|
+
"refs" => meta.fetch("refs", {}).dup }
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Merkezi indeks anlık görüntüsü (sınıf metodu — kök altındaki tüm
|
|
299
|
+
# projeler). Disk okunur; eksikse boş iskelet.
|
|
300
|
+
def self.index_snapshot(root)
|
|
301
|
+
path = Layout.index_path(File.expand_path(root.to_s))
|
|
302
|
+
return { "version" => 1, "updated_at" => nil, "projects" => {} } unless File.exist?(path)
|
|
303
|
+
|
|
304
|
+
raw = JSON.parse(File.read(path, encoding: "UTF-8").scrub)
|
|
305
|
+
raw.merge("projects" => raw.fetch("projects", {}))
|
|
306
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
307
|
+
{ "version" => 1, "updated_at" => nil, "projects" => {} }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def index_snapshot
|
|
311
|
+
self.class.index_snapshot(@root)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
def summary_of(record)
|
|
317
|
+
Summary.new(id: record["id"], row: record["row"].to_i, title: record["title"].to_s,
|
|
318
|
+
status: record["status"].to_s, updated_at: record["updated_at"].to_s,
|
|
319
|
+
tags: Array(record["tags"]).dup)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def resolve_record(records, selector, meta)
|
|
323
|
+
selector = selector.to_s.strip
|
|
324
|
+
return nil if selector.empty?
|
|
325
|
+
|
|
326
|
+
# Tam id içerik adreslemesidir: birebir eşleşme (bayat hash → not
|
|
327
|
+
# found; satır kısa formu ayrı dalda ele alınır).
|
|
328
|
+
return records.find { |r| r["id"] == selector } if selector.match?(ID_PATTERN)
|
|
329
|
+
|
|
330
|
+
# tip.satır formları alias'tan ÖNCE denir; kronikçi alias'ları bu
|
|
331
|
+
# şekle asla düşmez — tek Garantör: Vakanuvis::Vocabulary
|
|
332
|
+
# (rakam koruması, slug mint'inde).
|
|
333
|
+
if (m = selector.match(/\A([a-z]+)\.(\d+)\z/))
|
|
334
|
+
type = m[1]
|
|
335
|
+
row = m[2].to_i
|
|
336
|
+
return records.find { |r| r["type"] == type && r["row"] == row }
|
|
337
|
+
end
|
|
338
|
+
return records.find { |r| r["row"].to_s == selector } if selector.match?(/\A\d+\z/)
|
|
339
|
+
return records.find { |r| r["hash"].to_s.start_with?(selector.downcase) } if selector.match?(/\A[0-9a-f]{#{HASH_PREFIX_MIN},64}\z/i)
|
|
340
|
+
|
|
341
|
+
# Son şans: alias — refs üzerinden tek sekme. Eski depolarda
|
|
342
|
+
# transcript alias'ı ÇİFT önekle basılmıştı ("subagent-subagent-..."):
|
|
343
|
+
# doğal form ("subagent-1-...") gelirse öneki ekleyip bir kez daha
|
|
344
|
+
# bakarız — böylece completion-time transcript upsert'i eski satırı
|
|
345
|
+
# bulur, kopya varlık açılmaz (uris.rb transcript_ref düzeltmesiyle
|
|
346
|
+
# birlikte; js-tangram olay raporu 2026-10-04, olay #4).
|
|
347
|
+
refs = meta.fetch("refs", {})
|
|
348
|
+
resolved_id = refs[selector]
|
|
349
|
+
resolved_id = refs["#{Rubik::Uris::TRANSCRIPT_REF_PREFIX}#{selector}"] if resolved_id.nil? && selector.start_with?(Rubik::Uris::TRANSCRIPT_REF_PREFIX)
|
|
350
|
+
resolved_id ? records.find { |r| r["id"] == resolved_id } : nil
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def content_hash(type, title, body, tags, links)
|
|
354
|
+
canonical = {
|
|
355
|
+
"type" => type.to_s, "title" => title.to_s, "body" => body.to_s,
|
|
356
|
+
"tags" => normalize_tags(tags), "links" => normalize_links(links)
|
|
357
|
+
}
|
|
358
|
+
Digest::SHA256.hexdigest(Rubik::Platform.safe_json_generate(canonical))
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Yazım sınırında başarısız-hızlı (issue #100 P1): boolean/sayılar
|
|
362
|
+
# sessizce "false"/"42" dizgelerine dönüşerek etiketi çöpüne
|
|
363
|
+
# çeviriyordu — coercion yok, ret var. Mesaj ne geldiğini ve nasıl
|
|
364
|
+
# düzeltileceğini söyler.
|
|
365
|
+
def normalize_tags(tags)
|
|
366
|
+
list = Array(tags)
|
|
367
|
+
list.each do |tag|
|
|
368
|
+
next if tag.is_a?(String)
|
|
369
|
+
|
|
370
|
+
raise ArgumentError,
|
|
371
|
+
"tags must be strings — got #{tag.class} (#{tag.inspect}). " \
|
|
372
|
+
"Stringify the value before put, or pass an array of strings."
|
|
373
|
+
end
|
|
374
|
+
list.reject { |t| t.strip.empty? }.uniq.sort
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def normalize_links(links)
|
|
378
|
+
links.to_h { |k, v| [k.to_s, v.to_s] }.sort.to_h
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def meta_next_row(meta, type)
|
|
382
|
+
rows = (meta["next_row"] ||= {})
|
|
383
|
+
rows[type] = rows[type].to_i + 1
|
|
384
|
+
meta["updated_at"] = Time.now.utc.iso8601
|
|
385
|
+
rows[type]
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def meta_bump_counts(meta, type)
|
|
389
|
+
counts = (meta["counts"] ||= {})
|
|
390
|
+
counts[type] = counts[type].to_i + 1
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def meta_set_ref(meta, name, id)
|
|
394
|
+
refs = (meta["refs"] ||= {})
|
|
395
|
+
refs[name] = id
|
|
396
|
+
meta["updated_at"] = Time.now.utc.iso8601
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def ensure_project_dirs
|
|
400
|
+
FileUtils.mkdir_p(Layout.store_dir(project_dir))
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def load_meta
|
|
404
|
+
path = Layout.meta_path(project_dir)
|
|
405
|
+
return {} unless File.exist?(path)
|
|
406
|
+
|
|
407
|
+
JSON.parse(File.read(path, encoding: "UTF-8").scrub)
|
|
408
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
409
|
+
{}
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def save_meta(meta)
|
|
413
|
+
meta["created_at"] ||= Time.now.utc.iso8601
|
|
414
|
+
Rubik::Platform.atomic_write(Layout.meta_path(project_dir),
|
|
415
|
+
JSON.pretty_generate(meta))
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# nil => tüm tipler; String => tek tip; Array => çoklu tip (vakanuvis-v2
|
|
419
|
+
# §4-V8: recall artık memory+wiki ikilisini TEK aramada ister — 7 tipin
|
|
420
|
+
# tamamını parse etmekten kurtulur).
|
|
421
|
+
# Arama samanlığı (title+body+tags, Folding.fold — TR I-sınıfı dahil
|
|
422
|
+
# tek katlama kuralı, folding.rb): parse cache girdisinde tembel
|
|
423
|
+
# memoize edilir — sorgu başına 1 MB'lık dizgi kurulumu tekrarlamaz.
|
|
424
|
+
# Kayıt kümesi paylaşımlı olduğundan samanlıklar da dondurulmuş
|
|
425
|
+
# paylaşılır (include?/scan/bytesize mutasyon yapmaz).
|
|
426
|
+
def haystacks_for(type, records)
|
|
427
|
+
entry = @records_cache[type]
|
|
428
|
+
return records.map { |r| haystack_of(r) } unless entry
|
|
429
|
+
|
|
430
|
+
entry[:haystacks] ||= records.map { |r| haystack_of(r) }.freeze
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def haystack_of(record)
|
|
434
|
+
Folding.fold("#{record['title']}\n#{record['body']}\n#{Array(record['tags']).join("\n")}")
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def resolve_types(type)
|
|
438
|
+
return TYPES.dup if type.nil?
|
|
439
|
+
|
|
440
|
+
Array(type).map { |t| t.to_s.downcase }.select { |t| TYPES.include?(t) }
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def load_records(type)
|
|
444
|
+
path = Layout.entities_path(project_dir, type)
|
|
445
|
+
return [] unless File.exist?(path)
|
|
446
|
+
|
|
447
|
+
mtime = File.mtime(path)
|
|
448
|
+
cached = @records_cache[type]
|
|
449
|
+
return cached[:records] if cached && cached[:mtime] == mtime
|
|
450
|
+
|
|
451
|
+
# Depo dosyaları her zaman UTF-8 yazılır (safe_json_generate + "wb");
|
|
452
|
+
# okuma da işlem dış kodlamasına değil UTF-8'a kilitlenir —
|
|
453
|
+
# UTF-8 olmayan Windows yerelinde (TR: cp1254/cp857) yanlış etiketli
|
|
454
|
+
# dizgiler ASCII olmayan recall sorgularında CompatibilityError
|
|
455
|
+
# üretiyordu. Scrub bozuk baytı U+FFFD'ye düşürür, satırı düşürmez.
|
|
456
|
+
# Bozuk/Hash-olmayan satır atlanır ve SAYILIR (review: sessiz
|
|
457
|
+
# kısmi-okuma gözlemlenebilir olsun); Hash olmayan JSON değeri
|
|
458
|
+
# (elle düzenleme: "42", ["x"]) tüm tipi zehirlemesin.
|
|
459
|
+
skipped = 0
|
|
460
|
+
records = File.foreach(path, encoding: "UTF-8").filter_map do |line|
|
|
461
|
+
next if line.strip.empty?
|
|
462
|
+
|
|
463
|
+
parsed = begin
|
|
464
|
+
JSON.parse(line.scrub)
|
|
465
|
+
rescue JSON::ParserError
|
|
466
|
+
nil
|
|
467
|
+
end
|
|
468
|
+
if parsed.is_a?(Hash)
|
|
469
|
+
parsed
|
|
470
|
+
else
|
|
471
|
+
skipped += 1
|
|
472
|
+
nil
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
Rubik::Log.warn(:storage, "#{path}: skipped #{skipped} malformed row(s)") if skipped.positive?
|
|
476
|
+
# Paylaşılan cache girdisi dondurulur: dizi + her kayıt hash'i +
|
|
477
|
+
# kayıtların iç koleksiyonları (tags/links — shallow freeze yeterli
|
|
478
|
+
# değildi, en çok uzatılan alanlar sessiz bozulabilirdi).
|
|
479
|
+
# Haystack'ler ilk memoize'da donar (haystacks_for). Paylaşım
|
|
480
|
+
# korunur; kazara mutasyon fail-loud olur.
|
|
481
|
+
records.freeze
|
|
482
|
+
records.each do |record|
|
|
483
|
+
record.each_value { |v| v.freeze if v.is_a?(Array) || v.is_a?(Hash) }
|
|
484
|
+
record.freeze
|
|
485
|
+
end
|
|
486
|
+
@records_cache[type] = { mtime: mtime, records: records }
|
|
487
|
+
records
|
|
488
|
+
rescue Errno::ENOENT
|
|
489
|
+
[]
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def write_records(type, records)
|
|
493
|
+
@records_cache.delete(type) # mtime da yenilenir; çifte güvence
|
|
494
|
+
body = records.map { |r| Rubik::Platform.safe_json_generate(r) }.join("\n")
|
|
495
|
+
body += "\n" unless body.empty?
|
|
496
|
+
Rubik::Platform.atomic_write(Layout.entities_path(project_dir, type), body)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Merkezi indeksi günceller: bu projenin girdisi canlı meta'dan
|
|
500
|
+
# yeniden yazılır, diğer projelerin girdileri korunur.
|
|
501
|
+
def touch_index(meta)
|
|
502
|
+
snapshot = self.class.index_snapshot(@root)
|
|
503
|
+
snapshot["projects"][@project] = {
|
|
504
|
+
"path" => project_dir,
|
|
505
|
+
"entity_counts" => meta.fetch("counts", {}).dup,
|
|
506
|
+
"created_at" => (snapshot["projects"][@project] || {})["created_at"] || meta["created_at"],
|
|
507
|
+
"updated_at" => meta["updated_at"]
|
|
508
|
+
}
|
|
509
|
+
snapshot["updated_at"] = meta["updated_at"]
|
|
510
|
+
Rubik::Platform.atomic_write(Layout.index_path(@root), JSON.pretty_generate(snapshot))
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../security/redactor"
|
|
5
|
+
require_relative "../uris"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Storage
|
|
9
|
+
# Subagent tamamlanınca transcript'ı depoya indeksler (supervisor
|
|
10
|
+
# handle_subagent_completion'dan tek satır çağrılır). Gövde task +
|
|
11
|
+
# final result merkezli deterministik kompozisyondur (2026-10-10
|
|
12
|
+
# transcript sağlık paketi): system-prompt metni arama korpusuna HİÇ
|
|
13
|
+
# girmez; sonuç metni aranabilir olur ("hangi subagent kırmızı
|
|
14
|
+
# bildirdi?" sorusu görevle değil sonuçla bulunur). Tam kopya zaten
|
|
15
|
+
# rubik://subagent/<storage-id>/transcript üzerinden okunur.
|
|
16
|
+
# Başarısızlık asla subagent akışını bozmaz — uyarı basılır (AGENTS.md
|
|
17
|
+
# §5: sessiz yutma yok, yarım durum birikmez).
|
|
18
|
+
module TranscriptIndexer
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Gövde parçası başına üst sınır (~1-2KB): task ve result kendi
|
|
22
|
+
# içinde kırpılır, fazlalık işaretlenir.
|
|
23
|
+
PART_LIMIT = 1200
|
|
24
|
+
|
|
25
|
+
def index(store, inst)
|
|
26
|
+
return unless store && inst
|
|
27
|
+
|
|
28
|
+
file = inst.transcript_uri.to_s
|
|
29
|
+
# Gerçek upsert: alias üzerinden varsa mevcut id güncellenir — resume
|
|
30
|
+
# sonrası ikinci tamamlanma yinelenen transcript varlığı yaratmaz.
|
|
31
|
+
ref = Rubik::Uris.transcript_ref(inst.storage_id)
|
|
32
|
+
existing = store.get(type: "transcript", id: ref)
|
|
33
|
+
store.put(
|
|
34
|
+
type: "transcript",
|
|
35
|
+
title: "[#{inst.type_name}] #{inst.role} (#{inst.id}) — #{inst.state}",
|
|
36
|
+
body: entity_body(inst, file),
|
|
37
|
+
tags: [inst.type_name.to_s, inst.state.to_s].uniq,
|
|
38
|
+
links: {
|
|
39
|
+
"subagent_id" => inst.storage_id.to_s,
|
|
40
|
+
# The raw path is forensics-only (read_file denies it): the
|
|
41
|
+
# canonical read route rides in the entity itself so recall
|
|
42
|
+
# consumers land on show_file in one hop (issue #97 finding 5).
|
|
43
|
+
"canonical" => Rubik::Uris.subagent_transcript(inst.storage_id),
|
|
44
|
+
"transcript" => file
|
|
45
|
+
},
|
|
46
|
+
status: inst.state.to_s,
|
|
47
|
+
id: existing&.[]("id"),
|
|
48
|
+
ref: ref
|
|
49
|
+
)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
label = inst.respond_to?(:id) ? inst.id : inst.class
|
|
52
|
+
warn "[Storage] transcript index failed for #{label}: #{e.class}: #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Task: transcript dosyasındaki İLK user satırı (seed'deki brif);
|
|
56
|
+
# dosya yoksa inst.prompt'a düşer ve dürüst marker taşır. Result:
|
|
57
|
+
# terminalde nil kalabilir (failed) — gövde yine kurulur, state
|
|
58
|
+
# satırı düşer. System satırı bu kompozisyona asla girmez. Dosyadan
|
|
59
|
+
# gelmeyen her metin (prompt fallback, inst.result) HAM memory'den
|
|
60
|
+
# gelir — kalıcı+aranabilir gövdeye girmeden Redactor'dan geçer
|
|
61
|
+
# (dosya tarafının maskeleme paritesi; review bulgusu).
|
|
62
|
+
def entity_body(inst, file)
|
|
63
|
+
task = first_user_task(file)
|
|
64
|
+
task_part = if task
|
|
65
|
+
"[task] #{clip(task)}"
|
|
66
|
+
elsif inst.respond_to?(:prompt)
|
|
67
|
+
"[task] #{clip(redact(inst.prompt.to_s))} (transcript file unavailable)"
|
|
68
|
+
else
|
|
69
|
+
"[task] (transcript file unavailable)"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
result = inst.respond_to?(:result) ? redact(inst.result.to_s) : ""
|
|
73
|
+
result_part = result.empty? ? "[state] #{inst.state} — no result recorded" : "[result] #{clip(result)}"
|
|
74
|
+
"#{task_part}\n#{result_part}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def redact(text)
|
|
78
|
+
Rubik::Security::Redactor.redact(text)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# foreach: task için tüm dosyayı belleğe almak yok; ilk user satırında
|
|
82
|
+
# durur. UTF-8 kilitli okuma: transcript dosyaları UTF-8 yazılır;
|
|
83
|
+
# işlem dış kodlaması (Windows TR yereli) başlık alıntısını bozmasın
|
|
84
|
+
# (store.rb load_records ile aynı disiplin). Okuma/parse arızası
|
|
85
|
+
# sessiz yutulmaz: uyarı basılır, inst.prompt fallback'i döner.
|
|
86
|
+
def first_user_task(file)
|
|
87
|
+
return nil unless File.exist?(file)
|
|
88
|
+
|
|
89
|
+
File.foreach(file, encoding: "UTF-8") do |line|
|
|
90
|
+
row = parse_row(line)
|
|
91
|
+
next unless row.is_a?(Hash) && row["role"] == "user"
|
|
92
|
+
|
|
93
|
+
content = row["content"].to_s
|
|
94
|
+
return content unless content.empty?
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
warn "[Storage] transcript task read failed for #{file}: #{e.class}: #{e.message}"
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_row(line)
|
|
103
|
+
JSON.parse(line)
|
|
104
|
+
rescue StandardError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def clip(text)
|
|
109
|
+
text = text.to_s.scrub
|
|
110
|
+
return text if text.length <= PART_LIMIT
|
|
111
|
+
|
|
112
|
+
"#{text.slice(0, PART_LIMIT)}\n... [truncated #{text.length - PART_LIMIT} chars]"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|