woods 1.6.2 → 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.
Files changed (278) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1889 -11
  3. data/CONTRIBUTING.md +195 -129
  4. data/README.md +162 -520
  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 +620 -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 +415 -0
  19. data/docs/INTERNALS.md +415 -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 +197 -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 +77 -10
  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 +40 -1
  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/credential_index.rb +2 -20
  64. data/lib/woods/console/credential_scanner.rb +14 -14
  65. data/lib/woods/console/dispatch_pipeline.rb +42 -12
  66. data/lib/woods/console/embedded_executor.rb +806 -149
  67. data/lib/woods/console/eval_guard.rb +27 -20
  68. data/lib/woods/console/input_contract.rb +78 -0
  69. data/lib/woods/console/model_validator.rb +29 -1
  70. data/lib/woods/console/rack_middleware.rb +63 -43
  71. data/lib/woods/console/redactor.rb +26 -8
  72. data/lib/woods/console/safe_context.rb +58 -10
  73. data/lib/woods/console/scope_predicate_parser.rb +41 -0
  74. data/lib/woods/console/server.rb +135 -265
  75. data/lib/woods/console/sql_noise_stripper.rb +125 -16
  76. data/lib/woods/console/sql_table_scanner.rb +82 -22
  77. data/lib/woods/console/sql_validator.rb +459 -29
  78. data/lib/woods/console/table_gate.rb +2 -2
  79. data/lib/woods/console/tool_specs.rb +462 -88
  80. data/lib/woods/console/tools/tier1.rb +0 -3
  81. data/lib/woods/console/tools/tier4.rb +17 -7
  82. data/lib/woods/coordination/lock_heartbeat.rb +103 -0
  83. data/lib/woods/coordination/pipeline_lock.rb +263 -53
  84. data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
  85. data/lib/woods/db/migrator.rb +3 -9
  86. data/lib/woods/db/schema_version.rb +47 -2
  87. data/lib/woods/dependency_graph.rb +898 -64
  88. data/lib/woods/embedding/fake.rb +138 -0
  89. data/lib/woods/embedding/indexer.rb +832 -40
  90. data/lib/woods/embedding/openai.rb +77 -19
  91. data/lib/woods/embedding/provider.rb +189 -11
  92. data/lib/woods/embedding/text_preparer.rb +1 -1
  93. data/lib/woods/embedding/token_counter.rb +0 -7
  94. data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
  95. data/lib/woods/evaluation/ablation_executor.rb +67 -0
  96. data/lib/woods/evaluation/ablation_provenance.rb +38 -0
  97. data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
  98. data/lib/woods/evaluation/ablation_runner.rb +173 -0
  99. data/lib/woods/evaluation/ablation_summary.rb +65 -0
  100. data/lib/woods/evaluation/ablation_task.rb +66 -0
  101. data/lib/woods/evaluation/ablation_task_set.rb +77 -0
  102. data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
  103. data/lib/woods/evaluation/ablation_worktree.rb +71 -0
  104. data/lib/woods/evaluation/baseline.rb +60 -0
  105. data/lib/woods/evaluation/baseline_runner.rb +11 -3
  106. data/lib/woods/evaluation/evaluator.rb +41 -8
  107. data/lib/woods/evaluation/query_set.rb +79 -13
  108. data/lib/woods/evaluation/report_generator.rb +20 -1
  109. data/lib/woods/export/unit_facts.rb +0 -11
  110. data/lib/woods/extracted_unit.rb +22 -63
  111. data/lib/woods/extractor.rb +2503 -192
  112. data/lib/woods/extractors/action_cable_extractor.rb +9 -4
  113. data/lib/woods/extractors/ast_source_extraction.rb +20 -2
  114. data/lib/woods/extractors/caching_extractor.rb +46 -12
  115. data/lib/woods/extractors/callback_analyzer.rb +39 -9
  116. data/lib/woods/extractors/component_discovery.rb +123 -0
  117. data/lib/woods/extractors/concern_extractor.rb +17 -3
  118. data/lib/woods/extractors/controller_extractor.rb +389 -29
  119. data/lib/woods/extractors/decorator_extractor.rb +7 -14
  120. data/lib/woods/extractors/engine_extractor.rb +53 -8
  121. data/lib/woods/extractors/event_extractor.rb +55 -4
  122. data/lib/woods/extractors/factory_extractor.rb +49 -11
  123. data/lib/woods/extractors/graphql_extractor.rb +162 -66
  124. data/lib/woods/extractors/i18n_extractor.rb +6 -1
  125. data/lib/woods/extractors/job_extractor.rb +51 -21
  126. data/lib/woods/extractors/lib_extractor.rb +23 -17
  127. data/lib/woods/extractors/line_neutralizer.rb +171 -0
  128. data/lib/woods/extractors/mailer_extractor.rb +9 -1
  129. data/lib/woods/extractors/manager_extractor.rb +19 -2
  130. data/lib/woods/extractors/migration_extractor.rb +22 -11
  131. data/lib/woods/extractors/model_extractor.rb +292 -57
  132. data/lib/woods/extractors/package_extractor.rb +154 -0
  133. data/lib/woods/extractors/phlex_extractor.rb +18 -3
  134. data/lib/woods/extractors/policy_extractor.rb +6 -5
  135. data/lib/woods/extractors/poro_extractor.rb +13 -14
  136. data/lib/woods/extractors/pundit_extractor.rb +3 -3
  137. data/lib/woods/extractors/rails_source_extractor.rb +24 -7
  138. data/lib/woods/extractors/rake_task_extractor.rb +158 -30
  139. data/lib/woods/extractors/reference_patterns.rb +38 -0
  140. data/lib/woods/extractors/route_extractor.rb +58 -2
  141. data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
  142. data/lib/woods/extractors/serializer_extractor.rb +3 -4
  143. data/lib/woods/extractors/service_extractor.rb +11 -1
  144. data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
  145. data/lib/woods/extractors/shared_utility_methods.rb +36 -6
  146. data/lib/woods/extractors/source_nesting.rb +560 -0
  147. data/lib/woods/extractors/state_machine_extractor.rb +30 -18
  148. data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
  149. data/lib/woods/extractors/view_component_extractor.rb +28 -3
  150. data/lib/woods/extractors/view_engines/erb.rb +17 -3
  151. data/lib/woods/feedback/gap_detector.rb +9 -3
  152. data/lib/woods/feedback/store.rb +7 -1
  153. data/lib/woods/filename_utils.rb +29 -1
  154. data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
  155. data/lib/woods/flow_assembler.rb +63 -21
  156. data/lib/woods/flow_document.rb +1 -0
  157. data/lib/woods/flow_precomputer.rb +138 -22
  158. data/lib/woods/gem_mapper.rb +285 -0
  159. data/lib/woods/generation.rb +185 -0
  160. data/lib/woods/git_command.rb +38 -0
  161. data/lib/woods/git_provenance.rb +16 -2
  162. data/lib/woods/graph_analyzer.rb +408 -34
  163. data/lib/woods/index_artifact.rb +93 -23
  164. data/lib/woods/mcp/bearer_auth.rb +102 -13
  165. data/lib/woods/mcp/bootstrap_state.rb +77 -0
  166. data/lib/woods/mcp/bootstrapper.rb +582 -77
  167. data/lib/woods/mcp/config_resolver.rb +66 -6
  168. data/lib/woods/mcp/errors.rb +60 -0
  169. data/lib/woods/mcp/index_reader.rb +836 -117
  170. data/lib/woods/mcp/index_reader_pinning.rb +78 -0
  171. data/lib/woods/mcp/origin_guard.rb +66 -7
  172. data/lib/woods/mcp/protocol_policy.rb +98 -0
  173. data/lib/woods/mcp/provider_probe.rb +45 -6
  174. data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
  175. data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
  176. data/lib/woods/mcp/server.rb +907 -154
  177. data/lib/woods/mcp/tasks/extension.rb +196 -0
  178. data/lib/woods/mcp/tasks/request_capture.rb +45 -0
  179. data/lib/woods/mcp/tasks/store.rb +518 -0
  180. data/lib/woods/mcp/tool_contract.rb +171 -0
  181. data/lib/woods/mcp/tool_response_renderer.rb +7 -0
  182. data/lib/woods/mcp/version_aware_tool_dispatch.rb +3 -9
  183. data/lib/woods/model_name_cache.rb +19 -1
  184. data/lib/woods/notion/client.rb +132 -36
  185. data/lib/woods/notion/exporter.rb +456 -61
  186. data/lib/woods/notion/mappers/column_mapper.rb +34 -5
  187. data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
  188. data/lib/woods/notion/mappers/model_mapper.rb +21 -6
  189. data/lib/woods/notion/mappers/shared.rb +45 -3
  190. data/lib/woods/notion/sync_manifest.rb +258 -0
  191. data/lib/woods/obsidian/errors.rb +6 -0
  192. data/lib/woods/obsidian/name_mapper.rb +40 -24
  193. data/lib/woods/obsidian/vault_exporter.rb +103 -36
  194. data/lib/woods/operator/pipeline_guard.rb +118 -21
  195. data/lib/woods/operator/status_reporter.rb +20 -3
  196. data/lib/woods/path_dispatcher.rb +276 -0
  197. data/lib/woods/payload_store.rb +223 -0
  198. data/lib/woods/published_index/edge_shaper.rb +61 -0
  199. data/lib/woods/published_index/generation_catalog.rb +72 -0
  200. data/lib/woods/published_index/typed_unit_reader.rb +48 -0
  201. data/lib/woods/published_index.rb +287 -0
  202. data/lib/woods/railtie.rb +69 -30
  203. data/lib/woods/railtie_support.rb +167 -0
  204. data/lib/woods/release.rb +12 -0
  205. data/lib/woods/reload_policy.rb +206 -0
  206. data/lib/woods/resilience/circuit_breaker.rb +47 -8
  207. data/lib/woods/resilience/index_validator.rb +296 -10
  208. data/lib/woods/resilience/retryable_provider.rb +71 -6
  209. data/lib/woods/resolved_config.rb +55 -11
  210. data/lib/woods/retrieval/context_assembler.rb +132 -40
  211. data/lib/woods/retrieval/query_classifier.rb +25 -6
  212. data/lib/woods/retrieval/ranker.rb +193 -28
  213. data/lib/woods/retrieval/search_executor.rb +206 -39
  214. data/lib/woods/retriever.rb +317 -71
  215. data/lib/woods/retry_after.rb +22 -2
  216. data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
  217. data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
  218. data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
  219. data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
  220. data/lib/woods/ruby_analyzer.rb +21 -5
  221. data/lib/woods/session_tracer/file_store.rb +138 -19
  222. data/lib/woods/session_tracer/redis_store.rb +122 -12
  223. data/lib/woods/session_tracer/session_flow_assembler.rb +54 -11
  224. data/lib/woods/session_tracer/session_flow_document.rb +52 -6
  225. data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
  226. data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
  227. data/lib/woods/session_tracer/store.rb +14 -1
  228. data/lib/woods/storage/metadata_store.rb +230 -26
  229. data/lib/woods/storage/pgvector.rb +180 -22
  230. data/lib/woods/storage/qdrant.rb +367 -41
  231. data/lib/woods/storage/snapshotter/metadata.rb +79 -16
  232. data/lib/woods/storage/snapshotter/vector.rb +128 -17
  233. data/lib/woods/storage/snapshotter.rb +23 -5
  234. data/lib/woods/storage/vector_store.rb +49 -8
  235. data/lib/woods/storage_identity.rb +28 -0
  236. data/lib/woods/tasks.rb +53 -2
  237. data/lib/woods/temporal/json_snapshot_store.rb +112 -42
  238. data/lib/woods/temporal/snapshot_store.rb +139 -42
  239. data/lib/woods/unblocked/client.rb +119 -17
  240. data/lib/woods/unblocked/document_builder.rb +34 -2
  241. data/lib/woods/unblocked/exporter.rb +63 -27
  242. data/lib/woods/unblocked/rate_limiter.rb +23 -9
  243. data/lib/woods/unblocked/sync_manifest.rb +16 -8
  244. data/lib/woods/update_check.rb +24 -1
  245. data/lib/woods/util/uuid5.rb +124 -0
  246. data/lib/woods/version.rb +1 -1
  247. data/lib/woods/watch/daemon.rb +1345 -0
  248. data/lib/woods/watch/listen_watcher.rb +81 -0
  249. data/lib/woods/watch/polling_watcher.rb +137 -0
  250. data/lib/woods/watch/status.rb +169 -0
  251. data/lib/woods/watch/tree_scan.rb +163 -0
  252. data/lib/woods/watch/watcher.rb +100 -0
  253. data/lib/woods.rb +53 -9
  254. data/plugin/.claude-plugin/plugin.json +18 -0
  255. data/plugin/hooks/hooks.json +29 -0
  256. data/plugin/hooks/woods-post-edit.sh +226 -0
  257. data/plugin/hooks/woods-session-start.sh +77 -0
  258. data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
  259. data/plugin/skills/woods-diagnose/SKILL.md +75 -0
  260. data/plugin/skills/woods-investigate/SKILL.md +39 -0
  261. data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
  262. data/plugin/skills/woods-setup/SKILL.md +99 -0
  263. metadata +102 -26
  264. data/lib/woods/console/adapters/cache_adapter.rb +0 -58
  265. data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
  266. data/lib/woods/console/adapters/job_adapter.rb +0 -74
  267. data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
  268. data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
  269. data/lib/woods/console/bridge.rb +0 -210
  270. data/lib/woods/console/credential_scanner_registry.rb +0 -36
  271. data/lib/woods/console/encrypted_credential_snapshot.rb +0 -16
  272. data/lib/woods/formatting/claude_adapter.rb +0 -98
  273. data/lib/woods/formatting/generic_adapter.rb +0 -56
  274. data/lib/woods/formatting/gpt_adapter.rb +0 -64
  275. data/lib/woods/mcp/http_transport_options.rb +0 -24
  276. data/lib/woods/notion/mapper.rb +0 -40
  277. data/lib/woods/observability/health_check.rb +0 -79
  278. data/lib/woods/observability/instrumentation.rb +0 -34
