woods 1.6.1 → 2.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2035 -0
  3. data/CONTRIBUTING.md +253 -87
  4. data/README.md +161 -513
  5. data/SECURITY.md +92 -0
  6. data/assets/woods-wordmark-white-with-bg.png +0 -0
  7. data/docs/AGENT_GUIDE.md +204 -0
  8. data/docs/AGENT_SETUP.md +205 -0
  9. data/docs/BACKEND_MATRIX.md +470 -0
  10. data/docs/CONFIGURATION_REFERENCE.md +655 -0
  11. data/docs/CONSOLE_MCP_SETUP.md +829 -0
  12. data/docs/DOCKER_SETUP.md +454 -0
  13. data/docs/EMBEDDING_MODELS.md +136 -0
  14. data/docs/EVALUATION.md +91 -0
  15. data/docs/EXTRACTOR_REFERENCE.md +765 -0
  16. data/docs/FAQ.md +544 -0
  17. data/docs/GETTING_STARTED.md +183 -0
  18. data/docs/INCREMENTAL_EXTRACTION.md +455 -0
  19. data/docs/INTERNALS.md +418 -0
  20. data/docs/MCP_HTTP_TRANSPORT.md +144 -0
  21. data/docs/MCP_SERVERS.md +231 -0
  22. data/docs/MCP_TOOL_COOKBOOK.md +987 -0
  23. data/docs/MCP_WORKTREE_SETUP.md +127 -0
  24. data/docs/NOTION_INTEGRATION.md +283 -0
  25. data/docs/OBSIDIAN_INTEGRATION.md +170 -0
  26. data/docs/PUBLISHED_INDEX.md +213 -0
  27. data/docs/README.md +94 -0
  28. data/docs/RETRIEVAL_GUIDE.md +267 -0
  29. data/docs/TOKEN_BENCHMARK.md +68 -0
  30. data/docs/TROUBLESHOOTING.md +841 -0
  31. data/docs/UNBLOCKED_INTEGRATION.md +279 -0
  32. data/docs/UPGRADING_TO_2.md +321 -0
  33. data/docs/WATCH_DAEMON.md +667 -0
  34. data/docs/WHY_WOODS.md +219 -0
  35. data/exe/woods-console +40 -4
  36. data/exe/woods-console-mcp +21 -35
  37. data/exe/woods-mcp +20 -7
  38. data/exe/woods-mcp-http +80 -11
  39. data/exe/woods-mcp-start +57 -52
  40. data/lib/generators/woods/install_generator.rb +6 -5
  41. data/lib/generators/woods/pgvector_generator.rb +6 -3
  42. data/lib/generators/woods/templates/add_pgvector_to_woods.rb.erb +29 -9
  43. data/lib/generators/woods/templates/create_woods_tables.rb.erb +5 -1
  44. data/lib/generators/woods/templates/woods.rb.tt +49 -28
  45. data/lib/tasks/woods.rake +622 -168
  46. data/lib/tasks/woods_checks.rake +107 -0
  47. data/lib/tasks/woods_evaluation.rake +164 -80
  48. data/lib/woods/ast/call_site_extractor.rb +6 -15
  49. data/lib/woods/ast/method_extractor.rb +19 -9
  50. data/lib/woods/ast/parser.rb +54 -8
  51. data/lib/woods/atomic_file.rb +171 -2
  52. data/lib/woods/builder.rb +310 -22
  53. data/lib/woods/cache/cache_middleware.rb +7 -2
  54. data/lib/woods/cache/cache_store.rb +9 -1
  55. data/lib/woods/cache/solid_cache_store.rb +6 -4
  56. data/lib/woods/change_set.rb +88 -0
  57. data/lib/woods/checks/generation_resolution.rb +34 -0
  58. data/lib/woods/checks/moved_messages.rb +186 -0
  59. data/lib/woods/chunking/semantic_chunker.rb +160 -18
  60. data/lib/woods/console/audit_logger.rb +12 -3
  61. data/lib/woods/console/bridge_protocol.rb +3 -16
  62. data/lib/woods/console/connection_manager.rb +51 -136
  63. data/lib/woods/console/dispatch_pipeline.rb +42 -12
  64. data/lib/woods/console/embedded_executor.rb +806 -149
  65. data/lib/woods/console/eval_guard.rb +27 -20
  66. data/lib/woods/console/input_contract.rb +78 -0
  67. data/lib/woods/console/model_validator.rb +29 -1
  68. data/lib/woods/console/rack_middleware.rb +65 -42
  69. data/lib/woods/console/redactor.rb +26 -8
  70. data/lib/woods/console/safe_context.rb +58 -10
  71. data/lib/woods/console/scope_predicate_parser.rb +41 -0
  72. data/lib/woods/console/server.rb +119 -247
  73. data/lib/woods/console/sql_noise_stripper.rb +125 -16
  74. data/lib/woods/console/sql_table_scanner.rb +82 -22
  75. data/lib/woods/console/sql_validator.rb +459 -29
  76. data/lib/woods/console/table_gate.rb +2 -2
  77. data/lib/woods/console/tool_specs.rb +463 -90
  78. data/lib/woods/console/tools/tier1.rb +1 -5
  79. data/lib/woods/console/tools/tier4.rb +18 -9
  80. data/lib/woods/coordination/lock_heartbeat.rb +103 -0
  81. data/lib/woods/coordination/pipeline_lock.rb +263 -53
  82. data/lib/woods/db/migrations/007_typed_snapshot_units.rb +45 -0
  83. data/lib/woods/db/migrator.rb +3 -9
  84. data/lib/woods/db/schema_version.rb +47 -2
  85. data/lib/woods/dependency_graph.rb +898 -64
  86. data/lib/woods/embedding/fake.rb +138 -0
  87. data/lib/woods/embedding/indexer.rb +832 -40
  88. data/lib/woods/embedding/openai.rb +77 -19
  89. data/lib/woods/embedding/provider.rb +189 -11
  90. data/lib/woods/embedding/text_preparer.rb +1 -1
  91. data/lib/woods/embedding/token_counter.rb +0 -7
  92. data/lib/woods/evaluation/ablation_agent_payload.rb +38 -0
  93. data/lib/woods/evaluation/ablation_executor.rb +67 -0
  94. data/lib/woods/evaluation/ablation_provenance.rb +38 -0
  95. data/lib/woods/evaluation/ablation_report_writer.rb +43 -0
  96. data/lib/woods/evaluation/ablation_runner.rb +173 -0
  97. data/lib/woods/evaluation/ablation_summary.rb +65 -0
  98. data/lib/woods/evaluation/ablation_task.rb +66 -0
  99. data/lib/woods/evaluation/ablation_task_set.rb +77 -0
  100. data/lib/woods/evaluation/ablation_timed_executor.rb +91 -0
  101. data/lib/woods/evaluation/ablation_worktree.rb +71 -0
  102. data/lib/woods/evaluation/baseline.rb +60 -0
  103. data/lib/woods/evaluation/baseline_runner.rb +11 -3
  104. data/lib/woods/evaluation/evaluator.rb +41 -8
  105. data/lib/woods/evaluation/query_set.rb +79 -13
  106. data/lib/woods/evaluation/report_generator.rb +20 -1
  107. data/lib/woods/export/unit_facts.rb +0 -11
  108. data/lib/woods/extracted_unit.rb +22 -63
  109. data/lib/woods/extractor.rb +2783 -238
  110. data/lib/woods/extractors/action_cable_extractor.rb +9 -4
  111. data/lib/woods/extractors/ast_source_extraction.rb +20 -2
  112. data/lib/woods/extractors/caching_extractor.rb +46 -12
  113. data/lib/woods/extractors/callback_analyzer.rb +39 -9
  114. data/lib/woods/extractors/component_discovery.rb +123 -0
  115. data/lib/woods/extractors/concern_extractor.rb +17 -3
  116. data/lib/woods/extractors/controller_extractor.rb +389 -29
  117. data/lib/woods/extractors/decorator_extractor.rb +7 -14
  118. data/lib/woods/extractors/engine_extractor.rb +53 -8
  119. data/lib/woods/extractors/event_extractor.rb +55 -4
  120. data/lib/woods/extractors/factory_extractor.rb +49 -11
  121. data/lib/woods/extractors/graphql_extractor.rb +162 -66
  122. data/lib/woods/extractors/i18n_extractor.rb +6 -1
  123. data/lib/woods/extractors/job_extractor.rb +51 -21
  124. data/lib/woods/extractors/lib_extractor.rb +23 -17
  125. data/lib/woods/extractors/line_neutralizer.rb +171 -0
  126. data/lib/woods/extractors/mailer_extractor.rb +9 -1
  127. data/lib/woods/extractors/manager_extractor.rb +19 -2
  128. data/lib/woods/extractors/migration_extractor.rb +22 -11
  129. data/lib/woods/extractors/model_extractor.rb +292 -57
  130. data/lib/woods/extractors/package_extractor.rb +154 -0
  131. data/lib/woods/extractors/phlex_extractor.rb +18 -3
  132. data/lib/woods/extractors/policy_extractor.rb +6 -5
  133. data/lib/woods/extractors/poro_extractor.rb +13 -14
  134. data/lib/woods/extractors/pundit_extractor.rb +3 -3
  135. data/lib/woods/extractors/rails_source_extractor.rb +24 -7
  136. data/lib/woods/extractors/rake_task_extractor.rb +158 -30
  137. data/lib/woods/extractors/reference_patterns.rb +38 -0
  138. data/lib/woods/extractors/route_extractor.rb +58 -2
  139. data/lib/woods/extractors/scheduled_job_extractor.rb +51 -35
  140. data/lib/woods/extractors/serializer_extractor.rb +3 -4
  141. data/lib/woods/extractors/service_extractor.rb +11 -1
  142. data/lib/woods/extractors/shared_dependency_scanner.rb +24 -34
  143. data/lib/woods/extractors/shared_utility_methods.rb +36 -6
  144. data/lib/woods/extractors/source_nesting.rb +560 -0
  145. data/lib/woods/extractors/state_machine_extractor.rb +30 -18
  146. data/lib/woods/extractors/test_mapping_extractor.rb +26 -9
  147. data/lib/woods/extractors/view_component_extractor.rb +28 -3
  148. data/lib/woods/extractors/view_engines/erb.rb +17 -3
  149. data/lib/woods/feedback/gap_detector.rb +9 -3
  150. data/lib/woods/feedback/store.rb +7 -1
  151. data/lib/woods/filename_utils.rb +29 -1
  152. data/lib/woods/flow_analysis/operation_extractor.rb +22 -10
  153. data/lib/woods/flow_assembler.rb +147 -26
  154. data/lib/woods/flow_document.rb +1 -0
  155. data/lib/woods/flow_precomputer.rb +175 -22
  156. data/lib/woods/gem_mapper.rb +285 -0
  157. data/lib/woods/generation.rb +185 -0
  158. data/lib/woods/git_command.rb +38 -0
  159. data/lib/woods/git_provenance.rb +16 -2
  160. data/lib/woods/graph_analyzer.rb +564 -87
  161. data/lib/woods/index_artifact.rb +93 -23
  162. data/lib/woods/mcp/bearer_auth.rb +102 -13
  163. data/lib/woods/mcp/bootstrap_state.rb +77 -0
  164. data/lib/woods/mcp/bootstrapper.rb +582 -77
  165. data/lib/woods/mcp/config_resolver.rb +66 -6
  166. data/lib/woods/mcp/errors.rb +60 -0
  167. data/lib/woods/mcp/index_reader.rb +836 -117
  168. data/lib/woods/mcp/index_reader_pinning.rb +78 -0
  169. data/lib/woods/mcp/origin_guard.rb +66 -7
  170. data/lib/woods/mcp/protocol_policy.rb +98 -0
  171. data/lib/woods/mcp/provider_probe.rb +45 -6
  172. data/lib/woods/mcp/renderers/markdown_renderer.rb +72 -4
  173. data/lib/woods/mcp/renderers/plain_renderer.rb +54 -6
  174. data/lib/woods/mcp/server.rb +898 -152
  175. data/lib/woods/mcp/tasks/extension.rb +196 -0
  176. data/lib/woods/mcp/tasks/request_capture.rb +45 -0
  177. data/lib/woods/mcp/tasks/store.rb +518 -0
  178. data/lib/woods/mcp/tool_contract.rb +171 -0
  179. data/lib/woods/mcp/tool_response_renderer.rb +7 -0
  180. data/lib/woods/model_name_cache.rb +19 -1
  181. data/lib/woods/notion/client.rb +132 -36
  182. data/lib/woods/notion/exporter.rb +456 -61
  183. data/lib/woods/notion/mappers/column_mapper.rb +34 -5
  184. data/lib/woods/notion/mappers/migration_mapper.rb +32 -8
  185. data/lib/woods/notion/mappers/model_mapper.rb +21 -6
  186. data/lib/woods/notion/mappers/shared.rb +45 -3
  187. data/lib/woods/notion/sync_manifest.rb +258 -0
  188. data/lib/woods/obsidian/errors.rb +6 -0
  189. data/lib/woods/obsidian/name_mapper.rb +40 -24
  190. data/lib/woods/obsidian/vault_exporter.rb +103 -36
  191. data/lib/woods/operator/pipeline_guard.rb +118 -21
  192. data/lib/woods/operator/status_reporter.rb +20 -3
  193. data/lib/woods/path_dispatcher.rb +276 -0
  194. data/lib/woods/payload_store.rb +236 -0
  195. data/lib/woods/published_index/edge_shaper.rb +61 -0
  196. data/lib/woods/published_index/generation_catalog.rb +72 -0
  197. data/lib/woods/published_index/typed_unit_reader.rb +48 -0
  198. data/lib/woods/published_index.rb +287 -0
  199. data/lib/woods/railtie.rb +69 -30
  200. data/lib/woods/railtie_support.rb +167 -0
  201. data/lib/woods/release.rb +12 -0
  202. data/lib/woods/reload_policy.rb +206 -0
  203. data/lib/woods/resilience/circuit_breaker.rb +47 -8
  204. data/lib/woods/resilience/index_validator.rb +296 -10
  205. data/lib/woods/resilience/retryable_provider.rb +71 -6
  206. data/lib/woods/resolved_config.rb +55 -11
  207. data/lib/woods/retrieval/context_assembler.rb +132 -40
  208. data/lib/woods/retrieval/query_classifier.rb +26 -8
  209. data/lib/woods/retrieval/ranker.rb +193 -28
  210. data/lib/woods/retrieval/search_executor.rb +206 -39
  211. data/lib/woods/retriever.rb +317 -71
  212. data/lib/woods/retry_after.rb +22 -2
  213. data/lib/woods/ruby_analyzer/class_analyzer.rb +10 -14
  214. data/lib/woods/ruby_analyzer/fqn_builder.rb +2 -0
  215. data/lib/woods/ruby_analyzer/mermaid_renderer.rb +14 -4
  216. data/lib/woods/ruby_analyzer/method_analyzer.rb +1 -1
  217. data/lib/woods/ruby_analyzer/trace_enricher.rb +3 -0
  218. data/lib/woods/ruby_analyzer.rb +21 -5
  219. data/lib/woods/session_tracer/file_store.rb +138 -19
  220. data/lib/woods/session_tracer/middleware.rb +1 -2
  221. data/lib/woods/session_tracer/redis_store.rb +122 -12
  222. data/lib/woods/session_tracer/session_flow_assembler.rb +57 -17
  223. data/lib/woods/session_tracer/session_flow_document.rb +56 -14
  224. data/lib/woods/session_tracer/solid_cache_coordination.rb +192 -0
  225. data/lib/woods/session_tracer/solid_cache_store.rb +560 -91
  226. data/lib/woods/session_tracer/store.rb +14 -1
  227. data/lib/woods/storage/metadata_store.rb +230 -26
  228. data/lib/woods/storage/pgvector.rb +180 -22
  229. data/lib/woods/storage/qdrant.rb +367 -41
  230. data/lib/woods/storage/snapshotter/metadata.rb +79 -16
  231. data/lib/woods/storage/snapshotter/vector.rb +128 -17
  232. data/lib/woods/storage/snapshotter.rb +23 -5
  233. data/lib/woods/storage/vector_store.rb +49 -8
  234. data/lib/woods/storage_identity.rb +28 -0
  235. data/lib/woods/tasks.rb +53 -2
  236. data/lib/woods/temporal/json_snapshot_store.rb +112 -42
  237. data/lib/woods/temporal/snapshot_store.rb +139 -42
  238. data/lib/woods/unblocked/client.rb +119 -17
  239. data/lib/woods/unblocked/document_builder.rb +34 -2
  240. data/lib/woods/unblocked/exporter.rb +63 -27
  241. data/lib/woods/unblocked/rate_limiter.rb +23 -9
  242. data/lib/woods/unblocked/sync_manifest.rb +16 -8
  243. data/lib/woods/update_check.rb +24 -1
  244. data/lib/woods/util/uuid5.rb +124 -0
  245. data/lib/woods/version.rb +1 -1
  246. data/lib/woods/watch/daemon.rb +1345 -0
  247. data/lib/woods/watch/listen_watcher.rb +81 -0
  248. data/lib/woods/watch/polling_watcher.rb +137 -0
  249. data/lib/woods/watch/status.rb +169 -0
  250. data/lib/woods/watch/tree_scan.rb +163 -0
  251. data/lib/woods/watch/watcher.rb +100 -0
  252. data/lib/woods.rb +138 -9
  253. data/plugin/.claude-plugin/plugin.json +18 -0
  254. data/plugin/hooks/hooks.json +29 -0
  255. data/plugin/hooks/woods-post-edit.sh +226 -0
  256. data/plugin/hooks/woods-session-start.sh +77 -0
  257. data/plugin/skills/woods-agent-enable/SKILL.md +51 -0
  258. data/plugin/skills/woods-diagnose/SKILL.md +75 -0
  259. data/plugin/skills/woods-investigate/SKILL.md +39 -0
  260. data/plugin/skills/woods-mcp-config/SKILL.md +101 -0
  261. data/plugin/skills/woods-setup/SKILL.md +99 -0
  262. metadata +134 -23
  263. data/lib/woods/console/adapters/cache_adapter.rb +0 -58
  264. data/lib/woods/console/adapters/good_job_adapter.rb +0 -33
  265. data/lib/woods/console/adapters/job_adapter.rb +0 -74
  266. data/lib/woods/console/adapters/sidekiq_adapter.rb +0 -33
  267. data/lib/woods/console/adapters/solid_queue_adapter.rb +0 -33
  268. data/lib/woods/console/bridge.rb +0 -210
  269. data/lib/woods/formatting/claude_adapter.rb +0 -98
  270. data/lib/woods/formatting/generic_adapter.rb +0 -56
  271. data/lib/woods/formatting/gpt_adapter.rb +0 -64
  272. data/lib/woods/notion/mapper.rb +0 -40
  273. data/lib/woods/observability/health_check.rb +0 -79
  274. data/lib/woods/observability/instrumentation.rb +0 -34
