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
data/README.md
CHANGED
|
@@ -1,634 +1,276 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/woods-wordmark-white-with-bg.png" width="400" alt="
|
|
2
|
+
<img src="assets/woods-wordmark-white-with-bg.png" width="400" alt="Woods">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# Woods
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
**Give AI coding agents a runtime-accurate map of your Rails application.**
|
|
8
|
+
|
|
9
|
+
[](https://rubygems.org/gems/woods)
|
|
10
|
+
[](https://github.com/lost-in-the/woods/actions/workflows/ci.yml)
|
|
11
|
+
[](LICENSE.txt)
|
|
12
|
+
|
|
13
|
+
<!-- release-state:version-banner -->
|
|
14
|
+
> **This tree documents version 2.0.0.** It is a major update from 1.x: read [what changed and how to upgrade](docs/UPGRADING_TO_2.md) before updating. The full history is in the [CHANGELOG](CHANGELOG.md).
|
|
15
|
+
>
|
|
16
|
+
> `main` is the development branch and can run ahead of the latest published gem. The gem badge above shows the latest published version; documentation for a published version lives on its tag.
|
|
17
|
+
>
|
|
18
|
+
> ### Version: 2.0.0.beta1 is published as a prerelease; `main` documents 2.0.0
|
|
19
|
+
>
|
|
20
|
+
> | Line | Version | Documentation |
|
|
21
|
+
> |---|---|---|
|
|
22
|
+
> | Documented here | **2.0.0**, unreleased | this README and the [documentation index](docs/README.md) |
|
|
23
|
+
> | Latest prerelease | **2.0.0.beta1** | [the v2.0.0.beta1 tag](https://github.com/lost-in-the/woods/tree/v2.0.0.beta1) |
|
|
24
|
+
> | Latest published gem | **1.6.1** | [the v1.6.1 tag](https://github.com/lost-in-the/woods/tree/v1.6.1) |
|
|
25
|
+
>
|
|
26
|
+
> RubyGems treats 2.0.0.beta1 as a prerelease, so `gem "woods", "~> 2.0"` does not resolve it. Install it explicitly with `gem "woods", "2.0.0.beta1"`. The released constraint stays `gem "woods", "~> 1.6"`.
|
|
11
27
|
<!-- release-state:end -->
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
Woods boots your Rails app, extracts the behavior Rails assembles at runtime, and serves it to AI tools through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Agents can inspect resolved routes, schema, associations, callbacks, included concerns, dependencies, and execution flows instead of guessing from source files alone.
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
Woods 2.0 supports Ruby 3.0 or later and Rails 6.0 through 8.x. It connects AI coding tools and agents through MCP.
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
## What Woods adds
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## The Problem
|
|
22
|
-
|
|
23
|
-
Ask your AI assistant about your Rails app and watch it confidently hallucinate:
|
|
24
|
-
|
|
25
|
-
| You ask | What the AI says | What's actually true |
|
|
26
|
-
|---------|-----------------|---------------------|
|
|
27
|
-
| "What callbacks fire when User saves?" | `before_save :set_slug` | 11 callbacks across 4 files, including 3 from concerns |
|
|
28
|
-
| "What routes map to OrdersController?" | Standard REST routes | Custom `POST /checkout`, nested under `/shops/:shop_id` |
|
|
29
|
-
| "What does the checkout flow do?" | Describes `CheckoutService` | Misses that `order.save!` triggers 3 callbacks that enqueue 2 jobs |
|
|
30
|
-
|
|
31
|
-
The AI isn't bad — it just can't see what Rails is doing. Your 40-line model file has 10x that behavior when you factor in included concerns, schema context, callback chains, validations, and association reflections. Static analysis can't reach any of it.
|
|
32
|
-
|
|
33
|
-
**Woods fixes this by running inside Rails and extracting what's actually there.**
|
|
34
|
-
|
|
35
|
-
See [Why Woods?](docs/WHY_WOODS.md) for detailed before/after examples.
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Quick Start
|
|
40
|
-
|
|
41
|
-
Five steps from install to asking questions:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
# 1. Add to your Rails app's Gemfile
|
|
45
|
-
gem 'woods', group: :development
|
|
46
|
-
|
|
47
|
-
# 2. Install and configure
|
|
48
|
-
bundle install
|
|
49
|
-
rails generate woods:install
|
|
50
|
-
|
|
51
|
-
# 3. Extract your codebase (requires Rails to be running)
|
|
52
|
-
bundle exec rake woods:extract
|
|
53
|
-
# Aliases: woods:scan
|
|
54
|
-
|
|
55
|
-
# 4. Verify it worked
|
|
56
|
-
bundle exec rake woods:stats
|
|
57
|
-
# Aliases: woods:look
|
|
58
|
-
|
|
59
|
-
# 5. Add the MCP server to your AI tool (see "Connect to Your AI Tool" below)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
After extraction, your AI tool gets accurate, structured context about every model, controller, service, job, route, and more — including all the behavior that Rails hides.
|
|
63
|
-
|
|
64
|
-
> **Docker?** Run extraction inside the container: `docker compose exec app bundle exec rake woods:extract`. The MCP server runs on the host reading volume-mounted output. See [Docker Setup](docs/DOCKER_SETUP.md).
|
|
65
|
-
|
|
66
|
-
See [Getting Started](docs/GETTING_STARTED.md) for the full walkthrough including storage presets, CI setup, and common first-run issues.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## What Does It Actually Do?
|
|
71
|
-
|
|
72
|
-
Woods boots your Rails app, introspects everything using runtime APIs, and writes structured JSON that your AI tools can read. Here's what that means in practice:
|
|
73
|
-
|
|
74
|
-
### Concern Inlining
|
|
75
|
-
|
|
76
|
-
Your `User` model includes `Auditable`, `Searchable`, and `SoftDeletable`. An AI tool reading `app/models/user.rb` sees 40 lines. Woods inlines all three concerns directly into the extracted unit — the AI sees the full 200-line behavioral surface area in one block.
|
|
35
|
+
A Rails model rarely lives in one file. Its real behavior can include database schema, generated methods, framework defaults, and concerns loaded from elsewhere:
|
|
77
36
|
|
|
78
37
|
```ruby
|
|
79
|
-
#
|
|
80
|
-
class
|
|
38
|
+
# app/models/order.rb
|
|
39
|
+
class Order < ApplicationRecord
|
|
81
40
|
include Auditable
|
|
82
|
-
|
|
41
|
+
belongs_to :customer
|
|
42
|
+
after_commit :enqueue_receipt, on: :create
|
|
83
43
|
end
|
|
84
|
-
|
|
85
|
-
# What Woods produces — full source with schema + inlined concerns:
|
|
86
|
-
# == Schema Information
|
|
87
|
-
# email :string not null
|
|
88
|
-
# name :string
|
|
89
|
-
#
|
|
90
|
-
# class User < ApplicationRecord
|
|
91
|
-
# include Auditable
|
|
92
|
-
# include Searchable
|
|
93
|
-
# validates :email, presence: true, uniqueness: true
|
|
94
|
-
# ...
|
|
95
|
-
# end
|
|
96
|
-
#
|
|
97
|
-
# ┌─────────────────────────────────────────────────────────────────────┐
|
|
98
|
-
# │ Included from: Auditable │
|
|
99
|
-
# └─────────────────────────────────────────────────────────────────────┘
|
|
100
|
-
# def audit_trail ...
|
|
101
|
-
# ─────────────────────────── End Auditable ───────────────────────────
|
|
102
|
-
#
|
|
103
|
-
# ┌─────────────────────────────────────────────────────────────────────┐
|
|
104
|
-
# │ Included from: Searchable │
|
|
105
|
-
# └─────────────────────────────────────────────────────────────────────┘
|
|
106
|
-
# scope :search, ->(q) { where("name ILIKE ?", "%#{q}%") }
|
|
107
|
-
# ─────────────────────────── End Searchable ───────────────────────────
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
The `metadata[:inlined_concerns]` array lists which concerns were resolved, so retrieval can filter by concern inclusion.
|
|
111
|
-
|
|
112
|
-
### Schema Prepending
|
|
113
|
-
|
|
114
|
-
Model source gets a header with actual column types, indexes, and foreign keys pulled from the live database. No more guessing whether `name` is a `string` or `text`, or whether there's an index on `email`.
|
|
115
|
-
|
|
116
|
-
### Route Binding
|
|
117
|
-
|
|
118
|
-
Controller source gets a route map prepended showing the real HTTP verb + path + constraints for every action. No more assuming standard REST when your app has custom routes and nested resources.
|
|
119
|
-
|
|
120
|
-
### Dependency Graph
|
|
121
|
-
|
|
122
|
-
34 extractors build a bidirectional graph: what each unit depends on, and what depends on it. Change a concern and trace every model it touches. Refactor a service and see every controller that calls it. PageRank scoring identifies the most important nodes in your codebase.
|
|
123
|
-
|
|
124
|
-
Navigation edges (`link_to`, `redirect_to`, `form_action`) trace UI user journeys through the graph — filter with the `via` parameter on `dependencies`/`dependents` tools to isolate navigation paths from code references.
|
|
125
|
-
|
|
126
|
-
### Callback Side-Effect Analysis
|
|
127
|
-
|
|
128
|
-
`CallbackAnalyzer` detects what actually happens inside callbacks — which columns get written, which jobs get enqueued, which services get called, which mailers fire. This is the #1 source of unexpected bugs in Rails, and the #1 thing AI tools get wrong.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Examples
|
|
133
|
-
|
|
134
|
-
### Extracted Model with Schema and Associations
|
|
135
|
-
|
|
136
|
-
After extraction, each model is a self-contained JSON file with schema, associations, validations, and inlined concern source:
|
|
137
|
-
|
|
138
|
-
```json
|
|
139
|
-
{
|
|
140
|
-
"type": "model",
|
|
141
|
-
"identifier": "Order",
|
|
142
|
-
"file_path": "app/models/order.rb",
|
|
143
|
-
"source_code": "# == Schema Information\n# id :bigint not null, pk\n# user_id :bigint not null, fk\n# status :string default(\"pending\")\n# total_cents :integer\n#\nclass Order < ApplicationRecord\n belongs_to :user\n has_many :line_items\n validates :status, inclusion: { in: %w[pending paid shipped] }\n ...\nend\n\n# ┌───────────────────────────────────────────────────────────────────┐\n# │ Included from: Auditable │\n# └───────────────────────────────────────────────────────────────────┘\n# module Auditable\n# ...\n# end\n# ──────────────────────── End Auditable ────────────────────────────",
|
|
144
|
-
"metadata": {
|
|
145
|
-
"associations": [
|
|
146
|
-
{ "type": "belongs_to", "name": "user", "target": "User" },
|
|
147
|
-
{ "type": "has_many", "name": "line_items", "target": "LineItem" }
|
|
148
|
-
],
|
|
149
|
-
"validations": [
|
|
150
|
-
{ "attribute": "status", "type": "inclusion", "options": { "in": ["pending", "paid", "shipped"] } }
|
|
151
|
-
],
|
|
152
|
-
"enums": { "status": { "pending": 0, "active": 1, "shipped": 2 } },
|
|
153
|
-
"scopes": [{ "name": "active", "source": "-> { where(status: :active) }" }],
|
|
154
|
-
"inlined_concerns": ["Auditable"]
|
|
155
|
-
},
|
|
156
|
-
"dependencies": [
|
|
157
|
-
{ "type": "model", "target": "User", "via": "belongs_to" },
|
|
158
|
-
{ "type": "model", "target": "LineItem", "via": "has_many" }
|
|
159
|
-
]
|
|
160
|
-
}
|
|
161
44
|
```
|
|
162
45
|
|
|
163
|
-
|
|
46
|
+
Woods turns that runtime class into one connected unit with:
|
|
164
47
|
|
|
165
|
-
|
|
48
|
+
- column types, indexes, and foreign keys from the live database;
|
|
49
|
+
- associations, validations, scopes, enums, and resolved callbacks;
|
|
50
|
+
- source from included concerns, kept beside the owning class;
|
|
51
|
+
- callback side effects such as jobs, mailers, and columns written;
|
|
52
|
+
- forward dependencies and reverse dependents;
|
|
53
|
+
- route, controller, view, job, and service relationships.
|
|
166
54
|
|
|
167
|
-
|
|
168
|
-
"callbacks": [
|
|
169
|
-
{ "type": "before_validation", "filter": "normalize_email", "kind": "before", "conditions": {} },
|
|
170
|
-
{ "type": "before_save", "filter": "set_slug", "kind": "before", "conditions": {},
|
|
171
|
-
"side_effects": { "columns_written": ["slug"], "jobs_enqueued": [], "services_called": [], "mailers_triggered": [], "database_reads": [], "operations": [] } },
|
|
172
|
-
{ "type": "after_commit", "filter": "send_welcome", "kind": "after", "conditions": {},
|
|
173
|
-
"side_effects": { "columns_written": [], "jobs_enqueued": ["WelcomeEmailJob"], "services_called": [], "mailers_triggered": ["UserMailer"], "database_reads": [], "operations": [] } }
|
|
174
|
-
]
|
|
175
|
-
```
|
|
55
|
+
The result is a codebase index an agent can query by exact name, pattern, dependency path, graph structure, or natural language.
|
|
176
56
|
|
|
177
|
-
|
|
57
|
+
Still weighing it? [Why Woods](docs/WHY_WOODS.md) makes the case against grep, cloud indexers, and IDE language servers.
|
|
178
58
|
|
|
179
|
-
|
|
59
|
+
## Five-minute setup
|
|
180
60
|
|
|
181
|
-
|
|
61
|
+
The default setup provides structural code intelligence. It does not require an embedding provider, vector database, or access to live application records.
|
|
182
62
|
|
|
183
|
-
|
|
184
|
-
{
|
|
185
|
-
"type": "route",
|
|
186
|
-
"identifier": "POST /checkout",
|
|
187
|
-
"metadata": {
|
|
188
|
-
"controller": "orders",
|
|
189
|
-
"action": "create",
|
|
190
|
-
"route_name": "checkout"
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
To find which controller handles a URL, use the MCP `search` tool:
|
|
196
|
-
|
|
197
|
-
```json
|
|
198
|
-
{ "tool": "search", "params": { "query": "/checkout", "types": ["route"] } }
|
|
199
|
-
```
|
|
63
|
+
### 1. Install Woods
|
|
200
64
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```json
|
|
208
|
-
{ "tool": "lookup", "params": { "identifier": "Order", "include_source": true } }
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Returns the full `ExtractedUnit` JSON shown in the example above, including `source_code` (with schema header and inlined concerns), `metadata` (associations, callbacks, validations, enums, scopes), `dependencies`, and `dependents`.
|
|
212
|
-
|
|
213
|
-
To get just the structured metadata without source code:
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{ "tool": "lookup", "params": { "identifier": "Order", "include_source": false, "sections": ["metadata"] } }
|
|
65
|
+
```ruby
|
|
66
|
+
# Gemfile
|
|
67
|
+
group :development do
|
|
68
|
+
gem "woods", "~> 2.0"
|
|
69
|
+
end
|
|
217
70
|
```
|
|
218
71
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
```json
|
|
224
|
-
{ "tool": "dependencies", "params": { "identifier": "CheckoutService", "depth": 2, "types": ["job"] } }
|
|
72
|
+
```bash
|
|
73
|
+
bundle install
|
|
74
|
+
bin/rails generate woods:install
|
|
225
75
|
```
|
|
226
76
|
|
|
227
|
-
|
|
77
|
+
**Do not run the generated migration for a new default installation.** The generator creates an annotated `config/initializers/woods.rb` plus a legacy application migration for `woods_units`, `woods_edges`, and `woods_embeddings`. Woods 2's shipped structural index and storage backends do not use those application tables. Remove the migration before continuing; keep and run it only when deliberately preserving an older/custom integration that uses them.
|
|
228
78
|
|
|
229
|
-
###
|
|
79
|
+
### 2. Extract and verify the codebase
|
|
230
80
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
81
|
+
```bash
|
|
82
|
+
bin/rails woods:extract
|
|
83
|
+
bin/rails woods:validate
|
|
84
|
+
bin/rails woods:stats
|
|
235
85
|
```
|
|
236
86
|
|
|
237
|
-
|
|
87
|
+
Extraction must run where Rails can boot. The default index lives at `tmp/woods/`.
|
|
238
88
|
|
|
239
|
-
###
|
|
89
|
+
### 3. Connect the Index Server
|
|
240
90
|
|
|
241
|
-
|
|
91
|
+
Add this to your MCP client's project configuration. The configuration location varies by client:
|
|
242
92
|
|
|
243
93
|
```json
|
|
244
94
|
{
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"woods": {
|
|
97
|
+
"command": "bundle",
|
|
98
|
+
"args": ["exec", "woods-mcp-start", "./tmp/woods"],
|
|
99
|
+
"cwd": "/absolute/path/to/your-rails-app"
|
|
100
|
+
}
|
|
250
101
|
}
|
|
251
102
|
}
|
|
252
103
|
```
|
|
253
104
|
|
|
254
|
-
|
|
105
|
+
Restart or reconnect your MCP client, then ask it to call `woods_status`. A ready response with non-zero unit counts confirms the path from Rails extraction to the MCP client.
|
|
255
106
|
|
|
256
|
-
|
|
107
|
+
The Index Server reads the published index from disk. It does not boot Rails or query application records.
|
|
257
108
|
|
|
258
|
-
|
|
109
|
+
> **Using Docker?** Run Rails commands inside the application container. If Woods is installed only there, launch the Index Server through that container too; a host-side server requires a host Ruby bundle and host-visible index. Follow [Docker setup](docs/DOCKER_SETUP.md).
|
|
259
110
|
|
|
260
|
-
|
|
261
|
-
you through setup, MCP configuration, and troubleshooting without leaving your editor:
|
|
111
|
+
The complete walkthrough, including expected output and first questions to ask, is in [Getting started](docs/GETTING_STARTED.md).
|
|
262
112
|
|
|
263
|
-
|
|
264
|
-
- `woods-mcp-config` — generate a correct `.mcp.json` for your environment
|
|
265
|
-
- `woods-diagnose` — systematic troubleshooting for extraction/MCP/embedding/storage
|
|
113
|
+
## Let an agent install it
|
|
266
114
|
|
|
267
|
-
|
|
268
|
-
marketplace suite, which references this repo's `plugin/` subtree via a `git-subdir` source —
|
|
269
|
-
so installing fetches only the skill files, not the whole gem:
|
|
115
|
+
Woods is built to be agent-operated, and the fastest path is handing installation to the coding agent that will use it. Claude Code users can install the distributed skills once — they trigger on install, upgrade, configuration, investigation, and diagnosis on their own:
|
|
270
116
|
|
|
271
117
|
```bash
|
|
272
|
-
# In Claude Code:
|
|
273
118
|
/plugin marketplace add lost-in-the/plugins
|
|
274
119
|
/plugin install woods-plugin@lost-in-the-plugins
|
|
275
120
|
```
|
|
276
121
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
**Claude Code** — add to `.mcp.json` in your project root:
|
|
292
|
-
|
|
293
|
-
```json
|
|
294
|
-
{
|
|
295
|
-
"mcpServers": {
|
|
296
|
-
"woods": {
|
|
297
|
-
"command": "woods-mcp-start",
|
|
298
|
-
"args": ["./tmp/woods"]
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
> `woods-mcp-start` is a self-healing wrapper that validates the index, checks dependencies, and auto-restarts on failure. Recommended for Claude Code.
|
|
305
|
-
|
|
306
|
-
**Cursor / Windsurf** — add to your MCP config:
|
|
307
|
-
|
|
308
|
-
```json
|
|
309
|
-
{
|
|
310
|
-
"mcpServers": {
|
|
311
|
-
"woods": {
|
|
312
|
-
"command": "woods-mcp",
|
|
313
|
-
"args": ["/path/to/your-rails-app/tmp/woods"]
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Console Server — Live Rails Queries (Optional)
|
|
320
|
-
|
|
321
|
-
31 tools for querying real database records, monitoring job queues, running model diagnostics, and checking schema. Connects to a live Rails process. Every query runs in a rolled-back transaction with SQL validation — safe for development use.
|
|
322
|
-
|
|
323
|
-
```json
|
|
324
|
-
{
|
|
325
|
-
"mcpServers": {
|
|
326
|
-
"woods-console": {
|
|
327
|
-
"command": "bundle",
|
|
328
|
-
"args": ["exec", "rake", "woods:console"],
|
|
329
|
-
"cwd": "/path/to/your-rails-app"
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
122
|
+
With any coding agent (no plugin needed), paste this into a session opened at your Rails app's root:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
Install or upgrade the woods gem in this Rails application by following
|
|
126
|
+
https://github.com/lost-in-the/woods/blob/main/docs/AGENT_SETUP.md.
|
|
127
|
+
Structural setup only: add the gem to the development group, run the
|
|
128
|
+
installer, extract and validate the index, and register the Index MCP
|
|
129
|
+
server for this app. Do not run the generated legacy migration, and do
|
|
130
|
+
not add embedding providers, vector databases, Console/live-data access,
|
|
131
|
+
or secrets without asking me first. If woods 1.x is already installed,
|
|
132
|
+
follow the upgrade runbook in docs/UPGRADING_TO_2.md instead and plan a
|
|
133
|
+
clean re-index. Finish by reporting the installed version, files
|
|
134
|
+
changed, commands run, and one verified woods_status call through the
|
|
135
|
+
registered MCP server.
|
|
333
136
|
```
|
|
334
137
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## What Gets Extracted
|
|
138
|
+
The runbook holds the agent to the same guardrails the skills enforce: a version preflight, minimal diffs, and explicit approval before anything beyond the structural index. Prefer doing it by hand? The five-minute setup above is the same procedure as commands.
|
|
340
139
|
|
|
341
|
-
|
|
140
|
+
## Choose your path
|
|
342
141
|
|
|
343
|
-
|
|
|
344
|
-
|
|
345
|
-
|
|
|
346
|
-
|
|
|
347
|
-
|
|
|
348
|
-
|
|
|
349
|
-
|
|
|
350
|
-
|
|
|
351
|
-
|
|
|
352
|
-
|
|
|
353
|
-
|
|
|
354
|
-
| **Framework Source** | Rails internals, gem source for exact installed versions | Pinned to your `Gemfile.lock` versions |
|
|
142
|
+
| Goal | Start here |
|
|
143
|
+
|---|---|
|
|
144
|
+
| Install Woods yourself | [Getting started](docs/GETTING_STARTED.md) |
|
|
145
|
+
| Ask a coding agent to install Woods safely | [Agent setup runbook](docs/AGENT_SETUP.md) |
|
|
146
|
+
| Configure an MCP client, Docker, or HTTP | [MCP servers](docs/MCP_SERVERS.md) |
|
|
147
|
+
| Teach an agent how to query Woods effectively | [Agent guide](docs/AGENT_GUIDE.md) |
|
|
148
|
+
| Add semantic search with OpenAI or local Ollama | [Retrieval guide](docs/RETRIEVAL_GUIDE.md) |
|
|
149
|
+
| Query live Rails data through the optional Console Server | [Console MCP setup and security](docs/CONSOLE_MCP_SETUP.md) |
|
|
150
|
+
| Keep the index current automatically while coding | [Watch daemon](docs/WATCH_DAEMON.md) |
|
|
151
|
+
| Upgrade an existing 1.x installation | [Upgrade to Woods 2.0](docs/UPGRADING_TO_2.md) |
|
|
152
|
+
| Diagnose a failure | [Troubleshooting](docs/TROUBLESHOOTING.md) |
|
|
355
153
|
|
|
356
|
-
|
|
154
|
+
## Upgrading from 1.x
|
|
357
155
|
|
|
358
|
-
|
|
156
|
+
Woods 2.0 is a major release: identifiers, the on-disk layout, the MCP surface, and task failure posture all changed. [Upgrade to Woods 2.0](docs/UPGRADING_TO_2.md) holds the full what-changed table, the step-by-step runbook with backups and rollback, and an agent-operated upgrade prompt.
|
|
359
157
|
|
|
360
|
-
##
|
|
158
|
+
## Optional Claude Code workflows
|
|
361
159
|
|
|
362
|
-
|
|
160
|
+
Woods itself is MCP-client and model independent. The separately packaged Woods plugin (install commands under [Let an agent install it](#let-an-agent-install-it)) gives Claude Code five guided skills: setup and upgrade, MCP configuration, index-driven investigation, repository agent enablement, and diagnosis. Other MCP clients do not need it; follow the human or agent runbooks linked above and configure either stdio or Streamable HTTP directly.
|
|
363
161
|
|
|
364
|
-
|
|
365
|
-
- **Feature planning** — query the dependency graph to understand blast radius before changing anything
|
|
366
|
-
- **PR context** — compute affected units from a diff and explain downstream impact
|
|
367
|
-
- **Code review** — surface hidden callback side-effects that a reviewer might miss
|
|
368
|
-
- **Onboarding** — new team members ask "how does checkout work?" and get the real execution flow
|
|
162
|
+
## Two servers, two trust boundaries
|
|
369
163
|
|
|
370
|
-
|
|
164
|
+
Woods ships two MCP servers. Most users only need the Index Server.
|
|
371
165
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
166
|
+
| | Index Server | Console Server |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| Purpose | Query pre-extracted code context | Query live Rails models and schema |
|
|
169
|
+
| Data source | Files under `tmp/woods/` | A booted Rails process and its database |
|
|
170
|
+
| Default tools | 14 | 9 |
|
|
171
|
+
| Optional tools | Semantic retrieval activates after embedding; advanced Ruby embeddings can wire more collaborators | `console_sql` and `console_query` raise the total to 11 when explicitly enabled |
|
|
172
|
+
| Default posture | Read-only index | Disabled; live-data access requires deliberate setup |
|
|
378
173
|
|
|
379
|
-
|
|
174
|
+
The 14 Index tools cover health, exact lookup, search, dependency traversal, flow tracing, graph analysis, framework source, change recency, and optional semantic retrieval. The Console Server exposes nine supported model/schema tools by default. Nineteen Tier 2/3 Console schemas (9 Tier 2, 10 Tier 3) and `console_eval` exist as source inventory but do not register in any supported mode.
|
|
380
175
|
|
|
381
|
-
|
|
176
|
+
See [MCP servers](docs/MCP_SERVERS.md) for the callable tool lists and client configuration.
|
|
382
177
|
|
|
383
|
-
|
|
178
|
+
## Optional semantic search
|
|
384
179
|
|
|
385
|
-
|
|
180
|
+
Exact search, lookup, graph traversal, and flow tools work after extraction alone. Natural-language retrieval through `codebase_retrieve` also needs embeddings:
|
|
386
181
|
|
|
387
182
|
```ruby
|
|
388
183
|
# config/initializers/woods.rb
|
|
389
|
-
Woods.configure do |config|
|
|
390
|
-
config.output_dir = Rails.root.join('tmp/woods')
|
|
391
|
-
end
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Storage Presets
|
|
395
|
-
|
|
396
|
-
For embedding and semantic search, use a preset to configure storage and embedding together:
|
|
397
|
-
|
|
398
|
-
```ruby
|
|
399
|
-
# Local development — no external services needed
|
|
400
184
|
Woods.configure_with_preset(:local)
|
|
401
|
-
|
|
402
|
-
# PostgreSQL — pgvector + OpenAI embeddings
|
|
403
|
-
Woods.configure_with_preset(:postgresql)
|
|
404
|
-
|
|
405
|
-
# Production scale — Qdrant + OpenAI embeddings
|
|
406
|
-
Woods.configure_with_preset(:production)
|
|
407
185
|
```
|
|
408
186
|
|
|
409
|
-
|
|
187
|
+
The `:local` preset uses SQLite metadata, in-memory vectors persisted under the index, and a local Ollama service. It needs the `sqlite3` gem in the application bundle plus an installed, running Ollama service, but no cloud API key. Pull the default model before the first embed:
|
|
410
188
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|-----------|---------|
|
|
415
|
-
| **App Database** | MySQL, PostgreSQL, SQLite |
|
|
416
|
-
| **Vector Store** | In-memory, pgvector, Qdrant |
|
|
417
|
-
| **Embeddings** | OpenAI, Ollama (local, free) |
|
|
418
|
-
| **Job System** | Sidekiq, Solid Queue, GoodJob, inline |
|
|
419
|
-
| **View Layer** | ERB, Phlex, ViewComponent |
|
|
189
|
+
```bash
|
|
190
|
+
ollama pull nomic-embed-text
|
|
191
|
+
```
|
|
420
192
|
|
|
421
|
-
|
|
193
|
+
MySQL/PostgreSQL applications that do not bundle `sqlite3` can use `:shared_filesystem` for local persisted stores instead. PostgreSQL/OpenAI, Qdrant/OpenAI, and shared-filesystem configurations are documented in the [backend matrix](docs/BACKEND_MATRIX.md) and [configuration reference](docs/CONFIGURATION_REFERENCE.md).
|
|
422
194
|
|
|
423
|
-
|
|
195
|
+
For dense Ruby source, add `gem "tokenizers", "~> 0.5"` for exact WordPiece token counting. Without it, Woods uses a character estimate that can over-pack some Ollama chunks.
|
|
424
196
|
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
config.output_dir = Rails.root.join('tmp/woods')
|
|
428
|
-
|
|
429
|
-
# CI: only extract models and controllers for faster builds
|
|
430
|
-
config.extractors = %i[models controllers] if ENV['CI']
|
|
431
|
-
|
|
432
|
-
# Environment-conditional embedding provider
|
|
433
|
-
if ENV['OPENAI_API_KEY']
|
|
434
|
-
config.embedding_provider = :openai
|
|
435
|
-
config.embedding_options = { api_key: ENV['OPENAI_API_KEY'] }
|
|
436
|
-
else
|
|
437
|
-
config.embedding_provider = :ollama
|
|
438
|
-
config.embedding_options = { model: 'nomic-embed-text', host: 'http://localhost:11434' }
|
|
439
|
-
end
|
|
440
|
-
end
|
|
197
|
+
```bash
|
|
198
|
+
bin/rails woods:embed
|
|
441
199
|
```
|
|
442
200
|
|
|
443
|
-
|
|
201
|
+
Reconnect the Index Server after the first embed, then check `woods_status` before using `codebase_retrieve`.
|
|
444
202
|
|
|
445
|
-
## Keeping the
|
|
203
|
+
## Keeping the index current
|
|
446
204
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
After the initial extraction, update only changed files — typically 5-10x faster:
|
|
205
|
+
Run a full extraction after installation or broad configuration changes:
|
|
450
206
|
|
|
451
207
|
```bash
|
|
452
|
-
|
|
453
|
-
# Aliases: woods:tend
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### CI Integration
|
|
457
|
-
|
|
458
|
-
```yaml
|
|
459
|
-
# .github/workflows/index.yml
|
|
460
|
-
jobs:
|
|
461
|
-
index:
|
|
462
|
-
runs-on: ubuntu-latest
|
|
463
|
-
steps:
|
|
464
|
-
- uses: actions/checkout@v4
|
|
465
|
-
with:
|
|
466
|
-
fetch-depth: 2
|
|
467
|
-
- name: Update index
|
|
468
|
-
run: bundle exec rake woods:incremental
|
|
469
|
-
env:
|
|
470
|
-
GITHUB_BASE_REF: ${{ github.base_ref }}
|
|
208
|
+
bin/rails woods:extract
|
|
471
209
|
```
|
|
472
210
|
|
|
473
|
-
|
|
211
|
+
For automatic maintenance during development, run the watcher as a dedicated process:
|
|
474
212
|
|
|
475
213
|
```bash
|
|
476
|
-
|
|
477
|
-
rake woods:stats # Show unit counts and graph stats (alias: woods:look)
|
|
478
|
-
rake woods:clean # Remove index output (alias: woods:clear)
|
|
479
|
-
rake woods:embed # Embed units for semantic search (alias: woods:nest)
|
|
480
|
-
rake woods:embed_incremental # Embed changed units only (alias: woods:hone)
|
|
481
|
-
rake woods:notion_sync # Sync models/columns to Notion (alias: woods:send)
|
|
482
|
-
rake woods:obsidian # Export to an Obsidian vault — graph view + Bases (alias: woods:vault)
|
|
214
|
+
bin/rails woods:watch
|
|
483
215
|
```
|
|
484
216
|
|
|
485
|
-
|
|
486
|
-
> [Obsidian](https://obsidian.md) vault: one interlinked note per unit (explore the dependency graph
|
|
487
|
-
> in graph view), a filterable [Bases](https://help.obsidian.md/bases) table, and a `_woods/` machine
|
|
488
|
-
> sidecar so agents can load the whole topology in one read. See [Obsidian Integration](docs/OBSIDIAN_INTEGRATION.md).
|
|
489
|
-
|
|
490
|
-
---
|
|
491
|
-
|
|
492
|
-
## How It Works Under the Hood
|
|
217
|
+
Add it to your development process manager so it starts beside Rails:
|
|
493
218
|
|
|
219
|
+
```text
|
|
220
|
+
# Procfile.dev
|
|
221
|
+
web: bin/rails server
|
|
222
|
+
woods: bundle exec rake woods:watch
|
|
494
223
|
```
|
|
495
|
-
Inside your Rails app (rake task):
|
|
496
|
-
1. Boot Rails, eager-load all application classes
|
|
497
|
-
2. 34 extractors introspect models, controllers, routes, etc.
|
|
498
|
-
3. Dependency graph is built with forward + reverse edges
|
|
499
|
-
4. Git metadata enriches each unit (last modified, contributors, churn)
|
|
500
|
-
5. JSON output written to tmp/woods/
|
|
501
|
-
|
|
502
|
-
On the host (no Rails needed):
|
|
503
|
-
6. Embedding pipeline chunks and vectorizes units (optional)
|
|
504
|
-
7. MCP Index Server reads JSON and answers AI tool queries
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### The ExtractedUnit
|
|
508
|
-
|
|
509
|
-
Everything flows through `ExtractedUnit` — the universal data structure. Each unit carries:
|
|
510
|
-
|
|
511
|
-
| Field | What It Contains |
|
|
512
|
-
|-------|-----------------|
|
|
513
|
-
| `identifier` | Class name or descriptive key (`"User"`, `"POST /orders"`) |
|
|
514
|
-
| `type` | Category (`:model`, `:controller`, `:service`, `:job`, etc.) |
|
|
515
|
-
| `file_path` | Source file location relative to Rails root |
|
|
516
|
-
| `namespace` | Module namespace (`"Admin"`, `nil` for top-level) |
|
|
517
|
-
| `source_code` | Annotated source with inlined concerns and schema |
|
|
518
|
-
| `metadata` | Structured data — associations, callbacks, routes, fields |
|
|
519
|
-
| `dependencies` | What this unit depends on (forward edges) |
|
|
520
|
-
| `dependents` | What depends on this unit (reverse edges) |
|
|
521
|
-
| `chunks` | Semantic sub-sections for large units |
|
|
522
|
-
| `extracted_at` | ISO 8601 timestamp of extraction |
|
|
523
|
-
| `source_hash` | SHA-256 digest for change detection |
|
|
524
224
|
|
|
525
|
-
|
|
225
|
+
The watcher catches up changes made while it was stopped, batches new file changes, reloads Rails code when safe, and publishes complete generations atomically. The Index Server notices a new generation on its next tool call and refreshes itself. **After the initial extraction, ordinary code changes need no manual re-extraction or MCP restart.**
|
|
526
226
|
|
|
527
|
-
|
|
528
|
-
tmp/woods/
|
|
529
|
-
├── manifest.json # Git SHA, timestamps, checksums
|
|
530
|
-
├── dependency_graph.json # Full graph with PageRank scores
|
|
531
|
-
├── SUMMARY.md # Human-readable overview
|
|
532
|
-
├── models/
|
|
533
|
-
│ ├── _index.json # Quick lookup index
|
|
534
|
-
│ ├── User.json # Full unit with inlined concerns
|
|
535
|
-
│ └── Order.json
|
|
536
|
-
├── controllers/
|
|
537
|
-
│ └── OrdersController.json # With route map prepended
|
|
538
|
-
├── services/
|
|
539
|
-
│ └── CheckoutService.json
|
|
540
|
-
└── rails_source/
|
|
541
|
-
└── ... # Framework source for installed versions
|
|
542
|
-
```
|
|
227
|
+
Changes to boot-captured state, including dependencies, initializers, database configuration, credentials, or schema, make the watcher exit with status 75 so a process supervisor can restart it cleanly. If semantic retrieval is enabled, the watcher keeps structural context current; run `bin/rails woods:embed_incremental` to update vectors.
|
|
543
228
|
|
|
544
|
-
|
|
229
|
+
In CI on Rails 8.1, add one step to `config/ci.rb` so the index the gates read matches the commit under test:
|
|
545
230
|
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
│ Rails Application │
|
|
549
|
-
│ │
|
|
550
|
-
│ ┌────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
|
|
551
|
-
│ │ Extract │───>│ Resolve │───>│ Write JSON │ │
|
|
552
|
-
│ │ 34 types │ │ graph + │ │ per unit │ │
|
|
553
|
-
│ │ │ │ git data │ │ │ │
|
|
554
|
-
│ └────────────┘ └─────────────┘ └──────────────────────┘ │
|
|
555
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
556
|
-
│
|
|
557
|
-
┌─────────────────────────┘
|
|
558
|
-
▼
|
|
559
|
-
┌──────────────────────────────────────────────────────────────────┐
|
|
560
|
-
│ Host / CI Environment │
|
|
561
|
-
│ │
|
|
562
|
-
│ ┌────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
|
|
563
|
-
│ │ Embed │───>│ Vector Store│ │ MCP Index Server │ │
|
|
564
|
-
│ │ OpenAI / │ │ pgvector / │ │ 29 tools │ │
|
|
565
|
-
│ │ Ollama │ │ Qdrant │ │ No Rails required │ │
|
|
566
|
-
│ └────────────┘ └─────────────┘ └──────────────────────┘ │
|
|
567
|
-
│ │
|
|
568
|
-
│ ┌────────────────────────────────┐ │
|
|
569
|
-
│ │ Console MCP Server │ │
|
|
570
|
-
│ │ 31 tools, bridges to Rails │ │
|
|
571
|
-
│ └────────────────────────────────┘ │
|
|
572
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
231
|
+
```ruby
|
|
232
|
+
step "Woods: refresh", "bin/rails woods:incremental"
|
|
573
233
|
```
|
|
574
234
|
|
|
575
|
-
|
|
235
|
+
Claude Code users with the Woods plugin can opt into the same refresh from a `PostToolUse` hook, plus a `SessionStart` warning scoped to commit timestamps (it does not see uncommitted edits or an older checkout). Both ship disabled; set `WOODS_HOOKS_ENABLED=1` to turn them on. See [Watch daemon](docs/WATCH_DAEMON.md#hooks-for-agent-sessions).
|
|
576
236
|
|
|
577
|
-
|
|
237
|
+
Without a resident watcher, run `bin/rails woods:incremental` after changes. See [Watch daemon](docs/WATCH_DAEMON.md) for Docker polling, failure behavior, and restart triggers.
|
|
578
238
|
|
|
579
|
-
##
|
|
239
|
+
## What gets indexed
|
|
580
240
|
|
|
581
|
-
|
|
582
|
-
|---------|-------------|-------|
|
|
583
|
-
| **Semantic Search** | Natural-language queries like "find email validation logic" | [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) |
|
|
584
|
-
| **Temporal Snapshots** | Compare extraction state across git SHAs | [FAQ](docs/FAQ.md#what-are-temporal-snapshots) |
|
|
585
|
-
| **Session Tracing** | Record which code paths fire during a browser session | [FAQ](docs/FAQ.md#what-does-the-session-tracer-do) |
|
|
586
|
-
| **Notion Export** | Sync model/column data to Notion for non-technical stakeholders | [Notion Integration](docs/NOTION_INTEGRATION.md) |
|
|
587
|
-
| **Graph Analysis** | Find orphans, hubs, cycles, bridges in your dependency graph | [Architecture](docs/ARCHITECTURE.md) |
|
|
588
|
-
| **Evaluation Harness** | Measure retrieval precision, recall, and MRR | [Architecture](docs/ARCHITECTURE.md) |
|
|
589
|
-
| **Flow Precomputation** | Per-action request flow maps (controller → model → jobs) | [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) |
|
|
241
|
+
Woods recognizes the Rails application as a connected system, including:
|
|
590
242
|
|
|
591
|
-
|
|
243
|
+
- models, concerns, controllers, routes, middleware, and engines;
|
|
244
|
+
- services, interactors, commands, jobs, mailers, and scheduled work;
|
|
245
|
+
- ERB views, Phlex components, ViewComponents, and navigation edges;
|
|
246
|
+
- GraphQL types, mutations, resolvers, and fields;
|
|
247
|
+
- policies, serializers, decorators, validators, state machines, and events;
|
|
248
|
+
- migrations, database views, factories, tests, configuration, and installed framework source.
|
|
592
249
|
|
|
593
|
-
|
|
250
|
+
Read the [extractor reference](docs/EXTRACTOR_REFERENCE.md) for the complete per-type contract and [internals](docs/INTERNALS.md) for how extraction, storage, retrieval, and MCP fit together.
|
|
594
251
|
|
|
595
|
-
|
|
596
|
-
|-------|-------------|-------------|
|
|
597
|
-
| [Getting Started](docs/GETTING_STARTED.md) | Everyone | Install, configure, extract, inspect |
|
|
598
|
-
| [FAQ](docs/FAQ.md) | Everyone | Common questions about setup, extraction, MCP, Docker |
|
|
599
|
-
| [Troubleshooting](docs/TROUBLESHOOTING.md) | Everyone | Symptom → cause → fix |
|
|
600
|
-
| [MCP Servers](docs/MCP_SERVERS.md) | Setup | Full tool catalog for Claude Code, Cursor, Windsurf |
|
|
601
|
-
| [MCP Tool Cookbook](docs/MCP_TOOL_COOKBOOK.md) | Daily use | Scenario-based "how do I..." examples |
|
|
602
|
-
| [Docker Setup](docs/DOCKER_SETUP.md) | Docker users | Container extraction + host MCP server |
|
|
603
|
-
| [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) | Customization | Every option with defaults |
|
|
604
|
-
| [Extractor Reference](docs/EXTRACTOR_REFERENCE.md) | Deep dive | What each of the 34 extractors captures |
|
|
605
|
-
| [Architecture](docs/ARCHITECTURE.md) | Contributors | Pipeline stages, graph internals, retrieval |
|
|
606
|
-
| [Backend Matrix](docs/BACKEND_MATRIX.md) | Infrastructure | Supported database, vector, and embedding combos |
|
|
607
|
-
| [Why Woods?](docs/WHY_WOODS.md) | Evaluation | Detailed before/after comparisons |
|
|
252
|
+
## Security boundary
|
|
608
253
|
|
|
609
|
-
|
|
254
|
+
Woods extraction reads application code, resolved Rails configuration, and database schema. Treat the generated index as source code: do not publish it unless the source itself may be published.
|
|
610
255
|
|
|
611
|
-
|
|
256
|
+
The optional Console Server has a larger trust boundary because it can read live application data. It is disabled by default and adds table blocking, credential scanning, column redaction, SQL validation, and rolled-back transactions when enabled. Those controls reduce risk; they do not turn production data access into a harmless default. Review [Console MCP security](docs/CONSOLE_MCP_SETUP.md#safety-model) before enabling it.
|
|
612
257
|
|
|
613
|
-
|
|
614
|
-
- Rails >= 6.0
|
|
258
|
+
Report vulnerabilities privately through [SECURITY.md](SECURITY.md).
|
|
615
259
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
Works with MySQL, PostgreSQL, and SQLite. No additional infrastructure required for basic extraction — embedding and vector search are optional add-ons.
|
|
260
|
+
## Documentation
|
|
619
261
|
|
|
620
|
-
|
|
262
|
+
Use the [documentation index](docs/README.md) to find guides by task or audience. Frequently used references include:
|
|
621
263
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
264
|
+
- [Configuration reference](docs/CONFIGURATION_REFERENCE.md)
|
|
265
|
+
- [MCP tool cookbook](docs/MCP_TOOL_COOKBOOK.md)
|
|
266
|
+
- [FAQ](docs/FAQ.md)
|
|
267
|
+
- [Troubleshooting](docs/TROUBLESHOOTING.md)
|
|
268
|
+
- [Upgrade to Woods 2.0](docs/UPGRADING_TO_2.md)
|
|
627
269
|
|
|
628
270
|
## Contributing
|
|
629
271
|
|
|
630
|
-
|
|
272
|
+
Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request. Coding agents working in the source repository should also read [AGENTS.md](https://github.com/lost-in-the/woods/blob/main/AGENTS.md).
|
|
631
273
|
|
|
632
274
|
## License
|
|
633
275
|
|
|
634
|
-
|
|
276
|
+
Woods is available under the [MIT License](LICENSE.txt).
|