woods 1.6.1 → 2.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2035 -0
  3. data/CONTRIBUTING.md +253 -87
  4. data/README.md +161 -513
  5. data/SECURITY.md +92 -0
  6. data/assets/woods-wordmark-white-with-bg.png +0 -0
  7. data/docs/AGENT_GUIDE.md +204 -0
  8. data/docs/AGENT_SETUP.md +205 -0
  9. data/docs/BACKEND_MATRIX.md +470 -0
  10. data/docs/CONFIGURATION_REFERENCE.md +655 -0
  11. data/docs/CONSOLE_MCP_SETUP.md +829 -0
  12. data/docs/DOCKER_SETUP.md +454 -0
  13. data/docs/EMBEDDING_MODELS.md +136 -0
  14. data/docs/EVALUATION.md +91 -0
  15. data/docs/EXTRACTOR_REFERENCE.md +765 -0
  16. data/docs/FAQ.md +544 -0
  17. data/docs/GETTING_STARTED.md +183 -0
  18. data/docs/INCREMENTAL_EXTRACTION.md +455 -0
  19. data/docs/INTERNALS.md +418 -0
  20. data/docs/MCP_HTTP_TRANSPORT.md +144 -0
  21. data/docs/MCP_SERVERS.md +231 -0
  22. data/docs/MCP_TOOL_COOKBOOK.md +987 -0
  23. data/docs/MCP_WORKTREE_SETUP.md +127 -0
  24. data/docs/NOTION_INTEGRATION.md +283 -0
  25. data/docs/OBSIDIAN_INTEGRATION.md +170 -0
  26. data/docs/PUBLISHED_INDEX.md +213 -0
  27. data/docs/README.md +94 -0
  28. data/docs/RETRIEVAL_GUIDE.md +267 -0
  29. data/docs/TOKEN_BENCHMARK.md +68 -0
  30. data/docs/TROUBLESHOOTING.md +841 -0
  31. data/docs/UNBLOCKED_INTEGRATION.md +279 -0
  32. data/docs/UPGRADING_TO_2.md +321 -0
  33. data/docs/WATCH_DAEMON.md +667 -0
  34. data/docs/WHY_WOODS.md +219 -0
  35. data/exe/woods-console +40 -4
  36. data/exe/woods-console-mcp +21 -35
  37. data/exe/woods-mcp +20 -7
  38. data/exe/woods-mcp-http +80 -11
  39. data/exe/woods-mcp-start +57 -52
  40. data/lib/generators/woods/install_generator.rb +6 -5
  41. data/lib/generators/woods/pgvector_generator.rb +6 -3
  42. data/lib/generators/woods/templates/add_pgvector_to_woods.rb.erb +29 -9
  43. data/lib/generators/woods/templates/create_woods_tables.rb.erb +5 -1
  44. data/lib/generators/woods/templates/woods.rb.tt +49 -28
  45. data/lib/tasks/woods.rake +622 -168
  46. data/lib/tasks/woods_checks.rake +107 -0
  47. data/lib/tasks/woods_evaluation.rake +164 -80
  48. data/lib/woods/ast/call_site_extractor.rb +6 -15
  49. data/lib/woods/ast/method_extractor.rb +19 -9
  50. data/lib/woods/ast/parser.rb +54 -8
  51. data/lib/woods/atomic_file.rb +171 -2
  52. data/lib/woods/builder.rb +310 -22
  53. data/lib/woods/cache/cache_middleware.rb +7 -2
  54. data/lib/woods/cache/cache_store.rb +9 -1
  55. data/lib/woods/cache/solid_cache_store.rb +6 -4
  56. data/lib/woods/change_set.rb +88 -0
  57. data/lib/woods/checks/generation_resolution.rb +34 -0
  58. data/lib/woods/checks/moved_messages.rb +186 -0
  59. data/lib/woods/chunking/semantic_chunker.rb +160 -18
  60. data/lib/woods/console/audit_logger.rb +12 -3
  61. data/lib/woods/console/bridge_protocol.rb +3 -16
  62. data/lib/woods/console/connection_manager.rb +51 -136
  63. data/lib/woods/console/dispatch_pipeline.rb +42 -12
  64. data/lib/woods/console/embedded_executor.rb +806 -149
  65. data/lib/woods/console/eval_guard.rb +27 -20
  66. data/lib/woods/console/input_contract.rb +78 -0
  67. data/lib/woods/console/model_validator.rb +29 -1
  68. data/lib/woods/console/rack_middleware.rb +65 -42
  69. data/lib/woods/console/redactor.rb +26 -8
  70. data/lib/woods/console/safe_context.rb +58 -10
  71. data/lib/woods/console/scope_predicate_parser.rb +41 -0
  72. data/lib/woods/console/server.rb +119 -247
  73. data/lib/woods/console/sql_noise_stripper.rb +125 -16
  74. data/lib/woods/console/sql_table_scanner.rb +82 -22
  75. data/lib/woods/console/sql_validator.rb +459 -29
  76. data/lib/woods/console/table_gate.rb +2 -2
  77. data/lib/woods/console/tool_specs.rb +463 -90
  78. data/lib/woods/console/tools/tier1.rb +1 -5
  79. data/lib/woods/console/tools/tier4.rb +18 -9
  80. data/lib/woods/coordination/lock_heartbeat.rb +103 -0
  81. data/lib/woods/coordination/pipeline_lock.rb +263 -53
  82. data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
  83. data/lib/woods/db/migrator.rb +3 -9
  84. data/lib/woods/db/schema_version.rb +47 -2
  85. data/lib/woods/dependency_graph.rb +898 -64
  86. data/lib/woods/embedding/fake.rb +138 -0
  87. data/lib/woods/embedding/indexer.rb +832 -40
  88. data/lib/woods/embedding/openai.rb +77 -19
  89. data/lib/woods/embedding/provider.rb +189 -11
  90. data/lib/woods/embedding/text_preparer.rb +1 -1
  91. data/lib/woods/embedding/token_counter.rb +0 -7
  92. data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
  93. data/lib/woods/evaluation/ablation_executor.rb +67 -0
  94. data/lib/woods/evaluation/ablation_provenance.rb +38 -0
  95. data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
  96. data/lib/woods/evaluation/ablation_runner.rb +173 -0
  97. data/lib/woods/evaluation/ablation_summary.rb +65 -0
  98. data/lib/woods/evaluation/ablation_task.rb +66 -0
  99. data/lib/woods/evaluation/ablation_task_set.rb +77 -0
  100. data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
  101. data/lib/woods/evaluation/ablation_worktree.rb +71 -0
  102. data/lib/woods/evaluation/baseline.rb +60 -0
  103. data/lib/woods/evaluation/baseline_runner.rb +11 -3
  104. data/lib/woods/evaluation/evaluator.rb +41 -8
  105. data/lib/woods/evaluation/query_set.rb +79 -13
  106. data/lib/woods/evaluation/report_generator.rb +20 -1
  107. data/lib/woods/export/unit_facts.rb +0 -11
  108. data/lib/woods/extracted_unit.rb +22 -63
  109. data/lib/woods/extractor.rb +2783 -238
  110. data/lib/woods/extractors/action_cable_extractor.rb +9 -4
  111. data/lib/woods/extractors/ast_source_extraction.rb +20 -2
  112. data/lib/woods/extractors/caching_extractor.rb +46 -12
  113. data/lib/woods/extractors/callback_analyzer.rb +39 -9
  114. data/lib/woods/extractors/component_discovery.rb +123 -0
  115. data/lib/woods/extractors/concern_extractor.rb +17 -3
  116. data/lib/woods/extractors/controller_extractor.rb +389 -29
  117. data/lib/woods/extractors/decorator_extractor.rb +7 -14
  118. data/lib/woods/extractors/engine_extractor.rb +53 -8
  119. data/lib/woods/extractors/event_extractor.rb +55 -4
  120. data/lib/woods/extractors/factory_extractor.rb +49 -11
  121. data/lib/woods/extractors/graphql_extractor.rb +162 -66
  122. data/lib/woods/extractors/i18n_extractor.rb +6 -1
  123. data/lib/woods/extractors/job_extractor.rb +51 -21
  124. data/lib/woods/extractors/lib_extractor.rb +23 -17
  125. data/lib/woods/extractors/line_neutralizer.rb +171 -0
  126. data/lib/woods/extractors/mailer_extractor.rb +9 -1
  127. data/lib/woods/extractors/manager_extractor.rb +19 -2
  128. data/lib/woods/extractors/migration_extractor.rb +22 -11
  129. data/lib/woods/extractors/model_extractor.rb +292 -57
  130. data/lib/woods/extractors/package_extractor.rb +154 -0
  131. data/lib/woods/extractors/phlex_extractor.rb +18 -3
  132. data/lib/woods/extractors/policy_extractor.rb +6 -5
  133. data/lib/woods/extractors/poro_extractor.rb +13 -14
  134. data/lib/woods/extractors/pundit_extractor.rb +3 -3
  135. data/lib/woods/extractors/rails_source_extractor.rb +24 -7
  136. data/lib/woods/extractors/rake_task_extractor.rb +158 -30
  137. data/lib/woods/extractors/reference_patterns.rb +38 -0
  138. data/lib/woods/extractors/route_extractor.rb +58 -2
  139. data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
  140. data/lib/woods/extractors/serializer_extractor.rb +3 -4
  141. data/lib/woods/extractors/service_extractor.rb +11 -1
  142. data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
  143. data/lib/woods/extractors/shared_utility_methods.rb +36 -6
  144. data/lib/woods/extractors/source_nesting.rb +560 -0
  145. data/lib/woods/extractors/state_machine_extractor.rb +30 -18
  146. data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
  147. data/lib/woods/extractors/view_component_extractor.rb +28 -3
  148. data/lib/woods/extractors/view_engines/erb.rb +17 -3
  149. data/lib/woods/feedback/gap_detector.rb +9 -3
  150. data/lib/woods/feedback/store.rb +7 -1
  151. data/lib/woods/filename_utils.rb +29 -1
  152. data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
  153. data/lib/woods/flow_assembler.rb +147 -26
  154. data/lib/woods/flow_document.rb +1 -0
  155. data/lib/woods/flow_precomputer.rb +175 -22
  156. data/lib/woods/gem_mapper.rb +285 -0
  157. data/lib/woods/generation.rb +185 -0
  158. data/lib/woods/git_command.rb +38 -0
  159. data/lib/woods/git_provenance.rb +16 -2
  160. data/lib/woods/graph_analyzer.rb +564 -87
  161. data/lib/woods/index_artifact.rb +93 -23
  162. data/lib/woods/mcp/bearer_auth.rb +102 -13
  163. data/lib/woods/mcp/bootstrap_state.rb +77 -0
  164. data/lib/woods/mcp/bootstrapper.rb +582 -77
  165. data/lib/woods/mcp/config_resolver.rb +66 -6
  166. data/lib/woods/mcp/errors.rb +60 -0
  167. data/lib/woods/mcp/index_reader.rb +836 -117
  168. data/lib/woods/mcp/index_reader_pinning.rb +78 -0
  169. data/lib/woods/mcp/origin_guard.rb +66 -7
  170. data/lib/woods/mcp/protocol_policy.rb +98 -0
  171. data/lib/woods/mcp/provider_probe.rb +45 -6
  172. data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
  173. data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
  174. data/lib/woods/mcp/server.rb +898 -152
  175. data/lib/woods/mcp/tasks/extension.rb +196 -0
  176. data/lib/woods/mcp/tasks/request_capture.rb +45 -0
  177. data/lib/woods/mcp/tasks/store.rb +518 -0
  178. data/lib/woods/mcp/tool_contract.rb +171 -0
  179. data/lib/woods/mcp/tool_response_renderer.rb +7 -0
  180. data/lib/woods/model_name_cache.rb +19 -1
  181. data/lib/woods/notion/client.rb +132 -36
  182. data/lib/woods/notion/exporter.rb +456 -61
  183. data/lib/woods/notion/mappers/column_mapper.rb +34 -5
  184. data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
  185. data/lib/woods/notion/mappers/model_mapper.rb +21 -6
  186. data/lib/woods/notion/mappers/shared.rb +45 -3
  187. data/lib/woods/notion/sync_manifest.rb +258 -0
  188. data/lib/woods/obsidian/errors.rb +6 -0
  189. data/lib/woods/obsidian/name_mapper.rb +40 -24
  190. data/lib/woods/obsidian/vault_exporter.rb +103 -36
  191. data/lib/woods/operator/pipeline_guard.rb +118 -21
  192. data/lib/woods/operator/status_reporter.rb +20 -3
  193. data/lib/woods/path_dispatcher.rb +276 -0
  194. data/lib/woods/payload_store.rb +236 -0
  195. data/lib/woods/published_index/edge_shaper.rb +61 -0
  196. data/lib/woods/published_index/generation_catalog.rb +72 -0
  197. data/lib/woods/published_index/typed_unit_reader.rb +48 -0
  198. data/lib/woods/published_index.rb +287 -0
  199. data/lib/woods/railtie.rb +69 -30
  200. data/lib/woods/railtie_support.rb +167 -0
  201. data/lib/woods/release.rb +12 -0
  202. data/lib/woods/reload_policy.rb +206 -0
  203. data/lib/woods/resilience/circuit_breaker.rb +47 -8
  204. data/lib/woods/resilience/index_validator.rb +296 -10
  205. data/lib/woods/resilience/retryable_provider.rb +71 -6
  206. data/lib/woods/resolved_config.rb +55 -11
  207. data/lib/woods/retrieval/context_assembler.rb +132 -40
  208. data/lib/woods/retrieval/query_classifier.rb +26 -8
  209. data/lib/woods/retrieval/ranker.rb +193 -28
  210. data/lib/woods/retrieval/search_executor.rb +206 -39
  211. data/lib/woods/retriever.rb +317 -71
  212. data/lib/woods/retry_after.rb +22 -2
  213. data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
  214. data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
  215. data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
  216. data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
  217. data/lib/woods/ruby_analyzer/trace_enricher.rb +3 -0
  218. data/lib/woods/ruby_analyzer.rb +21 -5
  219. data/lib/woods/session_tracer/file_store.rb +138 -19
  220. data/lib/woods/session_tracer/middleware.rb +1 -2
  221. data/lib/woods/session_tracer/redis_store.rb +122 -12
  222. data/lib/woods/session_tracer/session_flow_assembler.rb +57 -17
  223. data/lib/woods/session_tracer/session_flow_document.rb +56 -14
  224. data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
  225. data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
  226. data/lib/woods/session_tracer/store.rb +14 -1
  227. data/lib/woods/storage/metadata_store.rb +230 -26
  228. data/lib/woods/storage/pgvector.rb +180 -22
  229. data/lib/woods/storage/qdrant.rb +367 -41
  230. data/lib/woods/storage/snapshotter/metadata.rb +79 -16
  231. data/lib/woods/storage/snapshotter/vector.rb +128 -17
  232. data/lib/woods/storage/snapshotter.rb +23 -5
  233. data/lib/woods/storage/vector_store.rb +49 -8
  234. data/lib/woods/storage_identity.rb +28 -0
  235. data/lib/woods/tasks.rb +53 -2
  236. data/lib/woods/temporal/json_snapshot_store.rb +112 -42
  237. data/lib/woods/temporal/snapshot_store.rb +139 -42
  238. data/lib/woods/unblocked/client.rb +119 -17
  239. data/lib/woods/unblocked/document_builder.rb +34 -2
  240. data/lib/woods/unblocked/exporter.rb +63 -27
  241. data/lib/woods/unblocked/rate_limiter.rb +23 -9
  242. data/lib/woods/unblocked/sync_manifest.rb +16 -8
  243. data/lib/woods/update_check.rb +24 -1
  244. data/lib/woods/util/uuid5.rb +124 -0
  245. data/lib/woods/version.rb +1 -1
  246. data/lib/woods/watch/daemon.rb +1345 -0
  247. data/lib/woods/watch/listen_watcher.rb +81 -0
  248. data/lib/woods/watch/polling_watcher.rb +137 -0
  249. data/lib/woods/watch/status.rb +169 -0
  250. data/lib/woods/watch/tree_scan.rb +163 -0
  251. data/lib/woods/watch/watcher.rb +100 -0
  252. data/lib/woods.rb +138 -9
  253. data/plugin/.claude-plugin/plugin.json +18 -0
  254. data/plugin/hooks/hooks.json +29 -0
  255. data/plugin/hooks/woods-post-edit.sh +226 -0
  256. data/plugin/hooks/woods-session-start.sh +77 -0
  257. data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
  258. data/plugin/skills/woods-diagnose/SKILL.md +75 -0
  259. data/plugin/skills/woods-investigate/SKILL.md +39 -0
  260. data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
  261. data/plugin/skills/woods-setup/SKILL.md +99 -0
  262. metadata +134 -23
  263. data/lib/woods/console/adapters/cache_adapter.rb +0 -58
  264. data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
  265. data/lib/woods/console/adapters/job_adapter.rb +0 -74
  266. data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
  267. data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
  268. data/lib/woods/console/bridge.rb +0 -210
  269. data/lib/woods/formatting/claude_adapter.rb +0 -98
  270. data/lib/woods/formatting/generic_adapter.rb +0 -56
  271. data/lib/woods/formatting/gpt_adapter.rb +0 -64
  272. data/lib/woods/notion/mapper.rb +0 -40
  273. data/lib/woods/observability/health_check.rb +0 -79
  274. data/lib/woods/observability/instrumentation.rb +0 -34
