woods 1.6.1 → 2.0.0.beta2
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 +2035 -0
- data/CONTRIBUTING.md +253 -87
- data/README.md +161 -513
- 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 +655 -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 +455 -0
- data/docs/INTERNALS.md +418 -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 +213 -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 +40 -4
- data/exe/woods-console-mcp +21 -35
- data/exe/woods-mcp +20 -7
- data/exe/woods-mcp-http +80 -11
- 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 +171 -2
- data/lib/woods/builder.rb +310 -22
- data/lib/woods/cache/cache_middleware.rb +7 -2
- 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/dispatch_pipeline.rb +42 -12
- data/lib/woods/console/embedded_executor.rb +806 -149
- 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 +29 -1
- data/lib/woods/console/rack_middleware.rb +65 -42
- data/lib/woods/console/redactor.rb +26 -8
- data/lib/woods/console/safe_context.rb +58 -10
- data/lib/woods/console/scope_predicate_parser.rb +41 -0
- data/lib/woods/console/server.rb +119 -247
- data/lib/woods/console/sql_noise_stripper.rb +125 -16
- data/lib/woods/console/sql_table_scanner.rb +82 -22
- data/lib/woods/console/sql_validator.rb +459 -29
- data/lib/woods/console/table_gate.rb +2 -2
- data/lib/woods/console/tool_specs.rb +463 -90
- data/lib/woods/console/tools/tier1.rb +1 -5
- data/lib/woods/console/tools/tier4.rb +18 -9
- 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 +2783 -238
- 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 +147 -26
- data/lib/woods/flow_document.rb +1 -0
- data/lib/woods/flow_precomputer.rb +175 -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 +564 -87
- data/lib/woods/index_artifact.rb +93 -23
- data/lib/woods/mcp/bearer_auth.rb +102 -13
- 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 +66 -7
- 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 +898 -152
- 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/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 +236 -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 +69 -30
- 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 +26 -8
- 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/trace_enricher.rb +3 -0
- data/lib/woods/ruby_analyzer.rb +21 -5
- data/lib/woods/session_tracer/file_store.rb +138 -19
- data/lib/woods/session_tracer/middleware.rb +1 -2
- data/lib/woods/session_tracer/redis_store.rb +122 -12
- data/lib/woods/session_tracer/session_flow_assembler.rb +57 -17
- data/lib/woods/session_tracer/session_flow_document.rb +56 -14
- 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 +138 -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 +134 -23
- 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/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/notion/mapper.rb +0 -40
- data/lib/woods/observability/health_check.rb +0 -79
- data/lib/woods/observability/instrumentation.rb +0 -34
|
@@ -61,6 +61,11 @@ module Woods
|
|
|
61
61
|
# in the deprecated auto-detect path. Defaults to {.ollama_reachable?} on
|
|
62
62
|
# this module. Callers may pass a different probe to facilitate testing
|
|
63
63
|
# without touching global state.
|
|
64
|
+
# @param stored_config [Woods::ResolvedConfig, nil] a pre-captured stored
|
|
65
|
+
# config to reconcile against INSTEAD of re-reading +artifact+ (M7: the
|
|
66
|
+
# reload transaction captures the config from the dump identity it
|
|
67
|
+
# pinned, so a concurrent promotion cannot change it mid-build). Nil
|
|
68
|
+
# keeps the default behavior of reading the artifact.
|
|
64
69
|
# @return [Array(Woods::Configuration, Symbol)] tuple of +[config, source]+
|
|
65
70
|
# where +source+ is one of +:snapshot+, +:host_config+, +:autodetect+,
|
|
66
71
|
# or +:none+. The config is the same object passed in, possibly mutated.
|
|
@@ -72,8 +77,8 @@ module Woods
|
|
|
72
77
|
# dimensions disagree.
|
|
73
78
|
# @raise [Woods::MCP::ConfigMismatch] when stored and live provider
|
|
74
79
|
# class/model disagree.
|
|
75
|
-
def self.resolve(config, artifact:, env: ENV, ollama_probe: nil)
|
|
76
|
-
stored = read_stored_config(artifact)
|
|
80
|
+
def self.resolve(config, artifact:, env: ENV, ollama_probe: nil, stored_config: :not_given)
|
|
81
|
+
stored = stored_config == :not_given ? read_stored_config(artifact) : stored_config
|
|
77
82
|
|
|
78
83
|
if stored
|
|
79
84
|
[apply_stored_config(config, stored, artifact: artifact, env: env), :snapshot]
|
|
@@ -86,14 +91,22 @@ module Woods
|
|
|
86
91
|
end
|
|
87
92
|
end
|
|
88
93
|
|
|
89
|
-
# Read and parse
|
|
94
|
+
# Read and parse the resolved config snapshot, if one exists.
|
|
95
|
+
#
|
|
96
|
+
# Delegates entirely to {IndexArtifact#read_config}, which prefers the
|
|
97
|
+
# config embedded in the promoted dump over the root +woods.json+ (#217
|
|
98
|
+
# / FIX 3 — the two used to be independent atomic writes, so a crash
|
|
99
|
+
# between them could publish a new config against the old dump). Do
|
|
100
|
+
# NOT gate on +artifact.config_path.exist?+ here: a promoted dump can
|
|
101
|
+
# carry its own config with no root +woods.json+ ever having been
|
|
102
|
+
# written, and that pre-check would skip straight past it.
|
|
90
103
|
#
|
|
91
104
|
# @param artifact [Woods::IndexArtifact, nil]
|
|
92
105
|
# @return [Woods::ResolvedConfig, nil]
|
|
93
106
|
# @raise [Woods::MCP::UnsupportedArtifact] if the file has an unsupported
|
|
94
107
|
# schema version.
|
|
95
108
|
def self.read_stored_config(artifact)
|
|
96
|
-
return nil unless artifact
|
|
109
|
+
return nil unless artifact
|
|
97
110
|
|
|
98
111
|
raw = artifact.read_config
|
|
99
112
|
return nil unless raw
|
|
@@ -142,7 +155,8 @@ module Woods
|
|
|
142
155
|
# Populate a blank {Woods::Configuration} from a stored {ResolvedConfig}.
|
|
143
156
|
#
|
|
144
157
|
# Maps the serialised provider class name back to the +:ollama+ /
|
|
145
|
-
# +:openai+ symbol that {Builder} expects, and restores
|
|
158
|
+
# +:openai+ / +:fake+ symbol that {Builder} expects, and restores
|
|
159
|
+
# store types.
|
|
146
160
|
# Applied when an MCP server starts without a host Rails initializer.
|
|
147
161
|
#
|
|
148
162
|
# @param config [Woods::Configuration]
|
|
@@ -150,9 +164,11 @@ module Woods
|
|
|
150
164
|
# @param artifact [Woods::IndexArtifact]
|
|
151
165
|
# @return [Woods::Configuration]
|
|
152
166
|
def self.populate_from_stored(config, stored, artifact:, env: ENV)
|
|
167
|
+
config.output_dir = artifact.output_dir.to_s
|
|
153
168
|
config.vector_store = stored.stores[:vector_store] || :in_memory
|
|
154
169
|
config.metadata_store = stored.stores[:metadata_store] || :in_memory
|
|
155
170
|
config.graph_store = stored.stores[:graph_store] || :in_memory
|
|
171
|
+
restore_store_options(config, stored, artifact: artifact, env: env)
|
|
156
172
|
provider_sym = provider_symbol(stored.embedding_provider[:class])
|
|
157
173
|
config.embedding_provider = provider_sym
|
|
158
174
|
|
|
@@ -170,8 +186,10 @@ module Woods
|
|
|
170
186
|
# ArgumentError from OpenAI.new(**opts) wasn't a BootstrapError
|
|
171
187
|
# and the top-level rescue in exe/woods-mcp wouldn't catch it.
|
|
172
188
|
if provider_sym == :openai
|
|
189
|
+
# Strip-aware, matching the resolver-default path (:321): a
|
|
190
|
+
# whitespace-only key is not a credential on either path (R1-5).
|
|
173
191
|
api_key = env.fetch('OPENAI_API_KEY', nil)
|
|
174
|
-
if api_key.nil? || api_key.empty?
|
|
192
|
+
if api_key.nil? || api_key.strip.empty?
|
|
175
193
|
raise Woods::MCP::MissingCredential.new(
|
|
176
194
|
'woods.json says the index was embedded with OpenAI but OPENAI_API_KEY is unset. ' \
|
|
177
195
|
'Export the key before starting the MCP server, or re-embed with a different provider.',
|
|
@@ -189,6 +207,43 @@ module Woods
|
|
|
189
207
|
end
|
|
190
208
|
private_class_method :populate_from_stored
|
|
191
209
|
|
|
210
|
+
def self.restore_store_options(config, stored, artifact:, env:)
|
|
211
|
+
if config.metadata_store == :sqlite
|
|
212
|
+
config.metadata_store_options = { database: artifact.output_dir.join('metadata.sqlite3').to_s }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
options = stored.store_options.fetch(:vector_store, {}).dup
|
|
216
|
+
case config.vector_store
|
|
217
|
+
when :qdrant
|
|
218
|
+
options.delete(:url)
|
|
219
|
+
options[:url] = env['WOODS_QDRANT_URL'] unless env['WOODS_QDRANT_URL'].to_s.empty?
|
|
220
|
+
if options[:collection].to_s.empty? && !env['WOODS_QDRANT_COLLECTION'].to_s.empty?
|
|
221
|
+
options[:collection] = env['WOODS_QDRANT_COLLECTION']
|
|
222
|
+
end
|
|
223
|
+
options[:api_key] = env['WOODS_QDRANT_API_KEY'] unless env['WOODS_QDRANT_API_KEY'].to_s.empty?
|
|
224
|
+
require_store_setting!(options, :url, 'WOODS_QDRANT_URL', :qdrant)
|
|
225
|
+
require_store_setting!(options, :collection, 'WOODS_QDRANT_COLLECTION', :qdrant)
|
|
226
|
+
when :pgvector
|
|
227
|
+
url = env['WOODS_PG_URL']
|
|
228
|
+
if url.to_s.empty?
|
|
229
|
+
raise ConfigMismatch, 'pgvector index requires WOODS_PG_URL when served outside its host application.'
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
require 'active_record'
|
|
233
|
+
options[:connection] = ActiveRecord::Base.establish_connection(url).connection
|
|
234
|
+
end
|
|
235
|
+
config.vector_store_options = options unless options.empty?
|
|
236
|
+
end
|
|
237
|
+
private_class_method :restore_store_options
|
|
238
|
+
|
|
239
|
+
def self.require_store_setting!(options, key, env_name, adapter)
|
|
240
|
+
return unless options[key].to_s.empty?
|
|
241
|
+
|
|
242
|
+
raise ConfigMismatch,
|
|
243
|
+
"#{adapter} index is missing #{key}; set #{env_name} or re-embed so woods.json records it."
|
|
244
|
+
end
|
|
245
|
+
private_class_method :require_store_setting!
|
|
246
|
+
|
|
192
247
|
# Convert a fully-qualified provider class name back to the symbol
|
|
193
248
|
# that {Builder} understands.
|
|
194
249
|
#
|
|
@@ -198,6 +253,7 @@ module Woods
|
|
|
198
253
|
case class_name.to_s
|
|
199
254
|
when /Ollama/ then :ollama
|
|
200
255
|
when /OpenAI/ then :openai
|
|
256
|
+
when /Fake/ then :fake
|
|
201
257
|
else class_name.to_s
|
|
202
258
|
end
|
|
203
259
|
end
|
|
@@ -261,6 +317,10 @@ module Woods
|
|
|
261
317
|
def self.autodetect_from_env(config, env:, ollama_probe:)
|
|
262
318
|
probe = ollama_probe || method(:ollama_reachable?)
|
|
263
319
|
openai_key = env.fetch('OPENAI_API_KEY', nil)
|
|
320
|
+
# A blank key is an absent key (M9). Truthiness alone wired :openai
|
|
321
|
+
# with api_key: "" — skipping the Ollama probe a missing key gets —
|
|
322
|
+
# and then died at Builder time with a raw ConfigurationError.
|
|
323
|
+
openai_key = nil if openai_key.is_a?(String) && openai_key.strip.empty?
|
|
264
324
|
if openai_key
|
|
265
325
|
config.vector_store = :in_memory
|
|
266
326
|
config.metadata_store = :in_memory
|
data/lib/woods/mcp/errors.rb
CHANGED
|
@@ -132,5 +132,65 @@ module Woods
|
|
|
132
132
|
'provider unreachable'
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
|
+
|
|
136
|
+
# Raised when an MCP +reload+ transaction cannot complete (M7). Deliberately
|
|
137
|
+
# a sibling of {BootstrapError}, not a child: this describes a runtime
|
|
138
|
+
# reload attempt, not boot state. The transaction is all-or-nothing —
|
|
139
|
+
# when this is raised, the reader caches and the retriever's store bundle
|
|
140
|
+
# were NOT touched and the previous generation is still being served.
|
|
141
|
+
#
|
|
142
|
+
# The exception carries the payload of the reload-phase degraded condition:
|
|
143
|
+
# the generation still being served, and the store component(s) whose
|
|
144
|
+
# refresh failed. The +reload+ tool maps it to a +degraded_index+ tool
|
|
145
|
+
# error with +phase: 'reload'+, and {BootstrapState#record_reload_failure}
|
|
146
|
+
# persists the same shape for +woods_status+.
|
|
147
|
+
#
|
|
148
|
+
# @example Raising from the reload transaction
|
|
149
|
+
# raise Woods::MCP::ReloadDegraded.new(
|
|
150
|
+
# "vector hydration failed: IOError: dump vanished",
|
|
151
|
+
# generation: served_marker.number, stores: [:vector], error: e
|
|
152
|
+
# )
|
|
153
|
+
class ReloadDegraded < Woods::Error
|
|
154
|
+
# @return [Integer] the generation number still being served
|
|
155
|
+
attr_reader :generation
|
|
156
|
+
|
|
157
|
+
# @return [Array<String>] store component(s) whose refresh failed
|
|
158
|
+
# (subset of +vector+, +metadata+, +graph+; all three when the
|
|
159
|
+
# failure is not attributable to one component)
|
|
160
|
+
attr_reader :stores
|
|
161
|
+
|
|
162
|
+
# @return [Exception, nil] the underlying error, when the failure was
|
|
163
|
+
# caused by one
|
|
164
|
+
attr_reader :cause_error
|
|
165
|
+
|
|
166
|
+
# @param message [String]
|
|
167
|
+
# @param generation [Integer] generation still being served
|
|
168
|
+
# @param stores [Array<Symbol, String>] failing store component(s)
|
|
169
|
+
# @param error [Exception, nil] underlying cause
|
|
170
|
+
def initialize(message, generation:, stores:, error: nil)
|
|
171
|
+
super(message)
|
|
172
|
+
@generation = generation
|
|
173
|
+
@stores = Array(stores).map(&:to_s)
|
|
174
|
+
@cause_error = error
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The generation recheck inside a reload transaction failed: the on-disk
|
|
179
|
+
# generation moved while candidate stores were being built off-side, so
|
|
180
|
+
# the built bundle is stale. Swapping it would pair reader state from one
|
|
181
|
+
# generation with stores from another. Nothing is swapped; the next
|
|
182
|
+
# +reload+ invocation is the recovery path (it rebuilds candidates
|
|
183
|
+
# against the fresh generation).
|
|
184
|
+
class ReloadGenerationMoved < ReloadDegraded; end
|
|
185
|
+
|
|
186
|
+
# The promoted-dump recheck inside a reload transaction failed. The embed
|
|
187
|
+
# path's commit point ({IndexArtifact#promote}, which flips +dumps/latest+)
|
|
188
|
+
# does not bump the generation file, so an embed-only publication moves
|
|
189
|
+
# the dump identity WITHOUT moving the generation marker — this recheck is
|
|
190
|
+
# the only thing standing between a reload and a bundle whose vector and
|
|
191
|
+
# metadata halves were hydrated from two different dumps. Nothing is
|
|
192
|
+
# swapped; the next +reload+ re-captures and rebuilds against whatever is
|
|
193
|
+
# promoted by then.
|
|
194
|
+
class ReloadDumpMoved < ReloadDegraded; end
|
|
135
195
|
end
|
|
136
196
|
end
|