woods 2.0.0.beta2 → 2.0.0.beta3

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +262 -1
  3. data/CONTRIBUTING.md +173 -9
  4. data/README.md +7 -3
  5. data/SECURITY.md +9 -6
  6. data/docs/AGENT_GUIDE.md +83 -4
  7. data/docs/AGENT_SETUP.md +82 -1
  8. data/docs/BACKEND_MATRIX.md +20 -0
  9. data/docs/CLIENT_HOOKS.md +111 -0
  10. data/docs/CONFIGURATION_REFERENCE.md +199 -14
  11. data/docs/CONSOLE_MCP_SETUP.md +35 -5
  12. data/docs/DOCKER_SETUP.md +21 -2
  13. data/docs/EVALUATION.md +464 -1
  14. data/docs/EXTRACTOR_REFERENCE.md +36 -5
  15. data/docs/FAQ.md +11 -12
  16. data/docs/GETTING_STARTED.md +17 -5
  17. data/docs/INCREMENTAL_EXTRACTION.md +117 -1
  18. data/docs/INDEX_LAYOUT.md +382 -0
  19. data/docs/INTERNALS.md +7 -2
  20. data/docs/MCP_SERVERS.md +221 -5
  21. data/docs/MCP_TOOL_COOKBOOK.md +33 -18
  22. data/docs/NOTION_INTEGRATION.md +13 -0
  23. data/docs/OBSIDIAN_INTEGRATION.md +57 -9
  24. data/docs/PUBLISHED_INDEX.md +55 -0
  25. data/docs/README.md +7 -0
  26. data/docs/RETRIEVAL_GUIDE.md +253 -11
  27. data/docs/RUNTIME_TRACING.md +71 -0
  28. data/docs/SOURCE_FRESHNESS.md +143 -0
  29. data/docs/TROUBLESHOOTING.md +117 -5
  30. data/docs/UNBLOCKED_INTEGRATION.md +25 -0
  31. data/docs/UPGRADING_TO_2.md +44 -22
  32. data/docs/WATCH_DAEMON.md +259 -59
  33. data/exe/woods-agent-config +6 -0
  34. data/exe/woods-extract +5 -0
  35. data/exe/woods-hook-context +6 -0
  36. data/lib/generators/woods/templates/woods.rb.tt +1 -3
  37. data/lib/tasks/woods.rake +47 -397
  38. data/lib/woods/agent_configuration/applier.rb +133 -0
  39. data/lib/woods/agent_configuration/cli.rb +101 -0
  40. data/lib/woods/agent_configuration/cli_options.rb +29 -0
  41. data/lib/woods/agent_configuration/document.rb +105 -0
  42. data/lib/woods/agent_configuration/error.rb +7 -0
  43. data/lib/woods/agent_configuration/launcher.rb +75 -0
  44. data/lib/woods/agent_configuration/layout.rb +59 -0
  45. data/lib/woods/agent_configuration/managed_section.rb +62 -0
  46. data/lib/woods/agent_configuration/plan.rb +98 -0
  47. data/lib/woods/agent_configuration/plan_diff.rb +38 -0
  48. data/lib/woods/agent_configuration/planned_files.rb +61 -0
  49. data/lib/woods/agent_configuration/planner.rb +63 -0
  50. data/lib/woods/agent_configuration/planner_validation.rb +77 -0
  51. data/lib/woods/agent_configuration/preflight.rb +100 -0
  52. data/lib/woods/agent_configuration/recovery.rb +49 -0
  53. data/lib/woods/ast/node.rb +2 -0
  54. data/lib/woods/ast/parser.rb +38 -5
  55. data/lib/woods/builder.rb +21 -5
  56. data/lib/woods/cache/cache_middleware.rb +28 -7
  57. data/lib/woods/cache/cache_store.rb +4 -5
  58. data/lib/woods/change_set.rb +5 -4
  59. data/lib/woods/console/credential_index.rb +20 -2
  60. data/lib/woods/console/credential_scanner.rb +14 -14
  61. data/lib/woods/console/credential_scanner_registry.rb +36 -0
  62. data/lib/woods/console/embedded_executor.rb +1 -1
  63. data/lib/woods/console/encrypted_credential_snapshot.rb +16 -0
  64. data/lib/woods/console/rack_middleware.rb +22 -13
  65. data/lib/woods/console/server.rb +18 -16
  66. data/lib/woods/dependency_graph.rb +65 -13
  67. data/lib/woods/embedding/corpus.rb +94 -0
  68. data/lib/woods/embedding/indexer.rb +90 -46
  69. data/lib/woods/embedding/openai.rb +17 -6
  70. data/lib/woods/evaluation/ablation_executor.rb +6 -1
  71. data/lib/woods/evaluation/ablation_timed_executor.rb +22 -4
  72. data/lib/woods/export/typed_reader.rb +56 -0
  73. data/lib/woods/extractor.rb +232 -137
  74. data/lib/woods/extractors/action_cable_extractor.rb +3 -1
  75. data/lib/woods/extractors/behavioral_profile.rb +9 -7
  76. data/lib/woods/extractors/caching_extractor.rb +3 -1
  77. data/lib/woods/extractors/concern_extractor.rb +64 -6
  78. data/lib/woods/extractors/configuration_extractor.rb +7 -3
  79. data/lib/woods/extractors/controller_extractor.rb +13 -4
  80. data/lib/woods/extractors/database_view_extractor.rb +3 -1
  81. data/lib/woods/extractors/decorator_extractor.rb +3 -1
  82. data/lib/woods/extractors/engine_extractor.rb +3 -1
  83. data/lib/woods/extractors/event_extractor.rb +4 -2
  84. data/lib/woods/extractors/factory_extractor.rb +3 -1
  85. data/lib/woods/extractors/graphql_extractor.rb +8 -2
  86. data/lib/woods/extractors/i18n_extractor.rb +3 -1
  87. data/lib/woods/extractors/job_extractor.rb +6 -19
  88. data/lib/woods/extractors/lib_extractor.rb +3 -1
  89. data/lib/woods/extractors/mailer_extractor.rb +20 -5
  90. data/lib/woods/extractors/manager_extractor.rb +3 -1
  91. data/lib/woods/extractors/method_parameters.rb +53 -0
  92. data/lib/woods/extractors/middleware_argument.rb +65 -0
  93. data/lib/woods/extractors/middleware_extractor.rb +9 -3
  94. data/lib/woods/extractors/migration_extractor.rb +3 -1
  95. data/lib/woods/extractors/model_extractor.rb +39 -33
  96. data/lib/woods/extractors/package_extractor.rb +24 -4
  97. data/lib/woods/extractors/phlex_extractor.rb +3 -1
  98. data/lib/woods/extractors/policy_extractor.rb +3 -1
  99. data/lib/woods/extractors/poro_extractor.rb +3 -1
  100. data/lib/woods/extractors/pundit_extractor.rb +3 -1
  101. data/lib/woods/extractors/rails_source_extractor.rb +4 -2
  102. data/lib/woods/extractors/rake_task_extractor.rb +4 -2
  103. data/lib/woods/extractors/route_extractor.rb +3 -1
  104. data/lib/woods/extractors/route_helper_resolver.rb +10 -33
  105. data/lib/woods/extractors/scheduled_job_extractor.rb +41 -15
  106. data/lib/woods/extractors/serializer_extractor.rb +4 -2
  107. data/lib/woods/extractors/service_extractor.rb +3 -1
  108. data/lib/woods/extractors/shared_dependency_scanner.rb +2 -2
  109. data/lib/woods/extractors/shared_utility_methods.rb +27 -15
  110. data/lib/woods/extractors/source_nesting.rb +1 -1
  111. data/lib/woods/extractors/state_machine_extractor.rb +3 -1
  112. data/lib/woods/extractors/test_mapping_extractor.rb +3 -1
  113. data/lib/woods/extractors/validator_extractor.rb +3 -1
  114. data/lib/woods/extractors/view_component_extractor.rb +3 -1
  115. data/lib/woods/extractors/view_template_extractor.rb +3 -1
  116. data/lib/woods/gem_mapper.rb +2 -0
  117. data/lib/woods/git_history.rb +116 -0
  118. data/lib/woods/graph_analyzer.rb +35 -6
  119. data/lib/woods/hooks/context_cli.rb +54 -0
  120. data/lib/woods/hooks/context_event.rb +88 -0
  121. data/lib/woods/hooks/context_hint.rb +73 -0
  122. data/lib/woods/hooks/context_impact.rb +77 -0
  123. data/lib/woods/hooks/context_output.rb +47 -0
  124. data/lib/woods/hooks/context_state.rb +102 -0
  125. data/lib/woods/hooks/refresh.rb +79 -0
  126. data/lib/woods/hooks/rule_projection.rb +78 -0
  127. data/lib/woods/input_rules.rb +19 -0
  128. data/lib/woods/mcp/bearer_auth.rb +20 -12
  129. data/lib/woods/mcp/bootstrapper.rb +62 -0
  130. data/lib/woods/mcp/index_reader.rb +323 -160
  131. data/lib/woods/mcp/initialization_guidance.rb +27 -0
  132. data/lib/woods/mcp/origin_guard.rb +17 -9
  133. data/lib/woods/mcp/published_lexical_retriever.rb +115 -0
  134. data/lib/woods/mcp/renderers/markdown_renderer.rb +8 -1
  135. data/lib/woods/mcp/renderers/plain_renderer.rb +7 -1
  136. data/lib/woods/mcp/search_results.rb +74 -0
  137. data/lib/woods/mcp/server.rb +158 -37
  138. data/lib/woods/mcp/tool_contract.rb +2 -0
  139. data/lib/woods/mcp/tool_response_renderer.rb +25 -0
  140. data/lib/woods/mcp/traversal_evidence.rb +113 -0
  141. data/lib/woods/mcp/traversal_evidence_index.rb +100 -0
  142. data/lib/woods/mcp/traversal_evidence_page.rb +41 -0
  143. data/lib/woods/mcp/traversal_evidence_text.rb +52 -0
  144. data/lib/woods/notion/exporter.rb +56 -17
  145. data/lib/woods/obsidian/destination_plan.rb +98 -0
  146. data/lib/woods/obsidian/name_mapper.rb +19 -3
  147. data/lib/woods/obsidian/note_builder.rb +19 -10
  148. data/lib/woods/obsidian/vault_exporter.rb +88 -32
  149. data/lib/woods/operator/pipeline_guard.rb +18 -13
  150. data/lib/woods/path_dispatcher.rb +7 -1
  151. data/lib/woods/payload_store.rb +27 -26
  152. data/lib/woods/railtie.rb +3 -3
  153. data/lib/woods/railtie_support.rb +12 -12
  154. data/lib/woods/rake_helpers.rb +392 -0
  155. data/lib/woods/resilience/graph_invariant_validator/membership_checks.rb +71 -0
  156. data/lib/woods/resilience/graph_invariant_validator/node_checks.rb +61 -0
  157. data/lib/woods/resilience/graph_invariant_validator/reverse_relationship_checks.rb +46 -0
  158. data/lib/woods/resilience/graph_invariant_validator.rb +119 -0
  159. data/lib/woods/resilience/index_validator/graph_checks.rb +80 -0
  160. data/lib/woods/resilience/index_validator.rb +112 -23
  161. data/lib/woods/retrieval/context_assembler.rb +50 -15
  162. data/lib/woods/retrieval/lexical_assembler.rb +73 -0
  163. data/lib/woods/retrieval/lexical_index.rb +119 -0
  164. data/lib/woods/retrieval/ranker.rb +4 -2
  165. data/lib/woods/retrieval/scope.rb +108 -0
  166. data/lib/woods/retrieval/scoped_graph_store.rb +32 -0
  167. data/lib/woods/retrieval/scoped_vector_store.rb +55 -0
  168. data/lib/woods/retrieval/search_executor.rb +86 -27
  169. data/lib/woods/retrieval/source_evidence.rb +200 -0
  170. data/lib/woods/retriever.rb +98 -22
  171. data/lib/woods/ruby_analyzer/trace_enricher.rb +77 -38
  172. data/lib/woods/session_tracer/middleware.rb +10 -12
  173. data/lib/woods/session_tracer/redis_store.rb +22 -6
  174. data/lib/woods/session_tracer/session_flow_assembler.rb +23 -17
  175. data/lib/woods/session_tracer/solid_cache_coordination.rb +6 -4
  176. data/lib/woods/session_tracer/unit_resolver.rb +63 -0
  177. data/lib/woods/source_inputs/consumer_errors.rb +27 -0
  178. data/lib/woods/source_inputs/handoff.rb +102 -0
  179. data/lib/woods/source_inputs/launcher.rb +157 -0
  180. data/lib/woods/source_inputs/manifest.rb +124 -0
  181. data/lib/woods/source_inputs/private_key.rb +55 -0
  182. data/lib/woods/source_inputs/scanner.rb +171 -0
  183. data/lib/woods/source_inputs/scopes.rb +71 -0
  184. data/lib/woods/source_inputs/session.rb +214 -0
  185. data/lib/woods/source_inputs/status.rb +84 -0
  186. data/lib/woods/source_inputs/verifier.rb +107 -0
  187. data/lib/woods/storage/metadata_store.rb +25 -25
  188. data/lib/woods/storage/pgvector.rb +29 -8
  189. data/lib/woods/storage/qdrant.rb +17 -7
  190. data/lib/woods/storage/vector_store.rb +18 -6
  191. data/lib/woods/tasks.rb +3 -2
  192. data/lib/woods/temporal/json_snapshot_store.rb +29 -8
  193. data/lib/woods/unblocked/exporter.rb +59 -70
  194. data/lib/woods/version.rb +1 -1
  195. data/lib/woods/watch/boot_snapshot.rb +52 -0
  196. data/lib/woods/watch/daemon.rb +136 -28
  197. data/lib/woods/watch/listen_watcher.rb +4 -0
  198. data/lib/woods/watch/polling_watcher.rb +5 -1
  199. data/lib/woods/watch/status.rb +20 -15
  200. data/lib/woods/watch/tree_scan.rb +21 -13
  201. data/lib/woods/watch/watcher.rb +4 -1
  202. data/lib/woods.rb +50 -11
  203. data/plugin/.claude-plugin/plugin.json +1 -1
  204. data/plugin/hooks/adapters/normalize.jq +15 -0
  205. data/plugin/hooks/adapters/normalize.rb +63 -0
  206. data/plugin/hooks/hooks.json +20 -0
  207. data/plugin/hooks/woods-context.sh +50 -0
  208. data/plugin/hooks/woods-input-rules.sh +159 -0
  209. data/plugin/hooks/woods-opencode.mjs +65 -0
  210. data/plugin/hooks/woods-post-edit.sh +2 -225
  211. data/plugin/hooks/woods-refresh.sh +260 -0
  212. data/plugin/hooks/woods-session-start.sh +47 -55
  213. data/plugin/skills/woods-agent-enable/SKILL.md +13 -0
  214. data/plugin/skills/woods-diagnose/SKILL.md +288 -1
  215. data/plugin/skills/woods-investigate/SKILL.md +106 -0
  216. data/plugin/skills/woods-mcp-config/SKILL.md +89 -1
  217. data/plugin/skills/woods-setup/SKILL.md +107 -6
  218. metadata +84 -5
