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,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "entry"
4
+ require_relative "parser"
5
+
6
+ module Rubik
7
+ module Diagnostics
8
+ # Unified Diagnostic Fusion Engine (Plan 57 D-3).
9
+ # Fuses LSP compiler diagnostics, static analysis / linter outputs, and test
10
+ # runner error streams into a normalized, deduplicated, priority-ordered model.
11
+ class Collector
12
+ SEVERITY_ORDER = { error: 0, warning: 1, info: 2, hint: 3, unknown: 4 }.freeze
13
+ LSP_SEVERITY_MAP = { 1 => :error, 2 => :warning, 3 => :info, 4 => :hint }.freeze
14
+
15
+ attr_reader :root_dir, :lsp_manager
16
+
17
+ def initialize(lsp_manager: nil, root_dir: Dir.pwd)
18
+ @lsp_manager = lsp_manager
19
+ @root_dir = File.expand_path(root_dir)
20
+ @entries = []
21
+ @mutex = Mutex.new
22
+ end
23
+
24
+ # Clears all collected diagnostics
25
+ def clear!
26
+ @mutex.synchronize { @entries.clear }
27
+ end
28
+
29
+ # Adds a normalized Entry object
30
+ def add_entry(entry)
31
+ return unless entry.is_a?(Entry)
32
+
33
+ @mutex.synchronize { @entries << entry }
34
+ end
35
+
36
+ # Adds multiple Entry objects
37
+ def add_entries(entries)
38
+ return unless entries.is_a?(Array)
39
+
40
+ @mutex.synchronize do
41
+ entries.each { |e| @entries << e if e.is_a?(Entry) }
42
+ end
43
+ end
44
+
45
+ # Parses CLI compiler/linter output and adds entries
46
+ def add_parsed(output, lang: :unknown, source: :compiler) # rubocop:disable Lint/UnusedMethodArgument -- cagiran arayüzüyle uyum için source kwarg'i korunuyor
47
+ parsed = Parser.parse(output, lang)
48
+ @mutex.synchronize do
49
+ parsed.each do |e|
50
+ rel_file = normalize_rel_path(e.file)
51
+ @entries << Entry.new(
52
+ file: rel_file,
53
+ line: e.line,
54
+ col: e.col,
55
+ severity: e.severity || :error,
56
+ code: e.code,
57
+ message: e.message,
58
+ category: e.category,
59
+ raw_text: e.raw_text
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ # Ingests raw LSP diagnostics from a file path
66
+ def add_lsp_diagnostics(file_path, lsp_diags)
67
+ return if lsp_diags.nil? || lsp_diags.empty?
68
+
69
+ rel_file = normalize_rel_path(file_path)
70
+ @mutex.synchronize do
71
+ lsp_diags.each do |d|
72
+ start_pos = d.dig("range", "start") || {}
73
+ line = (start_pos["line"] || 0) + 1
74
+ col = (start_pos["character"] || 0) + 1
75
+ sev = LSP_SEVERITY_MAP[d["severity"]] || :error
76
+ cat = d["severity"] == 1 ? :type : :lint
77
+
78
+ @entries << Entry.new(
79
+ file: rel_file,
80
+ line: line,
81
+ col: col,
82
+ severity: sev,
83
+ code: d["code"],
84
+ message: d["message"].to_s.strip,
85
+ category: cat,
86
+ raw_text: d.to_s
87
+ )
88
+ end
89
+ end
90
+ end
91
+
92
+ # Harvests live diagnostics from the LSP manager across all active servers
93
+ def collect_lsp!(manager = @lsp_manager)
94
+ return [] unless manager.respond_to?(:all_diagnostics)
95
+
96
+ all_diags = manager.all_diagnostics
97
+ all_diags.each do |file_path, diags|
98
+ add_lsp_diagnostics(file_path, diags)
99
+ end
100
+ entries
101
+ end
102
+
103
+ # Returns deduplicated, priority-sorted list of entries
104
+ def entries(max_limit: 50)
105
+ @mutex.synchronize do
106
+ deduped = {}
107
+ @entries.each do |e|
108
+ key = [e.file, e.line, e.col, e.message]
109
+ existing = deduped[key]
110
+ deduped[key] = e if existing.nil? || SEVERITY_ORDER.fetch(e.severity, 4) < SEVERITY_ORDER.fetch(existing.severity, 4)
111
+ end
112
+
113
+ sorted = deduped.values.sort_by do |e|
114
+ [
115
+ SEVERITY_ORDER.fetch(e.severity, 4),
116
+ e.file.to_s,
117
+ e.line.to_i,
118
+ e.col.to_i
119
+ ]
120
+ end
121
+
122
+ sorted.first(max_limit)
123
+ end
124
+ end
125
+
126
+ def errors
127
+ entries.select { |e| e.severity == :error }
128
+ end
129
+
130
+ def warnings
131
+ entries.select { |e| e.severity == :warning }
132
+ end
133
+
134
+ def empty?
135
+ entries.empty?
136
+ end
137
+
138
+ def count
139
+ entries.size
140
+ end
141
+
142
+ def error_count
143
+ errors.size
144
+ end
145
+
146
+ def warning_count
147
+ warnings.size
148
+ end
149
+
150
+ # Formats diagnostics as an XML block for LLM prompt context
151
+ def format_for_context(max_limit: 30)
152
+ current = entries(max_limit: max_limit)
153
+ return "" if current.empty?
154
+
155
+ lines = ["<diagnostics>"]
156
+ current.each do |e|
157
+ loc = e.file ? " file=\"#{e.file}\"" : ""
158
+ loc += " line=\"#{e.line}\"" if e.line
159
+ loc += " col=\"#{e.col}\"" if e.col
160
+ lines << " <issue severity=\"#{e.severity}\" category=\"#{e.category}\"#{loc}>#{escape_xml(e.message)}</issue>"
161
+ end
162
+ lines << "</diagnostics>"
163
+ lines.join("\n")
164
+ end
165
+
166
+ # Formats diagnostics for terminal/REPL display
167
+ def format_for_terminal
168
+ current = entries
169
+ return "No active compiler or linter diagnostics." if current.empty?
170
+
171
+ lines = ["Diagnostic Fusion Summary (#{error_count} error(s), #{warning_count} warning(s)):"]
172
+ current.each do |e|
173
+ icon = e.severity == :error ? "❌ [ERROR]" : "⚠️ [WARN]"
174
+ loc = e.file ? " #{e.file}:#{e.line || 1}" : ""
175
+ loc += ":#{e.col}" if e.col
176
+ lines << " #{icon}#{loc} - #{e.message} (#{e.category})"
177
+ end
178
+ lines.join("\n")
179
+ end
180
+
181
+ private
182
+
183
+ def escape_xml(str)
184
+ str.to_s
185
+ .gsub("&", "&amp;")
186
+ .gsub("<", "&lt;")
187
+ .gsub(">", "&gt;")
188
+ .gsub('"', "&quot;")
189
+ end
190
+
191
+ def normalize_rel_path(path)
192
+ return nil if path.nil? || path.to_s.empty?
193
+
194
+ norm_path = path.to_s.tr("\\", "/")
195
+ norm_root = @root_dir.to_s.tr("\\", "/")
196
+ norm_path.sub(%r{^#{Regexp.escape(norm_root)}/}, "")
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Diagnostics
5
+ class Entry
6
+ CATEGORIES = [:syntax, :type, :undefined_ref, :memory_safety, :test_failure, :lint, :unknown].freeze
7
+
8
+ attr_reader :file, :line, :col, :severity, :code, :message, :category, :raw_text
9
+
10
+ def initialize(file: nil, line: nil, col: nil, severity: :error, code: nil, message: "", category: :unknown, raw_text: "")
11
+ @file = file
12
+ @line = line&.to_i
13
+ @col = col&.to_i
14
+ @severity = severity.to_sym
15
+ @code = code
16
+ @message = message.to_s.strip
17
+ @category = CATEGORIES.include?(category.to_sym) ? category.to_sym : :unknown
18
+ @raw_text = raw_text
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ file: @file,
24
+ line: @line,
25
+ col: @col,
26
+ severity: @severity,
27
+ code: @code,
28
+ message: @message,
29
+ category: @category
30
+ }
31
+ end
32
+
33
+ def format_prompt_snippet(_graph = nil, root_dir = Dir.pwd, buffer_manager = nil)
34
+ lines = []
35
+ lines << "Diagnostic [#{@category.upcase}]: #{@message}"
36
+ lines << "File: #{@file}:#{@line}:#{@col}" if @file && @line
37
+
38
+ file_content_str = if buffer_manager && @file
39
+ buffer_manager.read(@file)
40
+ elsif @file && File.exist?(File.join(root_dir, @file))
41
+ File.read(File.join(root_dir, @file))
42
+ end
43
+
44
+ if file_content_str && @line
45
+ file_lines = file_content_str.lines
46
+ start_line = [@line - 10, 0].max
47
+ end_line = [@line + 10, file_lines.size - 1].min
48
+
49
+ lines << "Context Snippet:"
50
+ (start_line..end_line).each do |idx|
51
+ prefix = (idx + 1) == @line ? "=> " : " "
52
+ lines << "#{prefix}#{idx + 1}: #{file_lines[idx]}"
53
+ end
54
+ end
55
+
56
+ lines.join("\n")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "entry"
4
+
5
+ module Rubik
6
+ module Diagnostics
7
+ class Parser
8
+ def self.parse(output, lang = :unknown)
9
+ new.parse(output, lang)
10
+ end
11
+
12
+ def parse(output, lang = :unknown)
13
+ text = output.to_s
14
+ return [Entry.new(message: "Empty compiler output", category: :unknown)] if text.strip.empty?
15
+
16
+ entries = []
17
+ entries = case lang.to_sym
18
+ when :ruby
19
+ parse_ruby(text)
20
+ when :go
21
+ parse_go(text)
22
+ when :rust
23
+ parse_rust(text)
24
+ when :zig, :c
25
+ parse_zig_c(text)
26
+ else
27
+ parse_generic(text)
28
+ end
29
+
30
+ entries.empty? ? [Entry.new(message: text, category: :unknown, raw_text: text)] : entries
31
+ rescue StandardError => e
32
+ [Entry.new(message: "Parsing failed: #{e.message}\nRaw: #{text}", category: :unknown, raw_text: text)]
33
+ end
34
+
35
+ private
36
+
37
+ def parse_ruby(text)
38
+ entries = []
39
+ text.each_line do |line|
40
+ if (m = line.match(/^([^:]+):(\d+): (syntax error, unexpected .*)$/))
41
+ entries << Entry.new(file: m[1], line: m[2], message: m[3], category: :syntax, raw_text: line)
42
+ elsif (m = line.match(/Failure:\s*([^\n]+)/))
43
+ entries << Entry.new(message: m[1], category: :test_failure, raw_text: line)
44
+ end
45
+ end
46
+ entries
47
+ end
48
+
49
+ def parse_go(text)
50
+ entries = []
51
+ text.each_line do |line|
52
+ if (m = line.match(/^([^:]+):(\d+):(\d+):\s*(undefined:\s*.*)$/))
53
+ entries << Entry.new(file: m[1], line: m[2], col: m[3], message: m[4], category: :undefined_ref, raw_text: line)
54
+ elsif (m = line.match(/^([^:]+):(\d+):(\d+):\s*(.*)$/))
55
+ entries << Entry.new(file: m[1], line: m[2], col: m[3], message: m[4], category: :syntax, raw_text: line)
56
+ end
57
+ end
58
+ entries
59
+ end
60
+
61
+ def parse_rust(text)
62
+ entries = []
63
+ current_error = nil
64
+
65
+ text.each_line do |line|
66
+ if (m = line.match(/^error\[(E\d+)\]:\s*(.*)$/))
67
+ current_error = { code: m[1], message: m[2], category: :type }
68
+ elsif current_error && (m = line.match(/-->\s*([^:]+):(\d+):(\d+)/))
69
+ entries << Entry.new(
70
+ file: m[1],
71
+ line: m[2],
72
+ col: m[3],
73
+ code: current_error[:code],
74
+ message: current_error[:message],
75
+ category: current_error[:category],
76
+ raw_text: line
77
+ )
78
+ current_error = nil
79
+ end
80
+ end
81
+ entries
82
+ end
83
+
84
+ def parse_zig_c(text)
85
+ entries = []
86
+ text.each_line do |line|
87
+ next unless (m = line.match(/^([^:]+):(\d+):(\d+):\s*error:\s*(.*)$/))
88
+
89
+ msg = m[4]
90
+ cat = msg.include?("undeclared identifier") || msg.include?("use of undeclared") ? :undefined_ref : :syntax
91
+ entries << Entry.new(file: m[1], line: m[2], col: m[3], message: msg, category: cat, raw_text: line)
92
+ end
93
+ entries
94
+ end
95
+
96
+ def parse_generic(text)
97
+ entries = []
98
+ text.each_line do |line|
99
+ if (m = line.match(/^([^:\s]+):(\d+):(?:\d+:)?\s*(.*)$/))
100
+ entries << Entry.new(file: m[1], line: m[2], message: m[3], category: :unknown, raw_text: line)
101
+ end
102
+ end
103
+ entries
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "diagnostics/entry"
4
+ require_relative "diagnostics/parser"
5
+ require_relative "diagnostics/collector"
6
+
7
+ module Rubik
8
+ module Diagnostics
9
+ end
10
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require_relative "kernel/dispatcher"
5
+
6
+ module Rubik
7
+ # Event bus facade bridging to Rubik::Kernel::Dispatcher (Plan 103).
8
+ # Provides deterministic FIFO event processing, Dmesg integration, and fault isolation
9
+ # while maintaining 100% backward compatibility for subscribe, unsubscribe, emit, and drain!.
10
+ class EventBus
11
+ attr_reader :dispatcher
12
+
13
+ def initialize(dispatcher: nil, logger: Logger.new($stderr))
14
+ @logger = logger
15
+ @dispatcher = dispatcher || Rubik::Kernel::Dispatcher.new(logger: @logger)
16
+ end
17
+
18
+ def dispatcher=(new_dispatcher)
19
+ return if @dispatcher == new_dispatcher
20
+
21
+ old_dispatcher = @dispatcher
22
+ if old_dispatcher && new_dispatcher && old_dispatcher.respond_to?(:all_subscribers)
23
+ old_dispatcher.all_subscribers.each do |event_name, procs|
24
+ procs.each { |pr| new_dispatcher.subscribe(event_name, &pr) }
25
+ end
26
+ end
27
+ @dispatcher = new_dispatcher
28
+ old_dispatcher&.stop
29
+ end
30
+
31
+ def subscribe(event_name, context: nil, &)
32
+ @dispatcher.subscribe(event_name, context: context, &)
33
+ end
34
+
35
+ def unsubscribe(event_name, listener)
36
+ @dispatcher.unsubscribe(event_name, listener)
37
+ end
38
+
39
+ def post(event_name, payload = {}, subsystem: :kernel, level: :info, facility_id: nil, **)
40
+ @dispatcher.post(event_name, payload, subsystem: subsystem, level: level, facility_id: facility_id, **)
41
+ end
42
+
43
+ def emit(event_name, payload = {}, **)
44
+ post(event_name, payload, **)
45
+ drain!
46
+ end
47
+
48
+ def drain!(timeout = 2.0)
49
+ @dispatcher.sync_drain(timeout)
50
+ end
51
+
52
+ def stop(timeout = 2.0)
53
+ @dispatcher.stop(timeout)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module Rubik
6
+ module Kernel
7
+ # Deterministic sequential FIFO event dispatcher with worker queue (Plan 103).
8
+ # Backed by Thread::Queue and a dedicated kworker daemon.
9
+ # Dispatches events sequentially, logs to Dmesg ring buffer, and guarantees
10
+ # subscriber error isolation.
11
+ class Dispatcher
12
+ STOP_MARKER = :__dispatcher_stop__
13
+
14
+ Event = Struct.new(:name, :payload, :subsystem, :level, :facility_id, keyword_init: true)
15
+ Barrier = Struct.new(:queue)
16
+
17
+ attr_reader :dmesg, :worker_thread
18
+
19
+ def initialize(dmesg: nil, logger: nil)
20
+ @dmesg = dmesg
21
+ @logger = logger
22
+ @queue = Thread::Queue.new
23
+ @listeners = Hash.new { |h, k| h[k] = [] }
24
+ @lock = Monitor.new
25
+ @stopped = false
26
+ @worker_thread = Thread.new { process_queue }
27
+ @worker_thread.name = "rubik-kworker" if @worker_thread.respond_to?(:name=)
28
+ end
29
+
30
+ def post(event_name, payload = {}, subsystem: :kernel, level: :info, facility_id: nil, **kwargs)
31
+ return false if stopped?
32
+
33
+ eff_payload = if kwargs.empty?
34
+ payload || {}
35
+ elsif payload.is_a?(Hash)
36
+ payload.merge(kwargs)
37
+ else
38
+ kwargs
39
+ end
40
+
41
+ @queue.push(
42
+ Event.new(
43
+ name: event_name.to_sym, payload: eff_payload,
44
+ subsystem: subsystem.to_sym, level: level, facility_id: facility_id
45
+ )
46
+ )
47
+ true
48
+ end
49
+
50
+ def subscribe(event_name, context: nil, &block)
51
+ return unless block_given?
52
+
53
+ @lock.synchronize { @listeners[event_name.to_sym] << block }
54
+ context.on_dispose { unsubscribe(event_name, block) } if context.respond_to?(:on_dispose)
55
+ block
56
+ end
57
+
58
+ def unsubscribe(event_name_or_listener, listener = nil)
59
+ @lock.synchronize do
60
+ if listener.nil?
61
+ @listeners.each_value { |list| list.delete(event_name_or_listener) } if event_name_or_listener
62
+ elsif listener
63
+ @listeners[event_name_or_listener.to_sym].delete(listener)
64
+ end
65
+ end
66
+ nil
67
+ end
68
+
69
+ def sync_drain(timeout = 2.0)
70
+ return true if !worker_alive? || Thread.current == @worker_thread
71
+
72
+ barrier = Barrier.new(Thread::Queue.new)
73
+ @queue.push(barrier)
74
+ !barrier.queue.pop(timeout: timeout).nil?
75
+ end
76
+
77
+ def stop(timeout = 2.0)
78
+ @lock.synchronize do
79
+ return true if @stopped
80
+
81
+ @stopped = true
82
+ end
83
+
84
+ sync_drain(timeout) unless Thread.current == @worker_thread
85
+ @queue.push(STOP_MARKER)
86
+ if @worker_thread && Thread.current != @worker_thread
87
+ @worker_thread.join(timeout)
88
+ @worker_thread.kill if @worker_thread.alive?
89
+ @worker_thread = nil
90
+ end
91
+ true
92
+ end
93
+ alias dispose stop
94
+
95
+ def stopped?
96
+ @lock.synchronize { @stopped }
97
+ end
98
+
99
+ def alive?
100
+ !stopped? && worker_alive?
101
+ end
102
+
103
+ def worker_alive?
104
+ !@worker_thread.nil? && @worker_thread.alive?
105
+ end
106
+
107
+ def subscribers(event_name)
108
+ @lock.synchronize { @listeners[event_name.to_sym].dup }
109
+ end
110
+
111
+ def all_subscribers
112
+ @lock.synchronize { @listeners.transform_values(&:dup) }
113
+ end
114
+
115
+ def queue_size
116
+ @queue.size
117
+ end
118
+
119
+ private
120
+
121
+ def process_queue
122
+ loop do
123
+ item = @queue.pop
124
+ break if item == STOP_MARKER
125
+
126
+ if item.is_a?(Barrier)
127
+ item.queue.push(true)
128
+ next
129
+ end
130
+
131
+ dispatch_event(item)
132
+ rescue StandardError => e
133
+ @logger&.error("Dispatcher worker error: #{e.message}")
134
+ end
135
+ end
136
+
137
+ def dispatch_event(event)
138
+ log_to_dmesg(event)
139
+ listeners = @lock.synchronize { @listeners[event.name].dup }
140
+ listeners.each { |l| invoke_listener(l, event) }
141
+ end
142
+
143
+ def invoke_listener(listener, event)
144
+ listener.arity.zero? ? listener.call : listener.call(event.payload)
145
+ rescue StandardError => e
146
+ begin
147
+ @dmesg&.log(:kernel, :err, "Subscriber error on #{event.name}: #{e.message}")
148
+ rescue StandardError
149
+ nil
150
+ end
151
+ @logger&.error("EventBus subscriber error on event '#{event.name}': #{e.message}")
152
+ end
153
+
154
+ def log_to_dmesg(event)
155
+ return unless @dmesg
156
+
157
+ data = event.payload.is_a?(Hash) ? event.payload.dup : { value: event.payload }
158
+ fac_id = data.delete(:facility_id) || event.facility_id
159
+ safe_data = {}
160
+ data.each do |k, v|
161
+ safe_data[k.to_sym] = v
162
+ rescue StandardError
163
+ nil
164
+ end
165
+ @dmesg.log(event.subsystem, event.level, "event:#{event.name}", facility_id: fac_id, **safe_data)
166
+ rescue StandardError => e
167
+ @logger&.error("Dispatcher dmesg logging error: #{e.message}")
168
+ end
169
+ end
170
+ end
171
+ end