woods 1.6.1 → 2.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2035 -0
- data/CONTRIBUTING.md +253 -87
- data/README.md +161 -513
- 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 +655 -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 +455 -0
- data/docs/INTERNALS.md +418 -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 +213 -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 +80 -11
- 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 +171 -2
- 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/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 +65 -42
- 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 +119 -247
- 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 +463 -90
- data/lib/woods/console/tools/tier1.rb +1 -5
- data/lib/woods/console/tools/tier4.rb +18 -9
- 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 +2783 -238
- 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 +147 -26
- data/lib/woods/flow_document.rb +1 -0
- data/lib/woods/flow_precomputer.rb +175 -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 +564 -87
- 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 +898 -152
- 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/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 +236 -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 +26 -8
- 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/trace_enricher.rb +3 -0
- data/lib/woods/ruby_analyzer.rb +21 -5
- data/lib/woods/session_tracer/file_store.rb +138 -19
- data/lib/woods/session_tracer/middleware.rb +1 -2
- data/lib/woods/session_tracer/redis_store.rb +122 -12
- data/lib/woods/session_tracer/session_flow_assembler.rb +57 -17
- data/lib/woods/session_tracer/session_flow_document.rb +56 -14
- 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 +138 -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 +134 -23
- 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/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/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,841 @@
|
|
|
1
|
+
# Troubleshooting Woods
|
|
2
|
+
|
|
3
|
+
This guide covers the most common problems encountered when installing, extracting, and using Woods. Each section follows the pattern: **symptom → cause → fix**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Reference
|
|
8
|
+
|
|
9
|
+
| Error message | Cause | Fix |
|
|
10
|
+
|---------------|-------|-----|
|
|
11
|
+
| `No manifest.json found` | Wrong index path or no published generation | Use the path visible to the server process; run `woods:validate` |
|
|
12
|
+
| `uninitialized constant Rails` | Not running inside Rails app | Run via `bundle exec rake` in Rails root |
|
|
13
|
+
| `type "vector" does not exist` | pgvector not installed | `CREATE EXTENSION vector` in PostgreSQL |
|
|
14
|
+
| `Connection refused (localhost:11434)` | Ollama not running | `ollama serve` |
|
|
15
|
+
| `Connection refused (localhost:6333)` | Qdrant not running | Start Qdrant container |
|
|
16
|
+
| Qdrant private/loopback URL rejected | SSRF guard is working | Add `allow_private_hosts: true` only for a deliberately trusted endpoint |
|
|
17
|
+
| Missing `console_sql` / `console_query` | Read tools disabled | Enable `console_embedded_read_tools` |
|
|
18
|
+
| `database is locked` | SQLite concurrent access | Run one extraction at a time |
|
|
19
|
+
| `Dimension mismatch` | Embedding model changed | Full re-index: extract + embed |
|
|
20
|
+
| `401 Unauthorized` (Notion) | Invalid API token | Check `NOTION_API_TOKEN` env var |
|
|
21
|
+
| `404 Not Found` (Notion) | Wrong database ID | Verify ID + integration access |
|
|
22
|
+
| `broken pipe` (Docker console) | Missing `-i` flag | Add `-i` to docker exec args |
|
|
23
|
+
| `No such container` | Wrong container name | Check with `docker ps --format '{{.Names}}'` |
|
|
24
|
+
| `JSON parse errors` (MCP) | Rails boot noise on stdout | Remove `puts` calls from initializers |
|
|
25
|
+
| Query timeout | Large table, no scope | Add scope conditions to narrow results |
|
|
26
|
+
| Empty extraction output | `eager_load!` failure | Check for `NameError` in boot output |
|
|
27
|
+
| Git metadata missing | Shallow clone in CI | Use `fetch-depth: 2` or higher |
|
|
28
|
+
| Parallel tool calls all fail | MCP client batches calls | Send calls sequentially, validate params first |
|
|
29
|
+
| HTTP transport refuses to start on `0.0.0.0` | Missing bearer token | Set `WOODS_MCP_HTTP_TOKEN=…` or bind loopback only |
|
|
30
|
+
| HTTP transport returns `403 Origin not allowed` | Origin header not in allow-list | Set `WOODS_MCP_HTTP_ALLOWED_ORIGINS="https://example.com"` (comma-separated; default is loopback-only) |
|
|
31
|
+
| Tool returns `error_code: :not_configured` | Feature flag or credential not set | Check `config_key` in `_meta` and the linked `doc_link` |
|
|
32
|
+
| Tool returns `error_code: :rate_limited` | `PipelineGuard` 5-min cooldown hit | Wait `retry_after_seconds` from `_meta`, then retry |
|
|
33
|
+
|
|
34
|
+
### First-Pass Diagnostics
|
|
35
|
+
|
|
36
|
+
For a single-call health snapshot, call the Index Server's `woods_status` tool. It reports:
|
|
37
|
+
|
|
38
|
+
- Extraction freshness (last run time, unit count, index version)
|
|
39
|
+
- Overall readiness plus index, watch, retriever, and bootstrap state (`ready`, `index`, `watch`, `retriever`, `bootstrap` sections)
|
|
40
|
+
- Which optional features are configured (embedding provider, Notion, session tracer)
|
|
41
|
+
- Per-feature config-key hints for anything missing
|
|
42
|
+
- `server.update`: the installed gem version, the newest version the process knows about (the latest published release, or the installed version itself when the install is ahead of the registry or the check could not run), and an `update_available` flag (a best-effort RubyGems check, cached 24h; disable with `WOODS_NO_UPDATE_CHECK=1`)
|
|
43
|
+
|
|
44
|
+
Agents cold-connecting to a server should call `woods_status` before any other tool, it eliminates most "why is this empty?" guesswork.
|
|
45
|
+
|
|
46
|
+
If a tool call fails with **"Tool not found: … not available in the installed Woods v…"**, the client is asking for a tool a newer gem provides. Run `bundle update woods` and reconnect the MCP server, then retry.
|
|
47
|
+
|
|
48
|
+
## Extraction Problems
|
|
49
|
+
|
|
50
|
+
### Extraction produces empty or incomplete output
|
|
51
|
+
|
|
52
|
+
**Symptom:** Running `rake woods:extract` completes without errors but produces fewer units than expected, or only some model types appear.
|
|
53
|
+
|
|
54
|
+
**Cause:** `eager_load!` failed silently partway through loading your application. Zeitwerk processes directories alphabetically, if a directory early in the alphabet (e.g., `app/graphql/`) fails to load due to a missing gem, Zeitwerk aborts that pass and never reaches `app/models/`. Woods detects this and falls back to per-directory loading, but some units may still be missing.
|
|
55
|
+
|
|
56
|
+
**Fix:**
|
|
57
|
+
|
|
58
|
+
1. Check for `NameError` or `LoadError` in the extraction output:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bundle exec rake woods:extract 2>&1 | grep -i "error\|uninitialized"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Either install the missing gem(s) referenced in the error, or exclude the problem directory from eager loading:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# config/application.rb
|
|
68
|
+
config.eager_load_paths -= [Rails.root.join('app/graphql')]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. Re-run extraction after resolving the load issue.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Extraction fails with "Cannot find Rails" or "uninitialized constant"
|
|
76
|
+
|
|
77
|
+
**Symptom:** Running a rake task fails immediately with `NameError: uninitialized constant Rails` or a similar error about ActiveRecord, ApplicationRecord, or other Rails constants.
|
|
78
|
+
|
|
79
|
+
**Cause:** Extraction requires a booted Rails environment. Woods uses runtime introspection (`ActiveRecord::Base.descendants`, `Rails.application.routes`, reflection APIs), these APIs do not exist outside a running Rails app.
|
|
80
|
+
|
|
81
|
+
**Fix:** Always run extraction rake tasks inside your Rails app:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Correct: run from Rails app root
|
|
85
|
+
bundle exec rake woods:extract
|
|
86
|
+
|
|
87
|
+
# Docker: run inside container
|
|
88
|
+
docker compose exec app bundle exec rake woods:extract
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Woods cannot extract from source files alone. It is not a static analysis tool.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### Extraction is very slow
|
|
96
|
+
|
|
97
|
+
**Symptom:** A full extraction is much slower than this application's established baseline.
|
|
98
|
+
|
|
99
|
+
**Cause:** Two common causes, a very large codebase (500+ models), or framework source extraction enabled on an app with many gems.
|
|
100
|
+
|
|
101
|
+
**Fix:**
|
|
102
|
+
|
|
103
|
+
Disable framework source extraction if you don't need Rails/gem internals:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
config.include_framework_sources = false
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For subsequent runs, use incremental mode instead of full extraction:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bundle exec rake woods:incremental
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Incremental extraction only re-extracts files that changed since the last run. It skips unchanged units and is typically 5-10× faster.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Some extractor types are not appearing in output
|
|
120
|
+
|
|
121
|
+
**Symptom:** You expect state machines, events, decorators, or other unit types but they don't appear in the output directory.
|
|
122
|
+
|
|
123
|
+
**Cause:** All 35 extractors always run during extraction, there is no opt-in/opt-out mechanism. If a unit type is missing, it means the extractor found nothing to extract. Common reasons:
|
|
124
|
+
|
|
125
|
+
- The expected directory doesn't exist (e.g., no `app/decorators/` for decorators)
|
|
126
|
+
- The required gem isn't installed (e.g., `aasm` or `state_machines` for state machine extraction)
|
|
127
|
+
- The code doesn't match the extractor's expected patterns
|
|
128
|
+
|
|
129
|
+
**Fix:** Verify the code exists and matches what the extractor looks for:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Check if the directory exists
|
|
133
|
+
ls app/decorators/ app/state_machines/ 2>/dev/null
|
|
134
|
+
|
|
135
|
+
# Check extraction output for that type
|
|
136
|
+
ls tmp/woods/decorators/ tmp/woods/state_machines/ 2>/dev/null
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Note: `config.extractors` does not control anything today, it's accepted for forward compatibility only and is not consulted by extraction or retrieval. See [EXTRACTOR_REFERENCE.md](EXTRACTOR_REFERENCE.md) for what each extractor looks for.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### Incremental extraction doesn't seem to update routes, middleware, or engines
|
|
144
|
+
|
|
145
|
+
**Symptom:** After changing your routes file or adding a middleware, `rake woods:incremental` doesn't seem to update those units.
|
|
146
|
+
|
|
147
|
+
**Cause:** Nine unit types don't map to individual files, so they can't be diffed per file: `route`, `middleware`, `engine`, `scheduled_job`, `state_machine`, `factory`, `event`, `database_view`, and `rails_source`. Incremental mode still updates them, it re-runs the whole extractor when a specific trigger path changes, instead of skipping the type:
|
|
148
|
+
|
|
149
|
+
| Type | Trigger path |
|
|
150
|
+
|------|--------------|
|
|
151
|
+
| `route` | `config/routes.rb` |
|
|
152
|
+
| `engine` | `config/routes.rb`, `Gemfile.lock` |
|
|
153
|
+
| `middleware` | `config/application.rb`, `Gemfile.lock`, any file under `config/initializers`/`config/environments` |
|
|
154
|
+
| `scheduled_job` | `config/recurring.yml`, `config/sidekiq_cron.yml`, `config/schedule.rb` |
|
|
155
|
+
| `state_machine` | any `.rb` change under the scanned model directories |
|
|
156
|
+
| `factory` | any `.rb` change under `spec/factories`/`test/factories` |
|
|
157
|
+
| `event` | any `.rb` change under `app/` |
|
|
158
|
+
| `database_view` | any `.sql` change under `db/views` |
|
|
159
|
+
| `rails_source` | `Gemfile.lock` (only when `include_framework_sources` is enabled) |
|
|
160
|
+
|
|
161
|
+
If your change doesn't match one of these trigger paths, the type genuinely wasn't updated, that's the actual bug to chase, not a documented limitation.
|
|
162
|
+
|
|
163
|
+
**Fix:** Route and event changes that match the triggers above need no manual
|
|
164
|
+
full extraction; `woods:incremental` and `woods:watch` rerun their whole-app
|
|
165
|
+
extractors. If `woods:validate` still reports drift outside the trigger
|
|
166
|
+
contract, use a full extraction as the recovery step:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bundle exec rake woods:extract
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Git metadata is missing or shows zeros
|
|
175
|
+
|
|
176
|
+
**Symptom:** Units have `last_modified_at: null` or `change_frequency: 0` in the JSON output.
|
|
177
|
+
|
|
178
|
+
**Cause:** The git repository is a shallow clone (common in CI with `fetch-depth: 1`). Woods uses `git log` to compute change frequency, a shallow clone has no history to analyze.
|
|
179
|
+
|
|
180
|
+
**Fix:** Fetch at least two commits:
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
# .github/workflows/index.yml
|
|
184
|
+
- uses: actions/checkout@v4
|
|
185
|
+
with:
|
|
186
|
+
fetch-depth: 2 # minimum for incremental; use 0 for full history
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### Every unit reports `commit_count: 0` and `change_frequency: "new"`
|
|
192
|
+
|
|
193
|
+
**Symptom:** Not a few units, all of them, in an application whose files clearly
|
|
194
|
+
have history. `volatile_dependencies` comes back empty at the same time.
|
|
195
|
+
|
|
196
|
+
**Cause:** git ran but could not resolve any ref. The usual shape is a
|
|
197
|
+
containerized linked worktree: `GIT_DIR` points at the worktree's *private*
|
|
198
|
+
git directory, `git rev-parse --git-dir` succeeds, and every ref lookup fails,
|
|
199
|
+
because that private directory reaches the shared object store through a
|
|
200
|
+
relative `commondir` pointer that resolves outside the mount. `git log` then
|
|
201
|
+
exits 0 with no output, and zero commits is indistinguishable from a file that
|
|
202
|
+
was never committed.
|
|
203
|
+
|
|
204
|
+
Woods now requires `git rev-parse HEAD` to succeed before enriching anything.
|
|
205
|
+
When it does not, the git keys are omitted from every unit, provenance records
|
|
206
|
+
`"unknown"`, and one warning names git's own reason. Absent keys mean "not
|
|
207
|
+
known"; they never mean "brand new".
|
|
208
|
+
|
|
209
|
+
**Fix:** Point `WOODS_GIT_DIR` at the *canonical* git directory, the one the
|
|
210
|
+
worktree's `gitdir:` pointer ultimately leads to, and make sure it is mounted:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# docker-compose.yml, mounting the parent repository's git directory
|
|
214
|
+
# volumes:
|
|
215
|
+
# - /path/to/repo/.git:/canonical-git:ro
|
|
216
|
+
WOODS_GIT_DIR=/canonical-git bundle exec rake woods:extract
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`WOODS_GIT_DIR` wins over whatever the worktree pointer says, and applies to
|
|
220
|
+
every git call Woods makes: per-unit enrichment, `manifest.json` provenance,
|
|
221
|
+
and the diff range `woods:incremental` resolves. All three run through
|
|
222
|
+
`Woods::GitCommand.argv`, so the override cannot reach two of them and miss the
|
|
223
|
+
third.
|
|
224
|
+
|
|
225
|
+
**`GIT_DIR` alone is not enough for a linked worktree.** Woods honors git's own
|
|
226
|
+
`GIT_DIR` and `GIT_COMMON_DIR` because git does, but setting `GIT_DIR` to a
|
|
227
|
+
worktree's private git directory only moves the failure: the `commondir`
|
|
228
|
+
pointer inside it is relative, so it still resolves to a path that is not
|
|
229
|
+
mounted, and `GIT_COMMON_DIR` does not override it. Either mount the canonical
|
|
230
|
+
git directory at the same absolute path the pointer names, or use
|
|
231
|
+
`WOODS_GIT_DIR`.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### `woods:incremental` exits 1 with "could not resolve the git diff range"
|
|
236
|
+
|
|
237
|
+
**Symptom:** A CI job fails with `ERROR: could not resolve the git diff range "..."` instead of indexing.
|
|
238
|
+
|
|
239
|
+
**Cause:** The range git was asked to diff does not resolve in the checkout: a GitLab `CI_COMMIT_BEFORE_SHA` of all zeros (new branch), a GitHub base ref that was never fetched, a shallow clone with no `HEAD~1`, or a typo'd revision. This used to read as "no relevant files changed" and the task exited 0 while the sync never ran; it now fails closed, because a green job hiding a skipped sync lets index drift grow unbounded. The one stand-down: a *running* watch daemon maintaining the same index exits 0 with a printed reason, since its start-up catch-up covers the changes. A degraded daemon covers nothing and still exits 1.
|
|
240
|
+
|
|
241
|
+
**Fix:** Repair or provide the range (fetch the base ref, e.g. `fetch-depth: 2` or more, or correct the CI environment variables), set `CHANGED_FILES` explicitly to bypass range resolution, or run a full extraction:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
bundle exec rake woods:extract
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Note `WOODS_IGNORE_WATCH=1` removes daemon coverage rather than bypassing the failure — an unresolved range then exits 1. The full exit-behavior table lives in [Incremental Extraction](./INCREMENTAL_EXTRACTION.md#exit-behavior-in-ci-chains).
|
|
248
|
+
|
|
249
|
+
An image with no `git` binary at all reports `git unavailable: …` and takes the same decision — install git in the image, or set `CHANGED_FILES` so git is never consulted.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### `woods:embed`, `woods:embed_incremental`, or `woods:notion_sync` exits 1 after printing `Errors: N`
|
|
254
|
+
|
|
255
|
+
**Symptom:** The task prints its normal summary, reports a non-zero error count, and the job fails.
|
|
256
|
+
|
|
257
|
+
**Cause:** Per-item failures accumulated during the run — a revoked or rate-limited API key that kept failing after the resilience stack exhausted its retries, a full or unreachable vector store, or a Notion 401/400 on every page. These tasks used to print the count and exit 0, which left CI green while the embedding index or the Notion database drifted stale indefinitely. They now fail like their siblings `woods:unblocked_sync` and `woods:obsidian`, and like the extraction family.
|
|
258
|
+
|
|
259
|
+
**Fix:** Read the first five errors the task prints — they name the failing units or pages. Then:
|
|
260
|
+
|
|
261
|
+
- Embedding: check `OPENAI_API_KEY` (or the Ollama endpoint), the provider's rate limits, and free space in the vector store. Re-run `woods:embed_incremental`; the checkpoint means already-embedded units are not paid for twice.
|
|
262
|
+
- Notion: check `NOTION_API_TOKEN` and that every database ID in `notion_database_ids` is shared with the integration.
|
|
263
|
+
|
|
264
|
+
A partial run is not rolled back: whatever succeeded is durable, and re-running after the fix converges.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### Extraction exits non-zero after "Could not publish generation"
|
|
269
|
+
|
|
270
|
+
**Symptom:** `woods:extract`, `woods:incremental`, `woods:refresh`, or
|
|
271
|
+
`woods:extract_framework` raises `Woods::ExtractionError` after writing its
|
|
272
|
+
payload, with a message saying that the previous generation remains active.
|
|
273
|
+
|
|
274
|
+
**Cause:** Woods writes a complete candidate payload first and commits it by
|
|
275
|
+
atomically updating `generation.json` last. The marker write failed (commonly
|
|
276
|
+
permissions, a read-only mount, no free space, or an unhealthy filesystem), so
|
|
277
|
+
readers cannot safely discover the candidate payload. One-shot tasks fail
|
|
278
|
+
instead of printing a false success; existing readers continue serving the
|
|
279
|
+
previous complete generation.
|
|
280
|
+
|
|
281
|
+
**Fix:** Correct the filesystem or mount problem named in the exception, then
|
|
282
|
+
rerun the same task. Do not edit `generation.json` by hand or point readers at
|
|
283
|
+
the unreachable payload. A resident `woods:watch` process handles the same
|
|
284
|
+
failure differently: it reports `degraded`, carries the changed paths, and
|
|
285
|
+
retries after a later filesystem event.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### `manifest.json` shows the wrong branch (or `git_branch: "unknown"`) in a worktree
|
|
290
|
+
|
|
291
|
+
**Symptom:** `git_branch` / `git_sha` in `manifest.json` name a different branch than the worktree is actually on, or report `"unknown"`. The extracted units themselves are correct, only the provenance metadata is off.
|
|
292
|
+
|
|
293
|
+
**Cause:** In a linked git worktree, `.git` is a *file* containing a `gitdir:` pointer to the real git directory, often an absolute host path. When extraction runs where that path can't be resolved (e.g. inside a container where the host path isn't mounted), git can't read the ref. Woods now reports `"unknown"` in that case rather than emitting a stale, misleading value (previously it fell back to a baked `GIT_BRANCH`/`GIT_SHA` build arg).
|
|
294
|
+
|
|
295
|
+
**Fix:** Make the worktree's git directory reachable from the extraction environment, for example, mount the parent repository (the directory the `gitdir:` pointer references) into the container, or run extraction from a normal (non-worktree) checkout. With the real git directory reachable, `git_branch`/`git_sha` resolve correctly. If the checkout legitimately ships without a `.git` at all (a source tarball, or a Docker `COPY` that excludes it), set `GIT_BRANCH` / `GIT_SHA` explicitly. Woods honors these when there is no `.git` at the root (or no git binary), but suppresses them when a `.git` *is* present but unresolvable (so a stale build arg can't mask a worktree).
|
|
296
|
+
|
|
297
|
+
When the canonical git directory is mounted but not at the path the pointer
|
|
298
|
+
names, set `WOODS_GIT_DIR` to where it actually is. It wins over the pointer for
|
|
299
|
+
provenance and for unit-level git metadata both. Setting git's own `GIT_DIR` to
|
|
300
|
+
the worktree's private git directory does not work: its `commondir` pointer is
|
|
301
|
+
relative and resolves outside the mount.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## MCP Server Problems
|
|
306
|
+
|
|
307
|
+
### "No manifest.json" error when starting the Index Server
|
|
308
|
+
|
|
309
|
+
**Symptom:** `woods-mcp-start` exits with an error like `No manifest.json found at /path/to/...` even though extraction completed.
|
|
310
|
+
|
|
311
|
+
**Cause:** The Index Server is using the container-internal path rather than the host-side path to the volume-mounted output. The server runs on the host and cannot access container filesystem paths.
|
|
312
|
+
|
|
313
|
+
**Fix:** Use the host path in your `.mcp.json`:
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"mcpServers": {
|
|
318
|
+
"codebase": {
|
|
319
|
+
"command": "woods-mcp-start",
|
|
320
|
+
"args": ["./tmp/woods"]
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Verify the output is accessible from the host:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
ls ./tmp/woods/manifest.json
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Since Woods 2.0, a healthy index may not have `manifest.json` at the output root at all.** Extraction publishes each generation into an immutable `payloads/gen-<N>/` directory and points to it from `generation.json`. If the flat path is missing, check the payload path instead before assuming extraction failed:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
cat ./tmp/woods/generation.json # {"number": 42, "payload": "payloads/gen-42", ...}
|
|
336
|
+
ls ./tmp/woods/payloads/gen-42/manifest.json
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`woods-mcp-start` and `IndexReader` already resolve this automatically, this is only for manual inspection. If neither path has a manifest, your Docker volume mount is not configured correctly. See [DOCKER_SETUP.md](DOCKER_SETUP.md).
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### Index Server exits with `MissingArtifact`
|
|
344
|
+
|
|
345
|
+
**Symptom:** `woods-mcp` exits 2 with `MissingArtifact: No woods.json found ...`.
|
|
346
|
+
|
|
347
|
+
**Cause:** Strict mode is enabled (`WOODS_REQUIRE_INDEX=1`) but no embedding index has been written. By default the server boots without `woods.json`, it serves pattern/regex/structural tools and skips semantic search. You only see this error when you've explicitly opted into fail-closed behavior.
|
|
348
|
+
|
|
349
|
+
**Fix:** Either generate the index so semantic search is available:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
bundle exec rake woods:extract
|
|
353
|
+
bundle exec rake woods:embed # writes woods.json + vector dumps
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
…or unset `WOODS_REQUIRE_INDEX` to boot in pattern-only mode. (The older `WOODS_ALLOW_AUTODETECT=1` flag is no longer needed, auto-detect is the default.)
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### `codebase_retrieve` reports degraded semantic search
|
|
361
|
+
|
|
362
|
+
**Symptom:** `codebase_retrieve` answers with a tool error carrying `error_code: degraded_index` ("Semantic search is degraded…") instead of results, and never with a silently empty context. `woods_status` shows `bootstrap.status: "degraded"`, often with a `hydration_failures` report.
|
|
363
|
+
|
|
364
|
+
**Cause:** The retriever is not healthy, and the server refuses to disguise that as "no matches". Two phases are distinguished in the error metadata:
|
|
365
|
+
|
|
366
|
+
- `phase: "boot"` — a dump failed to hydrate at startup (corrupt or unreadable `vectors.bin` / `metadata.msgpack`), so the affected in-memory store is empty. Before this guidance existed, the server reported a healthy boot and answered every query with empty results as if they were legitimate.
|
|
367
|
+
- `phase: "query"` — the metadata store failed while serving (storage outage, permissions). The typed `Woods::Retriever::StoreError` is mapped to the same degraded payload.
|
|
368
|
+
- `phase: "reload"` — a `reload` attempt failed (M7). Candidate stores are built off-side from one captured generation marker and one captured promoted-dump identity, and a candidate that cannot hydrate aborts the whole transaction: nothing is swapped, the reader keeps the previous generation, and the old retriever keeps answering queries. The reload tool answers with a `degraded_index` error carrying `phase: "reload"`, the `generation:` still being served, and the `stores:` whose refresh failed; `woods_status` exposes the same condition additively as `bootstrap.reload_failure`. This is distinct from the boot `degraded` state — the old stores are healthy, so `codebase_retrieve` keeps working while the condition is visible.
|
|
369
|
+
|
|
370
|
+
The `stores:` field names what is affected (`vector`, `metadata`, `graph`) and `reason:` carries the underlying error.
|
|
371
|
+
|
|
372
|
+
**Fix:**
|
|
373
|
+
|
|
374
|
+
1. Read `reason:` from the error payload, or call `woods_status` and read `bootstrap.reason` / `bootstrap.hydration_failures`. For a `reload` failure, read `bootstrap.reload_failure` instead — its `generation` field names the generation still being served.
|
|
375
|
+
2. For a `boot` failure: confirm the index directory is readable, re-run `bundle exec rake woods:embed` if the dump may be corrupt, then restart the MCP server.
|
|
376
|
+
3. For a `query` failure: check the backing metadata store (the SQLite database in the index directory, or your remote vector backend) for availability and permissions.
|
|
377
|
+
4. For a `reload` failure: no restart is needed — the server is serving the previous complete generation. Fix the underlying issue the `reason:` names (a corrupt or missing dump usually means re-running `woods:embed`), then invoke `reload` again. A successful reload swaps the new bundle in and clears the condition (`bootstrap.reload_failure` disappears from `woods_status`). Three reasons resolve on their own:
|
|
378
|
+
- `promoted dump changed during reload` — an embed published while the reload was building candidates. Invoke `reload` again once the writer finishes.
|
|
379
|
+
- `index generation moved during reload` — same, for a unit-index publication. Invoke `reload` again.
|
|
380
|
+
- `could not acquire the extraction writer lock` — a writer held the extraction PipelineLock for the whole wait. Invoke `reload` again once the writer finishes. The reload needs write access to the index directory for this lock, same as every writer.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
### No tools appear in the MCP client
|
|
385
|
+
|
|
386
|
+
**Symptom:** The MCP client connects but shows no tools, or the server exits immediately.
|
|
387
|
+
|
|
388
|
+
**Cause:** The server failed to start, typically due to missing gem dependencies or an incorrect working directory.
|
|
389
|
+
|
|
390
|
+
**Fix:**
|
|
391
|
+
|
|
392
|
+
1. Check stderr for errors:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
woods-mcp-start ./tmp/woods 2>&1
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
2. Ensure the gem's executables are installed:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
bundle install
|
|
402
|
+
which woods-mcp-start
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
3. For the Console Server, run the rake task directly to see error output:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
bundle exec rake woods:console
|
|
409
|
+
# Should hang waiting for MCP protocol input: if it exits, check the error
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### A console inventory tool is not listed
|
|
415
|
+
|
|
416
|
+
**Symptom:** A tool from the 31-schema inventory is absent from `tools/list`.
|
|
417
|
+
|
|
418
|
+
**Cause:** Supported servers advertise only executable tools: 9 Tier 1 tools by
|
|
419
|
+
default, plus SQL/query when explicitly enabled.
|
|
420
|
+
|
|
421
|
+
**Fix:** Enable `console_embedded_read_tools` for `console_sql` and
|
|
422
|
+
`console_query`. Tier 2, Tier 3, and eval remain inventory only.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
### MCP client shows JSON parse errors
|
|
427
|
+
|
|
428
|
+
**Symptom:** The MCP client reports protocol errors, malformed JSON, or unexpected tokens.
|
|
429
|
+
|
|
430
|
+
**Cause:** Rails boot emits output to stdout (OpenTelemetry notices, gem warnings, initializer `puts` calls). The `woods:console` rake task redirects stdout to stderr before Rails boots, but custom initializers that print output before this capture can break the MCP protocol.
|
|
431
|
+
|
|
432
|
+
**Fix:**
|
|
433
|
+
|
|
434
|
+
1. Check for `puts` or `print` calls in your `config/initializers/` files that run at boot.
|
|
435
|
+
2. Use `Rails.logger` instead of `puts` in initializers.
|
|
436
|
+
3. Test by running the rake task and isolating streams:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
bundle exec rake woods:console 2>/dev/null
|
|
440
|
+
# MCP protocol output (stdout) should be valid JSON-RPC
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### Parallel tool calls fail together (sibling call failures)
|
|
446
|
+
|
|
447
|
+
**Symptom:** When an MCP client sends multiple tool calls in parallel and one fails, all sibling calls in the same batch also fail.
|
|
448
|
+
|
|
449
|
+
**Cause:** This is MCP client behavior, not a server bug. Some clients (including Claude Desktop and Claude Code) batch parallel tool calls into one request. If any call in the batch raises an error, the transport may reject the entire response frame.
|
|
450
|
+
|
|
451
|
+
**Fix:** There is no server-side fix. Workarounds:
|
|
452
|
+
|
|
453
|
+
1. **Send one tool call at a time.** If your client supports sequential mode, use it for unreliable calls.
|
|
454
|
+
2. **Validate parameters before calling.** Use `search` to confirm identifiers exist before passing them to `dependencies` or `lookup`.
|
|
455
|
+
3. **Avoid mixing high-risk and low-risk calls.** A `lookup` with a typo will take down a parallel `search` that would have succeeded.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### MCP client disconnects mid-session
|
|
460
|
+
|
|
461
|
+
**Symptom:** The MCP client reports "server disconnected" or "transport closed" during normal use.
|
|
462
|
+
|
|
463
|
+
**Cause:** Several possible causes, the server process crashed, the stdio transport pipe was broken, or the client's idle timeout expired.
|
|
464
|
+
|
|
465
|
+
**Fix:**
|
|
466
|
+
|
|
467
|
+
1. Check server stderr for crash output.
|
|
468
|
+
2. Run the configured command manually from the same `cwd` and inspect stderr. `woods-mcp-start` validates the index before launch but does not restart a crashed server.
|
|
469
|
+
3. For Docker setups, ensure the container stays running: `docker compose exec -d app tail -f /dev/null` keeps it alive.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### Console queries time out on large tables
|
|
474
|
+
|
|
475
|
+
**Symptom:** `console_count` or `console_sample` times out with an error mentioning statement timeout.
|
|
476
|
+
|
|
477
|
+
**Cause:** The default statement timeout is 5000ms (5 seconds). Large tables without a scope condition can exceed this.
|
|
478
|
+
|
|
479
|
+
**Fix:** Add scope conditions to narrow the result set:
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
console_count(model: "Order", scope: { status: "pending" })
|
|
483
|
+
console_sample(model: "Order", scope: { created_at_gteq: "2025-01-01" })
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Scope keys are flat, Ransack-style predicates (`_eq`, `_gt`, `_gteq`, `_lt`, `_lteq`, `_in`, `_not_in`, `_null`, `_not_null`, `_present`, `_blank`, `_matches`) suffixed onto a column name, `scope: { created_at: { gte: "..." } }` (a nested hash) is rejected. A plain key with no suffix (`status: "pending"`) is an equality match.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Embedding Problems
|
|
491
|
+
|
|
492
|
+
### Configuring vector search on MySQL
|
|
493
|
+
|
|
494
|
+
**Symptom:** You're on MySQL (or Percona / MariaDB / Aurora MySQL) and `config.vector_store = :pgvector` fails at boot, or you can't find a `:mysql` vector adapter in `lib/woods/storage/`.
|
|
495
|
+
|
|
496
|
+
**Cause:** MySQL has no native vector-search extension equivalent to `pgvector`. Woods does not emulate vector search in MySQL, every vector adapter the gem ships delegates to a real vector engine. Note that being on MySQL only constrains the *vector* choice: Woods keeps its own metadata in SQLite or memory (`metadata_store: :sqlite | :in_memory`), never in your application database, so there is no MySQL metadata adapter to configure. (Native `:mysql` / `:postgresql` metadata adapters are future work, `BACKEND_MATRIX.md` documents the shape they would take.)
|
|
497
|
+
|
|
498
|
+
**Fix:** Pair the host app with one of the supported external vector backends. Qdrant is the recommended default for self-hosted / Docker stacks:
|
|
499
|
+
|
|
500
|
+
```ruby
|
|
501
|
+
# config/initializers/woods.rb: MySQL host app: vectors go to Qdrant
|
|
502
|
+
Woods.configure do |config|
|
|
503
|
+
config.metadata_store = :sqlite # Woods-internal metadata, not your app DB
|
|
504
|
+
config.vector_store = :qdrant
|
|
505
|
+
config.vector_store_options = {
|
|
506
|
+
url: ENV.fetch("QDRANT_URL", "http://localhost:6333"),
|
|
507
|
+
collection: "woods_units",
|
|
508
|
+
allow_private_hosts: true # explicit opt-in for trusted localhost/private URL
|
|
509
|
+
}
|
|
510
|
+
config.embedding_provider = :openai
|
|
511
|
+
config.embedding_options = { api_key: ENV.fetch("OPENAI_API_KEY") }
|
|
512
|
+
end
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
The Postgres equivalent (in-database vectors via pgvector) is shown for contrast:
|
|
516
|
+
|
|
517
|
+
```ruby
|
|
518
|
+
# PostgreSQL host: vectors can live in the same database via pgvector
|
|
519
|
+
Woods.configure do |config|
|
|
520
|
+
config.metadata_store = :sqlite
|
|
521
|
+
config.vector_store = :pgvector
|
|
522
|
+
config.vector_store_options = {
|
|
523
|
+
connection: your_pg_connection, # a PG::Connection to a pgvector-enabled DB
|
|
524
|
+
dimensions: 1536
|
|
525
|
+
}
|
|
526
|
+
config.embedding_provider = :openai
|
|
527
|
+
config.embedding_options = { api_key: ENV.fetch("OPENAI_API_KEY") }
|
|
528
|
+
end
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
For local development against a MySQL app, the `:local` preset (`Woods.configure_with_preset(:local)`, in-memory vectors, SQLite metadata, Ollama embeddings) is a reasonable stand-in. It requires the `sqlite3` gem plus a running Ollama service, but does not exercise the production vector engine. Production MySQL stacks should run Qdrant; the `:production` preset (`vector_store: :qdrant`) is the matching starting point.
|
|
532
|
+
|
|
533
|
+
See [`docs/BACKEND_MATRIX.md`](BACKEND_MATRIX.md#database-compatibility) for the full matrix and the [MySQL + Qdrant section](BACKEND_MATRIX.md#mysql--qdrant-classic-rails) for graph-traversal details (recursive CTEs on 8.0+).
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
### "Dimension mismatch" error when querying embeddings
|
|
538
|
+
|
|
539
|
+
**Symptom:** `codebase_retrieve` raises an error about vector dimensions not matching.
|
|
540
|
+
|
|
541
|
+
**Cause:** The embedding model was changed after embeddings were already stored. The existing vectors have a different dimensionality than the current model produces, and the vector store cannot mix them.
|
|
542
|
+
|
|
543
|
+
**Fix:** Run a full re-index to regenerate all embeddings with the new model:
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
bundle exec rake woods:extract
|
|
547
|
+
bundle exec rake woods:embed
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Woods detects the dimension mismatch and raises `Woods::MCP::DimensionMismatch` rather than letting it become a runtime error: `rake woods:embed` refuses before embedding anything (comparing the provider's dimension against the width the `woods_vectors` table or Qdrant collection was created with), and the MCP server refuses at boot (comparing against the dump's WVF1 header). The message names both dimensions and the remedy, drop the vector store and re-index.
|
|
551
|
+
|
|
552
|
+
**A dimension mismatch is never silently tolerated.** If you are getting poor results without seeing this error, the cause is something else.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
### OpenAI API errors during embedding
|
|
557
|
+
|
|
558
|
+
**Symptom:** Embedding generation fails with `401 Unauthorized` or `429 Too Many Requests`.
|
|
559
|
+
|
|
560
|
+
**Cause:** Missing `OPENAI_API_KEY` environment variable (401), or hitting OpenAI rate limits (429).
|
|
561
|
+
|
|
562
|
+
**Fix:**
|
|
563
|
+
|
|
564
|
+
For 401, set the API key:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
export OPENAI_API_KEY=sk-...
|
|
568
|
+
bundle exec rake woods:embed
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Or configure it in your initializer:
|
|
572
|
+
|
|
573
|
+
```ruby
|
|
574
|
+
config.embedding_options = { api_key: ENV['OPENAI_API_KEY'] }
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
For 429, embedding generation is automatically retried with backoff. If rate limits persist, consider batching with smaller codebases or switching to Ollama for local embeddings.
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
### Ollama connection refused
|
|
582
|
+
|
|
583
|
+
**Symptom:** Embedding generation fails with `Connection refused` pointing to `localhost:11434`.
|
|
584
|
+
|
|
585
|
+
**Cause:** The Ollama server is not running, or it is running on a different port.
|
|
586
|
+
|
|
587
|
+
**Fix:**
|
|
588
|
+
|
|
589
|
+
1. Start Ollama: `ollama serve`
|
|
590
|
+
2. Verify the model is available: `ollama list`
|
|
591
|
+
3. If using a non-default port, update config:
|
|
592
|
+
|
|
593
|
+
```ruby
|
|
594
|
+
config.embedding_options = { host: 'http://localhost:11434' }
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
### Ollama `400 "the input length exceeds the context length"`
|
|
600
|
+
|
|
601
|
+
**Symptom:** `rake woods:embed` fails with `Ollama API error: 400 {"error":"the input length exceeds the context length"}`. Individual chunks may look smaller than the configured `num_ctx`.
|
|
602
|
+
|
|
603
|
+
**Cause:** Ollama's `/api/embed` endpoint enforces the model's **native** `context_length`, not the `options.num_ctx` override (see [ollama/ollama#14186](https://github.com/ollama/ollama/issues/14186)). For `nomic-embed-text` that's 2048 tokens, regardless of what `num_ctx` is set to. Separately, without the `tokenizers` gem, Woods estimates token counts from character length, which under-counts dense Ruby source, so chunks that look safe by char count still trip the 2048-token ceiling.
|
|
604
|
+
|
|
605
|
+
**Fix:** Use Woods 2.0 and install the `tokenizers` gem:
|
|
606
|
+
|
|
607
|
+
```ruby
|
|
608
|
+
# Gemfile
|
|
609
|
+
gem 'woods', '~> 2.0'
|
|
610
|
+
gem 'tokenizers', '~> 0.5' # exact BERT WordPiece token counting
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Woods now:
|
|
614
|
+
|
|
615
|
+
1. Advertises the native context ceiling per model (2048 for `nomic-embed-text`, 8192 for `bge-m3`/`snowflake-arctic-embed2`, etc.) so the chunker sizes inputs correctly.
|
|
616
|
+
2. Uses the real BERT tokenizer to verify every chunk, catching the 10–20% gap between char-based estimates and Ollama's internal count.
|
|
617
|
+
|
|
618
|
+
If you want fewer chunks per unit and have the disk space, switch to a larger-context model:
|
|
619
|
+
|
|
620
|
+
```ruby
|
|
621
|
+
config.embedding_options = {
|
|
622
|
+
model: 'bge-m3', # 8192 native context, 1024 dims
|
|
623
|
+
host: 'http://localhost:11434'
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Pull the model first (`ollama pull bge-m3`) and **drop the vector index before re-embedding**: the dimension change (768 → 1024) is incompatible with existing vectors. See [EMBEDDING_MODELS.md](EMBEDDING_MODELS.md) for the full tradeoff matrix.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Storage Problems
|
|
632
|
+
|
|
633
|
+
### "pgvector extension not found" in PostgreSQL
|
|
634
|
+
|
|
635
|
+
**Symptom:** Running migrations or extraction fails with `PG::UndefinedObject: ERROR: type "vector" does not exist`.
|
|
636
|
+
|
|
637
|
+
**Cause:** The pgvector PostgreSQL extension is not installed in the database.
|
|
638
|
+
|
|
639
|
+
**Fix:**
|
|
640
|
+
|
|
641
|
+
```sql
|
|
642
|
+
CREATE EXTENSION vector;
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Then run the Woods pgvector generator if you haven't already:
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
bundle exec rails generate woods:pgvector
|
|
649
|
+
bundle exec rails db:migrate
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
### Qdrant connection refused
|
|
655
|
+
|
|
656
|
+
**Symptom:** Embedding or retrieval fails with a connection error to port 6333.
|
|
657
|
+
|
|
658
|
+
**Cause:** The Qdrant server is not running.
|
|
659
|
+
|
|
660
|
+
**Fix:** Start Qdrant via Docker:
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
docker run -p 6333:6333 qdrant/qdrant
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Or update your `vector_store_options` to point at the correct host/port:
|
|
667
|
+
|
|
668
|
+
```ruby
|
|
669
|
+
config.vector_store_options = {
|
|
670
|
+
url: 'http://localhost:6333',
|
|
671
|
+
collection: 'woods',
|
|
672
|
+
allow_private_hosts: true
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
### SQLite locking errors under concurrent access
|
|
679
|
+
|
|
680
|
+
**Symptom:** Extraction or embedding fails with `SQLite3::BusyException: database is locked`.
|
|
681
|
+
|
|
682
|
+
**Cause:** SQLite does not support concurrent writers. If multiple extraction processes run simultaneously, they contend on the metadata store.
|
|
683
|
+
|
|
684
|
+
**Fix:** Use one embedding publisher at a time. A pgvector backend can accept
|
|
685
|
+
concurrent vector writes, but Woods' SQLite metadata/output artifact still
|
|
686
|
+
needs a coordinated publisher. Configure the hosted preset completely:
|
|
687
|
+
|
|
688
|
+
```ruby
|
|
689
|
+
Woods.configure_with_preset(:postgresql) do |config|
|
|
690
|
+
config.embedding_options = { api_key: ENV.fetch('OPENAI_API_KEY') }
|
|
691
|
+
config.vector_store_options = { connection: ActiveRecord::Base.connection }
|
|
692
|
+
end
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Docker Problems
|
|
698
|
+
|
|
699
|
+
### Extraction output not visible on the host
|
|
700
|
+
|
|
701
|
+
**Symptom:** `ls tmp/woods/manifest.json` fails on the host after successful extraction in the container.
|
|
702
|
+
|
|
703
|
+
**Cause:** The extraction output directory (`tmp/woods/`) inside the container is not volume-mounted to the host.
|
|
704
|
+
|
|
705
|
+
**Fix:** Add a volume mount to your `docker-compose.yml`:
|
|
706
|
+
|
|
707
|
+
```yaml
|
|
708
|
+
services:
|
|
709
|
+
app:
|
|
710
|
+
volumes:
|
|
711
|
+
- .:/app # Full app mount, output lands at ./tmp/woods/
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
Then re-run extraction. Prefer `docker compose exec app bundle exec rake woods:validate` and `woods:stats`; these checks follow the active v2 generation. Host visibility is only required for an optional host-side Index Server.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
### Console server exits immediately with "broken pipe"
|
|
719
|
+
|
|
720
|
+
**Symptom:** The MCP client reports a broken pipe or immediate disconnection when using Docker.
|
|
721
|
+
|
|
722
|
+
**Cause:** Plain `docker exec` lacks `-i`, or Docker Compose allocated its default pseudo-TTY. Either breaks stdio MCP communication.
|
|
723
|
+
|
|
724
|
+
**Fix:** Use `-T` with Compose (`stdin` remains attached), or `-i` with plain `docker exec`:
|
|
725
|
+
|
|
726
|
+
```json
|
|
727
|
+
{
|
|
728
|
+
"mcpServers": {
|
|
729
|
+
"codebase-console": {
|
|
730
|
+
"command": "docker",
|
|
731
|
+
"args": ["compose", "exec", "-T", "app",
|
|
732
|
+
"bundle", "exec", "rake", "woods:console"],
|
|
733
|
+
"cwd": "/absolute/host/path/to/app"
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
### "No such container" error
|
|
742
|
+
|
|
743
|
+
**Symptom:** `docker exec` fails with `Error response from daemon: No such container: my_app_web_1`.
|
|
744
|
+
|
|
745
|
+
**Cause:** The container name in your `.mcp.json` or `console.yml` doesn't match the actual running container name. Docker Compose generates names following the pattern `<project>-<service>-<index>`.
|
|
746
|
+
|
|
747
|
+
**Fix:** Find the exact name:
|
|
748
|
+
|
|
749
|
+
```bash
|
|
750
|
+
docker ps --format '{{.Names}}'
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
Update the container name in your configuration to match exactly.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### Path confusion: Index Server uses container path
|
|
758
|
+
|
|
759
|
+
**Symptom:** Index Server starts but fails to load units, or `woods-mcp-start` reports a missing manifest.
|
|
760
|
+
|
|
761
|
+
**Cause:** The `.mcp.json` is pointing at the container-internal path (e.g., `/app/tmp/woods`) instead of the host path.
|
|
762
|
+
|
|
763
|
+
**Fix:** Use the host path in `.mcp.json`. With a standard `.:/app` volume mount, the output is at `./tmp/woods` on the host:
|
|
764
|
+
|
|
765
|
+
```text
|
|
766
|
+
"args": ["./tmp/woods"] ✓ host path
|
|
767
|
+
"args": ["/app/tmp/woods"] ✗ container path. Index Server cannot read this
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## Notion Integration Problems
|
|
773
|
+
|
|
774
|
+
### 401 Unauthorized from Notion API
|
|
775
|
+
|
|
776
|
+
**Symptom:** `rake woods:notion_sync` fails with a 401 error.
|
|
777
|
+
|
|
778
|
+
**Cause:** The Notion API token is missing or invalid.
|
|
779
|
+
|
|
780
|
+
**Fix:** Set the token via environment variable (takes priority over config):
|
|
781
|
+
|
|
782
|
+
```bash
|
|
783
|
+
export NOTION_API_TOKEN=secret_...
|
|
784
|
+
bundle exec rake woods:notion_sync
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Or configure it in your initializer:
|
|
788
|
+
|
|
789
|
+
```ruby
|
|
790
|
+
config.notion_api_token = ENV['NOTION_API_TOKEN']
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
### 404 Not Found from Notion API
|
|
796
|
+
|
|
797
|
+
**Symptom:** Notion sync fails with a 404 error.
|
|
798
|
+
|
|
799
|
+
**Cause:** The database ID in `notion_database_ids` doesn't match any database the integration has access to.
|
|
800
|
+
|
|
801
|
+
**Fix:**
|
|
802
|
+
|
|
803
|
+
1. Verify the database ID from the Notion URL (the 32-character hex string).
|
|
804
|
+
2. Confirm the integration has been added to the database in Notion's share settings.
|
|
805
|
+
|
|
806
|
+
```ruby
|
|
807
|
+
config.notion_database_ids = {
|
|
808
|
+
data_models: 'your-32-char-database-id',
|
|
809
|
+
columns: 'your-other-32-char-database-id'
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
### 400 Bad Request from Notion API
|
|
816
|
+
|
|
817
|
+
**Symptom:** Notion sync fails with a 400 error mentioning property names or types.
|
|
818
|
+
|
|
819
|
+
**Cause:** The Notion database schema doesn't match the expected property structure. This happens when the database was created manually with different property names or types.
|
|
820
|
+
|
|
821
|
+
**Fix:** Use the Woods-generated database template. Re-create the database or update its properties to match the expected schema. Check the error message for which property name caused the mismatch.
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
### Notion sync is slow but eventually succeeds
|
|
826
|
+
|
|
827
|
+
**Symptom:** Notion sync takes much longer than expected on large codebases.
|
|
828
|
+
|
|
829
|
+
**Cause:** The Notion API enforces a 3 requests/second rate limit. `RateLimiter` handles this automatically, but a codebase with hundreds of models will take proportionally longer.
|
|
830
|
+
|
|
831
|
+
**Behavior:** This is expected and handled automatically. No action needed, the sync will complete.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## Units with the same name but different types
|
|
836
|
+
|
|
837
|
+
After upgrading, run embedding again if semantic results omit a factory or database
|
|
838
|
+
view sharing the same name. Current writers distinguish typed storage identities;
|
|
839
|
+
public names remain unchanged. Snapshot migration 007 runs automatically and keeps
|
|
840
|
+
existing rows, but cannot recover variants lost by older writers. See the
|
|
841
|
+
[upgrade guide](UPGRADING_TO_2.md) for storage, flow rebuild, and rollback details.
|