data/CONTRIBUTING.md CHANGED
@@ -1,127 +1,293 @@
1
1
  # Contributing to Woods
2
2
 
3
- Thank you for your interest in contributing to Woods!
3
+ <!-- release-state:contributing-intro -->
4
+ Woods welcomes bug fixes, extractor coverage, storage and retrieval improvements, MCP compatibility work, documentation, and focused performance changes. This guide covers the shared contribution contract. Coding agents working from a source checkout should also read the repository's [AGENTS.md](https://github.com/lost-in-the/woods/blob/v2.0.0.beta2/AGENTS.md).
5
+ <!-- release-state:end -->
4
6
 
5
- ## Bug Reports
7
+ ## Choose the right channel
6
8
 
7
- Please open an issue on GitHub with:
9
+ - **Bug:** open an issue with reproduction steps, expected and actual behavior, Woods/Ruby/Rails versions, database adapter, and the smallest useful log or stack trace.
10
+ - **Feature:** describe the user problem, intended outcome, alternatives considered, and affected extraction/MCP/storage surfaces.
11
+ - **Security issue:** do not open a public issue. Follow [SECURITY.md](SECURITY.md).
12
+ - **Question or documentation gap:** open an issue and point to the page or workflow that was unclear.
8
13
 
9
- - A clear description of the bug
10
- - Steps to reproduce
11
- - Expected vs. actual behavior
12
- - Your Ruby version, Rails version, and database adapter
14
+ Search existing issues and pull requests first. A minimal reproduction in a small Rails app is more useful than a large application dump; never attach secrets or production data.
13
15
 
