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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1879 -37
- data/CONTRIBUTING.md +195 -137
- data/README.md +162 -520
- data/SECURITY.md +92 -0
- data/assets/woods-wordmark-white-with-bg.png +0 -0
- data/docs/AGENT_GUIDE.md +204 -0
- data/docs/AGENT_SETUP.md +205 -0
- data/docs/BACKEND_MATRIX.md +470 -0
- data/docs/CONFIGURATION_REFERENCE.md +620 -0
- data/docs/CONSOLE_MCP_SETUP.md +829 -0
- data/docs/DOCKER_SETUP.md +454 -0
- data/docs/EMBEDDING_MODELS.md +136 -0
- data/docs/EVALUATION.md +91 -0
- data/docs/EXTRACTOR_REFERENCE.md +765 -0
- data/docs/FAQ.md +544 -0
- data/docs/GETTING_STARTED.md +183 -0
- data/docs/INCREMENTAL_EXTRACTION.md +415 -0
- data/docs/INTERNALS.md +415 -0
- data/docs/MCP_HTTP_TRANSPORT.md +144 -0
- data/docs/MCP_SERVERS.md +231 -0
- data/docs/MCP_TOOL_COOKBOOK.md +987 -0
- data/docs/MCP_WORKTREE_SETUP.md +127 -0
- data/docs/NOTION_INTEGRATION.md +283 -0
- data/docs/OBSIDIAN_INTEGRATION.md +170 -0
- data/docs/PUBLISHED_INDEX.md +197 -0
- data/docs/README.md +94 -0
- data/docs/RETRIEVAL_GUIDE.md +267 -0
- data/docs/TOKEN_BENCHMARK.md +68 -0
- data/docs/TROUBLESHOOTING.md +841 -0
- data/docs/UNBLOCKED_INTEGRATION.md +279 -0
- data/docs/UPGRADING_TO_2.md +321 -0
- data/docs/WATCH_DAEMON.md +667 -0
- data/docs/WHY_WOODS.md +219 -0
- data/exe/woods-console +39 -3
- data/exe/woods-console-mcp +21 -35
- data/exe/woods-mcp +20 -7
- data/exe/woods-mcp-http +78 -24
- data/exe/woods-mcp-start +57 -52
- data/lib/generators/woods/install_generator.rb +6 -5
- data/lib/generators/woods/pgvector_generator.rb +6 -3
- data/lib/generators/woods/templates/add_pgvector_to_woods.rb.erb +29 -9
- data/lib/generators/woods/templates/create_woods_tables.rb.erb +5 -1
- data/lib/generators/woods/templates/woods.rb.tt +49 -28
- data/lib/tasks/woods.rake +622 -168
- data/lib/tasks/woods_checks.rake +107 -0
- data/lib/tasks/woods_evaluation.rake +164 -80
- data/lib/woods/ast/call_site_extractor.rb +6 -15
- data/lib/woods/ast/method_extractor.rb +19 -9
- data/lib/woods/ast/parser.rb +54 -8
- data/lib/woods/atomic_file.rb +40 -1
- data/lib/woods/builder.rb +310 -22
- data/lib/woods/cache/cache_middleware.rb +18 -13
- data/lib/woods/cache/cache_store.rb +9 -1
- data/lib/woods/cache/solid_cache_store.rb +6 -4
- data/lib/woods/change_set.rb +88 -0
- data/lib/woods/checks/generation_resolution.rb +34 -0
- data/lib/woods/checks/moved_messages.rb +186 -0
- data/lib/woods/chunking/semantic_chunker.rb +160 -18
- data/lib/woods/console/audit_logger.rb +12 -3
- data/lib/woods/console/bridge_protocol.rb +3 -16
- data/lib/woods/console/connection_manager.rb +51 -136
- data/lib/woods/console/credential_index.rb +5 -53
- data/lib/woods/console/credential_scanner.rb +15 -16
- data/lib/woods/console/dispatch_pipeline.rb +46 -34
- data/lib/woods/console/embedded_executor.rb +806 -257
- data/lib/woods/console/eval_guard.rb +27 -20
- data/lib/woods/console/input_contract.rb +78 -0
- data/lib/woods/console/model_validator.rb +24 -6
- data/lib/woods/console/rack_middleware.rb +62 -63
- data/lib/woods/console/redactor.rb +10 -24
- data/lib/woods/console/safe_context.rb +45 -45
- data/lib/woods/console/scope_predicate_parser.rb +41 -0
- data/lib/woods/console/server.rb +136 -267
- data/lib/woods/console/sql_noise_stripper.rb +20 -51
- data/lib/woods/console/sql_table_scanner.rb +39 -90
- data/lib/woods/console/sql_validator.rb +455 -85
- data/lib/woods/console/table_gate.rb +2 -2
- data/lib/woods/console/tool_specs.rb +462 -88
- data/lib/woods/console/tools/tier1.rb +0 -3
- data/lib/woods/console/tools/tier4.rb +17 -7
- data/lib/woods/coordination/lock_heartbeat.rb +103 -0
- data/lib/woods/coordination/pipeline_lock.rb +263 -53
- data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
- data/lib/woods/db/migrator.rb +3 -9
- data/lib/woods/db/schema_version.rb +47 -2
- data/lib/woods/dependency_graph.rb +898 -64
- data/lib/woods/embedding/fake.rb +138 -0
- data/lib/woods/embedding/indexer.rb +832 -40
- data/lib/woods/embedding/openai.rb +77 -19
- data/lib/woods/embedding/provider.rb +189 -11
- data/lib/woods/embedding/text_preparer.rb +1 -1
- data/lib/woods/embedding/token_counter.rb +0 -7
- data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
- data/lib/woods/evaluation/ablation_executor.rb +67 -0
- data/lib/woods/evaluation/ablation_provenance.rb +38 -0
- data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
- data/lib/woods/evaluation/ablation_runner.rb +173 -0
- data/lib/woods/evaluation/ablation_summary.rb +65 -0
- data/lib/woods/evaluation/ablation_task.rb +66 -0
- data/lib/woods/evaluation/ablation_task_set.rb +77 -0
- data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
- data/lib/woods/evaluation/ablation_worktree.rb +71 -0
- data/lib/woods/evaluation/baseline.rb +60 -0
- data/lib/woods/evaluation/baseline_runner.rb +11 -3
- data/lib/woods/evaluation/evaluator.rb +41 -8
- data/lib/woods/evaluation/query_set.rb +79 -13
- data/lib/woods/evaluation/report_generator.rb +20 -1
- data/lib/woods/export/unit_facts.rb +0 -11
- data/lib/woods/extracted_unit.rb +22 -63
- data/lib/woods/extractor.rb +2503 -192
- data/lib/woods/extractors/action_cable_extractor.rb +9 -4
- data/lib/woods/extractors/ast_source_extraction.rb +20 -2
- data/lib/woods/extractors/caching_extractor.rb +46 -12
- data/lib/woods/extractors/callback_analyzer.rb +39 -9
- data/lib/woods/extractors/component_discovery.rb +123 -0
- data/lib/woods/extractors/concern_extractor.rb +17 -3
- data/lib/woods/extractors/controller_extractor.rb +389 -29
- data/lib/woods/extractors/decorator_extractor.rb +7 -14
- data/lib/woods/extractors/engine_extractor.rb +53 -8
- data/lib/woods/extractors/event_extractor.rb +55 -4
- data/lib/woods/extractors/factory_extractor.rb +49 -11
- data/lib/woods/extractors/graphql_extractor.rb +162 -66
- data/lib/woods/extractors/i18n_extractor.rb +6 -1
- data/lib/woods/extractors/job_extractor.rb +51 -21
- data/lib/woods/extractors/lib_extractor.rb +23 -17
- data/lib/woods/extractors/line_neutralizer.rb +171 -0
- data/lib/woods/extractors/mailer_extractor.rb +9 -1
- data/lib/woods/extractors/manager_extractor.rb +19 -2
- data/lib/woods/extractors/migration_extractor.rb +22 -11
- data/lib/woods/extractors/model_extractor.rb +292 -57
- data/lib/woods/extractors/package_extractor.rb +154 -0
- data/lib/woods/extractors/phlex_extractor.rb +18 -3
- data/lib/woods/extractors/policy_extractor.rb +6 -5
- data/lib/woods/extractors/poro_extractor.rb +13 -14
- data/lib/woods/extractors/pundit_extractor.rb +3 -3
- data/lib/woods/extractors/rails_source_extractor.rb +24 -7
- data/lib/woods/extractors/rake_task_extractor.rb +158 -30
- data/lib/woods/extractors/reference_patterns.rb +38 -0
- data/lib/woods/extractors/route_extractor.rb +58 -2
- data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
- data/lib/woods/extractors/serializer_extractor.rb +3 -4
- data/lib/woods/extractors/service_extractor.rb +11 -1
- data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
- data/lib/woods/extractors/shared_utility_methods.rb +36 -6
- data/lib/woods/extractors/source_nesting.rb +560 -0
- data/lib/woods/extractors/state_machine_extractor.rb +30 -18
- data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
- data/lib/woods/extractors/view_component_extractor.rb +28 -3
- data/lib/woods/extractors/view_engines/erb.rb +17 -3
- data/lib/woods/feedback/gap_detector.rb +9 -3
- data/lib/woods/feedback/store.rb +7 -1
- data/lib/woods/filename_utils.rb +29 -1
- data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
- data/lib/woods/flow_assembler.rb +63 -21
- data/lib/woods/flow_document.rb +1 -0
- data/lib/woods/flow_precomputer.rb +138 -22
- data/lib/woods/gem_mapper.rb +285 -0
- data/lib/woods/generation.rb +185 -0
- data/lib/woods/git_command.rb +38 -0
- data/lib/woods/git_provenance.rb +16 -2
- data/lib/woods/graph_analyzer.rb +408 -34
- data/lib/woods/index_artifact.rb +93 -23
- data/lib/woods/mcp/bearer_auth.rb +92 -22
- data/lib/woods/mcp/bootstrap_state.rb +77 -0
- data/lib/woods/mcp/bootstrapper.rb +582 -77
- data/lib/woods/mcp/config_resolver.rb +66 -6
- data/lib/woods/mcp/errors.rb +60 -0
- data/lib/woods/mcp/index_reader.rb +836 -117
- data/lib/woods/mcp/index_reader_pinning.rb +78 -0
- data/lib/woods/mcp/origin_guard.rb +108 -23
- data/lib/woods/mcp/protocol_policy.rb +98 -0
- data/lib/woods/mcp/provider_probe.rb +45 -6
- data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
- data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
- data/lib/woods/mcp/server.rb +907 -154
- data/lib/woods/mcp/tasks/extension.rb +196 -0
- data/lib/woods/mcp/tasks/request_capture.rb +45 -0
- data/lib/woods/mcp/tasks/store.rb +518 -0
- data/lib/woods/mcp/tool_contract.rb +171 -0
- data/lib/woods/mcp/tool_response_renderer.rb +7 -0
- data/lib/woods/mcp/version_aware_tool_dispatch.rb +3 -9
- data/lib/woods/model_name_cache.rb +19 -1
- data/lib/woods/notion/client.rb +132 -36
- data/lib/woods/notion/exporter.rb +456 -61
- data/lib/woods/notion/mappers/column_mapper.rb +34 -5
- data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
- data/lib/woods/notion/mappers/model_mapper.rb +21 -6
- data/lib/woods/notion/mappers/shared.rb +45 -3
- data/lib/woods/notion/sync_manifest.rb +258 -0
- data/lib/woods/obsidian/errors.rb +6 -0
- data/lib/woods/obsidian/name_mapper.rb +40 -24
- data/lib/woods/obsidian/vault_exporter.rb +103 -36
- data/lib/woods/operator/pipeline_guard.rb +118 -21
- data/lib/woods/operator/status_reporter.rb +20 -3
- data/lib/woods/path_dispatcher.rb +276 -0
- data/lib/woods/payload_store.rb +223 -0
- data/lib/woods/published_index/edge_shaper.rb +61 -0
- data/lib/woods/published_index/generation_catalog.rb +72 -0
- data/lib/woods/published_index/typed_unit_reader.rb +48 -0
- data/lib/woods/published_index.rb +287 -0
- data/lib/woods/railtie.rb +70 -38
- data/lib/woods/railtie_support.rb +167 -0
- data/lib/woods/release.rb +12 -0
- data/lib/woods/reload_policy.rb +206 -0
- data/lib/woods/resilience/circuit_breaker.rb +47 -8
- data/lib/woods/resilience/index_validator.rb +296 -10
- data/lib/woods/resilience/retryable_provider.rb +71 -6
- data/lib/woods/resolved_config.rb +55 -11
- data/lib/woods/retrieval/context_assembler.rb +132 -40
- data/lib/woods/retrieval/query_classifier.rb +25 -6
- data/lib/woods/retrieval/ranker.rb +193 -28
- data/lib/woods/retrieval/search_executor.rb +206 -39
- data/lib/woods/retriever.rb +317 -71
- data/lib/woods/retry_after.rb +22 -2
- data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
- data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
- data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
- data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
- data/lib/woods/ruby_analyzer.rb +21 -5
- data/lib/woods/session_tracer/file_store.rb +138 -19
- data/lib/woods/session_tracer/redis_store.rb +122 -12
- data/lib/woods/session_tracer/session_flow_assembler.rb +54 -11
- data/lib/woods/session_tracer/session_flow_document.rb +52 -6
- data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
- data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
- data/lib/woods/session_tracer/store.rb +14 -1
- data/lib/woods/storage/metadata_store.rb +230 -26
- data/lib/woods/storage/pgvector.rb +180 -22
- data/lib/woods/storage/qdrant.rb +367 -41
- data/lib/woods/storage/snapshotter/metadata.rb +79 -16
- data/lib/woods/storage/snapshotter/vector.rb +128 -17
- data/lib/woods/storage/snapshotter.rb +23 -5
- data/lib/woods/storage/vector_store.rb +49 -8
- data/lib/woods/storage_identity.rb +28 -0
- data/lib/woods/tasks.rb +53 -2
- data/lib/woods/temporal/json_snapshot_store.rb +112 -42
- data/lib/woods/temporal/snapshot_store.rb +139 -42
- data/lib/woods/unblocked/client.rb +119 -17
- data/lib/woods/unblocked/document_builder.rb +34 -2
- data/lib/woods/unblocked/exporter.rb +63 -27
- data/lib/woods/unblocked/rate_limiter.rb +23 -9
- data/lib/woods/unblocked/sync_manifest.rb +16 -8
- data/lib/woods/update_check.rb +24 -1
- data/lib/woods/util/uuid5.rb +124 -0
- data/lib/woods/version.rb +1 -1
- data/lib/woods/watch/daemon.rb +1345 -0
- data/lib/woods/watch/listen_watcher.rb +81 -0
- data/lib/woods/watch/polling_watcher.rb +137 -0
- data/lib/woods/watch/status.rb +169 -0
- data/lib/woods/watch/tree_scan.rb +163 -0
- data/lib/woods/watch/watcher.rb +100 -0
- data/lib/woods.rb +53 -9
- data/plugin/.claude-plugin/plugin.json +18 -0
- data/plugin/hooks/hooks.json +29 -0
- data/plugin/hooks/woods-post-edit.sh +226 -0
- data/plugin/hooks/woods-session-start.sh +77 -0
- data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
- data/plugin/skills/woods-diagnose/SKILL.md +75 -0
- data/plugin/skills/woods-investigate/SKILL.md +39 -0
- data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
- data/plugin/skills/woods-setup/SKILL.md +99 -0
- metadata +102 -30
- data/lib/woods/console/adapter_family.rb +0 -39
- data/lib/woods/console/adapters/cache_adapter.rb +0 -58
- data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
- data/lib/woods/console/adapters/job_adapter.rb +0 -74
- data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
- data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
- data/lib/woods/console/bridge.rb +0 -210
- data/lib/woods/console/credential_scanner_registry.rb +0 -36
- data/lib/woods/console/encrypted_credential_snapshot.rb +0 -16
- data/lib/woods/console/sql_output_policy.rb +0 -535
- data/lib/woods/console/sqlite_read_guard.rb +0 -46
- data/lib/woods/formatting/claude_adapter.rb +0 -98
- data/lib/woods/formatting/generic_adapter.rb +0 -56
- data/lib/woods/formatting/gpt_adapter.rb +0 -64
- data/lib/woods/mcp/http_transport_options.rb +0 -15
- data/lib/woods/mcp/origin_policy.rb +0 -113
- data/lib/woods/notion/mapper.rb +0 -40
- data/lib/woods/observability/health_check.rb +0 -79
- 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
|
data/lib/woods/console/bridge.rb
DELETED
|
@@ -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
|