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
data/docs/AGENT_SETUP.md CHANGED
@@ -40,13 +40,23 @@ If the worktree contains unrelated changes, preserve them. Do not overwrite an e
40
40
 
41
41
  Use structural-only setup when the user wants code navigation, runtime Rails structure, dependencies, flows, or blast-radius analysis. Fourteen tools register in the normal packaged launch without an embedding provider.
42
42
 
43
- Discuss semantic retrieval only if the user needs natural-language `codebase_retrieve`. The choice depends on whether they prefer local Ollama or hosted OpenAI and which vector store fits their environment. See [Backend matrix](BACKEND_MATRIX.md).
43
+ If the user wants ranked discovery through `codebase_retrieve`, offer [explicit lexical mode](RETRIEVAL_GUIDE.md#embedding-free-lexical-retrieval) over the published index without a provider or embeddings. Check that the installed version supports it, set `WOODS_RETRIEVAL_MODE=lexical` in the MCP process environment, restart that server, and verify `woods_status.retriever.mode`. Keep structural-only setup as the default unless this mode is requested.
44
+
45
+ For semantic matching, discuss local Ollama or hosted OpenAI and the appropriate vector store separately; adding a provider still requires authorization. See [Backend matrix](BACKEND_MATRIX.md).
44
46
 
45
47
  Do not infer permission to configure Console MCP from a request to “set up Woods” or “set up MCP.” The Index Server reads generated code context; the Console Server can read live data.
46
48
 
47
49
  ## 3. Install on a branch
48
50
 
49
- Create or switch to the branch requested by the repository owner. Add only the development dependency:
51
+ Create or switch to the branch requested by the repository owner. Select the
52
+ published version using the [installation guide](GETTING_STARTED.md#1-install-the-gem).
53
+ Before stable 2.x is published, use the exact published prerelease constraint
54
+ from the README release table; `~> 2.0` will not select a beta or release candidate.
55
+ Use the selected version's tag documentation and verify its capabilities before
56
+ configuring features described on `main`.
57
+
58
+ Add only the development dependency. The following constraint applies **after a
59
+ stable 2.x release is published**:
50
60
 
51
61
  ```ruby
52
62
  # Gemfile
@@ -120,15 +130,94 @@ For Docker, extraction runs inside the Rails container. If Woods is installed on
120
130
 
121
131
  Reconnect the client and call `woods_status`. Confirm a current generation and non-zero unit counts before claiming setup works.
122
132
 
133
+ ### Managed Claude Code configuration
134
+
135
+ `woods-agent-config` is available from Woods `2.0.0.beta3`.
136
+ Check `bundle exec woods-agent-config --help` in the selected application bundle;
137
+ use the manual client configuration below when it is absent. The supported
138
+ client format is Claude Code (tested with 2.1.267).
139
+
140
+ Create a private plan, inspect its paths and diff, then apply that same plan:
141
+
142
+ ```bash
143
+ bundle exec woods-agent-config setup --client claude --scope project \
144
+ --root "$PWD" --instructions CLAUDE.md,AGENTS.md --plan /tmp/woods-setup.json --diff
145
+ bundle exec woods-agent-config apply /tmp/woods-setup.json \
146
+ --client claude --scope project --root "$PWD"
147
+ ```
148
+
149
+ Choose a new, unused plan filename for each preview. Preview writes only the
150
+ requested plan file; it does not edit managed configuration. Plans contain the
151
+ complete replacement bytes, including unrelated settings, and use mode 0600:
152
+ keep them private and remove them when no longer needed. `show FILE` prints its
153
+ summary; `show FILE --diff` checks the original snapshots and prints a unified
154
+ diff. Applying a changed snapshot fails rather than replacing the new content.
155
+ A repeated identical setup makes no configuration edits.
156
+
157
+ | Selection | Managed files |
158
+ |---|---|
159
+ | `--scope project` | `<root>/.mcp.json`, explicitly selected `<root>/CLAUDE.md` and/or `AGENTS.md`, `<root>/.woods-agent-config.json` ownership receipt |
160
+ | `--scope user` | `~/.claude.json`, explicitly selected `~/.claude/CLAUDE.md`, application-specific receipt in `~/.claude/` |
161
+
162
+ With `CLAUDE_CONFIG_DIR`, user scope uses that directory's `.claude.json`,
163
+ `CLAUDE.md`, and receipt instead. Instruction edits are opt-in with
164
+ `--instructions`; existing selections carry forward on update. The command
165
+ configures the Index Server. Client trust and project approval remain Claude
166
+ Code settings; apply does not change them.
167
+
168
+ Preflight runs the selected installed bundle, validates its index, and checks
169
+ its actual registered capabilities. It does not boot Rails or contact an
170
+ embedding provider. The bundle must already resolve in frozen mode; prepare
171
+ its lockfile separately if Bundler reports a mismatch. Host mode uses the
172
+ application's absolute Gemfile and index paths. `--index tmp/woods` is relative
173
+ to the selected root. For Compose, also select `--mode compose --service web
174
+ --container-root /app`; run the configuration command where Docker Compose can
175
+ access that project. Preflight verifies the index and installed gem inside that
176
+ service. Both host and container subprocesses have time limits.
177
+
178
+ Use `update --plan FILE` with the same client/scope/root and the desired launch
179
+ options to change the owned entry or instruction selection. Update explicitly
180
+ records the current template and installed-gem evidence; background hooks never
181
+ update configuration. `remove --plan FILE` previews deletion of owned content
182
+ and does not require the application bundle or index to remain available.
183
+ Use `--name NAME` consistently if the installation uses a nondefault server name.
184
+ Apply each operation's saved plan with the same explicit client/scope/root.
185
+
186
+ Ownership comes from the receipt and exact managed section, not from a server
187
+ named `woods`. Existing unowned names, edited managed content, malformed JSON,
188
+ duplicate markers, symlinks, and concurrent edits cause conflicts. Preserve the
189
+ receipt for future update/removal. Unrelated servers, hooks, settings,
190
+ instruction text, permissions, and line-ending conventions are retained;
191
+ changing JSON may reformat its whitespace.
192
+
193
+ Unreleased after `2.0.0.beta3`: apply and recovery coordinate on the actual
194
+ managed file paths, including user configuration and shared instruction files.
195
+ Two application roots sharing those files cannot apply overlapping plans at the
196
+ same time. A competing operation reports a conflict; after it finishes, create a
197
+ fresh preview if the saved plan's snapshots changed. Both applications keep
198
+ their own ownership receipts. Do not delete an active coordination lock.
199
+
200
+ Writes use atomic replacement per file and a private recovery journal beside
201
+ the receipt. The plan summary names all adjacent `.woods.lock` files, the
202
+ receipt `.lock`, and the `.pending` journal;
203
+ a lock file may remain after completion. Multiple files are not one atomic
204
+ transaction. An ordinary write failure restores original files when safe; an
205
+ interruption or concurrent edit can retain the journal. Resolve reported
206
+ conflicts, then use `recover --client claude --scope project --root "$PWD"`
207
+ (or the original user scope). Recovery refuses to overwrite concurrent edits.
208
+ Keep journals private because they contain original configuration bytes. A plan
209
+ whose recovery journal would exceed 8 MiB is refused before any managed file
210
+ is changed; reduce the selected configuration before applying.
211
+
123
212
  ## 7. Verify useful behavior
124
213
 
125
214
  Use a class known to exist in the application:
126
215
 
127
- 1. Call `search` to obtain its exact identifier.
128
- 2. Call `lookup` to confirm source and metadata are present.
216
+ 1. Call `search` to obtain its exact identifier and type.
217
+ 2. Call `lookup` with that identifier and type to confirm source and metadata are present.
129
218
  3. Call `dependents` with depth 1 or 2 to confirm graph edges are queryable.
130
219
 
131
- If `codebase_retrieve` reports that semantic search is disabled, that is expected for structural-only setup. Do not configure credentials merely to remove the message.
220
+ If `codebase_retrieve` reports that semantic search is disabled, that is expected for structural-only setup. Do not configure credentials merely to remove the message. If lexical retrieval was requested, verify its mode with `woods_status` and make one `codebase_retrieve` call against the published index.
132
221
 
133
222
  ## 8. Offer automatic index maintenance
134
223
 
@@ -184,7 +273,7 @@ Verified capabilities:
184
273
  - Index Server connected: yes/no
185
274
  - woods_status current: yes/no
186
275
  - search/lookup/dependents checked: yes/no
187
- - semantic retrieval: disabled/enabled (provider)
276
+ - retrieval: disabled/lexical/semantic (provider when semantic)
188
277
  - Console MCP: disabled/enabled (authorization)
189
278
  - automatic structural updates: disabled/enabled (process manager)
190
279
 
@@ -195,10 +284,12 @@ Never report a capability as enabled solely because its schema exists in source.
195
284
 
196
285
  ## Copyable prompt for an installation agent
197
286
 
198
- > Install Woods 2.x in this Rails repository using `docs/AGENT_SETUP.md`. Start with read-only preflight and preserve unrelated changes. Default to the structural Index Server; do not enable embeddings, Console MCP, HTTP transport, secrets, or purge overrides without asking me. Inspect generated files before migrating, run extraction and validation in the app's normal execution environment, configure a project-scoped MCP server in the same filesystem context as the application bundle and index, and verify `woods_status`, `search`, `lookup`, and `dependents`. Finish with the runbook's handoff report.
287
+ > Install Woods 2.x in this Rails repository using https://github.com/lost-in-the/woods/blob/main/docs/AGENT_SETUP.md. Select a published version and follow that version's tag documentation and supported capabilities. Start with read-only preflight and preserve unrelated changes. Default to the structural Index Server; do not enable embeddings, Console MCP, HTTP transport, secrets, or purge overrides without asking me. Inspect generated files before migrating, run extraction and validation in the app's normal execution environment, configure a project-scoped MCP server in the same filesystem context as the application bundle and index, and verify `woods_status`, `search`, `lookup` with the discovered identifier and type, and `dependents`. Finish with the runbook's handoff report.
199
288
 
200
289
  ## Related guides
201
290
 
291
+ - [Edit client adapters](CLIENT_HOOKS.md) for separately opt-in Claude/OpenCode edit hooks; MCP setup does not enable them.
292
+
202
293
  - [Getting started](GETTING_STARTED.md) for the human walkthrough.
203
294
  - [MCP servers](MCP_SERVERS.md) for client-specific configuration and server boundaries.
204
295
  - [Upgrade to Woods 2.0](UPGRADING_TO_2.md) for an existing 1.x installation.
@@ -96,6 +96,11 @@ CREATE INDEX IF NOT EXISTS idx_woods_vectors_embedding_hnsw
96
96
  ON woods_vectors USING hnsw (embedding vector_cosine_ops);
97
97
  ```
98
98
 
99
+ **Dimension limit:** Woods uses `vector_cosine_ops` HNSW, limited to 2,000 dimensions.
100
+ The default 3,072-dimensional `text-embedding-3-large` output needs an explicit
101
+ smaller provider output width or another backend. See the
102
+ [pgvector configuration contract](CONFIGURATION_REFERENCE.md#pgvector-postgresql).
103
+
99
104
  **Performance notes:**
100
105
  - HNSW: ~5ms search at 10K vectors, ~20ms at 100K. Memory: ~1.5x vector size.
101
106
  - For codebase indexing (~1000-5000 units, potentially 5000-20000 chunks), HNSW is appropriate.
@@ -271,6 +276,12 @@ contract tests; it does not represent semantic quality. Other values raise
271
276
 
272
277
  `build_metadata_store` accepts `:in_memory` and `:sqlite`. Nothing else is implemented.
273
278
 
279
+ Both adapters search Boolean fields as the JSON words `true` and `false`,
280
+ with case-insensitive substring matching. Numeric values `1` and `0` remain
281
+ separate from Booleans. Strings are searched without JSON quotes; objects and
282
+ arrays use JSON text. Null or absent fields never match a field-scoped query.
283
+ Whole-record search (`fields: nil`) searches serialized JSON, including keys.
284
+
274
285
  ### SQLite
275
286
 
276
287
  **Best for:** Local development, zero-dependency setups, testing, and every shipped preset except pure in-memory.
@@ -284,6 +295,11 @@ contract tests; it does not represent semantic quality. Other values raise
284
295
  - Single writer at a time
285
296
  - No network access
286
297
 
298
+ Metadata search uses literal, ASCII-case-insensitive substring matching. Selected
299
+ string fields include embedded NUL characters in the searchable text. With no
300
+ field selection, search operates on serialized JSON, where NUL is represented
301
+ as `\u0000`; a literal NUL query therefore does not match that escaped text.
302
+
287
303
  ### In-memory
288
304
 
289
305
  **Best for:** Testing, evaluation, small codebases.
@@ -318,6 +334,15 @@ A recursive-CTE graph store (MySQL 8.0+ or PostgreSQL, storing edges in a table
318
334
 
319
335
  Indexing can be triggered synchronously (rake task, inline) or from a background job. The pipeline itself is job-system-agnostic, it's synchronous Ruby, and the wrapper below is just scheduling and concurrency control. Use `Woods.extract!` for a full run; incremental runs need a changed-file list, so a job usually just shells out to `rake woods:incremental` (which computes that list from git) rather than calling `Woods.extract_changed!` directly.
320
336
 
337
+ Both Ruby helpers hold the same heartbeat-maintained extraction lock as the
338
+ tasks and watch daemon. `WOODS_LOCK_WAIT` controls the wait (600 seconds by
339
+ default); timeout raises `Woods::Coordination::LockError`. Failed generation
340
+ publication raises `Woods::ExtractionError`, allowing job retries instead of
341
+ reporting unpublished work as success. The low-level `Woods::Extractor` remains
342
+ an orchestration building block: callers using it directly own locking and
343
+ publication-failure handling. Do not wrap the public helpers in a second Woods
344
+ extraction lock.
345
+
321
346
  ### Sidekiq
322
347
 
323
348
  ```ruby
@@ -0,0 +1,111 @@
1
+ # Edit hooks for Claude Code and OpenCode
2
+
3
+ Edit hooks are optional. MCP reads and `woods:watch` work independently of them.
4
+ Check the installed gem exposes `woods:hook_refresh` before enabling these
5
+ unreleased adapters; updating the plugin does not update the application gem.
6
+ Start the client from the Rails application root, with an existing index.
7
+
8
+ ## Supported client contracts
9
+
10
+ | Client | Verified version | Events covered |
11
+ |---|---|---|
12
+ | Claude Code | 2.1.267 | Successful `PostToolUse` for `Write` and `Edit`, using `tool_input.file_path` |
13
+ | Claude legacy compatibility | Existing single-file `MultiEdit` shape | One `tool_input.file_path`; this is not multi-file patch support |
14
+ | OpenCode | 1.18.27 | `tool.execute.after` for `apply_patch`, `write`, and `edit` |
15
+
16
+ The OpenCode patch adapter reads the successful tool's `metadata.files` array.
17
+ It includes every add, update, delete and move; a move becomes deletion of the
18
+ old path plus addition of the new path. `write` uses `metadata.filepath` and
19
+ `metadata.exists`; `edit` uses `metadata.filediff.file`. Patch text, source bytes,
20
+ diagnostics, session identifiers and arbitrary shell commands are not parsed
21
+ or placed in the queue. Actual client captures and their provenance live under
22
+ `spec/fixtures/hooks/`; the exact metadata contract is pinned to
23
+ [OpenCode v1.18.27 source](https://github.com/anomalyco/opencode/tree/v1.18.27/packages/opencode/src/tool).
24
+ See the primary [Claude hook reference](https://code.claude.com/docs/en/hooks)
25
+ and [OpenCode plugin reference](https://opencode.ai/docs/plugins/).
26
+
27
+ Other clients and arbitrary mutation tools are unsupported. Unknown shapes for
28
+ registered edit tools produce a short diagnostic instead of claiming refresh.
29
+ Use the resident watcher or an explicit extraction for unsupported operations.
30
+ OpenCode session-start/context hooks are not provided by this edit adapter.
31
+
32
+ ## Claude Code registration
33
+
34
+ The Woods Claude plugin registers `woods-post-edit.sh` through its existing
35
+ `hooks/hooks.json`. The wrapper selects the explicit Claude parser, then the
36
+ shared runner handles queueing and extraction. Do not install a second copy of
37
+ the same hook. Existing single-file `MultiEdit` compatibility remains registered.
38
+
39
+ Enable the existing environment settings in the process launching the client:
40
+
41
+ ```bash
42
+ export WOODS_HOOKS_ENABLED=1
43
+ # Optional; relative to the application root:
44
+ export WOODS_OUTPUT=tmp/woods
45
+ ```
46
+
47
+ `WOODS_HOOKS_DISABLED=1` always wins. Enablement does not authorize Console MCP,
48
+ provider calls, user configuration writes, or daemon lifecycle changes.
49
+
50
+ ## OpenCode project registration
51
+
52
+ Keep the complete Woods `plugin/` directory at a stable path visible to the
53
+ client. The Claude marketplace registration does not install a native OpenCode
54
+ plugin. Create a project-local `.opencode/plugins/woods.js` containing this one
55
+ import, replacing the path with that stable plugin location:
56
+
57
+ ```javascript
58
+ export { default } from "/absolute/path/to/woods/plugin/hooks/woods-opencode.mjs";
59
+ ```
60
+
61
+ OpenCode automatically loads project `.js` and `.ts` plugin files at startup.
62
+ Use the `.js` wrapper name above; copying a standalone `.mjs` file into the
63
+ autoload directory does not register it. The wrapper imports the complete
64
+ adapter and shared runner; copying only the shell entry point is insufficient.
65
+ Restart OpenCode after registration and set `WOODS_HOOKS_ENABLED=1` in its launch
66
+ environment. This setup does not require npm packages or a host Rails bundle.
67
+
68
+ The adapter uses OpenCode's `directory` as the application root and verifies
69
+ that it belongs to the supplied git `worktree`. A Rails application nested
70
+ inside a repository therefore uses its own index. Each affected path must stay
71
+ inside that application root. Linked worktrees keep separate queues and indexes.
72
+
73
+ ## Queue, paths and recovery
74
+
75
+ Both adapters use the same extraction eligibility, queue, command prefix,
76
+ deadline, locks and active-daemon behavior described in
77
+ [watch hook operation](WATCH_DAEMON.md#hooks-for-agent-sessions).
78
+ `WOODS_HOOK_RAKE="docker compose exec -T app bundle exec rake"` runs extraction
79
+ inside the application container. The host needs Bash 3.2 or later, Unix tools,
80
+ and either jq or Ruby; OpenCode supplies its own JavaScript runtime.
81
+
82
+ The complete event is validated before queueing. Empty/NUL paths, traversal,
83
+ foreign-project paths and symlink path components are rejected. Deleted paths
84
+ need not exist; contained symlinks are deliberately unsupported too. Spaces,
85
+ commas, newlines and Unicode paths remain intact. Adapter input is limited to
86
+ 1 MiB and 1,000 affected paths; the OpenCode handoff is additionally limited to
87
+ 48 KiB. An unsupported oversized event requires explicit extraction or watch.
88
+ Raw input uses a private temporary file during validation so the shell cannot
89
+ silently remove bytes. The runner removes that file before publishing the
90
+ path-only queue record, and cleans it up on handled exits.
91
+
92
+ One immutable queue file holds a multi-file event. The owner batches up to
93
+ 16 files, 1,000 paths and 48 KiB without splitting an event. Existing single-path
94
+ queue records remain readable. A successful task, including a confirmed no-op,
95
+ acknowledges the batch; failure, timeout or daemon exit 75 retains every path.
96
+ At-least-once delivery can repeat work after a crash or event replay.
97
+
98
+ The OpenCode callback hands the event to a detached runner, which owns its
99
+ existing deadline. Callback completion means handoff, not index publication.
100
+ Inspect `<output>/hook.log`, the pending queue and the published generation to
101
+ confirm refresh. Malformed input diagnostics omit the original tool payload.
102
+ For rollback, disable hooks before removing registration; preserve pending
103
+ multi-file records until a compatible runner has consumed them.
104
+
105
+ ## Optional Claude context
106
+
107
+ Claude has a separate opt-in for bounded synchronous orientation and change-impact
108
+ reminders. It does not change either client’s refresh queue contract and is not
109
+ enabled by `WOODS_HOOKS_ENABLED`. Check the installed helper, timing, path-mapping
110
+ and uncertainty contract in [bounded context hints](WATCH_DAEMON.md#optional-bounded-context-hints).
111
+ OpenCode refresh support does not imply context delivery support.