data/README.md CHANGED
@@ -1,628 +1,276 @@
1
1
  <p align="center">
2
- <img src="assets/woods-wordmark-white-with-bg.png" width="400" alt="woods">
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
- **Your AI coding assistant is guessing about your Rails app. Woods gives it the real answers.**
7
+ **Give AI coding agents a runtime-accurate map of your Rails application.**
8
8
 
9
- Rails hides enormous amounts of behavior behind conventions, concerns, and runtime magic. When you ask an AI assistant "what callbacks fire when a User saves?" or "what routes map to this controller?", it guesses from training data — and gets it wrong. Woods runs *inside* your Rails app, extracts what's actually happening at runtime, and serves that context directly to your AI tools via [MCP](https://modelcontextprotocol.io/).
9
+ [![Gem Version](https://img.shields.io/gem/v/woods)](https://rubygems.org/gems/woods)
10
+ [![CI](https://github.com/lost-in-the/woods/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/lost-in-the/woods/actions/workflows/ci.yml)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
10
12
 
11
- Works with **Claude Code**, **Cursor**, **Windsurf**, and any MCP-compatible tool.
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.beta2 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.beta2** | [the v2.0.0.beta2 tag](https://github.com/lost-in-the/woods/tree/v2.0.0.beta2) |
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.beta2 as a prerelease, so `gem "woods", "~> 2.0"` does not resolve it. Install it explicitly with `gem "woods", "2.0.0.beta2"`. The released constraint stays `gem "woods", "~> 1.6"`.
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
- ## The Problem
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
- Ask your AI assistant about your Rails app and watch it confidently hallucinate:
33
+ ## What Woods adds
18
34
 
19
- | You ask | What the AI says | What's actually true |
20
- |---------|-----------------|---------------------|
21
- | "What callbacks fire when User saves?" | `before_save :set_slug` | 11 callbacks across 4 files, including 3 from concerns |
22
- | "What routes map to OrdersController?" | Standard REST routes | Custom `POST /checkout`, nested under `/shops/:shop_id` |
23
- | "What does the checkout flow do?" | Describes `CheckoutService` | Misses that `order.save!` triggers 3 callbacks that enqueue 2 jobs |
24
-
25
- 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.
26
-
27
- **Woods fixes this by running inside Rails and extracting what's actually there.**
28
-
29
- See [Why Woods?](docs/WHY_WOODS.md) for detailed before/after examples.
30
-
31
- ---
32
-
33
- ## Quick Start
34
-
35
- Five steps from install to asking questions:
36
-
37
- ```bash
38
- # 1. Add to your Rails app's Gemfile
39
- gem 'woods', group: :development
40
-
41
- # 2. Install and configure
42
- bundle install
43
- rails generate woods:install
44
-
45
- # 3. Extract your codebase (requires Rails to be running)
46
- bundle exec rake woods:extract
47
- # Aliases: woods:scan
48
-
49
- # 4. Verify it worked
50
- bundle exec rake woods:stats
51
- # Aliases: woods:look
52
-
53
- # 5. Add the MCP server to your AI tool (see "Connect to Your AI Tool" below)
54
- ```
55
-
56
- 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.
57
-
58
- > **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).
59
-
60
- See [Getting Started](docs/GETTING_STARTED.md) for the full walkthrough including storage presets, CI setup, and common first-run issues.
61
-
62
- ---
63
-
64
- ## What Does It Actually Do?
65
-
66
- 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:
67
-
68
- ### Concern Inlining
69
-
70
- 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:
71
36
 
72
37
  ```ruby
73
- # What your AI sees (app/models/user.rb) — 4 lines:
74
- class User < ApplicationRecord
38
+ # app/models/order.rb
39
+ class Order < ApplicationRecord
75
40
  include Auditable
76
- include Searchable
41
+ belongs_to :customer
42
+ after_commit :enqueue_receipt, on: :create
77
43
  end
78
-
79
- # What Woods produces — full source with schema + inlined concerns:
80
- # == Schema Information
81
- # email :string not null
82
- # name :string
83
- #
84
- # class User < ApplicationRecord
85
- # include Auditable
86
- # include Searchable
87
- # validates :email, presence: true, uniqueness: true
88
- # ...
89
- # end
90
- #
91
- # ┌─────────────────────────────────────────────────────────────────────┐
92
- # │ Included from: Auditable │
93
- # └─────────────────────────────────────────────────────────────────────┘
94
- # def audit_trail ...
95
- # ─────────────────────────── End Auditable ───────────────────────────
96
- #
97
- # ┌─────────────────────────────────────────────────────────────────────┐
98
- # │ Included from: Searchable │
99
- # └─────────────────────────────────────────────────────────────────────┘
100
- # scope :search, ->(q) { where("name ILIKE ?", "%#{q}%") }
101
- # ─────────────────────────── End Searchable ───────────────────────────
102
44
  ```
103
45
 
104
- The `metadata[:inlined_concerns]` array lists which concerns were resolved, so retrieval can filter by concern inclusion.
105
-
106
- ### Schema Prepending
107
-
108
- 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`.
109
-
110
- ### Route Binding
111
-
112
- 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.
113
-
114
- ### Dependency Graph
46
+ Woods turns that runtime class into one connected unit with:
115
47
 
116
- 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.
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.
117
54
 
118
- 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.
55
+ The result is a codebase index an agent can query by exact name, pattern, dependency path, graph structure, or natural language.
119
56
 
120
- ### Callback Side-Effect Analysis
57
+ Still weighing it? [Why Woods](docs/WHY_WOODS.md) makes the case against grep, cloud indexers, and IDE language servers.
121
58
 
122
- `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.
123
-
124
- ---
125
-
126
- ## Examples
127
-
128
- ### Extracted Model with Schema and Associations
129
-
130
- After extraction, each model is a self-contained JSON file with schema, associations, validations, and inlined concern source:
131
-
132
- ```json
133
- {
134
- "type": "model",
135
- "identifier": "Order",
136
- "file_path": "app/models/order.rb",
137
- "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 ────────────────────────────",
138
- "metadata": {
139
- "associations": [
140
- { "type": "belongs_to", "name": "user", "target": "User" },
141
- { "type": "has_many", "name": "line_items", "target": "LineItem" }
142
- ],
143
- "validations": [
144
- { "attribute": "status", "type": "inclusion", "options": { "in": ["pending", "paid", "shipped"] } }
145
- ],
146
- "enums": { "status": { "pending": 0, "active": 1, "shipped": 2 } },
147
- "scopes": [{ "name": "active", "source": "-> { where(status: :active) }" }],
148
- "inlined_concerns": ["Auditable"]
149
- },
150
- "dependencies": [
151
- { "type": "model", "target": "User", "via": "belongs_to" },
152
- { "type": "model", "target": "LineItem", "via": "has_many" }
153
- ]
154
- }
155
- ```
59
+ ## Five-minute setup
156
60
 
157
- ### Callback Chain with Side-Effects
61
+ The default setup provides structural code intelligence. It does not require an embedding provider, vector database, or access to live application records.
158
62
 
159
- Woods resolves the full callback chain in execution order and detects side-effects — which columns get written, which jobs get enqueued, which mailers fire:
63
+ ### 1. Install Woods
160
64
 
161
- ```json
162
- "callbacks": [
163
- { "type": "before_validation", "filter": "normalize_email", "kind": "before", "conditions": {} },
164
- { "type": "before_save", "filter": "set_slug", "kind": "before", "conditions": {},
165
- "side_effects": { "columns_written": ["slug"], "jobs_enqueued": [], "services_called": [], "mailers_triggered": [], "database_reads": [], "operations": [] } },
166
- { "type": "after_commit", "filter": "send_welcome", "kind": "after", "conditions": {},
167
- "side_effects": { "columns_written": [], "jobs_enqueued": ["WelcomeEmailJob"], "services_called": [], "mailers_triggered": ["UserMailer"], "database_reads": [], "operations": [] } }
168
- ]
169
- ```
170
-
171
- Side-effects are detected by `CallbackAnalyzer`, which scans callback method bodies for patterns like `self.col =` (column writes), `perform_later` (job enqueues), and `deliver_later` (mailer triggers). This is the #1 thing AI tools get wrong about Rails models.
172
-
173
- ### Route-to-Controller Lookup
174
-
175
- Every route becomes its own `ExtractedUnit` with the controller and action bound from the live routing table:
176
-
177
- ```json
178
- {
179
- "type": "route",
180
- "identifier": "POST /checkout",
181
- "metadata": {
182
- "controller": "orders",
183
- "action": "create",
184
- "route_name": "checkout"
185
- }
186
- }
187
- ```
188
-
189
- To find which controller handles a URL, use the MCP `search` tool:
190
-
191
- ```json
192
- { "tool": "search", "params": { "query": "/checkout", "types": ["route"] } }
193
- ```
194
-
195
- This returns all matching route units with their controller and action — no guessing about custom routes, nested resources, or engine mount points.
196
-
197
- ### Looking Up a Model's Full Structure
198
-
199
- Use the MCP `lookup` tool to get a model's complete JSON representation — schema, associations, validations, callbacks, and inlined concerns in one call:
200
-
201
- ```json
202
- { "tool": "lookup", "params": { "identifier": "Order", "include_source": true } }
203
- ```
204
-
205
- 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`.
206
-
207
- To get just the structured metadata without source code:
208
-
209
- ```json
210
- { "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
211
70
  ```
212
71
 
213
- ### Finding Jobs Enqueued by a Service
214
-
215
- Use the MCP `dependencies` tool to trace what a service triggers:
216
-
217
- ```json
218
- { "tool": "dependencies", "params": { "identifier": "CheckoutService", "depth": 2, "types": ["job"] } }
72
+ ```bash
73
+ bundle install
74
+ bin/rails generate woods:install
219
75
  ```
220
76
 
221
- Returns all job units reachable from `CheckoutService` within 2 hops including jobs triggered indirectly via model callbacks (e.g., `CheckoutService` `Order` `OrderConfirmationJob`).
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.
222
78
 
223
- ### Tracing UI Navigation
79
+ ### 2. Extract and verify the codebase
224
80
 
225
- Use the MCP `dependents` tool with `via` filtering to find what links to a controller:
226
-
227
- ```json
228
- { "tool": "dependents", "params": { "identifier": "OrdersController", "depth": 1, "via": ["link_to", "form_action"] } }
81
+ ```bash
82
+ bin/rails woods:extract
83
+ bin/rails woods:validate
84
+ bin/rails woods:stats
229
85
  ```
230
86
 
231
- Returns view templates that navigate to `OrdersController` via `link_to` helpers or form submissions — isolating UI navigation edges from code references and other relationship types.
87
+ Extraction must run where Rails can boot. The default index lives at `tmp/woods/`.
232
88
 
233
- ### Runtime-Generated Method Detection
89
+ ### 3. Connect the Index Server
234
90
 
235
- Because Woods runs inside the booted Rails process, it captures every method Rails generates dynamically — enum predicates, association builders, attribute accessors, and scope methods that static analysis tools cannot see:
91
+ Add this to your MCP client's project configuration. The configuration location varies by client:
236
92
 
237
93
  ```json
238
94
  {
239
- "identifier": "Order",
240
- "metadata": {
241
- "enums": { "status": { "pending": 0, "active": 1, "shipped": 2 } },
242
- "scopes": [{ "name": "active", "source": "-> { where(status: :active) }" }],
243
- "associations": [{ "type": "has_many", "name": "line_items", "target": "LineItem" }]
95
+ "mcpServers": {
96
+ "woods": {
97
+ "command": "bundle",
98
+ "args": ["exec", "woods-mcp-start", "./tmp/woods"],
99
+ "cwd": "/absolute/path/to/your-rails-app"
100
+ }
244
101
  }
245
102
  }
246
103
  ```
247
104
 
248
- Static tools miss `status_active?`, `status_pending?`, `build_line_item`, `create_line_item!`, and dynamically registered scopes. Woods captures all of these because it queries the runtime class via `instance_methods(false)` after Rails has processed every DSL declaration.
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.
249
106
 
250
- ---
107
+ The Index Server reads the published index from disk. It does not boot Rails or query application records.
251
108
 
252
- ## Claude Code plugin
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).
253
110
 
254
- Woods publishes three **user-facing guide skills** as a Claude Code plugin so agents can walk
255
- 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).
256
112
 
257
- - `woods-setup` install, configure, extract, verify, connect MCP servers
258
- - `woods-mcp-config` — generate a correct `.mcp.json` for your environment
259
- - `woods-diagnose` — systematic troubleshooting for extraction/MCP/embedding/storage
113
+ ## Let an agent install it
260
114
 
261
- The plugin ships from the [`lost-in-the/plugins`](https://github.com/lost-in-the/plugins)
262
- marketplace suite, which references this repo's `plugin/` subtree via a `git-subdir` source —
263
- 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:
264
116
 
265
117
  ```bash
266
- # In Claude Code:
267
118
  /plugin marketplace add lost-in-the/plugins
268
119
  /plugin install woods-plugin@lost-in-the-plugins
269
120
  ```
270
121
 
271
- The skill files live in this repo under [`plugin/skills/`](plugin/skills/) (the plugin root is
272
- [`plugin/`](plugin/), with its manifest at `plugin/.claude-plugin/plugin.json`). Each skill
273
- opens with a Version Preflight so agents operate only against your installed Woods version. To
274
- test a local checkout: `claude --plugin-dir /path/to/woods/plugin`. Requires **Woods 1.5.0**.
275
- The internal dev-workflow skills under `.claude/skills/` are **not** part of this plugin.
276
-
277
- ## Connect to Your AI Tool
278
-
279
- Woods ships two MCP servers. Most users only need the **Index Server**.
280
-
281
- ### Index Server Reads Pre-Extracted Data (No Rails Required)
282
-
283
- 29 tools for code lookup, dependency traversal, semantic search, graph analysis, and more (14 always-on + 15 that register based on wiring: 5 operator / 4 feedback / 4 snapshot / 1 session-trace / 1 Notion). Reads static JSON from disk — fast, no Rails boot needed.
284
-
285
- **Claude Code** — add to `.mcp.json` in your project root:
286
-
287
- ```json
288
- {
289
- "mcpServers": {
290
- "woods": {
291
- "command": "woods-mcp-start",
292
- "args": ["./tmp/woods"]
293
- }
294
- }
295
- }
296
- ```
297
-
298
- > `woods-mcp-start` is a self-healing wrapper that validates the index, checks dependencies, and auto-restarts on failure. Recommended for Claude Code.
299
-
300
- **Cursor / Windsurf** — add to your MCP config:
301
-
302
- ```json
303
- {
304
- "mcpServers": {
305
- "woods": {
306
- "command": "woods-mcp",
307
- "args": ["/path/to/your-rails-app/tmp/woods"]
308
- }
309
- }
310
- }
311
- ```
312
-
313
- ### Console Server — Live Rails Queries (Optional)
314
-
315
- 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.
316
-
317
- ```json
318
- {
319
- "mcpServers": {
320
- "woods-console": {
321
- "command": "bundle",
322
- "args": ["exec", "rake", "woods:console"],
323
- "cwd": "/path/to/your-rails-app"
324
- }
325
- }
326
- }
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.
327
136
  ```
328
137
 
329
- See [MCP Servers](docs/MCP_SERVERS.md) for the full tool catalog and [MCP Tool Cookbook](docs/MCP_TOOL_COOKBOOK.md) for scenario-based examples.
330
-
331
- ---
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.
332
139
 
333
- ## What Gets Extracted
140
+ ## Choose your path
334
141
 
335
- 34 extractors cover every major Rails concept:
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) |
336
153
 
337
- | Category | What's Extracted | Key Details |
338
- |----------|-----------------|-------------|
339
- | **Models** | Schema, associations, validations, scopes, callbacks, enums | Concerns inlined, callback side-effects analyzed |
340
- | **Controllers** | Actions, filters, permitted params, response formats | Route map prepended, per-action filter chains |
341
- | **Services & Jobs** | Entry points, dependencies, retry config, queue names | Includes services, interactors, operations, commands |
342
- | **Views & Components** | ERB templates, Phlex components, ViewComponents | Partial references, slot definitions, prop interfaces, navigation edges (link_to, form_action) |
343
- | **Routes & Middleware** | Full route table, middleware stack order | Constraint resolution, engine mount points |
344
- | **GraphQL** | Types, mutations, resolvers, fields | Relay connections, argument definitions |
345
- | **Background Work** | Jobs, mailers, Action Cable channels, scheduled tasks | Queue configuration, retry policies |
346
- | **Data Layer** | Migrations, database views, state machines, events | DDL metadata, reversibility, transition graphs |
347
- | **Testing** | Factories, test-to-source mappings | FactoryBot definitions, spec file associations |
348
- | **Framework Source** | Rails internals, gem source for exact installed versions | Pinned to your `Gemfile.lock` versions |
154
+ ## Upgrading from 1.x
349
155
 
350
- See [Extractor Reference](docs/EXTRACTOR_REFERENCE.md) for per-extractor documentation with configuration options and example output.
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.
351
157
 
352
- ---
158
+ ## Optional Claude Code workflows
353
159
 
354
- ## Use Cases
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.
355
161
 
356
- ### For AI-Assisted Development
162
+ ## Two servers, two trust boundaries
357
163
 
358
- - **Context-aware code generation** your AI sees the full model (with concerns, schema, and callbacks) before writing new code
359
- - **Feature planning** — query the dependency graph to understand blast radius before changing anything
360
- - **PR context** — compute affected units from a diff and explain downstream impact
361
- - **Code review** — surface hidden callback side-effects that a reviewer might miss
362
- - **Onboarding** — new team members ask "how does checkout work?" and get the real execution flow
164
+ Woods ships two MCP servers. Most users only need the Index Server.
363
165
 
364
- ### For Architecture & Technical Debt
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 |
365
173
 
366
- - **Dead code detection** `GraphAnalyzer` finds orphaned units with no dependents
367
- - **Hub identification** — find models with 50+ dependents that are bottlenecks
368
- - **Cycle detection** — circular dependencies surfaced automatically
369
- - **Migration risk** — DDL metadata shows which pending migrations touch large tables
370
- - **API surface audit** — every endpoint, its method, path, filters, and permitted params
371
- - **Callback chain auditing** — the #1 source of Rails bugs, now visible and traceable
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.
372
175
 
373
- ---
176
+ See [MCP servers](docs/MCP_SERVERS.md) for the callable tool lists and client configuration.
374
177
 
375
- ## Configuration
178
+ ## Optional semantic search
376
179
 
377
- ### Zero-Config Start
378
-
379
- The install generator creates a working configuration. The only required option is `output_dir`, which defaults to `tmp/woods`:
180
+ Exact search, lookup, graph traversal, and flow tools work after extraction alone. Natural-language retrieval through `codebase_retrieve` also needs embeddings:
380
181
 
381
182
  ```ruby
382
183
  # config/initializers/woods.rb
383
- Woods.configure do |config|
384
- config.output_dir = Rails.root.join('tmp/woods')
385
- end
386
- ```
387
-
388
- ### Storage Presets
389
-
390
- For embedding and semantic search, use a preset to configure storage and embedding together:
391
-
392
- ```ruby
393
- # Local development — no external services needed
394
184
  Woods.configure_with_preset(:local)
395
-
396
- # PostgreSQL — pgvector + OpenAI embeddings
397
- Woods.configure_with_preset(:postgresql)
398
-
399
- # Production scale — Qdrant + OpenAI embeddings
400
- Woods.configure_with_preset(:production)
401
185
  ```
402
186
 
403
- ### Backend Compatibility
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:
404
188
 
405
- Woods is backend-agnostic. Your app database, vector store, embedding provider, and job system are all configurable independently:
406
-
407
- | Component | Options |
408
- |-----------|---------|
409
- | **App Database** | MySQL, PostgreSQL, SQLite |
410
- | **Vector Store** | In-memory, pgvector, Qdrant |
411
- | **Embeddings** | OpenAI, Ollama (local, free) |
412
- | **Job System** | Sidekiq, Solid Queue, GoodJob, inline |
413
- | **View Layer** | ERB, Phlex, ViewComponent |
189
+ ```bash
190
+ ollama pull nomic-embed-text
191
+ ```
414
192
 
415
- See [Backend Matrix](docs/BACKEND_MATRIX.md) for supported combinations and [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) for every option with defaults.
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).
416
194
 
417
- ### Environment-Specific Configuration
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.
418
196
 
419
- ```ruby
420
- Woods.configure do |config|
421
- config.output_dir = Rails.root.join('tmp/woods')
422
-
423
- # CI: only extract models and controllers for faster builds
424
- config.extractors = %i[models controllers] if ENV['CI']
425
-
426
- # Environment-conditional embedding provider
427
- if ENV['OPENAI_API_KEY']
428
- config.embedding_provider = :openai
429
- config.embedding_options = { api_key: ENV['OPENAI_API_KEY'] }
430
- else
431
- config.embedding_provider = :ollama
432
- config.embedding_options = { model: 'nomic-embed-text', host: 'http://localhost:11434' }
433
- end
434
- end
197
+ ```bash
198
+ bin/rails woods:embed
435
199
  ```
436
200
 
437
- ---
201
+ Reconnect the Index Server after the first embed, then check `woods_status` before using `codebase_retrieve`.
438
202
 
439
- ## Keeping the Index Current
203
+ ## Keeping the index current
440
204
 
441
- ### Incremental Updates
442
-
443
- After the initial extraction, update only changed files — typically 5-10x faster:
205
+ Run a full extraction after installation or broad configuration changes:
444
206
 
445
207
  ```bash
446
- bundle exec rake woods:incremental
447
- # Aliases: woods:tend
448
- ```
449
-
450
- ### CI Integration
451
-
452
- ```yaml
453
- # .github/workflows/index.yml
454
- jobs:
455
- index:
456
- runs-on: ubuntu-latest
457
- steps:
458
- - uses: actions/checkout@v4
459
- with:
460
- fetch-depth: 2
461
- - name: Update index
462
- run: bundle exec rake woods:incremental
463
- env:
464
- GITHUB_BASE_REF: ${{ github.base_ref }}
208
+ bin/rails woods:extract
465
209
  ```
466
210
 
467
- ### Other Tasks
211
+ For automatic maintenance during development, run the watcher as a dedicated process:
468
212
 
469
213
  ```bash
470
- rake woods:validate # Check index integrity (alias: woods:vet)
471
- rake woods:stats # Show unit counts and graph stats (alias: woods:look)
472
- rake woods:clean # Remove index output (alias: woods:clear)
473
- rake woods:embed # Embed units for semantic search (alias: woods:nest)
474
- rake woods:embed_incremental # Embed changed units only (alias: woods:hone)
475
- rake woods:notion_sync # Sync models/columns to Notion (alias: woods:send)
476
- rake woods:obsidian # Export to an Obsidian vault — graph view + Bases (alias: woods:vault)
214
+ bin/rails woods:watch
477
215
  ```
478
216
 
479
- > **Visualize connections in Obsidian.** `woods:obsidian` renders the codebase as a self-contained
480
- > [Obsidian](https://obsidian.md) vault: one interlinked note per unit (explore the dependency graph
481
- > in graph view), a filterable [Bases](https://help.obsidian.md/bases) table, and a `_woods/` machine
482
- > sidecar so agents can load the whole topology in one read. See [Obsidian Integration](docs/OBSIDIAN_INTEGRATION.md).
483
-
484
- ---
485
-
486
- ## How It Works Under the Hood
217
+ Add it to your development process manager so it starts beside Rails:
487
218
 
219
+ ```text
220
+ # Procfile.dev
221
+ web: bin/rails server
222
+ woods: bundle exec rake woods:watch
488
223
  ```
489
- Inside your Rails app (rake task):
490
- 1. Boot Rails, eager-load all application classes
491
- 2. 34 extractors introspect models, controllers, routes, etc.
492
- 3. Dependency graph is built with forward + reverse edges
493
- 4. Git metadata enriches each unit (last modified, contributors, churn)
494
- 5. JSON output written to tmp/woods/
495
-
496
- On the host (no Rails needed):
497
- 6. Embedding pipeline chunks and vectorizes units (optional)
498
- 7. MCP Index Server reads JSON and answers AI tool queries
499
- ```
500
-
501
- ### The ExtractedUnit
502
-
503
- Everything flows through `ExtractedUnit` — the universal data structure. Each unit carries:
504
-
505
- | Field | What It Contains |
506
- |-------|-----------------|
507
- | `identifier` | Class name or descriptive key (`"User"`, `"POST /orders"`) |
508
- | `type` | Category (`:model`, `:controller`, `:service`, `:job`, etc.) |
509
- | `file_path` | Source file location relative to Rails root |
510
- | `namespace` | Module namespace (`"Admin"`, `nil` for top-level) |
511
- | `source_code` | Annotated source with inlined concerns and schema |
512
- | `metadata` | Structured data — associations, callbacks, routes, fields |
513
- | `dependencies` | What this unit depends on (forward edges) |
514
- | `dependents` | What depends on this unit (reverse edges) |
515
- | `chunks` | Semantic sub-sections for large units |
516
- | `extracted_at` | ISO 8601 timestamp of extraction |
517
- | `source_hash` | SHA-256 digest for change detection |
518
224
 
519
- ### Output Structure
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.**
520
226
 
521
- ```
522
- tmp/woods/
523
- ├── manifest.json # Git SHA, timestamps, checksums
524
- ├── dependency_graph.json # Full graph with PageRank scores
525
- ├── SUMMARY.md # Human-readable overview
526
- ├── models/
527
- │ ├── _index.json # Quick lookup index
528
- │ ├── User.json # Full unit with inlined concerns
529
- │ └── Order.json
530
- ├── controllers/
531
- │ └── OrdersController.json # With route map prepended
532
- ├── services/
533
- │ └── CheckoutService.json
534
- └── rails_source/
535
- └── ... # Framework source for installed versions
536
- ```
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.
537
228
 
538
- ### Architecture Diagram
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:
539
230
 
540
- ```
541
- ┌──────────────────────────────────────────────────────────────────┐
542
- │ Rails Application │
543
- │ │
544
- │ ┌────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
545
- │ │ Extract │───>│ Resolve │───>│ Write JSON │ │
546
- │ │ 34 types │ │ graph + │ │ per unit │ │
547
- │ │ │ │ git data │ │ │ │
548
- │ └────────────┘ └─────────────┘ └──────────────────────┘ │
549
- └──────────────────────────────────────────────────────────────────┘
550
-
551
- ┌─────────────────────────┘
552
-
553
- ┌──────────────────────────────────────────────────────────────────┐
554
- │ Host / CI Environment │
555
- │ │
556
- │ ┌────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
557
- │ │ Embed │───>│ Vector Store│ │ MCP Index Server │ │
558
- │ │ OpenAI / │ │ pgvector / │ │ 29 tools │ │
559
- │ │ Ollama │ │ Qdrant │ │ No Rails required │ │
560
- │ └────────────┘ └─────────────┘ └──────────────────────┘ │
561
- │ │
562
- │ ┌────────────────────────────────┐ │
563
- │ │ Console MCP Server │ │
564
- │ │ 31 tools, bridges to Rails │ │
565
- │ └────────────────────────────────┘ │
566
- └──────────────────────────────────────────────────────────────────┘
231
+ ```ruby
232
+ step "Woods: refresh", "bin/rails woods:incremental"
567
233
  ```
568
234
 
569
- See [Architecture](docs/ARCHITECTURE.md) for the deep dive extraction phases, graph internals, retrieval pipeline, and semantic chunking.
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).
570
236
 
571
- ---
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.
572
238
 
573
- ## Advanced Features
239
+ ## What gets indexed
574
240
 
575
- | Feature | What It Does | Guide |
576
- |---------|-------------|-------|
577
- | **Semantic Search** | Natural-language queries like "find email validation logic" | [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) |
578
- | **Temporal Snapshots** | Compare extraction state across git SHAs | [FAQ](docs/FAQ.md#what-are-temporal-snapshots) |
579
- | **Session Tracing** | Record which code paths fire during a browser session | [FAQ](docs/FAQ.md#what-does-the-session-tracer-do) |
580
- | **Notion Export** | Sync model/column data to Notion for non-technical stakeholders | [Notion Integration](docs/NOTION_INTEGRATION.md) |
581
- | **Graph Analysis** | Find orphans, hubs, cycles, bridges in your dependency graph | [Architecture](docs/ARCHITECTURE.md) |
582
- | **Evaluation Harness** | Measure retrieval precision, recall, and MRR | [Architecture](docs/ARCHITECTURE.md) |
583
- | **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:
584
242
 
585
- ---
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.
586
249
 
587
- ## Documentation
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.
588
251
 
589
- | Guide | Who It's For | Description |
590
- |-------|-------------|-------------|
591
- | [Getting Started](docs/GETTING_STARTED.md) | Everyone | Install, configure, extract, inspect |
592
- | [FAQ](docs/FAQ.md) | Everyone | Common questions about setup, extraction, MCP, Docker |
593
- | [Troubleshooting](docs/TROUBLESHOOTING.md) | Everyone | Symptom → cause → fix |
594
- | [MCP Servers](docs/MCP_SERVERS.md) | Setup | Full tool catalog for Claude Code, Cursor, Windsurf |
595
- | [MCP Tool Cookbook](docs/MCP_TOOL_COOKBOOK.md) | Daily use | Scenario-based "how do I..." examples |
596
- | [Docker Setup](docs/DOCKER_SETUP.md) | Docker users | Container extraction + host MCP server |
597
- | [Configuration Reference](docs/CONFIGURATION_REFERENCE.md) | Customization | Every option with defaults |
598
- | [Extractor Reference](docs/EXTRACTOR_REFERENCE.md) | Deep dive | What each of the 34 extractors captures |
599
- | [Architecture](docs/ARCHITECTURE.md) | Contributors | Pipeline stages, graph internals, retrieval |
600
- | [Backend Matrix](docs/BACKEND_MATRIX.md) | Infrastructure | Supported database, vector, and embedding combos |
601
- | [Why Woods?](docs/WHY_WOODS.md) | Evaluation | Detailed before/after comparisons |
252
+ ## Security boundary
602
253
 
603
- ---
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.
604
255
 
605
- ## Requirements
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.
606
257
 
607
- - Ruby >= 3.0 (through Ruby 4.0)
608
- - Rails >= 6.0
258
+ Report vulnerabilities privately through [SECURITY.md](SECURITY.md).
609
259
 
610
- CI exercises Rails 6.0, 6.1, 7.0, 7.1, 7.2, and 8.0 across Ruby 3.0–4.0 — see the supported-version table in [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md).
611
-
612
- Works with MySQL, PostgreSQL, and SQLite. No additional infrastructure required for basic extraction — embedding and vector search are optional add-ons.
260
+ ## Documentation
613
261
 
614
- ## Development
262
+ Use the [documentation index](docs/README.md) to find guides by task or audience. Frequently used references include:
615
263
 
616
- ```bash
617
- bin/setup # Install dependencies
618
- bundle exec rake spec # Run tests (~3300 examples)
619
- bundle exec rubocop # Lint
620
- ```
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)
621
269
 
622
270
  ## Contributing
623
271
 
624
- Bug reports and pull requests are welcome on GitHub at https://github.com/lost-in-the/woods. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
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).
625
273
 
626
274
  ## License
627
275
 
628
- Available as open source under the [MIT License](LICENSE.txt).
276
+ Woods is available under the [MIT License](LICENSE.txt).