woods 2.0.0.beta2 → 2.0.0.beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +339 -1
  3. data/CONTRIBUTING.md +188 -12
  4. data/README.md +93 -174
  5. data/SECURITY.md +9 -6
  6. data/docs/AGENT_GUIDE.md +109 -8
  7. data/docs/AGENT_SETUP.md +98 -7
  8. data/docs/BACKEND_MATRIX.md +25 -0
  9. data/docs/CLIENT_HOOKS.md +111 -0
  10. data/docs/CONFIGURATION_REFERENCE.md +267 -16
  11. data/docs/CONSOLE_MCP_SETUP.md +80 -7
  12. data/docs/DOCKER_SETUP.md +22 -3
  13. data/docs/EVALUATION.md +464 -1
  14. data/docs/EXTRACTOR_REFERENCE.md +45 -6
  15. data/docs/FAQ.md +11 -12
  16. data/docs/GETTING_STARTED.md +17 -5
  17. data/docs/INCREMENTAL_EXTRACTION.md +147 -7
  18. data/docs/INDEX_LAYOUT.md +382 -0
  19. data/docs/INTERNALS.md +7 -2
  20. data/docs/MCP_SERVERS.md +276 -5
  21. data/docs/MCP_TOOL_COOKBOOK.md +37 -22
  22. data/docs/MCP_WORKTREE_SETUP.md +43 -83
  23. data/docs/NOTION_INTEGRATION.md +13 -0
  24. data/docs/OBSIDIAN_INTEGRATION.md +57 -9
  25. data/docs/PUBLISHED_INDEX.md +72 -0
  26. data/docs/README.md +7 -0
  27. data/docs/RETRIEVAL_GUIDE.md +273 -12
  28. data/docs/RUNTIME_TRACING.md +71 -0
  29. data/docs/SOURCE_FRESHNESS.md +143 -0
  30. data/docs/TROUBLESHOOTING.md +129 -18
  31. data/docs/UNBLOCKED_INTEGRATION.md +25 -0
  32. data/docs/UPGRADING_TO_2.md +48 -22
  33. data/docs/WATCH_DAEMON.md +277 -67
  34. data/exe/woods-agent-config +6 -0
  35. data/exe/woods-extract +5 -0
  36. data/exe/woods-hook-context +6 -0
  37. data/exe/woods-mcp-start +14 -9
  38. data/lib/generators/woods/pgvector_generator.rb +8 -2
  39. data/lib/generators/woods/templates/woods.rb.tt +1 -3
  40. data/lib/tasks/woods.rake +47 -397
  41. data/lib/woods/agent_configuration/applier.rb +135 -0
  42. data/lib/woods/agent_configuration/cli.rb +101 -0
  43. data/lib/woods/agent_configuration/cli_options.rb +29 -0
  44. data/lib/woods/agent_configuration/document.rb +105 -0
  45. data/lib/woods/agent_configuration/error.rb +7 -0
  46. data/lib/woods/agent_configuration/launcher.rb +75 -0
  47. data/lib/woods/agent_configuration/layout.rb +72 -0
  48. data/lib/woods/agent_configuration/managed_section.rb +62 -0
  49. data/lib/woods/agent_configuration/plan.rb +98 -0
  50. data/lib/woods/agent_configuration/plan_diff.rb +38 -0
  51. data/lib/woods/agent_configuration/planned_files.rb +61 -0
  52. data/lib/woods/agent_configuration/planner.rb +63 -0
  53. data/lib/woods/agent_configuration/planner_validation.rb +77 -0
  54. data/lib/woods/agent_configuration/preflight.rb +100 -0
  55. data/lib/woods/agent_configuration/recovery.rb +49 -0
  56. data/lib/woods/ast/node.rb +2 -0
  57. data/lib/woods/ast/parser.rb +38 -5
  58. data/lib/woods/builder.rb +21 -5
  59. data/lib/woods/cache/cache_middleware.rb +28 -7
  60. data/lib/woods/cache/cache_store.rb +4 -5
  61. data/lib/woods/change_set.rb +5 -4
  62. data/lib/woods/console/credential_index.rb +20 -2
  63. data/lib/woods/console/credential_scanner.rb +18 -17
  64. data/lib/woods/console/credential_scanner_registry.rb +36 -0
  65. data/lib/woods/console/dispatch_pipeline.rb +7 -0
  66. data/lib/woods/console/embedded_executor.rb +32 -10
  67. data/lib/woods/console/encrypted_credential_snapshot.rb +16 -0
  68. data/lib/woods/console/rack_middleware.rb +22 -13
  69. data/lib/woods/console/server.rb +18 -16
  70. data/lib/woods/console/sql_noise_stripper.rb +9 -7
  71. data/lib/woods/console/sql_table_scanner.rb +47 -7
  72. data/lib/woods/console/sql_validator.rb +49 -9
  73. data/lib/woods/console/sqlite_read_guard.rb +46 -0
  74. data/lib/woods/coordination/pipeline_lock.rb +3 -2
  75. data/lib/woods/dependency_graph.rb +65 -13
  76. data/lib/woods/embedding/corpus.rb +94 -0
  77. data/lib/woods/embedding/indexer.rb +114 -60
  78. data/lib/woods/embedding/openai.rb +17 -6
  79. data/lib/woods/evaluation/ablation_executor.rb +6 -1
  80. data/lib/woods/evaluation/ablation_timed_executor.rb +22 -4
  81. data/lib/woods/export/typed_reader.rb +56 -0
  82. data/lib/woods/extractor.rb +277 -149
  83. data/lib/woods/extractors/action_cable_extractor.rb +3 -1
  84. data/lib/woods/extractors/behavioral_profile.rb +9 -7
  85. data/lib/woods/extractors/caching_extractor.rb +3 -1
  86. data/lib/woods/extractors/concern_extractor.rb +64 -6
  87. data/lib/woods/extractors/configuration_extractor.rb +7 -3
  88. data/lib/woods/extractors/controller_extractor.rb +13 -4
  89. data/lib/woods/extractors/database_view_extractor.rb +3 -1
  90. data/lib/woods/extractors/declared_parent.rb +55 -0
  91. data/lib/woods/extractors/decorator_extractor.rb +3 -1
  92. data/lib/woods/extractors/engine_extractor.rb +3 -1
  93. data/lib/woods/extractors/event_extractor.rb +4 -2
  94. data/lib/woods/extractors/factory_extractor.rb +3 -1
  95. data/lib/woods/extractors/graphql_extractor.rb +10 -13
  96. data/lib/woods/extractors/i18n_extractor.rb +3 -1
  97. data/lib/woods/extractors/job_extractor.rb +6 -19
  98. data/lib/woods/extractors/lib_extractor.rb +13 -9
  99. data/lib/woods/extractors/mailer_extractor.rb +26 -15
  100. data/lib/woods/extractors/manager_extractor.rb +3 -1
  101. data/lib/woods/extractors/method_parameters.rb +53 -0
  102. data/lib/woods/extractors/middleware_argument.rb +65 -0
  103. data/lib/woods/extractors/middleware_extractor.rb +9 -3
  104. data/lib/woods/extractors/migration_extractor.rb +3 -1
  105. data/lib/woods/extractors/model_extractor.rb +26 -34
  106. data/lib/woods/extractors/package_extractor.rb +24 -4
  107. data/lib/woods/extractors/phlex_extractor.rb +3 -1
  108. data/lib/woods/extractors/policy_extractor.rb +3 -1
  109. data/lib/woods/extractors/poro_extractor.rb +13 -9
  110. data/lib/woods/extractors/pundit_extractor.rb +3 -1
  111. data/lib/woods/extractors/rails_source_extractor.rb +4 -2
  112. data/lib/woods/extractors/rake_task_extractor.rb +4 -2
  113. data/lib/woods/extractors/route_extractor.rb +3 -1
  114. data/lib/woods/extractors/route_helper_resolver.rb +10 -33
  115. data/lib/woods/extractors/scheduled_job_extractor.rb +41 -15
  116. data/lib/woods/extractors/serializer_extractor.rb +4 -2
  117. data/lib/woods/extractors/service_extractor.rb +3 -1
  118. data/lib/woods/extractors/shared_dependency_scanner.rb +2 -2
  119. data/lib/woods/extractors/shared_utility_methods.rb +48 -19
  120. data/lib/woods/extractors/source_nesting.rb +1 -1
  121. data/lib/woods/extractors/state_machine_extractor.rb +3 -1
  122. data/lib/woods/extractors/test_mapping_extractor.rb +3 -1
  123. data/lib/woods/extractors/validator_extractor.rb +3 -1
  124. data/lib/woods/extractors/view_component_extractor.rb +3 -1
  125. data/lib/woods/extractors/view_template_extractor.rb +3 -1
  126. data/lib/woods/gem_mapper.rb +2 -0
  127. data/lib/woods/git_history.rb +116 -0
  128. data/lib/woods/graph_analyzer.rb +35 -6
  129. data/lib/woods/hooks/context_cli.rb +54 -0
  130. data/lib/woods/hooks/context_event.rb +88 -0
  131. data/lib/woods/hooks/context_hint.rb +73 -0
  132. data/lib/woods/hooks/context_impact.rb +77 -0
  133. data/lib/woods/hooks/context_output.rb +47 -0
  134. data/lib/woods/hooks/context_state.rb +102 -0
  135. data/lib/woods/hooks/refresh.rb +79 -0
  136. data/lib/woods/hooks/rule_projection.rb +78 -0
  137. data/lib/woods/input_rules.rb +19 -0
  138. data/lib/woods/mcp/bearer_auth.rb +22 -13
  139. data/lib/woods/mcp/bootstrapper.rb +79 -4
  140. data/lib/woods/mcp/config_resolver.rb +2 -1
  141. data/lib/woods/mcp/index_reader.rb +334 -162
  142. data/lib/woods/mcp/initialization_guidance.rb +27 -0
  143. data/lib/woods/mcp/origin_guard.rb +17 -9
  144. data/lib/woods/mcp/published_lexical_retriever.rb +115 -0
  145. data/lib/woods/mcp/renderers/markdown_renderer.rb +22 -9
  146. data/lib/woods/mcp/renderers/plain_renderer.rb +18 -8
  147. data/lib/woods/mcp/search_results.rb +74 -0
  148. data/lib/woods/mcp/server.rb +178 -63
  149. data/lib/woods/mcp/tool_contract.rb +3 -1
  150. data/lib/woods/mcp/tool_response_renderer.rb +41 -0
  151. data/lib/woods/mcp/traversal_evidence.rb +113 -0
  152. data/lib/woods/mcp/traversal_evidence_index.rb +100 -0
  153. data/lib/woods/mcp/traversal_evidence_page.rb +41 -0
  154. data/lib/woods/mcp/traversal_evidence_text.rb +52 -0
  155. data/lib/woods/mcp/traversal_response.rb +22 -0
  156. data/lib/woods/notion/exporter.rb +56 -17
  157. data/lib/woods/obsidian/destination_plan.rb +98 -0
  158. data/lib/woods/obsidian/name_mapper.rb +19 -3
  159. data/lib/woods/obsidian/note_builder.rb +19 -10
  160. data/lib/woods/obsidian/vault_exporter.rb +88 -32
  161. data/lib/woods/operator/pipeline_guard.rb +18 -13
  162. data/lib/woods/path_dispatcher.rb +13 -6
  163. data/lib/woods/payload_store.rb +27 -26
  164. data/lib/woods/published_index/typed_unit_reader.rb +40 -3
  165. data/lib/woods/published_index.rb +2 -2
  166. data/lib/woods/railtie.rb +3 -3
  167. data/lib/woods/railtie_support.rb +12 -12
  168. data/lib/woods/rake_helpers.rb +382 -0
  169. data/lib/woods/resilience/graph_invariant_validator/membership_checks.rb +71 -0
  170. data/lib/woods/resilience/graph_invariant_validator/node_checks.rb +61 -0
  171. data/lib/woods/resilience/graph_invariant_validator/reverse_relationship_checks.rb +46 -0
  172. data/lib/woods/resilience/graph_invariant_validator.rb +119 -0
  173. data/lib/woods/resilience/index_validator/graph_checks.rb +80 -0
  174. data/lib/woods/resilience/index_validator.rb +112 -23
  175. data/lib/woods/retrieval/context_assembler.rb +50 -15
  176. data/lib/woods/retrieval/lexical_assembler.rb +84 -0
  177. data/lib/woods/retrieval/lexical_index.rb +120 -0
  178. data/lib/woods/retrieval/ranker.rb +4 -2
  179. data/lib/woods/retrieval/scope.rb +108 -0
  180. data/lib/woods/retrieval/scoped_graph_store.rb +32 -0
  181. data/lib/woods/retrieval/scoped_vector_store.rb +55 -0
  182. data/lib/woods/retrieval/search_executor.rb +86 -27
  183. data/lib/woods/retrieval/source_evidence.rb +200 -0
  184. data/lib/woods/retriever.rb +98 -22
  185. data/lib/woods/ruby_analyzer/trace_enricher.rb +77 -38
  186. data/lib/woods/session_tracer/file_store.rb +6 -1
  187. data/lib/woods/session_tracer/middleware.rb +10 -12
  188. data/lib/woods/session_tracer/redis_store.rb +22 -6
  189. data/lib/woods/session_tracer/session_flow_assembler.rb +23 -17
  190. data/lib/woods/session_tracer/solid_cache_coordination.rb +6 -4
  191. data/lib/woods/session_tracer/unit_resolver.rb +63 -0
  192. data/lib/woods/source_inputs/consumer_errors.rb +31 -0
  193. data/lib/woods/source_inputs/handoff.rb +102 -0
  194. data/lib/woods/source_inputs/launcher.rb +157 -0
  195. data/lib/woods/source_inputs/manifest.rb +124 -0
  196. data/lib/woods/source_inputs/private_key.rb +55 -0
  197. data/lib/woods/source_inputs/scanner.rb +171 -0
  198. data/lib/woods/source_inputs/scopes.rb +71 -0
  199. data/lib/woods/source_inputs/session.rb +214 -0
  200. data/lib/woods/source_inputs/status.rb +84 -0
  201. data/lib/woods/source_inputs/verifier.rb +107 -0
  202. data/lib/woods/storage/metadata_store.rb +25 -25
  203. data/lib/woods/storage/pgvector.rb +35 -10
  204. data/lib/woods/storage/qdrant.rb +17 -7
  205. data/lib/woods/storage/vector_store.rb +18 -6
  206. data/lib/woods/tasks.rb +3 -2
  207. data/lib/woods/temporal/json_snapshot_store.rb +58 -9
  208. data/lib/woods/unblocked/exporter.rb +59 -70
  209. data/lib/woods/version.rb +1 -1
  210. data/lib/woods/watch/boot_snapshot.rb +52 -0
  211. data/lib/woods/watch/daemon.rb +154 -32
  212. data/lib/woods/watch/listen_watcher.rb +4 -0
  213. data/lib/woods/watch/polling_watcher.rb +5 -1
  214. data/lib/woods/watch/status.rb +20 -15
  215. data/lib/woods/watch/tree_scan.rb +21 -13
  216. data/lib/woods/watch/watcher.rb +4 -1
  217. data/lib/woods.rb +50 -11
  218. data/plugin/.claude-plugin/plugin.json +1 -1
  219. data/plugin/hooks/adapters/normalize.jq +15 -0
  220. data/plugin/hooks/adapters/normalize.rb +63 -0
  221. data/plugin/hooks/hooks.json +20 -0
  222. data/plugin/hooks/woods-context.sh +50 -0
  223. data/plugin/hooks/woods-input-rules.sh +159 -0
  224. data/plugin/hooks/woods-opencode.mjs +65 -0
  225. data/plugin/hooks/woods-post-edit.sh +2 -225
  226. data/plugin/hooks/woods-refresh.sh +260 -0
  227. data/plugin/hooks/woods-session-start.sh +47 -55
  228. data/plugin/skills/woods-agent-enable/SKILL.md +19 -0
  229. data/plugin/skills/woods-diagnose/SKILL.md +319 -1
  230. data/plugin/skills/woods-investigate/SKILL.md +145 -0
  231. data/plugin/skills/woods-mcp-config/SKILL.md +90 -2
  232. data/plugin/skills/woods-setup/SKILL.md +110 -6
  233. metadata +87 -5
