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,279 @@
|
|
|
1
|
+
# Unblocked Integration
|
|
2
|
+
|
|
3
|
+
Woods can sync extraction data to [Unblocked](https://getunblocked.com) via its
|
|
4
|
+
Documents API. This gives Unblocked's automated code review and Q&A tools
|
|
5
|
+
structural codebase context, associations, blast radius, entry points, side
|
|
6
|
+
effects, alongside the institutional context (PRs, Slack, tickets) it already
|
|
7
|
+
provides.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
1. **Extract** codebase data with `woods:extract` (the usual pipeline)
|
|
12
|
+
2. **Sync** to Unblocked with `woods:unblocked_sync` (or the alias `woods:relay`)
|
|
13
|
+
3. Documents appear in your Unblocked collection within ~1 minute
|
|
14
|
+
4. Unblocked's code review agent and Q&A tools reference the structural context
|
|
15
|
+
|
|
16
|
+
Documents are **upserted by URI**: running sync again updates existing documents
|
|
17
|
+
without creating duplicates. URIs point to your GitHub repository for working
|
|
18
|
+
citation links in Unblocked answers.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
### 1. Create an Unblocked Collection
|
|
23
|
+
|
|
24
|
+
Collections for custom sources are created **via the API** (the web app does
|
|
25
|
+
not currently offer a creation path for them):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
curl -X POST https://getunblocked.com/api/v1/collections \
|
|
29
|
+
-H "Authorization: Bearer $UNBLOCKED_API_TOKEN" \
|
|
30
|
+
-H "Content-Type: application/json" \
|
|
31
|
+
-d '{"name": "Codebase Architecture", "description": "Structural metadata from Woods extraction, models, controllers, services, dependencies, and blast radius analysis.", "iconUrl": "https://raw.githubusercontent.com/lost-in-the/woods/main/assets/woods-mark-black.svg"}'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **Known API quirk:** the live API rejects collection creation with a bare
|
|
35
|
+
> `400 Bad Request` unless `iconUrl` is included, even though the API docs mark
|
|
36
|
+
> it optional. Always pass an `iconUrl`, the Woods mark above is a stable,
|
|
37
|
+
> repo-hosted square SVG you can use directly.
|
|
38
|
+
|
|
39
|
+
Creating the collection from Ruby is simpler, `Client#create_collection`
|
|
40
|
+
defaults `iconUrl` to the Woods mark (`Client::DEFAULT_ICON_URL`), so the quirk
|
|
41
|
+
can't bite:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'woods/unblocked/client'
|
|
45
|
+
|
|
46
|
+
client = Woods::Unblocked::Client.new(api_token: ENV['UNBLOCKED_API_TOKEN'])
|
|
47
|
+
collection = client.create_collection(
|
|
48
|
+
name: 'Codebase Architecture',
|
|
49
|
+
description: 'Structural metadata from Woods extraction, models, ' \
|
|
50
|
+
'controllers, services, dependencies, and blast radius analysis.'
|
|
51
|
+
)
|
|
52
|
+
collection['id'] # => use as UNBLOCKED_COLLECTION_ID
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pass `icon_url:` to use your own icon instead of the default.
|
|
56
|
+
|
|
57
|
+
### 2. Create an API Token
|
|
58
|
+
|
|
59
|
+
In the Unblocked web app: **Settings** → **API Tokens** → **Create Token**.
|
|
60
|
+
|
|
61
|
+
- **Personal Access Token**: 1,000 API calls/day, scoped to your account
|
|
62
|
+
- **Team Access Token**: access to all team documents (recommended for CI)
|
|
63
|
+
|
|
64
|
+
### 3. Configure Woods
|
|
65
|
+
|
|
66
|
+
Via environment variables (recommended for CI):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
export UNBLOCKED_API_TOKEN="ubk_..."
|
|
70
|
+
export UNBLOCKED_COLLECTION_ID="12345678-abcd-..."
|
|
71
|
+
export UNBLOCKED_REPO_URL="https://github.com/your-org/your-repo"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or in your initializer:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
Woods.configure do |config|
|
|
78
|
+
config.unblocked_api_token = ENV["UNBLOCKED_API_TOKEN"]
|
|
79
|
+
config.unblocked_collection_id = ENV["UNBLOCKED_COLLECTION_ID"]
|
|
80
|
+
config.unblocked_repo_url = "https://github.com/your-org/your-repo"
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4. Run the Sync
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bundle exec rake woods:extract # Extract codebase data
|
|
88
|
+
bundle exec rake woods:unblocked_sync # Sync to Unblocked
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or in Docker:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
docker compose exec app bin/rails woods:extract
|
|
95
|
+
docker compose exec app bin/rails woods:unblocked_sync
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## What Gets Synced
|
|
99
|
+
|
|
100
|
+
| Unit Type | Strategy | Typical Count |
|
|
101
|
+
|-----------|----------|---------------|
|
|
102
|
+
| Models | All | 100-300 |
|
|
103
|
+
| Controllers | All | 100-400 |
|
|
104
|
+
| Services | All | 20-50 |
|
|
105
|
+
| Jobs | All | 50-150 |
|
|
106
|
+
| GraphQL types/resolvers | All | 100-400 |
|
|
107
|
+
| Concerns | All | 20-50 |
|
|
108
|
+
| Mailers | All | 3-10 |
|
|
109
|
+
| Managers | All | 10-30 |
|
|
110
|
+
| Decorators | All | 10-30 |
|
|
111
|
+
| POROs | Top 100 by dependents | 100 max |
|
|
112
|
+
| Libs | Top 50 by dependents | 50 max |
|
|
113
|
+
|
|
114
|
+
Total: ~800-1200 documents depending on app size.
|
|
115
|
+
|
|
116
|
+
## Document Format
|
|
117
|
+
|
|
118
|
+
Each document is a condensed Markdown profile optimized for code review context.
|
|
119
|
+
|
|
120
|
+
**Models** (highest value) include:
|
|
121
|
+
- Association summary grouped by type (belongs_to/has_many/has_one)
|
|
122
|
+
- Dependent count by type with blast radius assessment
|
|
123
|
+
- Entry points (controllers, GraphQL resolvers, jobs)
|
|
124
|
+
- Schema highlights (enums, scopes, concerns, callbacks)
|
|
125
|
+
- Side effects (workers, mailers triggered)
|
|
126
|
+
|
|
127
|
+
**Controllers** include routes, inheritance chain, model dependencies, view templates.
|
|
128
|
+
|
|
129
|
+
**Services/Jobs/Other types** include dependencies, dependents summary, key
|
|
130
|
+
structural data.
|
|
131
|
+
|
|
132
|
+
## URI Scheme
|
|
133
|
+
|
|
134
|
+
Document URIs point to the source file on GitHub:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
https://github.com/your-org/your-repo/blob/main/app/models/order.rb
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This means Unblocked citations link directly to the relevant source code.
|
|
141
|
+
|
|
142
|
+
## Rate Limits
|
|
143
|
+
|
|
144
|
+
The Unblocked API allows 1,000 calls per day (resets at midnight PST). A typical
|
|
145
|
+
sync uses ~800-1200 calls for the initial build. If your app exceeds 1,000 units:
|
|
146
|
+
|
|
147
|
+
- The sync stops gracefully when the budget is exhausted
|
|
148
|
+
- Re-run the next day to continue where it left off (upsert is idempotent)
|
|
149
|
+
- Set `UNBLOCKED_DAILY_BUDGET` to adjust the limiter if your plan allows more
|
|
150
|
+
|
|
151
|
+
## CI Integration
|
|
152
|
+
|
|
153
|
+
Add a post-merge step. The sync is incremental (see below), so the one thing CI
|
|
154
|
+
must do beyond running the task is **persist the sync manifest between runs**, otherwise every deploy starts from scratch and re-pushes everything.
|
|
155
|
+
|
|
156
|
+
### GitHub Actions
|
|
157
|
+
|
|
158
|
+
`actions/cache` entries are **immutable**: an exact-key hit skips the post-job
|
|
159
|
+
save, which would freeze the manifest at its first-run state forever. Use a
|
|
160
|
+
unique key plus `restore-keys` so every run saves a fresh manifest and the next
|
|
161
|
+
run restores the most recent one:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
env:
|
|
165
|
+
UNBLOCKED_COLLECTION_ID: "..."
|
|
166
|
+
|
|
167
|
+
steps:
|
|
168
|
+
- uses: actions/cache@v4
|
|
169
|
+
with:
|
|
170
|
+
path: tmp/woods/unblocked_sync_manifest.json
|
|
171
|
+
key: unblocked-sync-${{ env.UNBLOCKED_COLLECTION_ID }}-${{ github.run_id }}
|
|
172
|
+
restore-keys: |
|
|
173
|
+
unblocked-sync-${{ env.UNBLOCKED_COLLECTION_ID }}-
|
|
174
|
+
- run: bin/rails woods:extract
|
|
175
|
+
- run: bin/rails woods:unblocked_sync
|
|
176
|
+
env:
|
|
177
|
+
UNBLOCKED_API_TOKEN: "ubk_..."
|
|
178
|
+
UNBLOCKED_REPO_URL: "https://github.com/your-org/your-repo"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Buildkite
|
|
182
|
+
|
|
183
|
+
Buildkite has no native cache primitive. Note that plain
|
|
184
|
+
`buildkite-agent artifact download` is **scoped to the current build**: it
|
|
185
|
+
cannot restore a previous build's manifest on its own. Use the S3-backed
|
|
186
|
+
[cache plugin](https://github.com/buildkite-plugins/cache-buildkite-plugin)
|
|
187
|
+
(simplest), or resolve the last successful build's ID via the REST API and pass
|
|
188
|
+
`--build`:
|
|
189
|
+
|
|
190
|
+
```yaml
|
|
191
|
+
- label: ":trees: Woods → Unblocked"
|
|
192
|
+
plugins:
|
|
193
|
+
- cache#v1.7.0:
|
|
194
|
+
manifest: Gemfile.lock # any stable file; key below does the work
|
|
195
|
+
path: tmp/woods/unblocked_sync_manifest.json
|
|
196
|
+
restore: pipeline
|
|
197
|
+
save: pipeline
|
|
198
|
+
command:
|
|
199
|
+
- bin/rails woods:extract
|
|
200
|
+
- bin/rails woods:unblocked_sync
|
|
201
|
+
branches: main
|
|
202
|
+
env:
|
|
203
|
+
UNBLOCKED_API_TOKEN: "ubk_..."
|
|
204
|
+
UNBLOCKED_COLLECTION_ID: "..."
|
|
205
|
+
UNBLOCKED_REPO_URL: "https://github.com/your-org/your-repo"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If you prefer the artifact API, the restore step must target a previous build
|
|
209
|
+
explicitly, e.g.
|
|
210
|
+
`buildkite-agent artifact download --build "$(previous_passed_build_id)" "tmp/woods/unblocked_sync_manifest.json" .`
|
|
211
|
+
where `previous_passed_build_id` queries the REST API for the last passed build
|
|
212
|
+
on the pipeline. Declaring `artifact_paths: tmp/woods/**/*` still uploads the
|
|
213
|
+
fresh manifest for free at the end of each run.
|
|
214
|
+
|
|
215
|
+
> **Concurrency:** two syncs racing (e.g. per-deploy CI on quick successive
|
|
216
|
+
> merges) can interleave manifest writes and put/delete calls. Gating this is
|
|
217
|
+
> the host pipeline's responsibility, set a Buildkite `concurrency_group` (or
|
|
218
|
+
> the GitHub Actions `concurrency:` key) on the sync step.
|
|
219
|
+
|
|
220
|
+
## Incremental Updates
|
|
221
|
+
|
|
222
|
+
The sync is incremental. A **sync manifest**
|
|
223
|
+
(`<output_dir>/unblocked_sync_manifest.json`) records the content hash and remote
|
|
224
|
+
document id of everything last pushed. On each run the exporter:
|
|
225
|
+
|
|
226
|
+
- **skips** documents whose built body hash is unchanged,
|
|
227
|
+
- **pushes** only new or changed documents,
|
|
228
|
+
- **deletes** documents whose source unit has disappeared.
|
|
229
|
+
|
|
230
|
+
Documents are upserted by URI, so the sync is always safe to re-run. If the
|
|
231
|
+
manifest is missing (first run, or a CI cache miss) the exporter reconciles
|
|
232
|
+
document ids from the remote collection and falls back to a full re-push,
|
|
233
|
+
rebuilding the manifest, correct, just more API calls than one run. In steady
|
|
234
|
+
state an unchanged codebase costs ~0 calls.
|
|
235
|
+
|
|
236
|
+
Pair with `woods:incremental` to re-extract only changed files; the sync then
|
|
237
|
+
pushes only the documents whose content actually changed.
|
|
238
|
+
|
|
239
|
+
### Escape hatches
|
|
240
|
+
|
|
241
|
+
- `UNBLOCKED_FORCE_FULL_SYNC=1`: re-push every document, ignoring the unchanged
|
|
242
|
+
check (still uses the manifest for deletes). Use after a `DocumentBuilder`
|
|
243
|
+
format change, which alters every body.
|
|
244
|
+
- `UNBLOCKED_FORCE_PURGE=1`: bypass the mass-deletion guard. The guard refuses
|
|
245
|
+
to delete more than 30% of a manifest tracking ≥10 documents in one run, which
|
|
246
|
+
protects against running the sync against a **partial index** (e.g.
|
|
247
|
+
`woods:incremental` output in a fresh directory), where the current unit set
|
|
248
|
+
is a small subset and an unguarded purge would wipe the collection.
|
|
249
|
+
|
|
250
|
+
The guard also fires on *intentional* large removals: dropping a unit type
|
|
251
|
+
from the sync set, changing `unblocked_repo_url` (every URI changes), or a
|
|
252
|
+
big codebase deletion can all legitimately exceed 30%. The refusal warning
|
|
253
|
+
names the counts, if the deletions are expected, re-run once with
|
|
254
|
+
`UNBLOCKED_FORCE_PURGE=1`.
|
|
255
|
+
|
|
256
|
+
## Troubleshooting
|
|
257
|
+
|
|
258
|
+
**"call budget exhausted for this run"**: The per-run cap
|
|
259
|
+
(`UNBLOCKED_DAILY_BUDGET`, default 1000) stopped the sync. This is a guard rail
|
|
260
|
+
against one run spending the whole allowance; it is **not** a reading of what
|
|
261
|
+
your token has left. The real 1,000 call/day limit is enforced server-side and
|
|
262
|
+
resets at midnight PST, so a fresh run starts the local counter at zero
|
|
263
|
+
regardless of what earlier runs (or other machines sharing the token) consumed.
|
|
264
|
+
Raise the cap if a legitimate cold sync needs more, or use a Team Access Token
|
|
265
|
+
if higher server-side limits are available.
|
|
266
|
+
|
|
267
|
+
**"Unblocked API error 401"**: Check your API token. Personal tokens are scoped
|
|
268
|
+
to your account; Team tokens access all team data.
|
|
269
|
+
|
|
270
|
+
**"collection_id is required"**: Set `UNBLOCKED_COLLECTION_ID` env var or
|
|
271
|
+
configure `config.unblocked_collection_id`.
|
|
272
|
+
|
|
273
|
+
**Documents not appearing in answers**: Documents take ~1 minute to become
|
|
274
|
+
available. Also verify the collection is enabled in your Unblocked data source
|
|
275
|
+
settings.
|
|
276
|
+
|
|
277
|
+
## Retries and Duplicates
|
|
278
|
+
|
|
279
|
+
A 429 is always retried, for any request, the server rejected it before doing any work. A 503 is different: an intermediary in front of Unblocked's API can synthesize a 503 for a request the origin already committed, so a blind retry risks creating a duplicate. The client only retries a 503 for **idempotent** requests (reads, and document upserts keyed by URI). Collection *creation* has no idempotency key, so a 503 on `create_collection` is raised immediately instead of retried. Document upserts and syncs are unaffected, they're idempotent by URI, so a 503 there retries normally.
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Upgrade from Woods 1.x to 2.0
|
|
2
|
+
|
|
3
|
+
Woods 2.0 changes observable index identifiers, publication layout, vector-store reconciliation, and the supported MCP surface. Plan a clean re-index. Do not upgrade a shared or durable index in place without a backup and a rollback window.
|
|
4
|
+
|
|
5
|
+
This guide assumes the last v1 release, 1.6.1, and targets 2.0.0.
|
|
6
|
+
|
|
7
|
+
<!-- release-state:upgrade-availability -->
|
|
8
|
+
> RubyGems lists 2.0.0.beta2 as a prerelease. Pin it explicitly with
|
|
9
|
+
> `gem "woods", "2.0.0.beta2"`; `~> 2.0` resolves only once
|
|
10
|
+
> 2.0.0 is published.
|
|
11
|
+
<!-- release-state:end -->
|
|
12
|
+
|
|
13
|
+
## Upgrade outcome
|
|
14
|
+
|
|
15
|
+
After this runbook you will have:
|
|
16
|
+
|
|
17
|
+
- Woods 2.0 selected in the application bundle;
|
|
18
|
+
- reviewed v2 configuration and migration state;
|
|
19
|
+
- a clean v2 extraction with corrected identifiers;
|
|
20
|
+
- rebuilt embeddings and exports if you use them;
|
|
21
|
+
- an MCP client connected to the v2 packaged tool surface;
|
|
22
|
+
- a documented way back to v1 if verification fails.
|
|
23
|
+
|
|
24
|
+
## What changes
|
|
25
|
+
|
|
26
|
+
| v2 change | What can break | Required response |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Correct namespaced and constrained-route identifiers | Saved identifiers, external links, retrieval vectors, and exports can miss renamed units | Clean extract; rebuild embeddings and exports |
|
|
29
|
+
| Wrapper-nested class identifiers resolved to the file's own constant | Files nested in class namespaces (e.g. `app/services/domain/container/parser.rb` defining `module Domain; class Container; class Parser`) gain identifiers like `Domain::Container::Parser` instead of sharing the wrapper's; layouts that still derive one type+identifier from two different files abort extraction naming both files | Clean extract; rebuild embeddings and exports |
|
|
30
|
+
| Typed graph identity variants | Custom graph consumers may assume one node per identifier | Re-index; update custom consumers to handle type variants |
|
|
31
|
+
| Atomic generations via `generation.json` | Custom scripts that read root `manifest.json` may fail | Follow the payload pointer or use Woods readers/tasks |
|
|
32
|
+
| `mcp >= 1.2, < 2.0` and protocol negotiation | Old lockfiles or manually pinned protocol versions can fail | Bundle update Woods/MCP; normally leave protocol version unset |
|
|
33
|
+
| Index MCP surface aligned to executable wiring | Agents may ask for tools that only exist as conditional schemas | Update agent instructions to the 14-tool default |
|
|
34
|
+
| Console surface tightened to 9 or 11 tools | Agents may ask for Tier 2/3 or eval schemas that do not execute | Use registered default/read tools only |
|
|
35
|
+
| Missing-token behavior changed outside production | An enabled Console HTTP endpoint now stays mounted but returns 401 without a valid token; production still refuses to boot without one | Preserve or configure a secret token of at least 32 characters; send it only to the HTTP transport |
|
|
36
|
+
| Durable-store reconciliation and a 30% purge guard | The first v2 embed may refuse a legitimate rename-heavy deletion | Back up, inspect the deletion, then use the one-run override only if correct |
|
|
37
|
+
| Embedding dimension preflight | A previously tolerated model/store mismatch now fails before writing | Rebuild into a store with the configured dimension |
|
|
38
|
+
| Export reconciliation guards | Obsidian or Unblocked can refuse a rename-heavy stale-document sweep | Back up and use exporter-specific override only after review |
|
|
39
|
+
| Notion column pages are grouped by physical table | Models sharing a table (STI, a shared `self.table_name`) previously rewrote each other's column pages on every run | Re-sync once after re-extraction; the shared pages settle and the churn stops |
|
|
40
|
+
| One-shot extraction tasks fail when the generation marker cannot be published | A run that wrote a payload readers cannot reach used to print success and exit 0 | Fix the write failure and re-run; the previous generation stays active meanwhile |
|
|
41
|
+
| `woods:incremental` and `woods:refresh` refuse an output directory with no baseline index | A restored-cache miss, a typo'd `WOODS_OUTPUT`, or a fresh runner now fails instead of publishing a near-empty index | Run a full `woods:extract` first, or point `WOODS_OUTPUT` at the directory holding the existing index |
|
|
42
|
+
| `woods:incremental` exits 1 over a git range it cannot resolve | An unresolvable base ref used to read as "nothing changed" and exit 0 | Fetch the base ref, set `CHANGED_FILES`, or accept the stand-down a live watch daemon provides |
|
|
43
|
+
| `woods:embed`, `woods:embed_incremental`, and `woods:notion_sync` exit 1 on reported errors | CI jobs that were green while every unit or page failed now fail | Read the printed errors, fix the cause, re-run; completed work is durable |
|
|
44
|
+
| The Index MCP `reload` tool needs write access to the index directory | A read-only index mount can serve structural reads but cannot reload in place | Grant write access, or restart the MCP process after publishing |
|
|
45
|
+
| `config.extractors` and `config.add_gem` warn as unimplemented | Old config may imply filtering that never occurred | Remove or comment the settings; do not rely on them |
|
|
46
|
+
| New watch, refresh, and evaluation tasks | New operational options become available | Optional; no migration action |
|
|
47
|
+
|
|
48
|
+
## Before changing the bundle
|
|
49
|
+
|
|
50
|
+
### 1. Record the current installation
|
|
51
|
+
|
|
52
|
+
Run in the same environment that boots Rails:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git status --short --branch
|
|
56
|
+
bundle exec ruby -rwoods/version -e 'puts Woods::VERSION'
|
|
57
|
+
bundle exec rails woods:stats
|
|
58
|
+
bundle exec rails woods:validate
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Record the current Woods version, output directory, storage preset/providers, embedding model and dimension, Console configuration, and enabled exports. For Console, record the transport, token source or presence without recording its value, allowed origins, path, and embedded read-tool setting. Save current MCP client configuration and any custom scripts that read `tmp/woods/` directly.
|
|
62
|
+
|
|
63
|
+
### 2. Back up durable data
|
|
64
|
+
|
|
65
|
+
The generated structural index can be recreated, but its location may also hold local vector dumps and exporter manifests. Copy or snapshot the complete configured output directory before cleaning it.
|
|
66
|
+
|
|
67
|
+
Back up external vector stores separately:
|
|
68
|
+
|
|
69
|
+
| Store | Backup |
|
|
70
|
+
|---|---|
|
|
71
|
+
| pgvector | Database/schema snapshot or `pg_dump` of the configured vector table |
|
|
72
|
+
| Qdrant | Collection snapshot through Qdrant's snapshot API |
|
|
73
|
+
| `:local` or `:shared_filesystem` | Copy the configured output directory, including `dumps/` |
|
|
74
|
+
|
|
75
|
+
Also back up managed Obsidian/Unblocked destinations before allowing a mass stale-document cleanup. Notion does not delete old pages during reconciliation, but save its sync manifest with the output directory.
|
|
76
|
+
|
|
77
|
+
### 3. Choose a rollback point
|
|
78
|
+
|
|
79
|
+
Keep the v1 Gemfile/lockfile commit and all durable-store backups until v2 extraction, MCP calls, retrieval, and exports are verified. Downgrading the gem does not translate v2 identifiers back to v1.
|
|
80
|
+
|
|
81
|
+
## Upgrade the application
|
|
82
|
+
|
|
83
|
+
### 1. Update Woods without broad dependency churn
|
|
84
|
+
|
|
85
|
+
Change the development dependency:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
gem "woods", "~> 2.0"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then update only Woods and the dependencies Bundler requires:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bundle update woods
|
|
95
|
+
bundle exec ruby -rwoods/version -e 'puts Woods::VERSION'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Confirm the result is 2.0.0 and the lockfile resolves `mcp` at `>= 1.2, < 2.0`.
|
|
99
|
+
|
|
100
|
+
### 2. Review configuration
|
|
101
|
+
|
|
102
|
+
Generate the v2 template only when you can inspect conflicts safely. Do not overwrite an existing initializer blindly. Compare your initializer with the v2 [Configuration reference](CONFIGURATION_REFERENCE.md).
|
|
103
|
+
|
|
104
|
+
Pay particular attention to:
|
|
105
|
+
|
|
106
|
+
- `output_dir` and environment overrides;
|
|
107
|
+
- storage and embedding provider settings;
|
|
108
|
+
- the configured embedding model/dimension;
|
|
109
|
+
- `console_mcp_enabled`, the `console_mcp_token` secret source, allowed origins, path, and embedded read-tool flags;
|
|
110
|
+
- snapshot, session, Notion, Obsidian, and Unblocked settings;
|
|
111
|
+
- old `config.extractors` or `config.add_gem` calls, which are not implemented selectors.
|
|
112
|
+
|
|
113
|
+
Review existing Woods migrations and tables before accepting any newly generated migration. Do not create duplicate `woods_units`, `woods_edges`, or `woods_embeddings` tables.
|
|
114
|
+
|
|
115
|
+
### 3. Clean and re-extract
|
|
116
|
+
|
|
117
|
+
After the backup is verified:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bin/rails woods:clean
|
|
121
|
+
bin/rails woods:extract
|
|
122
|
+
bin/rails woods:validate
|
|
123
|
+
bin/rails woods:stats
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The clean extract is required for corrected identifier shapes. Do not use an incremental run as the first v2 extraction: after `woods:clean` there is no baseline, and v2 `woods:incremental` refuses that state rather than publishing a near-empty index as the application's complete truth.
|
|
127
|
+
|
|
128
|
+
An interrupted extraction leaves readers on the last complete generation because Woods publishes `generation.json` only after the payload is complete. Re-run the task; do not delete a partial directory speculatively. A run that completes its payload but cannot publish the marker now fails loudly instead of reporting success, so treat a non-zero exit as work to redo rather than as a partial success.
|
|
129
|
+
|
|
130
|
+
## Rebuild optional systems
|
|
131
|
+
|
|
132
|
+
### Embeddings
|
|
133
|
+
|
|
134
|
+
If semantic retrieval is enabled:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
bin/rails woods:embed
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The first v2 run reconciles durable vectors with the new identifiers. A purge guard refuses deletion of more than 30% of the store or a purge into an empty extraction.
|
|
141
|
+
|
|
142
|
+
If it refuses:
|
|
143
|
+
|
|
144
|
+
1. Confirm extraction validation passed and unit counts are plausible.
|
|
145
|
+
2. Compare the proposed deletion with the expected identifier rename.
|
|
146
|
+
3. Confirm the vector-store backup is restorable.
|
|
147
|
+
4. Only then authorize one run:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
WOODS_ALLOW_PURGE=1 bin/rails woods:embed
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The override permits deletion; it is not a repair command. Do not set it permanently.
|
|
154
|
+
|
|
155
|
+
If Woods reports a dimension mismatch, verify the configured embedding model. Rebuild into a store created for the new dimension. Vectors cannot be converted in place.
|
|
156
|
+
|
|
157
|
+
An interrupted embed is safe to re-run; durable checkpoints resume or repair the missing unit.
|
|
158
|
+
|
|
159
|
+
### Exports
|
|
160
|
+
|
|
161
|
+
Re-run every export after extraction and embeddings are verified. Renamed identifiers appear as removal of the old document plus addition of the new one.
|
|
162
|
+
|
|
163
|
+
| Export | v2 behavior |
|
|
164
|
+
|---|---|
|
|
165
|
+
| Notion | Adds/updates current units and prunes manifest entries; it does not delete old Notion pages |
|
|
166
|
+
| Obsidian | Sweeps stale Woods-managed notes; refuses deletion beyond 30% unless `WOODS_OBSIDIAN_FORCE_PURGE` is explicitly set |
|
|
167
|
+
| Unblocked | Reconciles its sync manifest; for manifests with 10+ documents, refuses deletion beyond 30% unless `UNBLOCKED_FORCE_PURGE` is explicitly set |
|
|
168
|
+
|
|
169
|
+
Review the target and backup before any force-purge override. Use `WOODS_NOTION_FORCE=1` only when you intentionally want Notion to re-check unchanged content hashes.
|
|
170
|
+
|
|
171
|
+
**Notion needs one settling re-sync.** v2 groups column pages by physical table instead of by model, so models that share a table write one page per physical column, with the `Table` relation listing every owning model and their validations unioned. The page titles, and therefore the manifest keys, are unchanged, but the content hash of every shared-table column changes once. Expect the first post-upgrade `woods:notion_sync` to update those pages; subsequent runs skip them. This also ends the v1 behavior where two models sharing a table rewrote the same column page back and forth on every run. See [Notion integration](NOTION_INTEGRATION.md).
|
|
172
|
+
|
|
173
|
+
## Update CI and scheduled automation
|
|
174
|
+
|
|
175
|
+
v2 tasks fail instead of printing an error and exiting 0. Review any pipeline that runs Woods unattended before the first v2 run:
|
|
176
|
+
|
|
177
|
+
| Task | v2 exit behavior |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `woods:extract`, `woods:incremental`, `woods:refresh` | Raise when the payload cannot be published as a generation; the previous generation stays active |
|
|
180
|
+
| `woods:incremental` | Refuses an output directory with no baseline index, and exits 1 over a git range it cannot resolve unless a live watch daemon maintains the tree. See [Incremental extraction](INCREMENTAL_EXTRACTION.md) |
|
|
181
|
+
| `woods:embed`, `woods:embed_incremental`, `woods:notion_sync` | Exit 1 when the run reports errors, matching `woods:unblocked_sync` and `woods:obsidian` |
|
|
182
|
+
|
|
183
|
+
A job that restores the index directory from a cache must restore the whole thing. A missing or empty restore is exactly the state the baseline guard refuses, and the fix is a full `woods:extract` on that runner, not an override.
|
|
184
|
+
|
|
185
|
+
## Update direct index consumers
|
|
186
|
+
|
|
187
|
+
Woods 2.0 publishes immutable payload directories and atomically points to the active one:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
tmp/woods/
|
|
191
|
+
├── generation.json
|
|
192
|
+
└── payloads/
|
|
193
|
+
└── gen-42/
|
|
194
|
+
├── manifest.json
|
|
195
|
+
├── dependency_graph.json
|
|
196
|
+
└── <type>/*.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Woods tasks, readers, exporters, and MCP servers resolve this automatically. Custom tooling must read `generation.json`, resolve its `payload` relative to the index root, reject paths that escape that root, and then read the payload files. A missing payload key represents the legacy flat layout.
|
|
200
|
+
|
|
201
|
+
Graph consumers must also tolerate multiple typed variants for the same textual identifier. Do not collapse nodes by identifier alone when type is part of identity.
|
|
202
|
+
|
|
203
|
+
The bundle requires patched MessagePack >=1.8.2 and JSON >=2.19.9, <3.
|
|
204
|
+
JSON 3 removes an encoder option used by older supported Rails versions; keep the
|
|
205
|
+
compatible JSON 2.x dependency when resolving the v2 bundle.
|
|
206
|
+
|
|
207
|
+
Embedding preserves coexisting types with internal `@woods-unit:` storage keys
|
|
208
|
+
(Base64-encoded JSON `[identifier, type]`, with the existing chunk suffix appended
|
|
209
|
+
when needed). Public identifiers and source attribution stay unchanged. Unique
|
|
210
|
+
ordinary names retain their previous keys; names beginning with this reserved
|
|
211
|
+
prefix are escaped too. An incremental embed migrates an ambiguous legacy key only
|
|
212
|
+
after storing its replacement vectors. Existing typed keys stay stable when one
|
|
213
|
+
variant disappears. Normal mass-deletion guards still apply to vanished units.
|
|
214
|
+
Custom vector consumers must treat storage IDs as opaque and use metadata for
|
|
215
|
+
public identifiers. Evaluation baselines normalize storage keys back to public
|
|
216
|
+
names and count same-named typed variants once, matching name-based ground truth.
|
|
217
|
+
Older dumps remain readable; re-embed to recover variants
|
|
218
|
+
that an older writer had already overwritten.
|
|
219
|
+
|
|
220
|
+
SQLite migration 007 preserves snapshot rows and permits one row per
|
|
221
|
+
`(snapshot_id, identifier, unit_type)`. JSON snapshot readers accept older untyped
|
|
222
|
+
records, while new records preserve both names and types. Lost historical variants
|
|
223
|
+
cannot be reconstructed from old snapshots. Back up `woods.sqlite3` and the whole
|
|
224
|
+
index before upgrading: reverting code alone does not reverse this migration.
|
|
225
|
+
Restore the matching backup or rebuild in a separate store when rolling back.
|
|
226
|
+
|
|
227
|
+
Flow documents for identifiers containing literal underscores now use a digest to
|
|
228
|
+
avoid collisions with namespaced controllers and combined action names. Read the
|
|
229
|
+
published flow index instead of constructing filenames. A full extraction rebuilds
|
|
230
|
+
precomputed flows consistently; use it when upgrading an index with existing flow
|
|
231
|
+
artifacts.
|
|
232
|
+
|
|
233
|
+
## Reconnect MCP clients
|
|
234
|
+
|
|
235
|
+
Use the project bundle so the server and application resolve the same Woods version:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"mcpServers": {
|
|
240
|
+
"woods": {
|
|
241
|
+
"command": "bundle",
|
|
242
|
+
"args": ["exec", "woods-mcp-start", "./tmp/woods"],
|
|
243
|
+
"cwd": "/absolute/path/to/the-rails-app"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Normally leave `MCP_PROTOCOL_VERSION` unset. The SDK negotiates with legacy clients through `initialize` and supports newer discovery in the same process. Pin only as a temporary workaround for a client known to require one revision; a pin reduces compatibility and is announced on stderr.
|
|
250
|
+
|
|
251
|
+
Update agent prompts that refer to the old inventory. Standard Index launch provides 14 tools. Standard Console launch provides nine, or eleven with explicitly enabled embedded read tools. See [MCP servers](MCP_SERVERS.md).
|
|
252
|
+
|
|
253
|
+
Structural reads still work from a read-only index mount, but the `reload` tool does not: its transactional refresh takes the same on-disk writer lock as extraction and embedding, so the MCP process needs write access to the index directory. Without it, `reload` returns a typed degraded error and keeps serving the previous aligned generation rather than swapping in a partial one. Grant write access, or restart the MCP process after publishing. [MCP servers](MCP_SERVERS.md) owns the detail.
|
|
254
|
+
|
|
255
|
+
### Console users: preserve or configure the HTTP token
|
|
256
|
+
|
|
257
|
+
If Console MCP is disabled, no Console token action is required. If it is enabled, configure a token of at least 32 characters through a secret manager or environment variable rather than committing it to the initializer:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
WOODS_CONSOLE_MCP_TOKEN="$(openssl rand -hex 32)"
|
|
261
|
+
export WOODS_CONSOLE_MCP_TOKEN
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
config.console_mcp_token = ENV.fetch("WOODS_CONSOLE_MCP_TOKEN")
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Persist the generated value in the application's normal secret store before opening a new shell or deploying. Never print, log, or commit the token during an agent-operated upgrade.
|
|
269
|
+
|
|
270
|
+
Rails-mounted Console HTTP clients must send the token as `Authorization: Bearer <token>`. With Console enabled, a missing token has these outcomes:
|
|
271
|
+
|
|
272
|
+
- outside production, Rails warns and the Console HTTP endpoint returns 401;
|
|
273
|
+
- in production, Rails refuses to boot;
|
|
274
|
+
- in every environment, a configured token shorter than 32 characters raises a configuration error.
|
|
275
|
+
|
|
276
|
+
The stdio Console transport does not send or authenticate with the bearer token. Outside production it can run without one, although Rails still warns because enabling Console also activates the guarded Rack endpoint. In production, Rails boot validation still requires the token even when stdio is the intended transport. The safest default is to configure the token whenever Console is enabled, or leave Console disabled.
|
|
277
|
+
|
|
278
|
+
See [Console MCP setup](CONSOLE_MCP_SETUP.md) for client examples and [Configuration reference](CONFIGURATION_REFERENCE.md) for the complete security settings.
|
|
279
|
+
|
|
280
|
+
## Verify before rollout
|
|
281
|
+
|
|
282
|
+
Complete every applicable check:
|
|
283
|
+
|
|
284
|
+
- [ ] `bundle exec ruby -rwoods/version -e 'puts Woods::VERSION'` reports 2.0.0.
|
|
285
|
+
- [ ] Rails boots and eager-loads in the extraction environment.
|
|
286
|
+
- [ ] `woods:extract`, `woods:validate`, and `woods:stats` succeed.
|
|
287
|
+
- [ ] Expected namespaced and constrained identifiers appear.
|
|
288
|
+
- [ ] Custom index readers follow `generation.json`.
|
|
289
|
+
- [ ] `woods_status` reports the new generation.
|
|
290
|
+
- [ ] `search`, `lookup`, and `dependents` work with v2 identifiers.
|
|
291
|
+
- [ ] Semantic retrieval works after re-embedding, if enabled.
|
|
292
|
+
- [ ] Console exposes only the authorized 9/11 tools, if enabled.
|
|
293
|
+
- [ ] An enabled Console reads a token of at least 32 characters from a secret source; its value was not printed or committed.
|
|
294
|
+
- [ ] Console HTTP rejects a request without the bearer token with 401 and accepts the configured client, if HTTP is used.
|
|
295
|
+
- [ ] Console stdio starts through the application bundle, if stdio is used.
|
|
296
|
+
- [ ] Exports were reconciled and stale-document changes reviewed.
|
|
297
|
+
- [ ] A second `woods:notion_sync` reports the shared-table column pages as skipped, if Notion is enabled.
|
|
298
|
+
- [ ] Unattended pipelines were reviewed against the new exit behavior, and any job that restores the index directory restores a complete baseline.
|
|
299
|
+
- [ ] The MCP process has write access to the index directory, if agents call `reload`.
|
|
300
|
+
- [ ] MCP and automation prompts no longer name inventory-only tools.
|
|
301
|
+
- [ ] Backups remain available through the rollout window.
|
|
302
|
+
|
|
303
|
+
## Roll back
|
|
304
|
+
|
|
305
|
+
If verification fails:
|
|
306
|
+
|
|
307
|
+
1. stop v2 MCP, watcher, embedding, and exporter processes;
|
|
308
|
+
2. restore the v1 Gemfile and lockfile or deploy the recorded v1 commit;
|
|
309
|
+
3. run the v1 `woods:clean` before restoring anything under the configured output directory;
|
|
310
|
+
4. either restore the complete pre-upgrade v1 output-directory backup, or run a fresh v1 extraction and then restore its v1 `dumps/` and configuration artifacts;
|
|
311
|
+
5. restore external vector-store and managed export backups when v2 modified them;
|
|
312
|
+
6. restore v1 MCP configuration and reconnect clients;
|
|
313
|
+
7. verify v1 status and representative queries before reopening access.
|
|
314
|
+
|
|
315
|
+
A v1 gem cannot translate a v2 index or durable vector store back to v1 identifiers. Re-extraction and backup restoration are the rollback. Do not run `woods:clean` after restoring local or shared-filesystem dumps; v1 removes the entire output directory.
|
|
316
|
+
|
|
317
|
+
## Agent-operated upgrade prompt
|
|
318
|
+
|
|
319
|
+
> Upgrade this Rails application from Woods 1.x to 2.0 using `docs/UPGRADING_TO_2.md`. Start with read-only inventory and preserve unrelated changes. Before cleaning or reconciling anything, identify the output directory, providers, exports, direct index consumers, and restorable backups. Report only whether a Console token exists and where it comes from, never its value. Do not use purge overrides, enable Console/read tools, create or rotate tokens, change credentials, or modify shared infrastructure without asking me. Perform a clean v2 extraction, validate it, update MCP instructions to the packaged 14-tool Index and 9/11 Console surfaces, and return the completed verification checklist plus rollback location.
|
|
320
|
+
|
|
321
|
+
For failures, use [Troubleshooting](TROUBLESHOOTING.md). For current setup, use [Getting started](GETTING_STARTED.md).
|