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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1889 -11
- data/CONTRIBUTING.md +195 -129
- data/README.md +162 -520
- data/SECURITY.md +92 -0
- data/assets/woods-wordmark-white-with-bg.png +0 -0
- data/docs/AGENT_GUIDE.md +204 -0
- data/docs/AGENT_SETUP.md +205 -0
- data/docs/BACKEND_MATRIX.md +470 -0
- data/docs/CONFIGURATION_REFERENCE.md +620 -0
- data/docs/CONSOLE_MCP_SETUP.md +829 -0
- data/docs/DOCKER_SETUP.md +454 -0
- data/docs/EMBEDDING_MODELS.md +136 -0
- data/docs/EVALUATION.md +91 -0
- data/docs/EXTRACTOR_REFERENCE.md +765 -0
- data/docs/FAQ.md +544 -0
- data/docs/GETTING_STARTED.md +183 -0
- data/docs/INCREMENTAL_EXTRACTION.md +415 -0
- data/docs/INTERNALS.md +415 -0
- data/docs/MCP_HTTP_TRANSPORT.md +144 -0
- data/docs/MCP_SERVERS.md +231 -0
- data/docs/MCP_TOOL_COOKBOOK.md +987 -0
- data/docs/MCP_WORKTREE_SETUP.md +127 -0
- data/docs/NOTION_INTEGRATION.md +283 -0
- data/docs/OBSIDIAN_INTEGRATION.md +170 -0
- data/docs/PUBLISHED_INDEX.md +197 -0
- data/docs/README.md +94 -0
- data/docs/RETRIEVAL_GUIDE.md +267 -0
- data/docs/TOKEN_BENCHMARK.md +68 -0
- data/docs/TROUBLESHOOTING.md +841 -0
- data/docs/UNBLOCKED_INTEGRATION.md +279 -0
- data/docs/UPGRADING_TO_2.md +321 -0
- data/docs/WATCH_DAEMON.md +667 -0
- data/docs/WHY_WOODS.md +219 -0
- data/exe/woods-console +40 -4
- data/exe/woods-console-mcp +21 -35
- data/exe/woods-mcp +20 -7
- data/exe/woods-mcp-http +77 -10
- data/exe/woods-mcp-start +57 -52
- data/lib/generators/woods/install_generator.rb +6 -5
- data/lib/generators/woods/pgvector_generator.rb +6 -3
- data/lib/generators/woods/templates/add_pgvector_to_woods.rb.erb +29 -9
- data/lib/generators/woods/templates/create_woods_tables.rb.erb +5 -1
- data/lib/generators/woods/templates/woods.rb.tt +49 -28
- data/lib/tasks/woods.rake +622 -168
- data/lib/tasks/woods_checks.rake +107 -0
- data/lib/tasks/woods_evaluation.rake +164 -80
- data/lib/woods/ast/call_site_extractor.rb +6 -15
- data/lib/woods/ast/method_extractor.rb +19 -9
- data/lib/woods/ast/parser.rb +54 -8
- data/lib/woods/atomic_file.rb +40 -1
- data/lib/woods/builder.rb +310 -22
- data/lib/woods/cache/cache_middleware.rb +7 -2
- data/lib/woods/cache/cache_store.rb +9 -1
- data/lib/woods/cache/solid_cache_store.rb +6 -4
- data/lib/woods/change_set.rb +88 -0
- data/lib/woods/checks/generation_resolution.rb +34 -0
- data/lib/woods/checks/moved_messages.rb +186 -0
- data/lib/woods/chunking/semantic_chunker.rb +160 -18
- data/lib/woods/console/audit_logger.rb +12 -3
- data/lib/woods/console/bridge_protocol.rb +3 -16
- data/lib/woods/console/connection_manager.rb +51 -136
- data/lib/woods/console/credential_index.rb +2 -20
- data/lib/woods/console/credential_scanner.rb +14 -14
- data/lib/woods/console/dispatch_pipeline.rb +42 -12
- data/lib/woods/console/embedded_executor.rb +806 -149
- data/lib/woods/console/eval_guard.rb +27 -20
- data/lib/woods/console/input_contract.rb +78 -0
- data/lib/woods/console/model_validator.rb +29 -1
- data/lib/woods/console/rack_middleware.rb +63 -43
- data/lib/woods/console/redactor.rb +26 -8
- data/lib/woods/console/safe_context.rb +58 -10
- data/lib/woods/console/scope_predicate_parser.rb +41 -0
- data/lib/woods/console/server.rb +135 -265
- data/lib/woods/console/sql_noise_stripper.rb +125 -16
- data/lib/woods/console/sql_table_scanner.rb +82 -22
- data/lib/woods/console/sql_validator.rb +459 -29
- data/lib/woods/console/table_gate.rb +2 -2
- data/lib/woods/console/tool_specs.rb +462 -88
- data/lib/woods/console/tools/tier1.rb +0 -3
- data/lib/woods/console/tools/tier4.rb +17 -7
- data/lib/woods/coordination/lock_heartbeat.rb +103 -0
- data/lib/woods/coordination/pipeline_lock.rb +263 -53
- data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
- data/lib/woods/db/migrator.rb +3 -9
- data/lib/woods/db/schema_version.rb +47 -2
- data/lib/woods/dependency_graph.rb +898 -64
- data/lib/woods/embedding/fake.rb +138 -0
- data/lib/woods/embedding/indexer.rb +832 -40
- data/lib/woods/embedding/openai.rb +77 -19
- data/lib/woods/embedding/provider.rb +189 -11
- data/lib/woods/embedding/text_preparer.rb +1 -1
- data/lib/woods/embedding/token_counter.rb +0 -7
- data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
- data/lib/woods/evaluation/ablation_executor.rb +67 -0
- data/lib/woods/evaluation/ablation_provenance.rb +38 -0
- data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
- data/lib/woods/evaluation/ablation_runner.rb +173 -0
- data/lib/woods/evaluation/ablation_summary.rb +65 -0
- data/lib/woods/evaluation/ablation_task.rb +66 -0
- data/lib/woods/evaluation/ablation_task_set.rb +77 -0
- data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
- data/lib/woods/evaluation/ablation_worktree.rb +71 -0
- data/lib/woods/evaluation/baseline.rb +60 -0
- data/lib/woods/evaluation/baseline_runner.rb +11 -3
- data/lib/woods/evaluation/evaluator.rb +41 -8
- data/lib/woods/evaluation/query_set.rb +79 -13
- data/lib/woods/evaluation/report_generator.rb +20 -1
- data/lib/woods/export/unit_facts.rb +0 -11
- data/lib/woods/extracted_unit.rb +22 -63
- data/lib/woods/extractor.rb +2503 -192
- data/lib/woods/extractors/action_cable_extractor.rb +9 -4
- data/lib/woods/extractors/ast_source_extraction.rb +20 -2
- data/lib/woods/extractors/caching_extractor.rb +46 -12
- data/lib/woods/extractors/callback_analyzer.rb +39 -9
- data/lib/woods/extractors/component_discovery.rb +123 -0
- data/lib/woods/extractors/concern_extractor.rb +17 -3
- data/lib/woods/extractors/controller_extractor.rb +389 -29
- data/lib/woods/extractors/decorator_extractor.rb +7 -14
- data/lib/woods/extractors/engine_extractor.rb +53 -8
- data/lib/woods/extractors/event_extractor.rb +55 -4
- data/lib/woods/extractors/factory_extractor.rb +49 -11
- data/lib/woods/extractors/graphql_extractor.rb +162 -66
- data/lib/woods/extractors/i18n_extractor.rb +6 -1
- data/lib/woods/extractors/job_extractor.rb +51 -21
- data/lib/woods/extractors/lib_extractor.rb +23 -17
- data/lib/woods/extractors/line_neutralizer.rb +171 -0
- data/lib/woods/extractors/mailer_extractor.rb +9 -1
- data/lib/woods/extractors/manager_extractor.rb +19 -2
- data/lib/woods/extractors/migration_extractor.rb +22 -11
- data/lib/woods/extractors/model_extractor.rb +292 -57
- data/lib/woods/extractors/package_extractor.rb +154 -0
- data/lib/woods/extractors/phlex_extractor.rb +18 -3
- data/lib/woods/extractors/policy_extractor.rb +6 -5
- data/lib/woods/extractors/poro_extractor.rb +13 -14
- data/lib/woods/extractors/pundit_extractor.rb +3 -3
- data/lib/woods/extractors/rails_source_extractor.rb +24 -7
- data/lib/woods/extractors/rake_task_extractor.rb +158 -30
- data/lib/woods/extractors/reference_patterns.rb +38 -0
- data/lib/woods/extractors/route_extractor.rb +58 -2
- data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
- data/lib/woods/extractors/serializer_extractor.rb +3 -4
- data/lib/woods/extractors/service_extractor.rb +11 -1
- data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
- data/lib/woods/extractors/shared_utility_methods.rb +36 -6
- data/lib/woods/extractors/source_nesting.rb +560 -0
- data/lib/woods/extractors/state_machine_extractor.rb +30 -18
- data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
- data/lib/woods/extractors/view_component_extractor.rb +28 -3
- data/lib/woods/extractors/view_engines/erb.rb +17 -3
- data/lib/woods/feedback/gap_detector.rb +9 -3
- data/lib/woods/feedback/store.rb +7 -1
- data/lib/woods/filename_utils.rb +29 -1
- data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
- data/lib/woods/flow_assembler.rb +63 -21
- data/lib/woods/flow_document.rb +1 -0
- data/lib/woods/flow_precomputer.rb +138 -22
- data/lib/woods/gem_mapper.rb +285 -0
- data/lib/woods/generation.rb +185 -0
- data/lib/woods/git_command.rb +38 -0
- data/lib/woods/git_provenance.rb +16 -2
- data/lib/woods/graph_analyzer.rb +408 -34
- data/lib/woods/index_artifact.rb +93 -23
- data/lib/woods/mcp/bearer_auth.rb +102 -13
- data/lib/woods/mcp/bootstrap_state.rb +77 -0
- data/lib/woods/mcp/bootstrapper.rb +582 -77
- data/lib/woods/mcp/config_resolver.rb +66 -6
- data/lib/woods/mcp/errors.rb +60 -0
- data/lib/woods/mcp/index_reader.rb +836 -117
- data/lib/woods/mcp/index_reader_pinning.rb +78 -0
- data/lib/woods/mcp/origin_guard.rb +66 -7
- data/lib/woods/mcp/protocol_policy.rb +98 -0
- data/lib/woods/mcp/provider_probe.rb +45 -6
- data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
- data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
- data/lib/woods/mcp/server.rb +907 -154
- data/lib/woods/mcp/tasks/extension.rb +196 -0
- data/lib/woods/mcp/tasks/request_capture.rb +45 -0
- data/lib/woods/mcp/tasks/store.rb +518 -0
- data/lib/woods/mcp/tool_contract.rb +171 -0
- data/lib/woods/mcp/tool_response_renderer.rb +7 -0
- data/lib/woods/mcp/version_aware_tool_dispatch.rb +3 -9
- data/lib/woods/model_name_cache.rb +19 -1
- data/lib/woods/notion/client.rb +132 -36
- data/lib/woods/notion/exporter.rb +456 -61
- data/lib/woods/notion/mappers/column_mapper.rb +34 -5
- data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
- data/lib/woods/notion/mappers/model_mapper.rb +21 -6
- data/lib/woods/notion/mappers/shared.rb +45 -3
- data/lib/woods/notion/sync_manifest.rb +258 -0
- data/lib/woods/obsidian/errors.rb +6 -0
- data/lib/woods/obsidian/name_mapper.rb +40 -24
- data/lib/woods/obsidian/vault_exporter.rb +103 -36
- data/lib/woods/operator/pipeline_guard.rb +118 -21
- data/lib/woods/operator/status_reporter.rb +20 -3
- data/lib/woods/path_dispatcher.rb +276 -0
- data/lib/woods/payload_store.rb +223 -0
- data/lib/woods/published_index/edge_shaper.rb +61 -0
- data/lib/woods/published_index/generation_catalog.rb +72 -0
- data/lib/woods/published_index/typed_unit_reader.rb +48 -0
- data/lib/woods/published_index.rb +287 -0
- data/lib/woods/railtie.rb +69 -30
- data/lib/woods/railtie_support.rb +167 -0
- data/lib/woods/release.rb +12 -0
- data/lib/woods/reload_policy.rb +206 -0
- data/lib/woods/resilience/circuit_breaker.rb +47 -8
- data/lib/woods/resilience/index_validator.rb +296 -10
- data/lib/woods/resilience/retryable_provider.rb +71 -6
- data/lib/woods/resolved_config.rb +55 -11
- data/lib/woods/retrieval/context_assembler.rb +132 -40
- data/lib/woods/retrieval/query_classifier.rb +25 -6
- data/lib/woods/retrieval/ranker.rb +193 -28
- data/lib/woods/retrieval/search_executor.rb +206 -39
- data/lib/woods/retriever.rb +317 -71
- data/lib/woods/retry_after.rb +22 -2
- data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
- data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
- data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
- data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
- data/lib/woods/ruby_analyzer.rb +21 -5
- data/lib/woods/session_tracer/file_store.rb +138 -19
- data/lib/woods/session_tracer/redis_store.rb +122 -12
- data/lib/woods/session_tracer/session_flow_assembler.rb +54 -11
- data/lib/woods/session_tracer/session_flow_document.rb +52 -6
- data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
- data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
- data/lib/woods/session_tracer/store.rb +14 -1
- data/lib/woods/storage/metadata_store.rb +230 -26
- data/lib/woods/storage/pgvector.rb +180 -22
- data/lib/woods/storage/qdrant.rb +367 -41
- data/lib/woods/storage/snapshotter/metadata.rb +79 -16
- data/lib/woods/storage/snapshotter/vector.rb +128 -17
- data/lib/woods/storage/snapshotter.rb +23 -5
- data/lib/woods/storage/vector_store.rb +49 -8
- data/lib/woods/storage_identity.rb +28 -0
- data/lib/woods/tasks.rb +53 -2
- data/lib/woods/temporal/json_snapshot_store.rb +112 -42
- data/lib/woods/temporal/snapshot_store.rb +139 -42
- data/lib/woods/unblocked/client.rb +119 -17
- data/lib/woods/unblocked/document_builder.rb +34 -2
- data/lib/woods/unblocked/exporter.rb +63 -27
- data/lib/woods/unblocked/rate_limiter.rb +23 -9
- data/lib/woods/unblocked/sync_manifest.rb +16 -8
- data/lib/woods/update_check.rb +24 -1
- data/lib/woods/util/uuid5.rb +124 -0
- data/lib/woods/version.rb +1 -1
- data/lib/woods/watch/daemon.rb +1345 -0
- data/lib/woods/watch/listen_watcher.rb +81 -0
- data/lib/woods/watch/polling_watcher.rb +137 -0
- data/lib/woods/watch/status.rb +169 -0
- data/lib/woods/watch/tree_scan.rb +163 -0
- data/lib/woods/watch/watcher.rb +100 -0
- data/lib/woods.rb +53 -9
- data/plugin/.claude-plugin/plugin.json +18 -0
- data/plugin/hooks/hooks.json +29 -0
- data/plugin/hooks/woods-post-edit.sh +226 -0
- data/plugin/hooks/woods-session-start.sh +77 -0
- data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
- data/plugin/skills/woods-diagnose/SKILL.md +75 -0
- data/plugin/skills/woods-investigate/SKILL.md +39 -0
- data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
- data/plugin/skills/woods-setup/SKILL.md +99 -0
- metadata +102 -26
- data/lib/woods/console/adapters/cache_adapter.rb +0 -58
- data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
- data/lib/woods/console/adapters/job_adapter.rb +0 -74
- data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
- data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
- data/lib/woods/console/bridge.rb +0 -210
- data/lib/woods/console/credential_scanner_registry.rb +0 -36
- data/lib/woods/console/encrypted_credential_snapshot.rb +0 -16
- data/lib/woods/formatting/claude_adapter.rb +0 -98
- data/lib/woods/formatting/generic_adapter.rb +0 -56
- data/lib/woods/formatting/gpt_adapter.rb +0 -64
- data/lib/woods/mcp/http_transport_options.rb +0 -24
- data/lib/woods/notion/mapper.rb +0 -40
- data/lib/woods/observability/health_check.rb +0 -79
- data/lib/woods/observability/instrumentation.rb +0 -34
|
@@ -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
|
+
```
|