@@ -8,7 +8,7 @@ This guide covers the most common problems encountered when installing, extracti
8
8
 
9
9
  | Error message | Cause | Fix |
10
10
  |---------------|-------|-----|
11
- | `No manifest.json found` | Wrong index path or no published generation | Use the path visible to the server process; run `woods:validate` |
11
+ | `Could not resolve a published Woods index` (older versions: `No manifest.json found`) | Wrong index path or unresolved published generation | Select the existing index using a path visible to the server process; see [startup diagnostics](#index-cannot-be-resolved-at-startup) |
12
12
  | `uninitialized constant Rails` | Not running inside Rails app | Run via `bundle exec rake` in Rails root |
13
13
  | `type "vector" does not exist` | pgvector not installed | `CREATE EXTENSION vector` in PostgreSQL |
14
14
  | `Connection refused (localhost:11434)` | Ollama not running | `ollama serve` |
@@ -23,8 +23,9 @@ This guide covers the most common problems encountered when installing, extracti
23
23
  | `No such container` | Wrong container name | Check with `docker ps --format '{{.Names}}'` |
24
24
  | `JSON parse errors` (MCP) | Rails boot noise on stdout | Remove `puts` calls from initializers |
25
25
  | Query timeout | Large table, no scope | Add scope conditions to narrow results |
26
+ | `Extraction failed for …; the previous generation remains active` | A consumer handled a source error during incremental extraction or refresh (unreleased after `2.0.0.beta3`) | Fix the logged source error and retry the [complete batch](INCREMENTAL_EXTRACTION.md#handled-source-errors-and-retry); watch keeps it pending |
26
27
  | 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
+ | Git metadata missing | Shallow clone in CI | Use `fetch-depth: 0` for complete history |
28
29
  | Parallel tool calls all fail | MCP client batches calls | Send calls sequentially, validate params first |
29
30
  | HTTP transport refuses to start on `0.0.0.0` | Missing bearer token | Set `WOODS_MCP_HTTP_TOKEN=…` or bind loopback only |
30
31
  | 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) |
@@ -43,8 +44,55 @@ For a single-call health snapshot, call the Index Server's `woods_status` tool.
43
44
 
44
45
  Agents cold-connecting to a server should call `woods_status` before any other tool, it eliminates most "why is this empty?" guesswork.
45
46
 
47
+ If `woods:validate` warns that the manifest writer and reader major versions
48
+ differ, run full extraction using the intended gem and follow the upgrade guide.
49
+ An invalid `woods_version` warns without failing structural validation; a missing
50
+ or null value is normal for older indexes. Compare `index.woods_version` with
51
+ `server.version` in MCP status; never infer an unknown writer from the reader's
52
+ version. See [manifest writer provenance](PUBLISHED_INDEX.md#manifest-writer-provenance).
53
+
46
54
  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
55
 
56
+ ### Semantic graph validation errors
57
+
58
+ In development versions containing #413, `woods:validate` rejects graphs that
59
+ parse as JSON but disagree with their indexes. Errors name the section and
60
+ identity, for example `reverse["http_api"]: missing "Order"`, a duplicate typed
61
+ variant, or an indexed unit absent from `nodes`. This is unreleased after
62
+ `2.0.0.beta2`; check the installed gem before expecting these diagnostics.
63
+
64
+ Keep the failing generation and report the exact errors. Run a full extraction
65
+ in a fresh application process with the intended bundle, then validate again.
66
+ Do not edit derived reverse/file/type indexes to silence the check. If a fresh
67
+ full run still fails, report the invariant and source units as an extraction bug.
68
+ The checker never repairs or republishes the index itself.
69
+
70
+ An unresolved target is legal; missing both a node and its unit cannot be
71
+ classified as external versus accidentally omitted from current metadata alone.
72
+ A green report also does not prove that an indexed relationship executes at
73
+ runtime. See [the checked invariants and limitations](INDEX_LAYOUT.md#semantic-graph-validation).
74
+
75
+ ### Corrupt pipeline cooldown state
76
+
77
+ In a custom Index MCP server configured with an `operator` and
78
+ `pipeline_guard`, a corrupt `pipeline_guard.json` denies full pipeline runs
79
+ until repaired. The packaged `woods-mcp` executable does not expose pipeline
80
+ operations; confirm the connected server's tools before using this recovery.
81
+
82
+ For versions containing B-159, call `pipeline_repair` with
83
+ `{"action":"reset_cooldowns"}` to replace malformed JSON, non-object JSON, or an
84
+ empty guard file with an empty state object under the guard's file lock. This
85
+ explicit action clears the extraction and embedding cooldowns; subsequent runs
86
+ can start immediately. The direct Ruby equivalent is `guard.reset!(:all)`.
87
+ Scoped resets leave corrupt state untouched. Valid state keeps any unrelated
88
+ operation entries, and missing state remains a no-op without creating a file.
89
+ A permission failure must be corrected before repair can succeed.
90
+
91
+ This recovery is unreleased after `2.0.0.beta2`; check the installed version.
92
+ Older versions report corrupt state as nothing to repair. Stop pipeline writers,
93
+ back up the configured guard state's `pipeline_guard.json`, and remove only that
94
+ file before restarting, or upgrade to a version containing the fix.
95
+
48
96
  ## Extraction Problems
49
97
 
50
98
  ### Extraction produces empty or incomplete output
@@ -171,21 +219,60 @@ bundle exec rake woods:extract
171
219
 
172
220
  ---
173
221
 
222
+ ### External dependency targets lose dependents after incremental extraction
223
+
224
+ **Symptom:** An external target such as `http_api` loses previously indexed
225
+ dependents after an incremental run.
226
+
227
+ **Cause:** Versions affected by B-193 can split symbolic and string target
228
+ identities when restoring and updating the graph.
229
+
230
+ **Fix:** Check whether the installed version includes B-193; this fix is
231
+ unreleased. After upgrading to a version containing the fix, run
232
+ `bundle exec rake woods:extract` once to rebuild lost reverse dependencies.
233
+ Loading an already damaged graph does not restore discarded entries. See the
234
+ [incremental graph contract](INCREMENTAL_EXTRACTION.md#the-contract).
235
+
174
236
  ### Git metadata is missing or shows zeros
175
237
 
176
- **Symptom:** Units have `last_modified_at: null` or `change_frequency: 0` in the JSON output.
238
+ **Symptom:** Per-unit `metadata.git` is absent, or an older Woods version reports
239
+ most files as `change_frequency: new` in a shallow CI checkout.
177
240
 
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.
241
+ **Cause:** A shallow clone truncates HEAD ancestry. The shallow-checkout guard is
242
+ unreleased after 2.0.0.beta2: current source omits git enrichment and warns once,
243
+ rather than treating the truncated history as complete. If repository depth
244
+ cannot be verified, enrichment is also omitted; check git access and version.
179
245
 
180
- **Fix:** Fetch at least two commits:
246
+ **Fix:** Fetch complete history (`git fetch --unshallow` for an existing shallow
247
+ clone), then run full extraction to replace retained metadata:
181
248
 
182
249
  ```yaml
183
250
  # .github/workflows/index.yml
184
251
  - uses: actions/checkout@v4
185
252
  with:
186
- fetch-depth: 2 # minimum for incremental; use 0 for full history
253
+ fetch-depth: 0
187
254
  ```
188
255
 
256
+ Two commits can suffice for an incremental diff, but do not establish the full
257
+ ancestry needed for churn metadata.
258
+
259
+ ---
260
+
261
+ ### Git enrichment warns that history could not be read completely
262
+
263
+ Current source uses an explicit merge-diff mode requiring **Git 2.31 or newer**.
264
+ This is unreleased after 2.0.0.beta2: first confirm the installed Woods version.
265
+ Check `git --version` inside the same container/process environment as extraction,
266
+ and upgrade git if it is older. On a supported version, check that the application's
267
+ `HEAD` and object store can be read using the same `WOODS_GIT_DIR` setting.
268
+
269
+ A failed or incomplete history stream is discarded as a whole; extraction continues
270
+ without that enrichment, rather than publishing partial or zero-count history.
271
+ Previously retained incremental units can still carry older metadata. After fixing
272
+ git, run a full extraction to refresh every unit. See the
273
+ [history contract](CONFIGURATION_REFERENCE.md#git-enrichment-history) for merge
274
+ counting and upgrade compatibility.
275
+
189
276
  ---
190
277
 
191
278
  ### Every unit reports `commit_count: 0` and `change_frequency: "new"`
@@ -304,13 +391,17 @@ relative and resolves outside the mount.
304
391
 
305
392
  ## MCP Server Problems
306
393
 
307
- ### "No manifest.json" error when starting the Index Server
394
+ <a id="no-manifestjson-error-when-starting-the-index-server"></a>
308
395
 
309
- **Symptom:** `woods-mcp-start` exits with an error like `No manifest.json found at /path/to/...` even though extraction completed.
396
+ ### Index cannot be resolved at startup
310
397
 
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.
398
+ **Symptom:** An Index MCP executable exits with `Could not resolve a published Woods index in: /path/to/...` even though extraction completed. This headline is unreleased after `2.0.0.beta3`; older versions say `No manifest.json found`. Both mean the selected index could not resolve its manifest, not that an atomic index needs a root manifest.
312
399
 
313
- **Fix:** Use the host path in your `.mcp.json`:
400
+ Embedded Index MCP startup through `IndexReader` also raises an `ArgumentError` with the selected directory and layout guidance when the marker cannot resolve a manifest, including malformed marker shapes such as `[]` or a numeric `payload` (unreleased after `2.0.0.beta3`). Earlier builds may expose a raw `TypeError` or `NoMethodError` for those shapes. Inspect the marker and preserve the failing index before attempting recovery.
401
+
402
+ **Cause:** The selected directory is not the published index root, the published generation cannot be resolved, or the path is not visible to the MCP process. A container path is appropriate for a container process; a host process needs the host-visible path.
403
+
404
+ **Fix:** Point at the existing index before extracting again. Check the examined directory in the error and the [MCP path precedence](CONFIGURATION_REFERENCE.md#environment-variables). For a host-side launch whose working directory contains `tmp/woods`, for example:
314
405
 
315
406
  ```json
316
407
  {
@@ -323,20 +414,19 @@ relative and resolves outside the mount.
323
414
  }
324
415
  ```
325
416
 
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:
417
+ **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`. Inspect the marker and its payload in the MCP process's filesystem before assuming extraction failed (use the generation named by your marker):
333
418
 
334
419
  ```bash
335
420
  cat ./tmp/woods/generation.json # {"number": 42, "payload": "payloads/gen-42", ...}
336
421
  ls ./tmp/woods/payloads/gen-42/manifest.json
337
422
  ```
338
423
 
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).
424
+ Custom scripts that require a root `dependency_graph.json` have the same failure.
425
+ Update their gate using the [filesystem layout contract](INDEX_LAYOUT.md), which
426
+ includes Bash/jq and Python readers. An upload must pin and copy one complete
427
+ payload before publishing its captured pointer; keep a failed copy unpublished.
428
+
429
+ `woods-mcp-start` and `IndexReader` resolve this automatically; these commands are for manual inspection. Legacy flat indexes use a root `manifest.json`. If neither layout resolves, check the selected path, pointer, payload and any volume mount. See [DOCKER_SETUP.md](DOCKER_SETUP.md) for container launches.
340
430
 
341
431
  ---
342
432
 
@@ -839,3 +929,24 @@ view sharing the same name. Current writers distinguish typed storage identities
839
929
  public names remain unchanged. Snapshot migration 007 runs automatically and keeps
840
930
  existing rows, but cannot recover variants lost by older writers. See the
841
931
  [upgrade guide](UPGRADING_TO_2.md) for storage, flow rebuild, and rollback details.
932
+
933
+ ## Watch exits 75 repeatedly at startup
934
+
935
+ Check the installed Woods version and its [watch daemon guide](WATCH_DAEMON.md).
936
+ Older releases, including `2.0.0.beta2`, can repeatedly request restart when a
937
+ boot-captured file is newer than the last index generation. Stop the supervisor,
938
+ run one successful `bundle exec rake woods:extract` in the application environment,
939
+ then start the standalone `bundle exec rake woods:watch` process again.
940
+
941
+ With startup snapshot support, a fresh environment boot performs the full
942
+ reconciliation automatically. Changes during environment initialization or live
943
+ watching still require restart. Do not prepend `environment` to the watch command
944
+ or start it inside an already initialized process when relying on this recovery.
945
+
946
+ ## Source freshness is unknown or drifted
947
+
948
+ Read `woods_status.index.source_freshness.reasons`. Old indexes, an inaccessible
949
+ source root/private key, a quick scan limit and an unverified boot boundary are
950
+ different causes. Try `source_check: "deep"` for a budget limit; use the fresh
951
+ launcher for a new verified baseline. Do not delete pending hook events or alter
952
+ key permissions just to suppress a warning. See [source freshness](SOURCE_FRESHNESS.md).
@@ -139,6 +139,20 @@ https://github.com/your-org/your-repo/blob/main/app/models/order.rb
139
139
 
140
140
  This means Unblocked citations link directly to the relevant source code.
141
141
 
142
+ Different identifiers defined in one synced file retain the existing URI
143
+ rule: the lexically first identifier keeps the bare URI; siblings use
144
+ `?unit=<encoded identifier>`. Original identifiers and unambiguous URIs do
145
+ not change when two extracted types share a name but have different files.
146
+
147
+ Two **different types with the same identifier and source file** cannot be
148
+ represented by this URI rule. Woods reports `ambiguous export URI` and skips
149
+ all documents sharing that base URI. It also disables stale-document deletion
150
+ for that run, including with `UNBLOCKED_FORCE_PURGE=1`. Existing remote
151
+ documents remain untouched. This is a deliberate refusal pending an explicit
152
+ remote URI migration; renaming public extraction identifiers is not required.
153
+ Collision detection includes all published types, including excluded types
154
+ and units outside a partial sync's top-N selection.
155
+
142
156
  ## Rate Limits
143
157
 
144
158
  The Unblocked API allows 1,000 calls per day (resets at midnight PST). A typical
@@ -236,6 +250,17 @@ state an unchanged codebase costs ~0 calls.
236
250
  Pair with `woods:incremental` to re-extract only changed files; the sync then
237
251
  pushes only the documents whose content actually changed.
238
252
 
253
+ Before any API mutation, Woods reads and validates the complete published unit
254
+ set under one generation pin. Full and partial selection use actual unit types;
255
+ the GraphQL family includes its four published subtypes once. Standalone
256
+ `sync_type` and `sync_type_partial` calls perform the same preflight. Missing,
257
+ unreadable, or mismatched identities refuse before uploads, deletion, or
258
+ manifest writes. Preserve the error, validate and regenerate the index, then
259
+ retry. Force flags cannot bypass this check. Preflight reads all published
260
+ unit bodies, so local read cost and temporary memory scale with the index even
261
+ for a single-type sync. Custom readers must provide complete published
262
+ enumeration or complete per-bucket listings plus strict typed lookup.
263
+
239
264
  ### Escape hatches
240
265
 
241
266
  - `UNBLOCKED_FORCE_FULL_SYNC=1`: re-push every document, ignoring the unchanged
@@ -5,8 +5,8 @@ Woods 2.0 changes observable index identifiers, publication layout, vector-store
5
5
  This guide assumes the last v1 release, 1.6.1, and targets 2.0.0.
6
6
 
7
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
8
+ > This tree declares 2.0.0.beta4 as a prerelease. After RubyGems lists it, pin it with
9
+ > `gem "woods", "2.0.0.beta4"`; `~> 2.0` resolves only once
10
10
  > 2.0.0 is published.
11
11
  <!-- release-state:end -->
12
12
 
@@ -47,6 +47,25 @@ After this runbook you will have:
47
47
 
48
48
  ## Before changing the bundle
49
49
 
50
+ ### Check the loader for wrapper-nested classes
51
+
52
+ File-path-governed naming of classes inside class namespaces requires **Zeitwerk
53
+ mode with Zeitwerk 2.6.9 or later** (`cpath_expected_at`, introduced in
54
+ [Zeitwerk 2.6.9](https://github.com/fxn/zeitwerk/blob/main/CHANGELOG.md#269-25-july-2023)). This is a requirement
55
+ of that naming capability, not a higher Rails minimum. Older Zeitwerk and
56
+ classic-mode applications can still extract ordinary declarations, but Woods
57
+ cannot use the loader to distinguish a file's class from its enclosing class
58
+ wrappers. Two sibling files may then derive the same wrapper identifier and
59
+ extraction will abort rather than silently discard one.
60
+
61
+ For a `same-type identifier collision`, inspect both named files and the
62
+ application's loader mode/version before rewriting valid namespace wrappers.
63
+ On an older-loader host, move to a compatible Zeitwerk version and Zeitwerk mode,
64
+ verify that the application boots and eager-loads, then run a fresh full
65
+ extraction. Rebuild embeddings and exports if identifiers change. A genuine
66
+ duplicate under a supported loader still needs distinct constants or one source
67
+ file. Woods does not provide a classic-mode naming fallback for this case.
68
+
50
69
  ### 1. Record the current installation
51
70
 
52
71
  Run in the same environment that boots Rails:
@@ -106,7 +125,7 @@ Pay particular attention to:
106
125
  - `output_dir` and environment overrides;
107
126
  - storage and embedding provider settings;
108
127
  - the configured embedding model/dimension;
109
- - `console_mcp_enabled`, the `console_mcp_token` secret source, allowed origins, path, and embedded read-tool flags;
128
+ - `console_mcp_enabled`, `console_mcp_http_enabled`, the HTTP `console_mcp_token` secret source, allowed origins, path, and embedded read-tool flags;
110
129
  - snapshot, session, Notion, Obsidian, and Unblocked settings;
111
130
  - old `config.extractors` or `config.add_gem` calls, which are not implemented selectors.
112
131
 
@@ -123,6 +142,10 @@ bin/rails woods:validate
123
142
  bin/rails woods:stats
124
143
  ```
125
144
 
145
+ Unreleased after `2.0.0.beta3`: `woods:clean` removes index artifacts but keeps
146
+ the output directory and its hidden extraction guard. This stable guard lets
147
+ concurrent writers coordinate safely; its presence does not mean an index remains.
148
+
126
149
  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
150
 
128
151
  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.
@@ -198,6 +221,15 @@ tmp/woods/
198
221
 
199
222
  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
223
 
224
+ Use the [filesystem layout contract](INDEX_LAYOUT.md) for Bash/jq and Python
225
+ examples. Multi-file reads and uploads must keep the selected payload pinned
226
+ against retention for the complete read/copy; pointer resolution alone does not
227
+ protect a directory from being pruned.
228
+
229
+ The optional manifest `woods_version` records its last publisher. A matching
230
+ major version after an incremental run does not establish that older units were
231
+ migrated; keep the full re-extraction requirement. See [writer provenance](PUBLISHED_INDEX.md#manifest-writer-provenance).
232
+
201
233
  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
234
 
203
235
  The bundle requires patched MessagePack >=1.8.2 and JSON >=2.19.9, <3.
@@ -254,28 +286,22 @@ Structural reads still work from a read-only index mount, but the `reload` tool
254
286
 
255
287
  ### Console users: preserve or configure the HTTP token
256
288
 
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:
289
+ If HTTP Console is enabled, preserve or configure a secret token of at least
290
+ 32 characters. Missing tokens warn outside production and HTTP requests fail
291
+ closed with 401; production boot refuses them. Configured short tokens raise
292
+ while HTTP Console is enabled. Keep token values in the application's normal
293
+ secret store, never in committed configuration.
271
294
 
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.
295
+ Stdio does not use a bearer token. On versions supporting
296
+ `console_mcp_http_enabled`, set it to `false` for stdio-only use without HTTP
297
+ boot validation, while keeping the master `console_mcp_enabled` flag on.
298
+ The HTTP flag defaults to `true` to preserve existing deployments; choosing a
299
+ stdio client alone does not turn HTTP off. Older versions without this flag
300
+ still require a token at production boot whenever Console is enabled.
275
301
 
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.
302
+ Follow [Console MCP setup](CONSOLE_MCP_SETUP.md) for transport-specific setup
303
+ and the [Configuration reference](CONFIGURATION_REFERENCE.md) for defaults.
277
304
 
278
- See [Console MCP setup](CONSOLE_MCP_SETUP.md) for client examples and [Configuration reference](CONFIGURATION_REFERENCE.md) for the complete security settings.
279
305
 
280
306
  ## Verify before rollout
281
307