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,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Woods
|
|
6
|
-
module Formatting
|
|
7
|
-
# Formats assembled context as plain text for generic LLM consumption.
|
|
8
|
-
#
|
|
9
|
-
# Produces plain text with:
|
|
10
|
-
# - `=== CODEBASE CONTEXT ===` header
|
|
11
|
-
# - Token usage line
|
|
12
|
-
# - Content separated by `---` dividers
|
|
13
|
-
# - Sources in bracket notation
|
|
14
|
-
#
|
|
15
|
-
# @example
|
|
16
|
-
# adapter = GenericAdapter.new
|
|
17
|
-
# text = adapter.format(assembled_context)
|
|
18
|
-
# # => "=== CODEBASE CONTEXT ===\nTokens: 42 / 8000\n..."
|
|
19
|
-
#
|
|
20
|
-
class GenericAdapter < Base
|
|
21
|
-
# Format assembled context as plain text.
|
|
22
|
-
#
|
|
23
|
-
# @param assembled_context [Woods::Retrieval::AssembledContext]
|
|
24
|
-
# @return [String] Plain text formatted context
|
|
25
|
-
def format(assembled_context)
|
|
26
|
-
parts = []
|
|
27
|
-
parts << '=== CODEBASE CONTEXT ==='
|
|
28
|
-
parts << "Tokens: #{assembled_context.tokens_used} / #{assembled_context.budget}"
|
|
29
|
-
parts << '---'
|
|
30
|
-
parts << assembled_context.context
|
|
31
|
-
parts.concat(format_sources(assembled_context.sources))
|
|
32
|
-
parts.join("\n")
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
# Format sources in bracket notation.
|
|
38
|
-
#
|
|
39
|
-
# @param sources [Array<Hash>]
|
|
40
|
-
# @return [Array<String>] Lines to append
|
|
41
|
-
def format_sources(sources)
|
|
42
|
-
return [] if sources.empty?
|
|
43
|
-
|
|
44
|
-
lines = []
|
|
45
|
-
lines << '---'
|
|
46
|
-
sources.each do |source|
|
|
47
|
-
identifier = source[:identifier]
|
|
48
|
-
type = source[:type]
|
|
49
|
-
score = source[:score]
|
|
50
|
-
lines << "[Source: #{identifier} (#{type}) \u2014 score: #{score}]"
|
|
51
|
-
end
|
|
52
|
-
lines
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Woods
|
|
6
|
-
module Formatting
|
|
7
|
-
# Formats assembled context as Markdown for GPT models.
|
|
8
|
-
#
|
|
9
|
-
# Produces Markdown with:
|
|
10
|
-
# - `## Codebase Context` heading
|
|
11
|
-
# - Token usage in bold
|
|
12
|
-
# - Content in a fenced Ruby code block
|
|
13
|
-
# - Sources as a bullet list
|
|
14
|
-
#
|
|
15
|
-
# @example
|
|
16
|
-
# adapter = GptAdapter.new
|
|
17
|
-
# md = adapter.format(assembled_context)
|
|
18
|
-
# # => "## Codebase Context\n\n**Tokens:** 42/8000\n..."
|
|
19
|
-
#
|
|
20
|
-
class GptAdapter < Base
|
|
21
|
-
# Format assembled context as Markdown for GPT.
|
|
22
|
-
#
|
|
23
|
-
# @param assembled_context [Woods::Retrieval::AssembledContext]
|
|
24
|
-
# @return [String] Markdown-formatted context
|
|
25
|
-
def format(assembled_context)
|
|
26
|
-
parts = []
|
|
27
|
-
parts << '## Codebase Context'
|
|
28
|
-
parts << ''
|
|
29
|
-
parts << "**Tokens:** #{assembled_context.tokens_used}/#{assembled_context.budget}"
|
|
30
|
-
parts << ''
|
|
31
|
-
parts << '---'
|
|
32
|
-
parts << ''
|
|
33
|
-
parts << '```ruby'
|
|
34
|
-
parts << assembled_context.context
|
|
35
|
-
parts << '```'
|
|
36
|
-
parts.concat(format_sources(assembled_context.sources))
|
|
37
|
-
parts.join("\n")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
# Format sources as a Markdown bullet list.
|
|
43
|
-
#
|
|
44
|
-
# @param sources [Array<Hash>]
|
|
45
|
-
# @return [Array<String>] Lines to append
|
|
46
|
-
def format_sources(sources)
|
|
47
|
-
return [] if sources.empty?
|
|
48
|
-
|
|
49
|
-
lines = []
|
|
50
|
-
lines << ''
|
|
51
|
-
lines << '### Sources'
|
|
52
|
-
lines << ''
|
|
53
|
-
sources.each do |source|
|
|
54
|
-
identifier = source[:identifier]
|
|
55
|
-
type = source[:type]
|
|
56
|
-
score = source[:score]
|
|
57
|
-
file_path = source[:file_path]
|
|
58
|
-
lines << "- **#{identifier}** (#{type}) \u2014 score: #{score}, file: #{file_path}"
|
|
59
|
-
end
|
|
60
|
-
lines
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'origin_policy'
|
|
4
|
-
|
|
5
|
-
module Woods
|
|
6
|
-
module MCP
|
|
7
|
-
# Keep the SDK DNS-rebinding guard aligned with Woods' explicit allowlist.
|
|
8
|
-
# Empty configuration retains the SDK's strict loopback defaults.
|
|
9
|
-
module HttpTransportOptions
|
|
10
|
-
def self.for(origins)
|
|
11
|
-
OriginPolicy.new(allowed_origins: origins).transport_options
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'uri'
|
|
4
|
-
require_relative '../util/host_guard'
|
|
5
|
-
|
|
6
|
-
module Woods
|
|
7
|
-
module MCP
|
|
8
|
-
# One immutable policy for the outer Rack guard and the legacy MCP SDK.
|
|
9
|
-
# Keep SDK rebinding protection active and pass original request headers.
|
|
10
|
-
class OriginPolicy
|
|
11
|
-
LOOPBACK_HOSTS = %w[localhost 127.0.0.1 ::1].freeze
|
|
12
|
-
DEFAULT_ORIGINS = %w[
|
|
13
|
-
http://localhost http://127.0.0.1 http://[::1]
|
|
14
|
-
https://localhost https://127.0.0.1 https://[::1]
|
|
15
|
-
].freeze
|
|
16
|
-
|
|
17
|
-
attr_reader :transport_options
|
|
18
|
-
|
|
19
|
-
def initialize(allowed_origins: nil)
|
|
20
|
-
entries = Array(allowed_origins).compact.reject do |entry|
|
|
21
|
-
entry.to_s.valid_encoding? && entry.to_s.strip.empty?
|
|
22
|
-
end
|
|
23
|
-
@explicit = entries.map { |entry| configured_origin(entry) }.uniq.freeze
|
|
24
|
-
@allowed = @explicit.empty? ? DEFAULT_ORIGINS : @explicit
|
|
25
|
-
@hosts = @explicit.map do |origin|
|
|
26
|
-
host = authority(origin)
|
|
27
|
-
(host.end_with?(']') ? host[1...-1] : host).freeze
|
|
28
|
-
end.uniq.freeze
|
|
29
|
-
@transport_options = if @explicit.empty?
|
|
30
|
-
{}.freeze
|
|
31
|
-
else
|
|
32
|
-
{ allowed_origins: transport_origins, allowed_hosts: @hosts }.freeze
|
|
33
|
-
end
|
|
34
|
-
freeze
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def host_allowed?(host)
|
|
38
|
-
return true if host.nil?
|
|
39
|
-
return false unless host.is_a?(String) && host.valid_encoding?
|
|
40
|
-
|
|
41
|
-
parsed = parsed_origin("http://#{host}")
|
|
42
|
-
return false unless parsed
|
|
43
|
-
|
|
44
|
-
hostname = parsed.hostname.downcase
|
|
45
|
-
return false if Util::HostGuard.suspicious_numeric_host?(hostname)
|
|
46
|
-
return true if LOOPBACK_HOSTS.include?(hostname)
|
|
47
|
-
|
|
48
|
-
@hosts.include?(host.downcase) || @hosts.include?(hostname)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def origin_allowed?(origin, host:)
|
|
52
|
-
return true if origin.nil?
|
|
53
|
-
return false unless parsed_origin(origin)
|
|
54
|
-
|
|
55
|
-
normalized = normalized_origin(origin)
|
|
56
|
-
return true if @explicit.include?(normalized)
|
|
57
|
-
return false unless @allowed.include?(normalized) || @allowed.include?(normalized.sub(/:\d+\z/, ''))
|
|
58
|
-
return false unless host.is_a?(String) && host.valid_encoding?
|
|
59
|
-
|
|
60
|
-
default_port = normalized.start_with?('https://') ? ':443' : ':80'
|
|
61
|
-
authority(normalized).delete_suffix(default_port) == host.downcase.delete_suffix(default_port)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
|
|
66
|
-
def configured_origin(entry)
|
|
67
|
-
raw = entry.to_s
|
|
68
|
-
value = raw.strip.downcase.delete_suffix('/') if raw.valid_encoding?
|
|
69
|
-
unless parsed_origin(value)
|
|
70
|
-
label = raw.b.byteslice(0, 160).inspect
|
|
71
|
-
raise ArgumentError, "Invalid MCP allowed origin #{label}: expected http(s)://host[:port] without a path"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
normalized_origin(value).freeze
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def normalized_origin(origin)
|
|
78
|
-
value = origin.downcase
|
|
79
|
-
value.delete_suffix(value.start_with?('https://') ? ':443' : ':80')
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# SDK 0.23 and 0.25 compare explicit entries literally. Both default-port
|
|
83
|
-
# spellings are needed for cross-origin requests; same-authority matching
|
|
84
|
-
# is otherwise performed independently by both layers.
|
|
85
|
-
def transport_origins
|
|
86
|
-
@explicit.flat_map do |origin|
|
|
87
|
-
parsed = parsed_origin(origin)
|
|
88
|
-
port = parsed.scheme == 'https' ? 443 : 80
|
|
89
|
-
parsed.port == port ? [origin, "#{origin}:#{port}"] : [origin]
|
|
90
|
-
end.uniq.map(&:freeze).freeze
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def authority(origin)
|
|
94
|
-
origin.sub(%r{\Ahttps?://}, '')
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def parsed_origin(origin)
|
|
98
|
-
return unless origin.is_a?(String) && origin.valid_encoding? && origin.ascii_only?
|
|
99
|
-
return if origin.match?(/[[:space:][:cntrl:]]/)
|
|
100
|
-
|
|
101
|
-
parsed = URI.parse(origin)
|
|
102
|
-
return unless %w[http https].include?(parsed.scheme&.downcase)
|
|
103
|
-
return unless parsed.host && !parsed.host.empty?
|
|
104
|
-
return unless parsed.path.to_s.empty? && !parsed.userinfo && !parsed.query && !parsed.fragment
|
|
105
|
-
return unless parsed.port&.between?(1, 65_535)
|
|
106
|
-
|
|
107
|
-
parsed
|
|
108
|
-
rescue URI::InvalidURIError, ArgumentError
|
|
109
|
-
nil
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
data/lib/woods/notion/mapper.rb
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'mappers/shared'
|
|
4
|
-
require_relative 'mappers/model_mapper'
|
|
5
|
-
require_relative 'mappers/column_mapper'
|
|
6
|
-
require_relative 'mappers/migration_mapper'
|
|
7
|
-
|
|
8
|
-
module Woods
|
|
9
|
-
module Notion
|
|
10
|
-
# Dispatcher for Notion mappers. Returns the appropriate mapper for a unit type.
|
|
11
|
-
#
|
|
12
|
-
# @example
|
|
13
|
-
# mapper = Mapper.for("model")
|
|
14
|
-
# properties = mapper.map(unit_data)
|
|
15
|
-
#
|
|
16
|
-
class Mapper
|
|
17
|
-
REGISTRY = {
|
|
18
|
-
'model' => Mappers::ModelMapper,
|
|
19
|
-
'column' => Mappers::ColumnMapper,
|
|
20
|
-
'migration' => Mappers::MigrationMapper
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
# Get a mapper instance for a unit type.
|
|
24
|
-
#
|
|
25
|
-
# @param type [String] Unit type name (e.g. "model", "column", "migration")
|
|
26
|
-
# @return [Object, nil] Mapper instance or nil if type is not supported
|
|
27
|
-
def self.for(type)
|
|
28
|
-
klass = REGISTRY[type]
|
|
29
|
-
klass&.new
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# List all supported unit types.
|
|
33
|
-
#
|
|
34
|
-
# @return [Array<String>]
|
|
35
|
-
def self.supported_types
|
|
36
|
-
REGISTRY.keys
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Woods
|
|
4
|
-
module Observability
|
|
5
|
-
# Probes configured components and reports overall system health.
|
|
6
|
-
#
|
|
7
|
-
# Checks vector store, metadata store, and embedding provider by calling
|
|
8
|
-
# lightweight operations on each. Components that are nil are reported
|
|
9
|
-
# as :not_configured and do not affect the overall healthy? status.
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# check = HealthCheck.new(
|
|
13
|
-
# vector_store: vector_store,
|
|
14
|
-
# metadata_store: metadata_store,
|
|
15
|
-
# embedding_provider: provider
|
|
16
|
-
# )
|
|
17
|
-
# status = check.run
|
|
18
|
-
# status.healthy? # => true
|
|
19
|
-
# status.components # => { vector_store: :ok, metadata_store: :ok, embedding_provider: :ok }
|
|
20
|
-
#
|
|
21
|
-
class HealthCheck
|
|
22
|
-
# Value object representing the result of a health check.
|
|
23
|
-
HealthStatus = Struct.new(:healthy?, :components, keyword_init: true)
|
|
24
|
-
|
|
25
|
-
# @param vector_store [Object, nil] Vector store adapter (must respond to #count)
|
|
26
|
-
# @param metadata_store [Object, nil] Metadata store adapter (must respond to #count)
|
|
27
|
-
# @param embedding_provider [Object, nil] Embedding provider (must respond to #embed)
|
|
28
|
-
def initialize(vector_store: nil, metadata_store: nil, embedding_provider: nil)
|
|
29
|
-
@vector_store = vector_store
|
|
30
|
-
@metadata_store = metadata_store
|
|
31
|
-
@embedding_provider = embedding_provider
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Run health probes on all configured components.
|
|
35
|
-
#
|
|
36
|
-
# @return [HealthStatus] Result with healthy? flag and per-component status
|
|
37
|
-
def run
|
|
38
|
-
components = {
|
|
39
|
-
vector_store: probe_store(@vector_store),
|
|
40
|
-
metadata_store: probe_store(@metadata_store),
|
|
41
|
-
embedding_provider: probe_provider(@embedding_provider)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
all_healthy = components.values.all? { |status| %i[ok not_configured].include?(status) }
|
|
45
|
-
|
|
46
|
-
HealthStatus.new(healthy?: all_healthy, components: components)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
# Probe a store component by calling #count.
|
|
52
|
-
#
|
|
53
|
-
# @param store [Object, nil] Store adapter
|
|
54
|
-
# @return [Symbol] :ok, :error, or :not_configured
|
|
55
|
-
def probe_store(store)
|
|
56
|
-
return :not_configured if store.nil?
|
|
57
|
-
|
|
58
|
-
store.count
|
|
59
|
-
:ok
|
|
60
|
-
rescue StandardError
|
|
61
|
-
:error
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Probe an embedding provider by checking its capabilities without making network calls.
|
|
65
|
-
#
|
|
66
|
-
# @param provider [Object, nil] Embedding provider
|
|
67
|
-
# @return [Symbol] :ok, :error, or :not_configured
|
|
68
|
-
def probe_provider(provider)
|
|
69
|
-
return :not_configured if provider.nil?
|
|
70
|
-
|
|
71
|
-
if provider.respond_to?(:embed) && provider.respond_to?(:dimensions)
|
|
72
|
-
:ok
|
|
73
|
-
else
|
|
74
|
-
:error
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Woods
|
|
4
|
-
module Observability
|
|
5
|
-
# Lightweight instrumentation wrapper that delegates to ActiveSupport::Notifications
|
|
6
|
-
# when available, and falls back to a simple yield otherwise.
|
|
7
|
-
#
|
|
8
|
-
# @example
|
|
9
|
-
# Instrumentation.instrument('woods.extraction', unit: 'User') do
|
|
10
|
-
# extract_unit(user_model)
|
|
11
|
-
# end
|
|
12
|
-
#
|
|
13
|
-
module Instrumentation
|
|
14
|
-
module_function
|
|
15
|
-
|
|
16
|
-
# Instrument a block of code with an event name and payload.
|
|
17
|
-
#
|
|
18
|
-
# Delegates to ActiveSupport::Notifications.instrument when available.
|
|
19
|
-
# Otherwise, yields the block directly.
|
|
20
|
-
#
|
|
21
|
-
# @param event [String] Event name (e.g., 'woods.extraction')
|
|
22
|
-
# @param payload [Hash] Additional data to include with the event
|
|
23
|
-
# @yield [payload] The block to instrument
|
|
24
|
-
# @return [Object] The return value of the block
|
|
25
|
-
def instrument(event, payload = {}, &block)
|
|
26
|
-
if defined?(ActiveSupport::Notifications)
|
|
27
|
-
ActiveSupport::Notifications.instrument(event, payload, &block)
|
|
28
|
-
elsif block
|
|
29
|
-
yield payload
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|