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,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "symbol"
4
+ require_relative "body_loader"
5
+
6
+ module Rubik
7
+ module AST
8
+ class RustExtractor
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
+ current_impl = nil
26
+ impl_end_line = 0
27
+ i = 0
28
+
29
+ while i < @lines.size
30
+ line = @lines[i]
31
+ line_num = i + 1
32
+ stripped = line.strip
33
+
34
+ current_impl = nil if line_num > impl_end_line
35
+
36
+ if stripped.start_with?("use ")
37
+ symbols << Symbol.new(
38
+ name: stripped.sub(/;$/, ""),
39
+ kind: :import,
40
+ file: @file_path,
41
+ line_start: line_num,
42
+ line_end: line_num,
43
+ signature: stripped
44
+ )
45
+ elsif (match = stripped.match(/^(?:pub(?:\(crate\))?\s+)?(struct|enum|trait)\s+([A-Za-z0-9_]+)/))
46
+ kind = match[1].to_sym
47
+ name = match[2]
48
+ end_line = stripped.end_with?(";") ? line_num : find_closing_brace(i)
49
+ symbols << Symbol.new(
50
+ name: name,
51
+ kind: kind,
52
+ file: @file_path,
53
+ line_start: line_num,
54
+ line_end: end_line,
55
+ signature: line.split("{").first.to_s.strip,
56
+ body_loader: body_loader_for(line_num, end_line)
57
+ )
58
+ elsif (match = stripped.match(/^impl(?:\s*<.*?>)?\s+([A-Za-z0-9_]+(?:\s+for\s+[A-Za-z0-9_]+)?)/))
59
+ current_impl = match[1].split(" for ").last.to_s.strip
60
+ impl_end_line = find_closing_brace(i)
61
+ elsif (match = stripped.match(/^(?:pub(?:\(crate\))?\s+)?(?:async\s+)?fn\s+([A-Za-z0-9_]+)\s*(?:<.*?>)?\s*\((.*?)\)/))
62
+ fn_name = match[1]
63
+ full_name = current_impl ? "#{current_impl}::#{fn_name}" : fn_name
64
+ end_line = stripped.end_with?(";") ? line_num : find_closing_brace(i)
65
+ body_for_calls = extract_lines(line_num, end_line)
66
+ calls = extract_calls(body_for_calls, fn_name)
67
+
68
+ # De-duplicate: the same logical symbol must be emitted exactly once.
69
+ # Without this guard an `impl Trait for X` block that also matched
70
+ # the generic `fn` rule earlier could re-emit a method as a free
71
+ # function, producing a duplicate-symbol collision in the graph.
72
+ next if symbols.any? { |s| s.name == full_name }
73
+
74
+ symbols << Symbol.new(
75
+ name: full_name,
76
+ kind: :function,
77
+ file: @file_path,
78
+ line_start: line_num,
79
+ line_end: end_line,
80
+ signature: line.split("{").first.to_s.strip,
81
+ calls: calls,
82
+ body_loader: body_loader_for(line_num, end_line)
83
+ )
84
+ end
85
+
86
+ i += 1
87
+ end
88
+
89
+ symbols
90
+ end
91
+
92
+ private
93
+
94
+ def extract_calls(body, self_name)
95
+ calls = body.scan(/\b([A-Za-z0-9_]+)(?:!|\s*\()/).flatten.uniq
96
+ calls.reject { |c| c == self_name || ["fn", "pub", "async", "if", "for", "while", "loop", "match", "return", "let", "mut"].include?(c) }
97
+ end
98
+
99
+ def find_closing_brace(start_idx)
100
+ brace_count = 0
101
+ found_open = false
102
+
103
+ (start_idx..(@lines.size - 1)).each do |idx|
104
+ line = @lines[idx]
105
+ line.chars.each do |char|
106
+ if char == "{"
107
+ brace_count += 1
108
+ found_open = true
109
+ elsif char == "}"
110
+ brace_count -= 1
111
+ end
112
+ end
113
+
114
+ return idx + 1 if found_open && brace_count <= 0
115
+ end
116
+
117
+ start_idx + 1
118
+ end
119
+
120
+ def extract_lines(start_line, end_line)
121
+ s_idx = [start_line - 1, 0].max
122
+ e_idx = [end_line - 1, @lines.size - 1].min
123
+ @lines[s_idx..e_idx].join
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module AST
5
+ class Symbol
6
+ attr_reader :name, :kind, :file, :line_start, :line_end, :signature, :calls, :docstring
7
+
8
+ # All string fields are scrubbed to valid UTF-8 at construction: a
9
+ # broken-encoding source file must never leak invalid bytes into JSON
10
+ # payloads (request bodies, cache writes) — they raise
11
+ # JSON::GeneratorError far away from the read that caused them.
12
+ def initialize(name:, kind:, file:, line_start:, line_end:, signature: nil, calls: [], body: nil, docstring: nil, body_loader: nil)
13
+ scrub = self.class.method(:scrub_if_invalid)
14
+ @name = scrub.call(name)
15
+ @kind = kind.to_sym
16
+ @file = file
17
+ @line_start = line_start
18
+ @line_end = line_end
19
+ @signature = scrub.call(signature || name)
20
+ @calls = (calls || []).map { |c| scrub.call(c) }
21
+ @body = scrub.call(body)
22
+ @body_loader = body_loader
23
+ @docstring = scrub.call(docstring)
24
+ @body_mutex = Mutex.new
25
+ end
26
+
27
+ # In-memory symbols carry the extracted body; cache-loaded symbols do
28
+ # not (bodies are excluded from the persistent index to keep it small).
29
+ # Extractors may also defer the body slice via `body_loader:` — the
30
+ # closure runs at most once, on first access, synchronized via @body_mutex.
31
+ # For symbols with neither, read the recorded line range from source on demand.
32
+ def body
33
+ return @body if @body
34
+
35
+ (@body_mutex ||= Mutex.new).synchronize do
36
+ return @body if @body
37
+
38
+ if @body_loader
39
+ loader = @body_loader
40
+ @body_loader = nil
41
+ @body = self.class.scrub_if_invalid(loader.call)
42
+ end
43
+ @body ||= self.class.read_source_range(@file, @line_start, @line_end) if @file && !@line_start.nil?
44
+ @body
45
+ end
46
+ end
47
+
48
+ def self.read_source_range(file, l_start, l_end)
49
+ return nil unless file && l_start && File.exist?(file)
50
+
51
+ lines = File.readlines(file)
52
+ selected = lines[(l_start - 1)..(l_end - 1)] if l_start >= 1
53
+ scrub_if_invalid(selected&.join)
54
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR
55
+ nil
56
+ end
57
+
58
+ # JSON-safe view of a string: nil-safe. ASCII-8BIT (BINARY) tags pass
59
+ # `valid_encoding?` for ANY bytes yet still break JSON.dump, so they are
60
+ # re-tagged UTF-8 first; genuinely invalid sequences are then scrubbed.
61
+ def self.scrub_if_invalid(str)
62
+ return str if str.nil?
63
+
64
+ str = str.dup.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
65
+ str.valid_encoding? ? str : str.scrub
66
+ end
67
+
68
+ # `include_body: false` skips the lazy source read — used by the cache
69
+ # writer, which never persists bodies (reading every indexed file just
70
+ # to drop the result would be pure waste).
71
+ def to_h(include_body: true)
72
+ {
73
+ name: @name,
74
+ kind: @kind,
75
+ file: @file,
76
+ line_start: @line_start,
77
+ line_end: @line_end,
78
+ line_range: [@line_start, @line_end],
79
+ signature: @signature,
80
+ calls: @calls,
81
+ body: include_body ? body : nil,
82
+ docstring: @docstring
83
+ }
84
+ end
85
+
86
+ def self.from_h(hash, default_file = nil)
87
+ l_start = hash["line_start"] || hash[:line_start] || (hash["line_range"] || hash[:line_range])&.first || 1
88
+ l_end = hash["line_end"] || hash[:line_end] || (hash["line_range"] || hash[:line_range])&.last || l_start
89
+ new(
90
+ name: hash["name"] || hash[:name],
91
+ kind: (hash["kind"] || hash[:kind] || :function).to_sym,
92
+ file: hash["file"] || hash[:file] || default_file,
93
+ line_start: l_start,
94
+ line_end: l_end,
95
+ signature: hash["signature"] || hash[:signature],
96
+ calls: hash["calls"] || hash[:calls] || [],
97
+ body: hash["body"] || hash[:body],
98
+ docstring: hash["docstring"] || hash[:docstring]
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../symbol"
4
+ require_relative "../body_loader"
5
+
6
+ module Rubik
7
+ module AST
8
+ module TreeSitter
9
+ class BaseWalker
10
+ include BodyLoader
11
+
12
+ attr_reader :file_path, :code
13
+
14
+ def initialize(file_path, code)
15
+ @file_path = file_path
16
+ @code = code || ""
17
+ @lines = nil
18
+ end
19
+
20
+ def lines
21
+ @lines ||= @code.lines
22
+ end
23
+
24
+ def extract_lines(start_line, end_line)
25
+ return "" if lines.empty?
26
+
27
+ s_idx = [start_line - 1, 0].max
28
+ e_idx = [end_line - 1, lines.size - 1].min
29
+ lines[s_idx..e_idx]&.join || ""
30
+ end
31
+
32
+ def text_for(node)
33
+ return "" unless node
34
+
35
+ @code.byteslice(node.start_byte...node.end_byte) || ""
36
+ end
37
+
38
+ def clean_signature(node, max_length: 300)
39
+ return "" unless node
40
+
41
+ first_line = text_for(node).lines.first.to_s.strip
42
+ clean = first_line.sub(/\{$/, "").strip
43
+ clean.length > max_length ? "#{clean[0...max_length]}..." : clean
44
+ end
45
+
46
+ def line_start(node)
47
+ node ? node.start_position.row + 1 : 1
48
+ end
49
+
50
+ def line_end(node)
51
+ node ? node.end_position.row + 1 : 1
52
+ end
53
+
54
+ def build_symbol(name:, kind:, node:, signature: nil, calls: [], docstring: nil)
55
+ l_start = line_start(node)
56
+ l_end = line_end(node)
57
+ sig = (signature || name).to_s
58
+ sig = "#{sig[0...300]}..." if sig.length > 300
59
+ Symbol.new(
60
+ name: name,
61
+ kind: kind,
62
+ file: @file_path,
63
+ line_start: l_start,
64
+ line_end: l_end,
65
+ signature: sig,
66
+ calls: calls,
67
+ body_loader: body_loader_for(l_start, l_end),
68
+ docstring: docstring
69
+ )
70
+ end
71
+
72
+ # Traverses an AST subtree iteratively to extract called function/method identifiers
73
+ def extract_calls(node, call_kinds = ["call", "call_expression", "invocation"])
74
+ calls = []
75
+ return calls unless node
76
+
77
+ stack = [node]
78
+ while (n = stack.pop)
79
+ if call_kinds.include?(n.kind)
80
+ fn = n.child_by_field_name("function") || n.child_by_field_name("name") || n.named_child(0)
81
+ if fn
82
+ fn_text = text_for(fn).strip
83
+ # Extract clean identifier name (e.g. "fmt.Println" -> "Println", "obj.method" -> "method")
84
+ fn_name = fn_text.split(".").last.to_s.split("::").last.to_s.strip
85
+ calls << fn_name if fn_name && !fn_name.empty?
86
+ end
87
+ end
88
+ (0...n.named_child_count).reverse_each { |i| stack.push(n.named_child(i)) }
89
+ end
90
+ calls.uniq
91
+ end
92
+
93
+ # Helper to extract leading comment or docstring
94
+ def docstring_before(node)
95
+ parent = node&.parent
96
+ return nil unless parent
97
+
98
+ idx = (0...parent.named_child_count).find { |i| parent.named_child(i) == node }
99
+ return nil unless idx&.positive?
100
+
101
+ prev = parent.named_child(idx - 1)
102
+ return nil unless prev&.kind&.include?("comment")
103
+
104
+ text_for(prev).strip
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_walker"
4
+
5
+ module Rubik
6
+ module AST
7
+ module TreeSitter
8
+ class CWalker < BaseWalker
9
+ def walk(root_node)
10
+ symbols = []
11
+ return symbols unless root_node
12
+
13
+ walk_children(root_node, symbols)
14
+ symbols
15
+ end
16
+
17
+ private
18
+
19
+ def walk_children(node, symbols)
20
+ (0...node.named_child_count).each do |i|
21
+ child = node.named_child(i)
22
+ case child.kind
23
+ when "preproc_include"
24
+ walk_include(child, symbols)
25
+ when "type_definition"
26
+ walk_typedef(child, symbols)
27
+ when "struct_specifier"
28
+ walk_struct(child, symbols)
29
+ when "function_definition"
30
+ walk_function(child, symbols)
31
+ else
32
+ walk_children(child, symbols)
33
+ end
34
+ end
35
+ end
36
+
37
+ def walk_include(node, symbols)
38
+ raw = text_for(node).strip
39
+ symbols << build_symbol(
40
+ name: raw,
41
+ kind: :import,
42
+ node: node,
43
+ signature: raw
44
+ )
45
+ end
46
+
47
+ def walk_typedef(node, symbols)
48
+ declarator = node.child_by_field_name("declarator")
49
+ name = if declarator
50
+ text_for(declarator).strip
51
+ else
52
+ type_node = node.child_by_field_name("type")
53
+ text_for(type_node&.child_by_field_name("name")).strip
54
+ end
55
+ return if name.empty?
56
+
57
+ sig = clean_signature(node)
58
+ symbols << build_symbol(
59
+ name: name,
60
+ kind: :struct,
61
+ node: node,
62
+ signature: sig.empty? ? "typedef struct #{name}" : sig
63
+ )
64
+ end
65
+
66
+ def walk_struct(node, symbols)
67
+ name_node = node.child_by_field_name("name")
68
+ return unless name_node
69
+
70
+ name = text_for(name_node).strip
71
+ return if name.empty?
72
+
73
+ sig = "struct #{name}"
74
+ symbols << build_symbol(
75
+ name: name,
76
+ kind: :struct,
77
+ node: node,
78
+ signature: sig
79
+ )
80
+ end
81
+
82
+ def walk_function(node, symbols)
83
+ decl = node.child_by_field_name("declarator")
84
+ return unless decl
85
+
86
+ fn_name = extract_function_name(decl)
87
+ return if fn_name.nil? || fn_name.empty?
88
+
89
+ body = node.child_by_field_name("body")
90
+ calls = extract_calls(body, ["call_expression"])
91
+
92
+ sig = clean_signature(node)
93
+ symbols << build_symbol(
94
+ name: fn_name,
95
+ kind: :function,
96
+ node: node,
97
+ signature: sig.empty? ? "#{fn_name}()" : sig,
98
+ calls: calls
99
+ )
100
+ end
101
+
102
+ def extract_function_name(declarator_node)
103
+ current = declarator_node
104
+ while current
105
+ case current.kind
106
+ when "function_declarator", "pointer_declarator"
107
+ current = current.child_by_field_name("declarator")
108
+ when "identifier"
109
+ return text_for(current).strip
110
+ else
111
+ # Try first identifier child
112
+ ident = current.children.find { |c| c.kind == "identifier" }
113
+ return text_for(ident).strip if ident
114
+
115
+ return text_for(current).strip
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_walker"
4
+
5
+ module Rubik
6
+ module AST
7
+ module TreeSitter
8
+ class GoWalker < BaseWalker
9
+ def walk(root_node)
10
+ symbols = []
11
+ return symbols unless root_node
12
+
13
+ walk_children(root_node, symbols)
14
+ symbols
15
+ end
16
+
17
+ private
18
+
19
+ def walk_children(node, symbols)
20
+ (0...node.named_child_count).each do |i|
21
+ child = node.named_child(i)
22
+ case child.kind
23
+ when "type_declaration"
24
+ walk_type_declaration(child, symbols)
25
+ when "type_spec"
26
+ walk_type_spec(child, symbols)
27
+ when "method_declaration"
28
+ walk_method(child, symbols)
29
+ when "function_declaration"
30
+ walk_function(child, symbols)
31
+ when "import_declaration"
32
+ walk_import(child, symbols)
33
+ else
34
+ walk_children(child, symbols)
35
+ end
36
+ end
37
+ end
38
+
39
+ def walk_type_declaration(node, symbols)
40
+ (0...node.named_child_count).each do |i|
41
+ child = node.named_child(i)
42
+ walk_type_spec(child, symbols) if child.kind == "type_spec"
43
+ end
44
+ end
45
+
46
+ def walk_type_spec(node, symbols)
47
+ name_node = node.child_by_field_name("name")
48
+ return unless name_node
49
+
50
+ name = text_for(name_node)
51
+ type_node = node.child_by_field_name("type")
52
+ kind = case type_node&.kind
53
+ when "struct_type" then :struct
54
+ when "interface_type" then :interface
55
+ else :type
56
+ end
57
+
58
+ sig = "type #{name} #{kind}"
59
+ symbols << build_symbol(
60
+ name: name,
61
+ kind: kind,
62
+ node: node,
63
+ signature: sig
64
+ )
65
+ end
66
+
67
+ def walk_method(node, symbols)
68
+ name_node = node.child_by_field_name("name")
69
+ recv_node = node.child_by_field_name("receiver")
70
+ return unless name_node && recv_node
71
+
72
+ method_name = text_for(name_node)
73
+ recv_text = text_for(recv_node).strip
74
+ full_name = "#{recv_text}.#{method_name}"
75
+
76
+ body = node.child_by_field_name("body")
77
+ calls = extract_calls(body, ["call_expression"])
78
+
79
+ sig_line = clean_signature(node)
80
+ symbols << build_symbol(
81
+ name: full_name,
82
+ kind: :function,
83
+ node: node,
84
+ signature: sig_line.empty? ? full_name : sig_line,
85
+ calls: calls
86
+ )
87
+ end
88
+
89
+ def walk_function(node, symbols)
90
+ name_node = node.child_by_field_name("name")
91
+ return unless name_node
92
+
93
+ fn_name = text_for(name_node)
94
+ body = node.child_by_field_name("body")
95
+ calls = extract_calls(body, ["call_expression"])
96
+
97
+ sig_line = clean_signature(node)
98
+ symbols << build_symbol(
99
+ name: fn_name,
100
+ kind: :function,
101
+ node: node,
102
+ signature: sig_line.empty? ? "func #{fn_name}()" : sig_line,
103
+ calls: calls
104
+ )
105
+ end
106
+
107
+ def walk_import(node, symbols)
108
+ raw = text_for(node).strip
109
+ name = raw.include?("(") ? "import (...)" : raw.lines.first.to_s.strip
110
+ symbols << build_symbol(
111
+ name: name,
112
+ kind: :import,
113
+ node: node,
114
+ signature: name
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_walker"
4
+
5
+ module Rubik
6
+ module AST
7
+ module TreeSitter
8
+ class PythonWalker < BaseWalker
9
+ def walk(root_node)
10
+ symbols = []
11
+ return symbols unless root_node
12
+
13
+ walk_children(root_node, symbols, nil)
14
+ symbols
15
+ end
16
+
17
+ private
18
+
19
+ def walk_children(node, symbols, current_class, depth: 0)
20
+ return if depth > 50
21
+
22
+ (0...node.named_child_count).each do |i|
23
+ child = node.named_child(i)
24
+ case child.kind
25
+ when "class_definition"
26
+ walk_class(child, symbols, depth: depth + 1)
27
+ when "function_definition"
28
+ walk_function(child, symbols, current_class)
29
+ when "import_statement", "import_from_statement"
30
+ walk_import(child, symbols)
31
+ else
32
+ walk_children(child, symbols, current_class, depth: depth + 1)
33
+ end
34
+ end
35
+ end
36
+
37
+ def walk_class(node, symbols, depth: 0)
38
+ name_node = node.child_by_field_name("name")
39
+ return unless name_node
40
+
41
+ class_name = text_for(name_node)
42
+ sig = text_for(node).lines.first.to_s.strip.sub(/:$/, "")
43
+ docstring = extract_docstring(node.child_by_field_name("body"))
44
+
45
+ symbols << build_symbol(
46
+ name: class_name,
47
+ kind: :class,
48
+ node: node,
49
+ signature: sig,
50
+ docstring: docstring
51
+ )
52
+
53
+ body = node.child_by_field_name("body")
54
+ walk_children(body, symbols, class_name, depth: depth + 1) if body
55
+ end
56
+
57
+ def walk_function(node, symbols, current_class)
58
+ name_node = node.child_by_field_name("name")
59
+ return unless name_node
60
+
61
+ fn_name = text_for(name_node)
62
+ full_name = current_class ? "#{current_class}.#{fn_name}" : fn_name
63
+ params = node.child_by_field_name("parameters")
64
+ sig = "def #{fn_name}#{text_for(params)}"
65
+ body = node.child_by_field_name("body")
66
+ calls = extract_calls(body, ["call"])
67
+ docstring = extract_docstring(body)
68
+
69
+ symbols << build_symbol(
70
+ name: full_name,
71
+ kind: :function,
72
+ node: node,
73
+ signature: sig,
74
+ calls: calls,
75
+ docstring: docstring
76
+ )
77
+ end
78
+
79
+ def walk_import(node, symbols)
80
+ text = text_for(node).strip
81
+ symbols << build_symbol(
82
+ name: text,
83
+ kind: :import,
84
+ node: node,
85
+ signature: text
86
+ )
87
+ end
88
+
89
+ def extract_docstring(body_node)
90
+ return nil unless body_node&.named_child_count&.positive?
91
+
92
+ first_stmt = body_node.named_child(0)
93
+ return nil unless first_stmt
94
+
95
+ str_node = if first_stmt.kind == "string"
96
+ first_stmt
97
+ elsif first_stmt.kind == "expression_statement" && first_stmt.named_child(0)&.kind == "string"
98
+ first_stmt.named_child(0)
99
+ end
100
+ return nil unless str_node
101
+
102
+ text_for(str_node).gsub(/^["']{3}|["']{3}$/, "").strip
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end