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,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "symbol"
4
+ require_relative "body_loader"
5
+
6
+ module Rubik
7
+ module AST
8
+ class CExtractor
9
+ include BodyLoader
10
+
11
+ EXCLUDED_CALLS = Set.new(["if", "for", "while", "switch", "return", "sizeof", "malloc", "free", "printf"]).freeze
12
+
13
+ def self.extract(file_path, content = nil)
14
+ new(file_path, content).extract
15
+ end
16
+
17
+ def initialize(file_path, content = nil)
18
+ @file_path = file_path
19
+ @content = content || (File.exist?(file_path) ? File.read(file_path) : "")
20
+ @lines = @content.lines
21
+ end
22
+
23
+ def extract
24
+ symbols = []
25
+ return symbols if @content.empty?
26
+
27
+ i = 0
28
+ while i < @lines.size
29
+ line = @lines[i]
30
+ line_num = i + 1
31
+ stripped = line.strip
32
+
33
+ if stripped.start_with?("#include")
34
+ symbols << Symbol.new(
35
+ name: stripped,
36
+ kind: :import,
37
+ file: @file_path,
38
+ line_start: line_num,
39
+ line_end: line_num,
40
+ signature: stripped
41
+ )
42
+ elsif (match = stripped.match(/^(?:typedef\s+)?(struct|union|enum)\s+([A-Za-z0-9_]+)?\s*\{?/))
43
+ name = match[2] || "anonymous_#{kind}_#{line_num}"
44
+ end_line = stripped.include?(";") && !stripped.include?("{") ? line_num : find_closing_brace(i)
45
+ symbols << Symbol.new(
46
+ name: name,
47
+ kind: match[1].to_sym,
48
+ file: @file_path,
49
+ line_start: line_num,
50
+ line_end: end_line,
51
+ signature: line.split("{").first.to_s.strip,
52
+ body_loader: body_loader_for(line_num, end_line)
53
+ )
54
+ elsif (match = stripped.match(/^([A-Za-z0-9_*\s]+)\s+([A-Za-z0-9_]+)\s*\((.*?)\)\s*\{/))
55
+ fn_name = match[2]
56
+ unless ["if", "for", "while", "switch"].include?(fn_name)
57
+ end_line = find_closing_brace(i)
58
+ body = extract_lines(line_num, end_line)
59
+ calls = extract_calls(body, fn_name)
60
+
61
+ symbols << Symbol.new(
62
+ name: fn_name,
63
+ kind: :function,
64
+ file: @file_path,
65
+ line_start: line_num,
66
+ line_end: end_line,
67
+ signature: line.split("{").first.to_s.strip,
68
+ calls: calls,
69
+ body_loader: body_loader_for(line_num, end_line)
70
+ )
71
+ end
72
+ end
73
+
74
+ i += 1
75
+ end
76
+
77
+ symbols
78
+ end
79
+
80
+ private
81
+
82
+ def extract_calls(body, self_name)
83
+ body.scan(/\b[A-Za-z0-9_]+(?=\s*\()/).uniq.delete_if { |c| c == self_name || EXCLUDED_CALLS.include?(c) }
84
+ end
85
+
86
+ def find_closing_brace(start_idx)
87
+ brace_count = 0
88
+ found_open = false
89
+
90
+ (start_idx..(@lines.size - 1)).each do |idx|
91
+ line = @lines[idx]
92
+ line.each_char do |char|
93
+ if char == "{"
94
+ brace_count += 1
95
+ found_open = true
96
+ elsif char == "}"
97
+ brace_count -= 1
98
+ end
99
+ end
100
+
101
+ return idx + 1 if found_open && brace_count <= 0
102
+ end
103
+
104
+ start_idx + 1
105
+ end
106
+
107
+ def extract_lines(start_line, end_line)
108
+ s_idx = [start_line - 1, 0].max
109
+ e_idx = [end_line - 1, @lines.size - 1].min
110
+ @lines[s_idx..e_idx].join
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "digest"
6
+ require_relative "symbol"
7
+ require_relative "../platform"
8
+
9
+ module Rubik
10
+ module AST
11
+ class CacheStore
12
+ LANGUAGE_MAP = {
13
+ ".rb" => "ruby",
14
+ ".py" => "python",
15
+ ".go" => "go",
16
+ ".rs" => "rust",
17
+ ".zig" => "zig",
18
+ ".c" => "c",
19
+ ".h" => "c",
20
+ ".cpp" => "cpp",
21
+ ".hpp" => "cpp",
22
+ ".cc" => "cpp",
23
+ ".ts" => "typescript",
24
+ ".tsx" => "typescript",
25
+ ".js" => "javascript",
26
+ ".jsx" => "javascript"
27
+ }.freeze
28
+
29
+ attr_reader :root_dir, :home_dir
30
+
31
+ def initialize(root_dir: Dir.pwd, home_dir: nil)
32
+ @root_dir = File.expand_path(root_dir)
33
+ @home_dir = home_dir ? File.expand_path(home_dir) : Rubik::Platform.home_dir
34
+ end
35
+
36
+ def cache_path
37
+ rubik_dir = File.join(@root_dir, ".rubik")
38
+ primary = File.join(rubik_dir, "cache", "ast_index.json")
39
+ return primary if File.directory?(rubik_dir) && (File.writable?(rubik_dir) || (File.exist?(primary) && File.writable?(primary)))
40
+
41
+ project_hash = Digest::SHA256.hexdigest(@root_dir)[0..11]
42
+ File.join(@home_dir, ".rubik", "cache", project_hash, "ast_index.json")
43
+ end
44
+
45
+ def save(file_symbols_hash)
46
+ return false unless file_symbols_hash.is_a?(Hash)
47
+
48
+ data = {
49
+ "version" => 1,
50
+ "last_scan" => Time.now.to_i,
51
+ "files" => {}
52
+ }
53
+
54
+ file_symbols_hash.each do |file_path, syms|
55
+ next if syms.nil? || syms.empty?
56
+
57
+ full_path = File.expand_path(file_path, @root_dir)
58
+
59
+ stat = begin
60
+ File.stat(full_path)
61
+ rescue Errno::ENOENT, Errno::EACCES
62
+ nil
63
+ end
64
+ next unless stat&.file?
65
+
66
+ norm_root = Rubik::Platform.normalize_path_raw(@root_dir)
67
+ norm_full = Rubik::Platform.normalize_path_raw(full_path)
68
+ rel_path = if norm_full.start_with?("#{norm_root}/")
69
+ norm_full.delete_prefix("#{norm_root}/")
70
+ else
71
+ file_path.to_s.tr("\\", "/")
72
+ end
73
+
74
+ ext = File.extname(full_path).downcase
75
+ lang = LANGUAGE_MAP[ext] || "unknown"
76
+
77
+ digest = begin
78
+ Digest::SHA1.file(full_path).hexdigest
79
+ rescue Errno::ENOENT, Errno::EACCES
80
+ nil
81
+ end
82
+
83
+ # Bodies are intentionally NOT persisted: they dominate the cache
84
+ # size and are re-extracted lazily from source via Symbol#body.
85
+ data["files"][rel_path] = {
86
+ "language" => lang,
87
+ "mtime" => stat.mtime.to_i,
88
+ "mtime_f" => stat.mtime.to_f,
89
+ "size" => stat.size,
90
+ "digest" => digest,
91
+ "symbols" => syms.map { |sym| sym.to_h(include_body: false) }
92
+ }
93
+ end
94
+
95
+ target_path = cache_path
96
+ Rubik::Platform.atomic_write(target_path, JSON.generate(data))
97
+ true
98
+ rescue StandardError => e
99
+ Rubik::Log.warn(:ast, "cache save failed (#{e.class}: #{e.message}); next index rebuild starts from scratch")
100
+ false
101
+ end
102
+
103
+ def load
104
+ target_path = cache_path
105
+ return {} unless File.exist?(target_path)
106
+
107
+ content = File.read(target_path)
108
+ data = JSON.parse(content)
109
+ return {} unless data.is_a?(Hash) && data["files"].is_a?(Hash)
110
+
111
+ result = {}
112
+ data["files"].each do |rel_path, cached_info|
113
+ full_path = File.expand_path(rel_path, @root_dir)
114
+ next unless valid_entry?(full_path, cached_info)
115
+
116
+ symbols_raw = cached_info["symbols"] || []
117
+ symbols = symbols_raw.map do |s|
118
+ line_start = s["line_start"] || (s["line_range"] && s["line_range"][0]) || 1
119
+ line_end = s["line_end"] || (s["line_range"] && s["line_range"][1]) || line_start
120
+ Symbol.new(
121
+ name: s["name"],
122
+ kind: (s["kind"] || :function).to_sym,
123
+ file: full_path,
124
+ line_start: line_start,
125
+ line_end: line_end,
126
+ signature: s["signature"],
127
+ calls: s["calls"] || [],
128
+ body: s["body"],
129
+ docstring: s["docstring"]
130
+ )
131
+ end
132
+ result[full_path] = symbols
133
+ end
134
+
135
+ result
136
+ rescue JSON::ParserError, Errno::ENOENT, Errno::EACCES, TypeError, NoMethodError
137
+ # Corrupt-but-valid JSON shapes raise TypeError/NoMethodError inside
138
+ # the entry loop; EACCES covers an unreadable cache file. All mean
139
+ # "cache unusable" → rebuild from scratch.
140
+ {}
141
+ end
142
+
143
+ def valid_entry?(file_path, cached_info)
144
+ return false unless cached_info.is_a?(Hash)
145
+
146
+ full_path = File.expand_path(file_path, @root_dir)
147
+ return false unless File.exist?(full_path) && File.file?(full_path)
148
+
149
+ stat = File.stat(full_path)
150
+ cached_size = (cached_info["size"] || cached_info[:size]).to_i
151
+ return false unless stat.size == cached_size
152
+
153
+ cached_mtime_f = cached_info["mtime_f"]&.to_f
154
+ cached_mtime = (cached_info["mtime"] || cached_info[:mtime]).to_i
155
+
156
+ mtime_matches = if cached_mtime_f
157
+ (stat.mtime.to_f - cached_mtime_f).abs < 0.0001
158
+ else
159
+ stat.mtime.to_i == cached_mtime
160
+ end
161
+
162
+ return false unless mtime_matches
163
+
164
+ cached_digest = cached_info["digest"] || cached_info[:digest]
165
+ return false unless cached_digest
166
+
167
+ current_digest = begin
168
+ Digest::SHA1.file(full_path).hexdigest
169
+ rescue Errno::ENOENT, Errno::EACCES
170
+ nil
171
+ end
172
+ return false unless current_digest == cached_digest
173
+
174
+ true
175
+ rescue SystemCallError, NoMethodError, ArgumentError
176
+ # ArgumentError: a NUL-byte rel_path from a corrupt cache entry is
177
+ # one invalid entry, not a reason to rebuild the whole cache.
178
+ false
179
+ end
180
+ alias valid_cache_entry? valid_entry?
181
+
182
+ def clear!
183
+ target_path = cache_path
184
+ FileUtils.rm_f(target_path)
185
+ true
186
+ rescue SystemCallError
187
+ false
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby_extractor"
4
+ require_relative "go_extractor"
5
+ require_relative "rust_extractor"
6
+ require_relative "zig_extractor"
7
+ require_relative "c_extractor"
8
+ require_relative "python_extractor"
9
+ require_relative "typescript_extractor"
10
+ require_relative "treesitter_extractor"
11
+
12
+ module Rubik
13
+ module AST
14
+ class Extractor
15
+ FALLBACK_EXTRACTORS = {
16
+ ".rb" => RubyExtractor,
17
+ ".go" => GoExtractor,
18
+ ".rs" => RustExtractor,
19
+ ".zig" => ZigExtractor,
20
+ ".c" => CExtractor,
21
+ ".h" => CExtractor,
22
+ ".cpp" => CExtractor,
23
+ ".hpp" => CExtractor,
24
+ ".py" => PythonExtractor,
25
+ ".ts" => TypeScriptExtractor,
26
+ ".tsx" => TypeScriptExtractor,
27
+ ".js" => TypeScriptExtractor,
28
+ ".jsx" => TypeScriptExtractor
29
+ }.freeze
30
+
31
+ EXTENSIONS = FALLBACK_EXTRACTORS
32
+
33
+ def self.extract(file_path, content = nil)
34
+ ext = File.extname(file_path).downcase
35
+
36
+ # 1. Ruby files: always use Prism-powered RubyExtractor first
37
+ return RubyExtractor.extract(file_path, content) if ext == ".rb"
38
+
39
+ # 2. Polyglot files: use Tree-Sitter AST extractor if available
40
+ if TreeSitterExtractor.available? && TreeSitterExtractor.supports?(ext)
41
+ symbols = TreeSitterExtractor.extract(file_path, content)
42
+ return symbols unless symbols.nil?
43
+ end
44
+
45
+ # 3. Graceful Fallback: use legacy regex extractor
46
+ extractor_class = FALLBACK_EXTRACTORS[ext] || RubyExtractor
47
+ extractor_class.extract(file_path, content)
48
+ rescue StandardError, ScriptError => e
49
+ warn "[WARN] Extraction failed for #{file_path}: #{e.class}: #{e.message}" if ENV["RUBIK_DEBUG"]
50
+ # Graceful fallback to empty symbols on syntax/parsing anomalies
51
+ []
52
+ end
53
+
54
+ def self.supported_extensions
55
+ EXTENSIONS.keys
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module AST
5
+ # Quote/comment/heredoc-aware view of Ruby source, used only by
6
+ # RubyExtractor's Prism-less fallback scanner (Plan 54 D-7). This is
7
+ # deliberately a "lexer-lite", not a parser: it blanks string bodies,
8
+ # comments, regex literals, heredoc bodies, =begin/=end blocks,
9
+ # :keyword symbol literals and everything after __END__ so naive
10
+ # end-matching cannot be fooled by them. Known, accepted limits:
11
+ # %-literals (%w[end]), multiple heredocs per line, char classes
12
+ # containing slashes inside regexes, and `x <<identifier` (no space)
13
+ # being read as a heredoc.
14
+ module FallbackCleaner
15
+ SYMBOL_KEYWORDS = /:(?:class|module|def|do|if|unless|case|begin|while|for|until|end)\b/
16
+ HEREDOC_START = /\A<<([~-]?)([A-Za-z_]\w*)/
17
+ REGEX_PRECEDERS = "(=,~!&|{[;:^"
18
+ REGEX_KEYWORDS = ["and", "begin", "case", "do", "else", "elsif", "if", "not", "or", "return", "then", "unless", "until", "while"].freeze
19
+
20
+ module_function
21
+
22
+ def code_lines(raw_lines)
23
+ cleaned = []
24
+ quote = nil
25
+ terminator = nil
26
+ cut = false
27
+
28
+ raw_lines.each do |raw|
29
+ if cut || terminator
30
+ terminator = nil if heredoc_closed?(raw, terminator)
31
+ cleaned << blank(raw)
32
+ elsif raw.match?(/\A\s*=/) # =begin/=end block comments
33
+ cleaned << blank(raw)
34
+ else
35
+ line, quote, terminator = clean_line(raw, quote)
36
+ cleaned << blank_symbol_keywords(line)
37
+ end
38
+
39
+ cut = true if !cut && terminator.nil? && cleaned.last.strip == "__END__"
40
+ end
41
+ cleaned
42
+ end
43
+
44
+ # Cleans one line of code. Returns [cleaned_line, open_quote,
45
+ # heredoc_terminator]; an open quote carries across lines so
46
+ # multi-line string bodies are blanked too. Delimiters are kept,
47
+ # contents are blanked; comments and heredoc openers blank the rest
48
+ # of the line, regex literals blank their span.
49
+ def clean_line(raw, quote)
50
+ out = +""
51
+ i = 0
52
+ while i < raw.length
53
+ ch = raw[i]
54
+ if quote
55
+ if ch == "\\" && i + 1 < raw.length
56
+ out << " "
57
+ i += 2
58
+ next
59
+ end
60
+ quote = nil if ch == quote
61
+ out << (quote ? " " : ch)
62
+ i += 1
63
+ elsif ch == "/" && (fin = regex_span_end(raw, i))
64
+ out << blank(raw[i..fin])
65
+ i = fin + 1
66
+ elsif ch == "?" && "\"'`".include?(raw[i + 1].to_s) # ?" ?' ?` char literals
67
+ out << raw[i, 2]
68
+ i += 2
69
+ else
70
+ case ch
71
+ when "#"
72
+ out << blank(raw[i..])
73
+ return [out, quote, nil]
74
+ when '"', "'", "`"
75
+ quote = ch
76
+ out << ch
77
+ i += 1
78
+ when "<"
79
+ heredoc = heredoc_at(raw, i)
80
+ return [out << blank(raw[i..]), nil, heredoc] if heredoc
81
+
82
+ out << ch
83
+ i += 1
84
+ else
85
+ out << ch
86
+ i += 1
87
+ end
88
+ end
89
+ end
90
+ [out, quote, nil]
91
+ end
92
+
93
+ def blank(str)
94
+ " " * str.length
95
+ end
96
+
97
+ # Returns [terminator_id, indent_allowed] when raw[pos..] opens a
98
+ # heredoc, else nil.
99
+ def heredoc_at(raw, pos)
100
+ m = raw[pos..].match(HEREDOC_START)
101
+ [m[2], m[1] != ""] if m
102
+ end
103
+
104
+ # A plain <<EOS heredoc only terminates at column zero; <<-/<<~
105
+ # allow an indented terminator.
106
+ def heredoc_closed?(raw, terminator)
107
+ id, indent_allowed = terminator
108
+ return false unless raw.strip == id
109
+
110
+ indent_allowed || raw !~ /\A[ \t]/
111
+ end
112
+
113
+ # Index of the closing "/" of a regex literal opened at raw[start],
114
+ # nil when the "/" is division rather than an opener.
115
+ def regex_span_end(raw, start)
116
+ return nil unless regex_context?(raw, start)
117
+
118
+ j = start + 1
119
+ while j < raw.length
120
+ j += 1 if raw[j] == "\\"
121
+ break if raw[j] == "/"
122
+
123
+ j += 1
124
+ end
125
+ [j, raw.length - 1].min
126
+ end
127
+
128
+ # Crude regex-opener detection: a "/" starts a regex when the previous
129
+ # significant char is an operator/opening bracket, line start, or one
130
+ # of the keywords that takes a bare expression.
131
+ def regex_context?(raw, start)
132
+ k = start - 1
133
+ k -= 1 while !k.negative? && " \t\n".include?(raw[k])
134
+ return true if k.negative?
135
+ return true if REGEX_PRECEDERS.include?(raw[k])
136
+
137
+ REGEX_KEYWORDS.include?(raw[0..k][/([A-Za-z_][A-Za-z0-9_]*)\z/])
138
+ end
139
+
140
+ def blank_symbol_keywords(line)
141
+ line.gsub(SYMBOL_KEYWORDS) { |m| " " * m.length }
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "symbol"
4
+ require_relative "body_loader"
5
+
6
+ module Rubik
7
+ module AST
8
+ class GoExtractor
9
+ include BodyLoader
10
+
11
+ def self.extract(file_path, content = nil)
12
+ new(file_path, content).extract
13
+ end
14
+
15
+ def initialize(file_path, content = nil)
16
+ @file_path = file_path
17
+ @content = content || (File.exist?(file_path) ? File.read(file_path) : "")
18
+ @lines = @content.lines
19
+ end
20
+
21
+ def extract
22
+ symbols = []
23
+ return symbols if @content.empty?
24
+
25
+ i = 0
26
+ while i < @lines.size
27
+ line = @lines[i]
28
+ line_num = i + 1
29
+ stripped = line.strip
30
+
31
+ if stripped.start_with?("import ")
32
+ sym, i = parse_import(stripped, line_num, i)
33
+ symbols << sym
34
+ elsif (match = stripped.match(/^type\s+([A-Za-z0-9_]+)\s+(struct|interface)\b/))
35
+ symbols << parse_type(match, line_num, i)
36
+ elsif (match = stripped.match(/^func\s+(?:\(([A-Za-z0-9_*\s]+)\)\s+)?([A-Za-z0-9_]+)\s*\((.*?)\)/))
37
+ symbols << parse_func(line, match, line_num, i)
38
+ end
39
+
40
+ i += 1
41
+ end
42
+
43
+ symbols
44
+ end
45
+
46
+ private
47
+
48
+ def parse_import(stripped, line_num, index)
49
+ if stripped.include?("(")
50
+ end_line = find_closing_paren(index)
51
+ symbol = Symbol.new(
52
+ name: "import (...)",
53
+ kind: :import,
54
+ file: @file_path,
55
+ line_start: line_num,
56
+ line_end: end_line,
57
+ signature: "import (...)"
58
+ )
59
+ [symbol, end_line - 1]
60
+ else
61
+ symbol = Symbol.new(
62
+ name: stripped,
63
+ kind: :import,
64
+ file: @file_path,
65
+ line_start: line_num,
66
+ line_end: line_num,
67
+ signature: stripped
68
+ )
69
+ [symbol, index]
70
+ end
71
+ end
72
+
73
+ def parse_type(match, line_num, index)
74
+ name = match[1]
75
+ kind = match[2] == "struct" ? :struct : :interface
76
+ end_line = find_closing_brace(index)
77
+ Symbol.new(
78
+ name: name,
79
+ kind: kind,
80
+ file: @file_path,
81
+ line_start: line_num,
82
+ line_end: end_line,
83
+ signature: "type #{name} #{match[2]}",
84
+ body_loader: body_loader_for(line_num, end_line)
85
+ )
86
+ end
87
+
88
+ def parse_func(line, match, line_num, index)
89
+ receiver = match[1]&.strip
90
+ fn_name = match[2].strip
91
+ full_name = receiver ? "(#{receiver}).#{fn_name}" : fn_name
92
+ end_line = find_closing_brace(index)
93
+ body_for_calls = extract_lines(line_num, end_line)
94
+ calls = extract_calls(body_for_calls, fn_name)
95
+
96
+ Symbol.new(
97
+ name: full_name,
98
+ kind: :function,
99
+ file: @file_path,
100
+ line_start: line_num,
101
+ line_end: end_line,
102
+ signature: line.split("{").first.to_s.strip,
103
+ calls: calls,
104
+ body_loader: body_loader_for(line_num, end_line)
105
+ )
106
+ end
107
+
108
+ def extract_calls(body, self_name)
109
+ calls = body.scan(/\b([A-Za-z0-9_]+)\s*\(/).flatten.uniq
110
+ calls.reject { |c| c == self_name || ["func", "if", "for", "return", "make", "append", "len", "cap", "panic", "recover"].include?(c) }
111
+ end
112
+
113
+ def find_closing_brace(start_idx)
114
+ brace_count = 0
115
+ found_open = false
116
+
117
+ (start_idx..(@lines.size - 1)).each do |idx|
118
+ line = @lines[idx]
119
+ line.each_char do |char|
120
+ if char == "{"
121
+ brace_count += 1
122
+ found_open = true
123
+ elsif char == "}"
124
+ brace_count -= 1
125
+ end
126
+ end
127
+
128
+ return idx + 1 if found_open && brace_count <= 0
129
+ end
130
+
131
+ start_idx + 1
132
+ end
133
+
134
+ def find_closing_paren(start_idx)
135
+ (start_idx..(@lines.size - 1)).each do |idx|
136
+ return idx + 1 if @lines[idx].include?(")")
137
+ end
138
+ start_idx + 1
139
+ end
140
+
141
+ def extract_lines(start_line, end_line)
142
+ s_idx = [start_line - 1, 0].max
143
+ e_idx = [end_line - 1, @lines.size - 1].min
144
+ @lines[s_idx..e_idx].join
145
+ end
146
+ end
147
+ end
148
+ end