@@ -0,0 +1,987 @@
1
+ # Woods MCP Tool Cookbook
2
+
3
+ Scenario-based examples showing which tool to use, what parameters to pass, and what you'll get back. Each section answers a natural question you might ask while working in a Rails codebase.
4
+
5
+ ## Scenario index
6
+
7
+ **Understanding Your Codebase**
8
+ - ["What models do we have?"](#what-models-do-we-have)
9
+ - ["How is the User model structured?"](#how-is-the-user-model-structured)
10
+ - ["What callbacks fire when Order saves?"](#what-callbacks-fire-when-order-saves)
11
+ - ["Show me User with all concerns inlined"](#show-me-user-with-all-concerns-inlined)
12
+ - ["What depends on User?"](#what-depends-on-user)
13
+ - ["What views link to OrdersController?"](#what-views-link-to-orderscontroller)
14
+ - ["What does User depend on?"](#what-does-user-depend-on)
15
+ - ["Find all controllers that handle payments"](#find-all-controllers-that-handle-payments)
16
+ - ["Which controller handles POST /checkout?"](#which-controller-handles-post-checkout)
17
+ - ["What jobs does CheckoutService trigger?"](#what-jobs-does-checkoutservice-trigger)
18
+ - ["Where does UsersController redirect to?"](#where-does-userscontroller-redirect-to)
19
+ - ["What methods does Rails generate on Order at runtime?"](#what-methods-does-rails-generate-on-order-at-runtime)
20
+ - ["What changed recently?"](#what-changed-recently)
21
+ **Debugging**
22
+ - ["What happens when POST /orders is called?"](#what-happens-when-post-orders-is-called)
23
+ - ["Why is this page slow?"](#why-is-this-page-slow)
24
+ **Architecture Analysis**
25
+ - ["Find dead code in our codebase"](#find-dead-code-in-our-codebase)
26
+ - ["What are the most important models?"](#what-are-the-most-important-models)
27
+ - ["Are there circular dependencies?"](#are-there-circular-dependencies)
28
+ - ["What are the key integration points?"](#what-are-the-key-integration-points)
29
+ - ["Which units are structural dead ends?"](#which-units-are-structural-dead-ends)
30
+ - ["Which associations cross a database boundary?"](#which-associations-cross-a-database-boundary)
31
+ - ["What do we depend on that changes faster than we do?"](#what-do-we-depend-on-that-changes-faster-than-we-do)
32
+ - ["How does Rails implement has_many?"](#how-does-rails-implement-has_many)
33
+ **Data Exploration (Console Server)**
34
+ - [Scope predicates](#scope-predicates)
35
+ - ["How many active users do we have?"](#how-many-active-users-do-we-have)
36
+ - ["Show me a sample order"](#show-me-a-sample-order)
37
+ - ["What's the User table schema?"](#whats-the-user-table-schema)
38
+ - ["What are the average order totals by status?"](#what-are-the-average-order-totals-by-status)
39
+ - ["Find all email addresses for users who joined last month"](#find-all-email-addresses-for-users-who-joined-last-month)
40
+ - ["Run a custom SQL query"](#run-a-custom-sql-query)
41
+ **Semantic Search**
42
+ - ["Find code related to subscription billing"](#find-code-related-to-subscription-billing)
43
+ **Pipeline Management**
44
+ - ["Check if the index is stale"](#check-if-the-index-is-stale)
45
+ - ["Trigger a re-extraction without restarting the server"](#trigger-a-re-extraction-without-restarting-the-server)
46
+ **Temporal Snapshots**
47
+ - ["What changed between last week and now?"](#what-changed-between-last-week-and-now)
48
+ - ["How has the User model evolved?"](#how-has-the-user-model-evolved)
49
+ **CI Integration**
50
+ - [GitHub Actions for Incremental Extraction](#github-actions-for-incremental-extraction)
51
+ **Retrieval Feedback**
52
+ - ["Rate a retrieval result and report a gap"](#rate-a-retrieval-result-and-report-a-gap)
53
+
54
+ ## Conditional Tools & Wiring
55
+
56
+ The Index Server defines **29 schemas**: the packaged executable registers **14**, while **15** require specialized collaborators or configuration. A tool that is not registered is absent from `tools/list`; clients see “tool not found,” not a runtime failure.
57
+
58
+ | Tool group | Count | Wiring condition |
59
+ |------------|-------|------------------|
60
+ | Always-on | 14 | Always registered, `lookup`, `search`, `dependencies`, `dependents`, `structure`, `graph_analysis`, `domain_clusters`, `pagerank`, `framework`, `recent_changes`, `reload`, `codebase_retrieve`, `trace_flow`, `woods_status` |
61
+ | `session_trace` | 1 | `Woods.configuration.session_store` set and session tracer enabled |
62
+ | Operator (5) | 5 | Custom embedded server wires an operator: `pipeline_extract`, `pipeline_embed`, `pipeline_status`, `pipeline_diagnose`, `pipeline_repair` |
63
+ | Feedback (4) | 4 | Custom embedded server wires a feedback store: `retrieval_rate`, `retrieval_report_gap`, `retrieval_explain`, `retrieval_suggest` |
64
+ | Snapshot (4) | 4 | Extraction with `enable_snapshots = true` normally creates `woods.sqlite3`, which packaged servers discover. If extraction used the JSON fallback, set `WOODS_SNAPSHOTS=true` on the standalone server. Custom embedded servers pass `snapshot_store:`. Internal SQLite migrations are automatic. Tools: `list_snapshots`, `snapshot_diff`, `unit_history`, `snapshot_detail` |
65
+ | `notion_sync` | 1 | `notion_api_token` + `notion_database_ids` both set |
66
+
67
+ `codebase_retrieve` is always registered (no `retrieve` alias exists), but only returns results once an embedding provider is configured and `rake woods:embed` has run.
68
+
69
+ If an agent reports a missing tool, compare its request with the connected server's registered list and [MCP server boundaries](MCP_SERVERS.md#conditional-index-capabilities). The normal packaged executable does not wire operator or feedback collaborators. **Console Server tools are not all unconditionally registered**: 31 tool schemas exist as an inventory, but only the 9 Tier 1 tools are executable by default, or 11 with `console_embedded_read_tools: true` (adds `console_sql`/`console_query`). Tier 2, Tier 3, and `console_eval` are schema-only in every supported mode; there is no bridge or confirmation flow that unlocks them. See [MCP servers](MCP_SERVERS.md#console-server) for the supported inventory.
70
+
71
+ ---
72
+
73
+ ## Understanding Your Codebase
74
+
75
+ ### "What models do we have?"
76
+
77
+ **Tool:** `structure` (Index Server)
78
+
79
+ ```json
80
+ {
81
+ "detail": "full"
82
+ }
83
+ ```
84
+
85
+ Returns the manifest (unit counts by type, git SHA, extraction timestamp) plus the full `SUMMARY.md` overview. Use `detail: "summary"` for just the counts.
86
+
87
+ **What you'll get:** Total units broken down by type (models, controllers, services, jobs, etc.), the git commit the extraction reflects, and when it ran.
88
+
89
+ ---
90
+
91
+ ### "How is the User model structured?"
92
+
93
+ **Tool:** `lookup` (Index Server)
94
+
95
+ ```json
96
+ {
97
+ "identifier": "User",
98
+ "include_source": true
99
+ }
100
+ ```
101
+
102
+ **Example response:**
103
+
104
+ ```json
105
+ {
106
+ "identifier": "User",
107
+ "type": "model",
108
+ "file_path": "app/models/user.rb",
109
+ "source_code": "# == Schema Information\n# id :bigint not null, pk\n# email :string not null\n# name :string\n# created_at :datetime\n#\nclass User < ApplicationRecord\n has_many :orders\n validates :email, presence: true, uniqueness: true\n ...\nend\n\n# ┌───────────────────────────────────────────────────────────────────┐\n# │ Included from: Searchable │\n# └───────────────────────────────────────────────────────────────────┘\n# module Searchable\n# ...\n# end\n# ──────────────────────── End Searchable ───────────────────────────",
110
+ "metadata": {
111
+ "associations": [
112
+ { "type": "has_many", "name": "orders", "target": "Order" }
113
+ ],
114
+ "validations": [
115
+ { "attribute": "email", "type": "presence", "options": {}, "conditions": {} },
116
+ { "attribute": "email", "type": "uniqueness", "options": {}, "conditions": {} }
117
+ ],
118
+ "callbacks": [],
119
+ "inlined_concerns": ["Searchable"],
120
+ "enums": {},
121
+ "scopes": []
122
+ },
123
+ "dependencies": [
124
+ { "type": "model", "target": "Order", "via": "has_many" }
125
+ ],
126
+ "dependents": [
127
+ { "type": "controller", "identifier": "UsersController" }
128
+ ]
129
+ }
130
+ ```
131
+
132
+ To focus on just associations and callbacks without the full source:
133
+
134
+ ```json
135
+ {
136
+ "identifier": "User",
137
+ "include_source": false,
138
+ "sections": ["metadata", "dependencies"]
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ### "What callbacks fire when Order saves?"
145
+
146
+ **Tool:** `lookup` (Index Server)
147
+
148
+ ```json
149
+ {
150
+ "identifier": "Order",
151
+ "include_source": false,
152
+ "sections": ["metadata"]
153
+ }
154
+ ```
155
+
156
+ **Example response**: `metadata.callbacks` contains the resolved callback chain in execution order, including callbacks inherited from concerns. Side-effects show what each callback actually does:
157
+
158
+ ```json
159
+ {
160
+ "identifier": "Order",
161
+ "type": "model",
162
+ "metadata": {
163
+ "callbacks": [
164
+ { "type": "before_validation", "filter": "normalize_status", "kind": "before", "conditions": {} },
165
+ { "type": "before_save", "filter": "calculate_total", "kind": "before", "conditions": {},
166
+ "side_effects": { "columns_written": ["total_cents"], "jobs_enqueued": [], "services_called": [], "mailers_triggered": [], "database_reads": [], "operations": [] } },
167
+ { "type": "before_save", "filter": "set_slug", "kind": "before", "conditions": {},
168
+ "side_effects": { "columns_written": ["slug"], "jobs_enqueued": [], "services_called": [], "mailers_triggered": [], "database_reads": [], "operations": [] } },
169
+ { "type": "after_save", "filter": "reserve_stock", "kind": "after", "conditions": {},
170
+ "side_effects": { "columns_written": [], "jobs_enqueued": ["InventoryReserveJob"], "services_called": [], "mailers_triggered": [], "database_reads": [], "operations": [] } },
171
+ { "type": "after_commit", "filter": "send_confirmation_email", "kind": "after", "conditions": {},
172
+ "side_effects": { "columns_written": [], "jobs_enqueued": ["OrderConfirmationJob"], "services_called": [], "mailers_triggered": ["OrderMailer"], "database_reads": [], "operations": [] } },
173
+ { "type": "after_commit", "filter": "audit_trail", "kind": "after", "conditions": {},
174
+ "side_effects": { "columns_written": [], "jobs_enqueued": [], "services_called": ["AuditService"], "mailers_triggered": [], "database_reads": [], "operations": [] } }
175
+ ],
176
+ "inlined_concerns": ["Auditable"]
177
+ }
178
+ }
179
+ ```
180
+
181
+ Callbacks from included concerns (like `audit_trail` from `Auditable`) are resolved and included in the chain. The `side_effects` hash is populated by `CallbackAnalyzer`, which scans callback method bodies for patterns like `self.col =` (column writes), `perform_later` (job enqueues), and `deliver_later` (mailer triggers).
182
+
183
+ ---
184
+
185
+ ### "Show me User with all concerns inlined"
186
+
187
+ **Tool:** `lookup` (Index Server)
188
+
189
+ ```json
190
+ {
191
+ "identifier": "User",
192
+ "include_source": true
193
+ }
194
+ ```
195
+
196
+ **What you'll get:** The `source_code` field contains the model source with all included concerns appended inline. This is the key feature, your AI tool sees the full behavioral surface area in one block:
197
+
198
+ ```
199
+ # == Schema Information
200
+ # id :bigint not null, pk
201
+ # email :string not null
202
+ # name :string
203
+ # created_at :datetime
204
+ #
205
+ class User < ApplicationRecord
206
+ include Auditable
207
+ include Searchable
208
+ validates :email, presence: true, uniqueness: true
209
+ has_many :orders
210
+ end
211
+
212
+ # ┌─────────────────────────────────────────────────────────────────────┐
213
+ # │ Included from: Auditable │
214
+ # └─────────────────────────────────────────────────────────────────────┘
215
+ # module Auditable
216
+ # extend ActiveSupport::Concern
217
+ # included do
218
+ # after_save :audit_trail
219
+ # end
220
+ # def audit_trail
221
+ # AuditLog.create!(auditable: self)
222
+ # end
223
+ # end
224
+ # ─────────────────────────── End Auditable ───────────────────────────
225
+
226
+ # ┌─────────────────────────────────────────────────────────────────────┐
227
+ # │ Included from: Searchable │
228
+ # └─────────────────────────────────────────────────────────────────────┘
229
+ # module Searchable
230
+ # extend ActiveSupport::Concern
231
+ # included do
232
+ # scope :search, ->(q) { where("name ILIKE ?", "%#{q}%") }
233
+ # after_commit :reindex_search
234
+ # end
235
+ # end
236
+ # ─────────────────────────── End Searchable ───────────────────────────
237
+ ```
238
+
239
+ The `metadata.inlined_concerns` array lists which concerns were resolved:
240
+
241
+ ```json
242
+ { "inlined_concerns": ["Auditable", "Searchable"] }
243
+ ```
244
+
245
+ ---
246
+
247
+ ### "What depends on User?"
248
+
249
+ **Tool:** `dependents` (Index Server)
250
+
251
+ ```json
252
+ {
253
+ "identifier": "User",
254
+ "depth": 2
255
+ }
256
+ ```
257
+
258
+ **What you'll get:** A BFS tree of everything that references `User`, controllers, services, jobs, mailers, up to 2 hops out. Set `depth: 1` for direct dependents only.
259
+
260
+ The answer is bounded to 50 nodes. When it is cut, the response ends with a
261
+ `Showing N of M (truncated)` line, the same one `graph_analysis` prints. Reach
262
+ for `depth`, `types` and `via` first: they make the answer smaller. `limit` and
263
+ `offset` only page what those leave, so a hub read one page at a time still
264
+ costs every page.
265
+
266
+ To find only which jobs depend on `User`:
267
+
268
+ ```json
269
+ {
270
+ "identifier": "User",
271
+ "depth": 2,
272
+ "types": ["job"]
273
+ }
274
+ ```
275
+
276
+ In a multi-database app each row also names the unit's database, so you can see
277
+ which side of an edge lives where. A single-database index prints no such
278
+ column.
279
+
280
+ ---
281
+
282
+ ### "What views link to OrdersController?"
283
+
284
+ **Tool:** `dependents` (Index Server)
285
+
286
+ ```json
287
+ {
288
+ "identifier": "OrdersController",
289
+ "depth": 1,
290
+ "via": ["link_to", "form_action"]
291
+ }
292
+ ```
293
+
294
+ **What you'll get:** View templates and controllers that navigate to `OrdersController` via `link_to` helpers or form submissions. The `via` filter excludes code references and other relationship types, showing only UI navigation edges.
295
+
296
+ ---
297
+
298
+ ### "What does User depend on?"
299
+
300
+ **Tool:** `dependencies` (Index Server)
301
+
302
+ ```json
303
+ {
304
+ "identifier": "User",
305
+ "depth": 2
306
+ }
307
+ ```
308
+
309
+ **What you'll get:** Forward dependency tree, concerns, associations, services called from callbacks, jobs enqueued, etc.
310
+
311
+ ---
312
+
313
+ ### "Find all controllers that handle payments"
314
+
315
+ **Tool:** `search` (Index Server)
316
+
317
+ ```json
318
+ {
319
+ "query": "payment",
320
+ "types": ["controller"],
321
+ "fields": ["identifier", "source_code"],
322
+ "limit": 10
323
+ }
324
+ ```
325
+
326
+ **Example response:**
327
+
328
+ ```json
329
+ [
330
+ {
331
+ "identifier": "PaymentsController",
332
+ "type": "controller",
333
+ "file_path": "app/controllers/payments_controller.rb",
334
+ "metadata": {
335
+ "actions": ["create", "show", "webhook"],
336
+ "routes": [
337
+ { "verb": "POST", "path": "/payments", "action": "create" },
338
+ { "verb": "POST", "path": "/payments/webhook", "action": "webhook" }
339
+ ]
340
+ }
341
+ }
342
+ ]
343
+ ```
344
+
345
+ Search `source_code` when you want semantic matches, not just naming matches.
346
+
347
+ ---
348
+
349
+ ### "Which controller handles POST /checkout?"
350
+
351
+ **Tool:** `search` (Index Server)
352
+
353
+ ```json
354
+ {
355
+ "query": "/checkout",
356
+ "types": ["route"],
357
+ "limit": 5
358
+ }
359
+ ```
360
+
361
+ **What you'll get:** Route units matching `/checkout` with the bound controller and action:
362
+
363
+ ```json
364
+ [
365
+ {
366
+ "type": "route",
367
+ "identifier": "POST /checkout",
368
+ "metadata": { "controller": "orders", "action": "create", "route_name": "checkout" }
369
+ }
370
+ ]
371
+ ```
372
+
373
+ **Follow up**: look up the controller for full source with filters and route context:
374
+
375
+ ```json
376
+ {
377
+ "tool": "lookup",
378
+ "params": { "identifier": "OrdersController", "include_source": true }
379
+ }
380
+ ```
381
+
382
+ ---
383
+
384
+ ### "What jobs does CheckoutService trigger?"
385
+
386
+ **Tool:** `dependencies` (Index Server)
387
+
388
+ ```json
389
+ {
390
+ "identifier": "CheckoutService",
391
+ "depth": 2,
392
+ "types": ["job"]
393
+ }
394
+ ```
395
+
396
+ **What you'll get:** All job units reachable from `CheckoutService` within 2 hops, including jobs triggered indirectly via model callbacks:
397
+
398
+ ```json
399
+ {
400
+ "root": "CheckoutService",
401
+ "results": [
402
+ { "identifier": "OrderConfirmationJob", "type": "job", "path": ["CheckoutService", "Order", "OrderConfirmationJob"] },
403
+ { "identifier": "InventoryReserveJob", "type": "job", "path": ["CheckoutService", "LineItem", "InventoryReserveJob"] }
404
+ ]
405
+ }
406
+ ```
407
+
408
+ This traces through the dependency graph: `CheckoutService` calls `Order#save!`, which triggers `after_commit :send_confirmation`, which enqueues `OrderConfirmationJob`. Without the graph, you'd need to manually follow callbacks across multiple files.
409
+
410
+ ---
411
+
412
+ ### "Where does UsersController redirect to?"
413
+
414
+ **Tool:** `dependencies` (Index Server)
415
+
416
+ ```json
417
+ {
418
+ "identifier": "UsersController",
419
+ "depth": 1,
420
+ "via": ["redirect_to"]
421
+ }
422
+ ```
423
+
424
+ **What you'll get:** Controllers that `UsersController` redirects to via `redirect_to` with named route helpers. Useful for tracing user flow after form submissions or authentication.
425
+
426
+ ---
427
+
428
+ ### "What methods does Rails generate on Order at runtime?"
429
+
430
+ **Tool:** `lookup` (Index Server)
431
+
432
+ ```json
433
+ {
434
+ "identifier": "Order",
435
+ "include_source": false,
436
+ "sections": ["metadata"]
437
+ }
438
+ ```
439
+
440
+ Because Woods runs inside a booted Rails process, it captures every method Rails generates dynamically, things static analysis tools cannot see. The metadata shows these in structured form:
441
+
442
+ **Example response (relevant sections):**
443
+
444
+ ```json
445
+ {
446
+ "identifier": "Order",
447
+ "type": "model",
448
+ "metadata": {
449
+ "enums": {
450
+ "status": { "pending": 0, "active": 1, "shipped": 2, "cancelled": 3 }
451
+ },
452
+ "scopes": [
453
+ { "name": "active", "source": "-> { where(status: :active) }" },
454
+ { "name": "recent", "source": "-> { where('created_at > ?', 30.days.ago) }" }
455
+ ],
456
+ "associations": [
457
+ { "type": "belongs_to", "name": "user", "target": "User" },
458
+ { "type": "has_many", "name": "line_items", "target": "LineItem" }
459
+ ]
460
+ }
461
+ }
462
+ ```
463
+
464
+ Woods captures the `enums`, `scopes`, and `associations` metadata directly from ActiveRecord reflection, the method names below are inferred per standard Rails conventions, not listed explicitly in the `_index.json`. From this metadata, you can infer every runtime-generated method:
465
+
466
+ | Source | Generated Methods |
467
+ |--------|------------------|
468
+ | `enum status:` | `status_pending?`, `status_active?`, `status_shipped?`, `status_cancelled?`, `pending!`, `active!`, `shipped!`, `cancelled!` |
469
+ | `scope :active` | `Order.active` |
470
+ | `belongs_to :user` | `user`, `user=`, `build_user`, `create_user`, `create_user!`, `reload_user` |
471
+ | `has_many :line_items` | `line_items`, `line_items=`, `line_item_ids`, `line_item_ids=`, `build`, `create`, `create!` on the association |
472
+
473
+ Static tools miss all of these because they only exist after Rails processes the DSL declarations at boot time. Woods captures them because it queries the runtime class via `instance_methods(false)` after Rails has finished loading.
474
+
475
+ ---
476
+
477
+ ### "What changed recently?"
478
+
479
+ **Tool:** `recent_changes` (Index Server)
480
+
481
+ ```json
482
+ {
483
+ "limit": 20,
484
+ "types": ["model", "service"]
485
+ }
486
+ ```
487
+
488
+ **What you'll get:** Recently modified units sorted by git `last_modified` timestamp. Useful for getting up to speed after a teammate's changes.
489
+
490
+ ---
491
+
492
+ ## Debugging
493
+
494
+ ### "What happens when POST /orders is called?"
495
+
496
+ **Tool:** `trace_flow` (Index Server)
497
+
498
+ ```json
499
+ {
500
+ "entry_point": "OrdersController#create",
501
+ "depth": 3
502
+ }
503
+ ```
504
+
505
+ **What you'll get:** Execution flow from the controller action through services, callbacks, jobs enqueued, and mailers sent, assembled from the dependency graph. Increase `depth` to trace deeper call chains.
506
+
507
+ ---
508
+
509
+ ### "Why is this page slow?"
510
+
511
+ `console_slow_endpoints` is Tier 3, schema-only, not executable in any supported mode (see [Conditional Tools & Wiring](#conditional-tools--wiring)). Trace the code path directly instead:
512
+
513
+ **Tool:** `trace_flow` (Index Server)
514
+
515
+ ```json
516
+ {
517
+ "entry_point": "ProductsController#index",
518
+ "depth": 4
519
+ }
520
+ ```
521
+
522
+ **What you'll get:** A full execution flow showing every layer the request touches, services, callbacks, jobs enqueued, mailers sent. Pair this with your own APM/logging for the "which endpoint is actually slow" half of the question; Woods answers "why," not "which."
523
+
524
+ ---
525
+
526
+ ## Architecture Analysis
527
+
528
+ ### "Find dead code in our codebase"
529
+
530
+ **Tool:** `graph_analysis` (Index Server)
531
+
532
+ ```json
533
+ {
534
+ "analysis": "orphans",
535
+ "limit": 20
536
+ }
537
+ ```
538
+
539
+ **What you'll get:** Units with no dependents, nothing in the codebase references them. Good candidates for removal or investigation.
540
+
541
+ ---
542
+
543
+ ### "What are the most important models?"
544
+
545
+ **Tool:** `pagerank` (Index Server)
546
+
547
+ ```json
548
+ {
549
+ "limit": 10,
550
+ "types": ["model"]
551
+ }
552
+ ```
553
+
554
+ **What you'll get:** Models ranked by PageRank score. Higher scores mean more units depend on them, these are your core domain objects. Touching these files has the widest blast radius.
555
+
556
+ ---
557
+
558
+ ### "Are there circular dependencies?"
559
+
560
+ **Tool:** `graph_analysis` (Index Server)
561
+
562
+ ```json
563
+ {
564
+ "analysis": "cycles",
565
+ "limit": 10
566
+ }
567
+ ```
568
+
569
+ **What you'll get:** Circular dependency chains in the codebase. A cycle like `A → B → C → A` indicates tight coupling that may complicate testing or refactoring.
570
+
571
+ ---
572
+
573
+ ### "What are the key integration points?"
574
+
575
+ **Tool:** `graph_analysis` (Index Server)
576
+
577
+ ```json
578
+ {
579
+ "analysis": "bridges",
580
+ "limit": 15
581
+ }
582
+ ```
583
+
584
+ **What you'll get:** Units whose removal would disconnect parts of the dependency graph, the load-bearing structural elements of your codebase.
585
+
586
+ ---
587
+
588
+ ### "Which units are structural dead ends?"
589
+
590
+ **Tool:** `graph_analysis` (Index Server)
591
+
592
+ ```json
593
+ {
594
+ "analysis": "dead_ends"
595
+ }
596
+ ```
597
+
598
+ **What you'll get:** Units that have no forward dependencies, leaf nodes. These tend to be pure utility classes or simple value objects.
599
+
600
+ ---
601
+
602
+ ### "Which associations cross a database boundary?"
603
+
604
+ **Tool:** `graph_analysis` (Index Server)
605
+
606
+ ```json
607
+ {
608
+ "analysis": "cross_database_edges",
609
+ "limit": 20
610
+ }
611
+ ```
612
+
613
+ **What you'll get:** `from`, `to`, `via`, `from_db`, `to_db`, `through`, `through_db`, `disable_joins`, and `kind`. A `kind` of `join_through_across_databases` is a `has_many :through` that Rails will try to JOIN across connections; add `disable_joins: true`. `foreign_key_across_databases` is a database constraint whose target table lives elsewhere; an `ambiguous_owners` list means more than one database owns that table name and the target could not be resolved.
614
+
615
+ ---
616
+
617
+ ### "What do we depend on that changes faster than we do?"
618
+
619
+ **Tool:** `graph_analysis` (Index Server)
620
+
621
+ ```json
622
+ {
623
+ "analysis": "volatile_dependencies",
624
+ "limit": 10
625
+ }
626
+ ```
627
+
628
+ **What you'll get:** Edges whose dependency has at least `volatile_dependency_ratio` (default 3) times the dependent's commit count over the last year, ranked by the dependency's PageRank. A report, not a gate: young units are skipped and the ratio is configurable.
629
+
630
+ ---
631
+
632
+ ### "Does this call cross a package boundary we never declared?"
633
+
634
+ **Tool:** `graph_analysis` (Index Server)
635
+
636
+ ```json
637
+ {
638
+ "analysis": "undeclared_package_edges"
639
+ }
640
+ ```
641
+
642
+ **What you'll get:** `from`, `from_type`, `to`, `to_type`, `via`, `from_package`, and `to_package` for every edge whose source package never lists the target package as a dependency. Enforcement stays with `packwerk check` / `pks check`; this only makes the boundary visible before you write the call.
643
+
644
+ ---
645
+
646
+ ### "How does Rails implement has_many?"
647
+
648
+ **Tool:** `framework` (Index Server)
649
+
650
+ ```json
651
+ {
652
+ "keyword": "has_many",
653
+ "limit": 5
654
+ }
655
+ ```
656
+
657
+ **What you'll get:** Relevant Rails source units matching the keyword, the actual implementation from the installed gem. Useful for understanding framework behavior without leaving your AI tool.
658
+
659
+ ---
660
+
661
+ ## Data Exploration (Console Server)
662
+
663
+ ### Scope predicates
664
+
665
+ Tools that accept a `scope` parameter (`console_count`, `console_sample`, `console_pluck`, `console_aggregate`, `console_association_count`, `console_recent`) support Ransack-style predicate suffixes on hash keys. Plain keys are treated as equality, suffixed keys build safe Arel predicates. Column names are validated against the model's schema, SQL injection via column names is not possible.
666
+
667
+ | Suffix | SQL equivalent | Example |
668
+ |--------|----------------|---------|
669
+ | `_eq` | `col = value` | `{ "status_eq": "paid" }` |
670
+ | `_not_eq` | `col != value` | `{ "status_not_eq": "cancelled" }` |
671
+ | `_gt` | `col > value` | `{ "total_cents_gt": 1000 }` |
672
+ | `_gteq` | `col >= value` | `{ "created_at_gteq": "2026-01-01" }` |
673
+ | `_lt` | `col < value` | `{ "total_cents_lt": 5000 }` |
674
+ | `_lteq` | `col <= value` | `{ "created_at_lteq": "2026-12-31" }` |
675
+ | `_in` | `col IN (…)` | `{ "status_in": ["paid", "refunded"] }` |
676
+ | `_not_in` | `col NOT IN (…)` | `{ "status_not_in": ["cancelled"] }` |
677
+ | `_null` | `col IS NULL` (value: `true`) / `IS NOT NULL` (value: `false`) | `{ "deleted_at_null": true }` |
678
+ | `_not_null` | `col IS NOT NULL` (value: `true`) / `IS NULL` (value: `false`) | `{ "email_not_null": true }` |
679
+ | `_present` | `col IS NOT NULL AND col != ''` (value: `true`) | `{ "name_present": true }` |
680
+ | `_blank` | `col IS NULL OR col = ''` (value: `true`) | `{ "notes_blank": true }` |
681
+ | `_matches` | `col LIKE value` | `{ "email_matches": "%@example.com" }` |
682
+
683
+ Keys without a recognised suffix fall through to ActiveRecord `where(hash)` equality. You can mix both in a single scope:
684
+
685
+ ```json
686
+ { "status": "paid", "total_cents_gt": 1000, "created_at_gteq": "2026-01-01" }
687
+ ```
688
+
689
+ ---
690
+
691
+ ### "How many active users do we have?"
692
+
693
+ **Tool:** `console_count` (Console Server)
694
+
695
+ ```json
696
+ {
697
+ "model": "User",
698
+ "scope": { "active": true }
699
+ }
700
+ ```
701
+
702
+ **What you'll get:** An integer count. The `scope` hash maps directly to ActiveRecord `where` conditions.
703
+
704
+ ---
705
+
706
+ ### "Show me a sample order"
707
+
708
+ **Tool:** `console_sample` (Console Server)
709
+
710
+ ```json
711
+ {
712
+ "model": "Order",
713
+ "limit": 1
714
+ }
715
+ ```
716
+
717
+ **What you'll get:** A random order record with all columns. To focus on specific fields:
718
+
719
+ ```json
720
+ {
721
+ "model": "Order",
722
+ "limit": 3,
723
+ "columns": ["id", "status", "total_cents", "created_at"],
724
+ "scope": { "status": "pending" }
725
+ }
726
+ ```
727
+
728
+ ---
729
+
730
+ ### "What's the User table schema?"
731
+
732
+ **Tool:** `console_schema` (Console Server)
733
+
734
+ ```json
735
+ {
736
+ "model": "User",
737
+ "include_indexes": true
738
+ }
739
+ ```
740
+
741
+ **What you'll get:** Column names, types, nullability, defaults, and (with `include_indexes: true`) all defined indexes. Reflects the live database schema, not migrations.
742
+
743
+ ---
744
+
745
+ ### "What are the average order totals by status?"
746
+
747
+ **Tool:** `console_aggregate` (Console Server)
748
+
749
+ ```json
750
+ {
751
+ "model": "Order",
752
+ "function": "average",
753
+ "column": "total_cents",
754
+ "scope": { "status": "completed" }
755
+ }
756
+ ```
757
+
758
+ **What you'll get:** A single aggregate value. Functions: `sum`, `average`, `minimum`, `maximum`, `count`. The `column` parameter is required for every function except `count`, where it may be omitted to count all matching rows.
759
+
760
+ ---
761
+
762
+ ### "Find all email addresses for users who joined last month"
763
+
764
+ **Tool:** `console_pluck` (Console Server)
765
+
766
+ ```json
767
+ {
768
+ "model": "User",
769
+ "columns": ["email"],
770
+ "scope": { "created_at_gteq": "2025-01-01" },
771
+ "limit": 100,
772
+ "distinct": true
773
+ }
774
+ ```
775
+
776
+ **What you'll get:** An array of email values. `distinct: true` removes duplicates.
777
+
778
+ ---
779
+
780
+ ### "Run a custom SQL query"
781
+
782
+ **Tool:** `console_sql` (Console Server, requires `console_embedded_read_tools: true`; see [MCP servers](MCP_SERVERS.md#console-server))
783
+
784
+ ```json
785
+ {
786
+ "sql": "SELECT status, COUNT(*) as count FROM orders GROUP BY status ORDER BY count DESC",
787
+ "limit": 50
788
+ }
789
+ ```
790
+
791
+ **What you'll get:** Query results as an array of row hashes. Only `SELECT` and `WITH...SELECT` queries are permitted, all writes are rejected at the validator level before reaching the database.
792
+
793
+ ---
794
+
795
+ ## Semantic Search
796
+
797
+ ### "Find code related to subscription billing"
798
+
799
+ **Tool:** `codebase_retrieve` (Index Server, requires embedding provider)
800
+
801
+ ```json
802
+ {
803
+ "query": "subscription billing renewal payment processing",
804
+ "budget": 8000
805
+ }
806
+ ```
807
+
808
+ **What you'll get:** A token-budgeted context string of the most semantically relevant units, ranked by hybrid search (semantic + keyword + PageRank). Requires an embedding provider (`embedding_provider: :openai` or `:ollama`) to be configured.
809
+
810
+ ---
811
+
812
+ ## Pipeline Management
813
+
814
+ These tools require a custom embedded server with an operator; the packaged
815
+ `woods-mcp` executable does not register them. A client that declares the MCP
816
+ Tasks extension receives a durable task handle and polls `tasks/get` for
817
+ completion. Woods does not advertise safe cancellation: `tasks/cancel` returns
818
+ an unsupported-method error, and in-flight work continues to completion or
819
+ failure. Clients without the extension receive the legacy background-start
820
+ acknowledgement.
821
+
822
+ ### "Check if the index is stale"
823
+
824
+ **Tool:** `pipeline_status` (Index Server)
825
+
826
+ ```json
827
+ {}
828
+ ```
829
+
830
+ **What you'll get:** Last extraction time, current unit counts, and staleness indicators, whether the index reflects recent changes.
831
+
832
+ ---
833
+
834
+ ### "Trigger a re-extraction without restarting the server"
835
+
836
+ Trigger extraction, then reload the server's in-memory data:
837
+
838
+ **Step 1:**
839
+
840
+ **Tool:** `pipeline_extract` (Index Server)
841
+
842
+ ```json
843
+ {
844
+ "incremental": true
845
+ }
846
+ ```
847
+
848
+ **Step 2 (after extraction completes):**
849
+
850
+ **Tool:** `reload` (Index Server)
851
+
852
+ ```json
853
+ {}
854
+ ```
855
+
856
+ **What you'll get:** Confirmation that extraction started (runs in background), then updated manifest stats after reload.
857
+
858
+ ---
859
+
860
+ ## Temporal Snapshots
861
+
862
+ ### "What changed between last week and now?"
863
+
864
+ **Tool:** `snapshot_diff` (Index Server, requires `enable_snapshots: true`)
865
+
866
+ ```json
867
+ {
868
+ "sha_a": "abc1234",
869
+ "sha_b": "def5678"
870
+ }
871
+ ```
872
+
873
+ **What you'll get:** Lists of added, modified, and deleted units between the two git SHAs. Use `list_snapshots` first to find valid SHA values.
874
+
875
+ ---
876
+
877
+ ### "How has the User model evolved?"
878
+
879
+ **Tool:** `unit_history` (Index Server, requires `enable_snapshots: true`)
880
+
881
+ ```json
882
+ {
883
+ "identifier": "User",
884
+ "limit": 10
885
+ }
886
+ ```
887
+
888
+ **What you'll get:** A chronological list of snapshot versions showing when the `User` unit's source changed.
889
+
890
+ ---
891
+
892
+ ## CI Integration
893
+
894
+ ### GitHub Actions for Incremental Extraction
895
+
896
+ Run incremental extraction on every push, cache the index between runs:
897
+
898
+ ```yaml
899
+ # .github/workflows/woods.yml
900
+ name: Update Codebase Index
901
+
902
+ on:
903
+ push:
904
+ branches: [main]
905
+ pull_request:
906
+
907
+ jobs:
908
+ index:
909
+ runs-on: ubuntu-latest
910
+ steps:
911
+ - uses: actions/checkout@v4
912
+ with:
913
+ fetch-depth: 2 # needed for incremental diff
914
+
915
+ - name: Set up Ruby
916
+ uses: ruby/setup-ruby@v1
917
+ with:
918
+ bundler-cache: true
919
+
920
+ - name: Restore index cache
921
+ uses: actions/cache@v4
922
+ with:
923
+ path: tmp/woods
924
+ key: woods-${{ github.ref }}-${{ github.sha }}
925
+ restore-keys: |
926
+ woods-${{ github.ref }}-
927
+ woods-
928
+
929
+ - name: Run database migrations
930
+ run: bundle exec rails db:migrate RAILS_ENV=test
931
+
932
+ - name: Update codebase index
933
+ run: bundle exec rake woods:incremental
934
+ env:
935
+ RAILS_ENV: test
936
+ GITHUB_BASE_REF: ${{ github.base_ref }}
937
+
938
+ - name: Validate index
939
+ run: bundle exec rake woods:validate
940
+ ```
941
+
942
+ For Docker-based CI:
943
+
944
+ ```yaml
945
+ - name: Update codebase index
946
+ run: docker compose exec -T app bundle exec rake woods:incremental
947
+ ```
948
+
949
+ ---
950
+
951
+ ## Retrieval Feedback
952
+
953
+ ### "Rate a retrieval result and report a gap"
954
+
955
+ If semantic search missed a relevant unit, report it so the system can improve:
956
+
957
+ **Rate the result:**
958
+
959
+ **Tool:** `retrieval_rate` (Index Server)
960
+
961
+ ```json
962
+ {
963
+ "query": "user authentication flow",
964
+ "score": 2,
965
+ "comment": "Missed SessionsController entirely"
966
+ }
967
+ ```
968
+
969
+ **Report the missing unit:**
970
+
971
+ **Tool:** `retrieval_report_gap` (Index Server)
972
+
973
+ ```json
974
+ {
975
+ "query": "user authentication flow",
976
+ "missing_unit": "SessionsController",
977
+ "unit_type": "controller"
978
+ }
979
+ ```
980
+
981
+ **Check feedback statistics:**
982
+
983
+ **Tool:** `retrieval_explain` (Index Server)
984
+
985
+ ```json
986
+ {}
987
+ ```