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.
Files changed (523) hide show
  1. checksums.yaml +7 -0
  2. data/ATTRIBUTION.md +139 -0
  3. data/CHANGELOG.md +236 -0
  4. data/LICENSE +21 -0
  5. data/README.md +269 -0
  6. data/bin/rubik +82 -0
  7. data/lib/rubik/adapter/base.rb +195 -0
  8. data/lib/rubik/adapter/c.rb +36 -0
  9. data/lib/rubik/adapter/crystal.rb +78 -0
  10. data/lib/rubik/adapter/generic.rb +86 -0
  11. data/lib/rubik/adapter/go.rb +36 -0
  12. data/lib/rubik/adapter/node.rb +146 -0
  13. data/lib/rubik/adapter/process.rb +78 -0
  14. data/lib/rubik/adapter/python.rb +30 -0
  15. data/lib/rubik/adapter/registry.rb +81 -0
  16. data/lib/rubik/adapter/ruby.rb +30 -0
  17. data/lib/rubik/adapter/rust.rb +36 -0
  18. data/lib/rubik/adapter/zig.rb +36 -0
  19. data/lib/rubik/adapter.rb +18 -0
  20. data/lib/rubik/ast/body_loader.rb +19 -0
  21. data/lib/rubik/ast/c_extractor.rb +114 -0
  22. data/lib/rubik/ast/cache_store.rb +191 -0
  23. data/lib/rubik/ast/extractor.rb +59 -0
  24. data/lib/rubik/ast/fallback_cleaner.rb +145 -0
  25. data/lib/rubik/ast/go_extractor.rb +148 -0
  26. data/lib/rubik/ast/graph.rb +272 -0
  27. data/lib/rubik/ast/python_extractor.rb +120 -0
  28. data/lib/rubik/ast/ruby_extractor.rb +260 -0
  29. data/lib/rubik/ast/rust_extractor.rb +127 -0
  30. data/lib/rubik/ast/symbol.rb +103 -0
  31. data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
  32. data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
  33. data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
  34. data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
  35. data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
  36. data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
  37. data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
  38. data/lib/rubik/ast/treesitter_extractor.rb +93 -0
  39. data/lib/rubik/ast/typescript_extractor.rb +283 -0
  40. data/lib/rubik/ast/zig_extractor.rb +116 -0
  41. data/lib/rubik/ast.rb +13 -0
  42. data/lib/rubik/benchmark/runner.rb +224 -0
  43. data/lib/rubik/benchmark/synth_repo.rb +59 -0
  44. data/lib/rubik/benchmark.rb +9 -0
  45. data/lib/rubik/buffer/diff.rb +43 -0
  46. data/lib/rubik/buffer/manager.rb +427 -0
  47. data/lib/rubik/buffer/path_validator.rb +60 -0
  48. data/lib/rubik/buffer/snapshot.rb +99 -0
  49. data/lib/rubik/buffer.rb +10 -0
  50. data/lib/rubik/coeffects.rb +129 -0
  51. data/lib/rubik/concurrency/boundary.rb +74 -0
  52. data/lib/rubik/concurrency/dns_pool.rb +68 -0
  53. data/lib/rubik/concurrency/errors.rb +17 -0
  54. data/lib/rubik/concurrency/event_loop.rb +441 -0
  55. data/lib/rubik/concurrency/join.rb +55 -0
  56. data/lib/rubik/concurrency/process_waiter.rb +55 -0
  57. data/lib/rubik/concurrency/scheduler.rb +84 -0
  58. data/lib/rubik/concurrency/timers.rb +109 -0
  59. data/lib/rubik/concurrency.rb +132 -0
  60. data/lib/rubik/config/deep_ops.rb +80 -0
  61. data/lib/rubik/config/defaults.rb +248 -0
  62. data/lib/rubik/config/model_catalog.rb +93 -0
  63. data/lib/rubik/config/store.rb +155 -0
  64. data/lib/rubik/config.rb +216 -0
  65. data/lib/rubik/context/git_info.rb +84 -0
  66. data/lib/rubik/context/git_lister.rb +37 -0
  67. data/lib/rubik/context/index.rb +134 -0
  68. data/lib/rubik/context/repo_context_reader.rb +121 -0
  69. data/lib/rubik/context/scanner.rb +439 -0
  70. data/lib/rubik/context.rb +11 -0
  71. data/lib/rubik/diagnostics/collector.rb +200 -0
  72. data/lib/rubik/diagnostics/entry.rb +60 -0
  73. data/lib/rubik/diagnostics/parser.rb +107 -0
  74. data/lib/rubik/diagnostics.rb +10 -0
  75. data/lib/rubik/event_bus.rb +56 -0
  76. data/lib/rubik/kernel/dispatcher.rb +171 -0
  77. data/lib/rubik/kernel/dmesg.rb +116 -0
  78. data/lib/rubik/kernel/init.rb +177 -0
  79. data/lib/rubik/kernel/record.rb +85 -0
  80. data/lib/rubik/kernel/unit.rb +160 -0
  81. data/lib/rubik/llm/anthropic.rb +202 -0
  82. data/lib/rubik/llm/chunk_assembler.rb +285 -0
  83. data/lib/rubik/llm/client.rb +526 -0
  84. data/lib/rubik/llm/formatter.rb +450 -0
  85. data/lib/rubik/llm/message_sanitizer.rb +87 -0
  86. data/lib/rubik/llm/ollama.rb +167 -0
  87. data/lib/rubik/llm/openai.rb +21 -0
  88. data/lib/rubik/llm/openai_compatible.rb +216 -0
  89. data/lib/rubik/llm/provider.rb +125 -0
  90. data/lib/rubik/llm/registry.rb +336 -0
  91. data/lib/rubik/llm/router.rb +100 -0
  92. data/lib/rubik/llm.rb +18 -0
  93. data/lib/rubik/log.rb +38 -0
  94. data/lib/rubik/lsp/client.rb +475 -0
  95. data/lib/rubik/lsp/manager.rb +366 -0
  96. data/lib/rubik/lsp.rb +9 -0
  97. data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
  98. data/lib/rubik/mcp/client.rb +169 -0
  99. data/lib/rubik/mcp/errors.rb +11 -0
  100. data/lib/rubik/mcp/manager.rb +619 -0
  101. data/lib/rubik/mcp/proxy_tool.rb +104 -0
  102. data/lib/rubik/mcp/transport/stdio.rb +255 -0
  103. data/lib/rubik/mcp.rb +12 -0
  104. data/lib/rubik/orchestrator/activity_log.rb +75 -0
  105. data/lib/rubik/orchestrator/auto_apply.rb +166 -0
  106. data/lib/rubik/orchestrator/budget_manager.rb +97 -0
  107. data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
  108. data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
  109. data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
  110. data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
  111. data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
  112. data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
  113. data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
  114. data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
  115. data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
  116. data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
  117. data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
  118. data/lib/rubik/orchestrator/goal_state.rb +184 -0
  119. data/lib/rubik/orchestrator/goal_wait.rb +125 -0
  120. data/lib/rubik/orchestrator/loop.rb +388 -0
  121. data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
  122. data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
  123. data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
  124. data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
  125. data/lib/rubik/orchestrator/shell_deps.rb +85 -0
  126. data/lib/rubik/orchestrator/step_executor.rb +328 -0
  127. data/lib/rubik/orchestrator/stream_manager.rb +118 -0
  128. data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
  129. data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
  130. data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
  131. data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
  132. data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
  133. data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
  134. data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
  135. data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
  136. data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
  137. data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
  138. data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
  139. data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
  140. data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
  141. data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
  142. data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
  143. data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
  144. data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
  145. data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
  146. data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
  147. data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
  148. data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
  149. data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
  150. data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
  151. data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
  152. data/lib/rubik/orchestrator/tool_history.rb +39 -0
  153. data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
  154. data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
  155. data/lib/rubik/orchestrator/turn_executor.rb +376 -0
  156. data/lib/rubik/orchestrator.rb +11 -0
  157. data/lib/rubik/platform/filesystem.rb +64 -0
  158. data/lib/rubik/platform/io_drain.rb +53 -0
  159. data/lib/rubik/platform/path.rb +210 -0
  160. data/lib/rubik/platform/process.rb +38 -0
  161. data/lib/rubik/platform/sanitize.rb +157 -0
  162. data/lib/rubik/platform.rb +113 -0
  163. data/lib/rubik/plugin/dsl.rb +51 -0
  164. data/lib/rubik/plugin/manager.rb +247 -0
  165. data/lib/rubik/plugin/storage.rb +195 -0
  166. data/lib/rubik/plugin.rb +10 -0
  167. data/lib/rubik/prompt/context_builder.rb +377 -0
  168. data/lib/rubik/prompt/context_compactor.rb +326 -0
  169. data/lib/rubik/prompt/conversation_rows.rb +74 -0
  170. data/lib/rubik/prompt/doctrine.rb +160 -0
  171. data/lib/rubik/prompt/emergency_compactor.rb +81 -0
  172. data/lib/rubik/prompt/services.rb +60 -0
  173. data/lib/rubik/prompt/token_estimator.rb +63 -0
  174. data/lib/rubik/prompt/tool_teachings.rb +78 -0
  175. data/lib/rubik/prompt.rb +13 -0
  176. data/lib/rubik/repair/loop.rb +95 -0
  177. data/lib/rubik/repair.rb +8 -0
  178. data/lib/rubik/repl/ast_warmup.rb +129 -0
  179. data/lib/rubik/repl/command_context.rb +214 -0
  180. data/lib/rubik/repl/command_parser.rb +48 -0
  181. data/lib/rubik/repl/commands/agent.rb +30 -0
  182. data/lib/rubik/repl/commands/agent_context.rb +289 -0
  183. data/lib/rubik/repl/commands/agent_execution.rb +342 -0
  184. data/lib/rubik/repl/commands/agent_models.rb +212 -0
  185. data/lib/rubik/repl/commands/agent_providers.rb +232 -0
  186. data/lib/rubik/repl/commands/base.rb +55 -0
  187. data/lib/rubik/repl/commands/buffer.rb +124 -0
  188. data/lib/rubik/repl/commands/dmesg.rb +87 -0
  189. data/lib/rubik/repl/commands/lsp.rb +235 -0
  190. data/lib/rubik/repl/commands/mcp.rb +194 -0
  191. data/lib/rubik/repl/commands/plugins.rb +208 -0
  192. data/lib/rubik/repl/commands/pruner.rb +71 -0
  193. data/lib/rubik/repl/commands/session.rb +183 -0
  194. data/lib/rubik/repl/commands/skills.rb +111 -0
  195. data/lib/rubik/repl/commands/storage.rb +85 -0
  196. data/lib/rubik/repl/commands/subagents.rb +195 -0
  197. data/lib/rubik/repl/commands/system.rb +85 -0
  198. data/lib/rubik/repl/commands/tasks.rb +120 -0
  199. data/lib/rubik/repl/commands/workspace.rb +443 -0
  200. data/lib/rubik/repl/commands.rb +44 -0
  201. data/lib/rubik/repl/composition.rb +32 -0
  202. data/lib/rubik/repl/config_wiring.rb +142 -0
  203. data/lib/rubik/repl/confirmations.rb +56 -0
  204. data/lib/rubik/repl/hooks.rb +47 -0
  205. data/lib/rubik/repl/input_execution.rb +323 -0
  206. data/lib/rubik/repl/input_loop.rb +213 -0
  207. data/lib/rubik/repl/interrupt_handler.rb +217 -0
  208. data/lib/rubik/repl/lifecycle.rb +410 -0
  209. data/lib/rubik/repl/paste_support.rb +88 -0
  210. data/lib/rubik/repl/pending_notice_queue.rb +60 -0
  211. data/lib/rubik/repl/presentation.rb +132 -0
  212. data/lib/rubik/repl/pruner/brief.rb +101 -0
  213. data/lib/rubik/repl/pruner/gate.rb +279 -0
  214. data/lib/rubik/repl/pruner/harness.rb +236 -0
  215. data/lib/rubik/repl/pruner/rules.rb +316 -0
  216. data/lib/rubik/repl/pruner/service.rb +82 -0
  217. data/lib/rubik/repl/pruner.rb +39 -0
  218. data/lib/rubik/repl/service_catalog.rb +374 -0
  219. data/lib/rubik/repl/setup_factories.rb +66 -0
  220. data/lib/rubik/repl/shell.rb +231 -0
  221. data/lib/rubik/repl/step_formatter.rb +324 -0
  222. data/lib/rubik/repl/tee_io.rb +105 -0
  223. data/lib/rubik/repl/terminal_guard.rb +157 -0
  224. data/lib/rubik/repl/turn_recorder.rb +234 -0
  225. data/lib/rubik/repl.rb +14 -0
  226. data/lib/rubik/runtime/ast_service.rb +78 -0
  227. data/lib/rubik/runtime/context.rb +335 -0
  228. data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
  229. data/lib/rubik/runtime/discovery.rb +342 -0
  230. data/lib/rubik/runtime/fiber.rb +142 -0
  231. data/lib/rubik/runtime/fiber_ordering.rb +52 -0
  232. data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
  233. data/lib/rubik/runtime/index_service.rb +58 -0
  234. data/lib/rubik/runtime/loop_service.rb +73 -0
  235. data/lib/rubik/runtime/manager.rb +241 -0
  236. data/lib/rubik/runtime/overlay_storage.rb +481 -0
  237. data/lib/rubik/runtime/service_definition.rb +54 -0
  238. data/lib/rubik/runtime/service_registration.rb +33 -0
  239. data/lib/rubik/runtime/task_manager.rb +302 -0
  240. data/lib/rubik/runtime.rb +19 -0
  241. data/lib/rubik/sandbox/driver/base.rb +30 -0
  242. data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
  243. data/lib/rubik/sandbox/driver/docker.rb +113 -0
  244. data/lib/rubik/sandbox/driver/podman.rb +98 -0
  245. data/lib/rubik/sandbox/driver/process.rb +190 -0
  246. data/lib/rubik/sandbox/driver.rb +91 -0
  247. data/lib/rubik/sandbox/runner.rb +218 -0
  248. data/lib/rubik/sandbox/shell_resolver.rb +178 -0
  249. data/lib/rubik/sandbox.rb +8 -0
  250. data/lib/rubik/security/blocked_guidance.rb +187 -0
  251. data/lib/rubik/security/classifier/classifier.rb +440 -0
  252. data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
  253. data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
  254. data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
  255. data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
  256. data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
  257. data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
  258. data/lib/rubik/security/classifier/commands/black.yaml +12 -0
  259. data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
  260. data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
  261. data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
  262. data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
  263. data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
  264. data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
  265. data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
  266. data/lib/rubik/security/classifier/commands/column.yaml +7 -0
  267. data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
  268. data/lib/rubik/security/classifier/commands/command.yaml +12 -0
  269. data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
  270. data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
  271. data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
  272. data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
  273. data/lib/rubik/security/classifier/commands/date.yaml +10 -0
  274. data/lib/rubik/security/classifier/commands/df.yaml +7 -0
  275. data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
  276. data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
  277. data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
  278. data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
  279. data/lib/rubik/security/classifier/commands/du.yaml +7 -0
  280. data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
  281. data/lib/rubik/security/classifier/commands/env.yaml +15 -0
  282. data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
  283. data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
  284. data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
  285. data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
  286. data/lib/rubik/security/classifier/commands/false.yaml +7 -0
  287. data/lib/rubik/security/classifier/commands/file.yaml +7 -0
  288. data/lib/rubik/security/classifier/commands/find.yaml +25 -0
  289. data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
  290. data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
  291. data/lib/rubik/security/classifier/commands/free.yaml +7 -0
  292. data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
  293. data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
  294. data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
  295. data/lib/rubik/security/classifier/commands/git.yaml +524 -0
  296. data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
  297. data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
  298. data/lib/rubik/security/classifier/commands/go.yaml +44 -0
  299. data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
  300. data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
  301. data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
  302. data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
  303. data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
  304. data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
  305. data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
  306. data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
  307. data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
  308. data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
  309. data/lib/rubik/security/classifier/commands/head.yaml +7 -0
  310. data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
  311. data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
  312. data/lib/rubik/security/classifier/commands/id.yaml +7 -0
  313. data/lib/rubik/security/classifier/commands/install.yaml +7 -0
  314. data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
  315. data/lib/rubik/security/classifier/commands/java.yaml +35 -0
  316. data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
  317. data/lib/rubik/security/classifier/commands/join.yaml +7 -0
  318. data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
  319. data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
  320. data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
  321. data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
  322. data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
  323. data/lib/rubik/security/classifier/commands/less.yaml +7 -0
  324. data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
  325. data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
  326. data/lib/rubik/security/classifier/commands/make.yaml +21 -0
  327. data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
  328. data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
  329. data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
  330. data/lib/rubik/security/classifier/commands/more.yaml +7 -0
  331. data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
  332. data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
  333. data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
  334. data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
  335. data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
  336. data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
  337. data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
  338. data/lib/rubik/security/classifier/commands/node.yaml +20 -0
  339. data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
  340. data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
  341. data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
  342. data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
  343. data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
  344. data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
  345. data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
  346. data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
  347. data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
  348. data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
  349. data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
  350. data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
  351. data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
  352. data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
  353. data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
  354. data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
  355. data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
  356. data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
  357. data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
  358. data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
  359. data/lib/rubik/security/classifier/commands/python.yaml +9 -0
  360. data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
  361. data/lib/rubik/security/classifier/commands/read.yaml +7 -0
  362. data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
  363. data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
  364. data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
  365. data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
  366. data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
  367. data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
  368. data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
  369. data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
  370. data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
  371. data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
  372. data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
  373. data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
  374. data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
  375. data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
  376. data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
  377. data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
  378. data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
  379. data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
  380. data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
  381. data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
  382. data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
  383. data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
  384. data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
  385. data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
  386. data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
  387. data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
  388. data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
  389. data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
  390. data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
  391. data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
  392. data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
  393. data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
  394. data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
  395. data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
  396. data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
  397. data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
  398. data/lib/rubik/security/classifier/commands/test.yaml +7 -0
  399. data/lib/rubik/security/classifier/commands/time.yaml +8 -0
  400. data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
  401. data/lib/rubik/security/classifier/commands/top.yaml +7 -0
  402. data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
  403. data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
  404. data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
  405. data/lib/rubik/security/classifier/commands/true.yaml +7 -0
  406. data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
  407. data/lib/rubik/security/classifier/commands/type.yaml +7 -0
  408. data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
  409. data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
  410. data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
  411. data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
  412. data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
  413. data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
  414. data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
  415. data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
  416. data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
  417. data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
  418. data/lib/rubik/security/classifier/commands/which.yaml +7 -0
  419. data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
  420. data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
  421. data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
  422. data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
  423. data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
  424. data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
  425. data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
  426. data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
  427. data/lib/rubik/security/classifier/database.rb +216 -0
  428. data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
  429. data/lib/rubik/security/classifier/matcher/options.rb +245 -0
  430. data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
  431. data/lib/rubik/security/classifier/matcher.rb +249 -0
  432. data/lib/rubik/security/classifier/models.rb +170 -0
  433. data/lib/rubik/security/classifier/parser.rb +23 -0
  434. data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
  435. data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
  436. data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
  437. data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
  438. data/lib/rubik/security/classifier.rb +25 -0
  439. data/lib/rubik/security/errors.rb +39 -0
  440. data/lib/rubik/security/guard.rb +954 -0
  441. data/lib/rubik/security/redactor.rb +89 -0
  442. data/lib/rubik/security.rb +11 -0
  443. data/lib/rubik/session/manager.rb +331 -0
  444. data/lib/rubik/session/recorder.rb +149 -0
  445. data/lib/rubik/session/replayer.rb +65 -0
  446. data/lib/rubik/session.rb +10 -0
  447. data/lib/rubik/skills/frontmatter.rb +41 -0
  448. data/lib/rubik/skills/manager.rb +223 -0
  449. data/lib/rubik/skills/registry.rb +64 -0
  450. data/lib/rubik/skills/skill.rb +33 -0
  451. data/lib/rubik/skills/storage.rb +77 -0
  452. data/lib/rubik/skills/validator.rb +116 -0
  453. data/lib/rubik/skills.rb +20 -0
  454. data/lib/rubik/storage/folding.rb +23 -0
  455. data/lib/rubik/storage/layout.rb +145 -0
  456. data/lib/rubik/storage/render.rb +97 -0
  457. data/lib/rubik/storage/service.rb +42 -0
  458. data/lib/rubik/storage/store.rb +514 -0
  459. data/lib/rubik/storage/transcript_indexer.rb +116 -0
  460. data/lib/rubik/storage/uri_reader.rb +168 -0
  461. data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
  462. data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
  463. data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
  464. data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
  465. data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
  466. data/lib/rubik/storage/vakanuvis/service.rb +69 -0
  467. data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
  468. data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
  469. data/lib/rubik/storage/vakanuvis.rb +77 -0
  470. data/lib/rubik/storage.rb +29 -0
  471. data/lib/rubik/tools/base.rb +107 -0
  472. data/lib/rubik/tools/builtins/dmesg.rb +74 -0
  473. data/lib/rubik/tools/builtins/exec.rb +293 -0
  474. data/lib/rubik/tools/builtins/files.rb +354 -0
  475. data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
  476. data/lib/rubik/tools/builtins/grep.rb +281 -0
  477. data/lib/rubik/tools/builtins/interactive.rb +175 -0
  478. data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
  479. data/lib/rubik/tools/builtins/lsp.rb +283 -0
  480. data/lib/rubik/tools/builtins/pruner.rb +88 -0
  481. data/lib/rubik/tools/builtins/runtime.rb +120 -0
  482. data/lib/rubik/tools/builtins/send_message.rb +109 -0
  483. data/lib/rubik/tools/builtins/show_file.rb +84 -0
  484. data/lib/rubik/tools/builtins/skill.rb +72 -0
  485. data/lib/rubik/tools/builtins/storage.rb +232 -0
  486. data/lib/rubik/tools/builtins/subagent.rb +356 -0
  487. data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
  488. data/lib/rubik/tools/builtins/symbols.rb +209 -0
  489. data/lib/rubik/tools/builtins/synthesis.rb +314 -0
  490. data/lib/rubik/tools/builtins/task.rb +168 -0
  491. data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
  492. data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
  493. data/lib/rubik/tools/builtins.rb +30 -0
  494. data/lib/rubik/tools/catalog.rb +70 -0
  495. data/lib/rubik/tools/declaration.rb +90 -0
  496. data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
  497. data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
  498. data/lib/rubik/tools/native_search.rb +180 -0
  499. data/lib/rubik/tools/output.rb +54 -0
  500. data/lib/rubik/tools/preflight_runner.rb +209 -0
  501. data/lib/rubik/tools/registry.rb +247 -0
  502. data/lib/rubik/tools/result.rb +71 -0
  503. data/lib/rubik/tools/services.rb +96 -0
  504. data/lib/rubik/tools/truncator.rb +78 -0
  505. data/lib/rubik/tools/uri_resolver.rb +195 -0
  506. data/lib/rubik/tools.rb +14 -0
  507. data/lib/rubik/ui/approvals.rb +52 -0
  508. data/lib/rubik/ui/banner.rb +43 -0
  509. data/lib/rubik/ui/capability.rb +122 -0
  510. data/lib/rubik/ui/dialog.rb +106 -0
  511. data/lib/rubik/ui/diff_viewer.rb +78 -0
  512. data/lib/rubik/ui/frame.rb +95 -0
  513. data/lib/rubik/ui/markdown_stream.rb +107 -0
  514. data/lib/rubik/ui/panel.rb +98 -0
  515. data/lib/rubik/ui/renderer.rb +206 -0
  516. data/lib/rubik/ui/spinner.rb +120 -0
  517. data/lib/rubik/ui/statusline.rb +134 -0
  518. data/lib/rubik/ui/theme.rb +118 -0
  519. data/lib/rubik/ui.rb +57 -0
  520. data/lib/rubik/uris.rb +101 -0
  521. data/lib/rubik/version.rb +5 -0
  522. data/lib/rubik.rb +65 -0
  523. metadata +770 -0
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "json"
5
+ require_relative "../../prompt/context_compactor"
6
+ require_relative "../../prompt/conversation_rows"
7
+ require_relative "../../uris"
8
+
9
+ module Rubik
10
+ module Orchestrator
11
+ module GoalLedger
12
+ # !goal döngüsünün yapılandırılmış ΔΣ defteri (Σ) — SKILL.state
13
+ # uyarlamasının durumlu yarısı. C kütüphanesi (Patch/Schema/Validator)
14
+ # SAFTI; burada yaşam döngüsü durumu yaşar: Monitor korumalı doküman,
15
+ # ardışık-ret bütçesi, storage revizyon zinciri (her kabul YENİ satır;
16
+ # `goal-ledger` ref'i daima son revizyonu gösterir) ve dmesg telemetrisi.
17
+ #
18
+ # Thread modeli (plan A kuralı): yazmalar YALNIZ turn thread'indedir
19
+ # (araç execute, step-top refresh, finalize); tüm okumalar Monitor
20
+ # altındadır — rubik://goal/ledger read_file her an güvenlidir
21
+ # (GoalState mold'u). Arkaplan thread'i yoktur.
22
+ #
23
+ # Degradasyon sözleşmesi: Store yoksa/başarısızsa satır YAZILMAZ, tek
24
+ # dmesg warn düşer ve Σ bellekte yaşamaya devam eder — özellik asla
25
+ # storage hatasıyla çökmez ("prompt'tan atmak ≠ kayıttan atmak":
26
+ # transcripts/ToolHistory bu sınıfa hiç girmez).
27
+ class Ledger
28
+ Result = Struct.new(:status, :reasons, :rev, :uri, keyword_init: true)
29
+
30
+ # A planı üretim şemasını SAHİPLENİR: goal skeleton örnek örneği
31
+ # üretim yüzeyi olur (frozen; verify alanları optional+allow_null —
32
+ # re-verification planı doldurur).
33
+ PRODUCTION_SCHEMA = Schema.goal_skeleton
34
+
35
+ # rev = Σ sürüm SSOT'u (doc["rev"]) — ObservationProjection ve
36
+ # kullanıcılar Monitor altında okur (@rev ivarı YOKTUR; atanmaz).
37
+ def rev
38
+ @monitor.synchronize { @doc["rev"] }
39
+ end
40
+
41
+ attr_reader :event_bus
42
+
43
+ def initialize(goal_text:, max_retries: DEFAULT_MAX_RETRIES, store: nil, dmesg: nil, event_bus: nil)
44
+ @monitor = Monitor.new
45
+ @max_retries = max_retries.to_i
46
+ @store = store
47
+ @dmesg = dmesg
48
+ @event_bus = event_bus
49
+ @rejections = 0
50
+ @storage_id = nil
51
+ @last_links = {}
52
+ @finalized = false
53
+ @updated_at = nil
54
+ @last_drift_note = nil
55
+ @doc = seed_doc(goal_text)
56
+ end
57
+
58
+ # ------------------------------------------------------------------
59
+ # Sınıf yüzü: çift-diyalekt knob okuması (pruner Service mold'u —
60
+ # Rubik::Config dotted-path böler, düz Hash tek anahtar sayar) ve
61
+ # oluşturma fabrikası. Konum: Ledger üzerinde, çünkü GoalLedger
62
+ # modülü C sabitlerinin evi olmayı sürdürür (shim < 90 hygiene pin).
63
+ class << self
64
+ def enabled?(config = nil)
65
+ config_get(config, "goal.ledger.enabled", true)
66
+ end
67
+
68
+ # B'nin knob'u (re-verification): enabled kapalıyken İNERTTİR
69
+ # (coeffect hiç provide edilmez → verifier girişi no-op).
70
+ def verify_enabled?(config = nil)
71
+ config_get(config, "goal.ledger.verify", true)
72
+ end
73
+
74
+ def max_patch_retries(config = nil)
75
+ config_get(config, "goal.ledger.max_patch_retries", DEFAULT_MAX_RETRIES).to_i
76
+ end
77
+
78
+ def create!(goal_text:, config: nil, store: nil, dmesg: nil, event_bus: nil)
79
+ ledger = new(goal_text: goal_text.to_s, max_retries: max_patch_retries(config),
80
+ store: store, dmesg: dmesg, event_bus: event_bus)
81
+ ledger.persist_seed!
82
+ ledger
83
+ end
84
+
85
+ def config_get(config, key, default)
86
+ return default if config.nil?
87
+
88
+ val = if config.is_a?(Hash)
89
+ config.dig(*key.to_s.split("."))
90
+ elsif config.respond_to?(:fetch)
91
+ config.fetch(key, nil)
92
+ end
93
+ val.nil? ? default : val
94
+ end
95
+ end
96
+
97
+ def persist_seed!
98
+ row = put_row(title: row_title, body: canonical_json, tags: ["goal-ledger"], links: {}, ref: "goal-ledger")
99
+ @storage_id = row["id"] if row
100
+ degrade_storage_once unless row
101
+ rescue StandardError => e
102
+ degrade("seed revision row", e)
103
+ end
104
+
105
+ # Tek giriş: patch metni (ya da Patch VO) → sınıflandırılmış sonuç.
106
+ # Ret sayacı ardışıktır; başarı sıfırlar; bütçe aşımında :exhausted
107
+ # döner ve ESKİ Σ korunur (goal loop asla brick olmaz).
108
+ def update(patch_or_text)
109
+ verdict = Validator.validate(patch_or_text, schema: PRODUCTION_SCHEMA, current_doc: @doc)
110
+ @monitor.synchronize do
111
+ if verdict[0] == :ok
112
+ apply!(verdict[1])
113
+ elsif @rejections >= @max_retries
114
+ # C sözleşmesi: ardışık ret bütçesi dolu — ESKİ Σ korunur ve
115
+ # dürüst tükenme döner; geçerli bir patch geldiğinde kabul
116
+ # yolu yeniden açılır (goal loop asla brick olmaz).
117
+ exhausted_result
118
+ else
119
+ @rejections += 1
120
+ log(:patch_rejected, "patch rejected (#{@rejections}/#{@max_retries} budget): " \
121
+ "#{verdict[1].first[:class]} — #{verdict[1].first[:message]}")
122
+ Result.new(status: :rejected, reasons: verdict[1], rev: @doc["rev"], uri: ledger_uri)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Adım başı yenileme: mevcut Σ satırlarını düşürüp TEK güncel satırı
128
+ # <user_task> hedef satırının peşine yerleştirir (tek-Σ-satırı
129
+ # invaryantı — pinli satırlar compaction'ı baypas ederek şişemez).
130
+ # Ledger satırı role "user" zorunludur: EmergencyCompactor hard
131
+ # tier'ı user satırlarına dokunmaz (emergency_compactor.rb:59-77).
132
+ def refresh_in_history!(history)
133
+ return if history.nil? || !history.respond_to?(:reject!)
134
+
135
+ @monitor.synchronize do
136
+ history.reject! { |msg| ledger_row?(msg) || drift_row?(msg) }
137
+ anchor = history.rindex { |msg| Prompt::ContextCompactor.goal_message?(msg) }
138
+ if anchor
139
+ history.insert(anchor + 1, { role: "user", content: render_xml })
140
+ history.insert(anchor + 2, { role: "user", content: @last_drift_note }) if @last_drift_note
141
+ else
142
+ history << { role: "user", content: render_xml }
143
+ history << { role: "user", content: @last_drift_note } if @last_drift_note
144
+ end
145
+ end
146
+ end
147
+
148
+ # Terminal damga (complete!/fail!/stop! sonrası): son revizyon
149
+ # satırına status yazılır (id korunur — version++), idempotenttir.
150
+ def finalize!(status)
151
+ @monitor.synchronize do
152
+ return final_result if @finalized
153
+
154
+ @finalized = true
155
+ if @store && @storage_id
156
+ links = @last_links.merge("finish_status" => status.to_s)
157
+ row = @store.put(type: "goal", id: @storage_id, title: row_title, body: canonical_json,
158
+ tags: ["goal-ledger", "final"], links: links, status: status.to_s)
159
+ @storage_id = row["id"]
160
+ else
161
+ degrade_storage_once
162
+ end
163
+ log(:finalized, "goal ledger finalized at rev #{@doc['rev']} (status: #{status})")
164
+ final_result
165
+ end
166
+ rescue StandardError => e
167
+ degrade("final status stamp", e)
168
+ @monitor.synchronize { final_result }
169
+ end
170
+
171
+ # B (re-verification): iç mutasyon girişi — drift flip'leri MODEL
172
+ # ΔΣ validasyonunu ATLAR (bu bir model patch'i değildir) ama rev
173
+ # BUMP EDİLİR: :conflict, drift sinyalinin ta kendisidir (model
174
+ # bayat rev ile patch gönderir → conflict → yeniden okur → stale
175
+ # bayraklarını GÖRÜR — paper'ın zero-recovery mekanizması). Tek
176
+ # yazar disiplini: persistence A'nın kendi put yolundan geçer.
177
+ def apply_internal_drift!(flips)
178
+ @monitor.synchronize do
179
+ flips.each do |flip|
180
+ item = @doc[flip[:collection]]&.[](flip[:index])
181
+ next unless item.is_a?(Hash)
182
+
183
+ item["status"] = "stale" if flip[:collection] == "tasks"
184
+ verify_h = item["verify"].is_a?(Hash) ? item["verify"].dup : {}
185
+ verify_h["last_check"] = { "state" => "failed", "from" => flip[:from].to_s,
186
+ "reason" => flip[:reason].to_s, "at" => flip[:at].to_s }
187
+ item["verify"] = verify_h
188
+ end
189
+ return nil if flips.empty?
190
+
191
+ @doc["rev"] += 1
192
+ @updated_at = Time.now.utc
193
+ if @store && @storage_id
194
+ @store.put(type: "goal", id: @storage_id, title: row_title, body: canonical_json,
195
+ tags: ["goal-ledger"], links: @last_links, status: "active")
196
+ end
197
+ @last_drift_note = DriftNote.render(snap: deep_dup(@doc))
198
+ log(:ledger_drift, "internal drift applied: #{flips.size} flip(s), rev #{@doc['rev']}")
199
+ { rev: @doc["rev"], flipped: flips.size }
200
+ end
201
+ rescue StandardError => e
202
+ degrade("drift persistence", e)
203
+ nil
204
+ end
205
+
206
+ # Verifier girişi: Monitor'lu FROZEN kopya — FS IO kilit dışı
207
+ # değerlendirilir (verifier.rb disiplini).
208
+ def snapshot_doc
209
+ @monitor.synchronize { deep_freeze(deep_dup(@doc)) }
210
+ end
211
+
212
+ # Test/seam gözlemcisi: güncel drift notu (yoksa nil).
213
+ def render_drift_note
214
+ @monitor.synchronize { DriftNote.render(snap: deep_dup(@doc)) }
215
+ end
216
+
217
+ def to_xml
218
+ @monitor.synchronize { render_xml }
219
+ end
220
+
221
+ # §4.1 render: GoalState#to_xml mold'u — tüm metin xml_escape'ten
222
+ # geçer; boş koleksiyonların etiketi HİÇ basılmaz ("yokluk, boşluk
223
+ # değil" kuralı); optional attribute'lar (storage_id, verify) yalnız
224
+ # set iken basılır.
225
+ def render_xml
226
+ esc = Rubik::Platform.method(:xml_escape)
227
+ attrs = %(rev="#{@doc['rev']}")
228
+ attrs += %( storage_id="#{esc.call(@storage_id)}") if @storage_id
229
+ attrs += %( updated_at="#{@updated_at.utc.iso8601}") if @updated_at
230
+ body = []
231
+ body << %( <objective>#{esc.call(@doc['objective'].to_s)}</objective>)
232
+ tasks = @doc["tasks"].map do |task|
233
+ text = task["title"].to_s
234
+ text += " — notes: #{task['notes']}" if task["notes"]
235
+ attrs_t = %(id="#{esc.call(task['id'].to_s)}" status="#{esc.call(task['status'].to_s)}")
236
+ attrs_t += %( verify="#{esc.call(task['verify'].to_s)}") if task["verify"]
237
+ task["title"] || task["notes"] ? %( <task #{attrs_t}>#{esc.call(text)}</task>) : %( <task #{attrs_t} />)
238
+ end
239
+ body << "<tasks>\n#{tasks.join("\n")}\n </tasks>" unless tasks.empty?
240
+ findings = @doc["findings"].map do |f|
241
+ %( <finding id="#{esc.call(f['id'].to_s)}" source_step="#{f['source_step']}">#{esc.call(f['text'].to_s)}</finding>)
242
+ end
243
+ body << "<findings>\n#{findings.join("\n")}\n </findings>" unless findings.empty?
244
+ decisions = @doc["decisions"].map do |dec|
245
+ attrs_d = %(id="#{esc.call(dec['id'].to_s)}")
246
+ attrs_d += %( rationale="#{esc.call(dec['rationale'].to_s)}") if dec["rationale"]
247
+ %( <decision #{attrs_d}>#{esc.call(dec['text'].to_s)}</decision>)
248
+ end
249
+ body << "<decisions>\n#{decisions.join("\n")}\n </decisions>" unless decisions.empty?
250
+ questions = @doc["open_questions"].map { |q| %( <question>#{esc.call(q.to_s)}</question>) }
251
+ body << "<open_questions>\n#{questions.join("\n")}\n </open_questions>" unless questions.empty?
252
+ notes = @doc["environment_notes"].map do |note|
253
+ attrs_n = %(id="#{esc.call(note['id'].to_s)}")
254
+ attrs_n += %( verify="#{esc.call(note['verify'].to_s)}") if note["verify"]
255
+ %( <note #{attrs_n}>#{esc.call(note['text'].to_s)}</note>)
256
+ end
257
+ body << "<environment_notes>\n#{notes.join("\n")}\n </environment_notes>" unless notes.empty?
258
+ <<~XML.strip
259
+ <goal_ledger #{attrs}>
260
+ #{body.join("\n")}
261
+ </goal_ledger>
262
+ XML
263
+ end
264
+
265
+ def snapshot
266
+ @monitor.synchronize do
267
+ { rev: @doc["rev"], objective: @doc["objective"], storage_id: @storage_id,
268
+ updated_at: @updated_at, finalized: @finalized, tasks: @doc["tasks"].size,
269
+ findings: @doc["findings"].size, decisions: @doc["decisions"].size,
270
+ open_questions: @doc["open_questions"].size, environment_notes: @doc["environment_notes"].size }
271
+ end
272
+ end
273
+
274
+ private
275
+
276
+ def seed_doc(goal_text)
277
+ { "rev" => 0, "objective" => Rubik::Platform.safe_utf8(goal_text.to_s.slice(0, 400)),
278
+ "tasks" => [], "findings" => [], "decisions" => [],
279
+ "open_questions" => [], "environment_notes" => [] }
280
+ end
281
+
282
+ def apply!(new_doc)
283
+ # Validator deep-frozen çıkarır (VO sözleşmesi); durum sahibi
284
+ # ledger'ın ÇALIŞMA kopyası mutable kalmalıdır (apply_internal_drift!
285
+ # ve render_xml iç mutasyonu).
286
+ @doc = deep_dup(new_doc)
287
+ @rejections = 0
288
+ @updated_at = Time.now.utc
289
+ @last_drift_note = nil # model yeniden planladı — drift notu tek seferliktir
290
+ links = @storage_id ? { "ledger_prev" => @storage_id } : {}
291
+ row = put_row(title: row_title, body: canonical_json, tags: ["goal-ledger"], links: links, ref: "goal-ledger")
292
+ if row
293
+ @storage_id = row["id"]
294
+ @last_links = links
295
+ end
296
+ log(:patch_accepted, "goal ledger rev #{@doc['rev']} applied (#{row ? "stored #{@storage_id}" : 'memory-only'})")
297
+ Result.new(status: :applied, rev: @doc["rev"], uri: ledger_uri)
298
+ rescue StandardError => e
299
+ # Storage yazım başarısızlığı araç yolunu BOGMAMALI: Σ zaten
300
+ # uygulandı (atomik validate-then-apply), yalnız kalıcılık düştü.
301
+ degrade("revision row", e)
302
+ Result.new(status: :applied, rev: @doc["rev"], uri: ledger_uri)
303
+ end
304
+
305
+ def exhausted_result
306
+ log(:patch_exhausted, "patch budget exhausted after #{@rejections} consecutive rejections; " \
307
+ "ledger unchanged at rev #{@doc['rev']}")
308
+ Result.new(status: :exhausted, rev: @doc["rev"], uri: ledger_uri)
309
+ end
310
+
311
+ def final_result
312
+ { id: @storage_id, rev: @doc["rev"], uri: ledger_uri }
313
+ end
314
+
315
+ def put_row(title:, body:, tags:, links:, ref: nil)
316
+ return nil unless @store
317
+
318
+ @store.put(type: "goal", title: title, body: body, tags: tags, links: links, ref: ref)
319
+ end
320
+
321
+ def canonical_json
322
+ Rubik::Platform.safe_json_generate(@doc)
323
+ end
324
+
325
+ def row_title
326
+ "ledger r#{@doc['rev']}: #{Rubik::Platform.safe_utf8(@doc['objective'].to_s.slice(0, 60))}"
327
+ end
328
+
329
+ def ledger_uri
330
+ @storage_id ? Rubik::Uris.storage_entity(project: @store.project, type: "goal", id: @storage_id) : nil
331
+ end
332
+
333
+ def ledger_row?(msg)
334
+ return false unless Prompt::ConversationRows.row?(msg)
335
+
336
+ content = Prompt::ConversationRows.content_of(msg).to_s
337
+ Prompt::ConversationRows.role_of(msg) == "user" && content.start_with?("<goal_ledger ") && content.include?("</goal_ledger>")
338
+ end
339
+
340
+ def drift_row?(msg)
341
+ return false unless Prompt::ConversationRows.row?(msg)
342
+
343
+ content = Prompt::ConversationRows.content_of(msg).to_s
344
+ Prompt::ConversationRows.role_of(msg) == "user" && content.start_with?("<ledger_drift ") && content.include?("</ledger_drift>")
345
+ end
346
+
347
+ def deep_dup(value)
348
+ case value
349
+ when Hash then value.to_a.to_h { |key, val| [key.dup, deep_dup(val)] }
350
+ when Array then value.map { |val| deep_dup(val) }
351
+ else value
352
+ end
353
+ end
354
+
355
+ def deep_freeze(value)
356
+ case value
357
+ when Hash then value.each do |key, val|
358
+ deep_freeze(key)
359
+ deep_freeze(val)
360
+ end.freeze
361
+ when Array then value.each { |val| deep_freeze(val) }.freeze
362
+ when String then value.dup.freeze
363
+ else value
364
+ end
365
+ end
366
+
367
+ def log(event, message)
368
+ unless @dmesg.respond_to?(:log)
369
+ # Dürüst degradasyon (review 2026-10-10): dmesg yokken telemetri
370
+ # sessizliğe gömülmesin — bir kez Log'a düş, sonra sessiz.
371
+ unless @dmesg_degraded
372
+ @dmesg_degraded = true
373
+ Rubik::Log.warn(:goal_ledger, "dmesg unavailable — goal ledger telemetry degraded to Log only")
374
+ end
375
+ return
376
+ end
377
+
378
+ @dmesg.log(:goal_ledger, :info, "[#{event}] #{message}")
379
+ rescue StandardError
380
+ Rubik::Log.warn(:goal_ledger, "[#{event}] #{message}")
381
+ end
382
+
383
+ def degrade(what, error)
384
+ Rubik::Log.warn(:goal_ledger, "goal ledger #{what} unavailable (#{error.class}: #{error.message}); " \
385
+ "Σ continues in memory")
386
+ end
387
+
388
+ # Depo yokluğu bilinçli degradasyondur ama SESSIZ olamaz (sözleşme:
389
+ # tek dürüst warn) — memoize'lı tek atış.
390
+ def degrade_storage_once
391
+ return if @storage_degraded
392
+
393
+ @storage_degraded = true
394
+ Rubik::Log.warn(:goal_ledger, "storage unavailable — goal ledger revisions stay memory-only (no audit rows)")
395
+ end
396
+ end
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../prompt/conversation_rows"
4
+
5
+ module Rubik
6
+ module Orchestrator
7
+ module GoalLedger
8
+ # Gözlem projeksiyonu (goal-ledger planı D): goal-loop observation'larının
9
+ # Σ üzerinden erken yapısal projeksiyonu — compaction'ın TIER 0'ı.
10
+ #
11
+ # Sözleşmeler: (1) yalnız goal-mode user kanalı (directive_for önkoşul
12
+ # matrisi: goal_mode? → knob'lar → coeffect → rev ≥ 1; herhangi biri
13
+ # yoksa nil → compaction byte-özdeş); (2) satır DÜŞÜRME YOK — yalnız
14
+ # content tombstone'u (tool_call_id pairing'i korunur, orphan-id 400
15
+ # riski yapısal olarak imkânsız); (3) kayıt katmanı dokunulmaz —
16
+ # tombstone yalnız compact_history'nin dup kopyasına yazılır
17
+ # ("prompt'tan atmak ≠ kayıttan atmak"); (4) worker thread yok —
18
+ # directive çözümü + uygulama yalnız turn thread'indedir.
19
+ module ObservationProjection
20
+ MODULE_NAME = "update_goal_ledger" # ledger tool adının SSOT'u (Declaration name ile aynı değer)
21
+ SKILL_TOOL_NAME = "skill" # progressive disclosure payload muafiyeti
22
+
23
+ module_function
24
+
25
+ # Önkoşul matrisi -> Directive | nil. Misconfiguration dürüstlüğü:
26
+ # observe_projection ON ama enabled OFF iken tek dmesg warn (çağrı
27
+ # yeri compaction'tır — doğal oran-limitli).
28
+ def directive_for(deps:)
29
+ config = deps.config
30
+ # goal_mode? yüklü olmayan test double'ları (ShellDeps method_missing
31
+ # raise eder) yapısal olarak dışla — non-goal muamelesi görsünler.
32
+ return nil unless deps.respond_to?(:goal_mode?) && deps.goal_mode?
33
+
34
+ enabled = Ledger.enabled?(config)
35
+ unless enabled
36
+ if config_get(config, "goal.ledger.observe_projection", true) && deps.respond_to?(:runtime_manager)
37
+ dmesg = Rubik::Coeffects.get(deps.runtime_manager&.context, Rubik::Coeffects::DMESG)
38
+ if dmesg.respond_to?(:log)
39
+ dmesg.log(:orchestrator, :warn,
40
+ "goal.ledger.observe_projection is ON but goal.ledger.enabled is OFF — observation projection inert")
41
+ end
42
+ end
43
+ return nil
44
+ end
45
+ return nil unless config_get(config, "goal.ledger.observe_projection", true)
46
+
47
+ ledger = Rubik::Coeffects.get(deps.runtime_manager&.context, Rubik::Coeffects::GOAL_LEDGER)
48
+ return nil unless ledger.respond_to?(:rev)
49
+ return nil unless ledger.rev.is_a?(Integer) && ledger.rev >= 1
50
+
51
+ window = config_get(config, "goal.ledger.observe_window", 5).to_i
52
+ Directive.new(ledger: ledger, window: [window, 1].max, deps: deps)
53
+ end
54
+
55
+ def config_get(config, key, default)
56
+ return default if config.nil?
57
+
58
+ val = if config.is_a?(Hash)
59
+ config.dig(*key.to_s.split("."))
60
+ elsif config.respond_to?(:fetch)
61
+ config.fetch(key, nil)
62
+ end
63
+ val.nil? ? default : val
64
+ end
65
+ end
66
+
67
+ # Kural: anchor = en yeni KABUL edilmiş update_goal_ledger tool satırını
68
+ # içeren atomic unit (model o çağrıyı yaparken önceki tüm observation'ları
69
+ # görmüştü). Anchor'dan önceki birimler = banked; son K assistant-taşıyan
70
+ # birim + anchor + sonrası daima raw. Banked bölgedeki tool satırlarının
71
+ # content'i tombstone ile değişir; diğer anahtarlar bit-bit korunur.
72
+ class ObservationProjection::Directive
73
+ def initialize(ledger:, window:, deps:)
74
+ @ledger = ledger
75
+ @window = window
76
+ @deps = deps
77
+ end
78
+
79
+ attr_reader :rev
80
+
81
+ # -> {count:, rev:, reclaimed_tokens:} (rows KOPYASI üzerinde çalışır)
82
+ def apply!(rows)
83
+ before = Prompt::TokenEstimator.estimate_history(rows)
84
+ units = Prompt::ContextCompactor.group_atomic_units(rows)
85
+ anchor_idx = anchor_unit_index(units)
86
+ count = 0
87
+ rev_now = @ledger.rev
88
+ units.each_with_index do |unit, idx|
89
+ next unless banked?(idx, anchor_idx, units)
90
+ next if Prompt::ContextCompactor.unit_pins_goal?(unit) # <user_task> taşıyan birim asla emilmez
91
+
92
+ unit.each do |row|
93
+ count += 1 if tombstone!(row, rev_now)
94
+ end
95
+ end
96
+ return { count: 0, rev: rev_now, reclaimed_tokens: 0 } if count.zero?
97
+
98
+ reclaimed = before - Prompt::TokenEstimator.estimate_history(rows)
99
+ observe(count, rev_now, reclaimed)
100
+ { count: count, rev: rev_now, reclaimed_tokens: reclaimed }
101
+ end
102
+
103
+ private
104
+
105
+ # En yeni accepted anchor biriminin index'i; hiç yoksa nil → no-op
106
+ # (anchor'sız banked bölge tanımsızdır — dürüst iç çekim yapmayız).
107
+ def anchor_unit_index(units)
108
+ units.rindex { |unit| unit.any? { |row| anchor_row?(row) } }
109
+ end
110
+
111
+ def anchor_row?(row)
112
+ return false unless Prompt::ConversationRows.row?(row)
113
+
114
+ Prompt::ConversationRows.role_of(row) == "tool" &&
115
+ tool_name_of(row) == ObservationProjection::MODULE_NAME &&
116
+ row[:status].to_s != "error" && row["status"].to_s != "error"
117
+ end
118
+
119
+ # Recency: son K assistant-taşıyan birimin İLKİnden itibaren her şey
120
+ # (ve anchor'u ve sonrası) raw kalır; banked = idx < anchor VE
121
+ # idx < recency_start.
122
+ def banked?(idx, anchor_idx, units)
123
+ return false if anchor_idx.nil? || idx >= anchor_idx
124
+
125
+ assistant_units = units.each_index.select { |i| units[i].any? { |r| assistant_row?(r) } }
126
+ recency_start = assistant_units[[assistant_units.size - @window, 0].max] || units.size
127
+ idx < recency_start
128
+ end
129
+
130
+ def assistant_row?(row)
131
+ Prompt::ConversationRows.row?(row) && Prompt::ConversationRows.role_of(row) == "assistant"
132
+ end
133
+
134
+ # Muafiyetler: ledger tool satırları (Σ denetim izi), skill gövdeleri,
135
+ # goal-pinned birimler, user/assistant/system satırları (kural gereği
136
+ # zaten aday değil), hâlihazırda tombstone taşıyanlar (idempotence).
137
+ def tombstone!(row, rev_now)
138
+ return false unless Prompt::ConversationRows.row?(row)
139
+
140
+ role = Prompt::ConversationRows.role_of(row)
141
+ return false unless ["tool", "tool_result"].include?(role)
142
+
143
+ name = tool_name_of(row)
144
+ return false if name == ObservationProjection::MODULE_NAME
145
+ return false if name == ObservationProjection::SKILL_TOOL_NAME
146
+ return false if row[:status].to_s == "error" || row["status"].to_s == "error"
147
+
148
+ key = Prompt::ConversationRows.content_key_of(row)
149
+ content = row[key].to_s
150
+ return false if content.start_with?("<absorbed_into_ledger")
151
+
152
+ row[key] = %(<absorbed_into_ledger rev="#{rev_now}" tool="#{Rubik::Platform.xml_escape(name.to_s)}" />)
153
+ true
154
+ end
155
+
156
+ def tool_name_of(row)
157
+ (row[:tool_name] || row["tool_name"]).to_s
158
+ end
159
+
160
+ # Sessizlik sözleşmesi: tek gözlemlenebilirlik on_step
161
+ # (:ledger_absorb_drop — StepFormatter bilinmeyen tipte nil döner →
162
+ # UI sessiz) + dmesg ring kaydı. Notice/event_bus/wake YOK.
163
+ def observe(count, rev_now, reclaimed)
164
+ @deps.on_step&.call(type: :ledger_absorb_drop, count: count, rev: rev_now,
165
+ reclaimed_tokens: reclaimed,
166
+ step: @deps.respond_to?(:step_count) ? @deps.step_count : nil,
167
+ max_steps: @deps.respond_to?(:max_steps) ? @deps.max_steps : nil)
168
+ dmesg = Rubik::Coeffects.get(@deps.runtime_manager&.context, Rubik::Coeffects::DMESG)
169
+ if dmesg.respond_to?(:log)
170
+ dmesg.log(:orchestrator, :info,
171
+ "goal ledger absorbed #{count} observation row(s) (rev=#{rev_now}, ~#{reclaimed} tokens reclaimed)")
172
+ end
173
+ rescue StandardError => e
174
+ Rubik::Log.warn(:orchestrator, "observation projection observability failed (#{e.class}: #{e.message})")
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end