woods 1.6.4 → 2.0.0.beta1

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 (282) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1879 -37
  3. data/CONTRIBUTING.md +195 -137
  4. data/README.md +162 -520
  5. data/SECURITY.md +92 -0
  6. data/assets/woods-wordmark-white-with-bg.png +0 -0
  7. data/docs/AGENT_GUIDE.md +204 -0
  8. data/docs/AGENT_SETUP.md +205 -0
  9. data/docs/BACKEND_MATRIX.md +470 -0
  10. data/docs/CONFIGURATION_REFERENCE.md +620 -0
  11. data/docs/CONSOLE_MCP_SETUP.md +829 -0
  12. data/docs/DOCKER_SETUP.md +454 -0
  13. data/docs/EMBEDDING_MODELS.md +136 -0
  14. data/docs/EVALUATION.md +91 -0
  15. data/docs/EXTRACTOR_REFERENCE.md +765 -0
  16. data/docs/FAQ.md +544 -0
  17. data/docs/GETTING_STARTED.md +183 -0
  18. data/docs/INCREMENTAL_EXTRACTION.md +415 -0
  19. data/docs/INTERNALS.md +415 -0
  20. data/docs/MCP_HTTP_TRANSPORT.md +144 -0
  21. data/docs/MCP_SERVERS.md +231 -0
  22. data/docs/MCP_TOOL_COOKBOOK.md +987 -0
  23. data/docs/MCP_WORKTREE_SETUP.md +127 -0
  24. data/docs/NOTION_INTEGRATION.md +283 -0
  25. data/docs/OBSIDIAN_INTEGRATION.md +170 -0
  26. data/docs/PUBLISHED_INDEX.md +197 -0
  27. data/docs/README.md +94 -0
  28. data/docs/RETRIEVAL_GUIDE.md +267 -0
  29. data/docs/TOKEN_BENCHMARK.md +68 -0
  30. data/docs/TROUBLESHOOTING.md +841 -0
  31. data/docs/UNBLOCKED_INTEGRATION.md +279 -0
  32. data/docs/UPGRADING_TO_2.md +321 -0
  33. data/docs/WATCH_DAEMON.md +667 -0
  34. data/docs/WHY_WOODS.md +219 -0
  35. data/exe/woods-console +39 -3
  36. data/exe/woods-console-mcp +21 -35
  37. data/exe/woods-mcp +20 -7
  38. data/exe/woods-mcp-http +78 -24
  39. data/exe/woods-mcp-start +57 -52
  40. data/lib/generators/woods/install_generator.rb +6 -5
  41. data/lib/generators/woods/pgvector_generator.rb +6 -3
  42. data/lib/generators/woods/templates/add_pgvector_to_woods.rb.erb +29 -9
  43. data/lib/generators/woods/templates/create_woods_tables.rb.erb +5 -1
  44. data/lib/generators/woods/templates/woods.rb.tt +49 -28
  45. data/lib/tasks/woods.rake +622 -168
  46. data/lib/tasks/woods_checks.rake +107 -0
  47. data/lib/tasks/woods_evaluation.rake +164 -80
  48. data/lib/woods/ast/call_site_extractor.rb +6 -15
  49. data/lib/woods/ast/method_extractor.rb +19 -9
  50. data/lib/woods/ast/parser.rb +54 -8
  51. data/lib/woods/atomic_file.rb +40 -1
  52. data/lib/woods/builder.rb +310 -22
  53. data/lib/woods/cache/cache_middleware.rb +18 -13
  54. data/lib/woods/cache/cache_store.rb +9 -1
  55. data/lib/woods/cache/solid_cache_store.rb +6 -4
  56. data/lib/woods/change_set.rb +88 -0
  57. data/lib/woods/checks/generation_resolution.rb +34 -0
  58. data/lib/woods/checks/moved_messages.rb +186 -0
  59. data/lib/woods/chunking/semantic_chunker.rb +160 -18
  60. data/lib/woods/console/audit_logger.rb +12 -3
  61. data/lib/woods/console/bridge_protocol.rb +3 -16
  62. data/lib/woods/console/connection_manager.rb +51 -136
  63. data/lib/woods/console/credential_index.rb +5 -53
  64. data/lib/woods/console/credential_scanner.rb +15 -16
  65. data/lib/woods/console/dispatch_pipeline.rb +46 -34
  66. data/lib/woods/console/embedded_executor.rb +806 -257
  67. data/lib/woods/console/eval_guard.rb +27 -20
  68. data/lib/woods/console/input_contract.rb +78 -0
  69. data/lib/woods/console/model_validator.rb +24 -6
  70. data/lib/woods/console/rack_middleware.rb +62 -63
  71. data/lib/woods/console/redactor.rb +10 -24
  72. data/lib/woods/console/safe_context.rb +45 -45
  73. data/lib/woods/console/scope_predicate_parser.rb +41 -0
  74. data/lib/woods/console/server.rb +136 -267
  75. data/lib/woods/console/sql_noise_stripper.rb +20 -51
  76. data/lib/woods/console/sql_table_scanner.rb +39 -90
  77. data/lib/woods/console/sql_validator.rb +455 -85
  78. data/lib/woods/console/table_gate.rb +2 -2
  79. data/lib/woods/console/tool_specs.rb +462 -88
  80. data/lib/woods/console/tools/tier1.rb +0 -3
  81. data/lib/woods/console/tools/tier4.rb +17 -7
  82. data/lib/woods/coordination/lock_heartbeat.rb +103 -0
  83. data/lib/woods/coordination/pipeline_lock.rb +263 -53
  84. data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
  85. data/lib/woods/db/migrator.rb +3 -9
  86. data/lib/woods/db/schema_version.rb +47 -2
  87. data/lib/woods/dependency_graph.rb +898 -64
  88. data/lib/woods/embedding/fake.rb +138 -0
  89. data/lib/woods/embedding/indexer.rb +832 -40
  90. data/lib/woods/embedding/openai.rb +77 -19
  91. data/lib/woods/embedding/provider.rb +189 -11
  92. data/lib/woods/embedding/text_preparer.rb +1 -1
  93. data/lib/woods/embedding/token_counter.rb +0 -7
  94. data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
  95. data/lib/woods/evaluation/ablation_executor.rb +67 -0
  96. data/lib/woods/evaluation/ablation_provenance.rb +38 -0
  97. data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
  98. data/lib/woods/evaluation/ablation_runner.rb +173 -0
  99. data/lib/woods/evaluation/ablation_summary.rb +65 -0
  100. data/lib/woods/evaluation/ablation_task.rb +66 -0
  101. data/lib/woods/evaluation/ablation_task_set.rb +77 -0
  102. data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
  103. data/lib/woods/evaluation/ablation_worktree.rb +71 -0
  104. data/lib/woods/evaluation/baseline.rb +60 -0
  105. data/lib/woods/evaluation/baseline_runner.rb +11 -3
  106. data/lib/woods/evaluation/evaluator.rb +41 -8
  107. data/lib/woods/evaluation/query_set.rb +79 -13
  108. data/lib/woods/evaluation/report_generator.rb +20 -1
  109. data/lib/woods/export/unit_facts.rb +0 -11
  110. data/lib/woods/extracted_unit.rb +22 -63
  111. data/lib/woods/extractor.rb +2503 -192
  112. data/lib/woods/extractors/action_cable_extractor.rb +9 -4
  113. data/lib/woods/extractors/ast_source_extraction.rb +20 -2
  114. data/lib/woods/extractors/caching_extractor.rb +46 -12
  115. data/lib/woods/extractors/callback_analyzer.rb +39 -9
  116. data/lib/woods/extractors/component_discovery.rb +123 -0
  117. data/lib/woods/extractors/concern_extractor.rb +17 -3
  118. data/lib/woods/extractors/controller_extractor.rb +389 -29
  119. data/lib/woods/extractors/decorator_extractor.rb +7 -14
  120. data/lib/woods/extractors/engine_extractor.rb +53 -8
  121. data/lib/woods/extractors/event_extractor.rb +55 -4
  122. data/lib/woods/extractors/factory_extractor.rb +49 -11
  123. data/lib/woods/extractors/graphql_extractor.rb +162 -66
  124. data/lib/woods/extractors/i18n_extractor.rb +6 -1
  125. data/lib/woods/extractors/job_extractor.rb +51 -21
  126. data/lib/woods/extractors/lib_extractor.rb +23 -17
  127. data/lib/woods/extractors/line_neutralizer.rb +171 -0
  128. data/lib/woods/extractors/mailer_extractor.rb +9 -1
  129. data/lib/woods/extractors/manager_extractor.rb +19 -2
  130. data/lib/woods/extractors/migration_extractor.rb +22 -11
  131. data/lib/woods/extractors/model_extractor.rb +292 -57
  132. data/lib/woods/extractors/package_extractor.rb +154 -0
  133. data/lib/woods/extractors/phlex_extractor.rb +18 -3
  134. data/lib/woods/extractors/policy_extractor.rb +6 -5
  135. data/lib/woods/extractors/poro_extractor.rb +13 -14
  136. data/lib/woods/extractors/pundit_extractor.rb +3 -3
  137. data/lib/woods/extractors/rails_source_extractor.rb +24 -7
  138. data/lib/woods/extractors/rake_task_extractor.rb +158 -30
  139. data/lib/woods/extractors/reference_patterns.rb +38 -0
  140. data/lib/woods/extractors/route_extractor.rb +58 -2
  141. data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
  142. data/lib/woods/extractors/serializer_extractor.rb +3 -4
  143. data/lib/woods/extractors/service_extractor.rb +11 -1
  144. data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
  145. data/lib/woods/extractors/shared_utility_methods.rb +36 -6
  146. data/lib/woods/extractors/source_nesting.rb +560 -0
  147. data/lib/woods/extractors/state_machine_extractor.rb +30 -18
  148. data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
  149. data/lib/woods/extractors/view_component_extractor.rb +28 -3
  150. data/lib/woods/extractors/view_engines/erb.rb +17 -3
  151. data/lib/woods/feedback/gap_detector.rb +9 -3
  152. data/lib/woods/feedback/store.rb +7 -1
  153. data/lib/woods/filename_utils.rb +29 -1
  154. data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
  155. data/lib/woods/flow_assembler.rb +63 -21
  156. data/lib/woods/flow_document.rb +1 -0
  157. data/lib/woods/flow_precomputer.rb +138 -22
  158. data/lib/woods/gem_mapper.rb +285 -0
  159. data/lib/woods/generation.rb +185 -0
  160. data/lib/woods/git_command.rb +38 -0
  161. data/lib/woods/git_provenance.rb +16 -2
  162. data/lib/woods/graph_analyzer.rb +408 -34
  163. data/lib/woods/index_artifact.rb +93 -23
  164. data/lib/woods/mcp/bearer_auth.rb +92 -22
  165. data/lib/woods/mcp/bootstrap_state.rb +77 -0
  166. data/lib/woods/mcp/bootstrapper.rb +582 -77
  167. data/lib/woods/mcp/config_resolver.rb +66 -6
  168. data/lib/woods/mcp/errors.rb +60 -0
  169. data/lib/woods/mcp/index_reader.rb +836 -117
  170. data/lib/woods/mcp/index_reader_pinning.rb +78 -0
  171. data/lib/woods/mcp/origin_guard.rb +108 -23
  172. data/lib/woods/mcp/protocol_policy.rb +98 -0
  173. data/lib/woods/mcp/provider_probe.rb +45 -6
  174. data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
  175. data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
  176. data/lib/woods/mcp/server.rb +907 -154
  177. data/lib/woods/mcp/tasks/extension.rb +196 -0
  178. data/lib/woods/mcp/tasks/request_capture.rb +45 -0
  179. data/lib/woods/mcp/tasks/store.rb +518 -0
  180. data/lib/woods/mcp/tool_contract.rb +171 -0
  181. data/lib/woods/mcp/tool_response_renderer.rb +7 -0
  182. data/lib/woods/mcp/version_aware_tool_dispatch.rb +3 -9
  183. data/lib/woods/model_name_cache.rb +19 -1
  184. data/lib/woods/notion/client.rb +132 -36
  185. data/lib/woods/notion/exporter.rb +456 -61
  186. data/lib/woods/notion/mappers/column_mapper.rb +34 -5
  187. data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
  188. data/lib/woods/notion/mappers/model_mapper.rb +21 -6
  189. data/lib/woods/notion/mappers/shared.rb +45 -3
  190. data/lib/woods/notion/sync_manifest.rb +258 -0
  191. data/lib/woods/obsidian/errors.rb +6 -0
  192. data/lib/woods/obsidian/name_mapper.rb +40 -24
  193. data/lib/woods/obsidian/vault_exporter.rb +103 -36
  194. data/lib/woods/operator/pipeline_guard.rb +118 -21
  195. data/lib/woods/operator/status_reporter.rb +20 -3
  196. data/lib/woods/path_dispatcher.rb +276 -0
  197. data/lib/woods/payload_store.rb +223 -0
  198. data/lib/woods/published_index/edge_shaper.rb +61 -0
  199. data/lib/woods/published_index/generation_catalog.rb +72 -0
  200. data/lib/woods/published_index/typed_unit_reader.rb +48 -0
  201. data/lib/woods/published_index.rb +287 -0
  202. data/lib/woods/railtie.rb +70 -38
  203. data/lib/woods/railtie_support.rb +167 -0
  204. data/lib/woods/release.rb +12 -0
  205. data/lib/woods/reload_policy.rb +206 -0
  206. data/lib/woods/resilience/circuit_breaker.rb +47 -8
  207. data/lib/woods/resilience/index_validator.rb +296 -10
  208. data/lib/woods/resilience/retryable_provider.rb +71 -6
  209. data/lib/woods/resolved_config.rb +55 -11
  210. data/lib/woods/retrieval/context_assembler.rb +132 -40
  211. data/lib/woods/retrieval/query_classifier.rb +25 -6
  212. data/lib/woods/retrieval/ranker.rb +193 -28
  213. data/lib/woods/retrieval/search_executor.rb +206 -39
  214. data/lib/woods/retriever.rb +317 -71
  215. data/lib/woods/retry_after.rb +22 -2
  216. data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
  217. data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
  218. data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
  219. data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
  220. data/lib/woods/ruby_analyzer.rb +21 -5
  221. data/lib/woods/session_tracer/file_store.rb +138 -19
  222. data/lib/woods/session_tracer/redis_store.rb +122 -12
  223. data/lib/woods/session_tracer/session_flow_assembler.rb +54 -11
  224. data/lib/woods/session_tracer/session_flow_document.rb +52 -6
  225. data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
  226. data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
  227. data/lib/woods/session_tracer/store.rb +14 -1
  228. data/lib/woods/storage/metadata_store.rb +230 -26
  229. data/lib/woods/storage/pgvector.rb +180 -22
  230. data/lib/woods/storage/qdrant.rb +367 -41
  231. data/lib/woods/storage/snapshotter/metadata.rb +79 -16
  232. data/lib/woods/storage/snapshotter/vector.rb +128 -17
  233. data/lib/woods/storage/snapshotter.rb +23 -5
  234. data/lib/woods/storage/vector_store.rb +49 -8
  235. data/lib/woods/storage_identity.rb +28 -0
  236. data/lib/woods/tasks.rb +53 -2
  237. data/lib/woods/temporal/json_snapshot_store.rb +112 -42
  238. data/lib/woods/temporal/snapshot_store.rb +139 -42
  239. data/lib/woods/unblocked/client.rb +119 -17
  240. data/lib/woods/unblocked/document_builder.rb +34 -2
  241. data/lib/woods/unblocked/exporter.rb +63 -27
  242. data/lib/woods/unblocked/rate_limiter.rb +23 -9
  243. data/lib/woods/unblocked/sync_manifest.rb +16 -8
  244. data/lib/woods/update_check.rb +24 -1
  245. data/lib/woods/util/uuid5.rb +124 -0
  246. data/lib/woods/version.rb +1 -1
  247. data/lib/woods/watch/daemon.rb +1345 -0
  248. data/lib/woods/watch/listen_watcher.rb +81 -0
  249. data/lib/woods/watch/polling_watcher.rb +137 -0
  250. data/lib/woods/watch/status.rb +169 -0
  251. data/lib/woods/watch/tree_scan.rb +163 -0
  252. data/lib/woods/watch/watcher.rb +100 -0
  253. data/lib/woods.rb +53 -9
  254. data/plugin/.claude-plugin/plugin.json +18 -0
  255. data/plugin/hooks/hooks.json +29 -0
  256. data/plugin/hooks/woods-post-edit.sh +226 -0
  257. data/plugin/hooks/woods-session-start.sh +77 -0
  258. data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
  259. data/plugin/skills/woods-diagnose/SKILL.md +75 -0
  260. data/plugin/skills/woods-investigate/SKILL.md +39 -0
  261. data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
  262. data/plugin/skills/woods-setup/SKILL.md +99 -0
  263. metadata +102 -30
  264. data/lib/woods/console/adapter_family.rb +0 -39
  265. data/lib/woods/console/adapters/cache_adapter.rb +0 -58
  266. data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
  267. data/lib/woods/console/adapters/job_adapter.rb +0 -74
  268. data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
  269. data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
  270. data/lib/woods/console/bridge.rb +0 -210
  271. data/lib/woods/console/credential_scanner_registry.rb +0 -36
  272. data/lib/woods/console/encrypted_credential_snapshot.rb +0 -16
  273. data/lib/woods/console/sql_output_policy.rb +0 -535
  274. data/lib/woods/console/sqlite_read_guard.rb +0 -46
  275. data/lib/woods/formatting/claude_adapter.rb +0 -98
  276. data/lib/woods/formatting/generic_adapter.rb +0 -56
  277. data/lib/woods/formatting/gpt_adapter.rb +0 -64
  278. data/lib/woods/mcp/http_transport_options.rb +0 -15
  279. data/lib/woods/mcp/origin_policy.rb +0 -113
  280. data/lib/woods/notion/mapper.rb +0 -40
  281. data/lib/woods/observability/health_check.rb +0 -79
  282. data/lib/woods/observability/instrumentation.rb +0 -34
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Woods
4
- module Console
5
- module Adapters
6
- # Base class for job backend adapters.
7
- #
8
- # Subclasses implement `self.available?` and a private `prefix` method.
9
- # The prefix is used to build bridge tool names (e.g., "sidekiq_queue_stats").
10
- #
11
- # @example
12
- # class MyAdapter < JobAdapter
13
- # def self.available? = !!defined?(::MyQueue)
14
- # private
15
- # def prefix = 'my_queue'
16
- # end
17
- #
18
- class JobAdapter
19
- # Get queue statistics (sizes, latencies).
20
- #
21
- # @return [Hash] Bridge request
22
- def queue_stats
23
- { tool: "#{prefix}_queue_stats", params: {} }
24
- end
25
-
26
- # List recent job failures.
27
- #
28
- # @param limit [Integer] Max failures (default: 10, max: 100)
29
- # @return [Hash] Bridge request
30
- def recent_failures(limit: 10)
31
- limit = [limit, 100].min
32
- { tool: "#{prefix}_recent_failures", params: { limit: limit } }
33
- end
34
-
35
- # Find a job by its ID.
36
- #
37
- # A nil id is dropped from the bridge request so downstream tools see
38
- # a missing parameter rather than an explicit `nil` — symmetric with
39
- # `CacheAdapter.stats(namespace: nil)`.
40
- #
41
- # @param id [Object, nil] Job ID
42
- # @return [Hash] Bridge request
43
- def find_job(id:)
44
- { tool: "#{prefix}_find_job", params: { id: id }.compact }
45
- end
46
-
47
- # List scheduled jobs.
48
- #
49
- # @param limit [Integer] Max jobs (default: 20, max: 100)
50
- # @return [Hash] Bridge request
51
- def scheduled_jobs(limit: 20)
52
- limit = [limit, 100].min
53
- { tool: "#{prefix}_scheduled_jobs", params: { limit: limit } }
54
- end
55
-
56
- # Retry a failed job.
57
- #
58
- # A nil id is dropped from the bridge request — see #find_job.
59
- #
60
- # @param id [Object, nil] Job ID
61
- # @return [Hash] Bridge request
62
- def retry_job(id:)
63
- { tool: "#{prefix}_retry_job", params: { id: id }.compact }
64
- end
65
-
66
- private
67
-
68
- def prefix
69
- raise NotImplementedError, "#{self.class}#prefix must be implemented"
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'job_adapter'
4
-
5
- module Woods
6
- module Console
7
- module Adapters
8
- # Job backend adapter for Sidekiq.
9
- #
10
- # Builds bridge requests for Sidekiq queue stats, failure listing,
11
- # job lookup, scheduled jobs, and retry operations.
12
- #
13
- # @example
14
- # adapter = SidekiqAdapter.new
15
- # adapter.queue_stats # => { tool: 'sidekiq_queue_stats', params: {} }
16
- #
17
- class SidekiqAdapter < JobAdapter
18
- # Check if Sidekiq is available in the current environment.
19
- #
20
- # @return [Boolean]
21
- def self.available?
22
- !!defined?(::Sidekiq)
23
- end
24
-
25
- private
26
-
27
- def prefix
28
- 'sidekiq'
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'job_adapter'
4
-
5
- module Woods
6
- module Console
7
- module Adapters
8
- # Job backend adapter for Solid Queue.
9
- #
10
- # Builds bridge requests for Solid Queue job stats, failure listing,
11
- # job lookup, scheduled jobs, and retry operations.
12
- #
13
- # @example
14
- # adapter = SolidQueueAdapter.new
15
- # adapter.queue_stats # => { tool: 'solid_queue_queue_stats', params: {} }
16
- #
17
- class SolidQueueAdapter < JobAdapter
18
- # Check if Solid Queue is available in the current environment.
19
- #
20
- # @return [Boolean]
21
- def self.available?
22
- !!defined?(::SolidQueue)
23
- end
24
-
25
- private
26
-
27
- def prefix
28
- 'solid_queue'
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require_relative 'bridge_protocol'
5
- require_relative 'model_validator'
6
- require_relative 'safe_context'
7
-
8
- module Woods
9
- module Console
10
- # **PROTOCOL SCAFFOLD — does not execute real queries.** Every handler
11
- # below returns static empty data (`{ 'count' => 0 }`, `{ 'records' =>
12
- # [] }`, etc.). Real in-process execution lives in
13
- # {Woods::Console::EmbeddedExecutor}; the eventual real bridge process
14
- # for Option D will replace this scaffold with a class that performs
15
- # actual ActiveRecord queries.
16
- #
17
- # The scaffold pins the JSON-lines wire protocol — request envelope,
18
- # response envelope, supported-tools list, error shape — so other
19
- # components (EmbeddedExecutor, ConnectionManager, Server) can be
20
- # built and tested against a stable contract before the real
21
- # bridge-process implementation lands. Treat this class the way you'd
22
- # treat a Sinatra fake of a third-party API in tests: it satisfies
23
- # the protocol, nothing more.
24
- #
25
- # ## Why the name carries "Stub"
26
- #
27
- # Round-1 audit Track H-4 flagged this class as a "critical SafeContext
28
- # bypass" because `handle_request` doesn't wrap calls in `SafeContext`.
29
- # That finding wasn't exploitable — no live code path executes through
30
- # this class in the shipped gem — but the bare name `Bridge` made the
31
- # scaffold status invisible to auditors. The `Stub` prefix removes the
32
- # ambiguity. When the real bridge-process implementation is delivered,
33
- # it should claim the `Bridge` name; this class will either be deleted
34
- # (if the protocol is fully owned by the real bridge) or renamed to
35
- # `BridgeProtocol` and reduced to a constants module.
36
- #
37
- # ## Protocol
38
- #
39
- # Reads JSON-lines requests from an input IO, validates model/column names,
40
- # dispatches to (stub) tool handlers, and writes JSON-lines responses to
41
- # an output IO.
42
- #
43
- # Protocol:
44
- # Request: {"id":"req_1","tool":"count","params":{"model":"Order","scope":{"status":"pending"}}}
45
- # Response: {"id":"req_1","ok":true,"result":{"count":1847},"timing_ms":12.3}
46
- # Error: {"id":"req_1","ok":false,"error":"Model not found","error_type":"validation"}
47
- #
48
- # @example Wiring against a fake input/output (testing only — handlers return empty data)
49
- # bridge = StubBridge.new(input: $stdin, output: $stdout,
50
- # model_validator: validator, safe_context: ctx)
51
- # bridge.run
52
- #
53
- class StubBridge
54
- # Protocol constants live on {BridgeProtocol} so the real executor
55
- # (EmbeddedExecutor) and a future real bridge-process class can
56
- # reference them without importing the scaffold. These top-level
57
- # aliases keep `StubBridge::SUPPORTED_TOOLS` working for existing
58
- # callers and specs.
59
- SUPPORTED_TOOLS = BridgeProtocol::SUPPORTED_TOOLS
60
- TIER1_TOOLS = BridgeProtocol::TIER1_TOOLS
61
- TOOL_HANDLERS = BridgeProtocol::TOOL_HANDLERS
62
-
63
- # @param input [IO] Input stream (reads JSON-lines)
64
- # @param output [IO] Output stream (writes JSON-lines)
65
- # @param model_validator [ModelValidator] Validates model/column names
66
- # @param safe_context [SafeContext] Wraps execution in safe transaction
67
- def initialize(input:, output:, model_validator:, safe_context:)
68
- @input = input
69
- @output = output
70
- @model_validator = model_validator
71
- @safe_context = safe_context
72
- end
73
-
74
- # Read loop — processes requests until input is closed.
75
- #
76
- # @return [void]
77
- def run
78
- @input.each_line do |line|
79
- line = line.strip
80
- next if line.empty?
81
-
82
- request = parse_request(line)
83
- next unless request
84
-
85
- response = handle_request(request)
86
- write_response(response)
87
- end
88
- end
89
-
90
- # Process a single request hash and return a response hash.
91
- #
92
- # @param request [Hash] Parsed request with "id", "tool", "params"
93
- # @return [Hash] Response with "id", "ok", and "result" or "error"
94
- def handle_request(request)
95
- id = request['id']
96
- tool = request['tool']
97
- params = request['params'] || {}
98
-
99
- return error_response(id, "Unknown tool: #{tool}", 'unknown_tool') unless SUPPORTED_TOOLS.include?(tool)
100
-
101
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
102
- result = dispatch(tool, params)
103
- elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(1)
104
-
105
- { 'id' => id, 'ok' => true, 'result' => result, 'timing_ms' => elapsed }
106
- rescue ValidationError => e
107
- error_response(id, e.message, 'validation')
108
- rescue StandardError => e
109
- error_response(id, e.message, 'execution')
110
- end
111
-
112
- private
113
-
114
- # Parse a JSON line into a request hash.
115
- #
116
- # @param line [String] Raw JSON line
117
- # @return [Hash, nil] Parsed request or nil on parse error
118
- def parse_request(line)
119
- JSON.parse(line)
120
- rescue JSON::ParserError => e
121
- write_response(error_response(nil, "Invalid JSON: #{e.message}", 'parse'))
122
- nil
123
- end
124
-
125
- # Dispatch a tool request to the appropriate handler.
126
- #
127
- # @param tool [String] Tool name
128
- # @param params [Hash] Tool parameters
129
- # @return [Hash] Tool result
130
- def dispatch(tool, params)
131
- case tool
132
- when 'status'
133
- handle_status
134
- when 'schema'
135
- handle_schema(params)
136
- else
137
- validate_model_param(params)
138
- handler = TOOL_HANDLERS.fetch(tool) { raise ValidationError, "Unknown tool: #{tool}" }
139
- send(handler, params)
140
- end
141
- end
142
-
143
- # Validate that the model parameter is present and known.
144
- def validate_model_param(params)
145
- model = params['model']
146
- raise ValidationError, 'Missing required parameter: model' unless model
147
-
148
- @model_validator.validate_model!(model)
149
- end
150
-
151
- # Stub handlers below return empty/zero data by design — see the
152
- # class-level docstring. Real in-process execution happens in
153
- # EmbeddedExecutor; the eventual Option-D bridge process will replace
154
- # this class entirely.
155
-
156
- def handle_count(_params)
157
- { 'count' => 0 }
158
- end
159
-
160
- def handle_sample(_params)
161
- { 'records' => [] }
162
- end
163
-
164
- def handle_find(_params)
165
- { 'record' => nil }
166
- end
167
-
168
- def handle_pluck(params)
169
- @model_validator.validate_columns!(params['model'], params['columns']) if params['columns']
170
- { 'columns' => Array(params['columns']), 'values' => [] }
171
- end
172
-
173
- def handle_aggregate(params)
174
- @model_validator.validate_column!(params['model'], params['column']) if params['column']
175
- { 'value' => nil }
176
- end
177
-
178
- def handle_association_count(_params)
179
- { 'count' => 0 }
180
- end
181
-
182
- def handle_schema(params)
183
- model = params['model']
184
- raise ValidationError, 'Missing required parameter: model' unless model
185
-
186
- @model_validator.validate_model!(model)
187
- { 'columns' => @model_validator.columns_for(model), 'indexes' => [] }
188
- end
189
-
190
- def handle_recent(_params)
191
- { 'records' => [] }
192
- end
193
-
194
- def handle_status
195
- { 'status' => 'ok', 'models' => @model_validator.model_names }
196
- end
197
-
198
- # Build an error response hash.
199
- def error_response(id, message, error_type)
200
- { 'id' => id, 'ok' => false, 'error' => message, 'error_type' => error_type }
201
- end
202
-
203
- # Write a JSON-line response to the output stream.
204
- def write_response(response)
205
- @output.puts(JSON.generate(response))
206
- @output.flush
207
- end
208
- end
209
- end
210
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Woods
4
- module Console
5
- # Coordinates scanner construction and rotation without retaining servers
6
- # after their transports release them. Requests keep their own index snapshot.
7
- class CredentialScannerRegistry
8
- def initialize
9
- @scanners = ObjectSpace::WeakMap.new
10
- @mutex = Mutex.new
11
- end
12
-
13
- def register
14
- @mutex.synchronize do
15
- scanner = yield
16
- # Older Ruby WeakMap implementations validate the value's liveness
17
- # when enumerating. Keep the scanner in both weak positions so an
18
- # immortal sentinel cannot make an abandoned scanner look live.
19
- @scanners[scanner] = scanner
20
- scanner
21
- end
22
- end
23
-
24
- def rebuild
25
- @mutex.synchronize do
26
- scanners = @scanners.values
27
- return nil if scanners.empty?
28
-
29
- index = yield
30
- scanners.each { |scanner| scanner.replace_index!(index) }
31
- index
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/encrypted_configuration'
4
-
5
- module Woods
6
- module Console
7
- # A private, uncached Rails configuration for one credential-index build.
8
- # Unlike Rails' permissive configuration reader, a missing encrypted file
9
- # must raise during an explicit refresh rather than erase the last index.
10
- class EncryptedCredentialSnapshot < ActiveSupport::EncryptedConfiguration
11
- def read
12
- ActiveSupport::EncryptedFile.instance_method(:read).bind(self).call
13
- end
14
- end
15
- end
16
- end