woods 2.0.0.beta2 → 2.0.0.beta3
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 +262 -1
- data/CONTRIBUTING.md +173 -9
- data/README.md +7 -3
- data/SECURITY.md +9 -6
- data/docs/AGENT_GUIDE.md +83 -4
- data/docs/AGENT_SETUP.md +82 -1
- data/docs/BACKEND_MATRIX.md +20 -0
- data/docs/CLIENT_HOOKS.md +111 -0
- data/docs/CONFIGURATION_REFERENCE.md +199 -14
- data/docs/CONSOLE_MCP_SETUP.md +35 -5
- data/docs/DOCKER_SETUP.md +21 -2
- data/docs/EVALUATION.md +464 -1
- data/docs/EXTRACTOR_REFERENCE.md +36 -5
- data/docs/FAQ.md +11 -12
- data/docs/GETTING_STARTED.md +17 -5
- data/docs/INCREMENTAL_EXTRACTION.md +117 -1
- data/docs/INDEX_LAYOUT.md +382 -0
- data/docs/INTERNALS.md +7 -2
- data/docs/MCP_SERVERS.md +221 -5
- data/docs/MCP_TOOL_COOKBOOK.md +33 -18
- data/docs/NOTION_INTEGRATION.md +13 -0
- data/docs/OBSIDIAN_INTEGRATION.md +57 -9
- data/docs/PUBLISHED_INDEX.md +55 -0
- data/docs/README.md +7 -0
- data/docs/RETRIEVAL_GUIDE.md +253 -11
- data/docs/RUNTIME_TRACING.md +71 -0
- data/docs/SOURCE_FRESHNESS.md +143 -0
- data/docs/TROUBLESHOOTING.md +117 -5
- data/docs/UNBLOCKED_INTEGRATION.md +25 -0
- data/docs/UPGRADING_TO_2.md +44 -22
- data/docs/WATCH_DAEMON.md +259 -59
- data/exe/woods-agent-config +6 -0
- data/exe/woods-extract +5 -0
- data/exe/woods-hook-context +6 -0
- data/lib/generators/woods/templates/woods.rb.tt +1 -3
- data/lib/tasks/woods.rake +47 -397
- data/lib/woods/agent_configuration/applier.rb +133 -0
- data/lib/woods/agent_configuration/cli.rb +101 -0
- data/lib/woods/agent_configuration/cli_options.rb +29 -0
- data/lib/woods/agent_configuration/document.rb +105 -0
- data/lib/woods/agent_configuration/error.rb +7 -0
- data/lib/woods/agent_configuration/launcher.rb +75 -0
- data/lib/woods/agent_configuration/layout.rb +59 -0
- data/lib/woods/agent_configuration/managed_section.rb +62 -0
- data/lib/woods/agent_configuration/plan.rb +98 -0
- data/lib/woods/agent_configuration/plan_diff.rb +38 -0
- data/lib/woods/agent_configuration/planned_files.rb +61 -0
- data/lib/woods/agent_configuration/planner.rb +63 -0
- data/lib/woods/agent_configuration/planner_validation.rb +77 -0
- data/lib/woods/agent_configuration/preflight.rb +100 -0
- data/lib/woods/agent_configuration/recovery.rb +49 -0
- data/lib/woods/ast/node.rb +2 -0
- data/lib/woods/ast/parser.rb +38 -5
- data/lib/woods/builder.rb +21 -5
- data/lib/woods/cache/cache_middleware.rb +28 -7
- data/lib/woods/cache/cache_store.rb +4 -5
- data/lib/woods/change_set.rb +5 -4
- data/lib/woods/console/credential_index.rb +20 -2
- data/lib/woods/console/credential_scanner.rb +14 -14
- data/lib/woods/console/credential_scanner_registry.rb +36 -0
- data/lib/woods/console/embedded_executor.rb +1 -1
- data/lib/woods/console/encrypted_credential_snapshot.rb +16 -0
- data/lib/woods/console/rack_middleware.rb +22 -13
- data/lib/woods/console/server.rb +18 -16
- data/lib/woods/dependency_graph.rb +65 -13
- data/lib/woods/embedding/corpus.rb +94 -0
- data/lib/woods/embedding/indexer.rb +90 -46
- data/lib/woods/embedding/openai.rb +17 -6
- data/lib/woods/evaluation/ablation_executor.rb +6 -1
- data/lib/woods/evaluation/ablation_timed_executor.rb +22 -4
- data/lib/woods/export/typed_reader.rb +56 -0
- data/lib/woods/extractor.rb +232 -137
- data/lib/woods/extractors/action_cable_extractor.rb +3 -1
- data/lib/woods/extractors/behavioral_profile.rb +9 -7
- data/lib/woods/extractors/caching_extractor.rb +3 -1
- data/lib/woods/extractors/concern_extractor.rb +64 -6
- data/lib/woods/extractors/configuration_extractor.rb +7 -3
- data/lib/woods/extractors/controller_extractor.rb +13 -4
- data/lib/woods/extractors/database_view_extractor.rb +3 -1
- data/lib/woods/extractors/decorator_extractor.rb +3 -1
- data/lib/woods/extractors/engine_extractor.rb +3 -1
- data/lib/woods/extractors/event_extractor.rb +4 -2
- data/lib/woods/extractors/factory_extractor.rb +3 -1
- data/lib/woods/extractors/graphql_extractor.rb +8 -2
- data/lib/woods/extractors/i18n_extractor.rb +3 -1
- data/lib/woods/extractors/job_extractor.rb +6 -19
- data/lib/woods/extractors/lib_extractor.rb +3 -1
- data/lib/woods/extractors/mailer_extractor.rb +20 -5
- data/lib/woods/extractors/manager_extractor.rb +3 -1
- data/lib/woods/extractors/method_parameters.rb +53 -0
- data/lib/woods/extractors/middleware_argument.rb +65 -0
- data/lib/woods/extractors/middleware_extractor.rb +9 -3
- data/lib/woods/extractors/migration_extractor.rb +3 -1
- data/lib/woods/extractors/model_extractor.rb +39 -33
- data/lib/woods/extractors/package_extractor.rb +24 -4
- data/lib/woods/extractors/phlex_extractor.rb +3 -1
- data/lib/woods/extractors/policy_extractor.rb +3 -1
- data/lib/woods/extractors/poro_extractor.rb +3 -1
- data/lib/woods/extractors/pundit_extractor.rb +3 -1
- data/lib/woods/extractors/rails_source_extractor.rb +4 -2
- data/lib/woods/extractors/rake_task_extractor.rb +4 -2
- data/lib/woods/extractors/route_extractor.rb +3 -1
- data/lib/woods/extractors/route_helper_resolver.rb +10 -33
- data/lib/woods/extractors/scheduled_job_extractor.rb +41 -15
- data/lib/woods/extractors/serializer_extractor.rb +4 -2
- data/lib/woods/extractors/service_extractor.rb +3 -1
- data/lib/woods/extractors/shared_dependency_scanner.rb +2 -2
- data/lib/woods/extractors/shared_utility_methods.rb +27 -15
- data/lib/woods/extractors/source_nesting.rb +1 -1
- data/lib/woods/extractors/state_machine_extractor.rb +3 -1
- data/lib/woods/extractors/test_mapping_extractor.rb +3 -1
- data/lib/woods/extractors/validator_extractor.rb +3 -1
- data/lib/woods/extractors/view_component_extractor.rb +3 -1
- data/lib/woods/extractors/view_template_extractor.rb +3 -1
- data/lib/woods/gem_mapper.rb +2 -0
- data/lib/woods/git_history.rb +116 -0
- data/lib/woods/graph_analyzer.rb +35 -6
- data/lib/woods/hooks/context_cli.rb +54 -0
- data/lib/woods/hooks/context_event.rb +88 -0
- data/lib/woods/hooks/context_hint.rb +73 -0
- data/lib/woods/hooks/context_impact.rb +77 -0
- data/lib/woods/hooks/context_output.rb +47 -0
- data/lib/woods/hooks/context_state.rb +102 -0
- data/lib/woods/hooks/refresh.rb +79 -0
- data/lib/woods/hooks/rule_projection.rb +78 -0
- data/lib/woods/input_rules.rb +19 -0
- data/lib/woods/mcp/bearer_auth.rb +20 -12
- data/lib/woods/mcp/bootstrapper.rb +62 -0
- data/lib/woods/mcp/index_reader.rb +323 -160
- data/lib/woods/mcp/initialization_guidance.rb +27 -0
- data/lib/woods/mcp/origin_guard.rb +17 -9
- data/lib/woods/mcp/published_lexical_retriever.rb +115 -0
- data/lib/woods/mcp/renderers/markdown_renderer.rb +8 -1
- data/lib/woods/mcp/renderers/plain_renderer.rb +7 -1
- data/lib/woods/mcp/search_results.rb +74 -0
- data/lib/woods/mcp/server.rb +158 -37
- data/lib/woods/mcp/tool_contract.rb +2 -0
- data/lib/woods/mcp/tool_response_renderer.rb +25 -0
- data/lib/woods/mcp/traversal_evidence.rb +113 -0
- data/lib/woods/mcp/traversal_evidence_index.rb +100 -0
- data/lib/woods/mcp/traversal_evidence_page.rb +41 -0
- data/lib/woods/mcp/traversal_evidence_text.rb +52 -0
- data/lib/woods/notion/exporter.rb +56 -17
- data/lib/woods/obsidian/destination_plan.rb +98 -0
- data/lib/woods/obsidian/name_mapper.rb +19 -3
- data/lib/woods/obsidian/note_builder.rb +19 -10
- data/lib/woods/obsidian/vault_exporter.rb +88 -32
- data/lib/woods/operator/pipeline_guard.rb +18 -13
- data/lib/woods/path_dispatcher.rb +7 -1
- data/lib/woods/payload_store.rb +27 -26
- data/lib/woods/railtie.rb +3 -3
- data/lib/woods/railtie_support.rb +12 -12
- data/lib/woods/rake_helpers.rb +392 -0
- data/lib/woods/resilience/graph_invariant_validator/membership_checks.rb +71 -0
- data/lib/woods/resilience/graph_invariant_validator/node_checks.rb +61 -0
- data/lib/woods/resilience/graph_invariant_validator/reverse_relationship_checks.rb +46 -0
- data/lib/woods/resilience/graph_invariant_validator.rb +119 -0
- data/lib/woods/resilience/index_validator/graph_checks.rb +80 -0
- data/lib/woods/resilience/index_validator.rb +112 -23
- data/lib/woods/retrieval/context_assembler.rb +50 -15
- data/lib/woods/retrieval/lexical_assembler.rb +73 -0
- data/lib/woods/retrieval/lexical_index.rb +119 -0
- data/lib/woods/retrieval/ranker.rb +4 -2
- data/lib/woods/retrieval/scope.rb +108 -0
- data/lib/woods/retrieval/scoped_graph_store.rb +32 -0
- data/lib/woods/retrieval/scoped_vector_store.rb +55 -0
- data/lib/woods/retrieval/search_executor.rb +86 -27
- data/lib/woods/retrieval/source_evidence.rb +200 -0
- data/lib/woods/retriever.rb +98 -22
- data/lib/woods/ruby_analyzer/trace_enricher.rb +77 -38
- data/lib/woods/session_tracer/middleware.rb +10 -12
- data/lib/woods/session_tracer/redis_store.rb +22 -6
- data/lib/woods/session_tracer/session_flow_assembler.rb +23 -17
- data/lib/woods/session_tracer/solid_cache_coordination.rb +6 -4
- data/lib/woods/session_tracer/unit_resolver.rb +63 -0
- data/lib/woods/source_inputs/consumer_errors.rb +27 -0
- data/lib/woods/source_inputs/handoff.rb +102 -0
- data/lib/woods/source_inputs/launcher.rb +157 -0
- data/lib/woods/source_inputs/manifest.rb +124 -0
- data/lib/woods/source_inputs/private_key.rb +55 -0
- data/lib/woods/source_inputs/scanner.rb +171 -0
- data/lib/woods/source_inputs/scopes.rb +71 -0
- data/lib/woods/source_inputs/session.rb +214 -0
- data/lib/woods/source_inputs/status.rb +84 -0
- data/lib/woods/source_inputs/verifier.rb +107 -0
- data/lib/woods/storage/metadata_store.rb +25 -25
- data/lib/woods/storage/pgvector.rb +29 -8
- data/lib/woods/storage/qdrant.rb +17 -7
- data/lib/woods/storage/vector_store.rb +18 -6
- data/lib/woods/tasks.rb +3 -2
- data/lib/woods/temporal/json_snapshot_store.rb +29 -8
- data/lib/woods/unblocked/exporter.rb +59 -70
- data/lib/woods/version.rb +1 -1
- data/lib/woods/watch/boot_snapshot.rb +52 -0
- data/lib/woods/watch/daemon.rb +136 -28
- data/lib/woods/watch/listen_watcher.rb +4 -0
- data/lib/woods/watch/polling_watcher.rb +5 -1
- data/lib/woods/watch/status.rb +20 -15
- data/lib/woods/watch/tree_scan.rb +21 -13
- data/lib/woods/watch/watcher.rb +4 -1
- data/lib/woods.rb +50 -11
- data/plugin/.claude-plugin/plugin.json +1 -1
- data/plugin/hooks/adapters/normalize.jq +15 -0
- data/plugin/hooks/adapters/normalize.rb +63 -0
- data/plugin/hooks/hooks.json +20 -0
- data/plugin/hooks/woods-context.sh +50 -0
- data/plugin/hooks/woods-input-rules.sh +159 -0
- data/plugin/hooks/woods-opencode.mjs +65 -0
- data/plugin/hooks/woods-post-edit.sh +2 -225
- data/plugin/hooks/woods-refresh.sh +260 -0
- data/plugin/hooks/woods-session-start.sh +47 -55
- data/plugin/skills/woods-agent-enable/SKILL.md +13 -0
- data/plugin/skills/woods-diagnose/SKILL.md +288 -1
- data/plugin/skills/woods-investigate/SKILL.md +106 -0
- data/plugin/skills/woods-mcp-config/SKILL.md +89 -1
- data/plugin/skills/woods-setup/SKILL.md +107 -6
- metadata +84 -5
|
@@ -108,8 +108,9 @@ Columns:
|
|
|
108
108
|
| `output_dir` | Pathname/String | `Rails.root.join('tmp/woods')` | user-settable | Directory where extracted data is written |
|
|
109
109
|
| `extractors` | Array<Symbol> | `[:models, :controllers, :services, ...]` | accepted, not implemented | Does not select which extractors run. See [Extractors](#extractors) below. |
|
|
110
110
|
| `pretty_json` | Boolean | `true` | user-settable | Format extracted JSON with indentation |
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
111
|
+
| `retrieval_mode` | Symbol | `:semantic` | user-settable | `:semantic` uses configured embeddings; explicit `:lexical` ranks published text without a provider/vector store. See [retrieval modes](RETRIEVAL_GUIDE.md#embedding-free-lexical-retrieval). |
|
|
112
|
+
| `max_context_tokens` | Integer | `8000` | user-settable | Default context-assembly token budget captured when a retriever is built; per-call budget overrides it |
|
|
113
|
+
| `similarity_threshold` | Float | `0.7` | deprecated, inert | Accepted for compatibility; setting it warns. It does not filter or change retrieval ranking |
|
|
113
114
|
| `context_format` | Symbol | `:markdown` | user-settable | Output format for retrieval: `:claude`, `:markdown`, `:plain`, `:json` |
|
|
114
115
|
| `include_framework_sources` | Boolean | `true` | user-settable | Extract Rails and gem source code |
|
|
115
116
|
| `concurrent_extraction` | Boolean | `false` | user-settable | Enable parallel extraction (experimental) |
|
|
@@ -135,6 +136,17 @@ config.embedding_options = {
|
|
|
135
136
|
}
|
|
136
137
|
```
|
|
137
138
|
|
|
139
|
+
OpenAI embedding batches are sent in slices of at most 36 texts, preserving
|
|
140
|
+
input order. For inputs within the API's 8,192-token per-text limit, this stays
|
|
141
|
+
below both the 2,048-input limit and the 300,000-token total request limit.
|
|
142
|
+
See the [OpenAI embedding request contract](https://developers.openai.com/api/reference/resources/embeddings/methods/create).
|
|
143
|
+
Woods retains its conservative 8,191-token chunking ceiling; slicing does not
|
|
144
|
+
make an individually oversized text valid. This bound avoids relying on token
|
|
145
|
+
estimates and adds HTTP requests for batches containing many short chunks.
|
|
146
|
+
All slices must validate, including consistent vector dimensions, before the
|
|
147
|
+
provider returns any vectors for the batch. Ollama and custom providers keep
|
|
148
|
+
their existing batching behavior.
|
|
149
|
+
|
|
138
150
|
### Ollama embeddings
|
|
139
151
|
|
|
140
152
|
```ruby
|
|
@@ -224,6 +236,15 @@ config.metadata_store_options = {
|
|
|
224
236
|
}
|
|
225
237
|
```
|
|
226
238
|
|
|
239
|
+
Without an explicit `database` option, SQLite uses
|
|
240
|
+
`<output_dir>/metadata.sqlite3`. For `woods:embed` and
|
|
241
|
+
`woods:embed_incremental`, `WOODS_OUTPUT` overrides that directory together
|
|
242
|
+
with the index and embedding dumps. An explicit `database` path (including
|
|
243
|
+
`:memory:`) still takes precedence, so configure a separate path for each
|
|
244
|
+
worktree when overriding it. Existing databases at the old configured output
|
|
245
|
+
path are not moved or deleted; run `woods:embed` for the selected index after
|
|
246
|
+
upgrading to populate its default metadata database.
|
|
247
|
+
|
|
227
248
|
Requires the `sqlite3` gem in your host bundle. Rails apps backed by
|
|
228
249
|
MySQL or PostgreSQL won't have it by default, selecting `:sqlite`
|
|
229
250
|
without it raises `Woods::ConfigurationError` with install
|
|
@@ -236,10 +257,13 @@ unless cross-process metadata persistence matters.
|
|
|
236
257
|
config.metadata_store = :in_memory
|
|
237
258
|
```
|
|
238
259
|
|
|
239
|
-
Pure-Ruby hash-backed store
|
|
240
|
-
it
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
Pure-Ruby hash-backed store with no external dependencies. For local vector
|
|
261
|
+
presets, embedding runs persist it as `metadata.msgpack` alongside `vectors.bin`
|
|
262
|
+
in the promoted dump; the index MCP server loads that snapshot at startup or
|
|
263
|
+
reload. Incremental embedding publishes changes to paths, dependencies, and
|
|
264
|
+
other unit metadata even when unchanged source needs no new embedding. A run
|
|
265
|
+
with no content or metadata changes keeps the existing dump and retention
|
|
266
|
+
window. This is a reasonable default for hosts that don't bundle `sqlite3`.
|
|
243
267
|
|
|
244
268
|
## Retrieval cache options
|
|
245
269
|
|
|
@@ -278,6 +302,13 @@ overrides the wrapper defaults for `:embeddings` (24 hours) and `:context`
|
|
|
278
302
|
(15 minutes). `:memory` accepts `max_entries` (default 500); it ignores
|
|
279
303
|
`default_ttl` because each wrapper write supplies its domain TTL.
|
|
280
304
|
|
|
305
|
+
`Woods::Cache.cache_key` length-prefixes every component, including a single
|
|
306
|
+
component, so different argument counts cannot share a response. Existing
|
|
307
|
+
multi-component keys used by Woods' wrappers remain unchanged. Custom callers
|
|
308
|
+
using single-component keys must clear their affected persistent cache domain
|
|
309
|
+
when upgrading, since older unprefixed entries can alias the new encoding;
|
|
310
|
+
subsequent calls refill it normally. Namespace clearing still covers both formats.
|
|
311
|
+
|
|
281
312
|
## Deployment shapes
|
|
282
313
|
|
|
283
314
|
Woods supports three deployment shapes, pick the preset that matches yours.
|
|
@@ -358,12 +389,41 @@ end
|
|
|
358
389
|
| `extract_navigation_edges` | Boolean | `true` | Extract `link_to`, `redirect_to`, and `form_action` navigation edges from views and controllers |
|
|
359
390
|
| `enable_snapshots` | Boolean | `false` | Enable temporal snapshots. Woods automatically migrates its internal output-directory SQLite store; if SQLite is unavailable, it uses the JSON snapshot store. No Rails migration is required. |
|
|
360
391
|
| `volatile_dependency_ratio` | Float | `3.0` | A dependency whose commit count (last 365 days) exceeds the dependent's by this ratio appears in the `volatile_dependencies` report (top 20, ranked by PageRank). Must be greater than 1. Report only, never a gate. |
|
|
392
|
+
| `volatile_dependency_limit_per_target` | Integer or `nil` | `nil` | Optional maximum report edges per dependency (type and identifier), applied after ranking and before the global top 20. Positive integers only; `nil` leaves the default report unchanged. Distinct relationships consume separate slots. |
|
|
361
393
|
| `graph_cycle_limit` | Integer or `nil` | `500` | How many distinct cycles `GraphAnalyzer` enumerates before it stops. Cycle detection finds one cycle per DFS back-edge, so a dense graph has tens of thousands of them and enumerating every one is the largest single cost of the analysis that runs on every extraction. Set to `nil` for exhaustive enumeration. |
|
|
362
394
|
| `graph_cycle_max_length` | Integer or `nil` | `50` | The longest cycle recorded, in distinct nodes. A back-edge deep in the DFS closes a cycle as long as the path, which on a large graph is thousands of nodes: unreadable as a report and expensive to canonicalize. Set to `nil` to record a cycle of any length. |
|
|
363
395
|
|
|
364
396
|
| `incremental_blast_radius_depth` | Integer or `nil` | `nil` | How many reverse hops an incremental run walks from a changed file before it stops re-extracting dependents. `nil` keeps the unbounded transitive closure. See the note below before setting it. |
|
|
365
397
|
| `durable_payload_writes` | Boolean | `false` | Force an `fsync` on every payload file as it is written, on top of the single flush every publish already performs. See the note below before setting it. |
|
|
366
398
|
|
|
399
|
+
**Tuning volatile dependency reports.** Start with
|
|
400
|
+
`graph_analysis.json`'s `stats.volatile_dependency_count`: it counts every
|
|
401
|
+
qualifying edge before either cap, while the array normally keeps only 20.
|
|
402
|
+
Raise `volatile_dependency_ratio` until the remaining candidates are useful
|
|
403
|
+
for your application. A measured 7k-unit app had 544 qualifying edges at 3.0;
|
|
404
|
+
8–10 was a useful ratio there, not a universal recommendation. Commit counts
|
|
405
|
+
cover the last 365 days and need complete git history; missing git data is not
|
|
406
|
+
proof of stability.
|
|
407
|
+
|
|
408
|
+
If one hot dependency still fills the list, set
|
|
409
|
+
`volatile_dependency_limit_per_target` to a small positive integer such as 3.
|
|
410
|
+
Each dependency keeps its highest-ranked edges before the global limit applies,
|
|
411
|
+
allowing other dependencies into the report. Different relationship labels
|
|
412
|
+
remain separate edges and consume separate slots. With this option enabled,
|
|
413
|
+
`stats.volatile_dependencies_limit_per_target` records the setting and
|
|
414
|
+
`stats.volatile_dependency_reported_count` counts the final persisted array;
|
|
415
|
+
`stats.volatile_dependency_count` still counts all qualifying edges. The extra
|
|
416
|
+
stats are absent at the default `nil`. This limits the report, not the work of
|
|
417
|
+
finding qualifying edges. Run extraction again after changing either setting;
|
|
418
|
+
MCP reads the published report. These findings remain informational, never a
|
|
419
|
+
release or architecture gate.
|
|
420
|
+
|
|
421
|
+
When the JSON snapshot fallback is in use, malformed JSON, top-level values
|
|
422
|
+
other than objects, and files that cannot be read (including concurrent retention
|
|
423
|
+
removals) are warned about and treated as absent. Snapshot lists and unit history
|
|
424
|
+
omit them; direct lookup returns no snapshot, and a diff with an unavailable
|
|
425
|
+
snapshot returns empty added, modified, and deleted lists.
|
|
426
|
+
|
|
367
427
|
`incremental_blast_radius_depth` is unbounded by default because a unit two hops
|
|
368
428
|
out really can have content that depends on the changed file. An STI grandchild
|
|
369
429
|
(`SportsCar < Car < Vehicle`) inherits its grandparent's associations,
|
|
@@ -408,11 +468,54 @@ require 'woods/session_tracer/file_store' # the stores are not autoloaded
|
|
|
408
468
|
|
|
409
469
|
config.session_tracer_enabled = true
|
|
410
470
|
config.session_store = Woods::SessionTracer::FileStore.new(
|
|
411
|
-
Rails.root.join('tmp/session_traces')
|
|
471
|
+
base_dir: Rails.root.join('tmp/session_traces')
|
|
412
472
|
)
|
|
413
473
|
config.session_exclude_paths = ['/health', '/metrics', '/assets']
|
|
414
474
|
```
|
|
415
475
|
|
|
476
|
+
### Redis session index compatibility
|
|
477
|
+
|
|
478
|
+
`RedisStore` lists and clears both legacy SET indexes and recency ZSET indexes.
|
|
479
|
+
Listing removes expired members and orders summaries by their last request.
|
|
480
|
+
Reads and clears preserve a legacy SET, so upgrading only readers does not
|
|
481
|
+
break older writers. Type checks and index operations run atomically in Redis
|
|
482
|
+
and tolerate a concurrent writer converting the index.
|
|
483
|
+
|
|
484
|
+
The first `record` from a newer writer converts the SET to a ZSET atomically.
|
|
485
|
+
Upgrade writers together: older SET writers cannot write after that conversion.
|
|
486
|
+
Migrated members receive score zero, so they evict in lexical order at the
|
|
487
|
+
retention limit until recorded again; new records use their request timestamp.
|
|
488
|
+
Session list contents and TTLs are preserved by the index conversion.
|
|
489
|
+
|
|
490
|
+
### Solid Cache session retention and compatibility
|
|
491
|
+
|
|
492
|
+
`Woods::SessionTracer::SolidCacheStore` accepts a direct `SolidCache::Store`.
|
|
493
|
+
Its coordination layer uses private Solid Cache APIs for uncached, per-key
|
|
494
|
+
routing and atomic ownership. Missing APIs raise
|
|
495
|
+
`Woods::SessionTracer::SolidCacheCoordination::BackendError`; a gem's version
|
|
496
|
+
constraint alone does not establish compatibility. The live contract suite
|
|
497
|
+
validates SQLite, PostgreSQL, and MySQL (including MySQL's insert path without
|
|
498
|
+
`RETURNING`). See [the version-validation procedure](../CONTRIBUTING.md#solid-cache-session-compatibility)
|
|
499
|
+
before upgrading Solid Cache.
|
|
500
|
+
|
|
501
|
+
Session traces are best-effort diagnostic data. The slot directory and record
|
|
502
|
+
rings are bounded by `max_sessions` and `max_requests_per_session`, but the
|
|
503
|
+
following crash/eviction limits remain:
|
|
504
|
+
|
|
505
|
+
- A crashed admission can strand an active-session mapping outside the directory.
|
|
506
|
+
These mappings can accumulate across distinct session IDs, with a bounded
|
|
507
|
+
amount per ID. Recording that session again reclaims its mapping; the normal
|
|
508
|
+
directory bound is not a hard bound on these stranded keys.
|
|
509
|
+
- If the backend evicts a slot counter while deep crash-orphan records remain
|
|
510
|
+
in an unoccupied slot, new records can be silently dropped until the counter
|
|
511
|
+
advances past them. This loss is bounded and self-healing, but the affected
|
|
512
|
+
requests are not recovered.
|
|
513
|
+
- Losing the epoch key fences every live session. Old traces become unreadable,
|
|
514
|
+
and each session is admitted again on its next record. This deliberately
|
|
515
|
+
favors privacy over retention so previously cleared data stays cleared.
|
|
516
|
+
|
|
517
|
+
Do not use this store as an audit log or the only record of a request.
|
|
518
|
+
|
|
416
519
|
## Gem indexing
|
|
417
520
|
|
|
418
521
|
`config.add_gem` is accepted for forward compatibility but **not implemented**: nothing in the
|
|
@@ -525,8 +628,9 @@ deployment guide including defense layers.
|
|
|
525
628
|
|
|
526
629
|
| Key | Type | Default | Description |
|
|
527
630
|
|---|---|---|---|
|
|
528
|
-
| `console_mcp_enabled` | Boolean | `false` | Master switch. When `false`, the
|
|
529
|
-
| `
|
|
631
|
+
| `console_mcp_enabled` | Boolean | `false` | Master switch. When `false`, stdio exits and the mounted Console middleware passes requests through to Rails. |
|
|
632
|
+
| `console_mcp_http_enabled` | Boolean | `true` | HTTP transport switch; effective only while the master switch is on. Set `false` for stdio-only use without HTTP token validation or an active HTTP endpoint. Read at request time. |
|
|
633
|
+
| `console_mcp_token` | String | `ENV['WOODS_CONSOLE_MCP_TOKEN']` or `nil` | Bearer token required on every enabled Console HTTP request. With both Console flags enabled, production boot raises on a missing token; other environments warn and requests fail closed with 401. A configured token shorter than 32 characters raises at boot while HTTP is enabled. Explicit stdio-only configurations skip HTTP token validation. Generate with `SecureRandom.hex(32)`. |
|
|
530
634
|
| `console_mcp_allowed_origins` | Array\<String\> | `%w[http://localhost http://127.0.0.1 http://[::1]]` | `OriginGuard` allowlist. Port is stripped before comparison, so `http://localhost` matches any localhost port. Override for tunneled / internal-dashboard access. |
|
|
531
635
|
| `console_mcp_path` | String | `/mcp/console` | URL path the Rack middleware responds on. |
|
|
532
636
|
| `console_embedded_read_tools` | Boolean | `false` | Register `console_sql` and `console_query` in supported stdio and Rack modes. |
|
|
@@ -547,13 +651,14 @@ These variables are read by the gem and its MCP servers at runtime. They complem
|
|
|
547
651
|
|
|
548
652
|
| Variable | Default | Purpose |
|
|
549
653
|
|----------|---------|---------|
|
|
654
|
+
| `WOODS_RETRIEVAL_MODE` | `semantic` | Explicit packaged MCP retrieval mode: `semantic` or `lexical`. Lexical reads extraction unit JSON without provider autodetection, credentials or vector artifacts. |
|
|
550
655
|
| `WOODS_DIR` | `Dir.pwd` | Path to the extraction output directory. |
|
|
551
|
-
| `WOODS_REQUIRE_INDEX` | unset | Set to `"1"` to fail closed: the server refuses to boot (raises `MissingArtifact`) unless a real index (`woods.json`) is present. By default an extract-only host boots in pattern/structural mode without it. |
|
|
656
|
+
| `WOODS_REQUIRE_INDEX` | unset | Set to `"1"` to fail closed: the server refuses to boot (raises `MissingArtifact`) unless a real index (`woods.json`) is present. By default an extract-only host boots in pattern/structural mode without it. Explicit lexical mode requires a valid published extraction index, not `woods.json`. |
|
|
552
657
|
| `WOODS_ALLOW_AUTODETECT` | unset | **Deprecated no-op.** Auto-detect is now the default; accepted for backward compatibility only. |
|
|
553
658
|
| `WOODS_SEARCH_MAX_SCAN` | `500` | Cap on unit files loaded during a phase-2 (metadata/source_code) `search`. Hitting the cap sets `partial: true` in the response. |
|
|
554
659
|
| `WOODS_SNAPSHOTS` | unset | Set to `"true"` to force-enable temporal snapshot storage, even without a pre-existing SQLite database. |
|
|
555
660
|
| `WOODS_ALLOW_PURGE` | unset | Set to `"1"` to override the 30%-deletion purge guard in `woods:embed`/`woods:embed_incremental`. |
|
|
556
|
-
| `WOODS_PAYLOAD_RETENTION` | `3` | How many past generations' payload directories (`payloads/gen-N/`) to retain, and — when the JSON snapshot store is in use — how many temporal snapshots (`snapshots/`) to keep. A payload pinned by an active reader process is kept temporarily beyond this bound and reconsidered after the pin is released. |
|
|
661
|
+
| `WOODS_PAYLOAD_RETENTION` | `3` | How many past generations' payload directories (`payloads/gen-N/`) to retain, and — when the JSON snapshot store is in use — how many temporal snapshots (`snapshots/`) to keep. JSON snapshot retention counts corrupt or unreadable SHA-named files and prunes them first; the just-captured snapshot is protected. Cleanup failures are non-fatal. A payload pinned by an active reader process is kept temporarily beyond this bound and reconsidered after the pin is released. |
|
|
557
662
|
| `WOODS_MCP_CACHE_TTL_MS` | `10000` | Cache TTL advertised in tool result `_meta`. `0` disables caching. |
|
|
558
663
|
| `WOODS_NO_UPDATE_CHECK` | unset | Set to `"1"` to skip the `woods_status` RubyGems version check. |
|
|
559
664
|
| `XDG_CACHE_HOME` | `~/.cache` | Base directory for the best-effort update-check cache (`$XDG_CACHE_HOME/woods/update_check.json`). An unset or empty value uses `~/.cache`; if the home directory cannot be resolved, Woods falls back to the system temporary directory. |
|
|
@@ -590,24 +695,52 @@ These variables are read by the gem and its MCP servers at runtime. They complem
|
|
|
590
695
|
|
|
591
696
|
| Variable | Default | Purpose |
|
|
592
697
|
|----------|---------|---------|
|
|
593
|
-
| `WOODS_IGNORE_WATCH` | unset | Set to `"1"` to make `woods:incremental`/`woods:clean` proceed even when a daemon is (or claims to be) running. For `woods:incremental` this removes daemon coverage: a git range that fails to resolve then exits 1 instead of standing down (see [Incremental Extraction](./INCREMENTAL_EXTRACTION.md#exit-behavior-in-ci-chains)). |
|
|
698
|
+
| `WOODS_IGNORE_WATCH` | unset | Set to `"1"` to make `woods:incremental`/`woods:clean`/`woods:hook_refresh` proceed even when a daemon is (or claims to be) running. For `woods:incremental` this removes daemon coverage: a git range that fails to resolve then exits 1 instead of standing down (see [Incremental Extraction](./INCREMENTAL_EXTRACTION.md#exit-behavior-in-ci-chains)). |
|
|
594
699
|
| `WOODS_LOCK_WAIT` | `Watch::Daemon::LOCK_STALE_TIMEOUT` (600s) | How long a rake writer waits for `PipelineLock` before exiting non-zero. |
|
|
595
700
|
| `WOODS_WATCH_POLL` | auto-detected | Set to `"1"`/`"0"` to force/disable polling mode (vs. `listen` gem, e.g. in a container without inotify). |
|
|
701
|
+
| `WOODS_WATCH_POLL_INTERVAL` | `1.0` (seconds) | Positive, finite delay between polling scans; also used on native-watcher fallback. Does not force polling. Larger values reduce scan frequency and can delay detection and shutdown. |
|
|
596
702
|
| `WOODS_WATCH_DEBOUNCE` | `0.4` (seconds) | Delay before processing a batch of file-change events. |
|
|
597
703
|
| `WOODS_WATCH_FULL_THRESHOLD` | `50` | Number of changed paths in one batch that triggers a full extraction instead of incremental. |
|
|
598
704
|
| `WOODS_WATCH_IDLE_TIMEOUT` | unset (no timeout) | Seconds of inactivity before the daemon exits. |
|
|
599
705
|
| `WOODS_WATCH_CATCH_UP` | `1` (enabled) | Set to `"0"` to skip generation-watermark catch-up on daemon start. |
|
|
706
|
+
| `WOODS_WATCH_TRUST_FOREIGN_HOST` | unset (disabled) | Set to `"1"` in each task/MCP reader to trust a foreign daemon's heartbeat for up to 15 minutes, without a local pid check. See [cross-host liveness](WATCH_DAEMON.md#cross-host-liveness) for clock bounds, degraded coverage, and startup limitations. |
|
|
707
|
+
|
|
708
|
+
### Opt-in plugin refresh hooks
|
|
709
|
+
|
|
710
|
+
These settings control the plugin shell worker. Check installed
|
|
711
|
+
`woods:hook_refresh` support first; the task is unreleased after 2.0.0.beta2.
|
|
712
|
+
See [hook coverage and retry](WATCH_DAEMON.md#hooks-for-agent-sessions) and
|
|
713
|
+
[optional context limits](WATCH_DAEMON.md#optional-bounded-context-hints).
|
|
714
|
+
|
|
715
|
+
| Variable | Default | Purpose |
|
|
716
|
+
|----------|---------|---------|
|
|
717
|
+
| `WOODS_HOOKS_ENABLED` | unset (disabled) | Exact `1` enables the refresh/session hooks when an index exists. |
|
|
718
|
+
| `WOODS_HOOKS_DISABLED` | unset | Exact `1` disables both refresh and optional context hooks. |
|
|
719
|
+
| `WOODS_HOOK_CONTEXT_ENABLED` | unset (disabled) | Exact `1` enables separate bounded Claude orientation/impact hints; independent of refresh enablement. |
|
|
720
|
+
| `WOODS_HOOK_CONTEXT_COMMAND` | `bundle exec woods-hook-context` | Installed helper argv prefix; startup counts toward the fixed context deadline. Use a wrapper for quoting/container environment. |
|
|
721
|
+
| `WOODS_HOOK_CONTEXT_ROOT` | payload cwd | Explicit runtime-visible application root for context path mapping; set inside a container when host paths differ. |
|
|
722
|
+
| `WOODS_HOOK_RAKE` | `bundle exec rake` | Application command prefix; supports `docker compose exec -T app bundle exec rake`. Use a wrapper for shell quoting or explicit container environment. |
|
|
723
|
+
| `WOODS_SOURCE_CAPTURE` | internal | Private, one-use `woods-extract` child handoff. Do not set or persist this variable manually; see [source freshness](SOURCE_FRESHNESS.md). |
|
|
724
|
+
| `WOODS_HOOK_TIMEOUT_SECONDS` | `600` | PostToolUse worker deadline (SessionStart uses a fixed ten seconds), integer 1–3600 seconds; failed/deferred batches remain queued. Docker-side cancellation requires separate verification. |
|
|
725
|
+
| `WOODS_HOOK_LOCK_STALE_SECONDS` | `1800` | Age used only to reclaim legacy empty mkdir locks; live PID owners are never reclaimed merely by age. |
|
|
726
|
+
|
|
727
|
+
`woods:hook_refresh[<base64 JSON>]` is the internal plugin transport. Version 1
|
|
728
|
+
contains `output` and an `events` array of `{path, operation}` records; paths are
|
|
729
|
+
application-relative and operations are `add`, `update`, `delete`, or `move`.
|
|
730
|
+
The task validates inputs, defers active daemons with exit 75 before Rails boot,
|
|
731
|
+
and checks publication failure before acknowledging work. Use ordinary extraction
|
|
732
|
+
tasks for manual refreshes; hook transport is not a general shell execution API.
|
|
600
733
|
|
|
601
734
|
### Extraction rake tasks
|
|
602
735
|
|
|
603
736
|
| Variable | Default | Purpose |
|
|
604
737
|
|----------|---------|---------|
|
|
605
|
-
| `WOODS_OUTPUT` | `Woods.configuration.output_dir` | Overrides the output directory for
|
|
738
|
+
| `WOODS_OUTPUT` | `Woods.configuration.output_dir` | Overrides the output directory for extraction/watch and embedding tasks without editing the initializer; embedding also places its default SQLite metadata database there. Explicit database options take precedence. |
|
|
606
739
|
| `CHANGED_FILES` | unset | Comma-separated explicit changed-path list for `woods:incremental`; when set, git range resolution is skipped entirely. |
|
|
607
740
|
| `CI_COMMIT_BEFORE_SHA`, `CI_COMMIT_SHA` | unset (GitLab) | Build the diff range `<before>..<after>` for `woods:incremental`. A zero before-SHA (new branch) makes the range unresolvable, which exits 1 unless a running daemon covers the index. |
|
|
608
741
|
| `GITHUB_BASE_REF` | unset (GitHub Actions) | Build the diff range `origin/<ref>...HEAD` for `woods:incremental`; an unfetched ref makes the range unresolvable, same exit behavior. |
|
|
609
742
|
| `RAILS_ENV` | `development` | Rails environment the rake tasks boot in. |
|
|
610
|
-
| `WOODS_PROFILE` | unset | Set to `"1"` to log
|
|
743
|
+
| `WOODS_PROFILE` | unset | Set to `"1"` to log disjoint `[Woods] [profile] <phase> in N.NNs` durations, including git enrichment, reconciliation, payload sync, pointer publication (`publish`) and retention (`payload prune`). Separate `[profile total]` lines report whole extraction wall time, including unprofiled setup and failed runs; never add these totals to phase durations. Excludes process/Rails boot before extraction. Off by default. |
|
|
611
744
|
| `WOODS_GIT_DIR` | unset | Absolute path to the canonical git directory. Wins over the repository Woods would otherwise find, at all three of its git call sites: per-unit `commit_count`/`change_frequency` (enrichment), `manifest.json`'s `git_branch`/`git_sha` (provenance), and the `woods:incremental` diff range. All three build their command line with `Woods::GitCommand.argv`. |
|
|
612
745
|
| `GIT_BRANCH`, `GIT_SHA` | unset | Provenance for a checkout with no `.git` at all (a source tarball, a Docker `COPY` that excludes it). Ignored when a `.git` is present but unresolvable, so a stale build arg cannot mask a worktree. |
|
|
613
746
|
|
|
@@ -645,6 +778,49 @@ WOODS_GIT_DIR=/canonical-git bundle exec rake woods:extract
|
|
|
645
778
|
|
|
646
779
|
The `woods-mcp` bootstrapper emits a one-line STDERR banner at startup indicating whether semantic search is enabled and which provider is active. If no key/instance is found, pattern search still works and `codebase_retrieve` surfaces an actionable fix message.
|
|
647
780
|
|
|
781
|
+
## Git enrichment history
|
|
782
|
+
|
|
783
|
+
Current source requires **Git 2.31 or newer** for optional per-unit git
|
|
784
|
+
metadata. Extraction still succeeds when git is unavailable or history cannot
|
|
785
|
+
be read completely. Git enrichment is omitted in either case; a failed or
|
|
786
|
+
incomplete streamed history read logs a warning.
|
|
787
|
+
This requirement and the history policy below are unreleased after 2.0.0.beta2.
|
|
788
|
+
|
|
789
|
+
Per-unit enrichment also requires a non-shallow repository. A shallow checkout
|
|
790
|
+
or a failed repository-depth probe omits enrichment with one warning per
|
|
791
|
+
extractor instance. Fetch complete history (`git fetch --unshallow`, or
|
|
792
|
+
`actions/checkout` with `fetch-depth: 0`) and run full extraction to refresh
|
|
793
|
+
retained metadata. If depth cannot be verified, check git access and version.
|
|
794
|
+
A source archive without a repository remains quiet.
|
|
795
|
+
|
|
796
|
+
Full and incremental extraction use one streamed `HEAD` history walk, restricted
|
|
797
|
+
to the last 365 days by git's `--since` traversal. Only requested app-owned paths
|
|
798
|
+
are retained. Commit counts and contributors describe **HEAD-reachable touched-path
|
|
799
|
+
events**, independent of which other paths are requested:
|
|
800
|
+
|
|
801
|
+
- A root commit compares with an empty tree; a normal commit compares with its parent.
|
|
802
|
+
- A merge compares with its first parent, while traversal still visits all parents.
|
|
803
|
+
A normal merge can therefore count both a side commit and the merge that introduces
|
|
804
|
+
its change. An `ours` merge touches no paths itself, but its side commits remain
|
|
805
|
+
reachable and count. A conflict-resolution merge counts when its result differs
|
|
806
|
+
from the first parent.
|
|
807
|
+
- Renames are deletion/addition events at exact current names; Woods never follows
|
|
808
|
+
previous names. Unmerged branches, remote-only refs, and checkpoint refs are excluded.
|
|
809
|
+
- `change_frequency` uses total events and events newer than 90 days. Contributors
|
|
810
|
+
and recent commits retain their existing top-five limit; recent commits and
|
|
811
|
+
`last_modified` follow git's traversal order, not a separate global timestamp sort.
|
|
812
|
+
|
|
813
|
+
**Compatibility:** merge-related counts, authors, recent commits, and derived
|
|
814
|
+
churn analysis can differ from the old 500-path batches, which used git's
|
|
815
|
+
pathspec history simplification. This is an intentional semantics change.
|
|
816
|
+
Run a full extraction after upgrading to replace retained incremental metadata
|
|
817
|
+
consistently. No configuration key enables the new policy; rollback uses the
|
|
818
|
+
previous gem plus a full extraction. Shallow checkouts still provide truncated
|
|
819
|
+
history; fetch the complete history for complete 365-day evidence.
|
|
820
|
+
|
|
821
|
+
The single walk removes repeated pathspec matching. Its end-to-end speedup on
|
|
822
|
+
the original #305 host has not yet been measured.
|
|
823
|
+
|
|
648
824
|
## Database compatibility
|
|
649
825
|
|
|
650
826
|
All storage options work with both MySQL and PostgreSQL, except:
|
|
@@ -653,3 +829,12 @@ All storage options work with both MySQL and PostgreSQL, except:
|
|
|
653
829
|
- **SQLite metadata store**: uses a standalone SQLite database file, independent of your app's database
|
|
654
830
|
|
|
655
831
|
See [BACKEND_MATRIX.md](BACKEND_MATRIX.md) for the full compatibility matrix.
|
|
832
|
+
|
|
833
|
+
### Explicit retrieval and discovery scope
|
|
834
|
+
|
|
835
|
+
On a server whose tool schema advertises them, `packages` and `source_paths` narrow
|
|
836
|
+
`search` and `codebase_retrieve` before candidate limits. These are per-call
|
|
837
|
+
arguments, not configuration settings. Inspect applied scope and completeness;
|
|
838
|
+
a narrow graph query can omit relevant cross-boundary dependencies. See the
|
|
839
|
+
[scope contract](RETRIEVAL_GUIDE.md#explicit-package-and-source-path-scopes) for
|
|
840
|
+
root/nested ownership, path normalization, errors, storage support, and cost.
|
data/docs/CONSOLE_MCP_SETUP.md
CHANGED
|
@@ -26,13 +26,23 @@ The simplest setup. The `woods:console` rake task boots Rails, then starts the e
|
|
|
26
26
|
```ruby
|
|
27
27
|
Woods.configure do |config|
|
|
28
28
|
config.console_mcp_enabled = true
|
|
29
|
-
config.
|
|
29
|
+
config.console_mcp_http_enabled = false # stdio-only; no HTTP endpoint
|
|
30
30
|
end
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
The stdio and Docker entry points exit with status 1 while
|
|
33
|
+
The stdio and Docker entry points exit with status 1 while `console_mcp_enabled` is false. Enabling it grants the MCP process live read access under the blocked-table, redaction, and credential-scanning controls described below.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
`console_mcp_http_enabled` defaults to `true` to preserve existing HTTP
|
|
36
|
+
setups. Set it to `false` for stdio-only use: HTTP guards and the Console
|
|
37
|
+
middleware pass requests through to Rails, and boot skips HTTP token checks,
|
|
38
|
+
including in production. Stdio does not send or consume a bearer token.
|
|
39
|
+
|
|
40
|
+
If both Console flags are enabled, HTTP still requires a token of at least
|
|
41
|
+
32 characters. Missing tokens warn outside production and every guarded
|
|
42
|
+
request returns 401; production refuses to boot. A configured short token
|
|
43
|
+
raises at boot in every environment while HTTP is enabled. Store the token
|
|
44
|
+
in the application's normal secret store. Before enabling HTTP later, set
|
|
45
|
+
its token, allowed origins and TLS as described in [Option C](#option-c-http-rack-middleware).
|
|
36
46
|
|
|
37
47
|
### How it works
|
|
38
48
|
|
|
@@ -172,6 +182,7 @@ In an initializer (`config/initializers/woods.rb`):
|
|
|
172
182
|
```ruby
|
|
173
183
|
Woods.configure do |config|
|
|
174
184
|
config.console_mcp_enabled = true
|
|
185
|
+
config.console_mcp_http_enabled = true
|
|
175
186
|
config.console_mcp_token = ENV.fetch('WOODS_CONSOLE_MCP_TOKEN')
|
|
176
187
|
config.console_mcp_allowed_origins = [
|
|
177
188
|
'https://rails.internal.example', # public Rails/MCP Host
|
|
@@ -483,6 +494,14 @@ Until this flag is `true`, none of the transports route traffic:
|
|
|
483
494
|
|
|
484
495
|
Keep the flag off in environments where the Console isn't needed (production web tier, CI). Flip it on per-environment, e.g. in `config/environments/development.rb` or a staging-only initializer, once the layers below are configured for that environment's threat model.
|
|
485
496
|
|
|
497
|
+
### `console_mcp_http_enabled` (HTTP transport gate)
|
|
498
|
+
|
|
499
|
+
Defaults to `true` for compatibility. Set to `false` alongside
|
|
500
|
+
`console_mcp_enabled = true` to retain stdio access without enabling HTTP or
|
|
501
|
+
requiring an HTTP token. Both flags are read at request time, so settings in
|
|
502
|
+
`config/initializers/woods.rb` take effect. Disabling HTTP does not disable
|
|
503
|
+
blocked-table, redaction, credential-scanning or rollback controls on stdio.
|
|
504
|
+
|
|
486
505
|
### `console_blocked_tables` (layer 1: table gate)
|
|
487
506
|
|
|
488
507
|
Entries are lowercased table names. A tool call is rejected at dispatch time when:
|
|
@@ -536,7 +555,11 @@ registered in a supported mode, so this setting does not enable eval.
|
|
|
536
555
|
Woods::Console::Server.rebuild_credential_index(rails_app: Rails.application)
|
|
537
556
|
```
|
|
538
557
|
|
|
539
|
-
This
|
|
558
|
+
This reads a fresh encrypted-file and key snapshot, without changing Rails' cached application credentials, and replaces the index in every live embedded Console server in this process. Each response scan keeps one complete index; a rotation cannot change its index halfway through a response. Servers released by their transports are not retained by this registry. A later server construction also reads fresh credentials.
|
|
559
|
+
|
|
560
|
+
A refresh failure (including missing files or keys, failed decryption, and invalid YAML) raises and leaves every existing index intact. Treat a failed rebuild as an operational failure: resolve the credentials deployment and retry, or restart after verification. A valid empty credential mapping intentionally clears the index. A successful rebuild replaces the old set rather than retaining removed secret values. Custom `rails_app:` collaborators that expose `credentials.config` remain supported; those collaborators own freshness of their returned config.
|
|
561
|
+
|
|
562
|
+
The method is a no-op (returns `nil`) when no live scanner remains or when `console_credential_defense_enabled` is `false`. Normal server boot still permits unavailable credentials and falls back to the other configured defenses; this permissive boot behavior does not apply to an explicit rebuild.
|
|
540
563
|
|
|
541
564
|
**Rotation warning.** At boot time, Woods checks whether any credentials file (`config/credentials.yml.enc`, `config/credentials/<env>.yml.enc`) was modified *after* the process started. When it detects this, it emits a `console.credential_index.stale` warn-level structured log line with the file path, mtime, and a hint to restart or call `rebuild_credential_index`. This check is on by default; disable it with:
|
|
542
565
|
|
|
@@ -658,7 +681,8 @@ touching ActiveRecord, and neither is registered in `tools/list`.
|
|
|
658
681
|
```ruby
|
|
659
682
|
# config/initializers/woods.rb
|
|
660
683
|
Woods.configure do |config|
|
|
661
|
-
config.console_mcp_enabled = true #
|
|
684
|
+
config.console_mcp_enabled = true # master switch
|
|
685
|
+
config.console_mcp_http_enabled = true # false for stdio-only use
|
|
662
686
|
config.console_mcp_token = ENV.fetch('WOODS_CONSOLE_MCP_TOKEN')
|
|
663
687
|
config.console_embedded_read_tools = true # unlock console_sql / console_query
|
|
664
688
|
config.console_redacted_columns = Woods::DEFAULT_CONSOLE_REDACTED_COLUMNS
|
|
@@ -753,6 +777,12 @@ For `console_query`, a schema-qualified column reference such as `orders.total`
|
|
|
753
777
|
|
|
754
778
|
Scope hashes accept Ransack-style predicate suffixes (`_eq`, `_not_eq`, `_gt`, `_gteq`, `_lt`, `_lteq`, `_in`, `_not_in`, `_null`, `_not_null`, `_present`, `_blank`, `_matches`), see the [cookbook](MCP_TOOL_COOKBOOK.md#scope-predicates) for the full table. Every column name in a suffixed key is validated before an Arel predicate is built, so SQL injection via column names is not possible.
|
|
755
779
|
|
|
780
|
+
The internal scope-array defense also uses the connected adapter's dialect and
|
|
781
|
+
MySQL session quote modes when rejecting subqueries and forbidden keywords.
|
|
782
|
+
This protects direct/legacy executor callers; supported tool schemas continue
|
|
783
|
+
to enforce their narrower parameterized scope grammar.
|
|
784
|
+
|
|
785
|
+
|
|
756
786
|
---
|
|
757
787
|
|
|
758
788
|
## Troubleshooting
|
data/docs/DOCKER_SETUP.md
CHANGED
|
@@ -87,6 +87,12 @@ docker compose exec app bundle exec rake woods:extract_framework
|
|
|
87
87
|
|
|
88
88
|
Run the watcher as its own development service or process-manager entry, not as a one-off terminal command. Docker Desktop bind mounts may not deliver reliable native filesystem events; set `WOODS_WATCH_POLL=1` for polling when needed. The watcher updates structural generations automatically, while semantic vectors still require `woods:embed_incremental`.
|
|
89
89
|
|
|
90
|
+
When host-side tasks or one-off containers read the daemon's shared index,
|
|
91
|
+
`WOODS_WATCH_TRUST_FOREIGN_HOST=1` lets those readers trust its recent heartbeat.
|
|
92
|
+
Set it in each reader process; Docker does not forward host variables by default.
|
|
93
|
+
See [cross-host liveness](WATCH_DAEMON.md#cross-host-liveness) for the 15-minute
|
|
94
|
+
crash-detection bound and single-supervisor requirement.
|
|
95
|
+
|
|
90
96
|
### Index persistence
|
|
91
97
|
|
|
92
98
|
Persist `tmp/woods/` if the index should survive container replacement. A bind mount also makes it available to optional host-side tools:
|
|
@@ -185,6 +191,16 @@ Use this only when the application bundle, a supported Ruby, and the Woods execu
|
|
|
185
191
|
| **Survives container replacement** | Only with a bind/named volume | Yes, on host disk |
|
|
186
192
|
| **Needs Ruby/Woods bundle on host** | No | Yes |
|
|
187
193
|
|
|
194
|
+
### Index filesystem performance
|
|
195
|
+
|
|
196
|
+
Bind mounts backed by virtiofs or FUSE can make each hardlink, rename and
|
|
197
|
+
metadata lookup costly. Profile `payload seed`, writes and `payload prune`
|
|
198
|
+
before tuning extraction. A container volume can reduce these costs, but it
|
|
199
|
+
changes host visibility: use a distinct index location per worktree, run MCP
|
|
200
|
+
where that path is visible, and update export/archive paths together. A single
|
|
201
|
+
shared volume for every worktree would mix their indexes. See
|
|
202
|
+
[incremental profiling](INCREMENTAL_EXTRACTION.md#profiling-fixed-costs).
|
|
203
|
+
|
|
188
204
|
## Console Server Setup
|
|
189
205
|
|
|
190
206
|
The Console Server queries live Rails state. There are two launch paths for the same embedded server.
|
|
@@ -194,13 +210,16 @@ Before either path can start, deliberately enable live-data access in the Rails
|
|
|
194
210
|
```ruby
|
|
195
211
|
Woods.configure do |config|
|
|
196
212
|
config.console_mcp_enabled = true
|
|
197
|
-
config.
|
|
213
|
+
config.console_mcp_http_enabled = false # stdio-only
|
|
198
214
|
end
|
|
199
215
|
```
|
|
200
216
|
|
|
201
217
|
The process exits with status 1 while this master switch is false. Review [Console MCP setup and security](CONSOLE_MCP_SETUP.md) before enabling it.
|
|
202
218
|
|
|
203
|
-
|
|
219
|
+
This explicitly disables HTTP Console while retaining stdio access; no HTTP
|
|
220
|
+
token is needed at boot. Existing configurations default to HTTP enabled.
|
|
221
|
+
For HTTP deployment, enable the HTTP flag and configure its token, origins
|
|
222
|
+
and TLS using the [Console setup guide](CONSOLE_MCP_SETUP.md#option-c-http-rack-middleware).
|
|
204
223
|
|
|
205
224
|
### Comparison
|
|
206
225
|
|