data/docs/AGENT_GUIDE.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  This guide is for coding agents using an already connected Woods MCP server. Woods is evidence from the running Rails application and its extracted graph; it complements file search, tests, git history, and direct source inspection.
4
4
 
5
+ Supporting servers also send a concise version of this workflow in MCP
6
+ initialization/discovery instructions, without requiring an installed plugin.
7
+ Check the connected server's version and registered tools; this feature is
8
+ unreleased after `2.0.0.beta2`, and protocol `2024-11-05` omits the field.
9
+ The [initialization contract](MCP_SERVERS.md#initialization-guidance) describes
10
+ availability. This guide remains the detailed reference when instructions are
11
+ absent or the client does not display them.
12
+
5
13
  ## Start every session with status
6
14
 
7
15
  Call `woods_status` before relying on the index. Check:
@@ -13,6 +21,11 @@ Call `woods_status` before relying on the index. Check:
13
21
 
14
22
  If status is unhealthy, report the evidence and ask the owner to extract or refresh. Do not fill gaps by asserting that Woods found nothing.
15
23
 
24
+ Compare `index.woods_version` (last manifest publisher, unknown for older indexes)
25
+ with `server.version` (the MCP reader). A major-version difference warrants a
26
+ full extraction and upgrade review; a match does not prove every retained unit
27
+ was rewritten. See [manifest writer provenance](PUBLISHED_INDEX.md#manifest-writer-provenance).
28
+
16
29
  ## The default query loop
17
30
 
18
31
  Use this four-step loop for most codebase questions:
@@ -103,22 +116,48 @@ fields: ["identifier", "source_code", "metadata"]
103
116
 
104
117
  Start with identifier search. Add source or metadata only when name discovery fails. Restrict types and keep result limits small enough to inspect.
105
118
 
119
+ On supporting versions, read `completeness` before calling search exhaustive.
120
+ `result_count` counts returned rows. `result_limit` proves one more match exists;
121
+ `scan_budget` and `regex_timeout` leave that unknown. Only `exhausted` establishes
122
+ an exact total within the requested index/query domain. Narrow types, literal
123
+ prefix/suffix filters, or deep fields when `partial` is true. A detected artifact
124
+ failure remains an error with unknown completeness, never proof of no matches.
125
+
126
+ This metadata is unreleased after `2.0.0.beta2`; older servers may omit it.
127
+ Do not infer completeness from a full page or missing metadata. See the
128
+ [search response contract](MCP_SERVERS.md#search-completeness).
129
+
106
130
  ## Traverse deliberately
107
131
 
108
132
  `dependencies` means “what this unit uses.” `dependents` means “what uses this unit.” Both default to bounded breadth-first traversal and accept type or relationship filters.
109
133
 
110
134
  Start at depth 1 or 2. A deeper unfiltered traversal can obscure the direct evidence that matters. Common relationship values include associations (`belongs_to`, `has_many`, `has_one`), code references, renders, redirects, form actions, and navigation links.
111
135
 
112
- Both return at most 50 nodes and say so with a `Showing N of M (truncated)`
136
+ Both return at most 50 nodes by default and say so with a `Showing N of M (truncated)`
113
137
  line. Narrow with `depth`, `types` and `via` before paging with `limit` and
114
138
  `offset`: narrowing answers the question, paging only splits the same answer
115
139
  across turns. In a multi-database app each row names the unit's database.
116
140
 
117
- Use returned relationship labels as evidence. Do not infer call order from a dependency edge alone.
141
+ A traversal can also stop at its independent node or edge budget. Treat
142
+ `partial`/`partial_reason` as incomplete graph evidence even on the final page;
143
+ paging cannot recover nodes the walk never reached. Check the connected schema
144
+ before using `max_nodes`/`max_edges`, and follow the
145
+ [budget contract](MCP_SERVERS.md#dependency-traversal-budgets).
118
146
 
119
- ## Use semantic retrieval only when ready
147
+ When the connected schema supports `explain`, request `explain: true` to see
148
+ recorded source-to-target relationships and a shared shortest witness to each
149
+ row. Report `direct` relationships separately from `transitive` inferred impact.
150
+ Follow `parent`/`edge_id` references; `context: true` ancestors are outside the
151
+ current result page. Unknown labels and ambiguous candidate types stay unknown;
152
+ `typed_path_complete: false` does not establish a uniquely typed path. See the
153
+ [explanation contract](MCP_SERVERS.md#traversal-explanations).
120
154
 
121
- `codebase_retrieve` answers natural-language questions with token-budgeted context. Use it when `woods_status` reports a configured embedding provider and current vector data.
155
+ Use recorded relationship labels as evidence. Do not infer execution or call
156
+ order from a dependency edge alone.
157
+
158
+ ## Use ranked retrieval only when ready
159
+
160
+ `codebase_retrieve` answers natural-language questions with token-budgeted context. Use it when `woods_status` reports explicit lexical mode over a current published index, or a configured embedding provider and current vector data in semantic mode. Lexical mode explains matching terms/fields and does not infer synonyms absent from the text; a no-match response is not proof of missing behavior.
122
161
 
123
162
  Important parameters:
124
163
 
@@ -202,3 +241,43 @@ Verification: <source/test/history checked or still needed>
202
241
  - [Extractor reference](EXTRACTOR_REFERENCE.md): indexed unit and edge contracts.
203
242
  - [Retrieval guide](RETRIEVAL_GUIDE.md): embeddings, ranking, and token budgets.
204
243
  - [Troubleshooting](TROUBLESHOOTING.md): stale indexes, disabled retrieval, and startup failures.
244
+
245
+ ### Explicit retrieval and discovery scope
246
+
247
+ On a server whose tool schema advertises them, `packages` and `source_paths` narrow
248
+ `search` and `codebase_retrieve` before candidate limits. These are per-call
249
+ arguments, not configuration settings. Inspect applied scope and completeness;
250
+ a narrow graph query can omit relevant cross-boundary dependencies. See the
251
+ [scope contract](RETRIEVAL_GUIDE.md#explicit-package-and-source-path-scopes) for
252
+ root/nested ownership, path normalization, errors, storage support, and cost.
253
+
254
+ ### Verify source content before relying on freshness
255
+
256
+ When supported by the installed version, inspect `woods_status.index.source_freshness`.
257
+ Repeated edits can leave the porcelain fingerprint unchanged. A quick-budget
258
+ `unknown` can justify one explicit `source_check: "deep"` call; persistent unknown
259
+ needs the reported limitation resolved, not repeated status polling. Use
260
+ `bundle exec woods-extract full` to establish verified preboot source evidence.
261
+ A named refresh does not certify unrelated consumers or external runtime state.
262
+ Follow [source freshness](SOURCE_FRESHNESS.md) and keep ordinary query scopes narrow.
263
+
264
+ ### Recovering relevant code under a small context budget
265
+
266
+ Check the installed schemas before requesting `evidence: 'compact'` on retrieval
267
+ or lookup, or `evidence: 'outline'` for API orientation. These modes preserve
268
+ complete selected spans and explicitly report omissions. An outline is not proof
269
+ of implementation behavior. Follow the returned typed `full_evidence` call when
270
+ you need full source; its SHA guard refuses changed source instead of validating
271
+ a different publication accidentally. Published-unit line/byte ranges can include
272
+ synthesized or commented concern source and are not physical file coordinates.
273
+ See the [evidence contract](RETRIEVAL_GUIDE.md#compact-published-evidence-and-api-outlines).
274
+
275
+ ### Optional hook hints
276
+
277
+ When explicitly enabled on a supporting installed gem, Claude hook context offers
278
+ a small served-generation orientation and post-edit candidate dependents. Treat
279
+ pre-refresh, unknown freshness, truncation and ambiguous identity labels as limits
280
+ on the evidence. Verify direct and inferred downstream candidates using typed
281
+ lookup and `dependents explain:true`; suggested tests do not prove coverage.
282
+ Silence does not establish no impact. See [bounded context hints](WATCH_DAEMON.md#optional-bounded-context-hints)
283
+ for opt-in, independent refresh controls, limits and repeat suppression.
data/docs/AGENT_SETUP.md CHANGED
@@ -46,7 +46,15 @@ Do not infer permission to configure Console MCP from a request to “set up Woo
46
46
 
47
47
  ## 3. Install on a branch
48
48
 
49
- Create or switch to the branch requested by the repository owner. Add only the development dependency:
49
+ Create or switch to the branch requested by the repository owner. Select the
50
+ published version using the [installation guide](GETTING_STARTED.md#1-install-the-gem).
51
+ Before stable 2.x is published, use the exact published prerelease constraint
52
+ from the README release table; `~> 2.0` will not select a beta or release candidate.
53
+ Use the selected version's tag documentation and verify its capabilities before
54
+ configuring features described on `main`.
55
+
56
+ Add only the development dependency. The following constraint applies **after a
57
+ stable 2.x release is published**:
50
58
 
51
59
  ```ruby
52
60
  # Gemfile
@@ -120,6 +128,77 @@ For Docker, extraction runs inside the Rails container. If Woods is installed on
120
128
 
121
129
  Reconnect the client and call `woods_status`. Confirm a current generation and non-zero unit counts before claiming setup works.
122
130
 
131
+ ### Managed Claude Code configuration
132
+
133
+ The development command `woods-agent-config` is unreleased after 2.0.0.beta2.
134
+ Check `bundle exec woods-agent-config --help` in the selected application bundle;
135
+ use the manual client configuration below when it is absent. The supported
136
+ client format is Claude Code (tested with 2.1.267).
137
+
138
+ Create a private plan, inspect its paths and diff, then apply that same plan:
139
+
140
+ ```bash
141
+ bundle exec woods-agent-config setup --client claude --scope project \
142
+ --root "$PWD" --instructions CLAUDE.md,AGENTS.md --plan /tmp/woods-setup.json --diff
143
+ bundle exec woods-agent-config apply /tmp/woods-setup.json \
144
+ --client claude --scope project --root "$PWD"
145
+ ```
146
+
147
+ Choose a new, unused plan filename for each preview. Preview writes only the
148
+ requested plan file; it does not edit managed configuration. Plans contain the
149
+ complete replacement bytes, including unrelated settings, and use mode 0600:
150
+ keep them private and remove them when no longer needed. `show FILE` prints its
151
+ summary; `show FILE --diff` checks the original snapshots and prints a unified
152
+ diff. Applying a changed snapshot fails rather than replacing the new content.
153
+ A repeated identical setup makes no configuration edits.
154
+
155
+ | Selection | Managed files |
156
+ |---|---|
157
+ | `--scope project` | `<root>/.mcp.json`, explicitly selected `<root>/CLAUDE.md` and/or `AGENTS.md`, `<root>/.woods-agent-config.json` ownership receipt |
158
+ | `--scope user` | `~/.claude.json`, explicitly selected `~/.claude/CLAUDE.md`, application-specific receipt in `~/.claude/` |
159
+
160
+ With `CLAUDE_CONFIG_DIR`, user scope uses that directory's `.claude.json`,
161
+ `CLAUDE.md`, and receipt instead. Instruction edits are opt-in with
162
+ `--instructions`; existing selections carry forward on update. The command
163
+ configures the Index Server. Client trust and project approval remain Claude
164
+ Code settings; apply does not change them.
165
+
166
+ Preflight runs the selected installed bundle, validates its index, and checks
167
+ its actual registered capabilities. It does not boot Rails or contact an
168
+ embedding provider. The bundle must already resolve in frozen mode; prepare
169
+ its lockfile separately if Bundler reports a mismatch. Host mode uses the
170
+ application's absolute Gemfile and index paths. `--index tmp/woods` is relative
171
+ to the selected root. For Compose, also select `--mode compose --service web
172
+ --container-root /app`; run the configuration command where Docker Compose can
173
+ access that project. Preflight verifies the index and installed gem inside that
174
+ service. Both host and container subprocesses have time limits.
175
+
176
+ Use `update --plan FILE` with the same client/scope/root and the desired launch
177
+ options to change the owned entry or instruction selection. Update explicitly
178
+ records the current template and installed-gem evidence; background hooks never
179
+ update configuration. `remove --plan FILE` previews deletion of owned content
180
+ and does not require the application bundle or index to remain available.
181
+ Use `--name NAME` consistently if the installation uses a nondefault server name.
182
+ Apply each operation's saved plan with the same explicit client/scope/root.
183
+
184
+ Ownership comes from the receipt and exact managed section, not from a server
185
+ named `woods`. Existing unowned names, edited managed content, malformed JSON,
186
+ duplicate markers, symlinks, and concurrent edits cause conflicts. Preserve the
187
+ receipt for future update/removal. Unrelated servers, hooks, settings,
188
+ instruction text, permissions, and line-ending conventions are retained;
189
+ changing JSON may reformat its whitespace.
190
+
191
+ Writes use atomic replacement per file and a private recovery journal beside
192
+ the receipt. The plan summary names the `.lock` and `.pending` runtime paths;
193
+ a lock file may remain after completion. Multiple files are not one atomic
194
+ transaction. An ordinary write failure restores original files when safe; an
195
+ interruption or concurrent edit can retain the journal. Resolve reported
196
+ conflicts, then use `recover --client claude --scope project --root "$PWD"`
197
+ (or the original user scope). Recovery refuses to overwrite concurrent edits.
198
+ Keep journals private because they contain original configuration bytes. A plan
199
+ whose recovery journal would exceed 8 MiB is refused before any managed file
200
+ is changed; reduce the selected configuration before applying.
201
+
123
202
  ## 7. Verify useful behavior
124
203
 
125
204
  Use a class known to exist in the application:
@@ -199,6 +278,8 @@ Never report a capability as enabled solely because its schema exists in source.
199
278
 
200
279
  ## Related guides
201
280
 
281
+ - [Edit client adapters](CLIENT_HOOKS.md) for separately opt-in Claude/OpenCode edit hooks; MCP setup does not enable them.
282
+
202
283
  - [Getting started](GETTING_STARTED.md) for the human walkthrough.
203
284
  - [MCP servers](MCP_SERVERS.md) for client-specific configuration and server boundaries.
204
285
  - [Upgrade to Woods 2.0](UPGRADING_TO_2.md) for an existing 1.x installation.
@@ -271,6 +271,12 @@ contract tests; it does not represent semantic quality. Other values raise
271
271
 
272
272
  `build_metadata_store` accepts `:in_memory` and `:sqlite`. Nothing else is implemented.
273
273
 
274
+ Both adapters search Boolean fields as the JSON words `true` and `false`,
275
+ with case-insensitive substring matching. Numeric values `1` and `0` remain
276
+ separate from Booleans. Strings are searched without JSON quotes; objects and
277
+ arrays use JSON text. Null or absent fields never match a field-scoped query.
278
+ Whole-record search (`fields: nil`) searches serialized JSON, including keys.
279
+
274
280
  ### SQLite
275
281
 
276
282
  **Best for:** Local development, zero-dependency setups, testing, and every shipped preset except pure in-memory.
@@ -284,6 +290,11 @@ contract tests; it does not represent semantic quality. Other values raise
284
290
  - Single writer at a time
285
291
  - No network access
286
292
 
293
+ Metadata search uses literal, ASCII-case-insensitive substring matching. Selected
294
+ string fields include embedded NUL characters in the searchable text. With no
295
+ field selection, search operates on serialized JSON, where NUL is represented
296
+ as `\u0000`; a literal NUL query therefore does not match that escaped text.
297
+
287
298
  ### In-memory
288
299
 
289
300
  **Best for:** Testing, evaluation, small codebases.
@@ -318,6 +329,15 @@ A recursive-CTE graph store (MySQL 8.0+ or PostgreSQL, storing edges in a table
318
329
 
319
330
  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
331
 
332
+ Both Ruby helpers hold the same heartbeat-maintained extraction lock as the
333
+ tasks and watch daemon. `WOODS_LOCK_WAIT` controls the wait (600 seconds by
334
+ default); timeout raises `Woods::Coordination::LockError`. Failed generation
335
+ publication raises `Woods::ExtractionError`, allowing job retries instead of
336
+ reporting unpublished work as success. The low-level `Woods::Extractor` remains
337
+ an orchestration building block: callers using it directly own locking and
338
+ publication-failure handling. Do not wrap the public helpers in a second Woods
339
+ extraction lock.
340
+
321
341
  ### Sidekiq
322
342
 
323
343
  ```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.