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,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require_relative "record"
5
+
6
+ module Rubik
7
+ module Kernel
8
+ # Bounded circular ring buffer for monotonic kernel event telemetry (Plan 102).
9
+ # Zero memory growth: pre-allocated array overwritten in O(1) time.
10
+ class Dmesg
11
+ DEFAULT_CAPACITY = 2048
12
+
13
+ attr_reader :capacity, :seq_counter, :start_time
14
+
15
+ def initialize(capacity: DEFAULT_CAPACITY)
16
+ @capacity = [capacity.to_i, 1].max
17
+ @buffer = Array.new(@capacity)
18
+ @head = 0
19
+ @size = 0
20
+ @seq_counter = 0
21
+ @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
+ @monitor = Monitor.new
23
+ end
24
+
25
+ def size
26
+ @monitor.synchronize { @size }
27
+ end
28
+
29
+ def empty?
30
+ @monitor.synchronize { @size.zero? }
31
+ end
32
+
33
+ def log(subsystem, level, message, facility_id: nil, **data)
34
+ @monitor.synchronize do
35
+ @seq_counter += 1
36
+ uptime = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time
37
+ norm_sub = subsystem.to_sym
38
+ norm_lvl = Rubik::Kernel.normalize_level(level)
39
+
40
+ record = Record.new(
41
+ seq: @seq_counter,
42
+ uptime: uptime,
43
+ subsystem: norm_sub,
44
+ level: norm_lvl,
45
+ facility_id: facility_id,
46
+ message: message.to_s,
47
+ data: data
48
+ )
49
+
50
+ @buffer[@head] = record
51
+ @head = (@head + 1) % @capacity
52
+ @size += 1 if @size < @capacity
53
+ record
54
+ end
55
+ end
56
+
57
+ def tail(limit = 50, level: nil, subsystem: nil, since_seq: nil)
58
+ @monitor.synchronize do
59
+ records = snapshot_unlocked
60
+ records = filter_since(records, since_seq) if since_seq
61
+ records = filter_subsystem(records, subsystem) if subsystem
62
+ records = filter_level(records, level) if level
63
+ limit ? records.last(limit.to_i) : records
64
+ end
65
+ end
66
+
67
+ def to_a
68
+ @monitor.synchronize { snapshot_unlocked }
69
+ end
70
+
71
+ def clear!
72
+ @monitor.synchronize do
73
+ @buffer.fill(nil)
74
+ @head = 0
75
+ @size = 0
76
+ self
77
+ end
78
+ end
79
+
80
+ def formatted(records = nil, color: false)
81
+ recs = records || tail
82
+ return "" if recs.empty?
83
+
84
+ recs.map { |r| Rubik::Kernel.format_line(r, color: color) }.join("\n")
85
+ end
86
+
87
+ private
88
+
89
+ def snapshot_unlocked
90
+ return [] if @size.zero?
91
+
92
+ start_idx = @size < @capacity ? 0 : @head
93
+ Array.new(@size) { |i| @buffer[(start_idx + i) % @capacity] }
94
+ end
95
+
96
+ def filter_since(records, since_seq)
97
+ seq_threshold = since_seq.to_i
98
+ records.select { |r| r.seq > seq_threshold }
99
+ end
100
+
101
+ def filter_subsystem(records, subsystem)
102
+ subs = Array(subsystem).flat_map { |s| s.to_s.split(",") }.map(&:strip).map(&:downcase)
103
+ records.select { |r| subs.include?(r.subsystem.to_s.downcase) }
104
+ end
105
+
106
+ def filter_level(records, level)
107
+ if level.is_a?(Integer)
108
+ records.select { |r| SEVERITY_LEVELS.fetch(r.level, 7) <= level }
109
+ else
110
+ levels = Array(level).flat_map { |l| l.to_s.split(",") }.to_set { |l| Rubik::Kernel.normalize_level(l) }
111
+ records.select { |r| levels.include?(r.level) }
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require_relative "unit"
5
+
6
+ module Rubik
7
+ module Kernel
8
+ # Root lifecycle supervisor (PID 1) governing all kernel execution units (Plan 104).
9
+ # Single source of truth for background execution, zombie reaping, and LIFO shutdown.
10
+ class Init
11
+ attr_reader :context, :dmesg, :dispatcher
12
+
13
+ def initialize(context: nil, dmesg: nil, dispatcher: nil)
14
+ @context = context
15
+ @dmesg = dmesg
16
+ @dispatcher = dispatcher
17
+ @units = {}
18
+ @order = []
19
+ @monitor = Monitor.new
20
+ @stopped = false
21
+
22
+ bind_context(@context)
23
+ end
24
+
25
+ def register(unit)
26
+ @monitor.synchronize do
27
+ raise ArgumentError, "Unit cannot be nil" if unit.nil?
28
+ raise Unit::StateError, "Init supervisor is stopped" if @stopped
29
+
30
+ unit.dmesg ||= @dmesg if unit.respond_to?(:dmesg=)
31
+ unit.dispatcher ||= @dispatcher if unit.respond_to?(:dispatcher=)
32
+
33
+ @units[unit.id] = unit
34
+ @order << unit.id unless @order.include?(unit.id)
35
+ @dmesg&.log(:kernel, :info, "registered unit:#{unit.id} type:#{unit.type}", facility_id: "init", unit_id: unit.id)
36
+ unit
37
+ end
38
+ end
39
+ alias spawn register
40
+
41
+ def get(id)
42
+ @monitor.synchronize do
43
+ unit = @units[id.to_s]
44
+ unit&.sync_state! if unit.respond_to?(:sync_state!)
45
+ unit
46
+ end
47
+ end
48
+ alias [] get
49
+
50
+ def units
51
+ @monitor.synchronize do
52
+ @units.each_value { |u| u.sync_state! if u.respond_to?(:sync_state!) }
53
+ @units.values.dup
54
+ end
55
+ end
56
+
57
+ def units_by_state(*states)
58
+ target_states = states.flatten.map(&:to_sym)
59
+ @monitor.synchronize do
60
+ @units.values.select do |u|
61
+ u.sync_state! if u.respond_to?(:sync_state!)
62
+ target_states.include?(u.state)
63
+ end
64
+ end
65
+ end
66
+
67
+ def size
68
+ @monitor.synchronize { @units.size }
69
+ end
70
+
71
+ def empty?
72
+ @monitor.synchronize { @units.empty? }
73
+ end
74
+
75
+ def reap_dead_units!
76
+ @monitor.synchronize do
77
+ reaped = []
78
+ @units.each_value do |unit|
79
+ unit.sync_state! if unit.respond_to?(:sync_state!)
80
+ next unless unit.dead? && unit.state != :reaped
81
+
82
+ safely_dispose_context(unit)
83
+ unit.transition_to!(:reaped, reason: "reaped")
84
+ reaped << unit
85
+ end
86
+ @dmesg&.log(:kernel, :info, "reaped #{reaped.size} dead units", facility_id: "init") unless reaped.empty?
87
+ reaped
88
+ end
89
+ end
90
+
91
+ def kill(id, reason = "killed")
92
+ @monitor.synchronize do
93
+ unit = @units[id.to_s]
94
+ return false unless unit
95
+
96
+ safely_kill_unit(unit, reason)
97
+ true
98
+ end
99
+ end
100
+
101
+ def kill_all(reason = "killed")
102
+ @monitor.synchronize do
103
+ # LIFO shutdown: reverse chronological order of registered units
104
+ @order.reverse_each do |id|
105
+ unit = @units[id]
106
+ next unless unit
107
+
108
+ safely_kill_unit(unit, reason)
109
+ end
110
+ true
111
+ end
112
+ end
113
+
114
+ def resume_all
115
+ @monitor.synchronize do
116
+ resumed = []
117
+ @units.each_value do |unit|
118
+ unit.sync_state! if unit.respond_to?(:sync_state!)
119
+ next unless unit.paused?
120
+
121
+ begin
122
+ unit.respond_to?(:resume) ? unit.resume : unit.transition_to!(:active, reason: "resumed")
123
+ resumed << unit
124
+ rescue StandardError => e
125
+ @dmesg&.log(:kernel, :warn, "failed to resume unit:#{unit.id}: #{e.message}", facility_id: "init")
126
+ end
127
+ end
128
+ resumed
129
+ end
130
+ end
131
+
132
+ def stop
133
+ @monitor.synchronize do
134
+ return true if @stopped
135
+
136
+ @stopped = true
137
+ end
138
+
139
+ kill_all("init_stopped")
140
+ reap_dead_units!
141
+ @dmesg&.log(:kernel, :info, "init stopped; all units reaped", facility_id: "init")
142
+ true
143
+ end
144
+ alias dispose stop
145
+
146
+ def stopped?
147
+ @monitor.synchronize { @stopped }
148
+ end
149
+
150
+ private
151
+
152
+ def safely_kill_unit(unit, reason)
153
+ return if unit.dead?
154
+
155
+ if unit.respond_to?(:kill)
156
+ unit.kill(reason)
157
+ elsif unit.active? || unit.paused? || unit.pending?
158
+ unit.transition_to!(:failed, reason: reason)
159
+ end
160
+ rescue StandardError => e
161
+ @dmesg&.log(:kernel, :warn, "error killing unit:#{unit.id}: #{e.message}", facility_id: "init")
162
+ end
163
+
164
+ def safely_dispose_context(unit)
165
+ unit.context&.dispose if unit.context.respond_to?(:dispose)
166
+ rescue StandardError => e
167
+ @dmesg&.log(:kernel, :warn, "error disposing context for unit:#{unit.id}: #{e.message}", facility_id: "init")
168
+ end
169
+
170
+ def bind_context(ctx)
171
+ return unless ctx.respond_to?(:on_dispose)
172
+
173
+ ctx.on_dispose { stop }
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Kernel
5
+ SEVERITY_LEVELS = {
6
+ emerg: 0,
7
+ alert: 1,
8
+ crit: 2,
9
+ err: 3,
10
+ warn: 4,
11
+ notice: 5,
12
+ info: 6,
13
+ debug: 7
14
+ }.freeze
15
+
16
+ LEVEL_COLORS = {
17
+ emerg: "\e[1;31m",
18
+ alert: "\e[1;31m",
19
+ crit: "\e[1;31m",
20
+ err: "\e[31m",
21
+ warn: "\e[33m",
22
+ notice: "\e[36m",
23
+ info: "\e[32m",
24
+ debug: "\e[90m"
25
+ }.freeze
26
+
27
+ LEVEL_ALIASES = {
28
+ "error" => :err,
29
+ "warning" => :warn,
30
+ "critical" => :crit,
31
+ "emergency" => :emerg,
32
+ "informational" => :info
33
+ }.freeze
34
+
35
+ Record = Data.define(:seq, :uptime, :subsystem, :level, :facility_id, :message, :data) do
36
+ def format_line(color: false)
37
+ Rubik::Kernel.format_line(self, color: color)
38
+ end
39
+ alias_method :formatted, :format_line
40
+ alias_method :to_s, :format_line
41
+
42
+ def self.format_line(record, color: false)
43
+ Rubik::Kernel.format_line(record, color: color)
44
+ end
45
+ end
46
+
47
+ def self.normalize_level(lvl)
48
+ key = lvl.to_s.downcase.strip
49
+ sym = LEVEL_ALIASES.fetch(key, key.to_sym)
50
+ SEVERITY_LEVELS.key?(sym) ? sym : :info
51
+ end
52
+
53
+ def self.format_line(record, color: false)
54
+ uptime_str = format("[%+12.6f]", record.uptime)
55
+ fac = record.facility_id.to_s
56
+ tag = fac.empty? ? "[#{record.subsystem}]" : "[#{record.subsystem}/#{fac}]"
57
+ lvl_str = "#{record.level.to_s.upcase}:"
58
+ lvl_fmt = color ? "#{LEVEL_COLORS[record.level.to_sym] || ''}#{lvl_str}\e[0m" : lvl_str
59
+ "#{uptime_str} #{tag} #{lvl_fmt} #{record.message}#{payload_suffix(record)}"
60
+ end
61
+
62
+ # Session review B1: bus events land as message="event:<name>" with the
63
+ # facts (which server, why it failed) in `data` — rendered nowhere. Append
64
+ # a bounded key=value summary (6 keys, 160-char tails, flat newlines).
65
+ # safe_utf8 first: payload values carry raw error bodies (binary/UTF-16)
66
+ # and one poisoned record would cost the model the whole dmesg view.
67
+ def self.payload_suffix(record)
68
+ data = record.respond_to?(:data) ? record.data : nil
69
+ return "" unless data.is_a?(Hash) && !data.empty?
70
+
71
+ pairs = data.first(6).filter_map do |k, v|
72
+ flat = begin
73
+ Rubik::Platform.safe_utf8(v.to_s)
74
+ rescue StandardError
75
+ next
76
+ end.strip.tr("\r\n", " ").squeeze(" ")
77
+ next if flat.empty?
78
+
79
+ flat.length > 160 ? "#{k}=...#{flat[-160, 160]}" : "#{k}=#{flat}"
80
+ end
81
+ pairs << "(+#{data.size - 6} more)" if data.size > 6
82
+ pairs.empty? ? "" : " #{pairs.join(' ')}"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module Rubik
6
+ module Kernel
7
+ # Kernel execution unit representing an asynchronous computation (Plan 104).
8
+ class Unit
9
+ class StateError < StandardError; end
10
+
11
+ VALID_TYPES = [:subagent, :process, :task, :service].freeze
12
+ VALID_STATES = [:pending, :active, :paused_error, :waiting_budget, :budget_exhausted, :completed, :failed, :reaped].freeze
13
+ TERMINAL_STATES = [:completed, :failed, :reaped].freeze
14
+ PAUSED_STATES = [:paused_error, :waiting_budget, :budget_exhausted].freeze
15
+
16
+ # Dmesg severity by TARGET state (bug-llm-timeout-observability): a
17
+ # stalled unit must surface in `dmesg(level: "err")` — error pauses and
18
+ # failures are err, budget parks are warn, everything else stays info.
19
+ TRANSITION_SEVERITIES = { paused_error: :err, failed: :err, waiting_budget: :warn, budget_exhausted: :warn }.freeze
20
+
21
+ attr_reader :id, :type, :state, :context, :facility_id, :created_at, :started_at, :finished_at, :reason
22
+ attr_accessor :dmesg, :dispatcher
23
+
24
+ def initialize(id:, type:, context: nil, facility_id: nil, state: :pending, dmesg: nil, dispatcher: nil, reason: nil, started_at: nil, finished_at: nil)
25
+ @id = id.to_s
26
+ @type = type.to_sym
27
+ @context = context
28
+ @reason = reason
29
+ @state = state.to_sym
30
+ @facility_id = facility_id || id.to_s
31
+ @created_at = Time.now
32
+ @started_at = started_at || (Time.now if @state == :active)
33
+ @finished_at = finished_at || (Time.now if dead?)
34
+ @dmesg = dmesg
35
+ @dispatcher = dispatcher
36
+ @monitor = Monitor.new
37
+ end
38
+
39
+ def transition_to!(new_state, reason: nil)
40
+ @monitor.synchronize do
41
+ target = new_state.to_sym
42
+ raise ArgumentError, "Invalid state: #{target}" unless VALID_STATES.include?(target)
43
+ return self if @state == target
44
+
45
+ raise StateError, "Terminal :#{@state} cannot transition to :#{target}" if @state == :reaped || ([:completed, :failed].include?(@state) && target != :reaped)
46
+
47
+ old_state = @state
48
+ @state = target
49
+ @reason = reason || @reason
50
+ @started_at ||= Time.now if target == :active
51
+ @finished_at ||= Time.now if TERMINAL_STATES.include?(target)
52
+ notify_transition(old_state, target, reason)
53
+ self
54
+ end
55
+ end
56
+
57
+ def active? = @state == :active
58
+ def running? = active?
59
+ def pending? = @state == :pending
60
+ def paused? = PAUSED_STATES.include?(@state)
61
+ def dead? = TERMINAL_STATES.include?(@state)
62
+ def reaped? = @state == :reaped
63
+ def uptime = @started_at ? ((@finished_at || Time.now) - @started_at).to_f : 0.0
64
+
65
+ def kill(reason = "killed")
66
+ stop_underlying
67
+ transition_to!(:failed, reason: reason) if active? || paused? || pending?
68
+ end
69
+
70
+ def resume(additional_steps: 10)
71
+ resume_underlying(additional_steps)
72
+ transition_to!(:active, reason: "resumed") if paused?
73
+ end
74
+
75
+ def sync_state!
76
+ return @state if dead?
77
+
78
+ new_st = underlying_state
79
+ transition_to!(new_st, reason: underlying_reason) if new_st && new_st != @state
80
+ @state
81
+ end
82
+
83
+ def to_h
84
+ { id: @id, type: @type, state: @state, facility_id: @facility_id, uptime: uptime.round(2),
85
+ created_at: @created_at, started_at: @started_at, finished_at: @finished_at, reason: @reason }
86
+ end
87
+
88
+ private
89
+
90
+ def stop_underlying = nil
91
+ def resume_underlying(_steps) = nil
92
+ def underlying_state = nil
93
+ def underlying_reason = nil
94
+
95
+ def notify_transition(from_state, to_state, reason)
96
+ suffix = " (#{reason})" if reason
97
+ msg = "unit:#{@id} state transition #{from_state} -> #{to_state}#{suffix}"
98
+ level = TRANSITION_SEVERITIES.fetch(to_state, :info)
99
+ subsystem = (@type == :subagent ? :subagent : :kernel)
100
+ @dmesg&.log(subsystem, level, msg, facility_id: @facility_id, unit_id: @id, from_state: from_state, to_state: to_state)
101
+ @dispatcher&.post(:unit_state_changed, unit_id: @id, type: @type, from_state: from_state,
102
+ to_state: to_state, reason: reason, facility_id: @facility_id,
103
+ subsystem: subsystem, level: level)
104
+ end
105
+ end
106
+
107
+ # Unit wrapper for managed child background processes (TaskManager tasks).
108
+ class ProcessUnit < Unit
109
+ STATUS_MAP = { running: :active, completed: :completed, failed: :failed, killed: :failed }.freeze
110
+ attr_reader :task, :task_manager
111
+
112
+ def initialize(task_or_id, task_manager: nil, context: nil, dmesg: nil, dispatcher: nil, **kwargs)
113
+ @task = task_or_id.respond_to?(:id) ? task_or_id : kwargs[:task]
114
+ @task_manager = task_manager
115
+ super(id: @task ? @task.id : task_or_id, type: :process, context: context,
116
+ facility_id: "process-#{@task ? @task.id : task_or_id}",
117
+ state: @task ? STATUS_MAP.fetch(@task.status, :pending) : kwargs.fetch(:state, :pending),
118
+ dmesg: dmesg, dispatcher: dispatcher, started_at: @task&.started_at, finished_at: @task&.ended_at, **kwargs)
119
+ end
120
+
121
+ private
122
+
123
+ def stop_underlying = @task_manager&.kill(@id)
124
+ def underlying_state = (@task ? STATUS_MAP.fetch(@task.status, :pending) : nil)
125
+ def underlying_reason = ("process failed" if @task&.status == :failed)
126
+ end
127
+
128
+ # Unit wrapper for autonomous subagents (Subagent::Instance).
129
+ class SubagentUnit < Unit
130
+ # Instance -> unit symbols, strict and COMPLETE: fetch has no default, unmapped fails
131
+ # loud (contract: keys == Instance::TRANSITIONS.keys; the kernel plane is coarser by design).
132
+ STATE_MAP = {
133
+ pending: :pending, running: :active, waiting_input: :active, waiting_budget: :waiting_budget, budget_exhausted: :budget_exhausted,
134
+ paused_error: :paused_error, canceling: :failed, completed: :completed, failed: :failed, disposed: :failed
135
+ }.freeze
136
+ attr_reader :instance
137
+
138
+ # birth_state: the activation seam's override (activation.rb) — a unit
139
+ # registered while its instance is settled must be born LIVE, never dead.
140
+ def initialize(inst_or_id, context: nil, dmesg: nil, dispatcher: nil, birth_state: nil, **kwargs)
141
+ @instance = inst_or_id.respond_to?(:id) ? inst_or_id : kwargs[:instance]
142
+ super(id: @instance ? @instance.id : inst_or_id, type: :subagent,
143
+ context: context || (@instance.context if @instance.respond_to?(:context)),
144
+ facility_id: "subagent-#{@instance ? @instance.id : inst_or_id}",
145
+ state: birth_state || (@instance ? STATE_MAP.fetch(@instance.state) : kwargs.fetch(:state, :pending)),
146
+ dmesg: dmesg, dispatcher: dispatcher, started_at: @instance&.started_at,
147
+ finished_at: @instance&.ended_at, **kwargs)
148
+ @instance.unit = self if @instance.respond_to?(:unit=)
149
+ end
150
+
151
+ private
152
+
153
+ def stop_underlying = (@instance&.kill if @instance.respond_to?(:kill))
154
+ def resume_underlying(steps) = (@instance.resume!(additional_steps: steps) if @instance.respond_to?(:resume!))
155
+ def underlying_state = (@instance ? STATE_MAP.fetch(@instance.state) : nil)
156
+ # Derived pause reasons (Narration seam), never a stored placeholder.
157
+ def underlying_reason = (@instance.last_error || @instance.result || @instance.state_note if @instance.respond_to?(:last_error))
158
+ end
159
+ end
160
+ end