14
- ## Feature Requests
16
+ ## Development setup
15
17
 
16
- Open an issue describing:
18
+ Prerequisites are Git, Ruby 3.0 or later, and a Bundler version compatible with that Ruby. The repository tests several Ruby versions and intentionally does not pin one local version; select a supported Ruby with your normal version manager, then confirm `ruby --version` and `bundle --version`. `bin/setup` installs the bundle but does not install or select Ruby.
17
19
 
18
- - The problem you're trying to solve
19
- - Your proposed solution
20
- - Any alternatives you've considered
21
-
22
- ## Pull Requests
20
+ ```bash
21
+ git clone https://github.com/lost-in-the/woods.git
22
+ cd woods
23
+ bin/setup
24
+ bin/rake spec
25
+ bin/rubocop
26
+ ```
23
27
 
24
- 1. Fork the repo and create your branch from `main`
25
- 2. Install dependencies: `bin/setup`
26
- 3. Make your changes
27
- 4. Add tests for new functionality
28
- 5. Ensure the test suite passes: `bundle exec rake spec`
29
- 6. Ensure code style passes: `bundle exec rubocop`
30
- 7. Update CHANGELOG.md with your changes
31
- 8. Complete the **Pre-PR requirements** below
32
- 9. Open a pull request
28
+ `Gemfile.lock` is gitignored, so a fresh worktree (as opposed to a clone) needs it copied in from an existing checkout before running any `bin/*` command.
33
29
 
34
- ### Pre-PR requirements
30
+ Create a branch from current `main`. Keep each pull request to one logical change and preserve unrelated formatting and refactors for separate work.
35
31
 
36
- These are hard gates a PR that fails either is incomplete:
32
+ `main` is the development branch: it holds work for the next release and can run ahead of the latest published gem. Releases are cut from version tags by the guarded workflow in the [release section below](#release-flow); documentation matching a published gem lives on that release's tag.
37
33
 
38
- 1. **Documentation must be current.** Any doc affected by the change — README, `docs/`, the
39
- `plugin/skills/` user guides, `CHANGELOG.md` — must be updated in the *same* PR. Don't ship
40
- behavior the docs still describe the old way.
41
- 2. **Investigate plugin-functionality impact.** If the change touches anything the distributed
42
- user skills rely on — a rake task, MCP tool or its arguments, an executable (`woods-mcp`,
43
- `woods-mcp-start`, `woods-console-mcp`, `woods-mcp-http`), a config key, or setup steps —
44
- investigate whether `plugin/skills/{woods-setup,woods-mcp-config,woods-diagnose}` need to
45
- change.
34
+ ## Understand the repository
46
35
 
47
- ### Claude Code plugin changes
36
+ | Path | Responsibility |
37
+ |---|---|
38
+ | `lib/woods/extractor.rb`, `lib/woods/extractors/` | Runtime Rails extraction pipeline and extractors |
39
+ | `lib/woods/mcp/` | Read-only Index MCP server and protocol behavior |
40
+ | `lib/woods/console/` | Live Rails Console MCP and safeguards |
41
+ | `lib/woods/storage/`, `lib/woods/embedding/`, `lib/woods/retrieval/` | Persistence, vectors, and semantic retrieval |
42
+ | `lib/tasks/` | Rails/Rake operational interface |
43
+ | `spec/` | Unit, contract, and opt-in integration specs |
44
+ | `spec/dummy/` | Booted Rails fixture application |
45
+ | `docs/` | User, agent, operational, and reference documentation |
46
+ | `plugin/skills/` | Distributed Woods skills (setup/upgrade, MCP configuration, investigation, agent enablement, diagnosis) |
48
47
 
49
- `plugin/` is distributed as the `woods-plugin` via the
50
- [`lost-in-the/plugins`](https://github.com/lost-in-the/plugins) marketplace (a `git-subdir`
51
- reference to this subtree). Installed users may run an **older** gem than `main`, so:
48
+ <!-- release-state:contributing-architecture -->
49
+ Read [CLAUDE.md](https://github.com/lost-in-the/woods/blob/v2.0.0.beta2/CLAUDE.md) for architecture and implementation gotchas before changing runtime behavior.
50
+ <!-- release-state:end -->
52
51
 
53
- - If a change adds/removes/renames a tool, task, executable, or config key that a skill
54
- documents, **update the skill in the same PR**.
55
- - The skills carry a Version Preflight (operate only against the installed version). **Land the
56
- skill change with the release that ships the capability** — never document a feature in a
57
- skill before the version that provides it is released. Bump `plugin/.claude-plugin/plugin.json`
58
- `version` when the skill content changes.
59
- - If the change requires a new marketplace entry, `ref` pin, or metadata edit, open a **paired
60
- PR against `lost-in-the/plugins`** and link it from this PR.
52
+ ### Agent orientation and static self-map
61
53
 
62
- ## Development Setup
54
+ When investigating Woods itself, agents can create a disposable, MCP-queryable
55
+ map of the gem source before planning a broad change or debugging a cross-cutting
56
+ problem:
63
57
 
64
58
  ```bash
65
- git clone https://github.com/lost-in-the/woods.git
66
- cd woods
67
- bin/setup
68
- bundle exec rake spec # Run tests
69
- bundle exec rubocop # Check style
59
+ output_dir="$(mktemp -d)"
60
+ bin/rake "woods:self_map[$output_dir]"
61
+ bundle exec woods-mcp-start "$output_dir"
70
62
  ```
71
63
 
72
- ## Testing
64
+ This internal developer task publishes an atomic standard index generation.
65
+ Use `woods_status`, `structure`, `search`, `lookup`, `dependencies`, and
66
+ `dependents` to identify ownership and estimate the static blast radius. The
67
+ map is Woods-only, has no embeddings, and must remain out of version control.
68
+ It is not a replacement for booted Rails extraction or evidence of runtime
69
+ Rails behavior; use the normal host-app pipeline for that.
73
70
 
74
- Woods has two test suites:
71
+ ## Make the change
72
+
73
+ 1. Reproduce a bug or define the expected behavior.
74
+ 2. Add or update the smallest test that can fail for the behavior.
75
+ 3. Make the targeted implementation change.
76
+ 4. Run the narrow test, then the relevant broader suite.
77
+ 5. Update the canonical documentation, plugin skill, and changelog when the public contract changes.
78
+ 6. Review the complete diff before opening a pull request.
79
+
80
+ Woods extracts Rails behavior through a booted runtime. Features that depend on routes, Active Record reflections, descendants, or framework internals must use runtime introspection. Unit tests may isolate collaborators, but version-sensitive behavior also needs the booted-app lane.
81
+
82
+ ## Validate in proportion to the change
83
+
84
+ Start with the smallest command that exercises your work:
85
+
86
+ ```bash
87
+ # One spec file
88
+ bin/rspec spec/path/to/spec.rb
89
+
90
+ # Unit/contract suite (booted-app and live-backend lanes excluded)
91
+ bin/rake spec
92
+
93
+ # Style
94
+ bin/rubocop
95
+ ```
75
96
 
76
- - **Gem unit specs** (`spec/`): Run with `bundle exec rake spec`. No Rails boot required.
77
- - **Integration specs**: Run inside a host Rails app to test real extraction.
97
+ Before requesting review, run the full unit suite and style check unless the PR explains why one cannot run.
78
98
 
79
- All new features need tests. Bug fixes should include a regression test.
99
+ Coverage from the default process excludes opt-in Rails, installed-artifact, and live-backend lanes. Report their results separately; a low percentage for subprocess-driven tasks does not establish that they are untested. CI enforces the aggregate line floor and measures branches, but does not enforce a branch floor. Add behavior-based regressions and real optional-gem fixtures for changed extraction paths before proposing higher thresholds.
80
100
 
81
101
  ### Rails version matrix
82
102
 
83
- The gem supports `railties >= 6.0`. Coverage is split across two CI jobs:
103
+ The gem supports Ruby 3.0 or later and Rails 6.0 through 8.x. CI separates fast unit coverage from real Rails boots:
84
104
 
85
- - The base unit `test` job runs `rake spec` across **Ruby 3.0–4.0** on the
86
- default (newest) Rails. The unit specs stub Rails, so they run once on the base
87
- Gemfile rather than per Rails version.
88
- - The `rails-matrix` job runs the **booted-app extraction test**
89
- (`spec/integration/booted_extraction_spec.rb` against `spec/dummy`) under each
90
- supported Rails — 6.0, 6.1, 7.0, 7.1, 7.2, 8.0 — using per-version gemfiles
91
- under `gemfiles/`. This is the version-sensitive gate: it boots a real Rails
92
- app in-process and runs an extraction. (The booted spec is tagged `:booted_app`
93
- and excluded from the default `rake spec`; `WOODS_RUN_BOOTED_APP=1` opts it in,
94
- and it must run in its own process — it can't share one with the unit suite.)
105
+ - the base test job runs unit specs across supported Ruby versions;
106
+ - the `rails-matrix` job boots `spec/dummy` and performs extraction for each supported Rails line using `gemfiles/rails_*.gemfile`.
95
107
 
96
- The Rails pins live in `Appraisals`; the gemfiles are hand-maintained
97
- (`eval_gemfile`-ing the base `Gemfile` and pinning Rails) because Appraisal can't
98
- generate from the conditional base Gemfile. To run a single Rails row locally:
108
+ Run one Rails row locally:
99
109
 
100
110
  ```bash
101
111
  BUNDLE_GEMFILE=gemfiles/rails_7.2.gemfile bundle install
102
112
  WOODS_RUN_BOOTED_APP=1 BUNDLE_GEMFILE=gemfiles/rails_7.2.gemfile \
103
- bundle exec rspec spec/integration/booted_extraction_spec.rb
113
+ bin/rspec spec/integration/booted_extraction_spec.rb
114
+ ```
115
+
116
+ When adding a Rails line, update `Appraisals`, the corresponding hand-maintained gemfile, and `.github/workflows/ci.yml`. For Rails below 7.1, copy an existing 6.x gemfile so its sqlite3 and concurrent-ruby compatibility pins are preserved.
117
+
118
+ ### Live storage and SQL dialects
119
+
120
+ The opt-in `live-backends` lane verifies behavior against PostgreSQL/pgvector and Qdrant that doubles cannot prove, including batch conflicts, delete addressing, filter translation, and extension setup.
121
+
122
+ ```bash
123
+ BUNDLE_GEMFILE=gemfiles/live_backends.gemfile bundle install
124
+ WOODS_RUN_LIVE_BACKENDS=1 BUNDLE_GEMFILE=gemfiles/live_backends.gemfile \
125
+ bin/rspec spec/integration/live_backends_spec.rb spec/integration/console_sql_dialects_spec.rb
126
+ ```
127
+
128
+ The lane expects reachable PostgreSQL/pgvector, MySQL, and Qdrant services. Configure endpoints with `WOODS_PG_URL`, `WOODS_MYSQL_URL`, and `WOODS_QDRANT_URL`. The Console contracts exercise blocked-table enforcement and legitimate SQL on both database dialects. New adapter behavior that depends on a real server belongs in this lane.
129
+
130
+ ## Keep public surfaces synchronized
131
+
132
+ A pull request is incomplete when behavior and user guidance disagree.
133
+
134
+ Update the canonical owner for any changed contract:
135
+
136
+ | Change | Documentation owner |
137
+ |---|---|
138
+ | Install or first run | `docs/GETTING_STARTED.md` |
139
+ | Agent-operated installation | `docs/AGENT_SETUP.md` |
140
+ | Configuration key/default | `docs/CONFIGURATION_REFERENCE.md` |
141
+ | MCP setup or registered tools | `docs/MCP_SERVERS.md` |
142
+ | Agent query workflow | `docs/AGENT_GUIDE.md` |
143
+ | Console security/transport | `docs/CONSOLE_MCP_SETUP.md` |
144
+ | Major-version behavior | `docs/UPGRADING_TO_2.md` |
145
+ | Failure diagnosis | `docs/TROUBLESHOOTING.md` |
146
+
147
+ If a rake task, executable, MCP tool/argument, config key, setup step, or diagnosis path changes, inspect all five distributed skills under `plugin/skills/`. Update affected skills in the same Woods PR and bump `plugin/.claude-plugin/plugin.json` when skill content changes.
148
+
149
+ The plugin is published through the [`lost-in-the/plugins`](https://github.com/lost-in-the/plugins) marketplace as a git-subdir reference. Open and cross-link a paired marketplace PR when compatibility metadata, the entry, or its ref must change. Skills must check the installed Woods version and must not document unreleased capabilities as available.
150
+
151
+ Update `CHANGELOG.md` for user-visible changes. Internal refactors and typo-only documentation fixes normally do not need an entry.
152
+
153
+ ## Pull request evidence
154
+
155
+ Include:
156
+
157
+ - the problem and user-visible outcome;
158
+ - implementation scope and important tradeoffs;
159
+ - exact validation commands and results;
160
+ - Rails/storage lanes run or intentionally not run;
161
+ - public docs and plugin impact;
162
+ - migration, compatibility, security, and rollback notes when applicable;
163
+ - screenshots or transcript excerpts only when they materially verify behavior.
164
+
165
+ Do not use empty assertions or output-only tests. A regression test must fail before the fix and exercise the same runtime path as production behavior.
166
+
167
+ ## Code conventions
168
+
169
+ - Add `# frozen_string_literal: true` to Ruby files.
170
+ - Document public APIs with YARD where it improves their contract.
171
+ - Rescue `StandardError` or a narrower class; never use a bare rescue.
172
+ - Extractors return `Array<Woods::ExtractedUnit>`.
173
+ - Keep MCP stdout free of non-protocol output.
174
+ - Prefer explicit structured errors over suppressing a failure.
175
+
176
+ By contributing, you agree that your contribution is licensed under the [MIT License](LICENSE.txt).
177
+
178
+ ## Release flow
179
+
180
+ `main` is the development branch and never claims a released version. Between releases it carries the alpha development marker. Every release, including a beta or a release candidate, is an explicit commit plus a tag, cut by one rake task and published only by the guarded workflow.
181
+
182
+ | State | `Woods::VERSION` | Tagged | On RubyGems | Documentation links point at |
183
+ |---|---|---|---|---|
184
+ | Development | `X.Y.Z.alpha` | never | never | `main` |
185
+ | Beta | `X.Y.Z.betaN` | `vX.Y.Z.betaN` | prerelease | the tag |
186
+ | Release candidate | `X.Y.Z.rcN` | `vX.Y.Z.rcN` | prerelease | the tag |
187
+ | Release | `X.Y.Z` | `vX.Y.Z` | stable | the tag |
188
+
189
+ RubyGems treats any letter in a version as a prerelease, so a `~> 1.6` or `~> 2.0` constraint never resolves a beta or a release candidate. Adopting one is explicit: `gem "woods", "2.0.0.beta1"`.
190
+
191
+ `spec/release_v2/version_state_spec.rb` enforces this table on every commit. VERSION is either an alpha or the changelog carries its dated heading, and the four `release-state` documentation fences match the state VERSION declares.
192
+
193
+ ### During feature work
194
+
195
+ - Do not edit `lib/woods/version.rb` by hand.
196
+ - Put changelog entries under `## [Unreleased]` only, beneath one of its `###` headings. Duplicate headings are merged at release time, in the order they first appear.
197
+ - Leave the `release-state` fences alone. `release:prepare` rewrites them.
198
+
199
+ ### Preparing a release
200
+
201
+ One command per transition. It never commits, tags, pushes, or publishes.
202
+
203
+ | Transition | Command |
204
+ |---|---|
205
+ | Alpha to the first beta | `bin/rake "release:prepare[2.0.0.beta1]"` |
206
+ | Beta to the next beta or a release candidate | `bin/rake "release:prepare[2.0.0.rc1]"` |
207
+ | Release candidate to the release | `bin/rake "release:prepare[2.0.0]"` |
208
+ | After the release publishes, reopen development | `bin/rake "release:reopen[2.1.0.alpha]"` |
209
+
210
+ `release:prepare` refuses a dirty working tree, a version that moves backwards, a version whose base is not the line `main` is developing, and an alpha target. It then bumps VERSION, folds `## [Unreleased]` into `## [<version>] - <date>` with one block per `###` heading, restates the fences, regenerates the surface inventory, and prints the tag and dispatch commands. Every rewrite is computed before any of it is written, so a refusal leaves the working tree untouched.
211
+
212
+ A final release also absorbs every prerelease section of its own base version. Cutting `2.0.0` folds `## [2.0.0.beta1]` and `## [2.0.0.rc1]` into `## [2.0.0] - <date>` and removes their headings, prerelease entries first and anything written after them second, so the notes a user reads for 2.0.0 are the whole story rather than three fragments. An empty `## [Unreleased]` is therefore legitimate for a final release cut straight from a release candidate. A beta or a release candidate has nothing to absorb, so an empty Unreleased section refuses: there is nothing new to publish.
213
+
214
+ Review the diff and run the release contracts:
215
+
216
+ ```bash
217
+ bin/rspec spec/release_v2
218
+ bin/rake release_v2:verify_surface_inventory
219
+ bin/rspec spec/integration/packaged_gem_spec.rb
104
220
  ```
105
221
 
106
- When adding a Rails line: add it to both `Appraisals` and `gemfiles/`, and add a
107
- valid Ruby×Rails pair to the `rails-matrix` job in `.github/workflows/ci.yml`.
108
- **For a row below Rails 7.1**, the gemfile must set `ENV['WOODS_SQLITE3_REQ'] =
109
- '~> 1.4'` *before* `eval_gemfile` (those Rails versions pin `sqlite3 ~> 1.4` in
110
- their adapter at load time) and pin `concurrent-ruby '< 1.3.5'` (1.3.5 dropped
111
- the implicit `require "logger"` those releases rely on under Ruby 3.x) — copy an
112
- existing `gemfiles/rails_6.0.gemfile` as the template.
222
+ Then commit and open a pull request. The release commit lands on `main` through review like any other change.
223
+
224
+ ### After the release commit merges
225
+
226
+ A release is pinned by its tag, never by a branch:
227
+
228
+ | Step | Command | What guards it |
229
+ |---|---|---|
230
+ | Tag the merge commit | `git tag v<version> <merge-sha> && git push origin v<version>` (lightweight or annotated both work) | `script/validate-release` requires the tag to sit on `main` history, match `Woods::VERSION`, match the dated `CHANGELOG.md` heading, and not be an alpha |
231
+ | Trigger the release workflow | `gh api --method POST repos/lost-in-the/woods/dispatches -f event_type=release -F 'client_payload[tag]=v<version>' -F 'client_payload[ci_run_id]=<id>'` where `<id>` is the green CI run on the tagged SHA (requires Contents write) | `.github/workflows/release.yml` re-validates the named CI run through the API, verifies the artifact digest, and runs secret-free candidate package tests before publishing |
232
+ | Verify publication | `gem info woods --remote` shows the new version; for a prerelease, `gem info woods --remote --prerelease`. The README gem badge updates on its own | just before pushing, the workflow re-runs `script/verify-release-tag` so a tag that moved since validation aborts the publish |
233
+
234
+ Nothing is published from a laptop: the workflow builds and pushes the gem from the validated CI artifact, so the bytes on RubyGems are the bytes CI tested. `rake release` and `rake release:rubygem_push`, which `bundler/gem_tasks` installs, are blocked for that reason.
235
+
236
+ After a final release publishes, reopen development with `release:reopen` in a follow-up pull request.
237
+
238
+ ### When a dispatch fails
239
+
240
+ `release-context` and `publish` both check out `github.sha`, the default
241
+ branch's tip at dispatch time, not the tag: `release-context` re-validates the
242
+ named CI run, checks the live `release` environment, and runs
243
+ `script/validate-release`; `publish` runs `script/verify-release-tag` and
244
+ pushes the downloaded artifact. Only `package-test` checks out
245
+ `needs.release-context.outputs.release-sha`, the tag's own commit, because
246
+ that is what CI actually built and tested. The gem bytes `publish` pushes were
247
+ built by CI at `release-sha`; `publish` never rebuilds them.
113
248
 
114
- ## Code Style
249
+ That split decides the fix for a failed dispatch:
250
+
251
+ | What failed | Lives in | Fix |
252
+ |---|---|---|
253
+ | `script/validate-release-run`, `script/validate-release`, `script/verify-release-tag`, or the workflow files themselves | main, read at `github.sha` | merge the fix to main, then re-dispatch at the same tag; the tag never moves |
254
+ | Live `release` environment settings (protection rule, admin bypass) | GitHub environment configuration, not the tree | fix the setting directly; no commit or re-dispatch needed |
255
+ | Anything under `spec/` or `lib/` that `package-test` actually runs against the candidate | the tagged commit, read at `release-sha` | a main-only fix does not reach the candidate; merge it, then move the tag to the new main tip and get a fresh CI run on it |
256
+
257
+ Both failure classes happened in the beta1 dispatch: a `REQUIRED_CI_JOBS`
258
+ prefix left behind by a `ci.yml` job rename was a validator fix that needed
259
+ only a merge and a re-dispatch; two `packaged_gem_spec.rb` smoke failures
260
+ traced to hard-coded `2.0.0` literals needed the tag moved to the commit that
261
+ fixed them, because the candidate job runs the spec file at the tag.
262
+
263
+ **Moving a tag is acceptable only before publication.** Once `publish` has
264
+ pushed the gem to RubyGems, the tag is the permanent, immutable record of what
265
+ was published; move it before that point only, with
266
+ `git tag -f v<version> <new-sha> && git push --force origin v<version>` run by
267
+ the maintainer, followed by a fresh CI run on the new tag SHA before
268
+ re-dispatching.
269
+
270
+ ### Publishing the GitHub Release entry
271
+
272
+ The workflow deliberately creates no GitHub Release: the API cannot bind an
273
+ existing tag to an expected commit atomically, so automating it would race the
274
+ tag's own verification. Once `gem info woods --remote` (or `--remote
275
+ --prerelease`) confirms publication, create the entry by hand:
276
+
277
+ ```bash
278
+ gh release create v<version> --verify-tag --notes-file <file> # release
279
+ gh release create v<version> --verify-tag --prerelease --notes-file <file> # beta or rc
280
+ ```
115
281
 
116
- - `frozen_string_literal: true` on every file
117
- - YARD documentation on public methods
118
- - `rescue StandardError`, never bare `rescue`
119
- - All extractors return `Array<ExtractedUnit>`
282
+ `--verify-tag` refuses if the tag is missing or moved. Write `<file>` as a
283
+ short body that links `CHANGELOG.md` at the tag itself (not at `main`) and
284
+ anchors straight to that version's dated heading, so the note a reader lands
285
+ on always matches the bytes RubyGems published.
120
286
 
121
- ## Runtime Introspection Requirement
287
+ ### Stable branches
122
288
 
123
- Woods uses runtime introspection, not static parsing. If your feature requires access to Rails internals (ActiveRecord reflections, route introspection, etc.), it must run inside a booted Rails environment. Unit tests should use mocks/stubs; integration tests should run in a real Rails app.
289
+ A stable branch is `N-M-stable`, cut from the release tag. Create one only when a released line needs a patch after a newer major has shipped on `main`; until then, `main` is the only branch. There is no stable branch today.
124
290
 
125
- ## License
291
+ ### What coding agents may do
126
292
 
127
- By contributing, you agree that your contributions will be licensed under the MIT License.
293
+ Agents may run `release:prepare` and `release:reopen` when asked, report the commands those tasks print, and prepare the pull request. Agents must not edit `lib/woods/version.rb` or the `release-state` fences by hand, create or push tags, dispatch the release workflow, or run any form of `gem push`. See `.claude/skills/release-flow/SKILL.md`.